1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-01-16 15:59:54 +00:00
openmw/components/detournavigator/navmeshmanager.cpp
elsid 44429f0393
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.
2023-01-15 04:46:29 +01:00

243 lines
9.3 KiB
C++

#include "navmeshmanager.hpp"
#include "debug.hpp"
#include "exceptions.hpp"
#include "gettilespositions.hpp"
#include "makenavmesh.hpp"
#include "navmeshcacheitem.hpp"
#include "settings.hpp"
#include "settingsutils.hpp"
#include "waitconditiontype.hpp"
#include <components/bullethelpers/heightfield.hpp>
#include <components/debug/debuglog.hpp>
#include <components/esm/refid.hpp>
#include <components/misc/convert.hpp>
#include <osg/io_utils>
#include <DetourNavMesh.h>
#include <iterator>
namespace
{
/// Safely reset shared_ptr with definite underlying object destrutor call.
/// Assuming there is another thread holding copy of this shared_ptr or weak_ptr to this shared_ptr.
template <class T>
bool resetIfUnique(std::shared_ptr<T>& ptr)
{
const std::weak_ptr<T> weak(ptr);
ptr.reset();
if (auto shared = weak.lock())
{
ptr = std::move(shared);
return false;
}
return true;
}
}
namespace DetourNavigator
{
namespace
{
TilesPositionsRange makeRange(const TilePosition& center, int maxTiles)
{
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),
};
}
TilePosition toNavMeshTilePosition(const RecastSettings& settings, const osg::Vec3f& position)
{
return getTilePosition(settings, toNavMeshCoordinates(settings, position));
}
}
NavMeshManager::NavMeshManager(const Settings& settings, std::unique_ptr<NavMeshDb>&& db)
: mSettings(settings)
, mRecastMeshManager(settings.mRecast)
, mOffMeshConnectionsManager(settings.mRecast)
, mAsyncNavMeshUpdater(settings, mRecastMeshManager, mOffMeshConnectionsManager, std::move(db))
{
}
void NavMeshManager::setWorldspace(const ESM::RefId& worldspace, const UpdateGuard* guard)
{
if (worldspace == mWorldspace)
return;
mRecastMeshManager.setWorldspace(worldspace, getImpl(guard));
for (auto& [agent, cache] : mCache)
cache = std::make_shared<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), ++mGenerationCounter);
mWorldspace = worldspace;
}
void NavMeshManager::updateBounds(const osg::Vec3f& playerPosition, const UpdateGuard* guard)
{
const TilePosition playerTile = toNavMeshTilePosition(mSettings.mRecast, playerPosition);
const TilesPositionsRange range = makeRange(playerTile, mSettings.mMaxTilesNumber);
mRecastMeshManager.setRange(range, getImpl(guard));
}
bool NavMeshManager::addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform,
const AreaType areaType, const UpdateGuard* guard)
{
return mRecastMeshManager.addObject(id, shape, transform, areaType, getImpl(guard));
}
bool NavMeshManager::updateObject(
const ObjectId id, const btTransform& transform, const AreaType areaType, const UpdateGuard* guard)
{
return mRecastMeshManager.updateObject(id, transform, areaType, getImpl(guard));
}
void NavMeshManager::removeObject(const ObjectId id, const UpdateGuard* guard)
{
mRecastMeshManager.removeObject(id, getImpl(guard));
}
void NavMeshManager::addWater(const osg::Vec2i& cellPosition, int cellSize, float level, const UpdateGuard* guard)
{
mRecastMeshManager.addWater(cellPosition, cellSize, level, getImpl(guard));
}
void NavMeshManager::removeWater(const osg::Vec2i& cellPosition, const UpdateGuard* guard)
{
mRecastMeshManager.removeWater(cellPosition, getImpl(guard));
}
void NavMeshManager::addHeightfield(
const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape, const UpdateGuard* guard)
{
mRecastMeshManager.addHeightfield(cellPosition, cellSize, shape, getImpl(guard));
}
void NavMeshManager::removeHeightfield(const osg::Vec2i& cellPosition, const UpdateGuard* guard)
{
mRecastMeshManager.removeHeightfield(cellPosition, getImpl(guard));
}
void NavMeshManager::addAgent(const AgentBounds& agentBounds)
{
auto cached = mCache.find(agentBounds);
if (cached != mCache.end())
return;
mCache.insert(std::make_pair(
agentBounds, std::make_shared<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), ++mGenerationCounter)));
mPlayerTile.reset();
Log(Debug::Debug) << "cache add for agent=" << agentBounds;
}
bool NavMeshManager::reset(const AgentBounds& agentBounds)
{
const auto it = mCache.find(agentBounds);
if (it == mCache.end())
return true;
if (!resetIfUnique(it->second))
return false;
mCache.erase(agentBounds);
mPlayerTile.reset();
return true;
}
void NavMeshManager::addOffMeshConnection(
const ObjectId id, const osg::Vec3f& start, const osg::Vec3f& end, const AreaType areaType)
{
mOffMeshConnectionsManager.add(id, OffMeshConnection{ start, end, areaType });
const auto startTilePosition = getTilePosition(mSettings.mRecast, start);
const auto endTilePosition = getTilePosition(mSettings.mRecast, end);
mRecastMeshManager.addChangedTile(startTilePosition, ChangeType::add);
if (startTilePosition != endTilePosition)
mRecastMeshManager.addChangedTile(endTilePosition, ChangeType::add);
}
void NavMeshManager::removeOffMeshConnections(const ObjectId id)
{
const auto changedTiles = mOffMeshConnectionsManager.remove(id);
for (const auto& tile : changedTiles)
mRecastMeshManager.addChangedTile(tile, ChangeType::update);
}
void NavMeshManager::update(const osg::Vec3f& playerPosition, const UpdateGuard* guard)
{
const TilePosition playerTile = toNavMeshTilePosition(mSettings.mRecast, playerPosition);
if (mLastRecastMeshManagerRevision == mRecastMeshManager.getRevision() && mPlayerTile.has_value()
&& *mPlayerTile == playerTile)
return;
mLastRecastMeshManagerRevision = mRecastMeshManager.getRevision();
mPlayerTile = playerTile;
const auto changedTiles = mRecastMeshManager.takeChangedTiles(getImpl(guard));
const TilesPositionsRange range = mRecastMeshManager.getLimitedObjectsRange();
for (const auto& [agentBounds, cached] : mCache)
update(agentBounds, playerTile, range, cached, changedTiles);
}
void NavMeshManager::update(const AgentBounds& agentBounds, const TilePosition& playerTile,
const TilesPositionsRange& range, const SharedNavMeshCacheItem& cached,
const std::map<osg::Vec2i, ChangeType>& changedTiles)
{
std::map<osg::Vec2i, ChangeType> tilesToPost = changedTiles;
std::map<osg::Vec2i, ChangeType> tilesToPost1;
{
const auto locked = cached->lockConst();
const auto& navMesh = locked->getImpl();
const auto maxTiles = std::min(mSettings.mMaxTilesNumber, navMesh.getParams()->maxTiles);
getTilesPositions(range, [&](const TilePosition& tile) {
if (changedTiles.find(tile) != changedTiles.end())
return;
const bool shouldAdd = shouldAddTile(tile, playerTile, maxTiles);
const bool presentInNavMesh = navMesh.getTileAt(tile.x(), tile.y(), 0) != nullptr;
if (shouldAdd && !presentInNavMesh)
tilesToPost.emplace(tile, locked->isEmptyTile(tile) ? ChangeType::update : ChangeType::add);
else if (!shouldAdd && presentInNavMesh)
tilesToPost.emplace(tile, ChangeType::mixed);
});
}
mAsyncNavMeshUpdater.post(agentBounds, cached, playerTile, mWorldspace, tilesToPost);
Log(Debug::Debug) << "Cache update posted for agent=" << agentBounds << " playerTile=" << playerTile
<< " recastMeshManagerRevision=" << mLastRecastMeshManagerRevision;
}
void NavMeshManager::wait(WaitConditionType waitConditionType, Loading::Listener* listener)
{
mAsyncNavMeshUpdater.wait(waitConditionType, listener);
}
SharedNavMeshCacheItem NavMeshManager::getNavMesh(const AgentBounds& agentBounds) const
{
return getCached(agentBounds);
}
std::map<AgentBounds, SharedNavMeshCacheItem> NavMeshManager::getNavMeshes() const
{
return mCache;
}
Stats NavMeshManager::getStats() const
{
return Stats{ .mUpdater = mAsyncNavMeshUpdater.getStats() };
}
RecastMeshTiles NavMeshManager::getRecastMeshTiles() const
{
RecastMeshTiles result;
getTilesPositions(mRecastMeshManager.getLimitedObjectsRange(), [&](const TilePosition& v) {
if (auto mesh = mRecastMeshManager.getCachedMesh(mWorldspace, v))
result.emplace(v, std::move(mesh));
});
return result;
}
SharedNavMeshCacheItem NavMeshManager::getCached(const AgentBounds& agentBounds) const
{
const auto cached = mCache.find(agentBounds);
if (cached != mCache.end())
return cached->second;
return SharedNavMeshCacheItem();
}
}