split EntityPriorityQueue stuff into separate file

This commit is contained in:
Andrew Meadows 2017-08-08 13:05:08 -07:00
parent 64cd209835
commit abf968aab6
4 changed files with 262 additions and 230 deletions

View file

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

View file

@ -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 <queue>
#include <AACube.h>
#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>, PrioritizedEntity::Compare >;
#endif // hifi_EntityPriorityQueue_h

View file

@ -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));
}
}
}

View file

@ -12,80 +12,15 @@
#ifndef hifi_EntityTreeSendThread_h
#define hifi_EntityTreeSendThread_h
#include <queue>
#include "../octree/OctreeSendThread.h"
#include <AACube.h>
#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>, 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<Fork> _traversalPath;
std::vector<TraversalWaypoint> _traversalPath;
std::function<void (VisibleElement&)> _getNextVisibleElementCallback { nullptr };
std::function<void (VisibleElement&)> _scanNextElementCallback { nullptr };
uint64_t _startOfCompletedTraversal { 0 };