Do not use off mesh connections as a part of navmesh cache key

To reduce cache size and make it more flexible.

Adding off mesh connections to the navmesh is the last step of navmesh
generation and it's very fast comparing to other steps (microseconds vs
milliseconds). Having less cache size makes get and set operations almost 2x
times faster that also have an order of microseconds. So in total there is
no performance impact.
dont-compose-content
elsid 4 years ago
parent 6128fcfc82
commit beeb882ea8
No known key found for this signature in database
GPG Key ID: B845CB9FEE18AB40

@ -15,13 +15,12 @@ namespace
osg::Vec3f mAgentHalfExtents; osg::Vec3f mAgentHalfExtents;
TilePosition mTilePosition; TilePosition mTilePosition;
RecastMesh mRecastMesh; RecastMesh mRecastMesh;
std::vector<OffMeshConnection> mOffMeshConnections;
}; };
struct Item struct Item
{ {
Key mKey; Key mKey;
NavMeshData mValue; PreparedNavMeshData mValue;
}; };
template <typename Random> template <typename Random>
@ -105,8 +104,7 @@ namespace
generateWater(std::back_inserter(water), 2, random); generateWater(std::back_inserter(water), 2, random);
RecastMesh recastMesh(generation, revision, std::move(indices), std::move(vertices), RecastMesh recastMesh(generation, revision, std::move(indices), std::move(vertices),
std::move(areaTypes), std::move(water)); std::move(areaTypes), std::move(water));
std::vector<OffMeshConnection> offMeshConnections; return Key {agentHalfExtents, tilePosition, std::move(recastMesh)};
return Key {agentHalfExtents, tilePosition, std::move(recastMesh), std::move(offMeshConnections)};
} }
constexpr std::size_t trianglesPerTile = 310; constexpr std::size_t trianglesPerTile = 310;
@ -125,7 +123,8 @@ namespace
while (true) while (true)
{ {
Key key = generateKey(trianglesPerTile, random); Key key = generateKey(trianglesPerTile, random);
cache.set(key.mAgentHalfExtents, key.mTilePosition, key.mRecastMesh, key.mOffMeshConnections, NavMeshData()); cache.set(key.mAgentHalfExtents, key.mTilePosition, key.mRecastMesh,
std::make_unique<PreparedNavMeshData>());
*out++ = std::move(key); *out++ = std::move(key);
const std::size_t newSize = cache.getStats().mNavMeshCacheSize; const std::size_t newSize = cache.getStats().mNavMeshCacheSize;
if (size >= newSize) if (size >= newSize)
@ -147,7 +146,7 @@ namespace
while (state.KeepRunning()) while (state.KeepRunning())
{ {
const auto& key = keys[n++ % keys.size()]; const auto& key = keys[n++ % keys.size()];
const auto result = cache.get(key.mAgentHalfExtents, key.mTilePosition, key.mRecastMesh, key.mOffMeshConnections); const auto result = cache.get(key.mAgentHalfExtents, key.mTilePosition, key.mRecastMesh);
benchmark::DoNotOptimize(result); benchmark::DoNotOptimize(result);
} }
} }
@ -175,7 +174,8 @@ namespace
while (state.KeepRunning()) while (state.KeepRunning())
{ {
const auto& key = keys[n++ % keys.size()]; const auto& key = keys[n++ % keys.size()];
const auto result = cache.set(key.mAgentHalfExtents, key.mTilePosition, key.mRecastMesh, key.mOffMeshConnections, NavMeshData()); const auto result = cache.set(key.mAgentHalfExtents, key.mTilePosition, key.mRecastMesh,
std::make_unique<PreparedNavMeshData>());
benchmark::DoNotOptimize(result); benchmark::DoNotOptimize(result);
} }
} }

