From 289dcb0e7f7603737c41471109a791438bf6e8bf Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 8 May 2015 15:39:45 -0700 Subject: [PATCH 01/12] only set ACTIVATE flag on non-zero velocity --- libraries/entities/src/EntityItem.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index ee7ca54a98..ed78a03083 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1185,16 +1185,16 @@ void EntityItem::updateVelocityInDomainUnits(const glm::vec3& value) { void EntityItem::updateVelocity(const glm::vec3& value) { auto delta = glm::distance(_velocity, value); if (delta > IGNORE_LINEAR_VELOCITY_DELTA) { + _dirtyFlags |= EntityItem::DIRTY_LINEAR_VELOCITY; const float MIN_LINEAR_SPEED = 0.001f; if (glm::length(value) < MIN_LINEAR_SPEED) { _velocity = ENTITY_ITEM_ZERO_VEC3; } else { _velocity = value; - } - _dirtyFlags |= EntityItem::DIRTY_LINEAR_VELOCITY; - - if (delta > ACTIVATION_LINEAR_VELOCITY_DELTA) { - _dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION; + // only activate when setting non-zero velocity + if (delta > ACTIVATION_LINEAR_VELOCITY_DELTA) { + _dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION; + } } } } @@ -1232,9 +1232,10 @@ void EntityItem::updateAngularVelocity(const glm::vec3& value) { _angularVelocity = ENTITY_ITEM_ZERO_VEC3; } else { _angularVelocity = value; - } - if (delta > ACTIVATION_ANGULAR_VELOCITY_DELTA) { - _dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION; + // only activate when setting non-zero velocity + if (delta > ACTIVATION_ANGULAR_VELOCITY_DELTA) { + _dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION; + } } } } From e89471e7d6c0ac21e9dfed7ea86f6d981cc99292 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 8 May 2015 15:41:00 -0700 Subject: [PATCH 02/12] enable non-moving update when obj is active --- libraries/physics/src/EntityMotionState.cpp | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index b5d14e4814..e5d8c9dc81 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -205,18 +205,16 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { _lastStep = simulationStep; bool isActive = _body->isActive(); - if (!isActive) { - if (_sentMoving) { - // this object just went inactive so send an update immediately + const float NON_MOVING_UPDATE_PERIOD = 1.0f; + if (_sentMoving) { + if (!isActive) { + // object has gone inactive but our last send was moving --> send non-moving update immediately return true; - } else { - const float NON_MOVING_UPDATE_PERIOD = 1.0f; - if (dt > NON_MOVING_UPDATE_PERIOD && _numNonMovingUpdates < MAX_NUM_NON_MOVING_UPDATES) { - // RELIABLE_SEND_HACK: since we're not yet using a reliable method for non-moving update packets we repeat these - // at a faster rate than the MAX period above, and only send a limited number of them. - return true; - } } + } else if (dt > NON_MOVING_UPDATE_PERIOD && _numNonMovingUpdates < MAX_NUM_NON_MOVING_UPDATES) { + // RELIABLE_SEND_HACK: since we're not yet using a reliable method for non-moving update packets + // we repeat these at the the MAX period above, and only send a limited number of them. + return true; } // Else we measure the error between current and extrapolated transform (according to expected behavior From 2165d18c052ebe0410d8303702a1b70deb5fb207 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 12 May 2015 21:45:36 -0700 Subject: [PATCH 03/12] don't copy simulatorID from script value --- libraries/entities/src/EntityItemProperties.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 0b3472fc09..09d8fca7e6 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -505,7 +505,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object) { COPY_PROPERTY_FROM_QSCRIPTVALUE_BOOL(locked, setLocked); COPY_PROPERTY_FROM_QSCRIPTVALUE_STRING(textures, setTextures); COPY_PROPERTY_FROM_QSCRIPTVALUE_STRING(userData, setUserData); - COPY_PROPERTY_FROM_QSCRIPTVALUE_UUID(simulatorID, setSimulatorID); + //COPY_PROPERTY_FROM_QSCRIPTVALUE_UUID(simulatorID, setSimulatorID); // DO NOT accept this info from QScriptValue COPY_PROPERTY_FROM_QSCRIPTVALUE_STRING(text, setText); COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(lineHeight, setLineHeight); COPY_PROPERTY_FROM_QSCRIPTVALUE_COLOR(textColor, setTextColor); From 45d4fa91abf09783e20aa57fedf8a5415f24ab6e Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 12 May 2015 21:46:04 -0700 Subject: [PATCH 04/12] add a TODO comment --- libraries/entities/src/SimpleEntitySimulation.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/entities/src/SimpleEntitySimulation.cpp b/libraries/entities/src/SimpleEntitySimulation.cpp index 518d10d056..a399bf5d75 100644 --- a/libraries/entities/src/SimpleEntitySimulation.cpp +++ b/libraries/entities/src/SimpleEntitySimulation.cpp @@ -32,6 +32,7 @@ void SimpleEntitySimulation::updateEntitiesInternal(const quint64& now) { SharedNodePointer ownerNode = nodeList->nodeWithUUID(entity->getSimulatorID()); if (ownerNode.isNull() || !ownerNode->isAlive()) { qCDebug(entities) << "auto-removing simulation owner" << entity->getSimulatorID(); + // TODO: zero velocities when we clear simulatorID? entity->setSimulatorID(QUuid()); itemItr = _hasSimulationOwnerEntities.erase(itemItr); } else { From 4872a565c93d0d11082f4fb7944d93c389ee049d Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 12 May 2015 21:46:52 -0700 Subject: [PATCH 05/12] bid for simulation ownership --- libraries/entities/src/EntityItem.cpp | 10 +- libraries/entities/src/EntityItem.h | 2 + .../entities/src/EntityScriptingInterface.cpp | 27 ++-- libraries/entities/src/EntitySimulation.h | 3 +- libraries/entities/src/EntityTree.cpp | 63 ++++++-- libraries/entities/src/EntityTree.h | 7 +- libraries/physics/src/EntityMotionState.cpp | 150 ++++++++++-------- libraries/physics/src/EntityMotionState.h | 15 +- .../physics/src/PhysicalEntitySimulation.cpp | 18 ++- libraries/physics/src/PhysicsEngine.cpp | 2 + tests/octree/src/ModelTests.cpp | 4 +- 11 files changed, 185 insertions(+), 116 deletions(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index ed78a03083..36b78d9a41 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -565,7 +565,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef READ_ENTITY_PROPERTY_STRING(PROP_USER_DATA, setUserData); if (args.bitstreamVersion >= VERSION_ENTITIES_HAVE_ACCELERATION) { - READ_ENTITY_PROPERTY_UUID(PROP_SIMULATOR_ID, setSimulatorID); + READ_ENTITY_PROPERTY_UUID(PROP_SIMULATOR_ID, updateSimulatorID); } if (args.bitstreamVersion >= VERSION_ENTITIES_HAS_MARKETPLACE_ID) { @@ -940,7 +940,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(collisionsWillMove, updateCollisionsWillMove); SET_ENTITY_PROPERTY_FROM_PROPERTIES(locked, setLocked); SET_ENTITY_PROPERTY_FROM_PROPERTIES(userData, setUserData); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(simulatorID, setSimulatorID); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(simulatorID, updateSimulatorID); SET_ENTITY_PROPERTY_FROM_PROPERTIES(marketplaceID, setMarketplaceID); SET_ENTITY_PROPERTY_FROM_PROPERTIES(name, setName); @@ -1270,8 +1270,14 @@ void EntityItem::updateLifetime(float value) { } void EntityItem::setSimulatorID(const QUuid& value) { + _simulatorID = value; + _simulatorIDChangedTime = usecTimestampNow(); +} + +void EntityItem::updateSimulatorID(const QUuid& value) { if (_simulatorID != value) { _simulatorID = value; _simulatorIDChangedTime = usecTimestampNow(); + _dirtyFlags |= EntityItem::DIRTY_SIMULATOR_ID; } } diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index d9096bf429..4ae2178f14 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -82,6 +82,7 @@ public: DIRTY_UPDATEABLE = 0x0200, DIRTY_MATERIAL = 0x00400, DIRTY_PHYSICS_ACTIVATION = 0x0800, // we want to activate the object + DIRTY_SIMULATOR_ID = 0x1000, DIRTY_TRANSFORM = DIRTY_POSITION | DIRTY_ROTATION, DIRTY_VELOCITIES = DIRTY_LINEAR_VELOCITY | DIRTY_ANGULAR_VELOCITY }; @@ -284,6 +285,7 @@ public: QUuid getSimulatorID() const { return _simulatorID; } void setSimulatorID(const QUuid& value); + void updateSimulatorID(const QUuid& value); quint64 getSimulatorIDChangedTime() const { return _simulatorIDChangedTime; } const QString& getMarketplaceID() const { return _marketplaceID; } diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 46ca70aa43..62e61dc039 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -61,13 +61,12 @@ void EntityScriptingInterface::setEntityTree(EntityTree* modelTree) { } } - - -void setSimId(EntityItemProperties& propertiesWithSimID, EntityItem* entity) { +void bidForSimulationOwnership(EntityItemProperties& properties) { + // We make a bid for simulation ownership by declaring our sessionID as simulation owner + // in the outgoing properties. The EntityServer may accept the bid or might not. auto nodeList = DependencyManager::get(); const QUuid myNodeID = nodeList->getSessionUUID(); - propertiesWithSimID.setSimulatorID(myNodeID); - entity->setSimulatorID(myNodeID); + properties.setSimulatorID(myNodeID); } @@ -89,7 +88,7 @@ EntityItemID EntityScriptingInterface::addEntity(const EntityItemProperties& pro entity->setLastBroadcast(usecTimestampNow()); if (entity) { // This Node is creating a new object. If it's in motion, set this Node as the simulator. - setSimId(propertiesWithSimID, entity); + bidForSimulationOwnership(propertiesWithSimID); } else { qCDebug(entities) << "script failed to add new Entity to local Octree"; success = false; @@ -163,29 +162,31 @@ EntityItemID EntityScriptingInterface::editEntity(EntityItemID entityID, const E } } - EntityItemProperties propertiesWithSimID = properties; - // If we have a local entity tree set, then also update it. We can do this even if we don't know // the actual id, because we can edit out local entities just with creatorTokenID if (_entityTree) { _entityTree->lockForWrite(); - _entityTree->updateEntity(entityID, propertiesWithSimID, canAdjustLocks()); + _entityTree->updateEntity(entityID, properties); _entityTree->unlock(); } // if at this point, we know the id, send the update to the entity server if (entityID.isKnownID) { // make sure the properties has a type, so that the encode can know which properties to include - if (propertiesWithSimID.getType() == EntityTypes::Unknown) { + if (properties.getType() == EntityTypes::Unknown) { EntityItem* entity = _entityTree->findEntityByEntityItemID(entityID); if (entity) { + // we need to change the outgoing properties, so we make a copy, modify, and send. + EntityItemProperties modifiedProperties = properties; entity->setLastBroadcast(usecTimestampNow()); - propertiesWithSimID.setType(entity->getType()); - setSimId(propertiesWithSimID, entity); + modifiedProperties.setType(entity->getType()); + bidForSimulationOwnership(modifiedProperties); + queueEntityMessage(PacketTypeEntityAddOrEdit, entityID, modifiedProperties); + return entityID; } } - queueEntityMessage(PacketTypeEntityAddOrEdit, entityID, propertiesWithSimID); + queueEntityMessage(PacketTypeEntityAddOrEdit, entityID, properties); } return entityID; diff --git a/libraries/entities/src/EntitySimulation.h b/libraries/entities/src/EntitySimulation.h index a73afe9fd7..f5a100eba0 100644 --- a/libraries/entities/src/EntitySimulation.h +++ b/libraries/entities/src/EntitySimulation.h @@ -37,7 +37,8 @@ const int DIRTY_SIMULATION_FLAGS = EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_LIFETIME | EntityItem::DIRTY_UPDATEABLE | - EntityItem::DIRTY_MATERIAL; + EntityItem::DIRTY_MATERIAL | + EntityItem::DIRTY_SIMULATOR_ID; class EntitySimulation : public QObject { Q_OBJECT diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 5aea021e7a..306fbeb413 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -86,7 +86,7 @@ void EntityTree::postAddEntity(EntityItem* entity) { emit addingEntity(entity->getEntityItemID()); } -bool EntityTree::updateEntity(const EntityItemID& entityID, const EntityItemProperties& properties, bool allowLockChange) { +bool EntityTree::updateEntity(const EntityItemID& entityID, const EntityItemProperties& properties, const SharedNodePointer& senderNode) { EntityTreeElement* containingElement = getContainingElement(entityID); if (!containingElement) { qCDebug(entities) << "UNEXPECTED!!!! EntityTree::updateEntity() entityID doesn't exist!!! entityID=" << entityID; @@ -99,22 +99,34 @@ bool EntityTree::updateEntity(const EntityItemID& entityID, const EntityItemProp return false; } - return updateEntityWithElement(existingEntity, properties, containingElement, allowLockChange); + return updateEntityWithElement(existingEntity, properties, containingElement, senderNode); } -bool EntityTree::updateEntity(EntityItem* entity, const EntityItemProperties& properties, bool allowLockChange) { +bool EntityTree::updateEntity(EntityItem* entity, const EntityItemProperties& properties, const SharedNodePointer& senderNode) { EntityTreeElement* containingElement = getContainingElement(entity->getEntityItemID()); if (!containingElement) { qCDebug(entities) << "UNEXPECTED!!!! EntityTree::updateEntity() entity-->element lookup failed!!! entityID=" << entity->getEntityItemID(); return false; } - return updateEntityWithElement(entity, properties, containingElement, allowLockChange); + return updateEntityWithElement(entity, properties, containingElement, senderNode); } bool EntityTree::updateEntityWithElement(EntityItem* entity, const EntityItemProperties& origProperties, - EntityTreeElement* containingElement, bool allowLockChange) { + EntityTreeElement* containingElement, const SharedNodePointer& senderNode) { EntityItemProperties properties = origProperties; + + bool allowLockChange; + QUuid senderID; + if (senderNode.isNull()) { + auto nodeList = DependencyManager::get(); + allowLockChange = nodeList->getThisNodeCanAdjustLocks(); + senderID = nodeList->getSessionUUID(); + } else { + allowLockChange = senderNode->getCanAdjustLocks(); + senderID = senderNode->getUUID(); + } + if (!allowLockChange && (entity->getLocked() != properties.getLocked())) { qCDebug(entities) << "Refusing disallowed lock adjustment."; return false; @@ -134,22 +146,41 @@ bool EntityTree::updateEntityWithElement(EntityItem* entity, const EntityItemPro } } } else { - if (properties.simulatorIDChanged() && - !entity->getSimulatorID().isNull() && - properties.getSimulatorID() != entity->getSimulatorID()) { - // A Node is trying to take ownership of the simulation of this entity from another Node. Only allow this - // if ownership hasn't recently changed. - if (usecTimestampNow() - entity->getSimulatorIDChangedTime() < SIMULATOR_CHANGE_LOCKOUT_PERIOD) { - qCDebug(entities) << "simulator_change_lockout_period:" - << entity->getSimulatorID() << "to" << properties.getSimulatorID(); + if (getIsServer()) { + bool simulationBlocked = !entity->getSimulatorID().isNull(); + if (properties.simulatorIDChanged()) { + QUuid submittedID = properties.getSimulatorID(); + // a legit interface will only submit their own ID or NULL: + if (submittedID.isNull()) { + if (entity->getSimulatorID() == senderID) { + // We only allow the simulation owner to clear their own simulationID's. + simulationBlocked = false; + } + // else: We assume the sender really did believe it was the simulation owner when it sent + } else if (submittedID == senderID) { + // the sender is trying to take or continue ownership + if (entity->getSimulatorID().isNull() || entity->getSimulatorID() == senderID) { + simulationBlocked = false; + } else { + // the sender is trying to steal ownership from another simulator + // so we apply the ownership change filter + if (usecTimestampNow() - entity->getSimulatorIDChangedTime() > SIMULATOR_CHANGE_LOCKOUT_PERIOD) { + simulationBlocked = false; + } + } + } else { + // the entire update is suspect --> ignore it + return false; + } + } + if (simulationBlocked) { // squash the physics-related changes. properties.setSimulatorIDChanged(false); properties.setPositionChanged(false); properties.setRotationChanged(false); - } else { - qCDebug(entities) << "allowing simulatorID change"; } } + // else client accepts what the server says QString entityScriptBefore = entity->getScript(); uint32_t preFlags = entity->getDirtyFlags(); @@ -664,7 +695,7 @@ int EntityTree::processEditPacketData(PacketType packetType, const unsigned char qCDebug(entities) << "User [" << senderNode->getUUID() << "] editing entity. ID:" << entityItemID; qCDebug(entities) << " properties:" << properties; } - updateEntity(entityItemID, properties, senderNode->getCanAdjustLocks()); + updateEntity(entityItemID, properties, senderNode); existingEntity->markAsChangedOnServer(); } else { qCDebug(entities) << "User attempted to edit an unknown entity. ID:" << entityItemID; diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index f99160f4ed..c0cc81b3af 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -88,10 +88,10 @@ public: 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, bool allowLockChange); + bool updateEntity(const EntityItemID& entityID, const EntityItemProperties& properties, const SharedNodePointer& senderNode = SharedNodePointer(nullptr)); // use this method if you have a pointer to the entity (avoid an extra entity lookup) - bool updateEntity(EntityItem* entity, const EntityItemProperties& properties, bool allowLockChange); + bool updateEntity(EntityItem* entity, const EntityItemProperties& properties, const SharedNodePointer& senderNode = SharedNodePointer(nullptr)); void deleteEntity(const EntityItemID& entityID, bool force = false, bool ignoreWarnings = false); void deleteEntities(QSet entityIDs, bool force = false, bool ignoreWarnings = false); @@ -178,7 +178,8 @@ private: void processRemovedEntities(const DeleteEntityOperator& theOperator); bool updateEntityWithElement(EntityItem* entity, const EntityItemProperties& properties, - EntityTreeElement* containingElement, bool allowLockChange); + EntityTreeElement* containingElement, + const SharedNodePointer& senderNode = SharedNodePointer(nullptr)); static bool findNearPointOperation(OctreeElement* element, void* extraData); static bool findInSphereOperation(OctreeElement* element, void* extraData); static bool findInCubeOperation(OctreeElement* element, void* extraData); diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index e5d8c9dc81..4ac66678e7 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -35,8 +35,9 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItem* entity _serverGravity(0.0f), _serverAcceleration(0.0f), _accelerationNearlyGravityCount(0), - _shouldClaimSimulationOwnership(false), - _movingStepsWithoutSimulationOwner(0) + _touchesOurSimulation(false), + _framesSinceSimulatorBid(0), + _movingFramesWithoutSimulationOwner(0) { _type = MOTION_STATE_TYPE_ENTITY; assert(entity != nullptr); @@ -60,6 +61,15 @@ void EntityMotionState::updateServerPhysicsVariables(uint32_t flags) { if (flags & EntityItem::DIRTY_ANGULAR_VELOCITY) { _serverAngularVelocity = _entity->getAngularVelocity(); } + if (flags & EntityItem::DIRTY_SIMULATOR_ID) { + auto nodeList = DependencyManager::get(); + const QUuid& sessionID = nodeList->getSessionUUID(); + if (_entity->getSimulatorID() != sessionID) { + _touchesOurSimulation = false; + _movingFramesWithoutSimulationOwner = 0; + _framesSinceSimulatorBid = 0; + } + } } // virtual @@ -143,19 +153,16 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) { _entity->setLastSimulated(usecTimestampNow()); - // if (_entity->getSimulatorID().isNull() && isMoving()) { - if (_entity->getSimulatorID().isNull() && isMovingVsServer()) { - // if object is moving and has no owner, attempt to claim simulation ownership. - _movingStepsWithoutSimulationOwner++; + if (_entity->getSimulatorID().isNull()) { + _movingFramesWithoutSimulationOwner++; + + const uint32_t ownershipClaimDelay = 50; // TODO -- how to pick this? based on meters from our characterController? + if (_movingFramesWithoutSimulationOwner > ownershipClaimDelay) { + //qDebug() << "Warning -- claiming something I saw moving." << getName(); + _touchesOurSimulation = true; + } } else { - _movingStepsWithoutSimulationOwner = 0; - } - - uint32_t ownershipClaimDelay = 50; // TODO -- how to pick this? based on meters from our characterController? - - if (_movingStepsWithoutSimulationOwner > ownershipClaimDelay) { - //qDebug() << "Warning -- claiming something I saw moving." << getName(); - setShouldClaimSimulationOwnership(true); + _movingFramesWithoutSimulationOwner = 0; } #ifdef WANT_DEBUG @@ -177,8 +184,12 @@ void EntityMotionState::computeObjectShapeInfo(ShapeInfo& shapeInfo) { // we alwasy resend packets for objects that have stopped moving up to some max limit. const int MAX_NUM_NON_MOVING_UPDATES = 5; -bool EntityMotionState::doesNotNeedToSendUpdate() const { - return !_body || (_body->isActive() && _numNonMovingUpdates > MAX_NUM_NON_MOVING_UPDATES); +bool EntityMotionState::doesNotNeedToSendUpdate(const QUuid& sessionID) const { + if (!_body || !_entity) { + return true; + } + + return (sessionID != _entity->getSimulatorID() && !_touchesOurSimulation); } bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { @@ -191,6 +202,7 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { _serverVelocity = bulletToGLM(_body->getLinearVelocity()); _serverAngularVelocity = bulletToGLM(_body->getAngularVelocity()); _lastStep = simulationStep; + _sentMoving = false; return false; } @@ -202,35 +214,37 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { int numSteps = simulationStep - _lastStep; float dt = (float)(numSteps) * PHYSICS_ENGINE_FIXED_SUBSTEP; - _lastStep = simulationStep; - bool isActive = _body->isActive(); const float NON_MOVING_UPDATE_PERIOD = 1.0f; - if (_sentMoving) { - if (!isActive) { - // object has gone inactive but our last send was moving --> send non-moving update immediately - return true; - } - } else if (dt > NON_MOVING_UPDATE_PERIOD && _numNonMovingUpdates < MAX_NUM_NON_MOVING_UPDATES) { - // RELIABLE_SEND_HACK: since we're not yet using a reliable method for non-moving update packets - // we repeat these at the the MAX period above, and only send a limited number of them. + if (!_sentMoving) { + // we resend non-moving update every NON_MOVING_UPDATE_PERIOD + // until it is removed from the outgoing updates + // (which happens when we don't own the simulation and it isn't touching our simulation) + return (dt > NON_MOVING_UPDATE_PERIOD); + } + + bool isActive = _body->isActive(); + if (!isActive) { + // object has gone inactive but our last send was moving --> send non-moving update immediately return true; } + _lastStep = simulationStep; + if (glm::length2(_serverVelocity) > 0.0f) { + _serverVelocity += _serverAcceleration * dt; + _serverVelocity *= powf(1.0f - _body->getLinearDamping(), dt); + _serverPosition += dt * _serverVelocity; + } + // Else we measure the error between current and extrapolated transform (according to expected behavior // of remote EntitySimulation) and return true if the error is significant. // NOTE: math is done in the simulation-frame, which is NOT necessarily the same as the world-frame // due to _worldOffset. + // TODO: compensate for _worldOffset offset here // compute position error - if (glm::length2(_serverVelocity) > 0.0f) { - _serverVelocity += _serverAcceleration * dt; - _serverVelocity *= powf(1.0f - _body->getLinearDamping(), dt); - _serverPosition += dt * _serverVelocity; - } - // TODO: compensate for simulation offset here btTransform worldTrans = _body->getWorldTransform(); glm::vec3 position = bulletToGLM(worldTrans.getOrigin()); @@ -283,31 +297,36 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { return (fabsf(glm::dot(actualRotation, _serverRotation)) < MIN_ROTATION_DOT); } -bool EntityMotionState::shouldSendUpdate(uint32_t simulationFrame) { - if (!_entity || !remoteSimulationOutOfSync(simulationFrame)) { +bool EntityMotionState::shouldSendUpdate(uint32_t simulationFrame, const QUuid& sessionID) { + // NOTE: we expect _entity and _body to be valid in this context, since shouldSendUpdate() is only called + // after doesNotNeedToSendUpdate() returns false and that call should return 'true' if _entity or _body are NULL. + assert(_entity); + assert(_body); + + if (!remoteSimulationOutOfSync(simulationFrame)) { return false; } - if (getShouldClaimSimulationOwnership()) { + if (_entity->getSimulatorID() == sessionID) { + // we own the simulation return true; } - auto nodeList = DependencyManager::get(); - const QUuid& myNodeID = nodeList->getSessionUUID(); - const QUuid& simulatorID = _entity->getSimulatorID(); - - if (simulatorID != myNodeID) { - // some other Node owns the simulating of this, so don't broadcast the results of local simulation. - return false; + const uint32_t FRAMES_BETWEEN_OWNERSHIP_CLAIMS = 30; + if (_touchesOurSimulation) { + ++_framesSinceSimulatorBid; + if (_framesSinceSimulatorBid > FRAMES_BETWEEN_OWNERSHIP_CLAIMS) { + // we don't own the simulation, but it's time to bid for it + return true; + } } - return true; + return false; } -void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step) { - if (!_entity || !_entity->isKnownID()) { - return; // never update entities that are unknown - } +void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const QUuid& sessionID, uint32_t step) { + assert(_entity); + assert(_entity->isKnownID()); bool active = _body->isActive(); if (!active) { @@ -350,11 +369,11 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ _serverAcceleration = _entity->getAcceleration(); _serverAngularVelocity = _entity->getAngularVelocity(); - _sentMoving = _serverVelocity != glm::vec3(0.0f) || _serverAngularVelocity != glm::vec3(0.0f); + _sentMoving = _serverVelocity != glm::vec3(0.0f) || _serverAngularVelocity != _serverVelocity || _serverAcceleration != _serverVelocity; EntityItemProperties properties = _entity->getProperties(); - // explicitly set the properties that changed + // explicitly set the properties that changed so that they will be packed properties.setPosition(_serverPosition); properties.setRotation(_serverRotation); properties.setVelocity(_serverVelocity); @@ -386,23 +405,20 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ properties.setLastEdited(_entity->getLastEdited()); } - auto nodeList = DependencyManager::get(); - QUuid myNodeID = nodeList->getSessionUUID(); - QUuid simulatorID = _entity->getSimulatorID(); - - if (getShouldClaimSimulationOwnership()) { - // we think we should own it, so we tell the server that we do, - // but we don't remember that we own it... - // instead we expect the sever to tell us later whose ownership it has accepted - properties.setSimulatorID(myNodeID); - setShouldClaimSimulationOwnership(false); - } else if (simulatorID == myNodeID - && !_sentMoving - && _numNonMovingUpdates == MAX_NUM_NON_MOVING_UPDATES) { - // we own it, the entity has stopped, and we're sending the last non-moving update - // --> give up ownership - _entity->setSimulatorID(QUuid()); - properties.setSimulatorID(QUuid()); + if (sessionID == _entity->getSimulatorID()) { + // we think we own the simulation + if (!_sentMoving) { + // we own the simulation but the entity has stopped, so we tell the server that we're clearing simulatorID + // but we remember that we do still own it... and rely on the server to tell us that we don't + properties.setSimulatorID(QUuid()); + _touchesOurSimulation = false; + } else { + // explicitly set the property's simulatorID so that it is flagged as changed and will be packed + properties.setSimulatorID(sessionID); + } + } else { + // we don't own the simulation for this entity yet, but we're sending a bid for it + properties.setSimulatorID(sessionID); } if (EntityItem::getSendPhysicsUpdates()) { @@ -451,7 +467,7 @@ QUuid EntityMotionState::getSimulatorID() const { // virtual void EntityMotionState::bump() { - setShouldClaimSimulationOwnership(true); + _touchesOurSimulation = true; } void EntityMotionState::resetMeasuredBodyAcceleration() { diff --git a/libraries/physics/src/EntityMotionState.h b/libraries/physics/src/EntityMotionState.h index 6028662aa0..afb7d257a6 100644 --- a/libraries/physics/src/EntityMotionState.h +++ b/libraries/physics/src/EntityMotionState.h @@ -46,13 +46,11 @@ public: virtual void computeObjectShapeInfo(ShapeInfo& shapeInfo); - bool doesNotNeedToSendUpdate() const; + // TODO: Andrew to rename doesNotNeedToSendUpdate() + bool doesNotNeedToSendUpdate(const QUuid& sessionID) const; bool remoteSimulationOutOfSync(uint32_t simulationStep); - bool shouldSendUpdate(uint32_t simulationFrame); - void sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step); - - void setShouldClaimSimulationOwnership(bool value) { _shouldClaimSimulationOwnership = value; } - bool getShouldClaimSimulationOwnership() { return _shouldClaimSimulationOwnership; } + bool shouldSendUpdate(uint32_t simulationFrame, const QUuid& sessionID); + void sendUpdate(OctreeEditPacketSender* packetSender, const QUuid& sessionID, uint32_t step); virtual uint32_t getAndClearIncomingDirtyFlags() const; @@ -109,8 +107,9 @@ protected: glm::vec3 _measuredAcceleration; quint8 _accelerationNearlyGravityCount; - bool _shouldClaimSimulationOwnership; - quint32 _movingStepsWithoutSimulationOwner; + bool _touchesOurSimulation; + uint32_t _framesSinceOwnershipBid; + uint32_t _movingFramesWithoutSimulationOwner; }; #endif // hifi_EntityMotionState_h diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index ed7b986800..f1be0c2145 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -195,12 +195,22 @@ void PhysicalEntitySimulation::handleOutgoingChanges(VectorOfMotionStates& motio EntityMotionState* entityState = static_cast(state); EntityItem* entity = entityState->getEntity(); if (entity) { - _outgoingChanges.insert(entityState); + if (entity->isKnownID()) { + _outgoingChanges.insert(entityState); + } _entitiesToSort.insert(entityState->getEntity()); } } } + auto nodeList = DependencyManager::get(); + const QUuid& sessionID = nodeList->getSessionUUID(); + if (sessionID.isNull()) { + // no updates to send + _outgoingChanges.clear(); + return; + } + // send outgoing packets uint32_t numSubsteps = _physicsEngine->getNumSubsteps(); if (_lastStepSendPackets != numSubsteps) { @@ -209,10 +219,10 @@ void PhysicalEntitySimulation::handleOutgoingChanges(VectorOfMotionStates& motio QSet::iterator stateItr = _outgoingChanges.begin(); while (stateItr != _outgoingChanges.end()) { EntityMotionState* state = *stateItr; - if (state->doesNotNeedToSendUpdate()) { + if (state->doesNotNeedToSendUpdate(sessionID)) { stateItr = _outgoingChanges.erase(stateItr); - } else if (state->shouldSendUpdate(numSubsteps)) { - state->sendUpdate(_entityPacketSender, numSubsteps); + } else if (state->shouldSendUpdate(numSubsteps, sessionID)) { + state->sendUpdate(_entityPacketSender, sessionID, numSubsteps); ++stateItr; } else { ++stateItr; diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index ea74a87286..bfd0b6cb28 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -147,6 +147,7 @@ void PhysicsEngine::deleteObjects(VectorOfMotionStates& objects) { // NOTE: setRigidBody() modifies body->m_userPointer so we should clear the MotionState's body BEFORE deleting it. btRigidBody* body = object->getRigidBody(); object->setRigidBody(nullptr); + body->setMotionState(nullptr); delete body; object->releaseShape(); delete object; @@ -161,6 +162,7 @@ void PhysicsEngine::deleteObjects(SetOfMotionStates& objects) { // NOTE: setRigidBody() modifies body->m_userPointer so we should clear the MotionState's body BEFORE deleting it. object->setRigidBody(nullptr); + body->setMotionState(nullptr); delete body; object->releaseShape(); delete object; diff --git a/tests/octree/src/ModelTests.cpp b/tests/octree/src/ModelTests.cpp index e4309100af..2e2b873115 100644 --- a/tests/octree/src/ModelTests.cpp +++ b/tests/octree/src/ModelTests.cpp @@ -103,7 +103,7 @@ void EntityTests::entityTreeTests(bool verbose) { properties.setPosition(newPosition); - tree.updateEntity(entityID, properties, true); + tree.updateEntity(entityID, properties); float targetRadius = oneMeter * 2.0f; const EntityItem* foundEntityByRadius = tree.findClosestEntity(positionNearOrigin, targetRadius); @@ -143,7 +143,7 @@ void EntityTests::entityTreeTests(bool verbose) { properties.setPosition(newPosition); - tree.updateEntity(entityID, properties, true); + tree.updateEntity(entityID, properties); float targetRadius = oneMeter * 2.0f; const EntityItem* foundEntityByRadius = tree.findClosestEntity(positionAtCenter, targetRadius); From fb34a5ba842a69495d6f0367349c3d32597ec4c4 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 13 May 2015 08:44:10 -0700 Subject: [PATCH 06/12] renaming and cleanup --- interface/src/Application.cpp | 2 +- libraries/physics/src/EntityMotionState.cpp | 43 +++++++++---------- libraries/physics/src/EntityMotionState.h | 11 +++-- .../physics/src/PhysicalEntitySimulation.cpp | 22 +++++----- .../physics/src/PhysicalEntitySimulation.h | 2 +- libraries/physics/src/PhysicsEngine.h | 1 + 6 files changed, 39 insertions(+), 42 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 89df058e8d..8fd6380f1d 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2451,7 +2451,7 @@ void Application::update(float deltaTime) { if (_physicsEngine.hasOutgoingChanges()) { _entitySimulation.lock(); - _entitySimulation.handleOutgoingChanges(_physicsEngine.getOutgoingChanges()); + _entitySimulation.handleOutgoingChanges(_physicsEngine.getOutgoingChanges(), _physicsEngine.getSessionID()); _entitySimulation.handleCollisionEvents(_physicsEngine.getCollisionEvents()); _entitySimulation.unlock(); _physicsEngine.dumpStatsIfNecessary(); diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 4ac66678e7..759ce68e16 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -35,9 +35,9 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItem* entity _serverGravity(0.0f), _serverAcceleration(0.0f), _accelerationNearlyGravityCount(0), - _touchesOurSimulation(false), - _framesSinceSimulatorBid(0), - _movingFramesWithoutSimulationOwner(0) + _candidateForOwnership(false), + _loopsSinceOwnershipBid(0), + _loopsWithoutOwner(0) { _type = MOTION_STATE_TYPE_ENTITY; assert(entity != nullptr); @@ -65,9 +65,9 @@ void EntityMotionState::updateServerPhysicsVariables(uint32_t flags) { auto nodeList = DependencyManager::get(); const QUuid& sessionID = nodeList->getSessionUUID(); if (_entity->getSimulatorID() != sessionID) { - _touchesOurSimulation = false; - _movingFramesWithoutSimulationOwner = 0; - _framesSinceSimulatorBid = 0; + _candidateForOwnership = false; + _loopsWithoutOwner = 0; + _loopsSinceOwnershipBid = 0; } } } @@ -154,15 +154,15 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) { _entity->setLastSimulated(usecTimestampNow()); if (_entity->getSimulatorID().isNull()) { - _movingFramesWithoutSimulationOwner++; + _loopsWithoutOwner++; - const uint32_t ownershipClaimDelay = 50; // TODO -- how to pick this? based on meters from our characterController? - if (_movingFramesWithoutSimulationOwner > ownershipClaimDelay) { + const uint32_t OWNERSHIP_BID_DELAY = 50; + if (_loopsWithoutOwner > OWNERSHIP_BID_DELAY) { //qDebug() << "Warning -- claiming something I saw moving." << getName(); - _touchesOurSimulation = true; + _candidateForOwnership = true; } } else { - _movingFramesWithoutSimulationOwner = 0; + _loopsWithoutOwner = 0; } #ifdef WANT_DEBUG @@ -184,12 +184,11 @@ void EntityMotionState::computeObjectShapeInfo(ShapeInfo& shapeInfo) { // we alwasy resend packets for objects that have stopped moving up to some max limit. const int MAX_NUM_NON_MOVING_UPDATES = 5; -bool EntityMotionState::doesNotNeedToSendUpdate(const QUuid& sessionID) const { +bool EntityMotionState::isCandidateForOwnership(const QUuid& sessionID) const { if (!_body || !_entity) { - return true; + return false; } - - return (sessionID != _entity->getSimulatorID() && !_touchesOurSimulation); + return _candidateForOwnership || sessionID == _entity->getSimulatorID(); } bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { @@ -297,13 +296,13 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { return (fabsf(glm::dot(actualRotation, _serverRotation)) < MIN_ROTATION_DOT); } -bool EntityMotionState::shouldSendUpdate(uint32_t simulationFrame, const QUuid& sessionID) { +bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep, const QUuid& sessionID) { // NOTE: we expect _entity and _body to be valid in this context, since shouldSendUpdate() is only called // after doesNotNeedToSendUpdate() returns false and that call should return 'true' if _entity or _body are NULL. assert(_entity); assert(_body); - if (!remoteSimulationOutOfSync(simulationFrame)) { + if (!remoteSimulationOutOfSync(simulationStep)) { return false; } @@ -313,9 +312,9 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationFrame, const QUuid& } const uint32_t FRAMES_BETWEEN_OWNERSHIP_CLAIMS = 30; - if (_touchesOurSimulation) { - ++_framesSinceSimulatorBid; - if (_framesSinceSimulatorBid > FRAMES_BETWEEN_OWNERSHIP_CLAIMS) { + if (_candidateForOwnership) { + ++_loopsSinceOwnershipBid; + if (_loopsSinceOwnershipBid > FRAMES_BETWEEN_OWNERSHIP_CLAIMS) { // we don't own the simulation, but it's time to bid for it return true; } @@ -411,7 +410,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q // we own the simulation but the entity has stopped, so we tell the server that we're clearing simulatorID // but we remember that we do still own it... and rely on the server to tell us that we don't properties.setSimulatorID(QUuid()); - _touchesOurSimulation = false; + _candidateForOwnership = false; } else { // explicitly set the property's simulatorID so that it is flagged as changed and will be packed properties.setSimulatorID(sessionID); @@ -467,7 +466,7 @@ QUuid EntityMotionState::getSimulatorID() const { // virtual void EntityMotionState::bump() { - _touchesOurSimulation = true; + _candidateForOwnership = true; } void EntityMotionState::resetMeasuredBodyAcceleration() { diff --git a/libraries/physics/src/EntityMotionState.h b/libraries/physics/src/EntityMotionState.h index afb7d257a6..4c1b469261 100644 --- a/libraries/physics/src/EntityMotionState.h +++ b/libraries/physics/src/EntityMotionState.h @@ -46,10 +46,9 @@ public: virtual void computeObjectShapeInfo(ShapeInfo& shapeInfo); - // TODO: Andrew to rename doesNotNeedToSendUpdate() - bool doesNotNeedToSendUpdate(const QUuid& sessionID) const; + bool isCandidateForOwnership(const QUuid& sessionID) const; bool remoteSimulationOutOfSync(uint32_t simulationStep); - bool shouldSendUpdate(uint32_t simulationFrame, const QUuid& sessionID); + bool shouldSendUpdate(uint32_t simulationStep, const QUuid& sessionID); void sendUpdate(OctreeEditPacketSender* packetSender, const QUuid& sessionID, uint32_t step); virtual uint32_t getAndClearIncomingDirtyFlags() const; @@ -107,9 +106,9 @@ protected: glm::vec3 _measuredAcceleration; quint8 _accelerationNearlyGravityCount; - bool _touchesOurSimulation; - uint32_t _framesSinceOwnershipBid; - uint32_t _movingFramesWithoutSimulationOwner; + bool _candidateForOwnership; + uint32_t _loopsSinceOwnershipBid; + uint32_t _loopsWithoutOwner; }; #endif // hifi_EntityMotionState_h diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index 922fa0ce4f..3e43ab7454 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -188,7 +188,7 @@ VectorOfMotionStates& PhysicalEntitySimulation::getObjectsToChange() { return _tempVector; } -void PhysicalEntitySimulation::handleOutgoingChanges(VectorOfMotionStates& motionStates) { +void PhysicalEntitySimulation::handleOutgoingChanges(VectorOfMotionStates& motionStates, const QUuid& sessionID) { // walk the motionStates looking for those that correspond to entities for (auto stateItr : motionStates) { ObjectMotionState* state = &(*stateItr); @@ -196,7 +196,7 @@ void PhysicalEntitySimulation::handleOutgoingChanges(VectorOfMotionStates& motio EntityMotionState* entityState = static_cast(state); EntityItem* entity = entityState->getEntity(); if (entity) { - if (entity->isKnownID()) { + if (entity->isKnownID() && entityState->isCandidateForOwnership(sessionID)) { _outgoingChanges.insert(entityState); } _entitiesToSort.insert(entityState->getEntity()); @@ -204,23 +204,21 @@ void PhysicalEntitySimulation::handleOutgoingChanges(VectorOfMotionStates& motio } } - auto nodeList = DependencyManager::get(); - const QUuid& sessionID = nodeList->getSessionUUID(); - if (sessionID.isNull()) { - // no updates to send - _outgoingChanges.clear(); - return; - } - - // send outgoing packets uint32_t numSubsteps = _physicsEngine->getNumSubsteps(); if (_lastStepSendPackets != numSubsteps) { _lastStepSendPackets = numSubsteps; + if (sessionID.isNull()) { + // usually don't get here, but if so --> nothing to do + _outgoingChanges.clear(); + return; + } + + // send outgoing packets QSet::iterator stateItr = _outgoingChanges.begin(); while (stateItr != _outgoingChanges.end()) { EntityMotionState* state = *stateItr; - if (state->doesNotNeedToSendUpdate(sessionID)) { + if (!state->isCandidateForOwnership(sessionID)) { stateItr = _outgoingChanges.erase(stateItr); } else if (state->shouldSendUpdate(numSubsteps, sessionID)) { state->sendUpdate(_entityPacketSender, sessionID, numSubsteps); diff --git a/libraries/physics/src/PhysicalEntitySimulation.h b/libraries/physics/src/PhysicalEntitySimulation.h index 4fd54c60fb..b3ee7af1e1 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.h +++ b/libraries/physics/src/PhysicalEntitySimulation.h @@ -48,7 +48,7 @@ public: VectorOfMotionStates& getObjectsToAdd(); VectorOfMotionStates& getObjectsToChange(); - void handleOutgoingChanges(VectorOfMotionStates& motionStates); + void handleOutgoingChanges(VectorOfMotionStates& motionStates, const QUuid& sessionID); void handleCollisionEvents(CollisionEvents& collisionEvents); private: diff --git a/libraries/physics/src/PhysicsEngine.h b/libraries/physics/src/PhysicsEngine.h index 8b947d2510..d1dc5bcd79 100644 --- a/libraries/physics/src/PhysicsEngine.h +++ b/libraries/physics/src/PhysicsEngine.h @@ -53,6 +53,7 @@ public: void init(); void setSessionUUID(const QUuid& sessionID) { _sessionID = sessionID; } + const QUuid& getSessionID() const { return _sessionID; } void addObject(ObjectMotionState* motionState); void removeObject(ObjectMotionState* motionState); From 57fa3d8c5325a07b0a32aa8cffe30727a2b6983a Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 13 May 2015 14:43:31 -0700 Subject: [PATCH 07/12] fixes for other avatar receive stats rendering --- interface/src/avatar/Avatar.cpp | 217 +++++++++++++++++--------------- 1 file changed, 117 insertions(+), 100 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 09a3407d08..8a627c019c 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -81,7 +81,7 @@ Avatar::Avatar() : { // we may have been created in the network thread, but we live in the main thread moveToThread(Application::getInstance()->thread()); - + // give the pointer to our head to inherited _headData variable from AvatarData _headData = static_cast(new Head(this)); _handData = static_cast(new Hand(this)); @@ -122,7 +122,7 @@ float Avatar::getLODDistance() const { void Avatar::simulate(float deltaTime) { PerformanceTimer perfTimer("simulate"); - + // update the avatar's position according to its referential if (_referential) { if (_referential->hasExtraData()) { @@ -143,10 +143,10 @@ void Avatar::simulate(float deltaTime) { break; } } - + _referential->update(); } - + if (_scale != _targetScale) { setScale(_targetScale); } @@ -171,7 +171,7 @@ void Avatar::simulate(float deltaTime) { getHand()->simulate(deltaTime, false); } _skeletonModel.setLODDistance(getLODDistance()); - + if (!_shouldRenderBillboard && inViewFrustum) { { PerformanceTimer perfTimer("skeleton"); @@ -198,7 +198,7 @@ void Avatar::simulate(float deltaTime) { // update animation for display name fade in/out if ( _displayNameTargetAlpha != _displayNameAlpha) { - // the alpha function is + // the alpha function is // Fade out => alpha(t) = factor ^ t => alpha(t+dt) = alpha(t) * factor^(dt) // Fade in => alpha(t) = 1 - factor^t => alpha(t+dt) = 1-(1-alpha(t))*coef^(dt) // factor^(dt) = coef @@ -213,17 +213,17 @@ void Avatar::simulate(float deltaTime) { _displayNameAlpha = abs(_displayNameAlpha - _displayNameTargetAlpha) < 0.01f ? _displayNameTargetAlpha : _displayNameAlpha; } - // NOTE: we shouldn't extrapolate an Avatar instance forward in time... + // NOTE: we shouldn't extrapolate an Avatar instance forward in time... // until velocity is included in AvatarData update message. //_position += _velocity * deltaTime; measureMotionDerivatives(deltaTime); } -void Avatar::slamPosition(const glm::vec3& newPosition) { +void Avatar::slamPosition(const glm::vec3& newPosition) { AvatarData::setPosition(newPosition); _positionDeltaAccumulator = glm::vec3(0.0f); _velocity = glm::vec3(0.0f); - _lastVelocity = glm::vec3(0.0f); + _lastVelocity = glm::vec3(0.0f); } void Avatar::applyPositionDelta(const glm::vec3& delta) { @@ -249,7 +249,7 @@ void Avatar::measureMotionDerivatives(float deltaTime) { } enum TextRendererType { - CHAT, + CHAT, DISPLAYNAME }; @@ -272,7 +272,7 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderArgs::RenderMode rend if (_referential) { _referential->update(); } - + if (postLighting && glm::distance(DependencyManager::get()->getMyAvatar()->getPosition(), _position) < 10.0f) { auto geometryCache = DependencyManager::get(); @@ -303,15 +303,15 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderArgs::RenderMode rend float angle = glm::degrees(glm::angle(rotation)); glm::vec3 axis = glm::axis(rotation); glRotatef(angle, axis.x, axis.y, axis.z); - + geometryCache->renderLine(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, laserLength, 0.0f), laserColor); - + } glPopMatrix(); } } if (_handState & RIGHT_HAND_POINTING_FLAG) { - + if (_handState & IS_FINGER_POINTING_FLAG) { int rightIndexTip = getJointIndex("RightHandIndex4"); int rightIndexTipJoint = getJointIndex("RightHandIndex3"); @@ -330,12 +330,12 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderArgs::RenderMode rend glm::vec3 axis = glm::axis(rotation); glRotatef(angle, axis.x, axis.y, axis.z); geometryCache->renderLine(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, laserLength, 0.0f), laserColor); - + } glPopMatrix(); } } } - + // simple frustum check float boundingRadius = getBillboardSize(); ViewFrustum* frustum = nullptr; @@ -351,24 +351,24 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderArgs::RenderMode rend glm::vec3 toTarget = cameraPosition - getPosition(); float distanceToTarget = glm::length(toTarget); - + { // glow when moving far away const float GLOW_DISTANCE = 20.0f; const float GLOW_MAX_LOUDNESS = 2500.0f; const float MAX_GLOW = 0.5f; - + float GLOW_FROM_AVERAGE_LOUDNESS = ((this == DependencyManager::get()->getMyAvatar()) ? 0.0f : MAX_GLOW * getHeadData()->getAudioLoudness() / GLOW_MAX_LOUDNESS); if (!Menu::getInstance()->isOptionChecked(MenuOption::GlowWhenSpeaking)) { GLOW_FROM_AVERAGE_LOUDNESS = 0.0f; } - + float glowLevel = _moving && distanceToTarget > GLOW_DISTANCE && renderMode == RenderArgs::NORMAL_RENDER_MODE ? 1.0f : GLOW_FROM_AVERAGE_LOUDNESS; - + // render body renderBody(frustum, renderMode, postLighting, glowLevel); @@ -386,7 +386,7 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderArgs::RenderMode rend distance * 2.0f, light.color, 0.5f, orientation, LIGHT_EXPONENT, LIGHT_CUTOFF); } } - + if (postLighting) { bool renderSkeleton = Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionShapes); bool renderHead = Menu::getInstance()->isOptionChecked(MenuOption::RenderHeadCollisionShapes); @@ -435,7 +435,7 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderArgs::RenderMode rend glm::vec3 delta = height * (getHead()->getCameraOrientation() * IDENTITY_UP) / 2.0f; float angle = abs(angleBetween(toTarget + delta, toTarget - delta)); float sphereRadius = getHead()->getAverageLoudness() * SPHERE_LOUDNESS_SCALING; - + if (renderMode == RenderArgs::NORMAL_RENDER_MODE && (sphereRadius > MIN_SPHERE_SIZE) && (angle < MAX_SPHERE_ANGLE) && (angle > MIN_SPHERE_ANGLE)) { glPushMatrix(); @@ -483,7 +483,7 @@ void Avatar::renderBody(ViewFrustum* renderFrustum, RenderArgs::RenderMode rende Model::RenderMode modelRenderMode = renderMode; { Glower glower(glowLevel); - + if (_shouldRenderBillboard || !(_skeletonModel.isRenderable() && getHead()->getFaceModel().isRenderable())) { if (postLighting || renderMode == RenderArgs::SHADOW_RENDER_MODE) { // render the billboard until both models are loaded @@ -491,7 +491,7 @@ void Avatar::renderBody(ViewFrustum* renderFrustum, RenderArgs::RenderMode rende } return; } - + if (postLighting) { getHand()->render(false, modelRenderMode); } else { @@ -553,43 +553,43 @@ void Avatar::renderBillboard() { if (!_billboardTexture->isLoaded()) { return; } - + glEnable(GL_ALPHA_TEST); glAlphaFunc(GL_GREATER, 0.5f); - + glEnable(GL_TEXTURE_2D); glDisable(GL_LIGHTING); glBindTexture(GL_TEXTURE_2D, _billboardTexture->getID()); - + glPushMatrix(); glTranslatef(_position.x, _position.y, _position.z); - + // rotate about vertical to face the camera glm::quat rotation = getOrientation(); glm::vec3 cameraVector = glm::inverse(rotation) * (Application::getInstance()->getCamera()->getPosition() - _position); rotation = rotation * glm::angleAxis(atan2f(-cameraVector.x, -cameraVector.z), glm::vec3(0.0f, 1.0f, 0.0f)); glm::vec3 axis = glm::axis(rotation); glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z); - + // compute the size from the billboard camera parameters and scale float size = getBillboardSize(); glScalef(size, size, size); - + glm::vec2 topLeft(-1.0f, -1.0f); glm::vec2 bottomRight(1.0f, 1.0f); glm::vec2 texCoordTopLeft(0.0f, 0.0f); glm::vec2 texCoordBottomRight(1.0f, 1.0f); - DependencyManager::get()->renderQuad(topLeft, bottomRight, texCoordTopLeft, texCoordBottomRight, + DependencyManager::get()->renderQuad(topLeft, bottomRight, texCoordTopLeft, texCoordBottomRight, glm::vec4(1.0f, 1.0f, 1.0f, 1.0f)); - + glPopMatrix(); - + glDisable(GL_TEXTURE_2D); glEnable(GL_LIGHTING); glDisable(GL_ALPHA_TEST); - + glBindTexture(GL_TEXTURE_2D, 0); } @@ -611,26 +611,26 @@ glm::vec3 Avatar::getDisplayNamePosition() { float Avatar::calculateDisplayNameScaleFactor(const glm::vec3& textPosition, bool inHMD) { // We need to compute the scale factor such as the text remains with fixed size respect to window coordinates - // We project a unit vector and check the difference in screen coordinates, to check which is the + // We project a unit vector and check the difference in screen coordinates, to check which is the // correction scale needed - // save the matrices for later scale correction factor + // save the matrices for later scale correction factor // The up vector must be relative to the rotation current rotation matrix: // we set the identity glm::vec3 testPoint0 = textPosition; glm::vec3 testPoint1 = textPosition + (Application::getInstance()->getCamera()->getRotation() * IDENTITY_UP); - + double textWindowHeight; - + GLint viewportMatrix[4]; glGetIntegerv(GL_VIEWPORT, viewportMatrix); glm::dmat4 modelViewMatrix; float windowSizeX = viewportMatrix[2] - viewportMatrix[0]; float windowSizeY = viewportMatrix[3] - viewportMatrix[1]; - + glm::dmat4 projectionMatrix; Application::getInstance()->getModelViewMatrix(&modelViewMatrix); Application::getInstance()->getProjectionMatrix(&projectionMatrix); - + glm::dvec4 p0 = modelViewMatrix * glm::dvec4(testPoint0, 1.0); p0 = projectionMatrix * p0; @@ -655,23 +655,25 @@ float Avatar::calculateDisplayNameScaleFactor(const glm::vec3& textPosition, boo void Avatar::renderDisplayName() { - if (_displayName.isEmpty() || _displayNameAlpha == 0.0f) { + bool shouldShowReceiveStats = DependencyManager::get()->shouldShowReceiveStats(); + + if ((_displayName.isEmpty() && !shouldShowReceiveStats) || _displayNameAlpha == 0.0f) { return; } - + // which viewing mode? bool inHMD = Application::getInstance()->isHMDMode(); - + glDisable(GL_LIGHTING); - + glPushMatrix(); glm::vec3 textPosition = getDisplayNamePosition(); - - glTranslatef(textPosition.x, textPosition.y, textPosition.z); + + glTranslatef(textPosition.x, textPosition.y, textPosition.z); // we need "always facing camera": we must remove the camera rotation from the stack - + glm::vec3 frontAxis(0.0f, 0.0f, 1.0f); if (inHMD) { glm::vec3 camPosition = Application::getInstance()->getCamera()->getPosition(); @@ -680,22 +682,48 @@ void Avatar::renderDisplayName() { glm::quat rotation = Application::getInstance()->getCamera()->getRotation(); frontAxis = glm::rotate(rotation, frontAxis); } - + frontAxis = glm::normalize(glm::vec3(frontAxis.z, 0.0f, -frontAxis.x)); float angle = acos(frontAxis.x) * ((frontAxis.z < 0) ? 1.0f : -1.0f); glRotatef(glm::degrees(angle), 0.0f, 1.0f, 0.0f); - + float scaleFactor = calculateDisplayNameScaleFactor(textPosition, inHMD); glScalef(scaleFactor, -scaleFactor, scaleFactor); // TextRenderer::draw paints the text upside down in y axis - int text_x = -_displayNameBoundingRect.width() / 2; - int text_y = -_displayNameBoundingRect.height() / 2; + // optionally render timing stats for this avatar with the display name + QString renderedDisplayName = _displayName; + QRect nameDynamicRect = _displayNameBoundingRect; + + if (shouldShowReceiveStats) { + float kilobitsPerSecond = getAverageBytesReceivedPerSecond() / (float) BYTES_PER_KILOBIT; + + QString statsFormat = QString("(%1 Kbps, %2 Hz)"); + + if (!renderedDisplayName.isEmpty()) { + statsFormat.prepend(" - "); + } + + QString statsText = statsFormat.arg(QString::number(kilobitsPerSecond, 'f', 2)).arg(getReceiveRate()); + glm::vec2 extent = textRenderer(DISPLAYNAME)->computeExtent(statsText); + + // add the extent required for the stats to whatever was calculated for the display name + nameDynamicRect.setWidth(nameDynamicRect.width() + extent.x); + + if (extent.y > nameDynamicRect.height()) { + nameDynamicRect.setHeight(extent.y); + } + + renderedDisplayName += statsText; + } + + int text_x = -nameDynamicRect.width() / 2; + int text_y = -nameDynamicRect.height() / 2; // draw a gray background - int left = text_x + _displayNameBoundingRect.x(); - int right = left + _displayNameBoundingRect.width(); - int bottom = text_y + _displayNameBoundingRect.y(); - int top = bottom + _displayNameBoundingRect.height(); + int left = text_x + nameDynamicRect.x(); + int right = left + nameDynamicRect.width(); + int bottom = text_y + nameDynamicRect.y(); + int top = bottom + nameDynamicRect.height(); const int border = 8; bottom -= border; left -= border; @@ -708,22 +736,11 @@ void Avatar::renderDisplayName() { DependencyManager::get()->renderBevelCornersRect(left, bottom, right - left, top - bottom, 3, glm::vec4(0.2f, 0.2f, 0.2f, _displayNameAlpha * DISPLAYNAME_BACKGROUND_ALPHA / DISPLAYNAME_ALPHA)); - + glm::vec4 color(0.93f, 0.93f, 0.93f, _displayNameAlpha); - - // optionally render timing stats for this avatar with the display name - QString renderedDisplayName = _displayName; - - if (DependencyManager::get()->shouldShowReceiveStats()) { - float kilobitsPerSecond = getAverageBytesReceivedPerSecond() / (float) BYTES_PER_KILOBIT; - - renderedDisplayName += QString(" - (%1 Kbps, %2 Hz)") - .arg(QString::number(kilobitsPerSecond, 'f', 2)) - .arg(getReceiveRate()); - } - + QByteArray nameUTF8 = renderedDisplayName.toLocal8Bit(); - + glDisable(GL_POLYGON_OFFSET_FILL); textRenderer(DISPLAYNAME)->draw(text_x, text_y, nameUTF8.data(), color); @@ -769,11 +786,11 @@ void Avatar::setSkeletonOffset(const glm::vec3& offset) { } } -glm::vec3 Avatar::getSkeletonPosition() const { - // The avatar is rotated PI about the yAxis, so we have to correct for it +glm::vec3 Avatar::getSkeletonPosition() const { + // The avatar is rotated PI about the yAxis, so we have to correct for it // to get the skeleton offset contribution in the world-frame. const glm::quat FLIP = glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f)); - return _position + getOrientation() * FLIP * _skeletonOffset; + return _position + getOrientation() * FLIP * _skeletonOffset; } QVector Avatar::getJointRotations() const { @@ -868,7 +885,7 @@ const float SCRIPT_PRIORITY = DEFAULT_PRIORITY + 1.0f; void Avatar::setJointModelPositionAndOrientation(int index, glm::vec3 position, const glm::quat& rotation) { if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(const_cast(this), "setJointModelPositionAndOrientation", + QMetaObject::invokeMethod(const_cast(this), "setJointModelPositionAndOrientation", Qt::AutoConnection, Q_ARG(const int, index), Q_ARG(const glm::vec3, position), Q_ARG(const glm::quat&, rotation)); } else { @@ -878,7 +895,7 @@ void Avatar::setJointModelPositionAndOrientation(int index, glm::vec3 position, void Avatar::setJointModelPositionAndOrientation(const QString& name, glm::vec3 position, const glm::quat& rotation) { if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(const_cast(this), "setJointModelPositionAndOrientation", + QMetaObject::invokeMethod(const_cast(this), "setJointModelPositionAndOrientation", Qt::AutoConnection, Q_ARG(const QString&, name), Q_ARG(const glm::vec3, position), Q_ARG(const glm::quat&, rotation)); } else { @@ -919,7 +936,7 @@ void Avatar::setAttachmentData(const QVector& attachmentData) { while (_attachmentModels.size() > attachmentData.size()) { delete _attachmentModels.takeLast(); } - + // update the urls for (int i = 0; i < attachmentData.size(); i++) { _attachmentModels[i]->setURL(attachmentData.at(i).modelURL); @@ -932,12 +949,12 @@ void Avatar::setDisplayName(const QString& displayName) { AvatarData::setDisplayName(displayName); // FIXME is this a sufficient replacement for tightBoundingRect? glm::vec2 extent = textRenderer(DISPLAYNAME)->computeExtent(displayName); - _displayNameBoundingRect = QRect(QPoint(0, 0), QPoint((int)extent.x, (int)extent.y)); + _displayNameBoundingRect = QRect(0, 0, (int)extent.x, (int)extent.y); } void Avatar::setBillboard(const QByteArray& billboard) { AvatarData::setBillboard(billboard); - + // clear out any existing billboard texture _billboardTexture.reset(); } @@ -947,65 +964,65 @@ int Avatar::parseDataAtOffset(const QByteArray& packet, int offset) { // now that we have data for this Avatar we are go for init init(); } - + // change in position implies movement glm::vec3 oldPosition = _position; - + int bytesRead = AvatarData::parseDataAtOffset(packet, offset); - + const float MOVE_DISTANCE_THRESHOLD = 0.001f; _moving = glm::distance(oldPosition, _position) > MOVE_DISTANCE_THRESHOLD; - + return bytesRead; } int Avatar::_jointConesID = GeometryCache::UNKNOWN_ID; // render a makeshift cone section that serves as a body part connecting joint spheres -void Avatar::renderJointConnectingCone(glm::vec3 position1, glm::vec3 position2, +void Avatar::renderJointConnectingCone(glm::vec3 position1, glm::vec3 position2, float radius1, float radius2, const glm::vec4& color) { - + auto geometryCache = DependencyManager::get(); - + if (_jointConesID == GeometryCache::UNKNOWN_ID) { _jointConesID = geometryCache->allocateID(); } - + glm::vec3 axis = position2 - position1; float length = glm::length(axis); - + if (length > 0.0f) { - + axis /= length; - + glm::vec3 perpSin = glm::vec3(1.0f, 0.0f, 0.0f); glm::vec3 perpCos = glm::normalize(glm::cross(axis, perpSin)); perpSin = glm::cross(perpCos, axis); - + float anglea = 0.0f; float angleb = 0.0f; QVector points; - + for (int i = 0; i < NUM_BODY_CONE_SIDES; i ++) { - + // the rectangles that comprise the sides of the cone section are // referenced by "a" and "b" in one dimension, and "1", and "2" in the other dimension. anglea = angleb; angleb = ((float)(i+1) / (float)NUM_BODY_CONE_SIDES) * TWO_PI; - + float sa = sinf(anglea); float sb = sinf(angleb); float ca = cosf(anglea); float cb = cosf(angleb); - + glm::vec3 p1a = position1 + perpSin * sa * radius1 + perpCos * ca * radius1; - glm::vec3 p1b = position1 + perpSin * sb * radius1 + perpCos * cb * radius1; - glm::vec3 p2a = position2 + perpSin * sa * radius2 + perpCos * ca * radius2; - glm::vec3 p2b = position2 + perpSin * sb * radius2 + perpCos * cb * radius2; - + glm::vec3 p1b = position1 + perpSin * sb * radius1 + perpCos * cb * radius1; + glm::vec3 p2a = position2 + perpSin * sa * radius2 + perpCos * ca * radius2; + glm::vec3 p2b = position2 + perpSin * sb * radius2 + perpCos * cb * radius2; + points << p1a << p1b << p2a << p1b << p2a << p2b; } - + // TODO: this is really inefficient constantly recreating these vertices buffers. It would be // better if the avatars cached these buffers for each of the joints they are rendering geometryCache->updateVertices(_jointConesID, points, color); @@ -1052,7 +1069,7 @@ void Avatar::setShowDisplayName(bool showDisplayName) { _displayNameAlpha = 0.0f; return; } - + // For myAvatar, the alpha update is not done (called in simulate for other avatars) if (DependencyManager::get()->getMyAvatar() == this) { if (showDisplayName) { @@ -1060,7 +1077,7 @@ void Avatar::setShowDisplayName(bool showDisplayName) { } else { _displayNameAlpha = 0.0f; } - } + } if (showDisplayName) { _displayNameTargetAlpha = DISPLAYNAME_ALPHA; From c631f85bf97f14f815a0ba1f4a403b7ca0d741c3 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 14 May 2015 08:43:54 -0700 Subject: [PATCH 08/12] cleanup of simulation ownership/update logic --- libraries/physics/src/EntityMotionState.cpp | 125 +++++++++++--------- libraries/physics/src/EntityMotionState.h | 3 +- libraries/physics/src/ObjectMotionState.cpp | 4 - libraries/physics/src/ObjectMotionState.h | 6 +- 4 files changed, 70 insertions(+), 68 deletions(-) diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 759ce68e16..3986762f6f 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -25,7 +25,7 @@ static const quint8 STEPS_TO_DECIDE_BALLISTIC = 4; EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItem* entity) : ObjectMotionState(shape), _entity(entity), - _sentMoving(false), + _sentActive(false), _numNonMovingUpdates(0), _lastStep(0), _serverPosition(0.0f), @@ -61,21 +61,34 @@ void EntityMotionState::updateServerPhysicsVariables(uint32_t flags) { if (flags & EntityItem::DIRTY_ANGULAR_VELOCITY) { _serverAngularVelocity = _entity->getAngularVelocity(); } - if (flags & EntityItem::DIRTY_SIMULATOR_ID) { - auto nodeList = DependencyManager::get(); - const QUuid& sessionID = nodeList->getSessionUUID(); - if (_entity->getSimulatorID() != sessionID) { - _candidateForOwnership = false; - _loopsWithoutOwner = 0; - _loopsSinceOwnershipBid = 0; - } - } } // virtual void EntityMotionState::handleEasyChanges(uint32_t flags) { updateServerPhysicsVariables(flags); ObjectMotionState::handleEasyChanges(flags); + if (flags & EntityItem::DIRTY_SIMULATOR_ID) { + _loopsWithoutOwner = 0; + _candidateForOwnership = 0; + if (_entity->getSimulatorID().isNull() + && !_entity->isMoving() + && _body->isActive()) { + // remove the ACTIVATION flag because this object is coming to rest + // according to a remote simulation and we don't want to wake it up again + flags &= ~EntityItem::DIRTY_PHYSICS_ACTIVATION; + _body->setActivationState(WANTS_DEACTIVATION); + } else { + auto nodeList = DependencyManager::get(); + const QUuid& sessionID = nodeList->getSessionUUID(); + if (_entity->getSimulatorID() != sessionID) { + _loopsSinceOwnershipBid = 0; + } + } + } + if ((flags & EntityItem::DIRTY_PHYSICS_ACTIVATION) && !_body->isActive()) { + _body->activate(); + } + } @@ -105,15 +118,6 @@ bool EntityMotionState::isMoving() const { return _entity && _entity->isMoving(); } -bool EntityMotionState::isMovingVsServer() const { - auto alignmentDot = glm::abs(glm::dot(_serverRotation, _entity->getRotation())); - if (glm::distance(_serverPosition, _entity->getPosition()) > IGNORE_POSITION_DELTA || - alignmentDot < IGNORE_ALIGNMENT_DOT) { - return true; - } - return false; -} - // This callback is invoked by the physics simulation in two cases: // (1) when the RigidBody is first added to the world // (irregardless of MotionType: STATIC, DYNAMIC, or KINEMATIC) @@ -201,7 +205,7 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { _serverVelocity = bulletToGLM(_body->getLinearVelocity()); _serverAngularVelocity = bulletToGLM(_body->getAngularVelocity()); _lastStep = simulationStep; - _sentMoving = false; + _sentActive = false; return false; } @@ -214,12 +218,12 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { int numSteps = simulationStep - _lastStep; float dt = (float)(numSteps) * PHYSICS_ENGINE_FIXED_SUBSTEP; - const float NON_MOVING_UPDATE_PERIOD = 1.0f; - if (!_sentMoving) { - // we resend non-moving update every NON_MOVING_UPDATE_PERIOD + const float INACTIVE_UPDATE_PERIOD = 0.5f; + if (!_sentActive) { + // we resend the inactive update every INACTIVE_UPDATE_PERIOD // until it is removed from the outgoing updates // (which happens when we don't own the simulation and it isn't touching our simulation) - return (dt > NON_MOVING_UPDATE_PERIOD); + return (dt > INACTIVE_UPDATE_PERIOD); } bool isActive = _body->isActive(); @@ -303,23 +307,28 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep, const QUuid& s assert(_body); if (!remoteSimulationOutOfSync(simulationStep)) { + _candidateForOwnership = false; return false; } if (_entity->getSimulatorID() == sessionID) { // we own the simulation + _candidateForOwnership = false; return true; } const uint32_t FRAMES_BETWEEN_OWNERSHIP_CLAIMS = 30; if (_candidateForOwnership) { + _candidateForOwnership = false; ++_loopsSinceOwnershipBid; if (_loopsSinceOwnershipBid > FRAMES_BETWEEN_OWNERSHIP_CLAIMS) { // we don't own the simulation, but it's time to bid for it + _loopsSinceOwnershipBid = 0; return true; } } + _candidateForOwnership = false; return false; } @@ -329,14 +338,12 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q bool active = _body->isActive(); if (!active) { - if (_sentMoving) { - // make sure all derivatives are zero - glm::vec3 zero(0.0f); - _entity->setVelocity(zero); - _entity->setAngularVelocity(zero); - _entity->setAcceleration(zero); - } - + // make sure all derivatives are zero + glm::vec3 zero(0.0f); + _entity->setVelocity(zero); + _entity->setAngularVelocity(zero); + _entity->setAcceleration(zero); + _sentActive = false; } else { float gravityLength = glm::length(_entity->getGravity()); float accVsGravity = glm::abs(glm::length(_measuredAcceleration) - gravityLength); @@ -359,6 +366,21 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q } else { _entity->setAcceleration(glm::vec3(0.0f)); } + + const float DYNAMIC_LINEAR_VELOCITY_THRESHOLD = 0.05f; // 5 cm/sec + const float DYNAMIC_ANGULAR_VELOCITY_THRESHOLD = 0.087266f; // ~5 deg/sec + bool movingSlowly = glm::length2(_entity->getVelocity()) < (DYNAMIC_LINEAR_VELOCITY_THRESHOLD * DYNAMIC_LINEAR_VELOCITY_THRESHOLD) + && glm::length2(_entity->getAngularVelocity()) < (DYNAMIC_ANGULAR_VELOCITY_THRESHOLD * DYNAMIC_ANGULAR_VELOCITY_THRESHOLD) + && _entity->getAcceleration() == glm::vec3(0.0f); + + if (movingSlowly) { + // velocities might not be zero, but we'll fake them as such, which will hopefully help convince + // other simulating observers to deactivate their own copies + glm::vec3 zero(0.0f); + _entity->setVelocity(zero); + _entity->setAngularVelocity(zero); + } + _sentActive = true; } // remember properties for local server prediction @@ -368,8 +390,6 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q _serverAcceleration = _entity->getAcceleration(); _serverAngularVelocity = _entity->getAngularVelocity(); - _sentMoving = _serverVelocity != glm::vec3(0.0f) || _serverAngularVelocity != _serverVelocity || _serverAcceleration != _serverVelocity; - EntityItemProperties properties = _entity->getProperties(); // explicitly set the properties that changed so that they will be packed @@ -379,38 +399,25 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q properties.setAcceleration(_serverAcceleration); properties.setAngularVelocity(_serverAngularVelocity); - // RELIABLE_SEND_HACK: count number of updates for entities at rest - // so we can stop sending them after some limit. - if (_sentMoving) { - _numNonMovingUpdates = 0; - } else { - _numNonMovingUpdates++; - } - if (_numNonMovingUpdates <= 1) { - // we only update lastEdited when we're sending new physics data - quint64 lastSimulated = _entity->getLastSimulated(); - _entity->setLastEdited(lastSimulated); - properties.setLastEdited(lastSimulated); + // we only update lastEdited when we're sending new physics data + quint64 lastSimulated = _entity->getLastSimulated(); + _entity->setLastEdited(lastSimulated); + properties.setLastEdited(lastSimulated); - #ifdef WANT_DEBUG - quint64 now = usecTimestampNow(); - qCDebug(physics) << "EntityMotionState::sendUpdate()"; - qCDebug(physics) << " EntityItemId:" << _entity->getEntityItemID() - << "---------------------------------------------"; - qCDebug(physics) << " lastSimulated:" << debugTime(lastSimulated, now); - #endif //def WANT_DEBUG - - } else { - properties.setLastEdited(_entity->getLastEdited()); - } + #ifdef WANT_DEBUG + quint64 now = usecTimestampNow(); + qCDebug(physics) << "EntityMotionState::sendUpdate()"; + qCDebug(physics) << " EntityItemId:" << _entity->getEntityItemID() + << "---------------------------------------------"; + qCDebug(physics) << " lastSimulated:" << debugTime(lastSimulated, now); + #endif //def WANT_DEBUG if (sessionID == _entity->getSimulatorID()) { // we think we own the simulation - if (!_sentMoving) { + if (!active) { // we own the simulation but the entity has stopped, so we tell the server that we're clearing simulatorID // but we remember that we do still own it... and rely on the server to tell us that we don't properties.setSimulatorID(QUuid()); - _candidateForOwnership = false; } else { // explicitly set the property's simulatorID so that it is flagged as changed and will be packed properties.setSimulatorID(sessionID); diff --git a/libraries/physics/src/EntityMotionState.h b/libraries/physics/src/EntityMotionState.h index 4c1b469261..83b89a5a29 100644 --- a/libraries/physics/src/EntityMotionState.h +++ b/libraries/physics/src/EntityMotionState.h @@ -36,7 +36,6 @@ public: virtual MotionType computeObjectMotionType() const; virtual bool isMoving() const; - virtual bool isMovingVsServer() const; // this relays incoming position/rotation to the RigidBody virtual void getWorldTransform(btTransform& worldTrans) const; @@ -89,7 +88,7 @@ protected: EntityItem* _entity; - bool _sentMoving; // true if last update was moving + bool _sentActive; // true if body was active when we sent last update int _numNonMovingUpdates; // RELIABLE_SEND_HACK for "not so reliable" resends of packets for non-moving objects // these are for the prediction of the remote server's simple extrapolation diff --git a/libraries/physics/src/ObjectMotionState.cpp b/libraries/physics/src/ObjectMotionState.cpp index c1258ad6bc..c5288cfa76 100644 --- a/libraries/physics/src/ObjectMotionState.cpp +++ b/libraries/physics/src/ObjectMotionState.cpp @@ -146,10 +146,6 @@ void ObjectMotionState::handleEasyChanges(uint32_t flags) { _body->setMassProps(mass, inertia); _body->updateInertiaTensor(); } - - if (flags & EntityItem::DIRTY_PHYSICS_ACTIVATION) { - _body->activate(); - } } void ObjectMotionState::handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine) { diff --git a/libraries/physics/src/ObjectMotionState.h b/libraries/physics/src/ObjectMotionState.h index 1407be0d20..bfc9310ec6 100644 --- a/libraries/physics/src/ObjectMotionState.h +++ b/libraries/physics/src/ObjectMotionState.h @@ -36,11 +36,11 @@ enum MotionStateType { // and re-added to the physics engine and "easy" which just updates the body properties. const uint32_t HARD_DIRTY_PHYSICS_FLAGS = (uint32_t)(EntityItem::DIRTY_MOTION_TYPE | EntityItem::DIRTY_SHAPE); const uint32_t EASY_DIRTY_PHYSICS_FLAGS = (uint32_t)(EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES | - EntityItem::DIRTY_MASS | EntityItem::DIRTY_COLLISION_GROUP | - EntityItem::DIRTY_MATERIAL | EntityItem::DIRTY_PHYSICS_ACTIVATION); + EntityItem::DIRTY_MASS | EntityItem::DIRTY_COLLISION_GROUP); // These are the set of incoming flags that the PhysicsEngine needs to hear about: -const uint32_t DIRTY_PHYSICS_FLAGS = HARD_DIRTY_PHYSICS_FLAGS | EASY_DIRTY_PHYSICS_FLAGS; +const uint32_t DIRTY_PHYSICS_FLAGS = HARD_DIRTY_PHYSICS_FLAGS | EASY_DIRTY_PHYSICS_FLAGS | + EntityItem::DIRTY_MATERIAL | (uint32_t)EntityItem::DIRTY_PHYSICS_ACTIVATION; // These are the outgoing flags that the PhysicsEngine can affect: const uint32_t OUTGOING_DIRTY_PHYSICS_FLAGS = EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES; From 1e2e4001b1a03e2f87ccaed532cb4aa80919f0f3 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 14 May 2015 10:24:02 -0700 Subject: [PATCH 09/12] don't allow buildModelMesh to spam logs when it encounters flawed models --- libraries/fbx/src/FBXReader.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 8ab5171fce..464deb1059 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -29,7 +29,7 @@ #include #include #include - +#include #include "FBXReader.h" #include "ModelFormatLogging.h" @@ -1281,9 +1281,11 @@ FBXLight extractLight(const FBXNode& object) { #if USE_MODEL_MESH void buildModelMesh(ExtractedMesh& extracted) { + static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex("buildModelMesh failed -- .*"); + if (extracted.mesh.vertices.size() == 0) { extracted.mesh._mesh = model::Mesh(); - qDebug() << "buildModelMesh failed -- no vertices"; + qCDebug(modelformat) << "buildModelMesh failed -- no vertices"; return; } FBXMesh& fbxMesh = extracted.mesh; @@ -1370,7 +1372,7 @@ void buildModelMesh(ExtractedMesh& extracted) { if (! totalIndices) { extracted.mesh._mesh = model::Mesh(); - qDebug() << "buildModelMesh failed -- no indices"; + qCDebug(modelformat) << "buildModelMesh failed -- no indices"; return; } @@ -1410,7 +1412,7 @@ void buildModelMesh(ExtractedMesh& extracted) { mesh.setPartBuffer(pbv); } else { extracted.mesh._mesh = model::Mesh(); - qDebug() << "buildModelMesh failed -- no parts"; + qCDebug(modelformat) << "buildModelMesh failed -- no parts"; return; } From 3393b60aba92bfc9caa84a1ce0cdb5cf2ddc5383 Mon Sep 17 00:00:00 2001 From: Eric Levin Date: Thu, 14 May 2015 11:39:04 -0700 Subject: [PATCH 10/12] added hydraHockeyGrab.js, which constrains moving object to xz plane and disables rotation --- examples/example/games/hydraGrabHockey.js | 284 ++++++++++++++++++++++ 1 file changed, 284 insertions(+) create mode 100644 examples/example/games/hydraGrabHockey.js diff --git a/examples/example/games/hydraGrabHockey.js b/examples/example/games/hydraGrabHockey.js new file mode 100644 index 0000000000..d995b7014f --- /dev/null +++ b/examples/example/games/hydraGrabHockey.js @@ -0,0 +1,284 @@ +//same as hydraGrab script, but only x-z plane and no rotation +//Also tighter fall off force so can move puck faster + +var addedVelocity, newVelocity, angularVelocity, dT, cameraEntityDistance; +var RIGHT = 1; +var LASER_WIDTH = 3; +var LASER_COLOR = { + red: 50, + green: 150, + blue: 200 +}; +var LASER_HOVER_COLOR = { + red: 200, + green: 50, + blue: 50 +}; + +var DROP_DISTANCE = 5.0; +var DROP_COLOR = { + red: 200, + green: 200, + blue: 200 +}; + +var FULL_STRENGTH = 0.2; +var LASER_LENGTH_FACTOR = 500; +var CLOSE_ENOUGH = 0.001; +var SPRING_RATE = 1.5; +var DAMPING_RATE = 0.8; +var SCREEN_TO_METERS = 0.001; +var DISTANCE_SCALE_FACTOR = 1000 + +var grabSound = SoundCache.getSound("https://hifi-public.s3.amazonaws.com/eric/sounds/CloseClamp.wav"); +var releaseSound = SoundCache.getSound("https://hifi-public.s3.amazonaws.com/eric/sounds/ReleaseClamp.wav"); + +function getRayIntersection(pickRay) { + var intersection = Entities.findRayIntersection(pickRay); + return intersection; +} + + +function controller(side) { + this.triggerHeld = false; + this.triggerThreshold = 0.9; + this.side = side; + this.palm = 2 * side; + this.tip = 2 * side + 1; + this.trigger = side; + this.originalGravity = { + x: 0, + y: 0, + z: 0 + }; + + this.laser = Overlays.addOverlay("line3d", { + start: { + x: 0, + y: 0, + z: 0 + }, + end: { + x: 0, + y: 0, + z: 0 + }, + color: LASER_COLOR, + alpha: 1, + lineWidth: LASER_WIDTH, + anchor: "MyAvatar" + }); + + this.dropLine = Overlays.addOverlay("line3d", { + color: DROP_COLOR, + alpha: 1, + visible: false, + lineWidth: 2 + }); + + + this.update = function(deltaTime) { + this.updateControllerState(); + this.moveLaser(); + this.checkTrigger(); + this.checkEntityIntersection(); + if (this.grabbing) { + this.updateEntity(deltaTime); + } + + this.oldPalmPosition = this.palmPosition; + this.oldTipPosition = this.tipPosition; + } + + this.updateEntity = function(deltaTime) { + this.dControllerPosition = Vec3.subtract(this.palmPosition, this.oldPalmPosition); + this.dControllerPosition.y = 0; + this.cameraEntityDistance = Vec3.distance(Camera.getPosition(), this.currentPosition); + this.targetPosition = Vec3.sum(this.targetPosition, Vec3.multiply(this.dControllerPosition, this.cameraEntityDistance * SCREEN_TO_METERS * DISTANCE_SCALE_FACTOR)); + + this.entityProps = Entities.getEntityProperties(this.grabbedEntity); + this.currentPosition = this.entityProps.position; + this.currentVelocity = this.entityProps.velocity; + + var dPosition = Vec3.subtract(this.targetPosition, this.currentPosition); + this.distanceToTarget = Vec3.length(dPosition); + if (this.distanceToTarget > CLOSE_ENOUGH) { + // compute current velocity in the direction we want to move + this.velocityTowardTarget = Vec3.dot(this.currentVelocity, Vec3.normalize(dPosition)); + this.velocityTowardTarget = Vec3.multiply(Vec3.normalize(dPosition), this.velocityTowardTarget); + // compute the speed we would like to be going toward the target position + + this.desiredVelocity = Vec3.multiply(dPosition, (1.0 / deltaTime) * SPRING_RATE); + // compute how much we want to add to the existing velocity + this.addedVelocity = Vec3.subtract(this.desiredVelocity, this.velocityTowardTarget); + //If target is to far, roll off force as inverse square of distance + if(this.distanceToTarget/ this.cameraEntityDistance > FULL_STRENGTH) { + this.addedVelocity = Vec3.multiply(this.addedVelocity, Math.pow(FULL_STRENGTH/ this.distanceToTarget, 2.0)); + } + this.newVelocity = Vec3.sum(this.currentVelocity, this.addedVelocity); + this.newVelocity = Vec3.subtract(this.newVelocity, Vec3.multiply(this.newVelocity, DAMPING_RATE)); + } else { + this.newVelocity = { + x: 0, + y: 0, + z: 0 + }; + } + Entities.editEntity(this.grabbedEntity, { + velocity: this.newVelocity, + }); + + this.updateDropLine(this.targetPosition); + + } + + + this.updateControllerState = function() { + this.palmPosition = Controller.getSpatialControlPosition(this.palm); + this.tipPosition = Controller.getSpatialControlPosition(this.tip); + this.triggerValue = Controller.getTriggerValue(this.trigger); + } + + this.checkTrigger = function() { + if (this.triggerValue > this.triggerThreshold && !this.triggerHeld) { + this.triggerHeld = true; + } else if (this.triggerValue < this.triggerThreshold && this.triggerHeld) { + this.triggerHeld = false; + if (this.grabbing) { + this.release(); + } + } + } + + + this.updateDropLine = function(position) { + + Overlays.editOverlay(this.dropLine, { + visible: true, + start: { + x: position.x, + y: position.y + DROP_DISTANCE, + z: position.z + }, + end: { + x: position.x, + y: position.y - DROP_DISTANCE, + z: position.z + } + }); + + } + + this.checkEntityIntersection = function() { + + var pickRay = { + origin: this.palmPosition, + direction: Vec3.normalize(Vec3.subtract(this.tipPosition, this.palmPosition)) + }; + var intersection = getRayIntersection(pickRay); + if (intersection.intersects && intersection.properties.collisionsWillMove) { + this.laserWasHovered = true; + if (this.triggerHeld && !this.grabbing) { + this.grab(intersection.entityID); + } + Overlays.editOverlay(this.laser, { + color: LASER_HOVER_COLOR + }); + } else if (this.laserWasHovered) { + this.laserWasHovered = false; + Overlays.editOverlay(this.laser, { + color: LASER_COLOR + }); + } + } + + this.grab = function(entityId) { + this.grabbing = true; + this.grabbedEntity = entityId; + this.entityProps = Entities.getEntityProperties(this.grabbedEntity); + this.targetPosition = this.entityProps.position; + this.currentPosition = this.targetPosition; + this.oldPalmPosition = this.palmPosition; + this.originalGravity = this.entityProps.gravity; + Entities.editEntity(this.grabbedEntity, { + gravity: { + x: 0, + y: 0, + z: 0 + } + }); + Overlays.editOverlay(this.laser, { + visible: false + }); + Audio.playSound(grabSound, { + position: this.entityProps.position, + volume: 0.25 + }); + } + + this.release = function() { + this.grabbing = false; + this.grabbedEntity = null; + Overlays.editOverlay(this.laser, { + visible: true + }); + Overlays.editOverlay(this.dropLine, { + visible: false + }); + + Audio.playSound(releaseSound, { + position: this.entityProps.position, + volume: 0.25 + }); + + // only restore the original gravity if it's not zero. This is to avoid... + // 1. interface A grabs an entity and locally saves off its gravity + // 2. interface A sets the entity's gravity to zero + // 3. interface B grabs the entity and saves off its gravity (which is zero) + // 4. interface A releases the entity and puts the original gravity back + // 5. interface B releases the entity and puts the original gravity back (to zero) + if(vectorIsZero(this.originalGravity)) { + Entities.editEntity(this.grabbedEntity, { + gravity: this.originalGravity + }); + } + } + + this.moveLaser = function() { + var inverseRotation = Quat.inverse(MyAvatar.orientation); + var startPosition = Vec3.multiplyQbyV(inverseRotation, Vec3.subtract(this.palmPosition, MyAvatar.position)); + // startPosition = Vec3.multiply(startPosition, 1 / MyAvatar.scale); + var direction = Vec3.multiplyQbyV(inverseRotation, Vec3.subtract(this.tipPosition, this.palmPosition)); + direction = Vec3.multiply(direction, LASER_LENGTH_FACTOR / (Vec3.length(direction) * MyAvatar.scale)); + var endPosition = Vec3.sum(startPosition, direction); + + Overlays.editOverlay(this.laser, { + start: startPosition, + end: endPosition + }); + + } + + this.cleanup = function() { + Overlays.deleteOverlay(this.laser); + Overlays.deleteOverlay(this.dropLine); + } +} + +function update(deltaTime) { + rightController.update(deltaTime); +} + +function scriptEnding() { + rightController.cleanup(); +} + +function vectorIsZero(v) { + return v.x === 0 && v.y === 0 && v.z === 0; +} + +var rightController = new controller(RIGHT); + + +Script.update.connect(update); +Script.scriptEnding.connect(scriptEnding); \ No newline at end of file From ebb590e58afa6e6862c43df8371cbe14ac8acde4 Mon Sep 17 00:00:00 2001 From: Eric Levin Date: Thu, 14 May 2015 11:46:52 -0700 Subject: [PATCH 11/12] added new hydragrab script which works similarly to mouse grab script, and added header to hydraGrabHockey script --- examples/controllers/hydra/hydraGrab.js | 965 ++++++---------------- examples/example/games/hydraGrabHockey.js | 21 +- 2 files changed, 276 insertions(+), 710 deletions(-) diff --git a/examples/controllers/hydra/hydraGrab.js b/examples/controllers/hydra/hydraGrab.js index dc8cd14eaa..9e49838d88 100644 --- a/examples/controllers/hydra/hydraGrab.js +++ b/examples/controllers/hydra/hydraGrab.js @@ -3,750 +3,301 @@ // examples // // Created by Clément Brisset on 4/24/14. +// Updated by Eric Levin on 5/14/15. // Copyright 2014 High Fidelity, Inc. // -// This script allows you to edit models either with the razor hydras or with your mouse +// This script allows you to grab and move/rotate physical objects with the hydra // // Using the hydras : -// grab models with the triggers, you can then move the models around or scale them with both hands. -// You can switch mode using the bumpers so that you can move models around more easily. +// grab physical entities with the right trigger // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Script.include("../../libraries/entityPropertyDialogBox.js"); -var entityPropertyDialogBox = EntityPropertyDialogBox; -var MIN_ANGULAR_SIZE = 2; -var MAX_ANGULAR_SIZE = 45; -var allowLargeModels = true; -var allowSmallModels = true; -var wantEntityGlow = false; -var LEFT = 0; +var entityProps, currentPosition, currentVelocity, currentRotation, distanceToTarget, velocityTowardTarget, desiredVelocity; +var addedVelocity, newVelocity, angularVelocity, dT, cameraEntityDistance; var RIGHT = 1; - -var jointList = MyAvatar.getJointNames(); - var LASER_WIDTH = 3; -var LASER_COLOR = { red: 50, green: 150, blue: 200 }; -var DROP_COLOR = { red: 200, green: 200, blue: 200 }; -var DROP_WIDTH = 4; +var LASER_COLOR = { + red: 50, + green: 150, + blue: 200 +}; +var LASER_HOVER_COLOR = { + red: 200, + green: 50, + blue: 50 +}; + var DROP_DISTANCE = 5.0; +var DROP_COLOR = { + red: 200, + green: 200, + blue: 200 +}; +var FULL_STRENGTH = 0.05; var LASER_LENGTH_FACTOR = 500; +var CLOSE_ENOUGH = 0.001; +var SPRING_RATE = 1.5; +var DAMPING_RATE = 0.8; +var SCREEN_TO_METERS = 0.001; +var DISTANCE_SCALE_FACTOR = 1000 -var velocity = { x: 0, y: 0, z: 0 }; +var grabSound = SoundCache.getSound("https://hifi-public.s3.amazonaws.com/eric/sounds/CloseClamp.wav"); +var releaseSound = SoundCache.getSound("https://hifi-public.s3.amazonaws.com/eric/sounds/ReleaseClamp.wav"); -var lastAccurateIntersection = null; -var accurateIntersections = 0; -var totalIntersections = 0; -var inaccurateInARow = 0; -var maxInaccurateInARow = 0; -function getRayIntersection(pickRay) { // pickRay : { origin : {xyz}, direction : {xyz} } - if (lastAccurateIntersection === null) { - lastAccurateIntersection = Entities.findRayIntersectionBlocking(pickRay); - } else { - var intersection = Entities.findRayIntersection(pickRay); - if (intersection.accurate) { - lastAccurateIntersection = intersection; - accurateIntersections++; - maxInaccurateInARow = (maxInaccurateInARow > inaccurateInARow) ? maxInaccurateInARow : inaccurateInARow; - inaccurateInARow = 0; - } else { - inaccurateInARow++; - } - totalIntersections++; +function getRayIntersection(pickRay) { + var intersection = Entities.findRayIntersection(pickRay); + return intersection; +} + + +function controller(side) { + this.triggerHeld = false; + this.triggerThreshold = 0.9; + this.side = side; + this.palm = 2 * side; + this.tip = 2 * side + 1; + this.trigger = side; + this.originalGravity = { + x: 0, + y: 0, + z: 0 + }; + + this.laser = Overlays.addOverlay("line3d", { + start: { + x: 0, + y: 0, + z: 0 + }, + end: { + x: 0, + y: 0, + z: 0 + }, + color: LASER_COLOR, + alpha: 1, + lineWidth: LASER_WIDTH, + anchor: "MyAvatar" + }); + + this.dropLine = Overlays.addOverlay("line3d", { + color: DROP_COLOR, + alpha: 1, + visible: false, + lineWidth: 2 + }); + + + this.update = function(deltaTime) { + this.updateControllerState(); + this.moveLaser(); + this.checkTrigger(); + this.checkEntityIntersection(); + if (this.grabbing) { + this.updateEntity(deltaTime); } - return lastAccurateIntersection; -} -function printIntersectionsStats() { - var ratio = accurateIntersections / totalIntersections; - print("Out of " + totalIntersections + " intersections, " + accurateIntersections + " where accurate. (" + ratio * 100 +"%)"); - print("Worst case was " + maxInaccurateInARow + " inaccurate intersections in a row."); -} + this.oldPalmPosition = this.palmPosition; + this.oldTipPosition = this.tipPosition; + } + + this.updateEntity = function(deltaTime) { + this.dControllerPosition = Vec3.subtract(this.palmPosition, this.oldPalmPosition); + this.cameraEntityDistance = Vec3.distance(Camera.getPosition(), this.currentPosition); + this.targetPosition = Vec3.sum(this.targetPosition, Vec3.multiply(this.dControllerPosition, this.cameraEntityDistance * SCREEN_TO_METERS * DISTANCE_SCALE_FACTOR)); + + this.entityProps = Entities.getEntityProperties(this.grabbedEntity); + this.currentPosition = this.entityProps.position; + this.currentVelocity = this.entityProps.velocity; + + var dPosition = Vec3.subtract(this.targetPosition, this.currentPosition); + this.distanceToTarget = Vec3.length(dPosition); + if (this.distanceToTarget > CLOSE_ENOUGH) { + // compute current velocity in the direction we want to move + this.velocityTowardTarget = Vec3.dot(this.currentVelocity, Vec3.normalize(dPosition)); + this.velocityTowardTarget = Vec3.multiply(Vec3.normalize(dPosition), this.velocityTowardTarget); + // compute the speed we would like to be going toward the target position + + this.desiredVelocity = Vec3.multiply(dPosition, (1.0 / deltaTime) * SPRING_RATE); + // compute how much we want to add to the existing velocity + this.addedVelocity = Vec3.subtract(this.desiredVelocity, this.velocityTowardTarget); + //If target is to far, roll off force as inverse square of distance + if(this.distanceToTarget/ this.cameraEntityDistance > FULL_STRENGTH) { + this.addedVelocity = Vec3.multiply(this.addedVelocity, Math.pow(FULL_STRENGTH/ this.distanceToTarget, 2.0)); + } + this.newVelocity = Vec3.sum(this.currentVelocity, this.addedVelocity); + this.newVelocity = Vec3.subtract(this.newVelocity, Vec3.multiply(this.newVelocity, DAMPING_RATE)); + } else { + this.newVelocity = { + x: 0, + y: 0, + z: 0 + }; + } + this.transformedAngularVelocity = Controller.getSpatialControlRawAngularVelocity(this.tip); + this.transformedAngularVelocity = Vec3.multiplyQbyV(Camera.getOrientation(), this.transformedAngularVelocity); + + Entities.editEntity(this.grabbedEntity, { + velocity: this.newVelocity, + angularVelocity: this.transformedAngularVelocity + }); + + this.updateDropLine(this.targetPosition); + + } -function controller(wichSide) { - this.side = wichSide; - this.palm = 2 * wichSide; - this.tip = 2 * wichSide + 1; - this.trigger = wichSide; - this.bumper = 6 * wichSide + 5; - - this.oldPalmPosition = Controller.getSpatialControlPosition(this.palm); - this.palmPosition = this.oldPalmPosition; - - this.oldTipPosition = Controller.getSpatialControlPosition(this.tip); - this.tipPosition = this.oldTipPosition; - - this.oldUp = Controller.getSpatialControlNormal(this.palm); - this.up = this.oldUp; - - this.oldFront = Vec3.normalize(Vec3.subtract(this.tipPosition, this.palmPosition)); - this.front = this.oldFront; - - this.oldRight = Vec3.cross(this.front, this.up); - this.right = this.oldRight; - - this.oldRotation = Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(this.palm)); - this.rotation = this.oldRotation; - + this.updateControllerState = function() { + this.palmPosition = Controller.getSpatialControlPosition(this.palm); + this.tipPosition = Controller.getSpatialControlPosition(this.tip); this.triggerValue = Controller.getTriggerValue(this.trigger); - this.bumperValue = Controller.isButtonPressed(this.bumper); + } - this.pressed = false; // is trigger pressed - this.pressing = false; // is trigger being pressed (is pressed now but wasn't previously) + this.checkTrigger = function() { + if (this.triggerValue > this.triggerThreshold && !this.triggerHeld) { + this.triggerHeld = true; + } else if (this.triggerValue < this.triggerThreshold && this.triggerHeld) { + this.triggerHeld = false; + if (this.grabbing) { + this.release(); + } + } + } + + this.updateDropLine = function(position) { + + Overlays.editOverlay(this.dropLine, { + visible: true, + start: { + x: position.x, + y: position.y + DROP_DISTANCE, + z: position.z + }, + end: { + x: position.x, + y: position.y - DROP_DISTANCE, + z: position.z + } + }); + + } + + this.checkEntityIntersection = function() { + + var pickRay = { + origin: this.palmPosition, + direction: Vec3.normalize(Vec3.subtract(this.tipPosition, this.palmPosition)) + }; + var intersection = getRayIntersection(pickRay); + if (intersection.intersects && intersection.properties.collisionsWillMove) { + this.laserWasHovered = true; + if (this.triggerHeld && !this.grabbing) { + this.grab(intersection.entityID); + } + Overlays.editOverlay(this.laser, { + color: LASER_HOVER_COLOR + }); + } else if (this.laserWasHovered) { + this.laserWasHovered = false; + Overlays.editOverlay(this.laser, { + color: LASER_COLOR + }); + } + } + + this.grab = function(entityId) { + this.grabbing = true; + this.grabbedEntity = entityId; + this.entityProps = Entities.getEntityProperties(this.grabbedEntity); + this.targetPosition = this.entityProps.position; + this.currentPosition = this.targetPosition; + this.oldPalmPosition = this.palmPosition; + this.originalGravity = this.entityProps.gravity; + Entities.editEntity(this.grabbedEntity, { + gravity: { + x: 0, + y: 0, + z: 0 + } + }); + Overlays.editOverlay(this.laser, { + visible: false + }); + Audio.playSound(grabSound, { + position: this.entityProps.position, + volume: 0.25 + }); + } + + this.release = function() { this.grabbing = false; - this.entityID = { isKnownID: false }; - this.modelURL = ""; - this.oldModelRotation; - this.oldModelPosition; - this.oldModelHalfDiagonal; - - this.positionAtGrab; - this.rotationAtGrab; - this.gravityAtGrab; - this.modelPositionAtGrab; - this.modelRotationAtGrab; - this.jointsIntersectingFromStart = []; - - this.laser = Overlays.addOverlay("line3d", { - start: { x: 0, y: 0, z: 0 }, - end: { x: 0, y: 0, z: 0 }, - color: LASER_COLOR, - alpha: 1, - visible: false, - lineWidth: LASER_WIDTH, - anchor: "MyAvatar" + this.grabbedEntity = null; + Overlays.editOverlay(this.laser, { + visible: true + }); + Overlays.editOverlay(this.dropLine, { + visible: false }); - this.dropLine = Overlays.addOverlay("line3d", { - start: { x: 0, y: 0, z: 0 }, - end: { x: 0, y: 0, z: 0 }, - color: DROP_COLOR, - alpha: 1, - visible: false, - lineWidth: DROP_WIDTH }); - - this.guideScale = 0.02; - this.ball = Overlays.addOverlay("sphere", { - position: { x: 0, y: 0, z: 0 }, - size: this.guideScale, - solid: true, - color: { red: 0, green: 255, blue: 0 }, - alpha: 1, - visible: false, - anchor: "MyAvatar" + Audio.playSound(releaseSound, { + position: this.entityProps.position, + volume: 0.25 }); - this.leftRight = Overlays.addOverlay("line3d", { - start: { x: 0, y: 0, z: 0 }, - end: { x: 0, y: 0, z: 0 }, - color: { red: 0, green: 0, blue: 255 }, - alpha: 1, - visible: false, - lineWidth: LASER_WIDTH, - anchor: "MyAvatar" + + // only restore the original gravity if it's not zero. This is to avoid... + // 1. interface A grabs an entity and locally saves off its gravity + // 2. interface A sets the entity's gravity to zero + // 3. interface B grabs the entity and saves off its gravity (which is zero) + // 4. interface A releases the entity and puts the original gravity back + // 5. interface B releases the entity and puts the original gravity back (to zero) + if(vectorIsZero(this.originalGravity)) { + Entities.editEntity(this.grabbedEntity, { + gravity: this.originalGravity + }); + } + } + + this.moveLaser = function() { + var inverseRotation = Quat.inverse(MyAvatar.orientation); + var startPosition = Vec3.multiplyQbyV(inverseRotation, Vec3.subtract(this.palmPosition, MyAvatar.position)); + // startPosition = Vec3.multiply(startPosition, 1 / MyAvatar.scale); + var direction = Vec3.multiplyQbyV(inverseRotation, Vec3.subtract(this.tipPosition, this.palmPosition)); + direction = Vec3.multiply(direction, LASER_LENGTH_FACTOR / (Vec3.length(direction) * MyAvatar.scale)); + var endPosition = Vec3.sum(startPosition, direction); + + Overlays.editOverlay(this.laser, { + start: startPosition, + end: endPosition }); - this.topDown = Overlays.addOverlay("line3d", { - start: { x: 0, y: 0, z: 0 }, - end: { x: 0, y: 0, z: 0 }, - color: { red: 0, green: 0, blue: 255 }, - alpha: 1, - visible: false, - lineWidth: LASER_WIDTH, - anchor: "MyAvatar" - }); - - - this.grab = function (entityID, properties) { - print("Grabbing " + entityID.id); - this.grabbing = true; - this.entityID = entityID; - this.modelURL = properties.modelURL; + } - - this.oldModelPosition = properties.position; - this.oldModelRotation = properties.rotation; - this.oldModelHalfDiagonal = Vec3.length(properties.dimensions) / 2.0; - - this.positionAtGrab = this.palmPosition; - this.rotationAtGrab = this.rotation; - this.modelPositionAtGrab = properties.position; - this.modelRotationAtGrab = properties.rotation; - this.gravityAtGrab = properties.gravity; - Entities.editEntity(entityID, { gravity: { x: 0, y: 0, z: 0 }, velocity: { x: 0, y: 0, z: 0 } }); - - - this.jointsIntersectingFromStart = []; - for (var i = 0; i < jointList.length; i++) { - var distance = Vec3.distance(MyAvatar.getJointPosition(jointList[i]), this.oldModelPosition); - if (distance < this.oldModelHalfDiagonal) { - this.jointsIntersectingFromStart.push(i); - } - } - this.showLaser(false); - Overlays.editOverlay(this.dropLine, { visible: true }); - } - - this.release = function () { - if (this.grabbing) { - - Entities.editEntity(this.entityID, { gravity: this.gravityAtGrab }); - - jointList = MyAvatar.getJointNames(); - - var closestJointIndex = -1; - var closestJointDistance = 10; - for (var i = 0; i < jointList.length; i++) { - var distance = Vec3.distance(MyAvatar.getJointPosition(jointList[i]), this.oldModelPosition); - if (distance < closestJointDistance) { - closestJointDistance = distance; - closestJointIndex = i; - } - } - - if (closestJointIndex != -1) { - print("closestJoint: " + jointList[closestJointIndex]); - print("closestJointDistance (attach max distance): " + closestJointDistance + " (" + this.oldModelHalfDiagonal + ")"); - } - - if (closestJointDistance < this.oldModelHalfDiagonal) { - - if (this.jointsIntersectingFromStart.indexOf(closestJointIndex) != -1 || - (leftController.grabbing && rightController.grabbing && - leftController.entityID.id == rightController.entityID.id)) { - // Do nothing - } else { - print("Attaching to " + jointList[closestJointIndex]); - var jointPosition = MyAvatar.getJointPosition(jointList[closestJointIndex]); - var jointRotation = MyAvatar.getJointCombinedRotation(jointList[closestJointIndex]); - - var attachmentOffset = Vec3.subtract(this.oldModelPosition, jointPosition); - attachmentOffset = Vec3.multiplyQbyV(Quat.inverse(jointRotation), attachmentOffset); - var attachmentRotation = Quat.multiply(Quat.inverse(jointRotation), this.oldModelRotation); - - MyAvatar.attach(this.modelURL, jointList[closestJointIndex], - attachmentOffset, attachmentRotation, 2.0 * this.oldModelHalfDiagonal, - true, false); - Entities.deleteEntity(this.entityID); - } - } - - Overlays.editOverlay(this.dropLine, { visible: false }); - } - - this.grabbing = false; - this.entityID.isKnownID = false; - this.jointsIntersectingFromStart = []; - this.showLaser(true); - } - - this.checkTrigger = function () { - if (this.triggerValue > 0.9) { - if (this.pressed) { - this.pressing = false; - } else { - this.pressing = true; - } - this.pressed = true; - } else { - this.pressing = false; - this.pressed = false; - } - } - - this.checkEntity = function (properties) { - // P P - Model - // /| A - Palm - // / | d B - unit vector toward tip - // / | X - base of the perpendicular line - // A---X----->B d - distance fom axis - // x x - distance from A - // - // |X-A| = (P-A).B - // X == A + ((P-A).B)B - // d = |P-X| - - var A = this.palmPosition; - var B = this.front; - var P = properties.position; - - var x = Vec3.dot(Vec3.subtract(P, A), B); - var y = Vec3.dot(Vec3.subtract(P, A), this.up); - var z = Vec3.dot(Vec3.subtract(P, A), this.right); - var X = Vec3.sum(A, Vec3.multiply(B, x)); - var d = Vec3.length(Vec3.subtract(P, X)); - var halfDiagonal = Vec3.length(properties.dimensions) / 2.0; - - var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), properties.position)) * 180 / 3.14; - - var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE) - && (allowSmallModels || angularSize > MIN_ANGULAR_SIZE); - - if (0 < x && sizeOK) { - return { valid: true, x: x, y: y, z: z }; - } - return { valid: false }; - } - - this.glowedIntersectingModel = { isKnownID: false }; - this.moveLaser = function () { - // the overlays here are anchored to the avatar, which means they are specified in the avatar's local frame - - var inverseRotation = Quat.inverse(MyAvatar.orientation); - var startPosition = Vec3.multiplyQbyV(inverseRotation, Vec3.subtract(this.palmPosition, MyAvatar.position)); - startPosition = Vec3.multiply(startPosition, 1 / MyAvatar.scale); - var direction = Vec3.multiplyQbyV(inverseRotation, Vec3.subtract(this.tipPosition, this.palmPosition)); - direction = Vec3.multiply(direction, LASER_LENGTH_FACTOR / (Vec3.length(direction) * MyAvatar.scale)); - var endPosition = Vec3.sum(startPosition, direction); - - Overlays.editOverlay(this.laser, { - start: startPosition, - end: endPosition - }); - - Overlays.editOverlay(this.ball, { - position: endPosition - }); - Overlays.editOverlay(this.leftRight, { - start: Vec3.sum(endPosition, Vec3.multiply(this.right, 2 * this.guideScale)), - end: Vec3.sum(endPosition, Vec3.multiply(this.right, -2 * this.guideScale)) - }); - Overlays.editOverlay(this.topDown, { - start: Vec3.sum(endPosition, Vec3.multiply(this.up, 2 * this.guideScale)), - end: Vec3.sum(endPosition, Vec3.multiply(this.up, -2 * this.guideScale)) - }); - this.showLaser(!this.grabbing); - - if (this.glowedIntersectingModel.isKnownID) { - Entities.editEntity(this.glowedIntersectingModel, { glowLevel: 0.0 }); - this.glowedIntersectingModel.isKnownID = false; - } - if (!this.grabbing) { - var intersection = getRayIntersection({ origin: this.palmPosition, - direction: this.front - }); - - var halfDiagonal = Vec3.length(intersection.properties.dimensions) / 2.0; - - var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), intersection.properties.position)) * 180 / 3.14; - var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE) - && (allowSmallModels || angularSize > MIN_ANGULAR_SIZE); - if (intersection.accurate && intersection.entityID.isKnownID && sizeOK) { - this.glowedIntersectingModel = intersection.entityID; - - if (wantEntityGlow) { - Entities.editEntity(this.glowedIntersectingModel, { glowLevel: 0.25 }); - } - } - } - } - - this.showLaser = function (show) { - Overlays.editOverlay(this.laser, { visible: show }); - Overlays.editOverlay(this.ball, { visible: show }); - Overlays.editOverlay(this.leftRight, { visible: show }); - Overlays.editOverlay(this.topDown, { visible: show }); - } - this.moveEntity = function (deltaTime) { - if (this.grabbing) { - if (!this.entityID.isKnownID) { - print("Unknown grabbed ID " + this.entityID.id + ", isKnown: " + this.entityID.isKnownID); - this.entityID = getRayIntersection({ origin: this.palmPosition, - direction: this.front - }).entityID; - print("Identified ID " + this.entityID.id + ", isKnown: " + this.entityID.isKnownID); - } - var newPosition; - var newRotation; - - var CONSTANT_SCALING_FACTOR = 5.0; - var MINIMUM_SCALING_DISTANCE = 2.0; - var distanceToModel = Vec3.length(Vec3.subtract(this.oldModelPosition, this.palmPosition)); - if (distanceToModel < MINIMUM_SCALING_DISTANCE) { - distanceToModel = MINIMUM_SCALING_DISTANCE; - } - - var deltaPalm = Vec3.multiply(distanceToModel * CONSTANT_SCALING_FACTOR, Vec3.subtract(this.palmPosition, this.oldPalmPosition)); - newPosition = Vec3.sum(this.oldModelPosition, deltaPalm); - - newRotation = Quat.multiply(this.rotation, - Quat.inverse(this.rotationAtGrab)); - newRotation = Quat.multiply(newRotation, newRotation); - newRotation = Quat.multiply(newRotation, - this.modelRotationAtGrab); - - velocity = Vec3.multiply(1.0 / deltaTime, Vec3.subtract(newPosition, this.oldModelPosition)); - - Entities.editEntity(this.entityID, { - position: newPosition, - rotation: newRotation, - velocity: velocity - }); - this.oldModelRotation = newRotation; - this.oldModelPosition = newPosition; - - Overlays.editOverlay(this.dropLine, { start: newPosition, end: Vec3.sum(newPosition, { x: 0, y: -DROP_DISTANCE, z: 0 }) }); - - var indicesToRemove = []; - for (var i = 0; i < this.jointsIntersectingFromStart.length; ++i) { - var distance = Vec3.distance(MyAvatar.getJointPosition(this.jointsIntersectingFromStart[i]), this.oldModelPosition); - if (distance >= this.oldModelHalfDiagonal) { - indicesToRemove.push(this.jointsIntersectingFromStart[i]); - } - - } - for (var i = 0; i < indicesToRemove.length; ++i) { - this.jointsIntersectingFromStart.splice(this.jointsIntersectingFromStart.indexOf(indicesToRemove[i], 1)); - } - } - } - - this.update = function () { - this.oldPalmPosition = this.palmPosition; - this.oldTipPosition = this.tipPosition; - this.palmPosition = Controller.getSpatialControlPosition(this.palm); - this.tipPosition = Controller.getSpatialControlPosition(this.tip); - - this.oldUp = this.up; - this.up = Vec3.normalize(Controller.getSpatialControlNormal(this.palm)); - - this.oldFront = this.front; - this.front = Vec3.normalize(Vec3.subtract(this.tipPosition, this.palmPosition)); - - this.oldRight = this.right; - this.right = Vec3.normalize(Vec3.cross(this.front, this.up)); - - this.oldRotation = this.rotation; - this.rotation = Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(this.palm)); - - this.triggerValue = Controller.getTriggerValue(this.trigger); - - var bumperValue = Controller.isButtonPressed(this.bumper); - this.bumperValue = bumperValue; - - - this.checkTrigger(); - - this.moveLaser(); - - if (!this.pressed && this.grabbing) { - // release if trigger not pressed anymore. - this.release(); - } - - if (this.pressing) { - // Checking for attachments intersecting - var attachments = MyAvatar.getAttachmentData(); - var attachmentIndex = -1; - var attachmentX = LASER_LENGTH_FACTOR; - - var newModel; - var newProperties; - - for (var i = 0; i < attachments.length; ++i) { - var position = Vec3.sum(MyAvatar.getJointPosition(attachments[i].jointName), - Vec3.multiplyQbyV(MyAvatar.getJointCombinedRotation(attachments[i].jointName), attachments[i].translation)); - var scale = attachments[i].scale; - - var A = this.palmPosition; - var B = this.front; - var P = position; - - var x = Vec3.dot(Vec3.subtract(P, A), B); - var X = Vec3.sum(A, Vec3.multiply(B, x)); - var d = Vec3.length(Vec3.subtract(P, X)); - - if (d < scale / 2.0 && 0 < x && x < attachmentX) { - attachmentIndex = i; - attachmentX = d; - } - } - - if (attachmentIndex != -1) { - print("Detaching: " + attachments[attachmentIndex].modelURL); - MyAvatar.detachOne(attachments[attachmentIndex].modelURL, attachments[attachmentIndex].jointName); - - newProperties = { - type: "Model", - position: Vec3.sum(MyAvatar.getJointPosition(attachments[attachmentIndex].jointName), - Vec3.multiplyQbyV(MyAvatar.getJointCombinedRotation(attachments[attachmentIndex].jointName), attachments[attachmentIndex].translation)), - rotation: Quat.multiply(MyAvatar.getJointCombinedRotation(attachments[attachmentIndex].jointName), - attachments[attachmentIndex].rotation), - - // TODO: how do we know the correct dimensions for detachment??? - dimensions: { x: attachments[attachmentIndex].scale / 2.0, - y: attachments[attachmentIndex].scale / 2.0, - z: attachments[attachmentIndex].scale / 2.0 }, - - modelURL: attachments[attachmentIndex].modelURL - }; - - newModel = Entities.addEntity(newProperties); - - - } else { - // There is none so ... - // Checking model tree - Vec3.print("Looking at: ", this.palmPosition); - var pickRay = { origin: this.palmPosition, - direction: Vec3.normalize(Vec3.subtract(this.tipPosition, this.palmPosition)) }; - var foundIntersection = getRayIntersection(pickRay); - - if(!foundIntersection.intersects) { - print("No intersection"); - return; - } - newModel = foundIntersection.entityID; - if (!newModel.isKnownID) { - var identify = Entities.identifyEntity(newModel); - if (!identify.isKnownID) { - print("Unknown ID " + identify.id + " (update loop " + newModel.id + ")"); - return; - } - newModel = identify; - } - newProperties = Entities.getEntityProperties(newModel); - } - print("foundEntity.modelURL=" + newProperties.modelURL); - var check = this.checkEntity(newProperties); - if (!check.valid) { - return; - } - - this.grab(newModel, newProperties); - - this.x = check.x; - this.y = check.y; - this.z = check.z; - return; - } - } - - this.cleanup = function () { - Overlays.deleteOverlay(this.laser); - Overlays.deleteOverlay(this.ball); - Overlays.deleteOverlay(this.leftRight); - Overlays.deleteOverlay(this.topDown); - } + this.cleanup = function() { + Overlays.deleteOverlay(this.laser); + Overlays.deleteOverlay(this.dropLine); + } } -var leftController = new controller(LEFT); -var rightController = new controller(RIGHT); - -function moveEntities(deltaTime) { - if (leftController.grabbing && rightController.grabbing && rightController.entityID.id == leftController.entityID.id) { - var newPosition = leftController.oldModelPosition; - var rotation = leftController.oldModelRotation; - var ratio = 1; - - var u = Vec3.normalize(Vec3.subtract(rightController.oldPalmPosition, leftController.oldPalmPosition)); - var v = Vec3.normalize(Vec3.subtract(rightController.palmPosition, leftController.palmPosition)); - - var cos_theta = Vec3.dot(u, v); - if (cos_theta > 1) { - cos_theta = 1; - } - var angle = Math.acos(cos_theta) / Math.PI * 180; - if (angle < 0.1) { - return; - } - var w = Vec3.normalize(Vec3.cross(u, v)); - - rotation = Quat.multiply(Quat.angleAxis(angle, w), leftController.oldModelRotation); - - - leftController.positionAtGrab = leftController.palmPosition; - leftController.rotationAtGrab = leftController.rotation; - leftController.modelPositionAtGrab = leftController.oldModelPosition; - leftController.modelRotationAtGrab = rotation; - rightController.positionAtGrab = rightController.palmPosition; - rightController.rotationAtGrab = rightController.rotation; - rightController.modelPositionAtGrab = rightController.oldModelPosition; - rightController.modelRotationAtGrab = rotation; - - Entities.editEntity(leftController.entityID, { - position: newPosition, - rotation: rotation, - // TODO: how do we know the correct dimensions for detachment??? - //radius: leftController.oldModelHalfDiagonal * ratio - dimensions: { x: leftController.oldModelHalfDiagonal * ratio, - y: leftController.oldModelHalfDiagonal * ratio, - z: leftController.oldModelHalfDiagonal * ratio } - - }); - leftController.oldModelPosition = newPosition; - leftController.oldModelRotation = rotation; - leftController.oldModelHalfDiagonal *= ratio; - - rightController.oldModelPosition = newPosition; - rightController.oldModelRotation = rotation; - rightController.oldModelHalfDiagonal *= ratio; - return; - } - leftController.moveEntity(deltaTime); - rightController.moveEntity(deltaTime); -} - -var hydraConnected = false; -function checkController(deltaTime) { - var numberOfButtons = Controller.getNumberOfButtons(); - var numberOfTriggers = Controller.getNumberOfTriggers(); - var numberOfSpatialControls = Controller.getNumberOfSpatialControls(); - var controllersPerTrigger = numberOfSpatialControls / numberOfTriggers; - - // this is expected for hydras - if (numberOfButtons == 12 && numberOfTriggers == 2 && controllersPerTrigger == 2) { - if (!hydraConnected) { - hydraConnected = true; - } - - leftController.update(); - rightController.update(); - moveEntities(deltaTime); - } else { - if (hydraConnected) { - hydraConnected = false; - - leftController.showLaser(false); - rightController.showLaser(false); - } - } -} - -var glowedEntityID = { id: -1, isKnownID: false }; - -// In order for editVoxels and editModels to play nice together, they each check to see if a "delete" menu item already -// exists. If it doesn't they add it. If it does they don't. They also only delete the menu item if they were the one that -// added it. -var ROOT_MENU = "Edit"; -var ITEM_BEFORE = "Physics"; -var MENU_SEPARATOR = "Models"; -var EDIT_PROPERTIES = "Edit Properties..."; -var INTERSECTION_STATS = "Print Intersection Stats"; -var DELETE = "Delete"; -var LARGE_MODELS = "Allow Selecting of Large Models"; -var SMALL_MODELS = "Allow Selecting of Small Models"; - var LIGHTS = "Allow Selecting of Lights"; - -var modelMenuAddedDelete = false; -var originalLightsArePickable = Entities.getLightsArePickable(); -function setupModelMenus() { - print("setupModelMenus()"); - // adj our menuitems - Menu.addMenuItem({ menuName: ROOT_MENU, menuItemName: MENU_SEPARATOR, isSeparator: true, beforeItem: ITEM_BEFORE }); - Menu.addMenuItem({ menuName: ROOT_MENU, menuItemName: EDIT_PROPERTIES, - shortcutKeyEvent: { text: "`" }, afterItem: MENU_SEPARATOR }); - Menu.addMenuItem({ menuName: ROOT_MENU, menuItemName: INTERSECTION_STATS, afterItem: MENU_SEPARATOR }); - if (!Menu.menuItemExists(ROOT_MENU, DELETE)) { - print("no delete... adding ours"); - Menu.addMenuItem({ menuName: ROOT_MENU, menuItemName: DELETE, - shortcutKeyEvent: { text: "backspace" }, afterItem: MENU_SEPARATOR }); - modelMenuAddedDelete = true; - } else { - print("delete exists... don't add ours"); - } - - Menu.addMenuItem({ menuName: ROOT_MENU, menuItemName: LARGE_MODELS, shortcutKey: "CTRL+META+L", - afterItem: DELETE, isCheckable: true }); - Menu.addMenuItem({ menuName: ROOT_MENU, menuItemName: SMALL_MODELS, shortcutKey: "CTRL+META+S", - afterItem: LARGE_MODELS, isCheckable: true }); - Menu.addMenuItem({ menuName: ROOT_MENU, menuItemName: LIGHTS, shortcutKey: "CTRL+SHIFT+META+L", - afterItem: SMALL_MODELS, isCheckable: true }); - - Entities.setLightsArePickable(false); -} - -function cleanupModelMenus() { - Menu.removeSeparator(ROOT_MENU, MENU_SEPARATOR); - Menu.removeMenuItem(ROOT_MENU, EDIT_PROPERTIES); - Menu.removeMenuItem(ROOT_MENU, INTERSECTION_STATS); - if (modelMenuAddedDelete) { - // delete our menuitems - Menu.removeMenuItem(ROOT_MENU, DELETE); - } - - Menu.removeMenuItem(ROOT_MENU, LARGE_MODELS); - Menu.removeMenuItem(ROOT_MENU, SMALL_MODELS); - Menu.removeMenuItem(ROOT_MENU, LIGHTS); - +function update(deltaTime) { + rightController.update(deltaTime); } function scriptEnding() { - leftController.cleanup(); - rightController.cleanup(); - cleanupModelMenus(); - Entities.setLightsArePickable(originalLightsArePickable); -} -Script.scriptEnding.connect(scriptEnding); - -// register the call back so it fires before each data send -Script.update.connect(checkController); - -setupModelMenus(); - -var editModelID = -1; -function showPropertiesForm(editModelID) { - entityPropertyDialogBox.openDialog(editModelID); + rightController.cleanup(); } -Menu.menuItemEvent.connect(function (menuItem) { - print("menuItemEvent() in JS... menuItem=" + menuItem); - if (menuItem == SMALL_MODELS) { - allowSmallModels = Menu.isOptionChecked(SMALL_MODELS); - } else if (menuItem == LARGE_MODELS) { - allowLargeModels = Menu.isOptionChecked(LARGE_MODELS); - } else if (menuItem == LIGHTS) { - Entities.setLightsArePickable(Menu.isOptionChecked(LIGHTS)); - } else if (menuItem == DELETE) { - if (leftController.grabbing) { - print(" Delete Entity.... leftController.entityID="+ leftController.entityID); - Entities.deleteEntity(leftController.entityID); - leftController.grabbing = false; - if (glowedEntityID.id == leftController.entityID.id) { - glowedEntityID = { id: -1, isKnownID: false }; - } - } else if (rightController.grabbing) { - print(" Delete Entity.... rightController.entityID="+ rightController.entityID); - Entities.deleteEntity(rightController.entityID); - rightController.grabbing = false; - if (glowedEntityID.id == rightController.entityID.id) { - glowedEntityID = { id: -1, isKnownID: false }; - } - } else { - print(" Delete Entity.... not holding..."); - } - } else if (menuItem == EDIT_PROPERTIES) { - editModelID = -1; - if (leftController.grabbing) { - print(" Edit Properties.... leftController.entityID="+ leftController.entityID); - editModelID = leftController.entityID; - } else if (rightController.grabbing) { - print(" Edit Properties.... rightController.entityID="+ rightController.entityID); - editModelID = rightController.entityID; - } else { - print(" Edit Properties.... not holding..."); - } - if (editModelID != -1) { - print(" Edit Properties.... about to edit properties..."); - showPropertiesForm(editModelID); - } - } else if (menuItem == INTERSECTION_STATS) { - printIntersectionsStats(); - } -}); +function vectorIsZero(v) { + return v.x === 0 && v.y === 0 && v.z === 0; +} -Controller.keyReleaseEvent.connect(function (event) { - // since sometimes our menu shortcut keys don't work, trap our menu items here also and fire the appropriate menu items - if (event.text == "`") { - handeMenuEvent(EDIT_PROPERTIES); - } - if (event.text == "BACKSPACE") { - handeMenuEvent(DELETE); - } -}); +var rightController = new controller(RIGHT); + +Script.update.connect(update); +Script.scriptEnding.connect(scriptEnding); \ No newline at end of file diff --git a/examples/example/games/hydraGrabHockey.js b/examples/example/games/hydraGrabHockey.js index d995b7014f..9834dbad9f 100644 --- a/examples/example/games/hydraGrabHockey.js +++ b/examples/example/games/hydraGrabHockey.js @@ -1,5 +1,21 @@ -//same as hydraGrab script, but only x-z plane and no rotation -//Also tighter fall off force so can move puck faster +// +// hydraGrabHockey.js +// examples +// +// Created by Eric Levin on 5/14/15. +// Copyright 2015 High Fidelity, Inc. +// +// This script allows you to grab and move physical objects with the hydra +// Same as hydraGrab.js, but you object movement is constrained to xz plane and cannot rotate object +// +// +// Using the hydras : +// grab physical entities with the right hydra trigger +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + var addedVelocity, newVelocity, angularVelocity, dT, cameraEntityDistance; var RIGHT = 1; @@ -38,7 +54,6 @@ function getRayIntersection(pickRay) { return intersection; } - function controller(side) { this.triggerHeld = false; this.triggerThreshold = 0.9; From d28c400cea46fc1b41d50450f48027ec967c3d77 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 14 May 2015 12:36:59 -0700 Subject: [PATCH 12/12] add restitution and friction Entity properties --- examples/html/entityProperties.html | 23 +++++++- libraries/entities/src/EntityItem.cpp | 53 ++++++++++++++----- libraries/entities/src/EntityItem.h | 11 +++- .../entities/src/EntityItemProperties.cpp | 14 +++++ libraries/entities/src/EntityItemProperties.h | 4 ++ .../src/EntityItemPropertiesDefaults.h | 8 +++ libraries/entities/src/EntityPropertyFlags.h | 2 + libraries/networking/src/PacketHeaders.cpp | 2 +- libraries/networking/src/PacketHeaders.h | 1 + 9 files changed, 101 insertions(+), 17 deletions(-) diff --git a/examples/html/entityProperties.html b/examples/html/entityProperties.html index 25467f7573..0e3494bff8 100644 --- a/examples/html/entityProperties.html +++ b/examples/html/entityProperties.html @@ -233,6 +233,9 @@ var elAngularVelocityZ = document.getElementById("property-avel-z"); var elAngularDamping = document.getElementById("property-adamping"); + var elRestitution = document.getElementById("property-restitution"); + var elFriction = document.getElementById("property-friction"); + var elGravityX = document.getElementById("property-grav-x"); var elGravityY = document.getElementById("property-grav-y"); var elGravityZ = document.getElementById("property-grav-z"); @@ -427,6 +430,9 @@ elAngularVelocityZ.value = (properties.angularVelocity.z * RADIANS_TO_DEGREES).toFixed(2); elAngularDamping.value = properties.angularDamping.toFixed(2); + elRestitution.value = properties.restitution.toFixed(2); + elFriction.value = properties.friction.toFixed(2); + elGravityX.value = properties.gravity.x.toFixed(2); elGravityY.value = properties.gravity.y.toFixed(2); elGravityZ.value = properties.gravity.z.toFixed(2); @@ -607,7 +613,10 @@ elAngularVelocityX.addEventListener('change', angularVelocityChangeFunction); elAngularVelocityY.addEventListener('change', angularVelocityChangeFunction); elAngularVelocityZ.addEventListener('change', angularVelocityChangeFunction); - elAngularDamping.addEventListener('change', createEmitNumberPropertyUpdateFunction('angularDamping')) + elAngularDamping.addEventListener('change', createEmitNumberPropertyUpdateFunction('angularDamping')); + + elRestitution.addEventListener('change', createEmitNumberPropertyUpdateFunction('restitution')); + elFriction.addEventListener('change', createEmitNumberPropertyUpdateFunction('friction')); var gravityChangeFunction = createEmitVec3PropertyUpdateFunction( 'gravity', elGravityX, elGravityY, elGravityZ); @@ -963,6 +972,18 @@ +
+
Restitution
+
+ +
+
+
+
Friction
+
+ +
+
Gravity
diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 470426d55e..e5b620210f 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -51,6 +51,8 @@ EntityItem::EntityItem(const EntityItemID& entityItemID) : _gravity(ENTITY_ITEM_DEFAULT_GRAVITY), _acceleration(ENTITY_ITEM_DEFAULT_ACCELERATION), _damping(ENTITY_ITEM_DEFAULT_DAMPING), + _restitution(ENTITY_ITEM_DEFAULT_RESTITUTION), + _friction(ENTITY_ITEM_DEFAULT_FRICTION), _lifetime(ENTITY_ITEM_DEFAULT_LIFETIME), _script(ENTITY_ITEM_DEFAULT_SCRIPT), _collisionSoundURL(ENTITY_ITEM_DEFAULT_COLLISION_SOUND_URL), @@ -100,6 +102,8 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param requestedProperties += PROP_GRAVITY; requestedProperties += PROP_ACCELERATION; requestedProperties += PROP_DAMPING; + requestedProperties += PROP_RESTITUTION; + requestedProperties += PROP_FRICTION; requestedProperties += PROP_LIFETIME; requestedProperties += PROP_SCRIPT; requestedProperties += PROP_COLLISION_SOUND_URL; @@ -227,6 +231,8 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet APPEND_ENTITY_PROPERTY(PROP_GRAVITY, getGravity()); APPEND_ENTITY_PROPERTY(PROP_ACCELERATION, getAcceleration()); APPEND_ENTITY_PROPERTY(PROP_DAMPING, getDamping()); + APPEND_ENTITY_PROPERTY(PROP_RESTITUTION, getRestitution()); + APPEND_ENTITY_PROPERTY(PROP_FRICTION, getFriction()); APPEND_ENTITY_PROPERTY(PROP_LIFETIME, getLifetime()); APPEND_ENTITY_PROPERTY(PROP_SCRIPT, getScript()); APPEND_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, getRegistrationPoint()); @@ -552,7 +558,9 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef READ_ENTITY_PROPERTY(PROP_ACCELERATION, glm::vec3, setAcceleration); } - READ_ENTITY_PROPERTY(PROP_DAMPING, float, setDamping); + READ_ENTITY_PROPERTY(PROP_DAMPING, float, updateDamping); + READ_ENTITY_PROPERTY(PROP_RESTITUTION, float, updateRestitution); + READ_ENTITY_PROPERTY(PROP_FRICTION, float, updateFriction); READ_ENTITY_PROPERTY(PROP_LIFETIME, float, updateLifetime); READ_ENTITY_PROPERTY(PROP_SCRIPT, QString, setScript); READ_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, glm::vec3, setRegistrationPoint); @@ -561,7 +569,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef } else { READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocityInDegrees); } - READ_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, float, setAngularDamping); + READ_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, float, updateAngularDamping); READ_ENTITY_PROPERTY(PROP_VISIBLE, bool, setVisible); READ_ENTITY_PROPERTY(PROP_IGNORE_FOR_COLLISIONS, bool, updateIgnoreForCollisions); READ_ENTITY_PROPERTY(PROP_COLLISIONS_WILL_MOVE, bool, updateCollisionsWillMove); @@ -697,17 +705,6 @@ void EntityItem::setMass(float mass) { } } -const float DEFAULT_ENTITY_RESTITUTION = 0.5f; -const float DEFAULT_ENTITY_FRICTION = 0.5f; - -float EntityItem::getRestitution() const { - return DEFAULT_ENTITY_RESTITUTION; -} - -float EntityItem::getFriction() const { - return DEFAULT_ENTITY_FRICTION; -} - void EntityItem::simulate(const quint64& now) { if (_lastSimulated == 0) { _lastSimulated = now; @@ -900,6 +897,8 @@ EntityItemProperties EntityItem::getProperties() const { COPY_ENTITY_PROPERTY_TO_PROPERTIES(gravity, getGravity); COPY_ENTITY_PROPERTY_TO_PROPERTIES(acceleration, getAcceleration); COPY_ENTITY_PROPERTY_TO_PROPERTIES(damping, getDamping); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(restitution, getRestitution); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(friction, getFriction); COPY_ENTITY_PROPERTY_TO_PROPERTIES(lifetime, getLifetime); COPY_ENTITY_PROPERTY_TO_PROPERTIES(script, getScript); COPY_ENTITY_PROPERTY_TO_PROPERTIES(collisionSoundURL, getCollisionSoundURL); @@ -933,6 +932,8 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(gravity, updateGravity); SET_ENTITY_PROPERTY_FROM_PROPERTIES(acceleration, setAcceleration); SET_ENTITY_PROPERTY_FROM_PROPERTIES(damping, updateDamping); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(restitution, updateRestitution); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(friction, updateFriction); SET_ENTITY_PROPERTY_FROM_PROPERTIES(lifetime, updateLifetime); SET_ENTITY_PROPERTY_FROM_PROPERTIES(script, setScript); SET_ENTITY_PROPERTY_FROM_PROPERTIES(collisionSoundURL, setCollisionSoundURL); @@ -1267,6 +1268,32 @@ void EntityItem::updateCollisionsWillMove(bool value) { } } +void EntityItem::updateRestitution(float value) { + float clampedValue = glm::max(glm::min(ENTITY_ITEM_MAX_RESTITUTION, value), ENTITY_ITEM_MIN_RESTITUTION); + if (_restitution != clampedValue) { + _restitution = clampedValue; + _dirtyFlags |= EntityItem::DIRTY_MATERIAL; + } +} + +void EntityItem::updateFriction(float value) { + float clampedValue = glm::max(glm::min(ENTITY_ITEM_MAX_FRICTION, value), ENTITY_ITEM_MIN_FRICTION); + if (_friction != clampedValue) { + _friction = clampedValue; + _dirtyFlags |= EntityItem::DIRTY_MATERIAL; + } +} + +void EntityItem::setRestitution(float value) { + float clampedValue = glm::max(glm::min(ENTITY_ITEM_MAX_RESTITUTION, value), ENTITY_ITEM_MIN_RESTITUTION); + _restitution = clampedValue; +} + +void EntityItem::setFriction(float value) { + float clampedValue = glm::max(glm::min(ENTITY_ITEM_MAX_FRICTION, value), ENTITY_ITEM_MIN_FRICTION); + _friction = clampedValue; +} + void EntityItem::updateLifetime(float value) { if (_lifetime != value) { _lifetime = value; diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index b9dc14251c..4f51e8896c 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -221,8 +221,11 @@ public: float getDamping() const { return _damping; } void setDamping(float value) { _damping = value; } - float getRestitution() const; - float getFriction() const; + float getRestitution() const { return _restitution; } + void setRestitution(float value); + + float getFriction() const { return _friction; } + void setFriction(float value); // lifetime related properties. float getLifetime() const { return _lifetime; } /// get the lifetime in seconds for the entity @@ -314,6 +317,8 @@ public: void updateVelocityInDomainUnits(const glm::vec3& value); void updateVelocity(const glm::vec3& value); void updateDamping(float value); + void updateRestitution(float value); + void updateFriction(float value); void updateGravityInDomainUnits(const glm::vec3& value); void updateGravity(const glm::vec3& value); void updateAngularVelocity(const glm::vec3& value); @@ -375,6 +380,8 @@ protected: glm::vec3 _gravity; glm::vec3 _acceleration; float _damping; + float _restitution; + float _friction; float _lifetime; QString _script; QString _collisionSoundURL; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 2d68a20022..85f5a5511e 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -44,6 +44,8 @@ EntityItemProperties::EntityItemProperties() : CONSTRUCT_PROPERTY(gravity, ENTITY_ITEM_DEFAULT_GRAVITY), CONSTRUCT_PROPERTY(acceleration, ENTITY_ITEM_DEFAULT_ACCELERATION), CONSTRUCT_PROPERTY(damping, ENTITY_ITEM_DEFAULT_DAMPING), + CONSTRUCT_PROPERTY(restitution, ENTITY_ITEM_DEFAULT_RESTITUTION), + CONSTRUCT_PROPERTY(friction, ENTITY_ITEM_DEFAULT_FRICTION), CONSTRUCT_PROPERTY(lifetime, ENTITY_ITEM_DEFAULT_LIFETIME), CONSTRUCT_PROPERTY(script, ENTITY_ITEM_DEFAULT_SCRIPT), CONSTRUCT_PROPERTY(collisionSoundURL, ENTITY_ITEM_DEFAULT_COLLISION_SOUND_URL), @@ -287,6 +289,8 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_GRAVITY, gravity); CHECK_PROPERTY_CHANGE(PROP_ACCELERATION, acceleration); CHECK_PROPERTY_CHANGE(PROP_DAMPING, damping); + CHECK_PROPERTY_CHANGE(PROP_RESTITUTION, restitution); + CHECK_PROPERTY_CHANGE(PROP_FRICTION, friction); CHECK_PROPERTY_CHANGE(PROP_LIFETIME, lifetime); CHECK_PROPERTY_CHANGE(PROP_SCRIPT, script); CHECK_PROPERTY_CHANGE(PROP_COLLISION_SOUND_URL, collisionSoundURL); @@ -362,6 +366,8 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(gravity); COPY_PROPERTY_TO_QSCRIPTVALUE(acceleration); COPY_PROPERTY_TO_QSCRIPTVALUE(damping); + COPY_PROPERTY_TO_QSCRIPTVALUE(restitution); + COPY_PROPERTY_TO_QSCRIPTVALUE(friction); COPY_PROPERTY_TO_QSCRIPTVALUE(density); COPY_PROPERTY_TO_QSCRIPTVALUE(lifetime); if (!skipDefaults) { @@ -470,6 +476,8 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object) { COPY_PROPERTY_FROM_QSCRIPTVALUE(gravity, glmVec3, setGravity); COPY_PROPERTY_FROM_QSCRIPTVALUE(acceleration, glmVec3, setAcceleration); COPY_PROPERTY_FROM_QSCRIPTVALUE(damping, float, setDamping); + COPY_PROPERTY_FROM_QSCRIPTVALUE(restitution, float, setRestitution); + COPY_PROPERTY_FROM_QSCRIPTVALUE(friction, float, setFriction); COPY_PROPERTY_FROM_QSCRIPTVALUE(lifetime, float, setLifetime); COPY_PROPERTY_FROM_QSCRIPTVALUE(script, QString, setScript); COPY_PROPERTY_FROM_QSCRIPTVALUE(registrationPoint, glmVec3, setRegistrationPoint); @@ -662,6 +670,8 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem APPEND_ENTITY_PROPERTY(PROP_GRAVITY, properties.getGravity()); APPEND_ENTITY_PROPERTY(PROP_ACCELERATION, properties.getAcceleration()); APPEND_ENTITY_PROPERTY(PROP_DAMPING, properties.getDamping()); + APPEND_ENTITY_PROPERTY(PROP_RESTITUTION, properties.getRestitution()); + APPEND_ENTITY_PROPERTY(PROP_FRICTION, properties.getFriction()); APPEND_ENTITY_PROPERTY(PROP_LIFETIME, properties.getLifetime()); APPEND_ENTITY_PROPERTY(PROP_SCRIPT, properties.getScript()); APPEND_ENTITY_PROPERTY(PROP_COLOR, properties.getColor()); @@ -922,6 +932,8 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_GRAVITY, glm::vec3, setGravity); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ACCELERATION, glm::vec3, setAcceleration); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DAMPING, float, setDamping); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_RESTITUTION, float, setRestitution); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_FRICTION, float, setFriction); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LIFETIME, float, setLifetime); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SCRIPT,QString, setScript); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR, xColor, setColor); @@ -1036,6 +1048,8 @@ void EntityItemProperties::markAllChanged() { _gravityChanged = true; _accelerationChanged = true; _dampingChanged = true; + _restitutionChanged = true; + _frictionChanged = true; _lifetimeChanged = true; _userDataChanged = true; _simulatorIDChanged = true; diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 7d8cbfe9b1..530013bbb6 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -95,6 +95,8 @@ public: DEFINE_PROPERTY_REF(PROP_GRAVITY, Gravity, gravity, glm::vec3); DEFINE_PROPERTY_REF(PROP_ACCELERATION, Acceleration, acceleration, glm::vec3); DEFINE_PROPERTY(PROP_DAMPING, Damping, damping, float); + DEFINE_PROPERTY(PROP_RESTITUTION, Restitution, restitution, float); + DEFINE_PROPERTY(PROP_FRICTION, Friction, friction, float); DEFINE_PROPERTY(PROP_LIFETIME, Lifetime, lifetime, float); DEFINE_PROPERTY_REF(PROP_SCRIPT, Script, script, QString); DEFINE_PROPERTY_REF(PROP_COLLISION_SOUND_URL, CollisionSoundURL, collisionSoundURL, QString); @@ -243,6 +245,8 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { DEBUG_PROPERTY_IF_CHANGED(debug, properties, Gravity, gravity, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, Acceleration, acceleration, "in meters per second"); DEBUG_PROPERTY_IF_CHANGED(debug, properties, Damping, damping, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, Restitution, restitution, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, Friction, friction, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, Lifetime, lifetime, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, Script, script, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, CollisionSoundURL, collisionSoundURL, ""); diff --git a/libraries/entities/src/EntityItemPropertiesDefaults.h b/libraries/entities/src/EntityItemPropertiesDefaults.h index ae44322377..b33e6de1ac 100644 --- a/libraries/entities/src/EntityItemPropertiesDefaults.h +++ b/libraries/entities/src/EntityItemPropertiesDefaults.h @@ -53,6 +53,14 @@ const glm::vec3 ENTITY_ITEM_DEFAULT_ACCELERATION = ENTITY_ITEM_ZERO_VEC3; const float ENTITY_ITEM_DEFAULT_DAMPING = 0.39347f; // approx timescale = 2.0 sec (see damping timescale formula in header) const float ENTITY_ITEM_DEFAULT_ANGULAR_DAMPING = 0.39347f; // approx timescale = 2.0 sec (see damping timescale formula in header) +const float ENTITY_ITEM_MIN_RESTITUTION = 0.0f; +const float ENTITY_ITEM_MAX_RESTITUTION = 0.99f; +const float ENTITY_ITEM_DEFAULT_RESTITUTION = 0.5f; + +const float ENTITY_ITEM_MIN_FRICTION = 0.0f; +const float ENTITY_ITEM_MAX_FRICTION = 0.99f; +const float ENTITY_ITEM_DEFAULT_FRICTION = 0.5f; + const bool ENTITY_ITEM_DEFAULT_IGNORE_FOR_COLLISIONS = false; const bool ENTITY_ITEM_DEFAULT_COLLISIONS_WILL_MOVE = false; diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index 66ec70ae60..8a5d96e8d2 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -107,6 +107,8 @@ enum EntityPropertyList { PROP_SIMULATOR_ID, // all entities PROP_NAME, // all entities PROP_COLLISION_SOUND_URL, + PROP_RESTITUTION, + PROP_FRICTION, //////////////////////////////////////////////////////////////////////////////////////////////////// // ATTENTION: add new properties ABOVE this line diff --git a/libraries/networking/src/PacketHeaders.cpp b/libraries/networking/src/PacketHeaders.cpp index 0dcd30a55c..689c958cbb 100644 --- a/libraries/networking/src/PacketHeaders.cpp +++ b/libraries/networking/src/PacketHeaders.cpp @@ -72,7 +72,7 @@ PacketVersion versionForPacketType(PacketType packetType) { return 1; case PacketTypeEntityAddOrEdit: case PacketTypeEntityData: - return VERSION_ENTITIES_HAVE_COLLISION_SOUND_URL; + return VERSION_ENTITIES_HAVE_FRICTION; case PacketTypeEntityErase: return 2; case PacketTypeAudioStreamStats: diff --git a/libraries/networking/src/PacketHeaders.h b/libraries/networking/src/PacketHeaders.h index 01eb488c14..b4e630c677 100644 --- a/libraries/networking/src/PacketHeaders.h +++ b/libraries/networking/src/PacketHeaders.h @@ -177,5 +177,6 @@ const PacketVersion VERSION_ENTITIES_ZONE_ENTITIES_STAGE_HAS_AUTOMATIC_HOURDAY = const PacketVersion VERSION_ENTITIES_PARTICLE_ENTITIES_HAVE_TEXTURES = 23; const PacketVersion VERSION_ENTITIES_HAVE_LINE_TYPE = 24; const PacketVersion VERSION_ENTITIES_HAVE_COLLISION_SOUND_URL = 25; +const PacketVersion VERSION_ENTITIES_HAVE_FRICTION = 26; #endif // hifi_PacketHeaders_h