1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-12-13 18:13:07 +00:00

Read navmesh tile data from database

When tile is not found in memory cache try to find it in the database.
This commit is contained in:
elsid 2021-07-09 22:51:42 +02:00
parent 953a4c5550
commit c9b8ba7b46
No known key found for this signature in database
GPG key ID: B845CB9FEE18AB40
32 changed files with 424 additions and 157 deletions

View file

@ -264,6 +264,7 @@ namespace NavMeshTool
{ {
it = navMeshInputs.emplace(cell.mCellId.mWorldspace, it = navMeshInputs.emplace(cell.mCellId.mWorldspace,
std::make_unique<WorldspaceNavMeshInput>(cell.mCellId.mWorldspace, settings.mRecast)).first; std::make_unique<WorldspaceNavMeshInput>(cell.mCellId.mWorldspace, settings.mRecast)).first;
it->second->mTileCachedRecastMeshManager.setWorldspace(cell.mCellId.mWorldspace);
} }
return *it->second; return *it->second;
} (); } ();

View file

@ -788,7 +788,7 @@ namespace MWMechanics
MWBase::World* world = MWBase::Environment::get().getWorld(); MWBase::World* world = MWBase::Environment::get().getWorld();
world->getNavigator()->setUpdatesEnabled(mAI); world->getNavigator()->setUpdatesEnabled(mAI);
if (mAI) if (mAI)
world->getNavigator()->update(world->getPlayerPtr().getRefData().getPosition().asVec3()); world->getNavigator()->update(world->getPlayerPtr().getRefData().getPosition().asVec3());
return mAI; return mAI;
} }

View file

@ -350,8 +350,7 @@ namespace MWWorld
if (const auto pathgrid = world->getStore().get<ESM::Pathgrid>().search(*cell->getCell())) if (const auto pathgrid = world->getStore().get<ESM::Pathgrid>().search(*cell->getCell()))
mNavigator.removePathgrid(*pathgrid); mNavigator.removePathgrid(*pathgrid);
const auto player = world->getPlayerPtr(); mNavigator.update(world->getPlayerPtr().getRefData().getPosition().asVec3());
mNavigator.update(player.getRefData().getPosition().asVec3());
MWBase::Environment::get().getMechanicsManager()->drop (cell); MWBase::Environment::get().getMechanicsManager()->drop (cell);
@ -378,6 +377,8 @@ namespace MWWorld
const int cellX = cell->getCell()->getGridX(); const int cellX = cell->getCell()->getGridX();
const int cellY = cell->getCell()->getGridY(); const int cellY = cell->getCell()->getGridY();
mNavigator.setWorldspace(cell->getCell()->mCellId.mWorldspace);
if (cell->getCell()->isExterior()) if (cell->getCell()->isExterior())
{ {
osg::ref_ptr<const ESMTerrain::LandObject> land = mRendering.getLandManager()->getLand(cellX, cellY); osg::ref_ptr<const ESMTerrain::LandObject> land = mRendering.getLandManager()->getLand(cellX, cellY);
@ -507,10 +508,13 @@ namespace MWWorld
void Scene::playerMoved(const osg::Vec3f &pos) void Scene::playerMoved(const osg::Vec3f &pos)
{ {
if (mCurrentCell == nullptr)
return;
const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr();
mNavigator.updatePlayerPosition(player.getRefData().getPosition().asVec3()); mNavigator.updatePlayerPosition(player.getRefData().getPosition().asVec3());
if (!mCurrentCell || !mCurrentCell->isExterior()) if (!mCurrentCell->isExterior())
return; return;
osg::Vec2i newCell = getNewGridCenter(pos, &mCurrentGridCenter); osg::Vec2i newCell = getNewGridCenter(pos, &mCurrentGridCenter);
@ -886,8 +890,11 @@ namespace MWWorld
addObject(ptr, *mPhysics, mRendering, mPagedRefs); addObject(ptr, *mPhysics, mRendering, mPagedRefs);
addObject(ptr, *mPhysics, mNavigator); addObject(ptr, *mPhysics, mNavigator);
MWBase::Environment::get().getWorld()->scaleObject(ptr, ptr.getCellRef().getScale()); MWBase::Environment::get().getWorld()->scaleObject(ptr, ptr.getCellRef().getScale());
const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); if (mCurrentCell != nullptr)
mNavigator.update(player.getRefData().getPosition().asVec3()); {
const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr();
mNavigator.update(player.getRefData().getPosition().asVec3());
}
} }
catch (std::exception& e) catch (std::exception& e)
{ {
@ -903,8 +910,11 @@ namespace MWWorld
if (const auto object = mPhysics->getObject(ptr)) if (const auto object = mPhysics->getObject(ptr))
{ {
mNavigator.removeObject(DetourNavigator::ObjectId(object)); mNavigator.removeObject(DetourNavigator::ObjectId(object));
const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); if (mCurrentCell != nullptr)
mNavigator.update(player.getRefData().getPosition().asVec3()); {
const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr();
mNavigator.update(player.getRefData().getPosition().asVec3());
}
} }
else if (mPhysics->getActor(ptr)) else if (mPhysics->getActor(ptr))
{ {

View file

@ -187,7 +187,7 @@ namespace MWWorld
{ {
auto navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager(); auto navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager();
navigatorSettings.mRecast.mSwimHeightScale = mSwimHeightScale; navigatorSettings.mRecast.mSwimHeightScale = mSwimHeightScale;
mNavigator = DetourNavigator::makeNavigator(navigatorSettings); mNavigator = DetourNavigator::makeNavigator(navigatorSettings, userDataPath);
} }
else else
{ {
@ -1524,9 +1524,10 @@ namespace MWWorld
if (const auto object = mPhysics->getObject(door.first)) if (const auto object = mPhysics->getObject(door.first))
updateNavigatorObject(*object); updateNavigatorObject(*object);
if (mShouldUpdateNavigator) auto player = getPlayerPtr();
if (mShouldUpdateNavigator && player.getCell() != nullptr)
{ {
mNavigator->update(getPlayerPtr().getRefData().getPosition().asVec3()); mNavigator->update(player.getRefData().getPosition().asVec3());
mShouldUpdateNavigator = false; mShouldUpdateNavigator = false;
} }
} }

View file

@ -3,6 +3,7 @@
#include <components/detournavigator/navigatorimpl.hpp> #include <components/detournavigator/navigatorimpl.hpp>
#include <components/detournavigator/exceptions.hpp> #include <components/detournavigator/exceptions.hpp>
#include <components/detournavigator/navigatorutils.hpp> #include <components/detournavigator/navigatorutils.hpp>
#include <components/detournavigator/navmeshdb.hpp>
#include <components/misc/rng.hpp> #include <components/misc/rng.hpp>
#include <components/loadinglistener/loadinglistener.hpp> #include <components/loadinglistener/loadinglistener.hpp>
#include <components/esm/loadland.hpp> #include <components/esm/loadland.hpp>
@ -37,6 +38,7 @@ namespace
Settings mSettings; Settings mSettings;
std::unique_ptr<Navigator> mNavigator; std::unique_ptr<Navigator> mNavigator;
const osg::Vec3f mPlayerPosition; const osg::Vec3f mPlayerPosition;
const std::string mWorldspace;
const osg::Vec3f mAgentHalfExtents; const osg::Vec3f mAgentHalfExtents;
osg::Vec3f mStart; osg::Vec3f mStart;
osg::Vec3f mEnd; osg::Vec3f mEnd;
@ -53,6 +55,7 @@ namespace
DetourNavigatorNavigatorTest() DetourNavigatorNavigatorTest()
: mPlayerPosition(256, 256, 0) : mPlayerPosition(256, 256, 0)
, mWorldspace("sys::default")
, mAgentHalfExtents(29, 29, 66) , mAgentHalfExtents(29, 29, 66)
, mStart(52, 460, 1) , mStart(52, 460, 1)
, mEnd(460, 52, 1) , mEnd(460, 52, 1)
@ -87,7 +90,7 @@ namespace
mSettings.mDetour.mMaxPolys = 4096; mSettings.mDetour.mMaxPolys = 4096;
mSettings.mMaxTilesNumber = 512; mSettings.mMaxTilesNumber = 512;
mSettings.mMinUpdateInterval = std::chrono::milliseconds(50); mSettings.mMinUpdateInterval = std::chrono::milliseconds(50);
mNavigator.reset(new NavigatorImpl(mSettings)); mNavigator.reset(new NavigatorImpl(mSettings, std::make_unique<NavMeshDb>(":memory:")));
} }
}; };
@ -854,7 +857,7 @@ namespace
TEST_F(DetourNavigatorNavigatorTest, multiple_threads_should_lock_tiles) TEST_F(DetourNavigatorNavigatorTest, multiple_threads_should_lock_tiles)
{ {
mSettings.mAsyncNavMeshUpdaterThreads = 2; mSettings.mAsyncNavMeshUpdaterThreads = 2;
mNavigator.reset(new NavigatorImpl(mSettings)); mNavigator.reset(new NavigatorImpl(mSettings, std::make_unique<NavMeshDb>(":memory:")));
const std::array<float, 5 * 5> heightfieldData {{ const std::array<float, 5 * 5> heightfieldData {{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

View file

@ -24,14 +24,6 @@ namespace
using namespace DetourNavigator; using namespace DetourNavigator;
using namespace DetourNavigator::Tests; using namespace DetourNavigator::Tests;
void* permRecastAlloc(int size)
{
void* result = rcAlloc(static_cast<std::size_t>(size), RC_ALLOC_PERM);
if (result == nullptr)
throw std::bad_alloc();
return result;
}
template <class T, class Random> template <class T, class Random>
void generateRecastArray(T*& values, int size, Random& random) void generateRecastArray(T*& values, int size, Random& random)
{ {

View file

@ -38,7 +38,7 @@ namespace
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_empty_should_return_nullptr) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_empty_should_return_nullptr)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
EXPECT_EQ(manager.getMesh(TilePosition(0, 0)), nullptr); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(0, 0)), nullptr);
} }
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_revision_for_empty_should_return_zero) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_revision_for_empty_should_return_zero)
@ -75,12 +75,13 @@ namespace
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_should_add_tiles) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_should_add_tiles)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
manager.setWorldspace("worldspace");
const btBoxShape boxShape(btVector3(20, 20, 100)); const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform); const CollisionShape shape(mInstance, boxShape, mObjectTransform);
ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground));
for (int x = -1; x < 1; ++x) for (int x = -1; x < 1; ++x)
for (int y = -1; y < 1; ++y) for (int y = -1; y < 1; ++y)
ASSERT_NE(manager.getMesh(TilePosition(x, y)), nullptr); ASSERT_NE(manager.getMesh("worldspace", TilePosition(x, y)), nullptr);
} }
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, update_object_for_changed_object_should_return_changed_tiles) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, update_object_for_changed_object_should_return_changed_tiles)
@ -113,90 +114,98 @@ namespace
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_after_add_object_should_return_recast_mesh_for_each_used_tile) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_after_add_object_should_return_recast_mesh_for_each_used_tile)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
manager.setWorldspace("worldspace");
const btBoxShape boxShape(btVector3(20, 20, 100)); const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform); const CollisionShape shape(mInstance, boxShape, mObjectTransform);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
EXPECT_NE(manager.getMesh(TilePosition(-1, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr);
EXPECT_NE(manager.getMesh(TilePosition(-1, 0)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr);
EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr);
EXPECT_NE(manager.getMesh(TilePosition(0, 0)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, 0)), nullptr);
} }
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_after_add_object_should_return_nullptr_for_unused_tile) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_after_add_object_should_return_nullptr_for_unused_tile)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
manager.setWorldspace("worldspace");
const btBoxShape boxShape(btVector3(20, 20, 100)); const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform); const CollisionShape shape(mInstance, boxShape, mObjectTransform);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
EXPECT_EQ(manager.getMesh(TilePosition(1, 0)), nullptr); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(1, 0)), nullptr);
} }
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_moved_object_should_return_recast_mesh_for_each_used_tile) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_moved_object_should_return_recast_mesh_for_each_used_tile)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
manager.setWorldspace("worldspace");
const btBoxShape boxShape(btVector3(20, 20, 100)); const btBoxShape boxShape(btVector3(20, 20, 100));
const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0));
const CollisionShape shape(mInstance, boxShape, mObjectTransform); const CollisionShape shape(mInstance, boxShape, mObjectTransform);
manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground); manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground);
EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr);
EXPECT_NE(manager.getMesh(TilePosition(0, 0)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, 0)), nullptr);
EXPECT_NE(manager.getMesh(TilePosition(1, 0)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(1, 0)), nullptr);
EXPECT_NE(manager.getMesh(TilePosition(1, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(1, -1)), nullptr);
manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {});
EXPECT_NE(manager.getMesh(TilePosition(-1, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr);
EXPECT_NE(manager.getMesh(TilePosition(-1, 0)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr);
EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr);
EXPECT_NE(manager.getMesh(TilePosition(0, 0)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, 0)), nullptr);
} }
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_moved_object_should_return_nullptr_for_unused_tile) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_moved_object_should_return_nullptr_for_unused_tile)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
manager.setWorldspace("worldspace");
const btBoxShape boxShape(btVector3(20, 20, 100)); const btBoxShape boxShape(btVector3(20, 20, 100));
const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0));
const CollisionShape shape(mInstance, boxShape, mObjectTransform); const CollisionShape shape(mInstance, boxShape, mObjectTransform);
manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground); manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground);
EXPECT_EQ(manager.getMesh(TilePosition(-1, -1)), nullptr); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr);
EXPECT_EQ(manager.getMesh(TilePosition(-1, 0)), nullptr); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr);
manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {});
EXPECT_EQ(manager.getMesh(TilePosition(1, 0)), nullptr); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(1, 0)), nullptr);
EXPECT_EQ(manager.getMesh(TilePosition(1, -1)), nullptr); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(1, -1)), nullptr);
} }
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_removed_object_should_return_nullptr_for_all_previously_used_tiles) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_removed_object_should_return_nullptr_for_all_previously_used_tiles)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
manager.setWorldspace("worldspace");
const btBoxShape boxShape(btVector3(20, 20, 100)); const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform); const CollisionShape shape(mInstance, boxShape, mObjectTransform);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
manager.removeObject(ObjectId(&boxShape)); manager.removeObject(ObjectId(&boxShape));
EXPECT_EQ(manager.getMesh(TilePosition(-1, -1)), nullptr); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr);
EXPECT_EQ(manager.getMesh(TilePosition(-1, 0)), nullptr); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr);
EXPECT_EQ(manager.getMesh(TilePosition(0, -1)), nullptr); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr);
EXPECT_EQ(manager.getMesh(TilePosition(0, 0)), nullptr); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(0, 0)), nullptr);
} }
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_not_changed_object_after_update_should_return_recast_mesh_for_same_tiles) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_not_changed_object_after_update_should_return_recast_mesh_for_same_tiles)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
manager.setWorldspace("worldspace");
const btBoxShape boxShape(btVector3(20, 20, 100)); const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform); const CollisionShape shape(mInstance, boxShape, mObjectTransform);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
EXPECT_NE(manager.getMesh(TilePosition(-1, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr);
EXPECT_NE(manager.getMesh(TilePosition(-1, 0)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr);
EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr);
EXPECT_NE(manager.getMesh(TilePosition(0, 0)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, 0)), nullptr);
manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {});
EXPECT_NE(manager.getMesh(TilePosition(-1, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr);
EXPECT_NE(manager.getMesh(TilePosition(-1, 0)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr);
EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr);
EXPECT_NE(manager.getMesh(TilePosition(0, 0)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, 0)), nullptr);
} }
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_revision_after_add_object_new_should_return_incremented_value) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_revision_after_add_object_new_should_return_incremented_value)
@ -235,6 +244,7 @@ namespace
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_revision_after_update_not_changed_object_should_return_same_value) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_revision_after_update_not_changed_object_should_return_same_value)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
manager.setWorldspace("worldspace");
const btBoxShape boxShape(btVector3(20, 20, 100)); const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform); const CollisionShape shape(mInstance, boxShape, mObjectTransform);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
@ -273,17 +283,19 @@ namespace
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_water_for_not_max_int_should_add_new_tiles) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_water_for_not_max_int_should_add_new_tiles)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
manager.setWorldspace("worldspace");
const osg::Vec2i cellPosition(0, 0); const osg::Vec2i cellPosition(0, 0);
const int cellSize = 8192; const int cellSize = 8192;
ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f)); ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f));
for (int x = -1; x < 12; ++x) for (int x = -1; x < 12; ++x)
for (int y = -1; y < 12; ++y) for (int y = -1; y < 12; ++y)
ASSERT_NE(manager.getMesh(TilePosition(x, y)), nullptr); ASSERT_NE(manager.getMesh("worldspace", TilePosition(x, y)), nullptr);
} }
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_water_for_max_int_should_not_add_new_tiles) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_water_for_max_int_should_not_add_new_tiles)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
manager.setWorldspace("worldspace");
const btBoxShape boxShape(btVector3(20, 20, 100)); const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform); const CollisionShape shape(mInstance, boxShape, mObjectTransform);
ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground));
@ -292,7 +304,7 @@ namespace
ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f)); ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f));
for (int x = -6; x < 6; ++x) for (int x = -6; x < 6; ++x)
for (int y = -6; y < 6; ++y) for (int y = -6; y < 6; ++y)
ASSERT_EQ(manager.getMesh(TilePosition(x, y)) != nullptr, -1 <= x && x <= 0 && -1 <= y && y <= 0); ASSERT_EQ(manager.getMesh("worldspace", TilePosition(x, y)) != nullptr, -1 <= x && x <= 0 && -1 <= y && y <= 0);
} }
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_absent_cell_should_return_nullopt) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_absent_cell_should_return_nullopt)
@ -315,18 +327,20 @@ namespace
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_existing_cell_should_remove_empty_tiles) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_existing_cell_should_remove_empty_tiles)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
manager.setWorldspace("worldspace");
const osg::Vec2i cellPosition(0, 0); const osg::Vec2i cellPosition(0, 0);
const int cellSize = 8192; const int cellSize = 8192;
ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f)); ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f));
ASSERT_TRUE(manager.removeWater(cellPosition)); ASSERT_TRUE(manager.removeWater(cellPosition));
for (int x = -6; x < 6; ++x) for (int x = -6; x < 6; ++x)
for (int y = -6; y < 6; ++y) for (int y = -6; y < 6; ++y)
ASSERT_EQ(manager.getMesh(TilePosition(x, y)), nullptr); ASSERT_EQ(manager.getMesh("worldspace", TilePosition(x, y)), nullptr);
} }
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_existing_cell_should_leave_not_empty_tiles) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_existing_cell_should_leave_not_empty_tiles)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
manager.setWorldspace("worldspace");
const btBoxShape boxShape(btVector3(20, 20, 100)); const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform); const CollisionShape shape(mInstance, boxShape, mObjectTransform);
ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground));
@ -336,12 +350,13 @@ namespace
ASSERT_TRUE(manager.removeWater(cellPosition)); ASSERT_TRUE(manager.removeWater(cellPosition));
for (int x = -6; x < 6; ++x) for (int x = -6; x < 6; ++x)
for (int y = -6; y < 6; ++y) for (int y = -6; y < 6; ++y)
ASSERT_EQ(manager.getMesh(TilePosition(x, y)) != nullptr, -1 <= x && x <= 0 && -1 <= y && y <= 0); ASSERT_EQ(manager.getMesh("worldspace", TilePosition(x, y)) != nullptr, -1 <= x && x <= 0 && -1 <= y && y <= 0);
} }
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_object_should_not_remove_tile_with_water) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_object_should_not_remove_tile_with_water)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
manager.setWorldspace("worldspace");
const osg::Vec2i cellPosition(0, 0); const osg::Vec2i cellPosition(0, 0);
const int cellSize = 8192; const int cellSize = 8192;
const btBoxShape boxShape(btVector3(20, 20, 100)); const btBoxShape boxShape(btVector3(20, 20, 100));
@ -351,6 +366,19 @@ namespace
ASSERT_TRUE(manager.removeObject(ObjectId(&boxShape))); ASSERT_TRUE(manager.removeObject(ObjectId(&boxShape)));
for (int x = -1; x < 12; ++x) for (int x = -1; x < 12; ++x)
for (int y = -1; y < 12; ++y) for (int y = -1; y < 12; ++y)
ASSERT_NE(manager.getMesh(TilePosition(x, y)), nullptr); ASSERT_NE(manager.getMesh("worldspace", TilePosition(x, y)), nullptr);
}
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, set_new_worldspace_should_remove_tiles)
{
TileCachedRecastMeshManager manager(mSettings);
manager.setWorldspace("worldspace");
const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(nullptr, boxShape, mObjectTransform);
ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground));
manager.setWorldspace("other");
for (int x = -1; x < 1; ++x)
for (int y = -1; y < 1; ++y)
ASSERT_EQ(manager.getMesh("other", TilePosition(x, y)), nullptr);
} }
} }