@ -3,24 +3,131 @@
#include <components/detournavigator/navmeshtilescache.hpp> #include <components/detournavigator/navmeshtilescache.hpp>
#include <components/detournavigator/exceptions.hpp> #include <components/detournavigator/exceptions.hpp>
#include <components/detournavigator/recastmesh.hpp> #include <components/detournavigator/recastmesh.hpp>
#include <components/detournavigator/preparednavmeshdata.hpp>
#include <components/detournavigator/ref.hpp>
#include <components/detournavigator/preparednavmeshdatatuple.hpp>
#include <RecastAlloc.h>
#include <LinearMath/btTransform.h> #include <LinearMath/btTransform.h>
#include <gtest/gtest.h> #include <gtest/gtest.h>
namespace DetourNavigator #include <random>
{ #include <stdexcept>
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 namespace
{ {
using namespace testing; using namespace testing;
using namespace DetourNavigator; using namespace DetourNavigator;
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>
void generate(T*& values, int size)
{
values = static_cast<T*>(permRecastAlloc(size * sizeof(T)));
std::generate_n(values, static_cast<std::size_t>(size), [] { return static_cast<T>(std::rand()); });
}
void generate(rcPolyMesh& value, int size)
{
value.nverts = size;
value.maxpolys = size;
value.nvp = size;
value.npolys = size;
rcVcopy(value.bmin, osg::Vec3f(-1, -2, -3).ptr());
rcVcopy(value.bmax, osg::Vec3f(3, 2, 1).ptr());
value.cs = 1.0f / (std::rand() % 999 + 1);
value.ch = 1.0f / (std::rand() % 999 + 1);
value.borderSize = std::rand();
value.maxEdgeError = 1.0f / (std::rand() % 999 + 1);
generate(value.verts, 3 * value.nverts);
generate(value.polys, value.maxpolys * 2 * value.nvp);
generate(value.regs, value.maxpolys);
generate(value.flags, value.maxpolys);
generate(value.areas, value.maxpolys);
}
void generate(rcPolyMeshDetail& value, int size)
{
value.nmeshes = size;
value.nverts = size;
value.ntris = size;
generate(value.meshes, 4 * value.nmeshes);
generate(value.verts, 3 * value.nverts);
generate(value.tris, 4 * value.ntris);
}
void generate(PreparedNavMeshData& value, int size)
{
value.mUserId = std::rand();
value.mCellHeight = 1.0f / (std::rand() % 999 + 1);
value.mCellSize = 1.0f / (std::rand() % 999 + 1);
generate(value.mPolyMesh, size);
generate(value.mPolyMeshDetail, size);
}
std::unique_ptr<PreparedNavMeshData> makePeparedNavMeshData(int size)
{
auto result = std::make_unique<PreparedNavMeshData>();
generate(*result, size);
return result;
}
template <class T>
void clone(const T* src, T*& dst, int size)
{
dst = static_cast<T*>(permRecastAlloc(size * sizeof(T)));
std::memcpy(dst, src, static_cast<std::size_t>(size) * sizeof(T));
}
void clone(const rcPolyMesh& src, rcPolyMesh& dst)
{
dst.nverts = src.nverts;
dst.npolys = src.npolys;
dst.maxpolys = src.maxpolys;
dst.nvp = src.nvp;
rcVcopy(dst.bmin, src.bmin);
rcVcopy(dst.bmax, src.bmax);
dst.cs = src.cs;
dst.ch = src.ch;
dst.borderSize = src.borderSize;
dst.maxEdgeError = src.maxEdgeError;
clone(src.verts, dst.verts, 3 * dst.nverts);
clone(src.polys, dst.polys, dst.maxpolys * 2 * dst.nvp);
clone(src.regs, dst.regs, dst.maxpolys);
clone(src.flags, dst.flags, dst.maxpolys);
clone(src.areas, dst.areas, dst.maxpolys);
}
void clone(const rcPolyMeshDetail& src, rcPolyMeshDetail& dst)
{
dst.nmeshes = src.nmeshes;
dst.nverts = src.nverts;
dst.ntris = src.ntris;
clone(src.meshes, dst.meshes, 4 * dst.nmeshes);
clone(src.verts, dst.verts, 3 * dst.nverts);
clone(src.tris, dst.tris, 4 * dst.ntris);
}
std::unique_ptr<PreparedNavMeshData> clone(const PreparedNavMeshData& value)
{
auto result = std::make_unique<PreparedNavMeshData>();
result->mUserId = value.mUserId;
result->mCellHeight = value.mCellHeight;
result->mCellSize = value.mCellSize;
clone(value.mPolyMesh, result->mPolyMesh);
clone(value.mPolyMeshDetail, result->mPolyMeshDetail);
return result;
}
struct DetourNavigatorNavMeshTilesCacheTest : Test struct DetourNavigatorNavMeshTilesCacheTest : Test
{ {
const osg::Vec3f mAgentHalfExtents {1, 2, 3}; const osg::Vec3f mAgentHalfExtents {1, 2, 3};
@ -32,17 +139,11 @@ namespace
const std::vector<AreaType> mAreaTypes {1, AreaType_ground}; const std::vector<AreaType> mAreaTypes {1, AreaType_ground};
const std::vector<RecastMesh::Water> mWater {}; const std::vector<RecastMesh::Water> mWater {};
const RecastMesh mRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, mWater}; const RecastMesh mRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, mWater};
const std::vector<OffMeshConnection> mOffMeshConnections {}; std::unique_ptr<PreparedNavMeshData> mPreparedNavMeshData {makePeparedNavMeshData(3)};
unsigned char* const mData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
NavMeshData mNavMeshData {mData, 1};
const size_t cRecastMeshKeySize = mRecastMesh.getIndices().size() * sizeof(int)
+ mRecastMesh.getVertices().size() * sizeof(float)
+ mRecastMesh.getAreaTypes().size() * sizeof(AreaType)
+ mRecastMesh.getWater().size() * sizeof(RecastMesh::Water)
+ mOffMeshConnections.size() * sizeof(OffMeshConnection);
const size_t cRecastMeshWithWaterKeySize = cRecastMeshKeySize + sizeof(RecastMesh::Water); const std::size_t mRecastMeshSize = sizeof(mRecastMesh) + getSize(mRecastMesh);
const std::size_t mRecastMeshWithWaterSize = mRecastMeshSize + sizeof(RecastMesh::Water);
const std::size_t mPreparedNavMeshDataSize = sizeof(*mPreparedNavMeshData) + getSize(*mPreparedNavMeshData);
}; };
TEST_F(DetourNavigatorNavMeshTilesCacheTest, get_for_empty_cache_should_return_empty_value) TEST_F(DetourNavigatorNavMeshTilesCacheTest, get_for_empty_cache_should_return_empty_value)
@ -50,7 +151,7 @@ namespace
const std::size_t maxSize = 0; const std::size_t maxSize = 0;
NavMeshTilesCache cache(maxSize); NavMeshTilesCache cache(maxSize);
EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections)); EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh));
} }
TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_for_not_enought_cache_size_should_return_empty_value) TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_for_not_enought_cache_size_should_return_empty_value)
@ -58,51 +159,46 @@ namespace
const std::size_t maxSize = 0; const std::size_t maxSize = 0;
NavMeshTilesCache cache(maxSize); NavMeshTilesCache cache(maxSize);
EXPECT_FALSE(cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, EXPECT_FALSE(cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, std::move(mPreparedNavMeshData)));
std::move(mNavMeshData))); EXPECT_NE(mPreparedNavMeshData, nullptr);
EXPECT_NE(mNavMeshData.mValue, nullptr);
} }
TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_return_cached_value) TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_return_cached_value)
{ {
const std::size_t navMeshDataSize = 1; const std::size_t maxSize = mRecastMeshSize + mPreparedNavMeshDataSize;
const std::size_t navMeshKeySize = cRecastMeshKeySize;
const std::size_t maxSize = navMeshDataSize + navMeshKeySize;
NavMeshTilesCache cache(maxSize); NavMeshTilesCache cache(maxSize);
const auto copy = clone(*mPreparedNavMeshData);
ASSERT_EQ(*mPreparedNavMeshData, *copy);
const auto result = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, const auto result = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, std::move(mPreparedNavMeshData));
std::move(mNavMeshData));
ASSERT_TRUE(result); ASSERT_TRUE(result);
EXPECT_EQ(result.get(), (NavMeshDataRef {mData, 1})); EXPECT_EQ(result.get(), *copy);
} }
TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_existing_element_should_return_cached_element) TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_existing_element_should_return_cached_element)
{ {
const std::size_t navMeshDataSize = 1; const std::size_t maxSize = 2 * (mRecastMeshSize + mPreparedNavMeshDataSize);
const std::size_t navMeshKeySize = cRecastMeshKeySize;
const std::size_t maxSize = 2 * (navMeshDataSize + navMeshKeySize);
NavMeshTilesCache cache(maxSize); NavMeshTilesCache cache(maxSize);
const auto anotherData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM)); auto copy = clone(*mPreparedNavMeshData);
NavMeshData anotherNavMeshData {anotherData, 1}; const auto sameCopy = clone(*mPreparedNavMeshData);
cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, std::move(mPreparedNavMeshData));
EXPECT_EQ(mNavMeshData.mValue, nullptr); EXPECT_EQ(mPreparedNavMeshData, nullptr);
const auto result = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(anotherNavMeshData)); const auto result = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, std::move(copy));
ASSERT_TRUE(result); ASSERT_TRUE(result);
EXPECT_EQ(result.get(), (NavMeshDataRef {mData, 1})); EXPECT_EQ(result.get(), *sameCopy);
} }
TEST_F(DetourNavigatorNavMeshTilesCacheTest, get_should_return_cached_value) TEST_F(DetourNavigatorNavMeshTilesCacheTest, get_should_return_cached_value)
{ {
const std::size_t navMeshDataSize = 1; const std::size_t maxSize = mRecastMeshSize + mPreparedNavMeshDataSize;
const std::size_t navMeshKeySize = cRecastMeshKeySize;
const std::size_t maxSize = navMeshDataSize + navMeshKeySize;
NavMeshTilesCache cache(maxSize); NavMeshTilesCache cache(maxSize);
const auto copy = clone(*mPreparedNavMeshData);
cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, std::move(mPreparedNavMeshData));
const auto result = cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections); const auto result = cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh);
ASSERT_TRUE(result); ASSERT_TRUE(result);
EXPECT_EQ(result.get(), (NavMeshDataRef {mData, 1})); EXPECT_EQ(result.get(), *copy);
} }
TEST_F(DetourNavigatorNavMeshTilesCacheTest, get_for_cache_miss_by_agent_half_extents_should_return_empty_value) TEST_F(DetourNavigatorNavMeshTilesCacheTest, get_for_cache_miss_by_agent_half_extents_should_return_empty_value)
@ -111,8 +207,8 @@ namespace
NavMeshTilesCache cache(maxSize); NavMeshTilesCache cache(maxSize);
const osg::Vec3f unexsistentAgentHalfExtents {1, 1, 1}; const osg::Vec3f unexsistentAgentHalfExtents {1, 1, 1};
cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, std::move(mPreparedNavMeshData));
EXPECT_FALSE(cache.get(unexsistentAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections)); EXPECT_FALSE(cache.get(unexsistentAgentHalfExtents, mTilePosition, mRecastMesh));
} }
TEST_F(DetourNavigatorNavMeshTilesCacheTest, get_for_cache_miss_by_tile_position_should_return_empty_value) TEST_F(DetourNavigatorNavMeshTilesCacheTest, get_for_cache_miss_by_tile_position_should_return_empty_value)
@ -121,8 +217,8 @@ namespace
NavMeshTilesCache cache(maxSize); NavMeshTilesCache cache(maxSize);
const TilePosition unexistentTilePosition {1, 1}; const TilePosition unexistentTilePosition {1, 1};
cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, std::move(mPreparedNavMeshData));
EXPECT_FALSE(cache.get(mAgentHalfExtents, unexistentTilePosition, mRecastMesh, mOffMeshConnections)); EXPECT_FALSE(cache.get(mAgentHalfExtents, unexistentTilePosition, mRecastMesh));
} }
TEST_F(DetourNavigatorNavMeshTilesCacheTest, get_for_cache_miss_by_recast_mesh_should_return_empty_value) TEST_F(DetourNavigatorNavMeshTilesCacheTest, get_for_cache_miss_by_recast_mesh_should_return_empty_value)
@ -132,217 +228,189 @@ namespace
const std::vector<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; const std::vector<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}};
const RecastMesh unexistentRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, water}; const RecastMesh unexistentRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, water};
cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, std::move(mPreparedNavMeshData));
EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, unexistentRecastMesh, mOffMeshConnections)); EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, unexistentRecastMesh));
} }
TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_replace_unused_value) TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_replace_unused_value)
{ {
const std::size_t navMeshDataSize = 1; const std::size_t maxSize = mRecastMeshWithWaterSize + mPreparedNavMeshDataSize;
const std::size_t navMeshKeySize = cRecastMeshWithWaterKeySize;
const std::size_t maxSize = navMeshDataSize + navMeshKeySize;
NavMeshTilesCache cache(maxSize); NavMeshTilesCache cache(maxSize);
const std::vector<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; const std::vector<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}};
const RecastMesh anotherRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, water}; const RecastMesh anotherRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, water};
const auto anotherData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM)); auto anotherPreparedNavMeshData = makePeparedNavMeshData(3);
NavMeshData anotherNavMeshData {anotherData, 1}; const auto copy = clone(*anotherPreparedNavMeshData);
cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, std::move(mPreparedNavMeshData));
const auto result = cache.set(mAgentHalfExtents, mTilePosition, anotherRecastMesh, mOffMeshConnections, const auto result = cache.set(mAgentHalfExtents, mTilePosition, anotherRecastMesh,
std::move(anotherNavMeshData)); std::move(anotherPreparedNavMeshData));
ASSERT_TRUE(result); ASSERT_TRUE(result);
EXPECT_EQ(result.get(), (NavMeshDataRef {anotherData, 1})); EXPECT_EQ(result.get(), *copy);
EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections)); EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh));
} }
TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_not_replace_used_value) TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_not_replace_used_value)
{ {
const std::size_t navMeshDataSize = 1; const std::size_t maxSize = mRecastMeshWithWaterSize + mPreparedNavMeshDataSize;
const std::size_t navMeshKeySize = cRecastMeshKeySize;
const std::size_t maxSize = navMeshDataSize + navMeshKeySize;
NavMeshTilesCache cache(maxSize); NavMeshTilesCache cache(maxSize);
const std::vector<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; const std::vector<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}};
const RecastMesh anotherRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, water}; const RecastMesh anotherRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, water};
const auto anotherData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM)); auto anotherPreparedNavMeshData = makePeparedNavMeshData(3);
NavMeshData anotherNavMeshData {anotherData, 1};
const auto value = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, const auto value = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh,
std::move(mNavMeshData)); std::move(mPreparedNavMeshData));
EXPECT_FALSE(cache.set(mAgentHalfExtents, mTilePosition, anotherRecastMesh, mOffMeshConnections, EXPECT_FALSE(cache.set(mAgentHalfExtents, mTilePosition, anotherRecastMesh,
std::move(anotherNavMeshData))); std::move(anotherPreparedNavMeshData)));
} }
TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_replace_unused_least_recently_set_value) TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_replace_unused_least_recently_set_value)
{ {
const std::size_t navMeshDataSize = 1; const std::size_t maxSize = 2 * (mRecastMeshWithWaterSize + mPreparedNavMeshDataSize);
const std::size_t navMeshKeySize = cRecastMeshWithWaterKeySize;
const std::size_t maxSize = 2 * (navMeshDataSize + navMeshKeySize);
NavMeshTilesCache cache(maxSize); NavMeshTilesCache cache(maxSize);
const auto copy = clone(*mPreparedNavMeshData);
const std::vector<RecastMesh::Water> leastRecentlySetWater {1, RecastMesh::Water {1, btTransform::getIdentity()}}; const std::vector<RecastMesh::Water> leastRecentlySetWater {1, RecastMesh::Water {1, btTransform::getIdentity()}};
const RecastMesh leastRecentlySetRecastMesh {mGeneration, mRevision, mIndices, mVertices, const RecastMesh leastRecentlySetRecastMesh {mGeneration, mRevision, mIndices, mVertices,
mAreaTypes, leastRecentlySetWater}; mAreaTypes, leastRecentlySetWater};
const auto leastRecentlySetData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM)); auto leastRecentlySetData = makePeparedNavMeshData(3);
NavMeshData leastRecentlySetNavMeshData {leastRecentlySetData, 1};
const std::vector<RecastMesh::Water> mostRecentlySetWater {1, RecastMesh::Water {2, btTransform::getIdentity()}}; const std::vector<RecastMesh::Water> mostRecentlySetWater {1, RecastMesh::Water {2, btTransform::getIdentity()}};
const RecastMesh mostRecentlySetRecastMesh {mGeneration, mRevision, mIndices, mVertices, const RecastMesh mostRecentlySetRecastMesh {mGeneration, mRevision, mIndices, mVertices,
mAreaTypes, mostRecentlySetWater}; mAreaTypes, mostRecentlySetWater};
const auto mostRecentlySetData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM)); auto mostRecentlySetData = makePeparedNavMeshData(3);
NavMeshData mostRecentlySetNavMeshData {mostRecentlySetData, 1};
ASSERT_TRUE(cache.set(mAgentHalfExtents, mTilePosition, leastRecentlySetRecastMesh, mOffMeshConnections, ASSERT_TRUE(cache.set(mAgentHalfExtents, mTilePosition, leastRecentlySetRecastMesh,
std::move(leastRecentlySetNavMeshData))); std::move(leastRecentlySetData)));
ASSERT_TRUE(cache.set(mAgentHalfExtents, mTilePosition, mostRecentlySetRecastMesh, mOffMeshConnections, ASSERT_TRUE(cache.set(mAgentHalfExtents, mTilePosition, mostRecentlySetRecastMesh,
std::move(mostRecentlySetNavMeshData))); std::move(mostRecentlySetData)));
const auto result = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, const auto result = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh,
std::move(mNavMeshData)); std::move(mPreparedNavMeshData));
EXPECT_EQ(result.get(), (NavMeshDataRef {mData, 1})); EXPECT_EQ(result.get(), *copy);
EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, leastRecentlySetRecastMesh, mOffMeshConnections)); EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, leastRecentlySetRecastMesh));
EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, mostRecentlySetRecastMesh, mOffMeshConnections)); EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, mostRecentlySetRecastMesh));
} }
TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_replace_unused_least_recently_used_value) TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_replace_unused_least_recently_used_value)
{ {
const std::size_t navMeshDataSize = 1; const std::size_t maxSize = 2 * (mRecastMeshWithWaterSize + mPreparedNavMeshDataSize);
const std::size_t navMeshKeySize = cRecastMeshWithWaterKeySize;
const std::size_t maxSize = 2 * (navMeshDataSize + navMeshKeySize);
NavMeshTilesCache cache(maxSize); NavMeshTilesCache cache(maxSize);
const std::vector<RecastMesh::Water> leastRecentlyUsedWater {1, RecastMesh::Water {1, btTransform::getIdentity()}}; const std::vector<RecastMesh::Water> leastRecentlyUsedWater {1, RecastMesh::Water {1, btTransform::getIdentity()}};
const RecastMesh leastRecentlyUsedRecastMesh {mGeneration, mRevision, mIndices, mVertices, const RecastMesh leastRecentlyUsedRecastMesh {mGeneration, mRevision, mIndices, mVertices,
mAreaTypes, leastRecentlyUsedWater}; mAreaTypes, leastRecentlyUsedWater};
const auto leastRecentlyUsedData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM)); auto leastRecentlyUsedData = makePeparedNavMeshData(3);
NavMeshData leastRecentlyUsedNavMeshData {leastRecentlyUsedData, 1}; const auto leastRecentlyUsedCopy = clone(*leastRecentlyUsedData);
const std::vector<RecastMesh::Water> mostRecentlyUsedWater {1, RecastMesh::Water {2, btTransform::getIdentity()}}; const std::vector<RecastMesh::Water> mostRecentlyUsedWater {1, RecastMesh::Water {2, btTransform::getIdentity()}};
const RecastMesh mostRecentlyUsedRecastMesh {mGeneration, mRevision, mIndices, mVertices, const RecastMesh mostRecentlyUsedRecastMesh {mGeneration, mRevision, mIndices, mVertices,
mAreaTypes, mostRecentlyUsedWater}; mAreaTypes, mostRecentlyUsedWater};
const auto mostRecentlyUsedData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM)); auto mostRecentlyUsedData = makePeparedNavMeshData(3);
NavMeshData mostRecentlyUsedNavMeshData {mostRecentlyUsedData, 1}; const auto mostRecentlyUsedCopy = clone(*mostRecentlyUsedData);
cache.set(mAgentHalfExtents, mTilePosition, leastRecentlyUsedRecastMesh, mOffMeshConnections, cache.set(mAgentHalfExtents, mTilePosition, leastRecentlyUsedRecastMesh, std::move(leastRecentlyUsedData));
std::move(leastRecentlyUsedNavMeshData)); cache.set(mAgentHalfExtents, mTilePosition, mostRecentlyUsedRecastMesh, std::move(mostRecentlyUsedData));
cache.set(mAgentHalfExtents, mTilePosition, mostRecentlyUsedRecastMesh, mOffMeshConnections,
std::move(mostRecentlyUsedNavMeshData));
{ {
const auto value = cache.get(mAgentHalfExtents, mTilePosition, leastRecentlyUsedRecastMesh, mOffMeshConnections); const auto value = cache.get(mAgentHalfExtents, mTilePosition, leastRecentlyUsedRecastMesh);
ASSERT_TRUE(value); ASSERT_TRUE(value);
ASSERT_EQ(value.get(), (NavMeshDataRef {leastRecentlyUsedData, 1})); ASSERT_EQ(value.get(), *leastRecentlyUsedCopy);
} }
{ {
const auto value = cache.get(mAgentHalfExtents, mTilePosition, mostRecentlyUsedRecastMesh, mOffMeshConnections); const auto value = cache.get(mAgentHalfExtents, mTilePosition, mostRecentlyUsedRecastMesh);
ASSERT_TRUE(value); ASSERT_TRUE(value);
ASSERT_EQ(value.get(), (NavMeshDataRef {mostRecentlyUsedData, 1})); ASSERT_EQ(value.get(), *mostRecentlyUsedCopy);
} }
const auto result = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, const auto copy = clone(*mPreparedNavMeshData);
std::move(mNavMeshData)); const auto result = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh,
EXPECT_EQ(result.get(), (NavMeshDataRef {mData, 1})); std::move(mPreparedNavMeshData));
EXPECT_EQ(result.get(), *copy);
EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, leastRecentlyUsedRecastMesh, mOffMeshConnections)); EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, leastRecentlyUsedRecastMesh));
EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, mostRecentlyUsedRecastMesh, mOffMeshConnections)); EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, mostRecentlyUsedRecastMesh));
} }
TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_not_replace_unused_least_recently_used_value_when_item_does_not_not_fit_cache_max_size) 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 navMeshDataSize = 1; const std::size_t maxSize = 2 * (mRecastMeshWithWaterSize + mPreparedNavMeshDataSize);
const std::size_t navMeshKeySize = cRecastMeshKeySize;
const std::size_t maxSize = 2 * (navMeshDataSize + navMeshKeySize);
NavMeshTilesCache cache(maxSize); NavMeshTilesCache cache(maxSize);
const std::vector<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; const std::vector<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}};
const RecastMesh tooLargeRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, water}; const RecastMesh tooLargeRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, water};
const auto tooLargeData = reinterpret_cast<unsigned char*>(dtAlloc(2, DT_ALLOC_PERM)); auto tooLargeData = makePeparedNavMeshData(10);
NavMeshData tooLargeNavMeshData {tooLargeData, 2};
cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, std::move(mPreparedNavMeshData));
EXPECT_FALSE(cache.set(mAgentHalfExtents, mTilePosition, tooLargeRecastMesh, mOffMeshConnections, EXPECT_FALSE(cache.set(mAgentHalfExtents, mTilePosition, tooLargeRecastMesh, std::move(tooLargeData)));
std::move(tooLargeNavMeshData))); EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh));
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) 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 navMeshDataSize = 1; const std::size_t maxSize = 2 * (mRecastMeshWithWaterSize + mPreparedNavMeshDataSize);
const std::size_t navMeshKeySize1 = cRecastMeshKeySize;
const std::size_t navMeshKeySize2 = cRecastMeshWithWaterKeySize;
const std::size_t maxSize = 2 * navMeshDataSize + navMeshKeySize1 + navMeshKeySize2;
NavMeshTilesCache cache(maxSize); NavMeshTilesCache cache(maxSize);
const std::vector<RecastMesh::Water> anotherWater {1, RecastMesh::Water {1, btTransform::getIdentity()}}; const std::vector<RecastMesh::Water> anotherWater {1, RecastMesh::Water {1, btTransform::getIdentity()}};
const RecastMesh anotherRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, anotherWater}; const RecastMesh anotherRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, anotherWater};
const auto anotherData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM)); auto anotherData = makePeparedNavMeshData(3);
NavMeshData anotherNavMeshData {anotherData, 1};
const std::vector<RecastMesh::Water> tooLargeWater {1, RecastMesh::Water {2, btTransform::getIdentity()}}; const std::vector<RecastMesh::Water> tooLargeWater {1, RecastMesh::Water {2, btTransform::getIdentity()}};
const RecastMesh tooLargeRecastMesh {mGeneration, mRevision, mIndices, mVertices, const RecastMesh tooLargeRecastMesh {mGeneration, mRevision, mIndices, mVertices,
mAreaTypes, tooLargeWater}; mAreaTypes, tooLargeWater};
const auto tooLargeData = reinterpret_cast<unsigned char*>(dtAlloc(2, DT_ALLOC_PERM)); auto tooLargeData = makePeparedNavMeshData(10);
NavMeshData tooLargeNavMeshData {tooLargeData, 2};
const auto value = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, const auto value = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh,
std::move(mNavMeshData)); std::move(mPreparedNavMeshData));
ASSERT_TRUE(value); ASSERT_TRUE(value);
ASSERT_TRUE(cache.set(mAgentHalfExtents, mTilePosition, anotherRecastMesh, mOffMeshConnections, ASSERT_TRUE(cache.set(mAgentHalfExtents, mTilePosition, anotherRecastMesh,
std::move(anotherNavMeshData))); std::move(anotherData)));
EXPECT_FALSE(cache.set(mAgentHalfExtents, mTilePosition, tooLargeRecastMesh, mOffMeshConnections, EXPECT_FALSE(cache.set(mAgentHalfExtents, mTilePosition, tooLargeRecastMesh,
std::move(tooLargeNavMeshData))); std::move(tooLargeData)));
EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections)); EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh));
EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, anotherRecastMesh, mOffMeshConnections)); EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, anotherRecastMesh));
} }
TEST_F(DetourNavigatorNavMeshTilesCacheTest, release_used_after_set_then_used_by_get_item_should_left_this_item_available) TEST_F(DetourNavigatorNavMeshTilesCacheTest, release_used_after_set_then_used_by_get_item_should_left_this_item_available)
{ {
const std::size_t navMeshDataSize = 1; const std::size_t maxSize = mRecastMeshWithWaterSize + mPreparedNavMeshDataSize;
const std::size_t navMeshKeySize = cRecastMeshKeySize;
const std::size_t maxSize = navMeshDataSize + navMeshKeySize;
NavMeshTilesCache cache(maxSize); NavMeshTilesCache cache(maxSize);
const std::vector<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; const std::vector<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}};
const RecastMesh anotherRecastMesh {mGeneration, mRevision, mIndices, mVertices, const RecastMesh anotherRecastMesh {mGeneration, mRevision, mIndices, mVertices,
mAreaTypes, water}; mAreaTypes, water};
const auto anotherData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM)); auto anotherData = makePeparedNavMeshData(3);
NavMeshData anotherNavMeshData {anotherData, 1};
const auto firstCopy = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); const auto firstCopy = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, std::move(mPreparedNavMeshData));
ASSERT_TRUE(firstCopy); ASSERT_TRUE(firstCopy);
{ {
const auto secondCopy = cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections); const auto secondCopy = cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh);
ASSERT_TRUE(secondCopy); ASSERT_TRUE(secondCopy);
} }
EXPECT_FALSE(cache.set(mAgentHalfExtents, mTilePosition, anotherRecastMesh, mOffMeshConnections, EXPECT_FALSE(cache.set(mAgentHalfExtents, mTilePosition, anotherRecastMesh, std::move(anotherData)));
std::move(anotherNavMeshData))); EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh));
EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections));
} }
TEST_F(DetourNavigatorNavMeshTilesCacheTest, release_twice_used_item_should_left_this_item_available) TEST_F(DetourNavigatorNavMeshTilesCacheTest, release_twice_used_item_should_left_this_item_available)
{ {
const std::size_t navMeshDataSize = 1; const std::size_t maxSize = mRecastMeshWithWaterSize + mPreparedNavMeshDataSize;
const std::size_t navMeshKeySize = cRecastMeshKeySize;
const std::size_t maxSize = navMeshDataSize + navMeshKeySize;
NavMeshTilesCache cache(maxSize); NavMeshTilesCache cache(maxSize);
const std::vector<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; const std::vector<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}};
const RecastMesh anotherRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, water}; const RecastMesh anotherRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, water};
const auto anotherData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM)); auto anotherData = makePeparedNavMeshData(3);
NavMeshData anotherNavMeshData {anotherData, 1};
cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, std::move(mPreparedNavMeshData));
const auto firstCopy = cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections); const auto firstCopy = cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh);
ASSERT_TRUE(firstCopy); ASSERT_TRUE(firstCopy);
{ {
const auto secondCopy = cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections); const auto secondCopy = cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh);
ASSERT_TRUE(secondCopy); ASSERT_TRUE(secondCopy);
} }
EXPECT_FALSE(cache.set(mAgentHalfExtents, mTilePosition, anotherRecastMesh, mOffMeshConnections, EXPECT_FALSE(cache.set(mAgentHalfExtents, mTilePosition, anotherRecastMesh, std::move(anotherData)));
std::move(anotherNavMeshData))); EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh));
EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections));
} }
} }

