From be7729b27694102de36de256e88d57ee663b5b9b Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 28 Sep 2017 12:54:26 -0700 Subject: [PATCH 01/88] support animation of model overlays --- interface/src/ui/overlays/ModelOverlay.cpp | 210 +++++++++++++++++++++ interface/src/ui/overlays/ModelOverlay.h | 31 +++ 2 files changed, 241 insertions(+) diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index ca5ca54144..713115bffc 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -9,6 +9,9 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include +#include + #include "ModelOverlay.h" #include @@ -60,6 +63,15 @@ void ModelOverlay::update(float deltatime) { _model->simulate(deltatime); } _isLoaded = _model->isActive(); + + + if (isAnimatingSomething()) { + if (!jointsMapped()) { + mapAnimationJoints(_model->getJointNames()); + } + animate(); + } + } bool ModelOverlay::addToScene(Overlay::Pointer overlay, const render::ScenePointer& scene, render::Transaction& transaction) { @@ -172,6 +184,51 @@ void ModelOverlay::setProperties(const QVariantMap& properties) { } _updateModel = true; } + + auto animationSettings = properties["animationSettings"]; + if (animationSettings.canConvert(QVariant::Map)) { + QVariantMap animationSettingsMap = animationSettings.toMap(); + + auto animationURL = animationSettingsMap["url"]; + auto animationFPS = animationSettingsMap["fps"]; + auto animationCurrentFrame = animationSettingsMap["currentFrame"]; + auto animationFirstFrame = animationSettingsMap["firstFrame"]; + auto animationLastFrame = animationSettingsMap["lastFrame"]; + auto animationRunning = animationSettingsMap["running"]; + auto animationLoop = animationSettingsMap["loop"]; + auto animationHold = animationSettingsMap["hold"]; + auto animationAllowTranslation = animationSettingsMap["allowTranslation"]; + + if (animationURL.canConvert(QVariant::Url)) { + _animationURL = animationURL.toUrl(); + } + if (animationFPS.isValid()) { + _animationFPS = animationFPS.toFloat(); + } + if (animationCurrentFrame.isValid()) { + _animationCurrentFrame = animationCurrentFrame.toFloat(); + } + if (animationFirstFrame.isValid()) { + _animationFirstFrame = animationFirstFrame.toFloat(); + } + if (animationLastFrame.isValid()) { + _animationLastFrame = animationLastFrame.toFloat(); + } + + if (animationRunning.canConvert(QVariant::Bool)) { + _animationRunning = animationRunning.toBool(); + } + if (animationLoop.canConvert(QVariant::Bool)) { + _animationLoop = animationLoop.toBool(); + } + if (animationHold.canConvert(QVariant::Bool)) { + _animationHold = animationHold.toBool(); + } + if (animationAllowTranslation.canConvert(QVariant::Bool)) { + _animationAllowTranslation = animationAllowTranslation.toBool(); + } + + } } template @@ -259,6 +316,24 @@ QVariant ModelOverlay::getProperty(const QString& property) { }); } + // animation properties + if (property == "animationSettings") { + QVariantMap animationSettingsMap; + + animationSettingsMap["url"] = _animationURL; + animationSettingsMap["fps"] = _animationFPS; + animationSettingsMap["currentFrame"] = _animationCurrentFrame; + animationSettingsMap["firstFrame"] = _animationFirstFrame; + animationSettingsMap["lastFrame"] = _animationLastFrame; + animationSettingsMap["running"] = _animationRunning; + animationSettingsMap["loop"] = _animationLoop; + animationSettingsMap["hold"]= _animationHold; + animationSettingsMap["allowTranslation"] = _animationAllowTranslation; + + return animationSettingsMap; + } + + return Volume3DOverlay::getProperty(property); } @@ -301,3 +376,138 @@ QString ModelOverlay::getName() const { } return QString("Overlay:") + getType() + ":" + _url.toString(); } + + +void ModelOverlay::animate() { + + if (!_animation || !_animation->isLoaded() || !_model || !_model->isLoaded()) { + return; + } + + + QVector jointsData; + + const QVector& frames = _animation->getFramesReference(); // NOTE: getFrames() is too heavy + int frameCount = frames.size(); + if (frameCount <= 0) { + return; + } + + if (!_lastAnimated) { + _lastAnimated = usecTimestampNow(); + return; + } + + auto now = usecTimestampNow(); + auto interval = now - _lastAnimated; + _lastAnimated = now; + float deltaTime = (float)interval / (float)USECS_PER_SECOND; + _animationCurrentFrame += (deltaTime * _animationFPS); + + { + int animationCurrentFrame = (int)(glm::floor(_animationCurrentFrame)) % frameCount; + if (animationCurrentFrame < 0 || animationCurrentFrame > frameCount) { + animationCurrentFrame = 0; + } + + if (animationCurrentFrame == _lastKnownCurrentFrame) { + return; + } + _lastKnownCurrentFrame = animationCurrentFrame; + } + + if (_jointMapping.size() != _model->getJointStateCount()) { + return; + } + + QStringList animationJointNames = _animation->getGeometry().getJointNames(); + auto& fbxJoints = _animation->getGeometry().joints; + + auto& originalFbxJoints = _model->getFBXGeometry().joints; + auto& originalFbxIndices = _model->getFBXGeometry().jointIndices; + + const QVector& rotations = frames[_lastKnownCurrentFrame].rotations; + const QVector& translations = frames[_lastKnownCurrentFrame].translations; + + jointsData.resize(_jointMapping.size()); + for (int j = 0; j < _jointMapping.size(); j++) { + int index = _jointMapping[j]; + + if (index >= 0) { + glm::mat4 translationMat; + + if (_animationAllowTranslation) { + if (index < translations.size()) { + translationMat = glm::translate(translations[index]); + } + } + else if (index < animationJointNames.size()) { + QString jointName = fbxJoints[index].name; + + if (originalFbxIndices.contains(jointName)) { + // Making sure the joint names exist in the original model the animation is trying to apply onto. If they do, then remap and get it's translation. + int remappedIndex = originalFbxIndices[jointName] - 1; // JointIndeces seem to always start from 1 and the found index is always 1 higher than actual. + translationMat = glm::translate(originalFbxJoints[remappedIndex].translation); + } + } + glm::mat4 rotationMat; + if (index < rotations.size()) { + rotationMat = glm::mat4_cast(fbxJoints[index].preRotation * rotations[index] * fbxJoints[index].postRotation); + } + else { + rotationMat = glm::mat4_cast(fbxJoints[index].preRotation * fbxJoints[index].postRotation); + } + + glm::mat4 finalMat = (translationMat * fbxJoints[index].preTransform * + rotationMat * fbxJoints[index].postTransform); + auto& jointData = jointsData[j]; + jointData.translation = extractTranslation(finalMat); + jointData.translationSet = true; + jointData.rotation = glmExtractRotation(finalMat); + jointData.rotationSet = true; + } + } + // Set the data in the model + copyAnimationJointDataToModel(jointsData); +} + + +void ModelOverlay::mapAnimationJoints(const QStringList& modelJointNames) { + + // if we don't have animation, or we're already joint mapped then bail early + if (!hasAnimation() || jointsMapped()) { + return; + } + + if (!_animation || _animation->getURL() != _animationURL) { + _animation = DependencyManager::get()->getAnimation(_animationURL); + } + + if (_animation && _animation->isLoaded()) { + QStringList animationJointNames = _animation->getJointNames(); + + if (modelJointNames.size() > 0 && animationJointNames.size() > 0) { + _jointMapping.resize(modelJointNames.size()); + for (int i = 0; i < modelJointNames.size(); i++) { + _jointMapping[i] = animationJointNames.indexOf(modelJointNames[i]); + } + _jointMappingCompleted = true; + _jointMappingURL = _animationURL; + } + } +} + +void ModelOverlay::copyAnimationJointDataToModel(QVector jointsData) { + if (!_model || !_model->isLoaded()) { + return; + } + + // relay any inbound joint changes from scripts/animation/network to the model/rig + for (int index = 0; index < jointsData.size(); ++index) { + auto& jointData = jointsData[index]; + _model->setJointRotation(index, true, jointData.rotation, 1.0f); + _model->setJointTranslation(index, true, jointData.translation, 1.0f); + } + _updateModel = true; +} + diff --git a/interface/src/ui/overlays/ModelOverlay.h b/interface/src/ui/overlays/ModelOverlay.h index 8d8429b29e..edee4f7ac6 100644 --- a/interface/src/ui/overlays/ModelOverlay.h +++ b/interface/src/ui/overlays/ModelOverlay.h @@ -13,6 +13,7 @@ #define hifi_ModelOverlay_h #include +#include #include "Volume3DOverlay.h" @@ -45,6 +46,9 @@ public: float getLoadPriority() const { return _loadPriority; } + bool hasAnimation() const { return !_animationURL.isEmpty(); } + bool jointsMapped() const { return _jointMappingURL == _animationURL && _jointMappingCompleted; } + protected: Transform evalRenderTransform() override; @@ -53,6 +57,14 @@ protected: template vectorType mapJoints(mapFunction function) const; + void animate(); + void mapAnimationJoints(const QStringList& modelJointNames); + bool isAnimatingSomething() const { + return !_animationURL.isEmpty() && _animationRunning && _animationFPS != 0.0f; + } + void copyAnimationJointDataToModel(QVector jointsData); + + private: ModelPointer _model; @@ -62,6 +74,25 @@ private: bool _updateModel = { false }; bool _scaleToFit = { false }; float _loadPriority { 0.0f }; + + AnimationPointer _animation; + + QUrl _animationURL; + float _animationFPS { 0.0f }; + float _animationCurrentFrame { 0.0f }; + bool _animationRunning { false }; + bool _animationLoop { false }; + float _animationFirstFrame { 0.0f }; + float _animationLastFrame = { 0.0f }; + bool _animationHold { false }; + bool _animationAllowTranslation { false }; + uint64_t _lastAnimated { 0 }; + int _lastKnownCurrentFrame { -1 }; + + QUrl _jointMappingURL; + bool _jointMappingCompleted { false }; + QVector _jointMapping; // domain is index into model-joints, range is index into animation-joints + }; #endif // hifi_ModelOverlay_h From d8e2cbf871fd1b10a7507b979e832f65e13f8c44 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 28 Sep 2017 16:20:09 -0700 Subject: [PATCH 02/88] Oculus: Bug fix for head offset on large/small scaled avatars. --- plugins/oculus/src/OculusControllerManager.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/plugins/oculus/src/OculusControllerManager.cpp b/plugins/oculus/src/OculusControllerManager.cpp index 6f7be26554..d0c717bd20 100644 --- a/plugins/oculus/src/OculusControllerManager.cpp +++ b/plugins/oculus/src/OculusControllerManager.cpp @@ -334,10 +334,8 @@ void OculusControllerManager::TouchDevice::handleHeadPose(float deltaTime, glm::mat4 defaultHeadOffset = glm::inverse(inputCalibrationData.defaultCenterEyeMat) * inputCalibrationData.defaultHeadMat; - controller::Pose hmdHeadPose = pose.transform(sensorToAvatar); - pose.valid = true; - _poseStateMap[controller::HEAD] = hmdHeadPose.postTransform(defaultHeadOffset); + _poseStateMap[controller::HEAD] = pose.postTransform(defaultHeadOffset).transform(sensorToAvatar); } void OculusControllerManager::TouchDevice::handleRotationForUntrackedHand(const controller::InputCalibrationData& inputCalibrationData, From 9c81bc5479464e81889128d13dbdd8a20e7eff78 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 29 Sep 2017 09:52:30 -0700 Subject: [PATCH 03/88] CR fixes --- interface/src/ui/overlays/ModelOverlay.cpp | 26 +++++++++------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index 713115bffc..c857ad97ab 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -404,18 +404,16 @@ void ModelOverlay::animate() { float deltaTime = (float)interval / (float)USECS_PER_SECOND; _animationCurrentFrame += (deltaTime * _animationFPS); - { - int animationCurrentFrame = (int)(glm::floor(_animationCurrentFrame)) % frameCount; - if (animationCurrentFrame < 0 || animationCurrentFrame > frameCount) { - animationCurrentFrame = 0; - } - - if (animationCurrentFrame == _lastKnownCurrentFrame) { - return; - } - _lastKnownCurrentFrame = animationCurrentFrame; + int animationCurrentFrame = (int)(glm::floor(_animationCurrentFrame)) % frameCount; + if (animationCurrentFrame < 0 || animationCurrentFrame > frameCount) { + animationCurrentFrame = 0; } + if (animationCurrentFrame == _lastKnownCurrentFrame) { + return; + } + _lastKnownCurrentFrame = animationCurrentFrame; + if (_jointMapping.size() != _model->getJointStateCount()) { return; } @@ -440,12 +438,11 @@ void ModelOverlay::animate() { if (index < translations.size()) { translationMat = glm::translate(translations[index]); } - } - else if (index < animationJointNames.size()) { + } else if (index < animationJointNames.size()) { QString jointName = fbxJoints[index].name; if (originalFbxIndices.contains(jointName)) { - // Making sure the joint names exist in the original model the animation is trying to apply onto. If they do, then remap and get it's translation. + // Making sure the joint names exist in the original model the animation is trying to apply onto. If they do, then remap and get its translation. int remappedIndex = originalFbxIndices[jointName] - 1; // JointIndeces seem to always start from 1 and the found index is always 1 higher than actual. translationMat = glm::translate(originalFbxJoints[remappedIndex].translation); } @@ -453,8 +450,7 @@ void ModelOverlay::animate() { glm::mat4 rotationMat; if (index < rotations.size()) { rotationMat = glm::mat4_cast(fbxJoints[index].preRotation * rotations[index] * fbxJoints[index].postRotation); - } - else { + } else { rotationMat = glm::mat4_cast(fbxJoints[index].preRotation * fbxJoints[index].postRotation); } From fcfac9efc0b4787872eda616165cc70f43be093c Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Fri, 29 Sep 2017 11:14:29 -0700 Subject: [PATCH 04/88] no tpose when switching avatars --- libraries/animation/src/Rig.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 86a1e629b4..712c728dcb 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -249,6 +249,7 @@ void Rig::reset(const FBXGeometry& geometry) { _rightShoulderJointIndex = _rightElbowJointIndex >= 0 ? geometry.joints.at(_rightElbowJointIndex).parentIndex : -1; if (!_animGraphURL.isEmpty()) { + _animNode.reset(); initAnimGraph(_animGraphURL); } } @@ -1619,7 +1620,7 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo } void Rig::initAnimGraph(const QUrl& url) { - if (_animGraphURL != url) { + if (_animGraphURL != url || !_animNode) { _animGraphURL = url; _animNode.reset(); From 4fa60f51085ac36b9badda7a78640b5156731dc4 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 16 Aug 2017 11:26:29 -0700 Subject: [PATCH 05/88] cleanup AddEntityOperator --- libraries/entities/src/AddEntityOperator.cpp | 9 ++++----- libraries/entities/src/AddEntityOperator.h | 16 ++++++++++++---- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/libraries/entities/src/AddEntityOperator.cpp b/libraries/entities/src/AddEntityOperator.cpp index 78d986f538..2ff1c6f622 100644 --- a/libraries/entities/src/AddEntityOperator.cpp +++ b/libraries/entities/src/AddEntityOperator.cpp @@ -9,18 +9,17 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "AddEntityOperator.h" + #include "EntityItem.h" #include "EntityTree.h" #include "EntityTreeElement.h" -#include "AddEntityOperator.h" - AddEntityOperator::AddEntityOperator(EntityTreePointer tree, EntityItemPointer newEntity) : _tree(tree), _newEntity(newEntity), - _foundNew(false), - _changeTime(usecTimestampNow()), - _newEntityBox() + _newEntityBox(), + _foundNew(false) { // caller must have verified existence of newEntity assert(_newEntity); diff --git a/libraries/entities/src/AddEntityOperator.h b/libraries/entities/src/AddEntityOperator.h index 48ee49f4d1..0c36797e24 100644 --- a/libraries/entities/src/AddEntityOperator.h +++ b/libraries/entities/src/AddEntityOperator.h @@ -12,20 +12,28 @@ #ifndef hifi_AddEntityOperator_h #define hifi_AddEntityOperator_h +#include + +#include +#include + +#include "EntityTypes.h" + +class EntityTree; +using EntityTreePointer = std::shared_ptr; + class AddEntityOperator : public RecurseOctreeOperator { public: AddEntityOperator(EntityTreePointer tree, EntityItemPointer newEntity); - + virtual bool preRecursion(const OctreeElementPointer& element) override; virtual bool postRecursion(const OctreeElementPointer& element) override; virtual OctreeElementPointer possiblyCreateChildAt(const OctreeElementPointer& element, int childIndex) override; private: EntityTreePointer _tree; EntityItemPointer _newEntity; - bool _foundNew; - quint64 _changeTime; - AABox _newEntityBox; + bool _foundNew; }; From 55e9ced5c387c74b579d1b1c3028308c8c8ecfbf Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 16 Aug 2017 11:27:41 -0700 Subject: [PATCH 06/88] cleanup MovingEntitiesOperator --- libraries/entities/src/EntitySimulation.cpp | 2 +- .../entities/src/MovingEntitiesOperator.cpp | 21 ++++++++---------- .../entities/src/MovingEntitiesOperator.h | 22 +++++++++++-------- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/libraries/entities/src/EntitySimulation.cpp b/libraries/entities/src/EntitySimulation.cpp index 6a1c359b5a..2e330fdcc5 100644 --- a/libraries/entities/src/EntitySimulation.cpp +++ b/libraries/entities/src/EntitySimulation.cpp @@ -141,7 +141,7 @@ void EntitySimulation::callUpdateOnEntitiesThatNeedIt(const quint64& now) { void EntitySimulation::sortEntitiesThatMoved() { // NOTE: this is only for entities that have been moved by THIS EntitySimulation. // External changes to entity position/shape are expected to be sorted outside of the EntitySimulation. - MovingEntitiesOperator moveOperator(_entityTree); + MovingEntitiesOperator moveOperator; AACube domainBounds(glm::vec3((float)-HALF_TREE_SCALE), (float)TREE_SCALE); SetOfEntities::iterator itemItr = _entitiesToSort.begin(); while (itemItr != _entitiesToSort.end()) { diff --git a/libraries/entities/src/MovingEntitiesOperator.cpp b/libraries/entities/src/MovingEntitiesOperator.cpp index 42e5a2ece5..ea30ce85f2 100644 --- a/libraries/entities/src/MovingEntitiesOperator.cpp +++ b/libraries/entities/src/MovingEntitiesOperator.cpp @@ -16,15 +16,7 @@ #include "MovingEntitiesOperator.h" -MovingEntitiesOperator::MovingEntitiesOperator(EntityTreePointer tree) : - _tree(tree), - _changeTime(usecTimestampNow()), - _foundOldCount(0), - _foundNewCount(0), - _lookingCount(0), - _wantDebug(false) -{ -} +MovingEntitiesOperator::MovingEntitiesOperator() { } MovingEntitiesOperator::~MovingEntitiesOperator() { if (_wantDebug) { @@ -146,7 +138,7 @@ bool MovingEntitiesOperator::preRecursion(const OctreeElementPointer& element) { // In Pre-recursion, we're generally deciding whether or not we want to recurse this // path of the tree. For this operation, we want to recurse the branch of the tree if - // and of the following are true: + // any of the following are true: // * We have not yet found the old entity, and this branch contains our old entity // * We have not yet found the new entity, and this branch contains our new entity // @@ -230,8 +222,6 @@ bool MovingEntitiesOperator::postRecursion(const OctreeElementPointer& element) if ((shouldRecurseSubTree(element))) { element->markWithChangedTime(); } - - // It's not OK to prune if we have the potential of deleting the original containing element // because if we prune the containing element then new might end up reallocating the same memory later @@ -286,3 +276,10 @@ OctreeElementPointer MovingEntitiesOperator::possiblyCreateChildAt(const OctreeE } return NULL; } + +void MovingEntitiesOperator::reset() { + _entitiesToMove.clear(); + _foundOldCount = 0; + _foundNewCount = 0; + _lookingCount = 0; +} diff --git a/libraries/entities/src/MovingEntitiesOperator.h b/libraries/entities/src/MovingEntitiesOperator.h index fc6ccf2513..d93efa60f2 100644 --- a/libraries/entities/src/MovingEntitiesOperator.h +++ b/libraries/entities/src/MovingEntitiesOperator.h @@ -12,6 +12,11 @@ #ifndef hifi_MovingEntitiesOperator_h #define hifi_MovingEntitiesOperator_h +#include + +#include "EntityTypes.h" +#include "EntityTreeElement.h" + class EntityToMoveDetails { public: EntityItemPointer entity; @@ -34,7 +39,7 @@ inline bool operator==(const EntityToMoveDetails& a, const EntityToMoveDetails& class MovingEntitiesOperator : public RecurseOctreeOperator { public: - MovingEntitiesOperator(EntityTreePointer tree); + MovingEntitiesOperator(); ~MovingEntitiesOperator(); void addEntityToMoveList(EntityItemPointer entity, const AACube& newCube); @@ -42,16 +47,15 @@ public: virtual bool postRecursion(const OctreeElementPointer& element) override; virtual OctreeElementPointer possiblyCreateChildAt(const OctreeElementPointer& element, int childIndex) override; bool hasMovingEntities() const { return _entitiesToMove.size() > 0; } + void reset(); private: - EntityTreePointer _tree; - QSet _entitiesToMove; - quint64 _changeTime; - int _foundOldCount; - int _foundNewCount; - int _lookingCount; bool shouldRecurseSubTree(const OctreeElementPointer& element); - - bool _wantDebug; + + QSet _entitiesToMove; + int _foundOldCount { 0 }; + int _foundNewCount { 0 }; + int _lookingCount { 0 }; + bool _wantDebug { false }; }; #endif // hifi_MovingEntitiesOperator_h From 56bc48b31a3c09dd84e0e99a77018cdc96b79768 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 16 Aug 2017 11:31:02 -0700 Subject: [PATCH 07/88] don't use octcode data in entity update packets --- libraries/entities/src/EntityTree.cpp | 120 ++++++++++++++++- libraries/entities/src/EntityTree.h | 12 +- libraries/entities/src/EntityTreeElement.cpp | 135 +------------------ 3 files changed, 130 insertions(+), 137 deletions(-) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index bcb73f352c..3198ad4344 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -22,7 +22,6 @@ #include "VariantMapToScriptValue.h" #include "AddEntityOperator.h" -#include "MovingEntitiesOperator.h" #include "UpdateEntityOperator.h" #include "QVariantGLM.h" #include "EntitiesLogging.h" @@ -107,6 +106,121 @@ void EntityTree::eraseAllOctreeElements(bool createNewRoot) { clearDeletedEntities(); } +void EntityTree::readBitstreamToTree(const unsigned char* bitstream, + unsigned long int bufferSizeBytes, ReadBitstreamToTreeParams& args) { + Octree::readBitstreamToTree(bitstream, bufferSizeBytes, args); + + // add entities + QHash::const_iterator itr; + for (itr = _entitiesToAdd.constBegin(); itr != _entitiesToAdd.constEnd(); ++itr) { + EntityItemPointer entityItem = itr.value(); + AddEntityOperator theOperator(getThisPointer(), entityItem); + recurseTreeWithOperator(&theOperator); + if (!entityItem->getParentID().isNull()) { + addToNeedsParentFixupList(entityItem); + } + postAddEntity(entityItem); + } + _entitiesToAdd.clear(); + + // move entities + if (_entityMover.hasMovingEntities()) { + PerformanceTimer perfTimer("recurseTreeWithOperator"); + recurseTreeWithOperator(&_entityMover); + _entityMover.reset(); + } +} + +int EntityTree::readEntityDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args) { + const unsigned char* dataAt = data; + int bytesRead = 0; + uint16_t numberOfEntities = 0; + int expectedBytesPerEntity = EntityItem::expectedBytes(); + + args.elementsPerPacket++; + + if (bytesLeftToRead >= (int)sizeof(numberOfEntities)) { + // read our entities in.... + numberOfEntities = *(uint16_t*)dataAt; + + dataAt += sizeof(numberOfEntities); + bytesLeftToRead -= (int)sizeof(numberOfEntities); + bytesRead += sizeof(numberOfEntities); + + if (bytesLeftToRead >= (int)(numberOfEntities * expectedBytesPerEntity)) { + for (uint16_t i = 0; i < numberOfEntities; i++) { + int bytesForThisEntity = 0; + EntityItemID entityItemID = EntityItemID::readEntityItemIDFromBuffer(dataAt, bytesLeftToRead); + EntityItemPointer entity = findEntityByEntityItemID(entityItemID); + + if (entity) { + QString entityScriptBefore = entity->getScript(); + QUuid parentIDBefore = entity->getParentID(); + QString entityServerScriptsBefore = entity->getServerScripts(); + quint64 entityScriptTimestampBefore = entity->getScriptTimestamp(); + + bytesForThisEntity = entity->readEntityDataFromBuffer(dataAt, bytesLeftToRead, args); + if (entity->getDirtyFlags()) { + entityChanged(entity); + } + _entityMover.addEntityToMoveList(entity, entity->getQueryAACube()); + + QString entityScriptAfter = entity->getScript(); + QString entityServerScriptsAfter = entity->getServerScripts(); + quint64 entityScriptTimestampAfter = entity->getScriptTimestamp(); + bool reload = entityScriptTimestampBefore != entityScriptTimestampAfter; + + // If the script value has changed on us, or it's timestamp has changed to force + // a reload then we want to send out a script changing signal... + if (reload || entityScriptBefore != entityScriptAfter) { + emitEntityScriptChanging(entityItemID, reload); // the entity script has changed + } + if (reload || entityServerScriptsBefore != entityServerScriptsAfter) { + emitEntityServerScriptChanging(entityItemID, reload); // the entity server script has changed + } + + QUuid parentIDAfter = entity->getParentID(); + if (parentIDBefore != parentIDAfter) { + addToNeedsParentFixupList(entity); + } + } else { + entity = EntityTypes::constructEntityItem(dataAt, bytesLeftToRead, args); + if (entity) { + bytesForThisEntity = entity->readEntityDataFromBuffer(dataAt, bytesLeftToRead, args); + + // don't add if we've recently deleted.... + if (!isDeletedEntity(entityItemID)) { + _entitiesToAdd.insert(entityItemID, entity); + + /* + addEntityMapEntry(entity); + oldElement->addEntityItem(entity); // add this new entity to this elements entities + entityItemID = entity->getEntityItemID(); + postAddEntity(entity); + */ + + if (entity->getCreated() == UNKNOWN_CREATED_TIME) { + entity->recordCreationTime(); + } + } else { + #ifdef WANT_DEBUG + qCDebug(entities) << "Received packet for previously deleted entity [" << + entityItemID << "] ignoring. (inside " << __FUNCTION__ << ")"; + #endif + } + } + } + // Move the buffer forward to read more entities + dataAt += bytesForThisEntity; + bytesLeftToRead -= bytesForThisEntity; + bytesRead += bytesForThisEntity; + } + } + } + + return bytesRead; +} + bool EntityTree::handlesEditPacketType(PacketType packetType) const { // we handle these types of "edit" packets switch (packetType) { @@ -1250,7 +1364,7 @@ void EntityTree::entityChanged(EntityItemPointer entity) { void EntityTree::fixupNeedsParentFixups() { - MovingEntitiesOperator moveOperator(getThisPointer()); + MovingEntitiesOperator moveOperator; QWriteLocker locker(&_needsParentFixupLock); @@ -1674,7 +1788,7 @@ QVector EntityTree::sendEntities(EntityEditPacketSender* packetSen // add-entity packet to the server. // fix the queryAACubes of any children that were read in before their parents, get them into the correct element - MovingEntitiesOperator moveOperator(localTree); + MovingEntitiesOperator moveOperator; QHash::iterator i = map.begin(); while (i != map.end()) { EntityItemID newID = i.value(); diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index cb16f2fac1..7dff2985fc 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -19,11 +19,12 @@ #include class EntityTree; -typedef std::shared_ptr EntityTreePointer; - +using EntityTreePointer = std::shared_ptr; +#include "AddEntityOperator.h" #include "EntityTreeElement.h" #include "DeleteEntityOperator.h" +#include "MovingEntitiesOperator.h" class EntityEditFilters; class Model; @@ -80,6 +81,10 @@ public: virtual void eraseAllOctreeElements(bool createNewRoot = true) override; + virtual void readBitstreamToTree(const unsigned char* bitstream, + unsigned long int bufferSizeBytes, ReadBitstreamToTreeParams& args) override; + int readEntityDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args); + // These methods will allow the OctreeServer to send your tree inbound edit packets of your // own definition. Implement these to allow your octree based server to support editing virtual bool getWantSVOfileVersions() const override { return true; } @@ -347,6 +352,9 @@ protected: bool filterProperties(EntityItemPointer& existingEntity, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, FilterType filterType); bool _hasEntityEditFilter{ false }; QStringList _entityScriptSourceWhitelist; + + MovingEntitiesOperator _entityMover; + QHash _entitiesToAdd; }; #endif // hifi_EntityTree_h diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 487bf60f61..4056bbd0b7 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -107,7 +107,7 @@ bool EntityTreeElement::shouldIncludeChildData(int childIndex, EncodeBitstreamPa OctreeElementExtraEncodeData* extraEncodeData = &entityNodeData->extraEncodeData; assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes - + if (extraEncodeData->contains(this)) { EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData = std::static_pointer_cast((*extraEncodeData)[this]); @@ -305,7 +305,7 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData int numberOfEntitiesOffset = 0; withReadLock([&] { QVector indexesOfEntitiesToInclude; - + // It's possible that our element has been previous completed. In this case we'll simply not include any of our // entities for encoding. This is needed because we encode the element data at the "parent" level, and so we // need to handle the case where our sibling elements need encoding but we don't. @@ -928,138 +928,9 @@ bool EntityTreeElement::removeEntityItem(EntityItemPointer entity) { } -// Things we want to accomplish as we read these entities from the data buffer. -// -// 1) correctly update the properties of the entity -// 2) add any new entities that didn't previously exist -// -// TODO: Do we also need to do this? -// 3) mark our tree as dirty down to the path of the previous location of the entity -// 4) mark our tree as dirty down to the path of the new location of the entity -// -// Since we're potentially reading several entities, we'd prefer to do all the moving around -// and dirty path marking in one pass. int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args) { - // If we're the root, but this bitstream doesn't support root elements with data, then - // return without reading any bytes - if (this == _myTree->getRoot().get() && args.bitstreamVersion < VERSION_ROOT_ELEMENT_HAS_DATA) { - return 0; - } - - const unsigned char* dataAt = data; - int bytesRead = 0; - uint16_t numberOfEntities = 0; - int expectedBytesPerEntity = EntityItem::expectedBytes(); - - args.elementsPerPacket++; - - if (bytesLeftToRead >= (int)sizeof(numberOfEntities)) { - // read our entities in.... - numberOfEntities = *(uint16_t*)dataAt; - - dataAt += sizeof(numberOfEntities); - bytesLeftToRead -= (int)sizeof(numberOfEntities); - bytesRead += sizeof(numberOfEntities); - - if (bytesLeftToRead >= (int)(numberOfEntities * expectedBytesPerEntity)) { - for (uint16_t i = 0; i < numberOfEntities; i++) { - int bytesForThisEntity = 0; - EntityItemID entityItemID; - EntityItemPointer entityItem = NULL; - - // Old model files don't have UUIDs in them. So we don't want to try to read those IDs from the stream. - // Since this can only happen on loading an old file, we can safely treat these as new entity cases, - // which will correctly handle the case of creating models and letting them parse the old format. - if (args.bitstreamVersion >= VERSION_ENTITIES_SUPPORT_SPLIT_MTU) { - entityItemID = EntityItemID::readEntityItemIDFromBuffer(dataAt, bytesLeftToRead); - entityItem = _myTree->findEntityByEntityItemID(entityItemID); - } - - // If the item already exists in our tree, we want do the following... - // 1) allow the existing item to read from the databuffer - // 2) check to see if after reading the item, the containing element is still correct, fix it if needed - // - // TODO: Do we need to also do this? - // 3) remember the old cube for the entity so we can mark it as dirty - if (entityItem) { - QString entityScriptBefore = entityItem->getScript(); - QUuid parentIDBefore = entityItem->getParentID(); - QString entityServerScriptsBefore = entityItem->getServerScripts(); - quint64 entityScriptTimestampBefore = entityItem->getScriptTimestamp(); - bool bestFitBefore = bestFitEntityBounds(entityItem); - EntityTreeElementPointer currentContainingElement = _myTree->getContainingElement(entityItemID); - - bytesForThisEntity = entityItem->readEntityDataFromBuffer(dataAt, bytesLeftToRead, args); - if (entityItem->getDirtyFlags()) { - _myTree->entityChanged(entityItem); - } - bool bestFitAfter = bestFitEntityBounds(entityItem); - - if (bestFitBefore != bestFitAfter) { - // This is the case where the entity existed, and is in some element in our tree... - if (!bestFitBefore && bestFitAfter) { - // This is the case where the entity existed, and is in some element in our tree... - if (currentContainingElement.get() != this) { - // if the currentContainingElement is non-null, remove the entity from it - if (currentContainingElement) { - currentContainingElement->removeEntityItem(entityItem); - } - addEntityItem(entityItem); - } - } - } - - QString entityScriptAfter = entityItem->getScript(); - QString entityServerScriptsAfter = entityItem->getServerScripts(); - quint64 entityScriptTimestampAfter = entityItem->getScriptTimestamp(); - bool reload = entityScriptTimestampBefore != entityScriptTimestampAfter; - - // If the script value has changed on us, or it's timestamp has changed to force - // a reload then we want to send out a script changing signal... - if (entityScriptBefore != entityScriptAfter || reload) { - _myTree->emitEntityScriptChanging(entityItemID, reload); // the entity script has changed - } - if (entityServerScriptsBefore != entityServerScriptsAfter || reload) { - _myTree->emitEntityServerScriptChanging(entityItemID, reload); // the entity server script has changed - } - - QUuid parentIDAfter = entityItem->getParentID(); - if (parentIDBefore != parentIDAfter) { - _myTree->addToNeedsParentFixupList(entityItem); - } - - } else { - entityItem = EntityTypes::constructEntityItem(dataAt, bytesLeftToRead, args); - if (entityItem) { - bytesForThisEntity = entityItem->readEntityDataFromBuffer(dataAt, bytesLeftToRead, args); - - // don't add if we've recently deleted.... - if (!_myTree->isDeletedEntity(entityItem->getID())) { - _myTree->addEntityMapEntry(entityItem); - addEntityItem(entityItem); // add this new entity to this elements entities - entityItemID = entityItem->getEntityItemID(); - _myTree->postAddEntity(entityItem); - if (entityItem->getCreated() == UNKNOWN_CREATED_TIME) { - entityItem->recordCreationTime(); - } - } else { - #ifdef WANT_DEBUG - qCDebug(entities) << "Received packet for previously deleted entity [" << - entityItem->getID() << "] ignoring. (inside " << __FUNCTION__ << ")"; - #endif - } - } - } - // Move the buffer forward to read more entities - dataAt += bytesForThisEntity; - bytesLeftToRead -= bytesForThisEntity; - bytesRead += bytesForThisEntity; - } - } - } - - return bytesRead; + return _myTree->readEntityDataFromBuffer(data, bytesLeftToRead, args); } void EntityTreeElement::addEntityItem(EntityItemPointer entity) { From 171151b92ac47570364b226784793bb2260f6e97 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 16 Aug 2017 11:31:56 -0700 Subject: [PATCH 08/88] use new form of MovingEntitiesOperator ctor --- interface/src/avatar/MyAvatar.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 19179d613d..10e2202553 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -584,7 +584,7 @@ void MyAvatar::simulate(float deltaTime) { } auto now = usecTimestampNow(); EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender(); - MovingEntitiesOperator moveOperator(entityTree); + MovingEntitiesOperator moveOperator; forEachDescendant([&](SpatiallyNestablePointer object) { // if the queryBox has changed, tell the entity-server if (object->getNestableType() == NestableType::Entity && object->checkAndMaybeUpdateQueryAACube()) { From 82ed19386f02d06c9be8fe8533a06c65776124ff Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 16 Aug 2017 11:45:18 -0700 Subject: [PATCH 09/88] make Octree::readBitstreamToTree() virtual --- libraries/entities/src/EntityTree.cpp | 2 +- libraries/entities/src/EntityTree.h | 2 +- libraries/octree/src/Octree.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 3198ad4344..518d3bd883 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -107,7 +107,7 @@ void EntityTree::eraseAllOctreeElements(bool createNewRoot) { } void EntityTree::readBitstreamToTree(const unsigned char* bitstream, - unsigned long int bufferSizeBytes, ReadBitstreamToTreeParams& args) { + uint64_t bufferSizeBytes, ReadBitstreamToTreeParams& args) { Octree::readBitstreamToTree(bitstream, bufferSizeBytes, args); // add entities diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 7dff2985fc..17dda32b53 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -82,7 +82,7 @@ public: virtual void eraseAllOctreeElements(bool createNewRoot = true) override; virtual void readBitstreamToTree(const unsigned char* bitstream, - unsigned long int bufferSizeBytes, ReadBitstreamToTreeParams& args) override; + uint64_t bufferSizeBytes, ReadBitstreamToTreeParams& args) override; int readEntityDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args); // These methods will allow the OctreeServer to send your tree inbound edit packets of your diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index 3b84618a56..2794ca85f0 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -232,7 +232,7 @@ public: virtual void eraseAllOctreeElements(bool createNewRoot = true); - void readBitstreamToTree(const unsigned char* bitstream, uint64_t bufferSizeBytes, ReadBitstreamToTreeParams& args); + virtual void readBitstreamToTree(const unsigned char* bitstream, uint64_t bufferSizeBytes, ReadBitstreamToTreeParams& args); void deleteOctalCodeFromTree(const unsigned char* codeBuffer, bool collapseEmptyTrees = DONT_COLLAPSE); void reaverageOctreeElements(OctreeElementPointer startElement = OctreeElementPointer()); From 3ae5c215ba4dd1cbb565f31702a65d92dfded885 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 12 Jul 2017 16:03:48 -0700 Subject: [PATCH 10/88] stub EntityTreeSendThread::traverseTreeAndSendContents() --- assignment-client/src/entities/EntityTreeSendThread.cpp | 5 +++++ assignment-client/src/entities/EntityTreeSendThread.h | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index 7febdc67e1..8144bf2e83 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -80,6 +80,11 @@ void EntityTreeSendThread::preDistributionProcessing() { } } +void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, + bool viewFrustumChanged, bool isFullScene) { + OctreeSendThread::traverseTreeAndSendContents(node, nodeData, viewFrustumChanged, isFullScene); +} + bool EntityTreeSendThread::addAncestorsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData) { // check if this entity has a parent that is also an entity diff --git a/assignment-client/src/entities/EntityTreeSendThread.h b/assignment-client/src/entities/EntityTreeSendThread.h index bfb4c743f1..8bb5c3915d 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.h +++ b/assignment-client/src/entities/EntityTreeSendThread.h @@ -23,7 +23,9 @@ public: EntityTreeSendThread(OctreeServer* myServer, const SharedNodePointer& node) : OctreeSendThread(myServer, node) {}; protected: - virtual void preDistributionProcessing() override; + void preDistributionProcessing() override; + void traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, + bool viewFrustumChanged, bool isFullScene) override; private: // the following two methods return booleans to indicate if any extra flagged entities were new additions to set From 7edd99ca0b597ce83f9029619bd2375c88227dc8 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 26 Jul 2017 08:00:07 -0700 Subject: [PATCH 11/88] add basics for send queue and tree traversal --- .../src/entities/EntityTreeSendThread.cpp | 261 ++++++++++++++++++ .../src/entities/EntityTreeSendThread.h | 70 +++++ libraries/entities/src/EntityTreeElement.h | 8 +- 3 files changed, 335 insertions(+), 4 deletions(-) diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index 8144bf2e83..b971634d7e 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -10,12 +10,214 @@ // #include "EntityTreeSendThread.h" +#include // adebug #include #include #include "EntityServer.h" +const float INVALID_ENTITY_SEND_PRIORITY = -1.0e-6f; + +void PrioritizedEntity::updatePriority(const ViewFrustum& view) { + EntityItemPointer entity = _weakEntity.lock(); + if (entity) { + bool success; + AACube cube = entity->getQueryAACube(success); + if (success) { + glm::vec3 center = cube.calcCenter() - view.getPosition(); + const float MIN_DISTANCE = 0.001f; + float distanceToCenter = glm::length(center) + MIN_DISTANCE; + float distance = distanceToCenter; //- 0.5f * cube.getScale(); + if (distance < MIN_DISTANCE) { + // this object's bounding box overlaps the camera --> give it a big priority + _priority = cube.getScale(); + } else { + // NOTE: we assume view.aspectRatio < 1.0 (view width greater than height) + // so we only check against the larger (horizontal) view angle + float front = glm::dot(center, view.getDirection()) / distanceToCenter; + if (front > cosf(view.getFieldOfView()) || distance < view.getCenterRadius()) { + _priority = cube.getScale() / distance; // + front; + } else { + _priority = INVALID_ENTITY_SEND_PRIORITY; + } + } + } else { + // when in doubt just it something positive + _priority = 1.0f; + } + } else { + _priority = INVALID_ENTITY_SEND_PRIORITY; + } +} + +TreeTraversalPath::Fork::Fork(EntityTreeElementPointer& element) : _nextIndex(0) { + assert(element); + _weakElement = element; +} + +EntityTreeElementPointer TreeTraversalPath::Fork::getNextElement(const ViewFrustum& view) { + if (_nextIndex == -1) { + // only get here for the TreeTraversalPath's root Fork at the very beginning of traversal + // safe to assume this element is in view + ++_nextIndex; + return _weakElement.lock(); + } else if (_nextIndex < NUMBER_OF_CHILDREN) { + EntityTreeElementPointer element = _weakElement.lock(); + if (element) { + while (_nextIndex < NUMBER_OF_CHILDREN) { + EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); + ++_nextIndex; + if (nextElement && ViewFrustum::OUTSIDE != nextElement->computeViewIntersection(view)) { + return nextElement; + } + } + } + } + return EntityTreeElementPointer(); +} + +EntityTreeElementPointer TreeTraversalPath::Fork::getNextElementAgain(const ViewFrustum& view, uint64_t oldTime) { + if (_nextIndex == -1) { + // only get here for the TreeTraversalPath's root Fork at the very beginning of traversal + // safe to assume this element is in view + ++_nextIndex; + EntityTreeElementPointer element = _weakElement.lock(); + assert(element); // should never lose root element + if (element->getLastChanged() < oldTime) { + _nextIndex = NUMBER_OF_CHILDREN; + return EntityTreeElementPointer(); + } + if (element->getLastChanged() > oldTime) { + return element; + } + } + if (_nextIndex < NUMBER_OF_CHILDREN) { + EntityTreeElementPointer element = _weakElement.lock(); + if (element) { + while (_nextIndex < NUMBER_OF_CHILDREN) { + EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); + ++_nextIndex; + if (nextElement && nextElement->getLastChanged() > oldTime && + ViewFrustum::OUTSIDE != nextElement->computeViewIntersection(view)) { + return nextElement; + } + } + } + } + return EntityTreeElementPointer(); +} + +EntityTreeElementPointer TreeTraversalPath::Fork::getNextElementDelta(const ViewFrustum& newView, const ViewFrustum& oldView, uint64_t oldTime) { + if (_nextIndex == -1) { + // only get here for the TreeTraversalPath's root Fork at the very beginning of traversal + // safe to assume this element is in newView + ++_nextIndex; + EntityTreeElementPointer element = _weakElement.lock(); + assert(element); // should never lose root element + if (element->getLastChanged() < oldTime) { + _nextIndex = NUMBER_OF_CHILDREN; + return EntityTreeElementPointer(); + } + return element; + } else if (_nextIndex < NUMBER_OF_CHILDREN) { + EntityTreeElementPointer element = _weakElement.lock(); + if (element) { + while (_nextIndex < NUMBER_OF_CHILDREN) { + EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); + ++_nextIndex; + if (nextElement && + !(nextElement->getLastChanged() < oldTime && + ViewFrustum::INSIDE == nextElement->computeViewIntersection(oldView)) && + ViewFrustum::OUTSIDE != nextElement->computeViewIntersection(newView)) { + return nextElement; + } + } + } + } + return EntityTreeElementPointer(); +} + +TreeTraversalPath::TreeTraversalPath() { + const int32_t MIN_PATH_DEPTH = 16; + _forks.reserve(MIN_PATH_DEPTH); + _traversalCallback = std::bind(&TreeTraversalPath::traverseFirstTime, this); +} + +void TreeTraversalPath::startNewTraversal(const ViewFrustum& view, EntityTreeElementPointer root) { + if (_startOfLastCompletedTraversal == 0) { + _traversalCallback = std::bind(&TreeTraversalPath::traverseFirstTime, this); + _currentView = view; + } else if (_currentView.isVerySimilar(view)) { + _traversalCallback = std::bind(&TreeTraversalPath::traverseAgain, this); + } else { + _currentView = view; + _traversalCallback = std::bind(&TreeTraversalPath::traverseDelta, this); + } + _forks.clear(); + if (root) { + _forks.push_back(Fork(root)); + // set root fork's index such that root element returned at getNextElement() + _forks.back().initRootNextIndex(); + } + _startOfCurrentTraversal = usecTimestampNow(); +} + +EntityTreeElementPointer TreeTraversalPath::traverseFirstTime() { + return _forks.back().getNextElement(_currentView); +} + +EntityTreeElementPointer TreeTraversalPath::traverseAgain() { + return _forks.back().getNextElementAgain(_currentView, _startOfLastCompletedTraversal); +} + +EntityTreeElementPointer TreeTraversalPath::traverseDelta() { + return _forks.back().getNextElementDelta(_currentView, _lastCompletedView, _startOfLastCompletedTraversal); +} + +EntityTreeElementPointer TreeTraversalPath::getNextElement() { + if (_forks.empty() || !_traversalCallback) { + return EntityTreeElementPointer(); + } + EntityTreeElementPointer nextElement = _traversalCallback(); + if (nextElement) { + int8_t nextIndex = _forks.back().getNextIndex(); + if (nextIndex > 0) { + // nextElement needs to be added to the path + _forks.push_back(Fork(nextElement)); + } + } else { + // we're done at this level + while (!nextElement) { + // pop one level + _forks.pop_back(); + if (_forks.empty()) { + // we've traversed the entire tree + onCompleteTraversal(); + return nextElement; + } + // keep looking for nextElement + nextElement = _traversalCallback(); + if (nextElement) { + // we've descended one level so add it to the path + _forks.push_back(Fork(nextElement)); + } + } + } + return nextElement; +} + +void TreeTraversalPath::dump() const { + for (size_t i = 0; i < _forks.size(); ++i) { + std::cout << (int)(_forks[i].getNextIndex()) << "-->"; + } +} + +void TreeTraversalPath::onCompleteTraversal() { + _lastCompletedView = _currentView; + _startOfLastCompletedTraversal = _startOfCurrentTraversal; +} + void EntityTreeSendThread::preDistributionProcessing() { auto node = _node.toStrongRef(); auto nodeData = static_cast(node->getLinkedData()); @@ -80,8 +282,67 @@ void EntityTreeSendThread::preDistributionProcessing() { } } +static size_t adebug = 0; + void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged, bool isFullScene) { + if (viewFrustumChanged) { + ViewFrustum view; + nodeData->copyCurrentViewFrustum(view); + EntityTreeElementPointer root = std::dynamic_pointer_cast(_myServer->getOctree()->getRoot()); + _path.startNewTraversal(view, root); + + std::cout << "adebug reset view" << std::endl; // adebug + adebug = 0; + } + if (!_path.empty()) { + int32_t numElements = 0; + uint64_t t0 = usecTimestampNow(); + uint64_t now = t0; + + QVector entities; + EntityTreeElementPointer nextElement = _path.getNextElement(); + while (nextElement) { + nextElement->getEntities(_path.getView(), entities); + ++numElements; + + now = usecTimestampNow(); + const uint64_t PARTIAL_TRAVERSAL_TIME_BUDGET = 80; + if (now - t0 > PARTIAL_TRAVERSAL_TIME_BUDGET) { + break; + } + nextElement = _path.getNextElement(); + } + uint64_t dt1 = now - t0; + for (EntityItemPointer& entity : entities) { + PrioritizedEntity entry(entity); + entry.updatePriority(_path.getView()); + if (entry.getPriority() > INVALID_ENTITY_SEND_PRIORITY) { + _sendQueue.push(entry); + } + } + adebug += entities.size(); + std::cout << "adebug traverseTreeAndSendContents totalEntities = " << adebug + << " numElements = " << numElements + << " numEntities = " << entities.size() + << " dt = " << dt1 << std::endl; // adebug + } else if (!_sendQueue.empty()) { + + while (!_sendQueue.empty()) { + PrioritizedEntity entry = _sendQueue.top(); + EntityItemPointer entity = entry.getEntity(); + if (entity) { + std::cout << "adebug traverseTreeAndSendContents() " << entry.getPriority() + << " '" << entity->getName().toStdString() << "'" + << std::endl; // adebug + } + _sendQueue.pop(); + } + // std::priority_queue doesn't have a clear method, + // so we "clear" _sendQueue by setting it equal to an empty queue + _sendQueue = EntityPriorityQueue(); + } + OctreeSendThread::traverseTreeAndSendContents(node, nodeData, viewFrustumChanged, isFullScene); } diff --git a/assignment-client/src/entities/EntityTreeSendThread.h b/assignment-client/src/entities/EntityTreeSendThread.h index 8bb5c3915d..e81105c977 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.h +++ b/assignment-client/src/entities/EntityTreeSendThread.h @@ -12,11 +12,79 @@ #ifndef hifi_EntityTreeSendThread_h #define hifi_EntityTreeSendThread_h +#include + #include "../octree/OctreeSendThread.h" +#include "EntityTreeElement.h" + class EntityNodeData; class EntityItem; +class PrioritizedEntity { +public: + PrioritizedEntity(EntityItemPointer entity) : _weakEntity(entity) { } + void updatePriority(const ViewFrustum& view); + EntityItemPointer getEntity() const { return _weakEntity.lock(); } + float getPriority() const { return _priority; } + + class Compare { + public: + bool operator() (const PrioritizedEntity& A, const PrioritizedEntity& B) { return A._priority < B._priority; } + }; + + friend class Compare; + +private: + EntityItemWeakPointer _weakEntity; + float _priority { 0.0f }; +}; +using EntityPriorityQueue = std::priority_queue< PrioritizedEntity, std::vector, PrioritizedEntity::Compare >; + +class TreeTraversalPath { +public: + TreeTraversalPath(); + + void startNewTraversal(const ViewFrustum& view, EntityTreeElementPointer root); + + EntityTreeElementPointer getNextElement(); + + const ViewFrustum& getView() const { return _currentView; } + + bool empty() const { return _forks.empty(); } + size_t size() const { return _forks.size(); } // adebug + void dump() const; + + class Fork { + public: + Fork(EntityTreeElementPointer& element); + + EntityTreeElementPointer getNextElement(const ViewFrustum& view); + EntityTreeElementPointer getNextElementAgain(const ViewFrustum& view, uint64_t oldTime); + EntityTreeElementPointer getNextElementDelta(const ViewFrustum& newView, const ViewFrustum& oldView, uint64_t oldTime); + int8_t getNextIndex() const { return _nextIndex; } + void initRootNextIndex() { _nextIndex = -1; } + + protected: + EntityTreeElementWeakPointer _weakElement; + int8_t _nextIndex; + }; + +protected: + EntityTreeElementPointer traverseFirstTime(); + EntityTreeElementPointer traverseAgain(); + EntityTreeElementPointer traverseDelta(); + void onCompleteTraversal(); + + ViewFrustum _currentView; + ViewFrustum _lastCompletedView; + std::vector _forks; + std::function _traversalCallback { nullptr }; + uint64_t _startOfLastCompletedTraversal { 0 }; + uint64_t _startOfCurrentTraversal { 0 }; +}; + + class EntityTreeSendThread : public OctreeSendThread { public: @@ -32,6 +100,8 @@ private: bool addAncestorsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData); bool addDescendantsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData); + TreeTraversalPath _path; + EntityPriorityQueue _sendQueue; }; #endif // hifi_EntityTreeSendThread_h diff --git a/libraries/entities/src/EntityTreeElement.h b/libraries/entities/src/EntityTreeElement.h index aee8c7cfd6..3460a893fb 100644 --- a/libraries/entities/src/EntityTreeElement.h +++ b/libraries/entities/src/EntityTreeElement.h @@ -21,11 +21,12 @@ #include "EntityItem.h" #include "EntityTree.h" -typedef QVector EntityItems; - class EntityTree; class EntityTreeElement; -typedef std::shared_ptr EntityTreeElementPointer; + +using EntityItems = QVector; +using EntityTreeElementWeakPointer = std::weak_ptr; +using EntityTreeElementPointer = std::shared_ptr; class EntityTreeUpdateArgs { public: @@ -173,7 +174,6 @@ public: void setTree(EntityTreePointer tree) { _myTree = tree; } EntityTreePointer getTree() const { return _myTree; } - bool updateEntity(const EntityItem& entity); void addEntityItem(EntityItemPointer entity); EntityItemPointer getClosestEntity(glm::vec3 position) const; From ca470d67b449644fee2a1763761a4fc58e676947 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 26 Jul 2017 08:46:03 -0700 Subject: [PATCH 12/88] fix indentation --- libraries/entities/src/EntityItem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 643145942a..71b119f415 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -2024,7 +2024,7 @@ bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulationPoi _previouslyDeletedActions.insert(actionID, usecTimestampNow()); if (_objectActions.contains(actionID)) { if (!simulation) { - EntityTreeElementPointer element = _element; // use local copy of _element for logic below + EntityTreeElementPointer element = _element; // use local copy of _element for logic below EntityTreePointer entityTree = element ? element->getTree() : nullptr; simulation = entityTree ? entityTree->getSimulation() : nullptr; } From 2b31a746e34082f81dcc4b80a4c2468049203bec Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 26 Jul 2017 08:48:54 -0700 Subject: [PATCH 13/88] add content timestamp for EntityTreeElement --- libraries/entities/src/EntityTreeElement.cpp | 4 ++++ libraries/entities/src/EntityTreeElement.h | 4 ++++ libraries/entities/src/MovingEntitiesOperator.cpp | 2 ++ libraries/entities/src/UpdateEntityOperator.cpp | 2 ++ 4 files changed, 12 insertions(+) diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 4056bbd0b7..bf5780e7cb 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -893,6 +893,7 @@ void EntityTreeElement::cleanupEntities() { } _entityItems.clear(); }); + _lastChangedContent = usecTimestampNow(); } bool EntityTreeElement::removeEntityWithEntityItemID(const EntityItemID& id) { @@ -906,6 +907,7 @@ bool EntityTreeElement::removeEntityWithEntityItemID(const EntityItemID& id) { // NOTE: only EntityTreeElement should ever be changing the value of entity->_element entity->_element = NULL; _entityItems.removeAt(i); + _lastChangedContent = usecTimestampNow(); break; } } @@ -922,6 +924,7 @@ bool EntityTreeElement::removeEntityItem(EntityItemPointer entity) { // NOTE: only EntityTreeElement should ever be changing the value of entity->_element assert(entity->_element.get() == this); entity->_element = NULL; + _lastChangedContent = usecTimestampNow(); return true; } return false; @@ -939,6 +942,7 @@ void EntityTreeElement::addEntityItem(EntityItemPointer entity) { withWriteLock([&] { _entityItems.push_back(entity); }); + _lastChangedContent = usecTimestampNow(); entity->_element = getThisPointer(); } diff --git a/libraries/entities/src/EntityTreeElement.h b/libraries/entities/src/EntityTreeElement.h index 3460a893fb..c7fb80c330 100644 --- a/libraries/entities/src/EntityTreeElement.h +++ b/libraries/entities/src/EntityTreeElement.h @@ -238,10 +238,14 @@ public: return std::static_pointer_cast(shared_from_this()); } + void bumpChangedContent() { _lastChangedContent = usecTimestampNow(); } + uint64_t getLastChangedContent() const { return _lastChangedContent; } + protected: virtual void init(unsigned char * octalCode) override; EntityTreePointer _myTree; EntityItems _entityItems; + uint64_t _lastChangedContent { 0 }; }; #endif // hifi_EntityTreeElement_h diff --git a/libraries/entities/src/MovingEntitiesOperator.cpp b/libraries/entities/src/MovingEntitiesOperator.cpp index ea30ce85f2..cf043dd93e 100644 --- a/libraries/entities/src/MovingEntitiesOperator.cpp +++ b/libraries/entities/src/MovingEntitiesOperator.cpp @@ -192,6 +192,8 @@ bool MovingEntitiesOperator::preRecursion(const OctreeElementPointer& element) { oldElement->removeEntityItem(details.entity); } entityTreeElement->addEntityItem(details.entity); + } else { + entityTreeElement->bumpChangedContent(); } _foundNewCount++; //details.newFound = true; // TODO: would be nice to add this optimization diff --git a/libraries/entities/src/UpdateEntityOperator.cpp b/libraries/entities/src/UpdateEntityOperator.cpp index 7a5c87187a..ea284e6d5b 100644 --- a/libraries/entities/src/UpdateEntityOperator.cpp +++ b/libraries/entities/src/UpdateEntityOperator.cpp @@ -173,6 +173,8 @@ bool UpdateEntityOperator::preRecursion(const OctreeElementPointer& element) { if (oldElement != _containingElement) { qCDebug(entities) << "WARNING entity moved during UpdateEntityOperator recursion"; _containingElement->removeEntityItem(_existingEntity); + } else { + _containingElement->bumpChangedContent(); } if (_wantDebug) { From 929d52276e6fa8a108a134dd675a96c3675227f2 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 26 Jul 2017 09:03:57 -0700 Subject: [PATCH 14/88] minor cleanup --- .../src/entities/EntityTreeSendThread.cpp | 13 ++++--------- .../src/entities/EntityTreeSendThread.h | 1 - 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index b971634d7e..34b1c6e123 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -43,7 +43,7 @@ void PrioritizedEntity::updatePriority(const ViewFrustum& view) { } } } else { - // when in doubt just it something positive + // when in doubt give it something positive _priority = 1.0f; } } else { @@ -193,7 +193,8 @@ EntityTreeElementPointer TreeTraversalPath::getNextElement() { _forks.pop_back(); if (_forks.empty()) { // we've traversed the entire tree - onCompleteTraversal(); + _lastCompletedView = _currentView; + _startOfLastCompletedTraversal = _startOfCurrentTraversal; return nextElement; } // keep looking for nextElement @@ -213,11 +214,6 @@ void TreeTraversalPath::dump() const { } } -void TreeTraversalPath::onCompleteTraversal() { - _lastCompletedView = _currentView; - _startOfLastCompletedTraversal = _startOfCurrentTraversal; -} - void EntityTreeSendThread::preDistributionProcessing() { auto node = _node.toStrongRef(); auto nodeData = static_cast(node->getLinkedData()); @@ -307,7 +303,7 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O ++numElements; now = usecTimestampNow(); - const uint64_t PARTIAL_TRAVERSAL_TIME_BUDGET = 80; + const uint64_t PARTIAL_TRAVERSAL_TIME_BUDGET = 80; // usec if (now - t0 > PARTIAL_TRAVERSAL_TIME_BUDGET) { break; } @@ -327,7 +323,6 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O << " numEntities = " << entities.size() << " dt = " << dt1 << std::endl; // adebug } else if (!_sendQueue.empty()) { - while (!_sendQueue.empty()) { PrioritizedEntity entry = _sendQueue.top(); EntityItemPointer entity = entry.getEntity(); diff --git a/assignment-client/src/entities/EntityTreeSendThread.h b/assignment-client/src/entities/EntityTreeSendThread.h index e81105c977..dd072454b9 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.h +++ b/assignment-client/src/entities/EntityTreeSendThread.h @@ -74,7 +74,6 @@ protected: EntityTreeElementPointer traverseFirstTime(); EntityTreeElementPointer traverseAgain(); EntityTreeElementPointer traverseDelta(); - void onCompleteTraversal(); ViewFrustum _currentView; ViewFrustum _lastCompletedView; From 648b8ff0546ecb96e265d2c335bd29bd1bc9edbe Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 26 Jul 2017 13:31:15 -0700 Subject: [PATCH 15/88] fix repeated and differential traversals --- .../src/entities/EntityTreeSendThread.cpp | 29 +++++++------------ 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index 34b1c6e123..ba66c85be3 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -25,9 +25,9 @@ void PrioritizedEntity::updatePriority(const ViewFrustum& view) { bool success; AACube cube = entity->getQueryAACube(success); if (success) { - glm::vec3 center = cube.calcCenter() - view.getPosition(); + glm::vec3 offset = cube.calcCenter() - view.getPosition(); const float MIN_DISTANCE = 0.001f; - float distanceToCenter = glm::length(center) + MIN_DISTANCE; + float distanceToCenter = glm::length(offset) + MIN_DISTANCE; float distance = distanceToCenter; //- 0.5f * cube.getScale(); if (distance < MIN_DISTANCE) { // this object's bounding box overlaps the camera --> give it a big priority @@ -35,8 +35,8 @@ void PrioritizedEntity::updatePriority(const ViewFrustum& view) { } else { // NOTE: we assume view.aspectRatio < 1.0 (view width greater than height) // so we only check against the larger (horizontal) view angle - float front = glm::dot(center, view.getDirection()) / distanceToCenter; - if (front > cosf(view.getFieldOfView()) || distance < view.getCenterRadius()) { + float front = glm::dot(offset, view.getDirection()) / distanceToCenter; + if (front > cosf(glm::radians(view.getFieldOfView())) || distance < view.getCenterRadius()) { _priority = cube.getScale() / distance; // + front; } else { _priority = INVALID_ENTITY_SEND_PRIORITY; @@ -84,15 +84,8 @@ EntityTreeElementPointer TreeTraversalPath::Fork::getNextElementAgain(const View ++_nextIndex; EntityTreeElementPointer element = _weakElement.lock(); assert(element); // should never lose root element - if (element->getLastChanged() < oldTime) { - _nextIndex = NUMBER_OF_CHILDREN; - return EntityTreeElementPointer(); - } - if (element->getLastChanged() > oldTime) { - return element; - } - } - if (_nextIndex < NUMBER_OF_CHILDREN) { + return element; + } else if (_nextIndex < NUMBER_OF_CHILDREN) { EntityTreeElementPointer element = _weakElement.lock(); if (element) { while (_nextIndex < NUMBER_OF_CHILDREN) { @@ -115,10 +108,6 @@ EntityTreeElementPointer TreeTraversalPath::Fork::getNextElementDelta(const View ++_nextIndex; EntityTreeElementPointer element = _weakElement.lock(); assert(element); // should never lose root element - if (element->getLastChanged() < oldTime) { - _nextIndex = NUMBER_OF_CHILDREN; - return EntityTreeElementPointer(); - } return element; } else if (_nextIndex < NUMBER_OF_CHILDREN) { EntityTreeElementPointer element = _weakElement.lock(); @@ -282,13 +271,14 @@ static size_t adebug = 0; void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged, bool isFullScene) { - if (viewFrustumChanged) { + static int foo=0;++foo;bool verbose=(0==(foo%800)); // adebug + if (viewFrustumChanged || verbose) { ViewFrustum view; nodeData->copyCurrentViewFrustum(view); + std::cout << "adebug reset view" << std::endl; // adebug EntityTreeElementPointer root = std::dynamic_pointer_cast(_myServer->getOctree()->getRoot()); _path.startNewTraversal(view, root); - std::cout << "adebug reset view" << std::endl; // adebug adebug = 0; } if (!_path.empty()) { @@ -321,6 +311,7 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O std::cout << "adebug traverseTreeAndSendContents totalEntities = " << adebug << " numElements = " << numElements << " numEntities = " << entities.size() + << " queueSize = " << _sendQueue.size() << " dt = " << dt1 << std::endl; // adebug } else if (!_sendQueue.empty()) { while (!_sendQueue.empty()) { From 481df4938660a447d7a15115742d51144b14139e Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 7 Aug 2017 11:13:48 -0700 Subject: [PATCH 16/88] on server: note time of entity edit by remote --- libraries/entities/src/EntityTree.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 518d3bd883..f5fa7f4bdc 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -383,6 +383,9 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti UpdateEntityOperator theOperator(getThisPointer(), containingElement, entity, newQueryAACube); recurseTreeWithOperator(&theOperator); entity->setProperties(properties); + if (getIsServer()) { + entity->updateLastEditedFromRemote(); + } // if the entity has children, run UpdateEntityOperator on them. If the children have children, recurse QQueue toProcess; From 64fa3ec88f0b8967275805e24a9c7c331742b154 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 7 Aug 2017 11:14:40 -0700 Subject: [PATCH 17/88] repeated and differential view traversals work --- .../src/entities/EntityTreeSendThread.cpp | 228 ++++++++++-------- .../src/entities/EntityTreeSendThread.h | 104 ++++---- 2 files changed, 190 insertions(+), 142 deletions(-) diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index ba66c85be3..2ecd1c6d7b 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -17,38 +17,60 @@ #include "EntityServer.h" -const float INVALID_ENTITY_SEND_PRIORITY = -1.0e-6f; +const float DO_NOT_SEND = -1.0e-6f; -void PrioritizedEntity::updatePriority(const ViewFrustum& view) { +void TreeTraversalPath::ConicalView::set(const ViewFrustum& viewFrustum) { + // The ConicalView has two parts: a central sphere (same as ViewFrustm) and a circular cone that bounds the frustum part. + // Why? Because approximate intersection tests are much faster to compute for a cone than for a frustum. + _position = viewFrustum.getPosition(); + _direction = viewFrustum.getDirection(); + + // We cache the sin and cos of the half angle of the cone that bounds the frustum. + // (the math here is left as an exercise for the reader) + float A = viewFrustum.getAspectRatio(); + float t = tanf(0.5f * viewFrustum.getFieldOfView()); + _cosAngle = 1.0f / sqrtf(1.0f + (A * A + 1.0f) * (t * t)); + _sinAngle = sqrtf(1.0f - _cosAngle * _cosAngle); + + _radius = viewFrustum.getCenterRadius(); +} + +float TreeTraversalPath::ConicalView::computePriority(const AACube& cube) const { + glm::vec3 p = cube.calcCenter() - _position; // position of bounding sphere in view-frame + float d = glm::length(p); // distance to center of bounding sphere + float r = 0.5f * cube.getScale(); // radius of bounding sphere + if (d < _radius + r) { + return r; + } + if (glm::dot(p, _direction) > sqrtf(d * d - r * r) * _cosAngle - r * _sinAngle) { + const float AVOID_DIVIDE_BY_ZERO = 0.001f; + return r / (d + AVOID_DIVIDE_BY_ZERO); + } + return DO_NOT_SEND; +} + + +// static +float TreeTraversalPath::ConicalView::computePriority(const EntityItemPointer& entity) const { + assert(entity); + bool success; + AACube cube = entity->getQueryAACube(success); + if (success) { + return computePriority(cube); + } else { + // when in doubt give it something positive + return 1.0f; + } +} + +float TreeTraversalPath::PrioritizedEntity::updatePriority(const TreeTraversalPath::ConicalView& conicalView) { EntityItemPointer entity = _weakEntity.lock(); if (entity) { - bool success; - AACube cube = entity->getQueryAACube(success); - if (success) { - glm::vec3 offset = cube.calcCenter() - view.getPosition(); - const float MIN_DISTANCE = 0.001f; - float distanceToCenter = glm::length(offset) + MIN_DISTANCE; - float distance = distanceToCenter; //- 0.5f * cube.getScale(); - if (distance < MIN_DISTANCE) { - // this object's bounding box overlaps the camera --> give it a big priority - _priority = cube.getScale(); - } else { - // NOTE: we assume view.aspectRatio < 1.0 (view width greater than height) - // so we only check against the larger (horizontal) view angle - float front = glm::dot(offset, view.getDirection()) / distanceToCenter; - if (front > cosf(glm::radians(view.getFieldOfView())) || distance < view.getCenterRadius()) { - _priority = cube.getScale() / distance; // + front; - } else { - _priority = INVALID_ENTITY_SEND_PRIORITY; - } - } - } else { - // when in doubt give it something positive - _priority = 1.0f; - } + _priority = conicalView.computePriority(entity); } else { - _priority = INVALID_ENTITY_SEND_PRIORITY; + _priority = DO_NOT_SEND; } + return _priority; } TreeTraversalPath::Fork::Fork(EntityTreeElementPointer& element) : _nextIndex(0) { @@ -56,7 +78,7 @@ TreeTraversalPath::Fork::Fork(EntityTreeElementPointer& element) : _nextIndex(0) _weakElement = element; } -EntityTreeElementPointer TreeTraversalPath::Fork::getNextElement(const ViewFrustum& view) { +EntityTreeElementPointer TreeTraversalPath::Fork::getNextElementFirstTime(const ViewFrustum& view) { if (_nextIndex == -1) { // only get here for the TreeTraversalPath's root Fork at the very beginning of traversal // safe to assume this element is in view @@ -68,7 +90,7 @@ EntityTreeElementPointer TreeTraversalPath::Fork::getNextElement(const ViewFrust while (_nextIndex < NUMBER_OF_CHILDREN) { EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); ++_nextIndex; - if (nextElement && ViewFrustum::OUTSIDE != nextElement->computeViewIntersection(view)) { + if (nextElement && view.cubeIntersectsKeyhole(nextElement->getAACube())) { return nextElement; } } @@ -77,7 +99,7 @@ EntityTreeElementPointer TreeTraversalPath::Fork::getNextElement(const ViewFrust return EntityTreeElementPointer(); } -EntityTreeElementPointer TreeTraversalPath::Fork::getNextElementAgain(const ViewFrustum& view, uint64_t oldTime) { +EntityTreeElementPointer TreeTraversalPath::Fork::getNextElementAgain(const ViewFrustum& view, uint64_t lastTime) { if (_nextIndex == -1) { // only get here for the TreeTraversalPath's root Fork at the very beginning of traversal // safe to assume this element is in view @@ -91,8 +113,8 @@ EntityTreeElementPointer TreeTraversalPath::Fork::getNextElementAgain(const View while (_nextIndex < NUMBER_OF_CHILDREN) { EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); ++_nextIndex; - if (nextElement && nextElement->getLastChanged() > oldTime && - ViewFrustum::OUTSIDE != nextElement->computeViewIntersection(view)) { + if (nextElement && nextElement->getLastChanged() > lastTime && + nextElement && view.cubeIntersectsKeyhole(nextElement->getAACube())) { return nextElement; } } @@ -101,10 +123,10 @@ EntityTreeElementPointer TreeTraversalPath::Fork::getNextElementAgain(const View return EntityTreeElementPointer(); } -EntityTreeElementPointer TreeTraversalPath::Fork::getNextElementDelta(const ViewFrustum& newView, const ViewFrustum& oldView, uint64_t oldTime) { +EntityTreeElementPointer TreeTraversalPath::Fork::getNextElementDifferential(const ViewFrustum& view, const ViewFrustum& lastView, uint64_t lastTime) { if (_nextIndex == -1) { // only get here for the TreeTraversalPath's root Fork at the very beginning of traversal - // safe to assume this element is in newView + // safe to assume this element is in view ++_nextIndex; EntityTreeElementPointer element = _weakElement.lock(); assert(element); // should never lose root element @@ -116,9 +138,9 @@ EntityTreeElementPointer TreeTraversalPath::Fork::getNextElementDelta(const View EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); ++_nextIndex; if (nextElement && - !(nextElement->getLastChanged() < oldTime && - ViewFrustum::INSIDE == nextElement->computeViewIntersection(oldView)) && - ViewFrustum::OUTSIDE != nextElement->computeViewIntersection(newView)) { + (!(nextElement->getLastChanged() < lastTime && + ViewFrustum::INSIDE == lastView.calculateCubeKeyholeIntersection(nextElement->getAACube()))) && + ViewFrustum::OUTSIDE != view.calculateCubeKeyholeIntersection(nextElement->getAACube())) { return nextElement; } } @@ -130,45 +152,41 @@ EntityTreeElementPointer TreeTraversalPath::Fork::getNextElementDelta(const View TreeTraversalPath::TreeTraversalPath() { const int32_t MIN_PATH_DEPTH = 16; _forks.reserve(MIN_PATH_DEPTH); - _traversalCallback = std::bind(&TreeTraversalPath::traverseFirstTime, this); } -void TreeTraversalPath::startNewTraversal(const ViewFrustum& view, EntityTreeElementPointer root) { - if (_startOfLastCompletedTraversal == 0) { - _traversalCallback = std::bind(&TreeTraversalPath::traverseFirstTime, this); - _currentView = view; - } else if (_currentView.isVerySimilar(view)) { - _traversalCallback = std::bind(&TreeTraversalPath::traverseAgain, this); +void TreeTraversalPath::startNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root) { + if (_startOfCompletedTraversal == 0) { + _currentView = viewFrustum; + _getNextElementCallback = [&]() { + return _forks.back().getNextElementFirstTime(_currentView); + }; + + } else if (_currentView.isVerySimilar(viewFrustum)) { + _getNextElementCallback = [&]() { + return _forks.back().getNextElementAgain(_currentView, _startOfCompletedTraversal); + }; } else { - _currentView = view; - _traversalCallback = std::bind(&TreeTraversalPath::traverseDelta, this); + _currentView = viewFrustum; + + _getNextElementCallback = [&]() { + return _forks.back().getNextElementDifferential(_currentView, _completedView, _startOfCompletedTraversal); + }; } + _forks.clear(); - if (root) { - _forks.push_back(Fork(root)); - // set root fork's index such that root element returned at getNextElement() - _forks.back().initRootNextIndex(); - } + assert(root); + _forks.push_back(Fork(root)); + // set root fork's index such that root element returned at getNextElement() + _forks.back().initRootNextIndex(); + _startOfCurrentTraversal = usecTimestampNow(); } -EntityTreeElementPointer TreeTraversalPath::traverseFirstTime() { - return _forks.back().getNextElement(_currentView); -} - -EntityTreeElementPointer TreeTraversalPath::traverseAgain() { - return _forks.back().getNextElementAgain(_currentView, _startOfLastCompletedTraversal); -} - -EntityTreeElementPointer TreeTraversalPath::traverseDelta() { - return _forks.back().getNextElementDelta(_currentView, _lastCompletedView, _startOfLastCompletedTraversal); -} - EntityTreeElementPointer TreeTraversalPath::getNextElement() { - if (_forks.empty() || !_traversalCallback) { + if (_forks.empty()) { return EntityTreeElementPointer(); } - EntityTreeElementPointer nextElement = _traversalCallback(); + EntityTreeElementPointer nextElement = _getNextElementCallback(); if (nextElement) { int8_t nextIndex = _forks.back().getNextIndex(); if (nextIndex > 0) { @@ -182,12 +200,12 @@ EntityTreeElementPointer TreeTraversalPath::getNextElement() { _forks.pop_back(); if (_forks.empty()) { // we've traversed the entire tree - _lastCompletedView = _currentView; - _startOfLastCompletedTraversal = _startOfCurrentTraversal; + _completedView = _currentView; + _startOfCompletedTraversal = _startOfCurrentTraversal; return nextElement; } // keep looking for nextElement - nextElement = _traversalCallback(); + nextElement = _getNextElementCallback(); if (nextElement) { // we've descended one level so add it to the path _forks.push_back(Fork(nextElement)); @@ -199,7 +217,7 @@ EntityTreeElementPointer TreeTraversalPath::getNextElement() { void TreeTraversalPath::dump() const { for (size_t i = 0; i < _forks.size(); ++i) { - std::cout << (int)(_forks[i].getNextIndex()) << "-->"; + std::cout << (int32_t)(_forks[i].getNextIndex()) << "-->"; } } @@ -267,66 +285,74 @@ void EntityTreeSendThread::preDistributionProcessing() { } } -static size_t adebug = 0; - void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged, bool isFullScene) { - static int foo=0;++foo;bool verbose=(0==(foo%800)); // adebug - if (viewFrustumChanged || verbose) { - ViewFrustum view; - nodeData->copyCurrentViewFrustum(view); - std::cout << "adebug reset view" << std::endl; // adebug - EntityTreeElementPointer root = std::dynamic_pointer_cast(_myServer->getOctree()->getRoot()); - _path.startNewTraversal(view, root); - - adebug = 0; + if (nodeData->getUsesFrustum()) { + if (viewFrustumChanged) { + ViewFrustum viewFrustum; + nodeData->copyCurrentViewFrustum(viewFrustum); + EntityTreeElementPointer root = std::dynamic_pointer_cast(_myServer->getOctree()->getRoot()); + _path.startNewTraversal(viewFrustum, root); + } } if (!_path.empty()) { int32_t numElements = 0; uint64_t t0 = usecTimestampNow(); uint64_t now = t0; - QVector entities; + uint32_t numEntities = 0; EntityTreeElementPointer nextElement = _path.getNextElement(); + const ViewFrustum& currentView = _path.getCurrentView(); + TreeTraversalPath::ConicalView conicalView(currentView); while (nextElement) { - nextElement->getEntities(_path.getView(), entities); + nextElement->forEachEntity([&](EntityItemPointer entity) { + ++numEntities; + bool success = false; + AACube cube = entity->getQueryAACube(success); + if (success) { + if (currentView.cubeIntersectsKeyhole(cube)) { + float priority = conicalView.computePriority(cube); + _sendQueue.push(TreeTraversalPath::PrioritizedEntity(entity, priority)); + std::cout << "adebug '" << entity->getName().toStdString() << "' send = " << (priority != DO_NOT_SEND) << std::endl; // adebug + } else { + std::cout << "adebug '" << entity->getName().toStdString() << "' out of view" << std::endl; // adebug + } + } else { + const float WHEN_IN_DOUBT_PRIORITY = 1.0f; + _sendQueue.push(TreeTraversalPath::PrioritizedEntity(entity, WHEN_IN_DOUBT_PRIORITY)); + } + }); ++numElements; now = usecTimestampNow(); - const uint64_t PARTIAL_TRAVERSAL_TIME_BUDGET = 80; // usec + const uint64_t PARTIAL_TRAVERSAL_TIME_BUDGET = 100000; // usec if (now - t0 > PARTIAL_TRAVERSAL_TIME_BUDGET) { break; } nextElement = _path.getNextElement(); } uint64_t dt1 = now - t0; - for (EntityItemPointer& entity : entities) { - PrioritizedEntity entry(entity); - entry.updatePriority(_path.getView()); - if (entry.getPriority() > INVALID_ENTITY_SEND_PRIORITY) { - _sendQueue.push(entry); - } - } - adebug += entities.size(); - std::cout << "adebug traverseTreeAndSendContents totalEntities = " << adebug - << " numElements = " << numElements - << " numEntities = " << entities.size() - << " queueSize = " << _sendQueue.size() - << " dt = " << dt1 << std::endl; // adebug - } else if (!_sendQueue.empty()) { + + //} else if (!_sendQueue.empty()) { + size_t sendQueueSize = _sendQueue.size(); while (!_sendQueue.empty()) { - PrioritizedEntity entry = _sendQueue.top(); + TreeTraversalPath::PrioritizedEntity entry = _sendQueue.top(); EntityItemPointer entity = entry.getEntity(); if (entity) { - std::cout << "adebug traverseTreeAndSendContents() " << entry.getPriority() - << " '" << entity->getName().toStdString() << "'" - << std::endl; // adebug + std::cout << "adebug '" << entity->getName().toStdString() << "'" + << " : " << entry.getPriority() << std::endl; // adebug } _sendQueue.pop(); } // std::priority_queue doesn't have a clear method, // so we "clear" _sendQueue by setting it equal to an empty queue _sendQueue = EntityPriorityQueue(); + std::cout << "adebug -end" + << " E = " << numElements + << " e = " << numEntities + << " Q = " << sendQueueSize + << " dt = " << dt1 << std::endl; // adebug + std::cout << "adebug" << std::endl; // adebug } OctreeSendThread::traverseTreeAndSendContents(node, nodeData, viewFrustumChanged, isFullScene); diff --git a/assignment-client/src/entities/EntityTreeSendThread.h b/assignment-client/src/entities/EntityTreeSendThread.h index dd072454b9..a7ddf7daa1 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.h +++ b/assignment-client/src/entities/EntityTreeSendThread.h @@ -16,36 +16,71 @@ #include "../octree/OctreeSendThread.h" +#include + #include "EntityTreeElement.h" +const float SQRT_TWO_OVER_TWO = 0.7071067811865; +const float DEFAULT_VIEW_RADIUS = 10.0f; + class EntityNodeData; class EntityItem; -class PrioritizedEntity { -public: - PrioritizedEntity(EntityItemPointer entity) : _weakEntity(entity) { } - void updatePriority(const ViewFrustum& view); - EntityItemPointer getEntity() const { return _weakEntity.lock(); } - float getPriority() const { return _priority; } - - class Compare { - public: - bool operator() (const PrioritizedEntity& A, const PrioritizedEntity& B) { return A._priority < B._priority; } - }; - - friend class Compare; - -private: - EntityItemWeakPointer _weakEntity; - float _priority { 0.0f }; -}; -using EntityPriorityQueue = std::priority_queue< PrioritizedEntity, std::vector, PrioritizedEntity::Compare >; class TreeTraversalPath { public: + class ConicalView { + public: + ConicalView() {} + ConicalView(const ViewFrustum& viewFrustum) { set(viewFrustum); } + void set(const ViewFrustum& viewFrustum); + float computePriority(const AACube& cube) const; + float computePriority(const EntityItemPointer& entity) const; + private: + glm::vec3 _position { 0.0f, 0.0f, 0.0f }; + glm::vec3 _direction { 0.0f, 0.0f, 1.0f }; + float _sinAngle { SQRT_TWO_OVER_TWO }; + float _cosAngle { SQRT_TWO_OVER_TWO }; + float _radius { DEFAULT_VIEW_RADIUS }; + }; + + class PrioritizedEntity { + public: + PrioritizedEntity(EntityItemPointer entity, float priority) : _weakEntity(entity), _priority(priority) { } + float updatePriority(const ConicalView& view); + EntityItemPointer getEntity() const { return _weakEntity.lock(); } + float getPriority() const { return _priority; } + + class Compare { + public: + bool operator() (const PrioritizedEntity& A, const PrioritizedEntity& B) { return A._priority < B._priority; } + }; + friend class Compare; + + private: + EntityItemWeakPointer _weakEntity; + float _priority; + }; + + class Fork { + public: + Fork(EntityTreeElementPointer& element); + + EntityTreeElementPointer getNextElementFirstTime(const ViewFrustum& view); + EntityTreeElementPointer getNextElementAgain(const ViewFrustum& view, uint64_t lastTime); + EntityTreeElementPointer getNextElementDifferential(const ViewFrustum& view, const ViewFrustum& lastView, uint64_t lastTime); + + int8_t getNextIndex() const { return _nextIndex; } + void initRootNextIndex() { _nextIndex = -1; } + + protected: + EntityTreeElementWeakPointer _weakElement; + int8_t _nextIndex; + }; + TreeTraversalPath(); - void startNewTraversal(const ViewFrustum& view, EntityTreeElementPointer root); + void startNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root); EntityTreeElementPointer getNextElement(); @@ -55,34 +90,21 @@ public: size_t size() const { return _forks.size(); } // adebug void dump() const; - class Fork { - public: - Fork(EntityTreeElementPointer& element); - - EntityTreeElementPointer getNextElement(const ViewFrustum& view); - EntityTreeElementPointer getNextElementAgain(const ViewFrustum& view, uint64_t oldTime); - EntityTreeElementPointer getNextElementDelta(const ViewFrustum& newView, const ViewFrustum& oldView, uint64_t oldTime); - int8_t getNextIndex() const { return _nextIndex; } - void initRootNextIndex() { _nextIndex = -1; } - - protected: - EntityTreeElementWeakPointer _weakElement; - int8_t _nextIndex; - }; + const ViewFrustum& getCurrentView() const { return _currentView; } + //float computePriority(EntityItemPointer& entity) const { return _computePriorityCallback(entity); } protected: - EntityTreeElementPointer traverseFirstTime(); - EntityTreeElementPointer traverseAgain(); - EntityTreeElementPointer traverseDelta(); - ViewFrustum _currentView; - ViewFrustum _lastCompletedView; + ViewFrustum _completedView; std::vector _forks; - std::function _traversalCallback { nullptr }; - uint64_t _startOfLastCompletedTraversal { 0 }; + std::function _getNextElementCallback { nullptr }; + //std::function _computePriorityCallback { nullptr }; + uint64_t _startOfCompletedTraversal { 0 }; uint64_t _startOfCurrentTraversal { 0 }; }; +using EntityPriorityQueue = std::priority_queue< TreeTraversalPath::PrioritizedEntity, std::vector, TreeTraversalPath::PrioritizedEntity::Compare >; + class EntityTreeSendThread : public OctreeSendThread { From bf27412091f81d5e4c3d6fbfc0ae2608d5c83e53 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 7 Aug 2017 12:00:10 -0700 Subject: [PATCH 18/88] cleanup --- .../src/entities/EntityTreeSendThread.cpp | 11 ++--------- assignment-client/src/entities/EntityTreeSendThread.h | 2 -- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index 2ecd1c6d7b..3a206dae27 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -167,7 +167,6 @@ void TreeTraversalPath::startNewTraversal(const ViewFrustum& viewFrustum, Entity }; } else { _currentView = viewFrustum; - _getNextElementCallback = [&]() { return _forks.back().getNextElementDifferential(_currentView, _completedView, _startOfCompletedTraversal); }; @@ -296,17 +295,14 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O } } if (!_path.empty()) { - int32_t numElements = 0; uint64_t t0 = usecTimestampNow(); uint64_t now = t0; - uint32_t numEntities = 0; - EntityTreeElementPointer nextElement = _path.getNextElement(); const ViewFrustum& currentView = _path.getCurrentView(); TreeTraversalPath::ConicalView conicalView(currentView); + EntityTreeElementPointer nextElement = _path.getNextElement(); while (nextElement) { nextElement->forEachEntity([&](EntityItemPointer entity) { - ++numEntities; bool success = false; AACube cube = entity->getQueryAACube(success); if (success) { @@ -322,7 +318,6 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O _sendQueue.push(TreeTraversalPath::PrioritizedEntity(entity, WHEN_IN_DOUBT_PRIORITY)); } }); - ++numElements; now = usecTimestampNow(); const uint64_t PARTIAL_TRAVERSAL_TIME_BUDGET = 100000; // usec @@ -348,9 +343,7 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O // so we "clear" _sendQueue by setting it equal to an empty queue _sendQueue = EntityPriorityQueue(); std::cout << "adebug -end" - << " E = " << numElements - << " e = " << numEntities - << " Q = " << sendQueueSize + << " Q.size = " << sendQueueSize << " dt = " << dt1 << std::endl; // adebug std::cout << "adebug" << std::endl; // adebug } diff --git a/assignment-client/src/entities/EntityTreeSendThread.h b/assignment-client/src/entities/EntityTreeSendThread.h index a7ddf7daa1..0e41f032a9 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.h +++ b/assignment-client/src/entities/EntityTreeSendThread.h @@ -91,14 +91,12 @@ public: void dump() const; const ViewFrustum& getCurrentView() const { return _currentView; } - //float computePriority(EntityItemPointer& entity) const { return _computePriorityCallback(entity); } protected: ViewFrustum _currentView; ViewFrustum _completedView; std::vector _forks; std::function _getNextElementCallback { nullptr }; - //std::function _computePriorityCallback { nullptr }; uint64_t _startOfCompletedTraversal { 0 }; uint64_t _startOfCurrentTraversal { 0 }; }; From 91908ca3da759b0f691e4788149099b7e311cb31 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 7 Aug 2017 13:33:28 -0700 Subject: [PATCH 19/88] moved TreePathTraversal logic into EntityTreeSendThread --- .../src/entities/EntityTreeSendThread.cpp | 174 +++++++++--------- .../src/entities/EntityTreeSendThread.h | 133 ++++++------- 2 files changed, 144 insertions(+), 163 deletions(-) diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index 3a206dae27..8d2d620620 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -19,7 +19,7 @@ const float DO_NOT_SEND = -1.0e-6f; -void TreeTraversalPath::ConicalView::set(const ViewFrustum& viewFrustum) { +void ConicalView::set(const ViewFrustum& viewFrustum) { // The ConicalView has two parts: a central sphere (same as ViewFrustm) and a circular cone that bounds the frustum part. // Why? Because approximate intersection tests are much faster to compute for a cone than for a frustum. _position = viewFrustum.getPosition(); @@ -35,7 +35,7 @@ void TreeTraversalPath::ConicalView::set(const ViewFrustum& viewFrustum) { _radius = viewFrustum.getCenterRadius(); } -float TreeTraversalPath::ConicalView::computePriority(const AACube& cube) const { +float ConicalView::computePriority(const AACube& cube) const { glm::vec3 p = cube.calcCenter() - _position; // position of bounding sphere in view-frame float d = glm::length(p); // distance to center of bounding sphere float r = 0.5f * cube.getScale(); // radius of bounding sphere @@ -51,7 +51,7 @@ float TreeTraversalPath::ConicalView::computePriority(const AACube& cube) const // static -float TreeTraversalPath::ConicalView::computePriority(const EntityItemPointer& entity) const { +float ConicalView::computePriority(const EntityItemPointer& entity) const { assert(entity); bool success; AACube cube = entity->getQueryAACube(success); @@ -63,7 +63,7 @@ float TreeTraversalPath::ConicalView::computePriority(const EntityItemPointer& e } } -float TreeTraversalPath::PrioritizedEntity::updatePriority(const TreeTraversalPath::ConicalView& conicalView) { +float PrioritizedEntity::updatePriority(const ConicalView& conicalView) { EntityItemPointer entity = _weakEntity.lock(); if (entity) { _priority = conicalView.computePriority(entity); @@ -73,14 +73,14 @@ float TreeTraversalPath::PrioritizedEntity::updatePriority(const TreeTraversalPa return _priority; } -TreeTraversalPath::Fork::Fork(EntityTreeElementPointer& element) : _nextIndex(0) { +Fork::Fork(EntityTreeElementPointer& element) : _nextIndex(0) { assert(element); _weakElement = element; } -EntityTreeElementPointer TreeTraversalPath::Fork::getNextElementFirstTime(const ViewFrustum& view) { +EntityTreeElementPointer Fork::getNextElementFirstTime(const ViewFrustum& view) { if (_nextIndex == -1) { - // only get here for the TreeTraversalPath's root Fork at the very beginning of traversal + // only get here for the root Fork at the very beginning of traversal // safe to assume this element is in view ++_nextIndex; return _weakElement.lock(); @@ -99,9 +99,9 @@ EntityTreeElementPointer TreeTraversalPath::Fork::getNextElementFirstTime(const return EntityTreeElementPointer(); } -EntityTreeElementPointer TreeTraversalPath::Fork::getNextElementAgain(const ViewFrustum& view, uint64_t lastTime) { +EntityTreeElementPointer Fork::getNextElementAgain(const ViewFrustum& view, uint64_t lastTime) { if (_nextIndex == -1) { - // only get here for the TreeTraversalPath's root Fork at the very beginning of traversal + // only get here for the root Fork at the very beginning of traversal // safe to assume this element is in view ++_nextIndex; EntityTreeElementPointer element = _weakElement.lock(); @@ -123,9 +123,9 @@ EntityTreeElementPointer TreeTraversalPath::Fork::getNextElementAgain(const View return EntityTreeElementPointer(); } -EntityTreeElementPointer TreeTraversalPath::Fork::getNextElementDifferential(const ViewFrustum& view, const ViewFrustum& lastView, uint64_t lastTime) { +EntityTreeElementPointer Fork::getNextElementDifferential(const ViewFrustum& view, const ViewFrustum& lastView, uint64_t lastTime) { if (_nextIndex == -1) { - // only get here for the TreeTraversalPath's root Fork at the very beginning of traversal + // only get here for the root Fork at the very beginning of traversal // safe to assume this element is in view ++_nextIndex; EntityTreeElementPointer element = _weakElement.lock(); @@ -149,77 +149,12 @@ EntityTreeElementPointer TreeTraversalPath::Fork::getNextElementDifferential(con return EntityTreeElementPointer(); } -TreeTraversalPath::TreeTraversalPath() { +EntityTreeSendThread::EntityTreeSendThread(OctreeServer* myServer, const SharedNodePointer& node) + : OctreeSendThread(myServer, node) { const int32_t MIN_PATH_DEPTH = 16; _forks.reserve(MIN_PATH_DEPTH); } -void TreeTraversalPath::startNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root) { - if (_startOfCompletedTraversal == 0) { - _currentView = viewFrustum; - _getNextElementCallback = [&]() { - return _forks.back().getNextElementFirstTime(_currentView); - }; - - } else if (_currentView.isVerySimilar(viewFrustum)) { - _getNextElementCallback = [&]() { - return _forks.back().getNextElementAgain(_currentView, _startOfCompletedTraversal); - }; - } else { - _currentView = viewFrustum; - _getNextElementCallback = [&]() { - return _forks.back().getNextElementDifferential(_currentView, _completedView, _startOfCompletedTraversal); - }; - } - - _forks.clear(); - assert(root); - _forks.push_back(Fork(root)); - // set root fork's index such that root element returned at getNextElement() - _forks.back().initRootNextIndex(); - - _startOfCurrentTraversal = usecTimestampNow(); -} - -EntityTreeElementPointer TreeTraversalPath::getNextElement() { - if (_forks.empty()) { - return EntityTreeElementPointer(); - } - EntityTreeElementPointer nextElement = _getNextElementCallback(); - if (nextElement) { - int8_t nextIndex = _forks.back().getNextIndex(); - if (nextIndex > 0) { - // nextElement needs to be added to the path - _forks.push_back(Fork(nextElement)); - } - } else { - // we're done at this level - while (!nextElement) { - // pop one level - _forks.pop_back(); - if (_forks.empty()) { - // we've traversed the entire tree - _completedView = _currentView; - _startOfCompletedTraversal = _startOfCurrentTraversal; - return nextElement; - } - // keep looking for nextElement - nextElement = _getNextElementCallback(); - if (nextElement) { - // we've descended one level so add it to the path - _forks.push_back(Fork(nextElement)); - } - } - } - return nextElement; -} - -void TreeTraversalPath::dump() const { - for (size_t i = 0; i < _forks.size(); ++i) { - std::cout << (int32_t)(_forks[i].getNextIndex()) << "-->"; - } -} - void EntityTreeSendThread::preDistributionProcessing() { auto node = _node.toStrongRef(); auto nodeData = static_cast(node->getLinkedData()); @@ -291,31 +226,30 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O ViewFrustum viewFrustum; nodeData->copyCurrentViewFrustum(viewFrustum); EntityTreeElementPointer root = std::dynamic_pointer_cast(_myServer->getOctree()->getRoot()); - _path.startNewTraversal(viewFrustum, root); + startNewTraversal(viewFrustum, root); } } - if (!_path.empty()) { + if (!_forks.empty()) { uint64_t t0 = usecTimestampNow(); uint64_t now = t0; - const ViewFrustum& currentView = _path.getCurrentView(); - TreeTraversalPath::ConicalView conicalView(currentView); - EntityTreeElementPointer nextElement = _path.getNextElement(); + ConicalView conicalView(_currentView); + EntityTreeElementPointer nextElement = getNextElement(); while (nextElement) { nextElement->forEachEntity([&](EntityItemPointer entity) { bool success = false; AACube cube = entity->getQueryAACube(success); if (success) { - if (currentView.cubeIntersectsKeyhole(cube)) { + if (_currentView.cubeIntersectsKeyhole(cube)) { float priority = conicalView.computePriority(cube); - _sendQueue.push(TreeTraversalPath::PrioritizedEntity(entity, priority)); + _sendQueue.push(PrioritizedEntity(entity, priority)); std::cout << "adebug '" << entity->getName().toStdString() << "' send = " << (priority != DO_NOT_SEND) << std::endl; // adebug } else { std::cout << "adebug '" << entity->getName().toStdString() << "' out of view" << std::endl; // adebug } } else { const float WHEN_IN_DOUBT_PRIORITY = 1.0f; - _sendQueue.push(TreeTraversalPath::PrioritizedEntity(entity, WHEN_IN_DOUBT_PRIORITY)); + _sendQueue.push(PrioritizedEntity(entity, WHEN_IN_DOUBT_PRIORITY)); } }); @@ -324,14 +258,14 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O if (now - t0 > PARTIAL_TRAVERSAL_TIME_BUDGET) { break; } - nextElement = _path.getNextElement(); + nextElement = getNextElement(); } uint64_t dt1 = now - t0; //} else if (!_sendQueue.empty()) { size_t sendQueueSize = _sendQueue.size(); while (!_sendQueue.empty()) { - TreeTraversalPath::PrioritizedEntity entry = _sendQueue.top(); + PrioritizedEntity entry = _sendQueue.top(); EntityItemPointer entity = entry.getEntity(); if (entity) { std::cout << "adebug '" << entity->getName().toStdString() << "'" @@ -400,4 +334,68 @@ bool EntityTreeSendThread::addDescendantsToExtraFlaggedEntities(const QUuid& fil return hasNewChild || hasNewDescendants; } +void EntityTreeSendThread::startNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root) { + if (_startOfCompletedTraversal == 0) { + _currentView = viewFrustum; + _getNextElementCallback = [&]() { + return _forks.back().getNextElementFirstTime(_currentView); + }; + } else if (_currentView.isVerySimilar(viewFrustum)) { + _getNextElementCallback = [&]() { + return _forks.back().getNextElementAgain(_currentView, _startOfCompletedTraversal); + }; + } else { + _currentView = viewFrustum; + _getNextElementCallback = [&]() { + return _forks.back().getNextElementDifferential(_currentView, _completedView, _startOfCompletedTraversal); + }; + } + + _forks.clear(); + assert(root); + _forks.push_back(Fork(root)); + // set root fork's index such that root element returned at getNextElement() + _forks.back().initRootNextIndex(); + + _startOfCurrentTraversal = usecTimestampNow(); +} + +EntityTreeElementPointer EntityTreeSendThread::getNextElement() { + if (_forks.empty()) { + return EntityTreeElementPointer(); + } + EntityTreeElementPointer nextElement = _getNextElementCallback(); + if (nextElement) { + int8_t nextIndex = _forks.back().getNextIndex(); + if (nextIndex > 0) { + // nextElement needs to be added to the path + _forks.push_back(Fork(nextElement)); + } + } else { + // we're done at this level + while (!nextElement) { + // pop one level + _forks.pop_back(); + if (_forks.empty()) { + // we've traversed the entire tree + _completedView = _currentView; + _startOfCompletedTraversal = _startOfCurrentTraversal; + return nextElement; + } + // keep looking for nextElement + nextElement = _getNextElementCallback(); + if (nextElement) { + // we've descended one level so add it to the path + _forks.push_back(Fork(nextElement)); + } + } + } + return nextElement; +} + +void EntityTreeSendThread::dump() const { + for (size_t i = 0; i < _forks.size(); ++i) { + std::cout << (int32_t)(_forks[i].getNextIndex()) << "-->"; + } +} diff --git a/assignment-client/src/entities/EntityTreeSendThread.h b/assignment-client/src/entities/EntityTreeSendThread.h index 0e41f032a9..e9dfa5513d 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.h +++ b/assignment-client/src/entities/EntityTreeSendThread.h @@ -27,87 +27,61 @@ class EntityNodeData; class EntityItem; -class TreeTraversalPath { +class ConicalView { public: - class ConicalView { - public: - ConicalView() {} - ConicalView(const ViewFrustum& viewFrustum) { set(viewFrustum); } - void set(const ViewFrustum& viewFrustum); - float computePriority(const AACube& cube) const; - float computePriority(const EntityItemPointer& entity) const; - private: - glm::vec3 _position { 0.0f, 0.0f, 0.0f }; - glm::vec3 _direction { 0.0f, 0.0f, 1.0f }; - float _sinAngle { SQRT_TWO_OVER_TWO }; - float _cosAngle { SQRT_TWO_OVER_TWO }; - float _radius { DEFAULT_VIEW_RADIUS }; - }; - - class PrioritizedEntity { - public: - PrioritizedEntity(EntityItemPointer entity, float priority) : _weakEntity(entity), _priority(priority) { } - float updatePriority(const ConicalView& view); - EntityItemPointer getEntity() const { return _weakEntity.lock(); } - float getPriority() const { return _priority; } - - class Compare { - public: - bool operator() (const PrioritizedEntity& A, const PrioritizedEntity& B) { return A._priority < B._priority; } - }; - friend class Compare; - - private: - EntityItemWeakPointer _weakEntity; - float _priority; - }; - - class Fork { - public: - Fork(EntityTreeElementPointer& element); - - EntityTreeElementPointer getNextElementFirstTime(const ViewFrustum& view); - EntityTreeElementPointer getNextElementAgain(const ViewFrustum& view, uint64_t lastTime); - EntityTreeElementPointer getNextElementDifferential(const ViewFrustum& view, const ViewFrustum& lastView, uint64_t lastTime); - - int8_t getNextIndex() const { return _nextIndex; } - void initRootNextIndex() { _nextIndex = -1; } - - protected: - EntityTreeElementWeakPointer _weakElement; - int8_t _nextIndex; - }; - - TreeTraversalPath(); - - void startNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root); - - EntityTreeElementPointer getNextElement(); - - const ViewFrustum& getView() const { return _currentView; } - - bool empty() const { return _forks.empty(); } - size_t size() const { return _forks.size(); } // adebug - void dump() const; - - const ViewFrustum& getCurrentView() const { return _currentView; } - -protected: - ViewFrustum _currentView; - ViewFrustum _completedView; - std::vector _forks; - std::function _getNextElementCallback { nullptr }; - uint64_t _startOfCompletedTraversal { 0 }; - uint64_t _startOfCurrentTraversal { 0 }; + ConicalView() {} + ConicalView(const ViewFrustum& viewFrustum) { set(viewFrustum); } + void set(const ViewFrustum& viewFrustum); + float computePriority(const AACube& cube) const; + float computePriority(const EntityItemPointer& entity) const; +private: + glm::vec3 _position { 0.0f, 0.0f, 0.0f }; + glm::vec3 _direction { 0.0f, 0.0f, 1.0f }; + float _sinAngle { SQRT_TWO_OVER_TWO }; + float _cosAngle { SQRT_TWO_OVER_TWO }; + float _radius { DEFAULT_VIEW_RADIUS }; }; -using EntityPriorityQueue = std::priority_queue< TreeTraversalPath::PrioritizedEntity, std::vector, TreeTraversalPath::PrioritizedEntity::Compare >; +class PrioritizedEntity { +public: + PrioritizedEntity(EntityItemPointer entity, float priority) : _weakEntity(entity), _priority(priority) { } + float updatePriority(const ConicalView& view); + EntityItemPointer getEntity() const { return _weakEntity.lock(); } + float getPriority() const { return _priority; } + + class Compare { + public: + bool operator() (const PrioritizedEntity& A, const PrioritizedEntity& B) { return A._priority < B._priority; } + }; + friend class Compare; + +private: + EntityItemWeakPointer _weakEntity; + float _priority; +}; + +class Fork { +public: + Fork(EntityTreeElementPointer& element); + + EntityTreeElementPointer getNextElementFirstTime(const ViewFrustum& view); + EntityTreeElementPointer getNextElementAgain(const ViewFrustum& view, uint64_t lastTime); + EntityTreeElementPointer getNextElementDifferential(const ViewFrustum& view, const ViewFrustum& lastView, uint64_t lastTime); + + int8_t getNextIndex() const { return _nextIndex; } + void initRootNextIndex() { _nextIndex = -1; } + +protected: + EntityTreeElementWeakPointer _weakElement; + int8_t _nextIndex; +}; + +using EntityPriorityQueue = std::priority_queue< PrioritizedEntity, std::vector, PrioritizedEntity::Compare >; class EntityTreeSendThread : public OctreeSendThread { - public: - EntityTreeSendThread(OctreeServer* myServer, const SharedNodePointer& node) : OctreeSendThread(myServer, node) {}; + EntityTreeSendThread(OctreeServer* myServer, const SharedNodePointer& node); protected: void preDistributionProcessing() override; @@ -119,8 +93,17 @@ private: bool addAncestorsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData); bool addDescendantsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData); - TreeTraversalPath _path; + void startNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root); + EntityTreeElementPointer getNextElement(); + void dump() const; + EntityPriorityQueue _sendQueue; + ViewFrustum _currentView; + ViewFrustum _completedView; + std::vector _forks; + std::function _getNextElementCallback { nullptr }; + uint64_t _startOfCompletedTraversal { 0 }; + uint64_t _startOfCurrentTraversal { 0 }; }; #endif // hifi_EntityTreeSendThread_h From dd1febba2f33b5ce5ea6bc0ce78fac4b6d597699 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 8 Aug 2017 12:18:37 -0700 Subject: [PATCH 20/88] add missing bump to element changed content --- libraries/entities/src/UpdateEntityOperator.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/entities/src/UpdateEntityOperator.cpp b/libraries/entities/src/UpdateEntityOperator.cpp index ea284e6d5b..fa7e5ca38f 100644 --- a/libraries/entities/src/UpdateEntityOperator.cpp +++ b/libraries/entities/src/UpdateEntityOperator.cpp @@ -213,6 +213,7 @@ bool UpdateEntityOperator::preRecursion(const OctreeElementPointer& element) { if (_wantDebug) { qCDebug(entities) << " *** This is the same OLD ELEMENT ***"; } + _containingElement->bumpChangedContent(); } else { // otherwise, this is an add case. if (oldElement) { From 3665a3fbee844ae1c3f064d0c597c465a9eb3d76 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 8 Aug 2017 12:18:53 -0700 Subject: [PATCH 21/88] libraries/entities/src/EntityTreeElement.cpp --- libraries/entities/src/EntityTreeElement.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index bf5780e7cb..f6d27bcc87 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -893,7 +893,7 @@ void EntityTreeElement::cleanupEntities() { } _entityItems.clear(); }); - _lastChangedContent = usecTimestampNow(); + bumpChangedContent(); } bool EntityTreeElement::removeEntityWithEntityItemID(const EntityItemID& id) { @@ -907,7 +907,7 @@ bool EntityTreeElement::removeEntityWithEntityItemID(const EntityItemID& id) { // NOTE: only EntityTreeElement should ever be changing the value of entity->_element entity->_element = NULL; _entityItems.removeAt(i); - _lastChangedContent = usecTimestampNow(); + bumpChangedContent(); break; } } @@ -924,7 +924,7 @@ bool EntityTreeElement::removeEntityItem(EntityItemPointer entity) { // NOTE: only EntityTreeElement should ever be changing the value of entity->_element assert(entity->_element.get() == this); entity->_element = NULL; - _lastChangedContent = usecTimestampNow(); + bumpChangedContent(); return true; } return false; @@ -942,7 +942,7 @@ void EntityTreeElement::addEntityItem(EntityItemPointer entity) { withWriteLock([&] { _entityItems.push_back(entity); }); - _lastChangedContent = usecTimestampNow(); + bumpChangedContent(); entity->_element = getThisPointer(); } From 8d535f9c5a0a9cbcb2430a4b45df7467ec2c03f5 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 8 Aug 2017 12:19:12 -0700 Subject: [PATCH 22/88] remove bump to changeFromRemote for server case (revert) --- libraries/entities/src/EntityTree.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index f5fa7f4bdc..518d3bd883 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -383,9 +383,6 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti UpdateEntityOperator theOperator(getThisPointer(), containingElement, entity, newQueryAACube); recurseTreeWithOperator(&theOperator); entity->setProperties(properties); - if (getIsServer()) { - entity->updateLastEditedFromRemote(); - } // if the entity has children, run UpdateEntityOperator on them. If the children have children, recurse QQueue toProcess; From a4564f89d79efb5e8408e193250f142c2c5d45b2 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 8 Aug 2017 12:20:19 -0700 Subject: [PATCH 23/88] traversals work and cull checks of unchanged content --- .../src/entities/EntityTreeSendThread.cpp | 271 ++++++++++++------ .../src/entities/EntityTreeSendThread.h | 22 +- 2 files changed, 197 insertions(+), 96 deletions(-) diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index 8d2d620620..54f2288491 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -78,12 +78,14 @@ Fork::Fork(EntityTreeElementPointer& element) : _nextIndex(0) { _weakElement = element; } -EntityTreeElementPointer Fork::getNextElementFirstTime(const ViewFrustum& view) { +void Fork::getNextVisibleElementFirstTime(VisibleElement& next, const ViewFrustum& view) { + // NOTE: no need to set next.intersection in the "FirstTime" context if (_nextIndex == -1) { // only get here for the root Fork at the very beginning of traversal - // safe to assume this element is in view + // safe to assume this element intersects view ++_nextIndex; - return _weakElement.lock(); + next.element = _weakElement.lock(); + return; } else if (_nextIndex < NUMBER_OF_CHILDREN) { EntityTreeElementPointer element = _weakElement.lock(); if (element) { @@ -91,68 +93,93 @@ EntityTreeElementPointer Fork::getNextElementFirstTime(const ViewFrustum& view) EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); ++_nextIndex; if (nextElement && view.cubeIntersectsKeyhole(nextElement->getAACube())) { - return nextElement; + next.element = nextElement; + return; } } } } - return EntityTreeElementPointer(); + next.element.reset(); } -EntityTreeElementPointer Fork::getNextElementAgain(const ViewFrustum& view, uint64_t lastTime) { +void Fork::getNextVisibleElementAgain(VisibleElement& next, const ViewFrustum& view, uint64_t lastTime) { if (_nextIndex == -1) { // only get here for the root Fork at the very beginning of traversal - // safe to assume this element is in view + // safe to assume this element intersects view ++_nextIndex; EntityTreeElementPointer element = _weakElement.lock(); - assert(element); // should never lose root element - return element; - } else if (_nextIndex < NUMBER_OF_CHILDREN) { + // root case is special: its intersection is always INTERSECT + // and we can skip it if the content hasn't changed + if (element->getLastChangedContent() > lastTime) { + next.element = element; + next.intersection = ViewFrustum::INTERSECT; + return; + } + } + if (_nextIndex < NUMBER_OF_CHILDREN) { EntityTreeElementPointer element = _weakElement.lock(); if (element) { while (_nextIndex < NUMBER_OF_CHILDREN) { EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); ++_nextIndex; - if (nextElement && nextElement->getLastChanged() > lastTime && - nextElement && view.cubeIntersectsKeyhole(nextElement->getAACube())) { - return nextElement; + if (nextElement && nextElement->getLastChanged() > lastTime) { + ViewFrustum::intersection intersection = view.calculateCubeKeyholeIntersection(nextElement->getAACube()); + if (intersection != ViewFrustum::OUTSIDE) { + next.element = nextElement; + next.intersection = intersection; + return; + } } } } } - return EntityTreeElementPointer(); + next.element.reset(); + next.intersection = ViewFrustum::OUTSIDE; } -EntityTreeElementPointer Fork::getNextElementDifferential(const ViewFrustum& view, const ViewFrustum& lastView, uint64_t lastTime) { +void Fork::getNextVisibleElementDifferential(VisibleElement& next, + const ViewFrustum& view, const ViewFrustum& lastView, uint64_t lastTime) { if (_nextIndex == -1) { // only get here for the root Fork at the very beginning of traversal - // safe to assume this element is in view + // safe to assume this element intersects view ++_nextIndex; EntityTreeElementPointer element = _weakElement.lock(); - assert(element); // should never lose root element - return element; - } else if (_nextIndex < NUMBER_OF_CHILDREN) { + // root case is special: its intersection is always INTERSECT + // and we can skip it if the content hasn't changed + if (element->getLastChangedContent() > lastTime) { + next.element = element; + next.intersection = ViewFrustum::INTERSECT; + return; + } + } + if (_nextIndex < NUMBER_OF_CHILDREN) { EntityTreeElementPointer element = _weakElement.lock(); if (element) { while (_nextIndex < NUMBER_OF_CHILDREN) { EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); ++_nextIndex; - if (nextElement && - (!(nextElement->getLastChanged() < lastTime && - ViewFrustum::INSIDE == lastView.calculateCubeKeyholeIntersection(nextElement->getAACube()))) && - ViewFrustum::OUTSIDE != view.calculateCubeKeyholeIntersection(nextElement->getAACube())) { - return nextElement; + if (nextElement) { + AACube cube = nextElement->getAACube(); + // NOTE: for differential case next.intersection is against the _completedView + ViewFrustum::intersection intersection = lastView.calculateCubeKeyholeIntersection(cube); + if ( lastView.calculateCubeKeyholeIntersection(cube) != ViewFrustum::OUTSIDE && + !(intersection == ViewFrustum::INSIDE && nextElement->getLastChanged() < lastTime)) { + next.element = nextElement; + next.intersection = intersection; + return; + } } } } } - return EntityTreeElementPointer(); + next.element.reset(); + next.intersection = ViewFrustum::OUTSIDE; } EntityTreeSendThread::EntityTreeSendThread(OctreeServer* myServer, const SharedNodePointer& node) : OctreeSendThread(myServer, node) { const int32_t MIN_PATH_DEPTH = 16; - _forks.reserve(MIN_PATH_DEPTH); + _traversalPath.reserve(MIN_PATH_DEPTH); } void EntityTreeSendThread::preDistributionProcessing() { @@ -229,41 +256,31 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O startNewTraversal(viewFrustum, root); } } - if (!_forks.empty()) { - uint64_t t0 = usecTimestampNow(); - uint64_t now = t0; + if (!_traversalPath.empty()) { + uint64_t startTime = usecTimestampNow(); + uint64_t now = startTime; - ConicalView conicalView(_currentView); - EntityTreeElementPointer nextElement = getNextElement(); - while (nextElement) { - nextElement->forEachEntity([&](EntityItemPointer entity) { - bool success = false; - AACube cube = entity->getQueryAACube(success); - if (success) { - if (_currentView.cubeIntersectsKeyhole(cube)) { - float priority = conicalView.computePriority(cube); - _sendQueue.push(PrioritizedEntity(entity, priority)); - std::cout << "adebug '" << entity->getName().toStdString() << "' send = " << (priority != DO_NOT_SEND) << std::endl; // adebug - } else { - std::cout << "adebug '" << entity->getName().toStdString() << "' out of view" << std::endl; // adebug - } - } else { - const float WHEN_IN_DOUBT_PRIORITY = 1.0f; - _sendQueue.push(PrioritizedEntity(entity, WHEN_IN_DOUBT_PRIORITY)); - } - }); + VisibleElement next; + getNextVisibleElement(next); + while (next.element) { + if (next.element->hasContent()) { + _scanNextElementCallback(next); + } - now = usecTimestampNow(); + // TODO: pick a reasonable budget for each partial traversal const uint64_t PARTIAL_TRAVERSAL_TIME_BUDGET = 100000; // usec - if (now - t0 > PARTIAL_TRAVERSAL_TIME_BUDGET) { + now = usecTimestampNow(); + if (now - startTime > PARTIAL_TRAVERSAL_TIME_BUDGET) { break; } - nextElement = getNextElement(); + getNextVisibleElement(next); } - uint64_t dt1 = now - t0; - //} else if (!_sendQueue.empty()) { - size_t sendQueueSize = _sendQueue.size(); + uint64_t dt = now - startTime; + std::cout << "adebug traversal complete " << " Q.size = " << _sendQueue.size() << " dt = " << dt << std::endl; // adebug + } + if (!_sendQueue.empty()) { + // print what needs to be sent while (!_sendQueue.empty()) { PrioritizedEntity entry = _sendQueue.top(); EntityItemPointer entity = entry.getEntity(); @@ -272,14 +289,8 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O << " : " << entry.getPriority() << std::endl; // adebug } _sendQueue.pop(); + std::cout << "adebug" << std::endl; // adebug } - // std::priority_queue doesn't have a clear method, - // so we "clear" _sendQueue by setting it equal to an empty queue - _sendQueue = EntityPriorityQueue(); - std::cout << "adebug -end" - << " Q.size = " << sendQueueSize - << " dt = " << dt1 << std::endl; // adebug - std::cout << "adebug" << std::endl; // adebug } OctreeSendThread::traverseTreeAndSendContents(node, nodeData, viewFrustumChanged, isFullScene); @@ -335,67 +346,149 @@ bool EntityTreeSendThread::addDescendantsToExtraFlaggedEntities(const QUuid& fil } void EntityTreeSendThread::startNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root) { + // there are three types of traversal: + // + // (1) FirstTime = at login --> find everything in view + // (2) Again = view hasn't changed --> find what has changed since last complete traversal + // (3) Differential = view has changed --> find what has changed or in new view but not old + // + // For each traversal type we define two callback lambdas: + // + // _getNextVisibleElementCallback = identifies elements that need to be traversed,i + // updates VisibleElement ref argument with pointer-to-element and view-intersection + // (INSIDE, INTERSECT, or OUTSIDE) + // + // _scanNextElementCallback = identifies entities that need to be appended to _sendQueue + // + // The _conicalView is updated here as a cached view approximation used by the lambdas for efficient + // computation of entity sorting priorities. + // if (_startOfCompletedTraversal == 0) { + // first time _currentView = viewFrustum; - _getNextElementCallback = [&]() { - return _forks.back().getNextElementFirstTime(_currentView); + _conicalView.set(_currentView); + + _getNextVisibleElementCallback = [&](VisibleElement& next) { + _traversalPath.back().getNextVisibleElementFirstTime(next, _currentView); }; + _scanNextElementCallback = [&](VisibleElement& next) { + next.element->forEachEntity([&](EntityItemPointer entity) { + bool success = false; + AACube cube = entity->getQueryAACube(success); + if (success) { + if (_currentView.cubeIntersectsKeyhole(cube)) { + float priority = _conicalView.computePriority(cube); + _sendQueue.push(PrioritizedEntity(entity, priority)); + } + } else { + const float WHEN_IN_DOUBT_PRIORITY = 1.0f; + _sendQueue.push(PrioritizedEntity(entity, WHEN_IN_DOUBT_PRIORITY)); + } + }); + }; } else if (_currentView.isVerySimilar(viewFrustum)) { - _getNextElementCallback = [&]() { - return _forks.back().getNextElementAgain(_currentView, _startOfCompletedTraversal); + // again + _getNextVisibleElementCallback = [&](VisibleElement& next) { + _traversalPath.back().getNextVisibleElementAgain(next, _currentView, _startOfCompletedTraversal); + }; + + _scanNextElementCallback = [&](VisibleElement& next) { + if (next.element->getLastChangedContent() > _startOfCompletedTraversal) { + next.element->forEachEntity([&](EntityItemPointer entity) { + if (entity->getLastEdited() > _startOfCompletedTraversal) { + bool success = false; + AACube cube = entity->getQueryAACube(success); + if (success) { + if (next.intersection == ViewFrustum::INSIDE || _currentView.cubeIntersectsKeyhole(cube)) { + float priority = _conicalView.computePriority(cube); + _sendQueue.push(PrioritizedEntity(entity, priority)); + } + } else { + const float WHEN_IN_DOUBT_PRIORITY = 1.0f; + _sendQueue.push(PrioritizedEntity(entity, WHEN_IN_DOUBT_PRIORITY)); + } + } + }); + } }; } else { + // differential _currentView = viewFrustum; - _getNextElementCallback = [&]() { - return _forks.back().getNextElementDifferential(_currentView, _completedView, _startOfCompletedTraversal); + _conicalView.set(_currentView); + + _getNextVisibleElementCallback = [&](VisibleElement& next) { + _traversalPath.back().getNextVisibleElementDifferential(next, _currentView, _completedView, _startOfCompletedTraversal); + }; + + _scanNextElementCallback = [&](VisibleElement& next) { + // NOTE: for differential case next.intersection is against _completedView not _currentView + if (next.element->getLastChangedContent() > _startOfCompletedTraversal || next.intersection != ViewFrustum::INSIDE) { + next.element->forEachEntity([&](EntityItemPointer entity) { + bool success = false; + AACube cube = entity->getQueryAACube(success); + if (success) { + if (_currentView.cubeIntersectsKeyhole(cube) && + (entity->getLastEdited() > _startOfCompletedTraversal || + !_completedView.cubeIntersectsKeyhole(cube))) { + float priority = _conicalView.computePriority(cube); + _sendQueue.push(PrioritizedEntity(entity, priority)); + } + } else { + const float WHEN_IN_DOUBT_PRIORITY = 1.0f; + _sendQueue.push(PrioritizedEntity(entity, WHEN_IN_DOUBT_PRIORITY)); + } + }); + } }; } - _forks.clear(); + _traversalPath.clear(); assert(root); - _forks.push_back(Fork(root)); + _traversalPath.push_back(Fork(root)); // set root fork's index such that root element returned at getNextElement() - _forks.back().initRootNextIndex(); + _traversalPath.back().initRootNextIndex(); _startOfCurrentTraversal = usecTimestampNow(); } -EntityTreeElementPointer EntityTreeSendThread::getNextElement() { - if (_forks.empty()) { - return EntityTreeElementPointer(); +void EntityTreeSendThread::getNextVisibleElement(VisibleElement& next) { + if (_traversalPath.empty()) { + next.element.reset(); + next.intersection = ViewFrustum::OUTSIDE; + return; } - EntityTreeElementPointer nextElement = _getNextElementCallback(); - if (nextElement) { - int8_t nextIndex = _forks.back().getNextIndex(); + _getNextVisibleElementCallback(next); + if (next.element) { + int8_t nextIndex = _traversalPath.back().getNextIndex(); if (nextIndex > 0) { - // nextElement needs to be added to the path - _forks.push_back(Fork(nextElement)); + // next.element needs to be added to the path + _traversalPath.push_back(Fork(next.element)); } } else { // we're done at this level - while (!nextElement) { + while (!next.element) { // pop one level - _forks.pop_back(); - if (_forks.empty()) { + _traversalPath.pop_back(); + if (_traversalPath.empty()) { // we've traversed the entire tree _completedView = _currentView; _startOfCompletedTraversal = _startOfCurrentTraversal; - return nextElement; + return; } - // keep looking for nextElement - nextElement = _getNextElementCallback(); - if (nextElement) { + // keep looking for next + _getNextVisibleElementCallback(next); + if (next.element) { // we've descended one level so add it to the path - _forks.push_back(Fork(nextElement)); + _traversalPath.push_back(Fork(next.element)); } } } - return nextElement; } +// DEBUG method: delete later void EntityTreeSendThread::dump() const { - for (size_t i = 0; i < _forks.size(); ++i) { - std::cout << (int32_t)(_forks[i].getNextIndex()) << "-->"; + for (size_t i = 0; i < _traversalPath.size(); ++i) { + std::cout << (int32_t)(_traversalPath[i].getNextIndex()) << "-->"; } } diff --git a/assignment-client/src/entities/EntityTreeSendThread.h b/assignment-client/src/entities/EntityTreeSendThread.h index e9dfa5513d..93d5a99b24 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.h +++ b/assignment-client/src/entities/EntityTreeSendThread.h @@ -60,13 +60,19 @@ private: float _priority; }; +class VisibleElement { +public: + EntityTreeElementPointer element; + ViewFrustum::intersection intersection { ViewFrustum::OUTSIDE }; +}; + class Fork { public: Fork(EntityTreeElementPointer& element); - EntityTreeElementPointer getNextElementFirstTime(const ViewFrustum& view); - EntityTreeElementPointer getNextElementAgain(const ViewFrustum& view, uint64_t lastTime); - EntityTreeElementPointer getNextElementDifferential(const ViewFrustum& view, const ViewFrustum& lastView, uint64_t lastTime); + void getNextVisibleElementFirstTime(VisibleElement& next, const ViewFrustum& view); + void getNextVisibleElementAgain(VisibleElement& next, const ViewFrustum& view, uint64_t lastTime); + void getNextVisibleElementDifferential(VisibleElement& next, const ViewFrustum& view, const ViewFrustum& lastView, uint64_t lastTime); int8_t getNextIndex() const { return _nextIndex; } void initRootNextIndex() { _nextIndex = -1; } @@ -94,14 +100,16 @@ private: bool addDescendantsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData); void startNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root); - EntityTreeElementPointer getNextElement(); - void dump() const; + void getNextVisibleElement(VisibleElement& element); + void dump() const; // DEBUG method, delete later EntityPriorityQueue _sendQueue; ViewFrustum _currentView; ViewFrustum _completedView; - std::vector _forks; - std::function _getNextElementCallback { nullptr }; + ConicalView _conicalView; // optimized view for fast priority calculations + std::vector _traversalPath; + std::function _getNextVisibleElementCallback { nullptr }; + std::function _scanNextElementCallback { nullptr }; uint64_t _startOfCompletedTraversal { 0 }; uint64_t _startOfCurrentTraversal { 0 }; }; From 64cd209835f1f42404283d8b57c1f79cb4c983cb Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 8 Aug 2017 12:27:30 -0700 Subject: [PATCH 24/88] debug traverse again every two seconds --- assignment-client/src/entities/EntityTreeSendThread.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index 54f2288491..0ddf8a21f5 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -248,6 +248,14 @@ void EntityTreeSendThread::preDistributionProcessing() { void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged, bool isFullScene) { + // BEGIN EXPERIMENTAL DIFFERENTIAL TRAVERSAL + { + // DEBUG HACK: trigger traversal (Again) every so often + const uint64_t TRAVERSE_AGAIN_PERIOD = 2 * USECS_PER_SECOND; + if (!viewFrustumChanged && usecTimestampNow() > _startOfCompletedTraversal + TRAVERSE_AGAIN_PERIOD) { + viewFrustumChanged = true; + } + } if (nodeData->getUsesFrustum()) { if (viewFrustumChanged) { ViewFrustum viewFrustum; @@ -292,6 +300,7 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O std::cout << "adebug" << std::endl; // adebug } } + // END EXPERIMENTAL DIFFERENTIAL TRAVERSAL OctreeSendThread::traverseTreeAndSendContents(node, nodeData, viewFrustumChanged, isFullScene); } From abf968aab64f42c90343574f7bd7936a45be9492 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 8 Aug 2017 13:05:08 -0700 Subject: [PATCH 25/88] split EntityPriorityQueue stuff into separate file --- .../src/entities/EntityPriorityQueue.cpp | 170 ++++++++++++++++++ .../src/entities/EntityPriorityQueue.h | 86 +++++++++ .../src/entities/EntityTreeSendThread.cpp | 165 +---------------- .../src/entities/EntityTreeSendThread.h | 71 +------- 4 files changed, 262 insertions(+), 230 deletions(-) create mode 100644 assignment-client/src/entities/EntityPriorityQueue.cpp create mode 100644 assignment-client/src/entities/EntityPriorityQueue.h diff --git a/assignment-client/src/entities/EntityPriorityQueue.cpp b/assignment-client/src/entities/EntityPriorityQueue.cpp new file mode 100644 index 0000000000..87cc77161d --- /dev/null +++ b/assignment-client/src/entities/EntityPriorityQueue.cpp @@ -0,0 +1,170 @@ +// +// EntityPriorityQueue.cpp +// assignment-client/src/entities +// +// Created by Andrew Meadows 2017.08.08 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "EntityPriorityQueue.h" + +const float DO_NOT_SEND = -1.0e-6f; + +void ConicalView::set(const ViewFrustum& viewFrustum) { + // The ConicalView has two parts: a central sphere (same as ViewFrustm) and a circular cone that bounds the frustum part. + // Why? Because approximate intersection tests are much faster to compute for a cone than for a frustum. + _position = viewFrustum.getPosition(); + _direction = viewFrustum.getDirection(); + + // We cache the sin and cos of the half angle of the cone that bounds the frustum. + // (the math here is left as an exercise for the reader) + float A = viewFrustum.getAspectRatio(); + float t = tanf(0.5f * viewFrustum.getFieldOfView()); + _cosAngle = 1.0f / sqrtf(1.0f + (A * A + 1.0f) * (t * t)); + _sinAngle = sqrtf(1.0f - _cosAngle * _cosAngle); + + _radius = viewFrustum.getCenterRadius(); +} + +float ConicalView::computePriority(const AACube& cube) const { + glm::vec3 p = cube.calcCenter() - _position; // position of bounding sphere in view-frame + float d = glm::length(p); // distance to center of bounding sphere + float r = 0.5f * cube.getScale(); // radius of bounding sphere + if (d < _radius + r) { + return r; + } + if (glm::dot(p, _direction) > sqrtf(d * d - r * r) * _cosAngle - r * _sinAngle) { + const float AVOID_DIVIDE_BY_ZERO = 0.001f; + return r / (d + AVOID_DIVIDE_BY_ZERO); + } + return DO_NOT_SEND; +} + +// static +float ConicalView::computePriority(const EntityItemPointer& entity) const { + assert(entity); + bool success; + AACube cube = entity->getQueryAACube(success); + if (success) { + return computePriority(cube); + } else { + // when in doubt give it something positive + return 1.0f; + } +} + +float PrioritizedEntity::updatePriority(const ConicalView& conicalView) { + EntityItemPointer entity = _weakEntity.lock(); + if (entity) { + _priority = conicalView.computePriority(entity); + } else { + _priority = DO_NOT_SEND; + } + return _priority; +} + +TraversalWaypoint::TraversalWaypoint(EntityTreeElementPointer& element) : _nextIndex(0) { + assert(element); + _weakElement = element; +} + +void TraversalWaypoint::getNextVisibleElementFirstTime(VisibleElement& next, const ViewFrustum& view) { + // NOTE: no need to set next.intersection in the "FirstTime" context + if (_nextIndex == -1) { + // only get here for the root TraversalWaypoint at the very beginning of traversal + // safe to assume this element intersects view + ++_nextIndex; + next.element = _weakElement.lock(); + return; + } else if (_nextIndex < NUMBER_OF_CHILDREN) { + EntityTreeElementPointer element = _weakElement.lock(); + if (element) { + while (_nextIndex < NUMBER_OF_CHILDREN) { + EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); + ++_nextIndex; + if (nextElement && view.cubeIntersectsKeyhole(nextElement->getAACube())) { + next.element = nextElement; + return; + } + } + } + } + next.element.reset(); +} + +void TraversalWaypoint::getNextVisibleElementAgain(VisibleElement& next, const ViewFrustum& view, uint64_t lastTime) { + if (_nextIndex == -1) { + // only get here for the root TraversalWaypoint at the very beginning of traversal + // safe to assume this element intersects view + ++_nextIndex; + EntityTreeElementPointer element = _weakElement.lock(); + // root case is special: its intersection is always INTERSECT + // and we can skip it if the content hasn't changed + if (element->getLastChangedContent() > lastTime) { + next.element = element; + next.intersection = ViewFrustum::INTERSECT; + return; + } + } + if (_nextIndex < NUMBER_OF_CHILDREN) { + EntityTreeElementPointer element = _weakElement.lock(); + if (element) { + while (_nextIndex < NUMBER_OF_CHILDREN) { + EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); + ++_nextIndex; + if (nextElement && nextElement->getLastChanged() > lastTime) { + ViewFrustum::intersection intersection = view.calculateCubeKeyholeIntersection(nextElement->getAACube()); + if (intersection != ViewFrustum::OUTSIDE) { + next.element = nextElement; + next.intersection = intersection; + return; + } + } + } + } + } + next.element.reset(); + next.intersection = ViewFrustum::OUTSIDE; +} + +void TraversalWaypoint::getNextVisibleElementDifferential(VisibleElement& next, + const ViewFrustum& view, const ViewFrustum& lastView, uint64_t lastTime) { + if (_nextIndex == -1) { + // only get here for the root TraversalWaypoint at the very beginning of traversal + // safe to assume this element intersects view + ++_nextIndex; + EntityTreeElementPointer element = _weakElement.lock(); + // root case is special: its intersection is always INTERSECT + // and we can skip it if the content hasn't changed + if (element->getLastChangedContent() > lastTime) { + next.element = element; + next.intersection = ViewFrustum::INTERSECT; + return; + } + } + if (_nextIndex < NUMBER_OF_CHILDREN) { + EntityTreeElementPointer element = _weakElement.lock(); + if (element) { + while (_nextIndex < NUMBER_OF_CHILDREN) { + EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); + ++_nextIndex; + if (nextElement) { + AACube cube = nextElement->getAACube(); + // NOTE: for differential case next.intersection is against the _completedView + ViewFrustum::intersection intersection = lastView.calculateCubeKeyholeIntersection(cube); + if ( lastView.calculateCubeKeyholeIntersection(cube) != ViewFrustum::OUTSIDE && + !(intersection == ViewFrustum::INSIDE && nextElement->getLastChanged() < lastTime)) { + next.element = nextElement; + next.intersection = intersection; + return; + } + } + } + } + } + next.element.reset(); + next.intersection = ViewFrustum::OUTSIDE; +} diff --git a/assignment-client/src/entities/EntityPriorityQueue.h b/assignment-client/src/entities/EntityPriorityQueue.h new file mode 100644 index 0000000000..cc233e127a --- /dev/null +++ b/assignment-client/src/entities/EntityPriorityQueue.h @@ -0,0 +1,86 @@ +// +// EntityPriorityQueue.h +// assignment-client/src/entities +// +// Created by Andrew Meadows 2017.08.08 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_EntityPriorityQueue_h +#define hifi_EntityPriorityQueue_h + +#include + +#include + +#include "EntityTreeElement.h" + +const float SQRT_TWO_OVER_TWO = 0.7071067811865; +const float DEFAULT_VIEW_RADIUS = 10.0f; + +// ConicalView is an approximation of a ViewFrustum for fast calculation of sort priority. +class ConicalView { +public: + ConicalView() {} + ConicalView(const ViewFrustum& viewFrustum) { set(viewFrustum); } + void set(const ViewFrustum& viewFrustum); + float computePriority(const AACube& cube) const; + float computePriority(const EntityItemPointer& entity) const; +private: + glm::vec3 _position { 0.0f, 0.0f, 0.0f }; + glm::vec3 _direction { 0.0f, 0.0f, 1.0f }; + float _sinAngle { SQRT_TWO_OVER_TWO }; + float _cosAngle { SQRT_TWO_OVER_TWO }; + float _radius { DEFAULT_VIEW_RADIUS }; +}; + +// PrioritizedEntity is a placeholder in a sorted queue. +class PrioritizedEntity { +public: + PrioritizedEntity(EntityItemPointer entity, float priority) : _weakEntity(entity), _priority(priority) { } + float updatePriority(const ConicalView& view); + EntityItemPointer getEntity() const { return _weakEntity.lock(); } + float getPriority() const { return _priority; } + + class Compare { + public: + bool operator() (const PrioritizedEntity& A, const PrioritizedEntity& B) { return A._priority < B._priority; } + }; + friend class Compare; + +private: + EntityItemWeakPointer _weakEntity; + float _priority; +}; + +// VisibleElement is a struct identifying an element and how it intersected the view. +// The intersection is used to optimize culling entities from the sendQueue. +class VisibleElement { +public: + EntityTreeElementPointer element; + ViewFrustum::intersection intersection { ViewFrustum::OUTSIDE }; +}; + +// TraversalWaypoint is an bookmark in a "path" of waypoints during a traversal. +class TraversalWaypoint { +public: + TraversalWaypoint(EntityTreeElementPointer& element); + + void getNextVisibleElementFirstTime(VisibleElement& next, const ViewFrustum& view); + void getNextVisibleElementAgain(VisibleElement& next, const ViewFrustum& view, uint64_t lastTime); + void getNextVisibleElementDifferential(VisibleElement& next, const ViewFrustum& view, const ViewFrustum& lastView, uint64_t lastTime); + + int8_t getNextIndex() const { return _nextIndex; } + void initRootNextIndex() { _nextIndex = -1; } + +protected: + EntityTreeElementWeakPointer _weakElement; + int8_t _nextIndex; +}; + +using EntityPriorityQueue = std::priority_queue< PrioritizedEntity, std::vector, PrioritizedEntity::Compare >; + +#endif // hifi_EntityPriorityQueue_h diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index 0ddf8a21f5..7d6baaca3b 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -17,165 +17,6 @@ #include "EntityServer.h" -const float DO_NOT_SEND = -1.0e-6f; - -void ConicalView::set(const ViewFrustum& viewFrustum) { - // The ConicalView has two parts: a central sphere (same as ViewFrustm) and a circular cone that bounds the frustum part. - // Why? Because approximate intersection tests are much faster to compute for a cone than for a frustum. - _position = viewFrustum.getPosition(); - _direction = viewFrustum.getDirection(); - - // We cache the sin and cos of the half angle of the cone that bounds the frustum. - // (the math here is left as an exercise for the reader) - float A = viewFrustum.getAspectRatio(); - float t = tanf(0.5f * viewFrustum.getFieldOfView()); - _cosAngle = 1.0f / sqrtf(1.0f + (A * A + 1.0f) * (t * t)); - _sinAngle = sqrtf(1.0f - _cosAngle * _cosAngle); - - _radius = viewFrustum.getCenterRadius(); -} - -float ConicalView::computePriority(const AACube& cube) const { - glm::vec3 p = cube.calcCenter() - _position; // position of bounding sphere in view-frame - float d = glm::length(p); // distance to center of bounding sphere - float r = 0.5f * cube.getScale(); // radius of bounding sphere - if (d < _radius + r) { - return r; - } - if (glm::dot(p, _direction) > sqrtf(d * d - r * r) * _cosAngle - r * _sinAngle) { - const float AVOID_DIVIDE_BY_ZERO = 0.001f; - return r / (d + AVOID_DIVIDE_BY_ZERO); - } - return DO_NOT_SEND; -} - - -// static -float ConicalView::computePriority(const EntityItemPointer& entity) const { - assert(entity); - bool success; - AACube cube = entity->getQueryAACube(success); - if (success) { - return computePriority(cube); - } else { - // when in doubt give it something positive - return 1.0f; - } -} - -float PrioritizedEntity::updatePriority(const ConicalView& conicalView) { - EntityItemPointer entity = _weakEntity.lock(); - if (entity) { - _priority = conicalView.computePriority(entity); - } else { - _priority = DO_NOT_SEND; - } - return _priority; -} - -Fork::Fork(EntityTreeElementPointer& element) : _nextIndex(0) { - assert(element); - _weakElement = element; -} - -void Fork::getNextVisibleElementFirstTime(VisibleElement& next, const ViewFrustum& view) { - // NOTE: no need to set next.intersection in the "FirstTime" context - if (_nextIndex == -1) { - // only get here for the root Fork at the very beginning of traversal - // safe to assume this element intersects view - ++_nextIndex; - next.element = _weakElement.lock(); - return; - } else if (_nextIndex < NUMBER_OF_CHILDREN) { - EntityTreeElementPointer element = _weakElement.lock(); - if (element) { - while (_nextIndex < NUMBER_OF_CHILDREN) { - EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); - ++_nextIndex; - if (nextElement && view.cubeIntersectsKeyhole(nextElement->getAACube())) { - next.element = nextElement; - return; - } - } - } - } - next.element.reset(); -} - -void Fork::getNextVisibleElementAgain(VisibleElement& next, const ViewFrustum& view, uint64_t lastTime) { - if (_nextIndex == -1) { - // only get here for the root Fork at the very beginning of traversal - // safe to assume this element intersects view - ++_nextIndex; - EntityTreeElementPointer element = _weakElement.lock(); - // root case is special: its intersection is always INTERSECT - // and we can skip it if the content hasn't changed - if (element->getLastChangedContent() > lastTime) { - next.element = element; - next.intersection = ViewFrustum::INTERSECT; - return; - } - } - if (_nextIndex < NUMBER_OF_CHILDREN) { - EntityTreeElementPointer element = _weakElement.lock(); - if (element) { - while (_nextIndex < NUMBER_OF_CHILDREN) { - EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); - ++_nextIndex; - if (nextElement && nextElement->getLastChanged() > lastTime) { - ViewFrustum::intersection intersection = view.calculateCubeKeyholeIntersection(nextElement->getAACube()); - if (intersection != ViewFrustum::OUTSIDE) { - next.element = nextElement; - next.intersection = intersection; - return; - } - } - } - } - } - next.element.reset(); - next.intersection = ViewFrustum::OUTSIDE; -} - -void Fork::getNextVisibleElementDifferential(VisibleElement& next, - const ViewFrustum& view, const ViewFrustum& lastView, uint64_t lastTime) { - if (_nextIndex == -1) { - // only get here for the root Fork at the very beginning of traversal - // safe to assume this element intersects view - ++_nextIndex; - EntityTreeElementPointer element = _weakElement.lock(); - // root case is special: its intersection is always INTERSECT - // and we can skip it if the content hasn't changed - if (element->getLastChangedContent() > lastTime) { - next.element = element; - next.intersection = ViewFrustum::INTERSECT; - return; - } - } - if (_nextIndex < NUMBER_OF_CHILDREN) { - EntityTreeElementPointer element = _weakElement.lock(); - if (element) { - while (_nextIndex < NUMBER_OF_CHILDREN) { - EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); - ++_nextIndex; - if (nextElement) { - AACube cube = nextElement->getAACube(); - // NOTE: for differential case next.intersection is against the _completedView - ViewFrustum::intersection intersection = lastView.calculateCubeKeyholeIntersection(cube); - if ( lastView.calculateCubeKeyholeIntersection(cube) != ViewFrustum::OUTSIDE && - !(intersection == ViewFrustum::INSIDE && nextElement->getLastChanged() < lastTime)) { - next.element = nextElement; - next.intersection = intersection; - return; - } - } - } - } - } - next.element.reset(); - next.intersection = ViewFrustum::OUTSIDE; -} - EntityTreeSendThread::EntityTreeSendThread(OctreeServer* myServer, const SharedNodePointer& node) : OctreeSendThread(myServer, node) { const int32_t MIN_PATH_DEPTH = 16; @@ -454,7 +295,7 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& viewFrustum, Ent _traversalPath.clear(); assert(root); - _traversalPath.push_back(Fork(root)); + _traversalPath.push_back(TraversalWaypoint(root)); // set root fork's index such that root element returned at getNextElement() _traversalPath.back().initRootNextIndex(); @@ -472,7 +313,7 @@ void EntityTreeSendThread::getNextVisibleElement(VisibleElement& next) { int8_t nextIndex = _traversalPath.back().getNextIndex(); if (nextIndex > 0) { // next.element needs to be added to the path - _traversalPath.push_back(Fork(next.element)); + _traversalPath.push_back(TraversalWaypoint(next.element)); } } else { // we're done at this level @@ -489,7 +330,7 @@ void EntityTreeSendThread::getNextVisibleElement(VisibleElement& next) { _getNextVisibleElementCallback(next); if (next.element) { // we've descended one level so add it to the path - _traversalPath.push_back(Fork(next.element)); + _traversalPath.push_back(TraversalWaypoint(next.element)); } } } diff --git a/assignment-client/src/entities/EntityTreeSendThread.h b/assignment-client/src/entities/EntityTreeSendThread.h index 93d5a99b24..9a1a57b41c 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.h +++ b/assignment-client/src/entities/EntityTreeSendThread.h @@ -12,80 +12,15 @@ #ifndef hifi_EntityTreeSendThread_h #define hifi_EntityTreeSendThread_h -#include - #include "../octree/OctreeSendThread.h" -#include - -#include "EntityTreeElement.h" - -const float SQRT_TWO_OVER_TWO = 0.7071067811865; -const float DEFAULT_VIEW_RADIUS = 10.0f; +#include "EntityPriorityQueue.h" class EntityNodeData; class EntityItem; - -class ConicalView { -public: - ConicalView() {} - ConicalView(const ViewFrustum& viewFrustum) { set(viewFrustum); } - void set(const ViewFrustum& viewFrustum); - float computePriority(const AACube& cube) const; - float computePriority(const EntityItemPointer& entity) const; -private: - glm::vec3 _position { 0.0f, 0.0f, 0.0f }; - glm::vec3 _direction { 0.0f, 0.0f, 1.0f }; - float _sinAngle { SQRT_TWO_OVER_TWO }; - float _cosAngle { SQRT_TWO_OVER_TWO }; - float _radius { DEFAULT_VIEW_RADIUS }; -}; - -class PrioritizedEntity { -public: - PrioritizedEntity(EntityItemPointer entity, float priority) : _weakEntity(entity), _priority(priority) { } - float updatePriority(const ConicalView& view); - EntityItemPointer getEntity() const { return _weakEntity.lock(); } - float getPriority() const { return _priority; } - - class Compare { - public: - bool operator() (const PrioritizedEntity& A, const PrioritizedEntity& B) { return A._priority < B._priority; } - }; - friend class Compare; - -private: - EntityItemWeakPointer _weakEntity; - float _priority; -}; - -class VisibleElement { -public: - EntityTreeElementPointer element; - ViewFrustum::intersection intersection { ViewFrustum::OUTSIDE }; -}; - -class Fork { -public: - Fork(EntityTreeElementPointer& element); - - void getNextVisibleElementFirstTime(VisibleElement& next, const ViewFrustum& view); - void getNextVisibleElementAgain(VisibleElement& next, const ViewFrustum& view, uint64_t lastTime); - void getNextVisibleElementDifferential(VisibleElement& next, const ViewFrustum& view, const ViewFrustum& lastView, uint64_t lastTime); - - int8_t getNextIndex() const { return _nextIndex; } - void initRootNextIndex() { _nextIndex = -1; } - -protected: - EntityTreeElementWeakPointer _weakElement; - int8_t _nextIndex; -}; - -using EntityPriorityQueue = std::priority_queue< PrioritizedEntity, std::vector, PrioritizedEntity::Compare >; - - class EntityTreeSendThread : public OctreeSendThread { + public: EntityTreeSendThread(OctreeServer* myServer, const SharedNodePointer& node); @@ -107,7 +42,7 @@ private: ViewFrustum _currentView; ViewFrustum _completedView; ConicalView _conicalView; // optimized view for fast priority calculations - std::vector _traversalPath; + std::vector _traversalPath; std::function _getNextVisibleElementCallback { nullptr }; std::function _scanNextElementCallback { nullptr }; uint64_t _startOfCompletedTraversal { 0 }; From 3eb9cd4251026b122bfe9a2e5c956540fe7aa38e Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 8 Aug 2017 13:55:35 -0700 Subject: [PATCH 26/88] add TODO comments --- assignment-client/src/entities/EntityPriorityQueue.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/assignment-client/src/entities/EntityPriorityQueue.cpp b/assignment-client/src/entities/EntityPriorityQueue.cpp index 87cc77161d..7e2d217b12 100644 --- a/assignment-client/src/entities/EntityPriorityQueue.cpp +++ b/assignment-client/src/entities/EntityPriorityQueue.cpp @@ -72,6 +72,7 @@ TraversalWaypoint::TraversalWaypoint(EntityTreeElementPointer& element) : _nextI } void TraversalWaypoint::getNextVisibleElementFirstTime(VisibleElement& next, const ViewFrustum& view) { + // TODO: add LOD culling of elements // NOTE: no need to set next.intersection in the "FirstTime" context if (_nextIndex == -1) { // only get here for the root TraversalWaypoint at the very beginning of traversal @@ -96,6 +97,7 @@ void TraversalWaypoint::getNextVisibleElementFirstTime(VisibleElement& next, con } void TraversalWaypoint::getNextVisibleElementAgain(VisibleElement& next, const ViewFrustum& view, uint64_t lastTime) { + // TODO: add LOD culling of elements if (_nextIndex == -1) { // only get here for the root TraversalWaypoint at the very beginning of traversal // safe to assume this element intersects view @@ -132,6 +134,7 @@ void TraversalWaypoint::getNextVisibleElementAgain(VisibleElement& next, const V void TraversalWaypoint::getNextVisibleElementDifferential(VisibleElement& next, const ViewFrustum& view, const ViewFrustum& lastView, uint64_t lastTime) { + // TODO: add LOD culling of elements if (_nextIndex == -1) { // only get here for the root TraversalWaypoint at the very beginning of traversal // safe to assume this element intersects view From b537d3b1eee0acbb07d31431f68f2c7f20cc14e3 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 8 Aug 2017 14:18:33 -0700 Subject: [PATCH 27/88] more helpful comments --- assignment-client/src/entities/EntityPriorityQueue.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/assignment-client/src/entities/EntityPriorityQueue.cpp b/assignment-client/src/entities/EntityPriorityQueue.cpp index 7e2d217b12..94082593bc 100644 --- a/assignment-client/src/entities/EntityPriorityQueue.cpp +++ b/assignment-client/src/entities/EntityPriorityQueue.cpp @@ -36,6 +36,13 @@ float ConicalView::computePriority(const AACube& cube) const { if (d < _radius + r) { return r; } + // We check the angle between the center of the cube and the _direction of the view. + // If it is less than the sum of the half-angle from center of cone to outer edge plus + // the half apparent angle of the bounding sphere then it is in view. + // + // The math here is left as an exercise for the reader with the following hints: + // (1) We actually check the dot product of the cube's local position rather than the angle and + // (2) we take advantage of this trig identity: cos(A+B) = cos(A)*cos(B) - sin(A)*sin(B) if (glm::dot(p, _direction) > sqrtf(d * d - r * r) * _cosAngle - r * _sinAngle) { const float AVOID_DIVIDE_BY_ZERO = 0.001f; return r / (d + AVOID_DIVIDE_BY_ZERO); From 5fba4cb68ca607c8e34530e42f66eaa3154b315e Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 8 Aug 2017 15:00:07 -0700 Subject: [PATCH 28/88] fix warning about truncation from double to float --- assignment-client/src/entities/EntityPriorityQueue.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assignment-client/src/entities/EntityPriorityQueue.h b/assignment-client/src/entities/EntityPriorityQueue.h index cc233e127a..8eb28dffda 100644 --- a/assignment-client/src/entities/EntityPriorityQueue.h +++ b/assignment-client/src/entities/EntityPriorityQueue.h @@ -18,7 +18,7 @@ #include "EntityTreeElement.h" -const float SQRT_TWO_OVER_TWO = 0.7071067811865; +const float SQRT_TWO_OVER_TWO = 0.7071067811865f; const float DEFAULT_VIEW_RADIUS = 10.0f; // ConicalView is an approximation of a ViewFrustum for fast calculation of sort priority. From 0758b60afc855d77ac328d10a798d4ebd37d94b3 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 9 Aug 2017 13:14:36 -0700 Subject: [PATCH 29/88] abstract DiffTraversal out of EntityTreeSendThread --- .../src/entities/EntityPriorityQueue.cpp | 108 +------- .../src/entities/EntityPriorityQueue.h | 28 +- .../src/entities/EntityTreeSendThread.cpp | 173 +++---------- .../src/entities/EntityTreeSendThread.h | 16 +- libraries/entities/src/DiffTraversal.cpp | 239 ++++++++++++++++++ libraries/entities/src/DiffTraversal.h | 78 ++++++ 6 files changed, 366 insertions(+), 276 deletions(-) create mode 100644 libraries/entities/src/DiffTraversal.cpp create mode 100644 libraries/entities/src/DiffTraversal.h diff --git a/assignment-client/src/entities/EntityPriorityQueue.cpp b/assignment-client/src/entities/EntityPriorityQueue.cpp index 94082593bc..77b46afa24 100644 --- a/assignment-client/src/entities/EntityPriorityQueue.cpp +++ b/assignment-client/src/entities/EntityPriorityQueue.cpp @@ -14,7 +14,7 @@ const float DO_NOT_SEND = -1.0e-6f; void ConicalView::set(const ViewFrustum& viewFrustum) { - // The ConicalView has two parts: a central sphere (same as ViewFrustm) and a circular cone that bounds the frustum part. + // The ConicalView has two parts: a central sphere (same as ViewFrustum) and a circular cone that bounds the frustum part. // Why? Because approximate intersection tests are much faster to compute for a cone than for a frustum. _position = viewFrustum.getPosition(); _direction = viewFrustum.getDirection(); @@ -72,109 +72,3 @@ float PrioritizedEntity::updatePriority(const ConicalView& conicalView) { } return _priority; } - -TraversalWaypoint::TraversalWaypoint(EntityTreeElementPointer& element) : _nextIndex(0) { - assert(element); - _weakElement = element; -} - -void TraversalWaypoint::getNextVisibleElementFirstTime(VisibleElement& next, const ViewFrustum& view) { - // TODO: add LOD culling of elements - // NOTE: no need to set next.intersection in the "FirstTime" context - if (_nextIndex == -1) { - // only get here for the root TraversalWaypoint at the very beginning of traversal - // safe to assume this element intersects view - ++_nextIndex; - next.element = _weakElement.lock(); - return; - } else if (_nextIndex < NUMBER_OF_CHILDREN) { - EntityTreeElementPointer element = _weakElement.lock(); - if (element) { - while (_nextIndex < NUMBER_OF_CHILDREN) { - EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); - ++_nextIndex; - if (nextElement && view.cubeIntersectsKeyhole(nextElement->getAACube())) { - next.element = nextElement; - return; - } - } - } - } - next.element.reset(); -} - -void TraversalWaypoint::getNextVisibleElementAgain(VisibleElement& next, const ViewFrustum& view, uint64_t lastTime) { - // TODO: add LOD culling of elements - if (_nextIndex == -1) { - // only get here for the root TraversalWaypoint at the very beginning of traversal - // safe to assume this element intersects view - ++_nextIndex; - EntityTreeElementPointer element = _weakElement.lock(); - // root case is special: its intersection is always INTERSECT - // and we can skip it if the content hasn't changed - if (element->getLastChangedContent() > lastTime) { - next.element = element; - next.intersection = ViewFrustum::INTERSECT; - return; - } - } - if (_nextIndex < NUMBER_OF_CHILDREN) { - EntityTreeElementPointer element = _weakElement.lock(); - if (element) { - while (_nextIndex < NUMBER_OF_CHILDREN) { - EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); - ++_nextIndex; - if (nextElement && nextElement->getLastChanged() > lastTime) { - ViewFrustum::intersection intersection = view.calculateCubeKeyholeIntersection(nextElement->getAACube()); - if (intersection != ViewFrustum::OUTSIDE) { - next.element = nextElement; - next.intersection = intersection; - return; - } - } - } - } - } - next.element.reset(); - next.intersection = ViewFrustum::OUTSIDE; -} - -void TraversalWaypoint::getNextVisibleElementDifferential(VisibleElement& next, - const ViewFrustum& view, const ViewFrustum& lastView, uint64_t lastTime) { - // TODO: add LOD culling of elements - if (_nextIndex == -1) { - // only get here for the root TraversalWaypoint at the very beginning of traversal - // safe to assume this element intersects view - ++_nextIndex; - EntityTreeElementPointer element = _weakElement.lock(); - // root case is special: its intersection is always INTERSECT - // and we can skip it if the content hasn't changed - if (element->getLastChangedContent() > lastTime) { - next.element = element; - next.intersection = ViewFrustum::INTERSECT; - return; - } - } - if (_nextIndex < NUMBER_OF_CHILDREN) { - EntityTreeElementPointer element = _weakElement.lock(); - if (element) { - while (_nextIndex < NUMBER_OF_CHILDREN) { - EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); - ++_nextIndex; - if (nextElement) { - AACube cube = nextElement->getAACube(); - // NOTE: for differential case next.intersection is against the _completedView - ViewFrustum::intersection intersection = lastView.calculateCubeKeyholeIntersection(cube); - if ( lastView.calculateCubeKeyholeIntersection(cube) != ViewFrustum::OUTSIDE && - !(intersection == ViewFrustum::INSIDE && nextElement->getLastChanged() < lastTime)) { - next.element = nextElement; - next.intersection = intersection; - return; - } - } - } - } - } - next.element.reset(); - next.intersection = ViewFrustum::OUTSIDE; -} diff --git a/assignment-client/src/entities/EntityPriorityQueue.h b/assignment-client/src/entities/EntityPriorityQueue.h index 8eb28dffda..215c5262bf 100644 --- a/assignment-client/src/entities/EntityPriorityQueue.h +++ b/assignment-client/src/entities/EntityPriorityQueue.h @@ -15,8 +15,7 @@ #include #include - -#include "EntityTreeElement.h" +#include const float SQRT_TWO_OVER_TWO = 0.7071067811865f; const float DEFAULT_VIEW_RADIUS = 10.0f; @@ -56,31 +55,6 @@ private: float _priority; }; -// VisibleElement is a struct identifying an element and how it intersected the view. -// The intersection is used to optimize culling entities from the sendQueue. -class VisibleElement { -public: - EntityTreeElementPointer element; - ViewFrustum::intersection intersection { ViewFrustum::OUTSIDE }; -}; - -// TraversalWaypoint is an bookmark in a "path" of waypoints during a traversal. -class TraversalWaypoint { -public: - TraversalWaypoint(EntityTreeElementPointer& element); - - void getNextVisibleElementFirstTime(VisibleElement& next, const ViewFrustum& view); - void getNextVisibleElementAgain(VisibleElement& next, const ViewFrustum& view, uint64_t lastTime); - void getNextVisibleElementDifferential(VisibleElement& next, const ViewFrustum& view, const ViewFrustum& lastView, uint64_t lastTime); - - int8_t getNextIndex() const { return _nextIndex; } - void initRootNextIndex() { _nextIndex = -1; } - -protected: - EntityTreeElementWeakPointer _weakElement; - int8_t _nextIndex; -}; - using EntityPriorityQueue = std::priority_queue< PrioritizedEntity, std::vector, PrioritizedEntity::Compare >; #endif // hifi_EntityPriorityQueue_h diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index 7d6baaca3b..031d7ac3fb 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -10,19 +10,12 @@ // #include "EntityTreeSendThread.h" -#include // adebug #include #include #include "EntityServer.h" -EntityTreeSendThread::EntityTreeSendThread(OctreeServer* myServer, const SharedNodePointer& node) - : OctreeSendThread(myServer, node) { - const int32_t MIN_PATH_DEPTH = 16; - _traversalPath.reserve(MIN_PATH_DEPTH); -} - void EntityTreeSendThread::preDistributionProcessing() { auto node = _node.toStrongRef(); auto nodeData = static_cast(node->getLinkedData()); @@ -90,14 +83,14 @@ void EntityTreeSendThread::preDistributionProcessing() { void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged, bool isFullScene) { // BEGIN EXPERIMENTAL DIFFERENTIAL TRAVERSAL - { - // DEBUG HACK: trigger traversal (Again) every so often - const uint64_t TRAVERSE_AGAIN_PERIOD = 2 * USECS_PER_SECOND; - if (!viewFrustumChanged && usecTimestampNow() > _startOfCompletedTraversal + TRAVERSE_AGAIN_PERIOD) { - viewFrustumChanged = true; - } - } if (nodeData->getUsesFrustum()) { + { + // DEBUG HACK: trigger traversal (Again) every so often + const uint64_t TRAVERSE_AGAIN_PERIOD = 4 * USECS_PER_SECOND; + if (!viewFrustumChanged && usecTimestampNow() > _traversal.getStartOfCompletedTraversal() + TRAVERSE_AGAIN_PERIOD) { + viewFrustumChanged = true; + } + } if (viewFrustumChanged) { ViewFrustum viewFrustum; nodeData->copyCurrentViewFrustum(viewFrustum); @@ -105,28 +98,14 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O startNewTraversal(viewFrustum, root); } } - if (!_traversalPath.empty()) { + if (!_traversal.finished()) { uint64_t startTime = usecTimestampNow(); - uint64_t now = startTime; - VisibleElement next; - getNextVisibleElement(next); - while (next.element) { - if (next.element->hasContent()) { - _scanNextElementCallback(next); - } + const uint64_t TIME_BUDGET = 100000; // usec + _traversal.traverse(TIME_BUDGET); - // TODO: pick a reasonable budget for each partial traversal - const uint64_t PARTIAL_TRAVERSAL_TIME_BUDGET = 100000; // usec - now = usecTimestampNow(); - if (now - startTime > PARTIAL_TRAVERSAL_TIME_BUDGET) { - break; - } - getNextVisibleElement(next); - } - - uint64_t dt = now - startTime; - std::cout << "adebug traversal complete " << " Q.size = " << _sendQueue.size() << " dt = " << dt << std::endl; // adebug + uint64_t dt = usecTimestampNow() - startTime; + std::cout << "adebug traversal complete " << " Q.size = " << _sendQueue.size() << " dt = " << dt << std::endl; // adebug } if (!_sendQueue.empty()) { // print what needs to be sent @@ -134,11 +113,9 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O PrioritizedEntity entry = _sendQueue.top(); EntityItemPointer entity = entry.getEntity(); if (entity) { - std::cout << "adebug '" << entity->getName().toStdString() << "'" - << " : " << entry.getPriority() << std::endl; // adebug + std::cout << "adebug send '" << entity->getName().toStdString() << "'" << " : " << entry.getPriority() << std::endl; // adebug } _sendQueue.pop(); - std::cout << "adebug" << std::endl; // adebug } } // END EXPERIMENTAL DIFFERENTIAL TRAVERSAL @@ -195,39 +172,29 @@ bool EntityTreeSendThread::addDescendantsToExtraFlaggedEntities(const QUuid& fil return hasNewChild || hasNewDescendants; } -void EntityTreeSendThread::startNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root) { +void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTreeElementPointer root) { + DiffTraversal::Type type = _traversal.prepareNewTraversal(view, root); // there are three types of traversal: // // (1) FirstTime = at login --> find everything in view - // (2) Again = view hasn't changed --> find what has changed since last complete traversal + // (2) Repeat = view hasn't changed --> find what has changed since last complete traversal // (3) Differential = view has changed --> find what has changed or in new view but not old // - // For each traversal type we define two callback lambdas: - // - // _getNextVisibleElementCallback = identifies elements that need to be traversed,i - // updates VisibleElement ref argument with pointer-to-element and view-intersection - // (INSIDE, INTERSECT, or OUTSIDE) - // - // _scanNextElementCallback = identifies entities that need to be appended to _sendQueue + // The "scanCallback" we provide to the traversal depends on the type: // // The _conicalView is updated here as a cached view approximation used by the lambdas for efficient // computation of entity sorting priorities. // - if (_startOfCompletedTraversal == 0) { - // first time - _currentView = viewFrustum; - _conicalView.set(_currentView); + _conicalView.set(_traversal.getCurrentView()); - _getNextVisibleElementCallback = [&](VisibleElement& next) { - _traversalPath.back().getNextVisibleElementFirstTime(next, _currentView); - }; - - _scanNextElementCallback = [&](VisibleElement& next) { + switch (type) { + case DiffTraversal::First: + _traversal.setScanCallback([&] (DiffTraversal::VisibleElement& next) { next.element->forEachEntity([&](EntityItemPointer entity) { bool success = false; AACube cube = entity->getQueryAACube(success); if (success) { - if (_currentView.cubeIntersectsKeyhole(cube)) { + if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { float priority = _conicalView.computePriority(cube); _sendQueue.push(PrioritizedEntity(entity, priority)); } @@ -236,21 +203,18 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& viewFrustum, Ent _sendQueue.push(PrioritizedEntity(entity, WHEN_IN_DOUBT_PRIORITY)); } }); - }; - } else if (_currentView.isVerySimilar(viewFrustum)) { - // again - _getNextVisibleElementCallback = [&](VisibleElement& next) { - _traversalPath.back().getNextVisibleElementAgain(next, _currentView, _startOfCompletedTraversal); - }; - - _scanNextElementCallback = [&](VisibleElement& next) { - if (next.element->getLastChangedContent() > _startOfCompletedTraversal) { + }); + break; + case DiffTraversal::Repeat: + _traversal.setScanCallback([&] (DiffTraversal::VisibleElement& next) { + if (next.element->getLastChangedContent() > _traversal.getStartOfCompletedTraversal()) { + uint64_t timestamp = _traversal.getStartOfCompletedTraversal(); next.element->forEachEntity([&](EntityItemPointer entity) { - if (entity->getLastEdited() > _startOfCompletedTraversal) { + if (entity->getLastEdited() > timestamp) { bool success = false; AACube cube = entity->getQueryAACube(success); if (success) { - if (next.intersection == ViewFrustum::INSIDE || _currentView.cubeIntersectsKeyhole(cube)) { + if (next.intersection == ViewFrustum::INSIDE || _traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { float priority = _conicalView.computePriority(cube); _sendQueue.push(PrioritizedEntity(entity, priority)); } @@ -261,26 +225,20 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& viewFrustum, Ent } }); } - }; - } else { - // differential - _currentView = viewFrustum; - _conicalView.set(_currentView); - - _getNextVisibleElementCallback = [&](VisibleElement& next) { - _traversalPath.back().getNextVisibleElementDifferential(next, _currentView, _completedView, _startOfCompletedTraversal); - }; - - _scanNextElementCallback = [&](VisibleElement& next) { - // NOTE: for differential case next.intersection is against _completedView not _currentView - if (next.element->getLastChangedContent() > _startOfCompletedTraversal || next.intersection != ViewFrustum::INSIDE) { + }); + break; + case DiffTraversal::Differential: + _traversal.setScanCallback([&] (DiffTraversal::VisibleElement& next) { + // NOTE: for Differential case: next.intersection is against completedView not currentView + uint64_t timestamp = _traversal.getStartOfCompletedTraversal(); + if (next.element->getLastChangedContent() > timestamp || next.intersection != ViewFrustum::INSIDE) { next.element->forEachEntity([&](EntityItemPointer entity) { bool success = false; AACube cube = entity->getQueryAACube(success); if (success) { - if (_currentView.cubeIntersectsKeyhole(cube) && - (entity->getLastEdited() > _startOfCompletedTraversal || - !_completedView.cubeIntersectsKeyhole(cube))) { + if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube) && + (entity->getLastEdited() > timestamp || + !_traversal.getCompletedView().cubeIntersectsKeyhole(cube))) { float priority = _conicalView.computePriority(cube); _sendQueue.push(PrioritizedEntity(entity, priority)); } @@ -290,55 +248,8 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& viewFrustum, Ent } }); } - }; - } - - _traversalPath.clear(); - assert(root); - _traversalPath.push_back(TraversalWaypoint(root)); - // set root fork's index such that root element returned at getNextElement() - _traversalPath.back().initRootNextIndex(); - - _startOfCurrentTraversal = usecTimestampNow(); -} - -void EntityTreeSendThread::getNextVisibleElement(VisibleElement& next) { - if (_traversalPath.empty()) { - next.element.reset(); - next.intersection = ViewFrustum::OUTSIDE; - return; - } - _getNextVisibleElementCallback(next); - if (next.element) { - int8_t nextIndex = _traversalPath.back().getNextIndex(); - if (nextIndex > 0) { - // next.element needs to be added to the path - _traversalPath.push_back(TraversalWaypoint(next.element)); - } - } else { - // we're done at this level - while (!next.element) { - // pop one level - _traversalPath.pop_back(); - if (_traversalPath.empty()) { - // we've traversed the entire tree - _completedView = _currentView; - _startOfCompletedTraversal = _startOfCurrentTraversal; - return; - } - // keep looking for next - _getNextVisibleElementCallback(next); - if (next.element) { - // we've descended one level so add it to the path - _traversalPath.push_back(TraversalWaypoint(next.element)); - } - } + }); + break; } } -// DEBUG method: delete later -void EntityTreeSendThread::dump() const { - for (size_t i = 0; i < _traversalPath.size(); ++i) { - std::cout << (int32_t)(_traversalPath[i].getNextIndex()) << "-->"; - } -} diff --git a/assignment-client/src/entities/EntityTreeSendThread.h b/assignment-client/src/entities/EntityTreeSendThread.h index 9a1a57b41c..5cb2c4c76d 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.h +++ b/assignment-client/src/entities/EntityTreeSendThread.h @@ -14,6 +14,8 @@ #include "../octree/OctreeSendThread.h" +#include + #include "EntityPriorityQueue.h" class EntityNodeData; @@ -22,7 +24,7 @@ class EntityItem; class EntityTreeSendThread : public OctreeSendThread { public: - EntityTreeSendThread(OctreeServer* myServer, const SharedNodePointer& node); + EntityTreeSendThread(OctreeServer* myServer, const SharedNodePointer& node) : OctreeSendThread(myServer, node) { } protected: void preDistributionProcessing() override; @@ -35,18 +37,10 @@ private: bool addDescendantsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData); void startNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root); - void getNextVisibleElement(VisibleElement& element); - void dump() const; // DEBUG method, delete later + DiffTraversal _traversal; EntityPriorityQueue _sendQueue; - ViewFrustum _currentView; - ViewFrustum _completedView; - ConicalView _conicalView; // optimized view for fast priority calculations - std::vector _traversalPath; - std::function _getNextVisibleElementCallback { nullptr }; - std::function _scanNextElementCallback { nullptr }; - uint64_t _startOfCompletedTraversal { 0 }; - uint64_t _startOfCurrentTraversal { 0 }; + ConicalView _conicalView; // cached optimized view for fast priority calculations }; #endif // hifi_EntityTreeSendThread_h diff --git a/libraries/entities/src/DiffTraversal.cpp b/libraries/entities/src/DiffTraversal.cpp new file mode 100644 index 0000000000..b1fddcf5fb --- /dev/null +++ b/libraries/entities/src/DiffTraversal.cpp @@ -0,0 +1,239 @@ +// +// DiffTraversal.cpp +// assignment-client/src/entities +// +// Created by Andrew Meadows 2017.08.08 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "DiffTraversal.h" + +DiffTraversal::Waypoint::Waypoint(EntityTreeElementPointer& element) : _nextIndex(0) { + assert(element); + _weakElement = element; +} + +void DiffTraversal::Waypoint::getNextVisibleElementFirstTime(DiffTraversal::VisibleElement& next, const ViewFrustum& view) { + // TODO: add LOD culling of elements + // NOTE: no need to set next.intersection in the "FirstTime" context + if (_nextIndex == -1) { + // only get here for the root Waypoint at the very beginning of traversal + // safe to assume this element intersects view + ++_nextIndex; + next.element = _weakElement.lock(); + return; + } else if (_nextIndex < NUMBER_OF_CHILDREN) { + EntityTreeElementPointer element = _weakElement.lock(); + if (element) { + while (_nextIndex < NUMBER_OF_CHILDREN) { + EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); + ++_nextIndex; + if (nextElement && view.cubeIntersectsKeyhole(nextElement->getAACube())) { + next.element = nextElement; + return; + } + } + } + } + next.element.reset(); +} + +void DiffTraversal::Waypoint::getNextVisibleElementRepeat(DiffTraversal::VisibleElement& next, const ViewFrustum& view, uint64_t lastTime) { + // TODO: add LOD culling of elements + if (_nextIndex == -1) { + // only get here for the root Waypoint at the very beginning of traversal + // safe to assume this element intersects view + ++_nextIndex; + EntityTreeElementPointer element = _weakElement.lock(); + // root case is special: its intersection is always INTERSECT + // and we can skip it if the content hasn't changed + if (element->getLastChangedContent() > lastTime) { + next.element = element; + next.intersection = ViewFrustum::INTERSECT; + return; + } + } + if (_nextIndex < NUMBER_OF_CHILDREN) { + EntityTreeElementPointer element = _weakElement.lock(); + if (element) { + while (_nextIndex < NUMBER_OF_CHILDREN) { + EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); + ++_nextIndex; + if (nextElement && nextElement->getLastChanged() > lastTime) { + ViewFrustum::intersection intersection = view.calculateCubeKeyholeIntersection(nextElement->getAACube()); + if (intersection != ViewFrustum::OUTSIDE) { + next.element = nextElement; + next.intersection = intersection; + return; + } + } + } + } + } + next.element.reset(); + next.intersection = ViewFrustum::OUTSIDE; +} + +DiffTraversal::DiffTraversal() { + const int32_t MIN_PATH_DEPTH = 16; + _path.reserve(MIN_PATH_DEPTH); +} + +void DiffTraversal::Waypoint::getNextVisibleElementDifferential(DiffTraversal::VisibleElement& next, + const ViewFrustum& view, const ViewFrustum& lastView, uint64_t lastTime) { + // TODO: add LOD culling of elements + if (_nextIndex == -1) { + // only get here for the root Waypoint at the very beginning of traversal + // safe to assume this element intersects view + ++_nextIndex; + EntityTreeElementPointer element = _weakElement.lock(); + // root case is special: its intersection is always INTERSECT + // and we can skip it if the content hasn't changed + if (element->getLastChangedContent() > lastTime) { + next.element = element; + next.intersection = ViewFrustum::INTERSECT; + return; + } + } + if (_nextIndex < NUMBER_OF_CHILDREN) { + EntityTreeElementPointer element = _weakElement.lock(); + if (element) { + while (_nextIndex < NUMBER_OF_CHILDREN) { + EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); + ++_nextIndex; + if (nextElement) { + AACube cube = nextElement->getAACube(); + // NOTE: for differential case next.intersection is against the _completedView + ViewFrustum::intersection intersection = lastView.calculateCubeKeyholeIntersection(cube); + if ( lastView.calculateCubeKeyholeIntersection(cube) != ViewFrustum::OUTSIDE && + !(intersection == ViewFrustum::INSIDE && nextElement->getLastChanged() < lastTime)) { + next.element = nextElement; + next.intersection = intersection; + return; + } + } + } + } + } + next.element.reset(); + next.intersection = ViewFrustum::OUTSIDE; +} + +DiffTraversal::Type DiffTraversal::prepareNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root) { + // there are three types of traversal: + // + // (1) First = fresh view --> find all elements in view + // (2) Repeat = view hasn't changed --> find elements changed since last complete traversal + // (3) Differential = view has changed --> find elements changed or in new view but not old + // + // For each traversal type we define assign the appropriate _getNextVisibleElementCallback + // + // _getNextVisibleElementCallback = identifies elements that need to be traversed, + // updates VisibleElement ref argument with pointer-to-element and view-intersection + // (INSIDE, INTERSECT, or OUTSIDE) + // + // External code should update the _scanElementCallback after calling prepareNewTraversal + // + + Type type = Type::First; + if (_startOfCompletedTraversal == 0) { + // first time + _currentView = viewFrustum; + _getNextVisibleElementCallback = [&](DiffTraversal::VisibleElement& next) { + _path.back().getNextVisibleElementFirstTime(next, _currentView); + }; + } else if (_currentView.isVerySimilar(viewFrustum)) { + // again + _getNextVisibleElementCallback = [&](DiffTraversal::VisibleElement& next) { + _path.back().getNextVisibleElementRepeat(next, _currentView, _startOfCompletedTraversal); + }; + type = Type::Repeat; + } else { + // differential + _currentView = viewFrustum; + _getNextVisibleElementCallback = [&](DiffTraversal::VisibleElement& next) { + _path.back().getNextVisibleElementDifferential(next, _currentView, _completedView, _startOfCompletedTraversal); + }; + type = Type::Differential; + } + + assert(root); + _path.clear(); + _path.push_back(DiffTraversal::Waypoint(root)); + // set root fork's index such that root element returned at getNextElement() + _path.back().initRootNextIndex(); + _startOfCurrentTraversal = usecTimestampNow(); + + return type; +} + +void DiffTraversal::getNextVisibleElement(DiffTraversal::VisibleElement& next) { + if (_path.empty()) { + next.element.reset(); + next.intersection = ViewFrustum::OUTSIDE; + return; + } + _getNextVisibleElementCallback(next); + if (next.element) { + int8_t nextIndex = _path.back().getNextIndex(); + if (nextIndex > 0) { + // next.element needs to be added to the path + _path.push_back(DiffTraversal::Waypoint(next.element)); + } + } else { + // we're done at this level + while (!next.element) { + // pop one level + _path.pop_back(); + if (_path.empty()) { + // we've traversed the entire tree + _completedView = _currentView; + _startOfCompletedTraversal = _startOfCurrentTraversal; + return; + } + // keep looking for next + _getNextVisibleElementCallback(next); + if (next.element) { + // we've descended one level so add it to the path + _path.push_back(DiffTraversal::Waypoint(next.element)); + } + } + } +} + +void DiffTraversal::setScanCallback(std::function cb) { + if (!cb) { + _scanElementCallback = [](DiffTraversal::VisibleElement& a){}; + } else { + _scanElementCallback = cb; + } +} + +// DEBUG method: delete later +std::ostream& operator<<(std::ostream& s, const DiffTraversal& traversal) { + for (size_t i = 0; i < traversal._path.size(); ++i) { + s << (int32_t)(traversal._path[i].getNextIndex()); + if (i < traversal._path.size() - 1) { + s << "-->"; + } + } + return s; +} + +void DiffTraversal::traverse(uint64_t timeBudget) { + uint64_t expiry = usecTimestampNow() + timeBudget; + DiffTraversal::VisibleElement next; + getNextVisibleElement(next); + while (next.element) { + if (next.element->hasContent()) { + _scanElementCallback(next); + } + if (usecTimestampNow() > expiry) { + break; + } + getNextVisibleElement(next); + } +} diff --git a/libraries/entities/src/DiffTraversal.h b/libraries/entities/src/DiffTraversal.h new file mode 100644 index 0000000000..508d1e9c6b --- /dev/null +++ b/libraries/entities/src/DiffTraversal.h @@ -0,0 +1,78 @@ +// +// DiffTraversal.h +// assignment-client/src/entities +// +// Created by Andrew Meadows 2017.08.08 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_DiffTraversal_h +#define hifi_DiffTraversal_h + +#include // DEBUG + +#include + +#include "EntityTreeElement.h" + +// DiffTraversal traverses the tree and applies _scanElementCallback on elements it finds +class DiffTraversal { +public: + // VisibleElement is a struct identifying an element and how it intersected the view. + // The intersection is used to optimize culling entities from the sendQueue. + class VisibleElement { + public: + EntityTreeElementPointer element; + ViewFrustum::intersection intersection { ViewFrustum::OUTSIDE }; + }; + + // Waypoint is an bookmark in a "path" of waypoints during a traversal. + class Waypoint { + public: + Waypoint(EntityTreeElementPointer& element); + + void getNextVisibleElementFirstTime(VisibleElement& next, const ViewFrustum& view); + void getNextVisibleElementRepeat(VisibleElement& next, const ViewFrustum& view, uint64_t lastTime); + void getNextVisibleElementDifferential(VisibleElement& next, const ViewFrustum& view, const ViewFrustum& lastView, uint64_t lastTime); + + int8_t getNextIndex() const { return _nextIndex; } + void initRootNextIndex() { _nextIndex = -1; } + + protected: + EntityTreeElementWeakPointer _weakElement; + int8_t _nextIndex; + }; + + typedef enum { First, Repeat, Differential } Type; + + DiffTraversal(); + + DiffTraversal::Type prepareNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root); + + const ViewFrustum& getCurrentView() const { return _currentView; } + const ViewFrustum& getCompletedView() const { return _completedView; } + + uint64_t getStartOfCompletedTraversal() const { return _startOfCompletedTraversal; } + bool finished() const { return _path.empty(); } + + void setScanCallback(std::function cb); + void traverse(uint64_t timeBudget); + + friend std::ostream& operator<<(std::ostream& s, const DiffTraversal& traversal); // DEBUG + +private: + void getNextVisibleElement(VisibleElement& next); + + ViewFrustum _currentView; + ViewFrustum _completedView; + std::vector _path; + std::function _getNextVisibleElementCallback { nullptr }; + std::function _scanElementCallback { [](VisibleElement& e){} }; + uint64_t _startOfCompletedTraversal { 0 }; + uint64_t _startOfCurrentTraversal { 0 }; +}; + +#endif // hifi_EntityPriorityQueue_h From 8b7c43f3b1c79c85bbb98d3259eea495eba07eb0 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 9 Aug 2017 17:22:47 -0700 Subject: [PATCH 30/88] add LOD culling in DiffTraversal --- libraries/entities/src/DiffTraversal.cpp | 143 ++++++++++++++--------- libraries/entities/src/DiffTraversal.h | 26 +++-- libraries/octree/src/OctreeConstants.h | 3 +- 3 files changed, 109 insertions(+), 63 deletions(-) diff --git a/libraries/entities/src/DiffTraversal.cpp b/libraries/entities/src/DiffTraversal.cpp index b1fddcf5fb..fcaf2f06ee 100644 --- a/libraries/entities/src/DiffTraversal.cpp +++ b/libraries/entities/src/DiffTraversal.cpp @@ -11,29 +11,39 @@ #include "DiffTraversal.h" +#include + + DiffTraversal::Waypoint::Waypoint(EntityTreeElementPointer& element) : _nextIndex(0) { assert(element); _weakElement = element; } -void DiffTraversal::Waypoint::getNextVisibleElementFirstTime(DiffTraversal::VisibleElement& next, const ViewFrustum& view) { - // TODO: add LOD culling of elements +void DiffTraversal::Waypoint::getNextVisibleElementFirstTime(DiffTraversal::VisibleElement& next, + const DiffTraversal::View& view) { // NOTE: no need to set next.intersection in the "FirstTime" context if (_nextIndex == -1) { - // only get here for the root Waypoint at the very beginning of traversal - // safe to assume this element intersects view + // root case is special: + // its intersection is always INTERSECT, + // we never bother checking for LOD culling, and + // we can skip it if the content hasn't changed ++_nextIndex; next.element = _weakElement.lock(); return; } else if (_nextIndex < NUMBER_OF_CHILDREN) { EntityTreeElementPointer element = _weakElement.lock(); if (element) { - while (_nextIndex < NUMBER_OF_CHILDREN) { - EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); - ++_nextIndex; - if (nextElement && view.cubeIntersectsKeyhole(nextElement->getAACube())) { - next.element = nextElement; - return; + // check for LOD truncation + float visibleLimit = boundaryDistanceForRenderLevel(element->getLevel() + view.rootLevel, view.rootSizeScale); + float distance2 = glm::distance2(view.viewFrustum.getPosition(), element->getAACube().calcCenter()); + if (distance2 < visibleLimit * visibleLimit) { + while (_nextIndex < NUMBER_OF_CHILDREN) { + EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); + ++_nextIndex; + if (nextElement && view.viewFrustum.cubeIntersectsKeyhole(nextElement->getAACube())) { + next.element = nextElement; + return; + } } } } @@ -41,15 +51,12 @@ void DiffTraversal::Waypoint::getNextVisibleElementFirstTime(DiffTraversal::Visi next.element.reset(); } -void DiffTraversal::Waypoint::getNextVisibleElementRepeat(DiffTraversal::VisibleElement& next, const ViewFrustum& view, uint64_t lastTime) { - // TODO: add LOD culling of elements +void DiffTraversal::Waypoint::getNextVisibleElementRepeat( + DiffTraversal::VisibleElement& next, const DiffTraversal::View& view, uint64_t lastTime) { if (_nextIndex == -1) { - // only get here for the root Waypoint at the very beginning of traversal - // safe to assume this element intersects view + // root case is special ++_nextIndex; EntityTreeElementPointer element = _weakElement.lock(); - // root case is special: its intersection is always INTERSECT - // and we can skip it if the content hasn't changed if (element->getLastChangedContent() > lastTime) { next.element = element; next.intersection = ViewFrustum::INTERSECT; @@ -59,15 +66,20 @@ void DiffTraversal::Waypoint::getNextVisibleElementRepeat(DiffTraversal::Visible if (_nextIndex < NUMBER_OF_CHILDREN) { EntityTreeElementPointer element = _weakElement.lock(); if (element) { - while (_nextIndex < NUMBER_OF_CHILDREN) { - EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); - ++_nextIndex; - if (nextElement && nextElement->getLastChanged() > lastTime) { - ViewFrustum::intersection intersection = view.calculateCubeKeyholeIntersection(nextElement->getAACube()); - if (intersection != ViewFrustum::OUTSIDE) { - next.element = nextElement; - next.intersection = intersection; - return; + // check for LOD truncation + float visibleLimit = boundaryDistanceForRenderLevel(element->getLevel() - view.rootLevel + 1, view.rootSizeScale); + float distance2 = glm::distance2(view.viewFrustum.getPosition(), element->getAACube().calcCenter()); + if (distance2 < visibleLimit * visibleLimit) { + while (_nextIndex < NUMBER_OF_CHILDREN) { + EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); + ++_nextIndex; + if (nextElement && nextElement->getLastChanged() > lastTime) { + ViewFrustum::intersection intersection = view.viewFrustum.calculateCubeKeyholeIntersection(nextElement->getAACube()); + if (intersection != ViewFrustum::OUTSIDE) { + next.element = nextElement; + next.intersection = intersection; + return; + } } } } @@ -83,15 +95,11 @@ DiffTraversal::DiffTraversal() { } void DiffTraversal::Waypoint::getNextVisibleElementDifferential(DiffTraversal::VisibleElement& next, - const ViewFrustum& view, const ViewFrustum& lastView, uint64_t lastTime) { - // TODO: add LOD culling of elements + const DiffTraversal::View& view, const DiffTraversal::View& lastView, uint64_t lastTime) { if (_nextIndex == -1) { - // only get here for the root Waypoint at the very beginning of traversal - // safe to assume this element intersects view + // root case is special ++_nextIndex; EntityTreeElementPointer element = _weakElement.lock(); - // root case is special: its intersection is always INTERSECT - // and we can skip it if the content hasn't changed if (element->getLastChangedContent() > lastTime) { next.element = element; next.intersection = ViewFrustum::INTERSECT; @@ -101,18 +109,39 @@ void DiffTraversal::Waypoint::getNextVisibleElementDifferential(DiffTraversal::V if (_nextIndex < NUMBER_OF_CHILDREN) { EntityTreeElementPointer element = _weakElement.lock(); if (element) { - while (_nextIndex < NUMBER_OF_CHILDREN) { - EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); - ++_nextIndex; - if (nextElement) { - AACube cube = nextElement->getAACube(); - // NOTE: for differential case next.intersection is against the _completedView - ViewFrustum::intersection intersection = lastView.calculateCubeKeyholeIntersection(cube); - if ( lastView.calculateCubeKeyholeIntersection(cube) != ViewFrustum::OUTSIDE && - !(intersection == ViewFrustum::INSIDE && nextElement->getLastChanged() < lastTime)) { - next.element = nextElement; - next.intersection = intersection; - return; + // check for LOD truncation + uint32_t level = element->getLevel() - view.rootLevel + 1; + float visibleLimit = boundaryDistanceForRenderLevel(level, view.rootSizeScale); + float distance2 = glm::distance2(view.viewFrustum.getPosition(), element->getAACube().calcCenter()); + if (distance2 < visibleLimit * visibleLimit) { + while (_nextIndex < NUMBER_OF_CHILDREN) { + EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); + ++_nextIndex; + if (nextElement) { + AACube cube = nextElement->getAACube(); + if ( view.viewFrustum.calculateCubeKeyholeIntersection(cube) != ViewFrustum::OUTSIDE) { + ViewFrustum::intersection lastIntersection = lastView.viewFrustum.calculateCubeKeyholeIntersection(cube); + if (!(lastIntersection == ViewFrustum::INSIDE && nextElement->getLastChanged() < lastTime)) { + next.element = nextElement; + // NOTE: for differential case next.intersection is against the lastView + // because this helps the "external scan" optimize its culling + next.intersection = lastIntersection; + return; + } else { + // check for LOD truncation in the last traversal because + // we may need to traverse this element after all if the lastView skipped it for LOD + // + // NOTE: the element's "level" must be invariant (the differntial algorithm doesn't work otherwise) + // so we recycle the value computed higher up + visibleLimit = boundaryDistanceForRenderLevel(level, lastView.rootSizeScale); + distance2 = glm::distance2(lastView.viewFrustum.getPosition(), element->getAACube().calcCenter()); + if (distance2 >= visibleLimit * visibleLimit) { + next.element = nextElement; + next.intersection = lastIntersection; + return; + } + } + } } } } @@ -125,27 +154,27 @@ void DiffTraversal::Waypoint::getNextVisibleElementDifferential(DiffTraversal::V DiffTraversal::Type DiffTraversal::prepareNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root) { // there are three types of traversal: // - // (1) First = fresh view --> find all elements in view - // (2) Repeat = view hasn't changed --> find elements changed since last complete traversal - // (3) Differential = view has changed --> find elements changed or in new view but not old + // (1) First = fresh view --> find all elements in view + // (2) Repeat = view hasn't changed --> find elements changed since last complete traversal + // (3) Differential = view has changed --> find elements changed or in new view but not old // - // For each traversal type we define assign the appropriate _getNextVisibleElementCallback + // for each traversal type we assign the appropriate _getNextVisibleElementCallback // - // _getNextVisibleElementCallback = identifies elements that need to be traversed, - // updates VisibleElement ref argument with pointer-to-element and view-intersection - // (INSIDE, INTERSECT, or OUTSIDE) + // _getNextVisibleElementCallback = identifies elements that need to be traversed, + // updates VisibleElement ref argument with pointer-to-element and view-intersection + // (INSIDE, INTERSECT, or OUTSIDE) // - // External code should update the _scanElementCallback after calling prepareNewTraversal + // external code should update the _scanElementCallback after calling prepareNewTraversal // Type type = Type::First; if (_startOfCompletedTraversal == 0) { // first time - _currentView = viewFrustum; + _currentView.viewFrustum = viewFrustum; _getNextVisibleElementCallback = [&](DiffTraversal::VisibleElement& next) { _path.back().getNextVisibleElementFirstTime(next, _currentView); }; - } else if (_currentView.isVerySimilar(viewFrustum)) { + } else if (_currentView.viewFrustum.isVerySimilar(viewFrustum)) { // again _getNextVisibleElementCallback = [&](DiffTraversal::VisibleElement& next) { _path.back().getNextVisibleElementRepeat(next, _currentView, _startOfCompletedTraversal); @@ -153,7 +182,7 @@ DiffTraversal::Type DiffTraversal::prepareNewTraversal(const ViewFrustum& viewFr type = Type::Repeat; } else { // differential - _currentView = viewFrustum; + _currentView.viewFrustum = viewFrustum; _getNextVisibleElementCallback = [&](DiffTraversal::VisibleElement& next) { _path.back().getNextVisibleElementDifferential(next, _currentView, _completedView, _startOfCompletedTraversal); }; @@ -165,6 +194,11 @@ DiffTraversal::Type DiffTraversal::prepareNewTraversal(const ViewFrustum& viewFr _path.push_back(DiffTraversal::Waypoint(root)); // set root fork's index such that root element returned at getNextElement() _path.back().initRootNextIndex(); + + // cache LOD parameters in the _currentView + _currentView.rootLevel = root->getLevel(); + _currentView.rootSizeScale = root->getScale() * MAX_VISIBILITY_DISTANCE_FOR_UNIT_ELEMENT; + _startOfCurrentTraversal = usecTimestampNow(); return type; @@ -180,7 +214,6 @@ void DiffTraversal::getNextVisibleElement(DiffTraversal::VisibleElement& next) { if (next.element) { int8_t nextIndex = _path.back().getNextIndex(); if (nextIndex > 0) { - // next.element needs to be added to the path _path.push_back(DiffTraversal::Waypoint(next.element)); } } else { diff --git a/libraries/entities/src/DiffTraversal.h b/libraries/entities/src/DiffTraversal.h index 508d1e9c6b..53361780fc 100644 --- a/libraries/entities/src/DiffTraversal.h +++ b/libraries/entities/src/DiffTraversal.h @@ -29,14 +29,22 @@ public: ViewFrustum::intersection intersection { ViewFrustum::OUTSIDE }; }; + // View is a struct with a ViewFrustum and LOD parameters + class View { + public: + ViewFrustum viewFrustum; + float rootSizeScale { 1.0f }; + float rootLevel { 0.0f }; + }; + // Waypoint is an bookmark in a "path" of waypoints during a traversal. class Waypoint { public: Waypoint(EntityTreeElementPointer& element); - void getNextVisibleElementFirstTime(VisibleElement& next, const ViewFrustum& view); - void getNextVisibleElementRepeat(VisibleElement& next, const ViewFrustum& view, uint64_t lastTime); - void getNextVisibleElementDifferential(VisibleElement& next, const ViewFrustum& view, const ViewFrustum& lastView, uint64_t lastTime); + void getNextVisibleElementFirstTime(VisibleElement& next, const View& view); + void getNextVisibleElementRepeat(VisibleElement& next, const View& view, uint64_t lastTime); + void getNextVisibleElementDifferential(VisibleElement& next, const View& view, const View& lastView, uint64_t lastTime); int8_t getNextIndex() const { return _nextIndex; } void initRootNextIndex() { _nextIndex = -1; } @@ -52,8 +60,8 @@ public: DiffTraversal::Type prepareNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root); - const ViewFrustum& getCurrentView() const { return _currentView; } - const ViewFrustum& getCompletedView() const { return _completedView; } + const ViewFrustum& getCurrentView() const { return _currentView.viewFrustum; } + const ViewFrustum& getCompletedView() const { return _completedView.viewFrustum; } uint64_t getStartOfCompletedTraversal() const { return _startOfCompletedTraversal; } bool finished() const { return _path.empty(); } @@ -66,13 +74,17 @@ public: private: void getNextVisibleElement(VisibleElement& next); - ViewFrustum _currentView; - ViewFrustum _completedView; + View _currentView; + View _completedView; std::vector _path; std::function _getNextVisibleElementCallback { nullptr }; std::function _scanElementCallback { [](VisibleElement& e){} }; uint64_t _startOfCompletedTraversal { 0 }; uint64_t _startOfCurrentTraversal { 0 }; + + // LOD stuff + float _rootSizeScale { 1.0f }; + uint32_t _rootLevel { 0 }; }; #endif // hifi_EntityPriorityQueue_h diff --git a/libraries/octree/src/OctreeConstants.h b/libraries/octree/src/OctreeConstants.h index 53442f52d6..06f09e557c 100644 --- a/libraries/octree/src/OctreeConstants.h +++ b/libraries/octree/src/OctreeConstants.h @@ -21,7 +21,8 @@ const int TREE_SCALE = 32768; // ~20 miles.. This is the number of meters of the const int HALF_TREE_SCALE = TREE_SCALE / 2; // This controls the LOD. Larger number will make smaller voxels visible at greater distance. -const float DEFAULT_OCTREE_SIZE_SCALE = TREE_SCALE * 400.0f; +const float MAX_VISIBILITY_DISTANCE_FOR_UNIT_ELEMENT = 400.0f; // max distance where a 1x1x1 cube is visible for 20:20 vision +const float DEFAULT_OCTREE_SIZE_SCALE = TREE_SCALE * MAX_VISIBILITY_DISTANCE_FOR_UNIT_ELEMENT; // Since entities like models live inside of octree cells, and they themselves can have very small mesh parts, // we want to have some constant that controls have big a mesh part must be to render even if the octree cell itself From 3e50d01734336f3c4b564848dddaae04c1f2266c Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 10 Aug 2017 09:13:50 -0700 Subject: [PATCH 31/88] more correct handling of LOD --- .../src/entities/EntityTreeSendThread.cpp | 7 ++- .../src/entities/EntityTreeSendThread.h | 2 +- libraries/entities/src/DiffTraversal.cpp | 56 +++++++++---------- libraries/entities/src/DiffTraversal.h | 15 ++--- 4 files changed, 36 insertions(+), 44 deletions(-) diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index 031d7ac3fb..48cd8ea200 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -95,7 +95,8 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O ViewFrustum viewFrustum; nodeData->copyCurrentViewFrustum(viewFrustum); EntityTreeElementPointer root = std::dynamic_pointer_cast(_myServer->getOctree()->getRoot()); - startNewTraversal(viewFrustum, root); + int32_t lodLevelOffset = nodeData->getBoundaryLevelAdjust() + (viewFrustumChanged ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST); + startNewTraversal(viewFrustum, root, lodLevelOffset); } } if (!_traversal.finished()) { @@ -172,8 +173,8 @@ bool EntityTreeSendThread::addDescendantsToExtraFlaggedEntities(const QUuid& fil return hasNewChild || hasNewDescendants; } -void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTreeElementPointer root) { - DiffTraversal::Type type = _traversal.prepareNewTraversal(view, root); +void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTreeElementPointer root, int32_t lodLevelOffset) { + DiffTraversal::Type type = _traversal.prepareNewTraversal(view, root, lodLevelOffset); // there are three types of traversal: // // (1) FirstTime = at login --> find everything in view diff --git a/assignment-client/src/entities/EntityTreeSendThread.h b/assignment-client/src/entities/EntityTreeSendThread.h index 5cb2c4c76d..33c22c8c4a 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.h +++ b/assignment-client/src/entities/EntityTreeSendThread.h @@ -36,7 +36,7 @@ private: bool addAncestorsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData); bool addDescendantsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData); - void startNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root); + void startNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset); DiffTraversal _traversal; EntityPriorityQueue _sendQueue; diff --git a/libraries/entities/src/DiffTraversal.cpp b/libraries/entities/src/DiffTraversal.cpp index fcaf2f06ee..5f105f3fb5 100644 --- a/libraries/entities/src/DiffTraversal.cpp +++ b/libraries/entities/src/DiffTraversal.cpp @@ -34,7 +34,7 @@ void DiffTraversal::Waypoint::getNextVisibleElementFirstTime(DiffTraversal::Visi EntityTreeElementPointer element = _weakElement.lock(); if (element) { // check for LOD truncation - float visibleLimit = boundaryDistanceForRenderLevel(element->getLevel() + view.rootLevel, view.rootSizeScale); + float visibleLimit = boundaryDistanceForRenderLevel(element->getLevel() + view.lodLevelOffset, view.rootSizeScale); float distance2 = glm::distance2(view.viewFrustum.getPosition(), element->getAACube().calcCenter()); if (distance2 < visibleLimit * visibleLimit) { while (_nextIndex < NUMBER_OF_CHILDREN) { @@ -67,7 +67,7 @@ void DiffTraversal::Waypoint::getNextVisibleElementRepeat( EntityTreeElementPointer element = _weakElement.lock(); if (element) { // check for LOD truncation - float visibleLimit = boundaryDistanceForRenderLevel(element->getLevel() - view.rootLevel + 1, view.rootSizeScale); + float visibleLimit = boundaryDistanceForRenderLevel(element->getLevel() + view.lodLevelOffset, view.rootSizeScale); float distance2 = glm::distance2(view.viewFrustum.getPosition(), element->getAACube().calcCenter()); if (distance2 < visibleLimit * visibleLimit) { while (_nextIndex < NUMBER_OF_CHILDREN) { @@ -95,12 +95,12 @@ DiffTraversal::DiffTraversal() { } void DiffTraversal::Waypoint::getNextVisibleElementDifferential(DiffTraversal::VisibleElement& next, - const DiffTraversal::View& view, const DiffTraversal::View& lastView, uint64_t lastTime) { + const DiffTraversal::View& view, const DiffTraversal::View& lastView) { if (_nextIndex == -1) { // root case is special ++_nextIndex; EntityTreeElementPointer element = _weakElement.lock(); - if (element->getLastChangedContent() > lastTime) { + if (element->getLastChangedContent() > lastView.startTime) { next.element = element; next.intersection = ViewFrustum::INTERSECT; return; @@ -110,7 +110,7 @@ void DiffTraversal::Waypoint::getNextVisibleElementDifferential(DiffTraversal::V EntityTreeElementPointer element = _weakElement.lock(); if (element) { // check for LOD truncation - uint32_t level = element->getLevel() - view.rootLevel + 1; + int32_t level = element->getLevel() + view.lodLevelOffset; float visibleLimit = boundaryDistanceForRenderLevel(level, view.rootSizeScale); float distance2 = glm::distance2(view.viewFrustum.getPosition(), element->getAACube().calcCenter()); if (distance2 < visibleLimit * visibleLimit) { @@ -121,7 +121,7 @@ void DiffTraversal::Waypoint::getNextVisibleElementDifferential(DiffTraversal::V AACube cube = nextElement->getAACube(); if ( view.viewFrustum.calculateCubeKeyholeIntersection(cube) != ViewFrustum::OUTSIDE) { ViewFrustum::intersection lastIntersection = lastView.viewFrustum.calculateCubeKeyholeIntersection(cube); - if (!(lastIntersection == ViewFrustum::INSIDE && nextElement->getLastChanged() < lastTime)) { + if (!(lastIntersection == ViewFrustum::INSIDE && nextElement->getLastChanged() < lastView.startTime)) { next.element = nextElement; // NOTE: for differential case next.intersection is against the lastView // because this helps the "external scan" optimize its culling @@ -130,10 +130,8 @@ void DiffTraversal::Waypoint::getNextVisibleElementDifferential(DiffTraversal::V } else { // check for LOD truncation in the last traversal because // we may need to traverse this element after all if the lastView skipped it for LOD - // - // NOTE: the element's "level" must be invariant (the differntial algorithm doesn't work otherwise) - // so we recycle the value computed higher up - visibleLimit = boundaryDistanceForRenderLevel(level, lastView.rootSizeScale); + int32_t lastLevel = element->getLevel() + lastView.lodLevelOffset; + visibleLimit = boundaryDistanceForRenderLevel(lastLevel, lastView.rootSizeScale); distance2 = glm::distance2(lastView.viewFrustum.getPosition(), element->getAACube().calcCenter()); if (distance2 >= visibleLimit * visibleLimit) { next.element = nextElement; @@ -151,7 +149,9 @@ void DiffTraversal::Waypoint::getNextVisibleElementDifferential(DiffTraversal::V next.intersection = ViewFrustum::OUTSIDE; } -DiffTraversal::Type DiffTraversal::prepareNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root) { +DiffTraversal::Type DiffTraversal::prepareNewTraversal( + const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset) { + assert(root); // there are three types of traversal: // // (1) First = fresh view --> find all elements in view @@ -167,39 +167,36 @@ DiffTraversal::Type DiffTraversal::prepareNewTraversal(const ViewFrustum& viewFr // external code should update the _scanElementCallback after calling prepareNewTraversal // - Type type = Type::First; - if (_startOfCompletedTraversal == 0) { - // first time + Type type; + if (_completedView.startTime == 0) { + type = Type::First; _currentView.viewFrustum = viewFrustum; + _currentView.lodLevelOffset = root->getLevel() + lodLevelOffset - 1; // -1 because true root has level=1 + _currentView.rootSizeScale = root->getScale() * MAX_VISIBILITY_DISTANCE_FOR_UNIT_ELEMENT; _getNextVisibleElementCallback = [&](DiffTraversal::VisibleElement& next) { _path.back().getNextVisibleElementFirstTime(next, _currentView); }; - } else if (_currentView.viewFrustum.isVerySimilar(viewFrustum)) { - // again - _getNextVisibleElementCallback = [&](DiffTraversal::VisibleElement& next) { - _path.back().getNextVisibleElementRepeat(next, _currentView, _startOfCompletedTraversal); - }; + } else if (_completedView.viewFrustum.isVerySimilar(viewFrustum) && lodLevelOffset == _completedView.lodLevelOffset) { type = Type::Repeat; - } else { - // differential - _currentView.viewFrustum = viewFrustum; _getNextVisibleElementCallback = [&](DiffTraversal::VisibleElement& next) { - _path.back().getNextVisibleElementDifferential(next, _currentView, _completedView, _startOfCompletedTraversal); + _path.back().getNextVisibleElementRepeat(next, _completedView, _completedView.startTime); }; + } else { type = Type::Differential; + _currentView.viewFrustum = viewFrustum; + _currentView.lodLevelOffset = root->getLevel() + lodLevelOffset - 1; // -1 because true root has level=1 + _currentView.rootSizeScale = root->getScale() * MAX_VISIBILITY_DISTANCE_FOR_UNIT_ELEMENT; + _getNextVisibleElementCallback = [&](DiffTraversal::VisibleElement& next) { + _path.back().getNextVisibleElementDifferential(next, _currentView, _completedView); + }; } - assert(root); _path.clear(); _path.push_back(DiffTraversal::Waypoint(root)); // set root fork's index such that root element returned at getNextElement() _path.back().initRootNextIndex(); - // cache LOD parameters in the _currentView - _currentView.rootLevel = root->getLevel(); - _currentView.rootSizeScale = root->getScale() * MAX_VISIBILITY_DISTANCE_FOR_UNIT_ELEMENT; - - _startOfCurrentTraversal = usecTimestampNow(); + _currentView.startTime = usecTimestampNow(); return type; } @@ -224,7 +221,6 @@ void DiffTraversal::getNextVisibleElement(DiffTraversal::VisibleElement& next) { if (_path.empty()) { // we've traversed the entire tree _completedView = _currentView; - _startOfCompletedTraversal = _startOfCurrentTraversal; return; } // keep looking for next diff --git a/libraries/entities/src/DiffTraversal.h b/libraries/entities/src/DiffTraversal.h index 53361780fc..f1025f1e3a 100644 --- a/libraries/entities/src/DiffTraversal.h +++ b/libraries/entities/src/DiffTraversal.h @@ -33,8 +33,9 @@ public: class View { public: ViewFrustum viewFrustum; + uint64_t startTime { 0 }; float rootSizeScale { 1.0f }; - float rootLevel { 0.0f }; + int32_t lodLevelOffset { 0 }; }; // Waypoint is an bookmark in a "path" of waypoints during a traversal. @@ -44,7 +45,7 @@ public: void getNextVisibleElementFirstTime(VisibleElement& next, const View& view); void getNextVisibleElementRepeat(VisibleElement& next, const View& view, uint64_t lastTime); - void getNextVisibleElementDifferential(VisibleElement& next, const View& view, const View& lastView, uint64_t lastTime); + void getNextVisibleElementDifferential(VisibleElement& next, const View& view, const View& lastView); int8_t getNextIndex() const { return _nextIndex; } void initRootNextIndex() { _nextIndex = -1; } @@ -58,12 +59,12 @@ public: DiffTraversal(); - DiffTraversal::Type prepareNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root); + Type prepareNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset); const ViewFrustum& getCurrentView() const { return _currentView.viewFrustum; } const ViewFrustum& getCompletedView() const { return _completedView.viewFrustum; } - uint64_t getStartOfCompletedTraversal() const { return _startOfCompletedTraversal; } + uint64_t getStartOfCompletedTraversal() const { return _completedView.startTime; } bool finished() const { return _path.empty(); } void setScanCallback(std::function cb); @@ -79,12 +80,6 @@ private: std::vector _path; std::function _getNextVisibleElementCallback { nullptr }; std::function _scanElementCallback { [](VisibleElement& e){} }; - uint64_t _startOfCompletedTraversal { 0 }; - uint64_t _startOfCurrentTraversal { 0 }; - - // LOD stuff - float _rootSizeScale { 1.0f }; - uint32_t _rootLevel { 0 }; }; #endif // hifi_EntityPriorityQueue_h From e114fa1b824fcac3e85f5a89e95af42937c078da Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 10 Aug 2017 09:17:46 -0700 Subject: [PATCH 32/88] fix debug traversal repeat logic --- .../src/entities/EntityTreeSendThread.cpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index 48cd8ea200..bfa402e913 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -84,14 +84,10 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O bool viewFrustumChanged, bool isFullScene) { // BEGIN EXPERIMENTAL DIFFERENTIAL TRAVERSAL if (nodeData->getUsesFrustum()) { - { - // DEBUG HACK: trigger traversal (Again) every so often - const uint64_t TRAVERSE_AGAIN_PERIOD = 4 * USECS_PER_SECOND; - if (!viewFrustumChanged && usecTimestampNow() > _traversal.getStartOfCompletedTraversal() + TRAVERSE_AGAIN_PERIOD) { - viewFrustumChanged = true; - } - } - if (viewFrustumChanged) { + // DEBUG HACK: trigger traversal (Repeat) every so often + const uint64_t TRAVERSE_AGAIN_PERIOD = 4 * USECS_PER_SECOND; + bool repeatTraversal = usecTimestampNow() > _traversal.getStartOfCompletedTraversal() + TRAVERSE_AGAIN_PERIOD; + if (viewFrustumChanged || repeatTraversal) { ViewFrustum viewFrustum; nodeData->copyCurrentViewFrustum(viewFrustum); EntityTreeElementPointer root = std::dynamic_pointer_cast(_myServer->getOctree()->getRoot()); From 7597088c7c29bddba5fb0460408e743e1c25976d Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 16 Aug 2017 11:57:54 -0700 Subject: [PATCH 33/88] simpler logic flow --- libraries/entities/src/EntityTreeElement.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index f6d27bcc87..0c33855a61 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -432,11 +432,11 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData // and include the entity in our final count of entities packetData->endLevel(entityLevel); actualNumberOfEntities++; - } - // If the entity item got completely appended, then we can remove it from the extra encode data - if (appendEntityState == OctreeElement::COMPLETED) { - entityTreeElementExtraEncodeData->entities.remove(entity->getEntityItemID()); + // If the entity item got completely appended, then we can remove it from the extra encode data + if (appendEntityState == OctreeElement::COMPLETED) { + entityTreeElementExtraEncodeData->entities.remove(entity->getEntityItemID()); + } } // If any part of the entity items didn't fit, then the element is considered partial From 0b0de968940e5db61dd8af1f6e3430087f88f2ed Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 16 Aug 2017 12:22:05 -0700 Subject: [PATCH 34/88] use memcpy instead of copying one byte at a time --- libraries/octree/src/OctreePacketData.cpp | 25 +++++++++-------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/libraries/octree/src/OctreePacketData.cpp b/libraries/octree/src/OctreePacketData.cpp index 5fd7e4dba3..493dfdcff5 100644 --- a/libraries/octree/src/OctreePacketData.cpp +++ b/libraries/octree/src/OctreePacketData.cpp @@ -582,9 +582,7 @@ bool OctreePacketData::compressContent() { if (compressedData.size() < (int)MAX_OCTREE_PACKET_DATA_SIZE) { _compressedBytes = compressedData.size(); - for (int i = 0; i < _compressedBytes; i++) { - _compressed[i] = compressedData[i]; - } + memcpy(_compressed, compressedData.constData(), _compressedBytes); _dirty = false; success = true; } @@ -598,25 +596,22 @@ void OctreePacketData::loadFinalizedContent(const unsigned char* data, int lengt if (data && length > 0) { if (_enableCompression) { - QByteArray compressedData; - for (int i = 0; i < length; i++) { - compressedData[i] = data[i]; - _compressed[i] = compressedData[i]; - } _compressedBytes = length; + memcpy(_compressed, data, _compressedBytes); + + QByteArray compressedData; + compressedData.resize(_compressedBytes); + memcpy(compressedData.data(), data, _compressedBytes); + QByteArray uncompressedData = qUncompress(compressedData); if (uncompressedData.size() <= _bytesAvailable) { _bytesInUse = uncompressedData.size(); _bytesAvailable -= uncompressedData.size(); - - for (int i = 0; i < _bytesInUse; i++) { - _uncompressed[i] = uncompressedData[i]; - } + memcpy(_uncompressed, uncompressedData.constData(), _bytesInUse); } } else { - for (int i = 0; i < length; i++) { - _uncompressed[i] = _compressed[i] = data[i]; - } + memcpy(_uncompressed, data, length); + memcpy(_compressed, data, length); _bytesInUse = _compressedBytes = length; } } else { From 4c8f6834790ac67292c6b663ca7eab16ced6c101 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 16 Aug 2017 14:56:17 -0700 Subject: [PATCH 35/88] entity too small checks --- .../src/entities/EntityTreeSendThread.cpp | 84 ++++++++++++++++--- .../src/entities/EntityTreeSendThread.h | 2 +- 2 files changed, 74 insertions(+), 12 deletions(-) diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index bfa402e913..953bb0a54b 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -13,6 +13,7 @@ #include #include +#include #include "EntityServer.h" @@ -92,7 +93,7 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O nodeData->copyCurrentViewFrustum(viewFrustum); EntityTreeElementPointer root = std::dynamic_pointer_cast(_myServer->getOctree()->getRoot()); int32_t lodLevelOffset = nodeData->getBoundaryLevelAdjust() + (viewFrustumChanged ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST); - startNewTraversal(viewFrustum, root, lodLevelOffset); + startNewTraversal(viewFrustum, root, nodeData->getOctreeSizeScale(), lodLevelOffset); } } if (!_traversal.finished()) { @@ -169,7 +170,7 @@ bool EntityTreeSendThread::addDescendantsToExtraFlaggedEntities(const QUuid& fil return hasNewChild || hasNewDescendants; } -void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTreeElementPointer root, int32_t lodLevelOffset) { +void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTreeElementPointer root, float octreeSizeScale, int32_t lodLevelOffset) { DiffTraversal::Type type = _traversal.prepareNewTraversal(view, root, lodLevelOffset); // there are three types of traversal: // @@ -192,8 +193,30 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree AACube cube = entity->getQueryAACube(success); if (success) { if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { - float priority = _conicalView.computePriority(cube); - _sendQueue.push(PrioritizedEntity(entity, priority)); + // Check the size of the entity, it's possible that a "too small to see" entity is included in a + // larger octree cell because of its position (for example if it crosses the boundary of a cell it + // pops to the next higher cell. So we want to check to see that the entity is large enough to be seen + // before we consider including it. + // We can't cull a parent-entity by its dimensions because the child may be larger. We need to + // avoid sending details about a child but not the parent. The parent's queryAACube should have + // been adjusted to encompass the queryAACube of the child. + AABox entityBounds = entity->hasChildren() ? AABox(cube) : entity->getAABox(success); + if (!success) { + // if this entity is a child of an avatar, the entity-server wont be able to determine its + // AABox. If this happens, fall back to the queryAACube. + entityBounds = AABox(cube); + } + + float renderAccuracy = calculateRenderAccuracy(_traversal.getCurrentView().getPosition(), + entityBounds, + octreeSizeScale, + lodLevelOffset); + + // Only send entities if they are large enough to see + if (renderAccuracy > 0.0f) { + float priority = _conicalView.computePriority(entityBounds); + _sendQueue.push(PrioritizedEntity(entity, priority)); + } } } else { const float WHEN_IN_DOUBT_PRIORITY = 1.0f; @@ -212,8 +235,22 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree AACube cube = entity->getQueryAACube(success); if (success) { if (next.intersection == ViewFrustum::INSIDE || _traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { - float priority = _conicalView.computePriority(cube); - _sendQueue.push(PrioritizedEntity(entity, priority)); + // See the DiffTraversal::First case for an explanation of the "entity is too small" check + AABox entityBounds = entity->hasChildren() ? AABox(cube) : entity->getAABox(success); + if (!success) { + entityBounds = AABox(cube); + } + + float renderAccuracy = calculateRenderAccuracy(_traversal.getCurrentView().getPosition(), + entityBounds, + octreeSizeScale, + lodLevelOffset); + + // Only send entities if they are large enough to see + if (renderAccuracy > 0.0f) { + float priority = _conicalView.computePriority(entityBounds); + _sendQueue.push(PrioritizedEntity(entity, priority)); + } } } else { const float WHEN_IN_DOUBT_PRIORITY = 1.0f; @@ -233,11 +270,36 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree bool success = false; AACube cube = entity->getQueryAACube(success); if (success) { - if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube) && - (entity->getLastEdited() > timestamp || - !_traversal.getCompletedView().cubeIntersectsKeyhole(cube))) { - float priority = _conicalView.computePriority(cube); - _sendQueue.push(PrioritizedEntity(entity, priority)); + if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { + // See the DiffTraversal::First case for an explanation of the "entity is too small" check + AABox entityBounds = entity->hasChildren() ? AABox(cube) : entity->getAABox(success); + if (!success) { + entityBounds = AABox(cube); + } + + float renderAccuracy = calculateRenderAccuracy(_traversal.getCurrentView().getPosition(), + entityBounds, + octreeSizeScale, + lodLevelOffset); + + // Only send entities if they are large enough to see + if (renderAccuracy > 0.0f) { + if (entity->getLastEdited() > timestamp || !_traversal.getCompletedView().cubeIntersectsKeyhole(cube)) { + float priority = _conicalView.computePriority(entityBounds); + _sendQueue.push(PrioritizedEntity(entity, priority)); + } else { + // If this entity was skipped last time because it was too small, we still need to send it + float lastRenderAccuracy = calculateRenderAccuracy(_traversal.getCompletedView().getPosition(), + entityBounds, + octreeSizeScale, + lodLevelOffset); + + if (lastRenderAccuracy <= 0.0f) { + float priority = _conicalView.computePriority(entityBounds); + _sendQueue.push(PrioritizedEntity(entity, priority)); + } + } + } } } else { const float WHEN_IN_DOUBT_PRIORITY = 1.0f; diff --git a/assignment-client/src/entities/EntityTreeSendThread.h b/assignment-client/src/entities/EntityTreeSendThread.h index 33c22c8c4a..6b78172617 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.h +++ b/assignment-client/src/entities/EntityTreeSendThread.h @@ -36,7 +36,7 @@ private: bool addAncestorsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData); bool addDescendantsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData); - void startNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset); + void startNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, float octreeSizeScale, int32_t lodLevelOffset); DiffTraversal _traversal; EntityPriorityQueue _sendQueue; From b0f30acce2b3db38a8fb9e04b1e75fde61890b9b Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 16 Aug 2017 16:19:00 -0700 Subject: [PATCH 36/88] use cube instead of entityBounds --- .../src/entities/EntityTreeSendThread.cpp | 36 +++++-------------- 1 file changed, 8 insertions(+), 28 deletions(-) diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index 953bb0a54b..42a391d931 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -197,24 +197,14 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree // larger octree cell because of its position (for example if it crosses the boundary of a cell it // pops to the next higher cell. So we want to check to see that the entity is large enough to be seen // before we consider including it. - // We can't cull a parent-entity by its dimensions because the child may be larger. We need to - // avoid sending details about a child but not the parent. The parent's queryAACube should have - // been adjusted to encompass the queryAACube of the child. - AABox entityBounds = entity->hasChildren() ? AABox(cube) : entity->getAABox(success); - if (!success) { - // if this entity is a child of an avatar, the entity-server wont be able to determine its - // AABox. If this happens, fall back to the queryAACube. - entityBounds = AABox(cube); - } - float renderAccuracy = calculateRenderAccuracy(_traversal.getCurrentView().getPosition(), - entityBounds, + cube, octreeSizeScale, lodLevelOffset); // Only send entities if they are large enough to see if (renderAccuracy > 0.0f) { - float priority = _conicalView.computePriority(entityBounds); + float priority = _conicalView.computePriority(cube); _sendQueue.push(PrioritizedEntity(entity, priority)); } } @@ -236,19 +226,14 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree if (success) { if (next.intersection == ViewFrustum::INSIDE || _traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { // See the DiffTraversal::First case for an explanation of the "entity is too small" check - AABox entityBounds = entity->hasChildren() ? AABox(cube) : entity->getAABox(success); - if (!success) { - entityBounds = AABox(cube); - } - float renderAccuracy = calculateRenderAccuracy(_traversal.getCurrentView().getPosition(), - entityBounds, + cube, octreeSizeScale, lodLevelOffset); // Only send entities if they are large enough to see if (renderAccuracy > 0.0f) { - float priority = _conicalView.computePriority(entityBounds); + float priority = _conicalView.computePriority(cube); _sendQueue.push(PrioritizedEntity(entity, priority)); } } @@ -272,30 +257,25 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree if (success) { if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { // See the DiffTraversal::First case for an explanation of the "entity is too small" check - AABox entityBounds = entity->hasChildren() ? AABox(cube) : entity->getAABox(success); - if (!success) { - entityBounds = AABox(cube); - } - float renderAccuracy = calculateRenderAccuracy(_traversal.getCurrentView().getPosition(), - entityBounds, + cube, octreeSizeScale, lodLevelOffset); // Only send entities if they are large enough to see if (renderAccuracy > 0.0f) { if (entity->getLastEdited() > timestamp || !_traversal.getCompletedView().cubeIntersectsKeyhole(cube)) { - float priority = _conicalView.computePriority(entityBounds); + float priority = _conicalView.computePriority(cube); _sendQueue.push(PrioritizedEntity(entity, priority)); } else { // If this entity was skipped last time because it was too small, we still need to send it float lastRenderAccuracy = calculateRenderAccuracy(_traversal.getCompletedView().getPosition(), - entityBounds, + cube, octreeSizeScale, lodLevelOffset); if (lastRenderAccuracy <= 0.0f) { - float priority = _conicalView.computePriority(entityBounds); + float priority = _conicalView.computePriority(cube); _sendQueue.push(PrioritizedEntity(entity, priority)); } } From bb5368eb55c2c7caace8232313d64456f255c87b Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 16 Aug 2017 17:29:13 -0700 Subject: [PATCH 37/88] use correct rootSizeScale --- .../src/entities/EntityTreeSendThread.cpp | 10 +++++----- assignment-client/src/entities/EntityTreeSendThread.h | 2 +- libraries/entities/src/DiffTraversal.h | 3 +++ 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index 42a391d931..3a5fa2003f 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -170,7 +170,7 @@ bool EntityTreeSendThread::addDescendantsToExtraFlaggedEntities(const QUuid& fil return hasNewChild || hasNewDescendants; } -void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTreeElementPointer root, float octreeSizeScale, int32_t lodLevelOffset) { +void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTreeElementPointer root, int32_t lodLevelOffset) { DiffTraversal::Type type = _traversal.prepareNewTraversal(view, root, lodLevelOffset); // there are three types of traversal: // @@ -199,7 +199,7 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree // before we consider including it. float renderAccuracy = calculateRenderAccuracy(_traversal.getCurrentView().getPosition(), cube, - octreeSizeScale, + _traversal.getCurrentRootSizeScale(), lodLevelOffset); // Only send entities if they are large enough to see @@ -228,7 +228,7 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree // See the DiffTraversal::First case for an explanation of the "entity is too small" check float renderAccuracy = calculateRenderAccuracy(_traversal.getCurrentView().getPosition(), cube, - octreeSizeScale, + _traversal.getCurrentRootSizeScale(), lodLevelOffset); // Only send entities if they are large enough to see @@ -259,7 +259,7 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree // See the DiffTraversal::First case for an explanation of the "entity is too small" check float renderAccuracy = calculateRenderAccuracy(_traversal.getCurrentView().getPosition(), cube, - octreeSizeScale, + _traversal.getCurrentRootSizeScale(), lodLevelOffset); // Only send entities if they are large enough to see @@ -271,7 +271,7 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree // If this entity was skipped last time because it was too small, we still need to send it float lastRenderAccuracy = calculateRenderAccuracy(_traversal.getCompletedView().getPosition(), cube, - octreeSizeScale, + _traversal.getCompletedRootSizeScale(), lodLevelOffset); if (lastRenderAccuracy <= 0.0f) { diff --git a/assignment-client/src/entities/EntityTreeSendThread.h b/assignment-client/src/entities/EntityTreeSendThread.h index 6b78172617..33c22c8c4a 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.h +++ b/assignment-client/src/entities/EntityTreeSendThread.h @@ -36,7 +36,7 @@ private: bool addAncestorsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData); bool addDescendantsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData); - void startNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, float octreeSizeScale, int32_t lodLevelOffset); + void startNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset); DiffTraversal _traversal; EntityPriorityQueue _sendQueue; diff --git a/libraries/entities/src/DiffTraversal.h b/libraries/entities/src/DiffTraversal.h index f1025f1e3a..874e1ed869 100644 --- a/libraries/entities/src/DiffTraversal.h +++ b/libraries/entities/src/DiffTraversal.h @@ -64,6 +64,9 @@ public: const ViewFrustum& getCurrentView() const { return _currentView.viewFrustum; } const ViewFrustum& getCompletedView() const { return _completedView.viewFrustum; } + const float getCurrentRootSizeScale() const { return _currentView.rootSizeScale; } + const float getCompletedRootSizeScale() const { return _completedView.rootSizeScale; } + uint64_t getStartOfCompletedTraversal() const { return _completedView.startTime; } bool finished() const { return _path.empty(); } From cf2e500ec44672d776bc49bde4db837e94d19555 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 17 Aug 2017 15:49:11 -0700 Subject: [PATCH 38/88] remove unnecessary const qualifiers --- libraries/entities/src/DiffTraversal.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/entities/src/DiffTraversal.h b/libraries/entities/src/DiffTraversal.h index 874e1ed869..87bd83b70f 100644 --- a/libraries/entities/src/DiffTraversal.h +++ b/libraries/entities/src/DiffTraversal.h @@ -64,8 +64,8 @@ public: const ViewFrustum& getCurrentView() const { return _currentView.viewFrustum; } const ViewFrustum& getCompletedView() const { return _completedView.viewFrustum; } - const float getCurrentRootSizeScale() const { return _currentView.rootSizeScale; } - const float getCompletedRootSizeScale() const { return _completedView.rootSizeScale; } + float getCurrentRootSizeScale() const { return _currentView.rootSizeScale; } + float getCompletedRootSizeScale() const { return _completedView.rootSizeScale; } uint64_t getStartOfCompletedTraversal() const { return _completedView.startTime; } bool finished() const { return _path.empty(); } From 4f50b5755f45a86fdf9c53d8309e2fc2c930cb03 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 17 Aug 2017 15:50:44 -0700 Subject: [PATCH 39/88] remove crufty argument --- assignment-client/src/entities/EntityTreeSendThread.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index 3a5fa2003f..a968c6e2a3 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -93,7 +93,7 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O nodeData->copyCurrentViewFrustum(viewFrustum); EntityTreeElementPointer root = std::dynamic_pointer_cast(_myServer->getOctree()->getRoot()); int32_t lodLevelOffset = nodeData->getBoundaryLevelAdjust() + (viewFrustumChanged ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST); - startNewTraversal(viewFrustum, root, nodeData->getOctreeSizeScale(), lodLevelOffset); + startNewTraversal(viewFrustum, root, lodLevelOffset); } } if (!_traversal.finished()) { From 9fb7eb4ba6a4a91675bb097002016610d981e4e5 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Mon, 21 Aug 2017 10:47:04 -0700 Subject: [PATCH 40/88] resort _sendQueue when previous view didn't finish --- .../src/entities/EntityTreeSendThread.cpp | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index a968c6e2a3..b2ebfab7ee 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -84,6 +84,7 @@ void EntityTreeSendThread::preDistributionProcessing() { void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged, bool isFullScene) { // BEGIN EXPERIMENTAL DIFFERENTIAL TRAVERSAL + int32_t lodLevelOffset = nodeData->getBoundaryLevelAdjust() + (viewFrustumChanged ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST); if (nodeData->getUsesFrustum()) { // DEBUG HACK: trigger traversal (Repeat) every so often const uint64_t TRAVERSE_AGAIN_PERIOD = 4 * USECS_PER_SECOND; @@ -92,10 +93,14 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O ViewFrustum viewFrustum; nodeData->copyCurrentViewFrustum(viewFrustum); EntityTreeElementPointer root = std::dynamic_pointer_cast(_myServer->getOctree()->getRoot()); - int32_t lodLevelOffset = nodeData->getBoundaryLevelAdjust() + (viewFrustumChanged ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST); startNewTraversal(viewFrustum, root, lodLevelOffset); } } + + // If the previous traversal didn't finish, we'll need to resort the entities still in _sendQueue after calling traverse + EntityPriorityQueue prevSendQueue; + _sendQueue.swap(prevSendQueue); + if (!_traversal.finished()) { uint64_t startTime = usecTimestampNow(); @@ -105,6 +110,34 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O uint64_t dt = usecTimestampNow() - startTime; std::cout << "adebug traversal complete " << " Q.size = " << _sendQueue.size() << " dt = " << dt << std::endl; // adebug } + + // Re-add elements from previous traveral if they still need to be sent + while (!prevSendQueue.empty()) { + EntityItemPointer entity = prevSendQueue.top().getEntity(); + prevSendQueue.pop(); + if (entity) { + bool success = false; + AACube cube = entity->getQueryAACube(success); + if (success) { + if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { + float renderAccuracy = calculateRenderAccuracy(_traversal.getCurrentView().getPosition(), + cube, + _traversal.getCurrentRootSizeScale(), + lodLevelOffset); + + // Only send entities if they are large enough to see + if (renderAccuracy > 0.0f) { + float priority = _conicalView.computePriority(cube); + _sendQueue.push(PrioritizedEntity(entity, priority)); + } + } + } else { + const float WHEN_IN_DOUBT_PRIORITY = 1.0f; + _sendQueue.push(PrioritizedEntity(entity, WHEN_IN_DOUBT_PRIORITY)); + } + } + } + if (!_sendQueue.empty()) { // print what needs to be sent while (!_sendQueue.empty()) { From 18f88a5a643bb072a1766d16f68b49e49f7ed4c5 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Mon, 21 Aug 2017 16:54:39 -0700 Subject: [PATCH 41/88] keep track of readded entities in a set to avoid rechecking them, compute priority early --- .../src/entities/EntityTreeSendThread.cpp | 72 ++++++++++++------- .../src/entities/EntityTreeSendThread.h | 3 + 2 files changed, 48 insertions(+), 27 deletions(-) diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index b2ebfab7ee..adb9aedb6b 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -100,6 +100,39 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O // If the previous traversal didn't finish, we'll need to resort the entities still in _sendQueue after calling traverse EntityPriorityQueue prevSendQueue; _sendQueue.swap(prevSendQueue); + _prevEntitySet.clear(); + // Re-add elements from previous traveral if they still need to be sent + while (!prevSendQueue.empty()) { + EntityItemPointer entity = prevSendQueue.top().getEntity(); + prevSendQueue.pop(); + if (entity) { + // We can keep track of the entity regardless of if we decide to send it so that we don't have to check it again + // during the traversal + _prevEntitySet.insert(entity); + bool success = false; + AACube cube = entity->getQueryAACube(success); + if (success) { + if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { + const float DO_NOT_SEND = -1.0e-6f; + float priority = _conicalView.computePriority(cube); + if (priority != DO_NOT_SEND) { + float renderAccuracy = calculateRenderAccuracy(_traversal.getCurrentView().getPosition(), + cube, + _traversal.getCurrentRootSizeScale(), + lodLevelOffset); + + // Only send entities if they are large enough to see + if (renderAccuracy > 0.0f) { + _sendQueue.push(PrioritizedEntity(entity, priority)); + } + } + } + } else { + const float WHEN_IN_DOUBT_PRIORITY = 1.0f; + _sendQueue.push(PrioritizedEntity(entity, WHEN_IN_DOUBT_PRIORITY)); + } + } + } if (!_traversal.finished()) { uint64_t startTime = usecTimestampNow(); @@ -111,33 +144,6 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O std::cout << "adebug traversal complete " << " Q.size = " << _sendQueue.size() << " dt = " << dt << std::endl; // adebug } - // Re-add elements from previous traveral if they still need to be sent - while (!prevSendQueue.empty()) { - EntityItemPointer entity = prevSendQueue.top().getEntity(); - prevSendQueue.pop(); - if (entity) { - bool success = false; - AACube cube = entity->getQueryAACube(success); - if (success) { - if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { - float renderAccuracy = calculateRenderAccuracy(_traversal.getCurrentView().getPosition(), - cube, - _traversal.getCurrentRootSizeScale(), - lodLevelOffset); - - // Only send entities if they are large enough to see - if (renderAccuracy > 0.0f) { - float priority = _conicalView.computePriority(cube); - _sendQueue.push(PrioritizedEntity(entity, priority)); - } - } - } else { - const float WHEN_IN_DOUBT_PRIORITY = 1.0f; - _sendQueue.push(PrioritizedEntity(entity, WHEN_IN_DOUBT_PRIORITY)); - } - } - } - if (!_sendQueue.empty()) { // print what needs to be sent while (!_sendQueue.empty()) { @@ -222,6 +228,10 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree case DiffTraversal::First: _traversal.setScanCallback([&] (DiffTraversal::VisibleElement& next) { next.element->forEachEntity([&](EntityItemPointer entity) { + // Bail early if we've already checked this entity this frame + if (_prevEntitySet.find(entity) != _prevEntitySet.end()) { + return; + } bool success = false; AACube cube = entity->getQueryAACube(success); if (success) { @@ -253,6 +263,10 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree if (next.element->getLastChangedContent() > _traversal.getStartOfCompletedTraversal()) { uint64_t timestamp = _traversal.getStartOfCompletedTraversal(); next.element->forEachEntity([&](EntityItemPointer entity) { + // Bail early if we've already checked this entity this frame + if (_prevEntitySet.find(entity) != _prevEntitySet.end()) { + return; + } if (entity->getLastEdited() > timestamp) { bool success = false; AACube cube = entity->getQueryAACube(success); @@ -285,6 +299,10 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree uint64_t timestamp = _traversal.getStartOfCompletedTraversal(); if (next.element->getLastChangedContent() > timestamp || next.intersection != ViewFrustum::INSIDE) { next.element->forEachEntity([&](EntityItemPointer entity) { + // Bail early if we've already checked this entity this frame + if (_prevEntitySet.find(entity) != _prevEntitySet.end()) { + return; + } bool success = false; AACube cube = entity->getQueryAACube(success); if (success) { diff --git a/assignment-client/src/entities/EntityTreeSendThread.h b/assignment-client/src/entities/EntityTreeSendThread.h index 33c22c8c4a..f98d007eb3 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.h +++ b/assignment-client/src/entities/EntityTreeSendThread.h @@ -12,6 +12,8 @@ #ifndef hifi_EntityTreeSendThread_h #define hifi_EntityTreeSendThread_h +#include + #include "../octree/OctreeSendThread.h" #include @@ -40,6 +42,7 @@ private: DiffTraversal _traversal; EntityPriorityQueue _sendQueue; + std::unordered_set _prevEntitySet; ConicalView _conicalView; // cached optimized view for fast priority calculations }; From 1930c8f215ba0fe417b442d8f7a0f9ed1a873d47 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Tue, 22 Aug 2017 18:01:14 -0700 Subject: [PATCH 42/88] only resort if view changed --- .../src/entities/EntityTreeSendThread.cpp | 77 +++++++++++-------- .../src/entities/EntityTreeSendThread.h | 2 +- 2 files changed, 44 insertions(+), 35 deletions(-) diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index adb9aedb6b..946dedc3ff 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -94,42 +94,43 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O nodeData->copyCurrentViewFrustum(viewFrustum); EntityTreeElementPointer root = std::dynamic_pointer_cast(_myServer->getOctree()->getRoot()); startNewTraversal(viewFrustum, root, lodLevelOffset); - } - } - // If the previous traversal didn't finish, we'll need to resort the entities still in _sendQueue after calling traverse - EntityPriorityQueue prevSendQueue; - _sendQueue.swap(prevSendQueue); - _prevEntitySet.clear(); - // Re-add elements from previous traveral if they still need to be sent - while (!prevSendQueue.empty()) { - EntityItemPointer entity = prevSendQueue.top().getEntity(); - prevSendQueue.pop(); - if (entity) { - // We can keep track of the entity regardless of if we decide to send it so that we don't have to check it again - // during the traversal - _prevEntitySet.insert(entity); - bool success = false; - AACube cube = entity->getQueryAACube(success); - if (success) { - if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { - const float DO_NOT_SEND = -1.0e-6f; - float priority = _conicalView.computePriority(cube); - if (priority != DO_NOT_SEND) { - float renderAccuracy = calculateRenderAccuracy(_traversal.getCurrentView().getPosition(), - cube, - _traversal.getCurrentRootSizeScale(), - lodLevelOffset); + // If the previous traversal didn't finish, we'll need to resort the entities still in _sendQueue after calling traverse + if (!_sendQueue.empty()) { + EntityPriorityQueue prevSendQueue; + _sendQueue.swap(prevSendQueue); + // Re-add elements from previous traveral if they still need to be sent + while (!prevSendQueue.empty()) { + EntityItemPointer entity = prevSendQueue.top().getEntity(); + prevSendQueue.pop(); + _entitiesToSend.clear(); + if (entity) { + bool success = false; + AACube cube = entity->getQueryAACube(success); + if (success) { + if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { + const float DO_NOT_SEND = -1.0e-6f; + float priority = _conicalView.computePriority(cube); + if (priority != DO_NOT_SEND) { + float renderAccuracy = calculateRenderAccuracy(_traversal.getCurrentView().getPosition(), + cube, + _traversal.getCurrentRootSizeScale(), + lodLevelOffset); - // Only send entities if they are large enough to see - if (renderAccuracy > 0.0f) { - _sendQueue.push(PrioritizedEntity(entity, priority)); + // Only send entities if they are large enough to see + if (renderAccuracy > 0.0f) { + _sendQueue.push(PrioritizedEntity(entity, priority)); + _entitiesToSend.insert(entity); + } + } + } + } else { + const float WHEN_IN_DOUBT_PRIORITY = 1.0f; + _sendQueue.push(PrioritizedEntity(entity, WHEN_IN_DOUBT_PRIORITY)); + _entitiesToSend.insert(entity); } } } - } else { - const float WHEN_IN_DOUBT_PRIORITY = 1.0f; - _sendQueue.push(PrioritizedEntity(entity, WHEN_IN_DOUBT_PRIORITY)); } } } @@ -153,6 +154,7 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O std::cout << "adebug send '" << entity->getName().toStdString() << "'" << " : " << entry.getPriority() << std::endl; // adebug } _sendQueue.pop(); + _entitiesToSend.erase(entity); } } // END EXPERIMENTAL DIFFERENTIAL TRAVERSAL @@ -229,7 +231,7 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree _traversal.setScanCallback([&] (DiffTraversal::VisibleElement& next) { next.element->forEachEntity([&](EntityItemPointer entity) { // Bail early if we've already checked this entity this frame - if (_prevEntitySet.find(entity) != _prevEntitySet.end()) { + if (_entitiesToSend.find(entity) != _entitiesToSend.end()) { return; } bool success = false; @@ -249,11 +251,13 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree if (renderAccuracy > 0.0f) { float priority = _conicalView.computePriority(cube); _sendQueue.push(PrioritizedEntity(entity, priority)); + _entitiesToSend.insert(entity); } } } else { const float WHEN_IN_DOUBT_PRIORITY = 1.0f; _sendQueue.push(PrioritizedEntity(entity, WHEN_IN_DOUBT_PRIORITY)); + _entitiesToSend.insert(entity); } }); }); @@ -264,7 +268,7 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree uint64_t timestamp = _traversal.getStartOfCompletedTraversal(); next.element->forEachEntity([&](EntityItemPointer entity) { // Bail early if we've already checked this entity this frame - if (_prevEntitySet.find(entity) != _prevEntitySet.end()) { + if (_entitiesToSend.find(entity) != _entitiesToSend.end()) { return; } if (entity->getLastEdited() > timestamp) { @@ -282,11 +286,13 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree if (renderAccuracy > 0.0f) { float priority = _conicalView.computePriority(cube); _sendQueue.push(PrioritizedEntity(entity, priority)); + _entitiesToSend.insert(entity); } } } else { const float WHEN_IN_DOUBT_PRIORITY = 1.0f; _sendQueue.push(PrioritizedEntity(entity, WHEN_IN_DOUBT_PRIORITY)); + _entitiesToSend.insert(entity); } } }); @@ -300,7 +306,7 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree if (next.element->getLastChangedContent() > timestamp || next.intersection != ViewFrustum::INSIDE) { next.element->forEachEntity([&](EntityItemPointer entity) { // Bail early if we've already checked this entity this frame - if (_prevEntitySet.find(entity) != _prevEntitySet.end()) { + if (_entitiesToSend.find(entity) != _entitiesToSend.end()) { return; } bool success = false; @@ -318,6 +324,7 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree if (entity->getLastEdited() > timestamp || !_traversal.getCompletedView().cubeIntersectsKeyhole(cube)) { float priority = _conicalView.computePriority(cube); _sendQueue.push(PrioritizedEntity(entity, priority)); + _entitiesToSend.insert(entity); } else { // If this entity was skipped last time because it was too small, we still need to send it float lastRenderAccuracy = calculateRenderAccuracy(_traversal.getCompletedView().getPosition(), @@ -328,6 +335,7 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree if (lastRenderAccuracy <= 0.0f) { float priority = _conicalView.computePriority(cube); _sendQueue.push(PrioritizedEntity(entity, priority)); + _entitiesToSend.insert(entity); } } } @@ -335,6 +343,7 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree } else { const float WHEN_IN_DOUBT_PRIORITY = 1.0f; _sendQueue.push(PrioritizedEntity(entity, WHEN_IN_DOUBT_PRIORITY)); + _entitiesToSend.insert(entity); } }); } diff --git a/assignment-client/src/entities/EntityTreeSendThread.h b/assignment-client/src/entities/EntityTreeSendThread.h index f98d007eb3..50e7981938 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.h +++ b/assignment-client/src/entities/EntityTreeSendThread.h @@ -42,7 +42,7 @@ private: DiffTraversal _traversal; EntityPriorityQueue _sendQueue; - std::unordered_set _prevEntitySet; + std::unordered_set _entitiesToSend; ConicalView _conicalView; // cached optimized view for fast priority calculations }; From 971f1e79249a3fefeeb8f7bf4bd2051fc95ec185 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Tue, 22 Aug 2017 18:02:35 -0700 Subject: [PATCH 43/88] put lodLevelOffset back --- .../src/entities/EntityTreeSendThread.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index 946dedc3ff..a19badafe9 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -84,7 +84,6 @@ void EntityTreeSendThread::preDistributionProcessing() { void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged, bool isFullScene) { // BEGIN EXPERIMENTAL DIFFERENTIAL TRAVERSAL - int32_t lodLevelOffset = nodeData->getBoundaryLevelAdjust() + (viewFrustumChanged ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST); if (nodeData->getUsesFrustum()) { // DEBUG HACK: trigger traversal (Repeat) every so often const uint64_t TRAVERSE_AGAIN_PERIOD = 4 * USECS_PER_SECOND; @@ -93,17 +92,18 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O ViewFrustum viewFrustum; nodeData->copyCurrentViewFrustum(viewFrustum); EntityTreeElementPointer root = std::dynamic_pointer_cast(_myServer->getOctree()->getRoot()); + int32_t lodLevelOffset = nodeData->getBoundaryLevelAdjust() + (viewFrustumChanged ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST); startNewTraversal(viewFrustum, root, lodLevelOffset); // If the previous traversal didn't finish, we'll need to resort the entities still in _sendQueue after calling traverse if (!_sendQueue.empty()) { EntityPriorityQueue prevSendQueue; _sendQueue.swap(prevSendQueue); + _entitiesToSend.clear(); // Re-add elements from previous traveral if they still need to be sent while (!prevSendQueue.empty()) { EntityItemPointer entity = prevSendQueue.top().getEntity(); prevSendQueue.pop(); - _entitiesToSend.clear(); if (entity) { bool success = false; AACube cube = entity->getQueryAACube(success); @@ -113,9 +113,9 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O float priority = _conicalView.computePriority(cube); if (priority != DO_NOT_SEND) { float renderAccuracy = calculateRenderAccuracy(_traversal.getCurrentView().getPosition(), - cube, - _traversal.getCurrentRootSizeScale(), - lodLevelOffset); + cube, + _traversal.getCurrentRootSizeScale(), + lodLevelOffset); // Only send entities if they are large enough to see if (renderAccuracy > 0.0f) { From a32cc7f5554fcb221706c3499e265a7b784c29ca Mon Sep 17 00:00:00 2001 From: Sam Gondelman Date: Wed, 23 Aug 2017 13:50:08 -0700 Subject: [PATCH 44/88] typo --- assignment-client/src/entities/EntityTreeSendThread.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index a19badafe9..9cc0c3d34e 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -100,7 +100,7 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O EntityPriorityQueue prevSendQueue; _sendQueue.swap(prevSendQueue); _entitiesToSend.clear(); - // Re-add elements from previous traveral if they still need to be sent + // Re-add elements from previous traversal if they still need to be sent while (!prevSendQueue.empty()) { EntityItemPointer entity = prevSendQueue.top().getEntity(); prevSendQueue.pop(); From 1562fb153e704a4caff229f9bbf1272d9446f567 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 23 Aug 2017 14:56:18 -0700 Subject: [PATCH 45/88] cherrypick traverseTreeAndBuildNextPacketPayload() --- .../src/octree/OctreeSendThread.cpp | 2 +- .../src/octree/OctreeSendThread.h | 6 +- assignment-client/src/octree/OctreeServer.cpp | 58 +++++++++---------- libraries/octree/src/Octree.h | 3 +- 4 files changed, 34 insertions(+), 35 deletions(-) diff --git a/assignment-client/src/octree/OctreeSendThread.cpp b/assignment-client/src/octree/OctreeSendThread.cpp index 9345f96b1d..b6d9d323c1 100644 --- a/assignment-client/src/octree/OctreeSendThread.cpp +++ b/assignment-client/src/octree/OctreeSendThread.cpp @@ -27,8 +27,8 @@ quint64 startSceneSleepTime = 0; quint64 endSceneSleepTime = 0; OctreeSendThread::OctreeSendThread(OctreeServer* myServer, const SharedNodePointer& node) : - _myServer(myServer), _node(node), + _myServer(myServer), _nodeUuid(node->getUUID()) { QString safeServerName("Octree"); diff --git a/assignment-client/src/octree/OctreeSendThread.h b/assignment-client/src/octree/OctreeSendThread.h index 8f75092528..bcfede262c 100644 --- a/assignment-client/src/octree/OctreeSendThread.h +++ b/assignment-client/src/octree/OctreeSendThread.h @@ -57,18 +57,16 @@ protected: bool viewFrustumChanged, bool isFullScene); virtual bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params); - OctreeServer* _myServer { nullptr }; + OctreePacketData _packetData; QWeakPointer _node; + OctreeServer* _myServer { nullptr }; private: int handlePacketSend(SharedNodePointer node, OctreeQueryNode* nodeData, bool dontSuppressDuplicate = false); int packetDistributor(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged); - QUuid _nodeUuid; - OctreePacketData _packetData; - int _truePacketsSent { 0 }; // available for debug stats int _trueBytesSent { 0 }; // available for debug stats int _packetsSentThisInterval { 0 }; // used for bandwidth throttle condition diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index 974f00326b..4a40449e30 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -883,7 +883,7 @@ OctreeServer::UniqueSendThread OctreeServer::newSendThread(const SharedNodePoint OctreeServer::UniqueSendThread OctreeServer::createSendThread(const SharedNodePointer& node) { auto sendThread = newSendThread(node); - + // we want to be notified when the thread finishes connect(sendThread.get(), &GenericThread::finished, this, &OctreeServer::removeSendThread); sendThread->initialize(true); @@ -905,13 +905,13 @@ void OctreeServer::handleOctreeQueryPacket(QSharedPointer messa // need to make sure we have it in our nodeList. auto nodeList = DependencyManager::get(); nodeList->updateNodeWithDataFromPacket(message, senderNode); - + auto it = _sendThreads.find(senderNode->getUUID()); if (it == _sendThreads.end()) { _sendThreads.emplace(senderNode->getUUID(), createSendThread(senderNode)); } else if (it->second->isShuttingDown()) { _sendThreads.erase(it); // Remove right away and wait on thread to be - + _sendThreads.emplace(senderNode->getUUID(), createSendThread(senderNode)); } } @@ -1085,7 +1085,7 @@ void OctreeServer::readConfiguration() { if (getPayload().size() > 0) { parsePayload(); } - + const QJsonObject& settingsObject = DependencyManager::get()->getDomainHandler().getSettingsObject(); QString settingsKey = getMyDomainSettingsKey(); @@ -1212,9 +1212,9 @@ void OctreeServer::run() { OctreeElement::resetPopulationStatistics(); _tree = createTree(); _tree->setIsServer(true); - + qDebug() << "Waiting for connection to domain to request settings from domain-server."; - + // wait until we have the domain-server settings, otherwise we bail DomainHandler& domainHandler = DependencyManager::get()->getDomainHandler(); connect(&domainHandler, &DomainHandler::settingsReceived, this, &OctreeServer::domainSettingsRequestComplete); @@ -1225,9 +1225,9 @@ void OctreeServer::run() { } void OctreeServer::domainSettingsRequestComplete() { - + auto nodeList = DependencyManager::get(); - + // we need to ask the DS about agents so we can ping/reply with them nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer }); @@ -1237,26 +1237,26 @@ void OctreeServer::domainSettingsRequestComplete() { packetReceiver.registerListener(PacketType::JurisdictionRequest, this, "handleJurisdictionRequestPacket"); packetReceiver.registerListener(PacketType::OctreeFileReplacement, this, "handleOctreeFileReplacement"); packetReceiver.registerListener(PacketType::OctreeFileReplacementFromUrl, this, "handleOctreeFileReplacementFromURL"); - + readConfiguration(); - + beforeRun(); // after payload has been processed - + connect(nodeList.data(), SIGNAL(nodeAdded(SharedNodePointer)), SLOT(nodeAdded(SharedNodePointer))); connect(nodeList.data(), SIGNAL(nodeKilled(SharedNodePointer)), SLOT(nodeKilled(SharedNodePointer))); #ifndef WIN32 setvbuf(stdout, NULL, _IOLBF, 0); #endif - + nodeList->linkedDataCreateCallback = [this](Node* node) { auto queryNodeData = createOctreeQueryNode(); queryNodeData->init(); node->setLinkedData(std::move(queryNodeData)); }; - + srand((unsigned)time(0)); - + // if we want Persistence, set up the local file and persist thread if (_wantPersist) { // If persist filename does not exist, let's see if there is one beside the application binary @@ -1351,24 +1351,24 @@ void OctreeServer::domainSettingsRequestComplete() { } } qDebug() << "Backups will be stored in: " << _backupDirectoryPath; - + // now set up PersistThread _persistThread = new OctreePersistThread(_tree, _persistAbsoluteFilePath, _backupDirectoryPath, _persistInterval, _wantBackup, _settings, _debugTimestampNow, _persistAsFileType); _persistThread->initialize(true); } - + // set up our jurisdiction broadcaster... if (_jurisdiction) { _jurisdiction->setNodeType(getMyNodeType()); } _jurisdictionSender = new JurisdictionSender(_jurisdiction, getMyNodeType()); _jurisdictionSender->initialize(true); - + // set up our OctreeServerPacketProcessor _octreeInboundPacketProcessor = new OctreeInboundPacketProcessor(this); _octreeInboundPacketProcessor->initialize(true); - + // Convert now to tm struct for local timezone tm* localtm = localtime(&_started); const int MAX_TIME_LENGTH = 128; @@ -1380,7 +1380,7 @@ void OctreeServer::domainSettingsRequestComplete() { if (gmtm) { strftime(utcBuffer, MAX_TIME_LENGTH, " [%m/%d/%Y %X UTC]", gmtm); } - + qDebug() << "Now running... started at: " << localBuffer << utcBuffer; } @@ -1391,7 +1391,7 @@ void OctreeServer::nodeAdded(SharedNodePointer node) { void OctreeServer::nodeKilled(SharedNodePointer node) { quint64 start = usecTimestampNow(); - + // Shutdown send thread auto it = _sendThreads.find(node->getUUID()); if (it != _sendThreads.end()) { @@ -1437,13 +1437,13 @@ void OctreeServer::aboutToFinish() { if (_jurisdictionSender) { _jurisdictionSender->terminating(); } - + // Shut down all the send threads for (auto& it : _sendThreads) { auto& sendThread = *it.second; sendThread.setIsShuttingDown(); } - + // Clear will destruct all the unique_ptr to OctreeSendThreads which will call the GenericThread's dtor // which waits on the thread to be done before returning _sendThreads.clear(); // Cleans up all the send threads. @@ -1563,7 +1563,7 @@ void OctreeServer::sendStatsPacket() { threadsStats["2. packetDistributor"] = (double)howManyThreadsDidPacketDistributor(oneSecondAgo); threadsStats["3. handlePacektSend"] = (double)howManyThreadsDidHandlePacketSend(oneSecondAgo); threadsStats["4. writeDatagram"] = (double)howManyThreadsDidCallWriteDatagram(oneSecondAgo); - + QJsonObject statsArray1; statsArray1["1. configuration"] = getConfiguration(); statsArray1["2. detailed_stats_url"] = getStatusLink(); @@ -1571,13 +1571,13 @@ void OctreeServer::sendStatsPacket() { statsArray1["4. persistFileLoadTime"] = getFileLoadTime(); statsArray1["5. clients"] = getCurrentClientCount(); statsArray1["6. threads"] = threadsStats; - + // Octree Stats QJsonObject octreeStats; octreeStats["1. elementCount"] = (double)OctreeElement::getNodeCount(); octreeStats["2. internalElementCount"] = (double)OctreeElement::getInternalNodeCount(); octreeStats["3. leafElementCount"] = (double)OctreeElement::getLeafNodeCount(); - + // Stats Object 2 QJsonObject dataObject1; dataObject1["1. totalPackets"] = (double)OctreeSendThread::_totalPackets; @@ -1595,7 +1595,7 @@ void OctreeServer::sendStatsPacket() { timingArray1["5. avgCompressAndWriteTime"] = getAverageCompressAndWriteTime(); timingArray1["6. avgSendTime"] = getAveragePacketSendingTime(); timingArray1["7. nodeWaitTime"] = getAverageNodeWaitTime(); - + QJsonObject statsObject2; statsObject2["data"] = dataObject1; statsObject2["timing"] = timingArray1; @@ -1615,18 +1615,18 @@ void OctreeServer::sendStatsPacket() { timingArray2["4. avgProcessTimePerElement"] = (double)_octreeInboundPacketProcessor->getAverageProcessTimePerElement(); timingArray2["5. avgLockWaitTimePerElement"] = (double)_octreeInboundPacketProcessor->getAverageLockWaitTimePerElement(); } - + QJsonObject statsObject3; statsObject3["data"] = dataArray2; statsObject3["timing"] = timingArray2; - + // Merge everything QJsonObject jsonArray; jsonArray["1. misc"] = statsArray1; jsonArray["2. octree"] = octreeStats; jsonArray["3. outbound"] = statsObject2; jsonArray["4. inbound"] = statsObject3; - + QJsonObject statsObject; statsObject[QString(getMyServerName()) + "Server"] = jsonArray; addPacketStatsAndSendStatsPacket(statsObject); diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index 2794ca85f0..a2df5f44e5 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -92,7 +92,8 @@ public: OUT_OF_VIEW, WAS_IN_VIEW, NO_CHANGE, - OCCLUDED + OCCLUDED, + FINISHED } reason; reason stopReason; From b6818c4369b5da374a45d90ceee953847aa51446 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 23 Aug 2017 14:56:55 -0700 Subject: [PATCH 46/88] first-pass sending entities from _sendQueue --- .../src/entities/EntityTreeSendThread.cpp | 86 ++++++++++++++++++- .../src/entities/EntityTreeSendThread.h | 6 ++ libraries/entities/src/DiffTraversal.cpp | 20 ++--- libraries/entities/src/DiffTraversal.h | 2 + 4 files changed, 99 insertions(+), 15 deletions(-) diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index 9cc0c3d34e..8e5cdfed13 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -17,6 +17,8 @@ #include "EntityServer.h" +//#define SEND_SORTED_ENTITIES + void EntityTreeSendThread::preDistributionProcessing() { auto node = _node.toStrongRef(); auto nodeData = static_cast(node->getLinkedData()); @@ -145,6 +147,7 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O std::cout << "adebug traversal complete " << " Q.size = " << _sendQueue.size() << " dt = " << dt << std::endl; // adebug } +#ifndef SEND_SORTED_ENTITIES if (!_sendQueue.empty()) { // print what needs to be sent while (!_sendQueue.empty()) { @@ -157,6 +160,7 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O _entitiesToSend.erase(entity); } } +#endif // SEND_SORTED_ENTITIES // END EXPERIMENTAL DIFFERENTIAL TRAVERSAL OctreeSendThread::traverseTreeAndSendContents(node, nodeData, viewFrustumChanged, isFullScene); @@ -245,7 +249,7 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree float renderAccuracy = calculateRenderAccuracy(_traversal.getCurrentView().getPosition(), cube, _traversal.getCurrentRootSizeScale(), - lodLevelOffset); + _traversal.getCurrentLODOffset()); // Only send entities if they are large enough to see if (renderAccuracy > 0.0f) { @@ -280,7 +284,7 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree float renderAccuracy = calculateRenderAccuracy(_traversal.getCurrentView().getPosition(), cube, _traversal.getCurrentRootSizeScale(), - lodLevelOffset); + _traversal.getCurrentLODOffset()); // Only send entities if they are large enough to see if (renderAccuracy > 0.0f) { @@ -317,7 +321,7 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree float renderAccuracy = calculateRenderAccuracy(_traversal.getCurrentView().getPosition(), cube, _traversal.getCurrentRootSizeScale(), - lodLevelOffset); + _traversal.getCurrentLODOffset()); // Only send entities if they are large enough to see if (renderAccuracy > 0.0f) { @@ -330,7 +334,7 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree float lastRenderAccuracy = calculateRenderAccuracy(_traversal.getCompletedView().getPosition(), cube, _traversal.getCompletedRootSizeScale(), - lodLevelOffset); + _traversal.getCompletedLODOffset()); if (lastRenderAccuracy <= 0.0f) { float priority = _conicalView.computePriority(cube); @@ -352,3 +356,77 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree } } +bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params) { +#ifdef SEND_SORTED_ENTITIES + //auto entityTree = std::static_pointer_cast(_myServer->getOctree()); + if (_sendQueue.empty()) { + return false; + } + if (!_packetData.hasContent()) { + // This is the beginning of a new packet. + // We pack minimal data for this to be accepted as an OctreeElement payload for the root element. + // The Octree header bytes look like this: + // + // 0x00 octalcode for root + // 0x00 colors (1 bit where recipient should call: child->readElementDataFromBuffer()) + // 0xXX childrenInTreeMask (when params.includeExistsBits is true: 1 bit where child is existant) + // 0x00 childrenInBufferMask (1 bit where recipient should call: child->readElementData() recursively) + const uint8_t zeroByte = 0; + _packetData.appendValue(zeroByte); // octalcode + _packetData.appendValue(zeroByte); // colors + if (params.includeExistsBits) { + uint8_t childrenExistBits = 0; + EntityTreeElementPointer root = std::dynamic_pointer_cast(_myServer->getOctree()->getRoot()); + for (int32_t i = 0; i < NUMBER_OF_CHILDREN; ++i) { + if (root->getChildAtIndex(i)) { + childrenExistBits += (1 << i); + } + } + _packetData.appendValue(childrenExistBits); // childrenInTreeMask + } + _packetData.appendValue(zeroByte); // childrenInBufferMask + + // Pack zero for numEntities. + // But before we do: grab current byteOffset so we can come back later + // and update this with the real number. + _numEntities = 0; + _numEntitiesOffset = _packetData.getUncompressedByteOffset(); + _packetData.appendValue(_numEntities); + } + + LevelDetails entitiesLevel = _packetData.startLevel(); + while(!_sendQueue.empty()) { + PrioritizedEntity queuedItem = _sendQueue.top(); + EntityItemPointer entity = queuedItem.getEntity(); + if (entity) { + OctreeElement::AppendState appendEntityState = entity->appendEntityData(&_packetData, params, _extraEncodeData); + + if (appendEntityState != OctreeElement::COMPLETED) { + if (appendEntityState == OctreeElement::PARTIAL) { + ++_numEntities; + } + params.stopReason = EncodeBitstreamParams::DIDNT_FIT; + break; + } + ++_numEntities; + } + _sendQueue.pop(); + } + if (_sendQueue.empty()) { + params.stopReason = EncodeBitstreamParams::FINISHED; + _extraEncodeData->entities.clear(); + } + + if (_numEntities == 0) { + _packetData.discardLevel(entitiesLevel); + return false; + } + _packetData.endLevel(entitiesLevel); + _packetData.updatePriorBytes(_numEntitiesOffset, (const unsigned char*)&_numEntities, sizeof(_numEntities)); + return true; + +#else // SEND_SORTED_ENTITIES + return OctreeSendThread::traverseTreeAndBuildNextPacketPayload(params); +#endif // SEND_SORTED_ENTITIES +} + diff --git a/assignment-client/src/entities/EntityTreeSendThread.h b/assignment-client/src/entities/EntityTreeSendThread.h index 50e7981938..bda73f44ec 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.h +++ b/assignment-client/src/entities/EntityTreeSendThread.h @@ -39,11 +39,17 @@ private: bool addDescendantsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData); void startNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset); + bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params) override; DiffTraversal _traversal; EntityPriorityQueue _sendQueue; std::unordered_set _entitiesToSend; ConicalView _conicalView; // cached optimized view for fast priority calculations + + // packet construction stuff + EntityTreeElementExtraEncodeDataPointer _extraEncodeData { new EntityTreeElementExtraEncodeData() }; + int32_t _numEntitiesOffset { 0 }; + uint16_t _numEntities { 0 }; }; #endif // hifi_EntityTreeSendThread_h diff --git a/libraries/entities/src/DiffTraversal.cpp b/libraries/entities/src/DiffTraversal.cpp index 5f105f3fb5..e8e300081a 100644 --- a/libraries/entities/src/DiffTraversal.cpp +++ b/libraries/entities/src/DiffTraversal.cpp @@ -89,22 +89,15 @@ void DiffTraversal::Waypoint::getNextVisibleElementRepeat( next.intersection = ViewFrustum::OUTSIDE; } -DiffTraversal::DiffTraversal() { - const int32_t MIN_PATH_DEPTH = 16; - _path.reserve(MIN_PATH_DEPTH); -} - void DiffTraversal::Waypoint::getNextVisibleElementDifferential(DiffTraversal::VisibleElement& next, const DiffTraversal::View& view, const DiffTraversal::View& lastView) { if (_nextIndex == -1) { // root case is special ++_nextIndex; EntityTreeElementPointer element = _weakElement.lock(); - if (element->getLastChangedContent() > lastView.startTime) { - next.element = element; - next.intersection = ViewFrustum::INTERSECT; - return; - } + next.element = element; + next.intersection = ViewFrustum::INTERSECT; + return; } if (_nextIndex < NUMBER_OF_CHILDREN) { EntityTreeElementPointer element = _weakElement.lock(); @@ -149,6 +142,11 @@ void DiffTraversal::Waypoint::getNextVisibleElementDifferential(DiffTraversal::V next.intersection = ViewFrustum::OUTSIDE; } +DiffTraversal::DiffTraversal() { + const int32_t MIN_PATH_DEPTH = 16; + _path.reserve(MIN_PATH_DEPTH); +} + DiffTraversal::Type DiffTraversal::prepareNewTraversal( const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset) { assert(root); @@ -246,7 +244,7 @@ std::ostream& operator<<(std::ostream& s, const DiffTraversal& traversal) { for (size_t i = 0; i < traversal._path.size(); ++i) { s << (int32_t)(traversal._path[i].getNextIndex()); if (i < traversal._path.size() - 1) { - s << "-->"; + s << ":"; } } return s; diff --git a/libraries/entities/src/DiffTraversal.h b/libraries/entities/src/DiffTraversal.h index 87bd83b70f..e849b4fef3 100644 --- a/libraries/entities/src/DiffTraversal.h +++ b/libraries/entities/src/DiffTraversal.h @@ -66,6 +66,8 @@ public: float getCurrentRootSizeScale() const { return _currentView.rootSizeScale; } float getCompletedRootSizeScale() const { return _completedView.rootSizeScale; } + float getCurrentLODOffset() const { return _currentView.lodLevelOffset; } + float getCompletedLODOffset() const { return _completedView.lodLevelOffset; } uint64_t getStartOfCompletedTraversal() const { return _completedView.startTime; } bool finished() const { return _path.empty(); } From b85a5507e05f04d6f4b54e32ea0eb47f8c1830df Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 24 Aug 2017 12:19:27 -0700 Subject: [PATCH 47/88] time budget and raw pointer key for entitiesToSend --- .../src/entities/EntityPriorityQueue.h | 4 ++- .../src/entities/EntityTreeSendThread.cpp | 32 +++++++++++-------- .../src/entities/EntityTreeSendThread.h | 2 +- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/assignment-client/src/entities/EntityPriorityQueue.h b/assignment-client/src/entities/EntityPriorityQueue.h index 215c5262bf..aadaab5614 100644 --- a/assignment-client/src/entities/EntityPriorityQueue.h +++ b/assignment-client/src/entities/EntityPriorityQueue.h @@ -39,9 +39,10 @@ private: // PrioritizedEntity is a placeholder in a sorted queue. class PrioritizedEntity { public: - PrioritizedEntity(EntityItemPointer entity, float priority) : _weakEntity(entity), _priority(priority) { } + PrioritizedEntity(EntityItemPointer entity, float priority) : _weakEntity(entity), _rawEntityPointer(entity.get()), _priority(priority) {} float updatePriority(const ConicalView& view); EntityItemPointer getEntity() const { return _weakEntity.lock(); } + EntityItem* getRawEntityPointer() const { return _rawEntityPointer; } float getPriority() const { return _priority; } class Compare { @@ -52,6 +53,7 @@ public: private: EntityItemWeakPointer _weakEntity; + EntityItem* _rawEntityPointer; float _priority; }; diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index 8e5cdfed13..b837812d3d 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -122,14 +122,14 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O // Only send entities if they are large enough to see if (renderAccuracy > 0.0f) { _sendQueue.push(PrioritizedEntity(entity, priority)); - _entitiesToSend.insert(entity); + _entitiesToSend.insert(entity.get()); } } } } else { const float WHEN_IN_DOUBT_PRIORITY = 1.0f; _sendQueue.push(PrioritizedEntity(entity, WHEN_IN_DOUBT_PRIORITY)); - _entitiesToSend.insert(entity); + _entitiesToSend.insert(entity.get()); } } } @@ -140,7 +140,11 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O if (!_traversal.finished()) { uint64_t startTime = usecTimestampNow(); - const uint64_t TIME_BUDGET = 100000; // usec +#ifdef DEBUG + const uint64_t TIME_BUDGET = 400; // usec +#else + const uint64_t TIME_BUDGET = 200; // usec +#endif _traversal.traverse(TIME_BUDGET); uint64_t dt = usecTimestampNow() - startTime; @@ -157,7 +161,7 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O std::cout << "adebug send '" << entity->getName().toStdString() << "'" << " : " << entry.getPriority() << std::endl; // adebug } _sendQueue.pop(); - _entitiesToSend.erase(entity); + _entitiesToSend.erase(entry.getRawEntityPointer()); } } #endif // SEND_SORTED_ENTITIES @@ -235,7 +239,7 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree _traversal.setScanCallback([&] (DiffTraversal::VisibleElement& next) { next.element->forEachEntity([&](EntityItemPointer entity) { // Bail early if we've already checked this entity this frame - if (_entitiesToSend.find(entity) != _entitiesToSend.end()) { + if (_entitiesToSend.find(entity.get()) != _entitiesToSend.end()) { return; } bool success = false; @@ -255,13 +259,13 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree if (renderAccuracy > 0.0f) { float priority = _conicalView.computePriority(cube); _sendQueue.push(PrioritizedEntity(entity, priority)); - _entitiesToSend.insert(entity); + _entitiesToSend.insert(entity.get()); } } } else { const float WHEN_IN_DOUBT_PRIORITY = 1.0f; _sendQueue.push(PrioritizedEntity(entity, WHEN_IN_DOUBT_PRIORITY)); - _entitiesToSend.insert(entity); + _entitiesToSend.insert(entity.get()); } }); }); @@ -272,7 +276,7 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree uint64_t timestamp = _traversal.getStartOfCompletedTraversal(); next.element->forEachEntity([&](EntityItemPointer entity) { // Bail early if we've already checked this entity this frame - if (_entitiesToSend.find(entity) != _entitiesToSend.end()) { + if (_entitiesToSend.find(entity.get()) != _entitiesToSend.end()) { return; } if (entity->getLastEdited() > timestamp) { @@ -290,13 +294,13 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree if (renderAccuracy > 0.0f) { float priority = _conicalView.computePriority(cube); _sendQueue.push(PrioritizedEntity(entity, priority)); - _entitiesToSend.insert(entity); + _entitiesToSend.insert(entity.get()); } } } else { const float WHEN_IN_DOUBT_PRIORITY = 1.0f; _sendQueue.push(PrioritizedEntity(entity, WHEN_IN_DOUBT_PRIORITY)); - _entitiesToSend.insert(entity); + _entitiesToSend.insert(entity.get()); } } }); @@ -310,7 +314,7 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree if (next.element->getLastChangedContent() > timestamp || next.intersection != ViewFrustum::INSIDE) { next.element->forEachEntity([&](EntityItemPointer entity) { // Bail early if we've already checked this entity this frame - if (_entitiesToSend.find(entity) != _entitiesToSend.end()) { + if (_entitiesToSend.find(entity.get()) != _entitiesToSend.end()) { return; } bool success = false; @@ -328,7 +332,7 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree if (entity->getLastEdited() > timestamp || !_traversal.getCompletedView().cubeIntersectsKeyhole(cube)) { float priority = _conicalView.computePriority(cube); _sendQueue.push(PrioritizedEntity(entity, priority)); - _entitiesToSend.insert(entity); + _entitiesToSend.insert(entity.get()); } else { // If this entity was skipped last time because it was too small, we still need to send it float lastRenderAccuracy = calculateRenderAccuracy(_traversal.getCompletedView().getPosition(), @@ -339,7 +343,7 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree if (lastRenderAccuracy <= 0.0f) { float priority = _conicalView.computePriority(cube); _sendQueue.push(PrioritizedEntity(entity, priority)); - _entitiesToSend.insert(entity); + _entitiesToSend.insert(entity.get()); } } } @@ -347,7 +351,7 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree } else { const float WHEN_IN_DOUBT_PRIORITY = 1.0f; _sendQueue.push(PrioritizedEntity(entity, WHEN_IN_DOUBT_PRIORITY)); - _entitiesToSend.insert(entity); + _entitiesToSend.insert(entity.get()); } }); } diff --git a/assignment-client/src/entities/EntityTreeSendThread.h b/assignment-client/src/entities/EntityTreeSendThread.h index bda73f44ec..707a20dc94 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.h +++ b/assignment-client/src/entities/EntityTreeSendThread.h @@ -43,7 +43,7 @@ private: DiffTraversal _traversal; EntityPriorityQueue _sendQueue; - std::unordered_set _entitiesToSend; + std::unordered_set _entitiesToSend; ConicalView _conicalView; // cached optimized view for fast priority calculations // packet construction stuff From cbf82a6f2cd2f74a4f2be47c9acfe01273a76bd6 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 24 Aug 2017 13:32:44 -0700 Subject: [PATCH 48/88] fix timeout for physics check --- interface/src/Application.cpp | 6 +----- interface/src/Application.h | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 85172fc73f..6762217485 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4859,13 +4859,9 @@ void Application::update(float deltaTime) { // we haven't yet enabled physics. we wait until we think we have all the collision information // for nearby entities before starting bullet up. quint64 now = usecTimestampNow(); - bool timeout = false; const int PHYSICS_CHECK_TIMEOUT = 2 * USECS_PER_SECOND; - if (_lastPhysicsCheckTime > 0 && now - _lastPhysicsCheckTime > PHYSICS_CHECK_TIMEOUT) { - timeout = true; - } - if (timeout || _fullSceneReceivedCounter > _fullSceneCounterAtLastPhysicsCheck) { + if (now - _lastPhysicsCheckTime > PHYSICS_CHECK_TIMEOUT || _fullSceneReceivedCounter > _fullSceneCounterAtLastPhysicsCheck) { // we've received a new full-scene octree stats packet, or it's been long enough to try again anyway _lastPhysicsCheckTime = now; _fullSceneCounterAtLastPhysicsCheck = _fullSceneReceivedCounter; diff --git a/interface/src/Application.h b/interface/src/Application.h index 74e84ae92c..93f7a4ab79 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -659,7 +659,7 @@ private: uint32_t _fullSceneCounterAtLastPhysicsCheck { 0 }; // _fullSceneReceivedCounter last time we checked physics ready uint32_t _nearbyEntitiesCountAtLastPhysicsCheck { 0 }; // how many in-range entities last time we checked physics ready uint32_t _nearbyEntitiesStabilityCount { 0 }; // how many times has _nearbyEntitiesCountAtLastPhysicsCheck been the same - quint64 _lastPhysicsCheckTime { 0 }; // when did we last check to see if physics was ready + quint64 _lastPhysicsCheckTime { usecTimestampNow() }; // when did we last check to see if physics was ready bool _keyboardDeviceHasFocus { true }; From d54fa205fb20f090e2851bca2ad3dd709c2f0846 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 24 Aug 2017 17:07:09 -0700 Subject: [PATCH 49/88] namechange entitiesToSend --> entitiesInQueue --- .../src/entities/EntityTreeSendThread.cpp | 28 +++++++++---------- .../src/entities/EntityTreeSendThread.h | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index b837812d3d..08fcc39a0f 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -101,7 +101,7 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O if (!_sendQueue.empty()) { EntityPriorityQueue prevSendQueue; _sendQueue.swap(prevSendQueue); - _entitiesToSend.clear(); + _entitiesInQueue.clear(); // Re-add elements from previous traversal if they still need to be sent while (!prevSendQueue.empty()) { EntityItemPointer entity = prevSendQueue.top().getEntity(); @@ -122,14 +122,14 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O // Only send entities if they are large enough to see if (renderAccuracy > 0.0f) { _sendQueue.push(PrioritizedEntity(entity, priority)); - _entitiesToSend.insert(entity.get()); + _entitiesInQueue.insert(entity.get()); } } } } else { const float WHEN_IN_DOUBT_PRIORITY = 1.0f; _sendQueue.push(PrioritizedEntity(entity, WHEN_IN_DOUBT_PRIORITY)); - _entitiesToSend.insert(entity.get()); + _entitiesInQueue.insert(entity.get()); } } } @@ -161,7 +161,7 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O std::cout << "adebug send '" << entity->getName().toStdString() << "'" << " : " << entry.getPriority() << std::endl; // adebug } _sendQueue.pop(); - _entitiesToSend.erase(entry.getRawEntityPointer()); + _entitiesInQueue.erase(entry.getRawEntityPointer()); } } #endif // SEND_SORTED_ENTITIES @@ -239,7 +239,7 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree _traversal.setScanCallback([&] (DiffTraversal::VisibleElement& next) { next.element->forEachEntity([&](EntityItemPointer entity) { // Bail early if we've already checked this entity this frame - if (_entitiesToSend.find(entity.get()) != _entitiesToSend.end()) { + if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) { return; } bool success = false; @@ -259,13 +259,13 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree if (renderAccuracy > 0.0f) { float priority = _conicalView.computePriority(cube); _sendQueue.push(PrioritizedEntity(entity, priority)); - _entitiesToSend.insert(entity.get()); + _entitiesInQueue.insert(entity.get()); } } } else { const float WHEN_IN_DOUBT_PRIORITY = 1.0f; _sendQueue.push(PrioritizedEntity(entity, WHEN_IN_DOUBT_PRIORITY)); - _entitiesToSend.insert(entity.get()); + _entitiesInQueue.insert(entity.get()); } }); }); @@ -276,7 +276,7 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree uint64_t timestamp = _traversal.getStartOfCompletedTraversal(); next.element->forEachEntity([&](EntityItemPointer entity) { // Bail early if we've already checked this entity this frame - if (_entitiesToSend.find(entity.get()) != _entitiesToSend.end()) { + if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) { return; } if (entity->getLastEdited() > timestamp) { @@ -294,13 +294,13 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree if (renderAccuracy > 0.0f) { float priority = _conicalView.computePriority(cube); _sendQueue.push(PrioritizedEntity(entity, priority)); - _entitiesToSend.insert(entity.get()); + _entitiesInQueue.insert(entity.get()); } } } else { const float WHEN_IN_DOUBT_PRIORITY = 1.0f; _sendQueue.push(PrioritizedEntity(entity, WHEN_IN_DOUBT_PRIORITY)); - _entitiesToSend.insert(entity.get()); + _entitiesInQueue.insert(entity.get()); } } }); @@ -314,7 +314,7 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree if (next.element->getLastChangedContent() > timestamp || next.intersection != ViewFrustum::INSIDE) { next.element->forEachEntity([&](EntityItemPointer entity) { // Bail early if we've already checked this entity this frame - if (_entitiesToSend.find(entity.get()) != _entitiesToSend.end()) { + if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) { return; } bool success = false; @@ -332,7 +332,7 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree if (entity->getLastEdited() > timestamp || !_traversal.getCompletedView().cubeIntersectsKeyhole(cube)) { float priority = _conicalView.computePriority(cube); _sendQueue.push(PrioritizedEntity(entity, priority)); - _entitiesToSend.insert(entity.get()); + _entitiesInQueue.insert(entity.get()); } else { // If this entity was skipped last time because it was too small, we still need to send it float lastRenderAccuracy = calculateRenderAccuracy(_traversal.getCompletedView().getPosition(), @@ -343,7 +343,7 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree if (lastRenderAccuracy <= 0.0f) { float priority = _conicalView.computePriority(cube); _sendQueue.push(PrioritizedEntity(entity, priority)); - _entitiesToSend.insert(entity.get()); + _entitiesInQueue.insert(entity.get()); } } } @@ -351,7 +351,7 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree } else { const float WHEN_IN_DOUBT_PRIORITY = 1.0f; _sendQueue.push(PrioritizedEntity(entity, WHEN_IN_DOUBT_PRIORITY)); - _entitiesToSend.insert(entity.get()); + _entitiesInQueue.insert(entity.get()); } }); } diff --git a/assignment-client/src/entities/EntityTreeSendThread.h b/assignment-client/src/entities/EntityTreeSendThread.h index 707a20dc94..5ea90005b9 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.h +++ b/assignment-client/src/entities/EntityTreeSendThread.h @@ -43,7 +43,7 @@ private: DiffTraversal _traversal; EntityPriorityQueue _sendQueue; - std::unordered_set _entitiesToSend; + std::unordered_set _entitiesInQueue; ConicalView _conicalView; // cached optimized view for fast priority calculations // packet construction stuff From b788273f47f72e24b19ae63ea17084e33e20c76b Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 24 Aug 2017 17:33:51 -0700 Subject: [PATCH 50/88] fix repeat First traversals mid-First-traversal --- assignment-client/src/entities/EntityTreeSendThread.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index 08fcc39a0f..bdeb3bdbca 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -17,7 +17,7 @@ #include "EntityServer.h" -//#define SEND_SORTED_ENTITIES +#define SEND_SORTED_ENTITIES void EntityTreeSendThread::preDistributionProcessing() { auto node = _node.toStrongRef(); @@ -89,7 +89,7 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O if (nodeData->getUsesFrustum()) { // DEBUG HACK: trigger traversal (Repeat) every so often const uint64_t TRAVERSE_AGAIN_PERIOD = 4 * USECS_PER_SECOND; - bool repeatTraversal = usecTimestampNow() > _traversal.getStartOfCompletedTraversal() + TRAVERSE_AGAIN_PERIOD; + bool repeatTraversal = _traversal.finished() && usecTimestampNow() > _traversal.getStartOfCompletedTraversal() + TRAVERSE_AGAIN_PERIOD; if (viewFrustumChanged || repeatTraversal) { ViewFrustum viewFrustum; nodeData->copyCurrentViewFrustum(viewFrustum); @@ -272,8 +272,8 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree break; case DiffTraversal::Repeat: _traversal.setScanCallback([&] (DiffTraversal::VisibleElement& next) { - if (next.element->getLastChangedContent() > _traversal.getStartOfCompletedTraversal()) { - uint64_t timestamp = _traversal.getStartOfCompletedTraversal(); + uint64_t timestamp = _traversal.getStartOfCompletedTraversal(); + if (next.element->getLastChangedContent() > timestamp) { next.element->forEachEntity([&](EntityItemPointer entity) { // Bail early if we've already checked this entity this frame if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) { From 535d84abc75902d41748ba6d861a58a85ce22e5b Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 24 Aug 2017 18:51:03 -0700 Subject: [PATCH 51/88] cleanup and speed up repeat traversals --- .../src/entities/EntityPriorityQueue.cpp | 6 ++-- .../src/entities/EntityPriorityQueue.h | 2 ++ .../src/entities/EntityTreeSendThread.cpp | 33 ++++++++++--------- 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/assignment-client/src/entities/EntityPriorityQueue.cpp b/assignment-client/src/entities/EntityPriorityQueue.cpp index 77b46afa24..3755e6a5c7 100644 --- a/assignment-client/src/entities/EntityPriorityQueue.cpp +++ b/assignment-client/src/entities/EntityPriorityQueue.cpp @@ -11,7 +11,7 @@ #include "EntityPriorityQueue.h" -const float DO_NOT_SEND = -1.0e-6f; +const float PrioritizedEntity::DO_NOT_SEND = -1.0e-6f; void ConicalView::set(const ViewFrustum& viewFrustum) { // The ConicalView has two parts: a central sphere (same as ViewFrustum) and a circular cone that bounds the frustum part. @@ -47,7 +47,7 @@ float ConicalView::computePriority(const AACube& cube) const { const float AVOID_DIVIDE_BY_ZERO = 0.001f; return r / (d + AVOID_DIVIDE_BY_ZERO); } - return DO_NOT_SEND; + return PrioritizedEntity::DO_NOT_SEND; } // static @@ -68,7 +68,7 @@ float PrioritizedEntity::updatePriority(const ConicalView& conicalView) { if (entity) { _priority = conicalView.computePriority(entity); } else { - _priority = DO_NOT_SEND; + _priority = PrioritizedEntity::DO_NOT_SEND; } return _priority; } diff --git a/assignment-client/src/entities/EntityPriorityQueue.h b/assignment-client/src/entities/EntityPriorityQueue.h index aadaab5614..10db22d695 100644 --- a/assignment-client/src/entities/EntityPriorityQueue.h +++ b/assignment-client/src/entities/EntityPriorityQueue.h @@ -39,6 +39,8 @@ private: // PrioritizedEntity is a placeholder in a sorted queue. class PrioritizedEntity { public: + static const float DO_NOT_SEND; + PrioritizedEntity(EntityItemPointer entity, float priority) : _weakEntity(entity), _rawEntityPointer(entity.get()), _priority(priority) {} float updatePriority(const ConicalView& view); EntityItemPointer getEntity() const { return _weakEntity.lock(); } diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index bdeb3bdbca..851d0566ac 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -85,20 +85,17 @@ void EntityTreeSendThread::preDistributionProcessing() { void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged, bool isFullScene) { - // BEGIN EXPERIMENTAL DIFFERENTIAL TRAVERSAL if (nodeData->getUsesFrustum()) { - // DEBUG HACK: trigger traversal (Repeat) every so often - const uint64_t TRAVERSE_AGAIN_PERIOD = 4 * USECS_PER_SECOND; - bool repeatTraversal = _traversal.finished() && usecTimestampNow() > _traversal.getStartOfCompletedTraversal() + TRAVERSE_AGAIN_PERIOD; - if (viewFrustumChanged || repeatTraversal) { + if (viewFrustumChanged || _traversal.finished()) { ViewFrustum viewFrustum; nodeData->copyCurrentViewFrustum(viewFrustum); EntityTreeElementPointer root = std::dynamic_pointer_cast(_myServer->getOctree()->getRoot()); int32_t lodLevelOffset = nodeData->getBoundaryLevelAdjust() + (viewFrustumChanged ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST); startNewTraversal(viewFrustum, root, lodLevelOffset); - // If the previous traversal didn't finish, we'll need to resort the entities still in _sendQueue after calling traverse - if (!_sendQueue.empty()) { + // When the viewFrustum changed the sort order may be incorrect, so we re-sort + // and also use the opportunity to cull anything no longer in view + if (viewFrustumChanged && !_sendQueue.empty()) { EntityPriorityQueue prevSendQueue; _sendQueue.swap(prevSendQueue); _entitiesInQueue.clear(); @@ -111,9 +108,8 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O AACube cube = entity->getQueryAACube(success); if (success) { if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { - const float DO_NOT_SEND = -1.0e-6f; float priority = _conicalView.computePriority(cube); - if (priority != DO_NOT_SEND) { + if (priority != PrioritizedEntity::DO_NOT_SEND) { float renderAccuracy = calculateRenderAccuracy(_traversal.getCurrentView().getPosition(), cube, _traversal.getCurrentRootSizeScale(), @@ -165,7 +161,6 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O } } #endif // SEND_SORTED_ENTITIES - // END EXPERIMENTAL DIFFERENTIAL TRAVERSAL OctreeSendThread::traverseTreeAndSendContents(node, nodeData, viewFrustumChanged, isFullScene); } @@ -250,6 +245,10 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree // larger octree cell because of its position (for example if it crosses the boundary of a cell it // pops to the next higher cell. So we want to check to see that the entity is large enough to be seen // before we consider including it. + // + // TODO: compare priority against a threshold rather than bother with + // calculateRenderAccuracy(). Would need to replace all calculateRenderAccuracy() + // stuff everywhere with threshold in one sweep. float renderAccuracy = calculateRenderAccuracy(_traversal.getCurrentView().getPosition(), cube, _traversal.getCurrentRootSizeScale(), @@ -272,14 +271,14 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree break; case DiffTraversal::Repeat: _traversal.setScanCallback([&] (DiffTraversal::VisibleElement& next) { - uint64_t timestamp = _traversal.getStartOfCompletedTraversal(); - if (next.element->getLastChangedContent() > timestamp) { + uint64_t startOfCompletedTraversal = _traversal.getStartOfCompletedTraversal(); + if (next.element->getLastChangedContent() > startOfCompletedTraversal) { next.element->forEachEntity([&](EntityItemPointer entity) { // Bail early if we've already checked this entity this frame if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) { return; } - if (entity->getLastEdited() > timestamp) { + if (entity->getLastEdited() > startOfCompletedTraversal) { bool success = false; AACube cube = entity->getQueryAACube(success); if (success) { @@ -310,8 +309,9 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree case DiffTraversal::Differential: _traversal.setScanCallback([&] (DiffTraversal::VisibleElement& next) { // NOTE: for Differential case: next.intersection is against completedView not currentView - uint64_t timestamp = _traversal.getStartOfCompletedTraversal(); - if (next.element->getLastChangedContent() > timestamp || next.intersection != ViewFrustum::INSIDE) { + uint64_t startOfCompletedTraversal = _traversal.getStartOfCompletedTraversal(); + if (next.element->getLastChangedContent() > startOfCompletedTraversal || + next.intersection != ViewFrustum::INSIDE) { next.element->forEachEntity([&](EntityItemPointer entity) { // Bail early if we've already checked this entity this frame if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) { @@ -329,7 +329,8 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree // Only send entities if they are large enough to see if (renderAccuracy > 0.0f) { - if (entity->getLastEdited() > timestamp || !_traversal.getCompletedView().cubeIntersectsKeyhole(cube)) { + if (entity->getLastEdited() > startOfCompletedTraversal || + !_traversal.getCompletedView().cubeIntersectsKeyhole(cube)) { float priority = _conicalView.computePriority(cube); _sendQueue.push(PrioritizedEntity(entity, priority)); _entitiesInQueue.insert(entity.get()); From 6edba6d545b4f774d095de3a33e7f75274e4d4dc Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 25 Aug 2017 11:04:24 -0700 Subject: [PATCH 52/88] erase in _entitiesInQueue when pop _sendQueue --- assignment-client/src/entities/EntityTreeSendThread.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index 851d0566ac..0d71aa9619 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -143,8 +143,10 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O #endif _traversal.traverse(TIME_BUDGET); - uint64_t dt = usecTimestampNow() - startTime; - std::cout << "adebug traversal complete " << " Q.size = " << _sendQueue.size() << " dt = " << dt << std::endl; // adebug + if (_sendQueue.size() > 0) { + uint64_t dt = usecTimestampNow() - startTime; + std::cout << "adebug traversal complete " << " Q.size = " << _sendQueue.size() << " dt = " << dt << std::endl; // adebug + } } #ifndef SEND_SORTED_ENTITIES @@ -416,8 +418,10 @@ bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstream ++_numEntities; } _sendQueue.pop(); + _entitiesInQueue.erase(entity.get()); } if (_sendQueue.empty()) { + assert(_entitiesInQueue.empty()); params.stopReason = EncodeBitstreamParams::FINISHED; _extraEncodeData->entities.clear(); } From 247764b67964d1fc7b6a89a4e12ba92a24dd9b37 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Mon, 28 Aug 2017 13:47:27 -0700 Subject: [PATCH 53/88] add total entity packets in stat (kbps) --- interface/resources/qml/Stats.qml | 4 ++++ interface/src/ui/Stats.cpp | 3 +++ interface/src/ui/Stats.h | 2 ++ 3 files changed, 9 insertions(+) diff --git a/interface/resources/qml/Stats.qml b/interface/resources/qml/Stats.qml index 119f24c71f..159a696e5f 100644 --- a/interface/resources/qml/Stats.qml +++ b/interface/resources/qml/Stats.qml @@ -206,6 +206,10 @@ Item { text: "Audio Codec: " + root.audioCodec + " Noise Gate: " + root.audioNoiseGate; } + StatText { + visible: root.expanded; + text: "Entity Mixer In: " + root.entityPacketsInKbps + " kbps"; + } StatText { visible: root.expanded; text: "Downloads: " + root.downloads + "/" + root.downloadLimit + diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 8e3636dd7e..767e499503 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -180,10 +180,12 @@ void Stats::updateStats(bool force) { int totalPingOctree = 0; int octreeServerCount = 0; int pingOctreeMax = 0; + int totalEntityKbps = 0; nodeList->eachNode([&](const SharedNodePointer& node) { // TODO: this should also support entities if (node->getType() == NodeType::EntityServer) { totalPingOctree += node->getPingMs(); + totalEntityKbps += node->getInboundBandwidth(); octreeServerCount++; if (pingOctreeMax < node->getPingMs()) { pingOctreeMax = node->getPingMs(); @@ -248,6 +250,7 @@ void Stats::updateStats(bool force) { STAT_UPDATE(audioCodec, audioClient->getSelectedAudioFormat()); STAT_UPDATE(audioNoiseGate, audioClient->getNoiseGateOpen() ? "Open" : "Closed"); + STAT_UPDATE(entityPacketsInKbps, octreeServerCount ? totalEntityKbps / octreeServerCount : -1); auto loadingRequests = ResourceCache::getLoadingRequests(); STAT_UPDATE(downloads, loadingRequests.size()); diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h index 74d2589c35..b3c920d4ef 100644 --- a/interface/src/ui/Stats.h +++ b/interface/src/ui/Stats.h @@ -85,6 +85,7 @@ class Stats : public QQuickItem { STATS_PROPERTY(int, audioPacketLoss, 0) STATS_PROPERTY(QString, audioCodec, QString()) STATS_PROPERTY(QString, audioNoiseGate, QString()) + STATS_PROPERTY(int, entityPacketsInKbps, 0) STATS_PROPERTY(int, downloads, 0) STATS_PROPERTY(int, downloadLimit, 0) @@ -212,6 +213,7 @@ signals: void audioPacketLossChanged(); void audioCodecChanged(); void audioNoiseGateChanged(); + void entityPacketsInKbpsChanged(); void downloadsChanged(); void downloadLimitChanged(); From 6c066605cd5fbc0b90d557d595e8fc2209db2ec8 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Tue, 29 Aug 2017 15:35:36 -0700 Subject: [PATCH 54/88] add state to entity tree send thread --- .../src/entities/EntityTreeSendThread.cpp | 76 ++++++++++++------- .../src/entities/EntityTreeSendThread.h | 7 +- libraries/entities/src/EntityTree.cpp | 6 ++ libraries/entities/src/EntityTree.h | 1 + 4 files changed, 60 insertions(+), 30 deletions(-) diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index 0d71aa9619..e71331177b 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -19,6 +19,12 @@ #define SEND_SORTED_ENTITIES +EntityTreeSendThread::EntityTreeSendThread(OctreeServer* myServer, const SharedNodePointer& node) : + OctreeSendThread(myServer, node) +{ + connect(std::static_pointer_cast(myServer->getOctree()).get(), &EntityTree::deletingEntityPointer, this, &EntityTreeSendThread::deletingEntityPointer, Qt::QueuedConnection); +} + void EntityTreeSendThread::preDistributionProcessing() { auto node = _node.toStrongRef(); auto nodeData = static_cast(node->getLinkedData()); @@ -151,12 +157,14 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O #ifndef SEND_SORTED_ENTITIES if (!_sendQueue.empty()) { + uint64_t sendTime = usecTimestampNow(); // print what needs to be sent while (!_sendQueue.empty()) { PrioritizedEntity entry = _sendQueue.top(); EntityItemPointer entity = entry.getEntity(); if (entity) { std::cout << "adebug send '" << entity->getName().toStdString() << "'" << " : " << entry.getPriority() << std::endl; // adebug + _knownState[entity] = sendTime; } _sendQueue.pop(); _entitiesInQueue.erase(entry.getRawEntityPointer()); @@ -234,6 +242,8 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree switch (type) { case DiffTraversal::First: _traversal.setScanCallback([&] (DiffTraversal::VisibleElement& next) { + // When we get to a First traversal, clear the _knownState + _knownState.clear(); next.element->forEachEntity([&](EntityItemPointer entity) { // Bail early if we've already checked this entity this frame if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) { @@ -280,7 +290,8 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) { return; } - if (entity->getLastEdited() > startOfCompletedTraversal) { + if (_knownState.find(entity.get()) == _knownState.end() || + (_knownState.find(entity.get()) != _knownState.end() && entity->getLastEdited() > _knownState[entity.get()])) { bool success = false; AACube cube = entity->getQueryAACube(success); if (success) { @@ -319,42 +330,44 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) { return; } - bool success = false; - AACube cube = entity->getQueryAACube(success); - if (success) { - if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { - // See the DiffTraversal::First case for an explanation of the "entity is too small" check - float renderAccuracy = calculateRenderAccuracy(_traversal.getCurrentView().getPosition(), - cube, - _traversal.getCurrentRootSizeScale(), - _traversal.getCurrentLODOffset()); + if (_knownState.find(entity.get()) == _knownState.end() || + (_knownState.find(entity.get()) != _knownState.end() && entity->getLastEdited() > _knownState[entity.get()])) { + bool success = false; + AACube cube = entity->getQueryAACube(success); + if (success) { + if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { + // See the DiffTraversal::First case for an explanation of the "entity is too small" check + float renderAccuracy = calculateRenderAccuracy(_traversal.getCurrentView().getPosition(), + cube, + _traversal.getCurrentRootSizeScale(), + _traversal.getCurrentLODOffset()); - // Only send entities if they are large enough to see - if (renderAccuracy > 0.0f) { - if (entity->getLastEdited() > startOfCompletedTraversal || - !_traversal.getCompletedView().cubeIntersectsKeyhole(cube)) { - float priority = _conicalView.computePriority(cube); - _sendQueue.push(PrioritizedEntity(entity, priority)); - _entitiesInQueue.insert(entity.get()); - } else { - // If this entity was skipped last time because it was too small, we still need to send it - float lastRenderAccuracy = calculateRenderAccuracy(_traversal.getCompletedView().getPosition(), - cube, - _traversal.getCompletedRootSizeScale(), - _traversal.getCompletedLODOffset()); - - if (lastRenderAccuracy <= 0.0f) { + // Only send entities if they are large enough to see + if (renderAccuracy > 0.0f) { + if (!_traversal.getCompletedView().cubeIntersectsKeyhole(cube)) { float priority = _conicalView.computePriority(cube); _sendQueue.push(PrioritizedEntity(entity, priority)); _entitiesInQueue.insert(entity.get()); + } else { + // If this entity was skipped last time because it was too small, we still need to send it + float lastRenderAccuracy = calculateRenderAccuracy(_traversal.getCompletedView().getPosition(), + cube, + _traversal.getCompletedRootSizeScale(), + _traversal.getCompletedLODOffset()); + + if (lastRenderAccuracy <= 0.0f) { + float priority = _conicalView.computePriority(cube); + _sendQueue.push(PrioritizedEntity(entity, priority)); + _entitiesInQueue.insert(entity.get()); + } } } } + } else { + const float WHEN_IN_DOUBT_PRIORITY = 1.0f; + _sendQueue.push(PrioritizedEntity(entity, WHEN_IN_DOUBT_PRIORITY)); + _entitiesInQueue.insert(entity.get()); } - } else { - const float WHEN_IN_DOUBT_PRIORITY = 1.0f; - _sendQueue.push(PrioritizedEntity(entity, WHEN_IN_DOUBT_PRIORITY)); - _entitiesInQueue.insert(entity.get()); } }); } @@ -402,11 +415,13 @@ bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstream } LevelDetails entitiesLevel = _packetData.startLevel(); + uint64_t sendTime = usecTimestampNow(); while(!_sendQueue.empty()) { PrioritizedEntity queuedItem = _sendQueue.top(); EntityItemPointer entity = queuedItem.getEntity(); if (entity) { OctreeElement::AppendState appendEntityState = entity->appendEntityData(&_packetData, params, _extraEncodeData); + _knownState[entity.get()] = sendTime; if (appendEntityState != OctreeElement::COMPLETED) { if (appendEntityState == OctreeElement::PARTIAL) { @@ -439,3 +454,6 @@ bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstream #endif // SEND_SORTED_ENTITIES } +void EntityTreeSendThread::deletingEntityPointer(EntityItem* entity) { + _knownState.erase(entity); +} \ No newline at end of file diff --git a/assignment-client/src/entities/EntityTreeSendThread.h b/assignment-client/src/entities/EntityTreeSendThread.h index 5ea90005b9..db59d97e6f 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.h +++ b/assignment-client/src/entities/EntityTreeSendThread.h @@ -24,9 +24,10 @@ class EntityNodeData; class EntityItem; class EntityTreeSendThread : public OctreeSendThread { + Q_OBJECT public: - EntityTreeSendThread(OctreeServer* myServer, const SharedNodePointer& node) : OctreeSendThread(myServer, node) { } + EntityTreeSendThread(OctreeServer* myServer, const SharedNodePointer& node); protected: void preDistributionProcessing() override; @@ -44,12 +45,16 @@ private: DiffTraversal _traversal; EntityPriorityQueue _sendQueue; std::unordered_set _entitiesInQueue; + std::unordered_map _knownState; ConicalView _conicalView; // cached optimized view for fast priority calculations // packet construction stuff EntityTreeElementExtraEncodeDataPointer _extraEncodeData { new EntityTreeElementExtraEncodeData() }; int32_t _numEntitiesOffset { 0 }; uint16_t _numEntities { 0 }; + +private slots: + void deletingEntityPointer(EntityItem* entity); }; #endif // hifi_EntityTreeSendThread_h diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 518d3bd883..ebf479574b 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -557,6 +557,7 @@ void EntityTree::deleteEntity(const EntityItemID& entityID, bool force, bool ign unhookChildAvatar(entityID); emit deletingEntity(entityID); + emit deletingEntityPointer(existingEntity.get()); // NOTE: callers must lock the tree before using this method DeleteEntityOperator theOperator(getThisPointer(), entityID); @@ -565,6 +566,10 @@ void EntityTree::deleteEntity(const EntityItemID& entityID, bool force, bool ign auto descendantID = descendant->getID(); theOperator.addEntityIDToDeleteList(descendantID); emit deletingEntity(descendantID); + EntityItemPointer descendantEntity = std::static_pointer_cast(descendant); + if (descendantEntity) { + emit deletingEntityPointer(descendantEntity.get()); + } }); recurseTreeWithOperator(&theOperator); @@ -614,6 +619,7 @@ void EntityTree::deleteEntities(QSet entityIDs, bool force, bool i unhookChildAvatar(entityID); theOperator.addEntityIDToDeleteList(entityID); emit deletingEntity(entityID); + emit deletingEntityPointer(existingEntity.get()); } if (theOperator.getEntities().size() > 0) { diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 17dda32b53..b4155f474b 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -269,6 +269,7 @@ public: signals: void deletingEntity(const EntityItemID& entityID); + void deletingEntityPointer(EntityItem* entityID); void addingEntity(const EntityItemID& entityID); void entityScriptChanging(const EntityItemID& entityItemID, const bool reload); void entityServerScriptChanging(const EntityItemID& entityItemID, const bool reload); From 0ad5f47bfd77eb3d577dbee18f79e11df68dfe79 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Tue, 29 Aug 2017 18:14:27 -0700 Subject: [PATCH 55/88] trying to fix entity editing bugs, needs testing --- .../src/entities/EntityPriorityQueue.cpp | 1 + .../src/entities/EntityPriorityQueue.h | 5 ++- .../src/entities/EntityTreeSendThread.cpp | 39 +++++++++++++------ .../src/entities/EntityTreeSendThread.h | 1 + libraries/entities/src/EntityItem.cpp | 3 +- libraries/entities/src/EntityItem.h | 3 +- libraries/entities/src/EntityTree.cpp | 10 +++-- libraries/entities/src/EntityTree.h | 1 + 8 files changed, 45 insertions(+), 18 deletions(-) diff --git a/assignment-client/src/entities/EntityPriorityQueue.cpp b/assignment-client/src/entities/EntityPriorityQueue.cpp index 3755e6a5c7..f6c1161308 100644 --- a/assignment-client/src/entities/EntityPriorityQueue.cpp +++ b/assignment-client/src/entities/EntityPriorityQueue.cpp @@ -12,6 +12,7 @@ #include "EntityPriorityQueue.h" const float PrioritizedEntity::DO_NOT_SEND = -1.0e-6f; +const float PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY = 1.0f; void ConicalView::set(const ViewFrustum& viewFrustum) { // The ConicalView has two parts: a central sphere (same as ViewFrustum) and a circular cone that bounds the frustum part. diff --git a/assignment-client/src/entities/EntityPriorityQueue.h b/assignment-client/src/entities/EntityPriorityQueue.h index 10db22d695..29712b3fd3 100644 --- a/assignment-client/src/entities/EntityPriorityQueue.h +++ b/assignment-client/src/entities/EntityPriorityQueue.h @@ -40,12 +40,14 @@ private: class PrioritizedEntity { public: static const float DO_NOT_SEND; + static const float WHEN_IN_DOUBT_PRIORITY; - PrioritizedEntity(EntityItemPointer entity, float priority) : _weakEntity(entity), _rawEntityPointer(entity.get()), _priority(priority) {} + PrioritizedEntity(EntityItemPointer entity, float priority, bool forceSend = false) : _weakEntity(entity), _rawEntityPointer(entity.get()), _priority(priority), _forceSend(forceSend) {} float updatePriority(const ConicalView& view); EntityItemPointer getEntity() const { return _weakEntity.lock(); } EntityItem* getRawEntityPointer() const { return _rawEntityPointer; } float getPriority() const { return _priority; } + bool shouldForceSend() const { return _forceSend; } class Compare { public: @@ -57,6 +59,7 @@ private: EntityItemWeakPointer _weakEntity; EntityItem* _rawEntityPointer; float _priority; + bool _forceSend; }; using EntityPriorityQueue = std::priority_queue< PrioritizedEntity, std::vector, PrioritizedEntity::Compare >; diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index e71331177b..e34dfd97e7 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -22,6 +22,7 @@ EntityTreeSendThread::EntityTreeSendThread(OctreeServer* myServer, const SharedNodePointer& node) : OctreeSendThread(myServer, node) { + connect(std::static_pointer_cast(myServer->getOctree()).get(), &EntityTree::editingEntityPointer, this, &EntityTreeSendThread::editingEntityPointer, Qt::QueuedConnection); connect(std::static_pointer_cast(myServer->getOctree()).get(), &EntityTree::deletingEntityPointer, this, &EntityTreeSendThread::deletingEntityPointer, Qt::QueuedConnection); } @@ -108,29 +109,29 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O // Re-add elements from previous traversal if they still need to be sent while (!prevSendQueue.empty()) { EntityItemPointer entity = prevSendQueue.top().getEntity(); + bool forceSend = prevSendQueue.top().shouldForceSend(); prevSendQueue.pop(); if (entity) { bool success = false; AACube cube = entity->getQueryAACube(success); if (success) { - if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { + if (forceSend || _traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { float priority = _conicalView.computePriority(cube); - if (priority != PrioritizedEntity::DO_NOT_SEND) { + if (forceSend || priority != PrioritizedEntity::DO_NOT_SEND) { float renderAccuracy = calculateRenderAccuracy(_traversal.getCurrentView().getPosition(), cube, _traversal.getCurrentRootSizeScale(), lodLevelOffset); - // Only send entities if they are large enough to see - if (renderAccuracy > 0.0f) { + // Only send entities if they are large enough to see, or we need to update them to be out of view + if (forceSend || renderAccuracy > 0.0f) { _sendQueue.push(PrioritizedEntity(entity, priority)); _entitiesInQueue.insert(entity.get()); } } } } else { - const float WHEN_IN_DOUBT_PRIORITY = 1.0f; - _sendQueue.push(PrioritizedEntity(entity, WHEN_IN_DOUBT_PRIORITY)); + _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); _entitiesInQueue.insert(entity.get()); } } @@ -274,8 +275,7 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree } } } else { - const float WHEN_IN_DOUBT_PRIORITY = 1.0f; - _sendQueue.push(PrioritizedEntity(entity, WHEN_IN_DOUBT_PRIORITY)); + _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); _entitiesInQueue.insert(entity.get()); } }); @@ -310,8 +310,7 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree } } } else { - const float WHEN_IN_DOUBT_PRIORITY = 1.0f; - _sendQueue.push(PrioritizedEntity(entity, WHEN_IN_DOUBT_PRIORITY)); + _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); _entitiesInQueue.insert(entity.get()); } } @@ -364,8 +363,7 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree } } } else { - const float WHEN_IN_DOUBT_PRIORITY = 1.0f; - _sendQueue.push(PrioritizedEntity(entity, WHEN_IN_DOUBT_PRIORITY)); + _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); _entitiesInQueue.insert(entity.get()); } } @@ -454,6 +452,23 @@ bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstream #endif // SEND_SORTED_ENTITIES } +void EntityTreeSendThread::editingEntityPointer(const EntityItemPointer entity) { + if (entity) { + if (_entitiesInQueue.find(entity.get()) == _entitiesInQueue.end() && _knownState.find(entity.get()) != _knownState.end()) { + bool success = false; + AACube cube = entity->getQueryAACube(success); + if (success) { + float priority = _conicalView.computePriority(cube); + _sendQueue.push(PrioritizedEntity(entity, priority, true)); + _entitiesInQueue.insert(entity.get()); + } else { + _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY, true)); + _entitiesInQueue.insert(entity.get()); + } + } + } +} + void EntityTreeSendThread::deletingEntityPointer(EntityItem* entity) { _knownState.erase(entity); } \ No newline at end of file diff --git a/assignment-client/src/entities/EntityTreeSendThread.h b/assignment-client/src/entities/EntityTreeSendThread.h index db59d97e6f..8b763a1c94 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.h +++ b/assignment-client/src/entities/EntityTreeSendThread.h @@ -54,6 +54,7 @@ private: uint16_t _numEntities { 0 }; private slots: + void editingEntityPointer(const EntityItemPointer entity); void deletingEntityPointer(EntityItem* entity); }; diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 71b119f415..6e2e52b380 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -32,7 +32,8 @@ #include "EntitySimulation.h" #include "EntityDynamicFactoryInterface.h" - +Q_DECLARE_METATYPE(EntityItemPointer); +int entityItemPointernMetaTypeId = qRegisterMetaType(); int EntityItem::_maxActionsDataSize = 800; quint64 EntityItem::_rememberDeletedActionTime = 20 * USECS_PER_SECOND; diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 4eac23c867..88750da463 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -62,7 +62,8 @@ class MeshProxyList; /// EntityItem class this is the base class for all entity types. It handles the basic properties and functionality available /// to all other entity types. In particular: postion, size, rotation, age, lifetime, velocity, gravity. You can not instantiate /// one directly, instead you must only construct one of it's derived classes with additional features. -class EntityItem : public SpatiallyNestable, public ReadWriteLockable { +class EntityItem : public QObject, public SpatiallyNestable, public ReadWriteLockable { + Q_OBJECT // These two classes manage lists of EntityItem pointers and must be able to cleanup pointers when an EntityItem is deleted. // To make the cleanup robust each EntityItem has backpointers to its manager classes (which are only ever set/cleared by // the managers themselves, hence they are fiends) whose NULL status can be used to determine which managers still need to diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index ebf479574b..99ab5a7677 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -307,7 +307,9 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti } UpdateEntityOperator theOperator(getThisPointer(), containingElement, entity, queryCube); recurseTreeWithOperator(&theOperator); - entity->setProperties(tempProperties); + if (entity->setProperties(tempProperties)) { + emit editingEntityPointer(entity); + } _isDirty = true; } } @@ -382,7 +384,9 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti } UpdateEntityOperator theOperator(getThisPointer(), containingElement, entity, newQueryAACube); recurseTreeWithOperator(&theOperator); - entity->setProperties(properties); + if (entity->setProperties(properties)) { + emit editingEntityPointer(entity); + } // if the entity has children, run UpdateEntityOperator on them. If the children have children, recurse QQueue toProcess; @@ -1257,7 +1261,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c if (!isPhysics) { properties.setLastEditedBy(senderNode->getUUID()); } - updateEntity(entityItemID, properties, senderNode); + updateEntity(existingEntity, properties, senderNode); existingEntity->markAsChangedOnServer(); endUpdate = usecTimestampNow(); _totalUpdates++; diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index b4155f474b..d0448f438a 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -271,6 +271,7 @@ signals: void deletingEntity(const EntityItemID& entityID); void deletingEntityPointer(EntityItem* entityID); void addingEntity(const EntityItemID& entityID); + void editingEntityPointer(const EntityItemPointer& entityID); void entityScriptChanging(const EntityItemID& entityItemID, const bool reload); void entityServerScriptChanging(const EntityItemID& entityItemID, const bool reload); void newCollisionSoundURL(const QUrl& url, const EntityItemID& entityID); From 7938e301e732f09dadd9de3df99a6bc70a0e30be Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Fri, 1 Sep 2017 14:13:43 -0700 Subject: [PATCH 56/88] full scene traversal and json filters --- .../src/entities/EntityTreeSendThread.cpp | 53 ++++++++++++++----- .../src/entities/EntityTreeSendThread.h | 4 +- .../src/octree/OctreeSendThread.cpp | 4 +- .../src/octree/OctreeSendThread.h | 2 +- libraries/entities/src/DiffTraversal.cpp | 39 ++++++++++++-- libraries/entities/src/DiffTraversal.h | 5 +- 6 files changed, 82 insertions(+), 25 deletions(-) diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index e34dfd97e7..024855235a 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -98,7 +98,7 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O nodeData->copyCurrentViewFrustum(viewFrustum); EntityTreeElementPointer root = std::dynamic_pointer_cast(_myServer->getOctree()->getRoot()); int32_t lodLevelOffset = nodeData->getBoundaryLevelAdjust() + (viewFrustumChanged ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST); - startNewTraversal(viewFrustum, root, lodLevelOffset); + startNewTraversal(viewFrustum, root, lodLevelOffset, true); // When the viewFrustum changed the sort order may be incorrect, so we re-sort // and also use the opportunity to cull anything no longer in view @@ -138,6 +138,11 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O } } } + } else if (_traversal.finished()) { + ViewFrustum viewFrustum; + EntityTreeElementPointer root = std::dynamic_pointer_cast(_myServer->getOctree()->getRoot()); + int32_t lodLevelOffset = nodeData->getBoundaryLevelAdjust() + NO_BOUNDARY_ADJUST; + startNewTraversal(viewFrustum, root, lodLevelOffset, false); } if (!_traversal.finished()) { @@ -165,7 +170,7 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O EntityItemPointer entity = entry.getEntity(); if (entity) { std::cout << "adebug send '" << entity->getName().toStdString() << "'" << " : " << entry.getPriority() << std::endl; // adebug - _knownState[entity] = sendTime; + _knownState[entity.get()] = sendTime; } _sendQueue.pop(); _entitiesInQueue.erase(entry.getRawEntityPointer()); @@ -225,13 +230,14 @@ bool EntityTreeSendThread::addDescendantsToExtraFlaggedEntities(const QUuid& fil return hasNewChild || hasNewDescendants; } -void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTreeElementPointer root, int32_t lodLevelOffset) { - DiffTraversal::Type type = _traversal.prepareNewTraversal(view, root, lodLevelOffset); - // there are three types of traversal: +void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTreeElementPointer root, int32_t lodLevelOffset, bool usesFrustum) { + DiffTraversal::Type type = _traversal.prepareNewTraversal(view, root, lodLevelOffset, usesFrustum); + // there are four types of traversal: // // (1) FirstTime = at login --> find everything in view // (2) Repeat = view hasn't changed --> find what has changed since last complete traversal // (3) Differential = view has changed --> find what has changed or in new view but not old + // (4) FullScene = no view frustum -> send everything // // The "scanCallback" we provide to the traversal depends on the type: // @@ -371,10 +377,26 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree } }); break; + case DiffTraversal::FullScene: + _traversal.setScanCallback([&](DiffTraversal::VisibleElement& next) { + next.element->forEachEntity([&](EntityItemPointer entity) { + // Bail early if we've already checked this entity this frame + if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) { + return; + } + if (_knownState.find(entity.get()) == _knownState.end() || + (_knownState.find(entity.get()) != _knownState.end() && entity->getLastEdited() > _knownState[entity.get()])) { + // We don't have a view frustum from which to compute priority + _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); + _entitiesInQueue.insert(entity.get()); + } + }); + }); + break; } } -bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params) { +bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) { #ifdef SEND_SORTED_ENTITIES //auto entityTree = std::static_pointer_cast(_myServer->getOctree()); if (_sendQueue.empty()) { @@ -418,17 +440,20 @@ bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstream PrioritizedEntity queuedItem = _sendQueue.top(); EntityItemPointer entity = queuedItem.getEntity(); if (entity) { - OctreeElement::AppendState appendEntityState = entity->appendEntityData(&_packetData, params, _extraEncodeData); - _knownState[entity.get()] = sendTime; + // Only send entities that match the jsonFilters, but keep track of everything we've tried to send so we don't try to send it again + if (entity->matchesJSONFilters(jsonFilters)) { + OctreeElement::AppendState appendEntityState = entity->appendEntityData(&_packetData, params, _extraEncodeData); - if (appendEntityState != OctreeElement::COMPLETED) { - if (appendEntityState == OctreeElement::PARTIAL) { - ++_numEntities; + if (appendEntityState != OctreeElement::COMPLETED) { + if (appendEntityState == OctreeElement::PARTIAL) { + ++_numEntities; + } + params.stopReason = EncodeBitstreamParams::DIDNT_FIT; + break; } - params.stopReason = EncodeBitstreamParams::DIDNT_FIT; - break; + ++_numEntities; } - ++_numEntities; + _knownState[entity.get()] = sendTime; } _sendQueue.pop(); _entitiesInQueue.erase(entity.get()); diff --git a/assignment-client/src/entities/EntityTreeSendThread.h b/assignment-client/src/entities/EntityTreeSendThread.h index 8b763a1c94..bdda159ed5 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.h +++ b/assignment-client/src/entities/EntityTreeSendThread.h @@ -39,8 +39,8 @@ private: bool addAncestorsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData); bool addDescendantsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData); - void startNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset); - bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params) override; + void startNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset, bool usesFrustum); + bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) override; DiffTraversal _traversal; EntityPriorityQueue _sendQueue; diff --git a/assignment-client/src/octree/OctreeSendThread.cpp b/assignment-client/src/octree/OctreeSendThread.cpp index b6d9d323c1..5a563037bc 100644 --- a/assignment-client/src/octree/OctreeSendThread.cpp +++ b/assignment-client/src/octree/OctreeSendThread.cpp @@ -458,7 +458,7 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* return _truePacketsSent; } -bool OctreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params) { +bool OctreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) { bool somethingToSend = false; OctreeQueryNode* nodeData = static_cast(params.nodeData); if (!nodeData->elementBag.isEmpty()) { @@ -523,7 +523,7 @@ void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, Octre bool lastNodeDidntFit = false; // assume each node fits params.stopReason = EncodeBitstreamParams::UNKNOWN; // reset params.stopReason before traversal - somethingToSend = traverseTreeAndBuildNextPacketPayload(params); + somethingToSend = traverseTreeAndBuildNextPacketPayload(params, nodeData->getJSONParameters()); if (params.stopReason == EncodeBitstreamParams::DIDNT_FIT) { lastNodeDidntFit = true; diff --git a/assignment-client/src/octree/OctreeSendThread.h b/assignment-client/src/octree/OctreeSendThread.h index bcfede262c..a6ceba0e95 100644 --- a/assignment-client/src/octree/OctreeSendThread.h +++ b/assignment-client/src/octree/OctreeSendThread.h @@ -55,7 +55,7 @@ protected: virtual void preDistributionProcessing() {}; virtual void traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged, bool isFullScene); - virtual bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params); + virtual bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters); OctreePacketData _packetData; QWeakPointer _node; diff --git a/libraries/entities/src/DiffTraversal.cpp b/libraries/entities/src/DiffTraversal.cpp index e8e300081a..2cbe3f1064 100644 --- a/libraries/entities/src/DiffTraversal.cpp +++ b/libraries/entities/src/DiffTraversal.cpp @@ -142,19 +142,45 @@ void DiffTraversal::Waypoint::getNextVisibleElementDifferential(DiffTraversal::V next.intersection = ViewFrustum::OUTSIDE; } +void DiffTraversal::Waypoint::getNextVisibleElementFullScene(DiffTraversal::VisibleElement& next, uint64_t lastTime) { + // NOTE: no need to set next.intersection or check LOD truncation in the "FullScene" context + if (_nextIndex == -1) { + ++_nextIndex; + EntityTreeElementPointer element = _weakElement.lock(); + if (element->getLastChangedContent() > lastTime) { + next.element = element; + return; + } + } + if (_nextIndex < NUMBER_OF_CHILDREN) { + EntityTreeElementPointer element = _weakElement.lock(); + if (element) { + while (_nextIndex < NUMBER_OF_CHILDREN) { + EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); + ++_nextIndex; + if (nextElement && nextElement->getLastChanged() > lastTime) { + next.element = nextElement; + return; + } + } + } + } + next.element.reset(); +} + DiffTraversal::DiffTraversal() { const int32_t MIN_PATH_DEPTH = 16; _path.reserve(MIN_PATH_DEPTH); } -DiffTraversal::Type DiffTraversal::prepareNewTraversal( - const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset) { +DiffTraversal::Type DiffTraversal::prepareNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset, bool usesFrustum) { assert(root); - // there are three types of traversal: + // there are four types of traversal: // // (1) First = fresh view --> find all elements in view // (2) Repeat = view hasn't changed --> find elements changed since last complete traversal // (3) Differential = view has changed --> find elements changed or in new view but not old + // (4) FullScene = no view frustum -> send everything // // for each traversal type we assign the appropriate _getNextVisibleElementCallback // @@ -166,7 +192,12 @@ DiffTraversal::Type DiffTraversal::prepareNewTraversal( // Type type; - if (_completedView.startTime == 0) { + if (!usesFrustum) { + type = Type::FullScene; + _getNextVisibleElementCallback = [&](DiffTraversal::VisibleElement& next) { + _path.back().getNextVisibleElementFullScene(next, _completedView.startTime); + }; + } else if (_completedView.startTime == 0) { type = Type::First; _currentView.viewFrustum = viewFrustum; _currentView.lodLevelOffset = root->getLevel() + lodLevelOffset - 1; // -1 because true root has level=1 diff --git a/libraries/entities/src/DiffTraversal.h b/libraries/entities/src/DiffTraversal.h index e849b4fef3..83b2c99bb9 100644 --- a/libraries/entities/src/DiffTraversal.h +++ b/libraries/entities/src/DiffTraversal.h @@ -46,6 +46,7 @@ public: void getNextVisibleElementFirstTime(VisibleElement& next, const View& view); void getNextVisibleElementRepeat(VisibleElement& next, const View& view, uint64_t lastTime); void getNextVisibleElementDifferential(VisibleElement& next, const View& view, const View& lastView); + void getNextVisibleElementFullScene(VisibleElement& next, uint64_t lastTime); int8_t getNextIndex() const { return _nextIndex; } void initRootNextIndex() { _nextIndex = -1; } @@ -55,11 +56,11 @@ public: int8_t _nextIndex; }; - typedef enum { First, Repeat, Differential } Type; + typedef enum { First, Repeat, Differential, FullScene } Type; DiffTraversal(); - Type prepareNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset); + Type prepareNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset, bool isFullScene); const ViewFrustum& getCurrentView() const { return _currentView.viewFrustum; } const ViewFrustum& getCompletedView() const { return _completedView.viewFrustum; } From defed80be7aeb3c9fec1121ade6adebf7a895583 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 6 Sep 2017 13:42:32 -0700 Subject: [PATCH 57/88] edited entities are not repeatedly sent if out of view, handles cases where usesViewFrustum changes --- .../src/entities/EntityPriorityQueue.cpp | 1 + .../src/entities/EntityPriorityQueue.h | 7 +- .../src/entities/EntityTreeSendThread.cpp | 261 ++++++++++-------- .../src/entities/EntityTreeSendThread.h | 2 +- libraries/entities/src/DiffTraversal.cpp | 95 +++---- libraries/entities/src/DiffTraversal.h | 7 +- libraries/shared/src/ViewFrustum.cpp | 12 +- 7 files changed, 203 insertions(+), 182 deletions(-) diff --git a/assignment-client/src/entities/EntityPriorityQueue.cpp b/assignment-client/src/entities/EntityPriorityQueue.cpp index f6c1161308..6d94f911ea 100644 --- a/assignment-client/src/entities/EntityPriorityQueue.cpp +++ b/assignment-client/src/entities/EntityPriorityQueue.cpp @@ -12,6 +12,7 @@ #include "EntityPriorityQueue.h" const float PrioritizedEntity::DO_NOT_SEND = -1.0e-6f; +const float PrioritizedEntity::FORCE_REMOVE = -1.0e-5f; const float PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY = 1.0f; void ConicalView::set(const ViewFrustum& viewFrustum) { diff --git a/assignment-client/src/entities/EntityPriorityQueue.h b/assignment-client/src/entities/EntityPriorityQueue.h index 29712b3fd3..a5d0ab05ff 100644 --- a/assignment-client/src/entities/EntityPriorityQueue.h +++ b/assignment-client/src/entities/EntityPriorityQueue.h @@ -40,14 +40,15 @@ private: class PrioritizedEntity { public: static const float DO_NOT_SEND; + static const float FORCE_REMOVE; static const float WHEN_IN_DOUBT_PRIORITY; - PrioritizedEntity(EntityItemPointer entity, float priority, bool forceSend = false) : _weakEntity(entity), _rawEntityPointer(entity.get()), _priority(priority), _forceSend(forceSend) {} + PrioritizedEntity(EntityItemPointer entity, float priority, bool forceRemove = false) : _weakEntity(entity), _rawEntityPointer(entity.get()), _priority(priority), _forceRemove(forceRemove) {} float updatePriority(const ConicalView& view); EntityItemPointer getEntity() const { return _weakEntity.lock(); } EntityItem* getRawEntityPointer() const { return _rawEntityPointer; } float getPriority() const { return _priority; } - bool shouldForceSend() const { return _forceSend; } + bool shouldForceRemove() const { return _forceRemove; } class Compare { public: @@ -59,7 +60,7 @@ private: EntityItemWeakPointer _weakEntity; EntityItem* _rawEntityPointer; float _priority; - bool _forceSend; + bool _forceRemove; }; using EntityPriorityQueue = std::priority_queue< PrioritizedEntity, std::vector, PrioritizedEntity::Compare >; diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index 024855235a..817df55625 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -92,39 +92,39 @@ void EntityTreeSendThread::preDistributionProcessing() { void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged, bool isFullScene) { - if (nodeData->getUsesFrustum()) { - if (viewFrustumChanged || _traversal.finished()) { - ViewFrustum viewFrustum; - nodeData->copyCurrentViewFrustum(viewFrustum); - EntityTreeElementPointer root = std::dynamic_pointer_cast(_myServer->getOctree()->getRoot()); - int32_t lodLevelOffset = nodeData->getBoundaryLevelAdjust() + (viewFrustumChanged ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST); - startNewTraversal(viewFrustum, root, lodLevelOffset, true); + if (viewFrustumChanged || _traversal.finished()) { + ViewFrustum viewFrustum; + nodeData->copyCurrentViewFrustum(viewFrustum); + EntityTreeElementPointer root = std::dynamic_pointer_cast(_myServer->getOctree()->getRoot()); + int32_t lodLevelOffset = nodeData->getBoundaryLevelAdjust() + (viewFrustumChanged ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST); + startNewTraversal(viewFrustum, root, lodLevelOffset, nodeData->getUsesFrustum()); - // When the viewFrustum changed the sort order may be incorrect, so we re-sort - // and also use the opportunity to cull anything no longer in view - if (viewFrustumChanged && !_sendQueue.empty()) { - EntityPriorityQueue prevSendQueue; - _sendQueue.swap(prevSendQueue); - _entitiesInQueue.clear(); - // Re-add elements from previous traversal if they still need to be sent - while (!prevSendQueue.empty()) { - EntityItemPointer entity = prevSendQueue.top().getEntity(); - bool forceSend = prevSendQueue.top().shouldForceSend(); - prevSendQueue.pop(); - if (entity) { + // When the viewFrustum changed the sort order may be incorrect, so we re-sort + // and also use the opportunity to cull anything no longer in view + if (viewFrustumChanged && !_sendQueue.empty()) { + EntityPriorityQueue prevSendQueue; + _sendQueue.swap(prevSendQueue); + _entitiesInQueue.clear(); + // Re-add elements from previous traversal if they still need to be sent + while (!prevSendQueue.empty()) { + EntityItemPointer entity = prevSendQueue.top().getEntity(); + bool forceRemove = prevSendQueue.top().shouldForceRemove(); + prevSendQueue.pop(); + if (entity) { + if (!forceRemove) { bool success = false; AACube cube = entity->getQueryAACube(success); if (success) { - if (forceSend || _traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { + if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { float priority = _conicalView.computePriority(cube); - if (forceSend || priority != PrioritizedEntity::DO_NOT_SEND) { + if (priority != PrioritizedEntity::DO_NOT_SEND) { float renderAccuracy = calculateRenderAccuracy(_traversal.getCurrentView().getPosition(), cube, _traversal.getCurrentRootSizeScale(), lodLevelOffset); - // Only send entities if they are large enough to see, or we need to update them to be out of view - if (forceSend || renderAccuracy > 0.0f) { + // Only send entities if they are large enough to see + if (renderAccuracy > 0.0f) { _sendQueue.push(PrioritizedEntity(entity, priority)); _entitiesInQueue.insert(entity.get()); } @@ -134,15 +134,13 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); _entitiesInQueue.insert(entity.get()); } + } else { + _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::FORCE_REMOVE, true)); + _entitiesInQueue.insert(entity.get()); } } } } - } else if (_traversal.finished()) { - ViewFrustum viewFrustum; - EntityTreeElementPointer root = std::dynamic_pointer_cast(_myServer->getOctree()->getRoot()); - int32_t lodLevelOffset = nodeData->getBoundaryLevelAdjust() + NO_BOUNDARY_ADJUST; - startNewTraversal(viewFrustum, root, lodLevelOffset, false); } if (!_traversal.finished()) { @@ -230,14 +228,13 @@ bool EntityTreeSendThread::addDescendantsToExtraFlaggedEntities(const QUuid& fil return hasNewChild || hasNewDescendants; } -void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTreeElementPointer root, int32_t lodLevelOffset, bool usesFrustum) { - DiffTraversal::Type type = _traversal.prepareNewTraversal(view, root, lodLevelOffset, usesFrustum); - // there are four types of traversal: +void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTreeElementPointer root, int32_t lodLevelOffset, bool usesViewFrustum) { + DiffTraversal::Type type = _traversal.prepareNewTraversal(view, root, lodLevelOffset, usesViewFrustum); + // there are three types of traversal: // // (1) FirstTime = at login --> find everything in view // (2) Repeat = view hasn't changed --> find what has changed since last complete traversal // (3) Differential = view has changed --> find what has changed or in new view but not old - // (4) FullScene = no view frustum -> send everything // // The "scanCallback" we provide to the traversal depends on the type: // @@ -248,83 +245,118 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree switch (type) { case DiffTraversal::First: - _traversal.setScanCallback([&] (DiffTraversal::VisibleElement& next) { - // When we get to a First traversal, clear the _knownState - _knownState.clear(); - next.element->forEachEntity([&](EntityItemPointer entity) { - // Bail early if we've already checked this entity this frame - if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) { - return; - } - bool success = false; - AACube cube = entity->getQueryAACube(success); - if (success) { - if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { - // Check the size of the entity, it's possible that a "too small to see" entity is included in a - // larger octree cell because of its position (for example if it crosses the boundary of a cell it - // pops to the next higher cell. So we want to check to see that the entity is large enough to be seen - // before we consider including it. - // - // TODO: compare priority against a threshold rather than bother with - // calculateRenderAccuracy(). Would need to replace all calculateRenderAccuracy() - // stuff everywhere with threshold in one sweep. - float renderAccuracy = calculateRenderAccuracy(_traversal.getCurrentView().getPosition(), - cube, - _traversal.getCurrentRootSizeScale(), - _traversal.getCurrentLODOffset()); - - // Only send entities if they are large enough to see - if (renderAccuracy > 0.0f) { - float priority = _conicalView.computePriority(cube); - _sendQueue.push(PrioritizedEntity(entity, priority)); - _entitiesInQueue.insert(entity.get()); - } - } - } else { - _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); - _entitiesInQueue.insert(entity.get()); - } - }); - }); - break; - case DiffTraversal::Repeat: - _traversal.setScanCallback([&] (DiffTraversal::VisibleElement& next) { - uint64_t startOfCompletedTraversal = _traversal.getStartOfCompletedTraversal(); - if (next.element->getLastChangedContent() > startOfCompletedTraversal) { + // When we get to a First traversal, clear the _knownState + _knownState.clear(); + if (usesViewFrustum) { + _traversal.setScanCallback([&](DiffTraversal::VisibleElement& next) { next.element->forEachEntity([&](EntityItemPointer entity) { // Bail early if we've already checked this entity this frame if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) { return; } - if (_knownState.find(entity.get()) == _knownState.end() || - (_knownState.find(entity.get()) != _knownState.end() && entity->getLastEdited() > _knownState[entity.get()])) { - bool success = false; - AACube cube = entity->getQueryAACube(success); - if (success) { - if (next.intersection == ViewFrustum::INSIDE || _traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { - // See the DiffTraversal::First case for an explanation of the "entity is too small" check - float renderAccuracy = calculateRenderAccuracy(_traversal.getCurrentView().getPosition(), - cube, - _traversal.getCurrentRootSizeScale(), - _traversal.getCurrentLODOffset()); + bool success = false; + AACube cube = entity->getQueryAACube(success); + if (success) { + if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { + // Check the size of the entity, it's possible that a "too small to see" entity is included in a + // larger octree cell because of its position (for example if it crosses the boundary of a cell it + // pops to the next higher cell. So we want to check to see that the entity is large enough to be seen + // before we consider including it. + // + // TODO: compare priority against a threshold rather than bother with + // calculateRenderAccuracy(). Would need to replace all calculateRenderAccuracy() + // stuff everywhere with threshold in one sweep. + float renderAccuracy = calculateRenderAccuracy(_traversal.getCurrentView().getPosition(), + cube, + _traversal.getCurrentRootSizeScale(), + _traversal.getCurrentLODOffset()); - // Only send entities if they are large enough to see - if (renderAccuracy > 0.0f) { - float priority = _conicalView.computePriority(cube); - _sendQueue.push(PrioritizedEntity(entity, priority)); - _entitiesInQueue.insert(entity.get()); - } + // Only send entities if they are large enough to see + if (renderAccuracy > 0.0f) { + float priority = _conicalView.computePriority(cube); + _sendQueue.push(PrioritizedEntity(entity, priority)); + _entitiesInQueue.insert(entity.get()); } - } else { + } + } else { + _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); + _entitiesInQueue.insert(entity.get()); + } + }); + }); + } else { + _traversal.setScanCallback([&](DiffTraversal::VisibleElement& next) { + next.element->forEachEntity([&](EntityItemPointer entity) { + // Bail early if we've already checked this entity this frame + if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) { + return; + } + _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); + _entitiesInQueue.insert(entity.get()); + }); + }); + } + break; + case DiffTraversal::Repeat: + if (usesViewFrustum) { + _traversal.setScanCallback([&](DiffTraversal::VisibleElement& next) { + uint64_t startOfCompletedTraversal = _traversal.getStartOfCompletedTraversal(); + if (next.element->getLastChangedContent() > startOfCompletedTraversal) { + next.element->forEachEntity([&](EntityItemPointer entity) { + // Bail early if we've already checked this entity this frame + if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) { + return; + } + auto knownTimestamp = _knownState.find(entity.get()); + if (knownTimestamp == _knownState.end() || + (knownTimestamp != _knownState.end() && entity->getLastEdited() > knownTimestamp->second)) { + bool success = false; + AACube cube = entity->getQueryAACube(success); + if (success) { + if (next.intersection == ViewFrustum::INSIDE || _traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { + // See the DiffTraversal::First case for an explanation of the "entity is too small" check + float renderAccuracy = calculateRenderAccuracy(_traversal.getCurrentView().getPosition(), + cube, + _traversal.getCurrentRootSizeScale(), + _traversal.getCurrentLODOffset()); + + // Only send entities if they are large enough to see + if (renderAccuracy > 0.0f) { + float priority = _conicalView.computePriority(cube); + _sendQueue.push(PrioritizedEntity(entity, priority)); + _entitiesInQueue.insert(entity.get()); + } + } + } else { + _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); + _entitiesInQueue.insert(entity.get()); + } + } + }); + } + }); + } else { + _traversal.setScanCallback([&](DiffTraversal::VisibleElement& next) { + uint64_t startOfCompletedTraversal = _traversal.getStartOfCompletedTraversal(); + if (next.element->getLastChangedContent() > startOfCompletedTraversal) { + next.element->forEachEntity([&](EntityItemPointer entity) { + // Bail early if we've already checked this entity this frame + if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) { + return; + } + auto knownTimestamp = _knownState.find(entity.get()); + if (knownTimestamp == _knownState.end() || + (knownTimestamp != _knownState.end() && entity->getLastEdited() > knownTimestamp->second)) { _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); _entitiesInQueue.insert(entity.get()); } - } - }); - } - }); + }); + } + }); + } break; case DiffTraversal::Differential: + assert(usesViewFrustum); _traversal.setScanCallback([&] (DiffTraversal::VisibleElement& next) { // NOTE: for Differential case: next.intersection is against completedView not currentView uint64_t startOfCompletedTraversal = _traversal.getStartOfCompletedTraversal(); @@ -335,8 +367,9 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) { return; } - if (_knownState.find(entity.get()) == _knownState.end() || - (_knownState.find(entity.get()) != _knownState.end() && entity->getLastEdited() > _knownState[entity.get()])) { + auto knownTimestamp = _knownState.find(entity.get()); + if (knownTimestamp == _knownState.end() || + (knownTimestamp != _knownState.end() && entity->getLastEdited() > knownTimestamp->second)) { bool success = false; AACube cube = entity->getQueryAACube(success); if (success) { @@ -377,22 +410,6 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree } }); break; - case DiffTraversal::FullScene: - _traversal.setScanCallback([&](DiffTraversal::VisibleElement& next) { - next.element->forEachEntity([&](EntityItemPointer entity) { - // Bail early if we've already checked this entity this frame - if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) { - return; - } - if (_knownState.find(entity.get()) == _knownState.end() || - (_knownState.find(entity.get()) != _knownState.end() && entity->getLastEdited() > _knownState[entity.get()])) { - // We don't have a view frustum from which to compute priority - _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); - _entitiesInQueue.insert(entity.get()); - } - }); - }); - break; } } @@ -453,7 +470,11 @@ bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstream } ++_numEntities; } - _knownState[entity.get()] = sendTime; + if (queuedItem.shouldForceRemove()) { + _knownState.erase(entity.get()); + } else { + _knownState[entity.get()] = sendTime; + } } _sendQueue.pop(); _entitiesInQueue.erase(entity.get()); @@ -483,9 +504,11 @@ void EntityTreeSendThread::editingEntityPointer(const EntityItemPointer entity) bool success = false; AACube cube = entity->getQueryAACube(success); if (success) { - float priority = _conicalView.computePriority(cube); - _sendQueue.push(PrioritizedEntity(entity, priority, true)); - _entitiesInQueue.insert(entity.get()); + // We can force a removal from _knownState if the current view is used and entity is out of view + if (_traversal.doesCurrentUseViewFrustum() && !_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { + _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::FORCE_REMOVE, true)); + _entitiesInQueue.insert(entity.get()); + } } else { _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY, true)); _entitiesInQueue.insert(entity.get()); diff --git a/assignment-client/src/entities/EntityTreeSendThread.h b/assignment-client/src/entities/EntityTreeSendThread.h index bdda159ed5..c9751c5835 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.h +++ b/assignment-client/src/entities/EntityTreeSendThread.h @@ -39,7 +39,7 @@ private: bool addAncestorsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData); bool addDescendantsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData); - void startNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset, bool usesFrustum); + void startNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset, bool usesViewFrustum); bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) override; DiffTraversal _traversal; diff --git a/libraries/entities/src/DiffTraversal.cpp b/libraries/entities/src/DiffTraversal.cpp index 2cbe3f1064..86296c82de 100644 --- a/libraries/entities/src/DiffTraversal.cpp +++ b/libraries/entities/src/DiffTraversal.cpp @@ -33,18 +33,30 @@ void DiffTraversal::Waypoint::getNextVisibleElementFirstTime(DiffTraversal::Visi } else if (_nextIndex < NUMBER_OF_CHILDREN) { EntityTreeElementPointer element = _weakElement.lock(); if (element) { - // check for LOD truncation - float visibleLimit = boundaryDistanceForRenderLevel(element->getLevel() + view.lodLevelOffset, view.rootSizeScale); - float distance2 = glm::distance2(view.viewFrustum.getPosition(), element->getAACube().calcCenter()); - if (distance2 < visibleLimit * visibleLimit) { + // No LOD truncation if we aren't using the view frustum + if (!view.usesViewFrustum) { while (_nextIndex < NUMBER_OF_CHILDREN) { EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); ++_nextIndex; - if (nextElement && view.viewFrustum.cubeIntersectsKeyhole(nextElement->getAACube())) { + if (nextElement) { next.element = nextElement; return; } } + } else { + // check for LOD truncation + float visibleLimit = boundaryDistanceForRenderLevel(element->getLevel() + view.lodLevelOffset, view.rootSizeScale); + float distance2 = glm::distance2(view.viewFrustum.getPosition(), element->getAACube().calcCenter()); + if (distance2 < visibleLimit * visibleLimit) { + while (_nextIndex < NUMBER_OF_CHILDREN) { + EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); + ++_nextIndex; + if (nextElement && view.viewFrustum.cubeIntersectsKeyhole(nextElement->getAACube())) { + next.element = nextElement; + return; + } + } + } } } } @@ -66,19 +78,32 @@ void DiffTraversal::Waypoint::getNextVisibleElementRepeat( if (_nextIndex < NUMBER_OF_CHILDREN) { EntityTreeElementPointer element = _weakElement.lock(); if (element) { - // check for LOD truncation - float visibleLimit = boundaryDistanceForRenderLevel(element->getLevel() + view.lodLevelOffset, view.rootSizeScale); - float distance2 = glm::distance2(view.viewFrustum.getPosition(), element->getAACube().calcCenter()); - if (distance2 < visibleLimit * visibleLimit) { + // No LOD truncation if we aren't using the view frustum + if (!view.usesViewFrustum) { while (_nextIndex < NUMBER_OF_CHILDREN) { EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); ++_nextIndex; if (nextElement && nextElement->getLastChanged() > lastTime) { - ViewFrustum::intersection intersection = view.viewFrustum.calculateCubeKeyholeIntersection(nextElement->getAACube()); - if (intersection != ViewFrustum::OUTSIDE) { - next.element = nextElement; - next.intersection = intersection; - return; + next.element = nextElement; + next.intersection = ViewFrustum::INSIDE; + return; + } + } + } else { + // check for LOD truncation + float visibleLimit = boundaryDistanceForRenderLevel(element->getLevel() + view.lodLevelOffset, view.rootSizeScale); + float distance2 = glm::distance2(view.viewFrustum.getPosition(), element->getAACube().calcCenter()); + if (distance2 < visibleLimit * visibleLimit) { + while (_nextIndex < NUMBER_OF_CHILDREN) { + EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); + ++_nextIndex; + if (nextElement && nextElement->getLastChanged() > lastTime) { + ViewFrustum::intersection intersection = view.viewFrustum.calculateCubeKeyholeIntersection(nextElement->getAACube()); + if (intersection != ViewFrustum::OUTSIDE) { + next.element = nextElement; + next.intersection = intersection; + return; + } } } } @@ -142,45 +167,18 @@ void DiffTraversal::Waypoint::getNextVisibleElementDifferential(DiffTraversal::V next.intersection = ViewFrustum::OUTSIDE; } -void DiffTraversal::Waypoint::getNextVisibleElementFullScene(DiffTraversal::VisibleElement& next, uint64_t lastTime) { - // NOTE: no need to set next.intersection or check LOD truncation in the "FullScene" context - if (_nextIndex == -1) { - ++_nextIndex; - EntityTreeElementPointer element = _weakElement.lock(); - if (element->getLastChangedContent() > lastTime) { - next.element = element; - return; - } - } - if (_nextIndex < NUMBER_OF_CHILDREN) { - EntityTreeElementPointer element = _weakElement.lock(); - if (element) { - while (_nextIndex < NUMBER_OF_CHILDREN) { - EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); - ++_nextIndex; - if (nextElement && nextElement->getLastChanged() > lastTime) { - next.element = nextElement; - return; - } - } - } - } - next.element.reset(); -} - DiffTraversal::DiffTraversal() { const int32_t MIN_PATH_DEPTH = 16; _path.reserve(MIN_PATH_DEPTH); } -DiffTraversal::Type DiffTraversal::prepareNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset, bool usesFrustum) { +DiffTraversal::Type DiffTraversal::prepareNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset, bool usesViewFrustum) { assert(root); - // there are four types of traversal: + // there are three types of traversal: // // (1) First = fresh view --> find all elements in view // (2) Repeat = view hasn't changed --> find elements changed since last complete traversal // (3) Differential = view has changed --> find elements changed or in new view but not old - // (4) FullScene = no view frustum -> send everything // // for each traversal type we assign the appropriate _getNextVisibleElementCallback // @@ -190,14 +188,11 @@ DiffTraversal::Type DiffTraversal::prepareNewTraversal(const ViewFrustum& viewFr // // external code should update the _scanElementCallback after calling prepareNewTraversal // + _currentView.usesViewFrustum = usesViewFrustum; Type type; - if (!usesFrustum) { - type = Type::FullScene; - _getNextVisibleElementCallback = [&](DiffTraversal::VisibleElement& next) { - _path.back().getNextVisibleElementFullScene(next, _completedView.startTime); - }; - } else if (_completedView.startTime == 0) { + // If usesViewFrustum changes, treat it as a First traversal + if (_completedView.startTime == 0 || _currentView.usesViewFrustum != _completedView.usesViewFrustum) { type = Type::First; _currentView.viewFrustum = viewFrustum; _currentView.lodLevelOffset = root->getLevel() + lodLevelOffset - 1; // -1 because true root has level=1 @@ -205,7 +200,7 @@ DiffTraversal::Type DiffTraversal::prepareNewTraversal(const ViewFrustum& viewFr _getNextVisibleElementCallback = [&](DiffTraversal::VisibleElement& next) { _path.back().getNextVisibleElementFirstTime(next, _currentView); }; - } else if (_completedView.viewFrustum.isVerySimilar(viewFrustum) && lodLevelOffset == _completedView.lodLevelOffset) { + } else if (!_currentView.usesViewFrustum || (_completedView.viewFrustum.isVerySimilar(viewFrustum) && lodLevelOffset == _completedView.lodLevelOffset)) { type = Type::Repeat; _getNextVisibleElementCallback = [&](DiffTraversal::VisibleElement& next) { _path.back().getNextVisibleElementRepeat(next, _completedView, _completedView.startTime); diff --git a/libraries/entities/src/DiffTraversal.h b/libraries/entities/src/DiffTraversal.h index 83b2c99bb9..2bd44d041e 100644 --- a/libraries/entities/src/DiffTraversal.h +++ b/libraries/entities/src/DiffTraversal.h @@ -36,6 +36,7 @@ public: uint64_t startTime { 0 }; float rootSizeScale { 1.0f }; int32_t lodLevelOffset { 0 }; + bool usesViewFrustum { true }; }; // Waypoint is an bookmark in a "path" of waypoints during a traversal. @@ -46,7 +47,6 @@ public: void getNextVisibleElementFirstTime(VisibleElement& next, const View& view); void getNextVisibleElementRepeat(VisibleElement& next, const View& view, uint64_t lastTime); void getNextVisibleElementDifferential(VisibleElement& next, const View& view, const View& lastView); - void getNextVisibleElementFullScene(VisibleElement& next, uint64_t lastTime); int8_t getNextIndex() const { return _nextIndex; } void initRootNextIndex() { _nextIndex = -1; } @@ -56,15 +56,16 @@ public: int8_t _nextIndex; }; - typedef enum { First, Repeat, Differential, FullScene } Type; + typedef enum { First, Repeat, Differential } Type; DiffTraversal(); - Type prepareNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset, bool isFullScene); + Type prepareNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset, bool usesViewFrustum); const ViewFrustum& getCurrentView() const { return _currentView.viewFrustum; } const ViewFrustum& getCompletedView() const { return _completedView.viewFrustum; } + bool doesCurrentUseViewFrustum() const { return _currentView.usesViewFrustum; } float getCurrentRootSizeScale() const { return _currentView.rootSizeScale; } float getCompletedRootSizeScale() const { return _completedView.rootSizeScale; } float getCurrentLODOffset() const { return _currentView.lodLevelOffset; } diff --git a/libraries/shared/src/ViewFrustum.cpp b/libraries/shared/src/ViewFrustum.cpp index 7e4f64686b..d84826cf01 100644 --- a/libraries/shared/src/ViewFrustum.cpp +++ b/libraries/shared/src/ViewFrustum.cpp @@ -403,12 +403,12 @@ bool ViewFrustum::isVerySimilar(const ViewFrustum& compareTo, bool debug) const bool result = testMatches(0, positionDistance, POSITION_SIMILAR_ENOUGH) && testMatches(0, angleOrientation, ORIENTATION_SIMILAR_ENOUGH) && - testMatches(compareTo._fieldOfView, _fieldOfView) && - testMatches(compareTo._aspectRatio, _aspectRatio) && - testMatches(compareTo._nearClip, _nearClip) && - testMatches(compareTo._farClip, _farClip) && - testMatches(compareTo._focalLength, _focalLength); - + testMatches(compareTo._centerSphereRadius, _centerSphereRadius) && + testMatches(compareTo._fieldOfView, _fieldOfView) && + testMatches(compareTo._aspectRatio, _aspectRatio) && + testMatches(compareTo._nearClip, _nearClip) && + testMatches(compareTo._farClip, _farClip) && + testMatches(compareTo._focalLength, _focalLength); if (!result && debug) { qCDebug(shared, "ViewFrustum::isVerySimilar()... result=%s\n", debug::valueOf(result)); From 0597970bb4462aae837a9b2ac0fb99f4e2600f54 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 7 Sep 2017 15:57:56 -0700 Subject: [PATCH 58/88] faster, more correct ViewFrustum::isVerySimilar() remove OVERSEND hack add ViewFrustum::calculateProjection() method used by OctreeQueryNode --- libraries/shared/src/ViewFrustum.cpp | 160 +++++++-------------------- libraries/shared/src/ViewFrustum.h | 8 +- 2 files changed, 39 insertions(+), 129 deletions(-) diff --git a/libraries/shared/src/ViewFrustum.cpp b/libraries/shared/src/ViewFrustum.cpp index d84826cf01..0ba5d4c9b3 100644 --- a/libraries/shared/src/ViewFrustum.cpp +++ b/libraries/shared/src/ViewFrustum.cpp @@ -56,16 +56,17 @@ void ViewFrustum::setProjection(const glm::mat4& projection) { _projection = projection; glm::mat4 inverseProjection = glm::inverse(projection); - // compute our dimensions the usual way + // compute frustum corners for (int i = 0; i < NUM_FRUSTUM_CORNERS; ++i) { _corners[i] = inverseProjection * NDC_VALUES[i]; _corners[i] /= _corners[i].w; } + + // compute frustum properties _nearClip = -_corners[BOTTOM_LEFT_NEAR].z; _farClip = -_corners[BOTTOM_LEFT_FAR].z; _aspectRatio = (_corners[TOP_RIGHT_NEAR].x - _corners[BOTTOM_LEFT_NEAR].x) / (_corners[TOP_RIGHT_NEAR].y - _corners[BOTTOM_LEFT_NEAR].y); - glm::vec4 top = inverseProjection * vec4(0.0f, 1.0f, -1.0f, 1.0f); top /= top.w; _fieldOfView = abs(glm::degrees(2.0f * abs(glm::angle(vec3(0.0f, 0.0f, -1.0f), glm::normalize(vec3(top)))))); @@ -117,6 +118,23 @@ void ViewFrustum::calculate() { _ourModelViewProjectionMatrix = _projection * view; // Remember, matrix multiplication is the other way around } +void ViewFrustum::calculateProjection() { + if (0.0f != _aspectRatio && 0.0f != _nearClip && 0.0f != _farClip && _nearClip != _farClip) { + // _projection is calculated from the frustum parameters + _projection = glm::perspective( glm::radians(_fieldOfView), _aspectRatio, _nearClip, _farClip); + + // frustum corners are computed from inverseProjection + glm::mat4 inverseProjection = glm::inverse(_projection); + for (int i = 0; i < NUM_FRUSTUM_CORNERS; ++i) { + _corners[i] = inverseProjection * NDC_VALUES[i]; + _corners[i] /= _corners[i].w; + } + + // finally calculate planes and _ourModelViewProjectionMatrix + calculate(); + } +} + //enum { TOP_PLANE = 0, BOTTOM_PLANE, LEFT_PLANE, RIGHT_PLANE, NEAR_PLANE, FAR_PLANE }; const char* ViewFrustum::debugPlaneName (int plane) const { switch (plane) { @@ -160,16 +178,12 @@ void ViewFrustum::fromByteArray(const QByteArray& input) { setCenterRadius(cameraCenterRadius); // Also make sure it's got the correct lens details from the camera - const float VIEW_FRUSTUM_FOV_OVERSEND = 60.0f; - float originalFOV = cameraFov; - float wideFOV = originalFOV + VIEW_FRUSTUM_FOV_OVERSEND; - if (0.0f != cameraAspectRatio && 0.0f != cameraNearClip && 0.0f != cameraFarClip && cameraNearClip != cameraFarClip) { setProjection(glm::perspective( - glm::radians(wideFOV), // hack + glm::radians(cameraFov), cameraAspectRatio, cameraNearClip, cameraFarClip)); @@ -324,125 +338,25 @@ bool ViewFrustum::boxIntersectsKeyhole(const AABox& box) const { return true; } -bool testMatches(glm::quat lhs, glm::quat rhs, float epsilon = EPSILON) { - return (fabs(lhs.x - rhs.x) <= epsilon && fabs(lhs.y - rhs.y) <= epsilon && fabs(lhs.z - rhs.z) <= epsilon - && fabs(lhs.w - rhs.w) <= epsilon); +bool closeEnough(float a, float b, float relativeError) { + assert(relativeError >= 0.0f); + // NOTE: we add EPSILON to the denominator so we can avoid checking for division by zero. + // This method works fine when: fabsf(a + b) >> EPSILON + return fabsf(a - b) / (0.5f * fabsf(a + b) + EPSILON) < relativeError; } -bool testMatches(glm::vec3 lhs, glm::vec3 rhs, float epsilon = EPSILON) { - return (fabs(lhs.x - rhs.x) <= epsilon && fabs(lhs.y - rhs.y) <= epsilon && fabs(lhs.z - rhs.z) <= epsilon); -} +bool ViewFrustum::isVerySimilar(const ViewFrustum& other) const { + const float MIN_POSITION_SLOP_SQUARED = 25.0f; // 5 meters squared + const float MIN_ORIENTATION_DOT = 0.9924039f; // dot product of two quaternions 10 degrees apart + const float MIN_RELATIVE_ERROR = 0.01f; // 1% -bool testMatches(float lhs, float rhs, float epsilon = EPSILON) { - return (fabs(lhs - rhs) <= epsilon); -} - -bool ViewFrustum::matches(const ViewFrustum& compareTo, bool debug) const { - bool result = - testMatches(compareTo._position, _position) && - testMatches(compareTo._direction, _direction) && - testMatches(compareTo._up, _up) && - testMatches(compareTo._right, _right) && - testMatches(compareTo._fieldOfView, _fieldOfView) && - testMatches(compareTo._aspectRatio, _aspectRatio) && - testMatches(compareTo._nearClip, _nearClip) && - testMatches(compareTo._farClip, _farClip) && - testMatches(compareTo._focalLength, _focalLength); - - if (!result && debug) { - qCDebug(shared, "ViewFrustum::matches()... result=%s", debug::valueOf(result)); - qCDebug(shared, "%s -- compareTo._position=%f,%f,%f _position=%f,%f,%f", - (testMatches(compareTo._position,_position) ? "MATCHES " : "NO MATCH"), - (double)compareTo._position.x, (double)compareTo._position.y, (double)compareTo._position.z, - (double)_position.x, (double)_position.y, (double)_position.z); - qCDebug(shared, "%s -- compareTo._direction=%f,%f,%f _direction=%f,%f,%f", - (testMatches(compareTo._direction, _direction) ? "MATCHES " : "NO MATCH"), - (double)compareTo._direction.x, (double)compareTo._direction.y, (double)compareTo._direction.z, - (double)_direction.x, (double)_direction.y, (double)_direction.z ); - qCDebug(shared, "%s -- compareTo._up=%f,%f,%f _up=%f,%f,%f", - (testMatches(compareTo._up, _up) ? "MATCHES " : "NO MATCH"), - (double)compareTo._up.x, (double)compareTo._up.y, (double)compareTo._up.z, - (double)_up.x, (double)_up.y, (double)_up.z ); - qCDebug(shared, "%s -- compareTo._right=%f,%f,%f _right=%f,%f,%f", - (testMatches(compareTo._right, _right) ? "MATCHES " : "NO MATCH"), - (double)compareTo._right.x, (double)compareTo._right.y, (double)compareTo._right.z, - (double)_right.x, (double)_right.y, (double)_right.z ); - qCDebug(shared, "%s -- compareTo._fieldOfView=%f _fieldOfView=%f", - (testMatches(compareTo._fieldOfView, _fieldOfView) ? "MATCHES " : "NO MATCH"), - (double)compareTo._fieldOfView, (double)_fieldOfView); - qCDebug(shared, "%s -- compareTo._aspectRatio=%f _aspectRatio=%f", - (testMatches(compareTo._aspectRatio, _aspectRatio) ? "MATCHES " : "NO MATCH"), - (double)compareTo._aspectRatio, (double)_aspectRatio); - qCDebug(shared, "%s -- compareTo._nearClip=%f _nearClip=%f", - (testMatches(compareTo._nearClip, _nearClip) ? "MATCHES " : "NO MATCH"), - (double)compareTo._nearClip, (double)_nearClip); - qCDebug(shared, "%s -- compareTo._farClip=%f _farClip=%f", - (testMatches(compareTo._farClip, _farClip) ? "MATCHES " : "NO MATCH"), - (double)compareTo._farClip, (double)_farClip); - qCDebug(shared, "%s -- compareTo._focalLength=%f _focalLength=%f", - (testMatches(compareTo._focalLength, _focalLength) ? "MATCHES " : "NO MATCH"), - (double)compareTo._focalLength, (double)_focalLength); - } - return result; -} - -bool ViewFrustum::isVerySimilar(const ViewFrustum& compareTo, bool debug) const { - - // Compute distance between the two positions - const float POSITION_SIMILAR_ENOUGH = 5.0f; // 5 meters - float positionDistance = glm::distance(_position, compareTo._position); - - // Compute the angular distance between the two orientations - const float ORIENTATION_SIMILAR_ENOUGH = 10.0f; // 10 degrees in any direction - glm::quat dQOrientation = _orientation * glm::inverse(compareTo._orientation); - float angleOrientation = compareTo._orientation == _orientation ? 0.0f : glm::degrees(glm::angle(dQOrientation)); - if (isNaN(angleOrientation)) { - angleOrientation = 0.0f; - } - - bool result = - testMatches(0, positionDistance, POSITION_SIMILAR_ENOUGH) && - testMatches(0, angleOrientation, ORIENTATION_SIMILAR_ENOUGH) && - testMatches(compareTo._centerSphereRadius, _centerSphereRadius) && - testMatches(compareTo._fieldOfView, _fieldOfView) && - testMatches(compareTo._aspectRatio, _aspectRatio) && - testMatches(compareTo._nearClip, _nearClip) && - testMatches(compareTo._farClip, _farClip) && - testMatches(compareTo._focalLength, _focalLength); - - if (!result && debug) { - qCDebug(shared, "ViewFrustum::isVerySimilar()... result=%s\n", debug::valueOf(result)); - qCDebug(shared, "%s -- compareTo._position=%f,%f,%f _position=%f,%f,%f", - (testMatches(compareTo._position,_position, POSITION_SIMILAR_ENOUGH) ? - "IS SIMILAR ENOUGH " : "IS NOT SIMILAR ENOUGH"), - (double)compareTo._position.x, (double)compareTo._position.y, (double)compareTo._position.z, - (double)_position.x, (double)_position.y, (double)_position.z ); - - qCDebug(shared, "%s -- positionDistance=%f", - (testMatches(0,positionDistance, POSITION_SIMILAR_ENOUGH) ? "IS SIMILAR ENOUGH " : "IS NOT SIMILAR ENOUGH"), - (double)positionDistance); - - qCDebug(shared, "%s -- angleOrientation=%f", - (testMatches(0, angleOrientation, ORIENTATION_SIMILAR_ENOUGH) ? "IS SIMILAR ENOUGH " : "IS NOT SIMILAR ENOUGH"), - (double)angleOrientation); - - qCDebug(shared, "%s -- compareTo._fieldOfView=%f _fieldOfView=%f", - (testMatches(compareTo._fieldOfView, _fieldOfView) ? "MATCHES " : "NO MATCH"), - (double)compareTo._fieldOfView, (double)_fieldOfView); - qCDebug(shared, "%s -- compareTo._aspectRatio=%f _aspectRatio=%f", - (testMatches(compareTo._aspectRatio, _aspectRatio) ? "MATCHES " : "NO MATCH"), - (double)compareTo._aspectRatio, (double)_aspectRatio); - qCDebug(shared, "%s -- compareTo._nearClip=%f _nearClip=%f", - (testMatches(compareTo._nearClip, _nearClip) ? "MATCHES " : "NO MATCH"), - (double)compareTo._nearClip, (double)_nearClip); - qCDebug(shared, "%s -- compareTo._farClip=%f _farClip=%f", - (testMatches(compareTo._farClip, _farClip) ? "MATCHES " : "NO MATCH"), - (double)compareTo._farClip, (double)_farClip); - qCDebug(shared, "%s -- compareTo._focalLength=%f _focalLength=%f", - (testMatches(compareTo._focalLength, _focalLength) ? "MATCHES " : "NO MATCH"), - (double)compareTo._focalLength, (double)_focalLength); - } - return result; + return glm::distance2(_position, other._position) < MIN_POSITION_SLOP_SQUARED && + fabsf(glm::dot(_orientation, other._orientation)) > MIN_ORIENTATION_DOT && + closeEnough(_fieldOfView, other._fieldOfView, MIN_RELATIVE_ERROR) && + closeEnough(_aspectRatio, other._aspectRatio, MIN_RELATIVE_ERROR) && + closeEnough(_nearClip, other._nearClip, MIN_RELATIVE_ERROR) && + closeEnough(_farClip, other._farClip, MIN_RELATIVE_ERROR) && + closeEnough(_focalLength, other._focalLength, MIN_RELATIVE_ERROR); } PickRay ViewFrustum::computePickRay(float x, float y) { diff --git a/libraries/shared/src/ViewFrustum.h b/libraries/shared/src/ViewFrustum.h index 221b0b5a07..acf463810d 100644 --- a/libraries/shared/src/ViewFrustum.h +++ b/libraries/shared/src/ViewFrustum.h @@ -91,6 +91,7 @@ public: float getCenterRadius() const { return _centerSphereRadius; } void calculate(); + void calculateProjection(); typedef enum { OUTSIDE = 0, INTERSECT, INSIDE } intersection; @@ -107,12 +108,7 @@ public: bool cubeIntersectsKeyhole(const AACube& cube) const; bool boxIntersectsKeyhole(const AABox& box) const; - // some frustum comparisons - bool matches(const ViewFrustum& compareTo, bool debug = false) const; - bool matches(const ViewFrustum* compareTo, bool debug = false) const { return matches(*compareTo, debug); } - - bool isVerySimilar(const ViewFrustum& compareTo, bool debug = false) const; - bool isVerySimilar(const ViewFrustum* compareTo, bool debug = false) const { return isVerySimilar(*compareTo, debug); } + bool isVerySimilar(const ViewFrustum& compareTo) const; PickRay computePickRay(float x, float y); void computePickRay(float x, float y, glm::vec3& origin, glm::vec3& direction) const; From d061627a1d63a104626e1bf90c99d89637103f7c Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 7 Sep 2017 16:00:19 -0700 Subject: [PATCH 59/88] reasonable values for iitial OctreeQueryNode view make sure newViewFrustum is fully initialized before using it --- libraries/octree/src/OctreeQuery.cpp | 13 ++++++++++++- libraries/octree/src/OctreeQuery.h | 16 ++++++++-------- libraries/octree/src/OctreeQueryNode.cpp | 3 ++- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/libraries/octree/src/OctreeQuery.cpp b/libraries/octree/src/OctreeQuery.cpp index 7d9fc7d08c..a88f730a50 100644 --- a/libraries/octree/src/OctreeQuery.cpp +++ b/libraries/octree/src/OctreeQuery.cpp @@ -17,7 +17,18 @@ #include "OctreeConstants.h" #include "OctreeQuery.h" -OctreeQuery::OctreeQuery() { +const float DEFAULT_FOV = 45.0f; // degrees +const float DEFAULT_ASPECT_RATIO = 1.0f; +const float DEFAULT_NEAR_CLIP = 0.1f; +const float DEFAULT_FAR_CLIP = 3.0f; + +OctreeQuery::OctreeQuery() : + _cameraFov(DEFAULT_FOV), + _cameraAspectRatio(DEFAULT_ASPECT_RATIO), + _cameraNearClip(DEFAULT_NEAR_CLIP), + _cameraFarClip(DEFAULT_FAR_CLIP), + _cameraCenterRadius(DEFAULT_FAR_CLIP) +{ _maxQueryPPS = DEFAULT_MAX_OCTREE_PPS; } diff --git a/libraries/octree/src/OctreeQuery.h b/libraries/octree/src/OctreeQuery.h index 058c1dc585..81a63a696c 100644 --- a/libraries/octree/src/OctreeQuery.h +++ b/libraries/octree/src/OctreeQuery.h @@ -89,14 +89,14 @@ public slots: protected: // camera details for the avatar - glm::vec3 _cameraPosition = glm::vec3(0.0f); - glm::quat _cameraOrientation = glm::quat(); - float _cameraFov = 0.0f; - float _cameraAspectRatio = 1.0f; - float _cameraNearClip = 0.0f; - float _cameraFarClip = 0.0f; - float _cameraCenterRadius { 0.0f }; - glm::vec3 _cameraEyeOffsetPosition = glm::vec3(0.0f); + glm::vec3 _cameraPosition { glm::vec3(0.0f) }; + glm::quat _cameraOrientation { glm::quat() }; + float _cameraFov; + float _cameraAspectRatio; + float _cameraNearClip; + float _cameraFarClip; + float _cameraCenterRadius; + glm::vec3 _cameraEyeOffsetPosition { glm::vec3(0.0f) }; // octree server sending items int _maxQueryPPS = DEFAULT_MAX_OCTREE_PPS; diff --git a/libraries/octree/src/OctreeQueryNode.cpp b/libraries/octree/src/OctreeQueryNode.cpp index 4ebe650f6a..3003d76d14 100644 --- a/libraries/octree/src/OctreeQueryNode.cpp +++ b/libraries/octree/src/OctreeQueryNode.cpp @@ -182,6 +182,7 @@ bool OctreeQueryNode::updateCurrentViewFrustum() { getCameraAspectRatio(), getCameraNearClip(), getCameraFarClip())); + newestViewFrustum.calculate(); } @@ -189,7 +190,7 @@ bool OctreeQueryNode::updateCurrentViewFrustum() { QMutexLocker viewLocker(&_viewMutex); if (!newestViewFrustum.isVerySimilar(_currentViewFrustum)) { _currentViewFrustum = newestViewFrustum; - _currentViewFrustum.calculate(); + //_currentViewFrustum.calculateProjection(); currentViewFrustumChanged = true; } } From a22e5771002e133680e8e32049ae91505b4f953b Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 7 Sep 2017 16:02:51 -0700 Subject: [PATCH 60/88] zero out OVERSEND hack --- libraries/octree/src/OctreeConstants.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/octree/src/OctreeConstants.h b/libraries/octree/src/OctreeConstants.h index 06f09e557c..b839b90fce 100644 --- a/libraries/octree/src/OctreeConstants.h +++ b/libraries/octree/src/OctreeConstants.h @@ -37,7 +37,7 @@ const int NUMBER_OF_CHILDREN = 8; const int MAX_TREE_SLICE_BYTES = 26; -const float VIEW_FRUSTUM_FOV_OVERSEND = 60.0f; +const float VIEW_FRUSTUM_FOV_OVERSEND = 0.0f; // These are guards to prevent our voxel tree recursive routines from spinning out of control const int UNREASONABLY_DEEP_RECURSION = 29; // use this for something that you want to be shallow, but not spin out From fb3e74398fd6d733279892d09ce0efa2d67c2be5 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 7 Sep 2017 16:03:20 -0700 Subject: [PATCH 61/88] don't invalidate viewFrustum sent to server --- interface/src/Application.cpp | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 6762217485..60a653fdc9 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5433,6 +5433,7 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node << perUnknownServer << " to send us jurisdiction."; } + // TODO: remove this hackery: it no longer makes sense for streaming of entities in scene. // set the query's position/orientation to be degenerate in a manner that will get the scene quickly // If there's only one server, then don't do this, and just let the normal voxel query pass through // as expected... this way, we will actually get a valid scene if there is one to be seen @@ -5455,16 +5456,6 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node _octreeQuery.setMaxQueryPacketsPerSecond(0); } - // if asked to forceResend, then set the query's position/orientation to be degenerate in a manner - // that will cause our next query to be guarenteed to be different and the server will resend to us - if (forceResend) { - _octreeQuery.setCameraPosition(glm::vec3(-0.1, -0.1, -0.1)); - const glm::quat OFF_IN_NEGATIVE_SPACE = glm::quat(-0.5, 0, -0.5, 1.0); - _octreeQuery.setCameraOrientation(OFF_IN_NEGATIVE_SPACE); - _octreeQuery.setCameraNearClip(0.1f); - _octreeQuery.setCameraFarClip(0.1f); - } - // encode the query data int packetSize = _octreeQuery.getBroadcastData(reinterpret_cast(queryPacket->getPayload())); queryPacket->setPayloadSize(packetSize); From 3433c5c4145cd6786244532d9e089a6573e17fdd Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 8 Sep 2017 15:01:36 -0700 Subject: [PATCH 62/88] remove redundant boolean logic --- .../src/entities/EntityTreeSendThread.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index 817df55625..59a47f44aa 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -308,8 +308,7 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree return; } auto knownTimestamp = _knownState.find(entity.get()); - if (knownTimestamp == _knownState.end() || - (knownTimestamp != _knownState.end() && entity->getLastEdited() > knownTimestamp->second)) { + if (knownTimestamp == _knownState.end() || entity->getLastEdited() > knownTimestamp->second) { bool success = false; AACube cube = entity->getQueryAACube(success); if (success) { @@ -345,8 +344,7 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree return; } auto knownTimestamp = _knownState.find(entity.get()); - if (knownTimestamp == _knownState.end() || - (knownTimestamp != _knownState.end() && entity->getLastEdited() > knownTimestamp->second)) { + if (knownTimestamp == _knownState.end() || entity->getLastEdited() > knownTimestamp->second) { _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); _entitiesInQueue.insert(entity.get()); } @@ -368,8 +366,7 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree return; } auto knownTimestamp = _knownState.find(entity.get()); - if (knownTimestamp == _knownState.end() || - (knownTimestamp != _knownState.end() && entity->getLastEdited() > knownTimestamp->second)) { + if (knownTimestamp == _knownState.end() || entity->getLastEdited() > knownTimestamp->second) { bool success = false; AACube cube = entity->getQueryAACube(success); if (success) { @@ -519,4 +516,4 @@ void EntityTreeSendThread::editingEntityPointer(const EntityItemPointer entity) void EntityTreeSendThread::deletingEntityPointer(EntityItem* entity) { _knownState.erase(entity); -} \ No newline at end of file +} From a55661e1ff96b5c2fcc400dee6e797abe8636c87 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 8 Sep 2017 15:04:14 -0700 Subject: [PATCH 63/88] remove ViewFrustum::calculateProjection() --- libraries/octree/src/OctreeQueryNode.cpp | 1 - libraries/shared/src/ViewFrustum.cpp | 17 ----------------- libraries/shared/src/ViewFrustum.h | 1 - 3 files changed, 19 deletions(-) diff --git a/libraries/octree/src/OctreeQueryNode.cpp b/libraries/octree/src/OctreeQueryNode.cpp index 3003d76d14..c26b4ce77b 100644 --- a/libraries/octree/src/OctreeQueryNode.cpp +++ b/libraries/octree/src/OctreeQueryNode.cpp @@ -190,7 +190,6 @@ bool OctreeQueryNode::updateCurrentViewFrustum() { QMutexLocker viewLocker(&_viewMutex); if (!newestViewFrustum.isVerySimilar(_currentViewFrustum)) { _currentViewFrustum = newestViewFrustum; - //_currentViewFrustum.calculateProjection(); currentViewFrustumChanged = true; } } diff --git a/libraries/shared/src/ViewFrustum.cpp b/libraries/shared/src/ViewFrustum.cpp index 0ba5d4c9b3..aac8683b08 100644 --- a/libraries/shared/src/ViewFrustum.cpp +++ b/libraries/shared/src/ViewFrustum.cpp @@ -118,23 +118,6 @@ void ViewFrustum::calculate() { _ourModelViewProjectionMatrix = _projection * view; // Remember, matrix multiplication is the other way around } -void ViewFrustum::calculateProjection() { - if (0.0f != _aspectRatio && 0.0f != _nearClip && 0.0f != _farClip && _nearClip != _farClip) { - // _projection is calculated from the frustum parameters - _projection = glm::perspective( glm::radians(_fieldOfView), _aspectRatio, _nearClip, _farClip); - - // frustum corners are computed from inverseProjection - glm::mat4 inverseProjection = glm::inverse(_projection); - for (int i = 0; i < NUM_FRUSTUM_CORNERS; ++i) { - _corners[i] = inverseProjection * NDC_VALUES[i]; - _corners[i] /= _corners[i].w; - } - - // finally calculate planes and _ourModelViewProjectionMatrix - calculate(); - } -} - //enum { TOP_PLANE = 0, BOTTOM_PLANE, LEFT_PLANE, RIGHT_PLANE, NEAR_PLANE, FAR_PLANE }; const char* ViewFrustum::debugPlaneName (int plane) const { switch (plane) { diff --git a/libraries/shared/src/ViewFrustum.h b/libraries/shared/src/ViewFrustum.h index acf463810d..d1b88fb2a5 100644 --- a/libraries/shared/src/ViewFrustum.h +++ b/libraries/shared/src/ViewFrustum.h @@ -91,7 +91,6 @@ public: float getCenterRadius() const { return _centerSphereRadius; } void calculate(); - void calculateProjection(); typedef enum { OUTSIDE = 0, INTERSECT, INSIDE } intersection; From d55d45f6aa284cc479632c282e974b19ef780461 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 8 Sep 2017 15:24:21 -0700 Subject: [PATCH 64/88] check radius in ViewFrustum::isVerySimilar() --- libraries/shared/src/ViewFrustum.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/shared/src/ViewFrustum.cpp b/libraries/shared/src/ViewFrustum.cpp index aac8683b08..7c93b103cf 100644 --- a/libraries/shared/src/ViewFrustum.cpp +++ b/libraries/shared/src/ViewFrustum.cpp @@ -339,7 +339,8 @@ bool ViewFrustum::isVerySimilar(const ViewFrustum& other) const { closeEnough(_aspectRatio, other._aspectRatio, MIN_RELATIVE_ERROR) && closeEnough(_nearClip, other._nearClip, MIN_RELATIVE_ERROR) && closeEnough(_farClip, other._farClip, MIN_RELATIVE_ERROR) && - closeEnough(_focalLength, other._focalLength, MIN_RELATIVE_ERROR); + closeEnough(_focalLength, other._focalLength, MIN_RELATIVE_ERROR), + closeEnough(_centerSphereRadius, other._centerSphereRadius, MIN_RELATIVE_ERROR); } PickRay ViewFrustum::computePickRay(float x, float y) { From 355a59edb1fec825a721a8df889a8397a54b9f3b Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 8 Sep 2017 20:10:18 -0700 Subject: [PATCH 65/88] fix missing entities in differential traversal --- libraries/entities/src/DiffTraversal.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/libraries/entities/src/DiffTraversal.cpp b/libraries/entities/src/DiffTraversal.cpp index 86296c82de..d69cef5b8e 100644 --- a/libraries/entities/src/DiffTraversal.cpp +++ b/libraries/entities/src/DiffTraversal.cpp @@ -137,9 +137,9 @@ void DiffTraversal::Waypoint::getNextVisibleElementDifferential(DiffTraversal::V ++_nextIndex; if (nextElement) { AACube cube = nextElement->getAACube(); - if ( view.viewFrustum.calculateCubeKeyholeIntersection(cube) != ViewFrustum::OUTSIDE) { + if (view.viewFrustum.calculateCubeKeyholeIntersection(cube) != ViewFrustum::OUTSIDE) { ViewFrustum::intersection lastIntersection = lastView.viewFrustum.calculateCubeKeyholeIntersection(cube); - if (!(lastIntersection == ViewFrustum::INSIDE && nextElement->getLastChanged() < lastView.startTime)) { + if (lastIntersection != ViewFrustum::INSIDE || nextElement->getLastChanged() > lastView.startTime) { next.element = nextElement; // NOTE: for differential case next.intersection is against the lastView // because this helps the "external scan" optimize its culling @@ -153,7 +153,8 @@ void DiffTraversal::Waypoint::getNextVisibleElementDifferential(DiffTraversal::V distance2 = glm::distance2(lastView.viewFrustum.getPosition(), element->getAACube().calcCenter()); if (distance2 >= visibleLimit * visibleLimit) { next.element = nextElement; - next.intersection = lastIntersection; + // element's intersection with lastView was effectively OUTSIDE + next.intersection = ViewFrustum::OUTSIDE; return; } } From a0f95ca5bd57833542dfa9248a535b995031af63 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 8 Sep 2017 20:11:17 -0700 Subject: [PATCH 66/88] swap order of evaluation for minor theoretical speedup --- assignment-client/src/entities/EntityTreeSendThread.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index 59a47f44aa..d08cc725ce 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -358,8 +358,8 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree _traversal.setScanCallback([&] (DiffTraversal::VisibleElement& next) { // NOTE: for Differential case: next.intersection is against completedView not currentView uint64_t startOfCompletedTraversal = _traversal.getStartOfCompletedTraversal(); - if (next.element->getLastChangedContent() > startOfCompletedTraversal || - next.intersection != ViewFrustum::INSIDE) { + if (next.intersection != ViewFrustum::INSIDE || + next.element->getLastChangedContent() > startOfCompletedTraversal) { next.element->forEachEntity([&](EntityItemPointer entity) { // Bail early if we've already checked this entity this frame if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) { From bf1065b56ebc409aed2d1ade77e1a5f8006d8fa7 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Mon, 11 Sep 2017 10:06:18 -0700 Subject: [PATCH 67/88] track encode stats --- assignment-client/src/entities/EntityTreeSendThread.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index d08cc725ce..5e4a44fe71 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -414,8 +414,10 @@ bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstream #ifdef SEND_SORTED_ENTITIES //auto entityTree = std::static_pointer_cast(_myServer->getOctree()); if (_sendQueue.empty()) { + OctreeServer::trackEncodeTime(OctreeServer::SKIP_TIME); return false; } + quint64 encodeStart = usecTimestampNow(); if (!_packetData.hasContent()) { // This is the beginning of a new packet. // We pack minimal data for this to be accepted as an OctreeElement payload for the root element. @@ -450,6 +452,8 @@ bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstream LevelDetails entitiesLevel = _packetData.startLevel(); uint64_t sendTime = usecTimestampNow(); + auto nodeData = static_cast(params.nodeData); + nodeData->stats.encodeStarted(); while(!_sendQueue.empty()) { PrioritizedEntity queuedItem = _sendQueue.top(); EntityItemPointer entity = queuedItem.getEntity(); @@ -476,6 +480,7 @@ bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstream _sendQueue.pop(); _entitiesInQueue.erase(entity.get()); } + nodeData->stats.encodeStopped(); if (_sendQueue.empty()) { assert(_entitiesInQueue.empty()); params.stopReason = EncodeBitstreamParams::FINISHED; @@ -484,10 +489,12 @@ bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstream if (_numEntities == 0) { _packetData.discardLevel(entitiesLevel); + OctreeServer::trackEncodeTime((float)(usecTimestampNow() - encodeStart)); return false; } _packetData.endLevel(entitiesLevel); _packetData.updatePriorBytes(_numEntitiesOffset, (const unsigned char*)&_numEntities, sizeof(_numEntities)); + OctreeServer::trackEncodeTime((float)(usecTimestampNow() - encodeStart)); return true; #else // SEND_SORTED_ENTITIES From cbd20f89ddc2b417ad157892bb98e4b512bd5c43 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Mon, 11 Sep 2017 10:26:27 -0700 Subject: [PATCH 68/88] separate elementBag logic from EntityTreeSendThread --- .../src/entities/EntityTreeSendThread.h | 7 ++- .../src/octree/OctreeSendThread.cpp | 47 ++++++++++--------- .../src/octree/OctreeSendThread.h | 10 +++- 3 files changed, 38 insertions(+), 26 deletions(-) diff --git a/assignment-client/src/entities/EntityTreeSendThread.h b/assignment-client/src/entities/EntityTreeSendThread.h index c9751c5835..72ad2d0b10 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.h +++ b/assignment-client/src/entities/EntityTreeSendThread.h @@ -30,7 +30,6 @@ public: EntityTreeSendThread(OctreeServer* myServer, const SharedNodePointer& node); protected: - void preDistributionProcessing() override; void traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged, bool isFullScene) override; @@ -42,6 +41,12 @@ private: void startNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset, bool usesViewFrustum); bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) override; + void preDistributionProcessing() override; + bool hasSomethingToSend(OctreeQueryNode* nodeData) override { return !_sendQueue.empty(); } + bool shouldStartNewTraversal(OctreeQueryNode* nodeData, bool viewFrustumChanged) override { return viewFrustumChanged || _traversal.finished(); } + void preStartNewScene(OctreeQueryNode* nodeData, bool isFullScene) override {}; + bool shouldTraverseAndSend(OctreeQueryNode* nodeData) override { return true; } + DiffTraversal _traversal; EntityPriorityQueue _sendQueue; std::unordered_set _entitiesInQueue; diff --git a/assignment-client/src/octree/OctreeSendThread.cpp b/assignment-client/src/octree/OctreeSendThread.cpp index 5a563037bc..7d209e64dc 100644 --- a/assignment-client/src/octree/OctreeSendThread.cpp +++ b/assignment-client/src/octree/OctreeSendThread.cpp @@ -17,7 +17,6 @@ #include #include -#include "OctreeQueryNode.h" #include "OctreeSendThread.h" #include "OctreeServer.h" #include "OctreeServerConsts.h" @@ -301,9 +300,25 @@ int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode* return numPackets; } +void OctreeSendThread::preStartNewScene(OctreeQueryNode* nodeData, bool isFullScene) { + // If we're starting a full scene, then definitely we want to empty the elementBag + if (isFullScene) { + nodeData->elementBag.deleteAll(); + } + + // This is the start of "resending" the scene. + bool dontRestartSceneOnMove = false; // this is experimental + if (dontRestartSceneOnMove) { + if (nodeData->elementBag.isEmpty()) { + nodeData->elementBag.insert(_myServer->getOctree()->getRoot()); + } + } else { + nodeData->elementBag.insert(_myServer->getOctree()->getRoot()); + } +} + /// Version of octree element distributor that sends the deepest LOD level at once int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged) { - OctreeServer::didPacketDistributor(this); // if shutting down, exit early @@ -311,7 +326,7 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* return 0; } - if (nodeData->elementBag.isEmpty()) { + if (shouldStartNewTraversal(nodeData, viewFrustumChanged)) { // if we're about to do a fresh pass, // give our pre-distribution processing a chance to do what it needs preDistributionProcessing(); @@ -345,7 +360,7 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* // If the current view frustum has changed OR we have nothing to send, then search against // the current view frustum for things to send. - if (viewFrustumChanged || nodeData->elementBag.isEmpty()) { + if (shouldStartNewTraversal(nodeData, viewFrustumChanged)) { // if our view has changed, we need to reset these things... if (viewFrustumChanged) { @@ -367,11 +382,6 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* _packetsSentThisInterval += handlePacketSend(node, nodeData, isFullScene); - // If we're starting a full scene, then definitely we want to empty the elementBag - if (isFullScene) { - nodeData->elementBag.deleteAll(); - } - // TODO: add these to stats page //::startSceneSleepTime = _usleepTime; @@ -380,19 +390,11 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* nodeData->stats.sceneStarted(isFullScene, viewFrustumChanged, _myServer->getOctree()->getRoot(), _myServer->getJurisdiction()); - // This is the start of "resending" the scene. - bool dontRestartSceneOnMove = false; // this is experimental - if (dontRestartSceneOnMove) { - if (nodeData->elementBag.isEmpty()) { - nodeData->elementBag.insert(_myServer->getOctree()->getRoot()); - } - } else { - nodeData->elementBag.insert(_myServer->getOctree()->getRoot()); - } + preStartNewScene(nodeData, isFullScene); } // If we have something in our elementBag, then turn them into packets and send them out... - if (!nodeData->elementBag.isEmpty()) { + if (shouldTraverseAndSend(nodeData)) { quint64 start = usecTimestampNow(); traverseTreeAndSendContents(node, nodeData, viewFrustumChanged, isFullScene); @@ -441,7 +443,7 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* // if after sending packets we've emptied our bag, then we want to remember that we've sent all // the octree elements from the current view frustum - if (nodeData->elementBag.isEmpty()) { + if (!hasSomethingToSend(nodeData)) { nodeData->updateLastKnownViewFrustum(); nodeData->setViewSent(true); @@ -502,8 +504,7 @@ void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, Octre EncodeBitstreamParams params(INT_MAX, WANT_EXISTS_BITS, DONT_CHOP, viewFrustumChanged, boundaryLevelAdjust, octreeSizeScale, isFullScene, _myServer->getJurisdiction(), nodeData); - // Our trackSend() function is implemented by the server subclass, and will be called back - // during the encodeTreeBitstream() as new entities/data elements are sent + // Our trackSend() function is implemented by the server subclass, and will be called back as new entities/data elements are sent params.trackSend = [this](const QUuid& dataID, quint64 dataEdited) { _myServer->trackSend(dataID, dataEdited, _nodeUuid); }; @@ -513,7 +514,7 @@ void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, Octre } bool somethingToSend = true; // assume we have something - bool bagHadSomething = !nodeData->elementBag.isEmpty(); + bool bagHadSomething = hasSomethingToSend(nodeData); while (somethingToSend && _packetsSentThisInterval < maxPacketsPerInterval && !nodeData->isShuttingDown()) { float compressAndWriteElapsedUsec = OctreeServer::SKIP_TIME; float packetSendingElapsedUsec = OctreeServer::SKIP_TIME; diff --git a/assignment-client/src/octree/OctreeSendThread.h b/assignment-client/src/octree/OctreeSendThread.h index a6ceba0e95..bc7d2c2588 100644 --- a/assignment-client/src/octree/OctreeSendThread.h +++ b/assignment-client/src/octree/OctreeSendThread.h @@ -19,6 +19,7 @@ #include #include #include +#include "OctreeQueryNode.h" class OctreeQueryNode; class OctreeServer; @@ -51,8 +52,6 @@ protected: /// Implements generic processing behavior for this thread. virtual bool process() override; - /// Called before a packetDistributor pass to allow for pre-distribution processing - virtual void preDistributionProcessing() {}; virtual void traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged, bool isFullScene); virtual bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters); @@ -62,9 +61,16 @@ protected: OctreeServer* _myServer { nullptr }; private: + /// Called before a packetDistributor pass to allow for pre-distribution processing + virtual void preDistributionProcessing() {}; int handlePacketSend(SharedNodePointer node, OctreeQueryNode* nodeData, bool dontSuppressDuplicate = false); int packetDistributor(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged); + virtual bool hasSomethingToSend(OctreeQueryNode* nodeData) { return !nodeData->elementBag.isEmpty(); } + virtual bool shouldStartNewTraversal(OctreeQueryNode* nodeData, bool viewFrustumChanged) { return viewFrustumChanged || !hasSomethingToSend(nodeData); } + virtual void preStartNewScene(OctreeQueryNode* nodeData, bool isFullScene); + virtual bool shouldTraverseAndSend(OctreeQueryNode* nodeData) { return hasSomethingToSend(nodeData); } + QUuid _nodeUuid; int _truePacketsSent { 0 }; // available for debug stats From c39ac93fc8a5d15eacf11ba6a9d2629f63a580ca Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Mon, 11 Sep 2017 11:24:24 -0700 Subject: [PATCH 69/88] fix isVerySimilar --- libraries/shared/src/ViewFrustum.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/shared/src/ViewFrustum.cpp b/libraries/shared/src/ViewFrustum.cpp index 7c93b103cf..ccdeb830b6 100644 --- a/libraries/shared/src/ViewFrustum.cpp +++ b/libraries/shared/src/ViewFrustum.cpp @@ -339,7 +339,7 @@ bool ViewFrustum::isVerySimilar(const ViewFrustum& other) const { closeEnough(_aspectRatio, other._aspectRatio, MIN_RELATIVE_ERROR) && closeEnough(_nearClip, other._nearClip, MIN_RELATIVE_ERROR) && closeEnough(_farClip, other._farClip, MIN_RELATIVE_ERROR) && - closeEnough(_focalLength, other._focalLength, MIN_RELATIVE_ERROR), + closeEnough(_focalLength, other._focalLength, MIN_RELATIVE_ERROR) && closeEnough(_centerSphereRadius, other._centerSphereRadius, MIN_RELATIVE_ERROR); } From b1b77640561726e70695fe82522520fdc169a381 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 11 Sep 2017 12:50:52 -0700 Subject: [PATCH 70/88] use 20 degrees of OVERSEND --- libraries/octree/src/OctreeConstants.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/octree/src/OctreeConstants.h b/libraries/octree/src/OctreeConstants.h index b839b90fce..06b4748f51 100644 --- a/libraries/octree/src/OctreeConstants.h +++ b/libraries/octree/src/OctreeConstants.h @@ -37,7 +37,7 @@ const int NUMBER_OF_CHILDREN = 8; const int MAX_TREE_SLICE_BYTES = 26; -const float VIEW_FRUSTUM_FOV_OVERSEND = 0.0f; +const float VIEW_FRUSTUM_FOV_OVERSEND = 20.0f; // These are guards to prevent our voxel tree recursive routines from spinning out of control const int UNREASONABLY_DEEP_RECURSION = 29; // use this for something that you want to be shallow, but not spin out From 624d0c12a27c7d320ad67273172c0b78cfbeef4c Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 11 Sep 2017 12:51:01 -0700 Subject: [PATCH 71/88] minor cleanup --- assignment-client/src/octree/OctreeSendThread.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assignment-client/src/octree/OctreeSendThread.cpp b/assignment-client/src/octree/OctreeSendThread.cpp index 7d209e64dc..32d7ce5053 100644 --- a/assignment-client/src/octree/OctreeSendThread.cpp +++ b/assignment-client/src/octree/OctreeSendThread.cpp @@ -47,7 +47,7 @@ OctreeSendThread::OctreeSendThread(OctreeServer* myServer, const SharedNodePoint OctreeSendThread::~OctreeSendThread() { setIsShuttingDown(); - + QString safeServerName("Octree"); if (_myServer) { safeServerName = _myServer->getMyServerName(); @@ -514,7 +514,7 @@ void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, Octre } bool somethingToSend = true; // assume we have something - bool bagHadSomething = hasSomethingToSend(nodeData); + bool hadSomething = hasSomethingToSend(nodeData); while (somethingToSend && _packetsSentThisInterval < maxPacketsPerInterval && !nodeData->isShuttingDown()) { float compressAndWriteElapsedUsec = OctreeServer::SKIP_TIME; float packetSendingElapsedUsec = OctreeServer::SKIP_TIME; From f2de03bc38af175644be6469480c1e0623173577 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Tue, 12 Sep 2017 16:11:58 -0700 Subject: [PATCH 72/88] small fixes and LOD cull children instead of parent --- .../src/entities/EntityTreeSendThread.cpp | 12 +- libraries/entities/src/DiffTraversal.cpp | 103 +++++++++--------- libraries/entities/src/DiffTraversal.h | 4 +- 3 files changed, 58 insertions(+), 61 deletions(-) diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index 5e4a44fe71..3d9bbe374e 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -121,7 +121,7 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O float renderAccuracy = calculateRenderAccuracy(_traversal.getCurrentView().getPosition(), cube, _traversal.getCurrentRootSizeScale(), - lodLevelOffset); + _traversal.getCurrentLODOffset()); // Only send entities if they are large enough to see if (renderAccuracy > 0.0f) { @@ -385,12 +385,12 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree _entitiesInQueue.insert(entity.get()); } else { // If this entity was skipped last time because it was too small, we still need to send it - float lastRenderAccuracy = calculateRenderAccuracy(_traversal.getCompletedView().getPosition(), - cube, - _traversal.getCompletedRootSizeScale(), - _traversal.getCompletedLODOffset()); + renderAccuracy = calculateRenderAccuracy(_traversal.getCompletedView().getPosition(), + cube, + _traversal.getCompletedRootSizeScale(), + _traversal.getCompletedLODOffset()); - if (lastRenderAccuracy <= 0.0f) { + if (renderAccuracy <= 0.0f) { float priority = _conicalView.computePriority(cube); _sendQueue.push(PrioritizedEntity(entity, priority)); _entitiesInQueue.insert(entity.get()); diff --git a/libraries/entities/src/DiffTraversal.cpp b/libraries/entities/src/DiffTraversal.cpp index d69cef5b8e..7658a7ac99 100644 --- a/libraries/entities/src/DiffTraversal.cpp +++ b/libraries/entities/src/DiffTraversal.cpp @@ -33,25 +33,22 @@ void DiffTraversal::Waypoint::getNextVisibleElementFirstTime(DiffTraversal::Visi } else if (_nextIndex < NUMBER_OF_CHILDREN) { EntityTreeElementPointer element = _weakElement.lock(); if (element) { - // No LOD truncation if we aren't using the view frustum - if (!view.usesViewFrustum) { - while (_nextIndex < NUMBER_OF_CHILDREN) { - EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); - ++_nextIndex; - if (nextElement) { + while (_nextIndex < NUMBER_OF_CHILDREN) { + EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); + ++_nextIndex; + if (nextElement) { + if (!view.usesViewFrustum) { + // No LOD truncation if we aren't using the view frustum next.element = nextElement; return; - } - } - } else { - // check for LOD truncation - float visibleLimit = boundaryDistanceForRenderLevel(element->getLevel() + view.lodLevelOffset, view.rootSizeScale); - float distance2 = glm::distance2(view.viewFrustum.getPosition(), element->getAACube().calcCenter()); - if (distance2 < visibleLimit * visibleLimit) { - while (_nextIndex < NUMBER_OF_CHILDREN) { - EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); - ++_nextIndex; - if (nextElement && view.viewFrustum.cubeIntersectsKeyhole(nextElement->getAACube())) { + } else if (view.viewFrustum.cubeIntersectsKeyhole(nextElement->getAACube())) { + // check for LOD truncation + float renderAccuracy = calculateRenderAccuracy(view.viewFrustum.getPosition(), + nextElement->getAACube(), + view.rootSizeScale, + view.lodLevelOffset); + + if (renderAccuracy > 0.0f) { next.element = nextElement; return; } @@ -78,28 +75,25 @@ void DiffTraversal::Waypoint::getNextVisibleElementRepeat( if (_nextIndex < NUMBER_OF_CHILDREN) { EntityTreeElementPointer element = _weakElement.lock(); if (element) { - // No LOD truncation if we aren't using the view frustum - if (!view.usesViewFrustum) { - while (_nextIndex < NUMBER_OF_CHILDREN) { - EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); - ++_nextIndex; - if (nextElement && nextElement->getLastChanged() > lastTime) { + while (_nextIndex < NUMBER_OF_CHILDREN) { + EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); + ++_nextIndex; + if (nextElement && nextElement->getLastChanged() > lastTime) { + if (!view.usesViewFrustum) { + // No LOD truncation if we aren't using the view frustum next.element = nextElement; next.intersection = ViewFrustum::INSIDE; return; - } - } - } else { - // check for LOD truncation - float visibleLimit = boundaryDistanceForRenderLevel(element->getLevel() + view.lodLevelOffset, view.rootSizeScale); - float distance2 = glm::distance2(view.viewFrustum.getPosition(), element->getAACube().calcCenter()); - if (distance2 < visibleLimit * visibleLimit) { - while (_nextIndex < NUMBER_OF_CHILDREN) { - EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); - ++_nextIndex; - if (nextElement && nextElement->getLastChanged() > lastTime) { - ViewFrustum::intersection intersection = view.viewFrustum.calculateCubeKeyholeIntersection(nextElement->getAACube()); - if (intersection != ViewFrustum::OUTSIDE) { + } else { + ViewFrustum::intersection intersection = view.viewFrustum.calculateCubeKeyholeIntersection(nextElement->getAACube()); + if (intersection != ViewFrustum::OUTSIDE) { + // check for LOD truncation + float renderAccuracy = calculateRenderAccuracy(view.viewFrustum.getPosition(), + nextElement->getAACube(), + view.rootSizeScale, + view.lodLevelOffset); + + if (renderAccuracy > 0.0f) { next.element = nextElement; next.intersection = intersection; return; @@ -123,21 +117,22 @@ void DiffTraversal::Waypoint::getNextVisibleElementDifferential(DiffTraversal::V next.element = element; next.intersection = ViewFrustum::INTERSECT; return; - } - if (_nextIndex < NUMBER_OF_CHILDREN) { + } else if (_nextIndex < NUMBER_OF_CHILDREN) { EntityTreeElementPointer element = _weakElement.lock(); if (element) { - // check for LOD truncation - int32_t level = element->getLevel() + view.lodLevelOffset; - float visibleLimit = boundaryDistanceForRenderLevel(level, view.rootSizeScale); - float distance2 = glm::distance2(view.viewFrustum.getPosition(), element->getAACube().calcCenter()); - if (distance2 < visibleLimit * visibleLimit) { - while (_nextIndex < NUMBER_OF_CHILDREN) { - EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); - ++_nextIndex; - if (nextElement) { - AACube cube = nextElement->getAACube(); - if (view.viewFrustum.calculateCubeKeyholeIntersection(cube) != ViewFrustum::OUTSIDE) { + while (_nextIndex < NUMBER_OF_CHILDREN) { + EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); + ++_nextIndex; + if (nextElement) { + AACube cube = nextElement->getAACube(); + if (view.viewFrustum.calculateCubeKeyholeIntersection(cube) != ViewFrustum::OUTSIDE) { + // check for LOD truncation + float renderAccuracy = calculateRenderAccuracy(view.viewFrustum.getPosition(), + cube, + view.rootSizeScale, + view.lodLevelOffset); + + if (renderAccuracy > 0.0f) { ViewFrustum::intersection lastIntersection = lastView.viewFrustum.calculateCubeKeyholeIntersection(cube); if (lastIntersection != ViewFrustum::INSIDE || nextElement->getLastChanged() > lastView.startTime) { next.element = nextElement; @@ -148,10 +143,12 @@ void DiffTraversal::Waypoint::getNextVisibleElementDifferential(DiffTraversal::V } else { // check for LOD truncation in the last traversal because // we may need to traverse this element after all if the lastView skipped it for LOD - int32_t lastLevel = element->getLevel() + lastView.lodLevelOffset; - visibleLimit = boundaryDistanceForRenderLevel(lastLevel, lastView.rootSizeScale); - distance2 = glm::distance2(lastView.viewFrustum.getPosition(), element->getAACube().calcCenter()); - if (distance2 >= visibleLimit * visibleLimit) { + renderAccuracy = calculateRenderAccuracy(lastView.viewFrustum.getPosition(), + cube, + lastView.rootSizeScale, + lastView.lodLevelOffset); + + if (renderAccuracy <= 0.0f) { next.element = nextElement; // element's intersection with lastView was effectively OUTSIDE next.intersection = ViewFrustum::OUTSIDE; diff --git a/libraries/entities/src/DiffTraversal.h b/libraries/entities/src/DiffTraversal.h index 2bd44d041e..bffe6c651e 100644 --- a/libraries/entities/src/DiffTraversal.h +++ b/libraries/entities/src/DiffTraversal.h @@ -68,8 +68,8 @@ public: bool doesCurrentUseViewFrustum() const { return _currentView.usesViewFrustum; } float getCurrentRootSizeScale() const { return _currentView.rootSizeScale; } float getCompletedRootSizeScale() const { return _completedView.rootSizeScale; } - float getCurrentLODOffset() const { return _currentView.lodLevelOffset; } - float getCompletedLODOffset() const { return _completedView.lodLevelOffset; } + int32_t getCurrentLODOffset() const { return _currentView.lodLevelOffset; } + int32_t getCompletedLODOffset() const { return _completedView.lodLevelOffset; } uint64_t getStartOfCompletedTraversal() const { return _completedView.startTime; } bool finished() const { return _path.empty(); } From 49e11d2173743a807c894946456da32ab78d4b2d Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 12 Sep 2017 22:27:42 -0700 Subject: [PATCH 73/88] fix Differential scan logic for LOD culling --- .../src/entities/EntityTreeSendThread.cpp | 152 +++++++++--------- libraries/entities/src/DiffTraversal.cpp | 73 +++------ libraries/entities/src/DiffTraversal.h | 9 +- libraries/octree/src/OctreeUtils.h | 6 + 4 files changed, 105 insertions(+), 135 deletions(-) diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index 3d9bbe374e..5552c2b580 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -106,6 +106,8 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O _sendQueue.swap(prevSendQueue); _entitiesInQueue.clear(); // Re-add elements from previous traversal if they still need to be sent + float lodScaleFactor = _traversal.getCurrentLODScaleFactor(); + glm::vec3 viewPosition = _traversal.getCurrentView().getPosition(); while (!prevSendQueue.empty()) { EntityItemPointer entity = prevSendQueue.top().getEntity(); bool forceRemove = prevSendQueue.top().shouldForceRemove(); @@ -118,13 +120,9 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { float priority = _conicalView.computePriority(cube); if (priority != PrioritizedEntity::DO_NOT_SEND) { - float renderAccuracy = calculateRenderAccuracy(_traversal.getCurrentView().getPosition(), - cube, - _traversal.getCurrentRootSizeScale(), - _traversal.getCurrentLODOffset()); - - // Only send entities if they are large enough to see - if (renderAccuracy > 0.0f) { + float distance = (glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE); + float apparentAngle = cube.getScale() / distance; + if (apparentAngle > MIN_ENTITY_APPARENT_ANGLE * lodScaleFactor) { _sendQueue.push(PrioritizedEntity(entity, priority)); _entitiesInQueue.insert(entity.get()); } @@ -144,19 +142,20 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O } if (!_traversal.finished()) { - uint64_t startTime = usecTimestampNow(); #ifdef DEBUG + uint64_t startTime = usecTimestampNow(); const uint64_t TIME_BUDGET = 400; // usec -#else - const uint64_t TIME_BUDGET = 200; // usec -#endif _traversal.traverse(TIME_BUDGET); if (_sendQueue.size() > 0) { uint64_t dt = usecTimestampNow() - startTime; std::cout << "adebug traversal complete " << " Q.size = " << _sendQueue.size() << " dt = " << dt << std::endl; // adebug } +#else + const uint64_t TIME_BUDGET = 200; // usec + _traversal.traverse(TIME_BUDGET); +#endif } #ifndef SEND_SORTED_ENTITIES @@ -248,8 +247,10 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree // When we get to a First traversal, clear the _knownState _knownState.clear(); if (usesViewFrustum) { - _traversal.setScanCallback([&](DiffTraversal::VisibleElement& next) { - next.element->forEachEntity([&](EntityItemPointer entity) { + float lodScaleFactor = _traversal.getCurrentLODScaleFactor(); + glm::vec3 viewPosition = _traversal.getCurrentView().getPosition(); + _traversal.setScanCallback([=](DiffTraversal::VisibleElement& next) { + next.element->forEachEntity([=](EntityItemPointer entity) { // Bail early if we've already checked this entity this frame if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) { return; @@ -262,17 +263,9 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree // larger octree cell because of its position (for example if it crosses the boundary of a cell it // pops to the next higher cell. So we want to check to see that the entity is large enough to be seen // before we consider including it. - // - // TODO: compare priority against a threshold rather than bother with - // calculateRenderAccuracy(). Would need to replace all calculateRenderAccuracy() - // stuff everywhere with threshold in one sweep. - float renderAccuracy = calculateRenderAccuracy(_traversal.getCurrentView().getPosition(), - cube, - _traversal.getCurrentRootSizeScale(), - _traversal.getCurrentLODOffset()); - - // Only send entities if they are large enough to see - if (renderAccuracy > 0.0f) { + float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE; + float apparentAngle = cube.getScale() / distance; + if (apparentAngle > MIN_ENTITY_APPARENT_ANGLE * lodScaleFactor) { float priority = _conicalView.computePriority(cube); _sendQueue.push(PrioritizedEntity(entity, priority)); _entitiesInQueue.insert(entity.get()); @@ -285,7 +278,7 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree }); }); } else { - _traversal.setScanCallback([&](DiffTraversal::VisibleElement& next) { + _traversal.setScanCallback([=](DiffTraversal::VisibleElement& next) { next.element->forEachEntity([&](EntityItemPointer entity) { // Bail early if we've already checked this entity this frame if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) { @@ -299,28 +292,26 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree break; case DiffTraversal::Repeat: if (usesViewFrustum) { - _traversal.setScanCallback([&](DiffTraversal::VisibleElement& next) { + float lodScaleFactor = _traversal.getCurrentLODScaleFactor(); + glm::vec3 viewPosition = _traversal.getCurrentView().getPosition(); + _traversal.setScanCallback([=](DiffTraversal::VisibleElement& next) { uint64_t startOfCompletedTraversal = _traversal.getStartOfCompletedTraversal(); if (next.element->getLastChangedContent() > startOfCompletedTraversal) { - next.element->forEachEntity([&](EntityItemPointer entity) { + next.element->forEachEntity([=](EntityItemPointer entity) { // Bail early if we've already checked this entity this frame if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) { return; } auto knownTimestamp = _knownState.find(entity.get()); - if (knownTimestamp == _knownState.end() || entity->getLastEdited() > knownTimestamp->second) { + if (knownTimestamp == _knownState.end()) { bool success = false; AACube cube = entity->getQueryAACube(success); if (success) { if (next.intersection == ViewFrustum::INSIDE || _traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { // See the DiffTraversal::First case for an explanation of the "entity is too small" check - float renderAccuracy = calculateRenderAccuracy(_traversal.getCurrentView().getPosition(), - cube, - _traversal.getCurrentRootSizeScale(), - _traversal.getCurrentLODOffset()); - - // Only send entities if they are large enough to see - if (renderAccuracy > 0.0f) { + float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE; + float apparentAngle = cube.getScale() / distance; + if (apparentAngle > MIN_ENTITY_APPARENT_ANGLE * lodScaleFactor) { float priority = _conicalView.computePriority(cube); _sendQueue.push(PrioritizedEntity(entity, priority)); _entitiesInQueue.insert(entity.get()); @@ -330,15 +321,20 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); _entitiesInQueue.insert(entity.get()); } + } else if (entity->getLastEdited() > knownTimestamp->second) { + // it is known and it changed --> put it on the queue with any priority + // TODO: sort these correctly + _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); + _entitiesInQueue.insert(entity.get()); } }); } }); } else { - _traversal.setScanCallback([&](DiffTraversal::VisibleElement& next) { + _traversal.setScanCallback([=](DiffTraversal::VisibleElement& next) { uint64_t startOfCompletedTraversal = _traversal.getStartOfCompletedTraversal(); if (next.element->getLastChangedContent() > startOfCompletedTraversal) { - next.element->forEachEntity([&](EntityItemPointer entity) { + next.element->forEachEntity([=](EntityItemPointer entity) { // Bail early if we've already checked this entity this frame if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) { return; @@ -355,56 +351,54 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree break; case DiffTraversal::Differential: assert(usesViewFrustum); - _traversal.setScanCallback([&] (DiffTraversal::VisibleElement& next) { - // NOTE: for Differential case: next.intersection is against completedView not currentView - uint64_t startOfCompletedTraversal = _traversal.getStartOfCompletedTraversal(); - if (next.intersection != ViewFrustum::INSIDE || - next.element->getLastChangedContent() > startOfCompletedTraversal) { - next.element->forEachEntity([&](EntityItemPointer entity) { - // Bail early if we've already checked this entity this frame - if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) { - return; - } - auto knownTimestamp = _knownState.find(entity.get()); - if (knownTimestamp == _knownState.end() || entity->getLastEdited() > knownTimestamp->second) { - bool success = false; - AACube cube = entity->getQueryAACube(success); - if (success) { - if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { - // See the DiffTraversal::First case for an explanation of the "entity is too small" check - float renderAccuracy = calculateRenderAccuracy(_traversal.getCurrentView().getPosition(), - cube, - _traversal.getCurrentRootSizeScale(), - _traversal.getCurrentLODOffset()); - - // Only send entities if they are large enough to see - if (renderAccuracy > 0.0f) { - if (!_traversal.getCompletedView().cubeIntersectsKeyhole(cube)) { + float lodScaleFactor = _traversal.getCurrentLODScaleFactor(); + glm::vec3 viewPosition = _traversal.getCurrentView().getPosition(); + float completedLODScaleFactor = _traversal.getCompletedLODScaleFactor(); + glm::vec3 completedViewPosition = _traversal.getCompletedView().getPosition(); + _traversal.setScanCallback([=] (DiffTraversal::VisibleElement& next) { + next.element->forEachEntity([=](EntityItemPointer entity) { + // Bail early if we've already checked this entity this frame + if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) { + return; + } + auto knownTimestamp = _knownState.find(entity.get()); + if (knownTimestamp == _knownState.end()) { + bool success = false; + AACube cube = entity->getQueryAACube(success); + if (success) { + if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { + // See the DiffTraversal::First case for an explanation of the "entity is too small" check + float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE; + float apparentAngle = cube.getScale() / distance; + if (apparentAngle > MIN_ENTITY_APPARENT_ANGLE * lodScaleFactor) { + if (!_traversal.getCompletedView().cubeIntersectsKeyhole(cube)) { + float priority = _conicalView.computePriority(cube); + _sendQueue.push(PrioritizedEntity(entity, priority)); + _entitiesInQueue.insert(entity.get()); + } else { + // If this entity was skipped last time because it was too small, we still need to send it + distance = glm::distance(cube.calcCenter(), completedViewPosition) + MIN_VISIBLE_DISTANCE; + apparentAngle = cube.getScale() / distance; + if (apparentAngle <= MIN_ENTITY_APPARENT_ANGLE * completedLODScaleFactor) { + // this object was skipped in last completed traversal float priority = _conicalView.computePriority(cube); _sendQueue.push(PrioritizedEntity(entity, priority)); _entitiesInQueue.insert(entity.get()); - } else { - // If this entity was skipped last time because it was too small, we still need to send it - renderAccuracy = calculateRenderAccuracy(_traversal.getCompletedView().getPosition(), - cube, - _traversal.getCompletedRootSizeScale(), - _traversal.getCompletedLODOffset()); - - if (renderAccuracy <= 0.0f) { - float priority = _conicalView.computePriority(cube); - _sendQueue.push(PrioritizedEntity(entity, priority)); - _entitiesInQueue.insert(entity.get()); - } } } } - } else { - _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); - _entitiesInQueue.insert(entity.get()); } + } else { + _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); + _entitiesInQueue.insert(entity.get()); } - }); - } + } else if (entity->getLastEdited() > knownTimestamp->second) { + // it is known and it changed --> put it on the queue with any priority + // TODO: sort these correctly + _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); + _entitiesInQueue.insert(entity.get()); + } + }); }); break; } diff --git a/libraries/entities/src/DiffTraversal.cpp b/libraries/entities/src/DiffTraversal.cpp index 7658a7ac99..b33d9141c3 100644 --- a/libraries/entities/src/DiffTraversal.cpp +++ b/libraries/entities/src/DiffTraversal.cpp @@ -43,12 +43,9 @@ void DiffTraversal::Waypoint::getNextVisibleElementFirstTime(DiffTraversal::Visi return; } else if (view.viewFrustum.cubeIntersectsKeyhole(nextElement->getAACube())) { // check for LOD truncation - float renderAccuracy = calculateRenderAccuracy(view.viewFrustum.getPosition(), - nextElement->getAACube(), - view.rootSizeScale, - view.lodLevelOffset); - - if (renderAccuracy > 0.0f) { + float distance = glm::distance(view.viewFrustum.getPosition(), nextElement->getAACube().calcCenter()) + MIN_VISIBLE_DISTANCE; + float apparentAngle = nextElement->getAACube().getScale() / distance; + if (apparentAngle > MIN_ELEMENT_APPARENT_ANGLE * view.lodScaleFactor) { next.element = nextElement; return; } @@ -85,15 +82,12 @@ void DiffTraversal::Waypoint::getNextVisibleElementRepeat( next.intersection = ViewFrustum::INSIDE; return; } else { - ViewFrustum::intersection intersection = view.viewFrustum.calculateCubeKeyholeIntersection(nextElement->getAACube()); - if (intersection != ViewFrustum::OUTSIDE) { - // check for LOD truncation - float renderAccuracy = calculateRenderAccuracy(view.viewFrustum.getPosition(), - nextElement->getAACube(), - view.rootSizeScale, - view.lodLevelOffset); - - if (renderAccuracy > 0.0f) { + // check for LOD truncation + float distance = glm::distance(view.viewFrustum.getPosition(), nextElement->getAACube().calcCenter()) + MIN_VISIBLE_DISTANCE; + float apparentAngle = nextElement->getAACube().getScale() / distance; + if (apparentAngle > MIN_ELEMENT_APPARENT_ANGLE * view.lodScaleFactor) { + ViewFrustum::intersection intersection = view.viewFrustum.calculateCubeKeyholeIntersection(nextElement->getAACube()); + if (intersection != ViewFrustum::OUTSIDE) { next.element = nextElement; next.intersection = intersection; return; @@ -125,36 +119,14 @@ void DiffTraversal::Waypoint::getNextVisibleElementDifferential(DiffTraversal::V ++_nextIndex; if (nextElement) { AACube cube = nextElement->getAACube(); - if (view.viewFrustum.calculateCubeKeyholeIntersection(cube) != ViewFrustum::OUTSIDE) { - // check for LOD truncation - float renderAccuracy = calculateRenderAccuracy(view.viewFrustum.getPosition(), - cube, - view.rootSizeScale, - view.lodLevelOffset); - - if (renderAccuracy > 0.0f) { - ViewFrustum::intersection lastIntersection = lastView.viewFrustum.calculateCubeKeyholeIntersection(cube); - if (lastIntersection != ViewFrustum::INSIDE || nextElement->getLastChanged() > lastView.startTime) { - next.element = nextElement; - // NOTE: for differential case next.intersection is against the lastView - // because this helps the "external scan" optimize its culling - next.intersection = lastIntersection; - return; - } else { - // check for LOD truncation in the last traversal because - // we may need to traverse this element after all if the lastView skipped it for LOD - renderAccuracy = calculateRenderAccuracy(lastView.viewFrustum.getPosition(), - cube, - lastView.rootSizeScale, - lastView.lodLevelOffset); - - if (renderAccuracy <= 0.0f) { - next.element = nextElement; - // element's intersection with lastView was effectively OUTSIDE - next.intersection = ViewFrustum::OUTSIDE; - return; - } - } + // check for LOD truncation + float distance = glm::distance(view.viewFrustum.getPosition(), cube.calcCenter()) + MIN_VISIBLE_DISTANCE; + float apparentAngle = cube.getScale() / distance; + if (apparentAngle > MIN_ELEMENT_APPARENT_ANGLE * view.lodScaleFactor) { + if (view.viewFrustum.calculateCubeKeyholeIntersection(cube) != ViewFrustum::OUTSIDE) { + next.element = nextElement; + next.intersection = ViewFrustum::OUTSIDE; + return; } } } @@ -187,18 +159,20 @@ DiffTraversal::Type DiffTraversal::prepareNewTraversal(const ViewFrustum& viewFr // external code should update the _scanElementCallback after calling prepareNewTraversal // _currentView.usesViewFrustum = usesViewFrustum; + float lodScaleFactor = powf(2.0f, lodLevelOffset); Type type; // If usesViewFrustum changes, treat it as a First traversal if (_completedView.startTime == 0 || _currentView.usesViewFrustum != _completedView.usesViewFrustum) { type = Type::First; _currentView.viewFrustum = viewFrustum; - _currentView.lodLevelOffset = root->getLevel() + lodLevelOffset - 1; // -1 because true root has level=1 - _currentView.rootSizeScale = root->getScale() * MAX_VISIBILITY_DISTANCE_FOR_UNIT_ELEMENT; + _currentView.lodScaleFactor = lodScaleFactor; _getNextVisibleElementCallback = [&](DiffTraversal::VisibleElement& next) { _path.back().getNextVisibleElementFirstTime(next, _currentView); }; - } else if (!_currentView.usesViewFrustum || (_completedView.viewFrustum.isVerySimilar(viewFrustum) && lodLevelOffset == _completedView.lodLevelOffset)) { + } else if (!_currentView.usesViewFrustum || + (_completedView.viewFrustum.isVerySimilar(viewFrustum) && + lodScaleFactor == _completedView.lodScaleFactor)) { type = Type::Repeat; _getNextVisibleElementCallback = [&](DiffTraversal::VisibleElement& next) { _path.back().getNextVisibleElementRepeat(next, _completedView, _completedView.startTime); @@ -206,8 +180,7 @@ DiffTraversal::Type DiffTraversal::prepareNewTraversal(const ViewFrustum& viewFr } else { type = Type::Differential; _currentView.viewFrustum = viewFrustum; - _currentView.lodLevelOffset = root->getLevel() + lodLevelOffset - 1; // -1 because true root has level=1 - _currentView.rootSizeScale = root->getScale() * MAX_VISIBILITY_DISTANCE_FOR_UNIT_ELEMENT; + _currentView.lodScaleFactor = lodScaleFactor; _getNextVisibleElementCallback = [&](DiffTraversal::VisibleElement& next) { _path.back().getNextVisibleElementDifferential(next, _currentView, _completedView); }; diff --git a/libraries/entities/src/DiffTraversal.h b/libraries/entities/src/DiffTraversal.h index bffe6c651e..6b5fa15075 100644 --- a/libraries/entities/src/DiffTraversal.h +++ b/libraries/entities/src/DiffTraversal.h @@ -34,8 +34,7 @@ public: public: ViewFrustum viewFrustum; uint64_t startTime { 0 }; - float rootSizeScale { 1.0f }; - int32_t lodLevelOffset { 0 }; + float lodScaleFactor { 1.0f }; bool usesViewFrustum { true }; }; @@ -66,10 +65,8 @@ public: const ViewFrustum& getCompletedView() const { return _completedView.viewFrustum; } bool doesCurrentUseViewFrustum() const { return _currentView.usesViewFrustum; } - float getCurrentRootSizeScale() const { return _currentView.rootSizeScale; } - float getCompletedRootSizeScale() const { return _completedView.rootSizeScale; } - int32_t getCurrentLODOffset() const { return _currentView.lodLevelOffset; } - int32_t getCompletedLODOffset() const { return _completedView.lodLevelOffset; } + float getCurrentLODScaleFactor() const { return _currentView.lodScaleFactor; } + float getCompletedLODScaleFactor() const { return _completedView.lodScaleFactor; } uint64_t getStartOfCompletedTraversal() const { return _completedView.startTime; } bool finished() const { return _path.empty(); } diff --git a/libraries/octree/src/OctreeUtils.h b/libraries/octree/src/OctreeUtils.h index 279eb51509..3264520aac 100644 --- a/libraries/octree/src/OctreeUtils.h +++ b/libraries/octree/src/OctreeUtils.h @@ -27,4 +27,10 @@ float boundaryDistanceForRenderLevel(unsigned int renderLevel, float voxelSizeSc float getAccuracyAngle(float octreeSizeScale, int boundaryLevelAdjust); +const float MIN_ELEMENT_APPARENT_ANGLE = 0.0087266f; // ~0.57 degrees in radians +// NOTE: the entity bounding cube is larger than the smallest containing octree element by sqrt(3) +const float SQRT_THREE = 1.73205080f; +const float MIN_ENTITY_APPARENT_ANGLE = MIN_ELEMENT_APPARENT_ANGLE * SQRT_THREE; +const float MIN_VISIBLE_DISTANCE = 0.0001f; // helps avoid divide-by-zero check + #endif // hifi_OctreeUtils_h From 25d250898b462a5dc6cbdd8453feb2d7a724320a Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 12 Sep 2017 22:29:53 -0700 Subject: [PATCH 74/88] remove old debug info --- .../src/entities/EntityTreeSendThread.cpp | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index 5552c2b580..8e166f7a22 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -142,20 +142,12 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O } if (!_traversal.finished()) { - #ifdef DEBUG - uint64_t startTime = usecTimestampNow(); const uint64_t TIME_BUDGET = 400; // usec - _traversal.traverse(TIME_BUDGET); - - if (_sendQueue.size() > 0) { - uint64_t dt = usecTimestampNow() - startTime; - std::cout << "adebug traversal complete " << " Q.size = " << _sendQueue.size() << " dt = " << dt << std::endl; // adebug - } #else const uint64_t TIME_BUDGET = 200; // usec - _traversal.traverse(TIME_BUDGET); #endif + _traversal.traverse(TIME_BUDGET); } #ifndef SEND_SORTED_ENTITIES From 99265a575871a2bcd69840409ada9cebe02e983d Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 13 Sep 2017 10:20:41 -0700 Subject: [PATCH 75/88] remove extra parens --- assignment-client/src/entities/EntityTreeSendThread.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index 8e166f7a22..3d8af26757 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -120,7 +120,7 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { float priority = _conicalView.computePriority(cube); if (priority != PrioritizedEntity::DO_NOT_SEND) { - float distance = (glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE); + float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE; float apparentAngle = cube.getScale() / distance; if (apparentAngle > MIN_ENTITY_APPARENT_ANGLE * lodScaleFactor) { _sendQueue.push(PrioritizedEntity(entity, priority)); From a56c07614912aced7324a59048475b7e3ece957e Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 13 Sep 2017 13:41:48 -0700 Subject: [PATCH 76/88] fix bad resolution during rebase --- assignment-client/src/octree/OctreeSendThread.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assignment-client/src/octree/OctreeSendThread.cpp b/assignment-client/src/octree/OctreeSendThread.cpp index 32d7ce5053..89e3d403fc 100644 --- a/assignment-client/src/octree/OctreeSendThread.cpp +++ b/assignment-client/src/octree/OctreeSendThread.cpp @@ -532,7 +532,7 @@ void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, Octre } // If the bag had contents but is now empty then we know we've sent the entire scene. - bool completedScene = bagHadSomething && nodeData->elementBag.isEmpty(); + bool completedScene = hadSomething && nodeData->elementBag.isEmpty(); if (completedScene || lastNodeDidntFit) { // we probably want to flush what has accumulated in nodeData but: // do we have more data to send? and is there room? From f7af581c71a04630b2b582b1bcb7bcd80fae4c5e Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 14 Sep 2017 16:14:43 -0700 Subject: [PATCH 77/88] track traversal time, rename entity server stat --- .../src/entities/EntityTreeSendThread.cpp | 3 +++ assignment-client/src/octree/OctreeServer.cpp | 10 +++++++++- assignment-client/src/octree/OctreeServer.h | 5 +++++ interface/resources/qml/Stats.qml | 2 +- 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index 3d8af26757..00f5750cb2 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -142,12 +142,15 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O } if (!_traversal.finished()) { + quint64 startTime = usecTimestampNow(); + #ifdef DEBUG const uint64_t TIME_BUDGET = 400; // usec #else const uint64_t TIME_BUDGET = 200; // usec #endif _traversal.traverse(TIME_BUDGET); + OctreeServer::trackTreeTraverseTime((float)(usecTimestampNow() - startTime)); } #ifndef SEND_SORTED_ENTITIES diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index 4a40449e30..45cf35820f 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -60,6 +60,8 @@ int OctreeServer::_longTreeWait = 0; int OctreeServer::_shortTreeWait = 0; int OctreeServer::_noTreeWait = 0; +SimpleMovingAverage OctreeServer::_averageTreeTraverseTime(MOVING_AVERAGE_SAMPLE_COUNTS); + SimpleMovingAverage OctreeServer::_averageNodeWaitTime(MOVING_AVERAGE_SAMPLE_COUNTS); SimpleMovingAverage OctreeServer::_averageCompressAndWriteTime(MOVING_AVERAGE_SAMPLE_COUNTS); @@ -106,6 +108,8 @@ void OctreeServer::resetSendingStats() { _shortTreeWait = 0; _noTreeWait = 0; + _averageTreeTraverseTime.reset(); + _averageNodeWaitTime.reset(); _averageCompressAndWriteTime.reset(); @@ -522,6 +526,10 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url (double)_averageTreeExtraLongWaitTime.getAverage(), (double)(extraLongVsTotal * AS_PERCENT), _extraLongTreeWait); + // traverse + float averageTreeTraverseTime = getAverageTreeTraverseTime(); + statsString += QString().sprintf(" Average tree traverse time: %9.2f usecs\r\n", (double)averageTreeTraverseTime); + // encode float averageEncodeTime = getAverageEncodeTime(); statsString += QString().sprintf(" Average encode time: %9.2f usecs\r\n", (double)averageEncodeTime); @@ -1590,7 +1598,7 @@ void OctreeServer::sendStatsPacket() { QJsonObject timingArray1; timingArray1["1. avgLoopTime"] = getAverageLoopTime(); timingArray1["2. avgInsideTime"] = getAverageInsideTime(); - timingArray1["3. avgTreeLockTime"] = getAverageTreeWaitTime(); + timingArray1["3. avgTreeTraverseTime"] = getAverageTreeTraverseTime(); timingArray1["4. avgEncodeTime"] = getAverageEncodeTime(); timingArray1["5. avgCompressAndWriteTime"] = getAverageCompressAndWriteTime(); timingArray1["6. avgSendTime"] = getAveragePacketSendingTime(); diff --git a/assignment-client/src/octree/OctreeServer.h b/assignment-client/src/octree/OctreeServer.h index 5043ea681c..f930f299f3 100644 --- a/assignment-client/src/octree/OctreeServer.h +++ b/assignment-client/src/octree/OctreeServer.h @@ -96,6 +96,9 @@ public: static void trackTreeWaitTime(float time); static float getAverageTreeWaitTime() { return _averageTreeWaitTime.getAverage(); } + static void trackTreeTraverseTime(float time) { _averageTreeTraverseTime.updateAverage(time); } + static float getAverageTreeTraverseTime() { return _averageTreeTraverseTime.getAverage(); } + static void trackNodeWaitTime(float time) { _averageNodeWaitTime.updateAverage(time); } static float getAverageNodeWaitTime() { return _averageNodeWaitTime.getAverage(); } @@ -228,6 +231,8 @@ protected: static int _shortTreeWait; static int _noTreeWait; + static SimpleMovingAverage _averageTreeTraverseTime; + static SimpleMovingAverage _averageNodeWaitTime; static SimpleMovingAverage _averageCompressAndWriteTime; diff --git a/interface/resources/qml/Stats.qml b/interface/resources/qml/Stats.qml index 159a696e5f..96e267f67f 100644 --- a/interface/resources/qml/Stats.qml +++ b/interface/resources/qml/Stats.qml @@ -208,7 +208,7 @@ Item { } StatText { visible: root.expanded; - text: "Entity Mixer In: " + root.entityPacketsInKbps + " kbps"; + text: "Entity Servers In: " + root.entityPacketsInKbps + " kbps"; } StatText { visible: root.expanded; From 3ae41b9b750fdb9cb6ebbbb2ae1944a939ebbf90 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Tue, 19 Sep 2017 10:03:35 -0700 Subject: [PATCH 78/88] cleanup client and stats string --- assignment-client/src/octree/OctreeServer.cpp | 2 +- interface/src/Application.cpp | 24 ++++++------------- interface/src/Application.h | 4 +--- 3 files changed, 9 insertions(+), 21 deletions(-) diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index 45cf35820f..4a1aade59d 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -528,7 +528,7 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url // traverse float averageTreeTraverseTime = getAverageTreeTraverseTime(); - statsString += QString().sprintf(" Average tree traverse time: %9.2f usecs\r\n", (double)averageTreeTraverseTime); + statsString += QString().sprintf(" Average tree traverse time: %9.2f usecs\r\n\r\n", (double)averageTreeTraverseTime); // encode float averageEncodeTime = getAverageEncodeTime(); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 60a653fdc9..06ee4b68d2 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4696,12 +4696,8 @@ void Application::resetPhysicsReadyInformation() { void Application::reloadResourceCaches() { resetPhysicsReadyInformation(); - { - QMutexLocker viewLocker(&_viewMutex); - _viewFrustum.setPosition(glm::vec3(0.0f, 0.0f, TREE_SCALE)); - _viewFrustum.setOrientation(glm::quat()); - } - // Clear entities out of view frustum + // Query the octree to refresh everything in view + _lastQueriedTime = 0; queryOctree(NodeType::EntityServer, PacketType::EntityQuery, _entityServerJurisdictions); DependencyManager::get()->clearCache(); @@ -5299,7 +5295,7 @@ int Application::sendNackPackets() { return packetsSent; } -void Application::queryOctree(NodeType_t serverType, PacketType packetType, NodeToJurisdictionMap& jurisdictions, bool forceResend) { +void Application::queryOctree(NodeType_t serverType, PacketType packetType, NodeToJurisdictionMap& jurisdictions) { if (!_settingsLoaded) { return; // bail early if settings are not loaded @@ -5704,8 +5700,6 @@ void Application::clearDomainOctreeDetails() { skyStage->setBackgroundMode(model::SunSkyStage::SKY_DEFAULT); - _recentlyClearedDomain = true; - DependencyManager::get()->clearUnusedResources(); DependencyManager::get()->clearUnusedResources(); DependencyManager::get()->clearUnusedResources(); @@ -5752,14 +5746,10 @@ void Application::nodeActivated(SharedNodePointer node) { } } - // If we get a new EntityServer activated, do a "forceRedraw" query. This will send a degenerate - // query so that the server will think our next non-degenerate query is "different enough" to send - // us a full scene - if (_recentlyClearedDomain && node->getType() == NodeType::EntityServer) { - _recentlyClearedDomain = false; - if (DependencyManager::get()->shouldRenderEntities()) { - queryOctree(NodeType::EntityServer, PacketType::EntityQuery, _entityServerJurisdictions, true); - } + // If we get a new EntityServer activated, reset lastQueried time + // so we will do a proper query during update + if (node->getType() == NodeType::EntityServer) { + _lastQueriedTime = 0; } if (node->getType() == NodeType::AudioMixer) { diff --git a/interface/src/Application.h b/interface/src/Application.h index 93f7a4ab79..a706ce2b63 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -467,7 +467,7 @@ private: void updateThreads(float deltaTime); void updateDialogs(float deltaTime) const; - void queryOctree(NodeType_t serverType, PacketType packetType, NodeToJurisdictionMap& jurisdictions, bool forceResend = false); + void queryOctree(NodeType_t serverType, PacketType packetType, NodeToJurisdictionMap& jurisdictions); void renderRearViewMirror(RenderArgs* renderArgs, const QRect& region, bool isZoomed); @@ -663,8 +663,6 @@ private: bool _keyboardDeviceHasFocus { true }; - bool _recentlyClearedDomain { false }; - QString _returnFromFullScreenMirrorTo; ConnectionMonitor _connectionMonitor; From 1c30f7424e229b64b705566dea92460f5cc2c4c6 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 22 Sep 2017 15:54:25 -0700 Subject: [PATCH 79/88] remove cruft and add comments --- .../src/entities/EntityTreeSendThread.cpp | 30 ++----------------- interface/src/Application.cpp | 1 - libraries/entities/src/DiffTraversal.cpp | 11 ------- libraries/entities/src/DiffTraversal.h | 4 --- libraries/entities/src/EntityTree.cpp | 11 ++----- libraries/octree/src/OctreeConstants.h | 3 ++ libraries/octree/src/OctreeUtils.h | 5 ++-- libraries/shared/src/ViewFrustum.cpp | 1 + 8 files changed, 12 insertions(+), 54 deletions(-) diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index 00f5750cb2..1d506ba47c 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -17,7 +17,6 @@ #include "EntityServer.h" -#define SEND_SORTED_ENTITIES EntityTreeSendThread::EntityTreeSendThread(OctreeServer* myServer, const SharedNodePointer& node) : OctreeSendThread(myServer, node) @@ -144,32 +143,15 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O if (!_traversal.finished()) { quint64 startTime = usecTimestampNow(); -#ifdef DEBUG + #ifdef DEBUG const uint64_t TIME_BUDGET = 400; // usec -#else + #else const uint64_t TIME_BUDGET = 200; // usec -#endif + #endif _traversal.traverse(TIME_BUDGET); OctreeServer::trackTreeTraverseTime((float)(usecTimestampNow() - startTime)); } -#ifndef SEND_SORTED_ENTITIES - if (!_sendQueue.empty()) { - uint64_t sendTime = usecTimestampNow(); - // print what needs to be sent - while (!_sendQueue.empty()) { - PrioritizedEntity entry = _sendQueue.top(); - EntityItemPointer entity = entry.getEntity(); - if (entity) { - std::cout << "adebug send '" << entity->getName().toStdString() << "'" << " : " << entry.getPriority() << std::endl; // adebug - _knownState[entity.get()] = sendTime; - } - _sendQueue.pop(); - _entitiesInQueue.erase(entry.getRawEntityPointer()); - } - } -#endif // SEND_SORTED_ENTITIES - OctreeSendThread::traverseTreeAndSendContents(node, nodeData, viewFrustumChanged, isFullScene); } @@ -400,8 +382,6 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree } bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) { -#ifdef SEND_SORTED_ENTITIES - //auto entityTree = std::static_pointer_cast(_myServer->getOctree()); if (_sendQueue.empty()) { OctreeServer::trackEncodeTime(OctreeServer::SKIP_TIME); return false; @@ -485,10 +465,6 @@ bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstream _packetData.updatePriorBytes(_numEntitiesOffset, (const unsigned char*)&_numEntities, sizeof(_numEntities)); OctreeServer::trackEncodeTime((float)(usecTimestampNow() - encodeStart)); return true; - -#else // SEND_SORTED_ENTITIES - return OctreeSendThread::traverseTreeAndBuildNextPacketPayload(params); -#endif // SEND_SORTED_ENTITIES } void EntityTreeSendThread::editingEntityPointer(const EntityItemPointer entity) { diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 06ee4b68d2..36e5b3d859 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5429,7 +5429,6 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node << perUnknownServer << " to send us jurisdiction."; } - // TODO: remove this hackery: it no longer makes sense for streaming of entities in scene. // set the query's position/orientation to be degenerate in a manner that will get the scene quickly // If there's only one server, then don't do this, and just let the normal voxel query pass through // as expected... this way, we will actually get a valid scene if there is one to be seen diff --git a/libraries/entities/src/DiffTraversal.cpp b/libraries/entities/src/DiffTraversal.cpp index b33d9141c3..a2923c003a 100644 --- a/libraries/entities/src/DiffTraversal.cpp +++ b/libraries/entities/src/DiffTraversal.cpp @@ -236,17 +236,6 @@ void DiffTraversal::setScanCallback(std::function // DEBUG - #include #include "EntityTreeElement.h" @@ -74,8 +72,6 @@ public: void setScanCallback(std::function cb); void traverse(uint64_t timeBudget); - friend std::ostream& operator<<(std::ostream& s, const DiffTraversal& traversal); // DEBUG - private: void getNextVisibleElement(VisibleElement& next); diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 99ab5a7677..3feb9cc03c 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -192,21 +192,14 @@ int EntityTree::readEntityDataFromBuffer(const unsigned char* data, int bytesLef if (!isDeletedEntity(entityItemID)) { _entitiesToAdd.insert(entityItemID, entity); - /* - addEntityMapEntry(entity); - oldElement->addEntityItem(entity); // add this new entity to this elements entities - entityItemID = entity->getEntityItemID(); - postAddEntity(entity); - */ - if (entity->getCreated() == UNKNOWN_CREATED_TIME) { entity->recordCreationTime(); } + #ifdef WANT_DEBUG } else { - #ifdef WANT_DEBUG qCDebug(entities) << "Received packet for previously deleted entity [" << entityItemID << "] ignoring. (inside " << __FUNCTION__ << ")"; - #endif + #endif } } } diff --git a/libraries/octree/src/OctreeConstants.h b/libraries/octree/src/OctreeConstants.h index 06b4748f51..062d4e1ef2 100644 --- a/libraries/octree/src/OctreeConstants.h +++ b/libraries/octree/src/OctreeConstants.h @@ -37,6 +37,9 @@ const int NUMBER_OF_CHILDREN = 8; const int MAX_TREE_SLICE_BYTES = 26; +// The oversend below is 20 degrees because that is the minimum oversend necessary to prevent missing entities +// near the edge of the view. The value here is determined by hard-coded values in ViewFrsutum::isVerySimilar(). +// TODO: move this parameter to the OctreeQueryNode context. const float VIEW_FRUSTUM_FOV_OVERSEND = 20.0f; // These are guards to prevent our voxel tree recursive routines from spinning out of control diff --git a/libraries/octree/src/OctreeUtils.h b/libraries/octree/src/OctreeUtils.h index 3264520aac..122d82e267 100644 --- a/libraries/octree/src/OctreeUtils.h +++ b/libraries/octree/src/OctreeUtils.h @@ -27,8 +27,9 @@ float boundaryDistanceForRenderLevel(unsigned int renderLevel, float voxelSizeSc float getAccuracyAngle(float octreeSizeScale, int boundaryLevelAdjust); -const float MIN_ELEMENT_APPARENT_ANGLE = 0.0087266f; // ~0.57 degrees in radians -// NOTE: the entity bounding cube is larger than the smallest containing octree element by sqrt(3) +// MIN_ELEMENT_APPARENT_ANGLE = angular diameter of 1x1x1m cube at 400m = sqrt(3) / 400 = 0.0043301f; +const float MIN_ELEMENT_APPARENT_ANGLE = 0.0043301f; +// NOTE: the entity bounding cube is larger than the smallest possible containing octree element by sqrt(3) const float SQRT_THREE = 1.73205080f; const float MIN_ENTITY_APPARENT_ANGLE = MIN_ELEMENT_APPARENT_ANGLE * SQRT_THREE; const float MIN_VISIBLE_DISTANCE = 0.0001f; // helps avoid divide-by-zero check diff --git a/libraries/shared/src/ViewFrustum.cpp b/libraries/shared/src/ViewFrustum.cpp index ccdeb830b6..978221e167 100644 --- a/libraries/shared/src/ViewFrustum.cpp +++ b/libraries/shared/src/ViewFrustum.cpp @@ -328,6 +328,7 @@ bool closeEnough(float a, float b, float relativeError) { return fabsf(a - b) / (0.5f * fabsf(a + b) + EPSILON) < relativeError; } +// TODO: the slop and relative error should be passed in by argument rather than hard-coded. bool ViewFrustum::isVerySimilar(const ViewFrustum& other) const { const float MIN_POSITION_SLOP_SQUARED = 25.0f; // 5 meters squared const float MIN_ORIENTATION_DOT = 0.9924039f; // dot product of two quaternions 10 degrees apart From 5dcd6bc496fb777a25a1fc127941c037b0661627 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 22 Sep 2017 15:58:01 -0700 Subject: [PATCH 80/88] namechange: apparentAngle --> angularDiameter --- .../src/entities/EntityTreeSendThread.cpp | 20 +++++++++---------- libraries/entities/src/DiffTraversal.cpp | 12 +++++------ libraries/octree/src/OctreeUtils.h | 6 +++--- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index 1d506ba47c..e04dc5553e 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -120,8 +120,8 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O float priority = _conicalView.computePriority(cube); if (priority != PrioritizedEntity::DO_NOT_SEND) { float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE; - float apparentAngle = cube.getScale() / distance; - if (apparentAngle > MIN_ENTITY_APPARENT_ANGLE * lodScaleFactor) { + float angularDiameter = cube.getScale() / distance; + if (angularDiameter > MIN_ENTITY_ANGULAR_DIAMETER * lodScaleFactor) { _sendQueue.push(PrioritizedEntity(entity, priority)); _entitiesInQueue.insert(entity.get()); } @@ -241,8 +241,8 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree // pops to the next higher cell. So we want to check to see that the entity is large enough to be seen // before we consider including it. float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE; - float apparentAngle = cube.getScale() / distance; - if (apparentAngle > MIN_ENTITY_APPARENT_ANGLE * lodScaleFactor) { + float angularDiameter = cube.getScale() / distance; + if (angularDiameter > MIN_ENTITY_ANGULAR_DIAMETER * lodScaleFactor) { float priority = _conicalView.computePriority(cube); _sendQueue.push(PrioritizedEntity(entity, priority)); _entitiesInQueue.insert(entity.get()); @@ -287,8 +287,8 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree if (next.intersection == ViewFrustum::INSIDE || _traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { // See the DiffTraversal::First case for an explanation of the "entity is too small" check float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE; - float apparentAngle = cube.getScale() / distance; - if (apparentAngle > MIN_ENTITY_APPARENT_ANGLE * lodScaleFactor) { + float angularDiameter = cube.getScale() / distance; + if (angularDiameter > MIN_ENTITY_ANGULAR_DIAMETER * lodScaleFactor) { float priority = _conicalView.computePriority(cube); _sendQueue.push(PrioritizedEntity(entity, priority)); _entitiesInQueue.insert(entity.get()); @@ -346,8 +346,8 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { // See the DiffTraversal::First case for an explanation of the "entity is too small" check float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE; - float apparentAngle = cube.getScale() / distance; - if (apparentAngle > MIN_ENTITY_APPARENT_ANGLE * lodScaleFactor) { + float angularDiameter = cube.getScale() / distance; + if (angularDiameter > MIN_ENTITY_ANGULAR_DIAMETER * lodScaleFactor) { if (!_traversal.getCompletedView().cubeIntersectsKeyhole(cube)) { float priority = _conicalView.computePriority(cube); _sendQueue.push(PrioritizedEntity(entity, priority)); @@ -355,8 +355,8 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree } else { // If this entity was skipped last time because it was too small, we still need to send it distance = glm::distance(cube.calcCenter(), completedViewPosition) + MIN_VISIBLE_DISTANCE; - apparentAngle = cube.getScale() / distance; - if (apparentAngle <= MIN_ENTITY_APPARENT_ANGLE * completedLODScaleFactor) { + angularDiameter = cube.getScale() / distance; + if (angularDiameter <= MIN_ENTITY_ANGULAR_DIAMETER * completedLODScaleFactor) { // this object was skipped in last completed traversal float priority = _conicalView.computePriority(cube); _sendQueue.push(PrioritizedEntity(entity, priority)); diff --git a/libraries/entities/src/DiffTraversal.cpp b/libraries/entities/src/DiffTraversal.cpp index a2923c003a..9a48028045 100644 --- a/libraries/entities/src/DiffTraversal.cpp +++ b/libraries/entities/src/DiffTraversal.cpp @@ -44,8 +44,8 @@ void DiffTraversal::Waypoint::getNextVisibleElementFirstTime(DiffTraversal::Visi } else if (view.viewFrustum.cubeIntersectsKeyhole(nextElement->getAACube())) { // check for LOD truncation float distance = glm::distance(view.viewFrustum.getPosition(), nextElement->getAACube().calcCenter()) + MIN_VISIBLE_DISTANCE; - float apparentAngle = nextElement->getAACube().getScale() / distance; - if (apparentAngle > MIN_ELEMENT_APPARENT_ANGLE * view.lodScaleFactor) { + float angularDiameter = nextElement->getAACube().getScale() / distance; + if (angularDiameter > MIN_ELEMENT_ANGULAR_DIAMETER * view.lodScaleFactor) { next.element = nextElement; return; } @@ -84,8 +84,8 @@ void DiffTraversal::Waypoint::getNextVisibleElementRepeat( } else { // check for LOD truncation float distance = glm::distance(view.viewFrustum.getPosition(), nextElement->getAACube().calcCenter()) + MIN_VISIBLE_DISTANCE; - float apparentAngle = nextElement->getAACube().getScale() / distance; - if (apparentAngle > MIN_ELEMENT_APPARENT_ANGLE * view.lodScaleFactor) { + float angularDiameter = nextElement->getAACube().getScale() / distance; + if (angularDiameter > MIN_ELEMENT_ANGULAR_DIAMETER * view.lodScaleFactor) { ViewFrustum::intersection intersection = view.viewFrustum.calculateCubeKeyholeIntersection(nextElement->getAACube()); if (intersection != ViewFrustum::OUTSIDE) { next.element = nextElement; @@ -121,8 +121,8 @@ void DiffTraversal::Waypoint::getNextVisibleElementDifferential(DiffTraversal::V AACube cube = nextElement->getAACube(); // check for LOD truncation float distance = glm::distance(view.viewFrustum.getPosition(), cube.calcCenter()) + MIN_VISIBLE_DISTANCE; - float apparentAngle = cube.getScale() / distance; - if (apparentAngle > MIN_ELEMENT_APPARENT_ANGLE * view.lodScaleFactor) { + float angularDiameter = cube.getScale() / distance; + if (angularDiameter > MIN_ELEMENT_ANGULAR_DIAMETER * view.lodScaleFactor) { if (view.viewFrustum.calculateCubeKeyholeIntersection(cube) != ViewFrustum::OUTSIDE) { next.element = nextElement; next.intersection = ViewFrustum::OUTSIDE; diff --git a/libraries/octree/src/OctreeUtils.h b/libraries/octree/src/OctreeUtils.h index 122d82e267..7819db852c 100644 --- a/libraries/octree/src/OctreeUtils.h +++ b/libraries/octree/src/OctreeUtils.h @@ -27,11 +27,11 @@ float boundaryDistanceForRenderLevel(unsigned int renderLevel, float voxelSizeSc float getAccuracyAngle(float octreeSizeScale, int boundaryLevelAdjust); -// MIN_ELEMENT_APPARENT_ANGLE = angular diameter of 1x1x1m cube at 400m = sqrt(3) / 400 = 0.0043301f; -const float MIN_ELEMENT_APPARENT_ANGLE = 0.0043301f; +// MIN_ELEMENT_ANGULAR_DIAMETER = angular diameter of 1x1x1m cube at 400m = sqrt(3) / 400 = 0.0043301f +const float MIN_ELEMENT_ANGULAR_DIAMETER = 0.0043301f; // NOTE: the entity bounding cube is larger than the smallest possible containing octree element by sqrt(3) const float SQRT_THREE = 1.73205080f; -const float MIN_ENTITY_APPARENT_ANGLE = MIN_ELEMENT_APPARENT_ANGLE * SQRT_THREE; +const float MIN_ENTITY_ANGULAR_DIAMETER = MIN_ELEMENT_ANGULAR_DIAMETER * SQRT_THREE; const float MIN_VISIBLE_DISTANCE = 0.0001f; // helps avoid divide-by-zero check #endif // hifi_OctreeUtils_h From 0c934e863b65142013cc7c00f5b0ccf7d1c03474 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 22 Sep 2017 16:00:44 -0700 Subject: [PATCH 81/88] clarify some comments --- libraries/octree/src/OctreeUtils.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/octree/src/OctreeUtils.h b/libraries/octree/src/OctreeUtils.h index 7819db852c..c257bcd5f1 100644 --- a/libraries/octree/src/OctreeUtils.h +++ b/libraries/octree/src/OctreeUtils.h @@ -27,8 +27,8 @@ float boundaryDistanceForRenderLevel(unsigned int renderLevel, float voxelSizeSc float getAccuracyAngle(float octreeSizeScale, int boundaryLevelAdjust); -// MIN_ELEMENT_ANGULAR_DIAMETER = angular diameter of 1x1x1m cube at 400m = sqrt(3) / 400 = 0.0043301f -const float MIN_ELEMENT_ANGULAR_DIAMETER = 0.0043301f; +// MIN_ELEMENT_ANGULAR_DIAMETER = angular diameter of 1x1x1m cube at 400m = sqrt(3) / 400 = 0.0043301 radians ~= 0.25 degrees +const float MIN_ELEMENT_ANGULAR_DIAMETER = 0.0043301f; // radians // NOTE: the entity bounding cube is larger than the smallest possible containing octree element by sqrt(3) const float SQRT_THREE = 1.73205080f; const float MIN_ENTITY_ANGULAR_DIAMETER = MIN_ELEMENT_ANGULAR_DIAMETER * SQRT_THREE; From b16d66602649a4483ff106b03f218e30e637e6aa Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 25 Sep 2017 08:50:58 -0700 Subject: [PATCH 82/88] remove dupe addToNeedsParentFixupList() call --- libraries/entities/src/EntityTree.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 3feb9cc03c..6cf3b51001 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -116,9 +116,6 @@ void EntityTree::readBitstreamToTree(const unsigned char* bitstream, EntityItemPointer entityItem = itr.value(); AddEntityOperator theOperator(getThisPointer(), entityItem); recurseTreeWithOperator(&theOperator); - if (!entityItem->getParentID().isNull()) { - addToNeedsParentFixupList(entityItem); - } postAddEntity(entityItem); } _entitiesToAdd.clear(); From f5f1a64c92b9a0a8a92bd0a3e07c196a831874c6 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 25 Sep 2017 17:38:56 -0700 Subject: [PATCH 83/88] use const ref on pointer, and use dynamic_cast --- libraries/entities/src/EntityTree.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 6cf3b51001..6769af45a3 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -113,7 +113,7 @@ void EntityTree::readBitstreamToTree(const unsigned char* bitstream, // add entities QHash::const_iterator itr; for (itr = _entitiesToAdd.constBegin(); itr != _entitiesToAdd.constEnd(); ++itr) { - EntityItemPointer entityItem = itr.value(); + EntityItemPointer& entityItem = itr.value(); AddEntityOperator theOperator(getThisPointer(), entityItem); recurseTreeWithOperator(&theOperator); postAddEntity(entityItem); @@ -560,7 +560,7 @@ void EntityTree::deleteEntity(const EntityItemID& entityID, bool force, bool ign auto descendantID = descendant->getID(); theOperator.addEntityIDToDeleteList(descendantID); emit deletingEntity(descendantID); - EntityItemPointer descendantEntity = std::static_pointer_cast(descendant); + EntityItemPointer descendantEntity = std::dynamic_pointer_cast(descendant); if (descendantEntity) { emit deletingEntityPointer(descendantEntity.get()); } From 32910e6f40b33e4ead5fa6d69efbce85aa51b49c Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 25 Sep 2017 17:40:15 -0700 Subject: [PATCH 84/88] use [this] for lambda capture list --- libraries/entities/src/DiffTraversal.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/entities/src/DiffTraversal.cpp b/libraries/entities/src/DiffTraversal.cpp index 9a48028045..2f9423daa3 100644 --- a/libraries/entities/src/DiffTraversal.cpp +++ b/libraries/entities/src/DiffTraversal.cpp @@ -167,21 +167,21 @@ DiffTraversal::Type DiffTraversal::prepareNewTraversal(const ViewFrustum& viewFr type = Type::First; _currentView.viewFrustum = viewFrustum; _currentView.lodScaleFactor = lodScaleFactor; - _getNextVisibleElementCallback = [&](DiffTraversal::VisibleElement& next) { + _getNextVisibleElementCallback = [this](DiffTraversal::VisibleElement& next) { _path.back().getNextVisibleElementFirstTime(next, _currentView); }; } else if (!_currentView.usesViewFrustum || (_completedView.viewFrustum.isVerySimilar(viewFrustum) && lodScaleFactor == _completedView.lodScaleFactor)) { type = Type::Repeat; - _getNextVisibleElementCallback = [&](DiffTraversal::VisibleElement& next) { + _getNextVisibleElementCallback = [this](DiffTraversal::VisibleElement& next) { _path.back().getNextVisibleElementRepeat(next, _completedView, _completedView.startTime); }; } else { type = Type::Differential; _currentView.viewFrustum = viewFrustum; _currentView.lodScaleFactor = lodScaleFactor; - _getNextVisibleElementCallback = [&](DiffTraversal::VisibleElement& next) { + _getNextVisibleElementCallback = [this](DiffTraversal::VisibleElement& next) { _path.back().getNextVisibleElementDifferential(next, _currentView, _completedView); }; } From 01304de8c20988216a0fa65d41c6cb95b561ab00 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 25 Sep 2017 17:41:14 -0700 Subject: [PATCH 85/88] indent switch statement, use const ref --- .../src/entities/EntityTreeSendThread.cpp | 290 +++++++++--------- .../src/entities/EntityTreeSendThread.h | 2 +- 2 files changed, 146 insertions(+), 146 deletions(-) diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index e04dc5553e..03014bae6a 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -220,164 +220,164 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree _conicalView.set(_traversal.getCurrentView()); switch (type) { - case DiffTraversal::First: - // When we get to a First traversal, clear the _knownState - _knownState.clear(); - if (usesViewFrustum) { + case DiffTraversal::First: + // When we get to a First traversal, clear the _knownState + _knownState.clear(); + if (usesViewFrustum) { + float lodScaleFactor = _traversal.getCurrentLODScaleFactor(); + glm::vec3 viewPosition = _traversal.getCurrentView().getPosition(); + _traversal.setScanCallback([=](DiffTraversal::VisibleElement& next) { + next.element->forEachEntity([=](EntityItemPointer entity) { + // Bail early if we've already checked this entity this frame + if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) { + return; + } + bool success = false; + AACube cube = entity->getQueryAACube(success); + if (success) { + if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { + // Check the size of the entity, it's possible that a "too small to see" entity is included in a + // larger octree cell because of its position (for example if it crosses the boundary of a cell it + // pops to the next higher cell. So we want to check to see that the entity is large enough to be seen + // before we consider including it. + float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE; + float angularDiameter = cube.getScale() / distance; + if (angularDiameter > MIN_ENTITY_ANGULAR_DIAMETER * lodScaleFactor) { + float priority = _conicalView.computePriority(cube); + _sendQueue.push(PrioritizedEntity(entity, priority)); + _entitiesInQueue.insert(entity.get()); + } + } + } else { + _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); + _entitiesInQueue.insert(entity.get()); + } + }); + }); + } else { + _traversal.setScanCallback([this](DiffTraversal::VisibleElement& next) { + next.element->forEachEntity([this](EntityItemPointer entity) { + // Bail early if we've already checked this entity this frame + if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) { + return; + } + _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); + _entitiesInQueue.insert(entity.get()); + }); + }); + } + break; + case DiffTraversal::Repeat: + if (usesViewFrustum) { + float lodScaleFactor = _traversal.getCurrentLODScaleFactor(); + glm::vec3 viewPosition = _traversal.getCurrentView().getPosition(); + _traversal.setScanCallback([=](DiffTraversal::VisibleElement& next) { + uint64_t startOfCompletedTraversal = _traversal.getStartOfCompletedTraversal(); + if (next.element->getLastChangedContent() > startOfCompletedTraversal) { + next.element->forEachEntity([=](EntityItemPointer entity) { + // Bail early if we've already checked this entity this frame + if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) { + return; + } + auto knownTimestamp = _knownState.find(entity.get()); + if (knownTimestamp == _knownState.end()) { + bool success = false; + AACube cube = entity->getQueryAACube(success); + if (success) { + if (next.intersection == ViewFrustum::INSIDE || _traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { + // See the DiffTraversal::First case for an explanation of the "entity is too small" check + float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE; + float angularDiameter = cube.getScale() / distance; + if (angularDiameter > MIN_ENTITY_ANGULAR_DIAMETER * lodScaleFactor) { + float priority = _conicalView.computePriority(cube); + _sendQueue.push(PrioritizedEntity(entity, priority)); + _entitiesInQueue.insert(entity.get()); + } + } + } else { + _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); + _entitiesInQueue.insert(entity.get()); + } + } else if (entity->getLastEdited() > knownTimestamp->second) { + // it is known and it changed --> put it on the queue with any priority + // TODO: sort these correctly + _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); + _entitiesInQueue.insert(entity.get()); + } + }); + } + }); + } else { + _traversal.setScanCallback([this](DiffTraversal::VisibleElement& next) { + uint64_t startOfCompletedTraversal = _traversal.getStartOfCompletedTraversal(); + if (next.element->getLastChangedContent() > startOfCompletedTraversal) { + next.element->forEachEntity([this](EntityItemPointer entity) { + // Bail early if we've already checked this entity this frame + if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) { + return; + } + auto knownTimestamp = _knownState.find(entity.get()); + if (knownTimestamp == _knownState.end() || entity->getLastEdited() > knownTimestamp->second) { + _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); + _entitiesInQueue.insert(entity.get()); + } + }); + } + }); + } + break; + case DiffTraversal::Differential: + assert(usesViewFrustum); float lodScaleFactor = _traversal.getCurrentLODScaleFactor(); glm::vec3 viewPosition = _traversal.getCurrentView().getPosition(); - _traversal.setScanCallback([=](DiffTraversal::VisibleElement& next) { + float completedLODScaleFactor = _traversal.getCompletedLODScaleFactor(); + glm::vec3 completedViewPosition = _traversal.getCompletedView().getPosition(); + _traversal.setScanCallback([=] (DiffTraversal::VisibleElement& next) { next.element->forEachEntity([=](EntityItemPointer entity) { // Bail early if we've already checked this entity this frame if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) { return; } - bool success = false; - AACube cube = entity->getQueryAACube(success); - if (success) { - if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { - // Check the size of the entity, it's possible that a "too small to see" entity is included in a - // larger octree cell because of its position (for example if it crosses the boundary of a cell it - // pops to the next higher cell. So we want to check to see that the entity is large enough to be seen - // before we consider including it. - float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE; - float angularDiameter = cube.getScale() / distance; - if (angularDiameter > MIN_ENTITY_ANGULAR_DIAMETER * lodScaleFactor) { - float priority = _conicalView.computePriority(cube); - _sendQueue.push(PrioritizedEntity(entity, priority)); - _entitiesInQueue.insert(entity.get()); + auto knownTimestamp = _knownState.find(entity.get()); + if (knownTimestamp == _knownState.end()) { + bool success = false; + AACube cube = entity->getQueryAACube(success); + if (success) { + if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { + // See the DiffTraversal::First case for an explanation of the "entity is too small" check + float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE; + float angularDiameter = cube.getScale() / distance; + if (angularDiameter > MIN_ENTITY_ANGULAR_DIAMETER * lodScaleFactor) { + if (!_traversal.getCompletedView().cubeIntersectsKeyhole(cube)) { + float priority = _conicalView.computePriority(cube); + _sendQueue.push(PrioritizedEntity(entity, priority)); + _entitiesInQueue.insert(entity.get()); + } else { + // If this entity was skipped last time because it was too small, we still need to send it + distance = glm::distance(cube.calcCenter(), completedViewPosition) + MIN_VISIBLE_DISTANCE; + angularDiameter = cube.getScale() / distance; + if (angularDiameter <= MIN_ENTITY_ANGULAR_DIAMETER * completedLODScaleFactor) { + // this object was skipped in last completed traversal + float priority = _conicalView.computePriority(cube); + _sendQueue.push(PrioritizedEntity(entity, priority)); + _entitiesInQueue.insert(entity.get()); + } + } + } } + } else { + _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); + _entitiesInQueue.insert(entity.get()); } - } else { + } else if (entity->getLastEdited() > knownTimestamp->second) { + // it is known and it changed --> put it on the queue with any priority + // TODO: sort these correctly _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); _entitiesInQueue.insert(entity.get()); } }); }); - } else { - _traversal.setScanCallback([=](DiffTraversal::VisibleElement& next) { - next.element->forEachEntity([&](EntityItemPointer entity) { - // Bail early if we've already checked this entity this frame - if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) { - return; - } - _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); - _entitiesInQueue.insert(entity.get()); - }); - }); - } - break; - case DiffTraversal::Repeat: - if (usesViewFrustum) { - float lodScaleFactor = _traversal.getCurrentLODScaleFactor(); - glm::vec3 viewPosition = _traversal.getCurrentView().getPosition(); - _traversal.setScanCallback([=](DiffTraversal::VisibleElement& next) { - uint64_t startOfCompletedTraversal = _traversal.getStartOfCompletedTraversal(); - if (next.element->getLastChangedContent() > startOfCompletedTraversal) { - next.element->forEachEntity([=](EntityItemPointer entity) { - // Bail early if we've already checked this entity this frame - if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) { - return; - } - auto knownTimestamp = _knownState.find(entity.get()); - if (knownTimestamp == _knownState.end()) { - bool success = false; - AACube cube = entity->getQueryAACube(success); - if (success) { - if (next.intersection == ViewFrustum::INSIDE || _traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { - // See the DiffTraversal::First case for an explanation of the "entity is too small" check - float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE; - float angularDiameter = cube.getScale() / distance; - if (angularDiameter > MIN_ENTITY_ANGULAR_DIAMETER * lodScaleFactor) { - float priority = _conicalView.computePriority(cube); - _sendQueue.push(PrioritizedEntity(entity, priority)); - _entitiesInQueue.insert(entity.get()); - } - } - } else { - _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); - _entitiesInQueue.insert(entity.get()); - } - } else if (entity->getLastEdited() > knownTimestamp->second) { - // it is known and it changed --> put it on the queue with any priority - // TODO: sort these correctly - _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); - _entitiesInQueue.insert(entity.get()); - } - }); - } - }); - } else { - _traversal.setScanCallback([=](DiffTraversal::VisibleElement& next) { - uint64_t startOfCompletedTraversal = _traversal.getStartOfCompletedTraversal(); - if (next.element->getLastChangedContent() > startOfCompletedTraversal) { - next.element->forEachEntity([=](EntityItemPointer entity) { - // Bail early if we've already checked this entity this frame - if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) { - return; - } - auto knownTimestamp = _knownState.find(entity.get()); - if (knownTimestamp == _knownState.end() || entity->getLastEdited() > knownTimestamp->second) { - _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); - _entitiesInQueue.insert(entity.get()); - } - }); - } - }); - } - break; - case DiffTraversal::Differential: - assert(usesViewFrustum); - float lodScaleFactor = _traversal.getCurrentLODScaleFactor(); - glm::vec3 viewPosition = _traversal.getCurrentView().getPosition(); - float completedLODScaleFactor = _traversal.getCompletedLODScaleFactor(); - glm::vec3 completedViewPosition = _traversal.getCompletedView().getPosition(); - _traversal.setScanCallback([=] (DiffTraversal::VisibleElement& next) { - next.element->forEachEntity([=](EntityItemPointer entity) { - // Bail early if we've already checked this entity this frame - if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) { - return; - } - auto knownTimestamp = _knownState.find(entity.get()); - if (knownTimestamp == _knownState.end()) { - bool success = false; - AACube cube = entity->getQueryAACube(success); - if (success) { - if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { - // See the DiffTraversal::First case for an explanation of the "entity is too small" check - float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE; - float angularDiameter = cube.getScale() / distance; - if (angularDiameter > MIN_ENTITY_ANGULAR_DIAMETER * lodScaleFactor) { - if (!_traversal.getCompletedView().cubeIntersectsKeyhole(cube)) { - float priority = _conicalView.computePriority(cube); - _sendQueue.push(PrioritizedEntity(entity, priority)); - _entitiesInQueue.insert(entity.get()); - } else { - // If this entity was skipped last time because it was too small, we still need to send it - distance = glm::distance(cube.calcCenter(), completedViewPosition) + MIN_VISIBLE_DISTANCE; - angularDiameter = cube.getScale() / distance; - if (angularDiameter <= MIN_ENTITY_ANGULAR_DIAMETER * completedLODScaleFactor) { - // this object was skipped in last completed traversal - float priority = _conicalView.computePriority(cube); - _sendQueue.push(PrioritizedEntity(entity, priority)); - _entitiesInQueue.insert(entity.get()); - } - } - } - } - } else { - _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); - _entitiesInQueue.insert(entity.get()); - } - } else if (entity->getLastEdited() > knownTimestamp->second) { - // it is known and it changed --> put it on the queue with any priority - // TODO: sort these correctly - _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); - _entitiesInQueue.insert(entity.get()); - } - }); - }); - break; + break; } } @@ -467,7 +467,7 @@ bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstream return true; } -void EntityTreeSendThread::editingEntityPointer(const EntityItemPointer entity) { +void EntityTreeSendThread::editingEntityPointer(const EntityItemPointer& entity) { if (entity) { if (_entitiesInQueue.find(entity.get()) == _entitiesInQueue.end() && _knownState.find(entity.get()) != _knownState.end()) { bool success = false; diff --git a/assignment-client/src/entities/EntityTreeSendThread.h b/assignment-client/src/entities/EntityTreeSendThread.h index 72ad2d0b10..49901491ff 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.h +++ b/assignment-client/src/entities/EntityTreeSendThread.h @@ -59,7 +59,7 @@ private: uint16_t _numEntities { 0 }; private slots: - void editingEntityPointer(const EntityItemPointer entity); + void editingEntityPointer(const EntityItemPointer& entity); void deletingEntityPointer(EntityItem* entity); }; From 86cbea73c8bffefc6371cd2c3fe41a59fded4602 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 25 Sep 2017 17:41:45 -0700 Subject: [PATCH 86/88] less magic --- assignment-client/src/entities/EntityPriorityQueue.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assignment-client/src/entities/EntityPriorityQueue.cpp b/assignment-client/src/entities/EntityPriorityQueue.cpp index 6d94f911ea..dcd5892fba 100644 --- a/assignment-client/src/entities/EntityPriorityQueue.cpp +++ b/assignment-client/src/entities/EntityPriorityQueue.cpp @@ -61,7 +61,7 @@ float ConicalView::computePriority(const EntityItemPointer& entity) const { return computePriority(cube); } else { // when in doubt give it something positive - return 1.0f; + return PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY; } } From 8134e2b7f9a36cd62b04ae3917d44baf3e62130d Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 25 Sep 2017 17:52:50 -0700 Subject: [PATCH 87/88] fix const violation --- libraries/entities/src/EntityTree.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 6769af45a3..bf37a08386 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -113,7 +113,7 @@ void EntityTree::readBitstreamToTree(const unsigned char* bitstream, // add entities QHash::const_iterator itr; for (itr = _entitiesToAdd.constBegin(); itr != _entitiesToAdd.constEnd(); ++itr) { - EntityItemPointer& entityItem = itr.value(); + const EntityItemPointer& entityItem = itr.value(); AddEntityOperator theOperator(getThisPointer(), entityItem); recurseTreeWithOperator(&theOperator); postAddEntity(entityItem); From ad9a239b45ec4e5766e5e0a9d2f5aa3289667d15 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 26 Sep 2017 08:12:53 -0700 Subject: [PATCH 88/88] remove unused cruft --- .../src/entities/EntityPriorityQueue.cpp | 23 ------------------- .../src/entities/EntityPriorityQueue.h | 2 -- 2 files changed, 25 deletions(-) diff --git a/assignment-client/src/entities/EntityPriorityQueue.cpp b/assignment-client/src/entities/EntityPriorityQueue.cpp index dcd5892fba..999a05f2e2 100644 --- a/assignment-client/src/entities/EntityPriorityQueue.cpp +++ b/assignment-client/src/entities/EntityPriorityQueue.cpp @@ -51,26 +51,3 @@ float ConicalView::computePriority(const AACube& cube) const { } return PrioritizedEntity::DO_NOT_SEND; } - -// static -float ConicalView::computePriority(const EntityItemPointer& entity) const { - assert(entity); - bool success; - AACube cube = entity->getQueryAACube(success); - if (success) { - return computePriority(cube); - } else { - // when in doubt give it something positive - return PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY; - } -} - -float PrioritizedEntity::updatePriority(const ConicalView& conicalView) { - EntityItemPointer entity = _weakEntity.lock(); - if (entity) { - _priority = conicalView.computePriority(entity); - } else { - _priority = PrioritizedEntity::DO_NOT_SEND; - } - return _priority; -} diff --git a/assignment-client/src/entities/EntityPriorityQueue.h b/assignment-client/src/entities/EntityPriorityQueue.h index a5d0ab05ff..e308d9b549 100644 --- a/assignment-client/src/entities/EntityPriorityQueue.h +++ b/assignment-client/src/entities/EntityPriorityQueue.h @@ -27,7 +27,6 @@ public: ConicalView(const ViewFrustum& viewFrustum) { set(viewFrustum); } void set(const ViewFrustum& viewFrustum); float computePriority(const AACube& cube) const; - float computePriority(const EntityItemPointer& entity) const; private: glm::vec3 _position { 0.0f, 0.0f, 0.0f }; glm::vec3 _direction { 0.0f, 0.0f, 1.0f }; @@ -44,7 +43,6 @@ public: static const float WHEN_IN_DOUBT_PRIORITY; PrioritizedEntity(EntityItemPointer entity, float priority, bool forceRemove = false) : _weakEntity(entity), _rawEntityPointer(entity.get()), _priority(priority), _forceRemove(forceRemove) {} - float updatePriority(const ConicalView& view); EntityItemPointer getEntity() const { return _weakEntity.lock(); } EntityItem* getRawEntityPointer() const { return _rawEntityPointer; } float getPriority() const { return _priority; }