From 971804b98eeace4aad6f1fe0bbe859bedabac918 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 17 Nov 2014 16:14:49 -0800 Subject: [PATCH 1/8] name change shinkIfNeeded() -> shrinkIfNeeded() --- libraries/shared/src/PropertyFlags.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/libraries/shared/src/PropertyFlags.h b/libraries/shared/src/PropertyFlags.h index 7614ad7b7c..de05edc076 100644 --- a/libraries/shared/src/PropertyFlags.h +++ b/libraries/shared/src/PropertyFlags.h @@ -92,7 +92,7 @@ public: PropertyFlags operator<<(Enum flag) const; // NOTE: due to the nature of the compact storage of these property flags, and the fact that the upper bound of the - // enum is not know, these operators will only perform their bitwise operations on the set of properties that have + // enum is not known, these operators will only perform their bitwise operations on the set of properties that have // been previously set PropertyFlags& operator^=(const PropertyFlags& other); PropertyFlags& operator^=(Enum flag); @@ -106,7 +106,7 @@ public: private: - void shinkIfNeeded(); + void shrinkIfNeeded(); QBitArray _flags; int _maxFlag; @@ -142,7 +142,7 @@ template inline void PropertyFlags::setHasProperty(Enum fla _flags.setBit(flag, value); if (flag == _maxFlag && !value) { - shinkIfNeeded(); + shrinkIfNeeded(); } } @@ -274,27 +274,27 @@ template inline PropertyFlags& PropertyFlags::operato template inline PropertyFlags& PropertyFlags::operator&=(const PropertyFlags& other) { _flags &= other._flags; - shinkIfNeeded(); + shrinkIfNeeded(); return *this; } template inline PropertyFlags& PropertyFlags::operator&=(Enum flag) { PropertyFlags other(flag); _flags &= other._flags; - shinkIfNeeded(); + shrinkIfNeeded(); return *this; } template inline PropertyFlags& PropertyFlags::operator^=(const PropertyFlags& other) { _flags ^= other._flags; - shinkIfNeeded(); + shrinkIfNeeded(); return *this; } template inline PropertyFlags& PropertyFlags::operator^=(Enum flag) { PropertyFlags other(flag); _flags ^= other._flags; - shinkIfNeeded(); + shrinkIfNeeded(); return *this; } @@ -422,7 +422,7 @@ template inline PropertyFlags PropertyFlags::operator return result; } -template inline void PropertyFlags::shinkIfNeeded() { +template inline void PropertyFlags::shrinkIfNeeded() { int maxFlagWas = _maxFlag; while (_maxFlag >= 0) { if (_flags.testBit(_maxFlag)) { From 114364e37a11e0a9b221a6b76db2a13842ea82a1 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 17 Nov 2014 16:39:26 -0800 Subject: [PATCH 2/8] cleanup/optimization of Octree::readElementData() --- libraries/octree/src/Octree.cpp | 40 +++++++++++++-------------------- 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index e9cf1158fa..f13f832920 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -289,36 +289,26 @@ int Octree::readElementData(OctreeElement* destinationElement, const unsigned ch for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { // check the colors mask to see if we have a child to color in if (oneAtBit(colorInPacketMask, i)) { - // create the child if it doesn't exist - if (!destinationElement->getChildAtIndex(i)) { - destinationElement->addChildAtIndex(i); - if (destinationElement->isDirty()) { - _isDirty = true; - } - } + // addChildAtIndex() should actually be called getOrAddChildAtIndex(). + // When it adds the child it automatically sets the detinationElement dirty. + OctreeElement* childElementAt = destinationElement->addChildAtIndex(i); - OctreeElement* childElementAt = destinationElement->getChildAtIndex(i); - bool nodeIsDirty = false; - if (childElementAt) { + int childElementDataRead = childElementAt->readElementDataFromBuffer(nodeData + bytesRead, bytesLeftToRead, args); + childElementAt->setSourceUUID(args.sourceUUID); + + bytesRead += childElementDataRead; + bytesLeftToRead -= childElementDataRead; - int childElementDataRead = childElementAt->readElementDataFromBuffer(nodeData + bytesRead, bytesLeftToRead, args); - childElementAt->setSourceUUID(args.sourceUUID); - - bytesRead += childElementDataRead; - bytesLeftToRead -= childElementDataRead; - - // if we had a local version of the element already, it's possible that we have it already but - // with the same color data, so this won't count as a change. To address this we check the following - if (!childElementAt->isDirty() && childElementAt->getShouldRender() && !childElementAt->isRendered()) { - childElementAt->setDirtyBit(); // force dirty! - } - - nodeIsDirty = childElementAt->isDirty(); - } - if (nodeIsDirty) { + // It's possible that we already had this version of element data, in which case the unpacking of the data + // wouldn't flag childElementAt as dirty, so we manually flag it here... if the element is to be rendered. + if (childElementAt->getShouldRender() && !childElementAt->isRendered()) { + childElementAt->setDirtyBit(); // force dirty! _isDirty = true; } } + if (destinationElement->isDirty()) { + _isDirty = true; + } } unsigned char childrenInTreeMask = ALL_CHILDREN_ASSUMED_TO_EXIST; From af606f01a9418fb76975aaae890e61e8022a47c7 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 17 Nov 2014 17:35:44 -0800 Subject: [PATCH 3/8] combined SimulationStates Moving and Changing also funnel entity updates by UDP packet through a single list --- .../entities/RenderableModelEntityItem.cpp | 3 +- libraries/entities/src/BoxEntityItem.cpp | 2 +- libraries/entities/src/EntityItem.cpp | 11 +- libraries/entities/src/EntityItem.h | 17 +- libraries/entities/src/EntityTree.cpp | 178 ++++++------------ libraries/entities/src/EntityTree.h | 17 +- libraries/entities/src/EntityTreeElement.cpp | 11 +- libraries/entities/src/ModelEntityItem.cpp | 6 +- libraries/entities/src/ModelEntityItem.h | 2 +- 9 files changed, 95 insertions(+), 152 deletions(-) diff --git a/interface/src/entities/RenderableModelEntityItem.cpp b/interface/src/entities/RenderableModelEntityItem.cpp index 8d932b121d..7b2856bb18 100644 --- a/interface/src/entities/RenderableModelEntityItem.cpp +++ b/interface/src/entities/RenderableModelEntityItem.cpp @@ -246,8 +246,7 @@ Model* RenderableModelEntityItem::getModel(EntityTreeRenderer* renderer) { } bool RenderableModelEntityItem::needsSimulation() const { - SimulationState simulationState = getSimulationState(); - return _needsInitialSimulation || simulationState == Moving || simulationState == Changing; + return _needsInitialSimulation || getSimulationState() == EntityItem::Moving; } EntityItemProperties RenderableModelEntityItem::getProperties() const { diff --git a/libraries/entities/src/BoxEntityItem.cpp b/libraries/entities/src/BoxEntityItem.cpp index 83d6d7eff9..16204f0c16 100644 --- a/libraries/entities/src/BoxEntityItem.cpp +++ b/libraries/entities/src/BoxEntityItem.cpp @@ -94,4 +94,4 @@ void BoxEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitst bool successPropertyFits = true; APPEND_ENTITY_PROPERTY(PROP_COLOR, appendColor, getColor()); -} \ No newline at end of file +} diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index d5790d88a7..d0fafa06b1 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -92,6 +92,7 @@ EntityItem::EntityItem(const EntityItemID& entityItemID) { _created = 0; _changedOnServer = 0; initFromEntityItemID(entityItemID); + _simulationState = EntityItem::Static; } EntityItem::EntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) { @@ -104,6 +105,7 @@ EntityItem::EntityItem(const EntityItemID& entityItemID, const EntityItemPropert _changedOnServer = 0; initFromEntityItemID(entityItemID); setProperties(properties, true); // force copy + _simulationState = EntityItem::Static; } EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& params) const { @@ -404,7 +406,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef qDebug() << " fromSameServerEdit=" << fromSameServerEdit; } - bool ignoreServerPacket = false; // assume we're use this server packet + bool ignoreServerPacket = false; // assume we'll use this server packet // If this packet is from the same server edit as the last packet we accepted from the server // we probably want to use it. @@ -715,13 +717,10 @@ void EntityItem::update(const quint64& updateTime) { } } -EntityItem::SimulationState EntityItem::getSimulationState() const { - if (hasVelocity() || (hasGravity() && !isRestingOnSurface())) { +EntityItem::SimulationState EntityItem::computeSimulationState() const { + if (hasVelocity() || (hasGravity() && !isRestingOnSurface()) || hasAngularVelocity()) { return EntityItem::Moving; } - if (hasAngularVelocity()) { - return EntityItem::Changing; - } if (isMortal()) { return EntityItem::Mortal; } diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index cb153dee60..61e929d9b3 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -28,6 +28,7 @@ #include "EntityItemProperties.h" #include "EntityTypes.h" +class EntityTree; class EntityTreeElement; class EntityTreeElementExtraEncodeData; @@ -59,10 +60,10 @@ public: // methods for getting/setting all properties of an entity virtual EntityItemProperties getProperties() const; - /// returns true is something changed + /// returns true if something changed virtual bool setProperties(const EntityItemProperties& properties, bool forceCopy = false); - /// override this in your derived class if you'd like to be informed when something about the state of the entity + /// Override this in your derived class if you'd like to be informed when something about the state of the entity /// has changed. This will be called with properties change or when new data is loaded from a stream virtual void somethingChangedNotification() { } @@ -115,11 +116,13 @@ public: typedef enum SimulationState_t { Static, Mortal, - Changing, Moving } SimulationState; - virtual SimulationState getSimulationState() const; + // 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; + virtual void debugDump() const; // similar to assignment/copy, but it handles keeping lifetime accurate @@ -262,8 +265,13 @@ public: void applyHardCollision(const CollisionInfo& collisionInfo); virtual const Shape& getCollisionShapeInMeters() const { return _collisionShape; } virtual bool contains(const glm::vec3& point) const { return getAABox().contains(point); } + + SimulationState getSimulationState() const { return _simulationState; } protected: + friend EntityTree; + void setSimulationState(SimulationState state) { _simulationState = state; } + virtual void initFromEntityItemID(const EntityItemID& entityItemID); // maybe useful to allow subclasses to init virtual void recalculateCollisionShape(); @@ -305,6 +313,7 @@ protected: void setRadius(float value); AACubeShape _collisionShape; + SimulationState _simulationState; // only set by EntityTree }; diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 73957ad077..53d040c4a8 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -40,8 +40,8 @@ void EntityTree::eraseAllOctreeElements(bool createNewRoot) { _entityToElementMap.clear(); Octree::eraseAllOctreeElements(createNewRoot); _movingEntities.clear(); - _changingEntities.clear(); _mortalEntities.clear(); + _changedEntities.clear(); } bool EntityTree::handlesEditPacketType(PacketType packetType) const { @@ -80,7 +80,7 @@ void EntityTree::addEntityItem(EntityItem* entityItem) { EntityItemID entityID = entityItem->getEntityItemID(); EntityTreeElement* containingElement = getContainingElement(entityID); if (containingElement) { - qDebug() << "UNEXPECTED!!!! don't call addEntityItem() on existing entity items. entityID=" << entityID; + qDebug() << "UNEXPECTED!!!! don't call addEntityItem() on existing EntityItems. entityID=" << entityID; return; } @@ -89,7 +89,7 @@ void EntityTree::addEntityItem(EntityItem* entityItem) { recurseTreeWithOperator(&theOperator); // check to see if we need to simulate this entity.. - changeEntityState(entityItem, EntityItem::Static, entityItem->getSimulationState()); + changeEntityState(entityItem); _isDirty = true; } @@ -123,15 +123,13 @@ bool EntityTree::updateEntity(const EntityItemID& entityID, const EntityItemProp } } else { // check to see if we need to simulate this entity... - EntityItem::SimulationState oldState = existingEntity->getSimulationState(); QString entityScriptBefore = existingEntity->getScript(); UpdateEntityOperator theOperator(this, containingElement, existingEntity, properties); recurseTreeWithOperator(&theOperator); _isDirty = true; - EntityItem::SimulationState newState = existingEntity->getSimulationState(); - changeEntityState(existingEntity, oldState, newState); + changeEntityState(existingEntity); QString entityScriptAfter = existingEntity->getScript(); if (entityScriptBefore != entityScriptAfter) { @@ -229,10 +227,6 @@ void EntityTree::removeEntityFromSimulationLists(const EntityItemID& entityID) { // make sure to remove it from any of our simulation lists EntityItem::SimulationState theState = theEntity->getSimulationState(); switch (theState) { - case EntityItem::Changing: - _changingEntities.removeAll(theEntity); - break; - case EntityItem::Moving: _movingEntities.removeAll(theEntity); break; @@ -244,6 +238,7 @@ void EntityTree::removeEntityFromSimulationLists(const EntityItemID& entityID) { default: break; } + _changedEntities.remove(theEntity); } } @@ -518,7 +513,7 @@ int EntityTree::processEditPacketData(PacketType packetType, const unsigned char // search for the entity by EntityItemID EntityItem* existingEntity = findEntityByEntityItemID(entityItemID); - // if the entityItem exists, then update it + // if the EntityItem exists, then update it if (existingEntity) { updateEntity(entityItemID, properties); existingEntity->markAsChangedOnServer(); @@ -580,15 +575,42 @@ void EntityTree::releaseSceneEncodeData(OctreeElementExtraEncodeData* extraEncod extraEncodeData->clear(); } -void EntityTree::changeEntityState(EntityItem* const entity, - EntityItem::SimulationState oldState, EntityItem::SimulationState newState) { +void EntityTree::changeEntityState(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); + } +} - // TODO: can we short circuit this if the state isn't changing? +void EntityTree::clearEntityState(EntityItem* entity) { + EntityItem::SimulationState oldState = entity->getSimulationState(); switch (oldState) { - case EntityItem::Changing: - _changingEntities.removeAll(entity); - break; - case EntityItem::Moving: _movingEntities.removeAll(entity); break; @@ -600,24 +622,11 @@ void EntityTree::changeEntityState(EntityItem* const entity, default: break; } + entity->setSimulationState(EntityItem::Static); +} - - switch (newState) { - case EntityItem::Changing: - _changingEntities.push_back(entity); - break; - - case EntityItem::Moving: - _movingEntities.push_back(entity); - break; - - case EntityItem::Mortal: - _mortalEntities.push_back(entity); - break; - - default: - break; - } +void EntityTree::entityChanged(EntityItem* entity) { + _changedEntities.insert(entity); } void EntityTree::update() { @@ -633,7 +642,7 @@ void EntityTree::update() { lockForWrite(); quint64 now = usecTimestampNow(); QSet entitiesToDelete; - updateChangingEntities(now, entitiesToDelete); + updateChangedEntities(now, entitiesToDelete); updateMovingEntities(now, entitiesToDelete); updateMortalEntities(now, entitiesToDelete); @@ -643,55 +652,25 @@ void EntityTree::update() { unlock(); } -void EntityTree::updateChangingEntities(quint64 now, QSet& entitiesToDelete) { - QSet entitiesBecomingStatic; - QSet entitiesBecomingMortal; - QSet entitiesBecomingMoving; - +void EntityTree::updateChangedEntities(quint64 now, QSet& entitiesToDelete) { // TODO: switch these to iterators so we can remove items that get deleted - for (int i = 0; i < _changingEntities.size(); i++) { - EntityItem* thisEntity = _changingEntities[i]; - thisEntity->update(now); - // always check to see if the lifetime has expired, for immortal entities this is always false + foreach (EntityItem* thisEntity, _changedEntities) { + // check to see if the lifetime has expired, for immortal entities this is always false if (thisEntity->lifetimeHasExpired()) { - qDebug() << "Lifetime has expired for entity:" << thisEntity->getEntityItemID(); + qDebug() << "Lifetime has expired for thisEntity:" << thisEntity->getEntityItemID(); entitiesToDelete << thisEntity->getEntityItemID(); - entitiesBecomingStatic << thisEntity; + clearEntityState(thisEntity); } else { - // check to see if this entity is no longer moving - EntityItem::SimulationState newState = thisEntity->getSimulationState(); - - if (newState == EntityItem::Static) { - entitiesBecomingStatic << thisEntity; - } else if (newState == EntityItem::Mortal) { - entitiesBecomingMortal << thisEntity; - } else if (newState == EntityItem::Moving) { - entitiesBecomingMoving << thisEntity; - } + changeEntityState(thisEntity); } } - - // change state for any entities that were changing but are now either static, mortal, or moving - foreach(EntityItem* entity, entitiesBecomingStatic) { - changeEntityState(entity, EntityItem::Changing, EntityItem::Static); - } - foreach(EntityItem* entity, entitiesBecomingMortal) { - changeEntityState(entity, EntityItem::Changing, EntityItem::Mortal); - } - foreach(EntityItem* entity, entitiesBecomingMoving) { - changeEntityState(entity, EntityItem::Changing, EntityItem::Moving); - } + _changedEntities.clear(); } void EntityTree::updateMovingEntities(quint64 now, QSet& entitiesToDelete) { PerformanceTimer perfTimer("updateMovingEntities"); if (_movingEntities.size() > 0) { MovingEntitiesOperator moveOperator(this); - - QSet entitiesBecomingStatic; - QSet entitiesBecomingMortal; - QSet entitiesBecomingChanging; - { PerformanceTimer perfTimer("_movingEntities"); @@ -703,7 +682,7 @@ void EntityTree::updateMovingEntities(quint64 now, QSet& entitiesT if (thisEntity->lifetimeHasExpired()) { qDebug() << "Lifetime has expired for entity:" << thisEntity->getEntityItemID(); entitiesToDelete << thisEntity->getEntityItemID(); - entitiesBecomingStatic << thisEntity; + clearEntityState(thisEntity); } else { AACube oldCube = thisEntity->getMaximumAACube(); thisEntity->update(now); @@ -714,19 +693,10 @@ void EntityTree::updateMovingEntities(quint64 now, QSet& entitiesT if (!domainBounds.touches(newCube)) { qDebug() << "Entity " << thisEntity->getEntityItemID() << " moved out of domain bounds."; entitiesToDelete << thisEntity->getEntityItemID(); - entitiesBecomingStatic << thisEntity; + clearEntityState(thisEntity); } else { moveOperator.addEntityToMoveList(thisEntity, oldCube, newCube); - - // check to see if this entity is no longer moving - EntityItem::SimulationState newState = thisEntity->getSimulationState(); - if (newState == EntityItem::Changing) { - entitiesBecomingChanging << thisEntity; - } else if (newState == EntityItem::Mortal) { - entitiesBecomingMortal << thisEntity; - } else if (newState == EntityItem::Static) { - entitiesBecomingStatic << thisEntity; - } + changeEntityState(thisEntity); } } } @@ -735,25 +705,10 @@ void EntityTree::updateMovingEntities(quint64 now, QSet& entitiesT PerformanceTimer perfTimer("recurseTreeWithOperator"); recurseTreeWithOperator(&moveOperator); } - - // change state for any entities that were moving but are now either static, mortal, or changing - foreach(EntityItem* entity, entitiesBecomingStatic) { - changeEntityState(entity, EntityItem::Moving, EntityItem::Static); - } - foreach(EntityItem* entity, entitiesBecomingMortal) { - changeEntityState(entity, EntityItem::Moving, EntityItem::Mortal); - } - foreach(EntityItem* entity, entitiesBecomingChanging) { - changeEntityState(entity, EntityItem::Moving, EntityItem::Changing); - } } } void EntityTree::updateMortalEntities(quint64 now, QSet& entitiesToDelete) { - QSet entitiesBecomingStatic; - QSet entitiesBecomingChanging; - QSet entitiesBecomingMoving; - // TODO: switch these to iterators so we can remove items that get deleted for (int i = 0; i < _mortalEntities.size(); i++) { EntityItem* thisEntity = _mortalEntities[i]; @@ -762,31 +717,12 @@ void EntityTree::updateMortalEntities(quint64 now, QSet& entitiesT if (thisEntity->lifetimeHasExpired()) { qDebug() << "Lifetime has expired for entity:" << thisEntity->getEntityItemID(); entitiesToDelete << thisEntity->getEntityItemID(); - entitiesBecomingStatic << thisEntity; + clearEntityState(thisEntity); } else { // check to see if this entity is no longer moving - EntityItem::SimulationState newState = thisEntity->getSimulationState(); - - if (newState == EntityItem::Static) { - entitiesBecomingStatic << thisEntity; - } else if (newState == EntityItem::Changing) { - entitiesBecomingChanging << thisEntity; - } else if (newState == EntityItem::Moving) { - entitiesBecomingMoving << thisEntity; - } + changeEntityState(thisEntity); } } - - // change state for any entities that were mortal but are now either static, changing, or moving - foreach(EntityItem* entity, entitiesBecomingStatic) { - changeEntityState(entity, EntityItem::Mortal, EntityItem::Static); - } - foreach(EntityItem* entity, entitiesBecomingChanging) { - changeEntityState(entity, EntityItem::Mortal, EntityItem::Changing); - } - foreach(EntityItem* entity, entitiesBecomingMoving) { - changeEntityState(entity, EntityItem::Mortal, EntityItem::Moving); - } } diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 6fe2d256c2..bf9da5a15d 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -12,6 +12,8 @@ #ifndef hifi_EntityTree_h #define hifi_EntityTree_h +#include + #include #include "EntityTreeElement.h" @@ -135,8 +137,10 @@ public: void sendEntities(EntityEditPacketSender* packetSender, EntityTree* localTree, float x, float y, float z); - void changeEntityState(EntityItem* const entity, - EntityItem::SimulationState oldState, EntityItem::SimulationState newState); + void changeEntityState(EntityItem* entity); + void clearEntityState(EntityItem* entity); + + void entityChanged(EntityItem* entity); void trackDeletedEntity(const EntityItemID& entityID); @@ -153,7 +157,7 @@ signals: private: - void updateChangingEntities(quint64 now, QSet& entitiesToDelete); + void updateChangedEntities(quint64 now, QSet& entitiesToDelete); void updateMovingEntities(quint64 now, QSet& entitiesToDelete); void updateMortalEntities(quint64 now, QSet& entitiesToDelete); @@ -173,9 +177,10 @@ private: QHash _entityToElementMap; - QList _movingEntities; // entities that are moving as part of update - QList _changingEntities; // entities that are changing (like animating), but not moving - QList _mortalEntities; // entities that are mortal (have lifetime), but not moving or changing + QList _movingEntities; // entities that need to be updated + QList _mortalEntities; // entities that need to be checked for expiry + + QSet _changedEntities; // entities that have changed in the last frame }; #endif // hifi_EntityTree_h diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 1dea2bcd85..a461df5fde 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -737,14 +737,10 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int QString entityScriptBefore = entityItem->getScript(); bool bestFitBefore = bestFitEntityBounds(entityItem); EntityTreeElement* currentContainingElement = _myTree->getContainingElement(entityItemID); - EntityItem::SimulationState oldState = entityItem->getSimulationState(); bytesForThisEntity = entityItem->readEntityDataFromBuffer(dataAt, bytesLeftToRead, args); - - EntityItem::SimulationState newState = entityItem->getSimulationState(); - if (oldState != newState) { - _myTree->changeEntityState(entityItem, oldState, newState); - } + // TODO: Andrew to only set changed if something has actually changed + _myTree->entityChanged(entityItem); bool bestFitAfter = bestFitEntityBounds(entityItem); if (bestFitBefore != bestFitAfter) { @@ -771,9 +767,8 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int addEntityItem(entityItem); // add this new entity to this elements entities entityItemID = entityItem->getEntityItemID(); _myTree->setContainingElement(entityItemID, this); + _myTree->changeEntityState(entityItem); _myTree->emitAddingEntity(entityItemID); // we just added an entity - EntityItem::SimulationState newState = entityItem->getSimulationState(); - _myTree->changeEntityState(entityItem, EntityItem::Static, newState); } } // Move the buffer forward to read more entities diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index f50fe7866b..31a0c57f5b 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -371,15 +371,15 @@ bool ModelEntityItem::isAnimatingSomething() const { !getAnimationURL().isEmpty(); } -EntityItem::SimulationState ModelEntityItem::getSimulationState() const { - EntityItem::SimulationState baseClassState = EntityItem::getSimulationState(); +EntityItem::SimulationState ModelEntityItem::computeSimulationState() const { + EntityItem::SimulationState baseClassState = EntityItem::computeSimulationState(); // if the base class is static, then consider our animation state, and upgrade to changing if // we are animating. If the base class has a higher simulation state than static, then // use the base class state. if (baseClassState == EntityItem::Static) { if (isAnimatingSomething()) { - return EntityItem::Changing; + return EntityItem::Moving; } } return baseClassState; diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index 97ffed4076..0b2508ec80 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 getSimulationState() const; + virtual SimulationState computeSimulationState() const; virtual void debugDump() const; From 47615ba9c221ea575eba6a3ee2bbff0703766963 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 17 Nov 2014 17:38:26 -0800 Subject: [PATCH 4/8] changeEntityState() --> updateEntityState() --- libraries/entities/src/EntityTree.cpp | 12 ++++++------ libraries/entities/src/EntityTree.h | 2 +- libraries/entities/src/EntityTreeElement.cpp | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 53d040c4a8..a0b2d6f9c3 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -89,7 +89,7 @@ void EntityTree::addEntityItem(EntityItem* entityItem) { recurseTreeWithOperator(&theOperator); // check to see if we need to simulate this entity.. - changeEntityState(entityItem); + updateEntityState(entityItem); _isDirty = true; } @@ -129,7 +129,7 @@ bool EntityTree::updateEntity(const EntityItemID& entityID, const EntityItemProp recurseTreeWithOperator(&theOperator); _isDirty = true; - changeEntityState(existingEntity); + updateEntityState(existingEntity); QString entityScriptAfter = existingEntity->getScript(); if (entityScriptBefore != entityScriptAfter) { @@ -575,7 +575,7 @@ void EntityTree::releaseSceneEncodeData(OctreeElementExtraEncodeData* extraEncod extraEncodeData->clear(); } -void EntityTree::changeEntityState(EntityItem* entity) { +void EntityTree::updateEntityState(EntityItem* entity) { EntityItem::SimulationState oldState = entity->getSimulationState(); EntityItem::SimulationState newState = entity->computeSimulationState(); if (newState != oldState) { @@ -661,7 +661,7 @@ void EntityTree::updateChangedEntities(quint64 now, QSet& entities entitiesToDelete << thisEntity->getEntityItemID(); clearEntityState(thisEntity); } else { - changeEntityState(thisEntity); + updateEntityState(thisEntity); } } _changedEntities.clear(); @@ -696,7 +696,7 @@ void EntityTree::updateMovingEntities(quint64 now, QSet& entitiesT clearEntityState(thisEntity); } else { moveOperator.addEntityToMoveList(thisEntity, oldCube, newCube); - changeEntityState(thisEntity); + updateEntityState(thisEntity); } } } @@ -720,7 +720,7 @@ void EntityTree::updateMortalEntities(quint64 now, QSet& entitiesT clearEntityState(thisEntity); } else { // check to see if this entity is no longer moving - changeEntityState(thisEntity); + updateEntityState(thisEntity); } } } diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index bf9da5a15d..7370ebadc6 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -137,7 +137,7 @@ public: void sendEntities(EntityEditPacketSender* packetSender, EntityTree* localTree, float x, float y, float z); - void changeEntityState(EntityItem* entity); + void updateEntityState(EntityItem* entity); void clearEntityState(EntityItem* entity); void entityChanged(EntityItem* entity); diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index a461df5fde..7d4d95bf94 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -767,7 +767,7 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int addEntityItem(entityItem); // add this new entity to this elements entities entityItemID = entityItem->getEntityItemID(); _myTree->setContainingElement(entityItemID, this); - _myTree->changeEntityState(entityItem); + _myTree->updateEntityState(entityItem); _myTree->emitAddingEntity(entityItemID); // we just added an entity } } From 35b5dd520edbbfc3da73da6239c6c6e73e650cc0 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Mon, 17 Nov 2014 20:12:58 -0800 Subject: [PATCH 5/8] added simulated holdingClickOnEntity in case of no mouse move --- interface/src/entities/EntityTreeRenderer.cpp | 32 ++++++++++++++++++- interface/src/entities/EntityTreeRenderer.h | 4 +++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/interface/src/entities/EntityTreeRenderer.cpp b/interface/src/entities/EntityTreeRenderer.cpp index f8fd9cf298..016b7b5363 100644 --- a/interface/src/entities/EntityTreeRenderer.cpp +++ b/interface/src/entities/EntityTreeRenderer.cpp @@ -46,7 +46,9 @@ QThread* EntityTreeRenderer::getMainThread() { EntityTreeRenderer::EntityTreeRenderer(bool wantScripts) : OctreeRenderer(), _wantScripts(wantScripts), - _entitiesScriptEngine(NULL) { + _entitiesScriptEngine(NULL), + _lastMouseEventValid(false) +{ REGISTER_ENTITY_TYPE_WITH_FACTORY(Model, RenderableModelEntityItem::factory) REGISTER_ENTITY_TYPE_WITH_FACTORY(Box, RenderableBoxEntityItem::factory) REGISTER_ENTITY_TYPE_WITH_FACTORY(Sphere, RenderableSphereEntityItem::factory) @@ -202,6 +204,19 @@ void EntityTreeRenderer::update() { // check to see if the avatar has moved and if we need to handle enter/leave entity logic checkEnterLeaveEntities(); + + // Even if we're not moving the mouse, if we started clicking on an entity and we have + // not yet released the hold then this is still considered a holdingClickOnEntity event + // and we want to simulate this message here as well as in mouse move + if (_lastMouseEventValid && !_currentClickingOnEntityID.isInvalidID()) { + emit holdingClickOnEntity(_currentClickingOnEntityID, _lastMouseEvent); + QScriptValueList currentClickingEntityArgs = createMouseEventArgs(_currentClickingOnEntityID, _lastMouseEvent); + QScriptValue currentClickingEntity = loadEntityScript(_currentClickingOnEntityID); + if (currentClickingEntity.property("holdingClickOnEntity").isValid()) { + currentClickingEntity.property("holdingClickOnEntity").call(currentClickingEntity, currentClickingEntityArgs); + } + } + } } @@ -667,6 +682,14 @@ QScriptValueList EntityTreeRenderer::createMouseEventArgs(const EntityItemID& en return args; } +QScriptValueList EntityTreeRenderer::createMouseEventArgs(const EntityItemID& entityID, const MouseEvent& mouseEvent) { + QScriptValueList args; + args << entityID.toScriptValue(_entitiesScriptEngine); + args << mouseEvent.toScriptValue(_entitiesScriptEngine); + return args; +} + + QScriptValueList EntityTreeRenderer::createEntityArgs(const EntityItemID& entityID) { QScriptValueList args; args << entityID.toScriptValue(_entitiesScriptEngine); @@ -693,6 +716,8 @@ void EntityTreeRenderer::mousePressEvent(QMouseEvent* event, unsigned int device entityScript.property("clickDownOnEntity").call(entityScript, entityScriptArgs); } } + _lastMouseEvent = MouseEvent(*event, deviceID); + _lastMouseEventValid = true; } void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID) { @@ -724,10 +749,13 @@ void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event, unsigned int devi // makes it the unknown ID, we just released so we can't be clicking on anything _currentClickingOnEntityID = EntityItemID::createInvalidEntityID(); + _lastMouseEvent = MouseEvent(*event, deviceID); + _lastMouseEventValid = true; } void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceID) { PerformanceTimer perfTimer("EntityTreeRenderer::mouseMoveEvent"); + PickRay ray = computePickRay(event->x(), event->y()); RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::TryLock); if (rayPickResult.intersects) { @@ -809,6 +837,8 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceI currentClickingEntity.property("holdingClickOnEntity").call(currentClickingEntity, currentClickingEntityArgs); } } + _lastMouseEvent = MouseEvent(*event, deviceID); + _lastMouseEventValid = true; } void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) { diff --git a/interface/src/entities/EntityTreeRenderer.h b/interface/src/entities/EntityTreeRenderer.h index c94eb78b77..7a8155cd6b 100644 --- a/interface/src/entities/EntityTreeRenderer.h +++ b/interface/src/entities/EntityTreeRenderer.h @@ -129,8 +129,12 @@ private: QScriptValue loadEntityScript(const EntityItemID& entityItemID); QString loadScriptContents(const QString& scriptMaybeURLorText); QScriptValueList createMouseEventArgs(const EntityItemID& entityID, QMouseEvent* event, unsigned int deviceID); + QScriptValueList createMouseEventArgs(const EntityItemID& entityID, const MouseEvent& mouseEvent); QHash _entityScripts; + + bool _lastMouseEventValid; + MouseEvent _lastMouseEvent; }; #endif // hifi_EntityTreeRenderer_h From bc4e7ab66289e7b397cc4852a48de30f00b2d448 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Mon, 17 Nov 2014 20:13:17 -0800 Subject: [PATCH 6/8] add rotate support to movable.js --- examples/entityScripts/movable.js | 224 +++++++++++++++++++++++++++++- 1 file changed, 219 insertions(+), 5 deletions(-) diff --git a/examples/entityScripts/movable.js b/examples/entityScripts/movable.js index 432a0be3f0..fc0e2f8a8f 100644 --- a/examples/entityScripts/movable.js +++ b/examples/entityScripts/movable.js @@ -13,6 +13,28 @@ this.entityID = null; this.properties = null; this.graboffset = null; + this.clickedAt = null; + this.firstHolding = true; + this.clickedX = -1; + this.clickedY = -1; + this.rotateOverlayTarget = null; + this.rotateOverlayInner = null; + this.rotateOverlayOuter = null; + this.rotateOverlayCurrent = null; + this.rotateMode = false; + this.originalRotation = null; + + var rotateOverlayTargetSize = 10000; // really big target + var innerSnapAngle = 22.5; // the angle which we snap to on the inner rotation tool + var innerRadius; + var outerRadius; + var yawCenter; + var yawZero; + var rotationNormal; + var yawNormal; + + var debug = true; + // Pr, Vr are respectively the Ray's Point of origin and Vector director // Pp, Np are respectively the Plane's Point of origin and Normal vector @@ -40,7 +62,6 @@ var intersection = this.rayPlaneIntersection(pickRay.origin, pickRay.direction, this.properties.position, upVector); this.graboffset = Vec3.subtract(this.properties.position, intersection); - this.updatePosition(mouseEvent); }; this.move = function(mouseEvent) { @@ -51,6 +72,56 @@ this.updatePosition(mouseEvent); }; + this.rotate = function(mouseEvent) { + var pickRay = Camera.computePickRay(mouseEvent.x, mouseEvent.y) + var result = Overlays.findRayIntersection(pickRay); + + if (result.intersects) { + var center = yawCenter; + var zero = yawZero; + var centerToZero = Vec3.subtract(center, zero); + var centerToIntersect = Vec3.subtract(center, result.intersection); + var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal); + + var distanceFromCenter = Vec3.distance(center, result.intersection); + var snapToInner = false; + // var innerRadius = (Vec3.length(selectionManager.worldDimensions) / 2) * 1.1; + if (distanceFromCenter < innerRadius) { + angleFromZero = Math.floor(angleFromZero/innerSnapAngle) * innerSnapAngle; + snapToInner = true; + } + + var yawChange = Quat.fromVec3Degrees({ x: 0, y: angleFromZero, z: 0 }); + Entities.editEntity(this.entityID, { rotation: Quat.multiply(yawChange, this.originalRotation) }); + + + // update the rotation display accordingly... + var startAtCurrent = 360-angleFromZero; + var endAtCurrent = 360; + var startAtRemainder = 0; + var endAtRemainder = 360-angleFromZero; + if (angleFromZero < 0) { + startAtCurrent = 0; + endAtCurrent = -angleFromZero; + startAtRemainder = -angleFromZero; + endAtRemainder = 360; + } + + if (snapToInner) { + Overlays.editOverlay(this.rotateOverlayOuter, { startAt: 0, endAt: 360 }); + Overlays.editOverlay(this.rotateOverlayInner, { startAt: startAtRemainder, endAt: endAtRemainder }); + Overlays.editOverlay(this.rotateOverlayCurrent, { startAt: startAtCurrent, endAt: endAtCurrent, size: innerRadius, + majorTickMarksAngle: innerSnapAngle, minorTickMarksAngle: 0, + majorTickMarksLength: -0.25, minorTickMarksLength: 0, }); + } else { + Overlays.editOverlay(this.rotateOverlayInner, { startAt: 0, endAt: 360 }); + Overlays.editOverlay(this.rotateOverlayOuter, { startAt: startAtRemainder, endAt: endAtRemainder }); + Overlays.editOverlay(this.rotateOverlayCurrent, { startAt: startAtCurrent, endAt: endAtCurrent, size: outerRadius, + majorTickMarksAngle: 45.0, minorTickMarksAngle: 5, + majorTickMarksLength: 0.25, minorTickMarksLength: 0.1, }); + } + } + }; // All callbacks start by updating the properties this.updateProperties = function(entityID) { if (this.entityID === null || !this.entityID.isKnownID) { @@ -58,7 +129,109 @@ } this.properties = Entities.getEntityProperties(this.entityID); }; - + + this.cleanupRotateOverlay = function() { + Overlays.deleteOverlay(this.rotateOverlayTarget); + Overlays.deleteOverlay(this.rotateOverlayInner); + Overlays.deleteOverlay(this.rotateOverlayOuter); + Overlays.deleteOverlay(this.rotateOverlayCurrent); + this.rotateOverlayTarget = null; + this.rotateOverlayInner = null; + this.rotateOverlayOuter = null; + this.rotateOverlayCurrent = null; + } + + this.displayRotateOverlay = function(mouseEvent) { + var yawOverlayAngles = { x: 90, y: 0, z: 0 }; + var yawOverlayRotation = Quat.fromVec3Degrees(yawOverlayAngles); + + yawNormal = { x: 0, y: 1, z: 0 }; + yawCenter = this.properties.position; + rotationNormal = yawNormal; + + // Size the overlays to the current selection size + var diagonal = (Vec3.length(this.properties.dimensions) / 2) * 1.1; + var halfDimensions = Vec3.multiply(this.properties.dimensions, 0.5); + innerRadius = diagonal; + outerRadius = diagonal * 1.15; + var innerAlpha = 0.2; + var outerAlpha = 0.2; + + this.rotateOverlayTarget = Overlays.addOverlay("circle3d", { + position: this.properties.position, + size: 10000, + color: { red: 0, green: 0, blue: 0 }, + alpha: 0.0, + solid: true, + visible: true, + rotation: yawOverlayRotation, + ignoreRayIntersection: false + }); + + this.rotateOverlayInner = Overlays.addOverlay("circle3d", { + position: this.properties.position, + size: innerRadius, + innerRadius: 0.9, + alpha: innerAlpha, + color: { red: 51, green: 152, blue: 203 }, + solid: true, + visible: true, + rotation: yawOverlayRotation, + hasTickMarks: true, + majorTickMarksAngle: innerSnapAngle, + minorTickMarksAngle: 0, + majorTickMarksLength: -0.25, + minorTickMarksLength: 0, + majorTickMarksColor: { red: 0, green: 0, blue: 0 }, + minorTickMarksColor: { red: 0, green: 0, blue: 0 }, + ignoreRayIntersection: true, // always ignore this + }); + + this.rotateOverlayOuter = Overlays.addOverlay("circle3d", { + position: this.properties.position, + size: outerRadius, + innerRadius: 0.9, + startAt: 0, + endAt: 360, + alpha: outerAlpha, + color: { red: 51, green: 152, blue: 203 }, + solid: true, + visible: true, + rotation: yawOverlayRotation, + + hasTickMarks: true, + majorTickMarksAngle: 45.0, + minorTickMarksAngle: 5, + majorTickMarksLength: 0.25, + minorTickMarksLength: 0.1, + majorTickMarksColor: { red: 0, green: 0, blue: 0 }, + minorTickMarksColor: { red: 0, green: 0, blue: 0 }, + ignoreRayIntersection: true, // always ignore this + }); + + this.rotateOverlayCurrent = Overlays.addOverlay("circle3d", { + position: this.properties.position, + size: outerRadius, + startAt: 0, + endAt: 0, + innerRadius: 0.9, + color: { red: 224, green: 67, blue: 36}, + alpha: 0.8, + solid: true, + visible: true, + rotation: yawOverlayRotation, + ignoreRayIntersection: true, // always ignore this + hasTickMarks: true, + majorTickMarksColor: { red: 0, green: 0, blue: 0 }, + minorTickMarksColor: { red: 0, green: 0, blue: 0 }, + }); + + var pickRay = Camera.computePickRay(mouseEvent.x, mouseEvent.y) + var result = Overlays.findRayIntersection(pickRay); + yawZero = result.intersection; + + }; + this.preload = function(entityID) { this.updateProperties(entityID); // All callbacks start by updating the properties }; @@ -66,15 +239,56 @@ this.clickDownOnEntity = function(entityID, mouseEvent) { this.updateProperties(entityID); // All callbacks start by updating the properties this.grab(mouseEvent); + + var d = new Date(); + this.clickedAt = d.getTime(); + this.firstHolding = true; + + this.clickedX = mouseEvent.x; + this.clickedY = mouseEvent.y; }; - + this.holdingClickOnEntity = function(entityID, mouseEvent) { + this.updateProperties(entityID); // All callbacks start by updating the properties - this.move(mouseEvent); + + if (this.firstHolding) { + // if we haven't moved yet... + if (this.clickedX == mouseEvent.x && this.clickedY == mouseEvent.y) { + var d = new Date(); + var now = d.getTime(); + + if (now - this.clickedAt > 500) { + this.displayRotateOverlay(mouseEvent); + this.firstHolding = false; + this.rotateMode = true; + this.originalRotation = this.properties.rotation; + } + } else { + this.firstHolding = false; + } + } + + if (this.rotateMode) { + this.rotate(mouseEvent); + } else { + this.move(mouseEvent); + } }; this.clickReleaseOnEntity = function(entityID, mouseEvent) { this.updateProperties(entityID); // All callbacks start by updating the properties - this.release(mouseEvent); + if (this.rotateMode) { + this.rotate(mouseEvent); + } else { + this.release(mouseEvent); + } + + if (this.rotateOverlayTarget != null) { + this.cleanupRotateOverlay(); + this.rotateMode = false; + } + + this.firstHolding = false; }; }) From 8f9ca32a2bb84de0427bb24450accc2bf58b853c Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Mon, 17 Nov 2014 22:13:49 -0800 Subject: [PATCH 7/8] added sounds to moving --- examples/entityScripts/movable.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/examples/entityScripts/movable.js b/examples/entityScripts/movable.js index fc0e2f8a8f..94ed7137fe 100644 --- a/examples/entityScripts/movable.js +++ b/examples/entityScripts/movable.js @@ -23,6 +23,8 @@ this.rotateOverlayCurrent = null; this.rotateMode = false; this.originalRotation = null; + this.sound = null; + this.injector = null; var rotateOverlayTargetSize = 10000; // really big target var innerSnapAngle = 22.5; // the angle which we snap to on the inner rotation tool @@ -34,7 +36,28 @@ var yawNormal; var debug = true; + + // Download sound if needed + this.maybeDownloadSound = function() { + if (this.sound === null) { + this.sound = SoundCache.getSound("http://public.highfidelity.io/sounds/Collisions-otherorganic/whoosh2.raw"); + } + } + // Play drag sound + this.playSound = function() { + this.stopSound(); + if (this.sound && this.sound.downloaded) { + this.injector = Audio.playSound(this.sound, { position: this.properties.position, loop: true, volume: 0.1 }); + } + } + // stop drag sound + this.stopSound = function() { + if (this.injector) { + Audio.stopInjector(this.injector); + this.injector = null; + } + } // Pr, Vr are respectively the Ray's Point of origin and Vector director // Pp, Np are respectively the Plane's Point of origin and Normal vector @@ -66,6 +89,9 @@ this.move = function(mouseEvent) { this.updatePosition(mouseEvent); + if (this.injector === null) { + this.playSound(); + } }; this.release = function(mouseEvent) { @@ -234,6 +260,7 @@ this.preload = function(entityID) { this.updateProperties(entityID); // All callbacks start by updating the properties + this.maybeDownloadSound(); }; this.clickDownOnEntity = function(entityID, mouseEvent) { @@ -289,6 +316,7 @@ } this.firstHolding = false; + this.stopSound(); }; }) From 27614092700e6039562afb4fd4639a413b3873bb Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 18 Nov 2014 08:55:01 -0800 Subject: [PATCH 8/8] fix build error on linux --- libraries/entities/src/EntityItem.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 61e929d9b3..d57f547cc5 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -269,7 +269,7 @@ public: SimulationState getSimulationState() const { return _simulationState; } protected: - friend EntityTree; + friend class EntityTree; void setSimulationState(SimulationState state) { _simulationState = state; } virtual void initFromEntityItemID(const EntityItemID& entityItemID); // maybe useful to allow subclasses to init