1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2026-01-03 13:43:06 +00:00

Merge branch 'navmeshtool_worldspaces' into 'master'

Somewhat reduce navmeshtool peak memory usage

See merge request OpenMW/openmw!5076
This commit is contained in:
Alexei Kotov 2025-12-31 00:37:51 +03:00
commit 9efef817dc
6 changed files with 304 additions and 146 deletions

View file

@ -24,6 +24,7 @@
#include <components/resource/imagemanager.hpp>
#include <components/resource/niffilemanager.hpp>
#include <components/resource/scenemanager.hpp>
#include <components/sceneutil/workqueue.hpp>
#include <components/settings/values.hpp>
#include <components/toutf8/toutf8.hpp>
#include <components/version/version.hpp>
@ -39,9 +40,9 @@
#include <filesystem>
#include <iostream>
#include <map>
#include <regex>
#include <string>
#include <thread>
#include <type_traits>
#include <utility>
#include <vector>
@ -119,6 +120,10 @@ namespace NavMeshTool
addOption("write-binary-log", bpo::value<bool>()->implicit_value(true)->default_value(false),
"write progress in binary messages to be consumed by the launcher");
addOption("worldspace-filter", bpo::value<std::string>()->default_value(".*"),
"Regular expression to filter in specified worldspaces in modified ECMAScript grammar (see "
"https://en.cppreference.com/w/cpp/regex/ecmascript.html)");
Files::ConfigurationManager::addCommonOptions(result);
return result;
@ -180,6 +185,8 @@ namespace NavMeshTool
const bool removeUnusedTiles = variables["remove-unused-tiles"].as<bool>();
const bool writeBinaryLog = variables["write-binary-log"].as<bool>();
const std::regex worldspaceFilter(variables["worldspace-filter"].as<std::string>());
#ifdef WIN32
if (writeBinaryLog)
_setmode(_fileno(stderr), _O_BINARY);
@ -229,11 +236,42 @@ namespace NavMeshTool
navigatorSettings.mRecast.mSwimHeightScale
= EsmLoader::getGameSetting(esmData.mGameSettings, "fSwimHeightScale").getFloat();
WorldspaceData cellsData = gatherWorldspaceData(
navigatorSettings, readers, vfs, bulletShapeManager, esmData, processInteriorCells, writeBinaryLog);
const std::unordered_map<ESM::RefId, std::vector<std::size_t>> worldspaceCells
= collectWorldspaceCells(esmData, processInteriorCells, worldspaceFilter);
const Status status = generateAllNavMeshTiles(agentBounds, navigatorSettings, threadsNumber,
removeUnusedTiles, writeBinaryLog, cellsData, std::move(db));
Status status = Status::Ok;
bool needVacuum = false;
std::size_t count = 0;
SceneUtil::WorkQueue workQueue(threadsNumber);
Log(Debug::Info) << "Using " << threadsNumber << " parallel workers...";
for (const auto& [worldspace, cells] : worldspaceCells)
{
const WorldspaceData worldspaceData = gatherWorldspaceData(
navigatorSettings, readers, vfs, bulletShapeManager, esmData, writeBinaryLog, worldspace, cells);
const Result result = generateAllNavMeshTiles(
agentBounds, navigatorSettings, removeUnusedTiles, writeBinaryLog, worldspaceData, db, workQueue);
++count;
Log(Debug::Info) << "Processed worldspace (" << count << "/" << worldspaceCells.size() << ") "
<< worldspace;
status = result.mStatus;
needVacuum = needVacuum || result.mNeedVacuum;
if (status != Status::Ok)
break;
}
if (status == Status::Ok && needVacuum)
{
Log(Debug::Info) << "Vacuuming the database...";
db.vacuum();
}
switch (status)
{

View file

@ -26,7 +26,6 @@
#include <cstddef>
#include <random>
#include <string_view>
#include <utility>
#include <vector>
namespace NavMeshTool
@ -78,8 +77,8 @@ namespace NavMeshTool
public:
std::atomic_size_t mExpected{ 0 };
explicit NavMeshTileConsumer(NavMeshDb&& db, bool removeUnusedTiles, bool writeBinaryLog)
: mDb(std::move(db))
explicit NavMeshTileConsumer(NavMeshDb& db, bool removeUnusedTiles, bool writeBinaryLog)
: mDb(db)
, mRemoveUnusedTiles(removeUnusedTiles)
, mWriteBinaryLog(writeBinaryLog)
, mTransaction(mDb.startTransaction(Sqlite3::TransactionMode::Immediate))
@ -211,12 +210,6 @@ namespace NavMeshTool
mTransaction.commit();
}
void vacuum()
{
const std::lock_guard lock(mMutex);
mDb.vacuum();
}
void removeTilesOutsideRange(ESM::RefId worldspace, const TilesPositionsRange& range)
{
const std::lock_guard lock(mMutex);
@ -233,7 +226,7 @@ namespace NavMeshTool
std::size_t mDeleted = 0;
Status mStatus = Status::Ok;
mutable std::mutex mMutex;
NavMeshDb mDb;
NavMeshDb& mDb;
const bool mRemoveUnusedTiles;
const bool mWriteBinaryLog;
Transaction mTransaction;
@ -254,45 +247,39 @@ namespace NavMeshTool
};
}
Status generateAllNavMeshTiles(const AgentBounds& agentBounds, const Settings& settings, std::size_t threadsNumber,
bool removeUnusedTiles, bool writeBinaryLog, WorldspaceData& data, NavMeshDb&& db)
Result generateAllNavMeshTiles(const AgentBounds& agentBounds, const Settings& settings, bool removeUnusedTiles,
bool writeBinaryLog, const WorldspaceData& data, NavMeshDb& db, SceneUtil::WorkQueue& workQueue)
{
Log(Debug::Info) << "Generating navmesh tiles by " << threadsNumber << " parallel workers...";
Log(Debug::Info) << "Generating navmesh tiles for " << data.mWorldspace << " worldspace...";
SceneUtil::WorkQueue workQueue(threadsNumber);
auto navMeshTileConsumer
= std::make_shared<NavMeshTileConsumer>(std::move(db), removeUnusedTiles, writeBinaryLog);
std::size_t tiles = 0;
std::mt19937_64 random;
auto navMeshTileConsumer = std::make_shared<NavMeshTileConsumer>(db, removeUnusedTiles, writeBinaryLog);
const auto range = DetourNavigator::makeTilesPositionsRange(
Misc::Convert::toOsgXY(data.mAabb.m_min), Misc::Convert::toOsgXY(data.mAabb.m_max), settings.mRecast);
if (removeUnusedTiles)
navMeshTileConsumer->removeTilesOutsideRange(data.mWorldspace, range);
std::vector<TilePosition> worldspaceTiles = data.mTiles;
for (const std::unique_ptr<WorldspaceNavMeshInput>& input : data.mNavMeshInputs)
{
const auto range = DetourNavigator::makeTilesPositionsRange(Misc::Convert::toOsgXY(input->mAabb.m_min),
Misc::Convert::toOsgXY(input->mAabb.m_max), settings.mRecast);
if (removeUnusedTiles)
navMeshTileConsumer->removeTilesOutsideRange(input->mWorldspace, range);
std::vector<TilePosition> worldspaceTiles;
DetourNavigator::getTilesPositions(
range, [&](const TilePosition& tilePosition) { worldspaceTiles.push_back(tilePosition); });
tiles += worldspaceTiles.size();
const std::size_t tiles = worldspaceTiles.size();
if (writeBinaryLog)
serializeToStderr(ExpectedTiles{ static_cast<std::uint64_t>(tiles) });
navMeshTileConsumer->mExpected = tiles;
std::shuffle(worldspaceTiles.begin(), worldspaceTiles.end(), random);
for (const TilePosition& tilePosition : worldspaceTiles)
workQueue.addWorkItem(new GenerateNavMeshTile(input->mWorldspace, tilePosition,
RecastMeshProvider(input->mTileCachedRecastMeshManager), agentBounds, settings,
navMeshTileConsumer));
}
{
std::mt19937_64 random;
std::shuffle(worldspaceTiles.begin(), worldspaceTiles.end(), random);
}
for (const TilePosition& tilePosition : worldspaceTiles)
workQueue.addWorkItem(new GenerateNavMeshTile(data.mWorldspace, tilePosition,
RecastMeshProvider(*data.mTileCachedRecastMeshManager), agentBounds, settings, navMeshTileConsumer));
const Status status = navMeshTileConsumer->wait();
if (status == Status::Ok)
navMeshTileConsumer->commit();
@ -304,12 +291,9 @@ namespace NavMeshTool
Log(Debug::Info) << "Generated navmesh for " << navMeshTileConsumer->getProvided() << " tiles, " << inserted
<< " are inserted, " << updated << " updated and " << deleted << " deleted";
if (inserted + updated + deleted > 0)
{
Log(Debug::Info) << "Vacuuming the database...";
navMeshTileConsumer->vacuum();
}
return status;
return Result{
.mStatus = status,
.mNeedVacuum = inserted + updated + deleted > 0,
};
}
}

