diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 442fd04502..ce604580d0 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -166,6 +166,7 @@ namespace MWWorld mPhysics.reset(new MWPhysics::PhysicsSystem(resourceSystem, rootNode)); DetourNavigator::Settings navigatorSettings; + navigatorSettings.mBorderSize = Settings::Manager::getInt("border size", "Navigator"); navigatorSettings.mCellHeight = Settings::Manager::getFloat("cell height", "Navigator"); navigatorSettings.mCellSize = Settings::Manager::getFloat("cell size", "Navigator"); navigatorSettings.mDetailSampleDist = Settings::Manager::getFloat("detail sample dist", "Navigator"); diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 57fb1a0128..fc04648e5a 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -21,6 +21,7 @@ if (GTEST_FOUND AND GMOCK_FOUND) detournavigator/navigator.cpp detournavigator/settingsutils.cpp detournavigator/recastmeshbuilder.cpp + detournavigator/gettilespositions.cpp ) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) diff --git a/apps/openmw_test_suite/detournavigator/gettilespositions.cpp b/apps/openmw_test_suite/detournavigator/gettilespositions.cpp new file mode 100644 index 0000000000..1ad5c063d0 --- /dev/null +++ b/apps/openmw_test_suite/detournavigator/gettilespositions.cpp @@ -0,0 +1,104 @@ +#include +#include + +#include +#include + +namespace +{ + using namespace testing; + using namespace DetourNavigator; + + struct CollectTilesPositions + { + std::vector& mTilesPositions; + + void operator ()(const TilePosition& value) + { + mTilesPositions.push_back(value); + } + }; + + struct DetourNavigatorGetTilesPositionsTest : Test + { + Settings mSettings; + std::vector mTilesPositions; + CollectTilesPositions mCollect {mTilesPositions}; + + DetourNavigatorGetTilesPositionsTest() + { + mSettings.mBorderSize = 0; + mSettings.mCellSize = 0.5; + mSettings.mRecastScaleFactor = 1; + mSettings.mTileSize = 64; + } + }; + + TEST_F(DetourNavigatorGetTilesPositionsTest, for_object_in_single_tile_should_return_one_tile) + { + getTilesPositions(osg::Vec3f(2, 2, 0), osg::Vec3f(31, 31, 1), mSettings, mCollect); + + EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0))); + } + + TEST_F(DetourNavigatorGetTilesPositionsTest, for_object_with_x_bounds_in_two_tiles_should_return_two_tiles) + { + getTilesPositions(osg::Vec3f(0, 0, 0), osg::Vec3f(32, 31, 1), mSettings, mCollect); + + EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0), TilePosition(1, 0))); + } + + TEST_F(DetourNavigatorGetTilesPositionsTest, for_object_with_y_bounds_in_two_tiles_should_return_two_tiles) + { + getTilesPositions(osg::Vec3f(0, 0, 0), osg::Vec3f(31, 32, 1), mSettings, mCollect); + + EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0), TilePosition(0, 1))); + } + + TEST_F(DetourNavigatorGetTilesPositionsTest, tiling_works_only_for_x_and_y_coordinates) + { + getTilesPositions(osg::Vec3f(0, 0, 0), osg::Vec3f(31, 31, 32), mSettings, mCollect); + + EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0))); + } + + TEST_F(DetourNavigatorGetTilesPositionsTest, tiling_should_work_with_negative_coordinates) + { + getTilesPositions(osg::Vec3f(-31, -31, 0), osg::Vec3f(31, 31, 1), mSettings, mCollect); + + EXPECT_THAT(mTilesPositions, ElementsAre( + TilePosition(-1, -1), + TilePosition(-1, 0), + TilePosition(0, -1), + TilePosition(0, 0) + )); + } + + TEST_F(DetourNavigatorGetTilesPositionsTest, border_size_should_extend_tile_bounds) + { + mSettings.mBorderSize = 1; + + getTilesPositions(osg::Vec3f(0, 0, 0), osg::Vec3f(31.5, 31.5, 1), mSettings, mCollect); + + EXPECT_THAT(mTilesPositions, ElementsAre( + TilePosition(-1, -1), + TilePosition(-1, 0), + TilePosition(-1, 1), + TilePosition(0, -1), + TilePosition(0, 0), + TilePosition(0, 1), + TilePosition(1, -1), + TilePosition(1, 0), + TilePosition(1, 1) + )); + } + + TEST_F(DetourNavigatorGetTilesPositionsTest, should_apply_recast_scale_factor) + { + mSettings.mRecastScaleFactor = 0.5; + + getTilesPositions(osg::Vec3f(0, 0, 0), osg::Vec3f(32, 32, 1), mSettings, mCollect); + + EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0))); + } +} diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index 2bfab13a40..0e475ad9fc 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -37,6 +37,7 @@ namespace mSettings.mEnableWriteNavMeshToFile = false; mSettings.mEnableRecastMeshFileNameRevision = false; mSettings.mEnableNavMeshFileNameRevision = false; + mSettings.mBorderSize = 16; mSettings.mCellHeight = 0.2f; mSettings.mCellSize = 0.2f; mSettings.mDetailSampleDist = 6; diff --git a/components/detournavigator/gettilespositions.hpp b/components/detournavigator/gettilespositions.hpp new file mode 100644 index 0000000000..96ab89fd73 --- /dev/null +++ b/components/detournavigator/gettilespositions.hpp @@ -0,0 +1,55 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_GETTILESPOSITIONS_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_GETTILESPOSITIONS_H + +#include "settings.hpp" +#include "settingsutils.hpp" + +#include + +#include + +namespace DetourNavigator +{ + inline osg::Vec3f makeOsgVec3f(const btVector3& value) + { + return osg::Vec3f(value.x(), value.y(), value.z()); + } + + template + void getTilesPositions(const osg::Vec3f& aabbMin, const osg::Vec3f& aabbMax, + const Settings& settings, Callback&& callback) + { + auto min = toNavMeshCoordinates(settings, aabbMin); + auto max = toNavMeshCoordinates(settings, aabbMax); + + const auto border = getBorderSize(settings); + min -= osg::Vec3f(border, border, border); + max += osg::Vec3f(border, border, border); + + auto minTile = getTilePosition(settings, min); + auto maxTile = getTilePosition(settings, max); + + if (minTile.x() > maxTile.x()) + std::swap(minTile.x(), maxTile.x()); + + if (minTile.y() > maxTile.y()) + std::swap(minTile.y(), maxTile.y()); + + for (int tileX = minTile.x(); tileX <= maxTile.x(); ++tileX) + for (int tileY = minTile.y(); tileY <= maxTile.y(); ++tileY) + callback(TilePosition {tileX, tileY}); + } + + template + void getTilesPositions(const btCollisionShape& shape, const btTransform& transform, + const Settings& settings, Callback&& callback) + { + btVector3 aabbMin; + btVector3 aabbMax; + shape.getAabb(transform, aabbMin, aabbMax); + + getTilesPositions(makeOsgVec3f(aabbMin), makeOsgVec3f(aabbMax), settings, std::forward(callback)); + } +} + +#endif diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index 9be9ca5c30..2d5d4c2429 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -82,15 +82,15 @@ namespace config.maxVertsPerPoly = settings.mMaxVertsPerPoly; config.detailSampleDist = settings.mDetailSampleDist < 0.9f ? 0 : config.cs * settings.mDetailSampleDist; config.detailSampleMaxError = config.ch * settings.mDetailSampleMaxError; - config.borderSize = config.walkableRadius + 3; + config.borderSize = settings.mBorderSize; config.width = settings.mTileSize + config.borderSize * 2; config.height = settings.mTileSize + config.borderSize * 2; rcVcopy(config.bmin, boundsMin.ptr()); rcVcopy(config.bmax, boundsMax.ptr()); - config.bmin[0] -= config.borderSize * config.cs; - config.bmin[2] -= config.borderSize * config.cs; - config.bmax[0] += config.borderSize * config.cs; - config.bmax[2] += config.borderSize * config.cs; + config.bmin[0] -= getBorderSize(settings); + config.bmin[2] -= getBorderSize(settings); + config.bmax[0] += getBorderSize(settings); + config.bmax[2] += getBorderSize(settings); rcHeightfield solid; OPENMW_CHECK_DT_RESULT(rcCreateHeightfield(nullptr, solid, config.width, config.height, diff --git a/components/detournavigator/makenavmesh.hpp b/components/detournavigator/makenavmesh.hpp index 0a47dd66dd..60ad2acf7d 100644 --- a/components/detournavigator/makenavmesh.hpp +++ b/components/detournavigator/makenavmesh.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_MAKENAVMESH_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_MAKENAVMESH_H +#include "settings.hpp" #include "tileposition.hpp" #include diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index 741d6666e3..eeaae309d3 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -1,6 +1,7 @@ #include "navmeshmanager.hpp" #include "debug.hpp" #include "exceptions.hpp" +#include "gettilespositions.hpp" #include "makenavmesh.hpp" #include "settings.hpp" #include "sharednavmesh.hpp" @@ -63,12 +64,10 @@ namespace DetourNavigator const auto changedTiles = mChangedTiles.find(agentHalfExtents); if (changedTiles != mChangedTiles.end()) { - TilePosition playerTile; playerPosition *= mSettings.mRecastScaleFactor; std::swap(playerPosition.y(), playerPosition.z()); - cached->mValue.raw()->calcTileLoc(playerPosition.ptr(), &playerTile.x(), &playerTile.y()); - mAsyncNavMeshUpdater.post(agentHalfExtents, mRecastMeshManager.getMesh(), cached, playerTile, - changedTiles->second); + mAsyncNavMeshUpdater.post(agentHalfExtents, mRecastMeshManager.getMesh(), cached, + getTilePosition(mSettings, playerPosition), changedTiles->second); mChangedTiles.erase(changedTiles); log("cache update posted for agent=", agentHalfExtents); } @@ -91,39 +90,11 @@ namespace DetourNavigator void NavMeshManager::addChangedTiles(const btCollisionShape& shape, const btTransform& transform) { - btVector3 aabbMin; - btVector3 aabbMax; - shape.getAabb(transform, aabbMin, aabbMax); - osg::Vec3f min(aabbMin.x(), aabbMin.z(), aabbMin.y()); - osg::Vec3f max(aabbMax.x(), aabbMax.z(), aabbMax.y()); - min *= mSettings.mRecastScaleFactor; - max *= mSettings.mRecastScaleFactor; - - for (auto& v : mCache) - { - if (const auto& item = v.second) - { - auto& changedTiles = mChangedTiles[v.first]; - - int minTileX; - int minTileY; - item->mValue.raw()->calcTileLoc(min.ptr(), &minTileX, &minTileY); - - int maxTileX; - int maxTileY; - item->mValue.raw()->calcTileLoc(max.ptr(), &maxTileX, &maxTileY); - - if (minTileX > maxTileX) - std::swap(minTileX, maxTileX); - - if (minTileY > maxTileY) - std::swap(minTileY, maxTileY); - - for (int tileX = minTileX; tileX <= maxTileX; ++tileX) - for (int tileY = minTileY; tileY <= maxTileY; ++tileY) - changedTiles.insert(TilePosition {tileX, tileY}); - } - } + getTilesPositions(shape, transform, mSettings, [&] (const TilePosition& v) { + for (const auto& cached : mCache) + if (cached.second) + mChangedTiles[cached.first].insert(v); + }); } const std::shared_ptr& NavMeshManager::getCached(const osg::Vec3f& agentHalfExtents) const diff --git a/components/detournavigator/recastmeshmanager.cpp b/components/detournavigator/recastmeshmanager.cpp index 1c32ac1e77..ccfa253724 100644 --- a/components/detournavigator/recastmeshmanager.cpp +++ b/components/detournavigator/recastmeshmanager.cpp @@ -7,7 +7,8 @@ namespace DetourNavigator RecastMeshManager::RecastMeshManager(const Settings& settings) : mShouldRebuild(false) , mMeshBuilder(settings) - {} + { + } bool RecastMeshManager::addObject(std::size_t id, const btCollisionShape& shape, const btTransform& transform) { @@ -40,12 +41,7 @@ namespace DetourNavigator return; mMeshBuilder.reset(); for (const auto& v : mObjects) - { - if (v.second.mShape->getShapeType() == TERRAIN_SHAPE_PROXYTYPE) - mMeshBuilder.addObject(*static_cast(v.second.mShape), v.second.mTransform); - else if (v.second.mShape->isConcave()) - mMeshBuilder.addObject(*static_cast(v.second.mShape), v.second.mTransform); - } + mMeshBuilder.addObject(*v.second.mShape, v.second.mTransform); mShouldRebuild = false; } } diff --git a/components/detournavigator/settings.hpp b/components/detournavigator/settings.hpp index 6983eb4314..0d34ae7e95 100644 --- a/components/detournavigator/settings.hpp +++ b/components/detournavigator/settings.hpp @@ -19,6 +19,7 @@ namespace DetourNavigator float mMaxSimplificationError; float mMaxSlope; float mRecastScaleFactor; + int mBorderSize; int mMaxEdgeLen; int mMaxNavMeshQueryNodes; int mMaxVertsPerPoly; diff --git a/components/detournavigator/settingsutils.hpp b/components/detournavigator/settingsutils.hpp index d14863dca8..d0d51e1670 100644 --- a/components/detournavigator/settingsutils.hpp +++ b/components/detournavigator/settingsutils.hpp @@ -60,6 +60,11 @@ namespace DetourNavigator osg::Vec2f(tilePosition.x() + 1, tilePosition.y() + 1) * getTileSize(settings), }; } + + inline float getBorderSize(const Settings& settings) + { + return settings.mBorderSize * settings.mCellSize; + } } #endif diff --git a/components/detournavigator/sharednavmesh.hpp b/components/detournavigator/sharednavmesh.hpp index 101cc58666..657973c2f6 100644 --- a/components/detournavigator/sharednavmesh.hpp +++ b/components/detournavigator/sharednavmesh.hpp @@ -44,11 +44,6 @@ namespace DetourNavigator return LockedSharedNavMesh(*mMutex, mValue); } - NavMeshPtr raw() const - { - return mValue; - } - private: std::shared_ptr mMutex; NavMeshPtr mValue; diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 8a78e745c4..a1ea14678e 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -567,6 +567,9 @@ max simplification error = 1.3 # The width and height of each tile. (value > 0) tile size = 64 +# The size of the non-navigable border around the heightfield. (value >= 0) +border size = 16 + # The maximum allowed length for contour edges along the border of the mesh. (value >= 0) max edge len = 12 @@ -593,9 +596,21 @@ triangles per chunk = 256 # Enable debug log (true, false) enable log = false + +# Write recast mesh to file in .obj format for each use to update nav mesh (true, false) enable write recast mesh to file = false + +# Write NavMesh to file to be able to open by RecastDemo (true, false) enable write nav mesh to file = false + +# Write each recast mesh file with revision in name. Otherwise will rewrite same file. (true, false) enable recast mesh file name revision = false + +# Write each nav mesh file with revision in name. Otherwise will rewrite same file. (true, false) enable nav mesh file name revision = false + +# Write recast mesh file at path with this prefix recast mesh path prefix = + +# Write nav mesh file at path with this prefix nav mesh path prefix =