Merge branch 'master' of https://github.com/highfidelity/hifi into blackProp
3
.gitignore
vendored
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) – <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); }
|
||||
|
||||
|
|
|
@ -97,6 +97,7 @@ AudioMixer::AudioMixer(ReceivedMessage& message) :
|
|||
PacketType::RadiusIgnoreRequest,
|
||||
PacketType::RequestsDomainListData,
|
||||
PacketType::PerAvatarGainSet,
|
||||
PacketType::InjectorGainSet,
|
||||
PacketType::AudioSoloRequest },
|
||||
this, "queueAudioPacket");
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>();
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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']
|
||||
},
|
||||
|
|
|
@ -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" : [
|
||||
|
|
10
interface/resources/icons/tablet-icons/mic-clip-i.svg
Normal 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 |
3
interface/resources/icons/tablet-icons/mic-gate-i.svg
Normal 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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
1
interface/resources/icons/tablet-icons/mic-ptt-a.svg
Normal 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 |
24
interface/resources/icons/tablet-icons/mic-ptt-i.svg
Normal 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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
100
interface/resources/qml/BubbleIcon.qml
Normal 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";
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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>")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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>")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -28,7 +28,7 @@ TabletModalWindow {
|
|||
id: mouse;
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
|
||||
function click(button) {
|
||||
clickedButton = button;
|
||||
selected(button);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
152
interface/resources/qml/hifi/EditAvatarInputsBar.qml
Normal 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",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ import stylesUit 1.0
|
|||
|
||||
TabButton {
|
||||
id: control
|
||||
font.pixelSize: height / 2
|
||||
font.pixelSize: 14
|
||||
|
||||
HifiConstants { id: hifi; }
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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") : "";
|
||||
|
|
|
@ -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;
|
||||
|
|
255
interface/resources/qml/hifi/audio/MicBarApplication.qml
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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") : "";
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -113,6 +113,7 @@ Rectangle {
|
|||
} else if (prop === 'dimensions') {
|
||||
scalespinner.set(wearable[prop].x / wearable.naturalDimensions.x);
|
||||
}
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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'});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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});
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -40,7 +40,7 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
HifiAudio.MicBar {
|
||||
HifiAudio.MicBarApplication {
|
||||
anchors {
|
||||
left: parent.left
|
||||
leftMargin: 30
|
||||
|
|
|
@ -117,7 +117,6 @@ Rectangle {
|
|||
if (loader.item.hasOwnProperty("gotoPreviousApp")) {
|
||||
loader.item.gotoPreviousApp = true;
|
||||
}
|
||||
|
||||
screenChanged("Web", url)
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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...
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -40,6 +40,8 @@ signals:
|
|||
private:
|
||||
void diagnoseTextures();
|
||||
|
||||
void addError(const QString& errorMessage, const QString& docFragment);
|
||||
|
||||
QUrl _avatarFSTFileUrl;
|
||||
QVector<AvatarDiagnosticResult> _errors;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|