Merge branch 'master' of https://github.com/highfidelity/hifi into one

This commit is contained in:
Sam Gateau 2018-05-03 20:15:07 -07:00
commit 61c855a97b
41 changed files with 827 additions and 576 deletions

View file

@ -548,16 +548,21 @@ void Agent::setIsAvatar(bool isAvatar) {
if (_isAvatar && !_avatarIdentityTimer) { if (_isAvatar && !_avatarIdentityTimer) {
// set up the avatar timers // set up the avatar timers
_avatarIdentityTimer = new QTimer(this); _avatarIdentityTimer = new QTimer(this);
_avatarViewTimer = new QTimer(this);
// connect our slot // connect our slot
connect(_avatarIdentityTimer, &QTimer::timeout, this, &Agent::sendAvatarIdentityPacket); connect(_avatarIdentityTimer, &QTimer::timeout, this, &Agent::sendAvatarIdentityPacket);
connect(_avatarViewTimer, &QTimer::timeout, this, &Agent::sendAvatarViewFrustum);
static const int AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS = 1000;
static const int AVATAR_VIEW_PACKET_SEND_INTERVAL_MSECS = 1000;
// start the timers // start the timers
_avatarIdentityTimer->start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS); // FIXME - we shouldn't really need to constantly send identity packets _avatarIdentityTimer->start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS); // FIXME - we shouldn't really need to constantly send identity packets
_avatarViewTimer->start(AVATAR_VIEW_PACKET_SEND_INTERVAL_MSECS);
// tell the avatarAudioTimer to start ticking // tell the avatarAudioTimer to start ticking
QMetaObject::invokeMethod(&_avatarAudioTimer, "start"); QMetaObject::invokeMethod(&_avatarAudioTimer, "start");
} }
if (!_isAvatar) { if (!_isAvatar) {
@ -567,6 +572,10 @@ void Agent::setIsAvatar(bool isAvatar) {
delete _avatarIdentityTimer; delete _avatarIdentityTimer;
_avatarIdentityTimer = nullptr; _avatarIdentityTimer = nullptr;
_avatarViewTimer->stop();
delete _avatarViewTimer;
_avatarViewTimer = nullptr;
// The avatar mixer never times out a connection (e.g., based on identity or data packets) // The avatar mixer never times out a connection (e.g., based on identity or data packets)
// but rather keeps avatars in its list as long as "connected". As a result, clients timeout // but rather keeps avatars in its list as long as "connected". As a result, clients timeout
// when we stop sending identity, but then get woken up again by the mixer itself, which sends // when we stop sending identity, but then get woken up again by the mixer itself, which sends
@ -585,6 +594,7 @@ void Agent::setIsAvatar(bool isAvatar) {
nodeList->sendPacket(std::move(packet), *node); nodeList->sendPacket(std::move(packet), *node);
}); });
} }
QMetaObject::invokeMethod(&_avatarAudioTimer, "stop"); QMetaObject::invokeMethod(&_avatarAudioTimer, "stop");
} }
} }
@ -597,6 +607,25 @@ void Agent::sendAvatarIdentityPacket() {
} }
} }
void Agent::sendAvatarViewFrustum() {
auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>();
ViewFrustum view;
view.setPosition(scriptedAvatar->getWorldPosition());
view.setOrientation(scriptedAvatar->getHeadOrientation());
view.calculate();
uint8_t numFrustums = 1;
auto viewFrustumByteArray = view.toByteArray();
auto avatarPacket = NLPacket::create(PacketType::ViewFrustum, viewFrustumByteArray.size() + sizeof(numFrustums));
avatarPacket->writePrimitive(numFrustums);
avatarPacket->write(viewFrustumByteArray);
DependencyManager::get<NodeList>()->broadcastToNodes(std::move(avatarPacket),
{ NodeType::AvatarMixer });
}
void Agent::processAgentAvatar() { void Agent::processAgentAvatar() {
if (!_scriptEngine->isFinished() && _isAvatar) { if (!_scriptEngine->isFinished() && _isAvatar) {
auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>(); auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>();

View file

@ -97,6 +97,7 @@ private:
void setAvatarSound(SharedSoundPointer avatarSound) { _avatarSound = avatarSound; } void setAvatarSound(SharedSoundPointer avatarSound) { _avatarSound = avatarSound; }
void sendAvatarIdentityPacket(); void sendAvatarIdentityPacket();
void sendAvatarViewFrustum();
QString _scriptContents; QString _scriptContents;
QTimer* _scriptRequestTimeout { nullptr }; QTimer* _scriptRequestTimeout { nullptr };
@ -106,6 +107,7 @@ private:
int _numAvatarSoundSentBytes = 0; int _numAvatarSoundSentBytes = 0;
bool _isAvatar = false; bool _isAvatar = false;
QTimer* _avatarIdentityTimer = nullptr; QTimer* _avatarIdentityTimer = nullptr;
QTimer* _avatarViewTimer = nullptr;
QHash<QUuid, quint16> _outgoingScriptAudioSequenceNumbers; QHash<QUuid, quint16> _outgoingScriptAudioSequenceNumbers;
AudioGate _audioGate; AudioGate _audioGate;

View file

@ -521,11 +521,9 @@ void AvatarMixer::handleViewFrustumPacket(QSharedPointer<ReceivedMessage> messag
auto start = usecTimestampNow(); auto start = usecTimestampNow();
getOrCreateClientData(senderNode); getOrCreateClientData(senderNode);
if (senderNode->getLinkedData()) { AvatarMixerClientData* nodeData = dynamic_cast<AvatarMixerClientData*>(senderNode->getLinkedData());
AvatarMixerClientData* nodeData = dynamic_cast<AvatarMixerClientData*>(senderNode->getLinkedData()); if (nodeData) {
if (nodeData != nullptr) { nodeData->readViewFrustumPacket(message->getMessage());
nodeData->readViewFrustumPacket(message->getMessage());
}
} }
auto end = usecTimestampNow(); auto end = usecTimestampNow();

View file

@ -19,8 +19,6 @@
AvatarMixerClientData::AvatarMixerClientData(const QUuid& nodeID) : AvatarMixerClientData::AvatarMixerClientData(const QUuid& nodeID) :
NodeData(nodeID) NodeData(nodeID)
{ {
_currentViewFrustum.invalidate();
// in case somebody calls getSessionUUID on the AvatarData instance, make sure it has the right ID // in case somebody calls getSessionUUID on the AvatarData instance, make sure it has the right ID
_avatar->setID(nodeID); _avatar->setID(nodeID);
} }
@ -128,12 +126,27 @@ void AvatarMixerClientData::removeFromRadiusIgnoringSet(SharedNodePointer self,
} }
} }
void AvatarMixerClientData::readViewFrustumPacket(const QByteArray& message) { void AvatarMixerClientData::readViewFrustumPacket(QByteArray message) {
_currentViewFrustum.fromByteArray(message); _currentViewFrustums.clear();
uint8_t numFrustums = 0;
memcpy(&numFrustums, message.constData(), sizeof(numFrustums));
message.remove(0, sizeof(numFrustums));
for (uint8_t i = 0; i < numFrustums; ++i) {
ViewFrustum frustum;
auto bytesRead = frustum.fromByteArray(message);
message.remove(0, bytesRead);
_currentViewFrustums.push_back(frustum);
}
} }
bool AvatarMixerClientData::otherAvatarInView(const AABox& otherAvatarBox) { bool AvatarMixerClientData::otherAvatarInView(const AABox& otherAvatarBox) {
return _currentViewFrustum.boxIntersectsKeyhole(otherAvatarBox); return std::any_of(std::begin(_currentViewFrustums), std::end(_currentViewFrustums),
[&](const ViewFrustum& viewFrustum) {
return viewFrustum.boxIntersectsKeyhole(otherAvatarBox);
});
} }
void AvatarMixerClientData::loadJSONStats(QJsonObject& jsonObject) const { void AvatarMixerClientData::loadJSONStats(QJsonObject& jsonObject) const {

View file

@ -98,7 +98,7 @@ public:
void removeFromRadiusIgnoringSet(SharedNodePointer self, const QUuid& other); void removeFromRadiusIgnoringSet(SharedNodePointer self, const QUuid& other);
void ignoreOther(SharedNodePointer self, SharedNodePointer other); void ignoreOther(SharedNodePointer self, SharedNodePointer other);
void readViewFrustumPacket(const QByteArray& message); void readViewFrustumPacket(QByteArray message);
bool otherAvatarInView(const AABox& otherAvatarBox); bool otherAvatarInView(const AABox& otherAvatarBox);
@ -110,7 +110,7 @@ public:
bool getRequestsDomainListData() { return _requestsDomainListData; } bool getRequestsDomainListData() { return _requestsDomainListData; }
void setRequestsDomainListData(bool requesting) { _requestsDomainListData = requesting; } void setRequestsDomainListData(bool requesting) { _requestsDomainListData = requesting; }
ViewFrustum getViewFrustum() const { return _currentViewFrustum; } const ViewFrustums& getViewFrustums() const { return _currentViewFrustums; }
uint64_t getLastOtherAvatarEncodeTime(QUuid otherAvatar) const; uint64_t getLastOtherAvatarEncodeTime(QUuid otherAvatar) const;
void setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, uint64_t time); void setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, uint64_t time);
@ -150,7 +150,7 @@ private:
SimpleMovingAverage _avgOtherAvatarDataRate; SimpleMovingAverage _avgOtherAvatarDataRate;
std::unordered_set<QUuid> _radiusIgnoredOthers; std::unordered_set<QUuid> _radiusIgnoredOthers;
ViewFrustum _currentViewFrustum; ViewFrustums _currentViewFrustums;
int _recentOtherAvatarsInView { 0 }; int _recentOtherAvatarsInView { 0 };
int _recentOtherAvatarsOutOfView { 0 }; int _recentOtherAvatarsOutOfView { 0 };

View file

@ -222,8 +222,8 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
}; };
// prepare to sort // prepare to sort
ViewFrustum cameraView = nodeData->getViewFrustum(); const auto& cameraViews = nodeData->getViewFrustums();
PrioritySortUtil::PriorityQueue<SortableAvatar> sortedAvatars(cameraView, PrioritySortUtil::PriorityQueue<SortableAvatar> sortedAvatars(cameraViews,
AvatarData::_avatarSortCoefficientSize, AvatarData::_avatarSortCoefficientSize,
AvatarData::_avatarSortCoefficientCenter, AvatarData::_avatarSortCoefficientCenter,
AvatarData::_avatarSortCoefficientAge); AvatarData::_avatarSortCoefficientAge);

View file

