1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-01-29 08:15:35 +00:00

Merge branch 'navmesh_raw' into 'master'

Add to navigator unmodified water and heightfield data

See merge request OpenMW/openmw!1373
This commit is contained in:
psi29a 2021-12-08 13:36:17 +00:00
commit e04e139f37
28 changed files with 841 additions and 702 deletions

View file

@ -25,18 +25,10 @@ namespace
}; };
template <typename Random> template <typename Random>
TilePosition generateTilePosition(int max, Random& random) osg::Vec2i generateVec2i(int max, Random& random)
{ {
std::uniform_int_distribution<int> distribution(0, max); std::uniform_int_distribution<int> distribution(0, max);
return TilePosition(distribution(random), distribution(random)); return osg::Vec2i(distribution(random), distribution(random));
}
template <typename Random>
TileBounds generateTileBounds(Random& random)
{
std::uniform_real_distribution<float> distribution(0.0, 1.0);
const osg::Vec2f min(distribution(random), distribution(random));
return TileBounds {min, min + osg::Vec2f(1.0, 1.0)};
} }
template <typename Random> template <typename Random>
@ -91,8 +83,7 @@ namespace
{ {
std::uniform_real_distribution<float> distribution(0.0, 1.0); std::uniform_real_distribution<float> distribution(0.0, 1.0);
std::generate_n(out, count, [&] { std::generate_n(out, count, [&] {
const osg::Vec3f shift(distribution(random), distribution(random), distribution(random)); return CellWater {generateVec2i(1000, random), Water {ESM::Land::REAL_SIZE, distribution(random)}};
return Cell {1, shift};
}); });
} }
@ -117,16 +108,18 @@ namespace
{ {
std::uniform_real_distribution<float> distribution(0.0, 1.0); std::uniform_real_distribution<float> distribution(0.0, 1.0);
Heightfield result; Heightfield result;
result.mBounds = generateTileBounds(random); result.mCellPosition = generateVec2i(1000, random);
result.mCellSize = ESM::Land::REAL_SIZE;
result.mMinHeight = distribution(random); result.mMinHeight = distribution(random);
result.mMaxHeight = result.mMinHeight + 1.0; result.mMaxHeight = result.mMinHeight + 1.0;
result.mShift = osg::Vec3f(distribution(random), distribution(random), distribution(random));
result.mScale = distribution(random);
result.mLength = static_cast<std::uint8_t>(ESM::Land::LAND_SIZE); result.mLength = static_cast<std::uint8_t>(ESM::Land::LAND_SIZE);
std::generate_n(std::back_inserter(result.mHeights), ESM::Land::LAND_NUM_VERTS, [&] std::generate_n(std::back_inserter(result.mHeights), ESM::Land::LAND_NUM_VERTS, [&]
{ {
return distribution(random); return distribution(random);
}); });
result.mOriginalSize = ESM::Land::LAND_SIZE;
result.mMinX = 0;
result.mMinY = 0;
return result; return result;
} }
@ -135,7 +128,8 @@ namespace
{ {
std::uniform_real_distribution<float> distribution(0.0, 1.0); std::uniform_real_distribution<float> distribution(0.0, 1.0);
FlatHeightfield result; FlatHeightfield result;
result.mBounds = generateTileBounds(random); result.mCellPosition = generateVec2i(1000, random);
result.mCellSize = ESM::Land::REAL_SIZE;
result.mHeight = distribution(random); result.mHeight = distribution(random);
return result; return result;
} }
@ -144,11 +138,11 @@ namespace
Key generateKey(std::size_t triangles, Random& random) Key generateKey(std::size_t triangles, Random& random)
{ {
const osg::Vec3f agentHalfExtents = generateAgentHalfExtents(0.5, 1.5, random); const osg::Vec3f agentHalfExtents = generateAgentHalfExtents(0.5, 1.5, random);
const TilePosition tilePosition = generateTilePosition(10000, random); const TilePosition tilePosition = generateVec2i(10000, random);
const std::size_t generation = std::uniform_int_distribution<std::size_t>(0, 100)(random); const std::size_t generation = std::uniform_int_distribution<std::size_t>(0, 100)(random);
const std::size_t revision = std::uniform_int_distribution<std::size_t>(0, 10000)(random); const std::size_t revision = std::uniform_int_distribution<std::size_t>(0, 10000)(random);
Mesh mesh = generateMesh(triangles, random); Mesh mesh = generateMesh(triangles, random);
std::vector<Cell> water; std::vector<CellWater> water;
generateWater(std::back_inserter(water), 1, random); generateWater(std::back_inserter(water), 1, random);
RecastMesh recastMesh(generation, revision, std::move(mesh), std::move(water), RecastMesh recastMesh(generation, revision, std::move(mesh), std::move(water),
{generateHeightfield(random)}, {generateFlatHeightfield(random)}); {generateHeightfield(random)}, {generateFlatHeightfield(random)});

View file

@ -404,7 +404,7 @@ namespace MWWorld
return heights; return heights;
} }
} (); } ();
mNavigator.addHeightfield(cellPosition, ESM::Land::REAL_SIZE, shift, shape); mNavigator.addHeightfield(cellPosition, ESM::Land::REAL_SIZE, shape);
} }
} }
@ -434,18 +434,11 @@ namespace MWWorld
if (cell->getCell()->isExterior()) if (cell->getCell()->isExterior())
{ {
if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) if (const auto heightField = mPhysics->getHeightField(cellX, cellY))
{ mNavigator.addWater(osg::Vec2i(cellX, cellY), ESM::Land::REAL_SIZE, waterLevel);
const btTransform& transform =heightField->getCollisionObject()->getWorldTransform();
mNavigator.addWater(osg::Vec2i(cellX, cellY), ESM::Land::REAL_SIZE,
osg::Vec3f(static_cast<float>(transform.getOrigin().x()),
static_cast<float>(transform.getOrigin().y()),
waterLevel));
}
} }
else else
{ {
mNavigator.addWater(osg::Vec2i(cellX, cellY), std::numeric_limits<int>::max(), mNavigator.addWater(osg::Vec2i(cellX, cellY), std::numeric_limits<int>::max(), waterLevel);
osg::Vec3f(0, 0, waterLevel));
} }
} }
else else

File diff suppressed because it is too large Load diff

View file

@ -144,14 +144,14 @@ namespace
const std::size_t mGeneration = 0; const std::size_t mGeneration = 0;
const std::size_t mRevision = 0; const std::size_t mRevision = 0;
const Mesh mMesh {makeMesh()}; const Mesh mMesh {makeMesh()};
const std::vector<Cell> mWater {}; const std::vector<CellWater> mWater {};
const std::vector<Heightfield> mHeightfields {}; const std::vector<Heightfield> mHeightfields {};
const std::vector<FlatHeightfield> mFlatHeightfields {}; const std::vector<FlatHeightfield> mFlatHeightfields {};
const RecastMesh mRecastMesh {mGeneration, mRevision, mMesh, mWater, mHeightfields, mFlatHeightfields}; const RecastMesh mRecastMesh {mGeneration, mRevision, mMesh, mWater, mHeightfields, mFlatHeightfields};
std::unique_ptr<PreparedNavMeshData> mPreparedNavMeshData {makePeparedNavMeshData(3)}; std::unique_ptr<PreparedNavMeshData> mPreparedNavMeshData {makePeparedNavMeshData(3)};
const std::size_t mRecastMeshSize = sizeof(mRecastMesh) + getSize(mRecastMesh); const std::size_t mRecastMeshSize = sizeof(mRecastMesh) + getSize(mRecastMesh);
const std::size_t mRecastMeshWithWaterSize = mRecastMeshSize + sizeof(Cell); const std::size_t mRecastMeshWithWaterSize = mRecastMeshSize + sizeof(CellWater);
const std::size_t mPreparedNavMeshDataSize = sizeof(*mPreparedNavMeshData) + getSize(*mPreparedNavMeshData); const std::size_t mPreparedNavMeshDataSize = sizeof(*mPreparedNavMeshData) + getSize(*mPreparedNavMeshData);
}; };
@ -234,7 +234,7 @@ namespace
{ {
const std::size_t maxSize = 1; const std::size_t maxSize = 1;
NavMeshTilesCache cache(maxSize); NavMeshTilesCache cache(maxSize);
const std::vector<Cell> water {1, Cell {1, osg::Vec3f()}}; const std::vector<CellWater> water(1, CellWater {osg::Vec2i(), Water {1, 0.0f}});
const RecastMesh unexistentRecastMesh {mGeneration, mRevision, mMesh, water, mHeightfields, mFlatHeightfields}; const RecastMesh unexistentRecastMesh {mGeneration, mRevision, mMesh, water, mHeightfields, mFlatHeightfields};
cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, std::move(mPreparedNavMeshData)); cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, std::move(mPreparedNavMeshData));
@ -246,7 +246,7 @@ namespace
const std::size_t maxSize = mRecastMeshWithWaterSize + mPreparedNavMeshDataSize; const std::size_t maxSize = mRecastMeshWithWaterSize + mPreparedNavMeshDataSize;
NavMeshTilesCache cache(maxSize); NavMeshTilesCache cache(maxSize);
const std::vector<Cell> water {1, Cell {1, osg::Vec3f()}}; const std::vector<CellWater> water(1, CellWater {osg::Vec2i(), Water {1, 0.0f}});
const RecastMesh anotherRecastMesh {mGeneration, mRevision, mMesh, water, mHeightfields, mFlatHeightfields}; const RecastMesh anotherRecastMesh {mGeneration, mRevision, mMesh, water, mHeightfields, mFlatHeightfields};
auto anotherPreparedNavMeshData = makePeparedNavMeshData(3); auto anotherPreparedNavMeshData = makePeparedNavMeshData(3);
const auto copy = clone(*anotherPreparedNavMeshData); const auto copy = clone(*anotherPreparedNavMeshData);
@ -264,7 +264,7 @@ namespace
const std::size_t maxSize = mRecastMeshWithWaterSize + mPreparedNavMeshDataSize; const std::size_t maxSize = mRecastMeshWithWaterSize + mPreparedNavMeshDataSize;
NavMeshTilesCache cache(maxSize); NavMeshTilesCache cache(maxSize);
const std::vector<Cell> water {1, Cell {1, osg::Vec3f()}}; const std::vector<CellWater> water(1, CellWater {osg::Vec2i(), Water {1, 0.0f}});
const RecastMesh anotherRecastMesh {mGeneration, mRevision, mMesh, water, mHeightfields, mFlatHeightfields}; const RecastMesh anotherRecastMesh {mGeneration, mRevision, mMesh, water, mHeightfields, mFlatHeightfields};
auto anotherPreparedNavMeshData = makePeparedNavMeshData(3); auto anotherPreparedNavMeshData = makePeparedNavMeshData(3);
@ -280,12 +280,12 @@ namespace
NavMeshTilesCache cache(maxSize); NavMeshTilesCache cache(maxSize);
const auto copy = clone(*mPreparedNavMeshData); const auto copy = clone(*mPreparedNavMeshData);
const std::vector<Cell> leastRecentlySetWater {1, Cell {1, osg::Vec3f()}}; const std::vector<CellWater> leastRecentlySetWater(1, CellWater {osg::Vec2i(), Water {1, 0.0f}});
const RecastMesh leastRecentlySetRecastMesh {mGeneration, mRevision, mMesh, leastRecentlySetWater, const RecastMesh leastRecentlySetRecastMesh {mGeneration, mRevision, mMesh, leastRecentlySetWater,
mHeightfields, mFlatHeightfields}; mHeightfields, mFlatHeightfields};
auto leastRecentlySetData = makePeparedNavMeshData(3); auto leastRecentlySetData = makePeparedNavMeshData(3);
const std::vector<Cell> mostRecentlySetWater {1, Cell {2, osg::Vec3f()}}; const std::vector<CellWater> mostRecentlySetWater(1, CellWater {osg::Vec2i(), Water {2, 0.0f}});
const RecastMesh mostRecentlySetRecastMesh {mGeneration, mRevision, mMesh, mostRecentlySetWater, const RecastMesh mostRecentlySetRecastMesh {mGeneration, mRevision, mMesh, mostRecentlySetWater,
mHeightfields, mFlatHeightfields}; mHeightfields, mFlatHeightfields};
auto mostRecentlySetData = makePeparedNavMeshData(3); auto mostRecentlySetData = makePeparedNavMeshData(3);
@ -308,13 +308,13 @@ namespace
const std::size_t maxSize = 2 * (mRecastMeshWithWaterSize + mPreparedNavMeshDataSize); const std::size_t maxSize = 2 * (mRecastMeshWithWaterSize + mPreparedNavMeshDataSize);
NavMeshTilesCache cache(maxSize); NavMeshTilesCache cache(maxSize);
const std::vector<Cell> leastRecentlyUsedWater {1, Cell {1, osg::Vec3f()}}; const std::vector<CellWater> leastRecentlyUsedWater(1, CellWater {osg::Vec2i(), Water {1, 0.0f}});
const RecastMesh leastRecentlyUsedRecastMesh {mGeneration, mRevision, mMesh, leastRecentlyUsedWater, const RecastMesh leastRecentlyUsedRecastMesh {mGeneration, mRevision, mMesh, leastRecentlyUsedWater,
mHeightfields, mFlatHeightfields}; mHeightfields, mFlatHeightfields};
auto leastRecentlyUsedData = makePeparedNavMeshData(3); auto leastRecentlyUsedData = makePeparedNavMeshData(3);
const auto leastRecentlyUsedCopy = clone(*leastRecentlyUsedData); const auto leastRecentlyUsedCopy = clone(*leastRecentlyUsedData);
const std::vector<Cell> mostRecentlyUsedWater {1, Cell {2, osg::Vec3f()}}; const std::vector<CellWater> mostRecentlyUsedWater(1, CellWater {osg::Vec2i(), Water {2, 0.0f}});
const RecastMesh mostRecentlyUsedRecastMesh {mGeneration, mRevision, mMesh, mostRecentlyUsedWater, const RecastMesh mostRecentlyUsedRecastMesh {mGeneration, mRevision, mMesh, mostRecentlyUsedWater,
mHeightfields, mFlatHeightfields}; mHeightfields, mFlatHeightfields};
auto mostRecentlyUsedData = makePeparedNavMeshData(3); auto mostRecentlyUsedData = makePeparedNavMeshData(3);
@ -349,7 +349,7 @@ namespace
const std::size_t maxSize = 2 * (mRecastMeshWithWaterSize + mPreparedNavMeshDataSize); const std::size_t maxSize = 2 * (mRecastMeshWithWaterSize + mPreparedNavMeshDataSize);
NavMeshTilesCache cache(maxSize); NavMeshTilesCache cache(maxSize);
const std::vector<Cell> water {1, Cell {1, osg::Vec3f()}}; const std::vector<CellWater> water(1, CellWater {osg::Vec2i(), Water {1, 0.0f}});
const RecastMesh tooLargeRecastMesh {mGeneration, mRevision, mMesh, water, const RecastMesh tooLargeRecastMesh {mGeneration, mRevision, mMesh, water,
mHeightfields, mFlatHeightfields}; mHeightfields, mFlatHeightfields};
auto tooLargeData = makePeparedNavMeshData(10); auto tooLargeData = makePeparedNavMeshData(10);
@ -364,12 +364,12 @@ namespace
const std::size_t maxSize = 2 * (mRecastMeshWithWaterSize + mPreparedNavMeshDataSize); const std::size_t maxSize = 2 * (mRecastMeshWithWaterSize + mPreparedNavMeshDataSize);
NavMeshTilesCache cache(maxSize); NavMeshTilesCache cache(maxSize);
const std::vector<Cell> anotherWater {1, Cell {1, osg::Vec3f()}}; const std::vector<CellWater> anotherWater(1, CellWater {osg::Vec2i(), Water {1, 0.0f}});
const RecastMesh anotherRecastMesh {mGeneration, mRevision, mMesh, anotherWater, const RecastMesh anotherRecastMesh {mGeneration, mRevision, mMesh, anotherWater,
mHeightfields, mFlatHeightfields}; mHeightfields, mFlatHeightfields};
auto anotherData = makePeparedNavMeshData(3); auto anotherData = makePeparedNavMeshData(3);
const std::vector<Cell> tooLargeWater {1, Cell {2, osg::Vec3f()}}; const std::vector<CellWater> tooLargeWater(1, CellWater {osg::Vec2i(), Water {2, 0.0f}});
const RecastMesh tooLargeRecastMesh {mGeneration, mRevision, mMesh, tooLargeWater, const RecastMesh tooLargeRecastMesh {mGeneration, mRevision, mMesh, tooLargeWater,
mHeightfields, mFlatHeightfields}; mHeightfields, mFlatHeightfields};
auto tooLargeData = makePeparedNavMeshData(10); auto tooLargeData = makePeparedNavMeshData(10);
@ -390,7 +390,7 @@ namespace
const std::size_t maxSize = mRecastMeshWithWaterSize + mPreparedNavMeshDataSize; const std::size_t maxSize = mRecastMeshWithWaterSize + mPreparedNavMeshDataSize;
NavMeshTilesCache cache(maxSize); NavMeshTilesCache cache(maxSize);
const std::vector<Cell> water {1, Cell {1, osg::Vec3f()}}; const std::vector<CellWater> water(1, CellWater {osg::Vec2i(), Water {1, 0.0f}});
const RecastMesh anotherRecastMesh {mGeneration, mRevision, mMesh, water, mHeightfields, mFlatHeightfields}; const RecastMesh anotherRecastMesh {mGeneration, mRevision, mMesh, water, mHeightfields, mFlatHeightfields};
auto anotherData = makePeparedNavMeshData(3); auto anotherData = makePeparedNavMeshData(3);
@ -409,7 +409,7 @@ namespace
const std::size_t maxSize = mRecastMeshWithWaterSize + mPreparedNavMeshDataSize; const std::size_t maxSize = mRecastMeshWithWaterSize + mPreparedNavMeshDataSize;
NavMeshTilesCache cache(maxSize); NavMeshTilesCache cache(maxSize);
const std::vector<Cell> water {1, Cell {1, osg::Vec3f()}}; const std::vector<CellWater> water(1, CellWater {osg::Vec2i(), Water {1, 0.0f}});
const RecastMesh anotherRecastMesh {mGeneration, mRevision, mMesh, water, mHeightfields, mFlatHeightfields}; const RecastMesh anotherRecastMesh {mGeneration, mRevision, mMesh, water, mHeightfields, mFlatHeightfields};
auto anotherData = makePeparedNavMeshData(3); auto anotherData = makePeparedNavMeshData(3);

View file

@ -48,7 +48,7 @@ namespace testing
template <> template <>
inline testing::Message& Message::operator <<(const osg::Vec3f& value) inline testing::Message& Message::operator <<(const osg::Vec3f& value)
{ {
return (*this) << "osg::Vec3f(" << std::setprecision(std::numeric_limits<float>::max_exponent10) << value.x() return (*this) << "Vec3fEq(" << std::setprecision(std::numeric_limits<float>::max_exponent10) << value.x()
<< ", " << std::setprecision(std::numeric_limits<float>::max_exponent10) << value.y() << ", " << std::setprecision(std::numeric_limits<float>::max_exponent10) << value.y()
<< ", " << std::setprecision(std::numeric_limits<float>::max_exponent10) << value.z() << ", " << std::setprecision(std::numeric_limits<float>::max_exponent10) << value.z()
<< ')'; << ')';

View file

@ -23,9 +23,16 @@
namespace DetourNavigator namespace DetourNavigator
{ {
static inline bool operator ==(const Cell& lhs, const Cell& rhs) static inline bool operator ==(const Water& lhs, const Water& rhs)
{ {
return lhs.mSize == rhs.mSize && lhs.mShift == rhs.mShift; const auto tie = [] (const Water& v) { return std::tie(v.mCellSize, v.mLevel); };
return tie(lhs) == tie(rhs);
}
static inline bool operator ==(const CellWater& lhs, const CellWater& rhs)
{
const auto tie = [] (const CellWater& v) { return std::tie(v.mCellPosition, v.mWater); };
return tie(lhs) == tie(rhs);
} }
static inline bool operator==(const Heightfield& lhs, const Heightfield& rhs) static inline bool operator==(const Heightfield& lhs, const Heightfield& rhs)
@ -35,26 +42,40 @@ namespace DetourNavigator
static inline bool operator==(const FlatHeightfield& lhs, const FlatHeightfield& rhs) static inline bool operator==(const FlatHeightfield& lhs, const FlatHeightfield& rhs)
{ {
return std::tie(lhs.mBounds, lhs.mHeight) == std::tie(rhs.mBounds, rhs.mHeight); const auto tie = [] (const FlatHeightfield& v)
{
return std::tie(v.mCellPosition, v.mCellSize, v.mHeight);
};
return tie(lhs) == tie(rhs);
}
static inline std::ostream& operator<<(std::ostream& s, const Water& v)
{
return s << "Water {" << v.mCellSize << ", " << v.mLevel << "}";
}
static inline std::ostream& operator<<(std::ostream& s, const CellWater& v)
{
return s << "CellWater {" << v.mCellPosition << ", " << v.mWater << "}";
} }
static inline std::ostream& operator<<(std::ostream& s, const FlatHeightfield& v) static inline std::ostream& operator<<(std::ostream& s, const FlatHeightfield& v)
{ {
return s << "FlatHeightfield {" << v.mBounds << ", " << v.mHeight << "}"; return s << "FlatHeightfield {" << v.mCellPosition << ", " << v.mCellSize << ", " << v.mHeight << "}";
} }
static inline std::ostream& operator<<(std::ostream& s, const Heightfield& v) static inline std::ostream& operator<<(std::ostream& s, const Heightfield& v)
{ {
s << "Heightfield {.mBounds=" << v.mBounds s << "Heightfield {.mCellPosition=" << v.mCellPosition
<< ", .mLength=" << int(v.mLength) << ", .mCellSize=" << v.mCellSize
<< ", .mLength=" << static_cast<int>(v.mLength)
<< ", .mMinHeight=" << v.mMinHeight << ", .mMinHeight=" << v.mMinHeight
<< ", .mMaxHeight=" << v.mMaxHeight << ", .mMaxHeight=" << v.mMaxHeight
<< ", .mShift=" << v.mShift
<< ", .mScale=" << v.mScale
<< ", .mHeights={"; << ", .mHeights={";
for (float h : v.mHeights) for (float h : v.mHeights)
s << h << ", "; s << h << ", ";
return s << "}}"; s << "}";
return s << ", .mOriginalSize=" << v.mOriginalSize << "}";
} }
} }
@ -435,10 +456,10 @@ namespace
TEST_F(DetourNavigatorRecastMeshBuilderTest, add_water_then_get_water_should_return_it) TEST_F(DetourNavigatorRecastMeshBuilderTest, add_water_then_get_water_should_return_it)
{ {
RecastMeshBuilder builder(mBounds); RecastMeshBuilder builder(mBounds);
builder.addWater(1000, osg::Vec3f(100, 200, 300)); builder.addWater(osg::Vec2i(1, 2), Water {1000, 300.0f});
const auto recastMesh = std::move(builder).create(mGeneration, mRevision); const auto recastMesh = std::move(builder).create(mGeneration, mRevision);
EXPECT_EQ(recastMesh->getWater(), std::vector<Cell>({ EXPECT_EQ(recastMesh->getWater(), std::vector<CellWater>({
Cell {1000, osg::Vec3f(100, 200, 300)} CellWater {osg::Vec2i(1, 2), Water {1000, 300.0f}}
})); }));
} }
@ -464,62 +485,111 @@ namespace
TEST_F(DetourNavigatorRecastMeshBuilderTest, add_flat_heightfield_should_add_intersection) TEST_F(DetourNavigatorRecastMeshBuilderTest, add_flat_heightfield_should_add_intersection)
{ {
mBounds.mMin = osg::Vec2f(0, 0); const osg::Vec2i cellPosition(0, 0);
const int cellSize = 1000;
const float height = 10;
mBounds.mMin = osg::Vec2f(100, 100);
RecastMeshBuilder builder(mBounds); RecastMeshBuilder builder(mBounds);
builder.addHeightfield(1000, osg::Vec3f(1, 2, 3), 10); builder.addHeightfield(cellPosition, cellSize, height);
const auto recastMesh = std::move(builder).create(mGeneration, mRevision); const auto recastMesh = std::move(builder).create(mGeneration, mRevision);
EXPECT_EQ(recastMesh->getFlatHeightfields(), std::vector<FlatHeightfield>({ EXPECT_EQ(recastMesh->getFlatHeightfields(), std::vector<FlatHeightfield>({
FlatHeightfield {TileBounds {osg::Vec2f(0, 0), osg::Vec2f(501, 502)}, 13}, FlatHeightfield {cellPosition, cellSize, height},
})); }));
} }
TEST_F(DetourNavigatorRecastMeshBuilderTest, add_heightfield_inside_tile) TEST_F(DetourNavigatorRecastMeshBuilderTest, add_heightfield_inside_tile)
{ {
constexpr std::array<float, 9> heights {{ constexpr std::size_t size = 3;
constexpr std::array<float, size * size> heights {{
0, 1, 2, 0, 1, 2,
3, 4, 5, 3, 4, 5,
6, 7, 8, 6, 7, 8,
}}; }};
const osg::Vec2i cellPosition(0, 0);
const int cellSize = 1000;
const float minHeight = 0;
const float maxHeight = 8;
RecastMeshBuilder builder(mBounds); RecastMeshBuilder builder(mBounds);
builder.addHeightfield(1000, osg::Vec3f(1, 2, 3), heights.data(), 3, 0, 8); builder.addHeightfield(cellPosition, cellSize, heights.data(), size, minHeight, maxHeight);
const auto recastMesh = std::move(builder).create(mGeneration, mRevision); const auto recastMesh = std::move(builder).create(mGeneration, mRevision);
Heightfield expected; Heightfield expected;
expected.mBounds = TileBounds {osg::Vec2f(-499, -498), osg::Vec2f(501, 502)}; expected.mCellPosition = cellPosition;
expected.mLength = 3; expected.mCellSize = cellSize;
expected.mMinHeight = 0; expected.mLength = size;
expected.mMaxHeight = 8; expected.mMinHeight = minHeight;
expected.mShift = osg::Vec3f(-499, -498, 3); expected.mMaxHeight = maxHeight;
expected.mScale = 500;
expected.mHeights = { expected.mHeights = {
0, 1, 2, 0, 1, 2,
3, 4, 5, 3, 4, 5,
6, 7, 8, 6, 7, 8,
}; };
expected.mOriginalSize = 3;
expected.mMinX = 0;
expected.mMinY = 0;
EXPECT_EQ(recastMesh->getHeightfields(), std::vector<Heightfield>({expected}));
}
TEST_F(DetourNavigatorRecastMeshBuilderTest, add_heightfield_to_shifted_cell_inside_tile)
{
constexpr std::size_t size = 3;
constexpr std::array<float, size * size> heights {{
0, 1, 2,
3, 4, 5,
6, 7, 8,
}};
const osg::Vec2i cellPosition(1, 2);
const int cellSize = 1000;
const float minHeight = 0;
const float maxHeight = 8;
RecastMeshBuilder builder(maxCellTileBounds(cellPosition, cellSize));
builder.addHeightfield(cellPosition, cellSize, heights.data(), size, minHeight, maxHeight);
const auto recastMesh = std::move(builder).create(mGeneration, mRevision);
Heightfield expected;
expected.mCellPosition = cellPosition;
expected.mCellSize = cellSize;
expected.mLength = size;
expected.mMinHeight = minHeight;
expected.mMaxHeight = maxHeight;
expected.mHeights = {
0, 1, 2,
3, 4, 5,
6, 7, 8,
};
expected.mOriginalSize = 3;
expected.mMinX = 0;
expected.mMinY = 0;
EXPECT_EQ(recastMesh->getHeightfields(), std::vector<Heightfield>({expected})); EXPECT_EQ(recastMesh->getHeightfields(), std::vector<Heightfield>({expected}));
} }
TEST_F(DetourNavigatorRecastMeshBuilderTest, add_heightfield_should_add_intersection) TEST_F(DetourNavigatorRecastMeshBuilderTest, add_heightfield_should_add_intersection)
{ {
constexpr std::array<float, 9> heights {{ constexpr std::size_t size = 3;
constexpr std::array<float, 3 * 3> heights {{
0, 1, 2, 0, 1, 2,
3, 4, 5, 3, 4, 5,
6, 7, 8, 6, 7, 8,
}}; }};
mBounds.mMin = osg::Vec2f(250, 250); const osg::Vec2i cellPosition(0, 0);
const int cellSize = 1000;
const float minHeight = 0;
const float maxHeight = 8;
mBounds.mMin = osg::Vec2f(750, 750);
RecastMeshBuilder builder(mBounds); RecastMeshBuilder builder(mBounds);
builder.addHeightfield(1000, osg::Vec3f(-1, -2, 3), heights.data(), 3, 0, 8); builder.addHeightfield(cellPosition, cellSize, heights.data(), size, minHeight, maxHeight);
const auto recastMesh = std::move(builder).create(mGeneration, mRevision); const auto recastMesh = std::move(builder).create(mGeneration, mRevision);
Heightfield expected; Heightfield expected;
expected.mBounds = TileBounds {osg::Vec2f(250, 250), osg::Vec2f(499, 498)}; expected.mCellPosition = cellPosition;
expected.mCellSize = cellSize;
expected.mLength = 2; expected.mLength = 2;
expected.mMinHeight = 0; expected.mMinHeight = 0;
expected.mMaxHeight = 8; expected.mMaxHeight = 8;
expected.mShift = osg::Vec3f(-1, -2, 3);
expected.mScale = 500;
expected.mHeights = { expected.mHeights = {
4, 5, 4, 5,
7, 8, 7, 8,
}; };
expected.mOriginalSize = 3;
expected.mMinX = 1;
expected.mMinY = 1;
EXPECT_EQ(recastMesh->getHeightfields(), std::vector<Heightfield>({expected})); EXPECT_EQ(recastMesh->getHeightfields(), std::vector<Heightfield>({expected}));
} }
} }

View file

@ -264,7 +264,7 @@ namespace
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
const osg::Vec2i cellPosition(0, 0); const osg::Vec2i cellPosition(0, 0);
const int cellSize = 8192; const int cellSize = 8192;
EXPECT_TRUE(manager.addWater(cellPosition, cellSize, osg::Vec3f())); EXPECT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f));
} }
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_water_for_not_max_int_should_add_new_tiles) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_water_for_not_max_int_should_add_new_tiles)
@ -272,9 +272,9 @@ namespace
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
const osg::Vec2i cellPosition(0, 0); const osg::Vec2i cellPosition(0, 0);
const int cellSize = 8192; const int cellSize = 8192;
ASSERT_TRUE(manager.addWater(cellPosition, cellSize, osg::Vec3f())); ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f));
for (int x = -6; x < 6; ++x) for (int x = -1; x < 12; ++x)
for (int y = -6; y < 6; ++y) for (int y = -1; y < 12; ++y)
ASSERT_NE(manager.getMesh(TilePosition(x, y)), nullptr); ASSERT_NE(manager.getMesh(TilePosition(x, y)), nullptr);
} }
@ -286,7 +286,7 @@ namespace
ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground));
const osg::Vec2i cellPosition(0, 0); const osg::Vec2i cellPosition(0, 0);
const int cellSize = std::numeric_limits<int>::max(); const int cellSize = std::numeric_limits<int>::max();
ASSERT_TRUE(manager.addWater(cellPosition, cellSize, osg::Vec3f())); ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f));
for (int x = -6; x < 6; ++x) for (int x = -6; x < 6; ++x)
for (int y = -6; y < 6; ++y) for (int y = -6; y < 6; ++y)
ASSERT_EQ(manager.getMesh(TilePosition(x, y)) != nullptr, -1 <= x && x <= 0 && -1 <= y && y <= 0); ASSERT_EQ(manager.getMesh(TilePosition(x, y)) != nullptr, -1 <= x && x <= 0 && -1 <= y && y <= 0);
@ -303,10 +303,10 @@ namespace
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
const osg::Vec2i cellPosition(0, 0); const osg::Vec2i cellPosition(0, 0);
const int cellSize = 8192; const int cellSize = 8192;
ASSERT_TRUE(manager.addWater(cellPosition, cellSize, osg::Vec3f())); ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f));
const auto result = manager.removeWater(cellPosition); const auto result = manager.removeWater(cellPosition);
ASSERT_TRUE(result.has_value()); ASSERT_TRUE(result.has_value());
EXPECT_EQ(result->mSize, cellSize); EXPECT_EQ(result->mCellSize, cellSize);
} }
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_existing_cell_should_remove_empty_tiles) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_existing_cell_should_remove_empty_tiles)
@ -314,7 +314,7 @@ namespace
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
const osg::Vec2i cellPosition(0, 0); const osg::Vec2i cellPosition(0, 0);
const int cellSize = 8192; const int cellSize = 8192;
ASSERT_TRUE(manager.addWater(cellPosition, cellSize, osg::Vec3f())); ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f));
ASSERT_TRUE(manager.removeWater(cellPosition)); ASSERT_TRUE(manager.removeWater(cellPosition));
for (int x = -6; x < 6; ++x) for (int x = -6; x < 6; ++x)
for (int y = -6; y < 6; ++y) for (int y = -6; y < 6; ++y)
@ -329,7 +329,7 @@ namespace
ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground));
const osg::Vec2i cellPosition(0, 0); const osg::Vec2i cellPosition(0, 0);
const int cellSize = 8192; const int cellSize = 8192;
ASSERT_TRUE(manager.addWater(cellPosition, cellSize, osg::Vec3f())); ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f));
ASSERT_TRUE(manager.removeWater(cellPosition)); ASSERT_TRUE(manager.removeWater(cellPosition));
for (int x = -6; x < 6; ++x) for (int x = -6; x < 6; ++x)
for (int y = -6; y < 6; ++y) for (int y = -6; y < 6; ++y)
@ -344,10 +344,10 @@ namespace
const btBoxShape boxShape(btVector3(20, 20, 100)); const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(nullptr, boxShape); const CollisionShape shape(nullptr, boxShape);
ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground));
ASSERT_TRUE(manager.addWater(cellPosition, cellSize, osg::Vec3f())); ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f));
ASSERT_TRUE(manager.removeObject(ObjectId(&boxShape))); ASSERT_TRUE(manager.removeObject(ObjectId(&boxShape)));
for (int x = -6; x < 6; ++x) for (int x = -1; x < 12; ++x)
for (int y = -6; y < 6; ++y) for (int y = -1; y < 12; ++y)
ASSERT_NE(manager.getMesh(TilePosition(x, y)), nullptr); ASSERT_NE(manager.getMesh(TilePosition(x, y)), nullptr);
} }
} }

View file

