diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 2465f165b9..4baebf1119 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -572,11 +572,11 @@ namespace MWWorld void Scene::changeCellGrid(const osg::Vec3f& pos, ESM::ExteriorCellLocation playerCellIndex, bool changeEvent) { - mHalfGridSize + const int halfGridSize = isEsm4Ext(playerCellIndex.mWorldspace) ? Constants::ESM4CellGridRadius : Constants::CellGridRadius; auto navigatorUpdateGuard = mNavigator.makeUpdateGuard(); - int playerCellX = playerCellIndex.mX; - int playerCellY = playerCellIndex.mY; + const int playerCellX = playerCellIndex.mX; + const int playerCellY = playerCellIndex.mY; for (auto iter = mActiveCells.begin(); iter != mActiveCells.end();) { @@ -585,15 +585,21 @@ namespace MWWorld { const auto dx = std::abs(playerCellX - cell->getCell()->getGridX()); const auto dy = std::abs(playerCellY - cell->getCell()->getGridY()); - if (dx > mHalfGridSize || dy > mHalfGridSize) + if (dx > halfGridSize || dy > halfGridSize) unloadCell(cell, navigatorUpdateGuard.get()); } else unloadCell(cell, navigatorUpdateGuard.get()); } - mNavigator.updateBounds(playerCellIndex.mWorldspace, pos, navigatorUpdateGuard.get()); + const DetourNavigator::CellGridBounds cellGridBounds{ + .mCenter = osg::Vec2i(playerCellX, playerCellY), + .mHalfSize = halfGridSize, + }; + + mNavigator.updateBounds(playerCellIndex.mWorldspace, cellGridBounds, pos, navigatorUpdateGuard.get()); + mHalfGridSize = halfGridSize; mCurrentGridCenter = osg::Vec2i(playerCellX, playerCellY); osg::Vec4i newGrid = gridCenterToBounds(mCurrentGridCenter); mRendering.setActiveGrid(newGrid); @@ -696,7 +702,16 @@ namespace MWWorld ESM::ExteriorCellLocation(it->mData.mX, it->mData.mY, ESM::Cell::sDefaultWorldspaceId)); const osg::Vec3f position = osg::Vec3f(it->mData.mX + 0.5f, it->mData.mY + 0.5f, 0) * Constants::CellSizeInUnits; - mNavigator.updateBounds(ESM::Cell::sDefaultWorldspaceId, position, navigatorUpdateGuard.get()); + const osg::Vec2i cellPosition(it->mData.mX, it->mData.mY); + + const DetourNavigator::CellGridBounds cellGridBounds{ + .mCenter = osg::Vec2i(it->mData.mX, it->mData.mY), + .mHalfSize = Constants::CellGridRadius, + }; + + mNavigator.updateBounds( + ESM::Cell::sDefaultWorldspaceId, cellGridBounds, position, navigatorUpdateGuard.get()); + loadCell(cell, nullptr, false, position, navigatorUpdateGuard.get()); mNavigator.update(position, navigatorUpdateGuard.get()); @@ -752,7 +767,8 @@ namespace MWWorld CellStore& cell = mWorld.getWorldModel().getInterior(it->mName); ESM::Position position; mWorld.findInteriorPosition(it->mName, position); - mNavigator.updateBounds(cell.getCell()->getWorldSpace(), position.asVec3(), navigatorUpdateGuard.get()); + mNavigator.updateBounds( + cell.getCell()->getWorldSpace(), std::nullopt, position.asVec3(), navigatorUpdateGuard.get()); loadCell(cell, nullptr, false, position.asVec3(), navigatorUpdateGuard.get()); mNavigator.update(position.asVec3(), navigatorUpdateGuard.get()); @@ -902,7 +918,8 @@ namespace MWWorld loadingListener->setProgressRange(cell.count()); - mNavigator.updateBounds(cell.getCell()->getWorldSpace(), position.asVec3(), navigatorUpdateGuard.get()); + mNavigator.updateBounds( + cell.getCell()->getWorldSpace(), std::nullopt, position.asVec3(), navigatorUpdateGuard.get()); // Load cell. mPagedRefs.clear(); diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index c53ff2cc37..2927869782 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -859,6 +859,17 @@ namespace EXPECT_EQ(mNavigator->getNavMesh(mAgentBounds)->lockConst()->getVersion(), version); } + std::pair getMinMax(const RecastMeshTiles& tiles) + { + const auto lessByX = [](const auto& l, const auto& r) { return l.first.x() < r.first.x(); }; + const auto lessByY = [](const auto& l, const auto& r) { return l.first.y() < r.first.y(); }; + + const auto [minX, maxX] = std::ranges::minmax_element(tiles, lessByX); + const auto [minY, maxY] = std::ranges::minmax_element(tiles, lessByY); + + return { TilePosition(minX->first.x(), minY->first.y()), TilePosition(maxX->first.x(), maxY->first.y()) }; + } + TEST_F(DetourNavigatorNavigatorTest, update_for_very_big_object_should_be_limited) { const float size = static_cast((1 << 22) - 1); @@ -867,8 +878,10 @@ namespace .mPosition = ESM::Position{ .pos = { 0, 0, 0 }, .rot{ 0, 0, 0 } }, .mScale = 1.0f, }; + const std::optional cellGridBounds = std::nullopt; + const osg::Vec3f playerPosition(32, 1024, 0); - mNavigator->updateBounds(mWorldspace, mPlayerPosition, nullptr); + mNavigator->updateBounds(mWorldspace, cellGridBounds, playerPosition, nullptr); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); mNavigator->addObject(ObjectId(&bigBox.shape()), ObjectShapes(bigBox.instance(), objectTransform), btTransform::getIdentity(), nullptr); @@ -878,7 +891,61 @@ namespace std::mutex mutex; std::thread thread([&] { - mNavigator->update(mPlayerPosition, nullptr); + auto guard = mNavigator->makeUpdateGuard(); + mNavigator->update(playerPosition, guard.get()); + std::lock_guard lock(mutex); + updated = true; + updateFinished.notify_all(); + }); + + { + std::unique_lock lock(mutex); + updateFinished.wait_for(lock, std::chrono::seconds(10), [&] { return updated; }); + ASSERT_TRUE(updated); + } + + thread.join(); + + mNavigator->wait(WaitConditionType::allJobsDone, &mListener); + + const auto recastMeshTiles = mNavigator->getRecastMeshTiles(); + ASSERT_EQ(recastMeshTiles.size(), 1033); + EXPECT_EQ(getMinMax(recastMeshTiles), std::pair(TilePosition(-18, -17), TilePosition(18, 19))); + + const auto navMesh = mNavigator->getNavMesh(mAgentBounds); + ASSERT_NE(navMesh, nullptr); + + std::size_t usedNavMeshTiles = 0; + navMesh->lockConst()->forEachUsedTile([&](const auto&...) { ++usedNavMeshTiles; }); + EXPECT_EQ(usedNavMeshTiles, 1024); + } + + TEST_F(DetourNavigatorNavigatorTest, update_should_be_limited_by_cell_grid_bounds) + { + const float size = static_cast((1 << 22) - 1); + CollisionShapeInstance bigBox(std::make_unique(btVector3(size, size, 1))); + const ObjectTransform objectTransform{ + .mPosition = ESM::Position{ .pos = { 0, 0, 0 }, .rot{ 0, 0, 0 } }, + .mScale = 1.0f, + }; + const CellGridBounds cellGridBounds{ + .mCenter = osg::Vec2i(0, 0), + .mHalfSize = 1, + }; + const osg::Vec3f playerPosition(32, 1024, 0); + + mNavigator->updateBounds(mWorldspace, cellGridBounds, playerPosition, nullptr); + ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); + mNavigator->addObject(ObjectId(&bigBox.shape()), ObjectShapes(bigBox.instance(), objectTransform), + btTransform::getIdentity(), nullptr); + + bool updated = false; + std::condition_variable updateFinished; + std::mutex mutex; + + std::thread thread([&] { + auto guard = mNavigator->makeUpdateGuard(); + mNavigator->update(playerPosition, guard.get()); std::lock_guard lock(mutex); updated = true; updateFinished.notify_all(); @@ -886,7 +953,7 @@ namespace { std::unique_lock lock(mutex); - updateFinished.wait_for(lock, std::chrono::seconds(3), [&] { return updated; }); + updateFinished.wait_for(lock, std::chrono::seconds(10), [&] { return updated; }); ASSERT_TRUE(updated); } @@ -894,14 +961,16 @@ namespace mNavigator->wait(WaitConditionType::allJobsDone, &mListener); - EXPECT_EQ(mNavigator->getRecastMeshTiles().size(), 509); + const auto recastMeshTiles = mNavigator->getRecastMeshTiles(); + ASSERT_EQ(recastMeshTiles.size(), 854); + EXPECT_EQ(getMinMax(recastMeshTiles), std::pair(TilePosition(-12, -12), TilePosition(18, 19))); const auto navMesh = mNavigator->getNavMesh(mAgentBounds); ASSERT_NE(navMesh, nullptr); std::size_t usedNavMeshTiles = 0; navMesh->lockConst()->forEachUsedTile([&](const auto&...) { ++usedNavMeshTiles; }); - EXPECT_EQ(usedNavMeshTiles, 509); + EXPECT_EQ(usedNavMeshTiles, 854); } struct DetourNavigatorNavigatorNotSupportedAgentBoundsTest : TestWithParam diff --git a/apps/openmw_test_suite/detournavigator/settings.hpp b/apps/openmw_test_suite/detournavigator/settings.hpp index dc37dc7550..1ebbc5ba7b 100644 --- a/apps/openmw_test_suite/detournavigator/settings.hpp +++ b/apps/openmw_test_suite/detournavigator/settings.hpp @@ -39,7 +39,7 @@ namespace DetourNavigator result.mDetour.mMaxPolygonPathSize = 1024; result.mDetour.mMaxSmoothPathSize = 1024; result.mDetour.mMaxPolys = 4096; - result.mMaxTilesNumber = 512; + result.mMaxTilesNumber = 1024; result.mMinUpdateInterval = std::chrono::milliseconds(50); result.mWriteToNavMeshDb = true; return result; diff --git a/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp index 7585e405e5..e1805e993c 100644 --- a/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp @@ -173,7 +173,7 @@ namespace TileCachedRecastMeshManager manager(mSettings); const TilesPositionsRange range{ .mBegin = TilePosition(-1, -1), - .mEnd = TilePosition(1, 1), + .mEnd = TilePosition(2, 2), }; manager.setRange(range, nullptr); manager.setWorldspace(mWorldspace, nullptr); diff --git a/components/detournavigator/cellgridbounds.hpp b/components/detournavigator/cellgridbounds.hpp new file mode 100644 index 0000000000..75d1fce350 --- /dev/null +++ b/components/detournavigator/cellgridbounds.hpp @@ -0,0 +1,15 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_CELLGRIDBOUNDS_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_CELLGRIDBOUNDS_H + +#include + +namespace DetourNavigator +{ + struct CellGridBounds + { + osg::Vec2i mCenter; + int mHalfSize; + }; +} + +#endif diff --git a/components/detournavigator/navigator.hpp b/components/detournavigator/navigator.hpp index 609944b245..3f27bc1479 100644 --- a/components/detournavigator/navigator.hpp +++ b/components/detournavigator/navigator.hpp @@ -3,7 +3,9 @@ #include #include +#include +#include "cellgridbounds.hpp" #include "heightfieldshape.hpp" #include "objectid.hpp" #include "objecttransform.hpp" @@ -89,7 +91,8 @@ namespace DetourNavigator virtual void removeAgent(const AgentBounds& agentBounds) = 0; // Updates bounds for recast mesh and navmesh tiles, removes tiles outside the range. - virtual void updateBounds(ESM::RefId worldspace, const osg::Vec3f& playerPosition, const UpdateGuard* guard) + virtual void updateBounds(ESM::RefId worldspace, const std::optional& cellGridBounds, + const osg::Vec3f& playerPosition, const UpdateGuard* guard) = 0; /** diff --git a/components/detournavigator/navigatorimpl.cpp b/components/detournavigator/navigatorimpl.cpp index b14dde20fc..1cb35a58c6 100644 --- a/components/detournavigator/navigatorimpl.cpp +++ b/components/detournavigator/navigatorimpl.cpp @@ -33,9 +33,10 @@ namespace DetourNavigator --it->second; } - void NavigatorImpl::updateBounds(ESM::RefId worldspace, const osg::Vec3f& playerPosition, const UpdateGuard* guard) + void NavigatorImpl::updateBounds(ESM::RefId worldspace, const std::optional& cellGridBounds, + const osg::Vec3f& playerPosition, const UpdateGuard* guard) { - mNavMeshManager.updateBounds(worldspace, playerPosition, guard); + mNavMeshManager.updateBounds(worldspace, cellGridBounds, playerPosition, guard); } void NavigatorImpl::addObject( diff --git a/components/detournavigator/navigatorimpl.hpp b/components/detournavigator/navigatorimpl.hpp index 759ddf8d14..413fd8ef6b 100644 --- a/components/detournavigator/navigatorimpl.hpp +++ b/components/detournavigator/navigatorimpl.hpp @@ -27,7 +27,8 @@ namespace DetourNavigator void removeAgent(const AgentBounds& agentBounds) override; - void updateBounds(ESM::RefId worldspace, const osg::Vec3f& playerPosition, const UpdateGuard* guard) override; + void updateBounds(ESM::RefId worldspace, const std::optional& cellGridBounds, + const osg::Vec3f& playerPosition, const UpdateGuard* guard) override; void addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform, const UpdateGuard* guard) override; diff --git a/components/detournavigator/navigatorstub.hpp b/components/detournavigator/navigatorstub.hpp index 7638faac27..7102722517 100644 --- a/components/detournavigator/navigatorstub.hpp +++ b/components/detournavigator/navigatorstub.hpp @@ -24,8 +24,8 @@ namespace DetourNavigator void removeAgent(const AgentBounds& /*agentBounds*/) override {} - void updateBounds( - ESM::RefId /*worldspace*/, const osg::Vec3f& /*playerPosition*/, const UpdateGuard* /*guard*/) override + void updateBounds(ESM::RefId /*worldspace*/, const std::optional& /*cellGridBounds*/, + const osg::Vec3f& /*playerPosition*/, const UpdateGuard* /*guard*/) override { } diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index 502bda6f66..5db582f152 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -1,4 +1,5 @@ #include "navmeshmanager.hpp" + #include "debug.hpp" #include "gettilespositions.hpp" #include "makenavmesh.hpp" @@ -8,6 +9,7 @@ #include "waitconditiontype.hpp" #include +#include #include @@ -35,15 +37,46 @@ namespace DetourNavigator { namespace { - TilesPositionsRange makeRange(const TilePosition& center, int maxTiles) + int getMaxRadius(int maxTiles) + { + return static_cast(std::ceil(std::sqrt(static_cast(maxTiles) / osg::PIf) + 1)); + } + + TilesPositionsRange makeRange(const TilePosition& center, int radius) { - const int radius = static_cast(std::ceil(std::sqrt(static_cast(maxTiles) / osg::PIf) + 1)); return TilesPositionsRange{ .mBegin = center - TilePosition(radius, radius), .mEnd = center + TilePosition(radius + 1, radius + 1), }; } + osg::Vec2f getMinCellGridPosition(const osg::Vec2i& center, int offset, float cellSize) + { + const osg::Vec2i cell = center + osg::Vec2i(offset, offset); + return osg::Vec2f(static_cast(cell.x()) * cellSize, static_cast(cell.y()) * cellSize); + } + + TilesPositionsRange makeCellGridRange( + const RecastSettings& settings, ESM::RefId worldspace, const CellGridBounds& bounds) + { + const float floatCellSize = static_cast(ESM::getCellSize(worldspace)); + const osg::Vec2f min = getMinCellGridPosition(bounds.mCenter, -bounds.mHalfSize, floatCellSize); + const osg::Vec2f max = getMinCellGridPosition(bounds.mCenter, bounds.mHalfSize + 1, floatCellSize); + return TilesPositionsRange{ + .mBegin = getTilePosition(settings, toNavMeshCoordinates(settings, min)), + .mEnd = getTilePosition(settings, toNavMeshCoordinates(settings, max)), + }; + } + + TilesPositionsRange makeRange(const Settings& settings, ESM::RefId worldspace, + const std::optional& bounds, int radius, const TilePosition& center) + { + TilesPositionsRange result = makeRange(center, radius); + if (bounds.has_value()) + result = getIntersection(result, makeCellGridRange(settings.mRecast, worldspace, *bounds)); + return result; + } + TilePosition toNavMeshTilePosition(const RecastSettings& settings, const osg::Vec3f& position) { return getTilePosition(settings, toNavMeshCoordinates(settings, position)); @@ -52,13 +85,15 @@ namespace DetourNavigator NavMeshManager::NavMeshManager(const Settings& settings, std::unique_ptr&& db) : mSettings(settings) + , mMaxRadius(getMaxRadius(settings.mMaxTilesNumber)) , mRecastMeshManager(settings.mRecast) , mOffMeshConnectionsManager(settings.mRecast) , mAsyncNavMeshUpdater(settings, mRecastMeshManager, mOffMeshConnectionsManager, std::move(db)) { } - void NavMeshManager::updateBounds(ESM::RefId worldspace, const osg::Vec3f& playerPosition, const UpdateGuard* guard) + void NavMeshManager::updateBounds(ESM::RefId worldspace, const std::optional& cellGridBounds, + const osg::Vec3f& playerPosition, const UpdateGuard* guard) { if (worldspace != mWorldspace) { @@ -69,8 +104,9 @@ namespace DetourNavigator } const TilePosition playerTile = toNavMeshTilePosition(mSettings.mRecast, playerPosition); - const TilesPositionsRange range = makeRange(playerTile, mSettings.mMaxTilesNumber); - mRecastMeshManager.setRange(range, guard); + + mRecastMeshManager.setRange(makeRange(mSettings, worldspace, cellGridBounds, mMaxRadius, playerTile), guard); + mCellGridBounds = cellGridBounds; } bool NavMeshManager::addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, @@ -162,7 +198,7 @@ namespace DetourNavigator return; mLastRecastMeshManagerRevision = mRecastMeshManager.getRevision(); mPlayerTile = playerTile; - mRecastMeshManager.setRange(makeRange(playerTile, mSettings.mMaxTilesNumber), guard); + mRecastMeshManager.setRange(makeRange(mSettings, mWorldspace, mCellGridBounds, mMaxRadius, playerTile), guard); const auto changedTiles = mRecastMeshManager.takeChangedTiles(guard); const TilesPositionsRange range = mRecastMeshManager.getLimitedObjectsRange(); for (const auto& [agentBounds, cached] : mCache) diff --git a/components/detournavigator/navmeshmanager.hpp b/components/detournavigator/navmeshmanager.hpp index 89ced89f02..ed4703f4b1 100644 --- a/components/detournavigator/navmeshmanager.hpp +++ b/components/detournavigator/navmeshmanager.hpp @@ -3,6 +3,7 @@ #include "agentbounds.hpp" #include "asyncnavmeshupdater.hpp" +#include "cellgridbounds.hpp" #include "heightfieldshape.hpp" #include "offmeshconnectionsmanager.hpp" #include "recastmeshtiles.hpp" @@ -24,7 +25,8 @@ namespace DetourNavigator ScopedUpdateGuard makeUpdateGuard() { return mRecastMeshManager.makeUpdateGuard(); } - void updateBounds(ESM::RefId worldspace, const osg::Vec3f& playerPosition, const UpdateGuard* guard); + void updateBounds(ESM::RefId worldspace, const std::optional& cellGridBounds, + const osg::Vec3f& playerPosition, const UpdateGuard* guard); bool addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType, const UpdateGuard* guard); @@ -65,7 +67,9 @@ namespace DetourNavigator private: const Settings& mSettings; + const int mMaxRadius; ESM::RefId mWorldspace; + std::optional mCellGridBounds; TileCachedRecastMeshManager mRecastMeshManager; OffMeshConnectionsManager mOffMeshConnectionsManager; AsyncNavMeshUpdater mAsyncNavMeshUpdater; diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index 5cb95fed64..cf5a914ceb 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -107,11 +107,10 @@ namespace DetourNavigator }); } + const MaybeLockGuard lock(mMutex, guard); + if (changed) - { - const MaybeLockGuard lock(mMutex, guard); ++mRevision; - } mRange = range; } @@ -357,6 +356,8 @@ namespace DetourNavigator const std::lock_guard lock(mMutex); if (mWorldspace != worldspace) return nullptr; + if (!isInTilesPositionsRange(mRange, tilePosition)) + return nullptr; const auto it = mCache.find(tilePosition); if (it != mCache.end() && it->second.mRecastMesh->getVersion() == it->second.mVersion) return it->second.mRecastMesh; @@ -380,6 +381,8 @@ namespace DetourNavigator const std::lock_guard lock(mMutex); if (mWorldspace != worldspace) return nullptr; + if (!isInTilesPositionsRange(mRange, tilePosition)) + return nullptr; const auto it = mCache.find(tilePosition); if (it == mCache.end()) return nullptr;