Merge branch 'master' of https://github.com/highfidelity/hifi into blackProp

This commit is contained in:
Sam Gateau 2019-04-03 16:56:17 -07:00
commit 3ddde35d8f
367 changed files with 13769 additions and 5316 deletions
.gitignoreBUILD_LINUX.md
android
apps/questFramePlayer/src/main/cpp
libraries/oculus/src/main
assets/shaders
java/io/highfidelity/oculus
assignment-client/src
cmake/macros
domain-server
hifi_android.py
interface

3
.gitignore vendored
View file

@ -26,6 +26,9 @@ android/**/src/main/assets
android/**/gradle*
*.class
# Visual Studio
/.vs
# VSCode
# List taken from Github Global Ignores master@435c4d92
# https://github.com/github/gitignore/commits/master/Global/VisualStudioCode.gitignore

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

@ -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

@ -0,0 +1,39 @@
#version 320 es
precision highp float;
precision highp sampler2D;
layout(location = 0) in vec4 vTexCoordLR;
layout(location = 0) out vec4 FragColorL;
layout(location = 1) out vec4 FragColorR;
uniform sampler2D sampler;
// https://software.intel.com/en-us/node/503873
// sRGB ====> Linear
vec3 color_sRGBToLinear(vec3 srgb) {
return mix(pow((srgb + vec3(0.055)) / vec3(1.055), vec3(2.4)), srgb / vec3(12.92), vec3(lessThanEqual(srgb, vec3(0.04045))));
}
vec4 color_sRGBAToLinear(vec4 srgba) {
return vec4(color_sRGBToLinear(srgba.xyz), srgba.w);
}
// Linear ====> sRGB
vec3 color_LinearTosRGB(vec3 lrgb) {
return mix(vec3(1.055) * pow(vec3(lrgb), vec3(0.41666)) - vec3(0.055), vec3(lrgb) * vec3(12.92), vec3(lessThan(lrgb, vec3(0.0031308))));
}
vec4 color_LinearTosRGBA(vec4 lrgba) {
return vec4(color_LinearTosRGB(lrgba.xyz), lrgba.w);
}
// FIXME switch to texelfetch for getting from the source texture?
void main() {
//FragColorL = color_LinearTosRGBA(texture(sampler, vTexCoordLR.xy));
//FragColorR = color_LinearTosRGBA(texture(sampler, vTexCoordLR.zw));
FragColorL = texture(sampler, vTexCoordLR.xy);
FragColorR = texture(sampler, vTexCoordLR.zw);
}

View file

@ -0,0 +1,21 @@
#version 320 es
layout(location = 0) out vec4 vTexCoordLR;
void main(void) {
const float depth = 0.0;
const vec4 UNIT_QUAD[4] = vec4[4](
vec4(-1.0, -1.0, depth, 1.0),
vec4(1.0, -1.0, depth, 1.0),
vec4(-1.0, 1.0, depth, 1.0),
vec4(1.0, 1.0, depth, 1.0)
);
vec4 pos = UNIT_QUAD[gl_VertexID];
gl_Position = pos;
vTexCoordLR.xy = pos.xy;
vTexCoordLR.xy += 1.0;
vTexCoordLR.y *= 0.5;
vTexCoordLR.x *= 0.25;
vTexCoordLR.zw = vTexCoordLR.xy;
vTexCoordLR.z += 0.5;
}

View file

@ -7,6 +7,7 @@
//
package io.highfidelity.oculus;
import android.content.res.AssetManager;
import android.os.Bundle;
import android.util.Log;
import android.view.Surface;
@ -24,7 +25,7 @@ public class OculusMobileActivity extends QtActivity implements SurfaceHolder.Ca
private static final String TAG = OculusMobileActivity.class.getSimpleName();
static { System.loadLibrary("oculusMobile"); }
private native void nativeOnCreate();
private native void nativeOnCreate(AssetManager assetManager);
private native static void nativeOnResume();
private native static void nativeOnPause();
private native static void nativeOnSurfaceChanged(Surface s);
@ -53,7 +54,7 @@ public class OculusMobileActivity extends QtActivity implements SurfaceHolder.Ca
mView = new SurfaceView(this);
mView.getHolder().addCallback(this);
nativeOnCreate();
nativeOnCreate(getAssets());
questNativeOnCreate();
}
@ -81,7 +82,7 @@ public class OculusMobileActivity extends QtActivity implements SurfaceHolder.Ca
Log.w(TAG, "QQQ onResume");
super.onResume();
//Reconnect the global reference back to handler
nativeOnCreate();
nativeOnCreate(getAssets());
questNativeOnResume();
nativeOnResume();

View file

@ -52,6 +52,8 @@
#include <WebSocketServerClass.h>
#include <EntityScriptingInterface.h> // TODO: consider moving to scriptengine.h
#include <hfm/ModelFormatRegistry.h>
#include "entities/AssignmentParentFinder.h"
#include "AssignmentDynamicFactory.h"
#include "RecordingScriptingInterface.h"
@ -99,6 +101,9 @@ Agent::Agent(ReceivedMessage& message) :
DependencyManager::set<RecordingScriptingInterface>();
DependencyManager::set<UsersScriptingInterface>();
DependencyManager::set<ModelFormatRegistry>();
DependencyManager::set<ModelCache>();
// Needed to ensure the creation of the DebugDraw instance on the main thread
DebugDraw::getInstance();
@ -819,6 +824,9 @@ void Agent::aboutToFinish() {
DependencyManager::get<ResourceManager>()->cleanup();
DependencyManager::destroy<ModelFormatRegistry>();
DependencyManager::destroy<ModelCache>();
DependencyManager::destroy<PluginManager>();
// cleanup the AudioInjectorManager (and any still running injectors)

View file

@ -18,16 +18,25 @@
#include "Agent.h"
/**jsdoc
* The <code>Agent</code> API enables an assignment client to emulate an avatar. Setting <code>isAvatar = true</code> connects
* the assignment client to the avatar and audio mixers, and enables the {@link Avatar} API to be used.
*
* @namespace Agent
*
* @hifi-assignment-client
*
* @property {boolean} isAvatar
* @property {boolean} isPlayingAvatarSound <em>Read-only.</em>
* @property {boolean} isListeningToAudioStream
* @property {boolean} isNoiseGateEnabled
* @property {number} lastReceivedAudioLoudness <em>Read-only.</em>
* @property {Uuid} sessionUUID <em>Read-only.</em>
* @property {boolean} isAvatar - <code>true</code> if the assignment client script is emulating an avatar, otherwise
* <code>false</code>.
* @property {boolean} isPlayingAvatarSound - <code>true</code> if the script has a sound to play, otherwise <code>false</code>.
* Sounds are played when <code>isAvatar</code> is <code>true</code>, from the position and with the orientation of the
* scripted avatar's head. <em>Read-only.</em>
* @property {boolean} isListeningToAudioStream - <code>true</code> if the agent is "listening" to the audio stream from the
* domain, otherwise <code>false</code>.
* @property {boolean} isNoiseGateEnabled - <code>true</code> if the noise gate is enabled, otherwise <code>false</code>. When
* enabled, the input audio stream is blocked (fully attenuated) if it falls below an adaptive threshold.
* @property {number} lastReceivedAudioLoudness - The current loudness of the audio input. Nominal range [<code>0.0</code> (no
* sound) &ndash; <code>1.0</code> (the onset of clipping)]. <em>Read-only.</em>
* @property {Uuid} sessionUUID - The unique ID associated with the agent's current session in the domain. <em>Read-only.</em>
*/
class AgentScriptingInterface : public QObject {
Q_OBJECT
@ -54,20 +63,43 @@ public:
public slots:
/**jsdoc
* Sets whether the script should emulate an avatar.
* @function Agent.setIsAvatar
* @param {boolean} isAvatar
* @param {boolean} isAvatar - <code>true</code> if the script emulates an avatar, otherwise <code>false</code>.
* @example <caption>Make an assignment client script emulate an avatar.</caption>
* (function () {
* Agent.setIsAvatar(true);
* Avatar.displayName = "AC avatar";
* print("Position: " + JSON.stringify(Avatar.position)); // 0, 0, 0
* }());
*/
void setIsAvatar(bool isAvatar) const { _agent->setIsAvatar(isAvatar); }
/**jsdoc
* Checks whether the script is emulating an avatar.
* @function Agent.isAvatar
* @returns {boolean}
* @returns {boolean} <code>true</code> if the script is emulating an avatar, otherwise <code>false</code>.
* @example <caption>Check whether the agent is emulating an avatar.</caption>
* (function () {
* print("Agent is avatar: " + Agent.isAvatar());
* print("Agent is avatar: " + Agent.isAvatar); // Same result.
* }());
*/
bool isAvatar() const { return _agent->isAvatar(); }
/**jsdoc
* Plays a sound from the position and with the orientation of the emulated avatar's head. No sound is played unless
* <code>isAvatar == true</code>.
* @function Agent.playAvatarSound
* @param {object} avatarSound
* @param {SoundObject} avatarSound - The sound played.
* @example <caption>Play a sound from an emulated avatar.</caption>
* (function () {
* Agent.isAvatar = true;
* var sound = SoundCache.getSound(Script.resourcesPath() + "sounds/sample.wav");
* Script.setTimeout(function () { // Give the sound time to load.
* Agent.playAvatarSound(sound);
* }, 1000);
* }());
*/
void playAvatarSound(SharedSoundPointer avatarSound) const { _agent->playAvatarSound(avatarSound); }

View file

@ -97,6 +97,7 @@ AudioMixer::AudioMixer(ReceivedMessage& message) :
PacketType::RadiusIgnoreRequest,
PacketType::RequestsDomainListData,
PacketType::PerAvatarGainSet,
PacketType::InjectorGainSet,
PacketType::AudioSoloRequest },
this, "queueAudioPacket");

View file

