mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-14 11:46:34 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into pop_interfaceChanges2
This commit is contained in:
commit
1ec0f5d9a0
50 changed files with 1630 additions and 578 deletions
53
assignment-client/src/entities/EntityPriorityQueue.cpp
Normal file
53
assignment-client/src/entities/EntityPriorityQueue.cpp
Normal file
|
@ -0,0 +1,53 @@
|
|||
//
|
||||
// EntityPriorityQueue.cpp
|
||||
// assignment-client/src/entities
|
||||
//
|
||||
// Created by Andrew Meadows 2017.08.08
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "EntityPriorityQueue.h"
|
||||
|
||||
const float PrioritizedEntity::DO_NOT_SEND = -1.0e-6f;
|
||||
const float PrioritizedEntity::FORCE_REMOVE = -1.0e-5f;
|
||||
const float PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY = 1.0f;
|
||||
|
||||
void ConicalView::set(const ViewFrustum& viewFrustum) {
|
||||
// The ConicalView has two parts: a central sphere (same as ViewFrustum) and a circular cone that bounds the frustum part.
|
||||
// Why? Because approximate intersection tests are much faster to compute for a cone than for a frustum.
|
||||
_position = viewFrustum.getPosition();
|
||||
_direction = viewFrustum.getDirection();
|
||||
|
||||
// We cache the sin and cos of the half angle of the cone that bounds the frustum.
|
||||
// (the math here is left as an exercise for the reader)
|
||||
float A = viewFrustum.getAspectRatio();
|
||||
float t = tanf(0.5f * viewFrustum.getFieldOfView());
|
||||
_cosAngle = 1.0f / sqrtf(1.0f + (A * A + 1.0f) * (t * t));
|
||||
_sinAngle = sqrtf(1.0f - _cosAngle * _cosAngle);
|
||||
|
||||
_radius = viewFrustum.getCenterRadius();
|
||||
}
|
||||
|
||||
float ConicalView::computePriority(const AACube& cube) const {
|
||||
glm::vec3 p = cube.calcCenter() - _position; // position of bounding sphere in view-frame
|
||||
float d = glm::length(p); // distance to center of bounding sphere
|
||||
float r = 0.5f * cube.getScale(); // radius of bounding sphere
|
||||
if (d < _radius + r) {
|
||||
return r;
|
||||
}
|
||||
// We check the angle between the center of the cube and the _direction of the view.
|
||||
// If it is less than the sum of the half-angle from center of cone to outer edge plus
|
||||
// the half apparent angle of the bounding sphere then it is in view.
|
||||
//
|
||||
// The math here is left as an exercise for the reader with the following hints:
|
||||
// (1) We actually check the dot product of the cube's local position rather than the angle and
|
||||
// (2) we take advantage of this trig identity: cos(A+B) = cos(A)*cos(B) - sin(A)*sin(B)
|
||||
if (glm::dot(p, _direction) > sqrtf(d * d - r * r) * _cosAngle - r * _sinAngle) {
|
||||
const float AVOID_DIVIDE_BY_ZERO = 0.001f;
|
||||
return r / (d + AVOID_DIVIDE_BY_ZERO);
|
||||
}
|
||||
return PrioritizedEntity::DO_NOT_SEND;
|
||||
}
|
66
assignment-client/src/entities/EntityPriorityQueue.h
Normal file
66
assignment-client/src/entities/EntityPriorityQueue.h
Normal file
|
@ -0,0 +1,66 @@
|
|||
//
|
||||
// EntityPriorityQueue.h
|
||||
// assignment-client/src/entities
|
||||
//
|
||||
// Created by Andrew Meadows 2017.08.08
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_EntityPriorityQueue_h
|
||||
#define hifi_EntityPriorityQueue_h
|
||||
|
||||
#include <queue>
|
||||
|
||||
#include <AACube.h>
|
||||
#include <EntityTreeElement.h>
|
||||
|
||||
const float SQRT_TWO_OVER_TWO = 0.7071067811865f;
|
||||
const float DEFAULT_VIEW_RADIUS = 10.0f;
|
||||
|
||||
// ConicalView is an approximation of a ViewFrustum for fast calculation of sort priority.
|
||||
class ConicalView {
|
||||
public:
|
||||
ConicalView() {}
|
||||
ConicalView(const ViewFrustum& viewFrustum) { set(viewFrustum); }
|
||||
void set(const ViewFrustum& viewFrustum);
|
||||
float computePriority(const AACube& cube) const;
|
||||
private:
|
||||
glm::vec3 _position { 0.0f, 0.0f, 0.0f };
|
||||
glm::vec3 _direction { 0.0f, 0.0f, 1.0f };
|
||||
float _sinAngle { SQRT_TWO_OVER_TWO };
|
||||
float _cosAngle { SQRT_TWO_OVER_TWO };
|
||||
float _radius { DEFAULT_VIEW_RADIUS };
|
||||
};
|
||||
|
||||
// PrioritizedEntity is a placeholder in a sorted queue.
|
||||
class PrioritizedEntity {
|
||||
public:
|
||||
static const float DO_NOT_SEND;
|
||||
static const float FORCE_REMOVE;
|
||||
static const float WHEN_IN_DOUBT_PRIORITY;
|
||||
|
||||
PrioritizedEntity(EntityItemPointer entity, float priority, bool forceRemove = false) : _weakEntity(entity), _rawEntityPointer(entity.get()), _priority(priority), _forceRemove(forceRemove) {}
|
||||
EntityItemPointer getEntity() const { return _weakEntity.lock(); }
|
||||
EntityItem* getRawEntityPointer() const { return _rawEntityPointer; }
|
||||
float getPriority() const { return _priority; }
|
||||
bool shouldForceRemove() const { return _forceRemove; }
|
||||
|
||||
class Compare {
|
||||
public:
|
||||
bool operator() (const PrioritizedEntity& A, const PrioritizedEntity& B) { return A._priority < B._priority; }
|
||||
};
|
||||
friend class Compare;
|
||||
|
||||
private:
|
||||
EntityItemWeakPointer _weakEntity;
|
||||
EntityItem* _rawEntityPointer;
|
||||
float _priority;
|
||||
bool _forceRemove;
|
||||
};
|
||||
|
||||
using EntityPriorityQueue = std::priority_queue< PrioritizedEntity, std::vector<PrioritizedEntity>, PrioritizedEntity::Compare >;
|
||||
|
||||
#endif // hifi_EntityPriorityQueue_h
|
|
@ -13,9 +13,18 @@
|
|||
|
||||
#include <EntityNodeData.h>
|
||||
#include <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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 +
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
|
252
libraries/entities/src/DiffTraversal.cpp
Normal file
252
libraries/entities/src/DiffTraversal.cpp
Normal file
|
@ -0,0 +1,252 @@
|
|||
//
|
||||
// DiffTraversal.cpp
|
||||
// assignment-client/src/entities
|
||||
//
|
||||
// Created by Andrew Meadows 2017.08.08
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "DiffTraversal.h"
|
||||
|
||||
#include <OctreeUtils.h>
|
||||
|
||||
|
||||
DiffTraversal::Waypoint::Waypoint(EntityTreeElementPointer& element) : _nextIndex(0) {
|
||||
assert(element);
|
||||
_weakElement = element;
|
||||
}
|
||||
|
||||
void DiffTraversal::Waypoint::getNextVisibleElementFirstTime(DiffTraversal::VisibleElement& next,
|
||||
const DiffTraversal::View& view) {
|
||||
// NOTE: no need to set next.intersection in the "FirstTime" context
|
||||
if (_nextIndex == -1) {
|
||||
// root case is special:
|
||||
// its intersection is always INTERSECT,
|
||||
// we never bother checking for LOD culling, and
|
||||
// we can skip it if the content hasn't changed
|
||||
++_nextIndex;
|
||||
next.element = _weakElement.lock();
|
||||
return;
|
||||
} else if (_nextIndex < NUMBER_OF_CHILDREN) {
|
||||
EntityTreeElementPointer element = _weakElement.lock();
|
||||
if (element) {
|
||||
while (_nextIndex < NUMBER_OF_CHILDREN) {
|
||||
EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex);
|
||||
++_nextIndex;
|
||||
if (nextElement) {
|
||||
if (!view.usesViewFrustum) {
|
||||
// No LOD truncation if we aren't using the view frustum
|
||||
next.element = nextElement;
|
||||
return;
|
||||
} else if (view.viewFrustum.cubeIntersectsKeyhole(nextElement->getAACube())) {
|
||||
// check for LOD truncation
|
||||
float distance = glm::distance(view.viewFrustum.getPosition(), nextElement->getAACube().calcCenter()) + MIN_VISIBLE_DISTANCE;
|
||||
float angularDiameter = nextElement->getAACube().getScale() / distance;
|
||||
if (angularDiameter > MIN_ELEMENT_ANGULAR_DIAMETER * view.lodScaleFactor) {
|
||||
next.element = nextElement;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
next.element.reset();
|
||||
}
|
||||
|
||||
void DiffTraversal::Waypoint::getNextVisibleElementRepeat(
|
||||
DiffTraversal::VisibleElement& next, const DiffTraversal::View& view, uint64_t lastTime) {
|
||||
if (_nextIndex == -1) {
|
||||
// root case is special
|
||||
++_nextIndex;
|
||||
EntityTreeElementPointer element = _weakElement.lock();
|
||||
if (element->getLastChangedContent() > lastTime) {
|
||||
next.element = element;
|
||||
next.intersection = ViewFrustum::INTERSECT;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (_nextIndex < NUMBER_OF_CHILDREN) {
|
||||
EntityTreeElementPointer element = _weakElement.lock();
|
||||
if (element) {
|
||||
while (_nextIndex < NUMBER_OF_CHILDREN) {
|
||||
EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex);
|
||||
++_nextIndex;
|
||||
if (nextElement && nextElement->getLastChanged() > lastTime) {
|
||||
if (!view.usesViewFrustum) {
|
||||
// No LOD truncation if we aren't using the view frustum
|
||||
next.element = nextElement;
|
||||
next.intersection = ViewFrustum::INSIDE;
|
||||
return;
|
||||
} else {
|
||||
// check for LOD truncation
|
||||
float distance = glm::distance(view.viewFrustum.getPosition(), nextElement->getAACube().calcCenter()) + MIN_VISIBLE_DISTANCE;
|
||||
float angularDiameter = nextElement->getAACube().getScale() / distance;
|
||||
if (angularDiameter > MIN_ELEMENT_ANGULAR_DIAMETER * view.lodScaleFactor) {
|
||||
ViewFrustum::intersection intersection = view.viewFrustum.calculateCubeKeyholeIntersection(nextElement->getAACube());
|
||||
if (intersection != ViewFrustum::OUTSIDE) {
|
||||
next.element = nextElement;
|
||||
next.intersection = intersection;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
next.element.reset();
|
||||
next.intersection = ViewFrustum::OUTSIDE;
|
||||
}
|
||||
|
||||
void DiffTraversal::Waypoint::getNextVisibleElementDifferential(DiffTraversal::VisibleElement& next,
|
||||
const DiffTraversal::View& view, const DiffTraversal::View& lastView) {
|
||||
if (_nextIndex == -1) {
|
||||
// root case is special
|
||||
++_nextIndex;
|
||||
EntityTreeElementPointer element = _weakElement.lock();
|
||||
next.element = element;
|
||||
next.intersection = ViewFrustum::INTERSECT;
|
||||
return;
|
||||
} else if (_nextIndex < NUMBER_OF_CHILDREN) {
|
||||
EntityTreeElementPointer element = _weakElement.lock();
|
||||
if (element) {
|
||||
while (_nextIndex < NUMBER_OF_CHILDREN) {
|
||||
EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex);
|
||||
++_nextIndex;
|
||||
if (nextElement) {
|
||||
AACube cube = nextElement->getAACube();
|
||||
// check for LOD truncation
|
||||
float distance = glm::distance(view.viewFrustum.getPosition(), cube.calcCenter()) + MIN_VISIBLE_DISTANCE;
|
||||
float angularDiameter = cube.getScale() / distance;
|
||||
if (angularDiameter > MIN_ELEMENT_ANGULAR_DIAMETER * view.lodScaleFactor) {
|
||||
if (view.viewFrustum.calculateCubeKeyholeIntersection(cube) != ViewFrustum::OUTSIDE) {
|
||||
next.element = nextElement;
|
||||
next.intersection = ViewFrustum::OUTSIDE;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
next.element.reset();
|
||||
next.intersection = ViewFrustum::OUTSIDE;
|
||||
}
|
||||
|
||||
DiffTraversal::DiffTraversal() {
|
||||
const int32_t MIN_PATH_DEPTH = 16;
|
||||
_path.reserve(MIN_PATH_DEPTH);
|
||||
}
|
||||
|
||||
DiffTraversal::Type DiffTraversal::prepareNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset, bool usesViewFrustum) {
|
||||
assert(root);
|
||||
// there are three types of traversal:
|
||||
//
|
||||
// (1) First = fresh view --> find all elements in view
|
||||
// (2) Repeat = view hasn't changed --> find elements changed since last complete traversal
|
||||
// (3) Differential = view has changed --> find elements changed or in new view but not old
|
||||
//
|
||||
// for each traversal type we assign the appropriate _getNextVisibleElementCallback
|
||||
//
|
||||
// _getNextVisibleElementCallback = identifies elements that need to be traversed,
|
||||
// updates VisibleElement ref argument with pointer-to-element and view-intersection
|
||||
// (INSIDE, INTERSECT, or OUTSIDE)
|
||||
//
|
||||
// external code should update the _scanElementCallback after calling prepareNewTraversal
|
||||
//
|
||||
_currentView.usesViewFrustum = usesViewFrustum;
|
||||
float lodScaleFactor = powf(2.0f, lodLevelOffset);
|
||||
|
||||
Type type;
|
||||
// If usesViewFrustum changes, treat it as a First traversal
|
||||
if (_completedView.startTime == 0 || _currentView.usesViewFrustum != _completedView.usesViewFrustum) {
|
||||
type = Type::First;
|
||||
_currentView.viewFrustum = viewFrustum;
|
||||
_currentView.lodScaleFactor = lodScaleFactor;
|
||||
_getNextVisibleElementCallback = [this](DiffTraversal::VisibleElement& next) {
|
||||
_path.back().getNextVisibleElementFirstTime(next, _currentView);
|
||||
};
|
||||
} else if (!_currentView.usesViewFrustum ||
|
||||
(_completedView.viewFrustum.isVerySimilar(viewFrustum) &&
|
||||
lodScaleFactor == _completedView.lodScaleFactor)) {
|
||||
type = Type::Repeat;
|
||||
_getNextVisibleElementCallback = [this](DiffTraversal::VisibleElement& next) {
|
||||
_path.back().getNextVisibleElementRepeat(next, _completedView, _completedView.startTime);
|
||||
};
|
||||
} else {
|
||||
type = Type::Differential;
|
||||
_currentView.viewFrustum = viewFrustum;
|
||||
_currentView.lodScaleFactor = lodScaleFactor;
|
||||
_getNextVisibleElementCallback = [this](DiffTraversal::VisibleElement& next) {
|
||||
_path.back().getNextVisibleElementDifferential(next, _currentView, _completedView);
|
||||
};
|
||||
}
|
||||
|
||||
_path.clear();
|
||||
_path.push_back(DiffTraversal::Waypoint(root));
|
||||
// set root fork's index such that root element returned at getNextElement()
|
||||
_path.back().initRootNextIndex();
|
||||
|
||||
_currentView.startTime = usecTimestampNow();
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
void DiffTraversal::getNextVisibleElement(DiffTraversal::VisibleElement& next) {
|
||||
if (_path.empty()) {
|
||||
next.element.reset();
|
||||
next.intersection = ViewFrustum::OUTSIDE;
|
||||
return;
|
||||
}
|
||||
_getNextVisibleElementCallback(next);
|
||||
if (next.element) {
|
||||
int8_t nextIndex = _path.back().getNextIndex();
|
||||
if (nextIndex > 0) {
|
||||
_path.push_back(DiffTraversal::Waypoint(next.element));
|
||||
}
|
||||
} else {
|
||||
// we're done at this level
|
||||
while (!next.element) {
|
||||
// pop one level
|
||||
_path.pop_back();
|
||||
if (_path.empty()) {
|
||||
// we've traversed the entire tree
|
||||
_completedView = _currentView;
|
||||
return;
|
||||
}
|
||||
// keep looking for next
|
||||
_getNextVisibleElementCallback(next);
|
||||
if (next.element) {
|
||||
// we've descended one level so add it to the path
|
||||
_path.push_back(DiffTraversal::Waypoint(next.element));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DiffTraversal::setScanCallback(std::function<void (DiffTraversal::VisibleElement&)> cb) {
|
||||
if (!cb) {
|
||||
_scanElementCallback = [](DiffTraversal::VisibleElement& a){};
|
||||
} else {
|
||||
_scanElementCallback = cb;
|
||||
}
|
||||
}
|
||||
|
||||
void DiffTraversal::traverse(uint64_t timeBudget) {
|
||||
uint64_t expiry = usecTimestampNow() + timeBudget;
|
||||
DiffTraversal::VisibleElement next;
|
||||
getNextVisibleElement(next);
|
||||
while (next.element) {
|
||||
if (next.element->hasContent()) {
|
||||
_scanElementCallback(next);
|
||||
}
|
||||
if (usecTimestampNow() > expiry) {
|
||||
break;
|
||||
}
|
||||
getNextVisibleElement(next);
|
||||
}
|
||||
}
|
85
libraries/entities/src/DiffTraversal.h
Normal file
85
libraries/entities/src/DiffTraversal.h
Normal file
|
@ -0,0 +1,85 @@
|
|||
//
|
||||
// DiffTraversal.h
|
||||
// assignment-client/src/entities
|
||||
//
|
||||
// Created by Andrew Meadows 2017.08.08
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_DiffTraversal_h
|
||||
#define hifi_DiffTraversal_h
|
||||
|
||||
#include <ViewFrustum.h>
|
||||
|
||||
#include "EntityTreeElement.h"
|
||||
|
||||
// DiffTraversal traverses the tree and applies _scanElementCallback on elements it finds
|
||||
class DiffTraversal {
|
||||
public:
|
||||
// VisibleElement is a struct identifying an element and how it intersected the view.
|
||||
// The intersection is used to optimize culling entities from the sendQueue.
|
||||
class VisibleElement {
|
||||
public:
|
||||
EntityTreeElementPointer element;
|
||||
ViewFrustum::intersection intersection { ViewFrustum::OUTSIDE };
|
||||
};
|
||||
|
||||
// View is a struct with a ViewFrustum and LOD parameters
|
||||
class View {
|
||||
public:
|
||||
ViewFrustum viewFrustum;
|
||||
uint64_t startTime { 0 };
|
||||
float lodScaleFactor { 1.0f };
|
||||
bool usesViewFrustum { true };
|
||||
};
|
||||
|
||||
// Waypoint is an bookmark in a "path" of waypoints during a traversal.
|
||||
class Waypoint {
|
||||
public:
|
||||
Waypoint(EntityTreeElementPointer& element);
|
||||
|
||||
void getNextVisibleElementFirstTime(VisibleElement& next, const View& view);
|
||||
void getNextVisibleElementRepeat(VisibleElement& next, const View& view, uint64_t lastTime);
|
||||
void getNextVisibleElementDifferential(VisibleElement& next, const View& view, const View& lastView);
|
||||
|
||||
int8_t getNextIndex() const { return _nextIndex; }
|
||||
void initRootNextIndex() { _nextIndex = -1; }
|
||||
|
||||
protected:
|
||||
EntityTreeElementWeakPointer _weakElement;
|
||||
int8_t _nextIndex;
|
||||
};
|
||||
|
||||
typedef enum { First, Repeat, Differential } Type;
|
||||
|
||||
DiffTraversal();
|
||||
|
||||
Type prepareNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset, bool usesViewFrustum);
|
||||
|
||||
const ViewFrustum& getCurrentView() const { return _currentView.viewFrustum; }
|
||||
const ViewFrustum& getCompletedView() const { return _completedView.viewFrustum; }
|
||||
|
||||
bool doesCurrentUseViewFrustum() const { return _currentView.usesViewFrustum; }
|
||||
float getCurrentLODScaleFactor() const { return _currentView.lodScaleFactor; }
|
||||
float getCompletedLODScaleFactor() const { return _completedView.lodScaleFactor; }
|
||||
|
||||
uint64_t getStartOfCompletedTraversal() const { return _completedView.startTime; }
|
||||
bool finished() const { return _path.empty(); }
|
||||
|
||||
void setScanCallback(std::function<void (VisibleElement&)> cb);
|
||||
void traverse(uint64_t timeBudget);
|
||||
|
||||
private:
|
||||
void getNextVisibleElement(VisibleElement& next);
|
||||
|
||||
View _currentView;
|
||||
View _completedView;
|
||||
std::vector<Waypoint> _path;
|
||||
std::function<void (VisibleElement&)> _getNextVisibleElementCallback { nullptr };
|
||||
std::function<void (VisibleElement&)> _scanElementCallback { [](VisibleElement& e){} };
|
||||
};
|
||||
|
||||
#endif // hifi_EntityPriorityQueue_h
|
|
@ -32,7 +32,8 @@
|
|||
#include "EntitySimulation.h"
|
||||
#include "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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue