diff --git a/apps/openmw_test_suite/detournavigator/operators.hpp b/apps/openmw_test_suite/detournavigator/operators.hpp index e34d6278a..92740c65f 100644 --- a/apps/openmw_test_suite/detournavigator/operators.hpp +++ b/apps/openmw_test_suite/detournavigator/operators.hpp @@ -23,17 +23,23 @@ namespace DetourNavigator namespace { template - struct Wrapper { + struct Wrapper + { const T& mValue; }; template - inline testing::Message& writeRange(testing::Message& message, const Range& range) + inline testing::Message& writeRange(testing::Message& message, const Range& range, std::size_t newLine) { - message << "{\n"; + message << "{"; + std::size_t i = 0; for (const auto& v : range) - message << Wrapper::type> {v} << ",\n"; - return message << "}"; + { + if (i++ % newLine == 0) + message << "\n"; + message << Wrapper::type> {v} << ", "; + } + return message << "\n}"; } } @@ -60,22 +66,34 @@ namespace testing return (*this) << std::setprecision(std::numeric_limits::max_exponent10) << value.mValue; } + template <> + inline testing::Message& Message::operator <<(const Wrapper& value) + { + return (*this) << value.mValue; + } + template <> inline testing::Message& Message::operator <<(const std::deque& value) { - return writeRange(*this, value); + return writeRange(*this, value, 1); } template <> inline testing::Message& Message::operator <<(const std::vector& value) { - return writeRange(*this, value); + return writeRange(*this, value, 1); } template <> inline testing::Message& Message::operator <<(const std::vector& value) { - return writeRange(*this, value); + return writeRange(*this, value, 3); + } + + template <> + inline testing::Message& Message::operator <<(const std::vector& value) + { + return writeRange(*this, value, 3); } } diff --git a/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp b/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp index c86dee6e5..bcbf448ac 100644 --- a/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp +++ b/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp @@ -104,11 +104,9 @@ namespace -0.5, 0, -0.5, -0.5, 0, 0.5, 0.5, 0, -0.5, - 0.5, 0, -0.5, - -0.5, 0, 0.5, 0.5, 0, 0.5, })); - EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2, 3, 4, 5})); + EXPECT_EQ(recastMesh->getIndices(), std::vector({0, 1, 2, 2, 1, 3})); EXPECT_EQ(recastMesh->getAreaTypes(), std::vector({AreaType_ground, AreaType_ground})); } @@ -127,7 +125,7 @@ namespace -1, -2, 1, 1, -2, -1, -1, -2, -1, - })); + })) << recastMesh->getVertices(); EXPECT_EQ(recastMesh->getIndices(), std::vector({ 0, 2, 3, 3, 1, 0, @@ -141,7 +139,7 @@ namespace 2, 6, 7, 7, 6, 4, 4, 5, 7, - })); + })) << recastMesh->getIndices(); EXPECT_EQ(recastMesh->getAreaTypes(), std::vector(12, AreaType_ground)); } @@ -166,37 +164,35 @@ namespace ); const auto recastMesh = builder.create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getVertices(), std::vector({ - 1, 0, -1, - -1, 0, 1, + -1, -2, -1, + -1, -2, 1, -1, 0, -1, - 1, 2, 1, - -1, 2, 1, - 1, 2, -1, + -1, 0, 1, -1, 2, -1, - 1, -2, 1, - -1, -2, 1, + -1, 2, 1, 1, -2, -1, - -1, -2, -1, + 1, -2, 1, 1, 0, -1, - -1, 0, 1, 1, 0, 1, - })); + 1, 2, -1, + 1, 2, 1, + })) << recastMesh->getVertices(); EXPECT_EQ(recastMesh->getIndices(), std::vector({ - 0, 1, 2, - 3, 5, 6, - 6, 4, 3, - 3, 7, 9, - 9, 5, 3, - 3, 4, 8, - 8, 7, 3, - 10, 8, 4, - 4, 6, 10, - 10, 6, 5, - 5, 9, 10, - 10, 9, 7, - 7, 8, 10, - 11, 12, 13, - })); + 8, 3, 2, + 11, 10, 4, + 4, 5, 11, + 11, 7, 6, + 6, 10, 11, + 11, 5, 1, + 1, 7, 11, + 0, 1, 5, + 5, 4, 0, + 0, 4, 10, + 10, 6, 0, + 0, 6, 7, + 7, 1, 0, + 8, 3, 9, + })) << recastMesh->getIndices(); EXPECT_EQ(recastMesh->getAreaTypes(), std::vector(14, AreaType_ground)); } @@ -413,4 +409,24 @@ namespace RecastMesh::Water {1000, btTransform(btMatrix3x3::getIdentity(), btVector3(100, 200, 300))} })); } + + TEST_F(DetourNavigatorRecastMeshBuilderTest, add_bhv_triangle_mesh_shape_with_duplicated_vertices) + { + btTriangleMesh mesh; + mesh.addTriangle(btVector3(-1, -1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0)); + mesh.addTriangle(btVector3(1, 1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0)); + btBvhTriangleMeshShape shape(&mesh, true); + + RecastMeshBuilder builder(mSettings, mBounds); + builder.addObject(static_cast(shape), btTransform::getIdentity(), AreaType_ground); + const auto recastMesh = builder.create(mGeneration, mRevision); + EXPECT_EQ(recastMesh->getVertices(), std::vector({ + -1, 0, -1, + -1, 0, 1, + 1, 0, -1, + 1, 0, 1, + })) << recastMesh->getVertices(); + EXPECT_EQ(recastMesh->getIndices(), std::vector({2, 1, 0, 2, 1, 3})); + EXPECT_EQ(recastMesh->getAreaTypes(), std::vector({AreaType_ground, AreaType_ground})); + } } diff --git a/components/detournavigator/recastmeshbuilder.cpp b/components/detournavigator/recastmeshbuilder.cpp index f61368357..59f60394d 100644 --- a/components/detournavigator/recastmeshbuilder.cpp +++ b/components/detournavigator/recastmeshbuilder.cpp @@ -17,11 +17,50 @@ #include #include +#include namespace DetourNavigator { using BulletHelpers::makeProcessTriangleCallback; + namespace + { + void optimizeRecastMesh(std::vector& indices, std::vector& vertices) + { + std::vector> uniqueVertices; + uniqueVertices.reserve(vertices.size() / 3); + + for (std::size_t i = 0, n = vertices.size() / 3; i < n; ++i) + uniqueVertices.emplace_back(vertices[i * 3], vertices[i * 3 + 1], vertices[i * 3 + 2]); + + std::sort(uniqueVertices.begin(), uniqueVertices.end()); + const auto end = std::unique(uniqueVertices.begin(), uniqueVertices.end()); + uniqueVertices.erase(end, uniqueVertices.end()); + + if (uniqueVertices.size() == vertices.size() / 3) + return; + + for (std::size_t i = 0, n = indices.size(); i < n; ++i) + { + const auto index = indices[i]; + const auto vertex = std::make_tuple(vertices[index * 3], vertices[index * 3 + 1], vertices[index * 3 + 2]); + const auto it = std::lower_bound(uniqueVertices.begin(), uniqueVertices.end(), vertex); + assert(it != uniqueVertices.end()); + assert(*it == vertex); + indices[i] = std::distance(uniqueVertices.begin(), it); + } + + vertices.resize(uniqueVertices.size() * 3); + + for (std::size_t i = 0, n = uniqueVertices.size(); i < n; ++i) + { + vertices[i * 3] = std::get<0>(uniqueVertices[i]); + vertices[i * 3 + 1] = std::get<1>(uniqueVertices[i]); + vertices[i * 3 + 2] = std::get<2>(uniqueVertices[i]); + } + } + } + RecastMeshBuilder::RecastMeshBuilder(const Settings& settings, const TileBounds& bounds) : mSettings(settings) , mBounds(bounds) @@ -112,8 +151,9 @@ namespace DetourNavigator mWater.push_back(RecastMesh::Water {cellSize, transform}); } - std::shared_ptr RecastMeshBuilder::create(std::size_t generation, std::size_t revision) const + std::shared_ptr RecastMeshBuilder::create(std::size_t generation, std::size_t revision) { + optimizeRecastMesh(mIndices, mVertices); return std::make_shared(generation, revision, mIndices, mVertices, mAreaTypes, mWater, mSettings.get().mTrianglesPerChunk); } diff --git a/components/detournavigator/recastmeshbuilder.hpp b/components/detournavigator/recastmeshbuilder.hpp index d28558d0f..fc2bbbc02 100644 --- a/components/detournavigator/recastmeshbuilder.hpp +++ b/components/detournavigator/recastmeshbuilder.hpp @@ -34,7 +34,7 @@ namespace DetourNavigator void addWater(const int mCellSize, const btTransform& transform); - std::shared_ptr create(std::size_t generation, std::size_t revision) const; + std::shared_ptr create(std::size_t generation, std::size_t revision); void reset();