ShapeManager now under unit test

This commit is contained in:
Andrew Meadows 2014-10-31 17:13:17 -07:00
parent 50a97849bb
commit d26540b029
8 changed files with 544 additions and 45 deletions

View file

@ -4,16 +4,9 @@ set(TARGET_NAME physics)
setup_hifi_library() setup_hifi_library()
include_glm() include_glm()
include_bullet()
link_hifi_libraries(shared) 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 # call macro to link our dependencies and bubble them up via a property on our target
link_shared_dependencies() link_shared_dependencies()

View file

@ -18,6 +18,10 @@
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp> #include <glm/gtc/quaternion.hpp>
#ifdef USE_BULLET_PHYSICS
#include "PhysicsWorld.h"
#endif // USE_BULLET_PHYSICS
#include "CollisionInfo.h" #include "CollisionInfo.h"
#include "RayIntersectionInfo.h" #include "RayIntersectionInfo.h"

View file

@ -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 <btBulletDynamicsCommon.h>
#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<const btBoxShape*>(shape);
_data.push_back(boxShape->getHalfExtentsWithMargin());
}
break;
case SPHERE_SHAPE_PROXYTYPE:
{
const btSphereShape* sphereShape = static_cast<const btSphereShape*>(shape);
_data.push_back(btVector3(0.0f, 0.0f, sphereShape->getRadius()));
}
break;
case CYLINDER_SHAPE_PROXYTYPE:
{
const btCylinderShape* cylinderShape = static_cast<const btCylinderShape*>(shape);
_data.push_back(cylinderShape->getHalfExtentsWithMargin());
}
break;
case CAPSULE_SHAPE_PROXYTYPE:
{
const btCapsuleShape* capsuleShape = static_cast<const btCapsuleShape*>(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

View file

@ -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 <btBulletDynamicsCommon.h>
#include <LinearMath/btHashMap.h>
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<const btBoxShape*>(shape);
_data.push_back(boxShape->getHalfExtentsWithMargin());
}
break;
case SPHERE_SHAPE_PROXYTYPE:
{
const btSphereShape* sphereShape = static_cast<const btSphereShape*>(shape);
_data.push_back(btVector3(0.0f, 0.0f, sphereShape->getRadius()));
}
break;
case CYLINDER_SHAPE_PROXYTYPE:
{
const btCylinderShape* cylinderShape = static_cast<const btCylinderShape*>(shape);
_data.push_back(cylinderShape->getHalfExtentsWithMargin());
}
break;
case CAPSULE_SHAPE_PROXYTYPE:
{
const btCapsuleShape* capsuleShape = static_cast<const btCapsuleShape*>(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<btVector3> _data;
};
#endif // USE_BULLET_PHYSICS
#endif // hifi_ShapeInfo_h

View file

@ -25,7 +25,7 @@ ShapeManager::~ShapeManager() {
btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) { btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) {
int key = info.getHash(); int key = info.computeHash();
ShapeReference* shapeRef = _shapeMap.find(key); ShapeReference* shapeRef = _shapeMap.find(key);
if (shapeRef) { if (shapeRef) {
shapeRef->_refCount++; shapeRef->_refCount++;
@ -43,7 +43,7 @@ btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) {
} }
bool ShapeManager::releaseShape(const ShapeInfo& info) { bool ShapeManager::releaseShape(const ShapeInfo& info) {
int key = info.getHash(); int key = info.computeHash();
ShapeReference* shapeRef = _shapeMap.find(key); ShapeReference* shapeRef = _shapeMap.find(key);
if (shapeRef) { if (shapeRef) {
if (shapeRef->_refCount > 0) { if (shapeRef->_refCount > 0) {
@ -67,6 +67,35 @@ bool ShapeManager::releaseShape(const ShapeInfo& info) {
return false; 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() { void ShapeManager::collectGarbage() {
int numShapes = _pendingGarbage.size(); int numShapes = _pendingGarbage.size();
for (int i = 0; i < numShapes; ++i) { for (int i = 0; i < numShapes; ++i) {
@ -82,7 +111,7 @@ void ShapeManager::collectGarbage() {
} }
int ShapeManager::getNumReferences(const ShapeInfo& info) const { int ShapeManager::getNumReferences(const ShapeInfo& info) const {
int key = info.getHash(); int key = info.computeHash();
const ShapeReference* shapeRef = _shapeMap.find(key); const ShapeReference* shapeRef = _shapeMap.find(key);
if (shapeRef) { if (shapeRef) {
return shapeRef->_refCount; return shapeRef->_refCount;

View file

@ -17,48 +17,166 @@
#include <btBulletDynamicsCommon.h> #include <btBulletDynamicsCommon.h>
#include <LinearMath/btHashMap.h> #include <LinearMath/btHashMap.h>
#include "ShapeInfo.h"
/*
struct ShapeInfo { struct ShapeInfo {
int _type; int _type;
btVector3 _size; btAlignedObjectArray<btVector3> _data;
//btVector3 _scale;
ShapeInfo() : _type(BOX_SHAPE_PROXYTYPE), _size(1.0f, 1.0f, 1.0f) {} ShapeInfo() : _type(INVALID_SHAPE_PROXYTYPE) {}
virtual int getHash() const { ShapeInfo(const btCollisionShape* shape) : _type(INVALID_SHAPE_PROXYTYPE) {
// successfully multiply components of size by primes near 256 and convert to U32 getInfo(shape);
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;
} }
// 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<const btBoxShape*>(shape);
_data.push_back(boxShape->getHalfExtentsWithMargin());
}
break;
case SPHERE_SHAPE_PROXYTYPE:
{
const btSphereShape* sphereShape = static_cast<const btSphereShape*>(shape);
_data.push_back(btVector3(0.0f, 0.0f, sphereShape->getRadius()));
}
break;
case CYLINDER_SHAPE_PROXYTYPE:
{
const btCylinderShape* cylinderShape = static_cast<const btCylinderShape*>(shape);
_data.push_back(cylinderShape->getHalfExtentsWithMargin());
}
break;
case CAPSULE_SHAPE_PROXYTYPE:
{
const btCapsuleShape* capsuleShape = static_cast<const btCapsuleShape*>(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 { virtual btCollisionShape* createShape() const {
const float MAX_SHAPE_DIMENSION = 100.0f; btCollisionShape* shape = NULL;
const float MIN_SHAPE_DIMENSION = 0.01f; int numData = _data.size();
for (int i = 0; i < 3; ++i) { switch(_type) {
float side = _size[i]; case BOX_SHAPE_PROXYTYPE:
if (side > MAX_SHAPE_DIMENSION || side < MIN_SHAPE_DIMENSION) { {
return NULL; 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 shape;
return new btBoxShape(0.5f * _size);
} }
}; };
*/
struct ShapeReference {
int _refCount;
btCollisionShape* _shape;
ShapeReference() : _refCount(0), _shape(NULL) {}
};
class ShapeManager { class ShapeManager {
public: public:
@ -71,6 +189,7 @@ public:
/// \return true if shape was found and released /// \return true if shape was found and released
bool releaseShape(const ShapeInfo& info); bool releaseShape(const ShapeInfo& info);
// bool removeReference(const btCollisionShape*);
/// delete shapes that have zero references /// delete shapes that have zero references
void collectGarbage(); void collectGarbage();
@ -80,6 +199,12 @@ public:
int getNumReferences(const ShapeInfo& info) const; int getNumReferences(const ShapeInfo& info) const;
private: private:
struct ShapeReference {
int _refCount;
btCollisionShape* _shape;
ShapeReference() : _refCount(0), _shape(NULL) {}
};
btHashMap<btHashInt, ShapeReference> _shapeMap; btHashMap<btHashInt, ShapeReference> _shapeMap;
btAlignedObjectArray<int> _pendingGarbage; btAlignedObjectArray<int> _pendingGarbage;
}; };

View file

@ -3,8 +3,8 @@ set(TARGET_NAME physics-tests)
setup_hifi_project() setup_hifi_project()
include_glm() include_glm()
include_bullet()
# link in the shared libraries
link_hifi_libraries(shared physics) link_hifi_libraries(shared physics)
link_shared_dependencies() link_shared_dependencies()

View file

@ -10,9 +10,11 @@
#include "ShapeColliderTests.h" #include "ShapeColliderTests.h"
#include "VerletShapeTests.h" #include "VerletShapeTests.h"
#include "ShapeManagerTests.h"
int main(int argc, char** argv) { int main(int argc, char** argv) {
ShapeColliderTests::runAllTests(); //ShapeColliderTests::runAllTests();
VerletShapeTests::runAllTests(); //VerletShapeTests::runAllTests();
ShapeManagerTests::runAllTests();
return 0; return 0;
} }