mirror of
https://thingvellir.net/git/overte
synced 2025-03-27 23:52:03 +01:00
Merge pull request #12930 from Atlante45/fix/spec-cam
Add support for multifrustum entity queries
This commit is contained in:
commit
31d5633b4a
24 changed files with 585 additions and 457 deletions
|
@ -15,7 +15,7 @@ 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 ConicalView::set(const ViewFrustum& viewFrustum) {
|
||||
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();
|
||||
|
@ -31,7 +31,7 @@ void ConicalView::set(const ViewFrustum& viewFrustum) {
|
|||
_radius = viewFrustum.getCenterRadius();
|
||||
}
|
||||
|
||||
float ConicalView::computePriority(const AACube& cube) const {
|
||||
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
|
||||
|
@ -51,3 +51,26 @@ float ConicalView::computePriority(const AACube& cube) const {
|
|||
}
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -13,18 +13,20 @@
|
|||
#define hifi_EntityPriorityQueue_h
|
||||
|
||||
#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;
|
||||
|
||||
// ConicalView is an approximation of a ViewFrustum for fast calculation of sort priority.
|
||||
class ConicalView {
|
||||
// ConicalViewFrustum is an approximation of a ViewFrustum for fast calculation of sort priority.
|
||||
class ConicalViewFrustum {
|
||||
public:
|
||||
ConicalView() {}
|
||||
ConicalView(const ViewFrustum& viewFrustum) { set(viewFrustum); }
|
||||
ConicalViewFrustum() {}
|
||||
ConicalViewFrustum(const ViewFrustum& viewFrustum) { set(viewFrustum); }
|
||||
void set(const ViewFrustum& viewFrustum);
|
||||
float computePriority(const AACube& cube) const;
|
||||
private:
|
||||
|
@ -35,6 +37,16 @@ private:
|
|||
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;
|
||||
};
|
||||
|
||||
// PrioritizedEntity is a placeholder in a sorted queue.
|
||||
class PrioritizedEntity {
|
||||
public:
|
||||
|
@ -61,6 +73,50 @@ private:
|
|||
bool _forceRemove;
|
||||
};
|
||||
|
||||
using EntityPriorityQueue = std::priority_queue< PrioritizedEntity, std::vector<PrioritizedEntity>, PrioritizedEntity::Compare >;
|
||||
class EntityPriorityQueue {
|
||||
public:
|
||||
inline bool empty() const {
|
||||
assert(_queue.empty() == _entities.empty());
|
||||
return _queue.empty();
|
||||
}
|
||||
|
||||
inline const PrioritizedEntity& top() const {
|
||||
assert(!_queue.empty());
|
||||
return _queue.top();
|
||||
}
|
||||
|
||||
inline bool contains(const EntityItem* entity) const {
|
||||
return _entities.find(entity) != std::end(_entities);
|
||||
}
|
||||
|
||||
inline void emplace(const EntityItemPointer& entity, float priority, bool forceRemove = false) {
|
||||
assert(entity && !contains(entity.get()));
|
||||
_queue.emplace(entity, priority, forceRemove);
|
||||
_entities.insert(entity.get());
|
||||
assert(_queue.size() == _entities.size());
|
||||
}
|
||||
|
||||
inline void pop() {
|
||||
assert(!empty());
|
||||
_entities.erase(_queue.top().getRawEntityPointer());
|
||||
_queue.pop();
|
||||
assert(_queue.size() == _entities.size());
|
||||
}
|
||||
|
||||
inline void swap(EntityPriorityQueue& other) {
|
||||
std::swap(_queue, other._queue);
|
||||
std::swap(_entities, other._entities);
|
||||
}
|
||||
|
||||
private:
|
||||
using PriorityQueue = std::priority_queue<PrioritizedEntity,
|
||||
std::vector<PrioritizedEntity>,
|
||||
PrioritizedEntity::Compare>;
|
||||
|
||||
PriorityQueue _queue;
|
||||
// Keep dictionary of all the entities in the queue for fast contain checks.
|
||||
std::unordered_set<const EntityItem*> _entities;
|
||||
|
||||
};
|
||||
|
||||
#endif // hifi_EntityPriorityQueue_h
|
||||
|
|
|
@ -103,48 +103,58 @@ void EntityTreeSendThread::preDistributionProcessing() {
|
|||
void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData,
|
||||
bool viewFrustumChanged, bool isFullScene) {
|
||||
if (viewFrustumChanged || _traversal.finished()) {
|
||||
ViewFrustum viewFrustum;
|
||||
nodeData->copyCurrentViewFrustum(viewFrustum);
|
||||
EntityTreeElementPointer root = std::dynamic_pointer_cast<EntityTreeElement>(_myServer->getOctree()->getRoot());
|
||||
|
||||
|
||||
DiffTraversal::View newView;
|
||||
|
||||
ViewFrustum viewFrustum;
|
||||
if (nodeData->hasMainViewFrustum()) {
|
||||
nodeData->copyCurrentMainViewFrustum(viewFrustum);
|
||||
newView.viewFrustums.push_back(viewFrustum);
|
||||
}
|
||||
if (nodeData->hasSecondaryViewFrustum()) {
|
||||
nodeData->copyCurrentSecondaryViewFrustum(viewFrustum);
|
||||
newView.viewFrustums.push_back(viewFrustum);
|
||||
}
|
||||
|
||||
int32_t lodLevelOffset = nodeData->getBoundaryLevelAdjust() + (viewFrustumChanged ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST);
|
||||
startNewTraversal(viewFrustum, root, lodLevelOffset, nodeData->getUsesFrustum());
|
||||
newView.lodScaleFactor = powf(2.0f, lodLevelOffset);
|
||||
|
||||
startNewTraversal(newView, root);
|
||||
|
||||
// When the viewFrustum changed the sort order may be incorrect, so we re-sort
|
||||
// and also use the opportunity to cull anything no longer in view
|
||||
if (viewFrustumChanged && !_sendQueue.empty()) {
|
||||
EntityPriorityQueue prevSendQueue;
|
||||
_sendQueue.swap(prevSendQueue);
|
||||
_entitiesInQueue.clear();
|
||||
std::swap(_sendQueue, prevSendQueue);
|
||||
assert(_sendQueue.empty());
|
||||
|
||||
// Re-add elements from previous traversal if they still need to be sent
|
||||
float lodScaleFactor = _traversal.getCurrentLODScaleFactor();
|
||||
glm::vec3 viewPosition = _traversal.getCurrentView().getPosition();
|
||||
while (!prevSendQueue.empty()) {
|
||||
EntityItemPointer entity = prevSendQueue.top().getEntity();
|
||||
bool forceRemove = prevSendQueue.top().shouldForceRemove();
|
||||
prevSendQueue.pop();
|
||||
if (entity) {
|
||||
if (!forceRemove) {
|
||||
float priority = PrioritizedEntity::DO_NOT_SEND;
|
||||
|
||||
if (forceRemove) {
|
||||
priority = PrioritizedEntity::FORCE_REMOVE;
|
||||
} else {
|
||||
bool success = false;
|
||||
AACube cube = entity->getQueryAACube(success);
|
||||
if (success) {
|
||||
if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) {
|
||||
float priority = _conicalView.computePriority(cube);
|
||||
if (priority != PrioritizedEntity::DO_NOT_SEND) {
|
||||
float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE;
|
||||
float angularDiameter = cube.getScale() / distance;
|
||||
if (angularDiameter > MIN_ENTITY_ANGULAR_DIAMETER * lodScaleFactor) {
|
||||
_sendQueue.push(PrioritizedEntity(entity, priority));
|
||||
_entitiesInQueue.insert(entity.get());
|
||||
}
|
||||
}
|
||||
const auto& view = _traversal.getCurrentView();
|
||||
if (view.intersects(cube) && view.isBigEnough(cube)) {
|
||||
priority = _conicalView.computePriority(cube);
|
||||
}
|
||||
} else {
|
||||
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
|
||||
_entitiesInQueue.insert(entity.get());
|
||||
priority = PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY;
|
||||
}
|
||||
} else {
|
||||
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::FORCE_REMOVE, true));
|
||||
_entitiesInQueue.insert(entity.get());
|
||||
}
|
||||
|
||||
if (priority != PrioritizedEntity::DO_NOT_SEND) {
|
||||
_sendQueue.emplace(entity, priority, forceRemove);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -215,10 +225,9 @@ bool EntityTreeSendThread::addDescendantsToExtraFlaggedEntities(const QUuid& fil
|
|||
return hasNewChild || hasNewDescendants;
|
||||
}
|
||||
|
||||
void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTreeElementPointer root, int32_t lodLevelOffset,
|
||||
bool usesViewFrustum) {
|
||||
void EntityTreeSendThread::startNewTraversal(const DiffTraversal::View& view, EntityTreeElementPointer root) {
|
||||
|
||||
DiffTraversal::Type type = _traversal.prepareNewTraversal(view, root, lodLevelOffset, usesViewFrustum);
|
||||
DiffTraversal::Type type = _traversal.prepareNewTraversal(view, root);
|
||||
// there are three types of traversal:
|
||||
//
|
||||
// (1) FirstTime = at login --> find everything in view
|
||||
|
@ -236,161 +245,114 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree
|
|||
case DiffTraversal::First:
|
||||
// When we get to a First traversal, clear the _knownState
|
||||
_knownState.clear();
|
||||
if (usesViewFrustum) {
|
||||
float lodScaleFactor = _traversal.getCurrentLODScaleFactor();
|
||||
glm::vec3 viewPosition = _traversal.getCurrentView().getPosition();
|
||||
_traversal.setScanCallback([=](DiffTraversal::VisibleElement& next) {
|
||||
next.element->forEachEntity([=](EntityItemPointer entity) {
|
||||
// Bail early if we've already checked this entity this frame
|
||||
if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) {
|
||||
return;
|
||||
}
|
||||
bool success = false;
|
||||
AACube cube = entity->getQueryAACube(success);
|
||||
if (success) {
|
||||
if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) {
|
||||
// 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 distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE;
|
||||
float angularDiameter = cube.getScale() / distance;
|
||||
if (angularDiameter > MIN_ENTITY_ANGULAR_DIAMETER * lodScaleFactor) {
|
||||
float priority = _conicalView.computePriority(cube);
|
||||
_sendQueue.push(PrioritizedEntity(entity, priority));
|
||||
_entitiesInQueue.insert(entity.get());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
|
||||
_entitiesInQueue.insert(entity.get());
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
_traversal.setScanCallback([this](DiffTraversal::VisibleElement& next) {
|
||||
next.element->forEachEntity([this](EntityItemPointer entity) {
|
||||
// Bail early if we've already checked this entity this frame
|
||||
if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) {
|
||||
return;
|
||||
}
|
||||
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
|
||||
_entitiesInQueue.insert(entity.get());
|
||||
});
|
||||
});
|
||||
}
|
||||
break;
|
||||
case DiffTraversal::Repeat:
|
||||
if (usesViewFrustum) {
|
||||
float lodScaleFactor = _traversal.getCurrentLODScaleFactor();
|
||||
glm::vec3 viewPosition = _traversal.getCurrentView().getPosition();
|
||||
_traversal.setScanCallback([=](DiffTraversal::VisibleElement& next) {
|
||||
uint64_t startOfCompletedTraversal = _traversal.getStartOfCompletedTraversal();
|
||||
if (next.element->getLastChangedContent() > startOfCompletedTraversal) {
|
||||
next.element->forEachEntity([=](EntityItemPointer entity) {
|
||||
// Bail early if we've already checked this entity this frame
|
||||
if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) {
|
||||
return;
|
||||
}
|
||||
auto knownTimestamp = _knownState.find(entity.get());
|
||||
if (knownTimestamp == _knownState.end()) {
|
||||
bool success = false;
|
||||
AACube cube = entity->getQueryAACube(success);
|
||||
if (success) {
|
||||
if (next.intersection == ViewFrustum::INSIDE || _traversal.getCurrentView().cubeIntersectsKeyhole(cube)) {
|
||||
// See the DiffTraversal::First case for an explanation of the "entity is too small" check
|
||||
float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE;
|
||||
float angularDiameter = cube.getScale() / distance;
|
||||
if (angularDiameter > MIN_ENTITY_ANGULAR_DIAMETER * lodScaleFactor) {
|
||||
float priority = _conicalView.computePriority(cube);
|
||||
_sendQueue.push(PrioritizedEntity(entity, priority));
|
||||
_entitiesInQueue.insert(entity.get());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
|
||||
_entitiesInQueue.insert(entity.get());
|
||||
}
|
||||
} else if (entity->getLastEdited() > knownTimestamp->second
|
||||
|| entity->getLastChangedOnServer() > knownTimestamp->second) {
|
||||
// it is known and it changed --> put it on the queue with any priority
|
||||
// TODO: sort these correctly
|
||||
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
|
||||
_entitiesInQueue.insert(entity.get());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
_traversal.setScanCallback([this](DiffTraversal::VisibleElement& next) {
|
||||
uint64_t startOfCompletedTraversal = _traversal.getStartOfCompletedTraversal();
|
||||
if (next.element->getLastChangedContent() > startOfCompletedTraversal) {
|
||||
next.element->forEachEntity([this](EntityItemPointer entity) {
|
||||
// Bail early if we've already checked this entity this frame
|
||||
if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) {
|
||||
return;
|
||||
}
|
||||
auto knownTimestamp = _knownState.find(entity.get());
|
||||
if (knownTimestamp == _knownState.end()
|
||||
|| entity->getLastEdited() > knownTimestamp->second
|
||||
|| entity->getLastChangedOnServer() > knownTimestamp->second) {
|
||||
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
|
||||
_entitiesInQueue.insert(entity.get());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
case DiffTraversal::Differential:
|
||||
assert(usesViewFrustum);
|
||||
float lodScaleFactor = _traversal.getCurrentLODScaleFactor();
|
||||
glm::vec3 viewPosition = _traversal.getCurrentView().getPosition();
|
||||
float completedLODScaleFactor = _traversal.getCompletedLODScaleFactor();
|
||||
glm::vec3 completedViewPosition = _traversal.getCompletedView().getPosition();
|
||||
_traversal.setScanCallback([=] (DiffTraversal::VisibleElement& next) {
|
||||
next.element->forEachEntity([=](EntityItemPointer entity) {
|
||||
_traversal.setScanCallback([this](DiffTraversal::VisibleElement& next) {
|
||||
next.element->forEachEntity([&](EntityItemPointer entity) {
|
||||
// Bail early if we've already checked this entity this frame
|
||||
if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
if (priority != PrioritizedEntity::DO_NOT_SEND) {
|
||||
_sendQueue.emplace(entity, priority);
|
||||
}
|
||||
});
|
||||
});
|
||||
break;
|
||||
case DiffTraversal::Repeat:
|
||||
_traversal.setScanCallback([this](DiffTraversal::VisibleElement& next) {
|
||||
uint64_t startOfCompletedTraversal = _traversal.getStartOfCompletedTraversal();
|
||||
if (next.element->getLastChangedContent() > startOfCompletedTraversal) {
|
||||
next.element->forEachEntity([&](EntityItemPointer entity) {
|
||||
// Bail early if we've already checked this entity this frame
|
||||
if (_sendQueue.contains(entity.get())) {
|
||||
return;
|
||||
}
|
||||
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;
|
||||
}
|
||||
} else if (entity->getLastEdited() > knownTimestamp->second ||
|
||||
entity->getLastChangedOnServer() > knownTimestamp->second) {
|
||||
// it is known and it changed --> put it on the queue with any priority
|
||||
// TODO: sort these correctly
|
||||
priority = PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY;
|
||||
}
|
||||
|
||||
|
||||
if (priority != PrioritizedEntity::DO_NOT_SEND) {
|
||||
_sendQueue.emplace(entity, priority);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
break;
|
||||
case DiffTraversal::Differential:
|
||||
assert(view.usesViewFrustums());
|
||||
_traversal.setScanCallback([this] (DiffTraversal::VisibleElement& next) {
|
||||
next.element->forEachEntity([&](EntityItemPointer entity) {
|
||||
// Bail early if we've already checked this entity this frame
|
||||
if (_sendQueue.contains(entity.get())) {
|
||||
return;
|
||||
}
|
||||
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) {
|
||||
if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) {
|
||||
// See the DiffTraversal::First case for an explanation of the "entity is too small" check
|
||||
float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE;
|
||||
float angularDiameter = cube.getScale() / distance;
|
||||
if (angularDiameter > MIN_ENTITY_ANGULAR_DIAMETER * lodScaleFactor) {
|
||||
if (!_traversal.getCompletedView().cubeIntersectsKeyhole(cube)) {
|
||||
float priority = _conicalView.computePriority(cube);
|
||||
_sendQueue.push(PrioritizedEntity(entity, priority));
|
||||
_entitiesInQueue.insert(entity.get());
|
||||
} else {
|
||||
// If this entity was skipped last time because it was too small, we still need to send it
|
||||
distance = glm::distance(cube.calcCenter(), completedViewPosition) + MIN_VISIBLE_DISTANCE;
|
||||
angularDiameter = cube.getScale() / distance;
|
||||
if (angularDiameter <= MIN_ENTITY_ANGULAR_DIAMETER * completedLODScaleFactor) {
|
||||
// this object was skipped in last completed traversal
|
||||
float priority = _conicalView.computePriority(cube);
|
||||
_sendQueue.push(PrioritizedEntity(entity, priority));
|
||||
_entitiesInQueue.insert(entity.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
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 {
|
||||
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
|
||||
_entitiesInQueue.insert(entity.get());
|
||||
priority = PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY;
|
||||
}
|
||||
} else if (entity->getLastEdited() > knownTimestamp->second
|
||||
|| entity->getLastChangedOnServer() > knownTimestamp->second) {
|
||||
} else if (entity->getLastEdited() > knownTimestamp->second ||
|
||||
entity->getLastChangedOnServer() > knownTimestamp->second) {
|
||||
// it is known and it changed --> put it on the queue with any priority
|
||||
// TODO: sort these correctly
|
||||
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
|
||||
_entitiesInQueue.insert(entity.get());
|
||||
priority = PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY;
|
||||
}
|
||||
|
||||
|
||||
if (priority != PrioritizedEntity::DO_NOT_SEND) {
|
||||
_sendQueue.emplace(entity, priority);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -479,11 +441,10 @@ bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstream
|
|||
}
|
||||
}
|
||||
_sendQueue.pop();
|
||||
_entitiesInQueue.erase(entity.get());
|
||||
}
|
||||
nodeData->stats.encodeStopped();
|
||||
if (_sendQueue.empty()) {
|
||||
assert(_entitiesInQueue.empty());
|
||||
assert(_sendQueue.empty());
|
||||
params.stopReason = EncodeBitstreamParams::FINISHED;
|
||||
_extraEncodeData->entities.clear();
|
||||
}
|
||||
|
@ -501,18 +462,16 @@ bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstream
|
|||
|
||||
void EntityTreeSendThread::editingEntityPointer(const EntityItemPointer& entity) {
|
||||
if (entity) {
|
||||
if (_entitiesInQueue.find(entity.get()) == _entitiesInQueue.end() && _knownState.find(entity.get()) != _knownState.end()) {
|
||||
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().cubeIntersectsKeyhole(cube)) {
|
||||
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::FORCE_REMOVE, true));
|
||||
_entitiesInQueue.insert(entity.get());
|
||||
if (_traversal.doesCurrentUseViewFrustum() && !_traversal.getCurrentView().intersects(cube)) {
|
||||
_sendQueue.emplace(entity, PrioritizedEntity::FORCE_REMOVE, true);
|
||||
}
|
||||
} else {
|
||||
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY, true));
|
||||
_entitiesInQueue.insert(entity.get());
|
||||
_sendQueue.emplace(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,8 +41,7 @@ private:
|
|||
bool addAncestorsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData);
|
||||
bool addDescendantsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData);
|
||||
|
||||
void startNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset,
|
||||
bool usesViewFrustum);
|
||||
void startNewTraversal(const DiffTraversal::View& viewFrustum, EntityTreeElementPointer root);
|
||||
bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) override;
|
||||
|
||||
void preDistributionProcessing() override;
|
||||
|
@ -51,7 +50,6 @@ private:
|
|||
|
||||
DiffTraversal _traversal;
|
||||
EntityPriorityQueue _sendQueue;
|
||||
std::unordered_set<EntityItem*> _entitiesInQueue;
|
||||
std::unordered_map<EntityItem*, uint64_t> _knownState;
|
||||
ConicalView _conicalView; // cached optimized view for fast priority calculations
|
||||
|
||||
|
|
|
@ -14,32 +14,18 @@
|
|||
#include <NodeList.h>
|
||||
#include <OctreeLogging.h>
|
||||
|
||||
|
||||
OctreeHeadlessViewer::OctreeHeadlessViewer() {
|
||||
_viewFrustum.setProjection(glm::perspective(glm::radians(DEFAULT_FIELD_OF_VIEW_DEGREES), DEFAULT_ASPECT_RATIO, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP));
|
||||
}
|
||||
|
||||
void OctreeHeadlessViewer::queryOctree() {
|
||||
char serverType = getMyNodeType();
|
||||
PacketType packetType = getMyQueryMessageType();
|
||||
|
||||
_octreeQuery.setCameraPosition(_viewFrustum.getPosition());
|
||||
_octreeQuery.setCameraOrientation(_viewFrustum.getOrientation());
|
||||
_octreeQuery.setCameraFov(_viewFrustum.getFieldOfView());
|
||||
_octreeQuery.setCameraAspectRatio(_viewFrustum.getAspectRatio());
|
||||
_octreeQuery.setCameraNearClip(_viewFrustum.getNearClip());
|
||||
_octreeQuery.setCameraFarClip(_viewFrustum.getFarClip());
|
||||
_octreeQuery.setCameraEyeOffsetPosition(glm::vec3());
|
||||
_octreeQuery.setCameraCenterRadius(_viewFrustum.getCenterRadius());
|
||||
_octreeQuery.setOctreeSizeScale(_voxelSizeScale);
|
||||
_octreeQuery.setBoundaryLevelAdjust(_boundaryLevelAdjust);
|
||||
if (_hasViewFrustum) {
|
||||
_octreeQuery.setMainViewFrustum(_viewFrustum);
|
||||
}
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
auto node = nodeList->soloNodeOfType(serverType);
|
||||
if (node && node->getActiveSocket()) {
|
||||
_octreeQuery.setMaxQueryPacketsPerSecond(getMaxPacketsPerSecond());
|
||||
|
||||
auto queryPacket = NLPacket::create(packetType);
|
||||
|
||||
// encode the query data
|
||||
|
|
|
@ -20,9 +20,6 @@
|
|||
class OctreeHeadlessViewer : public OctreeProcessor {
|
||||
Q_OBJECT
|
||||
public:
|
||||
OctreeHeadlessViewer();
|
||||
virtual ~OctreeHeadlessViewer() {};
|
||||
|
||||
OctreeQuery& getOctreeQuery() { return _octreeQuery; }
|
||||
|
||||
static int parseOctreeStats(QSharedPointer<ReceivedMessage> message, SharedNodePointer sourceNode);
|
||||
|
@ -32,34 +29,32 @@ public slots:
|
|||
void queryOctree();
|
||||
|
||||
// setters for camera attributes
|
||||
void setPosition(const glm::vec3& position) { _viewFrustum.setPosition(position); }
|
||||
void setOrientation(const glm::quat& orientation) { _viewFrustum.setOrientation(orientation); }
|
||||
void setCenterRadius(float radius) { _viewFrustum.setCenterRadius(radius); }
|
||||
void setKeyholeRadius(float radius) { _viewFrustum.setCenterRadius(radius); } // TODO: remove this legacy support
|
||||
void setPosition(const glm::vec3& position) { _hasViewFrustum = true; _viewFrustum.setPosition(position); }
|
||||
void setOrientation(const glm::quat& orientation) { _hasViewFrustum = true; _viewFrustum.setOrientation(orientation); }
|
||||
void setCenterRadius(float radius) { _hasViewFrustum = true; _viewFrustum.setCenterRadius(radius); }
|
||||
void setKeyholeRadius(float radius) { _hasViewFrustum = true; _viewFrustum.setCenterRadius(radius); } // TODO: remove this legacy support
|
||||
|
||||
// setters for LOD and PPS
|
||||
void setVoxelSizeScale(float sizeScale) { _voxelSizeScale = sizeScale; }
|
||||
void setBoundaryLevelAdjust(int boundaryLevelAdjust) { _boundaryLevelAdjust = boundaryLevelAdjust; }
|
||||
void setMaxPacketsPerSecond(int maxPacketsPerSecond) { _maxPacketsPerSecond = maxPacketsPerSecond; }
|
||||
void setVoxelSizeScale(float sizeScale) { _octreeQuery.setOctreeSizeScale(sizeScale) ; }
|
||||
void setBoundaryLevelAdjust(int boundaryLevelAdjust) { _octreeQuery.setBoundaryLevelAdjust(boundaryLevelAdjust); }
|
||||
void setMaxPacketsPerSecond(int maxPacketsPerSecond) { _octreeQuery.setMaxQueryPacketsPerSecond(maxPacketsPerSecond); }
|
||||
|
||||
// getters for camera attributes
|
||||
const glm::vec3& getPosition() const { return _viewFrustum.getPosition(); }
|
||||
const glm::quat& getOrientation() const { return _viewFrustum.getOrientation(); }
|
||||
|
||||
// getters for LOD and PPS
|
||||
float getVoxelSizeScale() const { return _voxelSizeScale; }
|
||||
int getBoundaryLevelAdjust() const { return _boundaryLevelAdjust; }
|
||||
int getMaxPacketsPerSecond() const { return _maxPacketsPerSecond; }
|
||||
float getVoxelSizeScale() const { return _octreeQuery.getOctreeSizeScale(); }
|
||||
int getBoundaryLevelAdjust() const { return _octreeQuery.getBoundaryLevelAdjust(); }
|
||||
int getMaxPacketsPerSecond() const { return _octreeQuery.getMaxQueryPacketsPerSecond(); }
|
||||
|
||||
unsigned getOctreeElementsCount() const { return _tree->getOctreeElementsCount(); }
|
||||
|
||||
private:
|
||||
OctreeQuery _octreeQuery;
|
||||
|
||||
bool _hasViewFrustum { false };
|
||||
ViewFrustum _viewFrustum;
|
||||
float _voxelSizeScale { DEFAULT_OCTREE_SIZE_SCALE };
|
||||
int _boundaryLevelAdjust { 0 };
|
||||
int _maxPacketsPerSecond { DEFAULT_MAX_OCTREE_PPS };
|
||||
};
|
||||
|
||||
#endif // hifi_OctreeHeadlessViewer_h
|
||||
|
|
|
@ -330,8 +330,9 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
|
|||
} else {
|
||||
// we aren't forcing a full scene, check if something else suggests we should
|
||||
isFullScene = nodeData->haveJSONParametersChanged() ||
|
||||
(nodeData->getUsesFrustum()
|
||||
&& ((!viewFrustumChanged && nodeData->getViewFrustumJustStoppedChanging()) || nodeData->hasLodChanged()));
|
||||
(nodeData->hasMainViewFrustum() &&
|
||||
(nodeData->getViewFrustumJustStoppedChanging() ||
|
||||
nodeData->hasLodChanged()));
|
||||
}
|
||||
|
||||
if (nodeData->isPacketWaiting()) {
|
||||
|
@ -445,7 +446,6 @@ void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, Octre
|
|||
params.trackSend = [this](const QUuid& dataID, quint64 dataEdited) {
|
||||
_myServer->trackSend(dataID, dataEdited, _nodeUuid);
|
||||
};
|
||||
nodeData->copyCurrentViewFrustum(params.viewFrustum);
|
||||
|
||||
bool somethingToSend = true; // assume we have something
|
||||
bool hadSomething = hasSomethingToSend(nodeData);
|
||||
|
|
|
@ -294,7 +294,6 @@ void EntityScriptServer::run() {
|
|||
queryJSONParameters[EntityJSONQueryProperties::FLAGS_PROPERTY] = queryFlags;
|
||||
|
||||
// setup the JSON parameters so that OctreeQuery does not use a frustum and uses our JSON filter
|
||||
_entityViewer.getOctreeQuery().setUsesFrustum(false);
|
||||
_entityViewer.getOctreeQuery().setJSONParameters(queryJSONParameters);
|
||||
|
||||
entityScriptingInterface->setEntityTree(_entityViewer.getTree());
|
||||
|
|
|
@ -5218,6 +5218,78 @@ void Application::updateDialogs(float deltaTime) const {
|
|||
}
|
||||
}
|
||||
|
||||
void Application::updateSecondaryCameraViewFrustum() {
|
||||
// TODO: Fix this by modeling the way the secondary camera works on how the main camera works
|
||||
// ie. Use a camera object stored in the game logic and informs the Engine on where the secondary
|
||||
// camera should be.
|
||||
|
||||
// Code based on SecondaryCameraJob
|
||||
auto renderConfig = _renderEngine->getConfiguration();
|
||||
assert(renderConfig);
|
||||
auto camera = dynamic_cast<SecondaryCameraJobConfig*>(renderConfig->getConfig("SecondaryCamera"));
|
||||
assert(camera);
|
||||
|
||||
if (!camera->isEnabled()) {
|
||||
_hasSecondaryViewFrustum = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (camera->mirrorProjection && !camera->attachedEntityId.isNull()) {
|
||||
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||
auto entityProperties = entityScriptingInterface->getEntityProperties(camera->attachedEntityId);
|
||||
glm::vec3 mirrorPropertiesPosition = entityProperties.getPosition();
|
||||
glm::quat mirrorPropertiesRotation = entityProperties.getRotation();
|
||||
glm::vec3 mirrorPropertiesDimensions = entityProperties.getDimensions();
|
||||
glm::vec3 halfMirrorPropertiesDimensions = 0.5f * mirrorPropertiesDimensions;
|
||||
|
||||
// setup mirror from world as inverse of world from mirror transformation using inverted x and z for mirrored image
|
||||
// TODO: we are assuming here that UP is world y-axis
|
||||
glm::mat4 worldFromMirrorRotation = glm::mat4_cast(mirrorPropertiesRotation) * glm::scale(vec3(-1.0f, 1.0f, -1.0f));
|
||||
glm::mat4 worldFromMirrorTranslation = glm::translate(mirrorPropertiesPosition);
|
||||
glm::mat4 worldFromMirror = worldFromMirrorTranslation * worldFromMirrorRotation;
|
||||
glm::mat4 mirrorFromWorld = glm::inverse(worldFromMirror);
|
||||
|
||||
// get mirror camera position by reflecting main camera position's z coordinate in mirror space
|
||||
glm::vec3 mainCameraPositionWorld = getCamera().getPosition();
|
||||
glm::vec3 mainCameraPositionMirror = vec3(mirrorFromWorld * vec4(mainCameraPositionWorld, 1.0f));
|
||||
glm::vec3 mirrorCameraPositionMirror = vec3(mainCameraPositionMirror.x, mainCameraPositionMirror.y,
|
||||
-mainCameraPositionMirror.z);
|
||||
glm::vec3 mirrorCameraPositionWorld = vec3(worldFromMirror * vec4(mirrorCameraPositionMirror, 1.0f));
|
||||
|
||||
// set frustum position to be mirrored camera and set orientation to mirror's adjusted rotation
|
||||
glm::quat mirrorCameraOrientation = glm::quat_cast(worldFromMirrorRotation);
|
||||
_secondaryViewFrustum.setPosition(mirrorCameraPositionWorld);
|
||||
_secondaryViewFrustum.setOrientation(mirrorCameraOrientation);
|
||||
|
||||
// build frustum using mirror space translation of mirrored camera
|
||||
float nearClip = mirrorCameraPositionMirror.z + mirrorPropertiesDimensions.z * 2.0f;
|
||||
glm::vec3 upperRight = halfMirrorPropertiesDimensions - mirrorCameraPositionMirror;
|
||||
glm::vec3 bottomLeft = -halfMirrorPropertiesDimensions - mirrorCameraPositionMirror;
|
||||
glm::mat4 frustum = glm::frustum(bottomLeft.x, upperRight.x, bottomLeft.y, upperRight.y, nearClip, camera->farClipPlaneDistance);
|
||||
_secondaryViewFrustum.setProjection(frustum);
|
||||
} else {
|
||||
if (!camera->attachedEntityId.isNull()) {
|
||||
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||
auto entityProperties = entityScriptingInterface->getEntityProperties(camera->attachedEntityId);
|
||||
_secondaryViewFrustum.setPosition(entityProperties.getPosition());
|
||||
_secondaryViewFrustum.setOrientation(entityProperties.getRotation());
|
||||
} else {
|
||||
_secondaryViewFrustum.setPosition(camera->position);
|
||||
_secondaryViewFrustum.setOrientation(camera->orientation);
|
||||
}
|
||||
|
||||
float aspectRatio = (float)camera->textureWidth / (float)camera->textureHeight;
|
||||
_secondaryViewFrustum.setProjection(camera->vFoV,
|
||||
aspectRatio,
|
||||
camera->nearClipPlaneDistance,
|
||||
camera->farClipPlaneDistance);
|
||||
}
|
||||
// Without calculating the bound planes, the secondary camera will use the same culling frustum as the main camera,
|
||||
// which is not what we want here.
|
||||
_secondaryViewFrustum.calculate();
|
||||
_hasSecondaryViewFrustum = true;
|
||||
}
|
||||
|
||||
static bool domainLoadingInProgress = false;
|
||||
|
||||
void Application::update(float deltaTime) {
|
||||
|
@ -5571,6 +5643,11 @@ void Application::update(float deltaTime) {
|
|||
{
|
||||
QMutexLocker viewLocker(&_viewMutex);
|
||||
_myCamera.loadViewFrustum(_viewFrustum);
|
||||
|
||||
// TODO: Fix this by modeling the way the secondary camera works on how the main camera works
|
||||
// ie. Use a camera object stored in the game logic and informs the Engine on where the secondary
|
||||
// camera should be.
|
||||
updateSecondaryCameraViewFrustum();
|
||||
}
|
||||
|
||||
quint64 now = usecTimestampNow();
|
||||
|
@ -5584,6 +5661,8 @@ void Application::update(float deltaTime) {
|
|||
const quint64 TOO_LONG_SINCE_LAST_QUERY = 3 * USECS_PER_SECOND;
|
||||
bool queryIsDue = sinceLastQuery > TOO_LONG_SINCE_LAST_QUERY;
|
||||
bool viewIsDifferentEnough = !_lastQueriedViewFrustum.isVerySimilar(_viewFrustum);
|
||||
viewIsDifferentEnough |= _hasSecondaryViewFrustum && !_lastQueriedSecondaryViewFrustum.isVerySimilar(_secondaryViewFrustum);
|
||||
|
||||
// if it's been a while since our last query or the view has significantly changed then send a query, otherwise suppress it
|
||||
if (queryIsDue || viewIsDifferentEnough) {
|
||||
_lastQueriedTime = now;
|
||||
|
@ -5592,6 +5671,7 @@ void Application::update(float deltaTime) {
|
|||
}
|
||||
sendAvatarViewFrustum();
|
||||
_lastQueriedViewFrustum = _viewFrustum;
|
||||
_lastQueriedSecondaryViewFrustum = _secondaryViewFrustum;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5830,14 +5910,15 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType) {
|
|||
|
||||
ViewFrustum viewFrustum;
|
||||
copyViewFrustum(viewFrustum);
|
||||
_octreeQuery.setCameraPosition(viewFrustum.getPosition());
|
||||
_octreeQuery.setCameraOrientation(viewFrustum.getOrientation());
|
||||
_octreeQuery.setCameraFov(viewFrustum.getFieldOfView());
|
||||
_octreeQuery.setCameraAspectRatio(viewFrustum.getAspectRatio());
|
||||
_octreeQuery.setCameraNearClip(viewFrustum.getNearClip());
|
||||
_octreeQuery.setCameraFarClip(viewFrustum.getFarClip());
|
||||
_octreeQuery.setCameraEyeOffsetPosition(glm::vec3());
|
||||
_octreeQuery.setCameraCenterRadius(viewFrustum.getCenterRadius());
|
||||
_octreeQuery.setMainViewFrustum(viewFrustum);
|
||||
|
||||
if (hasSecondaryViewFrustum()) {
|
||||
copySecondaryViewFrustum(viewFrustum);
|
||||
_octreeQuery.setSecondaryViewFrustum(viewFrustum);
|
||||
} else {
|
||||
_octreeQuery.clearSecondaryViewFrustum();
|
||||
}
|
||||
|
||||
auto lodManager = DependencyManager::get<LODManager>();
|
||||
_octreeQuery.setOctreeSizeScale(lodManager->getOctreeSizeScale());
|
||||
_octreeQuery.setBoundaryLevelAdjust(lodManager->getBoundaryLevelAdjust());
|
||||
|
@ -5921,6 +6002,11 @@ void Application::copyDisplayViewFrustum(ViewFrustum& viewOut) const {
|
|||
viewOut = _displayViewFrustum;
|
||||
}
|
||||
|
||||
void Application::copySecondaryViewFrustum(ViewFrustum& viewOut) const {
|
||||
QMutexLocker viewLocker(&_viewMutex);
|
||||
viewOut = _secondaryViewFrustum;
|
||||
}
|
||||
|
||||
void Application::resetSensors(bool andReload) {
|
||||
DependencyManager::get<DdeFaceTracker>()->reset();
|
||||
DependencyManager::get<EyeTracker>()->reset();
|
||||
|
|
|
@ -149,6 +149,8 @@ public:
|
|||
void initializeRenderEngine();
|
||||
void initializeUi();
|
||||
|
||||
void updateSecondaryCameraViewFrustum();
|
||||
|
||||
void updateCamera(RenderArgs& renderArgs, float deltaTime);
|
||||
void paintGL();
|
||||
void resizeGL();
|
||||
|
@ -178,6 +180,9 @@ public:
|
|||
// which might be different from the viewFrustum, i.e. shadowmap
|
||||
// passes, mirror window passes, etc
|
||||
void copyDisplayViewFrustum(ViewFrustum& viewOut) const;
|
||||
void copySecondaryViewFrustum(ViewFrustum& viewOut) const;
|
||||
bool hasSecondaryViewFrustum() const { return _hasSecondaryViewFrustum; }
|
||||
|
||||
const OctreePacketProcessor& getOctreePacketProcessor() const { return _octreeProcessor; }
|
||||
QSharedPointer<EntityTreeRenderer> getEntities() const { return DependencyManager::get<EntityTreeRenderer>(); }
|
||||
QUndoStack* getUndoStack() { return &_undoStack; }
|
||||
|
@ -569,8 +574,11 @@ private:
|
|||
|
||||
mutable QMutex _viewMutex { QMutex::Recursive };
|
||||
ViewFrustum _viewFrustum; // current state of view frustum, perspective, orientation, etc.
|
||||
ViewFrustum _lastQueriedViewFrustum; /// last view frustum used to query octree servers (voxels)
|
||||
ViewFrustum _lastQueriedViewFrustum; // last view frustum used to query octree servers
|
||||
ViewFrustum _displayViewFrustum;
|
||||
ViewFrustum _secondaryViewFrustum;
|
||||
ViewFrustum _lastQueriedSecondaryViewFrustum; // last secondary view frustum used to query octree servers
|
||||
bool _hasSecondaryViewFrustum;
|
||||
quint64 _lastQueriedTime;
|
||||
|
||||
OctreeQuery _octreeQuery { true }; // NodeData derived class for querying octee cells from octree servers
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
|
||||
#include <OctreeUtils.h>
|
||||
|
||||
|
||||
DiffTraversal::Waypoint::Waypoint(EntityTreeElementPointer& element) : _nextIndex(0) {
|
||||
assert(element);
|
||||
_weakElement = element;
|
||||
|
@ -37,15 +36,14 @@ void DiffTraversal::Waypoint::getNextVisibleElementFirstTime(DiffTraversal::Visi
|
|||
EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex);
|
||||
++_nextIndex;
|
||||
if (nextElement) {
|
||||
if (!view.usesViewFrustum) {
|
||||
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.viewFrustum.cubeIntersectsKeyhole(nextElement->getAACube())) {
|
||||
} else if (view.intersects(cube)) {
|
||||
// check for LOD truncation
|
||||
float distance = glm::distance(view.viewFrustum.getPosition(), nextElement->getAACube().calcCenter()) + MIN_VISIBLE_DISTANCE;
|
||||
float angularDiameter = nextElement->getAACube().getScale() / distance;
|
||||
if (angularDiameter > MIN_ELEMENT_ANGULAR_DIAMETER * view.lodScaleFactor) {
|
||||
if (view.isBigEnough(cube, MIN_ELEMENT_ANGULAR_DIAMETER)) {
|
||||
next.element = nextElement;
|
||||
return;
|
||||
}
|
||||
|
@ -76,17 +74,16 @@ void DiffTraversal::Waypoint::getNextVisibleElementRepeat(
|
|||
EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex);
|
||||
++_nextIndex;
|
||||
if (nextElement && nextElement->getLastChanged() > lastTime) {
|
||||
if (!view.usesViewFrustum) {
|
||||
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
|
||||
float distance = glm::distance(view.viewFrustum.getPosition(), nextElement->getAACube().calcCenter()) + MIN_VISIBLE_DISTANCE;
|
||||
float angularDiameter = nextElement->getAACube().getScale() / distance;
|
||||
if (angularDiameter > MIN_ELEMENT_ANGULAR_DIAMETER * view.lodScaleFactor) {
|
||||
ViewFrustum::intersection intersection = view.viewFrustum.calculateCubeKeyholeIntersection(nextElement->getAACube());
|
||||
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;
|
||||
|
@ -118,14 +115,13 @@ void DiffTraversal::Waypoint::getNextVisibleElementDifferential(DiffTraversal::V
|
|||
EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex);
|
||||
++_nextIndex;
|
||||
if (nextElement) {
|
||||
AACube cube = nextElement->getAACube();
|
||||
// check for LOD truncation
|
||||
float distance = glm::distance(view.viewFrustum.getPosition(), cube.calcCenter()) + MIN_VISIBLE_DISTANCE;
|
||||
float angularDiameter = cube.getScale() / distance;
|
||||
if (angularDiameter > MIN_ELEMENT_ANGULAR_DIAMETER * view.lodScaleFactor) {
|
||||
if (view.viewFrustum.calculateCubeKeyholeIntersection(cube) != ViewFrustum::OUTSIDE) {
|
||||
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 = ViewFrustum::OUTSIDE;
|
||||
next.intersection = intersection;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -137,13 +133,83 @@ void DiffTraversal::Waypoint::getNextVisibleElementDifferential(DiffTraversal::V
|
|||
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 {
|
||||
return !viewFrustums.empty();
|
||||
}
|
||||
|
||||
bool DiffTraversal::View::isVerySimilar(const View& view) const {
|
||||
auto size = view.viewFrustums.size();
|
||||
|
||||
if (view.lodScaleFactor != lodScaleFactor ||
|
||||
viewFrustums.size() != size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
if (!viewFrustums[i].isVerySimilar(view.viewFrustums[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
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) {
|
||||
DiffTraversal::Type DiffTraversal::prepareNewTraversal(const DiffTraversal::View& view, EntityTreeElementPointer root) {
|
||||
assert(root);
|
||||
// there are three types of traversal:
|
||||
//
|
||||
|
@ -159,29 +225,25 @@ DiffTraversal::Type DiffTraversal::prepareNewTraversal(const ViewFrustum& viewFr
|
|||
//
|
||||
// external code should update the _scanElementCallback after calling prepareNewTraversal
|
||||
//
|
||||
_currentView.usesViewFrustum = usesViewFrustum;
|
||||
float lodScaleFactor = powf(2.0f, lodLevelOffset);
|
||||
|
||||
Type type;
|
||||
// If usesViewFrustum changes, treat it as a First traversal
|
||||
if (_completedView.startTime == 0 || _currentView.usesViewFrustum != _completedView.usesViewFrustum) {
|
||||
if (_completedView.startTime == 0 || _currentView.usesViewFrustums() != _completedView.usesViewFrustums()) {
|
||||
type = Type::First;
|
||||
_currentView.viewFrustum = viewFrustum;
|
||||
_currentView.lodScaleFactor = lodScaleFactor;
|
||||
_currentView.viewFrustums = view.viewFrustums;
|
||||
_currentView.lodScaleFactor = view.lodScaleFactor;
|
||||
_getNextVisibleElementCallback = [this](DiffTraversal::VisibleElement& next) {
|
||||
_path.back().getNextVisibleElementFirstTime(next, _currentView);
|
||||
};
|
||||
} else if (!_currentView.usesViewFrustum ||
|
||||
(_completedView.viewFrustum.isVerySimilar(viewFrustum) &&
|
||||
lodScaleFactor == _completedView.lodScaleFactor)) {
|
||||
} else if (!_currentView.usesViewFrustums() || _completedView.isVerySimilar(view)) {
|
||||
type = Type::Repeat;
|
||||
_getNextVisibleElementCallback = [this](DiffTraversal::VisibleElement& next) {
|
||||
_path.back().getNextVisibleElementRepeat(next, _completedView, _completedView.startTime);
|
||||
};
|
||||
} else {
|
||||
type = Type::Differential;
|
||||
_currentView.viewFrustum = viewFrustum;
|
||||
_currentView.lodScaleFactor = lodScaleFactor;
|
||||
_currentView.viewFrustums = view.viewFrustums;
|
||||
_currentView.lodScaleFactor = view.lodScaleFactor;
|
||||
_getNextVisibleElementCallback = [this](DiffTraversal::VisibleElement& next) {
|
||||
_path.back().getNextVisibleElementDifferential(next, _currentView, _completedView);
|
||||
};
|
||||
|
|
|
@ -30,10 +30,15 @@ public:
|
|||
// View is a struct with a ViewFrustum and LOD parameters
|
||||
class View {
|
||||
public:
|
||||
ViewFrustum viewFrustum;
|
||||
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;
|
||||
|
||||
std::vector<ViewFrustum> viewFrustums;
|
||||
uint64_t startTime { 0 };
|
||||
float lodScaleFactor { 1.0f };
|
||||
bool usesViewFrustum { true };
|
||||
};
|
||||
|
||||
// Waypoint is an bookmark in a "path" of waypoints during a traversal.
|
||||
|
@ -57,15 +62,12 @@ public:
|
|||
|
||||
DiffTraversal();
|
||||
|
||||
Type prepareNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset,
|
||||
bool usesViewFrustum);
|
||||
Type prepareNewTraversal(const DiffTraversal::View& view, EntityTreeElementPointer root);
|
||||
|
||||
const ViewFrustum& getCurrentView() const { return _currentView.viewFrustum; }
|
||||
const ViewFrustum& getCompletedView() const { return _completedView.viewFrustum; }
|
||||
const View& getCurrentView() const { return _currentView; }
|
||||
const View& getCompletedView() const { return _completedView; }
|
||||
|
||||
bool doesCurrentUseViewFrustum() const { return _currentView.usesViewFrustum; }
|
||||
float getCurrentLODScaleFactor() const { return _currentView.lodScaleFactor; }
|
||||
float getCompletedLODScaleFactor() const { return _completedView.lodScaleFactor; }
|
||||
bool doesCurrentUseViewFrustum() const { return _currentView.usesViewFrustums(); }
|
||||
|
||||
uint64_t getStartOfCompletedTraversal() const { return _completedView.startTime; }
|
||||
bool finished() const { return _path.empty(); }
|
||||
|
|
|
@ -27,9 +27,6 @@ using EntityTreePointer = std::shared_ptr<EntityTree>;
|
|||
#include "MovingEntitiesOperator.h"
|
||||
|
||||
class EntityEditFilters;
|
||||
class Model;
|
||||
using ModelPointer = std::shared_ptr<Model>;
|
||||
using ModelWeakPointer = std::weak_ptr<Model>;
|
||||
|
||||
class EntitySimulation;
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
|||
case PacketType::EntityPhysics:
|
||||
return static_cast<PacketVersion>(EntityVersion::MaterialData);
|
||||
case PacketType::EntityQuery:
|
||||
return static_cast<PacketVersion>(EntityQueryPacketVersion::RemovedJurisdictions);
|
||||
return static_cast<PacketVersion>(EntityQueryPacketVersion::MultiFrustumQuery);
|
||||
case PacketType::AvatarIdentity:
|
||||
case PacketType::AvatarData:
|
||||
case PacketType::BulkAvatarData:
|
||||
|
|
|
@ -244,7 +244,8 @@ enum class EntityQueryPacketVersion: PacketVersion {
|
|||
JSONFilter = 18,
|
||||
JSONFilterWithFamilyTree = 19,
|
||||
ConnectionIdentifier = 20,
|
||||
RemovedJurisdictions = 21
|
||||
RemovedJurisdictions = 21,
|
||||
MultiFrustumQuery = 22
|
||||
};
|
||||
|
||||
enum class AssetServerPacketVersion: PacketVersion {
|
||||
|
|
|
@ -59,7 +59,6 @@ const int LOW_RES_MOVING_ADJUST = 1;
|
|||
|
||||
class EncodeBitstreamParams {
|
||||
public:
|
||||
ViewFrustum viewFrustum;
|
||||
bool includeExistsBits;
|
||||
NodeData* nodeData;
|
||||
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "OctreeQuery.h"
|
||||
|
||||
#include <random>
|
||||
|
||||
#include <QtCore/QJsonDocument>
|
||||
|
@ -16,23 +18,11 @@
|
|||
#include <GLMHelpers.h>
|
||||
#include <udt/PacketHeaders.h>
|
||||
|
||||
#include "OctreeConstants.h"
|
||||
#include "OctreeQuery.h"
|
||||
|
||||
const float DEFAULT_FOV = 45.0f; // degrees
|
||||
const float DEFAULT_ASPECT_RATIO = 1.0f;
|
||||
const float DEFAULT_NEAR_CLIP = 0.1f;
|
||||
const float DEFAULT_FAR_CLIP = 3.0f;
|
||||
|
||||
OctreeQuery::OctreeQuery(bool randomizeConnectionID) :
|
||||
_cameraFov(DEFAULT_FOV),
|
||||
_cameraAspectRatio(DEFAULT_ASPECT_RATIO),
|
||||
_cameraNearClip(DEFAULT_NEAR_CLIP),
|
||||
_cameraFarClip(DEFAULT_FAR_CLIP),
|
||||
_cameraCenterRadius(DEFAULT_FAR_CLIP)
|
||||
{
|
||||
_maxQueryPPS = DEFAULT_MAX_OCTREE_PPS;
|
||||
using QueryFlags = uint8_t;
|
||||
const QueryFlags QUERY_HAS_MAIN_FRUSTUM = 1U << 0;
|
||||
const QueryFlags QUERY_HAS_SECONDARY_FRUSTUM = 1U << 1;
|
||||
|
||||
OctreeQuery::OctreeQuery(bool randomizeConnectionID) {
|
||||
if (randomizeConnectionID) {
|
||||
// randomize our initial octree query connection ID using random_device
|
||||
// the connection ID is 16 bits so we take a generated 32 bit value from random device and chop off the top
|
||||
|
@ -47,26 +37,28 @@ int OctreeQuery::getBroadcastData(unsigned char* destinationBuffer) {
|
|||
// pack the connection ID so the server can detect when we start a new connection
|
||||
memcpy(destinationBuffer, &_connectionID, sizeof(_connectionID));
|
||||
destinationBuffer += sizeof(_connectionID);
|
||||
|
||||
// back a boolean (cut to 1 byte) to designate if this query uses the sent view frustum
|
||||
memcpy(destinationBuffer, &_usesFrustum, sizeof(_usesFrustum));
|
||||
destinationBuffer += sizeof(_usesFrustum);
|
||||
|
||||
if (_usesFrustum) {
|
||||
// TODO: DRY this up to a shared method
|
||||
// that can pack any type given the number of bytes
|
||||
// and return the number of bytes to push the pointer
|
||||
|
||||
// camera details
|
||||
memcpy(destinationBuffer, &_cameraPosition, sizeof(_cameraPosition));
|
||||
destinationBuffer += sizeof(_cameraPosition);
|
||||
destinationBuffer += packOrientationQuatToBytes(destinationBuffer, _cameraOrientation);
|
||||
destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, _cameraFov);
|
||||
destinationBuffer += packFloatRatioToTwoByte(destinationBuffer, _cameraAspectRatio);
|
||||
destinationBuffer += packClipValueToTwoByte(destinationBuffer, _cameraNearClip);
|
||||
destinationBuffer += packClipValueToTwoByte(destinationBuffer, _cameraFarClip);
|
||||
memcpy(destinationBuffer, &_cameraEyeOffsetPosition, sizeof(_cameraEyeOffsetPosition));
|
||||
destinationBuffer += sizeof(_cameraEyeOffsetPosition);
|
||||
|
||||
// flags for wether the frustums are present
|
||||
QueryFlags frustumFlags = 0;
|
||||
if (_hasMainFrustum) {
|
||||
frustumFlags |= QUERY_HAS_MAIN_FRUSTUM;
|
||||
}
|
||||
if (_hasSecondaryFrustum) {
|
||||
frustumFlags |= QUERY_HAS_SECONDARY_FRUSTUM;
|
||||
}
|
||||
memcpy(destinationBuffer, &frustumFlags, sizeof(frustumFlags));
|
||||
destinationBuffer += sizeof(frustumFlags);
|
||||
|
||||
if (_hasMainFrustum) {
|
||||
auto byteArray = _mainViewFrustum.toByteArray();
|
||||
memcpy(destinationBuffer, byteArray.constData(), byteArray.size());
|
||||
destinationBuffer += byteArray.size();
|
||||
}
|
||||
|
||||
if (_hasSecondaryFrustum) {
|
||||
auto byteArray = _secondaryViewFrustum.toByteArray();
|
||||
memcpy(destinationBuffer, byteArray.constData(), byteArray.size());
|
||||
destinationBuffer += byteArray.size();
|
||||
}
|
||||
|
||||
// desired Max Octree PPS
|
||||
|
@ -80,9 +72,6 @@ int OctreeQuery::getBroadcastData(unsigned char* destinationBuffer) {
|
|||
// desired boundaryLevelAdjust
|
||||
memcpy(destinationBuffer, &_boundaryLevelAdjust, sizeof(_boundaryLevelAdjust));
|
||||
destinationBuffer += sizeof(_boundaryLevelAdjust);
|
||||
|
||||
memcpy(destinationBuffer, &_cameraCenterRadius, sizeof(_cameraCenterRadius));
|
||||
destinationBuffer += sizeof(_cameraCenterRadius);
|
||||
|
||||
// create a QByteArray that holds the binary representation of the JSON parameters
|
||||
QByteArray binaryParametersDocument;
|
||||
|
@ -110,6 +99,7 @@ int OctreeQuery::getBroadcastData(unsigned char* destinationBuffer) {
|
|||
int OctreeQuery::parseData(ReceivedMessage& message) {
|
||||
|
||||
const unsigned char* startPosition = reinterpret_cast<const unsigned char*>(message.getRawMessage());
|
||||
const unsigned char* endPosition = startPosition + message.getSize();
|
||||
const unsigned char* sourceBuffer = startPosition;
|
||||
|
||||
// unpack the connection ID
|
||||
|
@ -133,20 +123,23 @@ int OctreeQuery::parseData(ReceivedMessage& message) {
|
|||
}
|
||||
|
||||
// check if this query uses a view frustum
|
||||
memcpy(&_usesFrustum, sourceBuffer, sizeof(_usesFrustum));
|
||||
sourceBuffer += sizeof(_usesFrustum);
|
||||
|
||||
if (_usesFrustum) {
|
||||
// unpack camera details
|
||||
memcpy(&_cameraPosition, sourceBuffer, sizeof(_cameraPosition));
|
||||
sourceBuffer += sizeof(_cameraPosition);
|
||||
sourceBuffer += unpackOrientationQuatFromBytes(sourceBuffer, _cameraOrientation);
|
||||
sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &_cameraFov);
|
||||
sourceBuffer += unpackFloatRatioFromTwoByte(sourceBuffer,_cameraAspectRatio);
|
||||
sourceBuffer += unpackClipValueFromTwoByte(sourceBuffer,_cameraNearClip);
|
||||
sourceBuffer += unpackClipValueFromTwoByte(sourceBuffer,_cameraFarClip);
|
||||
memcpy(&_cameraEyeOffsetPosition, sourceBuffer, sizeof(_cameraEyeOffsetPosition));
|
||||
sourceBuffer += sizeof(_cameraEyeOffsetPosition);
|
||||
QueryFlags frustumFlags { 0 };
|
||||
memcpy(&frustumFlags, sourceBuffer, sizeof(frustumFlags));
|
||||
sourceBuffer += sizeof(frustumFlags);
|
||||
|
||||
_hasMainFrustum = frustumFlags & QUERY_HAS_MAIN_FRUSTUM;
|
||||
_hasSecondaryFrustum = frustumFlags & QUERY_HAS_SECONDARY_FRUSTUM;
|
||||
|
||||
if (_hasMainFrustum) {
|
||||
auto bytesLeft = endPosition - sourceBuffer;
|
||||
auto byteArray = QByteArray::fromRawData(reinterpret_cast<const char*>(sourceBuffer), bytesLeft);
|
||||
sourceBuffer += _mainViewFrustum.fromByteArray(byteArray);
|
||||
}
|
||||
|
||||
if (_hasSecondaryFrustum) {
|
||||
auto bytesLeft = endPosition - sourceBuffer;
|
||||
auto byteArray = QByteArray::fromRawData(reinterpret_cast<const char*>(sourceBuffer), bytesLeft);
|
||||
sourceBuffer += _secondaryViewFrustum.fromByteArray(byteArray);
|
||||
}
|
||||
|
||||
// desired Max Octree PPS
|
||||
|
@ -161,9 +154,6 @@ int OctreeQuery::parseData(ReceivedMessage& message) {
|
|||
memcpy(&_boundaryLevelAdjust, sourceBuffer, sizeof(_boundaryLevelAdjust));
|
||||
sourceBuffer += sizeof(_boundaryLevelAdjust);
|
||||
|
||||
memcpy(&_cameraCenterRadius, sourceBuffer, sizeof(_cameraCenterRadius));
|
||||
sourceBuffer += sizeof(_cameraCenterRadius);
|
||||
|
||||
// check if we have a packed JSON filter
|
||||
uint16_t binaryParametersBytes;
|
||||
memcpy(&binaryParametersBytes, sourceBuffer, sizeof(binaryParametersBytes));
|
||||
|
@ -184,8 +174,3 @@ int OctreeQuery::parseData(ReceivedMessage& message) {
|
|||
|
||||
return sourceBuffer - startPosition;
|
||||
}
|
||||
|
||||
glm::vec3 OctreeQuery::calculateCameraDirection() const {
|
||||
glm::vec3 direction = glm::vec3(_cameraOrientation * glm::vec4(IDENTITY_FORWARD, 0.0f));
|
||||
return direction;
|
||||
}
|
||||
|
|
|
@ -12,16 +12,14 @@
|
|||
#ifndef hifi_OctreeQuery_h
|
||||
#define hifi_OctreeQuery_h
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QReadWriteLock>
|
||||
|
||||
#include <NodeData.h>
|
||||
|
||||
#include <ViewFrustum.h>
|
||||
|
||||
#include "OctreeConstants.h"
|
||||
|
||||
class OctreeQuery : public NodeData {
|
||||
Q_OBJECT
|
||||
|
@ -30,31 +28,22 @@ public:
|
|||
OctreeQuery(bool randomizeConnectionID = false);
|
||||
virtual ~OctreeQuery() {}
|
||||
|
||||
OctreeQuery(const OctreeQuery&) = delete;
|
||||
OctreeQuery& operator=(const OctreeQuery&) = delete;
|
||||
|
||||
int getBroadcastData(unsigned char* destinationBuffer);
|
||||
int parseData(ReceivedMessage& message) override;
|
||||
|
||||
// getters for camera details
|
||||
const glm::vec3& getCameraPosition() const { return _cameraPosition; }
|
||||
const glm::quat& getCameraOrientation() const { return _cameraOrientation; }
|
||||
float getCameraFov() const { return _cameraFov; }
|
||||
float getCameraAspectRatio() const { return _cameraAspectRatio; }
|
||||
float getCameraNearClip() const { return _cameraNearClip; }
|
||||
float getCameraFarClip() const { return _cameraFarClip; }
|
||||
const glm::vec3& getCameraEyeOffsetPosition() const { return _cameraEyeOffsetPosition; }
|
||||
float getCameraCenterRadius() const { return _cameraCenterRadius; }
|
||||
bool hasMainViewFrustum() const { return _hasMainFrustum; }
|
||||
void setMainViewFrustum(const ViewFrustum& viewFrustum) { _hasMainFrustum = true; _mainViewFrustum = viewFrustum; }
|
||||
void clearMainViewFrustum() { _hasMainFrustum = false; }
|
||||
const ViewFrustum& getMainViewFrustum() const { return _mainViewFrustum; }
|
||||
|
||||
glm::vec3 calculateCameraDirection() const;
|
||||
bool hasSecondaryViewFrustum() const { return _hasSecondaryFrustum; }
|
||||
void setSecondaryViewFrustum(const ViewFrustum& viewFrustum) { _hasSecondaryFrustum = true; _secondaryViewFrustum = viewFrustum; }
|
||||
void clearSecondaryViewFrustum() { _hasSecondaryFrustum = false; }
|
||||
const ViewFrustum& getSecondaryViewFrustum() const { return _secondaryViewFrustum; }
|
||||
|
||||
// setters for camera details
|
||||
void setCameraPosition(const glm::vec3& position) { _cameraPosition = position; }
|
||||
void setCameraOrientation(const glm::quat& orientation) { _cameraOrientation = orientation; }
|
||||
void setCameraFov(float fov) { _cameraFov = fov; }
|
||||
void setCameraAspectRatio(float aspectRatio) { _cameraAspectRatio = aspectRatio; }
|
||||
void setCameraNearClip(float nearClip) { _cameraNearClip = nearClip; }
|
||||
void setCameraFarClip(float farClip) { _cameraFarClip = farClip; }
|
||||
void setCameraEyeOffsetPosition(const glm::vec3& eyeOffsetPosition) { _cameraEyeOffsetPosition = eyeOffsetPosition; }
|
||||
void setCameraCenterRadius(float radius) { _cameraCenterRadius = radius; }
|
||||
|
||||
// getters/setters for JSON filter
|
||||
QJsonObject getJSONParameters() { QReadLocker locker { &_jsonParametersLock }; return _jsonParameters; }
|
||||
void setJSONParameters(const QJsonObject& jsonParameters)
|
||||
|
@ -64,9 +53,6 @@ public:
|
|||
int getMaxQueryPacketsPerSecond() const { return _maxQueryPPS; }
|
||||
float getOctreeSizeScale() const { return _octreeElementSizeScale; }
|
||||
int getBoundaryLevelAdjust() const { return _boundaryLevelAdjust; }
|
||||
|
||||
bool getUsesFrustum() { return _usesFrustum; }
|
||||
void setUsesFrustum(bool usesFrustum) { _usesFrustum = usesFrustum; }
|
||||
|
||||
void incrementConnectionID() { ++_connectionID; }
|
||||
|
||||
|
@ -81,33 +67,22 @@ public slots:
|
|||
void setBoundaryLevelAdjust(int boundaryLevelAdjust) { _boundaryLevelAdjust = boundaryLevelAdjust; }
|
||||
|
||||
protected:
|
||||
// camera details for the avatar
|
||||
glm::vec3 _cameraPosition { glm::vec3(0.0f) };
|
||||
glm::quat _cameraOrientation { glm::quat() };
|
||||
float _cameraFov;
|
||||
float _cameraAspectRatio;
|
||||
float _cameraNearClip;
|
||||
float _cameraFarClip;
|
||||
float _cameraCenterRadius;
|
||||
glm::vec3 _cameraEyeOffsetPosition { glm::vec3(0.0f) };
|
||||
bool _hasMainFrustum { false };
|
||||
ViewFrustum _mainViewFrustum;
|
||||
bool _hasSecondaryFrustum { false };
|
||||
ViewFrustum _secondaryViewFrustum;
|
||||
|
||||
// octree server sending items
|
||||
int _maxQueryPPS = DEFAULT_MAX_OCTREE_PPS;
|
||||
float _octreeElementSizeScale = DEFAULT_OCTREE_SIZE_SCALE; /// used for LOD calculations
|
||||
int _boundaryLevelAdjust = 0; /// used for LOD calculations
|
||||
|
||||
uint8_t _usesFrustum = true;
|
||||
|
||||
uint16_t _connectionID; // query connection ID, randomized to start, increments with each new connection to server
|
||||
|
||||
QJsonObject _jsonParameters;
|
||||
QReadWriteLock _jsonParametersLock;
|
||||
|
||||
bool _hasReceivedFirstQuery { false };
|
||||
|
||||
private:
|
||||
// privatize the copy constructor and assignment operator so they cannot be called
|
||||
OctreeQuery(const OctreeQuery&);
|
||||
OctreeQuery& operator= (const OctreeQuery&);
|
||||
};
|
||||
|
||||
#endif // hifi_OctreeQuery_h
|
||||
|
|
|
@ -139,9 +139,14 @@ void OctreeQueryNode::writeToPacket(const unsigned char* buffer, unsigned int by
|
|||
}
|
||||
}
|
||||
|
||||
void OctreeQueryNode::copyCurrentViewFrustum(ViewFrustum& viewOut) const {
|
||||
void OctreeQueryNode::copyCurrentMainViewFrustum(ViewFrustum& viewOut) const {
|
||||
QMutexLocker viewLocker(&_viewMutex);
|
||||
viewOut = _currentViewFrustum;
|
||||
viewOut = _currentMainViewFrustum;
|
||||
}
|
||||
|
||||
void OctreeQueryNode::copyCurrentSecondaryViewFrustum(ViewFrustum& viewOut) const {
|
||||
QMutexLocker viewLocker(&_viewMutex);
|
||||
viewOut = _currentSecondaryViewFrustum;
|
||||
}
|
||||
|
||||
bool OctreeQueryNode::updateCurrentViewFrustum() {
|
||||
|
@ -150,70 +155,50 @@ bool OctreeQueryNode::updateCurrentViewFrustum() {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!_usesFrustum) {
|
||||
if (!_hasMainFrustum && !_hasSecondaryFrustum) {
|
||||
// this client does not use a view frustum so the view frustum for this query has not changed
|
||||
return false;
|
||||
} else {
|
||||
bool currentViewFrustumChanged = false;
|
||||
|
||||
ViewFrustum newestViewFrustum;
|
||||
// get position and orientation details from the camera
|
||||
newestViewFrustum.setPosition(getCameraPosition());
|
||||
newestViewFrustum.setOrientation(getCameraOrientation());
|
||||
|
||||
newestViewFrustum.setCenterRadius(getCameraCenterRadius());
|
||||
|
||||
// Also make sure it's got the correct lens details from the camera
|
||||
float originalFOV = getCameraFov();
|
||||
float wideFOV = originalFOV + VIEW_FRUSTUM_FOV_OVERSEND;
|
||||
|
||||
if (0.0f != getCameraAspectRatio() &&
|
||||
0.0f != getCameraNearClip() &&
|
||||
0.0f != getCameraFarClip() &&
|
||||
getCameraNearClip() != getCameraFarClip()) {
|
||||
newestViewFrustum.setProjection(glm::perspective(
|
||||
glm::radians(wideFOV), // hack
|
||||
getCameraAspectRatio(),
|
||||
getCameraNearClip(),
|
||||
getCameraFarClip()));
|
||||
newestViewFrustum.calculate();
|
||||
}
|
||||
|
||||
|
||||
{ // if there has been a change, then recalculate
|
||||
QMutexLocker viewLocker(&_viewMutex);
|
||||
if (!newestViewFrustum.isVerySimilar(_currentViewFrustum)) {
|
||||
_currentViewFrustum = newestViewFrustum;
|
||||
currentViewFrustumChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Also check for LOD changes from the client
|
||||
if (_lodInitialized) {
|
||||
if (_lastClientBoundaryLevelAdjust != getBoundaryLevelAdjust()) {
|
||||
_lastClientBoundaryLevelAdjust = getBoundaryLevelAdjust();
|
||||
_lodChanged = true;
|
||||
}
|
||||
if (_lastClientOctreeSizeScale != getOctreeSizeScale()) {
|
||||
_lastClientOctreeSizeScale = getOctreeSizeScale();
|
||||
_lodChanged = true;
|
||||
}
|
||||
} else {
|
||||
_lodInitialized = true;
|
||||
_lastClientOctreeSizeScale = getOctreeSizeScale();
|
||||
_lastClientBoundaryLevelAdjust = getBoundaryLevelAdjust();
|
||||
_lodChanged = false;
|
||||
}
|
||||
|
||||
// When we first detect that the view stopped changing, we record this.
|
||||
// but we don't change it back to false until we've completely sent this
|
||||
// scene.
|
||||
if (_viewFrustumChanging && !currentViewFrustumChanged) {
|
||||
_viewFrustumJustStoppedChanging = true;
|
||||
}
|
||||
_viewFrustumChanging = currentViewFrustumChanged;
|
||||
return currentViewFrustumChanged;
|
||||
}
|
||||
|
||||
bool currentViewFrustumChanged = false;
|
||||
|
||||
{ // if there has been a change, then recalculate
|
||||
QMutexLocker viewLocker(&_viewMutex);
|
||||
if (_hasMainFrustum && !_mainViewFrustum.isVerySimilar(_currentMainViewFrustum)) {
|
||||
_currentMainViewFrustum = _mainViewFrustum;
|
||||
currentViewFrustumChanged = true;
|
||||
}
|
||||
if (_hasSecondaryFrustum && !_secondaryViewFrustum.isVerySimilar(_currentSecondaryViewFrustum)) {
|
||||
_currentSecondaryViewFrustum = _secondaryViewFrustum;
|
||||
currentViewFrustumChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Also check for LOD changes from the client
|
||||
if (_lodInitialized) {
|
||||
if (_lastClientBoundaryLevelAdjust != getBoundaryLevelAdjust()) {
|
||||
_lastClientBoundaryLevelAdjust = getBoundaryLevelAdjust();
|
||||
_lodChanged = true;
|
||||
}
|
||||
if (_lastClientOctreeSizeScale != getOctreeSizeScale()) {
|
||||
_lastClientOctreeSizeScale = getOctreeSizeScale();
|
||||
_lodChanged = true;
|
||||
}
|
||||
} else {
|
||||
_lodInitialized = true;
|
||||
_lastClientOctreeSizeScale = getOctreeSizeScale();
|
||||
_lastClientBoundaryLevelAdjust = getBoundaryLevelAdjust();
|
||||
_lodChanged = false;
|
||||
}
|
||||
|
||||
// When we first detect that the view stopped changing, we record this.
|
||||
// but we don't change it back to false until we've completely sent this
|
||||
// scene.
|
||||
if (_viewFrustumChanging && !currentViewFrustumChanged) {
|
||||
_viewFrustumJustStoppedChanging = true;
|
||||
}
|
||||
_viewFrustumChanging = currentViewFrustumChanged;
|
||||
return currentViewFrustumChanged;
|
||||
}
|
||||
|
||||
void OctreeQueryNode::setViewSent(bool viewSent) {
|
||||
|
|
|
@ -49,7 +49,8 @@ public:
|
|||
|
||||
OctreeElementExtraEncodeData extraEncodeData;
|
||||
|
||||
void copyCurrentViewFrustum(ViewFrustum& viewOut) const;
|
||||
void copyCurrentMainViewFrustum(ViewFrustum& viewOut) const;
|
||||
void copyCurrentSecondaryViewFrustum(ViewFrustum& viewOut) const;
|
||||
|
||||
// These are not classic setters because they are calculating and maintaining state
|
||||
// which is set asynchronously through the network receive
|
||||
|
@ -87,9 +88,6 @@ public:
|
|||
void setShouldForceFullScene(bool shouldForceFullScene) { _shouldForceFullScene = shouldForceFullScene; }
|
||||
|
||||
private:
|
||||
OctreeQueryNode(const OctreeQueryNode &);
|
||||
OctreeQueryNode& operator= (const OctreeQueryNode&);
|
||||
|
||||
bool _viewSent { false };
|
||||
std::unique_ptr<NLPacket> _octreePacket;
|
||||
bool _octreePacketWaiting;
|
||||
|
@ -99,7 +97,8 @@ private:
|
|||
quint64 _firstSuppressedPacket { usecTimestampNow() };
|
||||
|
||||
mutable QMutex _viewMutex { QMutex::Recursive };
|
||||
ViewFrustum _currentViewFrustum;
|
||||
ViewFrustum _currentMainViewFrustum;
|
||||
ViewFrustum _currentSecondaryViewFrustum;
|
||||
bool _viewFrustumChanging { false };
|
||||
bool _viewFrustumJustStoppedChanging { true };
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include <glm/glm.hpp>
|
||||
|
||||
#include <AABox.h>
|
||||
#include <AACube.h>
|
||||
|
||||
float calculateRenderAccuracy(const glm::vec3& position,
|
||||
const AABox& bounds,
|
||||
|
@ -73,4 +74,10 @@ float getOrthographicAccuracySize(float octreeSizeScale, int boundaryLevelAdjust
|
|||
// Smallest visible element is 1cm
|
||||
const float smallestSize = 0.01f;
|
||||
return (smallestSize * MAX_VISIBILITY_DISTANCE_FOR_UNIT_ELEMENT) / boundaryDistanceForRenderLevel(boundaryLevelAdjust, octreeSizeScale);
|
||||
}
|
||||
}
|
||||
|
||||
bool isAngularSizeBigEnough(glm::vec3 position, const AACube& cube, float lodScaleFactor, float minDiameter) {
|
||||
float distance = glm::distance(cube.calcCenter(), position) + MIN_VISIBLE_DISTANCE;
|
||||
float angularDiameter = cube.getScale() / distance;
|
||||
return angularDiameter > minDiameter * lodScaleFactor;
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include "OctreeConstants.h"
|
||||
|
||||
class AABox;
|
||||
class AACube;
|
||||
class QJsonDocument;
|
||||
|
||||
/// renderAccuracy represents a floating point "visibility" of an object based on it's view from the camera. At a simple
|
||||
|
@ -36,4 +37,6 @@ const float SQRT_THREE = 1.73205080f;
|
|||
const float MIN_ENTITY_ANGULAR_DIAMETER = MIN_ELEMENT_ANGULAR_DIAMETER * SQRT_THREE;
|
||||
const float MIN_VISIBLE_DISTANCE = 0.0001f; // helps avoid divide-by-zero check
|
||||
|
||||
bool isAngularSizeBigEnough(glm::vec3 position, const AACube& cube, float lodScaleFactor, float minDiameter);
|
||||
|
||||
#endif // hifi_OctreeUtils_h
|
||||
|
|
|
@ -75,6 +75,10 @@ void ViewFrustum::setProjection(const glm::mat4& projection) {
|
|||
_width = _corners[TOP_RIGHT_NEAR].x - _corners[TOP_LEFT_NEAR].x;
|
||||
}
|
||||
|
||||
void ViewFrustum::setProjection(float cameraFov, float cameraAspectRatio, float cameraNearClip, float cameraFarClip) {
|
||||
setProjection(glm::perspective(glm::radians(cameraFov), cameraAspectRatio, cameraNearClip, cameraFarClip));
|
||||
}
|
||||
|
||||
// ViewFrustum::calculate()
|
||||
//
|
||||
// Description: this will calculate the view frustum bounds for a given position and direction
|
||||
|
@ -134,7 +138,7 @@ const char* ViewFrustum::debugPlaneName (int plane) const {
|
|||
return "Unknown";
|
||||
}
|
||||
|
||||
void ViewFrustum::fromByteArray(const QByteArray& input) {
|
||||
int ViewFrustum::fromByteArray(const QByteArray& input) {
|
||||
|
||||
// From the wire!
|
||||
glm::vec3 cameraPosition;
|
||||
|
@ -168,14 +172,12 @@ void ViewFrustum::fromByteArray(const QByteArray& input) {
|
|||
0.0f != cameraNearClip &&
|
||||
0.0f != cameraFarClip &&
|
||||
cameraNearClip != cameraFarClip) {
|
||||
setProjection(glm::perspective(
|
||||
glm::radians(cameraFov),
|
||||
cameraAspectRatio,
|
||||
cameraNearClip,
|
||||
cameraFarClip));
|
||||
|
||||
setProjection(cameraFov, cameraAspectRatio, cameraNearClip, cameraFarClip);
|
||||
calculate();
|
||||
}
|
||||
|
||||
return sourceBuffer - startPosition;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -48,6 +48,7 @@ public:
|
|||
|
||||
// setters for lens attributes
|
||||
void setProjection(const glm::mat4 & projection);
|
||||
void setProjection(float cameraFov, float cameraAspectRatio, float cameraNearClip, float cameraFarClip);
|
||||
void setFocalLength(float focalLength) { _focalLength = focalLength; }
|
||||
bool isPerspective() const;
|
||||
|
||||
|
@ -147,7 +148,7 @@ public:
|
|||
void invalidate(); // causes all reasonable intersection tests to fail
|
||||
|
||||
QByteArray toByteArray();
|
||||
void fromByteArray(const QByteArray& input);
|
||||
int fromByteArray(const QByteArray& input);
|
||||
|
||||
private:
|
||||
glm::mat4 _view;
|
||||
|
|
Loading…
Reference in a new issue