diff --git a/cmake/macros/IncludeBullet.cmake b/cmake/macros/IncludeBullet.cmake new file mode 100644 index 0000000000..a238b33660 --- /dev/null +++ b/cmake/macros/IncludeBullet.cmake @@ -0,0 +1,18 @@ +# +# IncludeBullet.cmake +# +# Copyright 2014 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 +# + +macro(INCLUDE_BULLET) + find_package(Bullet) + if (BULLET_FOUND) + include_directories("${BULLET_INCLUDE_DIRS}") + if (APPLE OR UNIX) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DUSE_BULLET_PHYSICS -isystem ${BULLET_INCLUDE_DIRS}") + endif () + endif (BULLET_FOUND) +endmacro(INCLUDE_BULLET) diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index a39c3d0c19..d05f0b1e20 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -38,6 +38,7 @@ endif () # set up the external glm library include_glm() +include_bullet() # create the InterfaceConfig.h file based on GL_HEADERS above configure_file(InterfaceConfig.h.in "${PROJECT_BINARY_DIR}/includes/InterfaceConfig.h") diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index f294da6fd5..4465899262 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -71,6 +71,7 @@ #include #include #include +#include #include #include #include @@ -155,6 +156,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : _voxelImporter(), _importSucceded(false), _sharedVoxelSystem(TREE_SCALE, DEFAULT_MAX_VOXELS_PER_SYSTEM, &_clipboard), +#ifdef USE_BULLET_PHYSICS + _physicsEngine(glm::vec3(0.0f)), +#endif // USE_BULLET_PHYSICS _entities(true, this, this), _entityCollisionSystem(), _entityClipboardRenderer(false, this, this), @@ -2038,6 +2042,12 @@ void Application::init() { // save settings when avatar changes connect(_myAvatar, &MyAvatar::transformChanged, this, &Application::bumpSettings); +#ifdef USE_BULLET_PHYSICS + EntityTree* tree = _entities.getTree(); + _physicsEngine.setEntityTree(tree); + tree->setSimulation(&_physicsEngine); + _physicsEngine.init(&_entityEditSender); +#endif // USE_BULLET_PHYSICS // make sure our texture cache knows about window size changes DependencyManager::get()->associateWithWidget(glCanvas.data()); @@ -2347,6 +2357,13 @@ void Application::update(float deltaTime) { updateDialogs(deltaTime); // update various stats dialogs if present updateCursor(deltaTime); // Handle cursor updates +#ifdef USE_BULLET_PHYSICS + { + PerformanceTimer perfTimer("physics"); + _physicsEngine.stepSimulation(); + } +#endif // USE_BULLET_PHYSICS + if (!_aboutToQuit) { PerformanceTimer perfTimer("entities"); // NOTE: the _entities.update() call below will wait for lock @@ -4197,6 +4214,25 @@ void Application::openUrl(const QUrl& url) { } } +void Application::updateMyAvatarTransform() { + bumpSettings(); +#ifdef USE_BULLET_PHYSICS + const float SIMULATION_OFFSET_QUANTIZATION = 16.0f; // meters + glm::vec3 avatarPosition = _myAvatar->getPosition(); + glm::vec3 physicsWorldOffset = _physicsEngine.getOriginOffset(); + if (glm::distance(avatarPosition, physicsWorldOffset) > SIMULATION_OFFSET_QUANTIZATION) { + glm::vec3 newOriginOffset = avatarPosition; + int halfExtent = (int)HALF_SIMULATION_EXTENT; + for (int i = 0; i < 3; ++i) { + newOriginOffset[i] = (float)(glm::max(halfExtent, + ((int)(avatarPosition[i] / SIMULATION_OFFSET_QUANTIZATION)) * (int)SIMULATION_OFFSET_QUANTIZATION)); + } + // TODO: Andrew to replace this with method that actually moves existing object positions in PhysicsEngine + _physicsEngine.setOriginOffset(newOriginOffset); + } +#endif // USE_BULLET_PHYSICS +} + void Application::domainSettingsReceived(const QJsonObject& domainSettingsObject) { // from the domain-handler, figure out the satoshi cost per voxel and per meter cubed diff --git a/interface/src/Application.h b/interface/src/Application.h index a3c3d51d59..a0fb5b45f4 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -47,6 +47,7 @@ #include "Menu.h" #include "MetavoxelSystem.h" #include "PacketHeaders.h" +#include "Physics.h" #include "Stars.h" #include "avatar/Avatar.h" #include "avatar/AvatarManager.h" @@ -378,6 +379,7 @@ public slots: void openUrl(const QUrl& url); + void updateMyAvatarTransform(); void bumpSettings() { ++_numChangedSettings; } void domainSettingsReceived(const QJsonObject& domainSettingsObject); @@ -501,6 +503,10 @@ private: VoxelSystem _sharedVoxelSystem; ViewFrustum _sharedVoxelSystemViewFrustum; +#ifdef USE_BULLET_PHYSICS + PhysicsEngine _physicsEngine; +#endif // USE_BULLET_PHYSICS + EntityTreeRenderer _entities; EntityCollisionSystem _entityCollisionSystem; EntityTreeRenderer _entityClipboardRenderer; diff --git a/interface/src/Physics.cpp b/interface/src/Physics.cpp index 23a6a56899..7efa520571 100644 --- a/interface/src/Physics.cpp +++ b/interface/src/Physics.cpp @@ -41,4 +41,4 @@ void applyDamping(float deltaTime, glm::vec3& velocity, float linearStrength, fl void applyDampedSpring(float deltaTime, glm::vec3& velocity, glm::vec3& position, glm::vec3& targetPosition, float k, float damping) { -} \ No newline at end of file +} diff --git a/libraries/avatars/CMakeLists.txt b/libraries/avatars/CMakeLists.txt index bacae45406..9e2f19df97 100644 --- a/libraries/avatars/CMakeLists.txt +++ b/libraries/avatars/CMakeLists.txt @@ -5,7 +5,7 @@ setup_hifi_library(Network Script) include_glm() -link_hifi_libraries(audio shared octree voxels networking physics gpu model fbx) +link_hifi_libraries(audio shared octree voxels networking gpu model fbx) # call macro to include our dependency includes and bubble them up via a property on our target include_dependency_includes() diff --git a/libraries/entities/CMakeLists.txt b/libraries/entities/CMakeLists.txt index 21afff496f..2da7745837 100644 --- a/libraries/entities/CMakeLists.txt +++ b/libraries/entities/CMakeLists.txt @@ -5,7 +5,7 @@ setup_hifi_library(Network Script) include_glm() -link_hifi_libraries(avatars shared octree gpu model fbx networking animation physics) +link_hifi_libraries(avatars shared octree gpu model fbx networking animation) # call macro to include our dependency includes and bubble them up via a property on our target include_dependency_includes() diff --git a/libraries/entities/src/BoxEntityItem.cpp b/libraries/entities/src/BoxEntityItem.cpp index cece544263..88e1a4b6b1 100644 --- a/libraries/entities/src/BoxEntityItem.cpp +++ b/libraries/entities/src/BoxEntityItem.cpp @@ -14,9 +14,9 @@ #include +#include "BoxEntityItem.h" #include "EntityTree.h" #include "EntityTreeElement.h" -#include "BoxEntityItem.h" EntityItem* BoxEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { @@ -95,3 +95,9 @@ void BoxEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitst APPEND_ENTITY_PROPERTY(PROP_COLOR, appendColor, getColor()); } + +void BoxEntityItem::computeShapeInfo(ShapeInfo& info) const { + glm::vec3 halfExtents = 0.5f * getDimensionsInMeters(); + info.setBox(halfExtents); +} + diff --git a/libraries/entities/src/BoxEntityItem.h b/libraries/entities/src/BoxEntityItem.h index da9e7544c6..3ce67369ee 100644 --- a/libraries/entities/src/BoxEntityItem.h +++ b/libraries/entities/src/BoxEntityItem.h @@ -46,11 +46,13 @@ public: void setColor(const rgbColor& value) { memcpy(_color, value, sizeof(_color)); } void setColor(const xColor& value) { - _color[RED_INDEX] = value.red; - _color[GREEN_INDEX] = value.green; - _color[BLUE_INDEX] = value.blue; + _color[RED_INDEX] = value.red; + _color[GREEN_INDEX] = value.green; + _color[BLUE_INDEX] = value.blue; } + void computeShapeInfo(ShapeInfo& info) const; + protected: rgbColor _color; }; diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 348efac9a3..d0b019467c 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -92,6 +92,7 @@ EntityItem::EntityItem(const EntityItemID& entityItemID) { _lastEditedFromRemote = 0; _lastEditedFromRemoteInRemoteTime = 0; _created = UNKNOWN_CREATED_TIME; + _physicsInfo = NULL; _dirtyFlags = 0; _changedOnServer = 0; initFromEntityItemID(entityItemID); @@ -106,12 +107,18 @@ EntityItem::EntityItem(const EntityItemID& entityItemID, const EntityItemPropert _lastEditedFromRemote = 0; _lastEditedFromRemoteInRemoteTime = 0; _created = UNKNOWN_CREATED_TIME; + _physicsInfo = NULL; _dirtyFlags = 0; _changedOnServer = 0; initFromEntityItemID(entityItemID); setProperties(properties); } +EntityItem::~EntityItem() { + // be sure to clean up _physicsInfo before calling this dtor + assert(_physicsInfo == NULL); +} + EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties; @@ -138,7 +145,6 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packetData, EncodeBitstreamParams& params, EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData) const { - // ALL this fits... // object ID [16 bytes] // ByteCountCoded(type code) [~1 byte] @@ -516,7 +522,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef READ_ENTITY_PROPERTY_SETTER(PROP_GRAVITY, glm::vec3, updateGravity); READ_ENTITY_PROPERTY(PROP_DAMPING, float, _damping); READ_ENTITY_PROPERTY_SETTER(PROP_LIFETIME, float, updateLifetime); - READ_ENTITY_PROPERTY_STRING(PROP_SCRIPT,setScript); + READ_ENTITY_PROPERTY_STRING(PROP_SCRIPT, setScript); READ_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, glm::vec3, _registrationPoint); READ_ENTITY_PROPERTY_SETTER(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocity); READ_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, float, _angularDamping); @@ -583,6 +589,11 @@ bool EntityItem::isRestingOnSurface() const { } void EntityItem::simulate(const quint64& now) { + if (_physicsInfo) { + // we rely on bullet for simulation, so bail + return; + } + bool wantDebug = false; if (_lastSimulated == 0) { @@ -795,7 +806,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(gravity, updateGravityInMeters); SET_ENTITY_PROPERTY_FROM_PROPERTIES(damping, setDamping); SET_ENTITY_PROPERTY_FROM_PROPERTIES(lifetime, updateLifetime); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(script, updateScript); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(script, setScript); SET_ENTITY_PROPERTY_FROM_PROPERTIES(registrationPoint, setRegistrationPoint); SET_ENTITY_PROPERTY_FROM_PROPERTIES(angularVelocity, updateAngularVelocity); SET_ENTITY_PROPERTY_FROM_PROPERTIES(angularDamping, setAngularDamping); @@ -977,15 +988,29 @@ float EntityItem::getRadius() const { return radius; } +void EntityItem::computeShapeInfo(ShapeInfo& info) const { + info.clear(); +} + void EntityItem::recalculateCollisionShape() { AACube entityAACube = getMinimumAACube(); entityAACube.scale(TREE_SCALE); // scale to meters _collisionShape.setTranslation(entityAACube.calcCenter()); _collisionShape.setScale(entityAACube.getScale()); + // TODO: use motionState to update physics object } +const float MIN_POSITION_DELTA = 0.0001f; +const float MIN_DIMENSION_DELTA = 0.0001f; +const float MIN_ALIGNMENT_DOT = 0.9999f; +const float MIN_MASS_DELTA = 0.001f; +const float MIN_VELOCITY_DELTA = 0.025f; +const float MIN_GRAVITY_DELTA = 0.001f; +const float MIN_SPIN_DELTA = 0.0003f; + void EntityItem::updatePosition(const glm::vec3& value) { - if (_position != value) { + glm::vec3 debugPosition = value * (float) TREE_SCALE; + if (glm::distance(_position, value) * (float)TREE_SCALE > MIN_POSITION_DELTA) { _position = value; recalculateCollisionShape(); _dirtyFlags |= EntityItem::DIRTY_POSITION; @@ -994,7 +1019,7 @@ void EntityItem::updatePosition(const glm::vec3& value) { void EntityItem::updatePositionInMeters(const glm::vec3& value) { glm::vec3 position = glm::clamp(value / (float) TREE_SCALE, 0.0f, 1.0f); - if (_position != position) { + if (glm::distance(_position, position) * (float)TREE_SCALE > MIN_POSITION_DELTA) { _position = position; recalculateCollisionShape(); _dirtyFlags |= EntityItem::DIRTY_POSITION; @@ -1002,24 +1027,24 @@ void EntityItem::updatePositionInMeters(const glm::vec3& value) { } void EntityItem::updateDimensions(const glm::vec3& value) { - if (_dimensions != value) { + if (glm::distance(_dimensions, value) * (float)TREE_SCALE > MIN_DIMENSION_DELTA) { _dimensions = value; recalculateCollisionShape(); - _dirtyFlags |= EntityItem::DIRTY_SHAPE; + _dirtyFlags |= (EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_MASS); } } void EntityItem::updateDimensionsInMeters(const glm::vec3& value) { glm::vec3 dimensions = value / (float) TREE_SCALE; - if (_dimensions != dimensions) { + if (glm::distance(_dimensions, dimensions) * (float)TREE_SCALE > MIN_DIMENSION_DELTA) { _dimensions = dimensions; recalculateCollisionShape(); - _dirtyFlags |= EntityItem::DIRTY_SHAPE; + _dirtyFlags |= (EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_MASS); } } void EntityItem::updateRotation(const glm::quat& rotation) { - if (_rotation != rotation) { + if (glm::dot(_rotation, rotation) < MIN_ALIGNMENT_DOT) { _rotation = rotation; recalculateCollisionShape(); _dirtyFlags |= EntityItem::DIRTY_POSITION; @@ -1027,29 +1052,37 @@ void EntityItem::updateRotation(const glm::quat& rotation) { } void EntityItem::updateMass(float value) { - if (_mass != value) { + if (fabsf(_mass - value) > MIN_MASS_DELTA) { _mass = value; _dirtyFlags |= EntityItem::DIRTY_MASS; } } -void EntityItem::updateVelocity(const glm::vec3& value) { - if (_velocity != value) { - _velocity = value; +void EntityItem::updateVelocity(const glm::vec3& value) { + if (glm::distance(_velocity, value) * (float)TREE_SCALE > MIN_VELOCITY_DELTA) { + if (glm::length(value) * (float)TREE_SCALE < MIN_VELOCITY_DELTA) { + _velocity = glm::vec3(0.0f); + } else { + _velocity = value; + } _dirtyFlags |= EntityItem::DIRTY_VELOCITY; } } void EntityItem::updateVelocityInMeters(const glm::vec3& value) { glm::vec3 velocity = value / (float) TREE_SCALE; - if (_velocity != velocity) { - _velocity = velocity; + if (glm::distance(_velocity, velocity) * (float)TREE_SCALE > MIN_VELOCITY_DELTA) { + if (glm::length(value) < MIN_VELOCITY_DELTA) { + _velocity = glm::vec3(0.0f); + } else { + _velocity = velocity; + } _dirtyFlags |= EntityItem::DIRTY_VELOCITY; } } void EntityItem::updateGravity(const glm::vec3& value) { - if (_gravity != value) { + if (glm::distance(_gravity, value) * (float)TREE_SCALE > MIN_GRAVITY_DELTA) { _gravity = value; _dirtyFlags |= EntityItem::DIRTY_VELOCITY; } @@ -1057,14 +1090,14 @@ void EntityItem::updateGravity(const glm::vec3& value) { void EntityItem::updateGravityInMeters(const glm::vec3& value) { glm::vec3 gravity = value / (float) TREE_SCALE; - if (_gravity != gravity) { + if ( glm::distance(_gravity, gravity) * (float)TREE_SCALE > MIN_GRAVITY_DELTA) { _gravity = gravity; _dirtyFlags |= EntityItem::DIRTY_VELOCITY; } } void EntityItem::updateAngularVelocity(const glm::vec3& value) { - if (_angularVelocity != value) { + if (glm::distance(_angularVelocity, value) > MIN_SPIN_DELTA) { _angularVelocity = value; _dirtyFlags |= EntityItem::DIRTY_VELOCITY; } @@ -1091,10 +1124,3 @@ void EntityItem::updateLifetime(float value) { } } -void EntityItem::updateScript(const QString& value) { - if (_script != value) { - _script = value; - _dirtyFlags |= EntityItem::DIRTY_SCRIPT; - } -} - diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 0d0aa96706..7acb55e8c3 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -22,6 +22,7 @@ #include // for EncodeBitstreamParams class #include // for OctreeElement::AppendState #include +#include #include #include "EntityItemID.h" @@ -50,16 +51,13 @@ public: DIRTY_SHAPE = 0x0020, DIRTY_LIFETIME = 0x0040, DIRTY_UPDATEABLE = 0x0080, - // add new simulation-relevant flags above - // all other flags below - DIRTY_SCRIPT = 0x8000 }; DONT_ALLOW_INSTANTIATION // This class can not be instantiated directly EntityItem(const EntityItemID& entityItemID); EntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties); - virtual ~EntityItem() { } + virtual ~EntityItem(); // ID and EntityItemID related methods QUuid getID() const { return _id; } @@ -276,6 +274,7 @@ public: void applyHardCollision(const CollisionInfo& collisionInfo); virtual const Shape& getCollisionShapeInMeters() const { return _collisionShape; } virtual bool contains(const glm::vec3& point) const { return getAABox().contains(point); } + virtual void computeShapeInfo(ShapeInfo& info) const; // updateFoo() methods to be used when changes need to be accumulated in the _dirtyFlags void updatePosition(const glm::vec3& value); @@ -292,13 +291,15 @@ public: void updateIgnoreForCollisions(bool value); void updateCollisionsWillMove(bool value); void updateLifetime(float value); - void updateScript(const QString& value); uint32_t getDirtyFlags() const { return _dirtyFlags; } void clearDirtyFlags(uint32_t mask = 0xffff) { _dirtyFlags &= ~mask; } bool isMoving() const; + void* getPhysicsInfo() const { return _physicsInfo; } + void setPhysicsInfo(void* data) { _physicsInfo = data; } + protected: virtual void initFromEntityItemID(const EntityItemID& entityItemID); // maybe useful to allow subclasses to init @@ -310,9 +311,9 @@ protected: bool _newlyCreated; quint64 _lastSimulated; // last time this entity called simulate() quint64 _lastUpdated; // last time this entity called update() - quint64 _lastEdited; // this is the last official local or remote edit time - quint64 _lastEditedFromRemote; // this is the last time we received and edit from the server - quint64 _lastEditedFromRemoteInRemoteTime; // time in server time space the last time we received and edit from the server + quint64 _lastEdited; // last official local or remote edit time + quint64 _lastEditedFromRemote; // last time we received and edit from the server + quint64 _lastEditedFromRemoteInRemoteTime; // last time we received and edit from the server (in server-time-frame) quint64 _created; quint64 _changedOnServer; @@ -344,6 +345,10 @@ protected: AACubeShape _collisionShape; + // _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; // 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 }; diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index b843c85327..f3424aee43 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -134,6 +134,13 @@ public: void debugDump() const; void setLastEdited(quint64 usecTime); + // Note: DEFINE_PROPERTY(PROP_FOO, Foo, foo, type) creates the following methods and variables: + // type getFoo() const; + // void setFoo(type); + // bool fooChanged() const; + // type _foo; + // bool _fooChanged; + DEFINE_PROPERTY(PROP_VISIBLE, Visible, visible, bool); DEFINE_PROPERTY_REF_WITH_SETTER(PROP_POSITION, Position, position, glm::vec3); DEFINE_PROPERTY_REF(PROP_DIMENSIONS, Dimensions, dimensions, glm::vec3); diff --git a/libraries/entities/src/EntitySimulation.cpp b/libraries/entities/src/EntitySimulation.cpp index 6d3b81715d..09f75c9ebf 100644 --- a/libraries/entities/src/EntitySimulation.cpp +++ b/libraries/entities/src/EntitySimulation.cpp @@ -101,12 +101,13 @@ void EntitySimulation::sortEntitiesThatMoved() { } ++itemItr; } - _entitiesToBeSorted.clear(); - if (moveOperator.hasMovingEntities()) { PerformanceTimer perfTimer("recurseTreeWithOperator"); _entityTree->recurseTreeWithOperator(&moveOperator); } + + sortEntitiesThatMovedInternal(); + _entitiesToBeSorted.clear(); } void EntitySimulation::addEntity(EntityItem* entity) { @@ -122,6 +123,10 @@ void EntitySimulation::addEntity(EntityItem* entity) { _updateableEntities.insert(entity); } addEntityInternal(entity); + + // 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(); } void EntitySimulation::removeEntity(EntityItem* entity) { @@ -173,7 +178,6 @@ void EntitySimulation::entityChanged(EntityItem* entity) { } entityChangedInternal(entity); } - entity->clearDirtyFlags(); } void EntitySimulation::clearEntities() { diff --git a/libraries/entities/src/EntitySimulation.h b/libraries/entities/src/EntitySimulation.h index 506e2ed9d0..a32a1226c7 100644 --- a/libraries/entities/src/EntitySimulation.h +++ b/libraries/entities/src/EntitySimulation.h @@ -16,8 +16,21 @@ #include +#include "EntityItem.h" #include "EntityTree.h" +// 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_MASS | + EntityItem::DIRTY_COLLISION_GROUP | + EntityItem::DIRTY_MOTION_TYPE | + EntityItem::DIRTY_SHAPE | + EntityItem::DIRTY_LIFETIME | + EntityItem::DIRTY_UPDATEABLE; + class EntitySimulation { public: EntitySimulation() : _entityTree(NULL) { } @@ -49,10 +62,18 @@ 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 removeEntityInternal(EntityItem* entity) = 0; + virtual void entityChangedInternal(EntityItem* entity) = 0; + + virtual void sortEntitiesThatMovedInternal() {} + virtual void clearEntitiesInternal() = 0; void expireMortalEntities(const quint64& now); diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 3580e64b35..73d944b084 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -132,19 +132,21 @@ bool EntityTree::updateEntityWithElement(EntityItem* entity, const EntityItemPro } } } else { - QString entityScriptBefore = entity->getScript(); - + uint32_t preFlags = entity->getDirtyFlags(); UpdateEntityOperator theOperator(this, containingElement, entity, properties); recurseTreeWithOperator(&theOperator); _isDirty = true; - if (_simulation && entity->getDirtyFlags() != 0) { - _simulation->entityChanged(entity); - } - - QString entityScriptAfter = entity->getScript(); - if (entityScriptBefore != entityScriptAfter) { - emit entityScriptChanging(entity->getEntityItemID()); // the entity script has changed + uint32_t newFlags = entity->getDirtyFlags() & ~preFlags; + if (newFlags) { + if (_simulation) { + if (newFlags & DIRTY_SIMULATION_FLAGS) { + _simulation->entityChanged(entity); + } + } else { + // normally the _simulation clears ALL updateFlags, but since there is none we do it explicitly + entity->clearDirtyFlags(); + } } } diff --git a/libraries/entities/src/SimpleEntitySimulation.cpp b/libraries/entities/src/SimpleEntitySimulation.cpp index 6fd2d7c48c..6d45768c26 100644 --- a/libraries/entities/src/SimpleEntitySimulation.cpp +++ b/libraries/entities/src/SimpleEntitySimulation.cpp @@ -14,7 +14,6 @@ #include "EntityItem.h" #include "SimpleEntitySimulation.h" - void SimpleEntitySimulation::updateEntitiesInternal(const quint64& now) { QSet::iterator itemItr = _movingEntities.begin(); while (itemItr != _movingEntities.end()) { @@ -57,6 +56,7 @@ void SimpleEntitySimulation::entityChangedInternal(EntityItem* entity) { _movableButStoppedEntities.remove(entity); } } + entity->clearDirtyFlags(); } void SimpleEntitySimulation::clearEntitiesInternal() { diff --git a/libraries/entities/src/SphereEntityItem.cpp b/libraries/entities/src/SphereEntityItem.cpp index 06035f6b6e..9a9c07ef9a 100644 --- a/libraries/entities/src/SphereEntityItem.cpp +++ b/libraries/entities/src/SphereEntityItem.cpp @@ -97,6 +97,12 @@ void SphereEntityItem::recalculateCollisionShape() { _sphereShape.setRadius(largestDiameter / 2.0f); } +void SphereEntityItem::computeShapeInfo(ShapeInfo& info) const { + glm::vec3 halfExtents = 0.5f * getDimensionsInMeters(); + // TODO: support ellipsoid shapes + info.setSphere(halfExtents.x); +} + bool SphereEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face, void** intersectedObject, bool precisionPicking) const { @@ -122,6 +128,3 @@ bool SphereEntityItem::findDetailedRayIntersection(const glm::vec3& origin, cons } return false; } - - - diff --git a/libraries/entities/src/SphereEntityItem.h b/libraries/entities/src/SphereEntityItem.h index 3b7dccaff9..c81f80d9ab 100644 --- a/libraries/entities/src/SphereEntityItem.h +++ b/libraries/entities/src/SphereEntityItem.h @@ -55,6 +55,8 @@ public: // TODO: implement proper contains for 3D ellipsoid //virtual bool contains(const glm::vec3& point) const; + + void computeShapeInfo(ShapeInfo& info) const; virtual bool supportsDetailedRayIntersection() const { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, diff --git a/libraries/octree/CMakeLists.txt b/libraries/octree/CMakeLists.txt index cd90857637..f4ac2263c2 100644 --- a/libraries/octree/CMakeLists.txt +++ b/libraries/octree/CMakeLists.txt @@ -5,7 +5,7 @@ setup_hifi_library() include_glm() -link_hifi_libraries(shared networking physics) +link_hifi_libraries(shared networking) # find ZLIB find_package(ZLIB REQUIRED) diff --git a/libraries/physics/CMakeLists.txt b/libraries/physics/CMakeLists.txt index 4f74038ff5..416ffa49f1 100644 --- a/libraries/physics/CMakeLists.txt +++ b/libraries/physics/CMakeLists.txt @@ -4,16 +4,13 @@ set(TARGET_NAME physics) setup_hifi_library() include_glm() +include_bullet() +if (BULLET_FOUND) + target_link_libraries(${TARGET_NAME} ${BULLET_LIBRARIES}) +endif (BULLET_FOUND) -link_hifi_libraries(shared) - -## find BULLET -#find_package(BULLET REQUIRED) -# -#include_directories(SYSTEM "${BULLET_INCLUDE_DIRS}") -# -## append BULLET to our list of libraries to link -#list(APPEND ${TARGET_NAME}_LIBRARIES_TO_LINK "${BULLET_LIBRARIES}") +link_hifi_libraries(shared fbx entities) +include_hifi_library_headers(fbx) # call macro to include our dependency includes and bubble them up via a property on our target include_dependency_includes() diff --git a/libraries/physics/src/BulletUtil.h b/libraries/physics/src/BulletUtil.h new file mode 100644 index 0000000000..78524a122e --- /dev/null +++ b/libraries/physics/src/BulletUtil.h @@ -0,0 +1,38 @@ +// +// BulletUtil.h +// libraries/physcis/src +// +// Created by Andrew Meadows 2014.11.02 +// Copyright 2014 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_BulletUtil_h +#define hifi_BulletUtil_h + +#ifdef USE_BULLET_PHYSICS + +#include +#include +#include + +inline glm::vec3 bulletToGLM(const btVector3& b) { + return glm::vec3(b.getX(), b.getY(), b.getZ()); +} + +inline glm::quat bulletToGLM(const btQuaternion& b) { + return glm::quat(b.getW(), b.getX(), b.getY(), b.getZ()); +} + +inline btVector3 glmToBullet(const glm::vec3& g) { + return btVector3(g.x, g.y, g.z); +} + +inline btQuaternion glmToBullet(const glm::quat& g) { + return btQuaternion(g.x, g.y, g.z, g.w); +} + +#endif // USE_BULLET_PHYSICS +#endif // hifi_BulletUtil_h diff --git a/libraries/physics/src/Constraint.h b/libraries/physics/src/Constraint.h index 9bbdc185e1..ed97d6cc73 100644 --- a/libraries/physics/src/Constraint.h +++ b/libraries/physics/src/Constraint.h @@ -1,6 +1,6 @@ // // Constraint.h -// libraries/shared/src +// libraries/physics/src // // Created by Andrew Meadows 2014.07.24 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/ContactConstraint.cpp b/libraries/physics/src/ContactConstraint.cpp index 9d1a92bb21..9734922a52 100644 --- a/libraries/physics/src/ContactConstraint.cpp +++ b/libraries/physics/src/ContactConstraint.cpp @@ -1,6 +1,6 @@ // // ContactConstraint.cpp -// interface/src/avatar +// libraries/physcis/src // // Created by Andrew Meadows 2014.07.24 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/ContactConstraint.h b/libraries/physics/src/ContactConstraint.h index 41be2f769d..44c9c1b879 100644 --- a/libraries/physics/src/ContactConstraint.h +++ b/libraries/physics/src/ContactConstraint.h @@ -1,6 +1,6 @@ // // ContactConstraint.h -// interface/src/avatar +// libraries/physcis/src // // Created by Andrew Meadows 2014.07.24 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/ContactPoint.cpp b/libraries/physics/src/ContactPoint.cpp index b9ad87aa8f..b949f12582 100644 --- a/libraries/physics/src/ContactPoint.cpp +++ b/libraries/physics/src/ContactPoint.cpp @@ -1,6 +1,6 @@ // // ContactPoint.cpp -// libraries/shared/src +// libraries/physics/src // // Created by Andrew Meadows 2014.07.30 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/ContactPoint.h b/libraries/physics/src/ContactPoint.h index d584945970..c2443f361f 100644 --- a/libraries/physics/src/ContactPoint.h +++ b/libraries/physics/src/ContactPoint.h @@ -1,6 +1,6 @@ // // ContactPoint.h -// libraries/shared/src +// libraries/physics/src // // Created by Andrew Meadows 2014.07.30 // Copyright 2014 High Fidelity, Inc. @@ -15,7 +15,8 @@ #include #include -#include "CollisionInfo.h" +#include + #include "VerletPoint.h" class Shape; diff --git a/libraries/physics/src/DistanceConstraint.cpp b/libraries/physics/src/DistanceConstraint.cpp index 94dbfeba24..d513c4785b 100644 --- a/libraries/physics/src/DistanceConstraint.cpp +++ b/libraries/physics/src/DistanceConstraint.cpp @@ -1,6 +1,6 @@ // // DistanceConstraint.cpp -// libraries/shared/src +// libraries/physics/src // // Created by Andrew Meadows 2014.07.24 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/DistanceConstraint.h b/libraries/physics/src/DistanceConstraint.h index c588807178..8c2dd38102 100644 --- a/libraries/physics/src/DistanceConstraint.h +++ b/libraries/physics/src/DistanceConstraint.h @@ -1,6 +1,6 @@ // // DistanceConstraint.h -// libraries/shared/src +// libraries/physics/src // // Created by Andrew Meadows 2014.07.24 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/DoubleHashKey.cpp b/libraries/physics/src/DoubleHashKey.cpp new file mode 100644 index 0000000000..fae1d715fb --- /dev/null +++ b/libraries/physics/src/DoubleHashKey.cpp @@ -0,0 +1,43 @@ +// +// DoubleHashKey.cpp +// libraries/physcis/src +// +// Created by Andrew Meadows 2014.11.02 +// Copyright 2014 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 "DoubleHashKey.h" + +const int NUM_PRIMES = 64; +const unsigned int PRIMES[] = { + 4194301U, 4194287U, 4194277U, 4194271U, 4194247U, 4194217U, 4194199U, 4194191U, + 4194187U, 4194181U, 4194173U, 4194167U, 4194143U, 4194137U, 4194131U, 4194107U, + 4194103U, 4194023U, 4194011U, 4194007U, 4193977U, 4193971U, 4193963U, 4193957U, + 4193939U, 4193929U, 4193909U, 4193869U, 4193807U, 4193803U, 4193801U, 4193789U, + 4193759U, 4193753U, 4193743U, 4193701U, 4193663U, 4193633U, 4193573U, 4193569U, + 4193551U, 4193549U, 4193531U, 4193513U, 4193507U, 4193459U, 4193447U, 4193443U, + 4193417U, 4193411U, 4193393U, 4193389U, 4193381U, 4193377U, 4193369U, 4193359U, + 4193353U, 4193327U, 4193309U, 4193303U, 4193297U, 4193279U, 4193269U, 4193263U +}; + +unsigned int DoubleHashKey::hashFunction(unsigned int value, int primeIndex) { + unsigned int hash = PRIMES[primeIndex % NUM_PRIMES] * (value + 1U); + hash += ~(hash << 15); + hash ^= (hash >> 10); + hash += (hash << 3); + hash ^= (hash >> 6); + hash += ~(hash << 11); + return hash ^ (hash >> 16); +} + +unsigned int DoubleHashKey::hashFunction2(unsigned int value) { + unsigned hash = 0x811c9dc5U; + for (int i = 0; i < 4; i++ ) { + unsigned int byte = (value << (i * 8)) >> (24 - i * 8); + hash = ( hash ^ byte ) * 0x01000193U; + } + return hash; +} diff --git a/libraries/physics/src/DoubleHashKey.h b/libraries/physics/src/DoubleHashKey.h new file mode 100644 index 0000000000..ebacf6c96a --- /dev/null +++ b/libraries/physics/src/DoubleHashKey.h @@ -0,0 +1,38 @@ +// +// DoubleHashKey.h +// libraries/physcis/src +// +// Created by Andrew Meadows 2014.11.02 +// Copyright 2014 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_DoubleHashKey_h +#define hifi_DoubleHashKey_h + +// DoubleHashKey for use with btHashMap +class DoubleHashKey { +public: + static unsigned int hashFunction(unsigned int value, int primeIndex); + static unsigned int hashFunction2(unsigned int value); + + DoubleHashKey() : _hash(0), _hash2(0) { } + + DoubleHashKey(unsigned int value, int primeIndex = 0) : + _hash(hashFunction(value, primeIndex)), + _hash2(hashFunction2(value)) { + } + + bool equals(const DoubleHashKey& other) const { + return _hash == other._hash && _hash2 == other._hash2; + } + + unsigned int getHash() const { return (unsigned int)_hash; } + + int _hash; + int _hash2; +}; + +#endif // hifi_DoubleHashKey_h diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp new file mode 100644 index 0000000000..6e26f2b9de --- /dev/null +++ b/libraries/physics/src/EntityMotionState.cpp @@ -0,0 +1,172 @@ +// +// EntityMotionState.cpp +// libraries/entities/src +// +// Created by Andrew Meadows on 2014.11.06 +// Copyright 2013 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 +#include + +#ifdef USE_BULLET_PHYSICS +#include "BulletUtil.h" +#endif // USE_BULLET_PHYSICS +#include "EntityMotionState.h" + +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) + : _entity(entity) { + assert(entity != NULL); +} + +EntityMotionState::~EntityMotionState() { + assert(_entity); + _entity->setPhysicsInfo(NULL); + _entity = NULL; +} + +MotionType EntityMotionState::computeMotionType() const { + // HACK: According to EntityTree the meaning of "static" is "not moving" whereas + // to Bullet it means "can't move". For demo purposes we temporarily interpret + // Entity::weightless to mean Bullet::static. + return _entity->getCollisionsWillMove() ? MOTION_TYPE_DYNAMIC : MOTION_TYPE_STATIC; +} + +#ifdef USE_BULLET_PHYSICS +// This callback is invoked by the physics simulation in two cases: +// (1) when the RigidBody is first added to the world +// (irregardless of MotionType: STATIC, DYNAMIC, or KINEMATIC) +// (2) at the beginning of each simulation frame for KINEMATIC RigidBody's -- +// it is an opportunity for outside code to update the object's simulation position +void EntityMotionState::getWorldTransform(btTransform& worldTrans) const { + worldTrans.setOrigin(glmToBullet(_entity->getPositionInMeters() - ObjectMotionState::getWorldOffset())); + worldTrans.setRotation(glmToBullet(_entity->getRotation())); +} + +// This callback is invoked by the physics simulation at the end of each simulation frame... +// iff the corresponding RigidBody is DYNAMIC and has moved. +void EntityMotionState::setWorldTransform(const btTransform& worldTrans) { + _entity->setPositionInMeters(bulletToGLM(worldTrans.getOrigin()) + ObjectMotionState::getWorldOffset()); + _entity->setRotation(bulletToGLM(worldTrans.getRotation())); + + glm::vec3 v; + getVelocity(v); + _entity->setVelocityInMeters(v); + + getAngularVelocity(v); + _entity->setAngularVelocity(v); + + _outgoingPacketFlags = DIRTY_PHYSICS_FLAGS; + EntityMotionState::enqueueOutgoingEntity(_entity); +} +#endif // USE_BULLET_PHYSICS + +void EntityMotionState::applyVelocities() const { +#ifdef USE_BULLET_PHYSICS + if (_body) { + setVelocity(_entity->getVelocityInMeters()); + setAngularVelocity(_entity->getAngularVelocity()); + _body->setActivationState(ACTIVE_TAG); + } +#endif // USE_BULLET_PHYSICS +} + +void EntityMotionState::applyGravity() const { +#ifdef USE_BULLET_PHYSICS + if (_body) { + setGravity(_entity->getGravityInMeters()); + _body->setActivationState(ACTIVE_TAG); + } +#endif // USE_BULLET_PHYSICS +} + +void EntityMotionState::computeShapeInfo(ShapeInfo& info) { + _entity->computeShapeInfo(info); +} + +void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_t frame) { +#ifdef USE_BULLET_PHYSICS + if (_outgoingPacketFlags) { + EntityItemProperties properties = _entity->getProperties(); + + 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); + } + + 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 = 4.0e-6f; // 2mm/sec + bool 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 + bool 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); + _sentAcceleration = bulletToGLM(_body->getGravity()); + properties.setGravity(_sentAcceleration); + properties.setAngularVelocity(_sentAngularVelocity); + } + + // 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, nore when we resend non-moving data) + quint64 lastSimulated = _entity->getLastSimulated(); + _entity->setLastEdited(lastSimulated); + properties.setLastEdited(lastSimulated); + } else { + properties.setLastEdited(_entity->getLastEdited()); + } + + EntityItemID id(_entity->getID()); + EntityEditPacketSender* entityPacketSender = static_cast(packetSender); + entityPacketSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, id, properties); + + // 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; + _sentFrame = frame; + } +#endif // USE_BULLET_PHYSICS +} diff --git a/libraries/physics/src/EntityMotionState.h b/libraries/physics/src/EntityMotionState.h new file mode 100644 index 0000000000..379470087f --- /dev/null +++ b/libraries/physics/src/EntityMotionState.h @@ -0,0 +1,71 @@ +// +// EntityMotionState.h +// libraries/entities/src +// +// Created by Andrew Meadows on 2014.11.06 +// Copyright 2013 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_EntityMotionState_h +#define hifi_EntityMotionState_h + +#include + +#include "ObjectMotionState.h" +#ifndef USE_BULLET_PHYSICS +// ObjectMotionState stubbery +class ObjectMotionState { +public: + // so that this stub implementation is not completely empty we give the class a data member + bool _stubData; +}; +#endif // USE_BULLET_PHYSICS + +class EntityItem; + +// From the MotionState's perspective: +// Inside = physics simulation +// Outside = external agents (scripts, user interaction, other simulations) + +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); + virtual ~EntityMotionState(); + + /// \return MOTION_TYPE_DYNAMIC or MOTION_TYPE_STATIC based on params set in EntityItem + MotionType computeMotionType() const; + +#ifdef USE_BULLET_PHYSICS + // this relays incoming position/rotation to the RigidBody + void getWorldTransform(btTransform& worldTrans) const; + + // this relays outgoing position/rotation to the EntityItem + void setWorldTransform(const btTransform& worldTrans); +#endif // USE_BULLET_PHYSICS + + // these relay incoming values to the RigidBody + void applyVelocities() const; + void applyGravity() const; + + void computeShapeInfo(ShapeInfo& info); + + void sendUpdate(OctreeEditPacketSender* packetSender, uint32_t frame); + + uint32_t getIncomingDirtyFlags() const { return _entity->getDirtyFlags(); } + void clearIncomingDirtyFlags(uint32_t flags) { _entity->clearDirtyFlags(flags); } + +protected: + EntityItem* _entity; +}; + +#endif // hifi_EntityMotionState_h diff --git a/libraries/physics/src/FixedConstraint.cpp b/libraries/physics/src/FixedConstraint.cpp index 8f1edc1fb5..23f1749fa9 100644 --- a/libraries/physics/src/FixedConstraint.cpp +++ b/libraries/physics/src/FixedConstraint.cpp @@ -1,6 +1,6 @@ // // FixedConstraint.cpp -// libraries/shared/src +// libraries/physics/src // // Created by Andrew Meadows 2014.07.24 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/FixedConstraint.h b/libraries/physics/src/FixedConstraint.h index 66a27369ce..067b9988cd 100644 --- a/libraries/physics/src/FixedConstraint.h +++ b/libraries/physics/src/FixedConstraint.h @@ -1,6 +1,6 @@ // // FixedConstraint.h -// libraries/shared/src +// libraries/physics/src // // Created by Andrew Meadows 2014.07.24 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/ObjectMotionState.cpp b/libraries/physics/src/ObjectMotionState.cpp new file mode 100644 index 0000000000..ded2291229 --- /dev/null +++ b/libraries/physics/src/ObjectMotionState.cpp @@ -0,0 +1,173 @@ +// +// ObjectMotionState.cpp +// libraries/physcis/src +// +// Created by Andrew Meadows 2014.11.05 +// Copyright 2014 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 +// + +#ifdef USE_BULLET_PHYSICS + +#include + +#include "BulletUtil.h" +#include "ObjectMotionState.h" + +const float MIN_DENSITY = 200.0f; +const float DEFAULT_DENSITY = 1000.0f; +const float MAX_DENSITY = 20000.0f; + +const float MIN_VOLUME = 0.001f; +const float DEFAULT_VOLUME = 1.0f; +const float MAX_VOLUME = 1000000.0f; + +const float DEFAULT_FRICTION = 0.5f; +const float MAX_FRICTION = 10.0f; + +const float DEFAULT_RESTITUTION = 0.0f; + +// origin of physics simulation in world frame +glm::vec3 _worldOffset(0.0f); + +// static +void ObjectMotionState::setWorldOffset(const glm::vec3& offset) { + _worldOffset = offset; +} + +// static +const glm::vec3& ObjectMotionState::getWorldOffset() { + return _worldOffset; +} + + +ObjectMotionState::ObjectMotionState() : + _density(DEFAULT_DENSITY), + _volume(DEFAULT_VOLUME), + _friction(DEFAULT_FRICTION), + _restitution(DEFAULT_RESTITUTION), + _wasInWorld(false), + _motionType(MOTION_TYPE_STATIC), + _body(NULL), + _sentMoving(false), + _numNonMovingUpdates(0), + _outgoingPacketFlags(DIRTY_PHYSICS_FLAGS), + _sentFrame(0), + _sentPosition(0.0f), + _sentRotation(), + _sentVelocity(0.0f), + _sentAngularVelocity(0.0f), + _sentAcceleration(0.0f) { +} + +ObjectMotionState::~ObjectMotionState() { + // NOTE: you MUST remove this MotionState from the world before you call the dtor. + assert(_body == NULL); +} + +void ObjectMotionState::setDensity(float density) { + _density = btMax(btMin(fabsf(density), MAX_DENSITY), MIN_DENSITY); +} + +void ObjectMotionState::setFriction(float friction) { + _friction = btMax(btMin(fabsf(friction), MAX_FRICTION), 0.0f); +} + +void ObjectMotionState::setRestitution(float restitution) { + _restitution = btMax(btMin(fabsf(restitution), 1.0f), 0.0f); +} + +void ObjectMotionState::setVolume(float volume) { + _volume = btMax(btMin(fabsf(volume), MAX_VOLUME), MIN_VOLUME); +} + +void ObjectMotionState::setVelocity(const glm::vec3& velocity) const { + _body->setLinearVelocity(glmToBullet(velocity)); +} + +void ObjectMotionState::setAngularVelocity(const glm::vec3& velocity) const { + _body->setAngularVelocity(glmToBullet(velocity)); +} + +void ObjectMotionState::setGravity(const glm::vec3& gravity) const { + _body->setGravity(glmToBullet(gravity)); +} + +void ObjectMotionState::getVelocity(glm::vec3& velocityOut) const { + velocityOut = bulletToGLM(_body->getLinearVelocity()); +} + +void ObjectMotionState::getAngularVelocity(glm::vec3& angularVelocityOut) const { + angularVelocityOut = 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; +} + +const float FIXED_SUBSTEP = 1.0f / 60.0f; + +bool ObjectMotionState::shouldSendUpdate(uint32_t simulationFrame, float subStepRemainder) const { + assert(_body); + float dt = (float)(simulationFrame - _sentFrame) * FIXED_SUBSTEP + subStepRemainder; + bool isActive = _body->isActive(); + + if (isActive) { + const float MAX_UPDATE_PERIOD_FOR_ACTIVE_THINGS = 10.0f; + if (dt > MAX_UPDATE_PERIOD_FOR_ACTIVE_THINGS) { + return true; + } + } else if (_sentMoving) { + if (!isActive) { + // 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 in done the simulation-frame, which is NOT necessarily the same as the world-frame + // due to _worldOffset. + + // TODO: Andrew to reconcile Bullet and legacy damping coefficients. + + // compute position error + glm::vec3 extrapolatedPosition = _sentPosition + dt * (_sentVelocity + (0.5f * dt) * _sentAcceleration); + + btTransform worldTrans = _body->getWorldTransform(); + glm::vec3 position = bulletToGLM(worldTrans.getOrigin()); + + float dx2 = glm::distance2(position, extrapolatedPosition); + const float MAX_POSITION_ERROR_SQUARED = 0.001f; // 0.001 m^2 ~~> 0.03 m + if (dx2 > MAX_POSITION_ERROR_SQUARED) { + return true; + } + + // compute rotation error + float spin = glm::length(_sentAngularVelocity); + glm::quat extrapolatedRotation = _sentRotation; + const float MIN_SPIN = 1.0e-4f; + if (spin > MIN_SPIN) { + glm::vec3 axis = _sentAngularVelocity / spin; + extrapolatedRotation = glm::angleAxis(dt * spin, axis) * _sentRotation; + } + const float MIN_ROTATION_DOT = 0.98f; + glm::quat actualRotation = bulletToGLM(worldTrans.getRotation()); + return (glm::dot(actualRotation, extrapolatedRotation) < MIN_ROTATION_DOT); +} + +#endif // USE_BULLET_PHYSICS diff --git a/libraries/physics/src/ObjectMotionState.h b/libraries/physics/src/ObjectMotionState.h new file mode 100644 index 0000000000..ea9dcad0fc --- /dev/null +++ b/libraries/physics/src/ObjectMotionState.h @@ -0,0 +1,115 @@ +// +// ObjectMotionState.h +// libraries/physcis/src +// +// Created by Andrew Meadows 2014.11.05 +// Copyright 2014 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_ObjectMotionState_h +#define hifi_ObjectMotionState_h + +#include + +enum MotionType { + MOTION_TYPE_STATIC, // no motion + MOTION_TYPE_DYNAMIC, // motion according to physical laws + MOTION_TYPE_KINEMATIC // keyframed motion +}; + +// 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. +typedef unsigned int uint32_t; +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); + +// 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; + +#ifdef USE_BULLET_PHYSICS + +#include +#include +#include // for EntityItem::DIRTY_FOO bitmasks + +#include "ShapeInfo.h" + +class OctreeEditPacketSender; + +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. + static void setWorldOffset(const glm::vec3& offset); + static const glm::vec3& getWorldOffset(); + + ObjectMotionState(); + ~ObjectMotionState(); + + virtual void applyVelocities() const = 0; + virtual void applyGravity() const = 0; + + virtual void computeShapeInfo(ShapeInfo& info) = 0; + + virtual MotionType getMotionType() const { return _motionType; } + + void setDensity(float density); + void setFriction(float friction); + void setRestitution(float restitution); + void setVolume(float volume); + + float getMass() const { return _volume * _density; } + + void setVelocity(const glm::vec3& velocity) const; + void setAngularVelocity(const glm::vec3& velocity) const; + void setGravity(const glm::vec3& gravity) const; + + void getVelocity(glm::vec3& velocityOut) const; + void getAngularVelocity(glm::vec3& angularVelocityOut) 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 simulationFrame, float subStepRemainder) const; + virtual void sendUpdate(OctreeEditPacketSender* packetSender, uint32_t frame) = 0; + + virtual MotionType computeMotionType() const = 0; + + friend class PhysicsEngine; +protected: + float _density; + float _volume; + float _friction; + float _restitution; + bool _wasInWorld; + MotionType _motionType; + + // _body has NO setters -- it is only changed by PhysicsEngine + btRigidBody* _body; + + 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 _sentFrame; + glm::vec3 _sentPosition; // in simulation-frame (not world-frame) + glm::quat _sentRotation;; + glm::vec3 _sentVelocity; + glm::vec3 _sentAngularVelocity; + glm::vec3 _sentAcceleration; +}; + +#endif // USE_BULLET_PHYSICS +#endif // hifi_ObjectMotionState_h diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp new file mode 100644 index 0000000000..a6317b4fcf --- /dev/null +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -0,0 +1,454 @@ +// +// PhysicsEngine.cpp +// libraries/physcis/src +// +// Created by Andrew Meadows 2014.10.29 +// Copyright 2014 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 "PhysicsEngine.h" +#ifdef USE_BULLET_PHYSICS + +#include "ShapeInfoUtil.h" +#include "ThreadSafeDynamicsWorld.h" + +class EntityTree; + +PhysicsEngine::PhysicsEngine(const glm::vec3& offset) + : _collisionConfig(NULL), + _collisionDispatcher(NULL), + _broadphaseFilter(NULL), + _constraintSolver(NULL), + _dynamicsWorld(NULL), + _originOffset(offset), + _voxels(), + _entityPacketSender(NULL), + _frameCount(0) { +} + +PhysicsEngine::~PhysicsEngine() { +} + +// begin EntitySimulation overrides +void PhysicsEngine::updateEntitiesInternal(const quint64& now) { + // 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(); + uint32_t frame = getFrameCount(); + float subStepRemainder = getSubStepRemainder(); + while (stateItr != _outgoingPackets.end()) { + ObjectMotionState* state = *stateItr; + if (state->doesNotNeedToSendUpdate()) { + stateItr = _outgoingPackets.erase(stateItr); + } else if (state->shouldSendUpdate(frame, subStepRemainder)) { + state->sendUpdate(_entityPacketSender, frame); + ++stateItr; + } else { + ++stateItr; + } + } +} + +void PhysicsEngine::addEntityInternal(EntityItem* entity) { + assert(entity); + void* physicsInfo = entity->getPhysicsInfo(); + if (!physicsInfo) { + EntityMotionState* motionState = new EntityMotionState(entity); + if (addObject(motionState)) { + entity->setPhysicsInfo(static_cast(motionState)); + _entityMotionStates.insert(motionState); + } else { + // We failed to add the entity to the simulation. Probably because we couldn't create a shape for it. + qDebug() << "failed to add entity " << entity->getEntityItemID() << " to physics engine"; + delete motionState; + } + } +} + +void PhysicsEngine::removeEntityInternal(EntityItem* entity) { + assert(entity); + void* physicsInfo = entity->getPhysicsInfo(); + if (physicsInfo) { + EntityMotionState* motionState = static_cast(physicsInfo); + removeObject(motionState); + _entityMotionStates.remove(motionState); + _incomingChanges.remove(motionState); + _outgoingPackets.remove(motionState); + 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) { + 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) { + removeObject(*stateItr); + delete (*stateItr); + } + _entityMotionStates.clear(); + _incomingChanges.clear(); + _outgoingPackets.clear(); +} +// end EntitySimulation overrides + +void PhysicsEngine::relayIncomingChangesToSimulation() { + // process incoming changes + QSet::iterator stateItr = _incomingChanges.begin(); + while (stateItr != _incomingChanges.end()) { + ObjectMotionState* motionState = *stateItr; + uint32_t flags = motionState->getIncomingDirtyFlags() & DIRTY_PHYSICS_FLAGS; + + btRigidBody* body = motionState->_body; + 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 + updateObjectHard(body, motionState, flags); + } else if (flags) { + // an EASY update does NOT require that the body be pulled out of physics engine + updateObjectEasy(body, motionState, flags); + } + } + + // 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 + ++stateItr; + } + _incomingChanges.clear(); +} + +// 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, _entityTree); + + // 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)); + + // GROUND HACK: add a big planar floor (and walls for testing) to catch falling objects + btTransform groundTransform; + groundTransform.setIdentity(); + for (int i = 0; i < 3; ++i) { + btVector3 normal(0.0f, 0.0f, 0.0f); + normal[i] = 1.0f; + btCollisionShape* plane = new btStaticPlaneShape(normal, 0.0f); + + btCollisionObject* groundObject = new btCollisionObject(); + groundObject->setCollisionFlags(btCollisionObject::CF_STATIC_OBJECT); + groundObject->setCollisionShape(plane); + + groundObject->setWorldTransform(groundTransform); + _dynamicsWorld->addCollisionObject(groundObject); + } + } + + assert(packetSender); + _entityPacketSender = packetSender; + EntityMotionState::setOutgoingEntityList(&_entitiesToBeSorted); +} + +const float FIXED_SUBSTEP = 1.0f / 60.0f; + +void PhysicsEngine::stepSimulation() { + // 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 (1) + relayIncomingChangesToSimulation(); + + const int MAX_NUM_SUBSTEPS = 4; + const float MAX_TIMESTEP = (float)MAX_NUM_SUBSTEPS * FIXED_SUBSTEP; + float dt = 1.0e-6f * (float)(_clock.getTimeMicroseconds()); + _clock.reset(); + float timeStep = btMin(dt, MAX_TIMESTEP); + + // steps (2) and (3) are performed by ThreadSafeDynamicsWorld::stepSimulation()) + int numSubSteps = _dynamicsWorld->stepSimulation(timeStep, MAX_NUM_SUBSTEPS, FIXED_SUBSTEP); + _frameCount += (uint32_t)numSubSteps; +} + +bool PhysicsEngine::addVoxel(const glm::vec3& position, float scale) { + glm::vec3 halfExtents = glm::vec3(0.5f * scale); + glm::vec3 trueCenter = position + halfExtents; + PositionHashKey key(trueCenter); + VoxelObject* proxy = _voxels.find(key); + if (!proxy) { + // create a shape + ShapeInfo info; + info.setBox(halfExtents); + btCollisionShape* shape = _shapeManager.getShape(info); + + // NOTE: the shape creation will fail when the size of the voxel is out of range + if (shape) { + // create a collisionObject + btCollisionObject* object = new btCollisionObject(); + object->setCollisionShape(shape); + btTransform transform; + transform.setIdentity(); + // we shift the center into the simulation's frame + glm::vec3 shiftedCenter = (position - _originOffset) + halfExtents; + transform.setOrigin(btVector3(shiftedCenter.x, shiftedCenter.y, shiftedCenter.z)); + object->setWorldTransform(transform); + + // add to map and world + _voxels.insert(key, VoxelObject(trueCenter, object)); + _dynamicsWorld->addCollisionObject(object); + return true; + } + } + return false; +} + +bool PhysicsEngine::removeVoxel(const glm::vec3& position, float scale) { + glm::vec3 halfExtents = glm::vec3(0.5f * scale); + glm::vec3 trueCenter = position + halfExtents; + PositionHashKey key(trueCenter); + VoxelObject* proxy = _voxels.find(key); + if (proxy) { + // remove from world + assert(proxy->_object); + _dynamicsWorld->removeCollisionObject(proxy->_object); + + // release shape + ShapeInfo info; + info.setBox(halfExtents); + bool released = _shapeManager.releaseShape(info); + assert(released); + + // delete object and remove from voxel map + delete proxy->_object; + _voxels.remove(key); + return true; + } + return false; +} + +// Bullet collision flags are as follows: +// CF_STATIC_OBJECT= 1, +// CF_KINEMATIC_OBJECT= 2, +// CF_NO_CONTACT_RESPONSE = 4, +// CF_CUSTOM_MATERIAL_CALLBACK = 8,//this allows per-triangle material (friction/restitution) +// CF_CHARACTER_OBJECT = 16, +// CF_DISABLE_VISUALIZE_OBJECT = 32, //disable debug drawing +// CF_DISABLE_SPU_COLLISION_PROCESSING = 64//disable parallel/SPU processing + +bool PhysicsEngine::addObject(ObjectMotionState* motionState) { + assert(motionState); + ShapeInfo info; + motionState->computeShapeInfo(info); + btCollisionShape* shape = _shapeManager.getShape(info); + if (shape) { + btVector3 inertia(0.0f, 0.0f, 0.0f); + float mass = 0.0f; + btRigidBody* body = NULL; + switch(motionState->computeMotionType()) { + case MOTION_TYPE_KINEMATIC: { + body = new btRigidBody(mass, motionState, shape, inertia); + body->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT); + body->setActivationState(DISABLE_DEACTIVATION); + body->updateInertiaTensor(); + motionState->_body = body; + break; + } + case MOTION_TYPE_DYNAMIC: { + mass = motionState->getMass(); + shape->calculateLocalInertia(mass, inertia); + body = new btRigidBody(mass, motionState, shape, inertia); + body->updateInertiaTensor(); + motionState->_body = body; + motionState->applyVelocities(); + motionState->applyGravity(); + break; + } + case MOTION_TYPE_STATIC: + default: { + body = new btRigidBody(mass, motionState, shape, inertia); + body->setCollisionFlags(btCollisionObject::CF_STATIC_OBJECT); + body->updateInertiaTensor(); + motionState->_body = body; + break; + } + } + // wtf? + body->setFlags(BT_DISABLE_WORLD_GRAVITY); + body->setRestitution(motionState->_restitution); + body->setFriction(motionState->_friction); + _dynamicsWorld->addRigidBody(body); + return true; + } + return false; +} + +bool PhysicsEngine::removeObject(ObjectMotionState* motionState) { + assert(motionState); + btRigidBody* body = motionState->_body; + if (body) { + const btCollisionShape* shape = body->getCollisionShape(); + ShapeInfo info; + ShapeInfoUtil::collectInfoFromShape(shape, info); + _dynamicsWorld->removeRigidBody(body); + _shapeManager.releaseShape(info); + delete body; + motionState->_body = NULL; + return true; + } + return false; +} + +// private +void PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags) { + MotionType newType = motionState->computeMotionType(); + + // pull body out of physics engine + _dynamicsWorld->removeRigidBody(body); + + if (flags & EntityItem::DIRTY_SHAPE) { + btCollisionShape* oldShape = body->getCollisionShape(); + ShapeInfo info; + motionState->computeShapeInfo(info); + btCollisionShape* newShape = _shapeManager.getShape(info); + if (newShape != oldShape) { + body->setCollisionShape(newShape); + _shapeManager.releaseShape(oldShape); + } else { + // whoops, shape hasn't changed after all so we must release the reference + // that was created when looking it up + _shapeManager.releaseShape(newShape); + } + // MASS bit should be set whenever SHAPE is set + assert(flags & EntityItem::DIRTY_MASS); + } + bool easyUpdate = flags & EASY_DIRTY_PHYSICS_FLAGS; + if (easyUpdate) { + updateObjectEasy(body, motionState, flags); + } + + // 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(); + 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) + btVector3 inertia(0.0f, 0.0f, 0.0f); + float mass = motionState->getMass(); + body->getCollisionShape()->calculateLocalInertia(mass, inertia); + body->setMassProps(mass, inertia); + body->updateInertiaTensor(); + } + body->forceActivationState(ACTIVE_TAG); + 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)); + break; + } + } + + // reinsert body into physics engine + _dynamicsWorld->addRigidBody(body); + + body->activate(); +} + +// private +void PhysicsEngine::updateObjectEasy(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags) { + if (flags & EntityItem::DIRTY_POSITION) { + btTransform transform; + motionState->getWorldTransform(transform); + body->setWorldTransform(transform); + } + if (flags & EntityItem::DIRTY_VELOCITY) { + motionState->applyVelocities(); + motionState->applyGravity(); + } + body->setRestitution(motionState->_restitution); + body->setFriction(motionState->_friction); + + if (flags & EntityItem::DIRTY_MASS) { + float mass = motionState->getMass(); + btVector3 inertia(0.0f, 0.0f, 0.0f); + body->getCollisionShape()->calculateLocalInertia(mass, inertia); + body->setMassProps(mass, inertia); + body->updateInertiaTensor(); + } + body->activate(); + + // TODO: support collision groups +}; + +#endif // USE_BULLET_PHYSICS diff --git a/libraries/physics/src/PhysicsEngine.h b/libraries/physics/src/PhysicsEngine.h new file mode 100644 index 0000000000..5d506fced6 --- /dev/null +++ b/libraries/physics/src/PhysicsEngine.h @@ -0,0 +1,123 @@ +// +// PhysicsEngine.h +// libraries/physcis/src +// +// Created by Andrew Meadows 2014.10.29 +// Copyright 2014 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_PhysicsEngine_h +#define hifi_PhysicsEngine_h + +typedef unsigned int uint32_t; + +#ifdef USE_BULLET_PHYSICS + +#include +#include + +#include +#include + +#include "BulletUtil.h" +#include "EntityMotionState.h" +#include "PositionHashKey.h" +#include "ShapeManager.h" +#include "ThreadSafeDynamicsWorld.h" +#include "VoxelObject.h" + +const float HALF_SIMULATION_EXTENT = 512.0f; // meters + +class PhysicsEngine : public EntitySimulation { +public: + + PhysicsEngine(const glm::vec3& offset); + + ~PhysicsEngine(); + + // overrides for EntitySimulation + void updateEntitiesInternal(const quint64& now); + void addEntityInternal(EntityItem* entity); + void removeEntityInternal(EntityItem* entity); + void entityChangedInternal(EntityItem* entity); + void sortEntitiesThatMovedInternal(); + void clearEntitiesInternal(); + + virtual void init(EntityEditPacketSender* packetSender); + + void stepSimulation(); + + /// \param offset position of simulation origin in domain-frame + void setOriginOffset(const glm::vec3& offset) { _originOffset = offset; } + + /// \return position of simulation origin in domain-frame + const glm::vec3& getOriginOffset() const { return _originOffset; } + + /// \param position the minimum corner of the voxel + /// \param scale the length of the voxel side + /// \return true if Voxel added + bool addVoxel(const glm::vec3& position, float scale); + + /// \param position the minimum corner of the voxel + /// \param scale the length of the voxel side + /// \return true if Voxel removed + bool removeVoxel(const glm::vec3& position, float scale); + + /// \param motionState pointer to Object's MotionState + /// \return true if Object added + bool addObject(ObjectMotionState* motionState); + + /// \param motionState pointer to Object's MotionState + /// \return true if Object removed + bool removeObject(ObjectMotionState* motionState); + + /// process queue of changed from external sources + void relayIncomingChangesToSimulation(); + + /// \return duration of fixed simulation substep + float getFixedSubStep() const; + + /// \return number of simulation frames the physics engine has taken + uint32_t getFrameCount() const { return _frameCount; } + + /// \return substep remainder used for Bullet MotionState extrapolation + // Bullet will extrapolate the positions provided to MotionState::setWorldTransform() in an effort to provide + // smoother visible motion when the render frame rate does not match that of the simulation loop. We provide + // access to this fraction for improved filtering of update packets to interested parties. + float getSubStepRemainder() { return _dynamicsWorld->getLocalTimeAccumulation(); } + +protected: + void updateObjectHard(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags); + void updateObjectEasy(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags); + + btClock _clock; + btDefaultCollisionConfiguration* _collisionConfig; + btCollisionDispatcher* _collisionDispatcher; + btBroadphaseInterface* _broadphaseFilter; + btSequentialImpulseConstraintSolver* _constraintSolver; + ThreadSafeDynamicsWorld* _dynamicsWorld; + ShapeManager _shapeManager; + +private: + glm::vec3 _originOffset; + btHashMap _voxels; + + // EntitySimulation stuff + QSet _entityMotionStates; // all entities that we track + 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; + + uint32_t _frameCount; +}; + +#else // USE_BULLET_PHYSICS +// PhysicsEngine stubbery until Bullet is required +class PhysicsEngine { +}; +#endif // USE_BULLET_PHYSICS +#endif // hifi_PhysicsEngine_h diff --git a/libraries/physics/src/PhysicsEntity.cpp b/libraries/physics/src/PhysicsEntity.cpp index a01706f539..80176edb76 100644 --- a/libraries/physics/src/PhysicsEntity.cpp +++ b/libraries/physics/src/PhysicsEntity.cpp @@ -1,6 +1,6 @@ // // PhysicsEntity.cpp -// libraries/shared/src +// libraries/physics/src // // Created by Andrew Meadows 2014.06.11 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/PhysicsEntity.h b/libraries/physics/src/PhysicsEntity.h index a96754b75c..92b302debb 100644 --- a/libraries/physics/src/PhysicsEntity.h +++ b/libraries/physics/src/PhysicsEntity.h @@ -1,6 +1,6 @@ // // PhysicsEntity.h -// libraries/shared/src +// libraries/physics/src // // Created by Andrew Meadows 2014.05.30 // Copyright 2014 High Fidelity, Inc. @@ -18,8 +18,12 @@ #include #include -#include "CollisionInfo.h" -#include "RayIntersectionInfo.h" +#include +#include + +#ifdef USE_BULLET_PHYSICS +#include "PhysicsEngine.h" +#endif // USE_BULLET_PHYSICS class Shape; class PhysicsSimulation; diff --git a/libraries/physics/src/PhysicsSimulation.cpp b/libraries/physics/src/PhysicsSimulation.cpp index 72a1eeebfd..b92d9bec8f 100644 --- a/libraries/physics/src/PhysicsSimulation.cpp +++ b/libraries/physics/src/PhysicsSimulation.cpp @@ -1,6 +1,6 @@ // // PhysicsSimulation.cpp -// interface/src/avatar +// libraries/physcis/src // // Created by Andrew Meadows 2014.06.06 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/PhysicsSimulation.h b/libraries/physics/src/PhysicsSimulation.h index 12506e23d0..90d6e38187 100644 --- a/libraries/physics/src/PhysicsSimulation.h +++ b/libraries/physics/src/PhysicsSimulation.h @@ -1,6 +1,6 @@ // // PhysicsSimulation.h -// interface/src/avatar +// libraries/physcis/src // // Created by Andrew Meadows 2014.06.06 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/PositionHashKey.cpp b/libraries/physics/src/PositionHashKey.cpp new file mode 100644 index 0000000000..91f8261fba --- /dev/null +++ b/libraries/physics/src/PositionHashKey.cpp @@ -0,0 +1,37 @@ +// +// PositionHashKey.cpp +// libraries/physcis/src +// +// Created by Andrew Meadows 2014.11.05 +// Copyright 2014 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 + +#include "PositionHashKey.h" + +// static +int computeHash(const glm::vec3& center) { + // NOTE: 0.49f is used to bump the float up almost half a millimeter + // so the cast to int produces a round() effect rather than a floor() + int hash = DoubleHashKey::hashFunction((int)(center.x * MILLIMETERS_PER_METER + copysignf(1.0f, center.x) * 0.49f), 0); + hash ^= DoubleHashKey::hashFunction((int)(center.y * MILLIMETERS_PER_METER + copysignf(1.0f, center.y) * 0.49f), 1); + return hash ^ DoubleHashKey::hashFunction((int)(center.z * MILLIMETERS_PER_METER + copysignf(1.0f, center.z) * 0.49f), 2); +} + +// static +int computeHash2(const glm::vec3& center) { + // NOTE: 0.49f is used to bump the float up almost half a millimeter + // so the cast to int produces a round() effect rather than a floor() + int hash = DoubleHashKey::hashFunction2((int)(center.x * MILLIMETERS_PER_METER + copysignf(1.0f, center.x) * 0.49f)); + hash ^= DoubleHashKey::hashFunction2((int)(center.y * MILLIMETERS_PER_METER + copysignf(1.0f, center.y) * 0.49f)); + return hash ^ DoubleHashKey::hashFunction2((int)(center.z * MILLIMETERS_PER_METER + copysignf(1.0f, center.z) * 0.49f)); +} + +PositionHashKey::PositionHashKey(glm::vec3 center) : DoubleHashKey() { + _hash = computeHash(center); + _hash2 = computeHash2(center); +} diff --git a/libraries/physics/src/PositionHashKey.h b/libraries/physics/src/PositionHashKey.h new file mode 100644 index 0000000000..1065034fa8 --- /dev/null +++ b/libraries/physics/src/PositionHashKey.h @@ -0,0 +1,26 @@ +// +// PositionHashKey.h +// libraries/physcis/src +// +// Created by Andrew Meadows 2014.11.05 +// Copyright 2014 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_PositionHashKey_h +#define hifi_PositionHashKey_h + +#include + +#include + +#include "DoubleHashKey.h" + +class PositionHashKey : public DoubleHashKey { +public: + PositionHashKey(glm::vec3 center); +}; + +#endif // hifi_PositionHashKey_h diff --git a/libraries/physics/src/Ragdoll.cpp b/libraries/physics/src/Ragdoll.cpp index 3eecef2333..7936828025 100644 --- a/libraries/physics/src/Ragdoll.cpp +++ b/libraries/physics/src/Ragdoll.cpp @@ -1,6 +1,6 @@ // // Ragdoll.cpp -// libraries/shared/src +// libraries/physics/src // // Created by Andrew Meadows 2014.05.30 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/Ragdoll.h b/libraries/physics/src/Ragdoll.h index 5447b6769e..ab81f165bd 100644 --- a/libraries/physics/src/Ragdoll.h +++ b/libraries/physics/src/Ragdoll.h @@ -1,6 +1,6 @@ // // Ragdoll.h -// libraries/shared/src +// libraries/physics/src // // Created by Andrew Meadows 2014.05.30 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/ShapeInfoUtil.cpp b/libraries/physics/src/ShapeInfoUtil.cpp new file mode 100644 index 0000000000..f562201f73 --- /dev/null +++ b/libraries/physics/src/ShapeInfoUtil.cpp @@ -0,0 +1,172 @@ +// +// ShapeInfoUtil.cpp +// libraries/physcis/src +// +// Created by Andrew Meadows 2014.12.01 +// Copyright 2014 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 // for FOO_SHAPE types +#include // for MILLIMETERS_PER_METER + +#include "ShapeInfoUtil.h" +#include "BulletUtil.h" + +#ifdef USE_BULLET_PHYSICS + + +int ShapeInfoUtil::toBulletShapeType(int shapeInfoType) { + int bulletShapeType = INVALID_SHAPE_PROXYTYPE; + switch(shapeInfoType) { + case BOX_SHAPE: + bulletShapeType = BOX_SHAPE_PROXYTYPE; + break; + case SPHERE_SHAPE: + bulletShapeType = SPHERE_SHAPE_PROXYTYPE; + break; + case CAPSULE_SHAPE: + bulletShapeType = CAPSULE_SHAPE_PROXYTYPE; + break; + case CYLINDER_SHAPE: + bulletShapeType = CYLINDER_SHAPE_PROXYTYPE; + break; + } + return bulletShapeType; +} + +int ShapeInfoUtil::fromBulletShapeType(int bulletShapeType) { + int shapeInfoType = INVALID_SHAPE; + switch(bulletShapeType) { + case BOX_SHAPE_PROXYTYPE: + shapeInfoType = BOX_SHAPE; + break; + case SPHERE_SHAPE_PROXYTYPE: + shapeInfoType = SPHERE_SHAPE; + break; + case CAPSULE_SHAPE_PROXYTYPE: + shapeInfoType = CAPSULE_SHAPE; + break; + case CYLINDER_SHAPE_PROXYTYPE: + shapeInfoType = CYLINDER_SHAPE; + break; + } + return shapeInfoType; +} + +void ShapeInfoUtil::collectInfoFromShape(const btCollisionShape* shape, ShapeInfo& info) { + if (shape) { + int type = ShapeInfoUtil::fromBulletShapeType(shape->getShapeType()); + switch(type) { + case BOX_SHAPE: { + const btBoxShape* boxShape = static_cast(shape); + info.setBox(bulletToGLM(boxShape->getHalfExtentsWithMargin())); + } + break; + case SPHERE_SHAPE: { + const btSphereShape* sphereShape = static_cast(shape); + info.setSphere(sphereShape->getRadius()); + } + break; + case CYLINDER_SHAPE: { + // NOTE: we only support cylinders along yAxis + const btCylinderShape* cylinderShape = static_cast(shape); + btVector3 halfExtents = cylinderShape->getHalfExtentsWithMargin(); + info.setCylinder(halfExtents.getX(), halfExtents.getY()); + } + break; + case CAPSULE_SHAPE: { + // NOTE: we only support capsules along yAxis + const btCapsuleShape* capsuleShape = static_cast(shape); + info.setCapsule(capsuleShape->getRadius(), capsuleShape->getHalfHeight()); + } + break; + default: + info.clear(); + break; + } + } else { + info.clear(); + } +} + +btCollisionShape* ShapeInfoUtil::createShapeFromInfo(const ShapeInfo& info) { + btCollisionShape* shape = NULL; + const QVector& data = info.getData(); + switch(info.getType()) { + case BOX_SHAPE: { + // data[0] is halfExtents + shape = new btBoxShape(glmToBullet(data[0])); + } + break; + case SPHERE_SHAPE: { + float radius = data[0].z; + shape = new btSphereShape(radius); + } + break; + case CYLINDER_SHAPE: { + // NOTE: default cylinder has (UpAxis = 1) axis along yAxis and radius stored in X + // data[0] = btVector3(radius, halfHeight, unused) + shape = new btCylinderShape(glmToBullet(data[0])); + } + break; + case CAPSULE_SHAPE: { + float radius = data[0].x; + float height = 2.0f * data[0].y; + shape = new btCapsuleShape(radius, height); + } + break; + } + return shape; +} + +DoubleHashKey ShapeInfoUtil::computeHash(const ShapeInfo& info) { + DoubleHashKey key; + // compute hash + // scramble the bits of the type + // TODO?: provide lookup table for hash of info._type rather than recompute? + int primeIndex = 0; + unsigned int hash = DoubleHashKey::hashFunction((unsigned int)info.getType(), primeIndex++); + const QVector& data = info.getData(); + + glm::vec3 tmpData; + int numData = data.size(); + for (int i = 0; i < numData; ++i) { + tmpData = data[i]; + for (int j = 0; j < 3; ++j) { + // NOTE: 0.49f is used to bump the float up almost half a millimeter + // so the cast to int produces a round() effect rather than a floor() + unsigned int floatHash = + DoubleHashKey::hashFunction((int)(tmpData[j] * MILLIMETERS_PER_METER + copysignf(1.0f, tmpData[j]) * 0.49f), primeIndex++); + hash ^= floatHash; + } + } + key._hash = (int)hash; + + // compute hash2 + // scramble the bits of the type + // TODO?: provide lookup table for hash2 of info._type rather than recompute? + hash = DoubleHashKey::hashFunction2((unsigned int)info.getType()); + + for (int i = 0; i < numData; ++i) { + tmpData = data[i]; + for (int j = 0; j < 3; ++j) { + // NOTE: 0.49f is used to bump the float up almost half a millimeter + // so the cast to int produces a round() effect rather than a floor() + unsigned int floatHash = + DoubleHashKey::hashFunction2((int)(tmpData[j] * MILLIMETERS_PER_METER + copysignf(1.0f, tmpData[j]) * 0.49f)); + hash += ~(floatHash << 17); + hash ^= (floatHash >> 11); + hash += (floatHash << 4); + hash ^= (floatHash >> 7); + hash += ~(floatHash << 10); + hash = (hash << 16) | (hash >> 16); + } + } + key._hash2 = (int)hash; + return key; +} + +#endif // USE_BULLET_PHYSICS diff --git a/libraries/physics/src/ShapeInfoUtil.h b/libraries/physics/src/ShapeInfoUtil.h new file mode 100644 index 0000000000..7363cf483b --- /dev/null +++ b/libraries/physics/src/ShapeInfoUtil.h @@ -0,0 +1,39 @@ +// +// ShapeInfoUtil.h +// libraries/physcis/src +// +// Created by Andrew Meadows 2014.12.01 +// Copyright 2014 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_ShapeInfoUtil_h +#define hifi_ShapeInfoUtil_h + +#ifdef USE_BULLET_PHYSICS + +#include +#include + +#include + +#include "DoubleHashKey.h" + +// translates between ShapeInfo and btShape + +namespace ShapeInfoUtil { + void collectInfoFromShape(const btCollisionShape* shape, ShapeInfo& info); + + btCollisionShape* createShapeFromInfo(const ShapeInfo& info); + + DoubleHashKey computeHash(const ShapeInfo& info); + + // TODO? just use bullet shape types everywhere? + int toBulletShapeType(int shapeInfoType); + int fromBulletShapeType(int bulletShapeType); +}; + +#endif // USE_BULLET_PHYSICS +#endif // hifi_ShapeInfoUtil_h diff --git a/libraries/physics/src/ShapeManager.cpp b/libraries/physics/src/ShapeManager.cpp new file mode 100644 index 0000000000..1da705a5ff --- /dev/null +++ b/libraries/physics/src/ShapeManager.cpp @@ -0,0 +1,109 @@ +// +// ShapeManager.cpp +// libraries/physcis/src +// +// Created by Andrew Meadows 2014.10.29 +// Copyright 2014 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 +// + +#ifdef USE_BULLET_PHYSICS + +#include + +#include "ShapeInfoUtil.h" +#include "ShapeManager.h" + +ShapeManager::ShapeManager() { +} + +ShapeManager::~ShapeManager() { + int numShapes = _shapeMap.size(); + for (int i = 0; i < numShapes; ++i) { + ShapeReference* shapeRef = _shapeMap.getAtIndex(i); + delete shapeRef->_shape; + } + _shapeMap.clear(); +} + +btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) { + // Very small or large objects are not supported. + float diagonal = glm::length2(info.getBoundingBoxDiagonal()); + const float MIN_SHAPE_DIAGONAL_SQUARED = 3.0e-4f; // 1 cm cube + const float MAX_SHAPE_DIAGONAL_SQUARED = 3.0e4f; // 100 m cube + if (diagonal < MIN_SHAPE_DIAGONAL_SQUARED || diagonal > MAX_SHAPE_DIAGONAL_SQUARED) { + return NULL; + } + DoubleHashKey key = ShapeInfoUtil::computeHash(info); + ShapeReference* shapeRef = _shapeMap.find(key); + if (shapeRef) { + shapeRef->_refCount++; + return shapeRef->_shape; + } + btCollisionShape* shape = ShapeInfoUtil::createShapeFromInfo(info); + if (shape) { + ShapeReference newRef; + newRef._refCount = 1; + newRef._shape = shape; + _shapeMap.insert(key, newRef); + } + return shape; +} + +bool ShapeManager::releaseShape(const ShapeInfo& info) { + DoubleHashKey key = ShapeInfoUtil::computeHash(info); + ShapeReference* shapeRef = _shapeMap.find(key); + if (shapeRef) { + if (shapeRef->_refCount > 0) { + shapeRef->_refCount--; + if (shapeRef->_refCount == 0) { + _pendingGarbage.push_back(key); + const int MAX_GARBAGE_CAPACITY = 127; + if (_pendingGarbage.size() > MAX_GARBAGE_CAPACITY) { + collectGarbage(); + } + } + return true; + } else { + // attempt to remove shape that has no refs + assert(false); + } + } else { + // attempt to remove unmanaged shape + assert(false); + } + return false; +} + +bool ShapeManager::releaseShape(const btCollisionShape* shape) { + ShapeInfo info; + ShapeInfoUtil::collectInfoFromShape(shape, info); + return releaseShape(info); +} + +void ShapeManager::collectGarbage() { + int numShapes = _pendingGarbage.size(); + for (int i = 0; i < numShapes; ++i) { + DoubleHashKey& key = _pendingGarbage[i]; + ShapeReference* shapeRef = _shapeMap.find(key); + if (shapeRef && shapeRef->_refCount == 0) { + delete shapeRef->_shape; + _shapeMap.remove(key); + } + } + _pendingGarbage.clear(); +} + +int ShapeManager::getNumReferences(const ShapeInfo& info) const { + DoubleHashKey key = ShapeInfoUtil::computeHash(info); + const ShapeReference* shapeRef = _shapeMap.find(key); + if (shapeRef) { + return shapeRef->_refCount; + } + return -1; +} + + +#endif // USE_BULLET_PHYSICS diff --git a/libraries/physics/src/ShapeManager.h b/libraries/physics/src/ShapeManager.h new file mode 100644 index 0000000000..6eb4f363ff --- /dev/null +++ b/libraries/physics/src/ShapeManager.h @@ -0,0 +1,56 @@ +// +// ShapeManager.h +// libraries/physcis/src +// +// Created by Andrew Meadows 2014.10.29 +// Copyright 2014 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_ShapeManager_h +#define hifi_ShapeManager_h + +#ifdef USE_BULLET_PHYSICS + +#include +#include + +#include + +#include "DoubleHashKey.h" + +class ShapeManager { +public: + + ShapeManager(); + ~ShapeManager(); + + /// \return pointer to shape + btCollisionShape* getShape(const ShapeInfo& info); + + /// \return true if shape was found and released + bool releaseShape(const ShapeInfo& info); + bool releaseShape(const btCollisionShape* shape); + + /// delete shapes that have zero references + void collectGarbage(); + + // validation methods + int getNumShapes() const { return _shapeMap.size(); } + int getNumReferences(const ShapeInfo& info) const; + +private: + struct ShapeReference { + int _refCount; + btCollisionShape* _shape; + ShapeReference() : _refCount(0), _shape(NULL) {} + }; + + btHashMap _shapeMap; + btAlignedObjectArray _pendingGarbage; +}; + +#endif // USE_BULLET_PHYSICS +#endif // hifi_ShapeManager_h diff --git a/libraries/physics/src/ThreadSafeDynamicsWorld.cpp b/libraries/physics/src/ThreadSafeDynamicsWorld.cpp new file mode 100644 index 0000000000..e70bdc7210 --- /dev/null +++ b/libraries/physics/src/ThreadSafeDynamicsWorld.cpp @@ -0,0 +1,91 @@ +/* + * Bullet Continuous Collision Detection and Physics Library + * Copyright (c) 2003-2009 Erwin Coumans http://bulletphysics.org + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from the use of this software. + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it freely, + * subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + * + * Copied and modified from btDiscreteDynamicsWorld.cpp by AndrewMeadows on 2014.11.12. + * */ + +#include + +#include "ThreadSafeDynamicsWorld.h" + +#ifdef USE_BULLET_PHYSICS +ThreadSafeDynamicsWorld::ThreadSafeDynamicsWorld( + btDispatcher* dispatcher, + btBroadphaseInterface* pairCache, + btConstraintSolver* constraintSolver, + btCollisionConfiguration* collisionConfiguration, + EntityTree* entities) + : btDiscreteDynamicsWorld(dispatcher, pairCache, constraintSolver, collisionConfiguration) { + assert(entities); + _entities = entities; +} + +void ThreadSafeDynamicsWorld::synchronizeMotionStates() { + _entities->lockForWrite(); + btDiscreteDynamicsWorld::synchronizeMotionStates(); + _entities->unlock(); +} + +int ThreadSafeDynamicsWorld::stepSimulation( btScalar timeStep, int maxSubSteps, btScalar fixedTimeStep) { + int subSteps = 0; + if (maxSubSteps) { + //fixed timestep with interpolation + m_fixedTimeStep = fixedTimeStep; + m_localTime += timeStep; + if (m_localTime >= fixedTimeStep) + { + subSteps = int( m_localTime / fixedTimeStep); + m_localTime -= subSteps * fixedTimeStep; + } + } else { + //variable timestep + fixedTimeStep = timeStep; + m_localTime = m_latencyMotionStateInterpolation ? 0 : timeStep; + m_fixedTimeStep = 0; + if (btFuzzyZero(timeStep)) + { + subSteps = 0; + maxSubSteps = 0; + } else + { + subSteps = 1; + maxSubSteps = 1; + } + } + + /*//process some debugging flags + if (getDebugDrawer()) { + btIDebugDraw* debugDrawer = getDebugDrawer (); + gDisableDeactivation = (debugDrawer->getDebugMode() & btIDebugDraw::DBG_NoDeactivation) != 0; + }*/ + if (subSteps) { + //clamp the number of substeps, to prevent simulation grinding spiralling down to a halt + int clampedSimulationSteps = (subSteps > maxSubSteps)? maxSubSteps : subSteps; + + saveKinematicState(fixedTimeStep*clampedSimulationSteps); + + applyGravity(); + + for (int i=0;i + +class EntityTree; + +ATTRIBUTE_ALIGNED16(class) ThreadSafeDynamicsWorld : public btDiscreteDynamicsWorld { +public: + BT_DECLARE_ALIGNED_ALLOCATOR(); + + ThreadSafeDynamicsWorld( + btDispatcher* dispatcher, + btBroadphaseInterface* pairCache, + btConstraintSolver* constraintSolver, + btCollisionConfiguration* collisionConfiguration, + EntityTree* entities); + + // 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; } + +private: + EntityTree* _entities; +}; + +#else // USE_BULLET_PHYSICS +// stubbery for ThreadSafeDynamicsWorld when Bullet not available +class ThreadSafeDynamicsWorld { +public: + ThreadSafeDynamicsWorld() {} +}; + +#endif // USE_BULLET_PHYSICS + +#endif // hifi_ThreadSafeDynamicsWorld_h diff --git a/libraries/physics/src/VerletCapsuleShape.cpp b/libraries/physics/src/VerletCapsuleShape.cpp index 78e3f6763b..a04c36d56d 100644 --- a/libraries/physics/src/VerletCapsuleShape.cpp +++ b/libraries/physics/src/VerletCapsuleShape.cpp @@ -1,6 +1,6 @@ // // VerletCapsuleShape.cpp -// libraries/shared/src +// libraries/physics/src // // Created by Andrew Meadows on 2014.06.16 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/VerletCapsuleShape.h b/libraries/physics/src/VerletCapsuleShape.h index 828e5def6c..19afab2b61 100644 --- a/libraries/physics/src/VerletCapsuleShape.h +++ b/libraries/physics/src/VerletCapsuleShape.h @@ -1,6 +1,6 @@ // // VerletCapsuleShape.h -// libraries/shared/src +// libraries/physics/src // // Created by Andrew Meadows on 2014.06.16 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/VerletPoint.cpp b/libraries/physics/src/VerletPoint.cpp index cf9aeca149..895f3be8b2 100644 --- a/libraries/physics/src/VerletPoint.cpp +++ b/libraries/physics/src/VerletPoint.cpp @@ -1,6 +1,6 @@ // // VerletPoint.cpp -// libraries/shared/src +// libraries/physics/src // // Created by Andrew Meadows 2014.07.24 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/VerletPoint.h b/libraries/physics/src/VerletPoint.h index 3c73e5eb01..d80698c9e2 100644 --- a/libraries/physics/src/VerletPoint.h +++ b/libraries/physics/src/VerletPoint.h @@ -1,6 +1,6 @@ // // VerletPoint.h -// libraries/shared/src +// libraries/physics/src // // Created by Andrew Meadows 2014.07.24 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/VerletSphereShape.cpp b/libraries/physics/src/VerletSphereShape.cpp index da8242f26a..83dcbd9a58 100644 --- a/libraries/physics/src/VerletSphereShape.cpp +++ b/libraries/physics/src/VerletSphereShape.cpp @@ -1,6 +1,6 @@ // // VerletSphereShape.cpp -// libraries/shared/src +// libraries/physics/src // // Created by Andrew Meadows on 2014.06.16 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/VerletSphereShape.h b/libraries/physics/src/VerletSphereShape.h index c9a23faef2..596f4a517b 100644 --- a/libraries/physics/src/VerletSphereShape.h +++ b/libraries/physics/src/VerletSphereShape.h @@ -1,6 +1,6 @@ // // VerletSphereShape.h -// libraries/shared/src +// libraries/physics/src // // Created by Andrew Meadows on 2014.06.16 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/VoxelObject.h b/libraries/physics/src/VoxelObject.h new file mode 100644 index 0000000000..1372f761c7 --- /dev/null +++ b/libraries/physics/src/VoxelObject.h @@ -0,0 +1,31 @@ +// +// VoxelObject.h +// libraries/physcis/src +// +// Created by Andrew Meadows 2014.11.05 +// Copyright 2014 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_VoxelObject_h +#define hifi_VoxelObject_h + +#ifdef USE_BULLET_PHYSICS + +#include +#include + +// VoxelObject is a simple wrapper for tracking a Voxel in a PhysicsEngine +class VoxelObject { +public: + VoxelObject(const glm::vec3& center, btCollisionObject* object) : _object(object), _center(center) { + assert(object != NULL); + } + btCollisionObject* _object; + glm::vec3 _center; +}; + +#endif // USE_BULLET_PHYSICS +#endif // hifi_VoxelObject_h diff --git a/libraries/shared/src/AACube.h b/libraries/shared/src/AACube.h index 7067175f3a..b29bc49bef 100644 --- a/libraries/shared/src/AACube.h +++ b/libraries/shared/src/AACube.h @@ -78,6 +78,10 @@ inline bool operator==(const AACube& a, const AACube& b) { return a.getCorner() == b.getCorner() && a.getScale() == b.getScale(); } +inline bool operator!=(const AACube& a, const AACube& b) { + return a.getCorner() != b.getCorner() || a.getScale() != b.getScale(); +} + inline QDebug operator<<(QDebug debug, const AACube& cube) { debug << "AACube[ (" << cube.getCorner().x << "," << cube.getCorner().y << "," << cube.getCorner().z << " ) to (" diff --git a/libraries/physics/src/AACubeShape.cpp b/libraries/shared/src/AACubeShape.cpp similarity index 98% rename from libraries/physics/src/AACubeShape.cpp rename to libraries/shared/src/AACubeShape.cpp index fa1a45b809..30197d6bfd 100644 --- a/libraries/physics/src/AACubeShape.cpp +++ b/libraries/shared/src/AACubeShape.cpp @@ -13,7 +13,7 @@ #include #include "AACubeShape.h" -#include // for SQUARE_ROOT_OF_3 +#include "SharedUtil.h" // for SQUARE_ROOT_OF_3 glm::vec3 faceNormals[3] = { glm::vec3(1.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f) }; diff --git a/libraries/physics/src/AACubeShape.h b/libraries/shared/src/AACubeShape.h similarity index 100% rename from libraries/physics/src/AACubeShape.h rename to libraries/shared/src/AACubeShape.h diff --git a/libraries/physics/src/CapsuleShape.cpp b/libraries/shared/src/CapsuleShape.cpp similarity index 99% rename from libraries/physics/src/CapsuleShape.cpp rename to libraries/shared/src/CapsuleShape.cpp index 778798c15b..24ac6634ea 100644 --- a/libraries/physics/src/CapsuleShape.cpp +++ b/libraries/shared/src/CapsuleShape.cpp @@ -12,11 +12,9 @@ #include #include -#include -#include - #include "CapsuleShape.h" - +#include "GeometryUtil.h" +#include "SharedUtil.h" CapsuleShape::CapsuleShape() : Shape(CAPSULE_SHAPE), _radius(0.0f), _halfHeight(0.0f) {} diff --git a/libraries/physics/src/CapsuleShape.h b/libraries/shared/src/CapsuleShape.h similarity index 98% rename from libraries/physics/src/CapsuleShape.h rename to libraries/shared/src/CapsuleShape.h index ede6993b40..ad3066bab7 100644 --- a/libraries/physics/src/CapsuleShape.h +++ b/libraries/shared/src/CapsuleShape.h @@ -12,9 +12,8 @@ #ifndef hifi_CapsuleShape_h #define hifi_CapsuleShape_h -#include - #include "Shape.h" +#include "SharedUtil.h" // default axis of CapsuleShape is Y-axis const glm::vec3 DEFAULT_CAPSULE_AXIS(0.0f, 1.0f, 0.0f); diff --git a/libraries/physics/src/CollisionInfo.cpp b/libraries/shared/src/CollisionInfo.cpp similarity index 99% rename from libraries/physics/src/CollisionInfo.cpp rename to libraries/shared/src/CollisionInfo.cpp index bfa1606e63..1403e444cf 100644 --- a/libraries/physics/src/CollisionInfo.cpp +++ b/libraries/shared/src/CollisionInfo.cpp @@ -10,10 +10,10 @@ // -#include #include "CollisionInfo.h" #include "Shape.h" +#include "SharedUtil.h" CollisionInfo::CollisionInfo() : _data(NULL), diff --git a/libraries/physics/src/CollisionInfo.h b/libraries/shared/src/CollisionInfo.h similarity index 100% rename from libraries/physics/src/CollisionInfo.h rename to libraries/shared/src/CollisionInfo.h diff --git a/libraries/physics/src/ListShape.cpp b/libraries/shared/src/ListShape.cpp similarity index 100% rename from libraries/physics/src/ListShape.cpp rename to libraries/shared/src/ListShape.cpp diff --git a/libraries/physics/src/ListShape.h b/libraries/shared/src/ListShape.h similarity index 100% rename from libraries/physics/src/ListShape.h rename to libraries/shared/src/ListShape.h diff --git a/libraries/physics/src/PlaneShape.cpp b/libraries/shared/src/PlaneShape.cpp similarity index 98% rename from libraries/physics/src/PlaneShape.cpp rename to libraries/shared/src/PlaneShape.cpp index 1a4122f3aa..a247641122 100644 --- a/libraries/physics/src/PlaneShape.cpp +++ b/libraries/shared/src/PlaneShape.cpp @@ -9,10 +9,9 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include -#include - +#include "GLMHelpers.h" #include "PlaneShape.h" +#include "SharedUtil.h" const glm::vec3 UNROTATED_NORMAL(0.0f, 1.0f, 0.0f); diff --git a/libraries/physics/src/PlaneShape.h b/libraries/shared/src/PlaneShape.h similarity index 100% rename from libraries/physics/src/PlaneShape.h rename to libraries/shared/src/PlaneShape.h diff --git a/libraries/physics/src/RayIntersectionInfo.h b/libraries/shared/src/RayIntersectionInfo.h similarity index 97% rename from libraries/physics/src/RayIntersectionInfo.h rename to libraries/shared/src/RayIntersectionInfo.h index 6c4eb3f8dd..06ec0aceaa 100644 --- a/libraries/physics/src/RayIntersectionInfo.h +++ b/libraries/shared/src/RayIntersectionInfo.h @@ -1,6 +1,6 @@ // // RayIntersectionInfo.h -// interface/src/avatar +// libraries/physcis/src // // Created by Andrew Meadows 2014.09.09 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/Shape.h b/libraries/shared/src/Shape.h similarity index 93% rename from libraries/physics/src/Shape.h rename to libraries/shared/src/Shape.h index e529761dfc..dd318c2776 100644 --- a/libraries/physics/src/Shape.h +++ b/libraries/shared/src/Shape.h @@ -25,12 +25,19 @@ class VerletPoint; const float MAX_SHAPE_MASS = 1.0e18f; // something less than sqrt(FLT_MAX) +// DANGER: until ShapeCollider goes away the order of these values matter. Specifically, +// UNKNOWN_SHAPE must be equal to the number of shapes that ShapeCollider actually supports. const quint8 SPHERE_SHAPE = 0; const quint8 CAPSULE_SHAPE = 1; const quint8 PLANE_SHAPE = 2; const quint8 AACUBE_SHAPE = 3; const quint8 LIST_SHAPE = 4; const quint8 UNKNOWN_SHAPE = 5; +const quint8 INVALID_SHAPE = 5; + +// new shapes to be supported by Bullet +const quint8 BOX_SHAPE = 7; +const quint8 CYLINDER_SHAPE = 8; class Shape { public: diff --git a/libraries/physics/src/ShapeCollider.cpp b/libraries/shared/src/ShapeCollider.cpp similarity index 99% rename from libraries/physics/src/ShapeCollider.cpp rename to libraries/shared/src/ShapeCollider.cpp index a147487f33..b4f9d984f7 100644 --- a/libraries/physics/src/ShapeCollider.cpp +++ b/libraries/shared/src/ShapeCollider.cpp @@ -11,16 +11,14 @@ #include -#include -#include - -#include "ShapeCollider.h" - #include "AACubeShape.h" #include "CapsuleShape.h" +#include "GeometryUtil.h" #include "ListShape.h" #include "PlaneShape.h" +#include "ShapeCollider.h" #include "SphereShape.h" +#include "StreamUtils.h" // NOTE: diff --git a/libraries/physics/src/ShapeCollider.h b/libraries/shared/src/ShapeCollider.h similarity index 99% rename from libraries/physics/src/ShapeCollider.h rename to libraries/shared/src/ShapeCollider.h index 7414665ca7..da37adcd49 100644 --- a/libraries/physics/src/ShapeCollider.h +++ b/libraries/shared/src/ShapeCollider.h @@ -14,10 +14,10 @@ #include -#include - #include "CollisionInfo.h" #include "RayIntersectionInfo.h" +#include "SharedUtil.h" + class Shape; class SphereShape; diff --git a/libraries/shared/src/ShapeInfo.cpp b/libraries/shared/src/ShapeInfo.cpp new file mode 100644 index 0000000000..d402a048a0 --- /dev/null +++ b/libraries/shared/src/ShapeInfo.cpp @@ -0,0 +1,60 @@ +// +// ShapeInfo.cpp +// libraries/physcis/src +// +// Created by Andrew Meadows 2014.10.29 +// Copyright 2014 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 + +#include "SharedUtil.h" // for MILLIMETERS_PER_METER + +//#include "DoubleHashKey.h" +#include "ShapeInfo.h" + +void ShapeInfo::clear() { + _type = INVALID_SHAPE; + _data.clear(); +} + +void ShapeInfo::setBox(const glm::vec3& halfExtents) { + _type = BOX_SHAPE; + _data.clear(); + _data.push_back(halfExtents); +} + +void ShapeInfo::setSphere(float radius) { + _type = SPHERE_SHAPE; + _data.clear(); + _data.push_back(glm::vec3(radius)); +} + +void ShapeInfo::setCylinder(float radius, float halfHeight) { + _type = CYLINDER_SHAPE; + _data.clear(); + // NOTE: default cylinder has (UpAxis = 1) axis along yAxis and radius stored in X + _data.push_back(glm::vec3(radius, halfHeight, radius)); +} + +void ShapeInfo::setCapsule(float radius, float halfHeight) { + _type = CAPSULE_SHAPE; + _data.clear(); + _data.push_back(glm::vec3(radius, halfHeight, radius)); +} + +glm::vec3 ShapeInfo::getBoundingBoxDiagonal() const { + switch(_type) { + case BOX_SHAPE: + case SPHERE_SHAPE: + case CYLINDER_SHAPE: + case CAPSULE_SHAPE: + return 2.0f * _data[0]; + default: + break; + } + return glm::vec3(0.0f); +} diff --git a/libraries/shared/src/ShapeInfo.h b/libraries/shared/src/ShapeInfo.h new file mode 100644 index 0000000000..7ba290122c --- /dev/null +++ b/libraries/shared/src/ShapeInfo.h @@ -0,0 +1,42 @@ +// +// ShapeInfo.h +// libraries/physcis/src +// +// Created by Andrew Meadows 2014.10.29 +// Copyright 2014 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_ShapeInfo_h +#define hifi_ShapeInfo_h + +#include +#include + +#include "Shape.h" + +class ShapeInfo { +public: + ShapeInfo() : _type(INVALID_SHAPE) {} + + void clear(); + + void setBox(const glm::vec3& halfExtents); + void setSphere(float radius); + void setCylinder(float radius, float halfHeight); + void setCapsule(float radius, float halfHeight); + + const int getType() const { return _type; } + const QVector& getData() const { return _data; } + + glm::vec3 getBoundingBoxDiagonal() const; + +protected: + int _type; + QVector _data; +}; + +#endif // hifi_ShapeInfo_h + diff --git a/libraries/physics/src/SphereShape.cpp b/libraries/shared/src/SphereShape.cpp similarity index 100% rename from libraries/physics/src/SphereShape.cpp rename to libraries/shared/src/SphereShape.cpp diff --git a/libraries/physics/src/SphereShape.h b/libraries/shared/src/SphereShape.h similarity index 98% rename from libraries/physics/src/SphereShape.h rename to libraries/shared/src/SphereShape.h index 72f46c1168..3bce2e96a1 100644 --- a/libraries/physics/src/SphereShape.h +++ b/libraries/shared/src/SphereShape.h @@ -12,9 +12,8 @@ #ifndef hifi_SphereShape_h #define hifi_SphereShape_h -#include - #include "Shape.h" +#include "SharedUtil.h" class SphereShape : public Shape { diff --git a/tests/physics/CMakeLists.txt b/tests/physics/CMakeLists.txt index 31c741eecf..14e6c56df3 100644 --- a/tests/physics/CMakeLists.txt +++ b/tests/physics/CMakeLists.txt @@ -3,8 +3,8 @@ set(TARGET_NAME physics-tests) setup_hifi_project() include_glm() +include_bullet() -# link in the shared libraries link_hifi_libraries(shared physics) include_dependency_includes() diff --git a/tests/physics/src/BulletUtilTests.cpp b/tests/physics/src/BulletUtilTests.cpp new file mode 100644 index 0000000000..06b1815c6e --- /dev/null +++ b/tests/physics/src/BulletUtilTests.cpp @@ -0,0 +1,114 @@ +// +// BulletUtilTests.cpp +// tests/physics/src +// +// Created by Andrew Meadows on 2014.11.02 +// Copyright 2014 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 + +#include +#include + +#include "BulletUtilTests.h" + +#ifdef USE_BULLET_PHYSICS +void BulletUtilTests::fromBulletToGLM() { + btVector3 bV(1.23f, 4.56f, 7.89f); + glm::vec3 gV = bulletToGLM(bV); + if (gV.x != bV.getX()) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: x mismatch bullet.x = " << bV.getX() << " != glm.x = " << gV.x << std::endl; + } + if (gV.y != bV.getY()) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: x mismatch bullet.y = " << bV.getY() << " != glm.y = " << gV.y << std::endl; + } + if (gV.z != bV.getZ()) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: x mismatch bullet.z = " << bV.getZ() << " != glm.z = " << gV.z << std::endl; + } + + float angle = 0.317f * PI; + btVector3 axis(1.23f, 2.34f, 3.45f); + axis.normalize(); + btQuaternion bQ(axis, angle); + + glm::quat gQ = bulletToGLM(bQ); + if (gQ.x != bQ.getX()) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: x mismatch bullet.x = " << bQ.getX() << " != glm.x = " << gQ.x << std::endl; + } + if (gQ.y != bQ.getY()) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: x mismatch bullet.y = " << bQ.getY() << " != glm.y = " << gQ.y << std::endl; + } + if (gQ.z != bQ.getZ()) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: x mismatch bullet.z = " << bQ.getZ() << " != glm.z = " << gQ.z << std::endl; + } + if (gQ.w != bQ.getW()) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: x mismatch bullet.w = " << bQ.getW() << " != glm.w = " << gQ.w << std::endl; + } +} + +void BulletUtilTests::fromGLMToBullet() { + glm::vec3 gV(1.23f, 4.56f, 7.89f); + btVector3 bV = glmToBullet(gV); + if (gV.x != bV.getX()) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: x mismatch glm.x = " << gV.x << " != bullet.x = " << bV.getX() << std::endl; + } + if (gV.y != bV.getY()) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: x mismatch glm.y = " << gV.y << " != bullet.y = " << bV.getY() << std::endl; + } + if (gV.z != bV.getZ()) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: x mismatch glm.z = " << gV.z << " != bullet.z = " << bV.getZ() << std::endl; + } + + float angle = 0.317f * PI; + btVector3 axis(1.23f, 2.34f, 3.45f); + axis.normalize(); + btQuaternion bQ(axis, angle); + + glm::quat gQ = bulletToGLM(bQ); + if (gQ.x != bQ.getX()) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: x mismatch glm.x = " << gQ.x << " != bullet.x = " << bQ.getX() << std::endl; + } + if (gQ.y != bQ.getY()) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: x mismatch glm.y = " << gQ.y << " != bullet.y = " << bQ.getY() << std::endl; + } + if (gQ.z != bQ.getZ()) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: x mismatch glm.z = " << gQ.z << " != bullet.z = " << bQ.getZ() << std::endl; + } + if (gQ.w != bQ.getW()) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: x mismatch glm.w = " << gQ.w << " != bullet.w = " << bQ.getW() << std::endl; + } +} + +void BulletUtilTests::runAllTests() { + fromBulletToGLM(); + fromGLMToBullet(); +} + +#else // USE_BULLET_PHYSICS +void BulletUtilTests::fromBulletToGLM() { +} + +void BulletUtilTests::fromGLMToBullet() { +} + +void BulletUtilTests::runAllTests() { +} +#endif // USE_BULLET_PHYSICS diff --git a/tests/physics/src/BulletUtilTests.h b/tests/physics/src/BulletUtilTests.h new file mode 100644 index 0000000000..b885777bdd --- /dev/null +++ b/tests/physics/src/BulletUtilTests.h @@ -0,0 +1,21 @@ +// +// BulletUtilTests.h +// tests/physics/src +// +// Created by Andrew Meadows on 2014.11.02 +// Copyright 2014 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_BulletUtilTests_h +#define hifi_BulletUtilTests_h + +namespace BulletUtilTests { + void fromBulletToGLM(); + void fromGLMToBullet(); + void runAllTests(); +} + +#endif // hifi_BulletUtilTests_h diff --git a/tests/physics/src/ShapeInfoTests.cpp b/tests/physics/src/ShapeInfoTests.cpp new file mode 100644 index 0000000000..a4db3a5764 --- /dev/null +++ b/tests/physics/src/ShapeInfoTests.cpp @@ -0,0 +1,260 @@ +// +// ShapeInfoTests.cpp +// tests/physics/src +// +// Created by Andrew Meadows on 2014.11.02 +// Copyright 2014 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 + +#ifdef USE_BULLET_PHYSICS +#include +#include +#endif // USE_BULLET_PHYSICS + +#include +#include +#include +#include + +#include "ShapeInfoTests.h" + +void ShapeInfoTests::testHashFunctions() { +#ifdef USE_BULLET_PHYSICS + int maxTests = 10000000; + ShapeInfo info; + btHashMap hashes; + + int bits[32]; + unsigned int masks[32]; + for (int i = 0; i < 32; ++i) { + bits[i] = 0; + masks[i] = 1U << i; + } + + float deltaLength = 0.002f; + float endLength = 100.0f; + int numSteps = (int)(endLength / deltaLength); + + int testCount = 0; + int numCollisions = 0; + btClock timer; + for (int x = 1; x < numSteps && testCount < maxTests; ++x) { + float radiusX = (float)x * deltaLength; + // test sphere + info.setSphere(radiusX); + ++testCount; + DoubleHashKey key = ShapeInfoUtil::computeHash(info); + int* hashPtr = hashes.find(key._hash); + if (hashPtr && *hashPtr == key._hash2) { + std::cout << testCount << " hash collision radiusX = " << radiusX + << " h1 = 0x" << std::hex << (unsigned int)(key._hash) + << " h2 = 0x" << std::hex << (unsigned int)(key._hash2) + << std::endl; + ++numCollisions; + assert(false); + } else { + hashes.insert(key._hash, key._hash2); + } + for (int k = 0; k < 32; ++k) { + if (masks[k] & key._hash2) { + ++bits[k]; + } + } + + for (int y = 1; y < numSteps && testCount < maxTests; ++y) { + float radiusY = (float)y * deltaLength; + // test cylinder and capsule + int types[] = { CYLINDER_SHAPE_PROXYTYPE, CAPSULE_SHAPE_PROXYTYPE }; + for (int i = 0; i < 2; ++i) { + switch(types[i]) { + case CYLINDER_SHAPE_PROXYTYPE: { + info.setCylinder(radiusX, radiusY); + break; + } + case CAPSULE_SHAPE_PROXYTYPE: { + info.setCapsule(radiusX, radiusY); + break; + } + } + + ++testCount; + key = ShapeInfoUtil::computeHash(info); + hashPtr = hashes.find(key._hash); + if (hashPtr && *hashPtr == key._hash2) { + std::cout << testCount << " hash collision radiusX = " << radiusX << " radiusY = " << radiusY + << " h1 = 0x" << std::hex << (unsigned int)(key._hash) + << " h2 = 0x" << std::hex << (unsigned int)(key._hash2) + << std::endl; + ++numCollisions; + assert(false); + } else { + hashes.insert(key._hash, key._hash2); + } + for (int k = 0; k < 32; ++k) { + if (masks[k] & key._hash2) { + ++bits[k]; + } + } + } + + for (int z = 1; z < numSteps && testCount < maxTests; ++z) { + float radiusZ = (float)z * deltaLength; + // test box + info.setBox(glm::vec3(radiusX, radiusY, radiusZ)); + ++testCount; + DoubleHashKey key = ShapeInfoUtil::computeHash(info); + hashPtr = hashes.find(key._hash); + if (hashPtr && *hashPtr == key._hash2) { + std::cout << testCount << " hash collision radiusX = " << radiusX + << " radiusY = " << radiusY << " radiusZ = " << radiusZ + << " h1 = 0x" << std::hex << (unsigned int)(key._hash) + << " h2 = 0x" << std::hex << (unsigned int)(key._hash2) + << std::endl; + ++numCollisions; + assert(false); + } else { + hashes.insert(key._hash, key._hash2); + } + for (int k = 0; k < 32; ++k) { + if (masks[k] & key._hash2) { + ++bits[k]; + } + } + } + } + } + unsigned long int msec = timer.getTimeMilliseconds(); + std::cout << msec << " msec with " << numCollisions << " collisions out of " << testCount << " hashes" << std::endl; + + // print out distribution of bits + for (int i = 0; i < 32; ++i) { + std::cout << "bit 0x" << std::hex << masks[i] << std::dec << " = " << bits[i] << std::endl; + } +#endif // USE_BULLET_PHYSICS +} + +void ShapeInfoTests::testBoxShape() { +#ifdef USE_BULLET_PHYSICS + ShapeInfo info; + glm::vec3 halfExtents(1.23f, 4.56f, 7.89f); + info.setBox(halfExtents); + DoubleHashKey key = ShapeInfoUtil::computeHash(info); + + btCollisionShape* shape = ShapeInfoUtil::createShapeFromInfo(info); + if (!shape) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: NULL Box shape" << std::endl; + } + + ShapeInfo otherInfo; + ShapeInfoUtil::collectInfoFromShape(shape, otherInfo); + + DoubleHashKey otherKey = ShapeInfoUtil::computeHash(otherInfo); + if (key._hash != otherKey._hash) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: expected Box shape hash = " << key._hash << " but found hash = " << otherKey._hash << std::endl; + } + + if (key._hash2 != otherKey._hash2) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: expected Box shape hash2 = " << key._hash2 << " but found hash2 = " << otherKey._hash2 << std::endl; + } + + delete shape; +#endif // USE_BULLET_PHYSICS +} + +void ShapeInfoTests::testSphereShape() { +#ifdef USE_BULLET_PHYSICS + ShapeInfo info; + float radius = 1.23f; + info.setSphere(radius); + DoubleHashKey key = ShapeInfoUtil::computeHash(info); + + btCollisionShape* shape = ShapeInfoUtil::createShapeFromInfo(info); + + ShapeInfo otherInfo; + ShapeInfoUtil::collectInfoFromShape(shape, otherInfo); + + DoubleHashKey otherKey = ShapeInfoUtil::computeHash(otherInfo); + if (key._hash != otherKey._hash) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: expected Sphere shape hash = " << key._hash << " but found hash = " << otherKey._hash << std::endl; + } + if (key._hash2 != otherKey._hash2) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: expected Sphere shape hash2 = " << key._hash2 << " but found hash2 = " << otherKey._hash2 << std::endl; + } + + delete shape; +#endif // USE_BULLET_PHYSICS +} + +void ShapeInfoTests::testCylinderShape() { +#ifdef USE_BULLET_PHYSICS + ShapeInfo info; + float radius = 1.23f; + float height = 4.56f; + info.setCylinder(radius, height); + DoubleHashKey key = ShapeInfoUtil::computeHash(info); + + btCollisionShape* shape = ShapeInfoUtil::createShapeFromInfo(info); + + ShapeInfo otherInfo; + ShapeInfoUtil::collectInfoFromShape(shape, otherInfo); + + DoubleHashKey otherKey = ShapeInfoUtil::computeHash(otherInfo); + if (key._hash != otherKey._hash) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: expected Cylinder shape hash = " << key._hash << " but found hash = " << otherKey._hash << std::endl; + } + if (key._hash2 != otherKey._hash2) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: expected Cylinder shape hash2 = " << key._hash2 << " but found hash2 = " << otherKey._hash2 << std::endl; + } + + delete shape; +#endif // USE_BULLET_PHYSICS +} + +void ShapeInfoTests::testCapsuleShape() { +#ifdef USE_BULLET_PHYSICS + ShapeInfo info; + float radius = 1.23f; + float height = 4.56f; + info.setCapsule(radius, height); + DoubleHashKey key = ShapeInfoUtil::computeHash(info); + + btCollisionShape* shape = ShapeInfoUtil::createShapeFromInfo(info); + + ShapeInfo otherInfo; + ShapeInfoUtil::collectInfoFromShape(shape, otherInfo); + + DoubleHashKey otherKey = ShapeInfoUtil::computeHash(otherInfo); + if (key._hash != otherKey._hash) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: expected Capsule shape hash = " << key._hash << " but found hash = " << otherKey._hash << std::endl; + } + if (key._hash2 != otherKey._hash2) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: expected Capsule shape hash2 = " << key._hash2 << " but found hash2 = " << otherKey._hash2 << std::endl; + } + + delete shape; +#endif // USE_BULLET_PHYSICS +} + +void ShapeInfoTests::runAllTests() { +//#define MANUAL_TEST +#ifdef MANUAL_TEST + testHashFunctions(); +#endif // MANUAL_TEST + testBoxShape(); + testSphereShape(); + testCylinderShape(); + testCapsuleShape(); +} diff --git a/tests/physics/src/ShapeInfoTests.h b/tests/physics/src/ShapeInfoTests.h new file mode 100644 index 0000000000..b786ca92d5 --- /dev/null +++ b/tests/physics/src/ShapeInfoTests.h @@ -0,0 +1,24 @@ +// +// ShapeInfoTests.h +// tests/physics/src +// +// Created by Andrew Meadows on 2014.11.02 +// Copyright 2014 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_ShapeInfoTests_h +#define hifi_ShapeInfoTests_h + +namespace ShapeInfoTests { + void testHashFunctions(); + void testBoxShape(); + void testSphereShape(); + void testCylinderShape(); + void testCapsuleShape(); + void runAllTests(); +} + +#endif // hifi_ShapeInfoTests_h diff --git a/tests/physics/src/ShapeManagerTests.cpp b/tests/physics/src/ShapeManagerTests.cpp new file mode 100644 index 0000000000..5be247f25f --- /dev/null +++ b/tests/physics/src/ShapeManagerTests.cpp @@ -0,0 +1,247 @@ +// +// ShapeManagerTests.cpp +// tests/physics/src +// +// Created by Andrew Meadows on 2014.10.30 +// Copyright 2014 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 +#include +#include +#include + +#include "ShapeManagerTests.h" + +void ShapeManagerTests::testShapeAccounting() { +#ifdef USE_BULLET_PHYSICS + ShapeManager shapeManager; + ShapeInfo info; + info.setBox(glm::vec3(1.0f, 1.0f, 1.0f)); + + // NOTE: ShapeManager returns -1 as refcount when the shape is unknown, + // which is distinct from "known but with zero references" + int numReferences = shapeManager.getNumReferences(info); + if (numReferences != -1) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: expected ignorant ShapeManager after initialization" << std::endl; + } + + // create one shape and verify we get a valid pointer + btCollisionShape* shape = shapeManager.getShape(info); + if (!shape) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: expected shape creation for default parameters" << std::endl; + } + + // verify number of shapes + if (shapeManager.getNumShapes() != 1) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: expected one shape" << std::endl; + } + + // reference the shape again and verify that we get the same pointer + btCollisionShape* otherShape = shapeManager.getShape(info); + if (otherShape != shape) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: expected shape* " << (void*)(shape) + << " but found shape* " << (void*)(otherShape) << std::endl; + } + + // verify number of references + numReferences = shapeManager.getNumReferences(info); + int expectedNumReferences = 2; + if (numReferences != expectedNumReferences) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: expected " << expectedNumReferences + << " references but found " << numReferences << std::endl; + } + + // release all references + bool released = shapeManager.releaseShape(info); + numReferences--; + while (numReferences > 0) { + released = shapeManager.releaseShape(info) && released; + numReferences--; + } + if (!released) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: expected shape released" << std::endl; + } + + // verify shape still exists (not yet garbage collected) + if (shapeManager.getNumShapes() != 1) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: expected one shape after release but before garbage collection" << std::endl; + } + + // verify shape's refcount is zero + numReferences = shapeManager.getNumReferences(info); + if (numReferences != 0) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: expected refcount = 0 for shape but found refcount = " << numReferences << std::endl; + } + + // reference the shape again and verify refcount is updated + otherShape = shapeManager.getShape(info); + numReferences = shapeManager.getNumReferences(info); + if (numReferences != 1) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: expected refcount = 1 for shape but found refcount = " << numReferences << std::endl; + } + + // verify that shape is not collected as garbage + shapeManager.collectGarbage(); + if (shapeManager.getNumShapes() != 1) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: expected one shape after release" << std::endl; + } + numReferences = shapeManager.getNumReferences(info); + if (numReferences != 1) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: expected refcount = 1 for shape but found refcount = " << numReferences << std::endl; + } + + // release reference and verify that it is collected as garbage + released = shapeManager.releaseShape(info); + shapeManager.collectGarbage(); + if (shapeManager.getNumShapes() != 0) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: expected zero shapes after release" << std::endl; + } + numReferences = shapeManager.getNumReferences(info); + if (numReferences != -1) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: expected ignorant ShapeManager after garbage collection" << std::endl; + } + + // add the shape again and verify that it gets added again + otherShape = shapeManager.getShape(info); + numReferences = shapeManager.getNumReferences(info); + if (numReferences != 1) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: expected refcount = 1 for shape but found refcount = " << numReferences << std::endl; + } +#endif // USE_BULLET_PHYSICS +} + +void ShapeManagerTests::addManyShapes() { +#ifdef USE_BULLET_PHYSICS + ShapeManager shapeManager; + + int numSizes = 100; + float startSize = 1.0f; + float endSize = 99.0f; + float deltaSize = (endSize - startSize) / (float)numSizes; + ShapeInfo info; + for (int i = 0; i < numSizes; ++i) { + float s = startSize + (float)i * deltaSize; + glm::vec3 scale(s, 1.23f + s, s - 0.573f); + info.setBox(0.5f * scale); + btCollisionShape* shape = shapeManager.getShape(info); + if (!shape) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: i = " << i << " null box shape for scale = " << scale << std::endl; + } + float radius = 0.5f * s; + info.setSphere(radius); + shape = shapeManager.getShape(info); + if (!shape) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: i = " << i << " null sphere shape for radius = " << radius << std::endl; + } + } + int numShapes = shapeManager.getNumShapes(); + if (numShapes != 2 * numSizes) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: expected numShapes = " << numSizes << " but found numShapes = " << numShapes << std::endl; + } +#endif // USE_BULLET_PHYSICS +} + +void ShapeManagerTests::addBoxShape() { +#ifdef USE_BULLET_PHYSICS + ShapeInfo info; + glm::vec3 halfExtents(1.23f, 4.56f, 7.89f); + info.setBox(halfExtents); + + ShapeManager shapeManager; + btCollisionShape* shape = shapeManager.getShape(info); + + ShapeInfo otherInfo; + ShapeInfoUtil::collectInfoFromShape(shape, otherInfo); + + btCollisionShape* otherShape = shapeManager.getShape(otherInfo); + if (shape != otherShape) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: Box ShapeInfo --> shape --> ShapeInfo --> shape did not work" << std::endl; + } +#endif // USE_BULLET_PHYSICS +} + +void ShapeManagerTests::addSphereShape() { +#ifdef USE_BULLET_PHYSICS + ShapeInfo info; + float radius = 1.23f; + info.setSphere(radius); + + ShapeManager shapeManager; + btCollisionShape* shape = shapeManager.getShape(info); + + ShapeInfo otherInfo; + ShapeInfoUtil::collectInfoFromShape(shape, otherInfo); + + btCollisionShape* otherShape = shapeManager.getShape(otherInfo); + if (shape != otherShape) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: Sphere ShapeInfo --> shape --> ShapeInfo --> shape did not work" << std::endl; + } +#endif // USE_BULLET_PHYSICS +} + +void ShapeManagerTests::addCylinderShape() { +#ifdef USE_BULLET_PHYSICS + ShapeInfo info; + float radius = 1.23f; + float height = 4.56f; + info.setCylinder(radius, height); + + ShapeManager shapeManager; + btCollisionShape* shape = shapeManager.getShape(info); + + ShapeInfo otherInfo; + ShapeInfoUtil::collectInfoFromShape(shape, otherInfo); + + btCollisionShape* otherShape = shapeManager.getShape(otherInfo); + if (shape != otherShape) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: Cylinder ShapeInfo --> shape --> ShapeInfo --> shape did not work" << std::endl; + } +#endif // USE_BULLET_PHYSICS +} + +void ShapeManagerTests::addCapsuleShape() { +#ifdef USE_BULLET_PHYSICS + ShapeInfo info; + float radius = 1.23f; + float height = 4.56f; + info.setCapsule(radius, height); + + ShapeManager shapeManager; + btCollisionShape* shape = shapeManager.getShape(info); + + ShapeInfo otherInfo; + ShapeInfoUtil::collectInfoFromShape(shape, otherInfo); + + btCollisionShape* otherShape = shapeManager.getShape(otherInfo); + if (shape != otherShape) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: Capsule ShapeInfo --> shape --> ShapeInfo --> shape did not work" << std::endl; + } +#endif // USE_BULLET_PHYSICS +} + +void ShapeManagerTests::runAllTests() { + testShapeAccounting(); + addManyShapes(); + addBoxShape(); + addSphereShape(); + addCylinderShape(); + addCapsuleShape(); +} diff --git a/tests/physics/src/ShapeManagerTests.h b/tests/physics/src/ShapeManagerTests.h new file mode 100644 index 0000000000..98703fda1e --- /dev/null +++ b/tests/physics/src/ShapeManagerTests.h @@ -0,0 +1,25 @@ +// +// ShapeManagerTests.h +// tests/physics/src +// +// Created by Andrew Meadows on 2014.10.30 +// Copyright 2014 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_ShapeManagerTests_h +#define hifi_ShapeManagerTests_h + +namespace ShapeManagerTests { + void testShapeAccounting(); + void addManyShapes(); + void addBoxShape(); + void addSphereShape(); + void addCylinderShape(); + void addCapsuleShape(); + void runAllTests(); +} + +#endif // hifi_ShapeManagerTests_h diff --git a/tests/physics/src/main.cpp b/tests/physics/src/main.cpp index 086bff4dcd..bcf26f4115 100644 --- a/tests/physics/src/main.cpp +++ b/tests/physics/src/main.cpp @@ -10,9 +10,15 @@ #include "ShapeColliderTests.h" #include "VerletShapeTests.h" +#include "ShapeInfoTests.h" +#include "ShapeManagerTests.h" +#include "BulletUtilTests.h" int main(int argc, char** argv) { ShapeColliderTests::runAllTests(); VerletShapeTests::runAllTests(); + ShapeInfoTests::runAllTests(); + ShapeManagerTests::runAllTests(); + BulletUtilTests::runAllTests(); return 0; }