@ -32,16 +32,15 @@ namespace DetourNavigator
return object; return object;
} }
bool CachedRecastMeshManager::addWater(const osg::Vec2i& cellPosition, const int cellSize, bool CachedRecastMeshManager::addWater(const osg::Vec2i& cellPosition, int cellSize, float level)
const osg::Vec3f& shift)
{ {
if (!mImpl.addWater(cellPosition, cellSize, shift)) if (!mImpl.addWater(cellPosition, cellSize, level))
return false; return false;
mOutdatedCache = true; mOutdatedCache = true;
return true; return true;
} }
std::optional<Cell> CachedRecastMeshManager::removeWater(const osg::Vec2i& cellPosition) std::optional<Water> CachedRecastMeshManager::removeWater(const osg::Vec2i& cellPosition)
{ {
const auto water = mImpl.removeWater(cellPosition); const auto water = mImpl.removeWater(cellPosition);
if (water) if (water)
@ -50,20 +49,20 @@ namespace DetourNavigator
} }
bool CachedRecastMeshManager::addHeightfield(const osg::Vec2i& cellPosition, int cellSize, bool CachedRecastMeshManager::addHeightfield(const osg::Vec2i& cellPosition, int cellSize,
const osg::Vec3f& shift, const HeightfieldShape& shape) const HeightfieldShape& shape)
{ {
if (!mImpl.addHeightfield(cellPosition, cellSize, shift, shape)) if (!mImpl.addHeightfield(cellPosition, cellSize, shape))
return false; return false;
mOutdatedCache = true; mOutdatedCache = true;
return true; return true;
} }
std::optional<Cell> CachedRecastMeshManager::removeHeightfield(const osg::Vec2i& cellPosition) std::optional<SizedHeightfieldShape> CachedRecastMeshManager::removeHeightfield(const osg::Vec2i& cellPosition)
{ {
const auto cell = mImpl.removeHeightfield(cellPosition); const auto heightfield = mImpl.removeHeightfield(cellPosition);
if (cell) if (heightfield)
mOutdatedCache = true; mOutdatedCache = true;
return cell; return heightfield;
} }
std::shared_ptr<RecastMesh> CachedRecastMeshManager::getMesh() std::shared_ptr<RecastMesh> CachedRecastMeshManager::getMesh()

View file

@ -23,14 +23,13 @@ namespace DetourNavigator
std::optional<RemovedRecastMeshObject> removeObject(const ObjectId id); std::optional<RemovedRecastMeshObject> removeObject(const ObjectId id);
bool addWater(const osg::Vec2i& cellPosition, const int cellSize, const osg::Vec3f& shift); bool addWater(const osg::Vec2i& cellPosition, int cellSize, float level);
std::optional<Cell> removeWater(const osg::Vec2i& cellPosition); std::optional<Water> removeWater(const osg::Vec2i& cellPosition);
bool addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const osg::Vec3f& shift, bool addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape);
const HeightfieldShape& shape);
std::optional<Cell> removeHeightfield(const osg::Vec2i& cellPosition); std::optional<SizedHeightfieldShape> removeHeightfield(const osg::Vec2i& cellPosition);
std::shared_ptr<RecastMesh> getMesh(); std::shared_ptr<RecastMesh> getMesh();

View file

@ -50,13 +50,13 @@ namespace DetourNavigator
} }
template <class Callback> template <class Callback>
void getTilesPositions(const int cellSize, const osg::Vec3f& shift, void getTilesPositions(const int cellSize, const btVector3& shift,
const Settings& settings, Callback&& callback) const Settings& settings, Callback&& callback)
{ {
using Misc::Convert::toOsg; using Misc::Convert::toOsg;
const auto halfCellSize = cellSize / 2; const auto halfCellSize = cellSize / 2;
const btTransform transform(btMatrix3x3::getIdentity(), Misc::Convert::toBullet(shift)); const btTransform transform(btMatrix3x3::getIdentity(), shift);
auto aabbMin = transform(btVector3(-halfCellSize, -halfCellSize, 0)); auto aabbMin = transform(btVector3(-halfCellSize, -halfCellSize, 0));
auto aabbMax = transform(btVector3(halfCellSize, halfCellSize, 0)); auto aabbMax = transform(btVector3(halfCellSize, halfCellSize, 0));

View file

@ -1,6 +1,10 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_HEIGHFIELDSHAPE_H #ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_HEIGHFIELDSHAPE_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_HEIGHFIELDSHAPE_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_HEIGHFIELDSHAPE_H
#include <components/bullethelpers/heightfield.hpp>
#include <osg/Vec2i>
#include <cstddef> #include <cstddef>
#include <variant> #include <variant>
@ -20,6 +24,21 @@ namespace DetourNavigator
}; };
using HeightfieldShape = std::variant<HeightfieldPlane, HeightfieldSurface>; using HeightfieldShape = std::variant<HeightfieldPlane, HeightfieldSurface>;
inline btVector3 getHeightfieldShift(const HeightfieldPlane& v, const osg::Vec2i& cellPosition, int cellSize)
{
return BulletHelpers::getHeightfieldShift(cellPosition.x(), cellPosition.y(), cellSize, v.mHeight, v.mHeight);
}
inline btVector3 getHeightfieldShift(const HeightfieldSurface& v, const osg::Vec2i& cellPosition, int cellSize)
{
return BulletHelpers::getHeightfieldShift(cellPosition.x(), cellPosition.y(), cellSize, v.mMinHeight, v.mMaxHeight);
}
inline btVector3 getHeightfieldShift(const HeightfieldShape& v, const osg::Vec2i& cellPosition, int cellSize)
{
return std::visit([&] (const auto& w) { return getHeightfieldShift(w, cellPosition, cellSize); }, v);
}
} }
#endif #endif

View file