@ -193,6 +193,8 @@ add_component_dir(detournavigator
navmeshtileview navmeshtileview
oscillatingrecastmeshobject oscillatingrecastmeshobject
offmeshconnectionsmanager offmeshconnectionsmanager
preparednavmeshdata
navmeshcacheitem
) )
set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui

@ -7,6 +7,8 @@
#include "sharednavmesh.hpp" #include "sharednavmesh.hpp"
#include "flags.hpp" #include "flags.hpp"
#include "navmeshtilescache.hpp" #include "navmeshtilescache.hpp"
#include "preparednavmeshdata.hpp"
#include "navmeshdata.hpp"
#include <components/misc/convert.hpp> #include <components/misc/convert.hpp>
@ -26,25 +28,6 @@ namespace
{ {
using namespace DetourNavigator; using namespace DetourNavigator;
void initPolyMeshDetail(rcPolyMeshDetail& value)
{
value.meshes = nullptr;
value.verts = nullptr;
value.tris = nullptr;
}
struct PolyMeshDetailStackDeleter
{
void operator ()(rcPolyMeshDetail* value) const
{
rcFree(value->meshes);
rcFree(value->verts);
rcFree(value->tris);
}
};
using PolyMeshDetailStackPtr = std::unique_ptr<rcPolyMeshDetail, PolyMeshDetailStackDeleter>;
struct WaterBounds struct WaterBounds
{ {
osg::Vec3f mMin; osg::Vec3f mMin;
@ -363,10 +346,25 @@ namespace
return true; return true;
} }
NavMeshData makeNavMeshTileData(const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh, template <class T>
const std::vector<OffMeshConnection>& offMeshConnections, const TilePosition& tile, unsigned long getMinValuableBitsNumber(const T value)
const osg::Vec3f& boundsMin, const osg::Vec3f& boundsMax, const Settings& settings) {
unsigned long power = 0;
while (power < sizeof(T) * 8 && (static_cast<T>(1) << power) < value)
++power;
return power;
}
}
namespace DetourNavigator
{
std::unique_ptr<PreparedNavMeshData> prepareNavMeshTileData(const RecastMesh& recastMesh,
const TilePosition& tile, const Bounds& bounds, const osg::Vec3f& agentHalfExtents, const Settings& settings)
{ {
const TileBounds tileBounds = makeTileBounds(settings, tile);
const osg::Vec3f boundsMin(tileBounds.mMin.x(), bounds.mMin.y() - 1, tileBounds.mMin.y());
const osg::Vec3f boundsMax(tileBounds.mMax.x(), bounds.mMax.y() + 1, tileBounds.mMax.y());
rcContext context; rcContext context;
const auto config = makeConfig(agentHalfExtents, boundsMin, boundsMax, settings); const auto config = makeConfig(agentHalfExtents, boundsMin, boundsMax, settings);
@ -374,19 +372,27 @@ namespace
createHeightfield(context, solid, config.width, config.height, config.bmin, config.bmax, config.cs, config.ch); createHeightfield(context, solid, config.width, config.height, config.bmin, config.bmax, config.cs, config.ch);
if (!rasterizeTriangles(context, agentHalfExtents, recastMesh, config, settings, solid)) if (!rasterizeTriangles(context, agentHalfExtents, recastMesh, config, settings, solid))
return NavMeshData(); return nullptr;
rcFilterLowHangingWalkableObstacles(&context, config.walkableClimb, solid); rcFilterLowHangingWalkableObstacles(&context, config.walkableClimb, solid);
rcFilterLedgeSpans(&context, config.walkableHeight, config.walkableClimb, solid); rcFilterLedgeSpans(&context, config.walkableHeight, config.walkableClimb, solid);
rcFilterWalkableLowHeightSpans(&context, config.walkableHeight, solid); rcFilterWalkableLowHeightSpans(&context, config.walkableHeight, solid);
rcPolyMesh polyMesh; std::unique_ptr<PreparedNavMeshData> result = std::make_unique<PreparedNavMeshData>();
rcPolyMeshDetail polyMeshDetail;
initPolyMeshDetail(polyMeshDetail);
const PolyMeshDetailStackPtr polyMeshDetailPtr(&polyMeshDetail);
if (!fillPolyMesh(context, config, solid, polyMesh, polyMeshDetail))
return NavMeshData();
if (!fillPolyMesh(context, config, solid, result->mPolyMesh, result->mPolyMeshDetail))
return nullptr;
result->mCellSize = config.cs;
result->mCellHeight = config.ch;
return result;
}
NavMeshData makeNavMeshTileData(const PreparedNavMeshData& data,
const std::vector<OffMeshConnection>& offMeshConnections, const osg::Vec3f& agentHalfExtents,
const TilePosition& tile, const Settings& settings)
{
const auto offMeshConVerts = getOffMeshVerts(offMeshConnections); const auto offMeshConVerts = getOffMeshVerts(offMeshConnections);
const std::vector<float> offMeshConRad(offMeshConnections.size(), getRadius(settings, agentHalfExtents)); const std::vector<float> offMeshConRad(offMeshConnections.size(), getRadius(settings, agentHalfExtents));
const std::vector<unsigned char> offMeshConDir(offMeshConnections.size(), 0); const std::vector<unsigned char> offMeshConDir(offMeshConnections.size(), 0);
@ -394,18 +400,18 @@ namespace
const std::vector<unsigned short> offMeshConFlags = getOffMeshFlags(offMeshConnections); const std::vector<unsigned short> offMeshConFlags = getOffMeshFlags(offMeshConnections);
dtNavMeshCreateParams params; dtNavMeshCreateParams params;
params.verts = polyMesh.verts; params.verts = data.mPolyMesh.verts;
params.vertCount = polyMesh.nverts; params.vertCount = data.mPolyMesh.nverts;
params.polys = polyMesh.polys; params.polys = data.mPolyMesh.polys;
params.polyAreas = polyMesh.areas; params.polyAreas = data.mPolyMesh.areas;
params.polyFlags = polyMesh.flags; params.polyFlags = data.mPolyMesh.flags;
params.polyCount = polyMesh.npolys; params.polyCount = data.mPolyMesh.npolys;
params.nvp = polyMesh.nvp; params.nvp = data.mPolyMesh.nvp;
params.detailMeshes = polyMeshDetail.meshes; params.detailMeshes = data.mPolyMeshDetail.meshes;
params.detailVerts = polyMeshDetail.verts; params.detailVerts = data.mPolyMeshDetail.verts;
params.detailVertsCount = polyMeshDetail.nverts; params.detailVertsCount = data.mPolyMeshDetail.nverts;
params.detailTris = polyMeshDetail.tris; params.detailTris = data.mPolyMeshDetail.tris;
params.detailTriCount = polyMeshDetail.ntris; params.detailTriCount = data.mPolyMeshDetail.ntris;
params.offMeshConVerts = offMeshConVerts.data(); params.offMeshConVerts = offMeshConVerts.data();
params.offMeshConRad = offMeshConRad.data(); params.offMeshConRad = offMeshConRad.data();
params.offMeshConDir = offMeshConDir.data(); params.offMeshConDir = offMeshConDir.data();
@ -416,12 +422,12 @@ namespace
params.walkableHeight = getHeight(settings, agentHalfExtents); params.walkableHeight = getHeight(settings, agentHalfExtents);
params.walkableRadius = getRadius(settings, agentHalfExtents); params.walkableRadius = getRadius(settings, agentHalfExtents);
params.walkableClimb = getMaxClimb(settings); params.walkableClimb = getMaxClimb(settings);
rcVcopy(params.bmin, polyMesh.bmin); rcVcopy(params.bmin, data.mPolyMesh.bmin);
rcVcopy(params.bmax, polyMesh.bmax); rcVcopy(params.bmax, data.mPolyMesh.bmax);
params.cs = config.cs; params.cs = data.mCellSize;
params.ch = config.ch; params.ch = data.mCellHeight;
params.buildBvTree = true; params.buildBvTree = true;
params.userId = 0; params.userId = data.mUserId;
params.tileX = tile.x(); params.tileX = tile.x();
params.tileY = tile.y(); params.tileY = tile.y();
params.tileLayer = 0; params.tileLayer = 0;
@ -436,20 +442,6 @@ namespace
return NavMeshData(navMeshData, navMeshDataSize); return NavMeshData(navMeshData, navMeshDataSize);
} }
template <class T>
unsigned long getMinValuableBitsNumber(const T value)
{
unsigned long power = 0;
while (power < sizeof(T) * 8 && (static_cast<T>(1) << power) < value)
++power;
return power;
}
}
namespace DetourNavigator
{
NavMeshPtr makeEmptyNavMesh(const Settings& settings) NavMeshPtr makeEmptyNavMesh(const Settings& settings)
{ {
// Max tiles and max polys affect how the tile IDs are caculated. // Max tiles and max polys affect how the tile IDs are caculated.
@ -522,35 +514,32 @@ namespace DetourNavigator
return navMeshCacheItem->lock()->removeTile(changedTile); return navMeshCacheItem->lock()->removeTile(changedTile);
} }
auto cachedNavMeshData = navMeshTilesCache.get(agentHalfExtents, changedTile, *recastMesh, offMeshConnections); auto cachedNavMeshData = navMeshTilesCache.get(agentHalfExtents, changedTile, *recastMesh);
bool cached = static_cast<bool>(cachedNavMeshData); bool cached = static_cast<bool>(cachedNavMeshData);
if (!cachedNavMeshData) if (!cachedNavMeshData)
{ {
const auto tileBounds = makeTileBounds(settings, changedTile); auto prepared = prepareNavMeshTileData(*recastMesh, changedTile, recastMeshBounds,
const osg::Vec3f tileBorderMin(tileBounds.mMin.x(), recastMeshBounds.mMin.y() - 1, tileBounds.mMin.y()); agentHalfExtents, settings);
const osg::Vec3f tileBorderMax(tileBounds.mMax.x(), recastMeshBounds.mMax.y() + 1, tileBounds.mMax.y());
auto navMeshData = makeNavMeshTileData(agentHalfExtents, *recastMesh, offMeshConnections, changedTile,
tileBorderMin, tileBorderMax, settings);
if (!navMeshData.mValue) if (prepared == nullptr)
{ {
Log(Debug::Debug) << "Ignore add tile: NavMeshData is null"; Log(Debug::Debug) << "Ignore add tile: NavMeshData is null";
return navMeshCacheItem->lock()->removeTile(changedTile); return navMeshCacheItem->lock()->removeTile(changedTile);
} }
cachedNavMeshData = navMeshTilesCache.set(agentHalfExtents, changedTile, *recastMesh, cachedNavMeshData = navMeshTilesCache.set(agentHalfExtents, changedTile, *recastMesh, std::move(prepared));
offMeshConnections, std::move(navMeshData));
if (!cachedNavMeshData) if (!cachedNavMeshData)
{ {
Log(Debug::Debug) << "Navigator cache overflow"; Log(Debug::Debug) << "Navigator cache overflow";
return navMeshCacheItem->lock()->updateTile(changedTile, std::move(navMeshData)); return navMeshCacheItem->lock()->updateTile(changedTile, NavMeshTilesCache::Value(),
makeNavMeshTileData(*prepared, offMeshConnections, agentHalfExtents, changedTile, settings));
} }
} }
const auto updateStatus = navMeshCacheItem->lock()->updateTile(changedTile, std::move(cachedNavMeshData)); const auto updateStatus = navMeshCacheItem->lock()->updateTile(changedTile, std::move(cachedNavMeshData),
makeNavMeshTileData(cachedNavMeshData.get(), offMeshConnections, agentHalfExtents, changedTile, settings));
return UpdateNavMeshStatusBuilder(updateStatus).cached(cached).getResult(); return UpdateNavMeshStatusBuilder(updateStatus).cached(cached).getResult();
} }