View file

@ -207,6 +207,7 @@ add_component_dir(detournavigator
navmeshdb navmeshdb
serialization serialization
navmeshdbutils navmeshdbutils
recast
) )
add_component_dir(loadinglistener add_component_dir(loadinglistener

View file

@ -76,10 +76,11 @@ namespace
namespace DetourNavigator namespace DetourNavigator
{ {
Job::Job(const osg::Vec3f& agentHalfExtents, std::weak_ptr<GuardedNavMeshCacheItem> navMeshCacheItem, Job::Job(const osg::Vec3f& agentHalfExtents, std::weak_ptr<GuardedNavMeshCacheItem> navMeshCacheItem,
const TilePosition& changedTile, ChangeType changeType, int distanceToPlayer, std::string_view worldspace, const TilePosition& changedTile, ChangeType changeType, int distanceToPlayer,
std::chrono::steady_clock::time_point processTime) std::chrono::steady_clock::time_point processTime)
: mAgentHalfExtents(agentHalfExtents) : mAgentHalfExtents(agentHalfExtents)
, mNavMeshCacheItem(std::move(navMeshCacheItem)) , mNavMeshCacheItem(std::move(navMeshCacheItem))
, mWorldspace(worldspace)
, mChangedTile(changedTile) , mChangedTile(changedTile)
, mProcessTime(processTime) , mProcessTime(processTime)
, mChangeType(changeType) , mChangeType(changeType)
@ -89,10 +90,11 @@ namespace DetourNavigator
} }
AsyncNavMeshUpdater::AsyncNavMeshUpdater(const Settings& settings, TileCachedRecastMeshManager& recastMeshManager, AsyncNavMeshUpdater::AsyncNavMeshUpdater(const Settings& settings, TileCachedRecastMeshManager& recastMeshManager,
OffMeshConnectionsManager& offMeshConnectionsManager) OffMeshConnectionsManager& offMeshConnectionsManager, std::unique_ptr<NavMeshDb>&& db)
: mSettings(settings) : mSettings(settings)
, mRecastMeshManager(recastMeshManager) , mRecastMeshManager(recastMeshManager)
, mOffMeshConnectionsManager(offMeshConnectionsManager) , mOffMeshConnectionsManager(offMeshConnectionsManager)
, mDb(std::move(db))
, mShouldStop() , mShouldStop()
, mNavMeshTilesCache(settings.mMaxNavMeshTilesCacheSize) , mNavMeshTilesCache(settings.mMaxNavMeshTilesCacheSize)
{ {
@ -111,8 +113,8 @@ namespace DetourNavigator
thread.join(); thread.join();
} }
void AsyncNavMeshUpdater::post(const osg::Vec3f& agentHalfExtents, void AsyncNavMeshUpdater::post(const osg::Vec3f& agentHalfExtents, const SharedNavMeshCacheItem& navMeshCacheItem,
const SharedNavMeshCacheItem& navMeshCacheItem, const TilePosition& playerTile, const TilePosition& playerTile, std::string_view worldspace,
const std::map<TilePosition, ChangeType>& changedTiles) const std::map<TilePosition, ChangeType>& changedTiles)
{ {
bool playerTileChanged = false; bool playerTileChanged = false;
@ -147,8 +149,8 @@ namespace DetourNavigator
? mLastUpdates[std::tie(agentHalfExtents, changedTile)] + mSettings.get().mMinUpdateInterval ? mLastUpdates[std::tie(agentHalfExtents, changedTile)] + mSettings.get().mMinUpdateInterval
: std::chrono::steady_clock::time_point(); : std::chrono::steady_clock::time_point();
const JobIt it = mJobs.emplace(mJobs.end(), agentHalfExtents, navMeshCacheItem, changedTile, const JobIt it = mJobs.emplace(mJobs.end(), agentHalfExtents, navMeshCacheItem, worldspace,
changeType, getManhattanDistance(changedTile, playerTile), processTime); changedTile, changeType, getManhattanDistance(changedTile, playerTile), processTime);
if (playerTileChanged) if (playerTileChanged)
mWaiting.push_back(it); mWaiting.push_back(it);
@ -302,12 +304,13 @@ namespace DetourNavigator
if (!navMeshCacheItem) if (!navMeshCacheItem)
return true; return true;
const auto recastMesh = mRecastMeshManager.get().getMesh(job.mChangedTile); const auto recastMesh = mRecastMeshManager.get().getMesh(job.mWorldspace, job.mChangedTile);
const auto playerTile = *mPlayerTile.lockConst(); const auto playerTile = *mPlayerTile.lockConst();
const auto offMeshConnections = mOffMeshConnectionsManager.get().get(job.mChangedTile); const auto offMeshConnections = mOffMeshConnectionsManager.get().get(job.mChangedTile);
const auto status = updateNavMesh(job.mAgentHalfExtents, recastMesh.get(), job.mChangedTile, playerTile, const auto status = updateNavMesh(job.mAgentHalfExtents, recastMesh.get(), job.mWorldspace, job.mChangedTile,
offMeshConnections, mSettings, navMeshCacheItem, mNavMeshTilesCache, getUpdateType(job.mChangeType)); playerTile, offMeshConnections, mSettings, navMeshCacheItem, mNavMeshTilesCache,
getUpdateType(job.mChangeType), mDb, mNextShapeId);
if (recastMesh != nullptr) if (recastMesh != nullptr)
{ {

View file

@ -7,6 +7,7 @@
#include "tileposition.hpp" #include "tileposition.hpp"
#include "navmeshtilescache.hpp" #include "navmeshtilescache.hpp"
#include "waitconditiontype.hpp" #include "waitconditiontype.hpp"
#include "navmeshdb.hpp"
#include <osg/Vec3f> #include <osg/Vec3f>
@ -57,6 +58,7 @@ namespace DetourNavigator
{ {
const osg::Vec3f mAgentHalfExtents; const osg::Vec3f mAgentHalfExtents;
const std::weak_ptr<GuardedNavMeshCacheItem> mNavMeshCacheItem; const std::weak_ptr<GuardedNavMeshCacheItem> mNavMeshCacheItem;
const std::string mWorldspace;
const TilePosition mChangedTile; const TilePosition mChangedTile;
const std::chrono::steady_clock::time_point mProcessTime; const std::chrono::steady_clock::time_point mProcessTime;
unsigned mTryNumber = 0; unsigned mTryNumber = 0;
@ -65,7 +67,7 @@ namespace DetourNavigator
const int mDistanceToOrigin; const int mDistanceToOrigin;
Job(const osg::Vec3f& agentHalfExtents, std::weak_ptr<GuardedNavMeshCacheItem> navMeshCacheItem, Job(const osg::Vec3f& agentHalfExtents, std::weak_ptr<GuardedNavMeshCacheItem> navMeshCacheItem,
const TilePosition& changedTile, ChangeType changeType, int distanceToPlayer, std::string_view worldspace, const TilePosition& changedTile, ChangeType changeType, int distanceToPlayer,
std::chrono::steady_clock::time_point processTime); std::chrono::steady_clock::time_point processTime);
}; };
@ -75,11 +77,12 @@ namespace DetourNavigator
{ {
public: public:
AsyncNavMeshUpdater(const Settings& settings, TileCachedRecastMeshManager& recastMeshManager, AsyncNavMeshUpdater(const Settings& settings, TileCachedRecastMeshManager& recastMeshManager,
OffMeshConnectionsManager& offMeshConnectionsManager); OffMeshConnectionsManager& offMeshConnectionsManager, std::unique_ptr<NavMeshDb>&& db);
~AsyncNavMeshUpdater(); ~AsyncNavMeshUpdater();
void post(const osg::Vec3f& agentHalfExtents, const SharedNavMeshCacheItem& mNavMeshCacheItem, void post(const osg::Vec3f& agentHalfExtents, const SharedNavMeshCacheItem& mNavMeshCacheItem,
const TilePosition& playerTile, const std::map<TilePosition, ChangeType>& changedTiles); const TilePosition& playerTile, std::string_view worldspace,
const std::map<TilePosition, ChangeType>& changedTiles);
void wait(Loading::Listener& listener, WaitConditionType waitConditionType); void wait(Loading::Listener& listener, WaitConditionType waitConditionType);
@ -89,6 +92,8 @@ namespace DetourNavigator
std::reference_wrapper<const Settings> mSettings; std::reference_wrapper<const Settings> mSettings;
std::reference_wrapper<TileCachedRecastMeshManager> mRecastMeshManager; std::reference_wrapper<TileCachedRecastMeshManager> mRecastMeshManager;
std::reference_wrapper<OffMeshConnectionsManager> mOffMeshConnectionsManager; std::reference_wrapper<OffMeshConnectionsManager> mOffMeshConnectionsManager;
Misc::ScopeGuarded<std::unique_ptr<NavMeshDb>> mDb;
ShapeId mNextShapeId {1};
std::atomic_bool mShouldStop; std::atomic_bool mShouldStop;
mutable std::mutex mMutex; mutable std::mutex mMutex;
std::condition_variable mHasJob; std::condition_variable mHasJob;

View file

@ -61,7 +61,7 @@ namespace DetourNavigator
{ {
Ignore ignore {consumer}; Ignore ignore {consumer};
const std::shared_ptr<RecastMesh> recastMesh = mRecastMeshProvider.getMesh(mTilePosition); const std::shared_ptr<RecastMesh> recastMesh = mRecastMeshProvider.getMesh(mWorldspace, mTilePosition);
if (recastMesh == nullptr || isEmpty(*recastMesh)) if (recastMesh == nullptr || isEmpty(*recastMesh))
return; return;

View file

@ -10,9 +10,15 @@
#include "preparednavmeshdata.hpp" #include "preparednavmeshdata.hpp"
#include "navmeshdata.hpp" #include "navmeshdata.hpp"
#include "recastmeshbuilder.hpp" #include "recastmeshbuilder.hpp"
#include "navmeshdb.hpp"
#include "serialization.hpp"
#include "dbrefgeometryobject.hpp"
#include "navmeshdbutils.hpp"
#include <components/misc/convert.hpp> #include <components/misc/convert.hpp>
#include <components/bullethelpers/processtrianglecallback.hpp> #include <components/bullethelpers/processtrianglecallback.hpp>
#include <components/misc/convert.hpp>
#include <components/misc/guarded.hpp>
#include <DetourNavMesh.h> #include <DetourNavMesh.h>
#include <DetourNavMeshBuilder.h> #include <DetourNavMeshBuilder.h>
@ -540,9 +546,10 @@ namespace DetourNavigator
} }
UpdateNavMeshStatus updateNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh* recastMesh, UpdateNavMeshStatus updateNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh* recastMesh,
const TilePosition& changedTile, const TilePosition& playerTile, const std::string& worldspace, const TilePosition& changedTile, const TilePosition& playerTile,
const std::vector<OffMeshConnection>& offMeshConnections, const Settings& settings, const std::vector<OffMeshConnection>& offMeshConnections, const Settings& settings,
const SharedNavMeshCacheItem& navMeshCacheItem, NavMeshTilesCache& navMeshTilesCache, UpdateType updateType) const SharedNavMeshCacheItem& navMeshCacheItem, NavMeshTilesCache& navMeshTilesCache, UpdateType updateType,
Misc::ScopeGuarded<std::unique_ptr<NavMeshDb>>& db, ShapeId& nextShapeId)
{ {
Log(Debug::Debug) << std::fixed << std::setprecision(2) << Log(Debug::Debug) << std::fixed << std::setprecision(2) <<
"Update NavMesh with multiple tiles:" << "Update NavMesh with multiple tiles:" <<
@ -578,7 +585,24 @@ namespace DetourNavigator
if (!cachedNavMeshData) if (!cachedNavMeshData)
{ {
auto prepared = prepareNavMeshTileData(*recastMesh, changedTile, agentHalfExtents, settings.mRecast); std::optional<TileData> stored;
if (const auto dbLocked = db.lock(); *dbLocked != nullptr)
{
const std::vector<DbRefGeometryObject> objects = makeDbRefGeometryObjects(recastMesh->getMeshSources(),
[&] (const MeshSource& v) { return resolveMeshSource(**dbLocked, v, nextShapeId); });
stored = (*dbLocked)->getTileData(worldspace, changedTile, serialize(settings.mRecast, *recastMesh, objects));
}
std::unique_ptr<PreparedNavMeshData> prepared;
if (stored.has_value() && stored->mVersion == settings.mNavMeshVersion)
{
prepared = std::make_unique<PreparedNavMeshData>();
if (!deserialize(stored->mData, *prepared))
prepared = nullptr;
}
if (prepared == nullptr)
prepared = prepareNavMeshTileData(*recastMesh, changedTile, agentHalfExtents, settings.mRecast);
if (prepared == nullptr) if (prepared == nullptr)
{ {

View file

@ -7,6 +7,9 @@
#include "sharednavmesh.hpp" #include "sharednavmesh.hpp"
#include "navmeshtilescache.hpp" #include "navmeshtilescache.hpp"
#include "offmeshconnection.hpp" #include "offmeshconnection.hpp"
#include "navmeshdb.hpp"
#include <components/misc/guarded.hpp>
#include <osg/Vec3f> #include <osg/Vec3f>
@ -63,9 +66,10 @@ namespace DetourNavigator
}; };
UpdateNavMeshStatus updateNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh* recastMesh, UpdateNavMeshStatus updateNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh* recastMesh,
const TilePosition& changedTile, const TilePosition& playerTile, const std::string& worldspace, const TilePosition& changedTile, const TilePosition& playerTile,
const std::vector<OffMeshConnection>& offMeshConnections, const Settings& settings, const std::vector<OffMeshConnection>& offMeshConnections, const Settings& settings,
const SharedNavMeshCacheItem& navMeshCacheItem, NavMeshTilesCache& navMeshTilesCache, UpdateType updateType); const SharedNavMeshCacheItem& navMeshCacheItem, NavMeshTilesCache& navMeshTilesCache, UpdateType updateType,
Misc::ScopeGuarded<std::unique_ptr<NavMeshDb>>& db, ShapeId& nextShapeId);
} }
#endif #endif

View file

@ -5,10 +5,15 @@
namespace DetourNavigator namespace DetourNavigator
{ {
std::unique_ptr<Navigator> makeNavigator(const Settings& settings) std::unique_ptr<Navigator> makeNavigator(const Settings& settings, const std::string& userDataPath)
{ {
DetourNavigator::RecastGlobalAllocator::init(); DetourNavigator::RecastGlobalAllocator::init();
return std::make_unique<NavigatorImpl>(settings);
std::unique_ptr<NavMeshDb> db;
if (settings.mEnableNavMeshDiskCache)
db = std::make_unique<NavMeshDb>(userDataPath + "/navmesh.db");
return std::make_unique<NavigatorImpl>(settings, std::move(db));
} }
std::unique_ptr<Navigator> makeNavigatorStub() std::unique_ptr<Navigator> makeNavigatorStub()

View file

@ -10,6 +10,8 @@
#include <components/resource/bulletshape.hpp> #include <components/resource/bulletshape.hpp>
#include <string_view>
namespace ESM namespace ESM
{ {
struct Cell; struct Cell;
@ -75,6 +77,12 @@ namespace DetourNavigator
*/ */
virtual void removeAgent(const osg::Vec3f& agentHalfExtents) = 0; virtual void removeAgent(const osg::Vec3f& agentHalfExtents) = 0;
/**
* @brief setWorldspace should be called before adding object from new worldspace
* @param worldspace
*/
virtual void setWorldspace(std::string_view worldspace) = 0;
/** /**
* @brief addObject is used to add complex object with allowed to walk and avoided to walk shapes * @brief addObject is used to add complex object with allowed to walk and avoided to walk shapes
* @param id is used to distinguish different objects * @param id is used to distinguish different objects
@ -188,7 +196,7 @@ namespace DetourNavigator
virtual float getMaxNavmeshAreaRealRadius() const = 0; virtual float getMaxNavmeshAreaRealRadius() const = 0;
}; };
std::unique_ptr<Navigator> makeNavigator(const Settings& settings); std::unique_ptr<Navigator> makeNavigator(const Settings& settings, const std::string& userDataPath);
std::unique_ptr<Navigator> makeNavigatorStub(); std::unique_ptr<Navigator> makeNavigatorStub();
} }

View file

@ -8,9 +8,9 @@
namespace DetourNavigator namespace DetourNavigator
{ {
NavigatorImpl::NavigatorImpl(const Settings& settings) NavigatorImpl::NavigatorImpl(const Settings& settings, std::unique_ptr<NavMeshDb>&& db)
: mSettings(settings) : mSettings(settings)
, mNavMeshManager(mSettings) , mNavMeshManager(mSettings, std::move(db))
, mUpdatesEnabled(true) , mUpdatesEnabled(true)
{ {
} }
@ -32,6 +32,11 @@ namespace DetourNavigator
--it->second; --it->second;
} }
void NavigatorImpl::setWorldspace(std::string_view worldspace)
{
mNavMeshManager.setWorldspace(worldspace);
}
bool NavigatorImpl::addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) bool NavigatorImpl::addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform)
{ {
const CollisionShape collisionShape(shapes.mShapeInstance, *shapes.mShapeInstance->mCollisionShape, shapes.mTransform); const CollisionShape collisionShape(shapes.mShapeInstance, *shapes.mShapeInstance->mCollisionShape, shapes.mTransform);

View file

@ -5,6 +5,7 @@
#include "navmeshmanager.hpp" #include "navmeshmanager.hpp"
#include <set> #include <set>
#include <memory>
namespace DetourNavigator namespace DetourNavigator
{ {
@ -15,12 +16,14 @@ namespace DetourNavigator
* @brief Navigator constructor initializes all internal data. Constructed object is ready to build a scene. * @brief Navigator constructor initializes all internal data. Constructed object is ready to build a scene.
* @param settings allows to customize navigator work. Constructor is only place to set navigator settings. * @param settings allows to customize navigator work. Constructor is only place to set navigator settings.
*/ */
explicit NavigatorImpl(const Settings& settings); explicit NavigatorImpl(const Settings& settings, std::unique_ptr<NavMeshDb>&& db);
void addAgent(const osg::Vec3f& agentHalfExtents) override; void addAgent(const osg::Vec3f& agentHalfExtents) override;
void removeAgent(const osg::Vec3f& agentHalfExtents) override; void removeAgent(const osg::Vec3f& agentHalfExtents) override;
void setWorldspace(std::string_view worldspace) override;
bool addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) override; bool addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) override;
bool addObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform) override; bool addObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform) override;

View file

@ -19,6 +19,8 @@ namespace DetourNavigator
void removeAgent(const osg::Vec3f& /*agentHalfExtents*/) override {} void removeAgent(const osg::Vec3f& /*agentHalfExtents*/) override {}
void setWorldspace(std::string_view /*worldspace*/) override {}
bool addObject(const ObjectId /*id*/, const ObjectShapes& /*shapes*/, const btTransform& /*transform*/) override bool addObject(const ObjectId /*id*/, const ObjectShapes& /*shapes*/, const btTransform& /*transform*/) override
{ {
return false; return false;

View file

@ -41,13 +41,23 @@ namespace
namespace DetourNavigator namespace DetourNavigator
{ {
NavMeshManager::NavMeshManager(const Settings& settings) NavMeshManager::NavMeshManager(const Settings& settings, std::unique_ptr<NavMeshDb>&& db)
: mSettings(settings) : mSettings(settings)
, mRecastMeshManager(mSettings.mRecast) , mRecastMeshManager(settings.mRecast)
, mOffMeshConnectionsManager(mSettings.mRecast) , mOffMeshConnectionsManager(settings.mRecast)
, mAsyncNavMeshUpdater(settings, mRecastMeshManager, mOffMeshConnectionsManager) , mAsyncNavMeshUpdater(settings, mRecastMeshManager, mOffMeshConnectionsManager, std::move(db))
{} {}
void NavMeshManager::setWorldspace(std::string_view worldspace)
{
if (worldspace == mWorldspace)
return;
mRecastMeshManager.setWorldspace(worldspace);
for (auto& [agent, cache] : mCache)
cache = std::make_shared<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), ++mGenerationCounter);
mWorldspace = worldspace;
}
bool NavMeshManager::addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, bool NavMeshManager::addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform,
const AreaType areaType) const AreaType areaType)
{ {
@ -208,7 +218,7 @@ namespace DetourNavigator
recastMeshManager.reportNavMeshChange(recastMeshManager.getVersion(), Version {0, 0}); recastMeshManager.reportNavMeshChange(recastMeshManager.getVersion(), Version {0, 0});
}); });
} }
mAsyncNavMeshUpdater.post(agentHalfExtents, cached, playerTile, tilesToPost); mAsyncNavMeshUpdater.post(agentHalfExtents, cached, playerTile, mRecastMeshManager.getWorldspace(), tilesToPost);
if (changedTiles != mChangedTiles.end()) if (changedTiles != mChangedTiles.end())
changedTiles->second.clear(); changedTiles->second.clear();
Log(Debug::Debug) << "Cache update posted for agent=" << agentHalfExtents << Log(Debug::Debug) << "Cache update posted for agent=" << agentHalfExtents <<
@ -241,9 +251,10 @@ namespace DetourNavigator
std::vector<TilePosition> tiles; std::vector<TilePosition> tiles;
mRecastMeshManager.forEachTile( mRecastMeshManager.forEachTile(
[&tiles] (const TilePosition& tile, const CachedRecastMeshManager&) { tiles.push_back(tile); }); [&tiles] (const TilePosition& tile, const CachedRecastMeshManager&) { tiles.push_back(tile); });
const std::string worldspace = mRecastMeshManager.getWorldspace();
RecastMeshTiles result; RecastMeshTiles result;
for (const TilePosition& tile : tiles) for (const TilePosition& tile : tiles)
if (auto mesh = mRecastMeshManager.getCachedMesh(tile)) if (auto mesh = mRecastMeshManager.getCachedMesh(worldspace, tile))
result.emplace(tile, std::move(mesh)); result.emplace(tile, std::move(mesh));
return result; return result;
} }