View file

@ -1,8 +1,6 @@
#ifndef OPENMW_NAVMESHTOOL_NAVMESH_H
#define OPENMW_NAVMESHTOOL_NAVMESH_H
#include <cstddef>
namespace DetourNavigator
{
class NavMeshDb;
@ -10,6 +8,11 @@ namespace DetourNavigator
struct AgentBounds;
}
namespace SceneUtil
{
class WorkQueue;
}
namespace NavMeshTool
{
struct WorldspaceData;
@ -21,9 +24,15 @@ namespace NavMeshTool
NotEnoughSpace,
};
Status generateAllNavMeshTiles(const DetourNavigator::AgentBounds& agentBounds,
const DetourNavigator::Settings& settings, std::size_t threadsNumber, bool removeUnusedTiles,
bool writeBinaryLog, WorldspaceData& cellsData, DetourNavigator::NavMeshDb&& db);
struct Result
{
Status mStatus;
bool mNeedVacuum;
};
Result generateAllNavMeshTiles(const DetourNavigator::AgentBounds& agentBounds,
const DetourNavigator::Settings& settings, bool removeUnusedTiles, bool writeBinaryLog,
const WorldspaceData& data, DetourNavigator::NavMeshDb& db, SceneUtil::WorkQueue& workQueue);
}
#endif

