Merge branch 'master' into M21460

# Conflicts:
#	libraries/image/src/image/Image.h
This commit is contained in:
David Rowe 2019-04-05 17:50:46 +13:00
commit 28dfac64ba
267 changed files with 9532 additions and 4282 deletions

View file

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

View file

@ -33,7 +33,7 @@
#include <NodeType.h>
#include <SharedUtil.h>
#include <PathUtils.h>
#include <image/Image.h>
#include <image/TextureProcessing.h>
#include "AssetServerLogging.h"
#include "BakeAssetTask.h"

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -129,7 +129,7 @@ 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
@ -140,23 +140,13 @@ int AvatarMixerClientData::parseData(ReceivedMessage& message, const SlaveShared
// Regardless of what the client says, restore the priority as we know it without triggering any update.
_avatar->setHasPriorityWithoutTimestampReset(oldHasPriority);
auto newPosition = getPosition();
if (newPosition != oldPosition) {
//#define AVATAR_HERO_TEST_HACK
#ifdef AVATAR_HERO_TEST_HACK
{
const static QString heroKey { "HERO" };
_avatar->setPriorityAvatar(_avatar->getDisplayName().contains(heroKey));
}
#else
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;
@ -341,7 +331,7 @@ void AvatarMixerClientData::checkSkeletonURLAgainstWhitelist(const SlaveSharedDa
// the returned set traits packet uses the trait version from the incoming packet
// so the client knows they should not overwrite if they have since changed the trait
_avatar->packTrait(AvatarTraits::SkeletonModelURL, *packet, traitVersion);
AvatarTraits::packVersionedTrait(AvatarTraits::SkeletonModelURL, *packet, traitVersion, *_avatar);
auto nodeList = DependencyManager::get<NodeList>();
nodeList->sendPacket(std::move(packet), sendingNode);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -17,8 +17,8 @@ if (WIN32)
ExternalProject_Add(
${EXTERNAL_NAME}
URL https://public.highfidelity.com/dependencies/ovr_sdk_win_1.26.0_public.zip
URL_MD5 06804ff9727b910dcd04a37c800053b5
URL https://hifi-public.s3.amazonaws.com/dependencies/ovr_sdk_win_1.35.0.zip
URL_MD5 1e3e8b2101387af07ff9c841d0ea285e
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
PATCH_COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/LibOVRCMakeLists.txt" <SOURCE_DIR>/CMakeLists.txt
LOG_DOWNLOAD 1

View file

@ -53,10 +53,5 @@ macro(add_crashpad)
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy ${CRASHPAD_HANDLER_EXE_PATH} "$<TARGET_FILE_DIR:${TARGET_NAME}>/"
)
install(
PROGRAMS ${CRASHPAD_HANDLER_EXE_PATH}
DESTINATION ${INTERFACE_INSTALL_DIR}
COMPONENT ${CLIENT_COMPONENT}
)
endif ()
endmacro()

View file

@ -0,0 +1,74 @@
#
# Copyright 2015 High Fidelity, Inc.
# Created by Olivier Prat on 2019/03/26
#
# Distributed under the Apache License, Version 2.0.
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
#
macro(TARGET_OPENEXR)
if (NOT ANDROID)
set(openexr_config_file "${VCPKG_INSTALL_ROOT}/include/OpenEXR/OpenEXRConfig.h")
if(EXISTS ${openexr_config_file})
file(STRINGS
${openexr_config_file}
TMP
REGEX "#define OPENEXR_VERSION_STRING.*$")
string(REGEX MATCHALL "[0-9.]+" OPENEXR_VERSION ${TMP})
file(STRINGS
${openexr_config_file}
TMP
REGEX "#define OPENEXR_VERSION_MAJOR.*$")
string(REGEX MATCHALL "[0-9]" OPENEXR_MAJOR_VERSION ${TMP})
file(STRINGS
${openexr_config_file}
TMP
REGEX "#define OPENEXR_VERSION_MINOR.*$")
string(REGEX MATCHALL "[0-9]" OPENEXR_MINOR_VERSION ${TMP})
endif()
foreach(OPENEXR_LIB
IlmImf
IlmImfUtil
Half
Iex
IexMath
Imath
IlmThread)
# OpenEXR libraries may be suffixed with the version number, so we search
# using both versioned and unversioned names.
find_library(OPENEXR_${OPENEXR_LIB}_LIBRARY_RELEASE
NAMES
${OPENEXR_LIB}-${OPENEXR_MAJOR_VERSION}_${OPENEXR_MINOR_VERSION}_s
${OPENEXR_LIB}_s
PATHS ${VCPKG_INSTALL_ROOT}/lib NO_DEFAULT_PATH
)
#mark_as_advanced(OPENEXR_${OPENEXR_LIB}_LIBRARY)
if(OPENEXR_${OPENEXR_LIB}_LIBRARY_RELEASE)
list(APPEND OPENEXR_LIBRARY_RELEASE ${OPENEXR_${OPENEXR_LIB}_LIBRARY_RELEASE})
endif()
# OpenEXR libraries may be suffixed with the version number, so we search
# using both versioned and unversioned names.
find_library(OPENEXR_${OPENEXR_LIB}_LIBRARY_DEBUG
NAMES
${OPENEXR_LIB}-${OPENEXR_MAJOR_VERSION}_${OPENEXR_MINOR_VERSION}_s_d
${OPENEXR_LIB}_s_d
PATHS ${VCPKG_INSTALL_ROOT}/debug/lib NO_DEFAULT_PATH
)
#mark_as_advanced(OPENEXR_${OPENEXR_LIB}_DEBUG_LIBRARY)
if(OPENEXR_${OPENEXR_LIB}_LIBRARY_DEBUG)
list(APPEND OPENEXR_LIBRARY_DEBUG ${OPENEXR_${OPENEXR_LIB}_LIBRARY_DEBUG})
endif()
endforeach(OPENEXR_LIB)
select_library_configurations(OPENEXR)
target_link_libraries(${TARGET_NAME} ${OPENEXR_LIBRARY})
endif()
endmacro()

View file

@ -1,4 +1,4 @@
Source: hifi-deps
Version: 0
Version: 0.1
Description: Collected dependencies for High Fidelity applications
Build-Depends: bullet3, draco, etc2comp, glm, nvtt, openssl (windows), tbb (!android&!osx), zlib
Build-Depends: bullet3, draco, etc2comp, glm, nvtt, openexr (!android), openssl (windows), tbb (!android&!osx), zlib

View file

@ -0,0 +1,4 @@
Source: openexr
Version: 2.3.0-2
Description: OpenEXR is a high dynamic-range (HDR) image file format developed by Industrial Light & Magic for use in computer imaging applications
Build-Depends: zlib

View file

@ -0,0 +1,87 @@
include(FindPackageHandleStandardArgs)
find_path(OpenEXR_INCLUDE_DIRS OpenEXR/OpenEXRConfig.h)
find_path(OPENEXR_INCLUDE_PATHS NAMES ImfRgbaFile.h PATH_SUFFIXES OpenEXR)
file(STRINGS "${OpenEXR_INCLUDE_DIRS}/OpenEXR/OpenEXRConfig.h" OPENEXR_CONFIG_H)
string(REGEX REPLACE "^.*define OPENEXR_VERSION_MAJOR ([0-9]+).*$" "\\1" OpenEXR_VERSION_MAJOR "${OPENEXR_CONFIG_H}")
string(REGEX REPLACE "^.*define OPENEXR_VERSION_MINOR ([0-9]+).*$" "\\1" OpenEXR_VERSION_MINOR "${OPENEXR_CONFIG_H}")
set(OpenEXR_LIB_SUFFIX "${OpenEXR_VERSION_MAJOR}_${OpenEXR_VERSION_MINOR}")
include(SelectLibraryConfigurations)
if(NOT OpenEXR_BASE_LIBRARY)
find_library(OpenEXR_BASE_LIBRARY_RELEASE NAMES IlmImf-${OpenEXR_LIB_SUFFIX})
find_library(OpenEXR_BASE_LIBRARY_DEBUG NAMES IlmImf-${OpenEXR_LIB_SUFFIX}_d)
select_library_configurations(OpenEXR_BASE)
endif()
if(NOT OpenEXR_UTIL_LIBRARY)
find_library(OpenEXR_UTIL_LIBRARY_RELEASE NAMES IlmImfUtil-${OpenEXR_LIB_SUFFIX})
find_library(OpenEXR_UTIL_LIBRARY_DEBUG NAMES IlmImfUtil-${OpenEXR_LIB_SUFFIX}_d)
select_library_configurations(OpenEXR_UTIL)
endif()
if(NOT OpenEXR_HALF_LIBRARY)
find_library(OpenEXR_HALF_LIBRARY_RELEASE NAMES Half-${OpenEXR_LIB_SUFFIX})
find_library(OpenEXR_HALF_LIBRARY_DEBUG NAMES Half-${OpenEXR_LIB_SUFFIX}_d)
select_library_configurations(OpenEXR_HALF)
endif()
if(NOT OpenEXR_IEX_LIBRARY)
find_library(OpenEXR_IEX_LIBRARY_RELEASE NAMES Iex-${OpenEXR_LIB_SUFFIX})
find_library(OpenEXR_IEX_LIBRARY_DEBUG NAMES Iex-${OpenEXR_LIB_SUFFIX}_d)
select_library_configurations(OpenEXR_IEX)
endif()
if(NOT OpenEXR_MATH_LIBRARY)
find_library(OpenEXR_MATH_LIBRARY_RELEASE NAMES Imath-${OpenEXR_LIB_SUFFIX})
find_library(OpenEXR_MATH_LIBRARY_DEBUG NAMES Imath-${OpenEXR_LIB_SUFFIX}_d)
select_library_configurations(OpenEXR_MATH)
endif()
if(NOT OpenEXR_THREAD_LIBRARY)
find_library(OpenEXR_THREAD_LIBRARY_RELEASE NAMES IlmThread-${OpenEXR_LIB_SUFFIX})
find_library(OpenEXR_THREAD_LIBRARY_DEBUG NAMES IlmThread-${OpenEXR_LIB_SUFFIX}_d)
select_library_configurations(OpenEXR_THREAD)
endif()
if(NOT OpenEXR_IEXMATH_LIBRARY)
find_library(OpenEXR_IEXMATH_LIBRARY_RELEASE NAMES IexMath-${OpenEXR_LIB_SUFFIX})
find_library(OpenEXR_IEXMATH_LIBRARY_DEBUG NAMES IexMath-${OpenEXR_LIB_SUFFIX}d)
select_library_configurations(OpenEXR_IEXMATH)
endif()
set(OPENEXR_HALF_LIBRARY "${OpenEXR_HALF_LIBRARY}")
set(OPENEXR_IEX_LIBRARY "${OpenEXR_IEX_LIBRARY}")
set(OPENEXR_IMATH_LIBRARY "${OpenEXR_MATH_LIBRARY}")
set(OPENEXR_ILMIMF_LIBRARY "${OpenEXR_BASE_LIBRARY}")
set(OPENEXR_ILMIMFUTIL_LIBRARY "${OpenEXR_UTIL_LIBRARY}")
set(OPENEXR_ILMTHREAD_LIBRARY "${OpenEXR_THREAD_LIBRARY}")
set(OpenEXR_LIBRARY "${OpenEXR_BASE_LIBRARY}")
set(OpenEXR_LIBRARIES
${OpenEXR_LIBRARY}
${OpenEXR_MATH_LIBRARY}
${OpenEXR_IEXMATH_LIBRARY}
${OpenEXR_UTIL_LIBRARY}
${OpenEXR_HALF_LIBRARY}
${OpenEXR_IEX_LIBRARY}
${OpenEXR_THREAD_LIBRARY}
)
set(OPENEXR_LIBRARIES
${OPENEXR_HALF_LIBRARY}
${OPENEXR_IEX_LIBRARY}
${OPENEXR_IMATH_LIBRARY}
${OPENEXR_ILMIMF_LIBRARY}
${OPENEXR_ILMTHREAD_LIBRARY}
)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(OpenEXR REQUIRED_VARS OpenEXR_LIBRARIES OpenEXR_INCLUDE_DIRS)
if(OpenEXR_FOUND)
set(OPENEXR_FOUND 1)
endif()

View file

@ -0,0 +1,19 @@
diff --git a/OpenEXR/IlmImf/CMakeLists.txt b/OpenEXR/IlmImf/CMakeLists.txt
index e1a8740..d31cf68 100644
--- a/OpenEXR/IlmImf/CMakeLists.txt
+++ b/OpenEXR/IlmImf/CMakeLists.txt
@@ -2,14 +2,6 @@
SET(CMAKE_INCLUDE_CURRENT_DIR 1)
-IF (WIN32)
- SET(RUNTIME_DIR ${OPENEXR_PACKAGE_PREFIX}/bin)
- SET(WORKING_DIR ${RUNTIME_DIR})
-ELSE ()
- SET(RUNTIME_DIR ${OPENEXR_PACKAGE_PREFIX}/lib)
- SET(WORKING_DIR .)
-ENDIF ()
-
SET(BUILD_B44EXPLOGTABLE OFF)
IF (NOT EXISTS "${CMAKE_CURRENT_BINARY_DIR}/b44ExpLogTable.h")
SET(BUILD_B44EXPLOGTABLE ON)

View file

@ -0,0 +1,74 @@
include(vcpkg_common_functions)
set(OPENEXR_VERSION 2.3.0)
set(OPENEXR_HASH 268ae64b40d21d662f405fba97c307dad1456b7d996a447aadafd41b640ca736d4851d9544b4741a94e7b7c335fe6e9d3b16180e710671abfc0c8b2740b147b2)
vcpkg_from_github(
OUT_SOURCE_PATH SOURCE_PATH
REPO openexr/openexr
REF v${OPENEXR_VERSION}
SHA512 ${OPENEXR_HASH}
HEAD_REF master
PATCHES "fix_install_ilmimf.patch"
)
set(OPENEXR_STATIC ON)
set(OPENEXR_SHARED OFF)
vcpkg_configure_cmake(SOURCE_PATH ${SOURCE_PATH}
PREFER_NINJA
OPTIONS
-DOPENEXR_BUILD_PYTHON_LIBS=OFF
-DOPENEXR_BUILD_VIEWERS=OFF
-DOPENEXR_ENABLE_TESTS=OFF
-DOPENEXR_RUN_FUZZ_TESTS=OFF
-DOPENEXR_BUILD_SHARED=${OPENEXR_SHARED}
-DOPENEXR_BUILD_STATIC=${OPENEXR_STATIC}
OPTIONS_DEBUG
-DILMBASE_PACKAGE_PREFIX=${CURRENT_INSTALLED_DIR}/debug
OPTIONS_RELEASE
-DILMBASE_PACKAGE_PREFIX=${CURRENT_INSTALLED_DIR})
vcpkg_install_cmake()
file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/include)
file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/share)
# NOTE: Only use ".exe" extension on Windows executables.
# Is there a cleaner way to do this?
if(WIN32)
set(EXECUTABLE_SUFFIX ".exe")
else()
set(EXECUTABLE_SUFFIX "")
endif()
file(REMOVE ${CURRENT_PACKAGES_DIR}/debug/bin/exrenvmap${EXECUTABLE_SUFFIX})
file(REMOVE ${CURRENT_PACKAGES_DIR}/debug/bin/exrheader${EXECUTABLE_SUFFIX})
file(REMOVE ${CURRENT_PACKAGES_DIR}/debug/bin/exrmakepreview${EXECUTABLE_SUFFIX})
file(REMOVE ${CURRENT_PACKAGES_DIR}/debug/bin/exrmaketiled${EXECUTABLE_SUFFIX})
file(REMOVE ${CURRENT_PACKAGES_DIR}/debug/bin/exrmultipart${EXECUTABLE_SUFFIX})
file(REMOVE ${CURRENT_PACKAGES_DIR}/debug/bin/exrmultiview${EXECUTABLE_SUFFIX})
file(REMOVE ${CURRENT_PACKAGES_DIR}/debug/bin/exrstdattr${EXECUTABLE_SUFFIX})
file(REMOVE ${CURRENT_PACKAGES_DIR}/bin/exrenvmap${EXECUTABLE_SUFFIX})
file(REMOVE ${CURRENT_PACKAGES_DIR}/bin/exrheader${EXECUTABLE_SUFFIX})
file(REMOVE ${CURRENT_PACKAGES_DIR}/bin/exrmakepreview${EXECUTABLE_SUFFIX})
file(REMOVE ${CURRENT_PACKAGES_DIR}/bin/exrmaketiled${EXECUTABLE_SUFFIX})
file(REMOVE ${CURRENT_PACKAGES_DIR}/bin/exrmultipart${EXECUTABLE_SUFFIX})
file(REMOVE ${CURRENT_PACKAGES_DIR}/bin/exrmultiview${EXECUTABLE_SUFFIX})
file(REMOVE ${CURRENT_PACKAGES_DIR}/bin/exrstdattr${EXECUTABLE_SUFFIX})
vcpkg_copy_pdbs()
if (OPENEXR_STATIC)
file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/bin ${CURRENT_PACKAGES_DIR}/debug/bin)
endif()
if (VCPKG_CMAKE_SYSTEM_NAME STREQUAL "Linux")
set(OPENEXR_PORT_DIR "openexr")
else()
set(OPENEXR_PORT_DIR "OpenEXR")
endif()
file(COPY ${SOURCE_PATH}/LICENSE DESTINATION ${CURRENT_PACKAGES_DIR}/share/${OPENEXR_PORT_DIR})
file(RENAME ${CURRENT_PACKAGES_DIR}/share/${OPENEXR_PORT_DIR}/LICENSE ${CURRENT_PACKAGES_DIR}/share/${OPENEXR_PORT_DIR}/copyright)
file(COPY ${CMAKE_CURRENT_LIST_DIR}/FindOpenEXR.cmake DESTINATION ${CURRENT_PACKAGES_DIR}/share/${OPENEXR_PORT_DIR})

View file

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

View file

