Seth Alvesmerge from upstreamb425cc611 years agoHistory
1074 lines · 41.0 KB · C++
RawDownload
////  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.