From 289dcb0e7f7603737c41471109a791438bf6e8bf Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 8 May 2015 15:39:45 -0700 Subject: [PATCH 01/14] 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/14] 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/14] 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/14] 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/14] 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 536fff4502c4d440a8a9e1ecb95c0377422266b9 Mon Sep 17 00:00:00 2001 From: Sam Cake Date: Tue, 12 May 2015 23:11:52 -0700 Subject: [PATCH 06/14] Trying to create the SH from the skybox cubemap --- interface/src/Application.cpp | 2 +- libraries/gpu/src/gpu/Texture.h | 2 +- libraries/model/src/model/Light.cpp | 3 + libraries/model/src/model/Light.h | 1 + libraries/model/src/model/Skybox.cpp | 252 +++++++++++++++++++++++++++ libraries/model/src/model/Skybox.h | 7 + 6 files changed, 265 insertions(+), 2 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 01e094e786..a33b4898b6 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3357,7 +3357,7 @@ void Application::displaySide(Camera& theCamera, bool selfAvatarOnly, RenderArgs auto skyStage = DependencyManager::get()->getSkyStage(); DependencyManager::get()->setGlobalLight(skyStage->getSunLight()->getDirection(), skyStage->getSunLight()->getColor(), skyStage->getSunLight()->getIntensity(), skyStage->getSunLight()->getAmbientIntensity()); DependencyManager::get()->setGlobalAtmosphere(skyStage->getAtmosphere()); - // NOt yet DependencyManager::get()->setGlobalSkybox(skybox); + DependencyManager::get()->setGlobalSkybox(skybox); PROFILE_RANGE("DeferredLighting"); PerformanceTimer perfTimer("lighting"); diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index e4f66c94e6..5d094ddbb4 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -127,7 +127,7 @@ public: CUBE_FACE_LEFT_NEG_X, CUBE_FACE_TOP_POS_Y, CUBE_FACE_BOTTOM_NEG_Y, - CUBE_FACE_BACK_POS_X, + CUBE_FACE_BACK_POS_Z, CUBE_FACE_FRONT_NEG_Z, NUM_CUBE_FACES, // Not a valid vace index diff --git a/libraries/model/src/model/Light.cpp b/libraries/model/src/model/Light.cpp index 6460e0b316..9a2af11664 100755 --- a/libraries/model/src/model/Light.cpp +++ b/libraries/model/src/model/Light.cpp @@ -99,3 +99,6 @@ void Light::setShowContour(float show) { } editSchema()._control.w = show; } + + + diff --git a/libraries/model/src/model/Light.h b/libraries/model/src/model/Light.h index 18d8cc5001..abcb06181f 100755 --- a/libraries/model/src/model/Light.h +++ b/libraries/model/src/model/Light.h @@ -180,6 +180,7 @@ public: } } }; +typedef std::shared_ptr< SphericalHarmonics > SHPointer; class Light { public: diff --git a/libraries/model/src/model/Skybox.cpp b/libraries/model/src/model/Skybox.cpp index 1a54ebd955..7ec15eb008 100755 --- a/libraries/model/src/model/Skybox.cpp +++ b/libraries/model/src/model/Skybox.cpp @@ -49,6 +49,9 @@ void Skybox::clearCubemap() { void Skybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, const Skybox& skybox) { if (skybox.getCubemap() && skybox.getCubemap()->isDefined()) { + + skybox.getAmbientSH(); + static gpu::PipelinePointer thePipeline; static gpu::BufferPointer theBuffer; static gpu::Stream::FormatPointer theFormat; @@ -113,3 +116,252 @@ void Skybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, const Sky batch.clearFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, glm::vec4(skybox.getColor(),1.0f), 0.f, 0); } } + + + +void sphericalHarmonicsAdd(float * result, int order, + const float * inputA, const float * inputB) +{ + const int numCoeff = order * order; + for(int i=0; i < numCoeff; i++) + { + result[i] = inputA[i] + inputB[i]; + } +} + +void sphericalHarmonicsScale(float * result, int order, + const float * input, float scale) +{ + const int numCoeff = order * order; + for(int i=0; i < numCoeff; i++) + { + result[i] = input[i] * scale; + } +} + +void sphericalHarmonicsEvaluateDirection(float * result, int order, + const glm::vec3 & dir) +{ + // calculate coefficients for first 3 bands of spherical harmonics + double p_0_0 = 0.282094791773878140; + double p_1_0 = 0.488602511902919920 * dir.z; + double p_1_1 = -0.488602511902919920; + double p_2_0 = 0.946174695757560080 * dir.z * dir.z - 0.315391565252520050; + double p_2_1 = -1.092548430592079200 * dir.z; + double p_2_2 = 0.546274215296039590; + result[0] = p_0_0; + result[1] = p_1_1 * dir.y; + result[2] = p_1_0; + result[3] = p_1_1 * dir.x; + result[4] = p_2_2 * (dir.x * dir.y + dir.y * dir.x); + result[5] = p_2_1 * dir.y; + result[6] = p_2_0; + result[7] = p_2_1 * dir.x; + result[8] = p_2_2 * (dir.x * dir.x - dir.y * dir.y); +} + +void sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, + std::vector & output, const uint order) +{ + const uint sqOrder = order*order; + + // allocate memory for calculations + output.resize(sqOrder); + std::vector resultR(sqOrder); + std::vector resultG(sqOrder); + std::vector resultB(sqOrder); + + // variables that describe current face of cube texture + //std::unique_ptr data; + GLint width, height; + GLint internalFormat; + GLint numComponents; + + // initialize values + float fWt = 0.0f; + for(uint i=0; i < sqOrder; i++) + { + output[i].x = 0; + output[i].y = 0; + output[i].z = 0; + resultR[i] = 0; + resultG[i] = 0; + resultB[i] = 0; + } + std::vector shBuff(sqOrder); + std::vector shBuffB(sqOrder); + + // bind current texture + // glBindTexture(GL_TEXTURE_CUBE_MAP, cubeTexture->texture()); + // for each face of cube texture + for(int face=0; face < 6; face++) + { + // get width and height + // glGetTexLevelParameteriv(cubeSides[face], 0, GL_TEXTURE_WIDTH, &width); + // glGetTexLevelParameteriv(cubeSides[face], 0, GL_TEXTURE_HEIGHT, &height); + + width = height = cubeTexture.getWidth(); + if(width != height) + { + return; + } + + + numComponents = cubeTexture.accessStoredMipFace(0,face)->_format.getDimensionCount(); + + auto data = cubeTexture.accessStoredMipFace(0,face)->_sysmem.readData(); + + // step between two texels for range [0, 1] + float invWidth = 1.0f / float(width); + // initial negative bound for range [-1, 1] + float negativeBound = -1.0f + invWidth; + // step between two texels for range [-1, 1] + float invWidthBy2 = 2.0f / float(width); + + for(int y=0; y < width; y++) + { + // texture coordinate V in range [-1 to 1] + const float fV = negativeBound + float(y) * invWidthBy2; + + for(int x=0; x < width; x++) + { + // texture coordinate U in range [-1 to 1] + const float fU = negativeBound + float(x) * invWidthBy2; + + // determine direction from center of cube texture to current texel + glm::vec3 dir; + switch(face) + { + case gpu::Texture::CUBE_FACE_RIGHT_POS_X: + dir.x = 1.0f; + dir.y = 1.0f - (invWidthBy2 * float(y) + invWidth); + dir.z = 1.0f - (invWidthBy2 * float(x) + invWidth); + dir = -dir; + break; + case gpu::Texture::CUBE_FACE_LEFT_NEG_X: + dir.x = -1.0f; + dir.y = 1.0f - (invWidthBy2 * float(y) + invWidth); + dir.z = -1.0f + (invWidthBy2 * float(x) + invWidth); + dir = -dir; + break; + case gpu::Texture::CUBE_FACE_TOP_POS_Y: + dir.x = - 1.0f + (invWidthBy2 * float(x) + invWidth); + dir.y = 1.0f; + dir.z = - 1.0f + (invWidthBy2 * float(y) + invWidth); + dir = -dir; + break; + case gpu::Texture::CUBE_FACE_BOTTOM_NEG_Y: + dir.x = - 1.0f + (invWidthBy2 * float(x) + invWidth); + dir.y = - 1.0f; + dir.z = 1.0f - (invWidthBy2 * float(y) + invWidth); + dir = -dir; + break; + case gpu::Texture::CUBE_FACE_BACK_POS_Z: + dir.x = - 1.0f + (invWidthBy2 * float(x) + invWidth); + dir.y = 1.0f - (invWidthBy2 * float(y) + invWidth); + dir.z = 1.0f; + break; + case gpu::Texture::CUBE_FACE_FRONT_NEG_Z: + dir.x = 1.0f - (invWidthBy2 * float(x) + invWidth); + dir.y = 1.0f - (invWidthBy2 * float(y) + invWidth); + dir.z = - 1.0f; + break; + default: + return; + } + + // normalize direction + dir = glm::normalize(dir); + + // scale factor depending on distance from center of the face + const float fDiffSolid = 4.0f / ((1.0f + fU*fU + fV*fV) * + sqrtf(1.0f + fU*fU + fV*fV)); + fWt += fDiffSolid; + + // calculate coefficients of spherical harmonics for current direction + sphericalHarmonicsEvaluateDirection(shBuff.data(), order, dir); + + // index of texel in texture + uint pixOffsetIndex = (x + y * width) * numComponents; + // get color from texture and map to range [0, 1] + glm::vec3 clr( + float(data[pixOffsetIndex]) / 255, + float(data[pixOffsetIndex+1]) / 255, + float(data[pixOffsetIndex+2]) / 255 + ); + + // scale color and add to previously accumulated coefficients + sphericalHarmonicsScale(shBuffB.data(), order, + shBuff.data(), clr.r * fDiffSolid); + sphericalHarmonicsAdd(resultR.data(), order, + resultR.data(), shBuffB.data()); + sphericalHarmonicsScale(shBuffB.data(), order, + shBuff.data(), clr.g * fDiffSolid); + sphericalHarmonicsAdd(resultG.data(), order, + resultG.data(), shBuffB.data()); + sphericalHarmonicsScale(shBuffB.data(), order, + shBuff.data(), clr.b * fDiffSolid); + sphericalHarmonicsAdd(resultB.data(), order, + resultB.data(), shBuffB.data()); + } + } + } + + // final scale for coefficients + const float fNormProj = (4.0f * glm::pi()) / fWt; + sphericalHarmonicsScale(resultR.data(), order, resultR.data(), fNormProj); + sphericalHarmonicsScale(resultG.data(), order, resultG.data(), fNormProj); + sphericalHarmonicsScale(resultB.data(), order, resultB.data(), fNormProj); + + // save result + for(uint i=0; i < sqOrder; i++) + { + output[i].r = resultR[i]; + output[i].g = resultG[i]; + output[i].b = resultB[i]; + } +} +/* +glm::vec3 sphericalHarmonicsFromTexture(glm::vec3 & N, std::vector & coef) +{ + return + + // constant term, lowest frequency ////// + C4 * coef[0] + + + // axis aligned terms /////////////////// + 2.0 * C2 * coef[1] * N.y + + 2.0 * C2 * coef[2] * N.z + + 2.0 * C2 * coef[3] * N.x + + + // band 2 terms ///////////////////////// + 2.0 * C1 * coef[4] * N.x * N.y + + 2.0 * C1 * coef[5] * N.y * N.z + + C3 * coef[6] * N.z * N.z - C5 * coef[6] + + 2.0 * C1 * coef[7] * N.x * N.z + + C1 * coef[8] * (N.x * N.x - N.y * N.y); +} +*/ + +const SphericalHarmonics& Skybox::getAmbientSH() const { + if (!_isSHValid) { + if (_cubemap && _cubemap->isDefined()) { + + std::vector< glm::vec3 > coefs(10, glm::vec3(0.0f)); + sphericalHarmonicsFromTexture(*_cubemap, coefs, 2); + + _ambientSH.L00 = coefs[0]; + _ambientSH.L1m1 = coefs[1]; + _ambientSH.L10 = coefs[2]; + _ambientSH.L11 = coefs[3]; + _ambientSH.L2m2 = coefs[4]; + _ambientSH.L2m1 = coefs[5]; + _ambientSH.L20 = coefs[6]; + _ambientSH.L21 = coefs[7]; + _ambientSH.L22 = coefs[8]; + + _isSHValid = true; + } + } + return _ambientSH; +} diff --git a/libraries/model/src/model/Skybox.h b/libraries/model/src/model/Skybox.h index b739032f39..c66cbfb627 100755 --- a/libraries/model/src/model/Skybox.h +++ b/libraries/model/src/model/Skybox.h @@ -13,6 +13,8 @@ #include "gpu/Texture.h" +#include "Light.h" + class ViewFrustum; //class Transform; namespace gpu { class Batch; } @@ -34,11 +36,16 @@ public: const gpu::TexturePointer& getCubemap() const { return _cubemap; } void clearCubemap(); + const SphericalHarmonics& getAmbientSH() const; + static void render(gpu::Batch& batch, const ViewFrustum& frustum, const Skybox& skybox); protected: gpu::TexturePointer _cubemap; + mutable SphericalHarmonics _ambientSH; + mutable bool _isSHValid = false; + Color _color{1.0f, 1.0f, 1.0f}; }; typedef std::shared_ptr< Skybox > SkyboxPointer; From fb34a5ba842a69495d6f0367349c3d32597ec4c4 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 13 May 2015 08:44:10 -0700 Subject: [PATCH 07/14] 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 65e83df214b2996723f2572fb6ce98eca22d4e41 Mon Sep 17 00:00:00 2001 From: Sam Cake Date: Wed, 13 May 2015 11:08:18 -0700 Subject: [PATCH 08/14] Generating the SH from the cube map! --- libraries/gpu/src/gpu/Texture.cpp | 2 +- libraries/model/src/model/Skybox.cpp | 5 ++++- libraries/model/src/model/Skybox.slf | 3 ++- libraries/render-utils/src/DeferredGlobalLight.slh | 4 ++-- libraries/render-utils/src/DeferredLightingEffect.cpp | 7 ++++++- libraries/render-utils/src/TextureCache.cpp | 3 ++- 6 files changed, 17 insertions(+), 7 deletions(-) diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp index 07bb73a375..542a47c915 100755 --- a/libraries/gpu/src/gpu/Texture.cpp +++ b/libraries/gpu/src/gpu/Texture.cpp @@ -57,7 +57,7 @@ const Texture::PixelsPointer Texture::Storage::getMipFace(uint16 level, uint8 fa void Texture::Storage::notifyMipFaceGPULoaded(uint16 level, uint8 face) const { PixelsPointer mipFace = getMipFace(level, face); - if (mipFace) { + if (mipFace && (_type != TEX_CUBE)) { mipFace->_isGPULoaded = true; mipFace->_sysmem.resize(0); } diff --git a/libraries/model/src/model/Skybox.cpp b/libraries/model/src/model/Skybox.cpp index 7ec15eb008..f9d2905864 100755 --- a/libraries/model/src/model/Skybox.cpp +++ b/libraries/model/src/model/Skybox.cpp @@ -39,6 +39,9 @@ void Skybox::setColor(const Color& color) { } void Skybox::setCubemap(const gpu::TexturePointer& cubemap) { + if (_isSHValid && (cubemap != _cubemap)) { + _isSHValid = false; + } _cubemap = cubemap; } @@ -348,7 +351,7 @@ const SphericalHarmonics& Skybox::getAmbientSH() const { if (_cubemap && _cubemap->isDefined()) { std::vector< glm::vec3 > coefs(10, glm::vec3(0.0f)); - sphericalHarmonicsFromTexture(*_cubemap, coefs, 2); + sphericalHarmonicsFromTexture(*_cubemap, coefs, 3); _ambientSH.L00 = coefs[0]; _ambientSH.L1m1 = coefs[1]; diff --git a/libraries/model/src/model/Skybox.slf b/libraries/model/src/model/Skybox.slf index 5739cfda34..abb30bf09c 100755 --- a/libraries/model/src/model/Skybox.slf +++ b/libraries/model/src/model/Skybox.slf @@ -20,5 +20,6 @@ varying vec3 color; void main(void) { vec3 coord = normalize(normal); vec4 texel = textureCube(cubeMap, coord); - gl_FragData[0] = vec4(texel.xyz * color, 0.0); + vec3 pixel = pow(texel.xyz * color, vec3(1.0/2.2)); // manual Gamma correction + gl_FragData[0] = vec4(pixel, 0.0); } diff --git a/libraries/render-utils/src/DeferredGlobalLight.slh b/libraries/render-utils/src/DeferredGlobalLight.slh index d676a9c6b7..2f312e42d8 100755 --- a/libraries/render-utils/src/DeferredGlobalLight.slh +++ b/libraries/render-utils/src/DeferredGlobalLight.slh @@ -111,8 +111,8 @@ vec3 evalSkyboxGlobalColor(float shadowAttenuation, vec3 position, vec3 normal, vec4 fragEyeVector = invViewMat * vec4(-position, 0.0); vec3 fragEyeDir = normalize(fragEyeVector.xyz); - vec3 color = diffuse.rgb * evalSkyboxLight(fragNormal, 0.75).xyz * getLightAmbientIntensity(light); - + vec3 color = diffuse.rgb * evalSphericalLight(ambientSphere, fragNormal).xyz * getLightAmbientIntensity(light); + vec4 shading = evalFragShading(fragNormal, -getLightDirection(light), fragEyeDir, specular, gloss); color += vec3(diffuse + shading.rgb) * shading.w * shadowAttenuation * getLightColor(light) * getLightIntensity(light); diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index 9b0c5647ed..d9a4aa0378 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -291,7 +291,12 @@ void DeferredLightingEffect::render() { auto globalLight = _allocatedLights[_globalLights.front()]; if (locations->ambientSphere >= 0) { - auto sh = globalLight->getAmbientSphere(); + model::SphericalHarmonics sh; + if (useSkyboxCubemap) { + sh = _skybox->getAmbientSH(); + } else { + sh = globalLight->getAmbientSphere(); + } for (int i =0; i setUniformValue(locations->ambientSphere + i, *(((QVector4D*) &sh) + i)); } diff --git a/libraries/render-utils/src/TextureCache.cpp b/libraries/render-utils/src/TextureCache.cpp index c1c8ec030f..a6c932b4d8 100644 --- a/libraries/render-utils/src/TextureCache.cpp +++ b/libraries/render-utils/src/TextureCache.cpp @@ -514,7 +514,8 @@ void NetworkTexture::setImage(const QImage& image, bool translucent, const QColo if ((_width > 0) && (_height > 0)) { - bool isLinearRGB = true; //(_type == NORMAL_TEXTURE) || (_type == EMISSIVE_TEXTURE); + // bool isLinearRGB = true; //(_type == NORMAL_TEXTURE) || (_type == EMISSIVE_TEXTURE); + bool isLinearRGB = !(_type == CUBE_TEXTURE); //(_type == NORMAL_TEXTURE) || (_type == EMISSIVE_TEXTURE); gpu::Element formatGPU = gpu::Element(gpu::VEC3, gpu::UINT8, (isLinearRGB ? gpu::RGB : gpu::SRGB)); gpu::Element formatMip = gpu::Element(gpu::VEC3, gpu::UINT8, (isLinearRGB ? gpu::RGB : gpu::SRGB)); From 57fa3d8c5325a07b0a32aa8cffe30727a2b6983a Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 13 May 2015 14:43:31 -0700 Subject: [PATCH 09/14] 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 238d3751c5ef88bac35c7a79ec3894850c450f82 Mon Sep 17 00:00:00 2001 From: Sam Cake Date: Wed, 13 May 2015 16:00:05 -0700 Subject: [PATCH 10/14] cleaning up the SH generation --- libraries/model/src/model/Skybox.cpp | 411 ++++++++---------- libraries/model/src/model/Skybox.h | 4 +- .../src/DeferredLightingEffect.cpp | 2 +- libraries/render-utils/src/TextureCache.cpp | 39 ++ 4 files changed, 233 insertions(+), 223 deletions(-) diff --git a/libraries/model/src/model/Skybox.cpp b/libraries/model/src/model/Skybox.cpp index f9d2905864..ab11966188 100755 --- a/libraries/model/src/model/Skybox.cpp +++ b/libraries/model/src/model/Skybox.cpp @@ -53,7 +53,7 @@ void Skybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, const Sky if (skybox.getCubemap() && skybox.getCubemap()->isDefined()) { - skybox.getAmbientSH(); + skybox.getIrradianceSH(); static gpu::PipelinePointer thePipeline; static gpu::BufferPointer theBuffer; @@ -121,250 +121,221 @@ void Skybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, const Sky } +glm::vec3 sRGBToLinear(glm::vec3& color) { + const float GAMMA_CORRECTION = 2.2f; + return glm::pow(color, glm::vec3(GAMMA_CORRECTION)); +} -void sphericalHarmonicsAdd(float * result, int order, - const float * inputA, const float * inputB) -{ +glm::vec3 linearTosRGB(glm::vec3& color) { + const float GAMMA_CORRECTION_INV = 1.0f / 2.2f; + return glm::pow(color, glm::vec3(GAMMA_CORRECTION_INV)); +} + +// Originial code for the Spherical Harmonics taken from "Sun and Black Cat- Igor Dykhta (igor dykhta email) © 2007-2014 " +void sphericalHarmonicsAdd(float * result, int order, const float * inputA, const float * inputB) { const int numCoeff = order * order; - for(int i=0; i < numCoeff; i++) - { + for(int i=0; i < numCoeff; i++) { result[i] = inputA[i] + inputB[i]; } } -void sphericalHarmonicsScale(float * result, int order, - const float * input, float scale) -{ +void sphericalHarmonicsScale(float * result, int order, const float * input, float scale) { const int numCoeff = order * order; - for(int i=0; i < numCoeff; i++) - { + for(int i=0; i < numCoeff; i++) { result[i] = input[i] * scale; } } -void sphericalHarmonicsEvaluateDirection(float * result, int order, - const glm::vec3 & dir) -{ +void sphericalHarmonicsEvaluateDirection(float * result, int order, const glm::vec3 & dir) { // calculate coefficients for first 3 bands of spherical harmonics - double p_0_0 = 0.282094791773878140; - double p_1_0 = 0.488602511902919920 * dir.z; - double p_1_1 = -0.488602511902919920; - double p_2_0 = 0.946174695757560080 * dir.z * dir.z - 0.315391565252520050; - double p_2_1 = -1.092548430592079200 * dir.z; - double p_2_2 = 0.546274215296039590; - result[0] = p_0_0; - result[1] = p_1_1 * dir.y; - result[2] = p_1_0; - result[3] = p_1_1 * dir.x; - result[4] = p_2_2 * (dir.x * dir.y + dir.y * dir.x); - result[5] = p_2_1 * dir.y; - result[6] = p_2_0; - result[7] = p_2_1 * dir.x; - result[8] = p_2_2 * (dir.x * dir.x - dir.y * dir.y); + double P_0_0 = 0.282094791773878140; + double P_1_0 = 0.488602511902919920 * dir.z; + double P_1_1 = -0.488602511902919920; + double P_2_0 = 0.946174695757560080 * dir.z * dir.z - 0.315391565252520050; + double P_2_1 = -1.092548430592079200 * dir.z; + double P_2_2 = 0.546274215296039590; + result[0] = P_0_0; + result[1] = P_1_1 * dir.y; + result[2] = P_1_0; + result[3] = P_1_1 * dir.x; + result[4] = P_2_2 * (dir.x * dir.y + dir.y * dir.x); + result[5] = P_2_1 * dir.y; + result[6] = P_2_0; + result[7] = P_2_1 * dir.x; + result[8] = P_2_2 * (dir.x * dir.x - dir.y * dir.y); } -void sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, - std::vector & output, const uint order) -{ - const uint sqOrder = order*order; +void sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector & output, const uint order) { + const uint sqOrder = order*order; - // allocate memory for calculations - output.resize(sqOrder); - std::vector resultR(sqOrder); - std::vector resultG(sqOrder); - std::vector resultB(sqOrder); + // allocate memory for calculations + output.resize(sqOrder); + std::vector resultR(sqOrder); + std::vector resultG(sqOrder); + std::vector resultB(sqOrder); - // variables that describe current face of cube texture - //std::unique_ptr data; - GLint width, height; - GLint internalFormat; - GLint numComponents; - - // initialize values - float fWt = 0.0f; - for(uint i=0; i < sqOrder; i++) - { - output[i].x = 0; - output[i].y = 0; - output[i].z = 0; - resultR[i] = 0; - resultG[i] = 0; - resultB[i] = 0; - } - std::vector shBuff(sqOrder); - std::vector shBuffB(sqOrder); - - // bind current texture - // glBindTexture(GL_TEXTURE_CUBE_MAP, cubeTexture->texture()); - // for each face of cube texture - for(int face=0; face < 6; face++) - { - // get width and height - // glGetTexLevelParameteriv(cubeSides[face], 0, GL_TEXTURE_WIDTH, &width); - // glGetTexLevelParameteriv(cubeSides[face], 0, GL_TEXTURE_HEIGHT, &height); + int width, height; + // initialize values + float fWt = 0.0f; + for(uint i=0; i < sqOrder; i++) { + output[i] = glm::vec3(0.0f); + resultR[i] = 0.0f; + resultG[i] = 0; + resultB[i] = 0; + } + std::vector shBuff(sqOrder); + std::vector shBuffB(sqOrder); + // get width and height width = height = cubeTexture.getWidth(); - if(width != height) - { - return; - } + if(width != height) { + return; + } - - numComponents = cubeTexture.accessStoredMipFace(0,face)->_format.getDimensionCount(); + const float UCHAR_TO_FLOAT = 1.0f / float(std::numeric_limits::max()); - auto data = cubeTexture.accessStoredMipFace(0,face)->_sysmem.readData(); + // for each face of cube texture + for(int face=0; face < gpu::Texture::NUM_CUBE_FACES; face++) { - // step between two texels for range [0, 1] - float invWidth = 1.0f / float(width); - // initial negative bound for range [-1, 1] - float negativeBound = -1.0f + invWidth; - // step between two texels for range [-1, 1] - float invWidthBy2 = 2.0f / float(width); + auto numComponents = cubeTexture.accessStoredMipFace(0,face)->_format.getDimensionCount(); + auto data = cubeTexture.accessStoredMipFace(0,face)->_sysmem.readData(); + if (data == nullptr) { + continue; + } - for(int y=0; y < width; y++) - { - // texture coordinate V in range [-1 to 1] - const float fV = negativeBound + float(y) * invWidthBy2; + // step between two texels for range [0, 1] + float invWidth = 1.0f / float(width); + // initial negative bound for range [-1, 1] + float negativeBound = -1.0f + invWidth; + // step between two texels for range [-1, 1] + float invWidthBy2 = 2.0f / float(width); - for(int x=0; x < width; x++) - { - // texture coordinate U in range [-1 to 1] - const float fU = negativeBound + float(x) * invWidthBy2; + for(int y=0; y < width; y++) { + // texture coordinate V in range [-1 to 1] + const float fV = negativeBound + float(y) * invWidthBy2; - // determine direction from center of cube texture to current texel - glm::vec3 dir; - switch(face) - { - case gpu::Texture::CUBE_FACE_RIGHT_POS_X: - dir.x = 1.0f; - dir.y = 1.0f - (invWidthBy2 * float(y) + invWidth); - dir.z = 1.0f - (invWidthBy2 * float(x) + invWidth); - dir = -dir; - break; - case gpu::Texture::CUBE_FACE_LEFT_NEG_X: - dir.x = -1.0f; - dir.y = 1.0f - (invWidthBy2 * float(y) + invWidth); - dir.z = -1.0f + (invWidthBy2 * float(x) + invWidth); - dir = -dir; - break; - case gpu::Texture::CUBE_FACE_TOP_POS_Y: - dir.x = - 1.0f + (invWidthBy2 * float(x) + invWidth); - dir.y = 1.0f; - dir.z = - 1.0f + (invWidthBy2 * float(y) + invWidth); - dir = -dir; - break; - case gpu::Texture::CUBE_FACE_BOTTOM_NEG_Y: - dir.x = - 1.0f + (invWidthBy2 * float(x) + invWidth); - dir.y = - 1.0f; - dir.z = 1.0f - (invWidthBy2 * float(y) + invWidth); - dir = -dir; - break; - case gpu::Texture::CUBE_FACE_BACK_POS_Z: - dir.x = - 1.0f + (invWidthBy2 * float(x) + invWidth); - dir.y = 1.0f - (invWidthBy2 * float(y) + invWidth); - dir.z = 1.0f; - break; - case gpu::Texture::CUBE_FACE_FRONT_NEG_Z: - dir.x = 1.0f - (invWidthBy2 * float(x) + invWidth); - dir.y = 1.0f - (invWidthBy2 * float(y) + invWidth); - dir.z = - 1.0f; - break; - default: - return; + for(int x=0; x < width; x++) { + // texture coordinate U in range [-1 to 1] + const float fU = negativeBound + float(x) * invWidthBy2; + + // determine direction from center of cube texture to current texel + glm::vec3 dir; + switch(face) { + case gpu::Texture::CUBE_FACE_RIGHT_POS_X: { + dir.x = 1.0f; + dir.y = 1.0f - (invWidthBy2 * float(y) + invWidth); + dir.z = 1.0f - (invWidthBy2 * float(x) + invWidth); + dir = -dir; + break; + } + case gpu::Texture::CUBE_FACE_LEFT_NEG_X: { + dir.x = -1.0f; + dir.y = 1.0f - (invWidthBy2 * float(y) + invWidth); + dir.z = -1.0f + (invWidthBy2 * float(x) + invWidth); + dir = -dir; + break; + } + case gpu::Texture::CUBE_FACE_TOP_POS_Y: { + dir.x = - 1.0f + (invWidthBy2 * float(x) + invWidth); + dir.y = 1.0f; + dir.z = - 1.0f + (invWidthBy2 * float(y) + invWidth); + dir = -dir; + break; + } + case gpu::Texture::CUBE_FACE_BOTTOM_NEG_Y: { + dir.x = - 1.0f + (invWidthBy2 * float(x) + invWidth); + dir.y = - 1.0f; + dir.z = 1.0f - (invWidthBy2 * float(y) + invWidth); + dir = -dir; + break; + } + case gpu::Texture::CUBE_FACE_BACK_POS_Z: { + dir.x = - 1.0f + (invWidthBy2 * float(x) + invWidth); + dir.y = 1.0f - (invWidthBy2 * float(y) + invWidth); + dir.z = 1.0f; + break; + } + case gpu::Texture::CUBE_FACE_FRONT_NEG_Z: { + dir.x = 1.0f - (invWidthBy2 * float(x) + invWidth); + dir.y = 1.0f - (invWidthBy2 * float(y) + invWidth); + dir.z = - 1.0f; + break; + } + default: + return; + } + + // normalize direction + dir = glm::normalize(dir); + + // scale factor depending on distance from center of the face + const float fDiffSolid = 4.0f / ((1.0f + fU*fU + fV*fV) * + sqrtf(1.0f + fU*fU + fV*fV)); + fWt += fDiffSolid; + + // calculate coefficients of spherical harmonics for current direction + sphericalHarmonicsEvaluateDirection(shBuff.data(), order, dir); + + // index of texel in texture + uint pixOffsetIndex = (x + y * width) * numComponents; + + // get color from texture and map to range [0, 1] + glm::vec3 clr(float(data[pixOffsetIndex]) * UCHAR_TO_FLOAT, + float(data[pixOffsetIndex+1]) * UCHAR_TO_FLOAT, + float(data[pixOffsetIndex+2]) * UCHAR_TO_FLOAT); + + // Gamma correct + clr = sRGBToLinear(clr); + + // scale color and add to previously accumulated coefficients + sphericalHarmonicsScale(shBuffB.data(), order, + shBuff.data(), clr.r * fDiffSolid); + sphericalHarmonicsAdd(resultR.data(), order, + resultR.data(), shBuffB.data()); + sphericalHarmonicsScale(shBuffB.data(), order, + shBuff.data(), clr.g * fDiffSolid); + sphericalHarmonicsAdd(resultG.data(), order, + resultG.data(), shBuffB.data()); + sphericalHarmonicsScale(shBuffB.data(), order, + shBuff.data(), clr.b * fDiffSolid); + sphericalHarmonicsAdd(resultB.data(), order, + resultB.data(), shBuffB.data()); } - - // normalize direction - dir = glm::normalize(dir); - - // scale factor depending on distance from center of the face - const float fDiffSolid = 4.0f / ((1.0f + fU*fU + fV*fV) * - sqrtf(1.0f + fU*fU + fV*fV)); - fWt += fDiffSolid; - - // calculate coefficients of spherical harmonics for current direction - sphericalHarmonicsEvaluateDirection(shBuff.data(), order, dir); - - // index of texel in texture - uint pixOffsetIndex = (x + y * width) * numComponents; - // get color from texture and map to range [0, 1] - glm::vec3 clr( - float(data[pixOffsetIndex]) / 255, - float(data[pixOffsetIndex+1]) / 255, - float(data[pixOffsetIndex+2]) / 255 - ); - - // scale color and add to previously accumulated coefficients - sphericalHarmonicsScale(shBuffB.data(), order, - shBuff.data(), clr.r * fDiffSolid); - sphericalHarmonicsAdd(resultR.data(), order, - resultR.data(), shBuffB.data()); - sphericalHarmonicsScale(shBuffB.data(), order, - shBuff.data(), clr.g * fDiffSolid); - sphericalHarmonicsAdd(resultG.data(), order, - resultG.data(), shBuffB.data()); - sphericalHarmonicsScale(shBuffB.data(), order, - shBuff.data(), clr.b * fDiffSolid); - sphericalHarmonicsAdd(resultB.data(), order, - resultB.data(), shBuffB.data()); - } - } - } - - // final scale for coefficients - const float fNormProj = (4.0f * glm::pi()) / fWt; - sphericalHarmonicsScale(resultR.data(), order, resultR.data(), fNormProj); - sphericalHarmonicsScale(resultG.data(), order, resultG.data(), fNormProj); - sphericalHarmonicsScale(resultB.data(), order, resultB.data(), fNormProj); - - // save result - for(uint i=0; i < sqOrder; i++) - { - output[i].r = resultR[i]; - output[i].g = resultG[i]; - output[i].b = resultB[i]; - } -} -/* -glm::vec3 sphericalHarmonicsFromTexture(glm::vec3 & N, std::vector & coef) -{ - return - - // constant term, lowest frequency ////// - C4 * coef[0] + - - // axis aligned terms /////////////////// - 2.0 * C2 * coef[1] * N.y + - 2.0 * C2 * coef[2] * N.z + - 2.0 * C2 * coef[3] * N.x + - - // band 2 terms ///////////////////////// - 2.0 * C1 * coef[4] * N.x * N.y + - 2.0 * C1 * coef[5] * N.y * N.z + - C3 * coef[6] * N.z * N.z - C5 * coef[6] + - 2.0 * C1 * coef[7] * N.x * N.z + - C1 * coef[8] * (N.x * N.x - N.y * N.y); -} -*/ - -const SphericalHarmonics& Skybox::getAmbientSH() const { - if (!_isSHValid) { - if (_cubemap && _cubemap->isDefined()) { - - std::vector< glm::vec3 > coefs(10, glm::vec3(0.0f)); - sphericalHarmonicsFromTexture(*_cubemap, coefs, 3); - - _ambientSH.L00 = coefs[0]; - _ambientSH.L1m1 = coefs[1]; - _ambientSH.L10 = coefs[2]; - _ambientSH.L11 = coefs[3]; - _ambientSH.L2m2 = coefs[4]; - _ambientSH.L2m1 = coefs[5]; - _ambientSH.L20 = coefs[6]; - _ambientSH.L21 = coefs[7]; - _ambientSH.L22 = coefs[8]; - - _isSHValid = true; } } - return _ambientSH; + + // final scale for coefficients + const float fNormProj = (4.0f * glm::pi()) / fWt; + sphericalHarmonicsScale(resultR.data(), order, resultR.data(), fNormProj); + sphericalHarmonicsScale(resultG.data(), order, resultG.data(), fNormProj); + sphericalHarmonicsScale(resultB.data(), order, resultB.data(), fNormProj); + + // save result + for(uint i=0; i < sqOrder; i++) { + // gamma Correct + // output[i] = linearTosRGB(glm::vec3(resultR[i], resultG[i], resultB[i])); + output[i] = glm::vec3(resultR[i], resultG[i], resultB[i]); + } +} + +const SphericalHarmonics& Skybox::getIrradianceSH() const { + if (!_isSHValid) { + if (_cubemap && _cubemap->isDefined()) { + std::vector< glm::vec3 > coefs; + sphericalHarmonicsFromTexture(*_cubemap, coefs, 3); + + _irradianceSH.L00 = coefs[0]; + _irradianceSH.L1m1 = coefs[1]; + _irradianceSH.L10 = coefs[2]; + _irradianceSH.L11 = coefs[3]; + _irradianceSH.L2m2 = coefs[4]; + _irradianceSH.L2m1 = coefs[5]; + _irradianceSH.L20 = coefs[6]; + _irradianceSH.L21 = coefs[7]; + _irradianceSH.L22 = coefs[8]; + + _isSHValid = true; + } + } + return _irradianceSH; } diff --git a/libraries/model/src/model/Skybox.h b/libraries/model/src/model/Skybox.h index c66cbfb627..9c413405f1 100755 --- a/libraries/model/src/model/Skybox.h +++ b/libraries/model/src/model/Skybox.h @@ -36,14 +36,14 @@ public: const gpu::TexturePointer& getCubemap() const { return _cubemap; } void clearCubemap(); - const SphericalHarmonics& getAmbientSH() const; + const SphericalHarmonics& getIrradianceSH() const; static void render(gpu::Batch& batch, const ViewFrustum& frustum, const Skybox& skybox); protected: gpu::TexturePointer _cubemap; - mutable SphericalHarmonics _ambientSH; + mutable SphericalHarmonics _irradianceSH; mutable bool _isSHValid = false; Color _color{1.0f, 1.0f, 1.0f}; diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index d9a4aa0378..972858be07 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -293,7 +293,7 @@ void DeferredLightingEffect::render() { if (locations->ambientSphere >= 0) { model::SphericalHarmonics sh; if (useSkyboxCubemap) { - sh = _skybox->getAmbientSH(); + sh = _skybox->getIrradianceSH(); } else { sh = globalLight->getAmbientSphere(); } diff --git a/libraries/render-utils/src/TextureCache.cpp b/libraries/render-utils/src/TextureCache.cpp index a6c932b4d8..2f793755d7 100644 --- a/libraries/render-utils/src/TextureCache.cpp +++ b/libraries/render-utils/src/TextureCache.cpp @@ -603,6 +603,45 @@ void NetworkTexture::setImage(const QImage& image, bool translucent, const QColo faces.push_back(image.copy(QRect(3 * faceWidth, faceWidth, faceWidth, faceWidth)).mirrored(true, false)); // Front = -Z faces.push_back(image.copy(QRect(1 * faceWidth, faceWidth, faceWidth, faceWidth)).mirrored(true, false)); + + } else if ((_height / 4) == (_width / 3)) { + int faceWidth = _height / 4; + + // Here is the expected layout for the faces in an image with the 4/3 aspect ratio: + // + // <-------WIDTH--------> + // ^ +------+------+------+ + // | | | | | + // | | | +Y | | + // | | | | | + // H +------+------+------+ + // E | | | | + // I | -X | -Z | +X | + // G | | | | + // H +------+------+------+ + // T | | | | + // | | | -Y | | + // | | | | | + // | +------+------+------+ + // | | | | | + // | | | +Z! | | <+Z is upside down! + // | | | | | + // V +------+------+------+ + // + // FaceWidth = width / 3 = height / 4 + + // Right = +X + faces.push_back(image.copy(QRect(2 * faceWidth, faceWidth, faceWidth, faceWidth)).mirrored(true, false)); + // Left = -X + faces.push_back(image.copy(QRect(0 * faceWidth, faceWidth, faceWidth, faceWidth)).mirrored(true, false)); + // Top = +Y + faces.push_back(image.copy(QRect(1 * faceWidth, 0, faceWidth, faceWidth)).mirrored(false, true)); + // Bottom = -Y + faces.push_back(image.copy(QRect(1 * faceWidth, 2 * faceWidth, faceWidth, faceWidth)).mirrored(false, true)); + // Back = +Z + faces.push_back(image.copy(QRect(1 * faceWidth, 3 * faceWidth, faceWidth, faceWidth)).mirrored(false, true)); + // Front = -Z + faces.push_back(image.copy(QRect(1 * faceWidth, faceWidth, faceWidth, faceWidth)).mirrored(true, false)); } if (faces.size() == gpu::Texture::NUM_FACES_PER_TYPE[gpu::Texture::TEX_CUBE]) { From 1e01aeb9ed49315e0a601f5a8179f417aeea56e8 Mon Sep 17 00:00:00 2001 From: Philip Rosedale Date: Wed, 13 May 2015 23:11:24 -0700 Subject: [PATCH 11/14] first version, air hockey game --- examples/example/games/airHockey.js | 252 ++++++++++++++++++++++++ examples/example/games/grabHockey.js | 278 +++++++++++++++++++++++++++ 2 files changed, 530 insertions(+) create mode 100644 examples/example/games/airHockey.js create mode 100644 examples/example/games/grabHockey.js diff --git a/examples/example/games/airHockey.js b/examples/example/games/airHockey.js new file mode 100644 index 0000000000..036d6ca5ac --- /dev/null +++ b/examples/example/games/airHockey.js @@ -0,0 +1,252 @@ +// +// AirHockey.js +// +// Created by Philip Rosedale on January 26, 2015 +// Copyright 2015 High Fidelity, Inc. +// +// AirHockey table and pucks +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var debugVisible = false; + +var FIELD_WIDTH = 1.21; +var FIELD_LENGTH = 1.92; +var FLOOR_THICKNESS = 0.20; +var EDGE_THICKESS = 0.10; +var EDGE_HEIGHT = 0.10; +var DROP_HEIGHT = 0.3; +var PUCK_SIZE = 0.15; +var PUCK_THICKNESS = 0.03; +var PADDLE_SIZE = 0.12; +var PADDLE_THICKNESS = 0.03; + +var GOAL_WIDTH = 0.35; + +var GRAVITY = -9.8; +var LIFETIME = 6000; +var PUCK_DAMPING = 0.03; +var PADDLE_DAMPING = 0.35; +var ANGULAR_DAMPING = 0.10; +var PADDLE_ANGULAR_DAMPING = 0.35; +var MODEL_SCALE = 1.52; +var MODEL_OFFSET = { x: 0, y: -0.18, z: 0 }; + +var scoreSound = SoundCache.getSound("https://s3.amazonaws.com/hifi-public/sounds/Collisions-hitsandslaps/airhockey_score.wav"); + +var polyTable = "https://hifi-public.s3.amazonaws.com/ozan/props/airHockeyTable/airHockeyTableForPolyworld.fbx" +var normalTable = "https://hifi-public.s3.amazonaws.com/ozan/props/airHockeyTable/airHockeyTable.fbx" +var hitSound1 = "https://s3.amazonaws.com/hifi-public/sounds/Collisions-hitsandslaps/airhockey_hit1.wav" +var hitSound2 = "https://s3.amazonaws.com/hifi-public/sounds/Collisions-hitsandslaps/airhockey_hit2.wav" +var hitSideSound = "https://s3.amazonaws.com/hifi-public/sounds/Collisions-hitsandslaps/airhockey_hit3.wav" + +var center = Vec3.sum(MyAvatar.position, Vec3.multiply((FIELD_WIDTH + FIELD_LENGTH) * 0.60, Quat.getFront(Camera.getOrientation()))); + +var floor = Entities.addEntity( + { type: "Box", + position: Vec3.subtract(center, { x: 0, y: 0, z: 0 }), + dimensions: { x: FIELD_WIDTH, y: FLOOR_THICKNESS, z: FIELD_LENGTH }, + color: { red: 128, green: 128, blue: 128 }, + gravity: { x: 0, y: 0, z: 0 }, + ignoreCollisions: false, + locked: true, + visible: debugVisible, + lifetime: LIFETIME }); + +var edge1 = Entities.addEntity( + { type: "Box", + collisionSoundURL: hitSideSound, + position: Vec3.sum(center, { x: FIELD_WIDTH / 2.0, y: FLOOR_THICKNESS / 2.0, z: 0 }), + dimensions: { x: EDGE_THICKESS, y: EDGE_HEIGHT, z: FIELD_LENGTH + EDGE_THICKESS }, + color: { red: 100, green: 100, blue: 100 }, + gravity: { x: 0, y: 0, z: 0 }, + ignoreCollisions: false, + visible: debugVisible, + locked: true, + lifetime: LIFETIME }); + +var edge2 = Entities.addEntity( + { type: "Box", + collisionSoundURL: hitSideSound, + position: Vec3.sum(center, { x: -FIELD_WIDTH / 2.0, y: FLOOR_THICKNESS / 2.0, z: 0 }), + dimensions: { x: EDGE_THICKESS, y: EDGE_HEIGHT, z: FIELD_LENGTH + EDGE_THICKESS }, + color: { red: 100, green: 100, blue: 100 }, + gravity: { x: 0, y: 0, z: 0 }, + ignoreCollisions: false, + visible: debugVisible, + locked: true, + lifetime: LIFETIME }); + +var edge3a = Entities.addEntity( + { type: "Box", + collisionSoundURL: hitSideSound, + position: Vec3.sum(center, { x: FIELD_WIDTH / 4.0 + (GOAL_WIDTH / 4.0), y: FLOOR_THICKNESS / 2.0, z: -FIELD_LENGTH / 2.0 }), + dimensions: { x: FIELD_WIDTH / 2.0 - GOAL_WIDTH / 2.0, y: EDGE_HEIGHT, z: EDGE_THICKESS }, + color: { red: 100, green: 100, blue: 100 }, + gravity: { x: 0, y: 0, z: 0 }, + ignoreCollisions: false, + visible: debugVisible, + locked: true, + lifetime: LIFETIME }); + +var edge3b = Entities.addEntity( + { type: "Box", + collisionSoundURL: hitSideSound, + position: Vec3.sum(center, { x: -FIELD_WIDTH / 4.0 - (GOAL_WIDTH / 4.0), y: FLOOR_THICKNESS / 2.0, z: -FIELD_LENGTH / 2.0 }), + dimensions: { x: FIELD_WIDTH / 2.0 - GOAL_WIDTH / 2.0, y: EDGE_HEIGHT, z: EDGE_THICKESS }, + color: { red: 100, green: 100, blue: 100 }, + gravity: { x: 0, y: 0, z: 0 }, + ignoreCollisions: false, + visible: debugVisible, + locked: true, + lifetime: LIFETIME }); + +var edge4a = Entities.addEntity( + { type: "Box", + collisionSoundURL: hitSideSound, + position: Vec3.sum(center, { x: FIELD_WIDTH / 4.0 + (GOAL_WIDTH / 4.0), y: FLOOR_THICKNESS / 2.0, z: FIELD_LENGTH / 2.0 }), + dimensions: { x: FIELD_WIDTH / 2.0 - GOAL_WIDTH / 2.0, y: EDGE_HEIGHT, z: EDGE_THICKESS }, + color: { red: 100, green: 100, blue: 100 }, + gravity: { x: 0, y: 0, z: 0 }, + ignoreCollisions: false, + visible: debugVisible, + locked: true, + lifetime: LIFETIME }); + +var edge4b = Entities.addEntity( + { type: "Box", + collisionSoundURL: hitSideSound, + position: Vec3.sum(center, { x: -FIELD_WIDTH / 4.0 - (GOAL_WIDTH / 4.0), y: FLOOR_THICKNESS / 2.0, z: FIELD_LENGTH / 2.0 }), + dimensions: { x: FIELD_WIDTH / 2.0 - GOAL_WIDTH / 2.0, y: EDGE_HEIGHT, z: EDGE_THICKESS }, + color: { red: 100, green: 100, blue: 100 }, + gravity: { x: 0, y: 0, z: 0 }, + ignoreCollisions: false, + visible: debugVisible, + locked: true, + lifetime: LIFETIME }); + +var table = Entities.addEntity( + { type: "Model", + modelURL: polyTable, + dimensions: Vec3.multiply({ x: 0.8, y: 0.45, z: 1.31 }, MODEL_SCALE), + position: Vec3.sum(center, MODEL_OFFSET), + ignoreCollisions: false, + visible: true, + locked: true, + lifetime: LIFETIME }); + +var puck; +var paddle1, paddle2; + +// Create pucks + +function makeNewProp(which) { + if (which == "puck") { + return Entities.addEntity( + { type: "Model", + modelURL: "http://headache.hungry.com/~seth/hifi/puck.obj", + compoundShapeURL: "http://headache.hungry.com/~seth/hifi/puck.obj", + collisionSoundURL: hitSound1, + position: Vec3.sum(center, { x: 0, y: DROP_HEIGHT, z: 0 }), + dimensions: { x: PUCK_SIZE, y: PUCK_THICKNESS, z: PUCK_SIZE }, + gravity: { x: 0, y: GRAVITY, z: 0 }, + velocity: { x: 0, y: 0.05, z: 0 }, + ignoreCollisions: false, + damping: PUCK_DAMPING, + angularDamping: ANGULAR_DAMPING, + lifetime: LIFETIME, + collisionsWillMove: true }); + } + else if (which == "paddle1") { + return Entities.addEntity( + { type: "Model", + modelURL: "http://headache.hungry.com/~seth/hifi/puck.obj", + compoundShapeURL: "http://headache.hungry.com/~seth/hifi/puck.obj", + collisionSoundURL: hitSound2, + position: Vec3.sum(center, { x: 0, y: DROP_HEIGHT, z: FIELD_LENGTH * 0.35 }), + dimensions: { x: PADDLE_SIZE, y: PADDLE_THICKNESS, z: PADDLE_SIZE }, + gravity: { x: 0, y: GRAVITY, z: 0 }, + velocity: { x: 0, y: 0.05, z: 0 }, + ignoreCollisions: false, + damping: PADDLE_DAMPING, + angularDamping: PADDLE_ANGULAR_DAMPING, + lifetime: LIFETIME, + collisionsWillMove: true }); + } + else if (which == "paddle2") { + return Entities.addEntity( + { type: "Model", + modelURL: "http://headache.hungry.com/~seth/hifi/puck.obj", + compoundShapeURL: "http://headache.hungry.com/~seth/hifi/puck.obj", + collisionSoundURL: hitSound2, + position: Vec3.sum(center, { x: 0, y: DROP_HEIGHT, z: -FIELD_LENGTH * 0.35 }), + dimensions: { x: PADDLE_SIZE, y: PADDLE_THICKNESS, z: PADDLE_SIZE }, + gravity: { x: 0, y: GRAVITY, z: 0 }, + velocity: { x: 0, y: 0.05, z: 0 }, + ignoreCollisions: false, + damping: PADDLE_DAMPING, + angularDamping: PADDLE_ANGULAR_DAMPING, + lifetime: LIFETIME, + collisionsWillMove: true }); + } +} + + +puck = makeNewProp("puck"); +paddle1 = makeNewProp("paddle1"); +paddle2 = makeNewProp("paddle2"); + +function update(deltaTime) { + if (Math.random() < 0.1) { + puckProps = Entities.getEntityProperties(puck); + paddle1Props = Entities.getEntityProperties(paddle1); + paddle2Props = Entities.getEntityProperties(paddle2); + if (puckProps.position.y < (center.y - DROP_HEIGHT)) { + Audio.playSound(scoreSound, { + position: center, + volume: 1.0 + }); + Entities.deleteEntity(puck); + puck = makeNewProp("puck"); + } + + if (paddle1Props.position.y < (center.y - DROP_HEIGHT)) { + Entities.deleteEntity(paddle1); + paddle1 = makeNewProp("paddle1"); + } + if (paddle2Props.position.y < (center.y - DROP_HEIGHT)) { + Entities.deleteEntity(paddle2); + paddle2 = makeNewProp("paddle2"); + } + } +} + +function scriptEnding() { + + Entities.editEntity(edge1, { locked: false }); + Entities.editEntity(edge2, { locked: false }); + Entities.editEntity(edge3a, { locked: false }); + Entities.editEntity(edge3b, { locked: false }); + Entities.editEntity(edge4a, { locked: false }); + Entities.editEntity(edge4b, { locked: false }); + Entities.editEntity(floor, { locked: false }); + Entities.editEntity(table, { locked: false }); + + + Entities.deleteEntity(edge1); + Entities.deleteEntity(edge2); + Entities.deleteEntity(edge3a); + Entities.deleteEntity(edge3b); + Entities.deleteEntity(edge4a); + Entities.deleteEntity(edge4b); + Entities.deleteEntity(floor); + Entities.deleteEntity(puck); + Entities.deleteEntity(paddle1); + Entities.deleteEntity(paddle2); + Entities.deleteEntity(table); +} + +Script.update.connect(update); +Script.scriptEnding.connect(scriptEnding); \ No newline at end of file diff --git a/examples/example/games/grabHockey.js b/examples/example/games/grabHockey.js new file mode 100644 index 0000000000..31597ba32d --- /dev/null +++ b/examples/example/games/grabHockey.js @@ -0,0 +1,278 @@ + +// grab.js +// examples +// +// Created by Eric Levin on May 1, 2015 +// Copyright 2015 High Fidelity, Inc. +// +// Grab's physically moveable entities with the mouse, by applying a spring force. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var isGrabbing = false; +var grabbedEntity = null; +var lineEntityID = null; +var prevMouse = {}; +var deltaMouse = { + z: 0 +} +var entityProps; +var moveUpDown = false; +var CLOSE_ENOUGH = 0.001; +var FULL_STRENGTH = 1.0; +var SPRING_RATE = 1.5; +var DAMPING_RATE = 0.80; +var ANGULAR_DAMPING_RATE = 0.40; +var SCREEN_TO_METERS = 0.001; +var currentPosition, currentVelocity, cameraEntityDistance, currentRotation; +var velocityTowardTarget, desiredVelocity, addedVelocity, newVelocity, dPosition, camYaw, distanceToTarget, targetPosition; +var originalGravity = {x: 0, y: 0, z: 0}; +var shouldRotate = false; +var dQ, theta, axisAngle, dT; +var angularVelocity = { + 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 VOLUME = 0.10; + +var DROP_DISTANCE = 5.0; +var DROP_COLOR = { + red: 200, + green: 200, + blue: 200 +}; +var DROP_WIDTH = 2; + + +var dropLine = Overlays.addOverlay("line3d", { + color: DROP_COLOR, + alpha: 1, + visible: false, + lineWidth: DROP_WIDTH +}); + + +function vectorIsZero(v) { + return v.x == 0 && v.y == 0 && v.z == 0; +} + +function nearLinePoint(targetPosition) { + // var handPosition = Vec3.sum(MyAvatar.position, {x:0, y:0.2, z:0}); + var handPosition = MyAvatar.getRightPalmPosition(); + var along = Vec3.subtract(targetPosition, handPosition); + along = Vec3.normalize(along); + along = Vec3.multiply(along, 0.4); + return Vec3.sum(handPosition, along); +} + + +function mousePressEvent(event) { + if (!event.isLeftButton) { + return; + } + var pickRay = Camera.computePickRay(event.x, event.y); + var intersection = Entities.findRayIntersection(pickRay, true); // accurate picking + if (intersection.intersects && intersection.properties.collisionsWillMove) { + grabbedEntity = intersection.entityID; + var props = Entities.getEntityProperties(grabbedEntity) + isGrabbing = true; + originalGravity = props.gravity; + targetPosition = props.position; + currentPosition = props.position; + currentVelocity = props.velocity; + updateDropLine(targetPosition); + + Entities.editEntity(grabbedEntity, { + gravity: {x: 0, y: 0, z: 0} + }); + + lineEntityID = Entities.addEntity({ + type: "Line", + position: nearLinePoint(targetPosition), + dimensions: Vec3.subtract(targetPosition, nearLinePoint(targetPosition)), + color: { red: 255, green: 255, blue: 255 }, + lifetime: 300 // if someone crashes while moving something, don't leave the line there forever. + }); + + Audio.playSound(grabSound, { + position: props.position, + volume: VOLUME + }); + } +} + +function updateDropLine(position) { + Overlays.editOverlay(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 + } + }) +} + + +function mouseReleaseEvent() { + if (isGrabbing) { + isGrabbing = false; + + // 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(originalGravity)) { + Entities.editEntity(grabbedEntity, { + gravity: originalGravity + }); + } + + Overlays.editOverlay(dropLine, { + visible: false + }); + targetPosition = null; + + Entities.deleteEntity(lineEntityID); + + Audio.playSound(releaseSound, { + position: entityProps.position, + volume: VOLUME + }); + + } +} + +function mouseMoveEvent(event) { + if (isGrabbing) { + // see if something added/restored gravity + var props = Entities.getEntityProperties(grabbedEntity); + if (!vectorIsZero(props.gravity)) { + originalGravity = props.gravity; + } + + deltaMouse.x = event.x - prevMouse.x; + if (!moveUpDown) { + deltaMouse.z = event.y - prevMouse.y; + deltaMouse.y = 0; + } else { + deltaMouse.y = (event.y - prevMouse.y) * -1; + deltaMouse.z = 0; + } + // Update the target position by the amount the mouse moved + camYaw = Quat.safeEulerAngles(Camera.getOrientation()).y; + dPosition = Vec3.multiplyQbyV(Quat.fromPitchYawRollDegrees(0, camYaw, 0), deltaMouse); + if (!shouldRotate) { + // Adjust target position for the object by the mouse move + cameraEntityDistance = Vec3.distance(Camera.getPosition(), currentPosition); + // Scale distance we want to move by the distance from the camera to the grabbed object + // TODO: Correct SCREEN_TO_METERS to be correct for the actual FOV, resolution + targetPosition = Vec3.sum(targetPosition, Vec3.multiply(dPosition, cameraEntityDistance * SCREEN_TO_METERS)); + } else if (shouldRotate) { + var transformedDeltaMouse = { + x: deltaMouse.z, + y: deltaMouse.x, + z: deltaMouse.y + }; + transformedDeltaMouse = Vec3.multiplyQbyV(Quat.fromPitchYawRollDegrees(0, camYaw, 0), transformedDeltaMouse); + dQ = Quat.fromVec3Degrees(transformedDeltaMouse); + theta = 2 * Math.acos(dQ.w); + axisAngle = Quat.axis(dQ); + angularVelocity = Vec3.multiply((theta / dT), axisAngle); + } + + Entities.editEntity(lineEntityID, { + position: nearLinePoint(targetPosition), + dimensions: Vec3.subtract(targetPosition, nearLinePoint(targetPosition)) + }); + } + prevMouse.x = event.x; + prevMouse.y = event.y; + +} + + +function keyReleaseEvent(event) { + if (event.text === "SHIFT") { + moveUpDown = false; + } + if (event.text === "SPACE") { + shouldRotate = false; + } +} + +function keyPressEvent(event) { + if (event.text === "SHIFT") { + moveUpDown = true; + } + if (event.text === "SPACE") { + shouldRotate = true; + } +} + +function update(deltaTime) { + dT = deltaTime; + if (isGrabbing) { + + entityProps = Entities.getEntityProperties(grabbedEntity); + currentPosition = entityProps.position; + currentVelocity = entityProps.velocity; + currentRotation = entityProps.rotation; + + var dPosition = Vec3.subtract(targetPosition, currentPosition); + + distanceToTarget = Vec3.length(dPosition); + if (distanceToTarget > CLOSE_ENOUGH) { + // compute current velocity in the direction we want to move + velocityTowardTarget = Vec3.dot(currentVelocity, Vec3.normalize(dPosition)); + velocityTowardTarget = Vec3.multiply(Vec3.normalize(dPosition), velocityTowardTarget); + // compute the speed we would like to be going toward the target position + + desiredVelocity = Vec3.multiply(dPosition, (1.0 / deltaTime) * SPRING_RATE); + // compute how much we want to add to the existing velocity + addedVelocity = Vec3.subtract(desiredVelocity, velocityTowardTarget); + // If target is too far, roll off the force as inverse square of distance + if (distanceToTarget / cameraEntityDistance > FULL_STRENGTH) { + addedVelocity = Vec3.multiply(addedVelocity, Math.pow(FULL_STRENGTH / distanceToTarget, 2.0)); + } + newVelocity = Vec3.sum(currentVelocity, addedVelocity); + // Add Damping + newVelocity = Vec3.subtract(newVelocity, Vec3.multiply(newVelocity, DAMPING_RATE)); + // Update entity + } else { + newVelocity = {x: 0, y: 0, z: 0}; + } + if (shouldRotate) { + angularVelocity = Vec3.subtract(angularVelocity, Vec3.multiply(angularVelocity, ANGULAR_DAMPING_RATE)); + } else { + angularVelocity = entityProps.angularVelocity; + } + + Entities.editEntity(grabbedEntity, { + position: currentPosition, + rotation: currentRotation, + velocity: newVelocity, + angularVelocity: angularVelocity + }); + updateDropLine(targetPosition); + } +} + +Controller.mouseMoveEvent.connect(mouseMoveEvent); +Controller.mousePressEvent.connect(mousePressEvent); +Controller.mouseReleaseEvent.connect(mouseReleaseEvent); +Controller.keyPressEvent.connect(keyPressEvent); +Controller.keyReleaseEvent.connect(keyReleaseEvent); +Script.update.connect(update); From a77816617d2dabd656b740fe2bdc084b1444d4ed Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 14 May 2015 01:15:20 -0700 Subject: [PATCH 12/14] Make web entities interactive (via the mouse) if they're not locked. --- .../src/EntityTreeRenderer.cpp | 21 +++++--- .../src/EntityTreeRenderer.h | 6 +-- .../src/RenderableWebEntityItem.cpp | 51 ++++++++++++++++++- .../render-utils/src/OffscreenQmlSurface.cpp | 3 ++ .../render-utils/src/OffscreenQmlSurface.h | 1 + 5 files changed, 71 insertions(+), 11 deletions(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index deba442c94..3cd5c9a268 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -850,9 +850,18 @@ RayToEntityIntersectionResult EntityTreeRenderer::findRayIntersectionWorker(cons } void EntityTreeRenderer::connectSignalsToSlots(EntityScriptingInterface* entityScriptingInterface) { - connect(this, &EntityTreeRenderer::mousePressOnEntity, entityScriptingInterface, &EntityScriptingInterface::mousePressOnEntity); - connect(this, &EntityTreeRenderer::mouseMoveOnEntity, entityScriptingInterface, &EntityScriptingInterface::mouseMoveOnEntity); - connect(this, &EntityTreeRenderer::mouseReleaseOnEntity, entityScriptingInterface, &EntityScriptingInterface::mouseReleaseOnEntity); + connect(this, &EntityTreeRenderer::mousePressOnEntity, entityScriptingInterface, + [=](const RayToEntityIntersectionResult& intersection, const QMouseEvent* event, unsigned int deviceId){ + entityScriptingInterface->mousePressOnEntity(intersection.entityID, MouseEvent(*event, deviceId)); + }); + connect(this, &EntityTreeRenderer::mouseMoveOnEntity, entityScriptingInterface, + [=](const RayToEntityIntersectionResult& intersection, const QMouseEvent* event, unsigned int deviceId) { + entityScriptingInterface->mouseMoveOnEntity(intersection.entityID, MouseEvent(*event, deviceId)); + }); + connect(this, &EntityTreeRenderer::mouseReleaseOnEntity, entityScriptingInterface, + [=](const RayToEntityIntersectionResult& intersection, const QMouseEvent* event, unsigned int deviceId) { + entityScriptingInterface->mouseReleaseOnEntity(intersection.entityID, MouseEvent(*event, deviceId)); + }); connect(this, &EntityTreeRenderer::clickDownOnEntity, entityScriptingInterface, &EntityScriptingInterface::clickDownOnEntity); connect(this, &EntityTreeRenderer::holdingClickOnEntity, entityScriptingInterface, &EntityScriptingInterface::holdingClickOnEntity); @@ -900,7 +909,7 @@ void EntityTreeRenderer::mousePressEvent(QMouseEvent* event, unsigned int device RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::Lock, precisionPicking); if (rayPickResult.intersects) { //qCDebug(entitiesrenderer) << "mousePressEvent over entity:" << rayPickResult.entityID; - emit mousePressOnEntity(rayPickResult.entityID, MouseEvent(*event, deviceID)); + emit mousePressOnEntity(rayPickResult, event, deviceID); QScriptValueList entityScriptArgs = createMouseEventArgs(rayPickResult.entityID, event, deviceID); QScriptValue entityScript = loadEntityScript(rayPickResult.entity); @@ -930,7 +939,7 @@ void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event, unsigned int devi RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::Lock, precisionPicking); if (rayPickResult.intersects) { //qCDebug(entitiesrenderer) << "mouseReleaseEvent over entity:" << rayPickResult.entityID; - emit mouseReleaseOnEntity(rayPickResult.entityID, MouseEvent(*event, deviceID)); + emit mouseReleaseOnEntity(rayPickResult, event, deviceID); QScriptValueList entityScriptArgs = createMouseEventArgs(rayPickResult.entityID, event, deviceID); QScriptValue entityScript = loadEntityScript(rayPickResult.entity); @@ -979,7 +988,7 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceI } //qCDebug(entitiesrenderer) << "mouseMoveEvent over entity:" << rayPickResult.entityID; - emit mouseMoveOnEntity(rayPickResult.entityID, MouseEvent(*event, deviceID)); + emit mouseMoveOnEntity(rayPickResult, event, deviceID); if (entityScript.property("mouseMoveOnEntity").isValid()) { entityScript.property("mouseMoveOnEntity").call(entityScript, entityScriptArgs); } diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index eb8b44f5eb..9768d4a20a 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -93,9 +93,9 @@ public: virtual void errorInLoadingScript(const QUrl& url); signals: - void mousePressOnEntity(const EntityItemID& entityItemID, const MouseEvent& event); - void mouseMoveOnEntity(const EntityItemID& entityItemID, const MouseEvent& event); - void mouseReleaseOnEntity(const EntityItemID& entityItemID, const MouseEvent& event); + void mousePressOnEntity(const RayToEntityIntersectionResult& entityItemID, const QMouseEvent* event, unsigned int deviceId); + void mouseMoveOnEntity(const RayToEntityIntersectionResult& entityItemID, const QMouseEvent* event, unsigned int deviceId); + void mouseReleaseOnEntity(const RayToEntityIntersectionResult& entityItemID, const QMouseEvent* event, unsigned int deviceId); void clickDownOnEntity(const EntityItemID& entityItemID, const MouseEvent& event); void holdingClickOnEntity(const EntityItemID& entityItemID, const MouseEvent& event); diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index fe85a37762..3afd930ab0 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -23,6 +23,8 @@ #include #include +#include "EntityTreeRenderer.h" + const int FIXED_FONT_POINT_SIZE = 40; const float DPI = 30.47; const float METERS_TO_INCHES = 39.3701; @@ -63,6 +65,7 @@ RenderableWebEntityItem::~RenderableWebEntityItem() { void RenderableWebEntityItem::render(RenderArgs* args) { QOpenGLContext * currentContext = QOpenGLContext::currentContext(); QSurface * currentSurface = currentContext->surface(); + if (!_webSurface) { _webSurface = new OffscreenQmlSurface(); _webSurface->create(currentContext); @@ -89,6 +92,52 @@ void RenderableWebEntityItem::render(RenderArgs* args) { _webSurface->doneCurrent(); } }); + + auto forwardMouseEvent = [=](const RayToEntityIntersectionResult& intersection, const QMouseEvent* event, unsigned int deviceId) { + // Ignore mouse interaction if we're locked + if (this->getLocked()) { + return; + } + + if (intersection.entityID.id == getID()) { + if (event->button() == Qt::MouseButton::RightButton) { + if (event->type() == QEvent::MouseButtonRelease) { + AbstractViewStateInterface::instance()->postLambdaEvent([this] { + QMetaObject::invokeMethod(_webSurface->getRootItem(), "goBack"); + }); + } + return; + } + + if (event->button() == Qt::MouseButton::MiddleButton) { + if (event->type() == QEvent::MouseButtonRelease) { + AbstractViewStateInterface::instance()->postLambdaEvent([this] { + _webSurface->getRootItem()->setProperty("url", _sourceUrl); + }); + } + return; + } + + // Map the intersection point to an actual offscreen pixel + glm::vec3 point = intersection.intersection; + point -= getPosition(); + point = glm::inverse(getRotation()) * point; + point /= _dimensions; + point += 0.5f; + point.y = 1.0f - point.y; + point *= _dimensions * METERS_TO_INCHES * DPI; + // Forward the mouse event. + QMouseEvent mappedEvent(event->type(), + QPoint((int)point.x, (int)point.y), + event->screenPos(), event->button(), + event->buttons(), event->modifiers()); + QCoreApplication::sendEvent(_webSurface->getWindow(), &mappedEvent); + } + }; + EntityTreeRenderer* renderer = static_cast(args->_renderer); + QObject::connect(renderer, &EntityTreeRenderer::mousePressOnEntity, forwardMouseEvent); + QObject::connect(renderer, &EntityTreeRenderer::mouseReleaseOnEntity, forwardMouseEvent); + QObject::connect(renderer, &EntityTreeRenderer::mouseMoveOnEntity, forwardMouseEvent); } glm::vec2 dims = glm::vec2(_dimensions); @@ -106,8 +155,6 @@ void RenderableWebEntityItem::render(RenderArgs* args) { glm::vec3 halfDimensions = dimensions / 2.0f; glm::quat rotation = getRotation(); - //qCDebug(entitytree) << "RenderableWebEntityItem::render() id:" << getEntityItemID() << "text:" << getText(); - glPushMatrix(); { glTranslatef(position.x, position.y, position.z); diff --git a/libraries/render-utils/src/OffscreenQmlSurface.cpp b/libraries/render-utils/src/OffscreenQmlSurface.cpp index 5b39610640..9b497964e7 100644 --- a/libraries/render-utils/src/OffscreenQmlSurface.cpp +++ b/libraries/render-utils/src/OffscreenQmlSurface.cpp @@ -374,3 +374,6 @@ void OffscreenQmlSurface::setProxyWindow(QWindow* window) { _renderControl->_renderWindow = window; } +QQuickWindow* OffscreenQmlSurface::getWindow() { + return _quickWindow; +} \ No newline at end of file diff --git a/libraries/render-utils/src/OffscreenQmlSurface.h b/libraries/render-utils/src/OffscreenQmlSurface.h index dcb3e1e17e..a1b66b48ad 100644 --- a/libraries/render-utils/src/OffscreenQmlSurface.h +++ b/libraries/render-utils/src/OffscreenQmlSurface.h @@ -71,6 +71,7 @@ public: void setBaseUrl(const QUrl& baseUrl); QQuickItem* getRootItem(); + QQuickWindow* getWindow(); virtual bool eventFilter(QObject* originalDestination, QEvent* event); From c631f85bf97f14f815a0ba1f4a403b7ca0d741c3 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 14 May 2015 08:43:54 -0700 Subject: [PATCH 13/14] 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 14/14] 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; }