overte-HifiExperiments/libraries/entities/src/DiffTraversal.cpp
2017-09-29 11:34:35 -07:00

293 lines
13 KiB
C++

//
// 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"
#include <OctreeUtils.h>
DiffTraversal::Waypoint::Waypoint(EntityTreeElementPointer& element) : _nextIndex(0) {
assert(element);
_weakElement = element;
}
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) {
// 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) {
// 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) {
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;
}
}
}
}
}
}
next.element.reset();
}
void DiffTraversal::Waypoint::getNextVisibleElementRepeat(
DiffTraversal::VisibleElement& next, const DiffTraversal::View& view, uint64_t lastTime) {
if (_nextIndex == -1) {
// root case is special
++_nextIndex;
EntityTreeElementPointer element = _weakElement.lock();
if (element->getLastChangedContent() > lastTime) {
next.element = element;
next.intersection = ViewFrustum::INTERSECT;
return;
}
}
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) {
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;
}
}
}
}
}
}
}
next.element.reset();
next.intersection = ViewFrustum::OUTSIDE;
}
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();
next.element = element;
next.intersection = ViewFrustum::INTERSECT;
return;
}
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) {
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
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;
// element's intersection with lastView was effectively OUTSIDE
next.intersection = ViewFrustum::OUTSIDE;
return;
}
}
}
}
}
}
}
}
next.element.reset();
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, bool usesViewFrustum) {
assert(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 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
//
_currentView.usesViewFrustum = usesViewFrustum;
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;
_getNextVisibleElementCallback = [&](DiffTraversal::VisibleElement& next) {
_path.back().getNextVisibleElementFirstTime(next, _currentView);
};
} 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);
};
} 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);
};
}
_path.clear();
_path.push_back(DiffTraversal::Waypoint(root));
// set root fork's index such that root element returned at getNextElement()
_path.back().initRootNextIndex();
_currentView.startTime = 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) {
_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;
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<void (DiffTraversal::VisibleElement&)> 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);
}
}