Cache navmesh tiles

Use LRU modification to hold currently used items. Use RecastMesh binary
data for item key.

Store original pointer of btCollisionShape in user pointer to make available
it as an identifier within all duplicates. Use pointer to heights data array
for btHeightfieldTerrainShape.
pull/1633/head
elsid 6 years ago
parent 69b5834c64
commit ed73d130f9
No known key found for this signature in database
GPG Key ID: B845CB9FEE18AB40

@ -31,7 +31,7 @@ namespace MWRender
return mEnabled; return mEnabled;
} }
void NavMesh::update(const DetourNavigator::SharedNavMesh& sharedNavMesh, const std::size_t id, void NavMesh::update(const dtNavMesh& navMesh, const std::size_t id,
const std::size_t generation, const std::size_t revision, const DetourNavigator::Settings& settings) const std::size_t generation, const std::size_t revision, const DetourNavigator::Settings& settings)
{ {
if (!mEnabled || (mId == id && mGeneration >= generation && mRevision >= revision)) if (!mEnabled || (mId == id && mGeneration >= generation && mRevision >= revision))
@ -42,7 +42,7 @@ namespace MWRender
mRevision = revision; mRevision = revision;
if (mGroup) if (mGroup)
mRootNode->removeChild(mGroup); mRootNode->removeChild(mGroup);
mGroup = SceneUtil::createNavMeshGroup(*sharedNavMesh.lock(), settings); mGroup = SceneUtil::createNavMeshGroup(navMesh, settings);
if (mGroup) if (mGroup)
{ {
mGroup->setNodeMask(Mask_Debug); mGroup->setNodeMask(Mask_Debug);

@ -21,9 +21,8 @@ namespace MWRender
bool toggle(); bool toggle();
void update(const DetourNavigator::SharedNavMesh& sharedNavMesh, void update(const dtNavMesh& navMesh, const std::size_t number, const std::size_t generation,
const std::size_t number, const std::size_t generation, const std::size_t revision, const std::size_t revision, const DetourNavigator::Settings& settings);
const DetourNavigator::Settings& settings);
void reset(); void reset();

@ -612,8 +612,9 @@ namespace MWRender
{ {
try try
{ {
mNavMesh->update(it->second->mValue, mNavMeshNumber, it->second->mGeneration, const auto locked = it->second.lockConst();
it->second->mNavMeshRevision, mNavigator.getSettings()); mNavMesh->update(locked->getValue(), mNavMeshNumber, locked->getGeneration(),
locked->getNavMeshRevision(), mNavigator.getSettings());
} }
catch (const std::exception& e) catch (const std::exception& e)
{ {

@ -216,6 +216,7 @@ namespace MWWorld
navigatorSettings.mRegionMinSize = Settings::Manager::getInt("region min size", "Navigator"); navigatorSettings.mRegionMinSize = Settings::Manager::getInt("region min size", "Navigator");
navigatorSettings.mTileSize = Settings::Manager::getInt("tile size", "Navigator"); navigatorSettings.mTileSize = Settings::Manager::getInt("tile size", "Navigator");
navigatorSettings.mAsyncNavMeshUpdaterThreads = static_cast<std::size_t>(Settings::Manager::getInt("async nav mesh updater threads", "Navigator")); navigatorSettings.mAsyncNavMeshUpdaterThreads = static_cast<std::size_t>(Settings::Manager::getInt("async nav mesh updater threads", "Navigator"));
navigatorSettings.mMaxNavMeshTilesCacheSize = static_cast<std::size_t>(Settings::Manager::getInt("max nav mesh tiles cache size", "Navigator"));
navigatorSettings.mMaxPolygonPathSize = static_cast<std::size_t>(Settings::Manager::getInt("max polygon path size", "Navigator")); navigatorSettings.mMaxPolygonPathSize = static_cast<std::size_t>(Settings::Manager::getInt("max polygon path size", "Navigator"));
navigatorSettings.mMaxSmoothPathSize = static_cast<std::size_t>(Settings::Manager::getInt("max smooth path size", "Navigator")); navigatorSettings.mMaxSmoothPathSize = static_cast<std::size_t>(Settings::Manager::getInt("max smooth path size", "Navigator"));
navigatorSettings.mTrianglesPerChunk = static_cast<std::size_t>(Settings::Manager::getInt("triangles per chunk", "Navigator")); navigatorSettings.mTrianglesPerChunk = static_cast<std::size_t>(Settings::Manager::getInt("triangles per chunk", "Navigator"));

@ -23,6 +23,7 @@ if (GTEST_FOUND AND GMOCK_FOUND)
detournavigator/recastmeshbuilder.cpp detournavigator/recastmeshbuilder.cpp
detournavigator/gettilespositions.cpp detournavigator/gettilespositions.cpp
detournavigator/recastmeshobject.cpp detournavigator/recastmeshobject.cpp
detournavigator/navmeshtilescache.cpp
) )
source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES})

@ -56,6 +56,7 @@ namespace
mSettings.mRegionMinSize = 8; mSettings.mRegionMinSize = 8;
mSettings.mTileSize = 64; mSettings.mTileSize = 64;
mSettings.mAsyncNavMeshUpdaterThreads = 1; mSettings.mAsyncNavMeshUpdaterThreads = 1;
mSettings.mMaxNavMeshTilesCacheSize = 1024 * 1024;
mSettings.mMaxPolygonPathSize = 1024; mSettings.mMaxPolygonPathSize = 1024;
mSettings.mMaxSmoothPathSize = 1024; mSettings.mMaxSmoothPathSize = 1024;
mSettings.mTrianglesPerChunk = 256; mSettings.mTrianglesPerChunk = 256;
@ -602,4 +603,58 @@ namespace
osg::Vec3f(0, -215, -94.753631591796875), osg::Vec3f(0, -215, -94.753631591796875),
})) << mPath; })) << mPath;
} }
TEST_F(DetourNavigatorNavigatorTest, update_remove_and_update_then_find_path_should_return_path)
{
const std::array<btScalar, 5 * 5> heightfieldData {{
0, 0, 0, 0, 0,
0, -25, -25, -25, -25,
0, -25, -100, -100, -100,
0, -25, -100, -100, -100,
0, -25, -100, -100, -100,
}};
btHeightfieldTerrainShape shape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false);
shape.setLocalScaling(btVector3(128, 128, 1));
mNavigator->addAgent(mAgentHalfExtents);
mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity());
mNavigator->update(mPlayerPosition);
mNavigator->wait();
mNavigator->removeObject(ObjectId(&shape));
mNavigator->update(mPlayerPosition);
mNavigator->wait();
mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity());
mNavigator->update(mPlayerPosition);
mNavigator->wait();
mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, mOut);
EXPECT_EQ(mPath, std::deque<osg::Vec3f>({
osg::Vec3f(-215, 215, 1.85963428020477294921875),
osg::Vec3f(-194.9653167724609375, 194.9653167724609375, -6.5760211944580078125),
osg::Vec3f(-174.930633544921875, 174.930633544921875, -15.01167774200439453125),
osg::Vec3f(-154.8959503173828125, 154.8959503173828125, -23.4473323822021484375),
osg::Vec3f(-134.86126708984375, 134.86126708984375, -31.8829898834228515625),
osg::Vec3f(-114.82657623291015625, 114.82657623291015625, -40.3186492919921875),
osg::Vec3f(-94.7918853759765625, 94.7918853759765625, -47.39907073974609375),
osg::Vec3f(-74.75719451904296875, 74.75719451904296875, -53.7258148193359375),
osg::Vec3f(-54.722499847412109375, 54.722499847412109375, -60.052555084228515625),
osg::Vec3f(-34.68780517578125, 34.68780517578125, -66.37929534912109375),
osg::Vec3f(-14.6531162261962890625, 14.6531162261962890625, -72.70604705810546875),
osg::Vec3f(5.3815765380859375, -5.3815765380859375, -75.35065460205078125),
osg::Vec3f(25.41626739501953125, -25.41626739501953125, -67.96945953369140625),
osg::Vec3f(45.450958251953125, -45.450958251953125, -60.58824920654296875),
osg::Vec3f(65.48564910888671875, -65.48564910888671875, -53.20705413818359375),
osg::Vec3f(85.5203399658203125, -85.5203399658203125, -45.825855255126953125),
osg::Vec3f(105.55503082275390625, -105.55503082275390625, -38.44464874267578125),
osg::Vec3f(125.5897216796875, -125.5897216796875, -31.063449859619140625),
osg::Vec3f(145.6244049072265625, -145.6244049072265625, -23.6822509765625),
osg::Vec3f(165.659088134765625, -165.659088134765625, -16.3010540008544921875),
osg::Vec3f(185.6937713623046875, -185.6937713623046875, -8.91985416412353515625),
osg::Vec3f(205.7284698486328125, -205.7284698486328125, -1.53864824771881103515625),
osg::Vec3f(215, -215, 1.877177715301513671875),
})) << mPath;
}
} }