View file

@ -22,7 +22,9 @@ namespace DetourNavigator
class NavMeshManager class NavMeshManager
{ {
public: public:
NavMeshManager(const Settings& settings); explicit NavMeshManager(const Settings& settings, std::unique_ptr<NavMeshDb>&& db);
void setWorldspace(std::string_view worldspace);
bool addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, bool addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform,
const AreaType areaType); const AreaType areaType);
@ -62,6 +64,7 @@ namespace DetourNavigator
private: private:
const Settings& mSettings; const Settings& mSettings;
std::string mWorldspace;
TileCachedRecastMeshManager mRecastMeshManager; TileCachedRecastMeshManager mRecastMeshManager;
OffMeshConnectionsManager mOffMeshConnectionsManager; OffMeshConnectionsManager mOffMeshConnectionsManager;
AsyncNavMeshUpdater mAsyncNavMeshUpdater; AsyncNavMeshUpdater mAsyncNavMeshUpdater;

View file

@ -1,8 +1,8 @@
#include "preparednavmeshdata.hpp" #include "preparednavmeshdata.hpp"
#include "preparednavmeshdatatuple.hpp" #include "preparednavmeshdatatuple.hpp"
#include "recast.hpp"
#include <Recast.h> #include <Recast.h>
#include <RecastAlloc.h>
namespace namespace
{ {
@ -15,13 +15,6 @@ namespace
value.nverts = 0; value.nverts = 0;
value.ntris = 0; value.ntris = 0;
} }
void freePolyMeshDetail(rcPolyMeshDetail& value) noexcept
{
rcFree(value.meshes);
rcFree(value.verts);
rcFree(value.tris);
}
} }
namespace DetourNavigator namespace DetourNavigator