@ -15,7 +15,7 @@ const float PrioritizedEntity::DO_NOT_SEND = -1.0e-6f;
const float PrioritizedEntity::FORCE_REMOVE = -1.0e-5f; const float PrioritizedEntity::FORCE_REMOVE = -1.0e-5f;
const float PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY = 1.0f; 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. // 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. // Why? Because approximate intersection tests are much faster to compute for a cone than for a frustum.
_position = viewFrustum.getPosition(); _position = viewFrustum.getPosition();
@ -31,7 +31,7 @@ void ConicalView::set(const ViewFrustum& viewFrustum) {
_radius = viewFrustum.getCenterRadius(); _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 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 d = glm::length(p); // distance to center of bounding sphere
float r = 0.5f * cube.getScale(); // radius 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; return PrioritizedEntity::DO_NOT_SEND;
} }
void ConicalView::set(const DiffTraversal::View& view) {
auto size = view.viewFrustums.size();
_conicalViewFrustums.resize(size);
for (size_t i = 0; i < size; ++i) {
_conicalViewFrustums[i].set(view.viewFrustums[i]);
}
}
float ConicalView::computePriority(const AACube& cube) const {
if (_conicalViewFrustums.empty()) {
return PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY;
}
float priority = PrioritizedEntity::DO_NOT_SEND;
for (const auto& view : _conicalViewFrustums) {
priority = std::max(priority, view.computePriority(cube));
}
return priority;
}

View file

@ -13,18 +13,20 @@
#define hifi_EntityPriorityQueue_h #define hifi_EntityPriorityQueue_h
#include <queue> #include <queue>
#include <unordered_set>
#include <AACube.h> #include <AACube.h>
#include <DiffTraversal.h>
#include <EntityTreeElement.h> #include <EntityTreeElement.h>
const float SQRT_TWO_OVER_TWO = 0.7071067811865f; const float SQRT_TWO_OVER_TWO = 0.7071067811865f;
const float DEFAULT_VIEW_RADIUS = 10.0f; const float DEFAULT_VIEW_RADIUS = 10.0f;
// ConicalView is an approximation of a ViewFrustum for fast calculation of sort priority. // ConicalViewFrustum is an approximation of a ViewFrustum for fast calculation of sort priority.
class ConicalView { class ConicalViewFrustum {
public: public:
ConicalView() {} ConicalViewFrustum() {}
ConicalView(const ViewFrustum& viewFrustum) { set(viewFrustum); } ConicalViewFrustum(const ViewFrustum& viewFrustum) { set(viewFrustum); }
void set(const ViewFrustum& viewFrustum); void set(const ViewFrustum& viewFrustum);
float computePriority(const AACube& cube) const; float computePriority(const AACube& cube) const;
private: private:
@ -35,6 +37,16 @@ private:
float _radius { DEFAULT_VIEW_RADIUS }; 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. // PrioritizedEntity is a placeholder in a sorted queue.
class PrioritizedEntity { class PrioritizedEntity {
public: public:
@ -61,6 +73,50 @@ private:
bool _forceRemove; 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 #endif // hifi_EntityPriorityQueue_h

View file

@ -103,48 +103,58 @@ void EntityTreeSendThread::preDistributionProcessing() {
void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData,
bool viewFrustumChanged, bool isFullScene) { bool viewFrustumChanged, bool isFullScene) {
if (viewFrustumChanged || _traversal.finished()) { if (viewFrustumChanged || _traversal.finished()) {
ViewFrustum viewFrustum;
nodeData->copyCurrentViewFrustum(viewFrustum);
EntityTreeElementPointer root = std::dynamic_pointer_cast<EntityTreeElement>(_myServer->getOctree()->getRoot()); 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); 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 // 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 // and also use the opportunity to cull anything no longer in view
if (viewFrustumChanged && !_sendQueue.empty()) { if (viewFrustumChanged && !_sendQueue.empty()) {
EntityPriorityQueue prevSendQueue; EntityPriorityQueue prevSendQueue;
_sendQueue.swap(prevSendQueue); std::swap(_sendQueue, prevSendQueue);
_entitiesInQueue.clear(); assert(_sendQueue.empty());
// Re-add elements from previous traversal if they still need to be sent // 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()) { while (!prevSendQueue.empty()) {
EntityItemPointer entity = prevSendQueue.top().getEntity(); EntityItemPointer entity = prevSendQueue.top().getEntity();
bool forceRemove = prevSendQueue.top().shouldForceRemove(); bool forceRemove = prevSendQueue.top().shouldForceRemove();
prevSendQueue.pop(); prevSendQueue.pop();
if (entity) { if (entity) {
if (!forceRemove) { float priority = PrioritizedEntity::DO_NOT_SEND;
if (forceRemove) {
priority = PrioritizedEntity::FORCE_REMOVE;
} else {
bool success = false; bool success = false;
AACube cube = entity->getQueryAACube(success); AACube cube = entity->getQueryAACube(success);
if (success) { if (success) {
if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { const auto& view = _traversal.getCurrentView();
float priority = _conicalView.computePriority(cube); if (view.intersects(cube) && view.isBigEnough(cube)) {
if (priority != PrioritizedEntity::DO_NOT_SEND) { priority = _conicalView.computePriority(cube);
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());
}
}
} }
} else { } else {
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); priority = PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY;
_entitiesInQueue.insert(entity.get());
} }
} 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; return hasNewChild || hasNewDescendants;
} }
void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTreeElementPointer root, int32_t lodLevelOffset, void EntityTreeSendThread::startNewTraversal(const DiffTraversal::View& view, EntityTreeElementPointer root) {
bool usesViewFrustum) {
DiffTraversal::Type type = _traversal.prepareNewTraversal(view, root, lodLevelOffset, usesViewFrustum); DiffTraversal::Type type = _traversal.prepareNewTraversal(view, root);
// there are three types of traversal: // there are three types of traversal:
// //
// (1) FirstTime = at login --> find everything in view // (1) FirstTime = at login --> find everything in view
@ -236,161 +245,114 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree
case DiffTraversal::First: case DiffTraversal::First:
// When we get to a First traversal, clear the _knownState // When we get to a First traversal, clear the _knownState
_knownState.clear(); _knownState.clear();
if (usesViewFrustum) { _traversal.setScanCallback([this](DiffTraversal::VisibleElement& next) {
float lodScaleFactor = _traversal.getCurrentLODScaleFactor(); next.element->forEachEntity([&](EntityItemPointer entity) {
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) {
// Bail early if we've already checked this entity this frame // Bail early if we've already checked this entity this frame
if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) { if (_sendQueue.contains(entity.get())) {
return; 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()); auto knownTimestamp = _knownState.find(entity.get());
if (knownTimestamp == _knownState.end()) { if (knownTimestamp == _knownState.end()) {
bool success = false; bool success = false;
AACube cube = entity->getQueryAACube(success); AACube cube = entity->getQueryAACube(success);
if (success) { if (success) {
if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { const auto& view = _traversal.getCurrentView();
// See the DiffTraversal::First case for an explanation of the "entity is too small" check // 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; if ((next.intersection == ViewFrustum::INSIDE || view.intersects(cube)) &&
float angularDiameter = cube.getScale() / distance; view.isBigEnough(cube)) {
if (angularDiameter > MIN_ENTITY_ANGULAR_DIAMETER * lodScaleFactor) { priority = _conicalView.computePriority(cube);
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());
}
}
}
} }
} else { } else {
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); priority = PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY;
_entitiesInQueue.insert(entity.get());
} }
} else if (entity->getLastEdited() > knownTimestamp->second } else if (entity->getLastEdited() > knownTimestamp->second ||
|| entity->getLastChangedOnServer() > knownTimestamp->second) { entity->getLastChangedOnServer() > knownTimestamp->second) {
// it is known and it changed --> put it on the queue with any priority // it is known and it changed --> put it on the queue with any priority
// TODO: sort these correctly // TODO: sort these correctly
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); priority = PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY;
_entitiesInQueue.insert(entity.get()); }
if (priority != PrioritizedEntity::DO_NOT_SEND) {
_sendQueue.emplace(entity, priority);
} }
}); });
}); });
@ -479,11 +441,10 @@ bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstream
} }
} }
_sendQueue.pop(); _sendQueue.pop();
_entitiesInQueue.erase(entity.get());
} }
nodeData->stats.encodeStopped(); nodeData->stats.encodeStopped();
if (_sendQueue.empty()) { if (_sendQueue.empty()) {
assert(_entitiesInQueue.empty()); assert(_sendQueue.empty());
params.stopReason = EncodeBitstreamParams::FINISHED; params.stopReason = EncodeBitstreamParams::FINISHED;
_extraEncodeData->entities.clear(); _extraEncodeData->entities.clear();
} }
@ -501,18 +462,16 @@ bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstream
void EntityTreeSendThread::editingEntityPointer(const EntityItemPointer& entity) { void EntityTreeSendThread::editingEntityPointer(const EntityItemPointer& entity) {
if (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; bool success = false;
AACube cube = entity->getQueryAACube(success); AACube cube = entity->getQueryAACube(success);
if (success) { if (success) {
// We can force a removal from _knownState if the current view is used and entity is out of view // 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)) { if (_traversal.doesCurrentUseViewFrustum() && !_traversal.getCurrentView().intersects(cube)) {
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::FORCE_REMOVE, true)); _sendQueue.emplace(entity, PrioritizedEntity::FORCE_REMOVE, true);
_entitiesInQueue.insert(entity.get());
} }
} else { } else {
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY, true)); _sendQueue.emplace(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY, true);
_entitiesInQueue.insert(entity.get());
} }
} }
} }

View file

@ -41,8 +41,7 @@ private:
bool addAncestorsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData); bool addAncestorsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData);
bool addDescendantsToExtraFlaggedEntities(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, void startNewTraversal(const DiffTraversal::View& viewFrustum, EntityTreeElementPointer root);
bool usesViewFrustum);
bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) override; bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) override;
void preDistributionProcessing() override; void preDistributionProcessing() override;
@ -51,7 +50,6 @@ private:
DiffTraversal _traversal; DiffTraversal _traversal;
EntityPriorityQueue _sendQueue; EntityPriorityQueue _sendQueue;
std::unordered_set<EntityItem*> _entitiesInQueue;
std::unordered_map<EntityItem*, uint64_t> _knownState; std::unordered_map<EntityItem*, uint64_t> _knownState;
ConicalView _conicalView; // cached optimized view for fast priority calculations ConicalView _conicalView; // cached optimized view for fast priority calculations

View file

