diff --git a/tests/physics/src/CollisionRenderMeshCacheTests.cpp b/tests/physics/src/CollisionRenderMeshCacheTests.cpp index 58b45f6400..b4ed49b605 100644 --- a/tests/physics/src/CollisionRenderMeshCacheTests.cpp +++ b/tests/physics/src/CollisionRenderMeshCacheTests.cpp @@ -9,20 +9,123 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include -#include -#include - #include "CollisionRenderMeshCacheTests.h" +#include +#include + +#include +#include + +#include +#include // for MAX_HULL_POINTS + +#include "MeshUtil.cpp" + + 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) +}; +void computeCubePoints(const btVector3& center, btScalar radius, uint32_t numPoints, + btAlignedObjectArray& 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; +} + +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); + return shape; +} + +void CollisionRenderMeshCacheTests::test001() { + // make a box shape + btBoxShape* box = createRandomCubeShape(); + + // wrap it with a ShapeHull + btShapeHull hull(box); + //const btScalar MARGIN = 0.01f; + const btScalar MARGIN = 0.00f; + hull.buildHull(MARGIN); + + // verify the vertex count is capped + uint32_t numVertices = (uint32_t)hull.numVertices(); + QVERIFY(numVertices <= MAX_HULL_POINTS); + + // verify the mesh is inside the radius + btVector3 halfExtents = box->getHalfExtentsWithMargin(); + btScalar acceptableRadiusError = 0.01f; + btScalar maxRadius = halfExtents.length() + acceptableRadiusError; + const btVector3* meshVertices = hull.getVertexPointer(); + for (uint32_t i = 0; i < numVertices; ++i) { + btVector3 vertex = meshVertices[i]; + QVERIFY(vertex.length() <= maxRadius); + } + + // verify the index count is capped + uint32_t numIndices = (uint32_t)hull.numIndices(); + QVERIFY(numIndices < 6 * MAX_HULL_POINTS); + + // verify the index count is a multiple of 3 + QVERIFY(numIndices % 3 == 0); + + // verify the mesh is closed + const uint32_t* meshIndices = hull.getIndexPointer(); + bool isClosed = MeshUtil::isClosedManifold(meshIndices, numIndices); + QVERIFY(isClosed); + + // verify the triangle normals are outward using right-hand-rule + const uint32_t INDICES_PER_TRIANGLE = 3; + for (uint32_t i = 0; i < numIndices; i += INDICES_PER_TRIANGLE) { + btVector3 A = meshVertices[meshIndices[i]]; + btVector3 B = meshVertices[meshIndices[i+1]]; + btVector3 C = meshVertices[meshIndices[i+2]]; + + btVector3 face = (B - A).cross(C - B); + btVector3 center = (A + B + C) / 3.0f; + QVERIFY(face.dot(center) > 0.0f); + } + + delete box; +} + +#ifdef FOO void CollisionRenderMeshCacheTests::test001() { CollisionRenderMeshCache cache; // create a compound shape - int32_t numSubShapes = 3; + btScalar radiusA = 1.0f; + btScalar radiusB = 1.5f; + btScalar radiusC = 2.75f; + + btVector3 centerA(radiusA, 0.0f, 0.0f); + btVector3 centerB(0.0f, radiusB, 0.0f); + btVector3 centerC(0.0f, 0.0f, radiusC); + + btCompoundShape compoundShape = new btCompoundShape(); + for (uint32_t i = 0; i < numSubShapes; ++i) { + } // get the mesh @@ -38,4 +141,133 @@ void CollisionRenderMeshCacheTests::test001() { // collect garbage } +#endif // FOO +/* +void CollisionRenderMeshCacheTests::addManyShapes() { + ShapeManager shapeManager; + + QVector 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); + } + + // 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); + } + + // verify zero references + for (int i = 0; i < numShapes; ++i) { + btCollisionShape* shape = shapes[i]; + int numReferences = shapeManager.getNumReferences(shape); + QCOMPARE(numReferences, 0); + } +} + +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 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); + } + + // 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(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); +} +*/ diff --git a/tests/physics/src/MeshUtil.cpp b/tests/physics/src/MeshUtil.cpp new file mode 100644 index 0000000000..d3eb815948 --- /dev/null +++ b/tests/physics/src/MeshUtil.cpp @@ -0,0 +1,45 @@ +// +// MeshUtil.cpp +// tests/physics/src +// +// Created by Andrew Meadows 2016.07.14 +// Copyright 2016 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 +// + +#include "MeshUtil.h" + +#include + +// returns false if any edge has only one adjacent triangle +bool MeshUtil::isClosedManifold(const uint32_t* meshIndices, uint32_t numIndices) { + using EdgeList = std::unordered_map; + EdgeList edges; + + // count the triangles for each edge + const uint32_t TRIANGLE_STRIDE = 3; + for (uint32_t i = 0; i < numIndices; i += TRIANGLE_STRIDE) { + MeshUtil::TriangleEdge edge; + // the triangles indices are stored in sequential order + for (uint32_t j = 0; j < 3; ++j) { + edge.setIndices(meshIndices[i + j], meshIndices[i + ((j + 1) % 3)]); + + EdgeList::iterator edgeEntry = edges.find(edge); + if (edgeEntry == edges.end()) { + edges.insert(std::pair(edge, 1)); + } else { + edgeEntry->second += 1; + } + } + } + // scan for outside edge + for (auto& edgeEntry : edges) { + if (edgeEntry.second == 1) { + return false; + } + } + return true; +} + diff --git a/tests/physics/src/MeshUtil.h b/tests/physics/src/MeshUtil.h new file mode 100644 index 0000000000..82d33d631b --- /dev/null +++ b/tests/physics/src/MeshUtil.h @@ -0,0 +1,61 @@ +// +// MeshUtil.h +// tests/physics/src +// +// Created by Andrew Meadows 2016.07.14 +// Copyright 2016 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_MeshUtil_h +#define hifi_MeshUtil_h + +#include + +namespace MeshUtil { + +class TriangleEdge { +public: + TriangleEdge() {} + TriangleEdge(uint32_t A, uint32_t B) { + setIndices(A, B); + } + void setIndices(uint32_t A, uint32_t B) { + if (A < B) { + _indexA = A; + _indexB = B; + } else { + _indexA = B; + _indexB = A; + } + } + bool operator==(const TriangleEdge& other) const { + return _indexA == other._indexA && _indexB == other._indexB; + } + + uint32_t getIndexA() const { return _indexA; } + uint32_t getIndexB() const { return _indexB; } +private: + uint32_t _indexA { (uint32_t)(-1) }; + uint32_t _indexB { (uint32_t)(-1) }; +}; + +bool isClosedManifold(const uint32_t* meshIndices, uint32_t numIndices); + +} // MeshUtil namespace + +namespace std { + template <> + struct hash { + std::size_t operator()(const MeshUtil::TriangleEdge& edge) const { + // use Cantor's pairing function to generate a hash of ZxZ --> Z + uint32_t ab = edge.getIndexA() + edge.getIndexB(); + return hash()((ab * (ab + 1)) / 2 + edge.getIndexB()); + } + }; +} + + +#endif // hifi_MeshUtil_h