@ -0,0 +1,312 @@
#include "operators.hpp"
#include <components/detournavigator/navmeshtilescache.hpp>
#include <components/detournavigator/exceptions.hpp>
#include <components/detournavigator/recastmesh.hpp>
#include <LinearMath/btTransform.h>
#include <gtest/gtest.h>
namespace DetourNavigator
{
static inline bool operator ==(const NavMeshDataRef& lhs, const NavMeshDataRef& rhs)
{
return std::make_pair(lhs.mValue, lhs.mSize) == std::make_pair(rhs.mValue, rhs.mSize);
}
}
namespace
{
using namespace testing;
using namespace DetourNavigator;
struct DetourNavigatorNavMeshTilesCacheTest : Test
{
const osg::Vec3f mAgentHalfExtents {1, 2, 3};
const TilePosition mTilePosition {0, 0};
const std::vector<int> mIndices {{0, 1, 2}};
const std::vector<float> mVertices {{0, 0, 0, 1, 0, 0, 1, 1, 0}};
const std::vector<AreaType> mAreaTypes {1, AreaType_ground};
const std::vector<RecastMesh::Water> mWater {};
const std::size_t mTrianglesPerChunk {1};
const RecastMesh mRecastMesh {mIndices, mVertices, mAreaTypes, mWater, mTrianglesPerChunk};
const std::vector<OffMeshConnection> mOffMeshConnections {};
unsigned char* const mData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
NavMeshData mNavMeshData {mData, 1};
};
TEST_F(DetourNavigatorNavMeshTilesCacheTest, get_for_empty_cache_should_return_empty_value)
{
const std::size_t maxSize = 0;
NavMeshTilesCache cache(maxSize);
EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections));
}
TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_for_not_enought_cache_size_should_return_empty_value)
{
const std::size_t maxSize = 0;
NavMeshTilesCache cache(maxSize);
EXPECT_FALSE(cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections,
std::move(mNavMeshData)));
}
TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_return_cached_value)
{
const std::size_t maxSize = 1;
NavMeshTilesCache cache(maxSize);
const auto result = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections,
std::move(mNavMeshData));
EXPECT_EQ(result.get(), (NavMeshDataRef {mData, 1}));
}
TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_existing_element_should_throw_exception)
{
const std::size_t maxSize = 2;
NavMeshTilesCache cache(maxSize);
const auto anotherData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
NavMeshData anotherNavMeshData {anotherData, 1};
cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData));
EXPECT_THROW(
cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(anotherNavMeshData)),
InvalidArgument
);
}
TEST_F(DetourNavigatorNavMeshTilesCacheTest, get_should_return_cached_value)
{
const std::size_t maxSize = 1;
NavMeshTilesCache cache(maxSize);
cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData));
const auto result = cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections);
ASSERT_TRUE(result);
EXPECT_EQ(result.get(), (NavMeshDataRef {mData, 1}));
}
TEST_F(DetourNavigatorNavMeshTilesCacheTest, get_for_cache_miss_by_agent_half_extents_should_return_empty_value)
{
const std::size_t maxSize = 1;
NavMeshTilesCache cache(maxSize);
const osg::Vec3f unexsistentAgentHalfExtents {1, 1, 1};
cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData));
EXPECT_FALSE(cache.get(unexsistentAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections));
}
TEST_F(DetourNavigatorNavMeshTilesCacheTest, get_for_cache_miss_by_tile_position_should_return_empty_value)
{
const std::size_t maxSize = 1;
NavMeshTilesCache cache(maxSize);
const TilePosition unexistentTilePosition {1, 1};
cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData));
EXPECT_FALSE(cache.get(mAgentHalfExtents, unexistentTilePosition, mRecastMesh, mOffMeshConnections));
}
TEST_F(DetourNavigatorNavMeshTilesCacheTest, get_for_cache_miss_by_recast_mesh_should_return_empty_value)
{
const std::size_t maxSize = 1;
NavMeshTilesCache cache(maxSize);
const std::vector<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}};
const RecastMesh unexistentRecastMesh {mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk};
cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData));
EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, unexistentRecastMesh, mOffMeshConnections));
}
TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_replace_unused_value)
{
const std::size_t maxSize = 1;
NavMeshTilesCache cache(maxSize);
const std::vector<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}};
const RecastMesh anotherRecastMesh {mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk};
const auto anotherData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
NavMeshData anotherNavMeshData {anotherData, 1};
cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData));
const auto result = cache.set(mAgentHalfExtents, mTilePosition, anotherRecastMesh, mOffMeshConnections,
std::move(anotherNavMeshData));
ASSERT_TRUE(result);
EXPECT_EQ(result.get(), (NavMeshDataRef {anotherData, 1}));
EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections));
}
TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_not_replace_used_value)
{
const std::size_t maxSize = 1;
NavMeshTilesCache cache(maxSize);
const std::vector<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}};
const RecastMesh anotherRecastMesh {mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk};
const auto anotherData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
NavMeshData anotherNavMeshData {anotherData, 1};
const auto value = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections,
std::move(mNavMeshData));
EXPECT_FALSE(cache.set(mAgentHalfExtents, mTilePosition, anotherRecastMesh, mOffMeshConnections,
std::move(anotherNavMeshData)));
}
TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_replace_unused_least_recently_set_value)
{
const std::size_t maxSize = 2;
NavMeshTilesCache cache(maxSize);
const std::vector<RecastMesh::Water> leastRecentlySetWater {1, RecastMesh::Water {1, btTransform::getIdentity()}};
const RecastMesh leastRecentlySetRecastMesh {mIndices, mVertices, mAreaTypes, leastRecentlySetWater,
mTrianglesPerChunk};
const auto leastRecentlySetData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
NavMeshData leastRecentlySetNavMeshData {leastRecentlySetData, 1};
const std::vector<RecastMesh::Water> mostRecentlySetWater {1, RecastMesh::Water {2, btTransform::getIdentity()}};
const RecastMesh mostRecentlySetRecastMesh {mIndices, mVertices, mAreaTypes, mostRecentlySetWater,
mTrianglesPerChunk};
const auto mostRecentlySetData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
NavMeshData mostRecentlySetNavMeshData {mostRecentlySetData, 1};
ASSERT_TRUE(cache.set(mAgentHalfExtents, mTilePosition, leastRecentlySetRecastMesh, mOffMeshConnections,
std::move(leastRecentlySetNavMeshData)));
ASSERT_TRUE(cache.set(mAgentHalfExtents, mTilePosition, mostRecentlySetRecastMesh, mOffMeshConnections,
std::move(mostRecentlySetNavMeshData)));
const auto result = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections,
std::move(mNavMeshData));
EXPECT_EQ(result.get(), (NavMeshDataRef {mData, 1}));
EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, leastRecentlySetRecastMesh, mOffMeshConnections));
EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, mostRecentlySetRecastMesh, mOffMeshConnections));
}
TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_replace_unused_least_recently_used_value)
{
const std::size_t maxSize = 2;
NavMeshTilesCache cache(maxSize);
const std::vector<RecastMesh::Water> leastRecentlyUsedWater {1, RecastMesh::Water {1, btTransform::getIdentity()}};
const RecastMesh leastRecentlyUsedRecastMesh {mIndices, mVertices, mAreaTypes, leastRecentlyUsedWater,
mTrianglesPerChunk};
const auto leastRecentlyUsedData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
NavMeshData leastRecentlyUsedNavMeshData {leastRecentlyUsedData, 1};
const std::vector<RecastMesh::Water> mostRecentlyUsedWater {1, RecastMesh::Water {2, btTransform::getIdentity()}};
const RecastMesh mostRecentlyUsedRecastMesh {mIndices, mVertices, mAreaTypes, mostRecentlyUsedWater,
mTrianglesPerChunk};
const auto mostRecentlyUsedData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
NavMeshData mostRecentlyUsedNavMeshData {mostRecentlyUsedData, 1};
cache.set(mAgentHalfExtents, mTilePosition, leastRecentlyUsedRecastMesh, mOffMeshConnections,
std::move(leastRecentlyUsedNavMeshData));
cache.set(mAgentHalfExtents, mTilePosition, mostRecentlyUsedRecastMesh, mOffMeshConnections,
std::move(mostRecentlyUsedNavMeshData));
{
const auto value = cache.get(mAgentHalfExtents, mTilePosition, leastRecentlyUsedRecastMesh, mOffMeshConnections);
ASSERT_TRUE(value);
ASSERT_EQ(value.get(), (NavMeshDataRef {leastRecentlyUsedData, 1}));
}
{
const auto value = cache.get(mAgentHalfExtents, mTilePosition, mostRecentlyUsedRecastMesh, mOffMeshConnections);
ASSERT_TRUE(value);
ASSERT_EQ(value.get(), (NavMeshDataRef {mostRecentlyUsedData, 1}));
}
const auto result = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections,
std::move(mNavMeshData));
EXPECT_EQ(result.get(), (NavMeshDataRef {mData, 1}));
EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, leastRecentlyUsedRecastMesh, mOffMeshConnections));
EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, mostRecentlyUsedRecastMesh, mOffMeshConnections));
}
TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_not_replace_unused_least_recently_used_value_when_item_does_not_not_fit_cache_max_size)
{
const std::size_t maxSize = 1;
NavMeshTilesCache cache(maxSize);
const std::vector<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}};
const RecastMesh tooLargeRecastMesh {mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk};
const auto tooLargeData = reinterpret_cast<unsigned char*>(dtAlloc(2, DT_ALLOC_PERM));
NavMeshData tooLargeNavMeshData {tooLargeData, 2};
cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData));
EXPECT_FALSE(cache.set(mAgentHalfExtents, mTilePosition, tooLargeRecastMesh, mOffMeshConnections,
std::move(tooLargeNavMeshData)));
EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections));
}
TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_not_replace_unused_least_recently_used_value_when_item_does_not_not_fit_size_of_unused_items)
{
const std::size_t maxSize = 2;
NavMeshTilesCache cache(maxSize);
const std::vector<RecastMesh::Water> anotherWater {1, RecastMesh::Water {1, btTransform::getIdentity()}};
const RecastMesh anotherRecastMesh {mIndices, mVertices, mAreaTypes, anotherWater, mTrianglesPerChunk};
const auto anotherData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
NavMeshData anotherNavMeshData {anotherData, 1};
const std::vector<RecastMesh::Water> tooLargeWater {1, RecastMesh::Water {2, btTransform::getIdentity()}};
const RecastMesh tooLargeRecastMesh {mIndices, mVertices, mAreaTypes, tooLargeWater, mTrianglesPerChunk};
const auto tooLargeData = reinterpret_cast<unsigned char*>(dtAlloc(2, DT_ALLOC_PERM));
NavMeshData tooLargeNavMeshData {tooLargeData, 2};
const auto value = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections,
std::move(mNavMeshData));
ASSERT_TRUE(value);
ASSERT_TRUE(cache.set(mAgentHalfExtents, mTilePosition, anotherRecastMesh, mOffMeshConnections,
std::move(anotherNavMeshData)));
EXPECT_FALSE(cache.set(mAgentHalfExtents, mTilePosition, tooLargeRecastMesh, mOffMeshConnections,
std::move(tooLargeNavMeshData)));
EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections));
EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, anotherRecastMesh, mOffMeshConnections));
}
TEST_F(DetourNavigatorNavMeshTilesCacheTest, release_used_after_set_then_used_by_get_item_should_left_this_item_available)
{
const std::size_t maxSize = 1;
NavMeshTilesCache cache(maxSize);
const std::vector<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}};
const RecastMesh anotherRecastMesh {mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk};
const auto anotherData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
NavMeshData anotherNavMeshData {anotherData, 1};
const auto firstCopy = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData));
ASSERT_TRUE(firstCopy);
{
const auto secondCopy = cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections);
ASSERT_TRUE(secondCopy);
}
EXPECT_FALSE(cache.set(mAgentHalfExtents, mTilePosition, anotherRecastMesh, mOffMeshConnections,
std::move(anotherNavMeshData)));
EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections));
}
TEST_F(DetourNavigatorNavMeshTilesCacheTest, release_twice_used_item_should_left_this_item_available)
{
const std::size_t maxSize = 1;
NavMeshTilesCache cache(maxSize);
const std::vector<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}};
const RecastMesh anotherRecastMesh {mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk};
const auto anotherData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
NavMeshData anotherNavMeshData {anotherData, 1};
cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData));
const auto firstCopy = cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections);
ASSERT_TRUE(firstCopy);
{
const auto secondCopy = cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections);
ASSERT_TRUE(secondCopy);
}
EXPECT_FALSE(cache.set(mAgentHalfExtents, mTilePosition, anotherRecastMesh, mOffMeshConnections,
std::move(anotherNavMeshData)));
EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections));
}
}

