From d774dc2060e546612526cf7df7877ee39ad87812 Mon Sep 17 00:00:00 2001 From: Clement Date: Thu, 26 Apr 2018 18:34:38 -0700 Subject: [PATCH] WIP Only use conical frustums on the server --- .../src/entities/EntityPriorityQueue.cpp | 76 -------- .../src/entities/EntityTreeSendThread.cpp | 87 ++------- .../src/entities/EntityTreeSendThread.h | 4 +- libraries/entities/src/DiffTraversal.cpp | 179 ++++++++---------- libraries/entities/src/DiffTraversal.h | 14 +- .../entities/src}/EntityPriorityQueue.h | 38 +--- libraries/shared/src/GLMHelpers.cpp | 6 + libraries/shared/src/GLMHelpers.h | 2 + libraries/shared/src/ViewFrustum.cpp | 7 - .../shared/src/shared/ConicalViewFrustum.cpp | 125 ++++++++++++ .../shared/src/shared/ConicalViewFrustum.h | 64 +++++++ 11 files changed, 306 insertions(+), 296 deletions(-) delete mode 100644 assignment-client/src/entities/EntityPriorityQueue.cpp rename {assignment-client/src/entities => libraries/entities/src}/EntityPriorityQueue.h (70%) create mode 100644 libraries/shared/src/shared/ConicalViewFrustum.cpp create mode 100644 libraries/shared/src/shared/ConicalViewFrustum.h diff --git a/assignment-client/src/entities/EntityPriorityQueue.cpp b/assignment-client/src/entities/EntityPriorityQueue.cpp deleted file mode 100644 index 88dee58f9d..0000000000 --- a/assignment-client/src/entities/EntityPriorityQueue.cpp +++ /dev/null @@ -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; -} diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index 2e57f2e00f..0d943055f4 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -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); } } diff --git a/assignment-client/src/entities/EntityTreeSendThread.h b/assignment-client/src/entities/EntityTreeSendThread.h index 9eea98b7fd..1305d7bfc7 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.h +++ b/assignment-client/src/entities/EntityTreeSendThread.h @@ -17,8 +17,9 @@ #include "../octree/OctreeSendThread.h" #include +#include +#include -#include "EntityPriorityQueue.h" class EntityNodeData; class EntityItem; @@ -51,7 +52,6 @@ private: DiffTraversal _traversal; EntityPriorityQueue _sendQueue; std::unordered_map _knownState; - ConicalView _conicalView; // cached optimized view for fast priority calculations // packet construction stuff EntityTreeElementExtraEncodeDataPointer _extraEncodeData { new EntityTreeElementExtraEncodeData() }; diff --git a/libraries/entities/src/DiffTraversal.cpp b/libraries/entities/src/DiffTraversal.cpp index 39328e11ad..36d1f41267 100644 --- a/libraries/entities/src/DiffTraversal.cpp +++ b/libraries/entities/src/DiffTraversal.cpp @@ -13,6 +13,8 @@ #include +#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); diff --git a/libraries/entities/src/DiffTraversal.h b/libraries/entities/src/DiffTraversal.h index 0fd014ac23..d62c7b8ee1 100644 --- a/libraries/entities/src/DiffTraversal.h +++ b/libraries/entities/src/DiffTraversal.h @@ -12,7 +12,7 @@ #ifndef hifi_DiffTraversal_h #define hifi_DiffTraversal_h -#include +#include #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(); } diff --git a/assignment-client/src/entities/EntityPriorityQueue.h b/libraries/entities/src/EntityPriorityQueue.h similarity index 70% rename from assignment-client/src/entities/EntityPriorityQueue.h rename to libraries/entities/src/EntityPriorityQueue.h index 9210ac549f..354cd70af3 100644 --- a/assignment-client/src/entities/EntityPriorityQueue.h +++ b/libraries/entities/src/EntityPriorityQueue.h @@ -15,44 +15,14 @@ #include #include -#include -#include -#include - -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 _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(); } diff --git a/libraries/shared/src/GLMHelpers.cpp b/libraries/shared/src/GLMHelpers.cpp index 72710a6a7d..1776d63173 100644 --- a/libraries/shared/src/GLMHelpers.cpp +++ b/libraries/shared/src/GLMHelpers.cpp @@ -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, diff --git a/libraries/shared/src/GLMHelpers.h b/libraries/shared/src/GLMHelpers.h index 5c9a8b5ca1..0e1af27cd2 100644 --- a/libraries/shared/src/GLMHelpers.h +++ b/libraries/shared/src/GLMHelpers.h @@ -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); diff --git a/libraries/shared/src/ViewFrustum.cpp b/libraries/shared/src/ViewFrustum.cpp index c2b92aac5f..c89a9fbd60 100644 --- a/libraries/shared/src/ViewFrustum.cpp +++ b/libraries/shared/src/ViewFrustum.cpp @@ -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 diff --git a/libraries/shared/src/shared/ConicalViewFrustum.cpp b/libraries/shared/src/shared/ConicalViewFrustum.cpp new file mode 100644 index 0000000000..906bbc48a3 --- /dev/null +++ b/libraries/shared/src/shared/ConicalViewFrustum.cpp @@ -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; +} diff --git a/libraries/shared/src/shared/ConicalViewFrustum.h b/libraries/shared/src/shared/ConicalViewFrustum.h new file mode 100644 index 0000000000..3cadedda9d --- /dev/null +++ b/libraries/shared/src/shared/ConicalViewFrustum.h @@ -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 + +#include + +class AACube; +class ViewFrustum; +using ViewFrustums = std::vector; + +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; + + +#endif /* hifi_ConicalViewFrustum_h */