diff --git a/assignment-client/src/audio/AudioMixerSlave.cpp b/assignment-client/src/audio/AudioMixerSlave.cpp index f7f8e8a9c1..cb90df58e5 100644 --- a/assignment-client/src/audio/AudioMixerSlave.cpp +++ b/assignment-client/src/audio/AudioMixerSlave.cpp @@ -51,7 +51,7 @@ void sendEnvironmentPacket(const SharedNodePointer& node, AudioMixerClientData& // mix helpers inline float approximateGain(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd); 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, const glm::vec3& relativePosition); @@ -504,14 +504,12 @@ void AudioMixerSlave::addStream(AudioMixerClientData::MixableStream& mixableStre glm::vec3 relativePosition = streamToAdd->getPosition() - listeningNodeStream.getPosition(); float distance = glm::max(glm::length(relativePosition), EPSILON); + float gain = isEcho ? 1.0f + : (isSoloing ? masterAvatarGain + : computeGain(masterAvatarGain, masterInjectorGain, listeningNodeStream, *streamToAdd, + relativePosition, distance)); float azimuth = isEcho ? 0.0f : computeAzimuth(listeningNodeStream, listeningNodeStream, relativePosition); - float gain = masterAvatarGain; - if (!isSoloing) { - gain = computeGain(masterAvatarGain, masterInjectorGain, listeningNodeStream, *streamToAdd, relativePosition, - distance, isEcho); - } - const int HRTF_DATASET_INDEX = 1; if (!streamToAdd->lastPopSucceeded()) { @@ -599,8 +597,8 @@ void AudioMixerSlave::updateHRTFParameters(AudioMixerClientData::MixableStream& glm::vec3 relativePosition = streamToAdd->getPosition() - listeningNodeStream.getPosition(); float distance = glm::max(glm::length(relativePosition), EPSILON); - float gain = computeGain(masterAvatarGain, masterInjectorGain, listeningNodeStream, *streamToAdd, relativePosition, - distance, isEcho); + float gain = isEcho ? 1.0f : computeGain(masterAvatarGain, masterInjectorGain, listeningNodeStream, *streamToAdd, + relativePosition, distance); float azimuth = isEcho ? 0.0f : computeAzimuth(listeningNodeStream, listeningNodeStream, relativePosition); mixableStream.hrtf->setParameterHistory(azimuth, distance, gain); @@ -743,8 +741,7 @@ float computeGain(float masterAvatarGain, const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd, const glm::vec3& relativePosition, - float distance, - bool isEcho) { + float distance) { float gain = 1.0f; // injector: apply attenuation @@ -754,7 +751,7 @@ float computeGain(float masterAvatarGain, gain *= masterInjectorGain; // avatar: apply fixed off-axis attenuation to make them quieter as they turn away - } else if (!isEcho && (streamToAdd.getType() == PositionalAudioStream::Microphone)) { + } else if (streamToAdd.getType() == PositionalAudioStream::Microphone) { glm::vec3 rotatedListenerPosition = glm::inverse(streamToAdd.getOrientation()) * relativePosition; // source directivity is based on angle of emission, in local coordinates diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 33e1034128..9816cebf43 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -253,10 +253,29 @@ void AvatarMixer::start() { int lockWait, nodeTransform, functor; + // Set our query each frame { _entityViewer.queryOctree(); } + // Dirty the hero status if there's been an entity change. + { + if (_dirtyHeroStatus) { + _dirtyHeroStatus = false; + nodeList->nestedEach([](NodeList::const_iterator cbegin, NodeList::const_iterator cend) { + std::for_each(cbegin, cend, [](const SharedNodePointer& node) { + if (node->getType() == NodeType::Agent) { + NodeData* nodeData = node->getLinkedData(); + if (nodeData) { + auto& avatar = static_cast<AvatarMixerClientData*>(nodeData)->getAvatar(); + avatar.setNeedsHeroCheck(); + } + } + }); + }); + } + } + // Allow nodes to process any pending/queued packets across our worker threads { auto start = usecTimestampNow(); @@ -827,7 +846,7 @@ void AvatarMixer::sendStatsPacket() { QJsonObject avatarsObject; auto nodeList = DependencyManager::get<NodeList>(); - // add stats for each listerner + // add stats for each listener nodeList->eachNode([&](const SharedNodePointer& node) { QJsonObject avatarStats; @@ -851,6 +870,12 @@ void AvatarMixer::sendStatsPacket() { avatarStats["delta_full_vs_avatar_data_kbps"] = (double)outboundAvatarDataKbps - avatarStats[OUTBOUND_AVATAR_DATA_STATS_KEY].toDouble(); } + + if (node->getType() != NodeType::Agent) { // Nodes that aren't avatars + const QString displayName + { node->getType() == NodeType::EntityScriptServer ? "ENTITY SCRIPT SERVER" : "ENTITY SERVER" }; + avatarStats["display_name"] = displayName; + } } avatarsObject[uuidStringWithoutCurlyBraces(node->getUUID())] = avatarStats; @@ -973,19 +998,30 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) { { const QString CONNECTION_RATE = "connection_rate"; auto nodeList = DependencyManager::get<NodeList>(); - auto defaultConnectionRate = nodeList->getMaxConnectionRate(); - int connectionRate = avatarMixerGroupObject[CONNECTION_RATE].toInt((int)defaultConnectionRate); - nodeList->setMaxConnectionRate(connectionRate); + bool success; + int connectionRate = avatarMixerGroupObject[CONNECTION_RATE].toString().toInt(&success); + if (success) { + nodeList->setMaxConnectionRate(connectionRate); + } + } + + { // Fraction of downstream bandwidth reserved for 'hero' avatars: + static const QString PRIORITY_FRACTION_KEY = "priority_fraction"; + if (avatarMixerGroupObject.contains(PRIORITY_FRACTION_KEY)) { + float priorityFraction = float(avatarMixerGroupObject[PRIORITY_FRACTION_KEY].toDouble()); + _slavePool.setPriorityReservedFraction(std::min(std::max(0.0f, priorityFraction), 1.0f)); + qCDebug(avatars) << "Avatar mixer reserving" << priorityFraction << "of bandwidth for priority avatars"; + } } const QString AVATARS_SETTINGS_KEY = "avatars"; static const QString MIN_HEIGHT_OPTION = "min_avatar_height"; - float settingMinHeight = domainSettings[AVATARS_SETTINGS_KEY].toObject()[MIN_HEIGHT_OPTION].toDouble(MIN_AVATAR_HEIGHT); + float settingMinHeight = avatarMixerGroupObject[MIN_HEIGHT_OPTION].toDouble(MIN_AVATAR_HEIGHT); _domainMinimumHeight = glm::clamp(settingMinHeight, MIN_AVATAR_HEIGHT, MAX_AVATAR_HEIGHT); static const QString MAX_HEIGHT_OPTION = "max_avatar_height"; - float settingMaxHeight = domainSettings[AVATARS_SETTINGS_KEY].toObject()[MAX_HEIGHT_OPTION].toDouble(MAX_AVATAR_HEIGHT); + float settingMaxHeight = avatarMixerGroupObject[MAX_HEIGHT_OPTION].toDouble(MAX_AVATAR_HEIGHT); _domainMaximumHeight = glm::clamp(settingMaxHeight, MIN_AVATAR_HEIGHT, MAX_AVATAR_HEIGHT); // make sure that the domain owner didn't flip min and max @@ -997,11 +1033,11 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) { << "and a maximum avatar height of" << _domainMaximumHeight; static const QString AVATAR_WHITELIST_OPTION = "avatar_whitelist"; - _slaveSharedData.skeletonURLWhitelist = domainSettings[AVATARS_SETTINGS_KEY].toObject()[AVATAR_WHITELIST_OPTION] + _slaveSharedData.skeletonURLWhitelist = avatarMixerGroupObject[AVATAR_WHITELIST_OPTION] .toString().split(',', QString::KeepEmptyParts); static const QString REPLACEMENT_AVATAR_OPTION = "replacement_avatar"; - _slaveSharedData.skeletonReplacementURL = domainSettings[AVATARS_SETTINGS_KEY].toObject()[REPLACEMENT_AVATAR_OPTION] + _slaveSharedData.skeletonReplacementURL = avatarMixerGroupObject[REPLACEMENT_AVATAR_OPTION] .toString(); if (_slaveSharedData.skeletonURLWhitelist.count() == 1 && _slaveSharedData.skeletonURLWhitelist[0].isEmpty()) { @@ -1018,9 +1054,12 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) { void AvatarMixer::setupEntityQuery() { _entityViewer.init(); + EntityTreePointer entityTree = _entityViewer.getTree(); DependencyManager::registerInheritance<SpatialParentFinder, AssignmentParentFinder>(); - DependencyManager::set<AssignmentParentFinder>(_entityViewer.getTree()); - _slaveSharedData.entityTree = _entityViewer.getTree(); + DependencyManager::set<AssignmentParentFinder>(entityTree); + + connect(entityTree.get(), &EntityTree::addingEntityPointer, this, &AvatarMixer::entityAdded); + connect(entityTree.get(), &EntityTree::deletingEntityPointer, this, &AvatarMixer::entityChange); // ES query: {"avatarPriority": true, "type": "Zone"} QJsonObject priorityZoneQuery; @@ -1028,6 +1067,7 @@ void AvatarMixer::setupEntityQuery() { priorityZoneQuery["type"] = "Zone"; _entityViewer.getOctreeQuery().setJSONParameters(priorityZoneQuery); + _slaveSharedData.entityTree = entityTree; } void AvatarMixer::handleOctreePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) { @@ -1064,6 +1104,25 @@ void AvatarMixer::handleOctreePacket(QSharedPointer<ReceivedMessage> message, Sh } } +void AvatarMixer::entityAdded(EntityItem* entity) { + if (entity->getType() == EntityTypes::Zone) { + _dirtyHeroStatus = true; + entity->registerChangeHandler([this](const EntityItemID& entityItemID) { + entityChange(); + }); + } +} + +void AvatarMixer::entityRemoved(EntityItem * entity) { + if (entity->getType() == EntityTypes::Zone) { + _dirtyHeroStatus = true; + } +} + +void AvatarMixer::entityChange() { + _dirtyHeroStatus = true; +} + void AvatarMixer::aboutToFinish() { DependencyManager::destroy<ResourceManager>(); DependencyManager::destroy<ResourceCacheSharedItems>(); diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index 9393ea6c56..10dff5e8a4 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -34,8 +34,8 @@ public: static bool shouldReplicateTo(const Node& from, const Node& to) { return to.getType() == NodeType::DownstreamAvatarMixer && - to.getPublicSocket() != from.getPublicSocket() && - to.getLocalSocket() != from.getLocalSocket(); + to.getPublicSocket() != from.getPublicSocket() && + to.getLocalSocket() != from.getLocalSocket(); } public slots: @@ -46,6 +46,11 @@ public slots: void sendStatsPacket() override; + // Avatar zone possibly changed + void entityAdded(EntityItem* entity); + void entityRemoved(EntityItem* entity); + void entityChange(); + private slots: void queueIncomingPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer node); void handleAdjustAvatarSorting(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode); @@ -80,6 +85,7 @@ private: // Attach to entity tree for avatar-priority zone info. EntityTreeHeadlessViewer _entityViewer; + bool _dirtyHeroStatus { true }; // Dirty the needs-hero-update // FIXME - new throttling - use these values somehow float _trailingMixRatio { 0.0f }; diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index 557c5c9fe3..1b86e0dff2 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -129,7 +129,7 @@ int AvatarMixerClientData::parseData(ReceivedMessage& message, const SlaveShared incrementNumOutOfOrderSends(); } _lastReceivedSequenceNumber = sequenceNumber; - glm::vec3 oldPosition = getPosition(); + glm::vec3 oldPosition = _avatar->getClientGlobalPosition(); bool oldHasPriority = _avatar->getHasPriority(); // compute the offset to the data payload @@ -140,23 +140,13 @@ int AvatarMixerClientData::parseData(ReceivedMessage& message, const SlaveShared // Regardless of what the client says, restore the priority as we know it without triggering any update. _avatar->setHasPriorityWithoutTimestampReset(oldHasPriority); - auto newPosition = getPosition(); - if (newPosition != oldPosition) { -//#define AVATAR_HERO_TEST_HACK -#ifdef AVATAR_HERO_TEST_HACK - { - const static QString heroKey { "HERO" }; - _avatar->setPriorityAvatar(_avatar->getDisplayName().contains(heroKey)); - } -#else + auto newPosition = _avatar->getClientGlobalPosition(); + if (newPosition != oldPosition || _avatar->getNeedsHeroCheck()) { EntityTree& entityTree = *slaveSharedData.entityTree; - FindPriorityZone findPriorityZone { newPosition, false } ; + FindPriorityZone findPriorityZone { newPosition } ; entityTree.recurseTreeWithOperation(&FindPriorityZone::operation, &findPriorityZone); _avatar->setHasPriority(findPriorityZone.isInPriorityZone); - //if (findPriorityZone.isInPriorityZone) { - // qCWarning(avatars) << "Avatar" << _avatar->getSessionDisplayName() << "in hero zone"; - //} -#endif + _avatar->setNeedsHeroCheck(false); } return true; @@ -341,7 +331,7 @@ void AvatarMixerClientData::checkSkeletonURLAgainstWhitelist(const SlaveSharedDa // the returned set traits packet uses the trait version from the incoming packet // so the client knows they should not overwrite if they have since changed the trait - _avatar->packTrait(AvatarTraits::SkeletonModelURL, *packet, traitVersion); + AvatarTraits::packVersionedTrait(AvatarTraits::SkeletonModelURL, *packet, traitVersion, *_avatar); auto nodeList = DependencyManager::get<NodeList>(); nodeList->sendPacket(std::move(packet), sendingNode); diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index e59c81f4b7..32c944f5b8 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -43,12 +43,14 @@ void AvatarMixerSlave::configure(ConstIter begin, ConstIter end) { void AvatarMixerSlave::configureBroadcast(ConstIter begin, ConstIter end, p_high_resolution_clock::time_point lastFrameTimestamp, - float maxKbpsPerNode, float throttlingRatio) { + float maxKbpsPerNode, float throttlingRatio, + float priorityReservedFraction) { _begin = begin; _end = end; _lastFrameTimestamp = lastFrameTimestamp; _maxKbpsPerNode = maxKbpsPerNode; _throttlingRatio = throttlingRatio; + _avatarHeroFraction = priorityReservedFraction; } void AvatarMixerSlave::harvestStats(AvatarMixerSlaveStats& stats) { @@ -139,7 +141,8 @@ qint64 AvatarMixerSlave::addChangedTraitsToBulkPacket(AvatarMixerClientData* lis if (lastReceivedVersion > lastSentVersionRef) { bytesWritten += addTraitsNodeHeader(listeningNodeData, sendingNodeData, traitsPacketList, bytesWritten); // there is an update to this trait, add it to the traits packet - bytesWritten += sendingAvatar->packTrait(traitType, traitsPacketList, lastReceivedVersion); + bytesWritten += AvatarTraits::packVersionedTrait(traitType, traitsPacketList, + lastReceivedVersion, *sendingAvatar); // update the last sent version lastSentVersionRef = lastReceivedVersion; // Remember which versions we sent in this particular packet @@ -194,7 +197,8 @@ qint64 AvatarMixerSlave::addChangedTraitsToBulkPacket(AvatarMixerClientData* lis bytesWritten += addTraitsNodeHeader(listeningNodeData, sendingNodeData, traitsPacketList, bytesWritten); // this instance version exists and has never been sent or is newer so we need to send it - bytesWritten += sendingAvatar->packTraitInstance(traitType, instanceID, traitsPacketList, receivedVersion); + bytesWritten += AvatarTraits::packVersionedTraitInstance(traitType, instanceID, traitsPacketList, + receivedVersion, *sendingAvatar); if (sentInstanceIt != sentIDValuePairs.end()) { sentInstanceIt->value = receivedVersion; @@ -308,7 +312,6 @@ namespace { } // Close anonymous namespace. void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) { - const float AVATAR_HERO_FRACTION { 0.4f }; const Node* destinationNode = node.data(); auto nodeList = DependencyManager::get<NodeList>(); @@ -343,7 +346,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // max number of avatarBytes per frame (13 900, typical) const int maxAvatarBytesPerFrame = int(_maxKbpsPerNode * BYTES_PER_KILOBIT / AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND); - const int maxHeroBytesPerFrame = int(maxAvatarBytesPerFrame * AVATAR_HERO_FRACTION); // 5555, typical + const int maxHeroBytesPerFrame = int(maxAvatarBytesPerFrame * _avatarHeroFraction); // 5555, typical // keep track of the number of other avatars held back in this frame int numAvatarsHeldBack = 0; @@ -469,8 +472,8 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) SortableAvatar(avatarNodeData, sourceAvatarNode, lastEncodeTime)); } - // If Avatar A's PAL WAS open but is no longer open, AND - // Avatar A is ignoring Avatar B OR Avatar B is ignoring Avatar A... + // If Node A's PAL WAS open but is no longer open, AND + // Node A is ignoring Avatar B OR Node B is ignoring Avatar A... // // This is a bit heavy-handed still - there are cases where a kill packet // will be sent when it doesn't need to be (but where it _should_ be OK to send). @@ -539,7 +542,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) const MixerAvatar* sourceAvatar = sourceNodeData->getConstAvatarData(); // Typically all out-of-view avatars but such avatars' priorities will rise with time: - bool isLowerPriority = currentVariant != kHero && sortedAvatar.getPriority() <= OUT_OF_VIEW_THRESHOLD; // XXX: hero handling? + bool isLowerPriority = sortedAvatar.getPriority() <= OUT_OF_VIEW_THRESHOLD; if (isLowerPriority) { detail = PALIsOpen ? AvatarData::PALMinimum : AvatarData::MinimumData; @@ -548,8 +551,8 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) detail = distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO ? AvatarData::SendAllData : AvatarData::CullSmallData; destinationNodeData->incrementAvatarInView(); - // If the time that the mixer sent AVATAR DATA about Avatar B to Avatar A is BEFORE OR EQUAL TO - // the time that Avatar B flagged an IDENTITY DATA change, send IDENTITY DATA about Avatar B to Avatar A. + // If the time that the mixer sent AVATAR DATA about Avatar B to Node A is BEFORE OR EQUAL TO + // the time that Avatar B flagged an IDENTITY DATA change, send IDENTITY DATA about Avatar B to Node A. if (sourceAvatar->hasProcessedFirstIdentity() && destinationNodeData->getLastBroadcastTime(sourceNode->getLocalID()) <= sourceNodeData->getIdentityChangeTimestamp()) { identityBytesSent += sendIdentityPacket(*identityPacketList, sourceNodeData, *destinationNode); diff --git a/assignment-client/src/avatars/AvatarMixerSlave.h b/assignment-client/src/avatars/AvatarMixerSlave.h index 8c5ad6b181..f14e50e11f 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.h +++ b/assignment-client/src/avatars/AvatarMixerSlave.h @@ -110,7 +110,8 @@ public: void configure(ConstIter begin, ConstIter end); void configureBroadcast(ConstIter begin, ConstIter end, p_high_resolution_clock::time_point lastFrameTimestamp, - float maxKbpsPerNode, float throttlingRatio); + float maxKbpsPerNode, float throttlingRatio, + float priorityReservedFraction); void processIncomingPackets(const SharedNodePointer& node); void broadcastAvatarData(const SharedNodePointer& node); @@ -140,6 +141,7 @@ private: p_high_resolution_clock::time_point _lastFrameTimestamp; float _maxKbpsPerNode { 0.0f }; float _throttlingRatio { 0.0f }; + float _avatarHeroFraction { 0.4f }; AvatarMixerSlaveStats _stats; SlaveSharedData* _sharedData; diff --git a/assignment-client/src/avatars/AvatarMixerSlavePool.cpp b/assignment-client/src/avatars/AvatarMixerSlavePool.cpp index 013d914cbe..357b347a94 100644 --- a/assignment-client/src/avatars/AvatarMixerSlavePool.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlavePool.cpp @@ -76,7 +76,8 @@ void AvatarMixerSlavePool::broadcastAvatarData(ConstIter begin, ConstIter end, float maxKbpsPerNode, float throttlingRatio) { _function = &AvatarMixerSlave::broadcastAvatarData; _configure = [=](AvatarMixerSlave& slave) { - slave.configureBroadcast(begin, end, lastFrameTimestamp, maxKbpsPerNode, throttlingRatio); + slave.configureBroadcast(begin, end, lastFrameTimestamp, maxKbpsPerNode, throttlingRatio, + _priorityReservedFraction); }; run(begin, end); } diff --git a/assignment-client/src/avatars/AvatarMixerSlavePool.h b/assignment-client/src/avatars/AvatarMixerSlavePool.h index 71a9ace0d3..b05abde2a3 100644 --- a/assignment-client/src/avatars/AvatarMixerSlavePool.h +++ b/assignment-client/src/avatars/AvatarMixerSlavePool.h @@ -73,7 +73,10 @@ public: void each(std::function<void(AvatarMixerSlave& slave)> functor); void setNumThreads(int numThreads); - int numThreads() { return _numThreads; } + int numThreads() const { return _numThreads; } + + void setPriorityReservedFraction(float fraction) { _priorityReservedFraction = fraction; } + float getPriorityReservedFraction() const { return _priorityReservedFraction; } private: void run(ConstIter begin, ConstIter end); @@ -91,7 +94,11 @@ private: ConditionVariable _poolCondition; void (AvatarMixerSlave::*_function)(const SharedNodePointer& node); std::function<void(AvatarMixerSlave&)> _configure; + + // Set from Domain Settings: + float _priorityReservedFraction { 0.4f }; int _numThreads { 0 }; + int _numStarted { 0 }; // guarded by _mutex int _numFinished { 0 }; // guarded by _mutex int _numStopped { 0 }; // guarded by _mutex diff --git a/assignment-client/src/avatars/MixerAvatar.h b/assignment-client/src/avatars/MixerAvatar.h index 3e80704495..01e5e91b44 100644 --- a/assignment-client/src/avatars/MixerAvatar.h +++ b/assignment-client/src/avatars/MixerAvatar.h @@ -19,8 +19,12 @@ class MixerAvatar : public AvatarData { public: + bool getNeedsHeroCheck() const { return _needsHeroCheck; } + void setNeedsHeroCheck(bool needsHeroCheck = true) + { _needsHeroCheck = needsHeroCheck; } private: + bool _needsHeroCheck { false }; }; using MixerAvatarSharedPointer = std::shared_ptr<MixerAvatar>; diff --git a/assignment-client/src/avatars/ScriptableAvatar.h b/assignment-client/src/avatars/ScriptableAvatar.h index fbe5675bd8..e5df411099 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.h +++ b/assignment-client/src/avatars/ScriptableAvatar.h @@ -74,6 +74,7 @@ * avatar. <em>Read-only.</em> * @property {number} sensorToWorldScale - The scale that transforms dimensions in the user's real world to the avatar's * size in the virtual world. <em>Read-only.</em> + * @property {boolean} hasPriority - is the avatar in a Hero zone? <em>Read-only.</em> * * @example <caption>Create a scriptable avatar.</caption> * (function () { diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 140c7d6c17..352106dcf7 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -1310,6 +1310,15 @@ "placeholder": "50", "default": "50", "advanced": true + }, + { + "name": "priority_fraction", + "type": "double", + "label": "Hero Bandwidth", + "help": "Fraction of downstream bandwidth reserved for avatars in 'Hero' zones", + "placeholder": "0.40", + "default": "0.40", + "advanced": true } ] }, diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 8d5cb165cb..5f82700e9c 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1766,14 +1766,14 @@ void DomainServer::processOctreeDataRequestMessage(QSharedPointer<ReceivedMessag bool remoteHasExistingData { false }; QUuid id; - int version; + int dataVersion; message->readPrimitive(&remoteHasExistingData); if (remoteHasExistingData) { constexpr size_t UUID_SIZE_BYTES = 16; auto idData = message->read(UUID_SIZE_BYTES); id = QUuid::fromRfc4122(idData); - message->readPrimitive(&version); - qCDebug(domain_server) << "Entity server does have existing data: ID(" << id << ") DataVersion(" << version << ")"; + message->readPrimitive(&dataVersion); + qCDebug(domain_server) << "Entity server does have existing data: ID(" << id << ") DataVersion(" << dataVersion << ")"; } else { qCDebug(domain_server) << "Entity server does not have existing data"; } @@ -1782,11 +1782,11 @@ void DomainServer::processOctreeDataRequestMessage(QSharedPointer<ReceivedMessag auto reply = NLPacketList::create(PacketType::OctreeDataFileReply, QByteArray(), true, true); OctreeUtils::RawEntityData data; if (data.readOctreeDataInfoFromFile(entityFilePath)) { - if (data.id == id && data.version <= version) { + if (data.id == id && data.dataVersion <= dataVersion) { qCDebug(domain_server) << "ES has sufficient octree data, not sending data"; reply->writePrimitive(false); } else { - qCDebug(domain_server) << "Sending newer octree data to ES: ID(" << data.id << ") DataVersion(" << data.version << ")"; + qCDebug(domain_server) << "Sending newer octree data to ES: ID(" << data.id << ") DataVersion(" << data.dataVersion << ")"; QFile file(entityFilePath); if (file.open(QIODevice::ReadOnly)) { reply->writePrimitive(true); diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index ba5e162391..4eea3566b8 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -9,7 +9,7 @@ // import QtQuick 2.7 -import Qt.labs.folderlistmodel 2.1 +import Qt.labs.folderlistmodel 2.2 import Qt.labs.settings 1.0 import QtQuick.Dialogs 1.2 as OriginalDialogs import QtQuick.Controls 1.4 as QQC1 @@ -320,6 +320,7 @@ ModalWindow { FolderListModel { id: folderListModel nameFilters: selectionType.currentFilter + caseSensitive: false showDirsFirst: true showDotAndDotDot: false showFiles: !root.selectDirectory diff --git a/interface/resources/qml/dialogs/TabletFileDialog.qml b/interface/resources/qml/dialogs/TabletFileDialog.qml index 6c4e32dc5a..5bcc42f101 100644 --- a/interface/resources/qml/dialogs/TabletFileDialog.qml +++ b/interface/resources/qml/dialogs/TabletFileDialog.qml @@ -9,7 +9,7 @@ // import QtQuick 2.7 -import Qt.labs.folderlistmodel 2.1 +import Qt.labs.folderlistmodel 2.2 import Qt.labs.settings 1.0 import QtQuick.Dialogs 1.2 as OriginalDialogs import QtQuick.Controls 1.4 as QQC1 @@ -285,6 +285,7 @@ TabletModalWindow { FolderListModel { id: folderListModel nameFilters: selectionType.currentFilter + caseSensitive: false showDirsFirst: true showDotAndDotDot: false showFiles: !root.selectDirectory diff --git a/interface/resources/qml/hifi/AvatarApp.qml b/interface/resources/qml/hifi/AvatarApp.qml index 753b9c5a81..997407885b 100644 --- a/interface/resources/qml/hifi/AvatarApp.qml +++ b/interface/resources/qml/hifi/AvatarApp.qml @@ -16,6 +16,8 @@ Rectangle { property bool keyboardRaised: false property bool punctuationMode: false + HifiConstants { id: hifi } + HifiControls.Keyboard { id: keyboard z: 1000 @@ -48,6 +50,7 @@ Rectangle { property var jointNames: [] property var currentAvatarSettings; + property bool wearablesFrozen; function fetchAvatarModelName(marketId, avatar) { var xmlhttp = new XMLHttpRequest(); @@ -187,6 +190,8 @@ Rectangle { updateCurrentAvatarInBookmarks(currentAvatar); } else if (message.method === 'selectAvatarEntity') { adjustWearables.selectWearableByID(message.entityID); + } else if (message.method === 'wearablesFrozenChanged') { + wearablesFrozen = message.wearablesFrozen; } } @@ -507,6 +512,7 @@ Rectangle { } SquareLabel { + id: adjustLabel anchors.right: parent.right anchors.verticalCenter: wearablesLabel.verticalCenter glyphText: "\ue02e" @@ -515,6 +521,17 @@ Rectangle { adjustWearables.open(currentAvatar); } } + + SquareLabel { + anchors.right: adjustLabel.left + anchors.verticalCenter: wearablesLabel.verticalCenter + anchors.rightMargin: 15 + glyphText: wearablesFrozen ? hifi.glyphs.lock : hifi.glyphs.unlock; + + onClicked: { + emitSendToScript({'method' : 'toggleWearablesFrozen'}); + } + } } Rectangle { diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index 89a30b4b91..55378589ec 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -40,6 +40,9 @@ Rectangle { AudioScriptingInterface.pushToTalkChanged.connect(function() { pushToTalk = AudioScriptingInterface.pushToTalk; }); + AudioScriptingInterface.pushingToTalkChanged.connect(function() { + pushingToTalk = AudioScriptingInterface.pushingToTalk; + }); } property bool standalone: false; @@ -100,16 +103,16 @@ Rectangle { QtObject { id: colors; - readonly property string unmuted: "#FFF"; - readonly property string muted: "#E2334D"; + readonly property string unmutedColor: "#FFF"; + readonly property string mutedColor: "#E2334D"; readonly property string gutter: "#575757"; readonly property string greenStart: "#39A38F"; readonly property string greenEnd: "#1FC6A6"; readonly property string yellow: "#C0C000"; - readonly property string red: colors.muted; + readonly property string red: colors.mutedColor; readonly property string fill: "#55000000"; readonly property string border: standalone ? "#80FFFFFF" : "#55FFFFFF"; - readonly property string icon: micBar.muted ? muted : unmuted; + readonly property string icon: muted ? colors.mutedColor : unmutedColor; } Item { @@ -157,7 +160,6 @@ Rectangle { Item { id: status; - visible: (pushToTalk && !pushingToTalk) || muted; anchors { diff --git a/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml b/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml index 136d535b3f..391e4fab37 100644 --- a/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml +++ b/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml @@ -113,6 +113,7 @@ Rectangle { } else if (prop === 'dimensions') { scalespinner.set(wearable[prop].x / wearable.naturalDimensions.x); } + modified = true; } } diff --git a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml index 7dcdf9b434..619547ef43 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml @@ -664,7 +664,7 @@ Rectangle { text: "LOG IN" onClicked: { - sendToScript({method: 'needsLogIn_loginClicked'}); + sendToScript({method: 'marketplace_loginClicked'}); } } diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml b/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml index a27c7b59dc..36a37134bf 100644 --- a/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml +++ b/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml @@ -9,7 +9,7 @@ // import QtQuick 2.7 -import Qt.labs.folderlistmodel 2.1 +import Qt.labs.folderlistmodel 2.2 import Qt.labs.settings 1.0 import QtQuick.Dialogs 1.2 as OriginalDialogs import QtQuick.Controls 1.4 as QQC1 @@ -279,6 +279,7 @@ Rectangle { FolderListModel { id: folderListModel nameFilters: selectionType.currentFilter + caseSensitive: false showDirsFirst: true showDotAndDotDot: false showFiles: !root.selectDirectory diff --git a/interface/resources/qml/stylesUit/+android_interface/HifiConstants.qml b/interface/resources/qml/stylesUit/+android_interface/HifiConstants.qml index d5fab57501..995af90f0b 100644 --- a/interface/resources/qml/stylesUit/+android_interface/HifiConstants.qml +++ b/interface/resources/qml/stylesUit/+android_interface/HifiConstants.qml @@ -344,6 +344,7 @@ Item { readonly property string stop_square: "\ue01e" readonly property string avatarTPose: "\ue01f" readonly property string lock: "\ue006" + readonly property string unlock: "\ue039" readonly property string checkmark: "\ue020" readonly property string leftRightArrows: "\ue021" readonly property string hfc: "\ue022" diff --git a/interface/resources/qml/stylesUit/HifiConstants.qml b/interface/resources/qml/stylesUit/HifiConstants.qml index 75f028cd4f..2394b36106 100644 --- a/interface/resources/qml/stylesUit/HifiConstants.qml +++ b/interface/resources/qml/stylesUit/HifiConstants.qml @@ -330,6 +330,7 @@ QtObject { readonly property string stop_square: "\ue01e" readonly property string avatarTPose: "\ue01f" readonly property string lock: "\ue006" + readonly property string unlock: "\ue039" readonly property string checkmark: "\ue020" readonly property string leftRightArrows: "\ue021" readonly property string hfc: "\ue022" diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index ecafbfdb2c..efb7bfa62f 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1210,10 +1210,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(&domainHandler, SIGNAL(connectedToDomain(QUrl)), SLOT(updateWindowTitle())); connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle())); connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, [this]() { - auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>(); - if (tabletScriptingInterface) { - tabletScriptingInterface->setQmlTabletRoot(SYSTEM_TABLET, nullptr); - } auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>(); entityScriptingInterface->deleteEntity(getTabletScreenID()); entityScriptingInterface->deleteEntity(getTabletHomeButtonID()); diff --git a/interface/src/avatar/AvatarDoctor.cpp b/interface/src/avatar/AvatarDoctor.cpp index 43e50ea049..01a40e89fd 100644 --- a/interface/src/avatar/AvatarDoctor.cpp +++ b/interface/src/avatar/AvatarDoctor.cpp @@ -92,7 +92,7 @@ void AvatarDoctor::startDiagnosing() { _model = resource; const auto model = resource.data(); const auto avatarModel = resource.data()->getHFMModel(); - if (!avatarModel.originalURL.endsWith(".fbx")) { + if (!avatarModel.originalURL.toLower().endsWith(".fbx")) { addError("Unsupported avatar model format.", "unsupported-format"); emit complete(getErrors()); return; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index f2d8730ebb..27151d0615 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -5463,7 +5463,7 @@ void MyAvatar::useFlow(bool isActive, bool isCollidable, const QVariantMap& phys } auto collisionJoints = collisionsConfig.keys(); if (collisionJoints.size() > 0) { - collisionSystem.resetCollisions(); + collisionSystem.clearSelfCollisions(); for (auto &jointName : collisionJoints) { int jointIndex = getJointIndex(jointName); FlowCollisionSettings collisionsSettings; @@ -5478,6 +5478,7 @@ void MyAvatar::useFlow(bool isActive, bool isCollidable, const QVariantMap& phys collisionSystem.addCollisionSphere(jointIndex, collisionsSettings); } } + flow.updateScale(); } } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index edb686a6a6..0859c20153 100755 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -295,6 +295,7 @@ class MyAvatar : public Avatar { * @comment Avatar.setAbsoluteJointTranslationInObjectFrame as setAbsoluteJointTranslationInObjectFrame - Don't borrow because implementation is different. * @borrows Avatar.getTargetScale as getTargetScale * @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 Q_PROPERTY(QVector3D qmlPosition READ getQmlPosition) @@ -2170,7 +2171,7 @@ private: bool getEnableStepResetRotation() const { return _stepResetRotationEnabled; } void setEnableDrawAverageFacing(bool drawAverage) { _drawAverageFacingEnabled = drawAverage; } 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 glm::vec3 getSkeletonPosition() const override; int _skeletonModelChangeCount { 0 }; diff --git a/interface/src/avatar/OtherAvatar.cpp b/interface/src/avatar/OtherAvatar.cpp index 11eb6542c4..b100b33dc8 100755 --- a/interface/src/avatar/OtherAvatar.cpp +++ b/interface/src/avatar/OtherAvatar.cpp @@ -365,7 +365,7 @@ void OtherAvatar::handleChangedAvatarEntityData() { // AVATAR ENTITY UPDATE FLOW // - if queueEditEntityMessage() sees "AvatarEntity" HostType it calls _myAvatar->storeAvatarEntityDataPayload() // - storeAvatarEntityDataPayload() saves the payload and flags the trait instance for the entity as updated, - // - ClientTraitsHandler::sendChangedTraitsToMixea() sends the entity bytes to the mixer which relays them to other interfaces + // - ClientTraitsHandler::sendChangedTraitsToMixer() sends the entity bytes to the mixer which relays them to other interfaces // - AvatarHashMap::processBulkAvatarTraits() on other interfaces calls avatar->processTraitInstance() // - AvatarData::processTraitInstance() calls storeAvatarEntityDataPayload(), which sets _avatarEntityDataChanged = true // - (My)Avatar::simulate() calls handleChangedAvatarEntityData() every frame which checks _avatarEntityDataChanged @@ -495,6 +495,18 @@ void OtherAvatar::handleChangedAvatarEntityData() { const QUuid NULL_ID = QUuid("{00000000-0000-0000-0000-000000000005}"); entity->setParentID(NULL_ID); entity->setParentID(oldParentID); + + if (entity->stillHasMyGrabAction()) { + // For this case: we want to ignore transform+velocities coming from authoritative OtherAvatar + // because the MyAvatar is grabbing and we expect the local grab state + // to have enough information to prevent simulation drift. + // + // Clever readers might realize this could cause problems. For example, + // if an ignored OtherAvagtar were to simultanously grab the object then there would be + // a noticeable discrepancy between participants in the distributed physics simulation, + // however the difference would be stable and would not drift. + properties.clearTransformOrVelocityChanges(); + } if (entityTree->updateEntity(entityID, properties)) { entity->updateLastEditedFromRemote(); } else { diff --git a/libraries/animation/src/Flow.cpp b/libraries/animation/src/Flow.cpp index 5bc2021e5e..1f9e72bf28 100644 --- a/libraries/animation/src/Flow.cpp +++ b/libraries/animation/src/Flow.cpp @@ -67,17 +67,23 @@ void FlowCollisionSystem::addCollisionSphere(int jointIndex, const FlowCollision auto collision = FlowCollisionSphere(jointIndex, settings, isTouch); collision.setPosition(position); if (isSelfCollision) { - _selfCollisions.push_back(collision); + if (!isTouch) { + _selfCollisions.push_back(collision); + } else { + _selfTouchCollisions.push_back(collision); + } } else { _othersCollisions.push_back(collision); } - }; + void FlowCollisionSystem::resetCollisions() { _allCollisions.clear(); _othersCollisions.clear(); + _selfTouchCollisions.clear(); _selfCollisions.clear(); } + FlowCollisionResult FlowCollisionSystem::computeCollision(const std::vector<FlowCollisionResult> collisions) { FlowCollisionResult result; if (collisions.size() > 1) { @@ -106,6 +112,10 @@ void FlowCollisionSystem::setScale(float scale) { _selfCollisions[j]._radius = _selfCollisions[j]._initialRadius * scale; _selfCollisions[j]._offset = _selfCollisions[j]._initialOffset * scale; } + for (size_t j = 0; j < _selfTouchCollisions.size(); j++) { + _selfTouchCollisions[j]._radius = _selfTouchCollisions[j]._initialRadius * scale; + _selfTouchCollisions[j]._offset = _selfTouchCollisions[j]._initialOffset * scale; + } }; std::vector<FlowCollisionResult> FlowCollisionSystem::checkFlowThreadCollisions(FlowThread* flowThread) { @@ -178,9 +188,9 @@ void FlowCollisionSystem::setCollisionSettingsByJoint(int jointIndex, const Flow } void FlowCollisionSystem::prepareCollisions() { _allCollisions.clear(); - _allCollisions.resize(_selfCollisions.size() + _othersCollisions.size()); - std::copy(_selfCollisions.begin(), _selfCollisions.begin() + _selfCollisions.size(), _allCollisions.begin()); - std::copy(_othersCollisions.begin(), _othersCollisions.begin() + _othersCollisions.size(), _allCollisions.begin() + _selfCollisions.size()); + _allCollisions.insert(_allCollisions.end(), _selfCollisions.begin(), _selfCollisions.end()); + _allCollisions.insert(_allCollisions.end(), _othersCollisions.begin(), _othersCollisions.end()); + _allCollisions.insert(_allCollisions.end(), _selfTouchCollisions.begin(), _selfTouchCollisions.end()); _othersCollisions.clear(); } @@ -273,18 +283,20 @@ void FlowJoint::setRecoveryPosition(const glm::vec3& recoveryPosition) { } void FlowJoint::update(float deltaTime) { - glm::vec3 accelerationOffset = glm::vec3(0.0f); - if (_settings._stiffness > 0.0f) { - glm::vec3 recoveryVector = _recoveryPosition - _currentPosition; - float recoveryFactor = powf(_settings._stiffness, 3.0f); - accelerationOffset = recoveryVector * recoveryFactor; - } - FlowNode::update(deltaTime, accelerationOffset); - if (_anchored) { - if (!_isHelper) { - _currentPosition = _updatedPosition; - } else { - _currentPosition = _parentPosition; + if (_settings._active) { + glm::vec3 accelerationOffset = glm::vec3(0.0f); + if (_settings._stiffness > 0.0f) { + glm::vec3 recoveryVector = _recoveryPosition - _currentPosition; + float recoveryFactor = powf(_settings._stiffness, 3.0f); + accelerationOffset = recoveryVector * recoveryFactor; + } + FlowNode::update(deltaTime, accelerationOffset); + if (_anchored) { + if (!_isHelper) { + _currentPosition = _updatedPosition; + } else { + _currentPosition = _parentPosition; + } } } }; @@ -674,6 +686,14 @@ bool Flow::updateRootFramePositions(const AnimPoseVec& absolutePoses, size_t thr return true; } +void Flow::updateCollisionJoint(FlowCollisionSphere& collision, AnimPoseVec& absolutePoses) { + glm::quat jointRotation; + getJointPositionInWorldFrame(absolutePoses, collision._jointIndex, collision._position, _entityPosition, _entityRotation); + getJointRotationInWorldFrame(absolutePoses, collision._jointIndex, jointRotation, _entityRotation); + glm::vec3 worldOffset = jointRotation * collision._offset; + collision._position = collision._position + worldOffset; +} + void Flow::updateJoints(AnimPoseVec& relativePoses, AnimPoseVec& absolutePoses) { updateAbsolutePoses(relativePoses, absolutePoses); for (auto &jointData : _flowJointData) { @@ -695,11 +715,11 @@ void Flow::updateJoints(AnimPoseVec& relativePoses, AnimPoseVec& absolutePoses) } auto &selfCollisions = _collisionSystem.getSelfCollisions(); for (auto &collision : selfCollisions) { - glm::quat jointRotation; - getJointPositionInWorldFrame(absolutePoses, collision._jointIndex, collision._position, _entityPosition, _entityRotation); - getJointRotationInWorldFrame(absolutePoses, collision._jointIndex, jointRotation, _entityRotation); - glm::vec3 worldOffset = jointRotation * collision._offset; - collision._position = collision._position + worldOffset; + updateCollisionJoint(collision, absolutePoses); + } + auto &selfTouchCollisions = _collisionSystem.getSelfTouchCollisions(); + for (auto &collision : selfTouchCollisions) { + updateCollisionJoint(collision, absolutePoses); } _collisionSystem.prepareCollisions(); } @@ -710,7 +730,7 @@ void Flow::setJoints(AnimPoseVec& relativePoses, const std::vector<bool>& overri for (int jointIndex : joints) { auto &joint = _flowJointData[jointIndex]; if (jointIndex >= 0 && jointIndex < (int)relativePoses.size() && !overrideFlags[jointIndex]) { - relativePoses[jointIndex].rot() = joint.getCurrentRotation(); + relativePoses[jointIndex].rot() = joint.getSettings()._active ? joint.getCurrentRotation() : joint.getInitialRotation(); } } } diff --git a/libraries/animation/src/Flow.h b/libraries/animation/src/Flow.h index ad81c2be77..5dc1a3ba3e 100644 --- a/libraries/animation/src/Flow.h +++ b/libraries/animation/src/Flow.h @@ -140,6 +140,7 @@ public: std::vector<FlowCollisionResult> checkFlowThreadCollisions(FlowThread* flowThread); std::vector<FlowCollisionSphere>& getSelfCollisions() { return _selfCollisions; }; + std::vector<FlowCollisionSphere>& getSelfTouchCollisions() { return _selfTouchCollisions; }; void setOthersCollisions(const std::vector<FlowCollisionSphere>& othersCollisions) { _othersCollisions = othersCollisions; } void prepareCollisions(); void resetCollisions(); @@ -150,9 +151,11 @@ public: void setActive(bool active) { _active = active; } bool getActive() const { return _active; } const std::vector<FlowCollisionSphere>& getCollisions() const { return _selfCollisions; } + void clearSelfCollisions() { _selfCollisions.clear(); } protected: std::vector<FlowCollisionSphere> _selfCollisions; std::vector<FlowCollisionSphere> _othersCollisions; + std::vector<FlowCollisionSphere> _selfTouchCollisions; std::vector<FlowCollisionSphere> _allCollisions; float _scale { 1.0f }; bool _active { false }; @@ -210,7 +213,7 @@ public: bool isHelper() const { return _isHelper; } const FlowPhysicsSettings& getSettings() { return _settings; } - void setSettings(const FlowPhysicsSettings& settings) { _settings = settings; } + void setSettings(const FlowPhysicsSettings& settings) { _settings = settings; _initialRadius = _settings._radius; } const glm::vec3& getCurrentPosition() const { return _currentPosition; } int getIndex() const { return _index; } @@ -222,6 +225,7 @@ public: const glm::quat& getCurrentRotation() const { return _currentRotation; } const glm::vec3& getCurrentTranslation() const { return _initialTranslation; } const glm::vec3& getInitialPosition() const { return _initialPosition; } + const glm::quat& getInitialRotation() const { return _initialRotation; } bool isColliding() const { return _colliding; } protected: @@ -297,6 +301,7 @@ public: void setPhysicsSettingsForGroup(const QString& group, const FlowPhysicsSettings& settings); const std::map<QString, FlowPhysicsSettings>& getGroupSettings() const { return _groupSettings; } void cleanUp(); + void updateScale() { setScale(_scale); } signals: void onCleanup(); @@ -311,6 +316,7 @@ private: void setJoints(AnimPoseVec& relativePoses, const std::vector<bool>& overrideFlags); void updateJoints(AnimPoseVec& relativePoses, AnimPoseVec& absolutePoses); + void updateCollisionJoint(FlowCollisionSphere& collision, AnimPoseVec& absolutePoses); bool updateRootFramePositions(const AnimPoseVec& absolutePoses, size_t threadIndex); void updateGroupSettings(const QString& group, const FlowPhysicsSettings& settings); void setScale(float scale); diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 82ab067472..43e94d23e8 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1422,7 +1422,8 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons _networkAnimState.blendTime += deltaTime; alpha = _computeNetworkAnimation ? (_networkAnimState.blendTime / TOTAL_BLEND_TIME) : (1.0f - (_networkAnimState.blendTime / TOTAL_BLEND_TIME)); alpha = glm::clamp(alpha, 0.0f, 1.0f); - for (size_t i = 0; i < _networkPoseSet._relativePoses.size(); i++) { + size_t numJoints = std::min(_networkPoseSet._relativePoses.size(), _internalPoseSet._relativePoses.size()); + for (size_t i = 0; i < numJoints; i++) { _networkPoseSet._relativePoses[i].blend(_internalPoseSet._relativePoses[i], alpha); } } diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 992ee5db96..839c4ed1d9 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -372,13 +372,6 @@ bool Avatar::applyGrabChanges() { target->removeGrab(grab); _avatarGrabs.erase(itr); grabAddedOrRemoved = true; - if (isMyAvatar()) { - const EntityItemPointer& entity = std::dynamic_pointer_cast<EntityItem>(target); - if (entity && entity->getEntityHostType() == entity::HostType::AVATAR && entity->getSimulationOwner().getID() == getID()) { - EntityItemProperties properties = entity->getProperties(); - sendPacket(entity->getID()); - } - } } else { undeleted.push_back(id); } diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index b4683d6032..974fae2034 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -180,7 +180,6 @@ public: /// Returns the distance to use as a LOD parameter. float getLODDistance() const; - virtual bool isMyAvatar() const override { return false; } virtual void createOrb() { } enum class LoadingStatus { diff --git a/libraries/avatars/src/AssociatedTraitValues.h b/libraries/avatars/src/AssociatedTraitValues.h index e3060a8097..0df8cd9bb5 100644 --- a/libraries/avatars/src/AssociatedTraitValues.h +++ b/libraries/avatars/src/AssociatedTraitValues.h @@ -28,9 +28,10 @@ namespace AvatarTraits { template<typename T, T defaultValue> class AssociatedTraitValues { + using SimpleTypesArray = std::array<T, NUM_SIMPLE_TRAITS>; public: // constructor that pre-fills _simpleTypes with the default value specified by the template - AssociatedTraitValues() : _simpleTypes(FirstInstancedTrait, defaultValue) {} + AssociatedTraitValues() { std::fill(_simpleTypes.begin(), _simpleTypes.end(), defaultValue); } /// inserts the given value for the given simple trait type void insert(TraitType type, T value) { _simpleTypes[type] = value; } @@ -71,12 +72,12 @@ namespace AvatarTraits { } /// const iterators for the vector of simple type values - typename std::vector<T>::const_iterator simpleCBegin() const { return _simpleTypes.cbegin(); } - typename std::vector<T>::const_iterator simpleCEnd() const { return _simpleTypes.cend(); } + typename SimpleTypesArray::const_iterator simpleCBegin() const { return _simpleTypes.cbegin(); } + typename SimpleTypesArray::const_iterator simpleCEnd() const { return _simpleTypes.cend(); } /// non-const iterators for the vector of simple type values - typename std::vector<T>::iterator simpleBegin() { return _simpleTypes.begin(); } - typename std::vector<T>::iterator simpleEnd() { return _simpleTypes.end(); } + typename SimpleTypesArray::iterator simpleBegin() { return _simpleTypes.begin(); } + typename SimpleTypesArray::iterator simpleEnd() { return _simpleTypes.end(); } struct TraitWithInstances { TraitType traitType; @@ -96,7 +97,7 @@ namespace AvatarTraits { typename std::vector<TraitWithInstances>::iterator instancedEnd() { return _instancedTypes.end(); } private: - std::vector<T> _simpleTypes; + SimpleTypesArray _simpleTypes; /// return the iterator to the matching TraitWithInstances object for a given instanced trait type typename std::vector<TraitWithInstances>::iterator instancesForTrait(TraitType traitType) { diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 39dfaa8a1a..a2b0b808ba 100755 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -1143,10 +1143,11 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { // we store the hand state as well as other items in a shared bitset. The hand state is an octal, but is split // into two sections to maintain backward compatibility. The bits are ordered as such (0-7 left to right). // AA 6/1/18 added three more flags bits 8,9, and 10 for procedural audio, blink, and eye saccade enabled - // +---+-----+-----+--+--+--+--+-----+ - // |x,x|H0,H1|x,x,x|H2|Au|Bl|Ey|xxxxx| - // +---+-----+-----+--+--+--+--+-----+ + // +---+-----+-----+--+--+--+--+--+----+ + // |x,x|H0,H1|x,x,x|H2|Au|Bl|Ey|He|xxxx| + // +---+-----+-----+--+--+--+--+--+----+ // Hand state - H0,H1,H2 is found in the 3rd, 4th, and 8th bits + // Hero-avatar status (He) - 12th bit auto newHandState = getSemiNibbleAt(bitItems, HAND_STATE_START_BIT) + (oneAtBit16(bitItems, HAND_STATE_FINGER_POINTING_BIT) ? IS_FINGER_POINTING_FLAG : 0); @@ -1990,42 +1991,16 @@ QUrl AvatarData::getWireSafeSkeletonModelURL() const { } } -qint64 AvatarData::packTrait(AvatarTraits::TraitType traitType, ExtendedIODevice& destination, - AvatarTraits::TraitVersion traitVersion) { - - qint64 bytesWritten = 0; - - if (traitType == AvatarTraits::SkeletonModelURL) { - - QByteArray encodedSkeletonURL = getWireSafeSkeletonModelURL().toEncoded(); - - if (encodedSkeletonURL.size() > AvatarTraits::MAXIMUM_TRAIT_SIZE) { - qWarning() << "Refusing to pack simple trait" << traitType << "of size" << encodedSkeletonURL.size() - << "bytes since it exceeds the maximum size" << AvatarTraits::MAXIMUM_TRAIT_SIZE << "bytes"; - return 0; - } - - bytesWritten += destination.writePrimitive(traitType); - - if (traitVersion > AvatarTraits::DEFAULT_TRAIT_VERSION) { - bytesWritten += destination.writePrimitive(traitVersion); - } - - AvatarTraits::TraitWireSize encodedURLSize = encodedSkeletonURL.size(); - bytesWritten += destination.writePrimitive(encodedURLSize); - - bytesWritten += destination.write(encodedSkeletonURL); - } - - return bytesWritten; +QByteArray AvatarData::packSkeletonModelURL() const { + return getWireSafeSkeletonModelURL().toEncoded(); } +void AvatarData::unpackSkeletonModelURL(const QByteArray& data) { + auto skeletonModelURL = QUrl::fromEncoded(data); + setSkeletonModelURL(skeletonModelURL); +} -qint64 AvatarData::packAvatarEntityTraitInstance(AvatarTraits::TraitType traitType, - AvatarTraits::TraitInstanceID traitInstanceID, - ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion) { - qint64 bytesWritten = 0; - +QByteArray AvatarData::packAvatarEntityTraitInstance(AvatarTraits::TraitInstanceID traitInstanceID) { // grab a read lock on the avatar entities and check for entity data for the given ID QByteArray entityBinaryData; _avatarEntitiesLock.withReadLock([this, &entityBinaryData, &traitInstanceID] { @@ -2034,104 +2009,48 @@ qint64 AvatarData::packAvatarEntityTraitInstance(AvatarTraits::TraitType traitTy } }); - if (entityBinaryData.size() > AvatarTraits::MAXIMUM_TRAIT_SIZE) { - qWarning() << "Refusing to pack instanced trait" << traitType << "of size" << entityBinaryData.size() - << "bytes since it exceeds the maximum size " << AvatarTraits::MAXIMUM_TRAIT_SIZE << "bytes"; - return 0; - } - - bytesWritten += destination.writePrimitive(traitType); - - if (traitVersion > AvatarTraits::DEFAULT_TRAIT_VERSION) { - bytesWritten += destination.writePrimitive(traitVersion); - } - - bytesWritten += destination.write(traitInstanceID.toRfc4122()); - - if (!entityBinaryData.isNull()) { - AvatarTraits::TraitWireSize entityBinarySize = entityBinaryData.size(); - - bytesWritten += destination.writePrimitive(entityBinarySize); - bytesWritten += destination.write(entityBinaryData); - } else { - bytesWritten += destination.writePrimitive(AvatarTraits::DELETED_TRAIT_SIZE); - } - - return bytesWritten; + return entityBinaryData; } - -qint64 AvatarData::packGrabTraitInstance(AvatarTraits::TraitType traitType, - AvatarTraits::TraitInstanceID traitInstanceID, - ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion) { - qint64 bytesWritten = 0; - +QByteArray AvatarData::packGrabTraitInstance(AvatarTraits::TraitInstanceID traitInstanceID) { // grab a read lock on the avatar grabs and check for grab data for the given ID QByteArray grabBinaryData; - _avatarGrabsLock.withReadLock([this, &grabBinaryData, &traitInstanceID] { if (_avatarGrabData.contains(traitInstanceID)) { grabBinaryData = _avatarGrabData[traitInstanceID]; } }); - if (grabBinaryData.size() > AvatarTraits::MAXIMUM_TRAIT_SIZE) { - qWarning() << "Refusing to pack instanced trait" << traitType << "of size" << grabBinaryData.size() - << "bytes since it exceeds the maximum size " << AvatarTraits::MAXIMUM_TRAIT_SIZE << "bytes"; - return 0; - } - - bytesWritten += destination.writePrimitive(traitType); - - if (traitVersion > AvatarTraits::DEFAULT_TRAIT_VERSION) { - bytesWritten += destination.writePrimitive(traitVersion); - } - - bytesWritten += destination.write(traitInstanceID.toRfc4122()); - - if (!grabBinaryData.isNull()) { - AvatarTraits::TraitWireSize grabBinarySize = grabBinaryData.size(); - - bytesWritten += destination.writePrimitive(grabBinarySize); - bytesWritten += destination.write(grabBinaryData); - } else { - bytesWritten += destination.writePrimitive(AvatarTraits::DELETED_TRAIT_SIZE); - } - - return bytesWritten; + return grabBinaryData; } -qint64 AvatarData::packTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID traitInstanceID, - ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion) { - qint64 bytesWritten = 0; +QByteArray AvatarData::packTrait(AvatarTraits::TraitType traitType) const { + QByteArray traitBinaryData; + // Call packer function + if (traitType == AvatarTraits::SkeletonModelURL) { + traitBinaryData = packSkeletonModelURL(); + } + + return traitBinaryData; +} + +QByteArray AvatarData::packTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID traitInstanceID) { + QByteArray traitBinaryData; + + // Call packer function if (traitType == AvatarTraits::AvatarEntity) { - bytesWritten += packAvatarEntityTraitInstance(traitType, traitInstanceID, destination, traitVersion); + traitBinaryData = packAvatarEntityTraitInstance(traitInstanceID); } else if (traitType == AvatarTraits::Grab) { - bytesWritten += packGrabTraitInstance(traitType, traitInstanceID, destination, traitVersion); + traitBinaryData = packGrabTraitInstance(traitInstanceID); } - return bytesWritten; -} - -void AvatarData::prepareResetTraitInstances() { - if (_clientTraitsHandler) { - _avatarEntitiesLock.withReadLock([this]{ - foreach (auto entityID, _packedAvatarEntityData.keys()) { - _clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::AvatarEntity, entityID); - } - foreach (auto grabID, _avatarGrabData.keys()) { - _clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::Grab, grabID); - } - }); - } + return traitBinaryData; } void AvatarData::processTrait(AvatarTraits::TraitType traitType, QByteArray traitBinaryData) { if (traitType == AvatarTraits::SkeletonModelURL) { - // get the URL from the binary data - auto skeletonModelURL = QUrl::fromEncoded(traitBinaryData); - setSkeletonModelURL(skeletonModelURL); + unpackSkeletonModelURL(traitBinaryData); } } @@ -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 identityData; QDataStream identityStream(&identityData, QIODevice::Append); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 795939ea2d..1c4b0cfc53 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -479,6 +479,7 @@ class AvatarData : public QObject, public SpatiallyNestable { * avatar. <em>Read-only.</em> * @property {number} sensorToWorldScale - The scale that transforms dimensions in the user's real world to the avatar's * size in the virtual world. <em>Read-only.</em> + * @property {boolean} hasPriority - is the avatar in a Hero zone? <em>Read-only.</em> */ Q_PROPERTY(glm::vec3 position READ getWorldPosition WRITE setPositionViaScript) 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(bool hasPriority READ getHasPriority) + public: 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. void processAvatarIdentity(QDataStream& packetStream, bool& identityChanged, bool& displayNameChanged); - qint64 packTrait(AvatarTraits::TraitType traitType, ExtendedIODevice& destination, - AvatarTraits::TraitVersion traitVersion = AvatarTraits::NULL_TRAIT_VERSION); - qint64 packTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID, - ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion = AvatarTraits::NULL_TRAIT_VERSION); - - void prepareResetTraitInstances(); + QByteArray packTrait(AvatarTraits::TraitType traitType) const; + QByteArray packTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID); void processTrait(AvatarTraits::TraitType traitType, QByteArray traitBinaryData); void processTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID, QByteArray traitBinaryData); void processDeletedTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID); + void prepareResetTraitInstances(); + QByteArray identityByteArray(bool setIsReplicated = false) const; QUrl getWireSafeSkeletonModelURL() const; @@ -1596,13 +1597,13 @@ protected: bool hasParent() const { return !getParentID().isNull(); } bool hasFaceTracker() const { return _headData ? _headData->_isFaceTrackerConnected : false; } - qint64 packAvatarEntityTraitInstance(AvatarTraits::TraitType traitType, - AvatarTraits::TraitInstanceID traitInstanceID, - ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion); - qint64 packGrabTraitInstance(AvatarTraits::TraitType traitType, - AvatarTraits::TraitInstanceID traitInstanceID, - ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion); + QByteArray packSkeletonModelURL() const; + QByteArray packAvatarEntityTraitInstance(AvatarTraits::TraitInstanceID traitInstanceID); + QByteArray packGrabTraitInstance(AvatarTraits::TraitInstanceID traitInstanceID); + void unpackSkeletonModelURL(const QByteArray& data); + + // 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. bool _isReplicated{ false }; diff --git a/libraries/avatars/src/AvatarTraits.cpp b/libraries/avatars/src/AvatarTraits.cpp new file mode 100644 index 0000000000..724f30e2f3 --- /dev/null +++ b/libraries/avatars/src/AvatarTraits.cpp @@ -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; + } +}; diff --git a/libraries/avatars/src/AvatarTraits.h b/libraries/avatars/src/AvatarTraits.h index 4516572e42..13d64ec225 100644 --- a/libraries/avatars/src/AvatarTraits.h +++ b/libraries/avatars/src/AvatarTraits.h @@ -14,20 +14,35 @@ #include <algorithm> #include <cstdint> +#include <array> #include <vector> #include <QtCore/QUuid> +class ExtendedIODevice; +class AvatarData; + namespace AvatarTraits { enum TraitType : int8_t { + // Null trait NullTrait = -1, - SkeletonModelURL, + + // Simple traits + SkeletonModelURL = 0, + + // Instanced traits FirstInstancedTrait, AvatarEntity = FirstInstancedTrait, Grab, + + // Traits count TotalTraitTypes }; + const int NUM_SIMPLE_TRAITS = (int)FirstInstancedTrait; + const int NUM_INSTANCED_TRAITS = (int)TotalTraitTypes - (int)FirstInstancedTrait; + const int NUM_TRAITS = (int)TotalTraitTypes; + using TraitInstanceID = QUuid; inline bool isSimpleTrait(TraitType traitType) { @@ -46,22 +61,19 @@ namespace AvatarTraits { const TraitMessageSequence FIRST_TRAIT_SEQUENCE = 0; const TraitMessageSequence MAX_TRAIT_SEQUENCE = INT64_MAX; - inline qint64 packInstancedTraitDelete(TraitType traitType, TraitInstanceID instanceID, ExtendedIODevice& destination, - TraitVersion traitVersion = NULL_TRAIT_VERSION) { - qint64 bytesWritten = 0; + qint64 packTrait(TraitType traitType, ExtendedIODevice& destination, const AvatarData& avatar); + qint64 packVersionedTrait(TraitType traitType, ExtendedIODevice& destination, + TraitVersion traitVersion, const AvatarData& avatar); - bytesWritten += destination.writePrimitive(traitType); + qint64 packTraitInstance(TraitType traitType, TraitInstanceID traitInstanceID, + ExtendedIODevice& destination, AvatarData& avatar); + qint64 packVersionedTraitInstance(TraitType traitType, TraitInstanceID traitInstanceID, + ExtendedIODevice& destination, TraitVersion traitVersion, + AvatarData& avatar); - if (traitVersion > DEFAULT_TRAIT_VERSION) { - bytesWritten += destination.writePrimitive(traitVersion); - } + qint64 packInstancedTraitDelete(TraitType traitType, TraitInstanceID instanceID, ExtendedIODevice& destination, + TraitVersion traitVersion = NULL_TRAIT_VERSION); - bytesWritten += destination.write(instanceID.toRfc4122()); - - bytesWritten += destination.writePrimitive(DELETED_TRAIT_SIZE); - - return bytesWritten; - } }; #endif // hifi_AvatarTraits_h diff --git a/libraries/avatars/src/ClientTraitsHandler.cpp b/libraries/avatars/src/ClientTraitsHandler.cpp index bcbe5308c7..f6bd66e89a 100644 --- a/libraries/avatars/src/ClientTraitsHandler.cpp +++ b/libraries/avatars/src/ClientTraitsHandler.cpp @@ -106,9 +106,10 @@ int ClientTraitsHandler::sendChangedTraitsToMixer() { auto traitType = static_cast<AvatarTraits::TraitType>(std::distance(traitStatusesCopy.simpleCBegin(), simpleIt)); if (initialSend || *simpleIt == Updated) { - if (traitType == AvatarTraits::SkeletonModelURL) { - bytesWritten += _owningAvatar->packTrait(traitType, *traitsPacketList); + bytesWritten += AvatarTraits::packTrait(traitType, *traitsPacketList, *_owningAvatar); + + if (traitType == AvatarTraits::SkeletonModelURL) { // keep track of our skeleton version in case we get an override back _currentSkeletonVersion = _currentTraitVersion; } @@ -124,7 +125,9 @@ int ClientTraitsHandler::sendChangedTraitsToMixer() { || instanceIDValuePair.value == Updated) { // this is a changed trait we need to send or we haven't send out trait information yet // ask the owning avatar to pack it - bytesWritten += _owningAvatar->packTraitInstance(instancedIt->traitType, instanceIDValuePair.id, *traitsPacketList); + bytesWritten += AvatarTraits::packTraitInstance(instancedIt->traitType, instanceIDValuePair.id, + *traitsPacketList, *_owningAvatar); + } else if (!initialSend && instanceIDValuePair.value == Deleted) { // pack delete for this trait instance bytesWritten += AvatarTraits::packInstancedTraitDelete(instancedIt->traitType, instanceIDValuePair.id, @@ -162,11 +165,11 @@ void ClientTraitsHandler::processTraitOverride(QSharedPointer<ReceivedMessage> m // override the skeleton URL but do not mark the trait as having changed // so that we don't unecessarily send a new trait packet to the mixer with the overriden URL - auto encodedSkeletonURL = QUrl::fromEncoded(message->readWithoutCopy(traitBinarySize)); auto hasChangesBefore = _hasChangedTraits; - _owningAvatar->setSkeletonModelURL(encodedSkeletonURL); + auto traitBinaryData = message->readWithoutCopy(traitBinarySize); + _owningAvatar->processTrait(traitType, traitBinaryData); // setSkeletonModelURL will flag us for changes to the SkeletonModelURL so we reset some state here to // avoid unnecessarily sending the overriden skeleton model URL back to the mixer diff --git a/libraries/avatars/src/ScriptAvatarData.cpp b/libraries/avatars/src/ScriptAvatarData.cpp index a716a40ad8..18717c8ca3 100644 --- a/libraries/avatars/src/ScriptAvatarData.cpp +++ b/libraries/avatars/src/ScriptAvatarData.cpp @@ -343,6 +343,14 @@ glm::mat4 ScriptAvatarData::getControllerRightHandMatrix() const { // END // +bool ScriptAvatarData::getHasPriority() const { + if (AvatarSharedPointer sharedAvatarData = _avatarData.lock()) { + return sharedAvatarData->getHasPriority(); + } else { + return false; + } +} + glm::quat ScriptAvatarData::getAbsoluteJointRotationInObjectFrame(int index) const { if (AvatarSharedPointer sharedAvatarData = _avatarData.lock()) { return sharedAvatarData->getAbsoluteJointRotationInObjectFrame(index); diff --git a/libraries/avatars/src/ScriptAvatarData.h b/libraries/avatars/src/ScriptAvatarData.h index 91bac61728..01f7ff360a 100644 --- a/libraries/avatars/src/ScriptAvatarData.h +++ b/libraries/avatars/src/ScriptAvatarData.h @@ -68,6 +68,8 @@ class ScriptAvatarData : public QObject { Q_PROPERTY(glm::mat4 controllerLeftHandMatrix READ getControllerLeftHandMatrix) Q_PROPERTY(glm::mat4 controllerRightHandMatrix READ getControllerRightHandMatrix) + Q_PROPERTY(bool hasPriority READ getHasPriority) + public: ScriptAvatarData(AvatarSharedPointer avatarData); @@ -133,6 +135,8 @@ public: glm::mat4 getControllerLeftHandMatrix() const; glm::mat4 getControllerRightHandMatrix() const; + bool getHasPriority() const; + signals: void displayNameChanged(); void sessionDisplayNameChanged(); diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index 33f4f2d751..d859d4b739 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -265,7 +265,7 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) { if (_procedural.isReady()) { outColor = _procedural.getColor(outColor); 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; } }); diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index 631148c27a..8a7fa3f8e7 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -33,7 +33,7 @@ using namespace render::entities; ZoneEntityRenderer::ZoneEntityRenderer(const EntityItemPointer& entity) : Parent(entity) { - _background->setSkybox(std::make_shared<ProceduralSkybox>()); + _background->setSkybox(std::make_shared<ProceduralSkybox>(entity->getCreated())); } void ZoneEntityRenderer::onRemoveFromSceneTyped(const TypedEntityPointer& entity) { diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index f0bf13891b..bd4c6e5c71 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -789,8 +789,10 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef auto lastEdited = lastEditedFromBufferAdjusted; bool otherOverwrites = overwriteLocalData && !weOwnSimulation; - auto shouldUpdate = [this, lastEdited, otherOverwrites, filterRejection](quint64 updatedTimestamp, bool valueChanged) { - if (stillHasGrabActions()) { + // calculate hasGrab once outside the lambda rather than calling it every time inside + bool hasGrab = stillHasGrabAction(); + auto shouldUpdate = [this, lastEdited, otherOverwrites, filterRejection, hasGrab](quint64 updatedTimestamp, bool valueChanged) { + if (hasGrab) { return false; } 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. // - 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 // 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 // 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. + // + // 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); // 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) { 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; } if (newPriority != _scriptSimulationPriority) { @@ -1439,7 +1447,7 @@ void EntityItem::upgradeScriptSimulationPriority(uint8_t priority) { void EntityItem::clearScriptSimulationPriority() { // 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. - _scriptSimulationPriority = stillHasGrabActions() ? SCRIPT_GRAB_SIMULATION_PRIORITY : 0; + _scriptSimulationPriority = stillHasMyGrabAction() ? SCRIPT_GRAB_SIMULATION_PRIORITY : 0; } void EntityItem::setPendingOwnershipPriority(uint8_t priority) { @@ -2186,7 +2194,7 @@ void EntityItem::enableNoBootstrap() { } void EntityItem::disableNoBootstrap() { - if (!stillHasGrabActions()) { + if (!stillHasMyGrabAction()) { _flags &= ~Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING; _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; } -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>::const_iterator i = holdActions.begin(); 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() { withWriteLock([&] { _changedOnServer = usecTimestampNow(); @@ -3479,6 +3479,9 @@ void EntityItem::addGrab(GrabPointer grab) { simulation->addDynamic(action); markDirtyFlags(Simulation::DIRTY_MOTION_TYPE); simulation->changeEntity(getThisPointer()); + + // don't forget to set isMine() for locally-created grabs + action->setIsMine(grab->getOwnerID() == Physics::getSessionUUID()); } } diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index fae871a124..01ed949a0c 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -124,8 +124,8 @@ public: { return (float)(usecTimestampNow() - getLastEdited()) / (float)USECS_PER_SECOND; } /// Last time we sent out an edit packet for this entity - quint64 getLastBroadcast() const; - void setLastBroadcast(quint64 lastBroadcast); + quint64 getLastBroadcast() const { return _lastBroadcast; } + void setLastBroadcast(quint64 lastBroadcast) { _lastBroadcast = lastBroadcast; } void markAsChangedOnServer(); quint64 getLastChangedOnServer() const; @@ -562,6 +562,8 @@ public: static void setPrimaryViewFrustumPositionOperator(std::function<glm::vec3()> getPrimaryViewFrustumPositionOperator) { _getPrimaryViewFrustumPositionOperator = getPrimaryViewFrustumPositionOperator; } static glm::vec3 getPrimaryViewFrustumPosition() { return _getPrimaryViewFrustumPositionOperator(); } + bool stillHasMyGrabAction() const; + signals: void requestRenderUpdate(); void spaceUpdate(std::pair<int32_t, glm::vec4> data); @@ -574,7 +576,7 @@ protected: void setSimulated(bool simulated) { _simulated = simulated; } const QByteArray getDynamicDataInternal() const; - bool stillHasGrabActions() const; + bool stillHasGrabAction() const; void setDynamicDataInternal(QByteArray dynamicData); virtual void dimensionsChanged() override; diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index aa4b3902c2..ca914731b5 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -1101,13 +1101,13 @@ void EntityScriptingInterface::handleEntityScriptCallMethodPacket(QSharedPointer void EntityScriptingInterface::onAddingEntity(EntityItem* entity) { if (entity->isWearable()) { - emit addingWearable(entity->getEntityItemID()); + QMetaObject::invokeMethod(this, "addingWearable", Q_ARG(QUuid, entity->getEntityItemID())); } } void EntityScriptingInterface::onDeletingEntity(EntityItem* entity) { if (entity->isWearable()) { - emit deletingWearable(entity->getEntityItemID()); + QMetaObject::invokeMethod(this, "deletingWearable", Q_ARG(QUuid, entity->getEntityItemID())); } } diff --git a/libraries/fbx/src/FBXSerializer.cpp b/libraries/fbx/src/FBXSerializer.cpp index 5c5b5fa002..aed313f54e 100644 --- a/libraries/fbx/src/FBXSerializer.cpp +++ b/libraries/fbx/src/FBXSerializer.cpp @@ -443,6 +443,7 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const QString hifiGlobalNodeID; unsigned int meshIndex = 0; haveReportedUnhandledRotationOrder = false; + int fbxVersionNumber = -1; foreach (const FBXNode& child, node.children) { if (child.name == "FBXHeaderExtension") { @@ -465,6 +466,8 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const } } } + } else if (object.name == "FBXVersion") { + fbxVersionNumber = object.properties.at(0).toInt(); } } } else if (child.name == "GlobalSettings") { @@ -1161,8 +1164,14 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const counter++; } } - _connectionParentMap.insert(getID(connection.properties, 1), getID(connection.properties, 2)); - _connectionChildMap.insert(getID(connection.properties, 2), getID(connection.properties, 1)); + if (_connectionParentMap.value(getID(connection.properties, 1)) == "0") { + // 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)); + } } } } @@ -1311,8 +1320,6 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const joint.bindTransformFoundInCluster = false; - hfmModel.joints.append(joint); - QString rotationID = localRotations.value(modelID); AnimationCurve xRotCurve = animationCurves.value(xComponents.value(rotationID)); AnimationCurve yRotCurve = animationCurves.value(yComponents.value(rotationID)); @@ -1335,7 +1342,13 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const xPosCurve.values.isEmpty() ? defaultPosValues.x : xPosCurve.values.at(i % xPosCurve.values.size()), yPosCurve.values.isEmpty() ? defaultPosValues.y : yPosCurve.values.at(i % yPosCurve.values.size()), zPosCurve.values.isEmpty() ? defaultPosValues.z : zPosCurve.values.at(i % zPosCurve.values.size())); + if ((fbxVersionNumber < 7500) && (i == 0)) { + joint.translation = hfmModel.animationFrames[i].translations[jointIndex]; + joint.rotation = hfmModel.animationFrames[i].rotations[jointIndex]; + } + } + hfmModel.joints.append(joint); } // NOTE: shapeVertices are in joint-frame diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 4647c50496..226433e388 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -536,7 +536,7 @@ void AccountManager::requestAccessToken(const QString& login, const QString& pas QByteArray postData; postData.append("grant_type=password&"); - postData.append("username=" + login + "&"); + postData.append("username=" + QUrl::toPercentEncoding(login) + "&"); postData.append("password=" + QUrl::toPercentEncoding(password) + "&"); postData.append("scope=" + ACCOUNT_MANAGER_REQUESTED_SCOPE); diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index a9dbc12b09..18a180ad79 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -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 removeOldNode(findNodeWithAddr(publicSocket)); 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); if (it == _connectionIDs.end()) { diff --git a/libraries/octree/src/OctreePersistThread.cpp b/libraries/octree/src/OctreePersistThread.cpp index 32ee72ea1c..20ba3cde60 100644 --- a/libraries/octree/src/OctreePersistThread.cpp +++ b/libraries/octree/src/OctreePersistThread.cpp @@ -82,11 +82,11 @@ void OctreePersistThread::start() { } 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); auto id = data.id.toRfc4122(); packet->write(id); - packet->writePrimitive(data.version); + packet->writePrimitive(data.dataVersion); } else { _cachedJSONData.clear(); qCWarning(octree) << "No octree data found"; @@ -144,8 +144,8 @@ void OctreePersistThread::handleOctreeDataFileReply(QSharedPointer<ReceivedMessa quint64 loadStarted = usecTimestampNow(); if (hasValidOctreeData) { - qDebug() << "Setting entity version info to: " << data.id << data.version; - _tree->setOctreeVersionInfo(data.id, data.version); + qDebug() << "Setting entity version info to: " << data.id << data.dataVersion; + _tree->setOctreeVersionInfo(data.id, data.dataVersion); } bool persistentFileRead; diff --git a/libraries/procedural/src/procedural/Procedural.cpp b/libraries/procedural/src/procedural/Procedural.cpp index ff8c270371..9940da0b9a 100644 --- a/libraries/procedural/src/procedural/Procedural.cpp +++ b/libraries/procedural/src/procedural/Procedural.cpp @@ -225,13 +225,15 @@ void Procedural::prepare(gpu::Batch& batch, const glm::vec3& position, const glm::vec3& size, const glm::quat& orientation, + const uint64_t& created, const ProceduralProgramKey key) { std::lock_guard<std::mutex> lock(_mutex); _entityDimensions = size; _entityPosition = position; _entityOrientation = glm::mat3_cast(orientation); + _entityCreated = created; if (!_shaderPath.isEmpty()) { - auto lastModified = (quint64)QFileInfo(_shaderPath).lastModified().toMSecsSinceEpoch(); + auto lastModified = (uint64_t)QFileInfo(_shaderPath).lastModified().toMSecsSinceEpoch(); if (lastModified > _shaderModified) { QFile file(_shaderPath); file.open(QIODevice::ReadOnly); @@ -278,7 +280,10 @@ void Procedural::prepare(gpu::Batch& batch, _proceduralPipelines[key] = gpu::Pipeline::create(program, key.isTransparent() ? _transparentState : _opaqueState); - _start = usecTimestampNow(); + _lastCompile = usecTimestampNow(); + if (_firstCompile == 0) { + _firstCompile = _lastCompile; + } _frameCount = 0; recompiledShader = true; } @@ -371,7 +376,11 @@ void Procedural::setupUniforms() { _uniforms.push_back([=](gpu::Batch& batch) { _standardInputs.position = vec4(_entityPosition, 1.0f); // 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 { diff --git a/libraries/procedural/src/procedural/Procedural.h b/libraries/procedural/src/procedural/Procedural.h index 8477e69afc..956cef368f 100644 --- a/libraries/procedural/src/procedural/Procedural.h +++ b/libraries/procedural/src/procedural/Procedural.h @@ -82,10 +82,11 @@ public: bool isReady() const; 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; - quint64 getFadeStartTime() const { return _fadeStartTime; } + uint64_t getFadeStartTime() const { return _fadeStartTime; } bool isFading() const { return _doesFade && _isFading; } void setIsFading(bool isFading) { _isFading = isFading; } void setDoesFade(bool doesFade) { _doesFade = doesFade; } @@ -106,9 +107,10 @@ protected: vec4 date; vec4 position; vec4 scale; - float time; + float timeSinceLastCompile; + float timeSinceFirstCompile; + float timeSinceEntityCreation; int frameCount; - vec2 _spare1; vec4 resolution[4]; mat4 orientation; }; @@ -116,9 +118,10 @@ protected: static_assert(0 == offsetof(StandardInputs, date), "ProceduralOffsets"); static_assert(16 == offsetof(StandardInputs, position), "ProceduralOffsets"); static_assert(32 == offsetof(StandardInputs, scale), "ProceduralOffsets"); - static_assert(48 == offsetof(StandardInputs, time), "ProceduralOffsets"); - static_assert(52 == offsetof(StandardInputs, frameCount), "ProceduralOffsets"); - static_assert(56 == offsetof(StandardInputs, _spare1), "ProceduralOffsets"); + static_assert(48 == offsetof(StandardInputs, timeSinceLastCompile), "ProceduralOffsets"); + static_assert(52 == offsetof(StandardInputs, timeSinceFirstCompile), "ProceduralOffsets"); + static_assert(56 == offsetof(StandardInputs, timeSinceEntityCreation), "ProceduralOffsets"); + static_assert(60 == offsetof(StandardInputs, frameCount), "ProceduralOffsets"); static_assert(64 == offsetof(StandardInputs, resolution), "ProceduralOffsets"); static_assert(128 == offsetof(StandardInputs, orientation), "ProceduralOffsets"); @@ -126,13 +129,14 @@ protected: ProceduralData _data; bool _enabled { false }; - uint64_t _start { 0 }; + uint64_t _lastCompile { 0 }; + uint64_t _firstCompile { 0 }; int32_t _frameCount { 0 }; // Rendering object descriptions, from userData QString _shaderSource; QString _shaderPath; - quint64 _shaderModified { 0 }; + uint64_t _shaderModified { 0 }; NetworkShaderPointer _networkShader; bool _shaderDirty { true }; bool _uniformsDirty { true }; @@ -152,11 +156,12 @@ protected: glm::vec3 _entityDimensions; glm::vec3 _entityPosition; glm::mat3 _entityOrientation; + uint64_t _entityCreated; private: void setupUniforms(); - mutable quint64 _fadeStartTime { 0 }; + mutable uint64_t _fadeStartTime { 0 }; mutable bool _hasStartedFade { false }; mutable bool _isFading { false }; bool _doesFade { true }; diff --git a/libraries/procedural/src/procedural/ProceduralCommon.slh b/libraries/procedural/src/procedural/ProceduralCommon.slh index bd894a9e92..6e73534440 100644 --- a/libraries/procedural/src/procedural/ProceduralCommon.slh +++ b/libraries/procedural/src/procedural/ProceduralCommon.slh @@ -36,9 +36,11 @@ LAYOUT_STD140(binding=0) uniform standardInputsBuffer { // Offset 48 float globalTime; // Offset 52 - int frameCount; + float localCreatedTime; // Offset 56 - vec2 _spare1; + float entityTime; + // Offset 60 + int frameCount; // Offset 64, acts as vec4[4] for alignment purposes vec3 channelResolution[4]; // Offset 128, acts as vec4[3] for alignment purposes @@ -52,6 +54,8 @@ LAYOUT_STD140(binding=0) uniform standardInputsBuffer { #define iWorldPosition standardInputs.worldPosition #define iWorldScale standardInputs.worldScale #define iGlobalTime standardInputs.globalTime +#define iLocalCreatedTime standardInputs.localCreatedTime +#define iEntityTime standardInputs.entityTime #define iFrameCount standardInputs.frameCount #define iChannelResolution standardInputs.channelResolution #define iWorldOrientation standardInputs.worldOrientation diff --git a/libraries/procedural/src/procedural/ProceduralSkybox.cpp b/libraries/procedural/src/procedural/ProceduralSkybox.cpp index 6eb6d531e1..53df1532dc 100644 --- a/libraries/procedural/src/procedural/ProceduralSkybox.cpp +++ b/libraries/procedural/src/procedural/ProceduralSkybox.cpp @@ -17,7 +17,7 @@ #include <ViewFrustum.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._opaqueFragmentSource = shader::Source::get(shader::procedural::fragment::proceduralSkybox); // 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 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); batch.draw(gpu::TRIANGLE_STRIP, 4); } diff --git a/libraries/procedural/src/procedural/ProceduralSkybox.h b/libraries/procedural/src/procedural/ProceduralSkybox.h index 5db1078a5f..a1d7ea8fa7 100644 --- a/libraries/procedural/src/procedural/ProceduralSkybox.h +++ b/libraries/procedural/src/procedural/ProceduralSkybox.h @@ -19,7 +19,7 @@ class ProceduralSkybox: public graphics::Skybox { public: - ProceduralSkybox(); + ProceduralSkybox(uint64_t created = 0); 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; static void render(gpu::Batch& batch, const ViewFrustum& frustum, const ProceduralSkybox& skybox); + uint64_t getCreated() const { return _created; } + protected: mutable Procedural _procedural; + uint64_t _created; }; typedef std::shared_ptr< ProceduralSkybox > ProceduralSkyboxPointer; diff --git a/scripts/modules/appUi.js b/scripts/modules/appUi.js index 3e8e0b1008..9771348377 100644 --- a/scripts/modules/appUi.js +++ b/scripts/modules/appUi.js @@ -353,10 +353,11 @@ function AppUi(properties) { // Close if necessary, clean up any remaining handlers, and remove the button. GlobalServices.myUsernameChanged.disconnect(restartNotificationPoll); GlobalServices.findableByChanged.disconnect(restartNotificationPoll); + that.tablet.screenChanged.disconnect(that.onScreenChanged); if (that.isOpen) { that.close(); + that.onScreenChanged("", ""); } - that.tablet.screenChanged.disconnect(that.onScreenChanged); if (that.button) { if (that.onClicked) { that.button.clicked.disconnect(that.onClicked); diff --git a/scripts/system/avatarapp.js b/scripts/system/avatarapp.js index fb61b914a3..6439d30023 100644 --- a/scripts/system/avatarapp.js +++ b/scripts/system/avatarapp.js @@ -1,6 +1,8 @@ "use strict"; /*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 }] */ // // avatarapp.js @@ -14,7 +16,6 @@ (function() { // BEGIN LOCAL_SCOPE -var request = Script.require('request').request; var AVATARAPP_QML_SOURCE = "hifi/AvatarApp.qml"; Script.include("/~/system/libraries/controllers.js"); @@ -22,7 +23,6 @@ Script.include("/~/system/libraries/controllers.js"); var ENTRY_AVATAR_URL = "avatarUrl"; var ENTRY_AVATAR_ENTITIES = "avatarEntites"; var ENTRY_AVATAR_SCALE = "avatarScale"; -var ENTRY_VERSION = "version"; function executeLater(callback) { Script.setTimeout(callback, 300); @@ -44,7 +44,7 @@ function getMyAvatarWearables() { } var localRotation = entity.properties.localRotation; - entity.properties.localRotationAngles = Quat.safeEulerAngles(localRotation) + entity.properties.localRotationAngles = Quat.safeEulerAngles(localRotation); wearablesArray.push(entity); } @@ -52,7 +52,7 @@ function getMyAvatarWearables() { } function getMyAvatar() { - var avatar = {} + var avatar = {}; avatar[ENTRY_AVATAR_URL] = MyAvatar.skeletonModelURL; avatar[ENTRY_AVATAR_SCALE] = MyAvatar.getAvatarScale(); avatar[ENTRY_AVATAR_ENTITIES] = getMyAvatarWearables(); @@ -68,7 +68,7 @@ function getMyAvatarSettings() { collisionSoundUrl : MyAvatar.collisionSoundURL, animGraphUrl: MyAvatar.getAnimGraphUrl(), animGraphOverrideUrl : MyAvatar.getAnimGraphOverrideUrl(), - } + }; } function updateAvatarWearables(avatar, callback, wearablesOverride) { @@ -76,7 +76,8 @@ function updateAvatarWearables(avatar, callback, wearablesOverride) { var wearables = wearablesOverride ? wearablesOverride : getMyAvatarWearables(); avatar[ENTRY_AVATAR_ENTITIES] = wearables; - sendToQml({'method' : 'wearablesUpdated', 'wearables' : wearables}) + sendToQml({'method' : 'wearablesUpdated', 'wearables' : wearables}); + sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()}); if(callback) callback(); @@ -101,7 +102,7 @@ var adjustWearables = { this.opened = value; } } -} +}; var currentAvatarWearablesBackup = null; var currentAvatar = null; @@ -112,7 +113,7 @@ function onTargetScaleChanged() { if(currentAvatar.scale !== MyAvatar.getAvatarScale()) { currentAvatar.scale = MyAvatar.getAvatarScale(); if(notifyScaleChanged) { - sendToQml({'method' : 'scaleChanged', 'value' : currentAvatar.scale}) + sendToQml({'method' : 'scaleChanged', 'value' : currentAvatar.scale}); } } } @@ -126,7 +127,7 @@ function onSkeletonModelURLChanged() { function onDominantHandChanged(dominantHand) { if(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) { if(currentAvatarSettings.collisionsEnabled !== enabled) { currentAvatarSettings.collisionsEnabled = enabled; - sendToQml({'method' : 'settingChanged', 'name' : 'collisionsEnabled', 'value' : enabled}) + sendToQml({'method' : 'settingChanged', 'name' : 'collisionsEnabled', 'value' : enabled}); } } function onOtherAvatarsCollisionsEnabledChanged(enabled) { if (currentAvatarSettings.otherAvatarsCollisionsEnabled !== enabled) { currentAvatarSettings.otherAvatarsCollisionsEnabled = enabled; - sendToQml({ 'method': 'settingChanged', 'name': 'otherAvatarsCollisionsEnabled', 'value': enabled }) + sendToQml({ 'method': 'settingChanged', 'name': 'otherAvatarsCollisionsEnabled', 'value': enabled }); } } function onNewCollisionSoundUrl(url) { if(currentAvatarSettings.collisionSoundUrl !== url) { currentAvatarSettings.collisionSoundUrl = url; - sendToQml({'method' : 'settingChanged', 'name' : 'collisionSoundUrl', 'value' : url}) + sendToQml({'method' : 'settingChanged', 'name' : 'collisionSoundUrl', 'value' : url}); } } function onAnimGraphUrlChanged(url) { if (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()) { 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 grabbedAvatarEntityChangeNotifier = null; @@ -178,6 +179,33 @@ var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/wallet/Wallet.qml"; var MARKETPLACE_URL = Account.metaverseServerURL + "/marketplace"; 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. switch (message.method) { case 'getAvatars': @@ -201,7 +229,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See } } - sendToQml(message) + sendToQml(message); break; case 'selectAvatar': Entities.addingWearable.disconnect(onAddingWearable); @@ -209,6 +237,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See AvatarBookmarks.loadBookmark(message.name); Entities.addingWearable.connect(onAddingWearable); Entities.deletingWearable.connect(onDeletingWearable); + sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()}); break; case 'deleteAvatar': 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); } - 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; case 'adjustWearablesOpened': currentAvatarWearablesBackup = getMyAvatarWearables(); adjustWearables.setOpened(true); + unfreezeWearables(); Entities.mousePressOnEntity.connect(onSelectedEntity); Messages.subscribe('Hifi-Object-Manipulation'); @@ -305,11 +335,11 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See var currentAvatarURL = MyAvatar.getFullAvatarURLFromPreferences(); if(currentAvatarURL !== message.avatarURL) { MyAvatar.useFullAvatarURL(message.avatarURL); - sendToQml({'method' : 'externalAvatarApplied', 'avatarURL' : message.avatarURL}) + sendToQml({'method' : 'externalAvatarApplied', 'avatarURL' : message.avatarURL}); } break; 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 === 'app://marketplace') { 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.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; default: print('Unrecognized message from AvatarApp.qml'); @@ -366,9 +406,11 @@ function isGrabbable(entityID) { } function setGrabbable(entityID, grabbable) { - var properties = Entities.getEntityProperties(entityID, ['avatarEntity']); - if (properties.avatarEntity) { - Entities.editEntity(entityID, { grab: { grabbable: grabbable }}); + var properties = Entities.getEntityProperties(entityID, ['avatarEntity', 'grab.grabbable']); + if (properties.avatarEntity && properties.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); grabbedAvatarEntityChangeNotifier = null; } - - if(selectedAvatarEntityID !== null) { - setGrabbable(selectedAvatarEntityID, selectedAvatarEntityGrabbable); - } - selectedAvatarEntityID = entityID; - selectedAvatarEntityGrabbable = isGrabbable(entityID); - - if(selectedAvatarEntityID !== null) { - setGrabbable(selectedAvatarEntityID, true); - } - return true; } @@ -398,7 +429,7 @@ function ensureWearableSelected(entityID) { function isEntityBeingWorn(entityID) { return Entities.getEntityProperties(entityID, 'parentID').parentID === MyAvatar.sessionUUID; -}; +} function onSelectedEntity(entityID, pointerEvent) { if(selectedAvatarEntityID !== entityID && isEntityBeingWorn(entityID)) @@ -413,12 +444,14 @@ function onAddingWearable(entityID) { updateAvatarWearables(currentAvatar, function() { sendToQml({'method' : 'updateAvatarInBookmarks'}); }); + sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()}); } function onDeletingWearable(entityID) { updateAvatarWearables(currentAvatar, function() { sendToQml({'method' : 'updateAvatarInBookmarks'}); }); + sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()}); } function handleWearableMessages(channel, message, sender) { @@ -435,30 +468,35 @@ function handleWearableMessages(channel, message, sender) { } 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(selectedAvatarEntityID !== entityID) { ensureWearableSelected(entityID); sendToQml({'method' : 'selectAvatarEntity', 'entityID' : selectedAvatarEntityID}); } - grabbedAvatarEntityChangeNotifier = Script.setInterval(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}) - - }, 1000); + grabbedAvatarEntityChangeNotifier = Script.setInterval(updateWearable, 1000); } else if(parsedMessage.action === 'release') { if(grabbedAvatarEntityChangeNotifier !== null) { Script.clearInterval(grabbedAvatarEntityChangeNotifier); grabbedAvatarEntityChangeNotifier = null; + updateWearable(); } } } @@ -481,8 +519,8 @@ function onBookmarkDeleted(bookmarkName) { function onBookmarkAdded(bookmarkName) { var bookmark = AvatarBookmarks.getBookmark(bookmarkName); 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 }); } @@ -601,14 +639,8 @@ function onTabletScreenChanged(type, url) { onAvatarAppScreen = onAvatarAppScreenNow; if(onAvatarAppScreenNow) { - var message = { - 'method' : 'initialize', - 'data' : { - 'jointNames' : MyAvatar.getJointNames() - } - }; - - sendToQml(message) + sendToQml({ 'method' : 'initialize', 'data' : { jointNames : MyAvatar.getJointNames() }}); + sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()}); } } diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index 17ff918243..86806fd8b4 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -377,6 +377,9 @@ function deleteSendMoneyParticleEffect() { } function onUsernameChanged() { + if (ui.checkIsOpen()) { + ui.open(WALLET_QML_SOURCE); + } } var MARKETPLACE_QML_PATH = "hifi/commerce/marketplace/Marketplace.qml"; diff --git a/scripts/system/libraries/controllerDispatcherUtils.js b/scripts/system/libraries/controllerDispatcherUtils.js index 5cb95f625d..51645e5502 100644 --- a/scripts/system/libraries/controllerDispatcherUtils.js +++ b/scripts/system/libraries/controllerDispatcherUtils.js @@ -341,8 +341,6 @@ entityIsGrabbable = function (eigProps) { var grabbable = getGrabbableData(eigProps).grabbable; if (!grabbable || eigProps.locked || - isAnothersAvatarEntity(eigProps) || - isAnothersChildEntity(eigProps) || FORBIDDEN_GRAB_TYPES.indexOf(eigProps.type) >= 0) { return false; } diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index e059081741..38287e3af3 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -615,11 +615,10 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) { openMarketplace(message.itemId, message.itemEdition); break; case 'passphrasePopup_cancelClicked': - case 'needsLogIn_cancelClicked': // Should/must NOT check for wallet setup. openMarketplace(); break; - case 'needsLogIn_loginClicked': + case 'marketplace_loginClicked': openLoginWindow(); break; case 'disableHmdPreview': diff --git a/tools/nitpick/compiledResources/.placeholder b/tools/nitpick/compiledResources/.placeholder new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tools/nitpick/compiledResources/resources.rcc b/tools/nitpick/compiledResources/resources.rcc deleted file mode 100644 index 15f51ed7f4..0000000000 Binary files a/tools/nitpick/compiledResources/resources.rcc and /dev/null differ diff --git a/tools/nitpick/src/TestRunnerMobile.cpp b/tools/nitpick/src/TestRunnerMobile.cpp index 62630cc7b3..53a74da82f 100644 --- a/tools/nitpick/src/TestRunnerMobile.cpp +++ b/tools/nitpick/src/TestRunnerMobile.cpp @@ -9,6 +9,7 @@ // #include "TestRunnerMobile.h" +#include <QNetworkInterface> #include <QThread> #include <QtWidgets/QMessageBox> #include <QtWidgets/QFileDialog> @@ -69,13 +70,25 @@ void TestRunnerMobile::connectDevice() { _adbInterface = new AdbInterface(); } + // Get list of devices QString devicesFullFilename{ _workingFolder + "/devices.txt" }; QString command = _adbInterface->getAdbCommand() + " devices -l > " + devicesFullFilename; appendLog(command); system(command.toStdString().c_str()); 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); } @@ -86,6 +99,8 @@ void TestRunnerMobile::connectDevice() { QString line2 = devicesFile.readLine(); const QString DEVICE{ "device" }; + const QString MODEL{ "model" }; + if (line2.contains("unauthorized")) { QMessageBox::critical(0, "Unauthorized device detected", "Please allow USB debugging on device"); } else if (line2.contains(DEVICE)) { @@ -98,13 +113,20 @@ void TestRunnerMobile::connectDevice() { QStringList tokens = line2.split(QRegExp("[\r\n\t ]+")); QString deviceID = tokens[0]; - QString modelID = tokens[3].split(':')[1]; - QString modelName = "UNKNOWN"; - if (modelNames.count(modelID) == 1) { - modelName = modelNames[modelID]; + // Find the model entry + _modelName = "UNKNOWN"; + for (int i = 0; i < tokens.size(); ++i) { + 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); _folderLineEdit->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" : _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() + - " shell am start -n io.highfidelity.hifiinterface/.PermissionChecker" + - " --es args \\\"" + - " --url file:///~/serverless/tutorial.json" + - " --no-updater" + - " --no-login-suggestion" + - " --testScript " + testScript + " quitWhenFinished" + - " --testResultsLocation /sdcard/snapshots" + + " shell am start -n " + startCommand + + " --es args \\\"" + + " --url hifi://" + serverIP + "/0,0,0" + " --no-updater" + + " --no-login-suggestion" + + " --testScript " + testScript + " quitWhenFinished" + + " --testResultsLocation /sdcard/snapshots" + "\\\""; appendLog(command); @@ -220,3 +256,85 @@ void TestRunnerMobile::pullFolder() { _statusLabel->setText("Pull complete"); #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); +} diff --git a/tools/nitpick/src/TestRunnerMobile.h b/tools/nitpick/src/TestRunnerMobile.h index f7b16da6f8..09f847785b 100644 --- a/tools/nitpick/src/TestRunnerMobile.h +++ b/tools/nitpick/src/TestRunnerMobile.h @@ -52,6 +52,9 @@ public: void pullFolder(); + QString getServerIP(); + qint64 convertToBinary (const QString& str); + private: QPushButton* _connectDeviceButton; QPushButton* _pullFolderButton; @@ -75,5 +78,9 @@ private: std::map<QString, QString> modelNames; AdbInterface* _adbInterface; + + QString _modelName; + + QString NETWORK_NOT_FOUND{ "NETWORK NOT FOUND"}; }; #endif diff --git a/tools/nitpick/src/common.h b/tools/nitpick/src/common.h index 7431f4979a..51f1f85113 100644 --- a/tools/nitpick/src/common.h +++ b/tools/nitpick/src/common.h @@ -60,5 +60,5 @@ const double R_Y = 0.212655f; const double G_Y = 0.715158f; const double B_Y = 0.072187f; -const QString nitpickVersion { "v3.1.4" }; +const QString nitpickVersion{ "v3.1.5" }; #endif // hifi_common_h \ No newline at end of file