Limit RecastMeshManager range by active cell grid

esm4-texture
elsid 7 months ago
parent 49db37ee29
commit 45d62ee59f
No known key found for this signature in database
GPG Key ID: 4DE04C198CBA7625

@ -572,11 +572,11 @@ namespace MWWorld
void Scene::changeCellGrid(const osg::Vec3f& pos, ESM::ExteriorCellLocation playerCellIndex, bool changeEvent)
{
mHalfGridSize
const int halfGridSize
= isEsm4Ext(playerCellIndex.mWorldspace) ? Constants::ESM4CellGridRadius : Constants::CellGridRadius;
auto navigatorUpdateGuard = mNavigator.makeUpdateGuard();
int playerCellX = playerCellIndex.mX;
int playerCellY = playerCellIndex.mY;
const int playerCellX = playerCellIndex.mX;
const int playerCellY = playerCellIndex.mY;
for (auto iter = mActiveCells.begin(); iter != mActiveCells.end();)
{
@ -585,15 +585,21 @@ namespace MWWorld
{
const auto dx = std::abs(playerCellX - cell->getCell()->getGridX());
const auto dy = std::abs(playerCellY - cell->getCell()->getGridY());
if (dx > mHalfGridSize || dy > mHalfGridSize)
if (dx > halfGridSize || dy > halfGridSize)
unloadCell(cell, navigatorUpdateGuard.get());
}
else
unloadCell(cell, navigatorUpdateGuard.get());
}
mNavigator.updateBounds(playerCellIndex.mWorldspace, pos, navigatorUpdateGuard.get());
const DetourNavigator::CellGridBounds cellGridBounds{
.mCenter = osg::Vec2i(playerCellX, playerCellY),
.mHalfSize = halfGridSize,
};
mNavigator.updateBounds(playerCellIndex.mWorldspace, cellGridBounds, pos, navigatorUpdateGuard.get());
mHalfGridSize = halfGridSize;
mCurrentGridCenter = osg::Vec2i(playerCellX, playerCellY);
osg::Vec4i newGrid = gridCenterToBounds(mCurrentGridCenter);
mRendering.setActiveGrid(newGrid);
@ -696,7 +702,16 @@ namespace MWWorld
ESM::ExteriorCellLocation(it->mData.mX, it->mData.mY, ESM::Cell::sDefaultWorldspaceId));
const osg::Vec3f position
= osg::Vec3f(it->mData.mX + 0.5f, it->mData.mY + 0.5f, 0) * Constants::CellSizeInUnits;
mNavigator.updateBounds(ESM::Cell::sDefaultWorldspaceId, position, navigatorUpdateGuard.get());
const osg::Vec2i cellPosition(it->mData.mX, it->mData.mY);
const DetourNavigator::CellGridBounds cellGridBounds{
.mCenter = osg::Vec2i(it->mData.mX, it->mData.mY),
.mHalfSize = Constants::CellGridRadius,
};
mNavigator.updateBounds(
ESM::Cell::sDefaultWorldspaceId, cellGridBounds, position, navigatorUpdateGuard.get());
loadCell(cell, nullptr, false, position, navigatorUpdateGuard.get());
mNavigator.update(position, navigatorUpdateGuard.get());
@ -752,7 +767,8 @@ namespace MWWorld
CellStore& cell = mWorld.getWorldModel().getInterior(it->mName);
ESM::Position position;
mWorld.findInteriorPosition(it->mName, position);
mNavigator.updateBounds(cell.getCell()->getWorldSpace(), position.asVec3(), navigatorUpdateGuard.get());
mNavigator.updateBounds(
cell.getCell()->getWorldSpace(), std::nullopt, position.asVec3(), navigatorUpdateGuard.get());
loadCell(cell, nullptr, false, position.asVec3(), navigatorUpdateGuard.get());
mNavigator.update(position.asVec3(), navigatorUpdateGuard.get());
@ -902,7 +918,8 @@ namespace MWWorld
loadingListener->setProgressRange(cell.count());
mNavigator.updateBounds(cell.getCell()->getWorldSpace(), position.asVec3(), navigatorUpdateGuard.get());
mNavigator.updateBounds(
cell.getCell()->getWorldSpace(), std::nullopt, position.asVec3(), navigatorUpdateGuard.get());
// Load cell.
mPagedRefs.clear();