@ -36,29 +36,28 @@ namespace
float mHeight; float mHeight;
}; };
Rectangle getSwimRectangle(const Cell& water, const Settings& settings, Rectangle getSwimRectangle(const CellWater& water, const Settings& settings, const osg::Vec3f& agentHalfExtents)
const osg::Vec3f& agentHalfExtents)
{ {
if (water.mSize == std::numeric_limits<int>::max()) if (water.mWater.mCellSize == std::numeric_limits<int>::max())
{ {
return Rectangle { return Rectangle {
TileBounds { TileBounds {
osg::Vec2f(-std::numeric_limits<float>::max(), -std::numeric_limits<float>::max()), osg::Vec2f(-std::numeric_limits<float>::max(), -std::numeric_limits<float>::max()),
osg::Vec2f(std::numeric_limits<float>::max(), std::numeric_limits<float>::max()) osg::Vec2f(std::numeric_limits<float>::max(), std::numeric_limits<float>::max())
}, },
toNavMeshCoordinates(settings, getSwimLevel(settings, water.mShift.z(), agentHalfExtents.z())) toNavMeshCoordinates(settings, getSwimLevel(settings, water.mWater.mLevel, agentHalfExtents.z()))
}; };
} }
else else
{ {
const osg::Vec2f shift(water.mShift.x(), water.mShift.y()); const osg::Vec2f shift = getWaterShift2d(water.mCellPosition, water.mWater.mCellSize);
const float halfCellSize = water.mSize / 2.0f; const float halfCellSize = water.mWater.mCellSize / 2.0f;
return Rectangle { return Rectangle {
TileBounds{ TileBounds{
toNavMeshCoordinates(settings, shift + osg::Vec2f(-halfCellSize, -halfCellSize)), toNavMeshCoordinates(settings, shift + osg::Vec2f(-halfCellSize, -halfCellSize)),
toNavMeshCoordinates(settings, shift + osg::Vec2f(halfCellSize, halfCellSize)) toNavMeshCoordinates(settings, shift + osg::Vec2f(halfCellSize, halfCellSize))
}, },
toNavMeshCoordinates(settings, getSwimLevel(settings, water.mShift.z(), agentHalfExtents.z())) toNavMeshCoordinates(settings, getSwimLevel(settings, water.mWater.mLevel, agentHalfExtents.z()))
}; };
} }
} }
@ -121,7 +120,7 @@ namespace
return result; return result;
} }
rcConfig makeConfig(const osg::Vec3f& agentHalfExtents, const osg::Vec3f& boundsMin, const osg::Vec3f& boundsMax, rcConfig makeConfig(const osg::Vec3f& agentHalfExtents, const TilePosition& tile, float minZ, float maxZ,
const Settings& settings) const Settings& settings)
{ {
rcConfig config; rcConfig config;
@ -140,15 +139,18 @@ namespace
config.detailSampleDist = settings.mDetailSampleDist < 0.9f ? 0 : config.cs * settings.mDetailSampleDist; config.detailSampleDist = settings.mDetailSampleDist < 0.9f ? 0 : config.cs * settings.mDetailSampleDist;
config.detailSampleMaxError = config.ch * settings.mDetailSampleMaxError; config.detailSampleMaxError = config.ch * settings.mDetailSampleMaxError;
config.borderSize = settings.mBorderSize; config.borderSize = settings.mBorderSize;
config.width = settings.mTileSize + config.borderSize * 2;
config.height = settings.mTileSize + config.borderSize * 2;
rcVcopy(config.bmin, boundsMin.ptr());
rcVcopy(config.bmax, boundsMax.ptr());
config.bmin[0] -= getBorderSize(settings);
config.bmin[2] -= getBorderSize(settings);
config.bmax[0] += getBorderSize(settings);
config.bmax[2] += getBorderSize(settings);
config.tileSize = settings.mTileSize; config.tileSize = settings.mTileSize;
const int size = config.tileSize + config.borderSize * 2;
config.width = size;
config.height = size;
const float halfBoundsSize = size * config.cs * 0.5f;
const osg::Vec2f shift = osg::Vec2f(tile.x() + 0.5f, tile.y() + 0.5f) * getTileSize(settings);
config.bmin[0] = shift.x() - halfBoundsSize;
config.bmin[1] = minZ;
config.bmin[2] = shift.y() - halfBoundsSize;
config.bmax[0] = shift.x() + halfBoundsSize;
config.bmax[1] = maxZ;
config.bmax[2] = shift.y() + halfBoundsSize;
return config; return config;
} }
@ -238,26 +240,29 @@ namespace
); );
} }
bool rasterizeTriangles(rcContext& context, const osg::Vec3f& agentHalfExtents, const std::vector<Cell>& cells, bool rasterizeTriangles(rcContext& context, const osg::Vec3f& agentHalfExtents, const std::vector<CellWater>& water,
const Settings& settings, const rcConfig& config, rcHeightfield& solid) const Settings& settings, const rcConfig& config, rcHeightfield& solid)
{ {
for (const Cell& cell : cells) for (const CellWater& cellWater : water)
{ {
const Rectangle rectangle = getSwimRectangle(cell, settings, agentHalfExtents); const Rectangle rectangle = getSwimRectangle(cellWater, settings, agentHalfExtents);
if (!rasterizeTriangles(context, rectangle, config, AreaType_water, solid)) if (!rasterizeTriangles(context, rectangle, config, AreaType_water, solid))
return false; return false;
} }
return true; return true;
} }
bool rasterizeTriangles(rcContext& context, const std::vector<FlatHeightfield>& heightfields, bool rasterizeTriangles(rcContext& context, const TileBounds& tileBounds, const std::vector<FlatHeightfield>& heightfields,
const Settings& settings, const rcConfig& config, rcHeightfield& solid) const Settings& settings, const rcConfig& config, rcHeightfield& solid)
{ {
for (const FlatHeightfield& heightfield : heightfields) for (const FlatHeightfield& heightfield : heightfields)
{ {
const Rectangle rectangle {heightfield.mBounds, toNavMeshCoordinates(settings, heightfield.mHeight)}; if (auto intersection = getIntersection(tileBounds, maxCellTileBounds(heightfield.mCellPosition, heightfield.mCellSize)))
if (!rasterizeTriangles(context, rectangle, config, AreaType_ground, solid)) {
return false; const Rectangle rectangle {*intersection, toNavMeshCoordinates(settings, heightfield.mHeight)};
if (!rasterizeTriangles(context, rectangle, config, AreaType_ground, solid))
return false;
}
} }
return true; return true;
} }
@ -276,13 +281,14 @@ namespace
return true; return true;
} }
bool rasterizeTriangles(rcContext& context, const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh, bool rasterizeTriangles(rcContext& context, const TilePosition& tilePosition, const osg::Vec3f& agentHalfExtents,
const rcConfig& config, const Settings& settings, rcHeightfield& solid) const RecastMesh& recastMesh, const rcConfig& config, const Settings& settings, rcHeightfield& solid)
{ {
return rasterizeTriangles(context, recastMesh.getMesh(), settings, config, solid) return rasterizeTriangles(context, recastMesh.getMesh(), settings, config, solid)
&& rasterizeTriangles(context, agentHalfExtents, recastMesh.getWater(), settings, config, solid) && rasterizeTriangles(context, agentHalfExtents, recastMesh.getWater(), settings, config, solid)
&& rasterizeTriangles(context, recastMesh.getHeightfields(), settings, config, solid) && rasterizeTriangles(context, recastMesh.getHeightfields(), settings, config, solid)
&& rasterizeTriangles(context, recastMesh.getFlatHeightfields(), settings, config, solid); && rasterizeTriangles(context, makeRealTileBoundsWithBorder(settings, tilePosition),
recastMesh.getFlatHeightfields(), settings, config, solid);
} }
void buildCompactHeightfield(rcContext& context, const int walkableHeight, const int walkableClimb, void buildCompactHeightfield(rcContext& context, const int walkableHeight, const int walkableClimb,
@ -357,6 +363,7 @@ namespace
rcPolyMeshDetail& polyMeshDetail) rcPolyMeshDetail& polyMeshDetail)
{ {
rcCompactHeightfield compact; rcCompactHeightfield compact;
compact.dist = nullptr;
buildCompactHeightfield(context, config.walkableHeight, config.walkableClimb, solid, compact); buildCompactHeightfield(context, config.walkableHeight, config.walkableClimb, solid, compact);
erodeWalkableArea(context, config.walkableRadius, compact); erodeWalkableArea(context, config.walkableRadius, compact);
@ -387,25 +394,61 @@ namespace
++power; ++power;
return power; return power;
} }
std::pair<float, float> getBoundsByZ(const RecastMesh& recastMesh, const osg::Vec3f& agentHalfExtents, const Settings& settings)
{
float minZ = 0;
float maxZ = 0;
const std::vector<float>& vertices = recastMesh.getMesh().getVertices();
for (std::size_t i = 0, n = vertices.size(); i < n; i += 3)
{
minZ = std::min(minZ, vertices[i + 2]);
maxZ = std::max(maxZ, vertices[i + 2]);
}
for (const CellWater& water : recastMesh.getWater())
{
const float swimLevel = getSwimLevel(settings, water.mWater.mLevel, agentHalfExtents.z());
minZ = std::min(minZ, swimLevel);
maxZ = std::max(maxZ, swimLevel);
}
for (const Heightfield& heightfield : recastMesh.getHeightfields())
{
if (heightfield.mHeights.empty())
continue;
const auto [minHeight, maxHeight] = std::minmax_element(heightfield.mHeights.begin(), heightfield.mHeights.end());
minZ = std::min(minZ, *minHeight);
maxZ = std::max(maxZ, *maxHeight);
}
for (const FlatHeightfield& heightfield : recastMesh.getFlatHeightfields())
{
minZ = std::min(minZ, heightfield.mHeight);
maxZ = std::max(maxZ, heightfield.mHeight);
}
return {minZ, maxZ};
}
} }
} // namespace DetourNavigator } // namespace DetourNavigator
namespace DetourNavigator namespace DetourNavigator
{ {
std::unique_ptr<PreparedNavMeshData> prepareNavMeshTileData(const RecastMesh& recastMesh, std::unique_ptr<PreparedNavMeshData> prepareNavMeshTileData(const RecastMesh& recastMesh,
const TilePosition& tile, const Bounds& bounds, const osg::Vec3f& agentHalfExtents, const Settings& settings) const TilePosition& tilePosition, const osg::Vec3f& agentHalfExtents, const Settings& settings)
{ {
const TileBounds tileBounds = makeTileBounds(settings, tile); const auto [minZ, maxZ] = getBoundsByZ(recastMesh, agentHalfExtents, settings);
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, tilePosition, toNavMeshCoordinates(settings, minZ),
toNavMeshCoordinates(settings, maxZ), settings);
rcHeightfield solid; rcHeightfield solid;
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, tilePosition, agentHalfExtents, recastMesh, config, settings, solid))
return nullptr; return nullptr;
rcFilterLowHangingWalkableObstacles(&context, config.walkableClimb, solid); rcFilterLowHangingWalkableObstacles(&context, config.walkableClimb, solid);
@ -528,18 +571,8 @@ namespace DetourNavigator
return navMeshCacheItem->lock()->removeTile(changedTile); return navMeshCacheItem->lock()->removeTile(changedTile);
} }
auto recastMeshBounds = recastMesh->getBounds(); if (recastMesh->getMesh().getIndices().empty() && recastMesh->getWater().empty()
recastMeshBounds.mMin = toNavMeshCoordinates(settings, recastMeshBounds.mMin); && recastMesh->getHeightfields().empty() && recastMesh->getFlatHeightfields().empty())
recastMeshBounds.mMax = toNavMeshCoordinates(settings, recastMeshBounds.mMax);
for (const auto& water : recastMesh->getWater())
{
const float height = toNavMeshCoordinates(settings, getSwimLevel(settings, water.mShift.z(), agentHalfExtents.z()));
recastMeshBounds.mMin.y() = std::min(recastMeshBounds.mMin.y(), height);
recastMeshBounds.mMax.y() = std::max(recastMeshBounds.mMax.y(), height);
}
if (isEmpty(recastMeshBounds))
{ {
Log(Debug::Debug) << "Ignore add tile: recastMesh is empty"; Log(Debug::Debug) << "Ignore add tile: recastMesh is empty";
return navMeshCacheItem->lock()->removeTile(changedTile); return navMeshCacheItem->lock()->removeTile(changedTile);
@ -558,8 +591,7 @@ namespace DetourNavigator
if (!cachedNavMeshData) if (!cachedNavMeshData)
{ {
auto prepared = prepareNavMeshTileData(*recastMesh, changedTile, recastMeshBounds, auto prepared = prepareNavMeshTileData(*recastMesh, changedTile, agentHalfExtents, settings);
agentHalfExtents, settings);
if (prepared == nullptr) if (prepared == nullptr)
{ {

View file

@ -122,7 +122,7 @@ namespace DetourNavigator
* at least single object is added to the scene, false if there is already water for given cell or there is no * at least single object is added to the scene, false if there is already water for given cell or there is no
* any other objects. * any other objects.
*/ */
virtual bool addWater(const osg::Vec2i& cellPosition, int cellSize, const osg::Vec3f& shift) = 0; virtual bool addWater(const osg::Vec2i& cellPosition, int cellSize, float level) = 0;
/** /**
* @brief removeWater to make it no more available at the scene. * @brief removeWater to make it no more available at the scene.
@ -131,8 +131,7 @@ namespace DetourNavigator
*/ */
virtual bool removeWater(const osg::Vec2i& cellPosition) = 0; virtual bool removeWater(const osg::Vec2i& cellPosition) = 0;
virtual bool addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const osg::Vec3f& shift, virtual bool addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape) = 0;
const HeightfieldShape& shape) = 0;
virtual bool removeHeightfield(const osg::Vec2i& cellPosition) = 0; virtual bool removeHeightfield(const osg::Vec2i& cellPosition) = 0;

View file

@ -97,9 +97,9 @@ namespace DetourNavigator
return result; return result;
} }
bool NavigatorImpl::addWater(const osg::Vec2i& cellPosition, int cellSize, const osg::Vec3f& shift) bool NavigatorImpl::addWater(const osg::Vec2i& cellPosition, int cellSize, float level)
{ {
return mNavMeshManager.addWater(cellPosition, cellSize, shift); return mNavMeshManager.addWater(cellPosition, cellSize, level);
} }
bool NavigatorImpl::removeWater(const osg::Vec2i& cellPosition) bool NavigatorImpl::removeWater(const osg::Vec2i& cellPosition)
@ -107,10 +107,9 @@ namespace DetourNavigator
return mNavMeshManager.removeWater(cellPosition); return mNavMeshManager.removeWater(cellPosition);
} }
bool NavigatorImpl::addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const osg::Vec3f& shift, bool NavigatorImpl::addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape)
const HeightfieldShape& shape)
{ {
return mNavMeshManager.addHeightfield(cellPosition, cellSize, shift, shape); return mNavMeshManager.addHeightfield(cellPosition, cellSize, shape);
} }
bool NavigatorImpl::removeHeightfield(const osg::Vec2i& cellPosition) bool NavigatorImpl::removeHeightfield(const osg::Vec2i& cellPosition)

View file

@ -31,12 +31,11 @@ namespace DetourNavigator
bool removeObject(const ObjectId id) override; bool removeObject(const ObjectId id) override;
bool addWater(const osg::Vec2i& cellPosition, int cellSize, const osg::Vec3f& shift) override; bool addWater(const osg::Vec2i& cellPosition, int cellSize, float level) override;
bool removeWater(const osg::Vec2i& cellPosition) override; bool removeWater(const osg::Vec2i& cellPosition) override;
bool addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const osg::Vec3f& shift, bool addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape) override;
const HeightfieldShape& shape) override;
bool removeHeightfield(const osg::Vec2i& cellPosition) override; bool removeHeightfield(const osg::Vec2i& cellPosition) override;

View file

@ -44,7 +44,7 @@ namespace DetourNavigator
return false; return false;
} }
bool addWater(const osg::Vec2i& /*cellPosition*/, int /*cellSize*/, const osg::Vec3f& /*shift*/) override bool addWater(const osg::Vec2i& /*cellPosition*/, int /*cellSize*/, float /*level*/) override
{ {
return false; return false;
} }
@ -54,8 +54,7 @@ namespace DetourNavigator
return false; return false;
} }
bool addHeightfield(const osg::Vec2i& /*cellPosition*/, int /*cellSize*/, const osg::Vec3f& /*shift*/, bool addHeightfield(const osg::Vec2i& /*cellPosition*/, int /*cellSize*/, const HeightfieldShape& /*height*/) override
const HeightfieldShape& /*height*/) override
{ {
return false; return false;
} }

View file

@ -8,6 +8,7 @@
#include "waitconditiontype.hpp" #include "waitconditiontype.hpp"
#include <components/debug/debuglog.hpp> #include <components/debug/debuglog.hpp>
#include <components/bullethelpers/heightfield.hpp>
#include <DetourNavMesh.h> #include <DetourNavMesh.h>
@ -73,10 +74,11 @@ namespace DetourNavigator
return true; return true;
} }
bool NavMeshManager::addWater(const osg::Vec2i& cellPosition, const int cellSize, const osg::Vec3f& shift) bool NavMeshManager::addWater(const osg::Vec2i& cellPosition, int cellSize, float level)
{ {
if (!mRecastMeshManager.addWater(cellPosition, cellSize, shift)) if (!mRecastMeshManager.addWater(cellPosition, cellSize, level))
return false; return false;
const btVector3 shift = Misc::Convert::toBullet(getWaterShift3d(cellPosition, cellSize, level));
addChangedTiles(cellSize, shift, ChangeType::add); addChangedTiles(cellSize, shift, ChangeType::add);
return true; return true;
} }
@ -86,15 +88,16 @@ namespace DetourNavigator
const auto water = mRecastMeshManager.removeWater(cellPosition); const auto water = mRecastMeshManager.removeWater(cellPosition);
if (!water) if (!water)
return false; return false;
addChangedTiles(water->mSize, water->mShift, ChangeType::remove); const btVector3 shift = Misc::Convert::toBullet(getWaterShift3d(cellPosition, water->mCellSize, water->mLevel));
addChangedTiles(water->mCellSize, shift, ChangeType::remove);
return true; return true;
} }
bool NavMeshManager::addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const osg::Vec3f& shift, bool NavMeshManager::addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape)
const HeightfieldShape& shape)
{ {
if (!mRecastMeshManager.addHeightfield(cellPosition, cellSize, shift, shape)) if (!mRecastMeshManager.addHeightfield(cellPosition, cellSize, shape))
return false; return false;
const btVector3 shift = getHeightfieldShift(shape, cellPosition, cellSize);
addChangedTiles(cellSize, shift, ChangeType::add); addChangedTiles(cellSize, shift, ChangeType::add);
return true; return true;
} }
@ -104,7 +107,8 @@ namespace DetourNavigator
const auto heightfield = mRecastMeshManager.removeHeightfield(cellPosition); const auto heightfield = mRecastMeshManager.removeHeightfield(cellPosition);
if (!heightfield) if (!heightfield)
return false; return false;
addChangedTiles(heightfield->mSize, heightfield->mShift, ChangeType::remove); const btVector3 shift = getHeightfieldShift(heightfield->mShape, cellPosition, heightfield->mCellSize);
addChangedTiles(heightfield->mCellSize, shift, ChangeType::remove);
return true; return true;
} }
@ -251,7 +255,7 @@ namespace DetourNavigator
[&] (const TilePosition& v) { addChangedTile(v, changeType); }); [&] (const TilePosition& v) { addChangedTile(v, changeType); });
} }
void NavMeshManager::addChangedTiles(const int cellSize, const osg::Vec3f& shift, void NavMeshManager::addChangedTiles(const int cellSize, const btVector3& shift,
const ChangeType changeType) const ChangeType changeType)
{ {
if (cellSize == std::numeric_limits<int>::max()) if (cellSize == std::numeric_limits<int>::max())

View file

@ -34,12 +34,11 @@ namespace DetourNavigator
void addAgent(const osg::Vec3f& agentHalfExtents); void addAgent(const osg::Vec3f& agentHalfExtents);
bool addWater(const osg::Vec2i& cellPosition, const int cellSize, const osg::Vec3f& shift); bool addWater(const osg::Vec2i& cellPosition, int cellSize, float level);
bool removeWater(const osg::Vec2i& cellPosition); bool removeWater(const osg::Vec2i& cellPosition);
bool addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const osg::Vec3f& shift, bool addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape);
const HeightfieldShape& shape);
bool removeHeightfield(const osg::Vec2i& cellPosition); bool removeHeightfield(const osg::Vec2i& cellPosition);
@ -74,7 +73,7 @@ namespace DetourNavigator
void addChangedTiles(const btCollisionShape& shape, const btTransform& transform, const ChangeType changeType); void addChangedTiles(const btCollisionShape& shape, const btTransform& transform, const ChangeType changeType);
void addChangedTiles(const int cellSize, const osg::Vec3f& shift, const ChangeType changeType); void addChangedTiles(const int cellSize, const btVector3& shift, const ChangeType changeType);
void addChangedTile(const TilePosition& tilePosition, const ChangeType changeType); void addChangedTile(const TilePosition& tilePosition, const ChangeType changeType);

View file

@ -23,7 +23,7 @@ namespace DetourNavigator
struct RecastMeshData struct RecastMeshData
{ {
Mesh mMesh; Mesh mMesh;
std::vector<Cell> mWater; std::vector<CellWater> mWater;
std::vector<Heightfield> mHeightfields; std::vector<Heightfield> mHeightfields;
std::vector<FlatHeightfield> mFlatHeightfields; std::vector<FlatHeightfield> mFlatHeightfields;
}; };

View file

@ -18,7 +18,7 @@ namespace DetourNavigator
mAreaTypes = std::move(areaTypes); mAreaTypes = std::move(areaTypes);
} }
RecastMesh::RecastMesh(std::size_t generation, std::size_t revision, Mesh mesh, std::vector<Cell> water, RecastMesh::RecastMesh(std::size_t generation, std::size_t revision, Mesh mesh, std::vector<CellWater> water,
std::vector<Heightfield> heightfields, std::vector<FlatHeightfield> flatHeightfields) std::vector<Heightfield> heightfields, std::vector<FlatHeightfield> flatHeightfields)
: mGeneration(generation) : mGeneration(generation)
, mRevision(revision) , mRevision(revision)
@ -27,31 +27,9 @@ namespace DetourNavigator
, mHeightfields(std::move(heightfields)) , mHeightfields(std::move(heightfields))
, mFlatHeightfields(std::move(flatHeightfields)) , mFlatHeightfields(std::move(flatHeightfields))
{ {
if (mMesh.getVerticesCount() > 0)
rcCalcBounds(mMesh.getVertices().data(), static_cast<int>(mMesh.getVerticesCount()),
mBounds.mMin.ptr(), mBounds.mMax.ptr());
mWater.shrink_to_fit(); mWater.shrink_to_fit();
mHeightfields.shrink_to_fit(); mHeightfields.shrink_to_fit();
for (Heightfield& v : mHeightfields) for (Heightfield& v : mHeightfields)
v.mHeights.shrink_to_fit(); v.mHeights.shrink_to_fit();
for (const Heightfield& v : mHeightfields)
{
const auto [min, max] = std::minmax_element(v.mHeights.begin(), v.mHeights.end());
mBounds.mMin.x() = std::min(mBounds.mMin.x(), v.mBounds.mMin.x());
mBounds.mMin.y() = std::min(mBounds.mMin.y(), v.mBounds.mMin.y());
mBounds.mMin.z() = std::min(mBounds.mMin.z(), *min);
mBounds.mMax.x() = std::max(mBounds.mMax.x(), v.mBounds.mMax.x());
mBounds.mMax.y() = std::max(mBounds.mMax.y(), v.mBounds.mMax.y());
mBounds.mMax.z() = std::max(mBounds.mMax.z(), *max);
}
for (const FlatHeightfield& v : mFlatHeightfields)
{
mBounds.mMin.x() = std::min(mBounds.mMin.x(), v.mBounds.mMin.x());
mBounds.mMin.y() = std::min(mBounds.mMin.y(), v.mBounds.mMin.y());
mBounds.mMin.z() = std::min(mBounds.mMin.z(), v.mHeight);
mBounds.mMax.x() = std::max(mBounds.mMax.x(), v.mBounds.mMax.x());
mBounds.mMax.y() = std::max(mBounds.mMax.y(), v.mBounds.mMax.y());
mBounds.mMax.z() = std::max(mBounds.mMax.z(), v.mHeight);
}
} }
} }

View file

@ -8,6 +8,7 @@
#include <components/bullethelpers/operators.hpp> #include <components/bullethelpers/operators.hpp>
#include <osg/Vec3f> #include <osg/Vec3f>
#include <osg/Vec2i>
#include <memory> #include <memory>
#include <string> #include <string>
@ -47,26 +48,57 @@ namespace DetourNavigator
} }
}; };
struct Cell struct Water
{ {
int mSize; int mCellSize;
osg::Vec3f mShift; float mLevel;
}; };
inline bool operator<(const Water& lhs, const Water& rhs) noexcept
{
const auto tie = [] (const Water& v) { return std::tie(v.mCellSize, v.mLevel); };
return tie(lhs) < tie(rhs);
}
struct CellWater
{
osg::Vec2i mCellPosition;
Water mWater;
};
inline bool operator<(const CellWater& lhs, const CellWater& rhs) noexcept
{
const auto tie = [] (const CellWater& v) { return std::tie(v.mCellPosition, v.mWater); };
return tie(lhs) < tie(rhs);
}
inline osg::Vec2f getWaterShift2d(const osg::Vec2i& cellPosition, int cellSize)
{
return osg::Vec2f((cellPosition.x() + 0.5f) * cellSize, (cellPosition.y() + 0.5f) * cellSize);
}
inline osg::Vec3f getWaterShift3d(const osg::Vec2i& cellPosition, int cellSize, float level)
{
return osg::Vec3f(getWaterShift2d(cellPosition, cellSize), level);
}
struct Heightfield struct Heightfield
{ {
TileBounds mBounds; osg::Vec2i mCellPosition;
int mCellSize;
std::uint8_t mLength; std::uint8_t mLength;
float mMinHeight; float mMinHeight;
float mMaxHeight; float mMaxHeight;
osg::Vec3f mShift;
float mScale;
std::vector<float> mHeights; std::vector<float> mHeights;
std::size_t mOriginalSize;
std::uint8_t mMinX;
std::uint8_t mMinY;
}; };
inline auto makeTuple(const Heightfield& v) noexcept inline auto makeTuple(const Heightfield& v) noexcept
{ {
return std::tie(v.mBounds, v.mLength, v.mMinHeight, v.mMaxHeight, v.mShift, v.mScale, v.mHeights); return std::tie(v.mCellPosition, v.mCellSize, v.mLength, v.mMinHeight, v.mMaxHeight,
v.mHeights, v.mOriginalSize, v.mMinX, v.mMinY);
} }
inline bool operator<(const Heightfield& lhs, const Heightfield& rhs) noexcept inline bool operator<(const Heightfield& lhs, const Heightfield& rhs) noexcept
@ -76,19 +108,21 @@ namespace DetourNavigator
struct FlatHeightfield struct FlatHeightfield
{ {
TileBounds mBounds; osg::Vec2i mCellPosition;
int mCellSize;
float mHeight; float mHeight;
}; };
inline bool operator<(const FlatHeightfield& lhs, const FlatHeightfield& rhs) noexcept inline bool operator<(const FlatHeightfield& lhs, const FlatHeightfield& rhs) noexcept
{ {
return std::tie(lhs.mBounds, lhs.mHeight) < std::tie(rhs.mBounds, rhs.mHeight); const auto tie = [] (const FlatHeightfield& v) { return std::tie(v.mCellPosition, v.mCellSize, v.mHeight); };
return tie(lhs) < tie(rhs);
} }
class RecastMesh class RecastMesh
{ {
public: public:
RecastMesh(std::size_t generation, std::size_t revision, Mesh mesh, std::vector<Cell> water, RecastMesh(std::size_t generation, std::size_t revision, Mesh mesh, std::vector<CellWater> water,
std::vector<Heightfield> heightfields, std::vector<FlatHeightfield> flatHeightfields); std::vector<Heightfield> heightfields, std::vector<FlatHeightfield> flatHeightfields);
std::size_t getGeneration() const std::size_t getGeneration() const
@ -103,7 +137,7 @@ namespace DetourNavigator
const Mesh& getMesh() const noexcept { return mMesh; } const Mesh& getMesh() const noexcept { return mMesh; }
const std::vector<Cell>& getWater() const const std::vector<CellWater>& getWater() const
{ {
return mWater; return mWater;
} }
@ -118,34 +152,23 @@ namespace DetourNavigator
return mFlatHeightfields; return mFlatHeightfields;
} }
const Bounds& getBounds() const
{
return mBounds;
}
private: private:
std::size_t mGeneration; std::size_t mGeneration;
std::size_t mRevision; std::size_t mRevision;
Mesh mMesh; Mesh mMesh;
std::vector<Cell> mWater; std::vector<CellWater> mWater;
std::vector<Heightfield> mHeightfields; std::vector<Heightfield> mHeightfields;
std::vector<FlatHeightfield> mFlatHeightfields; std::vector<FlatHeightfield> mFlatHeightfields;
Bounds mBounds;
friend inline std::size_t getSize(const RecastMesh& value) noexcept friend inline std::size_t getSize(const RecastMesh& value) noexcept
{ {
return getSize(value.mMesh) + value.mWater.size() * sizeof(Cell) return getSize(value.mMesh) + value.mWater.size() * sizeof(CellWater)
+ value.mHeightfields.size() * sizeof(Heightfield) + value.mHeightfields.size() * sizeof(Heightfield)
+ std::accumulate(value.mHeightfields.begin(), value.mHeightfields.end(), std::size_t {0}, + std::accumulate(value.mHeightfields.begin(), value.mHeightfields.end(), std::size_t {0},
[] (std::size_t r, const Heightfield& v) { return r + v.mHeights.size() * sizeof(float); }) [] (std::size_t r, const Heightfield& v) { return r + v.mHeights.size() * sizeof(float); })
+ value.mFlatHeightfields.size() * sizeof(FlatHeightfield); + value.mFlatHeightfields.size() * sizeof(FlatHeightfield);
} }
}; };
inline bool operator<(const Cell& lhs, const Cell& rhs) noexcept
{
return std::tie(lhs.mSize, lhs.mShift) < std::tie(rhs.mSize, rhs.mShift);
}
} }
#endif #endif