@ -6,10 +6,12 @@
#include "tileposition.hpp" #include "tileposition.hpp"
#include "sharednavmesh.hpp" #include "sharednavmesh.hpp"
#include "navmeshtilescache.hpp" #include "navmeshtilescache.hpp"
#include "offmeshconnection.hpp"
#include <osg/Vec3f> #include <osg/Vec3f>
#include <memory> #include <memory>
#include <vector>
class dtNavMesh; class dtNavMesh;
@ -17,6 +19,8 @@ namespace DetourNavigator
{ {
class RecastMesh; class RecastMesh;
struct Settings; struct Settings;
struct PreparedNavMeshData;
struct NavMeshData;
inline float getLength(const osg::Vec2i& value) inline float getLength(const osg::Vec2i& value)
{ {
@ -34,6 +38,13 @@ namespace DetourNavigator
return expectedTilesCount <= maxTiles; return expectedTilesCount <= maxTiles;
} }
std::unique_ptr<PreparedNavMeshData> prepareNavMeshTileData(const RecastMesh& recastMesh, const TilePosition& tile,
const Bounds& bounds, const osg::Vec3f& agentHalfExtents, const Settings& settings);
NavMeshData makeNavMeshTileData(const PreparedNavMeshData& data,
const std::vector<OffMeshConnection>& offMeshConnections, const osg::Vec3f& agentHalfExtents,
const TilePosition& tile, const Settings& settings);
NavMeshPtr makeEmptyNavMesh(const Settings& settings); NavMeshPtr makeEmptyNavMesh(const Settings& settings);
UpdateNavMeshStatus updateNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh* recastMesh, UpdateNavMeshStatus updateNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh* recastMesh,

@ -0,0 +1,82 @@
#include "tileposition.hpp"
#include "navmeshtilescache.hpp"
#include "dtstatus.hpp"
#include "navmeshtileview.hpp"
#include "navmeshcacheitem.hpp"
#include "navmeshdata.hpp"
#include <components/misc/guarded.hpp>
#include <DetourNavMesh.h>
namespace
{
using DetourNavigator::TilePosition;
const dtMeshTile* getTile(const dtNavMesh& navMesh, const TilePosition& position)
{
const int layer = 0;
return navMesh.getTileAt(position.x(), position.y(), layer);
}
bool removeTile(dtNavMesh& navMesh, const TilePosition& position)
{
const int layer = 0;
const auto tileRef = navMesh.getTileRefAt(position.x(), position.y(), layer);
if (tileRef == 0)
return false;
unsigned char** const data = nullptr;
int* const dataSize = nullptr;
return dtStatusSucceed(navMesh.removeTile(tileRef, data, dataSize));
}
dtStatus addTile(dtNavMesh& navMesh, unsigned char* data, int size)
{
const int doNotTransferOwnership = 0;
const dtTileRef lastRef = 0;
dtTileRef* const result = nullptr;
return navMesh.addTile(data, size, doNotTransferOwnership, lastRef, result);
}
}
namespace DetourNavigator
{
UpdateNavMeshStatus NavMeshCacheItem::updateTile(const TilePosition& position, NavMeshTilesCache::Value&& cached,
NavMeshData&& navMeshData)
{
const dtMeshTile* currentTile = ::getTile(*mImpl, position);
if (currentTile != nullptr
&& asNavMeshTileConstView(*currentTile) == asNavMeshTileConstView(navMeshData.mValue.get()))
{
return UpdateNavMeshStatus::ignored;
}
const auto removed = ::removeTile(*mImpl, position);
const auto addStatus = addTile(*mImpl, navMeshData.mValue.get(), navMeshData.mSize);
if (dtStatusSucceed(addStatus))
{
mUsedTiles[position] = std::make_pair(std::move(cached), std::move(navMeshData));
++mNavMeshRevision;
return UpdateNavMeshStatusBuilder().added(true).removed(removed).getResult();
}
else
{
if (removed)
{
mUsedTiles.erase(position);
++mNavMeshRevision;
}
return UpdateNavMeshStatusBuilder().removed(removed).failed((addStatus & DT_OUT_OF_MEMORY) != 0).getResult();
}
}
UpdateNavMeshStatus NavMeshCacheItem::removeTile(const TilePosition& position)
{
const auto removed = ::removeTile(*mImpl, position);
if (removed)
{
mUsedTiles.erase(position);
++mNavMeshRevision;
}
return UpdateNavMeshStatusBuilder().removed(removed).getResult();
}
}

@ -5,14 +5,14 @@
#include "tileposition.hpp" #include "tileposition.hpp"
#include "navmeshtilescache.hpp" #include "navmeshtilescache.hpp"
#include "dtstatus.hpp" #include "dtstatus.hpp"
#include "navmeshtileview.hpp" #include "navmeshdata.hpp"
#include <components/misc/guarded.hpp> #include <components/misc/guarded.hpp>
#include <DetourNavMesh.h>
#include <map> #include <map>
struct dtMeshTile;
namespace DetourNavigator namespace DetourNavigator
{ {
enum class UpdateNavMeshStatus : unsigned enum class UpdateNavMeshStatus : unsigned
@ -96,26 +96,6 @@ namespace DetourNavigator
} }
}; };
inline unsigned char* getRawData(NavMeshData& navMeshData)
{
return navMeshData.mValue.get();
}
inline unsigned char* getRawData(NavMeshTilesCache::Value& cachedNavMeshData)
{
return cachedNavMeshData.get().mValue;
}
inline int getSize(const NavMeshData& navMeshData)
{
return navMeshData.mSize;
}
inline int getSize(const NavMeshTilesCache::Value& cachedNavMeshData)
{
return cachedNavMeshData.get().mSize;
}
class NavMeshCacheItem class NavMeshCacheItem
{ {
public: public:
@ -139,86 +119,16 @@ namespace DetourNavigator
return mNavMeshRevision; return mNavMeshRevision;
} }
template <class T> UpdateNavMeshStatus updateTile(const TilePosition& position, NavMeshTilesCache::Value&& cached,
UpdateNavMeshStatus updateTile(const TilePosition& position, T&& navMeshData) NavMeshData&& navMeshData);
{
const dtMeshTile* currentTile = getTile(position);
if (currentTile != nullptr
&& asNavMeshTileConstView(*currentTile) == asNavMeshTileConstView(getRawData(navMeshData)))
{
return UpdateNavMeshStatus::ignored;
}
const auto removed = removeTileImpl(position);
const auto addStatus = addTileImpl(getRawData(navMeshData), getSize(navMeshData));
if (dtStatusSucceed(addStatus))
{
setUsedTile(position, std::forward<T>(navMeshData));
return UpdateNavMeshStatusBuilder().added(true).removed(removed).getResult();
}
else
{
if (removed)
removeUsedTile(position);
return UpdateNavMeshStatusBuilder().removed(removed).failed((addStatus & DT_OUT_OF_MEMORY) != 0).getResult();
}
}
UpdateNavMeshStatus removeTile(const TilePosition& position) UpdateNavMeshStatus removeTile(const TilePosition& position);
{
const auto removed = removeTileImpl(position);
if (removed)
removeUsedTile(position);
return UpdateNavMeshStatusBuilder().removed(removed).getResult();
}
private: private:
NavMeshPtr mImpl; NavMeshPtr mImpl;
std::size_t mGeneration; std::size_t mGeneration;
std::size_t mNavMeshRevision; std::size_t mNavMeshRevision;
std::map<TilePosition, std::pair<NavMeshTilesCache::Value, NavMeshData>> mUsedTiles; std::map<TilePosition, std::pair<NavMeshTilesCache::Value, NavMeshData>> mUsedTiles;
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;
}
dtStatus addTileImpl(unsigned char* data, int size)
{
const int doNotTransferOwnership = 0;
const dtTileRef lastRef = 0;
dtTileRef* const result = nullptr;
return mImpl->addTile(data, size, doNotTransferOwnership, lastRef, result);
}
bool removeTileImpl(const TilePosition& position)
{
const int layer = 0;
const auto tileRef = mImpl->getTileRefAt(position.x(), position.y(), layer);
if (tileRef == 0)
return false;
unsigned char** const data = nullptr;
int* const dataSize = nullptr;
return dtStatusSucceed(mImpl->removeTile(tileRef, data, dataSize));
}
const dtMeshTile* getTile(const TilePosition& position) const
{
const int layer = 0;
return mImpl->getTileAt(position.x(), position.y(), layer);
}
}; };
using GuardedNavMeshCacheItem = Misc::ScopeGuarded<NavMeshCacheItem>; using GuardedNavMeshCacheItem = Misc::ScopeGuarded<NavMeshCacheItem>;

