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

This commit is contained in:
Zach Fox 2017-10-02 09:26:59 -07:00
commit 1ec0f5d9a0
50 changed files with 1630 additions and 578 deletions

View 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;
}

View 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

View file

@ -13,9 +13,18 @@
#include <EntityNodeData.h>
#include <EntityTypes.h>
#include <OctreeUtils.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() {
auto node = _node.toStrongRef();
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,
EntityItem& entityItem, EntityNodeData& nodeData) {
// 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;
}
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);
}

View file

@ -12,24 +12,55 @@
#ifndef hifi_EntityTreeSendThread_h
#define hifi_EntityTreeSendThread_h
#include <unordered_set>
#include "../octree/OctreeSendThread.h"
#include <DiffTraversal.h>
#include "EntityPriorityQueue.h"
class EntityNodeData;
class EntityItem;
class EntityTreeSendThread : public OctreeSendThread {
Q_OBJECT
public:
EntityTreeSendThread(OctreeServer* myServer, const SharedNodePointer& node) : OctreeSendThread(myServer, node) {};
EntityTreeSendThread(OctreeServer* myServer, const SharedNodePointer& node);
protected:
virtual void preDistributionProcessing() override;
void traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData,
bool viewFrustumChanged, bool isFullScene) override;
private:
// 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 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

View file

