diff --git a/CHANGELOG.md b/CHANGELOG.md index ba8081dae..867ae0412 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -141,6 +141,7 @@ Feature #5456: Basic collada animation support Feature #5457: Realistic diagonal movement Feature #5486: Fixes trainers to choose their training skills based on their base skill points + Feature #5500: Prepare enough navmesh tiles before scene loading ends Feature #5511: Add in game option to toggle HRTF support in OpenMW Feature #5519: Code Patch tab in launcher Feature #5524: Resume failed script execution after reload diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 0cfca3be2..c2d96788c 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -620,6 +620,8 @@ namespace MWWorld if (changeEvent) mCellChanged = true; + + mNavigator.wait(*loadingListener); } void Scene::testExteriorCells() diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index 8d92b48e0..cb8ae4152 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -66,6 +66,7 @@ namespace mSettings.mRegionMergeSize = 20; mSettings.mRegionMinSize = 8; mSettings.mTileSize = 64; + mSettings.mWaitUntilMinDistanceToPlayer = std::numeric_limits::max(); mSettings.mAsyncNavMeshUpdaterThreads = 1; mSettings.mMaxNavMeshTilesCacheSize = 1024 * 1024; mSettings.mMaxPolygonPathSize = 1024; diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index 124d3c4e0..ff51e39aa 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -21,6 +21,16 @@ namespace { return std::abs(lhs.x() - rhs.x()) + std::abs(lhs.y() - rhs.y()); } + + int getMinDistanceTo(const TilePosition& position, int maxDistance, + const std::map>& tilesPerHalfExtents) + { + int result = maxDistance; + for (const auto& [halfExtents, tiles] : tilesPerHalfExtents) + for (const TilePosition& tile : tiles) + result = std::min(result, getManhattanDistance(position, tile)); + return result; + } } namespace DetourNavigator @@ -114,24 +124,40 @@ namespace DetourNavigator void AsyncNavMeshUpdater::wait(Loading::Listener& listener) { + if (mSettings.get().mWaitUntilMinDistanceToPlayer == 0) + return; listener.setLabel("Building navigation mesh"); const std::size_t initialJobsLeft = getTotalJobs(); std::size_t maxProgress = initialJobsLeft + mThreads.size(); listener.setProgressRange(maxProgress); - waitUntilJobsDone(initialJobsLeft, maxProgress, listener); - mProcessingTiles.wait(mProcessed, [] (const auto& v) { return v.empty(); }); - listener.setProgress(maxProgress); + const int minDistanceToPlayer = waitUntilJobsDone(initialJobsLeft, maxProgress, listener); + if (minDistanceToPlayer < mSettings.get().mWaitUntilMinDistanceToPlayer) + { + mProcessingTiles.wait(mProcessed, [] (const auto& v) { return v.empty(); }); + listener.setProgress(maxProgress); + } } - void AsyncNavMeshUpdater::waitUntilJobsDone(const std::size_t initialJobsLeft, std::size_t& maxProgress, Loading::Listener& listener) + int AsyncNavMeshUpdater::waitUntilJobsDone(const std::size_t initialJobsLeft, std::size_t& maxProgress, Loading::Listener& listener) { std::size_t prevJobsLeft = initialJobsLeft; std::size_t jobsDone = 0; std::size_t jobsLeft = 0; + const int maxDistanceToPlayer = mSettings.get().mWaitUntilMinDistanceToPlayer; + const TilePosition playerPosition = *mPlayerTile.lockConst(); + int minDistanceToPlayer = 0; const auto isDone = [&] { jobsLeft = mJobs.size() + getTotalThreadJobsUnsafe(); - return jobsLeft == 0; + if (jobsLeft == 0) + { + minDistanceToPlayer = 0; + return true; + } + minDistanceToPlayer = getMinDistanceTo(playerPosition, maxDistanceToPlayer, mPushed); + for (const auto& [threadId, queue] : mThreadsQueues) + minDistanceToPlayer = getMinDistanceTo(playerPosition, minDistanceToPlayer, queue.mPushed); + return minDistanceToPlayer >= maxDistanceToPlayer; }; std::unique_lock lock(mMutex); while (!mDone.wait_for(lock, std::chrono::milliseconds(250), isDone)) @@ -150,6 +176,7 @@ namespace DetourNavigator listener.increaseProgress(newJobsDone); } } + return minDistanceToPlayer; } void AsyncNavMeshUpdater::reportStats(unsigned int frameNumber, osg::Stats& stats) const diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index fcf57d683..c28d8f21d 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -142,7 +142,7 @@ namespace DetourNavigator void cleanupLastUpdates(); - void waitUntilJobsDone(const std::size_t initialJobsLeft, std::size_t& maxJobsLeft, Loading::Listener& listener); + int waitUntilJobsDone(const std::size_t initialJobsLeft, std::size_t& maxJobsLeft, Loading::Listener& listener); }; } diff --git a/components/detournavigator/settings.cpp b/components/detournavigator/settings.cpp index 0c4d0f838..ff99fae20 100644 --- a/components/detournavigator/settings.cpp +++ b/components/detournavigator/settings.cpp @@ -29,6 +29,7 @@ namespace DetourNavigator navigatorSettings.mRegionMergeSize = ::Settings::Manager::getInt("region merge size", "Navigator"); navigatorSettings.mRegionMinSize = ::Settings::Manager::getInt("region min size", "Navigator"); navigatorSettings.mTileSize = ::Settings::Manager::getInt("tile size", "Navigator"); + navigatorSettings.mWaitUntilMinDistanceToPlayer = ::Settings::Manager::getInt("wait until min distance to player", "Navigator"); navigatorSettings.mAsyncNavMeshUpdaterThreads = static_cast(::Settings::Manager::getInt("async nav mesh updater threads", "Navigator")); navigatorSettings.mMaxNavMeshTilesCacheSize = static_cast(::Settings::Manager::getInt("max nav mesh tiles cache size", "Navigator")); navigatorSettings.mMaxPolygonPathSize = static_cast(::Settings::Manager::getInt("max polygon path size", "Navigator")); diff --git a/components/detournavigator/settings.hpp b/components/detournavigator/settings.hpp index ece16e35a..39f5815b8 100644 --- a/components/detournavigator/settings.hpp +++ b/components/detournavigator/settings.hpp @@ -31,6 +31,7 @@ namespace DetourNavigator int mRegionMergeSize = 0; int mRegionMinSize = 0; int mTileSize = 0; + int mWaitUntilMinDistanceToPlayer = 0; std::size_t mAsyncNavMeshUpdaterThreads = 0; std::size_t mMaxNavMeshTilesCacheSize = 0; std::size_t mMaxPolygonPathSize = 0; diff --git a/docs/source/reference/modding/settings/navigator.rst b/docs/source/reference/modding/settings/navigator.rst index b9485c3e9..fee4b2626 100644 --- a/docs/source/reference/modding/settings/navigator.rst +++ b/docs/source/reference/modding/settings/navigator.rst @@ -42,6 +42,18 @@ Increasing this value may decrease performance. This condition is always true: ``max tiles number * max polygons per tile <= 4194304``. It's a limitation of `Recastnavigation `_ library. +wait until min distance to player +------------------------------ + +:Type: integer +:Range: >= 0 +:Default: 5 + +Distance in navmesh tiles around the player to keep loading screen until navigation mesh is generated. +Allows to complete cell loading only when minimal navigation mesh area is generated to correctly find path for actors +nearby the player. Increasing this value will keep loading screen longer but will slightly increase nav mesh generation +speed on systems bound by CPU. Zero means no waiting. + Advanced settings ***************** diff --git a/files/settings-default.cfg b/files/settings-default.cfg index ea678c70f..584fe5951 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -908,6 +908,10 @@ max tiles number = 512 # Min time duration for the same tile update in milliseconds (value >= 0) min update interval ms = 250 +# Keep loading screen until navmesh is generated around the player for all tiles within manhattan distance (value >= 0). +# Distance is measured in the number of tiles and can be only an integer value. +wait until min distance to player = 5 + [Shadows] # Enable or disable shadows. Bear in mind that this will force OpenMW to use shaders as if "[Shaders]/force shaders" was set to true.