diff --git a/apps/navmeshtool/main.cpp b/apps/navmeshtool/main.cpp index 6cfa7fc61d..ec40ada33c 100644 --- a/apps/navmeshtool/main.cpp +++ b/apps/navmeshtool/main.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -39,9 +40,9 @@ #include #include #include +#include #include #include -#include #include #include @@ -119,6 +120,10 @@ namespace NavMeshTool addOption("write-binary-log", bpo::value()->implicit_value(true)->default_value(false), "write progress in binary messages to be consumed by the launcher"); + addOption("worldspace-filter", bpo::value()->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(); const bool writeBinaryLog = variables["write-binary-log"].as(); + const std::regex worldspaceFilter(variables["worldspace-filter"].as()); + #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> 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) { diff --git a/apps/navmeshtool/navmesh.cpp b/apps/navmeshtool/navmesh.cpp index 6a4a708ef9..6f94092344 100644 --- a/apps/navmeshtool/navmesh.cpp +++ b/apps/navmeshtool/navmesh.cpp @@ -26,7 +26,6 @@ #include #include #include -#include #include 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(std::move(db), removeUnusedTiles, writeBinaryLog); - std::size_t tiles = 0; - std::mt19937_64 random; + auto navMeshTileConsumer = std::make_shared(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 worldspaceTiles = data.mTiles; - for (const std::unique_ptr& 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 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(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, + }; } } diff --git a/apps/navmeshtool/navmesh.hpp b/apps/navmeshtool/navmesh.hpp index 4e607f4f2b..2a0eed2fb0 100644 --- a/apps/navmeshtool/navmesh.hpp +++ b/apps/navmeshtool/navmesh.hpp @@ -1,8 +1,6 @@ #ifndef OPENMW_NAVMESHTOOL_NAVMESH_H #define OPENMW_NAVMESHTOOL_NAVMESH_H -#include - 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 diff --git a/apps/navmeshtool/worldspacedata.cpp b/apps/navmeshtool/worldspacedata.cpp index df5c48e7ab..dc8ce751cc 100644 --- a/apps/navmeshtool/worldspacedata.cpp +++ b/apps/navmeshtool/worldspacedata.cpp @@ -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& changedTiles, + std::span cellRefs) + { + if (changedTiles.empty()) + return; + + std::deque queue; + std::map 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 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(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> 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> navMeshInputs; - WorldspaceData data; - - std::size_t objectsCounter = 0; - - if (writeBinaryLog) - serializeToStderr(ExpectedCells{ static_cast(esmData.mCells.size()) }); + std::unordered_map> 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(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 cells) + { + Log(Debug::Info) << "Processing " << cells.size() << " cells from worldspace " << worldspace << "..."; + + if (writeBinaryLog) + serializeToStderr(ExpectedCells{ static_cast(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 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(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() : *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::max(), cell.mWater, guard.get()); + manager.addWater(cellPosition, std::numeric_limits::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(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 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; } diff --git a/apps/navmeshtool/worldspacedata.hpp b/apps/navmeshtool/worldspacedata.hpp index 7096cf95ed..3184089c28 100644 --- a/apps/navmeshtool/worldspacedata.hpp +++ b/apps/navmeshtool/worldspacedata.hpp @@ -12,7 +12,8 @@ #include #include -#include +#include +#include #include 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> mNavMeshInputs; + ESM::RefId mWorldspace; + std::unique_ptr mTileCachedRecastMeshManager; + btAABB mAabb; + bool mAabbInitialized = false; + std::vector mTiles; std::vector mObjects; std::vector> mLandData; std::vector> mHeightfields; + + WorldspaceData(ESM::RefId worldspace, const DetourNavigator::RecastSettings& settings); }; + std::unordered_map> 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 cells); } #endif diff --git a/components/navmeshtool/protocol.hpp b/components/navmeshtool/protocol.hpp index 3ee8baa949..47cfc4d77c 100644 --- a/components/navmeshtool/protocol.hpp +++ b/components/navmeshtool/protocol.hpp @@ -3,7 +3,6 @@ #include #include -#include #include #include