diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index cc99181fe1..8d660848f2 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -71,6 +71,7 @@ #include #include #include +#include #include #include #include @@ -432,6 +433,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : connect(nodeList.data(), &NodeList::nodeKilled, this, &Application::nodeKilled); connect(nodeList.data(), SIGNAL(nodeKilled(SharedNodePointer)), SLOT(nodeKilled(SharedNodePointer))); connect(nodeList.data(), &NodeList::uuidChanged, _myAvatar, &MyAvatar::setSessionUUID); + connect(nodeList.data(), &NodeList::uuidChanged, this, &Application::setSessionUUID); connect(nodeList.data(), &NodeList::limitOfSilentDomainCheckInsReached, nodeList.data(), &NodeList::reset); connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &Application::notifyPacketVersionMismatch); @@ -2119,19 +2121,20 @@ void Application::init() { _entities.init(); _entities.setViewFrustum(getViewFrustum()); - EntityTree* tree = _entities.getTree(); - _physicsEngine.setEntityTree(tree); - tree->setSimulation(&_physicsEngine); - _physicsEngine.init(&_entityEditSender); + ObjectMotionState::setShapeManager(&_shapeManager); + _physicsEngine.init(); + EntityTree* tree = _entities.getTree(); + _entitySimulation.init(tree, &_physicsEngine, &_shapeManager, &_entityEditSender); + tree->setSimulation(&_entitySimulation); auto entityScriptingInterface = DependencyManager::get(); - connect(&_physicsEngine, &EntitySimulation::entityCollisionWithEntity, + connect(&_entitySimulation, &EntitySimulation::entityCollisionWithEntity, entityScriptingInterface.data(), &EntityScriptingInterface::entityCollisionWithEntity); // connect the _entityCollisionSystem to our EntityTreeRenderer since that's what handles running entity scripts - connect(&_physicsEngine, &EntitySimulation::entityCollisionWithEntity, + connect(&_entitySimulation, &EntitySimulation::entityCollisionWithEntity, &_entities, &EntityTreeRenderer::entityCollisionWithEntity); // connect the _entities (EntityTreeRenderer) to our script engine's EntityScriptingInterface for firing @@ -2415,8 +2418,22 @@ void Application::update(float deltaTime) { { PerformanceTimer perfTimer("physics"); _myAvatar->relayDriveKeysToCharacterController(); + + _entitySimulation.lock(); + _physicsEngine.deleteObjects(_entitySimulation.getObjectsToDelete()); + _physicsEngine.addObjects(_entitySimulation.getObjectsToAdd()); + _physicsEngine.changeObjects(_entitySimulation.getObjectsToChange()); + _entitySimulation.unlock(); + _physicsEngine.stepSimulation(); - _physicsEngine.dumpStatsIfNecessary(); + + if (_physicsEngine.hasOutgoingChanges()) { + _entitySimulation.lock(); + _entitySimulation.handleOutgoingChanges(_physicsEngine.getOutgoingChanges()); + _entitySimulation.handleCollisionEvents(_physicsEngine.getCollisionEvents()); + _entitySimulation.unlock(); + _physicsEngine.dumpStatsIfNecessary(); + } } if (!_aboutToQuit) { @@ -3948,6 +3965,9 @@ bool Application::acceptURL(const QString& urlString) { return false; } +void Application::setSessionUUID(const QUuid& sessionUUID) { + _physicsEngine.setSessionUUID(sessionUUID); +} bool Application::askToSetAvatarUrl(const QString& url) { QUrl realUrl(url); diff --git a/interface/src/Application.h b/interface/src/Application.h index c4a3509b50..10df094c2e 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -33,8 +33,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -371,6 +373,7 @@ signals: void beforeAboutToQuit(); public slots: + void setSessionUUID(const QUuid& sessionUUID); void domainChanged(const QString& domainHostname); void updateWindowTitle(); void nodeAdded(SharedNodePointer node); @@ -525,6 +528,8 @@ private: bool _justStarted; Stars _stars; + ShapeManager _shapeManager; + PhysicalEntitySimulation _entitySimulation; PhysicsEngine _physicsEngine; EntityTreeRenderer _entities; diff --git a/libraries/entities-renderer/CMakeLists.txt b/libraries/entities-renderer/CMakeLists.txt index c0880ed15d..6c7fa04a21 100644 --- a/libraries/entities-renderer/CMakeLists.txt +++ b/libraries/entities-renderer/CMakeLists.txt @@ -7,4 +7,9 @@ add_dependency_external_projects(glm) find_package(GLM REQUIRED) target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS}) -link_hifi_libraries(shared gpu script-engine render-utils) \ No newline at end of file +add_dependency_external_projects(bullet) +find_package(Bullet REQUIRED) +target_include_directories(${TARGET_NAME} SYSTEM PRIVATE ${BULLET_INCLUDE_DIRS}) +target_link_libraries(${TARGET_NAME} ${BULLET_LIBRARIES}) + +link_hifi_libraries(shared gpu script-engine render-utils) diff --git a/libraries/entities-renderer/src/RenderableDebugableEntityItem.cpp b/libraries/entities-renderer/src/RenderableDebugableEntityItem.cpp index 94b554b96a..017cb289f0 100644 --- a/libraries/entities-renderer/src/RenderableDebugableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableDebugableEntityItem.cpp @@ -14,16 +14,17 @@ #include #include #include +#include #include "RenderableDebugableEntityItem.h" -void RenderableDebugableEntityItem::renderBoundingBox(EntityItem* entity, RenderArgs* args, bool puffedOut) { +void RenderableDebugableEntityItem::renderBoundingBox(EntityItem* entity, RenderArgs* args, + float puffedOut, glm::vec4& color) { glm::vec3 position = entity->getPosition(); glm::vec3 center = entity->getCenter(); glm::vec3 dimensions = entity->getDimensions(); glm::quat rotation = entity->getRotation(); - glm::vec4 greenColor(0.0f, 1.0f, 0.0f, 1.0f); glPushMatrix(); glTranslatef(position.x, position.y, position.z); @@ -33,15 +34,36 @@ void RenderableDebugableEntityItem::renderBoundingBox(EntityItem* entity, Render glm::vec3 positionToCenter = center - position; glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z); glScalef(dimensions.x, dimensions.y, dimensions.z); - if (puffedOut) { - DependencyManager::get()->renderWireCube(1.2f, greenColor); - } else { - DependencyManager::get()->renderWireCube(1.0f, greenColor); - } + DependencyManager::get()->renderWireCube(1.0f + puffedOut, color); glPopMatrix(); glPopMatrix(); } +void RenderableDebugableEntityItem::renderHoverDot(EntityItem* entity, RenderArgs* args) { + glm::vec3 position = entity->getPosition(); + glm::vec3 center = entity->getCenter(); + glm::vec3 dimensions = entity->getDimensions(); + glm::quat rotation = entity->getRotation(); + glm::vec4 blueColor(0.0f, 0.0f, 1.0f, 1.0f); + float radius = 0.05f; + + glPushMatrix(); + glTranslatef(position.x, position.y + dimensions.y + radius, position.z); + glm::vec3 axis = glm::axis(rotation); + glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z); + + glPushMatrix(); + glm::vec3 positionToCenter = center - position; + glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z); + + glScalef(radius, radius, radius); + + const int SLICES = 8; + const int STACKS = 8; + DependencyManager::get()->renderSolidSphere(0.5f, SLICES, STACKS, blueColor); + glPopMatrix(); + glPopMatrix(); +} void RenderableDebugableEntityItem::render(EntityItem* entity, RenderArgs* args) { bool debugSimulationOwnership = args->_debugFlags & RenderArgs::RENDER_DEBUG_SIMULATION_OWNERSHIP; @@ -49,7 +71,17 @@ void RenderableDebugableEntityItem::render(EntityItem* entity, RenderArgs* args) if (debugSimulationOwnership) { quint64 now = usecTimestampNow(); if (now - entity->getLastEditedFromRemote() < 0.1f * USECS_PER_SECOND) { - renderBoundingBox(entity, args, true); + glm::vec4 redColor(1.0f, 0.0f, 0.0f, 1.0f); + renderBoundingBox(entity, args, 0.2f, redColor); + } + + if (now - entity->getLastBroadcast() < 0.2f * USECS_PER_SECOND) { + glm::vec4 yellowColor(1.0f, 1.0f, 0.2f, 1.0f); + renderBoundingBox(entity, args, 0.3f, yellowColor); + } + + if (PhysicsEngine::physicsInfoIsActive(entity->getPhysicsInfo())) { + renderHoverDot(entity, args); } } } diff --git a/libraries/entities-renderer/src/RenderableDebugableEntityItem.h b/libraries/entities-renderer/src/RenderableDebugableEntityItem.h index 9a8344c96d..758bac353b 100644 --- a/libraries/entities-renderer/src/RenderableDebugableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableDebugableEntityItem.h @@ -16,7 +16,8 @@ class RenderableDebugableEntityItem { public: - static void renderBoundingBox(EntityItem* entity, RenderArgs* args, bool puffedOut); + static void renderBoundingBox(EntityItem* entity, RenderArgs* args, float puffedOut, glm::vec4& color); + static void renderHoverDot(EntityItem* entity, RenderArgs* args); static void render(EntityItem* entity, RenderArgs* args); }; diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index b3232bcb57..43112d7d73 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -192,7 +192,8 @@ void RenderableModelEntityItem::render(RenderArgs* args) { } if (!didDraw) { - RenderableDebugableEntityItem::renderBoundingBox(this, args, false); + glm::vec4 greenColor(0.0f, 1.0f, 0.0f, 1.0f); + RenderableDebugableEntityItem::renderBoundingBox(this, args, 0.0f, greenColor); } RenderableDebugableEntityItem::render(this, args); @@ -277,6 +278,11 @@ bool RenderableModelEntityItem::isReadyToComputeShape() { return false; // hmm... } + if (_needsInitialSimulation) { + // the _model's offset will be wrong until _needsInitialSimulation is false + return false; + } + assert(!_model->getCollisionURL().isEmpty()); if (_model->getURL().isEmpty()) { diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 7c0598fbfa..468fc164a2 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -64,9 +64,10 @@ EntityItem::EntityItem(const EntityItemID& entityItemID) : _simulatorIDChangedTime(0), _marketplaceID(ENTITY_ITEM_DEFAULT_MARKETPLACE_ID), _name(ENTITY_ITEM_DEFAULT_NAME), - _physicsInfo(NULL), _dirtyFlags(0), - _element(NULL) + _element(nullptr), + _physicsInfo(nullptr), + _simulated(false) { quint64 now = usecTimestampNow(); _lastSimulated = now; @@ -79,9 +80,11 @@ EntityItem::EntityItem(const EntityItemID& entityItemID, const EntityItemPropert } EntityItem::~EntityItem() { - // be sure to clean up _physicsInfo before calling this dtor - assert(_physicsInfo == NULL); - assert(_element == NULL); + // these pointers MUST be NULL at delete, else we probably have a dangling backpointer + // to this EntityItem in the corresponding data structure. + assert(!_simulated); + assert(!_element); + assert(!_physicsInfo); } EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& params) const { @@ -310,10 +313,10 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef // if this bitstream indicates that this node is the simulation owner, ignore any physics-related updates. glm::vec3 savePosition = _position; glm::quat saveRotation = _rotation; - glm::vec3 saveVelocity = _velocity; - glm::vec3 saveAngularVelocity = _angularVelocity; - glm::vec3 saveGravity = _gravity; - glm::vec3 saveAcceleration = _acceleration; + // glm::vec3 saveVelocity = _velocity; + // glm::vec3 saveAngularVelocity = _angularVelocity; + // glm::vec3 saveGravity = _gravity; + // glm::vec3 saveAcceleration = _acceleration; // Header bytes @@ -398,7 +401,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef if (lastEditedFromBufferAdjusted > now) { lastEditedFromBufferAdjusted = now; } - + bool fromSameServerEdit = (lastEditedFromBuffer == _lastEditedFromRemoteInRemoteTime); #ifdef WANT_DEBUG @@ -542,7 +545,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef READ_ENTITY_PROPERTY_SETTER(PROP_GRAVITY, glm::vec3, updateGravityInDomainUnits); } if (args.bitstreamVersion >= VERSION_ENTITIES_HAVE_ACCELERATION) { - READ_ENTITY_PROPERTY_SETTER(PROP_ACCELERATION, glm::vec3, updateAcceleration); + READ_ENTITY_PROPERTY_SETTER(PROP_ACCELERATION, glm::vec3, setAcceleration); } READ_ENTITY_PROPERTY(PROP_DAMPING, float, _damping); @@ -584,7 +587,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef READ_ENTITY_PROPERTY_STRING(PROP_MARKETPLACE_ID, setMarketplaceID); } - if (overwriteLocalData && (getDirtyFlags() & (EntityItem::DIRTY_POSITION | EntityItem::DIRTY_VELOCITY))) { + if (overwriteLocalData && (getDirtyFlags() & (EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES))) { // NOTE: This code is attempting to "repair" the old data we just got from the server to make it more // closely match where the entities should be if they'd stepped forward in time to "now". The server // is sending us data with a known "last simulated" time. That time is likely in the past, and therefore @@ -615,10 +618,10 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef // this node, so our version has to be newer than what the packet contained. _position = savePosition; _rotation = saveRotation; - _velocity = saveVelocity; - _angularVelocity = saveAngularVelocity; - _gravity = saveGravity; - _acceleration = saveAcceleration; + // _velocity = saveVelocity; + // _angularVelocity = saveAngularVelocity; + // _gravity = saveGravity; + // _acceleration = saveAcceleration; } return bytesRead; @@ -659,12 +662,17 @@ void EntityItem::setDensity(float density) { _density = glm::max(glm::min(density, ENTITY_ITEM_MAX_DENSITY), ENTITY_ITEM_MIN_DENSITY); } +const float ACTIVATION_RELATIVE_DENSITY_DELTA = 0.01f; // 1 percent + void EntityItem::updateDensity(float density) { - const float MIN_DENSITY_CHANGE_FACTOR = 0.001f; // 0.1 percent - float newDensity = glm::max(glm::min(density, ENTITY_ITEM_MAX_DENSITY), ENTITY_ITEM_MIN_DENSITY); - if (fabsf(_density - newDensity) / _density > MIN_DENSITY_CHANGE_FACTOR) { - _density = newDensity; - _dirtyFlags |= EntityItem::DIRTY_MASS; + float clampedDensity = glm::max(glm::min(density, ENTITY_ITEM_MAX_DENSITY), ENTITY_ITEM_MIN_DENSITY); + if (_density != clampedDensity) { + _density = clampedDensity; + + if (fabsf(_density - clampedDensity) / _density > ACTIVATION_RELATIVE_DENSITY_DELTA) { + // the density has changed enough that we should update the physics simulation + _dirtyFlags |= EntityItem::DIRTY_MASS; + } } } @@ -918,7 +926,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(density, updateDensity); SET_ENTITY_PROPERTY_FROM_PROPERTIES(velocity, updateVelocity); SET_ENTITY_PROPERTY_FROM_PROPERTIES(gravity, updateGravity); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(acceleration, updateAcceleration); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(acceleration, setAcceleration); SET_ENTITY_PROPERTY_FROM_PROPERTIES(damping, updateDamping); SET_ENTITY_PROPERTY_FROM_PROPERTIES(lifetime, updateLifetime); SET_ENTITY_PROPERTY_FROM_PROPERTIES(script, setScript); @@ -947,7 +955,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { if (_created != UNKNOWN_CREATED_TIME) { setLastEdited(now); } - if (getDirtyFlags() & (EntityItem::DIRTY_POSITION | EntityItem::DIRTY_VELOCITY)) { + if (getDirtyFlags() & (EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES)) { // TODO: Andrew & Brad to discuss. Is this correct? Maybe it is. Need to think through all cases. _lastSimulated = now; } @@ -1097,14 +1105,22 @@ void EntityItem::computeShapeInfo(ShapeInfo& info) { info.setParams(getShapeType(), 0.5f * getDimensions()); } -const float MIN_POSITION_DELTA = 0.0001f; -const float MIN_DIMENSIONS_DELTA = 0.0005f; -const float MIN_ALIGNMENT_DOT = 0.999999f; -const float MIN_VELOCITY_DELTA = 0.01f; -const float MIN_DAMPING_DELTA = 0.001f; -const float MIN_GRAVITY_DELTA = 0.001f; -const float MIN_ACCELERATION_DELTA = 0.001f; -const float MIN_SPIN_DELTA = 0.0003f; +// these thesholds determine what updates will be ignored (client and server) +const float IGNORE_POSITION_DELTA = 0.0001f; +const float IGNORE_DIMENSIONS_DELTA = 0.0005f; +const float IGNORE_ALIGNMENT_DOT = 0.99997f; +const float IGNORE_LINEAR_VELOCITY_DELTA = 0.001f; +const float IGNORE_DAMPING_DELTA = 0.001f; +const float IGNORE_GRAVITY_DELTA = 0.001f; +const float IGNORE_ANGULAR_VELOCITY_DELTA = 0.0002f; + +// these thresholds determine what updates will activate the physical object +const float ACTIVATION_POSITION_DELTA = 0.005f; +const float ACTIVATION_DIMENSIONS_DELTA = 0.005f; +const float ACTIVATION_ALIGNMENT_DOT = 0.99990f; +const float ACTIVATION_LINEAR_VELOCITY_DELTA = 0.01f; +const float ACTIVATION_GRAVITY_DELTA = 0.1f; +const float ACTIVATION_ANGULAR_VELOCITY_DELTA = 0.03f; void EntityItem::updatePositionInDomainUnits(const glm::vec3& value) { glm::vec3 position = value * (float)TREE_SCALE; @@ -1112,10 +1128,13 @@ void EntityItem::updatePositionInDomainUnits(const glm::vec3& value) { } void EntityItem::updatePosition(const glm::vec3& value) { - if (value != _position) { - auto distance = glm::distance(_position, value); - _dirtyFlags |= (distance > MIN_POSITION_DELTA) ? EntityItem::DIRTY_POSITION : EntityItem::DIRTY_PHYSICS_NO_WAKE; + auto delta = glm::distance(_position, value); + if (delta > IGNORE_POSITION_DELTA) { + _dirtyFlags |= EntityItem::DIRTY_POSITION; _position = value; + if (delta > ACTIVATION_POSITION_DELTA) { + _dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION; + } } } @@ -1125,17 +1144,27 @@ void EntityItem::updateDimensionsInDomainUnits(const glm::vec3& value) { } void EntityItem::updateDimensions(const glm::vec3& value) { - if (glm::distance(_dimensions, value) > MIN_DIMENSIONS_DELTA) { + auto delta = glm::distance(_dimensions, value); + if (delta > IGNORE_DIMENSIONS_DELTA) { _dimensions = value; - _dirtyFlags |= (EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_MASS); + if (delta > ACTIVATION_DIMENSIONS_DELTA) { + // rebuilding the shape will always activate + _dirtyFlags |= (EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_MASS); + } } } void EntityItem::updateRotation(const glm::quat& rotation) { - if (rotation != _rotation) { - auto alignmentDot = glm::abs(glm::dot(_rotation, rotation)); - _dirtyFlags |= (alignmentDot < MIN_ALIGNMENT_DOT) ? EntityItem::DIRTY_POSITION : EntityItem::DIRTY_PHYSICS_NO_WAKE; + if (_rotation != rotation) { _rotation = rotation; + + auto alignmentDot = glm::abs(glm::dot(_rotation, rotation)); + if (alignmentDot < IGNORE_ALIGNMENT_DOT) { + _dirtyFlags |= EntityItem::DIRTY_ROTATION; + } + if (alignmentDot < ACTIVATION_ALIGNMENT_DOT) { + _dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION; + } } } @@ -1156,9 +1185,11 @@ void EntityItem::updateMass(float mass) { newDensity = glm::max(glm::min(mass / volume, ENTITY_ITEM_MAX_DENSITY), ENTITY_ITEM_MIN_DENSITY); } - const float MIN_DENSITY_CHANGE_FACTOR = 0.001f; // 0.1 percent - if (fabsf(_density - newDensity) / _density > MIN_DENSITY_CHANGE_FACTOR) { - _density = newDensity; + float oldDensity = _density; + _density = newDensity; + + if (fabsf(_density - oldDensity) / _density > ACTIVATION_RELATIVE_DENSITY_DELTA) { + // the density has changed enough that we should update the physics simulation _dirtyFlags |= EntityItem::DIRTY_MASS; } } @@ -1169,19 +1200,26 @@ void EntityItem::updateVelocityInDomainUnits(const glm::vec3& value) { } void EntityItem::updateVelocity(const glm::vec3& value) { - if (glm::distance(_velocity, value) > MIN_VELOCITY_DELTA) { - if (glm::length(value) < MIN_VELOCITY_DELTA) { + auto delta = glm::distance(_velocity, value); + if (delta > IGNORE_LINEAR_VELOCITY_DELTA) { + 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_VELOCITY; + _dirtyFlags |= EntityItem::DIRTY_LINEAR_VELOCITY; + + if (delta > ACTIVATION_LINEAR_VELOCITY_DELTA) { + _dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION; + } } } void EntityItem::updateDamping(float value) { - if (fabsf(_damping - value) > MIN_DAMPING_DELTA) { - _damping = glm::clamp(value, 0.0f, 1.0f); + auto clampedDamping = glm::clamp(value, 0.0f, 1.0f); + if (fabsf(_damping - clampedDamping) > IGNORE_DAMPING_DELTA) { + _damping = clampedDamping; _dirtyFlags |= EntityItem::DIRTY_MATERIAL; } } @@ -1192,34 +1230,36 @@ void EntityItem::updateGravityInDomainUnits(const glm::vec3& value) { } void EntityItem::updateGravity(const glm::vec3& value) { - if (glm::distance(_gravity, value) > MIN_GRAVITY_DELTA) { + auto delta = glm::distance(_gravity, value); + if (delta > IGNORE_GRAVITY_DELTA) { _gravity = value; - _dirtyFlags |= EntityItem::DIRTY_VELOCITY; - } -} - -void EntityItem::updateAcceleration(const glm::vec3& value) { - if (glm::distance(_acceleration, value) > MIN_ACCELERATION_DELTA) { - _acceleration = value; - _dirtyFlags |= EntityItem::DIRTY_VELOCITY; + _dirtyFlags |= EntityItem::DIRTY_LINEAR_VELOCITY; + if (delta > ACTIVATION_GRAVITY_DELTA) { + _dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION; + } } } void EntityItem::updateAngularVelocity(const glm::vec3& value) { - auto distance = glm::distance(_angularVelocity, value); - if (distance > MIN_SPIN_DELTA) { - _dirtyFlags |= EntityItem::DIRTY_VELOCITY; - if (glm::length(value) < MIN_SPIN_DELTA) { + auto delta = glm::distance(_angularVelocity, value); + if (delta > IGNORE_ANGULAR_VELOCITY_DELTA) { + _dirtyFlags |= EntityItem::DIRTY_ANGULAR_VELOCITY; + const float MIN_ANGULAR_SPEED = 0.0002f; + if (glm::length(value) < MIN_ANGULAR_SPEED) { _angularVelocity = ENTITY_ITEM_ZERO_VEC3; } else { _angularVelocity = value; } + if (delta > ACTIVATION_ANGULAR_VELOCITY_DELTA) { + _dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION; + } } } void EntityItem::updateAngularDamping(float value) { - if (fabsf(_angularDamping - value) > MIN_DAMPING_DELTA) { - _angularDamping = glm::clamp(value, 0.0f, 1.0f); + auto clampedDamping = glm::clamp(value, 0.0f, 1.0f); + if (fabsf(_angularDamping - clampedDamping) > IGNORE_DAMPING_DELTA) { + _angularDamping = clampedDamping; _dirtyFlags |= EntityItem::DIRTY_MATERIAL; } } diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 6026a74e11..4f8282c29c 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -28,7 +28,7 @@ #include "EntityItemPropertiesDefaults.h" #include "EntityTypes.h" -class EntityTree; +class EntitySimulation; class EntityTreeElement; class EntityTreeElementExtraEncodeData; @@ -44,19 +44,28 @@ class EntityTreeElementExtraEncodeData; /// to all other entity types. In particular: postion, size, rotation, age, lifetime, velocity, gravity. You can not instantiate /// one directly, instead you must only construct one of it's derived classes with additional features. class EntityItem { + // These two classes manage lists of EntityItem pointers and must be able to cleanup pointers when an EntityItem is deleted. + // To make the cleanup robust each EntityItem has backpointers to its manager classes (which are only ever set/cleared by + // the managers themselves, hence they are fiends) whose NULL status can be used to determine which managers still need to + // do cleanup. friend class EntityTreeElement; + friend class EntitySimulation; public: enum EntityDirtyFlags { DIRTY_POSITION = 0x0001, - DIRTY_VELOCITY = 0x0002, - DIRTY_MASS = 0x0004, - DIRTY_COLLISION_GROUP = 0x0008, - DIRTY_MOTION_TYPE = 0x0010, - DIRTY_SHAPE = 0x0020, - DIRTY_LIFETIME = 0x0040, - DIRTY_UPDATEABLE = 0x0080, - DIRTY_MATERIAL = 0x00100, - DIRTY_PHYSICS_NO_WAKE = 0x0200 // we want to update values in physics engine without "waking" the object up + DIRTY_ROTATION = 0x0002, + DIRTY_LINEAR_VELOCITY = 0x0004, + DIRTY_ANGULAR_VELOCITY = 0x0008, + DIRTY_MASS = 0x0010, + DIRTY_COLLISION_GROUP = 0x0020, + DIRTY_MOTION_TYPE = 0x0040, + DIRTY_SHAPE = 0x0080, + DIRTY_LIFETIME = 0x0100, + DIRTY_UPDATEABLE = 0x0200, + DIRTY_MATERIAL = 0x00400, + DIRTY_PHYSICS_ACTIVATION = 0x0800, // we want to activate the object + DIRTY_TRANSFORM = DIRTY_POSITION | DIRTY_ROTATION, + DIRTY_VELOCITIES = DIRTY_LINEAR_VELOCITY | DIRTY_ANGULAR_VELOCITY }; DONT_ALLOW_INSTANTIATION // This class can not be instantiated directly @@ -66,7 +75,7 @@ public: virtual ~EntityItem(); // ID and EntityItemID related methods - QUuid getID() const { return _id; } + const QUuid& getID() const { return _id; } void setID(const QUuid& id) { _id = id; } uint32_t getCreatorTokenID() const { return _creatorTokenID; } void setCreatorTokenID(uint32_t creatorTokenID) { _creatorTokenID = creatorTokenID; } @@ -95,6 +104,10 @@ public: float getEditedAgo() const /// Elapsed seconds since this entity was last edited { return (float)(usecTimestampNow() - getLastEdited()) / (float)USECS_PER_SECOND; } + /// Last time we sent out an edit packet for this entity + quint64 getLastBroadcast() const { return _lastBroadcast; } + void setLastBroadcast(quint64 lastBroadcast) { _lastBroadcast = lastBroadcast; } + void markAsChangedOnServer() { _changedOnServer = usecTimestampNow(); } quint64 getLastChangedOnServer() const { return _changedOnServer; } @@ -243,6 +256,8 @@ public: bool getCollisionsWillMove() const { return _collisionsWillMove; } void setCollisionsWillMove(bool value) { _collisionsWillMove = value; } + virtual bool shouldBePhysical() const { return !_ignoreForCollisions; } + bool getLocked() const { return _locked; } void setLocked(bool value) { _locked = value; } @@ -281,7 +296,6 @@ public: void updateDamping(float value); void updateGravityInDomainUnits(const glm::vec3& value); void updateGravity(const glm::vec3& value); - void updateAcceleration(const glm::vec3& value); void updateAngularVelocity(const glm::vec3& value); void updateAngularVelocityInDegrees(const glm::vec3& value) { updateAngularVelocity(glm::radians(value)); } void updateAngularDamping(float value); @@ -296,8 +310,8 @@ public: bool isMoving() const; void* getPhysicsInfo() const { return _physicsInfo; } - void setPhysicsInfo(void* data) { _physicsInfo = data; } + void setPhysicsInfo(void* data) { _physicsInfo = data; } EntityTreeElement* getElement() const { return _element; } static void setSendPhysicsUpdates(bool value) { _sendPhysicsUpdates = value; } @@ -320,6 +334,7 @@ protected: quint64 _lastSimulated; // last time this entity called simulate(), this includes velocity, angular velocity, and physics changes quint64 _lastUpdated; // last time this entity called update(), this includes animations and non-physics changes quint64 _lastEdited; // last official local or remote edit time + quint64 _lastBroadcast; // the last time we sent an edit packet about this entity quint64 _lastEditedFromRemote; // last time we received and edit from the server quint64 _lastEditedFromRemoteInRemoteTime; // last time we received an edit from the server (in server-time-frame) @@ -371,16 +386,23 @@ protected: /// set radius in domain scale units (0.0 - 1.0) this will also reset dimensions to be equal for each axis void setRadius(float value); - // _physicsInfo is a hook reserved for use by the EntitySimulation, which is guaranteed to set _physicsInfo - // to a non-NULL value when the EntityItem has a representation in the physics engine. - void* _physicsInfo = NULL; // only set by EntitySimulation - // DirtyFlags are set whenever a property changes that the EntitySimulation needs to know about. uint32_t _dirtyFlags; // things that have changed from EXTERNAL changes (via script or packet) but NOT from simulation - EntityTreeElement* _element; // back pointer to containing Element + // these backpointers are only ever set/cleared by friends: + EntityTreeElement* _element = nullptr; // set by EntityTreeElement + void* _physicsInfo = nullptr; // set by EntitySimulation + bool _simulated; // set by EntitySimulation }; +extern const float IGNORE_POSITION_DELTA; +extern const float IGNORE_DIMENSIONS_DELTA; +extern const float IGNORE_ALIGNMENT_DOT; +extern const float IGNORE_LINEAR_VELOCITY_DELTA; +extern const float IGNORE_DAMPING_DELTA; +extern const float IGNORE_GRAVITY_DELTA; +extern const float IGNORE_ANGULAR_VELOCITY_DELTA; + #endif // hifi_EntityItem_h diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 6632574db8..46ca70aa43 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -86,6 +86,7 @@ EntityItemID EntityScriptingInterface::addEntity(const EntityItemProperties& pro if (_entityTree) { _entityTree->lockForWrite(); EntityItem* entity = _entityTree->addEntity(id, propertiesWithSimID); + 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); @@ -178,6 +179,7 @@ EntityItemID EntityScriptingInterface::editEntity(EntityItemID entityID, const E if (propertiesWithSimID.getType() == EntityTypes::Unknown) { EntityItem* entity = _entityTree->findEntityByEntityItemID(entityID); if (entity) { + entity->setLastBroadcast(usecTimestampNow()); propertiesWithSimID.setType(entity->getType()); setSimId(propertiesWithSimID, entity); } diff --git a/libraries/entities/src/EntitySimulation.cpp b/libraries/entities/src/EntitySimulation.cpp index a390521ed8..d28e139205 100644 --- a/libraries/entities/src/EntitySimulation.cpp +++ b/libraries/entities/src/EntitySimulation.cpp @@ -19,41 +19,67 @@ void EntitySimulation::setEntityTree(EntityTree* tree) { if (_entityTree && _entityTree != tree) { _mortalEntities.clear(); _nextExpiry = quint64(-1); - _updateableEntities.clear(); - _entitiesToBeSorted.clear(); + _entitiesToUpdate.clear(); + _entitiesToSort.clear(); + _simpleKinematicEntities.clear(); } _entityTree = tree; } -void EntitySimulation::updateEntities(QSet& entitiesToDelete) { +void EntitySimulation::updateEntities() { quint64 now = usecTimestampNow(); // these methods may accumulate entries in _entitiesToBeDeleted expireMortalEntities(now); callUpdateOnEntitiesThatNeedIt(now); + moveSimpleKinematics(now); updateEntitiesInternal(now); sortEntitiesThatMoved(); +} - // at this point we harvest _entitiesToBeDeleted - entitiesToDelete.unite(_entitiesToDelete); +void EntitySimulation::getEntitiesToDelete(VectorOfEntities& entitiesToDelete) { + for (auto entityItr : _entitiesToDelete) { + EntityItem* entity = &(*entityItr); + // this entity is still in its tree, so we insert into the external list + entitiesToDelete.push_back(entity); + ++entityItr; + } _entitiesToDelete.clear(); } -// private +void EntitySimulation::addEntityInternal(EntityItem* entity) { + if (entity->isMoving() && !entity->getPhysicsInfo()) { + _simpleKinematicEntities.insert(entity); + } +} + +void EntitySimulation::changeEntityInternal(EntityItem* entity) { + if (entity->isMoving() && !entity->getPhysicsInfo()) { + _simpleKinematicEntities.insert(entity); + } else { + _simpleKinematicEntities.remove(entity); + } +} + +// protected void EntitySimulation::expireMortalEntities(const quint64& now) { if (now > _nextExpiry) { // only search for expired entities if we expect to find one _nextExpiry = quint64(-1); - QSet::iterator itemItr = _mortalEntities.begin(); + SetOfEntities::iterator itemItr = _mortalEntities.begin(); while (itemItr != _mortalEntities.end()) { EntityItem* entity = *itemItr; quint64 expiry = entity->getExpiry(); if (expiry < now) { _entitiesToDelete.insert(entity); itemItr = _mortalEntities.erase(itemItr); - _updateableEntities.remove(entity); - _entitiesToBeSorted.remove(entity); + _entitiesToUpdate.remove(entity); + _entitiesToSort.remove(entity); + _simpleKinematicEntities.remove(entity); removeEntityInternal(entity); + + _allEntities.remove(entity); + entity->_simulated = false; } else { if (expiry < _nextExpiry) { // remeber the smallest _nextExpiry so we know when to start the next search @@ -65,16 +91,16 @@ void EntitySimulation::expireMortalEntities(const quint64& now) { } } -// private +// protected void EntitySimulation::callUpdateOnEntitiesThatNeedIt(const quint64& now) { PerformanceTimer perfTimer("updatingEntities"); - QSet::iterator itemItr = _updateableEntities.begin(); - while (itemItr != _updateableEntities.end()) { + SetOfEntities::iterator itemItr = _entitiesToUpdate.begin(); + while (itemItr != _entitiesToUpdate.end()) { EntityItem* entity = *itemItr; // TODO: catch transition from needing update to not as a "change" // so we don't have to scan for it here. if (!entity->needsToCallUpdate()) { - itemItr = _updateableEntities.erase(itemItr); + itemItr = _entitiesToUpdate.erase(itemItr); } else { entity->update(now); ++itemItr; @@ -82,15 +108,15 @@ void EntitySimulation::callUpdateOnEntitiesThatNeedIt(const quint64& now) { } } -// private +// protected void EntitySimulation::sortEntitiesThatMoved() { // NOTE: this is only for entities that have been moved by THIS EntitySimulation. // External changes to entity position/shape are expected to be sorted outside of the EntitySimulation. PerformanceTimer perfTimer("sortingEntities"); MovingEntitiesOperator moveOperator(_entityTree); AACube domainBounds(glm::vec3(0.0f,0.0f,0.0f), (float)TREE_SCALE); - QSet::iterator itemItr = _entitiesToBeSorted.begin(); - while (itemItr != _entitiesToBeSorted.end()) { + SetOfEntities::iterator itemItr = _entitiesToSort.begin(); + while (itemItr != _entitiesToSort.end()) { EntityItem* entity = *itemItr; // check to see if this movement has sent the entity outside of the domain. AACube newCube = entity->getMaximumAACube(); @@ -98,9 +124,14 @@ void EntitySimulation::sortEntitiesThatMoved() { qCDebug(entities) << "Entity " << entity->getEntityItemID() << " moved out of domain bounds."; _entitiesToDelete.insert(entity); _mortalEntities.remove(entity); - _updateableEntities.remove(entity); + _entitiesToUpdate.remove(entity); + _simpleKinematicEntities.remove(entity); removeEntityInternal(entity); - itemItr = _entitiesToBeSorted.erase(itemItr); + + _allEntities.remove(entity); + entity->_simulated = false; + + itemItr = _entitiesToSort.erase(itemItr); } else { moveOperator.addEntityToMoveList(entity, newCube); ++itemItr; @@ -111,8 +142,7 @@ void EntitySimulation::sortEntitiesThatMoved() { _entityTree->recurseTreeWithOperator(&moveOperator); } - sortEntitiesThatMovedInternal(); - _entitiesToBeSorted.clear(); + _entitiesToSort.clear(); } void EntitySimulation::addEntity(EntityItem* entity) { @@ -125,10 +155,13 @@ void EntitySimulation::addEntity(EntityItem* entity) { } } if (entity->needsToCallUpdate()) { - _updateableEntities.insert(entity); + _entitiesToUpdate.insert(entity); } addEntityInternal(entity); + _allEntities.insert(entity); + entity->_simulated = true; + // DirtyFlags are used to signal changes to entities that have already been added, // so we can clear them for this entity which has just been added. entity->clearDirtyFlags(); @@ -136,15 +169,25 @@ void EntitySimulation::addEntity(EntityItem* entity) { void EntitySimulation::removeEntity(EntityItem* entity) { assert(entity); - _updateableEntities.remove(entity); + _entitiesToUpdate.remove(entity); _mortalEntities.remove(entity); - _entitiesToBeSorted.remove(entity); + _entitiesToSort.remove(entity); + _simpleKinematicEntities.remove(entity); _entitiesToDelete.remove(entity); removeEntityInternal(entity); + + _allEntities.remove(entity); + entity->_simulated = false; } -void EntitySimulation::entityChanged(EntityItem* entity) { +void EntitySimulation::changeEntity(EntityItem* entity) { assert(entity); + if (!entity->_simulated) { + // This entity was either never added to the simulation or has been removed + // (probably for pending delete), so we don't want to keep a pointer to it + // on any internal lists. + return; + } // Although it is not the responsibility of the EntitySimulation to sort the tree for EXTERNAL changes // it IS responsibile for triggering deletes for entities that leave the bounds of the domain, hence @@ -158,8 +201,11 @@ void EntitySimulation::entityChanged(EntityItem* entity) { qCDebug(entities) << "Entity " << entity->getEntityItemID() << " moved out of domain bounds."; _entitiesToDelete.insert(entity); _mortalEntities.remove(entity); - _updateableEntities.remove(entity); + _entitiesToUpdate.remove(entity); + _entitiesToSort.remove(entity); + _simpleKinematicEntities.remove(entity); removeEntityInternal(entity); + entity->_simulated = false; wasRemoved = true; } } @@ -177,18 +223,41 @@ void EntitySimulation::entityChanged(EntityItem* entity) { entity->clearDirtyFlags(EntityItem::DIRTY_LIFETIME); } if (entity->needsToCallUpdate()) { - _updateableEntities.insert(entity); + _entitiesToUpdate.insert(entity); } else { - _updateableEntities.remove(entity); + _entitiesToUpdate.remove(entity); } - entityChangedInternal(entity); + changeEntityInternal(entity); } } void EntitySimulation::clearEntities() { _mortalEntities.clear(); _nextExpiry = quint64(-1); - _updateableEntities.clear(); - _entitiesToBeSorted.clear(); + _entitiesToUpdate.clear(); + _entitiesToSort.clear(); + _simpleKinematicEntities.clear(); + _entitiesToDelete.clear(); + clearEntitiesInternal(); + + for (auto entityItr : _allEntities) { + entityItr->_simulated = false; + } + _allEntities.clear(); +} + +void EntitySimulation::moveSimpleKinematics(const quint64& now) { + SetOfEntities::iterator itemItr = _simpleKinematicEntities.begin(); + while (itemItr != _simpleKinematicEntities.end()) { + EntityItem* entity = *itemItr; + if (entity->isMoving() && !entity->getPhysicsInfo()) { + entity->simulate(now); + _entitiesToSort.insert(entity); + ++itemItr; + } else { + // the entity is no longer non-physical-kinematic + itemItr = _simpleKinematicEntities.erase(itemItr); + } + } } diff --git a/libraries/entities/src/EntitySimulation.h b/libraries/entities/src/EntitySimulation.h index 1eb4fdc951..a73afe9fd7 100644 --- a/libraries/entities/src/EntitySimulation.h +++ b/libraries/entities/src/EntitySimulation.h @@ -14,23 +14,30 @@ #include #include +#include #include #include "EntityItem.h" #include "EntityTree.h" +typedef QSet SetOfEntities; +typedef QVector VectorOfEntities; + // the EntitySimulation needs to know when these things change on an entity, // so it can sort EntityItem or relay its state to the PhysicsEngine. const int DIRTY_SIMULATION_FLAGS = EntityItem::DIRTY_POSITION | - EntityItem::DIRTY_VELOCITY | + EntityItem::DIRTY_ROTATION | + EntityItem::DIRTY_LINEAR_VELOCITY | + EntityItem::DIRTY_ANGULAR_VELOCITY | EntityItem::DIRTY_MASS | EntityItem::DIRTY_COLLISION_GROUP | EntityItem::DIRTY_MOTION_TYPE | EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_LIFETIME | - EntityItem::DIRTY_UPDATEABLE; + EntityItem::DIRTY_UPDATEABLE | + EntityItem::DIRTY_MATERIAL; class EntitySimulation : public QObject { Q_OBJECT @@ -44,25 +51,34 @@ public: /// \param tree pointer to EntityTree which is stored internally void setEntityTree(EntityTree* tree); - /// \param[out] entitiesToDelete list of entities removed from simulation and should be deleted. - void updateEntities(QSet& entitiesToDelete); + void updateEntities(); - /// \param entity pointer to EntityItem to add to the simulation - /// \sideeffect the EntityItem::_simulationState member may be updated to indicate membership to internal list + friend class EntityTree; + +protected: // these only called by the EntityTree? + /// \param entity pointer to EntityItem to be added + /// \sideeffect sets relevant backpointers in entity, but maybe later when appropriate data structures are locked void addEntity(EntityItem* entity); - /// \param entity pointer to EntityItem to removed from the simulation - /// \sideeffect the EntityItem::_simulationState member may be updated to indicate non-membership to internal list + /// \param entity pointer to EntityItem to be removed + /// \brief the actual removal may happen later when appropriate data structures are locked + /// \sideeffect nulls relevant backpointers in entity void removeEntity(EntityItem* entity); /// \param entity pointer to EntityItem to that may have changed in a way that would affect its simulation /// call this whenever an entity was changed from some EXTERNAL event (NOT by the EntitySimulation itself) - void entityChanged(EntityItem* entity); + void changeEntity(EntityItem* entity); void clearEntities(); + void moveSimpleKinematics(const quint64& now); + +public: + EntityTree* getEntityTree() { return _entityTree; } + void getEntitiesToDelete(VectorOfEntities& entitiesToDelete); + signals: void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision); @@ -70,18 +86,10 @@ protected: // These pure virtual methods are protected because they are not to be called will-nilly. The base class // calls them in the right places. - - // NOTE: updateEntitiesInternal() should clear all dirty flags on each changed entity as side effect virtual void updateEntitiesInternal(const quint64& now) = 0; - - virtual void addEntityInternal(EntityItem* entity) = 0; - + virtual void addEntityInternal(EntityItem* entity); virtual void removeEntityInternal(EntityItem* entity) = 0; - - virtual void entityChangedInternal(EntityItem* entity) = 0; - - virtual void sortEntitiesThatMovedInternal() {} - + virtual void changeEntityInternal(EntityItem* entity); virtual void clearEntitiesInternal() = 0; void expireMortalEntities(const quint64& now); @@ -95,11 +103,16 @@ protected: // We maintain multiple lists, each for its distinct purpose. // An entity may be in more than one list. - QSet _mortalEntities; // entities that have an expiry + SetOfEntities _allEntities; // tracks all entities added the simulation + SetOfEntities _mortalEntities; // entities that have an expiry quint64 _nextExpiry; - QSet _updateableEntities; // entities that need update() called - QSet _entitiesToBeSorted; // entities that were moved by THIS simulation and might need to be resorted in the tree - QSet _entitiesToDelete; + SetOfEntities _entitiesToUpdate; // entities that need to call EntityItem::update() + SetOfEntities _entitiesToSort; // entities moved by simulation (and might need resort in EntityTree) + SetOfEntities _entitiesToDelete; // entities simulation decided needed to be deleted (EntityTree will actually delete) + SetOfEntities _simpleKinematicEntities; // entities undergoing non-colliding kinematic motion + +private: + void moveSimpleKinematics(); }; #endif // hifi_EntitySimulation_h diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index f8f94f8d17..5aea021e7a 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -145,8 +145,7 @@ bool EntityTree::updateEntityWithElement(EntityItem* entity, const EntityItemPro // squash the physics-related changes. properties.setSimulatorIDChanged(false); properties.setPositionChanged(false); - properties.setVelocityChanged(false); - properties.setAccelerationChanged(false); + properties.setRotationChanged(false); } else { qCDebug(entities) << "allowing simulatorID change"; } @@ -160,10 +159,10 @@ bool EntityTree::updateEntityWithElement(EntityItem* entity, const EntityItemPro uint32_t newFlags = entity->getDirtyFlags() & ~preFlags; if (newFlags) { - if (_simulation) { + if (_simulation) { if (newFlags & DIRTY_SIMULATION_FLAGS) { _simulation->lock(); - _simulation->entityChanged(entity); + _simulation->changeEntity(entity); _simulation->unlock(); } } else { @@ -351,8 +350,8 @@ void EntityTree::processRemovedEntities(const DeleteEntityOperator& theOperator) if (_simulation) { _simulation->removeEntity(theEntity); - } - delete theEntity; // now actually delete the entity! + } + delete theEntity; // we can delete the entity immediately } if (_simulation) { _simulation->unlock(); @@ -742,7 +741,7 @@ void EntityTree::releaseSceneEncodeData(OctreeElementExtraEncodeData* extraEncod void EntityTree::entityChanged(EntityItem* entity) { if (_simulation) { _simulation->lock(); - _simulation->entityChanged(entity); + _simulation->changeEntity(entity); _simulation->unlock(); } } @@ -750,16 +749,21 @@ void EntityTree::entityChanged(EntityItem* entity) { void EntityTree::update() { if (_simulation) { lockForWrite(); - QSet entitiesToDelete; _simulation->lock(); - _simulation->updateEntities(entitiesToDelete); + _simulation->updateEntities(); + VectorOfEntities pendingDeletes; + _simulation->getEntitiesToDelete(pendingDeletes); _simulation->unlock(); - if (entitiesToDelete.size() > 0) { + + if (pendingDeletes.size() > 0) { // translate into list of ID's QSet idsToDelete; - foreach (EntityItem* entity, entitiesToDelete) { + for (auto entityItr : pendingDeletes) { + EntityItem* entity = &(*entityItr); + assert(!entity->getPhysicsInfo()); // TODO: Andrew to remove this after testing idsToDelete.insert(entity->getEntityItemID()); } + // delete these things the roundabout way deleteEntities(idsToDelete, true); } unlock(); diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 3880d67eda..e5c4e68f2f 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -12,13 +12,13 @@ #ifndef hifi_EntityTree_h #define hifi_EntityTree_h -#include +#include #include + #include "EntityTreeElement.h" #include "DeleteEntityOperator.h" - class Model; class EntitySimulation; diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index 1abb48abcd..b5ec5e11d4 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -33,8 +33,8 @@ EntityItem* ModelEntityItem::factory(const EntityItemID& entityID, const EntityI } ModelEntityItem::ModelEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : - EntityItem(entityItemID, properties) -{ + EntityItem(entityItemID, properties) +{ _type = EntityTypes::Model; setProperties(properties); _lastAnimated = usecTimestampNow(); @@ -84,17 +84,17 @@ bool ModelEntityItem::setProperties(const EntityItemProperties& properties) { } setLastEdited(properties._lastEdited); } - + return somethingChanged; } -int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, +int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args, EntityPropertyFlags& propertyFlags, bool overwriteLocalData) { - + int bytesRead = 0; const unsigned char* dataAt = data; - + READ_ENTITY_PROPERTY_COLOR(PROP_COLOR, _color); READ_ENTITY_PROPERTY_STRING(PROP_MODEL_URL, setModelURL); if (args.bitstreamVersion < VERSION_ENTITIES_HAS_COLLISION_MODEL) { @@ -105,7 +105,7 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, READ_ENTITY_PROPERTY_STRING(PROP_COMPOUND_SHAPE_URL, setCompoundShapeURL); } READ_ENTITY_PROPERTY_STRING(PROP_ANIMATION_URL, setAnimationURL); - + // Because we're using AnimationLoop which will reset the frame index if you change it's running state // we want to read these values in the order they appear in the buffer, but call our setters in an // order that allows AnimationLoop to preserve the correct frame rate. @@ -115,7 +115,7 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, READ_ENTITY_PROPERTY(PROP_ANIMATION_FPS, float, animationFPS); READ_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, float, animationFrameIndex); READ_ENTITY_PROPERTY(PROP_ANIMATION_PLAYING, bool, animationIsPlaying); - + if (propertyFlags.getHasProperty(PROP_ANIMATION_PLAYING)) { if (animationIsPlaying != getAnimationIsPlaying()) { setAnimationIsPlaying(animationIsPlaying); @@ -148,12 +148,12 @@ EntityPropertyFlags ModelEntityItem::getEntityProperties(EncodeBitstreamParams& requestedProperties += PROP_ANIMATION_SETTINGS; requestedProperties += PROP_TEXTURES; requestedProperties += PROP_SHAPE_TYPE; - + return requestedProperties; } -void ModelEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, +void ModelEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData, EntityPropertyFlags& requestedProperties, EntityPropertyFlags& propertyFlags, @@ -186,7 +186,7 @@ void ModelEntityItem::cleanupLoadedAnimations() { Animation* ModelEntityItem::getAnimation(const QString& url) { AnimationPointer animation; - + // if we don't already have this model then create it and initialize it if (_loadedAnimations.find(url) == _loadedAnimations.end()) { animation = DependencyManager::get()->getAnimation(url); @@ -204,7 +204,7 @@ void ModelEntityItem::mapJoints(const QStringList& modelJointNames) { } Animation* myAnimation = getAnimation(_animationURL); - + if (!_jointMappingCompleted) { QStringList animationJointNames = myAnimation->getJointNames(); @@ -229,7 +229,7 @@ QVector ModelEntityItem::getAnimationFrame() { if (animationFrameIndex < 0 || animationFrameIndex > frameCount) { animationFrameIndex = 0; } - + QVector rotations = frames[animationFrameIndex].rotations; frameData.resize(_jointMapping.size()); @@ -244,8 +244,8 @@ QVector ModelEntityItem::getAnimationFrame() { return frameData; } -bool ModelEntityItem::isAnimatingSomething() const { - return getAnimationIsPlaying() && +bool ModelEntityItem::isAnimatingSomething() const { + return getAnimationIsPlaying() && getAnimationFPS() != 0.0f && !getAnimationURL().isEmpty(); } @@ -278,7 +278,7 @@ void ModelEntityItem::debugDump() const { void ModelEntityItem::updateShapeType(ShapeType type) { // BEGIN_TEMPORARY_WORKAROUND // we have allowed inconsistent ShapeType's to be stored in SVO files in the past (this was a bug) - // but we are now enforcing the entity properties to be consistent. To make the possible we're + // but we are now enforcing the entity properties to be consistent. To make the possible we're // introducing a temporary workaround: we will ignore ShapeType updates that conflict with the // _compoundShapeURL. if (hasCompoundShapeURL()) { @@ -292,7 +292,7 @@ void ModelEntityItem::updateShapeType(ShapeType type) { } } -// virtual +// virtual ShapeType ModelEntityItem::getShapeType() const { if (_shapeType == SHAPE_TYPE_COMPOUND) { return hasCompoundShapeURL() ? SHAPE_TYPE_COMPOUND : SHAPE_TYPE_NONE; @@ -309,9 +309,9 @@ void ModelEntityItem::setCompoundShapeURL(const QString& url) { } } -void ModelEntityItem::setAnimationURL(const QString& url) { +void ModelEntityItem::setAnimationURL(const QString& url) { _dirtyFlags |= EntityItem::DIRTY_UPDATEABLE; - _animationURL = url; + _animationURL = url; } void ModelEntityItem::setAnimationFrameIndex(float value) { @@ -327,7 +327,7 @@ void ModelEntityItem::setAnimationFrameIndex(float value) { _animationLoop.setFrameIndex(value); } -void ModelEntityItem::setAnimationSettings(const QString& value) { +void ModelEntityItem::setAnimationSettings(const QString& value) { // the animations setting is a JSON string that may contain various animation settings. // if it includes fps, frameIndex, or running, those values will be parsed out and // will over ride the regular animation settings @@ -388,21 +388,21 @@ void ModelEntityItem::setAnimationSettings(const QString& value) { setAnimationStartAutomatically(startAutomatically); } - _animationSettings = value; + _animationSettings = value; _dirtyFlags |= EntityItem::DIRTY_UPDATEABLE; } void ModelEntityItem::setAnimationIsPlaying(bool value) { _dirtyFlags |= EntityItem::DIRTY_UPDATEABLE; - _animationLoop.setRunning(value); + _animationLoop.setRunning(value); } void ModelEntityItem::setAnimationFPS(float value) { _dirtyFlags |= EntityItem::DIRTY_UPDATEABLE; - _animationLoop.setFPS(value); + _animationLoop.setFPS(value); } -QString ModelEntityItem::getAnimationSettings() const { +QString ModelEntityItem::getAnimationSettings() const { // the animations setting is a JSON string that may contain various animation settings. // if it includes fps, frameIndex, or running, those values will be parsed out and // will over ride the regular animation settings @@ -411,7 +411,7 @@ QString ModelEntityItem::getAnimationSettings() const { QJsonDocument settingsAsJson = QJsonDocument::fromJson(value.toUtf8()); QJsonObject settingsAsJsonObject = settingsAsJson.object(); QVariantMap settingsMap = settingsAsJsonObject.toVariantMap(); - + QVariant fpsValue(getAnimationFPS()); settingsMap["fps"] = fpsValue; @@ -435,10 +435,15 @@ QString ModelEntityItem::getAnimationSettings() const { QVariant startAutomaticallyValue(getAnimationStartAutomatically()); settingsMap["startAutomatically"] = startAutomaticallyValue; - + settingsAsJsonObject = QJsonObject::fromVariantMap(settingsMap); QJsonDocument newDocument(settingsAsJsonObject); QByteArray jsonByteArray = newDocument.toJson(QJsonDocument::Compact); QString jsonByteString(jsonByteArray); return jsonByteString; } + +// virtual +bool ModelEntityItem::shouldBePhysical() const { + return EntityItem::shouldBePhysical() && getShapeType() != SHAPE_TYPE_NONE; +} diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index 6fe2adc928..d4a4efda04 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -117,6 +117,8 @@ public: static const QString DEFAULT_TEXTURES; const QString& getTextures() const { return _textures; } void setTextures(const QString& textures) { _textures = textures; } + + virtual bool shouldBePhysical() const; static void cleanupLoadedAnimations(); diff --git a/libraries/entities/src/SimpleEntitySimulation.cpp b/libraries/entities/src/SimpleEntitySimulation.cpp index 6343ed3e47..518d10d056 100644 --- a/libraries/entities/src/SimpleEntitySimulation.cpp +++ b/libraries/entities/src/SimpleEntitySimulation.cpp @@ -18,32 +18,25 @@ const quint64 AUTO_REMOVE_SIMULATION_OWNER_USEC = 2 * USECS_PER_SECOND; void SimpleEntitySimulation::updateEntitiesInternal(const quint64& now) { - // now is usecTimestampNow() - QSet::iterator itemItr = _movingEntities.begin(); - while (itemItr != _movingEntities.end()) { - EntityItem* entity = *itemItr; - if (!entity->isMoving()) { - itemItr = _movingEntities.erase(itemItr); - _movableButStoppedEntities.insert(entity); - } else { - entity->simulate(now); - _entitiesToBeSorted.insert(entity); - ++itemItr; - } - } - // If an Entity has a simulation owner and we don't get an update for some amount of time, // clear the owner. This guards against an interface failing to release the Entity when it // has finished simulating it. - itemItr = _hasSimulationOwnerEntities.begin(); + auto nodeList = DependencyManager::get(); + + SetOfEntities::iterator itemItr = _hasSimulationOwnerEntities.begin(); while (itemItr != _hasSimulationOwnerEntities.end()) { EntityItem* entity = *itemItr; if (entity->getSimulatorID().isNull()) { itemItr = _hasSimulationOwnerEntities.erase(itemItr); - } else if (usecTimestampNow() - entity->getLastChangedOnServer() >= AUTO_REMOVE_SIMULATION_OWNER_USEC) { - qCDebug(entities) << "auto-removing simulation owner" << entity->getSimulatorID(); - entity->setSimulatorID(QUuid()); - itemItr = _hasSimulationOwnerEntities.erase(itemItr); + } else if (now - entity->getLastChangedOnServer() >= AUTO_REMOVE_SIMULATION_OWNER_USEC) { + SharedNodePointer ownerNode = nodeList->nodeWithUUID(entity->getSimulatorID()); + if (ownerNode.isNull() || !ownerNode->isAlive()) { + qCDebug(entities) << "auto-removing simulation owner" << entity->getSimulatorID(); + entity->setSimulatorID(QUuid()); + itemItr = _hasSimulationOwnerEntities.erase(itemItr); + } else { + ++itemItr; + } } else { ++itemItr; } @@ -51,45 +44,27 @@ void SimpleEntitySimulation::updateEntitiesInternal(const quint64& now) { } void SimpleEntitySimulation::addEntityInternal(EntityItem* entity) { - if (entity->isMoving()) { - _movingEntities.insert(entity); - } else if (entity->getCollisionsWillMove()) { - _movableButStoppedEntities.insert(entity); - } + EntitySimulation::addEntityInternal(entity); if (!entity->getSimulatorID().isNull()) { _hasSimulationOwnerEntities.insert(entity); } } void SimpleEntitySimulation::removeEntityInternal(EntityItem* entity) { - _movingEntities.remove(entity); - _movableButStoppedEntities.remove(entity); _hasSimulationOwnerEntities.remove(entity); } -const int SIMPLE_SIMULATION_DIRTY_FLAGS = EntityItem::DIRTY_VELOCITY | EntityItem::DIRTY_MOTION_TYPE; +const int SIMPLE_SIMULATION_DIRTY_FLAGS = EntityItem::DIRTY_VELOCITIES | EntityItem::DIRTY_MOTION_TYPE; -void SimpleEntitySimulation::entityChangedInternal(EntityItem* entity) { - int dirtyFlags = entity->getDirtyFlags(); - if (dirtyFlags & SIMPLE_SIMULATION_DIRTY_FLAGS) { - if (entity->isMoving()) { - _movingEntities.insert(entity); - } else if (entity->getCollisionsWillMove()) { - _movableButStoppedEntities.remove(entity); - } else { - _movingEntities.remove(entity); - _movableButStoppedEntities.remove(entity); - } - if (!entity->getSimulatorID().isNull()) { - _hasSimulationOwnerEntities.insert(entity); - } +void SimpleEntitySimulation::changeEntityInternal(EntityItem* entity) { + EntitySimulation::changeEntityInternal(entity); + if (!entity->getSimulatorID().isNull()) { + _hasSimulationOwnerEntities.insert(entity); } entity->clearDirtyFlags(); } void SimpleEntitySimulation::clearEntitiesInternal() { - _movingEntities.clear(); - _movableButStoppedEntities.clear(); _hasSimulationOwnerEntities.clear(); } diff --git a/libraries/entities/src/SimpleEntitySimulation.h b/libraries/entities/src/SimpleEntitySimulation.h index af79ec0131..3a6934adfa 100644 --- a/libraries/entities/src/SimpleEntitySimulation.h +++ b/libraries/entities/src/SimpleEntitySimulation.h @@ -25,12 +25,10 @@ protected: virtual void updateEntitiesInternal(const quint64& now); virtual void addEntityInternal(EntityItem* entity); virtual void removeEntityInternal(EntityItem* entity); - virtual void entityChangedInternal(EntityItem* entity); + virtual void changeEntityInternal(EntityItem* entity); virtual void clearEntitiesInternal(); - QSet _movingEntities; - QSet _movableButStoppedEntities; - QSet _hasSimulationOwnerEntities; + SetOfEntities _hasSimulationOwnerEntities; }; #endif // hifi_SimpleEntitySimulation_h diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index ebfa9b54f0..c4e2178b6b 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -484,7 +484,7 @@ FBXGeometry OBJReader::readOBJ(QIODevice* device, const QVariantHash& mapping, Q meshPart._material->setShininess(material->shininess); meshPart._material->setOpacity(material->opacity); } - qCDebug(modelformat) << "OBJ Reader part:" << meshPartCount << "name:" << leadFace.groupName << "material:" << groupMaterialName << "diffuse:" << meshPart._material->getDiffuse() << "faces:" << faceGroup.count() << "triangle indices will start with:" << mesh.vertices.count(); + // qCDebug(modelformat) << "OBJ Reader part:" << meshPartCount << "name:" << leadFace.groupName << "material:" << groupMaterialName << "diffuse:" << meshPart._material->getDiffuse() << "faces:" << faceGroup.count() << "triangle indices will start with:" << mesh.vertices.count(); foreach(OBJFace face, faceGroup) { glm::vec3 v0 = vertices[face.vertexIndices[0]]; glm::vec3 v1 = vertices[face.vertexIndices[1]]; @@ -526,7 +526,7 @@ FBXGeometry OBJReader::readOBJ(QIODevice* device, const QVariantHash& mapping, Q mesh.meshExtents.addPoint(vertex); geometry.meshExtents.addPoint(vertex); } - fbxDebugDump(geometry); + // fbxDebugDump(geometry); } catch(const std::exception& e) { qCDebug(modelformat) << "OBJ reader fail: " << e.what(); } diff --git a/libraries/physics/src/ContactInfo.cpp b/libraries/physics/src/ContactInfo.cpp index 71887b2dc9..c2ea6e8671 100644 --- a/libraries/physics/src/ContactInfo.cpp +++ b/libraries/physics/src/ContactInfo.cpp @@ -9,16 +9,14 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "BulletUtil.h" #include "ContactInfo.h" -void ContactInfo::update(uint32_t currentStep, btManifoldPoint& p, const glm::vec3& worldOffset) { +void ContactInfo::update(uint32_t currentStep, const btManifoldPoint& p) { _lastStep = currentStep; ++_numSteps; - contactPoint = bulletToGLM(p.m_positionWorldOnB) + worldOffset; - penetration = bulletToGLM(p.m_distance1 * p.m_normalWorldOnB); - // TODO: also report normal - //_normal = bulletToGLM(p.m_normalWorldOnB); + positionWorldOnB = p.m_positionWorldOnB; + normalWorldOnB = p.m_normalWorldOnB; + distance = p.m_distance1; } ContactEventType ContactInfo::computeType(uint32_t thisStep) { diff --git a/libraries/physics/src/ContactInfo.h b/libraries/physics/src/ContactInfo.h index 2c3c3a1e6f..11c908a414 100644 --- a/libraries/physics/src/ContactInfo.h +++ b/libraries/physics/src/ContactInfo.h @@ -17,17 +17,18 @@ #include "RegisteredMetaTypes.h" -enum ContactEventType { - CONTACT_EVENT_TYPE_START, - CONTACT_EVENT_TYPE_CONTINUE, - CONTACT_EVENT_TYPE_END -}; -class ContactInfo : public Collision -{ +class ContactInfo { public: - void update(uint32_t currentStep, btManifoldPoint& p, const glm::vec3& worldOffset); + void update(uint32_t currentStep, const btManifoldPoint& p); ContactEventType computeType(uint32_t thisStep); + + const btVector3& getPositionWorldOnB() const { return positionWorldOnB; } + btVector3 getPositionWorldOnA() const { return positionWorldOnB + normalWorldOnB * distance; } + + btVector3 positionWorldOnB; + btVector3 normalWorldOnB; + btScalar distance; private: uint32_t _lastStep = 0; uint32_t _numSteps = 0; diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index a7245c32d4..d3a1d67d4a 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -21,60 +21,87 @@ static const float ACCELERATION_EQUIVALENT_EPSILON_RATIO = 0.1f; static const quint8 STEPS_TO_DECIDE_BALLISTIC = 4; -QSet* _outgoingEntityList; -// static -void EntityMotionState::setOutgoingEntityList(QSet* list) { - assert(list); - _outgoingEntityList = list; -} - -// static -void EntityMotionState::enqueueOutgoingEntity(EntityItem* entity) { - assert(_outgoingEntityList); - _outgoingEntityList->insert(entity); -} - -EntityMotionState::EntityMotionState(EntityItem* entity) : +EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItem* entity) : + ObjectMotionState(shape), _entity(entity), + _sentMoving(false), + _numNonMovingUpdates(0), + _sentStep(0), + _sentPosition(0.0f), + _sentRotation(), + _sentVelocity(0.0f), + _sentAngularVelocity(0.0f), + _sentGravity(0.0f), + _sentAcceleration(0.0f), _accelerationNearlyGravityCount(0), _shouldClaimSimulationOwnership(false), _movingStepsWithoutSimulationOwner(0) { _type = MOTION_STATE_TYPE_ENTITY; - assert(entity != NULL); + assert(entity != nullptr); } EntityMotionState::~EntityMotionState() { - assert(_entity); - _entity->setPhysicsInfo(NULL); - _entity = NULL; + // be sure to clear _entity before calling the destructor + assert(!_entity); +} + +void EntityMotionState::updateServerPhysicsVariables(uint32_t flags) { + if (flags & EntityItem::DIRTY_POSITION) { + _sentPosition = _entity->getPosition(); + } + if (flags & EntityItem::DIRTY_ROTATION) { + _sentRotation = _entity->getRotation(); + } + if (flags & EntityItem::DIRTY_LINEAR_VELOCITY) { + _sentVelocity = _entity->getVelocity(); + } + if (flags & EntityItem::DIRTY_ANGULAR_VELOCITY) { + _sentAngularVelocity = _entity->getAngularVelocity(); + } +} + +// virtual +void EntityMotionState::handleEasyChanges(uint32_t flags) { + updateServerPhysicsVariables(flags); + ObjectMotionState::handleEasyChanges(flags); +} + + +// virtual +void EntityMotionState::handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine) { + updateServerPhysicsVariables(flags); + ObjectMotionState::handleHardAndEasyChanges(flags, engine); +} + +void EntityMotionState::clearEntity() { + _entity = nullptr; + // set the type to INVALID so that external logic that pivots on the type won't try to access _entity + _type = MOTION_STATE_TYPE_INVALID; } MotionType EntityMotionState::computeObjectMotionType() const { + if (!_entity) { + return MOTION_TYPE_STATIC; + } if (_entity->getCollisionsWillMove()) { return MOTION_TYPE_DYNAMIC; } return _entity->isMoving() ? MOTION_TYPE_KINEMATIC : MOTION_TYPE_STATIC; } -void EntityMotionState::updateKinematicState(uint32_t substep) { - setKinematic(_entity->isMoving(), substep); -} - -void EntityMotionState::stepKinematicSimulation(quint64 now) { - assert(_isKinematic); - // NOTE: this is non-physical kinematic motion which steps to real run-time (now) - // which is different from physical kinematic motion (inside getWorldTransform()) - // which steps in physics simulation time. - _entity->simulate(now); - // TODO: we can't use measureBodyAcceleration() here because the entity - // has no RigidBody and the timestep is a little bit out of sync with the physics simulation anyway. - // Hence we must manually measure kinematic velocity and acceleration. -} - bool EntityMotionState::isMoving() const { - return _entity->isMoving(); + return _entity && _entity->isMoving(); +} + +bool EntityMotionState::isMovingVsServer() const { + auto alignmentDot = glm::abs(glm::dot(_sentRotation, _entity->getRotation())); + if (glm::distance(_sentPosition, _entity->getPosition()) > IGNORE_POSITION_DELTA || + alignmentDot < IGNORE_ALIGNMENT_DOT) { + return true; + } + return false; } // This callback is invoked by the physics simulation in two cases: @@ -83,52 +110,54 @@ bool EntityMotionState::isMoving() const { // (2) at the beginning of each simulation step for KINEMATIC RigidBody's -- // it is an opportunity for outside code to update the object's simulation position void EntityMotionState::getWorldTransform(btTransform& worldTrans) const { - if (_isKinematic) { + if (!_entity) { + return; + } + if (_motionType == MOTION_TYPE_KINEMATIC) { // This is physical kinematic motion which steps strictly by the subframe count // of the physics simulation. - uint32_t substep = PhysicsEngine::getNumSubsteps(); - float dt = (substep - _lastKinematicSubstep) * PHYSICS_ENGINE_FIXED_SUBSTEP; + uint32_t thisStep = ObjectMotionState::getWorldSimulationStep(); + float dt = (thisStep - _lastKinematicStep) * PHYSICS_ENGINE_FIXED_SUBSTEP; _entity->simulateKinematicMotion(dt); _entity->setLastSimulated(usecTimestampNow()); - // bypass const-ness so we can remember the substep - const_cast(this)->_lastKinematicSubstep = substep; + // bypass const-ness so we can remember the step + const_cast(this)->_lastKinematicStep = thisStep; } - worldTrans.setOrigin(glmToBullet(_entity->getPosition() - ObjectMotionState::getWorldOffset())); + worldTrans.setOrigin(glmToBullet(getObjectPosition())); worldTrans.setRotation(glmToBullet(_entity->getRotation())); } // This callback is invoked by the physics simulation at the end of each simulation step... // iff the corresponding RigidBody is DYNAMIC and has moved. void EntityMotionState::setWorldTransform(const btTransform& worldTrans) { + if (!_entity) { + return; + } measureBodyAcceleration(); _entity->setPosition(bulletToGLM(worldTrans.getOrigin()) + ObjectMotionState::getWorldOffset()); _entity->setRotation(bulletToGLM(worldTrans.getRotation())); - glm::vec3 v; - getVelocity(v); - _entity->setVelocity(v); - - glm::vec3 av; - getAngularVelocity(av); - _entity->setAngularVelocity(av); + _entity->setVelocity(getBodyLinearVelocity()); + _entity->setAngularVelocity(getBodyAngularVelocity()); _entity->setLastSimulated(usecTimestampNow()); - if (_entity->getSimulatorID().isNull() && isMoving()) { - // object is moving and has no owner. attempt to claim simulation ownership. + // if (_entity->getSimulatorID().isNull() && isMoving()) { + if (_entity->getSimulatorID().isNull() && isMovingVsServer()) { + // if object is moving and has no owner, attempt to claim simulation ownership. _movingStepsWithoutSimulationOwner++; } else { _movingStepsWithoutSimulationOwner = 0; } - if (_movingStepsWithoutSimulationOwner > 4) { // XXX maybe meters from our characterController ? + int 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); } - _outgoingPacketFlags = DIRTY_PHYSICS_FLAGS; - EntityMotionState::enqueueOutgoingEntity(_entity); - #ifdef WANT_DEBUG quint64 now = usecTimestampNow(); qCDebug(physics) << "EntityMotionState::setWorldTransform()... changed entity:" << _entity->getEntityItemID(); @@ -138,76 +167,125 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) { #endif } -void EntityMotionState::updateBodyEasy(uint32_t flags, uint32_t step) { - if (flags & (EntityItem::DIRTY_POSITION | EntityItem::DIRTY_VELOCITY | EntityItem::DIRTY_PHYSICS_NO_WAKE)) { - if (flags & EntityItem::DIRTY_POSITION) { - _sentPosition = getObjectPosition() - ObjectMotionState::getWorldOffset(); - btTransform worldTrans; - worldTrans.setOrigin(glmToBullet(_sentPosition)); - - _sentRotation = getObjectRotation(); - worldTrans.setRotation(glmToBullet(_sentRotation)); - - _body->setWorldTransform(worldTrans); - } - if (flags & EntityItem::DIRTY_VELOCITY) { - updateBodyVelocities(); - } - _sentStep = step; - - if (flags & (EntityItem::DIRTY_POSITION | EntityItem::DIRTY_VELOCITY)) { - _body->activate(); - } - } - - if (flags & EntityItem::DIRTY_MATERIAL) { - updateBodyMaterialProperties(); - } - - if (flags & EntityItem::DIRTY_MASS) { - ShapeInfo shapeInfo; - _entity->computeShapeInfo(shapeInfo); - float mass = computeObjectMass(shapeInfo); - btVector3 inertia(0.0f, 0.0f, 0.0f); - _body->getCollisionShape()->calculateLocalInertia(mass, inertia); - _body->setMassProps(mass, inertia); - _body->updateInertiaTensor(); - } -} - -void EntityMotionState::updateBodyMaterialProperties() { - _body->setRestitution(getObjectRestitution()); - _body->setFriction(getObjectFriction()); - _body->setDamping(fabsf(btMin(getObjectLinearDamping(), 1.0f)), fabsf(btMin(getObjectAngularDamping(), 1.0f))); -} - -void EntityMotionState::updateBodyVelocities() { - if (_body) { - _sentVelocity = getObjectLinearVelocity(); - setBodyVelocity(_sentVelocity); - - _sentAngularVelocity = getObjectAngularVelocity(); - setBodyAngularVelocity(_sentAngularVelocity); - - _sentGravity = getObjectGravity(); - setBodyGravity(_sentGravity); - - _body->setActivationState(ACTIVE_TAG); - } -} - void EntityMotionState::computeObjectShapeInfo(ShapeInfo& shapeInfo) { - if (_entity->isReadyToComputeShape()) { + if (_entity) { _entity->computeShapeInfo(shapeInfo); } } -float EntityMotionState::computeObjectMass(const ShapeInfo& shapeInfo) const { - return _entity->computeMass(); +// RELIABLE_SEND_HACK: until we have truly reliable resends of non-moving updates +// 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->isActive() && _numNonMovingUpdates > MAX_NUM_NON_MOVING_UPDATES; +} + +bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { + assert(_body); + // if we've never checked before, our _sentStep will be 0, and we need to initialize our state + if (_sentStep == 0) { + btTransform xform = _body->getWorldTransform(); + _sentPosition = bulletToGLM(xform.getOrigin()); + _sentRotation = bulletToGLM(xform.getRotation()); + _sentVelocity = bulletToGLM(_body->getLinearVelocity()); + _sentAngularVelocity = bulletToGLM(_body->getAngularVelocity()); + _sentStep = simulationStep; + return false; + } + + #ifdef WANT_DEBUG + glm::vec3 wasPosition = _sentPosition; + glm::quat wasRotation = _sentRotation; + glm::vec3 wasAngularVelocity = _sentAngularVelocity; + #endif + + int numSteps = simulationStep - _sentStep; + float dt = (float)(numSteps) * PHYSICS_ENGINE_FIXED_SUBSTEP; + _sentStep = simulationStep; + bool isActive = _body->isActive(); + + if (!isActive) { + if (_sentMoving) { + // this object just went inactive so send an 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 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. + + // compute position error + if (glm::length2(_sentVelocity) > 0.0f) { + _sentVelocity += _sentAcceleration * dt; + _sentVelocity *= powf(1.0f - _body->getLinearDamping(), dt); + _sentPosition += dt * _sentVelocity; + } + + btTransform worldTrans = _body->getWorldTransform(); + glm::vec3 position = bulletToGLM(worldTrans.getOrigin()); + + float dx2 = glm::distance2(position, _sentPosition); + + const float MAX_POSITION_ERROR_SQUARED = 0.001f; // 0.001 m^2 ~~> 0.03 m + if (dx2 > MAX_POSITION_ERROR_SQUARED) { + + #ifdef WANT_DEBUG + qCDebug(physics) << ".... (dx2 > MAX_POSITION_ERROR_SQUARED) ...."; + qCDebug(physics) << "wasPosition:" << wasPosition; + qCDebug(physics) << "bullet position:" << position; + qCDebug(physics) << "_sentPosition:" << _sentPosition; + qCDebug(physics) << "dx2:" << dx2; + #endif + + return true; + } + + if (glm::length2(_sentAngularVelocity) > 0.0f) { + // compute rotation error + float attenuation = powf(1.0f - _body->getAngularDamping(), dt); + _sentAngularVelocity *= attenuation; + + // Bullet caps the effective rotation velocity inside its rotation integration step, therefore + // we must integrate with the same algorithm and timestep in order achieve similar results. + for (int i = 0; i < numSteps; ++i) { + _sentRotation = glm::normalize(computeBulletRotationStep(_sentAngularVelocity, PHYSICS_ENGINE_FIXED_SUBSTEP) * _sentRotation); + } + } + const float MIN_ROTATION_DOT = 0.99f; // 0.99 dot threshold coresponds to about 16 degrees of slop + glm::quat actualRotation = bulletToGLM(worldTrans.getRotation()); + + #ifdef WANT_DEBUG + if ((fabsf(glm::dot(actualRotation, _sentRotation)) < MIN_ROTATION_DOT)) { + qCDebug(physics) << ".... ((fabsf(glm::dot(actualRotation, _sentRotation)) < MIN_ROTATION_DOT)) ...."; + + qCDebug(physics) << "wasAngularVelocity:" << wasAngularVelocity; + qCDebug(physics) << "_sentAngularVelocity:" << _sentAngularVelocity; + + qCDebug(physics) << "length wasAngularVelocity:" << glm::length(wasAngularVelocity); + qCDebug(physics) << "length _sentAngularVelocity:" << glm::length(_sentAngularVelocity); + + qCDebug(physics) << "wasRotation:" << wasRotation; + qCDebug(physics) << "bullet actualRotation:" << actualRotation; + qCDebug(physics) << "_sentRotation:" << _sentRotation; + } + #endif + + return (fabsf(glm::dot(actualRotation, _sentRotation)) < MIN_ROTATION_DOT); } bool EntityMotionState::shouldSendUpdate(uint32_t simulationFrame) { - if (!ObjectMotionState::shouldSendUpdate(simulationFrame)) { + if (!_entity || !remoteSimulationOutOfSync(simulationFrame)) { return false; } @@ -228,144 +306,138 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationFrame) { } void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step) { - if (!_entity->isKnownID()) { + if (!_entity || !_entity->isKnownID()) { return; // never update entities that are unknown } - if (_outgoingPacketFlags) { - EntityItemProperties properties = _entity->getProperties(); - float gravityLength = glm::length(_entity->getGravity()); - float accVsGravity = glm::abs(glm::length(_measuredAcceleration) - gravityLength); - if (accVsGravity < ACCELERATION_EQUIVALENT_EPSILON_RATIO * gravityLength) { - // acceleration measured during the most recent simulation step was close to gravity. - if (getAccelerationNearlyGravityCount() < STEPS_TO_DECIDE_BALLISTIC) { - // only increment this if we haven't reached the threshold yet. this is to avoid - // overflowing the counter. - incrementAccelerationNearlyGravityCount(); - } - } else { - // acceleration wasn't similar to this entities gravity, so reset the went-ballistic counter - resetAccelerationNearlyGravityCount(); + EntityItemProperties properties = _entity->getProperties(); + + float gravityLength = glm::length(_entity->getGravity()); + float accVsGravity = glm::abs(glm::length(_measuredAcceleration) - gravityLength); + if (accVsGravity < ACCELERATION_EQUIVALENT_EPSILON_RATIO * gravityLength) { + // acceleration measured during the most recent simulation step was close to gravity. + if (getAccelerationNearlyGravityCount() < STEPS_TO_DECIDE_BALLISTIC) { + // only increment this if we haven't reached the threshold yet. this is to avoid + // overflowing the counter. + incrementAccelerationNearlyGravityCount(); } - - // if this entity has been accelerated at close to gravity for a certain number of simulation-steps, let - // the entity server's estimates include gravity. - if (getAccelerationNearlyGravityCount() >= STEPS_TO_DECIDE_BALLISTIC) { - _entity->setAcceleration(_entity->getGravity()); - } else { - _entity->setAcceleration(glm::vec3(0.0f)); - } - - if (_outgoingPacketFlags & EntityItem::DIRTY_POSITION) { - btTransform worldTrans = _body->getWorldTransform(); - _sentPosition = bulletToGLM(worldTrans.getOrigin()); - properties.setPosition(_sentPosition + ObjectMotionState::getWorldOffset()); - - _sentRotation = bulletToGLM(worldTrans.getRotation()); - properties.setRotation(_sentRotation); - } - - bool zeroSpeed = true; - bool zeroSpin = true; - - if (_outgoingPacketFlags & EntityItem::DIRTY_VELOCITY) { - if (_body->isActive()) { - _sentVelocity = bulletToGLM(_body->getLinearVelocity()); - _sentAngularVelocity = bulletToGLM(_body->getAngularVelocity()); - - // if the speeds are very small we zero them out - const float MINIMUM_EXTRAPOLATION_SPEED_SQUARED = 1.0e-4f; // 1cm/sec - zeroSpeed = (glm::length2(_sentVelocity) < MINIMUM_EXTRAPOLATION_SPEED_SQUARED); - if (zeroSpeed) { - _sentVelocity = glm::vec3(0.0f); - } - const float MINIMUM_EXTRAPOLATION_SPIN_SQUARED = 0.004f; // ~0.01 rotation/sec - zeroSpin = glm::length2(_sentAngularVelocity) < MINIMUM_EXTRAPOLATION_SPIN_SQUARED; - if (zeroSpin) { - _sentAngularVelocity = glm::vec3(0.0f); - } - - _sentMoving = ! (zeroSpeed && zeroSpin); - } else { - _sentVelocity = _sentAngularVelocity = glm::vec3(0.0f); - _sentMoving = false; - } - properties.setVelocity(_sentVelocity); - _sentGravity = _entity->getGravity(); - properties.setGravity(_entity->getGravity()); - _sentAcceleration = _entity->getAcceleration(); - properties.setAcceleration(_sentAcceleration); - properties.setAngularVelocity(_sentAngularVelocity); - } - - auto nodeList = DependencyManager::get(); - QUuid myNodeID = nodeList->getSessionUUID(); - QUuid simulatorID = _entity->getSimulatorID(); - - if (getShouldClaimSimulationOwnership()) { - properties.setSimulatorID(myNodeID); - setShouldClaimSimulationOwnership(false); - } else if (simulatorID == myNodeID && zeroSpeed && zeroSpin) { - // we are the simulator and the entity has stopped. give up "simulator" status - _entity->setSimulatorID(QUuid()); - properties.setSimulatorID(QUuid()); - } else if (simulatorID == myNodeID && !_body->isActive()) { - // it's not active. don't keep simulation ownership. - _entity->setSimulatorID(QUuid()); - properties.setSimulatorID(QUuid()); - } - - // 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 - // (i.e. NOT when we just simulate the positions forward, nor when we resend non-moving data) - // NOTE: Andrew & Brad to discuss. Let's make sure we're using lastEdited, lastSimulated, and lastUpdated correctly - 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()); - } - - if (EntityItem::getSendPhysicsUpdates()) { - EntityItemID id(_entity->getID()); - EntityEditPacketSender* entityPacketSender = static_cast(packetSender); - #ifdef WANT_DEBUG - qCDebug(physics) << "EntityMotionState::sendUpdate()... calling queueEditEntityMessage()..."; - #endif - - entityPacketSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, id, properties); - } else { - #ifdef WANT_DEBUG - qCDebug(physics) << "EntityMotionState::sendUpdate()... NOT sending update as requested."; - #endif - } - - // The outgoing flags only itemized WHAT to send, not WHETHER to send, hence we always set them - // to the full set. These flags may be momentarily cleared by incoming external changes. - _outgoingPacketFlags = DIRTY_PHYSICS_FLAGS; - _sentStep = step; + } else { + // acceleration wasn't similar to this entities gravity, so reset the went-ballistic counter + resetAccelerationNearlyGravityCount(); } + + // if this entity has been accelerated at close to gravity for a certain number of simulation-steps, let + // the entity server's estimates include gravity. + if (getAccelerationNearlyGravityCount() >= STEPS_TO_DECIDE_BALLISTIC) { + _entity->setAcceleration(_entity->getGravity()); + } else { + _entity->setAcceleration(glm::vec3(0.0f)); + } + + btTransform worldTrans = _body->getWorldTransform(); + _sentPosition = bulletToGLM(worldTrans.getOrigin()); + properties.setPosition(_sentPosition + ObjectMotionState::getWorldOffset()); + + _sentRotation = bulletToGLM(worldTrans.getRotation()); + properties.setRotation(_sentRotation); + + bool zeroSpeed = true; + bool zeroSpin = true; + + if (_body->isActive()) { + _sentVelocity = bulletToGLM(_body->getLinearVelocity()); + _sentAngularVelocity = bulletToGLM(_body->getAngularVelocity()); + + // if the speeds are very small we zero them out + const float MINIMUM_EXTRAPOLATION_SPEED_SQUARED = 1.0e-4f; // 1cm/sec + zeroSpeed = (glm::length2(_sentVelocity) < MINIMUM_EXTRAPOLATION_SPEED_SQUARED); + if (zeroSpeed) { + _sentVelocity = glm::vec3(0.0f); + } + const float MINIMUM_EXTRAPOLATION_SPIN_SQUARED = 0.004f; // ~0.01 rotation/sec + zeroSpin = glm::length2(_sentAngularVelocity) < MINIMUM_EXTRAPOLATION_SPIN_SQUARED; + if (zeroSpin) { + _sentAngularVelocity = glm::vec3(0.0f); + } + + _sentMoving = ! (zeroSpeed && zeroSpin); + } else { + _sentVelocity = _sentAngularVelocity = glm::vec3(0.0f); + _sentMoving = false; + } + properties.setVelocity(_sentVelocity); + _sentGravity = _entity->getGravity(); + properties.setGravity(_entity->getGravity()); + _sentAcceleration = _entity->getAcceleration(); + properties.setAcceleration(_sentAcceleration); + properties.setAngularVelocity(_sentAngularVelocity); + + auto nodeList = DependencyManager::get(); + QUuid myNodeID = nodeList->getSessionUUID(); + QUuid simulatorID = _entity->getSimulatorID(); + + if (getShouldClaimSimulationOwnership()) { + properties.setSimulatorID(myNodeID); + setShouldClaimSimulationOwnership(false); + } else if (simulatorID == myNodeID && zeroSpeed && zeroSpin) { + // we are the simulator and the entity has stopped. give up "simulator" status + _entity->setSimulatorID(QUuid()); + properties.setSimulatorID(QUuid()); + } else if (simulatorID == myNodeID && !_body->isActive()) { + // it's not active. don't keep simulation ownership. + _entity->setSimulatorID(QUuid()); + properties.setSimulatorID(QUuid()); + } + + // 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 + // (i.e. NOT when we just simulate the positions forward, nor when we resend non-moving data) + // NOTE: Andrew & Brad to discuss. Let's make sure we're using lastEdited, lastSimulated, and lastUpdated correctly + 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()); + } + + if (EntityItem::getSendPhysicsUpdates()) { + EntityItemID id(_entity->getID()); + EntityEditPacketSender* entityPacketSender = static_cast(packetSender); + #ifdef WANT_DEBUG + qCDebug(physics) << "EntityMotionState::sendUpdate()... calling queueEditEntityMessage()..."; + #endif + + entityPacketSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, id, properties); + _entity->setLastBroadcast(usecTimestampNow()); + } else { + #ifdef WANT_DEBUG + qCDebug(physics) << "EntityMotionState::sendUpdate()... NOT sending update as requested."; + #endif + } + + _sentStep = step; } -uint32_t EntityMotionState::getIncomingDirtyFlags() const { - uint32_t dirtyFlags = _entity->getDirtyFlags(); - - if (_body) { +uint32_t EntityMotionState::getAndClearIncomingDirtyFlags() const { + uint32_t dirtyFlags = 0; + if (_body && _entity) { + dirtyFlags = _entity->getDirtyFlags(); + _entity->clearDirtyFlags(); // we add DIRTY_MOTION_TYPE if the body's motion type disagrees with entity velocity settings int bodyFlags = _body->getCollisionFlags(); bool isMoving = _entity->isMoving(); @@ -376,3 +448,60 @@ uint32_t EntityMotionState::getIncomingDirtyFlags() const { } return dirtyFlags; } + + +// virtual +QUuid EntityMotionState::getSimulatorID() const { + if (_entity) { + return _entity->getSimulatorID(); + } + return QUuid(); +} + + +// virtual +void EntityMotionState::bump() { + setShouldClaimSimulationOwnership(true); +} + +void EntityMotionState::resetMeasuredBodyAcceleration() { + _lastMeasureStep = ObjectMotionState::getWorldSimulationStep(); + if (_body) { + _lastVelocity = bulletToGLM(_body->getLinearVelocity()); + } else { + _lastVelocity = glm::vec3(0.0f); + } + _measuredAcceleration = glm::vec3(0.0f); +} + +void EntityMotionState::measureBodyAcceleration() { + // try to manually measure the true acceleration of the object + uint32_t thisStep = ObjectMotionState::getWorldSimulationStep(); + uint32_t numSubsteps = thisStep - _lastMeasureStep; + if (numSubsteps > 0) { + float dt = ((float)numSubsteps * PHYSICS_ENGINE_FIXED_SUBSTEP); + float invDt = 1.0f / dt; + _lastMeasureStep = thisStep; + + // Note: the integration equation for velocity uses damping: v1 = (v0 + a * dt) * (1 - D)^dt + // hence the equation for acceleration is: a = (v1 / (1 - D)^dt - v0) / dt + glm::vec3 velocity = bulletToGLM(_body->getLinearVelocity()); + _measuredAcceleration = (velocity / powf(1.0f - _body->getLinearDamping(), dt) - _lastVelocity) * invDt; + _lastVelocity = velocity; + } +} + +// virtual +void EntityMotionState::setMotionType(MotionType motionType) { + ObjectMotionState::setMotionType(motionType); + resetMeasuredBodyAcceleration(); +} + + +// virtual +QString EntityMotionState::getName() { + if (_entity) { + return _entity->getName(); + } + return ""; +} diff --git a/libraries/physics/src/EntityMotionState.h b/libraries/physics/src/EntityMotionState.h index 98d05a0420..1c45b40627 100644 --- a/libraries/physics/src/EntityMotionState.h +++ b/libraries/physics/src/EntityMotionState.h @@ -25,22 +25,18 @@ class EntityItem; class EntityMotionState : public ObjectMotionState { public: - // The OutgoingEntityQueue is a pointer to a QSet (owned by an EntitySimulation) of EntityItem*'s - // that have been changed by the physics simulation. - // All ObjectMotionState's with outgoing changes put themselves on the list. - static void setOutgoingEntityList(QSet* list); - static void enqueueOutgoingEntity(EntityItem* entity); - - EntityMotionState(EntityItem* item); + EntityMotionState(btCollisionShape* shape, EntityItem* item); virtual ~EntityMotionState(); + void updateServerPhysicsVariables(uint32_t flags); + virtual void handleEasyChanges(uint32_t flags); + virtual void handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine); + /// \return MOTION_TYPE_DYNAMIC or MOTION_TYPE_STATIC based on params set in EntityItem virtual MotionType computeObjectMotionType() const; - virtual void updateKinematicState(uint32_t substep); - virtual void stepKinematicSimulation(quint64 now); - virtual bool isMoving() const; + virtual bool isMovingVsServer() const; // this relays incoming position/rotation to the RigidBody virtual void getWorldTransform(btTransform& worldTrans) const; @@ -48,41 +44,70 @@ public: // this relays outgoing position/rotation to the EntityItem virtual void setWorldTransform(const btTransform& worldTrans); - // these relay incoming values to the RigidBody - virtual void updateBodyEasy(uint32_t flags, uint32_t step); - virtual void updateBodyMaterialProperties(); - virtual void updateBodyVelocities(); - virtual void computeObjectShapeInfo(ShapeInfo& shapeInfo); - virtual float computeObjectMass(const ShapeInfo& shapeInfo) const; - virtual bool shouldSendUpdate(uint32_t simulationFrame); - virtual void sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step); + bool doesNotNeedToSendUpdate() const; + bool remoteSimulationOutOfSync(uint32_t simulationStep); + bool shouldSendUpdate(uint32_t simulationFrame); + void sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step); - virtual uint32_t getIncomingDirtyFlags() const; - virtual void clearIncomingDirtyFlags(uint32_t flags) { _entity->clearDirtyFlags(flags); } + void setShouldClaimSimulationOwnership(bool value) { _shouldClaimSimulationOwnership = value; } + bool getShouldClaimSimulationOwnership() { return _shouldClaimSimulationOwnership; } + + virtual uint32_t getAndClearIncomingDirtyFlags() const; void incrementAccelerationNearlyGravityCount() { _accelerationNearlyGravityCount++; } void resetAccelerationNearlyGravityCount() { _accelerationNearlyGravityCount = 0; } quint8 getAccelerationNearlyGravityCount() { return _accelerationNearlyGravityCount; } - virtual EntityItem* getEntity() const { return _entity; } - virtual void setShouldClaimSimulationOwnership(bool value) { _shouldClaimSimulationOwnership = value; } - virtual bool getShouldClaimSimulationOwnership() { return _shouldClaimSimulationOwnership; } - virtual float getObjectRestitution() const { return _entity->getRestitution(); } virtual float getObjectFriction() const { return _entity->getFriction(); } virtual float getObjectLinearDamping() const { return _entity->getDamping(); } virtual float getObjectAngularDamping() const { return _entity->getAngularDamping(); } - virtual const glm::vec3& getObjectPosition() const { return _entity->getPosition(); } + virtual glm::vec3 getObjectPosition() const { return _entity->getPosition() - ObjectMotionState::getWorldOffset(); } virtual const glm::quat& getObjectRotation() const { return _entity->getRotation(); } virtual const glm::vec3& getObjectLinearVelocity() const { return _entity->getVelocity(); } virtual const glm::vec3& getObjectAngularVelocity() const { return _entity->getAngularVelocity(); } virtual const glm::vec3& getObjectGravity() const { return _entity->getGravity(); } + virtual const QUuid& getObjectID() const { return _entity->getID(); } + + virtual QUuid getSimulatorID() const; + virtual void bump(); + + EntityItem* getEntity() const { return _entity; } + + void resetMeasuredBodyAcceleration(); + void measureBodyAcceleration(); + + virtual QString getName(); + + friend class PhysicalEntitySimulation; + protected: + void clearEntity(); + + virtual void setMotionType(MotionType motionType); + EntityItem* _entity; + + bool _sentMoving; // true if last update was moving + int _numNonMovingUpdates; // RELIABLE_SEND_HACK for "not so reliable" resends of packets for non-moving objects + + // TODO XXX rename _sent* to _server* + uint32_t _sentStep; + glm::vec3 _sentPosition; // in simulation-frame (not world-frame) + glm::quat _sentRotation;; + glm::vec3 _sentVelocity; + glm::vec3 _sentAngularVelocity; // radians per second + glm::vec3 _sentGravity; + glm::vec3 _sentAcceleration; + + uint32_t _lastMeasureStep; + glm::vec3 _lastVelocity; + glm::vec3 _measuredAcceleration; + quint8 _accelerationNearlyGravityCount; bool _shouldClaimSimulationOwnership; quint32 _movingStepsWithoutSimulationOwner; diff --git a/libraries/physics/src/ObjectMotionState.cpp b/libraries/physics/src/ObjectMotionState.cpp index 350556b3a8..c1258ad6bc 100644 --- a/libraries/physics/src/ObjectMotionState.cpp +++ b/libraries/physics/src/ObjectMotionState.cpp @@ -31,57 +31,43 @@ const glm::vec3& ObjectMotionState::getWorldOffset() { } // static -uint32_t _worldSimulationStep = 0; +uint32_t worldSimulationStep = 0; void ObjectMotionState::setWorldSimulationStep(uint32_t step) { - assert(step > _worldSimulationStep); - _worldSimulationStep = step; + assert(step > worldSimulationStep); + worldSimulationStep = step; } -ObjectMotionState::ObjectMotionState() : +uint32_t ObjectMotionState::getWorldSimulationStep() { + return worldSimulationStep; +} + +// static +ShapeManager* shapeManager = nullptr; +void ObjectMotionState::setShapeManager(ShapeManager* manager) { + assert(manager); + shapeManager = manager; +} + +ShapeManager* ObjectMotionState::getShapeManager() { + assert(shapeManager); // you must properly set shapeManager before calling getShapeManager() + return shapeManager; +} + +ObjectMotionState::ObjectMotionState(btCollisionShape* shape) : _motionType(MOTION_TYPE_STATIC), - _body(NULL), - _sentMoving(false), - _numNonMovingUpdates(0), - _outgoingPacketFlags(DIRTY_PHYSICS_FLAGS), - _sentStep(0), - _sentPosition(0.0f), - _sentRotation(), - _sentVelocity(0.0f), - _sentAngularVelocity(0.0f), - _sentGravity(0.0f), - _sentAcceleration(0.0f), - _lastSimulationStep(0), - _lastVelocity(0.0f), - _measuredAcceleration(0.0f) { + _shape(shape), + _body(nullptr), + _mass(0.0f), + _lastKinematicStep(worldSimulationStep) +{ } ObjectMotionState::~ObjectMotionState() { - // NOTE: you MUST remove this MotionState from the world before you call the dtor. - assert(_body == NULL); + assert(!_body); + assert(!_shape); } -void ObjectMotionState::measureBodyAcceleration() { - // try to manually measure the true acceleration of the object - uint32_t numSubsteps = _worldSimulationStep - _lastSimulationStep; - if (numSubsteps > 0) { - float dt = ((float)numSubsteps * PHYSICS_ENGINE_FIXED_SUBSTEP); - float invDt = 1.0f / dt; - _lastSimulationStep = _worldSimulationStep; - - // Note: the integration equation for velocity uses damping: v1 = (v0 + a * dt) * (1 - D)^dt - // hence the equation for acceleration is: a = (v1 / (1 - D)^dt - v0) / dt - glm::vec3 velocity = bulletToGLM(_body->getLinearVelocity()); - _measuredAcceleration = (velocity / powf(1.0f - _body->getLinearDamping(), dt) - _lastVelocity) * invDt; - _lastVelocity = velocity; - } -} - -void ObjectMotionState::resetMeasuredBodyAcceleration() { - _lastSimulationStep = _worldSimulationStep; - _lastVelocity = bulletToGLM(_body->getLinearVelocity()); -} - -void ObjectMotionState::setBodyVelocity(const glm::vec3& velocity) const { +void ObjectMotionState::setBodyLinearVelocity(const glm::vec3& velocity) const { _body->setLinearVelocity(glmToBullet(velocity)); } @@ -93,130 +79,30 @@ void ObjectMotionState::setBodyGravity(const glm::vec3& gravity) const { _body->setGravity(glmToBullet(gravity)); } -void ObjectMotionState::getVelocity(glm::vec3& velocityOut) const { - velocityOut = bulletToGLM(_body->getLinearVelocity()); +glm::vec3 ObjectMotionState::getBodyLinearVelocity() const { + return bulletToGLM(_body->getLinearVelocity()); } -void ObjectMotionState::getAngularVelocity(glm::vec3& angularVelocityOut) const { - angularVelocityOut = bulletToGLM(_body->getAngularVelocity()); +glm::vec3 ObjectMotionState::getBodyAngularVelocity() const { + return bulletToGLM(_body->getAngularVelocity()); } -// RELIABLE_SEND_HACK: until we have truly reliable resends of non-moving updates -// we alwasy resend packets for objects that have stopped moving up to some max limit. -const int MAX_NUM_NON_MOVING_UPDATES = 5; - -bool ObjectMotionState::doesNotNeedToSendUpdate() const { - return !_body->isActive() && _numNonMovingUpdates > MAX_NUM_NON_MOVING_UPDATES; +void ObjectMotionState::releaseShape() { + if (_shape) { + shapeManager->releaseShape(_shape); + _shape = nullptr; + } } -bool ObjectMotionState::shouldSendUpdate(uint32_t simulationStep) { - assert(_body); - // if we've never checked before, our _sentStep will be 0, and we need to initialize our state - if (_sentStep == 0) { - btTransform xform = _body->getWorldTransform(); - _sentPosition = bulletToGLM(xform.getOrigin()); - _sentRotation = bulletToGLM(xform.getRotation()); - _sentVelocity = bulletToGLM(_body->getLinearVelocity()); - _sentAngularVelocity = bulletToGLM(_body->getAngularVelocity()); - _sentStep = simulationStep; - return false; - } - - #ifdef WANT_DEBUG - glm::vec3 wasPosition = _sentPosition; - glm::quat wasRotation = _sentRotation; - glm::vec3 wasAngularVelocity = _sentAngularVelocity; - #endif - - int numSteps = simulationStep - _sentStep; - float dt = (float)(numSteps) * PHYSICS_ENGINE_FIXED_SUBSTEP; - _sentStep = simulationStep; - bool isActive = _body->isActive(); - - if (!isActive) { - if (_sentMoving) { - // this object just went inactive so send an 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 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. - - // compute position error - if (glm::length2(_sentVelocity) > 0.0f) { - _sentVelocity += _sentAcceleration * dt; - _sentVelocity *= powf(1.0f - _body->getLinearDamping(), dt); - _sentPosition += dt * _sentVelocity; - } - - btTransform worldTrans = _body->getWorldTransform(); - glm::vec3 position = bulletToGLM(worldTrans.getOrigin()); - - float dx2 = glm::distance2(position, _sentPosition); - - const float MAX_POSITION_ERROR_SQUARED = 0.001f; // 0.001 m^2 ~~> 0.03 m - if (dx2 > MAX_POSITION_ERROR_SQUARED) { - - #ifdef WANT_DEBUG - qCDebug(physics) << ".... (dx2 > MAX_POSITION_ERROR_SQUARED) ...."; - qCDebug(physics) << "wasPosition:" << wasPosition; - qCDebug(physics) << "bullet position:" << position; - qCDebug(physics) << "_sentPosition:" << _sentPosition; - qCDebug(physics) << "dx2:" << dx2; - #endif - - return true; - } - - if (glm::length2(_sentAngularVelocity) > 0.0f) { - // compute rotation error - float attenuation = powf(1.0f - _body->getAngularDamping(), dt); - _sentAngularVelocity *= attenuation; - - // Bullet caps the effective rotation velocity inside its rotation integration step, therefore - // we must integrate with the same algorithm and timestep in order achieve similar results. - for (int i = 0; i < numSteps; ++i) { - _sentRotation = glm::normalize(computeBulletRotationStep(_sentAngularVelocity, PHYSICS_ENGINE_FIXED_SUBSTEP) * _sentRotation); - } - } - const float MIN_ROTATION_DOT = 0.99f; // 0.99 dot threshold coresponds to about 16 degrees of slop - glm::quat actualRotation = bulletToGLM(worldTrans.getRotation()); - - #ifdef WANT_DEBUG - if ((fabsf(glm::dot(actualRotation, _sentRotation)) < MIN_ROTATION_DOT)) { - qCDebug(physics) << ".... ((fabsf(glm::dot(actualRotation, _sentRotation)) < MIN_ROTATION_DOT)) ...."; - - qCDebug(physics) << "wasAngularVelocity:" << wasAngularVelocity; - qCDebug(physics) << "_sentAngularVelocity:" << _sentAngularVelocity; - - qCDebug(physics) << "length wasAngularVelocity:" << glm::length(wasAngularVelocity); - qCDebug(physics) << "length _sentAngularVelocity:" << glm::length(_sentAngularVelocity); - - qCDebug(physics) << "wasRotation:" << wasRotation; - qCDebug(physics) << "bullet actualRotation:" << actualRotation; - qCDebug(physics) << "_sentRotation:" << _sentRotation; - } - #endif - - return (fabsf(glm::dot(actualRotation, _sentRotation)) < MIN_ROTATION_DOT); +void ObjectMotionState::setMotionType(MotionType motionType) { + _motionType = motionType; } void ObjectMotionState::setRigidBody(btRigidBody* body) { // give the body a (void*) back-pointer to this ObjectMotionState if (_body != body) { if (_body) { - _body->setUserPointer(NULL); + _body->setUserPointer(nullptr); } _body = body; if (_body) { @@ -225,7 +111,89 @@ void ObjectMotionState::setRigidBody(btRigidBody* body) { } } -void ObjectMotionState::setKinematic(bool kinematic, uint32_t substep) { - _isKinematic = kinematic; - _lastKinematicSubstep = substep; +void ObjectMotionState::handleEasyChanges(uint32_t flags) { + if (flags & EntityItem::DIRTY_POSITION) { + btTransform worldTrans; + if (flags & EntityItem::DIRTY_ROTATION) { + worldTrans.setRotation(glmToBullet(getObjectRotation())); + } else { + worldTrans = _body->getWorldTransform(); + } + worldTrans.setOrigin(glmToBullet(getObjectPosition())); + _body->setWorldTransform(worldTrans); + } else if (flags & EntityItem::DIRTY_ROTATION) { + btTransform worldTrans = _body->getWorldTransform(); + worldTrans.setRotation(glmToBullet(getObjectRotation())); + _body->setWorldTransform(worldTrans); + } + + if (flags & EntityItem::DIRTY_LINEAR_VELOCITY) { + _body->setLinearVelocity(glmToBullet(getObjectLinearVelocity())); + _body->setGravity(glmToBullet(getObjectGravity())); + } + if (flags & EntityItem::DIRTY_ANGULAR_VELOCITY) { + _body->setAngularVelocity(glmToBullet(getObjectAngularVelocity())); + } + + if (flags & EntityItem::DIRTY_MATERIAL) { + updateBodyMaterialProperties(); + } + + if (flags & EntityItem::DIRTY_MASS) { + float mass = getMass(); + btVector3 inertia(0.0f, 0.0f, 0.0f); + _body->getCollisionShape()->calculateLocalInertia(mass, inertia); + _body->setMassProps(mass, inertia); + _body->updateInertiaTensor(); + } + + if (flags & EntityItem::DIRTY_PHYSICS_ACTIVATION) { + _body->activate(); + } +} + +void ObjectMotionState::handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine) { + if (flags & EntityItem::DIRTY_SHAPE) { + // make sure the new shape is valid + ShapeInfo shapeInfo; + computeObjectShapeInfo(shapeInfo); + btCollisionShape* newShape = getShapeManager()->getShape(shapeInfo); + if (!newShape) { + qCDebug(physics) << "Warning: failed to generate new shape!"; + // failed to generate new shape! --> keep old shape and remove shape-change flag + flags &= ~EntityItem::DIRTY_SHAPE; + // TODO: force this object out of PhysicsEngine rather than just use the old shape + if ((flags & HARD_DIRTY_PHYSICS_FLAGS) == 0) { + // no HARD flags remain, so do any EASY changes + if (flags & EASY_DIRTY_PHYSICS_FLAGS) { + handleEasyChanges(flags); + } + return; + } + } + getShapeManager()->releaseShape(_shape); + _shape = newShape; + _body->setCollisionShape(_shape); + if (flags & EASY_DIRTY_PHYSICS_FLAGS) { + handleEasyChanges(flags); + } + } else { + if (flags & EASY_DIRTY_PHYSICS_FLAGS) { + handleEasyChanges(flags); + } + } + engine->reinsertObject(this); +} + +void ObjectMotionState::updateBodyMaterialProperties() { + _body->setRestitution(getObjectRestitution()); + _body->setFriction(getObjectFriction()); + _body->setDamping(fabsf(btMin(getObjectLinearDamping(), 1.0f)), fabsf(btMin(getObjectAngularDamping(), 1.0f))); +} + +void ObjectMotionState::updateBodyVelocities() { + setBodyLinearVelocity(getObjectLinearVelocity()); + setBodyAngularVelocity(getObjectAngularVelocity()); + setBodyGravity(getObjectGravity()); + _body->setActivationState(ACTIVE_TAG); } diff --git a/libraries/physics/src/ObjectMotionState.h b/libraries/physics/src/ObjectMotionState.h index 7c00eedd09..1407be0d20 100644 --- a/libraries/physics/src/ObjectMotionState.h +++ b/libraries/physics/src/ObjectMotionState.h @@ -18,7 +18,7 @@ #include #include "ContactInfo.h" -#include "ShapeInfo.h" +#include "ShapeManager.h" enum MotionType { MOTION_TYPE_STATIC, // no motion @@ -27,7 +27,7 @@ enum MotionType { }; enum MotionStateType { - MOTION_STATE_TYPE_UNKNOWN, + MOTION_STATE_TYPE_INVALID, MOTION_STATE_TYPE_ENTITY, MOTION_STATE_TYPE_AVATAR }; @@ -35,86 +35,71 @@ enum MotionStateType { // The update flags trigger two varieties of updates: "hard" which require the body to be pulled // 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_POSITION | EntityItem::DIRTY_VELOCITY | - EntityItem::DIRTY_MASS | EntityItem::DIRTY_COLLISION_GROUP | EntityItem::DIRTY_MATERIAL); +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); // 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; // These are the outgoing flags that the PhysicsEngine can affect: -const uint32_t OUTGOING_DIRTY_PHYSICS_FLAGS = EntityItem::DIRTY_POSITION | EntityItem::DIRTY_VELOCITY; +const uint32_t OUTGOING_DIRTY_PHYSICS_FLAGS = EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES; class OctreeEditPacketSender; +class PhysicsEngine; extern const int MAX_NUM_NON_MOVING_UPDATES; class ObjectMotionState : public btMotionState { public: - // The WorldOffset is used to keep the positions of objects in the simulation near the origin, to - // reduce numerical error when computing vector differences. In other words: The EntityMotionState - // class translates between the simulation-frame and the world-frame as known by the render pipeline, - // various object trees, etc. The EntityMotionState class uses a static "worldOffset" to help in - // the translations. + // These poroperties of the PhysicsEngine are "global" within the context of all ObjectMotionStates + // (assuming just one PhysicsEngine). They are cached as statics for fast calculations in the + // ObjectMotionState context. static void setWorldOffset(const glm::vec3& offset); static const glm::vec3& getWorldOffset(); - // The WorldSimulationStep is a cached copy of number of SubSteps of the simulation, used for local time measurements. static void setWorldSimulationStep(uint32_t step); + static uint32_t getWorldSimulationStep(); - ObjectMotionState(); + static void setShapeManager(ShapeManager* manager); + static ShapeManager* getShapeManager(); + + ObjectMotionState(btCollisionShape* shape); ~ObjectMotionState(); - void measureBodyAcceleration(); - void resetMeasuredBodyAcceleration(); + virtual void handleEasyChanges(uint32_t flags); + virtual void handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine); - // An EASY update does not require the object to be removed and then reinserted into the PhysicsEngine - virtual void updateBodyEasy(uint32_t flags, uint32_t frame) = 0; - - virtual void updateBodyMaterialProperties() = 0; - virtual void updateBodyVelocities() = 0; + virtual void updateBodyMaterialProperties(); + virtual void updateBodyVelocities(); MotionStateType getType() const { return _type; } virtual MotionType getMotionType() const { return _motionType; } - virtual void computeObjectShapeInfo(ShapeInfo& info) = 0; - virtual float computeObjectMass(const ShapeInfo& shapeInfo) const = 0; + void setMass(float mass) { _mass = fabsf(mass); } + float getMass() { return _mass; } - void setBodyVelocity(const glm::vec3& velocity) const; + void setBodyLinearVelocity(const glm::vec3& velocity) const; void setBodyAngularVelocity(const glm::vec3& velocity) const; void setBodyGravity(const glm::vec3& gravity) const; - void getVelocity(glm::vec3& velocityOut) const; - void getAngularVelocity(glm::vec3& angularVelocityOut) const; + glm::vec3 getBodyLinearVelocity() const; + glm::vec3 getBodyAngularVelocity() const; - virtual uint32_t getIncomingDirtyFlags() const = 0; - virtual void clearIncomingDirtyFlags(uint32_t flags) = 0; - void clearOutgoingPacketFlags(uint32_t flags) { _outgoingPacketFlags &= ~flags; } - - bool doesNotNeedToSendUpdate() const; - virtual bool shouldSendUpdate(uint32_t simulationStep); - virtual void sendUpdate(OctreeEditPacketSender* packetSender, uint32_t frame) = 0; + virtual uint32_t getAndClearIncomingDirtyFlags() const = 0; virtual MotionType computeObjectMotionType() const = 0; + virtual void computeObjectShapeInfo(ShapeInfo& shapeInfo) = 0; - virtual void updateKinematicState(uint32_t substep) = 0; + btCollisionShape* getShape() const { return _shape; } btRigidBody* getRigidBody() const { return _body; } - bool isKinematic() const { return _isKinematic; } - - void setKinematic(bool kinematic, uint32_t substep); - virtual void stepKinematicSimulation(quint64 now) = 0; + void releaseShape(); virtual bool isMoving() const = 0; - friend class PhysicsEngine; - - // these are here so we can call into EntityMotionObject with a base-class pointer - virtual EntityItem* getEntity() const { return NULL; } - virtual void setShouldClaimSimulationOwnership(bool value) { } - virtual bool getShouldClaimSimulationOwnership() { return false; } - // These pure virtual methods must be implemented for each MotionState type // and make it possible to implement more complicated methods in this base class. @@ -123,39 +108,33 @@ public: virtual float getObjectLinearDamping() const = 0; virtual float getObjectAngularDamping() const = 0; - virtual const glm::vec3& getObjectPosition() const = 0; + virtual glm::vec3 getObjectPosition() const = 0; virtual const glm::quat& getObjectRotation() const = 0; virtual const glm::vec3& getObjectLinearVelocity() const = 0; virtual const glm::vec3& getObjectAngularVelocity() const = 0; virtual const glm::vec3& getObjectGravity() const = 0; + virtual const QUuid& getObjectID() const = 0; + + virtual QUuid getSimulatorID() const = 0; + virtual void bump() = 0; + + virtual QString getName() { return ""; } + + friend class PhysicsEngine; + protected: + virtual void setMotionType(MotionType motionType); void setRigidBody(btRigidBody* body); - MotionStateType _type = MOTION_STATE_TYPE_UNKNOWN; // type of MotionState - + MotionStateType _type = MOTION_STATE_TYPE_INVALID; // type of MotionState MotionType _motionType; // type of motion: KINEMATIC, DYNAMIC, or STATIC + btCollisionShape* _shape; btRigidBody* _body; + float _mass; - bool _isKinematic = false; - uint32_t _lastKinematicSubstep = 0; - - bool _sentMoving; // true if last update was moving - int _numNonMovingUpdates; // RELIABLE_SEND_HACK for "not so reliable" resends of packets for non-moving objects - - uint32_t _outgoingPacketFlags; - uint32_t _sentStep; - glm::vec3 _sentPosition; // in simulation-frame (not world-frame) - glm::quat _sentRotation;; - glm::vec3 _sentVelocity; - glm::vec3 _sentAngularVelocity; // radians per second - glm::vec3 _sentGravity; - glm::vec3 _sentAcceleration; - - uint32_t _lastSimulationStep; - glm::vec3 _lastVelocity; - glm::vec3 _measuredAcceleration; + uint32_t _lastKinematicStep; }; #endif // hifi_ObjectMotionState_h diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp new file mode 100644 index 0000000000..06d025244a --- /dev/null +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -0,0 +1,238 @@ +// +// PhysicalEntitySimulation.cpp +// libraries/physcis/src +// +// Created by Andrew Meadows 2015.04.27 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "EntityMotionState.h" +#include "PhysicalEntitySimulation.h" +#include "PhysicsHelpers.h" +#include "PhysicsLogging.h" +#include "ShapeInfoUtil.h" +#include "ShapeManager.h" + +PhysicalEntitySimulation::PhysicalEntitySimulation() { +} + +PhysicalEntitySimulation::~PhysicalEntitySimulation() { +} + +void PhysicalEntitySimulation::init( + EntityTree* tree, + PhysicsEngine* physicsEngine, + ShapeManager* shapeManager, + EntityEditPacketSender* packetSender) { + assert(tree); + setEntityTree(tree); + + assert(physicsEngine); + _physicsEngine = physicsEngine; + + assert(shapeManager); + _shapeManager = shapeManager; + + assert(packetSender); + _entityPacketSender = packetSender; +} + +// begin EntitySimulation overrides +void PhysicalEntitySimulation::updateEntitiesInternal(const quint64& now) { + // TODO: add back non-physical kinematic objects and step them forward here +} + +void PhysicalEntitySimulation::addEntityInternal(EntityItem* entity) { + assert(entity); + if (entity->shouldBePhysical()) { + EntityMotionState* motionState = static_cast(entity->getPhysicsInfo()); + if (!motionState) { + _pendingAdds.insert(entity); + } + } else if (entity->isMoving()) { + _simpleKinematicEntities.insert(entity); + } +} + +void PhysicalEntitySimulation::removeEntityInternal(EntityItem* entity) { + EntityMotionState* motionState = static_cast(entity->getPhysicsInfo()); + if (motionState) { + motionState->clearEntity(); + entity->setPhysicsInfo(nullptr); + + // NOTE: we must remove entity from _pendingAdds immediately because we've disconnected the backpointers between + // motionState and entity and they can't be used to look up each other. However we don't need to remove + // motionState from _pendingChanges at this time becuase it will be removed during getObjectsToDelete(). + _pendingAdds.remove(entity); + + _pendingRemoves.insert(motionState); + _outgoingChanges.remove(motionState); + } +} + +void PhysicalEntitySimulation::changeEntityInternal(EntityItem* entity) { + // queue incoming changes: from external sources (script, EntityServer, etc) to physics engine + assert(entity); + EntityMotionState* motionState = static_cast(entity->getPhysicsInfo()); + if (motionState) { + if (!entity->shouldBePhysical()) { + // the entity should be removed from the physical simulation + _pendingChanges.remove(motionState); + _physicalObjects.remove(motionState); + _pendingRemoves.insert(motionState); + _outgoingChanges.remove(motionState); + if (entity->isMoving()) { + _simpleKinematicEntities.insert(entity); + } + } else { + _pendingChanges.insert(motionState); + } + } else if (entity->shouldBePhysical()) { + // The intent is for this object to be in the PhysicsEngine, but it has no MotionState yet. + // Perhaps it's shape has changed and it can now be added? + _pendingAdds.insert(entity); + _simpleKinematicEntities.remove(entity); // just in case it's non-physical-kinematic + } else if (entity->isMoving()) { + _simpleKinematicEntities.insert(entity); + } else { + _simpleKinematicEntities.remove(entity); // just in case it's non-physical-kinematic + } +} + +void PhysicalEntitySimulation::clearEntitiesInternal() { + // TODO: we should probably wait to lock the _physicsEngine so we don't mess up data structures + // while it is in the middle of a simulation step. As it is, we're probably in shutdown mode + // anyway, so maybe the simulation was already properly shutdown? Cross our fingers... + + // first disconnect each MotionStates from its Entity + for (auto stateItr : _physicalObjects) { + EntityMotionState* motionState = static_cast(&(*stateItr)); + EntityItem* entity = motionState->getEntity(); + if (entity) { + entity->setPhysicsInfo(nullptr); + } + motionState->clearEntity(); + } + + // then delete the objects (aka MotionStates) + _physicsEngine->deleteObjects(_physicalObjects); + + // finally clear all lists (which now have only dangling pointers) + _physicalObjects.clear(); + _pendingRemoves.clear(); + _pendingAdds.clear(); + _pendingChanges.clear(); +} +// end EntitySimulation overrides + + +VectorOfMotionStates& PhysicalEntitySimulation::getObjectsToDelete() { + _tempVector.clear(); + for (auto stateItr : _pendingRemoves) { + EntityMotionState* motionState = &(*stateItr); + _pendingChanges.remove(motionState); + _physicalObjects.remove(motionState); + + EntityItem* entity = motionState->getEntity(); + if (entity) { + _pendingAdds.remove(entity); + entity->setPhysicsInfo(nullptr); + motionState->clearEntity(); + } + _tempVector.push_back(motionState); + } + _pendingRemoves.clear(); + return _tempVector; +} + +VectorOfMotionStates& PhysicalEntitySimulation::getObjectsToAdd() { + _tempVector.clear(); + SetOfEntities::iterator entityItr = _pendingAdds.begin(); + while (entityItr != _pendingAdds.end()) { + EntityItem* entity = *entityItr; + assert(!entity->getPhysicsInfo()); + if (!entity->shouldBePhysical()) { + // this entity should no longer be on the internal _pendingAdds + entityItr = _pendingAdds.erase(entityItr); + if (entity->isMoving()) { + _simpleKinematicEntities.insert(entity); + } + } else if (entity->isReadyToComputeShape()) { + ShapeInfo shapeInfo; + entity->computeShapeInfo(shapeInfo); + btCollisionShape* shape = _shapeManager->getShape(shapeInfo); + if (shape) { + EntityMotionState* motionState = new EntityMotionState(shape, entity); + entity->setPhysicsInfo(static_cast(motionState)); + motionState->setMass(entity->computeMass()); + _physicalObjects.insert(motionState); + _tempVector.push_back(motionState); + entityItr = _pendingAdds.erase(entityItr); + } else { + qDebug() << "Warning! Failed to generate new shape for entity." << entity->getName(); + ++entityItr; + } + } else { + ++entityItr; + } + } + return _tempVector; +} + +VectorOfMotionStates& PhysicalEntitySimulation::getObjectsToChange() { + _tempVector.clear(); + for (auto stateItr : _pendingChanges) { + EntityMotionState* motionState = &(*stateItr); + _tempVector.push_back(motionState); + } + _pendingChanges.clear(); + return _tempVector; +} + +void PhysicalEntitySimulation::handleOutgoingChanges(VectorOfMotionStates& motionStates) { + // walk the motionStates looking for those that correspond to entities + for (auto stateItr : motionStates) { + ObjectMotionState* state = &(*stateItr); + if (state && state->getType() == MOTION_STATE_TYPE_ENTITY) { + EntityMotionState* entityState = static_cast(state); + EntityItem* entity = entityState->getEntity(); + if (entity) { + _outgoingChanges.insert(entityState); + _entitiesToSort.insert(entityState->getEntity()); + } + } + } + + // send outgoing packets + uint32_t numSubsteps = _physicsEngine->getNumSubsteps(); + if (_lastStepSendPackets != numSubsteps) { + _lastStepSendPackets = numSubsteps; + + QSet::iterator stateItr = _outgoingChanges.begin(); + while (stateItr != _outgoingChanges.end()) { + EntityMotionState* state = *stateItr; + if (state->doesNotNeedToSendUpdate()) { + stateItr = _outgoingChanges.erase(stateItr); + } else if (state->shouldSendUpdate(numSubsteps)) { + state->sendUpdate(_entityPacketSender, numSubsteps); + ++stateItr; + } else { + ++stateItr; + } + } + } +} + +void PhysicalEntitySimulation::handleCollisionEvents(CollisionEvents& collisionEvents) { + for (auto collision : collisionEvents) { + // NOTE: The collision event is always aligned such that idA is never NULL. + // however idB may be NULL. + if (!collision.idB.isNull()) { + emit entityCollisionWithEntity(collision.idA, collision.idB, collision); + } + } +} + diff --git a/libraries/physics/src/PhysicalEntitySimulation.h b/libraries/physics/src/PhysicalEntitySimulation.h new file mode 100644 index 0000000000..4fd54c60fb --- /dev/null +++ b/libraries/physics/src/PhysicalEntitySimulation.h @@ -0,0 +1,73 @@ +// +// PhysicalEntitySimulation.h +// libraries/physcis/src +// +// Created by Andrew Meadows 2015.04.27 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_PhysicalEntitySimulation_h +#define hifi_PhysicalEntitySimulation_h + +#include + +#include +#include + +#include +#include + +#include "PhysicsEngine.h" +#include "PhysicsTypedefs.h" + +class EntityMotionState; +class ShapeManager; + +typedef QSet SetOfEntityMotionStates; + +class PhysicalEntitySimulation :public EntitySimulation { +public: + PhysicalEntitySimulation(); + ~PhysicalEntitySimulation(); + + void init(EntityTree* tree, PhysicsEngine* engine, ShapeManager* shapeManager, EntityEditPacketSender* packetSender); + +protected: // only called by EntitySimulation + // overrides for EntitySimulation + virtual void updateEntitiesInternal(const quint64& now); + virtual void addEntityInternal(EntityItem* entity); + virtual void removeEntityInternal(EntityItem* entity); + virtual void changeEntityInternal(EntityItem* entity); + virtual void clearEntitiesInternal(); + +public: + VectorOfMotionStates& getObjectsToDelete(); + VectorOfMotionStates& getObjectsToAdd(); + VectorOfMotionStates& getObjectsToChange(); + + void handleOutgoingChanges(VectorOfMotionStates& motionStates); + void handleCollisionEvents(CollisionEvents& collisionEvents); + +private: + // incoming changes + SetOfEntityMotionStates _pendingRemoves; // EntityMotionStates to be removed from PhysicsEngine (and deleted) + SetOfEntities _pendingAdds; // entities to be be added to PhysicsEngine (and a their EntityMotionState created) + SetOfEntityMotionStates _pendingChanges; // EntityMotionStates already in PhysicsEngine that need their physics changed + + // outgoing changes + SetOfEntityMotionStates _outgoingChanges; // EntityMotionStates for which we need to send updates to entity-server + + SetOfMotionStates _physicalObjects; // MotionStates of entities in PhysicsEngine + VectorOfMotionStates _tempVector; // temporary array reference, valid immediately after getObjectsToRemove() (and friends) + + ShapeManager* _shapeManager = nullptr; + PhysicsEngine* _physicsEngine = nullptr; + EntityEditPacketSender* _entityPacketSender = nullptr; + + uint32_t _lastStepSendPackets = 0; +}; + +#endif // hifi_PhysicalEntitySimulation_h diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index 6f60fea013..ea74a87286 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -9,10 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include - +#include "ObjectMotionState.h" #include "PhysicsEngine.h" -#include "ShapeInfoUtil.h" #include "PhysicsHelpers.h" #include "ThreadSafeDynamicsWorld.h" #include "PhysicsLogging.h" @@ -26,12 +24,12 @@ uint32_t PhysicsEngine::getNumSubsteps() { PhysicsEngine::PhysicsEngine(const glm::vec3& offset) : _originOffset(offset), - _characterController(NULL) { + _characterController(nullptr) { } PhysicsEngine::~PhysicsEngine() { if (_characterController) { - _characterController->setDynamicsWorld(NULL); + _characterController->setDynamicsWorld(nullptr); } // TODO: delete engine components... if we ever plan to create more than one instance delete _collisionConfig; @@ -42,217 +40,153 @@ PhysicsEngine::~PhysicsEngine() { delete _ghostPairCallback; } -// begin EntitySimulation overrides -void PhysicsEngine::updateEntitiesInternal(const quint64& now) { - // no need to send updates unless the physics simulation has actually stepped - if (_lastNumSubstepsAtUpdateInternal != _numSubsteps) { - _lastNumSubstepsAtUpdateInternal = _numSubsteps; - // NOTE: the grand order of operations is: - // (1) relay incoming changes - // (2) step simulation - // (3) synchronize outgoing motion states - // (4) send outgoing packets - - // this is step (4) - QSet::iterator stateItr = _outgoingPackets.begin(); - while (stateItr != _outgoingPackets.end()) { - ObjectMotionState* state = *stateItr; - if (state->doesNotNeedToSendUpdate()) { - stateItr = _outgoingPackets.erase(stateItr); - } else if (state->shouldSendUpdate(_numSubsteps)) { - state->sendUpdate(_entityPacketSender, _numSubsteps); - ++stateItr; +void PhysicsEngine::init() { + if (!_dynamicsWorld) { + _collisionConfig = new btDefaultCollisionConfiguration(); + _collisionDispatcher = new btCollisionDispatcher(_collisionConfig); + _broadphaseFilter = new btDbvtBroadphase(); + _constraintSolver = new btSequentialImpulseConstraintSolver; + _dynamicsWorld = new ThreadSafeDynamicsWorld(_collisionDispatcher, _broadphaseFilter, _constraintSolver, _collisionConfig); + + _ghostPairCallback = new btGhostPairCallback(); + _dynamicsWorld->getPairCache()->setInternalGhostPairCallback(_ghostPairCallback); + + // default gravity of the world is zero, so each object must specify its own gravity + // TODO: set up gravity zones + _dynamicsWorld->setGravity(btVector3(0.0f, 0.0f, 0.0f)); + } +} + +void PhysicsEngine::addObject(ObjectMotionState* motionState) { + assert(motionState); + + btVector3 inertia(0.0f, 0.0f, 0.0f); + float mass = 0.0f; + // NOTE: the body may or may not already exist, depending on whether this corresponds to a reinsertion, or a new insertion. + btRigidBody* body = motionState->getRigidBody(); + MotionType motionType = motionState->computeObjectMotionType(); + motionState->setMotionType(motionType); + switch(motionType) { + case MOTION_TYPE_KINEMATIC: { + if (!body) { + btCollisionShape* shape = motionState->getShape(); + assert(shape); + body = new btRigidBody(mass, motionState, shape, inertia); } else { - ++stateItr; + body->setMassProps(mass, inertia); } + body->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT); + body->updateInertiaTensor(); + motionState->setRigidBody(body); + motionState->updateBodyVelocities(); + const float KINEMATIC_LINEAR_VELOCITY_THRESHOLD = 0.01f; // 1 cm/sec + const float KINEMATIC_ANGULAR_VELOCITY_THRESHOLD = 0.01f; // ~1 deg/sec + body->setSleepingThresholds(KINEMATIC_LINEAR_VELOCITY_THRESHOLD, KINEMATIC_ANGULAR_VELOCITY_THRESHOLD); + break; + } + case MOTION_TYPE_DYNAMIC: { + mass = motionState->getMass(); + btCollisionShape* shape = motionState->getShape(); + assert(shape); + shape->calculateLocalInertia(mass, inertia); + if (!body) { + body = new btRigidBody(mass, motionState, shape, inertia); + } else { + body->setMassProps(mass, inertia); + } + body->updateInertiaTensor(); + motionState->setRigidBody(body); + motionState->updateBodyVelocities(); + // NOTE: Bullet will deactivate any object whose velocity is below these thresholds for longer than 2 seconds. + // (the 2 seconds is determined by: static btRigidBody::gDeactivationTime + const float DYNAMIC_LINEAR_VELOCITY_THRESHOLD = 0.05f; // 5 cm/sec + const float DYNAMIC_ANGULAR_VELOCITY_THRESHOLD = 0.087266f; // ~5 deg/sec + body->setSleepingThresholds(DYNAMIC_LINEAR_VELOCITY_THRESHOLD, DYNAMIC_ANGULAR_VELOCITY_THRESHOLD); + if (!motionState->isMoving()) { + // try to initialize this object as inactive + body->forceActivationState(ISLAND_SLEEPING); + } + break; + } + case MOTION_TYPE_STATIC: + default: { + if (!body) { + assert(motionState->getShape()); + body = new btRigidBody(mass, motionState, motionState->getShape(), inertia); + } else { + body->setMassProps(mass, inertia); + } + body->setCollisionFlags(btCollisionObject::CF_STATIC_OBJECT); + body->updateInertiaTensor(); + motionState->setRigidBody(body); + break; + } + } + body->setFlags(BT_DISABLE_WORLD_GRAVITY); + motionState->updateBodyMaterialProperties(); + + _dynamicsWorld->addRigidBody(body); + + motionState->getAndClearIncomingDirtyFlags(); +} + +void PhysicsEngine::removeObject(ObjectMotionState* object) { + // wake up anything touching this object + bump(object); + removeContacts(object); + + btRigidBody* body = object->getRigidBody(); + assert(body); + _dynamicsWorld->removeRigidBody(body); +} + +void PhysicsEngine::deleteObjects(VectorOfMotionStates& objects) { + for (auto object : objects) { + removeObject(object); + + // NOTE: setRigidBody() modifies body->m_userPointer so we should clear the MotionState's body BEFORE deleting it. + btRigidBody* body = object->getRigidBody(); + object->setRigidBody(nullptr); + delete body; + object->releaseShape(); + delete object; + } +} + +// Same as above, but takes a Set instead of a Vector and ommits some cleanup operations. Only called during teardown. +void PhysicsEngine::deleteObjects(SetOfMotionStates& objects) { + for (auto object : objects) { + btRigidBody* body = object->getRigidBody(); + _dynamicsWorld->removeRigidBody(body); + + // NOTE: setRigidBody() modifies body->m_userPointer so we should clear the MotionState's body BEFORE deleting it. + object->setRigidBody(nullptr); + delete body; + object->releaseShape(); + delete object; + } +} + +void PhysicsEngine::addObjects(VectorOfMotionStates& objects) { + for (auto object : objects) { + addObject(object); + } +} + +void PhysicsEngine::changeObjects(VectorOfMotionStates& objects) { + for (auto object : objects) { + uint32_t flags = object->getAndClearIncomingDirtyFlags() & DIRTY_PHYSICS_FLAGS; + if (flags & HARD_DIRTY_PHYSICS_FLAGS) { + object->handleHardAndEasyChanges(flags, this); + } else if (flags & EASY_DIRTY_PHYSICS_FLAGS) { + object->handleEasyChanges(flags); } } } -void PhysicsEngine::addEntityInternal(EntityItem* entity) { - assert(entity); - void* physicsInfo = entity->getPhysicsInfo(); - if (!physicsInfo) { - if (entity->isReadyToComputeShape()) { - ShapeInfo shapeInfo; - entity->computeShapeInfo(shapeInfo); - btCollisionShape* shape = _shapeManager.getShape(shapeInfo); - if (shape) { - EntityMotionState* motionState = new EntityMotionState(entity); - entity->setPhysicsInfo(static_cast(motionState)); - _entityMotionStates.insert(motionState); - addObject(shapeInfo, shape, motionState); - } else if (entity->isMoving()) { - EntityMotionState* motionState = new EntityMotionState(entity); - entity->setPhysicsInfo(static_cast(motionState)); - _entityMotionStates.insert(motionState); - - motionState->setKinematic(true, _numSubsteps); - _nonPhysicalKinematicObjects.insert(motionState); - // We failed to add the entity to the simulation. Probably because we couldn't create a shape for it. - //qCDebug(physics) << "failed to add entity " << entity->getEntityItemID() << " to physics engine"; - } - } - } -} - -void PhysicsEngine::removeEntityInternal(EntityItem* entity) { - assert(entity); - void* physicsInfo = entity->getPhysicsInfo(); - if (physicsInfo) { - EntityMotionState* motionState = static_cast(physicsInfo); - if (motionState->getRigidBody()) { - removeObjectFromBullet(motionState); - } else { - // only need to hunt in this list when there is no RigidBody - _nonPhysicalKinematicObjects.remove(motionState); - } - _entityMotionStates.remove(motionState); - _incomingChanges.remove(motionState); - _outgoingPackets.remove(motionState); - // NOTE: EntityMotionState dtor will remove its backpointer from EntityItem - delete motionState; - } -} - -void PhysicsEngine::entityChangedInternal(EntityItem* entity) { - // queue incoming changes: from external sources (script, EntityServer, etc) to physics engine - assert(entity); - void* physicsInfo = entity->getPhysicsInfo(); - if (physicsInfo) { - if ((entity->getDirtyFlags() & (HARD_DIRTY_PHYSICS_FLAGS | EASY_DIRTY_PHYSICS_FLAGS)) > 0) { - ObjectMotionState* motionState = static_cast(physicsInfo); - _incomingChanges.insert(motionState); - } - } else { - // try to add this entity again (maybe something changed such that it will work this time) - addEntity(entity); - } -} - -void PhysicsEngine::sortEntitiesThatMovedInternal() { - // entities that have been simulated forward (hence in the _entitiesToBeSorted list) - // also need to be put in the outgoingPackets list - QSet::iterator entityItr = _entitiesToBeSorted.begin(); - while (entityItr != _entitiesToBeSorted.end()) { - EntityItem* entity = *entityItr; - void* physicsInfo = entity->getPhysicsInfo(); - assert(physicsInfo); - ObjectMotionState* motionState = static_cast(physicsInfo); - _outgoingPackets.insert(motionState); - ++entityItr; - } -} - -void PhysicsEngine::clearEntitiesInternal() { - // For now we assume this would only be called on shutdown in which case we can just let the memory get lost. - QSet::const_iterator stateItr = _entityMotionStates.begin(); - for (stateItr = _entityMotionStates.begin(); stateItr != _entityMotionStates.end(); ++stateItr) { - removeObjectFromBullet(*stateItr); - delete (*stateItr); - } - _entityMotionStates.clear(); - _nonPhysicalKinematicObjects.clear(); - _incomingChanges.clear(); - _outgoingPackets.clear(); -} -// end EntitySimulation overrides - -void PhysicsEngine::relayIncomingChangesToSimulation() { - BT_PROFILE("incomingChanges"); - // process incoming changes - QSet::iterator stateItr = _incomingChanges.begin(); - while (stateItr != _incomingChanges.end()) { - ObjectMotionState* motionState = *stateItr; - ++stateItr; - uint32_t flags = motionState->getIncomingDirtyFlags() & DIRTY_PHYSICS_FLAGS; - - bool removeMotionState = false; - btRigidBody* body = motionState->getRigidBody(); - if (body) { - if (flags & HARD_DIRTY_PHYSICS_FLAGS) { - // a HARD update requires the body be pulled out of physics engine, changed, then reinserted - // but it also handles all EASY changes - bool success = updateBodyHard(body, motionState, flags); - if (!success) { - // NOTE: since updateBodyHard() failed we know that motionState has been removed - // from simulation and body has been deleted. Depending on what else has changed - // we might need to remove motionState altogether... - if (flags & EntityItem::DIRTY_VELOCITY) { - motionState->updateKinematicState(_numSubsteps); - if (motionState->isKinematic()) { - // all is NOT lost, we still need to move this object around kinematically - _nonPhysicalKinematicObjects.insert(motionState); - } else { - // no need to keep motionState around - removeMotionState = true; - } - } else { - // no need to keep motionState around - removeMotionState = true; - } - } - } else if (flags) { - // an EASY update does NOT require that the body be pulled out of physics engine - // hence the MotionState has all the knowledge and authority to perform the update. - motionState->updateBodyEasy(flags, _numSubsteps); - } - if (flags & (EntityItem::DIRTY_POSITION | EntityItem::DIRTY_VELOCITY)) { - motionState->resetMeasuredBodyAcceleration(); - } - } else { - // the only way we should ever get here (motionState exists but no body) is when the object - // is undergoing non-physical kinematic motion. - assert(_nonPhysicalKinematicObjects.contains(motionState)); - - // it is possible that the changes are such that the object can now be added to the physical simulation - if (flags & EntityItem::DIRTY_SHAPE) { - ShapeInfo shapeInfo; - motionState->computeObjectShapeInfo(shapeInfo); - btCollisionShape* shape = _shapeManager.getShape(shapeInfo); - if (shape) { - addObject(shapeInfo, shape, motionState); - _nonPhysicalKinematicObjects.remove(motionState); - } else if (flags & EntityItem::DIRTY_VELOCITY) { - // although we couldn't add the object to the simulation, might need to update kinematic motion... - motionState->updateKinematicState(_numSubsteps); - if (!motionState->isKinematic()) { - _nonPhysicalKinematicObjects.remove(motionState); - removeMotionState = true; - } - } - } else if (flags & EntityItem::DIRTY_VELOCITY) { - // although we still can't add to physics simulation, might need to update kinematic motion... - motionState->updateKinematicState(_numSubsteps); - if (!motionState->isKinematic()) { - _nonPhysicalKinematicObjects.remove(motionState); - removeMotionState = true; - } - } - } - if (removeMotionState) { - // if we get here then there is no need to keep this motionState around (no physics or kinematics) - _outgoingPackets.remove(motionState); - if (motionState->getType() == MOTION_STATE_TYPE_ENTITY) { - _entityMotionStates.remove(static_cast(motionState)); - } - // NOTE: motionState will clean up its own backpointers in the Object - delete motionState; - continue; - } - - // NOTE: the grand order of operations is: - // (1) relay incoming changes - // (2) step simulation - // (3) synchronize outgoing motion states - // (4) send outgoing packets - // - // We're in the middle of step (1) hence incoming changes should trump corresponding - // outgoing changes at this point. - motionState->clearOutgoingPacketFlags(flags); // clear outgoing flags that were trumped - motionState->clearIncomingDirtyFlags(flags); // clear incoming flags that were processed - } - _incomingChanges.clear(); +void PhysicsEngine::reinsertObject(ObjectMotionState* object) { + removeObject(object); + addObject(object); } void PhysicsEngine::removeContacts(ObjectMotionState* motionState) { @@ -269,33 +203,7 @@ void PhysicsEngine::removeContacts(ObjectMotionState* motionState) { } } -// virtual -void PhysicsEngine::init(EntityEditPacketSender* packetSender) { - // _entityTree should be set prior to the init() call - assert(_entityTree); - - if (!_dynamicsWorld) { - _collisionConfig = new btDefaultCollisionConfiguration(); - _collisionDispatcher = new btCollisionDispatcher(_collisionConfig); - _broadphaseFilter = new btDbvtBroadphase(); - _constraintSolver = new btSequentialImpulseConstraintSolver; - _dynamicsWorld = new ThreadSafeDynamicsWorld(_collisionDispatcher, _broadphaseFilter, _constraintSolver, _collisionConfig); - - _ghostPairCallback = new btGhostPairCallback(); - _dynamicsWorld->getPairCache()->setInternalGhostPairCallback(_ghostPairCallback); - - // default gravity of the world is zero, so each object must specify its own gravity - // TODO: set up gravity zones - _dynamicsWorld->setGravity(btVector3(0.0f, 0.0f, 0.0f)); - } - - assert(packetSender); - _entityPacketSender = packetSender; - EntityMotionState::setOutgoingEntityList(&_entitiesToBeSorted); -} - void PhysicsEngine::stepSimulation() { - lock(); CProfileManager::Reset(); BT_PROFILE("stepSimulation"); // NOTE: the grand order of operations is: @@ -304,9 +212,6 @@ void PhysicsEngine::stepSimulation() { // (3) synchronize outgoing motion states // (4) send outgoing packets - // This is step (1) pull incoming changes - relayIncomingChangesToSimulation(); - const int MAX_NUM_SUBSTEPS = 4; const float MAX_TIMESTEP = (float)MAX_NUM_SUBSTEPS * PHYSICS_ENGINE_FIXED_SUBSTEP; float dt = 1.0e-6f * (float)(_clock.getTimeMicroseconds()); @@ -316,7 +221,7 @@ void PhysicsEngine::stepSimulation() { // TODO: move character->preSimulation() into relayIncomingChanges if (_characterController) { if (_characterController->needsRemoval()) { - _characterController->setDynamicsWorld(NULL); + _characterController->setDynamicsWorld(nullptr); } _characterController->updateShapeIfNecessary(); if (_characterController->needsAddition()) { @@ -325,92 +230,49 @@ void PhysicsEngine::stepSimulation() { _characterController->preSimulation(timeStep); } - // This is step (2) step simulation int numSubsteps = _dynamicsWorld->stepSimulation(timeStep, MAX_NUM_SUBSTEPS, PHYSICS_ENGINE_FIXED_SUBSTEP); - _numSubsteps += (uint32_t)numSubsteps; - stepNonPhysicalKinematics(usecTimestampNow()); - unlock(); - - // TODO: make all of this harvest stuff into one function: relayOutgoingChanges() if (numSubsteps > 0) { BT_PROFILE("postSimulation"); - // This is step (3) which is done outside of stepSimulation() so we can lock _entityTree. - // - // Unfortunately we have to unlock the simulation (above) before we try to lock the _entityTree - // to avoid deadlock -- the _entityTree may try to lock its EntitySimulation (from which this - // PhysicsEngine derives) when updating/adding/deleting entities so we need to wait for our own - // lock on the tree before we re-lock ourselves. - // - // TODO: untangle these lock sequences. + _numSubsteps += (uint32_t)numSubsteps; ObjectMotionState::setWorldSimulationStep(_numSubsteps); - _entityTree->lockForWrite(); - lock(); - _dynamicsWorld->synchronizeMotionStates(); if (_characterController) { _characterController->postSimulation(); } - - computeCollisionEvents(); - - unlock(); - _entityTree->unlock(); + updateContactMap(); + _hasOutgoingChanges = true; } } -void PhysicsEngine::stepNonPhysicalKinematics(const quint64& now) { - BT_PROFILE("nonPhysicalKinematics"); - QSet::iterator stateItr = _nonPhysicalKinematicObjects.begin(); - // TODO?: need to occasionally scan for stopped non-physical kinematics objects - while (stateItr != _nonPhysicalKinematicObjects.end()) { - ObjectMotionState* motionState = *stateItr; - motionState->stepKinematicSimulation(now); - ++stateItr; - } -} - - void PhysicsEngine::doOwnershipInfection(const btCollisionObject* objectA, const btCollisionObject* objectB) { - assert(objectA); - assert(objectB); - - auto nodeList = DependencyManager::get(); - QUuid myNodeID = nodeList->getSessionUUID(); - - if (myNodeID.isNull()) { + BT_PROFILE("ownershipInfection"); + if (_sessionID.isNull()) { return; } - const btCollisionObject* characterCollisionObject = - _characterController ? _characterController->getCollisionObject() : NULL; + const btCollisionObject* characterObject = _characterController ? _characterController->getCollisionObject() : nullptr; ObjectMotionState* a = static_cast(objectA->getUserPointer()); ObjectMotionState* b = static_cast(objectB->getUserPointer()); - EntityItem* entityA = a ? a->getEntity() : NULL; - EntityItem* entityB = b ? b->getEntity() : NULL; - bool aIsDynamic = entityA && !objectA->isStaticOrKinematicObject(); - bool bIsDynamic = entityB && !objectB->isStaticOrKinematicObject(); - // collisions cause infectious spread of simulation-ownership. we also attempt to take - // ownership of anything that collides with our avatar. - if ((aIsDynamic && (entityA->getSimulatorID() == myNodeID)) || - // (a && a->getShouldClaimSimulationOwnership()) || - (objectA == characterCollisionObject)) { - if (bIsDynamic) { - b->setShouldClaimSimulationOwnership(true); + if (b && ((a && a->getSimulatorID() == _sessionID && !objectA->isStaticObject()) || (objectA == characterObject))) { + // NOTE: we might own the simulation of a kinematic object (A) + // but we don't claim ownership of kinematic objects (B) based on collisions here. + if (!objectB->isStaticOrKinematicObject()) { + b->bump(); } - } else if ((bIsDynamic && (entityB->getSimulatorID() == myNodeID)) || - // (b && b->getShouldClaimSimulationOwnership()) || - (objectB == characterCollisionObject)) { - if (aIsDynamic) { - a->setShouldClaimSimulationOwnership(true); + } else if (a && ((b && b->getSimulatorID() == _sessionID && !objectB->isStaticObject()) || (objectB == characterObject))) { + // SIMILARLY: we might own the simulation of a kinematic object (B) + // but we don't claim ownership of kinematic objects (A) based on collisions here. + if (!objectA->isStaticOrKinematicObject()) { + a->bump(); } } } - -void PhysicsEngine::computeCollisionEvents() { - BT_PROFILE("computeCollisionEvents"); +void PhysicsEngine::updateContactMap() { + BT_PROFILE("updateContactMap"); + ++_numContactFrames; // update all contacts every frame int numManifolds = _collisionDispatcher->getNumManifolds(); @@ -432,39 +294,44 @@ void PhysicsEngine::computeCollisionEvents() { ObjectMotionState* b = static_cast(objectB->getUserPointer()); if (a || b) { // the manifold has up to 4 distinct points, but only extract info from the first - _contactMap[ContactKey(a, b)].update(_numContactFrames, contactManifold->getContactPoint(0), _originOffset); + _contactMap[ContactKey(a, b)].update(_numContactFrames, contactManifold->getContactPoint(0)); } doOwnershipInfection(objectA, objectB); } } - +} + +CollisionEvents& PhysicsEngine::getCollisionEvents() { const uint32_t CONTINUE_EVENT_FILTER_FREQUENCY = 10; + _collisionEvents.clear(); // scan known contacts and trigger events ContactMap::iterator contactItr = _contactMap.begin(); while (contactItr != _contactMap.end()) { - ObjectMotionState* A = static_cast(contactItr->first._a); - ObjectMotionState* B = static_cast(contactItr->first._b); + ContactInfo& contact = contactItr->second; + ContactEventType type = contact.computeType(_numContactFrames); + if(type != CONTACT_EVENT_TYPE_CONTINUE || _numSubsteps % CONTINUE_EVENT_FILTER_FREQUENCY == 0) { + ObjectMotionState* A = static_cast(contactItr->first._a); + ObjectMotionState* B = static_cast(contactItr->first._b); - // TODO: make triggering these events clean and efficient. The code at this context shouldn't - // have to figure out what kind of object (entity, avatar, etc) these are in order to properly - // emit a collision event. - // TODO: enable scripts to filter based on contact event type - ContactEventType type = contactItr->second.computeType(_numContactFrames); - if(type != CONTACT_EVENT_TYPE_CONTINUE || _numSubsteps % CONTINUE_EVENT_FILTER_FREQUENCY == 0){ if (A && A->getType() == MOTION_STATE_TYPE_ENTITY) { - EntityItemID idA = static_cast(A)->getEntity()->getEntityItemID(); - EntityItemID idB; + QUuid idA = A->getObjectID(); + QUuid idB; if (B && B->getType() == MOTION_STATE_TYPE_ENTITY) { - idB = static_cast(B)->getEntity()->getEntityItemID(); + idB = B->getObjectID(); } - emit entityCollisionWithEntity(idA, idB, contactItr->second); + glm::vec3 position = bulletToGLM(contact.getPositionWorldOnB()) + _originOffset; + glm::vec3 penetration = bulletToGLM(contact.distance * contact.normalWorldOnB); + _collisionEvents.push_back(Collision(type, idA, idB, position, penetration)); } else if (B && B->getType() == MOTION_STATE_TYPE_ENTITY) { - EntityItemID idA; - EntityItemID idB = static_cast(B)->getEntity()->getEntityItemID(); - emit entityCollisionWithEntity(idA, idB, contactItr->second); + QUuid idB = B->getObjectID(); + glm::vec3 position = bulletToGLM(contact.getPositionWorldOnA()) + _originOffset; + // NOTE: we're flipping the order of A and B (so that the first objectID is never NULL) + // hence we must negate the penetration. + glm::vec3 penetration = - bulletToGLM(contact.distance * contact.normalWorldOnB); + _collisionEvents.push_back(Collision(type, idB, QUuid(), position, penetration)); } } @@ -476,7 +343,14 @@ void PhysicsEngine::computeCollisionEvents() { ++contactItr; } } - ++_numContactFrames; + return _collisionEvents; +} + +VectorOfMotionStates& PhysicsEngine::getOutgoingChanges() { + BT_PROFILE("copyOutgoingChanges"); + _dynamicsWorld->synchronizeMotionStates(); + _hasOutgoingChanges = false; + return _dynamicsWorld->getChangedMotionStates(); } void PhysicsEngine::dumpStatsIfNecessary() { @@ -495,235 +369,80 @@ void PhysicsEngine::dumpStatsIfNecessary() { // CF_DISABLE_VISUALIZE_OBJECT = 32, //disable debug drawing // CF_DISABLE_SPU_COLLISION_PROCESSING = 64//disable parallel/SPU processing -void PhysicsEngine::addObject(const ShapeInfo& shapeInfo, btCollisionShape* shape, ObjectMotionState* motionState) { - assert(shape); +void PhysicsEngine::bump(ObjectMotionState* motionState) { + // Find all objects that touch the object corresponding to motionState and flag the other objects + // for simulation ownership by the local simulation. + assert(motionState); + btCollisionObject* object = motionState->getRigidBody(); - btVector3 inertia(0.0f, 0.0f, 0.0f); - float mass = 0.0f; - btRigidBody* body = NULL; - switch(motionState->computeObjectMotionType()) { - case MOTION_TYPE_KINEMATIC: { - body = new btRigidBody(mass, motionState, shape, inertia); - body->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT); - body->updateInertiaTensor(); - motionState->setRigidBody(body); - motionState->setKinematic(true, _numSubsteps); - const float KINEMATIC_LINEAR_VELOCITY_THRESHOLD = 0.01f; // 1 cm/sec - const float KINEMATIC_ANGULAR_VELOCITY_THRESHOLD = 0.01f; // ~1 deg/sec - body->setSleepingThresholds(KINEMATIC_LINEAR_VELOCITY_THRESHOLD, KINEMATIC_ANGULAR_VELOCITY_THRESHOLD); - break; - } - case MOTION_TYPE_DYNAMIC: { - mass = motionState->computeObjectMass(shapeInfo); - shape->calculateLocalInertia(mass, inertia); - body = new btRigidBody(mass, motionState, shape, inertia); - body->updateInertiaTensor(); - motionState->setRigidBody(body); - motionState->setKinematic(false, _numSubsteps); - motionState->updateBodyVelocities(); - // NOTE: Bullet will deactivate any object whose velocity is below these thresholds for longer than 2 seconds. - // (the 2 seconds is determined by: static btRigidBody::gDeactivationTime - const float DYNAMIC_LINEAR_VELOCITY_THRESHOLD = 0.05f; // 5 cm/sec - const float DYNAMIC_ANGULAR_VELOCITY_THRESHOLD = 0.087266f; // ~5 deg/sec - body->setSleepingThresholds(DYNAMIC_LINEAR_VELOCITY_THRESHOLD, DYNAMIC_ANGULAR_VELOCITY_THRESHOLD); - if (!motionState->isMoving()) { - // try to initialize this object as inactive - body->forceActivationState(ISLAND_SLEEPING); - } - break; - } - case MOTION_TYPE_STATIC: - default: { - body = new btRigidBody(mass, motionState, shape, inertia); - body->setCollisionFlags(btCollisionObject::CF_STATIC_OBJECT); - body->updateInertiaTensor(); - motionState->setRigidBody(body); - motionState->setKinematic(false, _numSubsteps); - break; - } - } - body->setFlags(BT_DISABLE_WORLD_GRAVITY); - motionState->updateBodyMaterialProperties(); - - _dynamicsWorld->addRigidBody(body); - motionState->resetMeasuredBodyAcceleration(); -} - -void PhysicsEngine::bump(EntityItem* bumpEntity) { - // If this node is doing something like deleting an entity, scan for contacts involving the - // entity. For each found, flag the other entity involved as being simulated by this node. - lock(); int numManifolds = _collisionDispatcher->getNumManifolds(); for (int i = 0; i < numManifolds; ++i) { btPersistentManifold* contactManifold = _collisionDispatcher->getManifoldByIndexInternal(i); if (contactManifold->getNumContacts() > 0) { const btCollisionObject* objectA = static_cast(contactManifold->getBody0()); const btCollisionObject* objectB = static_cast(contactManifold->getBody1()); - if (objectA && objectB) { - void* a = objectA->getUserPointer(); - void* b = objectB->getUserPointer(); - if (a && b) { - EntityMotionState* entityMotionStateA = static_cast(a); - EntityMotionState* entityMotionStateB = static_cast(b); - EntityItem* entityA = entityMotionStateA ? entityMotionStateA->getEntity() : NULL; - EntityItem* entityB = entityMotionStateB ? entityMotionStateB->getEntity() : NULL; - if (entityA && entityB) { - if (entityA == bumpEntity) { - entityMotionStateB->setShouldClaimSimulationOwnership(true); - if (!objectB->isActive()) { - objectB->setActivationState(ACTIVE_TAG); - } - } - if (entityB == bumpEntity) { - entityMotionStateA->setShouldClaimSimulationOwnership(true); - if (!objectA->isActive()) { - objectA->setActivationState(ACTIVE_TAG); - } - } + if (objectB == object) { + if (!objectA->isStaticOrKinematicObject()) { + ObjectMotionState* motionStateA = static_cast(objectA->getUserPointer()); + if (motionStateA) { + motionStateA->bump(); + objectA->setActivationState(ACTIVE_TAG); + } + } + } else if (objectA == object) { + if (!objectB->isStaticOrKinematicObject()) { + ObjectMotionState* motionStateB = static_cast(objectB->getUserPointer()); + if (motionStateB) { + motionStateB->bump(); + objectB->setActivationState(ACTIVE_TAG); } } } } } - unlock(); -} - -void PhysicsEngine::removeObjectFromBullet(ObjectMotionState* motionState) { - assert(motionState); - btRigidBody* body = motionState->getRigidBody(); - - // wake up anything touching this object - EntityItem* entityItem = motionState ? motionState->getEntity() : NULL; - bump(entityItem); - - if (body) { - const btCollisionShape* shape = body->getCollisionShape(); - _dynamicsWorld->removeRigidBody(body); - _shapeManager.releaseShape(shape); - // NOTE: setRigidBody() modifies body->m_userPointer so we should clear the MotionState's body BEFORE deleting it. - motionState->setRigidBody(NULL); - delete body; - motionState->setKinematic(false, _numSubsteps); - - removeContacts(motionState); - } -} - -// private -bool PhysicsEngine::updateBodyHard(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags) { - MotionType newType = motionState->computeObjectMotionType(); - - // pull body out of physics engine - _dynamicsWorld->removeRigidBody(body); - - if (flags & EntityItem::DIRTY_SHAPE) { - // MASS bit should be set whenever SHAPE is set - assert(flags & EntityItem::DIRTY_MASS); - - // get new shape - btCollisionShape* oldShape = body->getCollisionShape(); - ShapeInfo shapeInfo; - motionState->computeObjectShapeInfo(shapeInfo); - btCollisionShape* newShape = _shapeManager.getShape(shapeInfo); - if (!newShape) { - // FAIL! we are unable to support these changes! - _shapeManager.releaseShape(oldShape); - - // NOTE: setRigidBody() modifies body->m_userPointer so we should clear the MotionState's body BEFORE deleting it. - motionState->setRigidBody(NULL); - delete body; - motionState->setKinematic(false, _numSubsteps); - removeContacts(motionState); - return false; - } else if (newShape != oldShape) { - // BUG: if shape doesn't change but density does then we won't compute new mass properties - // TODO: fix this BUG by replacing DIRTY_MASS with DIRTY_DENSITY and then fix logic accordingly. - body->setCollisionShape(newShape); - _shapeManager.releaseShape(oldShape); - - // compute mass properties - float mass = motionState->computeObjectMass(shapeInfo); - btVector3 inertia(0.0f, 0.0f, 0.0f); - body->getCollisionShape()->calculateLocalInertia(mass, inertia); - body->setMassProps(mass, inertia); - body->updateInertiaTensor(); - } else { - // whoops, shape hasn't changed after all so we must release the reference - // that was created when looking it up - _shapeManager.releaseShape(newShape); - } - } - bool easyUpdate = flags & EASY_DIRTY_PHYSICS_FLAGS; - if (easyUpdate) { - motionState->updateBodyEasy(flags, _numSubsteps); - } - - // update the motion parameters - switch (newType) { - case MOTION_TYPE_KINEMATIC: { - int collisionFlags = body->getCollisionFlags() | btCollisionObject::CF_KINEMATIC_OBJECT; - collisionFlags &= ~(btCollisionObject::CF_STATIC_OBJECT); - body->setCollisionFlags(collisionFlags); - body->forceActivationState(DISABLE_DEACTIVATION); - - body->setMassProps(0.0f, btVector3(0.0f, 0.0f, 0.0f)); - body->updateInertiaTensor(); - motionState->setKinematic(true, _numSubsteps); - break; - } - case MOTION_TYPE_DYNAMIC: { - int collisionFlags = body->getCollisionFlags() & ~(btCollisionObject::CF_KINEMATIC_OBJECT | btCollisionObject::CF_STATIC_OBJECT); - body->setCollisionFlags(collisionFlags); - if (! (flags & EntityItem::DIRTY_MASS)) { - // always update mass properties when going dynamic (unless it's already been done above) - ShapeInfo shapeInfo; - motionState->computeObjectShapeInfo(shapeInfo); - float mass = motionState->computeObjectMass(shapeInfo); - btVector3 inertia(0.0f, 0.0f, 0.0f); - body->getCollisionShape()->calculateLocalInertia(mass, inertia); - body->setMassProps(mass, inertia); - body->updateInertiaTensor(); - } - body->forceActivationState(ACTIVE_TAG); - motionState->setKinematic(false, _numSubsteps); - break; - } - default: { - // MOTION_TYPE_STATIC - int collisionFlags = body->getCollisionFlags() | btCollisionObject::CF_STATIC_OBJECT; - collisionFlags &= ~(btCollisionObject::CF_KINEMATIC_OBJECT); - body->setCollisionFlags(collisionFlags); - body->forceActivationState(DISABLE_SIMULATION); - - body->setMassProps(0.0f, btVector3(0.0f, 0.0f, 0.0f)); - body->updateInertiaTensor(); - - body->setLinearVelocity(btVector3(0.0f, 0.0f, 0.0f)); - body->setAngularVelocity(btVector3(0.0f, 0.0f, 0.0f)); - motionState->setKinematic(false, _numSubsteps); - break; - } - } - - // reinsert body into physics engine - _dynamicsWorld->addRigidBody(body); - - body->activate(); - return true; } void PhysicsEngine::setCharacterController(DynamicCharacterController* character) { if (_characterController != character) { - lock(); if (_characterController) { // remove the character from the DynamicsWorld immediately - _characterController->setDynamicsWorld(NULL); - _characterController = NULL; + _characterController->setDynamicsWorld(nullptr); + _characterController = nullptr; } // the character will be added to the DynamicsWorld later _characterController = character; - unlock(); } } +bool PhysicsEngine::physicsInfoIsActive(void* physicsInfo) { + if (!physicsInfo) { + return false; + } + + ObjectMotionState* motionState = static_cast(physicsInfo); + btRigidBody* body = motionState->getRigidBody(); + if (!body) { + return false; + } + + return body->isActive(); +} + +bool PhysicsEngine::getBodyLocation(void* physicsInfo, glm::vec3& positionReturn, glm::quat& rotationReturn) { + if (!physicsInfo) { + return false; + } + + ObjectMotionState* motionState = static_cast(physicsInfo); + btRigidBody* body = motionState->getRigidBody(); + if (!body) { + return false; + } + + const btTransform& worldTrans = body->getCenterOfMassTransform(); + positionReturn = bulletToGLM(worldTrans.getOrigin()) + ObjectMotionState::getWorldOffset(); + rotationReturn = bulletToGLM(worldTrans.getRotation()); + + return true; +} diff --git a/libraries/physics/src/PhysicsEngine.h b/libraries/physics/src/PhysicsEngine.h index 4f931d939a..8b947d2510 100644 --- a/libraries/physics/src/PhysicsEngine.h +++ b/libraries/physics/src/PhysicsEngine.h @@ -14,18 +14,15 @@ #include -#include +#include +#include #include #include -#include -#include - #include "BulletUtil.h" -#include "DynamicCharacterController.h" #include "ContactInfo.h" -#include "EntityMotionState.h" -#include "ShapeManager.h" +#include "DynamicCharacterController.h" +#include "PhysicsTypedefs.h" #include "ThreadSafeDynamicsWorld.h" const float HALF_SIMULATION_EXTENT = 512.0f; // meters @@ -39,37 +36,45 @@ public: ContactKey(void* a, void* b) : _a(a), _b(b) {} bool operator<(const ContactKey& other) const { return _a < other._a || (_a == other._a && _b < other._b); } bool operator==(const ContactKey& other) const { return _a == other._a && _b == other._b; } - void* _a; // EntityMotionState pointer - void* _b; // EntityMotionState pointer + void* _a; // ObjectMotionState pointer + void* _b; // ObjectMotionState pointer }; typedef std::map ContactMap; -typedef std::pair ContactMapElement; +typedef QVector CollisionEvents; -class PhysicsEngine : public EntitySimulation { +class PhysicsEngine { public: // TODO: find a good way to make this a non-static method static uint32_t getNumSubsteps(); - PhysicsEngine() = delete; // prevent compiler from creating default ctor PhysicsEngine(const glm::vec3& offset); - ~PhysicsEngine(); + void init(); - // overrides for EntitySimulation - void updateEntitiesInternal(const quint64& now); - void addEntityInternal(EntityItem* entity); - void removeEntityInternal(EntityItem* entity); - void entityChangedInternal(EntityItem* entity); - void sortEntitiesThatMovedInternal(); - void clearEntitiesInternal(); + void setSessionUUID(const QUuid& sessionID) { _sessionID = sessionID; } - virtual void init(EntityEditPacketSender* packetSender); + void addObject(ObjectMotionState* motionState); + void removeObject(ObjectMotionState* motionState); + + void deleteObjects(VectorOfMotionStates& objects); + void deleteObjects(SetOfMotionStates& objects); // only called during teardown + void addObjects(VectorOfMotionStates& objects); + void changeObjects(VectorOfMotionStates& objects); + void reinsertObject(ObjectMotionState* object); void stepSimulation(); - void stepNonPhysicalKinematics(const quint64& now); - void computeCollisionEvents(); + void updateContactMap(); + bool hasOutgoingChanges() const { return _hasOutgoingChanges; } + + /// \return reference to list of changed MotionStates. The list is only valid until beginning of next simulation loop. + VectorOfMotionStates& getOutgoingChanges(); + + /// \return reference to list of Collision events. The list is only valid until beginning of next simulation loop. + CollisionEvents& getCollisionEvents(); + + /// \brief prints timings for last frame if stats have been requested. void dumpStatsIfNecessary(); /// \param offset position of simulation origin in domain-frame @@ -78,31 +83,23 @@ public: /// \return position of simulation origin in domain-frame const glm::vec3& getOriginOffset() const { return _originOffset; } - /// \param motionState pointer to Object's MotionState - /// \return true if Object added - void addObject(const ShapeInfo& shapeInfo, btCollisionShape* shape, ObjectMotionState* motionState); + /// \brief call bump on any objects that touch the object corresponding to motionState + void bump(ObjectMotionState* motionState); - /// process queue of changed from external sources - void relayIncomingChangesToSimulation(); + void removeRigidBody(btRigidBody* body); void setCharacterController(DynamicCharacterController* character); void dumpNextStats() { _dumpNextStats = true; } - void bump(EntityItem* bumpEntity); + static bool physicsInfoIsActive(void* physicsInfo); + static bool getBodyLocation(void* physicsInfo, glm::vec3& positionReturn, glm::quat& rotationReturn); private: - /// \param motionState pointer to Object's MotionState - void removeObjectFromBullet(ObjectMotionState* motionState); - void removeContacts(ObjectMotionState* motionState); void doOwnershipInfection(const btCollisionObject* objectA, const btCollisionObject* objectB); - // return 'true' of update was successful - bool updateBodyHard(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags); - void updateObjectEasy(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags); - btClock _clock; btDefaultCollisionConfiguration* _collisionConfig = NULL; btCollisionDispatcher* _collisionDispatcher = NULL; @@ -110,18 +107,9 @@ private: btSequentialImpulseConstraintSolver* _constraintSolver = NULL; ThreadSafeDynamicsWorld* _dynamicsWorld = NULL; btGhostPairCallback* _ghostPairCallback = NULL; - ShapeManager _shapeManager; glm::vec3 _originOffset; - // EntitySimulation stuff - QSet _entityMotionStates; // all entities that we track - QSet _nonPhysicalKinematicObjects; // not in physics simulation, but still need kinematic simulation - QSet _incomingChanges; // entities with pending physics changes by script or packet - QSet _outgoingPackets; // MotionStates with pending changes that need to be sent over wire - - EntityEditPacketSender* _entityPacketSender = NULL; - ContactMap _contactMap; uint32_t _numContactFrames = 0; uint32_t _lastNumSubstepsAtUpdateInternal = 0; @@ -130,6 +118,10 @@ private: DynamicCharacterController* _characterController = NULL; bool _dumpNextStats = false; + bool _hasOutgoingChanges = false; + + QUuid _sessionID; + CollisionEvents _collisionEvents; }; #endif // hifi_PhysicsEngine_h diff --git a/libraries/physics/src/PhysicsTypedefs.h b/libraries/physics/src/PhysicsTypedefs.h new file mode 100644 index 0000000000..9d9685a758 --- /dev/null +++ b/libraries/physics/src/PhysicsTypedefs.h @@ -0,0 +1,23 @@ +// +// PhysicsTypedefs.h +// libraries/physcis/src +// +// Created by Andrew Meadows 2015.04.29 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_PhysicsTypedefs_h +#define hifi_PhysicsTypedefs_h + +#include +#include + +class ObjectMotionState; + +typedef QSet SetOfMotionStates; +typedef QVector VectorOfMotionStates; + +#endif //hifi_PhysicsTypedefs_h diff --git a/libraries/physics/src/ThreadSafeDynamicsWorld.cpp b/libraries/physics/src/ThreadSafeDynamicsWorld.cpp index a345b915db..a2d3ee97d3 100644 --- a/libraries/physics/src/ThreadSafeDynamicsWorld.cpp +++ b/libraries/physics/src/ThreadSafeDynamicsWorld.cpp @@ -17,6 +17,7 @@ #include +#include "ObjectMotionState.h" #include "ThreadSafeDynamicsWorld.h" ThreadSafeDynamicsWorld::ThreadSafeDynamicsWorld( @@ -57,7 +58,7 @@ int ThreadSafeDynamicsWorld::stepSimulation( btScalar timeStep, int maxSubSteps, /*//process some debugging flags if (getDebugDrawer()) { - btIDebugDraw* debugDrawer = getDebugDrawer (); + btIDebugDraw* debugDrawer = getDebugDrawer(); gDisableDeactivation = (debugDrawer->getDebugMode() & btIDebugDraw::DBG_NoDeactivation) != 0; }*/ if (subSteps) { @@ -84,3 +85,33 @@ int ThreadSafeDynamicsWorld::stepSimulation( btScalar timeStep, int maxSubSteps, return subSteps; } + +void ThreadSafeDynamicsWorld::synchronizeMotionStates() { + _changedMotionStates.clear(); + BT_PROFILE("synchronizeMotionStates"); + if (m_synchronizeAllMotionStates) { + //iterate over all collision objects + for (int i=0;igetMotionState()) { + synchronizeSingleMotionState(body); + _changedMotionStates.push_back(static_cast(body->getMotionState())); + } + } + } + } else { + //iterate over all active rigid bodies + for (int i=0;iisActive()) { + if (body->getMotionState()) { + synchronizeSingleMotionState(body); + _changedMotionStates.push_back(static_cast(body->getMotionState())); + } + } + } + } +} + diff --git a/libraries/physics/src/ThreadSafeDynamicsWorld.h b/libraries/physics/src/ThreadSafeDynamicsWorld.h index 832efb9b60..4a96eae311 100644 --- a/libraries/physics/src/ThreadSafeDynamicsWorld.h +++ b/libraries/physics/src/ThreadSafeDynamicsWorld.h @@ -18,8 +18,11 @@ #ifndef hifi_ThreadSafeDynamicsWorld_h #define hifi_ThreadSafeDynamicsWorld_h +#include #include +#include "PhysicsTypedefs.h" + ATTRIBUTE_ALIGNED16(class) ThreadSafeDynamicsWorld : public btDiscreteDynamicsWorld { public: BT_DECLARE_ALIGNED_ALLOCATOR(); @@ -32,11 +35,17 @@ public: // virtual overrides from btDiscreteDynamicsWorld int stepSimulation( btScalar timeStep, int maxSubSteps=1, btScalar fixedTimeStep=btScalar(1.)/btScalar(60.)); + void synchronizeMotionStates(); // btDiscreteDynamicsWorld::m_localTime is the portion of real-time that has not yet been simulated // but is used for MotionState::setWorldTransform() extrapolation (a feature that Bullet uses to provide // smoother rendering of objects when the physics simulation loop is ansynchronous to the render loop). float getLocalTimeAccumulation() const { return m_localTime; } + + VectorOfMotionStates& getChangedMotionStates() { return _changedMotionStates; } + +private: + VectorOfMotionStates _changedMotionStates; }; #endif // hifi_ThreadSafeDynamicsWorld_h diff --git a/libraries/shared/src/RegisteredMetaTypes.cpp b/libraries/shared/src/RegisteredMetaTypes.cpp index 4099384aea..8c9d0c27bd 100644 --- a/libraries/shared/src/RegisteredMetaTypes.cpp +++ b/libraries/shared/src/RegisteredMetaTypes.cpp @@ -187,6 +187,9 @@ void pickRayFromScriptValue(const QScriptValue& object, PickRay& pickRay) { QScriptValue collisionToScriptValue(QScriptEngine* engine, const Collision& collision) { QScriptValue obj = engine->newObject(); + obj.setProperty("type", collision.type); + obj.setProperty("idA", quuidToScriptValue(engine, collision.idA)); + obj.setProperty("idB", quuidToScriptValue(engine, collision.idB)); obj.setProperty("penetration", vec3toScriptValue(engine, collision.penetration)); obj.setProperty("contactPoint", vec3toScriptValue(engine, collision.contactPoint)); return obj; diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index ca4898b65c..14f30c20fc 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -13,6 +13,7 @@ #define hifi_RegisteredMetaTypes_h #include +#include #include #include @@ -65,11 +66,21 @@ Q_DECLARE_METATYPE(PickRay) QScriptValue pickRayToScriptValue(QScriptEngine* engine, const PickRay& pickRay); void pickRayFromScriptValue(const QScriptValue& object, PickRay& pickRay); +enum ContactEventType { + CONTACT_EVENT_TYPE_START, + CONTACT_EVENT_TYPE_CONTINUE, + CONTACT_EVENT_TYPE_END +}; + class Collision { public: - Collision() : contactPoint(0.0f), penetration(0.0f) { } - Collision(const glm::vec3& contactPoint, const glm::vec3& penetration) : - contactPoint(contactPoint), penetration(penetration) { } + Collision() : type(CONTACT_EVENT_TYPE_START), idA(), idB(), contactPoint(0.0f), penetration(0.0f) { } + Collision(ContactEventType cType, const QUuid& cIdA, const QUuid& cIdB, const glm::vec3& cPoint, const glm::vec3& cPenetration) + : type(cType), idA(cIdA), idB(cIdB), contactPoint(cPoint), penetration(cPenetration) { } + + ContactEventType type; + QUuid idA; + QUuid idB; glm::vec3 contactPoint; glm::vec3 penetration; };