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

This commit is contained in:
raveenajain 2019-04-01 21:48:55 +01:00
commit ce6f2b7bcc
56 changed files with 781 additions and 372 deletions

View file

@ -51,7 +51,7 @@ void sendEnvironmentPacket(const SharedNodePointer& node, AudioMixerClientData&
// mix helpers // mix helpers
inline float approximateGain(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd); inline float approximateGain(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd);
inline float computeGain(float masterAvatarGain, float masterInjectorGain, const AvatarAudioStream& listeningNodeStream, inline float computeGain(float masterAvatarGain, float masterInjectorGain, const AvatarAudioStream& listeningNodeStream,
const PositionalAudioStream& streamToAdd, const glm::vec3& relativePosition, float distance, bool isEcho); const PositionalAudioStream& streamToAdd, const glm::vec3& relativePosition, float distance);
inline float computeAzimuth(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd, inline float computeAzimuth(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd,
const glm::vec3& relativePosition); const glm::vec3& relativePosition);
@ -504,14 +504,12 @@ void AudioMixerSlave::addStream(AudioMixerClientData::MixableStream& mixableStre
glm::vec3 relativePosition = streamToAdd->getPosition() - listeningNodeStream.getPosition(); glm::vec3 relativePosition = streamToAdd->getPosition() - listeningNodeStream.getPosition();
float distance = glm::max(glm::length(relativePosition), EPSILON); 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 azimuth = isEcho ? 0.0f : computeAzimuth(listeningNodeStream, listeningNodeStream, relativePosition);
float gain = masterAvatarGain;
if (!isSoloing) {
gain = computeGain(masterAvatarGain, masterInjectorGain, listeningNodeStream, *streamToAdd, relativePosition,
distance, isEcho);
}
const int HRTF_DATASET_INDEX = 1; const int HRTF_DATASET_INDEX = 1;
if (!streamToAdd->lastPopSucceeded()) { if (!streamToAdd->lastPopSucceeded()) {
@ -599,8 +597,8 @@ void AudioMixerSlave::updateHRTFParameters(AudioMixerClientData::MixableStream&
glm::vec3 relativePosition = streamToAdd->getPosition() - listeningNodeStream.getPosition(); glm::vec3 relativePosition = streamToAdd->getPosition() - listeningNodeStream.getPosition();
float distance = glm::max(glm::length(relativePosition), EPSILON); float distance = glm::max(glm::length(relativePosition), EPSILON);
float gain = computeGain(masterAvatarGain, masterInjectorGain, listeningNodeStream, *streamToAdd, relativePosition, float gain = isEcho ? 1.0f : computeGain(masterAvatarGain, masterInjectorGain, listeningNodeStream, *streamToAdd,
distance, isEcho); relativePosition, distance);
float azimuth = isEcho ? 0.0f : computeAzimuth(listeningNodeStream, listeningNodeStream, relativePosition); float azimuth = isEcho ? 0.0f : computeAzimuth(listeningNodeStream, listeningNodeStream, relativePosition);
mixableStream.hrtf->setParameterHistory(azimuth, distance, gain); mixableStream.hrtf->setParameterHistory(azimuth, distance, gain);
@ -743,8 +741,7 @@ float computeGain(float masterAvatarGain,
const AvatarAudioStream& listeningNodeStream, const AvatarAudioStream& listeningNodeStream,
const PositionalAudioStream& streamToAdd, const PositionalAudioStream& streamToAdd,
const glm::vec3& relativePosition, const glm::vec3& relativePosition,
float distance, float distance) {
bool isEcho) {
float gain = 1.0f; float gain = 1.0f;
// injector: apply attenuation // injector: apply attenuation
@ -754,7 +751,7 @@ float computeGain(float masterAvatarGain,
gain *= masterInjectorGain; gain *= masterInjectorGain;
// avatar: apply fixed off-axis attenuation to make them quieter as they turn away // 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; glm::vec3 rotatedListenerPosition = glm::inverse(streamToAdd.getOrientation()) * relativePosition;
// source directivity is based on angle of emission, in local coordinates // source directivity is based on angle of emission, in local coordinates

View file

@ -253,10 +253,29 @@ void AvatarMixer::start() {
int lockWait, nodeTransform, functor; int lockWait, nodeTransform, functor;
// Set our query each frame
{ {
_entityViewer.queryOctree(); _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 // Allow nodes to process any pending/queued packets across our worker threads
{ {
auto start = usecTimestampNow(); auto start = usecTimestampNow();
@ -827,7 +846,7 @@ void AvatarMixer::sendStatsPacket() {
QJsonObject avatarsObject; QJsonObject avatarsObject;
auto nodeList = DependencyManager::get<NodeList>(); auto nodeList = DependencyManager::get<NodeList>();
// add stats for each listerner // add stats for each listener
nodeList->eachNode([&](const SharedNodePointer& node) { nodeList->eachNode([&](const SharedNodePointer& node) {
QJsonObject avatarStats; QJsonObject avatarStats;
@ -851,6 +870,12 @@ void AvatarMixer::sendStatsPacket() {
avatarStats["delta_full_vs_avatar_data_kbps"] = avatarStats["delta_full_vs_avatar_data_kbps"] =
(double)outboundAvatarDataKbps - avatarStats[OUTBOUND_AVATAR_DATA_STATS_KEY].toDouble(); (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; avatarsObject[uuidStringWithoutCurlyBraces(node->getUUID())] = avatarStats;
@ -973,19 +998,30 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) {
{ {
const QString CONNECTION_RATE = "connection_rate"; const QString CONNECTION_RATE = "connection_rate";
auto nodeList = DependencyManager::get<NodeList>(); auto nodeList = DependencyManager::get<NodeList>();
auto defaultConnectionRate = nodeList->getMaxConnectionRate(); bool success;
int connectionRate = avatarMixerGroupObject[CONNECTION_RATE].toInt((int)defaultConnectionRate); int connectionRate = avatarMixerGroupObject[CONNECTION_RATE].toString().toInt(&success);
nodeList->setMaxConnectionRate(connectionRate); 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"; const QString AVATARS_SETTINGS_KEY = "avatars";
static const QString MIN_HEIGHT_OPTION = "min_avatar_height"; 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); _domainMinimumHeight = glm::clamp(settingMinHeight, MIN_AVATAR_HEIGHT, MAX_AVATAR_HEIGHT);
static const QString MAX_HEIGHT_OPTION = "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); _domainMaximumHeight = glm::clamp(settingMaxHeight, MIN_AVATAR_HEIGHT, MAX_AVATAR_HEIGHT);
// make sure that the domain owner didn't flip min and max // 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; << "and a maximum avatar height of" << _domainMaximumHeight;
static const QString AVATAR_WHITELIST_OPTION = "avatar_whitelist"; 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); .toString().split(',', QString::KeepEmptyParts);
static const QString REPLACEMENT_AVATAR_OPTION = "replacement_avatar"; 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(); .toString();
if (_slaveSharedData.skeletonURLWhitelist.count() == 1 && _slaveSharedData.skeletonURLWhitelist[0].isEmpty()) { if (_slaveSharedData.skeletonURLWhitelist.count() == 1 && _slaveSharedData.skeletonURLWhitelist[0].isEmpty()) {
@ -1018,9 +1054,12 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) {
void AvatarMixer::setupEntityQuery() { void AvatarMixer::setupEntityQuery() {
_entityViewer.init(); _entityViewer.init();
EntityTreePointer entityTree = _entityViewer.getTree();
DependencyManager::registerInheritance<SpatialParentFinder, AssignmentParentFinder>(); DependencyManager::registerInheritance<SpatialParentFinder, AssignmentParentFinder>();
DependencyManager::set<AssignmentParentFinder>(_entityViewer.getTree()); DependencyManager::set<AssignmentParentFinder>(entityTree);
_slaveSharedData.entityTree = _entityViewer.getTree();
connect(entityTree.get(), &EntityTree::addingEntityPointer, this, &AvatarMixer::entityAdded);
connect(entityTree.get(), &EntityTree::deletingEntityPointer, this, &AvatarMixer::entityChange);
// ES query: {"avatarPriority": true, "type": "Zone"} // ES query: {"avatarPriority": true, "type": "Zone"}
QJsonObject priorityZoneQuery; QJsonObject priorityZoneQuery;
@ -1028,6 +1067,7 @@ void AvatarMixer::setupEntityQuery() {
priorityZoneQuery["type"] = "Zone"; priorityZoneQuery["type"] = "Zone";
_entityViewer.getOctreeQuery().setJSONParameters(priorityZoneQuery); _entityViewer.getOctreeQuery().setJSONParameters(priorityZoneQuery);
_slaveSharedData.entityTree = entityTree;
} }
void AvatarMixer::handleOctreePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) { 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() { void AvatarMixer::aboutToFinish() {
DependencyManager::destroy<ResourceManager>(); DependencyManager::destroy<ResourceManager>();
DependencyManager::destroy<ResourceCacheSharedItems>(); DependencyManager::destroy<ResourceCacheSharedItems>();

View file

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

View file

@ -129,7 +129,7 @@ int AvatarMixerClientData::parseData(ReceivedMessage& message, const SlaveShared
incrementNumOutOfOrderSends(); incrementNumOutOfOrderSends();
} }
_lastReceivedSequenceNumber = sequenceNumber; _lastReceivedSequenceNumber = sequenceNumber;
glm::vec3 oldPosition = getPosition(); glm::vec3 oldPosition = _avatar->getClientGlobalPosition();
bool oldHasPriority = _avatar->getHasPriority(); bool oldHasPriority = _avatar->getHasPriority();
// compute the offset to the data payload // 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. // Regardless of what the client says, restore the priority as we know it without triggering any update.
_avatar->setHasPriorityWithoutTimestampReset(oldHasPriority); _avatar->setHasPriorityWithoutTimestampReset(oldHasPriority);
auto newPosition = getPosition(); auto newPosition = _avatar->getClientGlobalPosition();
if (newPosition != oldPosition) { if (newPosition != oldPosition || _avatar->getNeedsHeroCheck()) {
//#define AVATAR_HERO_TEST_HACK
#ifdef AVATAR_HERO_TEST_HACK
{
const static QString heroKey { "HERO" };
_avatar->setPriorityAvatar(_avatar->getDisplayName().contains(heroKey));
}
#else
EntityTree& entityTree = *slaveSharedData.entityTree; EntityTree& entityTree = *slaveSharedData.entityTree;
FindPriorityZone findPriorityZone { newPosition, false } ; FindPriorityZone findPriorityZone { newPosition } ;
entityTree.recurseTreeWithOperation(&FindPriorityZone::operation, &findPriorityZone); entityTree.recurseTreeWithOperation(&FindPriorityZone::operation, &findPriorityZone);
_avatar->setHasPriority(findPriorityZone.isInPriorityZone); _avatar->setHasPriority(findPriorityZone.isInPriorityZone);
//if (findPriorityZone.isInPriorityZone) { _avatar->setNeedsHeroCheck(false);
// qCWarning(avatars) << "Avatar" << _avatar->getSessionDisplayName() << "in hero zone";
//}
#endif
} }
return true; return true;
@ -341,7 +331,7 @@ void AvatarMixerClientData::checkSkeletonURLAgainstWhitelist(const SlaveSharedDa
// the returned set traits packet uses the trait version from the incoming packet // 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 // 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>(); auto nodeList = DependencyManager::get<NodeList>();
nodeList->sendPacket(std::move(packet), sendingNode); 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, void AvatarMixerSlave::configureBroadcast(ConstIter begin, ConstIter end,
p_high_resolution_clock::time_point lastFrameTimestamp, p_high_resolution_clock::time_point lastFrameTimestamp,
float maxKbpsPerNode, float throttlingRatio) { float maxKbpsPerNode, float throttlingRatio,
float priorityReservedFraction) {
_begin = begin; _begin = begin;
_end = end; _end = end;
_lastFrameTimestamp = lastFrameTimestamp; _lastFrameTimestamp = lastFrameTimestamp;
_maxKbpsPerNode = maxKbpsPerNode; _maxKbpsPerNode = maxKbpsPerNode;
_throttlingRatio = throttlingRatio; _throttlingRatio = throttlingRatio;
_avatarHeroFraction = priorityReservedFraction;
} }
void AvatarMixerSlave::harvestStats(AvatarMixerSlaveStats& stats) { void AvatarMixerSlave::harvestStats(AvatarMixerSlaveStats& stats) {
@ -139,7 +141,8 @@ qint64 AvatarMixerSlave::addChangedTraitsToBulkPacket(AvatarMixerClientData* lis
if (lastReceivedVersion > lastSentVersionRef) { if (lastReceivedVersion > lastSentVersionRef) {
bytesWritten += addTraitsNodeHeader(listeningNodeData, sendingNodeData, traitsPacketList, bytesWritten); bytesWritten += addTraitsNodeHeader(listeningNodeData, sendingNodeData, traitsPacketList, bytesWritten);
// there is an update to this trait, add it to the traits packet // 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 // update the last sent version
lastSentVersionRef = lastReceivedVersion; lastSentVersionRef = lastReceivedVersion;
// Remember which versions we sent in this particular packet // Remember which versions we sent in this particular packet
@ -194,7 +197,8 @@ qint64 AvatarMixerSlave::addChangedTraitsToBulkPacket(AvatarMixerClientData* lis
bytesWritten += addTraitsNodeHeader(listeningNodeData, sendingNodeData, traitsPacketList, bytesWritten); bytesWritten += addTraitsNodeHeader(listeningNodeData, sendingNodeData, traitsPacketList, bytesWritten);
// this instance version exists and has never been sent or is newer so we need to send it // 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()) { if (sentInstanceIt != sentIDValuePairs.end()) {
sentInstanceIt->value = receivedVersion; sentInstanceIt->value = receivedVersion;
@ -308,7 +312,6 @@ namespace {
} // Close anonymous namespace. } // Close anonymous namespace.
void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) { void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) {
const float AVATAR_HERO_FRACTION { 0.4f };
const Node* destinationNode = node.data(); const Node* destinationNode = node.data();
auto nodeList = DependencyManager::get<NodeList>(); auto nodeList = DependencyManager::get<NodeList>();
@ -343,7 +346,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
// max number of avatarBytes per frame (13 900, typical) // 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 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 // keep track of the number of other avatars held back in this frame
int numAvatarsHeldBack = 0; int numAvatarsHeldBack = 0;
@ -469,8 +472,8 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
SortableAvatar(avatarNodeData, sourceAvatarNode, lastEncodeTime)); SortableAvatar(avatarNodeData, sourceAvatarNode, lastEncodeTime));
} }
// If Avatar A's PAL WAS open but is no longer open, AND // If Node A's PAL WAS open but is no longer open, AND
// Avatar A is ignoring Avatar B OR Avatar B is ignoring Avatar A... // 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 // 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). // 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(); const MixerAvatar* sourceAvatar = sourceNodeData->getConstAvatarData();
// Typically all out-of-view avatars but such avatars' priorities will rise with time: // 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) { if (isLowerPriority) {
detail = PALIsOpen ? AvatarData::PALMinimum : AvatarData::MinimumData; 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; detail = distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO ? AvatarData::SendAllData : AvatarData::CullSmallData;
destinationNodeData->incrementAvatarInView(); destinationNodeData->incrementAvatarInView();
// If the time that the mixer sent AVATAR DATA about Avatar B to Avatar A is BEFORE OR EQUAL TO // 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 Avatar A. // the time that Avatar B flagged an IDENTITY DATA change, send IDENTITY DATA about Avatar B to Node A.
if (sourceAvatar->hasProcessedFirstIdentity() if (sourceAvatar->hasProcessedFirstIdentity()
&& destinationNodeData->getLastBroadcastTime(sourceNode->getLocalID()) <= sourceNodeData->getIdentityChangeTimestamp()) { && destinationNodeData->getLastBroadcastTime(sourceNode->getLocalID()) <= sourceNodeData->getIdentityChangeTimestamp()) {
identityBytesSent += sendIdentityPacket(*identityPacketList, sourceNodeData, *destinationNode); identityBytesSent += sendIdentityPacket(*identityPacketList, sourceNodeData, *destinationNode);

View file

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

View file

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

View file

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

View file

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

View file

@ -74,6 +74,7 @@
* avatar. <em>Read-only.</em> * avatar. <em>Read-only.</em>
* @property {number} sensorToWorldScale - The scale that transforms dimensions in the user's real world to the avatar's * @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> * size in the virtual world. <em>Read-only.</em>
* @property {boolean} hasPriority - is the avatar in a Hero zone? <em>Read-only.</em>
* *
* @example <caption>Create a scriptable avatar.</caption> * @example <caption>Create a scriptable avatar.</caption>
* (function () { * (function () {

View file

@ -1310,6 +1310,15 @@
"placeholder": "50", "placeholder": "50",
"default": "50", "default": "50",
"advanced": true "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

@ -1766,14 +1766,14 @@ void DomainServer::processOctreeDataRequestMessage(QSharedPointer<ReceivedMessag
bool remoteHasExistingData { false }; bool remoteHasExistingData { false };
QUuid id; QUuid id;
int version; int dataVersion;
message->readPrimitive(&remoteHasExistingData); message->readPrimitive(&remoteHasExistingData);
if (remoteHasExistingData) { if (remoteHasExistingData) {
constexpr size_t UUID_SIZE_BYTES = 16; constexpr size_t UUID_SIZE_BYTES = 16;
auto idData = message->read(UUID_SIZE_BYTES); auto idData = message->read(UUID_SIZE_BYTES);
id = QUuid::fromRfc4122(idData); id = QUuid::fromRfc4122(idData);
message->readPrimitive(&version); message->readPrimitive(&dataVersion);
qCDebug(domain_server) << "Entity server does have existing data: ID(" << id << ") DataVersion(" << version << ")"; qCDebug(domain_server) << "Entity server does have existing data: ID(" << id << ") DataVersion(" << dataVersion << ")";
} else { } else {
qCDebug(domain_server) << "Entity server does not have existing data"; 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); auto reply = NLPacketList::create(PacketType::OctreeDataFileReply, QByteArray(), true, true);
OctreeUtils::RawEntityData data; OctreeUtils::RawEntityData data;
if (data.readOctreeDataInfoFromFile(entityFilePath)) { 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"; qCDebug(domain_server) << "ES has sufficient octree data, not sending data";
reply->writePrimitive(false); reply->writePrimitive(false);
} else { } 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); QFile file(entityFilePath);
if (file.open(QIODevice::ReadOnly)) { if (file.open(QIODevice::ReadOnly)) {
reply->writePrimitive(true); reply->writePrimitive(true);

View file

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

View file

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

View file

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

View file

@ -18,12 +18,29 @@ import TabletScriptingInterface 1.0
Rectangle { Rectangle {
HifiConstants { id: hifi; } HifiConstants { id: hifi; }
property var muted: AudioScriptingInterface.muted;
readonly property var level: AudioScriptingInterface.inputLevel; readonly property var level: AudioScriptingInterface.inputLevel;
property var pushToTalk: AudioScriptingInterface.pushToTalk;
property var pushingToTalk: AudioScriptingInterface.pushingToTalk;
property bool gated: false; property bool gated: false;
Component.onCompleted: { Component.onCompleted: {
AudioScriptingInterface.noiseGateOpened.connect(function() { gated = false; }); AudioScriptingInterface.noiseGateOpened.connect(function() { gated = false; });
AudioScriptingInterface.noiseGateClosed.connect(function() { gated = true; }); AudioScriptingInterface.noiseGateClosed.connect(function() { gated = true; });
HMD.displayModeChanged.connect(function() {
muted = AudioScriptingInterface.muted;
pushToTalk = AudioScriptingInterface.pushToTalk;
});
AudioScriptingInterface.mutedChanged.connect(function() {
muted = AudioScriptingInterface.muted;
});
AudioScriptingInterface.pushToTalkChanged.connect(function() {
pushToTalk = AudioScriptingInterface.pushToTalk;
});
AudioScriptingInterface.pushingToTalkChanged.connect(function() {
pushingToTalk = AudioScriptingInterface.pushingToTalk;
});
} }
property bool standalone: false; property bool standalone: false;
@ -67,10 +84,10 @@ Rectangle {
hoverEnabled: true; hoverEnabled: true;
scrollGestureEnabled: false; scrollGestureEnabled: false;
onClicked: { onClicked: {
if (AudioScriptingInterface.pushToTalk) { if (pushToTalk) {
return; return;
} }
AudioScriptingInterface.muted = !AudioScriptingInterface.muted; muted = !muted;
Tablet.playSound(TabletEnums.ButtonClick); Tablet.playSound(TabletEnums.ButtonClick);
} }
drag.target: dragTarget; drag.target: dragTarget;
@ -84,16 +101,16 @@ Rectangle {
QtObject { QtObject {
id: colors; id: colors;
readonly property string unmuted: "#FFF"; readonly property string unmutedColor: "#FFF";
readonly property string muted: "#E2334D"; readonly property string mutedColor: "#E2334D";
readonly property string gutter: "#575757"; readonly property string gutter: "#575757";
readonly property string greenStart: "#39A38F"; readonly property string greenStart: "#39A38F";
readonly property string greenEnd: "#1FC6A6"; readonly property string greenEnd: "#1FC6A6";
readonly property string yellow: "#C0C000"; 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 fill: "#55000000";
readonly property string border: standalone ? "#80FFFFFF" : "#55FFFFFF"; readonly property string border: standalone ? "#80FFFFFF" : "#55FFFFFF";
readonly property string icon: AudioScriptingInterface.muted ? muted : unmuted; readonly property string icon: muted ? colors.mutedColor : unmutedColor;
} }
Item { Item {
@ -115,7 +132,7 @@ Rectangle {
readonly property string pushToTalkIcon: "../../../icons/tablet-icons/mic-ptt-i.svg"; readonly property string pushToTalkIcon: "../../../icons/tablet-icons/mic-ptt-i.svg";
id: image; id: image;
source: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) ? pushToTalkIcon : AudioScriptingInterface.muted ? mutedIcon : unmutedIcon; source: (pushToTalk && !pushingToTalk) ? pushToTalkIcon : muted ? mutedIcon : unmutedIcon;
width: 30; width: 30;
height: 30; height: 30;
@ -138,9 +155,7 @@ Rectangle {
Item { Item {
id: status; id: status;
readonly property string color: AudioScriptingInterface.muted ? colors.muted : colors.unmuted; visible: (pushToTalk && !pushingToTalk) || muted;
visible: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) || AudioScriptingInterface.muted;
anchors { anchors {
left: parent.left; left: parent.left;
@ -157,9 +172,9 @@ Rectangle {
verticalCenter: parent.verticalCenter; 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; font.pointSize: 12;
} }
@ -169,9 +184,9 @@ Rectangle {
verticalCenter: parent.verticalCenter; verticalCenter: parent.verticalCenter;
} }
width: AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk ? (HMD.active ? 27 : 25) : 50; width: pushToTalk && !pushingToTalk ? (HMD.active ? 27 : 25) : 50;
height: 4; height: 4;
color: parent.color; color: colors.icon;
} }
Rectangle { Rectangle {
@ -180,9 +195,9 @@ Rectangle {
verticalCenter: parent.verticalCenter; verticalCenter: parent.verticalCenter;
} }
width: AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk ? (HMD.active ? 27 : 25) : 50; width: pushToTalk && !pushingToTalk ? (HMD.active ? 27 : 25) : 50;
height: 4; height: 4;
color: parent.color; color: colors.icon;
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

@ -92,7 +92,7 @@ void AvatarDoctor::startDiagnosing() {
_model = resource; _model = resource;
const auto model = resource.data(); const auto model = resource.data();
const auto avatarModel = resource.data()->getHFMModel(); const auto avatarModel = resource.data()->getHFMModel();
if (!avatarModel.originalURL.endsWith(".fbx")) { if (!avatarModel.originalURL.toLower().endsWith(".fbx")) {
addError("Unsupported avatar model format.", "unsupported-format"); addError("Unsupported avatar model format.", "unsupported-format");
emit complete(getErrors()); emit complete(getErrors());
return; return;

View file

@ -295,6 +295,7 @@ class MyAvatar : public Avatar {
* @comment Avatar.setAbsoluteJointTranslationInObjectFrame as setAbsoluteJointTranslationInObjectFrame - Don't borrow because implementation is different. * @comment Avatar.setAbsoluteJointTranslationInObjectFrame as setAbsoluteJointTranslationInObjectFrame - Don't borrow because implementation is different.
* @borrows Avatar.getTargetScale as getTargetScale * @borrows Avatar.getTargetScale as getTargetScale
* @borrows Avatar.resetLastSent as resetLastSent * @borrows Avatar.resetLastSent as resetLastSent
* @borrows Avatar.hasPriority as hasPriority
*/ */
// FIXME: `glm::vec3 position` is not accessible from QML, so this exposes position in a QML-native type // FIXME: `glm::vec3 position` is not accessible from QML, so this exposes position in a QML-native type
Q_PROPERTY(QVector3D qmlPosition READ getQmlPosition) Q_PROPERTY(QVector3D qmlPosition READ getQmlPosition)
@ -2170,7 +2171,7 @@ private:
bool getEnableStepResetRotation() const { return _stepResetRotationEnabled; } bool getEnableStepResetRotation() const { return _stepResetRotationEnabled; }
void setEnableDrawAverageFacing(bool drawAverage) { _drawAverageFacingEnabled = drawAverage; } void setEnableDrawAverageFacing(bool drawAverage) { _drawAverageFacingEnabled = drawAverage; }
bool getEnableDrawAverageFacing() const { return _drawAverageFacingEnabled; } bool getEnableDrawAverageFacing() const { return _drawAverageFacingEnabled; }
bool isMyAvatar() const override { return true; } virtual bool isMyAvatar() const override { return true; }
virtual int parseDataFromBuffer(const QByteArray& buffer) override; virtual int parseDataFromBuffer(const QByteArray& buffer) override;
virtual glm::vec3 getSkeletonPosition() const override; virtual glm::vec3 getSkeletonPosition() const override;
int _skeletonModelChangeCount { 0 }; int _skeletonModelChangeCount { 0 };

View file

@ -365,7 +365,7 @@ void OtherAvatar::handleChangedAvatarEntityData() {
// AVATAR ENTITY UPDATE FLOW // AVATAR ENTITY UPDATE FLOW
// - if queueEditEntityMessage() sees "AvatarEntity" HostType it calls _myAvatar->storeAvatarEntityDataPayload() // - if queueEditEntityMessage() sees "AvatarEntity" HostType it calls _myAvatar->storeAvatarEntityDataPayload()
// - storeAvatarEntityDataPayload() saves the payload and flags the trait instance for the entity as updated, // - 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() // - AvatarHashMap::processBulkAvatarTraits() on other interfaces calls avatar->processTraitInstance()
// - AvatarData::processTraitInstance() calls storeAvatarEntityDataPayload(), which sets _avatarEntityDataChanged = true // - AvatarData::processTraitInstance() calls storeAvatarEntityDataPayload(), which sets _avatarEntityDataChanged = true
// - (My)Avatar::simulate() calls handleChangedAvatarEntityData() every frame which checks _avatarEntityDataChanged // - (My)Avatar::simulate() calls handleChangedAvatarEntityData() every frame which checks _avatarEntityDataChanged
@ -495,6 +495,18 @@ void OtherAvatar::handleChangedAvatarEntityData() {
const QUuid NULL_ID = QUuid("{00000000-0000-0000-0000-000000000005}"); const QUuid NULL_ID = QUuid("{00000000-0000-0000-0000-000000000005}");
entity->setParentID(NULL_ID); entity->setParentID(NULL_ID);
entity->setParentID(oldParentID); 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)) { if (entityTree->updateEntity(entityID, properties)) {
entity->updateLastEditedFromRemote(); entity->updateLastEditedFromRemote();
} else { } else {

View file

@ -224,10 +224,10 @@ void Audio::saveData() {
} }
void Audio::loadData() { void Audio::loadData() {
_desktopMuted = _desktopMutedSetting.get(); setMutedDesktop(_desktopMutedSetting.get());
_hmdMuted = _hmdMutedSetting.get(); setMutedHMD(_hmdMutedSetting.get());
_pttDesktop = _pttDesktopSetting.get(); setPTTDesktop(_pttDesktopSetting.get());
_pttHMD = _pttHMDSetting.get(); setPTTHMD(_pttHMDSetting.get());
auto client = DependencyManager::get<AudioClient>().data(); auto client = DependencyManager::get<AudioClient>().data();
QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted()), Q_ARG(bool, false)); QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted()), Q_ARG(bool, false));

View file

@ -372,13 +372,6 @@ bool Avatar::applyGrabChanges() {
target->removeGrab(grab); target->removeGrab(grab);
_avatarGrabs.erase(itr); _avatarGrabs.erase(itr);
grabAddedOrRemoved = true; 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 { } else {
undeleted.push_back(id); undeleted.push_back(id);
} }

View file

@ -180,7 +180,6 @@ public:
/// Returns the distance to use as a LOD parameter. /// Returns the distance to use as a LOD parameter.
float getLODDistance() const; float getLODDistance() const;
virtual bool isMyAvatar() const override { return false; }
virtual void createOrb() { } virtual void createOrb() { }
enum class LoadingStatus { enum class LoadingStatus {

View file

@ -28,9 +28,10 @@
namespace AvatarTraits { namespace AvatarTraits {
template<typename T, T defaultValue> template<typename T, T defaultValue>
class AssociatedTraitValues { class AssociatedTraitValues {
using SimpleTypesArray = std::array<T, NUM_SIMPLE_TRAITS>;
public: public:
// constructor that pre-fills _simpleTypes with the default value specified by the template // 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 /// inserts the given value for the given simple trait type
void insert(TraitType type, T value) { _simpleTypes[type] = value; } void insert(TraitType type, T value) { _simpleTypes[type] = value; }
@ -71,12 +72,12 @@ namespace AvatarTraits {
} }
/// const iterators for the vector of simple type values /// const iterators for the vector of simple type values
typename std::vector<T>::const_iterator simpleCBegin() const { return _simpleTypes.cbegin(); } typename SimpleTypesArray::const_iterator simpleCBegin() const { return _simpleTypes.cbegin(); }
typename std::vector<T>::const_iterator simpleCEnd() const { return _simpleTypes.cend(); } typename SimpleTypesArray::const_iterator simpleCEnd() const { return _simpleTypes.cend(); }
/// non-const iterators for the vector of simple type values /// non-const iterators for the vector of simple type values
typename std::vector<T>::iterator simpleBegin() { return _simpleTypes.begin(); } typename SimpleTypesArray::iterator simpleBegin() { return _simpleTypes.begin(); }
typename std::vector<T>::iterator simpleEnd() { return _simpleTypes.end(); } typename SimpleTypesArray::iterator simpleEnd() { return _simpleTypes.end(); }
struct TraitWithInstances { struct TraitWithInstances {
TraitType traitType; TraitType traitType;
@ -96,7 +97,7 @@ namespace AvatarTraits {
typename std::vector<TraitWithInstances>::iterator instancedEnd() { return _instancedTypes.end(); } typename std::vector<TraitWithInstances>::iterator instancedEnd() { return _instancedTypes.end(); }
private: private:
std::vector<T> _simpleTypes; SimpleTypesArray _simpleTypes;
/// return the iterator to the matching TraitWithInstances object for a given instanced trait type /// return the iterator to the matching TraitWithInstances object for a given instanced trait type
typename std::vector<TraitWithInstances>::iterator instancesForTrait(TraitType traitType) { 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 // 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). // 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 // 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 // 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) auto newHandState = getSemiNibbleAt(bitItems, HAND_STATE_START_BIT)
+ (oneAtBit16(bitItems, HAND_STATE_FINGER_POINTING_BIT) ? IS_FINGER_POINTING_FLAG : 0); + (oneAtBit16(bitItems, HAND_STATE_FINGER_POINTING_BIT) ? IS_FINGER_POINTING_FLAG : 0);
@ -1990,42 +1991,16 @@ QUrl AvatarData::getWireSafeSkeletonModelURL() const {
} }
} }
qint64 AvatarData::packTrait(AvatarTraits::TraitType traitType, ExtendedIODevice& destination, QByteArray AvatarData::packSkeletonModelURL() const {
AvatarTraits::TraitVersion traitVersion) { return getWireSafeSkeletonModelURL().toEncoded();
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;
} }
void AvatarData::unpackSkeletonModelURL(const QByteArray& data) {
auto skeletonModelURL = QUrl::fromEncoded(data);
setSkeletonModelURL(skeletonModelURL);
}
qint64 AvatarData::packAvatarEntityTraitInstance(AvatarTraits::TraitType traitType, QByteArray AvatarData::packAvatarEntityTraitInstance(AvatarTraits::TraitInstanceID traitInstanceID) {
AvatarTraits::TraitInstanceID traitInstanceID,
ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion) {
qint64 bytesWritten = 0;
// grab a read lock on the avatar entities and check for entity data for the given ID // grab a read lock on the avatar entities and check for entity data for the given ID
QByteArray entityBinaryData; QByteArray entityBinaryData;
_avatarEntitiesLock.withReadLock([this, &entityBinaryData, &traitInstanceID] { _avatarEntitiesLock.withReadLock([this, &entityBinaryData, &traitInstanceID] {
@ -2034,104 +2009,48 @@ qint64 AvatarData::packAvatarEntityTraitInstance(AvatarTraits::TraitType traitTy
} }
}); });
if (entityBinaryData.size() > AvatarTraits::MAXIMUM_TRAIT_SIZE) { return entityBinaryData;
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;
} }
QByteArray AvatarData::packGrabTraitInstance(AvatarTraits::TraitInstanceID traitInstanceID) {
qint64 AvatarData::packGrabTraitInstance(AvatarTraits::TraitType traitType,
AvatarTraits::TraitInstanceID traitInstanceID,
ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion) {
qint64 bytesWritten = 0;
// grab a read lock on the avatar grabs and check for grab data for the given ID // grab a read lock on the avatar grabs and check for grab data for the given ID
QByteArray grabBinaryData; QByteArray grabBinaryData;
_avatarGrabsLock.withReadLock([this, &grabBinaryData, &traitInstanceID] { _avatarGrabsLock.withReadLock([this, &grabBinaryData, &traitInstanceID] {
if (_avatarGrabData.contains(traitInstanceID)) { if (_avatarGrabData.contains(traitInstanceID)) {
grabBinaryData = _avatarGrabData[traitInstanceID]; grabBinaryData = _avatarGrabData[traitInstanceID];
} }
}); });
if (grabBinaryData.size() > AvatarTraits::MAXIMUM_TRAIT_SIZE) { return grabBinaryData;
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;
} }
qint64 AvatarData::packTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID traitInstanceID, QByteArray AvatarData::packTrait(AvatarTraits::TraitType traitType) const {
ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion) { QByteArray traitBinaryData;
qint64 bytesWritten = 0;
// 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) { if (traitType == AvatarTraits::AvatarEntity) {
bytesWritten += packAvatarEntityTraitInstance(traitType, traitInstanceID, destination, traitVersion); traitBinaryData = packAvatarEntityTraitInstance(traitInstanceID);
} else if (traitType == AvatarTraits::Grab) { } else if (traitType == AvatarTraits::Grab) {
bytesWritten += packGrabTraitInstance(traitType, traitInstanceID, destination, traitVersion); traitBinaryData = packGrabTraitInstance(traitInstanceID);
} }
return bytesWritten; return traitBinaryData;
}
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);
}
});
}
} }
void AvatarData::processTrait(AvatarTraits::TraitType traitType, QByteArray traitBinaryData) { void AvatarData::processTrait(AvatarTraits::TraitType traitType, QByteArray traitBinaryData) {
if (traitType == AvatarTraits::SkeletonModelURL) { if (traitType == AvatarTraits::SkeletonModelURL) {
// get the URL from the binary data unpackSkeletonModelURL(traitBinaryData);
auto skeletonModelURL = QUrl::fromEncoded(traitBinaryData);
setSkeletonModelURL(skeletonModelURL);
} }
} }
@ -2152,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 AvatarData::identityByteArray(bool setIsReplicated) const {
QByteArray identityData; QByteArray identityData;
QDataStream identityStream(&identityData, QIODevice::Append); QDataStream identityStream(&identityData, QIODevice::Append);

View file

@ -479,6 +479,7 @@ class AvatarData : public QObject, public SpatiallyNestable {
* avatar. <em>Read-only.</em> * avatar. <em>Read-only.</em>
* @property {number} sensorToWorldScale - The scale that transforms dimensions in the user's real world to the avatar's * @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> * size in the virtual world. <em>Read-only.</em>
* @property {boolean} hasPriority - is the avatar in a Hero zone? <em>Read-only.</em>
*/ */
Q_PROPERTY(glm::vec3 position READ getWorldPosition WRITE setPositionViaScript) Q_PROPERTY(glm::vec3 position READ getWorldPosition WRITE setPositionViaScript)
Q_PROPERTY(float scale READ getDomainLimitedScale WRITE setTargetScale) Q_PROPERTY(float scale READ getDomainLimitedScale WRITE setTargetScale)
@ -518,6 +519,8 @@ class AvatarData : public QObject, public SpatiallyNestable {
Q_PROPERTY(float sensorToWorldScale READ getSensorToWorldScale) Q_PROPERTY(float sensorToWorldScale READ getSensorToWorldScale)
Q_PROPERTY(bool hasPriority READ getHasPriority)
public: public:
virtual QString getName() const override { return QString("Avatar:") + _displayName; } virtual QString getName() const override { return QString("Avatar:") + _displayName; }
@ -1134,18 +1137,16 @@ public:
// identityChanged returns true if identity has changed, false otherwise. Similarly for displayNameChanged and skeletonModelUrlChange. // identityChanged returns true if identity has changed, false otherwise. Similarly for displayNameChanged and skeletonModelUrlChange.
void processAvatarIdentity(QDataStream& packetStream, bool& identityChanged, bool& displayNameChanged); void processAvatarIdentity(QDataStream& packetStream, bool& identityChanged, bool& displayNameChanged);
qint64 packTrait(AvatarTraits::TraitType traitType, ExtendedIODevice& destination, QByteArray packTrait(AvatarTraits::TraitType traitType) const;
AvatarTraits::TraitVersion traitVersion = AvatarTraits::NULL_TRAIT_VERSION); QByteArray packTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID);
qint64 packTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID,
ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion = AvatarTraits::NULL_TRAIT_VERSION);
void prepareResetTraitInstances();
void processTrait(AvatarTraits::TraitType traitType, QByteArray traitBinaryData); void processTrait(AvatarTraits::TraitType traitType, QByteArray traitBinaryData);
void processTraitInstance(AvatarTraits::TraitType traitType, void processTraitInstance(AvatarTraits::TraitType traitType,
AvatarTraits::TraitInstanceID instanceID, QByteArray traitBinaryData); AvatarTraits::TraitInstanceID instanceID, QByteArray traitBinaryData);
void processDeletedTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID); void processDeletedTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID);
void prepareResetTraitInstances();
QByteArray identityByteArray(bool setIsReplicated = false) const; QByteArray identityByteArray(bool setIsReplicated = false) const;
QUrl getWireSafeSkeletonModelURL() const; QUrl getWireSafeSkeletonModelURL() const;
@ -1596,12 +1597,12 @@ protected:
bool hasParent() const { return !getParentID().isNull(); } bool hasParent() const { return !getParentID().isNull(); }
bool hasFaceTracker() const { return _headData ? _headData->_isFaceTrackerConnected : false; } bool hasFaceTracker() const { return _headData ? _headData->_isFaceTrackerConnected : false; }
qint64 packAvatarEntityTraitInstance(AvatarTraits::TraitType traitType, QByteArray packSkeletonModelURL() const;
AvatarTraits::TraitInstanceID traitInstanceID, QByteArray packAvatarEntityTraitInstance(AvatarTraits::TraitInstanceID traitInstanceID);
ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion); QByteArray packGrabTraitInstance(AvatarTraits::TraitInstanceID traitInstanceID);
qint64 packGrabTraitInstance(AvatarTraits::TraitType traitType,
AvatarTraits::TraitInstanceID traitInstanceID, void unpackSkeletonModelURL(const QByteArray& data);
ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion);
// isReplicated will be true on downstream Avatar Mixers and their clients, but false on the upstream "master" // isReplicated will be true on downstream Avatar Mixers and their clients, but false on the upstream "master"
// Audio Mixer that the replicated avatar is connected to. // Audio Mixer that the replicated avatar is connected to.

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

View file

@ -106,9 +106,10 @@ int ClientTraitsHandler::sendChangedTraitsToMixer() {
auto traitType = static_cast<AvatarTraits::TraitType>(std::distance(traitStatusesCopy.simpleCBegin(), simpleIt)); auto traitType = static_cast<AvatarTraits::TraitType>(std::distance(traitStatusesCopy.simpleCBegin(), simpleIt));
if (initialSend || *simpleIt == Updated) { if (initialSend || *simpleIt == Updated) {
if (traitType == AvatarTraits::SkeletonModelURL) { bytesWritten += AvatarTraits::packTrait(traitType, *traitsPacketList, *_owningAvatar);
bytesWritten += _owningAvatar->packTrait(traitType, *traitsPacketList);
if (traitType == AvatarTraits::SkeletonModelURL) {
// keep track of our skeleton version in case we get an override back // keep track of our skeleton version in case we get an override back
_currentSkeletonVersion = _currentTraitVersion; _currentSkeletonVersion = _currentTraitVersion;
} }
@ -124,7 +125,9 @@ int ClientTraitsHandler::sendChangedTraitsToMixer() {
|| instanceIDValuePair.value == Updated) { || instanceIDValuePair.value == Updated) {
// this is a changed trait we need to send or we haven't send out trait information yet // 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 // 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) { } else if (!initialSend && instanceIDValuePair.value == Deleted) {
// pack delete for this trait instance // pack delete for this trait instance
bytesWritten += AvatarTraits::packInstancedTraitDelete(instancedIt->traitType, instanceIDValuePair.id, 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 // 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 // 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; 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 // 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 // avoid unnecessarily sending the overriden skeleton model URL back to the mixer

View file

@ -343,6 +343,14 @@ glm::mat4 ScriptAvatarData::getControllerRightHandMatrix() const {
// END // END
// //
bool ScriptAvatarData::getHasPriority() const {
if (AvatarSharedPointer sharedAvatarData = _avatarData.lock()) {
return sharedAvatarData->getHasPriority();
} else {
return false;
}
}
glm::quat ScriptAvatarData::getAbsoluteJointRotationInObjectFrame(int index) const { glm::quat ScriptAvatarData::getAbsoluteJointRotationInObjectFrame(int index) const {
if (AvatarSharedPointer sharedAvatarData = _avatarData.lock()) { if (AvatarSharedPointer sharedAvatarData = _avatarData.lock()) {
return sharedAvatarData->getAbsoluteJointRotationInObjectFrame(index); return sharedAvatarData->getAbsoluteJointRotationInObjectFrame(index);

View file

@ -68,6 +68,8 @@ class ScriptAvatarData : public QObject {
Q_PROPERTY(glm::mat4 controllerLeftHandMatrix READ getControllerLeftHandMatrix) Q_PROPERTY(glm::mat4 controllerLeftHandMatrix READ getControllerLeftHandMatrix)
Q_PROPERTY(glm::mat4 controllerRightHandMatrix READ getControllerRightHandMatrix) Q_PROPERTY(glm::mat4 controllerRightHandMatrix READ getControllerRightHandMatrix)
Q_PROPERTY(bool hasPriority READ getHasPriority)
public: public:
ScriptAvatarData(AvatarSharedPointer avatarData); ScriptAvatarData(AvatarSharedPointer avatarData);
@ -133,6 +135,8 @@ public:
glm::mat4 getControllerLeftHandMatrix() const; glm::mat4 getControllerLeftHandMatrix() const;
glm::mat4 getControllerRightHandMatrix() const; glm::mat4 getControllerRightHandMatrix() const;
bool getHasPriority() const;
signals: signals:
void displayNameChanged(); void displayNameChanged();
void sessionDisplayNameChanged(); void sessionDisplayNameChanged();

View file

@ -265,7 +265,7 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) {
if (_procedural.isReady()) { if (_procedural.isReady()) {
outColor = _procedural.getColor(outColor); outColor = _procedural.getColor(outColor);
outColor.a *= _procedural.isFading() ? Interpolate::calculateFadeRatio(_procedural.getFadeStartTime()) : 1.0f; outColor.a *= _procedural.isFading() ? Interpolate::calculateFadeRatio(_procedural.getFadeStartTime()) : 1.0f;
_procedural.prepare(batch, _position, _dimensions, _orientation, ProceduralProgramKey(outColor.a < 1.0f)); _procedural.prepare(batch, _position, _dimensions, _orientation, _created, ProceduralProgramKey(outColor.a < 1.0f));
proceduralRender = true; proceduralRender = true;
} }
}); });

View file

@ -33,7 +33,7 @@ using namespace render::entities;
ZoneEntityRenderer::ZoneEntityRenderer(const EntityItemPointer& entity) ZoneEntityRenderer::ZoneEntityRenderer(const EntityItemPointer& entity)
: Parent(entity) { : Parent(entity) {
_background->setSkybox(std::make_shared<ProceduralSkybox>()); _background->setSkybox(std::make_shared<ProceduralSkybox>(entity->getCreated()));
} }
void ZoneEntityRenderer::onRemoveFromSceneTyped(const TypedEntityPointer& entity) { void ZoneEntityRenderer::onRemoveFromSceneTyped(const TypedEntityPointer& entity) {

View file

@ -789,8 +789,10 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
auto lastEdited = lastEditedFromBufferAdjusted; auto lastEdited = lastEditedFromBufferAdjusted;
bool otherOverwrites = overwriteLocalData && !weOwnSimulation; bool otherOverwrites = overwriteLocalData && !weOwnSimulation;
auto shouldUpdate = [this, lastEdited, otherOverwrites, filterRejection](quint64 updatedTimestamp, bool valueChanged) { // calculate hasGrab once outside the lambda rather than calling it every time inside
if (stillHasGrabActions()) { bool hasGrab = stillHasGrabAction();
auto shouldUpdate = [this, lastEdited, otherOverwrites, filterRejection, hasGrab](quint64 updatedTimestamp, bool valueChanged) {
if (hasGrab) {
return false; return false;
} }
bool simulationChanged = lastEdited > updatedTimestamp; bool simulationChanged = lastEdited > updatedTimestamp;
@ -957,12 +959,18 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
// by doing this parsing here... but it's not likely going to fully recover the content. // by doing this parsing here... but it's not likely going to fully recover the content.
// //
if (overwriteLocalData && (getDirtyFlags() & (Simulation::DIRTY_TRANSFORM | Simulation::DIRTY_VELOCITIES))) { if (overwriteLocalData &&
!hasGrab &&
(getDirtyFlags() & (Simulation::DIRTY_TRANSFORM | Simulation::DIRTY_VELOCITIES))) {
// NOTE: This code is attempting to "repair" the old data we just got from the server to make it more // NOTE: This code is attempting to "repair" the old data we just got from the server to make it more
// closely match where the entities should be if they'd stepped forward in time to "now". The server // closely match where the entities should be if they'd stepped forward in time to "now". The server
// is sending us data with a known "last simulated" time. That time is likely in the past, and therefore // is sending us data with a known "last simulated" time. That time is likely in the past, and therefore
// this "new" data is actually slightly out of date. We calculate the time we need to skip forward and // this "new" data is actually slightly out of date. We calculate the time we need to skip forward and
// use our simulation helper routine to get a best estimate of where the entity should be. // use our simulation helper routine to get a best estimate of where the entity should be.
//
// NOTE: We don't want to do this in the hasGrab case because grabs "know best"
// (e.g. grabs will prevent drift between distributed physics simulations).
//
float skipTimeForward = (float)(now - lastSimulatedFromBufferAdjusted) / (float)(USECS_PER_SECOND); float skipTimeForward = (float)(now - lastSimulatedFromBufferAdjusted) / (float)(USECS_PER_SECOND);
// we want to extrapolate the motion forward to compensate for packet travel time, but // we want to extrapolate the motion forward to compensate for packet travel time, but
@ -1426,7 +1434,7 @@ void EntityItem::getTransformAndVelocityProperties(EntityItemProperties& propert
void EntityItem::upgradeScriptSimulationPriority(uint8_t priority) { void EntityItem::upgradeScriptSimulationPriority(uint8_t priority) {
uint8_t newPriority = glm::max(priority, _scriptSimulationPriority); uint8_t newPriority = glm::max(priority, _scriptSimulationPriority);
if (newPriority < SCRIPT_GRAB_SIMULATION_PRIORITY && stillHasGrabActions()) { if (newPriority < SCRIPT_GRAB_SIMULATION_PRIORITY && stillHasMyGrabAction()) {
newPriority = SCRIPT_GRAB_SIMULATION_PRIORITY; newPriority = SCRIPT_GRAB_SIMULATION_PRIORITY;
} }
if (newPriority != _scriptSimulationPriority) { if (newPriority != _scriptSimulationPriority) {
@ -1439,7 +1447,7 @@ void EntityItem::upgradeScriptSimulationPriority(uint8_t priority) {
void EntityItem::clearScriptSimulationPriority() { void EntityItem::clearScriptSimulationPriority() {
// DO NOT markDirtyFlags(Simulation::DIRTY_SIMULATION_OWNERSHIP_PRIORITY) here, because this // DO NOT markDirtyFlags(Simulation::DIRTY_SIMULATION_OWNERSHIP_PRIORITY) here, because this
// is only ever called from the code that actually handles the dirty flags, and it knows best. // is only ever called from the code that actually handles the dirty flags, and it knows best.
_scriptSimulationPriority = stillHasGrabActions() ? SCRIPT_GRAB_SIMULATION_PRIORITY : 0; _scriptSimulationPriority = stillHasMyGrabAction() ? SCRIPT_GRAB_SIMULATION_PRIORITY : 0;
} }
void EntityItem::setPendingOwnershipPriority(uint8_t priority) { void EntityItem::setPendingOwnershipPriority(uint8_t priority) {
@ -2186,7 +2194,7 @@ void EntityItem::enableNoBootstrap() {
} }
void EntityItem::disableNoBootstrap() { void EntityItem::disableNoBootstrap() {
if (!stillHasGrabActions()) { if (!stillHasMyGrabAction()) {
_flags &= ~Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING; _flags &= ~Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING;
_flags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar _flags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar
@ -2272,7 +2280,13 @@ bool EntityItem::removeAction(EntitySimulationPointer simulation, const QUuid& a
return success; return success;
} }
bool EntityItem::stillHasGrabActions() const { bool EntityItem::stillHasGrabAction() const {
return !_grabActions.empty();
}
// retutrns 'true' if there exists an action that returns 'true' for EntityActionInterface::isMine()
// (e.g. the action belongs to the MyAvatar instance)
bool EntityItem::stillHasMyGrabAction() const {
QList<EntityDynamicPointer> holdActions = getActionsOfType(DYNAMIC_TYPE_HOLD); QList<EntityDynamicPointer> holdActions = getActionsOfType(DYNAMIC_TYPE_HOLD);
QList<EntityDynamicPointer>::const_iterator i = holdActions.begin(); QList<EntityDynamicPointer>::const_iterator i = holdActions.begin();
while (i != holdActions.end()) { while (i != holdActions.end()) {
@ -2700,20 +2714,6 @@ void EntityItem::setLastEdited(quint64 lastEdited) {
}); });
} }
quint64 EntityItem::getLastBroadcast() const {
quint64 result;
withReadLock([&] {
result = _lastBroadcast;
});
return result;
}
void EntityItem::setLastBroadcast(quint64 lastBroadcast) {
withWriteLock([&] {
_lastBroadcast = lastBroadcast;
});
}
void EntityItem::markAsChangedOnServer() { void EntityItem::markAsChangedOnServer() {
withWriteLock([&] { withWriteLock([&] {
_changedOnServer = usecTimestampNow(); _changedOnServer = usecTimestampNow();
@ -3479,6 +3479,9 @@ void EntityItem::addGrab(GrabPointer grab) {
simulation->addDynamic(action); simulation->addDynamic(action);
markDirtyFlags(Simulation::DIRTY_MOTION_TYPE); markDirtyFlags(Simulation::DIRTY_MOTION_TYPE);
simulation->changeEntity(getThisPointer()); simulation->changeEntity(getThisPointer());
// don't forget to set isMine() for locally-created grabs
action->setIsMine(grab->getOwnerID() == Physics::getSessionUUID());
} }
} }

View file

@ -124,8 +124,8 @@ public:
{ return (float)(usecTimestampNow() - getLastEdited()) / (float)USECS_PER_SECOND; } { return (float)(usecTimestampNow() - getLastEdited()) / (float)USECS_PER_SECOND; }
/// Last time we sent out an edit packet for this entity /// Last time we sent out an edit packet for this entity
quint64 getLastBroadcast() const; quint64 getLastBroadcast() const { return _lastBroadcast; }
void setLastBroadcast(quint64 lastBroadcast); void setLastBroadcast(quint64 lastBroadcast) { _lastBroadcast = lastBroadcast; }
void markAsChangedOnServer(); void markAsChangedOnServer();
quint64 getLastChangedOnServer() const; quint64 getLastChangedOnServer() const;
@ -562,6 +562,8 @@ public:
static void setPrimaryViewFrustumPositionOperator(std::function<glm::vec3()> getPrimaryViewFrustumPositionOperator) { _getPrimaryViewFrustumPositionOperator = getPrimaryViewFrustumPositionOperator; } static void setPrimaryViewFrustumPositionOperator(std::function<glm::vec3()> getPrimaryViewFrustumPositionOperator) { _getPrimaryViewFrustumPositionOperator = getPrimaryViewFrustumPositionOperator; }
static glm::vec3 getPrimaryViewFrustumPosition() { return _getPrimaryViewFrustumPositionOperator(); } static glm::vec3 getPrimaryViewFrustumPosition() { return _getPrimaryViewFrustumPositionOperator(); }
bool stillHasMyGrabAction() const;
signals: signals:
void requestRenderUpdate(); void requestRenderUpdate();
void spaceUpdate(std::pair<int32_t, glm::vec4> data); void spaceUpdate(std::pair<int32_t, glm::vec4> data);
@ -574,7 +576,7 @@ protected:
void setSimulated(bool simulated) { _simulated = simulated; } void setSimulated(bool simulated) { _simulated = simulated; }
const QByteArray getDynamicDataInternal() const; const QByteArray getDynamicDataInternal() const;
bool stillHasGrabActions() const; bool stillHasGrabAction() const;
void setDynamicDataInternal(QByteArray dynamicData); void setDynamicDataInternal(QByteArray dynamicData);
virtual void dimensionsChanged() override; virtual void dimensionsChanged() override;

View file

@ -1101,13 +1101,13 @@ void EntityScriptingInterface::handleEntityScriptCallMethodPacket(QSharedPointer
void EntityScriptingInterface::onAddingEntity(EntityItem* entity) { void EntityScriptingInterface::onAddingEntity(EntityItem* entity) {
if (entity->isWearable()) { if (entity->isWearable()) {
emit addingWearable(entity->getEntityItemID()); QMetaObject::invokeMethod(this, "addingWearable", Q_ARG(QUuid, entity->getEntityItemID()));
} }
} }
void EntityScriptingInterface::onDeletingEntity(EntityItem* entity) { void EntityScriptingInterface::onDeletingEntity(EntityItem* entity) {
if (entity->isWearable()) { if (entity->isWearable()) {
emit deletingWearable(entity->getEntityItemID()); QMetaObject::invokeMethod(this, "deletingWearable", Q_ARG(QUuid, entity->getEntityItemID()));
} }
} }

View file

@ -1164,8 +1164,14 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const
counter++; counter++;
} }
} }
_connectionParentMap.insert(getID(connection.properties, 1), getID(connection.properties, 2)); if (_connectionParentMap.value(getID(connection.properties, 1)) == "0") {
_connectionChildMap.insert(getID(connection.properties, 2), getID(connection.properties, 1)); // don't assign the new parent
qCDebug(modelformat) << "root node " << getID(connection.properties, 1) << " has discarded parent " << getID(connection.properties, 2);
_connectionChildMap.insert(getID(connection.properties, 2), getID(connection.properties, 1));
} else {
_connectionParentMap.insert(getID(connection.properties, 1), getID(connection.properties, 2));
_connectionChildMap.insert(getID(connection.properties, 2), getID(connection.properties, 1));
}
} }
} }
} }

View file

@ -536,7 +536,7 @@ void AccountManager::requestAccessToken(const QString& login, const QString& pas
QByteArray postData; QByteArray postData;
postData.append("grant_type=password&"); postData.append("grant_type=password&");
postData.append("username=" + login + "&"); postData.append("username=" + QUrl::toPercentEncoding(login) + "&");
postData.append("password=" + QUrl::toPercentEncoding(password) + "&"); postData.append("password=" + QUrl::toPercentEncoding(password) + "&");
postData.append("scope=" + ACCOUNT_MANAGER_REQUESTED_SCOPE); postData.append("scope=" + ACCOUNT_MANAGER_REQUESTED_SCOPE);

View file

@ -677,6 +677,9 @@ SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t
// If there is a new node with the same socket, this is a reconnection, kill the old node // If there is a new node with the same socket, this is a reconnection, kill the old node
removeOldNode(findNodeWithAddr(publicSocket)); removeOldNode(findNodeWithAddr(publicSocket));
removeOldNode(findNodeWithAddr(localSocket)); removeOldNode(findNodeWithAddr(localSocket));
// If there is an old Connection to the new node's address kill it
_nodeSocket.cleanupConnection(publicSocket);
_nodeSocket.cleanupConnection(localSocket);
auto it = _connectionIDs.find(uuid); auto it = _connectionIDs.find(uuid);
if (it == _connectionIDs.end()) { if (it == _connectionIDs.end()) {

View file

@ -82,11 +82,11 @@ void OctreePersistThread::start() {
} }
if (data.readOctreeDataInfoFromData(_cachedJSONData)) { if (data.readOctreeDataInfoFromData(_cachedJSONData)) {
qCDebug(octree) << "Current octree data: ID(" << data.id << ") DataVersion(" << data.version << ")"; qCDebug(octree) << "Current octree data: ID(" << data.id << ") DataVersion(" << data.dataVersion << ")";
packet->writePrimitive(true); packet->writePrimitive(true);
auto id = data.id.toRfc4122(); auto id = data.id.toRfc4122();
packet->write(id); packet->write(id);
packet->writePrimitive(data.version); packet->writePrimitive(data.dataVersion);
} else { } else {
_cachedJSONData.clear(); _cachedJSONData.clear();
qCWarning(octree) << "No octree data found"; qCWarning(octree) << "No octree data found";
@ -144,8 +144,8 @@ void OctreePersistThread::handleOctreeDataFileReply(QSharedPointer<ReceivedMessa
quint64 loadStarted = usecTimestampNow(); quint64 loadStarted = usecTimestampNow();
if (hasValidOctreeData) { if (hasValidOctreeData) {
qDebug() << "Setting entity version info to: " << data.id << data.version; qDebug() << "Setting entity version info to: " << data.id << data.dataVersion;
_tree->setOctreeVersionInfo(data.id, data.version); _tree->setOctreeVersionInfo(data.id, data.dataVersion);
} }
bool persistentFileRead; bool persistentFileRead;

View file

@ -225,13 +225,15 @@ void Procedural::prepare(gpu::Batch& batch,
const glm::vec3& position, const glm::vec3& position,
const glm::vec3& size, const glm::vec3& size,
const glm::quat& orientation, const glm::quat& orientation,
const uint64_t& created,
const ProceduralProgramKey key) { const ProceduralProgramKey key) {
std::lock_guard<std::mutex> lock(_mutex); std::lock_guard<std::mutex> lock(_mutex);
_entityDimensions = size; _entityDimensions = size;
_entityPosition = position; _entityPosition = position;
_entityOrientation = glm::mat3_cast(orientation); _entityOrientation = glm::mat3_cast(orientation);
_entityCreated = created;
if (!_shaderPath.isEmpty()) { if (!_shaderPath.isEmpty()) {
auto lastModified = (quint64)QFileInfo(_shaderPath).lastModified().toMSecsSinceEpoch(); auto lastModified = (uint64_t)QFileInfo(_shaderPath).lastModified().toMSecsSinceEpoch();
if (lastModified > _shaderModified) { if (lastModified > _shaderModified) {
QFile file(_shaderPath); QFile file(_shaderPath);
file.open(QIODevice::ReadOnly); file.open(QIODevice::ReadOnly);
@ -278,7 +280,10 @@ void Procedural::prepare(gpu::Batch& batch,
_proceduralPipelines[key] = gpu::Pipeline::create(program, key.isTransparent() ? _transparentState : _opaqueState); _proceduralPipelines[key] = gpu::Pipeline::create(program, key.isTransparent() ? _transparentState : _opaqueState);
_start = usecTimestampNow(); _lastCompile = usecTimestampNow();
if (_firstCompile == 0) {
_firstCompile = _lastCompile;
}
_frameCount = 0; _frameCount = 0;
recompiledShader = true; recompiledShader = true;
} }
@ -371,7 +376,11 @@ void Procedural::setupUniforms() {
_uniforms.push_back([=](gpu::Batch& batch) { _uniforms.push_back([=](gpu::Batch& batch) {
_standardInputs.position = vec4(_entityPosition, 1.0f); _standardInputs.position = vec4(_entityPosition, 1.0f);
// Minimize floating point error by doing an integer division to milliseconds, before the floating point division to seconds // Minimize floating point error by doing an integer division to milliseconds, before the floating point division to seconds
_standardInputs.time = (float)((usecTimestampNow() - _start) / USECS_PER_MSEC) / MSECS_PER_SECOND; auto now = usecTimestampNow();
_standardInputs.timeSinceLastCompile = (float)((now - _lastCompile) / USECS_PER_MSEC) / MSECS_PER_SECOND;
_standardInputs.timeSinceFirstCompile = (float)((now - _firstCompile) / USECS_PER_MSEC) / MSECS_PER_SECOND;
_standardInputs.timeSinceEntityCreation = (float)((now - _entityCreated) / USECS_PER_MSEC) / MSECS_PER_SECOND;
// Date // Date
{ {

View file

@ -82,10 +82,11 @@ public:
bool isReady() const; bool isReady() const;
bool isEnabled() const { return _enabled; } bool isEnabled() const { return _enabled; }
void prepare(gpu::Batch& batch, const glm::vec3& position, const glm::vec3& size, const glm::quat& orientation, const ProceduralProgramKey key = ProceduralProgramKey()); void prepare(gpu::Batch& batch, const glm::vec3& position, const glm::vec3& size, const glm::quat& orientation,
const uint64_t& created, const ProceduralProgramKey key = ProceduralProgramKey());
glm::vec4 getColor(const glm::vec4& entityColor) const; glm::vec4 getColor(const glm::vec4& entityColor) const;
quint64 getFadeStartTime() const { return _fadeStartTime; } uint64_t getFadeStartTime() const { return _fadeStartTime; }
bool isFading() const { return _doesFade && _isFading; } bool isFading() const { return _doesFade && _isFading; }
void setIsFading(bool isFading) { _isFading = isFading; } void setIsFading(bool isFading) { _isFading = isFading; }
void setDoesFade(bool doesFade) { _doesFade = doesFade; } void setDoesFade(bool doesFade) { _doesFade = doesFade; }
@ -106,9 +107,10 @@ protected:
vec4 date; vec4 date;
vec4 position; vec4 position;
vec4 scale; vec4 scale;
float time; float timeSinceLastCompile;
float timeSinceFirstCompile;
float timeSinceEntityCreation;
int frameCount; int frameCount;
vec2 _spare1;
vec4 resolution[4]; vec4 resolution[4];
mat4 orientation; mat4 orientation;
}; };
@ -116,9 +118,10 @@ protected:
static_assert(0 == offsetof(StandardInputs, date), "ProceduralOffsets"); static_assert(0 == offsetof(StandardInputs, date), "ProceduralOffsets");
static_assert(16 == offsetof(StandardInputs, position), "ProceduralOffsets"); static_assert(16 == offsetof(StandardInputs, position), "ProceduralOffsets");
static_assert(32 == offsetof(StandardInputs, scale), "ProceduralOffsets"); static_assert(32 == offsetof(StandardInputs, scale), "ProceduralOffsets");
static_assert(48 == offsetof(StandardInputs, time), "ProceduralOffsets"); static_assert(48 == offsetof(StandardInputs, timeSinceLastCompile), "ProceduralOffsets");
static_assert(52 == offsetof(StandardInputs, frameCount), "ProceduralOffsets"); static_assert(52 == offsetof(StandardInputs, timeSinceFirstCompile), "ProceduralOffsets");
static_assert(56 == offsetof(StandardInputs, _spare1), "ProceduralOffsets"); static_assert(56 == offsetof(StandardInputs, timeSinceEntityCreation), "ProceduralOffsets");
static_assert(60 == offsetof(StandardInputs, frameCount), "ProceduralOffsets");
static_assert(64 == offsetof(StandardInputs, resolution), "ProceduralOffsets"); static_assert(64 == offsetof(StandardInputs, resolution), "ProceduralOffsets");
static_assert(128 == offsetof(StandardInputs, orientation), "ProceduralOffsets"); static_assert(128 == offsetof(StandardInputs, orientation), "ProceduralOffsets");
@ -126,13 +129,14 @@ protected:
ProceduralData _data; ProceduralData _data;
bool _enabled { false }; bool _enabled { false };
uint64_t _start { 0 }; uint64_t _lastCompile { 0 };
uint64_t _firstCompile { 0 };
int32_t _frameCount { 0 }; int32_t _frameCount { 0 };
// Rendering object descriptions, from userData // Rendering object descriptions, from userData
QString _shaderSource; QString _shaderSource;
QString _shaderPath; QString _shaderPath;
quint64 _shaderModified { 0 }; uint64_t _shaderModified { 0 };
NetworkShaderPointer _networkShader; NetworkShaderPointer _networkShader;
bool _shaderDirty { true }; bool _shaderDirty { true };
bool _uniformsDirty { true }; bool _uniformsDirty { true };
@ -152,11 +156,12 @@ protected:
glm::vec3 _entityDimensions; glm::vec3 _entityDimensions;
glm::vec3 _entityPosition; glm::vec3 _entityPosition;
glm::mat3 _entityOrientation; glm::mat3 _entityOrientation;
uint64_t _entityCreated;
private: private:
void setupUniforms(); void setupUniforms();
mutable quint64 _fadeStartTime { 0 }; mutable uint64_t _fadeStartTime { 0 };
mutable bool _hasStartedFade { false }; mutable bool _hasStartedFade { false };
mutable bool _isFading { false }; mutable bool _isFading { false };
bool _doesFade { true }; bool _doesFade { true };

View file

@ -36,9 +36,11 @@ LAYOUT_STD140(binding=0) uniform standardInputsBuffer {
// Offset 48 // Offset 48
float globalTime; float globalTime;
// Offset 52 // Offset 52
int frameCount; float localCreatedTime;
// Offset 56 // Offset 56
vec2 _spare1; float entityTime;
// Offset 60
int frameCount;
// Offset 64, acts as vec4[4] for alignment purposes // Offset 64, acts as vec4[4] for alignment purposes
vec3 channelResolution[4]; vec3 channelResolution[4];
// Offset 128, acts as vec4[3] for alignment purposes // Offset 128, acts as vec4[3] for alignment purposes
@ -52,6 +54,8 @@ LAYOUT_STD140(binding=0) uniform standardInputsBuffer {
#define iWorldPosition standardInputs.worldPosition #define iWorldPosition standardInputs.worldPosition
#define iWorldScale standardInputs.worldScale #define iWorldScale standardInputs.worldScale
#define iGlobalTime standardInputs.globalTime #define iGlobalTime standardInputs.globalTime
#define iLocalCreatedTime standardInputs.localCreatedTime
#define iEntityTime standardInputs.entityTime
#define iFrameCount standardInputs.frameCount #define iFrameCount standardInputs.frameCount
#define iChannelResolution standardInputs.channelResolution #define iChannelResolution standardInputs.channelResolution
#define iWorldOrientation standardInputs.worldOrientation #define iWorldOrientation standardInputs.worldOrientation

View file

@ -17,7 +17,7 @@
#include <ViewFrustum.h> #include <ViewFrustum.h>
#include <shaders/Shaders.h> #include <shaders/Shaders.h>
ProceduralSkybox::ProceduralSkybox() : graphics::Skybox() { ProceduralSkybox::ProceduralSkybox(uint64_t created) : graphics::Skybox(), _created(created) {
_procedural._vertexSource = gpu::Shader::createVertex(shader::graphics::vertex::skybox)->getSource(); _procedural._vertexSource = gpu::Shader::createVertex(shader::graphics::vertex::skybox)->getSource();
_procedural._opaqueFragmentSource = shader::Source::get(shader::procedural::fragment::proceduralSkybox); _procedural._opaqueFragmentSource = shader::Source::get(shader::procedural::fragment::proceduralSkybox);
// Adjust the pipeline state for background using the stencil test // Adjust the pipeline state for background using the stencil test
@ -59,7 +59,7 @@ void ProceduralSkybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum,
batch.setModelTransform(Transform()); // only for Mac batch.setModelTransform(Transform()); // only for Mac
auto& procedural = skybox._procedural; auto& procedural = skybox._procedural;
procedural.prepare(batch, glm::vec3(0), glm::vec3(1), glm::quat()); procedural.prepare(batch, glm::vec3(0), glm::vec3(1), glm::quat(), skybox.getCreated());
skybox.prepare(batch); skybox.prepare(batch);
batch.draw(gpu::TRIANGLE_STRIP, 4); batch.draw(gpu::TRIANGLE_STRIP, 4);
} }

View file

@ -19,7 +19,7 @@
class ProceduralSkybox: public graphics::Skybox { class ProceduralSkybox: public graphics::Skybox {
public: public:
ProceduralSkybox(); ProceduralSkybox(uint64_t created = 0);
void parse(const QString& userData) { _procedural.setProceduralData(ProceduralData::parse(userData)); } void parse(const QString& userData) { _procedural.setProceduralData(ProceduralData::parse(userData)); }
@ -29,8 +29,11 @@ public:
void render(gpu::Batch& batch, const ViewFrustum& frustum) const override; void render(gpu::Batch& batch, const ViewFrustum& frustum) const override;
static void render(gpu::Batch& batch, const ViewFrustum& frustum, const ProceduralSkybox& skybox); static void render(gpu::Batch& batch, const ViewFrustum& frustum, const ProceduralSkybox& skybox);
uint64_t getCreated() const { return _created; }
protected: protected:
mutable Procedural _procedural; mutable Procedural _procedural;
uint64_t _created;
}; };
typedef std::shared_ptr< ProceduralSkybox > ProceduralSkyboxPointer; typedef std::shared_ptr< ProceduralSkybox > ProceduralSkyboxPointer;

View file

@ -353,10 +353,11 @@ function AppUi(properties) {
// Close if necessary, clean up any remaining handlers, and remove the button. // Close if necessary, clean up any remaining handlers, and remove the button.
GlobalServices.myUsernameChanged.disconnect(restartNotificationPoll); GlobalServices.myUsernameChanged.disconnect(restartNotificationPoll);
GlobalServices.findableByChanged.disconnect(restartNotificationPoll); GlobalServices.findableByChanged.disconnect(restartNotificationPoll);
that.tablet.screenChanged.disconnect(that.onScreenChanged);
if (that.isOpen) { if (that.isOpen) {
that.close(); that.close();
that.onScreenChanged("", "");
} }
that.tablet.screenChanged.disconnect(that.onScreenChanged);
if (that.button) { if (that.button) {
if (that.onClicked) { if (that.onClicked) {
that.button.clicked.disconnect(that.onClicked); that.button.clicked.disconnect(that.onClicked);

View file

@ -1,6 +1,8 @@
"use strict"; "use strict";
/*jslint vars:true, plusplus:true, forin:true*/ /*jslint vars:true, plusplus:true, forin:true*/
/*global Tablet, Settings, Script, AvatarList, Users, Entities, MyAvatar, Camera, Overlays, Vec3, Quat, HMD, Controller, Account, UserActivityLogger, Messages, Window, XMLHttpRequest, print, location, getControllerWorldLocation*/ /*global Tablet, Script, Entities, MyAvatar, Camera, Quat, HMD, Account, UserActivityLogger, Messages, print,
AvatarBookmarks, ContextOverlay, AddressManager
*/
/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ /* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
// //
// avatarapp.js // avatarapp.js
@ -14,7 +16,6 @@
(function() { // BEGIN LOCAL_SCOPE (function() { // BEGIN LOCAL_SCOPE
var request = Script.require('request').request;
var AVATARAPP_QML_SOURCE = "hifi/AvatarApp.qml"; var AVATARAPP_QML_SOURCE = "hifi/AvatarApp.qml";
Script.include("/~/system/libraries/controllers.js"); Script.include("/~/system/libraries/controllers.js");
@ -22,7 +23,6 @@ Script.include("/~/system/libraries/controllers.js");
var ENTRY_AVATAR_URL = "avatarUrl"; var ENTRY_AVATAR_URL = "avatarUrl";
var ENTRY_AVATAR_ENTITIES = "avatarEntites"; var ENTRY_AVATAR_ENTITIES = "avatarEntites";
var ENTRY_AVATAR_SCALE = "avatarScale"; var ENTRY_AVATAR_SCALE = "avatarScale";
var ENTRY_VERSION = "version";
function executeLater(callback) { function executeLater(callback) {
Script.setTimeout(callback, 300); Script.setTimeout(callback, 300);
@ -44,7 +44,7 @@ function getMyAvatarWearables() {
} }
var localRotation = entity.properties.localRotation; var localRotation = entity.properties.localRotation;
entity.properties.localRotationAngles = Quat.safeEulerAngles(localRotation) entity.properties.localRotationAngles = Quat.safeEulerAngles(localRotation);
wearablesArray.push(entity); wearablesArray.push(entity);
} }
@ -52,7 +52,7 @@ function getMyAvatarWearables() {
} }
function getMyAvatar() { function getMyAvatar() {
var avatar = {} var avatar = {};
avatar[ENTRY_AVATAR_URL] = MyAvatar.skeletonModelURL; avatar[ENTRY_AVATAR_URL] = MyAvatar.skeletonModelURL;
avatar[ENTRY_AVATAR_SCALE] = MyAvatar.getAvatarScale(); avatar[ENTRY_AVATAR_SCALE] = MyAvatar.getAvatarScale();
avatar[ENTRY_AVATAR_ENTITIES] = getMyAvatarWearables(); avatar[ENTRY_AVATAR_ENTITIES] = getMyAvatarWearables();
@ -68,7 +68,7 @@ function getMyAvatarSettings() {
collisionSoundUrl : MyAvatar.collisionSoundURL, collisionSoundUrl : MyAvatar.collisionSoundURL,
animGraphUrl: MyAvatar.getAnimGraphUrl(), animGraphUrl: MyAvatar.getAnimGraphUrl(),
animGraphOverrideUrl : MyAvatar.getAnimGraphOverrideUrl(), animGraphOverrideUrl : MyAvatar.getAnimGraphOverrideUrl(),
} };
} }
function updateAvatarWearables(avatar, callback, wearablesOverride) { function updateAvatarWearables(avatar, callback, wearablesOverride) {
@ -76,7 +76,8 @@ function updateAvatarWearables(avatar, callback, wearablesOverride) {
var wearables = wearablesOverride ? wearablesOverride : getMyAvatarWearables(); var wearables = wearablesOverride ? wearablesOverride : getMyAvatarWearables();
avatar[ENTRY_AVATAR_ENTITIES] = wearables; avatar[ENTRY_AVATAR_ENTITIES] = wearables;
sendToQml({'method' : 'wearablesUpdated', 'wearables' : wearables}) sendToQml({'method' : 'wearablesUpdated', 'wearables' : wearables});
sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()});
if(callback) if(callback)
callback(); callback();
@ -101,7 +102,7 @@ var adjustWearables = {
this.opened = value; this.opened = value;
} }
} }
} };
var currentAvatarWearablesBackup = null; var currentAvatarWearablesBackup = null;
var currentAvatar = null; var currentAvatar = null;
@ -112,7 +113,7 @@ function onTargetScaleChanged() {
if(currentAvatar.scale !== MyAvatar.getAvatarScale()) { if(currentAvatar.scale !== MyAvatar.getAvatarScale()) {
currentAvatar.scale = MyAvatar.getAvatarScale(); currentAvatar.scale = MyAvatar.getAvatarScale();
if(notifyScaleChanged) { if(notifyScaleChanged) {
sendToQml({'method' : 'scaleChanged', 'value' : currentAvatar.scale}) sendToQml({'method' : 'scaleChanged', 'value' : currentAvatar.scale});
} }
} }
} }
@ -126,7 +127,7 @@ function onSkeletonModelURLChanged() {
function onDominantHandChanged(dominantHand) { function onDominantHandChanged(dominantHand) {
if(currentAvatarSettings.dominantHand !== dominantHand) { if(currentAvatarSettings.dominantHand !== dominantHand) {
currentAvatarSettings.dominantHand = dominantHand; currentAvatarSettings.dominantHand = dominantHand;
sendToQml({'method' : 'settingChanged', 'name' : 'dominantHand', 'value' : dominantHand}) sendToQml({'method' : 'settingChanged', 'name' : 'dominantHand', 'value' : dominantHand});
} }
} }
@ -140,37 +141,37 @@ function onHmdAvatarAlignmentTypeChanged(type) {
function onCollisionsEnabledChanged(enabled) { function onCollisionsEnabledChanged(enabled) {
if(currentAvatarSettings.collisionsEnabled !== enabled) { if(currentAvatarSettings.collisionsEnabled !== enabled) {
currentAvatarSettings.collisionsEnabled = enabled; currentAvatarSettings.collisionsEnabled = enabled;
sendToQml({'method' : 'settingChanged', 'name' : 'collisionsEnabled', 'value' : enabled}) sendToQml({'method' : 'settingChanged', 'name' : 'collisionsEnabled', 'value' : enabled});
} }
} }
function onOtherAvatarsCollisionsEnabledChanged(enabled) { function onOtherAvatarsCollisionsEnabledChanged(enabled) {
if (currentAvatarSettings.otherAvatarsCollisionsEnabled !== enabled) { if (currentAvatarSettings.otherAvatarsCollisionsEnabled !== enabled) {
currentAvatarSettings.otherAvatarsCollisionsEnabled = enabled; currentAvatarSettings.otherAvatarsCollisionsEnabled = enabled;
sendToQml({ 'method': 'settingChanged', 'name': 'otherAvatarsCollisionsEnabled', 'value': enabled }) sendToQml({ 'method': 'settingChanged', 'name': 'otherAvatarsCollisionsEnabled', 'value': enabled });
} }
} }
function onNewCollisionSoundUrl(url) { function onNewCollisionSoundUrl(url) {
if(currentAvatarSettings.collisionSoundUrl !== url) { if(currentAvatarSettings.collisionSoundUrl !== url) {
currentAvatarSettings.collisionSoundUrl = url; currentAvatarSettings.collisionSoundUrl = url;
sendToQml({'method' : 'settingChanged', 'name' : 'collisionSoundUrl', 'value' : url}) sendToQml({'method' : 'settingChanged', 'name' : 'collisionSoundUrl', 'value' : url});
} }
} }
function onAnimGraphUrlChanged(url) { function onAnimGraphUrlChanged(url) {
if (currentAvatarSettings.animGraphUrl !== url) { if (currentAvatarSettings.animGraphUrl !== url) {
currentAvatarSettings.animGraphUrl = url; currentAvatarSettings.animGraphUrl = url;
sendToQml({ 'method': 'settingChanged', 'name': 'animGraphUrl', 'value': currentAvatarSettings.animGraphUrl }) sendToQml({ 'method': 'settingChanged', 'name': 'animGraphUrl', 'value': currentAvatarSettings.animGraphUrl });
if (currentAvatarSettings.animGraphOverrideUrl !== MyAvatar.getAnimGraphOverrideUrl()) { if (currentAvatarSettings.animGraphOverrideUrl !== MyAvatar.getAnimGraphOverrideUrl()) {
currentAvatarSettings.animGraphOverrideUrl = MyAvatar.getAnimGraphOverrideUrl(); currentAvatarSettings.animGraphOverrideUrl = MyAvatar.getAnimGraphOverrideUrl();
sendToQml({ 'method': 'settingChanged', 'name': 'animGraphOverrideUrl', 'value': currentAvatarSettings.animGraphOverrideUrl }) sendToQml({ 'method': 'settingChanged', 'name': 'animGraphOverrideUrl',
'value': currentAvatarSettings.animGraphOverrideUrl });
} }
} }
} }
var selectedAvatarEntityGrabbable = false;
var selectedAvatarEntityID = null; var selectedAvatarEntityID = null;
var grabbedAvatarEntityChangeNotifier = null; var grabbedAvatarEntityChangeNotifier = null;
@ -178,6 +179,33 @@ var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/wallet/Wallet.qml";
var MARKETPLACE_URL = Account.metaverseServerURL + "/marketplace"; var MARKETPLACE_URL = Account.metaverseServerURL + "/marketplace";
var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("html/js/marketplacesInject.js"); var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("html/js/marketplacesInject.js");
function getWearablesFrozen() {
var wearablesFrozen = true;
var wearablesArray = getMyAvatarWearables();
wearablesArray.forEach(function(wearable) {
if (isGrabbable(wearable.id)) {
wearablesFrozen = false;
}
});
return wearablesFrozen;
}
function freezeWearables() {
var wearablesArray = getMyAvatarWearables();
wearablesArray.forEach(function(wearable) {
setGrabbable(wearable.id, false);
});
}
function unfreezeWearables() {
var wearablesArray = getMyAvatarWearables();
wearablesArray.forEach(function(wearable) {
setGrabbable(wearable.id, true);
});
}
function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml. function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml.
switch (message.method) { switch (message.method) {
case 'getAvatars': case 'getAvatars':
@ -201,7 +229,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
} }
} }
sendToQml(message) sendToQml(message);
break; break;
case 'selectAvatar': case 'selectAvatar':
Entities.addingWearable.disconnect(onAddingWearable); Entities.addingWearable.disconnect(onAddingWearable);
@ -209,6 +237,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
AvatarBookmarks.loadBookmark(message.name); AvatarBookmarks.loadBookmark(message.name);
Entities.addingWearable.connect(onAddingWearable); Entities.addingWearable.connect(onAddingWearable);
Entities.deletingWearable.connect(onDeletingWearable); Entities.deletingWearable.connect(onDeletingWearable);
sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()});
break; break;
case 'deleteAvatar': case 'deleteAvatar':
AvatarBookmarks.removeBookmark(message.name); AvatarBookmarks.removeBookmark(message.name);
@ -228,11 +257,12 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
message.properties.localRotationAngles = Quat.safeEulerAngles(message.properties.localRotation); message.properties.localRotationAngles = Quat.safeEulerAngles(message.properties.localRotation);
} }
sendToQml({'method' : 'wearableUpdated', 'entityID' : message.entityID, wearableIndex : message.wearableIndex, properties : message.properties, updateUI : false}) sendToQml({'method' : 'wearableUpdated', 'entityID' : message.entityID, wearableIndex : message.wearableIndex, properties : message.properties, updateUI : false});
break; break;
case 'adjustWearablesOpened': case 'adjustWearablesOpened':
currentAvatarWearablesBackup = getMyAvatarWearables(); currentAvatarWearablesBackup = getMyAvatarWearables();
adjustWearables.setOpened(true); adjustWearables.setOpened(true);
unfreezeWearables();
Entities.mousePressOnEntity.connect(onSelectedEntity); Entities.mousePressOnEntity.connect(onSelectedEntity);
Messages.subscribe('Hifi-Object-Manipulation'); Messages.subscribe('Hifi-Object-Manipulation');
@ -305,11 +335,11 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
var currentAvatarURL = MyAvatar.getFullAvatarURLFromPreferences(); var currentAvatarURL = MyAvatar.getFullAvatarURLFromPreferences();
if(currentAvatarURL !== message.avatarURL) { if(currentAvatarURL !== message.avatarURL) {
MyAvatar.useFullAvatarURL(message.avatarURL); MyAvatar.useFullAvatarURL(message.avatarURL);
sendToQml({'method' : 'externalAvatarApplied', 'avatarURL' : message.avatarURL}) sendToQml({'method' : 'externalAvatarApplied', 'avatarURL' : message.avatarURL});
} }
break; break;
case 'navigate': case 'navigate':
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system") var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
if(message.url.indexOf('app://') === 0) { if(message.url.indexOf('app://') === 0) {
if (message.url === 'app://marketplace') { if (message.url === 'app://marketplace') {
tablet.gotoWebScreen(MARKETPLACE_URL, MARKETPLACES_INJECT_SCRIPT_URL); tablet.gotoWebScreen(MARKETPLACE_URL, MARKETPLACES_INJECT_SCRIPT_URL);
@ -345,7 +375,17 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
MyAvatar.collisionSoundURL = message.settings.collisionSoundUrl; MyAvatar.collisionSoundURL = message.settings.collisionSoundUrl;
MyAvatar.setAnimGraphOverrideUrl(message.settings.animGraphOverrideUrl); MyAvatar.setAnimGraphOverrideUrl(message.settings.animGraphOverrideUrl);
settings = getMyAvatarSettings(); currentAvatarSettings = getMyAvatarSettings();
break;
case 'toggleWearablesFrozen':
var wearablesFrozen = getWearablesFrozen();
wearablesFrozen = !wearablesFrozen;
if (wearablesFrozen) {
freezeWearables();
} else {
unfreezeWearables();
}
sendToQml({'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : wearablesFrozen});
break; break;
default: default:
print('Unrecognized message from AvatarApp.qml'); print('Unrecognized message from AvatarApp.qml');
@ -366,9 +406,11 @@ function isGrabbable(entityID) {
} }
function setGrabbable(entityID, grabbable) { function setGrabbable(entityID, grabbable) {
var properties = Entities.getEntityProperties(entityID, ['avatarEntity']); var properties = Entities.getEntityProperties(entityID, ['avatarEntity', 'grab.grabbable']);
if (properties.avatarEntity) { if (properties.avatarEntity && properties.grab.grabbable != grabbable) {
Entities.editEntity(entityID, { grab: { grabbable: grabbable }}); var editProps = { grab: { grabbable: grabbable }};
Entities.editEntity(entityID, editProps);
sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()});
} }
} }
@ -378,18 +420,7 @@ function ensureWearableSelected(entityID) {
Script.clearInterval(grabbedAvatarEntityChangeNotifier); Script.clearInterval(grabbedAvatarEntityChangeNotifier);
grabbedAvatarEntityChangeNotifier = null; grabbedAvatarEntityChangeNotifier = null;
} }
if(selectedAvatarEntityID !== null) {
setGrabbable(selectedAvatarEntityID, selectedAvatarEntityGrabbable);
}
selectedAvatarEntityID = entityID; selectedAvatarEntityID = entityID;
selectedAvatarEntityGrabbable = isGrabbable(entityID);
if(selectedAvatarEntityID !== null) {
setGrabbable(selectedAvatarEntityID, true);
}
return true; return true;
} }
@ -398,7 +429,7 @@ function ensureWearableSelected(entityID) {
function isEntityBeingWorn(entityID) { function isEntityBeingWorn(entityID) {
return Entities.getEntityProperties(entityID, 'parentID').parentID === MyAvatar.sessionUUID; return Entities.getEntityProperties(entityID, 'parentID').parentID === MyAvatar.sessionUUID;
}; }
function onSelectedEntity(entityID, pointerEvent) { function onSelectedEntity(entityID, pointerEvent) {
if(selectedAvatarEntityID !== entityID && isEntityBeingWorn(entityID)) if(selectedAvatarEntityID !== entityID && isEntityBeingWorn(entityID))
@ -413,12 +444,14 @@ function onAddingWearable(entityID) {
updateAvatarWearables(currentAvatar, function() { updateAvatarWearables(currentAvatar, function() {
sendToQml({'method' : 'updateAvatarInBookmarks'}); sendToQml({'method' : 'updateAvatarInBookmarks'});
}); });
sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()});
} }
function onDeletingWearable(entityID) { function onDeletingWearable(entityID) {
updateAvatarWearables(currentAvatar, function() { updateAvatarWearables(currentAvatar, function() {
sendToQml({'method' : 'updateAvatarInBookmarks'}); sendToQml({'method' : 'updateAvatarInBookmarks'});
}); });
sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()});
} }
function handleWearableMessages(channel, message, sender) { function handleWearableMessages(channel, message, sender) {
@ -435,30 +468,35 @@ function handleWearableMessages(channel, message, sender) {
} }
var entityID = parsedMessage.grabbedEntity; var entityID = parsedMessage.grabbedEntity;
var updateWearable = function() {
// for some reasons Entities.getEntityProperties returns more than was asked..
var propertyNames = ['localPosition', 'localRotation', 'dimensions', 'naturalDimensions'];
var entityProperties = Entities.getEntityProperties(selectedAvatarEntityID, propertyNames);
var properties = {};
propertyNames.forEach(function(propertyName) {
properties[propertyName] = entityProperties[propertyName];
});
properties.localRotationAngles = Quat.safeEulerAngles(properties.localRotation);
sendToQml({'method' : 'wearableUpdated', 'entityID' : selectedAvatarEntityID,
'wearableIndex' : -1, 'properties' : properties, updateUI : true});
};
if(parsedMessage.action === 'grab') { if(parsedMessage.action === 'grab') {
if(selectedAvatarEntityID !== entityID) { if(selectedAvatarEntityID !== entityID) {
ensureWearableSelected(entityID); ensureWearableSelected(entityID);
sendToQml({'method' : 'selectAvatarEntity', 'entityID' : selectedAvatarEntityID}); sendToQml({'method' : 'selectAvatarEntity', 'entityID' : selectedAvatarEntityID});
} }
grabbedAvatarEntityChangeNotifier = Script.setInterval(function() { grabbedAvatarEntityChangeNotifier = Script.setInterval(updateWearable, 1000);
// for some reasons Entities.getEntityProperties returns more than was asked..
var propertyNames = ['localPosition', 'localRotation', 'dimensions', 'naturalDimensions'];
var entityProperties = Entities.getEntityProperties(selectedAvatarEntityID, propertyNames);
var properties = {}
propertyNames.forEach(function(propertyName) {
properties[propertyName] = entityProperties[propertyName];
})
properties.localRotationAngles = Quat.safeEulerAngles(properties.localRotation);
sendToQml({'method' : 'wearableUpdated', 'entityID' : selectedAvatarEntityID, 'wearableIndex' : -1, 'properties' : properties, updateUI : true})
}, 1000);
} else if(parsedMessage.action === 'release') { } else if(parsedMessage.action === 'release') {
if(grabbedAvatarEntityChangeNotifier !== null) { if(grabbedAvatarEntityChangeNotifier !== null) {
Script.clearInterval(grabbedAvatarEntityChangeNotifier); Script.clearInterval(grabbedAvatarEntityChangeNotifier);
grabbedAvatarEntityChangeNotifier = null; grabbedAvatarEntityChangeNotifier = null;
updateWearable();
} }
} }
} }
@ -481,8 +519,8 @@ function onBookmarkDeleted(bookmarkName) {
function onBookmarkAdded(bookmarkName) { function onBookmarkAdded(bookmarkName) {
var bookmark = AvatarBookmarks.getBookmark(bookmarkName); var bookmark = AvatarBookmarks.getBookmark(bookmarkName);
bookmark.avatarEntites.forEach(function(avatarEntity) { bookmark.avatarEntites.forEach(function(avatarEntity) {
avatarEntity.properties.localRotationAngles = Quat.safeEulerAngles(avatarEntity.properties.localRotation) avatarEntity.properties.localRotationAngles = Quat.safeEulerAngles(avatarEntity.properties.localRotation);
}) });
sendToQml({ 'method': 'bookmarkAdded', 'bookmarkName': bookmarkName, 'bookmark': bookmark }); sendToQml({ 'method': 'bookmarkAdded', 'bookmarkName': bookmarkName, 'bookmark': bookmark });
} }
@ -601,14 +639,8 @@ function onTabletScreenChanged(type, url) {
onAvatarAppScreen = onAvatarAppScreenNow; onAvatarAppScreen = onAvatarAppScreenNow;
if(onAvatarAppScreenNow) { if(onAvatarAppScreenNow) {
var message = { sendToQml({ 'method' : 'initialize', 'data' : { jointNames : MyAvatar.getJointNames() }});
'method' : 'initialize', sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()});
'data' : {
'jointNames' : MyAvatar.getJointNames()
}
};
sendToQml(message)
} }
} }

View file

@ -341,8 +341,6 @@ entityIsGrabbable = function (eigProps) {
var grabbable = getGrabbableData(eigProps).grabbable; var grabbable = getGrabbableData(eigProps).grabbable;
if (!grabbable || if (!grabbable ||
eigProps.locked || eigProps.locked ||
isAnothersAvatarEntity(eigProps) ||
isAnothersChildEntity(eigProps) ||
FORBIDDEN_GRAB_TYPES.indexOf(eigProps.type) >= 0) { FORBIDDEN_GRAB_TYPES.indexOf(eigProps.type) >= 0) {
return false; return false;
} }

View file

@ -9,6 +9,7 @@
// //
#include "TestRunnerMobile.h" #include "TestRunnerMobile.h"
#include <QNetworkInterface>
#include <QThread> #include <QThread>
#include <QtWidgets/QMessageBox> #include <QtWidgets/QMessageBox>
#include <QtWidgets/QFileDialog> #include <QtWidgets/QFileDialog>
@ -69,13 +70,25 @@ void TestRunnerMobile::connectDevice() {
_adbInterface = new AdbInterface(); _adbInterface = new AdbInterface();
} }
// Get list of devices
QString devicesFullFilename{ _workingFolder + "/devices.txt" }; QString devicesFullFilename{ _workingFolder + "/devices.txt" };
QString command = _adbInterface->getAdbCommand() + " devices -l > " + devicesFullFilename; QString command = _adbInterface->getAdbCommand() + " devices -l > " + devicesFullFilename;
appendLog(command); appendLog(command);
system(command.toStdString().c_str()); system(command.toStdString().c_str());
if (!QFile::exists(devicesFullFilename)) { if (!QFile::exists(devicesFullFilename)) {
QMessageBox::critical(0, "Internal error", "devicesFullFilename not found"); QMessageBox::critical(0, "Internal error", "devices.txt not found");
exit (-1);
}
// Get device IP address
QString ifconfigFullFilename{ _workingFolder + "/ifconfig.txt" };
command = _adbInterface->getAdbCommand() + " shell ifconfig > " + ifconfigFullFilename;
appendLog(command);
system(command.toStdString().c_str());
if (!QFile::exists(ifconfigFullFilename)) {
QMessageBox::critical(0, "Internal error", "ifconfig.txt not found");
exit (-1); exit (-1);
} }
@ -86,6 +99,8 @@ void TestRunnerMobile::connectDevice() {
QString line2 = devicesFile.readLine(); QString line2 = devicesFile.readLine();
const QString DEVICE{ "device" }; const QString DEVICE{ "device" };
const QString MODEL{ "model" };
if (line2.contains("unauthorized")) { if (line2.contains("unauthorized")) {
QMessageBox::critical(0, "Unauthorized device detected", "Please allow USB debugging on device"); QMessageBox::critical(0, "Unauthorized device detected", "Please allow USB debugging on device");
} else if (line2.contains(DEVICE)) { } else if (line2.contains(DEVICE)) {
@ -98,13 +113,20 @@ void TestRunnerMobile::connectDevice() {
QStringList tokens = line2.split(QRegExp("[\r\n\t ]+")); QStringList tokens = line2.split(QRegExp("[\r\n\t ]+"));
QString deviceID = tokens[0]; QString deviceID = tokens[0];
QString modelID = tokens[3].split(':')[1]; // Find the model entry
QString modelName = "UNKNOWN"; _modelName = "UNKNOWN";
if (modelNames.count(modelID) == 1) { for (int i = 0; i < tokens.size(); ++i) {
modelName = modelNames[modelID]; if (tokens[i].contains(MODEL)) {
QString modelID = tokens[i].split(':')[1];
if (modelNames.count(modelID) == 1) {
_modelName = modelNames[modelID];
}
break;
}
} }
_detectedDeviceLabel->setText(modelName + " [" + deviceID + "]"); _detectedDeviceLabel->setText(_modelName + " [" + deviceID + "]");
_pullFolderButton->setEnabled(true); _pullFolderButton->setEnabled(true);
_folderLineEdit->setEnabled(true); _folderLineEdit->setEnabled(true);
_downloadAPKPushbutton->setEnabled(true); _downloadAPKPushbutton->setEnabled(true);
@ -191,14 +213,28 @@ void TestRunnerMobile::runInterface() {
? QString("https://raw.githubusercontent.com/") + nitpick->getSelectedUser() + "/hifi_tests/" + nitpick->getSelectedBranch() + "/tests/testRecursive.js" ? QString("https://raw.githubusercontent.com/") + nitpick->getSelectedUser() + "/hifi_tests/" + nitpick->getSelectedBranch() + "/tests/testRecursive.js"
: _scriptURL->text(); : _scriptURL->text();
// Quest and Android have different commands to run interface
QString startCommand;
if (_modelName == "Quest") {
startCommand = "io.highfidelity.questInterface/.PermissionsChecker";
} else {
startCommand = "io.highfidelity.hifiinterface/.PermissionChecker";
}
QString serverIP { getServerIP() };
if (serverIP == NETWORK_NOT_FOUND) {
_runInterfacePushbutton->setEnabled(false);
return;
}
QString command = _adbInterface->getAdbCommand() + QString command = _adbInterface->getAdbCommand() +
" shell am start -n io.highfidelity.hifiinterface/.PermissionChecker" + " shell am start -n " + startCommand +
" --es args \\\"" + " --es args \\\"" +
" --url file:///~/serverless/tutorial.json" + " --url hifi://" + serverIP + "/0,0,0"
" --no-updater" + " --no-updater" +
" --no-login-suggestion" + " --no-login-suggestion" +
" --testScript " + testScript + " quitWhenFinished" + " --testScript " + testScript + " quitWhenFinished" +
" --testResultsLocation /sdcard/snapshots" + " --testResultsLocation /sdcard/snapshots" +
"\\\""; "\\\"";
appendLog(command); appendLog(command);
@ -220,3 +256,85 @@ void TestRunnerMobile::pullFolder() {
_statusLabel->setText("Pull complete"); _statusLabel->setText("Pull complete");
#endif #endif
} }
QString TestRunnerMobile::getServerIP() {
// Get device IP (ifconfig.txt was created when connecting)
QFile ifconfigFile{ _workingFolder + "/ifconfig.txt" };
if (!ifconfigFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),
"Could not open 'ifconfig.txt'");
exit(-1);
}
QTextStream stream(&ifconfigFile);
QString line = ifconfigFile.readLine();
while (!line.isNull()) {
// The device IP is in the line following the "wlan0" line
if (line.left(6) == "wlan0 ") {
break;
}
line = ifconfigFile.readLine();
}
// The following line looks like this "inet addr:192.168.0.15 Bcast:192.168.0.255 Mask:255.255.255.0"
// Extract the address and mask
line = ifconfigFile.readLine();
QStringList lineParts = line.split(':');
if (lineParts.size() < 4) {
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),
"IP address line not in expected format: " + line + "(check that device WIFI is on)");
return NETWORK_NOT_FOUND;
}
qint64 deviceIP = convertToBinary(lineParts[1].split(' ')[0]);
qint64 deviceMask = convertToBinary(lineParts[3].split(' ')[0]);
qint64 deviceSubnet = deviceMask & deviceIP;
// The device needs to be on the same subnet as the server
// To find which of our IPs is the server - choose the 1st that is on the same subnet
// If more than one found then report an error
QString serverIP;
QList<QNetworkInterface> interfaces = QNetworkInterface::allInterfaces();
for (int i = 0; i < interfaces.count(); i++) {
QList<QNetworkAddressEntry> entries = interfaces.at(i).addressEntries();
for (int j = 0; j < entries.count(); j++) {
if (entries.at(j).ip().protocol() == QAbstractSocket::IPv4Protocol) {
qint64 hostIP = convertToBinary(entries.at(j).ip().toString());
qint64 hostMask = convertToBinary(entries.at(j).netmask().toString());
qint64 hostSubnet = hostMask & hostIP;
if (hostSubnet == deviceSubnet) {
if (!serverIP.isNull()) {
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),
"Cannot identify server IP (multiple interfaces on device submask)");
return QString("CANNOT IDENTIFY SERVER IP");
} else {
union {
uint32_t ip;
uint8_t bytes[4];
} u;
u.ip = hostIP;
serverIP = QString::number(u.bytes[3]) + '.' + QString::number(u.bytes[2]) + '.' + QString::number(u.bytes[1]) + '.' + QString::number(u.bytes[0]);
}
}
}
}
}
ifconfigFile.close();
return serverIP;
}
qint64 TestRunnerMobile::convertToBinary(const QString& str) {
QString binary;
foreach (const QString& s, str.split(".")) {
binary += QString::number(s.toInt(), 2).rightJustified(8, '0');
}
return binary.toLongLong(NULL, 2);
}

View file

@ -52,6 +52,9 @@ public:
void pullFolder(); void pullFolder();
QString getServerIP();
qint64 convertToBinary (const QString& str);
private: private:
QPushButton* _connectDeviceButton; QPushButton* _connectDeviceButton;
QPushButton* _pullFolderButton; QPushButton* _pullFolderButton;
@ -75,5 +78,9 @@ private:
std::map<QString, QString> modelNames; std::map<QString, QString> modelNames;
AdbInterface* _adbInterface; AdbInterface* _adbInterface;
QString _modelName;
QString NETWORK_NOT_FOUND{ "NETWORK NOT FOUND"};
}; };
#endif #endif

View file

@ -60,5 +60,5 @@ const double R_Y = 0.212655f;
const double G_Y = 0.715158f; const double G_Y = 0.715158f;
const double B_Y = 0.072187f; const double B_Y = 0.072187f;
const QString nitpickVersion { "v3.1.4" }; const QString nitpickVersion{ "v3.1.5" };
#endif // hifi_common_h #endif // hifi_common_h