From 7d96ad5836bd6dcdc3feff80c22426ce5912c636 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 5 Dec 2014 18:08:53 -0800 Subject: [PATCH 1/3] cleanup of EntitySimulation API * remove EntityItem::_simulationState (only useful to EntitySimulation) * move code from SimpleEntitySimuation to EntitySimulation * EntitySimulation now maintans many QSets of entities * cleanup and clarify use of EnityTree::updateEntity() * reduced cost of polling for entity expiries * changed "UpdateFlags" to "DirtyFlags" and clarified what they're for --- .../entities/RenderableModelEntityItem.cpp | 6 +- .../src/entities/RenderableModelEntityItem.h | 4 +- .../entities/src/EntityCollisionSystem.cpp | 10 +- libraries/entities/src/EntityItem.cpp | 74 +++--- libraries/entities/src/EntityItem.h | 60 +++-- libraries/entities/src/EntitySimulation.cpp | 168 +++++++++++- libraries/entities/src/EntitySimulation.h | 39 ++- libraries/entities/src/EntityTree.cpp | 48 ++-- libraries/entities/src/EntityTree.h | 8 + libraries/entities/src/EntityTreeElement.cpp | 2 +- libraries/entities/src/ModelEntityItem.cpp | 13 +- libraries/entities/src/ModelEntityItem.h | 2 +- .../entities/src/MovingEntitiesOperator.cpp | 9 +- .../entities/src/MovingEntitiesOperator.h | 3 +- .../entities/src/SimpleEntitySimulation.cpp | 239 ++++-------------- .../entities/src/SimpleEntitySimulation.h | 28 +- 16 files changed, 384 insertions(+), 329 deletions(-) diff --git a/interface/src/entities/RenderableModelEntityItem.cpp b/interface/src/entities/RenderableModelEntityItem.cpp index 10b18ad9c5..6c8d85507d 100644 --- a/interface/src/entities/RenderableModelEntityItem.cpp +++ b/interface/src/entities/RenderableModelEntityItem.cpp @@ -150,7 +150,7 @@ void RenderableModelEntityItem::render(RenderArgs* args) { } glm::quat rotation = getRotation(); - if (needsSimulation() && _model->isActive()) { + if (needsToCallUpdate() && _model->isActive()) { _model->setScaleToFit(true, dimensions); _model->setSnapModelToRegistrationPoint(true, getRegistrationPoint()); _model->setRotation(rotation); @@ -245,8 +245,8 @@ Model* RenderableModelEntityItem::getModel(EntityTreeRenderer* renderer) { return result; } -bool RenderableModelEntityItem::needsSimulation() const { - return _needsInitialSimulation || getSimulationState() == EntityItem::Moving; +bool RenderableModelEntityItem::needsToCallUpdate() const { + return _needsInitialSimulation || ModelEntityItem::needsToCallUpdate(); } EntityItemProperties RenderableModelEntityItem::getProperties() const { diff --git a/interface/src/entities/RenderableModelEntityItem.h b/interface/src/entities/RenderableModelEntityItem.h index 48c9a26051..0aa2578a6c 100644 --- a/interface/src/entities/RenderableModelEntityItem.h +++ b/interface/src/entities/RenderableModelEntityItem.h @@ -52,9 +52,11 @@ public: virtual void render(RenderArgs* args); Model* getModel(EntityTreeRenderer* renderer); + + bool needsToCallUpdate() const; + private: void remapTextures(); - bool needsSimulation() const; Model* _model; bool _needsInitialSimulation; diff --git a/libraries/entities/src/EntityCollisionSystem.cpp b/libraries/entities/src/EntityCollisionSystem.cpp index 2ac8ea596d..b74b9fac80 100644 --- a/libraries/entities/src/EntityCollisionSystem.cpp +++ b/libraries/entities/src/EntityCollisionSystem.cpp @@ -208,7 +208,9 @@ void EntityCollisionSystem::updateCollisionWithEntities(EntityItem* entityA) { propertiesA.setPosition(newPositionA * (float)TREE_SCALE); propertiesA.setLastEdited(now); - _entityTree->updateEntity(idA, propertiesA); + // NOTE: EntityTree::updateEntity() will cause the entity to get sorted correctly in the EntitySimulation, + // thereby waking up static non-moving entities. + _entityTree->updateEntity(entityA, propertiesA); _packetSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, idA, propertiesA); } @@ -225,7 +227,9 @@ void EntityCollisionSystem::updateCollisionWithEntities(EntityItem* entityA) { propertiesB.setPosition(newPositionB * (float)TREE_SCALE); propertiesB.setLastEdited(now); - _entityTree->updateEntity(idB, propertiesB); + // NOTE: EntityTree::updateEntity() will cause the entity to get sorted correctly in the EntitySimulation, + // thereby waking up static non-moving entities. + _entityTree->updateEntity(entityB, propertiesB); _packetSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, idB, propertiesB); } } @@ -331,6 +335,6 @@ void EntityCollisionSystem::applyHardCollision(EntityItem* entity, const Collisi properties.setVelocity(velocity * (float)TREE_SCALE); properties.setLastEdited(usecTimestampNow()); - _entityTree->updateEntity(entityItemID, properties); + _entityTree->updateEntity(entity, properties); _packetSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, entityItemID, properties); } diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 98dda6f33f..d0d07d1227 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -58,6 +58,7 @@ void EntityItem::initFromEntityItemID(const EntityItemID& entityItemID) { _lastEditedFromRemote = 0; _lastEditedFromRemoteInRemoteTime = 0; + _lastSimulated = 0; _lastUpdated = 0; _created = 0; // TODO: when do we actually want to make this "now" _changedOnServer = 0; @@ -88,12 +89,12 @@ EntityItem::EntityItem(const EntityItemID& entityItemID) { _lastEdited = 0; _lastEditedFromRemote = 0; _lastEditedFromRemoteInRemoteTime = 0; + _lastSimulated = 0; _lastUpdated = 0; _created = 0; - _updateFlags = 0; + _dirtyFlags = 0; _changedOnServer = 0; initFromEntityItemID(entityItemID); - _simulationState = EntityItem::Static; } EntityItem::EntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) { @@ -101,13 +102,13 @@ EntityItem::EntityItem(const EntityItemID& entityItemID, const EntityItemPropert _lastEdited = 0; _lastEditedFromRemote = 0; _lastEditedFromRemoteInRemoteTime = 0; + _lastSimulated = 0; _lastUpdated = 0; _created = properties.getCreated(); - _updateFlags = 0; + _dirtyFlags = 0; _changedOnServer = 0; initFromEntityItemID(entityItemID); setProperties(properties, true); // force copy - _simulationState = EntityItem::Static; } EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& params) const { @@ -154,7 +155,7 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet ByteCountCoded typeCoder = getType(); QByteArray encodedType = typeCoder; - quint64 updateDelta = getLastUpdated() <= getLastEdited() ? 0 : getLastUpdated() - getLastEdited(); + quint64 updateDelta = getLastMoved() <= getLastEdited() ? 0 : getLastMoved() - getLastEdited(); ByteCountCoded updateDeltaCoder = updateDelta; QByteArray encodedUpdateDelta = updateDeltaCoder; EntityPropertyFlags propertyFlags(PROP_LAST_ITEM); @@ -450,9 +451,9 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef ByteCountCoded updateDeltaCoder = encodedUpdateDelta; quint64 updateDelta = updateDeltaCoder; if (overwriteLocalData) { - _lastUpdated = lastEditedFromBufferAdjusted + updateDelta; // don't adjust for clock skew since we already did that for _lastEdited + _lastSimulated = _lastUpdated = lastEditedFromBufferAdjusted + updateDelta; // don't adjust for clock skew since we already did that for _lastEdited if (wantDebug) { - qDebug() << "_lastUpdated=" << _lastUpdated; + qDebug() << "_lastUpdated =" << _lastUpdated; qDebug() << "_lastEdited=" << _lastEdited; qDebug() << "lastEditedFromBufferAdjusted=" << lastEditedFromBufferAdjusted; } @@ -565,20 +566,20 @@ bool EntityItem::isRestingOnSurface() const { && _gravity.y < 0.0f; } -void EntityItem::update(const quint64& updateTime) { +void EntityItem::simulate(const quint64& now) { bool wantDebug = false; - if (_lastUpdated == 0) { - _lastUpdated = updateTime; + if (_lastSimulated == 0) { + _lastSimulated = now; } - float timeElapsed = (float)(updateTime - _lastUpdated) / (float)(USECS_PER_SECOND); + float timeElapsed = (float)(now - _lastSimulated) / (float)(USECS_PER_SECOND); if (wantDebug) { qDebug() << "********** EntityItem::update()"; qDebug() << " entity ID=" << getEntityItemID(); - qDebug() << " updateTime=" << updateTime; - qDebug() << " _lastUpdated=" << _lastUpdated; + qDebug() << " now=" << now; + qDebug() << " _lastSimulated=" << _lastSimulated; qDebug() << " timeElapsed=" << timeElapsed; qDebug() << " hasVelocity=" << hasVelocity(); qDebug() << " hasGravity=" << hasGravity(); @@ -611,10 +612,10 @@ void EntityItem::update(const quint64& updateTime) { } } - _lastUpdated = updateTime; + _lastSimulated = now; if (wantDebug) { - qDebug() << " ********** EntityItem::update() .... SETTING _lastUpdated=" << _lastUpdated; + qDebug() << " ********** EntityItem::update() .... SETTING _lastSimulated=" << _lastSimulated; } if (hasAngularVelocity()) { @@ -707,6 +708,7 @@ void EntityItem::update(const quint64& updateTime) { velocity = NO_VELOCITY; } + // NOTE: the simulation should NOT set any DirtyFlags on this entity setPosition(position); // this will automatically recalculate our collision shape setVelocity(velocity); @@ -719,20 +721,18 @@ void EntityItem::update(const quint64& updateTime) { } } -EntityItem::SimulationState EntityItem::computeSimulationState() const { - if (hasVelocity() || (hasGravity() && !isRestingOnSurface()) || hasAngularVelocity()) { - return EntityItem::Moving; - } - if (isMortal()) { - return EntityItem::Mortal; - } - return EntityItem::Static; +bool EntityItem::isMoving() const { + return hasVelocity() || (hasGravity() && !isRestingOnSurface()) || hasAngularVelocity(); } bool EntityItem::lifetimeHasExpired() const { return isMortal() && (getAge() > getLifetime()); } +quint64 EntityItem::getExpiry() const { + return _created + (quint64)(_lifetime * (float)USECS_PER_SECOND); +} + EntityItemProperties EntityItem::getProperties() const { EntityItemProperties properties; properties._id = getID(); @@ -948,7 +948,7 @@ void EntityItem::updatePosition(const glm::vec3& value) { if (_position != value) { _position = value; recalculateCollisionShape(); - _updateFlags |= EntityItem::UPDATE_POSITION; + _dirtyFlags |= EntityItem::DIRTY_POSITION; } } @@ -957,7 +957,7 @@ void EntityItem::updatePositionInMeters(const glm::vec3& value) { if (_position != position) { _position = position; recalculateCollisionShape(); - _updateFlags |= EntityItem::UPDATE_POSITION; + _dirtyFlags |= EntityItem::DIRTY_POSITION; } } @@ -965,7 +965,7 @@ void EntityItem::updateDimensions(const glm::vec3& value) { if (_dimensions != value) { _dimensions = value; recalculateCollisionShape(); - _updateFlags |= EntityItem::UPDATE_SHAPE; + _dirtyFlags |= EntityItem::DIRTY_SHAPE; } } @@ -974,7 +974,7 @@ void EntityItem::updateDimensionsInMeters(const glm::vec3& value) { if (_dimensions != dimensions) { _dimensions = dimensions; recalculateCollisionShape(); - _updateFlags |= EntityItem::UPDATE_SHAPE; + _dirtyFlags |= EntityItem::DIRTY_SHAPE; } } @@ -982,21 +982,21 @@ void EntityItem::updateRotation(const glm::quat& rotation) { if (_rotation != rotation) { _rotation = rotation; recalculateCollisionShape(); - _updateFlags |= EntityItem::UPDATE_POSITION; + _dirtyFlags |= EntityItem::DIRTY_POSITION; } } void EntityItem::updateMass(float value) { if (_mass != value) { _mass = value; - _updateFlags |= EntityItem::UPDATE_MASS; + _dirtyFlags |= EntityItem::DIRTY_MASS; } } void EntityItem::updateVelocity(const glm::vec3& value) { if (_velocity != value) { _velocity = value; - _updateFlags |= EntityItem::UPDATE_VELOCITY; + _dirtyFlags |= EntityItem::DIRTY_VELOCITY; } } @@ -1004,14 +1004,14 @@ void EntityItem::updateVelocityInMeters(const glm::vec3& value) { glm::vec3 velocity = value / (float) TREE_SCALE; if (_velocity != velocity) { _velocity = velocity; - _updateFlags |= EntityItem::UPDATE_VELOCITY; + _dirtyFlags |= EntityItem::DIRTY_VELOCITY; } } void EntityItem::updateGravity(const glm::vec3& value) { if (_gravity != value) { _gravity = value; - _updateFlags |= EntityItem::UPDATE_VELOCITY; + _dirtyFlags |= EntityItem::DIRTY_VELOCITY; } } @@ -1019,35 +1019,35 @@ void EntityItem::updateGravityInMeters(const glm::vec3& value) { glm::vec3 gravity = value / (float) TREE_SCALE; if (_gravity != gravity) { _gravity = gravity; - _updateFlags |= EntityItem::UPDATE_VELOCITY; + _dirtyFlags |= EntityItem::DIRTY_VELOCITY; } } void EntityItem::updateAngularVelocity(const glm::vec3& value) { if (_angularVelocity != value) { _angularVelocity = value; - _updateFlags |= EntityItem::UPDATE_VELOCITY; + _dirtyFlags |= EntityItem::DIRTY_VELOCITY; } } void EntityItem::updateIgnoreForCollisions(bool value) { if (_ignoreForCollisions != value) { _ignoreForCollisions = value; - _updateFlags |= EntityItem::UPDATE_COLLISION_GROUP; + _dirtyFlags |= EntityItem::DIRTY_COLLISION_GROUP; } } void EntityItem::updateCollisionsWillMove(bool value) { if (_collisionsWillMove != value) { _collisionsWillMove = value; - _updateFlags |= EntityItem::UPDATE_MOTION_TYPE; + _dirtyFlags |= EntityItem::DIRTY_MOTION_TYPE; } } void EntityItem::updateLifetime(float value) { if (_lifetime != value) { _lifetime = value; - _updateFlags |= EntityItem::UPDATE_LIFETIME; + _dirtyFlags |= EntityItem::DIRTY_LIFETIME; } } diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 7dbcaed8fc..f0d3330087 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -41,15 +41,14 @@ class EntityTreeElementExtraEncodeData; class EntityItem { public: - enum EntityUpdateFlags { - UPDATE_POSITION = 0x0001, - UPDATE_VELOCITY = 0x0002, - UPDATE_MASS = 0x0004, - UPDATE_COLLISION_GROUP = 0x0008, - UPDATE_MOTION_TYPE = 0x0010, - UPDATE_SHAPE = 0x0020, - UPDATE_LIFETIME = 0x0040 - //UPDATE_APPEARANCE = 0x8000, + enum EntityDirtyFlags { + DIRTY_POSITION = 0x0001, + DIRTY_VELOCITY = 0x0002, + DIRTY_MASS = 0x0004, + DIRTY_COLLISION_GROUP = 0x0008, + DIRTY_MOTION_TYPE = 0x0010, + DIRTY_SHAPE = 0x0020, + DIRTY_LIFETIME = 0x0040 }; DONT_ALLOW_INSTANTIATION // This class can not be instantiated directly @@ -77,12 +76,12 @@ public: /// has changed. This will be called with properties change or when new data is loaded from a stream virtual void somethingChangedNotification() { } - quint64 getLastUpdated() const { return _lastUpdated; } /// Last simulated time of this entity universal usecs + quint64 getLastSimulated() const { return _lastSimulated; } /// Last simulated time of this entity universal usecs /// Last edited time of this entity universal usecs quint64 getLastEdited() const { return _lastEdited; } void setLastEdited(quint64 lastEdited) - { _lastEdited = _lastUpdated = lastEdited; _changedOnServer = glm::max(lastEdited, _changedOnServer); } + { _lastEdited = _lastSimulated = _lastUpdated = lastEdited; _changedOnServer = glm::max(lastEdited, _changedOnServer); } float getEditedAgo() const /// Elapsed seconds since this entity was last edited { return (float)(usecTimestampNow() - getLastEdited()) / (float)USECS_PER_SECOND; } @@ -121,17 +120,14 @@ public: unsigned char* bufferOut, int sizeIn, int& sizeOut); static void adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, size_t length, int clockSkew); - virtual void update(const quint64& now); + + // perform update + virtual void update(const quint64& now) { _lastUpdated = now; } - typedef enum SimulationState_t { - Static, - Mortal, - Moving - } SimulationState; + // perform linear extrapolation for SimpleEntitySimulation + void simulate(const quint64& now); - // computes the SimulationState that the entity SHOULD be in. - // Use getSimulationState() to find the state under which it is currently categorized. - virtual SimulationState computeSimulationState() const; + bool needsToCallUpdate() const { return false; } virtual void debugDump() const; @@ -221,11 +217,14 @@ public: /// age of this entity in seconds float getAge() const { return (float)(usecTimestampNow() - _created) / (float)USECS_PER_SECOND; } bool lifetimeHasExpired() const; + quint64 getExpiry() const; // position, size, and bounds related helpers float getSize() const; /// get maximum dimension in domain scale units (0.0 - 1.0) AACube getMaximumAACube() const; AACube getMinimumAACube() const; + AACube getOldMaximumAACube() const { return _oldMaximumAACube; } + void setOldMaximumAACube(const AACube& cube) { _oldMaximumAACube = cube; } AABox getAABox() const; /// axis aligned bounding box in domain scale units (0.0 - 1.0) static const QString DEFAULT_SCRIPT; @@ -278,7 +277,7 @@ public: virtual const Shape& getCollisionShapeInMeters() const { return _collisionShape; } virtual bool contains(const glm::vec3& point) const { return getAABox().contains(point); } - // updateFoo() methods to be used when changes need to be accumulated in the _updateFlags + // updateFoo() methods to be used when changes need to be accumulated in the _dirtyFlags void updatePosition(const glm::vec3& value); void updatePositionInMeters(const glm::vec3& value); void updateDimensions(const glm::vec3& value); @@ -294,12 +293,11 @@ public: void updateCollisionsWillMove(bool value); void updateLifetime(float value); - uint32_t getUpdateFlags() const { return _updateFlags; } - void clearUpdateFlags() { _updateFlags = 0; } - - SimulationState getSimulationState() const { return _simulationState; } + uint32_t getDirtyFlags() const { return _dirtyFlags; } + void clearDirtyFlags(uint32_t mask = 0xffff) { _dirtyFlags &= ~mask; } - void setSimulationState(SimulationState state) { _simulationState = state; } + bool isMoving() const; + protected: virtual void initFromEntityItemID(const EntityItemID& entityItemID); // maybe useful to allow subclasses to init @@ -309,7 +307,8 @@ protected: QUuid _id; uint32_t _creatorTokenID; bool _newlyCreated; - quint64 _lastUpdated; + quint64 _lastSimulated; // last time this entity called simulate() + quint64 _lastUpdated; // last time this entity called update() quint64 _lastEdited; // this is the last official local or remote edit time quint64 _lastEditedFromRemote; // this is the last time we received and edit from the server quint64 _lastEditedFromRemoteInRemoteTime; // time in server time space the last time we received and edit from the server @@ -343,11 +342,10 @@ protected: void setRadius(float value); AACubeShape _collisionShape; - SimulationState _simulationState; // only set by EntityTree + AACube _oldMaximumAACube; // remember this so we know where the entity used to live in the tree - // UpdateFlags are set whenever a property changes that requires the change to be communicated to other - // data structures. It is the responsibility of the EntityTree to relay changes entity and clear flags. - uint32_t _updateFlags; + // DirtyFlags are set whenever a property changes that the EntitySimulation needs to know about. + uint32_t _dirtyFlags; // things that have changed from EXTERNAL changes (via script or packet) but NOT from simulation }; diff --git a/libraries/entities/src/EntitySimulation.cpp b/libraries/entities/src/EntitySimulation.cpp index 8058c2f24e..223df588d5 100644 --- a/libraries/entities/src/EntitySimulation.cpp +++ b/libraries/entities/src/EntitySimulation.cpp @@ -9,12 +9,178 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include + #include "EntitySimulation.h" +#include "MovingEntitiesOperator.h" void EntitySimulation::setEntityTree(EntityTree* tree) { if (_entityTree && _entityTree != tree) { - clearEntities(); + _mortalEntities.clear(); + _nextExpiry = quint64(-1); + _updateableEntities.clear(); + _entitiesToBeSorted.clear(); } _entityTree = tree; } +void EntitySimulation::updateEntities(QSet& entitiesToDelete) { + quint64 now = usecTimestampNow(); + + // these methods may accumulate entries in _entitiesToBeDeleted + expireMortalEntities(now); + callUpdateOnEntitiesThatNeedIt(now); + updateEntitiesInternal(now); + sortEntitiesThatMoved(); + + // at this point we harvest _entitiesToBeDeleted + entitiesToDelete.unite(_entitiesToDelete); + _entitiesToDelete.clear(); +} + +void EntitySimulation::expireMortalEntities(const quint64& now) { + if (now > _nextExpiry) { + // only search for expired entities if we expect to find one + _nextExpiry = quint64(-1); + QSet::iterator itemItr = _mortalEntities.begin(); + while (itemItr != _mortalEntities.end()) { + EntityItem* entity = *itemItr; + quint64 expiry = entity->getExpiry(); + if (expiry < now) { + _entitiesToDelete.insert(entity); + itemItr = _mortalEntities.erase(itemItr); + _updateableEntities.remove(entity); + _entitiesToBeSorted.remove(entity); + removeEntityInternal(entity); + } else { + if (expiry < _nextExpiry) { + // remeber the smallest _nextExpiry so we know when to start the next search + _nextExpiry = expiry; + } + ++itemItr; + } + } + } +} + +void EntitySimulation::callUpdateOnEntitiesThatNeedIt(const quint64& now) { + PerformanceTimer perfTimer("updatingEntities"); + QSet::iterator itemItr = _updateableEntities.begin(); + while (itemItr != _updateableEntities.end()) { + EntityItem* entity = *itemItr; + // TODO: catch transition from needing update to not as a "change" + // so we don't have to scan for it here. + if (!entity->needsToCallUpdate()) { + itemItr = _updateableEntities.erase(itemItr); + } else { + entity->update(now); + ++itemItr; + } + } +} + +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. + PerformanceTimer perfTimer("sortingEntities"); + MovingEntitiesOperator moveOperator(_entityTree); + AACube domainBounds(glm::vec3(0.0f,0.0f,0.0f), 1.0f); + QSet::iterator itemItr = _entitiesToBeSorted.begin(); + while (itemItr != _entitiesToBeSorted.end()) { + EntityItem* entity = *itemItr; + // check to see if this movement has sent the entity outside of the domain. + AACube newCube = entity->getMaximumAACube(); + if (!domainBounds.touches(newCube)) { + qDebug() << "Entity " << entity->getEntityItemID() << " moved out of domain bounds."; + _entitiesToDelete.insert(entity); + _mortalEntities.remove(entity); + _updateableEntities.remove(entity); + removeEntityInternal(entity); + } else { + moveOperator.addEntityToMoveList(entity, newCube); + } + ++itemItr; + } + _entitiesToBeSorted.clear(); + + if (moveOperator.hasMovingEntities()) { + PerformanceTimer perfTimer("recurseTreeWithOperator"); + _entityTree->recurseTreeWithOperator(&moveOperator); + moveOperator.finish(); + } +} + +void EntitySimulation::addEntity(EntityItem* entity) { + assert(entity); + if (entity->isMortal()) { + _mortalEntities.insert(entity); + quint64 expiry = entity->getExpiry(); + if (expiry < _nextExpiry) { + _nextExpiry = expiry; + } + } + if (entity->needsToCallUpdate()) { + _updateableEntities.insert(entity); + } + addEntityInternal(entity); +} + +void EntitySimulation::removeEntity(EntityItem* entity) { + assert(entity); + _updateableEntities.remove(entity); + _mortalEntities.remove(entity); + _entitiesToBeSorted.remove(entity); + _entitiesToDelete.remove(entity); + removeEntityInternal(entity); +} + +void EntitySimulation::entityChanged(EntityItem* entity) { + assert(entity); + + // Although it is not the responsibility of the EntitySimulation to sort the tree for EXTERNAL changes + // it IS responsibile for triggering deletes for entities that leave the bounds of the domain, hence + // we must check for that case here, however we rely on the change event to have set DIRTY_POSITION flag. + bool wasRemoved = false; + uint32_t dirtyFlags = entity->getDirtyFlags(); + if (dirtyFlags & EntityItem::DIRTY_POSITION) { + AACube domainBounds(glm::vec3(0.0f,0.0f,0.0f), 1.0f); + AACube newCube = entity->getMaximumAACube(); + if (!domainBounds.touches(newCube)) { + qDebug() << "Entity " << entity->getEntityItemID() << " moved out of domain bounds."; + _entitiesToDelete.insert(entity); + _mortalEntities.remove(entity); + _updateableEntities.remove(entity); + removeEntityInternal(entity); + wasRemoved = true; + } + } + if (!wasRemoved) { + if (dirtyFlags & EntityItem::DIRTY_LIFETIME) { + if (entity->isMortal()) { + _mortalEntities.insert(entity); + quint64 expiry = entity->getExpiry(); + if (expiry < _nextExpiry) { + _nextExpiry = expiry; + } + } else { + _mortalEntities.remove(entity); + } + entity->clearDirtyFlags(EntityItem::DIRTY_LIFETIME); + } + if (entity->needsToCallUpdate()) { + _updateableEntities.insert(entity); + } else { + _updateableEntities.remove(entity); + } + entityChangedInternal(entity); + } + entity->clearDirtyFlags(); +} + +void EntitySimulation::clearEntities() { + _mortalEntities.clear(); + _nextExpiry = quint64(-1); + _updateableEntities.clear(); + _entitiesToBeSorted.clear(); + clearEntitiesInternal(); +} diff --git a/libraries/entities/src/EntitySimulation.h b/libraries/entities/src/EntitySimulation.h index 770d6ebdb0..506e2ed9d0 100644 --- a/libraries/entities/src/EntitySimulation.h +++ b/libraries/entities/src/EntitySimulation.h @@ -14,36 +14,61 @@ #include +#include + #include "EntityTree.h" class EntitySimulation { public: EntitySimulation() : _entityTree(NULL) { } - virtual ~EntitySimulation() {} + virtual ~EntitySimulation() { setEntityTree(NULL); } /// \param tree pointer to EntityTree which is stored internally - virtual void setEntityTree(EntityTree* tree); + void setEntityTree(EntityTree* tree); /// \param[out] entitiesToDelete list of entities removed from simulation and should be deleted. - virtual void update(QSet& entitiesToDelete) = 0; + void updateEntities(QSet& entitiesToDelete); /// \param entity pointer to EntityItem to add to the simulation /// \sideeffect the EntityItem::_simulationState member may be updated to indicate membership to internal list - virtual void addEntity(EntityItem* entity) = 0; + void addEntity(EntityItem* entity); /// \param entity pointer to EntityItem to removed from the simulation /// \sideeffect the EntityItem::_simulationState member may be updated to indicate non-membership to internal list - virtual void removeEntity(EntityItem* entity) = 0; + void removeEntity(EntityItem* entity); /// \param entity pointer to EntityItem to that may have changed in a way that would affect its simulation - virtual void entityChanged(EntityItem* entity) = 0; + /// call this whenever an entity was changed from some EXTERNAL event (NOT by the EntitySimulation itself) + void entityChanged(EntityItem* entity); - virtual void clearEntities() = 0; + void clearEntities(); EntityTree* getEntityTree() { return _entityTree; } protected: + + // These pure virtual methods are protected because they are not to be called will-nilly. The base class + // calls them in the right places. + virtual void updateEntitiesInternal(const quint64& now) = 0; + virtual void addEntityInternal(EntityItem* entity) = 0; + virtual void removeEntityInternal(EntityItem* entity) = 0; + virtual void entityChangedInternal(EntityItem* entity) = 0; + virtual void clearEntitiesInternal() = 0; + + void expireMortalEntities(const quint64& now); + void callUpdateOnEntitiesThatNeedIt(const quint64& now); + void sortEntitiesThatMoved(); + + // back pointer to EntityTree structure EntityTree* _entityTree; + + // We maintain multiple lists, each for its distinct purpose. + // An entity may be in more than one list. + QSet _mortalEntities; // entities that have an expiry + quint64 _nextExpiry; + QSet _updateableEntities; // entities that need update() called + QSet _entitiesToBeSorted; // entities that were moved by THIS simulation and might need to be resorted in the tree + QSet _entitiesToDelete; }; #endif // hifi_EntitySimulation_h diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index d3d9e2da53..8c18f965aa 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -79,6 +79,7 @@ EntityItem* EntityTree::getOrCreateEntityItem(const EntityItemID& entityID, cons /// Adds a new entity item to the tree void EntityTree::postAddEntity(EntityItem* entity) { assert(entity); + entity->setOldMaximumAACube(entity->getMaximumAACube()); // check to see if we need to simulate this entity.. if (_simulation) { _simulation->addEntity(entity); @@ -99,51 +100,64 @@ bool EntityTree::updateEntity(const EntityItemID& entityID, const EntityItemProp qDebug() << "UNEXPECTED!!!! don't call updateEntity() on entity items that don't exist. entityID=" << entityID; return false; } - + + return updateEntityWithElement(existingEntity, properties, containingElement); +} + +bool EntityTree::updateEntity(EntityItem* entity, const EntityItemProperties& properties) { + EntityTreeElement* containingElement = getContainingElement(entity->getEntityItemID()); + if (!containingElement) { + qDebug() << "UNEXPECTED!!!! EntityTree::updateEntity() entity-->element lookup failed!!! entityID=" + << entity->getEntityItemID(); + return false; + } + return updateEntityWithElement(entity, properties, containingElement); +} + +bool EntityTree::updateEntityWithElement(EntityItem* entity, const EntityItemProperties& properties, + EntityTreeElement* containingElement) { // 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 (existingEntity->getLocked()) { + if (entity->getLocked()) { if (properties.lockedChanged()) { bool wantsLocked = properties.getLocked(); if (!wantsLocked) { EntityItemProperties tempProperties; tempProperties.setLocked(wantsLocked); - UpdateEntityOperator theOperator(this, containingElement, existingEntity, tempProperties); + UpdateEntityOperator theOperator(this, containingElement, entity, tempProperties); recurseTreeWithOperator(&theOperator); _isDirty = true; - if (_simulation && existingEntity->getUpdateFlags() != 0) { - _simulation->entityChanged(existingEntity); - } } } } else { - // check to see if we need to simulate this entity... - QString entityScriptBefore = existingEntity->getScript(); + QString entityScriptBefore = entity->getScript(); - UpdateEntityOperator theOperator(this, containingElement, existingEntity, properties); + UpdateEntityOperator theOperator(this, containingElement, entity, properties); recurseTreeWithOperator(&theOperator); + entity->setOldMaximumAACube(entity->getMaximumAACube()); _isDirty = true; - if (_simulation && existingEntity->getUpdateFlags() != 0) { - _simulation->entityChanged(existingEntity); + if (_simulation && entity->getDirtyFlags() != 0) { + _simulation->entityChanged(entity); } - QString entityScriptAfter = existingEntity->getScript(); + QString entityScriptAfter = entity->getScript(); if (entityScriptBefore != entityScriptAfter) { - emitEntityScriptChanging(entityID); // the entity script has changed + emit entityScriptChanging(entity->getEntityItemID()); // the entity script has changed } } - containingElement = getContainingElement(entityID); + // TODO: this final containingElement check should eventually be removed (or wrapped in an #ifdef DEBUG). + containingElement = getContainingElement(entity->getEntityItemID()); if (!containingElement) { - qDebug() << "UNEXPECTED!!!! after updateEntity() we no longer have a containing element??? entityID=" << entityID; + qDebug() << "UNEXPECTED!!!! after updateEntity() we no longer have a containing element??? entityID=" + << entity->getEntityItemID(); return false; } return true; } - EntityItem* EntityTree::addEntity(const EntityItemID& entityID, const EntityItemProperties& properties) { EntityItem* result = NULL; @@ -572,7 +586,7 @@ void EntityTree::update() { if (_simulation) { lockForWrite(); QSet entitiesToDelete; - _simulation->update(entitiesToDelete); + _simulation->updateEntities(entitiesToDelete); if (entitiesToDelete.size() > 0) { // translate into list of ID's QSet idsToDelete; diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index eeb0182042..9a0fb43ecb 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -82,7 +82,13 @@ public: void postAddEntity(EntityItem* entityItem); EntityItem* addEntity(const EntityItemID& entityID, const EntityItemProperties& properties); + + // use this method if you only know the entityID bool updateEntity(const EntityItemID& entityID, const EntityItemProperties& properties); + + // use this method if you have a pointer to the entity (avoid an extra entity lookup) + bool updateEntity(EntityItem* entity, const EntityItemProperties& properties); + void deleteEntity(const EntityItemID& entityID); void deleteEntities(QSet entityIDs); void removeEntityFromSimulation(EntityItem* entity); @@ -156,6 +162,8 @@ signals: private: + bool updateEntityWithElement(EntityItem* entity, const EntityItemProperties& properties, + EntityTreeElement* containingElement); static bool findNearPointOperation(OctreeElement* element, void* extraData); static bool findInSphereOperation(OctreeElement* element, void* extraData); static bool findInCubeOperation(OctreeElement* element, void* extraData); diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index e18f79276e..d3e303009c 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -757,7 +757,7 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int EntityTreeElement* currentContainingElement = _myTree->getContainingElement(entityItemID); bytesForThisEntity = entityItem->readEntityDataFromBuffer(dataAt, bytesLeftToRead, args); - if (entityItem->getUpdateFlags()) { + if (entityItem->getDirtyFlags()) { _myTree->entityChanged(entityItem); } bool bestFitAfter = bestFitEntityBounds(entityItem); diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index 0de2035dec..565974d19e 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -373,17 +373,11 @@ bool ModelEntityItem::isAnimatingSomething() const { !getAnimationURL().isEmpty(); } -EntityItem::SimulationState ModelEntityItem::computeSimulationState() const { - // if we're animating then we need to have update() periodically called on this entity - // which means we need to categorized as Moving - return isAnimatingSomething() ? EntityItem::Moving : EntityItem::computeSimulationState(); +bool ModelEntityItem::needsToCallUpdate() const { + return isAnimatingSomething() ? true : EntityItem::needsToCallUpdate(); } -void ModelEntityItem::update(const quint64& updateTime) { - EntityItem::update(updateTime); // let our base class handle it's updates... - - quint64 now = updateTime; - +void ModelEntityItem::update(const quint64& now) { // only advance the frame index if we're playing if (getAnimationIsPlaying()) { float deltaTime = (float)(now - _lastAnimated) / (float)USECS_PER_SECOND; @@ -392,6 +386,7 @@ void ModelEntityItem::update(const quint64& updateTime) { } else { _lastAnimated = now; } + EntityItem::update(now); // let our base class handle it's updates... } void ModelEntityItem::debugDump() const { diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index 0b2508ec80..502b21af12 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -46,7 +46,7 @@ public: EntityPropertyFlags& propertyFlags, bool overwriteLocalData); virtual void update(const quint64& now); - virtual SimulationState computeSimulationState() const; + virtual bool needsToCallUpdate() const; virtual void debugDump() const; diff --git a/libraries/entities/src/MovingEntitiesOperator.cpp b/libraries/entities/src/MovingEntitiesOperator.cpp index 045e07a910..86b8de7b10 100644 --- a/libraries/entities/src/MovingEntitiesOperator.cpp +++ b/libraries/entities/src/MovingEntitiesOperator.cpp @@ -49,9 +49,10 @@ MovingEntitiesOperator::~MovingEntitiesOperator() { } -void MovingEntitiesOperator::addEntityToMoveList(EntityItem* entity, const AACube& oldCube, const AACube& newCube) { +void MovingEntitiesOperator::addEntityToMoveList(EntityItem* entity, const AACube& newCube) { EntityTreeElement* oldContainingElement = _tree->getContainingElement(entity->getEntityItemID()); AABox newCubeClamped = newCube.clamp(0.0f, 1.0f); + AACube oldCube = entity->getOldMaximumAACube(); AABox oldCubeClamped = oldCube.clamp(0.0f, 1.0f); if (_wantDebug) { @@ -290,3 +291,9 @@ OctreeElement* MovingEntitiesOperator::possiblyCreateChildAt(OctreeElement* elem } return NULL; } + +void MovingEntitiesOperator::finish() { + foreach(const EntityToMoveDetails& details, _entitiesToMove) { + details.entity->setOldMaximumAACube(details.newCube); + } +} diff --git a/libraries/entities/src/MovingEntitiesOperator.h b/libraries/entities/src/MovingEntitiesOperator.h index fbec898cec..bddf5da450 100644 --- a/libraries/entities/src/MovingEntitiesOperator.h +++ b/libraries/entities/src/MovingEntitiesOperator.h @@ -38,11 +38,12 @@ public: MovingEntitiesOperator(EntityTree* tree); ~MovingEntitiesOperator(); - void addEntityToMoveList(EntityItem* entity, const AACube& oldCube, const AACube& newCube); + void addEntityToMoveList(EntityItem* entity, const AACube& newCube); virtual bool preRecursion(OctreeElement* element); virtual bool postRecursion(OctreeElement* element); virtual OctreeElement* possiblyCreateChildAt(OctreeElement* element, int childIndex); bool hasMovingEntities() const { return _entitiesToMove.size() > 0; } + void finish(); private: EntityTree* _tree; QSet _entitiesToMove; diff --git a/libraries/entities/src/SimpleEntitySimulation.cpp b/libraries/entities/src/SimpleEntitySimulation.cpp index b3316978a9..b962f6980e 100644 --- a/libraries/entities/src/SimpleEntitySimulation.cpp +++ b/libraries/entities/src/SimpleEntitySimulation.cpp @@ -9,216 +9,63 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include -#include +//#include #include "EntityItem.h" -#include "MovingEntitiesOperator.h" #include "SimpleEntitySimulation.h" -void SimpleEntitySimulation::update(QSet& entitiesToDelete) { - quint64 now = usecTimestampNow(); - updateChangedEntities(now, entitiesToDelete); - updateMovingEntities(now, entitiesToDelete); - updateMortalEntities(now, entitiesToDelete); -} -void SimpleEntitySimulation::addEntity(EntityItem* entity) { - assert(entity && entity->getSimulationState() == EntityItem::Static); - EntityItem::SimulationState state = entity->computeSimulationState(); - switch(state) { - case EntityItem::Moving: - _movingEntities.push_back(entity); - entity->setSimulationState(state); - break; - case EntityItem::Mortal: - _mortalEntities.push_back(entity); - entity->setSimulationState(state); - break; - case EntityItem::Static: - default: - break; +void SimpleEntitySimulation::updateEntitiesInternal(const quint64& now) { + QSet::iterator itemItr = _movingEntities.begin(); + while (itemItr != _movingEntities.end()) { + EntityItem* entity = *itemItr; + if (!entity->isMoving()) { + itemItr = _movingEntities.erase(itemItr); + _movableButStoppedEntities.insert(entity); + } else { + entity->simulate(now); + _entitiesToBeSorted.insert(entity); + } } } -void SimpleEntitySimulation::removeEntity(EntityItem* entity) { - assert(entity); - // make sure to remove it from any of our simulation lists - EntityItem::SimulationState state = entity->getSimulationState(); - switch (state) { - case EntityItem::Moving: - _movingEntities.removeAll(entity); - break; - case EntityItem::Mortal: - _mortalEntities.removeAll(entity); - break; - - default: - break; +void SimpleEntitySimulation::addEntityInternal(EntityItem* entity) { + if (entity->getCollisionsWillMove()) { + if (entity->isMoving()) { + _movingEntities.insert(entity); + } else { + _movableButStoppedEntities.insert(entity); + } } - entity->setSimulationState(EntityItem::Static); - _changedEntities.remove(entity); } -void SimpleEntitySimulation::entityChanged(EntityItem* entity) { - assert(entity); - // we batch all changes and deal with them in updateChangedEntities() - _changedEntities.insert(entity); +void SimpleEntitySimulation::removeEntityInternal(EntityItem* entity) { + _movingEntities.remove(entity); + _movableButStoppedEntities.remove(entity); } -void SimpleEntitySimulation::clearEntities() { - foreach (EntityItem* entity, _changedEntities) { - entity->clearUpdateFlags(); - entity->setSimulationState(EntityItem::Static); +const int SIMPLE_SIMULATION_DIRTY_FLAGS = EntityItem::DIRTY_VELOCITY | EntityItem::DIRTY_MOTION_TYPE; + +void SimpleEntitySimulation::entityChangedInternal(EntityItem* entity) { + int dirtyFlags = entity->getDirtyFlags(); + if (dirtyFlags & SIMPLE_SIMULATION_DIRTY_FLAGS) { + if (entity->getCollisionsWillMove()) { + if (entity->isMoving()) { + _movingEntities.insert(entity); + _movableButStoppedEntities.remove(entity); + } else { + _movingEntities.remove(entity); + _movableButStoppedEntities.insert(entity); + } + } else { + _movingEntities.remove(entity); + _movableButStoppedEntities.remove(entity); + } } - _changedEntities.clear(); +} + +void SimpleEntitySimulation::clearEntitiesInternal() { _movingEntities.clear(); - _mortalEntities.clear(); -} - -void SimpleEntitySimulation::updateChangedEntities(quint64 now, QSet& entitiesToDelete) { - foreach (EntityItem* entity, _changedEntities) { - // check to see if the lifetime has expired, for immortal entities this is always false - if (entity->lifetimeHasExpired()) { - qDebug() << "Lifetime has expired for entity:" << entity->getEntityItemID(); - entitiesToDelete.insert(entity); - clearEntityState(entity); - } else { - updateEntityState(entity); - } - entity->clearUpdateFlags(); - } - _changedEntities.clear(); + _movableButStoppedEntities.clear(); } -void SimpleEntitySimulation::updateMovingEntities(quint64 now, QSet& entitiesToDelete) { - if (_entityTree && _movingEntities.size() > 0) { - PerformanceTimer perfTimer("_movingEntities"); - MovingEntitiesOperator moveOperator(_entityTree); - QList::iterator item_itr = _movingEntities.begin(); - while (item_itr != _movingEntities.end()) { - EntityItem* entity = *item_itr; - - // always check to see if the lifetime has expired, for immortal entities this is always false - if (entity->lifetimeHasExpired()) { - qDebug() << "Lifetime has expired for entity:" << entity->getEntityItemID(); - entitiesToDelete.insert(entity); - // remove entity from the list - item_itr = _movingEntities.erase(item_itr); - entity->setSimulationState(EntityItem::Static); - } else { - AACube oldCube = entity->getMaximumAACube(); - entity->update(now); - AACube newCube = entity->getMaximumAACube(); - - // check to see if this movement has sent the entity outside of the domain. - AACube domainBounds(glm::vec3(0.0f,0.0f,0.0f), 1.0f); - if (!domainBounds.touches(newCube)) { - qDebug() << "Entity " << entity->getEntityItemID() << " moved out of domain bounds."; - entitiesToDelete.insert(entity); - // remove entity from the list - item_itr = _movingEntities.erase(item_itr); - entity->setSimulationState(EntityItem::Static); - } else { - moveOperator.addEntityToMoveList(entity, oldCube, newCube); - EntityItem::SimulationState newState = entity->computeSimulationState(); - if (newState != EntityItem::Moving) { - if (newState == EntityItem::Mortal) { - _mortalEntities.push_back(entity); - } - // remove entity from the list - item_itr = _movingEntities.erase(item_itr); - entity->setSimulationState(newState); - } else { - ++item_itr; - } - } - } - } - if (moveOperator.hasMovingEntities()) { - PerformanceTimer perfTimer("recurseTreeWithOperator"); - _entityTree->recurseTreeWithOperator(&moveOperator); - } - } -} - -void SimpleEntitySimulation::updateMortalEntities(quint64 now, QSet& entitiesToDelete) { - QList::iterator item_itr = _mortalEntities.begin(); - while (item_itr != _mortalEntities.end()) { - EntityItem* entity = *item_itr; - // always check to see if the lifetime has expired, for immortal entities this is always false - if (entity->lifetimeHasExpired()) { - qDebug() << "Lifetime has expired for entity:" << entity->getEntityItemID(); - entitiesToDelete.insert(entity); - // remove entity from the list - item_itr = _mortalEntities.erase(item_itr); - entity->setSimulationState(EntityItem::Static); - } else { - // check to see if this entity is no longer moving - EntityItem::SimulationState newState = entity->computeSimulationState(); - if (newState != EntityItem::Mortal) { - if (newState == EntityItem::Moving) { - entity->update(now); - _movingEntities.push_back(entity); - } - // remove entity from the list - item_itr = _mortalEntities.erase(item_itr); - entity->setSimulationState(newState); - } else { - ++item_itr; - } - } - } -} - -void SimpleEntitySimulation::updateEntityState(EntityItem* entity) { - EntityItem::SimulationState oldState = entity->getSimulationState(); - EntityItem::SimulationState newState = entity->computeSimulationState(); - if (newState != oldState) { - switch (oldState) { - case EntityItem::Moving: - _movingEntities.removeAll(entity); - break; - - case EntityItem::Mortal: - _mortalEntities.removeAll(entity); - break; - - default: - break; - } - - switch (newState) { - case EntityItem::Moving: - _movingEntities.push_back(entity); - break; - - case EntityItem::Mortal: - _mortalEntities.push_back(entity); - break; - - default: - break; - } - entity->setSimulationState(newState); - } -} - -void SimpleEntitySimulation::clearEntityState(EntityItem* entity) { - EntityItem::SimulationState oldState = entity->getSimulationState(); - switch (oldState) { - case EntityItem::Moving: - _movingEntities.removeAll(entity); - break; - - case EntityItem::Mortal: - _mortalEntities.removeAll(entity); - break; - - default: - break; - } - entity->setSimulationState(EntityItem::Static); -} - - diff --git a/libraries/entities/src/SimpleEntitySimulation.h b/libraries/entities/src/SimpleEntitySimulation.h index 7d0e8f0113..92b6a28215 100644 --- a/libraries/entities/src/SimpleEntitySimulation.h +++ b/libraries/entities/src/SimpleEntitySimulation.h @@ -19,29 +19,17 @@ class SimpleEntitySimulation : public EntitySimulation { public: SimpleEntitySimulation() : EntitySimulation() { } - virtual ~SimpleEntitySimulation() { setEntityTree(NULL); } - - virtual void update(QSet& entitiesToDelete); - - virtual void addEntity(EntityItem* entity); - virtual void removeEntity(EntityItem* entity); - virtual void entityChanged(EntityItem* entity); - - virtual void clearEntities(); + virtual ~SimpleEntitySimulation() { clearEntitiesInternal(); } protected: - void updateEntityState(EntityItem* entity); - void clearEntityState(EntityItem* entity); + virtual void updateEntitiesInternal(const quint64& now); + virtual void addEntityInternal(EntityItem* entity); + virtual void removeEntityInternal(EntityItem* entity); + virtual void entityChangedInternal(EntityItem* entity); + virtual void clearEntitiesInternal(); - QList& getMovingEntities() { return _movingEntities; } - - void updateChangedEntities(quint64 now, QSet& entitiesToDelete); - void updateMovingEntities(quint64 now, QSet& entitiesToDelete); - void updateMortalEntities(quint64 now, QSet& entitiesToDelete); - - QSet _changedEntities; // entities that have changed in the last frame - QList _movingEntities; // entities that need to be updated - QList _mortalEntities; // non-moving entities that need to be checked for expiry + QSet _movingEntities; + QSet _movableButStoppedEntities; }; #endif // hifi_SimpleEntitySimulation_h From 6fdfde31d92803f84e11d93cfc7f1b3d79ede230 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 8 Dec 2014 11:20:27 -0800 Subject: [PATCH 2/3] whoops, forgot to increment the iterator --- libraries/entities/src/SimpleEntitySimulation.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/entities/src/SimpleEntitySimulation.cpp b/libraries/entities/src/SimpleEntitySimulation.cpp index b962f6980e..17dbd46727 100644 --- a/libraries/entities/src/SimpleEntitySimulation.cpp +++ b/libraries/entities/src/SimpleEntitySimulation.cpp @@ -25,6 +25,7 @@ void SimpleEntitySimulation::updateEntitiesInternal(const quint64& now) { } else { entity->simulate(now); _entitiesToBeSorted.insert(entity); + ++itemItr; } } } From 3ce1d4a38d5aac469b22485dc1f4bd67b50a5189 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 8 Dec 2014 11:21:01 -0800 Subject: [PATCH 3/3] set dirty bit when script changes simulation stuff also fixes for linear and angular damping --- libraries/entities/src/EntityItem.cpp | 58 +++++++++++++++------------ libraries/entities/src/EntityItem.h | 10 +++-- 2 files changed, 40 insertions(+), 28 deletions(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index d0d07d1227..b271439662 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -29,7 +29,7 @@ const float EntityItem::DEFAULT_LOCAL_RENDER_ALPHA = 1.0f; const float EntityItem::DEFAULT_MASS = 1.0f; const float EntityItem::DEFAULT_LIFETIME = EntityItem::IMMORTAL; const QString EntityItem::DEFAULT_USER_DATA = QString(""); -const float EntityItem::DEFAULT_DAMPING = 0.5f; +const float EntityItem::DEFAULT_DAMPING = 2.0f; const glm::vec3 EntityItem::NO_VELOCITY = glm::vec3(0, 0, 0); const float EntityItem::EPSILON_VELOCITY_LENGTH = (1.0f / 1000.0f) / (float)TREE_SCALE; // really small: 1mm/second const glm::vec3 EntityItem::DEFAULT_VELOCITY = EntityItem::NO_VELOCITY; @@ -42,7 +42,7 @@ const glm::vec3 EntityItem::DEFAULT_DIMENSIONS = glm::vec3(0.1f, 0.1f, 0.1f); const glm::vec3 EntityItem::DEFAULT_REGISTRATION_POINT = glm::vec3(0.5f, 0.5f, 0.5f); // center const glm::vec3 EntityItem::NO_ANGULAR_VELOCITY = glm::vec3(0.0f, 0.0f, 0.0f); const glm::vec3 EntityItem::DEFAULT_ANGULAR_VELOCITY = NO_ANGULAR_VELOCITY; -const float EntityItem::DEFAULT_ANGULAR_DAMPING = 0.5f; +const float EntityItem::DEFAULT_ANGULAR_DAMPING = 2.0f; const bool EntityItem::DEFAULT_VISIBLE = true; const bool EntityItem::DEFAULT_IGNORE_FOR_COLLISIONS = false; const bool EntityItem::DEFAULT_COLLISIONS_WILL_MOVE = false; @@ -155,7 +155,7 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet ByteCountCoded typeCoder = getType(); QByteArray encodedType = typeCoder; - quint64 updateDelta = getLastMoved() <= getLastEdited() ? 0 : getLastMoved() - getLastEdited(); + quint64 updateDelta = getLastSimulated() <= getLastEdited() ? 0 : getLastSimulated() - getLastEdited(); ByteCountCoded updateDeltaCoder = updateDelta; QByteArray encodedUpdateDelta = updateDeltaCoder; EntityPropertyFlags propertyFlags(PROP_LAST_ITEM); @@ -632,13 +632,13 @@ void EntityItem::simulate(const quint64& now) { setRotation(rotation); // handle damping for angular velocity - if (getAngularDamping() > 0.0f) { - glm::vec3 dampingResistance = getAngularVelocity() * getAngularDamping(); - glm::vec3 newAngularVelocity = getAngularVelocity() - (dampingResistance * timeElapsed); + float dampingTimescale = getAngularDamping(); + if (dampingTimescale > 0.0f) { + float dampingFactor = glm::clamp(timeElapsed / dampingTimescale, 0.0f, 1.0f); + glm::vec3 newAngularVelocity = (1.0f - dampingFactor) * getAngularVelocity(); setAngularVelocity(newAngularVelocity); if (wantDebug) { - qDebug() << " getDamping():" << getDamping(); - qDebug() << " dampingResistance:" << dampingResistance; + qDebug() << " dampingTimescale :" << dampingTimescale; qDebug() << " newAngularVelocity:" << newAngularVelocity; } } @@ -689,13 +689,15 @@ void EntityItem::simulate(const quint64& now) { } // handle damping for velocity - glm::vec3 dampingResistance = velocity * getDamping(); - if (wantDebug) { - qDebug() << " getDamping():" << getDamping(); - qDebug() << " dampingResistance:" << dampingResistance; - qDebug() << " dampingResistance * timeElapsed:" << dampingResistance * timeElapsed; + float dampingTimescale = getDamping(); + if (dampingTimescale > 0.0f) { + float dampingFactor = glm::clamp(timeElapsed / dampingTimescale, 0.0f, 1.0f); + velocity *= (1.0f - dampingFactor); + if (wantDebug) { + qDebug() << " dampingTimescale:" << dampingTimescale; + qDebug() << " newVelocity:" << velocity; + } } - velocity -= dampingResistance * timeElapsed; if (wantDebug) { qDebug() << " velocity AFTER dampingResistance:" << velocity; @@ -778,23 +780,23 @@ bool EntityItem::setProperties(const EntityItemProperties& properties, bool forc } } - SET_ENTITY_PROPERTY_FROM_PROPERTIES(position, setPositionInMeters); // this will call recalculate collision shape if needed - SET_ENTITY_PROPERTY_FROM_PROPERTIES(dimensions, setDimensionsInMeters); // NOTE: radius is obsolete - SET_ENTITY_PROPERTY_FROM_PROPERTIES(rotation, setRotation); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(mass, setMass); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(velocity, setVelocityInMeters); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(gravity, setGravityInMeters); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(position, updatePositionInMeters); // this will call recalculate collision shape if needed + SET_ENTITY_PROPERTY_FROM_PROPERTIES(dimensions, updateDimensionsInMeters); // NOTE: radius is obsolete + SET_ENTITY_PROPERTY_FROM_PROPERTIES(rotation, updateRotation); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(mass, updateMass); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(velocity, updateVelocityInMeters); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(gravity, updateGravityInMeters); SET_ENTITY_PROPERTY_FROM_PROPERTIES(damping, setDamping); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(lifetime, setLifetime); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(script, setScript); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(lifetime, updateLifetime); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(script, updateScript); SET_ENTITY_PROPERTY_FROM_PROPERTIES(registrationPoint, setRegistrationPoint); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(angularVelocity, setAngularVelocity); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(angularVelocity, updateAngularVelocity); SET_ENTITY_PROPERTY_FROM_PROPERTIES(angularDamping, setAngularDamping); SET_ENTITY_PROPERTY_FROM_PROPERTIES(glowLevel, setGlowLevel); SET_ENTITY_PROPERTY_FROM_PROPERTIES(localRenderAlpha, setLocalRenderAlpha); SET_ENTITY_PROPERTY_FROM_PROPERTIES(visible, setVisible); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(ignoreForCollisions, setIgnoreForCollisions); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(collisionsWillMove, setCollisionsWillMove); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(ignoreForCollisions, updateIgnoreForCollisions); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(collisionsWillMove, updateCollisionsWillMove); SET_ENTITY_PROPERTY_FROM_PROPERTIES(locked, setLocked); SET_ENTITY_PROPERTY_FROM_PROPERTIES(userData, setUserData); @@ -1051,4 +1053,10 @@ void EntityItem::updateLifetime(float value) { } } +void EntityItem::updateScript(const QString& value) { + if (_script != value) { + _script = value; + _dirtyFlags |= EntityItem::DIRTY_SCRIPT; + } +} diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index f0d3330087..bde1ebc684 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -48,7 +48,10 @@ public: DIRTY_COLLISION_GROUP = 0x0008, DIRTY_MOTION_TYPE = 0x0010, DIRTY_SHAPE = 0x0020, - DIRTY_LIFETIME = 0x0040 + DIRTY_LIFETIME = 0x0040, + // add new simulation-relevant flags above + // all other flags below + DIRTY_SCRIPT = 0x8000 }; DONT_ALLOW_INSTANTIATION // This class can not be instantiated directly @@ -292,6 +295,7 @@ public: void updateIgnoreForCollisions(bool value); void updateCollisionsWillMove(bool value); void updateLifetime(float value); + void updateScript(const QString& value); uint32_t getDirtyFlags() const { return _dirtyFlags; } void clearDirtyFlags(uint32_t mask = 0xffff) { _dirtyFlags &= ~mask; } @@ -323,12 +327,12 @@ protected: float _mass; glm::vec3 _velocity; glm::vec3 _gravity; - float _damping; + float _damping; // timescale float _lifetime; QString _script; glm::vec3 _registrationPoint; glm::vec3 _angularVelocity; - float _angularDamping; + float _angularDamping; // timescale bool _visible; bool _ignoreForCollisions; bool _collisionsWillMove;