mirror of
https://github.com/overte-org/overte.git
synced 2025-07-23 12:24:26 +02:00
Merge branch 'master' into hazeZone
This commit is contained in:
commit
2e1919eba5
49 changed files with 1536 additions and 475 deletions
53
assignment-client/src/entities/EntityPriorityQueue.cpp
Normal file
53
assignment-client/src/entities/EntityPriorityQueue.cpp
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
//
|
||||||
|
// EntityPriorityQueue.cpp
|
||||||
|
// assignment-client/src/entities
|
||||||
|
//
|
||||||
|
// Created by Andrew Meadows 2017.08.08
|
||||||
|
// Copyright 2017 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "EntityPriorityQueue.h"
|
||||||
|
|
||||||
|
const float PrioritizedEntity::DO_NOT_SEND = -1.0e-6f;
|
||||||
|
const float PrioritizedEntity::FORCE_REMOVE = -1.0e-5f;
|
||||||
|
const float PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY = 1.0f;
|
||||||
|
|
||||||
|
void ConicalView::set(const ViewFrustum& viewFrustum) {
|
||||||
|
// The ConicalView has two parts: a central sphere (same as ViewFrustum) and a circular cone that bounds the frustum part.
|
||||||
|
// Why? Because approximate intersection tests are much faster to compute for a cone than for a frustum.
|
||||||
|
_position = viewFrustum.getPosition();
|
||||||
|
_direction = viewFrustum.getDirection();
|
||||||
|
|
||||||
|
// We cache the sin and cos of the half angle of the cone that bounds the frustum.
|
||||||
|
// (the math here is left as an exercise for the reader)
|
||||||
|
float A = viewFrustum.getAspectRatio();
|
||||||
|
float t = tanf(0.5f * viewFrustum.getFieldOfView());
|
||||||
|
_cosAngle = 1.0f / sqrtf(1.0f + (A * A + 1.0f) * (t * t));
|
||||||
|
_sinAngle = sqrtf(1.0f - _cosAngle * _cosAngle);
|
||||||
|
|
||||||
|
_radius = viewFrustum.getCenterRadius();
|
||||||
|
}
|
||||||
|
|
||||||
|
float ConicalView::computePriority(const AACube& cube) const {
|
||||||
|
glm::vec3 p = cube.calcCenter() - _position; // position of bounding sphere in view-frame
|
||||||
|
float d = glm::length(p); // distance to center of bounding sphere
|
||||||
|
float r = 0.5f * cube.getScale(); // radius of bounding sphere
|
||||||
|
if (d < _radius + r) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
// We check the angle between the center of the cube and the _direction of the view.
|
||||||
|
// If it is less than the sum of the half-angle from center of cone to outer edge plus
|
||||||
|
// the half apparent angle of the bounding sphere then it is in view.
|
||||||
|
//
|
||||||
|
// The math here is left as an exercise for the reader with the following hints:
|
||||||
|
// (1) We actually check the dot product of the cube's local position rather than the angle and
|
||||||
|
// (2) we take advantage of this trig identity: cos(A+B) = cos(A)*cos(B) - sin(A)*sin(B)
|
||||||
|
if (glm::dot(p, _direction) > sqrtf(d * d - r * r) * _cosAngle - r * _sinAngle) {
|
||||||
|
const float AVOID_DIVIDE_BY_ZERO = 0.001f;
|
||||||
|
return r / (d + AVOID_DIVIDE_BY_ZERO);
|
||||||
|
}
|
||||||
|
return PrioritizedEntity::DO_NOT_SEND;
|
||||||
|
}
|
66
assignment-client/src/entities/EntityPriorityQueue.h
Normal file
66
assignment-client/src/entities/EntityPriorityQueue.h
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
//
|
||||||
|
// EntityPriorityQueue.h
|
||||||
|
// assignment-client/src/entities
|
||||||
|
//
|
||||||
|
// Created by Andrew Meadows 2017.08.08
|
||||||
|
// Copyright 2017 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef hifi_EntityPriorityQueue_h
|
||||||
|
#define hifi_EntityPriorityQueue_h
|
||||||
|
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
|
#include <AACube.h>
|
||||||
|
#include <EntityTreeElement.h>
|
||||||
|
|
||||||
|
const float SQRT_TWO_OVER_TWO = 0.7071067811865f;
|
||||||
|
const float DEFAULT_VIEW_RADIUS = 10.0f;
|
||||||
|
|
||||||
|
// ConicalView is an approximation of a ViewFrustum for fast calculation of sort priority.
|
||||||
|
class ConicalView {
|
||||||
|
public:
|
||||||
|
ConicalView() {}
|
||||||
|
ConicalView(const ViewFrustum& viewFrustum) { set(viewFrustum); }
|
||||||
|
void set(const ViewFrustum& viewFrustum);
|
||||||
|
float computePriority(const AACube& cube) const;
|
||||||
|
private:
|
||||||
|
glm::vec3 _position { 0.0f, 0.0f, 0.0f };
|
||||||
|
glm::vec3 _direction { 0.0f, 0.0f, 1.0f };
|
||||||
|
float _sinAngle { SQRT_TWO_OVER_TWO };
|
||||||
|
float _cosAngle { SQRT_TWO_OVER_TWO };
|
||||||
|
float _radius { DEFAULT_VIEW_RADIUS };
|
||||||
|
};
|
||||||
|
|
||||||
|
// PrioritizedEntity is a placeholder in a sorted queue.
|
||||||
|
class PrioritizedEntity {
|
||||||
|
public:
|
||||||
|
static const float DO_NOT_SEND;
|
||||||
|
static const float FORCE_REMOVE;
|
||||||
|
static const float WHEN_IN_DOUBT_PRIORITY;
|
||||||
|
|
||||||
|
PrioritizedEntity(EntityItemPointer entity, float priority, bool forceRemove = false) : _weakEntity(entity), _rawEntityPointer(entity.get()), _priority(priority), _forceRemove(forceRemove) {}
|
||||||
|
EntityItemPointer getEntity() const { return _weakEntity.lock(); }
|
||||||
|
EntityItem* getRawEntityPointer() const { return _rawEntityPointer; }
|
||||||
|
float getPriority() const { return _priority; }
|
||||||
|
bool shouldForceRemove() const { return _forceRemove; }
|
||||||
|
|
||||||
|
class Compare {
|
||||||
|
public:
|
||||||
|
bool operator() (const PrioritizedEntity& A, const PrioritizedEntity& B) { return A._priority < B._priority; }
|
||||||
|
};
|
||||||
|
friend class Compare;
|
||||||
|
|
||||||
|
private:
|
||||||
|
EntityItemWeakPointer _weakEntity;
|
||||||
|
EntityItem* _rawEntityPointer;
|
||||||
|
float _priority;
|
||||||
|
bool _forceRemove;
|
||||||
|
};
|
||||||
|
|
||||||
|
using EntityPriorityQueue = std::priority_queue< PrioritizedEntity, std::vector<PrioritizedEntity>, PrioritizedEntity::Compare >;
|
||||||
|
|
||||||
|
#endif // hifi_EntityPriorityQueue_h
|
|
@ -13,9 +13,18 @@
|
||||||
|
|
||||||
#include <EntityNodeData.h>
|
#include <EntityNodeData.h>
|
||||||
#include <EntityTypes.h>
|
#include <EntityTypes.h>
|
||||||
|
#include <OctreeUtils.h>
|
||||||
|
|
||||||
#include "EntityServer.h"
|
#include "EntityServer.h"
|
||||||
|
|
||||||
|
|
||||||
|
EntityTreeSendThread::EntityTreeSendThread(OctreeServer* myServer, const SharedNodePointer& node) :
|
||||||
|
OctreeSendThread(myServer, node)
|
||||||
|
{
|
||||||
|
connect(std::static_pointer_cast<EntityTree>(myServer->getOctree()).get(), &EntityTree::editingEntityPointer, this, &EntityTreeSendThread::editingEntityPointer, Qt::QueuedConnection);
|
||||||
|
connect(std::static_pointer_cast<EntityTree>(myServer->getOctree()).get(), &EntityTree::deletingEntityPointer, this, &EntityTreeSendThread::deletingEntityPointer, Qt::QueuedConnection);
|
||||||
|
}
|
||||||
|
|
||||||
void EntityTreeSendThread::preDistributionProcessing() {
|
void EntityTreeSendThread::preDistributionProcessing() {
|
||||||
auto node = _node.toStrongRef();
|
auto node = _node.toStrongRef();
|
||||||
auto nodeData = static_cast<EntityNodeData*>(node->getLinkedData());
|
auto nodeData = static_cast<EntityNodeData*>(node->getLinkedData());
|
||||||
|
@ -80,6 +89,72 @@ void EntityTreeSendThread::preDistributionProcessing() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData,
|
||||||
|
bool viewFrustumChanged, bool isFullScene) {
|
||||||
|
if (viewFrustumChanged || _traversal.finished()) {
|
||||||
|
ViewFrustum viewFrustum;
|
||||||
|
nodeData->copyCurrentViewFrustum(viewFrustum);
|
||||||
|
EntityTreeElementPointer root = std::dynamic_pointer_cast<EntityTreeElement>(_myServer->getOctree()->getRoot());
|
||||||
|
int32_t lodLevelOffset = nodeData->getBoundaryLevelAdjust() + (viewFrustumChanged ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST);
|
||||||
|
startNewTraversal(viewFrustum, root, lodLevelOffset, nodeData->getUsesFrustum());
|
||||||
|
|
||||||
|
// When the viewFrustum changed the sort order may be incorrect, so we re-sort
|
||||||
|
// and also use the opportunity to cull anything no longer in view
|
||||||
|
if (viewFrustumChanged && !_sendQueue.empty()) {
|
||||||
|
EntityPriorityQueue prevSendQueue;
|
||||||
|
_sendQueue.swap(prevSendQueue);
|
||||||
|
_entitiesInQueue.clear();
|
||||||
|
// Re-add elements from previous traversal if they still need to be sent
|
||||||
|
float lodScaleFactor = _traversal.getCurrentLODScaleFactor();
|
||||||
|
glm::vec3 viewPosition = _traversal.getCurrentView().getPosition();
|
||||||
|
while (!prevSendQueue.empty()) {
|
||||||
|
EntityItemPointer entity = prevSendQueue.top().getEntity();
|
||||||
|
bool forceRemove = prevSendQueue.top().shouldForceRemove();
|
||||||
|
prevSendQueue.pop();
|
||||||
|
if (entity) {
|
||||||
|
if (!forceRemove) {
|
||||||
|
bool success = false;
|
||||||
|
AACube cube = entity->getQueryAACube(success);
|
||||||
|
if (success) {
|
||||||
|
if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) {
|
||||||
|
float priority = _conicalView.computePriority(cube);
|
||||||
|
if (priority != PrioritizedEntity::DO_NOT_SEND) {
|
||||||
|
float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE;
|
||||||
|
float angularDiameter = cube.getScale() / distance;
|
||||||
|
if (angularDiameter > MIN_ENTITY_ANGULAR_DIAMETER * lodScaleFactor) {
|
||||||
|
_sendQueue.push(PrioritizedEntity(entity, priority));
|
||||||
|
_entitiesInQueue.insert(entity.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
|
||||||
|
_entitiesInQueue.insert(entity.get());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::FORCE_REMOVE, true));
|
||||||
|
_entitiesInQueue.insert(entity.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_traversal.finished()) {
|
||||||
|
quint64 startTime = usecTimestampNow();
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
const uint64_t TIME_BUDGET = 400; // usec
|
||||||
|
#else
|
||||||
|
const uint64_t TIME_BUDGET = 200; // usec
|
||||||
|
#endif
|
||||||
|
_traversal.traverse(TIME_BUDGET);
|
||||||
|
OctreeServer::trackTreeTraverseTime((float)(usecTimestampNow() - startTime));
|
||||||
|
}
|
||||||
|
|
||||||
|
OctreeSendThread::traverseTreeAndSendContents(node, nodeData, viewFrustumChanged, isFullScene);
|
||||||
|
}
|
||||||
|
|
||||||
bool EntityTreeSendThread::addAncestorsToExtraFlaggedEntities(const QUuid& filteredEntityID,
|
bool EntityTreeSendThread::addAncestorsToExtraFlaggedEntities(const QUuid& filteredEntityID,
|
||||||
EntityItem& entityItem, EntityNodeData& nodeData) {
|
EntityItem& entityItem, EntityNodeData& nodeData) {
|
||||||
// check if this entity has a parent that is also an entity
|
// check if this entity has a parent that is also an entity
|
||||||
|
@ -129,4 +204,288 @@ bool EntityTreeSendThread::addDescendantsToExtraFlaggedEntities(const QUuid& fil
|
||||||
return hasNewChild || hasNewDescendants;
|
return hasNewChild || hasNewDescendants;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTreeElementPointer root, int32_t lodLevelOffset, bool usesViewFrustum) {
|
||||||
|
DiffTraversal::Type type = _traversal.prepareNewTraversal(view, root, lodLevelOffset, usesViewFrustum);
|
||||||
|
// there are three types of traversal:
|
||||||
|
//
|
||||||
|
// (1) FirstTime = at login --> find everything in view
|
||||||
|
// (2) Repeat = view hasn't changed --> find what has changed since last complete traversal
|
||||||
|
// (3) Differential = view has changed --> find what has changed or in new view but not old
|
||||||
|
//
|
||||||
|
// The "scanCallback" we provide to the traversal depends on the type:
|
||||||
|
//
|
||||||
|
// The _conicalView is updated here as a cached view approximation used by the lambdas for efficient
|
||||||
|
// computation of entity sorting priorities.
|
||||||
|
//
|
||||||
|
_conicalView.set(_traversal.getCurrentView());
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case DiffTraversal::First:
|
||||||
|
// When we get to a First traversal, clear the _knownState
|
||||||
|
_knownState.clear();
|
||||||
|
if (usesViewFrustum) {
|
||||||
|
float lodScaleFactor = _traversal.getCurrentLODScaleFactor();
|
||||||
|
glm::vec3 viewPosition = _traversal.getCurrentView().getPosition();
|
||||||
|
_traversal.setScanCallback([=](DiffTraversal::VisibleElement& next) {
|
||||||
|
next.element->forEachEntity([=](EntityItemPointer entity) {
|
||||||
|
// Bail early if we've already checked this entity this frame
|
||||||
|
if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bool success = false;
|
||||||
|
AACube cube = entity->getQueryAACube(success);
|
||||||
|
if (success) {
|
||||||
|
if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) {
|
||||||
|
// Check the size of the entity, it's possible that a "too small to see" entity is included in a
|
||||||
|
// larger octree cell because of its position (for example if it crosses the boundary of a cell it
|
||||||
|
// pops to the next higher cell. So we want to check to see that the entity is large enough to be seen
|
||||||
|
// before we consider including it.
|
||||||
|
float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE;
|
||||||
|
float angularDiameter = cube.getScale() / distance;
|
||||||
|
if (angularDiameter > MIN_ENTITY_ANGULAR_DIAMETER * lodScaleFactor) {
|
||||||
|
float priority = _conicalView.computePriority(cube);
|
||||||
|
_sendQueue.push(PrioritizedEntity(entity, priority));
|
||||||
|
_entitiesInQueue.insert(entity.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
|
||||||
|
_entitiesInQueue.insert(entity.get());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
_traversal.setScanCallback([this](DiffTraversal::VisibleElement& next) {
|
||||||
|
next.element->forEachEntity([this](EntityItemPointer entity) {
|
||||||
|
// Bail early if we've already checked this entity this frame
|
||||||
|
if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
|
||||||
|
_entitiesInQueue.insert(entity.get());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DiffTraversal::Repeat:
|
||||||
|
if (usesViewFrustum) {
|
||||||
|
float lodScaleFactor = _traversal.getCurrentLODScaleFactor();
|
||||||
|
glm::vec3 viewPosition = _traversal.getCurrentView().getPosition();
|
||||||
|
_traversal.setScanCallback([=](DiffTraversal::VisibleElement& next) {
|
||||||
|
uint64_t startOfCompletedTraversal = _traversal.getStartOfCompletedTraversal();
|
||||||
|
if (next.element->getLastChangedContent() > startOfCompletedTraversal) {
|
||||||
|
next.element->forEachEntity([=](EntityItemPointer entity) {
|
||||||
|
// Bail early if we've already checked this entity this frame
|
||||||
|
if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto knownTimestamp = _knownState.find(entity.get());
|
||||||
|
if (knownTimestamp == _knownState.end()) {
|
||||||
|
bool success = false;
|
||||||
|
AACube cube = entity->getQueryAACube(success);
|
||||||
|
if (success) {
|
||||||
|
if (next.intersection == ViewFrustum::INSIDE || _traversal.getCurrentView().cubeIntersectsKeyhole(cube)) {
|
||||||
|
// See the DiffTraversal::First case for an explanation of the "entity is too small" check
|
||||||
|
float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE;
|
||||||
|
float angularDiameter = cube.getScale() / distance;
|
||||||
|
if (angularDiameter > MIN_ENTITY_ANGULAR_DIAMETER * lodScaleFactor) {
|
||||||
|
float priority = _conicalView.computePriority(cube);
|
||||||
|
_sendQueue.push(PrioritizedEntity(entity, priority));
|
||||||
|
_entitiesInQueue.insert(entity.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
|
||||||
|
_entitiesInQueue.insert(entity.get());
|
||||||
|
}
|
||||||
|
} else if (entity->getLastEdited() > knownTimestamp->second) {
|
||||||
|
// 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) {
|
||||||
|
_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
|
||||||
|
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 (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) {
|
||||||
|
// See the DiffTraversal::First case for an explanation of the "entity is too small" check
|
||||||
|
float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE;
|
||||||
|
float angularDiameter = cube.getScale() / distance;
|
||||||
|
if (angularDiameter > MIN_ENTITY_ANGULAR_DIAMETER * lodScaleFactor) {
|
||||||
|
if (!_traversal.getCompletedView().cubeIntersectsKeyhole(cube)) {
|
||||||
|
float priority = _conicalView.computePriority(cube);
|
||||||
|
_sendQueue.push(PrioritizedEntity(entity, priority));
|
||||||
|
_entitiesInQueue.insert(entity.get());
|
||||||
|
} else {
|
||||||
|
// If this entity was skipped last time because it was too small, we still need to send it
|
||||||
|
distance = glm::distance(cube.calcCenter(), completedViewPosition) + MIN_VISIBLE_DISTANCE;
|
||||||
|
angularDiameter = cube.getScale() / distance;
|
||||||
|
if (angularDiameter <= MIN_ENTITY_ANGULAR_DIAMETER * completedLODScaleFactor) {
|
||||||
|
// this object was skipped in last completed traversal
|
||||||
|
float priority = _conicalView.computePriority(cube);
|
||||||
|
_sendQueue.push(PrioritizedEntity(entity, priority));
|
||||||
|
_entitiesInQueue.insert(entity.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
|
||||||
|
_entitiesInQueue.insert(entity.get());
|
||||||
|
}
|
||||||
|
} else if (entity->getLastEdited() > 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());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) {
|
||||||
|
if (_sendQueue.empty()) {
|
||||||
|
OctreeServer::trackEncodeTime(OctreeServer::SKIP_TIME);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
quint64 encodeStart = usecTimestampNow();
|
||||||
|
if (!_packetData.hasContent()) {
|
||||||
|
// This is the beginning of a new packet.
|
||||||
|
// We pack minimal data for this to be accepted as an OctreeElement payload for the root element.
|
||||||
|
// The Octree header bytes look like this:
|
||||||
|
//
|
||||||
|
// 0x00 octalcode for root
|
||||||
|
// 0x00 colors (1 bit where recipient should call: child->readElementDataFromBuffer())
|
||||||
|
// 0xXX childrenInTreeMask (when params.includeExistsBits is true: 1 bit where child is existant)
|
||||||
|
// 0x00 childrenInBufferMask (1 bit where recipient should call: child->readElementData() recursively)
|
||||||
|
const uint8_t zeroByte = 0;
|
||||||
|
_packetData.appendValue(zeroByte); // octalcode
|
||||||
|
_packetData.appendValue(zeroByte); // colors
|
||||||
|
if (params.includeExistsBits) {
|
||||||
|
uint8_t childrenExistBits = 0;
|
||||||
|
EntityTreeElementPointer root = std::dynamic_pointer_cast<EntityTreeElement>(_myServer->getOctree()->getRoot());
|
||||||
|
for (int32_t i = 0; i < NUMBER_OF_CHILDREN; ++i) {
|
||||||
|
if (root->getChildAtIndex(i)) {
|
||||||
|
childrenExistBits += (1 << i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_packetData.appendValue(childrenExistBits); // childrenInTreeMask
|
||||||
|
}
|
||||||
|
_packetData.appendValue(zeroByte); // childrenInBufferMask
|
||||||
|
|
||||||
|
// Pack zero for numEntities.
|
||||||
|
// But before we do: grab current byteOffset so we can come back later
|
||||||
|
// and update this with the real number.
|
||||||
|
_numEntities = 0;
|
||||||
|
_numEntitiesOffset = _packetData.getUncompressedByteOffset();
|
||||||
|
_packetData.appendValue(_numEntities);
|
||||||
|
}
|
||||||
|
|
||||||
|
LevelDetails entitiesLevel = _packetData.startLevel();
|
||||||
|
uint64_t sendTime = usecTimestampNow();
|
||||||
|
auto nodeData = static_cast<OctreeQueryNode*>(params.nodeData);
|
||||||
|
nodeData->stats.encodeStarted();
|
||||||
|
while(!_sendQueue.empty()) {
|
||||||
|
PrioritizedEntity queuedItem = _sendQueue.top();
|
||||||
|
EntityItemPointer entity = queuedItem.getEntity();
|
||||||
|
if (entity) {
|
||||||
|
// Only send entities that match the jsonFilters, but keep track of everything we've tried to send so we don't try to send it again
|
||||||
|
if (entity->matchesJSONFilters(jsonFilters)) {
|
||||||
|
OctreeElement::AppendState appendEntityState = entity->appendEntityData(&_packetData, params, _extraEncodeData);
|
||||||
|
|
||||||
|
if (appendEntityState != OctreeElement::COMPLETED) {
|
||||||
|
if (appendEntityState == OctreeElement::PARTIAL) {
|
||||||
|
++_numEntities;
|
||||||
|
}
|
||||||
|
params.stopReason = EncodeBitstreamParams::DIDNT_FIT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++_numEntities;
|
||||||
|
}
|
||||||
|
if (queuedItem.shouldForceRemove()) {
|
||||||
|
_knownState.erase(entity.get());
|
||||||
|
} else {
|
||||||
|
_knownState[entity.get()] = sendTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_sendQueue.pop();
|
||||||
|
_entitiesInQueue.erase(entity.get());
|
||||||
|
}
|
||||||
|
nodeData->stats.encodeStopped();
|
||||||
|
if (_sendQueue.empty()) {
|
||||||
|
assert(_entitiesInQueue.empty());
|
||||||
|
params.stopReason = EncodeBitstreamParams::FINISHED;
|
||||||
|
_extraEncodeData->entities.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_numEntities == 0) {
|
||||||
|
_packetData.discardLevel(entitiesLevel);
|
||||||
|
OctreeServer::trackEncodeTime((float)(usecTimestampNow() - encodeStart));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_packetData.endLevel(entitiesLevel);
|
||||||
|
_packetData.updatePriorBytes(_numEntitiesOffset, (const unsigned char*)&_numEntities, sizeof(_numEntities));
|
||||||
|
OctreeServer::trackEncodeTime((float)(usecTimestampNow() - encodeStart));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EntityTreeSendThread::editingEntityPointer(const EntityItemPointer& entity) {
|
||||||
|
if (entity) {
|
||||||
|
if (_entitiesInQueue.find(entity.get()) == _entitiesInQueue.end() && _knownState.find(entity.get()) != _knownState.end()) {
|
||||||
|
bool success = false;
|
||||||
|
AACube cube = entity->getQueryAACube(success);
|
||||||
|
if (success) {
|
||||||
|
// We can force a removal from _knownState if the current view is used and entity is out of view
|
||||||
|
if (_traversal.doesCurrentUseViewFrustum() && !_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) {
|
||||||
|
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::FORCE_REMOVE, true));
|
||||||
|
_entitiesInQueue.insert(entity.get());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY, true));
|
||||||
|
_entitiesInQueue.insert(entity.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EntityTreeSendThread::deletingEntityPointer(EntityItem* entity) {
|
||||||
|
_knownState.erase(entity);
|
||||||
|
}
|
||||||
|
|
|
@ -12,24 +12,55 @@
|
||||||
#ifndef hifi_EntityTreeSendThread_h
|
#ifndef hifi_EntityTreeSendThread_h
|
||||||
#define hifi_EntityTreeSendThread_h
|
#define hifi_EntityTreeSendThread_h
|
||||||
|
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
#include "../octree/OctreeSendThread.h"
|
#include "../octree/OctreeSendThread.h"
|
||||||
|
|
||||||
|
#include <DiffTraversal.h>
|
||||||
|
|
||||||
|
#include "EntityPriorityQueue.h"
|
||||||
|
|
||||||
class EntityNodeData;
|
class EntityNodeData;
|
||||||
class EntityItem;
|
class EntityItem;
|
||||||
|
|
||||||
class EntityTreeSendThread : public OctreeSendThread {
|
class EntityTreeSendThread : public OctreeSendThread {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
EntityTreeSendThread(OctreeServer* myServer, const SharedNodePointer& node) : OctreeSendThread(myServer, node) {};
|
EntityTreeSendThread(OctreeServer* myServer, const SharedNodePointer& node);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void preDistributionProcessing() override;
|
void traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData,
|
||||||
|
bool viewFrustumChanged, bool isFullScene) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// the following two methods return booleans to indicate if any extra flagged entities were new additions to set
|
// the following two methods return booleans to indicate if any extra flagged entities were new additions to set
|
||||||
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, bool usesViewFrustum);
|
||||||
|
bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) override;
|
||||||
|
|
||||||
|
void preDistributionProcessing() override;
|
||||||
|
bool hasSomethingToSend(OctreeQueryNode* nodeData) override { return !_sendQueue.empty(); }
|
||||||
|
bool shouldStartNewTraversal(OctreeQueryNode* nodeData, bool viewFrustumChanged) override { return viewFrustumChanged || _traversal.finished(); }
|
||||||
|
void preStartNewScene(OctreeQueryNode* nodeData, bool isFullScene) override {};
|
||||||
|
bool shouldTraverseAndSend(OctreeQueryNode* nodeData) override { return true; }
|
||||||
|
|
||||||
|
DiffTraversal _traversal;
|
||||||
|
EntityPriorityQueue _sendQueue;
|
||||||
|
std::unordered_set<EntityItem*> _entitiesInQueue;
|
||||||
|
std::unordered_map<EntityItem*, uint64_t> _knownState;
|
||||||
|
ConicalView _conicalView; // cached optimized view for fast priority calculations
|
||||||
|
|
||||||
|
// packet construction stuff
|
||||||
|
EntityTreeElementExtraEncodeDataPointer _extraEncodeData { new EntityTreeElementExtraEncodeData() };
|
||||||
|
int32_t _numEntitiesOffset { 0 };
|
||||||
|
uint16_t _numEntities { 0 };
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void editingEntityPointer(const EntityItemPointer& entity);
|
||||||
|
void deletingEntityPointer(EntityItem* entity);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_EntityTreeSendThread_h
|
#endif // hifi_EntityTreeSendThread_h
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
#include <udt/PacketHeaders.h>
|
#include <udt/PacketHeaders.h>
|
||||||
#include <PerfStat.h>
|
#include <PerfStat.h>
|
||||||
|
|
||||||
#include "OctreeQueryNode.h"
|
|
||||||
#include "OctreeSendThread.h"
|
#include "OctreeSendThread.h"
|
||||||
#include "OctreeServer.h"
|
#include "OctreeServer.h"
|
||||||
#include "OctreeServerConsts.h"
|
#include "OctreeServerConsts.h"
|
||||||
|
@ -27,8 +26,8 @@ quint64 startSceneSleepTime = 0;
|
||||||
quint64 endSceneSleepTime = 0;
|
quint64 endSceneSleepTime = 0;
|
||||||
|
|
||||||
OctreeSendThread::OctreeSendThread(OctreeServer* myServer, const SharedNodePointer& node) :
|
OctreeSendThread::OctreeSendThread(OctreeServer* myServer, const SharedNodePointer& node) :
|
||||||
_myServer(myServer),
|
|
||||||
_node(node),
|
_node(node),
|
||||||
|
_myServer(myServer),
|
||||||
_nodeUuid(node->getUUID())
|
_nodeUuid(node->getUUID())
|
||||||
{
|
{
|
||||||
QString safeServerName("Octree");
|
QString safeServerName("Octree");
|
||||||
|
@ -48,7 +47,7 @@ OctreeSendThread::OctreeSendThread(OctreeServer* myServer, const SharedNodePoint
|
||||||
|
|
||||||
OctreeSendThread::~OctreeSendThread() {
|
OctreeSendThread::~OctreeSendThread() {
|
||||||
setIsShuttingDown();
|
setIsShuttingDown();
|
||||||
|
|
||||||
QString safeServerName("Octree");
|
QString safeServerName("Octree");
|
||||||
if (_myServer) {
|
if (_myServer) {
|
||||||
safeServerName = _myServer->getMyServerName();
|
safeServerName = _myServer->getMyServerName();
|
||||||
|
@ -301,9 +300,25 @@ int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode*
|
||||||
return numPackets;
|
return numPackets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OctreeSendThread::preStartNewScene(OctreeQueryNode* nodeData, bool isFullScene) {
|
||||||
|
// If we're starting a full scene, then definitely we want to empty the elementBag
|
||||||
|
if (isFullScene) {
|
||||||
|
nodeData->elementBag.deleteAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the start of "resending" the scene.
|
||||||
|
bool dontRestartSceneOnMove = false; // this is experimental
|
||||||
|
if (dontRestartSceneOnMove) {
|
||||||
|
if (nodeData->elementBag.isEmpty()) {
|
||||||
|
nodeData->elementBag.insert(_myServer->getOctree()->getRoot());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
nodeData->elementBag.insert(_myServer->getOctree()->getRoot());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Version of octree element distributor that sends the deepest LOD level at once
|
/// Version of octree element distributor that sends the deepest LOD level at once
|
||||||
int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged) {
|
int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged) {
|
||||||
|
|
||||||
OctreeServer::didPacketDistributor(this);
|
OctreeServer::didPacketDistributor(this);
|
||||||
|
|
||||||
// if shutting down, exit early
|
// if shutting down, exit early
|
||||||
|
@ -311,7 +326,7 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nodeData->elementBag.isEmpty()) {
|
if (shouldStartNewTraversal(nodeData, viewFrustumChanged)) {
|
||||||
// if we're about to do a fresh pass,
|
// if we're about to do a fresh pass,
|
||||||
// give our pre-distribution processing a chance to do what it needs
|
// give our pre-distribution processing a chance to do what it needs
|
||||||
preDistributionProcessing();
|
preDistributionProcessing();
|
||||||
|
@ -345,7 +360,7 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
|
||||||
|
|
||||||
// If the current view frustum has changed OR we have nothing to send, then search against
|
// If the current view frustum has changed OR we have nothing to send, then search against
|
||||||
// the current view frustum for things to send.
|
// the current view frustum for things to send.
|
||||||
if (viewFrustumChanged || nodeData->elementBag.isEmpty()) {
|
if (shouldStartNewTraversal(nodeData, viewFrustumChanged)) {
|
||||||
|
|
||||||
// if our view has changed, we need to reset these things...
|
// if our view has changed, we need to reset these things...
|
||||||
if (viewFrustumChanged) {
|
if (viewFrustumChanged) {
|
||||||
|
@ -367,11 +382,6 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
|
||||||
|
|
||||||
_packetsSentThisInterval += handlePacketSend(node, nodeData, isFullScene);
|
_packetsSentThisInterval += handlePacketSend(node, nodeData, isFullScene);
|
||||||
|
|
||||||
// If we're starting a full scene, then definitely we want to empty the elementBag
|
|
||||||
if (isFullScene) {
|
|
||||||
nodeData->elementBag.deleteAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: add these to stats page
|
// TODO: add these to stats page
|
||||||
//::startSceneSleepTime = _usleepTime;
|
//::startSceneSleepTime = _usleepTime;
|
||||||
|
|
||||||
|
@ -380,19 +390,11 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
|
||||||
nodeData->stats.sceneStarted(isFullScene, viewFrustumChanged,
|
nodeData->stats.sceneStarted(isFullScene, viewFrustumChanged,
|
||||||
_myServer->getOctree()->getRoot(), _myServer->getJurisdiction());
|
_myServer->getOctree()->getRoot(), _myServer->getJurisdiction());
|
||||||
|
|
||||||
// This is the start of "resending" the scene.
|
preStartNewScene(nodeData, isFullScene);
|
||||||
bool dontRestartSceneOnMove = false; // this is experimental
|
|
||||||
if (dontRestartSceneOnMove) {
|
|
||||||
if (nodeData->elementBag.isEmpty()) {
|
|
||||||
nodeData->elementBag.insert(_myServer->getOctree()->getRoot());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
nodeData->elementBag.insert(_myServer->getOctree()->getRoot());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have something in our elementBag, then turn them into packets and send them out...
|
// If we have something in our elementBag, then turn them into packets and send them out...
|
||||||
if (!nodeData->elementBag.isEmpty()) {
|
if (shouldTraverseAndSend(nodeData)) {
|
||||||
quint64 start = usecTimestampNow();
|
quint64 start = usecTimestampNow();
|
||||||
|
|
||||||
traverseTreeAndSendContents(node, nodeData, viewFrustumChanged, isFullScene);
|
traverseTreeAndSendContents(node, nodeData, viewFrustumChanged, isFullScene);
|
||||||
|
@ -441,7 +443,7 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
|
||||||
|
|
||||||
// if after sending packets we've emptied our bag, then we want to remember that we've sent all
|
// if after sending packets we've emptied our bag, then we want to remember that we've sent all
|
||||||
// the octree elements from the current view frustum
|
// the octree elements from the current view frustum
|
||||||
if (nodeData->elementBag.isEmpty()) {
|
if (!hasSomethingToSend(nodeData)) {
|
||||||
nodeData->updateLastKnownViewFrustum();
|
nodeData->updateLastKnownViewFrustum();
|
||||||
nodeData->setViewSent(true);
|
nodeData->setViewSent(true);
|
||||||
|
|
||||||
|
@ -458,7 +460,7 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
|
||||||
return _truePacketsSent;
|
return _truePacketsSent;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OctreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params) {
|
bool OctreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) {
|
||||||
bool somethingToSend = false;
|
bool somethingToSend = false;
|
||||||
OctreeQueryNode* nodeData = static_cast<OctreeQueryNode*>(params.nodeData);
|
OctreeQueryNode* nodeData = static_cast<OctreeQueryNode*>(params.nodeData);
|
||||||
if (!nodeData->elementBag.isEmpty()) {
|
if (!nodeData->elementBag.isEmpty()) {
|
||||||
|
@ -502,8 +504,7 @@ void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, Octre
|
||||||
EncodeBitstreamParams params(INT_MAX, WANT_EXISTS_BITS, DONT_CHOP,
|
EncodeBitstreamParams params(INT_MAX, WANT_EXISTS_BITS, DONT_CHOP,
|
||||||
viewFrustumChanged, boundaryLevelAdjust, octreeSizeScale,
|
viewFrustumChanged, boundaryLevelAdjust, octreeSizeScale,
|
||||||
isFullScene, _myServer->getJurisdiction(), nodeData);
|
isFullScene, _myServer->getJurisdiction(), nodeData);
|
||||||
// Our trackSend() function is implemented by the server subclass, and will be called back
|
// Our trackSend() function is implemented by the server subclass, and will be called back as new entities/data elements are sent
|
||||||
// during the encodeTreeBitstream() as new entities/data elements are sent
|
|
||||||
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);
|
||||||
};
|
};
|
||||||
|
@ -513,7 +514,7 @@ void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, Octre
|
||||||
}
|
}
|
||||||
|
|
||||||
bool somethingToSend = true; // assume we have something
|
bool somethingToSend = true; // assume we have something
|
||||||
bool bagHadSomething = !nodeData->elementBag.isEmpty();
|
bool hadSomething = hasSomethingToSend(nodeData);
|
||||||
while (somethingToSend && _packetsSentThisInterval < maxPacketsPerInterval && !nodeData->isShuttingDown()) {
|
while (somethingToSend && _packetsSentThisInterval < maxPacketsPerInterval && !nodeData->isShuttingDown()) {
|
||||||
float compressAndWriteElapsedUsec = OctreeServer::SKIP_TIME;
|
float compressAndWriteElapsedUsec = OctreeServer::SKIP_TIME;
|
||||||
float packetSendingElapsedUsec = OctreeServer::SKIP_TIME;
|
float packetSendingElapsedUsec = OctreeServer::SKIP_TIME;
|
||||||
|
@ -523,7 +524,7 @@ void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, Octre
|
||||||
bool lastNodeDidntFit = false; // assume each node fits
|
bool lastNodeDidntFit = false; // assume each node fits
|
||||||
params.stopReason = EncodeBitstreamParams::UNKNOWN; // reset params.stopReason before traversal
|
params.stopReason = EncodeBitstreamParams::UNKNOWN; // reset params.stopReason before traversal
|
||||||
|
|
||||||
somethingToSend = traverseTreeAndBuildNextPacketPayload(params);
|
somethingToSend = traverseTreeAndBuildNextPacketPayload(params, nodeData->getJSONParameters());
|
||||||
|
|
||||||
if (params.stopReason == EncodeBitstreamParams::DIDNT_FIT) {
|
if (params.stopReason == EncodeBitstreamParams::DIDNT_FIT) {
|
||||||
lastNodeDidntFit = true;
|
lastNodeDidntFit = true;
|
||||||
|
@ -531,7 +532,7 @@ void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, Octre
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the bag had contents but is now empty then we know we've sent the entire scene.
|
// If the bag had contents but is now empty then we know we've sent the entire scene.
|
||||||
bool completedScene = bagHadSomething && nodeData->elementBag.isEmpty();
|
bool completedScene = hadSomething && nodeData->elementBag.isEmpty();
|
||||||
if (completedScene || lastNodeDidntFit) {
|
if (completedScene || lastNodeDidntFit) {
|
||||||
// we probably want to flush what has accumulated in nodeData but:
|
// we probably want to flush what has accumulated in nodeData but:
|
||||||
// do we have more data to send? and is there room?
|
// do we have more data to send? and is there room?
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
#include <GenericThread.h>
|
#include <GenericThread.h>
|
||||||
#include <Node.h>
|
#include <Node.h>
|
||||||
#include <OctreePacketData.h>
|
#include <OctreePacketData.h>
|
||||||
|
#include "OctreeQueryNode.h"
|
||||||
|
|
||||||
class OctreeQueryNode;
|
class OctreeQueryNode;
|
||||||
class OctreeServer;
|
class OctreeServer;
|
||||||
|
@ -51,24 +52,27 @@ protected:
|
||||||
/// Implements generic processing behavior for this thread.
|
/// Implements generic processing behavior for this thread.
|
||||||
virtual bool process() override;
|
virtual bool process() override;
|
||||||
|
|
||||||
/// Called before a packetDistributor pass to allow for pre-distribution processing
|
|
||||||
virtual void preDistributionProcessing() {};
|
|
||||||
virtual void traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData,
|
virtual void traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData,
|
||||||
bool viewFrustumChanged, bool isFullScene);
|
bool viewFrustumChanged, bool isFullScene);
|
||||||
virtual bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params);
|
virtual bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters);
|
||||||
|
|
||||||
OctreeServer* _myServer { nullptr };
|
OctreePacketData _packetData;
|
||||||
QWeakPointer<Node> _node;
|
QWeakPointer<Node> _node;
|
||||||
|
OctreeServer* _myServer { nullptr };
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/// Called before a packetDistributor pass to allow for pre-distribution processing
|
||||||
|
virtual void preDistributionProcessing() {};
|
||||||
int handlePacketSend(SharedNodePointer node, OctreeQueryNode* nodeData, bool dontSuppressDuplicate = false);
|
int handlePacketSend(SharedNodePointer node, OctreeQueryNode* nodeData, bool dontSuppressDuplicate = false);
|
||||||
int packetDistributor(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged);
|
int packetDistributor(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged);
|
||||||
|
|
||||||
|
virtual bool hasSomethingToSend(OctreeQueryNode* nodeData) { return !nodeData->elementBag.isEmpty(); }
|
||||||
|
virtual bool shouldStartNewTraversal(OctreeQueryNode* nodeData, bool viewFrustumChanged) { return viewFrustumChanged || !hasSomethingToSend(nodeData); }
|
||||||
|
virtual void preStartNewScene(OctreeQueryNode* nodeData, bool isFullScene);
|
||||||
|
virtual bool shouldTraverseAndSend(OctreeQueryNode* nodeData) { return hasSomethingToSend(nodeData); }
|
||||||
|
|
||||||
QUuid _nodeUuid;
|
QUuid _nodeUuid;
|
||||||
|
|
||||||
OctreePacketData _packetData;
|
|
||||||
|
|
||||||
int _truePacketsSent { 0 }; // available for debug stats
|
int _truePacketsSent { 0 }; // available for debug stats
|
||||||
int _trueBytesSent { 0 }; // available for debug stats
|
int _trueBytesSent { 0 }; // available for debug stats
|
||||||
int _packetsSentThisInterval { 0 }; // used for bandwidth throttle condition
|
int _packetsSentThisInterval { 0 }; // used for bandwidth throttle condition
|
||||||
|
|
|
@ -60,6 +60,8 @@ int OctreeServer::_longTreeWait = 0;
|
||||||
int OctreeServer::_shortTreeWait = 0;
|
int OctreeServer::_shortTreeWait = 0;
|
||||||
int OctreeServer::_noTreeWait = 0;
|
int OctreeServer::_noTreeWait = 0;
|
||||||
|
|
||||||
|
SimpleMovingAverage OctreeServer::_averageTreeTraverseTime(MOVING_AVERAGE_SAMPLE_COUNTS);
|
||||||
|
|
||||||
SimpleMovingAverage OctreeServer::_averageNodeWaitTime(MOVING_AVERAGE_SAMPLE_COUNTS);
|
SimpleMovingAverage OctreeServer::_averageNodeWaitTime(MOVING_AVERAGE_SAMPLE_COUNTS);
|
||||||
|
|
||||||
SimpleMovingAverage OctreeServer::_averageCompressAndWriteTime(MOVING_AVERAGE_SAMPLE_COUNTS);
|
SimpleMovingAverage OctreeServer::_averageCompressAndWriteTime(MOVING_AVERAGE_SAMPLE_COUNTS);
|
||||||
|
@ -106,6 +108,8 @@ void OctreeServer::resetSendingStats() {
|
||||||
_shortTreeWait = 0;
|
_shortTreeWait = 0;
|
||||||
_noTreeWait = 0;
|
_noTreeWait = 0;
|
||||||
|
|
||||||
|
_averageTreeTraverseTime.reset();
|
||||||
|
|
||||||
_averageNodeWaitTime.reset();
|
_averageNodeWaitTime.reset();
|
||||||
|
|
||||||
_averageCompressAndWriteTime.reset();
|
_averageCompressAndWriteTime.reset();
|
||||||
|
@ -522,6 +526,10 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
||||||
(double)_averageTreeExtraLongWaitTime.getAverage(),
|
(double)_averageTreeExtraLongWaitTime.getAverage(),
|
||||||
(double)(extraLongVsTotal * AS_PERCENT), _extraLongTreeWait);
|
(double)(extraLongVsTotal * AS_PERCENT), _extraLongTreeWait);
|
||||||
|
|
||||||
|
// traverse
|
||||||
|
float averageTreeTraverseTime = getAverageTreeTraverseTime();
|
||||||
|
statsString += QString().sprintf(" Average tree traverse time: %9.2f usecs\r\n\r\n", (double)averageTreeTraverseTime);
|
||||||
|
|
||||||
// encode
|
// encode
|
||||||
float averageEncodeTime = getAverageEncodeTime();
|
float averageEncodeTime = getAverageEncodeTime();
|
||||||
statsString += QString().sprintf(" Average encode time: %9.2f usecs\r\n", (double)averageEncodeTime);
|
statsString += QString().sprintf(" Average encode time: %9.2f usecs\r\n", (double)averageEncodeTime);
|
||||||
|
@ -883,7 +891,7 @@ OctreeServer::UniqueSendThread OctreeServer::newSendThread(const SharedNodePoint
|
||||||
|
|
||||||
OctreeServer::UniqueSendThread OctreeServer::createSendThread(const SharedNodePointer& node) {
|
OctreeServer::UniqueSendThread OctreeServer::createSendThread(const SharedNodePointer& node) {
|
||||||
auto sendThread = newSendThread(node);
|
auto sendThread = newSendThread(node);
|
||||||
|
|
||||||
// we want to be notified when the thread finishes
|
// we want to be notified when the thread finishes
|
||||||
connect(sendThread.get(), &GenericThread::finished, this, &OctreeServer::removeSendThread);
|
connect(sendThread.get(), &GenericThread::finished, this, &OctreeServer::removeSendThread);
|
||||||
sendThread->initialize(true);
|
sendThread->initialize(true);
|
||||||
|
@ -905,13 +913,13 @@ void OctreeServer::handleOctreeQueryPacket(QSharedPointer<ReceivedMessage> messa
|
||||||
// need to make sure we have it in our nodeList.
|
// need to make sure we have it in our nodeList.
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
nodeList->updateNodeWithDataFromPacket(message, senderNode);
|
nodeList->updateNodeWithDataFromPacket(message, senderNode);
|
||||||
|
|
||||||
auto it = _sendThreads.find(senderNode->getUUID());
|
auto it = _sendThreads.find(senderNode->getUUID());
|
||||||
if (it == _sendThreads.end()) {
|
if (it == _sendThreads.end()) {
|
||||||
_sendThreads.emplace(senderNode->getUUID(), createSendThread(senderNode));
|
_sendThreads.emplace(senderNode->getUUID(), createSendThread(senderNode));
|
||||||
} else if (it->second->isShuttingDown()) {
|
} else if (it->second->isShuttingDown()) {
|
||||||
_sendThreads.erase(it); // Remove right away and wait on thread to be
|
_sendThreads.erase(it); // Remove right away and wait on thread to be
|
||||||
|
|
||||||
_sendThreads.emplace(senderNode->getUUID(), createSendThread(senderNode));
|
_sendThreads.emplace(senderNode->getUUID(), createSendThread(senderNode));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1085,7 +1093,7 @@ void OctreeServer::readConfiguration() {
|
||||||
if (getPayload().size() > 0) {
|
if (getPayload().size() > 0) {
|
||||||
parsePayload();
|
parsePayload();
|
||||||
}
|
}
|
||||||
|
|
||||||
const QJsonObject& settingsObject = DependencyManager::get<NodeList>()->getDomainHandler().getSettingsObject();
|
const QJsonObject& settingsObject = DependencyManager::get<NodeList>()->getDomainHandler().getSettingsObject();
|
||||||
|
|
||||||
QString settingsKey = getMyDomainSettingsKey();
|
QString settingsKey = getMyDomainSettingsKey();
|
||||||
|
@ -1212,9 +1220,9 @@ void OctreeServer::run() {
|
||||||
OctreeElement::resetPopulationStatistics();
|
OctreeElement::resetPopulationStatistics();
|
||||||
_tree = createTree();
|
_tree = createTree();
|
||||||
_tree->setIsServer(true);
|
_tree->setIsServer(true);
|
||||||
|
|
||||||
qDebug() << "Waiting for connection to domain to request settings from domain-server.";
|
qDebug() << "Waiting for connection to domain to request settings from domain-server.";
|
||||||
|
|
||||||
// wait until we have the domain-server settings, otherwise we bail
|
// wait until we have the domain-server settings, otherwise we bail
|
||||||
DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler();
|
DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler();
|
||||||
connect(&domainHandler, &DomainHandler::settingsReceived, this, &OctreeServer::domainSettingsRequestComplete);
|
connect(&domainHandler, &DomainHandler::settingsReceived, this, &OctreeServer::domainSettingsRequestComplete);
|
||||||
|
@ -1225,9 +1233,9 @@ void OctreeServer::run() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void OctreeServer::domainSettingsRequestComplete() {
|
void OctreeServer::domainSettingsRequestComplete() {
|
||||||
|
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
|
|
||||||
// we need to ask the DS about agents so we can ping/reply with them
|
// we need to ask the DS about agents so we can ping/reply with them
|
||||||
nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer });
|
nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer });
|
||||||
|
|
||||||
|
@ -1237,26 +1245,26 @@ void OctreeServer::domainSettingsRequestComplete() {
|
||||||
packetReceiver.registerListener(PacketType::JurisdictionRequest, this, "handleJurisdictionRequestPacket");
|
packetReceiver.registerListener(PacketType::JurisdictionRequest, this, "handleJurisdictionRequestPacket");
|
||||||
packetReceiver.registerListener(PacketType::OctreeFileReplacement, this, "handleOctreeFileReplacement");
|
packetReceiver.registerListener(PacketType::OctreeFileReplacement, this, "handleOctreeFileReplacement");
|
||||||
packetReceiver.registerListener(PacketType::OctreeFileReplacementFromUrl, this, "handleOctreeFileReplacementFromURL");
|
packetReceiver.registerListener(PacketType::OctreeFileReplacementFromUrl, this, "handleOctreeFileReplacementFromURL");
|
||||||
|
|
||||||
readConfiguration();
|
readConfiguration();
|
||||||
|
|
||||||
beforeRun(); // after payload has been processed
|
beforeRun(); // after payload has been processed
|
||||||
|
|
||||||
connect(nodeList.data(), SIGNAL(nodeAdded(SharedNodePointer)), SLOT(nodeAdded(SharedNodePointer)));
|
connect(nodeList.data(), SIGNAL(nodeAdded(SharedNodePointer)), SLOT(nodeAdded(SharedNodePointer)));
|
||||||
connect(nodeList.data(), SIGNAL(nodeKilled(SharedNodePointer)), SLOT(nodeKilled(SharedNodePointer)));
|
connect(nodeList.data(), SIGNAL(nodeKilled(SharedNodePointer)), SLOT(nodeKilled(SharedNodePointer)));
|
||||||
|
|
||||||
#ifndef WIN32
|
#ifndef WIN32
|
||||||
setvbuf(stdout, NULL, _IOLBF, 0);
|
setvbuf(stdout, NULL, _IOLBF, 0);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
nodeList->linkedDataCreateCallback = [this](Node* node) {
|
nodeList->linkedDataCreateCallback = [this](Node* node) {
|
||||||
auto queryNodeData = createOctreeQueryNode();
|
auto queryNodeData = createOctreeQueryNode();
|
||||||
queryNodeData->init();
|
queryNodeData->init();
|
||||||
node->setLinkedData(std::move(queryNodeData));
|
node->setLinkedData(std::move(queryNodeData));
|
||||||
};
|
};
|
||||||
|
|
||||||
srand((unsigned)time(0));
|
srand((unsigned)time(0));
|
||||||
|
|
||||||
// if we want Persistence, set up the local file and persist thread
|
// if we want Persistence, set up the local file and persist thread
|
||||||
if (_wantPersist) {
|
if (_wantPersist) {
|
||||||
// If persist filename does not exist, let's see if there is one beside the application binary
|
// If persist filename does not exist, let's see if there is one beside the application binary
|
||||||
|
@ -1351,24 +1359,24 @@ void OctreeServer::domainSettingsRequestComplete() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
qDebug() << "Backups will be stored in: " << _backupDirectoryPath;
|
qDebug() << "Backups will be stored in: " << _backupDirectoryPath;
|
||||||
|
|
||||||
// now set up PersistThread
|
// now set up PersistThread
|
||||||
_persistThread = new OctreePersistThread(_tree, _persistAbsoluteFilePath, _backupDirectoryPath, _persistInterval,
|
_persistThread = new OctreePersistThread(_tree, _persistAbsoluteFilePath, _backupDirectoryPath, _persistInterval,
|
||||||
_wantBackup, _settings, _debugTimestampNow, _persistAsFileType);
|
_wantBackup, _settings, _debugTimestampNow, _persistAsFileType);
|
||||||
_persistThread->initialize(true);
|
_persistThread->initialize(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// set up our jurisdiction broadcaster...
|
// set up our jurisdiction broadcaster...
|
||||||
if (_jurisdiction) {
|
if (_jurisdiction) {
|
||||||
_jurisdiction->setNodeType(getMyNodeType());
|
_jurisdiction->setNodeType(getMyNodeType());
|
||||||
}
|
}
|
||||||
_jurisdictionSender = new JurisdictionSender(_jurisdiction, getMyNodeType());
|
_jurisdictionSender = new JurisdictionSender(_jurisdiction, getMyNodeType());
|
||||||
_jurisdictionSender->initialize(true);
|
_jurisdictionSender->initialize(true);
|
||||||
|
|
||||||
// set up our OctreeServerPacketProcessor
|
// set up our OctreeServerPacketProcessor
|
||||||
_octreeInboundPacketProcessor = new OctreeInboundPacketProcessor(this);
|
_octreeInboundPacketProcessor = new OctreeInboundPacketProcessor(this);
|
||||||
_octreeInboundPacketProcessor->initialize(true);
|
_octreeInboundPacketProcessor->initialize(true);
|
||||||
|
|
||||||
// Convert now to tm struct for local timezone
|
// Convert now to tm struct for local timezone
|
||||||
tm* localtm = localtime(&_started);
|
tm* localtm = localtime(&_started);
|
||||||
const int MAX_TIME_LENGTH = 128;
|
const int MAX_TIME_LENGTH = 128;
|
||||||
|
@ -1380,7 +1388,7 @@ void OctreeServer::domainSettingsRequestComplete() {
|
||||||
if (gmtm) {
|
if (gmtm) {
|
||||||
strftime(utcBuffer, MAX_TIME_LENGTH, " [%m/%d/%Y %X UTC]", gmtm);
|
strftime(utcBuffer, MAX_TIME_LENGTH, " [%m/%d/%Y %X UTC]", gmtm);
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << "Now running... started at: " << localBuffer << utcBuffer;
|
qDebug() << "Now running... started at: " << localBuffer << utcBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1391,7 +1399,7 @@ void OctreeServer::nodeAdded(SharedNodePointer node) {
|
||||||
|
|
||||||
void OctreeServer::nodeKilled(SharedNodePointer node) {
|
void OctreeServer::nodeKilled(SharedNodePointer node) {
|
||||||
quint64 start = usecTimestampNow();
|
quint64 start = usecTimestampNow();
|
||||||
|
|
||||||
// Shutdown send thread
|
// Shutdown send thread
|
||||||
auto it = _sendThreads.find(node->getUUID());
|
auto it = _sendThreads.find(node->getUUID());
|
||||||
if (it != _sendThreads.end()) {
|
if (it != _sendThreads.end()) {
|
||||||
|
@ -1437,13 +1445,13 @@ void OctreeServer::aboutToFinish() {
|
||||||
if (_jurisdictionSender) {
|
if (_jurisdictionSender) {
|
||||||
_jurisdictionSender->terminating();
|
_jurisdictionSender->terminating();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shut down all the send threads
|
// Shut down all the send threads
|
||||||
for (auto& it : _sendThreads) {
|
for (auto& it : _sendThreads) {
|
||||||
auto& sendThread = *it.second;
|
auto& sendThread = *it.second;
|
||||||
sendThread.setIsShuttingDown();
|
sendThread.setIsShuttingDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear will destruct all the unique_ptr to OctreeSendThreads which will call the GenericThread's dtor
|
// Clear will destruct all the unique_ptr to OctreeSendThreads which will call the GenericThread's dtor
|
||||||
// which waits on the thread to be done before returning
|
// which waits on the thread to be done before returning
|
||||||
_sendThreads.clear(); // Cleans up all the send threads.
|
_sendThreads.clear(); // Cleans up all the send threads.
|
||||||
|
@ -1563,7 +1571,7 @@ void OctreeServer::sendStatsPacket() {
|
||||||
threadsStats["2. packetDistributor"] = (double)howManyThreadsDidPacketDistributor(oneSecondAgo);
|
threadsStats["2. packetDistributor"] = (double)howManyThreadsDidPacketDistributor(oneSecondAgo);
|
||||||
threadsStats["3. handlePacektSend"] = (double)howManyThreadsDidHandlePacketSend(oneSecondAgo);
|
threadsStats["3. handlePacektSend"] = (double)howManyThreadsDidHandlePacketSend(oneSecondAgo);
|
||||||
threadsStats["4. writeDatagram"] = (double)howManyThreadsDidCallWriteDatagram(oneSecondAgo);
|
threadsStats["4. writeDatagram"] = (double)howManyThreadsDidCallWriteDatagram(oneSecondAgo);
|
||||||
|
|
||||||
QJsonObject statsArray1;
|
QJsonObject statsArray1;
|
||||||
statsArray1["1. configuration"] = getConfiguration();
|
statsArray1["1. configuration"] = getConfiguration();
|
||||||
statsArray1["2. detailed_stats_url"] = getStatusLink();
|
statsArray1["2. detailed_stats_url"] = getStatusLink();
|
||||||
|
@ -1571,13 +1579,13 @@ void OctreeServer::sendStatsPacket() {
|
||||||
statsArray1["4. persistFileLoadTime"] = getFileLoadTime();
|
statsArray1["4. persistFileLoadTime"] = getFileLoadTime();
|
||||||
statsArray1["5. clients"] = getCurrentClientCount();
|
statsArray1["5. clients"] = getCurrentClientCount();
|
||||||
statsArray1["6. threads"] = threadsStats;
|
statsArray1["6. threads"] = threadsStats;
|
||||||
|
|
||||||
// Octree Stats
|
// Octree Stats
|
||||||
QJsonObject octreeStats;
|
QJsonObject octreeStats;
|
||||||
octreeStats["1. elementCount"] = (double)OctreeElement::getNodeCount();
|
octreeStats["1. elementCount"] = (double)OctreeElement::getNodeCount();
|
||||||
octreeStats["2. internalElementCount"] = (double)OctreeElement::getInternalNodeCount();
|
octreeStats["2. internalElementCount"] = (double)OctreeElement::getInternalNodeCount();
|
||||||
octreeStats["3. leafElementCount"] = (double)OctreeElement::getLeafNodeCount();
|
octreeStats["3. leafElementCount"] = (double)OctreeElement::getLeafNodeCount();
|
||||||
|
|
||||||
// Stats Object 2
|
// Stats Object 2
|
||||||
QJsonObject dataObject1;
|
QJsonObject dataObject1;
|
||||||
dataObject1["1. totalPackets"] = (double)OctreeSendThread::_totalPackets;
|
dataObject1["1. totalPackets"] = (double)OctreeSendThread::_totalPackets;
|
||||||
|
@ -1590,12 +1598,12 @@ void OctreeServer::sendStatsPacket() {
|
||||||
QJsonObject timingArray1;
|
QJsonObject timingArray1;
|
||||||
timingArray1["1. avgLoopTime"] = getAverageLoopTime();
|
timingArray1["1. avgLoopTime"] = getAverageLoopTime();
|
||||||
timingArray1["2. avgInsideTime"] = getAverageInsideTime();
|
timingArray1["2. avgInsideTime"] = getAverageInsideTime();
|
||||||
timingArray1["3. avgTreeLockTime"] = getAverageTreeWaitTime();
|
timingArray1["3. avgTreeTraverseTime"] = getAverageTreeTraverseTime();
|
||||||
timingArray1["4. avgEncodeTime"] = getAverageEncodeTime();
|
timingArray1["4. avgEncodeTime"] = getAverageEncodeTime();
|
||||||
timingArray1["5. avgCompressAndWriteTime"] = getAverageCompressAndWriteTime();
|
timingArray1["5. avgCompressAndWriteTime"] = getAverageCompressAndWriteTime();
|
||||||
timingArray1["6. avgSendTime"] = getAveragePacketSendingTime();
|
timingArray1["6. avgSendTime"] = getAveragePacketSendingTime();
|
||||||
timingArray1["7. nodeWaitTime"] = getAverageNodeWaitTime();
|
timingArray1["7. nodeWaitTime"] = getAverageNodeWaitTime();
|
||||||
|
|
||||||
QJsonObject statsObject2;
|
QJsonObject statsObject2;
|
||||||
statsObject2["data"] = dataObject1;
|
statsObject2["data"] = dataObject1;
|
||||||
statsObject2["timing"] = timingArray1;
|
statsObject2["timing"] = timingArray1;
|
||||||
|
@ -1615,18 +1623,18 @@ void OctreeServer::sendStatsPacket() {
|
||||||
timingArray2["4. avgProcessTimePerElement"] = (double)_octreeInboundPacketProcessor->getAverageProcessTimePerElement();
|
timingArray2["4. avgProcessTimePerElement"] = (double)_octreeInboundPacketProcessor->getAverageProcessTimePerElement();
|
||||||
timingArray2["5. avgLockWaitTimePerElement"] = (double)_octreeInboundPacketProcessor->getAverageLockWaitTimePerElement();
|
timingArray2["5. avgLockWaitTimePerElement"] = (double)_octreeInboundPacketProcessor->getAverageLockWaitTimePerElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject statsObject3;
|
QJsonObject statsObject3;
|
||||||
statsObject3["data"] = dataArray2;
|
statsObject3["data"] = dataArray2;
|
||||||
statsObject3["timing"] = timingArray2;
|
statsObject3["timing"] = timingArray2;
|
||||||
|
|
||||||
// Merge everything
|
// Merge everything
|
||||||
QJsonObject jsonArray;
|
QJsonObject jsonArray;
|
||||||
jsonArray["1. misc"] = statsArray1;
|
jsonArray["1. misc"] = statsArray1;
|
||||||
jsonArray["2. octree"] = octreeStats;
|
jsonArray["2. octree"] = octreeStats;
|
||||||
jsonArray["3. outbound"] = statsObject2;
|
jsonArray["3. outbound"] = statsObject2;
|
||||||
jsonArray["4. inbound"] = statsObject3;
|
jsonArray["4. inbound"] = statsObject3;
|
||||||
|
|
||||||
QJsonObject statsObject;
|
QJsonObject statsObject;
|
||||||
statsObject[QString(getMyServerName()) + "Server"] = jsonArray;
|
statsObject[QString(getMyServerName()) + "Server"] = jsonArray;
|
||||||
addPacketStatsAndSendStatsPacket(statsObject);
|
addPacketStatsAndSendStatsPacket(statsObject);
|
||||||
|
|
|
@ -96,6 +96,9 @@ public:
|
||||||
static void trackTreeWaitTime(float time);
|
static void trackTreeWaitTime(float time);
|
||||||
static float getAverageTreeWaitTime() { return _averageTreeWaitTime.getAverage(); }
|
static float getAverageTreeWaitTime() { return _averageTreeWaitTime.getAverage(); }
|
||||||
|
|
||||||
|
static void trackTreeTraverseTime(float time) { _averageTreeTraverseTime.updateAverage(time); }
|
||||||
|
static float getAverageTreeTraverseTime() { return _averageTreeTraverseTime.getAverage(); }
|
||||||
|
|
||||||
static void trackNodeWaitTime(float time) { _averageNodeWaitTime.updateAverage(time); }
|
static void trackNodeWaitTime(float time) { _averageNodeWaitTime.updateAverage(time); }
|
||||||
static float getAverageNodeWaitTime() { return _averageNodeWaitTime.getAverage(); }
|
static float getAverageNodeWaitTime() { return _averageNodeWaitTime.getAverage(); }
|
||||||
|
|
||||||
|
@ -228,6 +231,8 @@ protected:
|
||||||
static int _shortTreeWait;
|
static int _shortTreeWait;
|
||||||
static int _noTreeWait;
|
static int _noTreeWait;
|
||||||
|
|
||||||
|
static SimpleMovingAverage _averageTreeTraverseTime;
|
||||||
|
|
||||||
static SimpleMovingAverage _averageNodeWaitTime;
|
static SimpleMovingAverage _averageNodeWaitTime;
|
||||||
|
|
||||||
static SimpleMovingAverage _averageCompressAndWriteTime;
|
static SimpleMovingAverage _averageCompressAndWriteTime;
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
var isWindowFocused = true;
|
var isWindowFocused = true;
|
||||||
var isKeyboardRaised = false;
|
var isKeyboardRaised = false;
|
||||||
var isNumericKeyboard = false;
|
var isNumericKeyboard = false;
|
||||||
var KEYBOARD_HEIGHT = 200;
|
|
||||||
|
|
||||||
function shouldRaiseKeyboard() {
|
function shouldRaiseKeyboard() {
|
||||||
var nodeName = document.activeElement.nodeName;
|
var nodeName = document.activeElement.nodeName;
|
||||||
|
@ -38,6 +37,19 @@
|
||||||
return document.activeElement.type === "number";
|
return document.activeElement.type === "number";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function scheduleBringToView(timeout) {
|
||||||
|
|
||||||
|
var timer = setTimeout(function () {
|
||||||
|
clearTimeout(timer);
|
||||||
|
|
||||||
|
var elementRect = document.activeElement.getBoundingClientRect();
|
||||||
|
var absoluteElementTop = elementRect.top + window.scrollY;
|
||||||
|
var middle = absoluteElementTop - (window.innerHeight / 2);
|
||||||
|
|
||||||
|
window.scrollTo(0, middle);
|
||||||
|
}, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
setInterval(function () {
|
setInterval(function () {
|
||||||
var keyboardRaised = shouldRaiseKeyboard();
|
var keyboardRaised = shouldRaiseKeyboard();
|
||||||
var numericKeyboard = shouldSetNumeric();
|
var numericKeyboard = shouldSetNumeric();
|
||||||
|
@ -56,13 +68,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isKeyboardRaised) {
|
if (!isKeyboardRaised) {
|
||||||
var delta = document.activeElement.getBoundingClientRect().bottom + 10
|
scheduleBringToView(250); // Allow time for keyboard to be raised in QML.
|
||||||
- (document.body.clientHeight - KEYBOARD_HEIGHT);
|
// 2DO: should it be rather done from 'client area height changed' event?
|
||||||
if (delta > 0) {
|
|
||||||
setTimeout(function () {
|
|
||||||
document.body.scrollTop += delta;
|
|
||||||
}, 500); // Allow time for keyboard to be raised in QML.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isKeyboardRaised = keyboardRaised;
|
isKeyboardRaised = keyboardRaised;
|
||||||
|
@ -70,6 +77,13 @@
|
||||||
}
|
}
|
||||||
}, POLL_FREQUENCY);
|
}, POLL_FREQUENCY);
|
||||||
|
|
||||||
|
window.addEventListener("click", function () {
|
||||||
|
var keyboardRaised = shouldRaiseKeyboard();
|
||||||
|
if(keyboardRaised && isKeyboardRaised) {
|
||||||
|
scheduleBringToView(150);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
window.addEventListener("focus", function () {
|
window.addEventListener("focus", function () {
|
||||||
isWindowFocused = true;
|
isWindowFocused = true;
|
||||||
});
|
});
|
||||||
|
|
|
@ -206,6 +206,10 @@ Item {
|
||||||
text: "Audio Codec: " + root.audioCodec + " Noise Gate: " +
|
text: "Audio Codec: " + root.audioCodec + " Noise Gate: " +
|
||||||
root.audioNoiseGate;
|
root.audioNoiseGate;
|
||||||
}
|
}
|
||||||
|
StatText {
|
||||||
|
visible: root.expanded;
|
||||||
|
text: "Entity Servers In: " + root.entityPacketsInKbps + " kbps";
|
||||||
|
}
|
||||||
StatText {
|
StatText {
|
||||||
visible: root.expanded;
|
visible: root.expanded;
|
||||||
text: "Downloads: " + root.downloads + "/" + root.downloadLimit +
|
text: "Downloads: " + root.downloads + "/" + root.downloadLimit +
|
||||||
|
|
|
@ -4696,12 +4696,8 @@ void Application::resetPhysicsReadyInformation() {
|
||||||
|
|
||||||
void Application::reloadResourceCaches() {
|
void Application::reloadResourceCaches() {
|
||||||
resetPhysicsReadyInformation();
|
resetPhysicsReadyInformation();
|
||||||
{
|
// Query the octree to refresh everything in view
|
||||||
QMutexLocker viewLocker(&_viewMutex);
|
_lastQueriedTime = 0;
|
||||||
_viewFrustum.setPosition(glm::vec3(0.0f, 0.0f, TREE_SCALE));
|
|
||||||
_viewFrustum.setOrientation(glm::quat());
|
|
||||||
}
|
|
||||||
// Clear entities out of view frustum
|
|
||||||
queryOctree(NodeType::EntityServer, PacketType::EntityQuery, _entityServerJurisdictions);
|
queryOctree(NodeType::EntityServer, PacketType::EntityQuery, _entityServerJurisdictions);
|
||||||
|
|
||||||
DependencyManager::get<AssetClient>()->clearCache();
|
DependencyManager::get<AssetClient>()->clearCache();
|
||||||
|
@ -4859,13 +4855,9 @@ void Application::update(float deltaTime) {
|
||||||
// we haven't yet enabled physics. we wait until we think we have all the collision information
|
// we haven't yet enabled physics. we wait until we think we have all the collision information
|
||||||
// for nearby entities before starting bullet up.
|
// for nearby entities before starting bullet up.
|
||||||
quint64 now = usecTimestampNow();
|
quint64 now = usecTimestampNow();
|
||||||
bool timeout = false;
|
|
||||||
const int PHYSICS_CHECK_TIMEOUT = 2 * USECS_PER_SECOND;
|
const int PHYSICS_CHECK_TIMEOUT = 2 * USECS_PER_SECOND;
|
||||||
if (_lastPhysicsCheckTime > 0 && now - _lastPhysicsCheckTime > PHYSICS_CHECK_TIMEOUT) {
|
|
||||||
timeout = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timeout || _fullSceneReceivedCounter > _fullSceneCounterAtLastPhysicsCheck) {
|
if (now - _lastPhysicsCheckTime > PHYSICS_CHECK_TIMEOUT || _fullSceneReceivedCounter > _fullSceneCounterAtLastPhysicsCheck) {
|
||||||
// we've received a new full-scene octree stats packet, or it's been long enough to try again anyway
|
// we've received a new full-scene octree stats packet, or it's been long enough to try again anyway
|
||||||
_lastPhysicsCheckTime = now;
|
_lastPhysicsCheckTime = now;
|
||||||
_fullSceneCounterAtLastPhysicsCheck = _fullSceneReceivedCounter;
|
_fullSceneCounterAtLastPhysicsCheck = _fullSceneReceivedCounter;
|
||||||
|
@ -5303,7 +5295,7 @@ int Application::sendNackPackets() {
|
||||||
return packetsSent;
|
return packetsSent;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::queryOctree(NodeType_t serverType, PacketType packetType, NodeToJurisdictionMap& jurisdictions, bool forceResend) {
|
void Application::queryOctree(NodeType_t serverType, PacketType packetType, NodeToJurisdictionMap& jurisdictions) {
|
||||||
|
|
||||||
if (!_settingsLoaded) {
|
if (!_settingsLoaded) {
|
||||||
return; // bail early if settings are not loaded
|
return; // bail early if settings are not loaded
|
||||||
|
@ -5459,16 +5451,6 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node
|
||||||
_octreeQuery.setMaxQueryPacketsPerSecond(0);
|
_octreeQuery.setMaxQueryPacketsPerSecond(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if asked to forceResend, then set the query's position/orientation to be degenerate in a manner
|
|
||||||
// that will cause our next query to be guarenteed to be different and the server will resend to us
|
|
||||||
if (forceResend) {
|
|
||||||
_octreeQuery.setCameraPosition(glm::vec3(-0.1, -0.1, -0.1));
|
|
||||||
const glm::quat OFF_IN_NEGATIVE_SPACE = glm::quat(-0.5, 0, -0.5, 1.0);
|
|
||||||
_octreeQuery.setCameraOrientation(OFF_IN_NEGATIVE_SPACE);
|
|
||||||
_octreeQuery.setCameraNearClip(0.1f);
|
|
||||||
_octreeQuery.setCameraFarClip(0.1f);
|
|
||||||
}
|
|
||||||
|
|
||||||
// encode the query data
|
// encode the query data
|
||||||
int packetSize = _octreeQuery.getBroadcastData(reinterpret_cast<unsigned char*>(queryPacket->getPayload()));
|
int packetSize = _octreeQuery.getBroadcastData(reinterpret_cast<unsigned char*>(queryPacket->getPayload()));
|
||||||
queryPacket->setPayloadSize(packetSize);
|
queryPacket->setPayloadSize(packetSize);
|
||||||
|
@ -5717,8 +5699,6 @@ void Application::clearDomainOctreeDetails() {
|
||||||
|
|
||||||
skyStage->setBackgroundMode(model::SunSkyStage::SKY_DEFAULT);
|
skyStage->setBackgroundMode(model::SunSkyStage::SKY_DEFAULT);
|
||||||
|
|
||||||
_recentlyClearedDomain = true;
|
|
||||||
|
|
||||||
DependencyManager::get<AnimationCache>()->clearUnusedResources();
|
DependencyManager::get<AnimationCache>()->clearUnusedResources();
|
||||||
DependencyManager::get<ModelCache>()->clearUnusedResources();
|
DependencyManager::get<ModelCache>()->clearUnusedResources();
|
||||||
DependencyManager::get<SoundCache>()->clearUnusedResources();
|
DependencyManager::get<SoundCache>()->clearUnusedResources();
|
||||||
|
@ -5765,14 +5745,10 @@ void Application::nodeActivated(SharedNodePointer node) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we get a new EntityServer activated, do a "forceRedraw" query. This will send a degenerate
|
// If we get a new EntityServer activated, reset lastQueried time
|
||||||
// query so that the server will think our next non-degenerate query is "different enough" to send
|
// so we will do a proper query during update
|
||||||
// us a full scene
|
if (node->getType() == NodeType::EntityServer) {
|
||||||
if (_recentlyClearedDomain && node->getType() == NodeType::EntityServer) {
|
_lastQueriedTime = 0;
|
||||||
_recentlyClearedDomain = false;
|
|
||||||
if (DependencyManager::get<SceneScriptingInterface>()->shouldRenderEntities()) {
|
|
||||||
queryOctree(NodeType::EntityServer, PacketType::EntityQuery, _entityServerJurisdictions, true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node->getType() == NodeType::AudioMixer) {
|
if (node->getType() == NodeType::AudioMixer) {
|
||||||
|
|
|
@ -467,7 +467,7 @@ private:
|
||||||
void updateThreads(float deltaTime);
|
void updateThreads(float deltaTime);
|
||||||
void updateDialogs(float deltaTime) const;
|
void updateDialogs(float deltaTime) const;
|
||||||
|
|
||||||
void queryOctree(NodeType_t serverType, PacketType packetType, NodeToJurisdictionMap& jurisdictions, bool forceResend = false);
|
void queryOctree(NodeType_t serverType, PacketType packetType, NodeToJurisdictionMap& jurisdictions);
|
||||||
|
|
||||||
void renderRearViewMirror(RenderArgs* renderArgs, const QRect& region, bool isZoomed);
|
void renderRearViewMirror(RenderArgs* renderArgs, const QRect& region, bool isZoomed);
|
||||||
|
|
||||||
|
@ -659,12 +659,10 @@ private:
|
||||||
uint32_t _fullSceneCounterAtLastPhysicsCheck { 0 }; // _fullSceneReceivedCounter last time we checked physics ready
|
uint32_t _fullSceneCounterAtLastPhysicsCheck { 0 }; // _fullSceneReceivedCounter last time we checked physics ready
|
||||||
uint32_t _nearbyEntitiesCountAtLastPhysicsCheck { 0 }; // how many in-range entities last time we checked physics ready
|
uint32_t _nearbyEntitiesCountAtLastPhysicsCheck { 0 }; // how many in-range entities last time we checked physics ready
|
||||||
uint32_t _nearbyEntitiesStabilityCount { 0 }; // how many times has _nearbyEntitiesCountAtLastPhysicsCheck been the same
|
uint32_t _nearbyEntitiesStabilityCount { 0 }; // how many times has _nearbyEntitiesCountAtLastPhysicsCheck been the same
|
||||||
quint64 _lastPhysicsCheckTime { 0 }; // when did we last check to see if physics was ready
|
quint64 _lastPhysicsCheckTime { usecTimestampNow() }; // when did we last check to see if physics was ready
|
||||||
|
|
||||||
bool _keyboardDeviceHasFocus { true };
|
bool _keyboardDeviceHasFocus { true };
|
||||||
|
|
||||||
bool _recentlyClearedDomain { false };
|
|
||||||
|
|
||||||
QString _returnFromFullScreenMirrorTo;
|
QString _returnFromFullScreenMirrorTo;
|
||||||
|
|
||||||
ConnectionMonitor _connectionMonitor;
|
ConnectionMonitor _connectionMonitor;
|
||||||
|
|
|
@ -584,7 +584,7 @@ void MyAvatar::simulate(float deltaTime) {
|
||||||
}
|
}
|
||||||
auto now = usecTimestampNow();
|
auto now = usecTimestampNow();
|
||||||
EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender();
|
EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender();
|
||||||
MovingEntitiesOperator moveOperator(entityTree);
|
MovingEntitiesOperator moveOperator;
|
||||||
forEachDescendant([&](SpatiallyNestablePointer object) {
|
forEachDescendant([&](SpatiallyNestablePointer object) {
|
||||||
// if the queryBox has changed, tell the entity-server
|
// if the queryBox has changed, tell the entity-server
|
||||||
if (object->getNestableType() == NestableType::Entity && object->checkAndMaybeUpdateQueryAACube()) {
|
if (object->getNestableType() == NestableType::Entity && object->checkAndMaybeUpdateQueryAACube()) {
|
||||||
|
|
|
@ -180,10 +180,12 @@ void Stats::updateStats(bool force) {
|
||||||
int totalPingOctree = 0;
|
int totalPingOctree = 0;
|
||||||
int octreeServerCount = 0;
|
int octreeServerCount = 0;
|
||||||
int pingOctreeMax = 0;
|
int pingOctreeMax = 0;
|
||||||
|
int totalEntityKbps = 0;
|
||||||
nodeList->eachNode([&](const SharedNodePointer& node) {
|
nodeList->eachNode([&](const SharedNodePointer& node) {
|
||||||
// TODO: this should also support entities
|
// TODO: this should also support entities
|
||||||
if (node->getType() == NodeType::EntityServer) {
|
if (node->getType() == NodeType::EntityServer) {
|
||||||
totalPingOctree += node->getPingMs();
|
totalPingOctree += node->getPingMs();
|
||||||
|
totalEntityKbps += node->getInboundBandwidth();
|
||||||
octreeServerCount++;
|
octreeServerCount++;
|
||||||
if (pingOctreeMax < node->getPingMs()) {
|
if (pingOctreeMax < node->getPingMs()) {
|
||||||
pingOctreeMax = node->getPingMs();
|
pingOctreeMax = node->getPingMs();
|
||||||
|
@ -248,6 +250,7 @@ void Stats::updateStats(bool force) {
|
||||||
STAT_UPDATE(audioCodec, audioClient->getSelectedAudioFormat());
|
STAT_UPDATE(audioCodec, audioClient->getSelectedAudioFormat());
|
||||||
STAT_UPDATE(audioNoiseGate, audioClient->getNoiseGateOpen() ? "Open" : "Closed");
|
STAT_UPDATE(audioNoiseGate, audioClient->getNoiseGateOpen() ? "Open" : "Closed");
|
||||||
|
|
||||||
|
STAT_UPDATE(entityPacketsInKbps, octreeServerCount ? totalEntityKbps / octreeServerCount : -1);
|
||||||
|
|
||||||
auto loadingRequests = ResourceCache::getLoadingRequests();
|
auto loadingRequests = ResourceCache::getLoadingRequests();
|
||||||
STAT_UPDATE(downloads, loadingRequests.size());
|
STAT_UPDATE(downloads, loadingRequests.size());
|
||||||
|
|
|
@ -85,6 +85,7 @@ class Stats : public QQuickItem {
|
||||||
STATS_PROPERTY(int, audioPacketLoss, 0)
|
STATS_PROPERTY(int, audioPacketLoss, 0)
|
||||||
STATS_PROPERTY(QString, audioCodec, QString())
|
STATS_PROPERTY(QString, audioCodec, QString())
|
||||||
STATS_PROPERTY(QString, audioNoiseGate, QString())
|
STATS_PROPERTY(QString, audioNoiseGate, QString())
|
||||||
|
STATS_PROPERTY(int, entityPacketsInKbps, 0)
|
||||||
|
|
||||||
STATS_PROPERTY(int, downloads, 0)
|
STATS_PROPERTY(int, downloads, 0)
|
||||||
STATS_PROPERTY(int, downloadLimit, 0)
|
STATS_PROPERTY(int, downloadLimit, 0)
|
||||||
|
@ -212,6 +213,7 @@ signals:
|
||||||
void audioPacketLossChanged();
|
void audioPacketLossChanged();
|
||||||
void audioCodecChanged();
|
void audioCodecChanged();
|
||||||
void audioNoiseGateChanged();
|
void audioNoiseGateChanged();
|
||||||
|
void entityPacketsInKbpsChanged();
|
||||||
|
|
||||||
void downloadsChanged();
|
void downloadsChanged();
|
||||||
void downloadLimitChanged();
|
void downloadLimitChanged();
|
||||||
|
|
|
@ -278,9 +278,10 @@ Transform Line3DOverlay::evalRenderTransform() {
|
||||||
auto endPos = getEnd();
|
auto endPos = getEnd();
|
||||||
|
|
||||||
auto vec = endPos - transform.getTranslation();
|
auto vec = endPos - transform.getTranslation();
|
||||||
auto scale = glm::length(vec);
|
const float MIN_LINE_LENGTH = 0.0001f;
|
||||||
|
auto scale = glm::max(glm::length(vec), MIN_LINE_LENGTH);
|
||||||
auto dir = vec / scale;
|
auto dir = vec / scale;
|
||||||
auto orientation = glm::rotation(glm::vec3(0,0,-1), dir);
|
auto orientation = glm::rotation(glm::vec3(0.0f, 0.0f, -1.0f), dir);
|
||||||
transform.setRotation(orientation);
|
transform.setRotation(orientation);
|
||||||
transform.setScale(scale);
|
transform.setScale(scale);
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,9 @@
|
||||||
// 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 <glm/gtx/quaternion.hpp>
|
||||||
|
#include <glm/gtx/transform.hpp>
|
||||||
|
|
||||||
#include "ModelOverlay.h"
|
#include "ModelOverlay.h"
|
||||||
#include <Rig.h>
|
#include <Rig.h>
|
||||||
|
|
||||||
|
@ -60,6 +63,15 @@ void ModelOverlay::update(float deltatime) {
|
||||||
_model->simulate(deltatime);
|
_model->simulate(deltatime);
|
||||||
}
|
}
|
||||||
_isLoaded = _model->isActive();
|
_isLoaded = _model->isActive();
|
||||||
|
|
||||||
|
|
||||||
|
if (isAnimatingSomething()) {
|
||||||
|
if (!jointsMapped()) {
|
||||||
|
mapAnimationJoints(_model->getJointNames());
|
||||||
|
}
|
||||||
|
animate();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ModelOverlay::addToScene(Overlay::Pointer overlay, const render::ScenePointer& scene, render::Transaction& transaction) {
|
bool ModelOverlay::addToScene(Overlay::Pointer overlay, const render::ScenePointer& scene, render::Transaction& transaction) {
|
||||||
|
@ -172,6 +184,51 @@ void ModelOverlay::setProperties(const QVariantMap& properties) {
|
||||||
}
|
}
|
||||||
_updateModel = true;
|
_updateModel = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto animationSettings = properties["animationSettings"];
|
||||||
|
if (animationSettings.canConvert(QVariant::Map)) {
|
||||||
|
QVariantMap animationSettingsMap = animationSettings.toMap();
|
||||||
|
|
||||||
|
auto animationURL = animationSettingsMap["url"];
|
||||||
|
auto animationFPS = animationSettingsMap["fps"];
|
||||||
|
auto animationCurrentFrame = animationSettingsMap["currentFrame"];
|
||||||
|
auto animationFirstFrame = animationSettingsMap["firstFrame"];
|
||||||
|
auto animationLastFrame = animationSettingsMap["lastFrame"];
|
||||||
|
auto animationRunning = animationSettingsMap["running"];
|
||||||
|
auto animationLoop = animationSettingsMap["loop"];
|
||||||
|
auto animationHold = animationSettingsMap["hold"];
|
||||||
|
auto animationAllowTranslation = animationSettingsMap["allowTranslation"];
|
||||||
|
|
||||||
|
if (animationURL.canConvert(QVariant::Url)) {
|
||||||
|
_animationURL = animationURL.toUrl();
|
||||||
|
}
|
||||||
|
if (animationFPS.isValid()) {
|
||||||
|
_animationFPS = animationFPS.toFloat();
|
||||||
|
}
|
||||||
|
if (animationCurrentFrame.isValid()) {
|
||||||
|
_animationCurrentFrame = animationCurrentFrame.toFloat();
|
||||||
|
}
|
||||||
|
if (animationFirstFrame.isValid()) {
|
||||||
|
_animationFirstFrame = animationFirstFrame.toFloat();
|
||||||
|
}
|
||||||
|
if (animationLastFrame.isValid()) {
|
||||||
|
_animationLastFrame = animationLastFrame.toFloat();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (animationRunning.canConvert(QVariant::Bool)) {
|
||||||
|
_animationRunning = animationRunning.toBool();
|
||||||
|
}
|
||||||
|
if (animationLoop.canConvert(QVariant::Bool)) {
|
||||||
|
_animationLoop = animationLoop.toBool();
|
||||||
|
}
|
||||||
|
if (animationHold.canConvert(QVariant::Bool)) {
|
||||||
|
_animationHold = animationHold.toBool();
|
||||||
|
}
|
||||||
|
if (animationAllowTranslation.canConvert(QVariant::Bool)) {
|
||||||
|
_animationAllowTranslation = animationAllowTranslation.toBool();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename vectorType, typename itemType>
|
template <typename vectorType, typename itemType>
|
||||||
|
@ -259,6 +316,24 @@ QVariant ModelOverlay::getProperty(const QString& property) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// animation properties
|
||||||
|
if (property == "animationSettings") {
|
||||||
|
QVariantMap animationSettingsMap;
|
||||||
|
|
||||||
|
animationSettingsMap["url"] = _animationURL;
|
||||||
|
animationSettingsMap["fps"] = _animationFPS;
|
||||||
|
animationSettingsMap["currentFrame"] = _animationCurrentFrame;
|
||||||
|
animationSettingsMap["firstFrame"] = _animationFirstFrame;
|
||||||
|
animationSettingsMap["lastFrame"] = _animationLastFrame;
|
||||||
|
animationSettingsMap["running"] = _animationRunning;
|
||||||
|
animationSettingsMap["loop"] = _animationLoop;
|
||||||
|
animationSettingsMap["hold"]= _animationHold;
|
||||||
|
animationSettingsMap["allowTranslation"] = _animationAllowTranslation;
|
||||||
|
|
||||||
|
return animationSettingsMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return Volume3DOverlay::getProperty(property);
|
return Volume3DOverlay::getProperty(property);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -301,3 +376,134 @@ QString ModelOverlay::getName() const {
|
||||||
}
|
}
|
||||||
return QString("Overlay:") + getType() + ":" + _url.toString();
|
return QString("Overlay:") + getType() + ":" + _url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ModelOverlay::animate() {
|
||||||
|
|
||||||
|
if (!_animation || !_animation->isLoaded() || !_model || !_model->isLoaded()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QVector<JointData> jointsData;
|
||||||
|
|
||||||
|
const QVector<FBXAnimationFrame>& frames = _animation->getFramesReference(); // NOTE: getFrames() is too heavy
|
||||||
|
int frameCount = frames.size();
|
||||||
|
if (frameCount <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_lastAnimated) {
|
||||||
|
_lastAnimated = usecTimestampNow();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto now = usecTimestampNow();
|
||||||
|
auto interval = now - _lastAnimated;
|
||||||
|
_lastAnimated = now;
|
||||||
|
float deltaTime = (float)interval / (float)USECS_PER_SECOND;
|
||||||
|
_animationCurrentFrame += (deltaTime * _animationFPS);
|
||||||
|
|
||||||
|
int animationCurrentFrame = (int)(glm::floor(_animationCurrentFrame)) % frameCount;
|
||||||
|
if (animationCurrentFrame < 0 || animationCurrentFrame > frameCount) {
|
||||||
|
animationCurrentFrame = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (animationCurrentFrame == _lastKnownCurrentFrame) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_lastKnownCurrentFrame = animationCurrentFrame;
|
||||||
|
|
||||||
|
if (_jointMapping.size() != _model->getJointStateCount()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList animationJointNames = _animation->getGeometry().getJointNames();
|
||||||
|
auto& fbxJoints = _animation->getGeometry().joints;
|
||||||
|
|
||||||
|
auto& originalFbxJoints = _model->getFBXGeometry().joints;
|
||||||
|
auto& originalFbxIndices = _model->getFBXGeometry().jointIndices;
|
||||||
|
|
||||||
|
const QVector<glm::quat>& rotations = frames[_lastKnownCurrentFrame].rotations;
|
||||||
|
const QVector<glm::vec3>& translations = frames[_lastKnownCurrentFrame].translations;
|
||||||
|
|
||||||
|
jointsData.resize(_jointMapping.size());
|
||||||
|
for (int j = 0; j < _jointMapping.size(); j++) {
|
||||||
|
int index = _jointMapping[j];
|
||||||
|
|
||||||
|
if (index >= 0) {
|
||||||
|
glm::mat4 translationMat;
|
||||||
|
|
||||||
|
if (_animationAllowTranslation) {
|
||||||
|
if (index < translations.size()) {
|
||||||
|
translationMat = glm::translate(translations[index]);
|
||||||
|
}
|
||||||
|
} else if (index < animationJointNames.size()) {
|
||||||
|
QString jointName = fbxJoints[index].name;
|
||||||
|
|
||||||
|
if (originalFbxIndices.contains(jointName)) {
|
||||||
|
// Making sure the joint names exist in the original model the animation is trying to apply onto. If they do, then remap and get its translation.
|
||||||
|
int remappedIndex = originalFbxIndices[jointName] - 1; // JointIndeces seem to always start from 1 and the found index is always 1 higher than actual.
|
||||||
|
translationMat = glm::translate(originalFbxJoints[remappedIndex].translation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
glm::mat4 rotationMat;
|
||||||
|
if (index < rotations.size()) {
|
||||||
|
rotationMat = glm::mat4_cast(fbxJoints[index].preRotation * rotations[index] * fbxJoints[index].postRotation);
|
||||||
|
} else {
|
||||||
|
rotationMat = glm::mat4_cast(fbxJoints[index].preRotation * fbxJoints[index].postRotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::mat4 finalMat = (translationMat * fbxJoints[index].preTransform *
|
||||||
|
rotationMat * fbxJoints[index].postTransform);
|
||||||
|
auto& jointData = jointsData[j];
|
||||||
|
jointData.translation = extractTranslation(finalMat);
|
||||||
|
jointData.translationSet = true;
|
||||||
|
jointData.rotation = glmExtractRotation(finalMat);
|
||||||
|
jointData.rotationSet = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Set the data in the model
|
||||||
|
copyAnimationJointDataToModel(jointsData);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ModelOverlay::mapAnimationJoints(const QStringList& modelJointNames) {
|
||||||
|
|
||||||
|
// if we don't have animation, or we're already joint mapped then bail early
|
||||||
|
if (!hasAnimation() || jointsMapped()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_animation || _animation->getURL() != _animationURL) {
|
||||||
|
_animation = DependencyManager::get<AnimationCache>()->getAnimation(_animationURL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_animation && _animation->isLoaded()) {
|
||||||
|
QStringList animationJointNames = _animation->getJointNames();
|
||||||
|
|
||||||
|
if (modelJointNames.size() > 0 && animationJointNames.size() > 0) {
|
||||||
|
_jointMapping.resize(modelJointNames.size());
|
||||||
|
for (int i = 0; i < modelJointNames.size(); i++) {
|
||||||
|
_jointMapping[i] = animationJointNames.indexOf(modelJointNames[i]);
|
||||||
|
}
|
||||||
|
_jointMappingCompleted = true;
|
||||||
|
_jointMappingURL = _animationURL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelOverlay::copyAnimationJointDataToModel(QVector<JointData> jointsData) {
|
||||||
|
if (!_model || !_model->isLoaded()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// relay any inbound joint changes from scripts/animation/network to the model/rig
|
||||||
|
for (int index = 0; index < jointsData.size(); ++index) {
|
||||||
|
auto& jointData = jointsData[index];
|
||||||
|
_model->setJointRotation(index, true, jointData.rotation, 1.0f);
|
||||||
|
_model->setJointTranslation(index, true, jointData.translation, 1.0f);
|
||||||
|
}
|
||||||
|
_updateModel = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
#define hifi_ModelOverlay_h
|
#define hifi_ModelOverlay_h
|
||||||
|
|
||||||
#include <Model.h>
|
#include <Model.h>
|
||||||
|
#include <AnimationCache.h>
|
||||||
|
|
||||||
#include "Volume3DOverlay.h"
|
#include "Volume3DOverlay.h"
|
||||||
|
|
||||||
|
@ -45,6 +46,9 @@ public:
|
||||||
|
|
||||||
float getLoadPriority() const { return _loadPriority; }
|
float getLoadPriority() const { return _loadPriority; }
|
||||||
|
|
||||||
|
bool hasAnimation() const { return !_animationURL.isEmpty(); }
|
||||||
|
bool jointsMapped() const { return _jointMappingURL == _animationURL && _jointMappingCompleted; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Transform evalRenderTransform() override;
|
Transform evalRenderTransform() override;
|
||||||
|
|
||||||
|
@ -53,6 +57,14 @@ protected:
|
||||||
template <typename vectorType, typename itemType>
|
template <typename vectorType, typename itemType>
|
||||||
vectorType mapJoints(mapFunction<itemType> function) const;
|
vectorType mapJoints(mapFunction<itemType> function) const;
|
||||||
|
|
||||||
|
void animate();
|
||||||
|
void mapAnimationJoints(const QStringList& modelJointNames);
|
||||||
|
bool isAnimatingSomething() const {
|
||||||
|
return !_animationURL.isEmpty() && _animationRunning && _animationFPS != 0.0f;
|
||||||
|
}
|
||||||
|
void copyAnimationJointDataToModel(QVector<JointData> jointsData);
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
ModelPointer _model;
|
ModelPointer _model;
|
||||||
|
@ -62,6 +74,25 @@ private:
|
||||||
bool _updateModel = { false };
|
bool _updateModel = { false };
|
||||||
bool _scaleToFit = { false };
|
bool _scaleToFit = { false };
|
||||||
float _loadPriority { 0.0f };
|
float _loadPriority { 0.0f };
|
||||||
|
|
||||||
|
AnimationPointer _animation;
|
||||||
|
|
||||||
|
QUrl _animationURL;
|
||||||
|
float _animationFPS { 0.0f };
|
||||||
|
float _animationCurrentFrame { 0.0f };
|
||||||
|
bool _animationRunning { false };
|
||||||
|
bool _animationLoop { false };
|
||||||
|
float _animationFirstFrame { 0.0f };
|
||||||
|
float _animationLastFrame = { 0.0f };
|
||||||
|
bool _animationHold { false };
|
||||||
|
bool _animationAllowTranslation { false };
|
||||||
|
uint64_t _lastAnimated { 0 };
|
||||||
|
int _lastKnownCurrentFrame { -1 };
|
||||||
|
|
||||||
|
QUrl _jointMappingURL;
|
||||||
|
bool _jointMappingCompleted { false };
|
||||||
|
QVector<int> _jointMapping; // domain is index into model-joints, range is index into animation-joints
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_ModelOverlay_h
|
#endif // hifi_ModelOverlay_h
|
||||||
|
|
|
@ -45,7 +45,19 @@ void Volume3DOverlay::setProperties(const QVariantMap& properties) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dimensions.isValid()) {
|
if (dimensions.isValid()) {
|
||||||
setDimensions(vec3FromVariant(dimensions));
|
glm::vec3 scale = vec3FromVariant(dimensions);
|
||||||
|
// don't allow a zero or negative dimension component to reach the renderTransform
|
||||||
|
const float MIN_DIMENSION = 0.0001f;
|
||||||
|
if (scale.x < MIN_DIMENSION) {
|
||||||
|
scale.x = MIN_DIMENSION;
|
||||||
|
}
|
||||||
|
if (scale.y < MIN_DIMENSION) {
|
||||||
|
scale.y = MIN_DIMENSION;
|
||||||
|
}
|
||||||
|
if (scale.z < MIN_DIMENSION) {
|
||||||
|
scale.z = MIN_DIMENSION;
|
||||||
|
}
|
||||||
|
setDimensions(scale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -249,6 +249,7 @@ void Rig::reset(const FBXGeometry& geometry) {
|
||||||
_rightShoulderJointIndex = _rightElbowJointIndex >= 0 ? geometry.joints.at(_rightElbowJointIndex).parentIndex : -1;
|
_rightShoulderJointIndex = _rightElbowJointIndex >= 0 ? geometry.joints.at(_rightElbowJointIndex).parentIndex : -1;
|
||||||
|
|
||||||
if (!_animGraphURL.isEmpty()) {
|
if (!_animGraphURL.isEmpty()) {
|
||||||
|
_animNode.reset();
|
||||||
initAnimGraph(_animGraphURL);
|
initAnimGraph(_animGraphURL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1619,7 +1620,7 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo
|
||||||
}
|
}
|
||||||
|
|
||||||
void Rig::initAnimGraph(const QUrl& url) {
|
void Rig::initAnimGraph(const QUrl& url) {
|
||||||
if (_animGraphURL != url) {
|
if (_animGraphURL != url || !_animNode) {
|
||||||
_animGraphURL = url;
|
_animGraphURL = url;
|
||||||
|
|
||||||
_animNode.reset();
|
_animNode.reset();
|
||||||
|
|
|
@ -9,18 +9,17 @@
|
||||||
// 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 "AddEntityOperator.h"
|
||||||
|
|
||||||
#include "EntityItem.h"
|
#include "EntityItem.h"
|
||||||
#include "EntityTree.h"
|
#include "EntityTree.h"
|
||||||
#include "EntityTreeElement.h"
|
#include "EntityTreeElement.h"
|
||||||
|
|
||||||
#include "AddEntityOperator.h"
|
|
||||||
|
|
||||||
AddEntityOperator::AddEntityOperator(EntityTreePointer tree, EntityItemPointer newEntity) :
|
AddEntityOperator::AddEntityOperator(EntityTreePointer tree, EntityItemPointer newEntity) :
|
||||||
_tree(tree),
|
_tree(tree),
|
||||||
_newEntity(newEntity),
|
_newEntity(newEntity),
|
||||||
_foundNew(false),
|
_newEntityBox(),
|
||||||
_changeTime(usecTimestampNow()),
|
_foundNew(false)
|
||||||
_newEntityBox()
|
|
||||||
{
|
{
|
||||||
// caller must have verified existence of newEntity
|
// caller must have verified existence of newEntity
|
||||||
assert(_newEntity);
|
assert(_newEntity);
|
||||||
|
|
|
@ -12,20 +12,28 @@
|
||||||
#ifndef hifi_AddEntityOperator_h
|
#ifndef hifi_AddEntityOperator_h
|
||||||
#define hifi_AddEntityOperator_h
|
#define hifi_AddEntityOperator_h
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include <AABox.h>
|
||||||
|
#include <Octree.h>
|
||||||
|
|
||||||
|
#include "EntityTypes.h"
|
||||||
|
|
||||||
|
class EntityTree;
|
||||||
|
using EntityTreePointer = std::shared_ptr<EntityTree>;
|
||||||
|
|
||||||
class AddEntityOperator : public RecurseOctreeOperator {
|
class AddEntityOperator : public RecurseOctreeOperator {
|
||||||
public:
|
public:
|
||||||
AddEntityOperator(EntityTreePointer tree, EntityItemPointer newEntity);
|
AddEntityOperator(EntityTreePointer tree, EntityItemPointer newEntity);
|
||||||
|
|
||||||
virtual bool preRecursion(const OctreeElementPointer& element) override;
|
virtual bool preRecursion(const OctreeElementPointer& element) override;
|
||||||
virtual bool postRecursion(const OctreeElementPointer& element) override;
|
virtual bool postRecursion(const OctreeElementPointer& element) override;
|
||||||
virtual OctreeElementPointer possiblyCreateChildAt(const OctreeElementPointer& element, int childIndex) override;
|
virtual OctreeElementPointer possiblyCreateChildAt(const OctreeElementPointer& element, int childIndex) override;
|
||||||
private:
|
private:
|
||||||
EntityTreePointer _tree;
|
EntityTreePointer _tree;
|
||||||
EntityItemPointer _newEntity;
|
EntityItemPointer _newEntity;
|
||||||
bool _foundNew;
|
|
||||||
quint64 _changeTime;
|
|
||||||
|
|
||||||
AABox _newEntityBox;
|
AABox _newEntityBox;
|
||||||
|
bool _foundNew;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
252
libraries/entities/src/DiffTraversal.cpp
Normal file
252
libraries/entities/src/DiffTraversal.cpp
Normal file
|
@ -0,0 +1,252 @@
|
||||||
|
//
|
||||||
|
// DiffTraversal.cpp
|
||||||
|
// assignment-client/src/entities
|
||||||
|
//
|
||||||
|
// Created by Andrew Meadows 2017.08.08
|
||||||
|
// Copyright 2017 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "DiffTraversal.h"
|
||||||
|
|
||||||
|
#include <OctreeUtils.h>
|
||||||
|
|
||||||
|
|
||||||
|
DiffTraversal::Waypoint::Waypoint(EntityTreeElementPointer& element) : _nextIndex(0) {
|
||||||
|
assert(element);
|
||||||
|
_weakElement = element;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DiffTraversal::Waypoint::getNextVisibleElementFirstTime(DiffTraversal::VisibleElement& next,
|
||||||
|
const DiffTraversal::View& view) {
|
||||||
|
// NOTE: no need to set next.intersection in the "FirstTime" context
|
||||||
|
if (_nextIndex == -1) {
|
||||||
|
// root case is special:
|
||||||
|
// its intersection is always INTERSECT,
|
||||||
|
// we never bother checking for LOD culling, and
|
||||||
|
// we can skip it if the content hasn't changed
|
||||||
|
++_nextIndex;
|
||||||
|
next.element = _weakElement.lock();
|
||||||
|
return;
|
||||||
|
} else if (_nextIndex < NUMBER_OF_CHILDREN) {
|
||||||
|
EntityTreeElementPointer element = _weakElement.lock();
|
||||||
|
if (element) {
|
||||||
|
while (_nextIndex < NUMBER_OF_CHILDREN) {
|
||||||
|
EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex);
|
||||||
|
++_nextIndex;
|
||||||
|
if (nextElement) {
|
||||||
|
if (!view.usesViewFrustum) {
|
||||||
|
// No LOD truncation if we aren't using the view frustum
|
||||||
|
next.element = nextElement;
|
||||||
|
return;
|
||||||
|
} else if (view.viewFrustum.cubeIntersectsKeyhole(nextElement->getAACube())) {
|
||||||
|
// check for LOD truncation
|
||||||
|
float distance = glm::distance(view.viewFrustum.getPosition(), nextElement->getAACube().calcCenter()) + MIN_VISIBLE_DISTANCE;
|
||||||
|
float angularDiameter = nextElement->getAACube().getScale() / distance;
|
||||||
|
if (angularDiameter > MIN_ELEMENT_ANGULAR_DIAMETER * view.lodScaleFactor) {
|
||||||
|
next.element = nextElement;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next.element.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DiffTraversal::Waypoint::getNextVisibleElementRepeat(
|
||||||
|
DiffTraversal::VisibleElement& next, const DiffTraversal::View& view, uint64_t lastTime) {
|
||||||
|
if (_nextIndex == -1) {
|
||||||
|
// root case is special
|
||||||
|
++_nextIndex;
|
||||||
|
EntityTreeElementPointer element = _weakElement.lock();
|
||||||
|
if (element->getLastChangedContent() > lastTime) {
|
||||||
|
next.element = element;
|
||||||
|
next.intersection = ViewFrustum::INTERSECT;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_nextIndex < NUMBER_OF_CHILDREN) {
|
||||||
|
EntityTreeElementPointer element = _weakElement.lock();
|
||||||
|
if (element) {
|
||||||
|
while (_nextIndex < NUMBER_OF_CHILDREN) {
|
||||||
|
EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex);
|
||||||
|
++_nextIndex;
|
||||||
|
if (nextElement && nextElement->getLastChanged() > lastTime) {
|
||||||
|
if (!view.usesViewFrustum) {
|
||||||
|
// No LOD truncation if we aren't using the view frustum
|
||||||
|
next.element = nextElement;
|
||||||
|
next.intersection = ViewFrustum::INSIDE;
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// check for LOD truncation
|
||||||
|
float distance = glm::distance(view.viewFrustum.getPosition(), nextElement->getAACube().calcCenter()) + MIN_VISIBLE_DISTANCE;
|
||||||
|
float angularDiameter = nextElement->getAACube().getScale() / distance;
|
||||||
|
if (angularDiameter > MIN_ELEMENT_ANGULAR_DIAMETER * view.lodScaleFactor) {
|
||||||
|
ViewFrustum::intersection intersection = view.viewFrustum.calculateCubeKeyholeIntersection(nextElement->getAACube());
|
||||||
|
if (intersection != ViewFrustum::OUTSIDE) {
|
||||||
|
next.element = nextElement;
|
||||||
|
next.intersection = intersection;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next.element.reset();
|
||||||
|
next.intersection = ViewFrustum::OUTSIDE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DiffTraversal::Waypoint::getNextVisibleElementDifferential(DiffTraversal::VisibleElement& next,
|
||||||
|
const DiffTraversal::View& view, const DiffTraversal::View& lastView) {
|
||||||
|
if (_nextIndex == -1) {
|
||||||
|
// root case is special
|
||||||
|
++_nextIndex;
|
||||||
|
EntityTreeElementPointer element = _weakElement.lock();
|
||||||
|
next.element = element;
|
||||||
|
next.intersection = ViewFrustum::INTERSECT;
|
||||||
|
return;
|
||||||
|
} else if (_nextIndex < NUMBER_OF_CHILDREN) {
|
||||||
|
EntityTreeElementPointer element = _weakElement.lock();
|
||||||
|
if (element) {
|
||||||
|
while (_nextIndex < NUMBER_OF_CHILDREN) {
|
||||||
|
EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex);
|
||||||
|
++_nextIndex;
|
||||||
|
if (nextElement) {
|
||||||
|
AACube cube = nextElement->getAACube();
|
||||||
|
// check for LOD truncation
|
||||||
|
float distance = glm::distance(view.viewFrustum.getPosition(), cube.calcCenter()) + MIN_VISIBLE_DISTANCE;
|
||||||
|
float angularDiameter = cube.getScale() / distance;
|
||||||
|
if (angularDiameter > MIN_ELEMENT_ANGULAR_DIAMETER * view.lodScaleFactor) {
|
||||||
|
if (view.viewFrustum.calculateCubeKeyholeIntersection(cube) != ViewFrustum::OUTSIDE) {
|
||||||
|
next.element = nextElement;
|
||||||
|
next.intersection = ViewFrustum::OUTSIDE;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next.element.reset();
|
||||||
|
next.intersection = ViewFrustum::OUTSIDE;
|
||||||
|
}
|
||||||
|
|
||||||
|
DiffTraversal::DiffTraversal() {
|
||||||
|
const int32_t MIN_PATH_DEPTH = 16;
|
||||||
|
_path.reserve(MIN_PATH_DEPTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
DiffTraversal::Type DiffTraversal::prepareNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset, bool usesViewFrustum) {
|
||||||
|
assert(root);
|
||||||
|
// there are three types of traversal:
|
||||||
|
//
|
||||||
|
// (1) First = fresh view --> find all elements in view
|
||||||
|
// (2) Repeat = view hasn't changed --> find elements changed since last complete traversal
|
||||||
|
// (3) Differential = view has changed --> find elements changed or in new view but not old
|
||||||
|
//
|
||||||
|
// for each traversal type we assign the appropriate _getNextVisibleElementCallback
|
||||||
|
//
|
||||||
|
// _getNextVisibleElementCallback = identifies elements that need to be traversed,
|
||||||
|
// updates VisibleElement ref argument with pointer-to-element and view-intersection
|
||||||
|
// (INSIDE, INTERSECT, or OUTSIDE)
|
||||||
|
//
|
||||||
|
// external code should update the _scanElementCallback after calling prepareNewTraversal
|
||||||
|
//
|
||||||
|
_currentView.usesViewFrustum = usesViewFrustum;
|
||||||
|
float lodScaleFactor = powf(2.0f, lodLevelOffset);
|
||||||
|
|
||||||
|
Type type;
|
||||||
|
// If usesViewFrustum changes, treat it as a First traversal
|
||||||
|
if (_completedView.startTime == 0 || _currentView.usesViewFrustum != _completedView.usesViewFrustum) {
|
||||||
|
type = Type::First;
|
||||||
|
_currentView.viewFrustum = viewFrustum;
|
||||||
|
_currentView.lodScaleFactor = lodScaleFactor;
|
||||||
|
_getNextVisibleElementCallback = [this](DiffTraversal::VisibleElement& next) {
|
||||||
|
_path.back().getNextVisibleElementFirstTime(next, _currentView);
|
||||||
|
};
|
||||||
|
} else if (!_currentView.usesViewFrustum ||
|
||||||
|
(_completedView.viewFrustum.isVerySimilar(viewFrustum) &&
|
||||||
|
lodScaleFactor == _completedView.lodScaleFactor)) {
|
||||||
|
type = Type::Repeat;
|
||||||
|
_getNextVisibleElementCallback = [this](DiffTraversal::VisibleElement& next) {
|
||||||
|
_path.back().getNextVisibleElementRepeat(next, _completedView, _completedView.startTime);
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
type = Type::Differential;
|
||||||
|
_currentView.viewFrustum = viewFrustum;
|
||||||
|
_currentView.lodScaleFactor = lodScaleFactor;
|
||||||
|
_getNextVisibleElementCallback = [this](DiffTraversal::VisibleElement& next) {
|
||||||
|
_path.back().getNextVisibleElementDifferential(next, _currentView, _completedView);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_path.clear();
|
||||||
|
_path.push_back(DiffTraversal::Waypoint(root));
|
||||||
|
// set root fork's index such that root element returned at getNextElement()
|
||||||
|
_path.back().initRootNextIndex();
|
||||||
|
|
||||||
|
_currentView.startTime = usecTimestampNow();
|
||||||
|
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DiffTraversal::getNextVisibleElement(DiffTraversal::VisibleElement& next) {
|
||||||
|
if (_path.empty()) {
|
||||||
|
next.element.reset();
|
||||||
|
next.intersection = ViewFrustum::OUTSIDE;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_getNextVisibleElementCallback(next);
|
||||||
|
if (next.element) {
|
||||||
|
int8_t nextIndex = _path.back().getNextIndex();
|
||||||
|
if (nextIndex > 0) {
|
||||||
|
_path.push_back(DiffTraversal::Waypoint(next.element));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// we're done at this level
|
||||||
|
while (!next.element) {
|
||||||
|
// pop one level
|
||||||
|
_path.pop_back();
|
||||||
|
if (_path.empty()) {
|
||||||
|
// we've traversed the entire tree
|
||||||
|
_completedView = _currentView;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// keep looking for next
|
||||||
|
_getNextVisibleElementCallback(next);
|
||||||
|
if (next.element) {
|
||||||
|
// we've descended one level so add it to the path
|
||||||
|
_path.push_back(DiffTraversal::Waypoint(next.element));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DiffTraversal::setScanCallback(std::function<void (DiffTraversal::VisibleElement&)> cb) {
|
||||||
|
if (!cb) {
|
||||||
|
_scanElementCallback = [](DiffTraversal::VisibleElement& a){};
|
||||||
|
} else {
|
||||||
|
_scanElementCallback = cb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DiffTraversal::traverse(uint64_t timeBudget) {
|
||||||
|
uint64_t expiry = usecTimestampNow() + timeBudget;
|
||||||
|
DiffTraversal::VisibleElement next;
|
||||||
|
getNextVisibleElement(next);
|
||||||
|
while (next.element) {
|
||||||
|
if (next.element->hasContent()) {
|
||||||
|
_scanElementCallback(next);
|
||||||
|
}
|
||||||
|
if (usecTimestampNow() > expiry) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
getNextVisibleElement(next);
|
||||||
|
}
|
||||||
|
}
|
85
libraries/entities/src/DiffTraversal.h
Normal file
85
libraries/entities/src/DiffTraversal.h
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
//
|
||||||
|
// DiffTraversal.h
|
||||||
|
// assignment-client/src/entities
|
||||||
|
//
|
||||||
|
// Created by Andrew Meadows 2017.08.08
|
||||||
|
// Copyright 2017 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef hifi_DiffTraversal_h
|
||||||
|
#define hifi_DiffTraversal_h
|
||||||
|
|
||||||
|
#include <ViewFrustum.h>
|
||||||
|
|
||||||
|
#include "EntityTreeElement.h"
|
||||||
|
|
||||||
|
// DiffTraversal traverses the tree and applies _scanElementCallback on elements it finds
|
||||||
|
class DiffTraversal {
|
||||||
|
public:
|
||||||
|
// VisibleElement is a struct identifying an element and how it intersected the view.
|
||||||
|
// The intersection is used to optimize culling entities from the sendQueue.
|
||||||
|
class VisibleElement {
|
||||||
|
public:
|
||||||
|
EntityTreeElementPointer element;
|
||||||
|
ViewFrustum::intersection intersection { ViewFrustum::OUTSIDE };
|
||||||
|
};
|
||||||
|
|
||||||
|
// View is a struct with a ViewFrustum and LOD parameters
|
||||||
|
class View {
|
||||||
|
public:
|
||||||
|
ViewFrustum viewFrustum;
|
||||||
|
uint64_t startTime { 0 };
|
||||||
|
float lodScaleFactor { 1.0f };
|
||||||
|
bool usesViewFrustum { true };
|
||||||
|
};
|
||||||
|
|
||||||
|
// Waypoint is an bookmark in a "path" of waypoints during a traversal.
|
||||||
|
class Waypoint {
|
||||||
|
public:
|
||||||
|
Waypoint(EntityTreeElementPointer& element);
|
||||||
|
|
||||||
|
void getNextVisibleElementFirstTime(VisibleElement& next, const View& view);
|
||||||
|
void getNextVisibleElementRepeat(VisibleElement& next, const View& view, uint64_t lastTime);
|
||||||
|
void getNextVisibleElementDifferential(VisibleElement& next, const View& view, const View& lastView);
|
||||||
|
|
||||||
|
int8_t getNextIndex() const { return _nextIndex; }
|
||||||
|
void initRootNextIndex() { _nextIndex = -1; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
EntityTreeElementWeakPointer _weakElement;
|
||||||
|
int8_t _nextIndex;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef enum { First, Repeat, Differential } Type;
|
||||||
|
|
||||||
|
DiffTraversal();
|
||||||
|
|
||||||
|
Type prepareNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset, bool usesViewFrustum);
|
||||||
|
|
||||||
|
const ViewFrustum& getCurrentView() const { return _currentView.viewFrustum; }
|
||||||
|
const ViewFrustum& getCompletedView() const { return _completedView.viewFrustum; }
|
||||||
|
|
||||||
|
bool doesCurrentUseViewFrustum() const { return _currentView.usesViewFrustum; }
|
||||||
|
float getCurrentLODScaleFactor() const { return _currentView.lodScaleFactor; }
|
||||||
|
float getCompletedLODScaleFactor() const { return _completedView.lodScaleFactor; }
|
||||||
|
|
||||||
|
uint64_t getStartOfCompletedTraversal() const { return _completedView.startTime; }
|
||||||
|
bool finished() const { return _path.empty(); }
|
||||||
|
|
||||||
|
void setScanCallback(std::function<void (VisibleElement&)> cb);
|
||||||
|
void traverse(uint64_t timeBudget);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void getNextVisibleElement(VisibleElement& next);
|
||||||
|
|
||||||
|
View _currentView;
|
||||||
|
View _completedView;
|
||||||
|
std::vector<Waypoint> _path;
|
||||||
|
std::function<void (VisibleElement&)> _getNextVisibleElementCallback { nullptr };
|
||||||
|
std::function<void (VisibleElement&)> _scanElementCallback { [](VisibleElement& e){} };
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // hifi_EntityPriorityQueue_h
|
|
@ -32,7 +32,8 @@
|
||||||
#include "EntitySimulation.h"
|
#include "EntitySimulation.h"
|
||||||
#include "EntityDynamicFactoryInterface.h"
|
#include "EntityDynamicFactoryInterface.h"
|
||||||
|
|
||||||
|
Q_DECLARE_METATYPE(EntityItemPointer);
|
||||||
|
int entityItemPointernMetaTypeId = qRegisterMetaType<EntityItemPointer>();
|
||||||
int EntityItem::_maxActionsDataSize = 800;
|
int EntityItem::_maxActionsDataSize = 800;
|
||||||
quint64 EntityItem::_rememberDeletedActionTime = 20 * USECS_PER_SECOND;
|
quint64 EntityItem::_rememberDeletedActionTime = 20 * USECS_PER_SECOND;
|
||||||
|
|
||||||
|
@ -1570,13 +1571,17 @@ void EntityItem::updatePosition(const glm::vec3& value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityItem::updateParentID(const QUuid& value) {
|
void EntityItem::updateParentID(const QUuid& value) {
|
||||||
if (getParentID() != value) {
|
QUuid oldParentID = getParentID();
|
||||||
|
if (oldParentID != value) {
|
||||||
|
EntityTreePointer tree = getTree();
|
||||||
|
if (!oldParentID.isNull()) {
|
||||||
|
tree->removeFromChildrenOfAvatars(getThisPointer());
|
||||||
|
}
|
||||||
setParentID(value);
|
setParentID(value);
|
||||||
// children are forced to be kinematic
|
// children are forced to be kinematic
|
||||||
// may need to not collide with own avatar
|
// may need to not collide with own avatar
|
||||||
markDirtyFlags(Simulation::DIRTY_MOTION_TYPE | Simulation::DIRTY_COLLISION_GROUP);
|
markDirtyFlags(Simulation::DIRTY_MOTION_TYPE | Simulation::DIRTY_COLLISION_GROUP);
|
||||||
|
|
||||||
EntityTreePointer tree = getTree();
|
|
||||||
if (tree) {
|
if (tree) {
|
||||||
tree->addToNeedsParentFixupList(getThisPointer());
|
tree->addToNeedsParentFixupList(getThisPointer());
|
||||||
}
|
}
|
||||||
|
@ -2020,7 +2025,7 @@ bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulationPoi
|
||||||
_previouslyDeletedActions.insert(actionID, usecTimestampNow());
|
_previouslyDeletedActions.insert(actionID, usecTimestampNow());
|
||||||
if (_objectActions.contains(actionID)) {
|
if (_objectActions.contains(actionID)) {
|
||||||
if (!simulation) {
|
if (!simulation) {
|
||||||
EntityTreeElementPointer element = _element; // use local copy of _element for logic below
|
EntityTreeElementPointer element = _element; // use local copy of _element for logic below
|
||||||
EntityTreePointer entityTree = element ? element->getTree() : nullptr;
|
EntityTreePointer entityTree = element ? element->getTree() : nullptr;
|
||||||
simulation = entityTree ? entityTree->getSimulation() : nullptr;
|
simulation = entityTree ? entityTree->getSimulation() : nullptr;
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,7 +62,8 @@ class MeshProxyList;
|
||||||
/// EntityItem class this is the base class for all entity types. It handles the basic properties and functionality available
|
/// EntityItem class this is the base class for all entity types. It handles the basic properties and functionality available
|
||||||
/// to all other entity types. In particular: postion, size, rotation, age, lifetime, velocity, gravity. You can not instantiate
|
/// to all other entity types. In particular: postion, size, rotation, age, lifetime, velocity, gravity. You can not instantiate
|
||||||
/// one directly, instead you must only construct one of it's derived classes with additional features.
|
/// one directly, instead you must only construct one of it's derived classes with additional features.
|
||||||
class EntityItem : public SpatiallyNestable, public ReadWriteLockable {
|
class EntityItem : public QObject, public SpatiallyNestable, public ReadWriteLockable {
|
||||||
|
Q_OBJECT
|
||||||
// These two classes manage lists of EntityItem pointers and must be able to cleanup pointers when an EntityItem is deleted.
|
// These two classes manage lists of EntityItem pointers and must be able to cleanup pointers when an EntityItem is deleted.
|
||||||
// To make the cleanup robust each EntityItem has backpointers to its manager classes (which are only ever set/cleared by
|
// To make the cleanup robust each EntityItem has backpointers to its manager classes (which are only ever set/cleared by
|
||||||
// the managers themselves, hence they are fiends) whose NULL status can be used to determine which managers still need to
|
// the managers themselves, hence they are fiends) whose NULL status can be used to determine which managers still need to
|
||||||
|
|
|
@ -141,7 +141,7 @@ void EntitySimulation::callUpdateOnEntitiesThatNeedIt(const quint64& now) {
|
||||||
void EntitySimulation::sortEntitiesThatMoved() {
|
void EntitySimulation::sortEntitiesThatMoved() {
|
||||||
// NOTE: this is only for entities that have been moved by THIS EntitySimulation.
|
// NOTE: this is only for entities that have been moved by THIS EntitySimulation.
|
||||||
// External changes to entity position/shape are expected to be sorted outside of the EntitySimulation.
|
// External changes to entity position/shape are expected to be sorted outside of the EntitySimulation.
|
||||||
MovingEntitiesOperator moveOperator(_entityTree);
|
MovingEntitiesOperator moveOperator;
|
||||||
AACube domainBounds(glm::vec3((float)-HALF_TREE_SCALE), (float)TREE_SCALE);
|
AACube domainBounds(glm::vec3((float)-HALF_TREE_SCALE), (float)TREE_SCALE);
|
||||||
SetOfEntities::iterator itemItr = _entitiesToSort.begin();
|
SetOfEntities::iterator itemItr = _entitiesToSort.begin();
|
||||||
while (itemItr != _entitiesToSort.end()) {
|
while (itemItr != _entitiesToSort.end()) {
|
||||||
|
|
|
@ -22,7 +22,6 @@
|
||||||
#include "VariantMapToScriptValue.h"
|
#include "VariantMapToScriptValue.h"
|
||||||
|
|
||||||
#include "AddEntityOperator.h"
|
#include "AddEntityOperator.h"
|
||||||
#include "MovingEntitiesOperator.h"
|
|
||||||
#include "UpdateEntityOperator.h"
|
#include "UpdateEntityOperator.h"
|
||||||
#include "QVariantGLM.h"
|
#include "QVariantGLM.h"
|
||||||
#include "EntitiesLogging.h"
|
#include "EntitiesLogging.h"
|
||||||
|
@ -107,6 +106,111 @@ void EntityTree::eraseAllOctreeElements(bool createNewRoot) {
|
||||||
clearDeletedEntities();
|
clearDeletedEntities();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EntityTree::readBitstreamToTree(const unsigned char* bitstream,
|
||||||
|
uint64_t bufferSizeBytes, ReadBitstreamToTreeParams& args) {
|
||||||
|
Octree::readBitstreamToTree(bitstream, bufferSizeBytes, args);
|
||||||
|
|
||||||
|
// add entities
|
||||||
|
QHash<EntityItemID, EntityItemPointer>::const_iterator itr;
|
||||||
|
for (itr = _entitiesToAdd.constBegin(); itr != _entitiesToAdd.constEnd(); ++itr) {
|
||||||
|
const EntityItemPointer& entityItem = itr.value();
|
||||||
|
AddEntityOperator theOperator(getThisPointer(), entityItem);
|
||||||
|
recurseTreeWithOperator(&theOperator);
|
||||||
|
postAddEntity(entityItem);
|
||||||
|
}
|
||||||
|
_entitiesToAdd.clear();
|
||||||
|
|
||||||
|
// move entities
|
||||||
|
if (_entityMover.hasMovingEntities()) {
|
||||||
|
PerformanceTimer perfTimer("recurseTreeWithOperator");
|
||||||
|
recurseTreeWithOperator(&_entityMover);
|
||||||
|
_entityMover.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int EntityTree::readEntityDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args) {
|
||||||
|
const unsigned char* dataAt = data;
|
||||||
|
int bytesRead = 0;
|
||||||
|
uint16_t numberOfEntities = 0;
|
||||||
|
int expectedBytesPerEntity = EntityItem::expectedBytes();
|
||||||
|
|
||||||
|
args.elementsPerPacket++;
|
||||||
|
|
||||||
|
if (bytesLeftToRead >= (int)sizeof(numberOfEntities)) {
|
||||||
|
// read our entities in....
|
||||||
|
numberOfEntities = *(uint16_t*)dataAt;
|
||||||
|
|
||||||
|
dataAt += sizeof(numberOfEntities);
|
||||||
|
bytesLeftToRead -= (int)sizeof(numberOfEntities);
|
||||||
|
bytesRead += sizeof(numberOfEntities);
|
||||||
|
|
||||||
|
if (bytesLeftToRead >= (int)(numberOfEntities * expectedBytesPerEntity)) {
|
||||||
|
for (uint16_t i = 0; i < numberOfEntities; i++) {
|
||||||
|
int bytesForThisEntity = 0;
|
||||||
|
EntityItemID entityItemID = EntityItemID::readEntityItemIDFromBuffer(dataAt, bytesLeftToRead);
|
||||||
|
EntityItemPointer entity = findEntityByEntityItemID(entityItemID);
|
||||||
|
|
||||||
|
if (entity) {
|
||||||
|
QString entityScriptBefore = entity->getScript();
|
||||||
|
QUuid parentIDBefore = entity->getParentID();
|
||||||
|
QString entityServerScriptsBefore = entity->getServerScripts();
|
||||||
|
quint64 entityScriptTimestampBefore = entity->getScriptTimestamp();
|
||||||
|
|
||||||
|
bytesForThisEntity = entity->readEntityDataFromBuffer(dataAt, bytesLeftToRead, args);
|
||||||
|
if (entity->getDirtyFlags()) {
|
||||||
|
entityChanged(entity);
|
||||||
|
}
|
||||||
|
_entityMover.addEntityToMoveList(entity, entity->getQueryAACube());
|
||||||
|
|
||||||
|
QString entityScriptAfter = entity->getScript();
|
||||||
|
QString entityServerScriptsAfter = entity->getServerScripts();
|
||||||
|
quint64 entityScriptTimestampAfter = entity->getScriptTimestamp();
|
||||||
|
bool reload = entityScriptTimestampBefore != entityScriptTimestampAfter;
|
||||||
|
|
||||||
|
// If the script value has changed on us, or it's timestamp has changed to force
|
||||||
|
// a reload then we want to send out a script changing signal...
|
||||||
|
if (reload || entityScriptBefore != entityScriptAfter) {
|
||||||
|
emitEntityScriptChanging(entityItemID, reload); // the entity script has changed
|
||||||
|
}
|
||||||
|
if (reload || entityServerScriptsBefore != entityServerScriptsAfter) {
|
||||||
|
emitEntityServerScriptChanging(entityItemID, reload); // the entity server script has changed
|
||||||
|
}
|
||||||
|
|
||||||
|
QUuid parentIDAfter = entity->getParentID();
|
||||||
|
if (parentIDBefore != parentIDAfter) {
|
||||||
|
addToNeedsParentFixupList(entity);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
entity = EntityTypes::constructEntityItem(dataAt, bytesLeftToRead, args);
|
||||||
|
if (entity) {
|
||||||
|
bytesForThisEntity = entity->readEntityDataFromBuffer(dataAt, bytesLeftToRead, args);
|
||||||
|
|
||||||
|
// don't add if we've recently deleted....
|
||||||
|
if (!isDeletedEntity(entityItemID)) {
|
||||||
|
_entitiesToAdd.insert(entityItemID, entity);
|
||||||
|
|
||||||
|
if (entity->getCreated() == UNKNOWN_CREATED_TIME) {
|
||||||
|
entity->recordCreationTime();
|
||||||
|
}
|
||||||
|
#ifdef WANT_DEBUG
|
||||||
|
} else {
|
||||||
|
qCDebug(entities) << "Received packet for previously deleted entity [" <<
|
||||||
|
entityItemID << "] ignoring. (inside " << __FUNCTION__ << ")";
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Move the buffer forward to read more entities
|
||||||
|
dataAt += bytesForThisEntity;
|
||||||
|
bytesLeftToRead -= bytesForThisEntity;
|
||||||
|
bytesRead += bytesForThisEntity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytesRead;
|
||||||
|
}
|
||||||
|
|
||||||
bool EntityTree::handlesEditPacketType(PacketType packetType) const {
|
bool EntityTree::handlesEditPacketType(PacketType packetType) const {
|
||||||
// we handle these types of "edit" packets
|
// we handle these types of "edit" packets
|
||||||
switch (packetType) {
|
switch (packetType) {
|
||||||
|
@ -193,7 +297,9 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti
|
||||||
}
|
}
|
||||||
UpdateEntityOperator theOperator(getThisPointer(), containingElement, entity, queryCube);
|
UpdateEntityOperator theOperator(getThisPointer(), containingElement, entity, queryCube);
|
||||||
recurseTreeWithOperator(&theOperator);
|
recurseTreeWithOperator(&theOperator);
|
||||||
entity->setProperties(tempProperties);
|
if (entity->setProperties(tempProperties)) {
|
||||||
|
emit editingEntityPointer(entity);
|
||||||
|
}
|
||||||
_isDirty = true;
|
_isDirty = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -268,7 +374,9 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti
|
||||||
}
|
}
|
||||||
UpdateEntityOperator theOperator(getThisPointer(), containingElement, entity, newQueryAACube);
|
UpdateEntityOperator theOperator(getThisPointer(), containingElement, entity, newQueryAACube);
|
||||||
recurseTreeWithOperator(&theOperator);
|
recurseTreeWithOperator(&theOperator);
|
||||||
entity->setProperties(properties);
|
if (entity->setProperties(properties)) {
|
||||||
|
emit editingEntityPointer(entity);
|
||||||
|
}
|
||||||
|
|
||||||
// if the entity has children, run UpdateEntityOperator on them. If the children have children, recurse
|
// if the entity has children, run UpdateEntityOperator on them. If the children have children, recurse
|
||||||
QQueue<SpatiallyNestablePointer> toProcess;
|
QQueue<SpatiallyNestablePointer> toProcess;
|
||||||
|
@ -443,6 +551,7 @@ void EntityTree::deleteEntity(const EntityItemID& entityID, bool force, bool ign
|
||||||
|
|
||||||
unhookChildAvatar(entityID);
|
unhookChildAvatar(entityID);
|
||||||
emit deletingEntity(entityID);
|
emit deletingEntity(entityID);
|
||||||
|
emit deletingEntityPointer(existingEntity.get());
|
||||||
|
|
||||||
// NOTE: callers must lock the tree before using this method
|
// NOTE: callers must lock the tree before using this method
|
||||||
DeleteEntityOperator theOperator(getThisPointer(), entityID);
|
DeleteEntityOperator theOperator(getThisPointer(), entityID);
|
||||||
|
@ -451,6 +560,10 @@ void EntityTree::deleteEntity(const EntityItemID& entityID, bool force, bool ign
|
||||||
auto descendantID = descendant->getID();
|
auto descendantID = descendant->getID();
|
||||||
theOperator.addEntityIDToDeleteList(descendantID);
|
theOperator.addEntityIDToDeleteList(descendantID);
|
||||||
emit deletingEntity(descendantID);
|
emit deletingEntity(descendantID);
|
||||||
|
EntityItemPointer descendantEntity = std::dynamic_pointer_cast<EntityItem>(descendant);
|
||||||
|
if (descendantEntity) {
|
||||||
|
emit deletingEntityPointer(descendantEntity.get());
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
recurseTreeWithOperator(&theOperator);
|
recurseTreeWithOperator(&theOperator);
|
||||||
|
@ -500,6 +613,7 @@ void EntityTree::deleteEntities(QSet<EntityItemID> entityIDs, bool force, bool i
|
||||||
unhookChildAvatar(entityID);
|
unhookChildAvatar(entityID);
|
||||||
theOperator.addEntityIDToDeleteList(entityID);
|
theOperator.addEntityIDToDeleteList(entityID);
|
||||||
emit deletingEntity(entityID);
|
emit deletingEntity(entityID);
|
||||||
|
emit deletingEntityPointer(existingEntity.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (theOperator.getEntities().size() > 0) {
|
if (theOperator.getEntities().size() > 0) {
|
||||||
|
@ -1137,7 +1251,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
|
||||||
if (!isPhysics) {
|
if (!isPhysics) {
|
||||||
properties.setLastEditedBy(senderNode->getUUID());
|
properties.setLastEditedBy(senderNode->getUUID());
|
||||||
}
|
}
|
||||||
updateEntity(entityItemID, properties, senderNode);
|
updateEntity(existingEntity, properties, senderNode);
|
||||||
existingEntity->markAsChangedOnServer();
|
existingEntity->markAsChangedOnServer();
|
||||||
endUpdate = usecTimestampNow();
|
endUpdate = usecTimestampNow();
|
||||||
_totalUpdates++;
|
_totalUpdates++;
|
||||||
|
@ -1148,11 +1262,8 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
|
||||||
} else if (!senderNode->getCanRez() && !senderNode->getCanRezTmp()) {
|
} else if (!senderNode->getCanRez() && !senderNode->getCanRezTmp()) {
|
||||||
failedAdd = true;
|
failedAdd = true;
|
||||||
qCDebug(entities) << "User without 'rez rights' [" << senderNode->getUUID()
|
qCDebug(entities) << "User without 'rez rights' [" << senderNode->getUUID()
|
||||||
<< "] attempted to add an entity ID:" << entityItemID;
|
<< "] attempted to add an entity ID:" << entityItemID;
|
||||||
// FIXME after Cert ID property integrated
|
|
||||||
} else if (/*!properties.getCertificateID().isNull() && */!senderNode->getCanRezCertified() && !senderNode->getCanRezTmpCertified()) {
|
|
||||||
qCDebug(entities) << "User without 'certified rez rights' [" << senderNode->getUUID()
|
|
||||||
<< "] attempted to add a certified entity with ID:" << entityItemID;
|
|
||||||
} else {
|
} else {
|
||||||
// this is a new entity... assign a new entityID
|
// this is a new entity... assign a new entityID
|
||||||
properties.setCreated(properties.getLastEdited());
|
properties.setCreated(properties.getLastEdited());
|
||||||
|
@ -1253,7 +1364,7 @@ void EntityTree::entityChanged(EntityItemPointer entity) {
|
||||||
|
|
||||||
|
|
||||||
void EntityTree::fixupNeedsParentFixups() {
|
void EntityTree::fixupNeedsParentFixups() {
|
||||||
MovingEntitiesOperator moveOperator(getThisPointer());
|
MovingEntitiesOperator moveOperator;
|
||||||
|
|
||||||
QWriteLocker locker(&_needsParentFixupLock);
|
QWriteLocker locker(&_needsParentFixupLock);
|
||||||
|
|
||||||
|
@ -1329,6 +1440,13 @@ void EntityTree::deleteDescendantsOfAvatar(QUuid avatarID) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EntityTree::removeFromChildrenOfAvatars(EntityItemPointer entity) {
|
||||||
|
QUuid avatarID = entity->getParentID();
|
||||||
|
if (_childrenOfAvatars.contains(avatarID)) {
|
||||||
|
_childrenOfAvatars[avatarID].remove(entity->getID());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void EntityTree::addToNeedsParentFixupList(EntityItemPointer entity) {
|
void EntityTree::addToNeedsParentFixupList(EntityItemPointer entity) {
|
||||||
QWriteLocker locker(&_needsParentFixupLock);
|
QWriteLocker locker(&_needsParentFixupLock);
|
||||||
_needsParentFixup.append(entity);
|
_needsParentFixup.append(entity);
|
||||||
|
@ -1670,7 +1788,7 @@ QVector<EntityItemID> EntityTree::sendEntities(EntityEditPacketSender* packetSen
|
||||||
// add-entity packet to the server.
|
// add-entity packet to the server.
|
||||||
|
|
||||||
// fix the queryAACubes of any children that were read in before their parents, get them into the correct element
|
// fix the queryAACubes of any children that were read in before their parents, get them into the correct element
|
||||||
MovingEntitiesOperator moveOperator(localTree);
|
MovingEntitiesOperator moveOperator;
|
||||||
QHash<EntityItemID, EntityItemID>::iterator i = map.begin();
|
QHash<EntityItemID, EntityItemID>::iterator i = map.begin();
|
||||||
while (i != map.end()) {
|
while (i != map.end()) {
|
||||||
EntityItemID newID = i.value();
|
EntityItemID newID = i.value();
|
||||||
|
|
|
@ -19,11 +19,12 @@
|
||||||
#include <SpatialParentFinder.h>
|
#include <SpatialParentFinder.h>
|
||||||
|
|
||||||
class EntityTree;
|
class EntityTree;
|
||||||
typedef std::shared_ptr<EntityTree> EntityTreePointer;
|
using EntityTreePointer = std::shared_ptr<EntityTree>;
|
||||||
|
|
||||||
|
|
||||||
|
#include "AddEntityOperator.h"
|
||||||
#include "EntityTreeElement.h"
|
#include "EntityTreeElement.h"
|
||||||
#include "DeleteEntityOperator.h"
|
#include "DeleteEntityOperator.h"
|
||||||
|
#include "MovingEntitiesOperator.h"
|
||||||
|
|
||||||
class EntityEditFilters;
|
class EntityEditFilters;
|
||||||
class Model;
|
class Model;
|
||||||
|
@ -80,6 +81,10 @@ public:
|
||||||
|
|
||||||
virtual void eraseAllOctreeElements(bool createNewRoot = true) override;
|
virtual void eraseAllOctreeElements(bool createNewRoot = true) override;
|
||||||
|
|
||||||
|
virtual void readBitstreamToTree(const unsigned char* bitstream,
|
||||||
|
uint64_t bufferSizeBytes, ReadBitstreamToTreeParams& args) override;
|
||||||
|
int readEntityDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args);
|
||||||
|
|
||||||
// These methods will allow the OctreeServer to send your tree inbound edit packets of your
|
// These methods will allow the OctreeServer to send your tree inbound edit packets of your
|
||||||
// own definition. Implement these to allow your octree based server to support editing
|
// own definition. Implement these to allow your octree based server to support editing
|
||||||
virtual bool getWantSVOfileVersions() const override { return true; }
|
virtual bool getWantSVOfileVersions() const override { return true; }
|
||||||
|
@ -254,6 +259,7 @@ public:
|
||||||
void knowAvatarID(QUuid avatarID) { _avatarIDs += avatarID; }
|
void knowAvatarID(QUuid avatarID) { _avatarIDs += avatarID; }
|
||||||
void forgetAvatarID(QUuid avatarID) { _avatarIDs -= avatarID; }
|
void forgetAvatarID(QUuid avatarID) { _avatarIDs -= avatarID; }
|
||||||
void deleteDescendantsOfAvatar(QUuid avatarID);
|
void deleteDescendantsOfAvatar(QUuid avatarID);
|
||||||
|
void removeFromChildrenOfAvatars(EntityItemPointer entity);
|
||||||
|
|
||||||
void addToNeedsParentFixupList(EntityItemPointer entity);
|
void addToNeedsParentFixupList(EntityItemPointer entity);
|
||||||
|
|
||||||
|
@ -263,7 +269,9 @@ public:
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void deletingEntity(const EntityItemID& entityID);
|
void deletingEntity(const EntityItemID& entityID);
|
||||||
|
void deletingEntityPointer(EntityItem* entityID);
|
||||||
void addingEntity(const EntityItemID& entityID);
|
void addingEntity(const EntityItemID& entityID);
|
||||||
|
void editingEntityPointer(const EntityItemPointer& entityID);
|
||||||
void entityScriptChanging(const EntityItemID& entityItemID, const bool reload);
|
void entityScriptChanging(const EntityItemID& entityItemID, const bool reload);
|
||||||
void entityServerScriptChanging(const EntityItemID& entityItemID, const bool reload);
|
void entityServerScriptChanging(const EntityItemID& entityItemID, const bool reload);
|
||||||
void newCollisionSoundURL(const QUrl& url, const EntityItemID& entityID);
|
void newCollisionSoundURL(const QUrl& url, const EntityItemID& entityID);
|
||||||
|
@ -346,6 +354,9 @@ protected:
|
||||||
bool filterProperties(EntityItemPointer& existingEntity, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, FilterType filterType);
|
bool filterProperties(EntityItemPointer& existingEntity, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, FilterType filterType);
|
||||||
bool _hasEntityEditFilter{ false };
|
bool _hasEntityEditFilter{ false };
|
||||||
QStringList _entityScriptSourceWhitelist;
|
QStringList _entityScriptSourceWhitelist;
|
||||||
|
|
||||||
|
MovingEntitiesOperator _entityMover;
|
||||||
|
QHash<EntityItemID, EntityItemPointer> _entitiesToAdd;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_EntityTree_h
|
#endif // hifi_EntityTree_h
|
||||||
|
|
|
@ -107,7 +107,7 @@ bool EntityTreeElement::shouldIncludeChildData(int childIndex, EncodeBitstreamPa
|
||||||
|
|
||||||
OctreeElementExtraEncodeData* extraEncodeData = &entityNodeData->extraEncodeData;
|
OctreeElementExtraEncodeData* extraEncodeData = &entityNodeData->extraEncodeData;
|
||||||
assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes
|
assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes
|
||||||
|
|
||||||
if (extraEncodeData->contains(this)) {
|
if (extraEncodeData->contains(this)) {
|
||||||
EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData
|
EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData
|
||||||
= std::static_pointer_cast<EntityTreeElementExtraEncodeData>((*extraEncodeData)[this]);
|
= std::static_pointer_cast<EntityTreeElementExtraEncodeData>((*extraEncodeData)[this]);
|
||||||
|
@ -305,7 +305,7 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData
|
||||||
int numberOfEntitiesOffset = 0;
|
int numberOfEntitiesOffset = 0;
|
||||||
withReadLock([&] {
|
withReadLock([&] {
|
||||||
QVector<uint16_t> indexesOfEntitiesToInclude;
|
QVector<uint16_t> indexesOfEntitiesToInclude;
|
||||||
|
|
||||||
// It's possible that our element has been previous completed. In this case we'll simply not include any of our
|
// It's possible that our element has been previous completed. In this case we'll simply not include any of our
|
||||||
// entities for encoding. This is needed because we encode the element data at the "parent" level, and so we
|
// entities for encoding. This is needed because we encode the element data at the "parent" level, and so we
|
||||||
// need to handle the case where our sibling elements need encoding but we don't.
|
// need to handle the case where our sibling elements need encoding but we don't.
|
||||||
|
@ -432,11 +432,11 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData
|
||||||
// and include the entity in our final count of entities
|
// and include the entity in our final count of entities
|
||||||
packetData->endLevel(entityLevel);
|
packetData->endLevel(entityLevel);
|
||||||
actualNumberOfEntities++;
|
actualNumberOfEntities++;
|
||||||
}
|
|
||||||
|
|
||||||
// If the entity item got completely appended, then we can remove it from the extra encode data
|
// If the entity item got completely appended, then we can remove it from the extra encode data
|
||||||
if (appendEntityState == OctreeElement::COMPLETED) {
|
if (appendEntityState == OctreeElement::COMPLETED) {
|
||||||
entityTreeElementExtraEncodeData->entities.remove(entity->getEntityItemID());
|
entityTreeElementExtraEncodeData->entities.remove(entity->getEntityItemID());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If any part of the entity items didn't fit, then the element is considered partial
|
// If any part of the entity items didn't fit, then the element is considered partial
|
||||||
|
@ -893,6 +893,7 @@ void EntityTreeElement::cleanupEntities() {
|
||||||
}
|
}
|
||||||
_entityItems.clear();
|
_entityItems.clear();
|
||||||
});
|
});
|
||||||
|
bumpChangedContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EntityTreeElement::removeEntityWithEntityItemID(const EntityItemID& id) {
|
bool EntityTreeElement::removeEntityWithEntityItemID(const EntityItemID& id) {
|
||||||
|
@ -906,6 +907,7 @@ bool EntityTreeElement::removeEntityWithEntityItemID(const EntityItemID& id) {
|
||||||
// NOTE: only EntityTreeElement should ever be changing the value of entity->_element
|
// NOTE: only EntityTreeElement should ever be changing the value of entity->_element
|
||||||
entity->_element = NULL;
|
entity->_element = NULL;
|
||||||
_entityItems.removeAt(i);
|
_entityItems.removeAt(i);
|
||||||
|
bumpChangedContent();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -922,144 +924,16 @@ bool EntityTreeElement::removeEntityItem(EntityItemPointer entity) {
|
||||||
// NOTE: only EntityTreeElement should ever be changing the value of entity->_element
|
// NOTE: only EntityTreeElement should ever be changing the value of entity->_element
|
||||||
assert(entity->_element.get() == this);
|
assert(entity->_element.get() == this);
|
||||||
entity->_element = NULL;
|
entity->_element = NULL;
|
||||||
|
bumpChangedContent();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Things we want to accomplish as we read these entities from the data buffer.
|
|
||||||
//
|
|
||||||
// 1) correctly update the properties of the entity
|
|
||||||
// 2) add any new entities that didn't previously exist
|
|
||||||
//
|
|
||||||
// TODO: Do we also need to do this?
|
|
||||||
// 3) mark our tree as dirty down to the path of the previous location of the entity
|
|
||||||
// 4) mark our tree as dirty down to the path of the new location of the entity
|
|
||||||
//
|
|
||||||
// Since we're potentially reading several entities, we'd prefer to do all the moving around
|
|
||||||
// and dirty path marking in one pass.
|
|
||||||
int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
|
int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
|
||||||
ReadBitstreamToTreeParams& args) {
|
ReadBitstreamToTreeParams& args) {
|
||||||
// If we're the root, but this bitstream doesn't support root elements with data, then
|
return _myTree->readEntityDataFromBuffer(data, bytesLeftToRead, args);
|
||||||
// return without reading any bytes
|
|
||||||
if (this == _myTree->getRoot().get() && args.bitstreamVersion < VERSION_ROOT_ELEMENT_HAS_DATA) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const unsigned char* dataAt = data;
|
|
||||||
int bytesRead = 0;
|
|
||||||
uint16_t numberOfEntities = 0;
|
|
||||||
int expectedBytesPerEntity = EntityItem::expectedBytes();
|
|
||||||
|
|
||||||
args.elementsPerPacket++;
|
|
||||||
|
|
||||||
if (bytesLeftToRead >= (int)sizeof(numberOfEntities)) {
|
|
||||||
// read our entities in....
|
|
||||||
numberOfEntities = *(uint16_t*)dataAt;
|
|
||||||
|
|
||||||
dataAt += sizeof(numberOfEntities);
|
|
||||||
bytesLeftToRead -= (int)sizeof(numberOfEntities);
|
|
||||||
bytesRead += sizeof(numberOfEntities);
|
|
||||||
|
|
||||||
if (bytesLeftToRead >= (int)(numberOfEntities * expectedBytesPerEntity)) {
|
|
||||||
for (uint16_t i = 0; i < numberOfEntities; i++) {
|
|
||||||
int bytesForThisEntity = 0;
|
|
||||||
EntityItemID entityItemID;
|
|
||||||
EntityItemPointer entityItem = NULL;
|
|
||||||
|
|
||||||
// Old model files don't have UUIDs in them. So we don't want to try to read those IDs from the stream.
|
|
||||||
// Since this can only happen on loading an old file, we can safely treat these as new entity cases,
|
|
||||||
// which will correctly handle the case of creating models and letting them parse the old format.
|
|
||||||
if (args.bitstreamVersion >= VERSION_ENTITIES_SUPPORT_SPLIT_MTU) {
|
|
||||||
entityItemID = EntityItemID::readEntityItemIDFromBuffer(dataAt, bytesLeftToRead);
|
|
||||||
entityItem = _myTree->findEntityByEntityItemID(entityItemID);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the item already exists in our tree, we want do the following...
|
|
||||||
// 1) allow the existing item to read from the databuffer
|
|
||||||
// 2) check to see if after reading the item, the containing element is still correct, fix it if needed
|
|
||||||
//
|
|
||||||
// TODO: Do we need to also do this?
|
|
||||||
// 3) remember the old cube for the entity so we can mark it as dirty
|
|
||||||
if (entityItem) {
|
|
||||||
QString entityScriptBefore = entityItem->getScript();
|
|
||||||
QUuid parentIDBefore = entityItem->getParentID();
|
|
||||||
QString entityServerScriptsBefore = entityItem->getServerScripts();
|
|
||||||
quint64 entityScriptTimestampBefore = entityItem->getScriptTimestamp();
|
|
||||||
bool bestFitBefore = bestFitEntityBounds(entityItem);
|
|
||||||
EntityTreeElementPointer currentContainingElement = _myTree->getContainingElement(entityItemID);
|
|
||||||
|
|
||||||
bytesForThisEntity = entityItem->readEntityDataFromBuffer(dataAt, bytesLeftToRead, args);
|
|
||||||
if (entityItem->getDirtyFlags()) {
|
|
||||||
_myTree->entityChanged(entityItem);
|
|
||||||
}
|
|
||||||
bool bestFitAfter = bestFitEntityBounds(entityItem);
|
|
||||||
|
|
||||||
if (bestFitBefore != bestFitAfter) {
|
|
||||||
// This is the case where the entity existed, and is in some element in our tree...
|
|
||||||
if (!bestFitBefore && bestFitAfter) {
|
|
||||||
// This is the case where the entity existed, and is in some element in our tree...
|
|
||||||
if (currentContainingElement.get() != this) {
|
|
||||||
// if the currentContainingElement is non-null, remove the entity from it
|
|
||||||
if (currentContainingElement) {
|
|
||||||
currentContainingElement->removeEntityItem(entityItem);
|
|
||||||
}
|
|
||||||
addEntityItem(entityItem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QString entityScriptAfter = entityItem->getScript();
|
|
||||||
QString entityServerScriptsAfter = entityItem->getServerScripts();
|
|
||||||
quint64 entityScriptTimestampAfter = entityItem->getScriptTimestamp();
|
|
||||||
bool reload = entityScriptTimestampBefore != entityScriptTimestampAfter;
|
|
||||||
|
|
||||||
// If the script value has changed on us, or it's timestamp has changed to force
|
|
||||||
// a reload then we want to send out a script changing signal...
|
|
||||||
if (entityScriptBefore != entityScriptAfter || reload) {
|
|
||||||
_myTree->emitEntityScriptChanging(entityItemID, reload); // the entity script has changed
|
|
||||||
}
|
|
||||||
if (entityServerScriptsBefore != entityServerScriptsAfter || reload) {
|
|
||||||
_myTree->emitEntityServerScriptChanging(entityItemID, reload); // the entity server script has changed
|
|
||||||
}
|
|
||||||
|
|
||||||
QUuid parentIDAfter = entityItem->getParentID();
|
|
||||||
if (parentIDBefore != parentIDAfter) {
|
|
||||||
_myTree->addToNeedsParentFixupList(entityItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
entityItem = EntityTypes::constructEntityItem(dataAt, bytesLeftToRead, args);
|
|
||||||
if (entityItem) {
|
|
||||||
bytesForThisEntity = entityItem->readEntityDataFromBuffer(dataAt, bytesLeftToRead, args);
|
|
||||||
|
|
||||||
// don't add if we've recently deleted....
|
|
||||||
if (!_myTree->isDeletedEntity(entityItem->getID())) {
|
|
||||||
_myTree->addEntityMapEntry(entityItem);
|
|
||||||
addEntityItem(entityItem); // add this new entity to this elements entities
|
|
||||||
entityItemID = entityItem->getEntityItemID();
|
|
||||||
_myTree->postAddEntity(entityItem);
|
|
||||||
if (entityItem->getCreated() == UNKNOWN_CREATED_TIME) {
|
|
||||||
entityItem->recordCreationTime();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
#ifdef WANT_DEBUG
|
|
||||||
qCDebug(entities) << "Received packet for previously deleted entity [" <<
|
|
||||||
entityItem->getID() << "] ignoring. (inside " << __FUNCTION__ << ")";
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Move the buffer forward to read more entities
|
|
||||||
dataAt += bytesForThisEntity;
|
|
||||||
bytesLeftToRead -= bytesForThisEntity;
|
|
||||||
bytesRead += bytesForThisEntity;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return bytesRead;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityTreeElement::addEntityItem(EntityItemPointer entity) {
|
void EntityTreeElement::addEntityItem(EntityItemPointer entity) {
|
||||||
|
@ -1068,6 +942,7 @@ void EntityTreeElement::addEntityItem(EntityItemPointer entity) {
|
||||||
withWriteLock([&] {
|
withWriteLock([&] {
|
||||||
_entityItems.push_back(entity);
|
_entityItems.push_back(entity);
|
||||||
});
|
});
|
||||||
|
bumpChangedContent();
|
||||||
entity->_element = getThisPointer();
|
entity->_element = getThisPointer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,11 +21,12 @@
|
||||||
#include "EntityItem.h"
|
#include "EntityItem.h"
|
||||||
#include "EntityTree.h"
|
#include "EntityTree.h"
|
||||||
|
|
||||||
typedef QVector<EntityItemPointer> EntityItems;
|
|
||||||
|
|
||||||
class EntityTree;
|
class EntityTree;
|
||||||
class EntityTreeElement;
|
class EntityTreeElement;
|
||||||
typedef std::shared_ptr<EntityTreeElement> EntityTreeElementPointer;
|
|
||||||
|
using EntityItems = QVector<EntityItemPointer>;
|
||||||
|
using EntityTreeElementWeakPointer = std::weak_ptr<EntityTreeElement>;
|
||||||
|
using EntityTreeElementPointer = std::shared_ptr<EntityTreeElement>;
|
||||||
|
|
||||||
class EntityTreeUpdateArgs {
|
class EntityTreeUpdateArgs {
|
||||||
public:
|
public:
|
||||||
|
@ -173,7 +174,6 @@ public:
|
||||||
void setTree(EntityTreePointer tree) { _myTree = tree; }
|
void setTree(EntityTreePointer tree) { _myTree = tree; }
|
||||||
EntityTreePointer getTree() const { return _myTree; }
|
EntityTreePointer getTree() const { return _myTree; }
|
||||||
|
|
||||||
bool updateEntity(const EntityItem& entity);
|
|
||||||
void addEntityItem(EntityItemPointer entity);
|
void addEntityItem(EntityItemPointer entity);
|
||||||
|
|
||||||
EntityItemPointer getClosestEntity(glm::vec3 position) const;
|
EntityItemPointer getClosestEntity(glm::vec3 position) const;
|
||||||
|
@ -238,10 +238,14 @@ public:
|
||||||
return std::static_pointer_cast<const OctreeElement>(shared_from_this());
|
return std::static_pointer_cast<const OctreeElement>(shared_from_this());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void bumpChangedContent() { _lastChangedContent = usecTimestampNow(); }
|
||||||
|
uint64_t getLastChangedContent() const { return _lastChangedContent; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void init(unsigned char * octalCode) override;
|
virtual void init(unsigned char * octalCode) override;
|
||||||
EntityTreePointer _myTree;
|
EntityTreePointer _myTree;
|
||||||
EntityItems _entityItems;
|
EntityItems _entityItems;
|
||||||
|
uint64_t _lastChangedContent { 0 };
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_EntityTreeElement_h
|
#endif // hifi_EntityTreeElement_h
|
||||||
|
|
|
@ -16,15 +16,7 @@
|
||||||
|
|
||||||
#include "MovingEntitiesOperator.h"
|
#include "MovingEntitiesOperator.h"
|
||||||
|
|
||||||
MovingEntitiesOperator::MovingEntitiesOperator(EntityTreePointer tree) :
|
MovingEntitiesOperator::MovingEntitiesOperator() { }
|
||||||
_tree(tree),
|
|
||||||
_changeTime(usecTimestampNow()),
|
|
||||||
_foundOldCount(0),
|
|
||||||
_foundNewCount(0),
|
|
||||||
_lookingCount(0),
|
|
||||||
_wantDebug(false)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
MovingEntitiesOperator::~MovingEntitiesOperator() {
|
MovingEntitiesOperator::~MovingEntitiesOperator() {
|
||||||
if (_wantDebug) {
|
if (_wantDebug) {
|
||||||
|
@ -146,7 +138,7 @@ bool MovingEntitiesOperator::preRecursion(const OctreeElementPointer& element) {
|
||||||
|
|
||||||
// In Pre-recursion, we're generally deciding whether or not we want to recurse this
|
// In Pre-recursion, we're generally deciding whether or not we want to recurse this
|
||||||
// path of the tree. For this operation, we want to recurse the branch of the tree if
|
// path of the tree. For this operation, we want to recurse the branch of the tree if
|
||||||
// and of the following are true:
|
// any of the following are true:
|
||||||
// * We have not yet found the old entity, and this branch contains our old entity
|
// * We have not yet found the old entity, and this branch contains our old entity
|
||||||
// * We have not yet found the new entity, and this branch contains our new entity
|
// * We have not yet found the new entity, and this branch contains our new entity
|
||||||
//
|
//
|
||||||
|
@ -200,6 +192,8 @@ bool MovingEntitiesOperator::preRecursion(const OctreeElementPointer& element) {
|
||||||
oldElement->removeEntityItem(details.entity);
|
oldElement->removeEntityItem(details.entity);
|
||||||
}
|
}
|
||||||
entityTreeElement->addEntityItem(details.entity);
|
entityTreeElement->addEntityItem(details.entity);
|
||||||
|
} else {
|
||||||
|
entityTreeElement->bumpChangedContent();
|
||||||
}
|
}
|
||||||
_foundNewCount++;
|
_foundNewCount++;
|
||||||
//details.newFound = true; // TODO: would be nice to add this optimization
|
//details.newFound = true; // TODO: would be nice to add this optimization
|
||||||
|
@ -230,8 +224,6 @@ bool MovingEntitiesOperator::postRecursion(const OctreeElementPointer& element)
|
||||||
if ((shouldRecurseSubTree(element))) {
|
if ((shouldRecurseSubTree(element))) {
|
||||||
element->markWithChangedTime();
|
element->markWithChangedTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// It's not OK to prune if we have the potential of deleting the original containing element
|
// It's not OK to prune if we have the potential of deleting the original containing element
|
||||||
// because if we prune the containing element then new might end up reallocating the same memory later
|
// because if we prune the containing element then new might end up reallocating the same memory later
|
||||||
|
@ -286,3 +278,10 @@ OctreeElementPointer MovingEntitiesOperator::possiblyCreateChildAt(const OctreeE
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MovingEntitiesOperator::reset() {
|
||||||
|
_entitiesToMove.clear();
|
||||||
|
_foundOldCount = 0;
|
||||||
|
_foundNewCount = 0;
|
||||||
|
_lookingCount = 0;
|
||||||
|
}
|
||||||
|
|
|
@ -12,6 +12,11 @@
|
||||||
#ifndef hifi_MovingEntitiesOperator_h
|
#ifndef hifi_MovingEntitiesOperator_h
|
||||||
#define hifi_MovingEntitiesOperator_h
|
#define hifi_MovingEntitiesOperator_h
|
||||||
|
|
||||||
|
#include <QSet>
|
||||||
|
|
||||||
|
#include "EntityTypes.h"
|
||||||
|
#include "EntityTreeElement.h"
|
||||||
|
|
||||||
class EntityToMoveDetails {
|
class EntityToMoveDetails {
|
||||||
public:
|
public:
|
||||||
EntityItemPointer entity;
|
EntityItemPointer entity;
|
||||||
|
@ -34,7 +39,7 @@ inline bool operator==(const EntityToMoveDetails& a, const EntityToMoveDetails&
|
||||||
|
|
||||||
class MovingEntitiesOperator : public RecurseOctreeOperator {
|
class MovingEntitiesOperator : public RecurseOctreeOperator {
|
||||||
public:
|
public:
|
||||||
MovingEntitiesOperator(EntityTreePointer tree);
|
MovingEntitiesOperator();
|
||||||
~MovingEntitiesOperator();
|
~MovingEntitiesOperator();
|
||||||
|
|
||||||
void addEntityToMoveList(EntityItemPointer entity, const AACube& newCube);
|
void addEntityToMoveList(EntityItemPointer entity, const AACube& newCube);
|
||||||
|
@ -42,16 +47,15 @@ public:
|
||||||
virtual bool postRecursion(const OctreeElementPointer& element) override;
|
virtual bool postRecursion(const OctreeElementPointer& element) override;
|
||||||
virtual OctreeElementPointer possiblyCreateChildAt(const OctreeElementPointer& element, int childIndex) override;
|
virtual OctreeElementPointer possiblyCreateChildAt(const OctreeElementPointer& element, int childIndex) override;
|
||||||
bool hasMovingEntities() const { return _entitiesToMove.size() > 0; }
|
bool hasMovingEntities() const { return _entitiesToMove.size() > 0; }
|
||||||
|
void reset();
|
||||||
private:
|
private:
|
||||||
EntityTreePointer _tree;
|
|
||||||
QSet<EntityToMoveDetails> _entitiesToMove;
|
|
||||||
quint64 _changeTime;
|
|
||||||
int _foundOldCount;
|
|
||||||
int _foundNewCount;
|
|
||||||
int _lookingCount;
|
|
||||||
bool shouldRecurseSubTree(const OctreeElementPointer& element);
|
bool shouldRecurseSubTree(const OctreeElementPointer& element);
|
||||||
|
|
||||||
bool _wantDebug;
|
QSet<EntityToMoveDetails> _entitiesToMove;
|
||||||
|
int _foundOldCount { 0 };
|
||||||
|
int _foundNewCount { 0 };
|
||||||
|
int _lookingCount { 0 };
|
||||||
|
bool _wantDebug { false };
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_MovingEntitiesOperator_h
|
#endif // hifi_MovingEntitiesOperator_h
|
||||||
|
|
|
@ -173,6 +173,8 @@ bool UpdateEntityOperator::preRecursion(const OctreeElementPointer& element) {
|
||||||
if (oldElement != _containingElement) {
|
if (oldElement != _containingElement) {
|
||||||
qCDebug(entities) << "WARNING entity moved during UpdateEntityOperator recursion";
|
qCDebug(entities) << "WARNING entity moved during UpdateEntityOperator recursion";
|
||||||
_containingElement->removeEntityItem(_existingEntity);
|
_containingElement->removeEntityItem(_existingEntity);
|
||||||
|
} else {
|
||||||
|
_containingElement->bumpChangedContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_wantDebug) {
|
if (_wantDebug) {
|
||||||
|
@ -211,6 +213,7 @@ bool UpdateEntityOperator::preRecursion(const OctreeElementPointer& element) {
|
||||||
if (_wantDebug) {
|
if (_wantDebug) {
|
||||||
qCDebug(entities) << " *** This is the same OLD ELEMENT ***";
|
qCDebug(entities) << " *** This is the same OLD ELEMENT ***";
|
||||||
}
|
}
|
||||||
|
_containingElement->bumpChangedContent();
|
||||||
} else {
|
} else {
|
||||||
// otherwise, this is an add case.
|
// otherwise, this is an add case.
|
||||||
if (oldElement) {
|
if (oldElement) {
|
||||||
|
|
|
@ -92,7 +92,8 @@ public:
|
||||||
OUT_OF_VIEW,
|
OUT_OF_VIEW,
|
||||||
WAS_IN_VIEW,
|
WAS_IN_VIEW,
|
||||||
NO_CHANGE,
|
NO_CHANGE,
|
||||||
OCCLUDED
|
OCCLUDED,
|
||||||
|
FINISHED
|
||||||
} reason;
|
} reason;
|
||||||
reason stopReason;
|
reason stopReason;
|
||||||
|
|
||||||
|
@ -232,7 +233,7 @@ public:
|
||||||
|
|
||||||
virtual void eraseAllOctreeElements(bool createNewRoot = true);
|
virtual void eraseAllOctreeElements(bool createNewRoot = true);
|
||||||
|
|
||||||
void readBitstreamToTree(const unsigned char* bitstream, uint64_t bufferSizeBytes, ReadBitstreamToTreeParams& args);
|
virtual void readBitstreamToTree(const unsigned char* bitstream, uint64_t bufferSizeBytes, ReadBitstreamToTreeParams& args);
|
||||||
void deleteOctalCodeFromTree(const unsigned char* codeBuffer, bool collapseEmptyTrees = DONT_COLLAPSE);
|
void deleteOctalCodeFromTree(const unsigned char* codeBuffer, bool collapseEmptyTrees = DONT_COLLAPSE);
|
||||||
void reaverageOctreeElements(OctreeElementPointer startElement = OctreeElementPointer());
|
void reaverageOctreeElements(OctreeElementPointer startElement = OctreeElementPointer());
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,8 @@ const int TREE_SCALE = 32768; // ~20 miles.. This is the number of meters of the
|
||||||
const int HALF_TREE_SCALE = TREE_SCALE / 2;
|
const int HALF_TREE_SCALE = TREE_SCALE / 2;
|
||||||
|
|
||||||
// This controls the LOD. Larger number will make smaller voxels visible at greater distance.
|
// This controls the LOD. Larger number will make smaller voxels visible at greater distance.
|
||||||
const float DEFAULT_OCTREE_SIZE_SCALE = TREE_SCALE * 400.0f;
|
const float MAX_VISIBILITY_DISTANCE_FOR_UNIT_ELEMENT = 400.0f; // max distance where a 1x1x1 cube is visible for 20:20 vision
|
||||||
|
const float DEFAULT_OCTREE_SIZE_SCALE = TREE_SCALE * MAX_VISIBILITY_DISTANCE_FOR_UNIT_ELEMENT;
|
||||||
|
|
||||||
// Since entities like models live inside of octree cells, and they themselves can have very small mesh parts,
|
// Since entities like models live inside of octree cells, and they themselves can have very small mesh parts,
|
||||||
// we want to have some constant that controls have big a mesh part must be to render even if the octree cell itself
|
// we want to have some constant that controls have big a mesh part must be to render even if the octree cell itself
|
||||||
|
@ -36,7 +37,10 @@ const int NUMBER_OF_CHILDREN = 8;
|
||||||
|
|
||||||
const int MAX_TREE_SLICE_BYTES = 26;
|
const int MAX_TREE_SLICE_BYTES = 26;
|
||||||
|
|
||||||
const float VIEW_FRUSTUM_FOV_OVERSEND = 60.0f;
|
// The oversend below is 20 degrees because that is the minimum oversend necessary to prevent missing entities
|
||||||
|
// near the edge of the view. The value here is determined by hard-coded values in ViewFrsutum::isVerySimilar().
|
||||||
|
// TODO: move this parameter to the OctreeQueryNode context.
|
||||||
|
const float VIEW_FRUSTUM_FOV_OVERSEND = 20.0f;
|
||||||
|
|
||||||
// These are guards to prevent our voxel tree recursive routines from spinning out of control
|
// These are guards to prevent our voxel tree recursive routines from spinning out of control
|
||||||
const int UNREASONABLY_DEEP_RECURSION = 29; // use this for something that you want to be shallow, but not spin out
|
const int UNREASONABLY_DEEP_RECURSION = 29; // use this for something that you want to be shallow, but not spin out
|
||||||
|
|
|
@ -582,9 +582,7 @@ bool OctreePacketData::compressContent() {
|
||||||
|
|
||||||
if (compressedData.size() < (int)MAX_OCTREE_PACKET_DATA_SIZE) {
|
if (compressedData.size() < (int)MAX_OCTREE_PACKET_DATA_SIZE) {
|
||||||
_compressedBytes = compressedData.size();
|
_compressedBytes = compressedData.size();
|
||||||
for (int i = 0; i < _compressedBytes; i++) {
|
memcpy(_compressed, compressedData.constData(), _compressedBytes);
|
||||||
_compressed[i] = compressedData[i];
|
|
||||||
}
|
|
||||||
_dirty = false;
|
_dirty = false;
|
||||||
success = true;
|
success = true;
|
||||||
}
|
}
|
||||||
|
@ -598,25 +596,22 @@ void OctreePacketData::loadFinalizedContent(const unsigned char* data, int lengt
|
||||||
if (data && length > 0) {
|
if (data && length > 0) {
|
||||||
|
|
||||||
if (_enableCompression) {
|
if (_enableCompression) {
|
||||||
QByteArray compressedData;
|
|
||||||
for (int i = 0; i < length; i++) {
|
|
||||||
compressedData[i] = data[i];
|
|
||||||
_compressed[i] = compressedData[i];
|
|
||||||
}
|
|
||||||
_compressedBytes = length;
|
_compressedBytes = length;
|
||||||
|
memcpy(_compressed, data, _compressedBytes);
|
||||||
|
|
||||||
|
QByteArray compressedData;
|
||||||
|
compressedData.resize(_compressedBytes);
|
||||||
|
memcpy(compressedData.data(), data, _compressedBytes);
|
||||||
|
|
||||||
QByteArray uncompressedData = qUncompress(compressedData);
|
QByteArray uncompressedData = qUncompress(compressedData);
|
||||||
if (uncompressedData.size() <= _bytesAvailable) {
|
if (uncompressedData.size() <= _bytesAvailable) {
|
||||||
_bytesInUse = uncompressedData.size();
|
_bytesInUse = uncompressedData.size();
|
||||||
_bytesAvailable -= uncompressedData.size();
|
_bytesAvailable -= uncompressedData.size();
|
||||||
|
memcpy(_uncompressed, uncompressedData.constData(), _bytesInUse);
|
||||||
for (int i = 0; i < _bytesInUse; i++) {
|
|
||||||
_uncompressed[i] = uncompressedData[i];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (int i = 0; i < length; i++) {
|
memcpy(_uncompressed, data, length);
|
||||||
_uncompressed[i] = _compressed[i] = data[i];
|
memcpy(_compressed, data, length);
|
||||||
}
|
|
||||||
_bytesInUse = _compressedBytes = length;
|
_bytesInUse = _compressedBytes = length;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -17,7 +17,18 @@
|
||||||
#include "OctreeConstants.h"
|
#include "OctreeConstants.h"
|
||||||
#include "OctreeQuery.h"
|
#include "OctreeQuery.h"
|
||||||
|
|
||||||
OctreeQuery::OctreeQuery() {
|
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() :
|
||||||
|
_cameraFov(DEFAULT_FOV),
|
||||||
|
_cameraAspectRatio(DEFAULT_ASPECT_RATIO),
|
||||||
|
_cameraNearClip(DEFAULT_NEAR_CLIP),
|
||||||
|
_cameraFarClip(DEFAULT_FAR_CLIP),
|
||||||
|
_cameraCenterRadius(DEFAULT_FAR_CLIP)
|
||||||
|
{
|
||||||
_maxQueryPPS = DEFAULT_MAX_OCTREE_PPS;
|
_maxQueryPPS = DEFAULT_MAX_OCTREE_PPS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -89,14 +89,14 @@ public slots:
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// camera details for the avatar
|
// camera details for the avatar
|
||||||
glm::vec3 _cameraPosition = glm::vec3(0.0f);
|
glm::vec3 _cameraPosition { glm::vec3(0.0f) };
|
||||||
glm::quat _cameraOrientation = glm::quat();
|
glm::quat _cameraOrientation { glm::quat() };
|
||||||
float _cameraFov = 0.0f;
|
float _cameraFov;
|
||||||
float _cameraAspectRatio = 1.0f;
|
float _cameraAspectRatio;
|
||||||
float _cameraNearClip = 0.0f;
|
float _cameraNearClip;
|
||||||
float _cameraFarClip = 0.0f;
|
float _cameraFarClip;
|
||||||
float _cameraCenterRadius { 0.0f };
|
float _cameraCenterRadius;
|
||||||
glm::vec3 _cameraEyeOffsetPosition = glm::vec3(0.0f);
|
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;
|
||||||
|
|
|
@ -182,6 +182,7 @@ bool OctreeQueryNode::updateCurrentViewFrustum() {
|
||||||
getCameraAspectRatio(),
|
getCameraAspectRatio(),
|
||||||
getCameraNearClip(),
|
getCameraNearClip(),
|
||||||
getCameraFarClip()));
|
getCameraFarClip()));
|
||||||
|
newestViewFrustum.calculate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -189,7 +190,6 @@ bool OctreeQueryNode::updateCurrentViewFrustum() {
|
||||||
QMutexLocker viewLocker(&_viewMutex);
|
QMutexLocker viewLocker(&_viewMutex);
|
||||||
if (!newestViewFrustum.isVerySimilar(_currentViewFrustum)) {
|
if (!newestViewFrustum.isVerySimilar(_currentViewFrustum)) {
|
||||||
_currentViewFrustum = newestViewFrustum;
|
_currentViewFrustum = newestViewFrustum;
|
||||||
_currentViewFrustum.calculate();
|
|
||||||
currentViewFrustumChanged = true;
|
currentViewFrustumChanged = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,4 +27,11 @@ float boundaryDistanceForRenderLevel(unsigned int renderLevel, float voxelSizeSc
|
||||||
|
|
||||||
float getAccuracyAngle(float octreeSizeScale, int boundaryLevelAdjust);
|
float getAccuracyAngle(float octreeSizeScale, int boundaryLevelAdjust);
|
||||||
|
|
||||||
|
// MIN_ELEMENT_ANGULAR_DIAMETER = angular diameter of 1x1x1m cube at 400m = sqrt(3) / 400 = 0.0043301 radians ~= 0.25 degrees
|
||||||
|
const float MIN_ELEMENT_ANGULAR_DIAMETER = 0.0043301f; // radians
|
||||||
|
// NOTE: the entity bounding cube is larger than the smallest possible containing octree element by sqrt(3)
|
||||||
|
const float SQRT_THREE = 1.73205080f;
|
||||||
|
const float MIN_ENTITY_ANGULAR_DIAMETER = MIN_ELEMENT_ANGULAR_DIAMETER * SQRT_THREE;
|
||||||
|
const float MIN_VISIBLE_DISTANCE = 0.0001f; // helps avoid divide-by-zero check
|
||||||
|
|
||||||
#endif // hifi_OctreeUtils_h
|
#endif // hifi_OctreeUtils_h
|
||||||
|
|
|
@ -56,16 +56,17 @@ void ViewFrustum::setProjection(const glm::mat4& projection) {
|
||||||
_projection = projection;
|
_projection = projection;
|
||||||
glm::mat4 inverseProjection = glm::inverse(projection);
|
glm::mat4 inverseProjection = glm::inverse(projection);
|
||||||
|
|
||||||
// compute our dimensions the usual way
|
// compute frustum corners
|
||||||
for (int i = 0; i < NUM_FRUSTUM_CORNERS; ++i) {
|
for (int i = 0; i < NUM_FRUSTUM_CORNERS; ++i) {
|
||||||
_corners[i] = inverseProjection * NDC_VALUES[i];
|
_corners[i] = inverseProjection * NDC_VALUES[i];
|
||||||
_corners[i] /= _corners[i].w;
|
_corners[i] /= _corners[i].w;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// compute frustum properties
|
||||||
_nearClip = -_corners[BOTTOM_LEFT_NEAR].z;
|
_nearClip = -_corners[BOTTOM_LEFT_NEAR].z;
|
||||||
_farClip = -_corners[BOTTOM_LEFT_FAR].z;
|
_farClip = -_corners[BOTTOM_LEFT_FAR].z;
|
||||||
_aspectRatio = (_corners[TOP_RIGHT_NEAR].x - _corners[BOTTOM_LEFT_NEAR].x) /
|
_aspectRatio = (_corners[TOP_RIGHT_NEAR].x - _corners[BOTTOM_LEFT_NEAR].x) /
|
||||||
(_corners[TOP_RIGHT_NEAR].y - _corners[BOTTOM_LEFT_NEAR].y);
|
(_corners[TOP_RIGHT_NEAR].y - _corners[BOTTOM_LEFT_NEAR].y);
|
||||||
|
|
||||||
glm::vec4 top = inverseProjection * vec4(0.0f, 1.0f, -1.0f, 1.0f);
|
glm::vec4 top = inverseProjection * vec4(0.0f, 1.0f, -1.0f, 1.0f);
|
||||||
top /= top.w;
|
top /= top.w;
|
||||||
_fieldOfView = abs(glm::degrees(2.0f * abs(glm::angle(vec3(0.0f, 0.0f, -1.0f), glm::normalize(vec3(top))))));
|
_fieldOfView = abs(glm::degrees(2.0f * abs(glm::angle(vec3(0.0f, 0.0f, -1.0f), glm::normalize(vec3(top))))));
|
||||||
|
@ -160,16 +161,12 @@ void ViewFrustum::fromByteArray(const QByteArray& input) {
|
||||||
setCenterRadius(cameraCenterRadius);
|
setCenterRadius(cameraCenterRadius);
|
||||||
|
|
||||||
// Also make sure it's got the correct lens details from the camera
|
// Also make sure it's got the correct lens details from the camera
|
||||||
const float VIEW_FRUSTUM_FOV_OVERSEND = 60.0f;
|
|
||||||
float originalFOV = cameraFov;
|
|
||||||
float wideFOV = originalFOV + VIEW_FRUSTUM_FOV_OVERSEND;
|
|
||||||
|
|
||||||
if (0.0f != cameraAspectRatio &&
|
if (0.0f != cameraAspectRatio &&
|
||||||
0.0f != cameraNearClip &&
|
0.0f != cameraNearClip &&
|
||||||
0.0f != cameraFarClip &&
|
0.0f != cameraFarClip &&
|
||||||
cameraNearClip != cameraFarClip) {
|
cameraNearClip != cameraFarClip) {
|
||||||
setProjection(glm::perspective(
|
setProjection(glm::perspective(
|
||||||
glm::radians(wideFOV), // hack
|
glm::radians(cameraFov),
|
||||||
cameraAspectRatio,
|
cameraAspectRatio,
|
||||||
cameraNearClip,
|
cameraNearClip,
|
||||||
cameraFarClip));
|
cameraFarClip));
|
||||||
|
@ -324,125 +321,27 @@ bool ViewFrustum::boxIntersectsKeyhole(const AABox& box) const {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool testMatches(glm::quat lhs, glm::quat rhs, float epsilon = EPSILON) {
|
bool closeEnough(float a, float b, float relativeError) {
|
||||||
return (fabs(lhs.x - rhs.x) <= epsilon && fabs(lhs.y - rhs.y) <= epsilon && fabs(lhs.z - rhs.z) <= epsilon
|
assert(relativeError >= 0.0f);
|
||||||
&& fabs(lhs.w - rhs.w) <= epsilon);
|
// NOTE: we add EPSILON to the denominator so we can avoid checking for division by zero.
|
||||||
|
// This method works fine when: fabsf(a + b) >> EPSILON
|
||||||
|
return fabsf(a - b) / (0.5f * fabsf(a + b) + EPSILON) < relativeError;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool testMatches(glm::vec3 lhs, glm::vec3 rhs, float epsilon = EPSILON) {
|
// TODO: the slop and relative error should be passed in by argument rather than hard-coded.
|
||||||
return (fabs(lhs.x - rhs.x) <= epsilon && fabs(lhs.y - rhs.y) <= epsilon && fabs(lhs.z - rhs.z) <= epsilon);
|
bool ViewFrustum::isVerySimilar(const ViewFrustum& other) const {
|
||||||
}
|
const float MIN_POSITION_SLOP_SQUARED = 25.0f; // 5 meters squared
|
||||||
|
const float MIN_ORIENTATION_DOT = 0.9924039f; // dot product of two quaternions 10 degrees apart
|
||||||
|
const float MIN_RELATIVE_ERROR = 0.01f; // 1%
|
||||||
|
|
||||||
bool testMatches(float lhs, float rhs, float epsilon = EPSILON) {
|
return glm::distance2(_position, other._position) < MIN_POSITION_SLOP_SQUARED &&
|
||||||
return (fabs(lhs - rhs) <= epsilon);
|
fabsf(glm::dot(_orientation, other._orientation)) > MIN_ORIENTATION_DOT &&
|
||||||
}
|
closeEnough(_fieldOfView, other._fieldOfView, MIN_RELATIVE_ERROR) &&
|
||||||
|
closeEnough(_aspectRatio, other._aspectRatio, MIN_RELATIVE_ERROR) &&
|
||||||
bool ViewFrustum::matches(const ViewFrustum& compareTo, bool debug) const {
|
closeEnough(_nearClip, other._nearClip, MIN_RELATIVE_ERROR) &&
|
||||||
bool result =
|
closeEnough(_farClip, other._farClip, MIN_RELATIVE_ERROR) &&
|
||||||
testMatches(compareTo._position, _position) &&
|
closeEnough(_focalLength, other._focalLength, MIN_RELATIVE_ERROR) &&
|
||||||
testMatches(compareTo._direction, _direction) &&
|
closeEnough(_centerSphereRadius, other._centerSphereRadius, MIN_RELATIVE_ERROR);
|
||||||
testMatches(compareTo._up, _up) &&
|
|
||||||
testMatches(compareTo._right, _right) &&
|
|
||||||
testMatches(compareTo._fieldOfView, _fieldOfView) &&
|
|
||||||
testMatches(compareTo._aspectRatio, _aspectRatio) &&
|
|
||||||
testMatches(compareTo._nearClip, _nearClip) &&
|
|
||||||
testMatches(compareTo._farClip, _farClip) &&
|
|
||||||
testMatches(compareTo._focalLength, _focalLength);
|
|
||||||
|
|
||||||
if (!result && debug) {
|
|
||||||
qCDebug(shared, "ViewFrustum::matches()... result=%s", debug::valueOf(result));
|
|
||||||
qCDebug(shared, "%s -- compareTo._position=%f,%f,%f _position=%f,%f,%f",
|
|
||||||
(testMatches(compareTo._position,_position) ? "MATCHES " : "NO MATCH"),
|
|
||||||
(double)compareTo._position.x, (double)compareTo._position.y, (double)compareTo._position.z,
|
|
||||||
(double)_position.x, (double)_position.y, (double)_position.z);
|
|
||||||
qCDebug(shared, "%s -- compareTo._direction=%f,%f,%f _direction=%f,%f,%f",
|
|
||||||
(testMatches(compareTo._direction, _direction) ? "MATCHES " : "NO MATCH"),
|
|
||||||
(double)compareTo._direction.x, (double)compareTo._direction.y, (double)compareTo._direction.z,
|
|
||||||
(double)_direction.x, (double)_direction.y, (double)_direction.z );
|
|
||||||
qCDebug(shared, "%s -- compareTo._up=%f,%f,%f _up=%f,%f,%f",
|
|
||||||
(testMatches(compareTo._up, _up) ? "MATCHES " : "NO MATCH"),
|
|
||||||
(double)compareTo._up.x, (double)compareTo._up.y, (double)compareTo._up.z,
|
|
||||||
(double)_up.x, (double)_up.y, (double)_up.z );
|
|
||||||
qCDebug(shared, "%s -- compareTo._right=%f,%f,%f _right=%f,%f,%f",
|
|
||||||
(testMatches(compareTo._right, _right) ? "MATCHES " : "NO MATCH"),
|
|
||||||
(double)compareTo._right.x, (double)compareTo._right.y, (double)compareTo._right.z,
|
|
||||||
(double)_right.x, (double)_right.y, (double)_right.z );
|
|
||||||
qCDebug(shared, "%s -- compareTo._fieldOfView=%f _fieldOfView=%f",
|
|
||||||
(testMatches(compareTo._fieldOfView, _fieldOfView) ? "MATCHES " : "NO MATCH"),
|
|
||||||
(double)compareTo._fieldOfView, (double)_fieldOfView);
|
|
||||||
qCDebug(shared, "%s -- compareTo._aspectRatio=%f _aspectRatio=%f",
|
|
||||||
(testMatches(compareTo._aspectRatio, _aspectRatio) ? "MATCHES " : "NO MATCH"),
|
|
||||||
(double)compareTo._aspectRatio, (double)_aspectRatio);
|
|
||||||
qCDebug(shared, "%s -- compareTo._nearClip=%f _nearClip=%f",
|
|
||||||
(testMatches(compareTo._nearClip, _nearClip) ? "MATCHES " : "NO MATCH"),
|
|
||||||
(double)compareTo._nearClip, (double)_nearClip);
|
|
||||||
qCDebug(shared, "%s -- compareTo._farClip=%f _farClip=%f",
|
|
||||||
(testMatches(compareTo._farClip, _farClip) ? "MATCHES " : "NO MATCH"),
|
|
||||||
(double)compareTo._farClip, (double)_farClip);
|
|
||||||
qCDebug(shared, "%s -- compareTo._focalLength=%f _focalLength=%f",
|
|
||||||
(testMatches(compareTo._focalLength, _focalLength) ? "MATCHES " : "NO MATCH"),
|
|
||||||
(double)compareTo._focalLength, (double)_focalLength);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ViewFrustum::isVerySimilar(const ViewFrustum& compareTo, bool debug) const {
|
|
||||||
|
|
||||||
// Compute distance between the two positions
|
|
||||||
const float POSITION_SIMILAR_ENOUGH = 5.0f; // 5 meters
|
|
||||||
float positionDistance = glm::distance(_position, compareTo._position);
|
|
||||||
|
|
||||||
// Compute the angular distance between the two orientations
|
|
||||||
const float ORIENTATION_SIMILAR_ENOUGH = 10.0f; // 10 degrees in any direction
|
|
||||||
glm::quat dQOrientation = _orientation * glm::inverse(compareTo._orientation);
|
|
||||||
float angleOrientation = compareTo._orientation == _orientation ? 0.0f : glm::degrees(glm::angle(dQOrientation));
|
|
||||||
if (isNaN(angleOrientation)) {
|
|
||||||
angleOrientation = 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool result =
|
|
||||||
testMatches(0, positionDistance, POSITION_SIMILAR_ENOUGH) &&
|
|
||||||
testMatches(0, angleOrientation, ORIENTATION_SIMILAR_ENOUGH) &&
|
|
||||||
testMatches(compareTo._fieldOfView, _fieldOfView) &&
|
|
||||||
testMatches(compareTo._aspectRatio, _aspectRatio) &&
|
|
||||||
testMatches(compareTo._nearClip, _nearClip) &&
|
|
||||||
testMatches(compareTo._farClip, _farClip) &&
|
|
||||||
testMatches(compareTo._focalLength, _focalLength);
|
|
||||||
|
|
||||||
|
|
||||||
if (!result && debug) {
|
|
||||||
qCDebug(shared, "ViewFrustum::isVerySimilar()... result=%s\n", debug::valueOf(result));
|
|
||||||
qCDebug(shared, "%s -- compareTo._position=%f,%f,%f _position=%f,%f,%f",
|
|
||||||
(testMatches(compareTo._position,_position, POSITION_SIMILAR_ENOUGH) ?
|
|
||||||
"IS SIMILAR ENOUGH " : "IS NOT SIMILAR ENOUGH"),
|
|
||||||
(double)compareTo._position.x, (double)compareTo._position.y, (double)compareTo._position.z,
|
|
||||||
(double)_position.x, (double)_position.y, (double)_position.z );
|
|
||||||
|
|
||||||
qCDebug(shared, "%s -- positionDistance=%f",
|
|
||||||
(testMatches(0,positionDistance, POSITION_SIMILAR_ENOUGH) ? "IS SIMILAR ENOUGH " : "IS NOT SIMILAR ENOUGH"),
|
|
||||||
(double)positionDistance);
|
|
||||||
|
|
||||||
qCDebug(shared, "%s -- angleOrientation=%f",
|
|
||||||
(testMatches(0, angleOrientation, ORIENTATION_SIMILAR_ENOUGH) ? "IS SIMILAR ENOUGH " : "IS NOT SIMILAR ENOUGH"),
|
|
||||||
(double)angleOrientation);
|
|
||||||
|
|
||||||
qCDebug(shared, "%s -- compareTo._fieldOfView=%f _fieldOfView=%f",
|
|
||||||
(testMatches(compareTo._fieldOfView, _fieldOfView) ? "MATCHES " : "NO MATCH"),
|
|
||||||
(double)compareTo._fieldOfView, (double)_fieldOfView);
|
|
||||||
qCDebug(shared, "%s -- compareTo._aspectRatio=%f _aspectRatio=%f",
|
|
||||||
(testMatches(compareTo._aspectRatio, _aspectRatio) ? "MATCHES " : "NO MATCH"),
|
|
||||||
(double)compareTo._aspectRatio, (double)_aspectRatio);
|
|
||||||
qCDebug(shared, "%s -- compareTo._nearClip=%f _nearClip=%f",
|
|
||||||
(testMatches(compareTo._nearClip, _nearClip) ? "MATCHES " : "NO MATCH"),
|
|
||||||
(double)compareTo._nearClip, (double)_nearClip);
|
|
||||||
qCDebug(shared, "%s -- compareTo._farClip=%f _farClip=%f",
|
|
||||||
(testMatches(compareTo._farClip, _farClip) ? "MATCHES " : "NO MATCH"),
|
|
||||||
(double)compareTo._farClip, (double)_farClip);
|
|
||||||
qCDebug(shared, "%s -- compareTo._focalLength=%f _focalLength=%f",
|
|
||||||
(testMatches(compareTo._focalLength, _focalLength) ? "MATCHES " : "NO MATCH"),
|
|
||||||
(double)compareTo._focalLength, (double)_focalLength);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PickRay ViewFrustum::computePickRay(float x, float y) {
|
PickRay ViewFrustum::computePickRay(float x, float y) {
|
||||||
|
|
|
@ -107,12 +107,7 @@ public:
|
||||||
bool cubeIntersectsKeyhole(const AACube& cube) const;
|
bool cubeIntersectsKeyhole(const AACube& cube) const;
|
||||||
bool boxIntersectsKeyhole(const AABox& box) const;
|
bool boxIntersectsKeyhole(const AABox& box) const;
|
||||||
|
|
||||||
// some frustum comparisons
|
bool isVerySimilar(const ViewFrustum& compareTo) const;
|
||||||
bool matches(const ViewFrustum& compareTo, bool debug = false) const;
|
|
||||||
bool matches(const ViewFrustum* compareTo, bool debug = false) const { return matches(*compareTo, debug); }
|
|
||||||
|
|
||||||
bool isVerySimilar(const ViewFrustum& compareTo, bool debug = false) const;
|
|
||||||
bool isVerySimilar(const ViewFrustum* compareTo, bool debug = false) const { return isVerySimilar(*compareTo, debug); }
|
|
||||||
|
|
||||||
PickRay computePickRay(float x, float y);
|
PickRay computePickRay(float x, float y);
|
||||||
void computePickRay(float x, float y, glm::vec3& origin, glm::vec3& direction) const;
|
void computePickRay(float x, float y, glm::vec3& origin, glm::vec3& direction) const;
|
||||||
|
|
|
@ -334,10 +334,8 @@ void OculusControllerManager::TouchDevice::handleHeadPose(float deltaTime,
|
||||||
glm::mat4 defaultHeadOffset = glm::inverse(inputCalibrationData.defaultCenterEyeMat) *
|
glm::mat4 defaultHeadOffset = glm::inverse(inputCalibrationData.defaultCenterEyeMat) *
|
||||||
inputCalibrationData.defaultHeadMat;
|
inputCalibrationData.defaultHeadMat;
|
||||||
|
|
||||||
controller::Pose hmdHeadPose = pose.transform(sensorToAvatar);
|
|
||||||
|
|
||||||
pose.valid = true;
|
pose.valid = true;
|
||||||
_poseStateMap[controller::HEAD] = hmdHeadPose.postTransform(defaultHeadOffset);
|
_poseStateMap[controller::HEAD] = pose.postTransform(defaultHeadOffset).transform(sensorToAvatar);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OculusControllerManager::TouchDevice::handleRotationForUntrackedHand(const controller::InputCalibrationData& inputCalibrationData,
|
void OculusControllerManager::TouchDevice::handleRotationForUntrackedHand(const controller::InputCalibrationData& inputCalibrationData,
|
||||||
|
|
|
@ -255,6 +255,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
|
||||||
this.messageGrabEntity = false;
|
this.messageGrabEntity = false;
|
||||||
this.grabEntityProps = null;
|
this.grabEntityProps = null;
|
||||||
this.shouldSendStart = false;
|
this.shouldSendStart = false;
|
||||||
|
this.equipedWithSecondary = false;
|
||||||
|
|
||||||
this.parameters = makeDispatcherModuleParameters(
|
this.parameters = makeDispatcherModuleParameters(
|
||||||
300,
|
300,
|
||||||
|
@ -370,6 +371,10 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
|
||||||
return this.rawSecondaryValue < BUMPER_ON_VALUE;
|
return this.rawSecondaryValue < BUMPER_ON_VALUE;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.secondarySmoothedSqueezed = function() {
|
||||||
|
return this.rawSecondaryValue > BUMPER_ON_VALUE;
|
||||||
|
};
|
||||||
|
|
||||||
this.chooseNearEquipHotspots = function(candidateEntityProps, controllerData) {
|
this.chooseNearEquipHotspots = function(candidateEntityProps, controllerData) {
|
||||||
var _this = this;
|
var _this = this;
|
||||||
var collectedHotspots = flatten(candidateEntityProps.map(function(props) {
|
var collectedHotspots = flatten(candidateEntityProps.map(function(props) {
|
||||||
|
@ -592,11 +597,13 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
|
||||||
// if the potentialHotspot os not cloneable and locked return null
|
// if the potentialHotspot os not cloneable and locked return null
|
||||||
|
|
||||||
if (potentialEquipHotspot &&
|
if (potentialEquipHotspot &&
|
||||||
((this.triggerSmoothedSqueezed() && !this.waitForTriggerRelease) || this.messageGrabEntity)) {
|
(((this.triggerSmoothedSqueezed() || this.secondarySmoothedSqueezed()) && !this.waitForTriggerRelease) ||
|
||||||
|
this.messageGrabEntity)) {
|
||||||
this.grabbedHotspot = potentialEquipHotspot;
|
this.grabbedHotspot = potentialEquipHotspot;
|
||||||
this.targetEntityID = this.grabbedHotspot.entityID;
|
this.targetEntityID = this.grabbedHotspot.entityID;
|
||||||
this.startEquipEntity(controllerData);
|
this.startEquipEntity(controllerData);
|
||||||
this.messageGrabEnity = false;
|
this.messageGrabEnity = false;
|
||||||
|
this.equipedWithSecondary = this.secondarySmoothedSqueezed();
|
||||||
return makeRunningValues(true, [potentialEquipHotspot.entityID], []);
|
return makeRunningValues(true, [potentialEquipHotspot.entityID], []);
|
||||||
} else {
|
} else {
|
||||||
return makeRunningValues(false, [], []);
|
return makeRunningValues(false, [], []);
|
||||||
|
@ -627,7 +634,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
|
||||||
return this.checkNearbyHotspots(controllerData, deltaTime, timestamp);
|
return this.checkNearbyHotspots(controllerData, deltaTime, timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (controllerData.secondaryValues[this.hand]) {
|
if (controllerData.secondaryValues[this.hand] && !this.equipedWithSecondary) {
|
||||||
// this.secondaryReleased() will always be true when not depressed
|
// this.secondaryReleased() will always be true when not depressed
|
||||||
// so we cannot simply rely on that for release - ensure that the
|
// so we cannot simply rely on that for release - ensure that the
|
||||||
// trigger was first "prepared" by being pushed in before the release
|
// trigger was first "prepared" by being pushed in before the release
|
||||||
|
@ -644,7 +651,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
|
||||||
|
|
||||||
var dropDetected = this.dropGestureProcess(deltaTime);
|
var dropDetected = this.dropGestureProcess(deltaTime);
|
||||||
|
|
||||||
if (this.triggerSmoothedReleased()) {
|
if (this.triggerSmoothedReleased() || this.secondaryReleased()) {
|
||||||
if (this.shouldSendStart) {
|
if (this.shouldSendStart) {
|
||||||
// we don't want to send startEquip message until the trigger is released. otherwise,
|
// we don't want to send startEquip message until the trigger is released. otherwise,
|
||||||
// guns etc will fire right as they are equipped.
|
// guns etc will fire right as they are equipped.
|
||||||
|
@ -653,6 +660,9 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
|
||||||
this.shouldSendStart = false;
|
this.shouldSendStart = false;
|
||||||
}
|
}
|
||||||
this.waitForTriggerRelease = false;
|
this.waitForTriggerRelease = false;
|
||||||
|
if (this.secondaryReleased() && this.equipedWithSecondary) {
|
||||||
|
this.equipedWithSecondary = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dropDetected && this.prevDropDetected !== dropDetected) {
|
if (dropDetected && this.prevDropDetected !== dropDetected) {
|
||||||
|
|
|
@ -132,7 +132,7 @@ Script.include("/~/system/libraries/controllers.js");
|
||||||
this.updateLaserPointer = function(controllerData) {
|
this.updateLaserPointer = function(controllerData) {
|
||||||
var SEARCH_SPHERE_SIZE = 0.011;
|
var SEARCH_SPHERE_SIZE = 0.011;
|
||||||
var MIN_SPHERE_SIZE = 0.0005;
|
var MIN_SPHERE_SIZE = 0.0005;
|
||||||
var radius = Math.max(1.2 * SEARCH_SPHERE_SIZE * this.intersectionDistance, MIN_SPHERE_SIZE);
|
var radius = Math.max(1.2 * SEARCH_SPHERE_SIZE * this.intersectionDistance, MIN_SPHERE_SIZE) * MyAvatar.sensorToWorldScale;
|
||||||
var dim = {x: radius, y: radius, z: radius};
|
var dim = {x: radius, y: radius, z: radius};
|
||||||
var mode = "hold";
|
var mode = "hold";
|
||||||
if (!this.distanceHolding && !this.distanceRotating) {
|
if (!this.distanceHolding && !this.distanceRotating) {
|
||||||
|
@ -424,7 +424,7 @@ Script.include("/~/system/libraries/controllers.js");
|
||||||
this.laserPointerOff();
|
this.laserPointerOff();
|
||||||
return makeRunningValues(false, [], []);
|
return makeRunningValues(false, [], []);
|
||||||
}
|
}
|
||||||
|
this.intersectionDistance = controllerData.rayPicks[this.hand].distance;
|
||||||
this.updateLaserPointer(controllerData);
|
this.updateLaserPointer(controllerData);
|
||||||
|
|
||||||
var otherModuleName =this.hand === RIGHT_HAND ? "LeftFarActionGrabEntity" : "RightFarActionGrabEntity";
|
var otherModuleName =this.hand === RIGHT_HAND ? "LeftFarActionGrabEntity" : "RightFarActionGrabEntity";
|
||||||
|
|
|
@ -335,7 +335,7 @@ Script.include("/~/system/libraries/controllers.js");
|
||||||
var SCALED_TABLET_MAX_HOVER_DISTANCE = TABLET_MAX_HOVER_DISTANCE * sensorScaleFactor;
|
var SCALED_TABLET_MAX_HOVER_DISTANCE = TABLET_MAX_HOVER_DISTANCE * sensorScaleFactor;
|
||||||
|
|
||||||
if (nearestStylusTarget && nearestStylusTarget.distance > SCALED_TABLET_MIN_TOUCH_DISTANCE &&
|
if (nearestStylusTarget && nearestStylusTarget.distance > SCALED_TABLET_MIN_TOUCH_DISTANCE &&
|
||||||
nearestStylusTarget.distance < SCALED_TABLET_MAX_HOVER_DISTANCE) {
|
nearestStylusTarget.distance < SCALED_TABLET_MAX_HOVER_DISTANCE && !this.getOtherHandController().stylusTouchingTarget) {
|
||||||
|
|
||||||
this.requestTouchFocus(nearestStylusTarget);
|
this.requestTouchFocus(nearestStylusTarget);
|
||||||
|
|
||||||
|
|
|
@ -306,7 +306,7 @@ Grabber.prototype.computeNewGrabPlane = function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
Grabber.prototype.pressEvent = function(event) {
|
Grabber.prototype.pressEvent = function(event) {
|
||||||
if (isInEditMode()) {
|
if (isInEditMode() || HMD.active) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -401,7 +401,7 @@ Grabber.prototype.pressEvent = function(event) {
|
||||||
};
|
};
|
||||||
|
|
||||||
Grabber.prototype.releaseEvent = function(event) {
|
Grabber.prototype.releaseEvent = function(event) {
|
||||||
if (event.isLeftButton!==true || event.isRightButton===true || event.isMiddleButton===true) {
|
if ((event.isLeftButton!==true || event.isRightButton===true || event.isMiddleButton===true) && !HMD.active) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -447,7 +447,7 @@ Grabber.prototype.moveEvent = function(event) {
|
||||||
// during the handling of the event, do as little as possible. We save the updated mouse position,
|
// during the handling of the event, do as little as possible. We save the updated mouse position,
|
||||||
// and start a timer to react to the change. If more changes arrive before the timer fires, only
|
// and start a timer to react to the change. If more changes arrive before the timer fires, only
|
||||||
// the last update will be considered. This is done to avoid backing-up Qt's event queue.
|
// the last update will be considered. This is done to avoid backing-up Qt's event queue.
|
||||||
if (!this.isGrabbing) {
|
if (!this.isGrabbing || HMD.active) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mouse.updateDrag(event);
|
mouse.updateDrag(event);
|
||||||
|
@ -458,7 +458,7 @@ Grabber.prototype.moveEventProcess = function() {
|
||||||
this.moveEventTimer = null;
|
this.moveEventTimer = null;
|
||||||
// see if something added/restored gravity
|
// see if something added/restored gravity
|
||||||
var entityProperties = Entities.getEntityProperties(this.entityID);
|
var entityProperties = Entities.getEntityProperties(this.entityID);
|
||||||
if (!entityProperties || !entityProperties.gravity) {
|
if (!entityProperties || !entityProperties.gravity || HMD.active) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1474,6 +1474,8 @@ function onFileSaveChanged(filename) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function onFileOpenChanged(filename) {
|
function onFileOpenChanged(filename) {
|
||||||
|
// disconnect the event, otherwise the requests will stack up
|
||||||
|
Window.openFileChanged.disconnect(onFileOpenChanged);
|
||||||
var importURL = null;
|
var importURL = null;
|
||||||
if (filename !== "") {
|
if (filename !== "") {
|
||||||
importURL = "file:///" + filename;
|
importURL = "file:///" + filename;
|
||||||
|
|
Loading…
Reference in a new issue