@ -56,6 +56,7 @@ namespace
btTriangleMesh mesh; btTriangleMesh mesh;
mesh.addTriangle(btVector3(-1, -1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0)); mesh.addTriangle(btVector3(-1, -1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0));
btBvhTriangleMeshShape shape(&mesh, true); btBvhTriangleMeshShape shape(&mesh, true);
RecastMeshBuilder builder(mSettings, mBounds); RecastMeshBuilder builder(mSettings, mBounds);
builder.addObject(static_cast<const btCollisionShape&>(shape), btTransform::getIdentity(), AreaType_ground); builder.addObject(static_cast<const btCollisionShape&>(shape), btTransform::getIdentity(), AreaType_ground);
const auto recastMesh = builder.create(); const auto recastMesh = builder.create();

@ -268,7 +268,8 @@ namespace
value.target = Nif::ControlledPtr(nullptr); value.target = Nif::ControlledPtr(nullptr);
} }
void copy(const btTransform& src, Nif::Transformation& dst) { void copy(const btTransform& src, Nif::Transformation& dst)
{
dst.pos = osg::Vec3f(src.getOrigin().x(), src.getOrigin().y(), src.getOrigin().z()); dst.pos = osg::Vec3f(src.getOrigin().x(), src.getOrigin().y(), src.getOrigin().z());
for (int row = 0; row < 3; ++row) for (int row = 0; row < 3; ++row)
for (int column = 0; column < 3; ++column) for (int column = 0; column < 3; ++column)

@ -170,6 +170,7 @@ add_component_dir(detournavigator
recastmesh recastmesh
tilecachedrecastmeshmanager tilecachedrecastmeshmanager
recastmeshobject recastmeshobject
navmeshtilescache
) )
set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui

@ -52,6 +52,7 @@ namespace DetourNavigator
, mRecastMeshManager(recastMeshManager) , mRecastMeshManager(recastMeshManager)
, mOffMeshConnectionsManager(offMeshConnectionsManager) , mOffMeshConnectionsManager(offMeshConnectionsManager)
, mShouldStop() , mShouldStop()
, mNavMeshTilesCache(settings.mMaxNavMeshTilesCacheSize)
{ {
for (std::size_t i = 0; i < mSettings.get().mAsyncNavMeshUpdaterThreads; ++i) for (std::size_t i = 0; i < mSettings.get().mAsyncNavMeshUpdaterThreads; ++i)
mThreads.emplace_back([&] { process(); }); mThreads.emplace_back([&] { process(); });
@ -69,7 +70,7 @@ namespace DetourNavigator
} }
void AsyncNavMeshUpdater::post(const osg::Vec3f& agentHalfExtents, void AsyncNavMeshUpdater::post(const osg::Vec3f& agentHalfExtents,
const std::shared_ptr<NavMeshCacheItem>& navMeshCacheItem, const TilePosition& playerTile, const SharedNavMeshCacheItem& navMeshCacheItem, const TilePosition& playerTile,
const std::map<TilePosition, ChangeType>& changedTiles) const std::map<TilePosition, ChangeType>& changedTiles)
{ {
*mPlayerTile.lock() = playerTile; *mPlayerTile.lock() = playerTile;
@ -129,7 +130,7 @@ namespace DetourNavigator
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.mChangedTile, playerTile,
offMeshConnections, mSettings, *job.mNavMeshCacheItem); offMeshConnections, mSettings, job.mNavMeshCacheItem, mNavMeshTilesCache);
const auto finish = std::chrono::steady_clock::now(); const auto finish = std::chrono::steady_clock::now();
@ -137,9 +138,10 @@ namespace DetourNavigator
using FloatMs = std::chrono::duration<float, std::milli>; using FloatMs = std::chrono::duration<float, std::milli>;
const auto locked = job.mNavMeshCacheItem.lockConst();
log("cache updated for agent=", job.mAgentHalfExtents, " status=", status, log("cache updated for agent=", job.mAgentHalfExtents, " status=", status,
" generation=", job.mNavMeshCacheItem->mGeneration, " generation=", locked->getGeneration(),
" revision=", job.mNavMeshCacheItem->mNavMeshRevision, " revision=", locked->getNavMeshRevision(),
" time=", std::chrono::duration_cast<FloatMs>(finish - start).count(), "ms", " time=", std::chrono::duration_cast<FloatMs>(finish - start).count(), "ms",
" total_time=", std::chrono::duration_cast<FloatMs>(finish - firstStart).count(), "ms"); " total_time=", std::chrono::duration_cast<FloatMs>(finish - firstStart).count(), "ms");
} }
@ -151,6 +153,7 @@ namespace DetourNavigator
mHasJob.wait_for(lock, std::chrono::milliseconds(10)); mHasJob.wait_for(lock, std::chrono::milliseconds(10));
if (mJobs.empty()) if (mJobs.empty())
{ {
mFirstStart.lock()->reset();
mDone.notify_all(); mDone.notify_all();
return boost::none; return boost::none;
} }
@ -183,7 +186,7 @@ namespace DetourNavigator
writeToFile(*recastMesh, mSettings.get().mRecastMeshPathPrefix + std::to_string(job.mChangedTile.x()) writeToFile(*recastMesh, mSettings.get().mRecastMeshPathPrefix + std::to_string(job.mChangedTile.x())
+ "_" + std::to_string(job.mChangedTile.y()) + "_", recastMeshRevision); + "_" + std::to_string(job.mChangedTile.y()) + "_", recastMeshRevision);
if (mSettings.get().mEnableWriteNavMeshToFile) if (mSettings.get().mEnableWriteNavMeshToFile)
writeToFile(*job.mNavMeshCacheItem->mValue.lock(), mSettings.get().mNavMeshPathPrefix, navMeshRevision); writeToFile(job.mNavMeshCacheItem.lockConst()->getValue(), mSettings.get().mNavMeshPathPrefix, navMeshRevision);
} }
std::chrono::steady_clock::time_point AsyncNavMeshUpdater::setFirstStart(const std::chrono::steady_clock::time_point& value) std::chrono::steady_clock::time_point AsyncNavMeshUpdater::setFirstStart(const std::chrono::steady_clock::time_point& value)

