From 5e5af9ceea2c9fccbec45edfea0a040d5dcb5c81 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 3 Jul 2014 10:44:34 -0700 Subject: [PATCH] hacking on more support for virtual entities --- libraries/entities/src/EntityItem.cpp | 5 + libraries/entities/src/EntityItem.h | 15 +- libraries/entities/src/EntityTree.cpp | 204 ++++++++++++++++++- libraries/entities/src/EntityTree.h | 2 + libraries/entities/src/EntityTreeElement.cpp | 78 ++++--- libraries/entities/src/EntityTreeElement.h | 8 + 6 files changed, 277 insertions(+), 35 deletions(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 7eac3deb23..11c2375eca 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -42,6 +42,11 @@ bool EntityTypes::registerEntityTypeName(EntityType_t entityType, const QString& return true; } +EntityItem* EntityTypes::constructEntityItem(EntityType_t entityType, const EntityItemID& entityID, const EntityItemProperties& properties) { + return new EntityItem(entityID, properties); // for now, needs to support registration of constructor +} + + bool registered = EntityTypes::registerEntityTypeName(EntityTypes::Base, "Base") && EntityTypes::registerEntityTypeName(EntityTypes::Model, "Model"); // TODO: move this to model subclass diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 7e57c50e35..c4090eff2f 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -30,6 +30,7 @@ class EntityItem; +class EntityItemID; class EntityEditPacketSender; class EntityItemProperties; class EntitysScriptingInterface; @@ -73,7 +74,8 @@ public: } EntityType_t; static const QString& getEntityTypeName(EntityType_t entityType); - static bool registerEntityTypeName(EntityType_t entityType, const QString& name); + static bool registerEntityType(EntityType_t entityType, const QString& name); + static EntityItem* constructEntityItem(EntityType_t entityType, const EntityItemID& entityID, const EntityItemProperties& properties); private: static QHash _typeNameHash; }; @@ -122,11 +124,14 @@ public: glm::vec3 getMinimumPoint() const { return _position - glm::vec3(_radius, _radius, _radius); } glm::vec3 getMaximumPoint() const { return _position + glm::vec3(_radius, _radius, _radius); } + AACube getAACube() const { return AACube(getMinimumPoint(), getSize()); } /// AACube in domain scale units (0.0 - 1.0) void debugDump() const; // properties of all entities + EntityTypes::EntityType_t getType() const { return _type; } const glm::vec3& getPosition() const { return _position; } float getRadius() const { return _radius; } + glm::vec3 getSize() const { return glm::vec3(_radius, _radius, _radius) * 2.0f; } const glm::quat& getRotation() const { return _rotation; } bool getShouldBeDeleted() const { return _shouldBeDeleted; } @@ -134,6 +139,9 @@ public: void setPosition(const glm::vec3& value) { _position = value; _positionChanged = true; } void setRadius(float value) { _radius = value; _radiusChanged = true; } void setShouldBeDeleted(bool shouldBeDeleted) { _shouldBeDeleted = shouldBeDeleted; _shouldBeDeletedChanged = true; } + + // NOTE: how do we handle _defaultSettings??? + bool containsBoundsProperties() const { return (_positionChanged || _radiusChanged); } #if 0 // def HIDE_SUBCLASS_METHODS // properties we want to move to just models and particles @@ -161,6 +169,7 @@ private: bool _idSet; quint64 _lastEdited; + EntityTypes::EntityType_t _type; glm::vec3 _position; float _radius; glm::quat _rotation; @@ -311,7 +320,7 @@ public: // attributes applicable to all entity types - quint32 getType() const { return _type; } + EntityTypes::EntityType_t getType() const { return _type; } const glm::vec3& getPosition() const { return _position; } /// get position in domain scale units (0.0 - 1.0) void setPosition(const glm::vec3& value) { _position = value; } /// set position in domain scale units (0.0 - 1.0) @@ -378,7 +387,7 @@ protected: static std::map _tokenIDsToIDs; quint32 _id; - quint32 _type; + EntityTypes::EntityType_t _type; uint32_t _creatorTokenID; bool _newlyCreated; quint64 _lastUpdated; diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 28a8ced560..eba6a06ca6 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -218,17 +218,205 @@ void EntityTree::storeEntity(const EntityItem& model, const SharedNodePointer& s } -void EntityTree::updateEntity(const EntityItemID& entityID, const EntityItemProperties& properties) { - EntityItem updateItem(entityID); - bool wantDebug = false; - if (wantDebug) { - qDebug() << "EntityTree::updateEntity(entityID, properties) line:" << __LINE__ << "updateItem:"; - updateItem.debugDump(); +/// Give an EntityItemID and EntityItemProperties, this will either find the correct entity that already exists +/// in the tree or it will create a new entity of the type specified by the properties and return that item. +EntityItem* EntityTree::getOrCreateEntityItem(const EntityItemID& entityID, const EntityItemProperties& properties) { + EntityItem* result = NULL; + + // we need to first see if we already have the entity in our tree by finding the containing element of the entity + EntityTreeElement* containingElement = getContainingElement(entityID); + if (containingElement) { + result = containingElement->getEntityWithEntityItemID(entityID); } + // if the element does not exist, then create a new one of the specified type... + if (!result) { + EntityTypes::EntityType_t type = properties.getType(); + result = EntityTypes::constructEntityItem(type, entityID, properties); + } + return result; +} + + +class UpdateEntityOperator : public RecurseOctreeOperator { +public: + UpdateEntityOperator(EntityTree* tree, EntityTreeElement* containingElement, + EntityItem* oldEntity, const EntityItemProperties& properties); + virtual bool PreRecursion(OctreeElement* element); + virtual bool PostRecursion(OctreeElement* element); + virtual OctreeElement* PossiblyCreateChildAt(OctreeElement* element, int childIndex); +private: + EntityTree* _tree; + const EntityItem* _newEntity; + const EntityItem* _oldEntity; + EntityTreeElement* _containingElement; + bool _foundOld; + bool _foundNew; + quint64 _changeTime; + AABox _oldEntityCube; + AABox _newEntityCube; + + bool subTreeContainsOldEntity(OctreeElement* element); + bool subTreeContainsNewEntity(OctreeElement* element); +}; + +UpdateEntityOperator::UpdateEntityOperator(EntityTree* tree, + EntityTreeElement* containingElement, + EntityItem* oldEntity, + const EntityItemProperties& properties) : + _tree(tree), + _newEntity(NULL), + _oldEntity(oldEntity), + _containingElement(containingElement), + _foundOld(false), + _foundNew(false), + _changeTime(usecTimestampNow()) +{ + // caller must have verified existence of containingElement and oldEntity + assert(_containingElement && _oldEntity); + + _oldEntityCube = _oldEntity->getAACube(); + + // If our new properties don't have bounds details (no change to position, etc) or if this containing element would + // be the best fit for our new properties, then just do the new portion of the store pass, since the change path will + // be the same for both parts of the update + if (!properties.containsBoundsProperties() || _containingElement->bestFitBounds(properties)) { + _foundOld = true; + _newEntityCube = _oldEntityCube; + } else { + _newEntityCube = _oldEntity->getAACube(); + } + +} + +// does this model tree element contain the old model +bool UpdateEntityOperator::subTreeContainsOldEntity(OctreeElement* element) { + bool containsEntity = false; + + // If we don't have an old model, then we don't contain the model, otherwise + // check the bounds + if (_oldEntity) { + AACube elementCube = element->getAACube(); + AACube modelCube = _oldEntity->getAACube(); + containsEntity = elementCube.contains(modelCube); + } + return containsEntity; +} + +bool UpdateEntityOperator::subTreeContainsNewEntity(OctreeElement* element) { + AACube elementCube = element->getAACube(); + AACube modelCube = _newEntity.getAACube(); + return elementCube.contains(modelCube); +} + + +bool UpdateEntityOperator::PreRecursion(OctreeElement* element) { + EntityTreeElement* modelTreeElement = static_cast(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: + // * We have not yet found the old model, and this branch contains our old model + // * We have not yet found the new model, and this branch contains our new model + // + // Note: it's often the case that the branch in question contains both the old model + // and the new model. + + bool keepSearching = false; // assume we don't need to search any more + + // If we haven't yet found the old model, and this subTreeContains our old + // model, then we need to keep searching. + if (!_foundOld && subTreeContainsOldEntity(element)) { + + // If this is the element we're looking for, then ask it to remove the old model + // and we can stop searching. + if (modelTreeElement == _containingElement) { + + // If the containgElement IS NOT the best fit for the new model properties + // then we need to remove it, and the updateEntity below will store it in the + // correct element. + if (!_containingElement->bestFitEntityBounds(&_newEntity)) { + modelTreeElement->removeEntityWithEntityItemID(_newEntity.getEntityItemID()); + + // If we haven't yet found the new location, then we need to + // make sure to remove our model to element map, because for + // now we're not in that map + if (!_foundNew) { + _tree->setContainingElement(_newEntity.getEntityItemID(), NULL); + } + } + _foundOld = true; + } else { + // if this isn't the element we're looking for, then keep searching + keepSearching = true; + } + } + + // If we haven't yet found the new model, and this subTreeContains our new + // model, then we need to keep searching. + if (!_foundNew && subTreeContainsNewEntity(element)) { + + // Note: updateEntity() will only operate on correctly found models and/or add them + // to the element if they SHOULD be stored there. + if (modelTreeElement->updateEntity(_newEntity)) { + //qDebug() << "UpdateEntityOperator::PreRecursion()... model was updated!"; + _foundNew = true; + // NOTE: don't change the keepSearching here, if it came in here + // false then we stay false, if it came in here true, then it + // means we're still searching for our old model and this branch + // contains our old model. In which case we want to keep searching. + + } else { + keepSearching = true; + } + } + + return keepSearching; // if we haven't yet found it, keep looking +} + +bool UpdateEntityOperator::PostRecursion(OctreeElement* element) { + // Post-recursion is the unwinding process. For this operation, while we + // unwind we want to mark the path as being dirty if we changed it below. + // We might have two paths, one for the old model and one for the new model. + bool keepSearching = !_foundOld || !_foundNew; + + // As we unwind, if we're in either of these two paths, we mark our element + // as dirty. + if ((_foundOld && subTreeContainsOldEntity(element)) || + (_foundNew && subTreeContainsNewEntity(element))) { + element->markWithChangedTime(); + } + return keepSearching; // if we haven't yet found it, keep looking +} + +OctreeElement* UpdateEntityOperator::PossiblyCreateChildAt(OctreeElement* element, int childIndex) { + // If we're getting called, it's because there was no child element at this index while recursing. + // We only care if this happens while still searching for the new model location. + // Check to see if + if (!_foundNew) { + int indexOfChildContainingNewEntity = element->getMyChildContaining(_newEntity.getAACube()); + + if (childIndex == indexOfChildContainingNewEntity) { + return element->addChildAtIndex(childIndex); + } + } + return NULL; +} + +// NOTE: This should only be called on entities that are known to exist. If the entity can not be found, the method +// will exit without any changes to tree state +// +// NOTE: If the properties contain position/scale/rotation, then there is a potential that the entity could need to +// move to a different EntityTreeElement, otherwise it will not move. If the entity can not move, then the dirty path +// can be determined to just be the path to the entity +void EntityTree::updateEntity(const EntityItemID& entityID, const EntityItemProperties& properties) { + EntityItem* updateItem = NULL; + + bool entityMightMove = properties.containsBoundsProperties(); + // since the properties might not be complete, they may only contain some values, - // we need to first see if we already have the model in our tree, and make a copy of + // we need to first see if we already have the entity in our tree, and make a copy of // its existing properties first EntityTreeElement* containingElement = getContainingElement(entityID); @@ -259,6 +447,8 @@ void EntityTree::updateEntity(const EntityItemID& entityID, const EntityItemProp } } } + + if (updateItem) { updateItem.setProperties(properties); if (wantDebug) { diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index c16bba72b3..43ce620f72 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -50,6 +50,8 @@ public: virtual bool rootElementHasData() const { return true; } virtual void update(); + EntityItem* getOrCreateEntityItem(const EntityItemID& entityID, const EntityItemProperties& properties); + void storeEntity(const EntityItem& model, const SharedNodePointer& senderNode = SharedNodePointer()); void updateEntity(const EntityItemID& modelID, const EntityItemProperties& properties); void addEntity(const EntityItemID& modelID, const EntityItemProperties& properties); diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index fc5400093e..1acc5ddc33 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -175,14 +175,30 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData } bool EntityTreeElement::containsEntityBounds(const EntityItem* entity) const { - glm::vec3 clampedMin = glm::clamp(entity->getMinimumPoint(), 0.0f, 1.0f); - glm::vec3 clampedMax = glm::clamp(entity->getMaximumPoint(), 0.0f, 1.0f); - return _cube.contains(clampedMin) && _cube.contains(clampedMax); + return containsBounds(entity->getMinimumPoint(), entity->getMaximumPoint()); } bool EntityTreeElement::bestFitEntityBounds(const EntityItem* entity) const { - glm::vec3 clampedMin = glm::clamp(entity->getMinimumPoint(), 0.0f, 1.0f); - glm::vec3 clampedMax = glm::clamp(entity->getMaximumPoint(), 0.0f, 1.0f); + return bestFitBounds(entity->getMinimumPoint(), entity->getMaximumPoint()); +} + +bool EntityTreeElement::containsBounds(const EntityItemProperties& properties) const { + return containsBounds(properties.getMinimumPoint(), properties.getMaximumPoint()); +} + +bool EntityTreeElement::bestFitBounds(const EntityItemProperties& properties) const { + return bestFitBounds(properties.getMinimumPoint(), properties.getMaximumPoint()); +} + +bool EntityTreeElement::containsBounds(const glm::vec3& minPoint, const glm::vec3& maxPoint) const { + glm::vec3 clampedMin = glm::clamp(minPoint, 0.0f, 1.0f); + glm::vec3 clampedMax = glm::clamp(maxPoint, 0.0f, 1.0f); + return _cube.contains(clampedMin) && _cube.contains(clampedMax); +} + +bool EntityTreeElement::bestFitBounds(const glm::vec3& minPoint, const glm::vec3& maxPoint) const { + glm::vec3 clampedMin = glm::clamp(minPoint, 0.0f, 1.0f); + glm::vec3 clampedMax = glm::clamp(maxPoint, 0.0f, 1.0f); if (_cube.contains(clampedMin) && _cube.contains(clampedMax)) { int childForMinimumPoint = getMyChildContainingPoint(clampedMin); int childForMaximumPoint = getMyChildContainingPoint(clampedMax); @@ -200,6 +216,7 @@ bool EntityTreeElement::bestFitEntityBounds(const EntityItem* entity) const { return false; } + void EntityTreeElement::update(EntityTreeUpdateArgs& args) { args._totalElements++; // update our contained entities @@ -246,7 +263,7 @@ bool EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, con QList::const_iterator entityEnd = _entityItems->end(); bool somethingIntersected = false; while(entityItr != entityEnd) { - EntityItem& entity = (*entityItr); + EntityItem* entity = (*entityItr); AACube entityCube = entity.getAACube(); float localDistance; @@ -371,14 +388,14 @@ bool EntityTreeElement::updateEntity(const EntityItem& entity) { // (guaranteed that num entities per elemen is small?) uint16_t numberOfEntities = _entityItems->size(); for (uint16_t i = 0; i < numberOfEntities; i++) { - EntityItem& thisEntity = (*_entityItems)[i]; - if (thisEntity.getID() == entity.getID()) { + EntityItem* thisEntity = (*_entityItems)[i]; + if (thisEntity->getID() == entity.getID()) { if (wantDebug) { qDebug() << "found entity with id"; } - int difference = thisEntity.getLastUpdated() - entity.getLastUpdated(); - bool changedOnServer = thisEntity.getLastEdited() <= entity.getLastEdited(); - bool localOlder = thisEntity.getLastUpdated() < entity.getLastUpdated(); + int difference = thisEntity->getLastUpdated() - entity.getLastUpdated(); + bool changedOnServer = thisEntity->getLastEdited() <= entity.getLastEdited(); + bool localOlder = thisEntity->getLastUpdated() < entity.getLastUpdated(); if (changedOnServer || localOlder) { if (wantDebug) { qDebug("local entity [id:%d] %s and %s than server entity by %d, entity.isNewlyCreated()=%s", @@ -387,7 +404,7 @@ bool EntityTreeElement::updateEntity(const EntityItem& entity) { difference, debug::valueOf(entity.isNewlyCreated()) ); } - thisEntity.copyChangedProperties(entity); + thisEntity->copyChangedProperties(entity); markWithChangedTime(); // seems like we shouldn't need this @@ -421,30 +438,30 @@ void EntityTreeElement::updateEntityItemID(FindAndUpdateEntityItemIDArgs* args) bool wantDebug = false; uint16_t numberOfEntities = _entityItems->size(); for (uint16_t i = 0; i < numberOfEntities; i++) { - EntityItem& thisEntity = (*_entityItems)[i]; + EntityItem* thisEntity = (*_entityItems)[i]; if (!args->creatorTokenFound) { // first, we're looking for matching creatorTokenIDs, if we find that, then we fix it to know the actual ID - if (thisEntity.getCreatorTokenID() == args->creatorTokenID) { + if (thisEntity->getCreatorTokenID() == args->creatorTokenID) { if (wantDebug) { qDebug() << "EntityTreeElement::updateEntityItemID()... found the entity... updating it's ID... " << "creatorTokenID=" << args->creatorTokenID << "entityID=" << args->entityID; } - thisEntity.setID(args->entityID); + thisEntity->setID(args->entityID); args->creatorTokenFound = true; } } // if we're in an isViewing tree, we also need to look for an kill any viewed entities if (!args->viewedEntityFound && args->isViewing) { - if (thisEntity.getCreatorTokenID() == UNKNOWN_ENTITY_TOKEN && thisEntity.getID() == args->entityID) { + if (thisEntity->getCreatorTokenID() == UNKNOWN_ENTITY_TOKEN && thisEntity->getID() == args->entityID) { if (wantDebug) { qDebug() << "EntityTreeElement::updateEntityItemID()... VIEWED entity FOUND??? " << "args->creatorTokenID=" << args->creatorTokenID - << "thisEntity.getCreatorTokenID()=" << thisEntity.getCreatorTokenID() + << "thisEntity->getCreatorTokenID()=" << thisEntity->getCreatorTokenID() << "args->entityID=" << args->entityID; } @@ -464,7 +481,7 @@ const EntityItem* EntityTreeElement::getClosestEntity(glm::vec3 position) const float closestEntityDistance = FLT_MAX; uint16_t numberOfEntities = _entityItems->size(); for (uint16_t i = 0; i < numberOfEntities; i++) { - float distanceToEntity = glm::distance(position, (*_entityItems)[i].getPosition()); + float distanceToEntity = glm::distance(position, (*_entityItems)[i]->getPosition()); if (distanceToEntity < closestEntityDistance) { closestEntity = &(*_entityItems)[i]; } @@ -506,8 +523,8 @@ const EntityItem* EntityTreeElement::getEntityWithID(uint32_t id) const { const EntityItem* foundEntity = NULL; uint16_t numberOfEntities = _entityItems->size(); for (uint16_t i = 0; i < numberOfEntities; i++) { - if ((*_entityItems)[i].getID() == id) { - foundEntity = &(*_entityItems)[i]; + if ((*_entityItems)[i]->getID() == id) { + foundEntity = (*_entityItems)[i]; break; } } @@ -515,12 +532,23 @@ const EntityItem* EntityTreeElement::getEntityWithID(uint32_t id) const { } const EntityItem* EntityTreeElement::getEntityWithEntityItemID(const EntityItemID& id) const { - // NOTE: this lookup is O(N) but maybe we don't care? (guaranteed that num entities per elemen is small?) const EntityItem* foundEntity = NULL; uint16_t numberOfEntities = _entityItems->size(); for (uint16_t i = 0; i < numberOfEntities; i++) { - if ((*_entityItems)[i].getEntityItemID() == id) { - foundEntity = &(*_entityItems)[i]; + if ((*_entityItems)[i]->getEntityItemID() == id) { + foundEntity = (*_entityItems)[i]; + break; + } + } + return foundEntity; +} + +EntityItem* EntityTreeElement::getEntityWithEntityItemID(const EntityItemID& id) { + EntityItem* foundEntity = NULL; + uint16_t numberOfEntities = _entityItems->size(); + for (uint16_t i = 0; i < numberOfEntities; i++) { + if ((*_entityItems)[i]->getEntityItemID() == id) { + foundEntity = (*_entityItems)[i]; break; } } @@ -531,7 +559,7 @@ bool EntityTreeElement::removeEntityWithID(uint32_t id) { bool foundEntity = false; uint16_t numberOfEntities = _entityItems->size(); for (uint16_t i = 0; i < numberOfEntities; i++) { - if ((*_entityItems)[i].getID() == id) { + if ((*_entityItems)[i]->getID() == id) { foundEntity = true; _entityItems->removeAt(i); @@ -545,7 +573,7 @@ bool EntityTreeElement::removeEntityWithEntityItemID(const EntityItemID& id) { bool foundEntity = false; uint16_t numberOfEntities = _entityItems->size(); for (uint16_t i = 0; i < numberOfEntities; i++) { - if ((*_entityItems)[i].getEntityItemID() == id) { + if ((*_entityItems)[i]->getEntityItemID() == id) { foundEntity = true; _entityItems->removeAt(i); break; diff --git a/libraries/entities/src/EntityTreeElement.h b/libraries/entities/src/EntityTreeElement.h index e70116874a..bb9e0f1e36 100644 --- a/libraries/entities/src/EntityTreeElement.h +++ b/libraries/entities/src/EntityTreeElement.h @@ -139,12 +139,20 @@ public: const EntityItem* getEntityWithID(uint32_t id) const; const EntityItem* getEntityWithEntityItemID(const EntityItemID& id) const; + EntityItem* getEntityWithEntityItemID(const EntityItemID& id); + bool removeEntityWithID(uint32_t id); bool removeEntityWithEntityItemID(const EntityItemID& id); bool containsEntityBounds(const EntityItem* entity) const; bool bestFitEntityBounds(const EntityItem* entity) const; + bool containsBounds(const EntityItemProperties& properties) const; + bool bestFitBounds(const EntityItemProperties& properties) const; + + bool containsBounds(const glm::vec3& minPoint, const glm::vec3& maxPoint) const; + bool bestFitBounds(const glm::vec3& minPoint, const glm::vec3& maxPoint) const; + protected: virtual void init(unsigned char * octalCode); EntityTree* _myTree;