@ -859,6 +859,17 @@ namespace
EXPECT_EQ(mNavigator->getNavMesh(mAgentBounds)->lockConst()->getVersion(), version);
}
std::pair<TilePosition, TilePosition> getMinMax(const RecastMeshTiles& tiles)
{
const auto lessByX = [](const auto& l, const auto& r) { return l.first.x() < r.first.x(); };
const auto lessByY = [](const auto& l, const auto& r) { return l.first.y() < r.first.y(); };
const auto [minX, maxX] = std::ranges::minmax_element(tiles, lessByX);
const auto [minY, maxY] = std::ranges::minmax_element(tiles, lessByY);
return { TilePosition(minX->first.x(), minY->first.y()), TilePosition(maxX->first.x(), maxY->first.y()) };
}
TEST_F(DetourNavigatorNavigatorTest, update_for_very_big_object_should_be_limited)
{
const float size = static_cast<float>((1 << 22) - 1);
@ -867,8 +878,10 @@ namespace
.mPosition = ESM::Position{ .pos = { 0, 0, 0 }, .rot{ 0, 0, 0 } },
.mScale = 1.0f,
};
const std::optional<CellGridBounds> cellGridBounds = std::nullopt;
const osg::Vec3f playerPosition(32, 1024, 0);
mNavigator->updateBounds(mWorldspace, mPlayerPosition, nullptr);
mNavigator->updateBounds(mWorldspace, cellGridBounds, playerPosition, nullptr);
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
mNavigator->addObject(ObjectId(&bigBox.shape()), ObjectShapes(bigBox.instance(), objectTransform),
btTransform::getIdentity(), nullptr);
@ -878,7 +891,61 @@ namespace
std::mutex mutex;
std::thread thread([&] {
mNavigator->update(mPlayerPosition, nullptr);
auto guard = mNavigator->makeUpdateGuard();
mNavigator->update(playerPosition, guard.get());
std::lock_guard lock(mutex);
updated = true;
updateFinished.notify_all();
});
{
std::unique_lock lock(mutex);
updateFinished.wait_for(lock, std::chrono::seconds(10), [&] { return updated; });
ASSERT_TRUE(updated);
}
thread.join();
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
const auto recastMeshTiles = mNavigator->getRecastMeshTiles();
ASSERT_EQ(recastMeshTiles.size(), 1033);
EXPECT_EQ(getMinMax(recastMeshTiles), std::pair(TilePosition(-18, -17), TilePosition(18, 19)));
const auto navMesh = mNavigator->getNavMesh(mAgentBounds);
ASSERT_NE(navMesh, nullptr);
std::size_t usedNavMeshTiles = 0;
navMesh->lockConst()->forEachUsedTile([&](const auto&...) { ++usedNavMeshTiles; });
EXPECT_EQ(usedNavMeshTiles, 1024);
}
TEST_F(DetourNavigatorNavigatorTest, update_should_be_limited_by_cell_grid_bounds)
{
const float size = static_cast<float>((1 << 22) - 1);
CollisionShapeInstance bigBox(std::make_unique<btBoxShape>(btVector3(size, size, 1)));
const ObjectTransform objectTransform{
.mPosition = ESM::Position{ .pos = { 0, 0, 0 }, .rot{ 0, 0, 0 } },
.mScale = 1.0f,
};
const CellGridBounds cellGridBounds{
.mCenter = osg::Vec2i(0, 0),
.mHalfSize = 1,
};
const osg::Vec3f playerPosition(32, 1024, 0);
mNavigator->updateBounds(mWorldspace, cellGridBounds, playerPosition, nullptr);
ASSERT_TRUE(mNavigator->addAgent(mAgentBounds));
mNavigator->addObject(ObjectId(&bigBox.shape()), ObjectShapes(bigBox.instance(), objectTransform),
btTransform::getIdentity(), nullptr);
bool updated = false;
std::condition_variable updateFinished;
std::mutex mutex;
std::thread thread([&] {
auto guard = mNavigator->makeUpdateGuard();
mNavigator->update(playerPosition, guard.get());
std::lock_guard lock(mutex);
updated = true;
updateFinished.notify_all();
@ -886,7 +953,7 @@ namespace
{
std::unique_lock lock(mutex);
updateFinished.wait_for(lock, std::chrono::seconds(3), [&] { return updated; });
updateFinished.wait_for(lock, std::chrono::seconds(10), [&] { return updated; });
ASSERT_TRUE(updated);
}
@ -894,14 +961,16 @@ namespace
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
EXPECT_EQ(mNavigator->getRecastMeshTiles().size(), 509);
const auto recastMeshTiles = mNavigator->getRecastMeshTiles();
ASSERT_EQ(recastMeshTiles.size(), 854);
EXPECT_EQ(getMinMax(recastMeshTiles), std::pair(TilePosition(-12, -12), TilePosition(18, 19)));
const auto navMesh = mNavigator->getNavMesh(mAgentBounds);
ASSERT_NE(navMesh, nullptr);
std::size_t usedNavMeshTiles = 0;
navMesh->lockConst()->forEachUsedTile([&](const auto&...) { ++usedNavMeshTiles; });
EXPECT_EQ(usedNavMeshTiles, 509);
EXPECT_EQ(usedNavMeshTiles, 854);
}
struct DetourNavigatorNavigatorNotSupportedAgentBoundsTest : TestWithParam<AgentBounds>

@ -39,7 +39,7 @@ namespace DetourNavigator
result.mDetour.mMaxPolygonPathSize = 1024;
result.mDetour.mMaxSmoothPathSize = 1024;
result.mDetour.mMaxPolys = 4096;
result.mMaxTilesNumber = 512;
result.mMaxTilesNumber = 1024;
result.mMinUpdateInterval = std::chrono::milliseconds(50);
result.mWriteToNavMeshDb = true;
return result;

@ -173,7 +173,7 @@ namespace
TileCachedRecastMeshManager manager(mSettings);
const TilesPositionsRange range{
.mBegin = TilePosition(-1, -1),
.mEnd = TilePosition(1, 1),
.mEnd = TilePosition(2, 2),
};
manager.setRange(range, nullptr);
manager.setWorldspace(mWorldspace, nullptr);

@ -0,0 +1,15 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_CELLGRIDBOUNDS_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_CELLGRIDBOUNDS_H
#include <osg/Vec2i>
namespace DetourNavigator
{
struct CellGridBounds
{
osg::Vec2i mCenter;
int mHalfSize;
};
}
#endif

@ -3,7 +3,9 @@
#include <cassert>
#include <filesystem>
#include <optional>
#include "cellgridbounds.hpp"
#include "heightfieldshape.hpp"
#include "objectid.hpp"
#include "objecttransform.hpp"
@ -89,7 +91,8 @@ namespace DetourNavigator
virtual void removeAgent(const AgentBounds& agentBounds) = 0;
// Updates bounds for recast mesh and navmesh tiles, removes tiles outside the range.
virtual void updateBounds(ESM::RefId worldspace, const osg::Vec3f& playerPosition, const UpdateGuard* guard)
virtual void updateBounds(ESM::RefId worldspace, const std::optional<CellGridBounds>& cellGridBounds,
const osg::Vec3f& playerPosition, const UpdateGuard* guard)
= 0;
/**

@ -33,9 +33,10 @@ namespace DetourNavigator
--it->second;
}
void NavigatorImpl::updateBounds(ESM::RefId worldspace, const osg::Vec3f& playerPosition, const UpdateGuard* guard)
void NavigatorImpl::updateBounds(ESM::RefId worldspace, const std::optional<CellGridBounds>& cellGridBounds,
const osg::Vec3f& playerPosition, const UpdateGuard* guard)
{
mNavMeshManager.updateBounds(worldspace, playerPosition, guard);
mNavMeshManager.updateBounds(worldspace, cellGridBounds, playerPosition, guard);
}
void NavigatorImpl::addObject(

@ -27,7 +27,8 @@ namespace DetourNavigator
void removeAgent(const AgentBounds& agentBounds) override;
void updateBounds(ESM::RefId worldspace, const osg::Vec3f& playerPosition, const UpdateGuard* guard) override;
void updateBounds(ESM::RefId worldspace, const std::optional<CellGridBounds>& cellGridBounds,
const osg::Vec3f& playerPosition, const UpdateGuard* guard) override;
void addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform,
const UpdateGuard* guard) override;

@ -24,8 +24,8 @@ namespace DetourNavigator
void removeAgent(const AgentBounds& /*agentBounds*/) override {}
void updateBounds(
ESM::RefId /*worldspace*/, const osg::Vec3f& /*playerPosition*/, const UpdateGuard* /*guard*/) override
void updateBounds(ESM::RefId /*worldspace*/, const std::optional<CellGridBounds>& /*cellGridBounds*/,
const osg::Vec3f& /*playerPosition*/, const UpdateGuard* /*guard*/) override
{
}

@ -1,4 +1,5 @@
#include "navmeshmanager.hpp"
#include "debug.hpp"
#include "gettilespositions.hpp"
#include "makenavmesh.hpp"
@ -8,6 +9,7 @@
#include "waitconditiontype.hpp"
#include <components/debug/debuglog.hpp>
#include <components/esm/util.hpp>
#include <osg/io_utils>
@ -35,15 +37,46 @@ namespace DetourNavigator
{
namespace
{
TilesPositionsRange makeRange(const TilePosition& center, int maxTiles)
int getMaxRadius(int maxTiles)
{
return static_cast<int>(std::ceil(std::sqrt(static_cast<float>(maxTiles) / osg::PIf) + 1));
}
TilesPositionsRange makeRange(const TilePosition& center, int radius)
{
const int radius = static_cast<int>(std::ceil(std::sqrt(static_cast<float>(maxTiles) / osg::PIf) + 1));
return TilesPositionsRange{
.mBegin = center - TilePosition(radius, radius),
.mEnd = center + TilePosition(radius + 1, radius + 1),
};
}
osg::Vec2f getMinCellGridPosition(const osg::Vec2i& center, int offset, float cellSize)
{
const osg::Vec2i cell = center + osg::Vec2i(offset, offset);
return osg::Vec2f(static_cast<float>(cell.x()) * cellSize, static_cast<float>(cell.y()) * cellSize);
}
TilesPositionsRange makeCellGridRange(
const RecastSettings& settings, ESM::RefId worldspace, const CellGridBounds& bounds)
{
const float floatCellSize = static_cast<float>(ESM::getCellSize(worldspace));
const osg::Vec2f min = getMinCellGridPosition(bounds.mCenter, -bounds.mHalfSize, floatCellSize);
const osg::Vec2f max = getMinCellGridPosition(bounds.mCenter, bounds.mHalfSize + 1, floatCellSize);
return TilesPositionsRange{
.mBegin = getTilePosition(settings, toNavMeshCoordinates(settings, min)),
.mEnd = getTilePosition(settings, toNavMeshCoordinates(settings, max)),
};
}
TilesPositionsRange makeRange(const Settings& settings, ESM::RefId worldspace,
const std::optional<CellGridBounds>& bounds, int radius, const TilePosition& center)
{
TilesPositionsRange result = makeRange(center, radius);
if (bounds.has_value())
result = getIntersection(result, makeCellGridRange(settings.mRecast, worldspace, *bounds));
return result;
}
TilePosition toNavMeshTilePosition(const RecastSettings& settings, const osg::Vec3f& position)
{
return getTilePosition(settings, toNavMeshCoordinates(settings, position));
@ -52,13 +85,15 @@ namespace DetourNavigator
NavMeshManager::NavMeshManager(const Settings& settings, std::unique_ptr<NavMeshDb>&& db)
: mSettings(settings)
, mMaxRadius(getMaxRadius(settings.mMaxTilesNumber))
, mRecastMeshManager(settings.mRecast)
, mOffMeshConnectionsManager(settings.mRecast)
, mAsyncNavMeshUpdater(settings, mRecastMeshManager, mOffMeshConnectionsManager, std::move(db))
{
}
void NavMeshManager::updateBounds(ESM::RefId worldspace, const osg::Vec3f& playerPosition, const UpdateGuard* guard)
void NavMeshManager::updateBounds(ESM::RefId worldspace, const std::optional<CellGridBounds>& cellGridBounds,
const osg::Vec3f& playerPosition, const UpdateGuard* guard)
{
if (worldspace != mWorldspace)
{
@ -69,8 +104,9 @@ namespace DetourNavigator
}
const TilePosition playerTile = toNavMeshTilePosition(mSettings.mRecast, playerPosition);
const TilesPositionsRange range = makeRange(playerTile, mSettings.mMaxTilesNumber);
mRecastMeshManager.setRange(range, guard);
mRecastMeshManager.setRange(makeRange(mSettings, worldspace, cellGridBounds, mMaxRadius, playerTile), guard);
mCellGridBounds = cellGridBounds;
}
bool NavMeshManager::addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform,
@ -162,7 +198,7 @@ namespace DetourNavigator
return;
mLastRecastMeshManagerRevision = mRecastMeshManager.getRevision();
mPlayerTile = playerTile;
mRecastMeshManager.setRange(makeRange(playerTile, mSettings.mMaxTilesNumber), guard);
mRecastMeshManager.setRange(makeRange(mSettings, mWorldspace, mCellGridBounds, mMaxRadius, playerTile), guard);
const auto changedTiles = mRecastMeshManager.takeChangedTiles(guard);
const TilesPositionsRange range = mRecastMeshManager.getLimitedObjectsRange();
for (const auto& [agentBounds, cached] : mCache)

@ -3,6 +3,7 @@
#include "agentbounds.hpp"
#include "asyncnavmeshupdater.hpp"
#include "cellgridbounds.hpp"
#include "heightfieldshape.hpp"
#include "offmeshconnectionsmanager.hpp"
#include "recastmeshtiles.hpp"
@ -24,7 +25,8 @@ namespace DetourNavigator
ScopedUpdateGuard makeUpdateGuard() { return mRecastMeshManager.makeUpdateGuard(); }
void updateBounds(ESM::RefId worldspace, const osg::Vec3f& playerPosition, const UpdateGuard* guard);
void updateBounds(ESM::RefId worldspace, const std::optional<CellGridBounds>& cellGridBounds,
const osg::Vec3f& playerPosition, const UpdateGuard* guard);
bool addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform,
const AreaType areaType, const UpdateGuard* guard);
@ -65,7 +67,9 @@ namespace DetourNavigator
private:
const Settings& mSettings;
const int mMaxRadius;
ESM::RefId mWorldspace;
std::optional<CellGridBounds> mCellGridBounds;
TileCachedRecastMeshManager mRecastMeshManager;
OffMeshConnectionsManager mOffMeshConnectionsManager;
AsyncNavMeshUpdater mAsyncNavMeshUpdater;

@ -107,11 +107,10 @@ namespace DetourNavigator
});
}
const MaybeLockGuard lock(mMutex, guard);
if (changed)
{
const MaybeLockGuard lock(mMutex, guard);
++mRevision;
}
mRange = range;
}
@ -357,6 +356,8 @@ namespace DetourNavigator
const std::lock_guard lock(mMutex);
if (mWorldspace != worldspace)
return nullptr;
if (!isInTilesPositionsRange(mRange, tilePosition))
return nullptr;
const auto it = mCache.find(tilePosition);
if (it != mCache.end() && it->second.mRecastMesh->getVersion() == it->second.mVersion)
return it->second.mRecastMesh;
@ -380,6 +381,8 @@ namespace DetourNavigator
const std::lock_guard lock(mMutex);
if (mWorldspace != worldspace)
return nullptr;
if (!isInTilesPositionsRange(mRange, tilePosition))
return nullptr;
const auto it = mCache.find(tilePosition);
if (it == mCache.end())
return nullptr;

Loading…
Cancel
Save