WIP Only use conical frustums on the server

This commit is contained in:
Clement 2018-04-26 18:34:38 -07:00
parent 21396287a4
commit d774dc2060
11 changed files with 306 additions and 296 deletions

View file

@ -1,76 +0,0 @@
//
// 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 PrioritizedEntity::DO_NOT_SEND = -1.0e-6f;
const float PrioritizedEntity::FORCE_REMOVE = -1.0e-5f;
const float PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY = 1.0f;
void ConicalViewFrustum::set(const ViewFrustum& viewFrustum) {
// The ConicalView has two parts: a central sphere (same as ViewFrustum) 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 ConicalViewFrustum::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;
}
// We check the angle between the center of the cube and the _direction of the view.
// If it is less than the sum of the half-angle from center of cone to outer edge plus
// the half apparent angle of the bounding sphere then it is in view.
//
// The math here is left as an exercise for the reader with the following hints:
// (1) We actually check the dot product of the cube's local position rather than the angle and
// (2) we take advantage of this trig identity: cos(A+B) = cos(A)*cos(B) - sin(A)*sin(B)
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 PrioritizedEntity::DO_NOT_SEND;
}
void ConicalView::set(const DiffTraversal::View& view) {
auto size = view.viewFrustums.size();
_conicalViewFrustums.resize(size);
for (size_t i = 0; i < size; ++i) {
_conicalViewFrustums[i].set(view.viewFrustums[i]);
}
}
float ConicalView::computePriority(const AACube& cube) const {
if (_conicalViewFrustums.empty()) {
return PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY;
}
float priority = PrioritizedEntity::DO_NOT_SEND;
for (const auto& view : _conicalViewFrustums) {
priority = std::max(priority, view.computePriority(cube));
}
return priority;
}

View file

