add LOD culling in DiffTraversal

This commit is contained in:
Andrew Meadows 2017-08-09 17:22:47 -07:00
parent 0758b60afc
commit 8b7c43f3b1
3 changed files with 109 additions and 63 deletions

View file

@ -11,29 +11,39 @@
#include "DiffTraversal.h"
#include <OctreeUtils.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
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 {

View file

@ -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<Waypoint> _path;
std::function<void (VisibleElement&)> _getNextVisibleElementCallback { nullptr };
std::function<void (VisibleElement&)> _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

View file

@ -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