View file

@ -6,6 +6,7 @@
#include <components/bullethelpers/processtrianglecallback.hpp> #include <components/bullethelpers/processtrianglecallback.hpp>
#include <components/misc/convert.hpp> #include <components/misc/convert.hpp>
#include <components/debug/debuglog.hpp> #include <components/debug/debuglog.hpp>
#include <components/bullethelpers/heightfield.hpp>
#include <BulletCollision/CollisionShapes/btBoxShape.h> #include <BulletCollision/CollisionShapes/btBoxShape.h>
#include <BulletCollision/CollisionShapes/btCompoundShape.h> #include <BulletCollision/CollisionShapes/btCompoundShape.h>
@ -35,13 +36,9 @@ namespace DetourNavigator
return result; return result;
} }
TileBounds maxCellTileBounds(int size, const osg::Vec3f& shift) float getHeightfieldScale(int cellSize, std::size_t dataSize)
{ {
const float halfCellSize = static_cast<float>(size) / 2; return static_cast<float>(cellSize) / (dataSize - 1);
return TileBounds {
osg::Vec2f(shift.x() - halfCellSize, shift.y() - halfCellSize),
osg::Vec2f(shift.x() + halfCellSize, shift.y() + halfCellSize)
};
} }
} }
@ -108,7 +105,8 @@ namespace DetourNavigator
static_cast<int>(heightfield.mLength), heightfield.mHeights.data(), static_cast<int>(heightfield.mLength), heightfield.mHeights.data(),
heightfield.mMinHeight, heightfield.mMaxHeight, upAxis, flipQuadEdges); heightfield.mMinHeight, heightfield.mMaxHeight, upAxis, flipQuadEdges);
#endif #endif
shape.setLocalScaling(btVector3(heightfield.mScale, heightfield.mScale, 1)); const float scale = getHeightfieldScale(heightfield.mCellSize, heightfield.mOriginalSize);
shape.setLocalScaling(btVector3(scale, scale, 1));
btVector3 aabbMin; btVector3 aabbMin;
btVector3 aabbMax; btVector3 aabbMax;
shape.getAabb(btTransform::getIdentity(), aabbMin, aabbMax); shape.getAabb(btTransform::getIdentity(), aabbMin, aabbMax);
@ -118,8 +116,16 @@ namespace DetourNavigator
triangles.emplace_back(makeRecastMeshTriangle(vertices, AreaType_ground)); triangles.emplace_back(makeRecastMeshTriangle(vertices, AreaType_ground));
}); });
shape.processAllTriangles(&callback, aabbMin, aabbMax); shape.processAllTriangles(&callback, aabbMin, aabbMax);
const osg::Vec2f shift = (osg::Vec2f(aabbMax.x(), aabbMax.y()) - osg::Vec2f(aabbMin.x(), aabbMin.y())) * 0.5; const osg::Vec2f aabbShift = (osg::Vec2f(aabbMax.x(), aabbMax.y()) - osg::Vec2f(aabbMin.x(), aabbMin.y())) * 0.5;
return makeMesh(std::move(triangles), heightfield.mShift + osg::Vec3f(shift.x(), shift.y(), 0)); const osg::Vec2f tileShift = osg::Vec2f(heightfield.mMinX, heightfield.mMinY) * scale;
const osg::Vec2f localShift = aabbShift + tileShift;
const float cellSize = static_cast<float>(heightfield.mCellSize);
const osg::Vec3f cellShift(
heightfield.mCellPosition.x() * cellSize,
heightfield.mCellPosition.y() * cellSize,
(heightfield.mMinHeight + heightfield.mMaxHeight) * 0.5f
);
return makeMesh(std::move(triangles), cellShift + osg::Vec3f(localShift.x(), localShift.y(), 0));
} }
RecastMeshBuilder::RecastMeshBuilder(const TileBounds& bounds) noexcept RecastMeshBuilder::RecastMeshBuilder(const TileBounds& bounds) noexcept
@ -200,24 +206,25 @@ namespace DetourNavigator
} }
} }
void RecastMeshBuilder::addWater(const int cellSize, const osg::Vec3f& shift) void RecastMeshBuilder::addWater(const osg::Vec2i& cellPosition, const Water& water)
{ {
mWater.push_back(Cell {cellSize, shift}); mWater.push_back(CellWater {cellPosition, water});
} }
void RecastMeshBuilder::addHeightfield(int cellSize, const osg::Vec3f& shift, float height) void RecastMeshBuilder::addHeightfield(const osg::Vec2i& cellPosition, int cellSize, float height)
{ {
if (const auto intersection = getIntersection(mBounds, maxCellTileBounds(cellSize, shift))) if (const auto intersection = getIntersection(mBounds, maxCellTileBounds(cellPosition, cellSize)))
mFlatHeightfields.emplace_back(FlatHeightfield {*intersection, height + shift.z()}); mFlatHeightfields.emplace_back(FlatHeightfield {cellPosition, cellSize, height});
} }
void RecastMeshBuilder::addHeightfield(int cellSize, const osg::Vec3f& shift, const float* heights, void RecastMeshBuilder::addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const float* heights,
std::size_t size, float minHeight, float maxHeight) std::size_t size, float minHeight, float maxHeight)
{ {
const auto intersection = getIntersection(mBounds, maxCellTileBounds(cellSize, shift)); const auto intersection = getIntersection(mBounds, maxCellTileBounds(cellPosition, cellSize));
if (!intersection.has_value()) if (!intersection.has_value())
return; return;
const float stepSize = static_cast<float>(cellSize) / (size - 1); const osg::Vec3f shift = Misc::Convert::toOsg(BulletHelpers::getHeightfieldShift(cellPosition.x(), cellPosition.y(), cellSize, minHeight, maxHeight));
const float stepSize = getHeightfieldScale(cellSize, size);
const int halfCellSize = cellSize / 2; const int halfCellSize = cellSize / 2;
const auto local = [&] (float v, float shift) { return (v - shift + halfCellSize) / stepSize; }; const auto local = [&] (float v, float shift) { return (v - shift + halfCellSize) / stepSize; };
const auto index = [&] (float v, int add) { return std::clamp<int>(static_cast<int>(v) + add, 0, size); }; const auto index = [&] (float v, int add) { return std::clamp<int>(static_cast<int>(v) + add, 0, size); };
@ -236,13 +243,15 @@ namespace DetourNavigator
for (std::size_t x = minX; x < endX; ++x) for (std::size_t x = minX; x < endX; ++x)
tileHeights.push_back(heights[x + y * size]); tileHeights.push_back(heights[x + y * size]);
Heightfield heightfield; Heightfield heightfield;
heightfield.mBounds = *intersection; heightfield.mCellPosition = cellPosition;
heightfield.mCellSize = cellSize;
heightfield.mLength = static_cast<std::uint8_t>(endY - minY); heightfield.mLength = static_cast<std::uint8_t>(endY - minY);
heightfield.mMinHeight = minHeight; heightfield.mMinHeight = minHeight;
heightfield.mMaxHeight = maxHeight; heightfield.mMaxHeight = maxHeight;
heightfield.mShift = shift + osg::Vec3f(minX, minY, 0) * stepSize - osg::Vec3f(halfCellSize, halfCellSize, 0);
heightfield.mScale = stepSize;
heightfield.mHeights = std::move(tileHeights); heightfield.mHeights = std::move(tileHeights);
heightfield.mOriginalSize = size;
heightfield.mMinX = static_cast<std::uint8_t>(minX);
heightfield.mMinY = static_cast<std::uint8_t>(minY);
mHeightfields.push_back(std::move(heightfield)); mHeightfields.push_back(std::move(heightfield));
} }

View file

@ -48,11 +48,11 @@ namespace DetourNavigator
void addObject(const btBoxShape& shape, const btTransform& transform, const AreaType areaType); void addObject(const btBoxShape& shape, const btTransform& transform, const AreaType areaType);
void addWater(const int mCellSize, const osg::Vec3f& shift); void addWater(const osg::Vec2i& cellPosition, const Water& water);
void addHeightfield(int cellSize, const osg::Vec3f& shift, float height); void addHeightfield(const osg::Vec2i& cellPosition, int cellSize, float height);
void addHeightfield(int cellSize, const osg::Vec3f& shift, const float* heights, std::size_t size, void addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const float* heights, std::size_t size,
float minHeight, float maxHeight); float minHeight, float maxHeight);
std::shared_ptr<RecastMesh> create(std::size_t generation, std::size_t revision) &&; std::shared_ptr<RecastMesh> create(std::size_t generation, std::size_t revision) &&;
@ -60,7 +60,7 @@ namespace DetourNavigator
private: private:
const TileBounds mBounds; const TileBounds mBounds;
std::vector<RecastMeshTriangle> mTriangles; std::vector<RecastMeshTriangle> mTriangles;
std::vector<Cell> mWater; std::vector<CellWater> mWater;
std::vector<Heightfield> mHeightfields; std::vector<Heightfield> mHeightfields;
std::vector<FlatHeightfield> mFlatHeightfields; std::vector<FlatHeightfield> mFlatHeightfields;

View file

