diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 538bfa11ec..b977d86a97 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -219,7 +219,7 @@ macOS11_Xcode12: CCACHE_SIZE: 3G variables: &engine-targets - targets: "openmw,openmw-iniimporter,openmw-launcher,openmw-wizard" + targets: "openmw,openmw-iniimporter,openmw-launcher,openmw-wizard,openmw-navmeshtool" package: "Engine" variables: &cs-targets diff --git a/CHANGELOG.md b/CHANGELOG.md index 984b545ef1..9bc340bef0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -108,6 +108,7 @@ Feature #6128: Soft Particles Feature #6161: Refactor Sky to use shaders and GLES/GL3 friendly Feature #6162: Refactor GUI to use shaders and to be GLES and GL3+ friendly + Feature #6189: Navigation mesh disk cache Feature #6199: Support FBO Rendering Feature #6248: Embedded error marker mesh Feature #6249: Alpha testing support for Collada diff --git a/CI/before_script.android.sh b/CI/before_script.android.sh index bdf7d4a244..43422f68c1 100755 --- a/CI/before_script.android.sh +++ b/CI/before_script.android.sh @@ -22,6 +22,7 @@ cmake \ -DBUILD_ESSIMPORTER=0 \ -DBUILD_OPENCS=0 \ -DBUILD_WIZARD=0 \ +-DBUILD_NAVMESHTOOL=OFF \ -DOPENMW_USE_SYSTEM_MYGUI=OFF \ -DOPENMW_USE_SYSTEM_SQLITE3=OFF \ .. diff --git a/CI/before_script.linux.sh b/CI/before_script.linux.sh index b9fed204e3..19d8338721 100755 --- a/CI/before_script.linux.sh +++ b/CI/before_script.linux.sh @@ -71,6 +71,7 @@ if [[ "${BUILD_TESTS_ONLY}" ]]; then -DBUILD_ESSIMPORTER=OFF \ -DBUILD_OPENCS=OFF \ -DBUILD_WIZARD=OFF \ + -DBUILD_NAVMESHTOOL=OFF \ -DBUILD_UNITTESTS=${BUILD_UNITTESTS} \ -DBUILD_BENCHMARKS=${BUILD_BENCHMARKS} \ -DGTEST_ROOT="${GOOGLETEST_DIR}" \ diff --git a/CMakeLists.txt b/CMakeLists.txt index 644a7419a8..090e6c56e3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,6 +37,7 @@ option(BUILD_DOCS "Build documentation." OFF ) option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF) option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest" OFF) option(BUILD_BENCHMARKS "Build benchmarks with Google Benchmark" OFF) +option(BUILD_NAVMESHTOOL "Build navmesh tool" ON) set(OpenGL_GL_PREFERENCE LEGACY) # Use LEGACY as we use GL2; GLNVD is for GL3 and up. @@ -603,6 +604,10 @@ if (BUILD_BENCHMARKS) add_subdirectory(apps/benchmarks) endif() +if (BUILD_NAVMESHTOOL) + add_subdirectory(apps/navmeshtool) +endif() + if (WIN32) if (MSVC) if (OPENMW_MP_BUILD) @@ -702,6 +707,10 @@ if (WIN32) if (BUILD_BENCHMARKS) set_target_properties(openmw_detournavigator_navmeshtilescache_benchmark PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") endif() + + if (BUILD_NAVMESHTOOL) + set_target_properties(openmw-navmeshtool PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") + endif() endif(MSVC) # TODO: At some point release builds should not use the console but rather write to a log file @@ -944,6 +953,9 @@ elseif(NOT APPLE) IF(BUILD_WIZARD) INSTALL(PROGRAMS "${INSTALL_SOURCE}/openmw-wizard" DESTINATION "${BINDIR}" ) ENDIF(BUILD_WIZARD) + if(BUILD_NAVMESHTOOL) + install(PROGRAMS "${INSTALL_SOURCE}/openmw-navmeshtool" DESTINATION "${BINDIR}" ) + endif() # Install licenses INSTALL(FILES "files/mygui/DejaVuFontLicense.txt" DESTINATION "${LICDIR}" ) diff --git a/apps/benchmarks/detournavigator/navmeshtilescache.cpp b/apps/benchmarks/detournavigator/navmeshtilescache.cpp index 9d41b62597..c27fdc4289 100644 --- a/apps/benchmarks/detournavigator/navmeshtilescache.cpp +++ b/apps/benchmarks/detournavigator/navmeshtilescache.cpp @@ -145,7 +145,7 @@ namespace std::vector water; generateWater(std::back_inserter(water), 1, random); RecastMesh recastMesh(generation, revision, std::move(mesh), std::move(water), - {generateHeightfield(random)}, {generateFlatHeightfield(random)}); + {generateHeightfield(random)}, {generateFlatHeightfield(random)}, {}); return Key {agentHalfExtents, tilePosition, std::move(recastMesh)}; } diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index eb0950dae0..097dc21dd2 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -1,4 +1,5 @@ #include "datafilespage.hpp" +#include "maindialog.hpp" #include @@ -8,6 +9,7 @@ #include #include #include +#include #include #include @@ -24,11 +26,14 @@ const char *Launcher::DataFilesPage::mDefaultContentListName = "Default"; -Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings, Config::LauncherSettings &launcherSettings, QWidget *parent) +Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings, + Config::LauncherSettings &launcherSettings, MainDialog *parent) : QWidget(parent) + , mMainDialog(parent) , mCfgMgr(cfg) , mGameSettings(gameSettings) , mLauncherSettings(launcherSettings) + , mNavMeshToolInvoker(new Process::ProcessInvoker(this)) { ui.setupUi (this); setObjectName ("DataFilesPage"); @@ -57,8 +62,6 @@ Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config: void Launcher::DataFilesPage::buildView() { - ui.verticalLayout->insertWidget (0, mSelector->uiWidget()); - QToolButton * refreshButton = mSelector->refreshButton(); //tool buttons @@ -89,6 +92,13 @@ void Launcher::DataFilesPage::buildView() this, SLOT (slotProfileChangedByUser(QString, QString))); connect(ui.refreshDataFilesAction, SIGNAL(triggered()),this, SLOT(slotRefreshButtonClicked())); + + connect(ui.updateNavMeshButton, SIGNAL(clicked()), this, SLOT(startNavMeshTool())); + connect(ui.cancelNavMeshButton, SIGNAL(clicked()), this, SLOT(killNavMeshTool())); + + connect(mNavMeshToolInvoker->getProcess(), SIGNAL(readyReadStandardOutput()), this, SLOT(updateNavMeshProgress())); + connect(mNavMeshToolInvoker->getProcess(), SIGNAL(readyReadStandardError()), this, SLOT(updateNavMeshProgress())); + connect(mNavMeshToolInvoker->getProcess(), SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(navMeshToolFinished(int, QProcess::ExitStatus))); } bool Launcher::DataFilesPage::loadSettings() @@ -411,3 +421,55 @@ void Launcher::DataFilesPage::reloadCells(QStringList selectedFiles) std::sort(cellNamesList.begin(), cellNamesList.end()); emit signalLoadedCellsChanged(cellNamesList); } + +void Launcher::DataFilesPage::startNavMeshTool() +{ + mMainDialog->writeSettings(); + + ui.navMeshLogPlainTextEdit->clear(); + ui.navMeshProgressBar->setValue(0); + ui.navMeshProgressBar->setMaximum(1); + + if (!mNavMeshToolInvoker->startProcess(QLatin1String("openmw-navmeshtool"))) + return; + + ui.cancelNavMeshButton->setEnabled(true); + ui.navMeshProgressBar->setEnabled(true); +} + +void Launcher::DataFilesPage::killNavMeshTool() +{ + mNavMeshToolInvoker->killProcess(); +} + +void Launcher::DataFilesPage::updateNavMeshProgress() +{ + QProcess& process = *mNavMeshToolInvoker->getProcess(); + QString text; + while (process.canReadLine()) + { + const QByteArray line = process.readLine(); + const auto end = std::find_if(line.rbegin(), line.rend(), [] (auto v) { return v != '\n' && v != '\r'; }); + text = QString::fromUtf8(line.mid(0, line.size() - (end - line.rbegin()))); + ui.navMeshLogPlainTextEdit->appendPlainText(text); + } + const QRegularExpression pattern(R"([\( ](\d+)/(\d+)[\) ])"); + QRegularExpressionMatch match = pattern.match(text); + if (!match.hasMatch()) + return; + int maximum = match.captured(2).toInt(); + if (text.contains("cell")) + maximum *= 100; + ui.navMeshProgressBar->setMaximum(std::max(ui.navMeshProgressBar->maximum(), maximum)); + ui.navMeshProgressBar->setValue(match.captured(1).toInt()); +} + +void Launcher::DataFilesPage::navMeshToolFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + updateNavMeshProgress(); + ui.navMeshLogPlainTextEdit->appendPlainText(QString::fromUtf8(mNavMeshToolInvoker->getProcess()->readAll())); + if (exitCode == 0 && exitStatus == QProcess::ExitStatus::NormalExit) + ui.navMeshProgressBar->setValue(ui.navMeshProgressBar->maximum()); + ui.cancelNavMeshButton->setEnabled(false); + ui.navMeshProgressBar->setEnabled(false); +} diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index 5a7a6dc6e6..a039237590 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -2,6 +2,9 @@ #define DATAFILESPAGE_H #include "ui_datafilespage.h" + +#include + #include @@ -19,6 +22,7 @@ namespace Config { class GameSettings; namespace Launcher { + class MainDialog; class TextInputDialog; class ProfilesComboBox; @@ -31,7 +35,7 @@ namespace Launcher public: explicit DataFilesPage (Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings, - Config::LauncherSettings &launcherSettings, QWidget *parent = nullptr); + Config::LauncherSettings &launcherSettings, MainDialog *parent = nullptr); QAbstractItemModel* profilesModel() const; @@ -69,12 +73,18 @@ namespace Launcher void on_cloneProfileAction_triggered(); void on_deleteProfileAction_triggered(); + void startNavMeshTool(); + void killNavMeshTool(); + void updateNavMeshProgress(); + void navMeshToolFinished(int exitCode, QProcess::ExitStatus exitStatus); + public: /// Content List that is always present const static char *mDefaultContentListName; private: + MainDialog *mMainDialog; TextInputDialog *mNewProfileDialog; TextInputDialog *mCloneProfileDialog; @@ -87,6 +97,8 @@ namespace Launcher QStringList previousSelectedFiles; QString mDataLocal; + Process::ProcessInvoker* mNavMeshToolInvoker; + void buildView(); void setProfile (int index, bool savePrevious); void setProfile (const QString &previous, const QString ¤t, bool savePrevious); diff --git a/apps/navmeshtool/CMakeLists.txt b/apps/navmeshtool/CMakeLists.txt new file mode 100644 index 0000000000..7df049af97 --- /dev/null +++ b/apps/navmeshtool/CMakeLists.txt @@ -0,0 +1,22 @@ +set(NAVMESHTOOL + worldspacedata.cpp + navmesh.cpp + main.cpp +) +source_group(apps\\navmeshtool FILES ${NAVMESHTOOL}) + +openmw_add_executable(openmw-navmeshtool ${NAVMESHTOOL}) + +target_link_libraries(openmw-navmeshtool + ${Boost_PROGRAM_OPTIONS_LIBRARY} + components +) + +if (BUILD_WITH_CODE_COVERAGE) + add_definitions(--coverage) + target_link_libraries(openmw-navmeshtool gcov) +endif() + +if (WIN32) + install(TARGETS openmw-navmeshtool RUNTIME DESTINATION ".") +endif() diff --git a/apps/navmeshtool/main.cpp b/apps/navmeshtool/main.cpp new file mode 100644 index 0000000000..3e867fcbc9 --- /dev/null +++ b/apps/navmeshtool/main.cpp @@ -0,0 +1,209 @@ +#include "worldspacedata.hpp" +#include "navmesh.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include + +namespace NavMeshTool +{ + namespace + { + namespace bpo = boost::program_options; + + using StringsVector = std::vector; + + bpo::options_description makeOptionsDescription() + { + using Fallback::FallbackMap; + + bpo::options_description result; + + result.add_options() + ("help", "print help message") + + ("version", "print version information and quit") + + ("data", bpo::value()->default_value(Files::MaybeQuotedPathContainer(), "data") + ->multitoken()->composing(), "set data directories (later directories have higher priority)") + + ("data-local", bpo::value()->default_value(Files::MaybeQuotedPathContainer::value_type(), ""), + "set local data directory (highest priority)") + + ("fallback-archive", bpo::value()->default_value(StringsVector(), "fallback-archive") + ->multitoken()->composing(), "set fallback BSA archives (later archives have higher priority)") + + ("resources", bpo::value()->default_value(Files::MaybeQuotedPath(), "resources"), + "set resources directory") + + ("content", bpo::value()->default_value(StringsVector(), "") + ->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon/omwscripts") + + ("fs-strict", bpo::value()->implicit_value(true) + ->default_value(false), "strict file system handling (no case folding)") + + ("encoding", bpo::value()-> + default_value("win1252"), + "Character encoding used in OpenMW game messages:\n" + "\n\twin1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages\n" + "\n\twin1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages\n" + "\n\twin1252 - Western European (Latin) alphabet, used by default") + + ("fallback", bpo::value()->default_value(Fallback::FallbackMap(), "") + ->multitoken()->composing(), "fallback values") + + ("threads", bpo::value()->default_value(std::max(std::thread::hardware_concurrency() - 1, 1)), + "number of threads for parallel processing") + + ("process-interior-cells", bpo::value()->implicit_value(true) + ->default_value(false), "build navmesh for interior cells") + ; + + return result; + } + + void loadSettings(const Files::ConfigurationManager& config, Settings::Manager& settings) + { + const std::string localDefault = (config.getLocalPath() / "defaults.bin").string(); + const std::string globalDefault = (config.getGlobalPath() / "defaults.bin").string(); + + if (boost::filesystem::exists(localDefault)) + settings.loadDefault(localDefault); + else if (boost::filesystem::exists(globalDefault)) + settings.loadDefault(globalDefault); + else + throw std::runtime_error("No default settings file found! Make sure the file \"defaults.bin\" was properly installed."); + + const std::string settingsPath = (config.getUserConfigPath() / "settings.cfg").string(); + if (boost::filesystem::exists(settingsPath)) + settings.loadUser(settingsPath); + } + + int runNavMeshTool(int argc, char *argv[]) + { + bpo::options_description desc = makeOptionsDescription(); + + bpo::parsed_options options = bpo::command_line_parser(argc, argv) + .options(desc).allow_unregistered().run(); + bpo::variables_map variables; + + bpo::store(options, variables); + bpo::notify(variables); + + if (variables.find("help") != variables.end()) + { + getRawStdout() << desc << std::endl; + return 0; + } + + Files::ConfigurationManager config; + + bpo::variables_map composingVariables = Files::separateComposingVariables(variables, desc); + config.readConfiguration(variables, desc); + Files::mergeComposingVariables(variables, composingVariables, desc); + + const std::string encoding(variables["encoding"].as()); + Log(Debug::Info) << ToUTF8::encodingUsingMessage(encoding); + ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(encoding)); + + Files::PathContainer dataDirs(asPathContainer(variables["data"].as())); + + auto local = variables["data-local"].as(); + if (!local.empty()) + dataDirs.push_back(std::move(local)); + + config.processPaths(dataDirs); + + const auto fsStrict = variables["fs-strict"].as(); + const auto resDir = variables["resources"].as(); + Version::Version v = Version::getOpenmwVersion(resDir.string()); + Log(Debug::Info) << v.describe(); + dataDirs.insert(dataDirs.begin(), resDir / "vfs"); + const auto fileCollections = Files::Collections(dataDirs, !fsStrict); + const auto archives = variables["fallback-archive"].as(); + const auto contentFiles = variables["content"].as(); + const std::size_t threadsNumber = variables["threads"].as(); + + if (threadsNumber < 1) + { + std::cerr << "Invalid threads number: " << threadsNumber << ", expected >= 1"; + return -1; + } + + const bool processInteriorCells = variables["process-interior-cells"].as(); + + Fallback::Map::init(variables["fallback"].as().mMap); + + VFS::Manager vfs(fsStrict); + + VFS::registerArchives(&vfs, fileCollections, archives, true); + + Settings::Manager settings; + loadSettings(config, settings); + + const osg::Vec3f agentHalfExtents = Settings::Manager::getVector3("default actor pathfind half extents", "Game"); + + DetourNavigator::NavMeshDb db((config.getUserDataPath() / "navmesh.db").string()); + + std::vector readers(contentFiles.size()); + EsmLoader::Query query; + query.mLoadActivators = true; + query.mLoadCells = true; + query.mLoadContainers = true; + query.mLoadDoors = true; + query.mLoadGameSettings = true; + query.mLoadLands = true; + query.mLoadStatics = true; + const EsmLoader::EsmData esmData = EsmLoader::loadEsmData(query, contentFiles, fileCollections, readers, &encoder); + + Resource::ImageManager imageManager(&vfs); + Resource::NifFileManager nifFileManager(&vfs); + Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager); + Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager); + DetourNavigator::RecastGlobalAllocator::init(); + DetourNavigator::Settings navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager(); + navigatorSettings.mRecast.mSwimHeightScale = EsmLoader::getGameSetting(esmData.mGameSettings, "fSwimHeightScale").getFloat(); + + WorldspaceData cellsData = gatherWorldspaceData(navigatorSettings, readers, vfs, bulletShapeManager, + esmData, processInteriorCells); + + generateAllNavMeshTiles(agentHalfExtents, navigatorSettings, threadsNumber, cellsData, std::move(db)); + + Log(Debug::Info) << "Done"; + + return 0; + } + } +} + +int main(int argc, char *argv[]) +{ + return wrapApplication(NavMeshTool::runNavMeshTool, argc, argv, "NavMeshTool"); +} diff --git a/apps/navmeshtool/navmesh.cpp b/apps/navmeshtool/navmesh.cpp new file mode 100644 index 0000000000..6b8150f1c0 --- /dev/null +++ b/apps/navmeshtool/navmesh.cpp @@ -0,0 +1,212 @@ +#include "navmesh.hpp" + +#include "worldspacedata.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace NavMeshTool +{ + namespace + { + using DetourNavigator::GenerateNavMeshTile; + using DetourNavigator::NavMeshDb; + using DetourNavigator::NavMeshTileInfo; + using DetourNavigator::PreparedNavMeshData; + using DetourNavigator::RecastMeshProvider; + using DetourNavigator::MeshSource; + using DetourNavigator::Settings; + using DetourNavigator::ShapeId; + using DetourNavigator::TileId; + using DetourNavigator::TilePosition; + using DetourNavigator::TileVersion; + using Sqlite3::Transaction; + + void logGeneratedTiles(std::size_t provided, std::size_t expected) + { + Log(Debug::Info) << provided << "/" << expected << " (" + << (static_cast(provided) / static_cast(expected) * 100) + << "%) navmesh tiles are generated"; + } + + struct LogGeneratedTiles + { + void operator()(std::size_t provided, std::size_t expected) const + { + logGeneratedTiles(provided, expected); + } + }; + + class NavMeshTileConsumer final : public DetourNavigator::NavMeshTileConsumer + { + public: + std::atomic_size_t mExpected {0}; + + explicit NavMeshTileConsumer(NavMeshDb db) + : mDb(std::move(db)) + , mTransaction(mDb.startTransaction()) + , mNextTileId(mDb.getMaxTileId() + 1) + , mNextShapeId(mDb.getMaxShapeId() + 1) + {} + + std::size_t getProvided() const { return mProvided.load(); } + + std::size_t getInserted() const { return mInserted.load(); } + + std::size_t getUpdated() const { return mUpdated.load(); } + + std::int64_t resolveMeshSource(const MeshSource& source) override + { + const std::lock_guard lock(mMutex); + return DetourNavigator::resolveMeshSource(mDb, source, mNextShapeId); + } + + std::optional find(const std::string& worldspace, const TilePosition &tilePosition, + const std::vector &input) override + { + std::optional result; + std::lock_guard lock(mMutex); + if (const auto tile = mDb.findTile(worldspace, tilePosition, input)) + { + NavMeshTileInfo info; + info.mTileId = tile->mTileId; + info.mVersion = tile->mVersion; + result.emplace(info); + } + return result; + } + + void ignore() override { report(); } + + void insert(const std::string& worldspace, const TilePosition& tilePosition, std::int64_t version, + const std::vector& input, PreparedNavMeshData& data) override + { + data.mUserId = static_cast(mNextTileId); + { + std::lock_guard lock(mMutex); + mDb.insertTile(mNextTileId, worldspace, tilePosition, TileVersion {version}, input, serialize(data)); + ++mNextTileId.t; + } + ++mInserted; + report(); + } + + void update(std::int64_t tileId, std::int64_t version, PreparedNavMeshData& data) override + { + data.mUserId = static_cast(tileId); + { + std::lock_guard lock(mMutex); + mDb.updateTile(TileId {tileId}, TileVersion {version}, serialize(data)); + } + ++mUpdated; + report(); + } + + void wait() + { + constexpr std::size_t tilesPerTransaction = 3000; + std::unique_lock lock(mMutex); + while (mProvided < mExpected) + { + mHasTile.wait(lock); + if (mProvided % tilesPerTransaction == 0) + { + mTransaction.commit(); + mTransaction = mDb.startTransaction(); + } + } + logGeneratedTiles(mProvided, mExpected); + } + + void commit() { mTransaction.commit(); } + + private: + std::atomic_size_t mProvided {0}; + std::atomic_size_t mInserted {0}; + std::atomic_size_t mUpdated {0}; + std::mutex mMutex; + NavMeshDb mDb; + Transaction mTransaction; + TileId mNextTileId; + std::condition_variable mHasTile; + Misc::ProgressReporter mReporter; + ShapeId mNextShapeId; + + void report() + { + mReporter(mProvided + 1, mExpected); + ++mProvided; + mHasTile.notify_one(); + } + }; + } + + void generateAllNavMeshTiles(const osg::Vec3f& agentHalfExtents, const Settings& settings, + const std::size_t threadsNumber, WorldspaceData& data, NavMeshDb&& db) + { + Log(Debug::Info) << "Generating navmesh tiles by " << threadsNumber << " parallel workers..."; + + SceneUtil::WorkQueue workQueue(threadsNumber); + auto navMeshTileConsumer = std::make_shared(std::move(db)); + std::size_t tiles = 0; + + for (const std::unique_ptr& input : data.mNavMeshInputs) + { + DetourNavigator::getTilesPositions( + Misc::Convert::toOsg(input->mAabb.m_min), Misc::Convert::toOsg(input->mAabb.m_max), settings.mRecast, + [&] (const TilePosition& tilePosition) + { + workQueue.addWorkItem(new GenerateNavMeshTile( + input->mWorldspace, + tilePosition, + RecastMeshProvider(input->mTileCachedRecastMeshManager), + agentHalfExtents, + settings, + navMeshTileConsumer + )); + + ++tiles; + }); + + navMeshTileConsumer->mExpected = tiles; + } + + navMeshTileConsumer->wait(); + navMeshTileConsumer->commit(); + + Log(Debug::Info) << "Generated navmesh for " << navMeshTileConsumer->getProvided() << " tiles, " + << navMeshTileConsumer->getInserted() << " are inserted and " + << navMeshTileConsumer->getUpdated() << " updated"; + } +} diff --git a/apps/navmeshtool/navmesh.hpp b/apps/navmeshtool/navmesh.hpp new file mode 100644 index 0000000000..725f0cd6a4 --- /dev/null +++ b/apps/navmeshtool/navmesh.hpp @@ -0,0 +1,23 @@ +#ifndef OPENMW_NAVMESHTOOL_NAVMESH_H +#define OPENMW_NAVMESHTOOL_NAVMESH_H + +#include + +#include +#include + +namespace DetourNavigator +{ + class NavMeshDb; + struct Settings; +} + +namespace NavMeshTool +{ + struct WorldspaceData; + + void generateAllNavMeshTiles(const osg::Vec3f& agentHalfExtents, const DetourNavigator::Settings& settings, + const std::size_t threadsNumber, WorldspaceData& cellsData, DetourNavigator::NavMeshDb&& db); +} + +#endif diff --git a/apps/navmeshtool/worldspacedata.cpp b/apps/navmeshtool/worldspacedata.cpp new file mode 100644 index 0000000000..189d24f34a --- /dev/null +++ b/apps/navmeshtool/worldspacedata.cpp @@ -0,0 +1,330 @@ +#include "worldspacedata.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace NavMeshTool +{ + namespace + { + using DetourNavigator::CollisionShape; + using DetourNavigator::HeightfieldPlane; + using DetourNavigator::HeightfieldShape; + using DetourNavigator::HeightfieldSurface; + using DetourNavigator::ObjectId; + using DetourNavigator::ObjectTransform; + + struct CellRef + { + ESM::RecNameInts mType; + ESM::RefNum mRefNum; + std::string mRefId; + float mScale; + ESM::Position mPos; + + CellRef(ESM::RecNameInts type, ESM::RefNum refNum, std::string&& refId, float scale, const ESM::Position& pos) + : mType(type), mRefNum(refNum), mRefId(std::move(refId)), mScale(scale), mPos(pos) {} + }; + + ESM::RecNameInts getType(const EsmLoader::EsmData& esmData, std::string_view refId) + { + const auto it = std::lower_bound(esmData.mRefIdTypes.begin(), esmData.mRefIdTypes.end(), + refId, EsmLoader::LessById {}); + if (it == esmData.mRefIdTypes.end() || it->mId != refId) + return {}; + return it->mType; + } + + std::vector loadCellRefs(const ESM::Cell& cell, const EsmLoader::EsmData& esmData, + std::vector& readers) + { + std::vector> cellRefs; + + for (std::size_t i = 0; i < cell.mContextList.size(); i++) + { + ESM::ESMReader& reader = readers[static_cast(cell.mContextList[i].index)]; + cell.restore(reader, static_cast(i)); + ESM::CellRef cellRef; + bool deleted = false; + while (ESM::Cell::getNextRef(reader, cellRef, deleted)) + { + Misc::StringUtils::lowerCaseInPlace(cellRef.mRefID); + const ESM::RecNameInts type = getType(esmData, cellRef.mRefID); + if (type == ESM::RecNameInts {}) + continue; + cellRefs.emplace_back(deleted, type, cellRef.mRefNum, std::move(cellRef.mRefID), + cellRef.mScale, cellRef.mPos); + } + } + + Log(Debug::Debug) << "Loaded " << cellRefs.size() << " cell refs"; + + const auto getKey = [] (const EsmLoader::Record& v) -> const ESM::RefNum& { return v.mValue.mRefNum; }; + std::vector result = prepareRecords(cellRefs, getKey); + + Log(Debug::Debug) << "Prepared " << result.size() << " unique cell refs"; + + return result; + } + + template + void forEachObject(const ESM::Cell& cell, const EsmLoader::EsmData& esmData, const VFS::Manager& vfs, + Resource::BulletShapeManager& bulletShapeManager, std::vector& readers, + F&& f) + { + std::vector cellRefs = loadCellRefs(cell, esmData, readers); + + Log(Debug::Debug) << "Prepared " << cellRefs.size() << " unique cell refs"; + + for (CellRef& cellRef : cellRefs) + { + std::string model(getModel(esmData, cellRef.mRefId, cellRef.mType)); + if (model.empty()) + continue; + + if (cellRef.mType != ESM::REC_STAT) + model = Misc::ResourceHelpers::correctActorModelPath(model, &vfs); + + osg::ref_ptr shape = [&] + { + try + { + return bulletShapeManager.getShape("meshes/" + model); + } + catch (const std::exception& e) + { + Log(Debug::Warning) << "Failed to load cell ref \"" << cellRef.mRefId << "\" model \"" << model << "\": " << e.what(); + return osg::ref_ptr(); + } + } (); + + if (shape == nullptr || shape->mCollisionShape == nullptr) + continue; + + osg::ref_ptr shapeInstance(new Resource::BulletShapeInstance(std::move(shape))); + + switch (cellRef.mType) + { + case ESM::REC_ACTI: + case ESM::REC_CONT: + case ESM::REC_DOOR: + case ESM::REC_STAT: + f(BulletObject(std::move(shapeInstance), cellRef.mPos, cellRef.mScale)); + break; + default: + break; + } + } + } + + struct GetXY + { + osg::Vec2i operator()(const ESM::Land& value) const { return osg::Vec2i(value.mX, value.mY); } + }; + + struct LessByXY + { + bool operator ()(const ESM::Land& lhs, const ESM::Land& rhs) const + { + return GetXY {}(lhs) < GetXY {}(rhs); + } + + bool operator ()(const ESM::Land& lhs, const osg::Vec2i& rhs) const + { + return GetXY {}(lhs) < rhs; + } + + bool operator ()(const osg::Vec2i& lhs, const ESM::Land& rhs) const + { + return lhs < GetXY {}(rhs); + } + }; + + btAABB getAabb(const osg::Vec2i& cellPosition, btScalar minHeight, btScalar maxHeight) + { + btAABB aabb; + aabb.m_min = btVector3( + static_cast(cellPosition.x() * ESM::Land::REAL_SIZE), + static_cast(cellPosition.y() * ESM::Land::REAL_SIZE), + minHeight + ); + aabb.m_min = btVector3( + static_cast((cellPosition.x() + 1) * ESM::Land::REAL_SIZE), + static_cast((cellPosition.y() + 1) * ESM::Land::REAL_SIZE), + maxHeight + ); + return aabb; + } + + void mergeOrAssign(const btAABB& aabb, btAABB& target, bool& initialized) + { + if (initialized) + return target.merge(aabb); + + target.m_min = aabb.m_min; + target.m_max = aabb.m_max; + initialized = true; + } + + std::tuple makeHeightfieldShape(const std::optional& land, + const osg::Vec2i& cellPosition, std::vector>& heightfields, + std::vector>& landDatas) + { + if (!land.has_value() || osg::Vec2i(land->mX, land->mY) != cellPosition + || (land->mDataTypes & ESM::Land::DATA_VHGT) == 0) + return {HeightfieldPlane {ESM::Land::DEFAULT_HEIGHT}, ESM::Land::DEFAULT_HEIGHT, ESM::Land::DEFAULT_HEIGHT}; + + ESM::Land::LandData& landData = *landDatas.emplace_back(std::make_unique()); + land->loadData(ESM::Land::DATA_VHGT, &landData); + heightfields.emplace_back(std::vector(std::begin(landData.mHeights), std::end(landData.mHeights))); + HeightfieldSurface surface; + surface.mHeights = heightfields.back().data(); + surface.mMinHeight = landData.mMinHeight; + surface.mMaxHeight = landData.mMaxHeight; + surface.mSize = static_cast(ESM::Land::LAND_SIZE); + return {surface, landData.mMinHeight, landData.mMaxHeight}; + } + } + + WorldspaceNavMeshInput::WorldspaceNavMeshInput(std::string worldspace, const DetourNavigator::RecastSettings& settings) + : mWorldspace(std::move(worldspace)) + , mTileCachedRecastMeshManager(settings) + { + mAabb.m_min = btVector3(0, 0, 0); + mAabb.m_max = btVector3(0, 0, 0); + } + + WorldspaceData gatherWorldspaceData(const DetourNavigator::Settings& settings, std::vector& readers, + const VFS::Manager& vfs, Resource::BulletShapeManager& bulletShapeManager, const EsmLoader::EsmData& esmData, + bool processInteriorCells) + { + Log(Debug::Info) << "Processing " << esmData.mCells.size() << " cells..."; + + std::map> navMeshInputs; + WorldspaceData data; + + std::size_t objectsCounter = 0; + + for (std::size_t i = 0; i < esmData.mCells.size(); ++i) + { + const ESM::Cell& cell = esmData.mCells[i]; + const bool exterior = cell.isExterior(); + + if (!exterior && !processInteriorCells) + { + Log(Debug::Info) << "Skipped " << (exterior ? "exterior" : "interior") + << " cell (" << (i + 1) << "/" << esmData.mCells.size() << ") \"" << cell.getDescription() << "\""; + continue; + } + + Log(Debug::Debug) << "Processing " << (exterior ? "exterior" : "interior") + << " cell (" << (i + 1) << "/" << esmData.mCells.size() << ") \"" << cell.getDescription() << "\""; + + const osg::Vec2i cellPosition(cell.mData.mX, cell.mData.mY); + const std::size_t cellObjectsBegin = data.mObjects.size(); + + WorldspaceNavMeshInput& navMeshInput = [&] () -> WorldspaceNavMeshInput& + { + auto it = navMeshInputs.find(cell.mCellId.mWorldspace); + if (it == navMeshInputs.end()) + { + it = navMeshInputs.emplace(cell.mCellId.mWorldspace, + std::make_unique(cell.mCellId.mWorldspace, settings.mRecast)).first; + it->second->mTileCachedRecastMeshManager.setWorldspace(cell.mCellId.mWorldspace); + } + return *it->second; + } (); + + if (exterior) + { + const auto it = std::lower_bound(esmData.mLands.begin(), esmData.mLands.end(), cellPosition, LessByXY {}); + const auto [heightfieldShape, minHeight, maxHeight] = makeHeightfieldShape( + it == esmData.mLands.end() ? std::optional() : *it, + cellPosition, data.mHeightfields, data.mLandData + ); + + mergeOrAssign(getAabb(cellPosition, minHeight, maxHeight), + navMeshInput.mAabb, navMeshInput.mAabbInitialized); + + navMeshInput.mTileCachedRecastMeshManager.addHeightfield(cellPosition, ESM::Land::REAL_SIZE, heightfieldShape); + + navMeshInput.mTileCachedRecastMeshManager.addWater(cellPosition, ESM::Land::REAL_SIZE, -1); + } + else + { + if ((cell.mData.mFlags & ESM::Cell::HasWater) != 0) + navMeshInput.mTileCachedRecastMeshManager.addWater(cellPosition, std::numeric_limits::max(), cell.mWater); + } + + forEachObject(cell, esmData, vfs, bulletShapeManager, readers, + [&] (BulletObject object) + { + 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 ObjectId objectId(++objectsCounter); + const CollisionShape shape(object.getShapeInstance(), *object.getCollisionObject().getCollisionShape(), object.getObjectTransform()); + + navMeshInput.mTileCachedRecastMeshManager.addObject(objectId, shape, transform, DetourNavigator::AreaType_ground); + + if (const btCollisionShape* avoid = object.getShapeInstance()->mAvoidCollisionShape.get()) + { + const CollisionShape avoidShape(object.getShapeInstance(), *avoid, object.getObjectTransform()); + navMeshInput.mTileCachedRecastMeshManager.addObject(objectId, avoidShape, transform, DetourNavigator::AreaType_null); + } + + data.mObjects.emplace_back(std::move(object)); + }); + + Log(Debug::Info) << "Processed " << (exterior ? "exterior" : "interior") + << " cell (" << (i + 1) << "/" << esmData.mCells.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); }); + + Log(Debug::Info) << "Processed " << esmData.mCells.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 new file mode 100644 index 0000000000..3dccd5a8bc --- /dev/null +++ b/apps/navmeshtool/worldspacedata.hpp @@ -0,0 +1,97 @@ +#ifndef OPENMW_NAVMESHTOOL_WORLDSPACEDATA_H +#define OPENMW_NAVMESHTOOL_WORLDSPACEDATA_H + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +namespace ESM +{ + class ESMReader; +} + +namespace VFS +{ + class Manager; +} + +namespace Resource +{ + class BulletShapeManager; +} + +namespace EsmLoader +{ + struct EsmData; +} + +namespace DetourNavigator +{ + struct Settings; +} + +namespace NavMeshTool +{ + using DetourNavigator::TileCachedRecastMeshManager; + using DetourNavigator::ObjectTransform; + + struct WorldspaceNavMeshInput + { + std::string mWorldspace; + TileCachedRecastMeshManager mTileCachedRecastMeshManager; + btAABB mAabb; + bool mAabbInitialized = false; + + explicit WorldspaceNavMeshInput(std::string worldspace, const DetourNavigator::RecastSettings& settings); + }; + + class BulletObject + { + public: + BulletObject(osg::ref_ptr&& shapeInstance, const ESM::Position& position, + float localScaling) + : mShapeInstance(std::move(shapeInstance)) + , mObjectTransform {position, localScaling} + , mCollisionObject(BulletHelpers::makeCollisionObject( + mShapeInstance->mCollisionShape.get(), + Misc::Convert::toBullet(position.asVec3()), + Misc::Convert::toBullet(Misc::Convert::makeOsgQuat(position)) + )) + { + mShapeInstance->setLocalScaling(btVector3(localScaling, localScaling, localScaling)); + } + + const osg::ref_ptr& getShapeInstance() const noexcept { return mShapeInstance; } + const DetourNavigator::ObjectTransform& getObjectTransform() const noexcept { return mObjectTransform; } + btCollisionObject& getCollisionObject() const noexcept { return *mCollisionObject; } + + private: + osg::ref_ptr mShapeInstance; + DetourNavigator::ObjectTransform mObjectTransform; + std::unique_ptr mCollisionObject; + }; + + struct WorldspaceData + { + std::vector> mNavMeshInputs; + std::vector mObjects; + std::vector> mLandData; + std::vector> mHeightfields; + }; + + WorldspaceData gatherWorldspaceData(const DetourNavigator::Settings& settings, std::vector& readers, + const VFS::Manager& vfs, Resource::BulletShapeManager& bulletShapeManager, const EsmLoader::EsmData& esmData, + bool processInteriorCells); +} + +#endif diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index e450e76483..9974cb44cd 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -788,7 +788,7 @@ namespace MWMechanics MWBase::World* world = MWBase::Environment::get().getWorld(); world->getNavigator()->setUpdatesEnabled(mAI); if (mAI) - world->getNavigator()->update(world->getPlayerPtr().getRefData().getPosition().asVec3()); + world->getNavigator()->update(world->getPlayerPtr().getRefData().getPosition().asVec3()); return mAI; } diff --git a/apps/openmw/mwphysics/ptrholder.hpp b/apps/openmw/mwphysics/ptrholder.hpp index fcd6ce203a..e194f8e934 100644 --- a/apps/openmw/mwphysics/ptrholder.hpp +++ b/apps/openmw/mwphysics/ptrholder.hpp @@ -20,7 +20,7 @@ namespace MWPhysics mPtr = updated; } - MWWorld::Ptr getPtr() + MWWorld::Ptr getPtr() const { return mPtr; } diff --git a/apps/openmw/mwrender/actorspaths.cpp b/apps/openmw/mwrender/actorspaths.cpp index 941f37df75..c8c5f56d8f 100644 --- a/apps/openmw/mwrender/actorspaths.cpp +++ b/apps/openmw/mwrender/actorspaths.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include @@ -47,7 +48,7 @@ namespace MWRender if (group != mGroups.end()) mRootNode->removeChild(group->second); - const auto newGroup = SceneUtil::createAgentPathGroup(path, halfExtents, start, end, settings); + const auto newGroup = SceneUtil::createAgentPathGroup(path, halfExtents, start, end, settings.mRecast); if (newGroup) { MWBase::Environment::get().getResourceSystem()->getSceneManager()->recreateShaders(newGroup, "debug"); diff --git a/apps/openmw/mwrender/recastmesh.cpp b/apps/openmw/mwrender/recastmesh.cpp index f108536242..5f202720b2 100644 --- a/apps/openmw/mwrender/recastmesh.cpp +++ b/apps/openmw/mwrender/recastmesh.cpp @@ -54,7 +54,7 @@ namespace MWRender if (it->second.mGeneration != tile->second->getGeneration() || it->second.mRevision != tile->second->getRevision()) { - const auto group = SceneUtil::createRecastMeshGroup(*tile->second, settings); + const auto group = SceneUtil::createRecastMeshGroup(*tile->second, settings.mRecast); MWBase::Environment::get().getResourceSystem()->getSceneManager()->recreateShaders(group, "debug"); group->setNodeMask(Mask_Debug); mRootNode->removeChild(it->second.mValue); @@ -71,7 +71,7 @@ namespace MWRender { if (mGroups.count(tile.first)) continue; - const auto group = SceneUtil::createRecastMeshGroup(*tile.second, settings); + const auto group = SceneUtil::createRecastMeshGroup(*tile.second, settings.mRecast); MWBase::Environment::get().getResourceSystem()->getSceneManager()->recreateShaders(group, "debug"); group->setNodeMask(Mask_Debug); mGroups.emplace(tile.first, Group {tile.second->getGeneration(), tile.second->getRevision(), group}); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 15b3477cb8..054e26e855 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -64,14 +64,23 @@ namespace * osg::Quat(zr, osg::Vec3(0, 0, -1)); } - osg::Quat makeNodeRotation(const MWWorld::Ptr& ptr, RotationOrder order) + osg::Quat makeInverseNodeRotation(const MWWorld::Ptr& ptr) { const auto pos = ptr.getRefData().getPosition(); + return ptr.getClass().isActor() ? makeActorOsgQuat(pos) : makeInversedOrderObjectOsgQuat(pos); + } - const auto rot = ptr.getClass().isActor() ? makeActorOsgQuat(pos) - : (order == RotationOrder::inverse ? makeInversedOrderObjectOsgQuat(pos) : Misc::Convert::makeOsgQuat(pos)); + osg::Quat makeDirectNodeRotation(const MWWorld::Ptr& ptr) + { + const auto pos = ptr.getRefData().getPosition(); + return ptr.getClass().isActor() ? makeActorOsgQuat(pos) : Misc::Convert::makeOsgQuat(pos); + } - return rot; + osg::Quat makeNodeRotation(const MWWorld::Ptr& ptr, RotationOrder order) + { + if (order == RotationOrder::inverse) + return makeInverseNodeRotation(ptr); + return makeDirectNodeRotation(ptr); } void setNodeRotation(const MWWorld::Ptr& ptr, MWRender::RenderingManager& rendering, const osg::Quat &rotation) @@ -101,7 +110,7 @@ namespace } std::string model = getModel(ptr, rendering.getResourceSystem()->getVFS()); - const auto rotation = makeNodeRotation(ptr, RotationOrder::direct); + const auto rotation = makeDirectNodeRotation(ptr); const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); if (!refnum.hasContentFile() || pagedRefs.find(refnum) == pagedRefs.end()) @@ -128,6 +137,8 @@ namespace { if (const auto object = physics.getObject(ptr)) { + const DetourNavigator::ObjectTransform objectTransform {ptr.getRefData().getPosition(), ptr.getCellRef().getScale()}; + if (ptr.getClass().isDoor() && !ptr.getCellRef().getTeleport()) { btVector3 aabbMin; @@ -159,7 +170,7 @@ namespace navigator.addObject( DetourNavigator::ObjectId(object), - DetourNavigator::DoorShapes(object->getShapeInstance(), connectionStart, connectionEnd), + DetourNavigator::DoorShapes(object->getShapeInstance(), objectTransform, connectionStart, connectionEnd), transform ); } @@ -167,7 +178,7 @@ namespace { navigator.addObject( DetourNavigator::ObjectId(object), - DetourNavigator::ObjectShapes(object->getShapeInstance()), + DetourNavigator::ObjectShapes(object->getShapeInstance(), objectTransform), object->getTransform() ); } @@ -339,8 +350,7 @@ namespace MWWorld if (const auto pathgrid = world->getStore().get().search(*cell->getCell())) mNavigator.removePathgrid(*pathgrid); - const auto player = world->getPlayerPtr(); - mNavigator.update(player.getRefData().getPosition().asVec3()); + mNavigator.update(world->getPlayerPtr().getRefData().getPosition().asVec3()); MWBase::Environment::get().getMechanicsManager()->drop (cell); @@ -367,6 +377,8 @@ namespace MWWorld const int cellX = cell->getCell()->getGridX(); const int cellY = cell->getCell()->getGridY(); + mNavigator.setWorldspace(cell->getCell()->mCellId.mWorldspace); + if (cell->getCell()->isExterior()) { osg::ref_ptr land = mRendering.getLandManager()->getLand(cellX, cellY); @@ -496,10 +508,13 @@ namespace MWWorld void Scene::playerMoved(const osg::Vec3f &pos) { + if (mCurrentCell == nullptr) + return; + const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); mNavigator.updatePlayerPosition(player.getRefData().getPosition().asVec3()); - if (!mCurrentCell || !mCurrentCell->isExterior()) + if (!mCurrentCell->isExterior()) return; osg::Vec2i newCell = getNewGridCenter(pos, &mCurrentGridCenter); @@ -875,8 +890,11 @@ namespace MWWorld addObject(ptr, *mPhysics, mRendering, mPagedRefs); addObject(ptr, *mPhysics, mNavigator); MWBase::Environment::get().getWorld()->scaleObject(ptr, ptr.getCellRef().getScale()); - const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - mNavigator.update(player.getRefData().getPosition().asVec3()); + if (mCurrentCell != nullptr) + { + const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + mNavigator.update(player.getRefData().getPosition().asVec3()); + } } catch (std::exception& e) { @@ -892,8 +910,11 @@ namespace MWWorld if (const auto object = mPhysics->getObject(ptr)) { mNavigator.removeObject(DetourNavigator::ObjectId(object)); - const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - mNavigator.update(player.getRefData().getPosition().asVec3()); + if (mCurrentCell != nullptr) + { + const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + mNavigator.update(player.getRefData().getPosition().asVec3()); + } } else if (mPhysics->getActor(ptr)) { diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index f0ac894cfc..a44646ef37 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -186,8 +186,8 @@ namespace MWWorld if (Settings::Manager::getBool("enable", "Navigator")) { auto navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager(); - navigatorSettings.mSwimHeightScale = mSwimHeightScale; - mNavigator = DetourNavigator::makeNavigator(navigatorSettings); + navigatorSettings.mRecast.mSwimHeightScale = mSwimHeightScale; + mNavigator = DetourNavigator::makeNavigator(navigatorSettings, userDataPath); } else { @@ -1524,16 +1524,19 @@ namespace MWWorld if (const auto object = mPhysics->getObject(door.first)) updateNavigatorObject(*object); - if (mShouldUpdateNavigator) + auto player = getPlayerPtr(); + if (mShouldUpdateNavigator && player.getCell() != nullptr) { - mNavigator->update(getPlayerPtr().getRefData().getPosition().asVec3()); + mNavigator->update(player.getRefData().getPosition().asVec3()); mShouldUpdateNavigator = false; } } void World::updateNavigatorObject(const MWPhysics::Object& object) { - const DetourNavigator::ObjectShapes shapes(object.getShapeInstance()); + const MWWorld::Ptr ptr = object.getPtr(); + const DetourNavigator::ObjectShapes shapes(object.getShapeInstance(), + DetourNavigator::ObjectTransform {ptr.getRefData().getPosition(), ptr.getCellRef().getScale()}); mShouldUpdateNavigator = mNavigator->updateObject(DetourNavigator::ObjectId(&object), shapes, object.getTransform()) || mShouldUpdateNavigator; } diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 7cfac20421..19b31f78be 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -41,10 +41,14 @@ if (GTEST_FOUND AND GMOCK_FOUND) detournavigator/recastmeshobject.cpp detournavigator/navmeshtilescache.cpp detournavigator/tilecachedrecastmeshmanager.cpp - detournavigator/serialization/binaryreader.cpp - detournavigator/serialization/binarywriter.cpp - detournavigator/serialization/sizeaccumulator.cpp - detournavigator/serialization/integration.cpp + detournavigator/navmeshdb.cpp + detournavigator/serialization.cpp + detournavigator/asyncnavmeshupdater.cpp + + serialization/binaryreader.cpp + serialization/binarywriter.cpp + serialization/sizeaccumulator.cpp + serialization/integration.cpp settings/parser.cpp diff --git a/apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp b/apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp new file mode 100644 index 0000000000..d245838858 --- /dev/null +++ b/apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp @@ -0,0 +1,201 @@ +#include "settings.hpp" + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include + +namespace +{ + using namespace testing; + using namespace DetourNavigator; + using namespace DetourNavigator::Tests; + + void addHeightFieldPlane(TileCachedRecastMeshManager& recastMeshManager) + { + const osg::Vec2i cellPosition(0, 0); + const int cellSize = 8192; + recastMeshManager.addHeightfield(cellPosition, cellSize, HeightfieldPlane {0}); + } + + struct DetourNavigatorAsyncNavMeshUpdaterTest : Test + { + Settings mSettings = makeSettings(); + TileCachedRecastMeshManager mRecastMeshManager {mSettings.mRecast}; + OffMeshConnectionsManager mOffMeshConnectionsManager {mSettings.mRecast}; + const osg::Vec3f mAgentHalfExtents {29, 29, 66}; + const TilePosition mPlayerTile {0, 0}; + const std::string mWorldspace = "sys::default"; + Loading::Listener mListener; + }; + + TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, for_all_jobs_done_when_empty_wait_should_terminate) + { + AsyncNavMeshUpdater updater {mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr}; + updater.wait(mListener, WaitConditionType::allJobsDone); + } + + TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, for_required_tiles_present_when_empty_wait_should_terminate) + { + AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr); + updater.wait(mListener, WaitConditionType::requiredTilesPresent); + } + + TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, post_should_generate_navmesh_tile) + { + mRecastMeshManager.setWorldspace(mWorldspace); + addHeightFieldPlane(mRecastMeshManager); + AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr); + const auto navMeshCacheItem = std::make_shared(makeEmptyNavMesh(mSettings), 1); + const std::map changedTiles {{TilePosition {0, 0}, ChangeType::add}}; + updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); + updater.wait(mListener, WaitConditionType::allJobsDone); + EXPECT_NE(navMeshCacheItem->lockConst()->getImpl().getTileRefAt(0, 0, 0), 0); + } + + TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, repeated_post_should_lead_to_cache_hit) + { + mRecastMeshManager.setWorldspace(mWorldspace); + addHeightFieldPlane(mRecastMeshManager); + AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr); + const auto navMeshCacheItem = std::make_shared(makeEmptyNavMesh(mSettings), 1); + const std::map changedTiles {{TilePosition {0, 0}, ChangeType::add}}; + updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); + updater.wait(mListener, WaitConditionType::allJobsDone); + { + const auto stats = updater.getStats(); + ASSERT_EQ(stats.mCache.mGetCount, 1); + ASSERT_EQ(stats.mCache.mHitCount, 0); + } + updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); + updater.wait(mListener, WaitConditionType::allJobsDone); + { + const auto stats = updater.getStats(); + EXPECT_EQ(stats.mCache.mGetCount, 2); + EXPECT_EQ(stats.mCache.mHitCount, 1); + } + } + + TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, post_for_update_change_type_should_not_update_cache) + { + mRecastMeshManager.setWorldspace(mWorldspace); + addHeightFieldPlane(mRecastMeshManager); + AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr); + const auto navMeshCacheItem = std::make_shared(makeEmptyNavMesh(mSettings), 1); + const std::map changedTiles {{TilePosition {0, 0}, ChangeType::update}}; + updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); + updater.wait(mListener, WaitConditionType::allJobsDone); + { + const auto stats = updater.getStats(); + ASSERT_EQ(stats.mCache.mGetCount, 1); + ASSERT_EQ(stats.mCache.mHitCount, 0); + } + updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); + updater.wait(mListener, WaitConditionType::allJobsDone); + { + const auto stats = updater.getStats(); + EXPECT_EQ(stats.mCache.mGetCount, 2); + EXPECT_EQ(stats.mCache.mHitCount, 0); + } + } + + TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, post_should_write_generated_tile_to_db) + { + mRecastMeshManager.setWorldspace(mWorldspace); + addHeightFieldPlane(mRecastMeshManager); + auto db = std::make_unique(":memory:"); + NavMeshDb* const dbPtr = db.get(); + AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, std::move(db)); + const auto navMeshCacheItem = std::make_shared(makeEmptyNavMesh(mSettings), 1); + const TilePosition tilePosition {0, 0}; + const std::map changedTiles {{tilePosition, ChangeType::add}}; + updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); + updater.wait(mListener, WaitConditionType::allJobsDone); + const auto recastMesh = mRecastMeshManager.getMesh(mWorldspace, tilePosition); + ASSERT_NE(recastMesh, nullptr); + ShapeId nextShapeId {1}; + const std::vector objects = makeDbRefGeometryObjects(recastMesh->getMeshSources(), + [&] (const MeshSource& v) { return resolveMeshSource(*dbPtr, v, nextShapeId); }); + const auto tile = dbPtr->findTile(mWorldspace, tilePosition, serialize(mSettings.mRecast, *recastMesh, objects)); + ASSERT_TRUE(tile.has_value()); + EXPECT_EQ(tile->mTileId, 1); + EXPECT_EQ(tile->mVersion, mSettings.mNavMeshVersion); + } + + TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, post_when_writing_to_db_disabled_should_not_write) + { + mRecastMeshManager.setWorldspace(mWorldspace); + addHeightFieldPlane(mRecastMeshManager); + auto db = std::make_unique(":memory:"); + NavMeshDb* const dbPtr = db.get(); + mSettings.mWriteToNavMeshDb = false; + AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, std::move(db)); + const auto navMeshCacheItem = std::make_shared(makeEmptyNavMesh(mSettings), 1); + const TilePosition tilePosition {0, 0}; + const std::map changedTiles {{tilePosition, ChangeType::add}}; + updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); + updater.wait(mListener, WaitConditionType::allJobsDone); + const auto recastMesh = mRecastMeshManager.getMesh(mWorldspace, tilePosition); + ASSERT_NE(recastMesh, nullptr); + ShapeId nextShapeId {1}; + const std::vector objects = makeDbRefGeometryObjects(recastMesh->getMeshSources(), + [&] (const MeshSource& v) { return resolveMeshSource(*dbPtr, v, nextShapeId); }); + const auto tile = dbPtr->findTile(mWorldspace, tilePosition, serialize(mSettings.mRecast, *recastMesh, objects)); + ASSERT_FALSE(tile.has_value()); + } + + TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, post_should_read_from_db_on_cache_miss) + { + mRecastMeshManager.setWorldspace(mWorldspace); + addHeightFieldPlane(mRecastMeshManager); + mSettings.mMaxNavMeshTilesCacheSize = 0; + AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, std::make_unique(":memory:")); + const auto navMeshCacheItem = std::make_shared(makeEmptyNavMesh(mSettings), 1); + const std::map changedTiles {{TilePosition {0, 0}, ChangeType::add}}; + updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); + updater.wait(mListener, WaitConditionType::allJobsDone); + { + const auto stats = updater.getStats(); + ASSERT_EQ(stats.mCache.mGetCount, 1); + ASSERT_EQ(stats.mCache.mHitCount, 0); + ASSERT_TRUE(stats.mDb.has_value()); + ASSERT_EQ(stats.mDb->mGetTileCount, 1); + ASSERT_EQ(stats.mDbGetTileHits, 0); + } + updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); + updater.wait(mListener, WaitConditionType::allJobsDone); + { + const auto stats = updater.getStats(); + EXPECT_EQ(stats.mCache.mGetCount, 2); + EXPECT_EQ(stats.mCache.mHitCount, 0); + ASSERT_TRUE(stats.mDb.has_value()); + EXPECT_EQ(stats.mDb->mGetTileCount, 2); + EXPECT_EQ(stats.mDbGetTileHits, 1); + } + } + + TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, on_changing_player_tile_post_should_remove_tiles_out_of_range) + { + mRecastMeshManager.setWorldspace(mWorldspace); + addHeightFieldPlane(mRecastMeshManager); + AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr); + const auto navMeshCacheItem = std::make_shared(makeEmptyNavMesh(mSettings), 1); + const std::map changedTilesAdd {{TilePosition {0, 0}, ChangeType::add}}; + updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTilesAdd); + updater.wait(mListener, WaitConditionType::allJobsDone); + ASSERT_NE(navMeshCacheItem->lockConst()->getImpl().getTileRefAt(0, 0, 0), 0); + const std::map changedTilesRemove {{TilePosition {0, 0}, ChangeType::remove}}; + const TilePosition playerTile(100, 100); + updater.post(mAgentHalfExtents, navMeshCacheItem, playerTile, mWorldspace, changedTilesRemove); + updater.wait(mListener, WaitConditionType::allJobsDone); + EXPECT_EQ(navMeshCacheItem->lockConst()->getImpl().getTileRefAt(0, 0, 0), 0); + } +} diff --git a/apps/openmw_test_suite/detournavigator/generate.hpp b/apps/openmw_test_suite/detournavigator/generate.hpp new file mode 100644 index 0000000000..52d04495a7 --- /dev/null +++ b/apps/openmw_test_suite/detournavigator/generate.hpp @@ -0,0 +1,51 @@ +#ifndef OPENMW_TEST_SUITE_DETOURNAVIGATOR_GENERATE_H +#define OPENMW_TEST_SUITE_DETOURNAVIGATOR_GENERATE_H + +#include +#include +#include +#include + +namespace DetourNavigator +{ + namespace Tests + { + template + inline auto generateValue(T& value, Random& random) + -> std::enable_if_t= 2> + { + using Distribution = std::conditional_t< + std::is_floating_point_v, + std::uniform_real_distribution, + std::uniform_int_distribution + >; + Distribution distribution(std::numeric_limits::min(), std::numeric_limits::max()); + value = distribution(random); + } + + template + inline auto generateValue(T& value, Random& random) + -> std::enable_if_t + { + unsigned short v; + generateValue(v, random); + value = static_cast(v % 256); + } + + template + inline void generateValue(unsigned char& value, Random& random) + { + unsigned short v; + generateValue(v, random); + value = static_cast(v % 256); + } + + template + inline void generateRange(I begin, I end, Random& random) + { + std::for_each(begin, end, [&] (auto& v) { generateValue(v, random); }); + } + } +} + +#endif diff --git a/apps/openmw_test_suite/detournavigator/gettilespositions.cpp b/apps/openmw_test_suite/detournavigator/gettilespositions.cpp index 1ad5c063d0..ced33a99f0 100644 --- a/apps/openmw_test_suite/detournavigator/gettilespositions.cpp +++ b/apps/openmw_test_suite/detournavigator/gettilespositions.cpp @@ -21,7 +21,7 @@ namespace struct DetourNavigatorGetTilesPositionsTest : Test { - Settings mSettings; + RecastSettings mSettings; std::vector mTilesPositions; CollectTilesPositions mCollect {mTilesPositions}; diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index 4e2b7758ff..e7cd0a7db0 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -1,8 +1,10 @@ #include "operators.hpp" +#include "settings.hpp" #include #include #include +#include #include #include #include @@ -31,12 +33,14 @@ namespace { using namespace testing; using namespace DetourNavigator; + using namespace DetourNavigator::Tests; struct DetourNavigatorNavigatorTest : Test { - Settings mSettings; + Settings mSettings = makeSettings(); std::unique_ptr mNavigator; const osg::Vec3f mPlayerPosition; + const std::string mWorldspace; const osg::Vec3f mAgentHalfExtents; osg::Vec3f mStart; osg::Vec3f mEnd; @@ -49,44 +53,18 @@ namespace const int mHeightfieldTileSize = ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1); const float mEndTolerance = 0; const btTransform mTransform {btMatrix3x3::getIdentity(), btVector3(256, 256, 0)}; + const ObjectTransform mObjectTransform {ESM::Position {{256, 256, 0}, {0, 0, 0}}, 0.0f}; DetourNavigatorNavigatorTest() : mPlayerPosition(256, 256, 0) + , mWorldspace("sys::default") , mAgentHalfExtents(29, 29, 66) , mStart(52, 460, 1) , mEnd(460, 52, 1) , mOut(mPath) , mStepSize(28.333332061767578125f) { - mSettings.mEnableWriteRecastMeshToFile = false; - mSettings.mEnableWriteNavMeshToFile = false; - mSettings.mEnableRecastMeshFileNameRevision = false; - mSettings.mEnableNavMeshFileNameRevision = false; - mSettings.mBorderSize = 16; - mSettings.mCellHeight = 0.2f; - mSettings.mCellSize = 0.2f; - mSettings.mDetailSampleDist = 6; - mSettings.mDetailSampleMaxError = 1; - mSettings.mMaxClimb = 34; - mSettings.mMaxSimplificationError = 1.3f; - mSettings.mMaxSlope = 49; - mSettings.mRecastScaleFactor = 0.017647058823529415f; - mSettings.mSwimHeightScale = 0.89999997615814208984375f; - mSettings.mMaxEdgeLen = 12; - mSettings.mMaxNavMeshQueryNodes = 2048; - mSettings.mMaxVertsPerPoly = 6; - mSettings.mRegionMergeSize = 20; - mSettings.mRegionMinSize = 8; - mSettings.mTileSize = 64; - mSettings.mWaitUntilMinDistanceToPlayer = std::numeric_limits::max(); - mSettings.mAsyncNavMeshUpdaterThreads = 1; - mSettings.mMaxNavMeshTilesCacheSize = 1024 * 1024; - mSettings.mMaxPolygonPathSize = 1024; - mSettings.mMaxSmoothPathSize = 1024; - mSettings.mMaxPolys = 4096; - mSettings.mMaxTilesNumber = 512; - mSettings.mMinUpdateInterval = std::chrono::milliseconds(50); - mNavigator.reset(new NavigatorImpl(mSettings)); + mNavigator.reset(new NavigatorImpl(mSettings, std::make_unique(":memory:"))); } }; @@ -258,7 +236,7 @@ namespace Vec3fEq(460, 56.66666412353515625, 1.99998295307159423828125) )) << mPath; - mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance()), mTransform); + mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance(), mObjectTransform), mTransform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -311,7 +289,7 @@ namespace mNavigator->addAgent(mAgentHalfExtents); mNavigator->addHeightfield(mCellPosition, cellSize, surface); - mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance()), mTransform); + mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance(), mObjectTransform), mTransform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -346,7 +324,7 @@ namespace compound.shape().updateChildTransform(0, btTransform(btMatrix3x3::getIdentity(), btVector3(1000, 0, 0))); - mNavigator->updateObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance()), mTransform); + mNavigator->updateObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance(), mObjectTransform), mTransform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -404,8 +382,8 @@ namespace heightfield2.shape().setLocalScaling(btVector3(128, 128, 1)); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addObject(ObjectId(&heightfield1.shape()), ObjectShapes(heightfield1.instance()), mTransform); - mNavigator->addObject(ObjectId(&heightfield2.shape()), ObjectShapes(heightfield2.instance()), mTransform); + mNavigator->addObject(ObjectId(&heightfield1.shape()), ObjectShapes(heightfield1.instance(), mObjectTransform), mTransform); + mNavigator->addObject(ObjectId(&heightfield2.shape()), ObjectShapes(heightfield2.instance(), mObjectTransform), mTransform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -494,7 +472,7 @@ namespace osg::ref_ptr instance(new Resource::BulletShapeInstance(bulletShape)); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addObject(ObjectId(instance->mCollisionShape.get()), ObjectShapes(instance), mTransform); + mNavigator->addObject(ObjectId(instance->mCollisionShape.get()), ObjectShapes(instance, mObjectTransform), mTransform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -725,7 +703,7 @@ namespace heightfield.shape().setLocalScaling(btVector3(128, 128, 1)); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addObject(ObjectId(&heightfield.shape()), ObjectShapes(heightfield.instance()), mTransform); + mNavigator->addObject(ObjectId(&heightfield.shape()), ObjectShapes(heightfield.instance(), mObjectTransform), mTransform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -733,7 +711,7 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); - mNavigator->addObject(ObjectId(&heightfield.shape()), ObjectShapes(heightfield.instance()), mTransform); + mNavigator->addObject(ObjectId(&heightfield.shape()), ObjectShapes(heightfield.instance(), mObjectTransform), mTransform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -853,7 +831,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, multiple_threads_should_lock_tiles) { mSettings.mAsyncNavMeshUpdaterThreads = 2; - mNavigator.reset(new NavigatorImpl(mSettings)); + mNavigator.reset(new NavigatorImpl(mSettings, std::make_unique(":memory:"))); const std::array heightfieldData {{ 0, 0, 0, 0, 0, @@ -876,7 +854,7 @@ namespace for (std::size_t i = 0; i < boxes.size(); ++i) { const btTransform transform(btMatrix3x3::getIdentity(), btVector3(shift.x() + i * 10, shift.y() + i * 10, i * 10)); - mNavigator->addObject(ObjectId(&boxes[i].shape()), ObjectShapes(boxes[i].instance()), transform); + mNavigator->addObject(ObjectId(&boxes[i].shape()), ObjectShapes(boxes[i].instance(), mObjectTransform), transform); } std::this_thread::sleep_for(std::chrono::microseconds(1)); @@ -884,7 +862,7 @@ namespace for (std::size_t i = 0; i < boxes.size(); ++i) { const btTransform transform(btMatrix3x3::getIdentity(), btVector3(shift.x() + i * 10 + 1, shift.y() + i * 10 + 1, i * 10 + 1)); - mNavigator->updateObject(ObjectId(&boxes[i].shape()), ObjectShapes(boxes[i].instance()), transform); + mNavigator->updateObject(ObjectId(&boxes[i].shape()), ObjectShapes(boxes[i].instance(), mObjectTransform), transform); } mNavigator->update(mPlayerPosition); @@ -930,7 +908,7 @@ namespace for (std::size_t i = 0; i < shapes.size(); ++i) { const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 32, i * 32, i * 32)); - mNavigator->addObject(ObjectId(&shapes[i].shape()), ObjectShapes(shapes[i].instance()), transform); + mNavigator->addObject(ObjectId(&shapes[i].shape()), ObjectShapes(shapes[i].instance(), mObjectTransform), transform); } mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -939,7 +917,7 @@ namespace for (std::size_t i = 0; i < shapes.size(); ++i) { const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 32 + 1, i * 32 + 1, i * 32 + 1)); - mNavigator->updateObject(ObjectId(&shapes[i].shape()), ObjectShapes(shapes[i].instance()), transform); + mNavigator->updateObject(ObjectId(&shapes[i].shape()), ObjectShapes(shapes[i].instance(), mObjectTransform), transform); } mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -947,7 +925,7 @@ namespace for (std::size_t i = 0; i < shapes.size(); ++i) { const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 32 + 2, i * 32 + 2, i * 32 + 2)); - mNavigator->updateObject(ObjectId(&shapes[i].shape()), ObjectShapes(shapes[i].instance()), transform); + mNavigator->updateObject(ObjectId(&shapes[i].shape()), ObjectShapes(shapes[i].instance(), mObjectTransform), transform); } mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -1001,15 +979,15 @@ namespace mNavigator->addAgent(mAgentHalfExtents); mNavigator->addHeightfield(mCellPosition, cellSize, surface); - mNavigator->addObject(ObjectId(&oscillatingBox.shape()), ObjectShapes(oscillatingBox.instance()), + mNavigator->addObject(ObjectId(&oscillatingBox.shape()), ObjectShapes(oscillatingBox.instance(), mObjectTransform), btTransform(btMatrix3x3::getIdentity(), oscillatingBoxShapePosition)); // add this box to make navmesh bound box independent from oscillatingBoxShape rotations - mNavigator->addObject(ObjectId(&borderBox.shape()), ObjectShapes(borderBox.instance()), + mNavigator->addObject(ObjectId(&borderBox.shape()), ObjectShapes(borderBox.instance(), mObjectTransform), btTransform(btMatrix3x3::getIdentity(), oscillatingBoxShapePosition + btVector3(0, 0, 200))); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); - const Version expectedVersion {1, 1}; + const Version expectedVersion {1, 4}; const auto navMeshes = mNavigator->getNavMeshes(); ASSERT_EQ(navMeshes.size(), 1); @@ -1019,7 +997,7 @@ namespace { const btTransform transform(btQuaternion(btVector3(0, 0, 1), n * 2 * osg::PI / 10), oscillatingBoxShapePosition); - mNavigator->updateObject(ObjectId(&oscillatingBox.shape()), ObjectShapes(oscillatingBox.instance()), transform); + mNavigator->updateObject(ObjectId(&oscillatingBox.shape()), ObjectShapes(oscillatingBox.instance(), mObjectTransform), transform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); } @@ -1085,7 +1063,7 @@ namespace mNavigator->addAgent(mAgentHalfExtents); mNavigator->addHeightfield(mCellPosition, cellSize, surface); - mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance()), mTransform); + mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance(), mObjectTransform), mTransform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); @@ -1124,7 +1102,7 @@ namespace mNavigator->addAgent(mAgentHalfExtents); mNavigator->addHeightfield(mCellPosition, cellSize, surface); - mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance()), mTransform); + mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance(), mObjectTransform), mTransform); mNavigator->update(mPlayerPosition); mNavigator->wait(mListener, WaitConditionType::allJobsDone); diff --git a/apps/openmw_test_suite/detournavigator/navmeshdb.cpp b/apps/openmw_test_suite/detournavigator/navmeshdb.cpp new file mode 100644 index 0000000000..feadc2f59d --- /dev/null +++ b/apps/openmw_test_suite/detournavigator/navmeshdb.cpp @@ -0,0 +1,112 @@ +#include "generate.hpp" + +#include +#include + +#include + +#include +#include + +#include +#include + +namespace +{ + using namespace testing; + using namespace DetourNavigator; + using namespace DetourNavigator::Tests; + + struct Tile + { + std::string mWorldspace; + TilePosition mTilePosition; + std::vector mInput; + std::vector mData; + }; + + struct DetourNavigatorNavMeshDbTest : Test + { + NavMeshDb mDb {":memory:"}; + std::minstd_rand mRandom; + + std::vector generateData() + { + std::vector data(32); + generateRange(data.begin(), data.end(), mRandom); + return data; + } + + Tile insertTile(TileId tileId, TileVersion version) + { + std::string worldspace = "sys::default"; + const TilePosition tilePosition {3, 4}; + std::vector input = generateData(); + std::vector data = generateData(); + EXPECT_EQ(mDb.insertTile(tileId, worldspace, tilePosition, version, input, data), 1); + return {std::move(worldspace), tilePosition, std::move(input), std::move(data)}; + } + }; + + TEST_F(DetourNavigatorNavMeshDbTest, get_max_tile_id_for_empty_db_should_return_zero) + { + EXPECT_EQ(mDb.getMaxTileId(), TileId {0}); + } + + TEST_F(DetourNavigatorNavMeshDbTest, inserted_tile_should_be_found_by_key) + { + const TileId tileId {146}; + const TileVersion version {1}; + const auto [worldspace, tilePosition, input, data] = insertTile(tileId, version); + const auto result = mDb.findTile(worldspace, tilePosition, input); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->mTileId, tileId); + EXPECT_EQ(result->mVersion, version); + } + + TEST_F(DetourNavigatorNavMeshDbTest, inserted_tile_should_change_max_tile_id) + { + insertTile(TileId {53}, TileVersion {1}); + EXPECT_EQ(mDb.getMaxTileId(), TileId {53}); + } + + TEST_F(DetourNavigatorNavMeshDbTest, updated_tile_should_change_data) + { + const TileId tileId {13}; + const TileVersion version {1}; + auto [worldspace, tilePosition, input, data] = insertTile(tileId, version); + generateRange(data.begin(), data.end(), mRandom); + ASSERT_EQ(mDb.updateTile(tileId, version, data), 1); + const auto row = mDb.getTileData(worldspace, tilePosition, input); + ASSERT_TRUE(row.has_value()); + EXPECT_EQ(row->mTileId, tileId); + EXPECT_EQ(row->mVersion, version); + ASSERT_FALSE(row->mData.empty()); + EXPECT_EQ(row->mData, data); + } + + TEST_F(DetourNavigatorNavMeshDbTest, on_inserted_duplicate_should_throw_exception) + { + const TileId tileId {53}; + const TileVersion version {1}; + const std::string worldspace = "sys::default"; + const TilePosition tilePosition {3, 4}; + const std::vector input = generateData(); + const std::vector data = generateData(); + ASSERT_EQ(mDb.insertTile(tileId, worldspace, tilePosition, version, input, data), 1); + EXPECT_THROW(mDb.insertTile(tileId, worldspace, tilePosition, version, input, data), std::runtime_error); + } + + TEST_F(DetourNavigatorNavMeshDbTest, inserted_duplicate_leaves_db_in_correct_state) + { + const TileId tileId {53}; + const TileVersion version {1}; + const std::string worldspace = "sys::default"; + const TilePosition tilePosition {3, 4}; + const std::vector input = generateData(); + const std::vector data = generateData(); + ASSERT_EQ(mDb.insertTile(tileId, worldspace, tilePosition, version, input, data), 1); + EXPECT_THROW(mDb.insertTile(tileId, worldspace, tilePosition, version, input, data), std::runtime_error); + EXPECT_NO_THROW(insertTile(TileId {54}, version)); + } +} diff --git a/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp b/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp index c56be440e3..cbd68e0fe1 100644 --- a/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp +++ b/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp @@ -1,4 +1,5 @@ #include "operators.hpp" +#include "generate.hpp" #include #include @@ -21,23 +22,17 @@ namespace { using namespace testing; using namespace DetourNavigator; + using namespace DetourNavigator::Tests; - void* permRecastAlloc(int size) - { - void* result = rcAlloc(static_cast(size), RC_ALLOC_PERM); - if (result == nullptr) - throw std::bad_alloc(); - return result; - } - - template - void generate(T*& values, int size) + template + void generateRecastArray(T*& values, int size, Random& random) { values = static_cast(permRecastAlloc(size * sizeof(T))); - std::generate_n(values, static_cast(size), [] { return static_cast(std::rand()); }); + generateRange(values, values + static_cast(size), random); } - void generate(rcPolyMesh& value, int size) + template + void generate(rcPolyMesh& value, int size, Random& random) { value.nverts = size; value.maxpolys = size; @@ -45,88 +40,49 @@ namespace value.npolys = size; rcVcopy(value.bmin, osg::Vec3f(-1, -2, -3).ptr()); rcVcopy(value.bmax, osg::Vec3f(3, 2, 1).ptr()); - value.cs = 1.0f / (std::rand() % 999 + 1); - value.ch = 1.0f / (std::rand() % 999 + 1); - value.borderSize = std::rand(); - value.maxEdgeError = 1.0f / (std::rand() % 999 + 1); - generate(value.verts, getVertsLength(value)); - generate(value.polys, getPolysLength(value)); - generate(value.regs, getRegsLength(value)); - generate(value.flags, getFlagsLength(value)); - generate(value.areas, getAreasLength(value)); + generateValue(value.cs, random); + generateValue(value.ch, random); + generateValue(value.borderSize, random); + generateValue(value.maxEdgeError, random); + generateRecastArray(value.verts, getVertsLength(value), random); + generateRecastArray(value.polys, getPolysLength(value), random); + generateRecastArray(value.regs, getRegsLength(value), random); + generateRecastArray(value.flags, getFlagsLength(value), random); + generateRecastArray(value.areas, getAreasLength(value), random); } - void generate(rcPolyMeshDetail& value, int size) + template + void generate(rcPolyMeshDetail& value, int size, Random& random) { value.nmeshes = size; value.nverts = size; value.ntris = size; - generate(value.meshes, getMeshesLength(value)); - generate(value.verts, getVertsLength(value)); - generate(value.tris, getTrisLength(value)); + generateRecastArray(value.meshes, getMeshesLength(value), random); + generateRecastArray(value.verts, getVertsLength(value), random); + generateRecastArray(value.tris, getTrisLength(value), random); } - void generate(PreparedNavMeshData& value, int size) + template + void generate(PreparedNavMeshData& value, int size, Random& random) { - value.mUserId = std::rand(); - value.mCellHeight = 1.0f / (std::rand() % 999 + 1); - value.mCellSize = 1.0f / (std::rand() % 999 + 1); - generate(value.mPolyMesh, size); - generate(value.mPolyMeshDetail, size); + generateValue(value.mUserId, random); + generateValue(value.mCellHeight, random); + generateValue(value.mCellSize, random); + generate(value.mPolyMesh, size, random); + generate(value.mPolyMeshDetail, size, random); } std::unique_ptr makePeparedNavMeshData(int size) { + std::minstd_rand random; auto result = std::make_unique(); - generate(*result, size); + generate(*result, size, random); return result; } - template - void clone(const T* src, T*& dst, std::size_t size) - { - dst = static_cast(permRecastAlloc(static_cast(size) * sizeof(T))); - std::memcpy(dst, src, size * sizeof(T)); - } - - void clone(const rcPolyMesh& src, rcPolyMesh& dst) - { - dst.nverts = src.nverts; - dst.npolys = src.npolys; - dst.maxpolys = src.maxpolys; - dst.nvp = src.nvp; - rcVcopy(dst.bmin, src.bmin); - rcVcopy(dst.bmax, src.bmax); - dst.cs = src.cs; - dst.ch = src.ch; - dst.borderSize = src.borderSize; - dst.maxEdgeError = src.maxEdgeError; - clone(src.verts, dst.verts, getVertsLength(dst)); - clone(src.polys, dst.polys, getPolysLength(dst)); - clone(src.regs, dst.regs, getRegsLength(dst)); - clone(src.flags, dst.flags, getFlagsLength(dst)); - clone(src.areas, dst.areas, getAreasLength(dst)); - } - - void clone(const rcPolyMeshDetail& src, rcPolyMeshDetail& dst) - { - dst.nmeshes = src.nmeshes; - dst.nverts = src.nverts; - dst.ntris = src.ntris; - clone(src.meshes, dst.meshes, getMeshesLength(dst)); - clone(src.verts, dst.verts, getVertsLength(dst)); - clone(src.tris, dst.tris, getTrisLength(dst)); - } - std::unique_ptr clone(const PreparedNavMeshData& value) { - auto result = std::make_unique(); - result->mUserId = value.mUserId; - result->mCellHeight = value.mCellHeight; - result->mCellSize = value.mCellSize; - clone(value.mPolyMesh, result->mPolyMesh); - clone(value.mPolyMeshDetail, result->mPolyMeshDetail); - return result; + return std::make_unique(value); } Mesh makeMesh() @@ -147,7 +103,8 @@ namespace const std::vector mWater {}; const std::vector mHeightfields {}; const std::vector mFlatHeightfields {}; - const RecastMesh mRecastMesh {mGeneration, mRevision, mMesh, mWater, mHeightfields, mFlatHeightfields}; + const std::vector mSources {}; + const RecastMesh mRecastMesh {mGeneration, mRevision, mMesh, mWater, mHeightfields, mFlatHeightfields, mSources}; std::unique_ptr mPreparedNavMeshData {makePeparedNavMeshData(3)}; const std::size_t mRecastMeshSize = sizeof(mRecastMesh) + getSize(mRecastMesh); @@ -235,7 +192,7 @@ namespace const std::size_t maxSize = 1; NavMeshTilesCache cache(maxSize); const std::vector water(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); - const RecastMesh unexistentRecastMesh {mGeneration, mRevision, mMesh, water, mHeightfields, mFlatHeightfields}; + const RecastMesh unexistentRecastMesh(mGeneration, mRevision, mMesh, water, mHeightfields, mFlatHeightfields, mSources); cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, std::move(mPreparedNavMeshData)); EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, unexistentRecastMesh)); @@ -247,7 +204,7 @@ namespace NavMeshTilesCache cache(maxSize); const std::vector water(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); - const RecastMesh anotherRecastMesh {mGeneration, mRevision, mMesh, water, mHeightfields, mFlatHeightfields}; + const RecastMesh anotherRecastMesh(mGeneration, mRevision, mMesh, water, mHeightfields, mFlatHeightfields, mSources); auto anotherPreparedNavMeshData = makePeparedNavMeshData(3); const auto copy = clone(*anotherPreparedNavMeshData); @@ -265,7 +222,7 @@ namespace NavMeshTilesCache cache(maxSize); const std::vector water(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); - const RecastMesh anotherRecastMesh {mGeneration, mRevision, mMesh, water, mHeightfields, mFlatHeightfields}; + const RecastMesh anotherRecastMesh(mGeneration, mRevision, mMesh, water, mHeightfields, mFlatHeightfields, mSources); auto anotherPreparedNavMeshData = makePeparedNavMeshData(3); const auto value = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, @@ -281,13 +238,13 @@ namespace const auto copy = clone(*mPreparedNavMeshData); const std::vector leastRecentlySetWater(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); - const RecastMesh leastRecentlySetRecastMesh {mGeneration, mRevision, mMesh, leastRecentlySetWater, - mHeightfields, mFlatHeightfields}; + const RecastMesh leastRecentlySetRecastMesh(mGeneration, mRevision, mMesh, leastRecentlySetWater, + mHeightfields, mFlatHeightfields, mSources); auto leastRecentlySetData = makePeparedNavMeshData(3); const std::vector mostRecentlySetWater(1, CellWater {osg::Vec2i(), Water {2, 0.0f}}); - const RecastMesh mostRecentlySetRecastMesh {mGeneration, mRevision, mMesh, mostRecentlySetWater, - mHeightfields, mFlatHeightfields}; + const RecastMesh mostRecentlySetRecastMesh(mGeneration, mRevision, mMesh, mostRecentlySetWater, + mHeightfields, mFlatHeightfields, mSources); auto mostRecentlySetData = makePeparedNavMeshData(3); ASSERT_TRUE(cache.set(mAgentHalfExtents, mTilePosition, leastRecentlySetRecastMesh, @@ -309,14 +266,14 @@ namespace NavMeshTilesCache cache(maxSize); const std::vector leastRecentlyUsedWater(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); - const RecastMesh leastRecentlyUsedRecastMesh {mGeneration, mRevision, mMesh, leastRecentlyUsedWater, - mHeightfields, mFlatHeightfields}; + const RecastMesh leastRecentlyUsedRecastMesh(mGeneration, mRevision, mMesh, leastRecentlyUsedWater, + mHeightfields, mFlatHeightfields, mSources); auto leastRecentlyUsedData = makePeparedNavMeshData(3); const auto leastRecentlyUsedCopy = clone(*leastRecentlyUsedData); const std::vector mostRecentlyUsedWater(1, CellWater {osg::Vec2i(), Water {2, 0.0f}}); - const RecastMesh mostRecentlyUsedRecastMesh {mGeneration, mRevision, mMesh, mostRecentlyUsedWater, - mHeightfields, mFlatHeightfields}; + const RecastMesh mostRecentlyUsedRecastMesh(mGeneration, mRevision, mMesh, mostRecentlyUsedWater, + mHeightfields, mFlatHeightfields, mSources); auto mostRecentlyUsedData = makePeparedNavMeshData(3); const auto mostRecentlyUsedCopy = clone(*mostRecentlyUsedData); @@ -350,8 +307,8 @@ namespace NavMeshTilesCache cache(maxSize); const std::vector water(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); - const RecastMesh tooLargeRecastMesh {mGeneration, mRevision, mMesh, water, - mHeightfields, mFlatHeightfields}; + const RecastMesh tooLargeRecastMesh(mGeneration, mRevision, mMesh, water, + mHeightfields, mFlatHeightfields, mSources); auto tooLargeData = makePeparedNavMeshData(10); cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, std::move(mPreparedNavMeshData)); @@ -365,13 +322,13 @@ namespace NavMeshTilesCache cache(maxSize); const std::vector anotherWater(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); - const RecastMesh anotherRecastMesh {mGeneration, mRevision, mMesh, anotherWater, - mHeightfields, mFlatHeightfields}; + const RecastMesh anotherRecastMesh(mGeneration, mRevision, mMesh, anotherWater, + mHeightfields, mFlatHeightfields, mSources); auto anotherData = makePeparedNavMeshData(3); const std::vector tooLargeWater(1, CellWater {osg::Vec2i(), Water {2, 0.0f}}); - const RecastMesh tooLargeRecastMesh {mGeneration, mRevision, mMesh, tooLargeWater, - mHeightfields, mFlatHeightfields}; + const RecastMesh tooLargeRecastMesh(mGeneration, mRevision, mMesh, tooLargeWater, + mHeightfields, mFlatHeightfields, mSources); auto tooLargeData = makePeparedNavMeshData(10); const auto value = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, @@ -391,7 +348,7 @@ namespace NavMeshTilesCache cache(maxSize); const std::vector water(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); - const RecastMesh anotherRecastMesh {mGeneration, mRevision, mMesh, water, mHeightfields, mFlatHeightfields}; + const RecastMesh anotherRecastMesh(mGeneration, mRevision, mMesh, water, mHeightfields, mFlatHeightfields, mSources); auto anotherData = makePeparedNavMeshData(3); const auto firstCopy = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, std::move(mPreparedNavMeshData)); @@ -410,7 +367,7 @@ namespace NavMeshTilesCache cache(maxSize); const std::vector water(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); - const RecastMesh anotherRecastMesh {mGeneration, mRevision, mMesh, water, mHeightfields, mFlatHeightfields}; + const RecastMesh anotherRecastMesh(mGeneration, mRevision, mMesh, water, mHeightfields, mFlatHeightfields, mSources); auto anotherData = makePeparedNavMeshData(3); cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, std::move(mPreparedNavMeshData)); diff --git a/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp b/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp index ad28bf4053..36e0287461 100644 --- a/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp +++ b/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp @@ -48,35 +48,6 @@ namespace DetourNavigator }; return tie(lhs) == tie(rhs); } - - static inline std::ostream& operator<<(std::ostream& s, const Water& v) - { - return s << "Water {" << v.mCellSize << ", " << v.mLevel << "}"; - } - - static inline std::ostream& operator<<(std::ostream& s, const CellWater& v) - { - return s << "CellWater {" << v.mCellPosition << ", " << v.mWater << "}"; - } - - static inline std::ostream& operator<<(std::ostream& s, const FlatHeightfield& v) - { - return s << "FlatHeightfield {" << v.mCellPosition << ", " << v.mCellSize << ", " << v.mHeight << "}"; - } - - static inline std::ostream& operator<<(std::ostream& s, const Heightfield& v) - { - s << "Heightfield {.mCellPosition=" << v.mCellPosition - << ", .mCellSize=" << v.mCellSize - << ", .mLength=" << static_cast(v.mLength) - << ", .mMinHeight=" << v.mMinHeight - << ", .mMaxHeight=" << v.mMaxHeight - << ", .mHeights={"; - for (float h : v.mHeights) - s << h << ", "; - s << "}"; - return s << ", .mOriginalSize=" << v.mOriginalSize << "}"; - } } namespace @@ -89,6 +60,8 @@ namespace TileBounds mBounds; const std::size_t mGeneration = 0; const std::size_t mRevision = 0; + const osg::ref_ptr mSource {nullptr}; + const ObjectTransform mObjectTransform {ESM::Position {{0, 0, 0}, {0, 0, 0}}, 0.0f}; DetourNavigatorRecastMeshBuilderTest() { @@ -115,7 +88,8 @@ namespace btBvhTriangleMeshShape shape(&mesh, true); RecastMeshBuilder builder(mBounds); - builder.addObject(static_cast(shape), btTransform::getIdentity(), AreaType_ground); + builder.addObject(static_cast(shape), btTransform::getIdentity(), + AreaType_ground, mSource, mObjectTransform); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector({ -1, -1, 0, @@ -135,7 +109,7 @@ namespace builder.addObject( static_cast(shape), btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)), - AreaType_ground + AreaType_ground, mSource, mObjectTransform ); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector({ @@ -152,7 +126,8 @@ namespace const std::array heightfieldData {{0, 0, 0, 0}}; btHeightfieldTerrainShape shape(2, 2, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); RecastMeshBuilder builder(mBounds); - builder.addObject(static_cast(shape), btTransform::getIdentity(), AreaType_ground); + builder.addObject(static_cast(shape), btTransform::getIdentity(), + AreaType_ground, mSource, mObjectTransform); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector({ -0.5, -0.5, 0, @@ -168,7 +143,8 @@ namespace { btBoxShape shape(btVector3(1, 1, 2)); RecastMeshBuilder builder(mBounds); - builder.addObject(static_cast(shape), btTransform::getIdentity(), AreaType_ground); + builder.addObject(static_cast(shape), btTransform::getIdentity(), + AreaType_ground, mSource, mObjectTransform); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector({ -1, -1, -2, @@ -214,7 +190,7 @@ namespace builder.addObject( static_cast(shape), btTransform::getIdentity(), - AreaType_ground + AreaType_ground, mSource, mObjectTransform ); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector({ @@ -261,7 +237,7 @@ namespace builder.addObject( static_cast(shape), btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)), - AreaType_ground + AreaType_ground, mSource, mObjectTransform ); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector({ @@ -285,7 +261,7 @@ namespace builder.addObject( static_cast(shape), btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)), - AreaType_ground + AreaType_ground, mSource, mObjectTransform ); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector({ @@ -307,7 +283,7 @@ namespace builder.addObject( static_cast(shape), btTransform::getIdentity(), - AreaType_ground + AreaType_ground, mSource, mObjectTransform ); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector({ @@ -334,7 +310,7 @@ namespace builder.addObject( static_cast(shape), btTransform::getIdentity(), - AreaType_ground + AreaType_ground, mSource, mObjectTransform ); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector({ @@ -359,7 +335,7 @@ namespace static_cast(shape), btTransform(btQuaternion(btVector3(1, 0, 0), static_cast(-osg::PI_4))), - AreaType_ground + AreaType_ground, mSource, mObjectTransform ); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_THAT(recastMesh->getMesh().getVertices(), Pointwise(FloatNear(1e-5), std::vector({ @@ -384,7 +360,7 @@ namespace static_cast(shape), btTransform(btQuaternion(btVector3(0, 1, 0), static_cast(osg::PI_4))), - AreaType_ground + AreaType_ground, mSource, mObjectTransform ); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_THAT(recastMesh->getMesh().getVertices(), Pointwise(FloatNear(1e-5), std::vector({ @@ -409,7 +385,7 @@ namespace static_cast(shape), btTransform(btQuaternion(btVector3(0, 0, 1), static_cast(osg::PI_4))), - AreaType_ground + AreaType_ground, mSource, mObjectTransform ); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_THAT(recastMesh->getMesh().getVertices(), Pointwise(FloatNear(1e-5), std::vector({ @@ -433,12 +409,12 @@ namespace builder.addObject( static_cast(shape1), btTransform::getIdentity(), - AreaType_ground + AreaType_ground, mSource, mObjectTransform ); builder.addObject( static_cast(shape2), btTransform::getIdentity(), - AreaType_null + AreaType_null, mSource, mObjectTransform ); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector({ @@ -471,7 +447,7 @@ namespace btBvhTriangleMeshShape shape(&mesh, true); RecastMeshBuilder builder(mBounds); - builder.addObject(static_cast(shape), btTransform::getIdentity(), AreaType_ground); + builder.addObject(static_cast(shape), btTransform::getIdentity(), AreaType_ground, mSource, mObjectTransform); const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector({ -1, -1, 0, diff --git a/apps/openmw_test_suite/detournavigator/recastmeshobject.cpp b/apps/openmw_test_suite/detournavigator/recastmeshobject.cpp index 7751d5220c..ff0d3e519c 100644 --- a/apps/openmw_test_suite/detournavigator/recastmeshobject.cpp +++ b/apps/openmw_test_suite/detournavigator/recastmeshobject.cpp @@ -1,6 +1,7 @@ #include "operators.hpp" #include +#include #include #include @@ -15,10 +16,11 @@ namespace struct DetourNavigatorRecastMeshObjectTest : Test { btBoxShape mBoxShapeImpl {btVector3(1, 2, 3)}; - CollisionShape mBoxShape {nullptr, mBoxShapeImpl}; + const ObjectTransform mObjectTransform {ESM::Position {{1, 2, 3}, {1, 2, 3}}, 0.5f}; + CollisionShape mBoxShape {nullptr, mBoxShapeImpl, mObjectTransform}; btCompoundShape mCompoundShapeImpl {true}; - CollisionShape mCompoundShape {nullptr, mCompoundShapeImpl}; - btTransform mTransform {btQuaternion(btVector3(1, 2, 3), 1), btVector3(1, 2, 3)}; + CollisionShape mCompoundShape {nullptr, mCompoundShapeImpl, mObjectTransform}; + btTransform mTransform {Misc::Convert::makeBulletTransform(mObjectTransform.mPosition)}; DetourNavigatorRecastMeshObjectTest() { diff --git a/apps/openmw_test_suite/detournavigator/settings.hpp b/apps/openmw_test_suite/detournavigator/settings.hpp new file mode 100644 index 0000000000..dc37dc7550 --- /dev/null +++ b/apps/openmw_test_suite/detournavigator/settings.hpp @@ -0,0 +1,50 @@ +#ifndef OPENMW_TEST_SUITE_DETOURNAVIGATOR_SETTINGS_H +#define OPENMW_TEST_SUITE_DETOURNAVIGATOR_SETTINGS_H + +#include + +#include +#include + +namespace DetourNavigator +{ + namespace Tests + { + inline Settings makeSettings() + { + Settings result; + result.mEnableWriteRecastMeshToFile = false; + result.mEnableWriteNavMeshToFile = false; + result.mEnableRecastMeshFileNameRevision = false; + result.mEnableNavMeshFileNameRevision = false; + result.mRecast.mBorderSize = 16; + result.mRecast.mCellHeight = 0.2f; + result.mRecast.mCellSize = 0.2f; + result.mRecast.mDetailSampleDist = 6; + result.mRecast.mDetailSampleMaxError = 1; + result.mRecast.mMaxClimb = 34; + result.mRecast.mMaxSimplificationError = 1.3f; + result.mRecast.mMaxSlope = 49; + result.mRecast.mRecastScaleFactor = 0.017647058823529415f; + result.mRecast.mSwimHeightScale = 0.89999997615814208984375f; + result.mRecast.mMaxEdgeLen = 12; + result.mDetour.mMaxNavMeshQueryNodes = 2048; + result.mRecast.mMaxVertsPerPoly = 6; + result.mRecast.mRegionMergeArea = 400; + result.mRecast.mRegionMinArea = 64; + result.mRecast.mTileSize = 64; + result.mWaitUntilMinDistanceToPlayer = std::numeric_limits::max(); + result.mAsyncNavMeshUpdaterThreads = 1; + result.mMaxNavMeshTilesCacheSize = 1024 * 1024; + result.mDetour.mMaxPolygonPathSize = 1024; + result.mDetour.mMaxSmoothPathSize = 1024; + result.mDetour.mMaxPolys = 4096; + result.mMaxTilesNumber = 512; + result.mMinUpdateInterval = std::chrono::milliseconds(50); + result.mWriteToNavMeshDb = true; + return result; + } + } +} + +#endif diff --git a/apps/openmw_test_suite/detournavigator/settingsutils.cpp b/apps/openmw_test_suite/detournavigator/settingsutils.cpp index ffed64ab81..f06f3b3e32 100644 --- a/apps/openmw_test_suite/detournavigator/settingsutils.cpp +++ b/apps/openmw_test_suite/detournavigator/settingsutils.cpp @@ -11,7 +11,7 @@ namespace struct DetourNavigatorGetTilePositionTest : Test { - Settings mSettings; + RecastSettings mSettings; DetourNavigatorGetTilePositionTest() { @@ -47,7 +47,7 @@ namespace struct DetourNavigatorMakeTileBoundsTest : Test { - Settings mSettings; + RecastSettings mSettings; DetourNavigatorMakeTileBoundsTest() { diff --git a/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp index 3667e946e0..c44ebc5155 100644 --- a/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp @@ -15,8 +15,11 @@ namespace struct DetourNavigatorTileCachedRecastMeshManagerTest : Test { - Settings mSettings; + RecastSettings mSettings; std::vector mChangedTiles; + const ObjectTransform mObjectTransform {ESM::Position {{0, 0, 0}, {0, 0, 0}}, 0.0f}; + const osg::ref_ptr mShape = new Resource::BulletShape; + const osg::ref_ptr mInstance = new Resource::BulletShapeInstance(mShape); DetourNavigatorTileCachedRecastMeshManagerTest() { @@ -35,7 +38,7 @@ namespace TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_empty_should_return_nullptr) { TileCachedRecastMeshManager manager(mSettings); - EXPECT_EQ(manager.getMesh(TilePosition(0, 0)), nullptr); + EXPECT_EQ(manager.getMesh("worldspace", TilePosition(0, 0)), nullptr); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_revision_for_empty_should_return_zero) @@ -56,7 +59,7 @@ namespace { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); - const CollisionShape shape(nullptr, boxShape); + const CollisionShape shape(mInstance, boxShape, mObjectTransform); EXPECT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); } @@ -64,7 +67,7 @@ namespace { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); - const CollisionShape shape(nullptr, boxShape); + const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); EXPECT_FALSE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); } @@ -72,12 +75,13 @@ namespace TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_should_add_tiles) { TileCachedRecastMeshManager manager(mSettings); + manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); - const CollisionShape shape(nullptr, boxShape); + const CollisionShape shape(mInstance, boxShape, mObjectTransform); ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); for (int x = -1; x < 1; ++x) for (int y = -1; y < 1; ++y) - ASSERT_NE(manager.getMesh(TilePosition(x, y)), nullptr); + ASSERT_NE(manager.getMesh("worldspace", TilePosition(x, y)), nullptr); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, update_object_for_changed_object_should_return_changed_tiles) @@ -85,7 +89,7 @@ namespace TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); - const CollisionShape shape(nullptr, boxShape); + const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground); EXPECT_TRUE(manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [&] (const auto& v) { onChangedTile(v); })); @@ -100,7 +104,7 @@ namespace { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); - const CollisionShape shape(nullptr, boxShape); + const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); EXPECT_FALSE(manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [&] (const auto& v) { onChangedTile(v); })); @@ -110,90 +114,98 @@ namespace TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_after_add_object_should_return_recast_mesh_for_each_used_tile) { TileCachedRecastMeshManager manager(mSettings); + manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); - const CollisionShape shape(nullptr, boxShape); + const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); - EXPECT_NE(manager.getMesh(TilePosition(-1, -1)), nullptr); - EXPECT_NE(manager.getMesh(TilePosition(-1, 0)), nullptr); - EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); - EXPECT_NE(manager.getMesh(TilePosition(0, 0)), nullptr); + EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr); + EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr); + EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr); + EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, 0)), nullptr); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_after_add_object_should_return_nullptr_for_unused_tile) { TileCachedRecastMeshManager manager(mSettings); + manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); - const CollisionShape shape(nullptr, boxShape); + const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); - EXPECT_EQ(manager.getMesh(TilePosition(1, 0)), nullptr); + EXPECT_EQ(manager.getMesh("worldspace", TilePosition(1, 0)), nullptr); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_moved_object_should_return_recast_mesh_for_each_used_tile) { TileCachedRecastMeshManager manager(mSettings); + manager.setWorldspace("worldspace"); + const btBoxShape boxShape(btVector3(20, 20, 100)); const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); - const CollisionShape shape(nullptr, boxShape); + const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground); - EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); - EXPECT_NE(manager.getMesh(TilePosition(0, 0)), nullptr); - EXPECT_NE(manager.getMesh(TilePosition(1, 0)), nullptr); - EXPECT_NE(manager.getMesh(TilePosition(1, -1)), nullptr); + EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr); + EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, 0)), nullptr); + EXPECT_NE(manager.getMesh("worldspace", TilePosition(1, 0)), nullptr); + EXPECT_NE(manager.getMesh("worldspace", TilePosition(1, -1)), nullptr); manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); - EXPECT_NE(manager.getMesh(TilePosition(-1, -1)), nullptr); - EXPECT_NE(manager.getMesh(TilePosition(-1, 0)), nullptr); - EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); - EXPECT_NE(manager.getMesh(TilePosition(0, 0)), nullptr); + EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr); + EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr); + EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr); + EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, 0)), nullptr); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_moved_object_should_return_nullptr_for_unused_tile) { TileCachedRecastMeshManager manager(mSettings); + manager.setWorldspace("worldspace"); + const btBoxShape boxShape(btVector3(20, 20, 100)); const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); - const CollisionShape shape(nullptr, boxShape); + const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground); - EXPECT_EQ(manager.getMesh(TilePosition(-1, -1)), nullptr); - EXPECT_EQ(manager.getMesh(TilePosition(-1, 0)), nullptr); + EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr); + EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr); manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); - EXPECT_EQ(manager.getMesh(TilePosition(1, 0)), nullptr); - EXPECT_EQ(manager.getMesh(TilePosition(1, -1)), nullptr); + EXPECT_EQ(manager.getMesh("worldspace", TilePosition(1, 0)), nullptr); + EXPECT_EQ(manager.getMesh("worldspace", TilePosition(1, -1)), nullptr); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_removed_object_should_return_nullptr_for_all_previously_used_tiles) { TileCachedRecastMeshManager manager(mSettings); + manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); - const CollisionShape shape(nullptr, boxShape); + const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); manager.removeObject(ObjectId(&boxShape)); - EXPECT_EQ(manager.getMesh(TilePosition(-1, -1)), nullptr); - EXPECT_EQ(manager.getMesh(TilePosition(-1, 0)), nullptr); - EXPECT_EQ(manager.getMesh(TilePosition(0, -1)), nullptr); - EXPECT_EQ(manager.getMesh(TilePosition(0, 0)), nullptr); + EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr); + EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr); + EXPECT_EQ(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr); + EXPECT_EQ(manager.getMesh("worldspace", TilePosition(0, 0)), nullptr); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_not_changed_object_after_update_should_return_recast_mesh_for_same_tiles) { TileCachedRecastMeshManager manager(mSettings); + manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); - const CollisionShape shape(nullptr, boxShape); + const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); - EXPECT_NE(manager.getMesh(TilePosition(-1, -1)), nullptr); - EXPECT_NE(manager.getMesh(TilePosition(-1, 0)), nullptr); - EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); - EXPECT_NE(manager.getMesh(TilePosition(0, 0)), nullptr); + EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr); + EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr); + EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr); + EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, 0)), nullptr); manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); - EXPECT_NE(manager.getMesh(TilePosition(-1, -1)), nullptr); - EXPECT_NE(manager.getMesh(TilePosition(-1, 0)), nullptr); - EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); - EXPECT_NE(manager.getMesh(TilePosition(0, 0)), nullptr); + EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr); + EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr); + EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr); + EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, 0)), nullptr); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_revision_after_add_object_new_should_return_incremented_value) @@ -201,7 +213,7 @@ namespace TileCachedRecastMeshManager manager(mSettings); const auto initialRevision = manager.getRevision(); const btBoxShape boxShape(btVector3(20, 20, 100)); - const CollisionShape shape(nullptr, boxShape); + const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); EXPECT_EQ(manager.getRevision(), initialRevision + 1); } @@ -210,7 +222,7 @@ namespace { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); - const CollisionShape shape(nullptr, boxShape); + const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); const auto beforeAddRevision = manager.getRevision(); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); @@ -222,7 +234,7 @@ namespace TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); - const CollisionShape shape(nullptr, boxShape); + const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground); const auto beforeUpdateRevision = manager.getRevision(); manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); @@ -232,8 +244,9 @@ namespace TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_revision_after_update_not_changed_object_should_return_same_value) { TileCachedRecastMeshManager manager(mSettings); + manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); - const CollisionShape shape(nullptr, boxShape); + const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); const auto beforeUpdateRevision = manager.getRevision(); manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); @@ -244,7 +257,7 @@ namespace { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); - const CollisionShape shape(nullptr, boxShape); + const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); const auto beforeRemoveRevision = manager.getRevision(); manager.removeObject(ObjectId(&boxShape)); @@ -270,26 +283,28 @@ namespace TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_water_for_not_max_int_should_add_new_tiles) { TileCachedRecastMeshManager manager(mSettings); + manager.setWorldspace("worldspace"); const osg::Vec2i cellPosition(0, 0); const int cellSize = 8192; ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f)); for (int x = -1; x < 12; ++x) for (int y = -1; y < 12; ++y) - ASSERT_NE(manager.getMesh(TilePosition(x, y)), nullptr); + ASSERT_NE(manager.getMesh("worldspace", TilePosition(x, y)), nullptr); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_water_for_max_int_should_not_add_new_tiles) { TileCachedRecastMeshManager manager(mSettings); + manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); - const CollisionShape shape(nullptr, boxShape); + const CollisionShape shape(mInstance, boxShape, mObjectTransform); ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); const osg::Vec2i cellPosition(0, 0); const int cellSize = std::numeric_limits::max(); ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f)); for (int x = -6; x < 6; ++x) for (int y = -6; y < 6; ++y) - ASSERT_EQ(manager.getMesh(TilePosition(x, y)) != nullptr, -1 <= x && x <= 0 && -1 <= y && y <= 0); + ASSERT_EQ(manager.getMesh("worldspace", TilePosition(x, y)) != nullptr, -1 <= x && x <= 0 && -1 <= y && y <= 0); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_absent_cell_should_return_nullopt) @@ -312,20 +327,22 @@ namespace TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_existing_cell_should_remove_empty_tiles) { TileCachedRecastMeshManager manager(mSettings); + manager.setWorldspace("worldspace"); const osg::Vec2i cellPosition(0, 0); const int cellSize = 8192; ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f)); ASSERT_TRUE(manager.removeWater(cellPosition)); for (int x = -6; x < 6; ++x) for (int y = -6; y < 6; ++y) - ASSERT_EQ(manager.getMesh(TilePosition(x, y)), nullptr); + ASSERT_EQ(manager.getMesh("worldspace", TilePosition(x, y)), nullptr); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_existing_cell_should_leave_not_empty_tiles) { TileCachedRecastMeshManager manager(mSettings); + manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); - const CollisionShape shape(nullptr, boxShape); + const CollisionShape shape(mInstance, boxShape, mObjectTransform); ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); const osg::Vec2i cellPosition(0, 0); const int cellSize = 8192; @@ -333,21 +350,35 @@ namespace ASSERT_TRUE(manager.removeWater(cellPosition)); for (int x = -6; x < 6; ++x) for (int y = -6; y < 6; ++y) - ASSERT_EQ(manager.getMesh(TilePosition(x, y)) != nullptr, -1 <= x && x <= 0 && -1 <= y && y <= 0); + ASSERT_EQ(manager.getMesh("worldspace", TilePosition(x, y)) != nullptr, -1 <= x && x <= 0 && -1 <= y && y <= 0); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_object_should_not_remove_tile_with_water) { TileCachedRecastMeshManager manager(mSettings); + manager.setWorldspace("worldspace"); const osg::Vec2i cellPosition(0, 0); const int cellSize = 8192; const btBoxShape boxShape(btVector3(20, 20, 100)); - const CollisionShape shape(nullptr, boxShape); + const CollisionShape shape(mInstance, boxShape, mObjectTransform); ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f)); ASSERT_TRUE(manager.removeObject(ObjectId(&boxShape))); for (int x = -1; x < 12; ++x) for (int y = -1; y < 12; ++y) - ASSERT_NE(manager.getMesh(TilePosition(x, y)), nullptr); + ASSERT_NE(manager.getMesh("worldspace", TilePosition(x, y)), nullptr); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, set_new_worldspace_should_remove_tiles) + { + TileCachedRecastMeshManager manager(mSettings); + manager.setWorldspace("worldspace"); + const btBoxShape boxShape(btVector3(20, 20, 100)); + const CollisionShape shape(nullptr, boxShape, mObjectTransform); + ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); + manager.setWorldspace("other"); + for (int x = -1; x < 1; ++x) + for (int y = -1; y < 1; ++y) + ASSERT_EQ(manager.getMesh("other", TilePosition(x, y)), nullptr); } } diff --git a/apps/openmw_test_suite/detournavigator/serialization/binaryreader.cpp b/apps/openmw_test_suite/serialization/binaryreader.cpp similarity index 93% rename from apps/openmw_test_suite/detournavigator/serialization/binaryreader.cpp rename to apps/openmw_test_suite/serialization/binaryreader.cpp index d071326cf5..cb4f5c57bd 100644 --- a/apps/openmw_test_suite/detournavigator/serialization/binaryreader.cpp +++ b/apps/openmw_test_suite/serialization/binaryreader.cpp @@ -1,6 +1,6 @@ #include "format.hpp" -#include +#include #include #include @@ -12,8 +12,8 @@ namespace { using namespace testing; - using namespace DetourNavigator::Serialization; - using namespace DetourNavigator::SerializationTesting; + using namespace Serialization; + using namespace SerializationTesting; TEST(DetourNavigatorSerializationBinaryReaderTest, shouldReadArithmeticTypeValue) { diff --git a/apps/openmw_test_suite/detournavigator/serialization/binarywriter.cpp b/apps/openmw_test_suite/serialization/binarywriter.cpp similarity index 91% rename from apps/openmw_test_suite/detournavigator/serialization/binarywriter.cpp rename to apps/openmw_test_suite/serialization/binarywriter.cpp index fccc2be3da..cb0f29ba85 100644 --- a/apps/openmw_test_suite/detournavigator/serialization/binarywriter.cpp +++ b/apps/openmw_test_suite/serialization/binarywriter.cpp @@ -1,6 +1,6 @@ #include "format.hpp" -#include +#include #include #include @@ -12,8 +12,8 @@ namespace { using namespace testing; - using namespace DetourNavigator::Serialization; - using namespace DetourNavigator::SerializationTesting; + using namespace Serialization; + using namespace SerializationTesting; TEST(DetourNavigatorSerializationBinaryWriterTest, shouldWriteArithmeticTypeValue) { diff --git a/apps/openmw_test_suite/detournavigator/serialization/format.hpp b/apps/openmw_test_suite/serialization/format.hpp similarity index 89% rename from apps/openmw_test_suite/detournavigator/serialization/format.hpp rename to apps/openmw_test_suite/serialization/format.hpp index 7c5e26a0be..8f61838fde 100644 --- a/apps/openmw_test_suite/detournavigator/serialization/format.hpp +++ b/apps/openmw_test_suite/serialization/format.hpp @@ -1,12 +1,12 @@ -#ifndef OPENMW_TEST_SUITE_DETOURNAVIGATOR_SERIALIZATION_FORMAT_H -#define OPENMW_TEST_SUITE_DETOURNAVIGATOR_SERIALIZATION_FORMAT_H +#ifndef OPENMW_TEST_SUITE_SERIALIZATION_FORMAT_H +#define OPENMW_TEST_SUITE_SERIALIZATION_FORMAT_H -#include +#include #include #include -namespace DetourNavigator::SerializationTesting +namespace SerializationTesting { struct Pod { diff --git a/apps/openmw_test_suite/detournavigator/serialization/integration.cpp b/apps/openmw_test_suite/serialization/integration.cpp similarity index 85% rename from apps/openmw_test_suite/detournavigator/serialization/integration.cpp rename to apps/openmw_test_suite/serialization/integration.cpp index e7e8eacc20..cb8c711c67 100644 --- a/apps/openmw_test_suite/detournavigator/serialization/integration.cpp +++ b/apps/openmw_test_suite/serialization/integration.cpp @@ -1,8 +1,8 @@ #include "format.hpp" -#include -#include -#include +#include +#include +#include #include #include @@ -12,8 +12,8 @@ namespace { using namespace testing; - using namespace DetourNavigator::Serialization; - using namespace DetourNavigator::SerializationTesting; + using namespace Serialization; + using namespace SerializationTesting; struct DetourNavigatorSerializationIntegrationTest : Test { diff --git a/apps/openmw_test_suite/detournavigator/serialization/sizeaccumulator.cpp b/apps/openmw_test_suite/serialization/sizeaccumulator.cpp similarity index 85% rename from apps/openmw_test_suite/detournavigator/serialization/sizeaccumulator.cpp rename to apps/openmw_test_suite/serialization/sizeaccumulator.cpp index 39b7ea8646..dce148468a 100644 --- a/apps/openmw_test_suite/detournavigator/serialization/sizeaccumulator.cpp +++ b/apps/openmw_test_suite/serialization/sizeaccumulator.cpp @@ -1,6 +1,6 @@ #include "format.hpp" -#include +#include #include @@ -12,8 +12,8 @@ namespace { using namespace testing; - using namespace DetourNavigator::Serialization; - using namespace DetourNavigator::SerializationTesting; + using namespace Serialization; + using namespace SerializationTesting; TEST(DetourNavigatorSerializationSizeAccumulatorTest, shouldProvideSizeForArithmeticType) { diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index dd1ac36d91..f969ac1265 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -203,6 +203,11 @@ add_component_dir(detournavigator preparednavmeshdata navmeshcacheitem navigatorutils + generatenavmeshtile + navmeshdb + serialization + navmeshdbutils + recast ) add_component_dir(loadinglistener diff --git a/components/detournavigator/areatype.hpp b/components/detournavigator/areatype.hpp index 9d99421af0..125b7ed7b9 100644 --- a/components/detournavigator/areatype.hpp +++ b/components/detournavigator/areatype.hpp @@ -3,6 +3,8 @@ #include +#include + namespace DetourNavigator { enum AreaType : unsigned char @@ -21,6 +23,19 @@ namespace DetourNavigator float mPathgrid = 1.0f; float mGround = 1.0f; }; + + inline std::ostream& operator<<(std::ostream& stream, AreaType value) + { + switch (value) + { + case AreaType_null: return stream << "null"; + case AreaType_water: return stream << "water"; + case AreaType_door: return stream << "door"; + case AreaType_pathgrid: return stream << "pathgrid"; + case AreaType_ground: return stream << "ground"; + } + return stream << "unknown area type (" << static_cast>(value) << ")"; + } } #endif diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index 583fd1162a..47d984030f 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -3,6 +3,9 @@ #include "makenavmesh.hpp" #include "settings.hpp" #include "version.hpp" +#include "serialization.hpp" +#include "navmeshdbutils.hpp" +#include "dbrefgeometryobject.hpp" #include #include @@ -15,71 +18,104 @@ #include #include #include +#include -namespace +namespace DetourNavigator { - using DetourNavigator::ChangeType; - using DetourNavigator::TilePosition; - using DetourNavigator::UpdateType; - using DetourNavigator::ChangeType; - using DetourNavigator::Job; - using DetourNavigator::JobIt; - - int getManhattanDistance(const TilePosition& lhs, const TilePosition& rhs) + namespace { - return std::abs(lhs.x() - rhs.x()) + std::abs(lhs.y() - rhs.y()); - } + int getManhattanDistance(const TilePosition& lhs, const TilePosition& rhs) + { + return std::abs(lhs.x() - rhs.x()) + std::abs(lhs.y() - rhs.y()); + } - int getMinDistanceTo(const TilePosition& position, int maxDistance, - const std::set>& pushedTiles, - const std::set>& presentTiles) - { - int result = maxDistance; - for (const auto& [halfExtents, tile] : pushedTiles) - if (presentTiles.find(std::tie(halfExtents, tile)) == presentTiles.end()) - result = std::min(result, getManhattanDistance(position, tile)); - return result; - } + int getMinDistanceTo(const TilePosition& position, int maxDistance, + const std::set>& pushedTiles, + const std::set>& presentTiles) + { + int result = maxDistance; + for (const auto& [halfExtents, tile] : pushedTiles) + if (presentTiles.find(std::tie(halfExtents, tile)) == presentTiles.end()) + result = std::min(result, getManhattanDistance(position, tile)); + return result; + } - UpdateType getUpdateType(ChangeType changeType) noexcept - { - if (changeType == ChangeType::update) - return UpdateType::Temporary; - return UpdateType::Persistent; - } + auto getPriority(const Job& job) noexcept + { + return std::make_tuple(-static_cast>(job.mState), job.mProcessTime, + job.mChangeType, job.mTryNumber, job.mDistanceToPlayer, job.mDistanceToOrigin); + } - auto getPriority(const Job& job) noexcept - { - return std::make_tuple(job.mProcessTime, job.mChangeType, job.mTryNumber, job.mDistanceToPlayer, job.mDistanceToOrigin); - } + struct LessByJobPriority + { + bool operator()(JobIt lhs, JobIt rhs) const noexcept + { + return getPriority(*lhs) < getPriority(*rhs); + } + }; - struct LessByJobPriority - { - bool operator()(JobIt lhs, JobIt rhs) const noexcept + void insertPrioritizedJob(JobIt job, std::deque& queue) { - return getPriority(*lhs) < getPriority(*rhs); + const auto it = std::upper_bound(queue.begin(), queue.end(), job, LessByJobPriority {}); + queue.insert(it, job); } - }; - void insertPrioritizedJob(JobIt job, std::deque& queue) - { - const auto it = std::upper_bound(queue.begin(), queue.end(), job, LessByJobPriority {}); - queue.insert(it, job); - } + auto getDbPriority(const Job& job) noexcept + { + return std::make_tuple(static_cast>(job.mState), + job.mChangeType, job.mDistanceToPlayer, job.mDistanceToOrigin); + } - auto getAgentAndTile(const Job& job) noexcept - { - return std::make_tuple(job.mAgentHalfExtents, job.mChangedTile); + struct LessByJobDbPriority + { + bool operator()(JobIt lhs, JobIt rhs) const noexcept + { + return getDbPriority(*lhs) < getDbPriority(*rhs); + } + }; + + void insertPrioritizedDbJob(JobIt job, std::deque& queue) + { + const auto it = std::upper_bound(queue.begin(), queue.end(), job, LessByJobDbPriority {}); + queue.insert(it, job); + } + + auto getAgentAndTile(const Job& job) noexcept + { + return std::make_tuple(job.mAgentHalfExtents, job.mChangedTile); + } + + std::unique_ptr makeDbWorker(AsyncNavMeshUpdater& updater, std::unique_ptr&& db, const Settings& settings) + { + if (db == nullptr) + return nullptr; + return std::make_unique(updater, std::move(db), TileVersion(settings.mNavMeshVersion), settings.mRecast); + } + + void updateJobs(std::deque& jobs, TilePosition playerTile, int maxTiles) + { + for (JobIt job : jobs) + { + job->mDistanceToPlayer = getManhattanDistance(job->mChangedTile, playerTile); + if (!shouldAddTile(job->mChangedTile, playerTile, maxTiles)) + job->mChangeType = ChangeType::remove; + } + } + + std::size_t getNextJobId() + { + static std::atomic_size_t nextJobId {1}; + return nextJobId.fetch_add(1); + } } -} -namespace DetourNavigator -{ Job::Job(const osg::Vec3f& agentHalfExtents, std::weak_ptr navMeshCacheItem, - const TilePosition& changedTile, ChangeType changeType, int distanceToPlayer, + std::string_view worldspace, const TilePosition& changedTile, ChangeType changeType, int distanceToPlayer, std::chrono::steady_clock::time_point processTime) - : mAgentHalfExtents(agentHalfExtents) + : mId(getNextJobId()) + , mAgentHalfExtents(agentHalfExtents) , mNavMeshCacheItem(std::move(navMeshCacheItem)) + , mWorldspace(worldspace) , mChangedTile(changedTile) , mProcessTime(processTime) , mChangeType(changeType) @@ -89,12 +125,13 @@ namespace DetourNavigator } AsyncNavMeshUpdater::AsyncNavMeshUpdater(const Settings& settings, TileCachedRecastMeshManager& recastMeshManager, - OffMeshConnectionsManager& offMeshConnectionsManager) + OffMeshConnectionsManager& offMeshConnectionsManager, std::unique_ptr&& db) : mSettings(settings) , mRecastMeshManager(recastMeshManager) , mOffMeshConnectionsManager(offMeshConnectionsManager) , mShouldStop() , mNavMeshTilesCache(settings.mMaxNavMeshTilesCacheSize) + , mDbWorker(makeDbWorker(*this, std::move(db), mSettings)) { for (std::size_t i = 0; i < mSettings.get().mAsyncNavMeshUpdaterThreads; ++i) mThreads.emplace_back([&] { process(); }); @@ -103,6 +140,8 @@ namespace DetourNavigator AsyncNavMeshUpdater::~AsyncNavMeshUpdater() { mShouldStop = true; + if (mDbWorker != nullptr) + mDbWorker->stop(); std::unique_lock lock(mMutex); mWaiting.clear(); mHasJob.notify_all(); @@ -111,8 +150,8 @@ namespace DetourNavigator thread.join(); } - void AsyncNavMeshUpdater::post(const osg::Vec3f& agentHalfExtents, - const SharedNavMeshCacheItem& navMeshCacheItem, const TilePosition& playerTile, + void AsyncNavMeshUpdater::post(const osg::Vec3f& agentHalfExtents, const SharedNavMeshCacheItem& navMeshCacheItem, + const TilePosition& playerTile, std::string_view worldspace, const std::map& changedTiles) { bool playerTileChanged = false; @@ -126,18 +165,12 @@ namespace DetourNavigator return; const dtNavMeshParams params = *navMeshCacheItem->lockConst()->getImpl().getParams(); + const int maxTiles = std::min(mSettings.get().mMaxTilesNumber, params.maxTiles); - const std::lock_guard lock(mMutex); + std::unique_lock lock(mMutex); if (playerTileChanged) - { - for (JobIt job : mWaiting) - { - job->mDistanceToPlayer = getManhattanDistance(job->mChangedTile, playerTile); - if (!shouldAddTile(job->mChangedTile, playerTile, std::min(mSettings.get().mMaxTilesNumber, params.maxTiles))) - job->mChangeType = ChangeType::remove; - } - } + updateJobs(mWaiting, playerTile, maxTiles); for (const auto& [changedTile, changeType] : changedTiles) { @@ -147,8 +180,11 @@ namespace DetourNavigator ? mLastUpdates[std::tie(agentHalfExtents, changedTile)] + mSettings.get().mMinUpdateInterval : std::chrono::steady_clock::time_point(); - const JobIt it = mJobs.emplace(mJobs.end(), agentHalfExtents, navMeshCacheItem, changedTile, - changeType, getManhattanDistance(changedTile, playerTile), processTime); + const JobIt it = mJobs.emplace(mJobs.end(), agentHalfExtents, navMeshCacheItem, worldspace, + changedTile, changeType, getManhattanDistance(changedTile, playerTile), processTime); + + Log(Debug::Debug) << "Post job " << it->mId << " for agent=(" << it->mAgentHalfExtents << ")" + << " changedTile=(" << it->mChangedTile << ")"; if (playerTileChanged) mWaiting.push_back(it); @@ -164,6 +200,11 @@ namespace DetourNavigator if (!mWaiting.empty()) mHasJob.notify_all(); + + lock.unlock(); + + if (playerTileChanged && mDbWorker != nullptr) + mDbWorker->updateJobs(playerTile, maxTiles); } void AsyncNavMeshUpdater::wait(Loading::Listener& listener, WaitConditionType waitConditionType) @@ -241,25 +282,40 @@ namespace DetourNavigator mProcessingTiles.wait(mProcessed, [] (const auto& v) { return v.empty(); }); } - void AsyncNavMeshUpdater::reportStats(unsigned int frameNumber, osg::Stats& stats) const + AsyncNavMeshUpdater::Stats AsyncNavMeshUpdater::getStats() const { - std::size_t jobs = 0; - std::size_t waiting = 0; - std::size_t pushed = 0; - + Stats result; { const std::lock_guard lock(mMutex); - jobs = mJobs.size(); - waiting = mWaiting.size(); - pushed = mPushed.size(); + result.mJobs = mJobs.size(); + result.mWaiting = mWaiting.size(); + result.mPushed = mPushed.size(); } + result.mProcessing = mProcessingTiles.lockConst()->size(); + if (mDbWorker != nullptr) + result.mDb = mDbWorker->getStats(); + result.mCache = mNavMeshTilesCache.getStats(); + result.mDbGetTileHits = mDbGetTileHits.load(std::memory_order_relaxed); + return result; + } - stats.setAttribute(frameNumber, "NavMesh Jobs", jobs); - stats.setAttribute(frameNumber, "NavMesh Waiting", waiting); - stats.setAttribute(frameNumber, "NavMesh Pushed", pushed); - stats.setAttribute(frameNumber, "NavMesh Processing", mProcessingTiles.lockConst()->size()); + void reportStats(const AsyncNavMeshUpdater::Stats& stats, unsigned int frameNumber, osg::Stats& out) + { + out.setAttribute(frameNumber, "NavMesh Jobs", static_cast(stats.mJobs)); + out.setAttribute(frameNumber, "NavMesh Waiting", static_cast(stats.mWaiting)); + out.setAttribute(frameNumber, "NavMesh Pushed", static_cast(stats.mPushed)); + out.setAttribute(frameNumber, "NavMesh Processing", static_cast(stats.mProcessing)); - mNavMeshTilesCache.reportStats(frameNumber, stats); + if (stats.mDb.has_value()) + { + out.setAttribute(frameNumber, "NavMesh DbJobs", static_cast(stats.mDb->mJobs)); + + if (stats.mDb->mGetTileCount > 0) + out.setAttribute(frameNumber, "NavMesh DbCacheHitRate", static_cast(stats.mDbGetTileHits) + / static_cast(stats.mDb->mGetTileCount) * 100.0); + } + + reportStats(stats.mCache, frameNumber, out); } void AsyncNavMeshUpdater::process() noexcept @@ -272,12 +328,26 @@ namespace DetourNavigator { if (JobIt job = getNextJob(); job != mJobs.end()) { - const auto processed = processJob(*job); - unlockTile(job->mAgentHalfExtents, job->mChangedTile); - if (processed) - removeJob(job); - else - repost(job); + const JobStatus status = processJob(*job); + Log(Debug::Debug) << "Processed job " << job->mId << " with status=" << status; + switch (status) + { + case JobStatus::Done: + unlockTile(job->mAgentHalfExtents, job->mChangedTile); + if (job->mGeneratedNavMeshData != nullptr) + mDbWorker->enqueueJob(job); + else + removeJob(job); + break; + case JobStatus::Fail: + repost(job); + break; + case JobStatus::MemoryCacheMiss: + { + mDbWorker->enqueueJob(job); + break; + } + } } else cleanupLastUpdates(); @@ -290,33 +360,156 @@ namespace DetourNavigator Log(Debug::Debug) << "Stop navigator jobs processing by thread=" << std::this_thread::get_id(); } - bool AsyncNavMeshUpdater::processJob(const Job& job) + JobStatus AsyncNavMeshUpdater::processJob(Job& job) { - Log(Debug::Debug) << "Process job for agent=(" << std::fixed << std::setprecision(2) << job.mAgentHalfExtents << ")" - " by thread=" << std::this_thread::get_id(); - - const auto start = std::chrono::steady_clock::now(); + Log(Debug::Debug) << "Processing job " << job.mId << " by thread=" << std::this_thread::get_id(); const auto navMeshCacheItem = job.mNavMeshCacheItem.lock(); if (!navMeshCacheItem) - return true; + return JobStatus::Done; - const auto recastMesh = mRecastMeshManager.get().getMesh(job.mChangedTile); const auto playerTile = *mPlayerTile.lockConst(); + const auto params = *navMeshCacheItem->lockConst()->getImpl().getParams(); + + if (!shouldAddTile(job.mChangedTile, playerTile, std::min(mSettings.get().mMaxTilesNumber, params.maxTiles))) + { + Log(Debug::Debug) << "Ignore add tile by job " << job.mId << ": too far from player"; + navMeshCacheItem->lock()->removeTile(job.mChangedTile); + return JobStatus::Done; + } + + switch (job.mState) + { + case JobState::Initial: + return processInitialJob(job, *navMeshCacheItem); + case JobState::WithDbResult: + return processJobWithDbResult(job, *navMeshCacheItem); + } + + return JobStatus::Done; + } + + JobStatus AsyncNavMeshUpdater::processInitialJob(Job& job, GuardedNavMeshCacheItem& navMeshCacheItem) + { + Log(Debug::Debug) << "Processing initial job " << job.mId; + + std::shared_ptr recastMesh = mRecastMeshManager.get().getMesh(job.mWorldspace, job.mChangedTile); + + if (recastMesh == nullptr) + { + Log(Debug::Debug) << "Null recast mesh for job " << job.mId; + navMeshCacheItem.lock()->markAsEmpty(job.mChangedTile); + return JobStatus::Done; + } + + if (isEmpty(*recastMesh)) + { + Log(Debug::Debug) << "Empty bounds for job " << job.mId; + navMeshCacheItem.lock()->markAsEmpty(job.mChangedTile); + return JobStatus::Done; + } + + NavMeshTilesCache::Value cachedNavMeshData = mNavMeshTilesCache.get(job.mAgentHalfExtents, job.mChangedTile, *recastMesh); + std::unique_ptr preparedNavMeshData; + const PreparedNavMeshData* preparedNavMeshDataPtr = nullptr; + + if (cachedNavMeshData) + { + preparedNavMeshDataPtr = &cachedNavMeshData.get(); + } + else + { + if (job.mChangeType != ChangeType::update && mDbWorker != nullptr) + { + job.mRecastMesh = std::move(recastMesh); + return JobStatus::MemoryCacheMiss; + } + + preparedNavMeshData = prepareNavMeshTileData(*recastMesh, job.mChangedTile, job.mAgentHalfExtents, mSettings.get().mRecast); + + if (preparedNavMeshData == nullptr) + { + Log(Debug::Debug) << "Null navmesh data for job " << job.mId; + navMeshCacheItem.lock()->markAsEmpty(job.mChangedTile); + return JobStatus::Done; + } + + if (job.mChangeType == ChangeType::update) + { + preparedNavMeshDataPtr = preparedNavMeshData.get(); + } + else + { + cachedNavMeshData = mNavMeshTilesCache.set(job.mAgentHalfExtents, job.mChangedTile, + *recastMesh, std::move(preparedNavMeshData)); + preparedNavMeshDataPtr = cachedNavMeshData ? &cachedNavMeshData.get() : preparedNavMeshData.get(); + } + } + const auto offMeshConnections = mOffMeshConnectionsManager.get().get(job.mChangedTile); - const auto status = updateNavMesh(job.mAgentHalfExtents, recastMesh.get(), job.mChangedTile, playerTile, - offMeshConnections, mSettings, navMeshCacheItem, mNavMeshTilesCache, getUpdateType(job.mChangeType)); + const UpdateNavMeshStatus status = navMeshCacheItem.lock()->updateTile(job.mChangedTile, std::move(cachedNavMeshData), + makeNavMeshTileData(*preparedNavMeshDataPtr, offMeshConnections, job.mAgentHalfExtents, job.mChangedTile, mSettings.get().mRecast)); + + return handleUpdateNavMeshStatus(status, job, navMeshCacheItem, *recastMesh); + } + + JobStatus AsyncNavMeshUpdater::processJobWithDbResult(Job& job, GuardedNavMeshCacheItem& navMeshCacheItem) + { + Log(Debug::Debug) << "Processing job with db result " << job.mId; + + std::unique_ptr preparedNavMeshData; + bool generatedNavMeshData = false; + + if (job.mCachedTileData.has_value() && job.mCachedTileData->mVersion == mSettings.get().mNavMeshVersion) + { + preparedNavMeshData = std::make_unique(); + if (deserialize(job.mCachedTileData->mData, *preparedNavMeshData)) + ++mDbGetTileHits; + else + preparedNavMeshData = nullptr; + } + + if (preparedNavMeshData == nullptr) + { + preparedNavMeshData = prepareNavMeshTileData(*job.mRecastMesh, job.mChangedTile, job.mAgentHalfExtents, mSettings.get().mRecast); + generatedNavMeshData = true; + } - if (recastMesh != nullptr) + if (preparedNavMeshData == nullptr) { - const Version navMeshVersion = navMeshCacheItem->lockConst()->getVersion(); - mRecastMeshManager.get().reportNavMeshChange(job.mChangedTile, - Version {recastMesh->getGeneration(), recastMesh->getRevision()}, - navMeshVersion); + Log(Debug::Debug) << "Null navmesh data for job " << job.mId; + navMeshCacheItem.lock()->markAsEmpty(job.mChangedTile); + return JobStatus::Done; } + auto cachedNavMeshData = mNavMeshTilesCache.set(job.mAgentHalfExtents, job.mChangedTile, *job.mRecastMesh, + std::move(preparedNavMeshData)); + + const auto offMeshConnections = mOffMeshConnectionsManager.get().get(job.mChangedTile); + + const PreparedNavMeshData* preparedNavMeshDataPtr = cachedNavMeshData ? &cachedNavMeshData.get() : preparedNavMeshData.get(); + const UpdateNavMeshStatus status = navMeshCacheItem.lock()->updateTile(job.mChangedTile, std::move(cachedNavMeshData), + makeNavMeshTileData(*preparedNavMeshDataPtr, offMeshConnections, job.mAgentHalfExtents, job.mChangedTile, mSettings.get().mRecast)); + + const JobStatus result = handleUpdateNavMeshStatus(status, job, navMeshCacheItem, *job.mRecastMesh); + + if (result == JobStatus::Done && job.mChangeType != ChangeType::update + && mDbWorker != nullptr && mSettings.get().mWriteToNavMeshDb && generatedNavMeshData) + job.mGeneratedNavMeshData = std::make_unique(*preparedNavMeshDataPtr); + + return result; + } + + JobStatus AsyncNavMeshUpdater::handleUpdateNavMeshStatus(UpdateNavMeshStatus status, + const Job& job, const GuardedNavMeshCacheItem& navMeshCacheItem, const RecastMesh& recastMesh) + { + const Version navMeshVersion = navMeshCacheItem.lockConst()->getVersion(); + mRecastMeshManager.get().reportNavMeshChange(job.mChangedTile, + Version {recastMesh.getGeneration(), recastMesh.getRevision()}, + navMeshVersion); + if (status == UpdateNavMeshStatus::removed || status == UpdateNavMeshStatus::lost) { const std::scoped_lock lock(mMutex); @@ -328,23 +521,9 @@ namespace DetourNavigator mPresentTiles.insert(std::make_tuple(job.mAgentHalfExtents, job.mChangedTile)); } - const auto finish = std::chrono::steady_clock::now(); - - writeDebugFiles(job, recastMesh.get()); - - using FloatMs = std::chrono::duration; + writeDebugFiles(job, &recastMesh); - const Version version = navMeshCacheItem->lockConst()->getVersion(); - Log(Debug::Debug) << std::fixed << std::setprecision(2) << - "Cache updated for agent=(" << job.mAgentHalfExtents << ")" << - " tile=" << job.mChangedTile << - " status=" << status << - " generation=" << version.mGeneration << - " revision=" << version.mRevision << - " time=" << std::chrono::duration_cast(finish - start).count() << "ms" << - " thread=" << std::this_thread::get_id(); - - return isSuccess(status); + return isSuccess(status) ? JobStatus::Done : JobStatus::Fail; } JobIt AsyncNavMeshUpdater::getNextJob() @@ -373,8 +552,12 @@ namespace DetourNavigator mWaiting.pop_front(); + if (job->mRecastMesh != nullptr) + return job; + if (!lockTile(job->mAgentHalfExtents, job->mChangedTile)) { + Log(Debug::Debug) << "Failed to lock tile by " << job->mId; ++job->mTryNumber; insertPrioritizedJob(job, mWaiting); return mJobs.end(); @@ -404,7 +587,7 @@ namespace DetourNavigator } if (recastMesh && mSettings.get().mEnableWriteRecastMeshToFile) writeToFile(*recastMesh, mSettings.get().mRecastMeshPathPrefix + std::to_string(job.mChangedTile.x()) - + "_" + std::to_string(job.mChangedTile.y()) + "_", recastMeshRevision, mSettings); + + "_" + std::to_string(job.mChangedTile.y()) + "_", recastMeshRevision, mSettings.get().mRecast); if (mSettings.get().mEnableWriteNavMeshToFile) if (const auto shared = job.mNavMeshCacheItem.lock()) writeToFile(shared->lockConst()->getImpl(), mSettings.get().mNavMeshPathPrefix, navMeshRevision); @@ -412,6 +595,8 @@ namespace DetourNavigator void AsyncNavMeshUpdater::repost(JobIt job) { + unlockTile(job->mAgentHalfExtents, job->mChangedTile); + if (mShouldStop || job->mTryNumber > 2) return; @@ -430,17 +615,15 @@ namespace DetourNavigator bool AsyncNavMeshUpdater::lockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile) { - if (mSettings.get().mAsyncNavMeshUpdaterThreads <= 1) - return true; + Log(Debug::Debug) << "Locking tile agent=(" << agentHalfExtents << ") changedTile=(" << changedTile << ")"; return mProcessingTiles.lock()->emplace(agentHalfExtents, changedTile).second; } void AsyncNavMeshUpdater::unlockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile) { - if (mSettings.get().mAsyncNavMeshUpdaterThreads <= 1) - return; auto locked = mProcessingTiles.lock(); locked->erase(std::tie(agentHalfExtents, changedTile)); + Log(Debug::Debug) << "Unlocked tile agent=(" << agentHalfExtents << ") changedTile=(" << changedTile << ")"; if (locked->empty()) mProcessed.notify_all(); } @@ -466,9 +649,201 @@ namespace DetourNavigator } } + void AsyncNavMeshUpdater::enqueueJob(JobIt job) + { + Log(Debug::Debug) << "Enqueueing job " << job->mId << " by thread=" << std::this_thread::get_id(); + const std::lock_guard lock(mMutex); + insertPrioritizedJob(job, mWaiting); + mHasJob.notify_all(); + } + void AsyncNavMeshUpdater::removeJob(JobIt job) { + Log(Debug::Debug) << "Removing job " << job->mId << " by thread=" << std::this_thread::get_id(); const std::lock_guard lock(mMutex); mJobs.erase(job); } + + void DbJobQueue::push(JobIt job) + { + const std::lock_guard lock(mMutex); + insertPrioritizedDbJob(job, mJobs); + mHasJob.notify_all(); + } + + std::optional DbJobQueue::pop() + { + std::unique_lock lock(mMutex); + mHasJob.wait(lock, [&] { return mShouldStop || !mJobs.empty(); }); + if (mJobs.empty()) + return std::nullopt; + const JobIt job = mJobs.front(); + mJobs.pop_front(); + return job; + } + + void DbJobQueue::update(TilePosition playerTile, int maxTiles) + { + const std::lock_guard lock(mMutex); + updateJobs(mJobs, playerTile, maxTiles); + std::sort(mJobs.begin(), mJobs.end(), LessByJobDbPriority {}); + } + + void DbJobQueue::stop() + { + const std::lock_guard lock(mMutex); + mJobs.clear(); + mShouldStop = true; + mHasJob.notify_all(); + } + + std::size_t DbJobQueue::size() const + { + const std::lock_guard lock(mMutex); + return mJobs.size(); + } + + DbWorker::DbWorker(AsyncNavMeshUpdater& updater, std::unique_ptr&& db, + TileVersion version, const RecastSettings& recastSettings) + : mUpdater(updater) + , mRecastSettings(recastSettings) + , mDb(std::move(db)) + , mVersion(version) + , mNextTileId(mDb->getMaxTileId() + 1) + , mNextShapeId(mDb->getMaxShapeId() + 1) + , mThread([this] { run(); }) + { + } + + DbWorker::~DbWorker() + { + stop(); + mThread.join(); + } + + void DbWorker::enqueueJob(JobIt job) + { + Log(Debug::Debug) << "Enqueueing db job " << job->mId << " by thread=" << std::this_thread::get_id(); + mQueue.push(job); + } + + DbWorker::Stats DbWorker::getStats() const + { + Stats result; + result.mJobs = mQueue.size(); + result.mGetTileCount = mGetTileCount.load(std::memory_order::memory_order_relaxed); + return result; + } + + void DbWorker::stop() + { + mShouldStop = true; + mQueue.stop(); + } + + void DbWorker::run() noexcept + { + constexpr std::size_t writesPerTransaction = 100; + auto transaction = mDb->startTransaction(); + while (!mShouldStop) + { + try + { + if (const auto job = mQueue.pop()) + processJob(*job); + if (mWrites > writesPerTransaction) + { + mWrites = 0; + transaction.commit(); + transaction = mDb->startTransaction(); + } + } + catch (const std::exception& e) + { + Log(Debug::Error) << "DbWorker exception: " << e.what(); + } + } + transaction.commit(); + } + + void DbWorker::processJob(JobIt job) + { + const auto process = [&] (auto f) + { + try + { + f(job); + } + catch (const std::exception& e) + { + Log(Debug::Error) << "DbWorker exception while processing job " << job->mId << ": " << e.what(); + } + }; + + if (job->mGeneratedNavMeshData != nullptr) + { + process([&] (JobIt job) { processWritingJob(job); }); + mUpdater.removeJob(job); + return; + } + + process([&] (JobIt job) { processReadingJob(job); }); + job->mState = JobState::WithDbResult; + mUpdater.enqueueJob(job); + } + + void DbWorker::processReadingJob(JobIt job) + { + Log(Debug::Debug) << "Processing db read job " << job->mId; + + if (job->mInput.empty()) + { + Log(Debug::Debug) << "Serializing input for job " << job->mId; + const ShapeId shapeId = mNextShapeId; + const std::vector objects = makeDbRefGeometryObjects(job->mRecastMesh->getMeshSources(), + [&] (const MeshSource& v) { return resolveMeshSource(*mDb, v, mNextShapeId); }); + if (shapeId != mNextShapeId) + ++mWrites; + job->mInput = serialize(mRecastSettings, *job->mRecastMesh, objects); + } + + job->mCachedTileData = mDb->getTileData(job->mWorldspace, job->mChangedTile, job->mInput); + ++mGetTileCount; + } + + void DbWorker::processWritingJob(JobIt job) + { + ++mWrites; + + Log(Debug::Debug) << "Processing db write job " << job->mId; + + if (job->mInput.empty()) + { + Log(Debug::Debug) << "Serializing input for job " << job->mId; + const std::vector objects = makeDbRefGeometryObjects(job->mRecastMesh->getMeshSources(), + [&] (const MeshSource& v) { return resolveMeshSource(*mDb, v, mNextShapeId); }); + job->mInput = serialize(mRecastSettings, *job->mRecastMesh, objects); + } + + if (const auto& cachedTileData = job->mCachedTileData) + { + Log(Debug::Debug) << "Update db tile by job " << job->mId; + job->mGeneratedNavMeshData->mUserId = cachedTileData->mTileId; + mDb->updateTile(cachedTileData->mTileId, mVersion, serialize(*job->mGeneratedNavMeshData)); + return; + } + + const auto cached = mDb->findTile(job->mWorldspace, job->mChangedTile, job->mInput); + if (cached.has_value() && cached->mVersion == mVersion) + { + Log(Debug::Debug) << "Ignore existing db tile by job " << job->mId; + return; + } + + job->mGeneratedNavMeshData->mUserId = mNextTileId; + Log(Debug::Debug) << "Insert db tile by job " << job->mId; + mDb->insertTile(mNextTileId, job->mWorldspace, job->mChangedTile, + mVersion, job->mInput, serialize(*job->mGeneratedNavMeshData)); + ++mNextTileId.t; + } } diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index 2d915ad434..541d86fe9c 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -7,6 +7,7 @@ #include "tileposition.hpp" #include "navmeshtilescache.hpp" #include "waitconditiontype.hpp" +#include "navmeshdb.hpp" #include @@ -20,6 +21,7 @@ #include #include #include +#include class dtNavMesh; @@ -53,37 +55,150 @@ namespace DetourNavigator return stream << "ChangeType::" << static_cast(value); } + enum class JobState + { + Initial, + WithDbResult, + }; + struct Job { + const std::size_t mId; const osg::Vec3f mAgentHalfExtents; const std::weak_ptr mNavMeshCacheItem; + const std::string mWorldspace; const TilePosition mChangedTile; const std::chrono::steady_clock::time_point mProcessTime; unsigned mTryNumber = 0; ChangeType mChangeType; int mDistanceToPlayer; const int mDistanceToOrigin; + JobState mState = JobState::Initial; + std::vector mInput; + std::shared_ptr mRecastMesh; + std::optional mCachedTileData; + std::unique_ptr mGeneratedNavMeshData; Job(const osg::Vec3f& agentHalfExtents, std::weak_ptr navMeshCacheItem, - const TilePosition& changedTile, ChangeType changeType, int distanceToPlayer, + std::string_view worldspace, const TilePosition& changedTile, ChangeType changeType, int distanceToPlayer, std::chrono::steady_clock::time_point processTime); }; using JobIt = std::list::iterator; + enum class JobStatus + { + Done, + Fail, + MemoryCacheMiss, + }; + + inline std::ostream& operator<<(std::ostream& stream, JobStatus value) + { + switch (value) + { + case JobStatus::Done: return stream << "JobStatus::Done"; + case JobStatus::Fail: return stream << "JobStatus::Fail"; + case JobStatus::MemoryCacheMiss: return stream << "JobStatus::MemoryCacheMiss"; + } + return stream << "JobStatus::" << static_cast>(value); + } + + class DbJobQueue + { + public: + void push(JobIt job); + + std::optional pop(); + + void update(TilePosition playerTile, int maxTiles); + + void stop(); + + std::size_t size() const; + + private: + mutable std::mutex mMutex; + std::condition_variable mHasJob; + std::deque mJobs; + bool mShouldStop = false; + }; + + class AsyncNavMeshUpdater; + + class DbWorker + { + public: + struct Stats + { + std::size_t mJobs = 0; + std::size_t mGetTileCount = 0; + }; + + DbWorker(AsyncNavMeshUpdater& updater, std::unique_ptr&& db, + TileVersion version, const RecastSettings& recastSettings); + + ~DbWorker(); + + Stats getStats() const; + + void enqueueJob(JobIt job); + + void updateJobs(TilePosition playerTile, int maxTiles) { mQueue.update(playerTile, maxTiles); } + + void stop(); + + private: + AsyncNavMeshUpdater& mUpdater; + const RecastSettings& mRecastSettings; + const std::unique_ptr mDb; + const TileVersion mVersion; + TileId mNextTileId; + ShapeId mNextShapeId; + DbJobQueue mQueue; + std::atomic_bool mShouldStop {false}; + std::atomic_size_t mGetTileCount {0}; + std::size_t mWrites = 0; + std::thread mThread; + + inline void run() noexcept; + + inline void processJob(JobIt job); + + inline void processReadingJob(JobIt job); + + inline void processWritingJob(JobIt job); + }; + class AsyncNavMeshUpdater { public: + struct Stats + { + std::size_t mJobs = 0; + std::size_t mWaiting = 0; + std::size_t mPushed = 0; + std::size_t mProcessing = 0; + std::size_t mDbGetTileHits = 0; + std::optional mDb; + NavMeshTilesCache::Stats mCache; + }; + AsyncNavMeshUpdater(const Settings& settings, TileCachedRecastMeshManager& recastMeshManager, - OffMeshConnectionsManager& offMeshConnectionsManager); + OffMeshConnectionsManager& offMeshConnectionsManager, std::unique_ptr&& db); ~AsyncNavMeshUpdater(); - void post(const osg::Vec3f& agentHalfExtents, const SharedNavMeshCacheItem& mNavMeshCacheItem, - const TilePosition& playerTile, const std::map& changedTiles); + void post(const osg::Vec3f& agentHalfExtents, const SharedNavMeshCacheItem& navMeshCacheItem, + const TilePosition& playerTile, std::string_view worldspace, + const std::map& changedTiles); void wait(Loading::Listener& listener, WaitConditionType waitConditionType); - void reportStats(unsigned int frameNumber, osg::Stats& stats) const; + Stats getStats() const; + + void enqueueJob(JobIt job); + + void removeJob(JobIt job); private: std::reference_wrapper mSettings; @@ -103,14 +218,21 @@ namespace DetourNavigator std::map, std::chrono::steady_clock::time_point> mLastUpdates; std::set> mPresentTiles; std::vector mThreads; + std::unique_ptr mDbWorker; + std::atomic_size_t mDbGetTileHits {0}; void process() noexcept; - bool processJob(const Job& job); + JobStatus processJob(Job& job); - JobIt getNextJob(); + inline JobStatus processInitialJob(Job& job, GuardedNavMeshCacheItem& navMeshCacheItem); + + inline JobStatus processJobWithDbResult(Job& job, GuardedNavMeshCacheItem& navMeshCacheItem); + + inline JobStatus handleUpdateNavMeshStatus(UpdateNavMeshStatus status, const Job& job, + const GuardedNavMeshCacheItem& navMeshCacheItem, const RecastMesh& recastMesh); - JobIt getJob(std::deque& jobs, bool changeLastUpdate); + JobIt getNextJob(); void postThreadJob(JobIt job, std::deque& queue); @@ -129,9 +251,9 @@ namespace DetourNavigator int waitUntilJobsDoneForNotPresentTiles(const std::size_t initialJobsLeft, std::size_t& maxJobsLeft, Loading::Listener& listener); void waitUntilAllJobsDone(); - - inline void removeJob(JobIt job); }; + + void reportStats(const AsyncNavMeshUpdater::Stats& stats, unsigned int frameNumber, osg::Stats& out); } #endif diff --git a/components/detournavigator/cachedrecastmeshmanager.cpp b/components/detournavigator/cachedrecastmeshmanager.cpp index a3c7f4b172..e350c5591f 100644 --- a/components/detournavigator/cachedrecastmeshmanager.cpp +++ b/components/detournavigator/cachedrecastmeshmanager.cpp @@ -84,6 +84,11 @@ namespace DetourNavigator return *mCached.lockConst(); } + std::shared_ptr CachedRecastMeshManager::getNewMesh() const + { + return mImpl.getMesh(); + } + bool CachedRecastMeshManager::isEmpty() const { return mImpl.isEmpty(); diff --git a/components/detournavigator/cachedrecastmeshmanager.hpp b/components/detournavigator/cachedrecastmeshmanager.hpp index eb18519891..b92d39efa4 100644 --- a/components/detournavigator/cachedrecastmeshmanager.hpp +++ b/components/detournavigator/cachedrecastmeshmanager.hpp @@ -35,6 +35,8 @@ namespace DetourNavigator std::shared_ptr getCachedMesh() const; + std::shared_ptr getNewMesh() const; + bool isEmpty() const; void reportNavMeshChange(const Version& recastMeshVersion, const Version& navMeshVersion); diff --git a/components/detournavigator/dbrefgeometryobject.hpp b/components/detournavigator/dbrefgeometryobject.hpp new file mode 100644 index 0000000000..2be44ca175 --- /dev/null +++ b/components/detournavigator/dbrefgeometryobject.hpp @@ -0,0 +1,46 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_DBREFGEOMETRYOBJECT_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_DBREFGEOMETRYOBJECT_H + +#include "objecttransform.hpp" +#include "recastmesh.hpp" + +#include +#include +#include +#include + +namespace DetourNavigator +{ + struct DbRefGeometryObject + { + std::int64_t mShapeId; + ObjectTransform mObjectTransform; + + friend inline auto tie(const DbRefGeometryObject& v) + { + return std::tie(v.mShapeId, v.mObjectTransform); + } + + friend inline bool operator<(const DbRefGeometryObject& l, const DbRefGeometryObject& r) + { + return tie(l) < tie(r); + } + }; + + template + inline std::vector makeDbRefGeometryObjects(const std::vector& meshSources, + ResolveMeshSource&& resolveMeshSource) + { + std::vector result; + result.reserve(meshSources.size()); + std::transform(meshSources.begin(), meshSources.end(), std::back_inserter(result), + [&] (const MeshSource& meshSource) + { + return DbRefGeometryObject {resolveMeshSource(meshSource), meshSource.mObjectTransform}; + }); + std::sort(result.begin(), result.end()); + return result; + } +} + +#endif diff --git a/components/detournavigator/debug.cpp b/components/detournavigator/debug.cpp index 07832d748f..8394c6696d 100644 --- a/components/detournavigator/debug.cpp +++ b/components/detournavigator/debug.cpp @@ -11,7 +11,8 @@ namespace DetourNavigator { - void writeToFile(const RecastMesh& recastMesh, const std::string& pathPrefix, const std::string& revision, const Settings& settings) + void writeToFile(const RecastMesh& recastMesh, const std::string& pathPrefix, + const std::string& revision, const RecastSettings& settings) { const auto path = pathPrefix + "recastmesh" + revision + ".obj"; boost::filesystem::ofstream file(boost::filesystem::path(path), std::ios::out); diff --git a/components/detournavigator/debug.hpp b/components/detournavigator/debug.hpp index a868ac2a2e..ce1e2d2023 100644 --- a/components/detournavigator/debug.hpp +++ b/components/detournavigator/debug.hpp @@ -3,6 +3,7 @@ #include "tilebounds.hpp" #include "status.hpp" +#include "recastmesh.hpp" #include @@ -39,10 +40,40 @@ namespace DetourNavigator return stream << "DetourNavigator::Error::" << static_cast(value); } + inline std::ostream& operator<<(std::ostream& s, const Water& v) + { + return s << "Water {" << v.mCellSize << ", " << v.mLevel << "}"; + } + + inline std::ostream& operator<<(std::ostream& s, const CellWater& v) + { + return s << "CellWater {" << v.mCellPosition << ", " << v.mWater << "}"; + } + + inline std::ostream& operator<<(std::ostream& s, const FlatHeightfield& v) + { + return s << "FlatHeightfield {" << v.mCellPosition << ", " << v.mCellSize << ", " << v.mHeight << "}"; + } + + inline std::ostream& operator<<(std::ostream& s, const Heightfield& v) + { + s << "Heightfield {.mCellPosition=" << v.mCellPosition + << ", .mCellSize=" << v.mCellSize + << ", .mLength=" << static_cast(v.mLength) + << ", .mMinHeight=" << v.mMinHeight + << ", .mMaxHeight=" << v.mMaxHeight + << ", .mHeights={"; + for (float h : v.mHeights) + s << h << ", "; + s << "}"; + return s << ", .mOriginalSize=" << v.mOriginalSize << "}"; + } + class RecastMesh; - struct Settings; + struct RecastSettings; - void writeToFile(const RecastMesh& recastMesh, const std::string& pathPrefix, const std::string& revision, const Settings& settings); + void writeToFile(const RecastMesh& recastMesh, const std::string& pathPrefix, + const std::string& revision, const RecastSettings& settings); void writeToFile(const dtNavMesh& navMesh, const std::string& pathPrefix, const std::string& revision); } diff --git a/components/detournavigator/findrandompointaroundcircle.cpp b/components/detournavigator/findrandompointaroundcircle.cpp index a3407a61c3..1e1e2401c5 100644 --- a/components/detournavigator/findrandompointaroundcircle.cpp +++ b/components/detournavigator/findrandompointaroundcircle.cpp @@ -10,7 +10,7 @@ namespace DetourNavigator { std::optional findRandomPointAroundCircle(const dtNavMesh& navMesh, const osg::Vec3f& halfExtents, - const osg::Vec3f& start, const float maxRadius, const Flags includeFlags, const Settings& settings) + const osg::Vec3f& start, const float maxRadius, const Flags includeFlags, const DetourSettings& settings) { dtNavMeshQuery navMeshQuery; if (!initNavMeshQuery(navMeshQuery, navMesh, settings.mMaxNavMeshQueryNodes)) diff --git a/components/detournavigator/findrandompointaroundcircle.hpp b/components/detournavigator/findrandompointaroundcircle.hpp index d0dc2bbbc0..89a3c0964c 100644 --- a/components/detournavigator/findrandompointaroundcircle.hpp +++ b/components/detournavigator/findrandompointaroundcircle.hpp @@ -10,10 +10,10 @@ class dtNavMesh; namespace DetourNavigator { - struct Settings; + struct DetourSettings; std::optional findRandomPointAroundCircle(const dtNavMesh& navMesh, const osg::Vec3f& halfExtents, - const osg::Vec3f& start, const float maxRadius, const Flags includeFlags, const Settings& settings); + const osg::Vec3f& start, const float maxRadius, const Flags includeFlags, const DetourSettings& settings); } #endif diff --git a/components/detournavigator/findsmoothpath.hpp b/components/detournavigator/findsmoothpath.hpp index 2e63340578..fa35470c42 100644 --- a/components/detournavigator/findsmoothpath.hpp +++ b/components/detournavigator/findsmoothpath.hpp @@ -60,7 +60,7 @@ namespace DetourNavigator class OutputTransformIterator { public: - OutputTransformIterator(OutputIterator& impl, const Settings& settings) + explicit OutputTransformIterator(OutputIterator& impl, const RecastSettings& settings) : mImpl(impl), mSettings(settings) { } @@ -91,7 +91,7 @@ namespace DetourNavigator private: std::reference_wrapper mImpl; - std::reference_wrapper mSettings; + std::reference_wrapper mSettings; }; inline bool initNavMeshQuery(dtNavMeshQuery& value, const dtNavMesh& navMesh, const int maxNodes) @@ -261,7 +261,7 @@ namespace DetourNavigator const Settings& settings, float endTolerance, OutputIterator& out) { dtNavMeshQuery navMeshQuery; - if (!initNavMeshQuery(navMeshQuery, navMesh, settings.mMaxNavMeshQueryNodes)) + if (!initNavMeshQuery(navMeshQuery, navMesh, settings.mDetour.mMaxNavMeshQueryNodes)) return Status::InitNavMeshQueryFailed; dtQueryFilter queryFilter; @@ -283,7 +283,7 @@ namespace DetourNavigator if (endRef == 0) return Status::EndPolygonNotFound; - std::vector polygonPath(settings.mMaxPolygonPathSize); + std::vector polygonPath(settings.mDetour.mMaxPolygonPathSize); const auto polygonPathSize = findPath(navMeshQuery, startRef, endRef, start, end, queryFilter, polygonPath.data(), polygonPath.size()); @@ -294,9 +294,9 @@ namespace DetourNavigator return Status::Success; const bool partialPath = polygonPath[*polygonPathSize - 1] != endRef; - auto outTransform = OutputTransformIterator(out, settings); + auto outTransform = OutputTransformIterator(out, settings.mRecast); const Status smoothStatus = makeSmoothPath(navMesh, navMeshQuery, queryFilter, start, end, stepSize, - polygonPath, *polygonPathSize, settings.mMaxSmoothPathSize, outTransform); + polygonPath, *polygonPathSize, settings.mDetour.mMaxSmoothPathSize, outTransform); if (smoothStatus != Status::Success) return smoothStatus; diff --git a/components/detournavigator/generatenavmeshtile.cpp b/components/detournavigator/generatenavmeshtile.cpp new file mode 100644 index 0000000000..ad8978cd4b --- /dev/null +++ b/components/detournavigator/generatenavmeshtile.cpp @@ -0,0 +1,95 @@ +#include "generatenavmeshtile.hpp" + +#include "dbrefgeometryobject.hpp" +#include "makenavmesh.hpp" +#include "offmeshconnectionsmanager.hpp" +#include "preparednavmeshdata.hpp" +#include "serialization.hpp" +#include "settings.hpp" +#include "tilecachedrecastmeshmanager.hpp" + +#include + +#include +#include + +#include +#include +#include +#include +#include + +namespace DetourNavigator +{ + namespace + { + struct Ignore + { + std::shared_ptr mConsumer; + + ~Ignore() noexcept + { + if (mConsumer != nullptr) + mConsumer->ignore(); + } + }; + } + + GenerateNavMeshTile::GenerateNavMeshTile(std::string worldspace, const TilePosition& tilePosition, + RecastMeshProvider recastMeshProvider, const osg::Vec3f& agentHalfExtents, + const DetourNavigator::Settings& settings, std::weak_ptr consumer) + : mWorldspace(std::move(worldspace)) + , mTilePosition(tilePosition) + , mRecastMeshProvider(recastMeshProvider) + , mAgentHalfExtents(agentHalfExtents) + , mSettings(settings) + , mConsumer(std::move(consumer)) {} + + void GenerateNavMeshTile::doWork() + { + impl(); + } + + void GenerateNavMeshTile::impl() noexcept + { + const auto consumer = mConsumer.lock(); + + if (consumer == nullptr) + return; + + try + { + Ignore ignore {consumer}; + + const std::shared_ptr recastMesh = mRecastMeshProvider.getMesh(mWorldspace, mTilePosition); + + if (recastMesh == nullptr || isEmpty(*recastMesh)) + return; + + const std::vector objects = makeDbRefGeometryObjects(recastMesh->getMeshSources(), + [&] (const MeshSource& v) { return consumer->resolveMeshSource(v); }); + std::vector input = serialize(mSettings.mRecast, *recastMesh, objects); + const std::optional info = consumer->find(mWorldspace, mTilePosition, input); + + if (info.has_value() && info->mVersion == mSettings.mNavMeshVersion) + return; + + const auto data = prepareNavMeshTileData(*recastMesh, mTilePosition, mAgentHalfExtents, mSettings.mRecast); + + if (data == nullptr) + return; + + if (info.has_value()) + consumer->update(info->mTileId, mSettings.mNavMeshVersion, *data); + else + consumer->insert(mWorldspace, mTilePosition, mSettings.mNavMeshVersion, input, *data); + + ignore.mConsumer = nullptr; + } + catch (const std::exception& e) + { + Log(Debug::Warning) << "Failed to generate navmesh for worldspace \"" << mWorldspace + << "\" tile " << mTilePosition << ": " << e.what(); + } + } +} diff --git a/components/detournavigator/generatenavmeshtile.hpp b/components/detournavigator/generatenavmeshtile.hpp new file mode 100644 index 0000000000..511b8dfb8f --- /dev/null +++ b/components/detournavigator/generatenavmeshtile.hpp @@ -0,0 +1,71 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_GENERATENAVMESHTILE_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_GENERATENAVMESHTILE_H + +#include "recastmeshprovider.hpp" +#include "tileposition.hpp" + +#include + +#include + +#include +#include +#include +#include +#include +#include + +namespace DetourNavigator +{ + class OffMeshConnectionsManager; + class RecastMesh; + struct NavMeshTileConsumer; + struct OffMeshConnection; + struct PreparedNavMeshData; + struct Settings; + + struct NavMeshTileInfo + { + std::int64_t mTileId; + std::int64_t mVersion; + }; + + struct NavMeshTileConsumer + { + virtual ~NavMeshTileConsumer() = default; + + virtual std::int64_t resolveMeshSource(const MeshSource& source) = 0; + + virtual std::optional find(const std::string& worldspace, const TilePosition& tilePosition, + const std::vector& input) = 0; + + virtual void ignore() = 0; + + virtual void insert(const std::string& worldspace, const TilePosition& tilePosition, + std::int64_t version, const std::vector& input, PreparedNavMeshData& data) = 0; + + virtual void update(std::int64_t tileId, std::int64_t version, PreparedNavMeshData& data) = 0; + }; + + class GenerateNavMeshTile final : public SceneUtil::WorkItem + { + public: + GenerateNavMeshTile(std::string worldspace, const TilePosition& tilePosition, + RecastMeshProvider recastMeshProvider, const osg::Vec3f& agentHalfExtents, const Settings& settings, + std::weak_ptr consumer); + + void doWork() final; + + private: + const std::string mWorldspace; + const TilePosition mTilePosition; + const RecastMeshProvider mRecastMeshProvider; + const osg::Vec3f mAgentHalfExtents; + const Settings& mSettings; + std::weak_ptr mConsumer; + + inline void impl() noexcept; + }; +} + +#endif diff --git a/components/detournavigator/gettilespositions.hpp b/components/detournavigator/gettilespositions.hpp index abfce06464..707db0b512 100644 --- a/components/detournavigator/gettilespositions.hpp +++ b/components/detournavigator/gettilespositions.hpp @@ -15,7 +15,7 @@ namespace DetourNavigator { template void getTilesPositions(const osg::Vec3f& aabbMin, const osg::Vec3f& aabbMax, - const Settings& settings, Callback&& callback) + const RecastSettings& settings, Callback&& callback) { auto min = toNavMeshCoordinates(settings, aabbMin); auto max = toNavMeshCoordinates(settings, aabbMax); @@ -40,7 +40,7 @@ namespace DetourNavigator template void getTilesPositions(const btCollisionShape& shape, const btTransform& transform, - const Settings& settings, Callback&& callback) + const RecastSettings& settings, Callback&& callback) { btVector3 aabbMin; btVector3 aabbMax; @@ -51,7 +51,7 @@ namespace DetourNavigator template void getTilesPositions(const int cellSize, const btVector3& shift, - const Settings& settings, Callback&& callback) + const RecastSettings& settings, Callback&& callback) { using Misc::Convert::toOsg; diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index 7d478b9c1b..070a227454 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -10,9 +10,15 @@ #include "preparednavmeshdata.hpp" #include "navmeshdata.hpp" #include "recastmeshbuilder.hpp" +#include "navmeshdb.hpp" +#include "serialization.hpp" +#include "dbrefgeometryobject.hpp" +#include "navmeshdbutils.hpp" #include #include +#include +#include #include #include @@ -36,32 +42,6 @@ namespace float mHeight; }; - Rectangle getSwimRectangle(const CellWater& water, const Settings& settings, const osg::Vec3f& agentHalfExtents) - { - if (water.mWater.mCellSize == std::numeric_limits::max()) - { - return Rectangle { - TileBounds { - osg::Vec2f(-std::numeric_limits::max(), -std::numeric_limits::max()), - osg::Vec2f(std::numeric_limits::max(), std::numeric_limits::max()) - }, - toNavMeshCoordinates(settings, getSwimLevel(settings, water.mWater.mLevel, agentHalfExtents.z())) - }; - } - else - { - const osg::Vec2f shift = getWaterShift2d(water.mCellPosition, water.mWater.mCellSize); - const float halfCellSize = water.mWater.mCellSize / 2.0f; - return Rectangle { - TileBounds{ - toNavMeshCoordinates(settings, shift + osg::Vec2f(-halfCellSize, -halfCellSize)), - toNavMeshCoordinates(settings, shift + osg::Vec2f(halfCellSize, halfCellSize)) - }, - toNavMeshCoordinates(settings, getSwimLevel(settings, water.mWater.mLevel, agentHalfExtents.z())) - }; - } - } - std::vector getOffMeshVerts(const std::vector& connections) { std::vector result; @@ -120,52 +100,70 @@ namespace return result; } - rcConfig makeConfig(const osg::Vec3f& agentHalfExtents, const TilePosition& tile, float minZ, float maxZ, - const Settings& settings) - { - rcConfig config; - - config.cs = settings.mCellSize; - config.ch = settings.mCellHeight; - config.walkableSlopeAngle = settings.mMaxSlope; - config.walkableHeight = static_cast(std::ceil(getHeight(settings, agentHalfExtents) / config.ch)); - config.walkableClimb = static_cast(std::floor(getMaxClimb(settings) / config.ch)); - config.walkableRadius = static_cast(std::ceil(getRadius(settings, agentHalfExtents) / config.cs)); - config.maxEdgeLen = static_cast(std::round(settings.mMaxEdgeLen / config.cs)); - config.maxSimplificationError = settings.mMaxSimplificationError; - config.minRegionArea = settings.mRegionMinSize * settings.mRegionMinSize; - config.mergeRegionArea = settings.mRegionMergeSize * settings.mRegionMergeSize; - config.maxVertsPerPoly = settings.mMaxVertsPerPoly; - config.detailSampleDist = settings.mDetailSampleDist < 0.9f ? 0 : config.cs * settings.mDetailSampleDist; - config.detailSampleMaxError = config.ch * settings.mDetailSampleMaxError; - config.borderSize = settings.mBorderSize; - config.tileSize = settings.mTileSize; - const int size = config.tileSize + config.borderSize * 2; - config.width = size; - config.height = size; - const float halfBoundsSize = size * config.cs * 0.5f; - const osg::Vec2f shift = osg::Vec2f(tile.x() + 0.5f, tile.y() + 0.5f) * getTileSize(settings); - config.bmin[0] = shift.x() - halfBoundsSize; - config.bmin[1] = minZ; - config.bmin[2] = shift.y() - halfBoundsSize; - config.bmax[0] = shift.x() + halfBoundsSize; - config.bmax[1] = maxZ; - config.bmax[2] = shift.y() + halfBoundsSize; - - return config; + float getHeight(const RecastSettings& settings,const osg::Vec3f& agentHalfExtents) + { + return 2.0f * agentHalfExtents.z() * settings.mRecastScaleFactor; + } + + float getMaxClimb(const RecastSettings& settings) + { + return settings.mMaxClimb * settings.mRecastScaleFactor; + } + + float getRadius(const RecastSettings& settings, const osg::Vec3f& agentHalfExtents) + { + return std::max(agentHalfExtents.x(), agentHalfExtents.y()) * std::sqrt(2) * settings.mRecastScaleFactor; + } + + float getSwimLevel(const RecastSettings& settings, const float waterLevel, const float agentHalfExtentsZ) + { + return waterLevel - settings.mSwimHeightScale * agentHalfExtentsZ - agentHalfExtentsZ;; } - void createHeightfield(rcContext& context, rcHeightfield& solid, int width, int height, const float* bmin, - const float* bmax, const float cs, const float ch) + struct RecastParams { - const auto result = rcCreateHeightfield(&context, solid, width, height, bmin, bmax, cs, ch); + float mSampleDist = 0; + float mSampleMaxError = 0; + int mMaxEdgeLen = 0; + int mWalkableClimb = 0; + int mWalkableHeight = 0; + int mWalkableRadius = 0; + }; + + RecastParams makeRecastParams(const RecastSettings& settings, const osg::Vec3f& agentHalfExtents) + { + RecastParams result; + + result.mWalkableHeight = static_cast(std::ceil(getHeight(settings, agentHalfExtents) / settings.mCellHeight)); + result.mWalkableClimb = static_cast(std::floor(getMaxClimb(settings) / settings.mCellHeight)); + result.mWalkableRadius = static_cast(std::ceil(getRadius(settings, agentHalfExtents) / settings.mCellSize)); + result.mMaxEdgeLen = static_cast(std::round(static_cast(settings.mMaxEdgeLen) / settings.mCellSize)); + result.mSampleDist = settings.mDetailSampleDist < 0.9f ? 0 : settings.mCellSize * settings.mDetailSampleDist; + result.mSampleMaxError = settings.mCellHeight * settings.mDetailSampleMaxError; + + return result; + } + + void initHeightfield(rcContext& context, const TilePosition& tilePosition, float minZ, float maxZ, + const RecastSettings& settings, rcHeightfield& solid) + { + const int size = settings.mTileSize + settings.mBorderSize * 2; + const int width = size; + const int height = size; + const float halfBoundsSize = size * settings.mCellSize * 0.5f; + const osg::Vec2f shift = osg::Vec2f(tilePosition.x() + 0.5f, tilePosition.y() + 0.5f) * getTileSize(settings); + const osg::Vec3f bmin(shift.x() - halfBoundsSize, minZ, shift.y() - halfBoundsSize); + const osg::Vec3f bmax(shift.x() + halfBoundsSize, maxZ, shift.y() + halfBoundsSize); + + const auto result = rcCreateHeightfield(&context, solid, width, height, bmin.ptr(), bmax.ptr(), + settings.mCellSize, settings.mCellHeight); if (!result) throw NavigatorException("Failed to create heightfield for navmesh"); } - bool rasterizeTriangles(rcContext& context, const Mesh& mesh, const Settings& settings, const rcConfig& config, - rcHeightfield& solid) + bool rasterizeTriangles(rcContext& context, const Mesh& mesh, const RecastSettings& settings, + const RecastParams& params, rcHeightfield& solid) { std::vector areas(mesh.getAreaTypes().begin(), mesh.getAreaTypes().end()); std::vector vertices = mesh.getVertices(); @@ -179,7 +177,7 @@ namespace rcClearUnwalkableTriangles( &context, - config.walkableSlopeAngle, + settings.mMaxSlope, vertices.data(), static_cast(mesh.getVerticesCount()), mesh.getIndices().data(), @@ -195,30 +193,18 @@ namespace areas.data(), static_cast(areas.size()), solid, - config.walkableClimb + params.mWalkableClimb ); } - bool rasterizeTriangles(rcContext& context, const Rectangle& rectangle, const rcConfig& config, - AreaType areaType, rcHeightfield& solid) + bool rasterizeTriangles(rcContext& context, const Rectangle& rectangle, AreaType areaType, + const RecastParams& params, rcHeightfield& solid) { - const osg::Vec2f tileBoundsMin( - std::clamp(rectangle.mBounds.mMin.x(), config.bmin[0], config.bmax[0]), - std::clamp(rectangle.mBounds.mMin.y(), config.bmin[2], config.bmax[2]) - ); - const osg::Vec2f tileBoundsMax( - std::clamp(rectangle.mBounds.mMax.x(), config.bmin[0], config.bmax[0]), - std::clamp(rectangle.mBounds.mMax.y(), config.bmin[2], config.bmax[2]) - ); - - if (tileBoundsMax == tileBoundsMin) - return true; - const std::array vertices { - tileBoundsMin.x(), rectangle.mHeight, tileBoundsMin.y(), - tileBoundsMin.x(), rectangle.mHeight, tileBoundsMax.y(), - tileBoundsMax.x(), rectangle.mHeight, tileBoundsMax.y(), - tileBoundsMax.x(), rectangle.mHeight, tileBoundsMin.y(), + rectangle.mBounds.mMin.x(), rectangle.mHeight, rectangle.mBounds.mMin.y(), + rectangle.mBounds.mMin.x(), rectangle.mHeight, rectangle.mBounds.mMax.y(), + rectangle.mBounds.mMax.x(), rectangle.mHeight, rectangle.mBounds.mMax.y(), + rectangle.mBounds.mMax.x(), rectangle.mHeight, rectangle.mBounds.mMin.y(), }; const std::array indices { @@ -236,31 +222,42 @@ namespace areas.data(), static_cast(areas.size()), solid, - config.walkableClimb + params.mWalkableClimb ); } bool rasterizeTriangles(rcContext& context, const osg::Vec3f& agentHalfExtents, const std::vector& water, - const Settings& settings, const rcConfig& config, rcHeightfield& solid) + const RecastSettings& settings, const RecastParams& params, const TileBounds& realTileBounds, rcHeightfield& solid) { for (const CellWater& cellWater : water) { - const Rectangle rectangle = getSwimRectangle(cellWater, settings, agentHalfExtents); - if (!rasterizeTriangles(context, rectangle, config, AreaType_water, solid)) - return false; + const TileBounds cellTileBounds = maxCellTileBounds(cellWater.mCellPosition, cellWater.mWater.mCellSize); + if (auto intersection = getIntersection(realTileBounds, cellTileBounds)) + { + const Rectangle rectangle { + toNavMeshCoordinates(settings, *intersection), + toNavMeshCoordinates(settings, getSwimLevel(settings, cellWater.mWater.mLevel, agentHalfExtents.z())) + }; + if (!rasterizeTriangles(context, rectangle, AreaType_water, params, solid)) + return false; + } } return true; } - bool rasterizeTriangles(rcContext& context, const TileBounds& tileBounds, const std::vector& heightfields, - const Settings& settings, const rcConfig& config, rcHeightfield& solid) + bool rasterizeTriangles(rcContext& context, const TileBounds& realTileBounds, const std::vector& heightfields, + const RecastSettings& settings, const RecastParams& params, rcHeightfield& solid) { for (const FlatHeightfield& heightfield : heightfields) { - if (auto intersection = getIntersection(tileBounds, maxCellTileBounds(heightfield.mCellPosition, heightfield.mCellSize))) + const TileBounds cellTileBounds = maxCellTileBounds(heightfield.mCellPosition, heightfield.mCellSize); + if (auto intersection = getIntersection(realTileBounds, cellTileBounds)) { - const Rectangle rectangle {*intersection, toNavMeshCoordinates(settings, heightfield.mHeight)}; - if (!rasterizeTriangles(context, rectangle, config, AreaType_ground, solid)) + const Rectangle rectangle { + toNavMeshCoordinates(settings, *intersection), + toNavMeshCoordinates(settings, heightfield.mHeight) + }; + if (!rasterizeTriangles(context, rectangle, AreaType_ground, params, solid)) return false; } } @@ -268,27 +265,25 @@ namespace } bool rasterizeTriangles(rcContext& context, const std::vector& heightfields, - const Settings& settings, const rcConfig& config, rcHeightfield& solid) + const RecastSettings& settings, const RecastParams& params, rcHeightfield& solid) { - using BulletHelpers::makeProcessTriangleCallback; - for (const Heightfield& heightfield : heightfields) { const Mesh mesh = makeMesh(heightfield); - if (!rasterizeTriangles(context, mesh, settings, config, solid)) + if (!rasterizeTriangles(context, mesh, settings, params, solid)) return false; } return true; } bool rasterizeTriangles(rcContext& context, const TilePosition& tilePosition, const osg::Vec3f& agentHalfExtents, - const RecastMesh& recastMesh, const rcConfig& config, const Settings& settings, rcHeightfield& solid) + const RecastMesh& recastMesh, const RecastSettings& settings, const RecastParams& params, rcHeightfield& solid) { - return rasterizeTriangles(context, recastMesh.getMesh(), settings, config, solid) - && rasterizeTriangles(context, agentHalfExtents, recastMesh.getWater(), settings, config, solid) - && rasterizeTriangles(context, recastMesh.getHeightfields(), settings, config, solid) - && rasterizeTriangles(context, makeRealTileBoundsWithBorder(settings, tilePosition), - recastMesh.getFlatHeightfields(), settings, config, solid); + const TileBounds realTileBounds = makeRealTileBoundsWithBorder(settings, tilePosition); + return rasterizeTriangles(context, recastMesh.getMesh(), settings, params, solid) + && rasterizeTriangles(context, agentHalfExtents, recastMesh.getWater(), settings, params, realTileBounds, solid) + && rasterizeTriangles(context, recastMesh.getHeightfields(), settings, params, solid) + && rasterizeTriangles(context, realTileBounds, recastMesh.getFlatHeightfields(), settings, params, solid); } void buildCompactHeightfield(rcContext& context, const int walkableHeight, const int walkableClimb, @@ -359,27 +354,25 @@ namespace polyMesh.flags[i] = getFlag(static_cast(polyMesh.areas[i])); } - bool fillPolyMesh(rcContext& context, const rcConfig& config, rcHeightfield& solid, rcPolyMesh& polyMesh, - rcPolyMeshDetail& polyMeshDetail) + bool fillPolyMesh(rcContext& context, const RecastSettings& settings, const RecastParams& params, + rcHeightfield& solid, rcPolyMesh& polyMesh, rcPolyMeshDetail& polyMeshDetail) { rcCompactHeightfield compact; - compact.dist = nullptr; - buildCompactHeightfield(context, config.walkableHeight, config.walkableClimb, solid, compact); + buildCompactHeightfield(context, params.mWalkableHeight, params.mWalkableClimb, solid, compact); - erodeWalkableArea(context, config.walkableRadius, compact); + erodeWalkableArea(context, params.mWalkableRadius, compact); buildDistanceField(context, compact); - buildRegions(context, compact, config.borderSize, config.minRegionArea, config.mergeRegionArea); + buildRegions(context, compact, settings.mBorderSize, settings.mRegionMinArea, settings.mRegionMergeArea); rcContourSet contourSet; - buildContours(context, compact, config.maxSimplificationError, config.maxEdgeLen, contourSet); + buildContours(context, compact, settings.mMaxSimplificationError, params.mMaxEdgeLen, contourSet); if (contourSet.nconts == 0) return false; - buildPolyMesh(context, contourSet, config.maxVertsPerPoly, polyMesh); + buildPolyMesh(context, contourSet, settings.mMaxVertsPerPoly, polyMesh); - buildPolyMeshDetail(context, polyMesh, compact, config.detailSampleDist, config.detailSampleMaxError, - polyMeshDetail); + buildPolyMeshDetail(context, polyMesh, compact, params.mSampleDist, params.mSampleMaxError, polyMeshDetail); setPolyMeshFlags(polyMesh); @@ -395,7 +388,7 @@ namespace return power; } - std::pair getBoundsByZ(const RecastMesh& recastMesh, const osg::Vec3f& agentHalfExtents, const Settings& settings) + std::pair getBoundsByZ(const RecastMesh& recastMesh, const osg::Vec3f& agentHalfExtents, const RecastSettings& settings) { float minZ = 0; float maxZ = 0; @@ -437,38 +430,39 @@ namespace namespace DetourNavigator { std::unique_ptr prepareNavMeshTileData(const RecastMesh& recastMesh, - const TilePosition& tilePosition, const osg::Vec3f& agentHalfExtents, const Settings& settings) + const TilePosition& tilePosition, const osg::Vec3f& agentHalfExtents, const RecastSettings& settings) { - const auto [minZ, maxZ] = getBoundsByZ(recastMesh, agentHalfExtents, settings); - rcContext context; - const auto config = makeConfig(agentHalfExtents, tilePosition, toNavMeshCoordinates(settings, minZ), - toNavMeshCoordinates(settings, maxZ), settings); + + const auto [minZ, maxZ] = getBoundsByZ(recastMesh, agentHalfExtents, settings); rcHeightfield solid; - createHeightfield(context, solid, config.width, config.height, config.bmin, config.bmax, config.cs, config.ch); + initHeightfield(context, tilePosition, toNavMeshCoordinates(settings, minZ), + toNavMeshCoordinates(settings, maxZ), settings, solid); - if (!rasterizeTriangles(context, tilePosition, agentHalfExtents, recastMesh, config, settings, solid)) + const RecastParams params = makeRecastParams(settings, agentHalfExtents); + + if (!rasterizeTriangles(context, tilePosition, agentHalfExtents, recastMesh, settings, params, solid)) return nullptr; - rcFilterLowHangingWalkableObstacles(&context, config.walkableClimb, solid); - rcFilterLedgeSpans(&context, config.walkableHeight, config.walkableClimb, solid); - rcFilterWalkableLowHeightSpans(&context, config.walkableHeight, solid); + rcFilterLowHangingWalkableObstacles(&context, params.mWalkableClimb, solid); + rcFilterLedgeSpans(&context, params.mWalkableHeight, params.mWalkableClimb, solid); + rcFilterWalkableLowHeightSpans(&context, params.mWalkableHeight, solid); std::unique_ptr result = std::make_unique(); - if (!fillPolyMesh(context, config, solid, result->mPolyMesh, result->mPolyMeshDetail)) + if (!fillPolyMesh(context, settings, params, solid, result->mPolyMesh, result->mPolyMeshDetail)) return nullptr; - result->mCellSize = config.cs; - result->mCellHeight = config.ch; + result->mCellSize = settings.mCellSize; + result->mCellHeight = settings.mCellHeight; return result; } NavMeshData makeNavMeshTileData(const PreparedNavMeshData& data, const std::vector& offMeshConnections, const osg::Vec3f& agentHalfExtents, - const TilePosition& tile, const Settings& settings) + const TilePosition& tile, const RecastSettings& settings) { const auto offMeshConVerts = getOffMeshVerts(offMeshConnections); const std::vector offMeshConRad(offMeshConnections.size(), getRadius(settings, agentHalfExtents)); @@ -524,7 +518,7 @@ namespace DetourNavigator // Max tiles and max polys affect how the tile IDs are caculated. // There are 22 bits available for identifying a tile and a polygon. const int polysAndTilesBits = 22; - const auto polysBits = getMinValuableBitsNumber(settings.mMaxPolys); + const auto polysBits = getMinValuableBitsNumber(settings.mDetour.mMaxPolys); if (polysBits >= polysAndTilesBits) throw InvalidArgument("Too many polygons per tile"); @@ -533,8 +527,8 @@ namespace DetourNavigator dtNavMeshParams params; std::fill_n(params.orig, 3, 0.0f); - params.tileWidth = settings.mTileSize * settings.mCellSize; - params.tileHeight = settings.mTileSize * settings.mCellSize; + params.tileWidth = settings.mRecast.mTileSize * settings.mRecast.mCellSize; + params.tileHeight = settings.mRecast.mTileSize * settings.mRecast.mCellSize; params.maxTiles = 1 << tilesBits; params.maxPolys = 1 << polysBits; @@ -550,72 +544,4 @@ namespace DetourNavigator return navMesh; } - - UpdateNavMeshStatus updateNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh* recastMesh, - const TilePosition& changedTile, const TilePosition& playerTile, - const std::vector& offMeshConnections, const Settings& settings, - const SharedNavMeshCacheItem& navMeshCacheItem, NavMeshTilesCache& navMeshTilesCache, UpdateType updateType) - { - Log(Debug::Debug) << std::fixed << std::setprecision(2) << - "Update NavMesh with multiple tiles:" << - " agentHeight=" << getHeight(settings, agentHalfExtents) << - " agentMaxClimb=" << getMaxClimb(settings) << - " agentRadius=" << getRadius(settings, agentHalfExtents) << - " changedTile=(" << changedTile << ")" << - " playerTile=(" << playerTile << ")" << - " changedTileDistance=" << getDistance(changedTile, playerTile); - - if (!recastMesh) - { - Log(Debug::Debug) << "Ignore add tile: recastMesh is null"; - return navMeshCacheItem->lock()->removeTile(changedTile); - } - - if (recastMesh->getMesh().getIndices().empty() && recastMesh->getWater().empty() - && recastMesh->getHeightfields().empty() && recastMesh->getFlatHeightfields().empty()) - { - Log(Debug::Debug) << "Ignore add tile: recastMesh is empty"; - return navMeshCacheItem->lock()->removeTile(changedTile); - } - - const dtNavMeshParams params = *navMeshCacheItem->lockConst()->getImpl().getParams(); - - if (!shouldAddTile(changedTile, playerTile, std::min(settings.mMaxTilesNumber, params.maxTiles))) - { - Log(Debug::Debug) << "Ignore add tile: too far from player"; - return navMeshCacheItem->lock()->removeTile(changedTile); - } - - auto cachedNavMeshData = navMeshTilesCache.get(agentHalfExtents, changedTile, *recastMesh); - bool cached = static_cast(cachedNavMeshData); - - if (!cachedNavMeshData) - { - auto prepared = prepareNavMeshTileData(*recastMesh, changedTile, agentHalfExtents, settings); - - if (prepared == nullptr) - { - Log(Debug::Debug) << "Ignore add tile: NavMeshData is null"; - return navMeshCacheItem->lock()->removeTile(changedTile); - } - - if (updateType == UpdateType::Temporary) - return navMeshCacheItem->lock()->updateTile(changedTile, NavMeshTilesCache::Value(), - makeNavMeshTileData(*prepared, offMeshConnections, agentHalfExtents, changedTile, settings)); - - cachedNavMeshData = navMeshTilesCache.set(agentHalfExtents, changedTile, *recastMesh, std::move(prepared)); - - if (!cachedNavMeshData) - { - Log(Debug::Debug) << "Navigator cache overflow"; - return navMeshCacheItem->lock()->updateTile(changedTile, NavMeshTilesCache::Value(), - makeNavMeshTileData(*prepared, offMeshConnections, agentHalfExtents, changedTile, settings)); - } - } - - const auto updateStatus = navMeshCacheItem->lock()->updateTile(changedTile, std::move(cachedNavMeshData), - makeNavMeshTileData(cachedNavMeshData.get(), offMeshConnections, agentHalfExtents, changedTile, settings)); - - return UpdateNavMeshStatusBuilder(updateStatus).cached(cached).getResult(); - } } diff --git a/components/detournavigator/makenavmesh.hpp b/components/detournavigator/makenavmesh.hpp index 5b4169374b..14919ab134 100644 --- a/components/detournavigator/makenavmesh.hpp +++ b/components/detournavigator/makenavmesh.hpp @@ -7,6 +7,9 @@ #include "sharednavmesh.hpp" #include "navmeshtilescache.hpp" #include "offmeshconnection.hpp" +#include "navmeshdb.hpp" + +#include #include @@ -14,6 +17,7 @@ #include class dtNavMesh; +struct rcConfig; namespace DetourNavigator { @@ -38,25 +42,22 @@ namespace DetourNavigator return expectedTilesCount <= maxTiles; } - std::unique_ptr prepareNavMeshTileData(const RecastMesh& recastMesh, const TilePosition& tile, - const Bounds& bounds, const osg::Vec3f& agentHalfExtents, const Settings& settings); + inline bool isEmpty(const RecastMesh& recastMesh) + { + return recastMesh.getMesh().getIndices().empty() + && recastMesh.getWater().empty() + && recastMesh.getHeightfields().empty() + && recastMesh.getFlatHeightfields().empty(); + } + + std::unique_ptr prepareNavMeshTileData(const RecastMesh& recastMesh, + const TilePosition& tilePosition, const osg::Vec3f& agentHalfExtents, const RecastSettings& settings); NavMeshData makeNavMeshTileData(const PreparedNavMeshData& data, const std::vector& offMeshConnections, const osg::Vec3f& agentHalfExtents, - const TilePosition& tile, const Settings& settings); + const TilePosition& tile, const RecastSettings& settings); NavMeshPtr makeEmptyNavMesh(const Settings& settings); - - enum class UpdateType - { - Persistent, - Temporary - }; - - UpdateNavMeshStatus updateNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh* recastMesh, - const TilePosition& changedTile, const TilePosition& playerTile, - const std::vector& offMeshConnections, const Settings& settings, - const SharedNavMeshCacheItem& navMeshCacheItem, NavMeshTilesCache& navMeshTilesCache, UpdateType updateType); } #endif diff --git a/components/detournavigator/navigator.cpp b/components/detournavigator/navigator.cpp index 1877c3dd93..cf3c4ba5b3 100644 --- a/components/detournavigator/navigator.cpp +++ b/components/detournavigator/navigator.cpp @@ -5,10 +5,15 @@ namespace DetourNavigator { - std::unique_ptr makeNavigator(const Settings& settings) + std::unique_ptr makeNavigator(const Settings& settings, const std::string& userDataPath) { DetourNavigator::RecastGlobalAllocator::init(); - return std::make_unique(settings); + + std::unique_ptr db; + if (settings.mEnableNavMeshDiskCache) + db = std::make_unique(userDataPath + "/navmesh.db"); + + return std::make_unique(settings, std::move(db)); } std::unique_ptr makeNavigatorStub() diff --git a/components/detournavigator/navigator.hpp b/components/detournavigator/navigator.hpp index 6070a19fa8..14731fcc5b 100644 --- a/components/detournavigator/navigator.hpp +++ b/components/detournavigator/navigator.hpp @@ -6,9 +6,12 @@ #include "recastmeshtiles.hpp" #include "waitconditiontype.hpp" #include "heightfieldshape.hpp" +#include "objecttransform.hpp" #include +#include + namespace ESM { struct Cell; @@ -27,10 +30,14 @@ namespace DetourNavigator struct ObjectShapes { osg::ref_ptr mShapeInstance; + ObjectTransform mTransform; - ObjectShapes(const osg::ref_ptr& shapeInstance) + ObjectShapes(const osg::ref_ptr& shapeInstance, const ObjectTransform& transform) : mShapeInstance(shapeInstance) - {} + , mTransform(transform) + { + assert(mShapeInstance != nullptr); + } }; struct DoorShapes : ObjectShapes @@ -39,8 +46,8 @@ namespace DetourNavigator osg::Vec3f mConnectionEnd; DoorShapes(const osg::ref_ptr& shapeInstance, - const osg::Vec3f& connectionStart,const osg::Vec3f& connectionEnd) - : ObjectShapes(shapeInstance) + const ObjectTransform& transform, const osg::Vec3f& connectionStart, const osg::Vec3f& connectionEnd) + : ObjectShapes(shapeInstance, transform) , mConnectionStart(connectionStart) , mConnectionEnd(connectionEnd) {} @@ -70,6 +77,12 @@ namespace DetourNavigator */ virtual void removeAgent(const osg::Vec3f& agentHalfExtents) = 0; + /** + * @brief setWorldspace should be called before adding object from new worldspace + * @param worldspace + */ + virtual void setWorldspace(std::string_view worldspace) = 0; + /** * @brief addObject is used to add complex object with allowed to walk and avoided to walk shapes * @param id is used to distinguish different objects @@ -183,7 +196,7 @@ namespace DetourNavigator virtual float getMaxNavmeshAreaRealRadius() const = 0; }; - std::unique_ptr makeNavigator(const Settings& settings); + std::unique_ptr makeNavigator(const Settings& settings, const std::string& userDataPath); std::unique_ptr makeNavigatorStub(); } diff --git a/components/detournavigator/navigatorimpl.cpp b/components/detournavigator/navigatorimpl.cpp index f209a24c2c..85d86e6b2b 100644 --- a/components/detournavigator/navigatorimpl.cpp +++ b/components/detournavigator/navigatorimpl.cpp @@ -8,9 +8,9 @@ namespace DetourNavigator { - NavigatorImpl::NavigatorImpl(const Settings& settings) + NavigatorImpl::NavigatorImpl(const Settings& settings, std::unique_ptr&& db) : mSettings(settings) - , mNavMeshManager(mSettings) + , mNavMeshManager(mSettings, std::move(db)) , mUpdatesEnabled(true) { } @@ -32,14 +32,19 @@ namespace DetourNavigator --it->second; } + void NavigatorImpl::setWorldspace(std::string_view worldspace) + { + mNavMeshManager.setWorldspace(worldspace); + } + bool NavigatorImpl::addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) { - CollisionShape collisionShape {shapes.mShapeInstance, *shapes.mShapeInstance->mCollisionShape}; + const CollisionShape collisionShape(shapes.mShapeInstance, *shapes.mShapeInstance->mCollisionShape, shapes.mTransform); bool result = mNavMeshManager.addObject(id, collisionShape, transform, AreaType_ground); if (const btCollisionShape* const avoidShape = shapes.mShapeInstance->mAvoidCollisionShape.get()) { const ObjectId avoidId(avoidShape); - CollisionShape avoidCollisionShape {shapes.mShapeInstance, *avoidShape}; + const CollisionShape avoidCollisionShape(shapes.mShapeInstance, *avoidShape, shapes.mTransform); if (mNavMeshManager.addObject(avoidId, avoidCollisionShape, transform, AreaType_null)) { updateAvoidShapeId(id, avoidId); @@ -53,8 +58,8 @@ namespace DetourNavigator { if (addObject(id, static_cast(shapes), transform)) { - const osg::Vec3f start = toNavMeshCoordinates(mSettings, shapes.mConnectionStart); - const osg::Vec3f end = toNavMeshCoordinates(mSettings, shapes.mConnectionEnd); + const osg::Vec3f start = toNavMeshCoordinates(mSettings.mRecast, shapes.mConnectionStart); + const osg::Vec3f end = toNavMeshCoordinates(mSettings.mRecast, shapes.mConnectionEnd); mNavMeshManager.addOffMeshConnection(id, start, end, AreaType_door); mNavMeshManager.addOffMeshConnection(id, end, start, AreaType_door); return true; @@ -64,12 +69,12 @@ namespace DetourNavigator bool NavigatorImpl::updateObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) { - const CollisionShape collisionShape {shapes.mShapeInstance, *shapes.mShapeInstance->mCollisionShape}; + const CollisionShape collisionShape(shapes.mShapeInstance, *shapes.mShapeInstance->mCollisionShape, shapes.mTransform); bool result = mNavMeshManager.updateObject(id, collisionShape, transform, AreaType_ground); if (const btCollisionShape* const avoidShape = shapes.mShapeInstance->mAvoidCollisionShape.get()) { const ObjectId avoidId(avoidShape); - const CollisionShape avoidCollisionShape {shapes.mShapeInstance, *avoidShape}; + const CollisionShape avoidCollisionShape(shapes.mShapeInstance, *avoidShape, shapes.mTransform); if (mNavMeshManager.updateObject(avoidId, avoidCollisionShape, transform, AreaType_null)) { updateAvoidShapeId(id, avoidId); @@ -126,8 +131,8 @@ namespace DetourNavigator const auto dst = Misc::Convert::makeOsgVec3f(converter.toWorldPoint(pathgrid.mPoints[edge.mV1])); mNavMeshManager.addOffMeshConnection( ObjectId(&pathgrid), - toNavMeshCoordinates(mSettings, src), - toNavMeshCoordinates(mSettings, dst), + toNavMeshCoordinates(mSettings.mRecast, src), + toNavMeshCoordinates(mSettings.mRecast, dst), AreaType_pathgrid ); } @@ -149,7 +154,7 @@ namespace DetourNavigator void NavigatorImpl::updatePlayerPosition(const osg::Vec3f& playerPosition) { - const TilePosition tilePosition = getTilePosition(mSettings, toNavMeshCoordinates(mSettings, playerPosition)); + const TilePosition tilePosition = getTilePosition(mSettings.mRecast, toNavMeshCoordinates(mSettings.mRecast, playerPosition)); if (mLastPlayerPosition.has_value() && *mLastPlayerPosition == tilePosition) return; update(playerPosition); @@ -225,6 +230,6 @@ namespace DetourNavigator float NavigatorImpl::getMaxNavmeshAreaRealRadius() const { const auto& settings = getSettings(); - return getRealTileSize(settings) * getMaxNavmeshAreaRadius(settings); + return getRealTileSize(settings.mRecast) * getMaxNavmeshAreaRadius(settings); } } diff --git a/components/detournavigator/navigatorimpl.hpp b/components/detournavigator/navigatorimpl.hpp index 2c8474786f..116817395c 100644 --- a/components/detournavigator/navigatorimpl.hpp +++ b/components/detournavigator/navigatorimpl.hpp @@ -5,6 +5,7 @@ #include "navmeshmanager.hpp" #include +#include namespace DetourNavigator { @@ -15,12 +16,14 @@ namespace DetourNavigator * @brief Navigator constructor initializes all internal data. Constructed object is ready to build a scene. * @param settings allows to customize navigator work. Constructor is only place to set navigator settings. */ - explicit NavigatorImpl(const Settings& settings); + explicit NavigatorImpl(const Settings& settings, std::unique_ptr&& db); void addAgent(const osg::Vec3f& agentHalfExtents) override; void removeAgent(const osg::Vec3f& agentHalfExtents) override; + void setWorldspace(std::string_view worldspace) override; + bool addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) override; bool addObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform) override; diff --git a/components/detournavigator/navigatorstub.hpp b/components/detournavigator/navigatorstub.hpp index 1a6096feef..6a320c1a08 100644 --- a/components/detournavigator/navigatorstub.hpp +++ b/components/detournavigator/navigatorstub.hpp @@ -19,6 +19,8 @@ namespace DetourNavigator void removeAgent(const osg::Vec3f& /*agentHalfExtents*/) override {} + void setWorldspace(std::string_view /*worldspace*/) override {} + bool addObject(const ObjectId /*id*/, const ObjectShapes& /*shapes*/, const btTransform& /*transform*/) override { return false; diff --git a/components/detournavigator/navigatorutils.cpp b/components/detournavigator/navigatorutils.cpp index 82a108db6f..bc94e3f991 100644 --- a/components/detournavigator/navigatorutils.cpp +++ b/components/detournavigator/navigatorutils.cpp @@ -13,11 +13,11 @@ namespace DetourNavigator return std::nullopt; const auto settings = navigator.getSettings(); const auto result = DetourNavigator::findRandomPointAroundCircle(navMesh->lockConst()->getImpl(), - toNavMeshCoordinates(settings, agentHalfExtents), toNavMeshCoordinates(settings, start), - toNavMeshCoordinates(settings, maxRadius), includeFlags, settings); + toNavMeshCoordinates(settings.mRecast, agentHalfExtents), toNavMeshCoordinates(settings.mRecast, start), + toNavMeshCoordinates(settings.mRecast, maxRadius), includeFlags, settings.mDetour); if (!result) return std::nullopt; - return std::optional(fromNavMeshCoordinates(settings, *result)); + return std::optional(fromNavMeshCoordinates(settings.mRecast, *result)); } std::optional raycast(const Navigator& navigator, const osg::Vec3f& agentHalfExtents, const osg::Vec3f& start, @@ -28,10 +28,10 @@ namespace DetourNavigator return std::nullopt; const auto settings = navigator.getSettings(); const auto result = DetourNavigator::raycast(navMesh->lockConst()->getImpl(), - toNavMeshCoordinates(settings, agentHalfExtents), toNavMeshCoordinates(settings, start), - toNavMeshCoordinates(settings, end), includeFlags, settings); + toNavMeshCoordinates(settings.mRecast, agentHalfExtents), toNavMeshCoordinates(settings.mRecast, start), + toNavMeshCoordinates(settings.mRecast, end), includeFlags, settings.mDetour); if (!result) return std::nullopt; - return fromNavMeshCoordinates(settings, *result); + return fromNavMeshCoordinates(settings.mRecast, *result); } } diff --git a/components/detournavigator/navigatorutils.hpp b/components/detournavigator/navigatorutils.hpp index 4ccc238f97..8f3b6161f9 100644 --- a/components/detournavigator/navigatorutils.hpp +++ b/components/detournavigator/navigatorutils.hpp @@ -37,9 +37,9 @@ namespace DetourNavigator if (navMesh == nullptr) return Status::NavMeshNotFound; const auto settings = navigator.getSettings(); - return findSmoothPath(navMesh->lockConst()->getImpl(), toNavMeshCoordinates(settings, agentHalfExtents), - toNavMeshCoordinates(settings, stepSize), toNavMeshCoordinates(settings, start), - toNavMeshCoordinates(settings, end), includeFlags, areaCosts, settings, endTolerance, out); + return findSmoothPath(navMesh->lockConst()->getImpl(), toNavMeshCoordinates(settings.mRecast, agentHalfExtents), + toNavMeshCoordinates(settings.mRecast, stepSize), toNavMeshCoordinates(settings.mRecast, start), + toNavMeshCoordinates(settings.mRecast, end), includeFlags, areaCosts, settings, endTolerance, out); } /** diff --git a/components/detournavigator/navmeshcacheitem.cpp b/components/detournavigator/navmeshcacheitem.cpp index decf45de2c..ffcb0a7359 100644 --- a/components/detournavigator/navmeshcacheitem.cpp +++ b/components/detournavigator/navmeshcacheitem.cpp @@ -50,7 +50,8 @@ namespace DetourNavigator { return UpdateNavMeshStatus::ignored; } - const auto removed = ::removeTile(*mImpl, position); + bool removed = ::removeTile(*mImpl, position); + removed = mEmptyTiles.erase(position) > 0 || removed; const auto addStatus = addTile(*mImpl, navMeshData.mValue.get(), navMeshData.mSize); if (dtStatusSucceed(addStatus)) { @@ -82,7 +83,8 @@ namespace DetourNavigator UpdateNavMeshStatus NavMeshCacheItem::removeTile(const TilePosition& position) { - const auto removed = ::removeTile(*mImpl, position); + bool removed = ::removeTile(*mImpl, position); + removed = mEmptyTiles.erase(position) > 0 || removed; if (removed) { mUsedTiles.erase(position); @@ -90,4 +92,21 @@ namespace DetourNavigator } return UpdateNavMeshStatusBuilder().removed(removed).getResult(); } + + UpdateNavMeshStatus NavMeshCacheItem::markAsEmpty(const TilePosition& position) + { + bool removed = ::removeTile(*mImpl, position); + removed = mEmptyTiles.insert(position).second || removed; + if (removed) + { + mUsedTiles.erase(position); + ++mVersion.mRevision; + } + return UpdateNavMeshStatusBuilder().removed(removed).getResult(); + } + + bool NavMeshCacheItem::isEmptyTile(const TilePosition& position) const + { + return mEmptyTiles.find(position) != mEmptyTiles.end(); + } } diff --git a/components/detournavigator/navmeshcacheitem.hpp b/components/detournavigator/navmeshcacheitem.hpp index 5d3b404080..ae4a2de66b 100644 --- a/components/detournavigator/navmeshcacheitem.hpp +++ b/components/detournavigator/navmeshcacheitem.hpp @@ -12,6 +12,7 @@ #include #include +#include struct dtMeshTile; @@ -147,6 +148,10 @@ namespace DetourNavigator UpdateNavMeshStatus removeTile(const TilePosition& position); + UpdateNavMeshStatus markAsEmpty(const TilePosition& position); + + bool isEmptyTile(const TilePosition& position) const; + template void forEachUsedTile(Function&& function) const { @@ -166,6 +171,7 @@ namespace DetourNavigator NavMeshPtr mImpl; Version mVersion; std::map mUsedTiles; + std::set mEmptyTiles; }; using GuardedNavMeshCacheItem = Misc::ScopeGuarded; diff --git a/components/detournavigator/navmeshdb.cpp b/components/detournavigator/navmeshdb.cpp new file mode 100644 index 0000000000..ebff250ee0 --- /dev/null +++ b/components/detournavigator/navmeshdb.cpp @@ -0,0 +1,296 @@ +#include "navmeshdb.hpp" + +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include + +namespace DetourNavigator +{ + namespace + { + constexpr const char schema[] = R"( + BEGIN TRANSACTION; + + CREATE TABLE IF NOT EXISTS tiles ( + tile_id INTEGER PRIMARY KEY, + revision INTEGER NOT NULL DEFAULT 1, + worldspace TEXT NOT NULL, + tile_position_x INTEGER NOT NULL, + tile_position_y INTEGER NOT NULL, + version INTEGER NOT NULL, + input BLOB, + data BLOB + ); + + CREATE UNIQUE INDEX IF NOT EXISTS index_unique_tiles_by_worldspace_and_tile_position_and_input + ON tiles (worldspace, tile_position_x, tile_position_y, input); + + CREATE TABLE IF NOT EXISTS shapes ( + shape_id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + type INTEGER NOT NULL, + hash BLOB NOT NULL + ); + + CREATE UNIQUE INDEX IF NOT EXISTS index_unique_shapes_by_name_and_type_and_hash + ON shapes (name, type, hash); + + COMMIT; + )"; + + constexpr std::string_view getMaxTileIdQuery = R"( + SELECT max(tile_id) FROM tiles + )"; + + constexpr std::string_view findTileQuery = R"( + SELECT tile_id, version + FROM tiles + WHERE worldspace = :worldspace + AND tile_position_x = :tile_position_x + AND tile_position_y = :tile_position_y + AND input = :input + )"; + + constexpr std::string_view getTileDataQuery = R"( + SELECT tile_id, version, data + FROM tiles + WHERE worldspace = :worldspace + AND tile_position_x = :tile_position_x + AND tile_position_y = :tile_position_y + AND input = :input + )"; + + constexpr std::string_view insertTileQuery = R"( + INSERT INTO tiles ( tile_id, worldspace, version, tile_position_x, tile_position_y, input, data) + VALUES (:tile_id, :worldspace, :version, :tile_position_x, :tile_position_y, :input, :data) + )"; + + constexpr std::string_view updateTileQuery = R"( + UPDATE tiles + SET version = :version, + data = :data, + revision = revision + 1 + WHERE tile_id = :tile_id + )"; + + constexpr std::string_view getMaxShapeIdQuery = R"( + SELECT max(shape_id) FROM shapes + )"; + + constexpr std::string_view findShapeIdQuery = R"( + SELECT shape_id + FROM shapes + WHERE name = :name + AND type = :type + AND hash = :hash + )"; + + constexpr std::string_view insertShapeQuery = R"( + INSERT INTO shapes ( shape_id, name, type, hash) + VALUES (:shape_id, :name, :type, :hash) + )"; + } + + std::ostream& operator<<(std::ostream& stream, ShapeType value) + { + switch (value) + { + case ShapeType::Collision: return stream << "collision"; + case ShapeType::Avoid: return stream << "avoid"; + } + return stream << "unknown shape type (" << static_cast>(value) << ")"; + } + + NavMeshDb::NavMeshDb(std::string_view path) + : mDb(Sqlite3::makeDb(path, schema)) + , mGetMaxTileId(*mDb, DbQueries::GetMaxTileId {}) + , mFindTile(*mDb, DbQueries::FindTile {}) + , mGetTileData(*mDb, DbQueries::GetTileData {}) + , mInsertTile(*mDb, DbQueries::InsertTile {}) + , mUpdateTile(*mDb, DbQueries::UpdateTile {}) + , mGetMaxShapeId(*mDb, DbQueries::GetMaxShapeId {}) + , mFindShapeId(*mDb, DbQueries::FindShapeId {}) + , mInsertShape(*mDb, DbQueries::InsertShape {}) + { + } + + Sqlite3::Transaction NavMeshDb::startTransaction() + { + return Sqlite3::Transaction(*mDb); + } + + TileId NavMeshDb::getMaxTileId() + { + TileId tileId {0}; + request(*mDb, mGetMaxTileId, &tileId, 1); + return tileId; + } + + std::optional NavMeshDb::findTile(const std::string& worldspace, + const TilePosition& tilePosition, const std::vector& input) + { + Tile result; + auto row = std::tie(result.mTileId, result.mVersion); + const std::vector compressedInput = Misc::compress(input); + if (&row == request(*mDb, mFindTile, &row, 1, worldspace, tilePosition, compressedInput)) + return {}; + return result; + } + + std::optional NavMeshDb::getTileData(const std::string& worldspace, + const TilePosition& tilePosition, const std::vector& input) + { + TileData result; + auto row = std::tie(result.mTileId, result.mVersion, result.mData); + const std::vector compressedInput = Misc::compress(input); + if (&row == request(*mDb, mGetTileData, &row, 1, worldspace, tilePosition, compressedInput)) + return {}; + result.mData = Misc::decompress(result.mData); + return result; + } + + int NavMeshDb::insertTile(TileId tileId, const std::string& worldspace, const TilePosition& tilePosition, + TileVersion version, const std::vector& input, const std::vector& data) + { + const std::vector compressedInput = Misc::compress(input); + const std::vector compressedData = Misc::compress(data); + return execute(*mDb, mInsertTile, tileId, worldspace, tilePosition, version, compressedInput, compressedData); + } + + int NavMeshDb::updateTile(TileId tileId, TileVersion version, const std::vector& data) + { + const std::vector compressedData = Misc::compress(data); + return execute(*mDb, mUpdateTile, tileId, version, compressedData); + } + + ShapeId NavMeshDb::getMaxShapeId() + { + ShapeId shapeId {0}; + request(*mDb, mGetMaxShapeId, &shapeId, 1); + return shapeId; + } + + std::optional NavMeshDb::findShapeId(const std::string& name, ShapeType type, + const Sqlite3::ConstBlob& hash) + { + ShapeId shapeId; + if (&shapeId == request(*mDb, mFindShapeId, &shapeId, 1, name, type, hash)) + return {}; + return shapeId; + } + + int NavMeshDb::insertShape(ShapeId shapeId, const std::string& name, ShapeType type, + const Sqlite3::ConstBlob& hash) + { + return execute(*mDb, mInsertShape, shapeId, name, type, hash); + } + + namespace DbQueries + { + std::string_view GetMaxTileId::text() noexcept + { + return getMaxTileIdQuery; + } + + std::string_view FindTile::text() noexcept + { + return findTileQuery; + } + + void FindTile::bind(sqlite3& db, sqlite3_stmt& statement, const std::string& worldspace, + const TilePosition& tilePosition, const std::vector& input) + { + Sqlite3::bindParameter(db, statement, ":worldspace", worldspace); + Sqlite3::bindParameter(db, statement, ":tile_position_x", tilePosition.x()); + Sqlite3::bindParameter(db, statement, ":tile_position_y", tilePosition.y()); + Sqlite3::bindParameter(db, statement, ":input", input); + } + + std::string_view GetTileData::text() noexcept + { + return getTileDataQuery; + } + + void GetTileData::bind(sqlite3& db, sqlite3_stmt& statement, const std::string& worldspace, + const TilePosition& tilePosition, const std::vector& input) + { + Sqlite3::bindParameter(db, statement, ":worldspace", worldspace); + Sqlite3::bindParameter(db, statement, ":tile_position_x", tilePosition.x()); + Sqlite3::bindParameter(db, statement, ":tile_position_y", tilePosition.y()); + Sqlite3::bindParameter(db, statement, ":input", input); + } + + std::string_view InsertTile::text() noexcept + { + return insertTileQuery; + } + + void InsertTile::bind(sqlite3& db, sqlite3_stmt& statement, TileId tileId, const std::string& worldspace, + const TilePosition& tilePosition, TileVersion version, const std::vector& input, + const std::vector& data) + { + Sqlite3::bindParameter(db, statement, ":tile_id", tileId); + Sqlite3::bindParameter(db, statement, ":worldspace", worldspace); + Sqlite3::bindParameter(db, statement, ":tile_position_x", tilePosition.x()); + Sqlite3::bindParameter(db, statement, ":tile_position_y", tilePosition.y()); + Sqlite3::bindParameter(db, statement, ":version", version); + Sqlite3::bindParameter(db, statement, ":input", input); + Sqlite3::bindParameter(db, statement, ":data", data); + } + + std::string_view UpdateTile::text() noexcept + { + return updateTileQuery; + } + + void UpdateTile::bind(sqlite3& db, sqlite3_stmt& statement, TileId tileId, TileVersion version, + const std::vector& data) + { + Sqlite3::bindParameter(db, statement, ":tile_id", tileId); + Sqlite3::bindParameter(db, statement, ":version", version); + Sqlite3::bindParameter(db, statement, ":data", data); + } + + std::string_view GetMaxShapeId::text() noexcept + { + return getMaxShapeIdQuery; + } + + std::string_view FindShapeId::text() noexcept + { + return findShapeIdQuery; + } + + void FindShapeId::bind(sqlite3& db, sqlite3_stmt& statement, const std::string& name, + ShapeType type, const Sqlite3::ConstBlob& hash) + { + Sqlite3::bindParameter(db, statement, ":name", name); + Sqlite3::bindParameter(db, statement, ":type", static_cast(type)); + Sqlite3::bindParameter(db, statement, ":hash", hash); + } + + std::string_view InsertShape::text() noexcept + { + return insertShapeQuery; + } + + void InsertShape::bind(sqlite3& db, sqlite3_stmt& statement, ShapeId shapeId, const std::string& name, + ShapeType type, const Sqlite3::ConstBlob& hash) + { + Sqlite3::bindParameter(db, statement, ":shape_id", shapeId); + Sqlite3::bindParameter(db, statement, ":name", name); + Sqlite3::bindParameter(db, statement, ":type", static_cast(type)); + Sqlite3::bindParameter(db, statement, ":hash", hash); + } + } +} diff --git a/components/detournavigator/navmeshdb.hpp b/components/detournavigator/navmeshdb.hpp new file mode 100644 index 0000000000..636f1de000 --- /dev/null +++ b/components/detournavigator/navmeshdb.hpp @@ -0,0 +1,153 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHDB_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHDB_H + +#include "tileposition.hpp" + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct sqlite3; +struct sqlite3_stmt; + +namespace DetourNavigator +{ + BOOST_STRONG_TYPEDEF(std::int64_t, TileId) + BOOST_STRONG_TYPEDEF(std::int64_t, TileRevision) + BOOST_STRONG_TYPEDEF(std::int64_t, TileVersion) + BOOST_STRONG_TYPEDEF(std::int64_t, ShapeId) + + struct Tile + { + TileId mTileId; + TileVersion mVersion; + }; + + struct TileData + { + TileId mTileId; + TileVersion mVersion; + std::vector mData; + }; + + enum class ShapeType + { + Collision = 1, + Avoid = 2, + }; + + std::ostream& operator<<(std::ostream& stream, ShapeType value); + + namespace DbQueries + { + struct GetMaxTileId + { + static std::string_view text() noexcept; + static void bind(sqlite3&, sqlite3_stmt&) {} + }; + + struct FindTile + { + static std::string_view text() noexcept; + static void bind(sqlite3& db, sqlite3_stmt& statement, const std::string& worldspace, + const TilePosition& tilePosition, const std::vector& input); + }; + + struct GetTileData + { + static std::string_view text() noexcept; + static void bind(sqlite3& db, sqlite3_stmt& statement, const std::string& worldspace, + const TilePosition& tilePosition, const std::vector& input); + }; + + struct InsertTile + { + static std::string_view text() noexcept; + static void bind(sqlite3& db, sqlite3_stmt& statement, TileId tileId, const std::string& worldspace, + const TilePosition& tilePosition, TileVersion version, const std::vector& input, + const std::vector& data); + }; + + struct UpdateTile + { + static std::string_view text() noexcept; + static void bind(sqlite3& db, sqlite3_stmt& statement, TileId tileId, TileVersion version, + const std::vector& data); + }; + + struct GetMaxShapeId + { + static std::string_view text() noexcept; + static void bind(sqlite3&, sqlite3_stmt&) {} + }; + + struct FindShapeId + { + static std::string_view text() noexcept; + static void bind(sqlite3& db, sqlite3_stmt& statement, const std::string& name, + ShapeType type, const Sqlite3::ConstBlob& hash); + }; + + struct InsertShape + { + static std::string_view text() noexcept; + static void bind(sqlite3& db, sqlite3_stmt& statement, ShapeId shapeId, const std::string& name, + ShapeType type, const Sqlite3::ConstBlob& hash); + }; + } + + class NavMeshDb + { + public: + explicit NavMeshDb(std::string_view path); + + Sqlite3::Transaction startTransaction(); + + TileId getMaxTileId(); + + std::optional findTile(const std::string& worldspace, + const TilePosition& tilePosition, const std::vector& input); + + std::optional getTileData(const std::string& worldspace, + const TilePosition& tilePosition, const std::vector& input); + + int insertTile(TileId tileId, const std::string& worldspace, const TilePosition& tilePosition, + TileVersion version, const std::vector& input, const std::vector& data); + + int updateTile(TileId tileId, TileVersion version, const std::vector& data); + + ShapeId getMaxShapeId(); + + std::optional findShapeId(const std::string& name, ShapeType type, const Sqlite3::ConstBlob& hash); + + int insertShape(ShapeId shapeId, const std::string& name, ShapeType type, const Sqlite3::ConstBlob& hash); + + private: + Sqlite3::Db mDb; + Sqlite3::Statement mGetMaxTileId; + Sqlite3::Statement mFindTile; + Sqlite3::Statement mGetTileData; + Sqlite3::Statement mInsertTile; + Sqlite3::Statement mUpdateTile; + Sqlite3::Statement mGetMaxShapeId; + Sqlite3::Statement mFindShapeId; + Sqlite3::Statement mInsertShape; + }; +} + +#endif diff --git a/components/detournavigator/navmeshdbutils.cpp b/components/detournavigator/navmeshdbutils.cpp new file mode 100644 index 0000000000..dce6ef3a72 --- /dev/null +++ b/components/detournavigator/navmeshdbutils.cpp @@ -0,0 +1,40 @@ +#include "navmeshdbutils.hpp" +#include "navmeshdb.hpp" +#include "recastmesh.hpp" + +#include + +#include + +namespace DetourNavigator +{ + namespace + { + ShapeId getShapeId(NavMeshDb& db, const std::string& name, ShapeType type, const std::string& hash, ShapeId& nextShapeId) + { + const Sqlite3::ConstBlob hashData {hash.data(), static_cast(hash.size())}; + if (const auto existingShapeId = db.findShapeId(name, type, hashData)) + return *existingShapeId; + const ShapeId newShapeId = nextShapeId; + db.insertShape(newShapeId, name, type, hashData); + Log(Debug::Verbose) << "Added " << name << " " << type << " shape to navmeshdb with id " << newShapeId; + ++nextShapeId.t; + return newShapeId; + } + } + + ShapeId resolveMeshSource(NavMeshDb& db, const MeshSource& source, ShapeId& nextShapeId) + { + switch (source.mAreaType) + { + case AreaType_null: + return getShapeId(db, source.mShape->mFileName, ShapeType::Avoid, source.mShape->mFileHash, nextShapeId); + case AreaType_ground: + return getShapeId(db, source.mShape->mFileName, ShapeType::Collision, source.mShape->mFileHash, nextShapeId); + default: + Log(Debug::Warning) << "Trying to resolve recast mesh source with unsupported area type: " << source.mAreaType; + assert(false); + return ShapeId(0); + } + } +} diff --git a/components/detournavigator/navmeshdbutils.hpp b/components/detournavigator/navmeshdbutils.hpp new file mode 100644 index 0000000000..02b3bdbb00 --- /dev/null +++ b/components/detournavigator/navmeshdbutils.hpp @@ -0,0 +1,13 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHDBUTILS_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHDBUTILS_H + +#include "navmeshdb.hpp" + +namespace DetourNavigator +{ + struct MeshSource; + + ShapeId resolveMeshSource(NavMeshDb& db, const MeshSource& source, ShapeId& nextShapeId); +} + +#endif diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index fff7d8d7cc..399af8a6a9 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -41,13 +41,23 @@ namespace namespace DetourNavigator { - NavMeshManager::NavMeshManager(const Settings& settings) + NavMeshManager::NavMeshManager(const Settings& settings, std::unique_ptr&& db) : mSettings(settings) - , mRecastMeshManager(settings) - , mOffMeshConnectionsManager(settings) - , mAsyncNavMeshUpdater(settings, mRecastMeshManager, mOffMeshConnectionsManager) + , mRecastMeshManager(settings.mRecast) + , mOffMeshConnectionsManager(settings.mRecast) + , mAsyncNavMeshUpdater(settings, mRecastMeshManager, mOffMeshConnectionsManager, std::move(db)) {} + void NavMeshManager::setWorldspace(std::string_view worldspace) + { + if (worldspace == mWorldspace) + return; + mRecastMeshManager.setWorldspace(worldspace); + for (auto& [agent, cache] : mCache) + cache = std::make_shared(makeEmptyNavMesh(mSettings), ++mGenerationCounter); + mWorldspace = worldspace; + } + bool NavMeshManager::addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType) { @@ -140,8 +150,8 @@ namespace DetourNavigator { mOffMeshConnectionsManager.add(id, OffMeshConnection {start, end, areaType}); - const auto startTilePosition = getTilePosition(mSettings, start); - const auto endTilePosition = getTilePosition(mSettings, end); + const auto startTilePosition = getTilePosition(mSettings.mRecast, start); + const auto endTilePosition = getTilePosition(mSettings.mRecast, end); addChangedTile(startTilePosition, ChangeType::add); @@ -158,7 +168,7 @@ namespace DetourNavigator void NavMeshManager::update(const osg::Vec3f& playerPosition, const osg::Vec3f& agentHalfExtents) { - const auto playerTile = getTilePosition(mSettings, toNavMeshCoordinates(mSettings, playerPosition)); + const auto playerTile = getTilePosition(mSettings.mRecast, toNavMeshCoordinates(mSettings.mRecast, playerPosition)); auto& lastRevision = mLastRecastMeshManagerRevision[agentHalfExtents]; auto lastPlayerTile = mPlayerTile.find(agentHalfExtents); if (lastRevision == mRecastMeshManager.getRevision() && lastPlayerTile != mPlayerTile.end() @@ -201,14 +211,14 @@ namespace DetourNavigator const auto shouldAdd = shouldAddTile(tile, playerTile, maxTiles); const auto presentInNavMesh = bool(navMesh.getTileAt(tile.x(), tile.y(), 0)); if (shouldAdd && !presentInNavMesh) - tilesToPost.insert(std::make_pair(tile, ChangeType::add)); + tilesToPost.insert(std::make_pair(tile, locked->isEmptyTile(tile) ? ChangeType::update : ChangeType::add)); else if (!shouldAdd && presentInNavMesh) tilesToPost.insert(std::make_pair(tile, ChangeType::mixed)); else recastMeshManager.reportNavMeshChange(recastMeshManager.getVersion(), Version {0, 0}); }); } - mAsyncNavMeshUpdater.post(agentHalfExtents, cached, playerTile, tilesToPost); + mAsyncNavMeshUpdater.post(agentHalfExtents, cached, playerTile, mRecastMeshManager.getWorldspace(), tilesToPost); if (changedTiles != mChangedTiles.end()) changedTiles->second.clear(); Log(Debug::Debug) << "Cache update posted for agent=" << agentHalfExtents << @@ -233,7 +243,7 @@ namespace DetourNavigator void NavMeshManager::reportStats(unsigned int frameNumber, osg::Stats& stats) const { - mAsyncNavMeshUpdater.reportStats(frameNumber, stats); + DetourNavigator::reportStats(mAsyncNavMeshUpdater.getStats(), frameNumber, stats); } RecastMeshTiles NavMeshManager::getRecastMeshTiles() const @@ -241,9 +251,10 @@ namespace DetourNavigator std::vector tiles; mRecastMeshManager.forEachTile( [&tiles] (const TilePosition& tile, const CachedRecastMeshManager&) { tiles.push_back(tile); }); + const std::string worldspace = mRecastMeshManager.getWorldspace(); RecastMeshTiles result; for (const TilePosition& tile : tiles) - if (auto mesh = mRecastMeshManager.getCachedMesh(tile)) + if (auto mesh = mRecastMeshManager.getCachedMesh(worldspace, tile)) result.emplace(tile, std::move(mesh)); return result; } @@ -251,7 +262,7 @@ namespace DetourNavigator void NavMeshManager::addChangedTiles(const btCollisionShape& shape, const btTransform& transform, const ChangeType changeType) { - getTilesPositions(shape, transform, mSettings, + getTilesPositions(shape, transform, mSettings.mRecast, [&] (const TilePosition& v) { addChangedTile(v, changeType); }); } @@ -261,7 +272,7 @@ namespace DetourNavigator if (cellSize == std::numeric_limits::max()) return; - getTilesPositions(cellSize, shift, mSettings, + getTilesPositions(cellSize, shift, mSettings.mRecast, [&] (const TilePosition& v) { addChangedTile(v, changeType); }); } diff --git a/components/detournavigator/navmeshmanager.hpp b/components/detournavigator/navmeshmanager.hpp index b1926741c5..3fd2d28d74 100644 --- a/components/detournavigator/navmeshmanager.hpp +++ b/components/detournavigator/navmeshmanager.hpp @@ -22,7 +22,9 @@ namespace DetourNavigator class NavMeshManager { public: - NavMeshManager(const Settings& settings); + explicit NavMeshManager(const Settings& settings, std::unique_ptr&& db); + + void setWorldspace(std::string_view worldspace); bool addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType); @@ -62,6 +64,7 @@ namespace DetourNavigator private: const Settings& mSettings; + std::string mWorldspace; TileCachedRecastMeshManager mRecastMeshManager; OffMeshConnectionsManager mOffMeshConnectionsManager; AsyncNavMeshUpdater mAsyncNavMeshUpdater; diff --git a/components/detournavigator/navmeshtilescache.cpp b/components/detournavigator/navmeshtilescache.cpp index 3d595f13a8..bbda8a3179 100644 --- a/components/detournavigator/navmeshtilescache.cpp +++ b/components/detournavigator/navmeshtilescache.cpp @@ -79,12 +79,11 @@ namespace DetourNavigator return result; } - void NavMeshTilesCache::reportStats(unsigned int frameNumber, osg::Stats& out) const + void reportStats(const NavMeshTilesCache::Stats& stats, unsigned int frameNumber, osg::Stats& out) { - const Stats stats = getStats(); - out.setAttribute(frameNumber, "NavMesh CacheSize", stats.mNavMeshCacheSize); - out.setAttribute(frameNumber, "NavMesh UsedTiles", stats.mUsedNavMeshTiles); - out.setAttribute(frameNumber, "NavMesh CachedTiles", stats.mCachedNavMeshTiles); + out.setAttribute(frameNumber, "NavMesh CacheSize", static_cast(stats.mNavMeshCacheSize)); + out.setAttribute(frameNumber, "NavMesh UsedTiles", static_cast(stats.mUsedNavMeshTiles)); + out.setAttribute(frameNumber, "NavMesh CachedTiles", static_cast(stats.mCachedNavMeshTiles)); if (stats.mGetCount > 0) out.setAttribute(frameNumber, "NavMesh CacheHitRate", static_cast(stats.mHitCount) / stats.mGetCount * 100.0); } diff --git a/components/detournavigator/navmeshtilescache.hpp b/components/detournavigator/navmeshtilescache.hpp index fdafa0c6d6..e7e0b6c7a8 100644 --- a/components/detournavigator/navmeshtilescache.hpp +++ b/components/detournavigator/navmeshtilescache.hpp @@ -144,8 +144,6 @@ namespace DetourNavigator Stats getStats() const; - void reportStats(unsigned int frameNumber, osg::Stats& stats) const; - private: mutable std::mutex mMutex; std::size_t mMaxNavMeshDataSize; @@ -163,6 +161,8 @@ namespace DetourNavigator void releaseItem(ItemIterator iterator); }; + + void reportStats(const NavMeshTilesCache::Stats& stats, unsigned int frameNumber, osg::Stats& out); } #endif diff --git a/components/detournavigator/objectid.hpp b/components/detournavigator/objectid.hpp index 9c4b5b2710..22fc792c6f 100644 --- a/components/detournavigator/objectid.hpp +++ b/components/detournavigator/objectid.hpp @@ -15,6 +15,11 @@ namespace DetourNavigator { } + explicit ObjectId(std::size_t value) noexcept + : mValue(value) + { + } + std::size_t value() const noexcept { return mValue; diff --git a/components/detournavigator/objecttransform.hpp b/components/detournavigator/objecttransform.hpp new file mode 100644 index 0000000000..2da9a25348 --- /dev/null +++ b/components/detournavigator/objecttransform.hpp @@ -0,0 +1,27 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_OBJECTTRANSFORM_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_OBJECTTRANSFORM_H + +#include + +#include + +namespace DetourNavigator +{ + struct ObjectTransform + { + ESM::Position mPosition; + float mScale; + + friend inline auto tie(const ObjectTransform& v) + { + return std::tie(v.mPosition, v.mScale); + } + + friend inline bool operator<(const ObjectTransform& l, const ObjectTransform& r) + { + return tie(l) < tie(r); + } + }; +} + +#endif diff --git a/components/detournavigator/offmeshconnectionsmanager.cpp b/components/detournavigator/offmeshconnectionsmanager.cpp index a673ae3e68..a11da21218 100644 --- a/components/detournavigator/offmeshconnectionsmanager.cpp +++ b/components/detournavigator/offmeshconnectionsmanager.cpp @@ -11,7 +11,7 @@ namespace DetourNavigator { - OffMeshConnectionsManager::OffMeshConnectionsManager(const Settings& settings) + OffMeshConnectionsManager::OffMeshConnectionsManager(const RecastSettings& settings) : mSettings(settings) {} @@ -65,11 +65,11 @@ namespace DetourNavigator return removed; } - std::vector OffMeshConnectionsManager::get(const TilePosition& tilePosition) + std::vector OffMeshConnectionsManager::get(const TilePosition& tilePosition) const { std::vector result; - const auto values = mValues.lock(); + const auto values = mValues.lockConst(); const auto itByTilePosition = values->mByTilePosition.find(tilePosition); diff --git a/components/detournavigator/offmeshconnectionsmanager.hpp b/components/detournavigator/offmeshconnectionsmanager.hpp index 20a6427cd5..455b03276a 100644 --- a/components/detournavigator/offmeshconnectionsmanager.hpp +++ b/components/detournavigator/offmeshconnectionsmanager.hpp @@ -18,13 +18,13 @@ namespace DetourNavigator class OffMeshConnectionsManager { public: - OffMeshConnectionsManager(const Settings& settings); + explicit OffMeshConnectionsManager(const RecastSettings& settings); void add(const ObjectId id, const OffMeshConnection& value); std::set remove(const ObjectId id); - std::vector get(const TilePosition& tilePosition); + std::vector get(const TilePosition& tilePosition) const; private: struct Values @@ -33,7 +33,7 @@ namespace DetourNavigator std::map> mByTilePosition; }; - const Settings& mSettings; + const RecastSettings& mSettings; Misc::ScopeGuarded mValues; }; } diff --git a/components/detournavigator/preparednavmeshdata.cpp b/components/detournavigator/preparednavmeshdata.cpp index c86e18482c..a737ae19a5 100644 --- a/components/detournavigator/preparednavmeshdata.cpp +++ b/components/detournavigator/preparednavmeshdata.cpp @@ -1,8 +1,10 @@ #include "preparednavmeshdata.hpp" #include "preparednavmeshdatatuple.hpp" +#include "recast.hpp" #include -#include + +#include namespace { @@ -15,13 +17,6 @@ namespace value.nverts = 0; value.ntris = 0; } - - void freePolyMeshDetail(rcPolyMeshDetail& value) noexcept - { - rcFree(value.meshes); - rcFree(value.verts); - rcFree(value.tris); - } } namespace DetourNavigator @@ -31,6 +26,15 @@ namespace DetourNavigator initPolyMeshDetail(mPolyMeshDetail); } + PreparedNavMeshData::PreparedNavMeshData(const PreparedNavMeshData& other) + : mUserId(other.mUserId) + , mCellSize(other.mCellSize) + , mCellHeight(other.mCellHeight) + { + copyPolyMesh(other.mPolyMesh, mPolyMesh); + copyPolyMeshDetail(other.mPolyMeshDetail, mPolyMeshDetail); + } + PreparedNavMeshData::~PreparedNavMeshData() noexcept { freePolyMeshDetail(mPolyMeshDetail); diff --git a/components/detournavigator/preparednavmeshdata.hpp b/components/detournavigator/preparednavmeshdata.hpp index 3566cfc71b..b3de7a447f 100644 --- a/components/detournavigator/preparednavmeshdata.hpp +++ b/components/detournavigator/preparednavmeshdata.hpp @@ -18,7 +18,7 @@ namespace DetourNavigator rcPolyMeshDetail mPolyMeshDetail; PreparedNavMeshData() noexcept; - PreparedNavMeshData(const PreparedNavMeshData&) = delete; + PreparedNavMeshData(const PreparedNavMeshData& other); ~PreparedNavMeshData() noexcept; diff --git a/components/detournavigator/raycast.cpp b/components/detournavigator/raycast.cpp index 271da22496..be3217ba40 100644 --- a/components/detournavigator/raycast.cpp +++ b/components/detournavigator/raycast.cpp @@ -10,7 +10,7 @@ namespace DetourNavigator { std::optional raycast(const dtNavMesh& navMesh, const osg::Vec3f& halfExtents, - const osg::Vec3f& start, const osg::Vec3f& end, const Flags includeFlags, const Settings& settings) + const osg::Vec3f& start, const osg::Vec3f& end, const Flags includeFlags, const DetourSettings& settings) { dtNavMeshQuery navMeshQuery; if (!initNavMeshQuery(navMeshQuery, navMesh, settings.mMaxNavMeshQueryNodes)) diff --git a/components/detournavigator/raycast.hpp b/components/detournavigator/raycast.hpp index ddf61b49f4..60cdf0a157 100644 --- a/components/detournavigator/raycast.hpp +++ b/components/detournavigator/raycast.hpp @@ -10,10 +10,10 @@ class dtNavMesh; namespace DetourNavigator { - struct Settings; + struct DetourSettings; std::optional raycast(const dtNavMesh& navMesh, const osg::Vec3f& halfExtents, - const osg::Vec3f& start, const osg::Vec3f& end, const Flags includeFlags, const Settings& settings); + const osg::Vec3f& start, const osg::Vec3f& end, const Flags includeFlags, const DetourSettings& settings); } #endif diff --git a/components/detournavigator/recast.cpp b/components/detournavigator/recast.cpp new file mode 100644 index 0000000000..c1d14c0aa8 --- /dev/null +++ b/components/detournavigator/recast.cpp @@ -0,0 +1,80 @@ +#include "recast.hpp" + +#include +#include + +#include +#include + +namespace DetourNavigator +{ + void* permRecastAlloc(std::size_t size) + { + void* const result = rcAlloc(size, RC_ALLOC_PERM); + if (result == nullptr) + throw std::bad_alloc(); + return result; + } + + void permRecastAlloc(rcPolyMesh& value) + { + permRecastAlloc(value.verts, getVertsLength(value)); + permRecastAlloc(value.polys, getPolysLength(value)); + permRecastAlloc(value.regs, getRegsLength(value)); + permRecastAlloc(value.flags, getFlagsLength(value)); + permRecastAlloc(value.areas, getAreasLength(value)); + } + + void permRecastAlloc(rcPolyMeshDetail& value) + { + try + { + permRecastAlloc(value.meshes, getMeshesLength(value)); + permRecastAlloc(value.verts, getVertsLength(value)); + permRecastAlloc(value.tris, getTrisLength(value)); + } + catch (...) + { + freePolyMeshDetail(value); + throw; + } + } + + void freePolyMeshDetail(rcPolyMeshDetail& value) noexcept + { + rcFree(value.meshes); + rcFree(value.verts); + rcFree(value.tris); + } + + void copyPolyMesh(const rcPolyMesh& src, rcPolyMesh& dst) + { + dst.nverts = src.nverts; + dst.npolys = src.npolys; + dst.maxpolys = src.maxpolys; + dst.nvp = src.nvp; + rcVcopy(dst.bmin, src.bmin); + rcVcopy(dst.bmax, src.bmax); + dst.cs = src.cs; + dst.ch = src.ch; + dst.borderSize = src.borderSize; + dst.maxEdgeError = src.maxEdgeError; + permRecastAlloc(dst); + std::memcpy(dst.verts, src.verts, getVertsLength(src) * sizeof(*dst.verts)); + std::memcpy(dst.polys, src.polys, getPolysLength(src) * sizeof(*dst.polys)); + std::memcpy(dst.regs, src.regs, getRegsLength(src) * sizeof(*dst.regs)); + std::memcpy(dst.flags, src.flags, getFlagsLength(src) * sizeof(*dst.flags)); + std::memcpy(dst.areas, src.areas, getAreasLength(src) * sizeof(*dst.areas)); + } + + void copyPolyMeshDetail(const rcPolyMeshDetail& src, rcPolyMeshDetail& dst) + { + dst.nmeshes = src.nmeshes; + dst.nverts = src.nverts; + dst.ntris = src.ntris; + permRecastAlloc(dst); + std::memcpy(dst.meshes, src.meshes, getMeshesLength(src) * sizeof(*dst.meshes)); + std::memcpy(dst.verts, src.verts, getVertsLength(src) * sizeof(*dst.verts)); + std::memcpy(dst.tris, src.tris, getTrisLength(src) * sizeof(*dst.tris)); + } +} diff --git a/components/detournavigator/recast.hpp b/components/detournavigator/recast.hpp index 4e9ab329b7..1811d35772 100644 --- a/components/detournavigator/recast.hpp +++ b/components/detournavigator/recast.hpp @@ -2,8 +2,10 @@ #define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECAST_H #include +#include #include +#include namespace DetourNavigator { @@ -46,6 +48,25 @@ namespace DetourNavigator { return 4 * static_cast(value.ntris); } + + void* permRecastAlloc(std::size_t size); + + template + inline void permRecastAlloc(T*& values, std::size_t size) + { + static_assert(std::is_arithmetic_v); + values = new (permRecastAlloc(size * sizeof(T))) T[size]; + } + + void permRecastAlloc(rcPolyMesh& value); + + void permRecastAlloc(rcPolyMeshDetail& value); + + void freePolyMeshDetail(rcPolyMeshDetail& value) noexcept; + + void copyPolyMesh(const rcPolyMesh& src, rcPolyMesh& dst); + + void copyPolyMeshDetail(const rcPolyMeshDetail& src, rcPolyMeshDetail& dst); } #endif diff --git a/components/detournavigator/recastmesh.cpp b/components/detournavigator/recastmesh.cpp index 93a0e171a1..16220d74f1 100644 --- a/components/detournavigator/recastmesh.cpp +++ b/components/detournavigator/recastmesh.cpp @@ -19,13 +19,15 @@ namespace DetourNavigator } RecastMesh::RecastMesh(std::size_t generation, std::size_t revision, Mesh mesh, std::vector water, - std::vector heightfields, std::vector flatHeightfields) + std::vector heightfields, std::vector flatHeightfields, + std::vector meshSources) : mGeneration(generation) , mRevision(revision) , mMesh(std::move(mesh)) , mWater(std::move(water)) , mHeightfields(std::move(heightfields)) , mFlatHeightfields(std::move(flatHeightfields)) + , mMeshSources(std::move(meshSources)) { mWater.shrink_to_fit(); mHeightfields.shrink_to_fit(); diff --git a/components/detournavigator/recastmesh.hpp b/components/detournavigator/recastmesh.hpp index 3cfe9e1cab..df9d6414d5 100644 --- a/components/detournavigator/recastmesh.hpp +++ b/components/detournavigator/recastmesh.hpp @@ -4,8 +4,10 @@ #include "areatype.hpp" #include "bounds.hpp" #include "tilebounds.hpp" +#include "objecttransform.hpp" #include +#include #include #include @@ -119,11 +121,19 @@ namespace DetourNavigator return tie(lhs) < tie(rhs); } + struct MeshSource + { + osg::ref_ptr mShape; + ObjectTransform mObjectTransform; + AreaType mAreaType; + }; + class RecastMesh { public: RecastMesh(std::size_t generation, std::size_t revision, Mesh mesh, std::vector water, - std::vector heightfields, std::vector flatHeightfields); + std::vector heightfields, std::vector flatHeightfields, + std::vector sources); std::size_t getGeneration() const { @@ -152,6 +162,8 @@ namespace DetourNavigator return mFlatHeightfields; } + const std::vector& getMeshSources() const noexcept { return mMeshSources; } + private: std::size_t mGeneration; std::size_t mRevision; @@ -159,6 +171,7 @@ namespace DetourNavigator std::vector mWater; std::vector mHeightfields; std::vector mFlatHeightfields; + std::vector mMeshSources; friend inline std::size_t getSize(const RecastMesh& value) noexcept { diff --git a/components/detournavigator/recastmeshbuilder.cpp b/components/detournavigator/recastmeshbuilder.cpp index b54b927696..0f7552aa77 100644 --- a/components/detournavigator/recastmeshbuilder.cpp +++ b/components/detournavigator/recastmeshbuilder.cpp @@ -133,6 +133,13 @@ namespace DetourNavigator { } + void RecastMeshBuilder::addObject(const btCollisionShape& shape, const btTransform& transform, + const AreaType areaType, osg::ref_ptr source, const ObjectTransform& objectTransform) + { + addObject(shape, transform, areaType); + mSources.push_back(MeshSource {std::move(source), objectTransform, areaType}); + } + void RecastMeshBuilder::addObject(const btCollisionShape& shape, const btTransform& transform, const AreaType areaType) { @@ -261,7 +268,8 @@ namespace DetourNavigator std::sort(mWater.begin(), mWater.end()); Mesh mesh = makeMesh(std::move(mTriangles)); return std::make_shared(generation, revision, std::move(mesh), std::move(mWater), - std::move(mHeightfields), std::move(mFlatHeightfields)); + std::move(mHeightfields), std::move(mFlatHeightfields), + std::move(mSources)); } void RecastMeshBuilder::addObject(const btConcaveShape& shape, const btTransform& transform, diff --git a/components/detournavigator/recastmeshbuilder.hpp b/components/detournavigator/recastmeshbuilder.hpp index 4bdcb788e3..d0848c2a45 100644 --- a/components/detournavigator/recastmeshbuilder.hpp +++ b/components/detournavigator/recastmeshbuilder.hpp @@ -4,6 +4,8 @@ #include "recastmesh.hpp" #include "tilebounds.hpp" +#include + #include #include @@ -38,7 +40,8 @@ namespace DetourNavigator public: explicit RecastMeshBuilder(const TileBounds& bounds) noexcept; - void addObject(const btCollisionShape& shape, const btTransform& transform, const AreaType areaType); + void addObject(const btCollisionShape& shape, const btTransform& transform, const AreaType areaType, + osg::ref_ptr source, const ObjectTransform& objectTransform); void addObject(const btCompoundShape& shape, const btTransform& transform, const AreaType areaType); @@ -63,6 +66,9 @@ namespace DetourNavigator std::vector mWater; std::vector mHeightfields; std::vector mFlatHeightfields; + std::vector mSources; + + inline void addObject(const btCollisionShape& shape, const btTransform& transform, const AreaType areaType); void addObject(const btConcaveShape& shape, const btTransform& transform, btTriangleCallback&& callback); diff --git a/components/detournavigator/recastmeshmanager.cpp b/components/detournavigator/recastmeshmanager.cpp index 4772ec74a9..a7b24766fc 100644 --- a/components/detournavigator/recastmeshmanager.cpp +++ b/components/detournavigator/recastmeshmanager.cpp @@ -122,7 +122,8 @@ namespace DetourNavigator { RecastMeshBuilder builder(mTileBounds); using Object = std::tuple< - osg::ref_ptr, + osg::ref_ptr, + ObjectTransform, std::reference_wrapper, btTransform, AreaType @@ -139,12 +140,13 @@ namespace DetourNavigator for (const auto& [k, object] : mObjects) { const RecastMeshObject& impl = object.getImpl(); - objects.emplace_back(impl.getHolder(), impl.getShape(), impl.getTransform(), impl.getAreaType()); + objects.emplace_back(impl.getInstance(), impl.getObjectTransform(), impl.getShape(), + impl.getTransform(), impl.getAreaType()); } revision = mRevision; } - for (const auto& [holder, shape, transform, areaType] : objects) - builder.addObject(shape, transform, areaType); + for (const auto& [instance, objectTransform, shape, transform, areaType] : objects) + builder.addObject(shape, transform, areaType, instance->getSource(), objectTransform); return std::move(builder).create(mGeneration, revision); } diff --git a/components/detournavigator/recastmeshobject.cpp b/components/detournavigator/recastmeshobject.cpp index 31aa13a208..343aeeb39e 100644 --- a/components/detournavigator/recastmeshobject.cpp +++ b/components/detournavigator/recastmeshobject.cpp @@ -75,7 +75,8 @@ namespace DetourNavigator RecastMeshObject::RecastMeshObject(const CollisionShape& shape, const btTransform& transform, const AreaType areaType) - : mHolder(shape.getHolder()) + : mInstance(shape.getInstance()) + , mObjectTransform(shape.getObjectTransform()) , mImpl(shape.getShape(), transform, areaType) { } diff --git a/components/detournavigator/recastmeshobject.hpp b/components/detournavigator/recastmeshobject.hpp index e833ee37e3..760774353c 100644 --- a/components/detournavigator/recastmeshobject.hpp +++ b/components/detournavigator/recastmeshobject.hpp @@ -2,6 +2,9 @@ #define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHOBJECT_H #include "areatype.hpp" +#include "objecttransform.hpp" + +#include #include @@ -19,17 +22,21 @@ namespace DetourNavigator class CollisionShape { public: - CollisionShape(osg::ref_ptr holder, const btCollisionShape& shape) - : mHolder(std::move(holder)) + CollisionShape(osg::ref_ptr instance, const btCollisionShape& shape, + const ObjectTransform& transform) + : mInstance(std::move(instance)) , mShape(shape) + , mObjectTransform(transform) {} - const osg::ref_ptr& getHolder() const { return mHolder; } + const osg::ref_ptr& getInstance() const { return mInstance; } const btCollisionShape& getShape() const { return mShape; } + const ObjectTransform& getObjectTransform() const { return mObjectTransform; } private: - osg::ref_ptr mHolder; + osg::ref_ptr mInstance; std::reference_wrapper mShape; + ObjectTransform mObjectTransform; }; class ChildRecastMeshObject @@ -60,7 +67,7 @@ namespace DetourNavigator bool update(const btTransform& transform, const AreaType areaType) { return mImpl.update(transform, areaType); } - const osg::ref_ptr& getHolder() const { return mHolder; } + const osg::ref_ptr& getInstance() const { return mInstance; } const btCollisionShape& getShape() const { return mImpl.getShape(); } @@ -68,8 +75,11 @@ namespace DetourNavigator AreaType getAreaType() const { return mImpl.getAreaType(); } + const ObjectTransform& getObjectTransform() const { return mObjectTransform; } + private: - osg::ref_ptr mHolder; + osg::ref_ptr mInstance; + ObjectTransform mObjectTransform; ChildRecastMeshObject mImpl; }; } diff --git a/components/detournavigator/recastmeshprovider.hpp b/components/detournavigator/recastmeshprovider.hpp new file mode 100644 index 0000000000..b01b7c4ea1 --- /dev/null +++ b/components/detournavigator/recastmeshprovider.hpp @@ -0,0 +1,33 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHPROVIDER_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHPROVIDER_H + +#include "tileposition.hpp" +#include "recastmesh.hpp" +#include "tilecachedrecastmeshmanager.hpp" +#include "version.hpp" + +#include +#include + +namespace DetourNavigator +{ + class RecastMesh; + + class RecastMeshProvider + { + public: + RecastMeshProvider(TileCachedRecastMeshManager& impl) + : mImpl(impl) + {} + + std::shared_ptr getMesh(std::string_view worldspace, const TilePosition& tilePosition) const + { + return mImpl.get().getNewMesh(worldspace, tilePosition); + } + + private: + std::reference_wrapper mImpl; + }; +} + +#endif diff --git a/components/detournavigator/serialization.cpp b/components/detournavigator/serialization.cpp new file mode 100644 index 0000000000..50b014acb1 --- /dev/null +++ b/components/detournavigator/serialization.cpp @@ -0,0 +1,272 @@ +#include "serialization.hpp" + +#include "dbrefgeometryobject.hpp" +#include "preparednavmeshdata.hpp" +#include "recast.hpp" +#include "recastmesh.hpp" +#include "settings.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace DetourNavigator +{ +namespace +{ + template + struct Format : Serialization::Format> + { + using Serialization::Format>::operator(); + + template + void operator()(Visitor&& visitor, const osg::Vec2i& value) const + { + visitor(*this, value.ptr(), 2); + } + + template + void operator()(Visitor&& visitor, const osg::Vec2f& value) const + { + visitor(*this, value.ptr(), 2); + } + + template + void operator()(Visitor&& visitor, const osg::Vec3f& value) const + { + visitor(*this, value.ptr(), 3); + } + + template + void operator()(Visitor&& visitor, const Water& value) const + { + visitor(*this, value.mCellSize); + visitor(*this, value.mLevel); + } + + template + void operator()(Visitor&& visitor, const CellWater& value) const + { + visitor(*this, value.mCellPosition); + visitor(*this, value.mWater); + } + + template + void operator()(Visitor&& visitor, const RecastSettings& value) const + { + visitor(*this, value.mCellHeight); + visitor(*this, value.mCellSize); + visitor(*this, value.mDetailSampleDist); + visitor(*this, value.mDetailSampleMaxError); + visitor(*this, value.mMaxClimb); + visitor(*this, value.mMaxSimplificationError); + visitor(*this, value.mMaxSlope); + visitor(*this, value.mRecastScaleFactor); + visitor(*this, value.mSwimHeightScale); + visitor(*this, value.mBorderSize); + visitor(*this, value.mMaxEdgeLen); + visitor(*this, value.mMaxVertsPerPoly); + visitor(*this, value.mRegionMergeArea); + visitor(*this, value.mRegionMinArea); + visitor(*this, value.mTileSize); + } + + template + void operator()(Visitor&& visitor, const TileBounds& value) const + { + visitor(*this, value.mMin); + visitor(*this, value.mMax); + } + + template + void operator()(Visitor&& visitor, const Heightfield& value) const + { + visitor(*this, value.mCellPosition); + visitor(*this, value.mCellSize); + visitor(*this, value.mLength); + visitor(*this, value.mMinHeight); + visitor(*this, value.mMaxHeight); + visitor(*this, value.mHeights); + visitor(*this, value.mOriginalSize); + visitor(*this, value.mMinX); + visitor(*this, value.mMinY); + } + + template + void operator()(Visitor&& visitor, const FlatHeightfield& value) const + { + visitor(*this, value.mCellPosition); + visitor(*this, value.mCellSize); + visitor(*this, value.mHeight); + } + + template + void operator()(Visitor&& visitor, const RecastMesh& value) const + { + visitor(*this, value.getWater()); + visitor(*this, value.getHeightfields()); + visitor(*this, value.getFlatHeightfields()); + } + + template + void operator()(Visitor&& visitor, const ESM::Position& value) const + { + visitor(*this, value.pos); + visitor(*this, value.rot); + } + + template + void operator()(Visitor&& visitor, const ObjectTransform& value) const + { + visitor(*this, value.mPosition); + visitor(*this, value.mScale); + } + + template + void operator()(Visitor&& visitor, const DbRefGeometryObject& value) const + { + visitor(*this, value.mShapeId); + visitor(*this, value.mObjectTransform); + } + + template + void operator()(Visitor&& visitor, const RecastSettings& settings, const RecastMesh& recastMesh, + const std::vector& dbRefGeometryObjects) const + { + visitor(*this, DetourNavigator::recastMeshMagic); + visitor(*this, DetourNavigator::recastMeshVersion); + visitor(*this, settings); + visitor(*this, recastMesh); + visitor(*this, dbRefGeometryObjects); + } + + template + auto operator()(Visitor&& visitor, T& value) const + -> std::enable_if_t, rcPolyMesh>> + { + visitor(*this, value.nverts); + visitor(*this, value.npolys); + visitor(*this, value.maxpolys); + visitor(*this, value.nvp); + visitor(*this, value.bmin); + visitor(*this, value.bmax); + visitor(*this, value.cs); + visitor(*this, value.ch); + visitor(*this, value.borderSize); + visitor(*this, value.maxEdgeError); + if constexpr (mode == Serialization::Mode::Read) + { + if (value.verts == nullptr) + permRecastAlloc(value.verts, getVertsLength(value)); + if (value.polys == nullptr) + permRecastAlloc(value.polys, getPolysLength(value)); + if (value.regs == nullptr) + permRecastAlloc(value.regs, getRegsLength(value)); + if (value.flags == nullptr) + permRecastAlloc(value.flags, getFlagsLength(value)); + if (value.areas == nullptr) + permRecastAlloc(value.areas, getAreasLength(value)); + } + visitor(*this, value.verts, getVertsLength(value)); + visitor(*this, value.polys, getPolysLength(value)); + visitor(*this, value.regs, getRegsLength(value)); + visitor(*this, value.flags, getFlagsLength(value)); + visitor(*this, value.areas, getAreasLength(value)); + } + + template + auto operator()(Visitor&& visitor, T& value) const + -> std::enable_if_t, rcPolyMeshDetail>> + { + visitor(*this, value.nmeshes); + if constexpr (mode == Serialization::Mode::Read) + if (value.meshes == nullptr) + permRecastAlloc(value.meshes, getMeshesLength(value)); + visitor(*this, value.meshes, getMeshesLength(value)); + visitor(*this, value.nverts); + if constexpr (mode == Serialization::Mode::Read) + if (value.verts == nullptr) + permRecastAlloc(value.verts, getVertsLength(value)); + visitor(*this, value.verts, getVertsLength(value)); + visitor(*this, value.ntris); + if constexpr (mode == Serialization::Mode::Read) + if (value.tris == nullptr) + permRecastAlloc(value.tris, getTrisLength(value)); + visitor(*this, value.tris, getTrisLength(value)); + } + + template + auto operator()(Visitor&& visitor, T& value) const + -> std::enable_if_t, PreparedNavMeshData>> + { + if constexpr (mode == Serialization::Mode::Write) + { + visitor(*this, DetourNavigator::preparedNavMeshDataMagic); + visitor(*this, DetourNavigator::preparedNavMeshDataVersion); + } + else + { + static_assert(mode == Serialization::Mode::Read); + char magic[std::size(DetourNavigator::preparedNavMeshDataMagic)]; + visitor(*this, magic); + if (std::memcmp(magic, DetourNavigator::preparedNavMeshDataMagic, sizeof(magic)) != 0) + throw std::runtime_error("Bad PreparedNavMeshData magic"); + std::uint32_t version = 0; + visitor(*this, version); + if (version != DetourNavigator::preparedNavMeshDataVersion) + throw std::runtime_error("Bad PreparedNavMeshData version"); + } + visitor(*this, value.mUserId); + visitor(*this, value.mCellSize); + visitor(*this, value.mCellHeight); + visitor(*this, value.mPolyMesh); + visitor(*this, value.mPolyMeshDetail); + } + }; +} +} // namespace DetourNavigator + +namespace DetourNavigator +{ + std::vector serialize(const RecastSettings& settings, const RecastMesh& recastMesh, + const std::vector& dbRefGeometryObjects) + { + constexpr Format format; + Serialization::SizeAccumulator sizeAccumulator; + format(sizeAccumulator, settings, recastMesh, dbRefGeometryObjects); + std::vector result(sizeAccumulator.value()); + format(Serialization::BinaryWriter(result.data(), result.data() + result.size()), + settings, recastMesh, dbRefGeometryObjects); + return result; + } + + std::vector serialize(const PreparedNavMeshData& value) + { + constexpr Format format; + Serialization::SizeAccumulator sizeAccumulator; + format(sizeAccumulator, value); + std::vector result(sizeAccumulator.value()); + format(Serialization::BinaryWriter(result.data(), result.data() + result.size()), value); + return result; + } + + bool deserialize(const std::vector& data, PreparedNavMeshData& value) + { + try + { + constexpr Format format; + format(Serialization::BinaryReader(data.data(), data.data() + data.size()), value); + return true; + } + catch (const std::exception&) + { + return false; + } + } +} diff --git a/components/detournavigator/serialization.hpp b/components/detournavigator/serialization.hpp new file mode 100644 index 0000000000..194e50a994 --- /dev/null +++ b/components/detournavigator/serialization.hpp @@ -0,0 +1,29 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SERIALIZATION_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_SERIALIZATION_H + +#include +#include +#include + +namespace DetourNavigator +{ + class RecastMesh; + struct DbRefGeometryObject; + struct PreparedNavMeshData; + struct RecastSettings; + + constexpr char recastMeshMagic[] = {'r', 'c', 's', 't'}; + constexpr std::uint32_t recastMeshVersion = 1; + + constexpr char preparedNavMeshDataMagic[] = {'p', 'n', 'a', 'v'}; + constexpr std::uint32_t preparedNavMeshDataVersion = 1; + + std::vector serialize(const RecastSettings& settings, const RecastMesh& value, + const std::vector& dbRefGeometryObjects); + + std::vector serialize(const PreparedNavMeshData& value); + + bool deserialize(const std::vector& data, PreparedNavMeshData& value); +} + +#endif diff --git a/components/detournavigator/settings.cpp b/components/detournavigator/settings.cpp index e428f3695a..cc2c685992 100644 --- a/components/detournavigator/settings.cpp +++ b/components/detournavigator/settings.cpp @@ -3,43 +3,68 @@ #include #include +#include + namespace DetourNavigator { + RecastSettings makeRecastSettingsFromSettingsManager() + { + constexpr float epsilon = std::numeric_limits::epsilon(); + + RecastSettings result; + + result.mBorderSize = std::max(0, ::Settings::Manager::getInt("border size", "Navigator")); + result.mCellHeight = std::max(epsilon, ::Settings::Manager::getFloat("cell height", "Navigator")); + result.mCellSize = std::max(epsilon, ::Settings::Manager::getFloat("cell size", "Navigator")); + result.mDetailSampleDist = std::max(0.0f, ::Settings::Manager::getFloat("detail sample dist", "Navigator")); + result.mDetailSampleMaxError = std::max(0.0f, ::Settings::Manager::getFloat("detail sample max error", "Navigator")); + result.mMaxClimb = Constants::sStepSizeUp; + result.mMaxSimplificationError = std::max(0.0f, ::Settings::Manager::getFloat("max simplification error", "Navigator")); + result.mMaxSlope = Constants::sMaxSlope; + result.mRecastScaleFactor = std::max(epsilon, ::Settings::Manager::getFloat("recast scale factor", "Navigator")); + result.mSwimHeightScale = 0; + result.mMaxEdgeLen = std::max(0, ::Settings::Manager::getInt("max edge len", "Navigator")); + result.mMaxVertsPerPoly = std::max(3, ::Settings::Manager::getInt("max verts per poly", "Navigator")); + result.mRegionMergeArea = std::max(0, ::Settings::Manager::getInt("region merge area", "Navigator")); + result.mRegionMinArea = std::max(0, ::Settings::Manager::getInt("region min area", "Navigator")); + result.mTileSize = std::max(1, ::Settings::Manager::getInt("tile size", "Navigator")); + + return result; + } + + DetourSettings makeDetourSettingsFromSettingsManager() + { + DetourSettings result; + + result.mMaxNavMeshQueryNodes = std::clamp(::Settings::Manager::getInt("max nav mesh query nodes", "Navigator"), 1, 65535); + result.mMaxPolys = std::clamp(::Settings::Manager::getInt("max polygons per tile", "Navigator"), 1, (1 << 22) - 1); + result.mMaxPolygonPathSize = static_cast(std::max(0, ::Settings::Manager::getInt("max polygon path size", "Navigator"))); + result.mMaxSmoothPathSize = static_cast(std::max(0, ::Settings::Manager::getInt("max smooth path size", "Navigator"))); + + return result; + } + Settings makeSettingsFromSettingsManager() { - Settings navigatorSettings; - - navigatorSettings.mBorderSize = ::Settings::Manager::getInt("border size", "Navigator"); - navigatorSettings.mCellHeight = ::Settings::Manager::getFloat("cell height", "Navigator"); - navigatorSettings.mCellSize = ::Settings::Manager::getFloat("cell size", "Navigator"); - navigatorSettings.mDetailSampleDist = ::Settings::Manager::getFloat("detail sample dist", "Navigator"); - navigatorSettings.mDetailSampleMaxError = ::Settings::Manager::getFloat("detail sample max error", "Navigator"); - navigatorSettings.mMaxClimb = Constants::sStepSizeUp; - navigatorSettings.mMaxSimplificationError = ::Settings::Manager::getFloat("max simplification error", "Navigator"); - navigatorSettings.mMaxSlope = Constants::sMaxSlope; - navigatorSettings.mRecastScaleFactor = ::Settings::Manager::getFloat("recast scale factor", "Navigator"); - navigatorSettings.mSwimHeightScale = 0; - navigatorSettings.mMaxEdgeLen = ::Settings::Manager::getInt("max edge len", "Navigator"); - navigatorSettings.mMaxNavMeshQueryNodes = ::Settings::Manager::getInt("max nav mesh query nodes", "Navigator"); - navigatorSettings.mMaxPolys = ::Settings::Manager::getInt("max polygons per tile", "Navigator"); - navigatorSettings.mMaxTilesNumber = ::Settings::Manager::getInt("max tiles number", "Navigator"); - navigatorSettings.mMaxVertsPerPoly = ::Settings::Manager::getInt("max verts per poly", "Navigator"); - navigatorSettings.mRegionMergeSize = ::Settings::Manager::getInt("region merge size", "Navigator"); - navigatorSettings.mRegionMinSize = ::Settings::Manager::getInt("region min size", "Navigator"); - navigatorSettings.mTileSize = ::Settings::Manager::getInt("tile size", "Navigator"); - navigatorSettings.mWaitUntilMinDistanceToPlayer = ::Settings::Manager::getInt("wait until min distance to player", "Navigator"); - navigatorSettings.mAsyncNavMeshUpdaterThreads = static_cast(::Settings::Manager::getInt("async nav mesh updater threads", "Navigator")); - navigatorSettings.mMaxNavMeshTilesCacheSize = static_cast(::Settings::Manager::getInt("max nav mesh tiles cache size", "Navigator")); - navigatorSettings.mMaxPolygonPathSize = static_cast(::Settings::Manager::getInt("max polygon path size", "Navigator")); - navigatorSettings.mMaxSmoothPathSize = static_cast(::Settings::Manager::getInt("max smooth path size", "Navigator")); - navigatorSettings.mEnableWriteRecastMeshToFile = ::Settings::Manager::getBool("enable write recast mesh to file", "Navigator"); - navigatorSettings.mEnableWriteNavMeshToFile = ::Settings::Manager::getBool("enable write nav mesh to file", "Navigator"); - navigatorSettings.mRecastMeshPathPrefix = ::Settings::Manager::getString("recast mesh path prefix", "Navigator"); - navigatorSettings.mNavMeshPathPrefix = ::Settings::Manager::getString("nav mesh path prefix", "Navigator"); - navigatorSettings.mEnableRecastMeshFileNameRevision = ::Settings::Manager::getBool("enable recast mesh file name revision", "Navigator"); - navigatorSettings.mEnableNavMeshFileNameRevision = ::Settings::Manager::getBool("enable nav mesh file name revision", "Navigator"); - navigatorSettings.mMinUpdateInterval = std::chrono::milliseconds(::Settings::Manager::getInt("min update interval ms", "Navigator")); - - return navigatorSettings; + Settings result; + + result.mRecast = makeRecastSettingsFromSettingsManager(); + result.mDetour = makeDetourSettingsFromSettingsManager(); + result.mMaxTilesNumber = std::max(0, ::Settings::Manager::getInt("max tiles number", "Navigator")); + result.mWaitUntilMinDistanceToPlayer = ::Settings::Manager::getInt("wait until min distance to player", "Navigator"); + result.mAsyncNavMeshUpdaterThreads = static_cast(std::max(0, ::Settings::Manager::getInt("async nav mesh updater threads", "Navigator"))); + result.mMaxNavMeshTilesCacheSize = static_cast(std::max(std::int64_t {0}, ::Settings::Manager::getInt64("max nav mesh tiles cache size", "Navigator"))); + result.mEnableWriteRecastMeshToFile = ::Settings::Manager::getBool("enable write recast mesh to file", "Navigator"); + result.mEnableWriteNavMeshToFile = ::Settings::Manager::getBool("enable write nav mesh to file", "Navigator"); + result.mRecastMeshPathPrefix = ::Settings::Manager::getString("recast mesh path prefix", "Navigator"); + result.mNavMeshPathPrefix = ::Settings::Manager::getString("nav mesh path prefix", "Navigator"); + result.mEnableRecastMeshFileNameRevision = ::Settings::Manager::getBool("enable recast mesh file name revision", "Navigator"); + result.mEnableNavMeshFileNameRevision = ::Settings::Manager::getBool("enable nav mesh file name revision", "Navigator"); + result.mMinUpdateInterval = std::chrono::milliseconds(::Settings::Manager::getInt("min update interval ms", "Navigator")); + result.mNavMeshVersion = ::Settings::Manager::getInt("nav mesh version", "Navigator"); + result.mEnableNavMeshDiskCache = ::Settings::Manager::getBool("enable nav mesh disk cache", "Navigator"); + result.mWriteToNavMeshDb = ::Settings::Manager::getBool("write to navmeshdb", "Navigator"); + + return result; } } diff --git a/components/detournavigator/settings.hpp b/components/detournavigator/settings.hpp index 0ea35d9b49..e6be8017d5 100644 --- a/components/detournavigator/settings.hpp +++ b/components/detournavigator/settings.hpp @@ -6,12 +6,8 @@ namespace DetourNavigator { - struct Settings + struct RecastSettings { - bool mEnableWriteRecastMeshToFile = false; - bool mEnableWriteNavMeshToFile = false; - bool mEnableRecastMeshFileNameRevision = false; - bool mEnableNavMeshFileNameRevision = false; float mCellHeight = 0; float mCellSize = 0; float mDetailSampleDist = 0; @@ -23,23 +19,44 @@ namespace DetourNavigator float mSwimHeightScale = 0; int mBorderSize = 0; int mMaxEdgeLen = 0; - int mMaxNavMeshQueryNodes = 0; - int mMaxPolys = 0; - int mMaxTilesNumber = 0; int mMaxVertsPerPoly = 0; - int mRegionMergeSize = 0; - int mRegionMinSize = 0; + int mRegionMergeArea = 0; + int mRegionMinArea = 0; int mTileSize = 0; + }; + + struct DetourSettings + { + int mMaxPolys = 0; + int mMaxNavMeshQueryNodes = 0; + std::size_t mMaxPolygonPathSize = 0; + std::size_t mMaxSmoothPathSize = 0; + }; + + struct Settings + { + bool mEnableWriteRecastMeshToFile = false; + bool mEnableWriteNavMeshToFile = false; + bool mEnableRecastMeshFileNameRevision = false; + bool mEnableNavMeshFileNameRevision = false; + bool mEnableNavMeshDiskCache = false; + bool mWriteToNavMeshDb = false; + RecastSettings mRecast; + DetourSettings mDetour; int mWaitUntilMinDistanceToPlayer = 0; + int mMaxTilesNumber = 0; std::size_t mAsyncNavMeshUpdaterThreads = 0; std::size_t mMaxNavMeshTilesCacheSize = 0; - std::size_t mMaxPolygonPathSize = 0; - std::size_t mMaxSmoothPathSize = 0; std::string mRecastMeshPathPrefix; std::string mNavMeshPathPrefix; std::chrono::milliseconds mMinUpdateInterval; + std::int64_t mNavMeshVersion = 0; }; + RecastSettings makeRecastSettingsFromSettingsManager(); + + DetourSettings makeDetourSettingsFromSettingsManager(); + Settings makeSettingsFromSettingsManager(); } diff --git a/components/detournavigator/settingsutils.hpp b/components/detournavigator/settingsutils.hpp index 6f15faaa3e..285920e5a0 100644 --- a/components/detournavigator/settingsutils.hpp +++ b/components/detournavigator/settingsutils.hpp @@ -4,12 +4,8 @@ #include "settings.hpp" #include "tilebounds.hpp" #include "tileposition.hpp" -#include "tilebounds.hpp" - -#include #include -#include #include #include @@ -17,38 +13,31 @@ namespace DetourNavigator { - inline float getHeight(const Settings& settings,const osg::Vec3f& agentHalfExtents) - { - return 2.0f * agentHalfExtents.z() * settings.mRecastScaleFactor; - } - - inline float getMaxClimb(const Settings& settings) - { - return settings.mMaxClimb * settings.mRecastScaleFactor; - } - - inline float getRadius(const Settings& settings, const osg::Vec3f& agentHalfExtents) - { - return std::max(agentHalfExtents.x(), agentHalfExtents.y()) * std::sqrt(2) * settings.mRecastScaleFactor; - } - - inline float toNavMeshCoordinates(const Settings& settings, float value) + inline float toNavMeshCoordinates(const RecastSettings& settings, float value) { return value * settings.mRecastScaleFactor; } - inline osg::Vec2f toNavMeshCoordinates(const Settings& settings, osg::Vec2f position) + inline osg::Vec2f toNavMeshCoordinates(const RecastSettings& settings, osg::Vec2f position) { return position * settings.mRecastScaleFactor; } - inline osg::Vec3f toNavMeshCoordinates(const Settings& settings, osg::Vec3f position) + inline osg::Vec3f toNavMeshCoordinates(const RecastSettings& settings, osg::Vec3f position) { std::swap(position.y(), position.z()); return position * settings.mRecastScaleFactor; } - inline osg::Vec3f fromNavMeshCoordinates(const Settings& settings, osg::Vec3f position) + inline TileBounds toNavMeshCoordinates(const RecastSettings& settings, const TileBounds& value) + { + return TileBounds { + toNavMeshCoordinates(settings, value.mMin), + toNavMeshCoordinates(settings, value.mMax) + }; + } + + inline osg::Vec3f fromNavMeshCoordinates(const RecastSettings& settings, osg::Vec3f position) { const auto factor = 1.0f / settings.mRecastScaleFactor; position *= factor; @@ -56,12 +45,12 @@ namespace DetourNavigator return position; } - inline float getTileSize(const Settings& settings) + inline float getTileSize(const RecastSettings& settings) { return static_cast(settings.mTileSize) * settings.mCellSize; } - inline TilePosition getTilePosition(const Settings& settings, const osg::Vec3f& position) + inline TilePosition getTilePosition(const RecastSettings& settings, const osg::Vec3f& position) { return TilePosition( static_cast(std::floor(position.x() / getTileSize(settings))), @@ -69,7 +58,7 @@ namespace DetourNavigator ); } - inline TileBounds makeTileBounds(const Settings& settings, const TilePosition& tilePosition) + inline TileBounds makeTileBounds(const RecastSettings& settings, const TilePosition& tilePosition) { return TileBounds { osg::Vec2f(tilePosition.x(), tilePosition.y()) * getTileSize(settings), @@ -77,17 +66,12 @@ namespace DetourNavigator }; } - inline float getBorderSize(const Settings& settings) + inline float getBorderSize(const RecastSettings& settings) { return static_cast(settings.mBorderSize) * settings.mCellSize; } - inline float getSwimLevel(const Settings& settings, const float waterLevel, const float agentHalfExtentsZ) - { - return waterLevel - settings.mSwimHeightScale * agentHalfExtentsZ - agentHalfExtentsZ;; - } - - inline float getRealTileSize(const Settings& settings) + inline float getRealTileSize(const RecastSettings& settings) { return settings.mTileSize * settings.mCellSize / settings.mRecastScaleFactor; } @@ -97,7 +81,7 @@ namespace DetourNavigator return std::floor(std::sqrt(settings.mMaxTilesNumber / osg::PI)) - 1; } - inline TileBounds makeRealTileBoundsWithBorder(const Settings& settings, const TilePosition& tilePosition) + inline TileBounds makeRealTileBoundsWithBorder(const RecastSettings& settings, const TilePosition& tilePosition) { TileBounds result = makeTileBounds(settings, tilePosition); const float border = getBorderSize(settings); diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index 20fbe30667..bf3df92d6e 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -10,19 +10,34 @@ namespace DetourNavigator { - TileCachedRecastMeshManager::TileCachedRecastMeshManager(const Settings& settings) + TileCachedRecastMeshManager::TileCachedRecastMeshManager(const RecastSettings& settings) : mSettings(settings) {} + std::string TileCachedRecastMeshManager::getWorldspace() const + { + const std::lock_guard lock(mMutex); + return mWorldspace; + } + + void TileCachedRecastMeshManager::setWorldspace(std::string_view worldspace) + { + const std::lock_guard lock(mMutex); + if (mWorldspace == worldspace) + return; + mTiles.clear(); + mWorldspace = worldspace; + } + bool TileCachedRecastMeshManager::addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType) { std::vector tilesPositions; { - auto tiles = mTiles.lock(); + const std::lock_guard lock(mMutex); getTilesPositions(shape.getShape(), transform, mSettings, [&] (const TilePosition& tilePosition) { - if (addTile(id, shape, transform, areaType, tilePosition, tiles.get())) + if (addTile(id, shape, transform, areaType, tilePosition, mTiles)) tilesPositions.push_back(tilePosition); }); } @@ -41,10 +56,10 @@ namespace DetourNavigator return std::nullopt; std::optional result; { - auto tiles = mTiles.lock(); + const std::lock_guard lock(mMutex); for (const auto& tilePosition : object->second) { - const auto removed = removeTile(id, tilePosition, tiles.get()); + const auto removed = removeTile(id, tilePosition, mTiles); if (removed && !result) result = removed; } @@ -62,8 +77,8 @@ namespace DetourNavigator if (cellSize == std::numeric_limits::max()) { - const auto tiles = mTiles.lock(); - for (auto& tile : *tiles) + const std::lock_guard lock(mMutex); + for (auto& tile : mTiles) { if (tile.second->addWater(cellPosition, cellSize, level)) { @@ -77,13 +92,13 @@ namespace DetourNavigator const btVector3 shift = Misc::Convert::toBullet(getWaterShift3d(cellPosition, cellSize, level)); getTilesPositions(cellSize, shift, mSettings, [&] (const TilePosition& tilePosition) { - const auto tiles = mTiles.lock(); - auto tile = tiles->find(tilePosition); - if (tile == tiles->end()) + const std::lock_guard lock(mMutex); + auto tile = mTiles.find(tilePosition); + if (tile == mTiles.end()) { const TileBounds tileBounds = makeRealTileBoundsWithBorder(mSettings, tilePosition); - tile = tiles->emplace(tilePosition, - std::make_shared(tileBounds, mTilesGeneration)).first; + tile = mTiles.emplace_hint(tile, tilePosition, + std::make_shared(tileBounds, mTilesGeneration)); } if (tile->second->addWater(cellPosition, cellSize, level)) { @@ -107,14 +122,14 @@ namespace DetourNavigator std::optional result; for (const auto& tilePosition : object->second) { - const auto tiles = mTiles.lock(); - const auto tile = tiles->find(tilePosition); - if (tile == tiles->end()) + const std::lock_guard lock(mMutex); + const auto tile = mTiles.find(tilePosition); + if (tile == mTiles.end()) continue; const auto tileResult = tile->second->removeWater(cellPosition); if (tile->second->isEmpty()) { - tiles->erase(tile); + mTiles.erase(tile); ++mTilesGeneration; } if (tileResult && !result) @@ -135,13 +150,13 @@ namespace DetourNavigator getTilesPositions(cellSize, shift, mSettings, [&] (const TilePosition& tilePosition) { - const auto tiles = mTiles.lock(); - auto tile = tiles->find(tilePosition); - if (tile == tiles->end()) + const std::lock_guard lock(mMutex); + auto tile = mTiles.find(tilePosition); + if (tile == mTiles.end()) { const TileBounds tileBounds = makeRealTileBoundsWithBorder(mSettings, tilePosition); - tile = tiles->emplace(tilePosition, - std::make_shared(tileBounds, mTilesGeneration)).first; + tile = mTiles.emplace_hint(tile, tilePosition, + std::make_shared(tileBounds, mTilesGeneration)); } if (tile->second->addHeightfield(cellPosition, cellSize, shape)) { @@ -164,14 +179,14 @@ namespace DetourNavigator std::optional result; for (const auto& tilePosition : object->second) { - const auto tiles = mTiles.lock(); - const auto tile = tiles->find(tilePosition); - if (tile == tiles->end()) + const std::lock_guard lock(mMutex); + const auto tile = mTiles.find(tilePosition); + if (tile == mTiles.end()) continue; const auto tileResult = tile->second->removeHeightfield(cellPosition); if (tile->second->isEmpty()) { - tiles->erase(tile); + mTiles.erase(tile); ++mTilesGeneration; } if (tileResult && !result) @@ -182,20 +197,27 @@ namespace DetourNavigator return result; } - std::shared_ptr TileCachedRecastMeshManager::getMesh(const TilePosition& tilePosition) const + std::shared_ptr TileCachedRecastMeshManager::getMesh(std::string_view worldspace, const TilePosition& tilePosition) const { - if (const auto manager = getManager(tilePosition)) + if (const auto manager = getManager(worldspace, tilePosition)) return manager->getMesh(); return nullptr; } - std::shared_ptr TileCachedRecastMeshManager::getCachedMesh(const TilePosition& tilePosition) const + std::shared_ptr TileCachedRecastMeshManager::getCachedMesh(std::string_view worldspace, const TilePosition& tilePosition) const { - if (const auto manager = getManager(tilePosition)) + if (const auto manager = getManager(worldspace, tilePosition)) return manager->getCachedMesh(); return nullptr; } + std::shared_ptr TileCachedRecastMeshManager::getNewMesh(std::string_view worldspace, const TilePosition& tilePosition) const + { + if (const auto manager = getManager(worldspace, tilePosition)) + return manager->getNewMesh(); + return nullptr; + } + std::size_t TileCachedRecastMeshManager::getRevision() const { return mRevision; @@ -203,9 +225,9 @@ namespace DetourNavigator void TileCachedRecastMeshManager::reportNavMeshChange(const TilePosition& tilePosition, Version recastMeshVersion, Version navMeshVersion) const { - const auto tiles = mTiles.lockConst(); - const auto it = tiles->find(tilePosition); - if (it == tiles->end()) + const std::lock_guard lock(mMutex); + const auto it = mTiles.find(tilePosition); + if (it == mTiles.end()) return; it->second->reportNavMeshChange(recastMeshVersion, navMeshVersion); } @@ -218,8 +240,8 @@ namespace DetourNavigator if (tile == tiles.end()) { const TileBounds tileBounds = makeRealTileBoundsWithBorder(mSettings, tilePosition); - tile = tiles.emplace(tilePosition, - std::make_shared(tileBounds, mTilesGeneration)).first; + tile = tiles.emplace_hint(tile, tilePosition, + std::make_shared(tileBounds, mTilesGeneration)); } return tile->second->addObject(id, shape, transform, areaType); } @@ -246,11 +268,14 @@ namespace DetourNavigator return tileResult; } - std::shared_ptr TileCachedRecastMeshManager::getManager(const TilePosition& tilePosition) const + std::shared_ptr TileCachedRecastMeshManager::getManager(std::string_view worldspace, + const TilePosition& tilePosition) const { - const auto tiles = mTiles.lockConst(); - const auto it = tiles->find(tilePosition); - if (it == tiles->end()) + const std::lock_guard lock(mMutex); + if (mWorldspace != worldspace) + return nullptr; + const auto it = mTiles.find(tilePosition); + if (it == mTiles.end()) return nullptr; return it->second; } diff --git a/components/detournavigator/tilecachedrecastmeshmanager.hpp b/components/detournavigator/tilecachedrecastmeshmanager.hpp index bb08a4227e..23171f0925 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.hpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.hpp @@ -8,8 +8,6 @@ #include "version.hpp" #include "heightfieldshape.hpp" -#include - #include #include #include @@ -20,7 +18,11 @@ namespace DetourNavigator class TileCachedRecastMeshManager { public: - TileCachedRecastMeshManager(const Settings& settings); + explicit TileCachedRecastMeshManager(const RecastSettings& settings); + + std::string getWorldspace() const; + + void setWorldspace(std::string_view worldspace); bool addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType); @@ -36,19 +38,19 @@ namespace DetourNavigator bool changed = false; std::vector newTiles; { - auto tiles = mTiles.lock(); + const std::lock_guard lock(mMutex); const auto onTilePosition = [&] (const TilePosition& tilePosition) { if (std::binary_search(currentTiles.begin(), currentTiles.end(), tilePosition)) { newTiles.push_back(tilePosition); - if (updateTile(id, transform, areaType, tilePosition, tiles.get())) + if (updateTile(id, transform, areaType, tilePosition, mTiles)) { onChangedTile(tilePosition); changed = true; } } - else if (addTile(id, shape, transform, areaType, tilePosition, tiles.get())) + else if (addTile(id, shape, transform, areaType, tilePosition, mTiles)) { newTiles.push_back(tilePosition); onChangedTile(tilePosition); @@ -59,7 +61,7 @@ namespace DetourNavigator std::sort(newTiles.begin(), newTiles.end()); for (const auto& tile : currentTiles) { - if (!std::binary_search(newTiles.begin(), newTiles.end(), tile) && removeTile(id, tile, tiles.get())) + if (!std::binary_search(newTiles.begin(), newTiles.end(), tile) && removeTile(id, tile, mTiles)) { onChangedTile(tile); changed = true; @@ -84,14 +86,17 @@ namespace DetourNavigator std::optional removeHeightfield(const osg::Vec2i& cellPosition); - std::shared_ptr getMesh(const TilePosition& tilePosition) const; + std::shared_ptr getMesh(std::string_view worldspace, const TilePosition& tilePosition) const; + + std::shared_ptr getCachedMesh(std::string_view worldspace, const TilePosition& tilePosition) const; - std::shared_ptr getCachedMesh(const TilePosition& tilePosition) const; + std::shared_ptr getNewMesh(std::string_view worldspace, const TilePosition& tilePosition) const; template void forEachTile(Function&& function) const { - for (auto& [tilePosition, recastMeshManager] : *mTiles.lockConst()) + const std::lock_guard lock(mMutex); + for (auto& [tilePosition, recastMeshManager] : mTiles) function(tilePosition, *recastMeshManager); } @@ -102,8 +107,10 @@ namespace DetourNavigator private: using TilesMap = std::map>; - const Settings& mSettings; - Misc::ScopeGuarded mTiles; + const RecastSettings& mSettings; + mutable std::mutex mMutex; + std::string mWorldspace; + TilesMap mTiles; std::unordered_map> mObjectsTilesPositions; std::map> mWaterTilesPositions; std::map> mHeightfieldTilesPositions; @@ -119,7 +126,8 @@ namespace DetourNavigator std::optional removeTile(const ObjectId id, const TilePosition& tilePosition, TilesMap& tiles); - inline std::shared_ptr getManager(const TilePosition& tilePosition) const; + inline std::shared_ptr getManager(std::string_view worldspace, + const TilePosition& tilePosition) const; }; } diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index 254e66ec3a..e793c6fea7 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -3,6 +3,8 @@ #include +#include + #include namespace ESM @@ -59,6 +61,12 @@ struct Position { return osg::Vec3f(rot[0], rot[1], rot[2]); } + + friend inline bool operator<(const Position& l, const Position& r) + { + const auto tuple = [] (const Position& v) { return std::tuple(v.asVec3(), v.asRotationVec3()); }; + return tuple(l) < tuple(r); + } }; #pragma pack(pop) diff --git a/components/misc/convert.hpp b/components/misc/convert.hpp index 6f4a55cfcc..45f3504dc1 100644 --- a/components/misc/convert.hpp +++ b/components/misc/convert.hpp @@ -67,6 +67,11 @@ namespace Convert { return makeBulletQuaternion(position.rot); } + + inline btTransform makeBulletTransform(const ESM::Position& position) + { + return btTransform(makeBulletQuaternion(position), toBullet(position.asVec3())); + } } } diff --git a/components/process/processinvoker.cpp b/components/process/processinvoker.cpp index 78cf70038b..7c45c865a1 100644 --- a/components/process/processinvoker.cpp +++ b/components/process/processinvoker.cpp @@ -7,7 +7,8 @@ #include #include -Process::ProcessInvoker::ProcessInvoker() +Process::ProcessInvoker::ProcessInvoker(QObject* parent) + : QObject(parent) { mProcess = new QProcess(this); @@ -56,6 +57,7 @@ bool Process::ProcessInvoker::startProcess(const QString &name, const QStringLis // mProcess = new QProcess(this); mName = name; mArguments = arguments; + mIgnoreErrors = false; QString path(name); #ifdef Q_OS_WIN @@ -151,6 +153,8 @@ bool Process::ProcessInvoker::startProcess(const QString &name, const QStringLis void Process::ProcessInvoker::processError(QProcess::ProcessError error) { + if (mIgnoreErrors) + return; QMessageBox msgBox; msgBox.setWindowTitle(tr("Error running executable")); msgBox.setIcon(QMessageBox::Critical); @@ -166,6 +170,8 @@ void Process::ProcessInvoker::processError(QProcess::ProcessError error) void Process::ProcessInvoker::processFinished(int exitCode, QProcess::ExitStatus exitStatus) { if (exitCode != 0 || exitStatus == QProcess::CrashExit) { + if (mIgnoreErrors) + return; QString error(mProcess->readAllStandardError()); error.append(tr("\nArguments:\n")); error.append(mArguments.join(" ")); @@ -181,3 +187,9 @@ void Process::ProcessInvoker::processFinished(int exitCode, QProcess::ExitStatus msgBox.exec(); } } + +void Process::ProcessInvoker::killProcess() +{ + mIgnoreErrors = true; + mProcess->kill(); +} diff --git a/components/process/processinvoker.hpp b/components/process/processinvoker.hpp index 8fff6658ca..f4b402cb12 100644 --- a/components/process/processinvoker.hpp +++ b/components/process/processinvoker.hpp @@ -13,7 +13,7 @@ namespace Process public: - ProcessInvoker(); + ProcessInvoker(QObject* parent = nullptr); ~ProcessInvoker(); // void setProcessName(const QString &name); @@ -27,12 +27,16 @@ namespace Process inline bool startProcess(const QString &name, bool detached = false) { return startProcess(name, QStringList(), detached); } bool startProcess(const QString &name, const QStringList &arguments, bool detached = false); + void killProcess(); + private: QProcess *mProcess; QString mName; QStringList mArguments; + bool mIgnoreErrors = false; + private slots: void processError(QProcess::ProcessError error); void processFinished(int exitCode, QProcess::ExitStatus exitStatus); diff --git a/components/resource/stats.cpp b/components/resource/stats.cpp index b3705f69cc..d97ddd1d6f 100644 --- a/components/resource/stats.cpp +++ b/components/resource/stats.cpp @@ -393,6 +393,8 @@ void StatsHandler::setUpScene(osgViewer::ViewerBase *viewer) "NavMesh Waiting", "NavMesh Pushed", "NavMesh Processing", + "NavMesh DbJobs", + "NavMesh DbCacheHitRate", "NavMesh CacheSize", "NavMesh UsedTiles", "NavMesh CachedTiles", diff --git a/components/sceneutil/agentpath.cpp b/components/sceneutil/agentpath.cpp index 5721110f77..db026a3332 100644 --- a/components/sceneutil/agentpath.cpp +++ b/components/sceneutil/agentpath.cpp @@ -37,7 +37,7 @@ namespace SceneUtil { osg::ref_ptr createAgentPathGroup(const std::deque& path, const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end, - const DetourNavigator::Settings& settings) + const DetourNavigator::RecastSettings& settings) { using namespace DetourNavigator; diff --git a/components/sceneutil/agentpath.hpp b/components/sceneutil/agentpath.hpp index a8965d852e..1194fa512a 100644 --- a/components/sceneutil/agentpath.hpp +++ b/components/sceneutil/agentpath.hpp @@ -13,14 +13,14 @@ namespace osg namespace DetourNavigator { - struct Settings; + struct RecastSettings; } namespace SceneUtil { osg::ref_ptr createAgentPathGroup(const std::deque& path, const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end, - const DetourNavigator::Settings& settings); + const DetourNavigator::RecastSettings& settings); } #endif diff --git a/components/sceneutil/navmesh.cpp b/components/sceneutil/navmesh.cpp index d66f95381c..eac3d17156 100644 --- a/components/sceneutil/navmesh.cpp +++ b/components/sceneutil/navmesh.cpp @@ -254,9 +254,9 @@ namespace SceneUtil osg::ref_ptr group(new osg::Group); group->setStateSet(groupStateSet); constexpr float shift = 10.0f; - DebugDraw debugDraw(*group, debugDrawStateSet, osg::Vec3f(0, 0, shift), 1.0f / settings.mRecastScaleFactor); + DebugDraw debugDraw(*group, debugDrawStateSet, osg::Vec3f(0, 0, shift), 1.0f / settings.mRecast.mRecastScaleFactor); dtNavMeshQuery navMeshQuery; - navMeshQuery.init(&navMesh, settings.mMaxNavMeshQueryNodes); + navMeshQuery.init(&navMesh, settings.mDetour.mMaxNavMeshQueryNodes); drawMeshTile(&debugDraw, navMesh, &navMeshQuery, &meshTile, DU_DRAWNAVMESH_OFFMESHCONS | DU_DRAWNAVMESH_CLOSEDLIST); return group; diff --git a/components/sceneutil/recastmesh.cpp b/components/sceneutil/recastmesh.cpp index 9614673a64..d320624682 100644 --- a/components/sceneutil/recastmesh.cpp +++ b/components/sceneutil/recastmesh.cpp @@ -42,7 +42,7 @@ namespace namespace SceneUtil { osg::ref_ptr createRecastMeshGroup(const DetourNavigator::RecastMesh& recastMesh, - const DetourNavigator::Settings& settings) + const DetourNavigator::RecastSettings& settings) { using namespace DetourNavigator; diff --git a/components/sceneutil/recastmesh.hpp b/components/sceneutil/recastmesh.hpp index ee5d9865e5..674b5b1d2a 100644 --- a/components/sceneutil/recastmesh.hpp +++ b/components/sceneutil/recastmesh.hpp @@ -11,13 +11,13 @@ namespace osg namespace DetourNavigator { class RecastMesh; - struct Settings; + struct RecastSettings; } namespace SceneUtil { osg::ref_ptr createRecastMeshGroup(const DetourNavigator::RecastMesh& recastMesh, - const DetourNavigator::Settings& settings); + const DetourNavigator::RecastSettings& settings); } #endif diff --git a/components/detournavigator/serialization/binaryreader.hpp b/components/serialization/binaryreader.hpp similarity index 55% rename from components/detournavigator/serialization/binaryreader.hpp rename to components/serialization/binaryreader.hpp index 0d75c3ac99..66e09f6ffb 100644 --- a/components/detournavigator/serialization/binaryreader.hpp +++ b/components/serialization/binaryreader.hpp @@ -1,13 +1,16 @@ -#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SERIALIZATION_BINARYREADER_H -#define OPENMW_COMPONENTS_DETOURNAVIGATOR_SERIALIZATION_BINARYREADER_H +#ifndef OPENMW_COMPONENTS_SERIALIZATION_BINARYREADER_H +#define OPENMW_COMPONENTS_SERIALIZATION_BINARYREADER_H +#include + +#include #include #include #include #include #include -namespace DetourNavigator::Serialization +namespace Serialization { class BinaryReader { @@ -23,12 +26,15 @@ namespace DetourNavigator::Serialization template void operator()(Format&& format, T& value) { - if constexpr (std::is_arithmetic_v) + if constexpr (std::is_enum_v) + (*this)(std::forward(format), static_cast&>(value)); + else if constexpr (std::is_arithmetic_v) { - if (mEnd - mPos < static_cast(sizeof(value))) + if (mEnd - mPos < static_cast(sizeof(T))) throw std::runtime_error("Not enough data"); - std::memcpy(&value, mPos, sizeof(value)); - mPos += sizeof(value); + std::memcpy(&value, mPos, sizeof(T)); + mPos += sizeof(T); + value = Misc::toLittleEndian(value); } else { @@ -39,13 +45,17 @@ namespace DetourNavigator::Serialization template auto operator()(Format&& format, T* data, std::size_t count) { - if constexpr (std::is_arithmetic_v) + if constexpr (std::is_enum_v) + (*this)(std::forward(format), reinterpret_cast*>(data), count); + else if constexpr (std::is_arithmetic_v) { - if (mEnd - mPos < static_cast(count * sizeof(T))) - throw std::runtime_error("Not enough data"); const std::size_t size = sizeof(T) * count; + if (mEnd - mPos < static_cast(size)) + throw std::runtime_error("Not enough data"); std::memcpy(data, mPos, size); mPos += size; + if constexpr (!Misc::IS_LITTLE_ENDIAN) + std::for_each(data, data + count, [&] (T& v) { v = Misc::fromLittleEndian(v); }); } else { diff --git a/components/detournavigator/serialization/binarywriter.hpp b/components/serialization/binarywriter.hpp similarity index 51% rename from components/detournavigator/serialization/binarywriter.hpp rename to components/serialization/binarywriter.hpp index 5e710d85d5..199f208da9 100644 --- a/components/detournavigator/serialization/binarywriter.hpp +++ b/components/serialization/binarywriter.hpp @@ -1,13 +1,16 @@ -#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SERIALIZATION_BINARYWRITER_H -#define OPENMW_COMPONENTS_DETOURNAVIGATOR_SERIALIZATION_BINARYWRITER_H +#ifndef OPENMW_COMPONENTS_SERIALIZATION_BINARYWRITER_H +#define OPENMW_COMPONENTS_SERIALIZATION_BINARYWRITER_H +#include + +#include #include #include #include #include #include -namespace DetourNavigator::Serialization +namespace Serialization { struct BinaryWriter { @@ -23,12 +26,13 @@ namespace DetourNavigator::Serialization template void operator()(Format&& format, const T& value) { - if constexpr (std::is_arithmetic_v) + if constexpr (std::is_enum_v) + (*this)(std::forward(format), static_cast>(value)); + else if constexpr (std::is_arithmetic_v) { - if (mEnd - mDest < static_cast(sizeof(value))) + if (mEnd - mDest < static_cast(sizeof(T))) throw std::runtime_error("Not enough space"); - std::memcpy(mDest, &value, sizeof(value)); - mDest += sizeof(value); + writeValue(value); } else { @@ -39,13 +43,20 @@ namespace DetourNavigator::Serialization template auto operator()(Format&& format, const T* data, std::size_t count) { - if constexpr (std::is_arithmetic_v) + if constexpr (std::is_enum_v) + (*this)(std::forward(format), reinterpret_cast*>(data), count); + else if constexpr (std::is_arithmetic_v) { const std::size_t size = sizeof(T) * count; if (mEnd - mDest < static_cast(size)) throw std::runtime_error("Not enough space"); - std::memcpy(mDest, data, size); - mDest += size; + if constexpr (Misc::IS_LITTLE_ENDIAN) + { + std::memcpy(mDest, data, size); + mDest += size; + } + else + std::for_each(data, data + count, [&] (const T& v) { writeValue(v); }); } else { @@ -56,6 +67,14 @@ namespace DetourNavigator::Serialization private: std::byte* mDest; const std::byte* const mEnd; + + template + void writeValue(const T& value) noexcept + { + T coverted = Misc::toLittleEndian(value); + std::memcpy(mDest, &coverted, sizeof(T)); + mDest += sizeof(T); + } }; } diff --git a/components/detournavigator/serialization/format.hpp b/components/serialization/format.hpp similarity index 70% rename from components/detournavigator/serialization/format.hpp rename to components/serialization/format.hpp index d07ab9da6f..595afd0dad 100644 --- a/components/detournavigator/serialization/format.hpp +++ b/components/serialization/format.hpp @@ -1,5 +1,5 @@ -#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SERIALIZATION_FORMAT_H -#define OPENMW_COMPONENTS_DETOURNAVIGATOR_SERIALIZATION_FORMAT_H +#ifndef OPENMW_COMPONENTS_SERIALIZATION_FORMAT_H +#define OPENMW_COMPONENTS_SERIALIZATION_FORMAT_H #include #include @@ -8,7 +8,7 @@ #include #include -namespace DetourNavigator::Serialization +namespace Serialization { enum class Mode { @@ -22,6 +22,9 @@ namespace DetourNavigator::Serialization template struct IsContiguousContainer> : std::true_type {}; + template + struct IsContiguousContainer> : std::true_type {}; + template constexpr bool isContiguousContainer = IsContiguousContainer>::value; @@ -31,24 +34,10 @@ namespace DetourNavigator::Serialization template void operator()(Visitor&& visitor, T* data, std::size_t size) const { - if constexpr (std::is_arithmetic_v) - { + if constexpr (std::is_arithmetic_v || std::is_enum_v) visitor(self(), data, size); - } - else if constexpr (std::is_enum_v) - { - if constexpr (mode == Mode::Write) - visitor(self(), reinterpret_cast*>(data), size); - else - { - static_assert(mode == Mode::Read); - visitor(self(), reinterpret_cast*>(data), size); - } - } else - { std::for_each(data, data + size, [&] (auto& v) { visitor(self(), v); }); - } } template diff --git a/components/detournavigator/serialization/sizeaccumulator.hpp b/components/serialization/sizeaccumulator.hpp similarity index 72% rename from components/detournavigator/serialization/sizeaccumulator.hpp rename to components/serialization/sizeaccumulator.hpp index 28bdb5c1cb..4dcc004f73 100644 --- a/components/detournavigator/serialization/sizeaccumulator.hpp +++ b/components/serialization/sizeaccumulator.hpp @@ -1,10 +1,10 @@ -#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SERIALIZATION_SIZEACCUMULATOR_H -#define OPENMW_COMPONENTS_DETOURNAVIGATOR_SERIALIZATION_SIZEACCUMULATOR_H +#ifndef OPENMW_COMPONENTS_SERIALIZATION_SIZEACCUMULATOR_H +#define OPENMW_COMPONENTS_SERIALIZATION_SIZEACCUMULATOR_H #include #include -namespace DetourNavigator::Serialization +namespace Serialization { class SizeAccumulator { @@ -18,7 +18,7 @@ namespace DetourNavigator::Serialization template void operator()(Format&& format, const T& value) { - if constexpr (std::is_arithmetic_v) + if constexpr (std::is_arithmetic_v || std::is_enum_v) mValue += sizeof(T); else format(*this, value); @@ -27,7 +27,7 @@ namespace DetourNavigator::Serialization template auto operator()(Format&& format, const T* data, std::size_t count) { - if constexpr (std::is_arithmetic_v) + if constexpr (std::is_arithmetic_v || std::is_enum_v) mValue += count * sizeof(T); else format(*this, data, count); diff --git a/components/settings/settings.cpp b/components/settings/settings.cpp index cef627acd4..7fa625e4ab 100644 --- a/components/settings/settings.cpp +++ b/components/settings/settings.cpp @@ -79,6 +79,15 @@ int Manager::getInt (const std::string& setting, const std::string& category) return number; } +std::int64_t Manager::getInt64 (const std::string& setting, const std::string& category) +{ + const std::string& value = getString(setting, category); + std::stringstream stream(value); + std::size_t number = 0; + stream >> number; + return number; +} + bool Manager::getBool (const std::string& setting, const std::string& category) { const std::string& string = getString(setting, category); diff --git a/components/settings/settings.hpp b/components/settings/settings.hpp index 21d5aff770..a4b1cf3a54 100644 --- a/components/settings/settings.hpp +++ b/components/settings/settings.hpp @@ -51,6 +51,7 @@ namespace Settings ///< returns the list of changed settings intersecting with the filter static int getInt (const std::string& setting, const std::string& category); + static std::int64_t getInt64 (const std::string& setting, const std::string& category); static float getFloat (const std::string& setting, const std::string& category); static double getDouble (const std::string& setting, const std::string& category); static std::string getString (const std::string& setting, const std::string& category); diff --git a/components/sqlite3/request.hpp b/components/sqlite3/request.hpp index 339c7f7521..0a74bf1cb3 100644 --- a/components/sqlite3/request.hpp +++ b/components/sqlite3/request.hpp @@ -2,6 +2,7 @@ #define OPENMW_COMPONENTS_SQLITE3_REQUEST_H #include "statement.hpp" +#include "types.hpp" #include @@ -53,6 +54,13 @@ namespace Sqlite3 + ": " + std::string(sqlite3_errmsg(&db))); } + inline void bindParameter(sqlite3& db, sqlite3_stmt& stmt, int index, const ConstBlob& value) + { + if (sqlite3_bind_blob(&stmt, index, value.mData, value.mSize, SQLITE_STATIC) != SQLITE_OK) + throw std::runtime_error("Failed to bind blob to parameter " + std::to_string(index) + + ": " + std::string(sqlite3_errmsg(&db))); + } + template inline void bindParameter(sqlite3& db, sqlite3_stmt& stmt, const char* name, const T& value) { diff --git a/components/sqlite3/types.hpp b/components/sqlite3/types.hpp new file mode 100644 index 0000000000..325e9e6608 --- /dev/null +++ b/components/sqlite3/types.hpp @@ -0,0 +1,15 @@ +#ifndef OPENMW_COMPONENTS_SQLITE3_TYPES_H +#define OPENMW_COMPONENTS_SQLITE3_TYPES_H + +#include + +namespace Sqlite3 +{ + struct ConstBlob + { + const char* mData; + int mSize; + }; +} + +#endif diff --git a/docs/source/reference/modding/settings/game.rst b/docs/source/reference/modding/settings/game.rst index 605be3f6af..6b19885557 100644 --- a/docs/source/reference/modding/settings/game.rst +++ b/docs/source/reference/modding/settings/game.rst @@ -464,3 +464,4 @@ default actor pathfind half extents :Default: 29.27999496459961 28.479997634887695 66.5 Actor half extents used for exterior cells to generate navmesh. +Changing the value will invalidate navmesh disk cache. diff --git a/docs/source/reference/modding/settings/navigator.rst b/docs/source/reference/modding/settings/navigator.rst index aea817530e..6d00c770bc 100644 --- a/docs/source/reference/modding/settings/navigator.rst +++ b/docs/source/reference/modding/settings/navigator.rst @@ -54,6 +54,26 @@ Allows to complete cell loading only when minimal navigation mesh area is genera nearby the player. Increasing this value will keep loading screen longer but will slightly increase nav mesh generation speed on systems bound by CPU. Zero means no waiting. +enable nav mesh disk cache +-------------------------- + +:Type: boolean +:Range: True/False +:Default: True + +If true navmesh cache stored on disk will be used in addition to memory cache. +If navmesh tile is not present in memory cache, it will be looked up in the disk cache. +If it's not found there it will be generated. + +write to navmeshdb +------------------ + +:Type: boolean +:Range: True/False +:Default: False + +If true generated navmesh tiles will be stored into disk cache while game is running. + Advanced settings ***************** @@ -206,6 +226,17 @@ Absent pieces usually mean a bug in recast mesh tiles building. Allows to do in-game debug. Potentially decreases performance. +nav mesh version +---------------- + +:Type: integer +:Range: > 0 +:Default: 1 + +Version of navigation mesh generation algorithm. +Should be increased each time there is a difference between output of makeNavMeshTileData function for the same input. +Changing the value will invalidate navmesh disk cache. + Expert settings *************** @@ -365,20 +396,20 @@ max verts per poly The maximum number of vertices allowed for polygons generated during the contour to polygon conversion process. -region merge size +region merge area ----------------- :Type: integer :Range: >= 0 -:Default: 20 +:Default: 400 Any regions with a span count smaller than this value will, if possible, be merged with larger regions. -region min size +region min area --------------- :Type: integer :Range: >= 0 -:Default: 8 +:Default: 64 The minimum number of cells allowed to form isolated island areas. diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 9715e3793e..57faaba11d 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -876,10 +876,10 @@ max polygons per tile = 4096 max verts per poly = 6 # Any regions with a span count smaller than this value will, if possible, be merged with larger regions. (value >= 0) -region merge size = 20 +region merge area = 400 # The minimum number of cells allowed to form isolated island areas. (value >= 0) -region min size = 8 +region min area = 64 # Number of background threads to update nav mesh (value >= 1) async nav mesh updater threads = 1 @@ -930,6 +930,16 @@ min update interval ms = 250 # Distance is measured in the number of tiles and can be only an integer value. wait until min distance to player = 5 +# Version of navigation mesh generation algorithm. +# Should be increased each time there is a difference between output of makeNavMeshTileData function for the same input. +nav mesh version = 1 + +# Use navigation mesh cache stored on disk (true, false) +enable nav mesh disk cache = true + +# Cache navigation mesh tiles to disk (true, false) +write to navmeshdb = false + [Shadows] # Enable or disable shadows. Bear in mind that this will force OpenMW to use shaders as if "[Shaders]/force shaders" was set to true. diff --git a/files/ui/datafilespage.ui b/files/ui/datafilespage.ui index ccac5050ed..ff330391d2 100644 --- a/files/ui/datafilespage.ui +++ b/files/ui/datafilespage.ui @@ -2,12 +2,94 @@ DataFilesPage + + + 0 + 0 + 571 + 384 + + Qt::DefaultContextMenu - + + + 0 + + + + Data Files + + + + + + + + + + Navigation mesh cache + + + + + + + + Qt::TabFocus + + + Generate navigation mesh cache for all content. Will be used by the engine to make cell loading faster. + + + Update + + + + + + + false + + + 0 + + + + + + + false + + + Cancel navigation mesh generation. Already processed data will be saved. + + + Cancel + + + + + + + + + true + + + QPlainTextEdit::NoWrap + + + true + + + + + +