@ -141,16 +141,8 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O
if (forceRemove) {
priority = PrioritizedEntity::FORCE_REMOVE;
} else {
bool success = false;
AACube cube = entity->getQueryAACube(success);
if (success) {
const auto& view = _traversal.getCurrentView();
if (view.intersects(cube) && view.isBigEnough(cube)) {
priority = _conicalView.computePriority(cube);
}
} else {
priority = PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY;
}
const auto& view = _traversal.getCurrentView();
priority = view.computePriority(entity);
}
if (priority != PrioritizedEntity::DO_NOT_SEND) {
@ -235,11 +227,6 @@ void EntityTreeSendThread::startNewTraversal(const DiffTraversal::View& view, En
// (3) Differential = view has changed --> find what has changed or in new view but not old
//
// The "scanCallback" we provide to the traversal depends on the type:
//
// The _conicalView is updated here as a cached view approximation used by the lambdas for efficient
// computation of entity sorting priorities.
//
_conicalView.set(_traversal.getCurrentView());
switch (type) {
case DiffTraversal::First:
@ -251,25 +238,8 @@ void EntityTreeSendThread::startNewTraversal(const DiffTraversal::View& view, En
if (_sendQueue.contains(entity.get())) {
return;
}
float priority = PrioritizedEntity::DO_NOT_SEND;
bool success = false;
AACube cube = entity->getQueryAACube(success);
if (success) {
const auto& view = _traversal.getCurrentView();
// Check the size of the entity, it's possible that a "too small to see" entity is included in a
// 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.
if ((next.intersection == ViewFrustum::INSIDE || view.intersects(cube)) &&
view.isBigEnough(cube)) {
priority = _conicalView.computePriority(cube);
}
} else {
priority = PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY;
}
const auto& view = _traversal.getCurrentView();
float priority = view.computePriority(entity);
if (priority != PrioritizedEntity::DO_NOT_SEND) {
_sendQueue.emplace(entity, priority);
@ -288,21 +258,11 @@ void EntityTreeSendThread::startNewTraversal(const DiffTraversal::View& view, En
}
float priority = PrioritizedEntity::DO_NOT_SEND;
auto knownTimestamp = _knownState.find(entity.get());
if (knownTimestamp == _knownState.end()) {
bool success = false;
AACube cube = entity->getQueryAACube(success);
if (success) {
const auto& view = _traversal.getCurrentView();
// See the DiffTraversal::First case for an explanation of the "entity is too small" check
if ((next.intersection == ViewFrustum::INSIDE || view.intersects(cube)) &&
view.isBigEnough(cube)) {
priority = _conicalView.computePriority(cube);
}
} else {
priority = PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY;
}
const auto& view = _traversal.getCurrentView();
priority = view.computePriority(entity);
} else if (entity->getLastEdited() > knownTimestamp->second ||
entity->getLastChangedOnServer() > knownTimestamp->second) {
// it is known and it changed --> put it on the queue with any priority
@ -310,7 +270,6 @@ void EntityTreeSendThread::startNewTraversal(const DiffTraversal::View& view, En
priority = PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY;
}
if (priority != PrioritizedEntity::DO_NOT_SEND) {
_sendQueue.emplace(entity, priority);
}
@ -328,21 +287,11 @@ void EntityTreeSendThread::startNewTraversal(const DiffTraversal::View& view, En
}
float priority = PrioritizedEntity::DO_NOT_SEND;
auto knownTimestamp = _knownState.find(entity.get());
if (knownTimestamp == _knownState.end()) {
bool success = false;
AACube cube = entity->getQueryAACube(success);
if (success) {
const auto& view = _traversal.getCurrentView();
// See the DiffTraversal::First case for an explanation of the "entity is too small" check
if ((next.intersection == ViewFrustum::INSIDE || view.intersects(cube)) &&
view.isBigEnough(cube)) {
priority = _conicalView.computePriority(cube);
}
} else {
priority = PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY;
}
const auto& view = _traversal.getCurrentView();
priority = view.computePriority(entity);
} else if (entity->getLastEdited() > knownTimestamp->second ||
entity->getLastChangedOnServer() > knownTimestamp->second) {
// it is known and it changed --> put it on the queue with any priority
@ -350,7 +299,6 @@ void EntityTreeSendThread::startNewTraversal(const DiffTraversal::View& view, En
priority = PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY;
}
if (priority != PrioritizedEntity::DO_NOT_SEND) {
_sendQueue.emplace(entity, priority);
}
@ -463,14 +411,13 @@ bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstream
void EntityTreeSendThread::editingEntityPointer(const EntityItemPointer& entity) {
if (entity) {
if (!_sendQueue.contains(entity.get()) && _knownState.find(entity.get()) != _knownState.end()) {
bool success = false;
AACube cube = entity->getQueryAACube(success);
if (success) {
// We can force a removal from _knownState if the current view is used and entity is out of view
if (_traversal.doesCurrentUseViewFrustum() && !_traversal.getCurrentView().intersects(cube)) {
_sendQueue.emplace(entity, PrioritizedEntity::FORCE_REMOVE, true);
}
} else {
const auto& view = _traversal.getCurrentView();
float priority = view.computePriority(entity);
// We can force a removal from _knownState if the current view is used and entity is out of view
if (priority == PrioritizedEntity::DO_NOT_SEND) {
_sendQueue.emplace(entity, PrioritizedEntity::FORCE_REMOVE, true);
} else if (priority == PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY) {
_sendQueue.emplace(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY, true);
}
}

View file

@ -17,8 +17,9 @@
#include "../octree/OctreeSendThread.h"
#include <DiffTraversal.h>
#include <EntityPriorityQueue.h>
#include <shared/ConicalViewFrustum.h>
#include "EntityPriorityQueue.h"
class EntityNodeData;
class EntityItem;
@ -51,7 +52,6 @@ private:
DiffTraversal _traversal;
EntityPriorityQueue _sendQueue;
std::unordered_map<EntityItem*, uint64_t> _knownState;
ConicalView _conicalView; // cached optimized view for fast priority calculations
// packet construction stuff
EntityTreeElementExtraEncodeDataPointer _extraEncodeData { new EntityTreeElementExtraEncodeData() };

View file

@ -13,6 +13,8 @@
#include <OctreeUtils.h>
#include "EntityPriorityQueue.h"
DiffTraversal::Waypoint::Waypoint(EntityTreeElementPointer& element) : _nextIndex(0) {
assert(element);
_weakElement = element;
@ -35,19 +37,9 @@ void DiffTraversal::Waypoint::getNextVisibleElementFirstTime(DiffTraversal::Visi
while (_nextIndex < NUMBER_OF_CHILDREN) {
EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex);
++_nextIndex;
if (nextElement) {
const auto& cube = nextElement->getAACube();
if (!view.usesViewFrustums()) {
// No LOD truncation if we aren't using the view frustum
next.element = nextElement;
return;
} else if (view.intersects(cube)) {
// check for LOD truncation
if (view.isBigEnough(cube, MIN_ELEMENT_ANGULAR_DIAMETER)) {
next.element = nextElement;
return;
}
}
if (nextElement && view.shouldTraverseElement(*nextElement)) {
next.element = nextElement;
return;
}
}
}
@ -63,7 +55,6 @@ void DiffTraversal::Waypoint::getNextVisibleElementRepeat(
EntityTreeElementPointer element = _weakElement.lock();
if (element->getLastChangedContent() > lastTime) {
next.element = element;
next.intersection = ViewFrustum::INTERSECT;
return;
}
}
@ -73,30 +64,17 @@ void DiffTraversal::Waypoint::getNextVisibleElementRepeat(
while (_nextIndex < NUMBER_OF_CHILDREN) {
EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex);
++_nextIndex;
if (nextElement && nextElement->getLastChanged() > lastTime) {
if (!view.usesViewFrustums()) {
// No LOD truncation if we aren't using the view frustum
next.element = nextElement;
next.intersection = ViewFrustum::INSIDE;
return;
} else {
// check for LOD truncation
const auto& cube = nextElement->getAACube();
if (view.isBigEnough(cube, MIN_ELEMENT_ANGULAR_DIAMETER)) {
ViewFrustum::intersection intersection = view.calculateIntersection(cube);
if (intersection != ViewFrustum::OUTSIDE) {
next.element = nextElement;
next.intersection = intersection;
return;
}
}
}
if (nextElement &&
nextElement->getLastChanged() > lastTime &&
view.shouldTraverseElement(*nextElement)) {
next.element = nextElement;
return;
}
}
}
}
next.element.reset();
next.intersection = ViewFrustum::OUTSIDE;
}
void DiffTraversal::Waypoint::getNextVisibleElementDifferential(DiffTraversal::VisibleElement& next,
@ -106,7 +84,6 @@ void DiffTraversal::Waypoint::getNextVisibleElementDifferential(DiffTraversal::V
++_nextIndex;
EntityTreeElementPointer element = _weakElement.lock();
next.element = element;
next.intersection = ViewFrustum::INTERSECT;
return;
} else if (_nextIndex < NUMBER_OF_CHILDREN) {
EntityTreeElementPointer element = _weakElement.lock();
@ -114,74 +91,14 @@ void DiffTraversal::Waypoint::getNextVisibleElementDifferential(DiffTraversal::V
while (_nextIndex < NUMBER_OF_CHILDREN) {
EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex);
++_nextIndex;
if (nextElement) {
// check for LOD truncation
const auto& cube = nextElement->getAACube();
if (view.isBigEnough(cube, MIN_ELEMENT_ANGULAR_DIAMETER)) {
ViewFrustum::intersection intersection = view.calculateIntersection(cube);
if (intersection != ViewFrustum::OUTSIDE) {
next.element = nextElement;
next.intersection = intersection;
return;
}
}
if (nextElement && view.shouldTraverseElement(*nextElement)) {
next.element = nextElement;
return;
}
}
}
}
next.element.reset();
next.intersection = ViewFrustum::OUTSIDE;
}
bool DiffTraversal::View::isBigEnough(const AACube& cube, float minDiameter) const {
if (viewFrustums.empty()) {
// Everything is big enough when not using view frustums
return true;
}
bool isBigEnough = std::any_of(std::begin(viewFrustums), std::end(viewFrustums),
[&](const ViewFrustum& viewFrustum) {
return isAngularSizeBigEnough(viewFrustum.getPosition(), cube, lodScaleFactor, minDiameter);
});
return isBigEnough;
}
bool DiffTraversal::View::intersects(const AACube& cube) const {
if (viewFrustums.empty()) {
// Everything intersects when not using view frustums
return true;
}
bool intersects = std::any_of(std::begin(viewFrustums), std::end(viewFrustums),
[&](const ViewFrustum& viewFrustum) {
return viewFrustum.cubeIntersectsKeyhole(cube);
});
return intersects;
}
ViewFrustum::intersection DiffTraversal::View::calculateIntersection(const AACube& cube) const {
if (viewFrustums.empty()) {
// Everything is inside when not using view frustums
return ViewFrustum::INSIDE;
}
ViewFrustum::intersection intersection = ViewFrustum::OUTSIDE;
for (const auto& viewFrustum : viewFrustums) {
switch (viewFrustum.calculateCubeKeyholeIntersection(cube)) {
case ViewFrustum::INSIDE:
return ViewFrustum::INSIDE;
case ViewFrustum::INTERSECT:
intersection = ViewFrustum::INTERSECT;
break;
default:
// DO NOTHING
break;
}
}
return intersection;
}
bool DiffTraversal::View::usesViewFrustums() const {
@ -204,6 +121,73 @@ bool DiffTraversal::View::isVerySimilar(const View& view) const {
return true;
}
float DiffTraversal::View::computePriority(const EntityItemPointer& entity) const {
if (!entity) {
return PrioritizedEntity::DO_NOT_SEND;
}
if (!usesViewFrustums()) {
return PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY;
}
bool success = false;
auto cube = entity->getQueryAACube(success);
if (!success) {
return PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY;
}
auto center = cube.calcCenter(); // center of bounding sphere
auto radius = 0.5f * SQRT_THREE * cube.getScale(); // radius of bounding sphere
auto priority = PrioritizedEntity::DO_NOT_SEND;
for (const auto& frustum : viewFrustums) {
auto position = center - frustum.getPosition(); // position of bounding sphere in view-frame
float distance = glm::length(position); // distance to center of bounding sphere
// Check the size of the entity, it's possible that a "too small to see" entity is included in a
// 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.
float angularSize = frustum.getAngularSize(distance, radius);
if (angularSize > lodScaleFactor * MIN_ENTITY_ANGULAR_DIAMETER &&
frustum.intersects(position, distance, radius)) {
// use the angular size as priority
// we compute the max priority for all frustums
priority = std::max(priority, angularSize);
}
}
return priority;
}
bool DiffTraversal::View::shouldTraverseElement(const EntityTreeElement& element) const {
if (!usesViewFrustums()) {
return true;
}
const auto& cube = element.getAACube();
auto center = cube.calcCenter(); // center of bounding sphere
auto radius = 0.5f * SQRT_THREE * cube.getScale(); // radius of bounding sphere
return any_of(begin(viewFrustums), end(viewFrustums), [&](const ConicalViewFrustum& frustum) {
auto position = center - frustum.getPosition(); // position of bounding sphere in view-frame
float distance = glm::length(position); // distance to center of bounding sphere
// Check the size of the entity, it's possible that a "too small to see" entity is included in a
// 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.
float angularSize = frustum.getAngularSize(distance, radius);
return angularSize > lodScaleFactor * MIN_ELEMENT_ANGULAR_DIAMETER &&
frustum.intersects(position, distance, radius);
});
}
DiffTraversal::DiffTraversal() {
const int32_t MIN_PATH_DEPTH = 16;
_path.reserve(MIN_PATH_DEPTH);
@ -262,7 +246,6 @@ DiffTraversal::Type DiffTraversal::prepareNewTraversal(const DiffTraversal::View
void DiffTraversal::getNextVisibleElement(DiffTraversal::VisibleElement& next) {
if (_path.empty()) {
next.element.reset();
next.intersection = ViewFrustum::OUTSIDE;
return;
}
_getNextVisibleElementCallback(next);

View file

@ -12,7 +12,7 @@
#ifndef hifi_DiffTraversal_h
#define hifi_DiffTraversal_h
#include <ViewFrustum.h>
#include <shared/ConicalViewFrustum.h>
#include "EntityTreeElement.h"
@ -24,19 +24,18 @@ public:
class VisibleElement {
public:
EntityTreeElementPointer element;
ViewFrustum::intersection intersection { ViewFrustum::OUTSIDE };
};
// View is a struct with a ViewFrustum and LOD parameters
class View {
public:
bool isBigEnough(const AACube& cube, float minDiameter = MIN_ENTITY_ANGULAR_DIAMETER) const;
bool intersects(const AACube& cube) const;
bool usesViewFrustums() const;
bool isVerySimilar(const View& view) const;
ViewFrustum::intersection calculateIntersection(const AACube& cube) const;
ViewFrustums viewFrustums;
bool shouldTraverseElement(const EntityTreeElement& element) const;
float computePriority(const EntityItemPointer& entity) const;
ConicalViewFrustums viewFrustums;
uint64_t startTime { 0 };
float lodScaleFactor { 1.0f };
};
@ -65,9 +64,6 @@ public:
Type prepareNewTraversal(const DiffTraversal::View& view, EntityTreeElementPointer root);
const View& getCurrentView() const { return _currentView; }
const View& getCompletedView() const { return _completedView; }
bool doesCurrentUseViewFrustum() const { return _currentView.usesViewFrustums(); }
uint64_t getStartOfCompletedTraversal() const { return _completedView.startTime; }
bool finished() const { return _path.empty(); }

View file

@ -15,44 +15,14 @@
#include <queue>
#include <unordered_set>
#include <AACube.h>
#include <DiffTraversal.h>
#include <EntityTreeElement.h>
const float SQRT_TWO_OVER_TWO = 0.7071067811865f;
const float DEFAULT_VIEW_RADIUS = 10.0f;
// ConicalViewFrustum is an approximation of a ViewFrustum for fast calculation of sort priority.
class ConicalViewFrustum {
public:
ConicalViewFrustum() {}
ConicalViewFrustum(const ViewFrustum& viewFrustum) { set(viewFrustum); }
void set(const ViewFrustum& viewFrustum);
float computePriority(const AACube& cube) 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 };
};
// Simple wrapper around a set of conical view frustums
class ConicalView {
public:
ConicalView() {}
void set(const DiffTraversal::View& view);
float computePriority(const AACube& cube) const;
private:
std::vector<ConicalViewFrustum> _conicalViewFrustums;
};
#include "EntityItem.h"
// PrioritizedEntity is a placeholder in a sorted queue.
class PrioritizedEntity {
public:
static const float DO_NOT_SEND;
static const float FORCE_REMOVE;
static const float WHEN_IN_DOUBT_PRIORITY;
static constexpr float DO_NOT_SEND { -1.0e-6f };
static constexpr float FORCE_REMOVE { -1.0e-5f };
static constexpr float WHEN_IN_DOUBT_PRIORITY { 1.0f };
PrioritizedEntity(EntityItemPointer entity, float priority, bool forceRemove = false) : _weakEntity(entity), _rawEntityPointer(entity.get()), _priority(priority), _forceRemove(forceRemove) {}
EntityItemPointer getEntity() const { return _weakEntity.lock(); }

View file

@ -222,6 +222,12 @@ int unpackOrientationQuatFromSixBytes(const unsigned char* buffer, glm::quat& qu
return 6;
}
bool closeEnough(float a, float b, float relativeError) {
assert(relativeError >= 0.0f);
// NOTE: we add EPSILON to the denominator so we can avoid checking for division by zero.
// This method works fine when: fabsf(a + b) >> EPSILON
return fabsf(a - b) / (0.5f * fabsf(a + b) + EPSILON) < relativeError;
}
// Safe version of glm::eulerAngles; uses the factorization method described in David Eberly's
// http://www.geometrictools.com/Documentation/EulerAngles.pdf (via Clyde,

View file

@ -137,6 +137,8 @@ int unpackFloatScalarFromSignedTwoByteFixed(const int16_t* byteFixedPointer, flo
int packFloatVec3ToSignedTwoByteFixed(unsigned char* destBuffer, const glm::vec3& srcVector, int radix);
int unpackFloatVec3FromSignedTwoByteFixed(const unsigned char* sourceBuffer, glm::vec3& destination, int radix);
bool closeEnough(float a, float b, float relativeError);
/// \return vec3 with euler angles in radians
glm::vec3 safeEulerAngles(const glm::quat& q);

View file

@ -338,13 +338,6 @@ bool ViewFrustum::boxIntersectsKeyhole(const AABox& box) const {
return true;
}
bool closeEnough(float a, float b, float relativeError) {
assert(relativeError >= 0.0f);
// NOTE: we add EPSILON to the denominator so we can avoid checking for division by zero.
// This method works fine when: fabsf(a + b) >> EPSILON
return fabsf(a - b) / (0.5f * fabsf(a + b) + EPSILON) < relativeError;
}
// TODO: the slop and relative error should be passed in by argument rather than hard-coded.
bool ViewFrustum::isVerySimilar(const ViewFrustum& other) const {
const float MIN_POSITION_SLOP_SQUARED = 25.0f; // 5 meters squared

View file

@ -0,0 +1,125 @@
//
// ConicalViewFrustum.cpp
// libraries/shared/src/shared
//
// Created by Clement Brisset 4/26/18
// 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 "ConicalViewFrustum.h"
#include "../ViewFrustum.h"
void ConicalViewFrustum::set(const ViewFrustum& viewFrustum) {
// The ConicalViewFrustum has two parts: a central sphere (same as ViewFrustum) 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();
_radius = viewFrustum.getCenterRadius();
_farClip = viewFrustum.getFarClip();
// Considering the rectangle intersection the frustum and the perpendicular plane 1 unit
// away from the frustum's origin
// We are looking for the angle between the ray that goes through the center of the rectangle
// and the ray that goes through one of the corners of the rectangle
// (Both rays coming from the frustum's origin)
// This angle will let us construct a cone in which the frustum is inscribed
// Let's define:
// A = aspect ratio = width / height
// fov = vertical field of view
// y = half height of the rectangle
// x = half width of the rectangle
// r = half diagonal of the rectangle
// then, we have:
// y / 1 = tan(fov / 2)
// x = A * y = A * tan(fov / 2)
// r^2 = x^2 + y^2 = (A^2 + 1) * tan^2(fov / 2)
// r / 1 = tan(angle) = sqrt((A^2 + 1) * tan^2(fov / 2))
// angle = atan(sqrt((A^2 + 1) * tan^2(fov / 2)))
float A = viewFrustum.getAspectRatio();
float t = tanf(0.5f * viewFrustum.getFieldOfView());
auto tan2Angle = (A * A + 1.0f) * (t * t);
_angle = atanf(sqrt(tan2Angle));
calculate();
}
void ConicalViewFrustum::calculate() {
_cosAngle = cosf(_angle);
_sinAngle = sqrtf(1.0f - _cosAngle * _cosAngle);
}
bool ConicalViewFrustum::isVerySimilar(const ConicalViewFrustum& other) const {
const float MIN_POSITION_SLOP_SQUARED = 25.0f; // 5 meters squared
const float MIN_ANGLE_BETWEEN = 0.174533f; // radian angle between 2 vectors 10 degrees apart
const float MIN_RELATIVE_ERROR = 0.01f; // 1%
return glm::distance2(_position, other._position) < MIN_POSITION_SLOP_SQUARED &&
angleBetween(_direction, other._direction) > MIN_ANGLE_BETWEEN &&
closeEnough(_angle, other._angle, MIN_RELATIVE_ERROR) &&
closeEnough(_farClip, other._farClip, MIN_RELATIVE_ERROR) &&
closeEnough(_radius, other._radius, MIN_RELATIVE_ERROR);
}
bool ConicalViewFrustum::intersects(const glm::vec3& position, float distance, float radius) const {
if (distance < _radius + radius) {
// Inside keyhole radius
return true;
}
if (distance > _farClip + radius) {
// Past far clip
return false;
}
// We check the angle between the center of the cube and the _direction of the view.
// If it is less than the sum of the half-angle from center of cone to outer edge plus
// the half apparent angle of the bounding sphere then it is in view.
//
// The math here is left as an exercise for the reader with the following hints:
// (1) We actually check the dot product of the cube's local position rather than the angle and
// (2) we take advantage of this trig identity: cos(A+B) = cos(A)*cos(B) - sin(A)*sin(B)
return glm::dot(position, _direction) >
sqrtf(distance * distance - radius * radius) * _cosAngle - radius * _sinAngle;
}
bool ConicalViewFrustum::getAngularSize(float distance, float radius) const {
const float AVOID_DIVIDE_BY_ZERO = 0.001f;
float angularSize = radius / (distance + AVOID_DIVIDE_BY_ZERO);
return angularSize;
}
int ConicalViewFrustum::serialize(unsigned char* destinationBuffer) const {
const unsigned char* startPosition = destinationBuffer;
memcpy(destinationBuffer, &_position, sizeof(_position));
destinationBuffer += sizeof(_position);
memcpy(destinationBuffer, &_direction, sizeof(_direction));
destinationBuffer += sizeof(_direction);
destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, _angle);
destinationBuffer += packClipValueToTwoByte(destinationBuffer, _farClip);
memcpy(destinationBuffer, &_radius, sizeof(_radius));
destinationBuffer += sizeof(_radius);
return destinationBuffer - startPosition;
}
int ConicalViewFrustum::deserialize(const unsigned char* sourceBuffer) {
const unsigned char* startPosition = sourceBuffer;
memcpy(&_position, sourceBuffer, sizeof(_position));
sourceBuffer += sizeof(_position);
memcpy(&_direction, sourceBuffer, sizeof(_direction));
sourceBuffer += sizeof(_direction);
sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*)sourceBuffer, &_angle);
sourceBuffer += unpackClipValueFromTwoByte(sourceBuffer, _farClip);
memcpy(&_radius, sourceBuffer, sizeof(_radius));
sourceBuffer += sizeof(_radius);
calculate();
return sourceBuffer - startPosition;
}

View file

@ -0,0 +1,64 @@
//
// ConicalViewFrustum.h
// libraries/shared/src/shared
//
// Created by Clement Brisset 4/26/18
// 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_ConicalViewFrustum_h
#define hifi_ConicalViewFrustum_h
#include <vector>
#include <glm/glm.hpp>
class AACube;
class ViewFrustum;
using ViewFrustums = std::vector<ViewFrustum>;
const float SQRT_TWO_OVER_TWO = 0.7071067811865f;
const float DEFAULT_VIEW_ANGLE = 1.0f;
const float DEFAULT_VIEW_RADIUS = 10.0f;
const float DEFAULT_VIEW_FAR_CLIP = 100.0f;
// ConicalViewFrustum is an approximation of a ViewFrustum for fast calculation of sort priority.
class ConicalViewFrustum {
public:
ConicalViewFrustum() = default;
ConicalViewFrustum(const ViewFrustum& viewFrustum) { set(viewFrustum); }
void set(const ViewFrustum& viewFrustum);
void calculate();
const glm::vec3& getPosition() const { return _position; }
const glm::vec3& getDirection() const { return _direction; }
float getAngle() const { return _angle; }
float getRadius() const { return _radius; }
float getFarClip() const { return _farClip; }
bool isVerySimilar(const ConicalViewFrustum& other) const;
bool intersects(const glm::vec3& position, float distance, float radius) const;
bool getAngularSize(float distance, float radius) const;
int serialize(unsigned char* destinationBuffer) const;
int deserialize(const unsigned char* sourceBuffer);
private:
glm::vec3 _position { 0.0f, 0.0f, 0.0f };
glm::vec3 _direction { 0.0f, 0.0f, 1.0f };
float _angle { DEFAULT_VIEW_ANGLE };
float _radius { DEFAULT_VIEW_RADIUS };
float _farClip { DEFAULT_VIEW_FAR_CLIP };
float _sinAngle { SQRT_TWO_OVER_TWO };
float _cosAngle { SQRT_TWO_OVER_TWO };
};
using ConicalViewFrustums = std::vector<ConicalViewFrustum>;
#endif /* hifi_ConicalViewFrustum_h */