View file

@ -0,0 +1,49 @@
#include "recast.hpp"
#include <Recast.h>
#include <RecastAlloc.h>
#include <cstring>
#include <new>
namespace DetourNavigator
{
void* permRecastAlloc(std::size_t size)
{
void* const result = rcAlloc(size, RC_ALLOC_PERM);
if (result == nullptr)
throw std::bad_alloc();
return result;
}
void permRecastAlloc(rcPolyMesh& value)
{
permRecastAlloc(value.verts, getVertsLength(value));
permRecastAlloc(value.polys, getPolysLength(value));
permRecastAlloc(value.regs, getRegsLength(value));
permRecastAlloc(value.flags, getFlagsLength(value));
permRecastAlloc(value.areas, getAreasLength(value));
}
void permRecastAlloc(rcPolyMeshDetail& value)
{
try
{
permRecastAlloc(value.meshes, getMeshesLength(value));
permRecastAlloc(value.verts, getVertsLength(value));
permRecastAlloc(value.tris, getTrisLength(value));
}
catch (...)
{
freePolyMeshDetail(value);
throw;
}
}
void freePolyMeshDetail(rcPolyMeshDetail& value) noexcept
{
rcFree(value.meshes);
rcFree(value.verts);
rcFree(value.tris);
}
}

