Merge pull request #3922 from AndrewMeadows/thermonuclear

overhaul EntitySimulation API (and relay relevant EntityItem properties to simulation when they change)
This commit is contained in:
Brad Hefta-Gaub 2014-12-08 13:56:06 -08:00
commit a9079b64e4
16 changed files with 423 additions and 355 deletions

View file

@ -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);
@ -253,8 +253,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 {

View file

@ -57,9 +57,11 @@ public:
void** intersectedObject, bool precisionPicking) const;
Model* getModel(EntityTreeRenderer* renderer);
bool needsToCallUpdate() const;
private:
void remapTextures();
bool needsSimulation() const;
Model* _model;
bool _needsInitialSimulation;

View file

@ -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);
}

View file

@ -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;
@ -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<quint32> typeCoder = getType();
QByteArray encodedType = typeCoder;
quint64 updateDelta = getLastUpdated() <= getLastEdited() ? 0 : getLastUpdated() - getLastEdited();
quint64 updateDelta = getLastSimulated() <= getLastEdited() ? 0 : getLastSimulated() - getLastEdited();
ByteCountCoded<quint64> updateDeltaCoder = updateDelta;
QByteArray encodedUpdateDelta = updateDeltaCoder;
EntityPropertyFlags propertyFlags(PROP_LAST_ITEM);
@ -450,9 +451,9 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
ByteCountCoded<quint64> 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()) {
@ -631,13 +632,13 @@ void EntityItem::update(const quint64& updateTime) {
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;
}
}
@ -688,13 +689,15 @@ void EntityItem::update(const quint64& updateTime) {
}
// 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;
@ -707,6 +710,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 +723,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();
@ -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);
@ -948,7 +950,7 @@ void EntityItem::updatePosition(const glm::vec3& value) {
if (_position != value) {
_position = value;
recalculateCollisionShape();
_updateFlags |= EntityItem::UPDATE_POSITION;
_dirtyFlags |= EntityItem::DIRTY_POSITION;
}
}
@ -957,7 +959,7 @@ void EntityItem::updatePositionInMeters(const glm::vec3& value) {
if (_position != position) {
_position = position;
recalculateCollisionShape();
_updateFlags |= EntityItem::UPDATE_POSITION;
_dirtyFlags |= EntityItem::DIRTY_POSITION;
}
}
@ -965,7 +967,7 @@ void EntityItem::updateDimensions(const glm::vec3& value) {
if (_dimensions != value) {
_dimensions = value;
recalculateCollisionShape();
_updateFlags |= EntityItem::UPDATE_SHAPE;
_dirtyFlags |= EntityItem::DIRTY_SHAPE;
}
}
@ -974,7 +976,7 @@ void EntityItem::updateDimensionsInMeters(const glm::vec3& value) {
if (_dimensions != dimensions) {
_dimensions = dimensions;
recalculateCollisionShape();
_updateFlags |= EntityItem::UPDATE_SHAPE;
_dirtyFlags |= EntityItem::DIRTY_SHAPE;
}
}
@ -982,21 +984,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 +1006,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,36 +1021,42 @@ 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;
}
}
void EntityItem::updateScript(const QString& value) {
if (_script != value) {
_script = value;
_dirtyFlags |= EntityItem::DIRTY_SCRIPT;
}
}

View file

@ -41,15 +41,17 @@ 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,
// add new simulation-relevant flags above
// all other flags below
DIRTY_SCRIPT = 0x8000
};
DONT_ALLOW_INSTANTIATION // This class can not be instantiated directly
@ -77,12 +79,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 +123,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 +220,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 +280,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);
@ -293,13 +295,13 @@ public:
void updateIgnoreForCollisions(bool value);
void updateCollisionsWillMove(bool value);
void updateLifetime(float value);
void updateScript(const QString& 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 +311,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
@ -324,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;
@ -343,11 +346,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
};

View file

@ -9,12 +9,178 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <AACube.h>
#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<EntityItem*>& 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<EntityItem*>::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<EntityItem*>::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<EntityItem*>::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();
}

View file

@ -14,36 +14,61 @@
#include <QSet>
#include <PerfStat.h>
#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<EntityItem*>& entitiesToDelete) = 0;
void updateEntities(QSet<EntityItem*>& 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<EntityItem*> _mortalEntities; // entities that have an expiry
quint64 _nextExpiry;
QSet<EntityItem*> _updateableEntities; // entities that need update() called
QSet<EntityItem*> _entitiesToBeSorted; // entities that were moved by THIS simulation and might need to be resorted in the tree
QSet<EntityItem*> _entitiesToDelete;
};
#endif // hifi_EntitySimulation_h

View file

@ -83,6 +83,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);
@ -103,51 +104,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;
@ -576,7 +590,7 @@ void EntityTree::update() {
if (_simulation) {
lockForWrite();
QSet<EntityItem*> entitiesToDelete;
_simulation->update(entitiesToDelete);
_simulation->updateEntities(entitiesToDelete);
if (entitiesToDelete.size() > 0) {
// translate into list of ID's
QSet<EntityItemID> idsToDelete;

View file

@ -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<EntityItemID> 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);

View file

@ -761,7 +761,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);

View file

@ -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 {

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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<EntityToMoveDetails> _entitiesToMove;

View file

@ -9,216 +9,64 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <AACube.h>
#include <PerfStat.h>
//#include <PerfStat.h>
#include "EntityItem.h"
#include "MovingEntitiesOperator.h"
#include "SimpleEntitySimulation.h"
void SimpleEntitySimulation::update(QSet<EntityItem*>& 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<EntityItem*>::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);
++itemItr;
}
}
}
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<EntityItem*>& 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<EntityItem*>& entitiesToDelete) {
if (_entityTree && _movingEntities.size() > 0) {
PerformanceTimer perfTimer("_movingEntities");
MovingEntitiesOperator moveOperator(_entityTree);
QList<EntityItem*>::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<EntityItem*>& entitiesToDelete) {
QList<EntityItem*>::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);
}

View file

@ -19,29 +19,17 @@
class SimpleEntitySimulation : public EntitySimulation {
public:
SimpleEntitySimulation() : EntitySimulation() { }
virtual ~SimpleEntitySimulation() { setEntityTree(NULL); }
virtual void update(QSet<EntityItem*>& 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<EntityItem*>& getMovingEntities() { return _movingEntities; }
void updateChangedEntities(quint64 now, QSet<EntityItem*>& entitiesToDelete);
void updateMovingEntities(quint64 now, QSet<EntityItem*>& entitiesToDelete);
void updateMortalEntities(quint64 now, QSet<EntityItem*>& entitiesToDelete);
QSet<EntityItem*> _changedEntities; // entities that have changed in the last frame
QList<EntityItem*> _movingEntities; // entities that need to be updated
QList<EntityItem*> _mortalEntities; // non-moving entities that need to be checked for expiry
QSet<EntityItem*> _movingEntities;
QSet<EntityItem*> _movableButStoppedEntities;
};
#endif // hifi_SimpleEntitySimulation_h