From d26540b029b8ab6da29287c4761582558aef89a6 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 31 Oct 2014 17:13:17 -0700 Subject: [PATCH] ShapeManager now under unit test --- libraries/physics/CMakeLists.txt | 9 +- libraries/physics/src/PhysicsEntity.h | 4 + libraries/physics/src/ShapeInfo.cpp | 159 +++++++++++++++++++++ libraries/physics/src/ShapeInfo.h | 187 +++++++++++++++++++++++++ libraries/physics/src/ShapeManager.cpp | 35 ++++- libraries/physics/src/ShapeManager.h | 187 +++++++++++++++++++++---- tests/physics/CMakeLists.txt | 2 +- tests/physics/src/main.cpp | 6 +- 8 files changed, 544 insertions(+), 45 deletions(-) create mode 100644 libraries/physics/src/ShapeInfo.cpp create mode 100644 libraries/physics/src/ShapeInfo.h diff --git a/libraries/physics/CMakeLists.txt b/libraries/physics/CMakeLists.txt index ec289cd096..4799deea3d 100644 --- a/libraries/physics/CMakeLists.txt +++ b/libraries/physics/CMakeLists.txt @@ -4,16 +4,9 @@ set(TARGET_NAME physics) setup_hifi_library() include_glm() +include_bullet() link_hifi_libraries(shared) -# Note: we rely on the default FindBullet.cmake moduld that comes with cmake -find_package(Bullet) -if (BULLET_FOUND) - include_directories(SYSTEM "${BULLET_INCLUDE_DIRS}") - list(APPEND ${TARGET_NAME}_LIBRARIES_TO_LINK "${BULLET_LIBRARIES}") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DUSE_BULLET_PHYSICS") -endif (BULLET_FOUND) - # call macro to link our dependencies and bubble them up via a property on our target link_shared_dependencies() diff --git a/libraries/physics/src/PhysicsEntity.h b/libraries/physics/src/PhysicsEntity.h index fc39dcb7fc..0487b9b078 100644 --- a/libraries/physics/src/PhysicsEntity.h +++ b/libraries/physics/src/PhysicsEntity.h @@ -18,6 +18,10 @@ #include #include +#ifdef USE_BULLET_PHYSICS +#include "PhysicsWorld.h" +#endif // USE_BULLET_PHYSICS + #include "CollisionInfo.h" #include "RayIntersectionInfo.h" diff --git a/libraries/physics/src/ShapeInfo.cpp b/libraries/physics/src/ShapeInfo.cpp new file mode 100644 index 0000000000..03dc162f6b --- /dev/null +++ b/libraries/physics/src/ShapeInfo.cpp @@ -0,0 +1,159 @@ +// +// 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 +// + +#ifdef USE_BULLET_PHYSICS + +#include + +#include "ShapeInfo.h" + +void ShapeInfo::getInfo(const btCollisionShape* shape) { + _data.clear(); + if (shape) { + _type = (unsigned int)(shape->getShapeType()); + switch(_type) { + case BOX_SHAPE_PROXYTYPE: + { + const btBoxShape* boxShape = static_cast(shape); + _data.push_back(boxShape->getHalfExtentsWithMargin()); + } + break; + case SPHERE_SHAPE_PROXYTYPE: + { + const btSphereShape* sphereShape = static_cast(shape); + _data.push_back(btVector3(0.0f, 0.0f, sphereShape->getRadius())); + } + break; + case CYLINDER_SHAPE_PROXYTYPE: + { + const btCylinderShape* cylinderShape = static_cast(shape); + _data.push_back(cylinderShape->getHalfExtentsWithMargin()); + } + break; + case CAPSULE_SHAPE_PROXYTYPE: + { + const btCapsuleShape* capsuleShape = static_cast(shape); + _data.push_back(btVector3(capsuleShape->getRadius(), capsuleShape->getHalfHeight(), 0.0f)); + // NOTE: we only support capsules with axis along yAxis + } + break; + default: + _type = INVALID_SHAPE_PROXYTYPE; + break; + } + } else { + _type = INVALID_SHAPE_PROXYTYPE; + } +} + +void ShapeInfo::setBox(const btVector3& halfExtents) { + _type = BOX_SHAPE_PROXYTYPE; + _data.clear(); + _data.push_back(halfExtents); +} + +void ShapeInfo::setSphere(float radius) { + _type = SPHERE_SHAPE_PROXYTYPE; + _data.clear(); + _data.push_back(btVector3(0.0f, 0.0f, radius)); +} + +void ShapeInfo::setCylinder(float radius, float height) { + _type = CYLINDER_SHAPE_PROXYTYPE; + _data.clear(); + // NOTE: default cylinder has (UpAxis = 1) axis along yAxis and radius stored in X + // halfExtents = btVector3(radius, halfHeight, unused) + _data.push_back(btVector3(radius, 0.5f * height, radius)); +} + +void ShapeInfo::setCapsule(float radius, float height) { + _type = CAPSULE_SHAPE_PROXYTYPE; + _data.clear(); + _data.push_back(btVector3(radius, 0.5f * height, 0.0f)); +} + +int ShapeInfo::computeHash() const { + // This hash algorithm works well for shapes that have dimensions less than about 256m. + // At larger values the likelihood of hash collision goes up because of the most + // significant bits are pushed off the top and the result could be the same as for smaller + // dimensions (truncation). + // + // The algorithm may produce collisions for shapes whose dimensions differ by less than + // ~1/256 m, however this is by design -- we don't expect collision differences smaller + // than 1 mm to be noticable. + unsigned int key = 0; + btVector3 tempData; + int numData = _data.size(); + for (int i = 0; i < numData; ++i) { + // Successively multiply components of each vec3 by primes near 512 and convert to U32 + // to spread the data across more bits. Note that all dimensions are at half-value + // (half extents, radius, etc) which is why we multiply by primes near 512 rather + // than 256. + tempData = _data[i]; + key += (unsigned int)(509.0f * (tempData.getZ() + 0.01f)) + + 509 * (unsigned int)(521.0f * (tempData.getY() + 0.01f)) + + (509 * 521) * (unsigned int)(523.0f * (tempData.getX() + 0.01f)); + // avalanch the bits + key += ~(key << 15); + key ^= (key >> 10); + key += (key << 3); + key ^= (key >> 6); + key += ~(key << 11); + key ^= (key >> 16); + } + // finally XOR with type + return (int)(key ^ _type); +} + +btCollisionShape* ShapeInfo::createShape() const { + btCollisionShape* shape = NULL; + int numData = _data.size(); + switch(_type) { + case BOX_SHAPE_PROXYTYPE: + { + if (numData > 0) { + btVector3 halfExtents = _data[0]; + shape = new btBoxShape(halfExtents); + } + } + break; + case SPHERE_SHAPE_PROXYTYPE: + { + if (numData > 0) { + float radius = _data[0].getZ(); + shape = new btSphereShape(radius); + } + } + break; + case CYLINDER_SHAPE_PROXYTYPE: + { + if (numData > 0) { + btVector3 halfExtents = _data[0]; + // NOTE: default cylinder has (UpAxis = 1) axis along yAxis and radius stored in X + // halfExtents = btVector3(radius, halfHeight, unused) + shape = new btCylinderShape(halfExtents); + } + } + break; + case CAPSULE_SHAPE_PROXYTYPE: + { + if (numData > 0) { + float radius = _data[0].getX(); + float height = 2.0f * _data[0].getY(); + shape = new btCapsuleShape(radius, height); + } + } + break; + } + return shape; +} + +#endif // USE_BULLET_PHYSICS diff --git a/libraries/physics/src/ShapeInfo.h b/libraries/physics/src/ShapeInfo.h new file mode 100644 index 0000000000..86063694ca --- /dev/null +++ b/libraries/physics/src/ShapeInfo.h @@ -0,0 +1,187 @@ +// +// 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 + +#ifdef USE_BULLET_PHYSICS + +#include +#include + +const float DEFAULT_MARGIN = 0.04f; + +class ShapeInfo { +public: + ShapeInfo() : _type(INVALID_SHAPE_PROXYTYPE) {} + + ShapeInfo(const btCollisionShape* shape) : _type(INVALID_SHAPE_PROXYTYPE) { + getInfo(shape); + } + + // BOOKMARK -- move ShapeInfo to its own files + void getInfo(const btCollisionShape* shape); + /*{ + _data.clear(); + if (shape) { + _type = (unsigned int)(shape->getShapeType()); + switch(_type) { + case BOX_SHAPE_PROXYTYPE: + { + const btBoxShape* boxShape = static_cast(shape); + _data.push_back(boxShape->getHalfExtentsWithMargin()); + } + break; + case SPHERE_SHAPE_PROXYTYPE: + { + const btSphereShape* sphereShape = static_cast(shape); + _data.push_back(btVector3(0.0f, 0.0f, sphereShape->getRadius())); + } + break; + case CYLINDER_SHAPE_PROXYTYPE: + { + const btCylinderShape* cylinderShape = static_cast(shape); + _data.push_back(cylinderShape->getHalfExtentsWithMargin()); + } + break; + case CAPSULE_SHAPE_PROXYTYPE: + { + const btCapsuleShape* capsuleShape = static_cast(shape); + _data.push_back(btVector3(capsuleShape->getRadius(), capsuleShape->getHalfHeight(), 0.0f)); + // NOTE: we only support capsules with axis along yAxis + } + break; + default: + _type = INVALID_SHAPE_PROXYTYPE; + break; + } + } else { + _type = INVALID_SHAPE_PROXYTYPE; + } + }*/ + + void setBox(const btVector3& halfExtents); + /*{ + _type = BOX_SHAPE_PROXYTYPE; + _data.clear(); + _data.push_back(halfExtents); + }*/ + + void setSphere(float radius); + /* { + _type = SPHERE_SHAPE_PROXYTYPE; + _data.clear(); + _data.push_back(btVector3(0.0f, 0.0f, radius)); + }*/ + + void setCylinder(float radius, float height); + /*{ + _type = CYLINDER_SHAPE_PROXYTYPE; + _data.clear(); + // NOTE: default cylinder has (UpAxis = 1) axis along yAxis and radius stored in X + // halfExtents = btVector3(radius, halfHeight, unused) + _data.push_back(btVector3(radius, 0.5f * height, radius)); + }*/ + + void setCapsule(float radius, float height); + /*{ + _type = CAPSULE_SHAPE_PROXYTYPE; + _data.clear(); + _data.push_back(btVector3(radius, 0.5f * height, 0.0f)); + }*/ + + int computeHash() const; + /*{ + // This hash algorithm works well for shapes that have dimensions less than about 256m. + // At larger values the likelihood of hash collision goes up because of the most + // significant bits are pushed off the top and the result could be the same as for smaller + // dimensions (truncation). + // + // The algorithm may produce collisions for shapes whose dimensions differ by less than + // ~1/256 m, however this is by design -- we don't expect collision differences smaller + // than 1 mm to be noticable. + unsigned int key = 0; + btVector3 tempData; + int numData = _data.size(); + for (int i = 0; i < numData; ++i) { + // Successively multiply components of each vec3 by primes near 512 and convert to U32 + // to spread the data across more bits. Note that all dimensions are at half-value + // (half extents, radius, etc) which is why we multiply by primes near 512 rather + // than 256. + tempData = _data[i]; + key += (unsigned int)(509.0f * (tempData.getZ() + 0.01f)) + + 509 * (unsigned int)(521.0f * (tempData.getY() + 0.01f)) + + (509 * 521) * (unsigned int)(523.0f * (tempData.getX() + 0.01f)); + // avalanch the bits + key += ~(key << 15); + key ^= (key >> 10); + key += (key << 3); + key ^= (key >> 6); + key += ~(key << 11); + key ^= (key >> 16); + } + // finally XOR with type + return (int)(key ^ _type); + }*/ + +private: + friend class ShapeManager; + + btCollisionShape* createShape() const; + /*{ + btCollisionShape* shape = NULL; + int numData = _data.size(); + switch(_type) { + case BOX_SHAPE_PROXYTYPE: + { + if (numData > 0) { + btVector3 halfExtents = _data[0]; + shape = new btBoxShape(halfExtents); + } + } + break; + case SPHERE_SHAPE_PROXYTYPE: + { + if (numData > 0) { + float radius = _data[0].getZ(); + shape = new btSphereShape(radius); + } + } + break; + case CYLINDER_SHAPE_PROXYTYPE: + { + if (numData > 0) { + btVector3 halfExtents = _data[0]; + // NOTE: default cylinder has (UpAxis = 1) axis along yAxis and radius stored in X + // halfExtents = btVector3(radius, halfHeight, unused) + shape = new btCylinderShape(halfExtents); + } + } + break; + case CAPSULE_SHAPE_PROXYTYPE: + { + if (numData > 0) { + float radius = _data[0].getX(); + float height = 2.0f * _data[0].getY(); + shape = new btCapsuleShape(radius, height); + } + } + break; + } + return shape; + }*/ + + int _type; + btAlignedObjectArray _data; +}; + +#endif // USE_BULLET_PHYSICS +#endif // hifi_ShapeInfo_h diff --git a/libraries/physics/src/ShapeManager.cpp b/libraries/physics/src/ShapeManager.cpp index 99c1d8b2ef..9874f24859 100644 --- a/libraries/physics/src/ShapeManager.cpp +++ b/libraries/physics/src/ShapeManager.cpp @@ -25,7 +25,7 @@ ShapeManager::~ShapeManager() { btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) { - int key = info.getHash(); + int key = info.computeHash(); ShapeReference* shapeRef = _shapeMap.find(key); if (shapeRef) { shapeRef->_refCount++; @@ -43,7 +43,7 @@ btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) { } bool ShapeManager::releaseShape(const ShapeInfo& info) { - int key = info.getHash(); + int key = info.computeHash(); ShapeReference* shapeRef = _shapeMap.find(key); if (shapeRef) { if (shapeRef->_refCount > 0) { @@ -67,6 +67,35 @@ bool ShapeManager::releaseShape(const ShapeInfo& info) { return false; } +/* +bool ShapeManager::releaseShape(const btCollisionShape* shape) { + // when the number of shapes is high it's probably cheaper to try to construct a ShapeInfo + // and then compute the hash rather than walking the list in search of the pointer. + int key = info.computeHash(); + 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; +} +*/ + void ShapeManager::collectGarbage() { int numShapes = _pendingGarbage.size(); for (int i = 0; i < numShapes; ++i) { @@ -82,7 +111,7 @@ void ShapeManager::collectGarbage() { } int ShapeManager::getNumReferences(const ShapeInfo& info) const { - int key = info.getHash(); + int key = info.computeHash(); const ShapeReference* shapeRef = _shapeMap.find(key); if (shapeRef) { return shapeRef->_refCount; diff --git a/libraries/physics/src/ShapeManager.h b/libraries/physics/src/ShapeManager.h index 0a1f75c7c8..c683a041a3 100644 --- a/libraries/physics/src/ShapeManager.h +++ b/libraries/physics/src/ShapeManager.h @@ -17,48 +17,166 @@ #include #include +#include "ShapeInfo.h" + +/* struct ShapeInfo { int _type; - btVector3 _size; + btAlignedObjectArray _data; + //btVector3 _scale; - ShapeInfo() : _type(BOX_SHAPE_PROXYTYPE), _size(1.0f, 1.0f, 1.0f) {} + ShapeInfo() : _type(INVALID_SHAPE_PROXYTYPE) {} - virtual int getHash() const { - // successfully multiply components of size by primes near 256 and convert to U32 - unsigned int key = (unsigned int)(241.0f * _size.getX()) - + 241 * (unsigned int)(251.0f * _size.getY()) - + (241 * 251) * (unsigned int)(257.0f * _size.getZ()); - // then scramble the results - key += ~(key << 15); - key ^= (key >> 10); - key += (key << 3); - key ^= (key >> 6); - key += ~(key << 11); - key ^= (key >> 16); - // finally XOR with type - key ^= _type; - return key; + ShapeInfo(const btCollisionShape* shape) : _type(INVALID_SHAPE_PROXYTYPE) { + getInfo(shape); } + // BOOKMARK -- move ShapeInfo to its own files + void getInfo(const btCollisionShape* shape) { + _data.clear(); + if (shape) { + _type = (unsigned int)(shape->getShapeType()); + switch(_type) { + case BOX_SHAPE_PROXYTYPE: + { + const btBoxShape* boxShape = static_cast(shape); + _data.push_back(boxShape->getHalfExtentsWithMargin()); + } + break; + case SPHERE_SHAPE_PROXYTYPE: + { + const btSphereShape* sphereShape = static_cast(shape); + _data.push_back(btVector3(0.0f, 0.0f, sphereShape->getRadius())); + } + break; + case CYLINDER_SHAPE_PROXYTYPE: + { + const btCylinderShape* cylinderShape = static_cast(shape); + _data.push_back(cylinderShape->getHalfExtentsWithMargin()); + } + break; + case CAPSULE_SHAPE_PROXYTYPE: + { + const btCapsuleShape* capsuleShape = static_cast(shape); + _data.push_back(btVector3(capsuleShape->getRadius(), capsuleShape->getHalfHeight(), 0.0f)); + // NOTE: we only support capsules with axis along yAxis + } + break; + default: + _type = INVALID_SHAPE_PROXYTYPE; + break; + } + } else { + _type = INVALID_SHAPE_PROXYTYPE; + } + } + + void setBox(const btVector3& halfExtents) { + _type = BOX_SHAPE_PROXYTYPE; + _data.clear(); + _data.push_back(halfExtents); + } + + void setSphere(float radius) { + _type = SPHERE_SHAPE_PROXYTYPE; + _data.clear(); + _data.push_back(btVector3(0.0f, 0.0f, radius)); + } + + void setCylinder(float radius, float height) { + _type = CYLINDER_SHAPE_PROXYTYPE; + _data.clear(); + // NOTE: default cylinder has (UpAxis = 1) axis along yAxis and radius stored in X + // halfExtents = btVector3(radius, halfHeight, unused) + _data.push_back(btVector3(radius, 0.5f * height, radius)); + } + + void setCapsule(float radius, float height) { + _type = CAPSULE_SHAPE_PROXYTYPE; + _data.clear(); + _data.push_back(btVector3(radius, 0.5f * height, 0.0f)); + } + + virtual int computeHash() const { + // This hash algorithm works well for shapes that have dimensions less than about 256m. + // At larger values the likelihood of hash collision goes up because of the most + // significant bits are pushed off the top and the result could be the same as for smaller + // dimensions (truncation). + // + // The algorithm may produce collisions for shapes whose dimensions differ by less than + // ~1/256 m, however this is by design -- we don't expect collision differences smaller + // than 1 mm to be noticable. + unsigned int key = 0; + btVector3 tempData; + int numData = _data.size(); + for (int i = 0; i < numData; ++i) { + // Successively multiply components of each vec3 by primes near 512 and convert to U32 + // to spread the data across more bits. Note that all dimensions are at half-value + // (half extents, radius, etc) which is why we multiply by primes near 512 rather + // than 256. + tempData = _data[i]; + key += (unsigned int)(509.0f * (tempData.getZ() + 0.01f)) + + 509 * (unsigned int)(521.0f * (tempData.getY() + 0.01f)) + + (509 * 521) * (unsigned int)(523.0f * (tempData.getX() + 0.01f)); + // avalanch the bits + key += ~(key << 15); + key ^= (key >> 10); + key += (key << 3); + key ^= (key >> 6); + key += ~(key << 11); + key ^= (key >> 16); + } + // finally XOR with type + return (int)(key ^ _type); + } + +private: + friend class ShapeManager; + virtual btCollisionShape* createShape() const { - const float MAX_SHAPE_DIMENSION = 100.0f; - const float MIN_SHAPE_DIMENSION = 0.01f; - for (int i = 0; i < 3; ++i) { - float side = _size[i]; - if (side > MAX_SHAPE_DIMENSION || side < MIN_SHAPE_DIMENSION) { - return NULL; + btCollisionShape* shape = NULL; + int numData = _data.size(); + switch(_type) { + case BOX_SHAPE_PROXYTYPE: + { + if (numData > 0) { + btVector3 halfExtents = _data[0]; + shape = new btBoxShape(halfExtents); + } } + break; + case SPHERE_SHAPE_PROXYTYPE: + { + if (numData > 0) { + float radius = _data[0].getZ(); + shape = new btSphereShape(radius); + } + } + break; + case CYLINDER_SHAPE_PROXYTYPE: + { + if (numData > 0) { + btVector3 halfExtents = _data[0]; + // NOTE: default cylinder has (UpAxis = 1) axis along yAxis and radius stored in X + // halfExtents = btVector3(radius, halfHeight, unused) + shape = new btCylinderShape(halfExtents); + } + } + break; + case CAPSULE_SHAPE_PROXYTYPE: + { + if (numData > 0) { + float radius = _data[0].getX(); + float height = 2.0f * _data[0].getY(); + shape = new btCapsuleShape(radius, height); + } + } + break; } - // default behavior is to create a btBoxShape - return new btBoxShape(0.5f * _size); + return shape; } }; - -struct ShapeReference { - int _refCount; - btCollisionShape* _shape; - ShapeReference() : _refCount(0), _shape(NULL) {} -}; +*/ class ShapeManager { public: @@ -71,6 +189,7 @@ public: /// \return true if shape was found and released bool releaseShape(const ShapeInfo& info); +// bool removeReference(const btCollisionShape*); /// delete shapes that have zero references void collectGarbage(); @@ -80,6 +199,12 @@ public: int getNumReferences(const ShapeInfo& info) const; private: + struct ShapeReference { + int _refCount; + btCollisionShape* _shape; + ShapeReference() : _refCount(0), _shape(NULL) {} + }; + btHashMap _shapeMap; btAlignedObjectArray _pendingGarbage; }; diff --git a/tests/physics/CMakeLists.txt b/tests/physics/CMakeLists.txt index d47b979459..a7f54eab93 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) link_shared_dependencies() diff --git a/tests/physics/src/main.cpp b/tests/physics/src/main.cpp index 086bff4dcd..0e25f0f48c 100644 --- a/tests/physics/src/main.cpp +++ b/tests/physics/src/main.cpp @@ -10,9 +10,11 @@ #include "ShapeColliderTests.h" #include "VerletShapeTests.h" +#include "ShapeManagerTests.h" int main(int argc, char** argv) { - ShapeColliderTests::runAllTests(); - VerletShapeTests::runAllTests(); + //ShapeColliderTests::runAllTests(); + //VerletShapeTests::runAllTests(); + ShapeManagerTests::runAllTests(); return 0; }