@ -5,6 +5,7 @@
#include "offmeshconnectionsmanager.hpp" #include "offmeshconnectionsmanager.hpp"
#include "tilecachedrecastmeshmanager.hpp" #include "tilecachedrecastmeshmanager.hpp"
#include "tileposition.hpp" #include "tileposition.hpp"
#include "navmeshtilescache.hpp"
#include <osg/Vec3f> #include <osg/Vec3f>
@ -37,7 +38,7 @@ namespace DetourNavigator
OffMeshConnectionsManager& offMeshConnectionsManager); OffMeshConnectionsManager& offMeshConnectionsManager);
~AsyncNavMeshUpdater(); ~AsyncNavMeshUpdater();
void post(const osg::Vec3f& agentHalfExtents, const std::shared_ptr<NavMeshCacheItem>& mNavMeshCacheItem, void post(const osg::Vec3f& agentHalfExtents, const SharedNavMeshCacheItem& mNavMeshCacheItem,
const TilePosition& playerTile, const std::map<TilePosition, ChangeType>& changedTiles); const TilePosition& playerTile, const std::map<TilePosition, ChangeType>& changedTiles);
void wait(); void wait();
@ -46,7 +47,7 @@ namespace DetourNavigator
struct Job struct Job
{ {
osg::Vec3f mAgentHalfExtents; osg::Vec3f mAgentHalfExtents;
std::shared_ptr<NavMeshCacheItem> mNavMeshCacheItem; SharedNavMeshCacheItem mNavMeshCacheItem;
TilePosition mChangedTile; TilePosition mChangedTile;
std::tuple<ChangeType, int, int> mPriority; std::tuple<ChangeType, int, int> mPriority;
@ -69,6 +70,7 @@ namespace DetourNavigator
std::map<osg::Vec3f, std::set<TilePosition>> mPushed; std::map<osg::Vec3f, std::set<TilePosition>> mPushed;
Misc::ScopeGuarded<TilePosition> mPlayerTile; Misc::ScopeGuarded<TilePosition> mPlayerTile;
Misc::ScopeGuarded<boost::optional<std::chrono::steady_clock::time_point>> mFirstStart; Misc::ScopeGuarded<boost::optional<std::chrono::steady_clock::time_point>> mFirstStart;
NavMeshTilesCache mNavMeshTilesCache;
std::vector<std::thread> mThreads; std::vector<std::thread> mThreads;
void process() throw(); void process() throw();

@ -9,6 +9,7 @@
#include "sharednavmesh.hpp" #include "sharednavmesh.hpp"
#include "settingsutils.hpp" #include "settingsutils.hpp"
#include "flags.hpp" #include "flags.hpp"
#include "navmeshtilescache.hpp"
#include <DetourNavMesh.h> #include <DetourNavMesh.h>
#include <DetourNavMeshBuilder.h> #include <DetourNavMeshBuilder.h>
@ -23,6 +24,8 @@ namespace
{ {
using namespace DetourNavigator; using namespace DetourNavigator;
static const int doNotTransferOwnership = 0;
void initPolyMeshDetail(rcPolyMeshDetail& value) void initPolyMeshDetail(rcPolyMeshDetail& value)
{ {
value.meshes = nullptr; value.meshes = nullptr;
@ -42,29 +45,6 @@ namespace
using PolyMeshDetailStackPtr = std::unique_ptr<rcPolyMeshDetail, PolyMeshDetailStackDeleter>; using PolyMeshDetailStackPtr = std::unique_ptr<rcPolyMeshDetail, PolyMeshDetailStackDeleter>;
struct NavMeshDataValueDeleter
{
void operator ()(unsigned char* value) const
{
dtFree(value);
}
};
using NavMeshDataValue = std::unique_ptr<unsigned char, NavMeshDataValueDeleter>;
struct NavMeshData
{
NavMeshDataValue mValue;
int mSize;
NavMeshData() = default;
NavMeshData(unsigned char* value, int size)
: mValue(value)
, mSize(size)
{}
};
osg::Vec3f makeOsgVec3f(const btVector3& value) osg::Vec3f makeOsgVec3f(const btVector3& value)
{ {
return osg::Vec3f(value.x(), value.y(), value.z()); return osg::Vec3f(value.x(), value.y(), value.z());
@ -388,7 +368,7 @@ 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 TilePosition& changedTile, const TilePosition& playerTile,
const std::vector<OffMeshConnection>& offMeshConnections, const Settings& settings, const std::vector<OffMeshConnection>& offMeshConnections, const Settings& settings,
NavMeshCacheItem& navMeshCacheItem) const SharedNavMeshCacheItem& navMeshCacheItem, NavMeshTilesCache& navMeshTilesCache)
{ {
log("update NavMesh with mutiple tiles:", log("update NavMesh with mutiple tiles:",
" agentHeight=", std::setprecision(std::numeric_limits<float>::max_exponent10), " agentHeight=", std::setprecision(std::numeric_limits<float>::max_exponent10),
@ -401,17 +381,19 @@ namespace DetourNavigator
" playerTile=", playerTile, " playerTile=", playerTile,
" changedTileDistance=", getDistance(changedTile, playerTile)); " changedTileDistance=", getDistance(changedTile, playerTile));
auto& navMesh = navMeshCacheItem.mValue; const auto params = *navMeshCacheItem.lockConst()->getValue().getParams();
const auto& params = *navMesh.lock()->getParams();
const osg::Vec3f origin(params.orig[0], params.orig[1], params.orig[2]); const osg::Vec3f origin(params.orig[0], params.orig[1], params.orig[2]);
const auto x = changedTile.x(); const auto x = changedTile.x();
const auto y = changedTile.y(); const auto y = changedTile.y();
const auto removeTile = [&] { const auto removeTile = [&] {
const auto locked = navMesh.lock(); const auto locked = navMeshCacheItem.lock();
const auto removed = dtStatusSucceed(locked->removeTile(locked->getTileRefAt(x, y, 0), nullptr, nullptr)); auto& navMesh = locked->getValue();
navMeshCacheItem.mNavMeshRevision += removed; const auto tileRef = navMesh.getTileRefAt(x, y, 0);
const auto removed = dtStatusSucceed(navMesh.removeTile(tileRef, nullptr, nullptr));
if (removed)
locked->removeUsedTile(changedTile);
return makeUpdateNavMeshStatus(removed, false); return makeUpdateNavMeshStatus(removed, false);
}; };
@ -437,42 +419,82 @@ namespace DetourNavigator
return removeTile(); return removeTile();
} }
const auto maxTiles = navMesh.lock()->getParams()->maxTiles; if (!shouldAddTile(changedTile, playerTile, params.maxTiles))
if (!shouldAddTile(changedTile, playerTile, maxTiles))
{ {
log("ignore add tile: too far from player"); log("ignore add tile: too far from player");
return removeTile(); return removeTile();
} }
const auto tileBounds = makeTileBounds(settings, changedTile); auto cachedNavMeshData = navMeshTilesCache.get(agentHalfExtents, changedTile, *recastMesh, offMeshConnections);
const osg::Vec3f tileBorderMin(tileBounds.mMin.x(), boundsMin.y() - 1, tileBounds.mMin.y());
const osg::Vec3f tileBorderMax(tileBounds.mMax.x(), boundsMax.y() + 1, tileBounds.mMax.y());
auto navMeshData = makeNavMeshTileData(agentHalfExtents, *recastMesh, offMeshConnections, x, y,
tileBorderMin, tileBorderMax, settings);
if (!navMeshData.mValue) if (!cachedNavMeshData)
{ {
log("ignore add tile: NavMeshData is null"); const auto tileBounds = makeTileBounds(settings, changedTile);
return removeTile(); const osg::Vec3f tileBorderMin(tileBounds.mMin.x(), boundsMin.y() - 1, tileBounds.mMin.y());
} const osg::Vec3f tileBorderMax(tileBounds.mMax.x(), boundsMax.y() + 1, tileBounds.mMax.y());
dtStatus addStatus; auto navMeshData = makeNavMeshTileData(agentHalfExtents, *recastMesh, offMeshConnections, x, y,
bool removed; tileBorderMin, tileBorderMax, settings);
{
const auto locked = navMesh.lock(); if (!navMeshData.mValue)
removed = dtStatusSucceed(locked->removeTile(locked->getTileRefAt(x, y, 0), nullptr, nullptr)); {
addStatus = locked->addTile(navMeshData.mValue.get(), navMeshData.mSize, DT_TILE_FREE_DATA, 0, 0); log("ignore add tile: NavMeshData is null");
return removeTile();
}
try
{
cachedNavMeshData = navMeshTilesCache.set(agentHalfExtents, changedTile, *recastMesh,
offMeshConnections, std::move(navMeshData));
}
catch (const InvalidArgument&)
{
cachedNavMeshData = navMeshTilesCache.get(agentHalfExtents, changedTile, *recastMesh,
offMeshConnections);
}
if (!cachedNavMeshData)
{
log("cache overflow");
const auto locked = navMeshCacheItem.lock();
auto& navMesh = locked->getValue();
const auto tileRef = navMesh.getTileRefAt(x, y, 0);
const auto removed = dtStatusSucceed(navMesh.removeTile(tileRef, nullptr, nullptr));
const auto addStatus = navMesh.addTile(navMeshData.mValue.get(), navMeshData.mSize,
doNotTransferOwnership, 0, 0);
if (dtStatusSucceed(addStatus))
{
locked->setUsedTile(changedTile, std::move(navMeshData));
return makeUpdateNavMeshStatus(removed, true);
}
else
{
if (removed)
locked->removeUsedTile(changedTile);
log("failed to add tile with status=", WriteDtStatus {addStatus});
return makeUpdateNavMeshStatus(removed, false);
}
}
} }
const auto locked = navMeshCacheItem.lock();
auto& navMesh = locked->getValue();
const auto tileRef = navMesh.getTileRefAt(x, y, 0);
const auto removed = dtStatusSucceed(navMesh.removeTile(tileRef, nullptr, nullptr));
const auto addStatus = navMesh.addTile(cachedNavMeshData.get().mValue, cachedNavMeshData.get().mSize,
doNotTransferOwnership, 0, 0);
if (dtStatusSucceed(addStatus)) if (dtStatusSucceed(addStatus))
{ {
++navMeshCacheItem.mNavMeshRevision; locked->setUsedTile(changedTile, std::move(cachedNavMeshData));
navMeshData.mValue.release();
return makeUpdateNavMeshStatus(removed, true); return makeUpdateNavMeshStatus(removed, true);
} }
else else
{ {
if (removed)
locked->removeUsedTile(changedTile);
log("failed to add tile with status=", WriteDtStatus {addStatus}); log("failed to add tile with status=", WriteDtStatus {addStatus});
return makeUpdateNavMeshStatus(removed, false); return makeUpdateNavMeshStatus(removed, false);
} }

@ -7,6 +7,7 @@
#include "tileposition.hpp" #include "tileposition.hpp"
#include "tilebounds.hpp" #include "tilebounds.hpp"
#include "sharednavmesh.hpp" #include "sharednavmesh.hpp"
#include "navmeshtilescache.hpp"
#include <osg/Vec3f> #include <osg/Vec3f>
@ -48,7 +49,7 @@ 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 TilePosition& changedTile, const TilePosition& playerTile,
const std::vector<OffMeshConnection>& offMeshConnections, const Settings& settings, const std::vector<OffMeshConnection>& offMeshConnections, const Settings& settings,
NavMeshCacheItem& navMeshCacheItem); const SharedNavMeshCacheItem& navMeshCacheItem, NavMeshTilesCache& navMeshTilesCache);
} }
#endif #endif

@ -122,7 +122,7 @@ namespace DetourNavigator
mNavMeshManager.wait(); mNavMeshManager.wait();
} }
std::map<osg::Vec3f, std::shared_ptr<NavMeshCacheItem>> Navigator::getNavMeshes() const std::map<osg::Vec3f, SharedNavMeshCacheItem> Navigator::getNavMeshes() const
{ {
return mNavMeshManager.getNavMeshes(); return mNavMeshManager.getNavMeshes();
} }