View file

@ -51,6 +51,7 @@ namespace NavMeshTool
using DetourNavigator::HeightfieldSurface;
using DetourNavigator::ObjectId;
using DetourNavigator::ObjectTransform;
using DetourNavigator::TilesPositionsRange;
struct CellRef
{
@ -71,6 +72,25 @@ namespace NavMeshTool
}
};
struct AddedCellRef
{
std::string mCell;
CellRef mCellRef;
TilesPositionsRange mRange;
};
struct WriteArray
{
const float (&mValue)[3];
friend inline std::ostream& operator<<(std::ostream& stream, const WriteArray& value)
{
for (std::size_t i = 0; i < 2; ++i)
stream << value.mValue[i] << ", ";
return stream << value.mValue[2];
}
};
ESM::RecNameInts getType(const EsmLoader::EsmData& esmData, const ESM::RefId& refId)
{
const auto it = std::lower_bound(
@ -119,7 +139,7 @@ namespace NavMeshTool
Log(Debug::Debug) << "Prepared " << cellRefs.size() << " unique cell refs";
for (CellRef& cellRef : cellRefs)
for (const CellRef& cellRef : cellRefs)
{
VFS::Path::Normalized model(getModel(esmData, cellRef.mRefId, cellRef.mType));
if (model.empty())
@ -153,7 +173,7 @@ namespace NavMeshTool
case ESM::REC_CONT:
case ESM::REC_DOOR:
case ESM::REC_STAT:
f(BulletObject(std::move(shapeInstance), cellRef.mPos, cellRef.mScale));
f(BulletObject(std::move(shapeInstance), cellRef.mPos, cellRef.mScale), cellRef);
break;
default:
break;
@ -232,30 +252,104 @@ namespace NavMeshTool
<< " fileHash=" << Misc::StringUtils::toHex(shape.getInstance()->mFileHash);
return stream.str();
}
void detectDisconnectedTileGroups(ESM::RefId worldspace,
const std::map<osg::Vec2i, DetourNavigator::ChangeType>& changedTiles,
std::span<const AddedCellRef> cellRefs)
{
if (changedTiles.empty())
return;
std::deque<osg::Vec2i> queue;
std::map<osg::Vec2i, std::size_t> positionToComponent;
std::size_t componentIndex = 0;
for (const auto& [initial, changeType] : changedTiles)
{
if (positionToComponent.contains(initial))
continue;
queue.push_back(initial);
positionToComponent.emplace(initial, componentIndex);
while (!queue.empty())
{
const osg::Vec2i position = queue.front();
queue.pop_front();
for (int x = position.x() - 1; x <= position.x() + 1; ++x)
{
for (int y = position.y() - 1; y <= position.y() + 1; ++y)
{
const osg::Vec2i candidate(x, y);
if (candidate == position)
continue;
if (!changedTiles.contains(candidate))
continue;
const auto it = positionToComponent.find(candidate);
if (it != positionToComponent.end())
continue;
queue.push_back(candidate);
positionToComponent.emplace_hint(it, candidate, componentIndex);
}
}
}
++componentIndex;
}
if (componentIndex <= 1)
return;
Log(Debug::Warning) << "Found " << componentIndex << " disconnected tile groups";
std::vector<std::size_t> cellRefsPerComponent(componentIndex);
for (const AddedCellRef& v : cellRefs)
++cellRefsPerComponent[positionToComponent.at(v.mRange.mBegin)];
const std::size_t largestComponent
= std::max_element(cellRefsPerComponent.begin(), cellRefsPerComponent.end())
- cellRefsPerComponent.begin();
for (const AddedCellRef& v : cellRefs)
{
const std::size_t component = positionToComponent.at(v.mRange.mBegin);
if (component == largestComponent)
continue;
Log(Debug::Warning) << "CellRef belongs to not largest disconnected tile group:"
<< " worldspace=" << worldspace << " cell=\"" << v.mCell << "\""
<< " ref_num=" << v.mCellRef.mRefNum << " ref_id=" << v.mCellRef.mRefId
<< " scale=" << v.mCellRef.mScale << " pos=" << WriteArray{ v.mCellRef.mPos.pos }
<< " rot=" << WriteArray{ v.mCellRef.mPos.rot }
<< " begin_tile=" << v.mRange.mBegin.x() << ", " << v.mRange.mBegin.y()
<< " end_tile=" << v.mRange.mEnd.x() << ", " << v.mRange.mEnd.y()
<< " component=" << component;
}
}
}
WorldspaceNavMeshInput::WorldspaceNavMeshInput(
ESM::RefId worldspace, const DetourNavigator::RecastSettings& settings)
WorldspaceData::WorldspaceData(ESM::RefId worldspace, const DetourNavigator::RecastSettings& settings)
: mWorldspace(worldspace)
, mTileCachedRecastMeshManager(settings)
, mTileCachedRecastMeshManager(std::make_unique<TileCachedRecastMeshManager>(settings))
{
mAabb.m_min = btVector3(0, 0, 0);
mAabb.m_max = btVector3(0, 0, 0);
}
WorldspaceData gatherWorldspaceData(const DetourNavigator::Settings& settings, ESM::ReadersCache& readers,
const VFS::Manager& vfs, Resource::BulletShapeManager& bulletShapeManager, const EsmLoader::EsmData& esmData,
bool processInteriorCells, bool writeBinaryLog)
std::unordered_map<ESM::RefId, std::vector<std::size_t>> collectWorldspaceCells(
const EsmLoader::EsmData& esmData, bool processInteriorCells, const std::regex& worldspaceFilter)
{
Log(Debug::Info) << "Processing " << esmData.mCells.size() << " cells...";
Log(Debug::Info) << "Collecting worldspaces from " << esmData.mCells.size() << " cells...";
std::unordered_map<ESM::RefId, std::unique_ptr<WorldspaceNavMeshInput>> navMeshInputs;
WorldspaceData data;
std::size_t objectsCounter = 0;
if (writeBinaryLog)
serializeToStderr(ExpectedCells{ static_cast<std::uint64_t>(esmData.mCells.size()) });
std::unordered_map<ESM::RefId, std::vector<std::size_t>> result;
for (std::size_t i = 0; i < esmData.mCells.size(); ++i)
{
@ -264,34 +358,62 @@ namespace NavMeshTool
if (!exterior && !processInteriorCells)
{
if (writeBinaryLog)
serializeToStderr(ProcessedCells{ static_cast<std::uint64_t>(i + 1) });
Log(Debug::Info) << "Skipped interior"
<< " cell (" << (i + 1) << "/" << esmData.mCells.size() << ") \""
<< cell.getDescription() << "\"";
Log(Debug::Verbose) << "Skipped interior"
<< " cell (" << (i + 1) << "/" << esmData.mCells.size() << ") \""
<< cell.getDescription() << "\"";
continue;
}
const ESM::RefId cellWorldspace = cell.isExterior() ? ESM::Cell::sDefaultWorldspaceId : cell.mId;
if (!std::regex_match(cellWorldspace.toString(), worldspaceFilter))
{
Log(Debug::Verbose) << "Skipped filtered out"
<< " cell (" << (i + 1) << "/" << esmData.mCells.size() << ") \""
<< cell.getDescription() << "\" from " << cellWorldspace << " worldspace";
continue;
}
result[cellWorldspace].push_back(i);
Log(Debug::Info) << "Collected " << (exterior ? "exterior" : "interior") << " cell (" << (i + 1) << "/"
<< esmData.mCells.size() << ") " << cell.getDescription();
}
Log(Debug::Info) << "Collected " << result.size() << " worldspaces";
return result;
}
WorldspaceData gatherWorldspaceData(const DetourNavigator::Settings& settings, ESM::ReadersCache& readers,
const VFS::Manager& vfs, Resource::BulletShapeManager& bulletShapeManager, const EsmLoader::EsmData& esmData,
bool writeBinaryLog, ESM::RefId worldspace, std::span<const std::size_t> cells)
{
Log(Debug::Info) << "Processing " << cells.size() << " cells from worldspace " << worldspace << "...";
if (writeBinaryLog)
serializeToStderr(ExpectedCells{ static_cast<std::uint64_t>(cells.size()) });
WorldspaceData data(worldspace, settings.mRecast);
TileCachedRecastMeshManager& manager = *data.mTileCachedRecastMeshManager;
const auto guard = manager.makeUpdateGuard();
manager.setWorldspace(worldspace, guard.get());
std::size_t objectsCounter = 0;
std::vector<AddedCellRef> addedCellRefs;
for (std::size_t i = 0; i < cells.size(); ++i)
{
const ESM::Cell& cell = esmData.mCells[cells[i]];
const bool exterior = cell.isExterior();
Log(Debug::Debug) << "Processing " << (exterior ? "exterior" : "interior") << " cell (" << (i + 1) << "/"
<< esmData.mCells.size() << ") \"" << cell.getDescription() << "\"";
<< cells.size() << ") \"" << cell.getDescription() << "\"";
const osg::Vec2i cellPosition(cell.mData.mX, cell.mData.mY);
const std::size_t cellObjectsBegin = data.mObjects.size();
const ESM::RefId cellWorldspace = cell.isExterior() ? ESM::Cell::sDefaultWorldspaceId : cell.mId;
WorldspaceNavMeshInput& navMeshInput = [&]() -> WorldspaceNavMeshInput& {
auto it = navMeshInputs.find(cellWorldspace);
if (it == navMeshInputs.end())
{
it = navMeshInputs
.emplace(cellWorldspace,
std::make_unique<WorldspaceNavMeshInput>(cellWorldspace, settings.mRecast))
.first;
it->second->mTileCachedRecastMeshManager.setWorldspace(cellWorldspace, nullptr);
}
return *it->second;
}();
const auto guard = navMeshInput.mTileCachedRecastMeshManager.makeUpdateGuard();
if (exterior)
{
@ -301,69 +423,75 @@ namespace NavMeshTool
= makeHeightfieldShape(it == esmData.mLands.end() ? std::optional<ESM::Land>() : *it, cellPosition,
data.mHeightfields, data.mLandData);
mergeOrAssign(
getAabb(cellPosition, minHeight, maxHeight), navMeshInput.mAabb, navMeshInput.mAabbInitialized);
mergeOrAssign(getAabb(cellPosition, minHeight, maxHeight), data.mAabb, data.mAabbInitialized);
navMeshInput.mTileCachedRecastMeshManager.addHeightfield(
cellPosition, ESM::Land::REAL_SIZE, heightfieldShape, guard.get());
manager.addHeightfield(cellPosition, ESM::Land::REAL_SIZE, heightfieldShape, guard.get());
navMeshInput.mTileCachedRecastMeshManager.addWater(cellPosition, ESM::Land::REAL_SIZE, -1, guard.get());
manager.addWater(cellPosition, ESM::Land::REAL_SIZE, -1, guard.get());
}
else
{
if ((cell.mData.mFlags & ESM::Cell::HasWater) != 0)
navMeshInput.mTileCachedRecastMeshManager.addWater(
cellPosition, std::numeric_limits<int>::max(), cell.mWater, guard.get());
manager.addWater(cellPosition, std::numeric_limits<int>::max(), cell.mWater, guard.get());
}
forEachObject(cell, esmData, vfs, bulletShapeManager, readers, [&](BulletObject object) {
if (object.getShapeInstance()->mVisualCollisionType != Resource::VisualCollisionType::None)
return;
forEachObject(
cell, esmData, vfs, bulletShapeManager, readers, [&](BulletObject object, const CellRef& cellRef) {
if (object.getShapeInstance()->mVisualCollisionType != Resource::VisualCollisionType::None)
return;
const btTransform& transform = object.getCollisionObject().getWorldTransform();
const btAABB aabb = BulletHelpers::getAabb(*object.getCollisionObject().getCollisionShape(), transform);
mergeOrAssign(aabb, navMeshInput.mAabb, navMeshInput.mAabbInitialized);
if (const btCollisionShape* avoid = object.getShapeInstance()->mAvoidCollisionShape.get())
navMeshInput.mAabb.merge(BulletHelpers::getAabb(*avoid, transform));
const btTransform& transform = object.getCollisionObject().getWorldTransform();
const btAABB aabb
= BulletHelpers::getAabb(*object.getCollisionObject().getCollisionShape(), transform);
mergeOrAssign(aabb, data.mAabb, data.mAabbInitialized);
if (const btCollisionShape* avoid = object.getShapeInstance()->mAvoidCollisionShape.get())
data.mAabb.merge(BulletHelpers::getAabb(*avoid, transform));
const ObjectId objectId(++objectsCounter);
const CollisionShape shape(object.getShapeInstance(), *object.getCollisionObject().getCollisionShape(),
object.getObjectTransform());
const ObjectId objectId(++objectsCounter);
const CollisionShape shape(object.getShapeInstance(),
*object.getCollisionObject().getCollisionShape(), object.getObjectTransform());
if (!navMeshInput.mTileCachedRecastMeshManager.addObject(
objectId, shape, transform, DetourNavigator::AreaType_ground, guard.get()))
throw std::logic_error(
makeAddObjectErrorMessage(objectId, DetourNavigator::AreaType_ground, shape));
if (const btCollisionShape* avoid = object.getShapeInstance()->mAvoidCollisionShape.get())
{
const ObjectId avoidObjectId(++objectsCounter);
const CollisionShape avoidShape(object.getShapeInstance(), *avoid, object.getObjectTransform());
if (!navMeshInput.mTileCachedRecastMeshManager.addObject(
avoidObjectId, avoidShape, transform, DetourNavigator::AreaType_null, guard.get()))
if (!manager.addObject(objectId, shape, transform, DetourNavigator::AreaType_ground, guard.get()))
throw std::logic_error(
makeAddObjectErrorMessage(avoidObjectId, DetourNavigator::AreaType_null, avoidShape));
}
makeAddObjectErrorMessage(objectId, DetourNavigator::AreaType_ground, shape));
data.mObjects.emplace_back(std::move(object));
});
addedCellRefs.push_back(AddedCellRef{
.mCell = cell.getDescription(),
.mCellRef = cellRef,
.mRange = makeTilesPositionsRange(shape.getShape(), transform, settings.mRecast),
});
const auto cellDescription = cell.getDescription();
if (const btCollisionShape* avoid = object.getShapeInstance()->mAvoidCollisionShape.get())
{
const ObjectId avoidObjectId(++objectsCounter);
const CollisionShape avoidShape(object.getShapeInstance(), *avoid, object.getObjectTransform());
if (!manager.addObject(
avoidObjectId, avoidShape, transform, DetourNavigator::AreaType_null, guard.get()))
throw std::logic_error(
makeAddObjectErrorMessage(avoidObjectId, DetourNavigator::AreaType_null, avoidShape));
}
data.mObjects.emplace_back(std::move(object));
});
if (writeBinaryLog)
serializeToStderr(ProcessedCells{ static_cast<std::uint64_t>(i + 1) });
Log(Debug::Info) << "Processed " << (exterior ? "exterior" : "interior") << " cell (" << (i + 1) << "/"
<< esmData.mCells.size() << ") " << cellDescription << " with "
<< cells.size() << ") " << cell.getDescription() << " with "
<< (data.mObjects.size() - cellObjectsBegin) << " objects";
}
data.mNavMeshInputs.reserve(navMeshInputs.size());
std::transform(navMeshInputs.begin(), navMeshInputs.end(), std::back_inserter(data.mNavMeshInputs),
[](auto& v) { return std::move(v.second); });
const std::map<osg::Vec2i, DetourNavigator::ChangeType> changedTiles = manager.takeChangedTiles(guard.get());
Log(Debug::Info) << "Processed " << esmData.mCells.size() << " cells, added " << data.mObjects.size()
<< " objects and " << data.mHeightfields.size() << " height fields";
if (worldspace != ESM::Cell::sDefaultWorldspaceId)
detectDisconnectedTileGroups(worldspace, changedTiles, addedCellRefs);
data.mTiles.reserve(changedTiles.size());
std::ranges::transform(changedTiles, std::back_inserter(data.mTiles), [](const auto& v) { return v.first; });
Log(Debug::Info) << "Processed " << cells.size() << " cells, added " << data.mObjects.size() << " objects and "
<< data.mHeightfields.size() << " height fields";
return data;
}