View file

@ -4,6 +4,7 @@
#include <Recast.h> #include <Recast.h>
#include <cstddef> #include <cstddef>
#include <type_traits>
namespace DetourNavigator namespace DetourNavigator
{ {
@ -46,6 +47,21 @@ namespace DetourNavigator
{ {
return 4 * static_cast<std::size_t>(value.ntris); return 4 * static_cast<std::size_t>(value.ntris);
} }
void* permRecastAlloc(std::size_t size);
template <class T>
inline void permRecastAlloc(T*& values, std::size_t size)
{
static_assert(std::is_arithmetic_v<T>);
values = new (permRecastAlloc(size * sizeof(T))) T[size];
}
void permRecastAlloc(rcPolyMesh& value);
void permRecastAlloc(rcPolyMeshDetail& value);
void freePolyMeshDetail(rcPolyMeshDetail& value) noexcept;
} }
#endif #endif

View file

@ -20,9 +20,9 @@ namespace DetourNavigator
: mImpl(impl) : mImpl(impl)
{} {}
std::shared_ptr<RecastMesh> getMesh(const TilePosition& tilePosition) const std::shared_ptr<RecastMesh> getMesh(std::string_view worldspace, const TilePosition& tilePosition) const
{ {
return mImpl.get().getNewMesh(tilePosition); return mImpl.get().getNewMesh(worldspace, tilePosition);
} }
private: private:

View file

@ -2,14 +2,18 @@
#include "dbrefgeometryobject.hpp" #include "dbrefgeometryobject.hpp"
#include "preparednavmeshdata.hpp" #include "preparednavmeshdata.hpp"
#include "recast.hpp"
#include "recastmesh.hpp" #include "recastmesh.hpp"
#include "settings.hpp" #include "settings.hpp"
#include <components/serialization/binaryreader.hpp>
#include <components/serialization/binarywriter.hpp> #include <components/serialization/binarywriter.hpp>
#include <components/serialization/format.hpp> #include <components/serialization/format.hpp>
#include <components/serialization/sizeaccumulator.hpp> #include <components/serialization/sizeaccumulator.hpp>
#include <cstddef> #include <cstddef>
#include <cstring>
#include <type_traits>
#include <vector> #include <vector>
namespace DetourNavigator namespace DetourNavigator
@ -142,8 +146,9 @@ namespace
visitor(*this, dbRefGeometryObjects); visitor(*this, dbRefGeometryObjects);
} }
template <class Visitor> template <class Visitor, class T>
auto operator()(Visitor&& visitor, const rcPolyMesh& value) const auto operator()(Visitor&& visitor, T& value) const
-> std::enable_if_t<std::is_same_v<std::decay_t<T>, rcPolyMesh>>
{ {
visitor(*this, value.nverts); visitor(*this, value.nverts);
visitor(*this, value.npolys); visitor(*this, value.npolys);
@ -155,6 +160,19 @@ namespace
visitor(*this, value.ch); visitor(*this, value.ch);
visitor(*this, value.borderSize); visitor(*this, value.borderSize);
visitor(*this, value.maxEdgeError); visitor(*this, value.maxEdgeError);
if constexpr (mode == Serialization::Mode::Read)
{
if (value.verts == nullptr)
permRecastAlloc(value.verts, getVertsLength(value));
if (value.polys == nullptr)
permRecastAlloc(value.polys, getPolysLength(value));
if (value.regs == nullptr)
permRecastAlloc(value.regs, getRegsLength(value));
if (value.flags == nullptr)
permRecastAlloc(value.flags, getFlagsLength(value));
if (value.areas == nullptr)
permRecastAlloc(value.areas, getAreasLength(value));
}
visitor(*this, value.verts, getVertsLength(value)); visitor(*this, value.verts, getVertsLength(value));
visitor(*this, value.polys, getPolysLength(value)); visitor(*this, value.polys, getPolysLength(value));
visitor(*this, value.regs, getRegsLength(value)); visitor(*this, value.regs, getRegsLength(value));
@ -162,22 +180,48 @@ namespace
visitor(*this, value.areas, getAreasLength(value)); visitor(*this, value.areas, getAreasLength(value));
} }
template <class Visitor> template <class Visitor, class T>
auto operator()(Visitor&& visitor, const rcPolyMeshDetail& value) const auto operator()(Visitor&& visitor, T& value) const
-> std::enable_if_t<std::is_same_v<std::decay_t<T>, rcPolyMeshDetail>>
{ {
visitor(*this, value.nmeshes); visitor(*this, value.nmeshes);
if constexpr (mode == Serialization::Mode::Read)
if (value.meshes == nullptr)
permRecastAlloc(value.meshes, getMeshesLength(value));
visitor(*this, value.meshes, getMeshesLength(value)); visitor(*this, value.meshes, getMeshesLength(value));
visitor(*this, value.nverts); visitor(*this, value.nverts);
if constexpr (mode == Serialization::Mode::Read)
if (value.verts == nullptr)
permRecastAlloc(value.verts, getVertsLength(value));
visitor(*this, value.verts, getVertsLength(value)); visitor(*this, value.verts, getVertsLength(value));
visitor(*this, value.ntris); visitor(*this, value.ntris);
if constexpr (mode == Serialization::Mode::Read)
if (value.tris == nullptr)
permRecastAlloc(value.tris, getTrisLength(value));
visitor(*this, value.tris, getTrisLength(value)); visitor(*this, value.tris, getTrisLength(value));
} }
template <class Visitor> template <class Visitor, class T>
auto operator()(Visitor&& visitor, const PreparedNavMeshData& value) const auto operator()(Visitor&& visitor, T& value) const
-> std::enable_if_t<std::is_same_v<std::decay_t<T>, PreparedNavMeshData>>
{ {
visitor(*this, DetourNavigator::preparedNavMeshDataMagic); if constexpr (mode == Serialization::Mode::Write)
visitor(*this, DetourNavigator::preparedNavMeshDataVersion); {
visitor(*this, DetourNavigator::preparedNavMeshDataMagic);
visitor(*this, DetourNavigator::preparedNavMeshDataVersion);
}
else
{
static_assert(mode == Serialization::Mode::Read);
char magic[std::size(DetourNavigator::preparedNavMeshDataMagic)];
visitor(*this, magic);
if (std::memcmp(magic, DetourNavigator::preparedNavMeshDataMagic, sizeof(magic)) != 0)
throw std::runtime_error("Bad PreparedNavMeshData magic");
std::uint32_t version = 0;
visitor(*this, version);
if (version != DetourNavigator::preparedNavMeshDataVersion)
throw std::runtime_error("Bad PreparedNavMeshData version");
}
visitor(*this, value.mUserId); visitor(*this, value.mUserId);
visitor(*this, value.mCellSize); visitor(*this, value.mCellSize);
visitor(*this, value.mCellHeight); visitor(*this, value.mCellHeight);
@ -211,4 +255,18 @@ namespace DetourNavigator
format(Serialization::BinaryWriter(result.data(), result.data() + result.size()), value); format(Serialization::BinaryWriter(result.data(), result.data() + result.size()), value);
return result; return result;
} }
bool deserialize(const std::vector<std::byte>& data, PreparedNavMeshData& value)
{
try
{
constexpr Format<Serialization::Mode::Read> format;
format(Serialization::BinaryReader(data.data(), data.data() + data.size()), value);
return true;
}
catch (const std::exception&)
{
return false;
}
}
} }

View file

@ -22,6 +22,8 @@ namespace DetourNavigator
const std::vector<DbRefGeometryObject>& dbRefGeometryObjects); const std::vector<DbRefGeometryObject>& dbRefGeometryObjects);
std::vector<std::byte> serialize(const PreparedNavMeshData& value); std::vector<std::byte> serialize(const PreparedNavMeshData& value);
bool deserialize(const std::vector<std::byte>& data, PreparedNavMeshData& value);
} }
#endif #endif

View file

