From 49e11d2173743a807c894946456da32ab78d4b2d Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 12 Sep 2017 22:27:42 -0700 Subject: [PATCH] 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