Merge branch 'master' into hfm_oven_wip

This commit is contained in:
sabrina-shanman 2019-03-12 16:23:07 -07:00
commit ddc4f5349e
362 changed files with 15669 additions and 2969 deletions

9
.gitignore vendored
View file

@ -99,12 +99,9 @@ tools/jsdoc/package-lock.json
# Python compile artifacts
**/__pycache__
# ignore unneeded unity project files for avatar exporter
tools/unity-avatar-exporter/Library
tools/unity-avatar-exporter/Logs
tools/unity-avatar-exporter/Packages
tools/unity-avatar-exporter/ProjectSettings
tools/unity-avatar-exporter/Temp
# ignore local unity project files for avatar exporter
tools/unity-avatar-exporter
server-console/package-lock.json
vcpkg/
/tools/nitpick/compiledResources

BIN
.vs/slnx.sqlite Normal file

Binary file not shown.

View file

@ -37,8 +37,14 @@ sudo apt-get -y install libpulse0 libnss3 libnspr4 libfontconfig1 libxcursor1 li
Install build tools:
```bash
# For Ubuntu 18.04
sudo apt-get install cmake
```
```bash
# For Ubuntu 16.04
wget https://cmake.org/files/v3.9/cmake-3.9.5-Linux-x86_64.sh
sudo sh cmake-3.9.5-Linux-x86_64.sh --prefix=/usr/local --exclude-subdir
```
Install Python 3:
```bash
@ -61,7 +67,7 @@ git tags
Then checkout last tag with:
```bash
git checkout tags/v0.71.0
git checkout tags/v0.79.0
```
### Compiling

View file

@ -52,6 +52,7 @@ else()
set(MOBILE 0)
endif()
set(HIFI_USE_OPTIMIZED_IK OFF)
set(BUILD_CLIENT_OPTION ON)
set(BUILD_SERVER_OPTION ON)
set(BUILD_TESTS_OPTION OFF)
@ -115,7 +116,7 @@ if (USE_GLES AND (NOT ANDROID))
set(DISABLE_QML_OPTION ON)
endif()
option(HIFI_USE_OPTIMIZED_IK "USE OPTIMIZED IK" ${HIFI_USE_OPTIMIZED_IK_OPTION})
option(BUILD_CLIENT "Build client components" ${BUILD_CLIENT_OPTION})
option(BUILD_SERVER "Build server components" ${BUILD_SERVER_OPTION})
option(BUILD_TESTS "Build tests" ${BUILD_TESTS_OPTION})
@ -146,6 +147,7 @@ foreach(PLATFORM_QT_COMPONENT ${PLATFORM_QT_COMPONENTS})
list(APPEND PLATFORM_QT_LIBRARIES "Qt5::${PLATFORM_QT_COMPONENT}")
endforeach()
MESSAGE(STATUS "USE OPTIMIZED IK: " ${HIFI_USE_OPTIMIZED_IK})
MESSAGE(STATUS "Build server: " ${BUILD_SERVER})
MESSAGE(STATUS "Build client: " ${BUILD_CLIENT})
MESSAGE(STATUS "Build tests: " ${BUILD_TESTS})
@ -191,6 +193,10 @@ find_package( Threads )
add_definitions(-DGLM_FORCE_RADIANS)
add_definitions(-DGLM_ENABLE_EXPERIMENTAL)
add_definitions(-DGLM_FORCE_CTOR_INIT)
if (HIFI_USE_OPTIMIZED_IK)
MESSAGE(STATUS "SET THE USE IK DEFINITION ")
add_definitions(-DHIFI_USE_OPTIMIZED_IK)
endif()
set(HIFI_LIBRARY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libraries")
set(EXTERNAL_PROJECT_PREFIX "project")

View file

@ -36,11 +36,6 @@ apply plugin: 'com.android.application'
android {
compileSdkVersion 26
//buildToolsVersion '27.0.3'
def appVersionCode = Integer.valueOf(VERSION_CODE ?: 1)
def appVersionName = RELEASE_NUMBER ?: "1.0"
defaultConfig {
applicationId "io.highfidelity.hifiinterface"
minSdkVersion 24
@ -66,10 +61,10 @@ android {
}
signingConfigs {
release {
storeFile project.hasProperty("HIFI_ANDROID_KEYSTORE") ? file(HIFI_ANDROID_KEYSTORE) : null
storePassword project.hasProperty("HIFI_ANDROID_KEYSTORE_PASSWORD") ? HIFI_ANDROID_KEYSTORE_PASSWORD : ''
keyAlias project.hasProperty("HIFI_ANDROID_KEY_ALIAS") ? HIFI_ANDROID_KEY_ALIAS : ''
keyPassword project.hasProperty("HIFI_ANDROID_KEY_PASSWORD") ? HIFI_ANDROID_KEY_PASSWORD : ''
storeFile project.hasProperty("HIFI_ANDROID_KEYSTORE") ? file(HIFI_ANDROID_KEYSTORE) : file('../keystore.jks')
storePassword project.hasProperty("HIFI_ANDROID_KEYSTORE_PASSWORD") ? HIFI_ANDROID_KEYSTORE_PASSWORD : 'password'
keyAlias project.hasProperty("HIFI_ANDROID_KEY_ALIAS") ? HIFI_ANDROID_KEY_ALIAS : 'key0'
keyPassword project.hasProperty("HIFI_ANDROID_KEY_PASSWORD") ? HIFI_ANDROID_KEY_PASSWORD : 'password'
}
}
}
@ -90,10 +85,7 @@ android {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig project.hasProperty("HIFI_ANDROID_KEYSTORE") &&
project.hasProperty("HIFI_ANDROID_KEYSTORE_PASSWORD") &&
project.hasProperty("HIFI_ANDROID_KEY_ALIAS") &&
project.hasProperty("HIFI_ANDROID_KEY_PASSWORD")? signingConfigs.release : null
signingConfig signingConfigs.release
buildConfigField "String", "BACKTRACE_URL", "\"" + (System.getenv("CMAKE_BACKTRACE_URL") ? System.getenv("CMAKE_BACKTRACE_URL") : '') + "\""
buildConfigField "String", "BACKTRACE_TOKEN", "\"" + (System.getenv("CMAKE_BACKTRACE_TOKEN") ? System.getenv("CMAKE_BACKTRACE_TOKEN") : '') + "\""
buildConfigField "String", "OAUTH_CLIENT_ID", "\"" + (System.getenv("OAUTH_CLIENT_ID") ? System.getenv("OAUTH_CLIENT_ID") : '') + "\""

BIN
android/apps/keystore.jks Normal file

Binary file not shown.

View file

@ -117,7 +117,8 @@ void RenderThread::setup() {
{ std::unique_lock<std::mutex> lock(_frameLock); }
ovr::VrHandler::initVr();
ovr::VrHandler::setHandler(this);
// Enable KHR_no_error for this context
ovr::VrHandler::setHandler(this, true);
makeCurrent();

View file

@ -18,12 +18,11 @@ task renameHifiACTaskRelease(type: Copy) {
android {
compileSdkVersion 28
defaultConfig {
applicationId "io.highfidelity.questInterface"
minSdkVersion 24
targetSdkVersion 28
versionCode 1
versionCode appVersionCode
versionName appVersionName
ndk { abiFilters 'arm64-v8a' }
externalNativeBuild {
@ -44,10 +43,10 @@ android {
}
signingConfigs {
release {
storeFile project.hasProperty("HIFI_ANDROID_KEYSTORE") ? file(HIFI_ANDROID_KEYSTORE) : null
storePassword project.hasProperty("HIFI_ANDROID_KEYSTORE_PASSWORD") ? HIFI_ANDROID_KEYSTORE_PASSWORD : ''
keyAlias project.hasProperty("HIFI_ANDROID_KEY_ALIAS") ? HIFI_ANDROID_KEY_ALIAS : ''
keyPassword project.hasProperty("HIFI_ANDROID_KEY_PASSWORD") ? HIFI_ANDROID_KEY_PASSWORD : ''
storeFile project.hasProperty("HIFI_ANDROID_KEYSTORE") ? file(HIFI_ANDROID_KEYSTORE) : file('../keystore.jks')
storePassword project.hasProperty("HIFI_ANDROID_KEYSTORE_PASSWORD") ? HIFI_ANDROID_KEYSTORE_PASSWORD : 'password'
keyAlias project.hasProperty("HIFI_ANDROID_KEY_ALIAS") ? HIFI_ANDROID_KEY_ALIAS : 'key0'
keyPassword project.hasProperty("HIFI_ANDROID_KEY_PASSWORD") ? HIFI_ANDROID_KEY_PASSWORD : 'password'
v2SigningEnabled false
}
}
@ -133,12 +132,6 @@ android {
assetList.each { file -> out.println(file) }
}
}
variant.outputs.all {
if (RELEASE_NUMBER != '0') {
outputFileName = "app_" + RELEASE_NUMBER + "_" + RELEASE_TYPE + ".apk"
}
}
}
}

View file

@ -61,7 +61,7 @@ extern "C" {
Java_io_highfidelity_oculus_OculusMobileActivity_nativeInitOculusPlatform(JNIEnv *env, jobject obj){
initOculusPlatform(env, obj);
}
QAndroidJniObject __interfaceActivity;
QAndroidJniObject __interfaceActivity;
JNIEXPORT void JNICALL
Java_io_highfidelity_oculus_OculusMobileActivity_questNativeOnCreate(JNIEnv *env, jobject obj) {
@ -80,6 +80,10 @@ QAndroidJniObject __interfaceActivity;
});
}
JNIEXPORT void JNICALL
Java_io_highfidelity_oculus_OculusMobileActivity_questNativeAwayMode(JNIEnv *env, jobject obj) {
AndroidHelper::instance().toggleAwayMode();
}
JNIEXPORT void Java_io_highfidelity_oculus_OculusMobileActivity_questOnAppAfterLoad(JNIEnv* env, jobject obj) {

View file

@ -5,6 +5,7 @@ import io.highfidelity.oculus.OculusMobileActivity;
import io.highfidelity.utils.HifiUtils;
public class InterfaceActivity extends OculusMobileActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
HifiUtils.upackAssets(getAssets(), getCacheDir().getAbsolutePath());

View file

@ -5,6 +5,7 @@ import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.text.TextUtils;
import io.highfidelity.oculus.OculusMobileActivity;
import io.highfidelity.utils.HifiUtils;
@ -19,9 +20,18 @@ public class PermissionsChecker extends Activity {
Manifest.permission.CAMERA
};
private static final String EXTRA_ARGS = "args";
private String mArgs;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mArgs =(getIntent().getStringExtra(EXTRA_ARGS));
if(!TextUtils.isEmpty(mArgs)) {
System.out.println("Application launched with following args: " + mArgs);
}
requestAppPermissions(REQUIRED_PERMISSIONS,REQUEST_PERMISSIONS);
}
@ -47,7 +57,13 @@ public class PermissionsChecker extends Activity {
}
private void launchActivityWithPermissions() {
startActivity(new Intent(this, InterfaceActivity.class));
Intent intent= new Intent(this, InterfaceActivity.class);
if(!TextUtils.isEmpty(mArgs)) {
intent.putExtra("applicationArguments", mArgs);
}
startActivity(intent);
finish();
}

View file

@ -1,11 +1,35 @@
#!/usr/bin/env bash
set -xeuo pipefail
ANDROID_BUILD_TYPE=release
ANDROID_BUILD_TARGET=assembleRelease
if [[ "$RELEASE_TYPE" == "PR" ]]; then
ANDROID_APK_SUFFIX=PR${RELEASE_NUMBER}-${SHA7}.apk ;
elif [[ "${STABLE_BUILD}" == "1" ]]; then
ANDROID_APK_SUFFIX=${RELEASE_NUMBER}.apk ;
else
ANDROID_APK_SUFFIX=${RELEASE_NUMBER}-${SHA7}.apk ;
fi
# Interface build
ANDROID_APP=interface
ANDROID_OUTPUT_DIR=./apps/${ANDROID_APP}/build/outputs/apk/${ANDROID_BUILD_TYPE}
ANDROID_OUTPUT_FILE=${ANDROID_APP}-${ANDROID_BUILD_TYPE}.apk
ANDROID_APK_NAME=HighFidelity-Beta-${ANDROID_APK_SUFFIX}
./gradlew -PHIFI_ANDROID_PRECOMPILED=${HIFI_ANDROID_PRECOMPILED} -PVERSION_CODE=${VERSION_CODE} -PRELEASE_NUMBER=${RELEASE_NUMBER} -PRELEASE_TYPE=${RELEASE_TYPE} ${ANDROID_APP}:${ANDROID_BUILD_TARGET}
cp ${ANDROID_OUTPUT_DIR}/${ANDROID_OUTPUT_FILE} ./${ANDROID_APK_NAME}
# Quest Interface build
ANDROID_APP=questInterface
ANDROID_OUTPUT_DIR=./apps/${ANDROID_APP}/build/outputs/apk/${ANDROID_BUILD_TYPE}
ANDROID_OUTPUT_FILE=${ANDROID_APP}-${ANDROID_BUILD_TYPE}.apk
ANDROID_APK_NAME=HighFidelity-Quest-Beta-${ANDROID_APK_SUFFIX}
./gradlew -PHIFI_ANDROID_PRECOMPILED=${HIFI_ANDROID_PRECOMPILED} -PVERSION_CODE=${VERSION_CODE} -PRELEASE_NUMBER=${RELEASE_NUMBER} -PRELEASE_TYPE=${RELEASE_TYPE} ${ANDROID_APP}:${ANDROID_BUILD_TARGET} || true
cp ${ANDROID_OUTPUT_DIR}/${ANDROID_OUTPUT_FILE} ./${ANDROID_APK_NAME} || true
# This is the actual output from gradle, which no longer attempts to muck with the naming of the APK
OUTPUT_APK=./apps/${ANDROID_APP}/build/outputs/apk/${ANDROID_BUILD_DIR}/${ANDROID_BUILT_APK_NAME}
# This is the APK name requested by Jenkins
TARGET_APK=./${ANDROID_APK_NAME}
# Make sure this matches up with the new ARTIFACT_EXPRESSION for jenkins builds, which should be "android/*.apk"
cp ${OUTPUT_APK} ${TARGET_APK}

View file

@ -9,6 +9,11 @@ docker build --build-arg BUILD_UID=`id -u` -t "${DOCKER_IMAGE_NAME}" -f docker/D
# So make sure we use VERSION_CODE consistently
test -z "$VERSION_CODE" && export VERSION_CODE=$VERSION
# PR builds don't populate STABLE_BUILD, but the release builds do, and the build
# bash script requires it, so we need to populate it if it's not present
test -z "$STABLE_BUILD" && export STABLE_BUILD=0
# FIXME figure out which of these actually need to be forwarded and which can be eliminated
docker run \
--rm \
--security-opt seccomp:unconfined \
@ -27,6 +32,8 @@ docker run \
-e OAUTH_CLIENT_SECRET \
-e OAUTH_CLIENT_ID \
-e OAUTH_REDIRECT_URI \
-e SHA7 \
-e STABLE_BUILD \
-e VERSION_CODE \
"${DOCKER_IMAGE_NAME}" \
sh -c "./build_android.sh"

View file

@ -73,7 +73,7 @@ RUN mkdir "$HIFI_BASE" && \
RUN git clone https://github.com/jherico/hifi.git && \
cd ~/hifi && \
git checkout feature/quest_frame_player
git checkout quest/build
WORKDIR /home/jenkins/hifi

View file

@ -34,11 +34,16 @@ public class OculusMobileActivity extends QtActivity implements SurfaceHolder.Ca
private native void questNativeOnResume();
private native void questOnAppAfterLoad();
private native void questNativeAwayMode();
private SurfaceView mView;
private SurfaceHolder mSurfaceHolder;
public void onCreate(Bundle savedInstanceState) {
if(getIntent().hasExtra("applicationArguments")){
super.APPLICATION_PARAMETERS=getIntent().getStringExtra("applicationArguments");
}
super.onCreate(savedInstanceState);
Log.w(TAG, "QQQ onCreate");
@ -51,6 +56,7 @@ public class OculusMobileActivity extends QtActivity implements SurfaceHolder.Ca
nativeOnCreate();
questNativeOnCreate();
}
public void onAppLoadedComplete() {
Log.w(TAG, "QQQ Load Completed");
runOnUiThread(() -> {
@ -62,7 +68,8 @@ public class OculusMobileActivity extends QtActivity implements SurfaceHolder.Ca
@Override
protected void onDestroy() {
Log.w(TAG, "QQQ onDestroy");
isPausing=false;
super.onStop();
nativeOnSurfaceChanged(null);
Log.w(TAG, "QQQ onDestroy -- SUPER onDestroy");
@ -78,6 +85,7 @@ public class OculusMobileActivity extends QtActivity implements SurfaceHolder.Ca
questNativeOnResume();
nativeOnResume();
isPausing=false;
}
@Override
@ -87,40 +95,42 @@ public class OculusMobileActivity extends QtActivity implements SurfaceHolder.Ca
questNativeOnPause();
nativeOnPause();
isPausing=true;
}
@Override
protected void onStop(){
super.onStop();
Log.w(TAG, "QQQ Onstop called");
Log.w(TAG, "QQQ_ Onstop called");
questNativeAwayMode();
}
@Override
protected void onRestart(){
protected void onRestart() {
super.onRestart();
Log.w(TAG, "QQQ onRestart called ****");
Log.w(TAG, "QQQ_ onRestart called");
questOnAppAfterLoad();
questNativeAwayMode();
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
Log.w(TAG, "QQQ surfaceCreated ************************************");
Log.w(TAG, "QQQ_ surfaceCreated");
nativeOnSurfaceChanged(holder.getSurface());
mSurfaceHolder = holder;
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Log.w(TAG, "QQQ surfaceChanged");
Log.w(TAG, "QQQ_ surfaceChanged");
nativeOnSurfaceChanged(holder.getSurface());
mSurfaceHolder = holder;
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.w(TAG, "QQQ surfaceDestroyed ***************************************************");
Log.w(TAG, "QQQ_ surfaceDestroyed");
nativeOnSurfaceChanged(null);
mSurfaceHolder = null;
}
}

View file

@ -70,6 +70,7 @@ public class QtActivity extends Activity {
public final String QT_ANDROID_DEFAULT_THEME = QT_ANDROID_THEMES[0]; // sets the default theme.
private QtActivityLoader m_loader = new QtActivityLoader(this);
public boolean isPausing=false;
public QtActivity() {
}
@ -650,9 +651,13 @@ public class QtActivity extends Activity {
@Override
protected void onStop() {
super.onStop();
QtApplication.invokeDelegate();
if(!isPausing){
QtApplication.invokeDelegate();
}
}
//---------------------------------------------------------------------------
@Override

View file

@ -64,10 +64,6 @@ bool AudioMixerSlaveThread::try_pop(SharedNodePointer& node) {
return _pool._queue.try_pop(node);
}
#ifdef AUDIO_SINGLE_THREADED
static AudioMixerSlave slave;
#endif
void AudioMixerSlavePool::processPackets(ConstIter begin, ConstIter end) {
_function = &AudioMixerSlave::processPackets;
_configure = [](AudioMixerSlave& slave) {};
@ -87,19 +83,9 @@ void AudioMixerSlavePool::run(ConstIter begin, ConstIter end) {
_begin = begin;
_end = end;
#ifdef AUDIO_SINGLE_THREADED
_configure(slave);
std::for_each(begin, end, [&](const SharedNodePointer& node) {
_function(slave, node);
});
#else
// fill the queue
std::for_each(_begin, _end, [&](const SharedNodePointer& node) {
#if defined(__clang__) && defined(Q_OS_LINUX)
_queue.push(node);
#else
_queue.emplace(node);
#endif
});
{
@ -119,17 +105,12 @@ void AudioMixerSlavePool::run(ConstIter begin, ConstIter end) {
}
assert(_queue.empty());
#endif
}
void AudioMixerSlavePool::each(std::function<void(AudioMixerSlave& slave)> functor) {
#ifdef AUDIO_SINGLE_THREADED
functor(slave);
#else
for (auto& slave : _slaves) {
functor(*slave.get());
}
#endif
}
void AudioMixerSlavePool::setNumThreads(int numThreads) {
@ -155,9 +136,6 @@ void AudioMixerSlavePool::setNumThreads(int numThreads) {
void AudioMixerSlavePool::resize(int numThreads) {
assert(_numThreads == (int)_slaves.size());
#ifdef AUDIO_SINGLE_THREADED
qDebug("%s: running single threaded", __FUNCTION__, numThreads);
#else
qDebug("%s: set %d threads (was %d)", __FUNCTION__, numThreads, _numThreads);
Lock lock(_mutex);
@ -205,5 +183,4 @@ void AudioMixerSlavePool::resize(int numThreads) {
_numThreads = _numStarted = _numFinished = numThreads;
assert(_numThreads == (int)_slaves.size());
#endif
}

View file

@ -23,6 +23,7 @@
#include <QtCore/QRegularExpression>
#include <QtCore/QTimer>
#include <QtCore/QThread>
#include <QtCore/QJsonDocument>
#include <AABox.h>
#include <AvatarLogging.h>
@ -32,6 +33,10 @@
#include <SharedUtil.h>
#include <UUID.h>
#include <TryLocker.h>
#include "../AssignmentDynamicFactory.h"
#include "../entities/AssignmentParentFinder.h"
#include <model-networking/ModelCache.h>
#include <hfm/ModelFormatRegistry.h>
const QString AVATAR_MIXER_LOGGING_NAME = "avatar-mixer";
@ -55,6 +60,12 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) :
ThreadedAssignment(message),
_slavePool(&_slaveSharedData)
{
DependencyManager::registerInheritance<EntityDynamicFactoryInterface, AssignmentDynamicFactory>();
DependencyManager::set<AssignmentDynamicFactory>();
DependencyManager::set<ModelFormatRegistry>();
DependencyManager::set<ModelCache>();
DependencyManager::set<ResourceCacheSharedItems>();
DependencyManager::set<ResourceManager>();
// make sure we hear about node kills so we can tell the other nodes
connect(DependencyManager::get<NodeList>().data(), &NodeList::nodeKilled, this, &AvatarMixer::handleAvatarKilled);
@ -69,6 +80,8 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) :
packetReceiver.registerListener(PacketType::RequestsDomainListData, this, "handleRequestsDomainListDataPacket");
packetReceiver.registerListener(PacketType::SetAvatarTraits, this, "queueIncomingPacket");
packetReceiver.registerListener(PacketType::BulkAvatarTraitsAck, this, "queueIncomingPacket");
packetReceiver.registerListenerForTypes({ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase },
this, "handleOctreePacket");
packetReceiver.registerListenerForTypes({
PacketType::ReplicatedAvatarIdentity,
@ -240,6 +253,10 @@ void AvatarMixer::start() {
int lockWait, nodeTransform, functor;
{
_entityViewer.queryOctree();
}
// Allow nodes to process any pending/queued packets across our worker threads
{
auto start = usecTimestampNow();
@ -252,6 +269,10 @@ void AvatarMixer::start() {
}, &lockWait, &nodeTransform, &functor);
auto end = usecTimestampNow();
_processQueuedAvatarDataPacketsElapsedTime += (end - start);
_broadcastAvatarDataLockWait += lockWait;
_broadcastAvatarDataNodeTransform += nodeTransform;
_broadcastAvatarDataNodeFunctor += functor;
}
// process pending display names... this doesn't currently run on multiple threads, because it
@ -269,6 +290,10 @@ void AvatarMixer::start() {
}, &lockWait, &nodeTransform, &functor);
auto end = usecTimestampNow();
_displayNameManagementElapsedTime += (end - start);
_broadcastAvatarDataLockWait += lockWait;
_broadcastAvatarDataNodeTransform += nodeTransform;
_broadcastAvatarDataNodeFunctor += functor;
}
// this is where we need to put the real work...
@ -691,8 +716,11 @@ void AvatarMixer::handleRadiusIgnoreRequestPacket(QSharedPointer<ReceivedMessage
}
void AvatarMixer::sendStatsPacket() {
auto start = usecTimestampNow();
if (!_numTightLoopFrames) {
return;
}
auto start = usecTimestampNow();
QJsonObject statsObject;
@ -775,6 +803,7 @@ void AvatarMixer::sendStatsPacket() {
slavesAggregatObject["sent_4_averageDataBytes"] = TIGHT_LOOP_STAT(aggregateStats.numDataBytesSent);
slavesAggregatObject["sent_5_averageTraitsBytes"] = TIGHT_LOOP_STAT(aggregateStats.numTraitsBytesSent);
slavesAggregatObject["sent_6_averageIdentityBytes"] = TIGHT_LOOP_STAT(aggregateStats.numIdentityBytesSent);
slavesAggregatObject["sent_7_averageHeroAvatars"] = TIGHT_LOOP_STAT(aggregateStats.numHeroesIncluded);
slavesAggregatObject["timing_1_processIncomingPackets"] = TIGHT_LOOP_STAT_UINT64(aggregateStats.processIncomingPacketsElapsedTime);
slavesAggregatObject["timing_2_ignoreCalculation"] = TIGHT_LOOP_STAT_UINT64(aggregateStats.ignoreCalculationElapsedTime);
@ -882,13 +911,15 @@ AvatarMixerClientData* AvatarMixer::getOrCreateClientData(SharedNodePointer node
void AvatarMixer::domainSettingsRequestComplete() {
auto nodeList = DependencyManager::get<NodeList>();
nodeList->addSetOfNodeTypesToNodeInterestSet({
NodeType::Agent, NodeType::EntityScriptServer,
NodeType::Agent, NodeType::EntityScriptServer, NodeType::EntityServer,
NodeType::UpstreamAvatarMixer, NodeType::DownstreamAvatarMixer
});
// parse the settings to pull out the values we need
parseDomainServerSettings(nodeList->getDomainHandler().getSettingsObject());
setupEntityQuery();
// start our tight loop...
start();
}
@ -939,6 +970,14 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) {
qCDebug(avatars) << "Avatar mixer will automatically determine number of threads to use. Using:" << _slavePool.numThreads() << "threads.";
}
{
const QString CONNECTION_RATE = "connection_rate";
auto nodeList = DependencyManager::get<NodeList>();
auto defaultConnectionRate = nodeList->getMaxConnectionRate();
int connectionRate = avatarMixerGroupObject[CONNECTION_RATE].toInt((int)defaultConnectionRate);
nodeList->setMaxConnectionRate(connectionRate);
}
const QString AVATARS_SETTINGS_KEY = "avatars";
static const QString MIN_HEIGHT_OPTION = "min_avatar_height";
@ -976,3 +1015,62 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) {
qCDebug(avatars) << "Avatars other than" << _slaveSharedData.skeletonURLWhitelist << "will be replaced by" << (_slaveSharedData.skeletonReplacementURL.isEmpty() ? "default" : _slaveSharedData.skeletonReplacementURL.toString());
}
}
void AvatarMixer::setupEntityQuery() {
_entityViewer.init();
DependencyManager::registerInheritance<SpatialParentFinder, AssignmentParentFinder>();
DependencyManager::set<AssignmentParentFinder>(_entityViewer.getTree());
_slaveSharedData.entityTree = _entityViewer.getTree();
// ES query: {"avatarPriority": true, "type": "Zone"}
QJsonObject priorityZoneQuery;
priorityZoneQuery["avatarPriority"] = true;
priorityZoneQuery["type"] = "Zone";
_entityViewer.getOctreeQuery().setJSONParameters(priorityZoneQuery);
}
void AvatarMixer::handleOctreePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
PacketType packetType = message->getType();
switch (packetType) {
case PacketType::OctreeStats:
{ // Ignore stats, but may have a different Entity packet appended.
OctreeHeadlessViewer::parseOctreeStats(message, senderNode);
const auto piggyBackedSizeWithHeader = message->getBytesLeftToRead();
if (piggyBackedSizeWithHeader > 0) {
// pull out the piggybacked packet and create a new QSharedPointer<NLPacket> for it
auto buffer = std::unique_ptr<char[]>(new char[piggyBackedSizeWithHeader]);
memcpy(buffer.get(), message->getRawMessage() + message->getPosition(), piggyBackedSizeWithHeader);
auto newPacket = NLPacket::fromReceivedPacket(std::move(buffer), piggyBackedSizeWithHeader, message->getSenderSockAddr());
auto newMessage = QSharedPointer<ReceivedMessage>::create(*newPacket);
handleOctreePacket(newMessage, senderNode);
}
break;
}
case PacketType::EntityData:
_entityViewer.processDatagram(*message, senderNode);
break;
case PacketType::EntityErase:
_entityViewer.processEraseMessage(*message, senderNode);
break;
default:
qCDebug(avatars) << "Unexpected packet type:" << packetType;
break;
}
}
void AvatarMixer::aboutToFinish() {
DependencyManager::destroy<ResourceManager>();
DependencyManager::destroy<ResourceCacheSharedItems>();
DependencyManager::destroy<ModelCache>();
DependencyManager::destroy<ModelFormatRegistry>();
DependencyManager::destroy<AssignmentDynamicFactory>();
DependencyManager::destroy<AssignmentParentFinder>();
ThreadedAssignment::aboutToFinish();
}

View file

@ -20,6 +20,7 @@
#include <PortableHighResolutionClock.h>
#include <ThreadedAssignment.h>
#include "../entities/EntityTreeHeadlessViewer.h"
#include "AvatarMixerClientData.h"
#include "AvatarMixerSlavePool.h"
@ -29,6 +30,7 @@ class AvatarMixer : public ThreadedAssignment {
Q_OBJECT
public:
AvatarMixer(ReceivedMessage& message);
virtual void aboutToFinish() override;
static bool shouldReplicateTo(const Node& from, const Node& to) {
return to.getType() == NodeType::DownstreamAvatarMixer &&
@ -57,6 +59,7 @@ private slots:
void handleReplicatedBulkAvatarPacket(QSharedPointer<ReceivedMessage> message);
void domainSettingsRequestComplete();
void handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID);
void handleOctreePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
void start();
private:
@ -71,8 +74,13 @@ private:
void optionallyReplicatePacket(ReceivedMessage& message, const Node& node);
void setupEntityQuery();
p_high_resolution_clock::time_point _lastFrameTimestamp;
// Attach to entity tree for avatar-priority zone info.
EntityTreeHeadlessViewer _entityViewer;
// FIXME - new throttling - use these values somehow
float _trailingMixRatio { 0.0f };
float _throttlingRatio { 0.0f };

View file

@ -16,6 +16,10 @@
#include <DependencyManager.h>
#include <NodeList.h>
#include <EntityTree.h>
#include <ZoneEntityItem.h>
#include "AvatarLogging.h"
#include "AvatarMixerSlave.h"
@ -62,7 +66,7 @@ int AvatarMixerClientData::processPackets(const SlaveSharedData& slaveSharedData
switch (packet->getType()) {
case PacketType::AvatarData:
parseData(*packet);
parseData(*packet, slaveSharedData);
break;
case PacketType::SetAvatarTraits:
processSetTraitsMessage(*packet, slaveSharedData, *node);
@ -80,7 +84,42 @@ int AvatarMixerClientData::processPackets(const SlaveSharedData& slaveSharedData
return packetsProcessed;
}
int AvatarMixerClientData::parseData(ReceivedMessage& message) {
namespace {
using std::static_pointer_cast;
// Operator to find if a point is within an avatar-priority (hero) Zone Entity.
struct FindPriorityZone {
glm::vec3 position;
bool isInPriorityZone { false };
float zoneVolume { std::numeric_limits<float>::max() };
static bool operation(const OctreeElementPointer& element, void* extraData) {
auto findPriorityZone = static_cast<FindPriorityZone*>(extraData);
if (element->getAACube().contains(findPriorityZone->position)) {
const EntityTreeElementPointer entityTreeElement = static_pointer_cast<EntityTreeElement>(element);
entityTreeElement->forEachEntity([&findPriorityZone](EntityItemPointer item) {
if (item->getType() == EntityTypes::Zone
&& item->contains(findPriorityZone->position)) {
auto zoneItem = static_pointer_cast<ZoneEntityItem>(item);
if (zoneItem->getAvatarPriority() != COMPONENT_MODE_INHERIT) {
float volume = zoneItem->getVolumeEstimate();
if (volume < findPriorityZone->zoneVolume) { // Smaller volume wins
findPriorityZone->isInPriorityZone = zoneItem->getAvatarPriority() == COMPONENT_MODE_ENABLED;
findPriorityZone->zoneVolume = volume;
}
}
}
});
return true; // Keep recursing
} else { // Position isn't within this subspace, so end recursion.
return false;
}
}
};
} // Close anonymous namespace.
int AvatarMixerClientData::parseData(ReceivedMessage& message, const SlaveSharedData& slaveSharedData) {
// pull the sequence number from the data first
uint16_t sequenceNumber;
@ -90,9 +129,37 @@ int AvatarMixerClientData::parseData(ReceivedMessage& message) {
incrementNumOutOfOrderSends();
}
_lastReceivedSequenceNumber = sequenceNumber;
glm::vec3 oldPosition = getPosition();
bool oldHasPriority = _avatar->getHasPriority();
// compute the offset to the data payload
return _avatar->parseDataFromBuffer(message.readWithoutCopy(message.getBytesLeftToRead()));
if (!_avatar->parseDataFromBuffer(message.readWithoutCopy(message.getBytesLeftToRead()))) {
return false;
}
// Regardless of what the client says, restore the priority as we know it without triggering any update.
_avatar->setHasPriorityWithoutTimestampReset(oldHasPriority);
auto newPosition = getPosition();
if (newPosition != oldPosition) {
//#define AVATAR_HERO_TEST_HACK
#ifdef AVATAR_HERO_TEST_HACK
{
const static QString heroKey { "HERO" };
_avatar->setPriorityAvatar(_avatar->getDisplayName().contains(heroKey));
}
#else
EntityTree& entityTree = *slaveSharedData.entityTree;
FindPriorityZone findPriorityZone { newPosition, false } ;
entityTree.recurseTreeWithOperation(&FindPriorityZone::operation, &findPriorityZone);
_avatar->setHasPriority(findPriorityZone.isInPriorityZone);
//if (findPriorityZone.isInPriorityZone) {
// qCWarning(avatars) << "Avatar" << _avatar->getSessionDisplayName() << "in hero zone";
//}
#endif
}
return true;
}
void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message,

View file

@ -21,7 +21,7 @@
#include <QtCore/QJsonObject>
#include <QtCore/QUrl>
#include <AvatarData.h>
#include "MixerAvatar.h"
#include <AssociatedTraitValues.h>
#include <NodeData.h>
#include <NumericalConstants.h>
@ -45,11 +45,12 @@ public:
using HRCTime = p_high_resolution_clock::time_point;
using PerNodeTraitVersions = std::unordered_map<Node::LocalID, AvatarTraits::TraitVersions>;
int parseData(ReceivedMessage& message) override;
AvatarData& getAvatar() { return *_avatar; }
const AvatarData& getAvatar() const { return *_avatar; }
const AvatarData* getConstAvatarData() const { return _avatar.get(); }
AvatarSharedPointer getAvatarSharedPointer() const { return _avatar; }
using NodeData::parseData; // Avoid clang warning about hiding.
int parseData(ReceivedMessage& message, const SlaveSharedData& SlaveSharedData);
MixerAvatar& getAvatar() { return *_avatar; }
const MixerAvatar& getAvatar() const { return *_avatar; }
const MixerAvatar* getConstAvatarData() const { return _avatar.get(); }
MixerAvatarSharedPointer getAvatarSharedPointer() const { return _avatar; }
uint16_t getLastBroadcastSequenceNumber(NLPacket::LocalID nodeID) const;
void setLastBroadcastSequenceNumber(NLPacket::LocalID nodeID, uint16_t sequenceNumber)
@ -163,7 +164,7 @@ private:
};
PacketQueue _packetQueue;
AvatarSharedPointer _avatar { new AvatarData() };
MixerAvatarSharedPointer _avatar { new MixerAvatar() };
uint16_t _lastReceivedSequenceNumber { 0 };
std::unordered_map<NLPacket::LocalID, uint16_t> _lastBroadcastSequenceNumbers;

View file

@ -281,7 +281,34 @@ AABox computeBubbleBox(const AvatarData& avatar, float bubbleExpansionFactor) {
return box;
}
namespace {
class SortableAvatar : public PrioritySortUtil::Sortable {
public:
SortableAvatar() = delete;
SortableAvatar(const MixerAvatar* avatar, const Node* avatarNode, uint64_t lastEncodeTime)
: _avatar(avatar), _node(avatarNode), _lastEncodeTime(lastEncodeTime) {
}
glm::vec3 getPosition() const override { return _avatar->getClientGlobalPosition(); }
float getRadius() const override {
glm::vec3 nodeBoxScale = _avatar->getGlobalBoundingBox().getScale();
return 0.5f * glm::max(nodeBoxScale.x, glm::max(nodeBoxScale.y, nodeBoxScale.z));
}
uint64_t getTimestamp() const override {
return _lastEncodeTime;
}
const Node* getNode() const { return _node; }
const MixerAvatar* getAvatar() const { return _avatar; }
private:
const MixerAvatar* _avatar;
const Node* _node;
uint64_t _lastEncodeTime;
};
} // Close anonymous namespace.
void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) {
const float AVATAR_HERO_FRACTION { 0.4f };
const Node* destinationNode = node.data();
auto nodeList = DependencyManager::get<NodeList>();
@ -293,29 +320,30 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
_stats.nodesBroadcastedTo++;
AvatarMixerClientData* nodeData = reinterpret_cast<AvatarMixerClientData*>(destinationNode->getLinkedData());
AvatarMixerClientData* destinationNodeData = reinterpret_cast<AvatarMixerClientData*>(destinationNode->getLinkedData());
nodeData->resetInViewStats();
destinationNodeData->resetInViewStats();
const AvatarData& avatar = nodeData->getAvatar();
glm::vec3 myPosition = avatar.getClientGlobalPosition();
const AvatarData& avatar = destinationNodeData->getAvatar();
glm::vec3 destinationPosition = avatar.getClientGlobalPosition();
// reset the internal state for correct random number distribution
distribution.reset();
// Estimate number to sort on number sent last frame (with min. of 20).
const int numToSendEst = std::max(int(nodeData->getNumAvatarsSentLastFrame() * 2.5f), 20);
const int numToSendEst = std::max(int(destinationNodeData->getNumAvatarsSentLastFrame() * 2.5f), 20);
// reset the number of sent avatars
nodeData->resetNumAvatarsSentLastFrame();
destinationNodeData->resetNumAvatarsSentLastFrame();
// keep track of outbound data rate specifically for avatar data
int numAvatarDataBytes = 0;
int identityBytesSent = 0;
int traitBytesSent = 0;
// max number of avatarBytes per frame
int maxAvatarBytesPerFrame = int(_maxKbpsPerNode * BYTES_PER_KILOBIT / AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND);
// max number of avatarBytes per frame (13 900, typical)
const int maxAvatarBytesPerFrame = int(_maxKbpsPerNode * BYTES_PER_KILOBIT / AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND);
const int maxHeroBytesPerFrame = int(maxAvatarBytesPerFrame * AVATAR_HERO_FRACTION); // 5555, typical
// keep track of the number of other avatars held back in this frame
int numAvatarsHeldBack = 0;
@ -325,8 +353,8 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
// When this is true, the AvatarMixer will send Avatar data to a client
// about avatars they've ignored or that are out of view
bool PALIsOpen = nodeData->getRequestsDomainListData();
bool PALWasOpen = nodeData->getPrevRequestsDomainListData();
bool PALIsOpen = destinationNodeData->getRequestsDomainListData();
bool PALWasOpen = destinationNodeData->getPrevRequestsDomainListData();
// When this is true, the AvatarMixer will send Avatar data to a client about avatars that have ignored them
bool getsAnyIgnored = PALIsOpen && destinationNode->getCanKick();
@ -337,36 +365,23 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
// compute node bounding box
const float MY_AVATAR_BUBBLE_EXPANSION_FACTOR = 4.0f; // magic number determined emperically
AABox nodeBox = computeBubbleBox(avatar, MY_AVATAR_BUBBLE_EXPANSION_FACTOR);
class SortableAvatar: public PrioritySortUtil::Sortable {
public:
SortableAvatar() = delete;
SortableAvatar(const AvatarData* avatar, const Node* avatarNode, uint64_t lastEncodeTime)
: _avatar(avatar), _node(avatarNode), _lastEncodeTime(lastEncodeTime) {}
glm::vec3 getPosition() const override { return _avatar->getClientGlobalPosition(); }
float getRadius() const override {
glm::vec3 nodeBoxScale = _avatar->getGlobalBoundingBox().getScale();
return 0.5f * glm::max(nodeBoxScale.x, glm::max(nodeBoxScale.y, nodeBoxScale.z));
}
uint64_t getTimestamp() const override {
return _lastEncodeTime;
}
const Node* getNode() const { return _node; }
private:
const AvatarData* _avatar;
const Node* _node;
uint64_t _lastEncodeTime;
};
AABox destinationNodeBox = computeBubbleBox(avatar, MY_AVATAR_BUBBLE_EXPANSION_FACTOR);
// prepare to sort
const auto& cameraViews = nodeData->getViewFrustums();
PrioritySortUtil::PriorityQueue<SortableAvatar> sortedAvatars(cameraViews,
AvatarData::_avatarSortCoefficientSize,
AvatarData::_avatarSortCoefficientCenter,
AvatarData::_avatarSortCoefficientAge);
sortedAvatars.reserve(_end - _begin);
const auto& cameraViews = destinationNodeData->getViewFrustums();
using AvatarPriorityQueue = PrioritySortUtil::PriorityQueue<SortableAvatar>;
// Keep two independent queues, one for heroes and one for the riff-raff.
enum PriorityVariants { kHero, kNonhero };
AvatarPriorityQueue avatarPriorityQueues[2] =
{
{cameraViews, AvatarData::_avatarSortCoefficientSize,
AvatarData::_avatarSortCoefficientCenter, AvatarData::_avatarSortCoefficientAge},
{cameraViews, AvatarData::_avatarSortCoefficientSize,
AvatarData::_avatarSortCoefficientCenter, AvatarData::_avatarSortCoefficientAge}
};
avatarPriorityQueues[kNonhero].reserve(_end - _begin);
for (auto listedNode = _begin; listedNode != _end; ++listedNode) {
Node* otherNodeRaw = (*listedNode).data();
@ -376,47 +391,47 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
continue;
}
auto avatarNode = otherNodeRaw;
auto sourceAvatarNode = otherNodeRaw;
bool shouldIgnore = false;
bool sendAvatar = true; // We will consider this source avatar for sending.
// We ignore other nodes for a couple of reasons:
// 1) ignore bubbles and ignore specific node
// 2) the node hasn't really updated it's frame data recently, this can
// happen if for example the avatar is connected on a desktop and sending
// updates at ~30hz. So every 3 frames we skip a frame.
assert(avatarNode); // we can't have gotten here without the avatarData being a valid key in the map
assert(sourceAvatarNode); // we can't have gotten here without the avatarData being a valid key in the map
const AvatarMixerClientData* avatarClientNodeData = reinterpret_cast<const AvatarMixerClientData*>(avatarNode->getLinkedData());
assert(avatarClientNodeData); // we can't have gotten here without avatarNode having valid data
const AvatarMixerClientData* sourceAvatarNodeData = reinterpret_cast<const AvatarMixerClientData*>(sourceAvatarNode->getLinkedData());
assert(sourceAvatarNodeData); // we can't have gotten here without sourceAvatarNode having valid data
quint64 startIgnoreCalculation = usecTimestampNow();
// make sure we have data for this avatar, that it isn't the same node,
// and isn't an avatar that the viewing node has ignored
// or that has ignored the viewing node
if ((destinationNode->isIgnoringNodeWithID(avatarNode->getUUID()) && !PALIsOpen)
|| (avatarNode->isIgnoringNodeWithID(destinationNode->getUUID()) && !getsAnyIgnored)) {
shouldIgnore = true;
if ((destinationNode->isIgnoringNodeWithID(sourceAvatarNode->getUUID()) && !PALIsOpen)
|| (sourceAvatarNode->isIgnoringNodeWithID(destinationNode->getUUID()) && !getsAnyIgnored)) {
sendAvatar = false;
} else {
// Check to see if the space bubble is enabled
// Don't bother with these checks if the other avatar has their bubble enabled and we're gettingAnyIgnored
if (nodeData->isIgnoreRadiusEnabled() || (avatarClientNodeData->isIgnoreRadiusEnabled() && !getsAnyIgnored)) {
if (destinationNodeData->isIgnoreRadiusEnabled() || (sourceAvatarNodeData->isIgnoreRadiusEnabled() && !getsAnyIgnored)) {
// Perform the collision check between the two bounding boxes
AABox otherNodeBox = avatarClientNodeData->getAvatar().getDefaultBubbleBox();
if (nodeBox.touches(otherNodeBox)) {
nodeData->ignoreOther(destinationNode, avatarNode);
shouldIgnore = !getsAnyIgnored;
AABox sourceNodeBox = sourceAvatarNodeData->getAvatar().getDefaultBubbleBox();
if (destinationNodeBox.touches(sourceNodeBox)) {
destinationNodeData->ignoreOther(destinationNode, sourceAvatarNode);
sendAvatar = getsAnyIgnored;
}
}
// Not close enough to ignore
if (!shouldIgnore) {
nodeData->removeFromRadiusIgnoringSet(avatarNode->getUUID());
if (sendAvatar) {
destinationNodeData->removeFromRadiusIgnoringSet(sourceAvatarNode->getUUID());
}
}
if (!shouldIgnore) {
AvatarDataSequenceNumber lastSeqToReceiver = nodeData->getLastBroadcastSequenceNumber(avatarNode->getLocalID());
AvatarDataSequenceNumber lastSeqFromSender = avatarClientNodeData->getLastReceivedSequenceNumber();
if (sendAvatar) {
AvatarDataSequenceNumber lastSeqToReceiver = destinationNodeData->getLastBroadcastSequenceNumber(sourceAvatarNode->getLocalID());
AvatarDataSequenceNumber lastSeqFromSender = sourceAvatarNodeData->getLastReceivedSequenceNumber();
// FIXME - This code does appear to be working. But it seems brittle.
// It supports determining if the frame of data for this "other"
@ -430,26 +445,28 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
// or that somehow we haven't sent
if (lastSeqToReceiver == lastSeqFromSender && lastSeqToReceiver != 0) {
++numAvatarsHeldBack;
shouldIgnore = true;
sendAvatar = false;
} else if (lastSeqFromSender == 0) {
// We have have not yet recieved any data about this avatar. Ignore it for now
// We have have not yet received any data about this avatar. Ignore it for now
// This is important for Agent scripts that are not avatar
// so that they don't appear to be an avatar at the origin
shouldIgnore = true;
sendAvatar = false;
} else if (lastSeqFromSender - lastSeqToReceiver > 1) {
// this is a skip - we still send the packet but capture the presence of the skip so we see it happening
++numAvatarsWithSkippedFrames;
}
}
quint64 endIgnoreCalculation = usecTimestampNow();
_stats.ignoreCalculationElapsedTime += (endIgnoreCalculation - startIgnoreCalculation);
if (!shouldIgnore) {
if (sendAvatar) {
// sort this one for later
const AvatarData* avatarNodeData = avatarClientNodeData->getConstAvatarData();
auto lastEncodeTime = nodeData->getLastOtherAvatarEncodeTime(avatarNode->getLocalID());
const MixerAvatar* avatarNodeData = sourceAvatarNodeData->getConstAvatarData();
auto lastEncodeTime = destinationNodeData->getLastOtherAvatarEncodeTime(sourceAvatarNode->getLocalID());
sortedAvatars.push(SortableAvatar(avatarNodeData, avatarNode, lastEncodeTime));
avatarPriorityQueues[avatarNodeData->getHasPriority() ? kHero : kNonhero].push(
SortableAvatar(avatarNodeData, sourceAvatarNode, lastEncodeTime));
}
// If Avatar A's PAL WAS open but is no longer open, AND
@ -459,135 +476,153 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
// will be sent when it doesn't need to be (but where it _should_ be OK to send).
// However, it's less heavy-handed than using `shouldIgnore`.
if (PALWasOpen && !PALIsOpen &&
(destinationNode->isIgnoringNodeWithID(avatarNode->getUUID()) ||
avatarNode->isIgnoringNodeWithID(destinationNode->getUUID()))) {
(destinationNode->isIgnoringNodeWithID(sourceAvatarNode->getUUID()) ||
sourceAvatarNode->isIgnoringNodeWithID(destinationNode->getUUID()))) {
// ...send a Kill Packet to Node A, instructing Node A to kill Avatar B,
// then have Node A cleanup the killed Node B.
auto packet = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason), true);
packet->write(avatarNode->getUUID().toRfc4122());
packet->write(sourceAvatarNode->getUUID().toRfc4122());
packet->writePrimitive(KillAvatarReason::AvatarIgnored);
nodeList->sendPacket(std::move(packet), *destinationNode);
nodeData->cleanupKilledNode(avatarNode->getUUID(), avatarNode->getLocalID());
destinationNodeData->cleanupKilledNode(sourceAvatarNode->getUUID(), sourceAvatarNode->getLocalID());
}
nodeData->setPrevRequestsDomainListData(PALIsOpen);
destinationNodeData->setPrevRequestsDomainListData(PALIsOpen);
}
// loop through our sorted avatars and allocate our bandwidth to them accordingly
int remainingAvatars = (int)sortedAvatars.size();
int remainingAvatars = (int)avatarPriorityQueues[kHero].size() + (int)avatarPriorityQueues[kNonhero].size();
auto traitsPacketList = NLPacketList::create(PacketType::BulkAvatarTraits, QByteArray(), true, true);
auto avatarPacket = NLPacket::create(PacketType::BulkAvatarData);
const int avatarPacketCapacity = avatarPacket->getPayloadCapacity();
int avatarSpaceAvailable = avatarPacketCapacity;
int numPacketsSent = 0;
int numAvatarsSent = 0;
auto identityPacketList = NLPacketList::create(PacketType::AvatarIdentity, QByteArray(), true, true);
const auto& sortedAvatarVector = sortedAvatars.getSortedVector(numToSendEst);
for (const auto& sortedAvatar : sortedAvatarVector) {
const Node* otherNode = sortedAvatar.getNode();
auto lastEncodeForOther = sortedAvatar.getTimestamp();
// Loop over two priorities - hero avatars then everyone else:
for (PriorityVariants currentVariant = kHero; currentVariant <= kNonhero; ++((int&)currentVariant)) {
const auto& sortedAvatarVector = avatarPriorityQueues[currentVariant].getSortedVector(numToSendEst);
for (const auto& sortedAvatar : sortedAvatarVector) {
const Node* sourceNode = sortedAvatar.getNode();
auto lastEncodeForOther = sortedAvatar.getTimestamp();
assert(otherNode); // we can't have gotten here without the avatarData being a valid key in the map
assert(sourceNode); // we can't have gotten here without the avatarData being a valid key in the map
AvatarData::AvatarDataDetail detail = AvatarData::NoData;
AvatarData::AvatarDataDetail detail = AvatarData::NoData;
// NOTE: Here's where we determine if we are over budget and drop remaining avatars,
// or send minimal avatar data in uncommon case of PALIsOpen.
int minimRemainingAvatarBytes = minimumBytesPerAvatar * remainingAvatars;
auto frameByteEstimate = identityBytesSent + traitBytesSent + numAvatarDataBytes + minimRemainingAvatarBytes;
bool overBudget = frameByteEstimate > maxAvatarBytesPerFrame;
if (overBudget) {
if (PALIsOpen) {
_stats.overBudgetAvatars++;
detail = AvatarData::PALMinimum;
} else {
_stats.overBudgetAvatars += remainingAvatars;
break;
// NOTE: Here's where we determine if we are over budget and drop remaining avatars,
// or send minimal avatar data in uncommon case of PALIsOpen.
int minimRemainingAvatarBytes = minimumBytesPerAvatar * remainingAvatars;
auto frameByteEstimate = identityBytesSent + traitBytesSent + numAvatarDataBytes + minimRemainingAvatarBytes;
bool overBudget = frameByteEstimate > maxAvatarBytesPerFrame;
if (overBudget) {
if (PALIsOpen) {
_stats.overBudgetAvatars++;
detail = AvatarData::PALMinimum;
} else {
_stats.overBudgetAvatars += remainingAvatars;
break;
}
}
bool overHeroBudget = currentVariant == kHero && numAvatarDataBytes > maxHeroBytesPerFrame;
if (overHeroBudget) {
break; // No more heroes (this frame).
}
auto startAvatarDataPacking = chrono::high_resolution_clock::now();
const AvatarMixerClientData* sourceNodeData = reinterpret_cast<const AvatarMixerClientData*>(sourceNode->getLinkedData());
const MixerAvatar* sourceAvatar = sourceNodeData->getConstAvatarData();
// Typically all out-of-view avatars but such avatars' priorities will rise with time:
bool isLowerPriority = currentVariant != kHero && sortedAvatar.getPriority() <= OUT_OF_VIEW_THRESHOLD; // XXX: hero handling?
if (isLowerPriority) {
detail = PALIsOpen ? AvatarData::PALMinimum : AvatarData::MinimumData;
destinationNodeData->incrementAvatarOutOfView();
} else if (!overBudget) {
detail = distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO ? AvatarData::SendAllData : AvatarData::CullSmallData;
destinationNodeData->incrementAvatarInView();
// If the time that the mixer sent AVATAR DATA about Avatar B to Avatar A is BEFORE OR EQUAL TO
// the time that Avatar B flagged an IDENTITY DATA change, send IDENTITY DATA about Avatar B to Avatar A.
if (sourceAvatar->hasProcessedFirstIdentity()
&& destinationNodeData->getLastBroadcastTime(sourceNode->getLocalID()) <= sourceNodeData->getIdentityChangeTimestamp()) {
identityBytesSent += sendIdentityPacket(*identityPacketList, sourceNodeData, *destinationNode);
// remember the last time we sent identity details about this other node to the receiver
destinationNodeData->setLastBroadcastTime(sourceNode->getLocalID(), usecTimestampNow());
}
}
QVector<JointData>& lastSentJointsForOther = destinationNodeData->getLastOtherAvatarSentJoints(sourceNode->getLocalID());
const bool distanceAdjust = true;
const bool dropFaceTracking = false;
AvatarDataPacket::SendStatus sendStatus;
sendStatus.sendUUID = true;
do {
auto startSerialize = chrono::high_resolution_clock::now();
QByteArray bytes = sourceAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther,
sendStatus, dropFaceTracking, distanceAdjust, destinationPosition,
&lastSentJointsForOther, avatarSpaceAvailable);
auto endSerialize = chrono::high_resolution_clock::now();
_stats.toByteArrayElapsedTime +=
(quint64)chrono::duration_cast<chrono::microseconds>(endSerialize - startSerialize).count();
avatarPacket->write(bytes);
avatarSpaceAvailable -= bytes.size();
numAvatarDataBytes += bytes.size();
if (!sendStatus || avatarSpaceAvailable < (int)AvatarDataPacket::MIN_BULK_PACKET_SIZE) {
// Weren't able to fit everything.
nodeList->sendPacket(std::move(avatarPacket), *destinationNode);
++numPacketsSent;
avatarPacket = NLPacket::create(PacketType::BulkAvatarData);
avatarSpaceAvailable = avatarPacketCapacity;
}
} while (!sendStatus);
if (detail != AvatarData::NoData) {
_stats.numOthersIncluded++;
if (sourceAvatar->getHasPriority()) {
_stats.numHeroesIncluded++;
}
// increment the number of avatars sent to this receiver
destinationNodeData->incrementNumAvatarsSentLastFrame();
// set the last sent sequence number for this sender on the receiver
destinationNodeData->setLastBroadcastSequenceNumber(sourceNode->getLocalID(),
sourceNodeData->getLastReceivedSequenceNumber());
destinationNodeData->setLastOtherAvatarEncodeTime(sourceNode->getLocalID(), usecTimestampNow());
}
auto endAvatarDataPacking = chrono::high_resolution_clock::now();
_stats.avatarDataPackingElapsedTime +=
(quint64)chrono::duration_cast<chrono::microseconds>(endAvatarDataPacking - startAvatarDataPacking).count();
if (!overBudget) {
// use helper to add any changed traits to our packet list
traitBytesSent += addChangedTraitsToBulkPacket(destinationNodeData, sourceNodeData, *traitsPacketList);
}
numAvatarsSent++;
remainingAvatars--;
}
if (currentVariant == kHero) { // Dump any remaining heroes into the commoners.
for (auto avIter = sortedAvatarVector.begin() + numAvatarsSent; avIter < sortedAvatarVector.end(); ++avIter) {
avatarPriorityQueues[kNonhero].push(*avIter);
}
}
auto startAvatarDataPacking = chrono::high_resolution_clock::now();
const AvatarMixerClientData* otherNodeData = reinterpret_cast<const AvatarMixerClientData*>(otherNode->getLinkedData());
const AvatarData* otherAvatar = otherNodeData->getConstAvatarData();
// Typically all out-of-view avatars but such avatars' priorities will rise with time:
bool isLowerPriority = sortedAvatar.getPriority() <= OUT_OF_VIEW_THRESHOLD;
if (isLowerPriority) {
detail = PALIsOpen ? AvatarData::PALMinimum : AvatarData::MinimumData;
nodeData->incrementAvatarOutOfView();
} else if (!overBudget) {
detail = distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO ? AvatarData::SendAllData : AvatarData::CullSmallData;
nodeData->incrementAvatarInView();
// If the time that the mixer sent AVATAR DATA about Avatar B to Avatar A is BEFORE OR EQUAL TO
// the time that Avatar B flagged an IDENTITY DATA change, send IDENTITY DATA about Avatar B to Avatar A.
if (otherAvatar->hasProcessedFirstIdentity()
&& nodeData->getLastBroadcastTime(otherNode->getLocalID()) <= otherNodeData->getIdentityChangeTimestamp()) {
identityBytesSent += sendIdentityPacket(*identityPacketList, otherNodeData, *destinationNode);
// remember the last time we sent identity details about this other node to the receiver
nodeData->setLastBroadcastTime(otherNode->getLocalID(), usecTimestampNow());
}
}
QVector<JointData>& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getLocalID());
const bool distanceAdjust = true;
const bool dropFaceTracking = false;
AvatarDataPacket::SendStatus sendStatus;
sendStatus.sendUUID = true;
do {
auto startSerialize = chrono::high_resolution_clock::now();
QByteArray bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther,
sendStatus, dropFaceTracking, distanceAdjust, myPosition,
&lastSentJointsForOther, avatarSpaceAvailable);
auto endSerialize = chrono::high_resolution_clock::now();
_stats.toByteArrayElapsedTime +=
(quint64)chrono::duration_cast<chrono::microseconds>(endSerialize - startSerialize).count();
avatarPacket->write(bytes);
avatarSpaceAvailable -= bytes.size();
numAvatarDataBytes += bytes.size();
if (!sendStatus || avatarSpaceAvailable < (int)AvatarDataPacket::MIN_BULK_PACKET_SIZE) {
// Weren't able to fit everything.
nodeList->sendPacket(std::move(avatarPacket), *destinationNode);
++numPacketsSent;
avatarPacket = NLPacket::create(PacketType::BulkAvatarData);
avatarSpaceAvailable = avatarPacketCapacity;
}
} while (!sendStatus);
if (detail != AvatarData::NoData) {
_stats.numOthersIncluded++;
// increment the number of avatars sent to this receiver
nodeData->incrementNumAvatarsSentLastFrame();
// set the last sent sequence number for this sender on the receiver
nodeData->setLastBroadcastSequenceNumber(otherNode->getLocalID(),
otherNodeData->getLastReceivedSequenceNumber());
nodeData->setLastOtherAvatarEncodeTime(otherNode->getLocalID(), usecTimestampNow());
}
auto endAvatarDataPacking = chrono::high_resolution_clock::now();
_stats.avatarDataPackingElapsedTime +=
(quint64) chrono::duration_cast<chrono::microseconds>(endAvatarDataPacking - startAvatarDataPacking).count();
if (!overBudget) {
// use helper to add any changed traits to our packet list
traitBytesSent += addChangedTraitsToBulkPacket(nodeData, otherNodeData, *traitsPacketList);
}
remainingAvatars--;
}
if (nodeData->getNumAvatarsSentLastFrame() > numToSendEst) {
qCWarning(avatars) << "More avatars sent than upper estimate" << nodeData->getNumAvatarsSentLastFrame()
if (destinationNodeData->getNumAvatarsSentLastFrame() > numToSendEst) {
qCWarning(avatars) << "More avatars sent than upper estimate" << destinationNodeData->getNumAvatarsSentLastFrame()
<< " / " << numToSendEst;
}
@ -618,12 +653,12 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
}
// record the bytes sent for other avatar data in the AvatarMixerClientData
nodeData->recordSentAvatarData(numAvatarDataBytes, traitBytesSent);
destinationNodeData->recordSentAvatarData(numAvatarDataBytes, traitBytesSent);
// record the number of avatars held back this frame
nodeData->recordNumOtherAvatarStarves(numAvatarsHeldBack);
nodeData->recordNumOtherAvatarSkips(numAvatarsWithSkippedFrames);
destinationNodeData->recordNumOtherAvatarStarves(numAvatarsHeldBack);
destinationNodeData->recordNumOtherAvatarSkips(numAvatarsWithSkippedFrames);
quint64 endPacketSending = usecTimestampNow();
_stats.packetSendingElapsedTime += (endPacketSending - startPacketSending);

View file

@ -32,6 +32,7 @@ public:
int numIdentityPacketsSent { 0 };
int numOthersIncluded { 0 };
int overBudgetAvatars { 0 };
int numHeroesIncluded { 0 };
quint64 ignoreCalculationElapsedTime { 0 };
quint64 avatarDataPackingElapsedTime { 0 };
@ -57,6 +58,7 @@ public:
numIdentityPacketsSent = 0;
numOthersIncluded = 0;
overBudgetAvatars = 0;
numHeroesIncluded = 0;
ignoreCalculationElapsedTime = 0;
avatarDataPackingElapsedTime = 0;
@ -80,6 +82,7 @@ public:
numIdentityPacketsSent += rhs.numIdentityPacketsSent;
numOthersIncluded += rhs.numOthersIncluded;
overBudgetAvatars += rhs.overBudgetAvatars;
numHeroesIncluded += rhs.numHeroesIncluded;
ignoreCalculationElapsedTime += rhs.ignoreCalculationElapsedTime;
avatarDataPackingElapsedTime += rhs.avatarDataPackingElapsedTime;
@ -90,9 +93,13 @@ public:
}
};
class EntityTree;
using EntityTreePointer = std::shared_ptr<EntityTree>;
struct SlaveSharedData {
QStringList skeletonURLWhitelist;
QUrl skeletonReplacementURL;
EntityTreePointer entityTree;
};
class AvatarMixerSlave {

View file

@ -63,10 +63,6 @@ bool AvatarMixerSlaveThread::try_pop(SharedNodePointer& node) {
return _pool._queue.try_pop(node);
}
#ifdef AVATAR_SINGLE_THREADED
static AvatarMixerSlave slave;
#endif
void AvatarMixerSlavePool::processIncomingPackets(ConstIter begin, ConstIter end) {
_function = &AvatarMixerSlave::processIncomingPackets;
_configure = [=](AvatarMixerSlave& slave) {
@ -89,19 +85,9 @@ void AvatarMixerSlavePool::run(ConstIter begin, ConstIter end) {
_begin = begin;
_end = end;
#ifdef AUDIO_SINGLE_THREADED
_configure(slave);
std::for_each(begin, end, [&](const SharedNodePointer& node) {
_function(slave, node);
});
#else
// fill the queue
std::for_each(_begin, _end, [&](const SharedNodePointer& node) {
#if defined(__clang__) && defined(Q_OS_LINUX)
_queue.push(node);
#else
_queue.emplace(node);
#endif
});
{
@ -121,18 +107,13 @@ void AvatarMixerSlavePool::run(ConstIter begin, ConstIter end) {
}
assert(_queue.empty());
#endif
}
void AvatarMixerSlavePool::each(std::function<void(AvatarMixerSlave& slave)> functor) {
#ifdef AVATAR_SINGLE_THREADED
functor(slave);
#else
for (auto& slave : _slaves) {
functor(*slave.get());
}
#endif
}
void AvatarMixerSlavePool::setNumThreads(int numThreads) {
@ -158,9 +139,6 @@ void AvatarMixerSlavePool::setNumThreads(int numThreads) {
void AvatarMixerSlavePool::resize(int numThreads) {
assert(_numThreads == (int)_slaves.size());
#ifdef AVATAR_SINGLE_THREADED
qDebug("%s: running single threaded", __FUNCTION__, numThreads);
#else
qDebug("%s: set %d threads (was %d)", __FUNCTION__, numThreads, _numThreads);
Lock lock(_mutex);
@ -208,5 +186,4 @@ void AvatarMixerSlavePool::resize(int numThreads) {
_numThreads = _numStarted = _numFinished = numThreads;
assert(_numThreads == (int)_slaves.size());
#endif
}

View file

@ -0,0 +1,28 @@
//
// MixerAvatar.h
// assignment-client/src/avatars
//
// Created by Simon Walton Feb 2019.
// Copyright 2019 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// Avatar class for use within the avatar mixer - encapsulates data required only for
// sorting priorities within the mixer.
#ifndef hifi_MixerAvatar_h
#define hifi_MixerAvatar_h
#include <AvatarData.h>
class MixerAvatar : public AvatarData {
public:
private:
};
using MixerAvatarSharedPointer = std::shared_ptr<MixerAvatar>;
#endif // hifi_MixerAvatar_h

View file

@ -1203,7 +1203,8 @@ void OctreeServer::beginRunning() {
auto nodeList = DependencyManager::get<NodeList>();
// we need to ask the DS about agents so we can ping/reply with them
nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer });
nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer,
NodeType::AvatarMixer });
beforeRun(); // after payload has been processed

View file

@ -1,6 +1,6 @@
macro(target_oculus_mobile)
set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/oculus/VrApi)
set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/oculus_1.22/VrApi)
# Mobile SDK
set(OVR_MOBILE_INCLUDE_DIRS ${INSTALL_DIR}/Include)

View file

@ -1302,6 +1302,14 @@
"placeholder": "1",
"default": "1",
"advanced": true
},
{
"name": "connection_rate",
"label": "Connection Rate",
"help": "Number of new agents that can connect to the mixer every second",
"placeholder": "50",
"default": "50",
"advanced": true
}
]
},

View file

@ -1243,12 +1243,11 @@ void DomainServer::broadcastNewNode(const SharedNodePointer& addedNode) {
limitedNodeList->eachMatchingNode(
[this, addedNode](const SharedNodePointer& node)->bool {
if (node->getLinkedData() && node->getActiveSocket() && node != addedNode) {
// is the added Node in this node's interest list?
return isInInterestSet(node, addedNode);
} else {
return false;
}
// is the added Node in this node's interest list?
return node->getLinkedData()
&& node->getActiveSocket()
&& node != addedNode
&& isInInterestSet(node, addedNode);
},
[this, &addNodePacket, connectionSecretIndex, addedNode, limitedNodeListWeak](const SharedNodePointer& node) {
// send off this packet to the node
@ -2548,7 +2547,7 @@ bool DomainServer::processPendingContent(HTTPConnection* connection, QString ite
_pendingFileContent.seek(_pendingFileContent.size());
_pendingFileContent.write(dataChunk);
_pendingFileContent.close();
// Respond immediately - will timeout if we wait for restore.
connection->respond(HTTPConnection::StatusCode200);
if (itemName == "restore-file" || itemName == "restore-file-chunk-final" || itemName == "restore-file-chunk-only") {

View file

@ -25,13 +25,13 @@
#include <AccountManager.h>
#include <Assignment.h>
#include <AvatarData.h>
#include <HifiConfigVariantMap.h>
#include <HTTPConnection.h>
#include <NLPacketList.h>
#include <NumericalConstants.h>
#include <SettingHandle.h>
#include <SettingHelpers.h>
#include <AvatarData.h> //for KillAvatarReason
#include <FingerprintUtils.h>
#include "DomainServerNodeData.h"
@ -870,14 +870,6 @@ void DomainServerSettingsManager::processNodeKickRequestPacket(QSharedPointer<Re
}
}
// if we are here, then we kicked them, so send the KillAvatar message
auto packet = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason), true);
packet->write(nodeUUID.toRfc4122());
packet->writePrimitive(KillAvatarReason::NoReason);
// send to avatar mixer, it sends the kill to everyone else
limitedNodeList->broadcastToNodes(std::move(packet), NodeSet() << NodeType::AvatarMixer);
if (newPermissions) {
qDebug() << "Removing connect permission for node" << uuidStringWithoutCurlyBraces(matchingNode->getUUID())
<< "after kick request from" << uuidStringWithoutCurlyBraces(sendingNode->getUUID());

View file

@ -45,10 +45,10 @@ ANDROID_PACKAGES = {
'sharedLibFolder': 'lib',
'includeLibs': ['libnvtt.so', 'libnvmath.so', 'libnvimage.so', 'libnvcore.so']
},
'oculus': {
'file': 'ovr_sdk_mobile_1.19.0.zip',
'versionId': 's_RN1vlEvUi3pnT7WPxUC4pQ0RJBs27y',
'checksum': '98f0afb62861f1f02dd8110b31ed30eb',
'oculus_1.22': {
'file': 'ovr_sdk_mobile_1.22.zip',
'versionId': 'InhomR5gwkzyiLAelH3X9k4nvV3iIpA_',
'checksum': '1ac3c5b0521e5406f287f351015daff8',
'sharedLibFolder': 'VrApi/Libs/Android/arm64-v8a/Release',
'includeLibs': ['libvrapi.so']
},

View file

@ -1016,8 +1016,8 @@
"type": "clip",
"data": {
"url": "qrc:///avatar/animations/run_fwd.fbx",
"startFrame": 0.0,
"endFrame": 21.0,
"startFrame": 1.0,
"endFrame": 22.0,
"timeScale": 1.0,
"loopFlag": true
},

File diff suppressed because it is too large Load diff

View file

@ -5,6 +5,7 @@
{ "from": "Keyboard.D", "when": ["Keyboard.RightMouseButton", "!Keyboard.Control"], "to": "Actions.LATERAL_RIGHT" },
{ "from": "Keyboard.E", "when": "!Keyboard.Control", "to": "Actions.LATERAL_RIGHT" },
{ "from": "Keyboard.Q", "when": "!Keyboard.Control", "to": "Actions.LATERAL_LEFT" },
{ "from": "Keyboard.T", "when": "!Keyboard.Control", "to": "Actions.TogglePushToTalk" },
{ "comment" : "Mouse turn need to be small continuous increments",
"from": { "makeAxis" : [

View file

@ -14,7 +14,6 @@
<glyph glyph-name="headphones-mic" unicode="&#102;" d="M419 348l-22 0c-3 48-42 83-89 83l-105 0c-47 0-86-35-89-83l-20 0c-25 0-45-19-45-44l0-71c0-25 20-45 45-45l19 0c1-27 14-50 33-66-3-17 5-35 20-45l41-25c7-4 15-7 23-7 3 0 6 1 10 2 11 2 21 9 27 19 13 21 6 48-14 60l-41 26c-10 6-21 8-33 5-8-2-15-6-20-11-12 11-20 27-20 45l0 152c0 34 29 62 64 62l105 0c35 0 64-28 64-62l0-152c0-1-1-3-1-3l48 0c25 0 46 20 46 45l0 71c0 25-21 44-46 44z m-306-134l-19 0c-10 0-19 9-19 19l0 71c0 10 9 19 19 19l19 0z m61-90c3 4 7 6 11 7 2 1 3 1 4 1 4 0 7-1 9-3l41-25c8-5 11-16 6-24-3-4-7-7-11-8-5-1-10 0-14 2l-40 25c-8 6-11 16-6 25z m264 109c0-10-8-19-19-19l-22 0 0 109 22 0c11 0 19-9 19-19z"/>
<glyph glyph-name="gamepad" unicode="&#103;" d="M107 136c-10 0-20 3-29 10-46 37-8 131-4 141l1 1c1 4 3 7 5 11 16 30 37 73 81 73l182 0c51 0 71-47 87-85 5-10 44-101 4-138-28-26-67-1-102 22-21 13-42 26-56 26l-39 0c-13 0-33-13-53-27-25-16-52-34-77-34z m-10 141c-10-24-30-90-3-112 17-13 47 7 76 26 24 16 47 31 67 31l40 0c20 0 44-15 68-30 28-18 59-37 72-25 23 22 0 89-10 110-17 42-31 70-64 70l-182 0c-29 0-45-33-59-60-2-3-3-7-5-10z m247-36l-3 0c-7 0-13 6-13 13 0 7 6 13 13 13l3 0c7 0 13-6 13-13 0-7-6-13-13-13z m0 55l-3 0c-7 0-13 6-13 13 0 7 6 13 13 13l3 0c7 0 13-6 13-13 0-7-6-13-13-13z m-29-29l-2 0c-8 0-13 6-13 13 0 8 5 13 13 13l2 0c8 0 13-5 13-13 0-7-5-13-13-13z m57 0l-3 0c-7 0-13 6-13 13 0 8 6 13 13 13l3 0c7 0 13-5 13-13 0-7-6-13-13-13z m-172 26l-12 0 0 13c0 7-6 13-13 13-7 0-13-6-13-13l0-13-13 0c-7 0-13-6-13-13 0-7 6-13 13-13l13 0 0-12c0-8 6-14 13-14 7 0 13 6 13 14l0 12 12 0c8 0 13 6 13 13 0 7-5 13-13 13z"/>
<glyph glyph-name="headphones" unicode="&#104;" d="M141 168l0 152c0 35 28 65 63 65l106 0c34 0 62-30 62-65l0-151c0-1 1-3 0-4l48 0c25 0 47 21 47 46l0 70c0 25-22 45-47 45l-21 0c-4 47-42 83-89 83l-106 0c-47 0-86-36-89-83l-19 0c-25 0-45-20-45-45l0-70c0-25 20-46 45-46l45 0z m-63 43l0 70c0 11 7 19 18 19l19 0 0-108-19 0c-11 0-18 8-18 19z m362 0c0-11-9-19-20-19l-20 0 0 108 20 0c11 0 20-8 20-19z"/>
<glyph glyph-name="mic" unicode="&#105;" d="M318 370c0 33-26 59-59 59l-6 0c-33 0-59-26-59-59l0-105c0-33 26-59 59-59l6 0c33 0 59 26 59 59z m-25-103c0-19-15-34-34-34l-7 0c-19 0-34 15-34 34l0 104c0 19 15 34 34 34l7 0c19 0 34-15 34-34z m82 8c0 7-6 13-12 13-7 0-13-6-13-13 0-51-42-93-93-93-52 0-93 42-93 93 0 7-6 13-13 13-7 0-12-6-12-13 0-60 46-110 104-117l0-34-80 0c-8 0-14-6-14-14 0-7 6-13 14-13l186 0c7 0 13 6 13 13 0 8-6 14-13 14l-80 0 0 34c60 6 106 56 106 117z"/>
<glyph glyph-name="upload" unicode="&#106;" d="M330 193l-83 86-84-86 52 0 0-141 61 23 0 118z m-12 247c-39 0-76-15-105-41-23-21-40-49-47-80-9 3-19 4-29 4-53 0-97-43-97-97 0-54 44-98 97-98 1 0 19 0 45 0l0 29c-26 0-44 0-45 0-37 0-68 31-68 69 0 37 31 68 68 68 12 0 23-3 34-9l19-11 2 23c3 31 18 59 41 81 23 21 54 33 85 33 70 0 127-57 127-127 0-70-57-127-127-127 0 0-5 0-10 0l0-29c5 0 10 0 10 0 86 0 156 70 156 156 0 86-70 156-156 156z"/>
<glyph glyph-name="script" unicode="&#107;" d="M283 80l-150 0c-30 0-56 15-73 44-13 21-17 42-17 42l-3 15 91 0 0 252 315 0 0-238c1-7 5-58-21-87-13-14-29-21-50-21-42 0-63 23-73 41-6 11-9 21-10 29l-220 0c2-6 5-13 9-20 13-21 31-32 52-32l150 0c7 0 13-6 13-13 0-6-6-12-13-12z m-127 101l158 0 1-12c0 0 1-15 9-30 10-18 27-28 51-28 13 0 23 5 31 13 10 11 14 29 15 42 1 15 0 27 0 27l0 1 0 214-265 0z m225 168l-185 0c-8 0-14 6-14 13 0 8 6 14 14 14l185 0c8 0 14-6 14-14 0-7-6-13-14-13z m0-61l-185 0c-8 0-14 7-14 14 0 8 6 14 14 14l185 0c8 0 14-6 14-14 0-7-6-14-14-14z m0-60l-185 0c-8 0-14 6-14 14 0 7 6 13 14 13l185 0c8 0 14-6 14-13 0-8-6-14-14-14z"/>
<glyph glyph-name="text" unicode="&#108;" d="M220 134l-81 232c-1 2-3 4-6 4l-10 0c-3 0-5-2-6-4l-83-233c-1-2 0-5 1-7 1-1 3-3 6-3l16 0c3 0 5 3 6 5l27 79 74 0 27-79c1-2 4-5 7-5l16 0c2 0 4 2 5 4 2 1 2 4 1 7z m-120 102l26 73c1 2 1 3 2 5 0-2 1-3 2-4l24-74z m252 60c-10 12-25 18-44 18-17 0-35-5-53-14-3-2-4-6-3-9l5-14c1-2 2-3 4-4 2-1 4 0 6 1 14 8 28 12 41 12 11 0 19-3 23-10 5-7 8-18 8-33l0-4-23-1c-25 0-44-6-58-16-14-11-21-26-21-45 0-17 4-31 14-40 10-10 23-16 40-16 12 0 23 3 32 8 6 4 11 8 17 14l1-13c1-4 4-7 7-7l10 0c4 0 8 4 8 8l0 115c0 23-5 39-14 50z m-87-119c0 11 4 19 11 24 8 5 22 9 42 10l19 1 0-10c0-17-4-30-12-39-8-9-19-13-33-13-9 0-16 2-20 7-5 4-7 11-7 20z m186-105c-10 0-15 8-15 18 0 22 0 298 0 320 0 10 5 19 15 19 0 0 0 0 0 0 10 0 15-8 15-18 1-22 1-299 1-321 0-10-6-18-16-18 0 0 0 0 0 0z"/>
@ -97,7 +96,6 @@
<glyph glyph-name="acceleration" unicode="&#57347;" d="M207 350c3 2 8 3 14 3 3 0 5 0 8-1 2 0 4-1 5-2 2-1 3-2 4-4 1-1 2-3 2-4l0 0c1 1 2 3 3 4 2 1 3 3 5 4 2 1 4 2 7 2 2 1 5 1 8 1 5 0 8 0 11-1 3-2 5-3 7-5 1-2 2-5 3-7 1-3 1-6 1-8l0-35-15 0 0 35c0 1 0 3 0 4 0 2-1 4-2 5-1 1-2 2-3 3-2 1-4 1-7 1-2 0-4 0-6-1-2-1-4-2-6-4-1-1-2-3-3-5-1-2-1-4-1-7l0-31-14 0 0 35c0 1-1 3-1 4 0 2-1 3-1 5-1 1-2 2-4 3-2 1-4 1-7 1-2 0-4 0-6-1-2-1-4-2-5-4-2-1-3-3-3-5-1-2-2-4-2-7l0-32-14 0 0 41c0 3 0 5 0 8 0 2 0 4-1 6l14 0c0-1 1-3 1-5 0-2 0-3 0-4l0 0c2 3 4 6 8 8z m112-92c0-2 0-4-1-5 0-1-1-3-2-4 0-2-1-3-2-4-1-2-2-3-3-5l-11-15 18 0 0-6-25 0 0 6 14 20c2 2 3 4 4 6 1 2 1 4 1 6 0 3 0 5-1 6-1 2-3 3-5 3-2 0-4-1-5-2-1-2-2-3-2-6l-7 1c1 4 3 7 5 9 2 2 5 3 9 3 2 0 4 0 6-1 1 0 3-1 4-3 1-1 2-2 2-4 1-2 1-3 1-5z m-112-18c-1 1-2 2-3 3-1 1-3 1-5 1-1 0-3 0-4-2-1-1-2-3-2-5 0-2 1-3 2-4 1-1 3-2 6-3 1-1 2-1 3-2 2-1 3-2 4-3 1-1 2-2 2-4 1-1 1-3 1-5 0-2 0-4-1-6-1-2-2-3-3-5-1-1-3-2-4-2-2-1-4-1-6-1-3 0-5 0-7 1-3 1-4 3-6 5l5 5c1-2 2-3 3-4 2 0 3-1 5-1 2 0 4 1 5 2 1 2 2 3 2 6 0 1 0 2-1 3 0 1-1 2-2 2 0 1-1 1-2 2-1 0-2 1-3 1-2 1-3 1-4 2-1 0-2 1-3 2-1 1-1 2-2 4 0 1-1 3-1 5 0 2 1 4 1 5 1 2 2 3 3 5 1 1 3 2 4 2 2 1 4 1 6 1 2 0 5 0 7-1 2-1 3-2 5-4z m18-16c0-2 0-4 0-6 1-2 1-4 2-5 1-1 2-3 3-3 1-1 3-2 4-2 2 0 4 1 5 2 1 2 2 3 3 5l6-3c-2-3-3-6-6-8-2-1-5-2-8-2-5 0-10 2-13 6-3 4-4 10-4 18 0 4 0 7 1 10 1 3 2 6 4 8 1 2 3 3 5 5 2 1 4 1 7 1 2 0 5 0 7-1 2-2 3-3 5-5 1-2 2-5 2-7 1-3 1-6 1-9l0-4-24 0z m16 6c0 4 0 7-2 10-1 2-3 4-6 4-1 0-3-1-4-2-1-1-2-2-2-3-1-2-1-3-2-5 0-1 0-2 0-4l16 0z m31-28c-3 0-5 0-7 1-2 2-4 3-5 5-2 3-3 5-3 8-1 3-1 6-1 10 0 4 0 7 1 10 0 3 1 5 3 8 1 2 3 3 5 5 2 1 5 1 7 1 3 0 5 0 6-1 2-1 3-1 4-2l-4-6c0 1-1 1-2 2-1 0-2 1-4 1-1 0-2-1-3-2-1-1-2-2-3-3-1-2-1-4-2-6 0-2 0-4 0-7 0-2 0-5 0-7 1-2 1-4 2-5 1-2 2-3 3-4 1-1 2-1 4-1 1 0 3 0 4 1 1 0 1 1 2 2l4-6c-3-3-6-4-11-4z m17 74l-107 0c-2 0-4 2-4 4 0 2 2 4 4 4l107 0c2 0 4-2 4-4 0-2-2-4-4-4z"/>
<glyph glyph-name="particles" unicode="&#57348;" d="M332 229c0 12 10 23 23 23 13 0 23-11 23-23 0-13-10-24-23-24-13 0-23 11-23 24z m-54-68c0 13 10 23 23 23 13 0 23-10 23-23 0-13-10-23-23-23-13 0-23 10-23 23z m-62-18c0 13 11 23 24 23 12 0 23-10 23-23 0-13-11-23-23-23-13 0-24 10-24 23z m-46 60c0 13 10 23 23 23 13 0 23-10 23-23 0-13-10-23-23-23-13 0-23 10-23 23z m81-138c-5 3-8 9-9 15-1 6 1 12 4 17 4 5 9 8 15 9 6 1 13 0 18-4 5-4 8-9 9-15 1-6-1-13-4-18-4-4-9-8-16-9-6-1-12 1-17 5z m-183 201c0 13 10 23 23 23 13 0 23-10 23-23 0-13-10-23-23-23-13 0-23 10-23 23z m111 46c0 13 10 24 23 24 13 0 23-11 23-24 0-12-10-23-23-23-13 0-23 11-23 23z m71-94c0 13 11 23 24 23 12 0 23-10 23-23 0-13-11-23-23-23-13 0-24 10-24 23z m163 52c0 13 10 23 23 23 13 0 23-10 23-23 0-13-10-23-23-23-12 0-23 10-23 23z m-111 38c0 13 11 23 23 23 13 0 24-10 24-23 0-13-11-23-24-23-12 0-23 10-23 23z m-170 90c0 13 11 24 24 24 12 0 23-11 23-24 0-12-11-23-23-23-13 0-24 11-24 23z m235-23c0 13 10 23 23 23 13 0 23-10 23-23 0-13-10-23-23-23-13 0-23 10-23 23z"/>
<glyph glyph-name="voxels" unicode="&#57349;" d="M434 379l-85 49c-4 2-10 2-14 0l-77-45-79 46c-4 2-10 2-14 0l-85-49c-4-3-7-7-7-12l0-98c0-5 3-10 7-13l78-45 0-89c0-5 3-10 7-12l85-49c2-2 5-2 7-2 2 0 5 0 7 2l85 49c4 2 7 7 7 12l0 88 78 45c5 3 7 7 7 12l0 99c0 5-2 9-7 12z m-21-88l-59 34 0 68 59-35z m-69-55l-73 42 0 80 59 35 0-68-29-17c-6-3-8-11-4-16 2-4 6-6 10-6 2 0 4 0 6 1l29 17 60-34z m-75-57l0 66 59-34 0-66z m-26 113l-59 34 0 68 59-34z m-142 68l59 34 0-68-31-18c-6-3-8-11-5-17 2-3 7-6 11-6 2 0 4 1 6 2l31 18 59-34-60-34-70 41z m156-270l-71 41 0 81 59 34 0-67-28-16c-6-3-8-11-5-17 3-3 7-6 11-6 2 0 4 1 6 2l28 16 59-34z"/>
<glyph glyph-name="lock" unicode="&#57350;" d="M389 233l0 62c0 68-55 124-123 124-69 0-124-56-124-124l0-62c-24-4-44-26-44-52l0-74c0-29 24-52 52-52l230 0c29 0 53 23 53 52l0 74c0 26-18 48-44 52z m-123 129c37 0 67-30 67-67l0-61-135 0 0 61c0 37 31 67 68 67z"/>
<glyph glyph-name="visible" unicode="&#57351;" d="M258 116c-55 0-106 17-147 51-31 25-47 51-47 52-4 7-4 16 1 23 2 4 66 98 195 96 133-3 192-93 195-97 4-6 4-15 0-22 0-1-15-27-46-53-29-23-79-50-151-50 0 0 0 0 0 0z m-148 113c7-7 17-18 30-29 34-27 73-40 118-40 0 0 0 0 0 0 47 0 88 13 122 40 13 10 23 21 29 29-7 7-16 16-30 26-34 25-74 38-119 38-81 2-130-42-150-64z m-27 1z m227-4c0-25-21-46-47-46-26 0-47 21-47 46 0 26 21 47 47 47 26 0 47-21 47-47z"/>
<glyph glyph-name="model" unicode="&#57352;" d="M494 395c-2 5-8 8-13 7l-90-16 45 72c3 5 2 11-1 15-4 4-10 5-15 3l-213-98c-15 5-72 27-111 43 0 0-1 0-1 0 0 0 0 0 0 0 0 0-1 0-1 1 0 0-1 0-1 0 0 0-1 0-1 0 0 0 0 0 0 0-1 0-1 0-2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-1 0-1 0 0 0 0 0 0-1-1 0-1 0-2 0 0 0 0 0 0 0 0 0-1 0-1 0 0 0 0-1 0-1-1 0-2 0-3-1 0 0 0 0 0 0 0-1-1-1-1-1 0 0 0 0 0 0 0 0 0-1 0-1-1 0-1 0-1 0 0 0 0 0 0-1 0 0 0 0-1-1l-27-52-33-40c-3-4-3-10 0-15 2-3 6-5 10-5 1 0 2 0 4 1l50 17 40 2-26-51c-3-4-2-9 1-13 1-1 26-30 52-58 15-17 28-30 38-40 6-6 11-11 15-14l-16-61-46-18c-6-3-9-10-6-16 2-5 6-8 11-8 1 0 3 1 4 1l45 18 17-15c2-3 5-4 8-4 4 0 7 2 9 5 5 5 4 12-1 17l-17 15 16 61 76-90c2-2 6-4 9-4 1 0 2 0 3 0l85 23c5 2 8 6 9 11 0 5-2 9-7 12l-136 72 45 91 178 123c5 3 6 9 4 15z m-200-117l-122 55 41 21 181 83z m-148 73l-24 33c16-6 39-15 54-21z m-59-6l-9 13 15 29 27-38 2-2z m36-77l23 44c18-45 35-91 47-121-19 20-45 49-70 77z m194-194l-57 68 105-55z m-94 101c-5 14-42 104-30 77 0-2-21 49-28 66l121-59-48-95z m108 120l43 63 70 16z"/>
<glyph glyph-name="avatar-2" unicode="&#57353;" d="M256 88c-93 0-169 75-169 168 0 93 76 169 169 169 93 0 169-76 169-169 0-93-76-168-169-168z m0 316c-81 0-148-66-148-148 0-81 67-147 148-147 81 0 148 66 148 147 0 82-67 148-148 148z m97-90l-1 1c-3 3-7 4-10 4-1 0-61-9-86-9-1 0-1 0-2 0-25 0-87 10-87 10-5 0-10-2-13-6l-1-2c-2-3-2-7-1-10 1-4 3-6 6-8 12-5 49-20 60-22 2 0 5 0 6-7 1-8-3-46-7-65-5-17-13-40-13-41-2-6 1-13 7-15l8-3c3-1 6-1 9 1 3 1 5 4 6 7l21 65 20-67c1-3 3-6 6-7 2-1 4-1 5-1 2 0 3 0 5 0l7 3c5 2 8 8 7 14 0 0-6 24-11 44-3 12-4 30-5 45 0 9-1 16-2 22 0 1 0 4 5 5 0 0 1 0 2 0l55 22c4 2 6 5 7 9 1 4 0 8-3 11z m-68 37c0-16-13-29-29-29-16 0-29 13-29 29 0 16 13 29 29 29 16 0 29-13 29-29z"/>
@ -137,7 +135,6 @@
<glyph glyph-name="wallet" unicode="&#57383;" d="M400 400c-3 10-8 19-15 24-7 5-15 8-24 7l-227 0c-19 0-35-15-35-34l0-58c-19-2-34-18-34-37l0-95c0-19 15-35 34-37l0-53c0-19 16-34 35-34l228 0c20 0 30 16 38 31l0 1c1 2 21 53 21 139 0 83-19 140-21 146z m-309-193l0 95c0 6 5 11 11 11l86 0c6 0 11-5 11-11l0-95c0-6-5-11-11-11l-86 0c-6 0-11 5-11 11z m285-82c-8-16-11-16-14-16l-228 0c-5 0-9 4-9 8l0 53 63 0c21 0 37 17 37 37l0 95c0 20-16 37-37 37l-63 0 0 58c0 4 4 8 9 8l228 0c3 0 10 1 13-13l0 0 0-1c1 0 20-55 20-137 0-77-17-124-19-129z"/>
<glyph glyph-name="send" unicode="&#57384;" d="M391 376c4-4 4-10 2-16-7-21-14-42-22-63-21-63-43-125-65-188-1-4-4-7-6-10-8-6-17-4-22 5-15 28-30 56-44 85-1 1 0 3 0 4 18 21 35 43 53 64 5 6 10 12 15 18 4 6 5 10 1 14-4 4-8 3-14-1-18-15-36-30-54-44-9-8-19-16-28-24-1-1-3-1-4 0-29 14-57 29-85 44-6 3-8 8-8 14 1 7 5 11 11 13 36 13 72 25 107 37 49 17 97 34 145 51 7 2 13 2 18-3z"/>
<glyph glyph-name="password" unicode="&#57385;" d="M104 267l0 41 22 0 0-41 35 20 11-19-35-20 35-20-11-20-35 20 0-40-22 0 0 40-35-20-11 20 35 20-35 20 11 19z m136 0l0 41 23 0 0-41 35 20 11-19-35-20 35-20-11-20-35 20 0-40-23 0 0 40-35-20-11 20 35 20-35 20 11 19z m137 0l0 41 23 0 0-41 34 20 12-19-35-20 35-20-12-20-35 20 0-40-22 0 0 40-35-20-11 20 35 20-35 20 11 19z"/>
<glyph glyph-name="rez" unicode="&#57381;" d="M373 321c-2 5-6 8-11 8l-49 8 55 61c4 4 5 9 3 14-2 5-7 8-12 8 0 0 0 0 0 0l-114-1c-5-1-10-4-12-9l-54-136c-1-4-1-8 1-11 2-4 6-6 9-7l38-5-54-136c-2-6 0-13 6-16 2-1 4-2 7-2 3 0 7 2 10 5l175 206c3 4 3 9 2 13z"/>
<glyph glyph-name="keyboard-collapse" unicode="&#57387;" d="M373 249l-26 0 0 25 26 0z m-35 0l-27 0 0 25 27 0z m-36 0l-27 0 0 25 27 0z m-36 0l-26 0 0 25 26 0z m-35 0l-27 0 0 25 27 0z m-36 0l-27 0 0 25 27 0z m-36 0l-26 0 0 25 26 0z m224-1l18 0c7 0 13 6 13 13 0 7-6 13-13 13l-18 0m-262 0l-17 0c-7 0-13-6-13-13 0-7 6-13 13-13l17 0m252 39l-31 0 0 25 31 0z m-42 0l-31 0 0 25 31 0z m-41 0l-32 0 0 25 32 0z m-42 0l-32 0 0 25 32 0z m-42 0l-32 0 0 25 32 0z m-41 0l-32 0 0 25 32 0z m218-1l18 0c7 0 13 6 13 13 0 8-6 14-13 14l-18 0m-262 0l-17 0c-7 0-13-6-13-14 0-7 6-13 13-13l17 0m288-124l-315 0c-33 0-59 28-59 61l0 76c0 34 26 61 59 61l315 0c33 0 59-27 59-61l0-76c1-33-26-61-59-61z m-315 172c-18 0-33-16-33-34l0-77c0-19 15-34 33-34l315 0c18 0 33 15 33 34l0 77c0 19-15 34-33 34z m248-99l32 0 0-25-32 0z m-41 0l31 0 0-25-31 0z m-42 0l31 0 0-25-31 0z m-43 0l32 0 0-25-32 0z m-42 0l32 0 0-25-32 0z m-41 0l31 0 0-25-31 0z m250-26l18 0c7 0 13 6 13 14 0 7-6 13-13 13l-18 0m-262 0l-17 0c-7 0-13-6-13-13 0-8 6-13 13-13l17 0m81-82l50-50 53 54-107 0"/>
<glyph glyph-name="image" unicode="&#57386;" d="M257 428c52 0 104 0 156 0 24 0 37-13 37-37 1-90 1-179 0-269 0-25-13-38-39-38-103 0-207 0-311 0-26 0-39 13-39 40 0 88 0 176 0 263 0 28 13 41 41 41 51 0 103 0 155 0z m167-263c0 7 0 10 0 14 0 69 0 138 0 206 0 17 0 17-17 17-101 0-202 0-303 0-16 0-17-1-17-17 0-58 0-115 0-173 0-3 0-7 0-12 8 3 14 6 19 9 17 8 30 7 44-6 5-5 10-10 15-15 5-7 11-8 19-4 40 21 81 41 121 61 19 10 31 8 46-7 9-9 18-18 27-27 15-15 29-29 46-46z m-328-54c7 0 11-1 15-1 98 0 197 0 296 0 6 0 14 2 16 6 5 7 0 14-6 20-27 26-54 53-80 80-8 9-15 9-26 4-67-35-135-68-203-102-3-2-6-4-12-7z m-8 26c21 10 40 20 63 31-8 7-14 12-20 17-2 2-7 3-10 3-30-9-36-17-34-48 0 0 0-1 1-3z m134 169c1-25-21-46-46-46-25-1-46 20-47 46 0 25 21 46 47 47 25 0 46-21 46-47z m-46 22c-12 0-22-9-22-21 0-13 9-22 21-23 13 0 23 9 23 22 0 13-10 22-22 22z"/>
<glyph glyph-name="environments" unicode="&#57388;" d="M256 454c-110 0-199-89-199-199 0-110 89-199 199-199 0 0 0 0 0 0l2-1 0 1c109 1 197 90 197 199 0 110-89 199-199 199z m114-204l0 0 0 5c0 0 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 2l0 4 0 0c-1 22-4 43-10 63 21 5 36 11 46 15 15-26 24-56 24-87 0-30-8-59-23-85-8 4-23 11-47 16 6 21 9 42 10 64z m-19 102c-13 30-30 53-46 69 34-10 64-30 86-57-9-4-22-8-40-12z m-6-95c0-23-4-45-11-66-20 4-42 6-65 6l0 120c23 1 45 3 66 6 6-21 10-43 10-66z m-46-170c17 17 37 41 52 75 20-5 34-10 42-13-24-31-57-53-94-62z m-30 256l0 77c17-14 40-37 56-73-17-2-36-4-56-4z m0-248l0 77c20-1 39-3 56-5-16-35-40-59-56-72z m-83 252c17 35 40 59 56 73l0-77c-19 0-38 1-56 4z m-19-90c0 23 4 45 10 65 20-3 42-5 65-5l0-120c-23 0-45-1-65-4-6 20-10 42-10 64z m-47 106c22 28 52 48 86 58-15-16-33-39-46-70-18 4-31 8-40 12z m40-199c15-36 37-61 53-77-38 10-72 32-96 64 9 4 24 9 43 13z m82 8l0-77c-16 13-39 37-56 73 18 2 37 3 56 4z m-100 92l-1 0 0-4c0-1 0-1 0-2 0 0 0 0 0-1 0 0 0 0 0-1 0 0 0-1 0-1l0-5 1 0c0-21 4-42 10-62-23-5-39-10-49-15-14 25-21 53-21 82 0 30 8 60 23 86 10-4 25-10 47-14-6-20-10-41-10-63z"/>
@ -156,4 +153,10 @@
<glyph glyph-name="avatar-1" unicode="&#84;" d="M396 344l-2 2c-4 4-9 5-15 5-1 0-88-13-124-14-1 0-2 0-3 0-37 0-126 15-127 15-7 1-14-2-18-8l-2-4c-3-4-3-9-2-14 2-5 5-9 10-11 16-7 69-22 85-29 3-1 10-4 10-14 1-11-4-67-10-93-7-26-18-60-19-60-3-9 2-19 11-22l11-4c4-2 9-1 13 1 5 2 8 6 9 10l31 94 28-96c2-5 5-9 9-11 3-1 5-2 8-2 2 0 4 0 7 1l10 4c8 3 12 12 10 20 0 1-8 36-16 65-4 17-6 43-7 64-1 13-1 21-3 30 0 1 2 11 10 14 10 4 81 29 80 28 6 2 10 7 11 13 1 6-1 12-5 16z m-98 54c0-24-19-43-43-43-24 0-43 19-43 43 0 23 19 42 43 42 24 0 43-19 43-42z"/>
<glyph glyph-name="steam-square" unicode="&#57397;" d="M391 327c0 15-5 28-16 39-11 11-24 16-39 16-15 0-28-5-39-16-11-11-16-24-16-39 0-15 5-28 16-39 11-11 24-16 39-16 15 0 28 5 39 16 11 11 16 24 16 39z m-174-168c0-16-5-29-16-40-11-11-25-16-40-16-11 0-21 2-30 8-9 5-16 13-20 22 9-4 19-8 28-12 11-4 22-4 34 1 11 5 19 13 24 25 5 11 5 22 0 34-5 11-13 19-25 24l-23 9c4 1 8 2 12 2 15 0 29-6 40-17 11-11 16-24 16-40z m258 234l0-274c0-23-8-42-24-58-16-16-35-24-58-24l-274 0c-23 0-42 8-58 24-16 16-24 35-24 58l0 44 49-20c4-18 12-32 26-44 14-11 30-17 49-17 19 0 37 7 51 20 15 14 23 30 25 50l99 72c28 0 53 10 73 30 20 20 30 44 30 73 0 28-10 52-30 73-20 20-45 30-73 30-28 0-53-10-73-30-20-20-30-44-30-72l-64-92c-2 0-5 0-8 0-15 0-28-4-40-11l-84 34 0 134c0 23 8 42 24 58 16 16 35 24 58 24l274 0c23 0 42-8 58-24 16-16 24-35 24-58z m-70-66c0-19-7-36-20-49-14-14-30-20-49-20-19 0-36 6-49 20-13 13-20 30-20 49 0 19 7 35 20 48 13 14 30 21 49 21 19 0 35-7 49-20 13-14 20-30 20-49z"/>
<glyph glyph-name="oculus" unicode="&#57398;" d="M431 363c-16 13-34 22-54 26-11 3-23 4-34 5-9 0-18 0-26 0-41 0-82 0-123 0-9 0-18 0-26 0-12-1-24-2-35-5-20-4-38-13-54-26-32-26-51-65-51-106 0-41 19-80 51-106 16-13 34-22 54-27 12-2 23-3 35-4 8 0 17 0 26 0 41 0 82 0 123 0 8 0 17 0 26 0 11 0 23 2 34 4 20 5 38 14 54 27 32 26 51 65 51 106 0 41-19 80-51 106z m-60-143c-6-4-13-7-20-8-7-1-14-1-22-1-49 0-99 0-148 0-8 0-15 0-22 1-7 1-14 4-20 8-12 8-19 22-19 37 0 15 7 29 19 37 6 4 13 7 20 8 7 1 14 1 22 1 49 0 99 0 148 0 8 0 15 0 22-1 7-1 14-4 20-8 12-8 19-22 19-37 0-15-7-29-19-37z"/>
<glyph glyph-name="check-circled" unicode="&#57399;" d="M258 74c-48 0-94 19-128 53-34 34-53 80-53 130 0 47 19 92 53 126 35 34 80 53 128 53 0 0 0 0 0 0 49 0 95-19 129-54 34-34 52-80 52-129 0-47-19-92-53-126-35-34-80-53-128-53z m0 324c-38 0-74-15-101-42-27-27-42-62-42-100 0-39 15-75 42-102 27-27 63-42 101-42 38 0 74 15 101 42 27 27 42 62 42 99 0 39-15 75-42 102-27 28-62 43-101 43 0 0 0 0 0 0z m-25-164c-2 3-3 5-4 6-13 13-26 26-39 39-10 11-26 12-36 2-10-10-10-25 1-36 20-21 40-41 60-61 11-10 26-10 36 0 36 35 71 71 106 107 4 3 7 9 8 14 2 10-3 21-13 26-10 4-21 3-29-5-28-29-57-57-85-86-2-1-3-3-5-6z"/>
<glyph glyph-name="rez-01" unicode="&#57381;" d="M373 321c-2 5-6 8-11 8l-49 8 55 61c4 4 5 9 3 14-2 5-7 8-12 8 0 0 0 0 0 0l-114-1c-5-1-10-4-12-9l-54-136c-1-4-1-8 1-11 2-4 6-6 9-7l38-5-54-136c-2-6 0-13 6-16 2-1 4-2 7-2 3 0 7 2 10 5l175 206c3 4 3 9 2 13z"/>
<glyph glyph-name="locked" unicode="&#57350;" d="M367 271c-7 7-15 12-23 17l0 64c0 27-9 50-28 69-19 19-42 28-69 28l-6 0c-27 0-50-9-70-28-19-19-28-42-28-69l0-65c-8-4-16-10-23-16-19-19-29-42-29-69l0-43c0-27 10-50 29-68 20-19 43-28 70-28l108 0c27 0 50 9 69 28 19 19 28 42 28 68l0 43c0 27-9 50-28 69z m-187 81c0 17 6 31 18 42 12 12 26 18 43 18l6 0c17 0 31-6 43-18 12-11 18-25 18-42l0-54c-3 1-7 1-10 1l-108 0c-4 0-7 0-10-1z m179-193c0-17-6-30-18-42-12-12-26-18-43-18l-108 0c-17 0-31 6-43 18-12 12-18 25-18 42l0 43c0 17 6 30 18 42 12 12 26 18 43 18l108 0c17 0 31-6 43-18 12-12 18-25 18-42z m-116 55c-12 0-22-10-22-22l0-55c0-12 10-22 22-22 13 0 23 10 23 22l0 55c0 12-10 22-23 22z"/>
<glyph glyph-name="unlocked" unicode="&#57401;" d="M243 214c-12 0-22-10-22-22l0-55c0-12 10-22 22-22 13 0 23 10 23 22l0 55c0 12-10 22-23 22z m237 207c-19 19-42 28-69 28l-6 0c-27 0-50-9-69-28-20-19-29-42-29-69l0-53c-3 0-6 0-9 0l-108 0c-27 0-50-9-70-28-19-19-29-42-29-69l0-43c0-27 10-50 29-68 19-19 43-28 69-28l109 0c27 0 50 9 69 28 18 19 28 42 28 68l0 43c0 27-9 50-28 69-7 7-15 12-23 17l0 64c0 17 6 31 18 42 12 12 26 18 43 18l6 0c17 0 31-6 43-18 12-11 18-25 18-42l0-81 36 0 0 81c0 27-9 50-28 69z m-121-219l0-43c0-17-6-30-18-42-12-12-26-18-43-18l-108 0c-17 0-31 6-43 18-12 12-18 25-18 42l0 43c0 17 6 30 18 42 12 12 26 18 43 18l108 0c17 0 31-6 43-18 12-12 18-26 18-42z"/>
<glyph glyph-name="mic" unicode="&#105;" d="M299 347l0 47c0 21-17 38-40 38-21 0-39-17-39-38l0-47z m-79-41l0-46c0-21 18-38 39-38 22 0 40 17 40 38l0 46z m159 3l0-56c0-55-43-98-98-108l0-36 46 0c11 0 20-9 20-19 0-11-9-20-20-20l-131 0c-10 0-19 9-19 20 0 10 9 19 19 19l46 0 0 36c-55 9-98 53-98 108l0 58c0 11 10 19 20 18 10 0 17-9 17-20l0-56c0-40 37-72 80-72 44 0 80 32 80 72l0 55c0 11 8 19 18 19 10 0 20-7 20-18z"/>
<glyph glyph-name="92-lock-01" unicode="&#57400;" d="M389 233l0 62c0 68-55 124-123 124-69 0-124-56-124-124l0-62c-24-4-44-26-44-52l0-74c0-29 24-52 52-52l230 0c29 0 53 23 53 52l0 74c0 26-18 48-44 52z m-123 129c37 0 67-30 67-67l0-61-135 0 0 61c0 37 31 67 68 67z"/>
</font></defs></svg>

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 86 KiB

View file

@ -12,7 +12,7 @@
<body>
<div class="container">
<h1>HiFi Glyphs</h1>
<p class="small">This font was created for use in <a href="http://highfidelity.io/">High Fidelity</a></p>
<p class="small">This font was created with<a href="http://fontastic.me/">Fontastic</a></p>
<h2>CSS mapping</h2>
<ul class="glyphs css-mapping">
<li>
@ -43,10 +43,6 @@
<div class="icon icon-headphones"></div>
<input type="text" readonly="readonly" value="headphones">
</li>
<li>
<div class="icon icon-mic"></div>
<input type="text" readonly="readonly" value="mic">
</li>
<li>
<div class="icon icon-upload"></div>
<input type="text" readonly="readonly" value="upload">
@ -375,10 +371,6 @@
<div class="icon icon-voxels"></div>
<input type="text" readonly="readonly" value="voxels">
</li>
<li>
<div class="icon icon-lock"></div>
<input type="text" readonly="readonly" value="lock">
</li>
<li>
<div class="icon icon-visible"></div>
<input type="text" readonly="readonly" value="visible">
@ -535,10 +527,6 @@
<div class="icon icon-password"></div>
<input type="text" readonly="readonly" value="password">
</li>
<li>
<div class="icon icon-rez"></div>
<input type="text" readonly="readonly" value="rez">
</li>
<li>
<div class="icon icon-keyboard-collapse"></div>
<input type="text" readonly="readonly" value="keyboard-collapse">
@ -611,6 +599,30 @@
<div class="icon icon-oculus"></div>
<input type="text" readonly="readonly" value="oculus">
</li>
<li>
<div class="icon icon-check-circled"></div>
<input type="text" readonly="readonly" value="check-circled">
</li>
<li>
<div class="icon icon-rez-01"></div>
<input type="text" readonly="readonly" value="rez-01">
</li>
<li>
<div class="icon icon-locked"></div>
<input type="text" readonly="readonly" value="locked">
</li>
<li>
<div class="icon icon-unlocked"></div>
<input type="text" readonly="readonly" value="unlocked">
</li>
<li>
<div class="icon icon-mic"></div>
<input type="text" readonly="readonly" value="mic">
</li>
<li>
<div class="icon icon-92-lock-01"></div>
<input type="text" readonly="readonly" value="92-lock-01">
</li>
</ul>
<h2>Character mapping</h2>
<ul class="glyphs character-mapping">
@ -642,10 +654,6 @@
<div data-icon="h" class="icon"></div>
<input type="text" readonly="readonly" value="h">
</li>
<li>
<div data-icon="i" class="icon"></div>
<input type="text" readonly="readonly" value="i">
</li>
<li>
<div data-icon="j" class="icon"></div>
<input type="text" readonly="readonly" value="j">
@ -974,10 +982,6 @@
<div data-icon="&#xe005;" class="icon"></div>
<input type="text" readonly="readonly" value="&amp;#xe005;">
</li>
<li>
<div data-icon="&#xe006;" class="icon"></div>
<input type="text" readonly="readonly" value="&amp;#xe006;">
</li>
<li>
<div data-icon="&#xe007;" class="icon"></div>
<input type="text" readonly="readonly" value="&amp;#xe007;">
@ -1134,10 +1138,6 @@
<div data-icon="&#xe029;" class="icon"></div>
<input type="text" readonly="readonly" value="&amp;#xe029;">
</li>
<li>
<div data-icon="&#xe025;" class="icon"></div>
<input type="text" readonly="readonly" value="&amp;#xe025;">
</li>
<li>
<div data-icon="&#xe02b;" class="icon"></div>
<input type="text" readonly="readonly" value="&amp;#xe02b;">
@ -1210,6 +1210,30 @@
<div data-icon="&#xe036;" class="icon"></div>
<input type="text" readonly="readonly" value="&amp;#xe036;">
</li>
<li>
<div data-icon="&#xe037;" class="icon"></div>
<input type="text" readonly="readonly" value="&amp;#xe037;">
</li>
<li>
<div data-icon="&#xe025;" class="icon"></div>
<input type="text" readonly="readonly" value="&amp;#xe025;">
</li>
<li>
<div data-icon="&#xe006;" class="icon"></div>
<input type="text" readonly="readonly" value="&amp;#xe006;">
</li>
<li>
<div data-icon="&#xe039;" class="icon"></div>
<input type="text" readonly="readonly" value="&amp;#xe039;">
</li>
<li>
<div data-icon="i" class="icon"></div>
<input type="text" readonly="readonly" value="i">
</li>
<li>
<div data-icon="&#xe038;" class="icon"></div>
<input type="text" readonly="readonly" value="&amp;#xe038;">
</li>
</ul>
</div>
<script>(function() {
@ -1229,4 +1253,4 @@
</script>
</body>
</html>
</html>

View file

@ -59,9 +59,6 @@
.icon-headphones:before {
content: "\68";
}
.icon-mic:before {
content: "\69";
}
.icon-upload:before {
content: "\6a";
}
@ -308,9 +305,6 @@
.icon-voxels:before {
content: "\e005";
}
.icon-lock:before {
content: "\e006";
}
.icon-visible:before {
content: "\e007";
}
@ -428,9 +422,6 @@
.icon-password:before {
content: "\e029";
}
.icon-rez:before {
content: "\e025";
}
.icon-keyboard-collapse:before {
content: "\e02b";
}
@ -485,3 +476,21 @@
.icon-oculus:before {
content: "\e036";
}
.icon-check-circled:before {
content: "\e037";
}
.icon-rez-01:before {
content: "\e025";
}
.icon-locked:before {
content: "\e006";
}
.icon-unlocked:before {
content: "\e039";
}
.icon-mic:before {
content: "\69";
}
.icon-92-lock-01:before {
content: "\e038";
}

Binary file not shown.

View file

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_4" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 66.7 66" style="enable-background:new 0 0 66.7 66;" xml:space="preserve">
<style type="text/css">
.st0{fill:#00B4EF;}
</style>
<path class="st0" d="M66.7,29.5l-7-5.9l1.3-9l-8.9-2l-3.1-8.5l-8.9,2.3l-6.7-6.1l-6.7,6.1l-8.9-2.3l-3.1,8.5l-8.9,2l1.3,9l-7,5.9
l5.3,7.4l-3.4,8.4l8.2,4.1l0.9,9l9.2-0.2l5.1,7.5l8-4.4l8,4.4l5.1-7.5l9.2,0.2l0.9-9l8.2-4.1l-3.4-8.4L66.7,29.5z M33.3,58.4
C19.3,58.4,8,47,8,33S19.3,7.6,33.3,7.6c14,0,25.4,11.4,25.4,25.4S47.3,58.4,33.3,58.4z M45.5,22.9H21.2c-3,0-5.5,2.3-5.8,5.2h-1.5
c-1.1,0-2.1,0.9-2.1,2c0,0,0,0,0,0v5.7c0,1.1,0.9,2.1,2,2.1c0,0,0,0,0,0h1.5c0.1,0.5,0.2,0.9,0.3,1.3l0.1,0.2
c0.6,1.5,1.8,2.7,3.3,3.3h0.1c0.3,0.1,0.6,0.2,1,0.2c0.1,0,0.2,0.1,0.3,0.1H27c1.1,0,2.2-0.4,3-1.2c0.9-0.9,2.1-1.4,3.3-1.3
c1.2,0,2.4,0.4,3.3,1.3c0.8,0.8,1.9,1.2,3,1.2h5.9c2.9,0,5.4-2.2,5.8-5.1h1.5c1.1,0,2.1-0.9,2.1-2c0,0,0,0,0,0v-5.7
c-0.1-1.1-1-1.9-2.1-1.9h-1.4C51,25.2,48.5,22.9,45.5,22.9z M15.3,36.7h-1.4c-0.4,0-0.7-0.3-0.7-0.7c0,0,0,0,0,0v-5.7
c0-0.4,0.3-0.7,0.7-0.7h1.4V36.7z M33.3,38.1c-1,0-1.8-0.8-1.8-1.8s0.8-1.8,1.8-1.8s1.8,0.8,1.8,1.8S34.3,38.1,33.3,38.1z
M37.2,34.8C37,34.9,36.9,35,36.7,35c-0.3,0-0.6-0.1-0.7-0.4c-0.6-0.9-1.6-1.5-2.7-1.5c-1.1,0-2.1,0.5-2.7,1.5
c-0.3,0.4-0.8,0.5-1.2,0.3c-0.4-0.3-0.5-0.8-0.3-1.2c0.9-1.4,2.5-2.2,4.1-2.2c1.7,0,3.2,0.8,4.1,2.3C37.7,34,37.6,34.6,37.2,34.8z
M39.8,33.1c-0.1,0.1-0.3,0.1-0.5,0.1c-0.3,0-0.6-0.1-0.7-0.4C37.4,31,35.5,30,33.3,30c-2.1,0-4.1,1.1-5.3,2.9
c-0.3,0.4-0.8,0.5-1.2,0.3c-0.4-0.3-0.5-0.8-0.3-1.2c1.5-2.3,4-3.7,6.8-3.7c2.7,0,5.3,1.4,6.8,3.7C40.3,32.3,40.2,32.8,39.8,33.1z
M42.5,31.5c-0.1,0.1-0.3,0.1-0.5,0.1c-0.3,0-0.6-0.1-0.7-0.4c-1.8-2.8-4.7-4.4-8-4.4c-3.2,0-6.2,1.6-8,4.4
c-0.3,0.4-0.8,0.5-1.2,0.3c-0.4-0.3-0.5-0.8-0.3-1.2c2.1-3.3,5.6-5.2,9.5-5.2c3.9,0,7.4,2,9.5,5.2C43,30.7,42.9,31.3,42.5,31.5z
M51.3,29.6h1.4c0.4,0,0.7,0.3,0.7,0.7v5.7c0,0.4-0.3,0.7-0.7,0.7h-1.4V29.6z M30.9,16.9l2.4-1.8l2.4,1.8l-0.9-2.8l2.4-1.8h-3
l-0.9-2.8l-0.9,2.8h-3l2.4,1.8L30.9,16.9z M35.8,49.1l-2.4,1.8l-2.4-1.8l0.9,2.8l-2.4,1.8h3l0.9,2.8l0.9-2.8h3l-2.4-1.8L35.8,49.1z
M42,17.4l1.3,2.7l0.6-2.9l3-0.4l-2.6-1.5l0.6-2.9l-2.2,2L40,13l1.3,2.7l-2.2,2L42,17.4z M24.7,48.6l-1.3-2.7l-0.6,2.9l-3,0.4
l2.6,1.5l-0.6,2.9l2.2-2l2.6,1.5l-1.3-2.7l2.2-2L24.7,48.6z M43.8,48.8l-0.6-2.9L42,48.6l-3-0.4l2.2,2L40,53l2.6-1.5l2.2,2l-0.6-2.9
l2.6-1.5L43.8,48.8z M22.8,17.2l0.6,2.9l1.3-2.7l3,0.4l-2.2-2l1.3-2.7l-2.6,1.5l-2.2-2l0.6,2.9l-2.6,1.5L22.8,17.2z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View file

@ -0,0 +1 @@
<svg id="Art" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50"><defs><style>.cls-1{fill-rule:evenodd;}</style></defs><title>mic-ptt-a</title><path class="cls-1" d="M34.52,10.09l-1.85,1.85a13.19,13.19,0,0,0-9.82-4.46,13.35,13.35,0,0,0-9.58,4.2L11.42,9.84a15.66,15.66,0,0,1,23.1.25Zm-6.13,6.13,1.85-1.85a9.62,9.62,0,0,0-14.53-.25L17.55,16a7.34,7.34,0,0,1,5.3-2.44A6.85,6.85,0,0,1,28.39,16.22ZM23,16a3.58,3.58,0,0,1,1.51.3,3.68,3.68,0,0,1,1.26.88,3.88,3.88,0,0,1,.84,1.3A3.94,3.94,0,0,1,26.9,20v5.07a1.91,1.91,0,0,1,.48-.05A3.93,3.93,0,0,1,30,26.07a3.38,3.38,0,0,1,1.5-.32,3.27,3.27,0,0,1,2.77,1.36,2.75,2.75,0,0,1,.85-.1,3.35,3.35,0,0,1,1.33.25,3.18,3.18,0,0,1,1.12.76,3.23,3.23,0,0,1,.73,1.13,3.32,3.32,0,0,1,.24,1.32v3.31a12.27,12.27,0,0,1-.43,3.41l-1.36,5.65a2.67,2.67,0,0,1-1,1.55A2.89,2.89,0,0,1,34,45H23a4.47,4.47,0,0,1-1.76-.43,3.88,3.88,0,0,1-1.36-1.12L14.1,35.7a3.72,3.72,0,0,1-.8-2.35,3.64,3.64,0,0,1,.28-1.5,3.75,3.75,0,0,1,.84-1.27,3.9,3.9,0,0,1,2.77-1.18,4.5,4.5,0,0,1,2,.54V19.88a4.06,4.06,0,0,1,1.13-2.78,3.74,3.74,0,0,1,1.25-.83A3.85,3.85,0,0,1,23,16Zm0,2a1.89,1.89,0,0,0-.74.12,2,2,0,0,0-1.06,1,1.92,1.92,0,0,0-.15.74V35.21l-2.32-3.06a2,2,0,0,0-.7-.59,1.88,1.88,0,0,0-.9-.2,1.85,1.85,0,0,0-.74.15,2,2,0,0,0-.63.43,2,2,0,0,0-.4.63,1.9,1.9,0,0,0-.13.74,2,2,0,0,0,.38,1.17l5.86,7.79a1.79,1.79,0,0,0,.68.57A1.74,1.74,0,0,0,23,43H34a1.23,1.23,0,0,0,.59-.15.88.88,0,0,0,.24-.23.71.71,0,0,0,.13-.31l1.37-5.6a12,12,0,0,0,.37-3V30.43a1.7,1.7,0,0,0-.43-1.07,1.31,1.31,0,0,0-.47-.37,1.35,1.35,0,0,0-.59-.11,1.46,1.46,0,0,0-.55.11,1.23,1.23,0,0,0-.46.32,1.64,1.64,0,0,0-.43,1.07h-.48v-1a1.52,1.52,0,0,0-.12-.66,1.61,1.61,0,0,0-.37-.56,1.63,1.63,0,0,0-1.22-.54,2,2,0,0,0-1.23.54,1.77,1.77,0,0,0-.36.53,1.57,1.57,0,0,0-.11.64v1h-.49V29a2.22,2.22,0,0,0-.58-1.44,1.71,1.71,0,0,0-.62-.44A1.88,1.88,0,0,0,27.4,27a2,2,0,0,0-.74.13,1.85,1.85,0,0,0-.63.41,2,2,0,0,0-.53,1.36v1.5h-.57V20a2,2,0,0,0-.54-1.44,1.75,1.75,0,0,0-.63-.44A1.73,1.73,0,0,0,23,18Z"/></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Art" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<path class="st0" d="M16.7,13.2c-0.3,0.3-0.7,0.6-1,0.9l1.8,1.9c1.4-1.5,3.3-2.4,5.3-2.4c2.2,0,4.2,0.9,5.5,2.7l1.8-1.8
C26.8,10.3,20.8,9.8,16.7,13.2z M32.7,11.9l1.9-1.9c-0.3-0.3-0.6-0.7-1-1c-6.3-5.9-16.2-5.6-22.1,0.7l1.9,1.8
c2.5-2.6,5.9-4.2,9.6-4.2C26.6,7.5,30.2,9.1,32.7,11.9z M38.3,29.1c-0.2-0.4-0.4-0.8-0.7-1.1c-0.3-0.3-0.7-0.6-1.1-0.8
C36,27.1,35.6,27,35.1,27c-0.3,0-0.6,0-0.8,0.1c-0.6-0.9-1.7-1.4-2.8-1.4c-0.5,0-1,0.1-1.5,0.3c-0.7-0.7-1.6-1-2.6-1.1
c-0.2,0-0.3,0-0.5,0.1V20c0-0.5-0.1-1-0.3-1.5c-0.2-0.5-0.5-0.9-0.8-1.3c-0.4-0.4-0.8-0.7-1.3-0.9C24,16.1,23.5,16,23,16
c-0.5,0-1,0.1-1.4,0.3c-0.5,0.2-0.9,0.5-1.3,0.8c-0.7,0.7-1.1,1.7-1.1,2.8v10.1c-0.6-0.3-1.3-0.5-2-0.5c-1,0-2,0.4-2.8,1.2
c-0.4,0.4-0.6,0.8-0.8,1.3c-0.2,0.5-0.3,1-0.3,1.5c0,0.9,0.3,1.7,0.8,2.4l5.8,7.8c0.4,0.5,0.8,0.9,1.4,1.1c0.6,0.3,1.2,0.4,1.8,0.4
h11c0.6,0,1.2-0.2,1.8-0.6c0.5-0.4,0.9-0.9,1-1.6l1.4-5.6c0.3-1.1,0.4-2.3,0.4-3.4v-3.3C38.6,30,38.5,29.6,38.3,29.1z M36.7,33.7
c0,1-0.1,2-0.4,3L35,42.3c0,0.1-0.1,0.2-0.1,0.3c-0.1,0.1-0.1,0.2-0.2,0.2C34.4,42.9,34.2,43,34,43H23c-0.3,0-0.6,0-0.8-0.2
c-0.3-0.1-0.5-0.3-0.7-0.6l-5.9-7.8c-0.2-0.3-0.4-0.7-0.4-1.2c0-0.3,0-0.5,0.1-0.7c0.1-0.2,0.2-0.4,0.4-0.6c0.2-0.2,0.4-0.3,0.6-0.4
c0.2-0.1,0.5-0.2,0.7-0.2c0.3,0,0.6,0.1,0.9,0.2c0.3,0.1,0.5,0.3,0.7,0.6l2.3,3.1V19.9c0-0.3,0.1-0.5,0.2-0.7c0.2-0.5,0.6-0.8,1.1-1
C22.5,18,22.7,18,23,18c0.3,0,0.5,0,0.8,0.1c0.2,0.1,0.5,0.2,0.6,0.4c0.4,0.4,0.6,0.9,0.5,1.4v10.4h0.6v-1.5c0-0.5,0.2-1,0.5-1.4
c0.2-0.2,0.4-0.3,0.6-0.4c0.2-0.1,0.5-0.1,0.7-0.1c0.3,0,0.5,0,0.8,0.1c0.2,0.1,0.4,0.2,0.6,0.4c0.4,0.4,0.6,0.9,0.6,1.4v1.3h0.5v-1
c0-0.2,0-0.4,0.1-0.6c0.1-0.2,0.2-0.4,0.4-0.5c0.3-0.3,0.8-0.5,1.2-0.5c0.5,0,0.9,0.2,1.2,0.5c0.2,0.2,0.3,0.3,0.4,0.6
c0.1,0.2,0.1,0.4,0.1,0.7v1h0.5c0-0.4,0.2-0.8,0.4-1.1c0.1-0.1,0.3-0.3,0.5-0.3c0.2-0.1,0.4-0.1,0.6-0.1c0.2,0,0.4,0,0.6,0.1
c0.2,0.1,0.3,0.2,0.5,0.4c0.3,0.3,0.4,0.7,0.4,1.1V33.7z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -113,6 +113,10 @@ Item {
visible: root.expanded
text: "Avatars Updated: " + root.updatedAvatarCount
}
StatText {
visible: root.expanded
text: "Heroes Count/Updated: " + root.heroAvatarCount + "/" + root.updatedHeroAvatarCount
}
StatText {
visible: root.expanded
text: "Avatars NOT Updated: " + root.notUpdatedAvatarCount

View file

@ -115,6 +115,10 @@ Item {
visible: root.expanded
text: "Avatars Updated: " + root.updatedAvatarCount
}
StatText {
visible: root.expanded
text: "Heroes Count/Updated: " + root.heroAvatarCount + "/" + root.updatedHeroAvatarCount
}
StatText {
visible: root.expanded
text: "Avatars NOT Updated: " + root.notUpdatedAvatarCount

View file

@ -27,6 +27,7 @@ Item {
property string labelGlyphOnText: "";
property int labelGlyphOnSize: 32;
property alias checked: originalSwitch.checked;
property string backgroundOnColor: "#252525";
signal onCheckedChanged;
signal clicked;
@ -40,10 +41,10 @@ Item {
onClicked: rootSwitch.clicked();
hoverEnabled: true
topPadding: 3;
topPadding: 1;
leftPadding: 3;
rightPadding: 3;
bottomPadding: 3;
bottomPadding: 1;
onHoveredChanged: {
if (hovered) {
@ -54,7 +55,7 @@ Item {
}
background: Rectangle {
color: "#252525";
color: checked ? backgroundOnColor : "#252525";
implicitWidth: rootSwitch.switchWidth;
implicitHeight: rootSwitch.height;
radius: rootSwitch.switchRadius;

View file

@ -273,11 +273,7 @@ ModalWindow {
onTriggered: {
root.result = null;
root.canceled();
// FIXME we are leaking memory to avoid a crash
// root.destroy();
root.disableFade = true
visible = false;
root.destroy();
}
}
@ -299,11 +295,7 @@ ModalWindow {
}
root.result = JSON.stringify(result);
root.selected(root.result);
// FIXME we are leaking memory to avoid a crash
// root.destroy();
root.disableFade = true
visible = false;
root.destroy();
}
}
}

View file

@ -815,7 +815,7 @@ ModalWindow {
Action {
id: cancelAction
text: "Cancel"
onTriggered: { canceled(); root.shown = false; }
onTriggered: { canceled(); root.destroy(); }
}
}

View file

@ -168,11 +168,7 @@ ModalWindow {
shortcut: "Esc"
onTriggered: {
root.canceled();
// FIXME we are leaking memory to avoid a crash
// root.destroy();
root.disableFade = true
visible = false;
root.destroy();
}
}
@ -183,11 +179,7 @@ ModalWindow {
onTriggered: {
root.result = items ? comboBox.currentText : textResult.text
root.selected(root.result);
// FIXME we are leaking memory to avoid a crash
// root.destroy();
root.disableFade = true
visible = false;
root.destroy();
}
}
}

View file

@ -30,6 +30,7 @@ Item {
property string imageUrl: "";
property var goFunction: null;
property string storyId: "";
property bool standaloneOptimized: false;
property bool drillDownToPlace: false;
property bool showPlace: isConcurrency;
@ -40,6 +41,7 @@ Item {
property bool isAnnouncement: action === 'announcement';
property bool isStacked: !isConcurrency && drillDownToPlace;
property int textPadding: 10;
property int smallMargin: 4;
property int messageHeight: 40;
@ -267,9 +269,33 @@ Item {
hoverEnabled: false
onClicked: {
Tablet.playSound(TabletEnums.ButtonClick);
goFunction("hifi://" + hifiUrl);
goFunction("hifi://" + hifiUrl, standaloneOptimized);
}
}
Image {
id: standaloneOptomizedBadge
anchors {
right: actionIcon.left
top: actionIcon.top
topMargin: 2
rightMargin: 3
}
height: root.standaloneOptimized ? 25 : 0
width: 25
visible: root.standaloneOptimized && isConcurrency
fillMode: Image.PreserveAspectFit
source: "../../icons/standalone-optimized.svg"
}
ColorOverlay {
anchors.fill: standaloneOptomizedBadge
source: standaloneOptomizedBadge
color: hifi.colors.blueHighlight
visible: root.standaloneOptimized && isConcurrency
}
StateImage {
id: actionIcon;
visible: !isAnnouncement;
@ -281,7 +307,8 @@ Item {
right: parent.right;
margins: smallMargin;
}
}
}
function go() {
Tablet.playSound(TabletEnums.ButtonClick);
goFunction(drillDownToPlace ? ("/places/" + placeName) : ("/user_stories/" + storyId));

View file

@ -82,6 +82,7 @@ Column {
action: data.action || "",
thumbnail_url: resolveUrl(thumbnail_url),
image_url: resolveUrl(data.details && data.details.image_url),
standalone_optimized: data.standalone_optimized,
metaverseId: (data.id || "").toString(), // Some are strings from server while others are numbers. Model objects require uniformity.
@ -127,6 +128,7 @@ Column {
hifiUrl: model.place_name + model.path;
thumbnail: model.thumbnail_url;
imageUrl: model.image_url;
standaloneOptimized: model.standalone_optimized;
action: model.action;
timestamp: model.created_at;
onlineUsers: model.online_users;
@ -187,4 +189,14 @@ Column {
}
}
}
function isStandalone(address) {
var lowerAddress = address.toLowerCase();
for (var i=0; i < suggestions.count; i++) {
if (suggestions.get(i).place_name.toLowerCase() === lowerAddress) {
return suggestions.get(i).standalone_optimized;
}
}
return false;
}
}

View file

@ -376,7 +376,7 @@ Item {
}
FiraSansRegular {
id: nameCardConnectionInfoText
visible: selected && !isMyCard && pal.activeTab == "connectionsTab" && !isMyCard
visible: selected && !isMyCard && pal.activeTab == "connectionsTab"
width: parent.width
height: displayNameTextPixelSize
size: displayNameTextPixelSize - 4
@ -412,7 +412,7 @@ Item {
}
FiraSansRegular {
id: nameCardRemoveConnectionText
visible: selected && !isMyCard && pal.activeTab == "connectionsTab" && !isMyCard
visible: selected && !isMyCard && pal.activeTab == "connectionsTab"
width: parent.width
height: displayNameTextPixelSize
size: displayNameTextPixelSize - 4
@ -425,7 +425,7 @@ Item {
}
HifiControls.Button {
id: visitConnectionButton
visible: selected && !isMyCard && pal.activeTab == "connectionsTab" && !isMyCard
visible: selected && !isMyCard && pal.activeTab == "connectionsTab"
text: "Visit"
enabled: thisNameCard.placeName !== ""
anchors.verticalCenter: nameCardRemoveConnectionImage.verticalCenter
@ -450,7 +450,7 @@ Item {
// Style
radius: 4
color: "#c5c5c5"
visible: (isMyCard || (selected && pal.activeTab == "nearbyTab")) && isPresent
visible: (!isMyCard && (selected && pal.activeTab == "nearbyTab")) && isPresent
// Rectangle for the zero-gain point on the VU meter
Rectangle {
id: vuMeterZeroGain
@ -481,7 +481,7 @@ Item {
id: vuMeterBase
// Anchors
anchors.fill: parent
visible: isMyCard || selected
visible: !isMyCard && selected
// Style
color: parent.color
radius: parent.radius
@ -489,7 +489,7 @@ Item {
// Rectangle for the VU meter audio level
Rectangle {
id: vuMeterLevel
visible: isMyCard || selected
visible: !isMyCard && selected
// Size
width: (thisNameCard.audioLevel) * parent.width
// Style
@ -525,7 +525,7 @@ Item {
anchors.verticalCenter: nameCardVUMeter.verticalCenter;
anchors.left: nameCardVUMeter.left;
// Properties
visible: (isMyCard || (selected && pal.activeTab == "nearbyTab")) && isPresent;
visible: (!isMyCard && (selected && pal.activeTab == "nearbyTab")) && isPresent;
minimumValue: -60.0
maximumValue: 20.0
stepSize: 5
@ -572,19 +572,7 @@ Item {
implicitHeight: 16
}
}
RalewayRegular {
// The slider for my card is special, it controls the master gain
id: gainSliderText;
visible: isMyCard;
text: "master volume";
size: hifi.fontSizes.tabularData;
anchors.left: parent.right;
anchors.leftMargin: 8;
color: hifi.colors.baseGrayHighlight;
horizontalAlignment: Text.AlignLeft;
verticalAlignment: Text.AlignTop;
}
}
}
function updateGainFromQML(avatarUuid, sliderValue, isReleased) {
if (Users.getAvatarGain(avatarUuid) != sliderValue) {

View file

@ -11,12 +11,12 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import stylesUit 1.0
import controlsUit 1.0 as HifiControls
import controlsUit 1.0 as HifiControlsUit
import "../../windows"
import "./" as AudioControls
@ -26,7 +26,11 @@ Rectangle {
HifiConstants { id: hifi; }
property var eventBridge;
// leave as blank, this is user's volume for the avatar mixer
property var myAvatarUuid: ""
property string title: "Audio Settings"
property int switchHeight: 16
property int switchWidth: 40
signal sendToScript(var message);
color: hifi.colors.baseGray;
@ -38,7 +42,7 @@ Rectangle {
property bool isVR: AudioScriptingInterface.context === "VR"
property real rightMostInputLevelPos: 0
property real rightMostInputLevelPos: 450
//placeholder for control sizes and paddings
//recalculates dynamically in case of UI size is changed
QtObject {
@ -80,16 +84,16 @@ Rectangle {
});
}
function disablePeakValues() {
root.showPeaks = false;
AudioScriptingInterface.devices.input.peakValuesEnabled = false;
function updateMyAvatarGainFromQML(sliderValue, isReleased) {
if (Users.getAvatarGain(myAvatarUuid) != sliderValue) {
Users.setAvatarGain(myAvatarUuid, sliderValue);
}
}
Component.onCompleted: enablePeakValues();
Component.onDestruction: disablePeakValues();
onVisibleChanged: visible ? enablePeakValues() : disablePeakValues();
Column {
id: column
spacing: 12;
anchors.top: bar.bottom
anchors.bottom: parent.bottom
@ -98,70 +102,134 @@ Rectangle {
Separator { }
RalewayRegular {
x: margins.paddings + muteMic.boxSize + muteMic.spacing;
size: 16;
color: "white";
text: qsTr("Input Device Settings")
}
ColumnLayout {
x: margins.paddings;
spacing: 16;
RowLayout {
x: 2 * margins.paddings;
width: parent.width;
// mute is in its own row
RowLayout {
spacing: (margins.sizeCheckBox - 10.5) * 3;
AudioControls.CheckBox {
id: muteMic
text: qsTr("Mute microphone");
spacing: margins.sizeCheckBox - boxSize
isRedCheck: true;
ColumnLayout {
id: columnOne
spacing: 24;
x: margins.paddings
HifiControlsUit.Switch {
id: muteMic;
height: root.switchHeight;
switchWidth: root.switchWidth;
labelTextOn: "Mute microphone";
backgroundOnColor: "#E3E3E3";
checked: AudioScriptingInterface.muted;
onClicked: {
if (AudioScriptingInterface.pushToTalk && !checked) {
// disable push to talk if unmuting
AudioScriptingInterface.pushToTalk = false;
}
AudioScriptingInterface.muted = checked;
checked = Qt.binding(function() { return AudioScriptingInterface.muted; }); // restore binding
}
}
AudioControls.CheckBox {
id: stereoMic
spacing: muteMic.spacing;
text: qsTr("Enable stereo input");
checked: AudioScriptingInterface.isStereoInput;
onClicked: {
AudioScriptingInterface.isStereoInput = checked;
checked = Qt.binding(function() { return AudioScriptingInterface.isStereoInput; }); // restore binding
}
}
}
RowLayout {
spacing: muteMic.spacing*2; //make it visually distinguish
AudioControls.CheckBox {
spacing: muteMic.spacing
text: qsTr("Enable noise reduction");
HifiControlsUit.Switch {
height: root.switchHeight;
switchWidth: root.switchWidth;
labelTextOn: "Noise Reduction";
backgroundOnColor: "#E3E3E3";
checked: AudioScriptingInterface.noiseReduction;
onClicked: {
onCheckedChanged: {
AudioScriptingInterface.noiseReduction = checked;
checked = Qt.binding(function() { return AudioScriptingInterface.noiseReduction; }); // restore binding
}
}
AudioControls.CheckBox {
spacing: muteMic.spacing
text: qsTr("Show audio level meter");
checked: AvatarInputs.showAudioTools;
HifiControlsUit.Switch {
id: pttSwitch
height: root.switchHeight;
switchWidth: root.switchWidth;
labelTextOn: qsTr("Push To Talk (T)");
backgroundOnColor: "#E3E3E3";
checked: (bar.currentIndex === 0) ? AudioScriptingInterface.pushToTalkDesktop : AudioScriptingInterface.pushToTalkHMD;
onCheckedChanged: {
if (bar.currentIndex === 0) {
AudioScriptingInterface.pushToTalkDesktop = checked;
} else {
AudioScriptingInterface.pushToTalkHMD = checked;
}
checked = Qt.binding(function() {
if (bar.currentIndex === 0) {
return AudioScriptingInterface.pushToTalkDesktop;
} else {
return AudioScriptingInterface.pushToTalkHMD;
}
}); // restore binding
}
}
}
ColumnLayout {
spacing: 24;
HifiControlsUit.Switch {
id: warnMutedSwitch
height: root.switchHeight;
switchWidth: root.switchWidth;
labelTextOn: qsTr("Warn when muted");
backgroundOnColor: "#E3E3E3";
checked: AudioScriptingInterface.warnWhenMuted;
onClicked: {
AudioScriptingInterface.warnWhenMuted = checked;
checked = Qt.binding(function() { return AudioScriptingInterface.warnWhenMuted; }); // restore binding
}
}
HifiControlsUit.Switch {
id: audioLevelSwitch
height: root.switchHeight;
switchWidth: root.switchWidth;
labelTextOn: qsTr("Audio Level Meter");
backgroundOnColor: "#E3E3E3";
checked: AvatarInputs.showAudioTools;
onCheckedChanged: {
AvatarInputs.showAudioTools = checked;
checked = Qt.binding(function() { return AvatarInputs.showAudioTools; }); // restore binding
}
onXChanged: rightMostInputLevelPos = x + width
}
HifiControlsUit.Switch {
id: stereoInput;
height: root.switchHeight;
switchWidth: root.switchWidth;
labelTextOn: qsTr("Stereo input");
backgroundOnColor: "#E3E3E3";
checked: AudioScriptingInterface.isStereoInput;
onCheckedChanged: {
AudioScriptingInterface.isStereoInput = checked;
checked = Qt.binding(function() { return AudioScriptingInterface.isStereoInput; }); // restore binding
}
}
}
}
Separator {}
Item {
anchors.left: parent.left
width: rightMostInputLevelPos;
height: pttText.height;
RalewayRegular {
id: pttText
x: margins.paddings;
color: hifi.colors.white;
width: rightMostInputLevelPos;
height: paintedHeight;
wrapMode: Text.WordWrap;
font.italic: true
size: 16;
text: (bar.currentIndex === 0) ? qsTr("Press and hold the button \"T\" to talk.") :
qsTr("Press and hold grip triggers on both of your controllers to talk.");
}
}
Separator { }
Item {
x: margins.paddings;
@ -171,7 +239,7 @@ Rectangle {
HiFiGlyphs {
width: margins.sizeCheckBox
text: hifi.glyphs.mic;
color: hifi.colors.primaryHighlight;
color: hifi.colors.white;
anchors.left: parent.left
anchors.leftMargin: -size/4 //the glyph has empty space at left about 25%
anchors.verticalCenter: parent.verticalCenter;
@ -183,8 +251,8 @@ Rectangle {
anchors.left: parent.left
anchors.leftMargin: margins.sizeCheckBox
size: 16;
color: hifi.colors.lightGrayText;
text: qsTr("CHOOSE INPUT DEVICE");
color: hifi.colors.white;
text: qsTr("Choose input device");
}
}
@ -210,19 +278,19 @@ Rectangle {
width: parent.width - inputLevel.width
clip: true
checkable: !checked
checked: bar.currentIndex === 0 ? selectedDesktop : selectedHMD;
checked: bar.currentIndex === 0 ? selectedDesktop : selectedHMD;
boxSize: margins.sizeCheckBox / 2
isRound: true
text: devicename
onPressed: {
if (!checked) {
stereoMic.checked = false;
stereoInput.checked = false;
AudioScriptingInterface.setStereoInput(false); // the next selected audio device might not support stereo
AudioScriptingInterface.setInputDevice(info, bar.currentIndex === 1);
}
}
}
InputPeak {
AudioControls.InputPeak {
id: inputLevel
anchors.right: parent.right
peak: model.peak;
@ -233,6 +301,13 @@ Rectangle {
}
}
}
AudioControls.LoopbackAudio {
x: margins.paddings
visible: (bar.currentIndex === 1 && isVR) ||
(bar.currentIndex === 0 && !isVR);
anchors { left: parent.left; leftMargin: margins.paddings }
}
Separator {}
@ -247,7 +322,7 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter;
width: margins.sizeCheckBox
text: hifi.glyphs.unmuted;
color: hifi.colors.primaryHighlight;
color: hifi.colors.white;
size: 36;
}
@ -257,8 +332,8 @@ Rectangle {
anchors.leftMargin: margins.sizeCheckBox
anchors.verticalCenter: parent.verticalCenter;
size: 16;
color: hifi.colors.lightGrayText;
text: qsTr("CHOOSE OUTPUT DEVICE");
color: hifi.colors.white;
text: qsTr("Choose output device");
}
}
@ -293,7 +368,68 @@ Rectangle {
}
}
}
PlaySampleSound {
Item {
id: gainContainer
x: margins.paddings;
width: parent.width - margins.paddings*2
height: gainSliderTextMetrics.height
HifiControlsUit.Slider {
id: gainSlider
anchors.right: parent.right
height: parent.height
width: 200
minimumValue: -60.0
maximumValue: 20.0
stepSize: 5
value: Users.getAvatarGain(myAvatarUuid)
onValueChanged: {
updateMyAvatarGainFromQML(value, false);
}
onPressedChanged: {
if (!pressed) {
updateMyAvatarGainFromQML(value, false);
}
}
MouseArea {
anchors.fill: parent
onWheel: {
// Do nothing.
}
onDoubleClicked: {
gainSlider.value = 0.0
}
onPressed: {
// Pass through to Slider
mouse.accepted = false
}
onReleased: {
// the above mouse.accepted seems to make this
// never get called, nonetheless...
mouse.accepted = false
}
}
}
TextMetrics {
id: gainSliderTextMetrics
text: gainSliderText.text
font: gainSliderText.font
}
RalewayRegular {
// The slider for my card is special, it controls the master gain
id: gainSliderText;
text: "Avatar volume";
size: 16;
anchors.left: parent.left;
color: hifi.colors.white;
horizontalAlignment: Text.AlignLeft;
verticalAlignment: Text.AlignTop;
}
}
AudioControls.PlaySampleSound {
x: margins.paddings
visible: (bar.currentIndex === 1 && isVR) ||

View file

@ -0,0 +1,67 @@
//
// LoopbackAudio.qml
// qml/hifi/audio
//
// Created by Seth Alves on 2019-2-18
// Copyright 2019 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import stylesUit 1.0
import controlsUit 1.0 as HifiControlsUit
RowLayout {
property bool audioLoopedBack: AudioScriptingInterface.getServerEcho();
function startAudioLoopback() {
if (!audioLoopedBack) {
audioLoopedBack = true;
AudioScriptingInterface.setServerEcho(true);
}
}
function stopAudioLoopback() {
if (audioLoopedBack) {
audioLoopedBack = false;
AudioScriptingInterface.setServerEcho(false);
}
}
HifiConstants { id: hifi; }
Timer {
id: loopbackTimer
interval: 8000;
running: false;
repeat: false;
onTriggered: {
stopAudioLoopback();
}
}
HifiControlsUit.Button {
text: audioLoopedBack ? qsTr("STOP TESTING YOUR VOICE") : qsTr("TEST YOUR VOICE");
color: audioLoopedBack ? hifi.buttons.red : hifi.buttons.blue;
onClicked: {
if (audioLoopedBack) {
loopbackTimer.stop();
stopAudioLoopback();
} else {
loopbackTimer.restart();
startAudioLoopback();
}
}
}
RalewayRegular {
Layout.leftMargin: 2;
size: 14;
color: "white";
font.italic: true
text: audioLoopedBack ? qsTr("Speak in your input") : "";
}
}

View file

@ -11,18 +11,21 @@
import QtQuick 2.5
import QtGraphicalEffects 1.0
import stylesUit 1.0
import TabletScriptingInterface 1.0
Rectangle {
HifiConstants { id: hifi; }
readonly property var level: AudioScriptingInterface.inputLevel;
property bool gated: false;
Component.onCompleted: {
AudioScriptingInterface.noiseGateOpened.connect(function() { gated = false; });
AudioScriptingInterface.noiseGateClosed.connect(function() { gated = true; });
}
property bool standalone: false;
property var dragTarget: null;
@ -64,6 +67,9 @@ Rectangle {
hoverEnabled: true;
scrollGestureEnabled: false;
onClicked: {
if (AudioScriptingInterface.pushToTalk) {
return;
}
AudioScriptingInterface.muted = !AudioScriptingInterface.muted;
Tablet.playSound(TabletEnums.ButtonClick);
}
@ -106,9 +112,10 @@ Rectangle {
Image {
readonly property string unmutedIcon: "../../../icons/tablet-icons/mic-unmute-i.svg";
readonly property string mutedIcon: "../../../icons/tablet-icons/mic-mute-i.svg";
readonly property string pushToTalkIcon: "../../../icons/tablet-icons/mic-ptt-i.svg";
id: image;
source: AudioScriptingInterface.muted ? mutedIcon : unmutedIcon;
source: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) ? pushToTalkIcon : AudioScriptingInterface.muted ? mutedIcon : unmutedIcon;
width: 30;
height: 30;
@ -133,7 +140,7 @@ Rectangle {
readonly property string color: AudioScriptingInterface.muted ? colors.muted : colors.unmuted;
visible: AudioScriptingInterface.muted;
visible: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) || AudioScriptingInterface.muted;
anchors {
left: parent.left;
@ -152,7 +159,7 @@ Rectangle {
color: parent.color;
text: AudioScriptingInterface.muted ? "MUTED" : "MUTE";
text: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) ? (HMD.active ? "MUTED PTT" : "MUTED PTT-(T)") : (AudioScriptingInterface.muted ? "MUTED" : "MUTE");
font.pointSize: 12;
}
@ -162,7 +169,7 @@ Rectangle {
verticalCenter: parent.verticalCenter;
}
width: 50;
width: AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk ? (HMD.active ? 27 : 25) : 50;
height: 4;
color: parent.color;
}
@ -173,7 +180,7 @@ Rectangle {
verticalCenter: parent.verticalCenter;
}
width: 50;
width: AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk ? (HMD.active ? 27 : 25) : 50;
height: 4;
color: parent.color;
}
@ -228,12 +235,12 @@ Rectangle {
}
}
}
Rectangle {
id: gatedIndicator;
visible: gated && !AudioScriptingInterface.clipping
radius: 4;
radius: 4;
width: 2 * radius;
height: 2 * radius;
color: "#0080FF";
@ -242,12 +249,12 @@ Rectangle {
verticalCenter: parent.verticalCenter;
}
}
Rectangle {
id: clippingIndicator;
visible: AudioScriptingInterface.clipping
radius: 4;
radius: 4;
width: 2 * radius;
height: 2 * radius;
color: colors.red;

View file

@ -14,7 +14,7 @@ import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import stylesUit 1.0
import controlsUit 1.0 as HifiControls
import controlsUit 1.0 as HifiControlsUit
RowLayout {
property var sound: null;
@ -55,32 +55,9 @@ RowLayout {
HifiConstants { id: hifi; }
Button {
id: control
background: Rectangle {
implicitWidth: 20;
implicitHeight: 20;
radius: hifi.buttons.radius;
gradient: Gradient {
GradientStop {
position: 0.2;
color: isPlaying ? hifi.buttons.colorStart[hifi.buttons.blue] : hifi.buttons.colorStart[hifi.buttons.black];
}
GradientStop {
position: 1.0;
color: isPlaying ? hifi.buttons.colorFinish[hifi.buttons.blue] : hifi.buttons.colorFinish[hifi.buttons.black];
}
}
}
contentItem: HiFiGlyphs {
// absolutely position due to asymmetry in glyph
// x: isPlaying ? 0 : 1;
// y: 1;
size: 14;
color: (control.pressed || control.hovered) ? (isPlaying ? "black" : hifi.colors.primaryHighlight) : "white";
text: isPlaying ? hifi.glyphs.stop_square : hifi.glyphs.playback_play;
}
HifiControlsUit.Button {
text: isPlaying ? qsTr("STOP TESTING YOUR SOUND") : qsTr("TEST YOUR SOUND");
color: isPlaying ? hifi.buttons.red : hifi.buttons.blue;
onClicked: isPlaying ? stopSound() : playSound();
}
@ -88,7 +65,7 @@ RowLayout {
Layout.leftMargin: 2;
size: 14;
color: "white";
text: isPlaying ? qsTr("Stop sample sound") : qsTr("Play sample sound");
font.italic: true
text: isPlaying ? qsTr("Listen to your output") : "";
}
}

View file

@ -213,6 +213,63 @@ Item {
popup.open();
}
HiFiGlyphs {
id: errorsGlyph
visible: !AvatarPackagerCore.currentAvatarProject || AvatarPackagerCore.currentAvatarProject.hasErrors
text: hifi.glyphs.alert
size: 315
color: "#EA4C5F"
anchors {
top: parent.top
topMargin: -30
horizontalCenter: parent.horizontalCenter
}
}
Image {
id: successGlyph
visible: AvatarPackagerCore.currentAvatarProject && !AvatarPackagerCore.currentAvatarProject.hasErrors
anchors {
top: parent.top
topMargin: 52
horizontalCenter: parent.horizontalCenter
}
width: 149.6
height: 149
source: "../../../icons/checkmark-stroke.svg"
}
RalewayRegular {
id: doctorStatusMessage
states: [
State {
when: AvatarPackagerCore.currentAvatarProject && !AvatarPackagerCore.currentAvatarProject.hasErrors
name: "noErrors"
PropertyChanges {
target: doctorStatusMessage
text: "Your avatar looks fine."
}
},
State {
when: !AvatarPackagerCore.currentAvatarProject || AvatarPackagerCore.currentAvatarProject.hasErrors
name: "errors"
PropertyChanges {
target: doctorStatusMessage
text: "Your avatar has a few issues."
}
}
]
color: 'white'
size: 20
anchors.left: parent.left
anchors.right: parent.right
anchors.top: errorsGlyph.bottom
wrapMode: Text.Wrap
}
RalewayRegular {
id: infoMessage
@ -240,7 +297,7 @@ Item {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.top: doctorStatusMessage.bottom
anchors.bottomMargin: 24
@ -249,6 +306,53 @@ Item {
text: "You can upload your files to our servers to always access them, and to make your avatar visible to other users."
}
RalewayRegular {
id: notForSaleMessage
visible: root.hasSuccessfullyUploaded
color: 'white'
linkColor: '#00B4EF'
size: 20
anchors.left: parent.left
anchors.right: parent.right
anchors.top: infoMessage.bottom
anchors.topMargin: 10
anchors.bottomMargin: 24
wrapMode: Text.Wrap
text: "This item is not for sale yet, <a href='#'>learn more</a>."
onLinkActivated: {
Qt.openUrlExternally("https://docs.highfidelity.com/sell/add-item/upload-avatar.html");
}
}
RalewayRegular {
id: showErrorsLink
color: 'white'
linkColor: '#00B4EF'
visible: AvatarPackagerCore.currentAvatarProject && AvatarPackagerCore.currentAvatarProject.hasErrors
anchors {
top: notForSaleMessage.bottom
topMargin: 16
horizontalCenter: parent.horizontalCenter
}
size: 28
text: "<a href='toggle'>View all errors</a>"
onLinkActivated: {
avatarPackager.state = AvatarPackagerState.avatarDoctorErrorReport;
}
}
HifiControls.Button {
id: openFolderButton

View file

@ -40,9 +40,9 @@ Rectangle {
property string itemHref;
property string itemAuthor;
property int itemEdition: -1;
property bool hasSomethingToTradeIn: alreadyOwned && (itemEdition > 0); // i.e., don't trade in your artist's proof
property bool isTradingIn: isUpdating && hasSomethingToTradeIn;
property bool isStocking: availability === 'not for sale' && creator === Account.username;
property bool hasSomethingToTradeIn: itemEdition > 0; // i.e., don't trade in your artist's proof
property bool isTradingIn: canUpdate && hasSomethingToTradeIn;
property bool isStocking: (availability === 'not for sale') && (creator === Account.username) && !updated_item_id;
property string certificateId;
property double balanceAfterPurchase;
property bool alreadyOwned: false; // Including proofs
@ -59,8 +59,9 @@ Rectangle {
property bool canRezCertifiedItems: Entities.canRezCertified() || Entities.canRezTmpCertified();
property string referrer;
property bool isInstalled;
property bool isUpdating;
property bool canUpdate;
property string availability: "available";
property string updated_item_id: "";
property string creator: "";
property string baseAppURL;
property int currentUpdatesPage: 1;
@ -144,6 +145,7 @@ Rectangle {
}
onAvailableUpdatesResult: {
// Answers the updatable original item cert data still owned by this user that are EITHER instances of this marketplace id, or update to this marketplace id.
if (result.status !== 'success') {
console.log("Failed to get Available Updates", result.data.message);
} else {
@ -155,7 +157,7 @@ Rectangle {
if (root.itemEdition !== -1 && root.itemEdition !== parseInt(result.data.updates[i].edition_number)) {
continue;
}
root.isUpdating = true;
root.canUpdate = true;
root.baseItemName = result.data.updates[i].base_item_title;
// This CertID is the one corresponding to the base item CertID that the user already owns
root.certificateId = result.data.updates[i].certificate_id;
@ -166,7 +168,7 @@ Rectangle {
}
}
if (result.data.updates.length === 0 || root.isUpdating) {
if (result.data.updates.length === 0 || root.canUpdate) {
root.availableUpdatesReceived = true;
refreshBuyUI();
} else {
@ -178,7 +180,7 @@ Rectangle {
onUpdateItemResult: {
if (result.status !== 'success') {
failureErrorText.text = result.message;
failureErrorText.text = result.data ? (result.data.message || "Unknown Error") : JSON.stringify(result);
root.activeView = "checkoutFailure";
} else {
root.itemHref = result.data.download_url;
@ -266,13 +268,6 @@ Rectangle {
}
}
}
MouseArea {
enabled: titleBarContainer.usernameDropdownVisible;
anchors.fill: parent;
onClicked: {
titleBarContainer.usernameDropdownVisible = false;
}
}
//
// TITLE BAR END
//
@ -481,7 +476,7 @@ Rectangle {
FiraSansSemiBold {
id: itemPriceText;
text: isTradingIn ? "FREE\nUPDATE" :
(isStocking ? "Free for creator" :
(isStocking ? "Free for creator" :
((root.itemPrice === -1) ? "--" : ((root.itemPrice > 0) ? root.itemPrice : "FREE")));
// Text size
size: isTradingIn ? 20 : 26;
@ -580,7 +575,7 @@ Rectangle {
// "View in Inventory" button
HifiControlsUit.Button {
id: viewInMyPurchasesButton;
visible: isCertified && dataReady && (isUpdating ? !hasSomethingToTradeIn : alreadyOwned);
visible: isCertified && dataReady && (isTradingIn ? hasSomethingToTradeIn : alreadyOwned);
color: hifi.buttons.blue;
colorScheme: hifi.colorSchemes.light;
anchors.top: buyTextContainer.visible ? buyTextContainer.bottom : checkoutActionButtonsContainer.top;
@ -588,9 +583,9 @@ Rectangle {
height: 50;
anchors.left: parent.left;
anchors.right: parent.right;
text: root.isUpdating ? "UPDATE TO THIS ITEM FOR FREE" : "VIEW THIS ITEM IN YOUR INVENTORY";
text: (canUpdate && !isTradingIn) ? "UPDATE TO THIS ITEM FOR FREE" : "VIEW THIS ITEM IN YOUR INVENTORY";
onClicked: {
if (root.isUpdating) {
if (root.canUpdate) {
sendToScript({method: 'checkout_goToPurchases', filterText: root.baseItemName});
} else {
sendToScript({method: 'checkout_goToPurchases', filterText: root.itemName});
@ -602,7 +597,11 @@ Rectangle {
HifiControlsUit.Button {
id: buyButton;
visible: isTradingIn || !alreadyOwned || isStocking || !(root.itemType === "avatar" || root.itemType === "app");
enabled: (root.balanceAfterPurchase >= 0 && dataReady) || (!root.isCertified) || root.isUpdating;
property bool checkBalance: dataReady && (root.availability === "available")
enabled: (checkBalance && (balanceAfterPurchase >= 0)) || !isCertified || isTradingIn || isStocking;
text: isTradingIn ? "Confirm Update" :
(enabled ? (viewInMyPurchasesButton.visible ? "Get It Again" : (dataReady ? "Get Item" : "--")) :
(checkBalance ? "Insufficient Funds" : availability))
color: viewInMyPurchasesButton.visible ? hifi.buttons.white : hifi.buttons.blue;
colorScheme: hifi.colorSchemes.light;
anchors.top: viewInMyPurchasesButton.visible ? viewInMyPurchasesButton.bottom :
@ -611,13 +610,6 @@ Rectangle {
height: 50;
anchors.left: parent.left;
anchors.right: parent.right;
text: isTradingIn ?
"CONFIRM UPDATE" :
(((root.isCertified) ?
(dataReady ?
((viewInMyPurchasesButton.visible && !root.isUpdating) ? "Get It Again" : "Confirm") :
"--") :
"Get Item"));
onClicked: {
if (isTradingIn) {
// If we're updating an app, the existing app needs to be uninstalled.
@ -1110,6 +1102,7 @@ Rectangle {
root.itemAuthor = result.data.creator;
root.itemType = result.data.item_type || "unknown";
root.availability = result.data.availability;
root.updated_item_id = result.data.updated_item_id || ""
root.creator = result.data.creator;
if (root.itemType === "unknown") {
root.itemHref = result.data.review_url;
@ -1209,7 +1202,7 @@ Rectangle {
buyText.text = "";
// If the user IS on the checkout page for the updated version of an owned item...
if (root.isUpdating) {
if (root.canUpdate) {
// If the user HAS already selected a specific edition to update...
if (hasSomethingToTradeIn) {
buyText.text = "By pressing \"Confirm Update\", you agree to trade in your old item for the updated item that replaces it.";

View file

@ -30,6 +30,8 @@ Rectangle {
property string dateAcquired: "--";
property string itemCost: "--";
property string marketplace_item_id: "";
property bool standaloneOptimized: false;
property bool standaloneIncompatible: false;
property string certTitleTextColor: hifi.colors.darkGray;
property string certTextColor: hifi.colors.white;
property string infoTextColor: hifi.colors.blueAccent;
@ -71,6 +73,8 @@ Rectangle {
} else {
root.marketplace_item_id = result.data.marketplace_item_id;
root.isMyCert = result.isMyCert ? result.isMyCert : false;
root.standaloneOptimized = result.data.standalone_optimized;
root.standaloneIncompatible = result.data.standalone_incompatible;
if (root.certInfoReplaceMode > 3) {
root.itemName = result.data.marketplace_item_name;
@ -421,6 +425,24 @@ Rectangle {
anchors.rightMargin: 24;
height: root.useGoldCert ? 220 : 372;
HiFiGlyphs {
id: standaloneOptomizedBadge
anchors {
right: parent.right
top: ownedByHeader.top
rightMargin: 15
topMargin: 28
}
visible: root.standaloneOptimized
text: hifi.glyphs.hmd
size: 34
horizontalAlignment: Text.AlignHCenter
color: hifi.colors.blueHighlight
}
RalewayRegular {
id: errorText;
visible: !root.useGoldCert;
@ -467,6 +489,7 @@ Rectangle {
color: root.infoTextColor;
elide: Text.ElideRight;
}
AnonymousProRegular {
id: isMyCertText;
visible: root.isMyCert && ownedBy.text !== "--" && ownedBy.text !== "";
@ -485,14 +508,46 @@ Rectangle {
verticalAlignment: Text.AlignVCenter;
}
RalewayRegular {
id: standaloneHeader;
text: root.standaloneOptimized ? "STAND-ALONE OPTIMIZED" : "STAND-ALONE INCOMPATIBLE";
// Text size
size: 16;
// Anchors
anchors.top: ownedBy.bottom;
anchors.topMargin: 15;
anchors.left: parent.left;
anchors.right: parent.right;
visible: root.standaloneOptimized || root.standaloneIncompatible;
height: visible ? paintedHeight : 0;
// Style
color: hifi.colors.darkGray;
}
RalewayRegular {
id: standaloneText;
text: root.standaloneOptimized ? "This item is stand-alone optimized" : "This item is incompatible with stand-alone devices";
// Text size
size: 18;
// Anchors
anchors.top: standaloneHeader.bottom;
anchors.topMargin: 8;
anchors.left: standaloneHeader.left;
visible: root.standaloneOptimized || root.standaloneIncompatible;
height: visible ? paintedHeight : 0;
// Style
color: root.infoTextColor;
elide: Text.ElideRight;
}
RalewayRegular {
id: dateAcquiredHeader;
text: "ACQUISITION DATE";
// Text size
size: 16;
// Anchors
anchors.top: ownedBy.bottom;
anchors.topMargin: 28;
anchors.top: standaloneText.bottom;
anchors.topMargin: 20;
anchors.left: parent.left;
anchors.right: parent.horizontalCenter;
anchors.rightMargin: 8;
@ -521,8 +576,8 @@ Rectangle {
// Text size
size: 16;
// Anchors
anchors.top: ownedBy.bottom;
anchors.topMargin: 28;
anchors.top: standaloneText.bottom;
anchors.topMargin: 20;
anchors.left: parent.horizontalCenter;
anchors.right: parent.right;
height: paintedHeight;

View file

@ -41,6 +41,7 @@ Rectangle {
property string searchScopeString: "Featured"
property bool isLoggedIn: false
property bool supports3DHTML: true
property bool pendingGetMarketplaceItemCall: false
anchors.fill: (typeof parent === undefined) ? undefined : parent
@ -90,6 +91,14 @@ Rectangle {
id: -1,
name: "Everything"
});
categoriesModel.append({
id: -1,
name: "Stand-alone Optimized"
});
categoriesModel.append({
id: -1,
name: "Stand-alone Compatible"
});
result.data.items.forEach(function(category) {
categoriesModel.append({
id: category.id,
@ -100,7 +109,9 @@ Rectangle {
getMarketplaceItems();
}
onGetMarketplaceItemsResult: {
marketBrowseModel.handlePage(result.status !== "success" && result.message, result);
if (!pendingGetMarketplaceItemCall) {
marketBrowseModel.handlePage(result.status !== "success" && result.message, result);
}
}
onGetMarketplaceItemResult: {
@ -112,7 +123,7 @@ Rectangle {
marketplaceItem.image_url = result.data.thumbnail_url;
marketplaceItem.name = result.data.title;
marketplaceItem.likes = result.data.likes;
if(result.data.has_liked !== undefined) {
if (result.data.has_liked !== undefined) {
marketplaceItem.liked = result.data.has_liked;
}
marketplaceItem.creator = result.data.creator;
@ -122,10 +133,14 @@ Rectangle {
marketplaceItem.attributions = result.data.attributions;
marketplaceItem.license = result.data.license;
marketplaceItem.availability = result.data.availability;
marketplaceItem.updated_item_id = result.data.updated_item_id || "";
marketplaceItem.created_at = result.data.created_at;
marketplaceItem.standaloneOptimized = result.data.standalone_optimized;
marketplaceItem.standaloneVisible = result.data.standalone_optimized || result.data.standalone_incompatible;
marketplaceItemScrollView.contentHeight = marketplaceItemContent.height;
itemsList.visible = false;
marketplaceItemView.visible = true;
pendingGetMarketplaceItemCall = false;
}
}
}
@ -186,16 +201,16 @@ Rectangle {
visible: true
Image {
id: marketplaceHeaderImage;
source: "../common/images/marketplaceHeaderImage.png";
anchors.top: parent.top;
anchors.topMargin: 2;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 0;
anchors.left: parent.left;
anchors.leftMargin: 8;
width: 140;
fillMode: Image.PreserveAspectFit;
id: marketplaceHeaderImage
source: "../common/images/marketplaceHeaderImage.png"
anchors.top: parent.top
anchors.topMargin: 2
anchors.bottom: parent.bottom
anchors.bottomMargin: 0
anchors.left: parent.left
anchors.leftMargin: 8
width: 140
fillMode: Image.PreserveAspectFit
MouseArea {
anchors.fill: parent;
@ -541,7 +556,8 @@ Rectangle {
price: model.cost
availability: model.availability
isLoggedIn: root.isLoggedIn;
standaloneOptimized: model.standalone_optimized
onShowItem: {
MarketplaceScriptingInterface.getMarketplaceItem(item_id);
}
@ -979,7 +995,6 @@ Rectangle {
xhr.open("GET", url);
xhr.onreadystatechange = function() {
if (xhr.readyState == XMLHttpRequest.DONE) {
console.log(xhr.responseText);
licenseText.text = xhr.responseText;
licenseInfo.visible = true;
}
@ -1224,6 +1239,7 @@ Rectangle {
console.log("A message with method 'updateMarketplaceQMLItem' was sent without an itemId!");
return;
}
pendingGetMarketplaceItemCall = true;
marketplaceItem.edition = message.params.edition ? message.params.edition : -1;
MarketplaceScriptingInterface.getMarketplaceItem(message.params.itemId);
break;

View file

@ -2,7 +2,7 @@
// MarketplaceListItem.qml
// qml/hifi/commerce/marketplace
//
// MarketplaceListItem
// MarketplaceItem
//
// Created by Roxanne Skelly on 2019-01-22
// Copyright 2019 High Fidelity, Inc.
@ -15,6 +15,7 @@ import Hifi 1.0 as Hifi
import QtQuick 2.9
import QtQuick.Controls 2.2
import stylesUit 1.0
import QtGraphicalEffects 1.0
import controlsUit 1.0 as HifiControlsUit
import "../../../controls" as HifiControls
import "../common" as HifiCommerceCommon
@ -34,15 +35,17 @@ Rectangle {
property var categories: []
property int price: 0
property string availability: "unknown"
property string updated_item_id: ""
property var attributions: []
property string description: ""
property string license: ""
property string posted: ""
property string created_at: ""
property bool isLoggedIn: false;
property int edition: -1;
property bool supports3DHTML: false;
property bool isLoggedIn: false
property int edition: -1
property bool supports3DHTML: false
property bool standaloneVisible: false
property bool standaloneOptimized: false
onCategoriesChanged: {
categoriesListModel.clear();
@ -52,13 +55,7 @@ Rectangle {
}
onDescriptionChanged: {
if(root.supports3DHTML) {
descriptionTextModel.clear();
descriptionTextModel.append({text: description});
} else {
descriptionText.text = description;
}
descriptionText.text = description;
}
onAttributionsChanged: {
@ -246,11 +243,43 @@ Rectangle {
right: parent.right;
top: itemImage.bottom;
}
height: categoriesList.y - buyButton.y + categoriesList.height
height: categoriesList.y - badges.y + categoriesList.height
function evalHeight() {
height = categoriesList.y - buyButton.y + categoriesList.height;
console.log("HEIGHT: " + height);
height = categoriesList.y - badges.y + categoriesList.height;
}
Item {
id: badges
anchors {
right: buyButton.left
top: parent.top
rightMargin: 10
}
height: childrenRect.height
Image {
id: standaloneOptomizedBadge
anchors {
topMargin: 15
right: parent.right
top: parent.top
}
height: root.standaloneOptimized ? 50 : 0
width: 50
visible: root.standaloneOptimized
fillMode: Image.PreserveAspectFit
source: "../../../../icons/standalone-optimized.svg"
}
ColorOverlay {
anchors.fill: standaloneOptomizedBadge
source: standaloneOptomizedBadge
color: hifi.colors.blueHighlight
visible: root.standaloneOptimized
}
}
HifiControlsUit.Button {
@ -259,22 +288,22 @@ Rectangle {
anchors {
right: parent.right
top: parent.top
left: parent.left
topMargin: 15
}
height: 50
height: 50
width: 180
property bool isNFS: availability === "not for sale" // Note: server will say "sold out" or "invalidated" before it says NFS
property bool isMine: creator === Account.username
property bool isUpgrade: root.edition >= 0
property int costToMe: ((isMine && isNFS) || isUpgrade) ? 0 : price
property bool isAvailable: costToMe >= 0
text: isUpgrade ? "UPGRADE FOR FREE" : (isAvailable ? (costToMe || "FREE") : availability)
enabled: isAvailable
buttonGlyph: isAvailable ? (costToMe ? hifi.glyphs.hfc : "") : ""
property bool isUpdate: root.edition >= 0 // Special case of updating from a specific older item
property bool isStocking: (creator === Account.username) && (availability === "not for sale") && !updated_item_id // Note: server will say "sold out" or "invalidated" before it says NFS
property bool isFreeSpecial: isStocking || isUpdate
enabled: isFreeSpecial || (availability === 'available')
buttonGlyph: (enabled && !isUpdate && (price > 0)) ? hifi.glyphs.hfc : ""
text: isUpdate ? "UPDATE FOR FREE" : (isStocking ? "FREE STOCK" : (enabled ? (price || "FREE") : availability))
color: hifi.buttons.blue
buttonGlyphSize: 24
fontSize: 24
onClicked: root.buy();
}
@ -282,11 +311,11 @@ Rectangle {
id: creatorItem
anchors {
top: buyButton.bottom
top: parent.top
leftMargin: 15
topMargin: 15
}
width: parent.width
width: paintedWidth
height: childrenRect.height
RalewaySemiBold {
@ -535,13 +564,55 @@ Rectangle {
}
}
}
Item {
id: standaloneItem
anchors {
top: licenseItem.bottom
topMargin: 15
left: parent.left
right: parent.right
}
height: root.standaloneVisible ? childrenRect.height : 0
visible: root.standaloneVisible
RalewaySemiBold {
id: standaloneLabel
anchors.top: parent.top
anchors.left: parent.left
width: paintedWidth
height: 20
text: root.standaloneOptimized ? "STAND-ALONE OPTIMIZED:" : "STAND-ALONE INCOMPATIBLE:"
size: 14
color: hifi.colors.lightGrayText
verticalAlignment: Text.AlignVCenter
}
RalewaySemiBold {
id: standaloneOptimizedText
anchors.top: standaloneLabel.bottom
anchors.left: parent.left
anchors.topMargin: 5
width: paintedWidth
text: root.standaloneOptimized ? "This item is stand-alone optimized" : "This item is incompatible with stand-alone devices"
size: 14
color: hifi.colors.lightGray
verticalAlignment: Text.AlignVCenter
}
}
Item {
id: descriptionItem
property string text: ""
anchors {
top: licenseItem.bottom
top: standaloneItem.bottom
topMargin: 15
left: parent.left
right: parent.right

View file

@ -35,8 +35,9 @@ Rectangle {
property string category: ""
property int price: 0
property string availability: "unknown"
property bool isLoggedIn: false;
property bool isLoggedIn: false
property bool standaloneOptimized: false
signal buy()
signal showItem()
signal categoryClicked(string category)
@ -226,6 +227,7 @@ Rectangle {
top: parent.top
left: parent.left
leftMargin: 15
topMargin: 10
}
width: paintedWidth
@ -239,16 +241,18 @@ Rectangle {
id: creatorText
anchors {
top: creatorLabel.top;
left: creatorLabel.right;
leftMargin: 15;
top: creatorLabel.top
left: creatorLabel.right
leftMargin: 15
right: badges.left
}
width: paintedWidth;
width: paintedWidth
text: root.creator;
size: 14;
color: hifi.colors.lightGray;
verticalAlignment: Text.AlignVCenter;
text: root.creator
size: 14
elide: Text.ElideRight
color: hifi.colors.lightGray
verticalAlignment: Text.AlignVCenter
}
RalewaySemiBold {
@ -259,12 +263,12 @@ Rectangle {
left: parent.left
leftMargin: 15
}
width: paintedWidth;
width: paintedWidth
text: "IN:";
size: 14;
color: hifi.colors.lightGrayText;
verticalAlignment: Text.AlignVCenter;
text: "IN:"
size: 14
color: hifi.colors.lightGrayText
verticalAlignment: Text.AlignVCenter
}
RalewaySemiBold {
@ -274,22 +278,56 @@ Rectangle {
top: categoryLabel.top
left: categoryLabel.right
leftMargin: 15
right: badges.left
}
width: paintedWidth
text: root.category
size: 14
color: hifi.colors.blueHighlight;
verticalAlignment: Text.AlignVCenter;
color: hifi.colors.blueHighlight
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
MouseArea {
anchors.fill: parent
onClicked: root.categoryClicked(root.category);
}
}
Item {
id: badges
anchors {
right: buyButton.left
top: parent.top
topMargin: 10
rightMargin: 10
}
height: 50
Image {
id: standaloneOptomizedBadge
anchors {
right: parent.right
top: parent.top
}
height: root.standaloneOptimized ? 40 : 0
width: 40
visible: root.standaloneOptimized
fillMode: Image.PreserveAspectFit
source: "../../../../icons/standalone-optimized.svg"
}
ColorOverlay {
anchors.fill: standaloneOptomizedBadge
source: standaloneOptomizedBadge
color: hifi.colors.blueHighlight
visible: root.standaloneOptimized
}
}
HifiControlsUit.Button {
id: buyButton
anchors {
right: parent.right
top: parent.top
@ -298,19 +336,21 @@ Rectangle {
topMargin:10
bottomMargin: 10
}
width: 180
property bool isNFS: availability === "not for sale" // Note: server will say "sold out" or "invalidated" before it says NFS
property bool isMine: creator === Account.username
property bool isUpgrade: root.edition >= 0
property int costToMe: ((isMine && isNFS) || isUpgrade) ? 0 : price
property bool isAvailable: costToMe >= 0
property bool isAvailable: availability === "available"
text: isUpgrade ? "UPGRADE FOR FREE" : (isAvailable ? (costToMe || "FREE") : availability)
enabled: isAvailable
buttonGlyph: isAvailable ? (costToMe ? hifi.glyphs.hfc : "") : ""
color: hifi.buttons.blue;
buttonGlyphSize: 24
fontSize: 24
onClicked: root.buy();
}
}

View file

@ -13,6 +13,7 @@
import Hifi 1.0 as Hifi
import QtQuick 2.5
import QtGraphicalEffects 1.0
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import stylesUit 1.0
@ -50,6 +51,8 @@ Item {
property string upgradeTitle;
property bool updateAvailable: root.updateItemId && root.updateItemId !== "";
property bool valid;
property bool standaloneOptimized;
property bool standaloneIncompatible;
property string originalStatusText;
property string originalStatusColor;
@ -380,7 +383,7 @@ Item {
if (updateButton.visible && uninstallButton.visible) {
item.itemButtonText = "";
item.glyphSize = 20;
} else {
} else if (item) {
item.itemButtonText = "Send to Trash";
item.glyphSize = 30;
}
@ -403,7 +406,9 @@ Item {
id: permissionExplanationText;
anchors.fill: parent;
text: {
if (root.itemType === "contentSet") {
if (root.standaloneIncompatible) {
"This item is incompatible with stand-alone devices. <a href='#standaloneIncompatible'>Learn more</a>";
} else if (root.itemType === "contentSet") {
"You do not have 'Replace Content' permissions in this domain. <a href='#replaceContentPermission'>Learn more</a>";
} else if (root.itemType === "entity") {
"You do not have 'Rez Certified' permissions in this domain. <a href='#rezCertifiedPermission'>Learn more</a>";
@ -417,7 +422,11 @@ Item {
verticalAlignment: Text.AlignVCenter;
onLinkActivated: {
sendToPurchases({method: 'showPermissionsExplanation', itemType: root.itemType});
if (link === "#standaloneIncompatible") {
sendToPurchases({method: 'showStandaloneIncompatibleExplanation'});
} else {
sendToPurchases({method: 'showPermissionsExplanation', itemType: root.itemType});
}
}
}
}
@ -699,7 +708,8 @@ Item {
anchors.bottomMargin: 8;
width: 160;
height: 40;
enabled: root.hasPermissionToRezThis &&
enabled: !root.standaloneIncompatible &&
root.hasPermissionToRezThis &&
MyAvatar.skeletonModelURL !== root.itemHref &&
!root.wornEntityID &&
root.valid;
@ -838,6 +848,28 @@ Item {
root.sendToPurchases({ method: 'flipCard' });
}
}
}
Image {
id: standaloneOptomizedBadge
anchors {
right: parent.right
bottom: parent.bottom
rightMargin: 15
bottomMargin:12
}
height: root.standaloneOptimized ? 36 : 0
width: 36
visible: root.standaloneOptimized
fillMode: Image.PreserveAspectFit
source: "../../../../icons/standalone-optimized.svg"
}
ColorOverlay {
anchors.fill: standaloneOptomizedBadge
source: standaloneOptomizedBadge
color: hifi.colors.blueHighlight
visible: root.standaloneOptimized
}
}

View file

@ -12,7 +12,7 @@
//
import Hifi 1.0 as Hifi
import QtQuick 2.5
import QtQuick 2.9
import stylesUit 1.0
import controlsUit 1.0 as HifiControlsUit
import "../../../controls" as HifiControls
@ -33,6 +33,7 @@ Rectangle {
property bool purchasesReceived: false;
property bool punctuationMode: false;
property bool isDebuggingFirstUseTutorial: false;
property bool isStandalone: false;
property string installedApps;
property bool keyboardRaised: false;
property int numUpdatesAvailable: 0;
@ -44,6 +45,7 @@ Rectangle {
purchasesModel.getFirstPage();
Commerce.getAvailableUpdates();
}
Connections {
target: Commerce;
@ -110,6 +112,10 @@ Rectangle {
}
}
Component.onCompleted: {
isStandalone = PlatformInfo.isStandalone();
}
HifiCommerceCommon.CommerceLightbox {
id: lightboxPopup;
z: 999;
@ -527,6 +533,7 @@ Rectangle {
ListView {
id: purchasesContentsList;
visible: purchasesModel.count !== 0;
interactive: !lightboxPopup.visible;
clip: true;
model: purchasesModel;
snapMode: ListView.NoSnap;
@ -553,6 +560,8 @@ Rectangle {
upgradeTitle: model.upgrade_title;
itemType: model.item_type;
valid: model.valid;
standaloneOptimized: model.standalone_optimized
standaloneIncompatible: root.isStandalone && model.standalone_incompatible
anchors.topMargin: 10;
anchors.bottomMargin: 10;
@ -673,6 +682,14 @@ Rectangle {
lightboxPopup.visible = false;
}
lightboxPopup.visible = true;
} else if (msg.method === "showStandaloneIncompatibleExplanation") {
lightboxPopup.titleText = "Stand-alone Incompatible";
lightboxPopup.bodyText = "The item is incompatible with stand-alone devices.";
lightboxPopup.button1text = "CLOSE";
lightboxPopup.button1method = function() {
lightboxPopup.visible = false;
}
lightboxPopup.visible = true;
} else if (msg.method === "setFilterText") {
filterBar.text = msg.filterText;
} else if (msg.method === "flipCard") {

View file

@ -40,10 +40,6 @@ Rectangle {
source: "images/wallet-bg.jpg";
}
Component.onDestruction: {
KeyboardScriptingInterface.raised = false;
}
Connections {
target: Commerce;

View file

@ -97,10 +97,11 @@ Rectangle {
textFormat: Text.StyledText
linkColor: "#00B4EF"
color: "white"
text: "Blockchain technology from <a href=\"https://elementsproject.org/elements/\">Elements</a>."
property string link: "https://eos.io/"
text: "Blockchain technology from <a href=\"" + link + "\">EOS</a>."
size: 14
onLinkActivated: {
HiFiAbout.openUrl("https://elementsproject.org/elements/");
HiFiAbout.openUrl(link);
}
}
RalewayRegular {

View file

@ -0,0 +1,59 @@
//
// Created by Dante Ruiz on 3/4/19.
// Copyright 2019 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import stylesUit 1.0
import controlsUit 1.0 as HifiControls
Rectangle {
id: root
anchors.fill: parent
property string pluginName: ""
property var displayInformation: null
HifiConstants { id: hifi }
color: hifi.colors.baseGray
HifiControls.CheckBox {
id: box
width: 15
height: 15
anchors {
left: root.left
leftMargin: 75
}
onClicked: {
sendConfigurationSettings( { trackControllersInOculusHome: checked });
}
}
RalewaySemiBold {
id: head
text: "Track hand controllers in Oculus Home"
size: 12
color: "white"
anchors.left: box.right
anchors.leftMargin: 5
}
function displayConfiguration() {
var configurationSettings = InputConfiguration.configurationSettings(root.pluginName);
box.checked = configurationSettings.trackControllersInOculusHome;
}
function sendConfigurationSettings(settings) {
InputConfiguration.setConfigurationSettings(settings, root.pluginName);
}
}

View file

@ -0,0 +1,144 @@
//
// TADLightbox.qml
// qml/hifi/tablet
//
// TADLightbox
//
// Created by Roxanne Skelly on 2019-03-07
// Copyright 2019 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import Hifi 1.0 as Hifi
import QtQuick 2.5
import QtGraphicalEffects 1.0
import stylesUit 1.0
import controlsUit 1.0 as HifiControlsUit
import "qrc:////qml//controls" as HifiControls
// references XXX from root context
Rectangle {
property string titleText;
property string bodyImageSource;
property string bodyText;
property string button1color: hifi.buttons.noneBorderlessGray;
property string button1text;
property var button1method;
property string button2color: hifi.buttons.blue;
property string button2text;
property var button2method;
property string buttonLayout: "leftright";
id: root;
visible: false;
anchors.fill: parent;
color: Qt.rgba(0, 0, 0, 0.5);
z: 999;
HifiConstants { id: hifi; }
// This object is always used in a popup.
// This MouseArea is used to prevent a user from being
// able to click on a button/mouseArea underneath the popup.
MouseArea {
anchors.fill: parent;
propagateComposedEvents: false;
hoverEnabled: true;
}
Rectangle {
anchors.centerIn: parent;
width: 376;
height: childrenRect.height + 30;
color: "white";
RalewaySemiBold {
id: titleText;
text: root.titleText;
anchors.top: parent.top;
anchors.topMargin: 30;
anchors.left: parent.left;
anchors.leftMargin: 30;
anchors.right: parent.right;
anchors.rightMargin: 30;
height: paintedHeight;
color: hifi.colors.black;
size: 24;
verticalAlignment: Text.AlignTop;
wrapMode: Text.WordWrap;
}
RalewayRegular {
id: bodyText;
text: root.bodyText;
anchors.top: root.bodyImageSource ? bodyImage.bottom : (root.titleText ? titleText.bottom : parent.top);
anchors.topMargin: root.bodyImageSource ? 20 : (root.titleText ? 20 : 30);
anchors.left: parent.left;
anchors.leftMargin: 30;
anchors.right: parent.right;
anchors.rightMargin: 30;
height: paintedHeight;
color: hifi.colors.black;
size: 20;
verticalAlignment: Text.AlignTop;
wrapMode: Text.WordWrap;
}
Item {
id: buttons;
anchors.top: bodyText.bottom;
anchors.topMargin: 30;
anchors.left: parent.left;
anchors.right: parent.right;
height: root.buttonLayout === "leftright" ? 70 : 150;
// Button 1
HifiControlsUit.Button {
id: button1;
color: root.button1color;
colorScheme: hifi.colorSchemes.light;
anchors.top: root.buttonLayout === "leftright" ? parent.top : parent.top;
anchors.left: parent.left;
anchors.leftMargin: root.buttonLayout === "leftright" ? 30 : 10;
anchors.right: root.buttonLayout === "leftright" ? undefined : parent.right;
anchors.rightMargin: root.buttonLayout === "leftright" ? undefined : 10;
width: root.buttonLayout === "leftright" ? (root.button2text ? parent.width/2 - anchors.leftMargin*2 : parent.width - anchors.leftMargin * 2) :
(undefined);
height: 40;
text: root.button1text;
onClicked: {
button1method();
}
visible: (root.button1text !== "");
}
// Button 2
HifiControlsUit.Button {
id: button2;
visible: root.button2text;
color: root.button2color;
colorScheme: hifi.colorSchemes.light;
anchors.top: root.buttonLayout === "leftright" ? parent.top : button1.bottom;
anchors.topMargin: root.buttonLayout === "leftright" ? undefined : 20;
anchors.left: root.buttonLayout === "leftright" ? undefined : parent.left;
anchors.leftMargin: root.buttonLayout === "leftright" ? undefined : 10;
anchors.right: parent.right;
anchors.rightMargin: root.buttonLayout === "leftright" ? 30 : 10;
width: root.buttonLayout === "leftright" ? parent.width/2 - anchors.rightMargin*2 : undefined;
height: 40;
text: root.button2text;
onClicked: {
button2method();
}
}
}
}
//
// FUNCTION DEFINITIONS END
//
}

View file

@ -73,7 +73,7 @@ StackView {
function resetAfterTeleport() {
//storyCardFrame.shown = root.shown = false;
}
function goCard(targetString) {
function goCard(targetString, standaloneOptimized) {
if (0 !== targetString.indexOf('hifi://')) {
var card = tabletWebView.createObject();
card.url = addressBarDialog.metaverseServerUrl + targetString;
@ -82,7 +82,7 @@ StackView {
return;
}
location.text = targetString;
toggleOrGo(targetString, true);
toggleOrGo(targetString, true, standaloneOptimized);
clearAddressLineTimer.start();
}
@ -230,7 +230,7 @@ StackView {
updateLocationText(text.length > 0);
}
onAccepted: {
toggleOrGo();
toggleOrGo(addressLine.text, false, places.isStandalone(addressLine.text));
}
// unfortunately TextField from Quick Controls 2 disallow customization of placeHolderText color without creation of new style
@ -392,7 +392,18 @@ StackView {
right: parent.right
}
}
}
TADLightbox {
id: unoptimizedDomain
titleText: "Unoptimized Domain"
bodyText: "You're trying to access a place that hasn't been optimized for your device. Are you sure you want to continue."
button1text: "CANCEL"
button2text: "YES CONTINUE"
visible: false
button1method: function() {
visible = false;
}
}
function updateLocationText(enteringAddress) {
@ -407,14 +418,30 @@ StackView {
}
}
function toggleOrGo(address, fromSuggestions) {
if (address !== undefined && address !== "") {
addressBarDialog.loadAddress(address, fromSuggestions);
clearAddressLineTimer.start();
} else if (addressLine.text !== "") {
addressBarDialog.loadAddress(addressLine.text, fromSuggestions);
clearAddressLineTimer.start();
function toggleOrGo(address, fromSuggestions, standaloneOptimized) {
var goTarget = function () {
if (address !== undefined && address !== "") {
addressBarDialog.loadAddress(address, fromSuggestions);
clearAddressLineTimer.start();
} else if (addressLine.text !== "") {
addressBarDialog.loadAddress(addressLine.text, fromSuggestions);
clearAddressLineTimer.start();
}
DialogsManager.hideAddressBar();
}
unoptimizedDomain.button2method = function() {
Settings.setValue("ShowUnoptimizedDomainWarning", false);
goTarget();
}
var showPopup = PlatformInfo.isStandalone() && !standaloneOptimized && Settings.getValue("ShowUnoptimizedDomainWarning", true);
if(!showPopup) {
goTarget();
} else {
unoptimizedDomain.visible = true;
}
DialogsManager.hideAddressBar();
}
}

View file

@ -6,7 +6,7 @@ import QtQuick.Layouts 1.3
import TabletScriptingInterface 1.0
import "."
import stylesUit 1.0
import stylesUit 1.0 as HifiStylesUit
import "../audio" as HifiAudio
Item {
@ -49,44 +49,116 @@ Item {
}
Item {
width: 150
height: 50
id: rightContainer
width: clockItem.width > loginItem.width ? clockItem.width + clockAmPmTextMetrics.width :
loginItem.width + clockAmPmTextMetrics.width
height: parent.height
anchors.top: parent.top
anchors.topMargin: 15
anchors.right: parent.right
anchors.rightMargin: 30
anchors.verticalCenter: parent.verticalCenter
anchors.rightMargin: 20
anchors.bottom: parent.bottom
ColumnLayout {
anchors.fill: parent
function timeChanged() {
var date = new Date();
clockTime.text = date.toLocaleTimeString(Qt.locale("en_US"), "h:mm ap");
var regex = /[\sa-zA-z]+/;
clockTime.text = clockTime.text.replace(regex, "");
clockAmPm.text = date.toLocaleTimeString(Qt.locale("en_US"), "ap");
}
RalewaySemiBold {
text: Account.loggedIn ? qsTr("Log out") : qsTr("Log in")
horizontalAlignment: Text.AlignRight
Layout.alignment: Qt.AlignRight
font.pixelSize: 20
color: "#afafaf"
Timer {
interval: 1000; running: true; repeat: true;
onTriggered: rightContainer.timeChanged();
}
Item {
id: clockAmPmItem
width: clockAmPmTextMetrics.width
height: clockAmPmTextMetrics.height
anchors.top: parent.top
anchors.right: parent.right
TextMetrics {
id: clockAmPmTextMetrics
text: clockAmPm.text
font: clockAmPm.font
}
RalewaySemiBold {
visible: Account.loggedIn
height: Account.loggedIn ? parent.height/2 - parent.spacing/2 : 0
text: Account.loggedIn ? "[" + tabletRoot.usernameShort + "]" : ""
horizontalAlignment: Text.AlignRight
Layout.alignment: Qt.AlignRight
font.pixelSize: 20
Text {
anchors.left: parent.left
id: clockAmPm
anchors.right: parent.right
font.capitalization: Font.AllUppercase
font.pixelSize: 12
font.family: "Rawline"
color: "#afafaf"
}
}
MouseArea {
anchors.fill: parent
onClicked: {
if (!Account.loggedIn) {
DialogsManager.showLoginDialog()
} else {
Account.logOut()
Item {
id: clockItem
width: clockTimeTextMetrics.width
height: clockTimeTextMetrics.height
anchors {
top: parent.top
topMargin: -10
right: clockAmPmItem.left
rightMargin: 5
}
TextMetrics {
id: clockTimeTextMetrics
text: clockTime.text
font: clockTime.font
}
Text {
anchors.top: parent.top
anchors.right: parent.right
id: clockTime
font.bold: false
font.pixelSize: 36
font.family: "Rawline"
color: "#afafaf"
}
}
Item {
id: loginItem
width: loginTextMetrics.width
height: loginTextMetrics.height
anchors {
bottom: parent.bottom
bottomMargin: 10
right: clockAmPmItem.left
rightMargin: 5
}
Text {
id: loginText
anchors.right: parent.right
text: Account.loggedIn ? tabletRoot.usernameShort : qsTr("Log in")
horizontalAlignment: Text.AlignRight
Layout.alignment: Qt.AlignRight
font.pixelSize: 18
font.family: "Rawline"
color: "#afafaf"
}
TextMetrics {
id: loginTextMetrics
text: loginText.text
font: loginText.font
}
MouseArea {
anchors.fill: parent
onClicked: {
if (!Account.loggedIn) {
DialogsManager.showLoginDialog();
}
}
}
}
Component.onCompleted: {
rightContainer.timeChanged();
}
}
}

View file

@ -178,10 +178,10 @@ Rectangle {
function setUsername(newUsername) {
username = newUsername;
usernameShort = newUsername.substring(0, 8);
usernameShort = newUsername.substring(0, 14);
if (newUsername.length > 8) {
usernameShort = usernameShort + "..."
if (newUsername.length > 14) {
usernameShort = usernameShort + "..."
}
}

View file

@ -0,0 +1,20 @@
//
// Rawline.qml
//
// Created by Wayne Chen on 25 Feb 2019
// Copyright 2019 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.7
Text {
id: root
property real size: 32
font.pixelSize: size
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft
font.family: "Rawline"
}

View file

@ -0,0 +1,20 @@
//
// Rawline.qml
//
// Created by Wayne Chen on 25 Feb 2019
// Copyright 2019 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.7
Text {
id: root
property real size: 32
font.pixelSize: size
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft
font.family: "Rawline"
}

View file

@ -16,8 +16,8 @@
#include <QObject>
/**jsdoc
* The <code>HifiAbout</code> API provides information about the version of Interface that is currently running. It also
* provides the ability to open a Web page in an Interface browser window.
* The <code>HifiAbout</code> API provides information about the version of Interface that is currently running. It also
* has the functionality to open a web page in an Interface browser window.
*
* @namespace HifiAbout
*
@ -30,9 +30,9 @@
* @property {string} qtVersion - The Qt version used in Interface that is currently running. <em>Read-only.</em>
*
* @example <caption>Report build information for the version of Interface currently running.</caption>
* print("HiFi build date: " + HifiAbout.buildDate); // 11 Feb 2019
* print("HiFi version: " + HifiAbout.buildVersion); // 0.78.0
* print("Qt version: " + HifiAbout.qtVersion); // 5.10.1
* print("HiFi build date: " + HifiAbout.buildDate); // Returns the build date of the version of Interface currently running on your machine.
* print("HiFi version: " + HifiAbout.buildVersion); // Returns the build version of Interface currently running on your machine.
* print("Qt version: " + HifiAbout.qtVersion); // Returns the Qt version details of the version of Interface currently running on your machine.
*/
class AboutUtil : public QObject {
@ -52,9 +52,9 @@ public:
public slots:
/**jsdoc
* Display a Web page in an Interface browser window.
* Display a web page in an Interface browser window.
* @function HifiAbout.openUrl
* @param {string} url - The URL of the Web page to display.
* @param {string} url - The URL of the web page you want to view in Interface.
*/
void openUrl(const QString &url) const;
private:

View file

@ -45,6 +45,10 @@ void AndroidHelper::notifyBeforeEnterBackground() {
emit beforeEnterBackground();
}
void AndroidHelper::notifyToggleAwayMode() {
emit toggleAwayMode();
}
void AndroidHelper::notifyEnterBackground() {
emit enterBackground();
}

View file

@ -31,6 +31,7 @@ public:
void notifyEnterForeground();
void notifyBeforeEnterBackground();
void notifyEnterBackground();
void notifyToggleAwayMode();
void performHapticFeedback(int duration);
void processURL(const QString &url);
@ -55,7 +56,7 @@ signals:
void enterForeground();
void beforeEnterBackground();
void enterBackground();
void toggleAwayMode();
void hapticFeedbackRequested(int duration);
void handleSignupCompleted();

View file

@ -246,6 +246,8 @@
#include "AboutUtil.h"
#include <DisableDeferred.h>
#if defined(Q_OS_WIN)
#include <VersionHelpers.h>
@ -289,13 +291,6 @@ static const QString DISABLE_WATCHDOG_FLAG{ "HIFI_DISABLE_WATCHDOG" };
static bool DISABLE_WATCHDOG = nsightActive() || QProcessEnvironment::systemEnvironment().contains(DISABLE_WATCHDOG_FLAG);
#endif
#if defined(USE_GLES)
static bool DISABLE_DEFERRED = true;
#else
static const QString RENDER_FORWARD{ "HIFI_RENDER_FORWARD" };
static bool DISABLE_DEFERRED = QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD);
#endif
#if !defined(Q_OS_ANDROID)
static const uint32_t MAX_CONCURRENT_RESOURCE_DOWNLOADS = 16;
#else
@ -755,6 +750,11 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
bool isStore = cmdOptionExists(argc, const_cast<const char**>(argv), OCULUS_STORE_ARG);
qApp->setProperty(hifi::properties::OCULUS_STORE, isStore);
// emulate standalone device
static const auto STANDALONE_ARG = "--standalone";
bool isStandalone = cmdOptionExists(argc, const_cast<const char**>(argv), STANDALONE_ARG);
qApp->setProperty(hifi::properties::STANDALONE, isStandalone);
// Ignore any previous crashes if running from command line with a test script.
bool inTestMode { false };
for (int i = 0; i < argc; ++i) {
@ -1061,6 +1061,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
connect(PluginManager::getInstance().data(), &PluginManager::inputDeviceRunningChanged,
controllerScriptingInterface, &controller::ScriptingInterface::updateRunningInputDevices);
EntityTree::setEntityClicksCapturedOperator([this] {
return _controllerScriptingInterface->areEntityClicksCaptured();
});
_entityClipboard->createRootElement();
#ifdef Q_OS_WIN
@ -1075,6 +1079,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/FiraSans-SemiBold.ttf");
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Raleway-Light.ttf");
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Raleway-Regular.ttf");
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/rawline-500.ttf");
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Raleway-Bold.ttf");
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Raleway-SemiBold.ttf");
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Cairo-SemiBold.ttf");
@ -1594,12 +1599,21 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
connect(userInputMapper.data(), &UserInputMapper::actionEvent, [this](int action, float state) {
using namespace controller;
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
auto audioScriptingInterface = reinterpret_cast<scripting::Audio*>(DependencyManager::get<AudioScriptingInterface>().data());
{
auto actionEnum = static_cast<Action>(action);
int key = Qt::Key_unknown;
static int lastKey = Qt::Key_unknown;
bool navAxis = false;
switch (actionEnum) {
case Action::TOGGLE_PUSHTOTALK:
if (state > 0.0f) {
audioScriptingInterface->setPushingToTalk(true);
} else if (state <= 0.0f) {
audioScriptingInterface->setPushingToTalk(false);
}
break;
case Action::UI_NAV_VERTICAL:
navAxis = true;
if (state > 0.0f) {
@ -1756,6 +1770,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
#endif
});
// Setup the _keyboardMouseDevice, _touchscreenDevice, _touchscreenVirtualPadDevice and the user input mapper with the default bindings
userInputMapper->registerDevice(_keyboardMouseDevice->getInputDevice());
// if the _touchscreenDevice is not supported it will not be registered
@ -2301,31 +2316,31 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
DependencyManager::get<PickManager>()->setPrecisionPicking(rayPickID, value);
});
EntityItem::setBillboardRotationOperator([this](const glm::vec3& position, const glm::quat& rotation, BillboardMode billboardMode) {
EntityItem::setBillboardRotationOperator([this](const glm::vec3& position, const glm::quat& rotation, BillboardMode billboardMode, const glm::vec3& frustumPos) {
if (billboardMode == BillboardMode::YAW) {
//rotate about vertical to face the camera
ViewFrustum frustum;
copyViewFrustum(frustum);
glm::vec3 dPosition = frustum.getPosition() - position;
glm::vec3 dPosition = frustumPos - position;
// If x and z are 0, atan(x, z) is undefined, so default to 0 degrees
float yawRotation = dPosition.x == 0.0f && dPosition.z == 0.0f ? 0.0f : glm::atan(dPosition.x, dPosition.z);
return glm::quat(glm::vec3(0.0f, yawRotation, 0.0f));
} else if (billboardMode == BillboardMode::FULL) {
ViewFrustum frustum;
copyViewFrustum(frustum);
glm::vec3 cameraPos = frustum.getPosition();
// use the referencial from the avatar, y isn't always up
glm::vec3 avatarUP = DependencyManager::get<AvatarManager>()->getMyAvatar()->getWorldOrientation() * Vectors::UP;
// check to see if glm::lookAt will work / using glm::lookAt variable name
glm::highp_vec3 s(glm::cross(position - cameraPos, avatarUP));
glm::highp_vec3 s(glm::cross(position - frustumPos, avatarUP));
// make sure s is not NaN for any component
if (glm::length2(s) > 0.0f) {
return glm::conjugate(glm::toQuat(glm::lookAt(cameraPos, position, avatarUP)));
return glm::conjugate(glm::toQuat(glm::lookAt(frustumPos, position, avatarUP)));
}
}
return rotation;
});
EntityItem::setPrimaryViewFrustumPositionOperator([this]() {
ViewFrustum viewFrustum;
copyViewFrustum(viewFrustum);
return viewFrustum.getPosition();
});
render::entities::WebEntityRenderer::setAcquireWebSurfaceOperator([this](const QString& url, bool htmlContent, QSharedPointer<OffscreenQmlSurface>& webSurface, bool& cachedWebSurface) {
bool isTablet = url == TabletScriptingInterface::QML;
@ -2340,6 +2355,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
} else {
QObject::connect(webSurface.data(), &hifi::qml::OffscreenSurface::rootContextCreated, rootItemLoadedFunctor);
}
auto surfaceContext = webSurface->getSurfaceContext();
surfaceContext->setContextProperty("KeyboardScriptingInterface", DependencyManager::get<KeyboardScriptingInterface>().data());
} else {
// FIXME: the tablet should use the OffscreenQmlSurfaceCache
webSurface = QSharedPointer<OffscreenQmlSurface>(new OffscreenQmlSurface(), [](OffscreenQmlSurface* webSurface) {
@ -2411,6 +2428,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
connect(&AndroidHelper::instance(), &AndroidHelper::beforeEnterBackground, this, &Application::beforeEnterBackground);
connect(&AndroidHelper::instance(), &AndroidHelper::enterBackground, this, &Application::enterBackground);
connect(&AndroidHelper::instance(), &AndroidHelper::enterForeground, this, &Application::enterForeground);
connect(&AndroidHelper::instance(), &AndroidHelper::toggleAwayMode, this, &Application::toggleAwayMode);
AndroidHelper::instance().notifyLoadComplete();
#endif
pauseUntilLoginDetermined();
@ -3021,6 +3039,10 @@ void Application::initializeUi() {
};
OffscreenQmlSurface::addWhitelistContextHandler({
QUrl{ "hifi/commerce/marketplace/Marketplace.qml" },
QUrl{ "hifi/commerce/purchases/Purchases.qml" },
QUrl{ "hifi/commerce/wallet/Wallet.qml" },
QUrl{ "hifi/commerce/wallet/WalletHome.qml" },
QUrl{ "hifi/tablet/TabletAddressDialog.qml" },
}, platformInfoCallback);
QmlContextCallback ttsCallback = [](QQmlContext* context) {
@ -3275,6 +3297,7 @@ void Application::setupQmlSurface(QQmlContext* surfaceContext, bool setAdditiona
surfaceContext->setContextProperty("MyAvatar", DependencyManager::get<AvatarManager>()->getMyAvatar().get());
surfaceContext->setContextProperty("Entities", DependencyManager::get<EntityScriptingInterface>().data());
surfaceContext->setContextProperty("Snapshot", DependencyManager::get<Snapshot>().data());
surfaceContext->setContextProperty("KeyboardScriptingInterface", DependencyManager::get<KeyboardScriptingInterface>().data());
if (setAdditionalContextProperties) {
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
@ -3285,7 +3308,6 @@ void Application::setupQmlSurface(QQmlContext* surfaceContext, bool setAdditiona
surfaceContext->setContextProperty("Settings", SettingsScriptingInterface::getInstance());
surfaceContext->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance());
surfaceContext->setContextProperty("KeyboardScriptingInterface", DependencyManager::get<KeyboardScriptingInterface>().data());
surfaceContext->setContextProperty("Account", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED
surfaceContext->setContextProperty("GlobalServices", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED
@ -3661,8 +3683,8 @@ void Application::handleSandboxStatus(QNetworkReply* reply) {
// If this is a first run we short-circuit the address passed in
if (_firstRun.get()) {
#if !defined(Q_OS_ANDROID)
DependencyManager::get<AddressManager>()->goToEntry();
sentTo = SENT_TO_ENTRY;
DependencyManager::get<AddressManager>()->goToEntry();
sentTo = SENT_TO_ENTRY;
#endif
_firstRun.set(false);
@ -3773,9 +3795,12 @@ std::map<QString, QString> Application::prepareServerlessDomainContents(QUrl dom
tmpTree->reaverageOctreeElements();
tmpTree->sendEntities(&_entityEditSender, getEntities()->getTree(), 0, 0, 0);
}
std::map<QString, QString> namedPaths = tmpTree->getNamedPaths();
return tmpTree->getNamedPaths();
// we must manually eraseAllOctreeElements(false) else the tmpTree will mem-leak
tmpTree->eraseAllOctreeElements(false);
return namedPaths;
}
void Application::loadServerlessDomain(QUrl domainURL) {
@ -4294,6 +4319,7 @@ void Application::keyReleaseEvent(QKeyEvent* event) {
if (_keyboardMouseDevice->isActive()) {
_keyboardMouseDevice->keyReleaseEvent(event);
}
}
void Application::focusOutEvent(QFocusEvent* event) {
@ -4386,7 +4412,6 @@ void Application::mouseMoveEvent(QMouseEvent* event) {
if (compositor.getReticleVisible() || !isHMDMode() || !compositor.getReticleOverDesktop() ||
getOverlays().getOverlayAtPoint(glm::vec2(transformedPos.x(), transformedPos.y())) != UNKNOWN_ENTITY_ID) {
getEntities()->mouseMoveEvent(&mappedEvent);
getOverlays().mouseMoveEvent(&mappedEvent);
}
_controllerScriptingInterface->emitMouseMoveEvent(&mappedEvent); // send events to any registered scripts
@ -4420,14 +4445,8 @@ void Application::mousePressEvent(QMouseEvent* event) {
#endif
QMouseEvent mappedEvent(event->type(), transformedPos, event->screenPos(), event->button(), event->buttons(), event->modifiers());
std::pair<float, QUuid> entityResult;
if (!_controllerScriptingInterface->areEntityClicksCaptured()) {
entityResult = getEntities()->mousePressEvent(&mappedEvent);
}
std::pair<float, QUuid> overlayResult = getOverlays().mousePressEvent(&mappedEvent);
QUuid focusedEntity = entityResult.first < overlayResult.first ? entityResult.second : overlayResult.second;
setKeyboardFocusEntity(getEntities()->wantsKeyboardFocus(focusedEntity) ? focusedEntity : UNKNOWN_ENTITY_ID);
QUuid result = getEntities()->mousePressEvent(&mappedEvent);
setKeyboardFocusEntity(getEntities()->wantsKeyboardFocus(result) ? result : UNKNOWN_ENTITY_ID);
_controllerScriptingInterface->emitMousePressEvent(&mappedEvent); // send events to any registered scripts
@ -4466,11 +4485,7 @@ void Application::mouseDoublePressEvent(QMouseEvent* event) {
transformedPos,
event->screenPos(), event->button(),
event->buttons(), event->modifiers());
if (!_controllerScriptingInterface->areEntityClicksCaptured()) {
getEntities()->mouseDoublePressEvent(&mappedEvent);
}
getOverlays().mouseDoublePressEvent(&mappedEvent);
getEntities()->mouseDoublePressEvent(&mappedEvent);
// if one of our scripts have asked to capture this event, then stop processing it
if (_controllerScriptingInterface->isMouseCaptured()) {
@ -4495,7 +4510,6 @@ void Application::mouseReleaseEvent(QMouseEvent* event) {
event->buttons(), event->modifiers());
getEntities()->mouseReleaseEvent(&mappedEvent);
getOverlays().mouseReleaseEvent(&mappedEvent);
_controllerScriptingInterface->emitMouseReleaseEvent(&mappedEvent); // send events to any registered scripts
@ -4979,6 +4993,15 @@ void Application::idle() {
}
}
{
if (_keyboardFocusWaitingOnRenderable && getEntities()->renderableForEntityId(_keyboardFocusedEntity.get())) {
QUuid entityId = _keyboardFocusedEntity.get();
setKeyboardFocusEntity(UNKNOWN_ENTITY_ID);
_keyboardFocusWaitingOnRenderable = false;
setKeyboardFocusEntity(entityId);
}
}
{
PerformanceTimer perfTimer("pluginIdle");
PerformanceWarning warn(showWarnings, "Application::idle()... pluginIdle()");
@ -5228,6 +5251,9 @@ void Application::loadSettings() {
}
}
auto audioScriptingInterface = reinterpret_cast<scripting::Audio*>(DependencyManager::get<AudioScriptingInterface>().data());
audioScriptingInterface->loadData();
getMyAvatar()->loadData();
_settingsLoaded = true;
}
@ -5237,6 +5263,9 @@ void Application::saveSettings() const {
DependencyManager::get<AudioClient>()->saveSettings();
DependencyManager::get<LODManager>()->saveSettings();
auto audioScriptingInterface = reinterpret_cast<scripting::Audio*>(DependencyManager::get<AudioScriptingInterface>().data());
audioScriptingInterface->saveData();
Menu::getInstance()->saveSettings();
getMyAvatar()->saveData();
PluginManager::getInstance()->saveSettings();
@ -5761,6 +5790,11 @@ void Application::reloadResourceCaches() {
DependencyManager::get<NodeList>()->reset(); // Force redownload of .fst models
DependencyManager::get<ScriptEngines>()->reloadAllScripts();
getOffscreenUI()->clearCache();
DependencyManager::get<Keyboard>()->createKeyboard();
getMyAvatar()->resetFullAvatarURL();
}
@ -5807,7 +5841,7 @@ void Application::setKeyboardFocusEntity(const QUuid& id) {
if (qApp->getLoginDialogPoppedUp() && !_loginDialogID.isNull()) {
if (id == _loginDialogID) {
emit loginDialogFocusEnabled();
} else {
} else if (!_keyboardFocusWaitingOnRenderable) {
// that's the only entity we want in focus;
return;
}
@ -5824,7 +5858,10 @@ void Application::setKeyboardFocusEntity(const QUuid& id) {
if (properties.getVisible()) {
auto entities = getEntities();
auto entityId = _keyboardFocusedEntity.get();
if (entities->wantsKeyboardFocus(entityId)) {
auto entityItemRenderable = entities->renderableForEntityId(entityId);
if (!entityItemRenderable) {
_keyboardFocusWaitingOnRenderable = true;
} else if (entityItemRenderable->wantsKeyboardFocus()) {
entities->setProxyWindow(entityId, _window->windowHandle());
if (_keyboardMouseDevice->isActive()) {
_keyboardMouseDevice->pluginFocusOutEvent();
@ -6928,7 +6965,7 @@ void Application::clearDomainOctreeDetails(bool clearAll) {
});
// reset the model renderer
clearAll ? getEntities()->clear() : getEntities()->clearNonLocalEntities();
clearAll ? getEntities()->clear() : getEntities()->clearDomainAndNonOwnedEntities();
auto skyStage = DependencyManager::get<SceneScriptingInterface>()->getSkyStage();
@ -9135,6 +9172,8 @@ void Application::beforeEnterBackground() {
clearDomainOctreeDetails();
}
void Application::enterBackground() {
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(),
"stop", Qt::BlockingQueuedConnection);
@ -9158,6 +9197,15 @@ void Application::enterForeground() {
auto nodeList = DependencyManager::get<NodeList>();
nodeList->setSendDomainServerCheckInEnabled(true);
}
void Application::toggleAwayMode(){
QKeyEvent event = QKeyEvent (QEvent::KeyPress, Qt::Key_Escape, Qt::NoModifier);
QCoreApplication::sendEvent (this, &event);
}
#endif
#include "Application.moc"

View file

@ -338,7 +338,8 @@ public:
void beforeEnterBackground();
void enterBackground();
void enterForeground();
#endif
void toggleAwayMode();
#endif
signals:
void svoImportRequested(const QString& url);
@ -732,6 +733,7 @@ private:
bool _failedToConnectToEntityServer { false };
bool _reticleClickPressed { false };
bool _keyboardFocusWaitingOnRenderable { false };
int _avatarAttachmentRequest = 0;

View file

@ -19,13 +19,19 @@
#include <SimpleMovingAverage.h>
#include <render/Args.h>
#ifdef Q_OS_ANDROID
const float LOD_DEFAULT_QUALITY_LEVEL = 0.75f; // default quality level setting is High (lower framerate)
const float LOD_DEFAULT_QUALITY_LEVEL = 0.2f; // default quality level setting is High (lower framerate)
#else
const float LOD_DEFAULT_QUALITY_LEVEL = 0.5f; // default quality level setting is Mid
#endif
const float LOD_MAX_LIKELY_DESKTOP_FPS = 60.0f; // this is essentially, V-synch fps
#ifdef Q_OS_ANDROID
const float LOD_MAX_LIKELY_HMD_FPS = 36.0f; // this is essentially, V-synch fps
#else
const float LOD_MAX_LIKELY_HMD_FPS = 90.0f; // this is essentially, V-synch fps
#endif
const float LOD_OFFSET_FPS = 5.0f; // offset of FPS to add for computing the target framerate
class AABox;

View file

@ -12,10 +12,52 @@
#include "AvatarDoctor.h"
#include <model-networking/ModelCache.h>
#include <AvatarConstants.h>
#include <Rig.h>
#include <ResourceManager.h>
#include <QDir>
#include <FSTReader.h>
const int NETWORKED_JOINTS_LIMIT = 256;
const float HIPS_GROUND_MIN_Y = 0.01f;
const float HIPS_SPINE_CHEST_MIN_SEPARATION = 0.001f;
const QString LEFT_JOINT_PREFIX = "Left";
const QString RIGHT_JOINT_PREFIX = "Right";
AvatarDoctor::AvatarDoctor(QUrl avatarFSTFileUrl) :
const QStringList LEG_MAPPING_SUFFIXES = {
"UpLeg"
"Leg",
"Foot",
"ToeBase",
};
static QStringList ARM_MAPPING_SUFFIXES = {
"Shoulder",
"Arm",
"ForeArm",
"Hand",
};
static QStringList HAND_MAPPING_SUFFIXES = {
"HandIndex3",
"HandIndex2",
"HandIndex1",
"HandPinky3",
"HandPinky2",
"HandPinky1",
"HandMiddle3",
"HandMiddle2",
"HandMiddle1",
"HandRing3",
"HandRing2",
"HandRing1",
"HandThumb3",
"HandThumb2",
"HandThumb1",
};
const QUrl DEFAULT_DOCS_URL = QUrl("https://docs.highfidelity.com/create/avatars/create-avatars.html#create-your-own-avatar");
AvatarDoctor::AvatarDoctor(const QUrl& avatarFSTFileUrl) :
_avatarFSTFileUrl(avatarFSTFileUrl) {
connect(this, &AvatarDoctor::complete, this, [this](QVariantList errors) {
@ -39,136 +81,215 @@ void AvatarDoctor::startDiagnosing() {
const auto resource = DependencyManager::get<ModelCache>()->getGeometryResource(_avatarFSTFileUrl);
resource->refresh();
const QUrl DEFAULT_URL = QUrl("https://docs.highfidelity.com/create/avatars/create-avatars.html#create-your-own-avatar");
const auto resourceLoaded = [this, resource, DEFAULT_URL](bool success) {
const auto resourceLoaded = [this, resource](bool success) {
// MODEL
if (!success) {
_errors.push_back({ "Model file cannot be opened", DEFAULT_URL });
_errors.push_back({ "Model file cannot be opened.", DEFAULT_DOCS_URL });
emit complete(getErrors());
return;
}
_model = resource;
const auto model = resource.data();
const auto avatarModel = resource.data()->getHFMModel();
if (!avatarModel.originalURL.endsWith(".fbx")) {
_errors.push_back({ "Unsupported avatar model format", DEFAULT_URL });
_errors.push_back({ "Unsupported avatar model format.", DEFAULT_DOCS_URL });
emit complete(getErrors());
return;
}
// RIG
if (avatarModel.joints.isEmpty()) {
_errors.push_back({ "Avatar has no rig", DEFAULT_URL });
_errors.push_back({ "Avatar has no rig.", DEFAULT_DOCS_URL });
} else {
if (avatarModel.joints.length() > 256) {
_errors.push_back({ "Avatar has over 256 bones", DEFAULT_URL });
auto jointNames = avatarModel.getJointNames();
if (avatarModel.joints.length() > NETWORKED_JOINTS_LIMIT) {
_errors.push_back({tr( "Avatar has over %n bones.", "", NETWORKED_JOINTS_LIMIT), DEFAULT_DOCS_URL });
}
// Avatar does not have Hips bone mapped
if (!avatarModel.getJointNames().contains("Hips")) {
_errors.push_back({ "Hips are not mapped", DEFAULT_URL });
if (!jointNames.contains("Hips")) {
_errors.push_back({ "Hips are not mapped.", DEFAULT_DOCS_URL });
}
if (!avatarModel.getJointNames().contains("Spine")) {
_errors.push_back({ "Spine is not mapped", DEFAULT_URL });
if (!jointNames.contains("Spine")) {
_errors.push_back({ "Spine is not mapped.", DEFAULT_DOCS_URL });
}
if (!avatarModel.getJointNames().contains("Head")) {
_errors.push_back({ "Head is not mapped", DEFAULT_URL });
if (!jointNames.contains("Spine1")) {
_errors.push_back({ "Chest (Spine1) is not mapped.", DEFAULT_DOCS_URL });
}
if (!jointNames.contains("Neck")) {
_errors.push_back({ "Neck is not mapped.", DEFAULT_DOCS_URL });
}
if (!jointNames.contains("Head")) {
_errors.push_back({ "Head is not mapped.", DEFAULT_DOCS_URL });
}
if (!jointNames.contains("LeftEye")) {
if (jointNames.contains("RightEye")) {
_errors.push_back({ "LeftEye is not mapped.", DEFAULT_DOCS_URL });
} else {
_errors.push_back({ "Eyes are not mapped.", DEFAULT_DOCS_URL });
}
} else if (!jointNames.contains("RightEye")) {
_errors.push_back({ "RightEye is not mapped.", DEFAULT_DOCS_URL });
}
const auto checkJointAsymmetry = [jointNames] (const QStringList& jointMappingSuffixes) {
for (const QString& jointSuffix : jointMappingSuffixes) {
if (jointNames.contains(LEFT_JOINT_PREFIX + jointSuffix) !=
jointNames.contains(RIGHT_JOINT_PREFIX + jointSuffix)) {
return true;
}
}
return false;
};
const auto isDescendantOfJointWhenJointsExist = [avatarModel, jointNames] (const QString& jointName, const QString& ancestorName) {
if (!jointNames.contains(jointName) || !jointNames.contains(ancestorName)) {
return true;
}
auto currentJoint = avatarModel.joints[avatarModel.jointIndices[jointName] - 1];
while (currentJoint.parentIndex != -1) {
currentJoint = avatarModel.joints[currentJoint.parentIndex];
if (currentJoint.name == ancestorName) {
return true;
}
}
return false;
};
if (checkJointAsymmetry(ARM_MAPPING_SUFFIXES)) {
_errors.push_back({ "Asymmetrical arm bones.", DEFAULT_DOCS_URL });
}
if (checkJointAsymmetry(HAND_MAPPING_SUFFIXES)) {
_errors.push_back({ "Asymmetrical hand bones.", DEFAULT_DOCS_URL });
}
if (checkJointAsymmetry(LEG_MAPPING_SUFFIXES)) {
_errors.push_back({ "Asymmetrical leg bones.", DEFAULT_DOCS_URL });
}
// Multiple skeleton root joints checkup
int skeletonRootJoints = 0;
for (const HFMJoint& joint: avatarModel.joints) {
if (joint.parentIndex == -1 && joint.isSkeletonJoint) {
skeletonRootJoints++;
}
}
if (skeletonRootJoints > 1) {
_errors.push_back({ "Multiple top-level joints found.", DEFAULT_DOCS_URL });
}
Rig rig;
rig.reset(avatarModel);
const float eyeHeight = rig.getUnscaledEyeHeight();
const float ratio = eyeHeight / DEFAULT_AVATAR_HEIGHT;
const float avatarHeight = eyeHeight + ratio * DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD;
// SCALE
const float RECOMMENDED_MIN_HEIGHT = DEFAULT_AVATAR_HEIGHT * 0.25f;
const float RECOMMENDED_MAX_HEIGHT = DEFAULT_AVATAR_HEIGHT * 1.5f;
if (avatarHeight < RECOMMENDED_MIN_HEIGHT) {
_errors.push_back({ "Avatar is possibly too short.", DEFAULT_DOCS_URL });
} else if (avatarHeight > RECOMMENDED_MAX_HEIGHT) {
_errors.push_back({ "Avatar is possibly too tall.", DEFAULT_DOCS_URL });
}
// HipsNotOnGround
auto hipsIndex = rig.indexOfJoint("Hips");
if (hipsIndex >= 0) {
glm::vec3 hipsPosition;
if (rig.getJointPosition(hipsIndex, hipsPosition)) {
const auto hipJoint = avatarModel.joints.at(avatarModel.getJointIndex("Hips"));
if (hipsPosition.y < HIPS_GROUND_MIN_Y) {
_errors.push_back({ "Hips are on ground.", DEFAULT_DOCS_URL });
}
}
}
// HipsSpineChestNotCoincident
auto spineIndex = rig.indexOfJoint("Spine");
auto chestIndex = rig.indexOfJoint("Spine1");
if (hipsIndex >= 0 && spineIndex >= 0 && chestIndex >= 0) {
glm::vec3 hipsPosition;
glm::vec3 spinePosition;
glm::vec3 chestPosition;
if (rig.getJointPosition(hipsIndex, hipsPosition) &&
rig.getJointPosition(spineIndex, spinePosition) &&
rig.getJointPosition(chestIndex, chestPosition)) {
const auto hipsToSpine = glm::length(hipsPosition - spinePosition);
const auto spineToChest = glm::length(spinePosition - chestPosition);
if (hipsToSpine < HIPS_SPINE_CHEST_MIN_SEPARATION && spineToChest < HIPS_SPINE_CHEST_MIN_SEPARATION) {
_errors.push_back({ "Hips/Spine/Chest overlap.", DEFAULT_DOCS_URL });
}
}
}
auto mapping = resource->getMapping();
if (mapping.contains(JOINT_NAME_MAPPING_FIELD) && mapping[JOINT_NAME_MAPPING_FIELD].type() == QVariant::Hash) {
const auto& jointNameMappings = mapping[JOINT_NAME_MAPPING_FIELD].toHash();
QStringList jointValues;
for (const auto& jointVariant: jointNameMappings.values()) {
jointValues << jointVariant.toString();
}
const auto& uniqueJointValues = jointValues.toSet();
for (const auto& jointName: uniqueJointValues) {
if (jointValues.count(jointName) > 1) {
_errors.push_back({ tr("%1 is mapped multiple times.").arg(jointName), DEFAULT_DOCS_URL });
}
}
}
if (!isDescendantOfJointWhenJointsExist("Spine", "Hips")) {
_errors.push_back({ "Spine is not a child of Hips.", DEFAULT_DOCS_URL });
}
if (!isDescendantOfJointWhenJointsExist("Spine1", "Spine")) {
_errors.push_back({ "Spine1 is not a child of Spine.", DEFAULT_DOCS_URL });
}
if (!isDescendantOfJointWhenJointsExist("Head", "Spine1")) {
_errors.push_back({ "Head is not a child of Spine1.", DEFAULT_DOCS_URL });
}
}
// SCALE
const float RECOMMENDED_MIN_HEIGHT = DEFAULT_AVATAR_HEIGHT * 0.25f;
const float RECOMMENDED_MAX_HEIGHT = DEFAULT_AVATAR_HEIGHT * 1.5f;
const float avatarHeight = avatarModel.bindExtents.largestDimension();
if (avatarHeight < RECOMMENDED_MIN_HEIGHT) {
_errors.push_back({ "Avatar is possibly too small.", DEFAULT_URL });
} else if (avatarHeight > RECOMMENDED_MAX_HEIGHT) {
_errors.push_back({ "Avatar is possibly too large.", DEFAULT_URL });
}
// TEXTURES
QStringList externalTextures{};
QSet<QString> textureNames{};
auto addTextureToList = [&externalTextures](hfm::Texture texture) mutable {
if (!texture.filename.isEmpty() && texture.content.isEmpty() && !externalTextures.contains(texture.name)) {
externalTextures << texture.name;
auto materialMappingHandled = [this]() mutable {
_materialMappingLoadedCount++;
// Continue diagnosing the textures as soon as the material mappings have tried to load.
if (_materialMappingLoadedCount == _materialMappingCount) {
// TEXTURES
diagnoseTextures();
}
};
foreach(const HFMMaterial material, avatarModel.materials) {
addTextureToList(material.normalTexture);
addTextureToList(material.albedoTexture);
addTextureToList(material.opacityTexture);
addTextureToList(material.glossTexture);
addTextureToList(material.roughnessTexture);
addTextureToList(material.specularTexture);
addTextureToList(material.metallicTexture);
addTextureToList(material.emissiveTexture);
addTextureToList(material.occlusionTexture);
addTextureToList(material.scatteringTexture);
addTextureToList(material.lightmapTexture);
}
if (!externalTextures.empty()) {
// Check External Textures:
auto modelTexturesURLs = model->getTextures();
_externalTextureCount = externalTextures.length();
foreach(const QString textureKey, externalTextures) {
if (!modelTexturesURLs.contains(textureKey)) {
_missingTextureCount++;
_checkedTextureCount++;
continue;
}
const QUrl textureURL = modelTexturesURLs[textureKey].toUrl();
auto textureResource = DependencyManager::get<TextureCache>()->getTexture(textureURL);
auto checkTextureLoadingComplete = [this, DEFAULT_URL] () mutable {
qDebug() << "checkTextureLoadingComplete" << _checkedTextureCount << "/" << _externalTextureCount;
if (_checkedTextureCount == _externalTextureCount) {
if (_missingTextureCount > 0) {
_errors.push_back({ tr("Missing %n texture(s).","", _missingTextureCount), DEFAULT_URL });
}
if (_unsupportedTextureCount > 0) {
_errors.push_back({ tr("%n unsupported texture(s) found.", "", _unsupportedTextureCount), DEFAULT_URL });
}
emit complete(getErrors());
}
};
auto textureLoaded = [this, textureResource, checkTextureLoadingComplete] (bool success) mutable {
if (!success) {
auto normalizedURL = DependencyManager::get<ResourceManager>()->normalizeURL(textureResource->getURL());
if (normalizedURL.isLocalFile()) {
QFile textureFile(normalizedURL.toLocalFile());
if (textureFile.exists()) {
_unsupportedTextureCount++;
} else {
_missingTextureCount++;
}
} else {
_missingTextureCount++;
}
}
_checkedTextureCount++;
checkTextureLoadingComplete();
};
if (textureResource) {
textureResource->refresh();
if (textureResource->isLoaded()) {
textureLoaded(!textureResource->isFailed());
} else {
connect(textureResource.data(), &NetworkTexture::finished, this, textureLoaded);
}
_materialMappingCount = (int)model->getMaterialMapping().size();
_materialMappingLoadedCount = 0;
for (const auto& materialMapping : model->getMaterialMapping()) {
// refresh the texture mappings
auto materialMappingResource = materialMapping.second;
if (materialMappingResource) {
materialMappingResource->refresh();
if (materialMappingResource->isLoaded()) {
materialMappingHandled();
} else {
_missingTextureCount++;
_checkedTextureCount++;
checkTextureLoadingComplete();
connect(materialMappingResource.data(), &NetworkTexture::finished, this,
[materialMappingHandled](bool success) mutable {
materialMappingHandled();
});
}
} else {
materialMappingHandled();
}
} else {
emit complete(getErrors());
}
if (_materialMappingCount == 0) {
// TEXTURES
diagnoseTextures();
}
};
@ -179,11 +300,117 @@ void AvatarDoctor::startDiagnosing() {
connect(resource.data(), &GeometryResource::finished, this, resourceLoaded);
}
} else {
_errors.push_back({ "Model file cannot be opened", DEFAULT_URL });
_errors.push_back({ "Model file cannot be opened", DEFAULT_DOCS_URL });
emit complete(getErrors());
}
}
void AvatarDoctor::diagnoseTextures() {
const auto model = _model.data();
const auto avatarModel = _model.data()->getHFMModel();
QVector<QString> externalTextures{};
QVector<QString> textureNames{};
int texturesFound = 0;
auto addTextureToList = [&externalTextures, &texturesFound](hfm::Texture texture) mutable {
if (texture.filename.isEmpty()) {
return;
}
if (texture.content.isEmpty() && !externalTextures.contains(texture.name)) {
externalTextures << texture.name;
}
texturesFound++;
};
for (const auto& material : avatarModel.materials) {
addTextureToList(material.normalTexture);
addTextureToList(material.albedoTexture);
addTextureToList(material.opacityTexture);
addTextureToList(material.glossTexture);
addTextureToList(material.roughnessTexture);
addTextureToList(material.specularTexture);
addTextureToList(material.metallicTexture);
addTextureToList(material.emissiveTexture);
addTextureToList(material.occlusionTexture);
addTextureToList(material.scatteringTexture);
addTextureToList(material.lightmapTexture);
}
for (const auto& materialMapping : model->getMaterialMapping()) {
for (const auto& networkMaterial : materialMapping.second.data()->parsedMaterials.networkMaterials) {
texturesFound += (int)networkMaterial.second->getTextureMaps().size();
}
}
auto normalizedURL = DependencyManager::get<ResourceManager>()->normalizeURL(
QUrl(avatarModel.originalURL)).resolved(QUrl("textures"));
if (texturesFound == 0) {
_errors.push_back({ tr("No textures assigned."), DEFAULT_DOCS_URL });
}
if (!externalTextures.empty()) {
// Check External Textures:
auto modelTexturesURLs = model->getTextures();
_externalTextureCount = externalTextures.length();
auto checkTextureLoadingComplete = [this]() mutable {
if (_checkedTextureCount == _externalTextureCount) {
if (_missingTextureCount > 0) {
_errors.push_back({ tr("Missing %n texture(s).","", _missingTextureCount), DEFAULT_DOCS_URL });
}
if (_unsupportedTextureCount > 0) {
_errors.push_back({ tr("%n unsupported texture(s) found.", "", _unsupportedTextureCount),
DEFAULT_DOCS_URL });
}
emit complete(getErrors());
}
};
for (const QString& textureKey : externalTextures) {
if (!modelTexturesURLs.contains(textureKey)) {
_missingTextureCount++;
_checkedTextureCount++;
continue;
}
const QUrl textureURL = modelTexturesURLs[textureKey].toUrl();
auto textureResource = DependencyManager::get<TextureCache>()->getTexture(textureURL);
auto textureLoaded = [this, textureResource, checkTextureLoadingComplete](bool success) mutable {
if (!success) {
auto normalizedURL = DependencyManager::get<ResourceManager>()->normalizeURL(textureResource->getURL());
if (normalizedURL.isLocalFile()) {
QFile textureFile(normalizedURL.toLocalFile());
if (textureFile.exists()) {
_unsupportedTextureCount++;
} else {
_missingTextureCount++;
}
} else {
_missingTextureCount++;
}
}
_checkedTextureCount++;
checkTextureLoadingComplete();
};
if (textureResource) {
textureResource->refresh();
if (textureResource->isLoaded()) {
textureLoaded(!textureResource->isFailed());
} else {
connect(textureResource.data(), &NetworkTexture::finished, this, textureLoaded);
}
} else {
_missingTextureCount++;
_checkedTextureCount++;
}
}
checkTextureLoadingComplete();
} else {
emit complete(getErrors());
}
}
QVariantList AvatarDoctor::getErrors() const {
QVariantList result;
for (const auto& error : _errors) {

View file

@ -16,6 +16,7 @@
#include <QUrl>
#include <QVector>
#include <QVariantMap>
#include "GeometryCache.h"
struct AvatarDiagnosticResult {
QString message;
@ -27,7 +28,7 @@ Q_DECLARE_METATYPE(QVector<AvatarDiagnosticResult>)
class AvatarDoctor : public QObject {
Q_OBJECT
public:
AvatarDoctor(QUrl avatarFSTFileUrl);
AvatarDoctor(const QUrl& avatarFSTFileUrl);
Q_INVOKABLE void startDiagnosing();
@ -37,6 +38,8 @@ signals:
void complete(QVariantList errors);
private:
void diagnoseTextures();
QUrl _avatarFSTFileUrl;
QVector<AvatarDiagnosticResult> _errors;
@ -45,6 +48,11 @@ private:
int _missingTextureCount = 0;
int _unsupportedTextureCount = 0;
int _materialMappingCount = 0;
int _materialMappingLoadedCount = 0;
GeometryResource::Pointer _model;
bool _isDiagnosing = false;
};

View file

@ -232,93 +232,142 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
auto avatarMap = getHashCopy();
const auto& views = qApp->getConicalViews();
PrioritySortUtil::PriorityQueue<SortableAvatar> sortedAvatars(views,
AvatarData::_avatarSortCoefficientSize,
AvatarData::_avatarSortCoefficientCenter,
AvatarData::_avatarSortCoefficientAge);
sortedAvatars.reserve(avatarMap.size() - 1); // don't include MyAvatar
// Prepare 2 queues for heros and for crowd avatars
using AvatarPriorityQueue = PrioritySortUtil::PriorityQueue<SortableAvatar>;
// Keep two independent queues, one for heroes and one for the riff-raff.
enum PriorityVariants
{
kHero = 0,
kNonHero,
NumVariants
};
AvatarPriorityQueue avatarPriorityQueues[NumVariants] = {
{ views,
AvatarData::_avatarSortCoefficientSize,
AvatarData::_avatarSortCoefficientCenter,
AvatarData::_avatarSortCoefficientAge },
{ views,
AvatarData::_avatarSortCoefficientSize,
AvatarData::_avatarSortCoefficientCenter,
AvatarData::_avatarSortCoefficientAge } };
// Reserve space
//avatarPriorityQueues[kHero].reserve(10); // just few
avatarPriorityQueues[kNonHero].reserve(avatarMap.size() - 1); // don't include MyAvatar
// Build vector and compute priorities
auto nodeList = DependencyManager::get<NodeList>();
AvatarHash::iterator itr = avatarMap.begin();
while (itr != avatarMap.end()) {
const auto& avatar = std::static_pointer_cast<Avatar>(*itr);
auto avatar = std::static_pointer_cast<Avatar>(*itr);
// DO NOT update _myAvatar! Its update has already been done earlier in the main loop.
// DO NOT update or fade out uninitialized Avatars
if (avatar != _myAvatar && avatar->isInitialized() && !nodeList->isPersonalMutingNode(avatar->getID())) {
sortedAvatars.push(SortableAvatar(avatar));
if (avatar->getHasPriority()) {
avatarPriorityQueues[kHero].push(SortableAvatar(avatar));
} else {
avatarPriorityQueues[kNonHero].push(SortableAvatar(avatar));
}
}
++itr;
}
// Sort
const auto& sortedAvatarVector = sortedAvatars.getSortedVector();
_numHeroAvatars = (int)avatarPriorityQueues[kHero].size();
// process in sorted order
uint64_t startTime = usecTimestampNow();
uint64_t updateExpiry = startTime + MAX_UPDATE_AVATARS_TIME_BUDGET;
const uint64_t MAX_UPDATE_HEROS_TIME_BUDGET = uint64_t(0.8 * MAX_UPDATE_AVATARS_TIME_BUDGET);
uint64_t updatePriorityExpiries[NumVariants] = { startTime + MAX_UPDATE_HEROS_TIME_BUDGET, startTime + MAX_UPDATE_AVATARS_TIME_BUDGET };
int numHerosUpdated = 0;
int numAvatarsUpdated = 0;
int numAVatarsNotUpdated = 0;
int numAvatarsNotUpdated = 0;
render::Transaction renderTransaction;
workload::Transaction workloadTransaction;
for (auto it = sortedAvatarVector.begin(); it != sortedAvatarVector.end(); ++it) {
const SortableAvatar& sortData = *it;
const auto avatar = std::static_pointer_cast<OtherAvatar>(sortData.getAvatar());
if (!avatar->_isClientAvatar) {
avatar->setIsClientAvatar(true);
}
// TODO: to help us scale to more avatars it would be nice to not have to poll this stuff every update
if (avatar->getSkeletonModel()->isLoaded()) {
// remove the orb if it is there
avatar->removeOrb();
if (avatar->needsPhysicsUpdate()) {
_avatarsToChangeInPhysics.insert(avatar);
}
} else {
avatar->updateOrbPosition();
}
for (int p = kHero; p < NumVariants; p++) {
auto& priorityQueue = avatarPriorityQueues[p];
// Sorting the current queue HERE as part of the measured timing.
const auto& sortedAvatarVector = priorityQueue.getSortedVector();
// for ALL avatars...
if (_shouldRender) {
avatar->ensureInScene(avatar, qApp->getMain3DScene());
}
avatar->animateScaleChanges(deltaTime);
auto passExpiry = updatePriorityExpiries[p];
uint64_t now = usecTimestampNow();
if (now < updateExpiry) {
// we're within budget
bool inView = sortData.getPriority() > OUT_OF_VIEW_THRESHOLD;
if (inView && avatar->hasNewJointData()) {
numAvatarsUpdated++;
for (auto it = sortedAvatarVector.begin(); it != sortedAvatarVector.end(); ++it) {
const SortableAvatar& sortData = *it;
const auto avatar = std::static_pointer_cast<OtherAvatar>(sortData.getAvatar());
if (!avatar->_isClientAvatar) {
avatar->setIsClientAvatar(true);
}
auto transitStatus = avatar->_transit.update(deltaTime, avatar->_serverPosition, _transitConfig);
if (avatar->getIsNewAvatar() && (transitStatus == AvatarTransit::Status::START_TRANSIT || transitStatus == AvatarTransit::Status::ABORT_TRANSIT)) {
avatar->_transit.reset();
avatar->setIsNewAvatar(false);
}
avatar->simulate(deltaTime, inView);
avatar->updateRenderItem(renderTransaction);
avatar->updateSpaceProxy(workloadTransaction);
avatar->setLastRenderUpdateTime(startTime);
} else {
// we've spent our full time budget --> bail on the rest of the avatar updates
// --> more avatars may freeze until their priority trickles up
// --> some scale animations may glitch
// --> some avatar velocity measurements may be a little off
// no time to simulate, but we take the time to count how many were tragically missed
while (it != sortedAvatarVector.end()) {
const SortableAvatar& newSortData = *it;
const auto& newAvatar = newSortData.getAvatar();
bool inView = newSortData.getPriority() > OUT_OF_VIEW_THRESHOLD;
// Once we reach an avatar that's not in view, all avatars after it will also be out of view
if (!inView) {
break;
// TODO: to help us scale to more avatars it would be nice to not have to poll this stuff every update
if (avatar->getSkeletonModel()->isLoaded()) {
// remove the orb if it is there
avatar->removeOrb();
if (avatar->needsPhysicsUpdate()) {
_avatarsToChangeInPhysics.insert(avatar);
}
numAVatarsNotUpdated += (int)(newAvatar->hasNewJointData());
++it;
} else {
avatar->updateOrbPosition();
}
break;
// for ALL avatars...
if (_shouldRender) {
avatar->ensureInScene(avatar, qApp->getMain3DScene());
}
avatar->animateScaleChanges(deltaTime);
uint64_t now = usecTimestampNow();
if (now < passExpiry) {
// we're within budget
bool inView = sortData.getPriority() > OUT_OF_VIEW_THRESHOLD;
if (inView && avatar->hasNewJointData()) {
numAvatarsUpdated++;
}
auto transitStatus = avatar->_transit.update(deltaTime, avatar->_serverPosition, _transitConfig);
if (avatar->getIsNewAvatar() && (transitStatus == AvatarTransit::Status::START_TRANSIT ||
transitStatus == AvatarTransit::Status::ABORT_TRANSIT)) {
avatar->_transit.reset();
avatar->setIsNewAvatar(false);
}
avatar->simulate(deltaTime, inView);
if (avatar->getSkeletonModel()->isLoaded() && avatar->getWorkloadRegion() == workload::Region::R1) {
_myAvatar->addAvatarHandsToFlow(avatar);
}
avatar->updateRenderItem(renderTransaction);
avatar->updateSpaceProxy(workloadTransaction);
avatar->setLastRenderUpdateTime(startTime);
} else {
// we've spent our time budget for this priority bucket
// let's deal with the reminding avatars if this pass and BREAK from the for loop
if (p == kHero) {
// Hero,
// --> put them back in the non hero queue
auto& crowdQueue = avatarPriorityQueues[kNonHero];
while (it != sortedAvatarVector.end()) {
crowdQueue.push(SortableAvatar((*it).getAvatar()));
++it;
}
} else {
// Non Hero
// --> bail on the rest of the avatar updates
// --> more avatars may freeze until their priority trickles up
// --> some scale animations may glitch
// --> some avatar velocity measurements may be a little off
// no time to simulate, but we take the time to count how many were tragically missed
numAvatarsNotUpdated = sortedAvatarVector.end() - it;
}
// We had to cut short this pass, we must break out of the for loop here
break;
}
}
if (p == kHero) {
numHerosUpdated = numAvatarsUpdated;
}
}
@ -334,7 +383,8 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
_space->enqueueTransaction(workloadTransaction);
_numAvatarsUpdated = numAvatarsUpdated;
_numAvatarsNotUpdated = numAVatarsNotUpdated;
_numAvatarsNotUpdated = numAvatarsNotUpdated;
_numHeroAvatarsUpdated = numHerosUpdated;
simulateAvatarFades(deltaTime);
@ -716,7 +766,7 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic
}
}
if (rayAvatarResult._intersect && pickAgainstMesh) {
if (avatar && rayAvatarResult._intersect && pickAgainstMesh) {
glm::vec3 localRayOrigin = avatar->worldToJointPoint(ray.origin, rayAvatarResult._intersectWithJoint);
glm::vec3 localRayPoint = avatar->worldToJointPoint(ray.origin + rayAvatarResult._distance * rayDirection, rayAvatarResult._intersectWithJoint);

View file

@ -90,6 +90,8 @@ public:
int getNumAvatarsUpdated() const { return _numAvatarsUpdated; }
int getNumAvatarsNotUpdated() const { return _numAvatarsNotUpdated; }
int getNumHeroAvatars() const { return _numHeroAvatars; }
int getNumHeroAvatarsUpdated() const { return _numHeroAvatarsUpdated; }
float getAvatarSimulationTime() const { return _avatarSimulationTime; }
void updateMyAvatar(float deltaTime);
@ -242,6 +244,8 @@ private:
RateCounter<> _myAvatarSendRate;
int _numAvatarsUpdated { 0 };
int _numAvatarsNotUpdated { 0 };
int _numHeroAvatars{ 0 };
int _numHeroAvatarsUpdated{ 0 };
float _avatarSimulationTime { 0.0f };
bool _shouldRender { true };
bool _myAvatarDataPacketsPaused { false };

View file

@ -254,11 +254,15 @@ void AvatarProject::openInInventory() const {
DependencyManager::get<TabletScriptingInterface>()->getTablet("com.highfidelity.interface.tablet.system"));
tablet->loadQMLSource("hifi/commerce/wallet/Wallet.qml");
DependencyManager::get<HMDScriptingInterface>()->openTablet();
tablet->getTabletRoot()->forceActiveFocus();
auto name = getProjectName();
// I'm not a fan of this, but it's the only current option.
auto name = getProjectName();
QTimer::singleShot(TIME_TO_WAIT_FOR_INVENTORY_TO_OPEN_MS, [name, tablet]() {
tablet->sendToQml(QVariantMap({ { "method", "updatePurchases" }, { "filterText", name } }));
});
QQuickItem* root = tablet->getTabletRoot();
if (root) {
root->forceActiveFocus();
}
}

View file

@ -77,7 +77,10 @@ public:
return QDir::cleanPath(QDir(_projectPath).absoluteFilePath(_fst->getModelPath()));
}
Q_INVOKABLE bool getHasErrors() const { return _hasErrors; }
Q_INVOKABLE void setHasErrors(bool hasErrors) { _hasErrors = hasErrors; }
Q_INVOKABLE void setHasErrors(bool hasErrors) {
_hasErrors = hasErrors;
emit hasErrorsChanged();
}
/**
* returns the AvatarProject or a nullptr on failure.

View file

@ -32,6 +32,17 @@ void GrabManager::simulateGrabs() {
bool success;
SpatiallyNestablePointer grabbedThing = SpatiallyNestable::findByID(grabbedThingID, success);
if (success && grabbedThing) {
auto entity = std::dynamic_pointer_cast<EntityItem>(grabbedThing);
if (entity) {
if (entity->getLocked()) {
continue; // even if someone else claims to be grabbing it, don't move a locked thing
}
const GrabPropertyGroup& grabProps = entity->getGrabProperties();
if (!grabProps.getGrabbable()) {
continue; // even if someone else claims to be grabbing it, don't move non-grabbable
}
}
glm::vec3 finalPosition = acc.finalizePosition();
glm::quat finalOrientation = acc.finalizeOrientation();
grabbedThing->setTransform(createMatFromQuatAndPos(finalOrientation, finalPosition));

216
interface/src/avatar/MyAvatar.cpp Executable file → Normal file
View file

@ -910,7 +910,7 @@ void MyAvatar::simulate(float deltaTime, bool inView) {
recorder->recordFrame(FRAME_TYPE, toFrame(*this));
}
locationChanged();
locationChanged(true, false);
// if a entity-child of this avatar has moved outside of its queryAACube, update the cube and tell the entity server.
auto entityTreeRenderer = qApp->getEntities();
EntityTreePointer entityTree = entityTreeRenderer ? entityTreeRenderer->getTree() : nullptr;
@ -920,6 +920,7 @@ void MyAvatar::simulate(float deltaTime, bool inView) {
zoneInteractionProperties = entityTreeRenderer->getZoneInteractionProperties();
EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender();
forEachDescendant([&](SpatiallyNestablePointer object) {
locationChanged(true, false);
// we need to update attached queryAACubes in our own local tree so point-select always works
// however we don't want to flood the update pipeline with AvatarEntity updates, so we assume
// others have all info required to properly update queryAACube of AvatarEntities on their end
@ -932,7 +933,8 @@ void MyAvatar::simulate(float deltaTime, bool inView) {
bool isPhysicsEnabled = qApp->isPhysicsEnabled();
bool zoneAllowsFlying = zoneInteractionProperties.first;
bool collisionlessAllowed = zoneInteractionProperties.second;
_characterController.setFlyingAllowed((zoneAllowsFlying && _enableFlying) || !isPhysicsEnabled);
_characterController.setZoneFlyingAllowed(zoneAllowsFlying || !isPhysicsEnabled);
_characterController.setComfortFlyingAllowed(_enableFlying);
_characterController.setCollisionlessAllowed(collisionlessAllowed);
}
@ -2323,23 +2325,25 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
std::shared_ptr<QMetaObject::Connection> skeletonConnection = std::make_shared<QMetaObject::Connection>();
*skeletonConnection = QObject::connect(_skeletonModel.get(), &SkeletonModel::skeletonLoaded, [this, skeletonModelChangeCount, skeletonConnection]() {
if (skeletonModelChangeCount == _skeletonModelChangeCount) {
if (skeletonModelChangeCount == _skeletonModelChangeCount) {
if (_fullAvatarModelName.isEmpty()) {
// Store the FST file name into preferences
const auto& mapping = _skeletonModel->getGeometry()->getMapping();
if (mapping.value("name").isValid()) {
_fullAvatarModelName = mapping.value("name").toString();
}
}
if (_fullAvatarModelName.isEmpty()) {
// Store the FST file name into preferences
const auto& mapping = _skeletonModel->getGeometry()->getMapping();
if (mapping.value("name").isValid()) {
_fullAvatarModelName = mapping.value("name").toString();
}
}
initHeadBones();
_skeletonModel->setCauterizeBoneSet(_headBoneSet);
_fstAnimGraphOverrideUrl = _skeletonModel->getGeometry()->getAnimGraphOverrideUrl();
initAnimGraph();
_skeletonModelLoaded = true;
}
QObject::disconnect(*skeletonConnection);
initHeadBones();
_skeletonModel->setCauterizeBoneSet(_headBoneSet);
_fstAnimGraphOverrideUrl = _skeletonModel->getGeometry()->getAnimGraphOverrideUrl();
initAnimGraph();
initFlowFromFST();
_skeletonModelLoaded = true;
}
QObject::disconnect(*skeletonConnection);
});
saveAvatarUrl();
@ -2951,6 +2955,10 @@ void MyAvatar::initAnimGraph() {
graphUrl = _fstAnimGraphOverrideUrl;
} else {
graphUrl = PathUtils::resourcesUrl("avatar/avatar-animation.json");
#if defined(Q_OS_ANDROID) || defined(HIFI_USE_OPTIMIZED_IK)
graphUrl = PathUtils::resourcesUrl("avatar/avatar-animation_withSplineIKNode.json");
#endif
}
emit animGraphUrlChanged(graphUrl);
@ -5313,6 +5321,180 @@ void MyAvatar::releaseGrab(const QUuid& grabID) {
}
}
void MyAvatar::addAvatarHandsToFlow(const std::shared_ptr<Avatar>& otherAvatar) {
auto &flow = _skeletonModel->getRig().getFlow();
for (auto &handJointName : HAND_COLLISION_JOINTS) {
int jointIndex = otherAvatar->getJointIndex(handJointName);
if (jointIndex != -1) {
glm::vec3 position = otherAvatar->getJointPosition(jointIndex);
flow.setOthersCollision(otherAvatar->getID(), jointIndex, position);
}
}
}
void MyAvatar::useFlow(bool isActive, bool isCollidable, const QVariantMap& physicsConfig, const QVariantMap& collisionsConfig) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "useFlow",
Q_ARG(bool, isActive),
Q_ARG(bool, isCollidable),
Q_ARG(const QVariantMap&, physicsConfig),
Q_ARG(const QVariantMap&, collisionsConfig));
return;
}
if (_skeletonModel->isLoaded()) {
auto &flow = _skeletonModel->getRig().getFlow();
auto &collisionSystem = flow.getCollisionSystem();
if (!flow.isInitialized() && isActive) {
_skeletonModel->getRig().initFlow(true);
} else {
flow.setActive(isActive);
}
collisionSystem.setActive(isCollidable);
auto physicsGroups = physicsConfig.keys();
if (physicsGroups.size() > 0) {
for (auto &groupName : physicsGroups) {
auto settings = physicsConfig[groupName].toMap();
FlowPhysicsSettings physicsSettings;
if (settings.contains("active")) {
physicsSettings._active = settings["active"].toBool();
}
if (settings.contains("damping")) {
physicsSettings._damping = settings["damping"].toFloat();
}
if (settings.contains("delta")) {
physicsSettings._delta = settings["delta"].toFloat();
}
if (settings.contains("gravity")) {
physicsSettings._gravity = settings["gravity"].toFloat();
}
if (settings.contains("inertia")) {
physicsSettings._inertia = settings["inertia"].toFloat();
}
if (settings.contains("radius")) {
physicsSettings._radius = settings["radius"].toFloat();
}
if (settings.contains("stiffness")) {
physicsSettings._stiffness = settings["stiffness"].toFloat();
}
flow.setPhysicsSettingsForGroup(groupName, physicsSettings);
}
}
auto collisionJoints = collisionsConfig.keys();
if (collisionJoints.size() > 0) {
collisionSystem.resetCollisions();
for (auto &jointName : collisionJoints) {
int jointIndex = getJointIndex(jointName);
FlowCollisionSettings collisionsSettings;
auto settings = collisionsConfig[jointName].toMap();
collisionsSettings._entityID = getID();
if (settings.contains("radius")) {
collisionsSettings._radius = settings["radius"].toFloat();
}
if (settings.contains("offset")) {
collisionsSettings._offset = vec3FromVariant(settings["offset"]);
}
collisionSystem.addCollisionSphere(jointIndex, collisionsSettings);
}
}
}
}
QVariantMap MyAvatar::getFlowData() {
QVariantMap result;
if (QThread::currentThread() != thread()) {
BLOCKING_INVOKE_METHOD(this, "getFlowData",
Q_RETURN_ARG(QVariantMap, result));
return result;
}
if (_skeletonModel->isLoaded()) {
auto jointNames = getJointNames();
auto &flow = _skeletonModel->getRig().getFlow();
auto &collisionSystem = flow.getCollisionSystem();
bool initialized = flow.isInitialized();
result.insert("initialized", initialized);
result.insert("active", flow.getActive());
result.insert("colliding", collisionSystem.getActive());
QVariantMap physicsData;
QVariantMap collisionsData;
QVariantMap threadData;
std::map<QString, QVariantList> groupJointsMap;
QVariantList jointCollisionData;
auto &groups = flow.getGroupSettings();
for (auto &joint : flow.getJoints()) {
auto &groupName = joint.second.getGroup();
if (groups.find(groupName) != groups.end()) {
if (groupJointsMap.find(groupName) == groupJointsMap.end()) {
groupJointsMap.insert(std::pair<QString, QVariantList>(groupName, QVariantList()));
}
groupJointsMap[groupName].push_back(joint.second.getIndex());
}
}
for (auto &group : groups) {
QVariantMap settingsObject;
QString groupName = group.first;
FlowPhysicsSettings groupSettings = group.second;
settingsObject.insert("active", groupSettings._active);
settingsObject.insert("damping", groupSettings._damping);
settingsObject.insert("delta", groupSettings._delta);
settingsObject.insert("gravity", groupSettings._gravity);
settingsObject.insert("inertia", groupSettings._inertia);
settingsObject.insert("radius", groupSettings._radius);
settingsObject.insert("stiffness", groupSettings._stiffness);
settingsObject.insert("jointIndices", groupJointsMap[groupName]);
physicsData.insert(groupName, settingsObject);
}
auto &collisions = collisionSystem.getCollisions();
for (auto &collision : collisions) {
QVariantMap collisionObject;
collisionObject.insert("offset", vec3toVariant(collision._offset));
collisionObject.insert("radius", collision._radius);
collisionObject.insert("jointIndex", collision._jointIndex);
QString jointName = jointNames.size() > collision._jointIndex ? jointNames[collision._jointIndex] : "unknown";
collisionsData.insert(jointName, collisionObject);
}
for (auto &thread : flow.getThreads()) {
QVariantList indices;
for (int index : thread._joints) {
indices.append(index);
}
threadData.insert(thread._jointsPointer->at(thread._joints[0]).getName(), indices);
}
result.insert("physics", physicsData);
result.insert("collisions", collisionsData);
result.insert("threads", threadData);
}
return result;
}
QVariantList MyAvatar::getCollidingFlowJoints() {
QVariantList result;
if (QThread::currentThread() != thread()) {
BLOCKING_INVOKE_METHOD(this, "getCollidingFlowJoints",
Q_RETURN_ARG(QVariantList, result));
return result;
}
if (_skeletonModel->isLoaded()) {
auto& flow = _skeletonModel->getRig().getFlow();
for (auto &joint : flow.getJoints()) {
if (joint.second.isColliding()) {
result.append(joint.second.getIndex());
}
}
}
return result;
}
void MyAvatar::initFlowFromFST() {
if (_skeletonModel->isLoaded()) {
auto &flowData = _skeletonModel->getHFMModel().flowData;
if (flowData.shouldInitFlow()) {
useFlow(true, flowData.shouldInitCollisions(), flowData._physicsConfig, flowData._collisionsConfig);
}
}
}
void MyAvatar::sendPacket(const QUuid& entityID, const EntityItemProperties& properties) const {
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;

View file

@ -1183,6 +1183,33 @@ public:
void updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData) override;
void avatarEntityDataToJson(QJsonObject& root) const override;
int sendAvatarDataPacket(bool sendAll = false) override;
void addAvatarHandsToFlow(const std::shared_ptr<Avatar>& otherAvatar);
/**jsdoc
* Init flow simulation on avatar.
* @function MyAvatar.useFlow
* @param {boolean} - Set to <code>true</code> to activate flow simulation.
* @param {boolean} - Set to <code>true</code> to activate collisions.
* @param {Object} physicsConfig - object with the customized physic parameters
* i.e. {"hair": {"active": true, "stiffness": 0.0, "radius": 0.04, "gravity": -0.035, "damping": 0.8, "inertia": 0.8, "delta": 0.35}}
* @param {Object} collisionsConfig - object with the customized collision parameters
* i.e. {"Spine2": {"type": "sphere", "radius": 0.14, "offset": {"x": 0.0, "y": 0.2, "z": 0.0}}}
*/
Q_INVOKABLE void useFlow(bool isActive, bool isCollidable, const QVariantMap& physicsConfig = QVariantMap(), const QVariantMap& collisionsConfig = QVariantMap());
/**jsdoc
* @function MyAvatar.getFlowData
* @returns {object}
*/
Q_INVOKABLE QVariantMap getFlowData();
/**jsdoc
* returns the indices of every colliding flow joint
* @function MyAvatar.getCollidingFlowJoints
* @returns {int[]}
*/
Q_INVOKABLE QVariantList getCollidingFlowJoints();
public slots:
@ -1737,6 +1764,7 @@ private:
void updateCollisionSound(const glm::vec3& penetration, float deltaTime, float frequency);
void initHeadBones();
void initAnimGraph();
void initFlowFromFST();
// Avatar Preferences
QUrl _fullAvatarURLFromPreferences;

View file

@ -10,12 +10,14 @@
#include <avatars-renderer/Avatar.h>
#include <DebugDraw.h>
#include <CubicHermiteSpline.h>
#include "Application.h"
#include "InterfaceLogging.h"
#include "AnimUtil.h"
MySkeletonModel::MySkeletonModel(Avatar* owningAvatar, QObject* parent) : SkeletonModel(owningAvatar, parent) {
}
@ -33,6 +35,22 @@ Rig::CharacterControllerState convertCharacterControllerState(CharacterControlle
};
}
#if defined(Q_OS_ANDROID) || defined(HIFI_USE_OPTIMIZED_IK)
static glm::vec3 computeSpine2WithHeadHipsSpline(MyAvatar* myAvatar, AnimPose hipsIKTargetPose, AnimPose headIKTargetPose) {
// the the ik targets to compute the spline with
CubicHermiteSplineFunctorWithArcLength splineFinal(headIKTargetPose.rot(), headIKTargetPose.trans(), hipsIKTargetPose.rot(), hipsIKTargetPose.trans());
// measure the total arc length along the spline
float totalArcLength = splineFinal.arcLength(1.0f);
float tFinal = splineFinal.arcLengthInverse(myAvatar->getSpine2SplineRatio() * totalArcLength);
glm::vec3 spine2Translation = splineFinal(tFinal);
return spine2Translation + myAvatar->getSpine2SplineOffset();
}
#endif
static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) {
glm::mat4 worldToSensorMat = glm::inverse(myAvatar->getSensorToWorldMatrix());
@ -233,6 +251,12 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
myAvatar->getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND).isValid() &&
!(params.primaryControllerFlags[Rig::PrimaryControllerType_Spine2] & (uint8_t)Rig::ControllerFlags::Enabled)) {
#if defined(Q_OS_ANDROID) || defined(HIFI_USE_OPTIMIZED_IK)
AnimPose headAvatarSpace(avatarHeadPose.getRotation(), avatarHeadPose.getTranslation());
AnimPose headRigSpace = avatarToRigPose * headAvatarSpace;
AnimPose hipsRigSpace = sensorToRigPose * sensorHips;
glm::vec3 spine2TargetTranslation = computeSpine2WithHeadHipsSpline(myAvatar, hipsRigSpace, headRigSpace);
#endif
const float SPINE2_ROTATION_FILTER = 0.5f;
AnimPose currentSpine2Pose;
AnimPose currentHeadPose;
@ -243,6 +267,9 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
if (spine2Exists && headExists && hipsExists) {
AnimPose rigSpaceYaw(myAvatar->getSpine2RotationRigSpace());
#if defined(Q_OS_ANDROID) || defined(HIFI_USE_OPTIMIZED_IK)
rigSpaceYaw.rot() = safeLerp(Quaternions::IDENTITY, rigSpaceYaw.rot(), 0.5f);
#endif
glm::vec3 u, v, w;
glm::vec3 fwd = rigSpaceYaw.rot() * glm::vec3(0.0f, 0.0f, 1.0f);
glm::vec3 up = currentHeadPose.trans() - currentHipsPose.trans();
@ -253,6 +280,9 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
}
generateBasisVectors(up, fwd, u, v, w);
AnimPose newSpinePose(glm::mat4(glm::vec4(w, 0.0f), glm::vec4(u, 0.0f), glm::vec4(v, 0.0f), glm::vec4(glm::vec3(0.0f, 0.0f, 0.0f), 1.0f)));
#if defined(Q_OS_ANDROID) || defined(HIFI_USE_OPTIMIZED_IK)
currentSpine2Pose.trans() = spine2TargetTranslation;
#endif
currentSpine2Pose.rot() = safeLerp(currentSpine2Pose.rot(), newSpinePose.rot(), SPINE2_ROTATION_FILTER);
params.primaryControllerPoses[Rig::PrimaryControllerType_Spine2] = currentSpine2Pose;
params.primaryControllerFlags[Rig::PrimaryControllerType_Spine2] = (uint8_t)Rig::ControllerFlags::Enabled | (uint8_t)Rig::ControllerFlags::Estimated;

View file

@ -200,17 +200,6 @@ void OtherAvatar::resetDetailedMotionStates() {
void OtherAvatar::setWorkloadRegion(uint8_t region) {
_workloadRegion = region;
QString printRegion = "";
if (region == workload::Region::R1) {
printRegion = "R1";
} else if (region == workload::Region::R2) {
printRegion = "R2";
} else if (region == workload::Region::R3) {
printRegion = "R3";
} else {
printRegion = "invalid";
}
qCDebug(avatars) << "Setting workload region to " << printRegion;
computeShapeLOD();
}
@ -235,7 +224,6 @@ void OtherAvatar::computeShapeLOD() {
if (newLOD != _bodyLOD) {
_bodyLOD = newLOD;
if (isInPhysicsSimulation()) {
qCDebug(avatars) << "Changing to body LOD " << newLOD;
_needsReinsertion = true;
}
}
@ -368,7 +356,6 @@ void OtherAvatar::simulate(float deltaTime, bool inView) {
PROFILE_RANGE(simulation, "grabs");
applyGrabChanges();
}
updateFadingStatus();
}
@ -504,6 +491,7 @@ void OtherAvatar::handleChangedAvatarEntityData() {
// then set the the original ID for the changes to take effect
// TODO: This is a horrible hack and once properties.constructFromBuffer no longer causes
// side effects...remove the following three lines
const QUuid NULL_ID = QUuid("{00000000-0000-0000-0000-000000000005}");
entity->setParentID(NULL_ID);
entity->setParentID(oldParentID);

View file

@ -48,6 +48,7 @@ public:
void rebuildCollisionShape() override;
void setWorkloadRegion(uint8_t region);
uint8_t getWorkloadRegion() { return _workloadRegion; }
bool shouldBeInPhysicsSimulation() const;
bool needsPhysicsUpdate() const;

View file

@ -62,8 +62,8 @@ public:
* <tr><th>Value</th><th>Meaning</th><th>Description</th></tr>
* </thead>
* <tbody>
* <tr><td><code>0</code></td><td>Not logged in</td><td>The user isn't logged in.</td></tr>
* <tr><td><code>1</code></td><td>Not set up</td><td>The user's wallet isn't set up.</td></tr>
* <tr><td><code>0</code></td><td>Not logged in</td><td>The user is not logged in.</td></tr>
* <tr><td><code>1</code></td><td>Not set up</td><td>The user's wallet has not been set up.</td></tr>
* <tr><td><code>2</code></td><td>Pre-existing</td><td>There is a wallet present on the server but not one
* locally.</td></tr>
* <tr><td><code>3</code></td><td>Conflicting</td><td>There is a wallet present on the server plus one present locally,
@ -73,8 +73,8 @@ public:
* <tr><td><code>5</code></td><td>Ready</td><td>The wallet is ready for use.</td></tr>
* </tbody>
* </table>
* <p>Wallets used to be stored locally but now they're stored on the server, unless the computer once had a wallet stored
* locally in which case the wallet may be present in both places.</p>
* <p>Wallets used to be stored locally but now they're only stored on the server. A wallet is present in both places if
* your computer previously stored its information locally.</p>
* @typedef {number} WalletScriptingInterface.WalletStatus
*/
enum WalletStatus {

View file

@ -72,7 +72,7 @@ void SafeLanding::addTrackedEntity(const EntityItemID& entityID) {
Locker lock(_lock);
EntityItemPointer entity = _entityTree->findEntityByID(entityID);
if (entity && entity->getCreated() < _startTime) {
if (entity && !entity->isLocalEntity() && entity->getCreated() < _startTime) {
_trackedEntities.emplace(entityID, entity);
int trackedEntityCount = (int)_trackedEntities.size();
@ -81,7 +81,7 @@ void SafeLanding::addTrackedEntity(const EntityItemID& entityID) {
_maxTrackedEntityCount = trackedEntityCount;
_trackedEntityStabilityCount = 0;
}
qCDebug(interfaceapp) << "Safe Landing: Tracking entity " << entity->getItemName();
//qCDebug(interfaceapp) << "Safe Landing: Tracking entity " << entity->getItemName();
}
}
}

View file

@ -172,7 +172,10 @@ void LaserPointer::RenderState::update(const glm::vec3& origin, const glm::vec3&
properties.setVisible(true);
properties.setIgnorePickIntersection(doesPathIgnorePicks());
QVector<float> widths;
widths.append(getLineWidth() * parentScale);
float width = getLineWidth() * parentScale;
widths.append(width);
widths.append(width);
properties.setStrokeWidths(widths);
DependencyManager::get<EntityScriptingInterface>()->editEntity(getPathID(), properties);
}
}

View file

@ -89,6 +89,8 @@ glm::vec3 RayPick::intersectRayWithEntityXYPlane(const QUuid& entityID, const gl
return intersectRayWithXYPlane(origin, direction, props.getPosition(), props.getRotation(), props.getRegistrationPoint());
}
glm::vec2 RayPick::projectOntoXYPlane(const glm::vec3& worldPos, const glm::vec3& position, const glm::quat& rotation, const glm::vec3& dimensions, const glm::vec3& registrationPoint, bool unNormalized) {
glm::quat invRot = glm::inverse(rotation);
glm::vec3 localPos = invRot * (worldPos - position);
@ -102,6 +104,19 @@ glm::vec2 RayPick::projectOntoXYPlane(const glm::vec3& worldPos, const glm::vec3
return pos2D;
}
glm::vec2 RayPick::projectOntoXZPlane(const glm::vec3& worldPos, const glm::vec3& position, const glm::quat& rotation, const glm::vec3& dimensions, const glm::vec3& registrationPoint, bool unNormalized) {
glm::quat invRot = glm::inverse(rotation);
glm::vec3 localPos = invRot * (worldPos - position);
glm::vec3 normalizedPos = (localPos / dimensions) + registrationPoint;
glm::vec2 pos2D = glm::vec2(normalizedPos.x, (1.0f - normalizedPos.z));
if (unNormalized) {
pos2D *= glm::vec2(dimensions.x, dimensions.z);
}
return pos2D;
}
glm::vec2 RayPick::projectOntoEntityXYPlane(const QUuid& entityID, const glm::vec3& worldPos, bool unNormalized) {
EntityPropertyFlags desiredProperties;
desiredProperties += PROP_POSITION;

View file

@ -86,6 +86,7 @@ public:
static glm::vec2 projectOntoEntityXYPlane(const QUuid& entityID, const glm::vec3& worldPos, bool unNormalized = true);
static glm::vec2 projectOntoXYPlane(const glm::vec3& worldPos, const glm::vec3& position, const glm::quat& rotation, const glm::vec3& dimensions, const glm::vec3& registrationPoint, bool unNormalized);
static glm::vec2 projectOntoXZPlane(const glm::vec3& worldPos, const glm::vec3& position, const glm::quat& rotation, const glm::vec3& dimensions, const glm::vec3& registrationPoint, bool unNoemalized);
private:
static glm::vec3 intersectRayWithXYPlane(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& point, const glm::quat& rotation, const glm::vec3& registration);

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