@ -4,6 +4,7 @@
#include "heightfieldshape.hpp" #include "heightfieldshape.hpp"
#include <components/debug/debuglog.hpp> #include <components/debug/debuglog.hpp>
#include <components/misc/convert.hpp>
#include <utility> #include <utility>
@ -11,17 +12,18 @@ namespace
{ {
struct AddHeightfield struct AddHeightfield
{ {
const DetourNavigator::Cell& mCell; osg::Vec2i mCellPosition;
int mCellSize;
DetourNavigator::RecastMeshBuilder& mBuilder; DetourNavigator::RecastMeshBuilder& mBuilder;
void operator()(const DetourNavigator::HeightfieldSurface& v) void operator()(const DetourNavigator::HeightfieldSurface& v)
{ {
mBuilder.addHeightfield(mCell.mSize, mCell.mShift, v.mHeights, v.mSize, v.mMinHeight, v.mMaxHeight); mBuilder.addHeightfield(mCellPosition, mCellSize, v.mHeights, v.mSize, v.mMinHeight, v.mMaxHeight);
} }
void operator()(DetourNavigator::HeightfieldPlane v) void operator()(DetourNavigator::HeightfieldPlane v)
{ {
mBuilder.addHeightfield(mCell.mSize, mCell.mShift, v.mHeight); mBuilder.addHeightfield(mCellPosition, mCellSize, v.mHeight);
} }
}; };
} }
@ -73,45 +75,45 @@ namespace DetourNavigator
return result; return result;
} }
bool RecastMeshManager::addWater(const osg::Vec2i& cellPosition, const int cellSize, const osg::Vec3f& shift) bool RecastMeshManager::addWater(const osg::Vec2i& cellPosition, int cellSize, float level)
{ {
const std::lock_guard lock(mMutex); const std::lock_guard lock(mMutex);
if (!mWater.emplace(cellPosition, Cell {cellSize, shift}).second) if (!mWater.emplace(cellPosition, Water {cellSize, level}).second)
return false; return false;
++mRevision; ++mRevision;
return true; return true;
} }
std::optional<Cell> RecastMeshManager::removeWater(const osg::Vec2i& cellPosition) std::optional<Water> RecastMeshManager::removeWater(const osg::Vec2i& cellPosition)
{ {
const std::lock_guard lock(mMutex); const std::lock_guard lock(mMutex);
const auto water = mWater.find(cellPosition); const auto water = mWater.find(cellPosition);
if (water == mWater.end()) if (water == mWater.end())
return std::nullopt; return std::nullopt;
++mRevision; ++mRevision;
const Cell result = water->second; Water result = water->second;
mWater.erase(water); mWater.erase(water);
return result; return result;
} }
bool RecastMeshManager::addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const osg::Vec3f& shift, bool RecastMeshManager::addHeightfield(const osg::Vec2i& cellPosition, int cellSize,
const HeightfieldShape& shape) const HeightfieldShape& shape)
{ {
const std::lock_guard lock(mMutex); const std::lock_guard lock(mMutex);
if (!mHeightfields.emplace(cellPosition, Heightfield {Cell {cellSize, shift}, shape}).second) if (!mHeightfields.emplace(cellPosition, SizedHeightfieldShape {cellSize, shape}).second)
return false; return false;
++mRevision; ++mRevision;
return true; return true;
} }
std::optional<Cell> RecastMeshManager::removeHeightfield(const osg::Vec2i& cellPosition) std::optional<SizedHeightfieldShape> RecastMeshManager::removeHeightfield(const osg::Vec2i& cellPosition)
{ {
const std::lock_guard lock(mMutex); const std::lock_guard lock(mMutex);
const auto it = mHeightfields.find(cellPosition); const auto it = mHeightfields.find(cellPosition);
if (it == mHeightfields.end()) if (it == mHeightfields.end())
return std::nullopt; return std::nullopt;
++mRevision; ++mRevision;
const auto result = std::make_optional(it->second.mCell); auto result = std::make_optional(it->second);
mHeightfields.erase(it); mHeightfields.erase(it);
return result; return result;
} }
@ -130,9 +132,9 @@ namespace DetourNavigator
{ {
const std::lock_guard lock(mMutex); const std::lock_guard lock(mMutex);
for (const auto& [k, v] : mWater) for (const auto& [k, v] : mWater)
builder.addWater(v.mSize, v.mShift); builder.addWater(k, v);
for (const auto& [cellPosition, v] : mHeightfields) for (const auto& [cellPosition, v] : mHeightfields)
std::visit(AddHeightfield {v.mCell, builder}, v.mShape); std::visit(AddHeightfield {cellPosition, v.mCellSize, builder}, v.mShape);
objects.reserve(mObjects.size()); objects.reserve(mObjects.size());
for (const auto& [k, object] : mObjects) for (const auto& [k, object] : mObjects)
{ {

View file

@ -29,6 +29,12 @@ namespace DetourNavigator
btTransform mTransform; btTransform mTransform;
}; };
struct SizedHeightfieldShape
{
int mCellSize;
HeightfieldShape mShape;
};
class RecastMeshManager class RecastMeshManager
{ {
public: public:
@ -41,14 +47,13 @@ namespace DetourNavigator
std::optional<RemovedRecastMeshObject> removeObject(const ObjectId id); std::optional<RemovedRecastMeshObject> removeObject(const ObjectId id);
bool addWater(const osg::Vec2i& cellPosition, const int cellSize, const osg::Vec3f& shift); bool addWater(const osg::Vec2i& cellPosition, int cellSize, float level);
std::optional<Cell> removeWater(const osg::Vec2i& cellPosition); std::optional<Water> removeWater(const osg::Vec2i& cellPosition);
bool addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const osg::Vec3f& shift, bool addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape);
const HeightfieldShape& shape);
std::optional<Cell> removeHeightfield(const osg::Vec2i& cellPosition); std::optional<SizedHeightfieldShape> removeHeightfield(const osg::Vec2i& cellPosition);
std::shared_ptr<RecastMesh> getMesh() const; std::shared_ptr<RecastMesh> getMesh() const;
@ -65,19 +70,13 @@ namespace DetourNavigator
Version mNavMeshVersion; Version mNavMeshVersion;
}; };
struct Heightfield
{
Cell mCell;
HeightfieldShape mShape;
};
const std::size_t mGeneration; const std::size_t mGeneration;
const TileBounds mTileBounds; const TileBounds mTileBounds;
mutable std::mutex mMutex; mutable std::mutex mMutex;
std::size_t mRevision = 0; std::size_t mRevision = 0;
std::map<ObjectId, OscillatingRecastMeshObject> mObjects; std::map<ObjectId, OscillatingRecastMeshObject> mObjects;
std::map<osg::Vec2i, Cell> mWater; std::map<osg::Vec2i, Water> mWater;
std::map<osg::Vec2i, Heightfield> mHeightfields; std::map<osg::Vec2i, SizedHeightfieldShape> mHeightfields;
std::optional<Report> mLastNavMeshReportedChange; std::optional<Report> mLastNavMeshReportedChange;
std::optional<Report> mLastNavMeshReport; std::optional<Report> mLastNavMeshReport;
}; };

View file

@ -2,6 +2,7 @@
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_TILEBOUNDS_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_TILEBOUNDS_H
#include <osg/Vec2f> #include <osg/Vec2f>
#include <osg/Vec2i>
#include <algorithm> #include <algorithm>
#include <optional> #include <optional>
@ -32,6 +33,14 @@ namespace DetourNavigator
return std::nullopt; return std::nullopt;
return TileBounds {osg::Vec2f(minX, minY), osg::Vec2f(maxX, maxY)}; return TileBounds {osg::Vec2f(minX, minY), osg::Vec2f(maxX, maxY)};
} }
inline TileBounds maxCellTileBounds(const osg::Vec2i& position, int size)
{
return TileBounds {
osg::Vec2f(position.x(), position.y()) * size,
osg::Vec2f(position.x() + 1, position.y() + 1) * size
};
}
} }
#endif #endif

View file

@ -54,8 +54,7 @@ namespace DetourNavigator
return result; return result;
} }
bool TileCachedRecastMeshManager::addWater(const osg::Vec2i& cellPosition, const int cellSize, bool TileCachedRecastMeshManager::addWater(const osg::Vec2i& cellPosition, int cellSize, float level)
const osg::Vec3f& shift)
{ {
auto& tilesPositions = mWaterTilesPositions[cellPosition]; auto& tilesPositions = mWaterTilesPositions[cellPosition];
@ -66,7 +65,7 @@ namespace DetourNavigator
const auto tiles = mTiles.lock(); const auto tiles = mTiles.lock();
for (auto& tile : *tiles) for (auto& tile : *tiles)
{ {
if (tile.second->addWater(cellPosition, cellSize, shift)) if (tile.second->addWater(cellPosition, cellSize, level))
{ {
tilesPositions.push_back(tile.first); tilesPositions.push_back(tile.first);
result = true; result = true;
@ -75,6 +74,7 @@ namespace DetourNavigator
} }
else else
{ {
const btVector3 shift = Misc::Convert::toBullet(getWaterShift3d(cellPosition, cellSize, level));
getTilesPositions(cellSize, shift, mSettings, [&] (const TilePosition& tilePosition) getTilesPositions(cellSize, shift, mSettings, [&] (const TilePosition& tilePosition)
{ {
const auto tiles = mTiles.lock(); const auto tiles = mTiles.lock();
@ -85,7 +85,7 @@ namespace DetourNavigator
tile = tiles->emplace(tilePosition, tile = tiles->emplace(tilePosition,
std::make_shared<CachedRecastMeshManager>(tileBounds, mTilesGeneration)).first; std::make_shared<CachedRecastMeshManager>(tileBounds, mTilesGeneration)).first;
} }
if (tile->second->addWater(cellPosition, cellSize, shift)) if (tile->second->addWater(cellPosition, cellSize, level))
{ {
tilesPositions.push_back(tilePosition); tilesPositions.push_back(tilePosition);
result = true; result = true;
@ -99,12 +99,12 @@ namespace DetourNavigator
return result; return result;
} }
std::optional<Cell> TileCachedRecastMeshManager::removeWater(const osg::Vec2i& cellPosition) std::optional<Water> TileCachedRecastMeshManager::removeWater(const osg::Vec2i& cellPosition)
{ {
const auto object = mWaterTilesPositions.find(cellPosition); const auto object = mWaterTilesPositions.find(cellPosition);
if (object == mWaterTilesPositions.end()) if (object == mWaterTilesPositions.end())
return std::nullopt; return std::nullopt;
std::optional<Cell> result; std::optional<Water> result;
for (const auto& tilePosition : object->second) for (const auto& tilePosition : object->second)
{ {
const auto tiles = mTiles.lock(); const auto tiles = mTiles.lock();
@ -126,8 +126,9 @@ namespace DetourNavigator
} }
bool TileCachedRecastMeshManager::addHeightfield(const osg::Vec2i& cellPosition, int cellSize, bool TileCachedRecastMeshManager::addHeightfield(const osg::Vec2i& cellPosition, int cellSize,
const osg::Vec3f& shift, const HeightfieldShape& shape) const HeightfieldShape& shape)
{ {
const btVector3 shift = getHeightfieldShift(shape, cellPosition, cellSize);
auto& tilesPositions = mHeightfieldTilesPositions[cellPosition]; auto& tilesPositions = mHeightfieldTilesPositions[cellPosition];
bool result = false; bool result = false;
@ -142,7 +143,7 @@ namespace DetourNavigator
tile = tiles->emplace(tilePosition, tile = tiles->emplace(tilePosition,
std::make_shared<CachedRecastMeshManager>(tileBounds, mTilesGeneration)).first; std::make_shared<CachedRecastMeshManager>(tileBounds, mTilesGeneration)).first;
} }
if (tile->second->addHeightfield(cellPosition, cellSize, shift, shape)) if (tile->second->addHeightfield(cellPosition, cellSize, shape))
{ {
tilesPositions.push_back(tilePosition); tilesPositions.push_back(tilePosition);
result = true; result = true;
@ -155,12 +156,12 @@ namespace DetourNavigator
return result; return result;
} }
std::optional<Cell> TileCachedRecastMeshManager::removeHeightfield(const osg::Vec2i& cellPosition) std::optional<SizedHeightfieldShape> TileCachedRecastMeshManager::removeHeightfield(const osg::Vec2i& cellPosition)
{ {
const auto object = mHeightfieldTilesPositions.find(cellPosition); const auto object = mHeightfieldTilesPositions.find(cellPosition);
if (object == mHeightfieldTilesPositions.end()) if (object == mHeightfieldTilesPositions.end())
return std::nullopt; return std::nullopt;
std::optional<Cell> result; std::optional<SizedHeightfieldShape> result;
for (const auto& tilePosition : object->second) for (const auto& tilePosition : object->second)
{ {
const auto tiles = mTiles.lock(); const auto tiles = mTiles.lock();

View file

@ -76,14 +76,13 @@ namespace DetourNavigator
std::optional<RemovedRecastMeshObject> removeObject(const ObjectId id); std::optional<RemovedRecastMeshObject> removeObject(const ObjectId id);
bool addWater(const osg::Vec2i& cellPosition, const int cellSize, const osg::Vec3f& shift); bool addWater(const osg::Vec2i& cellPosition, int cellSize, float level);
std::optional<Cell> removeWater(const osg::Vec2i& cellPosition); std::optional<Water> removeWater(const osg::Vec2i& cellPosition);
bool addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const osg::Vec3f& shift, bool addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape);
const HeightfieldShape& shape);
std::optional<Cell> removeHeightfield(const osg::Vec2i& cellPosition); std::optional<SizedHeightfieldShape> removeHeightfield(const osg::Vec2i& cellPosition);
std::shared_ptr<RecastMesh> getMesh(const TilePosition& tilePosition) const; std::shared_ptr<RecastMesh> getMesh(const TilePosition& tilePosition) const;