@ -62,6 +62,7 @@ namespace DetourNavigator
result.mEnableNavMeshFileNameRevision = ::Settings::Manager::getBool("enable nav mesh file name revision", "Navigator"); result.mEnableNavMeshFileNameRevision = ::Settings::Manager::getBool("enable nav mesh file name revision", "Navigator");
result.mMinUpdateInterval = std::chrono::milliseconds(::Settings::Manager::getInt("min update interval ms", "Navigator")); result.mMinUpdateInterval = std::chrono::milliseconds(::Settings::Manager::getInt("min update interval ms", "Navigator"));
result.mNavMeshVersion = ::Settings::Manager::getInt("nav mesh version", "Navigator"); result.mNavMeshVersion = ::Settings::Manager::getInt("nav mesh version", "Navigator");
result.mEnableNavMeshDiskCache = ::Settings::Manager::getBool("enable nav mesh disk cache", "Navigator");
return result; return result;
} }

View file

@ -39,6 +39,7 @@ namespace DetourNavigator
bool mEnableWriteNavMeshToFile = false; bool mEnableWriteNavMeshToFile = false;
bool mEnableRecastMeshFileNameRevision = false; bool mEnableRecastMeshFileNameRevision = false;
bool mEnableNavMeshFileNameRevision = false; bool mEnableNavMeshFileNameRevision = false;
bool mEnableNavMeshDiskCache = false;
RecastSettings mRecast; RecastSettings mRecast;
DetourSettings mDetour; DetourSettings mDetour;
int mWaitUntilMinDistanceToPlayer = 0; int mWaitUntilMinDistanceToPlayer = 0;

View file

