1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-01-19 19:53:53 +00:00

Limit NavMeshManager update range by player tile and max tiles

Object AABB may be much larger than area currently covered by navmesh. In this
case all tiles beyond covered range should be ignored. Attempt to iterate over
them will not result in any new tile updates but can take quite a while. At
maximum this can be pow(INT_MAX - INT_MIN, 2) iterations.

Use arbitrary time limit to check for update call to finish in the test.
This commit is contained in:
elsid 2023-01-15 03:24:37 +01:00
parent ab54bf0641
commit 44429f0393
No known key found for this signature in database
GPG key ID: 4DE04C198CBA7625
5 changed files with 99 additions and 55 deletions

View file

@ -20,9 +20,12 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <array> #include <array>
#include <condition_variable>
#include <deque> #include <deque>
#include <limits> #include <limits>
#include <memory> #include <memory>
#include <mutex>
#include <thread>
MATCHER_P3(Vec3fEq, x, y, z, "") MATCHER_P3(Vec3fEq, x, y, z, "")
{ {
@ -1226,4 +1229,33 @@ namespace
findPath(*mNavigator, agentBounds, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), findPath(*mNavigator, agentBounds, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
Status::NavMeshNotFound); Status::NavMeshNotFound);
} }
TEST_F(DetourNavigatorNavigatorTest, update_for_very_big_object_should_be_limited)
{
CollisionShapeInstance bigBox(std::make_unique<btBoxShape>(btVector3(1e9, 1e9, 1e9)));
mNavigator->updateBounds(mPlayerPosition, nullptr);
mNavigator->addAgent(mAgentBounds);
mNavigator->addObject(ObjectId(&bigBox.shape()), ObjectShapes(bigBox.instance(), mObjectTransform),
btTransform::getIdentity(), nullptr);
bool updated = false;
std::condition_variable updateFinished;
std::mutex mutex;
std::thread thread([&] {
mNavigator->update(mPlayerPosition, nullptr);
std::lock_guard lock(mutex);
updated = true;
updateFinished.notify_all();
});
{
std::unique_lock lock(mutex);
updateFinished.wait_for(lock, std::chrono::seconds(3), [&] { return updated; });
ASSERT_TRUE(updated);
}
thread.join();
}
} }

View file

