From 8b7c43f3b1c79c85bbb98d3259eea495eba07eb0 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 9 Aug 2017 17:22:47 -0700 Subject: [PATCH] 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