From cbacb02010d139add3bf88ce99cd51aa581191e7 Mon Sep 17 00:00:00 2001
From: Andrew Meadows <andrew@highfidelity.io>
Date: Fri, 15 Jul 2016 11:28:11 -0700
Subject: [PATCH] more unit tests for CollisionRenderMeshCache

---
 .../physics/src/CollisionRenderMeshCache.cpp  |  41 ++-
 .../physics/src/CollisionRenderMeshCache.h    |  10 +-
 .../src/CollisionRenderMeshCacheTests.cpp     | 342 +++++++++---------
 .../src/CollisionRenderMeshCacheTests.h       |   4 +-
 4 files changed, 208 insertions(+), 189 deletions(-)

diff --git a/libraries/physics/src/CollisionRenderMeshCache.cpp b/libraries/physics/src/CollisionRenderMeshCache.cpp
index 3714d9d464..14386c6f48 100644
--- a/libraries/physics/src/CollisionRenderMeshCache.cpp
+++ b/libraries/physics/src/CollisionRenderMeshCache.cpp
@@ -68,9 +68,19 @@ void copyShapeToMesh(const btTransform& transform, const btConvexShape* shape, m
 }
 
 model::MeshPointer createMeshFromShape(const btCollisionShape* shape) {
-    model::MeshPointer mesh = std::make_shared<model::Mesh>();
-    if (shape) {
-        int32_t shapeType = shape->getShapeType();
+    model::MeshPointer mesh;
+    if (!shape) {
+        return mesh;
+    }
+
+    int32_t shapeType = shape->getShapeType();
+    if (shapeType == (int32_t)COMPOUND_SHAPE_PROXYTYPE || shape->isConvex()) {
+        // create the mesh and allocate buffers for it
+        mesh = std::make_shared<model::Mesh>();
+        mesh->setVertexBuffer(gpu::BufferView(new gpu::Buffer(), mesh->getVertexBuffer()._element));
+        mesh->setIndexBuffer(gpu::BufferView(new gpu::Buffer(), mesh->getIndexBuffer()._element));
+        mesh->setPartBuffer(gpu::BufferView(new gpu::Buffer(), mesh->getPartBuffer()._element));
+
         if (shapeType == (int32_t)COMPOUND_SHAPE_PROXYTYPE) {
             const btCompoundShape* compoundShape = static_cast<const btCompoundShape*>(shape);
             int32_t numSubShapes = compoundShape->getNumChildShapes();
@@ -81,7 +91,8 @@ model::MeshPointer createMeshFromShape(const btCollisionShape* shape) {
                     copyShapeToMesh(compoundShape->getChildTransform(i), convexShape, mesh);
                 }
             }
-        } else if (shape->isConvex()) {
+        } else {
+            // shape is convex
             const btConvexShape* convexShape = static_cast<const btConvexShape*>(shape);
             copyShapeToMesh(btTransform(), convexShape, mesh);
         }
@@ -93,20 +104,22 @@ CollisionRenderMeshCache::CollisionRenderMeshCache() {
 }
 
 CollisionRenderMeshCache::~CollisionRenderMeshCache() {
-    _geometryMap.clear();
+    _meshMap.clear();
     _pendingGarbage.clear();
 }
 
 model::MeshPointer CollisionRenderMeshCache::getMesh(CollisionRenderMeshCache::Key key) {
     model::MeshPointer mesh;
     if (key) {
-        CollisionMeshMap::const_iterator itr = _geometryMap.find(key);
-        if (itr != _geometryMap.end()) {
+        CollisionMeshMap::const_iterator itr = _meshMap.find(key);
+        if (itr == _meshMap.end()) {
             // make mesh and add it to map
             mesh = createMeshFromShape(key);
             if (mesh) {
-                _geometryMap.insert(std::make_pair(key, mesh));
+                _meshMap.insert(std::make_pair(key, mesh));
             }
+        } else {
+            mesh = itr->second;
         }
     }
     return mesh;
@@ -116,12 +129,12 @@ bool CollisionRenderMeshCache::releaseMesh(CollisionRenderMeshCache::Key key) {
     if (!key) {
         return false;
     }
-    CollisionMeshMap::const_iterator itr = _geometryMap.find(key);
-    if (itr != _geometryMap.end()) {
+    CollisionMeshMap::const_iterator itr = _meshMap.find(key);
+    if (itr != _meshMap.end()) {
         assert((*itr).second.use_count() != 1);
+        _pendingGarbage.push_back(key);
         if ((*itr).second.use_count() == 1) {
             // we hold all of the references inside the cache so we'll try to delete later
-            _pendingGarbage.push_back(key);
         }
         return true;
     }
@@ -132,11 +145,11 @@ void CollisionRenderMeshCache::collectGarbage() {
     int numShapes = _pendingGarbage.size();
     for (int i = 0; i < numShapes; ++i) {
         CollisionRenderMeshCache::Key key = _pendingGarbage[i];
-        CollisionMeshMap::const_iterator itr = _geometryMap.find(key);
-        if (itr != _geometryMap.end()) {
+        CollisionMeshMap::const_iterator itr = _meshMap.find(key);
+        if (itr != _meshMap.end()) {
             if ((*itr).second.use_count() == 1) {
                 // we hold the only reference
-                _geometryMap.erase(itr);
+                _meshMap.erase(itr);
             }
         }
     }
diff --git a/libraries/physics/src/CollisionRenderMeshCache.h b/libraries/physics/src/CollisionRenderMeshCache.h
index 05da3750c3..ad3c86562b 100644
--- a/libraries/physics/src/CollisionRenderMeshCache.h
+++ b/libraries/physics/src/CollisionRenderMeshCache.h
@@ -23,8 +23,8 @@ class btCollisionShape;
 namespace std {
     template <>
     struct hash<btCollisionShape*> {
-        std::size_t operator()(btCollisionShape* key) const {
-            return (hash<void*>()((void*)key));
+        std::size_t operator()(btCollisionShape* shape) const {
+            return (hash<void*>()((void*)shape));
         }
     };
 }
@@ -46,12 +46,12 @@ public:
     void collectGarbage();
 
     // validation methods
-    uint32_t getNumGeometries() const { return (uint32_t)_geometryMap.size(); }
-    bool hasMesh(Key key) const { return _geometryMap.find(key) == _geometryMap.end(); }
+    uint32_t getNumMeshes() const { return (uint32_t)_meshMap.size(); }
+    bool hasMesh(Key key) const { return _meshMap.find(key) == _meshMap.end(); }
 
 private:
     using CollisionMeshMap = std::unordered_map<Key, model::MeshPointer>;
-    CollisionMeshMap _geometryMap;
+    CollisionMeshMap _meshMap;
     std::vector<Key> _pendingGarbage;
 };
 
diff --git a/tests/physics/src/CollisionRenderMeshCacheTests.cpp b/tests/physics/src/CollisionRenderMeshCacheTests.cpp
index b4ed49b605..085c9a2fe3 100644
--- a/tests/physics/src/CollisionRenderMeshCacheTests.cpp
+++ b/tests/physics/src/CollisionRenderMeshCacheTests.cpp
@@ -25,48 +25,53 @@
 
 QTEST_MAIN(CollisionRenderMeshCacheTests)
 
-btVector3 directions[] = {
-    btVector3(1.0f, 1.0f, 1.0f),
-    btVector3(1.0f, 1.0f, -1.0f),
-    btVector3(1.0f, -1.0f, 1.0f),
-    btVector3(1.0f, -1.0f, -1.0f),
-    btVector3(-1.0f, 1.0f, 1.0f),
-    btVector3(-1.0f, 1.0f, -1.0f),
-    btVector3(-1.0f, -1.0f, 1.0f),
-    btVector3(-1.0f, -1.0f, -1.0f)
+const float INV_SQRT_THREE = 0.577350269f;
+
+const uint32_t numSphereDirections = 6 + 8;
+btVector3 sphereDirections[] = {
+    btVector3(1.0f, 0.0f, 0.0f),
+    btVector3(-1.0f, 0.0f, 0.0f),
+    btVector3(0.0f, 1.0f, 0.0f),
+    btVector3(0.0f, -1.0f, 0.0f),
+    btVector3(0.0f, 0.0f, 1.0f),
+    btVector3(0.0f, 0.0f, -1.0f),
+    btVector3(INV_SQRT_THREE, INV_SQRT_THREE, INV_SQRT_THREE),
+    btVector3(INV_SQRT_THREE, INV_SQRT_THREE, -INV_SQRT_THREE),
+    btVector3(INV_SQRT_THREE, -INV_SQRT_THREE, INV_SQRT_THREE),
+    btVector3(INV_SQRT_THREE, -INV_SQRT_THREE, -INV_SQRT_THREE),
+    btVector3(-INV_SQRT_THREE, INV_SQRT_THREE, INV_SQRT_THREE),
+    btVector3(-INV_SQRT_THREE, INV_SQRT_THREE, -INV_SQRT_THREE),
+    btVector3(-INV_SQRT_THREE, -INV_SQRT_THREE, INV_SQRT_THREE),
+    btVector3(-INV_SQRT_THREE, -INV_SQRT_THREE, -INV_SQRT_THREE)
 };
 
-void computeCubePoints(const btVector3& center, btScalar radius, uint32_t numPoints,
-        btAlignedObjectArray<btVector3>& points) {
-    points.reserve(points.size() + 8);
-    for (uint32_t i = 0; i < 8; ++i) {
-        points.push_back(center + radius * directions[i]);
-    }
-}
-
 float randomFloat() {
-    return (float)rand() / (float)RAND_MAX;
+    return 2.0f * ((float)rand() / (float)RAND_MAX) - 1.0f;
 }
 
-btBoxShape* createRandomCubeShape() {
-    //const btScalar MAX_RADIUS = 3.0;
-    //const btScalar MIN_RADIUS = 0.5;
-    //btScalar radius = randomFloat() * (MAX_RADIUS - MIN_RADIUS) + MIN_RADIUS;
-    btScalar radius = 0.5f;
-    btVector3 halfExtents(radius, radius, radius);
-
-    btBoxShape* shape = new btBoxShape(halfExtents);
+btBoxShape* createBoxShape(const btVector3& extent) {
+    btBoxShape* shape = new btBoxShape(0.5f * extent);
     return shape;
 }
 
-void CollisionRenderMeshCacheTests::test001() {
+btConvexHullShape* createConvexHull(float radius) {
+    btConvexHullShape* hull = new btConvexHullShape();
+    for (uint32_t i = 0; i < numSphereDirections; ++i) {
+        btVector3 point = radius * sphereDirections[i];
+        hull->addPoint(point, false);
+    }
+    hull->recalcLocalAabb();
+    return hull;
+}
+
+void CollisionRenderMeshCacheTests::testShapeHullManifold() {
     // make a box shape
-    btBoxShape* box = createRandomCubeShape();
+    btVector3 extent(1.0f, 2.0f, 3.0f);
+    btBoxShape* box = createBoxShape(extent);
 
     // wrap it with a ShapeHull
     btShapeHull hull(box);
-    //const btScalar MARGIN = 0.01f;
-    const btScalar MARGIN = 0.00f;
+    const float MARGIN = 0.0f;
     hull.buildHull(MARGIN);
 
     // verify the vertex count is capped
@@ -75,8 +80,8 @@ void CollisionRenderMeshCacheTests::test001() {
 
     // verify the mesh is inside the radius
     btVector3 halfExtents = box->getHalfExtentsWithMargin();
-    btScalar acceptableRadiusError = 0.01f;
-    btScalar maxRadius = halfExtents.length() + acceptableRadiusError;
+    float ACCEPTABLE_EXTENTS_ERROR = 0.01f;
+    float maxRadius = halfExtents.length() + ACCEPTABLE_EXTENTS_ERROR;
     const btVector3* meshVertices = hull.getVertexPointer();
     for (uint32_t i = 0; i < numVertices; ++i) {
         btVector3 vertex = meshVertices[i];
@@ -107,167 +112,166 @@ void CollisionRenderMeshCacheTests::test001() {
         QVERIFY(face.dot(center) > 0.0f);
     }
 
+    // delete unmanaged memory
     delete box;
 }
 
-#ifdef FOO
-void CollisionRenderMeshCacheTests::test001() {
-    CollisionRenderMeshCache cache;
+void CollisionRenderMeshCacheTests::testCompoundShape() {
+    uint32_t numSubShapes = 3;
 
-    // create a compound shape
-    btScalar radiusA = 1.0f;
-    btScalar radiusB = 1.5f;
-    btScalar radiusC = 2.75f;
+    btVector3 centers[] = {
+        btVector3(1.0f, 0.0f, 0.0f),
+        btVector3(0.0f, -2.0f, 0.0f),
+        btVector3(0.0f, 0.0f, 3.0f),
+    };
 
-    btVector3 centerA(radiusA, 0.0f, 0.0f);
-    btVector3 centerB(0.0f, radiusB, 0.0f);
-    btVector3 centerC(0.0f, 0.0f, radiusC);
+    float radii[] = { 3.0f, 2.0f, 1.0f };
 
-    btCompoundShape compoundShape = new btCompoundShape();
+    btCompoundShape* compoundShape = new btCompoundShape();
     for (uint32_t i = 0; i < numSubShapes; ++i) {
+        btTransform transform;
+        transform.setOrigin(centers[i]);
+        btConvexHullShape* hull = createConvexHull(radii[i]);
+        compoundShape->addChildShape(transform, hull);
     }
 
+    // create the cache
+    CollisionRenderMeshCache cache;
+    QVERIFY(cache.getNumMeshes() == 0);
 
-    // get the mesh
+    // get the mesh once
+    model::MeshPointer mesh = cache.getMesh(compoundShape);
+    QVERIFY((bool)mesh);
+    QVERIFY(cache.getNumMeshes() == 1);
 
     // get the mesh again
+    model::MeshPointer mesh2 = cache.getMesh(compoundShape);
+    QVERIFY(mesh2 == mesh);
+    QVERIFY(cache.getNumMeshes() == 1);
 
     // forget the mesh once
+    cache.releaseMesh(compoundShape);
+    mesh.reset();
+    QVERIFY(cache.getNumMeshes() == 1);
 
-    // collect garbage
+    // collect garbage (should still cache mesh)
+    cache.collectGarbage();
+    QVERIFY(cache.getNumMeshes() == 1);
 
-    // forget the mesh a second time
+    // forget the mesh a second time (should still cache mesh)
+    cache.releaseMesh(compoundShape);
+    mesh2.reset();
+    QVERIFY(cache.getNumMeshes() == 1);
 
-    // collect garbage
+    // collect garbage (should no longer cache mesh)
+    cache.collectGarbage();
+    QVERIFY(cache.getNumMeshes() == 0);
 
+    // delete unmanaged memory
+    for (int i = 0; i < compoundShape->getNumChildShapes(); ++i) {
+        delete compoundShape->getChildShape(i);
+    }
+    delete compoundShape;
 }
-#endif // FOO
 
-/*
-void CollisionRenderMeshCacheTests::addManyShapes() {
-    ShapeManager shapeManager;
-
-    QVector<btCollisionShape*> shapes;
-
-    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) {
-        // make a sphere
-        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);
-        shapes.push_back(shape);
-        QCOMPARE(shape != nullptr, true);
-
-        // make a box
-        float radius = 0.5f * s;
-        info.setSphere(radius);
-        shape = shapeManager.getShape(info);
-        shapes.push_back(shape);
-        QCOMPARE(shape != nullptr, true);
+void CollisionRenderMeshCacheTests::testMultipleShapes() {
+    // shapeA is compound of hulls
+    uint32_t numSubShapes = 3;
+    btVector3 centers[] = {
+        btVector3(1.0f, 0.0f, 0.0f),
+        btVector3(0.0f, -2.0f, 0.0f),
+        btVector3(0.0f, 0.0f, 3.0f),
+    };
+    float radii[] = { 3.0f, 2.0f, 1.0f };
+    btCompoundShape* shapeA = new btCompoundShape();
+    for (uint32_t i = 0; i < numSubShapes; ++i) {
+        btTransform transform;
+        transform.setOrigin(centers[i]);
+        btConvexHullShape* hull = createConvexHull(radii[i]);
+        shapeA->addChildShape(transform, hull);
     }
 
-    // verify shape count
-    int numShapes = shapeManager.getNumShapes();
-    QCOMPARE(numShapes, 2 * numSizes);
-
-    // release each shape by pointer
-    for (int i = 0; i < numShapes; ++i) {
-        btCollisionShape* shape = shapes[i];
-        bool success = shapeManager.releaseShape(shape);
-        QCOMPARE(success, true);
+    // shapeB is compound of boxes
+    btVector3 extents[] = {
+        btVector3(1.0f, 2.0f, 3.0f),
+        btVector3(2.0f, 3.0f, 1.0f),
+        btVector3(3.0f, 1.0f, 2.0f),
+    };
+    btCompoundShape* shapeB = new btCompoundShape();
+    for (uint32_t i = 0; i < numSubShapes; ++i) {
+        btTransform transform;
+        transform.setOrigin(centers[i]);
+        btBoxShape* box = createBoxShape(extents[i]);
+        shapeB->addChildShape(transform, box);
     }
 
-    // verify zero references
-    for (int i = 0; i < numShapes; ++i) {
-        btCollisionShape* shape = shapes[i];
-        int numReferences = shapeManager.getNumReferences(shape);
-        QCOMPARE(numReferences, 0);
+    // shapeC is just a box
+    btVector3 extentC(7.0f, 3.0f, 5.0f);
+    btBoxShape* shapeC = createBoxShape(extentC);
+
+    // create the cache
+    CollisionRenderMeshCache cache;
+    QVERIFY(cache.getNumMeshes() == 0);
+
+    // get the meshes
+    model::MeshPointer meshA = cache.getMesh(shapeA);
+    model::MeshPointer meshB = cache.getMesh(shapeB);
+    model::MeshPointer meshC = cache.getMesh(shapeC);
+    QVERIFY((bool)meshA);
+    QVERIFY((bool)meshB);
+    QVERIFY((bool)meshC);
+    QVERIFY(cache.getNumMeshes() == 3);
+
+    // get the meshes again
+    model::MeshPointer meshA2 = cache.getMesh(shapeA);
+    model::MeshPointer meshB2 = cache.getMesh(shapeB);
+    model::MeshPointer meshC2 = cache.getMesh(shapeC);
+    QVERIFY(meshA == meshA2);
+    QVERIFY(meshB == meshB2);
+    QVERIFY(meshC == meshC2);
+    QVERIFY(cache.getNumMeshes() == 3);
+
+    // forget the meshes once
+    cache.releaseMesh(shapeA);
+    cache.releaseMesh(shapeB);
+    cache.releaseMesh(shapeC);
+    meshA2.reset();
+    meshB2.reset();
+    meshC2.reset();
+    QVERIFY(cache.getNumMeshes() == 3);
+
+    // collect garbage (should still cache mesh)
+    cache.collectGarbage();
+    QVERIFY(cache.getNumMeshes() == 3);
+
+    // forget again, one mesh at a time...
+    // shapeA...
+    cache.releaseMesh(shapeA);
+    meshA.reset();
+    QVERIFY(cache.getNumMeshes() == 3);
+    cache.collectGarbage();
+    QVERIFY(cache.getNumMeshes() == 2);
+    // shapeB...
+    cache.releaseMesh(shapeB);
+    meshB.reset();
+    QVERIFY(cache.getNumMeshes() == 2);
+    cache.collectGarbage();
+    QVERIFY(cache.getNumMeshes() == 1);
+    // shapeC...
+    cache.releaseMesh(shapeC);
+    meshC.reset();
+    QVERIFY(cache.getNumMeshes() == 1);
+    cache.collectGarbage();
+    QVERIFY(cache.getNumMeshes() == 0);
+
+    // delete unmanaged memory
+    for (int i = 0; i < shapeA->getNumChildShapes(); ++i) {
+        delete shapeA->getChildShape(i);
     }
-}
-
-void CollisionRenderMeshCacheTests::addBoxShape() {
-    ShapeInfo info;
-    glm::vec3 halfExtents(1.23f, 4.56f, 7.89f);
-    info.setBox(halfExtents);
-
-    ShapeManager shapeManager;
-    btCollisionShape* shape = shapeManager.getShape(info);
-
-    ShapeInfo otherInfo = info;
-    btCollisionShape* otherShape = shapeManager.getShape(otherInfo);
-    QCOMPARE(shape, otherShape);
-}
-
-void CollisionRenderMeshCacheTests::addSphereShape() {
-    ShapeInfo info;
-    float radius = 1.23f;
-    info.setSphere(radius);
-
-    ShapeManager shapeManager;
-    btCollisionShape* shape = shapeManager.getShape(info);
-
-    ShapeInfo otherInfo = info;
-    btCollisionShape* otherShape = shapeManager.getShape(otherInfo);
-    QCOMPARE(shape, otherShape);
-}
-
-void CollisionRenderMeshCacheTests::addCompoundShape() {
-    // initialize some points for generating tetrahedral convex hulls
-    QVector<glm::vec3> tetrahedron;
-    tetrahedron.push_back(glm::vec3(1.0f, 1.0f, 1.0f));
-    tetrahedron.push_back(glm::vec3(1.0f, -1.0f, -1.0f));
-    tetrahedron.push_back(glm::vec3(-1.0f, 1.0f, -1.0f));
-    tetrahedron.push_back(glm::vec3(-1.0f, -1.0f, 1.0f));
-    int numHullPoints = tetrahedron.size();
-
-    // compute the points of the hulls
-    ShapeInfo::PointCollection pointCollection;
-    int numHulls = 5;
-    glm::vec3 offsetNormal(1.0f, 0.0f, 0.0f);
-    for (int i = 0; i < numHulls; ++i) {
-        glm::vec3 offset = (float)(i - numHulls/2) * offsetNormal;
-        ShapeInfo::PointList pointList;
-        float radius = (float)(i + 1);
-        for (int j = 0; j < numHullPoints; ++j) {
-            glm::vec3 point = radius * tetrahedron[j] + offset;
-            pointList.push_back(point);
-        }
-        pointCollection.push_back(pointList);
+    delete shapeA;
+    for (int i = 0; i < shapeB->getNumChildShapes(); ++i) {
+        delete shapeB->getChildShape(i);
     }
-
-    // create the ShapeInfo
-    ShapeInfo info;
-    info.setPointCollection(hulls);
-
-    // create the shape
-    ShapeManager shapeManager;
-    btCollisionShape* shape = shapeManager.getShape(info);
-    QVERIFY(shape != nullptr);
-
-    // verify the shape is correct type
-    QCOMPARE(shape->getShapeType(), (int)COMPOUND_SHAPE_PROXYTYPE);
-
-    // verify the shape has correct number of children
-    btCompoundShape* compoundShape = static_cast<btCompoundShape*>(shape);
-    QCOMPARE(compoundShape->getNumChildShapes(), numHulls);
-
-    // verify manager has only one shape
-    QCOMPARE(shapeManager.getNumShapes(), 1);
-    QCOMPARE(shapeManager.getNumReferences(info), 1);
-
-    // release the shape
-    shapeManager.releaseShape(shape);
-    QCOMPARE(shapeManager.getNumShapes(), 1);
-    QCOMPARE(shapeManager.getNumReferences(info), 0);
-
-    // collect garbage
-    shapeManager.collectGarbage();
-    QCOMPARE(shapeManager.getNumShapes(), 0);
-    QCOMPARE(shapeManager.getNumReferences(info), 0);
+    delete shapeB;
+    delete shapeC;
 }
-*/
diff --git a/tests/physics/src/CollisionRenderMeshCacheTests.h b/tests/physics/src/CollisionRenderMeshCacheTests.h
index d927bf8cab..640314a2a0 100644
--- a/tests/physics/src/CollisionRenderMeshCacheTests.h
+++ b/tests/physics/src/CollisionRenderMeshCacheTests.h
@@ -18,7 +18,9 @@ class CollisionRenderMeshCacheTests : public QObject {
     Q_OBJECT
 
 private slots:
-    void test001();
+    void testShapeHullManifold();
+    void testCompoundShape();
+    void testMultipleShapes();
 };
 
 #endif // hifi_CollisionRenderMeshCacheTests_h