@ -1,4 +1,5 @@
#include <components/detournavigator/debug.hpp>
#include <components/detournavigator/settingsutils.hpp> #include <components/detournavigator/settingsutils.hpp>
#include <components/detournavigator/tilecachedrecastmeshmanager.hpp> #include <components/detournavigator/tilecachedrecastmeshmanager.hpp>
#include <components/esm/refid.hpp> #include <components/esm/refid.hpp>
@ -82,10 +83,11 @@ namespace
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
const btBoxShape boxShape(btVector3(20, 20, 100)); const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform); const CollisionShape shape(mInstance, boxShape, mObjectTransform);
TileBounds bounds; const TilesPositionsRange range{
bounds.mMin = osg::Vec2f(182, 182); .mBegin = TilePosition(0, 0),
bounds.mMax = osg::Vec2f(1000, 1000); .mEnd = TilePosition(1, 1),
manager.setBounds(bounds, nullptr); };
manager.setRange(range, nullptr);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, nullptr); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, nullptr);
EXPECT_THAT(manager.takeChangedTiles(nullptr), ElementsAre(std::pair(TilePosition(0, 0), ChangeType::add))); EXPECT_THAT(manager.takeChangedTiles(nullptr), ElementsAre(std::pair(TilePosition(0, 0), ChangeType::add)));
} }
@ -97,10 +99,11 @@ namespace
const btTransform transform( const btTransform transform(
btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0));
const CollisionShape shape(mInstance, boxShape, mObjectTransform); const CollisionShape shape(mInstance, boxShape, mObjectTransform);
TileBounds bounds; const TilesPositionsRange range{
bounds.mMin = osg::Vec2f(-1000, -1000); .mBegin = TilePosition(-1, -1),
bounds.mMax = osg::Vec2f(1000, 1000); .mEnd = TilePosition(2, 2),
manager.setBounds(bounds, nullptr); };
manager.setRange(range, nullptr);
manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground, nullptr); manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground, nullptr);
manager.takeChangedTiles(nullptr); manager.takeChangedTiles(nullptr);
EXPECT_TRUE( EXPECT_TRUE(
@ -130,10 +133,11 @@ namespace
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
const btBoxShape boxShape(btVector3(20, 20, 100)); const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform); const CollisionShape shape(mInstance, boxShape, mObjectTransform);
TileBounds bounds; const TilesPositionsRange range{
bounds.mMin = osg::Vec2f(182, 182); .mBegin = TilePosition(0, 0),
bounds.mMax = osg::Vec2f(1000, 1000); .mEnd = TilePosition(1, 1),
manager.setBounds(bounds, nullptr); };
manager.setRange(range, nullptr);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, nullptr); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, nullptr);
manager.takeChangedTiles(nullptr); manager.takeChangedTiles(nullptr);
manager.removeObject(ObjectId(&boxShape), nullptr); manager.removeObject(ObjectId(&boxShape), nullptr);
@ -169,10 +173,11 @@ namespace
get_mesh_for_moved_object_should_return_recast_mesh_for_each_used_tile) get_mesh_for_moved_object_should_return_recast_mesh_for_each_used_tile)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
TileBounds bounds; const TilesPositionsRange range{
bounds.mMin = osg::Vec2f(-1000, -1000); .mBegin = TilePosition(-1, -1),
bounds.mMax = osg::Vec2f(1000, 1000); .mEnd = TilePosition(1, 1),
manager.setBounds(bounds, nullptr); };
manager.setRange(range, nullptr);
manager.setWorldspace(mWorldspace, nullptr); manager.setWorldspace(mWorldspace, nullptr);
const btBoxShape boxShape(btVector3(20, 20, 100)); const btBoxShape boxShape(btVector3(20, 20, 100));
@ -452,15 +457,18 @@ namespace
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
const btBoxShape boxShape(btVector3(20, 20, 100)); const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform); const CollisionShape shape(mInstance, boxShape, mObjectTransform);
TileBounds bounds; const TilesPositionsRange range1{
bounds.mMin = osg::Vec2f(182, 0); .mBegin = TilePosition(0, 0),
bounds.mMax = osg::Vec2f(1000, 1000); .mEnd = TilePosition(1, 1),
manager.setBounds(bounds, nullptr); };
manager.setRange(range1, nullptr);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, nullptr); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, nullptr);
bounds.mMin = osg::Vec2f(-1000, -1000); const TilesPositionsRange range2{
bounds.mMax = osg::Vec2f(0, -182); .mBegin = TilePosition(-1, -1),
.mEnd = TilePosition(0, 0),
};
manager.takeChangedTiles(nullptr); manager.takeChangedTiles(nullptr);
manager.setBounds(bounds, nullptr); manager.setRange(range2, nullptr);
EXPECT_THAT(manager.takeChangedTiles(nullptr), EXPECT_THAT(manager.takeChangedTiles(nullptr),
ElementsAre( ElementsAre(
std::pair(TilePosition(-1, -1), ChangeType::add), std::pair(TilePosition(0, 0), ChangeType::remove))); std::pair(TilePosition(-1, -1), ChangeType::add), std::pair(TilePosition(0, 0), ChangeType::remove)));

View file