@ -92,6 +92,9 @@ int AudioMixerClientData::processPackets(ConcurrentAddedStreams& addedStreams) {
case PacketType::PerAvatarGainSet:
parsePerAvatarGainSet(*packet, node);
break;
case PacketType::InjectorGainSet:
parseInjectorGainSet(*packet, node);
break;
case PacketType::NodeIgnoreRequest:
parseNodeIgnoreRequest(packet, node);
break;
@ -197,14 +200,25 @@ void AudioMixerClientData::parsePerAvatarGainSet(ReceivedMessage& message, const
if (avatarUUID.isNull()) {
// set the MASTER avatar gain
setMasterAvatarGain(gain);
qCDebug(audio) << "Setting MASTER avatar gain for " << uuid << " to " << gain;
qCDebug(audio) << "Setting MASTER avatar gain for" << uuid << "to" << gain;
} else {
// set the per-source avatar gain
setGainForAvatar(avatarUUID, gain);
qCDebug(audio) << "Setting avatar gain adjustment for hrtf[" << uuid << "][" << avatarUUID << "] to " << gain;
qCDebug(audio) << "Setting avatar gain adjustment for hrtf[" << uuid << "][" << avatarUUID << "] to" << gain;
}
}
void AudioMixerClientData::parseInjectorGainSet(ReceivedMessage& message, const SharedNodePointer& node) {
QUuid uuid = node->getUUID();
uint8_t packedGain;
message.readPrimitive(&packedGain);
float gain = unpackFloatGainFromByte(packedGain);
setMasterInjectorGain(gain);
qCDebug(audio) << "Setting MASTER injector gain for" << uuid << "to" << gain;
}
void AudioMixerClientData::setGainForAvatar(QUuid nodeID, float gain) {
auto it = std::find_if(_streams.active.cbegin(), _streams.active.cend(), [nodeID](const MixableStream& mixableStream){
return mixableStream.nodeStreamID.nodeID == nodeID && mixableStream.nodeStreamID.streamID.isNull();

View file

@ -63,6 +63,7 @@ public:
void negotiateAudioFormat(ReceivedMessage& message, const SharedNodePointer& node);
void parseRequestsDomainListData(ReceivedMessage& message);
void parsePerAvatarGainSet(ReceivedMessage& message, const SharedNodePointer& node);
void parseInjectorGainSet(ReceivedMessage& message, const SharedNodePointer& node);
void parseNodeIgnoreRequest(QSharedPointer<ReceivedMessage> message, const SharedNodePointer& node);
void parseRadiusIgnoreRequest(QSharedPointer<ReceivedMessage> message, const SharedNodePointer& node);
void parseSoloRequest(QSharedPointer<ReceivedMessage> message, const SharedNodePointer& node);
@ -84,6 +85,8 @@ public:
float getMasterAvatarGain() const { return _masterAvatarGain; }
void setMasterAvatarGain(float gain) { _masterAvatarGain = gain; }
float getMasterInjectorGain() const { return _masterInjectorGain; }
void setMasterInjectorGain(float gain) { _masterInjectorGain = gain; }
AudioLimiter audioLimiter;
@ -189,6 +192,7 @@ private:
int _frameToSendStats { 0 };
float _masterAvatarGain { 1.0f }; // per-listener mixing gain, applied only to avatars
float _masterInjectorGain { 1.0f }; // per-listener mixing gain, applied only to injectors
CodecPluginPointer _codec;
QString _selectedCodecName;

View file

@ -50,8 +50,8 @@ void sendEnvironmentPacket(const SharedNodePointer& node, AudioMixerClientData&
// mix helpers
inline float approximateGain(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd);
inline float computeGain(float masterListenerGain, const AvatarAudioStream& listeningNodeStream,
const PositionalAudioStream& streamToAdd, const glm::vec3& relativePosition, float distance, bool isEcho);
inline float computeGain(float masterAvatarGain, float masterInjectorGain, const AvatarAudioStream& listeningNodeStream,
const PositionalAudioStream& streamToAdd, const glm::vec3& relativePosition, float distance);
inline float computeAzimuth(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd,
const glm::vec3& relativePosition);
@ -338,8 +338,8 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) {
}
if (!isThrottling) {
updateHRTFParameters(stream, *listenerAudioStream,
listenerData->getMasterAvatarGain());
updateHRTFParameters(stream, *listenerAudioStream, listenerData->getMasterAvatarGain(),
listenerData->getMasterInjectorGain());
}
return false;
});
@ -363,8 +363,8 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) {
}
if (!isThrottling) {
updateHRTFParameters(stream, *listenerAudioStream,
listenerData->getMasterAvatarGain());
updateHRTFParameters(stream, *listenerAudioStream, listenerData->getMasterAvatarGain(),
listenerData->getMasterInjectorGain());
}
return false;
});
@ -381,13 +381,13 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) {
stream.approximateVolume = approximateVolume(stream, listenerAudioStream);
} else {
if (shouldBeSkipped(stream, *listener, *listenerAudioStream, *listenerData)) {
addStream(stream, *listenerAudioStream, 0.0f, isSoloing);
addStream(stream, *listenerAudioStream, 0.0f, 0.0f, isSoloing);
streams.skipped.push_back(move(stream));
++stats.activeToSkipped;
return true;
}
addStream(stream, *listenerAudioStream, listenerData->getMasterAvatarGain(),
addStream(stream, *listenerAudioStream, listenerData->getMasterAvatarGain(), listenerData->getMasterInjectorGain(),
isSoloing);
if (shouldBeInactive(stream)) {
@ -423,7 +423,7 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) {
return true;
}
addStream(stream, *listenerAudioStream, listenerData->getMasterAvatarGain(),
addStream(stream, *listenerAudioStream, listenerData->getMasterAvatarGain(), listenerData->getMasterInjectorGain(),
isSoloing);
if (shouldBeInactive(stream)) {
@ -491,7 +491,9 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) {
void AudioMixerSlave::addStream(AudioMixerClientData::MixableStream& mixableStream,
AvatarAudioStream& listeningNodeStream,
float masterListenerGain, bool isSoloing) {
float masterAvatarGain,
float masterInjectorGain,
bool isSoloing) {
++stats.totalMixes;
auto streamToAdd = mixableStream.positionalStream;
@ -502,13 +504,12 @@ void AudioMixerSlave::addStream(AudioMixerClientData::MixableStream& mixableStre
glm::vec3 relativePosition = streamToAdd->getPosition() - listeningNodeStream.getPosition();
float distance = glm::max(glm::length(relativePosition), EPSILON);
float gain = isEcho ? 1.0f
: (isSoloing ? masterAvatarGain
: computeGain(masterAvatarGain, masterInjectorGain, listeningNodeStream, *streamToAdd,
relativePosition, distance));
float azimuth = isEcho ? 0.0f : computeAzimuth(listeningNodeStream, listeningNodeStream, relativePosition);
float gain = masterListenerGain;
if (!isSoloing) {
gain = computeGain(masterListenerGain, listeningNodeStream, *streamToAdd, relativePosition, distance, isEcho);
}
const int HRTF_DATASET_INDEX = 1;
if (!streamToAdd->lastPopSucceeded()) {
@ -585,8 +586,9 @@ void AudioMixerSlave::addStream(AudioMixerClientData::MixableStream& mixableStre
}
void AudioMixerSlave::updateHRTFParameters(AudioMixerClientData::MixableStream& mixableStream,
AvatarAudioStream& listeningNodeStream,
float masterListenerGain) {
AvatarAudioStream& listeningNodeStream,
float masterAvatarGain,
float masterInjectorGain) {
auto streamToAdd = mixableStream.positionalStream;
// check if this is a server echo of a source back to itself
@ -595,7 +597,8 @@ void AudioMixerSlave::updateHRTFParameters(AudioMixerClientData::MixableStream&
glm::vec3 relativePosition = streamToAdd->getPosition() - listeningNodeStream.getPosition();
float distance = glm::max(glm::length(relativePosition), EPSILON);
float gain = computeGain(masterListenerGain, listeningNodeStream, *streamToAdd, relativePosition, distance, isEcho);
float gain = isEcho ? 1.0f : computeGain(masterAvatarGain, masterInjectorGain, listeningNodeStream, *streamToAdd,
relativePosition, distance);
float azimuth = isEcho ? 0.0f : computeAzimuth(listeningNodeStream, listeningNodeStream, relativePosition);
mixableStream.hrtf->setParameterHistory(azimuth, distance, gain);
@ -720,6 +723,7 @@ float approximateGain(const AvatarAudioStream& listeningNodeStream, const Positi
// injector: apply attenuation
if (streamToAdd.getType() == PositionalAudioStream::Injector) {
gain *= reinterpret_cast<const InjectedAudioStream*>(&streamToAdd)->getAttenuationRatio();
// injector: skip master gain
}
// avatar: skip attenuation - it is too costly to approximate
@ -729,19 +733,25 @@ float approximateGain(const AvatarAudioStream& listeningNodeStream, const Positi
float distance = glm::length(relativePosition);
return gain / distance;
// avatar: skip master gain - it is constant for all streams
// avatar: skip master gain
}
float computeGain(float masterListenerGain, const AvatarAudioStream& listeningNodeStream,
const PositionalAudioStream& streamToAdd, const glm::vec3& relativePosition, float distance, bool isEcho) {
float computeGain(float masterAvatarGain,
float masterInjectorGain,
const AvatarAudioStream& listeningNodeStream,
const PositionalAudioStream& streamToAdd,
const glm::vec3& relativePosition,
float distance) {
float gain = 1.0f;
// injector: apply attenuation
if (streamToAdd.getType() == PositionalAudioStream::Injector) {
gain *= reinterpret_cast<const InjectedAudioStream*>(&streamToAdd)->getAttenuationRatio();
// apply master gain
gain *= masterInjectorGain;
// avatar: apply fixed off-axis attenuation to make them quieter as they turn away
} else if (!isEcho && (streamToAdd.getType() == PositionalAudioStream::Microphone)) {
} else if (streamToAdd.getType() == PositionalAudioStream::Microphone) {
glm::vec3 rotatedListenerPosition = glm::inverse(streamToAdd.getOrientation()) * relativePosition;
// source directivity is based on angle of emission, in local coordinates
@ -754,8 +764,8 @@ float computeGain(float masterListenerGain, const AvatarAudioStream& listeningNo
gain *= offAxisCoefficient;
// apply master gain, only to avatars
gain *= masterListenerGain;
// apply master gain
gain *= masterAvatarGain;
}
auto& audioZones = AudioMixer::getAudioZones();
@ -797,8 +807,9 @@ float computeGain(float masterListenerGain, const AvatarAudioStream& listeningNo
return gain;
}
float computeAzimuth(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd,
const glm::vec3& relativePosition) {
float computeAzimuth(const AvatarAudioStream& listeningNodeStream,
const PositionalAudioStream& streamToAdd,
const glm::vec3& relativePosition) {
glm::quat inverseOrientation = glm::inverse(listeningNodeStream.getOrientation());
glm::vec3 rotatedSourcePosition = inverseOrientation * relativePosition;

View file

@ -57,10 +57,13 @@ private:
bool prepareMix(const SharedNodePointer& listener);
void addStream(AudioMixerClientData::MixableStream& mixableStream,
AvatarAudioStream& listeningNodeStream,
float masterListenerGain, bool isSoloing);
float masterAvatarGain,
float masterInjectorGain,
bool isSoloing);
void updateHRTFParameters(AudioMixerClientData::MixableStream& mixableStream,
AvatarAudioStream& listeningNodeStream,
float masterListenerGain);
float masterAvatarGain,
float masterInjectorGain);
void resetHRTFState(AudioMixerClientData::MixableStream& mixableStream);
void addStreams(Node& listener, AudioMixerClientData& listenerData);

View file

@ -253,10 +253,29 @@ void AvatarMixer::start() {
int lockWait, nodeTransform, functor;
// Set our query each frame
{
_entityViewer.queryOctree();
}
// Dirty the hero status if there's been an entity change.
{
if (_dirtyHeroStatus) {
_dirtyHeroStatus = false;
nodeList->nestedEach([](NodeList::const_iterator cbegin, NodeList::const_iterator cend) {
std::for_each(cbegin, cend, [](const SharedNodePointer& node) {
if (node->getType() == NodeType::Agent) {
NodeData* nodeData = node->getLinkedData();
if (nodeData) {
auto& avatar = static_cast<AvatarMixerClientData*>(nodeData)->getAvatar();
avatar.setNeedsHeroCheck();
}
}
});
});
}
}
// Allow nodes to process any pending/queued packets across our worker threads
{
auto start = usecTimestampNow();
@ -827,7 +846,7 @@ void AvatarMixer::sendStatsPacket() {
QJsonObject avatarsObject;
auto nodeList = DependencyManager::get<NodeList>();
// add stats for each listerner
// add stats for each listener
nodeList->eachNode([&](const SharedNodePointer& node) {
QJsonObject avatarStats;
@ -851,6 +870,12 @@ void AvatarMixer::sendStatsPacket() {
avatarStats["delta_full_vs_avatar_data_kbps"] =
(double)outboundAvatarDataKbps - avatarStats[OUTBOUND_AVATAR_DATA_STATS_KEY].toDouble();
}
if (node->getType() != NodeType::Agent) { // Nodes that aren't avatars
const QString displayName
{ node->getType() == NodeType::EntityScriptServer ? "ENTITY SCRIPT SERVER" : "ENTITY SERVER" };
avatarStats["display_name"] = displayName;
}
}
avatarsObject[uuidStringWithoutCurlyBraces(node->getUUID())] = avatarStats;
@ -973,19 +998,30 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) {
{
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);
bool success;
int connectionRate = avatarMixerGroupObject[CONNECTION_RATE].toString().toInt(&success);
if (success) {
nodeList->setMaxConnectionRate(connectionRate);
}
}
{ // Fraction of downstream bandwidth reserved for 'hero' avatars:
static const QString PRIORITY_FRACTION_KEY = "priority_fraction";
if (avatarMixerGroupObject.contains(PRIORITY_FRACTION_KEY)) {
float priorityFraction = float(avatarMixerGroupObject[PRIORITY_FRACTION_KEY].toDouble());
_slavePool.setPriorityReservedFraction(std::min(std::max(0.0f, priorityFraction), 1.0f));
qCDebug(avatars) << "Avatar mixer reserving" << priorityFraction << "of bandwidth for priority avatars";
}
}
const QString AVATARS_SETTINGS_KEY = "avatars";
static const QString MIN_HEIGHT_OPTION = "min_avatar_height";
float settingMinHeight = domainSettings[AVATARS_SETTINGS_KEY].toObject()[MIN_HEIGHT_OPTION].toDouble(MIN_AVATAR_HEIGHT);
float settingMinHeight = avatarMixerGroupObject[MIN_HEIGHT_OPTION].toDouble(MIN_AVATAR_HEIGHT);
_domainMinimumHeight = glm::clamp(settingMinHeight, MIN_AVATAR_HEIGHT, MAX_AVATAR_HEIGHT);
static const QString MAX_HEIGHT_OPTION = "max_avatar_height";
float settingMaxHeight = domainSettings[AVATARS_SETTINGS_KEY].toObject()[MAX_HEIGHT_OPTION].toDouble(MAX_AVATAR_HEIGHT);
float settingMaxHeight = avatarMixerGroupObject[MAX_HEIGHT_OPTION].toDouble(MAX_AVATAR_HEIGHT);
_domainMaximumHeight = glm::clamp(settingMaxHeight, MIN_AVATAR_HEIGHT, MAX_AVATAR_HEIGHT);
// make sure that the domain owner didn't flip min and max
@ -997,11 +1033,11 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) {
<< "and a maximum avatar height of" << _domainMaximumHeight;
static const QString AVATAR_WHITELIST_OPTION = "avatar_whitelist";
_slaveSharedData.skeletonURLWhitelist = domainSettings[AVATARS_SETTINGS_KEY].toObject()[AVATAR_WHITELIST_OPTION]
_slaveSharedData.skeletonURLWhitelist = avatarMixerGroupObject[AVATAR_WHITELIST_OPTION]
.toString().split(',', QString::KeepEmptyParts);
static const QString REPLACEMENT_AVATAR_OPTION = "replacement_avatar";
_slaveSharedData.skeletonReplacementURL = domainSettings[AVATARS_SETTINGS_KEY].toObject()[REPLACEMENT_AVATAR_OPTION]
_slaveSharedData.skeletonReplacementURL = avatarMixerGroupObject[REPLACEMENT_AVATAR_OPTION]
.toString();
if (_slaveSharedData.skeletonURLWhitelist.count() == 1 && _slaveSharedData.skeletonURLWhitelist[0].isEmpty()) {
@ -1018,9 +1054,12 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) {
void AvatarMixer::setupEntityQuery() {
_entityViewer.init();
EntityTreePointer entityTree = _entityViewer.getTree();
DependencyManager::registerInheritance<SpatialParentFinder, AssignmentParentFinder>();
DependencyManager::set<AssignmentParentFinder>(_entityViewer.getTree());
_slaveSharedData.entityTree = _entityViewer.getTree();
DependencyManager::set<AssignmentParentFinder>(entityTree);
connect(entityTree.get(), &EntityTree::addingEntityPointer, this, &AvatarMixer::entityAdded);
connect(entityTree.get(), &EntityTree::deletingEntityPointer, this, &AvatarMixer::entityChange);
// ES query: {"avatarPriority": true, "type": "Zone"}
QJsonObject priorityZoneQuery;
@ -1028,6 +1067,7 @@ void AvatarMixer::setupEntityQuery() {
priorityZoneQuery["type"] = "Zone";
_entityViewer.getOctreeQuery().setJSONParameters(priorityZoneQuery);
_slaveSharedData.entityTree = entityTree;
}
void AvatarMixer::handleOctreePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
@ -1064,6 +1104,25 @@ void AvatarMixer::handleOctreePacket(QSharedPointer<ReceivedMessage> message, Sh
}
}
void AvatarMixer::entityAdded(EntityItem* entity) {
if (entity->getType() == EntityTypes::Zone) {
_dirtyHeroStatus = true;
entity->registerChangeHandler([this](const EntityItemID& entityItemID) {
entityChange();
});
}
}
void AvatarMixer::entityRemoved(EntityItem * entity) {
if (entity->getType() == EntityTypes::Zone) {
_dirtyHeroStatus = true;
}
}
void AvatarMixer::entityChange() {
_dirtyHeroStatus = true;
}
void AvatarMixer::aboutToFinish() {
DependencyManager::destroy<ResourceManager>();
DependencyManager::destroy<ResourceCacheSharedItems>();

View file

@ -34,8 +34,8 @@ public:
static bool shouldReplicateTo(const Node& from, const Node& to) {
return to.getType() == NodeType::DownstreamAvatarMixer &&
to.getPublicSocket() != from.getPublicSocket() &&
to.getLocalSocket() != from.getLocalSocket();
to.getPublicSocket() != from.getPublicSocket() &&
to.getLocalSocket() != from.getLocalSocket();
}
public slots:
@ -46,6 +46,11 @@ public slots:
void sendStatsPacket() override;
// Avatar zone possibly changed
void entityAdded(EntityItem* entity);
void entityRemoved(EntityItem* entity);
void entityChange();
private slots:
void queueIncomingPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer node);
void handleAdjustAvatarSorting(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
@ -80,6 +85,7 @@ private:
// Attach to entity tree for avatar-priority zone info.
EntityTreeHeadlessViewer _entityViewer;
bool _dirtyHeroStatus { true }; // Dirty the needs-hero-update
// FIXME - new throttling - use these values somehow
float _trailingMixRatio { 0.0f };

View file

@ -129,30 +129,24 @@ int AvatarMixerClientData::parseData(ReceivedMessage& message, const SlaveShared
incrementNumOutOfOrderSends();
}
_lastReceivedSequenceNumber = sequenceNumber;
glm::vec3 oldPosition = getPosition();
glm::vec3 oldPosition = _avatar->getClientGlobalPosition();
bool oldHasPriority = _avatar->getHasPriority();
// compute the offset to the data payload
if (!_avatar->parseDataFromBuffer(message.readWithoutCopy(message.getBytesLeftToRead()))) {
return false;
}
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
// Regardless of what the client says, restore the priority as we know it without triggering any update.
_avatar->setHasPriorityWithoutTimestampReset(oldHasPriority);
auto newPosition = _avatar->getClientGlobalPosition();
if (newPosition != oldPosition || _avatar->getNeedsHeroCheck()) {
EntityTree& entityTree = *slaveSharedData.entityTree;
FindPriorityZone findPriorityZone { newPosition, false } ;
FindPriorityZone findPriorityZone { newPosition } ;
entityTree.recurseTreeWithOperation(&FindPriorityZone::operation, &findPriorityZone);
_avatar->setHasPriority(findPriorityZone.isInPriorityZone);
//if (findPriorityZone.isInPriorityZone) {
// qCWarning(avatars) << "Avatar" << _avatar->getSessionDisplayName() << "in hero zone";
//}
#endif
_avatar->setNeedsHeroCheck(false);
}
return true;
@ -337,7 +331,7 @@ void AvatarMixerClientData::checkSkeletonURLAgainstWhitelist(const SlaveSharedDa
// the returned set traits packet uses the trait version from the incoming packet
// so the client knows they should not overwrite if they have since changed the trait
_avatar->packTrait(AvatarTraits::SkeletonModelURL, *packet, traitVersion);
AvatarTraits::packVersionedTrait(AvatarTraits::SkeletonModelURL, *packet, traitVersion, *_avatar);
auto nodeList = DependencyManager::get<NodeList>();
nodeList->sendPacket(std::move(packet), sendingNode);

View file

@ -43,12 +43,14 @@ void AvatarMixerSlave::configure(ConstIter begin, ConstIter end) {
void AvatarMixerSlave::configureBroadcast(ConstIter begin, ConstIter end,
p_high_resolution_clock::time_point lastFrameTimestamp,
float maxKbpsPerNode, float throttlingRatio) {
float maxKbpsPerNode, float throttlingRatio,
float priorityReservedFraction) {
_begin = begin;
_end = end;
_lastFrameTimestamp = lastFrameTimestamp;
_maxKbpsPerNode = maxKbpsPerNode;
_throttlingRatio = throttlingRatio;
_avatarHeroFraction = priorityReservedFraction;
}
void AvatarMixerSlave::harvestStats(AvatarMixerSlaveStats& stats) {
@ -139,7 +141,8 @@ qint64 AvatarMixerSlave::addChangedTraitsToBulkPacket(AvatarMixerClientData* lis
if (lastReceivedVersion > lastSentVersionRef) {
bytesWritten += addTraitsNodeHeader(listeningNodeData, sendingNodeData, traitsPacketList, bytesWritten);
// there is an update to this trait, add it to the traits packet
bytesWritten += sendingAvatar->packTrait(traitType, traitsPacketList, lastReceivedVersion);
bytesWritten += AvatarTraits::packVersionedTrait(traitType, traitsPacketList,
lastReceivedVersion, *sendingAvatar);
// update the last sent version
lastSentVersionRef = lastReceivedVersion;
// Remember which versions we sent in this particular packet
@ -194,7 +197,8 @@ qint64 AvatarMixerSlave::addChangedTraitsToBulkPacket(AvatarMixerClientData* lis
bytesWritten += addTraitsNodeHeader(listeningNodeData, sendingNodeData, traitsPacketList, bytesWritten);
// this instance version exists and has never been sent or is newer so we need to send it
bytesWritten += sendingAvatar->packTraitInstance(traitType, instanceID, traitsPacketList, receivedVersion);
bytesWritten += AvatarTraits::packVersionedTraitInstance(traitType, instanceID, traitsPacketList,
receivedVersion, *sendingAvatar);
if (sentInstanceIt != sentIDValuePairs.end()) {
sentInstanceIt->value = receivedVersion;
@ -308,7 +312,6 @@ namespace {
} // 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>();
@ -343,7 +346,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
// 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
const int maxHeroBytesPerFrame = int(maxAvatarBytesPerFrame * _avatarHeroFraction); // 5555, typical
// keep track of the number of other avatars held back in this frame
int numAvatarsHeldBack = 0;
@ -469,8 +472,8 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
SortableAvatar(avatarNodeData, sourceAvatarNode, lastEncodeTime));
}
// If Avatar A's PAL WAS open but is no longer open, AND
// Avatar A is ignoring Avatar B OR Avatar B is ignoring Avatar A...
// If Node A's PAL WAS open but is no longer open, AND
// Node A is ignoring Avatar B OR Node B is ignoring Avatar A...
//
// This is a bit heavy-handed still - there are cases where a kill packet
// will be sent when it doesn't need to be (but where it _should_ be OK to send).
@ -539,7 +542,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
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?
bool isLowerPriority = sortedAvatar.getPriority() <= OUT_OF_VIEW_THRESHOLD;
if (isLowerPriority) {
detail = PALIsOpen ? AvatarData::PALMinimum : AvatarData::MinimumData;
@ -548,8 +551,8 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
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 the time that the mixer sent AVATAR DATA about Avatar B to Node A is BEFORE OR EQUAL TO
// the time that Avatar B flagged an IDENTITY DATA change, send IDENTITY DATA about Avatar B to Node A.
if (sourceAvatar->hasProcessedFirstIdentity()
&& destinationNodeData->getLastBroadcastTime(sourceNode->getLocalID()) <= sourceNodeData->getIdentityChangeTimestamp()) {
identityBytesSent += sendIdentityPacket(*identityPacketList, sourceNodeData, *destinationNode);

View file

@ -110,7 +110,8 @@ public:
void configure(ConstIter begin, ConstIter end);
void configureBroadcast(ConstIter begin, ConstIter end,
p_high_resolution_clock::time_point lastFrameTimestamp,
float maxKbpsPerNode, float throttlingRatio);
float maxKbpsPerNode, float throttlingRatio,
float priorityReservedFraction);
void processIncomingPackets(const SharedNodePointer& node);
void broadcastAvatarData(const SharedNodePointer& node);
@ -140,6 +141,7 @@ private:
p_high_resolution_clock::time_point _lastFrameTimestamp;
float _maxKbpsPerNode { 0.0f };
float _throttlingRatio { 0.0f };
float _avatarHeroFraction { 0.4f };
AvatarMixerSlaveStats _stats;
SlaveSharedData* _sharedData;

View file

@ -76,7 +76,8 @@ void AvatarMixerSlavePool::broadcastAvatarData(ConstIter begin, ConstIter end,
float maxKbpsPerNode, float throttlingRatio) {
_function = &AvatarMixerSlave::broadcastAvatarData;
_configure = [=](AvatarMixerSlave& slave) {
slave.configureBroadcast(begin, end, lastFrameTimestamp, maxKbpsPerNode, throttlingRatio);
slave.configureBroadcast(begin, end, lastFrameTimestamp, maxKbpsPerNode, throttlingRatio,
_priorityReservedFraction);
};
run(begin, end);
}

View file

@ -73,7 +73,10 @@ public:
void each(std::function<void(AvatarMixerSlave& slave)> functor);
void setNumThreads(int numThreads);
int numThreads() { return _numThreads; }
int numThreads() const { return _numThreads; }
void setPriorityReservedFraction(float fraction) { _priorityReservedFraction = fraction; }
float getPriorityReservedFraction() const { return _priorityReservedFraction; }
private:
void run(ConstIter begin, ConstIter end);
@ -91,7 +94,11 @@ private:
ConditionVariable _poolCondition;
void (AvatarMixerSlave::*_function)(const SharedNodePointer& node);
std::function<void(AvatarMixerSlave&)> _configure;
// Set from Domain Settings:
float _priorityReservedFraction { 0.4f };
int _numThreads { 0 };
int _numStarted { 0 }; // guarded by _mutex
int _numFinished { 0 }; // guarded by _mutex
int _numStopped { 0 }; // guarded by _mutex

View file

@ -19,11 +19,12 @@
class MixerAvatar : public AvatarData {
public:
bool getHasPriority() const { return _hasPriority; }
void setHasPriority(bool hasPriority) { _hasPriority = hasPriority; }
bool getNeedsHeroCheck() const { return _needsHeroCheck; }
void setNeedsHeroCheck(bool needsHeroCheck = true)
{ _needsHeroCheck = needsHeroCheck; }
private:
bool _hasPriority { false };
bool _needsHeroCheck { false };
};
using MixerAvatarSharedPointer = std::shared_ptr<MixerAvatar>;

View file

@ -20,25 +20,29 @@
/**jsdoc
* The <code>Avatar</code> API is used to manipulate scriptable avatars on the domain. This API is a subset of the
* {@link MyAvatar} API.
* {@link MyAvatar} API. To enable this API, set {@link Agent|Agent.isAvatar} to <code>true</code>.
*
* <p>For Interface, client entity, and avatar scripts, see {@link MyAvatar}.</p>
*
* <p><strong>Note:</strong> In the examples, use "<code>Avatar</code>" instead of "<code>MyAvatar</code>".</p>
*
* @namespace Avatar
*
* @hifi-assignment-client
*
* @property {Vec3} position
* @property {number} scale
* @property {number} density <em>Read-only.</em>
* @property {Vec3} handPosition
* @property {number} bodyYaw - The rotation left or right about an axis running from the head to the feet of the avatar.
* @comment IMPORTANT: This group of properties is copied from AvatarData.h; they should NOT be edited here.
* @property {Vec3} position - The position of the avatar.
* @property {number} scale=1.0 - The scale of the avatar. The value can be set to anything between <code>0.005</code> and
* <code>1000.0</code>. When the scale value is fetched, it may temporarily be further limited by the domain's settings.
* @property {number} density - The density of the avatar in kg/m<sup>3</sup>. The density is used to work out its mass in
* the application of physics. <em>Read-only.</em>
* @property {Vec3} handPosition - A user-defined hand position, in world coordinates. The position moves with the avatar
* but is otherwise not used or changed by Interface.
* @property {number} bodyYaw - The left or right rotation about an axis running from the head to the feet of the avatar.
* Yaw is sometimes called "heading".
* @property {number} bodyPitch - The rotation about an axis running from shoulder to shoulder of the avatar. Pitch is
* sometimes called "elevation".
* @property {number} bodyRoll - The rotation about an axis running from the chest to the back of the avatar. Roll is
* sometimes called "bank".
* @property {Quat} orientation
* @property {Quat} orientation - The orientation of the avatar.
* @property {Quat} headOrientation - The orientation of the avatar's head.
* @property {number} headPitch - The rotation about an axis running from ear to ear of the avatar's head. Pitch is
* sometimes called "elevation".
@ -46,79 +50,37 @@
* head. Yaw is sometimes called "heading".
* @property {number} headRoll - The rotation about an axis running from the nose to the back of the avatar's head. Roll is
* sometimes called "bank".
* @property {Vec3} velocity
* @property {Vec3} angularVelocity
* @property {number} audioLoudness
* @property {number} audioAverageLoudness
* @property {string} displayName
* @property {string} sessionDisplayName - Sanitized, defaulted version displayName that is defined by the AvatarMixer
* rather than by Interface clients. The result is unique among all avatars present at the time.
* @property {boolean} lookAtSnappingEnabled
* @property {string} skeletonModelURL
* @property {AttachmentData[]} attachmentData
* @property {Vec3} velocity - The current velocity of the avatar.
* @property {Vec3} angularVelocity - The current angular velocity of the avatar.
* @property {number} audioLoudness - The instantaneous loudness of the audio input that the avatar is injecting into the
* domain.
* @property {number} audioAverageLoudness - The rolling average loudness of the audio input that the avatar is injecting
* into the domain.
* @property {string} displayName - The avatar's display name.
* @property {string} sessionDisplayName - <code>displayName's</code> sanitized and default version defined by the avatar mixer
* rather than Interface clients. The result is unique among all avatars present in the domain at the time.
* @property {boolean} lookAtSnappingEnabled=true - <code>true</code> if the avatar's eyes snap to look at another avatar's
* eyes when the other avatar is in the line of sight and also has <code>lookAtSnappingEnabled == true</code>.
* @property {string} skeletonModelURL - The avatar's FST file.
* @property {AttachmentData[]} attachmentData - Information on the avatar's attachments.<br />
* <strong>Deprecated:</strong> Use avatar entities instead.
* @property {string[]} jointNames - The list of joints in the current avatar model. <em>Read-only.</em>
* @property {Uuid} sessionUUID <em>Read-only.</em>
* @property {Mat4} sensorToWorldMatrix <em>Read-only.</em>
* @property {Mat4} controllerLeftHandMatrix <em>Read-only.</em>
* @property {Mat4} controllerRightHandMatrix <em>Read-only.</em>
* @property {number} sensorToWorldScale <em>Read-only.</em>
* @property {Uuid} sessionUUID - Unique ID of the avatar in the domain. <em>Read-only.</em>
* @property {Mat4} sensorToWorldMatrix - The scale, rotation, and translation transform from the user's real world to the
* avatar's size, orientation, and position in the virtual world. <em>Read-only.</em>
* @property {Mat4} controllerLeftHandMatrix - The rotation and translation of the left hand controller relative to the
* avatar. <em>Read-only.</em>
* @property {Mat4} controllerRightHandMatrix - The rotation and translation of the right hand controller relative to the
* avatar. <em>Read-only.</em>
* @property {number} sensorToWorldScale - The scale that transforms dimensions in the user's real world to the avatar's
* size in the virtual world. <em>Read-only.</em>
* @property {boolean} hasPriority - is the avatar in a Hero zone? <em>Read-only.</em>
*
* @borrows MyAvatar.getDomainMinScale as getDomainMinScale
* @borrows MyAvatar.getDomainMaxScale as getDomainMaxScale
* @borrows MyAvatar.canMeasureEyeHeight as canMeasureEyeHeight
* @borrows MyAvatar.getEyeHeight as getEyeHeight
* @borrows MyAvatar.getHeight as getHeight
* @borrows MyAvatar.setHandState as setHandState
* @borrows MyAvatar.getHandState as getHandState
* @borrows MyAvatar.setRawJointData as setRawJointData
* @borrows MyAvatar.setJointData as setJointData
* @borrows MyAvatar.setJointRotation as setJointRotation
* @borrows MyAvatar.setJointTranslation as setJointTranslation
* @borrows MyAvatar.clearJointData as clearJointData
* @borrows MyAvatar.isJointDataValid as isJointDataValid
* @borrows MyAvatar.getJointRotation as getJointRotation
* @borrows MyAvatar.getJointTranslation as getJointTranslation
* @borrows MyAvatar.getJointRotations as getJointRotations
* @borrows MyAvatar.getJointTranslations as getJointTranslations
* @borrows MyAvatar.setJointRotations as setJointRotations
* @borrows MyAvatar.setJointTranslations as setJointTranslations
* @borrows MyAvatar.clearJointsData as clearJointsData
* @borrows MyAvatar.getJointIndex as getJointIndex
* @borrows MyAvatar.getJointNames as getJointNames
* @borrows MyAvatar.setBlendshape as setBlendshape
* @borrows MyAvatar.getAttachmentsVariant as getAttachmentsVariant
* @borrows MyAvatar.setAttachmentsVariant as setAttachmentsVariant
* @borrows MyAvatar.updateAvatarEntity as updateAvatarEntity
* @borrows MyAvatar.clearAvatarEntity as clearAvatarEntity
* @borrows MyAvatar.setForceFaceTrackerConnected as setForceFaceTrackerConnected
* @borrows MyAvatar.getAttachmentData as getAttachmentData
* @borrows MyAvatar.setAttachmentData as setAttachmentData
* @borrows MyAvatar.attach as attach
* @borrows MyAvatar.detachOne as detachOne
* @borrows MyAvatar.detachAll as detachAll
* @borrows MyAvatar.getAvatarEntityData as getAvatarEntityData
* @borrows MyAvatar.setAvatarEntityData as setAvatarEntityData
* @borrows MyAvatar.getSensorToWorldMatrix as getSensorToWorldMatrix
* @borrows MyAvatar.getSensorToWorldScale as getSensorToWorldScale
* @borrows MyAvatar.getControllerLeftHandMatrix as getControllerLeftHandMatrix
* @borrows MyAvatar.getControllerRightHandMatrix as getControllerRightHandMatrix
* @borrows MyAvatar.getDataRate as getDataRate
* @borrows MyAvatar.getUpdateRate as getUpdateRate
* @borrows MyAvatar.displayNameChanged as displayNameChanged
* @borrows MyAvatar.sessionDisplayNameChanged as sessionDisplayNameChanged
* @borrows MyAvatar.skeletonModelURLChanged as skeletonModelURLChanged
* @borrows MyAvatar.lookAtSnappingChanged as lookAtSnappingChanged
* @borrows MyAvatar.sessionUUIDChanged as sessionUUIDChanged
* @borrows MyAvatar.sendAvatarDataPacket as sendAvatarDataPacket
* @borrows MyAvatar.sendIdentityPacket as sendIdentityPacket
* @borrows MyAvatar.setJointMappingsFromNetworkReply as setJointMappingsFromNetworkReply
* @borrows MyAvatar.setSessionUUID as setSessionUUID
* @borrows MyAvatar.getAbsoluteJointRotationInObjectFrame as getAbsoluteJointRotationInObjectFrame
* @borrows MyAvatar.getAbsoluteJointTranslationInObjectFrame as getAbsoluteJointTranslationInObjectFrame
* @borrows MyAvatar.setAbsoluteJointRotationInObjectFrame as setAbsoluteJointRotationInObjectFrame
* @borrows MyAvatar.setAbsoluteJointTranslationInObjectFrame as setAbsoluteJointTranslationInObjectFrame
* @borrows MyAvatar.getTargetScale as getTargetScale
* @borrows MyAvatar.resetLastSent as resetLastSent
* @example <caption>Create a scriptable avatar.</caption>
* (function () {
* Agent.setIsAvatar(true);
* print("Position: " + JSON.stringify(Avatar.position)); // 0, 0, 0
* }());
*/
class ScriptableAvatar : public AvatarData, public Dependency {
@ -132,15 +94,17 @@ public:
ScriptableAvatar();
/**jsdoc
* Starts playing an animation on the avatar.
* @function Avatar.startAnimation
* @param {string} url
* @param {number} [fps=30]
* @param {number} [priority=1]
* @param {boolean} [loop=false]
* @param {boolean} [hold=false]
* @param {number} [firstFrame=0]
* @param {number} [lastFrame=3.403e+38]
* @param {string[]} [maskedJoints=[]]
* @param {string} url - The animation file's URL. Animation files need to be in the FBX format but only need to contain
* the avatar skeleton and animation data.
* @param {number} [fps=30] - The frames per second (FPS) rate for the animation playback. 30 FPS is normal speed.
* @param {number} [priority=1] - <em>Not used.</em>
* @param {boolean} [loop=false] - <code>true</code> if the animation should loop, <code>false</code> if it shouldn't.
* @param {boolean} [hold=false] - <em>Not used.</em>
* @param {number} [firstFrame=0] - The frame at which the animation starts.
* @param {number} [lastFrame=3.403e+38] - The frame at which the animation stops.
* @param {string[]} [maskedJoints=[]] - The names of joints that should not be animated.
*/
/// Allows scripts to run animations.
Q_INVOKABLE void startAnimation(const QString& url, float fps = 30.0f, float priority = 1.0f, bool loop = false,
@ -148,39 +112,37 @@ public:
const QStringList& maskedJoints = QStringList());
/**jsdoc
* Stops playing the current animation.
* @function Avatar.stopAnimation
*/
Q_INVOKABLE void stopAnimation();
/**jsdoc
* Gets the details of the current avatar animation that is being or was recently played.
* @function Avatar.getAnimationDetails
* @returns {Avatar.AnimationDetails}
* @returns {Avatar.AnimationDetails} The current or recent avatar animation.
* @example <caption>Report the current animation details.</caption>
* var animationDetails = Avatar.getAnimationDetails();
* print("Animation details: " + JSON.stringify(animationDetails));
*/
Q_INVOKABLE AnimationDetails getAnimationDetails();
/**jsdoc
* Get the names of all the joints in the current avatar.
* @function MyAvatar.getJointNames
* @returns {string[]} The joint names.
* @example <caption>Report the names of all the joints in your current avatar.</caption>
* print(JSON.stringify(MyAvatar.getJointNames()));
*/
* @comment Uses the base class's JSDoc.
*/
Q_INVOKABLE virtual QStringList getJointNames() const override;
/**jsdoc
* Get the joint index for a named joint. The joint index value is the position of the joint in the array returned by
* {@link MyAvatar.getJointNames} or {@link Avatar.getJointNames}.
* @function MyAvatar.getJointIndex
* @param {string} name - The name of the joint.
* @returns {number} The index of the joint.
* @example <caption>Report the index of your avatar's left arm joint.</caption>
* print(JSON.stringify(MyAvatar.getJointIndex("LeftArm"));
*/
* @comment Uses the base class's JSDoc.
*/
/// Returns the index of the joint with the specified name, or -1 if not found/unknown.
Q_INVOKABLE virtual int getJointIndex(const QString& name) const override;
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override;
/**jsdoc
* @comment Uses the base class's JSDoc.
*/
int sendAvatarDataPacket(bool sendAll = false) override;
virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking = false) override;
@ -192,32 +154,42 @@ public:
void setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement);
bool getHasAudioEnabledFaceMovement() const override { return _headData->getHasAudioEnabledFaceMovement(); }
/**jsdoc
* Potentially Very Expensive. Do not use.
/**jsdoc
* Gets details of all avatar entities.
* <p><strong>Warning:</strong> Potentially an expensive call. Do not use if possible.</p>
* @function Avatar.getAvatarEntityData
* @returns {object}
* @returns {AvatarEntityMap} Details of the avatar entities.
* @example <caption>Report the current avatar entities.</caption>
* var avatarEntityData = Avatar.getAvatarEntityData();
* print("Avatar entities: " + JSON.stringify(avatarEntityData));
*/
Q_INVOKABLE AvatarEntityMap getAvatarEntityData() const override;
/**jsdoc
* @function MyAvatar.setAvatarEntityData
* @param {object} avatarEntityData
*/
* Sets all avatar entities from an object.
* <p><strong>Warning:</strong> Potentially an expensive call. Do not use if possible.</p>
* @function Avatar.setAvatarEntityData
* @param {AvatarEntityMap} avatarEntityData - Details of the avatar entities.
*/
Q_INVOKABLE void setAvatarEntityData(const AvatarEntityMap& avatarEntityData) override;
/**jsdoc
* @function MyAvatar.updateAvatarEntity
* @param {Uuid} entityID
* @param {string} entityData
* @comment Uses the base class's JSDoc.
*/
Q_INVOKABLE void updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData) override;
public slots:
/**jsdoc
* @function Avatar.update
* @param {number} deltaTime - Delta time.
* @deprecated This function is deprecated and will be removed.
*/
void update(float deltatime);
/**jsdoc
* @function MyAvatar.setJointMappingsFromNetworkReply
*/
* @function Avatar.setJointMappingsFromNetworkReply
* @deprecated This function is deprecated and will be removed.
*/
void setJointMappingsFromNetworkReply();
private:

View file

@ -22,7 +22,6 @@
#include <ScriptCache.h>
#include <EntityEditFilters.h>
#include <NetworkingConstants.h>
#include <AddressManager.h>
#include <hfm/ModelFormatRegistry.h>
#include "../AssignmentDynamicFactory.h"
@ -471,77 +470,7 @@ void EntityServer::startDynamicDomainVerification() {
qCDebug(entities) << "Starting Dynamic Domain Verification...";
EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree);
QHash<QString, EntityItemID> localMap(tree->getEntityCertificateIDMap());
QHashIterator<QString, EntityItemID> i(localMap);
qCDebug(entities) << localMap.size() << "entities in _entityCertificateIDMap";
while (i.hasNext()) {
i.next();
const auto& certificateID = i.key();
const auto& entityID = i.value();
EntityItemPointer entity = tree->findEntityByEntityItemID(entityID);
if (entity) {
if (!entity->getProperties().verifyStaticCertificateProperties()) {
qCDebug(entities) << "During Dynamic Domain Verification, a certified entity with ID" << entityID << "failed"
<< "static certificate verification.";
// Delete the entity if it doesn't pass static certificate verification
tree->withWriteLock([&] {
tree->deleteEntity(entityID, true);
});
} else {
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkRequest networkRequest;
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL();
requestURL.setPath("/api/v1/commerce/proof_of_purchase_status/location");
QJsonObject request;
request["certificate_id"] = certificateID;
networkRequest.setUrl(requestURL);
QNetworkReply* networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson());
connect(networkReply, &QNetworkReply::finished, this, [this, entityID, networkReply] {
EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree);
QJsonObject jsonObject = QJsonDocument::fromJson(networkReply->readAll()).object();
jsonObject = jsonObject["data"].toObject();
if (networkReply->error() == QNetworkReply::NoError) {
QString thisDomainID = DependencyManager::get<AddressManager>()->getDomainID().remove(QRegExp("\\{|\\}"));
if (jsonObject["domain_id"].toString() != thisDomainID) {
EntityItemPointer entity = tree->findEntityByEntityItemID(entityID);
if (!entity) {
qCDebug(entities) << "Entity undergoing dynamic domain verification is no longer available:" << entityID;
networkReply->deleteLater();
return;
}
if (entity->getAge() > (_MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS/MSECS_PER_SECOND)) {
qCDebug(entities) << "Entity's cert's domain ID" << jsonObject["domain_id"].toString()
<< "doesn't match the current Domain ID" << thisDomainID << "; deleting entity" << entityID;
tree->withWriteLock([&] {
tree->deleteEntity(entityID, true);
});
} else {
qCDebug(entities) << "Entity failed dynamic domain verification, but was created too recently to necessitate deletion:" << entityID;
}
} else {
qCDebug(entities) << "Entity passed dynamic domain verification:" << entityID;
}
} else {
qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << "; NOT deleting entity" << entityID
<< "More info:" << jsonObject;
}
networkReply->deleteLater();
});
}
} else {
qCWarning(entities) << "During DDV, an entity with ID" << entityID << "was NOT found in the Entity Tree!";
}
}
tree->startDynamicDomainVerificationOnServer((float) _MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS / MSECS_PER_SECOND);
int nextInterval = qrand() % ((_MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS + 1) - _MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS) + _MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS;
qCDebug(entities) << "Restarting Dynamic Domain Verification timer for" << nextInterval / 1000 << "seconds";

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

@ -1,7 +1,7 @@
macro(TARGET_PYTHON)
if (NOT HIFI_PYTHON_EXEC)
# Find the python interpreter
if (CAME_VERSION VERSION_LESS 3.12)
if (CMAKE_VERSION VERSION_LESS 3.12)
# this logic is deprecated in CMake after 3.12
# FIXME eventually we should make 3.12 the min cmake verion and just use the Python3 find_package path
set(Python_ADDITIONAL_VERSIONS 3)

View file

@ -1310,6 +1310,15 @@
"placeholder": "50",
"default": "50",
"advanced": true
},
{
"name": "priority_fraction",
"type": "double",
"label": "Hero Bandwidth",
"help": "Fraction of downstream bandwidth reserved for avatars in 'Hero' zones",
"placeholder": "0.40",
"default": "0.40",
"advanced": true
}
]
},

View file

@ -1734,7 +1734,7 @@ void DomainServer::processOctreeDataPersistMessage(QSharedPointer<ReceivedMessag
f.write(data);
OctreeUtils::RawEntityData entityData;
if (entityData.readOctreeDataInfoFromData(data)) {
qCDebug(domain_server) << "Wrote new entities file" << entityData.id << entityData.version;
qCDebug(domain_server) << "Wrote new entities file" << entityData.id << entityData.dataVersion;
} else {
qCDebug(domain_server) << "Failed to read new octree data info";
}
@ -1766,14 +1766,14 @@ void DomainServer::processOctreeDataRequestMessage(QSharedPointer<ReceivedMessag
bool remoteHasExistingData { false };
QUuid id;
int version;
int dataVersion;
message->readPrimitive(&remoteHasExistingData);
if (remoteHasExistingData) {
constexpr size_t UUID_SIZE_BYTES = 16;
auto idData = message->read(UUID_SIZE_BYTES);
id = QUuid::fromRfc4122(idData);
message->readPrimitive(&version);
qCDebug(domain_server) << "Entity server does have existing data: ID(" << id << ") DataVersion(" << version << ")";
message->readPrimitive(&dataVersion);
qCDebug(domain_server) << "Entity server does have existing data: ID(" << id << ") DataVersion(" << dataVersion << ")";
} else {
qCDebug(domain_server) << "Entity server does not have existing data";
}
@ -1782,11 +1782,11 @@ void DomainServer::processOctreeDataRequestMessage(QSharedPointer<ReceivedMessag
auto reply = NLPacketList::create(PacketType::OctreeDataFileReply, QByteArray(), true, true);
OctreeUtils::RawEntityData data;
if (data.readOctreeDataInfoFromFile(entityFilePath)) {
if (data.id == id && data.version <= version) {
if (data.id == id && data.dataVersion <= dataVersion) {
qCDebug(domain_server) << "ES has sufficient octree data, not sending data";
reply->writePrimitive(false);
} else {
qCDebug(domain_server) << "Sending newer octree data to ES: ID(" << data.id << ") DataVersion(" << data.version << ")";
qCDebug(domain_server) << "Sending newer octree data to ES: ID(" << data.id << ") DataVersion(" << data.dataVersion << ")";
QFile file(entityFilePath);
if (file.open(QIODevice::ReadOnly)) {
reply->writePrimitive(true);

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

@ -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

@ -0,0 +1,10 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M25.3383 4.13446L23.8713 5.60152C21.9374 3.46761 19.2033 2.06723 16.0691 2.06723C13.0683 2.06723 10.4009 3.40093 8.46707 5.40147L7 3.9344C9.26728 1.53375 12.5348 0 16.0691 0C19.7368 0 23.071 1.60044 25.3383 4.13446ZM21.9376 7.53584L20.4705 9.0029C19.4703 7.66921 17.8698 6.86899 16.0693 6.86899C14.4022 6.86899 12.9351 7.66921 11.8682 8.80285L10.4011 7.33578C11.8015 5.80203 13.802 4.80176 16.0693 4.80176C18.4033 4.80176 20.5372 5.86871 21.9376 7.53584ZM17.9575 30.1572C17.9575 31.1771 17.1307 32.0039 16.1108 32.0039C15.0909 32.0039 14.2642 31.1771 14.2642 30.1572C14.2642 29.1373 15.0909 28.3105 16.1108 28.3105C17.1307 28.3105 17.9575 29.1373 17.9575 30.1572ZM18.3632 11.0801H14.1597L15.0116 25.8539H17.4867L18.3632 11.0801Z" fill="#EA4C5F"/>
</g>
<defs>
<clipPath id="clip0">
<rect width="32" height="32" fill="white"/>
</clipPath>
</defs>
</svg>

After

(image error) Size: 1 KiB

View file

@ -0,0 +1,3 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.1999 4.72587C17.5049 4.72587 18.5628 3.66795 18.5628 2.36294C18.5628 1.05792 17.5049 0 16.1999 0C14.8948 0 13.8369 1.05792 13.8369 2.36294C13.8369 3.66795 14.8948 4.72587 16.1999 4.72587ZM18.6667 11.8004V14.7337H13.7334V11.8004C13.7334 10.467 14.8667 9.40039 16.2 9.40039C17.6 9.40039 18.6667 10.467 18.6667 11.8004ZM13.7334 20.1332V17.2666H18.6667V20.1332C18.6667 21.4665 17.5333 22.5332 16.2 22.5332C14.8667 22.5332 13.7334 21.4665 13.7334 20.1332ZM23.6665 20.6V17.0667C23.6665 16.4 23.0665 15.9334 22.4665 15.9334C21.7998 15.9334 21.3332 16.4667 21.3332 17.1333V20.6C21.3332 23.0666 19.0665 25.0666 16.3332 25.0666C13.5999 25.0666 11.3333 23.0666 11.3333 20.6V17.0667C11.3333 16.4 10.8666 15.8667 10.2666 15.8667C9.59999 15.8 9 16.2667 9 16.9333V20.6C9 23.9999 11.6666 26.7999 15.1333 27.3332V29.5998H12.2666C11.6 29.5998 11.0666 30.1332 11.0666 30.7998C11.0666 31.4665 11.6 31.9998 12.2666 31.9998H20.4665C21.1332 31.9998 21.6665 31.4665 21.6665 30.7998C21.6665 30.1332 21.1332 29.5998 20.4665 29.5998H17.5332V27.3332C20.9998 26.7332 23.6665 23.9999 23.6665 20.6Z" fill="#00B4EF" fill-opacity="0.7"/>
</svg>

After

(image error) Size: 1.2 KiB

View file

@ -1,25 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1"
id="svg2" inkscape:version="0.91 r13725" sodipodi:docname="mic-mute-a.svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg"
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:#EA4C5F;}
</style>
<sodipodi:namedview bordercolor="#666666" borderopacity="1" gridtolerance="10" guidetolerance="10" id="namedview18" inkscape:current-layer="svg2" inkscape:cx="25" inkscape:cy="25" inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-height="480" inkscape:window-maximized="0" inkscape:window-width="852" inkscape:window-x="0" inkscape:window-y="0" inkscape:zoom="4.72" objecttolerance="10" pagecolor="#ff0000" showgrid="false">
</sodipodi:namedview>
<g id="Layer_2">
</g>
<g id="Layer_1">
<path id="path8" class="st0" d="M28.9,17.1v-0.5c0-2-1.7-3.6-3.7-3.6l0,0c-2,0-3.7,1.6-3.7,3.6v6.9L28.9,17.1z"/>
<path id="path10" class="st0" d="M21.5,29.2v0.2c0,2,1.6,3.6,3.7,3.6l0,0c2,0,3.7-1.6,3.7-3.6v-6.6L21.5,29.2z"/>
<path id="path12" class="st0" d="M39.1,16.8L13.6,39.1c-0.7,0.6-1.8,0.5-2.4-0.2L11,38.7c-0.6-0.7-0.5-1.8,0.2-2.4l25.4-22.4
c0.7-0.6,1.8-0.5,2.4,0.2l0.2,0.2C39.8,15.1,39.7,16.1,39.1,16.8z"/>
<path id="path14" class="st0" d="M23.4,40.2v3.4h-4.3c-1,0-1.8,0.8-1.8,1.8s0.8,1.8,1.8,1.8h12.3c1,0,1.8-0.8,1.8-1.8
s-0.8-1.8-1.8-1.8H27v-3.4c5.2-0.8,9.2-5,9.2-10.1c0-0.1,0-5.1,0-5.3c0-1-0.9-1.7-1.8-1.7c-1,0-1.7,0.8-1.7,1.8c0,0.3,0,4.8,0,5.2
c0,3.7-3.4,6.7-7.5,6.7c-3.6,0-6.7-2.3-7.3-5.4L15,34C16.4,37.2,19.6,39.7,23.4,40.2z"/>
<path id="path16" class="st0" d="M17.7,24.9c0-1-0.7-1.8-1.6-1.8c-1-0.1-1.8,0.7-1.9,1.6c0,0.2,0,4.2,0,5.3l3.5-3.1
C17.7,25.9,17.7,25,17.7,24.9z"/>
</g>
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M25.6441 4.13328L24.1775 5.59992C22.2442 3.46662 19.5109 2.06664 16.3776 2.06664C13.3776 2.06664 10.711 3.39995 8.77768 5.39993L7.31104 3.93328C9.57767 1.53331 12.8443 0 16.3776 0C20.0442 0 23.3775 1.59998 25.6441 4.13328ZM20.7777 9.00072L22.2444 7.53408C20.8444 5.86743 18.7111 4.80078 16.3778 4.80078C14.1111 4.80078 12.1112 5.80077 10.7112 7.33408L12.1778 8.80073C13.2445 7.66741 14.7111 6.86742 16.3778 6.86742C18.1777 6.86742 19.7777 7.66741 20.7777 9.00072ZM18.8803 12.0758V11.7496C18.8803 10.4445 17.7763 9.40039 16.4775 9.40039C15.1787 9.40039 14.0747 10.4445 14.0747 11.7496V16.2521L18.8803 12.0758ZM14.543 21.5129L12.6113 23.2103C13.4959 24.2599 14.9141 24.9311 16.4774 24.9311C19.14 24.9311 21.348 22.9735 21.348 20.559V17.1658C21.348 16.5132 21.8026 15.9912 22.452 15.9912C23.0364 15.9912 23.6209 16.448 23.6209 17.1005V20.559C23.6209 23.887 21.0233 26.6277 17.6464 27.1498V29.3684H20.5038C21.1532 29.3684 21.6727 29.8905 21.6727 30.543C21.6727 31.1956 21.1532 31.7176 20.5038 31.7176H12.5161C11.8667 31.7176 11.3471 31.1956 11.3471 30.543C11.3471 29.8905 11.8667 29.3684 12.5161 29.3684H15.3085V27.1498C13.5328 26.915 11.9589 26.0045 10.8771 24.7343L8.9443 26.4327C8.48972 26.8242 7.77537 26.759 7.38573 26.3022L7.25585 26.1717C6.8662 25.7149 6.93114 24.9971 7.38573 24.6056L23.8806 9.98853C24.3352 9.597 25.0495 9.66226 25.4392 10.119L25.5691 10.2495C25.9587 10.7716 25.8938 11.4241 25.5041 11.8809L18.8803 17.7015V20.1017C18.8803 21.4068 17.7763 22.4509 16.4775 22.4509C15.6689 22.4509 14.9744 22.0838 14.543 21.5129ZM10.5679 15.9919C11.1523 15.9919 11.6069 16.5139 11.6069 17.1664V18.4715L9.33398 20.4944V17.0359C9.39892 16.4486 9.91845 15.9266 10.5679 15.9919Z" fill="#EA4C5F"/>
</svg>

Before

(image error) Size: 2.1 KiB

After

(image error) Size: 1.8 KiB

View file

@ -1,25 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1"
id="svg2" inkscape:version="0.91 r13725" sodipodi:docname="mic-mute-a.svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg"
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:#EA4C5F;}
</style>
<sodipodi:namedview bordercolor="#666666" borderopacity="1" gridtolerance="10" guidetolerance="10" id="namedview18" inkscape:current-layer="svg2" inkscape:cx="25" inkscape:cy="25" inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-height="480" inkscape:window-maximized="0" inkscape:window-width="852" inkscape:window-x="0" inkscape:window-y="0" inkscape:zoom="4.72" objecttolerance="10" pagecolor="#ff0000" showgrid="false">
</sodipodi:namedview>
<g id="Layer_2">
</g>
<g id="Layer_1">
<path id="path8" class="st0" d="M28.9,17.1v-0.5c0-2-1.7-3.6-3.7-3.6l0,0c-2,0-3.7,1.6-3.7,3.6v6.9L28.9,17.1z"/>
<path id="path10" class="st0" d="M21.5,29.2v0.2c0,2,1.6,3.6,3.7,3.6l0,0c2,0,3.7-1.6,3.7-3.6v-6.6L21.5,29.2z"/>
<path id="path12" class="st0" d="M39.1,16.8L13.6,39.1c-0.7,0.6-1.8,0.5-2.4-0.2L11,38.7c-0.6-0.7-0.5-1.8,0.2-2.4l25.4-22.4
c0.7-0.6,1.8-0.5,2.4,0.2l0.2,0.2C39.8,15.1,39.7,16.1,39.1,16.8z"/>
<path id="path14" class="st0" d="M23.4,40.2v3.4h-4.3c-1,0-1.8,0.8-1.8,1.8s0.8,1.8,1.8,1.8h12.3c1,0,1.8-0.8,1.8-1.8
s-0.8-1.8-1.8-1.8H27v-3.4c5.2-0.8,9.2-5,9.2-10.1c0-0.1,0-5.1,0-5.3c0-1-0.9-1.7-1.8-1.7c-1,0-1.7,0.8-1.7,1.8c0,0.3,0,4.8,0,5.2
c0,3.7-3.4,6.7-7.5,6.7c-3.6,0-6.7-2.3-7.3-5.4L15,34C16.4,37.2,19.6,39.7,23.4,40.2z"/>
<path id="path16" class="st0" d="M17.7,24.9c0-1-0.7-1.8-1.6-1.8c-1-0.1-1.8,0.7-1.9,1.6c0,0.2,0,4.2,0,5.3l3.5-3.1
C17.7,25.9,17.7,25,17.7,24.9z"/>
</g>
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M25.6441 4.13328L24.1775 5.59992C22.2442 3.46662 19.5109 2.06664 16.3776 2.06664C13.3776 2.06664 10.711 3.39995 8.77768 5.39993L7.31104 3.93328C9.57767 1.53331 12.8443 0 16.3776 0C20.0442 0 23.3775 1.59998 25.6441 4.13328ZM20.7777 9.00072L22.2444 7.53408C20.8444 5.86743 18.7111 4.80078 16.3778 4.80078C14.1111 4.80078 12.1112 5.80077 10.7112 7.33408L12.1778 8.80073C13.2445 7.66741 14.7111 6.86742 16.3778 6.86742C18.1777 6.86742 19.7777 7.66741 20.7777 9.00072ZM18.8803 12.0758V11.7496C18.8803 10.4445 17.7763 9.40039 16.4775 9.40039C15.1787 9.40039 14.0747 10.4445 14.0747 11.7496V16.2521L18.8803 12.0758ZM14.543 21.5129L12.6113 23.2103C13.4959 24.2599 14.9141 24.9311 16.4774 24.9311C19.14 24.9311 21.348 22.9735 21.348 20.559V17.1658C21.348 16.5132 21.8026 15.9912 22.452 15.9912C23.0364 15.9912 23.6209 16.448 23.6209 17.1005V20.559C23.6209 23.887 21.0233 26.6277 17.6464 27.1498V29.3684H20.5038C21.1532 29.3684 21.6727 29.8905 21.6727 30.543C21.6727 31.1956 21.1532 31.7176 20.5038 31.7176H12.5161C11.8667 31.7176 11.3471 31.1956 11.3471 30.543C11.3471 29.8905 11.8667 29.3684 12.5161 29.3684H15.3085V27.1498C13.5328 26.915 11.9589 26.0045 10.8771 24.7343L8.9443 26.4327C8.48972 26.8242 7.77537 26.759 7.38573 26.3022L7.25585 26.1717C6.8662 25.7149 6.93114 24.9971 7.38573 24.6056L23.8806 9.98853C24.3352 9.597 25.0495 9.66226 25.4392 10.119L25.5691 10.2495C25.9587 10.7716 25.8938 11.4241 25.5041 11.8809L18.8803 17.7015V20.1017C18.8803 21.4068 17.7763 22.4509 16.4775 22.4509C15.6689 22.4509 14.9744 22.0838 14.543 21.5129ZM10.5679 15.9919C11.1523 15.9919 11.6069 16.5139 11.6069 17.1664V18.4715L9.33398 20.4944V17.0359C9.39893 16.4486 9.91845 15.9266 10.5679 15.9919Z" fill="#EA4C5F"/>
</svg>

Before

(image error) Size: 2.1 KiB

After

(image error) Size: 1.8 KiB

View file

@ -1,60 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="Layer_1"
x="0px"
y="0px"
viewBox="0 0 40 40"
style="enable-background:new 0 0 40 40;"
xml:space="preserve"
inkscape:version="0.91 r13725"
sodipodi:docname="mic-mute.svg"><metadata
id="metadata6958"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
id="defs6956" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1824"
inkscape:window-height="1057"
id="namedview6954"
showgrid="false"
inkscape:zoom="5.9"
inkscape:cx="-40.338983"
inkscape:cy="20"
inkscape:window-x="88"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="Layer_1" /><style
type="text/css"
id="style6942">
.st0{fill:#FFFFFF;}
</style><ellipse
style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
id="path8048"
cx="20.1"
cy="20.5"
rx="15.967586"
ry="15.967585" /><rect
style="fill:#ffffff;fill-opacity:1;stroke:#feffff;stroke-width:0;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
id="rect8065"
width="30.1991"
height="2.9999897"
x="13.432917"
y="-1.2235159"
transform="matrix(0.70710678,0.70710678,-0.70710678,0.70710678,0,0)" /></svg>

Before

(image error) Size: 2.2 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

(image error) 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

(image error) Size: 2.3 KiB

View file

@ -1,70 +1,3 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
x="0px"
y="0px"
viewBox="0 0 50 50"
style="enable-background:new 0 0 50 50;"
xml:space="preserve"
id="svg2"
inkscape:version="0.91 r13725"
sodipodi:docname="mic-unmute-a.svg"><metadata
id="metadata22"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs20" /><sodipodi:namedview
pagecolor="#ff0000"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="852"
inkscape:window-height="480"
id="namedview18"
showgrid="false"
inkscape:zoom="4.72"
inkscape:cx="25"
inkscape:cy="25"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="svg2" /><style
type="text/css"
id="style4">
.st0{fill:#FFFFFF;}
</style><g
id="Layer_2" /><g
id="Layer_1"
style="fill:#000000;fill-opacity:1"><path
class="st0"
d="M31.4,14.1l2.2-2.2c-2.1-2.5-5.3-4.1-8.8-4.1c-3.4,0-6.4,1.5-8.5,3.8c0.7,0.7,1.5,1.5,2.2,2.2 c1.6-1.7,3.8-2.9,6.3-2.9C27.5,10.9,29.9,12.1,31.4,14.1z"
id="path8"
style="fill:#000000;fill-opacity:1" /><path
class="st0"
d="M36.5,9l2.2-2.2C35.3,3,30.3,0.6,24.8,0.6c-5.3,0-10.2,2.3-13.6,5.9c0.7,0.7,1.5,1.5,2.2,2.2 c2.9-3,6.9-5,11.4-5C29.5,3.7,33.6,5.8,36.5,9z"
id="path10"
style="fill:#000000;fill-opacity:1" /><path
class="st0"
d="M28.5,22.7v-4.4c0-2-1.6-3.6-3.7-3.6h0c-2,0-3.7,1.6-3.7,3.6v4.4H28.5z"
id="path12"
style="fill:#000000;fill-opacity:1" /><path
class="st0"
d="M21.1,26.5v4.3c0,2,1.7,3.6,3.7,3.6h0c2,0,3.7-1.6,3.7-3.6v-4.3H21.1z"
id="path14"
style="fill:#000000;fill-opacity:1" /><path
class="st0"
d="M36,31.5c0-0.1,0-5.1,0-5.3c0-1-0.9-1.7-1.8-1.7c-1,0-1.7,0.8-1.7,1.8c0,0.3,0,4.8,0,5.2 c0,3.7-3.4,6.7-7.5,6.7c-4.1,0-7.5-3-7.5-6.7c0-0.4,0-4.9,0-5.3c0-1-0.7-1.8-1.6-1.8C14.9,24.3,14,25,14,26c0,0.3,0,5.4,0,5.5 c0,5.1,4,9.3,9.2,10.1l0,3.4h-4.3c-1,0-1.8,0.8-1.8,1.8c0,1,0.8,1.8,1.8,1.8h12.3c1,0,1.8-0.8,1.8-1.8c0-1-0.8-1.8-1.8-1.8h-4.4 l0-3.4C32,40.7,36,36.6,36,31.5z"
id="path16"
style="fill:#000000;fill-opacity:1" /></g></svg>
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.8664 5.59992L25.3331 4.13328C23.0664 1.59998 19.7332 0 16.0665 0C12.5333 0 9.26664 1.53331 7 3.93328L8.46665 5.39993C10.4 3.39995 13.0666 2.06664 16.0665 2.06664C19.1998 2.06664 21.9331 3.46662 23.8664 5.59992ZM20.4664 8.99975L21.9331 7.5331C20.5331 5.86646 18.3998 4.7998 16.0665 4.7998C13.7999 4.7998 11.7999 5.79979 10.3999 7.3331L11.8665 8.79975C12.9332 7.66643 14.3998 6.86644 16.0665 6.86644C17.8665 6.86644 19.4664 7.66643 20.4664 8.99975ZM18.5334 11.8004V14.7337H13.6001V11.8004C13.6001 10.467 14.7334 9.40039 16.0667 9.40039C17.4667 9.40039 18.5334 10.467 18.5334 11.8004ZM13.6001 17.2666V20.1332C13.6001 21.4665 14.7334 22.5332 16.0667 22.5332C17.4 22.5332 18.5334 21.4665 18.5334 20.1332V17.2666H13.6001ZM23.5332 17.0667V20.6C23.5332 23.9999 20.8665 26.7332 17.3999 27.3332V29.5998H20.3332C20.9999 29.5998 21.5332 30.1332 21.5332 30.7998C21.5332 31.4665 20.9999 31.9998 20.3332 31.9998H12.1333C11.4667 31.9998 10.9333 31.4665 10.9333 30.7998C10.9333 30.1332 11.4667 29.5998 12.1333 29.5998H14.9999V27.3332C11.5333 26.7999 8.8667 23.9999 8.8667 20.6V16.9333C8.8667 16.2667 9.46669 15.8 10.1333 15.8667C10.7333 15.8667 11.2 16.4 11.2 17.0667V20.6C11.2 23.0666 13.4666 25.0666 16.1999 25.0666C18.9332 25.0666 21.1999 23.0666 21.1999 20.6V17.1333C21.1999 16.4667 21.6665 15.9334 22.3332 15.9334C22.9332 15.9334 23.5332 16.4 23.5332 17.0667Z" fill="white"/>
</svg>

Before

(image error) Size: 2.9 KiB

After

(image error) Size: 1.5 KiB

View file

@ -1,22 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" 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>
<g id="Layer_2">
</g>
<g id="Layer_1">
<path class="st0" d="M31.4,14.1l2.2-2.2c-2.1-2.5-5.3-4.1-8.8-4.1c-3.4,0-6.4,1.5-8.5,3.8c0.7,0.7,1.5,1.5,2.2,2.2
c1.6-1.7,3.8-2.9,6.3-2.9C27.5,10.9,29.9,12.1,31.4,14.1z"/>
<path class="st0" d="M36.5,9l2.2-2.2C35.3,3,30.3,0.6,24.8,0.6c-5.3,0-10.2,2.3-13.6,5.9c0.7,0.7,1.5,1.5,2.2,2.2
c2.9-3,6.9-5,11.4-5C29.5,3.7,33.6,5.8,36.5,9z"/>
<path class="st0" d="M28.5,22.7v-4.4c0-2-1.6-3.6-3.7-3.6h0c-2,0-3.7,1.6-3.7,3.6v4.4H28.5z"/>
<path class="st0" d="M21.1,26.5v4.3c0,2,1.7,3.6,3.7,3.6h0c2,0,3.7-1.6,3.7-3.6v-4.3H21.1z"/>
<path class="st0" d="M36,31.5c0-0.1,0-5.1,0-5.3c0-1-0.9-1.7-1.8-1.7c-1,0-1.7,0.8-1.7,1.8c0,0.3,0,4.8,0,5.2
c0,3.7-3.4,6.7-7.5,6.7c-4.1,0-7.5-3-7.5-6.7c0-0.4,0-4.9,0-5.3c0-1-0.7-1.8-1.6-1.8C14.9,24.3,14,25,14,26c0,0.3,0,5.4,0,5.5
c0,5.1,4,9.3,9.2,10.1l0,3.4h-4.3c-1,0-1.8,0.8-1.8,1.8c0,1,0.8,1.8,1.8,1.8h12.3c1,0,1.8-0.8,1.8-1.8c0-1-0.8-1.8-1.8-1.8h-4.4
l0-3.4C32,40.7,36,36.6,36,31.5z"/>
</g>
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.8664 5.59992L25.3331 4.13328C23.0664 1.59998 19.7332 0 16.0665 0C12.5333 0 9.26664 1.53331 7 3.93328L8.46665 5.39993C10.4 3.39995 13.0666 2.06664 16.0665 2.06664C19.1998 2.06664 21.9331 3.46662 23.8664 5.59992ZM20.4664 8.99975L21.9331 7.5331C20.5331 5.86646 18.3998 4.7998 16.0665 4.7998C13.7999 4.7998 11.7999 5.79979 10.3999 7.3331L11.8665 8.79975C12.9332 7.66643 14.3998 6.86644 16.0665 6.86644C17.8665 6.86644 19.4664 7.66643 20.4664 8.99975ZM18.5334 11.8004V14.7337H13.6001V11.8004C13.6001 10.467 14.7334 9.40039 16.0667 9.40039C17.4667 9.40039 18.5334 10.467 18.5334 11.8004ZM13.6001 17.2666V20.1332C13.6001 21.4665 14.7334 22.5332 16.0667 22.5332C17.4 22.5332 18.5334 21.4665 18.5334 20.1332V17.2666H13.6001ZM23.5332 17.0667V20.6C23.5332 23.9999 20.8665 26.7332 17.3999 27.3332V29.5998H20.3332C20.9999 29.5998 21.5332 30.1332 21.5332 30.7998C21.5332 31.4665 20.9999 31.9998 20.3332 31.9998H12.1333C11.4667 31.9998 10.9333 31.4665 10.9333 30.7998C10.9333 30.1332 11.4667 29.5998 12.1333 29.5998H14.9999V27.3332C11.5333 26.7999 8.8667 23.9999 8.8667 20.6V16.9333C8.8667 16.2667 9.46669 15.8 10.1333 15.8667C10.7333 15.8667 11.2 16.4 11.2 17.0667V20.6C11.2 23.0666 13.4666 25.0666 16.1999 25.0666C18.9332 25.0666 21.1999 23.0666 21.1999 20.6V17.1333C21.1999 16.4667 21.6665 15.9334 22.3332 15.9334C22.9332 15.9334 23.5332 16.4 23.5332 17.0667Z" fill="white"/>
</svg>

Before

(image error) Size: 1.3 KiB

After

(image error) Size: 1.5 KiB

View file

@ -59,4 +59,4 @@
d="m 27.9,20.9 c 0,0 0,-3.6 0,-3.8 0,-0.7 -0.6,-1.2 -1.3,-1.2 -0.7,0 -1.2,0.6 -1.2,1.3 0,0.2 0,3.4 0,3.7 0,2.6 -2.4,4.8 -5.3,4.8 -2.9,0 -5.3,-2.1 -5.3,-4.8 0,-0.3 0,-3.5 0,-3.8 0,-0.7 -0.5,-1.3 -1.2,-1.3 -0.7,0 -1.3,0.5 -1.3,1.2 0,0.2 0,3.9 0,3.9 0,3.6 2.9,6.6 6.6,7.2 l 0,2.4 -3.1,0 c -0.7,0 -1.3,0.6 -1.3,1.3 0,0.7 0.6,1.3 1.3,1.3 l 8.8,0 c 0.7,0 1.3,-0.6 1.3,-1.3 0,-0.7 -0.6,-1.3 -1.3,-1.3 l -3.2,0 0,-2.4 c 3.6,-0.5 6.5,-3.5 6.5,-7.2 z"
id="path6952"
inkscape:connector-curvature="0"
style="fill:#ffffff" /></svg>
style="fill:#ffffff" /></svg>

Before

(image error) Size: 2.5 KiB

After

(image error) Size: 2.5 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

@ -7,24 +7,57 @@
//
import Hifi 1.0 as Hifi
import QtQuick 2.4
import QtQuick 2.5
import QtGraphicalEffects 1.0
import "./hifi/audio" as HifiAudio
import TabletScriptingInterface 1.0
Item {
id: root;
objectName: "AvatarInputsBar"
property int modality: Qt.NonModal
width: audio.width;
height: audio.height;
x: 10; y: 5;
readonly property bool ignoreRadiusEnabled: AvatarInputs.ignoreRadiusEnabled;
x: 10;
y: 5;
readonly property bool shouldReposition: true;
property bool hmdActive: HMD.active;
width: hmdActive ? audio.width : audioApplication.width;
height: hmdActive ? audio.height : audioApplication.height;
Timer {
id: hmdActiveCheckTimer;
interval: 500;
repeat: true;
onTriggered: {
root.hmdActive = HMD.active;
}
}
HifiAudio.MicBar {
id: audio;
visible: AvatarInputs.showAudioTools;
visible: AvatarInputs.showAudioTools && root.hmdActive;
standalone: true;
dragTarget: parent;
dragTarget: parent;
}
HifiAudio.MicBarApplication {
id: audioApplication;
visible: AvatarInputs.showAudioTools && !root.hmdActive;
standalone: true;
dragTarget: parent;
}
Component.onCompleted: {
HMD.displayModeChanged.connect(function(isHmdMode) {
root.hmdActive = isHmdMode;
});
}
BubbleIcon {
dragTarget: parent
visible: !root.hmdActive;
}
}

View file

@ -0,0 +1,100 @@
//
// Created by Bradley Austin Davis on 2015/06/19
// Copyright 2015 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 "./hifi/audio" as HifiAudio
import TabletScriptingInterface 1.0
Rectangle {
id: bubbleRect
width: bubbleIcon.width + 10
height: bubbleIcon.height + 10
radius: 5;
property var dragTarget: null;
property bool ignoreRadiusEnabled: AvatarInputs.ignoreRadiusEnabled;
function updateOpacity() {
if (ignoreRadiusEnabled) {
bubbleRect.opacity = 1.0;
} else {
bubbleRect.opacity = 0.7;
}
}
Component.onCompleted: {
updateOpacity();
}
onIgnoreRadiusEnabledChanged: {
updateOpacity();
}
color: "#00000000";
border {
width: mouseArea.containsMouse || mouseArea.containsPress ? 2 : 0;
color: "#80FFFFFF";
}
anchors {
left: dragTarget ? dragTarget.right : undefined
top: dragTarget ? dragTarget.top : undefined
}
// borders are painted over fill, so reduce the fill to fit inside the border
Rectangle {
color: "#55000000";
width: 40;
height: 40;
radius: 5;
anchors {
verticalCenter: parent.verticalCenter;
horizontalCenter: parent.horizontalCenter;
}
}
MouseArea {
id: mouseArea;
anchors.fill: parent
hoverEnabled: true;
scrollGestureEnabled: false;
onClicked: {
Tablet.playSound(TabletEnums.ButtonClick);
Users.toggleIgnoreRadius();
}
drag.target: dragTarget;
onContainsMouseChanged: {
var rectOpacity = (ignoreRadiusEnabled && containsMouse) ? 1.0 : (containsMouse ? 1.0 : 0.7);
if (containsMouse) {
Tablet.playSound(TabletEnums.ButtonHover);
}
bubbleRect.opacity = rectOpacity;
}
}
Image {
id: bubbleIcon
source: "../icons/tablet-icons/bubble-i.svg";
sourceSize: Qt.size(32, 32);
smooth: true;
anchors.top: parent.top
anchors.topMargin: (parent.height - bubbleIcon.height) / 2
anchors.left: parent.left
anchors.leftMargin: (parent.width - bubbleIcon.width) / 2
}
ColorOverlay {
id: bubbleIconOverlay
anchors.fill: bubbleIcon
source: bubbleIcon
color: "#FFFFFF";
}
}

View file

@ -379,9 +379,9 @@ Item {
Component.onCompleted: {
// with the link.
if (completeProfileBody.withOculus) {
termsText.text = qsTr("By signing up, you agree to <a href='https://highfidelity.com/terms'>High Fidelity's Terms of Service</a>")
termsText.text = qsTr("By signing up, you agree to <a href='https://www.highfidelity.com/termsofservice'>High Fidelity's Terms of Service</a>")
} else {
termsText.text = qsTr("By creating this user profile, you agree to <a href='https://highfidelity.com/terms'>High Fidelity's Terms of Service</a>")
termsText.text = qsTr("By creating this user profile, you agree to <a href='https://www.highfidelity.com/termsofservice'>High Fidelity's Terms of Service</a>")
}
}
}
@ -510,7 +510,7 @@ Item {
console.log("Create Failed: " + error);
if (completeProfileBody.withSteam || completeProfileBody.withOculus) {
if (completeProfileBody.loginDialogPoppedUp) {
action = completeProfileBody.withSteam ? "Steam" : "Oculus";
var action = completeProfileBody.withSteam ? "Steam" : "Oculus";
var data = {
"action": "user failed to create a profile with " + action + " from the complete profile screen"
}

View file

@ -395,7 +395,7 @@ Item {
text: signUpBody.termsContainerText
Component.onCompleted: {
// with the link.
termsText.text = qsTr("By signing up, you agree to <a href='https://highfidelity.com/terms'>High Fidelity's Terms of Service</a>")
termsText.text = qsTr("By signing up, you agree to <a href='https://www.highfidelity.com/termsofservice'>High Fidelity's Terms of Service</a>")
}
}

View file

@ -218,7 +218,7 @@ Item {
text: usernameCollisionBody.termsContainerText
Component.onCompleted: {
// with the link.
termsText.text = qsTr("By creating this user profile, you agree to <a href='https://highfidelity.com/terms'>High Fidelity's Terms of Service</a>")
termsText.text = qsTr("By creating this user profile, you agree to <a href='https://www.highfidelity.com/termsofservice'>High Fidelity's Terms of Service</a>")
}
}

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
@ -228,6 +232,10 @@ Item {
text: "Audio Codec: " + root.audioCodec + " Noise Gate: " +
root.audioNoiseGate;
}
StatText {
visible: root.expanded;
text: "Injectors (Local/NonLocal): " + root.audioInjectors.x + "/" + root.audioInjectors.y;
}
StatText {
visible: root.expanded;
text: "Entity Servers In: " + root.entityPacketsInKbps + " kbps";

View file

@ -143,6 +143,16 @@ Original.Button {
horizontalAlignment: Text.AlignHCenter
text: control.text
Component.onCompleted: {
setTextPosition();
}
onTextChanged: {
setTextPosition();
}
function setTextPosition() {
// force TextMetrics to re-evaluate the text field and glyph sizes
// as for some reason it's not automatically being done.
buttonGlyphTextMetrics.text = buttonGlyph.text;
buttonTextMetrics.text = text;
if (control.buttonGlyph !== "") {
buttonText.x = buttonContentItem.width/2 - buttonTextMetrics.width/2 + (buttonGlyphTextMetrics.width + control.buttonGlyphRightMargin)/2;
} else {

View file

@ -24,6 +24,7 @@ CheckBox {
leftPadding: 0
property int colorScheme: hifi.colorSchemes.light
property string color: hifi.colors.lightGrayText
property int fontSize: hifi.fontSizes.inputLabel
readonly property bool isLightColorScheme: colorScheme === hifi.colorSchemes.light
property bool isRedCheck: false
property bool isRound: false
@ -109,7 +110,7 @@ CheckBox {
contentItem: Text {
id: root
font.pixelSize: hifi.fontSizes.inputLabel
font.pixelSize: fontSize;
font.family: "Raleway"
font.weight: Font.DemiBold
text: checkBox.text

View file

@ -21,6 +21,7 @@ Item {
property int switchWidth: 70;
readonly property int switchRadius: height/2;
property string labelTextOff: "";
property int labelTextSize: hifi.fontSizes.inputLabel;
property string labelGlyphOffText: "";
property int labelGlyphOffSize: 32;
property string labelTextOn: "";
@ -89,7 +90,7 @@ Item {
RalewaySemiBold {
id: labelOff;
text: labelTextOff;
size: hifi.fontSizes.inputLabel;
size: labelTextSize;
color: originalSwitch.checked ? hifi.colors.lightGrayText : "#FFFFFF";
anchors.top: parent.top;
anchors.right: parent.right;
@ -130,7 +131,7 @@ Item {
RalewaySemiBold {
id: labelOn;
text: labelTextOn;
size: hifi.fontSizes.inputLabel;
size: labelTextSize;
color: originalSwitch.checked ? "#FFFFFF" : hifi.colors.lightGrayText;
anchors.top: parent.top;
anchors.left: parent.left;

View file

@ -9,7 +9,7 @@
//
import QtQuick 2.7
import Qt.labs.folderlistmodel 2.1
import Qt.labs.folderlistmodel 2.2
import Qt.labs.settings 1.0
import QtQuick.Dialogs 1.2 as OriginalDialogs
import QtQuick.Controls 1.4 as QQC1
@ -320,6 +320,7 @@ ModalWindow {
FolderListModel {
id: folderListModel
nameFilters: selectionType.currentFilter
caseSensitive: false
showDirsFirst: true
showDotAndDotDot: false
showFiles: !root.selectDirectory

View file

@ -9,7 +9,7 @@
//
import QtQuick 2.7
import Qt.labs.folderlistmodel 2.1
import Qt.labs.folderlistmodel 2.2
import Qt.labs.settings 1.0
import QtQuick.Dialogs 1.2 as OriginalDialogs
import QtQuick.Controls 1.4 as QQC1
@ -285,6 +285,7 @@ TabletModalWindow {
FolderListModel {
id: folderListModel
nameFilters: selectionType.currentFilter
caseSensitive: false
showDirsFirst: true
showDotAndDotDot: false
showFiles: !root.selectDirectory

View file

@ -28,7 +28,7 @@ TabletModalWindow {
id: mouse;
anchors.fill: parent
}
function click(button) {
clickedButton = button;
selected(button);

View file

@ -16,6 +16,8 @@ Rectangle {
property bool keyboardRaised: false
property bool punctuationMode: false
HifiConstants { id: hifi }
HifiControls.Keyboard {
id: keyboard
z: 1000
@ -48,6 +50,7 @@ Rectangle {
property var jointNames: []
property var currentAvatarSettings;
property bool wearablesFrozen;
function fetchAvatarModelName(marketId, avatar) {
var xmlhttp = new XMLHttpRequest();
@ -187,6 +190,8 @@ Rectangle {
updateCurrentAvatarInBookmarks(currentAvatar);
} else if (message.method === 'selectAvatarEntity') {
adjustWearables.selectWearableByID(message.entityID);
} else if (message.method === 'wearablesFrozenChanged') {
wearablesFrozen = message.wearablesFrozen;
}
}
@ -507,6 +512,7 @@ Rectangle {
}
SquareLabel {
id: adjustLabel
anchors.right: parent.right
anchors.verticalCenter: wearablesLabel.verticalCenter
glyphText: "\ue02e"
@ -515,6 +521,17 @@ Rectangle {
adjustWearables.open(currentAvatar);
}
}
SquareLabel {
anchors.right: adjustLabel.left
anchors.verticalCenter: wearablesLabel.verticalCenter
anchors.rightMargin: 15
glyphText: wearablesFrozen ? hifi.glyphs.lock : hifi.glyphs.unlock;
onClicked: {
emitSendToScript({'method' : 'toggleWearablesFrozen'});
}
}
}
Rectangle {

View file

@ -40,6 +40,7 @@ Item {
property bool isConcurrency: action === 'concurrency';
property bool isAnnouncement: action === 'announcement';
property bool isStacked: !isConcurrency && drillDownToPlace;
property bool has3DHTML: PlatformInfo.has3DHTML();
property int textPadding: 10;
@ -298,7 +299,7 @@ Item {
StateImage {
id: actionIcon;
visible: !isAnnouncement;
visible: !isAnnouncement && has3DHTML;
imageURL: "../../images/info-icon-2-state.svg";
size: 30;
buttonState: messageArea.containsMouse ? 1 : 0;
@ -315,7 +316,7 @@ Item {
}
MouseArea {
id: messageArea;
visible: !isAnnouncement;
visible: !isAnnouncement && has3DHTML;
width: parent.width;
height: messageHeight;
anchors.top: lobby.bottom;

View file

@ -0,0 +1,152 @@
//
// EditAvatarInputsBar.qml
// qml/hifi
//
// Audio setup
//
// Created by Wayne Chen on 3/20/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
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import stylesUit 1.0
import controlsUit 1.0 as HifiControlsUit
import "../windows"
Rectangle {
id: editRect
HifiConstants { id: hifi; }
color: hifi.colors.baseGray;
signal sendToScript(var message);
function emitSendToScript(message) {
sendToScript(message);
}
function fromScript(message) {
}
RalewayRegular {
id: title;
color: hifi.colors.white;
text: qsTr("Avatar Inputs Persistent UI Settings")
size: 20
font.bold: true
anchors {
top: parent.top
left: parent.left
leftMargin: (parent.width - width) / 2
}
}
HifiControlsUit.Slider {
id: xSlider
anchors {
top: title.bottom
topMargin: 50
left: parent.left
leftMargin: 20
}
label: "X OFFSET: " + value.toFixed(2);
maximumValue: 1.0
minimumValue: -1.0
stepSize: 0.05
value: -0.2
width: 300
onValueChanged: {
emitSendToScript({
"method": "reposition",
"x": value
});
}
}
HifiControlsUit.Slider {
id: ySlider
anchors {
top: xSlider.bottom
topMargin: 50
left: parent.left
leftMargin: 20
}
label: "Y OFFSET: " + value.toFixed(2);
maximumValue: 1.0
minimumValue: -1.0
stepSize: 0.05
value: -0.125
width: 300
onValueChanged: {
emitSendToScript({
"method": "reposition",
"y": value
});
}
}
HifiControlsUit.Slider {
id: zSlider
anchors {
top: ySlider.bottom
topMargin: 50
left: parent.left
leftMargin: 20
}
label: "Z OFFSET: " + value.toFixed(2);
maximumValue: 0.0
minimumValue: -1.0
stepSize: 0.05
value: -0.5
width: 300
onValueChanged: {
emitSendToScript({
"method": "reposition",
"z": value
});
}
}
HifiControlsUit.Button {
id: setVisibleButton;
text: setVisible ? "SET INVISIBLE" : "SET VISIBLE";
width: 300;
property bool setVisible: true;
anchors {
top: zSlider.bottom
topMargin: 50
left: parent.left
leftMargin: 20
}
onClicked: {
setVisible = !setVisible;
emitSendToScript({
"method": "setVisible",
"visible": setVisible
});
}
}
HifiControlsUit.Button {
id: printButton;
text: "PRINT POSITIONS";
width: 300;
anchors {
top: setVisibleButton.bottom
topMargin: 50
left: parent.left
leftMargin: 20
}
onClicked: {
emitSendToScript({
"method": "print",
});
}
}
}

View file

@ -54,7 +54,7 @@ Column {
'require_online=true',
'protocol=' + encodeURIComponent(Window.protocolSignature())
];
endpoint: '/api/v1/user_stories?' + options.join('&');
endpoint: '/api/v1/user_stories?' + options.join('&') + (PlatformInfo.isStandalone() ? '&standalone_optimized=true' : '')
itemsPerPage: 4;
processPage: function (data) {
return data.user_stories.map(makeModelData);

View file

@ -46,6 +46,8 @@ Item {
property string placeName: ""
property string profilePicBorderColor: (connectionStatus == "connection" ? hifi.colors.indigoAccent : (connectionStatus == "friend" ? hifi.colors.greenHighlight : "transparent"))
property alias avImage: avatarImage
property bool has3DHTML: PlatformInfo.has3DHTML();
Item {
id: avatarImage
visible: profileUrl !== "" && userName !== "";
@ -94,10 +96,12 @@ Item {
enabled: (selected && activeTab == "nearbyTab") || isMyCard;
hoverEnabled: enabled
onClicked: {
userInfoViewer.url = Account.metaverseServerURL + "/users/" + userName;
userInfoViewer.visible = true;
if (has3DHTML) {
userInfoViewer.url = Account.metaverseServerURL + "/users/" + userName;
userInfoViewer.visible = true;
}
}
onEntered: infoHoverImage.visible = true;
onEntered: infoHoverImage.visible = has3DHTML;
onExited: infoHoverImage.visible = false;
}
}
@ -125,6 +129,7 @@ Item {
height: 40
// Anchors
anchors.top: avatarImage.top
anchors.topMargin: avatarImage.visible ? 18 : 0;
anchors.left: avatarImage.right
anchors.leftMargin: avatarImage.visible ? 5 : 0;
anchors.rightMargin: 5;
@ -352,7 +357,7 @@ Item {
}
StateImage {
id: nameCardConnectionInfoImage
visible: selected && !isMyCard && pal.activeTab == "connectionsTab"
visible: selected && !isMyCard && pal.activeTab == "connectionsTab" && has3DHTML
imageURL: "../../images/info-icon-2-state.svg" // PLACEHOLDER!!!
size: 32;
buttonState: 0;
@ -364,8 +369,10 @@ Item {
enabled: selected
hoverEnabled: true
onClicked: {
userInfoViewer.url = Account.metaverseServerURL + "/users/" + userName;
userInfoViewer.visible = true;
if (has3DHTML) {
userInfoViewer.url = Account.metaverseServerURL + "/users/" + userName;
userInfoViewer.visible = true;
}
}
onEntered: {
nameCardConnectionInfoImage.buttonState = 1;
@ -376,8 +383,7 @@ Item {
}
FiraSansRegular {
id: nameCardConnectionInfoText
visible: selected && !isMyCard && pal.activeTab == "connectionsTab"
width: parent.width
visible: selected && !isMyCard && pal.activeTab == "connectionsTab" && PlatformInfo.has3DHTML()
height: displayNameTextPixelSize
size: displayNameTextPixelSize - 4
anchors.left: nameCardConnectionInfoImage.right
@ -391,9 +397,10 @@ Item {
id: nameCardRemoveConnectionImage
visible: selected && !isMyCard && pal.activeTab == "connectionsTab"
text: hifi.glyphs.close
size: 28;
size: 24;
x: 120
anchors.verticalCenter: nameCardConnectionInfoImage.verticalCenter
anchors.left: has3DHTML ? nameCardConnectionInfoText.right + 10 : avatarImage.right
}
MouseArea {
anchors.fill:nameCardRemoveConnectionImage

View file

@ -1261,6 +1261,14 @@ Rectangle {
case 'refreshConnections':
refreshConnections();
break;
case 'connectionRemoved':
for (var i=0; i<connectionsUserModel.count; ++i) {
if (connectionsUserModel.get(i).userName === message.params) {
connectionsUserModel.remove(i);
break;
}
}
break;
case 'avatarDisconnected':
var sessionID = message.params[0];
delete ignored[sessionID];

View file

@ -11,7 +11,7 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.7
import QtQuick 2.10
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
@ -31,6 +31,10 @@ Rectangle {
property string title: "Audio Settings"
property int switchHeight: 16
property int switchWidth: 40
property bool pushToTalk: (bar.currentIndex === 0) ? AudioScriptingInterface.pushToTalkDesktop : AudioScriptingInterface.pushToTalkHMD;
property bool muted: (bar.currentIndex === 0) ? AudioScriptingInterface.mutedDesktop : AudioScriptingInterface.mutedHMD;
readonly property real verticalScrollWidth: 10
readonly property real verticalScrollShaft: 8
signal sendToScript(var message);
color: hifi.colors.baseGray;
@ -42,7 +46,7 @@ Rectangle {
property bool isVR: AudioScriptingInterface.context === "VR"
property real rightMostInputLevelPos: 450
property real rightMostInputLevelPos: root.width
//placeholder for control sizes and paddings
//recalculates dynamically in case of UI size is changed
QtObject {
@ -60,8 +64,8 @@ Rectangle {
id: bar
spacing: 0
width: parent.width
height: 42
currentIndex: isVR ? 1 : 0
height: 28;
currentIndex: isVR ? 1 : 0;
AudioControls.AudioTabButton {
height: parent.height
@ -85,66 +89,130 @@ Rectangle {
}
function updateMyAvatarGainFromQML(sliderValue, isReleased) {
if (Users.getAvatarGain(myAvatarUuid) != sliderValue) {
Users.setAvatarGain(myAvatarUuid, sliderValue);
if (AudioScriptingInterface.getAvatarGain() != sliderValue) {
AudioScriptingInterface.setAvatarGain(sliderValue);
}
}
function updateInjectorGainFromQML(sliderValue, isReleased) {
if (AudioScriptingInterface.getInjectorGain() != sliderValue) {
AudioScriptingInterface.setInjectorGain(sliderValue); // server side
AudioScriptingInterface.setLocalInjectorGain(sliderValue); // client side
}
}
function updateSystemInjectorGainFromQML(sliderValue, isReleased) {
if (AudioScriptingInterface.getSystemInjectorGain() != sliderValue) {
AudioScriptingInterface.setSystemInjectorGain(sliderValue);
}
}
Component.onCompleted: enablePeakValues();
Component.onCompleted: {
enablePeakValues();
}
Column {
id: column
spacing: 12;
anchors.top: bar.bottom
anchors.bottom: parent.bottom
anchors.bottomMargin: 5
Flickable {
id: flickView;
anchors.top: bar.bottom;
anchors.left: parent.left;
anchors.bottom: parent.bottom;
width: parent.width;
contentWidth: parent.width;
contentHeight: contentItem.childrenRect.height;
boundsBehavior: Flickable.DragOverBounds;
flickableDirection: Flickable.VerticalFlick;
property bool isScrolling: (contentHeight - height) > 10 ? true : false;
clip: true;
Separator { }
ScrollBar.vertical: ScrollBar {
policy: flickView.isScrolling ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff;
parent: flickView.parent;
anchors.top: flickView.top;
anchors.right: flickView.right;
anchors.bottom: flickView.bottom;
anchors.rightMargin: -verticalScrollWidth; //compensate flickView's right margin
background: Item {
implicitWidth: verticalScrollWidth;
Rectangle {
color: hifi.colors.darkGray30;
radius: 4;
anchors {
fill: parent;
topMargin: -1; // Finesse size
bottomMargin: -2;
}
}
}
contentItem: Item {
implicitWidth: verticalScrollShaft;
Rectangle {
radius: verticalScrollShaft/2;
color: hifi.colors.white30;
anchors {
fill: parent;
leftMargin: 2; // Finesse size and position.
topMargin: 1;
bottomMargin: 1;
}
}
}
}
RowLayout {
Separator {
id: firstSeparator;
anchors.top: parent.top;
}
Item {
id: switchesContainer;
x: 2 * margins.paddings;
spacing: columnOne.width;
width: parent.width;
// switch heights + 2 * top margins
height: (root.switchHeight) * 3 + 48;
anchors.top: firstSeparator.bottom;
anchors.topMargin: 10;
// mute is in its own row
ColumnLayout {
id: columnOne
spacing: 24;
x: margins.paddings
Item {
id: switchContainer;
x: margins.paddings;
width: parent.width / 2;
height: parent.height;
anchors.left: parent.left;
HifiControlsUit.Switch {
id: muteMic;
height: root.switchHeight;
switchWidth: root.switchWidth;
labelTextOn: "Mute microphone";
labelTextSize: 16;
backgroundOnColor: "#E3E3E3";
checked: AudioScriptingInterface.muted;
onCheckedChanged: {
AudioScriptingInterface.muted = checked;
checked = Qt.binding(function() { return AudioScriptingInterface.muted; }); // restore binding
checked: muted;
onClicked: {
if (pushToTalk && !checked) {
// disable push to talk if unmuting
if (bar.currentIndex === 0) {
AudioScriptingInterface.pushToTalkDesktop = false;
}
else {
AudioScriptingInterface.pushToTalkHMD = false;
}
}
if (bar.currentIndex === 0) {
AudioScriptingInterface.mutedDesktop = checked;
}
else {
AudioScriptingInterface.mutedHMD = checked;
}
}
}
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
}
}
}
ColumnLayout {
spacing: 24;
HifiControlsUit.Switch {
id: noiseReductionSwitch;
height: root.switchHeight;
switchWidth: root.switchWidth;
anchors.top: muteMic.bottom;
anchors.topMargin: 24
anchors.left: parent.left
labelTextOn: "Noise Reduction";
labelTextSize: 16;
backgroundOnColor: "#E3E3E3";
checked: AudioScriptingInterface.noiseReduction;
onCheckedChanged: {
@ -153,11 +221,59 @@ Rectangle {
}
}
HifiControlsUit.Switch {
id: pttSwitch
height: root.switchHeight;
switchWidth: root.switchWidth;
anchors.top: noiseReductionSwitch.bottom
anchors.topMargin: 24
anchors.left: parent.left
labelTextOn: (bar.currentIndex === 0) ? qsTr("Push To Talk (T)") : qsTr("Push To Talk");
labelTextSize: 16;
backgroundOnColor: "#E3E3E3";
checked: (bar.currentIndex === 0) ? AudioScriptingInterface.pushToTalkDesktop : AudioScriptingInterface.pushToTalkHMD;
onCheckedChanged: {
if (bar.currentIndex === 0) {
AudioScriptingInterface.pushToTalkDesktop = checked;
} else {
AudioScriptingInterface.pushToTalkHMD = checked;
}
}
}
}
Item {
id: additionalSwitchContainer
width: switchContainer.width - margins.paddings;
height: parent.height;
anchors.top: parent.top
anchors.left: switchContainer.right;
HifiControlsUit.Switch {
id: warnMutedSwitch
height: root.switchHeight;
switchWidth: root.switchWidth;
anchors.top: parent.top
anchors.left: parent.left
labelTextOn: qsTr("Warn when muted in HMD");
labelTextSize: 16;
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;
anchors.top: warnMutedSwitch.bottom
anchors.topMargin: 24
anchors.left: parent.left
labelTextOn: qsTr("Audio Level Meter");
labelTextSize: 16;
backgroundOnColor: "#E3E3E3";
checked: AvatarInputs.showAudioTools;
onCheckedChanged: {
@ -165,47 +281,97 @@ Rectangle {
checked = Qt.binding(function() { return AvatarInputs.showAudioTools; }); // restore binding
}
}
HifiControlsUit.Switch {
id: stereoInput;
height: root.switchHeight;
switchWidth: root.switchWidth;
anchors.top: audioLevelSwitch.bottom
anchors.topMargin: 24
anchors.left: parent.left
labelTextOn: qsTr("Stereo input");
labelTextSize: 16;
backgroundOnColor: "#E3E3E3";
checked: AudioScriptingInterface.isStereoInput;
onCheckedChanged: {
AudioScriptingInterface.isStereoInput = checked;
checked = Qt.binding(function() { return AudioScriptingInterface.isStereoInput; }); // restore binding
}
}
}
}
Separator {}
Item {
id: pttTextContainer
anchors.top: switchesContainer.bottom;
anchors.topMargin: 10;
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 {
id: secondSeparator;
anchors.top: pttTextContainer.visible ? pttTextContainer.bottom : switchesContainer.bottom;
anchors.topMargin: 10;
}
Item {
id: inputDeviceHeader
x: margins.paddings;
width: parent.width - margins.paddings*2
height: 36
width: parent.width - margins.paddings*2;
height: 36;
anchors.top: secondSeparator.bottom;
anchors.topMargin: 10;
HiFiGlyphs {
width: margins.sizeCheckBox
width: margins.sizeCheckBox;
text: hifi.glyphs.mic;
color: hifi.colors.white;
anchors.left: parent.left
anchors.leftMargin: -size/4 //the glyph has empty space at left about 25%
anchors.left: parent.left;
anchors.leftMargin: -size/4; //the glyph has empty space at left about 25%
anchors.verticalCenter: parent.verticalCenter;
size: 30;
}
RalewayRegular {
anchors.verticalCenter: parent.verticalCenter;
width: margins.sizeText + margins.sizeLevel
anchors.left: parent.left
anchors.leftMargin: margins.sizeCheckBox
size: 16;
width: margins.sizeText + margins.sizeLevel;
anchors.left: parent.left;
anchors.leftMargin: margins.sizeCheckBox;
size: 22;
color: hifi.colors.white;
text: qsTr("Choose input device");
}
}
ListView {
id: inputView
width: parent.width - margins.paddings*2
id: inputView;
width: rightMostInputLevelPos;
anchors.top: inputDeviceHeader.bottom;
anchors.topMargin: 10;
x: margins.paddings
height: Math.min(150, contentHeight);
interactive: false;
height: contentHeight;
spacing: 4;
snapMode: ListView.SnapToItem;
clip: true;
model: AudioScriptingInterface.devices.input;
delegate: Item {
width: rightMostInputLevelPos
width: rightMostInputLevelPos - margins.paddings*2
height: margins.sizeCheckBox > checkBoxInput.implicitHeight ?
margins.sizeCheckBox : checkBoxInput.implicitHeight
@ -221,9 +387,10 @@ Rectangle {
boxSize: margins.sizeCheckBox / 2
isRound: true
text: devicename
fontSize: 16;
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);
}
@ -240,17 +407,28 @@ Rectangle {
}
}
}
AudioControls.LoopbackAudio {
id: loopbackAudio
x: margins.paddings
anchors.top: inputView.bottom;
anchors.topMargin: 10;
visible: (bar.currentIndex === 1 && isVR) ||
(bar.currentIndex === 0 && !isVR);
anchors { left: parent.left; leftMargin: margins.paddings }
}
Separator {}
Separator {
id: thirdSeparator;
anchors.top: loopbackAudio.visible ? loopbackAudio.bottom : inputView.bottom;
anchors.topMargin: 10;
}
Item {
id: outputDeviceHeader;
anchors.topMargin: 10;
anchors.top: thirdSeparator.bottom;
x: margins.paddings;
width: parent.width - margins.paddings*2
height: 36
@ -270,7 +448,7 @@ Rectangle {
anchors.left: parent.left
anchors.leftMargin: margins.sizeCheckBox
anchors.verticalCenter: parent.verticalCenter;
size: 16;
size: 22;
color: hifi.colors.white;
text: qsTr("Choose output device");
}
@ -279,10 +457,12 @@ Rectangle {
ListView {
id: outputView
width: parent.width - margins.paddings*2
x: margins.paddings
height: Math.min(360 - inputView.height, contentHeight);
x: margins.paddings;
interactive: false;
height: contentHeight;
anchors.top: outputDeviceHeader.bottom;
anchors.topMargin: 10;
spacing: 4;
snapMode: ListView.SnapToItem;
clip: true;
model: AudioScriptingInterface.devices.output;
delegate: Item {
@ -299,6 +479,7 @@ Rectangle {
checked: bar.currentIndex === 0 ? selectedDesktop : selectedHMD;
checkable: !checked
text: devicename
fontSize: 16
onPressed: {
if (!checked) {
AudioScriptingInterface.setOutputDevice(info, bar.currentIndex === 1);
@ -309,20 +490,22 @@ Rectangle {
}
Item {
id: gainContainer
id: avatarGainContainer
x: margins.paddings;
anchors.top: outputView.bottom;
anchors.topMargin: 10;
width: parent.width - margins.paddings*2
height: gainSliderTextMetrics.height
height: avatarGainSliderTextMetrics.height
HifiControlsUit.Slider {
id: gainSlider
id: avatarGainSlider
anchors.right: parent.right
height: parent.height
width: 200
minimumValue: -60.0
maximumValue: 20.0
stepSize: 5
value: Users.getAvatarGain(myAvatarUuid)
value: AudioScriptingInterface.getAvatarGain()
onValueChanged: {
updateMyAvatarGainFromQML(value, false);
}
@ -338,7 +521,7 @@ Rectangle {
// Do nothing.
}
onDoubleClicked: {
gainSlider.value = 0.0
avatarGainSlider.value = 0.0
}
onPressed: {
// Pass through to Slider
@ -352,14 +535,136 @@ Rectangle {
}
}
TextMetrics {
id: gainSliderTextMetrics
text: gainSliderText.text
font: gainSliderText.font
id: avatarGainSliderTextMetrics
text: avatarGainSliderText.text
font: avatarGainSliderText.font
}
RalewayRegular {
// The slider for my card is special, it controls the master gain
id: gainSliderText;
text: "Avatar volume";
id: avatarGainSliderText;
text: "People volume";
size: 16;
anchors.left: parent.left;
color: hifi.colors.white;
horizontalAlignment: Text.AlignLeft;
verticalAlignment: Text.AlignTop;
}
}
Item {
id: injectorGainContainer
x: margins.paddings;
width: parent.width - margins.paddings*2
height: injectorGainSliderTextMetrics.height
anchors.top: avatarGainContainer.bottom;
anchors.topMargin: 10;
HifiControlsUit.Slider {
id: injectorGainSlider
anchors.right: parent.right
height: parent.height
width: 200
minimumValue: -60.0
maximumValue: 20.0
stepSize: 5
value: AudioScriptingInterface.getInjectorGain()
onValueChanged: {
updateInjectorGainFromQML(value, false);
}
onPressedChanged: {
if (!pressed) {
updateInjectorGainFromQML(value, false);
}
}
MouseArea {
anchors.fill: parent
onWheel: {
// Do nothing.
}
onDoubleClicked: {
injectorGainSlider.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: injectorGainSliderTextMetrics
text: injectorGainSliderText.text
font: injectorGainSliderText.font
}
RalewayRegular {
id: injectorGainSliderText;
text: "Environment volume";
size: 16;
anchors.left: parent.left;
color: hifi.colors.white;
horizontalAlignment: Text.AlignLeft;
verticalAlignment: Text.AlignTop;
}
}
Item {
id: systemInjectorGainContainer
x: margins.paddings;
width: parent.width - margins.paddings*2
height: systemInjectorGainSliderTextMetrics.height
anchors.top: injectorGainContainer.bottom;
anchors.topMargin: 10;
HifiControlsUit.Slider {
id: systemInjectorGainSlider
anchors.right: parent.right
height: parent.height
width: 200
minimumValue: -60.0
maximumValue: 20.0
stepSize: 5
value: AudioScriptingInterface.getSystemInjectorGain()
onValueChanged: {
updateSystemInjectorGainFromQML(value, false);
}
onPressedChanged: {
if (!pressed) {
updateSystemInjectorGainFromQML(value, false);
}
}
MouseArea {
anchors.fill: parent
onWheel: {
// Do nothing.
}
onDoubleClicked: {
systemInjectorGainSlider.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: systemInjectorGainSliderTextMetrics
text: systemInjectorGainSliderText.text
font: systemInjectorGainSliderText.font
}
RalewayRegular {
id: systemInjectorGainSliderText;
text: "System Sound volume";
size: 16;
anchors.left: parent.left;
color: hifi.colors.white;
@ -369,11 +674,10 @@ Rectangle {
}
AudioControls.PlaySampleSound {
id: playSampleSound
x: margins.paddings
visible: (bar.currentIndex === 1 && isVR) ||
(bar.currentIndex === 0 && !isVR);
anchors { left: parent.left; leftMargin: margins.paddings }
anchors.top: systemInjectorGainContainer.bottom;
anchors.topMargin: 10;
}
}
}

View file

@ -16,7 +16,7 @@ import stylesUit 1.0
TabButton {
id: control
font.pixelSize: height / 2
font.pixelSize: 14
HifiConstants { id: hifi; }

View file

@ -12,24 +12,26 @@
import QtQuick 2.5
import QtGraphicalEffects 1.0
Rectangle {
Item {
property var peak;
width: 70;
height: 8;
color: "transparent";
Item {
QtObject {
id: colors;
readonly property string unmuted: "#FFF";
readonly property string muted: "#E2334D";
readonly property string gutter: "#575757";
readonly property string greenStart: "#39A38F";
readonly property string greenEnd: "#1FC6A6";
readonly property string yellow: "#C0C000";
readonly property string red: colors.muted;
readonly property string fill: "#55000000";
}
Text {
id: status;
@ -79,23 +81,19 @@ Rectangle {
anchors { fill: mask }
source: mask
start: Qt.point(0, 0);
end: Qt.point(70, 0);
end: Qt.point(bar.width, 0);
gradient: Gradient {
GradientStop {
position: 0;
color: colors.greenStart;
}
GradientStop {
position: 0.8;
position: 0.5;
color: colors.greenEnd;
}
GradientStop {
position: 0.801;
color: colors.red;
}
GradientStop {
position: 1;
color: colors.red;
color: colors.yellow;
}
}
}

View file

@ -17,17 +17,17 @@ import stylesUit 1.0
import controlsUit 1.0 as HifiControlsUit
RowLayout {
property bool audioLoopedBack: AudioScriptingInterface.getServerEcho();
property bool audioLoopedBack: AudioScriptingInterface.getLocalEcho();
function startAudioLoopback() {
if (!audioLoopedBack) {
audioLoopedBack = true;
AudioScriptingInterface.setServerEcho(true);
AudioScriptingInterface.setLocalEcho(true);
}
}
function stopAudioLoopback() {
if (audioLoopedBack) {
audioLoopedBack = false;
AudioScriptingInterface.setServerEcho(false);
AudioScriptingInterface.setLocalEcho(false);
}
}
@ -44,8 +44,11 @@ RowLayout {
}
HifiControlsUit.Button {
text: audioLoopedBack ? qsTr("STOP TESTING YOUR VOICE") : qsTr("TEST YOUR VOICE");
text: audioLoopedBack ? qsTr("STOP TESTING VOICE") : qsTr("TEST YOUR VOICE");
color: audioLoopedBack ? hifi.buttons.red : hifi.buttons.blue;
fontSize: 15;
width: 200;
height: 32;
onClicked: {
if (audioLoopedBack) {
loopbackTimer.stop();
@ -59,7 +62,7 @@ RowLayout {
RalewayRegular {
Layout.leftMargin: 2;
size: 14;
size: 18;
color: "white";
font.italic: true
text: audioLoopedBack ? qsTr("Speak in your input") : "";

View file

@ -11,18 +11,40 @@
import QtQuick 2.5
import QtGraphicalEffects 1.0
import stylesUit 1.0
import TabletScriptingInterface 1.0
Rectangle {
id: micBar
HifiConstants { id: hifi; }
property var muted: AudioScriptingInterface.muted;
readonly property var level: AudioScriptingInterface.inputLevel;
readonly property var clipping: AudioScriptingInterface.clipping;
property var pushToTalk: AudioScriptingInterface.pushToTalk;
property var pushingToTalk: AudioScriptingInterface.pushingToTalk;
readonly property var userSpeakingLevel: 0.4;
property bool gated: false;
Component.onCompleted: {
AudioScriptingInterface.noiseGateOpened.connect(function() { gated = false; });
AudioScriptingInterface.noiseGateClosed.connect(function() { gated = true; });
HMD.displayModeChanged.connect(function() {
muted = AudioScriptingInterface.muted;
pushToTalk = AudioScriptingInterface.pushToTalk;
});
AudioScriptingInterface.mutedChanged.connect(function() {
muted = AudioScriptingInterface.muted;
});
AudioScriptingInterface.pushToTalkChanged.connect(function() {
pushToTalk = AudioScriptingInterface.pushToTalk;
});
AudioScriptingInterface.pushingToTalkChanged.connect(function() {
pushingToTalk = AudioScriptingInterface.pushingToTalk;
});
}
property bool standalone: false;
property var dragTarget: null;
@ -64,7 +86,10 @@ Rectangle {
hoverEnabled: true;
scrollGestureEnabled: false;
onClicked: {
AudioScriptingInterface.muted = !AudioScriptingInterface.muted;
if (pushToTalk) {
return;
}
AudioScriptingInterface.muted = !muted;
Tablet.playSound(TabletEnums.ButtonClick);
}
drag.target: dragTarget;
@ -78,16 +103,16 @@ Rectangle {
QtObject {
id: colors;
readonly property string unmuted: "#FFF";
readonly property string muted: "#E2334D";
readonly property string unmutedColor: "#FFF";
readonly property string mutedColor: "#E2334D";
readonly property string gutter: "#575757";
readonly property string greenStart: "#39A38F";
readonly property string greenEnd: "#1FC6A6";
readonly property string yellow: "#C0C000";
readonly property string red: colors.muted;
readonly property string red: colors.mutedColor;
readonly property string fill: "#55000000";
readonly property string border: standalone ? "#80FFFFFF" : "#55FFFFFF";
readonly property string icon: AudioScriptingInterface.muted ? muted : unmuted;
readonly property string icon: muted ? colors.mutedColor : unmutedColor;
}
Item {
@ -106,9 +131,13 @@ 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";
readonly property string clippingIcon: "../../../icons/tablet-icons/mic-clip-i.svg";
readonly property string gatedIcon: "../../../icons/tablet-icons/mic-gate-i.svg";
id: image;
source: AudioScriptingInterface.muted ? mutedIcon : unmutedIcon;
source: (pushToTalk && !pushingToTalk) ? pushToTalkIcon : muted ? mutedIcon :
clipping ? clippingIcon : gated ? gatedIcon : unmutedIcon;
width: 30;
height: 30;
@ -131,9 +160,7 @@ Rectangle {
Item {
id: status;
readonly property string color: AudioScriptingInterface.muted ? colors.muted : colors.unmuted;
visible: AudioScriptingInterface.muted;
visible: (pushToTalk && !pushingToTalk) || muted;
anchors {
left: parent.left;
@ -150,9 +177,9 @@ Rectangle {
verticalCenter: parent.verticalCenter;
}
color: parent.color;
color: colors.icon;
text: AudioScriptingInterface.muted ? "MUTED" : "MUTE";
text: (pushToTalk && !pushingToTalk) ? (HMD.active ? "MUTED PTT" : "MUTED PTT-(T)") : (muted ? "MUTED" : "MUTE");
font.pointSize: 12;
}
@ -162,9 +189,9 @@ Rectangle {
verticalCenter: parent.verticalCenter;
}
width: 50;
width: pushToTalk && !pushingToTalk ? (HMD.active ? 27 : 25) : 50;
height: 4;
color: parent.color;
color: colors.icon;
}
Rectangle {
@ -173,9 +200,9 @@ Rectangle {
verticalCenter: parent.verticalCenter;
}
width: 50;
width: pushToTalk && !pushingToTalk ? (HMD.active ? 27 : 25) : 50;
height: 4;
color: parent.color;
color: colors.icon;
}
}
@ -228,12 +255,12 @@ Rectangle {
}
}
}
Rectangle {
id: gatedIndicator;
visible: gated && !AudioScriptingInterface.clipping
radius: 4;
radius: 4;
width: 2 * radius;
height: 2 * radius;
color: "#0080FF";
@ -242,12 +269,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

@ -0,0 +1,255 @@
//
// MicBarApplication.qml
// qml/hifi/audio
//
// Created by Zach Pomerantz on 6/14/2017
// Copyright 2017 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 QtGraphicalEffects 1.0
import stylesUit 1.0
import TabletScriptingInterface 1.0
Rectangle {
id: micBar;
readonly property var level: AudioScriptingInterface.inputLevel;
readonly property var clipping: AudioScriptingInterface.clipping;
property var muted: AudioScriptingInterface.muted;
property var pushToTalk: AudioScriptingInterface.pushToTalk;
property var pushingToTalk: AudioScriptingInterface.pushingToTalk;
readonly property var userSpeakingLevel: 0.4;
property bool gated: false;
Component.onCompleted: {
AudioScriptingInterface.noiseGateOpened.connect(function() { gated = false; });
AudioScriptingInterface.noiseGateClosed.connect(function() { gated = true; });
HMD.displayModeChanged.connect(function() {
muted = AudioScriptingInterface.muted;
pushToTalk = AudioScriptingInterface.pushToTalk;
});
AudioScriptingInterface.mutedChanged.connect(function() {
muted = AudioScriptingInterface.muted;
});
AudioScriptingInterface.pushToTalkChanged.connect(function() {
pushToTalk = AudioScriptingInterface.pushToTalk;
});
}
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";
readonly property string clippingIcon: "../../../icons/tablet-icons/mic-clip-i.svg";
readonly property string gatedIcon: "../../../icons/tablet-icons/mic-gate-i.svg";
property bool standalone: false;
property var dragTarget: null;
width: 44;
height: 44;
radius: 5;
opacity: 0.7;
onLevelChanged: {
var rectOpacity = (muted && (level >= userSpeakingLevel)) ? 1.0 : 0.7;
if (pushToTalk && !pushingToTalk) {
rectOpacity = (mouseArea.containsMouse) ? 1.0 : 0.7;
} else if (mouseArea.containsMouse && rectOpacity != 1.0) {
rectOpacity = 1.0;
}
micBar.opacity = rectOpacity;
}
color: "#00000000";
border {
width: mouseArea.containsMouse || mouseArea.containsPress ? 2 : 0;
color: colors.border;
}
// borders are painted over fill, so reduce the fill to fit inside the border
Rectangle {
color: standalone ? colors.fill : "#00000000";
width: 40;
height: 40;
radius: 5;
anchors {
verticalCenter: parent.verticalCenter;
horizontalCenter: parent.horizontalCenter;
}
}
MouseArea {
id: mouseArea;
anchors {
left: icon.left;
right: bar.right;
top: icon.top;
bottom: icon.bottom;
}
hoverEnabled: true;
scrollGestureEnabled: false;
onClicked: {
if (pushToTalk) {
return;
}
AudioScriptingInterface.muted = !muted;
Tablet.playSound(TabletEnums.ButtonClick);
muted = Qt.binding(function() { return AudioScriptingInterface.muted; }); // restore binding
}
drag.target: dragTarget;
onContainsMouseChanged: {
if (containsMouse) {
Tablet.playSound(TabletEnums.ButtonHover);
}
}
}
QtObject {
id: colors;
readonly property string unmutedColor: "#FFF";
readonly property string gatedColor: "#00BDFF";
readonly property string mutedColor: "#E2334D";
readonly property string gutter: "#575757";
readonly property string greenStart: "#39A38F";
readonly property string greenEnd: "#1FC6A6";
readonly property string yellow: "#C0C000";
readonly property string fill: "#55000000";
readonly property string border: standalone ? "#80FFFFFF" : "#55FFFFFF";
readonly property string icon: (muted || clipping) ? mutedColor : gated ? gatedColor : unmutedColor;
}
Item {
id: icon;
anchors {
left: parent.left;
top: parent.top;
}
width: 40;
height: 40;
Item {
Image {
id: image;
source: (pushToTalk) ? pushToTalkIcon : muted ? mutedIcon :
clipping ? clippingIcon : gated ? gatedIcon : unmutedIcon;
width: 29;
height: 32;
anchors {
left: parent.left;
top: parent.top;
topMargin: 5;
}
}
ColorOverlay {
id: imageOverlay
anchors { fill: image }
source: image;
color: pushToTalk ? (pushingToTalk ? colors.unmutedColor : colors.mutedColor) : colors.icon;
}
}
}
Item {
id: status;
visible: pushToTalk || (muted && (level >= userSpeakingLevel));
anchors {
left: parent.left;
top: icon.bottom;
topMargin: 2;
}
width: parent.width;
height: statusTextMetrics.height;
TextMetrics {
id: statusTextMetrics
text: statusText.text
font: statusText.font
}
RalewaySemiBold {
id: statusText
anchors {
horizontalCenter: parent.horizontalCenter;
verticalCenter: parent.verticalCenter;
}
color: pushToTalk ? (pushingToTalk ? colors.unmutedColor : colors.mutedColor) : (level >= userSpeakingLevel && muted) ? colors.mutedColor : colors.unmutedColor;
font.bold: true
text: pushToTalk ? (HMD.active ? "PTT" : "PTT-(T)") : (muted ? "MUTED" : "MUTE");
size: 12;
}
}
Item {
id: bar;
anchors {
right: parent.right;
rightMargin: 7;
top: parent.top
topMargin: 5
}
width: 8;
height: 32;
Rectangle { // base
id: baseBar
radius: 4;
anchors { fill: parent }
color: colors.gutter;
}
Rectangle { // mask
id: mask;
visible: (!(pushToTalk && !pushingToTalk))
height: parent.height * level;
width: parent.width;
radius: 5;
anchors {
bottom: parent.bottom;
bottomMargin: 0;
left: parent.left;
leftMargin: 0;
}
}
LinearGradient {
anchors { fill: mask }
visible: (!(pushToTalk && !pushingToTalk))
source: mask
start: Qt.point(0, 0);
end: Qt.point(0, bar.height);
rotation: 180
gradient: Gradient {
GradientStop {
position: 0.0;
color: colors.greenStart;
}
GradientStop {
position: 0.5;
color: colors.greenEnd;
}
GradientStop {
position: 1.0;
color: colors.yellow;
}
}
}
}
}

View file

@ -56,14 +56,17 @@ RowLayout {
HifiConstants { id: hifi; }
HifiControlsUit.Button {
text: isPlaying ? qsTr("STOP TESTING YOUR SOUND") : qsTr("TEST YOUR SOUND");
text: isPlaying ? qsTr("STOP TESTING") : qsTr("TEST YOUR SOUND");
color: isPlaying ? hifi.buttons.red : hifi.buttons.blue;
onClicked: isPlaying ? stopSound() : playSound();
fontSize: 15;
width: 200;
height: 32;
}
RalewayRegular {
Layout.leftMargin: 2;
size: 14;
size: 18;
color: "white";
font.italic: true
text: isPlaying ? qsTr("Listen to your output") : "";

View file

@ -133,7 +133,7 @@ Item {
states: [
State {
name: AvatarPackagerState.main
PropertyChanges { target: avatarPackagerHeader; title: qsTr("Avatar Packager"); docsEnabled: true; backButtonVisible: false }
PropertyChanges { target: avatarPackagerHeader; title: qsTr("Avatar Packager"); docsEnabled: true; videoEnabled: true; backButtonVisible: false }
PropertyChanges { target: avatarPackagerMain; visible: true }
PropertyChanges { target: avatarPackagerFooter; content: avatarPackagerMain.footer }
},
@ -229,7 +229,11 @@ Item {
}
function openDocs() {
Qt.openUrlExternally("https://docs.highfidelity.com/create/avatars/create-avatars#how-to-package-your-avatar");
Qt.openUrlExternally("https://docs.highfidelity.com/create/avatars/package-avatar.html");
}
function openVideo() {
Qt.openUrlExternally("https://youtu.be/zrkEowu_yps");
}
AvatarPackagerHeader {
@ -243,6 +247,9 @@ Item {
onDocsButtonClicked: {
avatarPackager.openDocs();
}
onVideoButtonClicked: {
avatarPackager.openVideo();
}
}
Item {

View file

@ -13,6 +13,7 @@ ShadowRectangle {
property string title: qsTr("Avatar Packager")
property alias docsEnabled: docs.visible
property alias videoEnabled: video.visible
property bool backButtonVisible: true // If false, is not visible and does not take up space
property bool backButtonEnabled: true // If false, is not visible but does not affect space
property bool canRename: false
@ -24,6 +25,7 @@ ShadowRectangle {
signal backButtonClicked
signal docsButtonClicked
signal videoButtonClicked
RalewayButton {
id: back
@ -126,6 +128,20 @@ ShadowRectangle {
}
}
RalewayButton {
id: video
visible: false
size: 28
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: docs.left
anchors.rightMargin: 16
text: qsTr("Video")
onClicked: videoButtonClicked()
}
RalewayButton {
id: docs
visible: false
@ -137,8 +153,6 @@ ShadowRectangle {
text: qsTr("Docs")
onClicked: {
docsButtonClicked();
}
onClicked: docsButtonClicked()
}
}

View file

@ -339,8 +339,8 @@ Item {
visible: AvatarPackagerCore.currentAvatarProject && AvatarPackagerCore.currentAvatarProject.hasErrors
anchors {
top: notForSaleMessage.bottom
topMargin: 16
top: notForSaleMessage.visible ? notForSaleMessage.bottom : infoMessage .bottom
bottom: showFilesText.top
horizontalCenter: parent.horizontalCenter
}

View file

@ -113,6 +113,7 @@ Rectangle {
} else if (prop === 'dimensions') {
scalespinner.set(wearable[prop].x / wearable.naturalDimensions.x);
}
modified = true;
}
}

View file

@ -71,6 +71,7 @@ Item {
onBalanceResult : {
balanceText.text = result.data.balance;
sendButton.enabled = true;
}
onTransferAssetToNodeResult: {
@ -1371,6 +1372,7 @@ Item {
height: 40;
width: 100;
text: "SUBMIT";
enabled: false;
onClicked: {
if (root.assetCertID === "" && parseInt(amountTextField.text) > parseInt(balanceText.text)) {
amountTextField.focus = true;
@ -2246,6 +2248,7 @@ Item {
if (sendAssetStep.selectedRecipientUserName === "") {
console.log("SendAsset: Script didn't specify a recipient username!");
sendAssetHome.visible = false;
root.nextActiveView = 'paymentFailure';
return;
}

View file

@ -87,22 +87,11 @@ Rectangle {
console.log("Failed to get Marketplace Categories", result.data.message);
} else {
categoriesModel.clear();
categoriesModel.append({
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) {
result.data.categories.forEach(function(category) {
categoriesModel.append({
id: category.id,
name: category.name
name: category.name,
count: category.count
});
});
}
@ -359,9 +348,11 @@ Rectangle {
}
onAccepted: {
root.searchString = searchField.text;
getMarketplaceItems();
searchField.forceActiveFocus();
if (root.searchString !== searchField.text) {
root.searchString = searchField.text;
getMarketplaceItems();
searchField.forceActiveFocus();
}
}
onActiveFocusChanged: {
@ -382,6 +373,7 @@ Rectangle {
id: categoriesDropdown
anchors.fill: parent;
anchors.topMargin: 2
visible: false
z: 10
@ -396,12 +388,12 @@ Rectangle {
Rectangle {
anchors {
left: parent.left;
bottom: parent.bottom;
top: parent.top;
topMargin: 100;
left: parent.left
bottom: parent.bottom
top: parent.top
topMargin: 100
}
width: parent.width/3
width: parent.width*2/3
color: hifi.colors.white
@ -420,6 +412,7 @@ Rectangle {
model: categoriesModel
delegate: ItemDelegate {
id: categoriesItemDelegate
height: 34
width: parent.width
@ -431,34 +424,71 @@ Rectangle {
color: hifi.colors.white
visible: true
border.color: hifi.colors.blueHighlight
border.width: 0
RalewayRegular {
RalewaySemiBold {
id: categoriesItemText
anchors.leftMargin: 15
anchors.fill:parent
anchors.top:parent.top
anchors.bottom: parent.bottom
anchors.left: categoryItemCount.right
elide: Text.ElideRight
text: model.name
color: ListView.isCurrentItem ? hifi.colors.lightBlueHighlight : hifi.colors.baseGray
color: categoriesItemDelegate.ListView.isCurrentItem ? hifi.colors.blueHighlight : hifi.colors.baseGray
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
size: 14
}
Rectangle {
id: categoryItemCount
anchors {
top: parent.top
bottom: parent.bottom
topMargin: 7
bottomMargin: 7
leftMargin: 10
rightMargin: 10
left: parent.left
}
width: childrenRect.width
color: hifi.colors.faintGray
radius: height/2
RalewaySemiBold {
anchors.top: parent.top
anchors.bottom: parent.bottom
width: 50
text: model.count
color: hifi.colors.lightGrayText
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
size: 16
}
}
}
MouseArea {
anchors.fill: parent
z: 10
hoverEnabled: true
propagateComposedEvents: false
onEntered: {
categoriesItem.color = ListView.isCurrentItem ? hifi.colors.white : hifi.colors.lightBlueHighlight;
onPositionChanged: {
// Must use onPositionChanged and not onEntered
// due to a QML bug where a mouseenter event was
// being fired on open of the categories list even
// though the mouse was outside the borders
categoriesItem.border.width = 2;
}
onExited: {
categoriesItem.border.width = 0;
}
onExited: {
categoriesItem.color = ListView.isCurrentItem ? hifi.colors.lightBlueHighlight : hifi.colors.white;
onCanceled: {
categoriesItem.border.width = 0;
}
onClicked: {
@ -476,9 +506,9 @@ Rectangle {
parent: categoriesListView.parent
anchors {
top: categoriesListView.top;
bottom: categoriesListView.bottom;
left: categoriesListView.right;
top: categoriesListView.top
bottom: categoriesListView.bottom
left: categoriesListView.right
}
contentItem.opacity: 1
@ -559,6 +589,8 @@ Rectangle {
standaloneOptimized: model.standalone_optimized
onShowItem: {
// reset the edition back to -1 to clear the 'update item' status
marketplaceItem.edition = -1;
MarketplaceScriptingInterface.getMarketplaceItem(item_id);
}
@ -632,7 +664,7 @@ Rectangle {
text: "LOG IN"
onClicked: {
sendToScript({method: 'needsLogIn_loginClicked'});
sendToScript({method: 'marketplace_loginClicked'});
}
}

View file

@ -298,7 +298,7 @@ Rectangle {
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))
text: isUpdate ? "UPDATE" : (isStocking ? "FREE STOCK" : (enabled ? (price || "FREE") : availability))
color: hifi.buttons.blue
buttonGlyphSize: 24

View file

@ -49,7 +49,7 @@ Item {
property string wornEntityID;
property string updatedItemId;
property string upgradeTitle;
property bool updateAvailable: root.updateItemId && root.updateItemId !== "";
property bool updateAvailable: root.updateItemId !== "";
property bool valid;
property bool standaloneOptimized;
property bool standaloneIncompatible;

View file

@ -523,9 +523,9 @@ Rectangle {
item.cardBackVisible = false;
item.isInstalled = root.installedApps.indexOf(item.id) > -1;
item.wornEntityID = '';
item.upgrade_id = item.upgrade_id ? item.upgrade_id : "";
});
sendToScript({ method: 'purchases_updateWearables' });
return data.assets;
}
}
@ -545,7 +545,7 @@ Rectangle {
delegate: PurchasedItem {
itemName: title;
itemId: id;
updateItemId: model.upgrade_id ? model.upgrade_id : "";
updateItemId: model.upgrade_id
itemPreviewImageUrl: preview;
itemHref: download_url;
certificateId: certificate_id;

View file

@ -32,6 +32,7 @@ Rectangle {
property string initialActiveViewAfterStatus5: "walletInventory";
property bool keyboardRaised: false;
property bool isPassword: false;
property bool has3DHTML: PlatformInfo.has3DHTML();
anchors.fill: (typeof parent === undefined) ? undefined : parent;
@ -335,8 +336,10 @@ Rectangle {
Connections {
onSendSignalToWallet: {
if (msg.method === 'transactionHistory_usernameLinkClicked') {
userInfoViewer.url = msg.usernameLink;
userInfoViewer.visible = true;
if (has3DHTML) {
userInfoViewer.url = msg.usernameLink;
userInfoViewer.visible = true;
}
} else {
sendToScript(msg);
}

View file

@ -24,6 +24,8 @@ Item {
HifiConstants { id: hifi; }
id: root;
property bool has3DHTML: PlatformInfo.has3DHTML();
onVisibleChanged: {
if (visible) {
@ -333,7 +335,9 @@ Item {
onLinkActivated: {
if (link.indexOf("users/") !== -1) {
sendSignalToWallet({method: 'transactionHistory_usernameLinkClicked', usernameLink: link});
if (has3DHTML) {
sendSignalToWallet({method: 'transactionHistory_usernameLinkClicked', usernameLink: link});
}
} else {
sendSignalToWallet({method: 'transactionHistory_linkClicked', itemId: model.marketplace_item});
}

View file

@ -1,27 +0,0 @@
//
// Audio.qml
//
// Created by Zach Pomerantz on 6/12/2017
// Copyright 2017 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 "../../windows"
import "../audio"
ScrollingWindow {
id: root;
resizable: true;
destroyOnHidden: true;
width: 400;
height: 577;
minSize: Qt.vector2d(400, 500);
Audio { id: audio; width: root.width }
objectName: "AudioDialog";
title: audio.title;
}

View file

@ -35,6 +35,7 @@ StackView {
property int cardWidth: 212;
property int cardHeight: 152;
property var tablet: null;
property bool has3DHTML: PlatformInfo.has3DHTML();
RootHttpRequest { id: http; }
signal sendToScript(var message);
@ -75,8 +76,10 @@ StackView {
}
function goCard(targetString, standaloneOptimized) {
if (0 !== targetString.indexOf('hifi://')) {
var card = tabletWebView.createObject();
card.url = addressBarDialog.metaverseServerUrl + targetString;
if(has3DHTML) {
var card = tabletWebView.createObject();
card.url = addressBarDialog.metaverseServerUrl + targetString;
}
card.parentStackItem = root;
root.push(card);
return;

View file

@ -40,7 +40,7 @@ Item {
}
}
HifiAudio.MicBar {
HifiAudio.MicBarApplication {
anchors {
left: parent.left
leftMargin: 30

View file

@ -117,7 +117,6 @@ Rectangle {
if (loader.item.hasOwnProperty("gotoPreviousApp")) {
loader.item.gotoPreviousApp = true;
}
screenChanged("Web", url)
});
}

View file

@ -9,7 +9,7 @@
//
import QtQuick 2.7
import Qt.labs.folderlistmodel 2.1
import Qt.labs.folderlistmodel 2.2
import Qt.labs.settings 1.0
import QtQuick.Dialogs 1.2 as OriginalDialogs
import QtQuick.Controls 1.4 as QQC1
@ -279,6 +279,7 @@ Rectangle {
FolderListModel {
id: folderListModel
nameFilters: selectionType.currentFilter
caseSensitive: false
showDirsFirst: true
showDotAndDotDot: false
showFiles: !root.selectDirectory

View file

@ -344,6 +344,7 @@ Item {
readonly property string stop_square: "\ue01e"
readonly property string avatarTPose: "\ue01f"
readonly property string lock: "\ue006"
readonly property string unlock: "\ue039"
readonly property string checkmark: "\ue020"
readonly property string leftRightArrows: "\ue021"
readonly property string hfc: "\ue022"

View file

@ -330,6 +330,7 @@ QtObject {
readonly property string stop_square: "\ue01e"
readonly property string avatarTPose: "\ue01f"
readonly property string lock: "\ue006"
readonly property string unlock: "\ue039"
readonly property string checkmark: "\ue020"
readonly property string leftRightArrows: "\ue021"
readonly property string hfc: "\ue022"

View file

@ -338,6 +338,10 @@ Setting::Handle<int> maxOctreePacketsPerSecond{"maxOctreePPS", DEFAULT_MAX_OCTRE
Setting::Handle<bool> loginDialogPoppedUp{"loginDialogPoppedUp", false};
static const QUrl AVATAR_INPUTS_BAR_QML = PathUtils::qmlUrl("AvatarInputsBar.qml");
static const QUrl MIC_BAR_APPLICATION_QML = PathUtils::qmlUrl("hifi/audio/MicBarApplication.qml");
static const QUrl BUBBLE_ICON_QML = PathUtils::qmlUrl("BubbleIcon.qml");
static const QString STANDARD_TO_ACTION_MAPPING_NAME = "Standard to Action";
static const QString NO_MOVEMENT_MAPPING_NAME = "Standard to Action (No Movement)";
static const QString NO_MOVEMENT_MAPPING_JSON = PathUtils::resourcesPath() + "/controllers/standard_nomovement.json";
@ -1206,10 +1210,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
connect(&domainHandler, SIGNAL(connectedToDomain(QUrl)), SLOT(updateWindowTitle()));
connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle()));
connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, [this]() {
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
if (tabletScriptingInterface) {
tabletScriptingInterface->setQmlTabletRoot(SYSTEM_TABLET, nullptr);
}
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
entityScriptingInterface->deleteEntity(getTabletScreenID());
entityScriptingInterface->deleteEntity(getTabletHomeButtonID());
@ -1599,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) {
@ -1976,6 +1985,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
return nullptr;
});
EntityTree::setEmitScriptEventOperator([this](const QUuid& id, const QVariant& message) {
auto entities = getEntities();
if (auto entity = entities->renderableForEntityId(id)) {
entity->emitScriptEvent(message);
}
});
EntityTree::setTextSizeOperator([this](const QUuid& id, const QString& text) {
auto entities = getEntities();
if (auto entity = entities->renderableForEntityId(id)) {
@ -2333,6 +2349,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
return viewFrustum.getPosition();
});
DependencyManager::get<UsersScriptingInterface>()->setKickConfirmationOperator([this] (const QUuid& nodeID) { userKickConfirmation(nodeID); });
render::entities::WebEntityRenderer::setAcquireWebSurfaceOperator([this](const QString& url, bool htmlContent, QSharedPointer<OffscreenQmlSurface>& webSurface, bool& cachedWebSurface) {
bool isTablet = url == TabletScriptingInterface::QML;
if (htmlContent) {
@ -2358,7 +2376,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
});
});
auto rootItemLoadedFunctor = [webSurface, url, isTablet] {
Application::setupQmlSurface(webSurface->getSurfaceContext(), isTablet || url == LOGIN_DIALOG.toString());
Application::setupQmlSurface(webSurface->getSurfaceContext(), isTablet || url == LOGIN_DIALOG.toString() || url == AVATAR_INPUTS_BAR_QML.toString() ||
url == BUBBLE_ICON_QML.toString());
};
if (webSurface->getRootItem()) {
rootItemLoadedFunctor();
@ -2695,9 +2714,7 @@ void Application::cleanupBeforeQuit() {
DependencyManager::destroy<OffscreenQmlSurfaceCache>();
if (_snapshotSoundInjector != nullptr) {
_snapshotSoundInjector->stop();
}
_snapshotSoundInjector = nullptr;
// destroy Audio so it and its threads have a chance to go down safely
// this must happen after QML, as there are unexplained audio crashes originating in qtwebengine
@ -2866,11 +2883,19 @@ void Application::initializeGL() {
}
#if !defined(DISABLE_QML)
QStringList chromiumFlags;
// Bug 21993: disable microphone and camera input
chromiumFlags << "--use-fake-device-for-media-stream";
// Disable signed distance field font rendering on ATI/AMD GPUs, due to
// https://highfidelity.manuscript.com/f/cases/13677/Text-showing-up-white-on-Marketplace-app
std::string vendor{ (const char*)glGetString(GL_VENDOR) };
if ((vendor.find("AMD") != std::string::npos) || (vendor.find("ATI") != std::string::npos)) {
qputenv("QTWEBENGINE_CHROMIUM_FLAGS", QByteArray("--disable-distance-field-text"));
chromiumFlags << "--disable-distance-field-text";
}
// Ensure all Qt webengine processes launched from us have the appropriate command line flags
if (!chromiumFlags.empty()) {
qputenv("QTWEBENGINE_CHROMIUM_FLAGS", chromiumFlags.join(' ').toLocal8Bit());
}
#endif
@ -3034,6 +3059,9 @@ void Application::initializeUi() {
QUrl{ "hifi/commerce/wallet/Wallet.qml" },
QUrl{ "hifi/commerce/wallet/WalletHome.qml" },
QUrl{ "hifi/tablet/TabletAddressDialog.qml" },
QUrl{ "hifi/Card.qml" },
QUrl{ "hifi/Pal.qml" },
QUrl{ "hifi/NameCard.qml" },
}, platformInfoCallback);
QmlContextCallback ttsCallback = [](QQmlContext* context) {
@ -3276,6 +3304,41 @@ void Application::onDesktopRootItemCreated(QQuickItem* rootItem) {
auto qml = PathUtils::qmlUrl("AvatarInputsBar.qml");
offscreenUi->show(qml, "AvatarInputsBar");
#endif
_desktopRootItemCreated = true;
}
void Application::userKickConfirmation(const QUuid& nodeID) {
auto avatarHashMap = DependencyManager::get<AvatarHashMap>();
auto avatar = avatarHashMap->getAvatarBySessionID(nodeID);
QString userName;
if (avatar) {
userName = avatar->getSessionDisplayName();
} else {
userName = nodeID.toString();
}
QString kickMessage = "Do you wish to kick " + userName + " from your domain";
ModalDialogListener* dlg = OffscreenUi::asyncQuestion("Kick User", kickMessage,
QMessageBox::Yes | QMessageBox::No);
if (dlg->getDialogItem()) {
QObject::connect(dlg, &ModalDialogListener::response, this, [=] (QVariant answer) {
QObject::disconnect(dlg, &ModalDialogListener::response, this, nullptr);
bool yes = (static_cast<QMessageBox::StandardButton>(answer.toInt()) == QMessageBox::Yes);
// ask the NodeList to kick the user with the given session ID
if (yes) {
DependencyManager::get<NodeList>()->kickNodeBySessionID(nodeID);
}
DependencyManager::get<UsersScriptingInterface>()->setWaitForKickResponse(false);
});
DependencyManager::get<UsersScriptingInterface>()->setWaitForKickResponse(true);
}
}
void Application::setupQmlSurface(QQmlContext* surfaceContext, bool setAdditionalContextProperties) {
@ -3673,14 +3736,11 @@ 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;
#endif
_firstRun.set(false);
} else {
#if !defined(Q_OS_ANDROID)
QString goingTo = "";
if (addressLookupString.isEmpty()) {
if (Menu::getInstance()->isOptionChecked(MenuOption::HomeLocation)) {
@ -3694,7 +3754,6 @@ void Application::handleSandboxStatus(QNetworkReply* reply) {
qCDebug(interfaceapp) << "Not first run... going to" << qPrintable(!goingTo.isEmpty() ? goingTo : addressLookupString);
DependencyManager::get<AddressManager>()->loadSettings(addressLookupString);
sentTo = SENT_TO_PREVIOUS_LOCATION;
#endif
}
UserActivityLogger::getInstance().logAction("startup_sent_to", {
@ -4216,10 +4275,9 @@ void Application::keyPressEvent(QKeyEvent* event) {
Setting::Handle<bool> notificationSoundSnapshot{ MenuOption::NotificationSoundsSnapshot, true };
if (notificationSounds.get() && notificationSoundSnapshot.get()) {
if (_snapshotSoundInjector) {
_snapshotSoundInjector->setOptions(options);
_snapshotSoundInjector->restart();
DependencyManager::get<AudioInjectorManager>()->setOptionsAndRestart(_snapshotSoundInjector, options);
} else {
_snapshotSoundInjector = AudioInjector::playSound(_snapshotSound, options);
_snapshotSoundInjector = DependencyManager::get<AudioInjectorManager>()->playSound(_snapshotSound, options);
}
}
takeSnapshot(true);
@ -4310,6 +4368,7 @@ void Application::keyReleaseEvent(QKeyEvent* event) {
if (_keyboardMouseDevice->isActive()) {
_keyboardMouseDevice->keyReleaseEvent(event);
}
}
void Application::focusOutEvent(QFocusEvent* event) {
@ -5241,6 +5300,9 @@ void Application::loadSettings() {
}
}
auto audioScriptingInterface = reinterpret_cast<scripting::Audio*>(DependencyManager::get<AudioScriptingInterface>().data());
audioScriptingInterface->loadData();
getMyAvatar()->loadData();
_settingsLoaded = true;
}
@ -5250,6 +5312,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();
@ -5756,6 +5821,7 @@ void Application::reloadResourceCaches() {
queryOctree(NodeType::EntityServer, PacketType::EntityQuery);
getMyAvatar()->prepareAvatarEntityDataForReload();
// Clear the entities and their renderables
getEntities()->clear();
@ -6931,9 +6997,6 @@ void Application::updateWindowTitle() const {
}
void Application::clearDomainOctreeDetails(bool clearAll) {
// before we delete all entities get MyAvatar's AvatarEntityData ready
getMyAvatar()->prepareAvatarEntityDataForReload();
// if we're about to quit, we really don't need to do the rest of these things...
if (_aboutToQuit) {
return;
@ -8919,6 +8982,38 @@ void Application::updateLoginDialogPosition() {
}
}
void Application::createAvatarInputsBar() {
const glm::vec3 LOCAL_POSITION { 0.0, 0.0, -1.0 };
// DEFAULT_DPI / tablet scale percentage
const float DPI = 31.0f / (75.0f / 100.0f);
EntityItemProperties properties;
properties.setType(EntityTypes::Web);
properties.setName("AvatarInputsBarEntity");
properties.setSourceUrl(AVATAR_INPUTS_BAR_QML.toString());
properties.setParentID(getMyAvatar()->getSelfID());
properties.setParentJointIndex(getMyAvatar()->getJointIndex("_CAMERA_MATRIX"));
properties.setPosition(LOCAL_POSITION);
properties.setLocalRotation(Quaternions::IDENTITY);
//properties.setDimensions(LOGIN_DIMENSIONS);
properties.setPrimitiveMode(PrimitiveMode::SOLID);
properties.getGrab().setGrabbable(false);
properties.setIgnorePickIntersection(false);
properties.setAlpha(1.0f);
properties.setDPI(DPI);
properties.setVisible(true);
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
_avatarInputsBarID = entityScriptingInterface->addEntityInternal(properties, entity::HostType::LOCAL);
}
void Application::destroyAvatarInputsBar() {
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
if (!_avatarInputsBarID.isNull()) {
entityScriptingInterface->deleteEntity(_avatarInputsBarID);
}
}
bool Application::hasRiftControllers() {
return PluginUtils::isOculusTouchControllerAvailable();
}

View file

@ -330,6 +330,9 @@ public:
void createLoginDialog();
void updateLoginDialogPosition();
void createAvatarInputsBar();
void destroyAvatarInputsBar();
// Check if a headset is connected
bool hasRiftControllers();
bool hasViveControllers();
@ -593,6 +596,7 @@ private:
void toggleTabletUI(bool shouldOpen = false) const;
static void setupQmlSurface(QQmlContext* surfaceContext, bool setAdditionalContextProperties);
void userKickConfirmation(const QUuid& nodeID);
MainWindow* _window;
QElapsedTimer& _sessionRunTimer;
@ -703,12 +707,14 @@ private:
int _maxOctreePPS = DEFAULT_MAX_OCTREE_PPS;
bool _interstitialModeEnabled{ false };
bool _loginDialogPoppedUp = false;
bool _loginDialogPoppedUp{ false };
bool _desktopRootItemCreated{ false };
bool _developerMenuVisible{ false };
QString _previousAvatarSkeletonModel;
float _previousAvatarTargetScale;
CameraMode _previousCameraMode;
QUuid _loginDialogID;
QUuid _avatarInputsBarID;
LoginStateManager _loginStateManager;
quint64 _lastFaceTrackerUpdate;

View file

@ -149,6 +149,9 @@ void AvatarBookmarks::removeBookmark(const QString& bookmarkName) {
emit bookmarkDeleted(bookmarkName);
}
void AvatarBookmarks::deleteBookmark() {
}
void AvatarBookmarks::updateAvatarEntities(const QVariantList &avatarEntities) {
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
auto currentAvatarEntities = myAvatar->getAvatarEntityData();

View file

@ -76,6 +76,9 @@ protected:
void readFromFile() override;
QVariantMap getAvatarDataToBookmark();
protected slots:
void deleteBookmark() override;
private:
const QString AVATARBOOKMARKS_FILENAME = "avatarbookmarks.json";
const QString ENTRY_AVATAR_URL = "avatarUrl";

View file

@ -51,13 +51,10 @@ protected:
bool _isMenuSorted;
protected slots:
/**jsdoc
* @function AvatarBookmarks.deleteBookmark
*/
/**jsdoc
* @function LocationBookmarks.deleteBookmark
*/
void deleteBookmark();
virtual void deleteBookmark();
private:
static bool sortOrder(QAction* a, QAction* b);

View file

@ -270,10 +270,14 @@ Menu::Menu() {
// Settings > Audio...
action = addActionToQMenuAndActionHash(settingsMenu, "Audio...");
connect(action, &QAction::triggered, [] {
static const QUrl widgetUrl("hifi/dialogs/Audio.qml");
static const QUrl tabletUrl("hifi/audio/Audio.qml");
static const QString name("AudioDialog");
qApp->showDialog(widgetUrl, tabletUrl, name);
auto tablet = DependencyManager::get<TabletScriptingInterface>()->getTablet("com.highfidelity.interface.tablet.system");
auto hmd = DependencyManager::get<HMDScriptingInterface>();
tablet->pushOntoStack(tabletUrl);
if (!hmd->getShouldShowTablet()) {
hmd->toggleShouldShowTablet();
}
});
// Settings > Graphics...

View file

@ -55,7 +55,7 @@ static QStringList HAND_MAPPING_SUFFIXES = {
"HandThumb1",
};
const QUrl DEFAULT_DOCS_URL = QUrl("https://docs.highfidelity.com/create/avatars/create-avatars.html#create-your-own-avatar");
const QUrl PACKAGE_AVATAR_DOCS_BASE_URL = QUrl("https://docs.highfidelity.com/create/avatars/package-avatar.html");
AvatarDoctor::AvatarDoctor(const QUrl& avatarFSTFileUrl) :
_avatarFSTFileUrl(avatarFSTFileUrl) {
@ -85,53 +85,53 @@ void AvatarDoctor::startDiagnosing() {
const auto resourceLoaded = [this, resource](bool success) {
// MODEL
if (!success) {
_errors.push_back({ "Model file cannot be opened.", DEFAULT_DOCS_URL });
addError("Model file cannot be opened.", "missing-file");
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_DOCS_URL });
if (!avatarModel.originalURL.toLower().endsWith(".fbx")) {
addError("Unsupported avatar model format.", "unsupported-format");
emit complete(getErrors());
return;
}
// RIG
if (avatarModel.joints.isEmpty()) {
_errors.push_back({ "Avatar has no rig.", DEFAULT_DOCS_URL });
addError("Avatar has no rig.", "no-rig");
} else {
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 });
addError(tr( "Avatar has over %n bones.", "", NETWORKED_JOINTS_LIMIT), "maximum-bone-limit");
}
// Avatar does not have Hips bone mapped
if (!jointNames.contains("Hips")) {
_errors.push_back({ "Hips are not mapped.", DEFAULT_DOCS_URL });
addError("Hips are not mapped.", "hips-not-mapped");
}
if (!jointNames.contains("Spine")) {
_errors.push_back({ "Spine is not mapped.", DEFAULT_DOCS_URL });
addError("Spine is not mapped.", "spine-not-mapped");
}
if (!jointNames.contains("Spine1")) {
_errors.push_back({ "Chest (Spine1) is not mapped.", DEFAULT_DOCS_URL });
addError("Chest (Spine1) is not mapped.", "chest-not-mapped");
}
if (!jointNames.contains("Neck")) {
_errors.push_back({ "Neck is not mapped.", DEFAULT_DOCS_URL });
addError("Neck is not mapped.", "neck-not-mapped");
}
if (!jointNames.contains("Head")) {
_errors.push_back({ "Head is not mapped.", DEFAULT_DOCS_URL });
addError("Head is not mapped.", "head-not-mapped");
}
if (!jointNames.contains("LeftEye")) {
if (jointNames.contains("RightEye")) {
_errors.push_back({ "LeftEye is not mapped.", DEFAULT_DOCS_URL });
addError("LeftEye is not mapped.", "eye-not-mapped");
} else {
_errors.push_back({ "Eyes are not mapped.", DEFAULT_DOCS_URL });
addError("Eyes are not mapped.", "eye-not-mapped");
}
} else if (!jointNames.contains("RightEye")) {
_errors.push_back({ "RightEye is not mapped.", DEFAULT_DOCS_URL });
addError("RightEye is not mapped.", "eye-not-mapped");
}
const auto checkJointAsymmetry = [jointNames] (const QStringList& jointMappingSuffixes) {
@ -159,13 +159,13 @@ void AvatarDoctor::startDiagnosing() {
};
if (checkJointAsymmetry(ARM_MAPPING_SUFFIXES)) {
_errors.push_back({ "Asymmetrical arm bones.", DEFAULT_DOCS_URL });
addError("Asymmetrical arm bones.", "asymmetrical-bones");
}
if (checkJointAsymmetry(HAND_MAPPING_SUFFIXES)) {
_errors.push_back({ "Asymmetrical hand bones.", DEFAULT_DOCS_URL });
addError("Asymmetrical hand bones.", "asymmetrical-bones");
}
if (checkJointAsymmetry(LEG_MAPPING_SUFFIXES)) {
_errors.push_back({ "Asymmetrical leg bones.", DEFAULT_DOCS_URL });
addError("Asymmetrical leg bones.", "asymmetrical-bones");
}
// Multiple skeleton root joints checkup
@ -177,7 +177,7 @@ void AvatarDoctor::startDiagnosing() {
}
if (skeletonRootJoints > 1) {
_errors.push_back({ "Multiple top-level joints found.", DEFAULT_DOCS_URL });
addError("Multiple top-level joints found.", "multiple-top-level-joints");
}
Rig rig;
@ -191,9 +191,9 @@ void AvatarDoctor::startDiagnosing() {
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 });
addError("Avatar is possibly too short.", "short-avatar");
} else if (avatarHeight > RECOMMENDED_MAX_HEIGHT) {
_errors.push_back({ "Avatar is possibly too tall.", DEFAULT_DOCS_URL });
addError("Avatar is possibly too tall.", "tall-avatar");
}
// HipsNotOnGround
@ -204,7 +204,7 @@ void AvatarDoctor::startDiagnosing() {
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 });
addError("Hips are on ground.", "hips-on-ground");
}
}
}
@ -223,7 +223,7 @@ void AvatarDoctor::startDiagnosing() {
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 });
addError("Hips/Spine/Chest overlap.", "overlap-error");
}
}
}
@ -240,21 +240,21 @@ void AvatarDoctor::startDiagnosing() {
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 });
addError(tr("%1 is mapped multiple times.").arg(jointName), "mapped-multiple-times");
}
}
}
if (!isDescendantOfJointWhenJointsExist("Spine", "Hips")) {
_errors.push_back({ "Spine is not a child of Hips.", DEFAULT_DOCS_URL });
addError("Spine is not a child of Hips.", "spine-not-child");
}
if (!isDescendantOfJointWhenJointsExist("Spine1", "Spine")) {
_errors.push_back({ "Spine1 is not a child of Spine.", DEFAULT_DOCS_URL });
addError("Spine1 is not a child of Spine.", "spine1-not-child");
}
if (!isDescendantOfJointWhenJointsExist("Head", "Spine1")) {
_errors.push_back({ "Head is not a child of Spine1.", DEFAULT_DOCS_URL });
addError("Head is not a child of Spine1.", "head-not-child");
}
}
@ -300,7 +300,7 @@ void AvatarDoctor::startDiagnosing() {
connect(resource.data(), &GeometryResource::finished, this, resourceLoaded);
}
} else {
_errors.push_back({ "Model file cannot be opened", DEFAULT_DOCS_URL });
addError("Model file cannot be opened", "missing-file");
emit complete(getErrors());
}
}
@ -345,7 +345,7 @@ void AvatarDoctor::diagnoseTextures() {
QUrl(avatarModel.originalURL)).resolved(QUrl("textures"));
if (texturesFound == 0) {
_errors.push_back({ tr("No textures assigned."), DEFAULT_DOCS_URL });
addError(tr("No textures assigned."), "no-textures-assigned");
}
if (!externalTextures.empty()) {
@ -356,11 +356,10 @@ void AvatarDoctor::diagnoseTextures() {
auto checkTextureLoadingComplete = [this]() mutable {
if (_checkedTextureCount == _externalTextureCount) {
if (_missingTextureCount > 0) {
_errors.push_back({ tr("Missing %n texture(s).","", _missingTextureCount), DEFAULT_DOCS_URL });
addError(tr("Missing %n texture(s).","", _missingTextureCount), "missing-textures");
}
if (_unsupportedTextureCount > 0) {
_errors.push_back({ tr("%n unsupported texture(s) found.", "", _unsupportedTextureCount),
DEFAULT_DOCS_URL });
addError(tr("%n unsupported texture(s) found.", "", _unsupportedTextureCount), "unsupported-textures");
}
emit complete(getErrors());
@ -411,6 +410,12 @@ void AvatarDoctor::diagnoseTextures() {
}
}
void AvatarDoctor::addError(const QString& errorMessage, const QString& docFragment) {
QUrl documentationURL = PACKAGE_AVATAR_DOCS_BASE_URL;
documentationURL.setFragment(docFragment);
_errors.push_back({ errorMessage, documentationURL });
}
QVariantList AvatarDoctor::getErrors() const {
QVariantList result;
for (const auto& error : _errors) {

View file

@ -40,6 +40,8 @@ signals:
private:
void diagnoseTextures();
void addError(const QString& errorMessage, const QString& docFragment);
QUrl _avatarFSTFileUrl;
QVector<AvatarDiagnosticResult> _errors;

View file

@ -38,6 +38,7 @@
#include <UsersScriptingInterface.h>
#include <UUID.h>
#include <shared/ConicalViewFrustum.h>
#include <ui/AvatarInputs.h>
#include "Application.h"
#include "InterfaceLogging.h"
@ -84,7 +85,6 @@ AvatarManager::AvatarManager(QObject* parent) :
AvatarSharedPointer AvatarManager::addAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer) {
AvatarSharedPointer avatar = AvatarHashMap::addAvatar(sessionUUID, mixerWeakPointer);
const auto otherAvatar = std::static_pointer_cast<OtherAvatar>(avatar);
if (otherAvatar && _space) {
std::unique_lock<std::mutex> lock(_spaceLock);
@ -210,7 +210,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
{
// lock the hash for read to check the size
QReadLocker lock(&_hashLock);
if (_avatarHash.size() < 2 && _avatarsToFadeOut.isEmpty()) {
if (_avatarHash.size() < 2) {
return;
}
}
@ -232,96 +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);
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 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;
}
}
@ -329,17 +375,11 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
qApp->getMain3DScene()->enqueueTransaction(renderTransaction);
}
if (!_spaceProxiesToDelete.empty() && _space) {
std::unique_lock<std::mutex> lock(_spaceLock);
workloadTransaction.remove(_spaceProxiesToDelete);
_spaceProxiesToDelete.clear();
}
_space->enqueueTransaction(workloadTransaction);
_numAvatarsUpdated = numAvatarsUpdated;
_numAvatarsNotUpdated = numAVatarsNotUpdated;
simulateAvatarFades(deltaTime);
_numAvatarsNotUpdated = numAvatarsNotUpdated;
_numHeroAvatarsUpdated = numHerosUpdated;
_avatarSimulationTime = (float)(usecTimestampNow() - startTime) / (float)USECS_PER_MSEC;
}
@ -353,31 +393,6 @@ void AvatarManager::postUpdate(float deltaTime, const render::ScenePointer& scen
}
}
void AvatarManager::simulateAvatarFades(float deltaTime) {
if (_avatarsToFadeOut.empty()) {
return;
}
QReadLocker locker(&_hashLock);
QVector<AvatarSharedPointer>::iterator avatarItr = _avatarsToFadeOut.begin();
const render::ScenePointer& scene = qApp->getMain3DScene();
render::Transaction transaction;
while (avatarItr != _avatarsToFadeOut.end()) {
auto avatar = std::static_pointer_cast<Avatar>(*avatarItr);
avatar->updateFadingStatus();
if (!avatar->isFading()) {
// fading to zero is such a rare event we push a unique transaction for each
if (avatar->isInScene()) {
avatar->removeFromScene(*avatarItr, scene, transaction);
}
avatarItr = _avatarsToFadeOut.erase(avatarItr);
} else {
++avatarItr;
}
}
scene->enqueueTransaction(transaction);
}
AvatarSharedPointer AvatarManager::newSharedAvatar(const QUuid& sessionUUID) {
auto otherAvatar = new OtherAvatar(qApp->thread());
otherAvatar->setSessionUUID(sessionUUID);
@ -405,7 +420,6 @@ void AvatarManager::buildPhysicsTransaction(PhysicsEngine::Transaction& transact
transaction.objectsToRemove.push_back(mState);
}
avatar->resetDetailedMotionStates();
} else {
if (avatar->getDetailedMotionStates().size() == 0) {
avatar->createDetailedMotionStates(avatar);
@ -473,10 +487,6 @@ void AvatarManager::removeDeadAvatarEntities(const SetOfEntities& deadEntities)
void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason) {
auto avatar = std::static_pointer_cast<OtherAvatar>(removedAvatar);
{
std::unique_lock<std::mutex> lock(_spaceLock);
_spaceProxiesToDelete.push_back(avatar->getSpaceIndex());
}
AvatarHashMap::handleRemovedAvatar(avatar, removalReason);
avatar->tearDownGrabs();
@ -487,16 +497,39 @@ void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar
// it might not fire until after we create a new instance for the same remote avatar, which creates a race
// on the creation of entities for that avatar instance and the deletion of entities for this instance
avatar->removeAvatarEntitiesFromTree();
if (removalReason == KillAvatarReason::TheirAvatarEnteredYourBubble) {
if (removalReason == KillAvatarReason::TheirAvatarEnteredYourBubble || removalReason == KillAvatarReason::NoReason) {
emit AvatarInputs::getInstance()->avatarEnteredIgnoreRadius(avatar->getSessionUUID());
emit DependencyManager::get<UsersScriptingInterface>()->enteredIgnoreRadius();
workload::Transaction workloadTransaction;
workloadTransaction.remove(avatar->getSpaceIndex());
_space->enqueueTransaction(workloadTransaction);
const render::ScenePointer& scene = qApp->getMain3DScene();
render::Transaction transaction;
avatar->removeFromScene(avatar, scene, transaction);
scene->enqueueTransaction(transaction);
} else if (removalReason == KillAvatarReason::AvatarDisconnected) {
// remove from node sets, if present
DependencyManager::get<NodeList>()->removeFromIgnoreMuteSets(avatar->getSessionUUID());
DependencyManager::get<UsersScriptingInterface>()->avatarDisconnected(avatar->getSessionUUID());
avatar->fadeOut(qApp->getMain3DScene(), removalReason);
render::Transaction transaction;
auto scene = qApp->getMain3DScene();
avatar->fadeOut(transaction, removalReason);
workload::SpacePointer space = _space;
transaction.transitionFinishedOperator(avatar->getRenderItemID(), [space, avatar]() {
const render::ScenePointer& scene = qApp->getMain3DScene();
render::Transaction transaction;
avatar->removeFromScene(avatar, scene, transaction);
scene->enqueueTransaction(transaction);
workload::Transaction workloadTransaction;
workloadTransaction.remove(avatar->getSpaceIndex());
space->enqueueTransaction(workloadTransaction);
});
scene->enqueueTransaction(transaction);
}
_avatarsToFadeOut.push_back(removedAvatar);
}
void AvatarManager::clearOtherAvatars() {
@ -582,8 +615,7 @@ void AvatarManager::handleCollisionEvents(const CollisionEvents& collisionEvents
// but most avatars are roughly the same size, so let's not be so fancy yet.
const float AVATAR_STRETCH_FACTOR = 1.0f;
_collisionInjectors.remove_if(
[](const AudioInjectorPointer& injector) { return !injector || injector->isFinished(); });
_collisionInjectors.remove_if([](const AudioInjectorPointer& injector) { return !injector; });
static const int MAX_INJECTOR_COUNT = 3;
if (_collisionInjectors.size() < MAX_INJECTOR_COUNT) {
@ -593,7 +625,7 @@ void AvatarManager::handleCollisionEvents(const CollisionEvents& collisionEvents
options.volume = energyFactorOfFull;
options.pitch = 1.0f / AVATAR_STRETCH_FACTOR;
auto injector = AudioInjector::playSoundAndDelete(collisionSound, options);
auto injector = DependencyManager::get<AudioInjectorManager>()->playSound(collisionSound, options, true);
_collisionInjectors.emplace_back(injector);
}
}

View file

@ -24,7 +24,7 @@
#include <SimpleMovingAverage.h>
#include <shared/RateCounter.h>
#include <avatars-renderer/ScriptAvatar.h>
#include <AudioInjector.h>
#include <AudioInjectorManager.h>
#include <workload/Space.h>
#include <EntitySimulation.h> // for SetOfEntities
@ -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);
@ -218,8 +220,6 @@ private:
explicit AvatarManager(QObject* parent = 0);
explicit AvatarManager(const AvatarManager& other);
void simulateAvatarFades(float deltaTime);
AvatarSharedPointer newSharedAvatar(const QUuid& sessionUUID) override;
// called only from the AvatarHashMap thread - cannot be called while this thread holds the
@ -229,26 +229,25 @@ private:
KillAvatarReason removalReason = KillAvatarReason::NoReason) override;
void handleTransitAnimations(AvatarTransit::Status status);
QVector<AvatarSharedPointer> _avatarsToFadeOut;
using SetOfOtherAvatars = std::set<OtherAvatarPointer>;
SetOfOtherAvatars _avatarsToChangeInPhysics;
std::shared_ptr<MyAvatar> _myAvatar;
quint64 _lastSendAvatarDataTime = 0; // Controls MyAvatar send data rate.
std::list<AudioInjectorPointer> _collisionInjectors;
std::list<QWeakPointer<AudioInjector>> _collisionInjectors;
RateCounter<> _myAvatarSendRate;
int _numAvatarsUpdated { 0 };
int _numAvatarsNotUpdated { 0 };
int _numHeroAvatars{ 0 };
int _numHeroAvatarsUpdated{ 0 };
float _avatarSimulationTime { 0.0f };
bool _shouldRender { true };
bool _myAvatarDataPacketsPaused { false };
mutable std::mutex _spaceLock;
workload::SpacePointer _space;
std::vector<int32_t> _spaceProxiesToDelete;
AvatarTransit::TransitConfig _transitConfig;
};

View file

@ -87,7 +87,7 @@ void MarketplaceItemUploader::doGetCategories() {
if (error == QNetworkReply::NoError) {
auto doc = QJsonDocument::fromJson(reply->readAll());
auto extractCategoryID = [&doc]() -> std::pair<bool, int> {
auto items = doc.object()["data"].toObject()["items"];
auto items = doc.object()["data"].toObject()["categories"];
if (!items.isArray()) {
qWarning() << "Categories parse error: data.items is not an array";
return { false, 0 };

View file

@ -324,8 +324,11 @@ QString MyAvatar::getDominantHand() const {
void MyAvatar::setDominantHand(const QString& hand) {
if (hand == DOMINANT_LEFT_HAND || hand == DOMINANT_RIGHT_HAND) {
_dominantHand.set(hand);
emit dominantHandChanged(hand);
bool changed = (hand != _dominantHand.get());
if (changed) {
_dominantHand.set(hand);
emit dominantHandChanged(hand);
}
}
}
@ -939,8 +942,6 @@ void MyAvatar::simulate(float deltaTime, bool inView) {
}
handleChangedAvatarEntityData();
updateFadingStatus();
}
// As far as I know no HMD system supports a play area of a kilometer in radius.
@ -1570,7 +1571,7 @@ void MyAvatar::handleChangedAvatarEntityData() {
entityTree->withWriteLock([&] {
EntityItemPointer entity = entityTree->addEntity(id, properties);
if (entity) {
packetSender->queueEditEntityMessage(PacketType::EntityAdd, entityTree, id, properties);
packetSender->queueEditAvatarEntityMessage(entityTree, id);
}
});
}
@ -2385,7 +2386,19 @@ void MyAvatar::clearWornAvatarEntities() {
}
}
/**jsdoc
* Information about an avatar entity.
* <table>
* <thead>
* <tr><th>Property</th><th>Type</th><th>Description</th></tr>
* </thead>
* <tbody>
* <tr><td><code>id</code></td><td>Uuid</td><td>Entity ID.</td></tr>
* <tr><td><code>properties</code></td><td>{@link Entities.EntityProperties}</td><td>Entity properties.</td></tr>
* </tbody>
* </table>
* @typedef {object} MyAvatar.AvatarEntityData
*/
QVariantList MyAvatar::getAvatarEntitiesVariant() {
// NOTE: this method is NOT efficient
QVariantList avatarEntitiesData;
@ -3450,6 +3463,42 @@ float MyAvatar::getGravity() {
return _characterController.getGravity();
}
void MyAvatar::setSessionUUID(const QUuid& sessionUUID) {
QUuid oldSessionID = getSessionUUID();
Avatar::setSessionUUID(sessionUUID);
QUuid newSessionID = getSessionUUID();
if (newSessionID != oldSessionID) {
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
if (entityTree) {
QList<QUuid> avatarEntityIDs;
_avatarEntitiesLock.withReadLock([&] {
avatarEntityIDs = _packedAvatarEntityData.keys();
});
bool sendPackets = !DependencyManager::get<NodeList>()->getSessionUUID().isNull();
EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender();
entityTree->withWriteLock([&] {
for (const auto& entityID : avatarEntityIDs) {
auto entity = entityTree->findEntityByID(entityID);
if (!entity) {
continue;
}
// update OwningAvatarID so entity can be identified as "ours" later
entity->setOwningAvatarID(newSessionID);
// NOTE: each attached AvatarEntity already have the correct updated parentID
// via magic in SpatiallyNestable, hence we check against newSessionID
if (sendPackets && entity->getParentID() == newSessionID) {
// but when we have a real session and the AvatarEntity is parented to MyAvatar
// we need to update the "packedAvatarEntityData" sent to the avatar-mixer
// because it contains a stale parentID somewhere deep inside
packetSender->queueEditAvatarEntityMessage(entityTree, entityID);
}
}
});
}
}
}
void MyAvatar::increaseSize() {
float minScale = getDomainMinScale();
float maxScale = getDomainMaxScale();
@ -3527,6 +3576,12 @@ void MyAvatar::clearScaleRestriction() {
_haveReceivedHeightLimitsFromDomain = false;
}
/**jsdoc
* A teleport target.
* @typedef {object} MyAvatar.GoToProperties
* @property {Vec3} position - The avatar's new position.
* @property {Quat} [orientation] - The avatar's new orientation.
*/
void MyAvatar::goToLocation(const QVariant& propertiesVar) {
qCDebug(interfaceapp, "MyAvatar QML goToLocation");
auto properties = propertiesVar.toMap();
@ -3883,6 +3938,13 @@ void MyAvatar::setCollisionWithOtherAvatarsFlags() {
_characterController.setPendingFlagsUpdateCollisionMask();
}
/**jsdoc
* A collision capsule is a cylinder with hemispherical ends. It is often used to approximate the extents of an avatar.
* @typedef {object} MyAvatar.CollisionCapsule
* @property {Vec3} start - The bottom end of the cylinder, excluding the bottom hemisphere.
* @property {Vec3} end - The top end of the cylinder, excluding the top hemisphere.
* @property {number} radius - The radius of the cylinder and the hemispheres.
*/
void MyAvatar::updateCollisionCapsuleCache() {
glm::vec3 start, end;
float radius;
@ -5332,6 +5394,24 @@ void MyAvatar::addAvatarHandsToFlow(const std::shared_ptr<Avatar>& otherAvatar)
}
}
/**jsdoc
* Physics options to use in the flow simulation of a joint.
* @typedef {object} MyAvatar.FlowPhysicsOptions
* @property {boolean} [active=true] - <code>true</code> to enable flow on the joint, otherwise <code>false</code>.
* @property {number} [radius=0.01] - The thickness of segments and knots (needed for collisions).
* @property {number} [gravity=-0.0096] - Y-value of the gravity vector.
* @property {number} [inertia=0.8] - Rotational inertia multiplier.
* @property {number} [damping=0.85] - The amount of damping on joint oscillation.
* @property {number} [stiffness=0.0] - The stiffness of each thread.
* @property {number} [delta=0.55] - Delta time for every integration step.
*/
/**jsdoc
* Collision options to use in the flow simulation of a joint.
* @typedef {object} MyAvatar.FlowCollisionsOptions
* @property {string} [type="sphere"] - Currently, only <code>"sphere"</code> is supported.
* @property {number} [radius=0.05] - Collision sphere radius.
* @property {number} [offset=Vec3.ZERO] - Offset of the collision sphere from the joint.
*/
void MyAvatar::useFlow(bool isActive, bool isCollidable, const QVariantMap& physicsConfig, const QVariantMap& collisionsConfig) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "useFlow",
@ -5381,7 +5461,7 @@ void MyAvatar::useFlow(bool isActive, bool isCollidable, const QVariantMap& phys
}
auto collisionJoints = collisionsConfig.keys();
if (collisionJoints.size() > 0) {
collisionSystem.resetCollisions();
collisionSystem.clearSelfCollisions();
for (auto &jointName : collisionJoints) {
int jointIndex = getJointIndex(jointName);
FlowCollisionSettings collisionsSettings;
@ -5396,9 +5476,43 @@ void MyAvatar::useFlow(bool isActive, bool isCollidable, const QVariantMap& phys
collisionSystem.addCollisionSphere(jointIndex, collisionsSettings);
}
}
flow.updateScale();
}
}
/**jsdoc
* Flow options currently used in flow simulation.
* @typedef {object} MyAvatar.FlowData
* @property {boolean} initialized - <code>true</code> if flow has been initialized for the current avatar, <code>false</code>
* if it hasn't.
* @property {boolean} active - <code>true</code> if flow is enabled, <code>false</code> if it isn't.
* @property {boolean} colliding - <code>true</code> if collisions are enabled, <code>false</code> if they aren't.
* @property {Object<GroupName, MyAvatar.FlowPhysicsData>} physicsData - The physics configuration for each group of joints
* that has been configured.
* @property {Object<JointName, MyAvatar.FlowCollisionsData>} collisions - The collisions configuration for each joint that
* has collisions configured.
* @property {Object<ThreadName, number[]>} threads - The threads that have been configured, with the first joint's name as the
* <code>ThreadName</code> and value as an array of the indexes of all the joints in the thread.
*/
/**jsdoc
* A set of physics options currently used in flow simulation.
* @typedef {object} MyAvatar.FlowPhysicsData
* @property {boolean} active - <code>true</code> to enable flow on the joint, otherwise <code>false</code>.
* @property {number} radius - The thickness of segments and knots. (Needed for collisions.)
* @property {number} gravity - Y-value of the gravity vector.
* @property {number} inertia - Rotational inertia multiplier.
* @property {number} damping - The amount of damping on joint oscillation.
* @property {number} stiffness - The stiffness of each thread.
* @property {number} delta - Delta time for every integration step.
* @property {number[]} jointIndices - The indexes of the joints the options are applied to.
*/
/**jsdoc
* A set of collision options currently used in flow simulation.
* @typedef {object} MyAvatar.FlowCollisionsData
* @property {number} radius - Collision sphere radius.
* @property {number} offset - Offset of the collision sphere from the joint.
* @property {number} jointIndex - The index of the joint the options are applied to.
*/
QVariantMap MyAvatar::getFlowData() {
QVariantMap result;
if (QThread::currentThread() != thread()) {
@ -5495,14 +5609,14 @@ void MyAvatar::initFlowFromFST() {
}
}
void MyAvatar::sendPacket(const QUuid& entityID, const EntityItemProperties& properties) const {
void MyAvatar::sendPacket(const QUuid& entityID) const {
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
if (entityTree) {
entityTree->withWriteLock([&] {
// force an update packet
EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender();
packetSender->queueEditEntityMessage(PacketType::EntityEdit, entityTree, entityID, properties);
packetSender->queueEditAvatarEntityMessage(entityTree, entityID);
});
}
}

File diff suppressed because it is too large Load diff

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();
}
void OtherAvatar::handleChangedAvatarEntityData() {
@ -377,7 +364,7 @@ void OtherAvatar::handleChangedAvatarEntityData() {
// AVATAR ENTITY UPDATE FLOW
// - if queueEditEntityMessage() sees "AvatarEntity" HostType it calls _myAvatar->storeAvatarEntityDataPayload()
// - storeAvatarEntityDataPayload() saves the payload and flags the trait instance for the entity as updated,
// - ClientTraitsHandler::sendChangedTraitsToMixea() sends the entity bytes to the mixer which relays them to other interfaces
// - ClientTraitsHandler::sendChangedTraitsToMixer() sends the entity bytes to the mixer which relays them to other interfaces
// - AvatarHashMap::processBulkAvatarTraits() on other interfaces calls avatar->processTraitInstance()
// - AvatarData::processTraitInstance() calls storeAvatarEntityDataPayload(), which sets _avatarEntityDataChanged = true
// - (My)Avatar::simulate() calls handleChangedAvatarEntityData() every frame which checks _avatarEntityDataChanged
@ -507,6 +494,18 @@ void OtherAvatar::handleChangedAvatarEntityData() {
const QUuid NULL_ID = QUuid("{00000000-0000-0000-0000-000000000005}");
entity->setParentID(NULL_ID);
entity->setParentID(oldParentID);
if (entity->stillHasMyGrabAction()) {
// For this case: we want to ignore transform+velocities coming from authoritative OtherAvatar
// because the MyAvatar is grabbing and we expect the local grab state
// to have enough information to prevent simulation drift.
//
// Clever readers might realize this could cause problems. For example,
// if an ignored OtherAvagtar were to simultanously grab the object then there would be
// a noticeable discrepancy between participants in the distributed physics simulation,
// however the difference would be stable and would not drift.
properties.clearTransformOrVelocityChanges();
}
if (entityTree->updateEntity(entityID, properties)) {
entity->updateLastEditedFromRemote();
} else {

View file

@ -17,6 +17,7 @@
#include <QJsonObject>
#include <DependencyManager.h>
#include <QtNetwork/QNetworkReply>
#include <EntityItemID.h>
#include "AccountManager.h"
@ -65,7 +66,7 @@ signals:
void availableUpdatesResult(QJsonObject result);
void updateItemResult(QJsonObject result);
void updateCertificateStatus(const QString& certID, uint certStatus);
void updateCertificateStatus(const EntityItemID& entityID, uint certStatus);
public slots:
void buySuccess(QNetworkReply* reply);

View file

@ -19,6 +19,7 @@
#include <QPixmap>
#include <EntityItemID.h>
#include <DependencyManager.h>
class QmlCommerce : public QObject, public Dependency {
@ -49,7 +50,7 @@ signals:
void availableUpdatesResult(QJsonObject result);
void updateItemResult(QJsonObject result);
void updateCertificateStatus(const QString& certID, uint certStatus);
void updateCertificateStatus(const EntityItemID& entityID, uint certStatus);
void transferAssetToNodeResult(QJsonObject result);
void transferAssetToUsernameResult(QJsonObject result);

View file

@ -816,18 +816,18 @@ void Wallet::handleChallengeOwnershipPacket(QSharedPointer<ReceivedMessage> pack
bool challengeOriginatedFromClient = packet->getType() == PacketType::ChallengeOwnershipRequest;
int status;
int certIDByteArraySize;
int idByteArraySize;
int textByteArraySize;
int challengingNodeUUIDByteArraySize;
packet->readPrimitive(&certIDByteArraySize);
packet->readPrimitive(&idByteArraySize);
packet->readPrimitive(&textByteArraySize); // returns a cast char*, size
if (challengeOriginatedFromClient) {
packet->readPrimitive(&challengingNodeUUIDByteArraySize);
}
// "encryptedText" is now a series of random bytes, a nonce
QByteArray certID = packet->read(certIDByteArraySize);
QByteArray id = packet->read(idByteArraySize);
QByteArray text = packet->read(textByteArraySize);
QByteArray challengingNodeUUID;
if (challengeOriginatedFromClient) {
@ -853,32 +853,32 @@ void Wallet::handleChallengeOwnershipPacket(QSharedPointer<ReceivedMessage> pack
textByteArray = sig.toUtf8();
}
textByteArraySize = textByteArray.size();
int certIDSize = certID.size();
int idSize = id.size();
// setup the packet
if (challengeOriginatedFromClient) {
auto textPacket = NLPacket::create(PacketType::ChallengeOwnershipReply,
certIDSize + textByteArraySize + challengingNodeUUIDByteArraySize + 3 * sizeof(int),
idSize + textByteArraySize + challengingNodeUUIDByteArraySize + 3 * sizeof(int),
true);
textPacket->writePrimitive(certIDSize);
textPacket->writePrimitive(idSize);
textPacket->writePrimitive(textByteArraySize);
textPacket->writePrimitive(challengingNodeUUIDByteArraySize);
textPacket->write(certID);
textPacket->write(id);
textPacket->write(textByteArray);
textPacket->write(challengingNodeUUID);
qCDebug(commerce) << "Sending ChallengeOwnershipReply Packet containing signed text" << textByteArray << "for CertID" << certID;
qCDebug(commerce) << "Sending ChallengeOwnershipReply Packet containing signed text" << textByteArray << "for id" << id;
nodeList->sendPacket(std::move(textPacket), *sendingNode);
} else {
auto textPacket = NLPacket::create(PacketType::ChallengeOwnership, certIDSize + textByteArraySize + 2 * sizeof(int), true);
auto textPacket = NLPacket::create(PacketType::ChallengeOwnership, idSize + textByteArraySize + 2 * sizeof(int), true);
textPacket->writePrimitive(certIDSize);
textPacket->writePrimitive(idSize);
textPacket->writePrimitive(textByteArraySize);
textPacket->write(certID);
textPacket->write(id);
textPacket->write(textByteArray);
qCDebug(commerce) << "Sending ChallengeOwnership Packet containing signed text" << textByteArray << "for CertID" << certID;
qCDebug(commerce) << "Sending ChallengeOwnership Packet containing signed text" << textByteArray << "for id" << id;
nodeList->sendPacket(std::move(textPacket), *sendingNode);
}

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