diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 1614e07057..85b312dd57 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -500,7 +500,7 @@ namespace MWWorld { const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - navigator->update(player.getRefData().getPosition().asVec3()); + navigator->updatePlayerPosition(player.getRefData().getPosition().asVec3()); if (!mCurrentCell || !mCurrentCell->isExterior()) return; diff --git a/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp index 2d6d9f66bb..fb0f97831a 100644 --- a/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp @@ -54,7 +54,7 @@ namespace { TileCachedRecastMeshManager manager(mSettings); std::size_t calls = 0; - manager.forEachTilePosition([&] (const TilePosition&) { ++calls; }); + manager.forEachTile([&] (const TilePosition&, const CachedRecastMeshManager&) { ++calls; }); EXPECT_EQ(calls, 0); } @@ -73,6 +73,16 @@ namespace EXPECT_FALSE(manager.addObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground)); } + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_should_add_tiles) + { + TileCachedRecastMeshManager manager(mSettings); + const btBoxShape boxShape(btVector3(20, 20, 100)); + ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground)); + for (int x = -1; x < 1; ++x) + for (int y = -1; y < 1; ++y) + ASSERT_TRUE(manager.hasTile(TilePosition(x, y))); + } + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, update_object_for_changed_object_should_return_changed_tiles) { TileCachedRecastMeshManager manager(mSettings); @@ -238,4 +248,79 @@ namespace manager.removeObject(ObjectId(&manager)); EXPECT_EQ(manager.getRevision(), beforeRemoveRevision); } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_water_for_new_water_should_return_true) + { + TileCachedRecastMeshManager manager(mSettings); + const osg::Vec2i cellPosition(0, 0); + const int cellSize = 8192; + EXPECT_TRUE(manager.addWater(cellPosition, cellSize, btTransform::getIdentity())); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_water_for_not_max_int_should_add_new_tiles) + { + TileCachedRecastMeshManager manager(mSettings); + const osg::Vec2i cellPosition(0, 0); + const int cellSize = 8192; + ASSERT_TRUE(manager.addWater(cellPosition, cellSize, btTransform::getIdentity())); + for (int x = -6; x < 6; ++x) + for (int y = -6; y < 6; ++y) + ASSERT_TRUE(manager.hasTile(TilePosition(x, y))); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_water_for_max_int_should_not_add_new_tiles) + { + TileCachedRecastMeshManager manager(mSettings); + const btBoxShape boxShape(btVector3(20, 20, 100)); + ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground)); + const osg::Vec2i cellPosition(0, 0); + const int cellSize = std::numeric_limits::max(); + ASSERT_TRUE(manager.addWater(cellPosition, cellSize, btTransform::getIdentity())); + for (int x = -6; x < 6; ++x) + for (int y = -6; y < 6; ++y) + ASSERT_EQ(manager.hasTile(TilePosition(x, y)), -1 <= x && x <= 0 && -1 <= y && y <= 0); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_absent_cell_should_return_nullopt) + { + TileCachedRecastMeshManager manager(mSettings); + EXPECT_EQ(manager.removeWater(osg::Vec2i(0, 0)), std::nullopt); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_existing_cell_should_return_removed_water) + { + TileCachedRecastMeshManager manager(mSettings); + const osg::Vec2i cellPosition(0, 0); + const int cellSize = 8192; + ASSERT_TRUE(manager.addWater(cellPosition, cellSize, btTransform::getIdentity())); + const auto result = manager.removeWater(cellPosition); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->mCellSize, cellSize); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_existing_cell_should_remove_empty_tiles) + { + TileCachedRecastMeshManager manager(mSettings); + const osg::Vec2i cellPosition(0, 0); + const int cellSize = 8192; + ASSERT_TRUE(manager.addWater(cellPosition, cellSize, btTransform::getIdentity())); + ASSERT_TRUE(manager.removeWater(cellPosition)); + for (int x = -6; x < 6; ++x) + for (int y = -6; y < 6; ++y) + ASSERT_FALSE(manager.hasTile(TilePosition(x, y))); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_existing_cell_should_leave_not_empty_tiles) + { + TileCachedRecastMeshManager manager(mSettings); + const btBoxShape boxShape(btVector3(20, 20, 100)); + ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground)); + const osg::Vec2i cellPosition(0, 0); + const int cellSize = 8192; + ASSERT_TRUE(manager.addWater(cellPosition, cellSize, btTransform::getIdentity())); + ASSERT_TRUE(manager.removeWater(cellPosition)); + for (int x = -6; x < 6; ++x) + for (int y = -6; y < 6; ++y) + ASSERT_EQ(manager.hasTile(TilePosition(x, y)), -1 <= x && x <= 0 && -1 <= y && y <= 0); + } } diff --git a/components/detournavigator/cachedrecastmeshmanager.cpp b/components/detournavigator/cachedrecastmeshmanager.cpp index 22b047fbd3..2788b80461 100644 --- a/components/detournavigator/cachedrecastmeshmanager.cpp +++ b/components/detournavigator/cachedrecastmeshmanager.cpp @@ -66,4 +66,9 @@ namespace DetourNavigator { mImpl.reportNavMeshChange(recastMeshVersion, navMeshVersion); } + + Version CachedRecastMeshManager::getVersion() const + { + return mImpl.getVersion(); + } } diff --git a/components/detournavigator/cachedrecastmeshmanager.hpp b/components/detournavigator/cachedrecastmeshmanager.hpp index a19f017a4a..ea55348f70 100644 --- a/components/detournavigator/cachedrecastmeshmanager.hpp +++ b/components/detournavigator/cachedrecastmeshmanager.hpp @@ -28,6 +28,8 @@ namespace DetourNavigator void reportNavMeshChange(Version recastMeshVersion, Version navMeshVersion); + Version getVersion() const; + private: RecastMeshManager mImpl; std::shared_ptr mCached; diff --git a/components/detournavigator/navigator.hpp b/components/detournavigator/navigator.hpp index 8cf4cb80e8..0dab7ba6b3 100644 --- a/components/detournavigator/navigator.hpp +++ b/components/detournavigator/navigator.hpp @@ -155,11 +155,17 @@ namespace DetourNavigator virtual void removePathgrid(const ESM::Pathgrid& pathgrid) = 0; /** - * @brief update start background navmesh update using current scene state. + * @brief update starts background navmesh update using current scene state. * @param playerPosition setup initial point to order build tiles of navmesh. */ virtual void update(const osg::Vec3f& playerPosition) = 0; + /** + * @brief updatePlayerPosition starts background navmesh update using current scene state only when player position has been changed. + * @param playerPosition setup initial point to order build tiles of navmesh. + */ + virtual void updatePlayerPosition(const osg::Vec3f& playerPosition) = 0; + /** * @brief disable navigator updates */ diff --git a/components/detournavigator/navigatorimpl.cpp b/components/detournavigator/navigatorimpl.cpp index 7522fe6227..b7b3bbd586 100644 --- a/components/detournavigator/navigatorimpl.cpp +++ b/components/detournavigator/navigatorimpl.cpp @@ -146,6 +146,15 @@ namespace DetourNavigator mNavMeshManager.update(playerPosition, v.first); } + void NavigatorImpl::updatePlayerPosition(const osg::Vec3f& playerPosition) + { + const TilePosition tilePosition = getTilePosition(mSettings, toNavMeshCoordinates(mSettings, playerPosition)); + if (mLastPlayerPosition.has_value() && *mLastPlayerPosition == tilePosition) + return; + update(playerPosition); + mLastPlayerPosition = tilePosition; + } + void NavigatorImpl::setUpdatesEnabled(bool enabled) { mUpdatesEnabled = enabled; diff --git a/components/detournavigator/navigatorimpl.hpp b/components/detournavigator/navigatorimpl.hpp index 3249462616..80c6957d79 100644 --- a/components/detournavigator/navigatorimpl.hpp +++ b/components/detournavigator/navigatorimpl.hpp @@ -46,6 +46,8 @@ namespace DetourNavigator void update(const osg::Vec3f& playerPosition) override; + void updatePlayerPosition(const osg::Vec3f& playerPosition) override; + void setUpdatesEnabled(bool enabled) override; void wait(Loading::Listener& listener, WaitConditionType waitConditionType) override; @@ -66,6 +68,7 @@ namespace DetourNavigator Settings mSettings; NavMeshManager mNavMeshManager; bool mUpdatesEnabled; + std::optional mLastPlayerPosition; std::map mAgents; std::unordered_map mAvoidIds; std::unordered_map mWaterIds; diff --git a/components/detournavigator/navigatorstub.hpp b/components/detournavigator/navigatorstub.hpp index 2c12c45eb5..0a08813938 100644 --- a/components/detournavigator/navigatorstub.hpp +++ b/components/detournavigator/navigatorstub.hpp @@ -71,6 +71,8 @@ namespace DetourNavigator void update(const osg::Vec3f& /*playerPosition*/) override {} + void updatePlayerPosition(const osg::Vec3f& /*playerPosition*/) override {}; + void setUpdatesEnabled(bool /*enabled*/) override {} void wait(Loading::Listener& /*listener*/, WaitConditionType /*waitConditionType*/) override {} diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index 8f1aa86d44..de4c21c68a 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -171,7 +171,7 @@ namespace DetourNavigator } } const auto maxTiles = std::min(mSettings.mMaxTilesNumber, navMesh.getParams()->maxTiles); - mRecastMeshManager.forEachTilePosition([&] (const TilePosition& tile) + mRecastMeshManager.forEachTile([&] (const TilePosition& tile, CachedRecastMeshManager& recastMeshManager) { if (tilesToPost.count(tile)) return; @@ -181,6 +181,8 @@ namespace DetourNavigator tilesToPost.insert(std::make_pair(tile, ChangeType::add)); else if (!shouldAdd && presentInNavMesh) tilesToPost.insert(std::make_pair(tile, ChangeType::mixed)); + else + recastMeshManager.reportNavMeshChange(recastMeshManager.getVersion(), Version {0, 0}); }); } mAsyncNavMeshUpdater.post(agentHalfExtents, cached, playerTile, tilesToPost); @@ -214,8 +216,8 @@ namespace DetourNavigator RecastMeshTiles NavMeshManager::getRecastMeshTiles() { std::vector tiles; - mRecastMeshManager.forEachTilePosition( - [&tiles] (const TilePosition& tile) { tiles.push_back(tile); }); + mRecastMeshManager.forEachTile( + [&tiles] (const TilePosition& tile, const CachedRecastMeshManager&) { tiles.push_back(tile); }); RecastMeshTiles result; std::transform(tiles.begin(), tiles.end(), std::inserter(result, result.end()), [this] (const TilePosition& tile) { return std::make_pair(tile, mRecastMeshManager.getMesh(tile)); }); diff --git a/components/detournavigator/oscillatingrecastmeshobject.cpp b/components/detournavigator/oscillatingrecastmeshobject.cpp index 5b84231838..fbe4b77ffd 100644 --- a/components/detournavigator/oscillatingrecastmeshobject.cpp +++ b/components/detournavigator/oscillatingrecastmeshobject.cpp @@ -1,9 +1,23 @@ #include "oscillatingrecastmeshobject.hpp" +#include "tilebounds.hpp" #include +#include + namespace DetourNavigator { + namespace + { + void limitBy(btAABB& aabb, const TileBounds& bounds) + { + aabb.m_min.setX(std::max(aabb.m_min.x(), static_cast(bounds.mMin.x()))); + aabb.m_min.setY(std::max(aabb.m_min.y(), static_cast(bounds.mMin.y()))); + aabb.m_max.setX(std::min(aabb.m_max.x(), static_cast(bounds.mMax.x()))); + aabb.m_max.setY(std::min(aabb.m_max.y(), static_cast(bounds.mMax.y()))); + } + } + OscillatingRecastMeshObject::OscillatingRecastMeshObject(RecastMeshObject&& impl, std::size_t lastChangeRevision) : mImpl(std::move(impl)) , mLastChangeRevision(lastChangeRevision) @@ -19,7 +33,7 @@ namespace DetourNavigator } bool OscillatingRecastMeshObject::update(const btTransform& transform, const AreaType areaType, - std::size_t lastChangeRevision) + std::size_t lastChangeRevision, const TileBounds& bounds) { const btTransform oldTransform = mImpl.getTransform(); if (!mImpl.update(transform, areaType)) @@ -37,6 +51,7 @@ namespace DetourNavigator } const btAABB currentAabb = mAabb; mAabb.merge(BulletHelpers::getAabb(mImpl.getShape(), transform)); + limitBy(mAabb, bounds); return currentAabb != mAabb; } } diff --git a/components/detournavigator/oscillatingrecastmeshobject.hpp b/components/detournavigator/oscillatingrecastmeshobject.hpp index 78a0c4b689..f8aabce628 100644 --- a/components/detournavigator/oscillatingrecastmeshobject.hpp +++ b/components/detournavigator/oscillatingrecastmeshobject.hpp @@ -3,6 +3,7 @@ #include "areatype.hpp" #include "recastmeshobject.hpp" +#include "tilebounds.hpp" #include #include @@ -15,7 +16,8 @@ namespace DetourNavigator explicit OscillatingRecastMeshObject(RecastMeshObject&& impl, std::size_t lastChangeRevision); explicit OscillatingRecastMeshObject(const RecastMeshObject& impl, std::size_t lastChangeRevision); - bool update(const btTransform& transform, const AreaType areaType, std::size_t lastChangeRevision); + bool update(const btTransform& transform, const AreaType areaType, std::size_t lastChangeRevision, + const TileBounds& bounds); const RecastMeshObject& getImpl() const { return mImpl; } diff --git a/components/detournavigator/recastmeshmanager.cpp b/components/detournavigator/recastmeshmanager.cpp index cdb9169d9e..bfc0409a51 100644 --- a/components/detournavigator/recastmeshmanager.cpp +++ b/components/detournavigator/recastmeshmanager.cpp @@ -5,19 +5,19 @@ namespace DetourNavigator RecastMeshManager::RecastMeshManager(const Settings& settings, const TileBounds& bounds, std::size_t generation) : mGeneration(generation) , mMeshBuilder(settings, bounds) + , mTileBounds(bounds) { } bool RecastMeshManager::addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, const AreaType areaType) { + const auto object = mObjects.lower_bound(id); + if (object != mObjects.end() && object->first == id) + return false; const auto iterator = mObjectsOrder.emplace(mObjectsOrder.end(), OscillatingRecastMeshObject(RecastMeshObject(shape, transform, areaType), mRevision + 1)); - if (!mObjects.emplace(id, iterator).second) - { - mObjectsOrder.erase(iterator); - return false; - } + mObjects.emplace_hint(object, id, iterator); ++mRevision; return true; } @@ -29,7 +29,7 @@ namespace DetourNavigator return false; const std::size_t lastChangeRevision = mLastNavMeshReportedChange.has_value() ? mLastNavMeshReportedChange->mRevision : mRevision; - if (!object->second->update(transform, areaType, lastChangeRevision)) + if (!object->second->update(transform, areaType, lastChangeRevision, mTileBounds)) return false; ++mRevision; return true; @@ -95,6 +95,11 @@ namespace DetourNavigator mLastNavMeshReportedChange = mLastNavMeshReport; } + Version RecastMeshManager::getVersion() const + { + return Version {mGeneration, mRevision}; + } + void RecastMeshManager::rebuild() { mMeshBuilder.reset(); diff --git a/components/detournavigator/recastmeshmanager.hpp b/components/detournavigator/recastmeshmanager.hpp index daa123fcbc..5922821f26 100644 --- a/components/detournavigator/recastmeshmanager.hpp +++ b/components/detournavigator/recastmeshmanager.hpp @@ -13,7 +13,6 @@ #include #include #include -#include class btCollisionShape; @@ -53,6 +52,8 @@ namespace DetourNavigator void reportNavMeshChange(Version recastMeshVersion, Version navMeshVersion); + Version getVersion() const; + private: struct Report { @@ -63,8 +64,9 @@ namespace DetourNavigator std::size_t mRevision = 0; std::size_t mGeneration; RecastMeshBuilder mMeshBuilder; + TileBounds mTileBounds; std::list mObjectsOrder; - std::unordered_map::iterator> mObjects; + std::map::iterator> mObjects; std::list mWaterOrder; std::map::iterator> mWater; std::optional mLastNavMeshReportedChange; diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index 20ebc8fea9..42ae93e806 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -3,6 +3,9 @@ #include "gettilespositions.hpp" #include "settingsutils.hpp" +#include +#include + namespace DetourNavigator { TileCachedRecastMeshManager::TileCachedRecastMeshManager(const Settings& settings) @@ -12,23 +15,22 @@ namespace DetourNavigator bool TileCachedRecastMeshManager::addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, const AreaType areaType) { - bool result = false; - auto& tilesPositions = mObjectsTilesPositions[id]; + std::vector tilesPositions; const auto border = getBorderSize(mSettings); { auto tiles = mTiles.lock(); getTilesPositions(shape, transform, mSettings, [&] (const TilePosition& tilePosition) { if (addTile(id, shape, transform, areaType, tilePosition, border, tiles.get())) - { - tilesPositions.insert(tilePosition); - result = true; - } + tilesPositions.push_back(tilePosition); }); } - if (result) - ++mRevision; - return result; + if (tilesPositions.empty()) + return false; + std::sort(tilesPositions.begin(), tilesPositions.end()); + mObjectsTilesPositions.insert_or_assign(id, std::move(tilesPositions)); + ++mRevision; + return true; } std::optional TileCachedRecastMeshManager::removeObject(const ObjectId id) diff --git a/components/detournavigator/tilecachedrecastmeshmanager.hpp b/components/detournavigator/tilecachedrecastmeshmanager.hpp index 68683f4100..23ecc77634 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.hpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.hpp @@ -9,9 +9,10 @@ #include +#include #include #include -#include +#include namespace DetourNavigator { @@ -33,14 +34,14 @@ namespace DetourNavigator auto& currentTiles = object->second; const auto border = getBorderSize(mSettings); bool changed = false; - std::set newTiles; + std::vector newTiles; { auto tiles = mTiles.lock(); const auto onTilePosition = [&] (const TilePosition& tilePosition) { - if (currentTiles.count(tilePosition)) + if (std::binary_search(currentTiles.begin(), currentTiles.end(), tilePosition)) { - newTiles.insert(tilePosition); + newTiles.push_back(tilePosition); if (updateTile(id, transform, areaType, tilePosition, tiles.get())) { onChangedTile(tilePosition); @@ -49,24 +50,27 @@ namespace DetourNavigator } else if (addTile(id, shape, transform, areaType, tilePosition, border, tiles.get())) { - newTiles.insert(tilePosition); + newTiles.push_back(tilePosition); onChangedTile(tilePosition); changed = true; } }; getTilesPositions(shape, transform, mSettings, onTilePosition); + std::sort(newTiles.begin(), newTiles.end()); for (const auto& tile : currentTiles) { - if (!newTiles.count(tile) && removeTile(id, tile, tiles.get())) + if (!std::binary_search(newTiles.begin(), newTiles.end(), tile) && removeTile(id, tile, tiles.get())) { onChangedTile(tile); changed = true; } } } - std::swap(currentTiles, newTiles); if (changed) + { + currentTiles = std::move(newTiles); ++mRevision; + } return changed; } @@ -81,10 +85,10 @@ namespace DetourNavigator bool hasTile(const TilePosition& tilePosition); template - void forEachTilePosition(Function&& function) + void forEachTile(Function&& function) { - for (const auto& tile : *mTiles.lock()) - function(tile.first); + for (auto& [tilePosition, recastMeshManager] : *mTiles.lock()) + function(tilePosition, recastMeshManager); } std::size_t getRevision() const; @@ -94,7 +98,7 @@ 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; std::size_t mTilesGeneration = 0;