@ -41,14 +41,18 @@ namespace DetourNavigator
{ {
namespace namespace
{ {
TileBounds makeBounds(const RecastSettings& settings, const osg::Vec2f& center, int maxTiles) TilesPositionsRange makeRange(const TilePosition& center, int maxTiles)
{ {
const float radius = fromNavMeshCoordinates( const int radius = static_cast<int>(std::ceil(std::sqrt(static_cast<float>(maxTiles) / osg::PIf) + 1));
settings, std::ceil(std::sqrt(static_cast<float>(maxTiles) / osg::PIf) + 1) * getTileSize(settings)); return TilesPositionsRange{
TileBounds result; .mBegin = center - TilePosition(radius, radius),
result.mMin = center - osg::Vec2f(radius, radius); .mEnd = center + TilePosition(radius + 1, radius + 1),
result.mMax = center + osg::Vec2f(radius, radius); };
return result; }
TilePosition toNavMeshTilePosition(const RecastSettings& settings, const osg::Vec3f& position)
{
return getTilePosition(settings, toNavMeshCoordinates(settings, position));
} }
} }
@ -72,9 +76,9 @@ namespace DetourNavigator
void NavMeshManager::updateBounds(const osg::Vec3f& playerPosition, const UpdateGuard* guard) void NavMeshManager::updateBounds(const osg::Vec3f& playerPosition, const UpdateGuard* guard)
{ {
const TileBounds bounds = makeBounds( const TilePosition playerTile = toNavMeshTilePosition(mSettings.mRecast, playerPosition);
mSettings.mRecast, osg::Vec2f(playerPosition.x(), playerPosition.y()), mSettings.mMaxTilesNumber); const TilesPositionsRange range = makeRange(playerTile, mSettings.mMaxTilesNumber);
mRecastMeshManager.setBounds(bounds, getImpl(guard)); mRecastMeshManager.setRange(range, getImpl(guard));
} }
bool NavMeshManager::addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, bool NavMeshManager::addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform,
@ -161,15 +165,14 @@ namespace DetourNavigator
void NavMeshManager::update(const osg::Vec3f& playerPosition, const UpdateGuard* guard) void NavMeshManager::update(const osg::Vec3f& playerPosition, const UpdateGuard* guard)
{ {
const auto playerTile const TilePosition playerTile = toNavMeshTilePosition(mSettings.mRecast, playerPosition);
= getTilePosition(mSettings.mRecast, toNavMeshCoordinates(mSettings.mRecast, playerPosition));
if (mLastRecastMeshManagerRevision == mRecastMeshManager.getRevision() && mPlayerTile.has_value() if (mLastRecastMeshManagerRevision == mRecastMeshManager.getRevision() && mPlayerTile.has_value()
&& *mPlayerTile == playerTile) && *mPlayerTile == playerTile)
return; return;
mLastRecastMeshManagerRevision = mRecastMeshManager.getRevision(); mLastRecastMeshManagerRevision = mRecastMeshManager.getRevision();
mPlayerTile = playerTile; mPlayerTile = playerTile;
const auto changedTiles = mRecastMeshManager.takeChangedTiles(getImpl(guard)); const auto changedTiles = mRecastMeshManager.takeChangedTiles(getImpl(guard));
const TilesPositionsRange range = mRecastMeshManager.getRange(); const TilesPositionsRange range = mRecastMeshManager.getLimitedObjectsRange();
for (const auto& [agentBounds, cached] : mCache) for (const auto& [agentBounds, cached] : mCache)
update(agentBounds, playerTile, range, cached, changedTiles); update(agentBounds, playerTile, range, cached, changedTiles);
} }
@ -179,6 +182,7 @@ namespace DetourNavigator
const std::map<osg::Vec2i, ChangeType>& changedTiles) const std::map<osg::Vec2i, ChangeType>& changedTiles)
{ {
std::map<osg::Vec2i, ChangeType> tilesToPost = changedTiles; std::map<osg::Vec2i, ChangeType> tilesToPost = changedTiles;
std::map<osg::Vec2i, ChangeType> tilesToPost1;
{ {
const auto locked = cached->lockConst(); const auto locked = cached->lockConst();
const auto& navMesh = locked->getImpl(); const auto& navMesh = locked->getImpl();
@ -222,7 +226,7 @@ namespace DetourNavigator
RecastMeshTiles NavMeshManager::getRecastMeshTiles() const RecastMeshTiles NavMeshManager::getRecastMeshTiles() const
{ {
RecastMeshTiles result; RecastMeshTiles result;
getTilesPositions(mRecastMeshManager.getRange(), [&](const TilePosition& v) { getTilesPositions(mRecastMeshManager.getLimitedObjectsRange(), [&](const TilePosition& v) {
if (auto mesh = mRecastMeshManager.getCachedMesh(mWorldspace, v)) if (auto mesh = mRecastMeshManager.getCachedMesh(mWorldspace, v))
result.emplace(v, std::move(mesh)); result.emplace(v, std::move(mesh));
}); });

View file

@ -18,9 +18,10 @@ namespace DetourNavigator
{ {
namespace namespace
{ {
const TileBounds infiniteTileBounds{ osg::Vec2f(-std::numeric_limits<float>::max(), const TilesPositionsRange infiniteRange{
-std::numeric_limits<float>::max()), .mBegin = TilePosition(std::numeric_limits<int>::min(), std::numeric_limits<int>::min()),
osg::Vec2f(std::numeric_limits<float>::max(), std::numeric_limits<float>::max()) }; .mEnd = TilePosition(std::numeric_limits<int>::max(), std::numeric_limits<int>::max()),
};
struct AddHeightfield struct AddHeightfield
{ {
@ -57,19 +58,17 @@ namespace DetourNavigator
TileCachedRecastMeshManager::TileCachedRecastMeshManager(const RecastSettings& settings) TileCachedRecastMeshManager::TileCachedRecastMeshManager(const RecastSettings& settings)
: mSettings(settings) : mSettings(settings)
, mBounds(infiniteTileBounds) , mRange(infiniteRange)
, mRange(makeTilesPositionsRange(mBounds.mMin, mBounds.mMax, mSettings))
{ {
} }
void TileCachedRecastMeshManager::setBounds(const TileBounds& bounds, const UpdateGuard* guard) void TileCachedRecastMeshManager::setRange(const TilesPositionsRange& range, const UpdateGuard* guard)
{ {
if (mBounds == bounds) if (mRange == range)
return; return;
bool changed = false; bool changed = false;
const auto newRange = makeTilesPositionsRange(bounds.mMin, bounds.mMax, mSettings); if (mRange != infiniteRange)
if (mBounds != infiniteTileBounds)
{ {
for (const auto& [id, data] : mObjects) for (const auto& [id, data] : mObjects)
{ {
@ -77,14 +76,14 @@ namespace DetourNavigator
= makeTilesPositionsRange(data->mObject.getShape(), data->mObject.getTransform(), mSettings); = makeTilesPositionsRange(data->mObject.getShape(), data->mObject.getTransform(), mSettings);
getTilesPositions(getIntersection(mRange, objectRange), [&](const TilePosition& v) { getTilesPositions(getIntersection(mRange, objectRange), [&](const TilePosition& v) {
if (!isInTilesPositionsRange(newRange, v)) if (!isInTilesPositionsRange(range, v))
{ {
addChangedTile(v, ChangeType::remove); addChangedTile(v, ChangeType::remove);
changed = true; changed = true;
} }
}); });
getTilesPositions(getIntersection(newRange, objectRange), [&](const TilePosition& v) { getTilesPositions(getIntersection(range, objectRange), [&](const TilePosition& v) {
if (!isInTilesPositionsRange(mRange, v)) if (!isInTilesPositionsRange(mRange, v))
{ {
addChangedTile(v, ChangeType::add); addChangedTile(v, ChangeType::add);
@ -100,17 +99,19 @@ namespace DetourNavigator
++mRevision; ++mRevision;
} }
mBounds = bounds; mRange = range;
mRange = newRange;
} }
TilesPositionsRange TileCachedRecastMeshManager::getRange() const TilesPositionsRange TileCachedRecastMeshManager::getLimitedObjectsRange() const
{ {
if (mObjects.empty())
return {};
const auto bounds = mObjectIndex.bounds(); const auto bounds = mObjectIndex.bounds();
return TilesPositionsRange{ const TilesPositionsRange objectsRange{
.mBegin = makeTilePosition(bounds.min_corner()), .mBegin = makeTilePosition(bounds.min_corner()),
.mEnd = makeTilePosition(bounds.max_corner()) + TilePosition(1, 1), .mEnd = makeTilePosition(bounds.max_corner()) + TilePosition(1, 1),
}; };
return getIntersection(mRange, objectsRange);
} }
void TileCachedRecastMeshManager::setWorldspace(const ESM::RefId& worldspace, const UpdateGuard* guard) void TileCachedRecastMeshManager::setWorldspace(const ESM::RefId& worldspace, const UpdateGuard* guard)

View file

@ -44,9 +44,9 @@ namespace DetourNavigator
explicit TileCachedRecastMeshManager(const RecastSettings& settings); explicit TileCachedRecastMeshManager(const RecastSettings& settings);
void setBounds(const TileBounds& bounds, const UpdateGuard* guard); void setRange(const TilesPositionsRange& range, const UpdateGuard* guard);
TilesPositionsRange getRange() const; TilesPositionsRange getLimitedObjectsRange() const;
void setWorldspace(const ESM::RefId& worldspace, const UpdateGuard* guard); void setWorldspace(const ESM::RefId& worldspace, const UpdateGuard* guard);
@ -126,7 +126,6 @@ namespace DetourNavigator
using HeightfieldIndexValue = std::pair<IndexBox, std::map<osg::Vec2i, HeightfieldData>::const_iterator>; using HeightfieldIndexValue = std::pair<IndexBox, std::map<osg::Vec2i, HeightfieldData>::const_iterator>;
const RecastSettings& mSettings; const RecastSettings& mSettings;
TileBounds mBounds;
TilesPositionsRange mRange; TilesPositionsRange mRange;
ESM::RefId mWorldspace; ESM::RefId mWorldspace;
std::unordered_map<ObjectId, std::unique_ptr<ObjectData>> mObjects; std::unordered_map<ObjectId, std::unique_ptr<ObjectData>> mObjects;