@ -176,7 +176,7 @@ namespace DetourNavigator
"out is not an OutputIterator" "out is not an OutputIterator"
); );
const auto navMesh = mNavMeshManager.getNavMesh(agentHalfExtents); const auto navMesh = mNavMeshManager.getNavMesh(agentHalfExtents);
return findSmoothPath(*navMesh.lock(), toNavMeshCoordinates(mSettings, agentHalfExtents), return findSmoothPath(navMesh.lock()->getValue(), toNavMeshCoordinates(mSettings, agentHalfExtents),
toNavMeshCoordinates(mSettings, start), toNavMeshCoordinates(mSettings, end), includeFlags, toNavMeshCoordinates(mSettings, start), toNavMeshCoordinates(mSettings, end), includeFlags,
mSettings, out); mSettings, out);
} }
@ -185,7 +185,7 @@ namespace DetourNavigator
* @brief getNavMeshes returns all current navmeshes * @brief getNavMeshes returns all current navmeshes
* @return map of agent half extents to navmesh * @return map of agent half extents to navmesh
*/ */
std::map<osg::Vec3f, std::shared_ptr<NavMeshCacheItem>> getNavMeshes() const; std::map<osg::Vec3f, SharedNavMeshCacheItem> getNavMeshes() const;
const Settings& getSettings() const; const Settings& getSettings() const;

@ -2,20 +2,69 @@
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHCACHEITEM_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHCACHEITEM_H
#include "sharednavmesh.hpp" #include "sharednavmesh.hpp"
#include "tileposition.hpp"
#include "navmeshtilescache.hpp"
#include <atomic> #include <components/misc/guarded.hpp>
#include <map>
namespace DetourNavigator namespace DetourNavigator
{ {
struct NavMeshCacheItem class NavMeshCacheItem
{ {
SharedNavMesh mValue; public:
std::size_t mGeneration;
std::atomic_size_t mNavMeshRevision;
NavMeshCacheItem(const NavMeshPtr& value, std::size_t generation) NavMeshCacheItem(const NavMeshPtr& value, std::size_t generation)
: mValue(value), mGeneration(generation), mNavMeshRevision(0) {} : mValue(value), mGeneration(generation), mNavMeshRevision(0)
{
}
const dtNavMesh& getValue() const
{
return *mValue;
}
dtNavMesh& getValue()
{
return *mValue;
}
std::size_t getGeneration() const
{
return mGeneration;
}
std::size_t getNavMeshRevision() const
{
return mNavMeshRevision;
}
void setUsedTile(const TilePosition& tilePosition, NavMeshTilesCache::Value value)
{
mUsedTiles[tilePosition] = std::make_pair(std::move(value), NavMeshData());
++mNavMeshRevision;
}
void setUsedTile(const TilePosition& tilePosition, NavMeshData value)
{
mUsedTiles[tilePosition] = std::make_pair(NavMeshTilesCache::Value(), std::move(value));
++mNavMeshRevision;
}
void removeUsedTile(const TilePosition& tilePosition)
{
mUsedTiles.erase(tilePosition);
++mNavMeshRevision;
}
private:
NavMeshPtr mValue;
std::size_t mGeneration;
std::size_t mNavMeshRevision;
std::map<TilePosition, std::pair<NavMeshTilesCache::Value, NavMeshData>> mUsedTiles;
}; };
using SharedNavMeshCacheItem = Misc::SharedGuarded<NavMeshCacheItem>;
} }
#endif #endif

@ -0,0 +1,35 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHDATA_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHDATA_H
#include <DetourAlloc.h>
#include <algorithm>
#include <memory>
namespace DetourNavigator
{
struct NavMeshDataValueDeleter
{
void operator ()(unsigned char* value) const
{
dtFree(value);
}
};
using NavMeshDataValue = std::unique_ptr<unsigned char, NavMeshDataValueDeleter>;
struct NavMeshData
{
NavMeshDataValue mValue;
int mSize;
NavMeshData() = default;
NavMeshData(unsigned char* value, int size)
: mValue(value)
, mSize(size)
{}
};
}
#endif

