From 40451a1fc1f22fa9e6f169b36f0b3802777cd095 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Thu, 13 Sep 2018 17:21:07 -0700 Subject: [PATCH] Revert "Revert "Merge branch 'master' into fixGoToButtonInterstitial"" This reverts commit c5468fd8e9169cf97a814e3db2db6d66ac1549ed. --- .../src/avatars/AvatarMixerClientData.cpp | 22 +- .../src/avatars/AvatarMixerClientData.h | 13 +- .../src/avatars/AvatarMixerSlave.cpp | 211 ++++++++---------- .../meshes/redirect/oopsDialog_timeout.fbx | Bin 0 -> 31904 bytes .../meshes/redirect/oopsDialog_timeout.png | Bin 0 -> 4555 bytes .../meshes/redirect/oopsDialog_vague.fbx | Bin 32480 -> 32448 bytes .../qml/LoginDialog/LinkAccountBody.qml | 25 ++- interface/src/Application.cpp | 14 +- interface/src/avatar/AvatarManager.cpp | 40 ++-- interface/src/octree/SafeLanding.cpp | 30 ++- libraries/avatars/src/AvatarData.cpp | 196 +++++++--------- libraries/avatars/src/AvatarData.h | 4 +- libraries/networking/src/AccountManager.cpp | 7 - libraries/networking/src/AccountManager.h | 1 - libraries/networking/src/AddressManager.cpp | 4 +- libraries/networking/src/DomainHandler.cpp | 7 +- libraries/networking/src/DomainHandler.h | 5 + libraries/networking/src/udt/Socket.cpp | 82 +++++-- libraries/networking/src/udt/Socket.h | 14 ++ libraries/physics/src/PhysicsEngine.cpp | 1 + libraries/shared/src/PrioritySortUtil.h | 77 ++----- scripts/defaultScripts.js | 5 +- scripts/system/edit.js | 4 +- .../system/libraries/entitySelectionTool.js | 168 +++++++++----- 24 files changed, 479 insertions(+), 451 deletions(-) create mode 100644 interface/resources/meshes/redirect/oopsDialog_timeout.fbx create mode 100644 interface/resources/meshes/redirect/oopsDialog_timeout.png diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index f524c071ec..6c01e6e02b 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -11,6 +11,7 @@ #include "AvatarMixerClientData.h" +#include #include #include @@ -218,6 +219,10 @@ uint16_t AvatarMixerClientData::getLastBroadcastSequenceNumber(const QUuid& node } void AvatarMixerClientData::ignoreOther(SharedNodePointer self, SharedNodePointer other) { + ignoreOther(self.data(), other.data()); +} + +void AvatarMixerClientData::ignoreOther(const Node* self, const Node* other) { if (!isRadiusIgnoring(other->getUUID())) { addToRadiusIgnoringSet(other->getUUID()); auto killPacket = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason), true); @@ -235,9 +240,20 @@ void AvatarMixerClientData::ignoreOther(SharedNodePointer self, SharedNodePointe } } -void AvatarMixerClientData::removeFromRadiusIgnoringSet(SharedNodePointer self, const QUuid& other) { - if (isRadiusIgnoring(other)) { - _radiusIgnoredOthers.erase(other); +bool AvatarMixerClientData::isRadiusIgnoring(const QUuid& other) const { + return std::find(_radiusIgnoredOthers.cbegin(), _radiusIgnoredOthers.cend(), other) != _radiusIgnoredOthers.cend(); +} + +void AvatarMixerClientData::addToRadiusIgnoringSet(const QUuid& other) { + if (!isRadiusIgnoring(other)) { + _radiusIgnoredOthers.push_back(other); + } +} + +void AvatarMixerClientData::removeFromRadiusIgnoringSet(const QUuid& other) { + auto ignoredOtherIter = std::find(_radiusIgnoredOthers.cbegin(), _radiusIgnoredOthers.cend(), other); + if (ignoredOtherIter != _radiusIgnoredOthers.cend()) { + _radiusIgnoredOthers.erase(ignoredOtherIter); } } diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index a892455fe3..d38a90ef1f 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -15,7 +15,7 @@ #include #include #include -#include +#include #include #include @@ -45,6 +45,7 @@ public: int parseData(ReceivedMessage& message) override; AvatarData& getAvatar() { return *_avatar; } + const AvatarData& getAvatar() const { return *_avatar; } const AvatarData* getConstAvatarData() const { return _avatar.get(); } AvatarSharedPointer getAvatarSharedPointer() const { return _avatar; } @@ -90,11 +91,11 @@ public: void loadJSONStats(QJsonObject& jsonObject) const; glm::vec3 getPosition() const { return _avatar ? _avatar->getWorldPosition() : glm::vec3(0); } - glm::vec3 getGlobalBoundingBoxCorner() const { return _avatar ? _avatar->getGlobalBoundingBoxCorner() : glm::vec3(0); } - bool isRadiusIgnoring(const QUuid& other) const { return _radiusIgnoredOthers.find(other) != _radiusIgnoredOthers.end(); } - void addToRadiusIgnoringSet(const QUuid& other) { _radiusIgnoredOthers.insert(other); } - void removeFromRadiusIgnoringSet(SharedNodePointer self, const QUuid& other); + bool isRadiusIgnoring(const QUuid& other) const; + void addToRadiusIgnoringSet(const QUuid& other); + void removeFromRadiusIgnoringSet(const QUuid& other); void ignoreOther(SharedNodePointer self, SharedNodePointer other); + void ignoreOther(const Node* self, const Node* other); void readViewFrustumPacket(const QByteArray& message); @@ -166,7 +167,7 @@ private: int _numOutOfOrderSends = 0; SimpleMovingAverage _avgOtherAvatarDataRate; - std::unordered_set _radiusIgnoredOthers; + std::vector _radiusIgnoredOthers; ConicalViewFrustums _currentViewFrustums; int _recentOtherAvatarsInView { 0 }; diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index c434d82116..7368db0c31 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -33,6 +34,8 @@ #include "AvatarMixer.h" #include "AvatarMixerClientData.h" +namespace chrono = std::chrono; + void AvatarMixerSlave::configure(ConstIter begin, ConstIter end) { _begin = begin; _end = end; @@ -209,7 +212,18 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { _stats.jobElapsedTime += (end - start); } +AABox computeBubbleBox(const AvatarData& avatar, float bubbleExpansionFactor) { + AABox box = avatar.getGlobalBoundingBox(); + glm::vec3 scale = box.getScale(); + scale *= bubbleExpansionFactor; + const glm::vec3 MIN_BUBBLE_SCALE(0.3f, 1.3f, 0.3); + scale = glm::max(scale, MIN_BUBBLE_SCALE); + box.setScaleStayCentered(glm::max(scale, MIN_BUBBLE_SCALE)); + return box; +} + void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) { + const Node* destinationNode = node.data(); auto nodeList = DependencyManager::get(); @@ -220,7 +234,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) _stats.nodesBroadcastedTo++; - AvatarMixerClientData* nodeData = reinterpret_cast(node->getLinkedData()); + AvatarMixerClientData* nodeData = reinterpret_cast(destinationNode->getLinkedData()); nodeData->resetInViewStats(); @@ -242,12 +256,8 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) int traitBytesSent = 0; // max number of avatarBytes per frame - auto maxAvatarBytesPerFrame = (_maxKbpsPerNode * BYTES_PER_KILOBIT) / AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND; + int maxAvatarBytesPerFrame = int(_maxKbpsPerNode * BYTES_PER_KILOBIT / AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND); - // FIXME - find a way to not send the sessionID for every avatar - int minimumBytesPerAvatar = AvatarDataPacket::AVATAR_HAS_FLAGS_SIZE + NUM_BYTES_RFC4122_UUID; - - int overBudgetAvatars = 0; // keep track of the number of other avatars held back in this frame int numAvatarsHeldBack = 0; @@ -260,66 +270,38 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) bool PALIsOpen = nodeData->getRequestsDomainListData(); // When this is true, the AvatarMixer will send Avatar data to a client about avatars that have ignored them - bool getsAnyIgnored = PALIsOpen && node->getCanKick(); + bool getsAnyIgnored = PALIsOpen && destinationNode->getCanKick(); - if (PALIsOpen) { - // Increase minimumBytesPerAvatar if the PAL is open - minimumBytesPerAvatar += sizeof(AvatarDataPacket::AvatarGlobalPosition) + - sizeof(AvatarDataPacket::AudioLoudness); - } + // Bandwidth allowance for data that must be sent. + int minimumBytesPerAvatar = PALIsOpen ? AvatarDataPacket::AVATAR_HAS_FLAGS_SIZE + NUM_BYTES_RFC4122_UUID + + sizeof(AvatarDataPacket::AvatarGlobalPosition) + sizeof(AvatarDataPacket::AudioLoudness) : 0; // setup a PacketList for the avatarPackets auto avatarPacketList = NLPacketList::create(PacketType::BulkAvatarData); + static auto maxAvatarDataBytes = avatarPacketList->getMaxSegmentSize() - NUM_BYTES_RFC4122_UUID; - // Define the minimum bubble size - static const glm::vec3 minBubbleSize = avatar.getSensorToWorldScale() * glm::vec3(0.3f, 1.3f, 0.3f); - // Define the scale of the box for the current node - glm::vec3 nodeBoxScale = (nodeData->getPosition() - nodeData->getGlobalBoundingBoxCorner()) * 2.0f * avatar.getSensorToWorldScale(); - // Set up the bounding box for the current node - AABox nodeBox(nodeData->getGlobalBoundingBoxCorner(), nodeBoxScale); - // Clamp the size of the bounding box to a minimum scale - if (glm::any(glm::lessThan(nodeBoxScale, minBubbleSize))) { - nodeBox.setScaleStayCentered(minBubbleSize); - } - // Quadruple the scale of both bounding boxes - nodeBox.embiggen(4.0f); - - - // setup list of AvatarData as well as maps to map betweeen the AvatarData and the original nodes - std::vector avatarsToSort; - std::unordered_map avatarDataToNodes; - std::unordered_map avatarEncodeTimes; - std::for_each(_begin, _end, [&](const SharedNodePointer& otherNode) { - // make sure this is an agent that we have avatar data for before considering it for inclusion - if (otherNode->getType() == NodeType::Agent - && otherNode->getLinkedData()) { - const AvatarMixerClientData* otherNodeData = reinterpret_cast(otherNode->getLinkedData()); - - AvatarSharedPointer otherAvatar = otherNodeData->getAvatarSharedPointer(); - avatarsToSort.push_back(otherAvatar); - avatarDataToNodes[otherAvatar] = otherNode; - QUuid id = otherAvatar->getSessionUUID(); - avatarEncodeTimes[id] = nodeData->getLastOtherAvatarEncodeTime(id); - } - }); + // compute node bounding box + const float MY_AVATAR_BUBBLE_EXPANSION_FACTOR = 4.0f; // magic number determined emperically + AABox nodeBox = computeBubbleBox(avatar, MY_AVATAR_BUBBLE_EXPANSION_FACTOR); class SortableAvatar: public PrioritySortUtil::Sortable { public: SortableAvatar() = delete; - SortableAvatar(const AvatarSharedPointer& avatar, uint64_t lastEncodeTime) - : _avatar(avatar), _lastEncodeTime(lastEncodeTime) {} - glm::vec3 getPosition() const override { return _avatar->getWorldPosition(); } + SortableAvatar(const AvatarData* avatar, const Node* avatarNode, uint64_t lastEncodeTime) + : _avatar(avatar), _node(avatarNode), _lastEncodeTime(lastEncodeTime) {} + glm::vec3 getPosition() const override { return _avatar->getClientGlobalPosition(); } float getRadius() const override { - glm::vec3 nodeBoxHalfScale = (_avatar->getWorldPosition() - _avatar->getGlobalBoundingBoxCorner() * _avatar->getSensorToWorldScale()); - return glm::max(nodeBoxHalfScale.x, glm::max(nodeBoxHalfScale.y, nodeBoxHalfScale.z)); + glm::vec3 nodeBoxScale = _avatar->getGlobalBoundingBox().getScale(); + return 0.5f * glm::max(nodeBoxScale.x, glm::max(nodeBoxScale.y, nodeBoxScale.z)); } uint64_t getTimestamp() const override { return _lastEncodeTime; } - AvatarSharedPointer getAvatar() const { return _avatar; } + const Node* getNode() const { return _node; } private: - AvatarSharedPointer _avatar; + const AvatarData* _avatar; + const Node* _node; uint64_t _lastEncodeTime; }; @@ -329,16 +311,18 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) AvatarData::_avatarSortCoefficientSize, AvatarData::_avatarSortCoefficientCenter, AvatarData::_avatarSortCoefficientAge); - sortedAvatars.reserve(avatarsToSort.size()); + sortedAvatars.reserve(_end - _begin); - // ignore or sort - const AvatarSharedPointer& thisAvatar = nodeData->getAvatarSharedPointer(); - for (const auto& avatar : avatarsToSort) { - if (avatar == thisAvatar) { - // don't echo updates to self + for (auto listedNode = _begin; listedNode != _end; ++listedNode) { + Node* otherNodeRaw = (*listedNode).data(); + if (otherNodeRaw->getType() != NodeType::Agent + || !otherNodeRaw->getLinkedData() + || otherNodeRaw == destinationNode) { continue; } + auto avatarNode = otherNodeRaw; + bool shouldIgnore = false; // We ignore other nodes for a couple of reasons: // 1) ignore bubbles and ignore specific node @@ -346,53 +330,39 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // happen if for example the avatar is connected on a desktop and sending // updates at ~30hz. So every 3 frames we skip a frame. - auto avatarNode = avatarDataToNodes[avatar]; assert(avatarNode); // we can't have gotten here without the avatarData being a valid key in the map - const AvatarMixerClientData* avatarNodeData = reinterpret_cast(avatarNode->getLinkedData()); - assert(avatarNodeData); // we can't have gotten here without avatarNode having valid data + const AvatarMixerClientData* avatarClientNodeData = reinterpret_cast(avatarNode->getLinkedData()); + assert(avatarClientNodeData); // we can't have gotten here without avatarNode having valid data quint64 startIgnoreCalculation = usecTimestampNow(); // make sure we have data for this avatar, that it isn't the same node, // and isn't an avatar that the viewing node has ignored // or that has ignored the viewing node - if (!avatarNode->getLinkedData() - || avatarNode->getUUID() == node->getUUID() - || (node->isIgnoringNodeWithID(avatarNode->getUUID()) && !PALIsOpen) - || (avatarNode->isIgnoringNodeWithID(node->getUUID()) && !getsAnyIgnored)) { + if ((destinationNode->isIgnoringNodeWithID(avatarNode->getUUID()) && !PALIsOpen) + || (avatarNode->isIgnoringNodeWithID(destinationNode->getUUID()) && !getsAnyIgnored)) { shouldIgnore = true; } else { // Check to see if the space bubble is enabled // Don't bother with these checks if the other avatar has their bubble enabled and we're gettingAnyIgnored - if (node->isIgnoreRadiusEnabled() || (avatarNode->isIgnoreRadiusEnabled() && !getsAnyIgnored)) { - float sensorToWorldScale = avatarNodeData->getAvatarSharedPointer()->getSensorToWorldScale(); - // Define the scale of the box for the current other node - glm::vec3 otherNodeBoxScale = (avatarNodeData->getPosition() - avatarNodeData->getGlobalBoundingBoxCorner()) * 2.0f * sensorToWorldScale; - // Set up the bounding box for the current other node - AABox otherNodeBox(avatarNodeData->getGlobalBoundingBoxCorner(), otherNodeBoxScale); - // Clamp the size of the bounding box to a minimum scale - if (glm::any(glm::lessThan(otherNodeBoxScale, minBubbleSize))) { - otherNodeBox.setScaleStayCentered(minBubbleSize); - } - // Change the scale of both bounding boxes - // (This is an arbitrary number determined empirically) - otherNodeBox.embiggen(2.4f); - + if (destinationNode->isIgnoreRadiusEnabled() || (avatarNode->isIgnoreRadiusEnabled() && !getsAnyIgnored)) { // Perform the collision check between the two bounding boxes + const float OTHER_AVATAR_BUBBLE_EXPANSION_FACTOR = 2.4f; // magic number determined empirically + AABox otherNodeBox = computeBubbleBox(avatarClientNodeData->getAvatar(), OTHER_AVATAR_BUBBLE_EXPANSION_FACTOR); if (nodeBox.touches(otherNodeBox)) { - nodeData->ignoreOther(node, avatarNode); + nodeData->ignoreOther(destinationNode, avatarNode); shouldIgnore = !getsAnyIgnored; } } // Not close enough to ignore if (!shouldIgnore) { - nodeData->removeFromRadiusIgnoringSet(node, avatarNode->getUUID()); + nodeData->removeFromRadiusIgnoringSet(avatarNode->getUUID()); } } if (!shouldIgnore) { AvatarDataSequenceNumber lastSeqToReceiver = nodeData->getLastBroadcastSequenceNumber(avatarNode->getUUID()); - AvatarDataSequenceNumber lastSeqFromSender = avatarNodeData->getLastReceivedSequenceNumber(); + AvatarDataSequenceNumber lastSeqFromSender = avatarClientNodeData->getLastReceivedSequenceNumber(); // FIXME - This code does appear to be working. But it seems brittle. // It supports determining if the frame of data for this "other" @@ -417,12 +387,10 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) if (!shouldIgnore) { // sort this one for later - uint64_t lastEncodeTime = 0; - std::unordered_map::const_iterator itr = avatarEncodeTimes.find(avatar->getSessionUUID()); - if (itr != avatarEncodeTimes.end()) { - lastEncodeTime = itr->second; - } - sortedAvatars.push(SortableAvatar(avatar, lastEncodeTime)); + const AvatarData* avatarNodeData = avatarClientNodeData->getConstAvatarData(); + auto lastEncodeTime = nodeData->getLastOtherAvatarEncodeTime(avatarNodeData->getSessionUUID()); + + sortedAvatars.push(SortableAvatar(avatarNodeData, avatarNode, lastEncodeTime)); } } @@ -430,19 +398,31 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) int remainingAvatars = (int)sortedAvatars.size(); auto traitsPacketList = NLPacketList::create(PacketType::BulkAvatarTraits, QByteArray(), true, true); + const auto& sortedAvatarVector = sortedAvatars.getSortedVector(); for (const auto& sortedAvatar : sortedAvatarVector) { - const auto& avatarData = sortedAvatar.getAvatar(); - remainingAvatars--; + const Node* otherNode = sortedAvatar.getNode(); + auto lastEncodeForOther = sortedAvatar.getTimestamp(); - auto otherNode = avatarDataToNodes[avatarData]; assert(otherNode); // we can't have gotten here without the avatarData being a valid key in the map - // NOTE: Here's where we determine if we are over budget and drop to bare minimum data + AvatarData::AvatarDataDetail detail = AvatarData::NoData; + + // NOTE: Here's where we determine if we are over budget and drop remaining avatars, + // or send minimal avatar data in uncommon case of PALIsOpen. int minimRemainingAvatarBytes = minimumBytesPerAvatar * remainingAvatars; bool overBudget = (identityBytesSent + numAvatarDataBytes + minimRemainingAvatarBytes) > maxAvatarBytesPerFrame; + if (overBudget) { + if (PALIsOpen) { + _stats.overBudgetAvatars++; + detail = AvatarData::PALMinimum; + } else { + _stats.overBudgetAvatars += remainingAvatars; + break; + } + } - quint64 startAvatarDataPacking = usecTimestampNow(); + auto startAvatarDataPacking = chrono::high_resolution_clock::now(); ++numOtherAvatars; @@ -459,32 +439,18 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) nodeData->setLastBroadcastTime(otherNode->getUUID(), usecTimestampNow()); } - // determine if avatar is in view which determines how much data to send - glm::vec3 otherPosition = otherAvatar->getClientGlobalPosition(); - glm::vec3 otherNodeBoxScale = (otherPosition - otherNodeData->getGlobalBoundingBoxCorner()) * 2.0f * otherAvatar->getSensorToWorldScale(); - AABox otherNodeBox(otherNodeData->getGlobalBoundingBoxCorner(), otherNodeBoxScale); - bool isInView = nodeData->otherAvatarInView(otherNodeBox); + // Typically all out-of-view avatars but such avatars' priorities will rise with time: + bool isLowerPriority = sortedAvatar.getPriority() <= OUT_OF_VIEW_THRESHOLD; - // start a new segment in the PacketList for this avatar - avatarPacketList->startSegment(); - - AvatarData::AvatarDataDetail detail; - - if (overBudget) { - overBudgetAvatars++; - _stats.overBudgetAvatars++; - detail = PALIsOpen ? AvatarData::PALMinimum : AvatarData::NoData; - } else if (!isInView) { + if (isLowerPriority) { detail = PALIsOpen ? AvatarData::PALMinimum : AvatarData::MinimumData; nodeData->incrementAvatarOutOfView(); - } else { - detail = distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO - ? AvatarData::SendAllData : AvatarData::CullSmallData; + } else if (!overBudget) { + detail = distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO ? AvatarData::SendAllData : AvatarData::CullSmallData; nodeData->incrementAvatarInView(); } bool includeThisAvatar = true; - auto lastEncodeForOther = nodeData->getLastOtherAvatarEncodeTime(otherNode->getUUID()); QVector& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getUUID()); lastSentJointsForOther.resize(otherAvatar->getJointCount()); @@ -494,14 +460,14 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) AvatarDataPacket::HasFlags hasFlagsOut; // the result of the toByteArray bool dropFaceTracking = false; - quint64 start = usecTimestampNow(); + auto startSerialize = chrono::high_resolution_clock::now(); QByteArray bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther); - quint64 end = usecTimestampNow(); - _stats.toByteArrayElapsedTime += (end - start); + auto endSerialize = chrono::high_resolution_clock::now(); + _stats.toByteArrayElapsedTime += + (quint64) chrono::duration_cast(endSerialize - startSerialize).count(); - static auto maxAvatarDataBytes = avatarPacketList->getMaxSegmentSize() - NUM_BYTES_RFC4122_UUID; if (bytes.size() > maxAvatarDataBytes) { qCWarning(avatars) << "otherAvatar.toByteArray() for" << otherNode->getUUID() << "resulted in very large buffer of" << bytes.size() << "bytes - dropping facial data"; @@ -527,8 +493,11 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) } if (includeThisAvatar) { + // start a new segment in the PacketList for this avatar + avatarPacketList->startSegment(); numAvatarDataBytes += avatarPacketList->write(otherNode->getUUID().toRfc4122()); numAvatarDataBytes += avatarPacketList->write(bytes); + avatarPacketList->endSegment(); if (detail != AvatarData::NoData) { _stats.numOthersIncluded++; @@ -546,15 +515,13 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // It would be nice if we could tweak its future sort priority to put it at the back of the list. } - avatarPacketList->endSegment(); - - quint64 endAvatarDataPacking = usecTimestampNow(); - _stats.avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); + auto endAvatarDataPacking = chrono::high_resolution_clock::now(); + _stats.avatarDataPackingElapsedTime += + (quint64) chrono::duration_cast(endAvatarDataPacking - startAvatarDataPacking).count(); // use helper to add any changed traits to our packet list traitBytesSent += addChangedTraitsToBulkPacket(nodeData, otherNodeData, *traitsPacketList); - - traitsPacketList->getDataSize(); + remainingAvatars--; } quint64 startPacketSending = usecTimestampNow(); @@ -566,7 +533,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) _stats.numBytesSent += numAvatarDataBytes; // send the avatar data PacketList - nodeList->sendPacketList(std::move(avatarPacketList), *node); + nodeList->sendPacketList(std::move(avatarPacketList), *destinationNode); // record the bytes sent for other avatar data in the AvatarMixerClientData nodeData->recordSentAvatarData(numAvatarDataBytes); @@ -576,7 +543,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) if (traitsPacketList->getNumPackets() >= 1) { // send the traits packet list - nodeList->sendPacketList(std::move(traitsPacketList), *node); + nodeList->sendPacketList(std::move(traitsPacketList), *destinationNode); } // record the number of avatars held back this frame diff --git a/interface/resources/meshes/redirect/oopsDialog_timeout.fbx b/interface/resources/meshes/redirect/oopsDialog_timeout.fbx new file mode 100644 index 0000000000000000000000000000000000000000..846d38bbee3f38ddb874005669bb38561f293584 GIT binary patch literal 31904 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 literal 0 HcmV?d00001 diff --git a/interface/resources/meshes/redirect/oopsDialog_vague.fbx b/interface/resources/meshes/redirect/oopsDialog_vague.fbx index 324d90578b833552f01a1199fbded078b49900ba..707f09e51a8872b44615aafbe7d5dab5c7306f51 100644 GIT binary patch delta 1251 zcmZ`&UrbwN6hGft|BN~;AnSsotX(_CoY3B~G9V_5F$H5giXd%C=GK_jb+X>udv8k% z?O=)73pkM@50Xt-Voaus5$k-I$?TK|eK0uVpTq~3r?&dJF+ zzw`Uf`JG#9thmNr&occs0GOu*w!^6F;Zx$n7P`P3%t}S(FjN9S1=1yEp>^;U=fOjWxNztXC(+`Tn*D%A;7%UKh()i7iNb7)%c@8Cqq`l z&)q7LiMQfPf?f$Z@poQAMZ9)l*i8LYf;hO>t3?0Hx1a%JklXmtD z`IENY6%Q$R0R3hFh{cDaTpO@~Xhu$n(S87c2tAwR*g3k9bmH|S^*&;uwWJM~MYSs@ zlC`0S7MwaJ({tisd`qOoh6m`yS}U%L!waKfR zxb$HhOqY1Ed0I}p@R#%^t!C;Q=|o|qL<=t~?m_%&gigr^usTBvvJcm@bUssWIPus^ ziYl&UcHy^Kwbyr%Vmb?HkktJ(G4PD7L`(jLMsM8EIZCKXT zj{wjc@ckE~0sscYNJ0`KVl*ME{mOG%;g*g18pYCluDnV+1%OSULo}J&U8%|%z*qW< zM3Xstm3FHDzS2ZqR>EfOGB?AtShf>&XH;dcTG%M*SVv2lV+LpoZO4cikc|vnD=-+em@Ok(qz3KVzHMm> zj3s6_%7yYwFQ&3_vzRO&22-(0TOT5AP!G#}jADP5tR?XW2W=ni8pPW3; zFXue}H=W@-8Jo5NIV&sjCp9Ea^D*meUxEv_lLsc#Y&+i^=0>I z=5d62_Mpvd!(8KTz6oHc+VRYjS}*G zvKW^XZ`~8#D=`sx6l;vh>1AvN@bSw?kDl; zAICczg*{{w7ov~x%|)|(N1`TiBbMc}#kTn5*cRXQ*d`zU4Jl?79m8O`Lre~50dFES zoCOpGaeN~m5=Y|L9&rdcj^>D4oJqnOe@3PIb?JVF@nXD8v?Q}(tqDxWTg5vG^cOd3 zB-g}OP9pj)0b`{BoXwv_7SsX^XmT% zK21c&0s{k4IX72<=23G|uF*>VMooINa;}d=?#;kAvvTeY61g|A=M-6+dvRN`^2gv( zq(_Zx#_DL55a)1ruO8)NMLY)Qn4M?w%2*x$jQ7Vb=y%>uufFu@$od;)_bQ}~B@m(d G9R3AsWTBP- diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index 4196b7f168..57293cb5e3 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -346,7 +346,15 @@ Item { target: loginDialog onHandleLoginCompleted: { console.log("Login Succeeded, linking steam account") - + var poppedUp = Settings.getValue("loginDialogPoppedUp", false); + if (poppedUp) { + console.log("[ENCOURAGELOGINDIALOG]: logging in") + var data = { + "action": "user logged in" + }; + UserActivityLogger.logAction("encourageLoginDialog", data); + Settings.setValue("loginDialogPoppedUp", false); + } if (loginDialog.isSteamRunning()) { loginDialog.linkSteam() } else { @@ -354,23 +362,20 @@ Item { bodyLoader.item.width = root.pane.width bodyLoader.item.height = root.pane.height } - if (Settings.getValue("loginDialogPoppedUp", false)) { - var data = { - "action": "user logged in" - }; - UserActivityLogger.logAction("encourageLoginDialog", data); - } } onHandleLoginFailed: { console.log("Login Failed") - mainTextContainer.visible = true - toggleLoading(false) - if (Settings.getValue("loginDialogPoppedUp", false)) { + var poppedUp = Settings.getValue("loginDialogPoppedUp", false); + if (poppedUp) { + console.log("[ENCOURAGELOGINDIALOG]: failed logging in") var data = { "action": "user failed logging in" }; UserActivityLogger.logAction("encourageLoginDialog", data); + Settings.setValue("loginDialogPoppedUp", false); } + mainTextContainer.visible = true + toggleLoading(false) } onHandleLinkCompleted: { console.log("Link Succeeded") diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 77e2cb3211..826f6a87a9 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3498,7 +3498,9 @@ bool Application::isServerlessMode() const { } void Application::setIsInterstitialMode(bool interstitialMode) { - if (_interstitialMode != interstitialMode) { + Settings settings; + bool enableInterstitial = settings.value("enableIntersitialMode", false).toBool(); + if (_interstitialMode != interstitialMode && enableInterstitial) { _interstitialMode = interstitialMode; DependencyManager::get()->setAudioPaused(_interstitialMode); @@ -6346,6 +6348,7 @@ void Application::updateWindowTitle() const { auto nodeList = DependencyManager::get(); auto accountManager = DependencyManager::get(); + auto isInErrorState = nodeList->getDomainHandler().isInErrorState(); QString buildVersion = " - " + (BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Stable ? QString("Version") : QString("Build")) @@ -6353,14 +6356,19 @@ void Application::updateWindowTitle() const { QString loginStatus = accountManager->isLoggedIn() ? "" : " (NOT LOGGED IN)"; - QString connectionStatus = nodeList->getDomainHandler().isConnected() ? "" : " (NOT CONNECTED)"; + QString connectionStatus = isInErrorState ? " (ERROR CONNECTING)" : + nodeList->getDomainHandler().isConnected() ? "" : " (NOT CONNECTED)"; QString username = accountManager->getAccountInfo().getUsername(); setCrashAnnotation("username", username.toStdString()); QString currentPlaceName; if (isServerlessMode()) { - currentPlaceName = "serverless: " + DependencyManager::get()->getDomainURL().toString(); + if (isInErrorState) { + currentPlaceName = "serverless: " + nodeList->getDomainHandler().getErrorDomainURL().toString(); + } else { + currentPlaceName = "serverless: " + DependencyManager::get()->getDomainURL().toString(); + } } else { currentPlaceName = DependencyManager::get()->getDomainURL().host(); if (currentPlaceName.isEmpty()) { diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 443d19e473..1faf17ea9a 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -176,29 +176,29 @@ float AvatarManager::getAvatarSimulationRate(const QUuid& sessionID, const QStri } void AvatarManager::updateOtherAvatars(float deltaTime) { - // lock the hash for read to check the size - QReadLocker lock(&_hashLock); - if (_avatarHash.size() < 2 && _avatarsToFade.isEmpty()) { - return; + { + // lock the hash for read to check the size + QReadLocker lock(&_hashLock); + if (_avatarHash.size() < 2 && _avatarsToFade.isEmpty()) { + return; + } } - lock.unlock(); PerformanceTimer perfTimer("otherAvatars"); class SortableAvatar: public PrioritySortUtil::Sortable { public: SortableAvatar() = delete; - SortableAvatar(const AvatarSharedPointer& avatar) : _avatar(avatar) {} + SortableAvatar(const std::shared_ptr& avatar) : _avatar(avatar) {} glm::vec3 getPosition() const override { return _avatar->getWorldPosition(); } - float getRadius() const override { return std::static_pointer_cast(_avatar)->getBoundingRadius(); } - uint64_t getTimestamp() const override { return std::static_pointer_cast(_avatar)->getLastRenderUpdateTime(); } - AvatarSharedPointer getAvatar() const { return _avatar; } + float getRadius() const override { return _avatar->getBoundingRadius(); } + uint64_t getTimestamp() const override { return _avatar->getLastRenderUpdateTime(); } + std::shared_ptr getAvatar() const { return _avatar; } private: - AvatarSharedPointer _avatar; + std::shared_ptr _avatar; }; auto avatarMap = getHashCopy(); - AvatarHash::iterator itr = avatarMap.begin(); const auto& views = qApp->getConicalViews(); PrioritySortUtil::PriorityQueue sortedAvatars(views, @@ -207,22 +207,24 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { AvatarData::_avatarSortCoefficientAge); sortedAvatars.reserve(avatarMap.size() - 1); // don't include MyAvatar - // sort + // Build vector and compute priorities + auto nodeList = DependencyManager::get(); + AvatarHash::iterator itr = avatarMap.begin(); while (itr != avatarMap.end()) { const auto& avatar = std::static_pointer_cast(*itr); // DO NOT update _myAvatar! Its update has already been done earlier in the main loop. // DO NOT update or fade out uninitialized Avatars - if (avatar != _myAvatar && avatar->isInitialized()) { + if (avatar != _myAvatar && avatar->isInitialized() && !nodeList->isPersonalMutingNode(avatar->getID())) { sortedAvatars.push(SortableAvatar(avatar)); } ++itr; } + // Sort const auto& sortedAvatarVector = sortedAvatars.getSortedVector(); // process in sorted order uint64_t startTime = usecTimestampNow(); - const uint64_t UPDATE_BUDGET = 2000; // usec - uint64_t updateExpiry = startTime + UPDATE_BUDGET; + uint64_t updateExpiry = startTime + MAX_UPDATE_AVATARS_TIME_BUDGET; int numAvatarsUpdated = 0; int numAVatarsNotUpdated = 0; @@ -241,18 +243,12 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { avatar->updateOrbPosition(); } - bool ignoring = DependencyManager::get()->isPersonalMutingNode(avatar->getID()); - if (ignoring) { - continue; - } - // for ALL avatars... if (_shouldRender) { avatar->ensureInScene(avatar, qApp->getMain3DScene()); } avatar->animateScaleChanges(deltaTime); - const float OUT_OF_VIEW_THRESHOLD = 0.5f * AvatarData::OUT_OF_VIEW_PENALTY; uint64_t now = usecTimestampNow(); if (now < updateExpiry) { // we're within budget @@ -273,7 +269,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { // no time to simulate, but we take the time to count how many were tragically missed while (it != sortedAvatarVector.end()) { const SortableAvatar& newSortData = *it; - const auto newAvatar = std::static_pointer_cast(newSortData.getAvatar()); + const auto& newAvatar = newSortData.getAvatar(); bool inView = newSortData.getPriority() > OUT_OF_VIEW_THRESHOLD; // Once we reach an avatar that's not in view, all avatars after it will also be out of view if (!inView) { diff --git a/interface/src/octree/SafeLanding.cpp b/interface/src/octree/SafeLanding.cpp index 9785e0ba95..5d4ebe9853 100644 --- a/interface/src/octree/SafeLanding.cpp +++ b/interface/src/octree/SafeLanding.cpp @@ -66,13 +66,18 @@ void SafeLanding::addTrackedEntity(const EntityItemID& entityID) { Locker lock(_lock); EntityItemPointer entity = _entityTree->findEntityByID(entityID); - _trackedEntities.emplace(entityID, entity); - int trackedEntityCount = (int)_trackedEntities.size(); + if (entity) { - if (trackedEntityCount > _maxTrackedEntityCount) { - _maxTrackedEntityCount = trackedEntityCount; + _trackedEntities.emplace(entityID, entity); + int trackedEntityCount = (int)_trackedEntities.size(); + + if (trackedEntityCount > _maxTrackedEntityCount) { + _maxTrackedEntityCount = trackedEntityCount; + } + qCDebug(interfaceapp) << "Safe Landing: Tracking entity " << entity->getItemName(); } - qCDebug(interfaceapp) << "Safe Landing: Tracking entity " << entity->getItemName(); + } else { + qCDebug(interfaceapp) << "Safe Landing: Null Entity: " << entityID; } } @@ -146,7 +151,7 @@ bool isEntityPhysicsReady(const EntityItemPointer& entity) { bool hasAABox; entity->getAABox(hasAABox); if (hasAABox && downloadedCollisionTypes.count(modelEntity->getShapeType()) != 0) { - return entity->isReadyToComputeShape(); + return (!entity->shouldBePhysical() || entity->isReadyToComputeShape()); } } } @@ -156,12 +161,23 @@ bool isEntityPhysicsReady(const EntityItemPointer& entity) { bool SafeLanding::isEntityLoadingComplete() { Locker lock(_lock); + + auto entityTree = qApp->getEntities(); auto entityMapIter = _trackedEntities.begin(); while (entityMapIter != _trackedEntities.end()) { auto entity = entityMapIter->second; - bool isVisuallyReady = (entity->isVisuallyReady() || !entityTree->renderableForEntityId(entityMapIter->first)); + + bool isVisuallyReady = true; + + Settings settings; + bool enableInterstitial = settings.value("enableIntersitialMode", false).toBool(); + + if (enableInterstitial) { + isVisuallyReady = (entity->isVisuallyReady() || !entityTree->renderableForEntityId(entityMapIter->first)); + } + if (isEntityPhysicsReady(entity) && isVisuallyReady) { entityMapIter = _trackedEntities.erase(entityMapIter); } else { diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index ea6dbd7074..834754e228 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -363,13 +363,13 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent memcpy(destinationBuffer, &packetStateFlags, sizeof(packetStateFlags)); destinationBuffer += sizeof(packetStateFlags); +#define AVATAR_MEMCPY(src) \ + memcpy(destinationBuffer, &(src), sizeof(src)); \ + destinationBuffer += sizeof(src); + if (hasAvatarGlobalPosition) { auto startSection = destinationBuffer; - auto data = reinterpret_cast(destinationBuffer); - data->globalPosition[0] = _globalPosition.x; - data->globalPosition[1] = _globalPosition.y; - data->globalPosition[2] = _globalPosition.z; - destinationBuffer += sizeof(AvatarDataPacket::AvatarGlobalPosition); + AVATAR_MEMCPY(_globalPosition); int numBytes = destinationBuffer - startSection; @@ -380,17 +380,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent if (hasAvatarBoundingBox) { auto startSection = destinationBuffer; - auto data = reinterpret_cast(destinationBuffer); - - data->avatarDimensions[0] = _globalBoundingBoxDimensions.x; - data->avatarDimensions[1] = _globalBoundingBoxDimensions.y; - data->avatarDimensions[2] = _globalBoundingBoxDimensions.z; - - data->boundOriginOffset[0] = _globalBoundingBoxOffset.x; - data->boundOriginOffset[1] = _globalBoundingBoxOffset.y; - data->boundOriginOffset[2] = _globalBoundingBoxOffset.z; - - destinationBuffer += sizeof(AvatarDataPacket::AvatarBoundingBox); + AVATAR_MEMCPY(_globalBoundingBoxDimensions); + AVATAR_MEMCPY(_globalBoundingBoxOffset); int numBytes = destinationBuffer - startSection; if (outboundDataRateOut) { @@ -424,13 +415,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent if (hasLookAtPosition) { auto startSection = destinationBuffer; - auto data = reinterpret_cast(destinationBuffer); - auto lookAt = _headData->getLookAtPosition(); - data->lookAtPosition[0] = lookAt.x; - data->lookAtPosition[1] = lookAt.y; - data->lookAtPosition[2] = lookAt.z; - destinationBuffer += sizeof(AvatarDataPacket::LookAtPosition); - + AVATAR_MEMCPY(_headData->getLookAtPosition()); int numBytes = destinationBuffer - startSection; if (outboundDataRateOut) { outboundDataRateOut->lookAtPositionRate.increment(numBytes); @@ -531,12 +516,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent if (hasAvatarLocalPosition) { auto startSection = destinationBuffer; - auto data = reinterpret_cast(destinationBuffer); - auto localPosition = getLocalPosition(); - data->localPosition[0] = localPosition.x; - data->localPosition[1] = localPosition.y; - data->localPosition[2] = localPosition.z; - destinationBuffer += sizeof(AvatarDataPacket::AvatarLocalPosition); + const auto localPosition = getLocalPosition(); + AVATAR_MEMCPY(localPosition); int numBytes = destinationBuffer - startSection; if (outboundDataRateOut) { @@ -567,19 +548,24 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } } + QVector jointData; + if (hasJointData || hasJointDefaultPoseFlags) { + QReadLocker readLock(&_jointDataLock); + jointData = _jointData; + } + // If it is connected, pack up the data if (hasJointData) { auto startSection = destinationBuffer; - QReadLocker readLock(&_jointDataLock); // joint rotation data - int numJoints = _jointData.size(); + int numJoints = jointData.size(); *destinationBuffer++ = (uint8_t)numJoints; unsigned char* validityPosition = destinationBuffer; unsigned char validity = 0; int validityBit = 0; - int numValidityBytes = (int)std::ceil(numJoints / (float)BITS_IN_BYTE); + int numValidityBytes = calcBitVectorSize(numJoints); #ifdef WANT_DEBUG int rotationSentCount = 0; @@ -589,43 +575,37 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent destinationBuffer += numValidityBytes; // Move pointer past the validity bytes // sentJointDataOut and lastSentJointData might be the same vector - // build sentJointDataOut locally and then swap it at the end. - QVector localSentJointDataOut; if (sentJointDataOut) { - localSentJointDataOut.resize(numJoints); // Make sure the destination is resized before using it + sentJointDataOut->resize(numJoints); // Make sure the destination is resized before using it } - float minRotationDOT = !distanceAdjust ? AVATAR_MIN_ROTATION_DOT : getDistanceBasedMinRotationDOT(viewerPosition); + float minRotationDOT = (distanceAdjust && cullSmallChanges) ? getDistanceBasedMinRotationDOT(viewerPosition) : AVATAR_MIN_ROTATION_DOT; - for (int i = 0; i < _jointData.size(); i++) { - const JointData& data = _jointData[i]; + for (int i = 0; i < jointData.size(); i++) { + const JointData& data = jointData[i]; const JointData& last = lastSentJointData[i]; if (!data.rotationIsDefaultPose) { - bool mustSend = sendAll || last.rotationIsDefaultPose; - if (mustSend || last.rotation != data.rotation) { - - bool largeEnoughRotation = true; - if (cullSmallChanges) { - // The dot product for smaller rotations is a smaller number. - // So if the dot() is less than the value, then the rotation is a larger angle of rotation - largeEnoughRotation = fabsf(glm::dot(last.rotation, data.rotation)) < minRotationDOT; - } - - if (mustSend || !cullSmallChanges || largeEnoughRotation) { - validity |= (1 << validityBit); + // The dot product for larger rotations is a lower number. + // So if the dot() is less than the value, then the rotation is a larger angle of rotation + if (sendAll || last.rotationIsDefaultPose || (!cullSmallChanges && last.rotation != data.rotation) + || (cullSmallChanges && glm::dot(last.rotation, data.rotation) < minRotationDOT) ) { + validity |= (1 << validityBit); #ifdef WANT_DEBUG - rotationSentCount++; + rotationSentCount++; #endif - destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, data.rotation); + destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, data.rotation); - if (sentJointDataOut) { - localSentJointDataOut[i].rotation = data.rotation; - localSentJointDataOut[i].rotationIsDefaultPose = false; - } + if (sentJointDataOut) { + (*sentJointDataOut)[i].rotation = data.rotation; } } } + + if (sentJointDataOut) { + (*sentJointDataOut)[i].rotationIsDefaultPose = data.rotationIsDefaultPose; + } + if (++validityBit == BITS_IN_BYTE) { *validityPosition++ = validity; validityBit = validity = 0; @@ -647,35 +627,38 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent destinationBuffer += numValidityBytes; // Move pointer past the validity bytes - float minTranslation = !distanceAdjust ? AVATAR_MIN_TRANSLATION : getDistanceBasedMinTranslationDistance(viewerPosition); + float minTranslation = (distanceAdjust && cullSmallChanges) ? getDistanceBasedMinTranslationDistance(viewerPosition) : AVATAR_MIN_TRANSLATION; float maxTranslationDimension = 0.0; - for (int i = 0; i < _jointData.size(); i++) { - const JointData& data = _jointData[i]; + for (int i = 0; i < jointData.size(); i++) { + const JointData& data = jointData[i]; const JointData& last = lastSentJointData[i]; if (!data.translationIsDefaultPose) { - bool mustSend = sendAll || last.translationIsDefaultPose; - if (mustSend || last.translation != data.translation) { - if (mustSend || !cullSmallChanges || glm::distance(data.translation, lastSentJointData[i].translation) > minTranslation) { - validity |= (1 << validityBit); + if (sendAll || last.translationIsDefaultPose || (!cullSmallChanges && last.translation != data.translation) + || (cullSmallChanges && glm::distance(data.translation, lastSentJointData[i].translation) > minTranslation)) { + + validity |= (1 << validityBit); #ifdef WANT_DEBUG - translationSentCount++; + translationSentCount++; #endif - maxTranslationDimension = glm::max(fabsf(data.translation.x), maxTranslationDimension); - maxTranslationDimension = glm::max(fabsf(data.translation.y), maxTranslationDimension); - maxTranslationDimension = glm::max(fabsf(data.translation.z), maxTranslationDimension); + maxTranslationDimension = glm::max(fabsf(data.translation.x), maxTranslationDimension); + maxTranslationDimension = glm::max(fabsf(data.translation.y), maxTranslationDimension); + maxTranslationDimension = glm::max(fabsf(data.translation.z), maxTranslationDimension); - destinationBuffer += - packFloatVec3ToSignedTwoByteFixed(destinationBuffer, data.translation, TRANSLATION_COMPRESSION_RADIX); + destinationBuffer += + packFloatVec3ToSignedTwoByteFixed(destinationBuffer, data.translation, TRANSLATION_COMPRESSION_RADIX); - if (sentJointDataOut) { - localSentJointDataOut[i].translation = data.translation; - localSentJointDataOut[i].translationIsDefaultPose = false; - } + if (sentJointDataOut) { + (*sentJointDataOut)[i].translation = data.translation; } } } + + if (sentJointDataOut) { + (*sentJointDataOut)[i].translationIsDefaultPose = data.translationIsDefaultPose; + } + if (++validityBit == BITS_IN_BYTE) { *validityPosition++ = validity; validityBit = validity = 0; @@ -691,6 +674,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, controllerLeftHandTransform.getRotation()); destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, controllerLeftHandTransform.getTranslation(), TRANSLATION_COMPRESSION_RADIX); + Transform controllerRightHandTransform = Transform(getControllerRightHandMatrix()); destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, controllerRightHandTransform.getRotation()); destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, controllerRightHandTransform.getTranslation(), @@ -707,34 +691,27 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent glm::vec3 mouseFarGrabPosition = extractTranslation(mouseFarGrabMatrix); glm::quat mouseFarGrabRotation = extractRotation(mouseFarGrabMatrix); - data->leftFarGrabPosition[0] = leftFarGrabPosition.x; - data->leftFarGrabPosition[1] = leftFarGrabPosition.y; - data->leftFarGrabPosition[2] = leftFarGrabPosition.z; - + AVATAR_MEMCPY(leftFarGrabPosition); + // Can't do block copy as struct order is x, y, z, w. data->leftFarGrabRotation[0] = leftFarGrabRotation.w; data->leftFarGrabRotation[1] = leftFarGrabRotation.x; data->leftFarGrabRotation[2] = leftFarGrabRotation.y; data->leftFarGrabRotation[3] = leftFarGrabRotation.z; + destinationBuffer += sizeof(data->leftFarGrabPosition); - data->rightFarGrabPosition[0] = rightFarGrabPosition.x; - data->rightFarGrabPosition[1] = rightFarGrabPosition.y; - data->rightFarGrabPosition[2] = rightFarGrabPosition.z; - + AVATAR_MEMCPY(rightFarGrabPosition); data->rightFarGrabRotation[0] = rightFarGrabRotation.w; data->rightFarGrabRotation[1] = rightFarGrabRotation.x; data->rightFarGrabRotation[2] = rightFarGrabRotation.y; data->rightFarGrabRotation[3] = rightFarGrabRotation.z; + destinationBuffer += sizeof(data->rightFarGrabRotation); - data->mouseFarGrabPosition[0] = mouseFarGrabPosition.x; - data->mouseFarGrabPosition[1] = mouseFarGrabPosition.y; - data->mouseFarGrabPosition[2] = mouseFarGrabPosition.z; - + AVATAR_MEMCPY(mouseFarGrabPosition); data->mouseFarGrabRotation[0] = mouseFarGrabRotation.w; data->mouseFarGrabRotation[1] = mouseFarGrabRotation.x; data->mouseFarGrabRotation[2] = mouseFarGrabRotation.y; data->mouseFarGrabRotation[3] = mouseFarGrabRotation.z; - - destinationBuffer += sizeof(AvatarDataPacket::FarGrabJoints); + destinationBuffer += sizeof(data->mouseFarGrabRotation); int numBytes = destinationBuffer - startSection; @@ -761,41 +738,23 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent outboundDataRateOut->jointDataRate.increment(numBytes); } - if (sentJointDataOut) { - - // Mark default poses in lastSentJointData, so when they become non-default we send them. - for (int i = 0; i < _jointData.size(); i++) { - const JointData& data = _jointData[i]; - JointData& local = localSentJointDataOut[i]; - if (data.rotationIsDefaultPose) { - local.rotationIsDefaultPose = true; - } - if (data.translationIsDefaultPose) { - local.translationIsDefaultPose = true; - } - } - - // Push new sent joint data to sentJointDataOut - sentJointDataOut->swap(localSentJointDataOut); - } } if (hasJointDefaultPoseFlags) { auto startSection = destinationBuffer; - QReadLocker readLock(&_jointDataLock); // write numJoints - int numJoints = _jointData.size(); + int numJoints = jointData.size(); *destinationBuffer++ = (uint8_t)numJoints; // write rotationIsDefaultPose bits destinationBuffer += writeBitVector(destinationBuffer, numJoints, [&](int i) { - return _jointData[i].rotationIsDefaultPose; + return jointData[i].rotationIsDefaultPose; }); // write translationIsDefaultPose bits destinationBuffer += writeBitVector(destinationBuffer, numJoints, [&](int i) { - return _jointData[i].translationIsDefaultPose; + return jointData[i].translationIsDefaultPose; }); if (outboundDataRateOut) { @@ -880,7 +839,6 @@ const unsigned char* unpackFauxJoint(const unsigned char* sourceBuffer, ThreadSa // read data in packet starting at byte offset and return number of bytes parsed int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { - // lazily allocate memory for HeadData in case we're not an Avatar instance lazyInitHeadData(); @@ -932,7 +890,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { auto newValue = glm::vec3(data->globalPosition[0], data->globalPosition[1], data->globalPosition[2]) + offset; if (_globalPosition != newValue) { _globalPosition = newValue; - _globalPositionChanged = usecTimestampNow(); + _globalPositionChanged = now; } sourceBuffer += sizeof(AvatarDataPacket::AvatarGlobalPosition); int numBytesRead = sourceBuffer - startSection; @@ -956,11 +914,11 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { if (_globalBoundingBoxDimensions != newDimensions) { _globalBoundingBoxDimensions = newDimensions; - _avatarBoundingBoxChanged = usecTimestampNow(); + _avatarBoundingBoxChanged = now; } if (_globalBoundingBoxOffset != newOffset) { _globalBoundingBoxOffset = newOffset; - _avatarBoundingBoxChanged = usecTimestampNow(); + _avatarBoundingBoxChanged = now; } sourceBuffer += sizeof(AvatarDataPacket::AvatarBoundingBox); @@ -1061,7 +1019,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { glm::mat4 sensorToWorldMatrix = createMatFromScaleQuatAndPos(glm::vec3(sensorToWorldScale), sensorToWorldQuat, sensorToWorldTrans); if (_sensorToWorldMatrixCache.get() != sensorToWorldMatrix) { _sensorToWorldMatrixCache.set(sensorToWorldMatrix); - _sensorToWorldMatrixChanged = usecTimestampNow(); + _sensorToWorldMatrixChanged = now; } sourceBuffer += sizeof(AvatarDataPacket::SensorToWorldMatrix); int numBytesRead = sourceBuffer - startSection; @@ -1118,7 +1076,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { sourceBuffer += sizeof(AvatarDataPacket::AdditionalFlags); if (somethingChanged) { - _additionalFlagsChanged = usecTimestampNow(); + _additionalFlagsChanged = now; } int numBytesRead = sourceBuffer - startSection; _additionalFlagsRate.increment(numBytesRead); @@ -1138,7 +1096,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { if ((getParentID() != newParentID) || (getParentJointIndex() != parentInfo->parentJointIndex)) { SpatiallyNestable::setParentID(newParentID); SpatiallyNestable::setParentJointIndex(parentInfo->parentJointIndex); - _parentChanged = usecTimestampNow(); + _parentChanged = now; } int numBytesRead = sourceBuffer - startSection; @@ -1187,8 +1145,6 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { int numBytesRead = sourceBuffer - startSection; _faceTrackerRate.increment(numBytesRead); _faceTrackerUpdateRate.increment(); - } else { - _headData->_blendshapeCoefficients.fill(0, _headData->_blendshapeCoefficients.size()); } if (hasJointData) { @@ -2873,10 +2829,8 @@ void RayToAvatarIntersectionResultFromScriptValue(const QScriptValue& object, Ra value.extraInfo = object.property("extraInfo").toVariant().toMap(); } -const float AvatarData::OUT_OF_VIEW_PENALTY = -10.0f; - -float AvatarData::_avatarSortCoefficientSize { 1.0f }; -float AvatarData::_avatarSortCoefficientCenter { 0.25 }; +float AvatarData::_avatarSortCoefficientSize { 8.0f }; +float AvatarData::_avatarSortCoefficientCenter { 4.0f }; float AvatarData::_avatarSortCoefficientAge { 1.0f }; QScriptValue AvatarEntityMapToScriptValue(QScriptEngine* engine, const AvatarEntityMap& value) { diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index db2b82b0b7..056e745370 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -1097,7 +1097,7 @@ public: void fromJson(const QJsonObject& json, bool useFrameSkeleton = true); glm::vec3 getClientGlobalPosition() const { return _globalPosition; } - glm::vec3 getGlobalBoundingBoxCorner() const { return _globalPosition + _globalBoundingBoxOffset - _globalBoundingBoxDimensions; } + AABox getGlobalBoundingBox() const { return AABox(_globalPosition + _globalBoundingBoxOffset - _globalBoundingBoxDimensions, _globalBoundingBoxDimensions); } /**jsdoc * @function MyAvatar.getAvatarEntityData @@ -1169,8 +1169,6 @@ public: // A method intended to be overriden by MyAvatar for polling orientation for network transmission. virtual glm::quat getOrientationOutbound() const; - static const float OUT_OF_VIEW_PENALTY; - // TODO: remove this HACK once we settle on optimal sort coefficients // These coefficients exposed for fine tuning the sort priority for transfering new _jointData to the render pipeline. static float _avatarSortCoefficientSize; diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 5b3196a2bf..d99c0020da 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -538,7 +538,6 @@ void AccountManager::requestAccessToken(const QString& login, const QString& pas QNetworkReply* requestReply = networkAccessManager.post(request, postData); connect(requestReply, &QNetworkReply::finished, this, &AccountManager::requestAccessTokenFinished); - connect(requestReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestAccessTokenError(QNetworkReply::NetworkError))); } void AccountManager::requestAccessTokenWithSteam(QByteArray authSessionTicket) { @@ -633,12 +632,6 @@ void AccountManager::requestAccessTokenFinished() { } } -void AccountManager::requestAccessTokenError(QNetworkReply::NetworkError error) { - // TODO: error handling - qCDebug(networking) << "AccountManager: failed to fetch access token - " << error; - emit loginFailed(); -} - void AccountManager::refreshAccessTokenFinished() { QNetworkReply* requestReply = reinterpret_cast(sender()); diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index b122115dd0..f3b81cf1c9 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -106,7 +106,6 @@ public slots: void requestAccessTokenFinished(); void refreshAccessTokenFinished(); void requestProfileFinished(); - void requestAccessTokenError(QNetworkReply::NetworkError error); void refreshAccessTokenError(QNetworkReply::NetworkError error); void requestProfileError(QNetworkReply::NetworkError error); void logout(); diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 9e5cbcaa7b..8085039b02 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -816,8 +816,10 @@ bool AddressManager::setDomainInfo(const QUrl& domainURL, LookupTrigger trigger) const QString hostname = domainURL.host(); quint16 port = domainURL.port(); bool emitHostChanged { false }; + // Check if domain handler is in error state. always emit host changed if true. + bool isInErrorState = DependencyManager::get()->getDomainHandler().isInErrorState(); - if (domainURL != _domainURL) { + if (domainURL != _domainURL || isInErrorState) { addCurrentAddressToHistory(trigger); emitHostChanged = true; } diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index 59e3de922f..4c9231b7c9 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -55,6 +55,9 @@ DomainHandler::DomainHandler(QObject* parent) : // stop the refresh timer if we connect to a domain connect(this, &DomainHandler::connectedToDomain, &_apiRefreshTimer, &QTimer::stop); + + // stop the refresh timer if redirected to the error domain + connect(this, &DomainHandler::redirectToErrorDomainURL, &_apiRefreshTimer, &QTimer::stop); } void DomainHandler::disconnect() { @@ -107,13 +110,15 @@ void DomainHandler::softReset() { QMetaObject::invokeMethod(&_settingsTimer, "stop"); // restart the API refresh timer in case we fail to connect and need to refresh information - QMetaObject::invokeMethod(&_apiRefreshTimer, "start"); + if (!_isInErrorState) { + QMetaObject::invokeMethod(&_apiRefreshTimer, "start"); } void DomainHandler::hardReset() { emit resetting(); softReset(); + _isInErrorState = false; qCDebug(networking) << "Hard reset in NodeList DomainHandler."; _pendingDomainID = QUuid(); diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index ab20936f43..90da86a929 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -172,6 +172,11 @@ public slots: void processICEResponsePacket(QSharedPointer icePacket); void processDomainServerConnectionDeniedPacket(QSharedPointer message); + // sets domain handler in error state. + void setRedirectErrorState(QUrl errorUrl, int reasonCode); + + bool isInErrorState() { return _isInErrorState; } + private slots: void completedHostnameLookup(const QHostInfo& hostInfo); void completedIceServerHostnameLookup(); diff --git a/libraries/networking/src/udt/Socket.cpp b/libraries/networking/src/udt/Socket.cpp index c378987cd0..44220df8f8 100644 --- a/libraries/networking/src/udt/Socket.cpp +++ b/libraries/networking/src/udt/Socket.cpp @@ -37,6 +37,7 @@ Socket::Socket(QObject* parent, bool shouldChangeSocketOptions) : _shouldChangeSocketOptions(shouldChangeSocketOptions) { connect(&_udpSocket, &QUdpSocket::readyRead, this, &Socket::readPendingDatagrams); + connect(this, &Socket::pendingDatagrams, this, &Socket::processPendingDatagrams, Qt::QueuedConnection); // make sure we hear about errors and state changes from the underlying socket connect(&_udpSocket, SIGNAL(error(QAbstractSocket::SocketError)), @@ -315,55 +316,85 @@ void Socket::checkForReadyReadBackup() { } void Socket::readPendingDatagrams() { + int packetsRead = 0; + int packetSizeWithHeader = -1; - - while (_udpSocket.hasPendingDatagrams() && (packetSizeWithHeader = _udpSocket.pendingDatagramSize()) != -1) { - - // we're reading a packet so re-start the readyRead backup timer - _readyReadBackupTimer->start(); - + // Max datagrams to read before processing: + static const int MAX_DATAGRAMS_CONSECUTIVELY = 10000; + while (_udpSocket.hasPendingDatagrams() + && (packetSizeWithHeader = _udpSocket.pendingDatagramSize()) != -1 + && packetsRead <= MAX_DATAGRAMS_CONSECUTIVELY) { // grab a time point we can mark as the receive time of this packet auto receiveTime = p_high_resolution_clock::now(); - // setup a HifiSockAddr to read into - HifiSockAddr senderSockAddr; // setup a buffer to read the packet into auto buffer = std::unique_ptr(new char[packetSizeWithHeader]); + QHostAddress senderAddress; + quint16 senderPort; + // pull the datagram auto sizeRead = _udpSocket.readDatagram(buffer.get(), packetSizeWithHeader, - senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer()); + &senderAddress, &senderPort); - // save information for this packet, in case it is the one that sticks readyRead - _lastPacketSizeRead = sizeRead; - _lastPacketSockAddr = senderSockAddr; - - if (sizeRead <= 0) { - // we either didn't pull anything for this packet or there was an error reading (this seems to trigger - // on windows even if there's not a packet available) + // we either didn't pull anything for this packet or there was an error reading (this seems to trigger + // on windows even if there's not a packet available) + if (sizeRead < 0) { continue; } - auto it = _unfilteredHandlers.find(senderSockAddr); + _incomingDatagrams.push_back({ senderAddress, senderPort, packetSizeWithHeader, + std::move(buffer), receiveTime }); + ++packetsRead; + } + + if (packetsRead > _maxDatagramsRead) { + _maxDatagramsRead = packetsRead; + qCDebug(networking) << "readPendingDatagrams: Datagrams read:" << packetsRead; + } + emit pendingDatagrams(packetsRead); +} + +void Socket::processPendingDatagrams(int) { + // setup a HifiSockAddr to read into + HifiSockAddr senderSockAddr; + + while (!_incomingDatagrams.empty()) { + auto& datagram = _incomingDatagrams.front(); + senderSockAddr.setAddress(datagram._senderAddress); + senderSockAddr.setPort(datagram._senderPort); + int datagramSize = datagram._datagramLength; + auto receiveTime = datagram._receiveTime; + + // we're reading a packet so re-start the readyRead backup timer + _readyReadBackupTimer->start(); + + // save information for this packet, in case it is the one that sticks readyRead + _lastPacketSizeRead = datagramSize; + _lastPacketSockAddr = senderSockAddr; + + // Process unfiltered packets first. + auto it = _unfilteredHandlers.find(senderSockAddr); if (it != _unfilteredHandlers.end()) { - // we have a registered unfiltered handler for this HifiSockAddr - call that and return + // we have a registered unfiltered handler for this HifiSockAddr (eg. STUN packet) - call that and return if (it->second) { - auto basePacket = BasePacket::fromReceivedPacket(std::move(buffer), packetSizeWithHeader, senderSockAddr); + auto basePacket = BasePacket::fromReceivedPacket(std::move(datagram._datagram), + datagramSize, senderSockAddr); basePacket->setReceiveTime(receiveTime); it->second(std::move(basePacket)); } - + _incomingDatagrams.pop_front(); continue; } // check if this was a control packet or a data packet - bool isControlPacket = *reinterpret_cast(buffer.get()) & CONTROL_BIT_MASK; + bool isControlPacket = *reinterpret_cast(datagram._datagram.get()) & CONTROL_BIT_MASK; if (isControlPacket) { // setup a control packet from the data we just read - auto controlPacket = ControlPacket::fromReceivedPacket(std::move(buffer), packetSizeWithHeader, senderSockAddr); + auto controlPacket = ControlPacket::fromReceivedPacket(std::move(datagram._datagram), datagramSize, senderSockAddr); controlPacket->setReceiveTime(receiveTime); // move this control packet to the matching connection, if there is one @@ -375,13 +406,13 @@ void Socket::readPendingDatagrams() { } else { // setup a Packet from the data we just read - auto packet = Packet::fromReceivedPacket(std::move(buffer), packetSizeWithHeader, senderSockAddr); + auto packet = Packet::fromReceivedPacket(std::move(datagram._datagram), datagramSize, senderSockAddr); packet->setReceiveTime(receiveTime); // save the sequence number in case this is the packet that sticks readyRead _lastReceivedSequenceNumber = packet->getSequenceNumber(); - // call our verification operator to see if this packet is verified + // call our hash verification operator to see if this packet is verified if (!_packetFilterOperator || _packetFilterOperator(*packet)) { if (packet->isReliable()) { // if this was a reliable packet then signal the matching connection with the sequence number @@ -395,6 +426,7 @@ void Socket::readPendingDatagrams() { qCDebug(networking) << "Can't process packet: version" << (unsigned int)NLPacket::versionInHeader(*packet) << ", type" << NLPacket::typeInHeader(*packet); #endif + _incomingDatagrams.pop_front(); continue; } } @@ -410,6 +442,8 @@ void Socket::readPendingDatagrams() { } } } + + _incomingDatagrams.pop_front(); } } diff --git a/libraries/networking/src/udt/Socket.h b/libraries/networking/src/udt/Socket.h index 1f28592c83..ef4a457116 100644 --- a/libraries/networking/src/udt/Socket.h +++ b/libraries/networking/src/udt/Socket.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -94,6 +95,7 @@ public: signals: void clientHandshakeRequestComplete(const HifiSockAddr& sockAddr); + void pendingDatagrams(int datagramCount); public slots: void cleanupConnection(HifiSockAddr sockAddr); @@ -101,6 +103,7 @@ public slots: private slots: void readPendingDatagrams(); + void processPendingDatagrams(int datagramCount); void checkForReadyReadBackup(); void handleSocketError(QAbstractSocket::SocketError socketError); @@ -144,6 +147,17 @@ private: int _lastPacketSizeRead { 0 }; SequenceNumber _lastReceivedSequenceNumber; HifiSockAddr _lastPacketSockAddr; + + struct Datagram { + QHostAddress _senderAddress; + int _senderPort; + int _datagramLength; + std::unique_ptr _datagram; + p_high_resolution_clock::time_point _receiveTime; + }; + + std::list _incomingDatagrams; + int _maxDatagramsRead { 0 }; friend UDTTest; }; diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index 58c197d6f4..871958f883 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -286,6 +286,7 @@ void PhysicsEngine::reinsertObject(ObjectMotionState* object) { void PhysicsEngine::processTransaction(PhysicsEngine::Transaction& transaction) { // removes for (auto object : transaction.objectsToRemove) { + bumpAndPruneContacts(object); btRigidBody* body = object->getRigidBody(); if (body) { removeDynamicsForBody(body); diff --git a/libraries/shared/src/PrioritySortUtil.h b/libraries/shared/src/PrioritySortUtil.h index 8ded047212..27f6b193ba 100644 --- a/libraries/shared/src/PrioritySortUtil.h +++ b/libraries/shared/src/PrioritySortUtil.h @@ -16,55 +16,16 @@ #include "NumericalConstants.h" #include "shared/ConicalViewFrustum.h" -/* PrioritySortUtil is a helper for sorting 3D things relative to a ViewFrustum. To use: +// PrioritySortUtil is a helper for sorting 3D things relative to a ViewFrustum. -(1) Derive a class from pure-virtual PrioritySortUtil::Sortable that wraps a copy of - the Thing you want to prioritize and sort: - - class SortableWrapper: public PrioritySortUtil::Sortable { - public: - SortableWrapper(const Thing& thing) : _thing(thing) { } - glm::vec3 getPosition() const override { return _thing->getPosition(); } - float getRadius() const override { return 0.5f * _thing->getBoundingRadius(); } - uint64_t getTimestamp() const override { return _thing->getLastTime(); } - Thing getThing() const { return _thing; } - private: - Thing _thing; - }; - -(2) Make a PrioritySortUtil::PriorityQueue and add them to the queue: - - PrioritySortUtil::PriorityQueue sortedThings(viewFrustum); - std::priority_queue< PrioritySortUtil::Sortable > sortedThings; - for (thing in things) { - sortedThings.push(SortableWrapper(thing)); - } - -(3) Loop over your priority queue and do timeboxed work: - - NOTE: Be careful using references to members of instances of T from std::priority_queue. - Under the hood std::priority_queue may re-use instances of T. - For example, after a pop() or a push() the top T may have the same memory address - as the top T before the pop() or push() (but point to a swapped instance of T). - This causes a reference to member variable of T to point to a different value - when operations taken on std::priority_queue shuffle around the instances of T. - - uint64_t cutoffTime = usecTimestampNow() + TIME_BUDGET; - while (!sortedThings.empty()) { - const Thing& thing = sortedThings.top(); - // ...do work on thing... - sortedThings.pop(); - if (usecTimestampNow() > cutoffTime) { - break; - } - } -*/ +const float OUT_OF_VIEW_PENALTY = -10.0f; +const float OUT_OF_VIEW_THRESHOLD = 0.5f * OUT_OF_VIEW_PENALTY; namespace PrioritySortUtil { constexpr float DEFAULT_ANGULAR_COEF { 1.0f }; constexpr float DEFAULT_CENTER_COEF { 0.5f }; - constexpr float DEFAULT_AGE_COEF { 0.25f / (float)(USECS_PER_SECOND) }; + constexpr float DEFAULT_AGE_COEF { 0.25f }; class Sortable { public: @@ -84,8 +45,9 @@ namespace PrioritySortUtil { PriorityQueue() = delete; PriorityQueue(const ConicalViewFrustums& views) : _views(views) { } PriorityQueue(const ConicalViewFrustums& views, float angularWeight, float centerWeight, float ageWeight) - : _views(views), _angularWeight(angularWeight), _centerWeight(centerWeight), _ageWeight(ageWeight) - { } + : _views(views), _angularWeight(angularWeight), _centerWeight(centerWeight), _ageWeight(ageWeight) + , _usecCurrentTime(usecTimestampNow()) { + } void setViews(const ConicalViewFrustums& views) { _views = views; } @@ -93,6 +55,7 @@ namespace PrioritySortUtil { _angularWeight = angularWeight; _centerWeight = centerWeight; _ageWeight = ageWeight; + _usecCurrentTime = usecTimestampNow(); } size_t size() const { return _vector.size(); } @@ -131,23 +94,18 @@ namespace PrioritySortUtil { glm::vec3 offset = position - view.getPosition(); float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero const float MIN_RADIUS = 0.1f; // WORKAROUND for zero size objects (we still want them to sort by distance) - float radius = glm::min(thing.getRadius(), MIN_RADIUS); - float cosineAngle = (glm::dot(offset, view.getDirection()) / distance); - float age = (float)(usecTimestampNow() - thing.getTimestamp()); + float radius = glm::max(thing.getRadius(), MIN_RADIUS); + // Other item's angle from view centre: + float cosineAngle = glm::dot(offset, view.getDirection()) / distance; + float age = float((_usecCurrentTime - thing.getTimestamp()) / USECS_PER_SECOND); - // we modulatate "age" drift rate by the cosineAngle term to make periphrial objects sort forward - // at a reduced rate but we don't want the "age" term to go zero or negative so we clamp it - const float MIN_COSINE_ANGLE_FACTOR = 0.1f; - float cosineAngleFactor = glm::max(cosineAngle, MIN_COSINE_ANGLE_FACTOR); - - float priority = _angularWeight * glm::max(radius, MIN_RADIUS) / distance - + _centerWeight * cosineAngle - + _ageWeight * cosineAngleFactor * age; + // the "age" term accumulates at the sum of all weights + float angularSize = radius / distance; + float priority = (_angularWeight * angularSize + _centerWeight * cosineAngle) * (age + 1.0f) + _ageWeight * age; // decrement priority of things outside keyhole if (distance - radius > view.getRadius()) { if (!view.intersects(offset, distance, radius)) { - constexpr float OUT_OF_VIEW_PENALTY = -10.0f; priority += OUT_OF_VIEW_PENALTY; } } @@ -159,12 +117,13 @@ namespace PrioritySortUtil { float _angularWeight { DEFAULT_ANGULAR_COEF }; float _centerWeight { DEFAULT_CENTER_COEF }; float _ageWeight { DEFAULT_AGE_COEF }; + quint64 _usecCurrentTime { 0 }; }; } // namespace PrioritySortUtil -// for now we're keeping hard-coded sorted time budgets in one spot + // for now we're keeping hard-coded sorted time budgets in one spot const uint64_t MAX_UPDATE_RENDERABLES_TIME_BUDGET = 2000; // usec const uint64_t MIN_SORTED_UPDATE_RENDERABLES_TIME_BUDGET = 1000; // usec +const uint64_t MAX_UPDATE_AVATARS_TIME_BUDGET = 2000; // usec #endif // hifi_PrioritySortUtil_h - diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index 31510831c8..aaf5ca7260 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -35,10 +35,13 @@ var DEFAULT_SCRIPTS_COMBINED = [ ]; var DEFAULT_SCRIPTS_SEPARATE = [ "system/controllers/controllerScripts.js", - "system/interstitialPage.js" //"system/chat.js" ]; +if (Settings.getValue("enableInterstitialMode", false)) { + DEFAULT_SCRIPTS_SEPARATE.push("system/interstitialPage.js"); +} + // add a menu item for debugging var MENU_CATEGORY = "Developer"; var MENU_ITEM = "Debug defaultScripts.js"; diff --git a/scripts/system/edit.js b/scripts/system/edit.js index ed2a179613..88ae2852be 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -860,7 +860,7 @@ var toolBar = (function () { propertiesTool.setVisible(false); selectionManager.clearSelections(); cameraManager.disable(); - selectionDisplay.triggerMapping.disable(); + selectionDisplay.disableTriggerMapping(); tablet.landscape = false; Controller.disableMapping(CONTROLLER_MAPPING_NAME); } else { @@ -876,7 +876,7 @@ var toolBar = (function () { gridTool.setVisible(true); grid.setEnabled(true); propertiesTool.setVisible(true); - selectionDisplay.triggerMapping.enable(); + selectionDisplay.enableTriggerMapping(); print("starting tablet in landscape mode"); tablet.landscape = true; Controller.enableMapping(CONTROLLER_MAPPING_NAME); diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 05f5e41950..5bca58b1ac 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -14,7 +14,7 @@ // /* global SelectionManager, SelectionDisplay, grid, rayPlaneIntersection, rayPlaneIntersection2, pushCommandForSelections, - getMainTabletIDs, getControllerWorldLocation */ + getMainTabletIDs, getControllerWorldLocation, TRIGGER_ON_VALUE */ var SPACE_LOCAL = "local"; var SPACE_WORLD = "world"; @@ -22,6 +22,7 @@ var HIGHLIGHT_LIST_NAME = "editHandleHighlightList"; Script.include([ "./controllers.js", + "./controllerDispatcherUtils.js", "./utils.js" ]); @@ -448,6 +449,8 @@ SelectionDisplay = (function() { var CTRL_KEY_CODE = 16777249; var RAIL_AXIS_LENGTH = 10000; + + var NO_HAND = -1; var TRANSLATE_DIRECTION = { X: 0, @@ -478,8 +481,6 @@ SelectionDisplay = (function() { YAW: 1, ROLL: 2 }; - - var NO_TRIGGER_HAND = -1; var spaceMode = SPACE_LOCAL; var overlayNames = []; @@ -802,11 +803,21 @@ SelectionDisplay = (function() { // We get mouseMoveEvents from the handControllers, via handControllerPointer. // But we dont' get mousePressEvents. - that.triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click'); - Script.scriptEnding.connect(that.triggerMapping.disable); - that.triggeredHand = NO_TRIGGER_HAND; + that.triggerClickMapping = Controller.newMapping(Script.resolvePath('') + '-click'); + that.triggerPressMapping = Controller.newMapping(Script.resolvePath('') + '-press'); + that.triggeredHand = NO_HAND; + that.pressedHand = NO_HAND; that.triggered = function() { - return that.triggeredHand !== NO_TRIGGER_HAND; + return that.triggeredHand !== NO_HAND; + } + function pointingAtDesktopWindowOrTablet(hand) { + var pointingAtDesktopWindow = (hand === Controller.Standard.RightHand && + SelectionManager.pointingAtDesktopWindowRight) || + (hand === Controller.Standard.LeftHand && + SelectionManager.pointingAtDesktopWindowLeft); + var pointingAtTablet = (hand === Controller.Standard.RightHand && SelectionManager.pointingAtTabletRight) || + (hand === Controller.Standard.LeftHand && SelectionManager.pointingAtTabletLeft); + return pointingAtDesktopWindow || pointingAtTablet; } function makeClickHandler(hand) { return function (clicked) { @@ -814,26 +825,39 @@ SelectionDisplay = (function() { if (that.triggered() && hand !== that.triggeredHand) { return; } - if (!that.triggered() && clicked) { - var pointingAtDesktopWindow = (hand === Controller.Standard.RightHand && - SelectionManager.pointingAtDesktopWindowRight) || - (hand === Controller.Standard.LeftHand && - SelectionManager.pointingAtDesktopWindowLeft); - var pointingAtTablet = (hand === Controller.Standard.RightHand && SelectionManager.pointingAtTabletRight) || - (hand === Controller.Standard.LeftHand && SelectionManager.pointingAtTabletLeft); - if (pointingAtDesktopWindow || pointingAtTablet) { - return; - } + if (!that.triggered() && clicked && !pointingAtDesktopWindowOrTablet(hand)) { that.triggeredHand = hand; that.mousePressEvent({}); } else if (that.triggered() && !clicked) { - that.triggeredHand = NO_TRIGGER_HAND; + that.triggeredHand = NO_HAND; that.mouseReleaseEvent({}); } }; } - that.triggerMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand)); - that.triggerMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand)); + function makePressHandler(hand) { + return function (value) { + if (value >= TRIGGER_ON_VALUE && !that.triggered() && !pointingAtDesktopWindowOrTablet(hand)) { + that.pressedHand = hand; + that.updateHighlight({}); + } else { + that.pressedHand = NO_HAND; + that.resetPreviousHandleColor(); + } + } + } + that.triggerClickMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand)); + that.triggerClickMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand)); + that.triggerPressMapping.from(Controller.Standard.RT).peek().to(makePressHandler(Controller.Standard.RightHand)); + that.triggerPressMapping.from(Controller.Standard.LT).peek().to(makePressHandler(Controller.Standard.LeftHand)); + that.enableTriggerMapping = function() { + that.triggerClickMapping.enable(); + that.triggerPressMapping.enable(); + }; + that.disableTriggerMapping = function() { + that.triggerClickMapping.disable(); + that.triggerPressMapping.disable(); + } + Script.scriptEnding.connect(that.disableTriggerMapping); // FUNCTION DEF(s): Intersection Check Helpers function testRayIntersect(queryRay, overlayIncludes, overlayExcludes) { @@ -960,35 +984,10 @@ SelectionDisplay = (function() { } return Uuid.NULL; }; - - // FUNCTION: MOUSE MOVE EVENT - var lastMouseEvent = null; - that.mouseMoveEvent = function(event) { - var wantDebug = false; - if (wantDebug) { - print("=============== eST::MouseMoveEvent BEG ======================="); - } - lastMouseEvent = event; - if (activeTool) { - if (wantDebug) { - print(" Trigger ActiveTool(" + activeTool.mode + ")'s onMove"); - } - activeTool.onMove(event); - - if (wantDebug) { - print(" Trigger SelectionManager::update"); - } - SelectionManager._update(); - - if (wantDebug) { - print("=============== eST::MouseMoveEvent END ======================="); - } - // EARLY EXIT--(Move handled via active tool) - return true; - } - + + that.updateHighlight = function(event) { // if no tool is active, then just look for handles to highlight... - var pickRay = generalComputePickRay(event.x, event.y); + var pickRay = generalComputePickRay(event.x, event.y); var result = Overlays.findRayIntersection(pickRay); var pickedColor; var highlightNeeded = false; @@ -1039,7 +1038,36 @@ SelectionDisplay = (function() { } else { that.resetPreviousHandleColor(); } + }; + // FUNCTION: MOUSE MOVE EVENT + var lastMouseEvent = null; + that.mouseMoveEvent = function(event) { + var wantDebug = false; + if (wantDebug) { + print("=============== eST::MouseMoveEvent BEG ======================="); + } + lastMouseEvent = event; + if (activeTool) { + if (wantDebug) { + print(" Trigger ActiveTool(" + activeTool.mode + ")'s onMove"); + } + activeTool.onMove(event); + + if (wantDebug) { + print(" Trigger SelectionManager::update"); + } + SelectionManager._update(); + + if (wantDebug) { + print("=============== eST::MouseMoveEvent END ======================="); + } + // EARLY EXIT--(Move handled via active tool) + return true; + } + + that.updateHighlight(event); + if (wantDebug) { print("=============== eST::MouseMoveEvent END ======================="); } @@ -1135,9 +1163,10 @@ SelectionDisplay = (function() { } }; - function controllerComputePickRay() { - var controllerPose = getControllerWorldLocation(that.triggeredHand, true); - if (controllerPose.valid && that.triggered()) { + function controllerComputePickRay(hand) { + var hand = that.triggered() ? that.triggeredHand : that.pressedHand; + var controllerPose = getControllerWorldLocation(hand, true); + if (controllerPose.valid) { var controllerPosition = controllerPose.translation; // This gets point direction right, but if you want general quaternion it would be more complicated: var controllerDirection = Quat.getUp(controllerPose.rotation); @@ -1148,6 +1177,12 @@ SelectionDisplay = (function() { function generalComputePickRay(x, y) { return controllerComputePickRay() || Camera.computePickRay(x, y); } + + function getControllerAvatarFramePositionFromPickRay(pickRay) { + var controllerPosition = Vec3.subtract(pickRay.origin, MyAvatar.position); + controllerPosition = Vec3.multiplyQbyV(Quat.inverse(MyAvatar.orientation), controllerPosition); + return controllerPosition; + } function getDistanceToCamera(position) { var cameraPosition = Camera.getPosition(); @@ -2083,6 +2118,7 @@ SelectionDisplay = (function() { var rotation = null; var previousPickRay = null; var beginMouseEvent = null; + var beginControllerPosition = null; var onBegin = function(event, pickRay, pickResult) { var proportional = directionEnum === STRETCH_DIRECTION.ALL; @@ -2218,6 +2254,9 @@ SelectionDisplay = (function() { previousPickRay = pickRay; beginMouseEvent = event; + if (that.triggered()) { + beginControllerPosition = getControllerAvatarFramePositionFromPickRay(pickRay); + } }; var onEnd = function(event, reason) { @@ -2256,13 +2295,15 @@ SelectionDisplay = (function() { if (usePreviousPickRay(pickRay.direction, previousPickRay.direction, planeNormal)) { pickRay = previousPickRay; } + + var controllerPose = getControllerWorldLocation(that.triggeredHand, true); + var controllerTrigger = HMD.isHMDAvailable() && HMD.isHandControllerAvailable() && + controllerPose.valid && that.triggered(); // Are we using handControllers or Mouse - only relevant for 3D tools - var controllerPose = getControllerWorldLocation(that.triggeredHand, true); var vector = null; var newPick = null; - if (HMD.isHMDAvailable() && HMD.isHandControllerAvailable() && - controllerPose.valid && that.triggered() && directionFor3DStretch) { + if (controllerTrigger && directionFor3DStretch) { localDeltaPivot = deltaPivot3D; newPick = pickRay.origin; vector = Vec3.subtract(newPick, lastPick3D); @@ -2286,12 +2327,23 @@ SelectionDisplay = (function() { var newDimensions; if (proportional) { var viewportDimensions = Controller.getViewportDimensions(); - var mouseXDifference = (event.x - beginMouseEvent.x) / viewportDimensions.x; - var mouseYDifference = (beginMouseEvent.y - event.y) / viewportDimensions.y; - var mouseDifference = mouseXDifference + mouseYDifference; var toCameraDistance = getDistanceToCamera(position); var dimensionsMultiple = toCameraDistance * STRETCH_DIRECTION_ALL_CAMERA_DISTANCE_MULTIPLE; - var dimensionChange = mouseDifference * dimensionsMultiple; + + var dimensionChange; + if (controllerTrigger) { + var controllerPosition = getControllerAvatarFramePositionFromPickRay(pickRay); + var vecControllerDifference = Vec3.subtract(controllerPosition, beginControllerPosition); + var controllerDifference = vecControllerDifference.x + vecControllerDifference.y + + vecControllerDifference.z; + dimensionChange = controllerDifference * dimensionsMultiple; + } else { + var mouseXDifference = (event.x - beginMouseEvent.x) / viewportDimensions.x; + var mouseYDifference = (beginMouseEvent.y - event.y) / viewportDimensions.y; + var mouseDifference = mouseXDifference + mouseYDifference; + dimensionChange = mouseDifference * dimensionsMultiple; + } + var averageInitialDimension = (initialDimensions.x + initialDimensions.y + initialDimensions.z) / 3; percentChange = dimensionChange / averageInitialDimension; percentChange += 1.0;