diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 9070528827..12775035ba 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -24,6 +24,7 @@ if (GTEST_FOUND AND GMOCK_FOUND) detournavigator/gettilespositions.cpp detournavigator/recastmeshobject.cpp detournavigator/navmeshtilescache.cpp + detournavigator/tilecachedrecastmeshmanager.cpp ) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) diff --git a/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp new file mode 100644 index 0000000000..0443fc9cef --- /dev/null +++ b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp @@ -0,0 +1,123 @@ +#include "operators.hpp" + +#include +#include + +#include +#include +#include +#include +#include + +#include + +namespace +{ + using namespace testing; + using namespace DetourNavigator; + + struct DetourNavigatorTileCachedRecastMeshManagerTest : Test + { + Settings mSettings; + + DetourNavigatorTileCachedRecastMeshManagerTest() + { + mSettings.mBorderSize = 16; + mSettings.mCellSize = 0.2f; + mSettings.mRecastScaleFactor = 0.017647058823529415f; + mSettings.mTileSize = 64; + mSettings.mTrianglesPerChunk = 256; + } + }; + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_empty_should_return_nullptr) + { + TileCachedRecastMeshManager manager(mSettings); + EXPECT_EQ(manager.getMesh(TilePosition(0, 0)), nullptr); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, has_tile_for_empty_should_return_false) + { + TileCachedRecastMeshManager manager(mSettings); + EXPECT_FALSE(manager.hasTile(TilePosition(0, 0))); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_revision_for_empty_should_return_zero) + { + const TileCachedRecastMeshManager manager(mSettings); + EXPECT_EQ(manager.getRevision(), 0); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, for_each_tile_position_for_empty_should_call_none) + { + TileCachedRecastMeshManager manager(mSettings); + std::size_t calls = 0; + manager.forEachTilePosition([&] (const TilePosition&) { ++calls; }); + EXPECT_EQ(calls, 0); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_after_add_object_should_return_recast_mesh_for_each_used_tile) + { + TileCachedRecastMeshManager manager(mSettings); + const btBoxShape boxShape(btVector3(20, 20, 100)); + manager.addObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + EXPECT_NE(manager.getMesh(TilePosition(-1, -1)), nullptr); + EXPECT_NE(manager.getMesh(TilePosition(-1, 0)), nullptr); + EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); + EXPECT_NE(manager.getMesh(TilePosition(0, 0)), nullptr); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_after_add_object_should_return_nullptr_for_unused_tile) + { + TileCachedRecastMeshManager manager(mSettings); + const btBoxShape boxShape(btVector3(20, 20, 100)); + manager.addObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + EXPECT_EQ(manager.getMesh(TilePosition(1, 0)), nullptr); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_moved_object_should_return_recast_mesh_for_each_used_tile) + { + TileCachedRecastMeshManager manager(mSettings); + const btBoxShape boxShape(btVector3(20, 20, 100)); + const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); + + manager.addObject(ObjectId(1ul), boxShape, transform, AreaType::AreaType_ground); + EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); + EXPECT_NE(manager.getMesh(TilePosition(0, 0)), nullptr); + EXPECT_NE(manager.getMesh(TilePosition(1, 0)), nullptr); + EXPECT_NE(manager.getMesh(TilePosition(1, -1)), nullptr); + + manager.updateObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + EXPECT_NE(manager.getMesh(TilePosition(-1, -1)), nullptr); + EXPECT_NE(manager.getMesh(TilePosition(-1, 0)), nullptr); + EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); + EXPECT_NE(manager.getMesh(TilePosition(0, 0)), nullptr); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_moved_object_should_return_nullptr_for_unused_tile) + { + TileCachedRecastMeshManager manager(mSettings); + const btBoxShape boxShape(btVector3(20, 20, 100)); + const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); + + manager.addObject(ObjectId(1ul), boxShape, transform, AreaType::AreaType_ground); + EXPECT_EQ(manager.getMesh(TilePosition(-1, -1)), nullptr); + EXPECT_EQ(manager.getMesh(TilePosition(-1, 0)), nullptr); + + manager.updateObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + EXPECT_EQ(manager.getMesh(TilePosition(1, 0)), nullptr); + EXPECT_EQ(manager.getMesh(TilePosition(1, -1)), nullptr); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_removed_object_should_return_nullptr_for_all_previously_used_tiles) + { + TileCachedRecastMeshManager manager(mSettings); + const btBoxShape boxShape(btVector3(20, 20, 100)); + manager.addObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + manager.removeObject(ObjectId(1ul)); + EXPECT_EQ(manager.getMesh(TilePosition(-1, -1)), nullptr); + EXPECT_EQ(manager.getMesh(TilePosition(-1, 0)), nullptr); + EXPECT_EQ(manager.getMesh(TilePosition(0, -1)), nullptr); + EXPECT_EQ(manager.getMesh(TilePosition(0, 0)), nullptr); + } +} diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index 66bf39aaf0..3a31d010e8 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -44,9 +44,11 @@ namespace DetourNavigator bool NavMeshManager::updateObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, const AreaType areaType) { - if (!mRecastMeshManager.updateObject(id, transform, areaType)) + const auto changedTiles = mRecastMeshManager.updateObject(id, shape, transform, areaType); + if (changedTiles.empty()) return false; - addChangedTiles(shape, transform, ChangeType::update); + for (const auto& tile : changedTiles) + addChangedTile(tile, ChangeType::update); return true; } diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index d624800e92..7070603c72 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -15,48 +15,59 @@ namespace DetourNavigator bool result = false; auto& tilesPositions = mObjectsTilesPositions[id]; const auto border = getBorderSize(mSettings); - getTilesPositions(shape, transform, mSettings, [&] (const TilePosition& tilePosition) - { - const auto tiles = mTiles.lock(); - auto tile = tiles->find(tilePosition); - if (tile == tiles->end()) + { + auto tiles = mTiles.lock(); + getTilesPositions(shape, transform, mSettings, [&] (const TilePosition& tilePosition) { - auto tileBounds = makeTileBounds(mSettings, tilePosition); - tileBounds.mMin -= osg::Vec2f(border, border); - tileBounds.mMax += osg::Vec2f(border, border); - tile = tiles->insert(std::make_pair(tilePosition, - CachedRecastMeshManager(mSettings, tileBounds))).first; - } - if (tile->second.addObject(id, shape, transform, areaType)) - { - tilesPositions.push_back(tilePosition); - result = true; - } - }); + if (addTile(id, shape, transform, areaType, tilePosition, border, tiles.get())) + { + tilesPositions.insert(tilePosition); + result = true; + } + }); + } if (result) ++mRevision; return result; } - bool TileCachedRecastMeshManager::updateObject(const ObjectId id, const btTransform& transform, - const AreaType areaType) + std::vector TileCachedRecastMeshManager::updateObject(const ObjectId id, const btCollisionShape& shape, + const btTransform& transform, const AreaType areaType) { const auto object = mObjectsTilesPositions.find(id); if (object == mObjectsTilesPositions.end()) - return false; - bool result = false; + return std::vector(); + auto& currentTiles = object->second; + const auto border = getBorderSize(mSettings); + std::vector changedTiles; + std::set newTiles; { - const auto tiles = mTiles.lock(); - for (const auto& tilePosition : object->second) + auto tiles = mTiles.lock(); + const auto onTilePosition = [&] (const TilePosition& tilePosition) { - const auto tile = tiles->find(tilePosition); - if (tile != tiles->end()) - result = tile->second.updateObject(id, transform, areaType) || result; - } + if (currentTiles.count(tilePosition)) + { + if (updateTile(id, transform, areaType, tilePosition, tiles.get())) + { + newTiles.insert(tilePosition); + changedTiles.push_back(tilePosition); + } + } + else if (addTile(id, shape, transform, areaType, tilePosition, border, tiles.get())) + { + newTiles.insert(tilePosition); + changedTiles.push_back(tilePosition); + } + }; + getTilesPositions(shape, transform, mSettings, onTilePosition); + for (const auto& tile : currentTiles) + if (!newTiles.count(tile) && removeTile(id, tile, tiles.get())) + changedTiles.push_back(tile); + std::swap(currentTiles, newTiles); } - if (result) + if (!changedTiles.empty()) ++mRevision; - return result; + return changedTiles; } boost::optional TileCachedRecastMeshManager::removeObject(const ObjectId id) @@ -65,17 +76,14 @@ namespace DetourNavigator if (object == mObjectsTilesPositions.end()) return boost::none; boost::optional result; - for (const auto& tilePosition : object->second) { - const auto tiles = mTiles.lock(); - const auto tile = tiles->find(tilePosition); - if (tile == tiles->end()) - continue; - const auto tileResult = tile->second.removeObject(id); - if (tile->second.isEmpty()) - tiles->erase(tile); - if (tileResult && !result) - result = tileResult; + auto tiles = mTiles.lock(); + for (const auto& tilePosition : object->second) + { + const auto removed = removeTile(id, tilePosition, tiles.get()); + if (removed && !result) + result = removed; + } } if (result) ++mRevision; @@ -172,4 +180,38 @@ namespace DetourNavigator { return mRevision; } + + bool TileCachedRecastMeshManager::addTile(const ObjectId id, const btCollisionShape& shape, + const btTransform& transform, const AreaType areaType, const TilePosition& tilePosition, float border, + std::map& tiles) + { + auto tile = tiles.find(tilePosition); + if (tile == tiles.end()) + { + auto tileBounds = makeTileBounds(mSettings, tilePosition); + tileBounds.mMin -= osg::Vec2f(border, border); + tileBounds.mMax += osg::Vec2f(border, border); + tile = tiles.insert(std::make_pair(tilePosition, CachedRecastMeshManager(mSettings, tileBounds))).first; + } + return tile->second.addObject(id, shape, transform, areaType); + } + + bool TileCachedRecastMeshManager::updateTile(const ObjectId id, const btTransform& transform, + const AreaType areaType, const TilePosition& tilePosition, std::map& tiles) + { + const auto tile = tiles.find(tilePosition); + return tile != tiles.end() && tile->second.updateObject(id, transform, areaType); + } + + boost::optional TileCachedRecastMeshManager::removeTile(const ObjectId id, + const TilePosition& tilePosition, std::map& tiles) + { + const auto tile = tiles.find(tilePosition); + if (tile == tiles.end()) + return boost::optional(); + const auto tileResult = tile->second.removeObject(id); + if (tile->second.isEmpty()) + tiles.erase(tile); + return tileResult; + } } diff --git a/components/detournavigator/tilecachedrecastmeshmanager.hpp b/components/detournavigator/tilecachedrecastmeshmanager.hpp index f82ef85c5c..a3d0ae1e53 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.hpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.hpp @@ -8,6 +8,7 @@ #include #include +#include namespace DetourNavigator { @@ -19,7 +20,8 @@ namespace DetourNavigator bool addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, const AreaType areaType); - bool updateObject(const ObjectId id, const btTransform& transform, const AreaType areaType); + std::vector updateObject(const ObjectId id, const btCollisionShape& shape, + const btTransform& transform, const AreaType areaType); boost::optional removeObject(const ObjectId id); @@ -43,9 +45,19 @@ namespace DetourNavigator private: const Settings& mSettings; Misc::ScopeGuarded> mTiles; - std::unordered_map> mObjectsTilesPositions; + std::unordered_map> mObjectsTilesPositions; std::map> mWaterTilesPositions; std::size_t mRevision = 0; + + bool addTile(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, + const AreaType areaType, const TilePosition& tilePosition, float border, + std::map& tiles); + + bool updateTile(const ObjectId id, const btTransform& transform, const AreaType areaType, + const TilePosition& tilePosition, std::map& tiles); + + boost::optional removeTile(const ObjectId id, const TilePosition& tilePosition, + std::map& tiles); }; }