@ -136,11 +136,12 @@ namespace DetourNavigator
const auto& cached = getCached(agentHalfExtents); const auto& cached = getCached(agentHalfExtents);
const auto changedTiles = mChangedTiles.find(agentHalfExtents); const auto changedTiles = mChangedTiles.find(agentHalfExtents);
{ {
const auto locked = cached->mValue.lock(); const auto locked = cached.lock();
const auto& navMesh = locked->getValue();
if (changedTiles != mChangedTiles.end()) if (changedTiles != mChangedTiles.end())
{ {
for (const auto& tile : changedTiles->second) for (const auto& tile : changedTiles->second)
if (locked->getTileAt(tile.first.x(), tile.first.y(), 0)) if (navMesh.getTileAt(tile.first.x(), tile.first.y(), 0))
{ {
auto tileToPost = tilesToPost.find(tile.first); auto tileToPost = tilesToPost.find(tile.first);
if (tileToPost == tilesToPost.end()) if (tileToPost == tilesToPost.end())
@ -153,13 +154,13 @@ namespace DetourNavigator
if (changedTiles->second.empty()) if (changedTiles->second.empty())
mChangedTiles.erase(changedTiles); mChangedTiles.erase(changedTiles);
} }
const auto maxTiles = locked->getParams()->maxTiles; const auto maxTiles = navMesh.getParams()->maxTiles;
mRecastMeshManager.forEachTilePosition([&] (const TilePosition& tile) mRecastMeshManager.forEachTilePosition([&] (const TilePosition& tile)
{ {
if (tilesToPost.count(tile)) if (tilesToPost.count(tile))
return; return;
const auto shouldAdd = shouldAddTile(tile, playerTile, maxTiles); const auto shouldAdd = shouldAddTile(tile, playerTile, maxTiles);
const auto presentInNavMesh = bool(locked->getTileAt(tile.x(), tile.y(), 0)); const auto presentInNavMesh = bool(navMesh.getTileAt(tile.x(), tile.y(), 0));
if (shouldAdd && !presentInNavMesh) if (shouldAdd && !presentInNavMesh)
tilesToPost.insert(std::make_pair(tile, ChangeType::add)); tilesToPost.insert(std::make_pair(tile, ChangeType::add));
else if (!shouldAdd && presentInNavMesh) else if (!shouldAdd && presentInNavMesh)
@ -169,8 +170,7 @@ namespace DetourNavigator
mAsyncNavMeshUpdater.post(agentHalfExtents, cached, playerTile, tilesToPost); mAsyncNavMeshUpdater.post(agentHalfExtents, cached, playerTile, tilesToPost);
log("cache update posted for agent=", agentHalfExtents, log("cache update posted for agent=", agentHalfExtents,
" playerTile=", lastPlayerTile->second, " playerTile=", lastPlayerTile->second,
" recastMeshManagerRevision=", lastRevision, " recastMeshManagerRevision=", lastRevision);
" changedTiles=", changedTiles->second.size());
} }
void NavMeshManager::wait() void NavMeshManager::wait()
@ -178,12 +178,12 @@ namespace DetourNavigator
mAsyncNavMeshUpdater.wait(); mAsyncNavMeshUpdater.wait();
} }
SharedNavMesh NavMeshManager::getNavMesh(const osg::Vec3f& agentHalfExtents) const SharedNavMeshCacheItem NavMeshManager::getNavMesh(const osg::Vec3f& agentHalfExtents) const
{ {
return getCached(agentHalfExtents)->mValue; return getCached(agentHalfExtents);
} }
std::map<osg::Vec3f, std::shared_ptr<NavMeshCacheItem>> NavMeshManager::getNavMeshes() const std::map<osg::Vec3f, SharedNavMeshCacheItem> NavMeshManager::getNavMeshes() const
{ {
return mCache; return mCache;
} }
@ -209,19 +209,16 @@ namespace DetourNavigator
{ {
for (const auto& cached : mCache) for (const auto& cached : mCache)
{ {
if (cached.second) auto& tiles = mChangedTiles[cached.first];
{ auto tile = tiles.find(tilePosition);
auto& tiles = mChangedTiles[cached.first]; if (tile == tiles.end())
auto tile = tiles.find(tilePosition); tiles.insert(std::make_pair(tilePosition, changeType));
if (tile == tiles.end()) else
tiles.insert(std::make_pair(tilePosition, changeType)); tile->second = addChangeType(tile->second, changeType);
else
tile->second = addChangeType(tile->second, changeType);
}
} }
} }
const std::shared_ptr<NavMeshCacheItem>& NavMeshManager::getCached(const osg::Vec3f& agentHalfExtents) const const SharedNavMeshCacheItem& NavMeshManager::getCached(const osg::Vec3f& agentHalfExtents) const
{ {
const auto cached = mCache.find(agentHalfExtents); const auto cached = mCache.find(agentHalfExtents);
if (cached != mCache.end()) if (cached != mCache.end())

@ -46,17 +46,17 @@ namespace DetourNavigator
void wait(); void wait();
SharedNavMesh getNavMesh(const osg::Vec3f& agentHalfExtents) const; SharedNavMeshCacheItem getNavMesh(const osg::Vec3f& agentHalfExtents) const;
std::map<osg::Vec3f, std::shared_ptr<NavMeshCacheItem>> getNavMeshes() const; std::map<osg::Vec3f, SharedNavMeshCacheItem> getNavMeshes() const;
private: private:
const Settings& mSettings; const Settings& mSettings;
TileCachedRecastMeshManager mRecastMeshManager; TileCachedRecastMeshManager mRecastMeshManager;
OffMeshConnectionsManager mOffMeshConnectionsManager; OffMeshConnectionsManager mOffMeshConnectionsManager;
std::map<osg::Vec3f, std::shared_ptr<NavMeshCacheItem>> mCache;
std::map<osg::Vec3f, std::map<TilePosition, ChangeType>> mChangedTiles;
AsyncNavMeshUpdater mAsyncNavMeshUpdater; AsyncNavMeshUpdater mAsyncNavMeshUpdater;
std::map<osg::Vec3f, SharedNavMeshCacheItem> mCache;
std::map<osg::Vec3f, std::map<TilePosition, ChangeType>> mChangedTiles;
std::size_t mGenerationCounter = 0; std::size_t mGenerationCounter = 0;
std::map<osg::Vec3f, TilePosition> mPlayerTile; std::map<osg::Vec3f, TilePosition> mPlayerTile;
std::map<osg::Vec3f, std::size_t> mLastRecastMeshManagerRevision; std::map<osg::Vec3f, std::size_t> mLastRecastMeshManagerRevision;
@ -67,7 +67,7 @@ namespace DetourNavigator
void addChangedTile(const TilePosition& tilePosition, const ChangeType changeType); void addChangedTile(const TilePosition& tilePosition, const ChangeType changeType);
const std::shared_ptr<NavMeshCacheItem>& getCached(const osg::Vec3f& agentHalfExtents) const; const SharedNavMeshCacheItem& getCached(const osg::Vec3f& agentHalfExtents) const;
}; };
} }

@ -0,0 +1,158 @@
#include "navmeshtilescache.hpp"
#include "exceptions.hpp"
namespace DetourNavigator
{
namespace
{
inline std::string makeNavMeshKey(const RecastMesh& recastMesh,
const std::vector<OffMeshConnection>& offMeshConnections)
{
std::string result;
result.reserve(
recastMesh.getIndices().size() * sizeof(int)
+ recastMesh.getVertices().size() * sizeof(float)
+ recastMesh.getAreaTypes().size() * sizeof(AreaType)
+ recastMesh.getWater().size() * sizeof(RecastMesh::Water)
+ offMeshConnections.size() * sizeof(OffMeshConnection)
);
std::copy(
reinterpret_cast<const char*>(recastMesh.getIndices().data()),
reinterpret_cast<const char*>(recastMesh.getIndices().data() + recastMesh.getIndices().size()),
std::back_inserter(result)
);
std::copy(
reinterpret_cast<const char*>(recastMesh.getVertices().data()),
reinterpret_cast<const char*>(recastMesh.getVertices().data() + recastMesh.getVertices().size()),
std::back_inserter(result)
);
std::copy(
reinterpret_cast<const char*>(recastMesh.getAreaTypes().data()),
reinterpret_cast<const char*>(recastMesh.getAreaTypes().data() + recastMesh.getAreaTypes().size()),
std::back_inserter(result)
);
std::copy(
reinterpret_cast<const char*>(recastMesh.getWater().data()),
reinterpret_cast<const char*>(recastMesh.getWater().data() + recastMesh.getWater().size()),
std::back_inserter(result)
);
std::copy(
reinterpret_cast<const char*>(offMeshConnections.data()),
reinterpret_cast<const char*>(offMeshConnections.data() + offMeshConnections.size()),
std::back_inserter(result)
);
return result;
}
}
NavMeshTilesCache::NavMeshTilesCache(const std::size_t maxNavMeshDataSize)
: mMaxNavMeshDataSize(maxNavMeshDataSize), mUsedNavMeshDataSize(0), mFreeNavMeshDataSize(0) {}
NavMeshTilesCache::Value NavMeshTilesCache::get(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile,
const RecastMesh& recastMesh, const std::vector<OffMeshConnection>& offMeshConnections)
{
const std::lock_guard<std::mutex> lock(mMutex);
const auto agentValues = mValues.find(agentHalfExtents);
if (agentValues == mValues.end())
return Value();
const auto tileValues = agentValues->second.find(changedTile);
if (tileValues == agentValues->second.end())
return Value();
const auto tile = tileValues->second.find(makeNavMeshKey(recastMesh, offMeshConnections));
if (tile == tileValues->second.end())
return Value();
acquireItemUnsafe(tile->second);
return Value(*this, tile->second);
}
NavMeshTilesCache::Value NavMeshTilesCache::set(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile,
const RecastMesh& recastMesh, const std::vector<OffMeshConnection>& offMeshConnections,
NavMeshData value)
{
const auto navMeshSize = static_cast<std::size_t>(value.mSize);
const std::lock_guard<std::mutex> lock(mMutex);
if (navMeshSize > mMaxNavMeshDataSize)
return Value();
if (navMeshSize > mFreeNavMeshDataSize + (mMaxNavMeshDataSize - mUsedNavMeshDataSize))
return Value();
while (!mFreeItems.empty() && mUsedNavMeshDataSize + navMeshSize > mMaxNavMeshDataSize)
removeLeastRecentlyUsed();
const auto navMeshKey = makeNavMeshKey(recastMesh, offMeshConnections);
const auto iterator = mFreeItems.emplace(mFreeItems.end(), agentHalfExtents, changedTile, navMeshKey);
const auto emplaced = mValues[agentHalfExtents][changedTile].emplace(navMeshKey, iterator);
if (!emplaced.second)
{
mFreeItems.erase(iterator);
throw InvalidArgument("Set existing cache value");
}
iterator->mNavMeshData = std::move(value);
mUsedNavMeshDataSize += navMeshSize;
mFreeNavMeshDataSize += navMeshSize;
acquireItemUnsafe(iterator);
return Value(*this, iterator);
}
void NavMeshTilesCache::removeLeastRecentlyUsed()
{
const auto& item = mFreeItems.back();
const auto agentValues = mValues.find(item.mAgentHalfExtents);
if (agentValues == mValues.end())
return;
const auto tileValues = agentValues->second.find(item.mChangedTile);
if (tileValues == agentValues->second.end())
return;
const auto value = tileValues->second.find(item.mNavMeshKey);
if (value == tileValues->second.end())
return;
mUsedNavMeshDataSize -= static_cast<std::size_t>(item.mNavMeshData.mSize);
mFreeItems.pop_back();
tileValues->second.erase(value);
if (!tileValues->second.empty())
return;
agentValues->second.erase(tileValues);
if (!agentValues->second.empty())
return;
mValues.erase(agentValues);
}
void NavMeshTilesCache::acquireItemUnsafe(ItemIterator iterator)
{
if (++iterator->mUseCount > 1)
return;
mBusyItems.splice(mBusyItems.end(), mFreeItems, iterator);
mFreeNavMeshDataSize -= static_cast<std::size_t>(iterator->mNavMeshData.mSize);
}
void NavMeshTilesCache::releaseItem(ItemIterator iterator)
{
if (--iterator->mUseCount > 0)
return;
const std::lock_guard<std::mutex> lock(mMutex);
mFreeItems.splice(mFreeItems.begin(), mBusyItems, iterator);
mFreeNavMeshDataSize += static_cast<std::size_t>(iterator->mNavMeshData.mSize);
}
}

@ -0,0 +1,127 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHTILESCACHE_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHTILESCACHE_H
#include "offmeshconnection.hpp"
#include "navmeshdata.hpp"
#include "recastmesh.hpp"
#include "tileposition.hpp"
#include <atomic>
#include <map>
#include <list>
#include <mutex>
namespace DetourNavigator
{
struct NavMeshDataRef
{
unsigned char* mValue;
int mSize;
};
class NavMeshTilesCache
{
public:
struct Item
{
std::atomic<std::int64_t> mUseCount;
osg::Vec3f mAgentHalfExtents;
TilePosition mChangedTile;
std::string mNavMeshKey;
NavMeshData mNavMeshData;
Item(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile, std::string navMeshKey)
: mUseCount(0)
, mAgentHalfExtents(agentHalfExtents)
, mChangedTile(changedTile)
, mNavMeshKey(std::move(navMeshKey))
{}
};
using ItemIterator = std::list<Item>::iterator;
class Value
{
public:
Value()
: mOwner(nullptr), mIterator() {}
Value(NavMeshTilesCache& owner, ItemIterator iterator)
: mOwner(&owner), mIterator(iterator)
{
}
Value(const Value& other) = delete;
Value(Value&& other)
: mOwner(other.mOwner), mIterator(other.mIterator)
{
other.mIterator = ItemIterator();
}
~Value()
{
if (mIterator != ItemIterator())
mOwner->releaseItem(mIterator);
}
Value& operator =(const Value& other) = delete;
Value& operator =(Value&& other)
{
if (mIterator == other.mIterator)
return *this;
if (mIterator != ItemIterator())
mOwner->releaseItem(mIterator);
mOwner = other.mOwner;
mIterator = other.mIterator;
other.mIterator = ItemIterator();
return *this;
}
NavMeshDataRef get() const
{
return NavMeshDataRef {mIterator->mNavMeshData.mValue.get(), mIterator->mNavMeshData.mSize};
}
operator bool() const
{
return mIterator != ItemIterator();
}
private:
NavMeshTilesCache* mOwner;
ItemIterator mIterator;
};
NavMeshTilesCache(const std::size_t maxNavMeshDataSize);
Value get(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile,
const RecastMesh& recastMesh, const std::vector<OffMeshConnection>& offMeshConnections);
Value set(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile,
const RecastMesh& recastMesh, const std::vector<OffMeshConnection>& offMeshConnections,
NavMeshData value);
private:
std::mutex mMutex;
std::size_t mMaxNavMeshDataSize;
std::size_t mUsedNavMeshDataSize;
std::size_t mFreeNavMeshDataSize;
std::list<Item> mBusyItems;
std::list<Item> mFreeItems;
std::map<osg::Vec3f, std::map<TilePosition, std::map<std::string, ItemIterator>>> mValues;
void removeLeastRecentlyUsed();
void acquireItemUnsafe(ItemIterator iterator);
void releaseItem(ItemIterator iterator);
};
}
#endif

@ -10,7 +10,7 @@ namespace DetourNavigator
{ {
public: public:
template <class T> template <class T>
explicit ObjectId(const T* value) throw() explicit ObjectId(const T value) throw()
: mValue(reinterpret_cast<std::size_t>(value)) : mValue(reinterpret_cast<std::size_t>(value))
{ {
} }

@ -0,0 +1,15 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_OFFMESHCONNECTION_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_OFFMESHCONNECTION_H
#include <osg/Vec3f>
namespace DetourNavigator
{
struct OffMeshConnection
{
osg::Vec3f mStart;
osg::Vec3f mEnd;
};
}
#endif

@ -5,6 +5,7 @@
#include "settingsutils.hpp" #include "settingsutils.hpp"
#include "tileposition.hpp" #include "tileposition.hpp"
#include "objectid.hpp" #include "objectid.hpp"
#include "offmeshconnection.hpp"
#include <components/misc/guarded.hpp> #include <components/misc/guarded.hpp>
@ -20,12 +21,6 @@
namespace DetourNavigator namespace DetourNavigator
{ {
struct OffMeshConnection
{
osg::Vec3f mStart;
osg::Vec3f mEnd;
};
class OffMeshConnectionsManager class OffMeshConnectionsManager
{ {
public: public:

@ -1,18 +1,17 @@
#include "recastmesh.hpp" #include "recastmesh.hpp"
#include "settings.hpp"
#include "exceptions.hpp" #include "exceptions.hpp"
#include <Recast.h> #include <Recast.h>
namespace DetourNavigator namespace DetourNavigator
{ {
RecastMesh::RecastMesh(std::vector<int> indices, std::vector<float> vertices, RecastMesh::RecastMesh(std::vector<int> indices, std::vector<float> vertices, std::vector<AreaType> areaTypes,
std::vector<AreaType> areaTypes, std::vector<Water> water, const Settings& settings) std::vector<Water> water, const std::size_t trianglesPerChunk)
: mIndices(std::move(indices)) : mIndices(std::move(indices))
, mVertices(std::move(vertices)) , mVertices(std::move(vertices))
, mAreaTypes(std::move(areaTypes)) , mAreaTypes(std::move(areaTypes))
, mWater(std::move(water)) , mWater(std::move(water))
, mChunkyTriMesh(mVertices, mIndices, mAreaTypes, settings.mTrianglesPerChunk) , mChunkyTriMesh(mVertices, mIndices, mAreaTypes, trianglesPerChunk)
{ {
if (getTrianglesCount() != mAreaTypes.size()) if (getTrianglesCount() != mAreaTypes.size())
throw InvalidArgument("number of flags doesn't match number of triangles: triangles=" throw InvalidArgument("number of flags doesn't match number of triangles: triangles="

@ -5,6 +5,7 @@
#include "chunkytrimesh.hpp" #include "chunkytrimesh.hpp"
#include <memory> #include <memory>
#include <string>
#include <vector> #include <vector>
#include <osg/Vec3f> #include <osg/Vec3f>
@ -13,8 +14,6 @@
namespace DetourNavigator namespace DetourNavigator
{ {
struct Settings;
class RecastMesh class RecastMesh
{ {
public: public:
@ -24,8 +23,8 @@ namespace DetourNavigator
btTransform mTransform; btTransform mTransform;
}; };
RecastMesh(std::vector<int> indices, std::vector<float> vertices, RecastMesh(std::vector<int> indices, std::vector<float> vertices, std::vector<AreaType> areaTypes,
std::vector<AreaType> areaTypes, std::vector<Water> water, const Settings& settings); std::vector<Water> water, const std::size_t trianglesPerChunk);
const std::vector<int>& getIndices() const const std::vector<int>& getIndices() const
{ {

@ -119,7 +119,7 @@ namespace DetourNavigator
std::shared_ptr<RecastMesh> RecastMeshBuilder::create() const std::shared_ptr<RecastMesh> RecastMeshBuilder::create() const
{ {
return std::make_shared<RecastMesh>(mIndices, mVertices, mAreaTypes, mWater, mSettings); return std::make_shared<RecastMesh>(mIndices, mVertices, mAreaTypes, mWater, mSettings.get().mTrianglesPerChunk);
} }
void RecastMeshBuilder::reset() void RecastMeshBuilder::reset()

@ -6,9 +6,6 @@
#include <LinearMath/btTransform.h> #include <LinearMath/btTransform.h>
#include <osg/Vec2f>
#include <osg/Vec2i>
class btBoxShape; class btBoxShape;
class btCollisionShape; class btCollisionShape;
class btCompoundShape; class btCompoundShape;
@ -18,6 +15,8 @@ class btTriangleCallback;
namespace DetourNavigator namespace DetourNavigator
{ {
struct Settings;
class RecastMeshBuilder class RecastMeshBuilder
{ {
public: public:

@ -14,8 +14,12 @@ namespace DetourNavigator
bool RecastMeshManager::addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, bool RecastMeshManager::addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform,
const AreaType areaType) const AreaType areaType)
{ {
if (!mObjects.emplace(id, RecastMeshObject(shape, transform, areaType)).second) const auto iterator = mObjectsOrder.emplace(mObjectsOrder.end(), RecastMeshObject(shape, transform, areaType));
if (!mObjects.emplace(id, iterator).second)
{
mObjectsOrder.erase(iterator);
return false; return false;
}
mShouldRebuild = true; mShouldRebuild = true;
return mShouldRebuild; return mShouldRebuild;
} }
@ -25,7 +29,7 @@ namespace DetourNavigator
const auto object = mObjects.find(id); const auto object = mObjects.find(id);
if (object == mObjects.end()) if (object == mObjects.end())
return false; return false;
if (!object->second.update(transform, areaType)) if (!object->second->update(transform, areaType))
return false; return false;
mShouldRebuild = true; mShouldRebuild = true;
return mShouldRebuild; return mShouldRebuild;
@ -36,7 +40,8 @@ namespace DetourNavigator
const auto object = mObjects.find(id); const auto object = mObjects.find(id);
if (object == mObjects.end()) if (object == mObjects.end())
return boost::none; return boost::none;
const RemovedRecastMeshObject result {object->second.getShape(), object->second.getTransform()}; const RemovedRecastMeshObject result {object->second->getShape(), object->second->getTransform()};
mObjectsOrder.erase(object->second);
mObjects.erase(object); mObjects.erase(object);
mShouldRebuild = true; mShouldRebuild = true;
return result; return result;
@ -45,8 +50,12 @@ namespace DetourNavigator
bool RecastMeshManager::addWater(const osg::Vec2i& cellPosition, const int cellSize, bool RecastMeshManager::addWater(const osg::Vec2i& cellPosition, const int cellSize,
const btTransform& transform) const btTransform& transform)
{ {
if (!mWater.insert(std::make_pair(cellPosition, Water {cellSize, transform})).second) const auto iterator = mWaterOrder.emplace(mWaterOrder.end(), Water {cellSize, transform});
if (!mWater.emplace(cellPosition, iterator).second)
{
mWaterOrder.erase(iterator);
return false; return false;
}
mShouldRebuild = true; mShouldRebuild = true;
return true; return true;
} }
@ -57,7 +66,8 @@ namespace DetourNavigator
if (water == mWater.end()) if (water == mWater.end())
return boost::none; return boost::none;
mShouldRebuild = true; mShouldRebuild = true;
const auto result = water->second; const auto result = *water->second;
mWaterOrder.erase(water->second);
mWater.erase(water); mWater.erase(water);
return result; return result;
} }
@ -78,10 +88,10 @@ namespace DetourNavigator
if (!mShouldRebuild) if (!mShouldRebuild)
return; return;
mMeshBuilder.reset(); mMeshBuilder.reset();
for (const auto& v : mWater) for (const auto& v : mWaterOrder)
mMeshBuilder.addWater(v.second.mCellSize, v.second.mTransform); mMeshBuilder.addWater(v.mCellSize, v.mTransform);
for (const auto& v : mObjects) for (const auto& v : mObjectsOrder)
mMeshBuilder.addObject(v.second.getShape(), v.second.getTransform(), v.second.getAreaType()); mMeshBuilder.addObject(v.getShape(), v.getTransform(), v.getAreaType());
mShouldRebuild = false; mShouldRebuild = false;
} }
} }

@ -13,6 +13,7 @@
#include <map> #include <map>
#include <unordered_map> #include <unordered_map>
#include <list>
class btCollisionShape; class btCollisionShape;
@ -53,8 +54,10 @@ namespace DetourNavigator
private: private:
bool mShouldRebuild; bool mShouldRebuild;
RecastMeshBuilder mMeshBuilder; RecastMeshBuilder mMeshBuilder;
std::unordered_map<ObjectId, RecastMeshObject> mObjects; std::list<RecastMeshObject> mObjectsOrder;
std::map<osg::Vec2i, Water> mWater; std::unordered_map<ObjectId, std::list<RecastMeshObject>::iterator> mObjects;
std::list<Water> mWaterOrder;
std::map<osg::Vec2i, std::list<Water>::iterator> mWater;
void rebuild(); void rebuild();
}; };

@ -4,6 +4,7 @@
#include <BulletCollision/CollisionShapes/btCompoundShape.h> #include <BulletCollision/CollisionShapes/btCompoundShape.h>
#include <cassert>
#include <numeric> #include <numeric>
namespace DetourNavigator namespace DetourNavigator

@ -28,6 +28,7 @@ namespace DetourNavigator
int mRegionMinSize; int mRegionMinSize;
int mTileSize; int mTileSize;
std::size_t mAsyncNavMeshUpdaterThreads; std::size_t mAsyncNavMeshUpdaterThreads;
std::size_t mMaxNavMeshTilesCacheSize;
std::size_t mMaxPolygonPathSize; std::size_t mMaxPolygonPathSize;
std::size_t mMaxSmoothPathSize; std::size_t mMaxSmoothPathSize;
std::size_t mTrianglesPerChunk; std::size_t mTrianglesPerChunk;

@ -1,9 +1,6 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SHAREDNAVMESH_H #ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SHAREDNAVMESH_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_SHAREDNAVMESH_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_SHAREDNAVMESH_H
#include <components/misc/guarded.hpp>
#include <mutex>
#include <memory> #include <memory>
class dtNavMesh; class dtNavMesh;
@ -11,7 +8,6 @@ class dtNavMesh;
namespace DetourNavigator namespace DetourNavigator
{ {
using NavMeshPtr = std::shared_ptr<dtNavMesh>; using NavMeshPtr = std::shared_ptr<dtNavMesh>;
using SharedNavMesh = Misc::SharedGuarded<dtNavMesh>;
} }
#endif #endif

@ -48,6 +48,21 @@ namespace Misc
: mValue(std::move(value)) : mValue(std::move(value))
{} {}
template <class ... Args>
ScopeGuarded(Args&& ... args)
: mValue(std::forward<Args>(args) ...)
{}
ScopeGuarded(const ScopeGuarded& other)
: mMutex()
, mValue(other.lock().get())
{}
ScopeGuarded(ScopeGuarded&& other)
: mMutex()
, mValue(std::move(other.lock().get()))
{}
Locked<T> lock() Locked<T> lock()
{ {
return Locked<T>(mMutex, mValue); return Locked<T>(mMutex, mValue);

@ -127,7 +127,9 @@ osg::ref_ptr<Resource::BulletShape> BulletNifLoader::load(const Nif::File& nif)
{ {
btTransform trans; btTransform trans;
trans.setIdentity(); trans.setIdentity();
mCompoundShape->addChildShape(trans, new Resource::TriangleMeshShape(mStaticMesh.get(), true)); std::unique_ptr<btCollisionShape> child(new Resource::TriangleMeshShape(mStaticMesh.get(), true));
mCompoundShape->addChildShape(trans, child.get());
child.release();
mStaticMesh.release(); mStaticMesh.release();
} }
mShape->mCollisionShape = mCompoundShape.release(); mShape->mCollisionShape = mCompoundShape.release();
@ -139,7 +141,10 @@ osg::ref_ptr<Resource::BulletShape> BulletNifLoader::load(const Nif::File& nif)
} }
if (mAvoidStaticMesh) if (mAvoidStaticMesh)
mShape->mAvoidCollisionShape = new Resource::TriangleMeshShape(mAvoidStaticMesh.release(), false); {
mShape->mAvoidCollisionShape = new Resource::TriangleMeshShape(mAvoidStaticMesh.get(), false);
mAvoidStaticMesh.release();
}
return mShape; return mShape;
} }

@ -588,6 +588,9 @@ region min size = 8
# Number of background threads to update nav mesh (value >= 1) # Number of background threads to update nav mesh (value >= 1)
async nav mesh updater threads = 1 async nav mesh updater threads = 1
# Maximum total cached size of all nav mesh tiles in bytes (value >= 0)
max nav mesh tiles cache size = 268435456
# Maximum size of path over polygons (value > 0) # Maximum size of path over polygons (value > 0)
max polygon path size = 1024 max polygon path size = 1024

Loading…
Cancel
Save