add SHAPE_TYPE_MESH and build mesh shapes

This commit is contained in:
Andrew Meadows 2016-06-14 14:22:28 -07:00
parent 9fc77ccfa2
commit a519b77ae7
6 changed files with 235 additions and 32 deletions

View file

@ -599,13 +599,13 @@ bool RenderableModelEntityItem::isReadyToComputeShape() {
void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
ShapeType type = getShapeType();
glm::vec3 dimensions = getDimensions();
if (type == SHAPE_TYPE_COMPOUND) {
updateModelBounds();
// should never fall in here when collision model not fully loaded
// hence we assert that all geometries exist and are loaded
assert(_model->isLoaded() && _model->isCollisionLoaded());
const FBXGeometry& renderGeometry = _model->getFBXGeometry();
const FBXGeometry& collisionGeometry = _model->getCollisionFBXGeometry();
ShapeInfo::PointCollection& pointCollection = info.getPointCollection();
@ -677,7 +677,8 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
// to the visual model and apply them to the collision model (without regard for the
// collision model's extents).
glm::vec3 scale = getDimensions() / renderGeometry.getUnscaledMeshExtents().size();
const FBXGeometry& renderGeometry = _model->getFBXGeometry();
glm::vec3 scale = dimensions / renderGeometry.getUnscaledMeshExtents().size();
// multiply each point by scale before handing the point-set off to the physics engine.
// also determine the extents of the collision model.
AABox box;
@ -697,9 +698,104 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
glm::vec3 collisionModelDimensions = box.getDimensions();
info.setParams(type, collisionModelDimensions, _compoundShapeURL);
info.setOffset(_model->getOffset());
} else if (type == SHAPE_TYPE_MESH) {
updateModelBounds();
// should never fall in here when collision model not fully loaded
assert(_model->isLoaded());
ShapeInfo::PointCollection& pointCollection = info.getPointCollection();
pointCollection.clear();
ShapeInfo::PointList points;
ShapeInfo::TriangleIndices& triangleIndices = info.getTriangleIndices();
auto& meshes = _model->getGeometry()->getGeometry()->getMeshes();
glm::vec3 modelOffset = _model->getOffset();
for (auto& mesh : meshes) {
const gpu::BufferView& vertices = mesh->getVertexBuffer();
const gpu::BufferView& indices = mesh->getIndexBuffer();
const gpu::BufferView& parts = mesh->getPartBuffer();
// copy points
uint32_t meshIndexOffset = (uint32_t)points.size();
gpu::BufferView::Iterator<const glm::vec3> vertexItr = vertices.cbegin<const glm::vec3>();
points.reserve(points.size() + vertices.getNumElements());
Extents extents;
while (vertexItr != vertices.cend<const glm::vec3>()) {
points.push_back(*vertexItr);
extents.addPoint(*vertexItr);
++vertexItr;
}
// scale points and shift by modelOffset
glm::vec3 extentsSize = extents.size();
glm::vec3 scale = dimensions / extents.size();
for (int i = 0; i < 3; ++i) {
if (extentsSize[i] < 1.0e-6f) {
scale[i] = 1.0f;
}
}
for (int i = 0; i < points.size(); ++i) {
points[i] = (points[i] * scale) + modelOffset;
}
// copy triangleIndices
triangleIndices.reserve(triangleIndices.size() + indices.getNumElements());
gpu::BufferView::Iterator<const model::Mesh::Part> partItr = parts.cbegin<const model::Mesh::Part>();
while (partItr != parts.cend<const model::Mesh::Part>()) {
if (partItr->_topology == model::Mesh::TRIANGLES) {
assert(partItr->_numIndices % 3 == 0);
auto indexItr = indices.cbegin<const gpu::BufferView::Index>() + partItr->_startIndex;
auto indexEnd = indexItr + partItr->_numIndices;
while (indexItr != indexEnd) {
triangleIndices.push_back(*indexItr + meshIndexOffset);
++indexItr;
}
} else if (partItr->_topology == model::Mesh::TRIANGLE_STRIP) {
assert(partItr->_numIndices > 2);
uint32_t approxNumIndices = 3 * partItr->_numIndices;
if (approxNumIndices > (uint32_t)(triangleIndices.capacity() - triangleIndices.size())) {
// we underestimated the final size of triangleIndices so we pre-emptively expand it
triangleIndices.reserve(triangleIndices.size() + approxNumIndices);
}
auto indexItr = indices.cbegin<const gpu::BufferView::Index>() + partItr->_startIndex;
auto indexEnd = indexItr + (partItr->_numIndices - 2);
// first triangle uses the first three indices
triangleIndices.push_back(*indexItr + meshIndexOffset);
triangleIndices.push_back(*(++indexItr) + meshIndexOffset);
triangleIndices.push_back(*(indexItr + 1) + meshIndexOffset);
// the rest use previous and next index
uint32_t triangleCount = 1;
while (indexItr != indexEnd) {
if (triangleCount % 2 == 0) {
// even triangles use first two indices in order
triangleIndices.push_back(*(indexItr) + meshIndexOffset);
triangleIndices.push_back(*(++indexItr) + meshIndexOffset); // yes pre-increment
} else {
// odd triangles swap order of first two indices
triangleIndices.push_back(*(indexItr + 1) + meshIndexOffset);
triangleIndices.push_back(*(indexItr++) + meshIndexOffset); // yes post-increment
}
triangleIndices.push_back(*(indexItr + 1) + meshIndexOffset);
++triangleCount;
}
} else if (partItr->_topology == model::Mesh::QUADS) {
// TODO: support model::Mesh::QUADS
}
// TODO? support model::Mesh::QUAD_STRIP?
++partItr;
}
}
pointCollection.push_back(points);
info.setParams(SHAPE_TYPE_MESH, 0.5f * dimensions, _modelURL);
} else {
ModelEntityItem::computeShapeInfo(info);
info.setParams(type, 0.5f * getDimensions());
info.setParams(type, 0.5f * dimensions);
adjustShapeInfoByRegistration(info);
}
}