@ -3,7 +3,6 @@
#include <DetourAlloc.h> #include <DetourAlloc.h>
#include <algorithm>
#include <memory> #include <memory>
namespace DetourNavigator namespace DetourNavigator

@ -6,32 +6,18 @@
namespace DetourNavigator namespace DetourNavigator
{ {
namespace
{
inline std::size_t getSize(const RecastMesh& recastMesh,
const std::vector<OffMeshConnection>& offMeshConnections)
{
const std::size_t indicesSize = recastMesh.getIndices().size() * sizeof(int);
const std::size_t verticesSize = recastMesh.getVertices().size() * sizeof(float);
const std::size_t areaTypesSize = recastMesh.getAreaTypes().size() * sizeof(AreaType);
const std::size_t waterSize = recastMesh.getWater().size() * sizeof(RecastMesh::Water);
const std::size_t offMeshConnectionsSize = offMeshConnections.size() * sizeof(OffMeshConnection);
return indicesSize + verticesSize + areaTypesSize + waterSize + offMeshConnectionsSize;
}
}
NavMeshTilesCache::NavMeshTilesCache(const std::size_t maxNavMeshDataSize) NavMeshTilesCache::NavMeshTilesCache(const std::size_t maxNavMeshDataSize)
: mMaxNavMeshDataSize(maxNavMeshDataSize), mUsedNavMeshDataSize(0), mFreeNavMeshDataSize(0), : mMaxNavMeshDataSize(maxNavMeshDataSize), mUsedNavMeshDataSize(0), mFreeNavMeshDataSize(0),
mHitCount(0), mGetCount(0) {} mHitCount(0), mGetCount(0) {}
NavMeshTilesCache::Value NavMeshTilesCache::get(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile, NavMeshTilesCache::Value NavMeshTilesCache::get(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile,
const RecastMesh& recastMesh, const std::vector<OffMeshConnection>& offMeshConnections) const RecastMesh& recastMesh)
{ {
const std::lock_guard<std::mutex> lock(mMutex); const std::lock_guard<std::mutex> lock(mMutex);
++mGetCount; ++mGetCount;
const auto tile = mValues.find(std::make_tuple(agentHalfExtents, changedTile, NavMeshKeyView(recastMesh, offMeshConnections))); const auto tile = mValues.find(std::make_tuple(agentHalfExtents, changedTile, recastMesh));
if (tile == mValues.end()) if (tile == mValues.end())
return Value(); return Value();
@ -43,10 +29,10 @@ namespace DetourNavigator
} }
NavMeshTilesCache::Value NavMeshTilesCache::set(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile, NavMeshTilesCache::Value NavMeshTilesCache::set(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile,
const RecastMesh& recastMesh, const std::vector<OffMeshConnection>& offMeshConnections, const RecastMesh& recastMesh, std::unique_ptr<PreparedNavMeshData>&& value)
NavMeshData&& value)
{ {
const auto itemSize = static_cast<std::size_t>(value.mSize) + getSize(recastMesh, offMeshConnections); const auto itemSize = sizeof(RecastMesh) + getSize(recastMesh)
+ (value == nullptr ? 0 : sizeof(PreparedNavMeshData) + getSize(*value));
const std::lock_guard<std::mutex> lock(mMutex); const std::lock_guard<std::mutex> lock(mMutex);
@ -56,13 +42,10 @@ namespace DetourNavigator
while (!mFreeItems.empty() && mUsedNavMeshDataSize + itemSize > mMaxNavMeshDataSize) while (!mFreeItems.empty() && mUsedNavMeshDataSize + itemSize > mMaxNavMeshDataSize)
removeLeastRecentlyUsed(); removeLeastRecentlyUsed();
NavMeshKey navMeshKey { RecastMeshData key {recastMesh.getIndices(), recastMesh.getVertices(), recastMesh.getAreaTypes(), recastMesh.getWater()};
RecastMeshData {recastMesh.getIndices(), recastMesh.getVertices(), recastMesh.getAreaTypes(), recastMesh.getWater()},
offMeshConnections
};
const auto iterator = mFreeItems.emplace(mFreeItems.end(), agentHalfExtents, changedTile, std::move(navMeshKey), itemSize); const auto iterator = mFreeItems.emplace(mFreeItems.end(), agentHalfExtents, changedTile, std::move(key), itemSize);
const auto emplaced = mValues.emplace(std::make_tuple(agentHalfExtents, changedTile, NavMeshKeyRef(iterator->mNavMeshKey)), iterator); const auto emplaced = mValues.emplace(std::make_tuple(agentHalfExtents, changedTile, std::cref(iterator->mRecastMeshData)), iterator);
if (!emplaced.second) if (!emplaced.second)
{ {
@ -73,7 +56,7 @@ namespace DetourNavigator
return Value(*this, emplaced.first->second); return Value(*this, emplaced.first->second);
} }
iterator->mNavMeshData = std::move(value); iterator->mPreparedNavMeshData = std::move(value);
++iterator->mUseCount; ++iterator->mUseCount;
mUsedNavMeshDataSize += itemSize; mUsedNavMeshDataSize += itemSize;
mBusyItems.splice(mBusyItems.end(), mFreeItems, iterator); mBusyItems.splice(mBusyItems.end(), mFreeItems, iterator);
@ -108,7 +91,7 @@ namespace DetourNavigator
{ {
const auto& item = mFreeItems.back(); const auto& item = mFreeItems.back();
const auto value = mValues.find(std::make_tuple(item.mAgentHalfExtents, item.mChangedTile, NavMeshKeyRef(item.mNavMeshKey))); const auto value = mValues.find(std::make_tuple(item.mAgentHalfExtents, item.mChangedTile, std::cref(item.mRecastMeshData)));
if (value == mValues.end()) if (value == mValues.end())
return; return;

@ -1,8 +1,7 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHTILESCACHE_H #ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHTILESCACHE_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHTILESCACHE_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHTILESCACHE_H
#include "offmeshconnection.hpp" #include "preparednavmeshdata.hpp"
#include "navmeshdata.hpp"
#include "recastmesh.hpp" #include "recastmesh.hpp"
#include "tileposition.hpp" #include "tileposition.hpp"
@ -21,12 +20,6 @@ namespace osg
namespace DetourNavigator namespace DetourNavigator
{ {
struct NavMeshDataRef
{
unsigned char* mValue;
int mSize;
};
struct RecastMeshData struct RecastMeshData
{ {
std::vector<int> mIndices; std::vector<int> mIndices;
@ -53,71 +46,6 @@ namespace DetourNavigator
< std::tie(rhs.mIndices, rhs.mVertices, rhs.mAreaTypes, rhs.mWater); < std::tie(rhs.mIndices, rhs.mVertices, rhs.mAreaTypes, rhs.mWater);
} }
struct NavMeshKey
{
RecastMeshData mRecastMesh;
std::vector<OffMeshConnection> mOffMeshConnections;
};
inline bool operator <(const NavMeshKey& lhs, const NavMeshKey& rhs)
{
return std::tie(lhs.mRecastMesh, lhs.mOffMeshConnections)
< std::tie(rhs.mRecastMesh, rhs.mOffMeshConnections);
}
struct NavMeshKeyRef
{
std::reference_wrapper<const NavMeshKey> mRef;
explicit NavMeshKeyRef(const NavMeshKey& ref) : mRef(ref) {}
};
inline bool operator <(const NavMeshKeyRef& lhs, const NavMeshKeyRef& rhs)
{
return lhs.mRef.get() < rhs.mRef.get();
}
struct NavMeshKeyView
{
std::reference_wrapper<const RecastMesh> mRecastMesh;
std::reference_wrapper<const std::vector<OffMeshConnection>> mOffMeshConnections;
NavMeshKeyView(const RecastMesh& recastMesh, const std::vector<OffMeshConnection>& offMeshConnections)
: mRecastMesh(recastMesh), mOffMeshConnections(offMeshConnections) {}
};
inline bool operator <(const NavMeshKeyView& lhs, const NavMeshKey& rhs)
{
return std::tie(lhs.mRecastMesh.get(), lhs.mOffMeshConnections.get())
< std::tie(rhs.mRecastMesh, rhs.mOffMeshConnections);
}
inline bool operator <(const NavMeshKey& lhs, const NavMeshKeyView& rhs)
{
return std::tie(lhs.mRecastMesh, lhs.mOffMeshConnections)
< std::tie(rhs.mRecastMesh.get(), rhs.mOffMeshConnections.get());
}
template <class R>
inline bool operator <(const NavMeshKeyRef& lhs, const R& rhs)
{
return lhs.mRef.get() < rhs;
}
template <class L>
inline bool operator <(const L& lhs, const NavMeshKeyRef& rhs)
{
return lhs < rhs.mRef.get();
}
template <class L, class R>
inline bool operator <(const std::tuple<osg::Vec3f, TilePosition, L>& lhs, const std::tuple<osg::Vec3f, TilePosition, R>& rhs)
{
const auto left = std::tie(std::get<0>(lhs), std::get<1>(lhs));
const auto right = std::tie(std::get<0>(rhs), std::get<1>(rhs));
return std::tie(left, std::get<2>(lhs)) < std::tie(right, std::get<2>(rhs));
}
class NavMeshTilesCache class NavMeshTilesCache
{ {
public: public:
@ -126,15 +54,16 @@ namespace DetourNavigator
std::atomic<std::int64_t> mUseCount; std::atomic<std::int64_t> mUseCount;
osg::Vec3f mAgentHalfExtents; osg::Vec3f mAgentHalfExtents;
TilePosition mChangedTile; TilePosition mChangedTile;
NavMeshKey mNavMeshKey; RecastMeshData mRecastMeshData;
NavMeshData mNavMeshData; std::unique_ptr<PreparedNavMeshData> mPreparedNavMeshData;
std::size_t mSize; std::size_t mSize;
Item(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile, NavMeshKey&& navMeshKey, std::size_t size) Item(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile,
RecastMeshData&& recastMeshData, std::size_t size)
: mUseCount(0) : mUseCount(0)
, mAgentHalfExtents(agentHalfExtents) , mAgentHalfExtents(agentHalfExtents)
, mChangedTile(changedTile) , mChangedTile(changedTile)
, mNavMeshKey(navMeshKey) , mRecastMeshData(std::move(recastMeshData))
, mSize(size) , mSize(size)
{} {}
}; };
@ -181,9 +110,9 @@ namespace DetourNavigator
return *this; return *this;
} }
NavMeshDataRef get() const const PreparedNavMeshData& get() const
{ {
return NavMeshDataRef {mIterator->mNavMeshData.mValue.get(), mIterator->mNavMeshData.mSize}; return *mIterator->mPreparedNavMeshData;
} }
operator bool() const operator bool() const
@ -208,11 +137,10 @@ namespace DetourNavigator
NavMeshTilesCache(const std::size_t maxNavMeshDataSize); NavMeshTilesCache(const std::size_t maxNavMeshDataSize);
Value get(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile, Value get(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile,
const RecastMesh& recastMesh, const std::vector<OffMeshConnection>& offMeshConnections); const RecastMesh& recastMesh);
Value set(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile, Value set(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile,
const RecastMesh& recastMesh, const std::vector<OffMeshConnection>& offMeshConnections, const RecastMesh& recastMesh, std::unique_ptr<PreparedNavMeshData>&& value);
NavMeshData&& value);
Stats getStats() const; Stats getStats() const;
@ -227,7 +155,7 @@ namespace DetourNavigator
std::size_t mGetCount; std::size_t mGetCount;
std::list<Item> mBusyItems; std::list<Item> mBusyItems;
std::list<Item> mFreeItems; std::list<Item> mFreeItems;
std::map<std::tuple<osg::Vec3f, TilePosition, NavMeshKeyRef>, ItemIterator, std::less<>> mValues; std::map<std::tuple<osg::Vec3f, TilePosition, std::reference_wrapper<const RecastMeshData>>, ItemIterator, std::less<>> mValues;
void removeLeastRecentlyUsed(); void removeLeastRecentlyUsed();

@ -1,4 +1,5 @@
#include "navmeshtileview.hpp" #include "navmeshtileview.hpp"
#include "ref.hpp"
#include <DetourCommon.h> #include <DetourCommon.h>
#include <DetourNavMesh.h> #include <DetourNavMesh.h>
@ -10,47 +11,9 @@
namespace namespace
{ {
template <typename T> using DetourNavigator::ArrayRef;
struct Ref using DetourNavigator::Ref;
{ using DetourNavigator::Span;
T& mRef;
explicit Ref(T& ref) : mRef(ref) {}
friend bool operator==(const Ref& lhs, const Ref& rhs)
{
return lhs.mRef == rhs.mRef;
}
};
template <typename T, std::size_t size>
struct ArrayRef
{
T (&mRef)[size];
explicit ArrayRef(T (&ref)[size]) : mRef(ref) {}
friend bool operator==(const ArrayRef& lhs, const ArrayRef& rhs)
{
return std::equal(std::begin(lhs.mRef), std::end(lhs.mRef), std::begin(rhs.mRef));
}
};
template <typename T>
struct Span
{
T* mBegin;
T* mEnd;
explicit Span(T* data, int size) : mBegin(data), mEnd(data + static_cast<std::size_t>(size)) {}
friend bool operator==(const Span& lhs, const Span& rhs)
{
// size is already equal if headers are equal
assert((lhs.mEnd - lhs.mBegin) == (rhs.mEnd - rhs.mBegin));
return std::equal(lhs.mBegin, lhs.mEnd, rhs.mBegin);
}
};
auto makeTuple(const dtMeshHeader& v) auto makeTuple(const dtMeshHeader& v)
{ {

@ -5,6 +5,7 @@
#include <osg/Vec3f> #include <osg/Vec3f>
#include <vector>
#include <tuple> #include <tuple>
namespace DetourNavigator namespace DetourNavigator

@ -0,0 +1,52 @@
#include "preparednavmeshdata.hpp"
#include "preparednavmeshdatatuple.hpp"
#include <Recast.h>
#include <RecastAlloc.h>
namespace
{
using namespace DetourNavigator;
void initPolyMeshDetail(rcPolyMeshDetail& value) noexcept
{
value.meshes = nullptr;
value.verts = nullptr;
value.tris = nullptr;
value.nmeshes = 0;
value.nverts = 0;
value.ntris = 0;
}
void freePolyMeshDetail(rcPolyMeshDetail& value) noexcept
{
rcFree(value.meshes);
rcFree(value.verts);
rcFree(value.tris);
}
}
template <class T>
inline constexpr auto operator==(const T& lhs, const T& rhs) noexcept
-> std::enable_if_t<std::is_same_v<std::void_t<decltype(makeTuple(lhs))>, void>, bool>
{
return makeTuple(lhs) == makeTuple(rhs);
}
namespace DetourNavigator
{
PreparedNavMeshData::PreparedNavMeshData() noexcept
{
initPolyMeshDetail(mPolyMeshDetail);
}
PreparedNavMeshData::~PreparedNavMeshData() noexcept
{
freePolyMeshDetail(mPolyMeshDetail);
}
bool operator==(const PreparedNavMeshData& lhs, const PreparedNavMeshData& rhs) noexcept
{
return makeTuple(lhs) == makeTuple(rhs);
}
}

@ -0,0 +1,48 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_PREPAREDNAVMESHDATA_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_PREPAREDNAVMESHDATA_H
#include <Recast.h>
#include <cstddef>
namespace DetourNavigator
{
struct PreparedNavMeshData
{
unsigned int mUserId = 0;
float mCellSize = 0;
float mCellHeight = 0;
rcPolyMesh mPolyMesh;
rcPolyMeshDetail mPolyMeshDetail;
PreparedNavMeshData() noexcept;
PreparedNavMeshData(const PreparedNavMeshData&) = delete;
~PreparedNavMeshData() noexcept;
friend bool operator==(const PreparedNavMeshData& lhs, const PreparedNavMeshData& rhs) noexcept;
};
inline constexpr std::size_t getSize(const rcPolyMesh& value) noexcept
{
return static_cast<std::size_t>(3 * value.nverts) * sizeof(*value.verts)
+ static_cast<std::size_t>(value.maxpolys * 2 * value.nvp) * sizeof(*value.polys)
+ static_cast<std::size_t>(value.maxpolys) * sizeof(*value.regs)
+ static_cast<std::size_t>(value.maxpolys) * sizeof(*value.flags)
+ static_cast<std::size_t>(value.maxpolys) * sizeof(*value.areas);
}
inline constexpr std::size_t getSize(const rcPolyMeshDetail& value) noexcept
{
return static_cast<std::size_t>(4 * value.nmeshes) * sizeof(*value.meshes)
+ static_cast<std::size_t>(4 * value.ntris) * sizeof(*value.tris)
+ static_cast<std::size_t>(3 * value.nverts) * sizeof(*value.verts);
}
inline constexpr std::size_t getSize(const PreparedNavMeshData& value) noexcept
{
return getSize(value.mPolyMesh) + getSize(value.mPolyMeshDetail);
}
}
#endif

@ -0,0 +1,51 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_PREPAREDNAVMESHDATATUPLE_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_PREPAREDNAVMESHDATATUPLE_H
#include "preparednavmeshdata.hpp"
#include "ref.hpp"
#include <Recast.h>
#include <tuple>
namespace DetourNavigator
{
constexpr auto makeTuple(const rcPolyMesh& v) noexcept
{
return std::tuple(
Span(v.verts, 3 * v.nverts),
Span(v.polys, v.maxpolys * 2 * v.nvp),
Span(v.regs, v.maxpolys),
Span(v.flags, v.maxpolys),
Span(v.areas, v.maxpolys),
ArrayRef(v.bmin),
ArrayRef(v.bmax),
v.cs,
v.ch,
v.borderSize,
v.maxEdgeError
);
}
constexpr auto makeTuple(const rcPolyMeshDetail& v) noexcept
{
return std::tuple(
Span(v.meshes, 4 * v.nmeshes),
Span(v.verts, 3 * v.nverts),
Span(v.tris, 4 * v.ntris)
);
}
constexpr auto makeTuple(const PreparedNavMeshData& v) noexcept
{
return std::tuple(
v.mUserId,
v.mCellHeight,
v.mCellSize,
Ref(v.mPolyMesh),
Ref(v.mPolyMeshDetail)
);
}
}
#endif

@ -80,6 +80,15 @@ namespace DetourNavigator
std::vector<AreaType> mAreaTypes; std::vector<AreaType> mAreaTypes;
std::vector<Water> mWater; std::vector<Water> mWater;
Bounds mBounds; Bounds mBounds;
friend inline std::size_t getSize(const RecastMesh& recastMesh) noexcept
{
const std::size_t indicesSize = recastMesh.mIndices.size() * sizeof(int);
const std::size_t verticesSize = recastMesh.mVertices.size() * sizeof(float);
const std::size_t areaTypesSize = recastMesh.mAreaTypes.size() * sizeof(AreaType);
const std::size_t waterSize = recastMesh.mWater.size() * sizeof(RecastMesh::Water);
return indicesSize + verticesSize + areaTypesSize + waterSize;
}
}; };
inline bool operator<(const RecastMesh::Water& lhs, const RecastMesh::Water& rhs) inline bool operator<(const RecastMesh::Water& lhs, const RecastMesh::Water& rhs)

@ -0,0 +1,55 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_REF_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_REF_H
#include <algorithm>
#include <cassert>
namespace DetourNavigator
{
template <typename T>
struct Ref
{
T& mRef;
constexpr explicit Ref(T& ref) noexcept : mRef(ref) {}
friend bool operator==(const Ref& lhs, const Ref& rhs)
{
return lhs.mRef == rhs.mRef;
}
};
template <typename T, std::size_t size>
struct ArrayRef
{
T (&mRef)[size];
constexpr explicit ArrayRef(T (&ref)[size]) noexcept : mRef(ref) {}
friend bool operator==(const ArrayRef& lhs, const ArrayRef& rhs)
{
return std::equal(std::begin(lhs.mRef), std::end(lhs.mRef), std::begin(rhs.mRef));
}
};
template <typename T>
struct Span
{
T* mBegin;
T* mEnd;
constexpr explicit Span(T* data, int size) noexcept
: mBegin(data)
, mEnd(data + static_cast<std::size_t>(size))
{}
friend bool operator==(const Span& lhs, const Span& rhs)
{
// size is already equal if headers are equal
assert((lhs.mEnd - lhs.mBegin) == (rhs.mEnd - rhs.mBegin));
return std::equal(lhs.mBegin, lhs.mEnd, rhs.mBegin);
}
};
}
#endif
Loading…
Cancel
Save