@ -14,15 +14,30 @@ namespace DetourNavigator
: mSettings(settings) : mSettings(settings)
{} {}
std::string TileCachedRecastMeshManager::getWorldspace() const
{
const std::lock_guard lock(mMutex);
return mWorldspace;
}
void TileCachedRecastMeshManager::setWorldspace(std::string_view worldspace)
{
const std::lock_guard lock(mMutex);
if (mWorldspace == worldspace)
return;
mTiles.clear();
mWorldspace = worldspace;
}
bool TileCachedRecastMeshManager::addObject(const ObjectId id, const CollisionShape& shape, bool TileCachedRecastMeshManager::addObject(const ObjectId id, const CollisionShape& shape,
const btTransform& transform, const AreaType areaType) const btTransform& transform, const AreaType areaType)
{ {
std::vector<TilePosition> tilesPositions; std::vector<TilePosition> tilesPositions;
{ {
auto tiles = mTiles.lock(); const std::lock_guard lock(mMutex);
getTilesPositions(shape.getShape(), transform, mSettings, [&] (const TilePosition& tilePosition) getTilesPositions(shape.getShape(), transform, mSettings, [&] (const TilePosition& tilePosition)
{ {
if (addTile(id, shape, transform, areaType, tilePosition, tiles.get())) if (addTile(id, shape, transform, areaType, tilePosition, mTiles))
tilesPositions.push_back(tilePosition); tilesPositions.push_back(tilePosition);
}); });
} }
@ -41,10 +56,10 @@ namespace DetourNavigator
return std::nullopt; return std::nullopt;
std::optional<RemovedRecastMeshObject> result; std::optional<RemovedRecastMeshObject> result;
{ {
auto tiles = mTiles.lock(); const std::lock_guard lock(mMutex);
for (const auto& tilePosition : object->second) for (const auto& tilePosition : object->second)
{ {
const auto removed = removeTile(id, tilePosition, tiles.get()); const auto removed = removeTile(id, tilePosition, mTiles);
if (removed && !result) if (removed && !result)
result = removed; result = removed;
} }
@ -62,8 +77,8 @@ namespace DetourNavigator
if (cellSize == std::numeric_limits<int>::max()) if (cellSize == std::numeric_limits<int>::max())
{ {
const auto tiles = mTiles.lock(); const std::lock_guard lock(mMutex);
for (auto& tile : *tiles) for (auto& tile : mTiles)
{ {
if (tile.second->addWater(cellPosition, cellSize, level)) if (tile.second->addWater(cellPosition, cellSize, level))
{ {
@ -77,13 +92,13 @@ namespace DetourNavigator
const btVector3 shift = Misc::Convert::toBullet(getWaterShift3d(cellPosition, cellSize, level)); const btVector3 shift = Misc::Convert::toBullet(getWaterShift3d(cellPosition, cellSize, level));
getTilesPositions(cellSize, shift, mSettings, [&] (const TilePosition& tilePosition) getTilesPositions(cellSize, shift, mSettings, [&] (const TilePosition& tilePosition)
{ {
const auto tiles = mTiles.lock(); const std::lock_guard lock(mMutex);
auto tile = tiles->find(tilePosition); auto tile = mTiles.find(tilePosition);
if (tile == tiles->end()) if (tile == mTiles.end())
{ {
const TileBounds tileBounds = makeRealTileBoundsWithBorder(mSettings, tilePosition); const TileBounds tileBounds = makeRealTileBoundsWithBorder(mSettings, tilePosition);
tile = tiles->emplace(tilePosition, tile = mTiles.emplace_hint(tile, tilePosition,
std::make_shared<CachedRecastMeshManager>(tileBounds, mTilesGeneration)).first; std::make_shared<CachedRecastMeshManager>(tileBounds, mTilesGeneration));
} }
if (tile->second->addWater(cellPosition, cellSize, level)) if (tile->second->addWater(cellPosition, cellSize, level))
{ {
@ -107,14 +122,14 @@ namespace DetourNavigator
std::optional<Water> result; std::optional<Water> result;
for (const auto& tilePosition : object->second) for (const auto& tilePosition : object->second)
{ {
const auto tiles = mTiles.lock(); const std::lock_guard lock(mMutex);
const auto tile = tiles->find(tilePosition); const auto tile = mTiles.find(tilePosition);
if (tile == tiles->end()) if (tile == mTiles.end())
continue; continue;
const auto tileResult = tile->second->removeWater(cellPosition); const auto tileResult = tile->second->removeWater(cellPosition);
if (tile->second->isEmpty()) if (tile->second->isEmpty())
{ {
tiles->erase(tile); mTiles.erase(tile);
++mTilesGeneration; ++mTilesGeneration;
} }
if (tileResult && !result) if (tileResult && !result)
@ -135,13 +150,13 @@ namespace DetourNavigator
getTilesPositions(cellSize, shift, mSettings, [&] (const TilePosition& tilePosition) getTilesPositions(cellSize, shift, mSettings, [&] (const TilePosition& tilePosition)
{ {
const auto tiles = mTiles.lock(); const std::lock_guard lock(mMutex);
auto tile = tiles->find(tilePosition); auto tile = mTiles.find(tilePosition);
if (tile == tiles->end()) if (tile == mTiles.end())
{ {
const TileBounds tileBounds = makeRealTileBoundsWithBorder(mSettings, tilePosition); const TileBounds tileBounds = makeRealTileBoundsWithBorder(mSettings, tilePosition);
tile = tiles->emplace(tilePosition, tile = mTiles.emplace_hint(tile, tilePosition,
std::make_shared<CachedRecastMeshManager>(tileBounds, mTilesGeneration)).first; std::make_shared<CachedRecastMeshManager>(tileBounds, mTilesGeneration));
} }
if (tile->second->addHeightfield(cellPosition, cellSize, shape)) if (tile->second->addHeightfield(cellPosition, cellSize, shape))
{ {
@ -164,14 +179,14 @@ namespace DetourNavigator
std::optional<SizedHeightfieldShape> result; std::optional<SizedHeightfieldShape> result;
for (const auto& tilePosition : object->second) for (const auto& tilePosition : object->second)
{ {
const auto tiles = mTiles.lock(); const std::lock_guard lock(mMutex);
const auto tile = tiles->find(tilePosition); const auto tile = mTiles.find(tilePosition);
if (tile == tiles->end()) if (tile == mTiles.end())
continue; continue;
const auto tileResult = tile->second->removeHeightfield(cellPosition); const auto tileResult = tile->second->removeHeightfield(cellPosition);
if (tile->second->isEmpty()) if (tile->second->isEmpty())
{ {
tiles->erase(tile); mTiles.erase(tile);
++mTilesGeneration; ++mTilesGeneration;
} }
if (tileResult && !result) if (tileResult && !result)
@ -182,23 +197,23 @@ namespace DetourNavigator
return result; return result;
} }
std::shared_ptr<RecastMesh> TileCachedRecastMeshManager::getMesh(const TilePosition& tilePosition) const std::shared_ptr<RecastMesh> TileCachedRecastMeshManager::getMesh(std::string_view worldspace, const TilePosition& tilePosition) const
{ {
if (const auto manager = getManager(tilePosition)) if (const auto manager = getManager(worldspace, tilePosition))
return manager->getMesh(); return manager->getMesh();
return nullptr; return nullptr;
} }
std::shared_ptr<RecastMesh> TileCachedRecastMeshManager::getCachedMesh(const TilePosition& tilePosition) const std::shared_ptr<RecastMesh> TileCachedRecastMeshManager::getCachedMesh(std::string_view worldspace, const TilePosition& tilePosition) const
{ {
if (const auto manager = getManager(tilePosition)) if (const auto manager = getManager(worldspace, tilePosition))
return manager->getCachedMesh(); return manager->getCachedMesh();
return nullptr; return nullptr;
} }
std::shared_ptr<RecastMesh> TileCachedRecastMeshManager::getNewMesh(const TilePosition& tilePosition) const std::shared_ptr<RecastMesh> TileCachedRecastMeshManager::getNewMesh(std::string_view worldspace, const TilePosition& tilePosition) const
{ {
if (const auto manager = getManager(tilePosition)) if (const auto manager = getManager(worldspace, tilePosition))
return manager->getNewMesh(); return manager->getNewMesh();
return nullptr; return nullptr;
} }
@ -210,9 +225,9 @@ namespace DetourNavigator
void TileCachedRecastMeshManager::reportNavMeshChange(const TilePosition& tilePosition, Version recastMeshVersion, Version navMeshVersion) const void TileCachedRecastMeshManager::reportNavMeshChange(const TilePosition& tilePosition, Version recastMeshVersion, Version navMeshVersion) const
{ {
const auto tiles = mTiles.lockConst(); const std::lock_guard lock(mMutex);
const auto it = tiles->find(tilePosition); const auto it = mTiles.find(tilePosition);
if (it == tiles->end()) if (it == mTiles.end())
return; return;
it->second->reportNavMeshChange(recastMeshVersion, navMeshVersion); it->second->reportNavMeshChange(recastMeshVersion, navMeshVersion);
} }
@ -225,8 +240,8 @@ namespace DetourNavigator
if (tile == tiles.end()) if (tile == tiles.end())
{ {
const TileBounds tileBounds = makeRealTileBoundsWithBorder(mSettings, tilePosition); const TileBounds tileBounds = makeRealTileBoundsWithBorder(mSettings, tilePosition);
tile = tiles.emplace(tilePosition, tile = tiles.emplace_hint(tile, tilePosition,
std::make_shared<CachedRecastMeshManager>(tileBounds, mTilesGeneration)).first; std::make_shared<CachedRecastMeshManager>(tileBounds, mTilesGeneration));
} }
return tile->second->addObject(id, shape, transform, areaType); return tile->second->addObject(id, shape, transform, areaType);
} }
@ -253,11 +268,14 @@ namespace DetourNavigator
return tileResult; return tileResult;
} }
std::shared_ptr<CachedRecastMeshManager> TileCachedRecastMeshManager::getManager(const TilePosition& tilePosition) const std::shared_ptr<CachedRecastMeshManager> TileCachedRecastMeshManager::getManager(std::string_view worldspace,
const TilePosition& tilePosition) const
{ {
const auto tiles = mTiles.lockConst(); const std::lock_guard lock(mMutex);
const auto it = tiles->find(tilePosition); if (mWorldspace != worldspace)
if (it == tiles->end()) return nullptr;
const auto it = mTiles.find(tilePosition);
if (it == mTiles.end())
return nullptr; return nullptr;
return it->second; return it->second;
} }

View file

@ -8,8 +8,6 @@
#include "version.hpp" #include "version.hpp"
#include "heightfieldshape.hpp" #include "heightfieldshape.hpp"
#include <components/misc/guarded.hpp>
#include <algorithm> #include <algorithm>
#include <map> #include <map>
#include <mutex> #include <mutex>
@ -22,6 +20,10 @@ namespace DetourNavigator
public: public:
explicit TileCachedRecastMeshManager(const RecastSettings& settings); explicit TileCachedRecastMeshManager(const RecastSettings& settings);
std::string getWorldspace() const;
void setWorldspace(std::string_view worldspace);
bool addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, bool addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform,
const AreaType areaType); const AreaType areaType);
@ -36,19 +38,19 @@ namespace DetourNavigator
bool changed = false; bool changed = false;
std::vector<TilePosition> newTiles; std::vector<TilePosition> newTiles;
{ {
auto tiles = mTiles.lock(); const std::lock_guard lock(mMutex);
const auto onTilePosition = [&] (const TilePosition& tilePosition) const auto onTilePosition = [&] (const TilePosition& tilePosition)
{ {
if (std::binary_search(currentTiles.begin(), currentTiles.end(), tilePosition)) if (std::binary_search(currentTiles.begin(), currentTiles.end(), tilePosition))
{ {
newTiles.push_back(tilePosition); newTiles.push_back(tilePosition);
if (updateTile(id, transform, areaType, tilePosition, tiles.get())) if (updateTile(id, transform, areaType, tilePosition, mTiles))
{ {
onChangedTile(tilePosition); onChangedTile(tilePosition);
changed = true; changed = true;
} }
} }
else if (addTile(id, shape, transform, areaType, tilePosition, tiles.get())) else if (addTile(id, shape, transform, areaType, tilePosition, mTiles))
{ {
newTiles.push_back(tilePosition); newTiles.push_back(tilePosition);
onChangedTile(tilePosition); onChangedTile(tilePosition);
@ -59,7 +61,7 @@ namespace DetourNavigator
std::sort(newTiles.begin(), newTiles.end()); std::sort(newTiles.begin(), newTiles.end());
for (const auto& tile : currentTiles) for (const auto& tile : currentTiles)
{ {
if (!std::binary_search(newTiles.begin(), newTiles.end(), tile) && removeTile(id, tile, tiles.get())) if (!std::binary_search(newTiles.begin(), newTiles.end(), tile) && removeTile(id, tile, mTiles))
{ {
onChangedTile(tile); onChangedTile(tile);
changed = true; changed = true;
@ -84,16 +86,17 @@ namespace DetourNavigator
std::optional<SizedHeightfieldShape> removeHeightfield(const osg::Vec2i& cellPosition); std::optional<SizedHeightfieldShape> removeHeightfield(const osg::Vec2i& cellPosition);
std::shared_ptr<RecastMesh> getMesh(const TilePosition& tilePosition) const; std::shared_ptr<RecastMesh> getMesh(std::string_view worldspace, const TilePosition& tilePosition) const;
std::shared_ptr<RecastMesh> getCachedMesh(const TilePosition& tilePosition) const; std::shared_ptr<RecastMesh> getCachedMesh(std::string_view worldspace, const TilePosition& tilePosition) const;
std::shared_ptr<RecastMesh> getNewMesh(const TilePosition& tilePosition) const; std::shared_ptr<RecastMesh> getNewMesh(std::string_view worldspace, const TilePosition& tilePosition) const;
template <class Function> template <class Function>
void forEachTile(Function&& function) const void forEachTile(Function&& function) const
{ {
for (auto& [tilePosition, recastMeshManager] : *mTiles.lockConst()) const std::lock_guard lock(mMutex);
for (auto& [tilePosition, recastMeshManager] : mTiles)
function(tilePosition, *recastMeshManager); function(tilePosition, *recastMeshManager);
} }
@ -105,7 +108,9 @@ namespace DetourNavigator
using TilesMap = std::map<TilePosition, std::shared_ptr<CachedRecastMeshManager>>; using TilesMap = std::map<TilePosition, std::shared_ptr<CachedRecastMeshManager>>;
const RecastSettings& mSettings; const RecastSettings& mSettings;
Misc::ScopeGuarded<TilesMap> mTiles; mutable std::mutex mMutex;
std::string mWorldspace;
TilesMap mTiles;
std::unordered_map<ObjectId, std::vector<TilePosition>> mObjectsTilesPositions; std::unordered_map<ObjectId, std::vector<TilePosition>> mObjectsTilesPositions;
std::map<osg::Vec2i, std::vector<TilePosition>> mWaterTilesPositions; std::map<osg::Vec2i, std::vector<TilePosition>> mWaterTilesPositions;
std::map<osg::Vec2i, std::vector<TilePosition>> mHeightfieldTilesPositions; std::map<osg::Vec2i, std::vector<TilePosition>> mHeightfieldTilesPositions;
@ -121,7 +126,8 @@ namespace DetourNavigator
std::optional<RemovedRecastMeshObject> removeTile(const ObjectId id, const TilePosition& tilePosition, std::optional<RemovedRecastMeshObject> removeTile(const ObjectId id, const TilePosition& tilePosition,
TilesMap& tiles); TilesMap& tiles);
inline std::shared_ptr<CachedRecastMeshManager> getManager(const TilePosition& tilePosition) const; inline std::shared_ptr<CachedRecastMeshManager> getManager(std::string_view worldspace,
const TilePosition& tilePosition) const;
}; };
} }

View file

@ -54,6 +54,17 @@ Allows to complete cell loading only when minimal navigation mesh area is genera
nearby the player. Increasing this value will keep loading screen longer but will slightly increase nav mesh generation 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. speed on systems bound by CPU. Zero means no waiting.
enable nav mesh disk cache
--------------------------
:Type: boolean
:Range: True/False
:Default: True
If true navmesh cache stored on disk will be used in addition to memory cache.
If navmesh tile is not present in memory cache, it will be looked up in the disk cache.
If it's not found there it will be generated.
Advanced settings Advanced settings
***************** *****************

View file

@ -934,6 +934,9 @@ wait until min distance to player = 5
# Should be increased each time there is a difference between output of makeNavMeshTileData function for the same input. # Should be increased each time there is a difference between output of makeNavMeshTileData function for the same input.
nav mesh version = 1 nav mesh version = 1
# Use navigation mesh cache stored on disk (true, false)
enable nav mesh disk cache = true
[Shadows] [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. # Enable or disable shadows. Bear in mind that this will force OpenMW to use shaders as if "[Shaders]/force shaders" was set to true.