@ -17,7 +17,6 @@
#include <udt/PacketHeaders.h>
#include <PerfStat.h>
#include "OctreeQueryNode.h"
#include "OctreeSendThread.h"
#include "OctreeServer.h"
#include "OctreeServerConsts.h"
@ -27,8 +26,8 @@ quint64 startSceneSleepTime = 0;
quint64 endSceneSleepTime = 0;
OctreeSendThread::OctreeSendThread(OctreeServer* myServer, const SharedNodePointer& node) :
_myServer(myServer),
_node(node),
_myServer(myServer),
_nodeUuid(node->getUUID())
{
QString safeServerName("Octree");
@ -48,7 +47,7 @@ OctreeSendThread::OctreeSendThread(OctreeServer* myServer, const SharedNodePoint
OctreeSendThread::~OctreeSendThread() {
setIsShuttingDown();
QString safeServerName("Octree");
if (_myServer) {
safeServerName = _myServer->getMyServerName();
@ -301,9 +300,25 @@ int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode*
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
int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged) {
OctreeServer::didPacketDistributor(this);
// if shutting down, exit early
@ -311,7 +326,7 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
return 0;
}
if (nodeData->elementBag.isEmpty()) {
if (shouldStartNewTraversal(nodeData, viewFrustumChanged)) {
// if we're about to do a fresh pass,
// give our pre-distribution processing a chance to do what it needs
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
// 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 (viewFrustumChanged) {
@ -367,11 +382,6 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
_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
//::startSceneSleepTime = _usleepTime;
@ -380,19 +390,11 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
nodeData->stats.sceneStarted(isFullScene, viewFrustumChanged,
_myServer->getOctree()->getRoot(), _myServer->getJurisdiction());
// 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());
}
preStartNewScene(nodeData, isFullScene);
}
// 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();
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
// the octree elements from the current view frustum
if (nodeData->elementBag.isEmpty()) {
if (!hasSomethingToSend(nodeData)) {
nodeData->updateLastKnownViewFrustum();
nodeData->setViewSent(true);
@ -458,7 +460,7 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
return _truePacketsSent;
}
bool OctreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params) {
bool OctreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) {
bool somethingToSend = false;
OctreeQueryNode* nodeData = static_cast<OctreeQueryNode*>(params.nodeData);
if (!nodeData->elementBag.isEmpty()) {
@ -502,8 +504,7 @@ void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, Octre
EncodeBitstreamParams params(INT_MAX, WANT_EXISTS_BITS, DONT_CHOP,
viewFrustumChanged, boundaryLevelAdjust, octreeSizeScale,
isFullScene, _myServer->getJurisdiction(), nodeData);
// Our trackSend() function is implemented by the server subclass, and will be called back
// during the encodeTreeBitstream() as new entities/data elements are sent
// Our trackSend() function is implemented by the server subclass, and will be called back as new entities/data elements are sent
params.trackSend = [this](const QUuid& dataID, quint64 dataEdited) {
_myServer->trackSend(dataID, dataEdited, _nodeUuid);
};
@ -513,7 +514,7 @@ void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, Octre
}
bool somethingToSend = true; // assume we have something
bool bagHadSomething = !nodeData->elementBag.isEmpty();
bool hadSomething = hasSomethingToSend(nodeData);
while (somethingToSend && _packetsSentThisInterval < maxPacketsPerInterval && !nodeData->isShuttingDown()) {
float compressAndWriteElapsedUsec = 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
params.stopReason = EncodeBitstreamParams::UNKNOWN; // reset params.stopReason before traversal
somethingToSend = traverseTreeAndBuildNextPacketPayload(params);
somethingToSend = traverseTreeAndBuildNextPacketPayload(params, nodeData->getJSONParameters());
if (params.stopReason == EncodeBitstreamParams::DIDNT_FIT) {
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.
bool completedScene = bagHadSomething && nodeData->elementBag.isEmpty();
bool completedScene = hadSomething && nodeData->elementBag.isEmpty();
if (completedScene || lastNodeDidntFit) {
// we probably want to flush what has accumulated in nodeData but:
// do we have more data to send? and is there room?

View file

@ -19,6 +19,7 @@
#include <GenericThread.h>
#include <Node.h>
#include <OctreePacketData.h>
#include "OctreeQueryNode.h"
class OctreeQueryNode;
class OctreeServer;
@ -51,24 +52,27 @@ protected:
/// Implements generic processing behavior for this thread.
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,
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;
OctreeServer* _myServer { nullptr };
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 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;
OctreePacketData _packetData;
int _truePacketsSent { 0 }; // available for debug stats
int _trueBytesSent { 0 }; // available for debug stats
int _packetsSentThisInterval { 0 }; // used for bandwidth throttle condition

View file

@ -60,6 +60,8 @@ int OctreeServer::_longTreeWait = 0;
int OctreeServer::_shortTreeWait = 0;
int OctreeServer::_noTreeWait = 0;
SimpleMovingAverage OctreeServer::_averageTreeTraverseTime(MOVING_AVERAGE_SAMPLE_COUNTS);
SimpleMovingAverage OctreeServer::_averageNodeWaitTime(MOVING_AVERAGE_SAMPLE_COUNTS);
SimpleMovingAverage OctreeServer::_averageCompressAndWriteTime(MOVING_AVERAGE_SAMPLE_COUNTS);
@ -106,6 +108,8 @@ void OctreeServer::resetSendingStats() {
_shortTreeWait = 0;
_noTreeWait = 0;
_averageTreeTraverseTime.reset();
_averageNodeWaitTime.reset();
_averageCompressAndWriteTime.reset();
@ -522,6 +526,10 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
(double)_averageTreeExtraLongWaitTime.getAverage(),
(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
float averageEncodeTime = getAverageEncodeTime();
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) {
auto sendThread = newSendThread(node);
// we want to be notified when the thread finishes
connect(sendThread.get(), &GenericThread::finished, this, &OctreeServer::removeSendThread);
sendThread->initialize(true);
@ -905,13 +913,13 @@ void OctreeServer::handleOctreeQueryPacket(QSharedPointer<ReceivedMessage> messa
// need to make sure we have it in our nodeList.
auto nodeList = DependencyManager::get<NodeList>();
nodeList->updateNodeWithDataFromPacket(message, senderNode);
auto it = _sendThreads.find(senderNode->getUUID());
if (it == _sendThreads.end()) {
_sendThreads.emplace(senderNode->getUUID(), createSendThread(senderNode));
} else if (it->second->isShuttingDown()) {
_sendThreads.erase(it); // Remove right away and wait on thread to be
_sendThreads.emplace(senderNode->getUUID(), createSendThread(senderNode));
}
}
@ -1085,7 +1093,7 @@ void OctreeServer::readConfiguration() {
if (getPayload().size() > 0) {
parsePayload();
}
const QJsonObject& settingsObject = DependencyManager::get<NodeList>()->getDomainHandler().getSettingsObject();
QString settingsKey = getMyDomainSettingsKey();
@ -1212,9 +1220,9 @@ void OctreeServer::run() {
OctreeElement::resetPopulationStatistics();
_tree = createTree();
_tree->setIsServer(true);
qDebug() << "Waiting for connection to domain to request settings from domain-server.";
// wait until we have the domain-server settings, otherwise we bail
DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler();
connect(&domainHandler, &DomainHandler::settingsReceived, this, &OctreeServer::domainSettingsRequestComplete);
@ -1225,9 +1233,9 @@ void OctreeServer::run() {
}
void OctreeServer::domainSettingsRequestComplete() {
auto nodeList = DependencyManager::get<NodeList>();
// we need to ask the DS about agents so we can ping/reply with them
nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer });
@ -1237,26 +1245,26 @@ void OctreeServer::domainSettingsRequestComplete() {
packetReceiver.registerListener(PacketType::JurisdictionRequest, this, "handleJurisdictionRequestPacket");
packetReceiver.registerListener(PacketType::OctreeFileReplacement, this, "handleOctreeFileReplacement");
packetReceiver.registerListener(PacketType::OctreeFileReplacementFromUrl, this, "handleOctreeFileReplacementFromURL");
readConfiguration();
beforeRun(); // after payload has been processed
connect(nodeList.data(), SIGNAL(nodeAdded(SharedNodePointer)), SLOT(nodeAdded(SharedNodePointer)));
connect(nodeList.data(), SIGNAL(nodeKilled(SharedNodePointer)), SLOT(nodeKilled(SharedNodePointer)));
#ifndef WIN32
setvbuf(stdout, NULL, _IOLBF, 0);
#endif
nodeList->linkedDataCreateCallback = [this](Node* node) {
auto queryNodeData = createOctreeQueryNode();
queryNodeData->init();
node->setLinkedData(std::move(queryNodeData));
};
srand((unsigned)time(0));
// if we want Persistence, set up the local file and persist thread
if (_wantPersist) {
// 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;
// now set up PersistThread
_persistThread = new OctreePersistThread(_tree, _persistAbsoluteFilePath, _backupDirectoryPath, _persistInterval,
_wantBackup, _settings, _debugTimestampNow, _persistAsFileType);
_persistThread->initialize(true);
}
// set up our jurisdiction broadcaster...
if (_jurisdiction) {
_jurisdiction->setNodeType(getMyNodeType());
}
_jurisdictionSender = new JurisdictionSender(_jurisdiction, getMyNodeType());
_jurisdictionSender->initialize(true);
// set up our OctreeServerPacketProcessor
_octreeInboundPacketProcessor = new OctreeInboundPacketProcessor(this);
_octreeInboundPacketProcessor->initialize(true);
// Convert now to tm struct for local timezone
tm* localtm = localtime(&_started);
const int MAX_TIME_LENGTH = 128;
@ -1380,7 +1388,7 @@ void OctreeServer::domainSettingsRequestComplete() {
if (gmtm) {
strftime(utcBuffer, MAX_TIME_LENGTH, " [%m/%d/%Y %X UTC]", gmtm);
}
qDebug() << "Now running... started at: " << localBuffer << utcBuffer;
}
@ -1391,7 +1399,7 @@ void OctreeServer::nodeAdded(SharedNodePointer node) {
void OctreeServer::nodeKilled(SharedNodePointer node) {
quint64 start = usecTimestampNow();
// Shutdown send thread
auto it = _sendThreads.find(node->getUUID());
if (it != _sendThreads.end()) {
@ -1437,13 +1445,13 @@ void OctreeServer::aboutToFinish() {
if (_jurisdictionSender) {
_jurisdictionSender->terminating();
}
// Shut down all the send threads
for (auto& it : _sendThreads) {
auto& sendThread = *it.second;
sendThread.setIsShuttingDown();
}
// 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
_sendThreads.clear(); // Cleans up all the send threads.
@ -1563,7 +1571,7 @@ void OctreeServer::sendStatsPacket() {
threadsStats["2. packetDistributor"] = (double)howManyThreadsDidPacketDistributor(oneSecondAgo);
threadsStats["3. handlePacektSend"] = (double)howManyThreadsDidHandlePacketSend(oneSecondAgo);
threadsStats["4. writeDatagram"] = (double)howManyThreadsDidCallWriteDatagram(oneSecondAgo);
QJsonObject statsArray1;
statsArray1["1. configuration"] = getConfiguration();
statsArray1["2. detailed_stats_url"] = getStatusLink();
@ -1571,13 +1579,13 @@ void OctreeServer::sendStatsPacket() {
statsArray1["4. persistFileLoadTime"] = getFileLoadTime();
statsArray1["5. clients"] = getCurrentClientCount();
statsArray1["6. threads"] = threadsStats;
// Octree Stats
QJsonObject octreeStats;
octreeStats["1. elementCount"] = (double)OctreeElement::getNodeCount();
octreeStats["2. internalElementCount"] = (double)OctreeElement::getInternalNodeCount();
octreeStats["3. leafElementCount"] = (double)OctreeElement::getLeafNodeCount();
// Stats Object 2
QJsonObject dataObject1;
dataObject1["1. totalPackets"] = (double)OctreeSendThread::_totalPackets;
@ -1590,12 +1598,12 @@ void OctreeServer::sendStatsPacket() {
QJsonObject timingArray1;
timingArray1["1. avgLoopTime"] = getAverageLoopTime();
timingArray1["2. avgInsideTime"] = getAverageInsideTime();
timingArray1["3. avgTreeLockTime"] = getAverageTreeWaitTime();
timingArray1["3. avgTreeTraverseTime"] = getAverageTreeTraverseTime();
timingArray1["4. avgEncodeTime"] = getAverageEncodeTime();
timingArray1["5. avgCompressAndWriteTime"] = getAverageCompressAndWriteTime();
timingArray1["6. avgSendTime"] = getAveragePacketSendingTime();
timingArray1["7. nodeWaitTime"] = getAverageNodeWaitTime();
QJsonObject statsObject2;
statsObject2["data"] = dataObject1;
statsObject2["timing"] = timingArray1;
@ -1615,18 +1623,18 @@ void OctreeServer::sendStatsPacket() {
timingArray2["4. avgProcessTimePerElement"] = (double)_octreeInboundPacketProcessor->getAverageProcessTimePerElement();
timingArray2["5. avgLockWaitTimePerElement"] = (double)_octreeInboundPacketProcessor->getAverageLockWaitTimePerElement();
}
QJsonObject statsObject3;
statsObject3["data"] = dataArray2;
statsObject3["timing"] = timingArray2;
// Merge everything
QJsonObject jsonArray;
jsonArray["1. misc"] = statsArray1;
jsonArray["2. octree"] = octreeStats;
jsonArray["3. outbound"] = statsObject2;
jsonArray["4. inbound"] = statsObject3;
QJsonObject statsObject;
statsObject[QString(getMyServerName()) + "Server"] = jsonArray;
addPacketStatsAndSendStatsPacket(statsObject);

View file

@ -96,6 +96,9 @@ public:
static void trackTreeWaitTime(float time);
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 float getAverageNodeWaitTime() { return _averageNodeWaitTime.getAverage(); }
@ -228,6 +231,8 @@ protected:
static int _shortTreeWait;
static int _noTreeWait;
static SimpleMovingAverage _averageTreeTraverseTime;
static SimpleMovingAverage _averageNodeWaitTime;
static SimpleMovingAverage _averageCompressAndWriteTime;

View file

@ -206,6 +206,10 @@ Item {
text: "Audio Codec: " + root.audioCodec + " Noise Gate: " +
root.audioNoiseGate;
}
StatText {
visible: root.expanded;
text: "Entity Servers In: " + root.entityPacketsInKbps + " kbps";
}
StatText {
visible: root.expanded;
text: "Downloads: " + root.downloads + "/" + root.downloadLimit +

View file

@ -75,6 +75,8 @@ Item {
height: 50;
echoMode: TextInput.Password;
placeholderText: "enter current passphrase";
activeFocusOnPress: true;
activeFocusOnTab: true;
onFocusChanged: {
if (focus) {
@ -86,9 +88,9 @@ Item {
MouseArea {
anchors.fill: parent;
onClicked: {
parent.focus = true;
onPressed: {
sendSignalToWallet({method: 'walletSetup_raiseKeyboard'});
mouse.accepted = false;
}
}
@ -108,6 +110,16 @@ Item {
height: 50;
echoMode: TextInput.Password;
placeholderText: root.isShowingTip ? "" : "enter new passphrase";
activeFocusOnPress: true;
activeFocusOnTab: true;
MouseArea {
anchors.fill: parent;
onPressed: {
sendSignalToWallet({method: 'walletSetup_raiseKeyboard'});
mouse.accepted = false;
}
}
onFocusChanged: {
if (focus) {
@ -117,18 +129,11 @@ Item {
}
}
MouseArea {
anchors.fill: parent;
onClicked: {
parent.focus = true;
sendMessageToLightbox({method: 'walletSetup_raiseKeyboard'});
}
}
onAccepted: {
passphraseFieldAgain.focus = true;
}
}
HifiControlsUit.TextField {
id: passphraseFieldAgain;
colorScheme: hifi.colorSchemes.dark;
@ -139,6 +144,16 @@ Item {
height: 50;
echoMode: TextInput.Password;
placeholderText: root.isShowingTip ? "" : "re-enter new passphrase";
activeFocusOnPress: true;
activeFocusOnTab: true;
MouseArea {
anchors.fill: parent;
onPressed: {
sendSignalToWallet({method: 'walletSetup_raiseKeyboard'});
mouse.accepted = false;
}
}
onFocusChanged: {
if (focus) {
@ -148,14 +163,6 @@ Item {
}
}
MouseArea {
anchors.fill: parent;
onClicked: {
parent.focus = true;
sendMessageToLightbox({method: 'walletSetup_raiseKeyboard'});
}
}
onAccepted: {
focus = false;
}

View file

@ -4699,12 +4699,8 @@ void Application::resetPhysicsReadyInformation() {
void Application::reloadResourceCaches() {
resetPhysicsReadyInformation();
{
QMutexLocker viewLocker(&_viewMutex);
_viewFrustum.setPosition(glm::vec3(0.0f, 0.0f, TREE_SCALE));
_viewFrustum.setOrientation(glm::quat());
}
// Clear entities out of view frustum
// Query the octree to refresh everything in view
_lastQueriedTime = 0;
queryOctree(NodeType::EntityServer, PacketType::EntityQuery, _entityServerJurisdictions);
DependencyManager::get<AssetClient>()->clearCache();
@ -4862,13 +4858,9 @@ void Application::update(float deltaTime) {
// we haven't yet enabled physics. we wait until we think we have all the collision information
// for nearby entities before starting bullet up.
quint64 now = usecTimestampNow();
bool timeout = false;
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
_lastPhysicsCheckTime = now;
_fullSceneCounterAtLastPhysicsCheck = _fullSceneReceivedCounter;
@ -5306,7 +5298,7 @@ int Application::sendNackPackets() {
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) {
return; // bail early if settings are not loaded
@ -5462,16 +5454,6 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node
_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
int packetSize = _octreeQuery.getBroadcastData(reinterpret_cast<unsigned char*>(queryPacket->getPayload()));
queryPacket->setPayloadSize(packetSize);
@ -5720,8 +5702,6 @@ void Application::clearDomainOctreeDetails() {
skyStage->setBackgroundMode(model::SunSkyStage::SKY_DEFAULT);
_recentlyClearedDomain = true;
DependencyManager::get<AnimationCache>()->clearUnusedResources();
DependencyManager::get<ModelCache>()->clearUnusedResources();
DependencyManager::get<SoundCache>()->clearUnusedResources();
@ -5768,14 +5748,10 @@ void Application::nodeActivated(SharedNodePointer node) {
}
}
// If we get a new EntityServer activated, do a "forceRedraw" query. This will send a degenerate
// query so that the server will think our next non-degenerate query is "different enough" to send
// us a full scene
if (_recentlyClearedDomain && node->getType() == NodeType::EntityServer) {
_recentlyClearedDomain = false;
if (DependencyManager::get<SceneScriptingInterface>()->shouldRenderEntities()) {
queryOctree(NodeType::EntityServer, PacketType::EntityQuery, _entityServerJurisdictions, true);
}
// If we get a new EntityServer activated, reset lastQueried time
// so we will do a proper query during update
if (node->getType() == NodeType::EntityServer) {
_lastQueriedTime = 0;
}
if (node->getType() == NodeType::AudioMixer) {

View file

@ -467,7 +467,7 @@ private:
void updateThreads(float deltaTime);
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);
@ -659,12 +659,10 @@ private:
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 _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 _recentlyClearedDomain { false };
QString _returnFromFullScreenMirrorTo;
ConnectionMonitor _connectionMonitor;

View file

@ -584,7 +584,7 @@ void MyAvatar::simulate(float deltaTime) {
}
auto now = usecTimestampNow();
EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender();
MovingEntitiesOperator moveOperator(entityTree);
MovingEntitiesOperator moveOperator;
forEachDescendant([&](SpatiallyNestablePointer object) {
// if the queryBox has changed, tell the entity-server
if (object->getNestableType() == NestableType::Entity && object->checkAndMaybeUpdateQueryAACube()) {

View file

@ -180,10 +180,12 @@ void Stats::updateStats(bool force) {
int totalPingOctree = 0;
int octreeServerCount = 0;
int pingOctreeMax = 0;
int totalEntityKbps = 0;
nodeList->eachNode([&](const SharedNodePointer& node) {
// TODO: this should also support entities
if (node->getType() == NodeType::EntityServer) {
totalPingOctree += node->getPingMs();
totalEntityKbps += node->getInboundBandwidth();
octreeServerCount++;
if (pingOctreeMax < node->getPingMs()) {
pingOctreeMax = node->getPingMs();
@ -248,6 +250,7 @@ void Stats::updateStats(bool force) {
STAT_UPDATE(audioCodec, audioClient->getSelectedAudioFormat());
STAT_UPDATE(audioNoiseGate, audioClient->getNoiseGateOpen() ? "Open" : "Closed");
STAT_UPDATE(entityPacketsInKbps, octreeServerCount ? totalEntityKbps / octreeServerCount : -1);
auto loadingRequests = ResourceCache::getLoadingRequests();
STAT_UPDATE(downloads, loadingRequests.size());

View file

@ -85,6 +85,7 @@ class Stats : public QQuickItem {
STATS_PROPERTY(int, audioPacketLoss, 0)
STATS_PROPERTY(QString, audioCodec, QString())
STATS_PROPERTY(QString, audioNoiseGate, QString())
STATS_PROPERTY(int, entityPacketsInKbps, 0)
STATS_PROPERTY(int, downloads, 0)
STATS_PROPERTY(int, downloadLimit, 0)
@ -212,6 +213,7 @@ signals:
void audioPacketLossChanged();
void audioCodecChanged();
void audioNoiseGateChanged();
void entityPacketsInKbpsChanged();
void downloadsChanged();
void downloadLimitChanged();

View file

@ -47,7 +47,7 @@ public:
void setIsSolid(bool isSolid) { _isSolid = isSolid; }
void setIsDashedLine(bool isDashedLine) { _isDashedLine = isDashedLine; }
void setIgnoreRayIntersection(bool value) { _ignoreRayIntersection = value; }
void setDrawInFront(bool value) { _drawInFront = value; }
virtual void setDrawInFront(bool value) { _drawInFront = value; }
void setIsGrabbable(bool value) { _isGrabbable = value; }
virtual AABox getBounds() const override = 0;

View file

@ -9,6 +9,9 @@
// 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 <Rig.h>
@ -60,6 +63,32 @@ void ModelOverlay::update(float deltatime) {
_model->simulate(deltatime);
}
_isLoaded = _model->isActive();
if (isAnimatingSomething()) {
if (!jointsMapped()) {
mapAnimationJoints(_model->getJointNames());
}
animate();
}
// check to see if when we added our model to the scene they were ready, if they were not ready, then
// fix them up in the scene
render::ScenePointer scene = qApp->getMain3DScene();
render::Transaction transaction;
if (_model->needsFixupInScene()) {
_model->removeFromScene(scene, transaction);
_model->addToScene(scene, transaction);
}
if (_visibleDirty) {
_visibleDirty = false;
_model->setVisibleInScene(getVisible(), scene);
}
if (_drawInFrontDirty) {
_drawInFrontDirty = false;
_model->setLayeredInFront(getDrawInFront(), scene);
}
scene->enqueueTransaction(transaction);
}
bool ModelOverlay::addToScene(Overlay::Pointer overlay, const render::ScenePointer& scene, render::Transaction& transaction) {
@ -73,21 +102,14 @@ void ModelOverlay::removeFromScene(Overlay::Pointer overlay, const render::Scene
_model->removeFromScene(scene, transaction);
}
void ModelOverlay::render(RenderArgs* args) {
void ModelOverlay::setVisible(bool visible) {
Overlay::setVisible(visible);
_visibleDirty = true;
}
// check to see if when we added our model to the scene they were ready, if they were not ready, then
// fix them up in the scene
render::ScenePointer scene = qApp->getMain3DScene();
render::Transaction transaction;
if (_model->needsFixupInScene()) {
_model->removeFromScene(scene, transaction);
_model->addToScene(scene, transaction);
}
_model->setVisibleInScene(_visible, scene);
_model->setLayeredInFront(getDrawInFront(), scene);
scene->enqueueTransaction(transaction);
void ModelOverlay::setDrawInFront(bool drawInFront) {
Base3DOverlay::setDrawInFront(drawInFront);
_drawInFrontDirty = true;
}
void ModelOverlay::setProperties(const QVariantMap& properties) {
@ -172,6 +194,51 @@ void ModelOverlay::setProperties(const QVariantMap& properties) {
}
_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>
@ -259,6 +326,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);
}
@ -301,3 +386,134 @@ QString ModelOverlay::getName() const {
}
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;
}

View file

@ -13,6 +13,7 @@
#define hifi_ModelOverlay_h
#include <Model.h>
#include <AnimationCache.h>
#include "Volume3DOverlay.h"
@ -28,7 +29,7 @@ public:
ModelOverlay(const ModelOverlay* modelOverlay);
virtual void update(float deltatime) override;
virtual void render(RenderArgs* args) override;
virtual void render(RenderArgs* args) override {};
void setProperties(const QVariantMap& properties) override;
QVariant getProperty(const QString& property) override;
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
@ -45,6 +46,12 @@ public:
float getLoadPriority() const { return _loadPriority; }
bool hasAnimation() const { return !_animationURL.isEmpty(); }
bool jointsMapped() const { return _jointMappingURL == _animationURL && _jointMappingCompleted; }
void setVisible(bool visible) override;
void setDrawInFront(bool drawInFront) override;
protected:
Transform evalRenderTransform() override;
@ -53,6 +60,14 @@ protected:
template <typename vectorType, typename itemType>
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:
ModelPointer _model;
@ -62,6 +77,28 @@ private:
bool _updateModel = { false };
bool _scaleToFit = { false };
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
bool _visibleDirty { false };
bool _drawInFrontDirty { false };
};
#endif // hifi_ModelOverlay_h

View file

@ -73,7 +73,7 @@ public:
float getAlphaPulse() const { return _alphaPulse; }
// setters
void setVisible(bool visible) { _visible = visible; }
virtual void setVisible(bool visible) { _visible = visible; }
void setDrawHUDLayer(bool drawHUDLayer);
void setColor(const xColor& color) { _color = color; }
void setAlpha(float alpha) { _alpha = alpha; }

View file

@ -249,6 +249,7 @@ void Rig::reset(const FBXGeometry& geometry) {
_rightShoulderJointIndex = _rightElbowJointIndex >= 0 ? geometry.joints.at(_rightElbowJointIndex).parentIndex : -1;
if (!_animGraphURL.isEmpty()) {
_animNode.reset();
initAnimGraph(_animGraphURL);
}
}
@ -1619,7 +1620,7 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo
}
void Rig::initAnimGraph(const QUrl& url) {
if (_animGraphURL != url) {
if (_animGraphURL != url || !_animNode) {
_animGraphURL = url;
_animNode.reset();

View file

@ -9,18 +9,17 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "AddEntityOperator.h"
#include "EntityItem.h"
#include "EntityTree.h"
#include "EntityTreeElement.h"
#include "AddEntityOperator.h"
AddEntityOperator::AddEntityOperator(EntityTreePointer tree, EntityItemPointer newEntity) :
_tree(tree),
_newEntity(newEntity),
_foundNew(false),
_changeTime(usecTimestampNow()),
_newEntityBox()
_newEntityBox(),
_foundNew(false)
{
// caller must have verified existence of newEntity
assert(_newEntity);

View file

@ -12,20 +12,28 @@
#ifndef 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 {
public:
AddEntityOperator(EntityTreePointer tree, EntityItemPointer newEntity);
virtual bool preRecursion(const OctreeElementPointer& element) override;
virtual bool postRecursion(const OctreeElementPointer& element) override;
virtual OctreeElementPointer possiblyCreateChildAt(const OctreeElementPointer& element, int childIndex) override;
private:
EntityTreePointer _tree;
EntityItemPointer _newEntity;
bool _foundNew;
quint64 _changeTime;
AABox _newEntityBox;
bool _foundNew;
};

View 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);
}
}

View 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

View file

@ -32,7 +32,8 @@
#include "EntitySimulation.h"
#include "EntityDynamicFactoryInterface.h"
Q_DECLARE_METATYPE(EntityItemPointer);
int entityItemPointernMetaTypeId = qRegisterMetaType<EntityItemPointer>();
int EntityItem::_maxActionsDataSize = 800;
quint64 EntityItem::_rememberDeletedActionTime = 20 * USECS_PER_SECOND;
@ -1570,13 +1571,17 @@ void EntityItem::updatePosition(const glm::vec3& 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);
// children are forced to be kinematic
// may need to not collide with own avatar
markDirtyFlags(Simulation::DIRTY_MOTION_TYPE | Simulation::DIRTY_COLLISION_GROUP);
EntityTreePointer tree = getTree();
if (tree) {
tree->addToNeedsParentFixupList(getThisPointer());
}
@ -2020,7 +2025,7 @@ bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulationPoi
_previouslyDeletedActions.insert(actionID, usecTimestampNow());
if (_objectActions.contains(actionID)) {
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;
simulation = entityTree ? entityTree->getSimulation() : nullptr;
}

View file

@ -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
/// 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.
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.
// 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

View file

@ -141,7 +141,7 @@ void EntitySimulation::callUpdateOnEntitiesThatNeedIt(const quint64& now) {
void EntitySimulation::sortEntitiesThatMoved() {
// 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.
MovingEntitiesOperator moveOperator(_entityTree);
MovingEntitiesOperator moveOperator;
AACube domainBounds(glm::vec3((float)-HALF_TREE_SCALE), (float)TREE_SCALE);
SetOfEntities::iterator itemItr = _entitiesToSort.begin();
while (itemItr != _entitiesToSort.end()) {

View file

@ -22,7 +22,6 @@
#include "VariantMapToScriptValue.h"
#include "AddEntityOperator.h"
#include "MovingEntitiesOperator.h"
#include "UpdateEntityOperator.h"
#include "QVariantGLM.h"
#include "EntitiesLogging.h"
@ -107,6 +106,111 @@ void EntityTree::eraseAllOctreeElements(bool createNewRoot) {
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 {
// we handle these types of "edit" packets
switch (packetType) {
@ -193,7 +297,9 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti
}
UpdateEntityOperator theOperator(getThisPointer(), containingElement, entity, queryCube);
recurseTreeWithOperator(&theOperator);
entity->setProperties(tempProperties);
if (entity->setProperties(tempProperties)) {
emit editingEntityPointer(entity);
}
_isDirty = true;
}
}
@ -268,7 +374,9 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti
}
UpdateEntityOperator theOperator(getThisPointer(), containingElement, entity, newQueryAACube);
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
QQueue<SpatiallyNestablePointer> toProcess;
@ -443,6 +551,7 @@ void EntityTree::deleteEntity(const EntityItemID& entityID, bool force, bool ign
unhookChildAvatar(entityID);
emit deletingEntity(entityID);
emit deletingEntityPointer(existingEntity.get());
// NOTE: callers must lock the tree before using this method
DeleteEntityOperator theOperator(getThisPointer(), entityID);
@ -451,6 +560,10 @@ void EntityTree::deleteEntity(const EntityItemID& entityID, bool force, bool ign
auto descendantID = descendant->getID();
theOperator.addEntityIDToDeleteList(descendantID);
emit deletingEntity(descendantID);
EntityItemPointer descendantEntity = std::dynamic_pointer_cast<EntityItem>(descendant);
if (descendantEntity) {
emit deletingEntityPointer(descendantEntity.get());
}
});
recurseTreeWithOperator(&theOperator);
@ -500,6 +613,7 @@ void EntityTree::deleteEntities(QSet<EntityItemID> entityIDs, bool force, bool i
unhookChildAvatar(entityID);
theOperator.addEntityIDToDeleteList(entityID);
emit deletingEntity(entityID);
emit deletingEntityPointer(existingEntity.get());
}
if (theOperator.getEntities().size() > 0) {
@ -1137,7 +1251,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
if (!isPhysics) {
properties.setLastEditedBy(senderNode->getUUID());
}
updateEntity(entityItemID, properties, senderNode);
updateEntity(existingEntity, properties, senderNode);
existingEntity->markAsChangedOnServer();
endUpdate = usecTimestampNow();
_totalUpdates++;
@ -1148,11 +1262,8 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
} else if (!senderNode->getCanRez() && !senderNode->getCanRezTmp()) {
failedAdd = true;
qCDebug(entities) << "User without 'rez rights' [" << senderNode->getUUID()
<< "] 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;
<< "] attempted to add an entity ID:" << entityItemID;
} else {
// this is a new entity... assign a new entityID
properties.setCreated(properties.getLastEdited());
@ -1253,7 +1364,7 @@ void EntityTree::entityChanged(EntityItemPointer entity) {
void EntityTree::fixupNeedsParentFixups() {
MovingEntitiesOperator moveOperator(getThisPointer());
MovingEntitiesOperator moveOperator;
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) {
QWriteLocker locker(&_needsParentFixupLock);
_needsParentFixup.append(entity);
@ -1670,7 +1788,7 @@ QVector<EntityItemID> EntityTree::sendEntities(EntityEditPacketSender* packetSen
// 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
MovingEntitiesOperator moveOperator(localTree);
MovingEntitiesOperator moveOperator;
QHash<EntityItemID, EntityItemID>::iterator i = map.begin();
while (i != map.end()) {
EntityItemID newID = i.value();

View file

@ -19,11 +19,12 @@
#include <SpatialParentFinder.h>
class EntityTree;
typedef std::shared_ptr<EntityTree> EntityTreePointer;
using EntityTreePointer = std::shared_ptr<EntityTree>;
#include "AddEntityOperator.h"
#include "EntityTreeElement.h"
#include "DeleteEntityOperator.h"
#include "MovingEntitiesOperator.h"
class EntityEditFilters;
class Model;
@ -80,6 +81,10 @@ public:
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
// own definition. Implement these to allow your octree based server to support editing
virtual bool getWantSVOfileVersions() const override { return true; }
@ -254,6 +259,7 @@ public:
void knowAvatarID(QUuid avatarID) { _avatarIDs += avatarID; }
void forgetAvatarID(QUuid avatarID) { _avatarIDs -= avatarID; }
void deleteDescendantsOfAvatar(QUuid avatarID);
void removeFromChildrenOfAvatars(EntityItemPointer entity);
void addToNeedsParentFixupList(EntityItemPointer entity);
@ -263,7 +269,9 @@ public:
signals:
void deletingEntity(const EntityItemID& entityID);
void deletingEntityPointer(EntityItem* entityID);
void addingEntity(const EntityItemID& entityID);
void editingEntityPointer(const EntityItemPointer& entityID);
void entityScriptChanging(const EntityItemID& entityItemID, const bool reload);
void entityServerScriptChanging(const EntityItemID& entityItemID, const bool reload);
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 _hasEntityEditFilter{ false };
QStringList _entityScriptSourceWhitelist;
MovingEntitiesOperator _entityMover;
QHash<EntityItemID, EntityItemPointer> _entitiesToAdd;
};
#endif // hifi_EntityTree_h

View file

@ -107,7 +107,7 @@ bool EntityTreeElement::shouldIncludeChildData(int childIndex, EncodeBitstreamPa
OctreeElementExtraEncodeData* extraEncodeData = &entityNodeData->extraEncodeData;
assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes
if (extraEncodeData->contains(this)) {
EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData
= std::static_pointer_cast<EntityTreeElementExtraEncodeData>((*extraEncodeData)[this]);
@ -305,7 +305,7 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData
int numberOfEntitiesOffset = 0;
withReadLock([&] {
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
// 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.
@ -432,11 +432,11 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData
// and include the entity in our final count of entities
packetData->endLevel(entityLevel);
actualNumberOfEntities++;
}
// If the entity item got completely appended, then we can remove it from the extra encode data
if (appendEntityState == OctreeElement::COMPLETED) {
entityTreeElementExtraEncodeData->entities.remove(entity->getEntityItemID());
// If the entity item got completely appended, then we can remove it from the extra encode data
if (appendEntityState == OctreeElement::COMPLETED) {
entityTreeElementExtraEncodeData->entities.remove(entity->getEntityItemID());
}
}
// 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();
});
bumpChangedContent();
}
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
entity->_element = NULL;
_entityItems.removeAt(i);
bumpChangedContent();
break;
}
}
@ -922,144 +924,16 @@ bool EntityTreeElement::removeEntityItem(EntityItemPointer entity) {
// NOTE: only EntityTreeElement should ever be changing the value of entity->_element
assert(entity->_element.get() == this);
entity->_element = NULL;
bumpChangedContent();
return true;
}
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,
ReadBitstreamToTreeParams& args) {
// If we're the root, but this bitstream doesn't support root elements with data, then
// 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;
return _myTree->readEntityDataFromBuffer(data, bytesLeftToRead, args);
}
void EntityTreeElement::addEntityItem(EntityItemPointer entity) {
@ -1068,6 +942,7 @@ void EntityTreeElement::addEntityItem(EntityItemPointer entity) {
withWriteLock([&] {
_entityItems.push_back(entity);
});
bumpChangedContent();
entity->_element = getThisPointer();
}

View file

@ -21,11 +21,12 @@
#include "EntityItem.h"
#include "EntityTree.h"
typedef QVector<EntityItemPointer> EntityItems;
class EntityTree;
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 {
public:
@ -173,7 +174,6 @@ public:
void setTree(EntityTreePointer tree) { _myTree = tree; }
EntityTreePointer getTree() const { return _myTree; }
bool updateEntity(const EntityItem& entity);
void addEntityItem(EntityItemPointer entity);
EntityItemPointer getClosestEntity(glm::vec3 position) const;
@ -238,10 +238,14 @@ public:
return std::static_pointer_cast<const OctreeElement>(shared_from_this());
}
void bumpChangedContent() { _lastChangedContent = usecTimestampNow(); }
uint64_t getLastChangedContent() const { return _lastChangedContent; }
protected:
virtual void init(unsigned char * octalCode) override;
EntityTreePointer _myTree;
EntityItems _entityItems;
uint64_t _lastChangedContent { 0 };
};
#endif // hifi_EntityTreeElement_h

View file

@ -16,15 +16,7 @@
#include "MovingEntitiesOperator.h"
MovingEntitiesOperator::MovingEntitiesOperator(EntityTreePointer tree) :
_tree(tree),
_changeTime(usecTimestampNow()),
_foundOldCount(0),
_foundNewCount(0),
_lookingCount(0),
_wantDebug(false)
{
}
MovingEntitiesOperator::MovingEntitiesOperator() { }
MovingEntitiesOperator::~MovingEntitiesOperator() {
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
// 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 new entity, and this branch contains our new entity
//
@ -200,6 +192,8 @@ bool MovingEntitiesOperator::preRecursion(const OctreeElementPointer& element) {
oldElement->removeEntityItem(details.entity);
}
entityTreeElement->addEntityItem(details.entity);
} else {
entityTreeElement->bumpChangedContent();
}
_foundNewCount++;
//details.newFound = true; // TODO: would be nice to add this optimization
@ -230,8 +224,6 @@ bool MovingEntitiesOperator::postRecursion(const OctreeElementPointer& element)
if ((shouldRecurseSubTree(element))) {
element->markWithChangedTime();
}
// 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
@ -286,3 +278,10 @@ OctreeElementPointer MovingEntitiesOperator::possiblyCreateChildAt(const OctreeE
}
return NULL;
}
void MovingEntitiesOperator::reset() {
_entitiesToMove.clear();
_foundOldCount = 0;
_foundNewCount = 0;
_lookingCount = 0;
}

View file

@ -12,6 +12,11 @@
#ifndef hifi_MovingEntitiesOperator_h
#define hifi_MovingEntitiesOperator_h
#include <QSet>
#include "EntityTypes.h"
#include "EntityTreeElement.h"
class EntityToMoveDetails {
public:
EntityItemPointer entity;
@ -34,7 +39,7 @@ inline bool operator==(const EntityToMoveDetails& a, const EntityToMoveDetails&
class MovingEntitiesOperator : public RecurseOctreeOperator {
public:
MovingEntitiesOperator(EntityTreePointer tree);
MovingEntitiesOperator();
~MovingEntitiesOperator();
void addEntityToMoveList(EntityItemPointer entity, const AACube& newCube);
@ -42,16 +47,15 @@ public:
virtual bool postRecursion(const OctreeElementPointer& element) override;
virtual OctreeElementPointer possiblyCreateChildAt(const OctreeElementPointer& element, int childIndex) override;
bool hasMovingEntities() const { return _entitiesToMove.size() > 0; }
void reset();
private:
EntityTreePointer _tree;
QSet<EntityToMoveDetails> _entitiesToMove;
quint64 _changeTime;
int _foundOldCount;
int _foundNewCount;
int _lookingCount;
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

View file

@ -173,6 +173,8 @@ bool UpdateEntityOperator::preRecursion(const OctreeElementPointer& element) {
if (oldElement != _containingElement) {
qCDebug(entities) << "WARNING entity moved during UpdateEntityOperator recursion";
_containingElement->removeEntityItem(_existingEntity);
} else {
_containingElement->bumpChangedContent();
}
if (_wantDebug) {
@ -211,6 +213,7 @@ bool UpdateEntityOperator::preRecursion(const OctreeElementPointer& element) {
if (_wantDebug) {
qCDebug(entities) << " *** This is the same OLD ELEMENT ***";
}
_containingElement->bumpChangedContent();
} else {
// otherwise, this is an add case.
if (oldElement) {

View file

@ -92,7 +92,8 @@ public:
OUT_OF_VIEW,
WAS_IN_VIEW,
NO_CHANGE,
OCCLUDED
OCCLUDED,
FINISHED
} reason;
reason stopReason;
@ -232,7 +233,7 @@ public:
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 reaverageOctreeElements(OctreeElementPointer startElement = OctreeElementPointer());

View file

@ -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;
// 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,
// 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 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
const int UNREASONABLY_DEEP_RECURSION = 29; // use this for something that you want to be shallow, but not spin out

View file

@ -582,9 +582,7 @@ bool OctreePacketData::compressContent() {
if (compressedData.size() < (int)MAX_OCTREE_PACKET_DATA_SIZE) {
_compressedBytes = compressedData.size();
for (int i = 0; i < _compressedBytes; i++) {
_compressed[i] = compressedData[i];
}
memcpy(_compressed, compressedData.constData(), _compressedBytes);
_dirty = false;
success = true;
}
@ -598,25 +596,22 @@ void OctreePacketData::loadFinalizedContent(const unsigned char* data, int lengt
if (data && length > 0) {
if (_enableCompression) {
QByteArray compressedData;
for (int i = 0; i < length; i++) {
compressedData[i] = data[i];
_compressed[i] = compressedData[i];
}
_compressedBytes = length;
memcpy(_compressed, data, _compressedBytes);
QByteArray compressedData;
compressedData.resize(_compressedBytes);
memcpy(compressedData.data(), data, _compressedBytes);
QByteArray uncompressedData = qUncompress(compressedData);
if (uncompressedData.size() <= _bytesAvailable) {
_bytesInUse = uncompressedData.size();
_bytesAvailable -= uncompressedData.size();
for (int i = 0; i < _bytesInUse; i++) {
_uncompressed[i] = uncompressedData[i];
}
memcpy(_uncompressed, uncompressedData.constData(), _bytesInUse);
}
} else {
for (int i = 0; i < length; i++) {
_uncompressed[i] = _compressed[i] = data[i];
}
memcpy(_uncompressed, data, length);
memcpy(_compressed, data, length);
_bytesInUse = _compressedBytes = length;
}
} else {

View file

@ -17,7 +17,18 @@
#include "OctreeConstants.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;
}

View file

@ -89,14 +89,14 @@ public slots:
protected:
// camera details for the avatar
glm::vec3 _cameraPosition = glm::vec3(0.0f);
glm::quat _cameraOrientation = glm::quat();
float _cameraFov = 0.0f;
float _cameraAspectRatio = 1.0f;
float _cameraNearClip = 0.0f;
float _cameraFarClip = 0.0f;
float _cameraCenterRadius { 0.0f };
glm::vec3 _cameraEyeOffsetPosition = glm::vec3(0.0f);
glm::vec3 _cameraPosition { glm::vec3(0.0f) };
glm::quat _cameraOrientation { glm::quat() };
float _cameraFov;
float _cameraAspectRatio;
float _cameraNearClip;
float _cameraFarClip;
float _cameraCenterRadius;
glm::vec3 _cameraEyeOffsetPosition { glm::vec3(0.0f) };
// octree server sending items
int _maxQueryPPS = DEFAULT_MAX_OCTREE_PPS;

View file

@ -182,6 +182,7 @@ bool OctreeQueryNode::updateCurrentViewFrustum() {
getCameraAspectRatio(),
getCameraNearClip(),
getCameraFarClip()));
newestViewFrustum.calculate();
}
@ -189,7 +190,6 @@ bool OctreeQueryNode::updateCurrentViewFrustum() {
QMutexLocker viewLocker(&_viewMutex);
if (!newestViewFrustum.isVerySimilar(_currentViewFrustum)) {
_currentViewFrustum = newestViewFrustum;
_currentViewFrustum.calculate();
currentViewFrustumChanged = true;
}
}

View file

@ -27,4 +27,11 @@ float boundaryDistanceForRenderLevel(unsigned int renderLevel, float voxelSizeSc
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

View file

@ -56,16 +56,17 @@ void ViewFrustum::setProjection(const glm::mat4& projection) {
_projection = 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) {
_corners[i] = inverseProjection * NDC_VALUES[i];
_corners[i] /= _corners[i].w;
}
// compute frustum properties
_nearClip = -_corners[BOTTOM_LEFT_NEAR].z;
_farClip = -_corners[BOTTOM_LEFT_FAR].z;
_aspectRatio = (_corners[TOP_RIGHT_NEAR].x - _corners[BOTTOM_LEFT_NEAR].x) /
(_corners[TOP_RIGHT_NEAR].y - _corners[BOTTOM_LEFT_NEAR].y);
glm::vec4 top = inverseProjection * vec4(0.0f, 1.0f, -1.0f, 1.0f);
top /= top.w;
_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);
// 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 &&
0.0f != cameraNearClip &&
0.0f != cameraFarClip &&
cameraNearClip != cameraFarClip) {
setProjection(glm::perspective(
glm::radians(wideFOV), // hack
glm::radians(cameraFov),
cameraAspectRatio,
cameraNearClip,
cameraFarClip));
@ -324,125 +321,27 @@ bool ViewFrustum::boxIntersectsKeyhole(const AABox& box) const {
return true;
}
bool testMatches(glm::quat lhs, glm::quat rhs, float epsilon = EPSILON) {
return (fabs(lhs.x - rhs.x) <= epsilon && fabs(lhs.y - rhs.y) <= epsilon && fabs(lhs.z - rhs.z) <= epsilon
&& fabs(lhs.w - rhs.w) <= epsilon);
bool closeEnough(float a, float b, float relativeError) {
assert(relativeError >= 0.0f);
// NOTE: we add EPSILON to the denominator so we can avoid checking for division by zero.
// This method works fine when: fabsf(a + b) >> EPSILON
return fabsf(a - b) / (0.5f * fabsf(a + b) + EPSILON) < relativeError;
}
bool testMatches(glm::vec3 lhs, glm::vec3 rhs, float epsilon = EPSILON) {
return (fabs(lhs.x - rhs.x) <= epsilon && fabs(lhs.y - rhs.y) <= epsilon && fabs(lhs.z - rhs.z) <= epsilon);
}
// TODO: the slop and relative error should be passed in by argument rather than hard-coded.
bool ViewFrustum::isVerySimilar(const ViewFrustum& other) const {
const float MIN_POSITION_SLOP_SQUARED = 25.0f; // 5 meters squared
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 (fabs(lhs - rhs) <= epsilon);
}
bool ViewFrustum::matches(const ViewFrustum& compareTo, bool debug) const {
bool result =
testMatches(compareTo._position, _position) &&
testMatches(compareTo._direction, _direction) &&
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;
return glm::distance2(_position, other._position) < MIN_POSITION_SLOP_SQUARED &&
fabsf(glm::dot(_orientation, other._orientation)) > MIN_ORIENTATION_DOT &&
closeEnough(_fieldOfView, other._fieldOfView, MIN_RELATIVE_ERROR) &&
closeEnough(_aspectRatio, other._aspectRatio, MIN_RELATIVE_ERROR) &&
closeEnough(_nearClip, other._nearClip, MIN_RELATIVE_ERROR) &&
closeEnough(_farClip, other._farClip, MIN_RELATIVE_ERROR) &&
closeEnough(_focalLength, other._focalLength, MIN_RELATIVE_ERROR) &&
closeEnough(_centerSphereRadius, other._centerSphereRadius, MIN_RELATIVE_ERROR);
}
PickRay ViewFrustum::computePickRay(float x, float y) {

View file

@ -107,12 +107,7 @@ public:
bool cubeIntersectsKeyhole(const AACube& cube) const;
bool boxIntersectsKeyhole(const AABox& box) const;
// some frustum comparisons
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); }
bool isVerySimilar(const ViewFrustum& compareTo) const;
PickRay computePickRay(float x, float y);
void computePickRay(float x, float y, glm::vec3& origin, glm::vec3& direction) const;

View file

@ -334,10 +334,8 @@ void OculusControllerManager::TouchDevice::handleHeadPose(float deltaTime,
glm::mat4 defaultHeadOffset = glm::inverse(inputCalibrationData.defaultCenterEyeMat) *
inputCalibrationData.defaultHeadMat;
controller::Pose hmdHeadPose = pose.transform(sensorToAvatar);
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,

View file

@ -49,6 +49,7 @@ createControllerDisplay = function(config) {
partOverlays: {},
parts: {},
mappingName: "mapping-display-" + Math.random(),
partValues: {},
setVisible: function(visible) {
for (var i = 0; i < this.overlays.length; ++i) {
@ -109,12 +110,53 @@ createControllerDisplay = function(config) {
for (var partName in controller.parts) {
overlayID = this.overlays[i++];
var part = controller.parts[partName];
var partPosition = Vec3.multiply(sensorScaleFactor, Vec3.sum(controller.position, Vec3.multiplyQbyV(controller.rotation, part.naturalPosition)));
var partDimensions = Vec3.multiply(sensorScaleFactor, part.naturalDimensions);
Overlays.editOverlay(overlayID, {
dimensions: partDimensions,
localPosition: partPosition
});
localPosition = Vec3.sum(controller.position, Vec3.multiplyQbyV(controller.rotation, part.naturalPosition));
var localRotation;
var value = this.partValues[partName];
var offset, rotation;
if (value !== undefined) {
if (part.type === "linear") {
var axis = Vec3.multiplyQbyV(controller.rotation, part.axis);
offset = Vec3.multiply(part.maxTranslation * value, axis);
localPosition = Vec3.sum(localPosition, offset);
localRotation = undefined;
} else if (part.type === "joystick") {
rotation = Quat.fromPitchYawRollDegrees(value.y * part.xHalfAngle, 0, value.x * part.yHalfAngle);
if (part.originOffset) {
offset = Vec3.multiplyQbyV(rotation, part.originOffset);
offset = Vec3.subtract(part.originOffset, offset);
} else {
offset = { x: 0, y: 0, z: 0 };
}
localPosition = Vec3.sum(controller.position, Vec3.multiplyQbyV(controller.rotation, Vec3.sum(offset, part.naturalPosition)));
localRotation = Quat.multiply(controller.rotation, rotation);
} else if (part.type === "rotational") {
value = clamp(value, part.minValue, part.maxValue);
var pct = (value - part.minValue) / part.maxValue;
var angle = pct * part.maxAngle;
rotation = Quat.angleAxis(angle, part.axis);
if (part.origin) {
offset = Vec3.multiplyQbyV(rotation, part.origin);
offset = Vec3.subtract(offset, part.origin);
} else {
offset = { x: 0, y: 0, z: 0 };
}
localPosition = Vec3.sum(controller.position, Vec3.multiplyQbyV(controller.rotation, Vec3.sum(offset, part.naturalPosition)));
localRotation = Quat.multiply(controller.rotation, rotation);
}
}
if (localRotation !== undefined) {
Overlays.editOverlay(overlayID, {
dimensions: Vec3.multiply(sensorScaleFactor, part.naturalDimensions),
localPosition: Vec3.multiply(sensorScaleFactor, localPosition),
localRotation: localRotation
});
} else {
Overlays.editOverlay(overlayID, {
dimensions: Vec3.multiply(sensorScaleFactor, part.naturalDimensions),
localPosition: Vec3.multiply(sensorScaleFactor, localPosition)
});
}
}
}
}
@ -172,29 +214,13 @@ createControllerDisplay = function(config) {
if (part.type === "rotational") {
var input = resolveHardware(part.input);
print("Mapping to: ", part.input, input);
mapping.from([input]).peek().to(function(controller, overlayID, part) {
mapping.from([input]).peek().to(function(partName) {
return function(value) {
value = clamp(value, part.minValue, part.maxValue);
var pct = (value - part.minValue) / part.maxValue;
var angle = pct * part.maxAngle;
var rotation = Quat.angleAxis(angle, part.axis);
var offset = { x: 0, y: 0, z: 0 };
if (part.origin) {
offset = Vec3.multiplyQbyV(rotation, part.origin);
offset = Vec3.subtract(offset, part.origin);
}
var partPosition = Vec3.sum(controller.position,
Vec3.multiplyQbyV(controller.rotation, Vec3.sum(offset, part.naturalPosition)));
Overlays.editOverlay(overlayID, {
localPosition: partPosition,
localRotation: Quat.multiply(controller.rotation, rotation)
});
// insert the most recent controller value into controllerDisplay.partValues.
controllerDisplay.partValues[partName] = value;
controllerDisplay.resize(MyAvatar.sensorToWorldScale);
};
}(controller, overlayID, part));
}(partName));
} else if (part.type === "touchpad") {
var visibleInput = resolveHardware(part.visibleInput);
var xInput = resolveHardware(part.xInput);
@ -210,69 +236,38 @@ createControllerDisplay = function(config) {
mapping.from([yInput]).peek().invert().to(function(value) {
});
} else if (part.type === "joystick") {
(function(controller, overlayID, part) {
(function(part, partName) {
var xInput = resolveHardware(part.xInput);
var yInput = resolveHardware(part.yInput);
var xvalue = 0;
var yvalue = 0;
function calculatePositionAndRotation(xValue, yValue) {
var rotation = Quat.fromPitchYawRollDegrees(yValue * part.xHalfAngle, 0, xValue * part.yHalfAngle);
var offset = { x: 0, y: 0, z: 0 };
if (part.originOffset) {
offset = Vec3.multiplyQbyV(rotation, part.originOffset);
offset = Vec3.subtract(part.originOffset, offset);
}
var partPosition = Vec3.sum(controller.position,
Vec3.multiplyQbyV(controller.rotation, Vec3.sum(offset, part.naturalPosition)));
var partRotation = Quat.multiply(controller.rotation, rotation);
return {
position: partPosition,
rotation: partRotation
};
}
mapping.from([xInput]).peek().to(function(value) {
xvalue = value;
var posRot = calculatePositionAndRotation(xvalue, yvalue);
Overlays.editOverlay(overlayID, {
localPosition: posRot.position,
localRotation: posRot.rotation
});
// insert the most recent controller value into controllerDisplay.partValues.
if (controllerDisplay.partValues[partName]) {
controllerDisplay.partValues[partName].x = value;
} else {
controllerDisplay.partValues[partName] = {x: value, y: 0};
}
controllerDisplay.resize(MyAvatar.sensorToWorldScale);
});
mapping.from([yInput]).peek().to(function(value) {
yvalue = value;
var posRot = calculatePositionAndRotation(xvalue, yvalue);
Overlays.editOverlay(overlayID, {
localPosition: posRot.position,
localRotation: posRot.rotation
});
// insert the most recent controller value into controllerDisplay.partValues.
if (controllerDisplay.partValues[partName]) {
controllerDisplay.partValues[partName].y = value;
} else {
controllerDisplay.partValues[partName] = {x: 0, y: value};
}
controllerDisplay.resize(MyAvatar.sensorToWorldScale);
});
})(controller, overlayID, part);
})(part, partName);
} else if (part.type === "linear") {
(function(controller, overlayID, part) {
(function(part, partName) {
var input = resolveHardware(part.input);
mapping.from([input]).peek().to(function(value) {
var axis = Vec3.multiplyQbyV(controller.rotation, part.axis);
var offset = Vec3.multiply(part.maxTranslation * value, axis);
var partPosition = Vec3.sum(controller.position, Vec3.multiplyQbyV(controller.rotation, part.naturalPosition));
var position = Vec3.sum(partPosition, offset);
Overlays.editOverlay(overlayID, {
localPosition: position
});
// insert the most recent controller value into controllerDisplay.partValues.
controllerDisplay.partValues[partName] = value;
controllerDisplay.resize(MyAvatar.sensorToWorldScale);
});
})(controller, overlayID, part);
})(part, partName);
} else if (part.type === "static") {
// do nothing

View file

@ -255,6 +255,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
this.messageGrabEntity = false;
this.grabEntityProps = null;
this.shouldSendStart = false;
this.equipedWithSecondary = false;
this.parameters = makeDispatcherModuleParameters(
300,
@ -370,6 +371,10 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
return this.rawSecondaryValue < BUMPER_ON_VALUE;
};
this.secondarySmoothedSqueezed = function() {
return this.rawSecondaryValue > BUMPER_ON_VALUE;
};
this.chooseNearEquipHotspots = function(candidateEntityProps, controllerData) {
var _this = this;
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 (potentialEquipHotspot &&
((this.triggerSmoothedSqueezed() && !this.waitForTriggerRelease) || this.messageGrabEntity)) {
(((this.triggerSmoothedSqueezed() || this.secondarySmoothedSqueezed()) && !this.waitForTriggerRelease) ||
this.messageGrabEntity)) {
this.grabbedHotspot = potentialEquipHotspot;
this.targetEntityID = this.grabbedHotspot.entityID;
this.startEquipEntity(controllerData);
this.messageGrabEnity = false;
this.equipedWithSecondary = this.secondarySmoothedSqueezed();
return makeRunningValues(true, [potentialEquipHotspot.entityID], []);
} else {
return makeRunningValues(false, [], []);
@ -627,7 +634,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
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
// so we cannot simply rely on that for release - ensure that the
// 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);
if (this.triggerSmoothedReleased()) {
if (this.triggerSmoothedReleased() || this.secondaryReleased()) {
if (this.shouldSendStart) {
// we don't want to send startEquip message until the trigger is released. otherwise,
// guns etc will fire right as they are equipped.
@ -653,6 +660,9 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
this.shouldSendStart = false;
}
this.waitForTriggerRelease = false;
if (this.secondaryReleased() && this.equipedWithSecondary) {
this.equipedWithSecondary = false;
}
}
if (dropDetected && this.prevDropDetected !== dropDetected) {

View file

@ -132,7 +132,7 @@ Script.include("/~/system/libraries/controllers.js");
this.updateLaserPointer = function(controllerData) {
var SEARCH_SPHERE_SIZE = 0.011;
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 mode = "hold";
if (!this.distanceHolding && !this.distanceRotating) {
@ -424,7 +424,7 @@ Script.include("/~/system/libraries/controllers.js");
this.laserPointerOff();
return makeRunningValues(false, [], []);
}
this.intersectionDistance = controllerData.rayPicks[this.hand].distance;
this.updateLaserPointer(controllerData);
var otherModuleName =this.hand === RIGHT_HAND ? "LeftFarActionGrabEntity" : "RightFarActionGrabEntity";

View file

@ -335,7 +335,7 @@ Script.include("/~/system/libraries/controllers.js");
var SCALED_TABLET_MAX_HOVER_DISTANCE = TABLET_MAX_HOVER_DISTANCE * sensorScaleFactor;
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);

View file

@ -306,7 +306,7 @@ Grabber.prototype.computeNewGrabPlane = function() {
};
Grabber.prototype.pressEvent = function(event) {
if (isInEditMode()) {
if (isInEditMode() || HMD.active) {
return;
}
@ -401,7 +401,7 @@ Grabber.prototype.pressEvent = 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;
}
@ -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,
// 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.
if (!this.isGrabbing) {
if (!this.isGrabbing || HMD.active) {
return;
}
mouse.updateDrag(event);
@ -458,7 +458,7 @@ Grabber.prototype.moveEventProcess = function() {
this.moveEventTimer = null;
// see if something added/restored gravity
var entityProperties = Entities.getEntityProperties(this.entityID);
if (!entityProperties || !entityProperties.gravity) {
if (!entityProperties || !entityProperties.gravity || HMD.active) {
return;
}

View file

@ -1474,6 +1474,8 @@ function onFileSaveChanged(filename) {
}
function onFileOpenChanged(filename) {
// disconnect the event, otherwise the requests will stack up
Window.openFileChanged.disconnect(onFileOpenChanged);
var importURL = null;
if (filename !== "") {
importURL = "file:///" + filename;