View file

@ -12,7 +12,8 @@
#include <LinearMath/btVector3.h>
#include <memory>
#include <string>
#include <regex>
#include <span>
#include <vector>
namespace ESM
@ -46,16 +47,6 @@ namespace NavMeshTool
using DetourNavigator::ObjectTransform;
using DetourNavigator::TileCachedRecastMeshManager;
struct WorldspaceNavMeshInput
{
ESM::RefId mWorldspace;
TileCachedRecastMeshManager mTileCachedRecastMeshManager;
btAABB mAabb;
bool mAabbInitialized = false;
explicit WorldspaceNavMeshInput(ESM::RefId worldspace, const DetourNavigator::RecastSettings& settings);
};
class BulletObject
{
public:
@ -82,15 +73,24 @@ namespace NavMeshTool
struct WorldspaceData
{
std::vector<std::unique_ptr<WorldspaceNavMeshInput>> mNavMeshInputs;
ESM::RefId mWorldspace;
std::unique_ptr<TileCachedRecastMeshManager> mTileCachedRecastMeshManager;
btAABB mAabb;
bool mAabbInitialized = false;
std::vector<DetourNavigator::TilePosition> mTiles;
std::vector<BulletObject> mObjects;
std::vector<std::unique_ptr<ESM::Land::LandData>> mLandData;
std::vector<std::vector<float>> mHeightfields;
WorldspaceData(ESM::RefId worldspace, const DetourNavigator::RecastSettings& settings);
};
std::unordered_map<ESM::RefId, std::vector<std::size_t>> collectWorldspaceCells(
const EsmLoader::EsmData& esmData, bool processInteriorCells, const std::regex& worldspaceFilter);
WorldspaceData gatherWorldspaceData(const DetourNavigator::Settings& settings, ESM::ReadersCache& readers,
const VFS::Manager& vfs, Resource::BulletShapeManager& bulletShapeManager, const EsmLoader::EsmData& esmData,
bool processInteriorCells, bool writeBinaryLog);
bool writeBinaryLog, ESM::RefId worldspace, std::span<const std::size_t> cells);
}
#endif

View file

@ -3,7 +3,6 @@
#include <cstddef>
#include <cstdint>
#include <stdexcept>
#include <variant>
#include <vector>