//// EntityTree.cpp// libraries/entities/src//// Created by Brad Hefta-Gaub on 12/4/13.// Copyright 2013 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 <PerfStat.h>#include <QDateTime>#include <QtScript/QScriptEngine> #include "EntityTree.h"#include "EntitySimulation.h"#include "VariantMapToScriptValue.h" #include "AddEntityOperator.h"#include "MovingEntitiesOperator.h"#include "UpdateEntityOperator.h"#include "QVariantGLM.h"#include "EntitiesLogging.h"#include "RecurseOctreeToMapOperator.h" const quint64 SIMULATOR_CHANGE_LOCKOUT_PERIOD = (quint64)(0.2f * USECS_PER_SECOND); EntityTree::EntityTree(bool shouldReaverage) : Octree(shouldReaverage), _fbxService(NULL), _simulation(NULL){ _rootElement = createNewElement();} EntityTree::~EntityTree() { eraseAllOctreeElements(false);} EntityTreeElement* EntityTree::createNewElement(unsigned char * octalCode) { EntityTreeElement* newElement = new EntityTreeElement(octalCode); newElement->setTree(this); return newElement;} void EntityTree::eraseAllOctreeElements(bool createNewRoot) { emit clearingEntities(); // this would be a good place to clean up our entities... if (_simulation) { _simulation->lock(); _simulation->clearEntities(); _simulation->unlock(); } foreach (EntityTreeElement* element, _entityToElementMap) { element->cleanupEntities(); } _entityToElementMap.clear(); Octree::eraseAllOctreeElements(createNewRoot);} bool EntityTree::handlesEditPacketType(PacketType packetType) const { // we handle these types of "edit" packets switch (packetType) { case PacketTypeEntityAdd: case PacketTypeEntityEdit: case PacketTypeEntityErase: return true; default: return false; }} /// Adds a new entity item to the treevoid EntityTree::postAddEntity(EntityItemPointer entity) { assert(entity); // check to see if we need to simulate this entity.. if (_simulation) { _simulation->lock(); _simulation->addEntity(entity); _simulation->unlock(); } _isDirty = true; maybeNotifyNewCollisionSoundURL("", entity->getCollisionSoundURL()); emit addingEntity(entity->getEntityItemID());} bool EntityTree::updateEntity(const EntityItemID& entityID, const EntityItemProperties& properties, const SharedNodePointer& senderNode) { EntityTreeElement* containingElement = getContainingElement(entityID); if (!containingElement) { qCDebug(entities) << "UNEXPECTED!!!! EntityTree::updateEntity() entityID doesn't exist!!! entityID=" << entityID; return false; } EntityItemPointer existingEntity = containingElement->getEntityWithEntityItemID(entityID); if (!existingEntity) { qCDebug(entities) << "UNEXPECTED!!!! don't call updateEntity() on entity items that don't exist. entityID=" << entityID; return false; } return updateEntityWithElement(existingEntity, properties, containingElement, senderNode);} bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperties& properties, const SharedNodePointer& senderNode) { EntityTreeElement* containingElement = getContainingElement(entity->getEntityItemID()); if (!containingElement) { qCDebug(entities) << "UNEXPECTED!!!! EntityTree::updateEntity() entity-->element lookup failed!!! entityID=" << entity->getEntityItemID(); return false; } return updateEntityWithElement(entity, properties, containingElement, senderNode);} bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityItemProperties& origProperties, EntityTreeElement* containingElement, const SharedNodePointer& senderNode) { EntityItemProperties properties = origProperties; bool allowLockChange; QUuid senderID; if (senderNode.isNull()) { auto nodeList = DependencyManager::get<NodeList>(); allowLockChange = nodeList->getThisNodeCanAdjustLocks(); senderID = nodeList->getSessionUUID(); } else { allowLockChange = senderNode->getCanAdjustLocks(); senderID = senderNode->getUUID(); } if (!allowLockChange && (entity->getLocked() != properties.getLocked())) { qCDebug(entities) << "Refusing disallowed lock adjustment."; return false; } // enforce support for locked entities. If an entity is currently locked, then the only // property we allow you to change is the locked property. if (entity->getLocked()) { if (properties.lockedChanged()) { bool wantsLocked = properties.getLocked(); if (!wantsLocked) { EntityItemProperties tempProperties; tempProperties.setLocked(wantsLocked); UpdateEntityOperator theOperator(this, containingElement, entity, tempProperties); recurseTreeWithOperator(&theOperator); _isDirty = true; } } } else { if (getIsServer()) { bool simulationBlocked = !entity->getSimulatorID().isNull(); if (properties.simulatorIDChanged()) { QUuid submittedID = properties.getSimulatorID(); // a legit interface will only submit their own ID or NULL: if (submittedID.isNull()) { if (entity->getSimulatorID() == senderID) { // We only allow the simulation owner to clear their own simulationID's. simulationBlocked = false; } // else: We assume the sender really did believe it was the simulation owner when it sent } else if (submittedID == senderID) { // the sender is trying to take or continue ownership if (entity->getSimulatorID().isNull() || entity->getSimulatorID() == senderID) { simulationBlocked = false; } else { // the sender is trying to steal ownership from another simulator // so we apply the ownership change filter if (usecTimestampNow() - entity->getSimulatorIDChangedTime() > SIMULATOR_CHANGE_LOCKOUT_PERIOD) { simulationBlocked = false; } } } else { // the entire update is suspect --> ignore it return false; } } if (simulationBlocked) { // squash the physics-related changes. properties.setSimulatorIDChanged(false); properties.setPositionChanged(false); properties.setRotationChanged(false); } } // else client accepts what the server says QString entityScriptBefore = entity->getScript(); QString collisionSoundURLBefore = entity->getCollisionSoundURL(); uint32_t preFlags = entity->getDirtyFlags(); UpdateEntityOperator theOperator(this, containingElement, entity, properties); recurseTreeWithOperator(&theOperator); _isDirty = true; uint32_t newFlags = entity->getDirtyFlags() & ~preFlags; if (newFlags) { if (_simulation) { if (newFlags & DIRTY_SIMULATION_FLAGS) { _simulation->lock(); _simulation->changeEntity(entity); _simulation->unlock(); } } else { // normally the _simulation clears ALL updateFlags, but since there is none we do it explicitly entity->clearDirtyFlags(); } } QString entityScriptAfter = entity->getScript(); if (entityScriptBefore != entityScriptAfter) { emitEntityScriptChanging(entity->getEntityItemID()); // the entity script has changed } maybeNotifyNewCollisionSoundURL(collisionSoundURLBefore, entity->getCollisionSoundURL()); } // TODO: this final containingElement check should eventually be removed (or wrapped in an #ifdef DEBUG). containingElement = getContainingElement(entity->getEntityItemID()); if (!containingElement) { qCDebug(entities) << "UNEXPECTED!!!! after updateEntity() we no longer have a containing element??? entityID=" << entity->getEntityItemID(); return false; } return true;} EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const EntityItemProperties& properties) { EntityItemPointer result = NULL; if (getIsClient()) { // if our Node isn't allowed to create entities in this domain, don't try. auto nodeList = DependencyManager::get<NodeList>(); if (!nodeList->getThisNodeCanRez()) { return NULL; } } bool recordCreationTime = false; if (properties.getCreated() == UNKNOWN_CREATED_TIME) { // the entity's creation time was not specified in properties, which means this is a NEW entity // and we must record its creation time recordCreationTime = true; } // You should not call this on existing entities that are already part of the tree! Call updateEntity() EntityTreeElement* containingElement = getContainingElement(entityID); if (containingElement) { qCDebug(entities) << "UNEXPECTED!!! ----- don't call addEntity() on existing entity items. entityID=" << entityID << "containingElement=" << containingElement; return result; } // construct the instance of the entity EntityTypes::EntityType type = properties.getType(); result = EntityTypes::constructEntityItem(type, entityID, properties); if (result) { if (recordCreationTime) { result->recordCreationTime(); } // Recurse the tree and store the entity in the correct tree element AddEntityOperator theOperator(this, result); recurseTreeWithOperator(&theOperator); postAddEntity(result); } return result;} void EntityTree::emitEntityScriptChanging(const EntityItemID& entityItemID) { emit entityScriptChanging(entityItemID);}void EntityTree::maybeNotifyNewCollisionSoundURL(const QString& previousCollisionSoundURL, const QString& nextCollisionSoundURL) { if (!nextCollisionSoundURL.isEmpty() && (nextCollisionSoundURL != previousCollisionSoundURL)) { emit newCollisionSoundURL(QUrl(nextCollisionSoundURL)); }} void EntityTree::setSimulation(EntitySimulation* simulation) { if (simulation) { // assert that the simulation's backpointer has already been properly connected assert(simulation->getEntityTree() == this); } if (_simulation && _simulation != simulation) { // It's important to clearEntities() on the simulation since taht will update each // EntityItem::_simulationState correctly so as to not confuse the next _simulation. _simulation->lock(); _simulation->clearEntities(); _simulation->unlock(); } _simulation = simulation;} void EntityTree::deleteEntity(const EntityItemID& entityID, bool force, bool ignoreWarnings) { EntityTreeElement* containingElement = getContainingElement(entityID); if (!containingElement) { if (!ignoreWarnings) { qCDebug(entities) << "UNEXPECTED!!!! EntityTree::deleteEntity() entityID doesn't exist!!! entityID=" << entityID; } return; } EntityItemPointer existingEntity = containingElement->getEntityWithEntityItemID(entityID); if (!existingEntity) { if (!ignoreWarnings) { qCDebug(entities) << "UNEXPECTED!!!! don't call EntityTree::deleteEntity() on entity items that don't exist. " "entityID=" << entityID; } return; } if (existingEntity->getLocked() && !force) { if (!ignoreWarnings) { qCDebug(entities) << "ERROR! EntityTree::deleteEntity() trying to delete locked entity. entityID=" << entityID; } return; } emit deletingEntity(entityID); // NOTE: callers must lock the tree before using this method DeleteEntityOperator theOperator(this, entityID); recurseTreeWithOperator(&theOperator); processRemovedEntities(theOperator); _isDirty = true;} void EntityTree::deleteEntities(QSet<EntityItemID> entityIDs, bool force, bool ignoreWarnings) { // NOTE: callers must lock the tree before using this method DeleteEntityOperator theOperator(this); foreach(const EntityItemID& entityID, entityIDs) { EntityTreeElement* containingElement = getContainingElement(entityID); if (!containingElement) { if (!ignoreWarnings) { qCDebug(entities) << "UNEXPECTED!!!! EntityTree::deleteEntities() entityID doesn't exist!!! entityID=" << entityID; } continue; } EntityItemPointer existingEntity = containingElement->getEntityWithEntityItemID(entityID); if (!existingEntity) { if (!ignoreWarnings) { qCDebug(entities) << "UNEXPECTED!!!! don't call EntityTree::deleteEntities() on entity items that don't exist. " "entityID=" << entityID; } continue; } if (existingEntity->getLocked() && !force) { if (!ignoreWarnings) { qCDebug(entities) << "ERROR! EntityTree::deleteEntities() trying to delete locked entity. entityID=" << entityID; } continue; } // tell our delete operator about this entityID theOperator.addEntityIDToDeleteList(entityID); emit deletingEntity(entityID); } if (theOperator.getEntities().size() > 0) { recurseTreeWithOperator(&theOperator); processRemovedEntities(theOperator); _isDirty = true; }} void EntityTree::processRemovedEntities(const DeleteEntityOperator& theOperator) { const RemovedEntities& entities = theOperator.getEntities(); if (_simulation) { _simulation->lock(); } foreach(const EntityToDeleteDetails& details, entities) { EntityItemPointer theEntity = details.entity; if (getIsServer()) { // set up the deleted entities ID quint64 deletedAt = usecTimestampNow(); _recentlyDeletedEntitiesLock.lockForWrite(); _recentlyDeletedEntityItemIDs.insert(deletedAt, theEntity->getEntityItemID()); _recentlyDeletedEntitiesLock.unlock(); } if (_simulation) { theEntity->clearActions(_simulation); _simulation->removeEntity(theEntity); } } if (_simulation) { _simulation->unlock(); }} class FindNearPointArgs {public: glm::vec3 position; float targetRadius; bool found; EntityItemPointer closestEntity; float closestEntityDistance;}; bool EntityTree::findNearPointOperation(OctreeElement* element, void* extraData) { FindNearPointArgs* args = static_cast<FindNearPointArgs*>(extraData); EntityTreeElement* entityTreeElement = static_cast<EntityTreeElement*>(element); glm::vec3 penetration; bool sphereIntersection = entityTreeElement->getAACube().findSpherePenetration(args->position, args->targetRadius, penetration); // If this entityTreeElement contains the point, then search it... if (sphereIntersection) { EntityItemPointer thisClosestEntity = entityTreeElement->getClosestEntity(args->position); // we may have gotten NULL back, meaning no entity was available if (thisClosestEntity) { glm::vec3 entityPosition = thisClosestEntity->getPosition(); float distanceFromPointToEntity = glm::distance(entityPosition, args->position); // If we're within our target radius if (distanceFromPointToEntity <= args->targetRadius) { // we are closer than anything else we've found if (distanceFromPointToEntity < args->closestEntityDistance) { args->closestEntity = thisClosestEntity; args->closestEntityDistance = distanceFromPointToEntity; args->found = true; } } } // we should be able to optimize this... return true; // keep searching in case children have closer entities } // if this element doesn't contain the point, then none of its children can contain the point, so stop searching return false;} EntityItemPointer EntityTree::findClosestEntity(glm::vec3 position, float targetRadius) { FindNearPointArgs args = { position, targetRadius, false, NULL, FLT_MAX }; lockForRead(); // NOTE: This should use recursion, since this is a spatial operation recurseTreeWithOperation(findNearPointOperation, &args); unlock(); return args.closestEntity;} class FindAllNearPointArgs {public: glm::vec3 position; float targetRadius; QVector<EntityItemPointer> entities;}; bool EntityTree::findInSphereOperation(OctreeElement* element, void* extraData) { FindAllNearPointArgs* args = static_cast<FindAllNearPointArgs*>(extraData); glm::vec3 penetration; bool sphereIntersection = element->getAACube().findSpherePenetration(args->position, args->targetRadius, penetration); // If this element contains the point, then search it... if (sphereIntersection) { EntityTreeElement* entityTreeElement = static_cast<EntityTreeElement*>(element); entityTreeElement->getEntities(args->position, args->targetRadius, args->entities); return true; // keep searching in case children have closer entities } // if this element doesn't contain the point, then none of it's children can contain the point, so stop searching return false;} // NOTE: assumes caller has handled lockingvoid EntityTree::findEntities(const glm::vec3& center, float radius, QVector<EntityItemPointer>& foundEntities) { FindAllNearPointArgs args = { center, radius, QVector<EntityItemPointer>() }; // NOTE: This should use recursion, since this is a spatial operation recurseTreeWithOperation(findInSphereOperation, &args); // swap the two lists of entity pointers instead of copy foundEntities.swap(args.entities);} class FindEntitiesInCubeArgs {public: FindEntitiesInCubeArgs(const AACube& cube) : _cube(cube), _foundEntities() { } AACube _cube; QVector<EntityItemPointer> _foundEntities;}; bool EntityTree::findInCubeOperation(OctreeElement* element, void* extraData) { FindEntitiesInCubeArgs* args = static_cast<FindEntitiesInCubeArgs*>(extraData); if (element->getAACube().touches(args->_cube)) { EntityTreeElement* entityTreeElement = static_cast<EntityTreeElement*>(element); entityTreeElement->getEntities(args->_cube, args->_foundEntities); return true; } return false;}Lines 1–500 of 1074. Load more
// // EntityTree.cpp // libraries/entities/src // // Created by Brad Hefta-Gaub on 12/4/13. // Copyright 2013 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 <PerfStat.h> #include <QDateTime> #include <QtScript/QScriptEngine> #include "EntityTree.h" #include "EntitySimulation.h" #include "VariantMapToScriptValue.h" #include "AddEntityOperator.h" #include "MovingEntitiesOperator.h" #include "UpdateEntityOperator.h" #include "QVariantGLM.h" #include "EntitiesLogging.h" #include "RecurseOctreeToMapOperator.h" const quint64 SIMULATOR_CHANGE_LOCKOUT_PERIOD = (quint64)(0.2f * USECS_PER_SECOND); EntityTree::EntityTree(bool shouldReaverage) : Octree(shouldReaverage), _fbxService(NULL), _simulation(NULL) { _rootElement = createNewElement(); } EntityTree::~EntityTree() { eraseAllOctreeElements(false); } EntityTreeElement* EntityTree::createNewElement(unsigned char * octalCode) { EntityTreeElement* newElement = new EntityTreeElement(octalCode); newElement->setTree(this); return newElement; } void EntityTree::eraseAllOctreeElements(bool createNewRoot) { emit clearingEntities(); // this would be a good place to clean up our entities... if (_simulation) { _simulation->lock(); _simulation->clearEntities(); _simulation->unlock(); } foreach (EntityTreeElement* element, _entityToElementMap) { element->cleanupEntities(); } _entityToElementMap.clear(); Octree::eraseAllOctreeElements(createNewRoot); } bool EntityTree::handlesEditPacketType(PacketType packetType) const { // we handle these types of "edit" packets switch (packetType) { case PacketTypeEntityAdd: case PacketTypeEntityEdit: case PacketTypeEntityErase: return true; default: return false; } } /// Adds a new entity item to the tree void EntityTree::postAddEntity(EntityItemPointer entity) { assert(entity); // check to see if we need to simulate this entity.. if (_simulation) { _simulation->lock(); _simulation->addEntity(entity); _simulation->unlock(); } _isDirty = true; maybeNotifyNewCollisionSoundURL("", entity->getCollisionSoundURL()); emit addingEntity(entity->getEntityItemID()); } bool EntityTree::updateEntity(const EntityItemID& entityID, const EntityItemProperties& properties, const SharedNodePointer& senderNode) { EntityTreeElement* containingElement = getContainingElement(entityID); if (!containingElement) { qCDebug(entities) << "UNEXPECTED!!!! EntityTree::updateEntity() entityID doesn't exist!!! entityID=" << entityID; return false; } EntityItemPointer existingEntity = containingElement->getEntityWithEntityItemID(entityID); if (!existingEntity) { qCDebug(entities) << "UNEXPECTED!!!! don't call updateEntity() on entity items that don't exist. entityID=" << entityID; return false; } return updateEntityWithElement(existingEntity, properties, containingElement, senderNode); } bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperties& properties, const SharedNodePointer& senderNode) { EntityTreeElement* containingElement = getContainingElement(entity->getEntityItemID()); if (!containingElement) { qCDebug(entities) << "UNEXPECTED!!!! EntityTree::updateEntity() entity-->element lookup failed!!! entityID=" << entity->getEntityItemID(); return false; } return updateEntityWithElement(entity, properties, containingElement, senderNode); } bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityItemProperties& origProperties, EntityTreeElement* containingElement, const SharedNodePointer& senderNode) { EntityItemProperties properties = origProperties; bool allowLockChange; QUuid senderID; if (senderNode.isNull()) { auto nodeList = DependencyManager::get<NodeList>(); allowLockChange = nodeList->getThisNodeCanAdjustLocks(); senderID = nodeList->getSessionUUID(); } else { allowLockChange = senderNode->getCanAdjustLocks(); senderID = senderNode->getUUID(); } if (!allowLockChange && (entity->getLocked() != properties.getLocked())) { qCDebug(entities) << "Refusing disallowed lock adjustment."; return false; } // enforce support for locked entities. If an entity is currently locked, then the only // property we allow you to change is the locked property. if (entity->getLocked()) { if (properties.lockedChanged()) { bool wantsLocked = properties.getLocked(); if (!wantsLocked) { EntityItemProperties tempProperties; tempProperties.setLocked(wantsLocked); UpdateEntityOperator theOperator(this, containingElement, entity, tempProperties); recurseTreeWithOperator(&theOperator); _isDirty = true; } } } else { if (getIsServer()) { bool simulationBlocked = !entity->getSimulatorID().isNull(); if (properties.simulatorIDChanged()) { QUuid submittedID = properties.getSimulatorID(); // a legit interface will only submit their own ID or NULL: if (submittedID.isNull()) { if (entity->getSimulatorID() == senderID) { // We only allow the simulation owner to clear their own simulationID's. simulationBlocked = false; } // else: We assume the sender really did believe it was the simulation owner when it sent } else if (submittedID == senderID) { // the sender is trying to take or continue ownership if (entity->getSimulatorID().isNull() || entity->getSimulatorID() == senderID) { simulationBlocked = false; } else { // the sender is trying to steal ownership from another simulator // so we apply the ownership change filter if (usecTimestampNow() - entity->getSimulatorIDChangedTime() > SIMULATOR_CHANGE_LOCKOUT_PERIOD) { simulationBlocked = false; } } } else { // the entire update is suspect --> ignore it return false; } } if (simulationBlocked) { // squash the physics-related changes. properties.setSimulatorIDChanged(false); properties.setPositionChanged(false); properties.setRotationChanged(false); } } // else client accepts what the server says QString entityScriptBefore = entity->getScript(); QString collisionSoundURLBefore = entity->getCollisionSoundURL(); uint32_t preFlags = entity->getDirtyFlags(); UpdateEntityOperator theOperator(this, containingElement, entity, properties); recurseTreeWithOperator(&theOperator); _isDirty = true; uint32_t newFlags = entity->getDirtyFlags() & ~preFlags; if (newFlags) { if (_simulation) { if (newFlags & DIRTY_SIMULATION_FLAGS) { _simulation->lock(); _simulation->changeEntity(entity); _simulation->unlock(); } } else { // normally the _simulation clears ALL updateFlags, but since there is none we do it explicitly entity->clearDirtyFlags(); } } QString entityScriptAfter = entity->getScript(); if (entityScriptBefore != entityScriptAfter) { emitEntityScriptChanging(entity->getEntityItemID()); // the entity script has changed } maybeNotifyNewCollisionSoundURL(collisionSoundURLBefore, entity->getCollisionSoundURL()); } // TODO: this final containingElement check should eventually be removed (or wrapped in an #ifdef DEBUG). containingElement = getContainingElement(entity->getEntityItemID()); if (!containingElement) { qCDebug(entities) << "UNEXPECTED!!!! after updateEntity() we no longer have a containing element??? entityID=" << entity->getEntityItemID(); return false; } return true; } EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const EntityItemProperties& properties) { EntityItemPointer result = NULL; if (getIsClient()) { // if our Node isn't allowed to create entities in this domain, don't try. auto nodeList = DependencyManager::get<NodeList>(); if (!nodeList->getThisNodeCanRez()) { return NULL; } } bool recordCreationTime = false; if (properties.getCreated() == UNKNOWN_CREATED_TIME) { // the entity's creation time was not specified in properties, which means this is a NEW entity // and we must record its creation time recordCreationTime = true; } // You should not call this on existing entities that are already part of the tree! Call updateEntity() EntityTreeElement* containingElement = getContainingElement(entityID); if (containingElement) { qCDebug(entities) << "UNEXPECTED!!! ----- don't call addEntity() on existing entity items. entityID=" << entityID << "containingElement=" << containingElement; return result; } // construct the instance of the entity EntityTypes::EntityType type = properties.getType(); result = EntityTypes::constructEntityItem(type, entityID, properties); if (result) { if (recordCreationTime) { result->recordCreationTime(); } // Recurse the tree and store the entity in the correct tree element AddEntityOperator theOperator(this, result); recurseTreeWithOperator(&theOperator); postAddEntity(result); } return result; } void EntityTree::emitEntityScriptChanging(const EntityItemID& entityItemID) { emit entityScriptChanging(entityItemID); } void EntityTree::maybeNotifyNewCollisionSoundURL(const QString& previousCollisionSoundURL, const QString& nextCollisionSoundURL) { if (!nextCollisionSoundURL.isEmpty() && (nextCollisionSoundURL != previousCollisionSoundURL)) { emit newCollisionSoundURL(QUrl(nextCollisionSoundURL)); } } void EntityTree::setSimulation(EntitySimulation* simulation) { if (simulation) { // assert that the simulation's backpointer has already been properly connected assert(simulation->getEntityTree() == this); } if (_simulation && _simulation != simulation) { // It's important to clearEntities() on the simulation since taht will update each // EntityItem::_simulationState correctly so as to not confuse the next _simulation. _simulation->lock(); _simulation->clearEntities(); _simulation->unlock(); } _simulation = simulation; } void EntityTree::deleteEntity(const EntityItemID& entityID, bool force, bool ignoreWarnings) { EntityTreeElement* containingElement = getContainingElement(entityID); if (!containingElement) { if (!ignoreWarnings) { qCDebug(entities) << "UNEXPECTED!!!! EntityTree::deleteEntity() entityID doesn't exist!!! entityID=" << entityID; } return; } EntityItemPointer existingEntity = containingElement->getEntityWithEntityItemID(entityID); if (!existingEntity) { if (!ignoreWarnings) { qCDebug(entities) << "UNEXPECTED!!!! don't call EntityTree::deleteEntity() on entity items that don't exist. " "entityID=" << entityID; } return; } if (existingEntity->getLocked() && !force) { if (!ignoreWarnings) { qCDebug(entities) << "ERROR! EntityTree::deleteEntity() trying to delete locked entity. entityID=" << entityID; } return; } emit deletingEntity(entityID); // NOTE: callers must lock the tree before using this method DeleteEntityOperator theOperator(this, entityID); recurseTreeWithOperator(&theOperator); processRemovedEntities(theOperator); _isDirty = true; } void EntityTree::deleteEntities(QSet<EntityItemID> entityIDs, bool force, bool ignoreWarnings) { // NOTE: callers must lock the tree before using this method DeleteEntityOperator theOperator(this); foreach(const EntityItemID& entityID, entityIDs) { EntityTreeElement* containingElement = getContainingElement(entityID); if (!containingElement) { if (!ignoreWarnings) { qCDebug(entities) << "UNEXPECTED!!!! EntityTree::deleteEntities() entityID doesn't exist!!! entityID=" << entityID; } continue; } EntityItemPointer existingEntity = containingElement->getEntityWithEntityItemID(entityID); if (!existingEntity) { if (!ignoreWarnings) { qCDebug(entities) << "UNEXPECTED!!!! don't call EntityTree::deleteEntities() on entity items that don't exist. " "entityID=" << entityID; } continue; } if (existingEntity->getLocked() && !force) { if (!ignoreWarnings) { qCDebug(entities) << "ERROR! EntityTree::deleteEntities() trying to delete locked entity. entityID=" << entityID; } continue; } // tell our delete operator about this entityID theOperator.addEntityIDToDeleteList(entityID); emit deletingEntity(entityID); } if (theOperator.getEntities().size() > 0) { recurseTreeWithOperator(&theOperator); processRemovedEntities(theOperator); _isDirty = true; } } void EntityTree::processRemovedEntities(const DeleteEntityOperator& theOperator) { const RemovedEntities& entities = theOperator.getEntities(); if (_simulation) { _simulation->lock(); } foreach(const EntityToDeleteDetails& details, entities) { EntityItemPointer theEntity = details.entity; if (getIsServer()) { // set up the deleted entities ID quint64 deletedAt = usecTimestampNow(); _recentlyDeletedEntitiesLock.lockForWrite(); _recentlyDeletedEntityItemIDs.insert(deletedAt, theEntity->getEntityItemID()); _recentlyDeletedEntitiesLock.unlock(); } if (_simulation) { theEntity->clearActions(_simulation); _simulation->removeEntity(theEntity); } } if (_simulation) { _simulation->unlock(); } } class FindNearPointArgs { public: glm::vec3 position; float targetRadius; bool found; EntityItemPointer closestEntity; float closestEntityDistance; }; bool EntityTree::findNearPointOperation(OctreeElement* element, void* extraData) { FindNearPointArgs* args = static_cast<FindNearPointArgs*>(extraData); EntityTreeElement* entityTreeElement = static_cast<EntityTreeElement*>(element); glm::vec3 penetration; bool sphereIntersection = entityTreeElement->getAACube().findSpherePenetration(args->position, args->targetRadius, penetration); // If this entityTreeElement contains the point, then search it... if (sphereIntersection) { EntityItemPointer thisClosestEntity = entityTreeElement->getClosestEntity(args->position); // we may have gotten NULL back, meaning no entity was available if (thisClosestEntity) { glm::vec3 entityPosition = thisClosestEntity->getPosition(); float distanceFromPointToEntity = glm::distance(entityPosition, args->position); // If we're within our target radius if (distanceFromPointToEntity <= args->targetRadius) { // we are closer than anything else we've found if (distanceFromPointToEntity < args->closestEntityDistance) { args->closestEntity = thisClosestEntity; args->closestEntityDistance = distanceFromPointToEntity; args->found = true; } } } // we should be able to optimize this... return true; // keep searching in case children have closer entities } // if this element doesn't contain the point, then none of its children can contain the point, so stop searching return false; } EntityItemPointer EntityTree::findClosestEntity(glm::vec3 position, float targetRadius) { FindNearPointArgs args = { position, targetRadius, false, NULL, FLT_MAX }; lockForRead(); // NOTE: This should use recursion, since this is a spatial operation recurseTreeWithOperation(findNearPointOperation, &args); unlock(); return args.closestEntity; } class FindAllNearPointArgs { public: glm::vec3 position; float targetRadius; QVector<EntityItemPointer> entities; }; bool EntityTree::findInSphereOperation(OctreeElement* element, void* extraData) { FindAllNearPointArgs* args = static_cast<FindAllNearPointArgs*>(extraData); glm::vec3 penetration; bool sphereIntersection = element->getAACube().findSpherePenetration(args->position, args->targetRadius, penetration); // If this element contains the point, then search it... if (sphereIntersection) { EntityTreeElement* entityTreeElement = static_cast<EntityTreeElement*>(element); entityTreeElement->getEntities(args->position, args->targetRadius, args->entities); return true; // keep searching in case children have closer entities } // if this element doesn't contain the point, then none of it's children can contain the point, so stop searching return false; } // NOTE: assumes caller has handled locking void EntityTree::findEntities(const glm::vec3& center, float radius, QVector<EntityItemPointer>& foundEntities) { FindAllNearPointArgs args = { center, radius, QVector<EntityItemPointer>() }; // NOTE: This should use recursion, since this is a spatial operation recurseTreeWithOperation(findInSphereOperation, &args); // swap the two lists of entity pointers instead of copy foundEntities.swap(args.entities); } class FindEntitiesInCubeArgs { public: FindEntitiesInCubeArgs(const AACube& cube) : _cube(cube), _foundEntities() { } AACube _cube; QVector<EntityItemPointer> _foundEntities; }; bool EntityTree::findInCubeOperation(OctreeElement* element, void* extraData) { FindEntitiesInCubeArgs* args = static_cast<FindEntitiesInCubeArgs*>(extraData); if (element->getAACube().touches(args->_cube)) { EntityTreeElement* entityTreeElement = static_cast<EntityTreeElement*>(element); entityTreeElement->getEntities(args->_cube, args->_foundEntities); return true; } return false; }