View file

@ -67,7 +67,8 @@ static const btVector3 _unitSphereDirections[NUM_UNIT_SPHERE_DIRECTIONS] = {
};
btConvexHullShape* ShapeFactory::createConvexHull(const QVector<glm::vec3>& points) {
// util method
btConvexHullShape* createConvexHull(const ShapeInfo::PointList& points) {
assert(points.size() > 0);
btConvexHullShape* hull = new btConvexHullShape();
@ -158,6 +159,84 @@ btConvexHullShape* ShapeFactory::createConvexHull(const QVector<glm::vec3>& poin
return hull;
}
// util method
btTriangleIndexVertexArray* createStaticMeshArray(const ShapeInfo& info) {
assert(info.getType() == SHAPE_TYPE_MESH); // should only get here for mesh shapes
const ShapeInfo::PointCollection& pointCollection = info.getPointCollection();
assert(pointCollection.size() == 1); // should only have one mesh
const ShapeInfo::PointList& pointList = pointCollection[0];
assert(pointList.size() > 2); // should have at least one triangle's worth of points
const ShapeInfo::TriangleIndices& triangleIndices = info.getTriangleIndices();
assert(triangleIndices.size() > 2); // should have at least one triangle's worth of indices
// allocate mesh buffers
btIndexedMesh mesh;
int32_t numIndices = triangleIndices.size();
const int32_t VERTICES_PER_TRIANGLE = 3;
mesh.m_numTriangles = numIndices / VERTICES_PER_TRIANGLE;
if (numIndices < INT16_MAX) {
// small number of points so we can use 16-bit indices
mesh.m_triangleIndexBase = new unsigned char[sizeof(int16_t) * (size_t)numIndices];
mesh.m_indexType = PHY_SHORT;
mesh.m_triangleIndexStride = VERTICES_PER_TRIANGLE * sizeof(int16_t);
} else {
mesh.m_triangleIndexBase = new unsigned char[sizeof(int32_t) * (size_t)numIndices];
mesh.m_indexType = PHY_INTEGER;
mesh.m_triangleIndexStride = VERTICES_PER_TRIANGLE * sizeof(int32_t);
}
mesh.m_numVertices = pointList.size();
mesh.m_vertexBase = new unsigned char[VERTICES_PER_TRIANGLE * sizeof(btScalar) * (size_t)mesh.m_numVertices];
mesh.m_vertexStride = VERTICES_PER_TRIANGLE * sizeof(btScalar);
mesh.m_vertexType = PHY_FLOAT;
// copy data into buffers
btScalar* vertexData = static_cast<btScalar*>((void*)(mesh.m_vertexBase));
for (int32_t i = 0; i < mesh.m_numVertices; ++i) {
int32_t j = i * VERTICES_PER_TRIANGLE;
const glm::vec3& point = pointList[i];
vertexData[j] = point.x;
vertexData[j + 1] = point.y;
vertexData[j + 2] = point.z;
}
if (numIndices < INT16_MAX) {
int16_t* indices = static_cast<int16_t*>((void*)(mesh.m_triangleIndexBase));
for (int32_t i = 0; i < numIndices; ++i) {
indices[i] = triangleIndices[i];
}
} else {
int32_t* indices = static_cast<int32_t*>((void*)(mesh.m_triangleIndexBase));
for (int32_t i = 0; i < numIndices; ++i) {
indices[i] = triangleIndices[i];
}
}
// store buffers in a new dataArray and return the pointer
// (external StaticMeshShape will own all of the data that was allocated here)
btTriangleIndexVertexArray* dataArray = new btTriangleIndexVertexArray;
dataArray->addIndexedMesh(mesh, mesh.m_indexType);
return dataArray;
}
// util method
void deleteStaticMeshArray(btTriangleIndexVertexArray* dataArray) {
assert(dataArray);
IndexedMeshArray& meshes = dataArray->getIndexedMeshArray();
for (int32_t i = 0; i < meshes.size(); ++i) {
btIndexedMesh mesh = meshes[i];
mesh.m_numTriangles = 0;
delete [] mesh.m_triangleIndexBase;
mesh.m_triangleIndexBase = nullptr;
mesh.m_numVertices = 0;
delete [] mesh.m_vertexBase;
mesh.m_vertexBase = nullptr;
}
meshes.clear();
delete dataArray;
}
btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) {
btCollisionShape* shape = NULL;
int type = info.getType();
@ -195,6 +274,11 @@ btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) {
}
}
break;
case SHAPE_TYPE_MESH: {
btTriangleIndexVertexArray* dataArray = createStaticMeshArray(info);
shape = new StaticMeshShape(dataArray);
}
break;
}
if (shape) {
if (glm::length2(info.getOffset()) > MIN_SHAPE_OFFSET * MIN_SHAPE_OFFSET) {
@ -228,3 +312,14 @@ void ShapeFactory::deleteShape(btCollisionShape* shape) {
}
delete shape;
}
// the dataArray must be created before we create the StaticMeshShape
ShapeFactory::StaticMeshShape::StaticMeshShape(btTriangleIndexVertexArray* dataArray)
: btBvhTriangleMeshShape(dataArray, true), _dataArray(dataArray) {
assert(dataArray);
}
ShapeFactory::StaticMeshShape::~StaticMeshShape() {
deleteStaticMeshArray(_dataArray);
_dataArray = nullptr;
}

View file

@ -20,9 +20,22 @@
// translates between ShapeInfo and btShape
namespace ShapeFactory {
btConvexHullShape* createConvexHull(const QVector<glm::vec3>& points);
btCollisionShape* createShapeFromInfo(const ShapeInfo& info);
void deleteShape(btCollisionShape* shape);
//btTriangleIndexVertexArray* createStaticMeshArray(const ShapeInfo& info);
//void deleteStaticMeshArray(btTriangleIndexVertexArray* dataArray);
class StaticMeshShape : public btBvhTriangleMeshShape {
public:
StaticMeshShape() = delete;
StaticMeshShape(btTriangleIndexVertexArray* dataArray);
~StaticMeshShape();
private:
// the StaticMeshShape owns its vertex/index data
btTriangleIndexVertexArray* _dataArray;
};
};
#endif // hifi_ShapeFactory_h

View file

@ -32,15 +32,13 @@ btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) {
if (info.getType() == SHAPE_TYPE_NONE) {
return NULL;
}
if (info.getType() != SHAPE_TYPE_COMPOUND) {
// Very small or large non-compound objects are not supported.
float diagonal = 4.0f * glm::length2(info.getHalfExtents());
const float MIN_SHAPE_DIAGONAL_SQUARED = 3.0e-4f; // 1 cm cube
if (diagonal < MIN_SHAPE_DIAGONAL_SQUARED) {
// qCDebug(physics) << "ShapeManager::getShape -- not making shape due to size" << diagonal;
return NULL;
}
const float MIN_SHAPE_DIAGONAL_SQUARED = 3.0e-4f; // 1 cm cube
if (4.0f * glm::length2(info.getHalfExtents()) < MIN_SHAPE_DIAGONAL_SQUARED) {
// tiny shapes are not supported
// qCDebug(physics) << "ShapeManager::getShape -- not making shape due to size" << diagonal;
return NULL;
}
DoubleHashKey key = info.getHash();
ShapeReference* shapeRef = _shapeMap.find(key);
if (shapeRef) {
@ -66,8 +64,8 @@ bool ShapeManager::releaseShapeByKey(const DoubleHashKey& key) {
shapeRef->refCount--;
if (shapeRef->refCount == 0) {
_pendingGarbage.push_back(key);
const int MAX_GARBAGE_CAPACITY = 255;
if (_pendingGarbage.size() > MAX_GARBAGE_CAPACITY) {
const int MAX_SHAPE_GARBAGE_CAPACITY = 255;
if (_pendingGarbage.size() > MAX_SHAPE_GARBAGE_CAPACITY) {
collectGarbage();
}
}

View file

@ -41,9 +41,9 @@ void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QString
break;
}
case SHAPE_TYPE_COMPOUND:
case SHAPE_TYPE_MESH:
_url = QUrl(url);
_halfExtents = halfExtents;
break;
// yes, fall through
default:
_halfExtents = halfExtents;
break;
@ -182,18 +182,15 @@ const DoubleHashKey& ShapeInfo::getHash() const {
// NOTE: we cache the key so we only ever need to compute it once for any valid ShapeInfo instance.
if (_doubleHashKey.isNull() && _type != SHAPE_TYPE_NONE) {
bool useOffset = glm::length2(_offset) > MIN_SHAPE_OFFSET * MIN_SHAPE_OFFSET;
// The key is not yet cached therefore we must compute it! To this end we bypass the const-ness
// of this method by grabbing a non-const pointer to "this" and a non-const reference to _doubleHashKey.
ShapeInfo* thisPtr = const_cast<ShapeInfo*>(this);
DoubleHashKey& key = thisPtr->_doubleHashKey;
// The key is not yet cached therefore we must compute it.
// compute hash1
// TODO?: provide lookup table for hash/hash2 of _type rather than recompute?
uint32_t primeIndex = 0;
key.computeHash((uint32_t)_type, primeIndex++);
_doubleHashKey.computeHash((uint32_t)_type, primeIndex++);
// compute hash1
uint32_t hash = key.getHash();
uint32_t hash = _doubleHashKey.getHash();
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()
@ -206,10 +203,10 @@ const DoubleHashKey& ShapeInfo::getHash() const {
primeIndex++);
}
}
key.setHash(hash);
_doubleHashKey.setHash(hash);
// compute hash2
hash = key.getHash2();
hash = _doubleHashKey.getHash2();
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()
@ -226,7 +223,7 @@ const DoubleHashKey& ShapeInfo::getHash() const {
hash += ~(floatHash << 10);
hash = (hash << 16) | (hash >> 16);
}
key.setHash2(hash);
_doubleHashKey.setHash2(hash);
if (_type == SHAPE_TYPE_COMPOUND || _type == SHAPE_TYPE_MESH) {
QString url = _url.toString();
@ -235,8 +232,8 @@ const DoubleHashKey& ShapeInfo::getHash() const {
QByteArray baUrl = url.toLocal8Bit();
const char *cUrl = baUrl.data();
uint32_t urlHash = qChecksum(cUrl, baUrl.count());
key.setHash(key.getHash() ^ urlHash);
key.setHash2(key.getHash2() ^ urlHash);
_doubleHashKey.setHash(_doubleHashKey.getHash() ^ urlHash);
_doubleHashKey.setHash2(_doubleHashKey.getHash2() ^ urlHash);
}
}
}

View file

@ -30,14 +30,15 @@ enum ShapeType {
SHAPE_TYPE_NONE,
SHAPE_TYPE_BOX,
SHAPE_TYPE_SPHERE,
SHAPE_TYPE_PLANE,
SHAPE_TYPE_COMPOUND,
SHAPE_TYPE_CAPSULE_X,
SHAPE_TYPE_CAPSULE_Y,
SHAPE_TYPE_CAPSULE_Z,
SHAPE_TYPE_CYLINDER_X,
SHAPE_TYPE_CYLINDER_Y,
SHAPE_TYPE_CYLINDER_Z,
SHAPE_TYPE_HULL,
SHAPE_TYPE_PLANE,
SHAPE_TYPE_COMPOUND,
SHAPE_TYPE_MESH
};
@ -62,10 +63,13 @@ public:
const glm::vec3& getHalfExtents() const { return _halfExtents; }
const glm::vec3& getOffset() const { return _offset; }
uint32_t getNumSubShapes() const;
PointCollection& getPointCollection() { return _pointCollection; }
const PointCollection& getPointCollection() const { return _pointCollection; }
uint32_t getNumSubShapes() const;
TriangleIndices& getTriangleIndices() { return _triangleIndices; }
const TriangleIndices& getTriangleIndices() const { return _triangleIndices; }
int getLargestSubshapePointCount() const;
@ -83,7 +87,7 @@ protected:
TriangleIndices _triangleIndices;
glm::vec3 _halfExtents = glm::vec3(0.0f);
glm::vec3 _offset = glm::vec3(0.0f);
DoubleHashKey _doubleHashKey;
mutable DoubleHashKey _doubleHashKey;
ShapeType _type = SHAPE_TYPE_NONE;
};