@ -14,32 +14,18 @@
#include <NodeList.h> #include <NodeList.h>
#include <OctreeLogging.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() { void OctreeHeadlessViewer::queryOctree() {
char serverType = getMyNodeType(); char serverType = getMyNodeType();
PacketType packetType = getMyQueryMessageType(); PacketType packetType = getMyQueryMessageType();
_octreeQuery.setCameraPosition(_viewFrustum.getPosition()); if (_hasViewFrustum) {
_octreeQuery.setCameraOrientation(_viewFrustum.getOrientation()); _octreeQuery.setMainViewFrustum(_viewFrustum);
_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);
auto nodeList = DependencyManager::get<NodeList>(); auto nodeList = DependencyManager::get<NodeList>();
auto node = nodeList->soloNodeOfType(serverType); auto node = nodeList->soloNodeOfType(serverType);
if (node && node->getActiveSocket()) { if (node && node->getActiveSocket()) {
_octreeQuery.setMaxQueryPacketsPerSecond(getMaxPacketsPerSecond());
auto queryPacket = NLPacket::create(packetType); auto queryPacket = NLPacket::create(packetType);
// encode the query data // encode the query data

View file

@ -20,9 +20,6 @@
class OctreeHeadlessViewer : public OctreeProcessor { class OctreeHeadlessViewer : public OctreeProcessor {
Q_OBJECT Q_OBJECT
public: public:
OctreeHeadlessViewer();
virtual ~OctreeHeadlessViewer() {};
OctreeQuery& getOctreeQuery() { return _octreeQuery; } OctreeQuery& getOctreeQuery() { return _octreeQuery; }
static int parseOctreeStats(QSharedPointer<ReceivedMessage> message, SharedNodePointer sourceNode); static int parseOctreeStats(QSharedPointer<ReceivedMessage> message, SharedNodePointer sourceNode);
@ -32,34 +29,32 @@ public slots:
void queryOctree(); void queryOctree();
// setters for camera attributes // setters for camera attributes
void setPosition(const glm::vec3& position) { _viewFrustum.setPosition(position); } void setPosition(const glm::vec3& position) { _hasViewFrustum = true; _viewFrustum.setPosition(position); }
void setOrientation(const glm::quat& orientation) { _viewFrustum.setOrientation(orientation); } void setOrientation(const glm::quat& orientation) { _hasViewFrustum = true; _viewFrustum.setOrientation(orientation); }
void setCenterRadius(float radius) { _viewFrustum.setCenterRadius(radius); } void setCenterRadius(float radius) { _hasViewFrustum = true; _viewFrustum.setCenterRadius(radius); }
void setKeyholeRadius(float radius) { _viewFrustum.setCenterRadius(radius); } // TODO: remove this legacy support void setKeyholeRadius(float radius) { _hasViewFrustum = true; _viewFrustum.setCenterRadius(radius); } // TODO: remove this legacy support
// setters for LOD and PPS // setters for LOD and PPS
void setVoxelSizeScale(float sizeScale) { _voxelSizeScale = sizeScale; } void setVoxelSizeScale(float sizeScale) { _octreeQuery.setOctreeSizeScale(sizeScale) ; }
void setBoundaryLevelAdjust(int boundaryLevelAdjust) { _boundaryLevelAdjust = boundaryLevelAdjust; } void setBoundaryLevelAdjust(int boundaryLevelAdjust) { _octreeQuery.setBoundaryLevelAdjust(boundaryLevelAdjust); }
void setMaxPacketsPerSecond(int maxPacketsPerSecond) { _maxPacketsPerSecond = maxPacketsPerSecond; } void setMaxPacketsPerSecond(int maxPacketsPerSecond) { _octreeQuery.setMaxQueryPacketsPerSecond(maxPacketsPerSecond); }
// getters for camera attributes // getters for camera attributes
const glm::vec3& getPosition() const { return _viewFrustum.getPosition(); } const glm::vec3& getPosition() const { return _viewFrustum.getPosition(); }
const glm::quat& getOrientation() const { return _viewFrustum.getOrientation(); } const glm::quat& getOrientation() const { return _viewFrustum.getOrientation(); }
// getters for LOD and PPS // getters for LOD and PPS
float getVoxelSizeScale() const { return _voxelSizeScale; } float getVoxelSizeScale() const { return _octreeQuery.getOctreeSizeScale(); }
int getBoundaryLevelAdjust() const { return _boundaryLevelAdjust; } int getBoundaryLevelAdjust() const { return _octreeQuery.getBoundaryLevelAdjust(); }
int getMaxPacketsPerSecond() const { return _maxPacketsPerSecond; } int getMaxPacketsPerSecond() const { return _octreeQuery.getMaxQueryPacketsPerSecond(); }
unsigned getOctreeElementsCount() const { return _tree->getOctreeElementsCount(); } unsigned getOctreeElementsCount() const { return _tree->getOctreeElementsCount(); }
private: private:
OctreeQuery _octreeQuery; OctreeQuery _octreeQuery;
bool _hasViewFrustum { false };
ViewFrustum _viewFrustum; ViewFrustum _viewFrustum;
float _voxelSizeScale { DEFAULT_OCTREE_SIZE_SCALE };
int _boundaryLevelAdjust { 0 };
int _maxPacketsPerSecond { DEFAULT_MAX_OCTREE_PPS };
}; };
#endif // hifi_OctreeHeadlessViewer_h #endif // hifi_OctreeHeadlessViewer_h

View file

@ -330,8 +330,9 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
} else { } else {
// we aren't forcing a full scene, check if something else suggests we should // we aren't forcing a full scene, check if something else suggests we should
isFullScene = nodeData->haveJSONParametersChanged() || isFullScene = nodeData->haveJSONParametersChanged() ||
(nodeData->getUsesFrustum() (nodeData->hasMainViewFrustum() &&
&& ((!viewFrustumChanged && nodeData->getViewFrustumJustStoppedChanging()) || nodeData->hasLodChanged())); (nodeData->getViewFrustumJustStoppedChanging() ||
nodeData->hasLodChanged()));
} }
if (nodeData->isPacketWaiting()) { if (nodeData->isPacketWaiting()) {
@ -445,7 +446,6 @@ void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, Octre
params.trackSend = [this](const QUuid& dataID, quint64 dataEdited) { params.trackSend = [this](const QUuid& dataID, quint64 dataEdited) {
_myServer->trackSend(dataID, dataEdited, _nodeUuid); _myServer->trackSend(dataID, dataEdited, _nodeUuid);
}; };
nodeData->copyCurrentViewFrustum(params.viewFrustum);
bool somethingToSend = true; // assume we have something bool somethingToSend = true; // assume we have something
bool hadSomething = hasSomethingToSend(nodeData); bool hadSomething = hasSomethingToSend(nodeData);

View file

@ -294,7 +294,6 @@ void EntityScriptServer::run() {
queryJSONParameters[EntityJSONQueryProperties::FLAGS_PROPERTY] = queryFlags; queryJSONParameters[EntityJSONQueryProperties::FLAGS_PROPERTY] = queryFlags;
// setup the JSON parameters so that OctreeQuery does not use a frustum and uses our JSON filter // 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); _entityViewer.getOctreeQuery().setJSONParameters(queryJSONParameters);
entityScriptingInterface->setEntityTree(_entityViewer.getTree()); entityScriptingInterface->setEntityTree(_entityViewer.getTree());

View file

@ -26,7 +26,7 @@
<span class='step-description'> <span class='step-description'>
<a target='_blank' href='https://docs.highfidelity.com/create-and-explore/start-working-in-your-sandbox/place-names'>Place names</a> are similar to web addresses. Users who want to visit your domain can <a target='_blank' href='https://docs.highfidelity.com/create-and-explore/start-working-in-your-sandbox/place-names'>Place names</a> are similar to web addresses. Users who want to visit your domain can
enter its Place Name in High Fidelity's Interface. You can choose a Place Name for your domain.</br> enter its Place Name in High Fidelity's Interface. You can choose a Place Name for your domain.</br>
People can also use your <b>domain's IP address (shown below)</b> to visit your High Fidelity domain. Your domain may also be reachable by <b>IP address</b>.
</span> </span>
</div> </div>
</div> </div>
@ -35,10 +35,10 @@
<div class="centered-hack-parent"> <div class="centered-hack-parent">
<div id="place-name-group" class="centered-hack"> <div id="place-name-group" class="centered-hack">
<p id="place-name-link"></p> <p id="place-name-link"></p>
<div id="place-name-edit"> <div id="place-name-edit">
<span class='glyphicon glyphicon-pencil'></span> <span class='glyphicon glyphicon-pencil'></span>
<a href="#" id="change-place-name">Choose a custom Place Name instead</a> <a href="#" id="change-place-name">Choose a custom Place Name instead</a>
</div> </div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -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; static bool domainLoadingInProgress = false;
void Application::update(float deltaTime) { void Application::update(float deltaTime) {
@ -5571,6 +5643,11 @@ void Application::update(float deltaTime) {
{ {
QMutexLocker viewLocker(&_viewMutex); QMutexLocker viewLocker(&_viewMutex);
_myCamera.loadViewFrustum(_viewFrustum); _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(); quint64 now = usecTimestampNow();
@ -5584,6 +5661,8 @@ void Application::update(float deltaTime) {
const quint64 TOO_LONG_SINCE_LAST_QUERY = 3 * USECS_PER_SECOND; const quint64 TOO_LONG_SINCE_LAST_QUERY = 3 * USECS_PER_SECOND;
bool queryIsDue = sinceLastQuery > TOO_LONG_SINCE_LAST_QUERY; bool queryIsDue = sinceLastQuery > TOO_LONG_SINCE_LAST_QUERY;
bool viewIsDifferentEnough = !_lastQueriedViewFrustum.isVerySimilar(_viewFrustum); 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 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) { if (queryIsDue || viewIsDifferentEnough) {
_lastQueriedTime = now; _lastQueriedTime = now;
@ -5592,6 +5671,7 @@ void Application::update(float deltaTime) {
} }
sendAvatarViewFrustum(); sendAvatarViewFrustum();
_lastQueriedViewFrustum = _viewFrustum; _lastQueriedViewFrustum = _viewFrustum;
_lastQueriedSecondaryViewFrustum = _secondaryViewFrustum;
} }
} }
@ -5764,8 +5844,16 @@ void Application::update(float deltaTime) {
} }
void Application::sendAvatarViewFrustum() { void Application::sendAvatarViewFrustum() {
uint8_t numFrustums = 1;
QByteArray viewFrustumByteArray = _viewFrustum.toByteArray(); QByteArray viewFrustumByteArray = _viewFrustum.toByteArray();
auto avatarPacket = NLPacket::create(PacketType::ViewFrustum, viewFrustumByteArray.size());
if (_hasSecondaryViewFrustum) {
++numFrustums;
viewFrustumByteArray += _secondaryViewFrustum.toByteArray();
}
auto avatarPacket = NLPacket::create(PacketType::ViewFrustum, viewFrustumByteArray.size() + sizeof(numFrustums));
avatarPacket->writePrimitive(numFrustums);
avatarPacket->write(viewFrustumByteArray); avatarPacket->write(viewFrustumByteArray);
DependencyManager::get<NodeList>()->broadcastToNodes(std::move(avatarPacket), NodeSet() << NodeType::AvatarMixer); DependencyManager::get<NodeList>()->broadcastToNodes(std::move(avatarPacket), NodeSet() << NodeType::AvatarMixer);
@ -5830,14 +5918,15 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType) {
ViewFrustum viewFrustum; ViewFrustum viewFrustum;
copyViewFrustum(viewFrustum); copyViewFrustum(viewFrustum);
_octreeQuery.setCameraPosition(viewFrustum.getPosition()); _octreeQuery.setMainViewFrustum(viewFrustum);
_octreeQuery.setCameraOrientation(viewFrustum.getOrientation());
_octreeQuery.setCameraFov(viewFrustum.getFieldOfView()); if (hasSecondaryViewFrustum()) {
_octreeQuery.setCameraAspectRatio(viewFrustum.getAspectRatio()); copySecondaryViewFrustum(viewFrustum);
_octreeQuery.setCameraNearClip(viewFrustum.getNearClip()); _octreeQuery.setSecondaryViewFrustum(viewFrustum);
_octreeQuery.setCameraFarClip(viewFrustum.getFarClip()); } else {
_octreeQuery.setCameraEyeOffsetPosition(glm::vec3()); _octreeQuery.clearSecondaryViewFrustum();
_octreeQuery.setCameraCenterRadius(viewFrustum.getCenterRadius()); }
auto lodManager = DependencyManager::get<LODManager>(); auto lodManager = DependencyManager::get<LODManager>();
_octreeQuery.setOctreeSizeScale(lodManager->getOctreeSizeScale()); _octreeQuery.setOctreeSizeScale(lodManager->getOctreeSizeScale());
_octreeQuery.setBoundaryLevelAdjust(lodManager->getBoundaryLevelAdjust()); _octreeQuery.setBoundaryLevelAdjust(lodManager->getBoundaryLevelAdjust());
@ -5921,6 +6010,11 @@ void Application::copyDisplayViewFrustum(ViewFrustum& viewOut) const {
viewOut = _displayViewFrustum; viewOut = _displayViewFrustum;
} }
void Application::copySecondaryViewFrustum(ViewFrustum& viewOut) const {
QMutexLocker viewLocker(&_viewMutex);
viewOut = _secondaryViewFrustum;
}
void Application::resetSensors(bool andReload) { void Application::resetSensors(bool andReload) {
DependencyManager::get<DdeFaceTracker>()->reset(); DependencyManager::get<DdeFaceTracker>()->reset();
DependencyManager::get<EyeTracker>()->reset(); DependencyManager::get<EyeTracker>()->reset();

View file

@ -149,6 +149,8 @@ public:
void initializeRenderEngine(); void initializeRenderEngine();
void initializeUi(); void initializeUi();
void updateSecondaryCameraViewFrustum();
void updateCamera(RenderArgs& renderArgs, float deltaTime); void updateCamera(RenderArgs& renderArgs, float deltaTime);
void paintGL(); void paintGL();
void resizeGL(); void resizeGL();
@ -173,11 +175,14 @@ public:
Camera& getCamera() { return _myCamera; } Camera& getCamera() { return _myCamera; }
const Camera& getCamera() const { return _myCamera; } const Camera& getCamera() const { return _myCamera; }
// Represents the current view frustum of the avatar. // Represents the current view frustum of the avatar.
void copyViewFrustum(ViewFrustum& viewOut) const; void copyViewFrustum(ViewFrustum& viewOut) const override;
void copySecondaryViewFrustum(ViewFrustum& viewOut) const override;
bool hasSecondaryViewFrustum() const override { return _hasSecondaryViewFrustum; }
// Represents the view frustum of the current rendering pass, // Represents the view frustum of the current rendering pass,
// which might be different from the viewFrustum, i.e. shadowmap // which might be different from the viewFrustum, i.e. shadowmap
// passes, mirror window passes, etc // passes, mirror window passes, etc
void copyDisplayViewFrustum(ViewFrustum& viewOut) const; void copyDisplayViewFrustum(ViewFrustum& viewOut) const;
const OctreePacketProcessor& getOctreePacketProcessor() const { return _octreeProcessor; } const OctreePacketProcessor& getOctreePacketProcessor() const { return _octreeProcessor; }
QSharedPointer<EntityTreeRenderer> getEntities() const { return DependencyManager::get<EntityTreeRenderer>(); } QSharedPointer<EntityTreeRenderer> getEntities() const { return DependencyManager::get<EntityTreeRenderer>(); }
QUndoStack* getUndoStack() { return &_undoStack; } QUndoStack* getUndoStack() { return &_undoStack; }
@ -569,8 +574,11 @@ private:
mutable QMutex _viewMutex { QMutex::Recursive }; mutable QMutex _viewMutex { QMutex::Recursive };
ViewFrustum _viewFrustum; // current state of view frustum, perspective, orientation, etc. 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 _displayViewFrustum;
ViewFrustum _secondaryViewFrustum;
ViewFrustum _lastQueriedSecondaryViewFrustum; // last secondary view frustum used to query octree servers
bool _hasSecondaryViewFrustum;
quint64 _lastQueriedTime; quint64 _lastQueriedTime;
OctreeQuery _octreeQuery { true }; // NodeData derived class for querying octee cells from octree servers OctreeQuery _octreeQuery { true }; // NodeData derived class for querying octee cells from octree servers

View file

@ -11,6 +11,8 @@
#include "Crashpad.h" #include "Crashpad.h"
#include <assert.h>
#include <QDebug> #include <QDebug>
#if HAS_CRASHPAD #if HAS_CRASHPAD
@ -20,7 +22,7 @@
#include <QStandardPaths> #include <QStandardPaths>
#include <QDir> #include <QDir>
#include <BuildInfo.h> #include <Windows.h>
#include <client/crashpad_client.h> #include <client/crashpad_client.h>
#include <client/crash_report_database.h> #include <client/crash_report_database.h>
@ -28,28 +30,27 @@
#include <client/annotation_list.h> #include <client/annotation_list.h>
#include <client/crashpad_info.h> #include <client/crashpad_info.h>
#include <BuildInfo.h>
using namespace crashpad; using namespace crashpad;
static const std::string BACKTRACE_URL { CMAKE_BACKTRACE_URL }; static const std::string BACKTRACE_URL { CMAKE_BACKTRACE_URL };
static const std::string BACKTRACE_TOKEN { CMAKE_BACKTRACE_TOKEN }; static const std::string BACKTRACE_TOKEN { CMAKE_BACKTRACE_TOKEN };
static std::wstring gIPCPipe;
extern QString qAppFileName(); extern QString qAppFileName();
CrashpadClient* client { nullptr };
std::mutex annotationMutex; std::mutex annotationMutex;
crashpad::SimpleStringDictionary* crashpadAnnotations { nullptr }; crashpad::SimpleStringDictionary* crashpadAnnotations { nullptr };
#include <Windows.h>
LONG WINAPI vectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) { LONG WINAPI vectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) {
if (!client) {
return EXCEPTION_CONTINUE_SEARCH;
}
if (pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_HEAP_CORRUPTION || if (pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_HEAP_CORRUPTION ||
pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_STACK_BUFFER_OVERRUN) { pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_STACK_BUFFER_OVERRUN) {
CrashpadClient client; client->DumpAndCrash(pExceptionInfo);
if (gIPCPipe.length()) {
client.SetHandlerIPCPipe(gIPCPipe);
}
client.DumpAndCrash(pExceptionInfo);
} }
return EXCEPTION_CONTINUE_SEARCH; return EXCEPTION_CONTINUE_SEARCH;
@ -60,7 +61,8 @@ bool startCrashHandler() {
return false; return false;
} }
CrashpadClient client; assert(!client);
client = new CrashpadClient();
std::vector<std::string> arguments; std::vector<std::string> arguments;
std::map<std::string, std::string> annotations; std::map<std::string, std::string> annotations;
@ -96,12 +98,9 @@ bool startCrashHandler() {
// Enable automated uploads. // Enable automated uploads.
database->GetSettings()->SetUploadsEnabled(true); database->GetSettings()->SetUploadsEnabled(true);
bool result = client.StartHandler(handler, db, db, BACKTRACE_URL, annotations, arguments, true, true);
gIPCPipe = client.GetHandlerIPCPipe();
AddVectoredExceptionHandler(0, vectoredExceptionHandler); AddVectoredExceptionHandler(0, vectoredExceptionHandler);
return result; return client->StartHandler(handler, db, db, BACKTRACE_URL, annotations, arguments, true, true);
} }
void setCrashAnnotation(std::string name, std::string value) { void setCrashAnnotation(std::string name, std::string value) {

View file

@ -155,9 +155,19 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
AvatarSharedPointer _avatar; AvatarSharedPointer _avatar;
}; };
ViewFrustum cameraView;
qApp->copyDisplayViewFrustum(cameraView); ViewFrustums views;
PrioritySortUtil::PriorityQueue<SortableAvatar> sortedAvatars(cameraView,
ViewFrustum view;
qApp->copyCurrentViewFrustum(view);
views.push_back(view);
if (qApp->hasSecondaryViewFrustum()) {
qApp->copySecondaryViewFrustum(view);
views.push_back(view);
}
PrioritySortUtil::PriorityQueue<SortableAvatar> sortedAvatars(views,
AvatarData::_avatarSortCoefficientSize, AvatarData::_avatarSortCoefficientSize,
AvatarData::_avatarSortCoefficientCenter, AvatarData::_avatarSortCoefficientCenter,
AvatarData::_avatarSortCoefficientAge); AvatarData::_avatarSortCoefficientAge);

View file

@ -277,8 +277,6 @@ namespace AvatarDataPacket {
const float MAX_AUDIO_LOUDNESS = 1000.0f; // close enough for mouth animation const float MAX_AUDIO_LOUDNESS = 1000.0f; // close enough for mouth animation
const int AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS = 1000;
// See also static AvatarData::defaultFullAvatarModelUrl(). // See also static AvatarData::defaultFullAvatarModelUrl().
const QString DEFAULT_FULL_AVATAR_MODEL_NAME = QString("Default"); const QString DEFAULT_FULL_AVATAR_MODEL_NAME = QString("Default");

View file

@ -296,7 +296,8 @@ void EntityTreeRenderer::addPendingEntities(const render::ScenePointer& scene, r
} }
} }
void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene, const ViewFrustum& view, render::Transaction& transaction) { void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene, const ViewFrustums& views,
render::Transaction& transaction) {
PROFILE_RANGE_EX(simulation_physics, "ChangeInScene", 0xffff00ff, (uint64_t)_changedEntities.size()); PROFILE_RANGE_EX(simulation_physics, "ChangeInScene", 0xffff00ff, (uint64_t)_changedEntities.size());
PerformanceTimer pt("change"); PerformanceTimer pt("change");
std::unordered_set<EntityItemID> changedEntities; std::unordered_set<EntityItemID> changedEntities;
@ -357,7 +358,7 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene
// prioritize and sort the renderables // prioritize and sort the renderables
uint64_t sortStart = usecTimestampNow(); uint64_t sortStart = usecTimestampNow();
PrioritySortUtil::PriorityQueue<SortableRenderer> sortedRenderables(view); PrioritySortUtil::PriorityQueue<SortableRenderer> sortedRenderables(views);
{ {
PROFILE_RANGE_EX(simulation_physics, "SortRenderables", 0xffff00ff, (uint64_t)_renderablesToUpdate.size()); PROFILE_RANGE_EX(simulation_physics, "SortRenderables", 0xffff00ff, (uint64_t)_renderablesToUpdate.size());
std::unordered_map<EntityItemID, EntityRendererPointer>::iterator itr = _renderablesToUpdate.begin(); std::unordered_map<EntityItemID, EntityRendererPointer>::iterator itr = _renderablesToUpdate.begin();
@ -415,9 +416,20 @@ void EntityTreeRenderer::update(bool simulate) {
if (scene) { if (scene) {
render::Transaction transaction; render::Transaction transaction;
addPendingEntities(scene, transaction); addPendingEntities(scene, transaction);
ViewFrustums views;
ViewFrustum view; ViewFrustum view;
_viewState->copyCurrentViewFrustum(view); _viewState->copyViewFrustum(view);
updateChangedEntities(scene, view, transaction); views.push_back(view);
if (_viewState->hasSecondaryViewFrustum()) {
_viewState->copySecondaryViewFrustum(view);
views.push_back(view);
}
updateChangedEntities(scene, views, transaction);
scene->enqueueTransaction(transaction); scene->enqueueTransaction(transaction);
} }
} }

View file

@ -148,7 +148,8 @@ protected:
private: private:
void addPendingEntities(const render::ScenePointer& scene, render::Transaction& transaction); void addPendingEntities(const render::ScenePointer& scene, render::Transaction& transaction);
void updateChangedEntities(const render::ScenePointer& scene, const ViewFrustum& view, render::Transaction& transaction); void updateChangedEntities(const render::ScenePointer& scene, const ViewFrustums& views,
render::Transaction& transaction);
EntityRendererPointer renderableForEntity(const EntityItemPointer& entity) const { return renderableForEntityId(entity->getID()); } EntityRendererPointer renderableForEntity(const EntityItemPointer& entity) const { return renderableForEntityId(entity->getID()); }
render::ItemID renderableIdForEntity(const EntityItemPointer& entity) const { return renderableIdForEntityId(entity->getID()); } render::ItemID renderableIdForEntity(const EntityItemPointer& entity) const { return renderableIdForEntityId(entity->getID()); }

View file

@ -13,7 +13,6 @@
#include <OctreeUtils.h> #include <OctreeUtils.h>
DiffTraversal::Waypoint::Waypoint(EntityTreeElementPointer& element) : _nextIndex(0) { DiffTraversal::Waypoint::Waypoint(EntityTreeElementPointer& element) : _nextIndex(0) {
assert(element); assert(element);
_weakElement = element; _weakElement = element;
@ -37,15 +36,14 @@ void DiffTraversal::Waypoint::getNextVisibleElementFirstTime(DiffTraversal::Visi
EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex);
++_nextIndex; ++_nextIndex;
if (nextElement) { if (nextElement) {
if (!view.usesViewFrustum) { const auto& cube = nextElement->getAACube();
if (!view.usesViewFrustums()) {
// No LOD truncation if we aren't using the view frustum // No LOD truncation if we aren't using the view frustum
next.element = nextElement; next.element = nextElement;
return; return;
} else if (view.viewFrustum.cubeIntersectsKeyhole(nextElement->getAACube())) { } else if (view.intersects(cube)) {
// check for LOD truncation // check for LOD truncation
float distance = glm::distance(view.viewFrustum.getPosition(), nextElement->getAACube().calcCenter()) + MIN_VISIBLE_DISTANCE; if (view.isBigEnough(cube, MIN_ELEMENT_ANGULAR_DIAMETER)) {
float angularDiameter = nextElement->getAACube().getScale() / distance;
if (angularDiameter > MIN_ELEMENT_ANGULAR_DIAMETER * view.lodScaleFactor) {
next.element = nextElement; next.element = nextElement;
return; return;
} }
@ -76,17 +74,16 @@ void DiffTraversal::Waypoint::getNextVisibleElementRepeat(
EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex);
++_nextIndex; ++_nextIndex;
if (nextElement && nextElement->getLastChanged() > lastTime) { if (nextElement && nextElement->getLastChanged() > lastTime) {
if (!view.usesViewFrustum) { if (!view.usesViewFrustums()) {
// No LOD truncation if we aren't using the view frustum // No LOD truncation if we aren't using the view frustum
next.element = nextElement; next.element = nextElement;
next.intersection = ViewFrustum::INSIDE; next.intersection = ViewFrustum::INSIDE;
return; return;
} else { } else {
// check for LOD truncation // check for LOD truncation
float distance = glm::distance(view.viewFrustum.getPosition(), nextElement->getAACube().calcCenter()) + MIN_VISIBLE_DISTANCE; const auto& cube = nextElement->getAACube();
float angularDiameter = nextElement->getAACube().getScale() / distance; if (view.isBigEnough(cube, MIN_ELEMENT_ANGULAR_DIAMETER)) {
if (angularDiameter > MIN_ELEMENT_ANGULAR_DIAMETER * view.lodScaleFactor) { ViewFrustum::intersection intersection = view.calculateIntersection(cube);
ViewFrustum::intersection intersection = view.viewFrustum.calculateCubeKeyholeIntersection(nextElement->getAACube());
if (intersection != ViewFrustum::OUTSIDE) { if (intersection != ViewFrustum::OUTSIDE) {
next.element = nextElement; next.element = nextElement;
next.intersection = intersection; next.intersection = intersection;
@ -118,14 +115,13 @@ void DiffTraversal::Waypoint::getNextVisibleElementDifferential(DiffTraversal::V
EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex);
++_nextIndex; ++_nextIndex;
if (nextElement) { if (nextElement) {
AACube cube = nextElement->getAACube();
// check for LOD truncation // check for LOD truncation
float distance = glm::distance(view.viewFrustum.getPosition(), cube.calcCenter()) + MIN_VISIBLE_DISTANCE; const auto& cube = nextElement->getAACube();
float angularDiameter = cube.getScale() / distance; if (view.isBigEnough(cube, MIN_ELEMENT_ANGULAR_DIAMETER)) {
if (angularDiameter > MIN_ELEMENT_ANGULAR_DIAMETER * view.lodScaleFactor) { ViewFrustum::intersection intersection = view.calculateIntersection(cube);
if (view.viewFrustum.calculateCubeKeyholeIntersection(cube) != ViewFrustum::OUTSIDE) { if (intersection != ViewFrustum::OUTSIDE) {
next.element = nextElement; next.element = nextElement;
next.intersection = ViewFrustum::OUTSIDE; next.intersection = intersection;
return; return;
} }
} }
@ -137,13 +133,83 @@ void DiffTraversal::Waypoint::getNextVisibleElementDifferential(DiffTraversal::V
next.intersection = ViewFrustum::OUTSIDE; 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() { DiffTraversal::DiffTraversal() {
const int32_t MIN_PATH_DEPTH = 16; const int32_t MIN_PATH_DEPTH = 16;
_path.reserve(MIN_PATH_DEPTH); _path.reserve(MIN_PATH_DEPTH);
} }
DiffTraversal::Type DiffTraversal::prepareNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, DiffTraversal::Type DiffTraversal::prepareNewTraversal(const DiffTraversal::View& view, EntityTreeElementPointer root) {
int32_t lodLevelOffset, bool usesViewFrustum) {
assert(root); assert(root);
// there are three types of traversal: // 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 // external code should update the _scanElementCallback after calling prepareNewTraversal
// //
_currentView.usesViewFrustum = usesViewFrustum;
float lodScaleFactor = powf(2.0f, lodLevelOffset);
Type type; Type type;
// If usesViewFrustum changes, treat it as a First traversal // 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; type = Type::First;
_currentView.viewFrustum = viewFrustum; _currentView.viewFrustums = view.viewFrustums;
_currentView.lodScaleFactor = lodScaleFactor; _currentView.lodScaleFactor = view.lodScaleFactor;
_getNextVisibleElementCallback = [this](DiffTraversal::VisibleElement& next) { _getNextVisibleElementCallback = [this](DiffTraversal::VisibleElement& next) {
_path.back().getNextVisibleElementFirstTime(next, _currentView); _path.back().getNextVisibleElementFirstTime(next, _currentView);
}; };
} else if (!_currentView.usesViewFrustum || } else if (!_currentView.usesViewFrustums() || _completedView.isVerySimilar(view)) {
(_completedView.viewFrustum.isVerySimilar(viewFrustum) &&
lodScaleFactor == _completedView.lodScaleFactor)) {
type = Type::Repeat; type = Type::Repeat;
_getNextVisibleElementCallback = [this](DiffTraversal::VisibleElement& next) { _getNextVisibleElementCallback = [this](DiffTraversal::VisibleElement& next) {
_path.back().getNextVisibleElementRepeat(next, _completedView, _completedView.startTime); _path.back().getNextVisibleElementRepeat(next, _completedView, _completedView.startTime);
}; };
} else { } else {
type = Type::Differential; type = Type::Differential;
_currentView.viewFrustum = viewFrustum; _currentView.viewFrustums = view.viewFrustums;
_currentView.lodScaleFactor = lodScaleFactor; _currentView.lodScaleFactor = view.lodScaleFactor;
_getNextVisibleElementCallback = [this](DiffTraversal::VisibleElement& next) { _getNextVisibleElementCallback = [this](DiffTraversal::VisibleElement& next) {
_path.back().getNextVisibleElementDifferential(next, _currentView, _completedView); _path.back().getNextVisibleElementDifferential(next, _currentView, _completedView);
}; };

View file

@ -30,10 +30,15 @@ public:
// View is a struct with a ViewFrustum and LOD parameters // View is a struct with a ViewFrustum and LOD parameters
class View { class View {
public: 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;
ViewFrustums viewFrustums;
uint64_t startTime { 0 }; uint64_t startTime { 0 };
float lodScaleFactor { 1.0f }; float lodScaleFactor { 1.0f };
bool usesViewFrustum { true };
}; };
// Waypoint is an bookmark in a "path" of waypoints during a traversal. // Waypoint is an bookmark in a "path" of waypoints during a traversal.
@ -57,15 +62,12 @@ public:
DiffTraversal(); DiffTraversal();
Type prepareNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset, Type prepareNewTraversal(const DiffTraversal::View& view, EntityTreeElementPointer root);
bool usesViewFrustum);
const ViewFrustum& getCurrentView() const { return _currentView.viewFrustum; } const View& getCurrentView() const { return _currentView; }
const ViewFrustum& getCompletedView() const { return _completedView.viewFrustum; } const View& getCompletedView() const { return _completedView; }
bool doesCurrentUseViewFrustum() const { return _currentView.usesViewFrustum; } bool doesCurrentUseViewFrustum() const { return _currentView.usesViewFrustums(); }
float getCurrentLODScaleFactor() const { return _currentView.lodScaleFactor; }
float getCompletedLODScaleFactor() const { return _completedView.lodScaleFactor; }
uint64_t getStartOfCompletedTraversal() const { return _completedView.startTime; } uint64_t getStartOfCompletedTraversal() const { return _completedView.startTime; }
bool finished() const { return _path.empty(); } bool finished() const { return _path.empty(); }

View file

@ -27,9 +27,6 @@ using EntityTreePointer = std::shared_ptr<EntityTree>;
#include "MovingEntitiesOperator.h" #include "MovingEntitiesOperator.h"
class EntityEditFilters; class EntityEditFilters;
class Model;
using ModelPointer = std::shared_ptr<Model>;
using ModelWeakPointer = std::weak_ptr<Model>;
class EntitySimulation; class EntitySimulation;

View file

@ -34,7 +34,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
case PacketType::EntityPhysics: case PacketType::EntityPhysics:
return static_cast<PacketVersion>(EntityVersion::MaterialData); return static_cast<PacketVersion>(EntityVersion::MaterialData);
case PacketType::EntityQuery: case PacketType::EntityQuery:
return static_cast<PacketVersion>(EntityQueryPacketVersion::RemovedJurisdictions); return static_cast<PacketVersion>(EntityQueryPacketVersion::MultiFrustumQuery);
case PacketType::AvatarIdentity: case PacketType::AvatarIdentity:
case PacketType::AvatarData: case PacketType::AvatarData:
case PacketType::BulkAvatarData: case PacketType::BulkAvatarData:
@ -90,6 +90,8 @@ PacketVersion versionForPacketType(PacketType packetType) {
return 18; // replace min_avatar_scale and max_avatar_scale with min_avatar_height and max_avatar_height return 18; // replace min_avatar_scale and max_avatar_scale with min_avatar_height and max_avatar_height
case PacketType::Ping: case PacketType::Ping:
return static_cast<PacketVersion>(PingVersion::IncludeConnectionID); return static_cast<PacketVersion>(PingVersion::IncludeConnectionID);
case PacketType::ViewFrustum:
return static_cast<PacketVersion>(ViewFrustumVersion::SendMultipleFrustums);
default: default:
return 20; return 20;
} }

View file

@ -244,7 +244,8 @@ enum class EntityQueryPacketVersion: PacketVersion {
JSONFilter = 18, JSONFilter = 18,
JSONFilterWithFamilyTree = 19, JSONFilterWithFamilyTree = 19,
ConnectionIdentifier = 20, ConnectionIdentifier = 20,
RemovedJurisdictions = 21 RemovedJurisdictions = 21,
MultiFrustumQuery = 22
}; };
enum class AssetServerPacketVersion: PacketVersion { enum class AssetServerPacketVersion: PacketVersion {
@ -327,4 +328,8 @@ enum class PingVersion : PacketVersion {
IncludeConnectionID = 18 IncludeConnectionID = 18
}; };
enum class ViewFrustumVersion : PacketVersion {
SendMultipleFrustums = 21
};
#endif // hifi_PacketHeaders_h #endif // hifi_PacketHeaders_h

View file

@ -59,7 +59,6 @@ const int LOW_RES_MOVING_ADJUST = 1;
class EncodeBitstreamParams { class EncodeBitstreamParams {
public: public:
ViewFrustum viewFrustum;
bool includeExistsBits; bool includeExistsBits;
NodeData* nodeData; NodeData* nodeData;

View file

@ -9,6 +9,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
#include "OctreeQuery.h"
#include <random> #include <random>
#include <QtCore/QJsonDocument> #include <QtCore/QJsonDocument>
@ -16,23 +18,11 @@
#include <GLMHelpers.h> #include <GLMHelpers.h>
#include <udt/PacketHeaders.h> #include <udt/PacketHeaders.h>
#include "OctreeConstants.h" using QueryFlags = uint8_t;
#include "OctreeQuery.h" const QueryFlags QUERY_HAS_MAIN_FRUSTUM = 1U << 0;
const QueryFlags QUERY_HAS_SECONDARY_FRUSTUM = 1U << 1;
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;
OctreeQuery::OctreeQuery(bool randomizeConnectionID) {
if (randomizeConnectionID) { if (randomizeConnectionID) {
// randomize our initial octree query connection ID using random_device // 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 // 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 // pack the connection ID so the server can detect when we start a new connection
memcpy(destinationBuffer, &_connectionID, sizeof(_connectionID)); memcpy(destinationBuffer, &_connectionID, sizeof(_connectionID));
destinationBuffer += sizeof(_connectionID); destinationBuffer += sizeof(_connectionID);
// back a boolean (cut to 1 byte) to designate if this query uses the sent view frustum // flags for wether the frustums are present
memcpy(destinationBuffer, &_usesFrustum, sizeof(_usesFrustum)); QueryFlags frustumFlags = 0;
destinationBuffer += sizeof(_usesFrustum); if (_hasMainFrustum) {
frustumFlags |= QUERY_HAS_MAIN_FRUSTUM;
if (_usesFrustum) { }
// TODO: DRY this up to a shared method if (_hasSecondaryFrustum) {
// that can pack any type given the number of bytes frustumFlags |= QUERY_HAS_SECONDARY_FRUSTUM;
// and return the number of bytes to push the pointer }
memcpy(destinationBuffer, &frustumFlags, sizeof(frustumFlags));
// camera details destinationBuffer += sizeof(frustumFlags);
memcpy(destinationBuffer, &_cameraPosition, sizeof(_cameraPosition));
destinationBuffer += sizeof(_cameraPosition); if (_hasMainFrustum) {
destinationBuffer += packOrientationQuatToBytes(destinationBuffer, _cameraOrientation); auto byteArray = _mainViewFrustum.toByteArray();
destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, _cameraFov); memcpy(destinationBuffer, byteArray.constData(), byteArray.size());
destinationBuffer += packFloatRatioToTwoByte(destinationBuffer, _cameraAspectRatio); destinationBuffer += byteArray.size();
destinationBuffer += packClipValueToTwoByte(destinationBuffer, _cameraNearClip); }
destinationBuffer += packClipValueToTwoByte(destinationBuffer, _cameraFarClip);
memcpy(destinationBuffer, &_cameraEyeOffsetPosition, sizeof(_cameraEyeOffsetPosition)); if (_hasSecondaryFrustum) {
destinationBuffer += sizeof(_cameraEyeOffsetPosition); auto byteArray = _secondaryViewFrustum.toByteArray();
memcpy(destinationBuffer, byteArray.constData(), byteArray.size());
destinationBuffer += byteArray.size();
} }
// desired Max Octree PPS // desired Max Octree PPS
@ -80,9 +72,6 @@ int OctreeQuery::getBroadcastData(unsigned char* destinationBuffer) {
// desired boundaryLevelAdjust // desired boundaryLevelAdjust
memcpy(destinationBuffer, &_boundaryLevelAdjust, sizeof(_boundaryLevelAdjust)); memcpy(destinationBuffer, &_boundaryLevelAdjust, sizeof(_boundaryLevelAdjust));
destinationBuffer += 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 // create a QByteArray that holds the binary representation of the JSON parameters
QByteArray binaryParametersDocument; QByteArray binaryParametersDocument;
@ -110,6 +99,7 @@ int OctreeQuery::getBroadcastData(unsigned char* destinationBuffer) {
int OctreeQuery::parseData(ReceivedMessage& message) { int OctreeQuery::parseData(ReceivedMessage& message) {
const unsigned char* startPosition = reinterpret_cast<const unsigned char*>(message.getRawMessage()); const unsigned char* startPosition = reinterpret_cast<const unsigned char*>(message.getRawMessage());
const unsigned char* endPosition = startPosition + message.getSize();
const unsigned char* sourceBuffer = startPosition; const unsigned char* sourceBuffer = startPosition;
// unpack the connection ID // unpack the connection ID
@ -133,20 +123,23 @@ int OctreeQuery::parseData(ReceivedMessage& message) {
} }
// check if this query uses a view frustum // check if this query uses a view frustum
memcpy(&_usesFrustum, sourceBuffer, sizeof(_usesFrustum)); QueryFlags frustumFlags { 0 };
sourceBuffer += sizeof(_usesFrustum); memcpy(&frustumFlags, sourceBuffer, sizeof(frustumFlags));
sourceBuffer += sizeof(frustumFlags);
if (_usesFrustum) {
// unpack camera details _hasMainFrustum = frustumFlags & QUERY_HAS_MAIN_FRUSTUM;
memcpy(&_cameraPosition, sourceBuffer, sizeof(_cameraPosition)); _hasSecondaryFrustum = frustumFlags & QUERY_HAS_SECONDARY_FRUSTUM;
sourceBuffer += sizeof(_cameraPosition);
sourceBuffer += unpackOrientationQuatFromBytes(sourceBuffer, _cameraOrientation); if (_hasMainFrustum) {
sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &_cameraFov); auto bytesLeft = endPosition - sourceBuffer;
sourceBuffer += unpackFloatRatioFromTwoByte(sourceBuffer,_cameraAspectRatio); auto byteArray = QByteArray::fromRawData(reinterpret_cast<const char*>(sourceBuffer), bytesLeft);
sourceBuffer += unpackClipValueFromTwoByte(sourceBuffer,_cameraNearClip); sourceBuffer += _mainViewFrustum.fromByteArray(byteArray);
sourceBuffer += unpackClipValueFromTwoByte(sourceBuffer,_cameraFarClip); }
memcpy(&_cameraEyeOffsetPosition, sourceBuffer, sizeof(_cameraEyeOffsetPosition));
sourceBuffer += sizeof(_cameraEyeOffsetPosition); if (_hasSecondaryFrustum) {
auto bytesLeft = endPosition - sourceBuffer;
auto byteArray = QByteArray::fromRawData(reinterpret_cast<const char*>(sourceBuffer), bytesLeft);
sourceBuffer += _secondaryViewFrustum.fromByteArray(byteArray);
} }
// desired Max Octree PPS // desired Max Octree PPS
@ -161,9 +154,6 @@ int OctreeQuery::parseData(ReceivedMessage& message) {
memcpy(&_boundaryLevelAdjust, sourceBuffer, sizeof(_boundaryLevelAdjust)); memcpy(&_boundaryLevelAdjust, sourceBuffer, sizeof(_boundaryLevelAdjust));
sourceBuffer += sizeof(_boundaryLevelAdjust); sourceBuffer += sizeof(_boundaryLevelAdjust);
memcpy(&_cameraCenterRadius, sourceBuffer, sizeof(_cameraCenterRadius));
sourceBuffer += sizeof(_cameraCenterRadius);
// check if we have a packed JSON filter // check if we have a packed JSON filter
uint16_t binaryParametersBytes; uint16_t binaryParametersBytes;
memcpy(&binaryParametersBytes, sourceBuffer, sizeof(binaryParametersBytes)); memcpy(&binaryParametersBytes, sourceBuffer, sizeof(binaryParametersBytes));
@ -184,8 +174,3 @@ int OctreeQuery::parseData(ReceivedMessage& message) {
return sourceBuffer - startPosition; return sourceBuffer - startPosition;
} }
glm::vec3 OctreeQuery::calculateCameraDirection() const {
glm::vec3 direction = glm::vec3(_cameraOrientation * glm::vec4(IDENTITY_FORWARD, 0.0f));
return direction;
}

View file

@ -12,16 +12,14 @@
#ifndef hifi_OctreeQuery_h #ifndef hifi_OctreeQuery_h
#define hifi_OctreeQuery_h #define hifi_OctreeQuery_h
#include <inttypes.h>
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
#include <QtCore/QJsonObject> #include <QtCore/QJsonObject>
#include <QtCore/QReadWriteLock> #include <QtCore/QReadWriteLock>
#include <NodeData.h> #include <NodeData.h>
#include <ViewFrustum.h>
#include "OctreeConstants.h"
class OctreeQuery : public NodeData { class OctreeQuery : public NodeData {
Q_OBJECT Q_OBJECT
@ -30,31 +28,22 @@ public:
OctreeQuery(bool randomizeConnectionID = false); OctreeQuery(bool randomizeConnectionID = false);
virtual ~OctreeQuery() {} virtual ~OctreeQuery() {}
OctreeQuery(const OctreeQuery&) = delete;
OctreeQuery& operator=(const OctreeQuery&) = delete;
int getBroadcastData(unsigned char* destinationBuffer); int getBroadcastData(unsigned char* destinationBuffer);
int parseData(ReceivedMessage& message) override; int parseData(ReceivedMessage& message) override;
// getters for camera details bool hasMainViewFrustum() const { return _hasMainFrustum; }
const glm::vec3& getCameraPosition() const { return _cameraPosition; } void setMainViewFrustum(const ViewFrustum& viewFrustum) { _hasMainFrustum = true; _mainViewFrustum = viewFrustum; }
const glm::quat& getCameraOrientation() const { return _cameraOrientation; } void clearMainViewFrustum() { _hasMainFrustum = false; }
float getCameraFov() const { return _cameraFov; } const ViewFrustum& getMainViewFrustum() const { return _mainViewFrustum; }
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; }
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 // getters/setters for JSON filter
QJsonObject getJSONParameters() { QReadLocker locker { &_jsonParametersLock }; return _jsonParameters; } QJsonObject getJSONParameters() { QReadLocker locker { &_jsonParametersLock }; return _jsonParameters; }
void setJSONParameters(const QJsonObject& jsonParameters) void setJSONParameters(const QJsonObject& jsonParameters)
@ -64,9 +53,6 @@ public:
int getMaxQueryPacketsPerSecond() const { return _maxQueryPPS; } int getMaxQueryPacketsPerSecond() const { return _maxQueryPPS; }
float getOctreeSizeScale() const { return _octreeElementSizeScale; } float getOctreeSizeScale() const { return _octreeElementSizeScale; }
int getBoundaryLevelAdjust() const { return _boundaryLevelAdjust; } int getBoundaryLevelAdjust() const { return _boundaryLevelAdjust; }
bool getUsesFrustum() { return _usesFrustum; }
void setUsesFrustum(bool usesFrustum) { _usesFrustum = usesFrustum; }
void incrementConnectionID() { ++_connectionID; } void incrementConnectionID() { ++_connectionID; }
@ -81,33 +67,22 @@ public slots:
void setBoundaryLevelAdjust(int boundaryLevelAdjust) { _boundaryLevelAdjust = boundaryLevelAdjust; } void setBoundaryLevelAdjust(int boundaryLevelAdjust) { _boundaryLevelAdjust = boundaryLevelAdjust; }
protected: protected:
// camera details for the avatar bool _hasMainFrustum { false };
glm::vec3 _cameraPosition { glm::vec3(0.0f) }; ViewFrustum _mainViewFrustum;
glm::quat _cameraOrientation { glm::quat() }; bool _hasSecondaryFrustum { false };
float _cameraFov; ViewFrustum _secondaryViewFrustum;
float _cameraAspectRatio;
float _cameraNearClip;
float _cameraFarClip;
float _cameraCenterRadius;
glm::vec3 _cameraEyeOffsetPosition { glm::vec3(0.0f) };
// octree server sending items // octree server sending items
int _maxQueryPPS = DEFAULT_MAX_OCTREE_PPS; int _maxQueryPPS = DEFAULT_MAX_OCTREE_PPS;
float _octreeElementSizeScale = DEFAULT_OCTREE_SIZE_SCALE; /// used for LOD calculations float _octreeElementSizeScale = DEFAULT_OCTREE_SIZE_SCALE; /// used for LOD calculations
int _boundaryLevelAdjust = 0; /// 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 uint16_t _connectionID; // query connection ID, randomized to start, increments with each new connection to server
QJsonObject _jsonParameters; QJsonObject _jsonParameters;
QReadWriteLock _jsonParametersLock; QReadWriteLock _jsonParametersLock;
bool _hasReceivedFirstQuery { false }; 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 #endif // hifi_OctreeQuery_h

View file

@ -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); QMutexLocker viewLocker(&_viewMutex);
viewOut = _currentViewFrustum; viewOut = _currentMainViewFrustum;
}
void OctreeQueryNode::copyCurrentSecondaryViewFrustum(ViewFrustum& viewOut) const {
QMutexLocker viewLocker(&_viewMutex);
viewOut = _currentSecondaryViewFrustum;
} }
bool OctreeQueryNode::updateCurrentViewFrustum() { bool OctreeQueryNode::updateCurrentViewFrustum() {
@ -150,70 +155,50 @@ bool OctreeQueryNode::updateCurrentViewFrustum() {
return false; 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 // this client does not use a view frustum so the view frustum for this query has not changed
return false; 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) { void OctreeQueryNode::setViewSent(bool viewSent) {

View file

@ -49,7 +49,8 @@ public:
OctreeElementExtraEncodeData extraEncodeData; 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 // These are not classic setters because they are calculating and maintaining state
// which is set asynchronously through the network receive // which is set asynchronously through the network receive
@ -87,9 +88,6 @@ public:
void setShouldForceFullScene(bool shouldForceFullScene) { _shouldForceFullScene = shouldForceFullScene; } void setShouldForceFullScene(bool shouldForceFullScene) { _shouldForceFullScene = shouldForceFullScene; }
private: private:
OctreeQueryNode(const OctreeQueryNode &);
OctreeQueryNode& operator= (const OctreeQueryNode&);
bool _viewSent { false }; bool _viewSent { false };
std::unique_ptr<NLPacket> _octreePacket; std::unique_ptr<NLPacket> _octreePacket;
bool _octreePacketWaiting; bool _octreePacketWaiting;
@ -99,7 +97,8 @@ private:
quint64 _firstSuppressedPacket { usecTimestampNow() }; quint64 _firstSuppressedPacket { usecTimestampNow() };
mutable QMutex _viewMutex { QMutex::Recursive }; mutable QMutex _viewMutex { QMutex::Recursive };
ViewFrustum _currentViewFrustum; ViewFrustum _currentMainViewFrustum;
ViewFrustum _currentSecondaryViewFrustum;
bool _viewFrustumChanging { false }; bool _viewFrustumChanging { false };
bool _viewFrustumJustStoppedChanging { true }; bool _viewFrustumJustStoppedChanging { true };

View file

@ -16,6 +16,7 @@
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <AABox.h> #include <AABox.h>
#include <AACube.h>
float calculateRenderAccuracy(const glm::vec3& position, float calculateRenderAccuracy(const glm::vec3& position,
const AABox& bounds, const AABox& bounds,
@ -73,4 +74,10 @@ float getOrthographicAccuracySize(float octreeSizeScale, int boundaryLevelAdjust
// Smallest visible element is 1cm // Smallest visible element is 1cm
const float smallestSize = 0.01f; const float smallestSize = 0.01f;
return (smallestSize * MAX_VISIBILITY_DISTANCE_FOR_UNIT_ELEMENT) / boundaryDistanceForRenderLevel(boundaryLevelAdjust, octreeSizeScale); 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;
}

View file

@ -15,6 +15,7 @@
#include "OctreeConstants.h" #include "OctreeConstants.h"
class AABox; class AABox;
class AACube;
class QJsonDocument; class QJsonDocument;
/// renderAccuracy represents a floating point "visibility" of an object based on it's view from the camera. At a simple /// 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_ENTITY_ANGULAR_DIAMETER = MIN_ELEMENT_ANGULAR_DIAMETER * SQRT_THREE;
const float MIN_VISIBLE_DISTANCE = 0.0001f; // helps avoid divide-by-zero check 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 #endif // hifi_OctreeUtils_h

View file

@ -31,6 +31,10 @@ public:
/// copies the current view frustum for rendering the view state /// copies the current view frustum for rendering the view state
virtual void copyCurrentViewFrustum(ViewFrustum& viewOut) const = 0; virtual void copyCurrentViewFrustum(ViewFrustum& viewOut) const = 0;
virtual void copyViewFrustum(ViewFrustum& viewOut) const = 0;
virtual void copySecondaryViewFrustum(ViewFrustum& viewOut) const = 0;
virtual bool hasSecondaryViewFrustum() const = 0;
virtual QThread* getMainThread() = 0; virtual QThread* getMainThread() = 0;
virtual PickRay computePickRay(float x, float y) const = 0; virtual PickRay computePickRay(float x, float y) const = 0;

View file

@ -0,0 +1,77 @@
//
// CrashHelpers.cpp
// libraries/shared/src
//
// Created by Clement Brisset on 4/30/18.
// Copyright 2018 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 "CrashHelpers.h"
namespace crash {
class B;
class A {
public:
A(B* b) : _b(b) { }
~A();
virtual void virtualFunction() = 0;
private:
B* _b;
};
class B : public A {
public:
B() : A(this) { }
virtual void virtualFunction() override { }
};
A::~A() {
_b->virtualFunction();
}
void pureVirtualCall() {
qCDebug(shared) << "About to make a pure virtual call";
B b;
}
void doubleFree() {
qCDebug(shared) << "About to double delete memory";
int* blah = new int(200);
delete blah;
delete blah;
}
void nullDeref() {
qCDebug(shared) << "About to dereference a null pointer";
int* p = nullptr;
*p = 1;
}
void doAbort() {
qCDebug(shared) << "About to abort";
abort();
}
void outOfBoundsVectorCrash() {
qCDebug(shared) << "std::vector out of bounds crash!";
std::vector<int> v;
v[0] = 42;
}
void newFault() {
qCDebug(shared) << "About to crash inside new fault";
// Force crash with multiple large allocations
while (true) {
const size_t GIGABYTE = 1024 * 1024 * 1024;
new char[GIGABYTE];
}
}
}

View file

@ -18,66 +18,12 @@
namespace crash { namespace crash {
class B; void pureVirtualCall();
class A { void doubleFree();
public: void nullDeref();
A(B* b) : _b(b) { } void doAbort();
~A(); void outOfBoundsVectorCrash();
virtual void virtualFunction() = 0; void newFault();
private:
B* _b;
};
class B : public A {
public:
B() : A(this) { }
virtual void virtualFunction() override { }
};
A::~A() {
_b->virtualFunction();
}
void pureVirtualCall() {
qCDebug(shared) << "About to make a pure virtual call";
B b;
}
void doubleFree() {
qCDebug(shared) << "About to double delete memory";
int* blah = new int(200);
delete blah;
delete blah;
}
void nullDeref() {
qCDebug(shared) << "About to dereference a null pointer";
int* p = nullptr;
*p = 1;
}
void doAbort() {
qCDebug(shared) << "About to abort";
abort();
}
void outOfBoundsVectorCrash() {
qCDebug(shared) << "std::vector out of bounds crash!";
std::vector<int> v;
v[0] = 42;
}
void newFault() {
qCDebug(shared) << "About to crash inside new fault";
// Force crash with multiple large allocations
while (true) {
const size_t GIGABYTE = 1024 * 1024 * 1024;
new char[GIGABYTE];
}
}
} }

View file

@ -84,14 +84,12 @@ namespace PrioritySortUtil {
class PriorityQueue { class PriorityQueue {
public: public:
PriorityQueue() = delete; PriorityQueue() = delete;
PriorityQueue(const ViewFrustums& views) : _views(views) { }
PriorityQueue(const ViewFrustum& view) : _view(view) { } PriorityQueue(const ViewFrustums& views, float angularWeight, float centerWeight, float ageWeight)
: _views(views), _angularWeight(angularWeight), _centerWeight(centerWeight), _ageWeight(ageWeight)
PriorityQueue(const ViewFrustum& view, float angularWeight, float centerWeight, float ageWeight)
: _view(view), _angularWeight(angularWeight), _centerWeight(centerWeight), _ageWeight(ageWeight)
{ } { }
void setView(const ViewFrustum& view) { _view = view; } void setViews(const ViewFrustums& views) { _views = views; }
void setWeights(float angularWeight, float centerWeight, float ageWeight) { void setWeights(float angularWeight, float centerWeight, float ageWeight) {
_angularWeight = angularWeight; _angularWeight = angularWeight;
@ -109,7 +107,18 @@ namespace PrioritySortUtil {
bool empty() const { return _queue.empty(); } bool empty() const { return _queue.empty(); }
private: private:
float computePriority(const T& thing) const { float computePriority(const T& thing) const {
float priority = std::numeric_limits<float>::min();
for (const auto& view : _views) {
priority = std::max(priority, computePriority(view, thing));
}
return priority;
}
float computePriority(const ViewFrustum& view, const T& thing) const {
// priority = weighted linear combination of multiple values: // priority = weighted linear combination of multiple values:
// (a) angular size // (a) angular size
// (b) proximity to center of view // (b) proximity to center of view
@ -117,11 +126,11 @@ namespace PrioritySortUtil {
// where the relative "weights" are tuned to scale the contributing values into units of "priority". // where the relative "weights" are tuned to scale the contributing values into units of "priority".
glm::vec3 position = thing.getPosition(); glm::vec3 position = thing.getPosition();
glm::vec3 offset = position - _view.getPosition(); glm::vec3 offset = position - view.getPosition();
float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero
const float MIN_RADIUS = 0.1f; // WORKAROUND for zero size objects (we still want them to sort by distance) const float MIN_RADIUS = 0.1f; // WORKAROUND for zero size objects (we still want them to sort by distance)
float radius = glm::min(thing.getRadius(), MIN_RADIUS); float radius = glm::min(thing.getRadius(), MIN_RADIUS);
float cosineAngle = (glm::dot(offset, _view.getDirection()) / distance); float cosineAngle = (glm::dot(offset, view.getDirection()) / distance);
float age = (float)(usecTimestampNow() - thing.getTimestamp()); float age = (float)(usecTimestampNow() - thing.getTimestamp());
// we modulatate "age" drift rate by the cosineAngle term to make periphrial objects sort forward // we modulatate "age" drift rate by the cosineAngle term to make periphrial objects sort forward
@ -134,8 +143,8 @@ namespace PrioritySortUtil {
+ _ageWeight * cosineAngleFactor * age; + _ageWeight * cosineAngleFactor * age;
// decrement priority of things outside keyhole // decrement priority of things outside keyhole
if (distance - radius > _view.getCenterRadius()) { if (distance - radius > view.getCenterRadius()) {
if (!_view.sphereIntersectsFrustum(position, radius)) { if (!view.sphereIntersectsFrustum(position, radius)) {
constexpr float OUT_OF_VIEW_PENALTY = -10.0f; constexpr float OUT_OF_VIEW_PENALTY = -10.0f;
priority += OUT_OF_VIEW_PENALTY; priority += OUT_OF_VIEW_PENALTY;
} }
@ -143,7 +152,7 @@ namespace PrioritySortUtil {
return priority; return priority;
} }
ViewFrustum _view; ViewFrustums _views;
std::priority_queue<T> _queue; std::priority_queue<T> _queue;
float _angularWeight { DEFAULT_ANGULAR_COEF }; float _angularWeight { DEFAULT_ANGULAR_COEF };
float _centerWeight { DEFAULT_CENTER_COEF }; float _centerWeight { DEFAULT_CENTER_COEF };

View file

@ -75,6 +75,10 @@ void ViewFrustum::setProjection(const glm::mat4& projection) {
_width = _corners[TOP_RIGHT_NEAR].x - _corners[TOP_LEFT_NEAR].x; _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() // ViewFrustum::calculate()
// //
// Description: this will calculate the view frustum bounds for a given position and direction // 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"; return "Unknown";
} }
void ViewFrustum::fromByteArray(const QByteArray& input) { int ViewFrustum::fromByteArray(const QByteArray& input) {
// From the wire! // From the wire!
glm::vec3 cameraPosition; glm::vec3 cameraPosition;
@ -168,14 +172,12 @@ void ViewFrustum::fromByteArray(const QByteArray& input) {
0.0f != cameraNearClip && 0.0f != cameraNearClip &&
0.0f != cameraFarClip && 0.0f != cameraFarClip &&
cameraNearClip != cameraFarClip) { cameraNearClip != cameraFarClip) {
setProjection(glm::perspective(
glm::radians(cameraFov),
cameraAspectRatio,
cameraNearClip,
cameraFarClip));
setProjection(cameraFov, cameraAspectRatio, cameraNearClip, cameraFarClip);
calculate(); calculate();
} }
return sourceBuffer - startPosition;
} }

View file

@ -48,6 +48,7 @@ public:
// setters for lens attributes // setters for lens attributes
void setProjection(const glm::mat4 & projection); void setProjection(const glm::mat4 & projection);
void setProjection(float cameraFov, float cameraAspectRatio, float cameraNearClip, float cameraFarClip);
void setFocalLength(float focalLength) { _focalLength = focalLength; } void setFocalLength(float focalLength) { _focalLength = focalLength; }
bool isPerspective() const; bool isPerspective() const;
@ -147,7 +148,7 @@ public:
void invalidate(); // causes all reasonable intersection tests to fail void invalidate(); // causes all reasonable intersection tests to fail
QByteArray toByteArray(); QByteArray toByteArray();
void fromByteArray(const QByteArray& input); int fromByteArray(const QByteArray& input);
private: private:
glm::mat4 _view; glm::mat4 _view;
@ -188,5 +189,6 @@ private:
}; };
using ViewFrustumPointer = std::shared_ptr<ViewFrustum>; using ViewFrustumPointer = std::shared_ptr<ViewFrustum>;
using ViewFrustums = std::vector<ViewFrustum>;
#endif // hifi_ViewFrustum_h #endif // hifi_ViewFrustum_h

View file

@ -441,6 +441,16 @@ protected:
viewOut = _viewFrustum; viewOut = _viewFrustum;
} }
void copyViewFrustum(ViewFrustum& viewOut) const override {
viewOut = _viewFrustum;
}
void copySecondaryViewFrustum(ViewFrustum& viewOut) const override {}
bool hasSecondaryViewFrustum() const override {
return false;
}
QThread* getMainThread() override { QThread* getMainThread() override {
return QThread::currentThread(); return QThread::currentThread();
} }