@ -678,12 +678,9 @@ $(document).ready(function(){
var errorEl = createDomainLoadingError("There was an error retrieving your places.");
$("#" + Settings.PLACES_TABLE_ID).after(errorEl);
// do we have a domain ID?
if (!domainIDIsSet()) {
// we don't have a domain ID - add a button to offer the user a chance to get a temporary one
var temporaryPlaceButton = dynamicButton(Settings.GET_TEMPORARY_NAME_BTN_ID, 'Get a temporary place name');
$('#' + Settings.PLACES_TABLE_ID).after(temporaryPlaceButton);
}
var temporaryPlaceButton = dynamicButton(Settings.GET_TEMPORARY_NAME_BTN_ID, 'Get a temporary place name');
temporaryPlaceButton.hide();
$('#' + Settings.PLACES_TABLE_ID).after(temporaryPlaceButton);
if (accessTokenIsSet()) {
appendAddButtonToPlacesTable();
}
@ -774,8 +771,9 @@ $(document).ready(function(){
// check if we have owner_places (for a real domain) or a name (for a temporary domain)
if (data.status == "success") {
$('#' + Settings.GET_TEMPORARY_NAME_BTN_ID).hide();
$('.domain-loading-hide').show();
if (data.domain.owner_places) {
if (data.domain.owner_places && data.domain.owner_places.length > 0) {
// add a table row for each of these names
_.each(data.domain.owner_places, function(place){
$('#' + Settings.PLACES_TABLE_ID + " tbody").append(placeTableRowForPlaceObject(place));
@ -783,8 +781,9 @@ $(document).ready(function(){
} else if (data.domain.name) {
// add a table row for this temporary domain name
$('#' + Settings.PLACES_TABLE_ID + " tbody").append(placeTableRow(data.domain.name, '/', true));
} else {
$('#' + Settings.GET_TEMPORARY_NAME_BTN_ID).show();
}
// Update label
if (showOrHideLabel()) {
var label = data.domain.label;

View file

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

View file

@ -13,11 +13,11 @@
{ "from": "OculusTouch.LY", "to": "Standard.LY",
"filters": [
{ "type": "deadZone", "min": 0.7 },
{ "type": "deadZone", "min": 0.15 },
"invert"
]
},
{ "from": "OculusTouch.LX", "filters": { "type": "deadZone", "min": 0.7 }, "to": "Standard.LX" },
{ "from": "OculusTouch.LX", "filters": { "type": "deadZone", "min": 0.15 }, "to": "Standard.LX" },
{ "from": "OculusTouch.LT", "to": "Standard.LTClick",
"peek": true,
"filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ]
@ -29,11 +29,11 @@
{ "from": "OculusTouch.RY", "to": "Standard.RY",
"filters": [
{ "type": "deadZone", "min": 0.7 },
{ "type": "deadZone", "min": 0.15 },
"invert"
]
},
{ "from": "OculusTouch.RX", "filters": { "type": "deadZone", "min": 0.7 }, "to": "Standard.RX" },
{ "from": "OculusTouch.RX", "filters": { "type": "deadZone", "min": 0.15 }, "to": "Standard.RX" },
{ "from": "OculusTouch.RT", "to": "Standard.RTClick",
"peek": true,
"filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ]

View file

@ -1,11 +1,14 @@
{
"name": "Standard to Action",
"channels": [
{ "from": "Standard.LY", "to": "Actions.TranslateZ" },
{ "from": "Standard.LY",
"when": ["Application.RightHandDominant", "!Standard.RY"],
"to": "Actions.TranslateZ"
},
{ "from": "Standard.LX",
"when": [
"Application.InHMD", "!Application.AdvancedMovement",
"Application.InHMD", "!Application.AdvancedMovement", "Application.RightHandDominant",
"Application.SnapTurn", "!Standard.RX"
],
"to": "Actions.StepYaw",
@ -18,14 +21,14 @@
]
},
{ "from": "Standard.LX", "to": "Actions.TranslateX",
"when": [ "Application.AdvancedMovement" ]
"when": [ "Application.AdvancedMovement", "Application.StrafeEnabled", "Application.RightHandDominant" ]
},
{ "from": "Standard.LX", "to": "Actions.Yaw",
"when": [ "!Application.AdvancedMovement", "!Application.SnapTurn" ]
"when": [ "!Application.AdvancedMovement", "!Application.SnapTurn", "Application.RightHandDominant" ]
},
{ "from": "Standard.RX",
"when": [ "Application.SnapTurn" ],
"when": [ "Application.SnapTurn", "Application.RightHandDominant" ],
"to": "Actions.StepYaw",
"filters":
[
@ -36,20 +39,69 @@
]
},
{ "from": "Standard.RX", "to": "Actions.Yaw",
"when": [ "!Application.SnapTurn" ]
"when": [ "!Application.SnapTurn", "Application.RightHandDominant" ]
},
{ "from": "Standard.LeftSecondaryThumb",
"when": [ "Application.Grounded", "Application.LeftHandDominant" ],
"to": "Actions.Up"
},
{ "from": "Standard.LeftSecondaryThumb",
"when": "Application.LeftHandDominant",
"to": "Actions.Up"
},
{ "from": "Standard.RY",
"when": "Application.Grounded",
"to": "Actions.Up",
"when": ["Application.LeftHandDominant", "!Standard.LY"],
"to": "Actions.TranslateZ"
},
{ "from": "Standard.RX",
"when": [
"Application.InHMD", "!Application.AdvancedMovement", "Application.LeftHandDominant",
"Application.SnapTurn", "!Standard.RX"
],
"to": "Actions.StepYaw",
"filters":
[
{ "type": "deadZone", "min": 0.6 },
"invert"
{ "type": "deadZone", "min": 0.15 },
"constrainToInteger",
{ "type": "pulse", "interval": 0.25 },
{ "type": "scale", "scale": 22.5 }
]
},
{ "from": "Standard.RX", "to": "Actions.TranslateX",
"when": [ "Application.AdvancedMovement", "Application.StrafeEnabled", "Application.LeftHandDominant" ]
},
{ "from": "Standard.RX", "to": "Actions.Yaw",
"when": [ "!Application.AdvancedMovement", "!Application.SnapTurn", "Application.LeftHandDominant" ]
},
{ "from": "Standard.RY", "to": "Actions.Up", "filters": "invert"},
{ "from": "Standard.LX",
"when": [ "Application.SnapTurn", "Application.LeftHandDominant" ],
"to": "Actions.StepYaw",
"filters":
[
{ "type": "deadZone", "min": 0.15 },
"constrainToInteger",
{ "type": "pulse", "interval": 0.25 },
{ "type": "scale", "scale": 22.5 }
]
},
{ "from": "Standard.LX", "to": "Actions.Yaw",
"when": [ "!Application.SnapTurn", "Application.LeftHandDominant" ]
},
{ "from": "Standard.RightSecondaryThumb",
"when": [ "Application.Grounded", "Application.RightHandDominant" ],
"to": "Actions.Up"
},
{ "from": "Standard.RightSecondaryThumb",
"when": "Application.RightHandDominant",
"to": "Actions.Up"
},
{ "from": "Standard.Back", "to": "Actions.CycleCamera" },
{ "from": "Standard.Start", "to": "Actions.ContextMenu" },
@ -128,4 +180,4 @@
{ "from": "Standard.TrackedObject14", "to" : "Actions.TrackedObject14" },
{ "from": "Standard.TrackedObject15", "to" : "Actions.TrackedObject15" }
]
}
}

View file

@ -10,8 +10,9 @@
"filters": [ { "type": "hysteresis", "min": 0.7, "max": 0.75 } ]
},
{ "from": "Vive.LY", "when": "Vive.LSY", "filters": ["invert"], "to": "Standard.LY" },
{ "from": "Vive.LX", "when": "Vive.LSX", "to": "Standard.LX" },
{ "from": "Vive.LY", "when": "Vive.LS", "filters": [ { "type": "deadZone", "min": 0.15 }, "invert" ], "to": "Standard.LY" },
{ "from": "Vive.LX", "when": ["Vive.LS", "Application.RightHandDominant"], "filters": { "type": "deadZone", "min": 0.15 }, "to": "Standard.LX" },
{ "from": "Vive.LX", "when": ["Vive.LS", "Vive.LSX", "!Vive.LSY", "Application.LeftHandDominant"], "filters": { "type": "deadZone", "min": 0.15 }, "to": "Standard.LX" },
{
"from": "Vive.LT", "to": "Standard.LT",
"filters": [
@ -28,8 +29,9 @@
},
{ "from": "Vive.LSTouch", "to": "Standard.LSTouch" },
{ "from": "Vive.RY", "when": "Vive.RSY", "filters": ["invert"], "to": "Standard.RY" },
{ "from": "Vive.RX", "when": "Vive.RSX", "to": "Standard.RX" },
{ "from": "Vive.RY", "when": "Vive.RS", "filters": [ { "type": "deadZone", "min": 0.15 }, "invert" ], "to": "Standard.RY" },
{ "from": "Vive.RX", "when": ["Vive.RS", "Application.LeftHandDominant"], "filters": { "type": "deadZone", "min": 0.15 }, "to": "Standard.RX" },
{ "from": "Vive.RX", "when": ["Vive.RS", "Vive.RSX", "!Vive.RSY", "Application.RightHandDominant"], "filters": { "type": "deadZone", "min": 0.15 }, "to": "Standard.RX" },
{
"from": "Vive.RT", "to": "Standard.RT",
"filters": [

View file

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

After

Width:  |  Height:  |  Size: 1 KiB

View file

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

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

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

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

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

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

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

Before

Width:  |  Height:  |  Size: 2.2 KiB

View file

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

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

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

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

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

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,152 @@
//
// EditAvatarInputsBar.qml
// qml/hifi
//
// Audio setup
//
// Created by Wayne Chen on 3/20/2019
// Copyright 2019 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import stylesUit 1.0
import controlsUit 1.0 as HifiControlsUit
import "../windows"
Rectangle {
id: editRect
HifiConstants { id: hifi; }
color: hifi.colors.baseGray;
signal sendToScript(var message);
function emitSendToScript(message) {
sendToScript(message);
}
function fromScript(message) {
}
RalewayRegular {
id: title;
color: hifi.colors.white;
text: qsTr("Avatar Inputs Persistent UI Settings")
size: 20
font.bold: true
anchors {
top: parent.top
left: parent.left
leftMargin: (parent.width - width) / 2
}
}
HifiControlsUit.Slider {
id: xSlider
anchors {
top: title.bottom
topMargin: 50
left: parent.left
leftMargin: 20
}
label: "X OFFSET: " + value.toFixed(2);
maximumValue: 1.0
minimumValue: -1.0
stepSize: 0.05
value: -0.2
width: 300
onValueChanged: {
emitSendToScript({
"method": "reposition",
"x": value
});
}
}
HifiControlsUit.Slider {
id: ySlider
anchors {
top: xSlider.bottom
topMargin: 50
left: parent.left
leftMargin: 20
}
label: "Y OFFSET: " + value.toFixed(2);
maximumValue: 1.0
minimumValue: -1.0
stepSize: 0.05
value: -0.125
width: 300
onValueChanged: {
emitSendToScript({
"method": "reposition",
"y": value
});
}
}
HifiControlsUit.Slider {
id: zSlider
anchors {
top: ySlider.bottom
topMargin: 50
left: parent.left
leftMargin: 20
}
label: "Z OFFSET: " + value.toFixed(2);
maximumValue: 0.0
minimumValue: -1.0
stepSize: 0.05
value: -0.5
width: 300
onValueChanged: {
emitSendToScript({
"method": "reposition",
"z": value
});
}
}
HifiControlsUit.Button {
id: setVisibleButton;
text: setVisible ? "SET INVISIBLE" : "SET VISIBLE";
width: 300;
property bool setVisible: true;
anchors {
top: zSlider.bottom
topMargin: 50
left: parent.left
leftMargin: 20
}
onClicked: {
setVisible = !setVisible;
emitSendToScript({
"method": "setVisible",
"visible": setVisible
});
}
}
HifiControlsUit.Button {
id: printButton;
text: "PRINT POSITIONS";
width: 300;
anchors {
top: setVisibleButton.bottom
topMargin: 50
left: parent.left
leftMargin: 20
}
onClicked: {
emitSendToScript({
"method": "print",
});
}
}
}

View file

@ -129,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;

View file

@ -31,6 +31,8 @@ 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);
@ -44,7 +46,7 @@ Rectangle {
property bool isVR: AudioScriptingInterface.context === "VR"
property real rightMostInputLevelPos: 440
property real rightMostInputLevelPos: root.width
//placeholder for control sizes and paddings
//recalculates dynamically in case of UI size is changed
QtObject {
@ -87,12 +89,25 @@ 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();
}
Flickable {
id: flickView;
@ -167,15 +182,25 @@ Rectangle {
height: root.switchHeight;
switchWidth: root.switchWidth;
labelTextOn: "Mute microphone";
labelTextSize: 16;
backgroundOnColor: "#E3E3E3";
checked: AudioScriptingInterface.muted;
checked: muted;
onClicked: {
if (AudioScriptingInterface.pushToTalk && !checked) {
if (pushToTalk && !checked) {
// disable push to talk if unmuting
AudioScriptingInterface.pushToTalk = false;
if (bar.currentIndex === 0) {
AudioScriptingInterface.pushToTalkDesktop = false;
}
else {
AudioScriptingInterface.pushToTalkHMD = false;
}
}
if (bar.currentIndex === 0) {
AudioScriptingInterface.mutedDesktop = checked;
}
else {
AudioScriptingInterface.mutedHMD = checked;
}
AudioScriptingInterface.muted = checked;
checked = Qt.binding(function() { return AudioScriptingInterface.muted; }); // restore binding
}
}
@ -187,6 +212,7 @@ Rectangle {
anchors.topMargin: 24
anchors.left: parent.left
labelTextOn: "Noise Reduction";
labelTextSize: 16;
backgroundOnColor: "#E3E3E3";
checked: AudioScriptingInterface.noiseReduction;
onCheckedChanged: {
@ -202,7 +228,8 @@ Rectangle {
anchors.top: noiseReductionSwitch.bottom
anchors.topMargin: 24
anchors.left: parent.left
labelTextOn: qsTr("Push To Talk (T)");
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: {
@ -211,13 +238,6 @@ Rectangle {
} else {
AudioScriptingInterface.pushToTalkHMD = checked;
}
checked = Qt.binding(function() {
if (bar.currentIndex === 0) {
return AudioScriptingInterface.pushToTalkDesktop;
} else {
return AudioScriptingInterface.pushToTalkHMD;
}
}); // restore binding
}
}
}
@ -234,7 +254,8 @@ Rectangle {
switchWidth: root.switchWidth;
anchors.top: parent.top
anchors.left: parent.left
labelTextOn: qsTr("Warn when muted");
labelTextOn: qsTr("Warn when muted in HMD");
labelTextSize: 16;
backgroundOnColor: "#E3E3E3";
checked: AudioScriptingInterface.warnWhenMuted;
onClicked: {
@ -252,6 +273,7 @@ Rectangle {
anchors.topMargin: 24
anchors.left: parent.left
labelTextOn: qsTr("Audio Level Meter");
labelTextSize: 16;
backgroundOnColor: "#E3E3E3";
checked: AvatarInputs.showAudioTools;
onCheckedChanged: {
@ -268,6 +290,7 @@ Rectangle {
anchors.topMargin: 24
anchors.left: parent.left
labelTextOn: qsTr("Stereo input");
labelTextSize: 16;
backgroundOnColor: "#E3E3E3";
checked: AudioScriptingInterface.isStereoInput;
onCheckedChanged: {
@ -303,7 +326,7 @@ Rectangle {
Separator {
id: secondSeparator;
anchors.top: pttTextContainer.bottom;
anchors.top: pttTextContainer.visible ? pttTextContainer.bottom : switchesContainer.bottom;
anchors.topMargin: 10;
}
@ -330,7 +353,7 @@ Rectangle {
width: margins.sizeText + margins.sizeLevel;
anchors.left: parent.left;
anchors.leftMargin: margins.sizeCheckBox;
size: 16;
size: 22;
color: hifi.colors.white;
text: qsTr("Choose input device");
}
@ -338,16 +361,17 @@ Rectangle {
ListView {
id: inputView;
width: parent.width - margins.paddings*2;
width: rightMostInputLevelPos;
anchors.top: inputDeviceHeader.bottom;
anchors.topMargin: 10;
x: margins.paddings
interactive: false;
height: contentHeight;
spacing: 4;
clip: true;
model: AudioScriptingInterface.devices.input;
delegate: Item {
width: rightMostInputLevelPos
width: rightMostInputLevelPos - margins.paddings*2
height: margins.sizeCheckBox > checkBoxInput.implicitHeight ?
margins.sizeCheckBox : checkBoxInput.implicitHeight
@ -363,6 +387,7 @@ Rectangle {
boxSize: margins.sizeCheckBox / 2
isRound: true
text: devicename
fontSize: 16;
onPressed: {
if (!checked) {
stereoInput.checked = false;
@ -382,6 +407,7 @@ Rectangle {
}
}
}
AudioControls.LoopbackAudio {
id: loopbackAudio
x: margins.paddings
@ -395,7 +421,7 @@ Rectangle {
Separator {
id: thirdSeparator;
anchors.top: loopbackAudio.bottom;
anchors.top: loopbackAudio.visible ? loopbackAudio.bottom : inputView.bottom;
anchors.topMargin: 10;
}
@ -422,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");
}
@ -431,7 +457,8 @@ Rectangle {
ListView {
id: outputView
width: parent.width - margins.paddings*2
x: margins.paddings
x: margins.paddings;
interactive: false;
height: contentHeight;
anchors.top: outputDeviceHeader.bottom;
anchors.topMargin: 10;
@ -452,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);
@ -462,22 +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);
}
@ -493,7 +521,7 @@ Rectangle {
// Do nothing.
}
onDoubleClicked: {
gainSlider.value = 0.0
avatarGainSlider.value = 0.0
}
onPressed: {
// Pass through to Slider
@ -507,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;
@ -526,12 +676,8 @@ Rectangle {
AudioControls.PlaySampleSound {
id: playSampleSound
x: margins.paddings
anchors.top: gainContainer.bottom;
anchors.top: systemInjectorGainContainer.bottom;
anchors.topMargin: 10;
visible: (bar.currentIndex === 1 && isVR) ||
(bar.currentIndex === 0 && !isVR);
anchors { left: parent.left; leftMargin: margins.paddings }
}
}
}

View file

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

View file

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

View file

@ -16,14 +16,33 @@ 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;
@ -67,10 +86,10 @@ Rectangle {
hoverEnabled: true;
scrollGestureEnabled: false;
onClicked: {
if (AudioScriptingInterface.pushToTalk) {
if (pushToTalk) {
return;
}
AudioScriptingInterface.muted = !AudioScriptingInterface.muted;
AudioScriptingInterface.muted = !muted;
Tablet.playSound(TabletEnums.ButtonClick);
}
drag.target: dragTarget;
@ -84,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 {
@ -113,9 +132,12 @@ Rectangle {
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.pushToTalk && !AudioScriptingInterface.pushingToTalk) ? pushToTalkIcon : AudioScriptingInterface.muted ? mutedIcon : unmutedIcon;
source: (pushToTalk && !pushingToTalk) ? pushToTalkIcon : muted ? mutedIcon :
clipping ? clippingIcon : gated ? gatedIcon : unmutedIcon;
width: 30;
height: 30;
@ -138,9 +160,7 @@ Rectangle {
Item {
id: status;
readonly property string color: AudioScriptingInterface.muted ? colors.muted : colors.unmuted;
visible: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) || AudioScriptingInterface.muted;
visible: (pushToTalk && !pushingToTalk) || muted;
anchors {
left: parent.left;
@ -157,9 +177,9 @@ Rectangle {
verticalCenter: parent.verticalCenter;
}
color: parent.color;
color: colors.icon;
text: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) ? (HMD.active ? "MUTED PTT" : "MUTED PTT-(T)") : (AudioScriptingInterface.muted ? "MUTED" : "MUTE");
text: (pushToTalk && !pushingToTalk) ? (HMD.active ? "MUTED PTT" : "MUTED PTT-(T)") : (muted ? "MUTED" : "MUTE");
font.pointSize: 12;
}
@ -169,9 +189,9 @@ Rectangle {
verticalCenter: parent.verticalCenter;
}
width: AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk ? (HMD.active ? 27 : 25) : 50;
width: pushToTalk && !pushingToTalk ? (HMD.active ? 27 : 25) : 50;
height: 4;
color: parent.color;
color: colors.icon;
}
Rectangle {
@ -180,9 +200,9 @@ Rectangle {
verticalCenter: parent.verticalCenter;
}
width: AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk ? (HMD.active ? 27 : 25) : 50;
width: pushToTalk && !pushingToTalk ? (HMD.active ? 27 : 25) : 50;
height: 4;
color: parent.color;
color: colors.icon;
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -664,7 +664,7 @@ Rectangle {
text: "LOG IN"
onClicked: {
sendToScript({method: 'needsLogIn_loginClicked'});
sendToScript({method: 'marketplace_loginClicked'});
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -338,6 +338,10 @@ Setting::Handle<int> maxOctreePacketsPerSecond{"maxOctreePPS", DEFAULT_MAX_OCTRE
Setting::Handle<bool> loginDialogPoppedUp{"loginDialogPoppedUp", false};
static const QUrl AVATAR_INPUTS_BAR_QML = PathUtils::qmlUrl("AvatarInputsBar.qml");
static const QUrl MIC_BAR_APPLICATION_QML = PathUtils::qmlUrl("hifi/audio/MicBarApplication.qml");
static const QUrl BUBBLE_ICON_QML = PathUtils::qmlUrl("BubbleIcon.qml");
static const QString STANDARD_TO_ACTION_MAPPING_NAME = "Standard to Action";
static const QString NO_MOVEMENT_MAPPING_NAME = "Standard to Action (No Movement)";
static const QString NO_MOVEMENT_MAPPING_JSON = PathUtils::resourcesPath() + "/controllers/standard_nomovement.json";
@ -676,6 +680,8 @@ private:
* <tr><td><code>InHMD</code></td><td>number</td><td>number</td><td>The user is in HMD mode.</td></tr>
* <tr><td><code>AdvancedMovement</code></td><td>number</td><td>number</td><td>Advanced movement controls are enabled.
* </td></tr>
* <tr><td><code>LeftHandDominant</code></td><td>number</td><td>number</td><td>Dominant hand set to left.</td></tr>
* <tr><td><code>RightHandDominant</code></td><td>number</td><td>number</td><td>Dominant hand set to right.</td></tr>
* <tr><td><code>SnapTurn</code></td><td>number</td><td>number</td><td>Snap turn is enabled.</td></tr>
* <tr><td><code>Grounded</code></td><td>number</td><td>number</td><td>The user's avatar is on the ground.</td></tr>
* <tr><td><code>NavigationFocused</code></td><td>number</td><td>number</td><td><em>Not used.</em></td></tr>
@ -697,6 +703,9 @@ static const QString STATE_NAV_FOCUSED = "NavigationFocused";
static const QString STATE_PLATFORM_WINDOWS = "PlatformWindows";
static const QString STATE_PLATFORM_MAC = "PlatformMac";
static const QString STATE_PLATFORM_ANDROID = "PlatformAndroid";
static const QString STATE_LEFT_HAND_DOMINANT = "LeftHandDominant";
static const QString STATE_RIGHT_HAND_DOMINANT = "RightHandDominant";
static const QString STATE_STRAFE_ENABLED = "StrafeEnabled";
// Statically provided display and input plugins
extern DisplayPluginList getDisplayPlugins();
@ -898,7 +907,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
controller::StateController::setStateVariables({ { STATE_IN_HMD, STATE_CAMERA_FULL_SCREEN_MIRROR,
STATE_CAMERA_FIRST_PERSON, STATE_CAMERA_THIRD_PERSON, STATE_CAMERA_ENTITY, STATE_CAMERA_INDEPENDENT,
STATE_SNAP_TURN, STATE_ADVANCED_MOVEMENT_CONTROLS, STATE_GROUNDED, STATE_NAV_FOCUSED,
STATE_PLATFORM_WINDOWS, STATE_PLATFORM_MAC, STATE_PLATFORM_ANDROID } });
STATE_PLATFORM_WINDOWS, STATE_PLATFORM_MAC, STATE_PLATFORM_ANDROID, STATE_LEFT_HAND_DOMINANT, STATE_RIGHT_HAND_DOMINANT, STATE_STRAFE_ENABLED } });
DependencyManager::set<UserInputMapper>();
DependencyManager::set<controller::ScriptingInterface, ControllerScriptingInterface>();
DependencyManager::set<InterfaceParentFinder>();
@ -1206,10 +1215,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());
@ -1740,6 +1745,15 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
_applicationStateDevice->setInputVariant(STATE_ADVANCED_MOVEMENT_CONTROLS, []() -> float {
return qApp->getMyAvatar()->useAdvancedMovementControls() ? 1 : 0;
});
_applicationStateDevice->setInputVariant(STATE_LEFT_HAND_DOMINANT, []() -> float {
return qApp->getMyAvatar()->getDominantHand() == "left" ? 1 : 0;
});
_applicationStateDevice->setInputVariant(STATE_RIGHT_HAND_DOMINANT, []() -> float {
return qApp->getMyAvatar()->getDominantHand() == "right" ? 1 : 0;
});
_applicationStateDevice->setInputVariant(STATE_STRAFE_ENABLED, []() -> float {
return qApp->getMyAvatar()->getStrafeEnabled() ? 1 : 0;
});
_applicationStateDevice->setInputVariant(STATE_GROUNDED, []() -> float {
return qApp->getMyAvatar()->getCharacterController()->onGround() ? 1 : 0;
@ -2376,7 +2390,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();
@ -2882,11 +2897,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
@ -3295,6 +3318,7 @@ void Application::onDesktopRootItemCreated(QQuickItem* rootItem) {
auto qml = PathUtils::qmlUrl("AvatarInputsBar.qml");
offscreenUi->show(qml, "AvatarInputsBar");
#endif
_desktopRootItemCreated = true;
}
void Application::userKickConfirmation(const QUuid& nodeID) {
@ -3726,14 +3750,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)) {
@ -3747,7 +3768,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", {
@ -6703,6 +6723,11 @@ void Application::updateRenderArgs(float deltaTime) {
// Configure the type of display / stereo
appRenderArgs._renderArgs._displayMode = (isHMDMode() ? RenderArgs::STEREO_HMD : RenderArgs::STEREO_MONITOR);
}
appRenderArgs._renderArgs._stencilMode = getActiveDisplayPlugin()->getStencilMaskMode();
if (appRenderArgs._renderArgs._stencilMode == StencilMode::MESH) {
appRenderArgs._renderArgs._stencilMaskOperator = getActiveDisplayPlugin()->getStencilMaskMeshOperator();
}
}
{
@ -8976,6 +9001,38 @@ void Application::updateLoginDialogPosition() {
}
}
void Application::createAvatarInputsBar() {
const glm::vec3 LOCAL_POSITION { 0.0, 0.0, -1.0 };
// DEFAULT_DPI / tablet scale percentage
const float DPI = 31.0f / (75.0f / 100.0f);
EntityItemProperties properties;
properties.setType(EntityTypes::Web);
properties.setName("AvatarInputsBarEntity");
properties.setSourceUrl(AVATAR_INPUTS_BAR_QML.toString());
properties.setParentID(getMyAvatar()->getSelfID());
properties.setParentJointIndex(getMyAvatar()->getJointIndex("_CAMERA_MATRIX"));
properties.setPosition(LOCAL_POSITION);
properties.setLocalRotation(Quaternions::IDENTITY);
//properties.setDimensions(LOGIN_DIMENSIONS);
properties.setPrimitiveMode(PrimitiveMode::SOLID);
properties.getGrab().setGrabbable(false);
properties.setIgnorePickIntersection(false);
properties.setAlpha(1.0f);
properties.setDPI(DPI);
properties.setVisible(true);
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
_avatarInputsBarID = entityScriptingInterface->addEntityInternal(properties, entity::HostType::LOCAL);
}
void Application::destroyAvatarInputsBar() {
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
if (!_avatarInputsBarID.isNull()) {
entityScriptingInterface->deleteEntity(_avatarInputsBarID);
}
}
bool Application::hasRiftControllers() {
return PluginUtils::isOculusTouchControllerAvailable();
}

View file

@ -330,6 +330,9 @@ public:
void createLoginDialog();
void updateLoginDialogPosition();
void createAvatarInputsBar();
void destroyAvatarInputsBar();
// Check if a headset is connected
bool hasRiftControllers();
bool hasViveControllers();
@ -704,12 +707,14 @@ private:
int _maxOctreePPS = DEFAULT_MAX_OCTREE_PPS;
bool _interstitialModeEnabled{ false };
bool _loginDialogPoppedUp = false;
bool _loginDialogPoppedUp{ false };
bool _desktopRootItemCreated{ false };
bool _developerMenuVisible{ false };
QString _previousAvatarSkeletonModel;
float _previousAvatarTargetScale;
CameraMode _previousCameraMode;
QUuid _loginDialogID;
QUuid _avatarInputsBarID;
LoginStateManager _loginStateManager;
quint64 _lastFaceTrackerUpdate;

View file

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

View file

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

View file

@ -38,6 +38,7 @@
#include <UsersScriptingInterface.h>
#include <UUID.h>
#include <shared/ConicalViewFrustum.h>
#include <ui/AvatarInputs.h>
#include "Application.h"
#include "InterfaceLogging.h"
@ -84,7 +85,6 @@ AvatarManager::AvatarManager(QObject* parent) :
AvatarSharedPointer AvatarManager::addAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer) {
AvatarSharedPointer avatar = AvatarHashMap::addAvatar(sessionUUID, mixerWeakPointer);
const auto otherAvatar = std::static_pointer_cast<OtherAvatar>(avatar);
if (otherAvatar && _space) {
std::unique_lock<std::mutex> lock(_spaceLock);
@ -210,7 +210,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
{
// lock the hash for read to check the size
QReadLocker lock(&_hashLock);
if (_avatarHash.size() < 2 && _avatarsToFadeOut.isEmpty()) {
if (_avatarHash.size() < 2) {
return;
}
}
@ -375,19 +375,12 @@ 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;
_numHeroAvatarsUpdated = numHerosUpdated;
simulateAvatarFades(deltaTime);
_avatarSimulationTime = (float)(usecTimestampNow() - startTime) / (float)USECS_PER_MSEC;
}
@ -400,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);
@ -452,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);
@ -520,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();
@ -534,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() {

View file

@ -220,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
@ -231,8 +229,6 @@ private:
KillAvatarReason removalReason = KillAvatarReason::NoReason) override;
void handleTransitAnimations(AvatarTransit::Status status);
QVector<AvatarSharedPointer> _avatarsToFadeOut;
using SetOfOtherAvatars = std::set<OtherAvatarPointer>;
SetOfOtherAvatars _avatarsToChangeInPhysics;
@ -252,7 +248,6 @@ private:
mutable std::mutex _spaceLock;
workload::SpacePointer _space;
std::vector<int32_t> _spaceProxiesToDelete;
AvatarTransit::TransitConfig _transitConfig;
};

View file

@ -155,6 +155,7 @@ MyAvatar::MyAvatar(QThread* thread) :
_prevShouldDrawHead(true),
_audioListenerMode(FROM_HEAD),
_dominantHandSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "dominantHand", DOMINANT_RIGHT_HAND),
_strafeEnabledSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "strafeEnabled", DEFAULT_STRAFE_ENABLED),
_hmdAvatarAlignmentTypeSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "hmdAvatarAlignmentType", DEFAULT_HMD_AVATAR_ALIGNMENT_TYPE),
_headPitchSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "", 0.0f),
_scaleSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "scale", _targetScale),
@ -169,7 +170,16 @@ MyAvatar::MyAvatar(QThread* thread) :
_useSnapTurnSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "useSnapTurn", _useSnapTurn),
_userHeightSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "userHeight", DEFAULT_AVATAR_HEIGHT),
_flyingHMDSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "flyingHMD", _flyingPrefHMD),
_movementReferenceSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "movementReference", _movementReference),
_avatarEntityCountSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "avatarEntityData" << "size", 0),
_driveGear1Setting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "driveGear1", _driveGear1),
_driveGear2Setting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "driveGear2", _driveGear2),
_driveGear3Setting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "driveGear3", _driveGear3),
_driveGear4Setting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "driveGear4", _driveGear4),
_driveGear5Setting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "driveGear5", _driveGear5),
_analogWalkSpeedSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "analogWalkSpeed", _analogWalkSpeed.get()),
_analogPlusWalkSpeedSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "analogPlusWalkSpeed", _analogPlusWalkSpeed.get()),
_controlSchemeIndexSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "controlSchemeIndex", _controlSchemeIndex),
_userRecenterModelSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "userRecenterModel", USER_RECENTER_MODEL_AUTO)
{
_clientTraitsHandler.reset(new ClientTraitsHandler(this));
@ -322,10 +332,21 @@ QString MyAvatar::getDominantHand() const {
return _dominantHand.get();
}
void MyAvatar::setStrafeEnabled(bool enabled) {
_strafeEnabled.set(enabled);
}
bool MyAvatar::getStrafeEnabled() const {
return _strafeEnabled.get();
}
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 +960,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.
@ -1255,6 +1274,7 @@ void MyAvatar::resizeAvatarEntitySettingHandles(uint32_t maxIndex) {
void MyAvatar::saveData() {
_dominantHandSetting.set(getDominantHand());
_strafeEnabledSetting.set(getStrafeEnabled());
_hmdAvatarAlignmentTypeSetting.set(getHmdAvatarAlignmentType());
_headPitchSetting.set(getHead()->getBasePitch());
_scaleSetting.set(_targetScale);
@ -1278,6 +1298,15 @@ void MyAvatar::saveData() {
_useSnapTurnSetting.set(_useSnapTurn);
_userHeightSetting.set(getUserHeight());
_flyingHMDSetting.set(getFlyingHMDPref());
_movementReferenceSetting.set(getMovementReference());
_driveGear1Setting.set(getDriveGear1());
_driveGear2Setting.set(getDriveGear2());
_driveGear3Setting.set(getDriveGear3());
_driveGear4Setting.set(getDriveGear4());
_driveGear5Setting.set(getDriveGear5());
_analogWalkSpeedSetting.set(getAnalogWalkSpeed());
_analogPlusWalkSpeedSetting.set(getAnalogPlusWalkSpeed());
_controlSchemeIndexSetting.set(getControlSchemeIndex());
_userRecenterModelSetting.set(userRecenterModelToString(getUserRecenterModel()));
auto hmdInterface = DependencyManager::get<HMDScriptingInterface>();
@ -1855,12 +1884,22 @@ void MyAvatar::loadData() {
// Flying preferences must be loaded before calling setFlyingEnabled()
Setting::Handle<bool> firstRunVal { Settings::firstRun, true };
setFlyingHMDPref(firstRunVal.get() ? false : _flyingHMDSetting.get());
setMovementReference(firstRunVal.get() ? false : _movementReferenceSetting.get());
setDriveGear1(firstRunVal.get() ? DEFAULT_GEAR_1 : _driveGear1Setting.get());
setDriveGear2(firstRunVal.get() ? DEFAULT_GEAR_2 : _driveGear2Setting.get());
setDriveGear3(firstRunVal.get() ? DEFAULT_GEAR_3 : _driveGear3Setting.get());
setDriveGear4(firstRunVal.get() ? DEFAULT_GEAR_4 : _driveGear4Setting.get());
setDriveGear5(firstRunVal.get() ? DEFAULT_GEAR_5 : _driveGear5Setting.get());
setControlSchemeIndex(firstRunVal.get() ? LocomotionControlsMode::CONTROLS_DEFAULT : _controlSchemeIndexSetting.get());
setAnalogWalkSpeed(firstRunVal.get() ? ANALOG_AVATAR_MAX_WALKING_SPEED : _analogWalkSpeedSetting.get());
setAnalogPlusWalkSpeed(firstRunVal.get() ? ANALOG_PLUS_AVATAR_MAX_WALKING_SPEED : _analogPlusWalkSpeedSetting.get());
setFlyingEnabled(getFlyingEnabled());
setDisplayName(_displayNameSetting.get());
setCollisionSoundURL(_collisionSoundURLSetting.get(QUrl(DEFAULT_AVATAR_COLLISION_SOUND_URL)).toString());
setSnapTurn(_useSnapTurnSetting.get());
setDominantHand(_dominantHandSetting.get(DOMINANT_RIGHT_HAND).toLower());
setStrafeEnabled(_strafeEnabledSetting.get(DEFAULT_STRAFE_ENABLED));
setHmdAvatarAlignmentType(_hmdAvatarAlignmentTypeSetting.get(DEFAULT_HMD_AVATAR_ALIGNMENT_TYPE).toLower());
setUserHeight(_userHeightSetting.get(DEFAULT_AVATAR_HEIGHT));
setTargetScale(_scaleSetting.get());
@ -2385,7 +2424,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;
@ -2506,6 +2557,12 @@ controller::Pose MyAvatar::getControllerPoseInAvatarFrame(controller::Action act
}
}
glm::quat MyAvatar::getOffHandRotation() const {
auto hand = (getDominantHand() == DOMINANT_RIGHT_HAND) ? controller::Action::LEFT_HAND : controller::Action::RIGHT_HAND;
auto pose = getControllerPoseInAvatarFrame(hand);
return pose.rotation;
}
void MyAvatar::updateMotors() {
_characterController.clearMotors();
glm::quat motorRotation;
@ -3272,21 +3329,131 @@ void MyAvatar::updateOrientation(float deltaTime) {
}
}
static float scaleSpeedByDirection(const glm::vec2 velocityDirection, const float forwardSpeed, const float backwardSpeed) {
// for the elipse function --> (x^2)/(backwardSpeed*backwardSpeed) + y^2/(forwardSpeed*forwardSpeed) = 1, scale == y^2 when x is 0
float fwdScale = forwardSpeed * forwardSpeed;
float backScale = backwardSpeed * backwardSpeed;
float scaledX = velocityDirection.x * backwardSpeed;
float scaledSpeed = forwardSpeed;
if (velocityDirection.y < 0.0f) {
if (backScale > 0.0f) {
float yValue = sqrtf(fwdScale * (1.0f - ((scaledX * scaledX) / backScale)));
scaledSpeed = sqrtf((scaledX * scaledX) + (yValue * yValue));
float MyAvatar::calculateGearedSpeed(const float driveKey) {
float absDriveKey = abs(driveKey);
float sign = (driveKey < 0.0f) ? -1.0f : 1.0f;
if (absDriveKey > getDriveGear5()) {
return sign * 1.0f;
}
else if (absDriveKey > getDriveGear4()) {
return sign * 0.8f;
}
else if (absDriveKey > getDriveGear3()) {
return sign * 0.6f;
}
else if (absDriveKey > getDriveGear2()) {
return sign * 0.4f;
}
else if (absDriveKey > getDriveGear1()) {
return sign * 0.2f;
}
else {
return sign * 0.0f;
}
}
glm::vec3 MyAvatar::scaleMotorSpeed(const glm::vec3 forward, const glm::vec3 right) {
float stickFullOn = 0.85f;
auto zSpeed = getDriveKey(TRANSLATE_Z);
auto xSpeed = getDriveKey(TRANSLATE_X);
glm::vec3 direction;
if (!useAdvancedMovementControls() && qApp->isHMDMode()) {
// Walking disabled in settings.
return Vectors::ZERO;
} else if (qApp->isHMDMode()) {
// HMD advanced movement controls.
switch (_controlSchemeIndex) {
case LocomotionControlsMode::CONTROLS_DEFAULT:
// No acceleration curve for this one, constant speed.
if (zSpeed || xSpeed) {
direction = (zSpeed * forward) + (xSpeed * right);
// Normalize direction.
auto length = glm::length(direction);
if (length > EPSILON) {
direction /= length;
}
return getSensorToWorldScale() * direction * getSprintSpeed() * _walkSpeedScalar;
} else {
return Vectors::ZERO;
}
case LocomotionControlsMode::CONTROLS_ANALOG:
case LocomotionControlsMode::CONTROLS_ANALOG_PLUS:
if (zSpeed || xSpeed) {
glm::vec3 scaledForward = getSensorToWorldScale() * calculateGearedSpeed(zSpeed) * _walkSpeedScalar * ((zSpeed >= stickFullOn) ? getSprintSpeed() : getWalkSpeed()) * forward;
glm::vec3 scaledRight = getSensorToWorldScale() * calculateGearedSpeed(xSpeed) * _walkSpeedScalar * ((xSpeed > stickFullOn) ? getSprintSpeed() : getWalkSpeed()) * right;
direction = scaledForward + scaledRight;
return direction;
} else {
return Vectors::ZERO;
}
default:
qDebug() << "Invalid control scheme index.";
return Vectors::ZERO;
}
} else {
scaledSpeed = backwardSpeed;
// Desktop mode.
direction = (zSpeed * forward) + (xSpeed * right);
auto length = glm::length(direction);
if (length > EPSILON) {
direction /= length;
}
direction *= getWalkSpeed() * _walkSpeedScalar;
return direction;
}
return scaledSpeed;
}
glm::vec3 MyAvatar::calculateScaledDirection(){
CharacterController::State state = _characterController.getState();
// compute action input
// Determine if we're head or controller relative...
glm::vec3 forward, right;
if (qApp->isHMDMode()) {
auto handRotation = getOffHandRotation();
glm::vec3 controllerForward(0.0f, 1.0f, 0.0f);
glm::vec3 controllerRight(0.0f, 0.0f, (getDominantHand() == DOMINANT_RIGHT_HAND ? 1.0f : -1.0f));
glm::vec3 transform;
switch (getMovementReference()) {
case LocomotionRelativeMovementMode::MOVEMENT_HAND_RELATIVE:
forward = (handRotation * controllerForward);
right = (handRotation * controllerRight);
break;
case LocomotionRelativeMovementMode::MOVEMENT_HAND_RELATIVE_LEVELED:
forward = (handRotation * controllerForward);
transform = forward - (glm::dot(forward, Vectors::UNIT_Y) * Vectors::UNIT_Y);
if (glm::length(transform) > EPSILON) {
forward = glm::normalize(transform);
} else {
forward = Vectors::ZERO;
}
right = (handRotation * controllerRight);
transform = right - (glm::dot(right, Vectors::UNIT_Y) * Vectors::UNIT_Y);
if (glm::length(transform) > EPSILON) {
right = glm::normalize(transform);
} else {
right = Vectors::ZERO;
}
break;
case LocomotionRelativeMovementMode::MOVEMENT_HMD_RELATIVE:
default:
forward = IDENTITY_FORWARD;
right = IDENTITY_RIGHT;
}
} else {
forward = IDENTITY_FORWARD;
right = IDENTITY_RIGHT;
}
glm::vec3 direction = scaleMotorSpeed(forward, right);
if (state == CharacterController::State::Hover ||
_characterController.computeCollisionMask() == BULLET_COLLISION_MASK_COLLISIONLESS) {
glm::vec3 up = (getDriveKey(TRANSLATE_Y)) * IDENTITY_UP;
direction += up;
}
return direction;
}
void MyAvatar::updateActionMotor(float deltaTime) {
@ -3306,25 +3473,13 @@ void MyAvatar::updateActionMotor(float deltaTime) {
CharacterController::State state = _characterController.getState();
// compute action input
glm::vec3 forward = (getDriveKey(TRANSLATE_Z)) * IDENTITY_FORWARD;
glm::vec3 right = (getDriveKey(TRANSLATE_X)) * IDENTITY_RIGHT;
glm::vec3 direction = forward + right;
if (state == CharacterController::State::Hover ||
_characterController.computeCollisionMask() == BULLET_COLLISION_MASK_COLLISIONLESS) {
glm::vec3 up = (getDriveKey(TRANSLATE_Y)) * IDENTITY_UP;
direction += up;
}
glm::vec3 direction = calculateScaledDirection();
_wasPushing = _isPushing;
float directionLength = glm::length(direction);
_isPushing = directionLength > EPSILON;
// normalize direction
if (_isPushing) {
direction /= directionLength;
} else {
if (!_isPushing) {
direction = Vectors::ZERO;
}
@ -3340,6 +3495,7 @@ void MyAvatar::updateActionMotor(float deltaTime) {
const float maxBoostSpeed = sensorToWorldScale * MAX_BOOST_SPEED;
if (_isPushing) {
direction /= directionLength;
if (motorSpeed < maxBoostSpeed) {
// an active action motor should never be slower than this
float boostCoefficient = (maxBoostSpeed - motorSpeed) / maxBoostSpeed;
@ -3350,11 +3506,17 @@ void MyAvatar::updateActionMotor(float deltaTime) {
}
_actionMotorVelocity = motorSpeed * direction;
} else {
// we're interacting with a floor --> simple horizontal speed and exponential decay
const glm::vec2 currentVel = { direction.x, direction.z };
float scaledSpeed = scaleSpeedByDirection(currentVel, _walkSpeed.get(), _walkBackwardSpeed.get());
// _walkSpeedScalar is a multiplier if we are in sprint mode, otherwise 1.0
_actionMotorVelocity = sensorToWorldScale * (scaledSpeed * _walkSpeedScalar) * direction;
_actionMotorVelocity = direction;
}
float previousBoomLength = _boomLength;
float boomChange = getDriveKey(ZOOM);
_boomLength += 2.0f * _boomLength * boomChange + boomChange * boomChange;
_boomLength = glm::clamp<float>(_boomLength, ZOOM_MIN, ZOOM_MAX);
// May need to change view if boom length has changed
if (previousBoomLength != _boomLength) {
qApp->changeViewAsNeeded(_boomLength);
}
}
@ -3454,11 +3616,6 @@ void MyAvatar::setSessionUUID(const QUuid& sessionUUID) {
QUuid oldSessionID = getSessionUUID();
Avatar::setSessionUUID(sessionUUID);
QUuid newSessionID = getSessionUUID();
if (DependencyManager::get<NodeList>()->getSessionUUID().isNull()) {
// we don't actually have a connection to a domain right now
// so there is no need to queue AvatarEntity messages --> bail early
return;
}
if (newSessionID != oldSessionID) {
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
@ -3467,6 +3624,7 @@ void MyAvatar::setSessionUUID(const QUuid& sessionUUID) {
_avatarEntitiesLock.withReadLock([&] {
avatarEntityIDs = _packedAvatarEntityData.keys();
});
bool sendPackets = !DependencyManager::get<NodeList>()->getSessionUUID().isNull();
EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender();
entityTree->withWriteLock([&] {
for (const auto& entityID : avatarEntityIDs) {
@ -3474,12 +3632,14 @@ void MyAvatar::setSessionUUID(const QUuid& sessionUUID) {
if (!entity) {
continue;
}
// update OwningAvatarID so entity can be identified as "ours" later
entity->setOwningAvatarID(newSessionID);
// NOTE: each attached AvatarEntity should already have the correct updated parentID
// via magic in SpatiallyNestable, but when an AvatarEntity IS parented to MyAvatar
// we need to update the "packedAvatarEntityData" we send to the avatar-mixer
// so that others will get the updated state.
if (entity->getParentID() == 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);
}
}
@ -3565,6 +3725,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();
@ -3863,6 +4029,136 @@ void MyAvatar::setFlyingHMDPref(bool enabled) {
_flyingPrefHMD = enabled;
}
void MyAvatar::setMovementReference(int enabled) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setMovementReference", Q_ARG(bool, enabled));
return;
}
_movementReference = enabled;
}
int MyAvatar::getMovementReference() {
return _movementReference;
}
void MyAvatar::setControlSchemeIndex(int index){
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setControlSchemeIndex", Q_ARG(int, index));
return;
}
// Need to add checks for valid indices.
_controlSchemeIndex = index;
}
int MyAvatar::getControlSchemeIndex() {
return _controlSchemeIndex;
}
void MyAvatar::setDriveGear1(float shiftPoint) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setDriveGear1", Q_ARG(float, shiftPoint));
return;
}
if (shiftPoint > 1.0f || shiftPoint < 0.0f) return;
_driveGear1 = (shiftPoint < _driveGear2) ? shiftPoint : _driveGear1;
}
float MyAvatar::getDriveGear1() {
switch (_controlSchemeIndex) {
case LocomotionControlsMode::CONTROLS_ANALOG:
return ANALOG_AVATAR_GEAR_1;
case LocomotionControlsMode::CONTROLS_ANALOG_PLUS:
return _driveGear1;
case LocomotionControlsMode::CONTROLS_DEFAULT:
default:
return 1.0f;
}
}
void MyAvatar::setDriveGear2(float shiftPoint) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setDriveGear2", Q_ARG(float, shiftPoint));
return;
}
if (shiftPoint > 1.0f || shiftPoint < 0.0f) return;
_driveGear2 = (shiftPoint < _driveGear3 && shiftPoint >= _driveGear1) ? shiftPoint : _driveGear2;
}
float MyAvatar::getDriveGear2() {
switch (_controlSchemeIndex) {
case LocomotionControlsMode::CONTROLS_ANALOG:
return ANALOG_AVATAR_GEAR_2;
case LocomotionControlsMode::CONTROLS_ANALOG_PLUS:
return _driveGear2;
case LocomotionControlsMode::CONTROLS_DEFAULT:
default:
return 1.0f;
}
}
void MyAvatar::setDriveGear3(float shiftPoint) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setDriveGear3", Q_ARG(float, shiftPoint));
return;
}
if (shiftPoint > 1.0f || shiftPoint < 0.0f) return;
_driveGear3 = (shiftPoint < _driveGear4 && shiftPoint >= _driveGear2) ? shiftPoint : _driveGear3;
}
float MyAvatar::getDriveGear3() {
switch (_controlSchemeIndex) {
case LocomotionControlsMode::CONTROLS_ANALOG:
return ANALOG_AVATAR_GEAR_3;
case LocomotionControlsMode::CONTROLS_ANALOG_PLUS:
return _driveGear3;
case LocomotionControlsMode::CONTROLS_DEFAULT:
default:
return 1.0f;
}
}
void MyAvatar::setDriveGear4(float shiftPoint) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setDriveGear4", Q_ARG(float, shiftPoint));
return;
}
if (shiftPoint > 1.0f || shiftPoint < 0.0f) return;
_driveGear4 = (shiftPoint < _driveGear5 && shiftPoint >= _driveGear3) ? shiftPoint : _driveGear4;
}
float MyAvatar::getDriveGear4() {
switch (_controlSchemeIndex) {
case LocomotionControlsMode::CONTROLS_ANALOG:
return ANALOG_AVATAR_GEAR_4;
case LocomotionControlsMode::CONTROLS_ANALOG_PLUS:
return _driveGear4;
case LocomotionControlsMode::CONTROLS_DEFAULT:
default:
return 1.0f;
}
}
void MyAvatar::setDriveGear5(float shiftPoint) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setDriveGear5", Q_ARG(float, shiftPoint));
return;
}
if (shiftPoint > 1.0f || shiftPoint < 0.0f) return;
_driveGear5 = (shiftPoint > _driveGear4) ? shiftPoint : _driveGear5;
}
float MyAvatar::getDriveGear5() {
switch (_controlSchemeIndex) {
case LocomotionControlsMode::CONTROLS_ANALOG:
return ANALOG_AVATAR_GEAR_5;
case LocomotionControlsMode::CONTROLS_ANALOG_PLUS:
return _driveGear5;
case LocomotionControlsMode::CONTROLS_DEFAULT:
default:
return 1.0f;
}
}
bool MyAvatar::getFlyingHMDPref() {
return _flyingPrefHMD;
}
@ -3921,6 +4217,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;
@ -4464,11 +4767,37 @@ bool MyAvatar::getIsSitStandStateLocked() const {
}
float MyAvatar::getWalkSpeed() const {
return _walkSpeed.get() * _walkSpeedScalar;
if (qApp->isHMDMode()) {
switch (_controlSchemeIndex) {
case LocomotionControlsMode::CONTROLS_ANALOG:
return _analogWalkSpeed.get();
case LocomotionControlsMode::CONTROLS_ANALOG_PLUS:
return _analogPlusWalkSpeed.get();
case LocomotionControlsMode::CONTROLS_DEFAULT:
default:
return _defaultWalkSpeed.get();
}
} else {
return _defaultWalkSpeed.get();
}
}
float MyAvatar::getWalkBackwardSpeed() const {
return _walkSpeed.get() * _walkSpeedScalar;
if (qApp->isHMDMode()) {
switch (_controlSchemeIndex) {
case LocomotionControlsMode::CONTROLS_ANALOG:
return _analogWalkBackwardSpeed.get();
case LocomotionControlsMode::CONTROLS_ANALOG_PLUS:
return _analogPlusWalkBackwardSpeed.get();
case LocomotionControlsMode::CONTROLS_DEFAULT:
default:
return _defaultWalkBackwardSpeed.get();
}
} else {
return _defaultWalkBackwardSpeed.get();
}
}
bool MyAvatar::isReadyForPhysics() const {
@ -4476,7 +4805,7 @@ bool MyAvatar::isReadyForPhysics() const {
}
void MyAvatar::setSprintMode(bool sprint) {
_walkSpeedScalar = sprint ? _sprintSpeed.get() : AVATAR_WALK_SPEED_SCALAR;
_walkSpeedScalar = sprint ? AVATAR_SPRINT_SPEED_SCALAR : AVATAR_WALK_SPEED_SCALAR;
}
void MyAvatar::setIsInWalkingState(bool isWalking) {
@ -4539,19 +4868,103 @@ void MyAvatar::setIsSitStandStateLocked(bool isLocked) {
}
void MyAvatar::setWalkSpeed(float value) {
_walkSpeed.set(value);
switch (_controlSchemeIndex) {
case LocomotionControlsMode::CONTROLS_DEFAULT:
_defaultWalkSpeed.set(value);
break;
case LocomotionControlsMode::CONTROLS_ANALOG:
_analogWalkSpeed.set(value);
break;
case LocomotionControlsMode::CONTROLS_ANALOG_PLUS:
_analogPlusWalkSpeed.set(value);
break;
default:
break;
}
}
void MyAvatar::setWalkBackwardSpeed(float value) {
_walkBackwardSpeed.set(value);
switch (_controlSchemeIndex) {
case LocomotionControlsMode::CONTROLS_DEFAULT:
_defaultWalkBackwardSpeed.set(value);
break;
case LocomotionControlsMode::CONTROLS_ANALOG:
_analogWalkBackwardSpeed.set(value);
break;
case LocomotionControlsMode::CONTROLS_ANALOG_PLUS:
_analogPlusWalkBackwardSpeed.set(value);
break;
default:
break;
}
}
void MyAvatar::setSprintSpeed(float value) {
_sprintSpeed.set(value);
switch (_controlSchemeIndex) {
case LocomotionControlsMode::CONTROLS_DEFAULT:
_defaultSprintSpeed.set(value);
break;
case LocomotionControlsMode::CONTROLS_ANALOG:
_analogSprintSpeed.set(value);
break;
case LocomotionControlsMode::CONTROLS_ANALOG_PLUS:
_analogPlusSprintSpeed.set(value);
break;
default:
break;
}
}
float MyAvatar::getSprintSpeed() const {
return _sprintSpeed.get();
if (qApp->isHMDMode()) {
switch (_controlSchemeIndex) {
case LocomotionControlsMode::CONTROLS_ANALOG:
return _analogSprintSpeed.get();
case LocomotionControlsMode::CONTROLS_ANALOG_PLUS:
return _analogPlusSprintSpeed.get();
case LocomotionControlsMode::CONTROLS_DEFAULT:
default:
return _defaultSprintSpeed.get();
}
} else {
return _defaultSprintSpeed.get();
}
}
void MyAvatar::setAnalogWalkSpeed(float value) {
_analogWalkSpeed.set(value);
// Sprint speed for Analog should be double walk speed.
_analogSprintSpeed.set(value * 2.0f);
}
float MyAvatar::getAnalogWalkSpeed() const {
return _analogWalkSpeed.get();
}
void MyAvatar::setAnalogSprintSpeed(float value) {
_analogSprintSpeed.set(value);
}
float MyAvatar::getAnalogSprintSpeed() const {
return _analogSprintSpeed.get();
}
void MyAvatar::setAnalogPlusWalkSpeed(float value) {
_analogPlusWalkSpeed.set(value);
// Sprint speed for Analog Plus should be double walk speed.
_analogPlusSprintSpeed.set(value * 2.0f);
}
float MyAvatar::getAnalogPlusWalkSpeed() const {
return _analogPlusWalkSpeed.get();
}
void MyAvatar::setAnalogPlusSprintSpeed(float value) {
_analogPlusSprintSpeed.set(value);
}
float MyAvatar::getAnalogPlusSprintSpeed() const {
return _analogPlusSprintSpeed.get();
}
void MyAvatar::setSitStandStateChange(bool stateChanged) {
@ -5370,6 +5783,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",
@ -5419,7 +5850,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;
@ -5434,9 +5865,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()) {

File diff suppressed because it is too large Load diff

View file

@ -356,7 +356,6 @@ void OtherAvatar::simulate(float deltaTime, bool inView) {
PROFILE_RANGE(simulation, "grabs");
applyGrabChanges();
}
updateFadingStatus();
}
void OtherAvatar::handleChangedAvatarEntityData() {
@ -365,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
@ -495,6 +494,18 @@ void OtherAvatar::handleChangedAvatarEntityData() {
const QUuid NULL_ID = QUuid("{00000000-0000-0000-0000-000000000005}");
entity->setParentID(NULL_ID);
entity->setParentID(oldParentID);
if (entity->stillHasMyGrabAction()) {
// For this case: we want to ignore transform+velocities coming from authoritative OtherAvatar
// because the MyAvatar is grabbing and we expect the local grab state
// to have enough information to prevent simulation drift.
//
// Clever readers might realize this could cause problems. For example,
// if an ignored OtherAvagtar were to simultanously grab the object then there would be
// a noticeable discrepancy between participants in the distributed physics simulation,
// however the difference would be stable and would not drift.
properties.clearTransformOrVelocityChanges();
}
if (entityTree->updateEntity(entityID, properties)) {
entity->updateLastEditedFromRemote();
} else {

View file

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

View file

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

View file

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

View file

@ -88,44 +88,44 @@ void Audio::setMuted(bool isMuted) {
void Audio::setMutedDesktop(bool isMuted) {
bool changed = false;
withWriteLock([&] {
if (_desktopMuted != isMuted) {
if (_mutedDesktop != isMuted) {
changed = true;
_desktopMuted = isMuted;
_mutedDesktop = isMuted;
auto client = DependencyManager::get<AudioClient>().data();
QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false));
}
});
if (changed) {
emit mutedChanged(isMuted);
emit desktopMutedChanged(isMuted);
emit mutedDesktopChanged(isMuted);
}
}
bool Audio::getMutedDesktop() const {
return resultWithReadLock<bool>([&] {
return _desktopMuted;
return _mutedDesktop;
});
}
void Audio::setMutedHMD(bool isMuted) {
bool changed = false;
withWriteLock([&] {
if (_hmdMuted != isMuted) {
if (_mutedHMD != isMuted) {
changed = true;
_hmdMuted = isMuted;
_mutedHMD = isMuted;
auto client = DependencyManager::get<AudioClient>().data();
QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false));
}
});
if (changed) {
emit mutedChanged(isMuted);
emit hmdMutedChanged(isMuted);
emit mutedHMDChanged(isMuted);
}
}
bool Audio::getMutedHMD() const {
return resultWithReadLock<bool>([&] {
return _hmdMuted;
return _mutedHMD;
});
}
@ -217,17 +217,17 @@ void Audio::setPTTHMD(bool enabled) {
}
void Audio::saveData() {
_desktopMutedSetting.set(getMutedDesktop());
_hmdMutedSetting.set(getMutedHMD());
_mutedDesktopSetting.set(getMutedDesktop());
_mutedHMDSetting.set(getMutedHMD());
_pttDesktopSetting.set(getPTTDesktop());
_pttHMDSetting.set(getPTTHMD());
}
void Audio::loadData() {
_desktopMuted = _desktopMutedSetting.get();
_hmdMuted = _hmdMutedSetting.get();
_pttDesktop = _pttDesktopSetting.get();
_pttHMD = _pttHMDSetting.get();
setMutedDesktop(_mutedDesktopSetting.get());
setMutedHMD(_mutedHMDSetting.get());
setPTTDesktop(_pttDesktopSetting.get());
setPTTHMD(_pttHMDSetting.get());
auto client = DependencyManager::get<AudioClient>().data();
QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted()), Q_ARG(bool, false));
@ -377,6 +377,18 @@ void Audio::handlePushedToTalk(bool enabled) {
}
}
void Audio::setInputDevice(const QAudioDeviceInfo& device, bool isHMD) {
withWriteLock([&] {
_devices.chooseInputDevice(device, isHMD);
});
}
void Audio::setOutputDevice(const QAudioDeviceInfo& device, bool isHMD) {
withWriteLock([&] {
_devices.chooseOutputDevice(device, isHMD);
});
}
void Audio::setReverb(bool enable) {
withWriteLock([&] {
DependencyManager::get<AudioClient>()->setReverb(enable);
@ -389,14 +401,66 @@ void Audio::setReverbOptions(const AudioEffectOptions* options) {
});
}
void Audio::setInputDevice(const QAudioDeviceInfo& device, bool isHMD) {
void Audio::setAvatarGain(float gain) {
withWriteLock([&] {
_devices.chooseInputDevice(device, isHMD);
// ask the NodeList to set the master avatar gain
DependencyManager::get<NodeList>()->setAvatarGain(QUuid(), gain);
});
}
void Audio::setOutputDevice(const QAudioDeviceInfo& device, bool isHMD) {
withWriteLock([&] {
_devices.chooseOutputDevice(device, isHMD);
float Audio::getAvatarGain() {
return resultWithReadLock<float>([&] {
return DependencyManager::get<NodeList>()->getAvatarGain(QUuid());
});
}
void Audio::setInjectorGain(float gain) {
withWriteLock([&] {
// ask the NodeList to set the audio injector gain
DependencyManager::get<NodeList>()->setInjectorGain(gain);
});
}
float Audio::getInjectorGain() {
return resultWithReadLock<float>([&] {
return DependencyManager::get<NodeList>()->getInjectorGain();
});
}
void Audio::setLocalInjectorGain(float gain) {
withWriteLock([&] {
if (_localInjectorGain != gain) {
_localInjectorGain = gain;
// convert dB to amplitude
gain = fastExp2f(gain / 6.02059991f);
// quantize and limit to match NodeList::setInjectorGain()
gain = unpackFloatGainFromByte(packFloatGainToByte(gain));
DependencyManager::get<AudioClient>()->setLocalInjectorGain(gain);
}
});
}
float Audio::getLocalInjectorGain() {
return resultWithReadLock<float>([&] {
return _localInjectorGain;
});
}
void Audio::setSystemInjectorGain(float gain) {
withWriteLock([&] {
if (_systemInjectorGain != gain) {
_systemInjectorGain = gain;
// convert dB to amplitude
gain = fastExp2f(gain / 6.02059991f);
// quantize and limit to match NodeList::setInjectorGain()
gain = unpackFloatGainFromByte(packFloatGainToByte(gain));
DependencyManager::get<AudioClient>()->setSystemInjectorGain(gain);
}
});
}
float Audio::getSystemInjectorGain() {
return resultWithReadLock<float>([&] {
return _systemInjectorGain;
});
}

View file

@ -41,6 +41,7 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable {
* @hifi-assignment-client
*
* @property {boolean} muted - <code>true</code> if the audio input is muted, otherwise <code>false</code>.
* @property {boolean} mutedDesktop - <code>true</code> if the audio input is muted, otherwise <code>false</code>.
* @property {boolean} noiseReduction - <code>true</code> if noise reduction is enabled, otherwise <code>false</code>. When
* enabled, the input audio signal is blocked (fully attenuated) when it falls below an adaptive threshold set just
* above the noise floor.
@ -68,8 +69,8 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable {
Q_PROPERTY(bool clipping READ isClipping NOTIFY clippingChanged)
Q_PROPERTY(QString context READ getContext NOTIFY contextChanged)
Q_PROPERTY(AudioDevices* devices READ getDevices NOTIFY nop)
Q_PROPERTY(bool desktopMuted READ getMutedDesktop WRITE setMutedDesktop NOTIFY desktopMutedChanged)
Q_PROPERTY(bool hmdMuted READ getMutedHMD WRITE setMutedHMD NOTIFY hmdMutedChanged)
Q_PROPERTY(bool mutedDesktop READ getMutedDesktop WRITE setMutedDesktop NOTIFY mutedDesktopChanged)
Q_PROPERTY(bool mutedHMD READ getMutedHMD WRITE setMutedHMD NOTIFY mutedHMDChanged)
Q_PROPERTY(bool pushToTalk READ getPTT WRITE setPTT NOTIFY pushToTalkChanged);
Q_PROPERTY(bool pushToTalkDesktop READ getPTTDesktop WRITE setPTTDesktop NOTIFY pushToTalkDesktopChanged)
Q_PROPERTY(bool pushToTalkHMD READ getPTTHMD WRITE setPTTHMD NOTIFY pushToTalkHMDChanged)
@ -170,6 +171,66 @@ public:
*/
Q_INVOKABLE void setReverbOptions(const AudioEffectOptions* options);
/**jsdoc
* Sets the avatar gain at the server.
* Units are Decibels (dB)
* @function Audio.setAvatarGain
* @param {number} gain (in dB)
*/
Q_INVOKABLE void setAvatarGain(float gain);
/**jsdoc
* Gets the avatar gain at the server.
* @function Audio.getAvatarGain
* @returns {number} gain (in dB)
*/
Q_INVOKABLE float getAvatarGain();
/**jsdoc
* Sets the injector gain at the server.
* Units are Decibels (dB)
* @function Audio.setInjectorGain
* @param {number} gain (in dB)
*/
Q_INVOKABLE void setInjectorGain(float gain);
/**jsdoc
* Gets the injector gain at the server.
* @function Audio.getInjectorGain
* @returns {number} gain (in dB)
*/
Q_INVOKABLE float getInjectorGain();
/**jsdoc
* Sets the local injector gain in the client.
* Units are Decibels (dB)
* @function Audio.setLocalInjectorGain
* @param {number} gain (in dB)
*/
Q_INVOKABLE void setLocalInjectorGain(float gain);
/**jsdoc
* Gets the local injector gain in the client.
* @function Audio.getLocalInjectorGain
* @returns {number} gain (in dB)
*/
Q_INVOKABLE float getLocalInjectorGain();
/**jsdoc
* Sets the injector gain for system sounds.
* Units are Decibels (dB)
* @function Audio.setSystemInjectorGain
* @param {number} gain (in dB)
*/
Q_INVOKABLE void setSystemInjectorGain(float gain);
/**jsdoc
* Gets the injector gain for system sounds.
* @function Audio.getSystemInjectorGain
* @returns {number} gain (in dB)
*/
Q_INVOKABLE float getSystemInjectorGain();
/**jsdoc
* Starts making an audio recording of the audio being played in-world (i.e., not local-only audio) to a file in WAV format.
* @function Audio.startRecording
@ -227,19 +288,19 @@ signals:
/**jsdoc
* Triggered when desktop audio input is muted or unmuted.
* @function Audio.desktopMutedChanged
* @function Audio.mutedDesktopChanged
* @param {boolean} isMuted - <code>true</code> if the audio input is muted for desktop mode, otherwise <code>false</code>.
* @returns {Signal}
*/
void desktopMutedChanged(bool isMuted);
void mutedDesktopChanged(bool isMuted);
/**jsdoc
* Triggered when HMD audio input is muted or unmuted.
* @function Audio.hmdMutedChanged
* @function Audio.mutedHMDChanged
* @param {boolean} isMuted - <code>true</code> if the audio input is muted for HMD mode, otherwise <code>false</code>.
* @returns {Signal}
*/
void hmdMutedChanged(bool isMuted);
void mutedHMDChanged(bool isMuted);
/**
* Triggered when Push-to-Talk has been enabled or disabled.
@ -350,18 +411,20 @@ private:
float _inputVolume { 1.0f };
float _inputLevel { 0.0f };
float _localInjectorGain { 0.0f }; // in dB
float _systemInjectorGain { 0.0f }; // in dB
bool _isClipping { false };
bool _enableNoiseReduction { true }; // Match default value of AudioClient::_isNoiseGateEnabled.
bool _enableWarnWhenMuted { true };
bool _contextIsHMD { false };
AudioDevices* getDevices() { return &_devices; }
AudioDevices _devices;
Setting::Handle<bool> _desktopMutedSetting{ QStringList { Audio::AUDIO, "desktopMuted" }, true };
Setting::Handle<bool> _hmdMutedSetting{ QStringList { Audio::AUDIO, "hmdMuted" }, true };
Setting::Handle<bool> _mutedDesktopSetting{ QStringList { Audio::AUDIO, "mutedDesktop" }, true };
Setting::Handle<bool> _mutedHMDSetting{ QStringList { Audio::AUDIO, "mutedHMD" }, true };
Setting::Handle<bool> _pttDesktopSetting{ QStringList { Audio::AUDIO, "pushToTalkDesktop" }, false };
Setting::Handle<bool> _pttHMDSetting{ QStringList { Audio::AUDIO, "pushToTalkHMD" }, false };
bool _desktopMuted{ true };
bool _hmdMuted{ false };
bool _mutedDesktop{ true };
bool _mutedHMD{ false };
bool _pttDesktop{ false };
bool _pttHMD{ false };
bool _pushingToTalk{ false };

View file

@ -12,6 +12,7 @@
#include <AudioClient.h>
#include <SettingHandle.h>
#include <trackers/FaceTracker.h>
#include <UsersScriptingInterface.h>
#include "Application.h"
#include "Menu.h"
@ -30,6 +31,10 @@ AvatarInputs* AvatarInputs::getInstance() {
AvatarInputs::AvatarInputs(QObject* parent) : QObject(parent) {
_showAudioTools = showAudioToolsSetting.get();
auto nodeList = DependencyManager::get<NodeList>();
auto usersScriptingInterface = DependencyManager::get<UsersScriptingInterface>();
connect(nodeList.data(), &NodeList::ignoreRadiusEnabledChanged, this, &AvatarInputs::ignoreRadiusEnabledChanged);
connect(usersScriptingInterface.data(), &UsersScriptingInterface::enteredIgnoreRadius, this, &AvatarInputs::enteredIgnoreRadiusChanged);
}
#define AI_UPDATE(name, src) \
@ -83,6 +88,10 @@ void AvatarInputs::setShowAudioTools(bool showAudioTools) {
emit showAudioToolsChanged(_showAudioTools);
}
bool AvatarInputs::getIgnoreRadiusEnabled() const {
return DependencyManager::get<NodeList>()->getIgnoreRadiusEnabled();
}
void AvatarInputs::toggleCameraMute() {
FaceTracker* faceTracker = qApp->getSelectedFaceTracker();
if (faceTracker) {

View file

@ -42,6 +42,8 @@ class AvatarInputs : public QObject {
AI_PROPERTY(bool, isHMD, false)
Q_PROPERTY(bool showAudioTools READ showAudioTools WRITE setShowAudioTools NOTIFY showAudioToolsChanged)
Q_PROPERTY(bool ignoreRadiusEnabled READ getIgnoreRadiusEnabled NOTIFY ignoreRadiusEnabledChanged)
//Q_PROPERTY(bool enteredIgnoreRadius READ getEnteredIgnoreRadius NOTIFY enteredIgnoreRadiusChanged)
public:
static AvatarInputs* getInstance();
@ -55,7 +57,9 @@ public:
AvatarInputs(QObject* parent = nullptr);
void update();
bool showAudioTools() const { return _showAudioTools; }
bool showAudioTools() const { return _showAudioTools; }
bool getIgnoreRadiusEnabled() const;
//bool getEnteredIgnoreRadius() const;
public slots:
@ -93,6 +97,34 @@ signals:
*/
void showAudioToolsChanged(bool show);
/**jsdoc
* @function AvatarInputs.avatarEnteredIgnoreRadius
* @param {QUuid} avatarID
* @returns {Signal}
*/
void avatarEnteredIgnoreRadius(QUuid avatarID);
/**jsdoc
* @function AvatarInputs.avatarLeftIgnoreRadius
* @param {QUuid} avatarID
* @returns {Signal}
*/
void avatarLeftIgnoreRadius(QUuid avatarID);
/**jsdoc
* @function AvatarInputs.ignoreRadiusEnabledChanged
* @param {boolean} enabled
* @returns {Signal}
*/
void ignoreRadiusEnabledChanged(bool enabled);
/**jsdoc
* @function AvatarInputs.enteredIgnoreRadiusChanged
* @param {boolean} enabled
* @returns {Signal}
*/
void enteredIgnoreRadiusChanged();
protected:
/**jsdoc
@ -106,6 +138,8 @@ protected:
Q_INVOKABLE void toggleCameraMute();
private:
void onAvatarEnteredIgnoreRadius();
void onAvatarLeftIgnoreRadius();
float _trailingAudioLoudness{ 0 };
bool _showAudioTools { false };
};

View file

@ -266,6 +266,11 @@ void setupPreferences() {
auto preference = new CheckPreference(VR_MOVEMENT, "Walking", getter, setter);
preferences->addPreference(preference);
}
{
auto getter = [myAvatar]()->bool { return myAvatar->getStrafeEnabled(); };
auto setter = [myAvatar](bool value) { myAvatar->setStrafeEnabled(value); };
preferences->addPreference(new CheckPreference(VR_MOVEMENT, "Strafing", getter, setter));
}
{
auto getter = [myAvatar]()->bool { return myAvatar->getFlyingHMDPref(); };
auto setter = [myAvatar](bool value) { myAvatar->setFlyingHMDPref(value); };
@ -273,6 +278,22 @@ void setupPreferences() {
preference->setIndented(true);
preferences->addPreference(preference);
}
{
auto getter = [myAvatar]()->int { return myAvatar->getMovementReference(); };
auto setter = [myAvatar](int value) { myAvatar->setMovementReference(value); };
//auto preference = new CheckPreference(VR_MOVEMENT, "Hand-Relative Movement", getter, setter);
auto preference = new RadioButtonsPreference(VR_MOVEMENT, "Movement Direction", getter, setter);
QStringList items;
items << "HMD-Relative" << "Hand-Relative" << "Hand-Relative (Leveled)";
preference->setHeading("Movement Direction");
preference->setItems(items);
preferences->addPreference(preference);
}
{
auto getter = [myAvatar]()->QString { return myAvatar->getDominantHand(); };
auto setter = [myAvatar](const QString& value) { myAvatar->setDominantHand(value); };
preferences->addPreference(new PrimaryHandPreference(VR_MOVEMENT, "Dominant Hand", getter, setter));
}
{
auto getter = [myAvatar]()->int { return myAvatar->getSnapTurn() ? 0 : 1; };
auto setter = [myAvatar](int value) { myAvatar->setSnapTurn(value == 0); };
@ -283,6 +304,26 @@ void setupPreferences() {
preference->setItems(items);
preferences->addPreference(preference);
}
{
auto getter = [myAvatar]()->int { return myAvatar->getControlScheme(); };
auto setter = [myAvatar](int index) { myAvatar->setControlScheme(index); };
auto preference = new RadioButtonsPreference(VR_MOVEMENT, "Control Scheme", getter, setter);
QStringList items;
items << "Default" << "Analog" << "Analog++";
preference->setHeading("Control Scheme Selection");
preference->setItems(items);
preferences->addPreference(preference);
}
{
auto getter = [myAvatar]()->float { return myAvatar->getAnalogPlusWalkSpeed(); };
auto setter = [myAvatar](float value) { myAvatar->setAnalogPlusWalkSpeed(value); };
auto preference = new SpinnerSliderPreference(VR_MOVEMENT, "Analog++ Walk Speed", getter, setter);
preference->setMin(6.0f);
preference->setMax(30.0f);
preference->setStep(1);
preference->setDecimals(2);
preferences->addPreference(preference);
}
{
auto getter = [myAvatar]()->bool { return myAvatar->getShowPlayArea(); };
auto setter = [myAvatar](bool value) { myAvatar->setShowPlayArea(value); };

View file

@ -292,7 +292,7 @@ void ContextOverlayInterface::requestOwnershipVerification(const QUuid& entityID
setLastInspectedEntity(entityID);
EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(_lastInspectedEntity, _entityPropertyFlags);
EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(entityID, _entityPropertyFlags);
auto nodeList = DependencyManager::get<NodeList>();
@ -328,31 +328,31 @@ void ContextOverlayInterface::requestOwnershipVerification(const QUuid& entityID
} else {
QString ownerKey = jsonObject["transfer_recipient_key"].toString();
QByteArray certID = entityProperties.getCertificateID().toUtf8();
QByteArray text = DependencyManager::get<EntityTreeRenderer>()->getTree()->computeNonce(certID, ownerKey);
QByteArray id = entityID.toByteArray();
QByteArray text = DependencyManager::get<EntityTreeRenderer>()->getTree()->computeNonce(entityID, ownerKey);
QByteArray nodeToChallengeByteArray = entityProperties.getOwningAvatarID().toRfc4122();
int certIDByteArraySize = certID.length();
int idByteArraySize = id.length();
int textByteArraySize = text.length();
int nodeToChallengeByteArraySize = nodeToChallengeByteArray.length();
auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnershipRequest,
certIDByteArraySize + textByteArraySize + nodeToChallengeByteArraySize + 3 * sizeof(int),
idByteArraySize + textByteArraySize + nodeToChallengeByteArraySize + 3 * sizeof(int),
true);
challengeOwnershipPacket->writePrimitive(certIDByteArraySize);
challengeOwnershipPacket->writePrimitive(idByteArraySize);
challengeOwnershipPacket->writePrimitive(textByteArraySize);
challengeOwnershipPacket->writePrimitive(nodeToChallengeByteArraySize);
challengeOwnershipPacket->write(certID);
challengeOwnershipPacket->write(id);
challengeOwnershipPacket->write(text);
challengeOwnershipPacket->write(nodeToChallengeByteArray);
nodeList->sendPacket(std::move(challengeOwnershipPacket), *entityServer);
// Kickoff a 10-second timeout timer that marks the cert if we don't get an ownership response in time
if (thread() != QThread::currentThread()) {
QMetaObject::invokeMethod(this, "startChallengeOwnershipTimer");
QMetaObject::invokeMethod(this, "startChallengeOwnershipTimer", Q_ARG(const EntityItemID&, entityID));
return;
} else {
startChallengeOwnershipTimer();
startChallengeOwnershipTimer(entityID);
}
}
} else {
@ -370,14 +370,14 @@ void ContextOverlayInterface::requestOwnershipVerification(const QUuid& entityID
// so they always pass Ownership Verification. It's necessary to emit this signal
// so that the Inspection Certificate can continue its information-grabbing process.
auto ledger = DependencyManager::get<Ledger>();
emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_SUCCESS));
emit ledger->updateCertificateStatus(entityID, (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_SUCCESS));
}
} else {
auto ledger = DependencyManager::get<Ledger>();
_challengeOwnershipTimeoutTimer.stop();
emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED));
emit DependencyManager::get<WalletScriptingInterface>()->ownershipVerificationFailed(_lastInspectedEntity);
qCDebug(context_overlay) << "Entity" << _lastInspectedEntity << "failed static certificate verification!";
emit ledger->updateCertificateStatus(entityID, (uint)(ledger->CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED));
emit DependencyManager::get<WalletScriptingInterface>()->ownershipVerificationFailed(entityID);
qCDebug(context_overlay) << "Entity" << entityID << "failed static certificate verification!";
}
}
@ -395,14 +395,14 @@ void ContextOverlayInterface::deletingEntity(const EntityItemID& entityID) {
}
}
void ContextOverlayInterface::startChallengeOwnershipTimer() {
void ContextOverlayInterface::startChallengeOwnershipTimer(const EntityItemID& entityItemID) {
auto ledger = DependencyManager::get<Ledger>();
EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(_lastInspectedEntity, _entityPropertyFlags);
EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(entityItemID, _entityPropertyFlags);
connect(&_challengeOwnershipTimeoutTimer, &QTimer::timeout, this, [=]() {
qCDebug(entities) << "Ownership challenge timed out for" << _lastInspectedEntity;
emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_TIMEOUT));
emit DependencyManager::get<WalletScriptingInterface>()->ownershipVerificationFailed(_lastInspectedEntity);
qCDebug(entities) << "Ownership challenge timed out for" << entityItemID;
emit ledger->updateCertificateStatus(entityItemID, (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_TIMEOUT));
emit DependencyManager::get<WalletScriptingInterface>()->ownershipVerificationFailed(entityItemID);
});
_challengeOwnershipTimeoutTimer.start(5000);
@ -413,23 +413,22 @@ void ContextOverlayInterface::handleChallengeOwnershipReplyPacket(QSharedPointer
_challengeOwnershipTimeoutTimer.stop();
int certIDByteArraySize;
int idByteArraySize;
int textByteArraySize;
packet->readPrimitive(&certIDByteArraySize);
packet->readPrimitive(&idByteArraySize);
packet->readPrimitive(&textByteArraySize);
QString certID(packet->read(certIDByteArraySize));
EntityItemID id(packet->read(idByteArraySize));
QString text(packet->read(textByteArraySize));
EntityItemID id;
bool verificationSuccess = DependencyManager::get<EntityTreeRenderer>()->getTree()->verifyNonce(certID, text, id);
bool verificationSuccess = DependencyManager::get<EntityTreeRenderer>()->getTree()->verifyNonce(id, text);
if (verificationSuccess) {
emit ledger->updateCertificateStatus(certID, (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_SUCCESS));
emit DependencyManager::get<WalletScriptingInterface>()->ownershipVerificationSuccess(_lastInspectedEntity);
emit ledger->updateCertificateStatus(id, (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_SUCCESS));
emit DependencyManager::get<WalletScriptingInterface>()->ownershipVerificationSuccess(id);
} else {
emit ledger->updateCertificateStatus(certID, (uint)(ledger->CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED));
emit DependencyManager::get<WalletScriptingInterface>()->ownershipVerificationFailed(_lastInspectedEntity);
emit ledger->updateCertificateStatus(id, (uint)(ledger->CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED));
emit DependencyManager::get<WalletScriptingInterface>()->ownershipVerificationFailed(id);
}
}

View file

@ -46,7 +46,7 @@ public:
ContextOverlayInterface();
Q_INVOKABLE QUuid getCurrentEntityWithContextOverlay() { return _currentEntityWithContextOverlay; }
void setCurrentEntityWithContextOverlay(const QUuid& entityID) { _currentEntityWithContextOverlay = entityID; }
void setLastInspectedEntity(const QUuid& entityID) { _challengeOwnershipTimeoutTimer.stop(); _lastInspectedEntity = entityID; }
void setLastInspectedEntity(const QUuid& entityID) { _challengeOwnershipTimeoutTimer.stop(); }
void setEnabled(bool enabled);
bool getEnabled() { return _enabled; }
bool getIsInMarketplaceInspectionMode() { return _isInMarketplaceInspectionMode; }
@ -83,7 +83,6 @@ private:
EntityItemID _mouseDownEntity;
quint64 _mouseDownEntityTimestamp;
EntityItemID _currentEntityWithContextOverlay;
EntityItemID _lastInspectedEntity;
QString _entityMarketplaceID;
bool _contextOverlayJustClicked { false };
@ -94,7 +93,7 @@ private:
void deletingEntity(const EntityItemID& entityItemID);
Q_INVOKABLE void startChallengeOwnershipTimer();
Q_INVOKABLE void startChallengeOwnershipTimer(const EntityItemID& entityItemID);
QTimer _challengeOwnershipTimeoutTimer;
};

View file

@ -1711,9 +1711,9 @@ QVector<QUuid> Overlays::findOverlays(const glm::vec3& center, float radius) {
* @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if
* <code>parentID</code> is an avatar skeleton. A value of <code>65535</code> means "no joint".
*
* @property {boolean} isFacingAvatar - If <code>true< / code>, the overlay is rotated to face the user's camera about an axis
* @property {boolean} isFacingAvatar - If <code>true</code>, the overlay is rotated to face the user's camera about an axis
* parallel to the user's avatar's "up" direction.
* @property {string} text="" - The text to display.Text does not automatically wrap; use <code>\n< / code> for a line break.
* @property {string} text="" - The text to display.Text does not automatically wrap; use <code>\n</code> for a line break.
* @property {number} textAlpha=1 - The text alpha value.
* @property {Color} backgroundColor=0,0,0 - The background color.
* @property {number} backgroundAlpha=0.7 - The background alpha value.
@ -1876,7 +1876,7 @@ QVector<QUuid> Overlays::findOverlays(const glm::vec3& center, float radius) {
* @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a
* <code>parentID</code> set, otherwise the same value as <code>position</code>.
* @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a
* <code>parentID</code> set, otherwise the same value as <code>rotation</code>. Synonym: <code>localOrientation</code>.
* <code>parentID</code> set, otherwise the same value as <code>rotation</code>. Synonym: <code>localOrientation</code>.
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of objects in the world, but behind the HUD.
* @property {boolean} drawHUDLayer=false - If <code>true</code>, the overlay is rendered in front of everything, including the HUD.
@ -1916,7 +1916,7 @@ QVector<QUuid> Overlays::findOverlays(const glm::vec3& center, float radius) {
* @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a
* <code>parentID</code> set, otherwise the same value as <code>position</code>.
* @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a
* <code>parentID</code> set, otherwise the same value as <code>rotation</code>. Synonym: <code>localOrientation</code>.
* <code>parentID</code> set, otherwise the same value as <code>rotation</code>. Synonym: <code>localOrientation</code>.
* @property {boolean} isSolid=false - Synonyms: <ode>solid</code>, <code>isFilled</code>, and <code>filled</code>.
* Antonyms: <code>isWire</code> and <code>wire</code>.
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
@ -1927,46 +1927,46 @@ QVector<QUuid> Overlays::findOverlays(const glm::vec3& center, float radius) {
* @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if
* <code>parentID</code> is an avatar skeleton. A value of <code>65535</code> means "no joint".
*
* @property {number} startAt = 0 - The counter - clockwise angle from the overlay's x-axis that drawing starts at, in degrees.
* @property {number} endAt = 360 - The counter - clockwise angle from the overlay's x-axis that drawing ends at, in degrees.
* @property {number} outerRadius = 1 - The outer radius of the overlay, in meters.Synonym: <code>radius< / code>.
* @property {number} innerRadius = 0 - The inner radius of the overlay, in meters.
* @property {Color} color = 255, 255, 255 - The color of the overlay.Setting this value also sets the values of
* <code>innerStartColor< / code>, <code>innerEndColor< / code>, <code>outerStartColor< / code>, and <code>outerEndColor< / code>.
* @property {Color} startColor - Sets the values of <code>innerStartColor< / code> and <code>outerStartColor< / code>.
* <em>Write - only.< / em>
* @property {Color} endColor - Sets the values of <code>innerEndColor< / code> and <code>outerEndColor< / code>.
* <em>Write - only.< / em>
* @property {Color} innerColor - Sets the values of <code>innerStartColor< / code> and <code>innerEndColor< / code>.
* <em>Write - only.< / em>
* @property {Color} outerColor - Sets the values of <code>outerStartColor< / code> and <code>outerEndColor< / code>.
* <em>Write - only.< / em>
* @property {number} startAt = 0 - The counter - clockwise angle from the overlay's x-axis that drawing starts at in degrees.
* @property {number} endAt = 360 - The counter - clockwise angle from the overlay's x-axis that drawing ends at in degrees.
* @property {number} outerRadius = 1 - The outer radius of the overlay in meters. Synonym: <code>radius</code>.
* @property {number} innerRadius = 0 - The inner radius of the overlay in meters.
* @property {Color} color = 255, 255, 255 - The color of the overlay. Setting this value also sets the values of
* <code>innerStartColor</code>, <code>innerEndColor</code>, <code>outerStartColor</code>, and <code>outerEndColor</code>.
* @property {Color} startColor - Sets the values of <code>innerStartColor</code> and <code>outerStartColor</code>.
* <em>Write - only.</em>
* @property {Color} endColor - Sets the values of <code>innerEndColor</code> and <code>outerEndColor</code>.
* <em>Write - only.</em>
* @property {Color} innerColor - Sets the values of <code>innerStartColor</code> and <code>innerEndColor</code>.
* <em>Write - only.</em>
* @property {Color} outerColor - Sets the values of <code>outerStartColor</code> and <code>outerEndColor</code>.
* <em>Write - only.</em>
* @property {Color} innerStartcolor - The color at the inner start point of the overlay.
* @property {Color} innerEndColor - The color at the inner end point of the overlay.
* @property {Color} outerStartColor - The color at the outer start point of the overlay.
* @property {Color} outerEndColor - The color at the outer end point of the overlay.
* @property {number} alpha = 0.5 - The opacity of the overlay, <code>0.0< / code> -<code>1.0< / code>.Setting this value also sets
* the values of <code>innerStartAlpha< / code>, <code>innerEndAlpha< / code>, <code>outerStartAlpha< / code>, and
* <code>outerEndAlpha< / code>.Synonym: <code>Alpha< / code>; <em>write - only< / em>.
* @property {number} startAlpha - Sets the values of <code>innerStartAlpha< / code> and <code>outerStartAlpha< / code>.
* <em>Write - only.< / em>
* @property {number} endAlpha - Sets the values of <code>innerEndAlpha< / code> and <code>outerEndAlpha< / code>.
* <em>Write - only.< / em>
* @property {number} innerAlpha - Sets the values of <code>innerStartAlpha< / code> and <code>innerEndAlpha< / code>.
* <em>Write - only.< / em>
* @property {number} outerAlpha - Sets the values of <code>outerStartAlpha< / code> and <code>outerEndAlpha< / code>.
* <em>Write - only.< / em>
* @property {number} alpha = 0.5 - The opacity of the overlay, <code>0.0</code> -<code>1.0</code>. Setting this value also sets
* the values of <code>innerStartAlpha</code>, <code>innerEndAlpha</code>, <code>outerStartAlpha</code>, and
* <code>outerEndAlpha</code>. Synonym: <code>Alpha</code>; <em>write - only</em>.
* @property {number} startAlpha - Sets the values of <code>innerStartAlpha</code> and <code>outerStartAlpha</code>.
* <em>Write - only.</em>
* @property {number} endAlpha - Sets the values of <code>innerEndAlpha</code> and <code>outerEndAlpha</code>.
* <em>Write - only.</em>
* @property {number} innerAlpha - Sets the values of <code>innerStartAlpha</code> and <code>innerEndAlpha</code>.
* <em>Write - only.</em>
* @property {number} outerAlpha - Sets the values of <code>outerStartAlpha</code> and <code>outerEndAlpha</code>.
* <em>Write - only.</em>
* @property {number} innerStartAlpha = 0 - The alpha at the inner start point of the overlay.
* @property {number} innerEndAlpha = 0 - The alpha at the inner end point of the overlay.
* @property {number} outerStartAlpha = 0 - The alpha at the outer start point of the overlay.
* @property {number} outerEndAlpha = 0 - The alpha at the outer end point of the overlay.
*
* @property {boolean} hasTickMarks = false - If <code>true< / code>, tick marks are drawn.
* @property {boolean} hasTickMarks = false - If <code>true</code>, tick marks are drawn.
* @property {number} majorTickMarksAngle = 0 - The angle between major tick marks, in degrees.
* @property {number} minorTickMarksAngle = 0 - The angle between minor tick marks, in degrees.
* @property {number} majorTickMarksLength = 0 - The length of the major tick marks, in meters.A positive value draws tick marks
* @property {number} majorTickMarksLength = 0 - The length of the major tick marks, in meters. A positive value draws tick marks
* outwards from the inner radius; a negative value draws tick marks inwards from the outer radius.
* @property {number} minorTickMarksLength = 0 - The length of the minor tick marks, in meters.A positive value draws tick marks
* @property {number} minorTickMarksLength = 0 - The length of the minor tick marks, in meters. A positive value draws tick marks
* outwards from the inner radius; a negative value draws tick marks inwards from the outer radius.
* @property {Color} majorTickMarksColor = 0, 0, 0 - The color of the major tick marks.
* @property {Color} minorTickMarksColor = 0, 0, 0 - The color of the minor tick marks.

View file

@ -59,6 +59,46 @@ public:
float getMaxErrorOnLastSolve() { return _maxErrorOnLastSolve; }
/**jsdoc
* <p>Specifies the initial conditions of the IK solver.</p>
* <table>
* <thead>
* <tr><th>Value</th><th>Name</p><th>Description</th>
* </thead>
* <tbody>
* <tr><td><code>0</code></td><td>RelaxToUnderPoses</td><td>This is a blend: it is 15/16 <code>PreviousSolution</code>
* and 1/16 <code>UnderPoses</code>. This provides some of the benefits of using <code>UnderPoses</code> so that the
* underlying animation is still visible, while at the same time converging faster then using the
* <code>UnderPoses</code> as the only initial solution.</td></tr>
* <tr><td><code>1</code></td><td>RelaxToLimitCenterPoses</td><td>This is a blend: it is 15/16
* <code>PreviousSolution</code> and 1/16 <code>LimitCenterPoses</code>. This should converge quickly because it is
* close to the previous solution, but still provides the benefits of avoiding limb locking.</td></tr>
* <tr><td><code>2</code></td><td>PreviousSolution</td><td>The IK system will begin to solve from the same position and
* orientations for each joint that was the result from the previous frame.<br />
* Pros: As the end effectors typically do not move much from frame to frame, this is likely to converge quickly
* to a valid solution.<br />
* Cons: If the previous solution resulted in an awkward or uncomfortable posture, the next frame will also be
* awkward and uncomfortable. It can also result in locked elbows and knees.</td></tr>
* <tr><td><code>3</code></td><td>UnderPoses</td><td>The IK occurs at one of the top-most layers. It has access to the
* full posture that was computed via canned animations and blends. We call this animated set of poses the "under
* pose". The under poses are what would be visible if IK was completely disabled. Using the under poses as the
* initial conditions of the CCD solve will cause some of the animated motion to be blended into the result of the
* IK. This can result in very natural results, especially if there are only a few IK targets enabled. On the other
* hand, because the under poses might be quite far from the desired end effector, it can converge slowly in some
* cases, causing it to never reach the IK target in the allotted number of iterations. Also, in situations where all
* of the IK targets are being controlled by external sensors, sometimes starting from the under poses can cause
* awkward motions from the underlying animations to leak into the IK result.</td></tr>
* <tr><td><code>4</code></td><td>LimitCenterPoses</td><td>This pose is taken to be the center of all the joint
* constraints. This can prevent the IK solution from getting locked or stuck at a particular constraint. For
* example, if the arm is pointing straight outward from the body, as the end effector moves towards the body, at
* some point the elbow should bend to accommodate. However, because the CCD solver is stuck at a local maximum, it
* will not rotate the elbow, unless the initial conditions already have the elbow bent, which is the case for
* <code>LimitCenterPoses</code>. When all the IK targets are enabled, this result will provide a consistent starting
* point for each IK solve, hopefully resulting in a consistent, natural result.</td></tr>
* </tbody>
* </table>
* @typedef {number} MyAvatar.AnimIKSolutionSource
*/
enum class SolutionSource {
RelaxToUnderPoses = 0,
RelaxToLimitCenterPoses,

View file

@ -24,6 +24,37 @@ class AnimOverlay : public AnimNode {
public:
friend class AnimTests;
/**jsdoc
* <p>Specifies sets of joints.</p>
* <table>
* <thead>
* <tr><th>Value</th><th>Name</p><th>Description</th>
* </thead>
* <tbody>
* <tr><td><code>0</code></td><td>FullBodyBoneSet</td><td>All joints.</td></tr>
* <tr><td><code>1</code></td><td>UpperBodyBoneSet</td><td>Only the "Spine" joint and its children.</td></tr>
* <tr><td><code>2</code></td><td>LowerBodyBoneSet</td><td>Only the leg joints and their children.</td></tr>
* <tr><td><code>3</code></td><td>LeftArmBoneSet</td><td>Joints that are the children of the "LeftShoulder"
* joint.</td></tr>
* <tr><td><code>4</code></td><td>RightArmBoneSet</td><td>Joints that are the children of the "RightShoulder"
* joint.</td></tr>
* <tr><td><code>5</code></td><td>AboveTheHeadBoneSet</td><td>Joints that are the children of the "Head"
* joint.</td></tr>
* <tr><td><code>6</code></td><td>BelowTheHeadBoneSet</td><td>Joints that are NOT the children of the "head"
* joint.</td></tr>
* <tr><td><code>7</code></td><td>HeadOnlyBoneSet</td><td>The "Head" joint.</td></tr>
* <tr><td><code>8</code></td><td>SpineOnlyBoneSet</td><td>The "Spine" joint.</td></tr>
* <tr><td><code>9</code></td><td>EmptyBoneSet</td><td>No joints.</td></tr>
* <tr><td><code>10</code></td><td>LeftHandBoneSet</td><td>joints that are the children of the "LeftHand"
* joint.</td></tr>
* <tr><td><code>11</code></td><td>RightHandBoneSet</td><td>Joints that are the children of the "RightHand"
* joint.</td></tr>
* <tr><td><code>12</code></td><td>HipsOnlyBoneSet</td><td>The "Hips" joint.</td></tr>
* <tr><td><code>13</code></td><td>BothFeetBoneSet</td><td>The "LeftFoot" and "RightFoot" joints.</td></tr>
* </tbody>
* </table>
* @typedef {number} MyAvatar.AnimOverlayBoneSet
*/
enum BoneSet {
FullBodyBoneSet = 0,
UpperBodyBoneSet,

View file

@ -67,17 +67,23 @@ void FlowCollisionSystem::addCollisionSphere(int jointIndex, const FlowCollision
auto collision = FlowCollisionSphere(jointIndex, settings, isTouch);
collision.setPosition(position);
if (isSelfCollision) {
_selfCollisions.push_back(collision);
if (!isTouch) {
_selfCollisions.push_back(collision);
} else {
_selfTouchCollisions.push_back(collision);
}
} else {
_othersCollisions.push_back(collision);
}
};
void FlowCollisionSystem::resetCollisions() {
_allCollisions.clear();
_othersCollisions.clear();
_selfTouchCollisions.clear();
_selfCollisions.clear();
}
FlowCollisionResult FlowCollisionSystem::computeCollision(const std::vector<FlowCollisionResult> collisions) {
FlowCollisionResult result;
if (collisions.size() > 1) {
@ -106,6 +112,10 @@ void FlowCollisionSystem::setScale(float scale) {
_selfCollisions[j]._radius = _selfCollisions[j]._initialRadius * scale;
_selfCollisions[j]._offset = _selfCollisions[j]._initialOffset * scale;
}
for (size_t j = 0; j < _selfTouchCollisions.size(); j++) {
_selfTouchCollisions[j]._radius = _selfTouchCollisions[j]._initialRadius * scale;
_selfTouchCollisions[j]._offset = _selfTouchCollisions[j]._initialOffset * scale;
}
};
std::vector<FlowCollisionResult> FlowCollisionSystem::checkFlowThreadCollisions(FlowThread* flowThread) {
@ -178,9 +188,9 @@ void FlowCollisionSystem::setCollisionSettingsByJoint(int jointIndex, const Flow
}
void FlowCollisionSystem::prepareCollisions() {
_allCollisions.clear();
_allCollisions.resize(_selfCollisions.size() + _othersCollisions.size());
std::copy(_selfCollisions.begin(), _selfCollisions.begin() + _selfCollisions.size(), _allCollisions.begin());
std::copy(_othersCollisions.begin(), _othersCollisions.begin() + _othersCollisions.size(), _allCollisions.begin() + _selfCollisions.size());
_allCollisions.insert(_allCollisions.end(), _selfCollisions.begin(), _selfCollisions.end());
_allCollisions.insert(_allCollisions.end(), _othersCollisions.begin(), _othersCollisions.end());
_allCollisions.insert(_allCollisions.end(), _selfTouchCollisions.begin(), _selfTouchCollisions.end());
_othersCollisions.clear();
}
@ -273,18 +283,20 @@ void FlowJoint::setRecoveryPosition(const glm::vec3& recoveryPosition) {
}
void FlowJoint::update(float deltaTime) {
glm::vec3 accelerationOffset = glm::vec3(0.0f);
if (_settings._stiffness > 0.0f) {
glm::vec3 recoveryVector = _recoveryPosition - _currentPosition;
float recoveryFactor = powf(_settings._stiffness, 3.0f);
accelerationOffset = recoveryVector * recoveryFactor;
}
FlowNode::update(deltaTime, accelerationOffset);
if (_anchored) {
if (!_isHelper) {
_currentPosition = _updatedPosition;
} else {
_currentPosition = _parentPosition;
if (_settings._active) {
glm::vec3 accelerationOffset = glm::vec3(0.0f);
if (_settings._stiffness > 0.0f) {
glm::vec3 recoveryVector = _recoveryPosition - _currentPosition;
float recoveryFactor = powf(_settings._stiffness, 3.0f);
accelerationOffset = recoveryVector * recoveryFactor;
}
FlowNode::update(deltaTime, accelerationOffset);
if (_anchored) {
if (!_isHelper) {
_currentPosition = _updatedPosition;
} else {
_currentPosition = _parentPosition;
}
}
}
};
@ -674,6 +686,14 @@ bool Flow::updateRootFramePositions(const AnimPoseVec& absolutePoses, size_t thr
return true;
}
void Flow::updateCollisionJoint(FlowCollisionSphere& collision, AnimPoseVec& absolutePoses) {
glm::quat jointRotation;
getJointPositionInWorldFrame(absolutePoses, collision._jointIndex, collision._position, _entityPosition, _entityRotation);
getJointRotationInWorldFrame(absolutePoses, collision._jointIndex, jointRotation, _entityRotation);
glm::vec3 worldOffset = jointRotation * collision._offset;
collision._position = collision._position + worldOffset;
}
void Flow::updateJoints(AnimPoseVec& relativePoses, AnimPoseVec& absolutePoses) {
updateAbsolutePoses(relativePoses, absolutePoses);
for (auto &jointData : _flowJointData) {
@ -695,11 +715,11 @@ void Flow::updateJoints(AnimPoseVec& relativePoses, AnimPoseVec& absolutePoses)
}
auto &selfCollisions = _collisionSystem.getSelfCollisions();
for (auto &collision : selfCollisions) {
glm::quat jointRotation;
getJointPositionInWorldFrame(absolutePoses, collision._jointIndex, collision._position, _entityPosition, _entityRotation);
getJointRotationInWorldFrame(absolutePoses, collision._jointIndex, jointRotation, _entityRotation);
glm::vec3 worldOffset = jointRotation * collision._offset;
collision._position = collision._position + worldOffset;
updateCollisionJoint(collision, absolutePoses);
}
auto &selfTouchCollisions = _collisionSystem.getSelfTouchCollisions();
for (auto &collision : selfTouchCollisions) {
updateCollisionJoint(collision, absolutePoses);
}
_collisionSystem.prepareCollisions();
}
@ -710,7 +730,7 @@ void Flow::setJoints(AnimPoseVec& relativePoses, const std::vector<bool>& overri
for (int jointIndex : joints) {
auto &joint = _flowJointData[jointIndex];
if (jointIndex >= 0 && jointIndex < (int)relativePoses.size() && !overrideFlags[jointIndex]) {
relativePoses[jointIndex].rot() = joint.getCurrentRotation();
relativePoses[jointIndex].rot() = joint.getSettings()._active ? joint.getCurrentRotation() : joint.getInitialRotation();
}
}
}

View file

@ -140,6 +140,7 @@ public:
std::vector<FlowCollisionResult> checkFlowThreadCollisions(FlowThread* flowThread);
std::vector<FlowCollisionSphere>& getSelfCollisions() { return _selfCollisions; };
std::vector<FlowCollisionSphere>& getSelfTouchCollisions() { return _selfTouchCollisions; };
void setOthersCollisions(const std::vector<FlowCollisionSphere>& othersCollisions) { _othersCollisions = othersCollisions; }
void prepareCollisions();
void resetCollisions();
@ -150,9 +151,11 @@ public:
void setActive(bool active) { _active = active; }
bool getActive() const { return _active; }
const std::vector<FlowCollisionSphere>& getCollisions() const { return _selfCollisions; }
void clearSelfCollisions() { _selfCollisions.clear(); }
protected:
std::vector<FlowCollisionSphere> _selfCollisions;
std::vector<FlowCollisionSphere> _othersCollisions;
std::vector<FlowCollisionSphere> _selfTouchCollisions;
std::vector<FlowCollisionSphere> _allCollisions;
float _scale { 1.0f };
bool _active { false };
@ -210,7 +213,7 @@ public:
bool isHelper() const { return _isHelper; }
const FlowPhysicsSettings& getSettings() { return _settings; }
void setSettings(const FlowPhysicsSettings& settings) { _settings = settings; }
void setSettings(const FlowPhysicsSettings& settings) { _settings = settings; _initialRadius = _settings._radius; }
const glm::vec3& getCurrentPosition() const { return _currentPosition; }
int getIndex() const { return _index; }
@ -222,6 +225,7 @@ public:
const glm::quat& getCurrentRotation() const { return _currentRotation; }
const glm::vec3& getCurrentTranslation() const { return _initialTranslation; }
const glm::vec3& getInitialPosition() const { return _initialPosition; }
const glm::quat& getInitialRotation() const { return _initialRotation; }
bool isColliding() const { return _colliding; }
protected:
@ -297,6 +301,7 @@ public:
void setPhysicsSettingsForGroup(const QString& group, const FlowPhysicsSettings& settings);
const std::map<QString, FlowPhysicsSettings>& getGroupSettings() const { return _groupSettings; }
void cleanUp();
void updateScale() { setScale(_scale); }
signals:
void onCleanup();
@ -311,6 +316,7 @@ private:
void setJoints(AnimPoseVec& relativePoses, const std::vector<bool>& overrideFlags);
void updateJoints(AnimPoseVec& relativePoses, AnimPoseVec& absolutePoses);
void updateCollisionJoint(FlowCollisionSphere& collision, AnimPoseVec& absolutePoses);
bool updateRootFramePositions(const AnimPoseVec& absolutePoses, size_t threadIndex);
void updateGroupSettings(const QString& group, const FlowPhysicsSettings& settings);
void setScale(float scale);

View file

@ -16,6 +16,27 @@ const float HACK_HMD_TARGET_WEIGHT = 8.0f;
class IKTarget {
public:
/**jsdoc
* <p>An IK target type.</p>
* <table>
* <thead>
* <tr><th>Value</th><th>Name</p><th>Description</th>
* </thead>
* <tbody>
* <tr><td><code>0</code></td><td>RotationAndPosition</td><td>Attempt to reach the rotation and position end
* effector.</td></tr>
* <tr><td><code>1</code></td><td>RotationOnly</td><td>Attempt to reach the end effector rotation only.</td></tr>
* <tr><td><code>2</code></td><td>HmdHead</td><td><strong>Deprecated:</strong> A special mode of IK that would attempt
* to prevent unnecessary bending of the spine.</td></tr>
* <tr><td><code>3</code></td><td>HipsRelativeRotationAndPosition</td><td>Attempt to reach a rotation and position end
* effector that is not in absolute rig coordinates but is offset by the avatar hips translation.</td></tr>
* <tr><td><code>4</code></td><td>Spline</td><td>Use a cubic Hermite spline to model the human spine. This prevents
* kinks in the spine and allows for a small amount of stretch and squash.</td></tr>
* <tr><td><code>5</code></td><td>Unknown</td><td>IK is disabled.</td></tr>
* </tbody>
* </table>
* @typedef {number} MyAvatar.IKTargetType
*/
enum class Type {
RotationAndPosition,
RotationOnly,

View file

@ -88,6 +88,218 @@ static const QString MAIN_STATE_MACHINE_RIGHT_HAND_ROTATION("mainStateMachineRig
static const QString MAIN_STATE_MACHINE_RIGHT_HAND_POSITION("mainStateMachineRightHandPosition");
/**jsdoc
* <p>An <code>AnimStateDictionary</code> object may have the following properties. It may also have other properties, set by
* scripts.</p>
* <p><strong>Warning:</strong> These properties are subject to change.
* <table>
* <thead>
* <tr><th>Name</th><th>Type</p><th>Description</th>
* </thead>
* <tbody>
* <tr><td><code>userAnimNone</code></td><td>boolean</td><td><code>true</code> when no user overrideAnimation is
* playing.</td></tr>
* <tr><td><code>userAnimA</code></td><td>boolean</td><td><code>true</code> when a user overrideAnimation is
* playing.</td></tr>
* <tr><td><code>userAnimB</code></td><td>boolean</td><td><code>true</code> when a user overrideAnimation is
* playing.</td></tr>
*
* <tr><td><code>sine</code></td><td>number</td><td>Oscillating sine wave.</td></tr>
* <tr><td><code>moveForwardSpeed</code></td><td>number</td><td>Controls the blend between the various forward walking
* &amp; running animations.</td></tr>
* <tr><td><code>moveBackwardSpeed</code></td><td>number</td><td>Controls the blend between the various backward walking
* &amp; running animations.</td></tr>
* <tr><td><code>moveLateralSpeed</code></td><td>number</td><td>Controls the blend between the various sidestep walking
* &amp; running animations.</td></tr>
*
* <tr><td><code>isMovingForward</code></td><td>boolean</td><td><code>true</code> if the avatar is moving
* forward.</td></tr>
* <tr><td><code>isMovingBackward</code></td><td>boolean</td><td><code>true</code> if the avatar is moving
* backward.</td></tr>
* <tr><td><code>isMovingRight</code></td><td>boolean</td><td><code>true</code> if the avatar is moving to the
* right.</td></tr>
* <tr><td><code>isMovingLeft</code></td><td>boolean</td><td><code>true</code> if the avatar is moving to the
* left.</td></tr>
* <tr><td><code>isMovingRightHmd</code></td><td>boolean</td><td><code>true</code> if the avatar is moving to the right
* while the user is in HMD mode.</td></tr>
* <tr><td><code>isMovingLeftHmd</code></td><td>boolean</td><td><code>true</code> if the avatar is moving to the left while
* the user is in HMD mode.</td></tr>
* <tr><td><code>isNotMoving</code></td><td>boolean</td><td><code>true</code> if the avatar is stationary.</td></tr>
*
* <tr><td><code>isTurningRight</code></td><td>boolean</td><td><code>true</code> if the avatar is turning
* clockwise.</td></tr>
* <tr><td><code>isTurningLeft</code></td><td>boolean</td><td><code>true</code> if the avatar is turning
* counter-clockwise.</td></tr>
* <tr><td><code>isNotTurning</code></td><td>boolean</td><td><code>true</code> if the avatar is not turning.</td></tr>
* <tr><td><code>isFlying</code></td><td>boolean</td><td><code>true</code> if the avatar is flying.</td></tr>
* <tr><td><code>isNotFlying</code></td><td>boolean</td><td><code>true</code> if the avatar is not flying.</td></tr>
* <tr><td><code>isTakeoffStand</code></td><td>boolean</td><td><code>true</code> if the avatar is about to execute a
* standing jump.</td></tr>
* <tr><td><code>isTakeoffRun</code></td><td>boolean</td><td><code>true</code> if the avatar is about to execute a running
* jump.</td></tr>
* <tr><td><code>isNotTakeoff</code></td><td>boolean</td><td><code>true</code> if the avatar is not jumping.</td></tr>
* <tr><td><code>isInAirStand</code></td><td>boolean</td><td><code>true</code> if the avatar is in the air after a standing
* jump.</td></tr>
* <tr><td><code>isInAirRun</code></td><td>boolean</td><td><code>true</code> if the avatar is in the air after a running
* jump.</td></tr>
* <tr><td><code>isNotInAir</code></td><td>boolean</td><td><code>true</code> if the avatar on the ground.</td></tr>
*
* <tr><td><code>inAirAlpha</code></td><td>number</td><td>Used to interpolate between the up, apex, and down in-air
* animations.</td></tr>
* <tr><td><code>ikOverlayAlpha</code></td><td>number</td><td>The blend between upper body and spline IK versus the
* underlying animation</td></tr>
*
* <tr><td><code>headPosition</code></td><td>{@link Vec3}</td><td>The desired position of the <code>Head</code> joint in
* rig coordinates.</td></tr>
* <tr><td><code>headRotation</code></td><td>{@link Quat}</td><td>The desired orientation of the <code>Head</code> joint in
* rig coordinates.</td></tr>
* <tr><td><code>headType</code></td><td>{@link MyAvatar.IKTargetType|IKTargetType}</td><td>The type of IK used for the
* head.</td></tr>
* <tr><td><code>headWeight</code></td><td>number</td><td>How strongly the head chain blends with the other IK
* chains.</td></tr>
*
* <tr><td><code>leftHandPosition</code></td><td>{@link Vec3}</td><td>The desired position of the <code>LeftHand</code>
* joint in rig coordinates.</td></tr>
* <tr><td><code>leftHandRotation</code></td><td>{@link Quat}</td><td>The desired orientation of the <code>LeftHand</code>
* joint in rig coordinates.</td></tr>
* <tr><td><code>leftHandType</code></td><td>{@link MyAvatar.IKTargetType|IKTargetType}</td><td>The type of IK used for the
* left arm.</td></tr>
* <tr><td><code>leftHandPoleVectorEnabled</code></td><td>boolean</td><td>When <code>true</code>, the elbow angle is
* controlled by the <code>rightHandPoleVector</code> property value. Otherwise the elbow direction comes from the
* underlying animation.</td></tr>
* <tr><td><code>leftHandPoleReferenceVector</code></td><td>{@link Vec3}</td><td>The direction of the elbow in the local
* coordinate system of the elbow.</td></tr>
* <tr><td><code>leftHandPoleVector</code></td><td>{@link Vec3}</td><td>The direction the elbow should point in rig
* coordinates.</td></tr>
*
* <tr><td><code>rightHandPosition</code></td><td>{@link Vec3}</td><td>The desired position of the <code>RightHand</code>
* joint in rig coordinates.</td></tr>
* <tr><td><code>rightHandRotation</code></td><td>{@link Quat}</td><td>The desired orientation of the
* <code>RightHand</code> joint in rig coordinates.</td></tr>
* <tr><td><code>rightHandType</code></td><td>{@link MyAvatar.IKTargetType|IKTargetType}</td><td>The type of IK used for
* the right arm.</td></tr>
* <tr><td><code>rightHandPoleVectorEnabled</code></td><td>boolean</td><td>When <code>true</code>, the elbow angle is
* controlled by the <code>rightHandPoleVector</code> property value. Otherwise the elbow direction comes from the
* underlying animation.</td></tr>
* <tr><td><code>rightHandPoleReferenceVector</code></td><td>{@link Vec3}</td><td>The direction of the elbow in the local
* coordinate system of the elbow.</td></tr>
* <tr><td><code>rightHandPoleVector</code></td><td>{@link Vec3}</td><td>The direction the elbow should point in rig
* coordinates.</td></tr>
*
* <tr><td><code>leftFootIKEnabled</code></td><td>boolean</td><td><code>true</code> if IK is enabled for the left
* foot.</td></tr>
* <tr><td><code>rightFootIKEnabled</code></td><td>boolean</td><td><code>true</code> if IK is enabled for the right
* foot.</td></tr>
*
* <tr><td><code>leftFootIKPositionVar</code></td><td>string</td><td>The name of the source for the desired position
* of the <code>LeftFoot</code> joint. If not set, the foot rotation of the underlying animation will be used.</td></tr>
* <tr><td><code>leftFootIKRotationVar</code></td><td>string</td><td>The name of the source for the desired rotation
* of the <code>LeftFoot</code> joint. If not set, the foot rotation of the underlying animation will be used.</td></tr>
* <tr><td><code>leftFootPoleVectorEnabled</code></td><td>boolean</td><td>When <code>true</code>, the knee angle is
* controlled by the <code>leftFootPoleVector</code> property value. Otherwise the knee direction comes from the
* underlying animation.</td></tr>
* <tr><td><code>leftFootPoleVector</code></td><td>{@link Vec3}</td><td>The direction the knee should face in rig
* coordinates.</td></tr>
* <tr><td><code>rightFootIKPositionVar</code></td><td>string</td><td>The name of the source for the desired position
* of the <code>RightFoot</code> joint. If not set, the foot rotation of the underlying animation will be used.</td></tr>
* <tr><td><code>rightFootIKRotationVar</code></td><td>string</td><td>The name of the source for the desired rotation
* of the <code>RightFoot</code> joint. If not set, the foot rotation of the underlying animation will be used.</td></tr>
* <tr><td><code>rightFootPoleVectorEnabled</code></td><td>boolean</td><td>When <code>true</code>, the knee angle is
* controlled by the <code>rightFootPoleVector</code> property value. Otherwise the knee direction comes from the
* underlying animation.</td></tr>
* <tr><td><code>rightFootPoleVector</code></td><td>{@link Vec3}</td><td>The direction the knee should face in rig
* coordinates.</td></tr>
*
* <tr><td><code>isTalking</code></td><td>boolean</td><td><code>true</code> if the avatar is talking.</td></tr>
* <tr><td><code>notIsTalking</code></td><td>boolean</td><td><code>true</code> if the avatar is not talking.</td></tr>
*
* <tr><td><code>solutionSource</code></td><td>{@link MyAvatar.AnimIKSolutionSource|AnimIKSolutionSource}</td>
* <td>Determines the initial conditions of the IK solver.</td></tr>
* <tr><td><code>defaultPoseOverlayAlpha</code></td><td>number</td><td>Controls the blend between the main animation state
* machine and the default pose. Mostly used during full body tracking so that walking &amp; jumping animations do not
* affect the IK of the figure.</td></tr>
* <tr><td><code>defaultPoseOverlayBoneSet</code></td><td>{@link MyAvatar.AnimOverlayBoneSet|AnimOverlayBoneSet}</td>
* <td>Specifies which bones will be replace by the source overlay.</td></tr>
* <tr><td><code>hipsType</code></td><td>{@link MyAvatar.IKTargetType|IKTargetType}</td><td>The type of IK used for the
* hips.</td></tr>
* <tr><td><code>hipsPosition</code></td><td>{@link Vec3}</td><td>The desired position of <code>Hips</code> joint in rig
* coordinates.</td></tr>
* <tr><td><code>hipsRotation</code></td><td>{@link Quat}</td><td>the desired orientation of the <code>Hips</code> joint in
* rig coordinates.</td></tr>
* <tr><td><code>spine2Type</code></td><td>{@link MyAvatar.IKTargetType|IKTargetType}</td><td>The type of IK used for the
* <code>Spine2</code> joint.</td></tr>
* <tr><td><code>spine2Position</code></td><td>{@link Vec3}</td><td>The desired position of the <code>Spine2</code> joint
* in rig coordinates.</td></tr>
* <tr><td><code>spine2Rotation</code></td><td>{@link Quat}</td><td>The desired orientation of the <code>Spine2</code>
* joint in rig coordinates.</td></tr>
*
* <tr><td><code>leftFootIKAlpha</code></td><td>number</td><td>Blends between full IK for the leg and the underlying
* animation.</td></tr>
* <tr><td><code>rightFootIKAlpha</code></td><td>number</td><td>Blends between full IK for the leg and the underlying
* animation.</td></tr>
* <tr><td><code>hipsWeight</code></td><td>number</td><td>How strongly the hips target blends with the IK solution for
* other IK chains.</td></tr>
* <tr><td><code>leftHandWeight</code></td><td>number</td><td>How strongly the left hand blends with IK solution of other
* IK chains.</td></tr>
* <tr><td><code>rightHandWeight</code></td><td>number</td><td>How strongly the right hand blends with IK solution of other
* IK chains.</td></tr>
* <tr><td><code>spine2Weight</code></td><td>number</td><td>How strongly the spine2 chain blends with the rest of the IK
* solution.</td></tr>
*
* <tr><td><code>leftHandOverlayAlpha</code></td><td>number</td><td>Used to blend in the animated hand gesture poses, such
* as point and thumbs up.</td></tr>
* <tr><td><code>leftHandGraspAlpha</code></td><td>number</td><td>Used to blend between an open hand and a closed hand.
* Usually changed as you squeeze the trigger of the hand controller.</td></tr>
* <tr><td><code>rightHandOverlayAlpha</code></td><td>number</td><td>Used to blend in the animated hand gesture poses,
* such as point and thumbs up.</td></tr>
* <tr><td><code>rightHandGraspAlpha</code></td><td>number</td><td>Used to blend between an open hand and a closed hand.
* Usually changed as you squeeze the trigger of the hand controller.</td></tr>
* <tr><td><code>isLeftIndexPoint</code></td><td>boolean</td><td><code>true</code> if the left hand should be
* pointing.</td></tr>
* <tr><td><code>isLeftThumbRaise</code></td><td>boolean</td><td><code>true</code> if the left hand should be
* thumbs-up.</td></tr>
* <tr><td><code>isLeftIndexPointAndThumbRaise</code></td><td>boolean</td><td><code>true</code> if the left hand should be
* pointing and thumbs-up.</td></tr>
* <tr><td><code>isLeftHandGrasp</code></td><td>boolean</td><td><code>true</code> if the left hand should be at rest,
* grasping the controller.</td></tr>
* <tr><td><code>isRightIndexPoint</code></td><td>boolean</td><td><code>true</code> if the right hand should be
* pointing.</td></tr>
* <tr><td><code>isRightThumbRaise</code></td><td>boolean</td><td><code>true</code> if the right hand should be
* thumbs-up.</td></tr>
* <tr><td><code>isRightIndexPointAndThumbRaise</code></td><td>boolean</td><td><code>true</code> if the right hand should
* be pointing and thumbs-up.</td></tr>
* <tr><td><code>isRightHandGrasp</code></td><td>boolean</td><td><code>true</code> if the right hand should be at rest,
* grasping the controller.</td></tr>
*
* </tbody>
* </table>
* <p>Note: Rig coordinates are <code>+z</code> forward and <code>+y</code> up.</p>
* @typedef {object} MyAvatar.AnimStateDictionary
*/
// Note: The following animVars are intentionally not documented:
// - leftFootPosition
// - leftFootRotation
// - rightFooKPosition
// - rightFooKRotation
// Note: The following items aren't set in the code below but are still intentionally documented:
// - leftFootIKAlpha
// - rightFootIKAlpha
// - hipsWeight
// - leftHandWeight
// - rightHandWeight
// - spine2Weight
// - rightHandOverlayAlpha
// - rightHandGraspAlpha
// - leftHandOverlayAlpha
// - leftHandGraspAlpha
// - isRightIndexPoint
// - isRightThumbRaise
// - isRightIndexPointAndThumbRaise
// - isRightHandGrasp
// - isLeftIndexPoint
// - isLeftThumbRaise
// - isLeftIndexPointAndThumbRaise
// - isLeftHandGrasp
Rig::Rig() {
// Ensure thread-safe access to the rigRegistry.
std::lock_guard<std::mutex> guard(rigRegistryMutex);
@ -1210,7 +1422,8 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons
_networkAnimState.blendTime += deltaTime;
alpha = _computeNetworkAnimation ? (_networkAnimState.blendTime / TOTAL_BLEND_TIME) : (1.0f - (_networkAnimState.blendTime / TOTAL_BLEND_TIME));
alpha = glm::clamp(alpha, 0.0f, 1.0f);
for (size_t i = 0; i < _networkPoseSet._relativePoses.size(); i++) {
size_t numJoints = std::min(_networkPoseSet._relativePoses.size(), _internalPoseSet._relativePoses.size());
for (size_t i = 0; i < numJoints; i++) {
_networkPoseSet._relativePoses[i].blend(_internalPoseSet._relativePoses[i], alpha);
}
}

View file

@ -1052,7 +1052,7 @@ void AudioClient::setReverbOptions(const AudioEffectOptions* options) {
void AudioClient::handleLocalEchoAndReverb(QByteArray& inputByteArray) {
// If there is server echo, reverb will be applied to the recieved audio stream so no need to have it here.
bool hasReverb = _reverb || _receivedAudioStream.hasReverb();
if (_muted || !_audioOutput || (!_shouldEchoLocally && !hasReverb)) {
if ((_muted && !_shouldEchoLocally) || !_audioOutput || (!_shouldEchoLocally && !hasReverb)) {
return;
}
@ -1368,7 +1368,9 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
memset(_localScratchBuffer, 0, bytesToRead);
if (0 < injectorBuffer->readData((char*)_localScratchBuffer, bytesToRead)) {
float gain = options.volume;
bool isSystemSound = !options.positionSet && !options.ambisonic;
float gain = options.volume * (isSystemSound ? _systemInjectorGain : _localInjectorGain);
if (options.ambisonic) {

View file

@ -241,6 +241,8 @@ public slots:
void setInputVolume(float volume, bool emitSignal = true);
void setReverb(bool reverb);
void setReverbOptions(const AudioEffectOptions* options);
void setLocalInjectorGain(float gain) { _localInjectorGain = gain; };
void setSystemInjectorGain(float gain) { _systemInjectorGain = gain; };
void outputNotify();
@ -395,6 +397,8 @@ private:
int16_t* _outputScratchBuffer { NULL };
// for local audio (used by audio injectors thread)
std::atomic<float> _localInjectorGain { 1.0f };
std::atomic<float> _systemInjectorGain { 1.0f };
float _localMixBuffer[AudioConstants::NETWORK_FRAME_SAMPLES_STEREO];
int16_t _localScratchBuffer[AudioConstants::NETWORK_FRAME_SAMPLES_AMBISONIC];
float* _localOutputMixBuffer { NULL };

View file

@ -372,13 +372,6 @@ bool Avatar::applyGrabChanges() {
target->removeGrab(grab);
_avatarGrabs.erase(itr);
grabAddedOrRemoved = true;
if (isMyAvatar()) {
const EntityItemPointer& entity = std::dynamic_pointer_cast<EntityItem>(target);
if (entity && entity->getEntityHostType() == entity::HostType::AVATAR && entity->getSimulationOwner().getID() == getID()) {
EntityItemProperties properties = entity->getProperties();
sendPacket(entity->getID());
}
}
} else {
undeleted.push_back(id);
}
@ -659,9 +652,8 @@ void Avatar::fadeIn(render::ScenePointer scene) {
scene->enqueueTransaction(transaction);
}
void Avatar::fadeOut(render::ScenePointer scene, KillAvatarReason reason) {
void Avatar::fadeOut(render::Transaction& transaction, KillAvatarReason reason) {
render::Transition::Type transitionType = render::Transition::USER_LEAVE_DOMAIN;
render::Transaction transaction;
if (reason == KillAvatarReason::YourAvatarEnteredTheirBubble) {
transitionType = render::Transition::BUBBLE_ISECT_TRESPASSER;
@ -669,7 +661,6 @@ void Avatar::fadeOut(render::ScenePointer scene, KillAvatarReason reason) {
transitionType = render::Transition::BUBBLE_ISECT_OWNER;
}
fade(transaction, transitionType);
scene->enqueueTransaction(transaction);
}
void Avatar::fade(render::Transaction& transaction, render::Transition::Type type) {
@ -679,19 +670,6 @@ void Avatar::fade(render::Transaction& transaction, render::Transition::Type typ
transaction.addTransitionToItem(itemId, type, _renderItemID);
}
}
_isFading = true;
}
void Avatar::updateFadingStatus() {
if (_isFading) {
render::Transaction transaction;
transaction.queryTransitionOnItem(_renderItemID, [this](render::ItemID id, const render::Transition* transition) {
if (!transition || transition->isFinished) {
_isFading = false;
}
});
AbstractViewStateInterface::instance()->getMain3DScene()->enqueueTransaction(transaction);
}
}
void Avatar::removeFromScene(AvatarSharedPointer self, const render::ScenePointer& scene, render::Transaction& transaction) {

View file

@ -127,7 +127,12 @@ private:
class Avatar : public AvatarData, public scriptable::ModelProvider, public MetaModelPayload {
Q_OBJECT
// This property has JSDoc in MyAvatar.h.
/*jsdoc
* @comment IMPORTANT: The JSDoc for the following properties should be copied to MyAvatar.h.
*
* @property {Vec3} skeletonOffset - Can be used to apply a translation offset between the avatar's position and the
* registration point of the 3D model.
*/
Q_PROPERTY(glm::vec3 skeletonOffset READ getSkeletonOffset WRITE setSkeletonOffset)
public:
@ -175,7 +180,6 @@ public:
/// Returns the distance to use as a LOD parameter.
float getLODDistance() const;
virtual bool isMyAvatar() const override { return false; }
virtual void createOrb() { }
enum class LoadingStatus {
@ -196,36 +200,52 @@ public:
virtual QStringList getJointNames() const override;
/**jsdoc
* Gets the default rotation of a joint (in the current avatar) relative to its parent.
* <p>For information on the joint hierarchy used, see
* <a href="https://docs.highfidelity.com/create/avatars/avatar-standards">Avatar Standards</a>.</p>
* @function MyAvatar.getDefaultJointRotation
* @param {number} index
* @returns {Quat}
* @param {number} index - The joint index.
* @returns {Quat} The default rotation of the joint if the joint index is valid, otherwise {@link Quat(0)|Quat.IDENTITY}.
*/
Q_INVOKABLE virtual glm::quat getDefaultJointRotation(int index) const;
/**jsdoc
* Gets the default translation of a joint (in the current avatar) relative to its parent, in model coordinates.
* <p><strong>Warning:</strong> These coordinates are not necessarily in meters.</p>
* <p>For information on the joint hierarchy used, see
* <a href="https://docs.highfidelity.com/create/avatars/avatar-standards">Avatar Standards</a>.</p>
* @function MyAvatar.getDefaultJointTranslation
* @param {number} index
* @returns {Vec3}
* @param {number} index - The joint index.
* @returns {Vec3} The default translation of the joint (in model coordinates) if the joint index is valid, otherwise
* {@link Vec3(0)|Vec3.ZERO}.
*/
Q_INVOKABLE virtual glm::vec3 getDefaultJointTranslation(int index) const;
/**jsdoc
* Provides read only access to the default joint rotations in avatar coordinates.
* Gets the default joint rotations in avatar coordinates.
* The default pose of the avatar is defined by the position and orientation of all bones
* in the avatar's model file. Typically this is a T-pose.
* @function MyAvatar.getAbsoluteDefaultJointRotationInObjectFrame
* @param index {number} index number
* @returns {Quat} The rotation of this joint in avatar coordinates.
* @param index {number} - The joint index.
* @returns {Quat} The default rotation of the joint in avatar coordinates.
* @example <caption>Report the default rotation of your avatar's head joint relative to your avatar.</caption>
* var headIndex = MyAvatar.getJointIndex("Head");
* var defaultHeadRotation = MyAvatar.getAbsoluteDefaultJointRotationInObjectFrame(headIndex);
* print("Default head rotation: " + JSON.stringify(Quat.safeEulerAngles(defaultHeadRotation))); // Degrees
*/
Q_INVOKABLE virtual glm::quat getAbsoluteDefaultJointRotationInObjectFrame(int index) const;
/**jsdoc
* Provides read only access to the default joint translations in avatar coordinates.
* Gets the default joint translations in avatar coordinates.
* The default pose of the avatar is defined by the position and orientation of all bones
* in the avatar's model file. Typically this is a T-pose.
* @function MyAvatar.getAbsoluteDefaultJointTranslationInObjectFrame
* @param index {number} index number
* @returns {Vec3} The position of this joint in avatar coordinates.
* @param index {number} - The joint index.
* @returns {Vec3} The default position of the joint in avatar coordinates.
* @example <caption>Report the default translation of your avatar's head joint relative to your avatar.</caption>
* var headIndex = MyAvatar.getJointIndex("Head");
* var defaultHeadTranslation = MyAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(headIndex);
* print("Default head translation: " + JSON.stringify(defaultHeadTranslation));
*/
Q_INVOKABLE virtual glm::vec3 getAbsoluteDefaultJointTranslationInObjectFrame(int index) const;
@ -233,59 +253,88 @@ public:
virtual glm::vec3 getAbsoluteJointScaleInObjectFrame(int index) const override;
virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override;
virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override;
/**jsdoc
* Sets the rotation of a joint relative to the avatar.
* <p><strong>Warning:</strong> Not able to be used in the <code>MyAvatar</code> API.</p>
* @function MyAvatar.setAbsoluteJointRotationInObjectFrame
* @param {number} index - The index of the joint. <em>Not used.</em>
* @param {Quat} rotation - The rotation of the joint relative to the avatar. <em>Not used.</em>
* @returns {boolean} <code>false</code>.
*/
virtual bool setAbsoluteJointRotationInObjectFrame(int index, const glm::quat& rotation) override { return false; }
/**jsdoc
* Sets the translation of a joint relative to the avatar.
* <p><strong>Warning:</strong> Not able to be used in the <code>MyAvatar</code> API.</p>
* @function MyAvatar.setAbsoluteJointTranslationInObjectFrame
* @param {number} index - The index of the joint. <em>Not used.</em>
* @param {Vec3} translation - The translation of the joint relative to the avatar. <em>Not used.</em>
* @returns {boolean} <code>false</code>.
*/
virtual bool setAbsoluteJointTranslationInObjectFrame(int index, const glm::vec3& translation) override { return false; }
virtual glm::vec3 getSpine2SplineOffset() const { return _spine2SplineOffset; }
virtual float getSpine2SplineRatio() const { return _spine2SplineRatio; }
// world-space to avatar-space rigconversion functions
/**jsdoc
* @function MyAvatar.worldToJointPoint
* @param {Vec3} position
* @param {number} [jointIndex=-1]
* @returns {Vec3}
*/
* Transforms a position in world coordinates to a position in a joint's coordinates, or avatar coordinates if no joint is
* specified.
* @function MyAvatar.worldToJointPoint
* @param {Vec3} position - The position in world coordinates.
* @param {number} [jointIndex=-1] - The index of the joint.
* @returns {Vec3} The position in the joint's coordinate system, or avatar coordinate system if no joint is specified.
*/
Q_INVOKABLE glm::vec3 worldToJointPoint(const glm::vec3& position, const int jointIndex = -1) const;
/**jsdoc
* @function MyAvatar.worldToJointDirection
* @param {Vec3} direction
* @param {number} [jointIndex=-1]
* @returns {Vec3}
*/
* Transforms a direction in world coordinates to a direction in a joint's coordinates, or avatar coordinates if no joint
* is specified.
* @function MyAvatar.worldToJointDirection
* @param {Vec3} direction - The direction in world coordinates.
* @param {number} [jointIndex=-1] - The index of the joint.
* @returns {Vec3} The direction in the joint's coordinate system, or avatar coordinate system if no joint is specified.
*/
Q_INVOKABLE glm::vec3 worldToJointDirection(const glm::vec3& direction, const int jointIndex = -1) const;
/**jsdoc
* @function MyAvatar.worldToJointRotation
* @param {Quat} rotation
* @param {number} [jointIndex=-1]
* @returns {Quat}
* Transforms a rotation in world coordinates to a rotation in a joint's coordinates, or avatar coordinates if no joint is
* specified.
* @function MyAvatar.worldToJointRotation
* @param {Quat} rotation - The rotation in world coordinates.
* @param {number} [jointIndex=-1] - The index of the joint.
* @returns {Quat} The rotation in the joint's coordinate system, or avatar coordinate system if no joint is specified.
*/
Q_INVOKABLE glm::quat worldToJointRotation(const glm::quat& rotation, const int jointIndex = -1) const;
/**jsdoc
* @function MyAvatar.jointToWorldPoint
* @param {vec3} position
* @param {number} [jointIndex=-1]
* @returns {Vec3}
*/
* Transforms a position in a joint's coordinates, or avatar coordinates if no joint is specified, to a position in world
* coordinates.
* @function MyAvatar.jointToWorldPoint
* @param {Vec3} position - The position in joint coordinates, or avatar coordinates if no joint is specified.
* @param {number} [jointIndex=-1] - The index of the joint.
* @returns {Vec3} The position in world coordinates.
*/
Q_INVOKABLE glm::vec3 jointToWorldPoint(const glm::vec3& position, const int jointIndex = -1) const;
/**jsdoc
* @function MyAvatar.jointToWorldDirection
* @param {Vec3} direction
* @param {number} [jointIndex=-1]
* @returns {Vec3}
*/
* Transforms a direction in a joint's coordinates, or avatar coordinates if no joint is specified, to a direction in world
* coordinates.
* @function MyAvatar.jointToWorldDirection
* @param {Vec3} direction - The direction in joint coordinates, or avatar coordinates if no joint is specified.
* @param {number} [jointIndex=-1] - The index of the joint.
* @returns {Vec3} The direction in world coordinates.
*/
Q_INVOKABLE glm::vec3 jointToWorldDirection(const glm::vec3& direction, const int jointIndex = -1) const;
/**jsdoc
* @function MyAvatar.jointToWorldRotation
* @param {Quat} rotation
* @param {number} [jointIndex=-1]
* @returns {Quat}
*/
* Transforms a rotation in a joint's coordinates, or avatar coordinates if no joint is specified, to a rotation in world
* coordinates.
* @function MyAvatar.jointToWorldRotation
* @param {Quat} rotation - The rotation in joint coordinates, or avatar coordinates if no joint is specified.
* @param {number} [jointIndex=-1] - The index of the joint.
* @returns {Quat} The rotation in world coordinates.
*/
Q_INVOKABLE glm::quat jointToWorldRotation(const glm::quat& rotation, const int jointIndex = -1) const;
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override;
@ -297,7 +346,7 @@ public:
virtual int parseDataFromBuffer(const QByteArray& buffer) override;
/**jsdoc
* Set the offset applied to the current avatar. The offset adjusts the position that the avatar is rendered. For example,
* Sets the offset applied to the current avatar. The offset adjusts the position that the avatar is rendered. For example,
* with an offset of <code>{ x: 0, y: 0.1, z: 0 }</code>, your avatar will appear to be raised off the ground slightly.
* @function MyAvatar.setSkeletonOffset
* @param {Vec3} offset - The skeleton offset to set.
@ -313,7 +362,7 @@ public:
Q_INVOKABLE void setSkeletonOffset(const glm::vec3& offset);
/**jsdoc
* Get the offset applied to the current avatar. The offset adjusts the position that the avatar is rendered. For example,
* Gets the offset applied to the current avatar. The offset adjusts the position that the avatar is rendered. For example,
* with an offset of <code>{ x: 0, y: 0.1, z: 0 }</code>, your avatar will appear to be raised off the ground slightly.
* @function MyAvatar.getSkeletonOffset
* @returns {Vec3} The current skeleton offset.
@ -325,7 +374,7 @@ public:
virtual glm::vec3 getSkeletonPosition() const;
/**jsdoc
* Get the position of a joint in the current avatar.
* Gets the position of a joint in the current avatar.
* @function MyAvatar.getJointPosition
* @param {number} index - The index of the joint.
* @returns {Vec3} The position of the joint in world coordinates.
@ -333,7 +382,7 @@ public:
Q_INVOKABLE glm::vec3 getJointPosition(int index) const;
/**jsdoc
* Get the position of a joint in the current avatar.
* Gets the position of a joint in the current avatar.
* @function MyAvatar.getJointPosition
* @param {string} name - The name of the joint.
* @returns {Vec3} The position of the joint in world coordinates.
@ -343,7 +392,7 @@ public:
Q_INVOKABLE glm::vec3 getJointPosition(const QString& name) const;
/**jsdoc
* Get the position of the current avatar's neck in world coordinates.
* Gets the position of the current avatar's neck in world coordinates.
* @function MyAvatar.getNeckPosition
* @returns {Vec3} The position of the neck in world coordinates.
* @example <caption>Report the position of your avatar's neck.</caption>
@ -352,8 +401,9 @@ public:
Q_INVOKABLE glm::vec3 getNeckPosition() const;
/**jsdoc
* Gets the current acceleration of the avatar.
* @function MyAvatar.getAcceleration
* @returns {Vec3}
* @returns {Vec3} The current acceleration of the avatar.
*/
Q_INVOKABLE glm::vec3 getAcceleration() const { return _acceleration; }
@ -377,47 +427,55 @@ public:
void getCapsule(glm::vec3& start, glm::vec3& end, float& radius);
float computeMass();
/**jsdoc
* Get the position of the current avatar's feet (or rather, bottom of its collision capsule) in world coordinates.
* Gets the position of the current avatar's feet (or rather, bottom of its collision capsule) in world coordinates.
* @function MyAvatar.getWorldFeetPosition
* @returns {Vec3} The position of the avatar's feet in world coordinates.
*/
*/
Q_INVOKABLE glm::vec3 getWorldFeetPosition();
void setPositionViaScript(const glm::vec3& position) override;
void setOrientationViaScript(const glm::quat& orientation) override;
/**jsdoc
* Gets the ID of the entity of avatar that the avatar is parented to.
* @function MyAvatar.getParentID
* @returns {Uuid}
* @returns {Uuid} The ID of the entity or avatar that the avatar is parented to. {@link Uuid|Uuid.NULL} if not parented.
*/
// This calls through to the SpatiallyNestable versions, but is here to expose these to JavaScript.
Q_INVOKABLE virtual const QUuid getParentID() const override { return SpatiallyNestable::getParentID(); }
/**jsdoc
* Sets the ID of the entity of avatar that the avatar is parented to.
* @function MyAvatar.setParentID
* @param {Uuid} parentID
* @param {Uuid} parentID - The ID of the entity or avatar that the avatar should be parented to. Set to
* {@link Uuid|Uuid.NULL} to unparent.
*/
// This calls through to the SpatiallyNestable versions, but is here to expose these to JavaScript.
Q_INVOKABLE virtual void setParentID(const QUuid& parentID) override;
/**jsdoc
* Gets the joint of the entity or avatar that the avatar is parented to.
* @function MyAvatar.getParentJointIndex
* @returns {number}
* @returns {number} The joint of the entity or avatar that the avatar is parented to. <code>65535</code> or
* <code>-1</code> if parented to the entity or avatar's position and orientation rather than a joint.
*/
// This calls through to the SpatiallyNestable versions, but is here to expose these to JavaScript.
Q_INVOKABLE virtual quint16 getParentJointIndex() const override { return SpatiallyNestable::getParentJointIndex(); }
/**jsdoc
* Sets the joint of the entity or avatar that the avatar is parented to.
* @function MyAvatar.setParentJointIndex
* @param {number} parentJointIndex
* @param {number} parentJointIndex - he joint of the entity or avatar that the avatar should be parented to. Use
* <code>65535</code> or <code>-1</code> to parent to the entity or avatar's position and orientation rather than a
* joint.
*/
// This calls through to the SpatiallyNestable versions, but is here to expose these to JavaScript.
Q_INVOKABLE virtual void setParentJointIndex(quint16 parentJointIndex) override;
/**jsdoc
* Returns an array of joints, where each joint is an object containing name, index, and parentIndex fields.
* Gets information on all the joints in the avatar's skeleton.
* @function MyAvatar.getSkeleton
* @returns {MyAvatar.SkeletonJoint[]} A list of information about each joint in this avatar's skeleton.
* @returns {MyAvatar.SkeletonJoint[]} Information about each joint in the avatar's skeleton.
*/
/**jsdoc
* Information about a single joint in an Avatar's skeleton hierarchy.
@ -443,8 +501,9 @@ public:
/**jsdoc
* @function MyAvatar.getSimulationRate
* @param {string} [rateName=""]
* @returns {number}
* @param {string} [rateName=""] - Rate name.
* @returns {number} Simulation rate.
* @deprecated This function is deprecated and will be removed.
*/
Q_INVOKABLE float getSimulationRate(const QString& rateName = QString("")) const;
@ -461,9 +520,7 @@ public:
bool isMoving() const { return _moving; }
void fadeIn(render::ScenePointer scene);
void fadeOut(render::ScenePointer scene, KillAvatarReason reason);
bool isFading() const { return _isFading; }
void updateFadingStatus();
void fadeOut(render::Transaction& transaction, KillAvatarReason reason);
// JSDoc is in AvatarData.h.
Q_INVOKABLE virtual float getEyeHeight() const override;
@ -500,6 +557,13 @@ public:
uint32_t appendSubMetaItems(render::ItemIDs& subItems);
signals:
/**jsdoc
* Triggered when the avatar's target scale is changed. The target scale is the desired scale of the avatar without any
* restrictions on permissible scale values imposed by the domain.
* @function MyAvatar.targetScaleChanged
* @param {number} targetScale - The avatar's target scale.
* @returns Signal
*/
void targetScaleChanged(float targetScale);
public slots:
@ -508,7 +572,7 @@ public slots:
// thread safe, will return last valid palm from cache
/**jsdoc
* Get the position of the left palm in world coordinates.
* Gets the position of the left palm in world coordinates.
* @function MyAvatar.getLeftPalmPosition
* @returns {Vec3} The position of the left palm in world coordinates.
* @example <caption>Report the position of your avatar's left palm.</caption>
@ -517,15 +581,16 @@ public slots:
glm::vec3 getLeftPalmPosition() const;
/**jsdoc
* Get the rotation of the left palm in world coordinates.
* Gets the rotation of the left palm in world coordinates.
* @function MyAvatar.getLeftPalmRotation
* @returns {Quat} The rotation of the left palm in world coordinates.
* @example <caption>Report the rotation of your avatar's left palm.</caption>
* print(JSON.stringify(MyAvatar.getLeftPalmRotation()));
*/
glm::quat getLeftPalmRotation() const;
/**jsdoc
* Get the position of the right palm in world coordinates.
* Gets the position of the right palm in world coordinates.
* @function MyAvatar.getRightPalmPosition
* @returns {Vec3} The position of the right palm in world coordinates.
* @example <caption>Report the position of your avatar's right palm.</caption>
@ -542,21 +607,26 @@ public slots:
*/
glm::quat getRightPalmRotation() const;
/**jsdoc
* @function MyAvatar.setModelURLFinished
* @param {boolean} success
* @deprecated This function is deprecated and will be removed.
*/
// hooked up to Model::setURLFinished signal
void setModelURLFinished(bool success);
/**jsdoc
* @function MyAvatar.rigReady
* @returns {Signal}
* @deprecated This function is deprecated and will be removed.
*/
// Hooked up to Model::rigReady signal
void rigReady();
/**jsdoc
* @function MyAvatar.rigReset
* @returns {Signal}
* @deprecated This function is deprecated and will be removed.
*/
// Jooked up to Model::rigReset signal
// Hooked up to Model::rigReset signal
void rigReset();
protected:
@ -655,7 +725,6 @@ protected:
bool _initialized { false };
bool _isAnimatingScale { false };
bool _mustFadeIn { false };
bool _isFading { false };
bool _reconstructSoftEntitiesJointMap { false };
float _modelScale { 1.0f };

View file

@ -28,9 +28,10 @@
namespace AvatarTraits {
template<typename T, T defaultValue>
class AssociatedTraitValues {
using SimpleTypesArray = std::array<T, NUM_SIMPLE_TRAITS>;
public:
// constructor that pre-fills _simpleTypes with the default value specified by the template
AssociatedTraitValues() : _simpleTypes(FirstInstancedTrait, defaultValue) {}
AssociatedTraitValues() { std::fill(_simpleTypes.begin(), _simpleTypes.end(), defaultValue); }
/// inserts the given value for the given simple trait type
void insert(TraitType type, T value) { _simpleTypes[type] = value; }
@ -71,12 +72,12 @@ namespace AvatarTraits {
}
/// const iterators for the vector of simple type values
typename std::vector<T>::const_iterator simpleCBegin() const { return _simpleTypes.cbegin(); }
typename std::vector<T>::const_iterator simpleCEnd() const { return _simpleTypes.cend(); }
typename SimpleTypesArray::const_iterator simpleCBegin() const { return _simpleTypes.cbegin(); }
typename SimpleTypesArray::const_iterator simpleCEnd() const { return _simpleTypes.cend(); }
/// non-const iterators for the vector of simple type values
typename std::vector<T>::iterator simpleBegin() { return _simpleTypes.begin(); }
typename std::vector<T>::iterator simpleEnd() { return _simpleTypes.end(); }
typename SimpleTypesArray::iterator simpleBegin() { return _simpleTypes.begin(); }
typename SimpleTypesArray::iterator simpleEnd() { return _simpleTypes.end(); }
struct TraitWithInstances {
TraitType traitType;
@ -96,7 +97,7 @@ namespace AvatarTraits {
typename std::vector<TraitWithInstances>::iterator instancedEnd() { return _instancedTypes.end(); }
private:
std::vector<T> _simpleTypes;
SimpleTypesArray _simpleTypes;
/// return the iterator to the matching TraitWithInstances object for a given instanced trait type
typename std::vector<TraitWithInstances>::iterator instancesForTrait(TraitType traitType) {

View file

@ -1143,10 +1143,11 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
// we store the hand state as well as other items in a shared bitset. The hand state is an octal, but is split
// into two sections to maintain backward compatibility. The bits are ordered as such (0-7 left to right).
// AA 6/1/18 added three more flags bits 8,9, and 10 for procedural audio, blink, and eye saccade enabled
// +---+-----+-----+--+--+--+--+-----+
// |x,x|H0,H1|x,x,x|H2|Au|Bl|Ey|xxxxx|
// +---+-----+-----+--+--+--+--+-----+
// +---+-----+-----+--+--+--+--+--+----+
// |x,x|H0,H1|x,x,x|H2|Au|Bl|Ey|He|xxxx|
// +---+-----+-----+--+--+--+--+--+----+
// Hand state - H0,H1,H2 is found in the 3rd, 4th, and 8th bits
// Hero-avatar status (He) - 12th bit
auto newHandState = getSemiNibbleAt(bitItems, HAND_STATE_START_BIT)
+ (oneAtBit16(bitItems, HAND_STATE_FINGER_POINTING_BIT) ? IS_FINGER_POINTING_FLAG : 0);
@ -1434,6 +1435,47 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
return numBytesRead;
}
/**jsdoc
* The avatar mixer data comprises different types of data, with the data rates of each being tracked in kbps.
*
* <table>
* <thead>
* <tr><th>Rate Name</th><th>Description</th></tr>
* </thead>
* <tbody>
* <tr><td><code>"globalPosition"</code></td><td>Incoming global position.</td></tr>
* <tr><td><code>"localPosition"</code></td><td>Incoming local position.</td></tr>
* <tr><td><code>"avatarBoundingBox"</code></td><td>Incoming avatar bounding box.</td></tr>
* <tr><td><code>"avatarOrientation"</code></td><td>Incoming avatar orientation.</td></tr>
* <tr><td><code>"avatarScale"</code></td><td>Incoming avatar scale.</td></tr>
* <tr><td><code>"lookAtPosition"</code></td><td>Incoming look-at position.</td></tr>
* <tr><td><code>"audioLoudness"</code></td><td>Incoming audio loudness.</td></tr>
* <tr><td><code>"sensorToWorkMatrix"</code></td><td>Incoming sensor-to-world matrix.</td></tr>
* <tr><td><code>"additionalFlags"</code></td><td>Incoming additional avatar flags.</td></tr>
* <tr><td><code>"parentInfo"</code></td><td>Incoming parent information.</td></tr>
* <tr><td><code>"faceTracker"</code></td><td>Incoming face tracker data.</td></tr>
* <tr><td><code>"jointData"</code></td><td>Incoming joint data.</td></tr>
* <tr><td><code>"jointDefaultPoseFlagsRate"</code></td><td>Incoming joint default pose flags.</td></tr>
* <tr><td><code>"farGrabJointRate"</code></td><td>Incoming far grab joint.</td></tr>
* <tr><td><code>"globalPositionOutbound"</code></td><td>Outgoing global position.</td></tr>
* <tr><td><code>"localPositionOutbound"</code></td><td>Outgoing local position.</td></tr>
* <tr><td><code>"avatarBoundingBoxOutbound"</code></td><td>Outgoing avatar bounding box.</td></tr>
* <tr><td><code>"avatarOrientationOutbound"</code></td><td>Outgoing avatar orientation.</td></tr>
* <tr><td><code>"avatarScaleOutbound"</code></td><td>Outgoing avatar scale.</td></tr>
* <tr><td><code>"lookAtPositionOutbound"</code></td><td>Outgoing look-at position.</td></tr>
* <tr><td><code>"audioLoudnessOutbound"</code></td><td>Outgoing audio loudness.</td></tr>
* <tr><td><code>"sensorToWorkMatrixOutbound"</code></td><td>Outgoing sensor-to-world matrix.</td></tr>
* <tr><td><code>"additionalFlagsOutbound"</code></td><td>Outgoing additional avatar flags.</td></tr>
* <tr><td><code>"parentInfoOutbound"</code></td><td>Outgoing parent information.</td></tr>
* <tr><td><code>"faceTrackerOutbound"</code></td><td>Outgoing face tracker data.</td></tr>
* <tr><td><code>"jointDataOutbound"</code></td><td>Outgoing joint data.</td></tr>
* <tr><td><code>"jointDefaultPoseFlagsOutbound"</code></td><td>Outgoing joint default pose flags.</td></tr>
* <tr><td><code>""</code></td><td>When no rate name is specified, the total incoming data rate is provided.</td></tr>
* </tbody>
* </table>
*
* @typedef {string} AvatarDataRate
*/
float AvatarData::getDataRate(const QString& rateName) const {
if (rateName == "") {
return _parseBufferRate.rate() / BYTES_PER_KILOBIT;
@ -1495,6 +1537,35 @@ float AvatarData::getDataRate(const QString& rateName) const {
return 0.0f;
}
/**jsdoc
* The avatar mixer data comprises different types of data updated at different rates, in Hz.
*
* <table>
* <thead>
* <tr><th>Rate Name</th><th>Description</th></tr>
* </thead>
* <tbody>
* <tr><td><code>"globalPosition"</code></td><td>Global position.</td></tr>
* <tr><td><code>"localPosition"</code></td><td>Local position.</td></tr>
* <tr><td><code>"avatarBoundingBox"</code></td><td>Avatar bounding box.</td></tr>
* <tr><td><code>"avatarOrientation"</code></td><td>Avatar orientation.</td></tr>
* <tr><td><code>"avatarScale"</code></td><td>Avatar scale.</td></tr>
* <tr><td><code>"lookAtPosition"</code></td><td>Look-at position.</td></tr>
* <tr><td><code>"audioLoudness"</code></td><td>Audio loudness.</td></tr>
* <tr><td><code>"sensorToWorkMatrix"</code></td><td>Sensor-to-world matrix.</td></tr>
* <tr><td><code>"additionalFlags"</code></td><td>Additional avatar flags.</td></tr>
* <tr><td><code>"parentInfo"</code></td><td>Parent information.</td></tr>
* <tr><td><code>"faceTracker"</code></td><td>Face tracker data.</td></tr>
* <tr><td><code>"jointData"</code></td><td>Joint data.</td></tr>
* <tr><td><code>"farGrabJointData"</code></td><td>Far grab joint data.</td></tr>
* <tr><td><code>""</code></td><td>When no rate name is specified, the overall update rate is provided.</td></tr>
* </tbody>
* </table>
*
* @typedef {string} AvatarUpdateRate
*/
float AvatarData::getUpdateRate(const QString& rateName) const {
if (rateName == "") {
return _parseBufferUpdateRate.rate();
@ -1920,42 +1991,16 @@ QUrl AvatarData::getWireSafeSkeletonModelURL() const {
}
}
qint64 AvatarData::packTrait(AvatarTraits::TraitType traitType, ExtendedIODevice& destination,
AvatarTraits::TraitVersion traitVersion) {
qint64 bytesWritten = 0;
if (traitType == AvatarTraits::SkeletonModelURL) {
QByteArray encodedSkeletonURL = getWireSafeSkeletonModelURL().toEncoded();
if (encodedSkeletonURL.size() > AvatarTraits::MAXIMUM_TRAIT_SIZE) {
qWarning() << "Refusing to pack simple trait" << traitType << "of size" << encodedSkeletonURL.size()
<< "bytes since it exceeds the maximum size" << AvatarTraits::MAXIMUM_TRAIT_SIZE << "bytes";
return 0;
}
bytesWritten += destination.writePrimitive(traitType);
if (traitVersion > AvatarTraits::DEFAULT_TRAIT_VERSION) {
bytesWritten += destination.writePrimitive(traitVersion);
}
AvatarTraits::TraitWireSize encodedURLSize = encodedSkeletonURL.size();
bytesWritten += destination.writePrimitive(encodedURLSize);
bytesWritten += destination.write(encodedSkeletonURL);
}
return bytesWritten;
QByteArray AvatarData::packSkeletonModelURL() const {
return getWireSafeSkeletonModelURL().toEncoded();
}
void AvatarData::unpackSkeletonModelURL(const QByteArray& data) {
auto skeletonModelURL = QUrl::fromEncoded(data);
setSkeletonModelURL(skeletonModelURL);
}
qint64 AvatarData::packAvatarEntityTraitInstance(AvatarTraits::TraitType traitType,
AvatarTraits::TraitInstanceID traitInstanceID,
ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion) {
qint64 bytesWritten = 0;
QByteArray AvatarData::packAvatarEntityTraitInstance(AvatarTraits::TraitInstanceID traitInstanceID) {
// grab a read lock on the avatar entities and check for entity data for the given ID
QByteArray entityBinaryData;
_avatarEntitiesLock.withReadLock([this, &entityBinaryData, &traitInstanceID] {
@ -1964,104 +2009,48 @@ qint64 AvatarData::packAvatarEntityTraitInstance(AvatarTraits::TraitType traitTy
}
});
if (entityBinaryData.size() > AvatarTraits::MAXIMUM_TRAIT_SIZE) {
qWarning() << "Refusing to pack instanced trait" << traitType << "of size" << entityBinaryData.size()
<< "bytes since it exceeds the maximum size " << AvatarTraits::MAXIMUM_TRAIT_SIZE << "bytes";
return 0;
}
bytesWritten += destination.writePrimitive(traitType);
if (traitVersion > AvatarTraits::DEFAULT_TRAIT_VERSION) {
bytesWritten += destination.writePrimitive(traitVersion);
}
bytesWritten += destination.write(traitInstanceID.toRfc4122());
if (!entityBinaryData.isNull()) {
AvatarTraits::TraitWireSize entityBinarySize = entityBinaryData.size();
bytesWritten += destination.writePrimitive(entityBinarySize);
bytesWritten += destination.write(entityBinaryData);
} else {
bytesWritten += destination.writePrimitive(AvatarTraits::DELETED_TRAIT_SIZE);
}
return bytesWritten;
return entityBinaryData;
}
qint64 AvatarData::packGrabTraitInstance(AvatarTraits::TraitType traitType,
AvatarTraits::TraitInstanceID traitInstanceID,
ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion) {
qint64 bytesWritten = 0;
QByteArray AvatarData::packGrabTraitInstance(AvatarTraits::TraitInstanceID traitInstanceID) {
// grab a read lock on the avatar grabs and check for grab data for the given ID
QByteArray grabBinaryData;
_avatarGrabsLock.withReadLock([this, &grabBinaryData, &traitInstanceID] {
if (_avatarGrabData.contains(traitInstanceID)) {
grabBinaryData = _avatarGrabData[traitInstanceID];
}
});
if (grabBinaryData.size() > AvatarTraits::MAXIMUM_TRAIT_SIZE) {
qWarning() << "Refusing to pack instanced trait" << traitType << "of size" << grabBinaryData.size()
<< "bytes since it exceeds the maximum size " << AvatarTraits::MAXIMUM_TRAIT_SIZE << "bytes";
return 0;
}
bytesWritten += destination.writePrimitive(traitType);
if (traitVersion > AvatarTraits::DEFAULT_TRAIT_VERSION) {
bytesWritten += destination.writePrimitive(traitVersion);
}
bytesWritten += destination.write(traitInstanceID.toRfc4122());
if (!grabBinaryData.isNull()) {
AvatarTraits::TraitWireSize grabBinarySize = grabBinaryData.size();
bytesWritten += destination.writePrimitive(grabBinarySize);
bytesWritten += destination.write(grabBinaryData);
} else {
bytesWritten += destination.writePrimitive(AvatarTraits::DELETED_TRAIT_SIZE);
}
return bytesWritten;
return grabBinaryData;
}
qint64 AvatarData::packTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID traitInstanceID,
ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion) {
qint64 bytesWritten = 0;
QByteArray AvatarData::packTrait(AvatarTraits::TraitType traitType) const {
QByteArray traitBinaryData;
// Call packer function
if (traitType == AvatarTraits::SkeletonModelURL) {
traitBinaryData = packSkeletonModelURL();
}
return traitBinaryData;
}
QByteArray AvatarData::packTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID traitInstanceID) {
QByteArray traitBinaryData;
// Call packer function
if (traitType == AvatarTraits::AvatarEntity) {
bytesWritten += packAvatarEntityTraitInstance(traitType, traitInstanceID, destination, traitVersion);
traitBinaryData = packAvatarEntityTraitInstance(traitInstanceID);
} else if (traitType == AvatarTraits::Grab) {
bytesWritten += packGrabTraitInstance(traitType, traitInstanceID, destination, traitVersion);
traitBinaryData = packGrabTraitInstance(traitInstanceID);
}
return bytesWritten;
}
void AvatarData::prepareResetTraitInstances() {
if (_clientTraitsHandler) {
_avatarEntitiesLock.withReadLock([this]{
foreach (auto entityID, _packedAvatarEntityData.keys()) {
_clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::AvatarEntity, entityID);
}
foreach (auto grabID, _avatarGrabData.keys()) {
_clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::Grab, grabID);
}
});
}
return traitBinaryData;
}
void AvatarData::processTrait(AvatarTraits::TraitType traitType, QByteArray traitBinaryData) {
if (traitType == AvatarTraits::SkeletonModelURL) {
// get the URL from the binary data
auto skeletonModelURL = QUrl::fromEncoded(traitBinaryData);
setSkeletonModelURL(skeletonModelURL);
unpackSkeletonModelURL(traitBinaryData);
}
}
@ -2082,6 +2071,19 @@ void AvatarData::processDeletedTraitInstance(AvatarTraits::TraitType traitType,
}
}
void AvatarData::prepareResetTraitInstances() {
if (_clientTraitsHandler) {
_avatarEntitiesLock.withReadLock([this]{
foreach (auto entityID, _packedAvatarEntityData.keys()) {
_clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::AvatarEntity, entityID);
}
foreach (auto grabID, _avatarGrabData.keys()) {
_clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::Grab, grabID);
}
});
}
}
QByteArray AvatarData::identityByteArray(bool setIsReplicated) const {
QByteArray identityData;
QDataStream identityStream(&identityData, QIODevice::Append);
@ -2730,13 +2732,16 @@ glm::vec3 AvatarData::getAbsoluteJointTranslationInObjectFrame(int index) const
}
/**jsdoc
* Information on an attachment worn by the avatar.
* @typedef {object} AttachmentData
* @property {string} modelUrl
* @property {string} jointName
* @property {Vec3} translation
* @property {Vec3} rotation
* @property {number} scale
* @property {boolean} soft
* @property {string} modelUrl - The URL of the model file. Models can be FBX or OBJ format.
* @property {string} jointName - The offset to apply to the model relative to the joint position.
* @property {Vec3} translation - The offset from the joint that the attachment is positioned at.
* @property {Vec3} rotation - The rotation applied to the model relative to the joint orientation.
* @property {number} scale - The scale applied to the attachment model.
* @property {boolean} soft - If <code>true</code> and the model has a skeleton, the bones of the attached model's skeleton are
* rotated to fit the avatar's current pose. If <code>true</code>, the <code>translation</code>, <code>rotation</code>, and
* <code>scale</code> parameters are ignored.
*/
QVariant AttachmentData::toVariant() const {
QVariantMap result;
@ -2942,6 +2947,10 @@ float AvatarData::_avatarSortCoefficientSize { 8.0f };
float AvatarData::_avatarSortCoefficientCenter { 0.25f };
float AvatarData::_avatarSortCoefficientAge { 1.0f };
/**jsdoc
* An object with the UUIDs of avatar entities as keys and avatar entity properties objects as values.
* @typedef {Object.<Uuid, Entities.EntityProperties>} AvatarEntityMap
*/
QScriptValue AvatarEntityMapToScriptValue(QScriptEngine* engine, const AvatarEntityMap& value) {
QScriptValue obj = engine->newObject();
for (auto entityID : value.keys()) {

File diff suppressed because it is too large Load diff

View file

@ -439,7 +439,6 @@ void AvatarHashMap::removeAvatar(const QUuid& sessionUUID, KillAvatarReason remo
}
auto removedAvatar = _avatarHash.take(sessionUUID);
if (removedAvatar) {
removedAvatars.push_back(removedAvatar);
}

View file

@ -0,0 +1,135 @@
//
// AvatarTraits.cpp
// libraries/avatars/src
//
// Created by Clement Brisset on 3/19/19.
// Copyright 2019 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "AvatarTraits.h"
#include <ExtendedIODevice.h>
#include "AvatarData.h"
namespace AvatarTraits {
qint64 packTrait(TraitType traitType, ExtendedIODevice& destination, const AvatarData& avatar) {
// Call packer function
auto traitBinaryData = avatar.packTrait(traitType);
auto traitBinaryDataSize = traitBinaryData.size();
// Verify packed data
if (traitBinaryDataSize > MAXIMUM_TRAIT_SIZE) {
qWarning() << "Refusing to pack simple trait" << traitType << "of size" << traitBinaryDataSize
<< "bytes since it exceeds the maximum size" << MAXIMUM_TRAIT_SIZE << "bytes";
return 0;
}
// Write packed data to stream
qint64 bytesWritten = 0;
bytesWritten += destination.writePrimitive((TraitType)traitType);
bytesWritten += destination.writePrimitive((TraitWireSize)traitBinaryDataSize);
bytesWritten += destination.write(traitBinaryData);
return bytesWritten;
}
qint64 packVersionedTrait(TraitType traitType, ExtendedIODevice& destination,
TraitVersion traitVersion, const AvatarData& avatar) {
// Call packer function
auto traitBinaryData = avatar.packTrait(traitType);
auto traitBinaryDataSize = traitBinaryData.size();
// Verify packed data
if (traitBinaryDataSize > MAXIMUM_TRAIT_SIZE) {
qWarning() << "Refusing to pack simple trait" << traitType << "of size" << traitBinaryDataSize
<< "bytes since it exceeds the maximum size" << MAXIMUM_TRAIT_SIZE << "bytes";
return 0;
}
// Write packed data to stream
qint64 bytesWritten = 0;
bytesWritten += destination.writePrimitive((TraitType)traitType);
bytesWritten += destination.writePrimitive((TraitVersion)traitVersion);
bytesWritten += destination.writePrimitive((TraitWireSize)traitBinaryDataSize);
bytesWritten += destination.write(traitBinaryData);
return bytesWritten;
}
qint64 packTraitInstance(TraitType traitType, TraitInstanceID traitInstanceID,
ExtendedIODevice& destination, AvatarData& avatar) {
// Call packer function
auto traitBinaryData = avatar.packTraitInstance(traitType, traitInstanceID);
auto traitBinaryDataSize = traitBinaryData.size();
// Verify packed data
if (traitBinaryDataSize > AvatarTraits::MAXIMUM_TRAIT_SIZE) {
qWarning() << "Refusing to pack instanced trait" << traitType << "of size" << traitBinaryDataSize
<< "bytes since it exceeds the maximum size " << AvatarTraits::MAXIMUM_TRAIT_SIZE << "bytes";
return 0;
}
// Write packed data to stream
qint64 bytesWritten = 0;
bytesWritten += destination.writePrimitive((TraitType)traitType);
bytesWritten += destination.write(traitInstanceID.toRfc4122());
if (!traitBinaryData.isNull()) {
bytesWritten += destination.writePrimitive((TraitWireSize)traitBinaryDataSize);
bytesWritten += destination.write(traitBinaryData);
} else {
bytesWritten += destination.writePrimitive(AvatarTraits::DELETED_TRAIT_SIZE);
}
return bytesWritten;
}
qint64 packVersionedTraitInstance(TraitType traitType, TraitInstanceID traitInstanceID,
ExtendedIODevice& destination, TraitVersion traitVersion,
AvatarData& avatar) {
// Call packer function
auto traitBinaryData = avatar.packTraitInstance(traitType, traitInstanceID);
auto traitBinaryDataSize = traitBinaryData.size();
// Verify packed data
if (traitBinaryDataSize > AvatarTraits::MAXIMUM_TRAIT_SIZE) {
qWarning() << "Refusing to pack instanced trait" << traitType << "of size" << traitBinaryDataSize
<< "bytes since it exceeds the maximum size " << AvatarTraits::MAXIMUM_TRAIT_SIZE << "bytes";
return 0;
}
// Write packed data to stream
qint64 bytesWritten = 0;
bytesWritten += destination.writePrimitive((TraitType)traitType);
bytesWritten += destination.writePrimitive((TraitVersion)traitVersion);
bytesWritten += destination.write(traitInstanceID.toRfc4122());
if (!traitBinaryData.isNull()) {
bytesWritten += destination.writePrimitive((TraitWireSize)traitBinaryDataSize);
bytesWritten += destination.write(traitBinaryData);
} else {
bytesWritten += destination.writePrimitive(AvatarTraits::DELETED_TRAIT_SIZE);
}
return bytesWritten;
}
qint64 packInstancedTraitDelete(TraitType traitType, TraitInstanceID instanceID, ExtendedIODevice& destination,
TraitVersion traitVersion) {
qint64 bytesWritten = 0;
bytesWritten += destination.writePrimitive(traitType);
if (traitVersion > DEFAULT_TRAIT_VERSION) {
bytesWritten += destination.writePrimitive(traitVersion);
}
bytesWritten += destination.write(instanceID.toRfc4122());
bytesWritten += destination.writePrimitive(DELETED_TRAIT_SIZE);
return bytesWritten;
}
};

View file

@ -14,20 +14,35 @@
#include <algorithm>
#include <cstdint>
#include <array>
#include <vector>
#include <QtCore/QUuid>
class ExtendedIODevice;
class AvatarData;
namespace AvatarTraits {
enum TraitType : int8_t {
// Null trait
NullTrait = -1,
SkeletonModelURL,
// Simple traits
SkeletonModelURL = 0,
// Instanced traits
FirstInstancedTrait,
AvatarEntity = FirstInstancedTrait,
Grab,
// Traits count
TotalTraitTypes
};
const int NUM_SIMPLE_TRAITS = (int)FirstInstancedTrait;
const int NUM_INSTANCED_TRAITS = (int)TotalTraitTypes - (int)FirstInstancedTrait;
const int NUM_TRAITS = (int)TotalTraitTypes;
using TraitInstanceID = QUuid;
inline bool isSimpleTrait(TraitType traitType) {
@ -46,22 +61,19 @@ namespace AvatarTraits {
const TraitMessageSequence FIRST_TRAIT_SEQUENCE = 0;
const TraitMessageSequence MAX_TRAIT_SEQUENCE = INT64_MAX;
inline qint64 packInstancedTraitDelete(TraitType traitType, TraitInstanceID instanceID, ExtendedIODevice& destination,
TraitVersion traitVersion = NULL_TRAIT_VERSION) {
qint64 bytesWritten = 0;
qint64 packTrait(TraitType traitType, ExtendedIODevice& destination, const AvatarData& avatar);
qint64 packVersionedTrait(TraitType traitType, ExtendedIODevice& destination,
TraitVersion traitVersion, const AvatarData& avatar);
bytesWritten += destination.writePrimitive(traitType);
qint64 packTraitInstance(TraitType traitType, TraitInstanceID traitInstanceID,
ExtendedIODevice& destination, AvatarData& avatar);
qint64 packVersionedTraitInstance(TraitType traitType, TraitInstanceID traitInstanceID,
ExtendedIODevice& destination, TraitVersion traitVersion,
AvatarData& avatar);
if (traitVersion > DEFAULT_TRAIT_VERSION) {
bytesWritten += destination.writePrimitive(traitVersion);
}
qint64 packInstancedTraitDelete(TraitType traitType, TraitInstanceID instanceID, ExtendedIODevice& destination,
TraitVersion traitVersion = NULL_TRAIT_VERSION);
bytesWritten += destination.write(instanceID.toRfc4122());
bytesWritten += destination.writePrimitive(DELETED_TRAIT_SIZE);
return bytesWritten;
}
};
#endif // hifi_AvatarTraits_h

View file

@ -106,9 +106,10 @@ int ClientTraitsHandler::sendChangedTraitsToMixer() {
auto traitType = static_cast<AvatarTraits::TraitType>(std::distance(traitStatusesCopy.simpleCBegin(), simpleIt));
if (initialSend || *simpleIt == Updated) {
if (traitType == AvatarTraits::SkeletonModelURL) {
bytesWritten += _owningAvatar->packTrait(traitType, *traitsPacketList);
bytesWritten += AvatarTraits::packTrait(traitType, *traitsPacketList, *_owningAvatar);
if (traitType == AvatarTraits::SkeletonModelURL) {
// keep track of our skeleton version in case we get an override back
_currentSkeletonVersion = _currentTraitVersion;
}
@ -124,7 +125,9 @@ int ClientTraitsHandler::sendChangedTraitsToMixer() {
|| instanceIDValuePair.value == Updated) {
// this is a changed trait we need to send or we haven't send out trait information yet
// ask the owning avatar to pack it
bytesWritten += _owningAvatar->packTraitInstance(instancedIt->traitType, instanceIDValuePair.id, *traitsPacketList);
bytesWritten += AvatarTraits::packTraitInstance(instancedIt->traitType, instanceIDValuePair.id,
*traitsPacketList, *_owningAvatar);
} else if (!initialSend && instanceIDValuePair.value == Deleted) {
// pack delete for this trait instance
bytesWritten += AvatarTraits::packInstancedTraitDelete(instancedIt->traitType, instanceIDValuePair.id,
@ -162,11 +165,11 @@ void ClientTraitsHandler::processTraitOverride(QSharedPointer<ReceivedMessage> m
// override the skeleton URL but do not mark the trait as having changed
// so that we don't unecessarily send a new trait packet to the mixer with the overriden URL
auto encodedSkeletonURL = QUrl::fromEncoded(message->readWithoutCopy(traitBinarySize));
auto hasChangesBefore = _hasChangedTraits;
_owningAvatar->setSkeletonModelURL(encodedSkeletonURL);
auto traitBinaryData = message->readWithoutCopy(traitBinarySize);
_owningAvatar->processTrait(traitType, traitBinaryData);
// setSkeletonModelURL will flag us for changes to the SkeletonModelURL so we reset some state here to
// avoid unnecessarily sending the overriden skeleton model URL back to the mixer

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