ShapeManager has improved HashMap

This commit is contained in:
Andrew Meadows 2014-11-03 14:01:19 -08:00
parent f3ead98624
commit b1b6188bb8
4 changed files with 101 additions and 338 deletions

View file

@ -15,7 +15,20 @@
#include "ShapeInfo.h" #include "ShapeInfo.h"
void ShapeInfo::getInfo(const btCollisionShape* shape) { // prime numbers larger than 1e6
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
};
void ShapeInfo::collectInfo(const btCollisionShape* shape) {
_data.clear(); _data.clear();
if (shape) { if (shape) {
_type = (unsigned int)(shape->getShapeType()); _type = (unsigned int)(shape->getShapeType());
@ -80,61 +93,87 @@ void ShapeInfo::setCapsule(float radius, float height) {
_data.push_back(btVector3(radius, 0.5f * height, 0.0f)); _data.push_back(btVector3(radius, 0.5f * height, 0.0f));
} }
unsigned int ShapeInfo::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 ShapeInfo::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;
}
const float MILLIMETERS_PER_METER = 1000.0f;
int ShapeInfo::computeHash() const { int ShapeInfo::computeHash() const {
// This hash algorithm works well for shapes that have dimensions less than about 256m. // scramble the bits of the type
// At larger values the likelihood of hash collision goes up because of the most // TODO?: provide lookup table for hash of _type?
// significant bits are pushed off the top and the result could be the same as for smaller int primeIndex = 0;
// dimensions (truncation). unsigned int hash = ShapeInfo::hashFunction((unsigned int)_type, primeIndex++);
//
// The algorithm may produce collisions for shapes whose dimensions differ by less than btVector3 tmpData;
// ~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(); int numData = _data.size();
for (int i = 0; i < numData; ++i) { for (int i = 0; i < numData; ++i) {
// Successively multiply components of each vec3 by primes near 512 and convert to U32 tmpData = _data[i];
// to spread the data across more bits. Note that all dimensions are at half-value for (int j = 0; j < 3; ++j) {
// (half extents, radius, etc) which is why we multiply by primes near 512 rather // multiply these mm by a new prime
// than 256. unsigned int floatHash = ShapeInfo::hashFunction((unsigned int)(tmpData[j] * MILLIMETERS_PER_METER + 0.49f), primeIndex++);
tempData = _data[i]; hash ^= floatHash;
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 hash;
return (int)(key ^ _type); }
int ShapeInfo::computeHash2() const {
// scramble the bits of the type
// TODO?: provide lookup table for hash of _type?
unsigned int hash = ShapeInfo::hashFunction2((unsigned int)_type);
btVector3 tmpData;
int numData = _data.size();
for (int i = 0; i < numData; ++i) {
tmpData = _data[i];
for (int j = 0; j < 3; ++j) {
unsigned int floatHash = ShapeInfo::hashFunction2((unsigned int)(tmpData[j] * MILLIMETERS_PER_METER + 0.49f));
hash += ~(floatHash << 17);
hash ^= (floatHash >> 11);
hash += (floatHash << 4);
hash ^= (floatHash >> 7);
hash += ~(floatHash << 10);
hash = (hash << 16) | (hash >> 16);
}
}
return hash;
} }
btCollisionShape* ShapeInfo::createShape() const { btCollisionShape* ShapeInfo::createShape() const {
btCollisionShape* shape = NULL; btCollisionShape* shape = NULL;
int numData = _data.size(); int numData = _data.size();
switch(_type) { switch(_type) {
case BOX_SHAPE_PROXYTYPE: case BOX_SHAPE_PROXYTYPE: {
{
if (numData > 0) { if (numData > 0) {
btVector3 halfExtents = _data[0]; btVector3 halfExtents = _data[0];
shape = new btBoxShape(halfExtents); shape = new btBoxShape(halfExtents);
} }
} }
break; break;
case SPHERE_SHAPE_PROXYTYPE: case SPHERE_SHAPE_PROXYTYPE: {
{
if (numData > 0) { if (numData > 0) {
float radius = _data[0].getZ(); float radius = _data[0].getZ();
shape = new btSphereShape(radius); shape = new btSphereShape(radius);
} }
} }
break; break;
case CYLINDER_SHAPE_PROXYTYPE: case CYLINDER_SHAPE_PROXYTYPE: {
{
if (numData > 0) { if (numData > 0) {
btVector3 halfExtents = _data[0]; btVector3 halfExtents = _data[0];
// NOTE: default cylinder has (UpAxis = 1) axis along yAxis and radius stored in X // NOTE: default cylinder has (UpAxis = 1) axis along yAxis and radius stored in X
@ -143,8 +182,7 @@ btCollisionShape* ShapeInfo::createShape() const {
} }
} }
break; break;
case CAPSULE_SHAPE_PROXYTYPE: case CAPSULE_SHAPE_PROXYTYPE: {
{
if (numData > 0) { if (numData > 0) {
float radius = _data[0].getX(); float radius = _data[0].getX();
float height = 2.0f * _data[0].getY(); float height = 2.0f * _data[0].getY();

View file

@ -17,167 +17,31 @@
#include <btBulletDynamicsCommon.h> #include <btBulletDynamicsCommon.h>
#include <LinearMath/btHashMap.h> #include <LinearMath/btHashMap.h>
const float DEFAULT_MARGIN = 0.04f;
class ShapeInfo { class ShapeInfo {
public: public:
ShapeInfo() : _type(INVALID_SHAPE_PROXYTYPE) {} ShapeInfo() : _type(INVALID_SHAPE_PROXYTYPE) {}
ShapeInfo(const btCollisionShape* shape) : _type(INVALID_SHAPE_PROXYTYPE) { ShapeInfo(const btCollisionShape* shape) : _type(INVALID_SHAPE_PROXYTYPE) {
getInfo(shape); collectInfo(shape);
} }
// BOOKMARK -- move ShapeInfo to its own files void collectInfo(const btCollisionShape* shape);
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); void setBox(const btVector3& halfExtents);
/*{
_type = BOX_SHAPE_PROXYTYPE;
_data.clear();
_data.push_back(halfExtents);
}*/
void setSphere(float radius); 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); 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); 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; int computeHash() const;
/*{ int computeHash2() 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 static unsigned int hashFunction(unsigned int value, int primeIndex);
// significant bits are pushed off the top and the result could be the same as for smaller static unsigned int hashFunction2(unsigned int value);
// 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: private:
friend class ShapeManager; friend class ShapeManager;
btCollisionShape* createShape() const; 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; int _type;
btAlignedObjectArray<btVector3> _data; btAlignedObjectArray<btVector3> _data;

View file

@ -21,11 +21,12 @@ ShapeManager::~ShapeManager() {
ShapeReference* shapeRef = _shapeMap.getAtIndex(i); ShapeReference* shapeRef = _shapeMap.getAtIndex(i);
delete shapeRef->_shape; delete shapeRef->_shape;
} }
_shapeMap.clear();
} }
btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) { btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) {
int key = info.computeHash(); ShapeKey key(info);
ShapeReference* shapeRef = _shapeMap.find(key); ShapeReference* shapeRef = _shapeMap.find(key);
if (shapeRef) { if (shapeRef) {
shapeRef->_refCount++; shapeRef->_refCount++;
@ -43,7 +44,7 @@ btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) {
} }
bool ShapeManager::releaseShape(const ShapeInfo& info) { bool ShapeManager::releaseShape(const ShapeInfo& info) {
int key = info.computeHash(); ShapeKey key(info);
ShapeReference* shapeRef = _shapeMap.find(key); ShapeReference* shapeRef = _shapeMap.find(key);
if (shapeRef) { if (shapeRef) {
if (shapeRef->_refCount > 0) { if (shapeRef->_refCount > 0) {
@ -71,7 +72,7 @@ bool ShapeManager::releaseShape(const ShapeInfo& info) {
bool ShapeManager::releaseShape(const btCollisionShape* shape) { bool ShapeManager::releaseShape(const btCollisionShape* shape) {
// when the number of shapes is high it's probably cheaper to try to construct a ShapeInfo // 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. // and then compute the hash rather than walking the list in search of the pointer.
int key = info.computeHash(); ShapeKey key(info);
ShapeReference* shapeRef = _shapeMap.find(key); ShapeReference* shapeRef = _shapeMap.find(key);
if (shapeRef) { if (shapeRef) {
if (shapeRef->_refCount > 0) { if (shapeRef->_refCount > 0) {
@ -99,10 +100,9 @@ bool ShapeManager::releaseShape(const btCollisionShape* shape) {
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) {
int key = _pendingGarbage[i]; ShapeKey& key = _pendingGarbage[i];
ShapeReference* shapeRef = _shapeMap.find(key); ShapeReference* shapeRef = _shapeMap.find(key);
assert(shapeRef != NULL); if (shapeRef && shapeRef->_refCount == 0) {
if (shapeRef->_refCount == 0) {
delete shapeRef->_shape; delete shapeRef->_shape;
_shapeMap.remove(key); _shapeMap.remove(key);
} }
@ -111,7 +111,7 @@ void ShapeManager::collectGarbage() {
} }
int ShapeManager::getNumReferences(const ShapeInfo& info) const { int ShapeManager::getNumReferences(const ShapeInfo& info) const {
int key = info.computeHash(); ShapeKey key(info);
const ShapeReference* shapeRef = _shapeMap.find(key); const ShapeReference* shapeRef = _shapeMap.find(key);
if (shapeRef) { if (shapeRef) {
return shapeRef->_refCount; return shapeRef->_refCount;

View file

@ -19,164 +19,25 @@
#include "ShapeInfo.h" #include "ShapeInfo.h"
/* class ShapeKey
struct ShapeInfo { {
int _type; public:
btAlignedObjectArray<btVector3> _data; ShapeKey(const ShapeInfo& info) : _hash(0), _hash2(0) {
//btVector3 _scale; _hash = info.computeHash();
_hash2 = info.computeHash2();
ShapeInfo() : _type(INVALID_SHAPE_PROXYTYPE) {}
ShapeInfo(const btCollisionShape* shape) : _type(INVALID_SHAPE_PROXYTYPE) {
getInfo(shape);
} }
// BOOKMARK -- move ShapeInfo to its own files bool equals(const ShapeKey& other) const {
void getInfo(const btCollisionShape* shape) { return _hash == other._hash && _hash2 == other._hash2;
_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) { unsigned int getHash() const { return (unsigned int)_hash; }
_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: private:
friend class ShapeManager; int _hash;
int _hash2;
virtual 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;
}
}; };
*/
class ShapeManager { class ShapeManager {
public: public:
@ -205,8 +66,8 @@ private:
ShapeReference() : _refCount(0), _shape(NULL) {} ShapeReference() : _refCount(0), _shape(NULL) {}
}; };
btHashMap<btHashInt, ShapeReference> _shapeMap; btHashMap<ShapeKey, ShapeReference> _shapeMap;
btAlignedObjectArray<int> _pendingGarbage; btAlignedObjectArray<ShapeKey> _pendingGarbage;
}; };
#endif // USE_BULLET_PHYSICS #endif // USE_BULLET_PHYSICS