Merge branch 'navmeshtool' into 'master'

Navmesh disk cache (#6189)

Closes #6189

See merge request OpenMW/openmw!1058
pull/3225/head
psi29a 3 years ago
commit 2988ab55d5

@ -219,7 +219,7 @@ macOS11_Xcode12:
CCACHE_SIZE: 3G CCACHE_SIZE: 3G
variables: &engine-targets variables: &engine-targets
targets: "openmw,openmw-iniimporter,openmw-launcher,openmw-wizard" targets: "openmw,openmw-iniimporter,openmw-launcher,openmw-wizard,openmw-navmeshtool"
package: "Engine" package: "Engine"
variables: &cs-targets variables: &cs-targets

@ -108,6 +108,7 @@
Feature #6128: Soft Particles Feature #6128: Soft Particles
Feature #6161: Refactor Sky to use shaders and GLES/GL3 friendly 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 #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 #6199: Support FBO Rendering
Feature #6248: Embedded error marker mesh Feature #6248: Embedded error marker mesh
Feature #6249: Alpha testing support for Collada Feature #6249: Alpha testing support for Collada

@ -22,6 +22,7 @@ cmake \
-DBUILD_ESSIMPORTER=0 \ -DBUILD_ESSIMPORTER=0 \
-DBUILD_OPENCS=0 \ -DBUILD_OPENCS=0 \
-DBUILD_WIZARD=0 \ -DBUILD_WIZARD=0 \
-DBUILD_NAVMESHTOOL=OFF \
-DOPENMW_USE_SYSTEM_MYGUI=OFF \ -DOPENMW_USE_SYSTEM_MYGUI=OFF \
-DOPENMW_USE_SYSTEM_SQLITE3=OFF \ -DOPENMW_USE_SYSTEM_SQLITE3=OFF \
.. ..

@ -71,6 +71,7 @@ if [[ "${BUILD_TESTS_ONLY}" ]]; then
-DBUILD_ESSIMPORTER=OFF \ -DBUILD_ESSIMPORTER=OFF \
-DBUILD_OPENCS=OFF \ -DBUILD_OPENCS=OFF \
-DBUILD_WIZARD=OFF \ -DBUILD_WIZARD=OFF \
-DBUILD_NAVMESHTOOL=OFF \
-DBUILD_UNITTESTS=${BUILD_UNITTESTS} \ -DBUILD_UNITTESTS=${BUILD_UNITTESTS} \
-DBUILD_BENCHMARKS=${BUILD_BENCHMARKS} \ -DBUILD_BENCHMARKS=${BUILD_BENCHMARKS} \
-DGTEST_ROOT="${GOOGLETEST_DIR}" \ -DGTEST_ROOT="${GOOGLETEST_DIR}" \

@ -37,6 +37,7 @@ option(BUILD_DOCS "Build documentation." OFF )
option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF) option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF)
option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest" OFF) option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest" OFF)
option(BUILD_BENCHMARKS "Build benchmarks with Google Benchmark" 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. 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) add_subdirectory(apps/benchmarks)
endif() endif()
if (BUILD_NAVMESHTOOL)
add_subdirectory(apps/navmeshtool)
endif()
if (WIN32) if (WIN32)
if (MSVC) if (MSVC)
if (OPENMW_MP_BUILD) if (OPENMW_MP_BUILD)
@ -702,6 +707,10 @@ if (WIN32)
if (BUILD_BENCHMARKS) if (BUILD_BENCHMARKS)
set_target_properties(openmw_detournavigator_navmeshtilescache_benchmark PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") set_target_properties(openmw_detournavigator_navmeshtilescache_benchmark PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}")
endif() endif()
if (BUILD_NAVMESHTOOL)
set_target_properties(openmw-navmeshtool PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}")
endif()
endif(MSVC) endif(MSVC)
# TODO: At some point release builds should not use the console but rather write to a log file # 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) IF(BUILD_WIZARD)
INSTALL(PROGRAMS "${INSTALL_SOURCE}/openmw-wizard" DESTINATION "${BINDIR}" ) INSTALL(PROGRAMS "${INSTALL_SOURCE}/openmw-wizard" DESTINATION "${BINDIR}" )
ENDIF(BUILD_WIZARD) ENDIF(BUILD_WIZARD)
if(BUILD_NAVMESHTOOL)
install(PROGRAMS "${INSTALL_SOURCE}/openmw-navmeshtool" DESTINATION "${BINDIR}" )
endif()
# Install licenses # Install licenses
INSTALL(FILES "files/mygui/DejaVuFontLicense.txt" DESTINATION "${LICDIR}" ) INSTALL(FILES "files/mygui/DejaVuFontLicense.txt" DESTINATION "${LICDIR}" )

@ -145,7 +145,7 @@ namespace
std::vector<CellWater> water; std::vector<CellWater> water;
generateWater(std::back_inserter(water), 1, random); generateWater(std::back_inserter(water), 1, random);
RecastMesh recastMesh(generation, revision, std::move(mesh), std::move(water), 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)}; return Key {agentHalfExtents, tilePosition, std::move(recastMesh)};
} }

@ -1,4 +1,5 @@
#include "datafilespage.hpp" #include "datafilespage.hpp"
#include "maindialog.hpp"
#include <QDebug> #include <QDebug>
@ -8,6 +9,7 @@
#include <QSortFilterProxyModel> #include <QSortFilterProxyModel>
#include <thread> #include <thread>
#include <mutex> #include <mutex>
#include <algorithm>
#include <apps/launcher/utils/cellnameloader.hpp> #include <apps/launcher/utils/cellnameloader.hpp>
#include <components/files/configurationmanager.hpp> #include <components/files/configurationmanager.hpp>
@ -24,11 +26,14 @@
const char *Launcher::DataFilesPage::mDefaultContentListName = "Default"; 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) : QWidget(parent)
, mMainDialog(parent)
, mCfgMgr(cfg) , mCfgMgr(cfg)
, mGameSettings(gameSettings) , mGameSettings(gameSettings)
, mLauncherSettings(launcherSettings) , mLauncherSettings(launcherSettings)
, mNavMeshToolInvoker(new Process::ProcessInvoker(this))
{ {
ui.setupUi (this); ui.setupUi (this);
setObjectName ("DataFilesPage"); setObjectName ("DataFilesPage");
@ -57,8 +62,6 @@ Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config:
void Launcher::DataFilesPage::buildView() void Launcher::DataFilesPage::buildView()
{ {
ui.verticalLayout->insertWidget (0, mSelector->uiWidget());
QToolButton * refreshButton = mSelector->refreshButton(); QToolButton * refreshButton = mSelector->refreshButton();
//tool buttons //tool buttons
@ -89,6 +92,13 @@ void Launcher::DataFilesPage::buildView()
this, SLOT (slotProfileChangedByUser(QString, QString))); this, SLOT (slotProfileChangedByUser(QString, QString)));
connect(ui.refreshDataFilesAction, SIGNAL(triggered()),this, SLOT(slotRefreshButtonClicked())); 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() bool Launcher::DataFilesPage::loadSettings()
@ -411,3 +421,55 @@ void Launcher::DataFilesPage::reloadCells(QStringList selectedFiles)
std::sort(cellNamesList.begin(), cellNamesList.end()); std::sort(cellNamesList.begin(), cellNamesList.end());
emit signalLoadedCellsChanged(cellNamesList); 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);
}

@ -2,6 +2,9 @@
#define DATAFILESPAGE_H #define DATAFILESPAGE_H
#include "ui_datafilespage.h" #include "ui_datafilespage.h"
#include <components/process/processinvoker.hpp>
#include <QWidget> #include <QWidget>
@ -19,6 +22,7 @@ namespace Config { class GameSettings;
namespace Launcher namespace Launcher
{ {
class MainDialog;
class TextInputDialog; class TextInputDialog;
class ProfilesComboBox; class ProfilesComboBox;
@ -31,7 +35,7 @@ namespace Launcher
public: public:
explicit DataFilesPage (Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings, explicit DataFilesPage (Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings,
Config::LauncherSettings &launcherSettings, QWidget *parent = nullptr); Config::LauncherSettings &launcherSettings, MainDialog *parent = nullptr);
QAbstractItemModel* profilesModel() const; QAbstractItemModel* profilesModel() const;
@ -69,12 +73,18 @@ namespace Launcher
void on_cloneProfileAction_triggered(); void on_cloneProfileAction_triggered();
void on_deleteProfileAction_triggered(); void on_deleteProfileAction_triggered();
void startNavMeshTool();
void killNavMeshTool();
void updateNavMeshProgress();
void navMeshToolFinished(int exitCode, QProcess::ExitStatus exitStatus);
public: public:
/// Content List that is always present /// Content List that is always present
const static char *mDefaultContentListName; const static char *mDefaultContentListName;
private: private:
MainDialog *mMainDialog;
TextInputDialog *mNewProfileDialog; TextInputDialog *mNewProfileDialog;
TextInputDialog *mCloneProfileDialog; TextInputDialog *mCloneProfileDialog;
@ -87,6 +97,8 @@ namespace Launcher
QStringList previousSelectedFiles; QStringList previousSelectedFiles;
QString mDataLocal; QString mDataLocal;
Process::ProcessInvoker* mNavMeshToolInvoker;
void buildView(); void buildView();
void setProfile (int index, bool savePrevious); void setProfile (int index, bool savePrevious);
void setProfile (const QString &previous, const QString &current, bool savePrevious); void setProfile (const QString &previous, const QString &current, bool savePrevious);

@ -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()

@ -0,0 +1,209 @@
#include "worldspacedata.hpp"
#include "navmesh.hpp"
#include <components/debug/debugging.hpp>
#include <components/detournavigator/navmeshdb.hpp>
#include <components/detournavigator/recastglobalallocator.hpp>
#include <components/esm/esmreader.hpp>
#include <components/esm/variant.hpp>
#include <components/esmloader/esmdata.hpp>
#include <components/esmloader/load.hpp>
#include <components/fallback/fallback.hpp>
#include <components/fallback/validate.hpp>
#include <components/files/configurationmanager.hpp>
#include <components/resource/bulletshapemanager.hpp>
#include <components/resource/imagemanager.hpp>
#include <components/resource/niffilemanager.hpp>
#include <components/resource/scenemanager.hpp>
#include <components/settings/settings.hpp>
#include <components/to_utf8/to_utf8.hpp>
#include <components/version/version.hpp>
#include <components/vfs/manager.hpp>
#include <components/vfs/registerarchives.hpp>
#include <osg/Vec3f>
#include <boost/filesystem.hpp>
#include <boost/program_options.hpp>
#include <cstddef>
#include <stdexcept>
#include <string>
#include <thread>
#include <vector>
namespace NavMeshTool
{
namespace
{
namespace bpo = boost::program_options;
using StringsVector = std::vector<std::string>;
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<Files::MaybeQuotedPathContainer>()->default_value(Files::MaybeQuotedPathContainer(), "data")
->multitoken()->composing(), "set data directories (later directories have higher priority)")
("data-local", bpo::value<Files::MaybeQuotedPathContainer::value_type>()->default_value(Files::MaybeQuotedPathContainer::value_type(), ""),
"set local data directory (highest priority)")
("fallback-archive", bpo::value<StringsVector>()->default_value(StringsVector(), "fallback-archive")
->multitoken()->composing(), "set fallback BSA archives (later archives have higher priority)")
("resources", bpo::value<Files::MaybeQuotedPath>()->default_value(Files::MaybeQuotedPath(), "resources"),
"set resources directory")
("content", bpo::value<StringsVector>()->default_value(StringsVector(), "")
->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon/omwscripts")
("fs-strict", bpo::value<bool>()->implicit_value(true)
->default_value(false), "strict file system handling (no case folding)")
("encoding", bpo::value<std::string>()->
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<Fallback::FallbackMap>()->default_value(Fallback::FallbackMap(), "")
->multitoken()->composing(), "fallback values")
("threads", bpo::value<std::size_t>()->default_value(std::max<std::size_t>(std::thread::hardware_concurrency() - 1, 1)),
"number of threads for parallel processing")
("process-interior-cells", bpo::value<bool>()->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<std::string>());
Log(Debug::Info) << ToUTF8::encodingUsingMessage(encoding);
ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(encoding));
Files::PathContainer dataDirs(asPathContainer(variables["data"].as<Files::MaybeQuotedPathContainer>()));
auto local = variables["data-local"].as<Files::MaybeQuotedPathContainer::value_type>();
if (!local.empty())
dataDirs.push_back(std::move(local));
config.processPaths(dataDirs);
const auto fsStrict = variables["fs-strict"].as<bool>();
const auto resDir = variables["resources"].as<Files::MaybeQuotedPath>();
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<StringsVector>();
const auto contentFiles = variables["content"].as<StringsVector>();
const std::size_t threadsNumber = variables["threads"].as<std::size_t>();
if (threadsNumber < 1)
{
std::cerr << "Invalid threads number: " << threadsNumber << ", expected >= 1";
return -1;
}
const bool processInteriorCells = variables["process-interior-cells"].as<bool>();
Fallback::Map::init(variables["fallback"].as<Fallback::FallbackMap>().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<ESM::ESMReader> 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");
}

@ -0,0 +1,212 @@
#include "navmesh.hpp"
#include "worldspacedata.hpp"
#include <components/bullethelpers/aabb.hpp>
#include <components/debug/debuglog.hpp>
#include <components/detournavigator/generatenavmeshtile.hpp>
#include <components/detournavigator/gettilespositions.hpp>
#include <components/detournavigator/navmeshdb.hpp>
#include <components/detournavigator/navmeshdbutils.hpp>
#include <components/detournavigator/offmeshconnection.hpp>
#include <components/detournavigator/offmeshconnectionsmanager.hpp>
#include <components/detournavigator/preparednavmeshdata.hpp>
#include <components/detournavigator/recastmesh.hpp>
#include <components/detournavigator/recastmeshprovider.hpp>
#include <components/detournavigator/serialization.hpp>
#include <components/detournavigator/tilecachedrecastmeshmanager.hpp>
#include <components/detournavigator/tileposition.hpp>
#include <components/esm/loadcell.hpp>
#include <components/misc/guarded.hpp>
#include <components/misc/progressreporter.hpp>
#include <components/sceneutil/workqueue.hpp>
#include <components/sqlite3/transaction.hpp>
#include <DetourNavMesh.h>
#include <osg/Vec3f>
#include <algorithm>
#include <atomic>
#include <chrono>
#include <cstddef>
#include <stdexcept>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
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<double>(provided) / static_cast<double>(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<NavMeshTileInfo> find(const std::string& worldspace, const TilePosition &tilePosition,
const std::vector<std::byte> &input) override
{
std::optional<NavMeshTileInfo> 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<std::byte>& input, PreparedNavMeshData& data) override
{
data.mUserId = static_cast<unsigned>(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<unsigned>(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<LogGeneratedTiles> 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<NavMeshTileConsumer>(std::move(db));
std::size_t tiles = 0;
for (const std::unique_ptr<WorldspaceNavMeshInput>& 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";
}
}

@ -0,0 +1,23 @@
#ifndef OPENMW_NAVMESHTOOL_NAVMESH_H
#define OPENMW_NAVMESHTOOL_NAVMESH_H
#include <osg/Vec3f>
#include <cstddef>
#include <string_view>
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

@ -0,0 +1,330 @@
#include "worldspacedata.hpp"
#include <components/bullethelpers/aabb.hpp>
#include <components/bullethelpers/heightfield.hpp>
#include <components/debug/debuglog.hpp>
#include <components/detournavigator/gettilespositions.hpp>
#include <components/detournavigator/objectid.hpp>
#include <components/detournavigator/recastmesh.hpp>
#include <components/detournavigator/tilecachedrecastmeshmanager.hpp>
#include <components/esm/cellref.hpp>
#include <components/esm/esmreader.hpp>
#include <components/esm/loadcell.hpp>
#include <components/esm/loadland.hpp>
#include <components/esmloader/esmdata.hpp>
#include <components/esmloader/lessbyid.hpp>
#include <components/esmloader/record.hpp>
#include <components/misc/coordinateconverter.hpp>
#include <components/misc/resourcehelpers.hpp>
#include <components/misc/stringops.hpp>
#include <components/resource/bulletshapemanager.hpp>
#include <components/settings/settings.hpp>
#include <components/vfs/manager.hpp>
#include <LinearMath/btVector3.h>
#include <osg/Vec2i>
#include <osg/Vec3f>
#include <osg/ref_ptr>
#include <algorithm>
#include <memory>
#include <stdexcept>
#include <string>
#include <string_view>
#include <thread>
#include <tuple>
#include <utility>
#include <vector>
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<CellRef> loadCellRefs(const ESM::Cell& cell, const EsmLoader::EsmData& esmData,
std::vector<ESM::ESMReader>& readers)
{
std::vector<EsmLoader::Record<CellRef>> cellRefs;
for (std::size_t i = 0; i < cell.mContextList.size(); i++)
{
ESM::ESMReader& reader = readers[static_cast<std::size_t>(cell.mContextList[i].index)];
cell.restore(reader, static_cast<int>(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<CellRef>& v) -> const ESM::RefNum& { return v.mValue.mRefNum; };
std::vector<CellRef> result = prepareRecords(cellRefs, getKey);
Log(Debug::Debug) << "Prepared " << result.size() << " unique cell refs";
return result;
}
template <class F>
void forEachObject(const ESM::Cell& cell, const EsmLoader::EsmData& esmData, const VFS::Manager& vfs,
Resource::BulletShapeManager& bulletShapeManager, std::vector<ESM::ESMReader>& readers,
F&& f)
{
std::vector<CellRef> 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<const Resource::BulletShape> 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<const Resource::BulletShape>();
}
} ();
if (shape == nullptr || shape->mCollisionShape == nullptr)
continue;
osg::ref_ptr<Resource::BulletShapeInstance> 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<btScalar>(cellPosition.x() * ESM::Land::REAL_SIZE),
static_cast<btScalar>(cellPosition.y() * ESM::Land::REAL_SIZE),
minHeight
);
aabb.m_min = btVector3(
static_cast<btScalar>((cellPosition.x() + 1) * ESM::Land::REAL_SIZE),
static_cast<btScalar>((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<HeightfieldShape, float, float> makeHeightfieldShape(const std::optional<ESM::Land>& land,
const osg::Vec2i& cellPosition, std::vector<std::vector<float>>& heightfields,
std::vector<std::unique_ptr<ESM::Land::LandData>>& 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<ESM::Land::LandData>());
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<std::size_t>(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<ESM::ESMReader>& readers,
const VFS::Manager& vfs, Resource::BulletShapeManager& bulletShapeManager, const EsmLoader::EsmData& esmData,
bool processInteriorCells)
{
Log(Debug::Info) << "Processing " << esmData.mCells.size() << " cells...";
std::map<std::string_view, std::unique_ptr<WorldspaceNavMeshInput>> 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<WorldspaceNavMeshInput>(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<ESM::Land>() : *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<int>::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;
}
}

@ -0,0 +1,97 @@
#ifndef OPENMW_NAVMESHTOOL_WORLDSPACEDATA_H
#define OPENMW_NAVMESHTOOL_WORLDSPACEDATA_H
#include <components/bullethelpers/collisionobject.hpp>
#include <components/detournavigator/tilecachedrecastmeshmanager.hpp>
#include <components/esm/loadland.hpp>
#include <components/misc/convert.hpp>
#include <components/resource/bulletshape.hpp>
#include <BulletCollision/CollisionDispatch/btCollisionObject.h>
#include <BulletCollision/Gimpact/btBoxCollision.h>
#include <LinearMath/btVector3.h>
#include <memory>
#include <string>
#include <vector>
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<Resource::BulletShapeInstance>&& 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<Resource::BulletShapeInstance>& getShapeInstance() const noexcept { return mShapeInstance; }
const DetourNavigator::ObjectTransform& getObjectTransform() const noexcept { return mObjectTransform; }
btCollisionObject& getCollisionObject() const noexcept { return *mCollisionObject; }
private:
osg::ref_ptr<Resource::BulletShapeInstance> mShapeInstance;
DetourNavigator::ObjectTransform mObjectTransform;
std::unique_ptr<btCollisionObject> mCollisionObject;
};
struct WorldspaceData
{
std::vector<std::unique_ptr<WorldspaceNavMeshInput>> mNavMeshInputs;
std::vector<BulletObject> mObjects;
std::vector<std::unique_ptr<ESM::Land::LandData>> mLandData;
std::vector<std::vector<float>> mHeightfields;
};
WorldspaceData gatherWorldspaceData(const DetourNavigator::Settings& settings, std::vector<ESM::ESMReader>& readers,
const VFS::Manager& vfs, Resource::BulletShapeManager& bulletShapeManager, const EsmLoader::EsmData& esmData,
bool processInteriorCells);
}
#endif

@ -20,7 +20,7 @@ namespace MWPhysics
mPtr = updated; mPtr = updated;
} }
MWWorld::Ptr getPtr() MWWorld::Ptr getPtr() const
{ {
return mPtr; return mPtr;
} }

@ -4,6 +4,7 @@
#include <components/sceneutil/agentpath.hpp> #include <components/sceneutil/agentpath.hpp>
#include <components/resource/resourcesystem.hpp> #include <components/resource/resourcesystem.hpp>
#include <components/resource/scenemanager.hpp> #include <components/resource/scenemanager.hpp>
#include <components/detournavigator/settings.hpp>
#include <osg/PositionAttitudeTransform> #include <osg/PositionAttitudeTransform>
@ -47,7 +48,7 @@ namespace MWRender
if (group != mGroups.end()) if (group != mGroups.end())
mRootNode->removeChild(group->second); 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) if (newGroup)
{ {
MWBase::Environment::get().getResourceSystem()->getSceneManager()->recreateShaders(newGroup, "debug"); MWBase::Environment::get().getResourceSystem()->getSceneManager()->recreateShaders(newGroup, "debug");

@ -54,7 +54,7 @@ namespace MWRender
if (it->second.mGeneration != tile->second->getGeneration() if (it->second.mGeneration != tile->second->getGeneration()
|| it->second.mRevision != tile->second->getRevision()) || 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"); MWBase::Environment::get().getResourceSystem()->getSceneManager()->recreateShaders(group, "debug");
group->setNodeMask(Mask_Debug); group->setNodeMask(Mask_Debug);
mRootNode->removeChild(it->second.mValue); mRootNode->removeChild(it->second.mValue);
@ -71,7 +71,7 @@ namespace MWRender
{ {
if (mGroups.count(tile.first)) if (mGroups.count(tile.first))
continue; 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"); MWBase::Environment::get().getResourceSystem()->getSceneManager()->recreateShaders(group, "debug");
group->setNodeMask(Mask_Debug); group->setNodeMask(Mask_Debug);
mGroups.emplace(tile.first, Group {tile.second->getGeneration(), tile.second->getRevision(), group}); mGroups.emplace(tile.first, Group {tile.second->getGeneration(), tile.second->getRevision(), group});

@ -64,14 +64,23 @@ namespace
* osg::Quat(zr, osg::Vec3(0, 0, -1)); * 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(); const auto pos = ptr.getRefData().getPosition();
return ptr.getClass().isActor() ? makeActorOsgQuat(pos) : makeInversedOrderObjectOsgQuat(pos);
}
const auto rot = ptr.getClass().isActor() ? makeActorOsgQuat(pos) osg::Quat makeDirectNodeRotation(const MWWorld::Ptr& ptr)
: (order == RotationOrder::inverse ? makeInversedOrderObjectOsgQuat(pos) : Misc::Convert::makeOsgQuat(pos)); {
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) 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()); 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(); const ESM::RefNum& refnum = ptr.getCellRef().getRefNum();
if (!refnum.hasContentFile() || pagedRefs.find(refnum) == pagedRefs.end()) if (!refnum.hasContentFile() || pagedRefs.find(refnum) == pagedRefs.end())
@ -128,6 +137,8 @@ namespace
{ {
if (const auto object = physics.getObject(ptr)) if (const auto object = physics.getObject(ptr))
{ {
const DetourNavigator::ObjectTransform objectTransform {ptr.getRefData().getPosition(), ptr.getCellRef().getScale()};
if (ptr.getClass().isDoor() && !ptr.getCellRef().getTeleport()) if (ptr.getClass().isDoor() && !ptr.getCellRef().getTeleport())
{ {
btVector3 aabbMin; btVector3 aabbMin;
@ -159,7 +170,7 @@ namespace
navigator.addObject( navigator.addObject(
DetourNavigator::ObjectId(object), DetourNavigator::ObjectId(object),
DetourNavigator::DoorShapes(object->getShapeInstance(), connectionStart, connectionEnd), DetourNavigator::DoorShapes(object->getShapeInstance(), objectTransform, connectionStart, connectionEnd),
transform transform
); );
} }
@ -167,7 +178,7 @@ namespace
{ {
navigator.addObject( navigator.addObject(
DetourNavigator::ObjectId(object), DetourNavigator::ObjectId(object),
DetourNavigator::ObjectShapes(object->getShapeInstance()), DetourNavigator::ObjectShapes(object->getShapeInstance(), objectTransform),
object->getTransform() object->getTransform()
); );
} }
@ -339,8 +350,7 @@ namespace MWWorld
if (const auto pathgrid = world->getStore().get<ESM::Pathgrid>().search(*cell->getCell())) if (const auto pathgrid = world->getStore().get<ESM::Pathgrid>().search(*cell->getCell()))
mNavigator.removePathgrid(*pathgrid); mNavigator.removePathgrid(*pathgrid);
const auto player = world->getPlayerPtr(); mNavigator.update(world->getPlayerPtr().getRefData().getPosition().asVec3());
mNavigator.update(player.getRefData().getPosition().asVec3());
MWBase::Environment::get().getMechanicsManager()->drop (cell); MWBase::Environment::get().getMechanicsManager()->drop (cell);
@ -367,6 +377,8 @@ namespace MWWorld
const int cellX = cell->getCell()->getGridX(); const int cellX = cell->getCell()->getGridX();
const int cellY = cell->getCell()->getGridY(); const int cellY = cell->getCell()->getGridY();
mNavigator.setWorldspace(cell->getCell()->mCellId.mWorldspace);
if (cell->getCell()->isExterior()) if (cell->getCell()->isExterior())
{ {
osg::ref_ptr<const ESMTerrain::LandObject> land = mRendering.getLandManager()->getLand(cellX, cellY); osg::ref_ptr<const ESMTerrain::LandObject> land = mRendering.getLandManager()->getLand(cellX, cellY);
@ -496,10 +508,13 @@ namespace MWWorld
void Scene::playerMoved(const osg::Vec3f &pos) void Scene::playerMoved(const osg::Vec3f &pos)
{ {
if (mCurrentCell == nullptr)
return;
const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr();
mNavigator.updatePlayerPosition(player.getRefData().getPosition().asVec3()); mNavigator.updatePlayerPosition(player.getRefData().getPosition().asVec3());
if (!mCurrentCell || !mCurrentCell->isExterior()) if (!mCurrentCell->isExterior())
return; return;
osg::Vec2i newCell = getNewGridCenter(pos, &mCurrentGridCenter); osg::Vec2i newCell = getNewGridCenter(pos, &mCurrentGridCenter);
@ -875,9 +890,12 @@ namespace MWWorld
addObject(ptr, *mPhysics, mRendering, mPagedRefs); addObject(ptr, *mPhysics, mRendering, mPagedRefs);
addObject(ptr, *mPhysics, mNavigator); addObject(ptr, *mPhysics, mNavigator);
MWBase::Environment::get().getWorld()->scaleObject(ptr, ptr.getCellRef().getScale()); MWBase::Environment::get().getWorld()->scaleObject(ptr, ptr.getCellRef().getScale());
if (mCurrentCell != nullptr)
{
const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr();
mNavigator.update(player.getRefData().getPosition().asVec3()); mNavigator.update(player.getRefData().getPosition().asVec3());
} }
}
catch (std::exception& e) catch (std::exception& e)
{ {
Log(Debug::Error) << "failed to render '" << ptr.getCellRef().getRefId() << "': " << e.what(); Log(Debug::Error) << "failed to render '" << ptr.getCellRef().getRefId() << "': " << e.what();
@ -892,9 +910,12 @@ namespace MWWorld
if (const auto object = mPhysics->getObject(ptr)) if (const auto object = mPhysics->getObject(ptr))
{ {
mNavigator.removeObject(DetourNavigator::ObjectId(object)); mNavigator.removeObject(DetourNavigator::ObjectId(object));
if (mCurrentCell != nullptr)
{
const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr();
mNavigator.update(player.getRefData().getPosition().asVec3()); mNavigator.update(player.getRefData().getPosition().asVec3());
} }
}
else if (mPhysics->getActor(ptr)) else if (mPhysics->getActor(ptr))
{ {
mNavigator.removeAgent(MWBase::Environment::get().getWorld()->getPathfindingHalfExtents(ptr)); mNavigator.removeAgent(MWBase::Environment::get().getWorld()->getPathfindingHalfExtents(ptr));

@ -186,8 +186,8 @@ namespace MWWorld
if (Settings::Manager::getBool("enable", "Navigator")) if (Settings::Manager::getBool("enable", "Navigator"))
{ {
auto navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager(); auto navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager();
navigatorSettings.mSwimHeightScale = mSwimHeightScale; navigatorSettings.mRecast.mSwimHeightScale = mSwimHeightScale;
mNavigator = DetourNavigator::makeNavigator(navigatorSettings); mNavigator = DetourNavigator::makeNavigator(navigatorSettings, userDataPath);
} }
else else
{ {
@ -1524,16 +1524,19 @@ namespace MWWorld
if (const auto object = mPhysics->getObject(door.first)) if (const auto object = mPhysics->getObject(door.first))
updateNavigatorObject(*object); 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; mShouldUpdateNavigator = false;
} }
} }
void World::updateNavigatorObject(const MWPhysics::Object& object) 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 = mNavigator->updateObject(DetourNavigator::ObjectId(&object), shapes, object.getTransform())
|| mShouldUpdateNavigator; || mShouldUpdateNavigator;
} }

@ -41,10 +41,14 @@ if (GTEST_FOUND AND GMOCK_FOUND)
detournavigator/recastmeshobject.cpp detournavigator/recastmeshobject.cpp
detournavigator/navmeshtilescache.cpp detournavigator/navmeshtilescache.cpp
detournavigator/tilecachedrecastmeshmanager.cpp detournavigator/tilecachedrecastmeshmanager.cpp
detournavigator/serialization/binaryreader.cpp detournavigator/navmeshdb.cpp
detournavigator/serialization/binarywriter.cpp detournavigator/serialization.cpp
detournavigator/serialization/sizeaccumulator.cpp detournavigator/asyncnavmeshupdater.cpp
detournavigator/serialization/integration.cpp
serialization/binaryreader.cpp
serialization/binarywriter.cpp
serialization/sizeaccumulator.cpp
serialization/integration.cpp
settings/parser.cpp settings/parser.cpp

@ -0,0 +1,201 @@
#include "settings.hpp"
#include <components/detournavigator/asyncnavmeshupdater.hpp>
#include <components/detournavigator/makenavmesh.hpp>
#include <components/detournavigator/serialization.hpp>
#include <components/loadinglistener/loadinglistener.hpp>
#include <components/detournavigator/navmeshdbutils.hpp>
#include <components/detournavigator/dbrefgeometryobject.hpp>
#include <DetourNavMesh.h>
#include <gtest/gtest.h>
#include <map>
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<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), 1);
const std::map<TilePosition, ChangeType> 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<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), 1);
const std::map<TilePosition, ChangeType> 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<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), 1);
const std::map<TilePosition, ChangeType> 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<NavMeshDb>(":memory:");
NavMeshDb* const dbPtr = db.get();
AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, std::move(db));
const auto navMeshCacheItem = std::make_shared<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), 1);
const TilePosition tilePosition {0, 0};
const std::map<TilePosition, ChangeType> 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<DbRefGeometryObject> 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<NavMeshDb>(":memory:");
NavMeshDb* const dbPtr = db.get();
mSettings.mWriteToNavMeshDb = false;
AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, std::move(db));
const auto navMeshCacheItem = std::make_shared<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), 1);
const TilePosition tilePosition {0, 0};
const std::map<TilePosition, ChangeType> 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<DbRefGeometryObject> 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<NavMeshDb>(":memory:"));
const auto navMeshCacheItem = std::make_shared<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), 1);
const std::map<TilePosition, ChangeType> 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<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), 1);
const std::map<TilePosition, ChangeType> 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<TilePosition, ChangeType> 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);
}
}

@ -0,0 +1,51 @@
#ifndef OPENMW_TEST_SUITE_DETOURNAVIGATOR_GENERATE_H
#define OPENMW_TEST_SUITE_DETOURNAVIGATOR_GENERATE_H
#include <algorithm>
#include <numeric>
#include <random>
#include <type_traits>
namespace DetourNavigator
{
namespace Tests
{
template <class T, class Random>
inline auto generateValue(T& value, Random& random)
-> std::enable_if_t<sizeof(T) >= 2>
{
using Distribution = std::conditional_t<
std::is_floating_point_v<T>,
std::uniform_real_distribution<T>,
std::uniform_int_distribution<T>
>;
Distribution distribution(std::numeric_limits<T>::min(), std::numeric_limits<T>::max());
value = distribution(random);
}
template <class T, class Random>
inline auto generateValue(T& value, Random& random)
-> std::enable_if_t<sizeof(T) == 1>
{
unsigned short v;
generateValue(v, random);
value = static_cast<T>(v % 256);
}
template <class Random>
inline void generateValue(unsigned char& value, Random& random)
{
unsigned short v;
generateValue(v, random);
value = static_cast<unsigned char>(v % 256);
}
template <class I, class Random>
inline void generateRange(I begin, I end, Random& random)
{
std::for_each(begin, end, [&] (auto& v) { generateValue(v, random); });
}
}
}
#endif

@ -21,7 +21,7 @@ namespace
struct DetourNavigatorGetTilesPositionsTest : Test struct DetourNavigatorGetTilesPositionsTest : Test
{ {
Settings mSettings; RecastSettings mSettings;
std::vector<TilePosition> mTilesPositions; std::vector<TilePosition> mTilesPositions;
CollectTilesPositions mCollect {mTilesPositions}; CollectTilesPositions mCollect {mTilesPositions};

@ -1,8 +1,10 @@
#include "operators.hpp" #include "operators.hpp"
#include "settings.hpp"
#include <components/detournavigator/navigatorimpl.hpp> #include <components/detournavigator/navigatorimpl.hpp>
#include <components/detournavigator/exceptions.hpp> #include <components/detournavigator/exceptions.hpp>
#include <components/detournavigator/navigatorutils.hpp> #include <components/detournavigator/navigatorutils.hpp>
#include <components/detournavigator/navmeshdb.hpp>
#include <components/misc/rng.hpp> #include <components/misc/rng.hpp>
#include <components/loadinglistener/loadinglistener.hpp> #include <components/loadinglistener/loadinglistener.hpp>
#include <components/esm/loadland.hpp> #include <components/esm/loadland.hpp>
@ -31,12 +33,14 @@ namespace
{ {
using namespace testing; using namespace testing;
using namespace DetourNavigator; using namespace DetourNavigator;
using namespace DetourNavigator::Tests;
struct DetourNavigatorNavigatorTest : Test struct DetourNavigatorNavigatorTest : Test
{ {
Settings mSettings; Settings mSettings = makeSettings();
std::unique_ptr<Navigator> mNavigator; std::unique_ptr<Navigator> mNavigator;
const osg::Vec3f mPlayerPosition; const osg::Vec3f mPlayerPosition;
const std::string mWorldspace;
const osg::Vec3f mAgentHalfExtents; const osg::Vec3f mAgentHalfExtents;
osg::Vec3f mStart; osg::Vec3f mStart;
osg::Vec3f mEnd; osg::Vec3f mEnd;
@ -49,44 +53,18 @@ namespace
const int mHeightfieldTileSize = ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1); const int mHeightfieldTileSize = ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1);
const float mEndTolerance = 0; const float mEndTolerance = 0;
const btTransform mTransform {btMatrix3x3::getIdentity(), btVector3(256, 256, 0)}; const btTransform mTransform {btMatrix3x3::getIdentity(), btVector3(256, 256, 0)};
const ObjectTransform mObjectTransform {ESM::Position {{256, 256, 0}, {0, 0, 0}}, 0.0f};
DetourNavigatorNavigatorTest() DetourNavigatorNavigatorTest()
: mPlayerPosition(256, 256, 0) : mPlayerPosition(256, 256, 0)
, mWorldspace("sys::default")
, mAgentHalfExtents(29, 29, 66) , mAgentHalfExtents(29, 29, 66)
, mStart(52, 460, 1) , mStart(52, 460, 1)
, mEnd(460, 52, 1) , mEnd(460, 52, 1)
, mOut(mPath) , mOut(mPath)
, mStepSize(28.333332061767578125f) , mStepSize(28.333332061767578125f)
{ {
mSettings.mEnableWriteRecastMeshToFile = false; mNavigator.reset(new NavigatorImpl(mSettings, std::make_unique<NavMeshDb>(":memory:")));
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<int>::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));
} }
}; };
@ -258,7 +236,7 @@ namespace
Vec3fEq(460, 56.66666412353515625, 1.99998295307159423828125) Vec3fEq(460, 56.66666412353515625, 1.99998295307159423828125)
)) << mPath; )) << mPath;
mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance()), mTransform); mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance(), mObjectTransform), mTransform);
mNavigator->update(mPlayerPosition); mNavigator->update(mPlayerPosition);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->wait(mListener, WaitConditionType::allJobsDone);
@ -311,7 +289,7 @@ namespace
mNavigator->addAgent(mAgentHalfExtents); mNavigator->addAgent(mAgentHalfExtents);
mNavigator->addHeightfield(mCellPosition, cellSize, surface); 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->update(mPlayerPosition);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->wait(mListener, WaitConditionType::allJobsDone);
@ -346,7 +324,7 @@ namespace
compound.shape().updateChildTransform(0, btTransform(btMatrix3x3::getIdentity(), btVector3(1000, 0, 0))); 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->update(mPlayerPosition);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->wait(mListener, WaitConditionType::allJobsDone);
@ -404,8 +382,8 @@ namespace
heightfield2.shape().setLocalScaling(btVector3(128, 128, 1)); heightfield2.shape().setLocalScaling(btVector3(128, 128, 1));
mNavigator->addAgent(mAgentHalfExtents); mNavigator->addAgent(mAgentHalfExtents);
mNavigator->addObject(ObjectId(&heightfield1.shape()), ObjectShapes(heightfield1.instance()), mTransform); mNavigator->addObject(ObjectId(&heightfield1.shape()), ObjectShapes(heightfield1.instance(), mObjectTransform), mTransform);
mNavigator->addObject(ObjectId(&heightfield2.shape()), ObjectShapes(heightfield2.instance()), mTransform); mNavigator->addObject(ObjectId(&heightfield2.shape()), ObjectShapes(heightfield2.instance(), mObjectTransform), mTransform);
mNavigator->update(mPlayerPosition); mNavigator->update(mPlayerPosition);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->wait(mListener, WaitConditionType::allJobsDone);
@ -494,7 +472,7 @@ namespace
osg::ref_ptr<const Resource::BulletShapeInstance> instance(new Resource::BulletShapeInstance(bulletShape)); osg::ref_ptr<const Resource::BulletShapeInstance> instance(new Resource::BulletShapeInstance(bulletShape));
mNavigator->addAgent(mAgentHalfExtents); 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->update(mPlayerPosition);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->wait(mListener, WaitConditionType::allJobsDone);
@ -725,7 +703,7 @@ namespace
heightfield.shape().setLocalScaling(btVector3(128, 128, 1)); heightfield.shape().setLocalScaling(btVector3(128, 128, 1));
mNavigator->addAgent(mAgentHalfExtents); 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->update(mPlayerPosition);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->wait(mListener, WaitConditionType::allJobsDone);
@ -733,7 +711,7 @@ namespace
mNavigator->update(mPlayerPosition); mNavigator->update(mPlayerPosition);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); 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->update(mPlayerPosition);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->wait(mListener, WaitConditionType::allJobsDone);
@ -853,7 +831,7 @@ namespace
TEST_F(DetourNavigatorNavigatorTest, multiple_threads_should_lock_tiles) TEST_F(DetourNavigatorNavigatorTest, multiple_threads_should_lock_tiles)
{ {
mSettings.mAsyncNavMeshUpdaterThreads = 2; mSettings.mAsyncNavMeshUpdaterThreads = 2;
mNavigator.reset(new NavigatorImpl(mSettings)); mNavigator.reset(new NavigatorImpl(mSettings, std::make_unique<NavMeshDb>(":memory:")));
const std::array<float, 5 * 5> heightfieldData {{ const std::array<float, 5 * 5> heightfieldData {{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
@ -876,7 +854,7 @@ namespace
for (std::size_t i = 0; i < boxes.size(); ++i) 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)); 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)); std::this_thread::sleep_for(std::chrono::microseconds(1));
@ -884,7 +862,7 @@ namespace
for (std::size_t i = 0; i < boxes.size(); ++i) 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)); 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); mNavigator->update(mPlayerPosition);
@ -930,7 +908,7 @@ namespace
for (std::size_t i = 0; i < shapes.size(); ++i) for (std::size_t i = 0; i < shapes.size(); ++i)
{ {
const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 32, i * 32, i * 32)); 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->update(mPlayerPosition);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->wait(mListener, WaitConditionType::allJobsDone);
@ -939,7 +917,7 @@ namespace
for (std::size_t i = 0; i < shapes.size(); ++i) 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)); 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->update(mPlayerPosition);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->wait(mListener, WaitConditionType::allJobsDone);
@ -947,7 +925,7 @@ namespace
for (std::size_t i = 0; i < shapes.size(); ++i) 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)); 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->update(mPlayerPosition);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->wait(mListener, WaitConditionType::allJobsDone);
@ -1001,15 +979,15 @@ namespace
mNavigator->addAgent(mAgentHalfExtents); mNavigator->addAgent(mAgentHalfExtents);
mNavigator->addHeightfield(mCellPosition, cellSize, surface); 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)); btTransform(btMatrix3x3::getIdentity(), oscillatingBoxShapePosition));
// add this box to make navmesh bound box independent from oscillatingBoxShape rotations // 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))); btTransform(btMatrix3x3::getIdentity(), oscillatingBoxShapePosition + btVector3(0, 0, 200)));
mNavigator->update(mPlayerPosition); mNavigator->update(mPlayerPosition);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->wait(mListener, WaitConditionType::allJobsDone);
const Version expectedVersion {1, 1}; const Version expectedVersion {1, 4};
const auto navMeshes = mNavigator->getNavMeshes(); const auto navMeshes = mNavigator->getNavMeshes();
ASSERT_EQ(navMeshes.size(), 1); ASSERT_EQ(navMeshes.size(), 1);
@ -1019,7 +997,7 @@ namespace
{ {
const btTransform transform(btQuaternion(btVector3(0, 0, 1), n * 2 * osg::PI / 10), const btTransform transform(btQuaternion(btVector3(0, 0, 1), n * 2 * osg::PI / 10),
oscillatingBoxShapePosition); oscillatingBoxShapePosition);
mNavigator->updateObject(ObjectId(&oscillatingBox.shape()), ObjectShapes(oscillatingBox.instance()), transform); mNavigator->updateObject(ObjectId(&oscillatingBox.shape()), ObjectShapes(oscillatingBox.instance(), mObjectTransform), transform);
mNavigator->update(mPlayerPosition); mNavigator->update(mPlayerPosition);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->wait(mListener, WaitConditionType::allJobsDone);
} }
@ -1085,7 +1063,7 @@ namespace
mNavigator->addAgent(mAgentHalfExtents); mNavigator->addAgent(mAgentHalfExtents);
mNavigator->addHeightfield(mCellPosition, cellSize, surface); 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->update(mPlayerPosition);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->wait(mListener, WaitConditionType::allJobsDone);
@ -1124,7 +1102,7 @@ namespace
mNavigator->addAgent(mAgentHalfExtents); mNavigator->addAgent(mAgentHalfExtents);
mNavigator->addHeightfield(mCellPosition, cellSize, surface); 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->update(mPlayerPosition);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->wait(mListener, WaitConditionType::allJobsDone);

@ -0,0 +1,112 @@
#include "generate.hpp"
#include <components/detournavigator/navmeshdb.hpp>
#include <components/esm/cellid.hpp>
#include <DetourAlloc.h>
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <numeric>
#include <random>
namespace
{
using namespace testing;
using namespace DetourNavigator;
using namespace DetourNavigator::Tests;
struct Tile
{
std::string mWorldspace;
TilePosition mTilePosition;
std::vector<std::byte> mInput;
std::vector<std::byte> mData;
};
struct DetourNavigatorNavMeshDbTest : Test
{
NavMeshDb mDb {":memory:"};
std::minstd_rand mRandom;
std::vector<std::byte> generateData()
{
std::vector<std::byte> 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<std::byte> input = generateData();
std::vector<std::byte> 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<std::byte> input = generateData();
const std::vector<std::byte> 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<std::byte> input = generateData();
const std::vector<std::byte> 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));
}
}

@ -1,4 +1,5 @@
#include "operators.hpp" #include "operators.hpp"
#include "generate.hpp"
#include <components/detournavigator/navmeshtilescache.hpp> #include <components/detournavigator/navmeshtilescache.hpp>
#include <components/detournavigator/exceptions.hpp> #include <components/detournavigator/exceptions.hpp>
@ -21,23 +22,17 @@ namespace
{ {
using namespace testing; using namespace testing;
using namespace DetourNavigator; using namespace DetourNavigator;
using namespace DetourNavigator::Tests;
void* permRecastAlloc(int size) template <class T, class Random>
{ void generateRecastArray(T*& values, int size, Random& random)
void* result = rcAlloc(static_cast<std::size_t>(size), RC_ALLOC_PERM);
if (result == nullptr)
throw std::bad_alloc();
return result;
}
template <class T>
void generate(T*& values, int size)
{ {
values = static_cast<T*>(permRecastAlloc(size * sizeof(T))); values = static_cast<T*>(permRecastAlloc(size * sizeof(T)));
std::generate_n(values, static_cast<std::size_t>(size), [] { return static_cast<T>(std::rand()); }); generateRange(values, values + static_cast<std::ptrdiff_t>(size), random);
} }
void generate(rcPolyMesh& value, int size) template <class Random>
void generate(rcPolyMesh& value, int size, Random& random)
{ {
value.nverts = size; value.nverts = size;
value.maxpolys = size; value.maxpolys = size;
@ -45,88 +40,49 @@ namespace
value.npolys = size; value.npolys = size;
rcVcopy(value.bmin, osg::Vec3f(-1, -2, -3).ptr()); rcVcopy(value.bmin, osg::Vec3f(-1, -2, -3).ptr());
rcVcopy(value.bmax, osg::Vec3f(3, 2, 1).ptr()); rcVcopy(value.bmax, osg::Vec3f(3, 2, 1).ptr());
value.cs = 1.0f / (std::rand() % 999 + 1); generateValue(value.cs, random);
value.ch = 1.0f / (std::rand() % 999 + 1); generateValue(value.ch, random);
value.borderSize = std::rand(); generateValue(value.borderSize, random);
value.maxEdgeError = 1.0f / (std::rand() % 999 + 1); generateValue(value.maxEdgeError, random);
generate(value.verts, getVertsLength(value)); generateRecastArray(value.verts, getVertsLength(value), random);
generate(value.polys, getPolysLength(value)); generateRecastArray(value.polys, getPolysLength(value), random);
generate(value.regs, getRegsLength(value)); generateRecastArray(value.regs, getRegsLength(value), random);
generate(value.flags, getFlagsLength(value)); generateRecastArray(value.flags, getFlagsLength(value), random);
generate(value.areas, getAreasLength(value)); generateRecastArray(value.areas, getAreasLength(value), random);
} }
void generate(rcPolyMeshDetail& value, int size) template <class Random>
void generate(rcPolyMeshDetail& value, int size, Random& random)
{ {
value.nmeshes = size; value.nmeshes = size;
value.nverts = size; value.nverts = size;
value.ntris = size; value.ntris = size;
generate(value.meshes, getMeshesLength(value)); generateRecastArray(value.meshes, getMeshesLength(value), random);
generate(value.verts, getVertsLength(value)); generateRecastArray(value.verts, getVertsLength(value), random);
generate(value.tris, getTrisLength(value)); generateRecastArray(value.tris, getTrisLength(value), random);
} }
void generate(PreparedNavMeshData& value, int size) template <class Random>
void generate(PreparedNavMeshData& value, int size, Random& random)
{ {
value.mUserId = std::rand(); generateValue(value.mUserId, random);
value.mCellHeight = 1.0f / (std::rand() % 999 + 1); generateValue(value.mCellHeight, random);
value.mCellSize = 1.0f / (std::rand() % 999 + 1); generateValue(value.mCellSize, random);
generate(value.mPolyMesh, size); generate(value.mPolyMesh, size, random);
generate(value.mPolyMeshDetail, size); generate(value.mPolyMeshDetail, size, random);
} }
std::unique_ptr<PreparedNavMeshData> makePeparedNavMeshData(int size) std::unique_ptr<PreparedNavMeshData> makePeparedNavMeshData(int size)
{ {
std::minstd_rand random;
auto result = std::make_unique<PreparedNavMeshData>(); auto result = std::make_unique<PreparedNavMeshData>();
generate(*result, size); generate(*result, size, random);
return result; return result;
} }
template <class T>
void clone(const T* src, T*& dst, std::size_t size)
{
dst = static_cast<T*>(permRecastAlloc(static_cast<int>(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<PreparedNavMeshData> clone(const PreparedNavMeshData& value) std::unique_ptr<PreparedNavMeshData> clone(const PreparedNavMeshData& value)
{ {
auto result = std::make_unique<PreparedNavMeshData>(); return std::make_unique<PreparedNavMeshData>(value);
result->mUserId = value.mUserId;
result->mCellHeight = value.mCellHeight;
result->mCellSize = value.mCellSize;
clone(value.mPolyMesh, result->mPolyMesh);
clone(value.mPolyMeshDetail, result->mPolyMeshDetail);
return result;
} }
Mesh makeMesh() Mesh makeMesh()
@ -147,7 +103,8 @@ namespace
const std::vector<CellWater> mWater {}; const std::vector<CellWater> mWater {};
const std::vector<Heightfield> mHeightfields {}; const std::vector<Heightfield> mHeightfields {};
const std::vector<FlatHeightfield> mFlatHeightfields {}; const std::vector<FlatHeightfield> mFlatHeightfields {};
const RecastMesh mRecastMesh {mGeneration, mRevision, mMesh, mWater, mHeightfields, mFlatHeightfields}; const std::vector<MeshSource> mSources {};
const RecastMesh mRecastMesh {mGeneration, mRevision, mMesh, mWater, mHeightfields, mFlatHeightfields, mSources};
std::unique_ptr<PreparedNavMeshData> mPreparedNavMeshData {makePeparedNavMeshData(3)}; std::unique_ptr<PreparedNavMeshData> mPreparedNavMeshData {makePeparedNavMeshData(3)};
const std::size_t mRecastMeshSize = sizeof(mRecastMesh) + getSize(mRecastMesh); const std::size_t mRecastMeshSize = sizeof(mRecastMesh) + getSize(mRecastMesh);
@ -235,7 +192,7 @@ namespace
const std::size_t maxSize = 1; const std::size_t maxSize = 1;
NavMeshTilesCache cache(maxSize); NavMeshTilesCache cache(maxSize);
const std::vector<CellWater> water(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); const std::vector<CellWater> 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)); cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, std::move(mPreparedNavMeshData));
EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, unexistentRecastMesh)); EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, unexistentRecastMesh));
@ -247,7 +204,7 @@ namespace
NavMeshTilesCache cache(maxSize); NavMeshTilesCache cache(maxSize);
const std::vector<CellWater> water(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); const std::vector<CellWater> 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); auto anotherPreparedNavMeshData = makePeparedNavMeshData(3);
const auto copy = clone(*anotherPreparedNavMeshData); const auto copy = clone(*anotherPreparedNavMeshData);
@ -265,7 +222,7 @@ namespace
NavMeshTilesCache cache(maxSize); NavMeshTilesCache cache(maxSize);
const std::vector<CellWater> water(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); const std::vector<CellWater> 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); auto anotherPreparedNavMeshData = makePeparedNavMeshData(3);
const auto value = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, const auto value = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh,
@ -281,13 +238,13 @@ namespace
const auto copy = clone(*mPreparedNavMeshData); const auto copy = clone(*mPreparedNavMeshData);
const std::vector<CellWater> leastRecentlySetWater(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); const std::vector<CellWater> leastRecentlySetWater(1, CellWater {osg::Vec2i(), Water {1, 0.0f}});
const RecastMesh leastRecentlySetRecastMesh {mGeneration, mRevision, mMesh, leastRecentlySetWater, const RecastMesh leastRecentlySetRecastMesh(mGeneration, mRevision, mMesh, leastRecentlySetWater,
mHeightfields, mFlatHeightfields}; mHeightfields, mFlatHeightfields, mSources);
auto leastRecentlySetData = makePeparedNavMeshData(3); auto leastRecentlySetData = makePeparedNavMeshData(3);
const std::vector<CellWater> mostRecentlySetWater(1, CellWater {osg::Vec2i(), Water {2, 0.0f}}); const std::vector<CellWater> mostRecentlySetWater(1, CellWater {osg::Vec2i(), Water {2, 0.0f}});
const RecastMesh mostRecentlySetRecastMesh {mGeneration, mRevision, mMesh, mostRecentlySetWater, const RecastMesh mostRecentlySetRecastMesh(mGeneration, mRevision, mMesh, mostRecentlySetWater,
mHeightfields, mFlatHeightfields}; mHeightfields, mFlatHeightfields, mSources);
auto mostRecentlySetData = makePeparedNavMeshData(3); auto mostRecentlySetData = makePeparedNavMeshData(3);
ASSERT_TRUE(cache.set(mAgentHalfExtents, mTilePosition, leastRecentlySetRecastMesh, ASSERT_TRUE(cache.set(mAgentHalfExtents, mTilePosition, leastRecentlySetRecastMesh,
@ -309,14 +266,14 @@ namespace
NavMeshTilesCache cache(maxSize); NavMeshTilesCache cache(maxSize);
const std::vector<CellWater> leastRecentlyUsedWater(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); const std::vector<CellWater> leastRecentlyUsedWater(1, CellWater {osg::Vec2i(), Water {1, 0.0f}});
const RecastMesh leastRecentlyUsedRecastMesh {mGeneration, mRevision, mMesh, leastRecentlyUsedWater, const RecastMesh leastRecentlyUsedRecastMesh(mGeneration, mRevision, mMesh, leastRecentlyUsedWater,
mHeightfields, mFlatHeightfields}; mHeightfields, mFlatHeightfields, mSources);
auto leastRecentlyUsedData = makePeparedNavMeshData(3); auto leastRecentlyUsedData = makePeparedNavMeshData(3);
const auto leastRecentlyUsedCopy = clone(*leastRecentlyUsedData); const auto leastRecentlyUsedCopy = clone(*leastRecentlyUsedData);
const std::vector<CellWater> mostRecentlyUsedWater(1, CellWater {osg::Vec2i(), Water {2, 0.0f}}); const std::vector<CellWater> mostRecentlyUsedWater(1, CellWater {osg::Vec2i(), Water {2, 0.0f}});
const RecastMesh mostRecentlyUsedRecastMesh {mGeneration, mRevision, mMesh, mostRecentlyUsedWater, const RecastMesh mostRecentlyUsedRecastMesh(mGeneration, mRevision, mMesh, mostRecentlyUsedWater,
mHeightfields, mFlatHeightfields}; mHeightfields, mFlatHeightfields, mSources);
auto mostRecentlyUsedData = makePeparedNavMeshData(3); auto mostRecentlyUsedData = makePeparedNavMeshData(3);
const auto mostRecentlyUsedCopy = clone(*mostRecentlyUsedData); const auto mostRecentlyUsedCopy = clone(*mostRecentlyUsedData);
@ -350,8 +307,8 @@ namespace
NavMeshTilesCache cache(maxSize); NavMeshTilesCache cache(maxSize);
const std::vector<CellWater> water(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); const std::vector<CellWater> water(1, CellWater {osg::Vec2i(), Water {1, 0.0f}});
const RecastMesh tooLargeRecastMesh {mGeneration, mRevision, mMesh, water, const RecastMesh tooLargeRecastMesh(mGeneration, mRevision, mMesh, water,
mHeightfields, mFlatHeightfields}; mHeightfields, mFlatHeightfields, mSources);
auto tooLargeData = makePeparedNavMeshData(10); auto tooLargeData = makePeparedNavMeshData(10);
cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, std::move(mPreparedNavMeshData)); cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, std::move(mPreparedNavMeshData));
@ -365,13 +322,13 @@ namespace
NavMeshTilesCache cache(maxSize); NavMeshTilesCache cache(maxSize);
const std::vector<CellWater> anotherWater(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); const std::vector<CellWater> anotherWater(1, CellWater {osg::Vec2i(), Water {1, 0.0f}});
const RecastMesh anotherRecastMesh {mGeneration, mRevision, mMesh, anotherWater, const RecastMesh anotherRecastMesh(mGeneration, mRevision, mMesh, anotherWater,
mHeightfields, mFlatHeightfields}; mHeightfields, mFlatHeightfields, mSources);
auto anotherData = makePeparedNavMeshData(3); auto anotherData = makePeparedNavMeshData(3);
const std::vector<CellWater> tooLargeWater(1, CellWater {osg::Vec2i(), Water {2, 0.0f}}); const std::vector<CellWater> tooLargeWater(1, CellWater {osg::Vec2i(), Water {2, 0.0f}});
const RecastMesh tooLargeRecastMesh {mGeneration, mRevision, mMesh, tooLargeWater, const RecastMesh tooLargeRecastMesh(mGeneration, mRevision, mMesh, tooLargeWater,
mHeightfields, mFlatHeightfields}; mHeightfields, mFlatHeightfields, mSources);
auto tooLargeData = makePeparedNavMeshData(10); auto tooLargeData = makePeparedNavMeshData(10);
const auto value = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, const auto value = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh,
@ -391,7 +348,7 @@ namespace
NavMeshTilesCache cache(maxSize); NavMeshTilesCache cache(maxSize);
const std::vector<CellWater> water(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); const std::vector<CellWater> 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); auto anotherData = makePeparedNavMeshData(3);
const auto firstCopy = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, std::move(mPreparedNavMeshData)); const auto firstCopy = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, std::move(mPreparedNavMeshData));
@ -410,7 +367,7 @@ namespace
NavMeshTilesCache cache(maxSize); NavMeshTilesCache cache(maxSize);
const std::vector<CellWater> water(1, CellWater {osg::Vec2i(), Water {1, 0.0f}}); const std::vector<CellWater> 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); auto anotherData = makePeparedNavMeshData(3);
cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, std::move(mPreparedNavMeshData)); cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, std::move(mPreparedNavMeshData));

@ -48,35 +48,6 @@ namespace DetourNavigator
}; };
return tie(lhs) == tie(rhs); 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<int>(v.mLength)
<< ", .mMinHeight=" << v.mMinHeight
<< ", .mMaxHeight=" << v.mMaxHeight
<< ", .mHeights={";
for (float h : v.mHeights)
s << h << ", ";
s << "}";
return s << ", .mOriginalSize=" << v.mOriginalSize << "}";
}
} }
namespace namespace
@ -89,6 +60,8 @@ namespace
TileBounds mBounds; TileBounds mBounds;
const std::size_t mGeneration = 0; const std::size_t mGeneration = 0;
const std::size_t mRevision = 0; const std::size_t mRevision = 0;
const osg::ref_ptr<const Resource::BulletShape> mSource {nullptr};
const ObjectTransform mObjectTransform {ESM::Position {{0, 0, 0}, {0, 0, 0}}, 0.0f};
DetourNavigatorRecastMeshBuilderTest() DetourNavigatorRecastMeshBuilderTest()
{ {
@ -115,7 +88,8 @@ namespace
btBvhTriangleMeshShape shape(&mesh, true); btBvhTriangleMeshShape shape(&mesh, true);
RecastMeshBuilder builder(mBounds); RecastMeshBuilder builder(mBounds);
builder.addObject(static_cast<const btCollisionShape&>(shape), btTransform::getIdentity(), AreaType_ground); builder.addObject(static_cast<const btCollisionShape&>(shape), btTransform::getIdentity(),
AreaType_ground, mSource, mObjectTransform);
const auto recastMesh = std::move(builder).create(mGeneration, mRevision); const auto recastMesh = std::move(builder).create(mGeneration, mRevision);
EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector<float>({ EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector<float>({
-1, -1, 0, -1, -1, 0,
@ -135,7 +109,7 @@ namespace
builder.addObject( builder.addObject(
static_cast<const btCollisionShape&>(shape), static_cast<const btCollisionShape&>(shape),
btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)), 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); const auto recastMesh = std::move(builder).create(mGeneration, mRevision);
EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector<float>({ EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector<float>({
@ -152,7 +126,8 @@ namespace
const std::array<btScalar, 4> heightfieldData {{0, 0, 0, 0}}; const std::array<btScalar, 4> heightfieldData {{0, 0, 0, 0}};
btHeightfieldTerrainShape shape(2, 2, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); btHeightfieldTerrainShape shape(2, 2, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false);
RecastMeshBuilder builder(mBounds); RecastMeshBuilder builder(mBounds);
builder.addObject(static_cast<const btCollisionShape&>(shape), btTransform::getIdentity(), AreaType_ground); builder.addObject(static_cast<const btCollisionShape&>(shape), btTransform::getIdentity(),
AreaType_ground, mSource, mObjectTransform);
const auto recastMesh = std::move(builder).create(mGeneration, mRevision); const auto recastMesh = std::move(builder).create(mGeneration, mRevision);
EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector<float>({ EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector<float>({
-0.5, -0.5, 0, -0.5, -0.5, 0,
@ -168,7 +143,8 @@ namespace
{ {
btBoxShape shape(btVector3(1, 1, 2)); btBoxShape shape(btVector3(1, 1, 2));
RecastMeshBuilder builder(mBounds); RecastMeshBuilder builder(mBounds);
builder.addObject(static_cast<const btCollisionShape&>(shape), btTransform::getIdentity(), AreaType_ground); builder.addObject(static_cast<const btCollisionShape&>(shape), btTransform::getIdentity(),
AreaType_ground, mSource, mObjectTransform);
const auto recastMesh = std::move(builder).create(mGeneration, mRevision); const auto recastMesh = std::move(builder).create(mGeneration, mRevision);
EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector<float>({ EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector<float>({
-1, -1, -2, -1, -1, -2,
@ -214,7 +190,7 @@ namespace
builder.addObject( builder.addObject(
static_cast<const btCollisionShape&>(shape), static_cast<const btCollisionShape&>(shape),
btTransform::getIdentity(), btTransform::getIdentity(),
AreaType_ground AreaType_ground, mSource, mObjectTransform
); );
const auto recastMesh = std::move(builder).create(mGeneration, mRevision); const auto recastMesh = std::move(builder).create(mGeneration, mRevision);
EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector<float>({ EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector<float>({
@ -261,7 +237,7 @@ namespace
builder.addObject( builder.addObject(
static_cast<const btCollisionShape&>(shape), static_cast<const btCollisionShape&>(shape),
btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)), 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); const auto recastMesh = std::move(builder).create(mGeneration, mRevision);
EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector<float>({ EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector<float>({
@ -285,7 +261,7 @@ namespace
builder.addObject( builder.addObject(
static_cast<const btCollisionShape&>(shape), static_cast<const btCollisionShape&>(shape),
btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)), 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); const auto recastMesh = std::move(builder).create(mGeneration, mRevision);
EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector<float>({ EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector<float>({
@ -307,7 +283,7 @@ namespace
builder.addObject( builder.addObject(
static_cast<const btCollisionShape&>(shape), static_cast<const btCollisionShape&>(shape),
btTransform::getIdentity(), btTransform::getIdentity(),
AreaType_ground AreaType_ground, mSource, mObjectTransform
); );
const auto recastMesh = std::move(builder).create(mGeneration, mRevision); const auto recastMesh = std::move(builder).create(mGeneration, mRevision);
EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector<float>({ EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector<float>({
@ -334,7 +310,7 @@ namespace
builder.addObject( builder.addObject(
static_cast<const btCollisionShape&>(shape), static_cast<const btCollisionShape&>(shape),
btTransform::getIdentity(), btTransform::getIdentity(),
AreaType_ground AreaType_ground, mSource, mObjectTransform
); );
const auto recastMesh = std::move(builder).create(mGeneration, mRevision); const auto recastMesh = std::move(builder).create(mGeneration, mRevision);
EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector<float>({ EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector<float>({
@ -359,7 +335,7 @@ namespace
static_cast<const btCollisionShape&>(shape), static_cast<const btCollisionShape&>(shape),
btTransform(btQuaternion(btVector3(1, 0, 0), btTransform(btQuaternion(btVector3(1, 0, 0),
static_cast<btScalar>(-osg::PI_4))), static_cast<btScalar>(-osg::PI_4))),
AreaType_ground AreaType_ground, mSource, mObjectTransform
); );
const auto recastMesh = std::move(builder).create(mGeneration, mRevision); const auto recastMesh = std::move(builder).create(mGeneration, mRevision);
EXPECT_THAT(recastMesh->getMesh().getVertices(), Pointwise(FloatNear(1e-5), std::vector<float>({ EXPECT_THAT(recastMesh->getMesh().getVertices(), Pointwise(FloatNear(1e-5), std::vector<float>({
@ -384,7 +360,7 @@ namespace
static_cast<const btCollisionShape&>(shape), static_cast<const btCollisionShape&>(shape),
btTransform(btQuaternion(btVector3(0, 1, 0), btTransform(btQuaternion(btVector3(0, 1, 0),
static_cast<btScalar>(osg::PI_4))), static_cast<btScalar>(osg::PI_4))),
AreaType_ground AreaType_ground, mSource, mObjectTransform
); );
const auto recastMesh = std::move(builder).create(mGeneration, mRevision); const auto recastMesh = std::move(builder).create(mGeneration, mRevision);
EXPECT_THAT(recastMesh->getMesh().getVertices(), Pointwise(FloatNear(1e-5), std::vector<float>({ EXPECT_THAT(recastMesh->getMesh().getVertices(), Pointwise(FloatNear(1e-5), std::vector<float>({
@ -409,7 +385,7 @@ namespace
static_cast<const btCollisionShape&>(shape), static_cast<const btCollisionShape&>(shape),
btTransform(btQuaternion(btVector3(0, 0, 1), btTransform(btQuaternion(btVector3(0, 0, 1),
static_cast<btScalar>(osg::PI_4))), static_cast<btScalar>(osg::PI_4))),
AreaType_ground AreaType_ground, mSource, mObjectTransform
); );
const auto recastMesh = std::move(builder).create(mGeneration, mRevision); const auto recastMesh = std::move(builder).create(mGeneration, mRevision);
EXPECT_THAT(recastMesh->getMesh().getVertices(), Pointwise(FloatNear(1e-5), std::vector<float>({ EXPECT_THAT(recastMesh->getMesh().getVertices(), Pointwise(FloatNear(1e-5), std::vector<float>({
@ -433,12 +409,12 @@ namespace
builder.addObject( builder.addObject(
static_cast<const btCollisionShape&>(shape1), static_cast<const btCollisionShape&>(shape1),
btTransform::getIdentity(), btTransform::getIdentity(),
AreaType_ground AreaType_ground, mSource, mObjectTransform
); );
builder.addObject( builder.addObject(
static_cast<const btCollisionShape&>(shape2), static_cast<const btCollisionShape&>(shape2),
btTransform::getIdentity(), btTransform::getIdentity(),
AreaType_null AreaType_null, mSource, mObjectTransform
); );
const auto recastMesh = std::move(builder).create(mGeneration, mRevision); const auto recastMesh = std::move(builder).create(mGeneration, mRevision);
EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector<float>({ EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector<float>({
@ -471,7 +447,7 @@ namespace
btBvhTriangleMeshShape shape(&mesh, true); btBvhTriangleMeshShape shape(&mesh, true);
RecastMeshBuilder builder(mBounds); RecastMeshBuilder builder(mBounds);
builder.addObject(static_cast<const btCollisionShape&>(shape), btTransform::getIdentity(), AreaType_ground); builder.addObject(static_cast<const btCollisionShape&>(shape), btTransform::getIdentity(), AreaType_ground, mSource, mObjectTransform);
const auto recastMesh = std::move(builder).create(mGeneration, mRevision); const auto recastMesh = std::move(builder).create(mGeneration, mRevision);
EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector<float>({ EXPECT_EQ(recastMesh->getMesh().getVertices(), std::vector<float>({
-1, -1, 0, -1, -1, 0,

@ -1,6 +1,7 @@
#include "operators.hpp" #include "operators.hpp"
#include <components/detournavigator/recastmeshobject.hpp> #include <components/detournavigator/recastmeshobject.hpp>
#include <components/misc/convert.hpp>
#include <BulletCollision/CollisionShapes/btBoxShape.h> #include <BulletCollision/CollisionShapes/btBoxShape.h>
#include <BulletCollision/CollisionShapes/btCompoundShape.h> #include <BulletCollision/CollisionShapes/btCompoundShape.h>
@ -15,10 +16,11 @@ namespace
struct DetourNavigatorRecastMeshObjectTest : Test struct DetourNavigatorRecastMeshObjectTest : Test
{ {
btBoxShape mBoxShapeImpl {btVector3(1, 2, 3)}; 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}; btCompoundShape mCompoundShapeImpl {true};
CollisionShape mCompoundShape {nullptr, mCompoundShapeImpl}; CollisionShape mCompoundShape {nullptr, mCompoundShapeImpl, mObjectTransform};
btTransform mTransform {btQuaternion(btVector3(1, 2, 3), 1), btVector3(1, 2, 3)}; btTransform mTransform {Misc::Convert::makeBulletTransform(mObjectTransform.mPosition)};
DetourNavigatorRecastMeshObjectTest() DetourNavigatorRecastMeshObjectTest()
{ {

@ -0,0 +1,50 @@
#ifndef OPENMW_TEST_SUITE_DETOURNAVIGATOR_SETTINGS_H
#define OPENMW_TEST_SUITE_DETOURNAVIGATOR_SETTINGS_H
#include <components/detournavigator/settings.hpp>
#include <chrono>
#include <limits>
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<int>::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

@ -11,7 +11,7 @@ namespace
struct DetourNavigatorGetTilePositionTest : Test struct DetourNavigatorGetTilePositionTest : Test
{ {
Settings mSettings; RecastSettings mSettings;
DetourNavigatorGetTilePositionTest() DetourNavigatorGetTilePositionTest()
{ {
@ -47,7 +47,7 @@ namespace
struct DetourNavigatorMakeTileBoundsTest : Test struct DetourNavigatorMakeTileBoundsTest : Test
{ {
Settings mSettings; RecastSettings mSettings;
DetourNavigatorMakeTileBoundsTest() DetourNavigatorMakeTileBoundsTest()
{ {

@ -15,8 +15,11 @@ namespace
struct DetourNavigatorTileCachedRecastMeshManagerTest : Test struct DetourNavigatorTileCachedRecastMeshManagerTest : Test
{ {
Settings mSettings; RecastSettings mSettings;
std::vector<TilePosition> mChangedTiles; std::vector<TilePosition> mChangedTiles;
const ObjectTransform mObjectTransform {ESM::Position {{0, 0, 0}, {0, 0, 0}}, 0.0f};
const osg::ref_ptr<const Resource::BulletShape> mShape = new Resource::BulletShape;
const osg::ref_ptr<const Resource::BulletShapeInstance> mInstance = new Resource::BulletShapeInstance(mShape);
DetourNavigatorTileCachedRecastMeshManagerTest() DetourNavigatorTileCachedRecastMeshManagerTest()
{ {
@ -35,7 +38,7 @@ namespace
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_empty_should_return_nullptr) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_empty_should_return_nullptr)
{ {
TileCachedRecastMeshManager manager(mSettings); 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) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_revision_for_empty_should_return_zero)
@ -56,7 +59,7 @@ namespace
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
const btBoxShape boxShape(btVector3(20, 20, 100)); 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)); EXPECT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground));
} }
@ -64,7 +67,7 @@ namespace
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
const btBoxShape boxShape(btVector3(20, 20, 100)); 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.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
EXPECT_FALSE(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) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_should_add_tiles)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
manager.setWorldspace("worldspace");
const btBoxShape boxShape(btVector3(20, 20, 100)); 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.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground));
for (int x = -1; x < 1; ++x) for (int x = -1; x < 1; ++x)
for (int y = -1; y < 1; ++y) 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) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, update_object_for_changed_object_should_return_changed_tiles)
@ -85,7 +89,7 @@ namespace
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
const btBoxShape boxShape(btVector3(20, 20, 100)); const btBoxShape boxShape(btVector3(20, 20, 100));
const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); 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); manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground);
EXPECT_TRUE(manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, EXPECT_TRUE(manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground,
[&] (const auto& v) { onChangedTile(v); })); [&] (const auto& v) { onChangedTile(v); }));
@ -100,7 +104,7 @@ namespace
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
const btBoxShape boxShape(btVector3(20, 20, 100)); 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.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
EXPECT_FALSE(manager.updateObject(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); })); [&] (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) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_after_add_object_should_return_recast_mesh_for_each_used_tile)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
manager.setWorldspace("worldspace");
const btBoxShape boxShape(btVector3(20, 20, 100)); 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.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
EXPECT_NE(manager.getMesh(TilePosition(-1, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr);
EXPECT_NE(manager.getMesh(TilePosition(-1, 0)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr);
EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr);
EXPECT_NE(manager.getMesh(TilePosition(0, 0)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, 0)), nullptr);
} }
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_after_add_object_should_return_nullptr_for_unused_tile) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_after_add_object_should_return_nullptr_for_unused_tile)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
manager.setWorldspace("worldspace");
const btBoxShape boxShape(btVector3(20, 20, 100)); 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.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) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_moved_object_should_return_recast_mesh_for_each_used_tile)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
manager.setWorldspace("worldspace");
const btBoxShape boxShape(btVector3(20, 20, 100)); const btBoxShape boxShape(btVector3(20, 20, 100));
const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); 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); manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground);
EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr);
EXPECT_NE(manager.getMesh(TilePosition(0, 0)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, 0)), nullptr);
EXPECT_NE(manager.getMesh(TilePosition(1, 0)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(1, 0)), nullptr);
EXPECT_NE(manager.getMesh(TilePosition(1, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(1, -1)), nullptr);
manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {});
EXPECT_NE(manager.getMesh(TilePosition(-1, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr);
EXPECT_NE(manager.getMesh(TilePosition(-1, 0)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr);
EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr);
EXPECT_NE(manager.getMesh(TilePosition(0, 0)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, 0)), nullptr);
} }
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_moved_object_should_return_nullptr_for_unused_tile) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_moved_object_should_return_nullptr_for_unused_tile)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
manager.setWorldspace("worldspace");
const btBoxShape boxShape(btVector3(20, 20, 100)); const btBoxShape boxShape(btVector3(20, 20, 100));
const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); 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); manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground);
EXPECT_EQ(manager.getMesh(TilePosition(-1, -1)), nullptr); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr);
EXPECT_EQ(manager.getMesh(TilePosition(-1, 0)), nullptr); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr);
manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {});
EXPECT_EQ(manager.getMesh(TilePosition(1, 0)), nullptr); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(1, 0)), nullptr);
EXPECT_EQ(manager.getMesh(TilePosition(1, -1)), 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) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_removed_object_should_return_nullptr_for_all_previously_used_tiles)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
manager.setWorldspace("worldspace");
const btBoxShape boxShape(btVector3(20, 20, 100)); 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.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
manager.removeObject(ObjectId(&boxShape)); manager.removeObject(ObjectId(&boxShape));
EXPECT_EQ(manager.getMesh(TilePosition(-1, -1)), nullptr); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr);
EXPECT_EQ(manager.getMesh(TilePosition(-1, 0)), nullptr); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr);
EXPECT_EQ(manager.getMesh(TilePosition(0, -1)), nullptr); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr);
EXPECT_EQ(manager.getMesh(TilePosition(0, 0)), 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) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_not_changed_object_after_update_should_return_recast_mesh_for_same_tiles)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
manager.setWorldspace("worldspace");
const btBoxShape boxShape(btVector3(20, 20, 100)); 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.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
EXPECT_NE(manager.getMesh(TilePosition(-1, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr);
EXPECT_NE(manager.getMesh(TilePosition(-1, 0)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr);
EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr);
EXPECT_NE(manager.getMesh(TilePosition(0, 0)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, 0)), nullptr);
manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {});
EXPECT_NE(manager.getMesh(TilePosition(-1, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr);
EXPECT_NE(manager.getMesh(TilePosition(-1, 0)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr);
EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr);
EXPECT_NE(manager.getMesh(TilePosition(0, 0)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, 0)), nullptr);
} }
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_revision_after_add_object_new_should_return_incremented_value) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_revision_after_add_object_new_should_return_incremented_value)
@ -201,7 +213,7 @@ namespace
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
const auto initialRevision = manager.getRevision(); const auto initialRevision = manager.getRevision();
const btBoxShape boxShape(btVector3(20, 20, 100)); 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.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
EXPECT_EQ(manager.getRevision(), initialRevision + 1); EXPECT_EQ(manager.getRevision(), initialRevision + 1);
} }
@ -210,7 +222,7 @@ namespace
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
const btBoxShape boxShape(btVector3(20, 20, 100)); 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.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
const auto beforeAddRevision = manager.getRevision(); const auto beforeAddRevision = manager.getRevision();
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
@ -222,7 +234,7 @@ namespace
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
const btBoxShape boxShape(btVector3(20, 20, 100)); const btBoxShape boxShape(btVector3(20, 20, 100));
const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); 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); manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground);
const auto beforeUpdateRevision = manager.getRevision(); const auto beforeUpdateRevision = manager.getRevision();
manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); 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) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_revision_after_update_not_changed_object_should_return_same_value)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
manager.setWorldspace("worldspace");
const btBoxShape boxShape(btVector3(20, 20, 100)); 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.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
const auto beforeUpdateRevision = manager.getRevision(); const auto beforeUpdateRevision = manager.getRevision();
manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {});
@ -244,7 +257,7 @@ namespace
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
const btBoxShape boxShape(btVector3(20, 20, 100)); 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.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
const auto beforeRemoveRevision = manager.getRevision(); const auto beforeRemoveRevision = manager.getRevision();
manager.removeObject(ObjectId(&boxShape)); manager.removeObject(ObjectId(&boxShape));
@ -270,26 +283,28 @@ namespace
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_water_for_not_max_int_should_add_new_tiles) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_water_for_not_max_int_should_add_new_tiles)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
manager.setWorldspace("worldspace");
const osg::Vec2i cellPosition(0, 0); const osg::Vec2i cellPosition(0, 0);
const int cellSize = 8192; const int cellSize = 8192;
ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f)); ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f));
for (int x = -1; x < 12; ++x) for (int x = -1; x < 12; ++x)
for (int y = -1; y < 12; ++y) 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) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_water_for_max_int_should_not_add_new_tiles)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
manager.setWorldspace("worldspace");
const btBoxShape boxShape(btVector3(20, 20, 100)); 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.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground));
const osg::Vec2i cellPosition(0, 0); const osg::Vec2i cellPosition(0, 0);
const int cellSize = std::numeric_limits<int>::max(); const int cellSize = std::numeric_limits<int>::max();
ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f)); ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f));
for (int x = -6; x < 6; ++x) for (int x = -6; x < 6; ++x)
for (int y = -6; y < 6; ++y) 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) 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) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_existing_cell_should_remove_empty_tiles)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
manager.setWorldspace("worldspace");
const osg::Vec2i cellPosition(0, 0); const osg::Vec2i cellPosition(0, 0);
const int cellSize = 8192; const int cellSize = 8192;
ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f)); ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f));
ASSERT_TRUE(manager.removeWater(cellPosition)); ASSERT_TRUE(manager.removeWater(cellPosition));
for (int x = -6; x < 6; ++x) for (int x = -6; x < 6; ++x)
for (int y = -6; y < 6; ++y) 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) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_existing_cell_should_leave_not_empty_tiles)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
manager.setWorldspace("worldspace");
const btBoxShape boxShape(btVector3(20, 20, 100)); 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.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground));
const osg::Vec2i cellPosition(0, 0); const osg::Vec2i cellPosition(0, 0);
const int cellSize = 8192; const int cellSize = 8192;
@ -333,21 +350,35 @@ namespace
ASSERT_TRUE(manager.removeWater(cellPosition)); ASSERT_TRUE(manager.removeWater(cellPosition));
for (int x = -6; x < 6; ++x) for (int x = -6; x < 6; ++x)
for (int y = -6; y < 6; ++y) 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) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_object_should_not_remove_tile_with_water)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
manager.setWorldspace("worldspace");
const osg::Vec2i cellPosition(0, 0); const osg::Vec2i cellPosition(0, 0);
const int cellSize = 8192; const int cellSize = 8192;
const btBoxShape boxShape(btVector3(20, 20, 100)); 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.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground));
ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f)); ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f));
ASSERT_TRUE(manager.removeObject(ObjectId(&boxShape))); ASSERT_TRUE(manager.removeObject(ObjectId(&boxShape)));
for (int x = -1; x < 12; ++x) for (int x = -1; x < 12; ++x)
for (int y = -1; y < 12; ++y) 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);
} }
} }

@ -1,6 +1,6 @@
#include "format.hpp" #include "format.hpp"
#include <components/detournavigator/serialization/binaryreader.hpp> #include <components/serialization/binaryreader.hpp>
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <gmock/gmock.h> #include <gmock/gmock.h>
@ -12,8 +12,8 @@
namespace namespace
{ {
using namespace testing; using namespace testing;
using namespace DetourNavigator::Serialization; using namespace Serialization;
using namespace DetourNavigator::SerializationTesting; using namespace SerializationTesting;
TEST(DetourNavigatorSerializationBinaryReaderTest, shouldReadArithmeticTypeValue) TEST(DetourNavigatorSerializationBinaryReaderTest, shouldReadArithmeticTypeValue)
{ {

@ -1,6 +1,6 @@
#include "format.hpp" #include "format.hpp"
#include <components/detournavigator/serialization/binarywriter.hpp> #include <components/serialization/binarywriter.hpp>
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <gmock/gmock.h> #include <gmock/gmock.h>
@ -12,8 +12,8 @@
namespace namespace
{ {
using namespace testing; using namespace testing;
using namespace DetourNavigator::Serialization; using namespace Serialization;
using namespace DetourNavigator::SerializationTesting; using namespace SerializationTesting;
TEST(DetourNavigatorSerializationBinaryWriterTest, shouldWriteArithmeticTypeValue) TEST(DetourNavigatorSerializationBinaryWriterTest, shouldWriteArithmeticTypeValue)
{ {

@ -1,12 +1,12 @@
#ifndef OPENMW_TEST_SUITE_DETOURNAVIGATOR_SERIALIZATION_FORMAT_H #ifndef OPENMW_TEST_SUITE_SERIALIZATION_FORMAT_H
#define OPENMW_TEST_SUITE_DETOURNAVIGATOR_SERIALIZATION_FORMAT_H #define OPENMW_TEST_SUITE_SERIALIZATION_FORMAT_H
#include <components/detournavigator/serialization/format.hpp> #include <components/serialization/format.hpp>
#include <utility> #include <utility>
#include <type_traits> #include <type_traits>
namespace DetourNavigator::SerializationTesting namespace SerializationTesting
{ {
struct Pod struct Pod
{ {

@ -1,8 +1,8 @@
#include "format.hpp" #include "format.hpp"
#include <components/detournavigator/serialization/sizeaccumulator.hpp> #include <components/serialization/sizeaccumulator.hpp>
#include <components/detournavigator/serialization/binarywriter.hpp> #include <components/serialization/binarywriter.hpp>
#include <components/detournavigator/serialization/binaryreader.hpp> #include <components/serialization/binaryreader.hpp>
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <gmock/gmock.h> #include <gmock/gmock.h>
@ -12,8 +12,8 @@
namespace namespace
{ {
using namespace testing; using namespace testing;
using namespace DetourNavigator::Serialization; using namespace Serialization;
using namespace DetourNavigator::SerializationTesting; using namespace SerializationTesting;
struct DetourNavigatorSerializationIntegrationTest : Test struct DetourNavigatorSerializationIntegrationTest : Test
{ {

@ -1,6 +1,6 @@
#include "format.hpp" #include "format.hpp"
#include <components/detournavigator/serialization/sizeaccumulator.hpp> #include <components/serialization/sizeaccumulator.hpp>
#include <gtest/gtest.h> #include <gtest/gtest.h>
@ -12,8 +12,8 @@
namespace namespace
{ {
using namespace testing; using namespace testing;
using namespace DetourNavigator::Serialization; using namespace Serialization;
using namespace DetourNavigator::SerializationTesting; using namespace SerializationTesting;
TEST(DetourNavigatorSerializationSizeAccumulatorTest, shouldProvideSizeForArithmeticType) TEST(DetourNavigatorSerializationSizeAccumulatorTest, shouldProvideSizeForArithmeticType)
{ {

@ -203,6 +203,11 @@ add_component_dir(detournavigator
preparednavmeshdata preparednavmeshdata
navmeshcacheitem navmeshcacheitem
navigatorutils navigatorutils
generatenavmeshtile
navmeshdb
serialization
navmeshdbutils
recast
) )
add_component_dir(loadinglistener add_component_dir(loadinglistener

@ -3,6 +3,8 @@
#include <Recast.h> #include <Recast.h>
#include <ostream>
namespace DetourNavigator namespace DetourNavigator
{ {
enum AreaType : unsigned char enum AreaType : unsigned char
@ -21,6 +23,19 @@ namespace DetourNavigator
float mPathgrid = 1.0f; float mPathgrid = 1.0f;
float mGround = 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<std::underlying_type_t<AreaType>>(value) << ")";
}
} }
#endif #endif

@ -3,6 +3,9 @@
#include "makenavmesh.hpp" #include "makenavmesh.hpp"
#include "settings.hpp" #include "settings.hpp"
#include "version.hpp" #include "version.hpp"
#include "serialization.hpp"
#include "navmeshdbutils.hpp"
#include "dbrefgeometryobject.hpp"
#include <components/debug/debuglog.hpp> #include <components/debug/debuglog.hpp>
#include <components/misc/thread.hpp> #include <components/misc/thread.hpp>
@ -15,16 +18,12 @@
#include <algorithm> #include <algorithm>
#include <numeric> #include <numeric>
#include <set> #include <set>
#include <type_traits>
namespace DetourNavigator
{
namespace namespace
{ {
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) int getManhattanDistance(const TilePosition& lhs, const TilePosition& rhs)
{ {
return std::abs(lhs.x() - rhs.x()) + std::abs(lhs.y() - rhs.y()); return std::abs(lhs.x() - rhs.x()) + std::abs(lhs.y() - rhs.y());
@ -41,16 +40,10 @@ namespace
return result; return result;
} }
UpdateType getUpdateType(ChangeType changeType) noexcept
{
if (changeType == ChangeType::update)
return UpdateType::Temporary;
return UpdateType::Persistent;
}
auto getPriority(const Job& job) noexcept auto getPriority(const Job& job) noexcept
{ {
return std::make_tuple(job.mProcessTime, job.mChangeType, job.mTryNumber, job.mDistanceToPlayer, job.mDistanceToOrigin); return std::make_tuple(-static_cast<std::underlying_type_t<JobState>>(job.mState), job.mProcessTime,
job.mChangeType, job.mTryNumber, job.mDistanceToPlayer, job.mDistanceToOrigin);
} }
struct LessByJobPriority struct LessByJobPriority
@ -67,19 +60,62 @@ namespace
queue.insert(it, job); queue.insert(it, job);
} }
auto getDbPriority(const Job& job) noexcept
{
return std::make_tuple(static_cast<std::underlying_type_t<JobState>>(job.mState),
job.mChangeType, job.mDistanceToPlayer, job.mDistanceToOrigin);
}
struct LessByJobDbPriority
{
bool operator()(JobIt lhs, JobIt rhs) const noexcept
{
return getDbPriority(*lhs) < getDbPriority(*rhs);
}
};
void insertPrioritizedDbJob(JobIt job, std::deque<JobIt>& queue)
{
const auto it = std::upper_bound(queue.begin(), queue.end(), job, LessByJobDbPriority {});
queue.insert(it, job);
}
auto getAgentAndTile(const Job& job) noexcept auto getAgentAndTile(const Job& job) noexcept
{ {
return std::make_tuple(job.mAgentHalfExtents, job.mChangedTile); return std::make_tuple(job.mAgentHalfExtents, job.mChangedTile);
} }
std::unique_ptr<DbWorker> makeDbWorker(AsyncNavMeshUpdater& updater, std::unique_ptr<NavMeshDb>&& db, const Settings& settings)
{
if (db == nullptr)
return nullptr;
return std::make_unique<DbWorker>(updater, std::move(db), TileVersion(settings.mNavMeshVersion), settings.mRecast);
} }
namespace DetourNavigator void updateJobs(std::deque<JobIt>& 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);
}
}
Job::Job(const osg::Vec3f& agentHalfExtents, std::weak_ptr<GuardedNavMeshCacheItem> navMeshCacheItem, Job::Job(const osg::Vec3f& agentHalfExtents, std::weak_ptr<GuardedNavMeshCacheItem> 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) std::chrono::steady_clock::time_point processTime)
: mAgentHalfExtents(agentHalfExtents) : mId(getNextJobId())
, mAgentHalfExtents(agentHalfExtents)
, mNavMeshCacheItem(std::move(navMeshCacheItem)) , mNavMeshCacheItem(std::move(navMeshCacheItem))
, mWorldspace(worldspace)
, mChangedTile(changedTile) , mChangedTile(changedTile)
, mProcessTime(processTime) , mProcessTime(processTime)
, mChangeType(changeType) , mChangeType(changeType)
@ -89,12 +125,13 @@ namespace DetourNavigator
} }
AsyncNavMeshUpdater::AsyncNavMeshUpdater(const Settings& settings, TileCachedRecastMeshManager& recastMeshManager, AsyncNavMeshUpdater::AsyncNavMeshUpdater(const Settings& settings, TileCachedRecastMeshManager& recastMeshManager,
OffMeshConnectionsManager& offMeshConnectionsManager) OffMeshConnectionsManager& offMeshConnectionsManager, std::unique_ptr<NavMeshDb>&& db)
: mSettings(settings) : mSettings(settings)
, mRecastMeshManager(recastMeshManager) , mRecastMeshManager(recastMeshManager)
, mOffMeshConnectionsManager(offMeshConnectionsManager) , mOffMeshConnectionsManager(offMeshConnectionsManager)
, mShouldStop() , mShouldStop()
, mNavMeshTilesCache(settings.mMaxNavMeshTilesCacheSize) , mNavMeshTilesCache(settings.mMaxNavMeshTilesCacheSize)
, mDbWorker(makeDbWorker(*this, std::move(db), mSettings))
{ {
for (std::size_t i = 0; i < mSettings.get().mAsyncNavMeshUpdaterThreads; ++i) for (std::size_t i = 0; i < mSettings.get().mAsyncNavMeshUpdaterThreads; ++i)
mThreads.emplace_back([&] { process(); }); mThreads.emplace_back([&] { process(); });
@ -103,6 +140,8 @@ namespace DetourNavigator
AsyncNavMeshUpdater::~AsyncNavMeshUpdater() AsyncNavMeshUpdater::~AsyncNavMeshUpdater()
{ {
mShouldStop = true; mShouldStop = true;
if (mDbWorker != nullptr)
mDbWorker->stop();
std::unique_lock<std::mutex> lock(mMutex); std::unique_lock<std::mutex> lock(mMutex);
mWaiting.clear(); mWaiting.clear();
mHasJob.notify_all(); mHasJob.notify_all();
@ -111,8 +150,8 @@ namespace DetourNavigator
thread.join(); thread.join();
} }
void AsyncNavMeshUpdater::post(const osg::Vec3f& agentHalfExtents, void AsyncNavMeshUpdater::post(const osg::Vec3f& agentHalfExtents, const SharedNavMeshCacheItem& navMeshCacheItem,
const SharedNavMeshCacheItem& navMeshCacheItem, const TilePosition& playerTile, const TilePosition& playerTile, std::string_view worldspace,
const std::map<TilePosition, ChangeType>& changedTiles) const std::map<TilePosition, ChangeType>& changedTiles)
{ {
bool playerTileChanged = false; bool playerTileChanged = false;
@ -126,18 +165,12 @@ namespace DetourNavigator
return; return;
const dtNavMeshParams params = *navMeshCacheItem->lockConst()->getImpl().getParams(); const dtNavMeshParams params = *navMeshCacheItem->lockConst()->getImpl().getParams();
const int maxTiles = std::min(mSettings.get().mMaxTilesNumber, params.maxTiles);
const std::lock_guard<std::mutex> lock(mMutex); std::unique_lock lock(mMutex);
if (playerTileChanged) if (playerTileChanged)
{ updateJobs(mWaiting, playerTile, maxTiles);
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;
}
}
for (const auto& [changedTile, changeType] : changedTiles) for (const auto& [changedTile, changeType] : changedTiles)
{ {
@ -147,8 +180,11 @@ namespace DetourNavigator
? mLastUpdates[std::tie(agentHalfExtents, changedTile)] + mSettings.get().mMinUpdateInterval ? mLastUpdates[std::tie(agentHalfExtents, changedTile)] + mSettings.get().mMinUpdateInterval
: std::chrono::steady_clock::time_point(); : std::chrono::steady_clock::time_point();
const JobIt it = mJobs.emplace(mJobs.end(), agentHalfExtents, navMeshCacheItem, changedTile, const JobIt it = mJobs.emplace(mJobs.end(), agentHalfExtents, navMeshCacheItem, worldspace,
changeType, getManhattanDistance(changedTile, playerTile), processTime); changedTile, changeType, getManhattanDistance(changedTile, playerTile), processTime);
Log(Debug::Debug) << "Post job " << it->mId << " for agent=(" << it->mAgentHalfExtents << ")"
<< " changedTile=(" << it->mChangedTile << ")";
if (playerTileChanged) if (playerTileChanged)
mWaiting.push_back(it); mWaiting.push_back(it);
@ -164,6 +200,11 @@ namespace DetourNavigator
if (!mWaiting.empty()) if (!mWaiting.empty())
mHasJob.notify_all(); mHasJob.notify_all();
lock.unlock();
if (playerTileChanged && mDbWorker != nullptr)
mDbWorker->updateJobs(playerTile, maxTiles);
} }
void AsyncNavMeshUpdater::wait(Loading::Listener& listener, WaitConditionType waitConditionType) void AsyncNavMeshUpdater::wait(Loading::Listener& listener, WaitConditionType waitConditionType)
@ -241,25 +282,40 @@ namespace DetourNavigator
mProcessingTiles.wait(mProcessed, [] (const auto& v) { return v.empty(); }); 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; Stats result;
std::size_t waiting = 0;
std::size_t pushed = 0;
{ {
const std::lock_guard<std::mutex> lock(mMutex); const std::lock_guard<std::mutex> lock(mMutex);
jobs = mJobs.size(); result.mJobs = mJobs.size();
waiting = mWaiting.size(); result.mWaiting = mWaiting.size();
pushed = mPushed.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); void reportStats(const AsyncNavMeshUpdater::Stats& stats, unsigned int frameNumber, osg::Stats& out)
stats.setAttribute(frameNumber, "NavMesh Waiting", waiting); {
stats.setAttribute(frameNumber, "NavMesh Pushed", pushed); out.setAttribute(frameNumber, "NavMesh Jobs", static_cast<double>(stats.mJobs));
stats.setAttribute(frameNumber, "NavMesh Processing", mProcessingTiles.lockConst()->size()); out.setAttribute(frameNumber, "NavMesh Waiting", static_cast<double>(stats.mWaiting));
out.setAttribute(frameNumber, "NavMesh Pushed", static_cast<double>(stats.mPushed));
out.setAttribute(frameNumber, "NavMesh Processing", static_cast<double>(stats.mProcessing));
if (stats.mDb.has_value())
{
out.setAttribute(frameNumber, "NavMesh DbJobs", static_cast<double>(stats.mDb->mJobs));
mNavMeshTilesCache.reportStats(frameNumber, stats); if (stats.mDb->mGetTileCount > 0)
out.setAttribute(frameNumber, "NavMesh DbCacheHitRate", static_cast<double>(stats.mDbGetTileHits)
/ static_cast<double>(stats.mDb->mGetTileCount) * 100.0);
}
reportStats(stats.mCache, frameNumber, out);
} }
void AsyncNavMeshUpdater::process() noexcept void AsyncNavMeshUpdater::process() noexcept
@ -272,12 +328,26 @@ namespace DetourNavigator
{ {
if (JobIt job = getNextJob(); job != mJobs.end()) if (JobIt job = getNextJob(); job != mJobs.end())
{ {
const auto processed = processJob(*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); unlockTile(job->mAgentHalfExtents, job->mChangedTile);
if (processed) if (job->mGeneratedNavMeshData != nullptr)
removeJob(job); mDbWorker->enqueueJob(job);
else else
removeJob(job);
break;
case JobStatus::Fail:
repost(job); repost(job);
break;
case JobStatus::MemoryCacheMiss:
{
mDbWorker->enqueueJob(job);
break;
}
}
} }
else else
cleanupLastUpdates(); cleanupLastUpdates();
@ -290,32 +360,155 @@ namespace DetourNavigator
Log(Debug::Debug) << "Stop navigator jobs processing by thread=" << std::this_thread::get_id(); 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 << ")" Log(Debug::Debug) << "Processing job " << job.mId << " by thread=" << std::this_thread::get_id();
" by thread=" << std::this_thread::get_id();
const auto start = std::chrono::steady_clock::now();
const auto navMeshCacheItem = job.mNavMeshCacheItem.lock(); const auto navMeshCacheItem = job.mNavMeshCacheItem.lock();
if (!navMeshCacheItem) if (!navMeshCacheItem)
return true; return JobStatus::Done;
const auto recastMesh = mRecastMeshManager.get().getMesh(job.mChangedTile);
const auto playerTile = *mPlayerTile.lockConst(); 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> 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> 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 offMeshConnections = mOffMeshConnectionsManager.get().get(job.mChangedTile);
const auto status = updateNavMesh(job.mAgentHalfExtents, recastMesh.get(), job.mChangedTile, playerTile, const UpdateNavMeshStatus status = navMeshCacheItem.lock()->updateTile(job.mChangedTile, std::move(cachedNavMeshData),
offMeshConnections, mSettings, navMeshCacheItem, mNavMeshTilesCache, getUpdateType(job.mChangeType)); 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> preparedNavMeshData;
bool generatedNavMeshData = false;
if (recastMesh != nullptr) if (job.mCachedTileData.has_value() && job.mCachedTileData->mVersion == mSettings.get().mNavMeshVersion)
{ {
const Version navMeshVersion = navMeshCacheItem->lockConst()->getVersion(); preparedNavMeshData = std::make_unique<PreparedNavMeshData>();
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 (preparedNavMeshData == nullptr)
{
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<PreparedNavMeshData>(*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, mRecastMeshManager.get().reportNavMeshChange(job.mChangedTile,
Version {recastMesh->getGeneration(), recastMesh->getRevision()}, Version {recastMesh.getGeneration(), recastMesh.getRevision()},
navMeshVersion); navMeshVersion);
}
if (status == UpdateNavMeshStatus::removed || status == UpdateNavMeshStatus::lost) if (status == UpdateNavMeshStatus::removed || status == UpdateNavMeshStatus::lost)
{ {
@ -328,23 +521,9 @@ namespace DetourNavigator
mPresentTiles.insert(std::make_tuple(job.mAgentHalfExtents, job.mChangedTile)); mPresentTiles.insert(std::make_tuple(job.mAgentHalfExtents, job.mChangedTile));
} }
const auto finish = std::chrono::steady_clock::now(); writeDebugFiles(job, &recastMesh);
writeDebugFiles(job, recastMesh.get());
using FloatMs = std::chrono::duration<float, std::milli>;
const Version version = navMeshCacheItem->lockConst()->getVersion(); return isSuccess(status) ? JobStatus::Done : JobStatus::Fail;
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<FloatMs>(finish - start).count() << "ms" <<
" thread=" << std::this_thread::get_id();
return isSuccess(status);
} }
JobIt AsyncNavMeshUpdater::getNextJob() JobIt AsyncNavMeshUpdater::getNextJob()
@ -373,8 +552,12 @@ namespace DetourNavigator
mWaiting.pop_front(); mWaiting.pop_front();
if (job->mRecastMesh != nullptr)
return job;
if (!lockTile(job->mAgentHalfExtents, job->mChangedTile)) if (!lockTile(job->mAgentHalfExtents, job->mChangedTile))
{ {
Log(Debug::Debug) << "Failed to lock tile by " << job->mId;
++job->mTryNumber; ++job->mTryNumber;
insertPrioritizedJob(job, mWaiting); insertPrioritizedJob(job, mWaiting);
return mJobs.end(); return mJobs.end();
@ -404,7 +587,7 @@ namespace DetourNavigator
} }
if (recastMesh && mSettings.get().mEnableWriteRecastMeshToFile) if (recastMesh && mSettings.get().mEnableWriteRecastMeshToFile)
writeToFile(*recastMesh, mSettings.get().mRecastMeshPathPrefix + std::to_string(job.mChangedTile.x()) 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 (mSettings.get().mEnableWriteNavMeshToFile)
if (const auto shared = job.mNavMeshCacheItem.lock()) if (const auto shared = job.mNavMeshCacheItem.lock())
writeToFile(shared->lockConst()->getImpl(), mSettings.get().mNavMeshPathPrefix, navMeshRevision); writeToFile(shared->lockConst()->getImpl(), mSettings.get().mNavMeshPathPrefix, navMeshRevision);
@ -412,6 +595,8 @@ namespace DetourNavigator
void AsyncNavMeshUpdater::repost(JobIt job) void AsyncNavMeshUpdater::repost(JobIt job)
{ {
unlockTile(job->mAgentHalfExtents, job->mChangedTile);
if (mShouldStop || job->mTryNumber > 2) if (mShouldStop || job->mTryNumber > 2)
return; return;
@ -430,17 +615,15 @@ namespace DetourNavigator
bool AsyncNavMeshUpdater::lockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile) bool AsyncNavMeshUpdater::lockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile)
{ {
if (mSettings.get().mAsyncNavMeshUpdaterThreads <= 1) Log(Debug::Debug) << "Locking tile agent=(" << agentHalfExtents << ") changedTile=(" << changedTile << ")";
return true;
return mProcessingTiles.lock()->emplace(agentHalfExtents, changedTile).second; return mProcessingTiles.lock()->emplace(agentHalfExtents, changedTile).second;
} }
void AsyncNavMeshUpdater::unlockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile) void AsyncNavMeshUpdater::unlockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile)
{ {
if (mSettings.get().mAsyncNavMeshUpdaterThreads <= 1)
return;
auto locked = mProcessingTiles.lock(); auto locked = mProcessingTiles.lock();
locked->erase(std::tie(agentHalfExtents, changedTile)); locked->erase(std::tie(agentHalfExtents, changedTile));
Log(Debug::Debug) << "Unlocked tile agent=(" << agentHalfExtents << ") changedTile=(" << changedTile << ")";
if (locked->empty()) if (locked->empty())
mProcessed.notify_all(); 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) 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); const std::lock_guard lock(mMutex);
mJobs.erase(job); mJobs.erase(job);
} }
void DbJobQueue::push(JobIt job)
{
const std::lock_guard lock(mMutex);
insertPrioritizedDbJob(job, mJobs);
mHasJob.notify_all();
}
std::optional<JobIt> 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<NavMeshDb>&& 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<DbRefGeometryObject> 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<DbRefGeometryObject> 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;
}
} }

@ -7,6 +7,7 @@
#include "tileposition.hpp" #include "tileposition.hpp"
#include "navmeshtilescache.hpp" #include "navmeshtilescache.hpp"
#include "waitconditiontype.hpp" #include "waitconditiontype.hpp"
#include "navmeshdb.hpp"
#include <osg/Vec3f> #include <osg/Vec3f>
@ -20,6 +21,7 @@
#include <thread> #include <thread>
#include <tuple> #include <tuple>
#include <list> #include <list>
#include <optional>
class dtNavMesh; class dtNavMesh;
@ -53,37 +55,150 @@ namespace DetourNavigator
return stream << "ChangeType::" << static_cast<int>(value); return stream << "ChangeType::" << static_cast<int>(value);
} }
enum class JobState
{
Initial,
WithDbResult,
};
struct Job struct Job
{ {
const std::size_t mId;
const osg::Vec3f mAgentHalfExtents; const osg::Vec3f mAgentHalfExtents;
const std::weak_ptr<GuardedNavMeshCacheItem> mNavMeshCacheItem; const std::weak_ptr<GuardedNavMeshCacheItem> mNavMeshCacheItem;
const std::string mWorldspace;
const TilePosition mChangedTile; const TilePosition mChangedTile;
const std::chrono::steady_clock::time_point mProcessTime; const std::chrono::steady_clock::time_point mProcessTime;
unsigned mTryNumber = 0; unsigned mTryNumber = 0;
ChangeType mChangeType; ChangeType mChangeType;
int mDistanceToPlayer; int mDistanceToPlayer;
const int mDistanceToOrigin; const int mDistanceToOrigin;
JobState mState = JobState::Initial;
std::vector<std::byte> mInput;
std::shared_ptr<RecastMesh> mRecastMesh;
std::optional<TileData> mCachedTileData;
std::unique_ptr<PreparedNavMeshData> mGeneratedNavMeshData;
Job(const osg::Vec3f& agentHalfExtents, std::weak_ptr<GuardedNavMeshCacheItem> navMeshCacheItem, Job(const osg::Vec3f& agentHalfExtents, std::weak_ptr<GuardedNavMeshCacheItem> 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); std::chrono::steady_clock::time_point processTime);
}; };
using JobIt = std::list<Job>::iterator; using JobIt = std::list<Job>::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<std::underlying_type_t<JobState>>(value);
}
class DbJobQueue
{
public:
void push(JobIt job);
std::optional<JobIt> 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<JobIt> 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<NavMeshDb>&& 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<NavMeshDb> 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 class AsyncNavMeshUpdater
{ {
public: 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<DbWorker::Stats> mDb;
NavMeshTilesCache::Stats mCache;
};
AsyncNavMeshUpdater(const Settings& settings, TileCachedRecastMeshManager& recastMeshManager, AsyncNavMeshUpdater(const Settings& settings, TileCachedRecastMeshManager& recastMeshManager,
OffMeshConnectionsManager& offMeshConnectionsManager); OffMeshConnectionsManager& offMeshConnectionsManager, std::unique_ptr<NavMeshDb>&& db);
~AsyncNavMeshUpdater(); ~AsyncNavMeshUpdater();
void post(const osg::Vec3f& agentHalfExtents, const SharedNavMeshCacheItem& mNavMeshCacheItem, void post(const osg::Vec3f& agentHalfExtents, const SharedNavMeshCacheItem& navMeshCacheItem,
const TilePosition& playerTile, const std::map<TilePosition, ChangeType>& changedTiles); const TilePosition& playerTile, std::string_view worldspace,
const std::map<TilePosition, ChangeType>& changedTiles);
void wait(Loading::Listener& listener, WaitConditionType waitConditionType); 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: private:
std::reference_wrapper<const Settings> mSettings; std::reference_wrapper<const Settings> mSettings;
@ -103,14 +218,21 @@ namespace DetourNavigator
std::map<std::tuple<osg::Vec3f, TilePosition>, std::chrono::steady_clock::time_point> mLastUpdates; std::map<std::tuple<osg::Vec3f, TilePosition>, std::chrono::steady_clock::time_point> mLastUpdates;
std::set<std::tuple<osg::Vec3f, TilePosition>> mPresentTiles; std::set<std::tuple<osg::Vec3f, TilePosition>> mPresentTiles;
std::vector<std::thread> mThreads; std::vector<std::thread> mThreads;
std::unique_ptr<DbWorker> mDbWorker;
std::atomic_size_t mDbGetTileHits {0};
void process() noexcept; 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<JobIt>& jobs, bool changeLastUpdate); JobIt getNextJob();
void postThreadJob(JobIt job, std::deque<JobIt>& queue); void postThreadJob(JobIt job, std::deque<JobIt>& queue);
@ -129,9 +251,9 @@ namespace DetourNavigator
int waitUntilJobsDoneForNotPresentTiles(const std::size_t initialJobsLeft, std::size_t& maxJobsLeft, Loading::Listener& listener); int waitUntilJobsDoneForNotPresentTiles(const std::size_t initialJobsLeft, std::size_t& maxJobsLeft, Loading::Listener& listener);
void waitUntilAllJobsDone(); void waitUntilAllJobsDone();
inline void removeJob(JobIt job);
}; };
void reportStats(const AsyncNavMeshUpdater::Stats& stats, unsigned int frameNumber, osg::Stats& out);
} }
#endif #endif

@ -84,6 +84,11 @@ namespace DetourNavigator
return *mCached.lockConst(); return *mCached.lockConst();
} }
std::shared_ptr<RecastMesh> CachedRecastMeshManager::getNewMesh() const
{
return mImpl.getMesh();
}
bool CachedRecastMeshManager::isEmpty() const bool CachedRecastMeshManager::isEmpty() const
{ {
return mImpl.isEmpty(); return mImpl.isEmpty();

@ -35,6 +35,8 @@ namespace DetourNavigator
std::shared_ptr<RecastMesh> getCachedMesh() const; std::shared_ptr<RecastMesh> getCachedMesh() const;
std::shared_ptr<RecastMesh> getNewMesh() const;
bool isEmpty() const; bool isEmpty() const;
void reportNavMeshChange(const Version& recastMeshVersion, const Version& navMeshVersion); void reportNavMeshChange(const Version& recastMeshVersion, const Version& navMeshVersion);

@ -0,0 +1,46 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_DBREFGEOMETRYOBJECT_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_DBREFGEOMETRYOBJECT_H
#include "objecttransform.hpp"
#include "recastmesh.hpp"
#include <algorithm>
#include <cstdint>
#include <tuple>
#include <vector>
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 <class ResolveMeshSource>
inline std::vector<DbRefGeometryObject> makeDbRefGeometryObjects(const std::vector<MeshSource>& meshSources,
ResolveMeshSource&& resolveMeshSource)
{
std::vector<DbRefGeometryObject> 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

@ -11,7 +11,8 @@
namespace DetourNavigator 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"; const auto path = pathPrefix + "recastmesh" + revision + ".obj";
boost::filesystem::ofstream file(boost::filesystem::path(path), std::ios::out); boost::filesystem::ofstream file(boost::filesystem::path(path), std::ios::out);

@ -3,6 +3,7 @@
#include "tilebounds.hpp" #include "tilebounds.hpp"
#include "status.hpp" #include "status.hpp"
#include "recastmesh.hpp"
#include <osg/io_utils> #include <osg/io_utils>
@ -39,10 +40,40 @@ namespace DetourNavigator
return stream << "DetourNavigator::Error::" << static_cast<int>(value); return stream << "DetourNavigator::Error::" << static_cast<int>(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<int>(v.mLength)
<< ", .mMinHeight=" << v.mMinHeight
<< ", .mMaxHeight=" << v.mMaxHeight
<< ", .mHeights={";
for (float h : v.mHeights)
s << h << ", ";
s << "}";
return s << ", .mOriginalSize=" << v.mOriginalSize << "}";
}
class RecastMesh; 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); void writeToFile(const dtNavMesh& navMesh, const std::string& pathPrefix, const std::string& revision);
} }

@ -10,7 +10,7 @@
namespace DetourNavigator namespace DetourNavigator
{ {
std::optional<osg::Vec3f> findRandomPointAroundCircle(const dtNavMesh& navMesh, const osg::Vec3f& halfExtents, std::optional<osg::Vec3f> 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; dtNavMeshQuery navMeshQuery;
if (!initNavMeshQuery(navMeshQuery, navMesh, settings.mMaxNavMeshQueryNodes)) if (!initNavMeshQuery(navMeshQuery, navMesh, settings.mMaxNavMeshQueryNodes))

@ -10,10 +10,10 @@ class dtNavMesh;
namespace DetourNavigator namespace DetourNavigator
{ {
struct Settings; struct DetourSettings;
std::optional<osg::Vec3f> findRandomPointAroundCircle(const dtNavMesh& navMesh, const osg::Vec3f& halfExtents, std::optional<osg::Vec3f> 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 #endif

@ -60,7 +60,7 @@ namespace DetourNavigator
class OutputTransformIterator class OutputTransformIterator
{ {
public: public:
OutputTransformIterator(OutputIterator& impl, const Settings& settings) explicit OutputTransformIterator(OutputIterator& impl, const RecastSettings& settings)
: mImpl(impl), mSettings(settings) : mImpl(impl), mSettings(settings)
{ {
} }
@ -91,7 +91,7 @@ namespace DetourNavigator
private: private:
std::reference_wrapper<OutputIterator> mImpl; std::reference_wrapper<OutputIterator> mImpl;
std::reference_wrapper<const Settings> mSettings; std::reference_wrapper<const RecastSettings> mSettings;
}; };
inline bool initNavMeshQuery(dtNavMeshQuery& value, const dtNavMesh& navMesh, const int maxNodes) inline bool initNavMeshQuery(dtNavMeshQuery& value, const dtNavMesh& navMesh, const int maxNodes)
@ -261,7 +261,7 @@ namespace DetourNavigator
const Settings& settings, float endTolerance, OutputIterator& out) const Settings& settings, float endTolerance, OutputIterator& out)
{ {
dtNavMeshQuery navMeshQuery; dtNavMeshQuery navMeshQuery;
if (!initNavMeshQuery(navMeshQuery, navMesh, settings.mMaxNavMeshQueryNodes)) if (!initNavMeshQuery(navMeshQuery, navMesh, settings.mDetour.mMaxNavMeshQueryNodes))
return Status::InitNavMeshQueryFailed; return Status::InitNavMeshQueryFailed;
dtQueryFilter queryFilter; dtQueryFilter queryFilter;
@ -283,7 +283,7 @@ namespace DetourNavigator
if (endRef == 0) if (endRef == 0)
return Status::EndPolygonNotFound; return Status::EndPolygonNotFound;
std::vector<dtPolyRef> polygonPath(settings.mMaxPolygonPathSize); std::vector<dtPolyRef> polygonPath(settings.mDetour.mMaxPolygonPathSize);
const auto polygonPathSize = findPath(navMeshQuery, startRef, endRef, start, end, queryFilter, const auto polygonPathSize = findPath(navMeshQuery, startRef, endRef, start, end, queryFilter,
polygonPath.data(), polygonPath.size()); polygonPath.data(), polygonPath.size());
@ -294,9 +294,9 @@ namespace DetourNavigator
return Status::Success; return Status::Success;
const bool partialPath = polygonPath[*polygonPathSize - 1] != endRef; const bool partialPath = polygonPath[*polygonPathSize - 1] != endRef;
auto outTransform = OutputTransformIterator<OutputIterator>(out, settings); auto outTransform = OutputTransformIterator<OutputIterator>(out, settings.mRecast);
const Status smoothStatus = makeSmoothPath(navMesh, navMeshQuery, queryFilter, start, end, stepSize, 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) if (smoothStatus != Status::Success)
return smoothStatus; return smoothStatus;

@ -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 <components/debug/debuglog.hpp>
#include <osg/Vec3f>
#include <osg/io_utils>
#include <memory>
#include <stdexcept>
#include <vector>
#include <optional>
#include <functional>
namespace DetourNavigator
{
namespace
{
struct Ignore
{
std::shared_ptr<NavMeshTileConsumer> 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<NavMeshTileConsumer> 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> recastMesh = mRecastMeshProvider.getMesh(mWorldspace, mTilePosition);
if (recastMesh == nullptr || isEmpty(*recastMesh))
return;
const std::vector<DbRefGeometryObject> objects = makeDbRefGeometryObjects(recastMesh->getMeshSources(),
[&] (const MeshSource& v) { return consumer->resolveMeshSource(v); });
std::vector<std::byte> input = serialize(mSettings.mRecast, *recastMesh, objects);
const std::optional<NavMeshTileInfo> 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();
}
}
}

@ -0,0 +1,71 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_GENERATENAVMESHTILE_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_GENERATENAVMESHTILE_H
#include "recastmeshprovider.hpp"
#include "tileposition.hpp"
#include <components/sceneutil/workqueue.hpp>
#include <osg/Vec3f>
#include <cstdint>
#include <functional>
#include <memory>
#include <optional>
#include <string_view>
#include <vector>
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<NavMeshTileInfo> find(const std::string& worldspace, const TilePosition& tilePosition,
const std::vector<std::byte>& input) = 0;
virtual void ignore() = 0;
virtual void insert(const std::string& worldspace, const TilePosition& tilePosition,
std::int64_t version, const std::vector<std::byte>& 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<NavMeshTileConsumer> 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<NavMeshTileConsumer> mConsumer;
inline void impl() noexcept;
};
}
#endif

@ -15,7 +15,7 @@ namespace DetourNavigator
{ {
template <class Callback> template <class Callback>
void getTilesPositions(const osg::Vec3f& aabbMin, const osg::Vec3f& aabbMax, 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 min = toNavMeshCoordinates(settings, aabbMin);
auto max = toNavMeshCoordinates(settings, aabbMax); auto max = toNavMeshCoordinates(settings, aabbMax);
@ -40,7 +40,7 @@ namespace DetourNavigator
template <class Callback> template <class Callback>
void getTilesPositions(const btCollisionShape& shape, const btTransform& transform, void getTilesPositions(const btCollisionShape& shape, const btTransform& transform,
const Settings& settings, Callback&& callback) const RecastSettings& settings, Callback&& callback)
{ {
btVector3 aabbMin; btVector3 aabbMin;
btVector3 aabbMax; btVector3 aabbMax;
@ -51,7 +51,7 @@ namespace DetourNavigator
template <class Callback> template <class Callback>
void getTilesPositions(const int cellSize, const btVector3& shift, void getTilesPositions(const int cellSize, const btVector3& shift,
const Settings& settings, Callback&& callback) const RecastSettings& settings, Callback&& callback)
{ {
using Misc::Convert::toOsg; using Misc::Convert::toOsg;

@ -10,9 +10,15 @@
#include "preparednavmeshdata.hpp" #include "preparednavmeshdata.hpp"
#include "navmeshdata.hpp" #include "navmeshdata.hpp"
#include "recastmeshbuilder.hpp" #include "recastmeshbuilder.hpp"
#include "navmeshdb.hpp"
#include "serialization.hpp"
#include "dbrefgeometryobject.hpp"
#include "navmeshdbutils.hpp"
#include <components/misc/convert.hpp> #include <components/misc/convert.hpp>
#include <components/bullethelpers/processtrianglecallback.hpp> #include <components/bullethelpers/processtrianglecallback.hpp>
#include <components/misc/convert.hpp>
#include <components/misc/guarded.hpp>
#include <DetourNavMesh.h> #include <DetourNavMesh.h>
#include <DetourNavMeshBuilder.h> #include <DetourNavMeshBuilder.h>
@ -36,32 +42,6 @@ namespace
float mHeight; float mHeight;
}; };
Rectangle getSwimRectangle(const CellWater& water, const Settings& settings, const osg::Vec3f& agentHalfExtents)
{
if (water.mWater.mCellSize == std::numeric_limits<int>::max())
{
return Rectangle {
TileBounds {
osg::Vec2f(-std::numeric_limits<float>::max(), -std::numeric_limits<float>::max()),
osg::Vec2f(std::numeric_limits<float>::max(), std::numeric_limits<float>::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<float> getOffMeshVerts(const std::vector<OffMeshConnection>& connections) std::vector<float> getOffMeshVerts(const std::vector<OffMeshConnection>& connections)
{ {
std::vector<float> result; std::vector<float> result;
@ -120,52 +100,70 @@ namespace
return result; return result;
} }
rcConfig makeConfig(const osg::Vec3f& agentHalfExtents, const TilePosition& tile, float minZ, float maxZ, float getHeight(const RecastSettings& settings,const osg::Vec3f& agentHalfExtents)
const Settings& settings) {
{ return 2.0f * agentHalfExtents.z() * settings.mRecastScaleFactor;
rcConfig config; }
config.cs = settings.mCellSize; float getMaxClimb(const RecastSettings& settings)
config.ch = settings.mCellHeight; {
config.walkableSlopeAngle = settings.mMaxSlope; return settings.mMaxClimb * settings.mRecastScaleFactor;
config.walkableHeight = static_cast<int>(std::ceil(getHeight(settings, agentHalfExtents) / config.ch)); }
config.walkableClimb = static_cast<int>(std::floor(getMaxClimb(settings) / config.ch));
config.walkableRadius = static_cast<int>(std::ceil(getRadius(settings, agentHalfExtents) / config.cs)); float getRadius(const RecastSettings& settings, const osg::Vec3f& agentHalfExtents)
config.maxEdgeLen = static_cast<int>(std::round(settings.mMaxEdgeLen / config.cs)); {
config.maxSimplificationError = settings.mMaxSimplificationError; return std::max(agentHalfExtents.x(), agentHalfExtents.y()) * std::sqrt(2) * settings.mRecastScaleFactor;
config.minRegionArea = settings.mRegionMinSize * settings.mRegionMinSize; }
config.mergeRegionArea = settings.mRegionMergeSize * settings.mRegionMergeSize;
config.maxVertsPerPoly = settings.mMaxVertsPerPoly; float getSwimLevel(const RecastSettings& settings, const float waterLevel, const float agentHalfExtentsZ)
config.detailSampleDist = settings.mDetailSampleDist < 0.9f ? 0 : config.cs * settings.mDetailSampleDist; {
config.detailSampleMaxError = config.ch * settings.mDetailSampleMaxError; return waterLevel - settings.mSwimHeightScale * agentHalfExtentsZ - agentHalfExtentsZ;;
config.borderSize = settings.mBorderSize; }
config.tileSize = settings.mTileSize;
const int size = config.tileSize + config.borderSize * 2; struct RecastParams
config.width = size; {
config.height = size; float mSampleDist = 0;
const float halfBoundsSize = size * config.cs * 0.5f; float mSampleMaxError = 0;
const osg::Vec2f shift = osg::Vec2f(tile.x() + 0.5f, tile.y() + 0.5f) * getTileSize(settings); int mMaxEdgeLen = 0;
config.bmin[0] = shift.x() - halfBoundsSize; int mWalkableClimb = 0;
config.bmin[1] = minZ; int mWalkableHeight = 0;
config.bmin[2] = shift.y() - halfBoundsSize; int mWalkableRadius = 0;
config.bmax[0] = shift.x() + halfBoundsSize; };
config.bmax[1] = maxZ;
config.bmax[2] = shift.y() + halfBoundsSize; RecastParams makeRecastParams(const RecastSettings& settings, const osg::Vec3f& agentHalfExtents)
{
return config; RecastParams result;
}
result.mWalkableHeight = static_cast<int>(std::ceil(getHeight(settings, agentHalfExtents) / settings.mCellHeight));
void createHeightfield(rcContext& context, rcHeightfield& solid, int width, int height, const float* bmin, result.mWalkableClimb = static_cast<int>(std::floor(getMaxClimb(settings) / settings.mCellHeight));
const float* bmax, const float cs, const float ch) result.mWalkableRadius = static_cast<int>(std::ceil(getRadius(settings, agentHalfExtents) / settings.mCellSize));
{ result.mMaxEdgeLen = static_cast<int>(std::round(static_cast<float>(settings.mMaxEdgeLen) / settings.mCellSize));
const auto result = rcCreateHeightfield(&context, solid, width, height, bmin, bmax, cs, ch); 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) if (!result)
throw NavigatorException("Failed to create heightfield for navmesh"); throw NavigatorException("Failed to create heightfield for navmesh");
} }
bool rasterizeTriangles(rcContext& context, const Mesh& mesh, const Settings& settings, const rcConfig& config, bool rasterizeTriangles(rcContext& context, const Mesh& mesh, const RecastSettings& settings,
rcHeightfield& solid) const RecastParams& params, rcHeightfield& solid)
{ {
std::vector<unsigned char> areas(mesh.getAreaTypes().begin(), mesh.getAreaTypes().end()); std::vector<unsigned char> areas(mesh.getAreaTypes().begin(), mesh.getAreaTypes().end());
std::vector<float> vertices = mesh.getVertices(); std::vector<float> vertices = mesh.getVertices();
@ -179,7 +177,7 @@ namespace
rcClearUnwalkableTriangles( rcClearUnwalkableTriangles(
&context, &context,
config.walkableSlopeAngle, settings.mMaxSlope,
vertices.data(), vertices.data(),
static_cast<int>(mesh.getVerticesCount()), static_cast<int>(mesh.getVerticesCount()),
mesh.getIndices().data(), mesh.getIndices().data(),
@ -195,30 +193,18 @@ namespace
areas.data(), areas.data(),
static_cast<int>(areas.size()), static_cast<int>(areas.size()),
solid, solid,
config.walkableClimb params.mWalkableClimb
); );
} }
bool rasterizeTriangles(rcContext& context, const Rectangle& rectangle, const rcConfig& config, bool rasterizeTriangles(rcContext& context, const Rectangle& rectangle, AreaType areaType,
AreaType areaType, rcHeightfield& solid) 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 { const std::array vertices {
tileBoundsMin.x(), rectangle.mHeight, tileBoundsMin.y(), rectangle.mBounds.mMin.x(), rectangle.mHeight, rectangle.mBounds.mMin.y(),
tileBoundsMin.x(), rectangle.mHeight, tileBoundsMax.y(), rectangle.mBounds.mMin.x(), rectangle.mHeight, rectangle.mBounds.mMax.y(),
tileBoundsMax.x(), rectangle.mHeight, tileBoundsMax.y(), rectangle.mBounds.mMax.x(), rectangle.mHeight, rectangle.mBounds.mMax.y(),
tileBoundsMax.x(), rectangle.mHeight, tileBoundsMin.y(), rectangle.mBounds.mMax.x(), rectangle.mHeight, rectangle.mBounds.mMin.y(),
}; };
const std::array indices { const std::array indices {
@ -236,31 +222,42 @@ namespace
areas.data(), areas.data(),
static_cast<int>(areas.size()), static_cast<int>(areas.size()),
solid, solid,
config.walkableClimb params.mWalkableClimb
); );
} }
bool rasterizeTriangles(rcContext& context, const osg::Vec3f& agentHalfExtents, const std::vector<CellWater>& water, bool rasterizeTriangles(rcContext& context, const osg::Vec3f& agentHalfExtents, const std::vector<CellWater>& 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) for (const CellWater& cellWater : water)
{ {
const Rectangle rectangle = getSwimRectangle(cellWater, settings, agentHalfExtents); const TileBounds cellTileBounds = maxCellTileBounds(cellWater.mCellPosition, cellWater.mWater.mCellSize);
if (!rasterizeTriangles(context, rectangle, config, AreaType_water, solid)) 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 false;
} }
}
return true; return true;
} }
bool rasterizeTriangles(rcContext& context, const TileBounds& tileBounds, const std::vector<FlatHeightfield>& heightfields, bool rasterizeTriangles(rcContext& context, const TileBounds& realTileBounds, const std::vector<FlatHeightfield>& heightfields,
const Settings& settings, const rcConfig& config, rcHeightfield& solid) const RecastSettings& settings, const RecastParams& params, rcHeightfield& solid)
{ {
for (const FlatHeightfield& heightfield : heightfields) 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)}; const Rectangle rectangle {
if (!rasterizeTriangles(context, rectangle, config, AreaType_ground, solid)) toNavMeshCoordinates(settings, *intersection),
toNavMeshCoordinates(settings, heightfield.mHeight)
};
if (!rasterizeTriangles(context, rectangle, AreaType_ground, params, solid))
return false; return false;
} }
} }
@ -268,27 +265,25 @@ namespace
} }
bool rasterizeTriangles(rcContext& context, const std::vector<Heightfield>& heightfields, bool rasterizeTriangles(rcContext& context, const std::vector<Heightfield>& 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) for (const Heightfield& heightfield : heightfields)
{ {
const Mesh mesh = makeMesh(heightfield); const Mesh mesh = makeMesh(heightfield);
if (!rasterizeTriangles(context, mesh, settings, config, solid)) if (!rasterizeTriangles(context, mesh, settings, params, solid))
return false; return false;
} }
return true; return true;
} }
bool rasterizeTriangles(rcContext& context, const TilePosition& tilePosition, const osg::Vec3f& agentHalfExtents, 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) const TileBounds realTileBounds = makeRealTileBoundsWithBorder(settings, tilePosition);
&& rasterizeTriangles(context, agentHalfExtents, recastMesh.getWater(), settings, config, solid) return rasterizeTriangles(context, recastMesh.getMesh(), settings, params, solid)
&& rasterizeTriangles(context, recastMesh.getHeightfields(), settings, config, solid) && rasterizeTriangles(context, agentHalfExtents, recastMesh.getWater(), settings, params, realTileBounds, solid)
&& rasterizeTriangles(context, makeRealTileBoundsWithBorder(settings, tilePosition), && rasterizeTriangles(context, recastMesh.getHeightfields(), settings, params, solid)
recastMesh.getFlatHeightfields(), settings, config, solid); && rasterizeTriangles(context, realTileBounds, recastMesh.getFlatHeightfields(), settings, params, solid);
} }
void buildCompactHeightfield(rcContext& context, const int walkableHeight, const int walkableClimb, void buildCompactHeightfield(rcContext& context, const int walkableHeight, const int walkableClimb,
@ -359,27 +354,25 @@ namespace
polyMesh.flags[i] = getFlag(static_cast<AreaType>(polyMesh.areas[i])); polyMesh.flags[i] = getFlag(static_cast<AreaType>(polyMesh.areas[i]));
} }
bool fillPolyMesh(rcContext& context, const rcConfig& config, rcHeightfield& solid, rcPolyMesh& polyMesh, bool fillPolyMesh(rcContext& context, const RecastSettings& settings, const RecastParams& params,
rcPolyMeshDetail& polyMeshDetail) rcHeightfield& solid, rcPolyMesh& polyMesh, rcPolyMeshDetail& polyMeshDetail)
{ {
rcCompactHeightfield compact; rcCompactHeightfield compact;
compact.dist = nullptr; buildCompactHeightfield(context, params.mWalkableHeight, params.mWalkableClimb, solid, compact);
buildCompactHeightfield(context, config.walkableHeight, config.walkableClimb, solid, compact);
erodeWalkableArea(context, config.walkableRadius, compact); erodeWalkableArea(context, params.mWalkableRadius, compact);
buildDistanceField(context, compact); buildDistanceField(context, compact);
buildRegions(context, compact, config.borderSize, config.minRegionArea, config.mergeRegionArea); buildRegions(context, compact, settings.mBorderSize, settings.mRegionMinArea, settings.mRegionMergeArea);
rcContourSet contourSet; rcContourSet contourSet;
buildContours(context, compact, config.maxSimplificationError, config.maxEdgeLen, contourSet); buildContours(context, compact, settings.mMaxSimplificationError, params.mMaxEdgeLen, contourSet);
if (contourSet.nconts == 0) if (contourSet.nconts == 0)
return false; return false;
buildPolyMesh(context, contourSet, config.maxVertsPerPoly, polyMesh); buildPolyMesh(context, contourSet, settings.mMaxVertsPerPoly, polyMesh);
buildPolyMeshDetail(context, polyMesh, compact, config.detailSampleDist, config.detailSampleMaxError, buildPolyMeshDetail(context, polyMesh, compact, params.mSampleDist, params.mSampleMaxError, polyMeshDetail);
polyMeshDetail);
setPolyMeshFlags(polyMesh); setPolyMeshFlags(polyMesh);
@ -395,7 +388,7 @@ namespace
return power; return power;
} }
std::pair<float, float> getBoundsByZ(const RecastMesh& recastMesh, const osg::Vec3f& agentHalfExtents, const Settings& settings) std::pair<float, float> getBoundsByZ(const RecastMesh& recastMesh, const osg::Vec3f& agentHalfExtents, const RecastSettings& settings)
{ {
float minZ = 0; float minZ = 0;
float maxZ = 0; float maxZ = 0;
@ -437,38 +430,39 @@ namespace
namespace DetourNavigator namespace DetourNavigator
{ {
std::unique_ptr<PreparedNavMeshData> prepareNavMeshTileData(const RecastMesh& recastMesh, std::unique_ptr<PreparedNavMeshData> 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; 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; 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; return nullptr;
rcFilterLowHangingWalkableObstacles(&context, config.walkableClimb, solid); rcFilterLowHangingWalkableObstacles(&context, params.mWalkableClimb, solid);
rcFilterLedgeSpans(&context, config.walkableHeight, config.walkableClimb, solid); rcFilterLedgeSpans(&context, params.mWalkableHeight, params.mWalkableClimb, solid);
rcFilterWalkableLowHeightSpans(&context, config.walkableHeight, solid); rcFilterWalkableLowHeightSpans(&context, params.mWalkableHeight, solid);
std::unique_ptr<PreparedNavMeshData> result = std::make_unique<PreparedNavMeshData>(); std::unique_ptr<PreparedNavMeshData> result = std::make_unique<PreparedNavMeshData>();
if (!fillPolyMesh(context, config, solid, result->mPolyMesh, result->mPolyMeshDetail)) if (!fillPolyMesh(context, settings, params, solid, result->mPolyMesh, result->mPolyMeshDetail))
return nullptr; return nullptr;
result->mCellSize = config.cs; result->mCellSize = settings.mCellSize;
result->mCellHeight = config.ch; result->mCellHeight = settings.mCellHeight;
return result; return result;
} }
NavMeshData makeNavMeshTileData(const PreparedNavMeshData& data, NavMeshData makeNavMeshTileData(const PreparedNavMeshData& data,
const std::vector<OffMeshConnection>& offMeshConnections, const osg::Vec3f& agentHalfExtents, const std::vector<OffMeshConnection>& offMeshConnections, const osg::Vec3f& agentHalfExtents,
const TilePosition& tile, const Settings& settings) const TilePosition& tile, const RecastSettings& settings)
{ {
const auto offMeshConVerts = getOffMeshVerts(offMeshConnections); const auto offMeshConVerts = getOffMeshVerts(offMeshConnections);
const std::vector<float> offMeshConRad(offMeshConnections.size(), getRadius(settings, agentHalfExtents)); const std::vector<float> offMeshConRad(offMeshConnections.size(), getRadius(settings, agentHalfExtents));
@ -524,7 +518,7 @@ namespace DetourNavigator
// Max tiles and max polys affect how the tile IDs are caculated. // Max tiles and max polys affect how the tile IDs are caculated.
// There are 22 bits available for identifying a tile and a polygon. // There are 22 bits available for identifying a tile and a polygon.
const int polysAndTilesBits = 22; const int polysAndTilesBits = 22;
const auto polysBits = getMinValuableBitsNumber(settings.mMaxPolys); const auto polysBits = getMinValuableBitsNumber(settings.mDetour.mMaxPolys);
if (polysBits >= polysAndTilesBits) if (polysBits >= polysAndTilesBits)
throw InvalidArgument("Too many polygons per tile"); throw InvalidArgument("Too many polygons per tile");
@ -533,8 +527,8 @@ namespace DetourNavigator
dtNavMeshParams params; dtNavMeshParams params;
std::fill_n(params.orig, 3, 0.0f); std::fill_n(params.orig, 3, 0.0f);
params.tileWidth = settings.mTileSize * settings.mCellSize; params.tileWidth = settings.mRecast.mTileSize * settings.mRecast.mCellSize;
params.tileHeight = settings.mTileSize * settings.mCellSize; params.tileHeight = settings.mRecast.mTileSize * settings.mRecast.mCellSize;
params.maxTiles = 1 << tilesBits; params.maxTiles = 1 << tilesBits;
params.maxPolys = 1 << polysBits; params.maxPolys = 1 << polysBits;
@ -550,72 +544,4 @@ namespace DetourNavigator
return navMesh; return navMesh;
} }
UpdateNavMeshStatus updateNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh* recastMesh,
const TilePosition& changedTile, const TilePosition& playerTile,
const std::vector<OffMeshConnection>& 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<bool>(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();
}
} }

@ -7,6 +7,9 @@
#include "sharednavmesh.hpp" #include "sharednavmesh.hpp"
#include "navmeshtilescache.hpp" #include "navmeshtilescache.hpp"
#include "offmeshconnection.hpp" #include "offmeshconnection.hpp"
#include "navmeshdb.hpp"
#include <components/misc/guarded.hpp>
#include <osg/Vec3f> #include <osg/Vec3f>
@ -14,6 +17,7 @@
#include <vector> #include <vector>
class dtNavMesh; class dtNavMesh;
struct rcConfig;
namespace DetourNavigator namespace DetourNavigator
{ {
@ -38,25 +42,22 @@ namespace DetourNavigator
return expectedTilesCount <= maxTiles; return expectedTilesCount <= maxTiles;
} }
std::unique_ptr<PreparedNavMeshData> prepareNavMeshTileData(const RecastMesh& recastMesh, const TilePosition& tile, inline bool isEmpty(const RecastMesh& recastMesh)
const Bounds& bounds, const osg::Vec3f& agentHalfExtents, const Settings& settings); {
return recastMesh.getMesh().getIndices().empty()
&& recastMesh.getWater().empty()
&& recastMesh.getHeightfields().empty()
&& recastMesh.getFlatHeightfields().empty();
}
std::unique_ptr<PreparedNavMeshData> prepareNavMeshTileData(const RecastMesh& recastMesh,
const TilePosition& tilePosition, const osg::Vec3f& agentHalfExtents, const RecastSettings& settings);
NavMeshData makeNavMeshTileData(const PreparedNavMeshData& data, NavMeshData makeNavMeshTileData(const PreparedNavMeshData& data,
const std::vector<OffMeshConnection>& offMeshConnections, const osg::Vec3f& agentHalfExtents, const std::vector<OffMeshConnection>& offMeshConnections, const osg::Vec3f& agentHalfExtents,
const TilePosition& tile, const Settings& settings); const TilePosition& tile, const RecastSettings& settings);
NavMeshPtr makeEmptyNavMesh(const Settings& 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<OffMeshConnection>& offMeshConnections, const Settings& settings,
const SharedNavMeshCacheItem& navMeshCacheItem, NavMeshTilesCache& navMeshTilesCache, UpdateType updateType);
} }
#endif #endif

@ -5,10 +5,15 @@
namespace DetourNavigator namespace DetourNavigator
{ {
std::unique_ptr<Navigator> makeNavigator(const Settings& settings) std::unique_ptr<Navigator> makeNavigator(const Settings& settings, const std::string& userDataPath)
{ {
DetourNavigator::RecastGlobalAllocator::init(); DetourNavigator::RecastGlobalAllocator::init();
return std::make_unique<NavigatorImpl>(settings);
std::unique_ptr<NavMeshDb> db;
if (settings.mEnableNavMeshDiskCache)
db = std::make_unique<NavMeshDb>(userDataPath + "/navmesh.db");
return std::make_unique<NavigatorImpl>(settings, std::move(db));
} }
std::unique_ptr<Navigator> makeNavigatorStub() std::unique_ptr<Navigator> makeNavigatorStub()

@ -6,9 +6,12 @@
#include "recastmeshtiles.hpp" #include "recastmeshtiles.hpp"
#include "waitconditiontype.hpp" #include "waitconditiontype.hpp"
#include "heightfieldshape.hpp" #include "heightfieldshape.hpp"
#include "objecttransform.hpp"
#include <components/resource/bulletshape.hpp> #include <components/resource/bulletshape.hpp>
#include <string_view>
namespace ESM namespace ESM
{ {
struct Cell; struct Cell;
@ -27,10 +30,14 @@ namespace DetourNavigator
struct ObjectShapes struct ObjectShapes
{ {
osg::ref_ptr<const Resource::BulletShapeInstance> mShapeInstance; osg::ref_ptr<const Resource::BulletShapeInstance> mShapeInstance;
ObjectTransform mTransform;
ObjectShapes(const osg::ref_ptr<const Resource::BulletShapeInstance>& shapeInstance) ObjectShapes(const osg::ref_ptr<const Resource::BulletShapeInstance>& shapeInstance, const ObjectTransform& transform)
: mShapeInstance(shapeInstance) : mShapeInstance(shapeInstance)
{} , mTransform(transform)
{
assert(mShapeInstance != nullptr);
}
}; };
struct DoorShapes : ObjectShapes struct DoorShapes : ObjectShapes
@ -39,8 +46,8 @@ namespace DetourNavigator
osg::Vec3f mConnectionEnd; osg::Vec3f mConnectionEnd;
DoorShapes(const osg::ref_ptr<const Resource::BulletShapeInstance>& shapeInstance, DoorShapes(const osg::ref_ptr<const Resource::BulletShapeInstance>& shapeInstance,
const osg::Vec3f& connectionStart,const osg::Vec3f& connectionEnd) const ObjectTransform& transform, const osg::Vec3f& connectionStart, const osg::Vec3f& connectionEnd)
: ObjectShapes(shapeInstance) : ObjectShapes(shapeInstance, transform)
, mConnectionStart(connectionStart) , mConnectionStart(connectionStart)
, mConnectionEnd(connectionEnd) , mConnectionEnd(connectionEnd)
{} {}
@ -70,6 +77,12 @@ namespace DetourNavigator
*/ */
virtual void removeAgent(const osg::Vec3f& agentHalfExtents) = 0; 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 * @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 * @param id is used to distinguish different objects
@ -183,7 +196,7 @@ namespace DetourNavigator
virtual float getMaxNavmeshAreaRealRadius() const = 0; virtual float getMaxNavmeshAreaRealRadius() const = 0;
}; };
std::unique_ptr<Navigator> makeNavigator(const Settings& settings); std::unique_ptr<Navigator> makeNavigator(const Settings& settings, const std::string& userDataPath);
std::unique_ptr<Navigator> makeNavigatorStub(); std::unique_ptr<Navigator> makeNavigatorStub();
} }

@ -8,9 +8,9 @@
namespace DetourNavigator namespace DetourNavigator
{ {
NavigatorImpl::NavigatorImpl(const Settings& settings) NavigatorImpl::NavigatorImpl(const Settings& settings, std::unique_ptr<NavMeshDb>&& db)
: mSettings(settings) : mSettings(settings)
, mNavMeshManager(mSettings) , mNavMeshManager(mSettings, std::move(db))
, mUpdatesEnabled(true) , mUpdatesEnabled(true)
{ {
} }
@ -32,14 +32,19 @@ namespace DetourNavigator
--it->second; --it->second;
} }
void NavigatorImpl::setWorldspace(std::string_view worldspace)
{
mNavMeshManager.setWorldspace(worldspace);
}
bool NavigatorImpl::addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) 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); bool result = mNavMeshManager.addObject(id, collisionShape, transform, AreaType_ground);
if (const btCollisionShape* const avoidShape = shapes.mShapeInstance->mAvoidCollisionShape.get()) if (const btCollisionShape* const avoidShape = shapes.mShapeInstance->mAvoidCollisionShape.get())
{ {
const ObjectId avoidId(avoidShape); 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)) if (mNavMeshManager.addObject(avoidId, avoidCollisionShape, transform, AreaType_null))
{ {
updateAvoidShapeId(id, avoidId); updateAvoidShapeId(id, avoidId);
@ -53,8 +58,8 @@ namespace DetourNavigator
{ {
if (addObject(id, static_cast<const ObjectShapes&>(shapes), transform)) if (addObject(id, static_cast<const ObjectShapes&>(shapes), transform))
{ {
const osg::Vec3f start = toNavMeshCoordinates(mSettings, shapes.mConnectionStart); const osg::Vec3f start = toNavMeshCoordinates(mSettings.mRecast, shapes.mConnectionStart);
const osg::Vec3f end = toNavMeshCoordinates(mSettings, shapes.mConnectionEnd); const osg::Vec3f end = toNavMeshCoordinates(mSettings.mRecast, shapes.mConnectionEnd);
mNavMeshManager.addOffMeshConnection(id, start, end, AreaType_door); mNavMeshManager.addOffMeshConnection(id, start, end, AreaType_door);
mNavMeshManager.addOffMeshConnection(id, end, start, AreaType_door); mNavMeshManager.addOffMeshConnection(id, end, start, AreaType_door);
return true; return true;
@ -64,12 +69,12 @@ namespace DetourNavigator
bool NavigatorImpl::updateObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) 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); bool result = mNavMeshManager.updateObject(id, collisionShape, transform, AreaType_ground);
if (const btCollisionShape* const avoidShape = shapes.mShapeInstance->mAvoidCollisionShape.get()) if (const btCollisionShape* const avoidShape = shapes.mShapeInstance->mAvoidCollisionShape.get())
{ {
const ObjectId avoidId(avoidShape); 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)) if (mNavMeshManager.updateObject(avoidId, avoidCollisionShape, transform, AreaType_null))
{ {
updateAvoidShapeId(id, avoidId); updateAvoidShapeId(id, avoidId);
@ -126,8 +131,8 @@ namespace DetourNavigator
const auto dst = Misc::Convert::makeOsgVec3f(converter.toWorldPoint(pathgrid.mPoints[edge.mV1])); const auto dst = Misc::Convert::makeOsgVec3f(converter.toWorldPoint(pathgrid.mPoints[edge.mV1]));
mNavMeshManager.addOffMeshConnection( mNavMeshManager.addOffMeshConnection(
ObjectId(&pathgrid), ObjectId(&pathgrid),
toNavMeshCoordinates(mSettings, src), toNavMeshCoordinates(mSettings.mRecast, src),
toNavMeshCoordinates(mSettings, dst), toNavMeshCoordinates(mSettings.mRecast, dst),
AreaType_pathgrid AreaType_pathgrid
); );
} }
@ -149,7 +154,7 @@ namespace DetourNavigator
void NavigatorImpl::updatePlayerPosition(const osg::Vec3f& playerPosition) 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) if (mLastPlayerPosition.has_value() && *mLastPlayerPosition == tilePosition)
return; return;
update(playerPosition); update(playerPosition);
@ -225,6 +230,6 @@ namespace DetourNavigator
float NavigatorImpl::getMaxNavmeshAreaRealRadius() const float NavigatorImpl::getMaxNavmeshAreaRealRadius() const
{ {
const auto& settings = getSettings(); const auto& settings = getSettings();
return getRealTileSize(settings) * getMaxNavmeshAreaRadius(settings); return getRealTileSize(settings.mRecast) * getMaxNavmeshAreaRadius(settings);
} }
} }

@ -5,6 +5,7 @@
#include "navmeshmanager.hpp" #include "navmeshmanager.hpp"
#include <set> #include <set>
#include <memory>
namespace DetourNavigator namespace DetourNavigator
{ {
@ -15,12 +16,14 @@ namespace DetourNavigator
* @brief Navigator constructor initializes all internal data. Constructed object is ready to build a scene. * @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. * @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<NavMeshDb>&& db);
void addAgent(const osg::Vec3f& agentHalfExtents) override; void addAgent(const osg::Vec3f& agentHalfExtents) override;
void removeAgent(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 ObjectShapes& shapes, const btTransform& transform) override;
bool addObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform) override; bool addObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform) override;

@ -19,6 +19,8 @@ namespace DetourNavigator
void removeAgent(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 ObjectShapes& /*shapes*/, const btTransform& /*transform*/) override
{ {
return false; return false;

@ -13,11 +13,11 @@ namespace DetourNavigator
return std::nullopt; return std::nullopt;
const auto settings = navigator.getSettings(); const auto settings = navigator.getSettings();
const auto result = DetourNavigator::findRandomPointAroundCircle(navMesh->lockConst()->getImpl(), const auto result = DetourNavigator::findRandomPointAroundCircle(navMesh->lockConst()->getImpl(),
toNavMeshCoordinates(settings, agentHalfExtents), toNavMeshCoordinates(settings, start), toNavMeshCoordinates(settings.mRecast, agentHalfExtents), toNavMeshCoordinates(settings.mRecast, start),
toNavMeshCoordinates(settings, maxRadius), includeFlags, settings); toNavMeshCoordinates(settings.mRecast, maxRadius), includeFlags, settings.mDetour);
if (!result) if (!result)
return std::nullopt; return std::nullopt;
return std::optional<osg::Vec3f>(fromNavMeshCoordinates(settings, *result)); return std::optional<osg::Vec3f>(fromNavMeshCoordinates(settings.mRecast, *result));
} }
std::optional<osg::Vec3f> raycast(const Navigator& navigator, const osg::Vec3f& agentHalfExtents, const osg::Vec3f& start, std::optional<osg::Vec3f> raycast(const Navigator& navigator, const osg::Vec3f& agentHalfExtents, const osg::Vec3f& start,
@ -28,10 +28,10 @@ namespace DetourNavigator
return std::nullopt; return std::nullopt;
const auto settings = navigator.getSettings(); const auto settings = navigator.getSettings();
const auto result = DetourNavigator::raycast(navMesh->lockConst()->getImpl(), const auto result = DetourNavigator::raycast(navMesh->lockConst()->getImpl(),
toNavMeshCoordinates(settings, agentHalfExtents), toNavMeshCoordinates(settings, start), toNavMeshCoordinates(settings.mRecast, agentHalfExtents), toNavMeshCoordinates(settings.mRecast, start),
toNavMeshCoordinates(settings, end), includeFlags, settings); toNavMeshCoordinates(settings.mRecast, end), includeFlags, settings.mDetour);
if (!result) if (!result)
return std::nullopt; return std::nullopt;
return fromNavMeshCoordinates(settings, *result); return fromNavMeshCoordinates(settings.mRecast, *result);
} }
} }

@ -37,9 +37,9 @@ namespace DetourNavigator
if (navMesh == nullptr) if (navMesh == nullptr)
return Status::NavMeshNotFound; return Status::NavMeshNotFound;
const auto settings = navigator.getSettings(); const auto settings = navigator.getSettings();
return findSmoothPath(navMesh->lockConst()->getImpl(), toNavMeshCoordinates(settings, agentHalfExtents), return findSmoothPath(navMesh->lockConst()->getImpl(), toNavMeshCoordinates(settings.mRecast, agentHalfExtents),
toNavMeshCoordinates(settings, stepSize), toNavMeshCoordinates(settings, start), toNavMeshCoordinates(settings.mRecast, stepSize), toNavMeshCoordinates(settings.mRecast, start),
toNavMeshCoordinates(settings, end), includeFlags, areaCosts, settings, endTolerance, out); toNavMeshCoordinates(settings.mRecast, end), includeFlags, areaCosts, settings, endTolerance, out);
} }
/** /**

@ -50,7 +50,8 @@ namespace DetourNavigator
{ {
return UpdateNavMeshStatus::ignored; 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); const auto addStatus = addTile(*mImpl, navMeshData.mValue.get(), navMeshData.mSize);
if (dtStatusSucceed(addStatus)) if (dtStatusSucceed(addStatus))
{ {
@ -82,7 +83,8 @@ namespace DetourNavigator
UpdateNavMeshStatus NavMeshCacheItem::removeTile(const TilePosition& position) 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) if (removed)
{ {
mUsedTiles.erase(position); mUsedTiles.erase(position);
@ -90,4 +92,21 @@ namespace DetourNavigator
} }
return UpdateNavMeshStatusBuilder().removed(removed).getResult(); 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();
}
} }

@ -12,6 +12,7 @@
#include <map> #include <map>
#include <ostream> #include <ostream>
#include <set>
struct dtMeshTile; struct dtMeshTile;
@ -147,6 +148,10 @@ namespace DetourNavigator
UpdateNavMeshStatus removeTile(const TilePosition& position); UpdateNavMeshStatus removeTile(const TilePosition& position);
UpdateNavMeshStatus markAsEmpty(const TilePosition& position);
bool isEmptyTile(const TilePosition& position) const;
template <class Function> template <class Function>
void forEachUsedTile(Function&& function) const void forEachUsedTile(Function&& function) const
{ {
@ -166,6 +171,7 @@ namespace DetourNavigator
NavMeshPtr mImpl; NavMeshPtr mImpl;
Version mVersion; Version mVersion;
std::map<TilePosition, Tile> mUsedTiles; std::map<TilePosition, Tile> mUsedTiles;
std::set<TilePosition> mEmptyTiles;
}; };
using GuardedNavMeshCacheItem = Misc::ScopeGuarded<NavMeshCacheItem>; using GuardedNavMeshCacheItem = Misc::ScopeGuarded<NavMeshCacheItem>;

@ -0,0 +1,296 @@
#include "navmeshdb.hpp"
#include <components/debug/debuglog.hpp>
#include <components/misc/compression.hpp>
#include <components/sqlite3/db.hpp>
#include <components/sqlite3/request.hpp>
#include <DetourAlloc.h>
#include <sqlite3.h>
#include <cstddef>
#include <string>
#include <string_view>
#include <vector>
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<std::underlying_type_t<ShapeType>>(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<Tile> NavMeshDb::findTile(const std::string& worldspace,
const TilePosition& tilePosition, const std::vector<std::byte>& input)
{
Tile result;
auto row = std::tie(result.mTileId, result.mVersion);
const std::vector<std::byte> compressedInput = Misc::compress(input);
if (&row == request(*mDb, mFindTile, &row, 1, worldspace, tilePosition, compressedInput))
return {};
return result;
}
std::optional<TileData> NavMeshDb::getTileData(const std::string& worldspace,
const TilePosition& tilePosition, const std::vector<std::byte>& input)
{
TileData result;
auto row = std::tie(result.mTileId, result.mVersion, result.mData);
const std::vector<std::byte> 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<std::byte>& input, const std::vector<std::byte>& data)
{
const std::vector<std::byte> compressedInput = Misc::compress(input);
const std::vector<std::byte> compressedData = Misc::compress(data);
return execute(*mDb, mInsertTile, tileId, worldspace, tilePosition, version, compressedInput, compressedData);
}
int NavMeshDb::updateTile(TileId tileId, TileVersion version, const std::vector<std::byte>& data)
{
const std::vector<std::byte> 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<ShapeId> 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<std::byte>& 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<std::byte>& 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<std::byte>& input,
const std::vector<std::byte>& 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<std::byte>& 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<int>(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<int>(type));
Sqlite3::bindParameter(db, statement, ":hash", hash);
}
}
}

@ -0,0 +1,153 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHDB_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHDB_H
#include "tileposition.hpp"
#include <components/sqlite3/db.hpp>
#include <components/sqlite3/statement.hpp>
#include <components/sqlite3/transaction.hpp>
#include <components/sqlite3/types.hpp>
#include <boost/serialization/strong_typedef.hpp>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <optional>
#include <stdexcept>
#include <string>
#include <string_view>
#include <tuple>
#include <utility>
#include <vector>
#include <memory>
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<std::byte> 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<std::byte>& 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<std::byte>& 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<std::byte>& input,
const std::vector<std::byte>& data);
};
struct UpdateTile
{
static std::string_view text() noexcept;
static void bind(sqlite3& db, sqlite3_stmt& statement, TileId tileId, TileVersion version,
const std::vector<std::byte>& 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<Tile> findTile(const std::string& worldspace,
const TilePosition& tilePosition, const std::vector<std::byte>& input);
std::optional<TileData> getTileData(const std::string& worldspace,
const TilePosition& tilePosition, const std::vector<std::byte>& input);
int insertTile(TileId tileId, const std::string& worldspace, const TilePosition& tilePosition,
TileVersion version, const std::vector<std::byte>& input, const std::vector<std::byte>& data);
int updateTile(TileId tileId, TileVersion version, const std::vector<std::byte>& data);
ShapeId getMaxShapeId();
std::optional<ShapeId> 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<DbQueries::GetMaxTileId> mGetMaxTileId;
Sqlite3::Statement<DbQueries::FindTile> mFindTile;
Sqlite3::Statement<DbQueries::GetTileData> mGetTileData;
Sqlite3::Statement<DbQueries::InsertTile> mInsertTile;
Sqlite3::Statement<DbQueries::UpdateTile> mUpdateTile;
Sqlite3::Statement<DbQueries::GetMaxShapeId> mGetMaxShapeId;
Sqlite3::Statement<DbQueries::FindShapeId> mFindShapeId;
Sqlite3::Statement<DbQueries::InsertShape> mInsertShape;
};
}
#endif

@ -0,0 +1,40 @@
#include "navmeshdbutils.hpp"
#include "navmeshdb.hpp"
#include "recastmesh.hpp"
#include <components/debug/debuglog.hpp>
#include <cassert>
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<int>(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);
}
}
}

@ -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

@ -41,13 +41,23 @@ namespace
namespace DetourNavigator namespace DetourNavigator
{ {
NavMeshManager::NavMeshManager(const Settings& settings) NavMeshManager::NavMeshManager(const Settings& settings, std::unique_ptr<NavMeshDb>&& db)
: mSettings(settings) : mSettings(settings)
, mRecastMeshManager(settings) , mRecastMeshManager(settings.mRecast)
, mOffMeshConnectionsManager(settings) , mOffMeshConnectionsManager(settings.mRecast)
, mAsyncNavMeshUpdater(settings, mRecastMeshManager, mOffMeshConnectionsManager) , 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<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), ++mGenerationCounter);
mWorldspace = worldspace;
}
bool NavMeshManager::addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, bool NavMeshManager::addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform,
const AreaType areaType) const AreaType areaType)
{ {
@ -140,8 +150,8 @@ namespace DetourNavigator
{ {
mOffMeshConnectionsManager.add(id, OffMeshConnection {start, end, areaType}); mOffMeshConnectionsManager.add(id, OffMeshConnection {start, end, areaType});
const auto startTilePosition = getTilePosition(mSettings, start); const auto startTilePosition = getTilePosition(mSettings.mRecast, start);
const auto endTilePosition = getTilePosition(mSettings, end); const auto endTilePosition = getTilePosition(mSettings.mRecast, end);
addChangedTile(startTilePosition, ChangeType::add); addChangedTile(startTilePosition, ChangeType::add);
@ -158,7 +168,7 @@ namespace DetourNavigator
void NavMeshManager::update(const osg::Vec3f& playerPosition, const osg::Vec3f& agentHalfExtents) 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& lastRevision = mLastRecastMeshManagerRevision[agentHalfExtents];
auto lastPlayerTile = mPlayerTile.find(agentHalfExtents); auto lastPlayerTile = mPlayerTile.find(agentHalfExtents);
if (lastRevision == mRecastMeshManager.getRevision() && lastPlayerTile != mPlayerTile.end() if (lastRevision == mRecastMeshManager.getRevision() && lastPlayerTile != mPlayerTile.end()
@ -201,14 +211,14 @@ namespace DetourNavigator
const auto shouldAdd = shouldAddTile(tile, playerTile, maxTiles); const auto shouldAdd = shouldAddTile(tile, playerTile, maxTiles);
const auto presentInNavMesh = bool(navMesh.getTileAt(tile.x(), tile.y(), 0)); const auto presentInNavMesh = bool(navMesh.getTileAt(tile.x(), tile.y(), 0));
if (shouldAdd && !presentInNavMesh) 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) else if (!shouldAdd && presentInNavMesh)
tilesToPost.insert(std::make_pair(tile, ChangeType::mixed)); tilesToPost.insert(std::make_pair(tile, ChangeType::mixed));
else else
recastMeshManager.reportNavMeshChange(recastMeshManager.getVersion(), Version {0, 0}); 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()) if (changedTiles != mChangedTiles.end())
changedTiles->second.clear(); changedTiles->second.clear();
Log(Debug::Debug) << "Cache update posted for agent=" << agentHalfExtents << 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 void NavMeshManager::reportStats(unsigned int frameNumber, osg::Stats& stats) const
{ {
mAsyncNavMeshUpdater.reportStats(frameNumber, stats); DetourNavigator::reportStats(mAsyncNavMeshUpdater.getStats(), frameNumber, stats);
} }
RecastMeshTiles NavMeshManager::getRecastMeshTiles() const RecastMeshTiles NavMeshManager::getRecastMeshTiles() const
@ -241,9 +251,10 @@ namespace DetourNavigator
std::vector<TilePosition> tiles; std::vector<TilePosition> tiles;
mRecastMeshManager.forEachTile( mRecastMeshManager.forEachTile(
[&tiles] (const TilePosition& tile, const CachedRecastMeshManager&) { tiles.push_back(tile); }); [&tiles] (const TilePosition& tile, const CachedRecastMeshManager&) { tiles.push_back(tile); });
const std::string worldspace = mRecastMeshManager.getWorldspace();
RecastMeshTiles result; RecastMeshTiles result;
for (const TilePosition& tile : tiles) for (const TilePosition& tile : tiles)
if (auto mesh = mRecastMeshManager.getCachedMesh(tile)) if (auto mesh = mRecastMeshManager.getCachedMesh(worldspace, tile))
result.emplace(tile, std::move(mesh)); result.emplace(tile, std::move(mesh));
return result; return result;
} }
@ -251,7 +262,7 @@ namespace DetourNavigator
void NavMeshManager::addChangedTiles(const btCollisionShape& shape, const btTransform& transform, void NavMeshManager::addChangedTiles(const btCollisionShape& shape, const btTransform& transform,
const ChangeType changeType) const ChangeType changeType)
{ {
getTilesPositions(shape, transform, mSettings, getTilesPositions(shape, transform, mSettings.mRecast,
[&] (const TilePosition& v) { addChangedTile(v, changeType); }); [&] (const TilePosition& v) { addChangedTile(v, changeType); });
} }
@ -261,7 +272,7 @@ namespace DetourNavigator
if (cellSize == std::numeric_limits<int>::max()) if (cellSize == std::numeric_limits<int>::max())
return; return;
getTilesPositions(cellSize, shift, mSettings, getTilesPositions(cellSize, shift, mSettings.mRecast,
[&] (const TilePosition& v) { addChangedTile(v, changeType); }); [&] (const TilePosition& v) { addChangedTile(v, changeType); });
} }

@ -22,7 +22,9 @@ namespace DetourNavigator
class NavMeshManager class NavMeshManager
{ {
public: public:
NavMeshManager(const Settings& settings); explicit NavMeshManager(const Settings& settings, std::unique_ptr<NavMeshDb>&& db);
void setWorldspace(std::string_view worldspace);
bool addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, bool addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform,
const AreaType areaType); const AreaType areaType);
@ -62,6 +64,7 @@ namespace DetourNavigator
private: private:
const Settings& mSettings; const Settings& mSettings;
std::string mWorldspace;
TileCachedRecastMeshManager mRecastMeshManager; TileCachedRecastMeshManager mRecastMeshManager;
OffMeshConnectionsManager mOffMeshConnectionsManager; OffMeshConnectionsManager mOffMeshConnectionsManager;
AsyncNavMeshUpdater mAsyncNavMeshUpdater; AsyncNavMeshUpdater mAsyncNavMeshUpdater;

@ -79,12 +79,11 @@ namespace DetourNavigator
return result; 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", static_cast<double>(stats.mNavMeshCacheSize));
out.setAttribute(frameNumber, "NavMesh CacheSize", stats.mNavMeshCacheSize); out.setAttribute(frameNumber, "NavMesh UsedTiles", static_cast<double>(stats.mUsedNavMeshTiles));
out.setAttribute(frameNumber, "NavMesh UsedTiles", stats.mUsedNavMeshTiles); out.setAttribute(frameNumber, "NavMesh CachedTiles", static_cast<double>(stats.mCachedNavMeshTiles));
out.setAttribute(frameNumber, "NavMesh CachedTiles", stats.mCachedNavMeshTiles);
if (stats.mGetCount > 0) if (stats.mGetCount > 0)
out.setAttribute(frameNumber, "NavMesh CacheHitRate", static_cast<double>(stats.mHitCount) / stats.mGetCount * 100.0); out.setAttribute(frameNumber, "NavMesh CacheHitRate", static_cast<double>(stats.mHitCount) / stats.mGetCount * 100.0);
} }

@ -144,8 +144,6 @@ namespace DetourNavigator
Stats getStats() const; Stats getStats() const;
void reportStats(unsigned int frameNumber, osg::Stats& stats) const;
private: private:
mutable std::mutex mMutex; mutable std::mutex mMutex;
std::size_t mMaxNavMeshDataSize; std::size_t mMaxNavMeshDataSize;
@ -163,6 +161,8 @@ namespace DetourNavigator
void releaseItem(ItemIterator iterator); void releaseItem(ItemIterator iterator);
}; };
void reportStats(const NavMeshTilesCache::Stats& stats, unsigned int frameNumber, osg::Stats& out);
} }
#endif #endif

@ -15,6 +15,11 @@ namespace DetourNavigator
{ {
} }
explicit ObjectId(std::size_t value) noexcept
: mValue(value)
{
}
std::size_t value() const noexcept std::size_t value() const noexcept
{ {
return mValue; return mValue;

@ -0,0 +1,27 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_OBJECTTRANSFORM_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_OBJECTTRANSFORM_H
#include <components/esm/defs.hpp>
#include <tuple>
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

@ -11,7 +11,7 @@
namespace DetourNavigator namespace DetourNavigator
{ {
OffMeshConnectionsManager::OffMeshConnectionsManager(const Settings& settings) OffMeshConnectionsManager::OffMeshConnectionsManager(const RecastSettings& settings)
: mSettings(settings) : mSettings(settings)
{} {}
@ -65,11 +65,11 @@ namespace DetourNavigator
return removed; return removed;
} }
std::vector<OffMeshConnection> OffMeshConnectionsManager::get(const TilePosition& tilePosition) std::vector<OffMeshConnection> OffMeshConnectionsManager::get(const TilePosition& tilePosition) const
{ {
std::vector<OffMeshConnection> result; std::vector<OffMeshConnection> result;
const auto values = mValues.lock(); const auto values = mValues.lockConst();
const auto itByTilePosition = values->mByTilePosition.find(tilePosition); const auto itByTilePosition = values->mByTilePosition.find(tilePosition);

@ -18,13 +18,13 @@ namespace DetourNavigator
class OffMeshConnectionsManager class OffMeshConnectionsManager
{ {
public: public:
OffMeshConnectionsManager(const Settings& settings); explicit OffMeshConnectionsManager(const RecastSettings& settings);
void add(const ObjectId id, const OffMeshConnection& value); void add(const ObjectId id, const OffMeshConnection& value);
std::set<TilePosition> remove(const ObjectId id); std::set<TilePosition> remove(const ObjectId id);
std::vector<OffMeshConnection> get(const TilePosition& tilePosition); std::vector<OffMeshConnection> get(const TilePosition& tilePosition) const;
private: private:
struct Values struct Values
@ -33,7 +33,7 @@ namespace DetourNavigator
std::map<TilePosition, std::unordered_set<ObjectId>> mByTilePosition; std::map<TilePosition, std::unordered_set<ObjectId>> mByTilePosition;
}; };
const Settings& mSettings; const RecastSettings& mSettings;
Misc::ScopeGuarded<Values> mValues; Misc::ScopeGuarded<Values> mValues;
}; };
} }

@ -1,8 +1,10 @@
#include "preparednavmeshdata.hpp" #include "preparednavmeshdata.hpp"
#include "preparednavmeshdatatuple.hpp" #include "preparednavmeshdatatuple.hpp"
#include "recast.hpp"
#include <Recast.h> #include <Recast.h>
#include <RecastAlloc.h>
#include <cstring>
namespace namespace
{ {
@ -15,13 +17,6 @@ namespace
value.nverts = 0; value.nverts = 0;
value.ntris = 0; value.ntris = 0;
} }
void freePolyMeshDetail(rcPolyMeshDetail& value) noexcept
{
rcFree(value.meshes);
rcFree(value.verts);
rcFree(value.tris);
}
} }
namespace DetourNavigator namespace DetourNavigator
@ -31,6 +26,15 @@ namespace DetourNavigator
initPolyMeshDetail(mPolyMeshDetail); 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 PreparedNavMeshData::~PreparedNavMeshData() noexcept
{ {
freePolyMeshDetail(mPolyMeshDetail); freePolyMeshDetail(mPolyMeshDetail);

@ -18,7 +18,7 @@ namespace DetourNavigator
rcPolyMeshDetail mPolyMeshDetail; rcPolyMeshDetail mPolyMeshDetail;
PreparedNavMeshData() noexcept; PreparedNavMeshData() noexcept;
PreparedNavMeshData(const PreparedNavMeshData&) = delete; PreparedNavMeshData(const PreparedNavMeshData& other);
~PreparedNavMeshData() noexcept; ~PreparedNavMeshData() noexcept;

@ -10,7 +10,7 @@
namespace DetourNavigator namespace DetourNavigator
{ {
std::optional<osg::Vec3f> raycast(const dtNavMesh& navMesh, const osg::Vec3f& halfExtents, std::optional<osg::Vec3f> 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; dtNavMeshQuery navMeshQuery;
if (!initNavMeshQuery(navMeshQuery, navMesh, settings.mMaxNavMeshQueryNodes)) if (!initNavMeshQuery(navMeshQuery, navMesh, settings.mMaxNavMeshQueryNodes))

@ -10,10 +10,10 @@ class dtNavMesh;
namespace DetourNavigator namespace DetourNavigator
{ {
struct Settings; struct DetourSettings;
std::optional<osg::Vec3f> raycast(const dtNavMesh& navMesh, const osg::Vec3f& halfExtents, std::optional<osg::Vec3f> 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 #endif

@ -0,0 +1,80 @@
#include "recast.hpp"
#include <Recast.h>
#include <RecastAlloc.h>
#include <cstring>
#include <new>
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));
}
}

@ -2,8 +2,10 @@
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECAST_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECAST_H
#include <Recast.h> #include <Recast.h>
#include <RecastAlloc.h>
#include <cstddef> #include <cstddef>
#include <type_traits>
namespace DetourNavigator namespace DetourNavigator
{ {
@ -46,6 +48,25 @@ namespace DetourNavigator
{ {
return 4 * static_cast<std::size_t>(value.ntris); return 4 * static_cast<std::size_t>(value.ntris);
} }
void* permRecastAlloc(std::size_t size);
template <class T>
inline void permRecastAlloc(T*& values, std::size_t size)
{
static_assert(std::is_arithmetic_v<T>);
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 #endif

@ -19,13 +19,15 @@ namespace DetourNavigator
} }
RecastMesh::RecastMesh(std::size_t generation, std::size_t revision, Mesh mesh, std::vector<CellWater> water, RecastMesh::RecastMesh(std::size_t generation, std::size_t revision, Mesh mesh, std::vector<CellWater> water,
std::vector<Heightfield> heightfields, std::vector<FlatHeightfield> flatHeightfields) std::vector<Heightfield> heightfields, std::vector<FlatHeightfield> flatHeightfields,
std::vector<MeshSource> meshSources)
: mGeneration(generation) : mGeneration(generation)
, mRevision(revision) , mRevision(revision)
, mMesh(std::move(mesh)) , mMesh(std::move(mesh))
, mWater(std::move(water)) , mWater(std::move(water))
, mHeightfields(std::move(heightfields)) , mHeightfields(std::move(heightfields))
, mFlatHeightfields(std::move(flatHeightfields)) , mFlatHeightfields(std::move(flatHeightfields))
, mMeshSources(std::move(meshSources))
{ {
mWater.shrink_to_fit(); mWater.shrink_to_fit();
mHeightfields.shrink_to_fit(); mHeightfields.shrink_to_fit();

@ -4,8 +4,10 @@
#include "areatype.hpp" #include "areatype.hpp"
#include "bounds.hpp" #include "bounds.hpp"
#include "tilebounds.hpp" #include "tilebounds.hpp"
#include "objecttransform.hpp"
#include <components/bullethelpers/operators.hpp> #include <components/bullethelpers/operators.hpp>
#include <components/resource/bulletshape.hpp>
#include <osg/Vec3f> #include <osg/Vec3f>
#include <osg/Vec2i> #include <osg/Vec2i>
@ -119,11 +121,19 @@ namespace DetourNavigator
return tie(lhs) < tie(rhs); return tie(lhs) < tie(rhs);
} }
struct MeshSource
{
osg::ref_ptr<const Resource::BulletShape> mShape;
ObjectTransform mObjectTransform;
AreaType mAreaType;
};
class RecastMesh class RecastMesh
{ {
public: public:
RecastMesh(std::size_t generation, std::size_t revision, Mesh mesh, std::vector<CellWater> water, RecastMesh(std::size_t generation, std::size_t revision, Mesh mesh, std::vector<CellWater> water,
std::vector<Heightfield> heightfields, std::vector<FlatHeightfield> flatHeightfields); std::vector<Heightfield> heightfields, std::vector<FlatHeightfield> flatHeightfields,
std::vector<MeshSource> sources);
std::size_t getGeneration() const std::size_t getGeneration() const
{ {
@ -152,6 +162,8 @@ namespace DetourNavigator
return mFlatHeightfields; return mFlatHeightfields;
} }
const std::vector<MeshSource>& getMeshSources() const noexcept { return mMeshSources; }
private: private:
std::size_t mGeneration; std::size_t mGeneration;
std::size_t mRevision; std::size_t mRevision;
@ -159,6 +171,7 @@ namespace DetourNavigator
std::vector<CellWater> mWater; std::vector<CellWater> mWater;
std::vector<Heightfield> mHeightfields; std::vector<Heightfield> mHeightfields;
std::vector<FlatHeightfield> mFlatHeightfields; std::vector<FlatHeightfield> mFlatHeightfields;
std::vector<MeshSource> mMeshSources;
friend inline std::size_t getSize(const RecastMesh& value) noexcept friend inline std::size_t getSize(const RecastMesh& value) noexcept
{ {

@ -133,6 +133,13 @@ namespace DetourNavigator
{ {
} }
void RecastMeshBuilder::addObject(const btCollisionShape& shape, const btTransform& transform,
const AreaType areaType, osg::ref_ptr<const Resource::BulletShape> 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, void RecastMeshBuilder::addObject(const btCollisionShape& shape, const btTransform& transform,
const AreaType areaType) const AreaType areaType)
{ {
@ -261,7 +268,8 @@ namespace DetourNavigator
std::sort(mWater.begin(), mWater.end()); std::sort(mWater.begin(), mWater.end());
Mesh mesh = makeMesh(std::move(mTriangles)); Mesh mesh = makeMesh(std::move(mTriangles));
return std::make_shared<RecastMesh>(generation, revision, std::move(mesh), std::move(mWater), return std::make_shared<RecastMesh>(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, void RecastMeshBuilder::addObject(const btConcaveShape& shape, const btTransform& transform,

@ -4,6 +4,8 @@
#include "recastmesh.hpp" #include "recastmesh.hpp"
#include "tilebounds.hpp" #include "tilebounds.hpp"
#include <components/resource/bulletshape.hpp>
#include <osg/Vec3f> #include <osg/Vec3f>
#include <LinearMath/btTransform.h> #include <LinearMath/btTransform.h>
@ -38,7 +40,8 @@ namespace DetourNavigator
public: public:
explicit RecastMeshBuilder(const TileBounds& bounds) noexcept; 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<const Resource::BulletShape> source, const ObjectTransform& objectTransform);
void addObject(const btCompoundShape& shape, const btTransform& transform, const AreaType areaType); void addObject(const btCompoundShape& shape, const btTransform& transform, const AreaType areaType);
@ -63,6 +66,9 @@ namespace DetourNavigator
std::vector<CellWater> mWater; std::vector<CellWater> mWater;
std::vector<Heightfield> mHeightfields; std::vector<Heightfield> mHeightfields;
std::vector<FlatHeightfield> mFlatHeightfields; std::vector<FlatHeightfield> mFlatHeightfields;
std::vector<MeshSource> mSources;
inline void addObject(const btCollisionShape& shape, const btTransform& transform, const AreaType areaType);
void addObject(const btConcaveShape& shape, const btTransform& transform, btTriangleCallback&& callback); void addObject(const btConcaveShape& shape, const btTransform& transform, btTriangleCallback&& callback);

@ -122,7 +122,8 @@ namespace DetourNavigator
{ {
RecastMeshBuilder builder(mTileBounds); RecastMeshBuilder builder(mTileBounds);
using Object = std::tuple< using Object = std::tuple<
osg::ref_ptr<const osg::Referenced>, osg::ref_ptr<const Resource::BulletShapeInstance>,
ObjectTransform,
std::reference_wrapper<const btCollisionShape>, std::reference_wrapper<const btCollisionShape>,
btTransform, btTransform,
AreaType AreaType
@ -139,12 +140,13 @@ namespace DetourNavigator
for (const auto& [k, object] : mObjects) for (const auto& [k, object] : mObjects)
{ {
const RecastMeshObject& impl = object.getImpl(); 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; revision = mRevision;
} }
for (const auto& [holder, shape, transform, areaType] : objects) for (const auto& [instance, objectTransform, shape, transform, areaType] : objects)
builder.addObject(shape, transform, areaType); builder.addObject(shape, transform, areaType, instance->getSource(), objectTransform);
return std::move(builder).create(mGeneration, revision); return std::move(builder).create(mGeneration, revision);
} }

@ -75,7 +75,8 @@ namespace DetourNavigator
RecastMeshObject::RecastMeshObject(const CollisionShape& shape, const btTransform& transform, RecastMeshObject::RecastMeshObject(const CollisionShape& shape, const btTransform& transform,
const AreaType areaType) const AreaType areaType)
: mHolder(shape.getHolder()) : mInstance(shape.getInstance())
, mObjectTransform(shape.getObjectTransform())
, mImpl(shape.getShape(), transform, areaType) , mImpl(shape.getShape(), transform, areaType)
{ {
} }

@ -2,6 +2,9 @@
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHOBJECT_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHOBJECT_H
#include "areatype.hpp" #include "areatype.hpp"
#include "objecttransform.hpp"
#include <components/resource/bulletshape.hpp>
#include <LinearMath/btTransform.h> #include <LinearMath/btTransform.h>
@ -19,17 +22,21 @@ namespace DetourNavigator
class CollisionShape class CollisionShape
{ {
public: public:
CollisionShape(osg::ref_ptr<const osg::Referenced> holder, const btCollisionShape& shape) CollisionShape(osg::ref_ptr<const Resource::BulletShapeInstance> instance, const btCollisionShape& shape,
: mHolder(std::move(holder)) const ObjectTransform& transform)
: mInstance(std::move(instance))
, mShape(shape) , mShape(shape)
, mObjectTransform(transform)
{} {}
const osg::ref_ptr<const osg::Referenced>& getHolder() const { return mHolder; } const osg::ref_ptr<const Resource::BulletShapeInstance>& getInstance() const { return mInstance; }
const btCollisionShape& getShape() const { return mShape; } const btCollisionShape& getShape() const { return mShape; }
const ObjectTransform& getObjectTransform() const { return mObjectTransform; }
private: private:
osg::ref_ptr<const osg::Referenced> mHolder; osg::ref_ptr<const Resource::BulletShapeInstance> mInstance;
std::reference_wrapper<const btCollisionShape> mShape; std::reference_wrapper<const btCollisionShape> mShape;
ObjectTransform mObjectTransform;
}; };
class ChildRecastMeshObject class ChildRecastMeshObject
@ -60,7 +67,7 @@ namespace DetourNavigator
bool update(const btTransform& transform, const AreaType areaType) { return mImpl.update(transform, areaType); } bool update(const btTransform& transform, const AreaType areaType) { return mImpl.update(transform, areaType); }
const osg::ref_ptr<const osg::Referenced>& getHolder() const { return mHolder; } const osg::ref_ptr<const Resource::BulletShapeInstance>& getInstance() const { return mInstance; }
const btCollisionShape& getShape() const { return mImpl.getShape(); } const btCollisionShape& getShape() const { return mImpl.getShape(); }
@ -68,8 +75,11 @@ namespace DetourNavigator
AreaType getAreaType() const { return mImpl.getAreaType(); } AreaType getAreaType() const { return mImpl.getAreaType(); }
const ObjectTransform& getObjectTransform() const { return mObjectTransform; }
private: private:
osg::ref_ptr<const osg::Referenced> mHolder; osg::ref_ptr<const Resource::BulletShapeInstance> mInstance;
ObjectTransform mObjectTransform;
ChildRecastMeshObject mImpl; ChildRecastMeshObject mImpl;
}; };
} }

@ -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 <functional>
#include <memory>
namespace DetourNavigator
{
class RecastMesh;
class RecastMeshProvider
{
public:
RecastMeshProvider(TileCachedRecastMeshManager& impl)
: mImpl(impl)
{}
std::shared_ptr<RecastMesh> getMesh(std::string_view worldspace, const TilePosition& tilePosition) const
{
return mImpl.get().getNewMesh(worldspace, tilePosition);
}
private:
std::reference_wrapper<TileCachedRecastMeshManager> mImpl;
};
}
#endif

@ -0,0 +1,272 @@
#include "serialization.hpp"
#include "dbrefgeometryobject.hpp"
#include "preparednavmeshdata.hpp"
#include "recast.hpp"
#include "recastmesh.hpp"
#include "settings.hpp"
#include <components/serialization/binaryreader.hpp>
#include <components/serialization/binarywriter.hpp>
#include <components/serialization/format.hpp>
#include <components/serialization/sizeaccumulator.hpp>
#include <cstddef>
#include <cstring>
#include <type_traits>
#include <vector>
namespace DetourNavigator
{
namespace
{
template <Serialization::Mode mode>
struct Format : Serialization::Format<mode, Format<mode>>
{
using Serialization::Format<mode, Format<mode>>::operator();
template <class Visitor>
void operator()(Visitor&& visitor, const osg::Vec2i& value) const
{
visitor(*this, value.ptr(), 2);
}
template <class Visitor>
void operator()(Visitor&& visitor, const osg::Vec2f& value) const
{
visitor(*this, value.ptr(), 2);
}
template <class Visitor>
void operator()(Visitor&& visitor, const osg::Vec3f& value) const
{
visitor(*this, value.ptr(), 3);
}
template <class Visitor>
void operator()(Visitor&& visitor, const Water& value) const
{
visitor(*this, value.mCellSize);
visitor(*this, value.mLevel);
}
template <class Visitor>
void operator()(Visitor&& visitor, const CellWater& value) const
{
visitor(*this, value.mCellPosition);
visitor(*this, value.mWater);
}
template <class Visitor>
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 <class Visitor>
void operator()(Visitor&& visitor, const TileBounds& value) const
{
visitor(*this, value.mMin);
visitor(*this, value.mMax);
}
template <class Visitor>
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 <class Visitor>
void operator()(Visitor&& visitor, const FlatHeightfield& value) const
{
visitor(*this, value.mCellPosition);
visitor(*this, value.mCellSize);
visitor(*this, value.mHeight);
}
template <class Visitor>
void operator()(Visitor&& visitor, const RecastMesh& value) const
{
visitor(*this, value.getWater());
visitor(*this, value.getHeightfields());
visitor(*this, value.getFlatHeightfields());
}
template <class Visitor>
void operator()(Visitor&& visitor, const ESM::Position& value) const
{
visitor(*this, value.pos);
visitor(*this, value.rot);
}
template <class Visitor>
void operator()(Visitor&& visitor, const ObjectTransform& value) const
{
visitor(*this, value.mPosition);
visitor(*this, value.mScale);
}
template <class Visitor>
void operator()(Visitor&& visitor, const DbRefGeometryObject& value) const
{
visitor(*this, value.mShapeId);
visitor(*this, value.mObjectTransform);
}
template <class Visitor>
void operator()(Visitor&& visitor, const RecastSettings& settings, const RecastMesh& recastMesh,
const std::vector<DbRefGeometryObject>& dbRefGeometryObjects) const
{
visitor(*this, DetourNavigator::recastMeshMagic);
visitor(*this, DetourNavigator::recastMeshVersion);
visitor(*this, settings);
visitor(*this, recastMesh);
visitor(*this, dbRefGeometryObjects);
}
template <class Visitor, class T>
auto operator()(Visitor&& visitor, T& value) const
-> std::enable_if_t<std::is_same_v<std::decay_t<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 <class Visitor, class T>
auto operator()(Visitor&& visitor, T& value) const
-> std::enable_if_t<std::is_same_v<std::decay_t<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 <class Visitor, class T>
auto operator()(Visitor&& visitor, T& value) const
-> std::enable_if_t<std::is_same_v<std::decay_t<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<std::byte> serialize(const RecastSettings& settings, const RecastMesh& recastMesh,
const std::vector<DbRefGeometryObject>& dbRefGeometryObjects)
{
constexpr Format<Serialization::Mode::Write> format;
Serialization::SizeAccumulator sizeAccumulator;
format(sizeAccumulator, settings, recastMesh, dbRefGeometryObjects);
std::vector<std::byte> result(sizeAccumulator.value());
format(Serialization::BinaryWriter(result.data(), result.data() + result.size()),
settings, recastMesh, dbRefGeometryObjects);
return result;
}
std::vector<std::byte> serialize(const PreparedNavMeshData& value)
{
constexpr Format<Serialization::Mode::Write> format;
Serialization::SizeAccumulator sizeAccumulator;
format(sizeAccumulator, value);
std::vector<std::byte> result(sizeAccumulator.value());
format(Serialization::BinaryWriter(result.data(), result.data() + result.size()), value);
return result;
}
bool deserialize(const std::vector<std::byte>& data, PreparedNavMeshData& value)
{
try
{
constexpr Format<Serialization::Mode::Read> format;
format(Serialization::BinaryReader(data.data(), data.data() + data.size()), value);
return true;
}
catch (const std::exception&)
{
return false;
}
}
}

@ -0,0 +1,29 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SERIALIZATION_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_SERIALIZATION_H
#include <cstddef>
#include <cstdint>
#include <vector>
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<std::byte> serialize(const RecastSettings& settings, const RecastMesh& value,
const std::vector<DbRefGeometryObject>& dbRefGeometryObjects);
std::vector<std::byte> serialize(const PreparedNavMeshData& value);
bool deserialize(const std::vector<std::byte>& data, PreparedNavMeshData& value);
}
#endif

@ -3,43 +3,68 @@
#include <components/settings/settings.hpp> #include <components/settings/settings.hpp>
#include <components/misc/constants.hpp> #include <components/misc/constants.hpp>
#include <algorithm>
namespace DetourNavigator namespace DetourNavigator
{ {
RecastSettings makeRecastSettingsFromSettingsManager()
{
constexpr float epsilon = std::numeric_limits<float>::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::size_t>(std::max(0, ::Settings::Manager::getInt("max polygon path size", "Navigator")));
result.mMaxSmoothPathSize = static_cast<std::size_t>(std::max(0, ::Settings::Manager::getInt("max smooth path size", "Navigator")));
return result;
}
Settings makeSettingsFromSettingsManager() Settings makeSettingsFromSettingsManager()
{ {
Settings navigatorSettings; Settings result;
navigatorSettings.mBorderSize = ::Settings::Manager::getInt("border size", "Navigator"); result.mRecast = makeRecastSettingsFromSettingsManager();
navigatorSettings.mCellHeight = ::Settings::Manager::getFloat("cell height", "Navigator"); result.mDetour = makeDetourSettingsFromSettingsManager();
navigatorSettings.mCellSize = ::Settings::Manager::getFloat("cell size", "Navigator"); result.mMaxTilesNumber = std::max(0, ::Settings::Manager::getInt("max tiles number", "Navigator"));
navigatorSettings.mDetailSampleDist = ::Settings::Manager::getFloat("detail sample dist", "Navigator"); result.mWaitUntilMinDistanceToPlayer = ::Settings::Manager::getInt("wait until min distance to player", "Navigator");
navigatorSettings.mDetailSampleMaxError = ::Settings::Manager::getFloat("detail sample max error", "Navigator"); result.mAsyncNavMeshUpdaterThreads = static_cast<std::size_t>(std::max(0, ::Settings::Manager::getInt("async nav mesh updater threads", "Navigator")));
navigatorSettings.mMaxClimb = Constants::sStepSizeUp; result.mMaxNavMeshTilesCacheSize = static_cast<std::size_t>(std::max(std::int64_t {0}, ::Settings::Manager::getInt64("max nav mesh tiles cache size", "Navigator")));
navigatorSettings.mMaxSimplificationError = ::Settings::Manager::getFloat("max simplification error", "Navigator"); result.mEnableWriteRecastMeshToFile = ::Settings::Manager::getBool("enable write recast mesh to file", "Navigator");
navigatorSettings.mMaxSlope = Constants::sMaxSlope; result.mEnableWriteNavMeshToFile = ::Settings::Manager::getBool("enable write nav mesh to file", "Navigator");
navigatorSettings.mRecastScaleFactor = ::Settings::Manager::getFloat("recast scale factor", "Navigator"); result.mRecastMeshPathPrefix = ::Settings::Manager::getString("recast mesh path prefix", "Navigator");
navigatorSettings.mSwimHeightScale = 0; result.mNavMeshPathPrefix = ::Settings::Manager::getString("nav mesh path prefix", "Navigator");
navigatorSettings.mMaxEdgeLen = ::Settings::Manager::getInt("max edge len", "Navigator"); result.mEnableRecastMeshFileNameRevision = ::Settings::Manager::getBool("enable recast mesh file name revision", "Navigator");
navigatorSettings.mMaxNavMeshQueryNodes = ::Settings::Manager::getInt("max nav mesh query nodes", "Navigator"); result.mEnableNavMeshFileNameRevision = ::Settings::Manager::getBool("enable nav mesh file name revision", "Navigator");
navigatorSettings.mMaxPolys = ::Settings::Manager::getInt("max polygons per tile", "Navigator"); result.mMinUpdateInterval = std::chrono::milliseconds(::Settings::Manager::getInt("min update interval ms", "Navigator"));
navigatorSettings.mMaxTilesNumber = ::Settings::Manager::getInt("max tiles number", "Navigator"); result.mNavMeshVersion = ::Settings::Manager::getInt("nav mesh version", "Navigator");
navigatorSettings.mMaxVertsPerPoly = ::Settings::Manager::getInt("max verts per poly", "Navigator"); result.mEnableNavMeshDiskCache = ::Settings::Manager::getBool("enable nav mesh disk cache", "Navigator");
navigatorSettings.mRegionMergeSize = ::Settings::Manager::getInt("region merge size", "Navigator"); result.mWriteToNavMeshDb = ::Settings::Manager::getBool("write to navmeshdb", "Navigator");
navigatorSettings.mRegionMinSize = ::Settings::Manager::getInt("region min size", "Navigator");
navigatorSettings.mTileSize = ::Settings::Manager::getInt("tile size", "Navigator"); return result;
navigatorSettings.mWaitUntilMinDistanceToPlayer = ::Settings::Manager::getInt("wait until min distance to player", "Navigator");
navigatorSettings.mAsyncNavMeshUpdaterThreads = static_cast<std::size_t>(::Settings::Manager::getInt("async nav mesh updater threads", "Navigator"));
navigatorSettings.mMaxNavMeshTilesCacheSize = static_cast<std::size_t>(::Settings::Manager::getInt("max nav mesh tiles cache size", "Navigator"));
navigatorSettings.mMaxPolygonPathSize = static_cast<std::size_t>(::Settings::Manager::getInt("max polygon path size", "Navigator"));
navigatorSettings.mMaxSmoothPathSize = static_cast<std::size_t>(::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;
} }
} }

@ -6,12 +6,8 @@
namespace DetourNavigator namespace DetourNavigator
{ {
struct Settings struct RecastSettings
{ {
bool mEnableWriteRecastMeshToFile = false;
bool mEnableWriteNavMeshToFile = false;
bool mEnableRecastMeshFileNameRevision = false;
bool mEnableNavMeshFileNameRevision = false;
float mCellHeight = 0; float mCellHeight = 0;
float mCellSize = 0; float mCellSize = 0;
float mDetailSampleDist = 0; float mDetailSampleDist = 0;
@ -23,23 +19,44 @@ namespace DetourNavigator
float mSwimHeightScale = 0; float mSwimHeightScale = 0;
int mBorderSize = 0; int mBorderSize = 0;
int mMaxEdgeLen = 0; int mMaxEdgeLen = 0;
int mMaxNavMeshQueryNodes = 0;
int mMaxPolys = 0;
int mMaxTilesNumber = 0;
int mMaxVertsPerPoly = 0; int mMaxVertsPerPoly = 0;
int mRegionMergeSize = 0; int mRegionMergeArea = 0;
int mRegionMinSize = 0; int mRegionMinArea = 0;
int mTileSize = 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 mWaitUntilMinDistanceToPlayer = 0;
int mMaxTilesNumber = 0;
std::size_t mAsyncNavMeshUpdaterThreads = 0; std::size_t mAsyncNavMeshUpdaterThreads = 0;
std::size_t mMaxNavMeshTilesCacheSize = 0; std::size_t mMaxNavMeshTilesCacheSize = 0;
std::size_t mMaxPolygonPathSize = 0;
std::size_t mMaxSmoothPathSize = 0;
std::string mRecastMeshPathPrefix; std::string mRecastMeshPathPrefix;
std::string mNavMeshPathPrefix; std::string mNavMeshPathPrefix;
std::chrono::milliseconds mMinUpdateInterval; std::chrono::milliseconds mMinUpdateInterval;
std::int64_t mNavMeshVersion = 0;
}; };
RecastSettings makeRecastSettingsFromSettingsManager();
DetourSettings makeDetourSettingsFromSettingsManager();
Settings makeSettingsFromSettingsManager(); Settings makeSettingsFromSettingsManager();
} }

@ -4,12 +4,8 @@
#include "settings.hpp" #include "settings.hpp"
#include "tilebounds.hpp" #include "tilebounds.hpp"
#include "tileposition.hpp" #include "tileposition.hpp"
#include "tilebounds.hpp"
#include <LinearMath/btTransform.h>
#include <osg/Vec2f> #include <osg/Vec2f>
#include <osg/Vec2i>
#include <osg/Vec3f> #include <osg/Vec3f>
#include <algorithm> #include <algorithm>
@ -17,38 +13,31 @@
namespace DetourNavigator namespace DetourNavigator
{ {
inline float getHeight(const Settings& settings,const osg::Vec3f& agentHalfExtents) inline float toNavMeshCoordinates(const RecastSettings& settings, float value)
{
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)
{ {
return value * settings.mRecastScaleFactor; 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; 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()); std::swap(position.y(), position.z());
return position * settings.mRecastScaleFactor; 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; const auto factor = 1.0f / settings.mRecastScaleFactor;
position *= factor; position *= factor;
@ -56,12 +45,12 @@ namespace DetourNavigator
return position; return position;
} }
inline float getTileSize(const Settings& settings) inline float getTileSize(const RecastSettings& settings)
{ {
return static_cast<float>(settings.mTileSize) * settings.mCellSize; return static_cast<float>(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( return TilePosition(
static_cast<int>(std::floor(position.x() / getTileSize(settings))), static_cast<int>(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 { return TileBounds {
osg::Vec2f(tilePosition.x(), tilePosition.y()) * getTileSize(settings), 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<float>(settings.mBorderSize) * settings.mCellSize; return static_cast<float>(settings.mBorderSize) * settings.mCellSize;
} }
inline float getSwimLevel(const Settings& settings, const float waterLevel, const float agentHalfExtentsZ) inline float getRealTileSize(const RecastSettings& settings)
{
return waterLevel - settings.mSwimHeightScale * agentHalfExtentsZ - agentHalfExtentsZ;;
}
inline float getRealTileSize(const Settings& settings)
{ {
return settings.mTileSize * settings.mCellSize / settings.mRecastScaleFactor; return settings.mTileSize * settings.mCellSize / settings.mRecastScaleFactor;
} }
@ -97,7 +81,7 @@ namespace DetourNavigator
return std::floor(std::sqrt(settings.mMaxTilesNumber / osg::PI)) - 1; 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); TileBounds result = makeTileBounds(settings, tilePosition);
const float border = getBorderSize(settings); const float border = getBorderSize(settings);

@ -10,19 +10,34 @@
namespace DetourNavigator namespace DetourNavigator
{ {
TileCachedRecastMeshManager::TileCachedRecastMeshManager(const Settings& settings) TileCachedRecastMeshManager::TileCachedRecastMeshManager(const RecastSettings& settings)
: mSettings(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, bool TileCachedRecastMeshManager::addObject(const ObjectId id, const CollisionShape& shape,
const btTransform& transform, const AreaType areaType) const btTransform& transform, const AreaType areaType)
{ {
std::vector<TilePosition> tilesPositions; std::vector<TilePosition> tilesPositions;
{ {
auto tiles = mTiles.lock(); const std::lock_guard lock(mMutex);
getTilesPositions(shape.getShape(), transform, mSettings, [&] (const TilePosition& tilePosition) 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); tilesPositions.push_back(tilePosition);
}); });
} }
@ -41,10 +56,10 @@ namespace DetourNavigator
return std::nullopt; return std::nullopt;
std::optional<RemovedRecastMeshObject> result; std::optional<RemovedRecastMeshObject> result;
{ {
auto tiles = mTiles.lock(); const std::lock_guard lock(mMutex);
for (const auto& tilePosition : object->second) for (const auto& tilePosition : object->second)
{ {
const auto removed = removeTile(id, tilePosition, tiles.get()); const auto removed = removeTile(id, tilePosition, mTiles);
if (removed && !result) if (removed && !result)
result = removed; result = removed;
} }
@ -62,8 +77,8 @@ namespace DetourNavigator
if (cellSize == std::numeric_limits<int>::max()) if (cellSize == std::numeric_limits<int>::max())
{ {
const auto tiles = mTiles.lock(); const std::lock_guard lock(mMutex);
for (auto& tile : *tiles) for (auto& tile : mTiles)
{ {
if (tile.second->addWater(cellPosition, cellSize, level)) if (tile.second->addWater(cellPosition, cellSize, level))
{ {
@ -77,13 +92,13 @@ namespace DetourNavigator
const btVector3 shift = Misc::Convert::toBullet(getWaterShift3d(cellPosition, cellSize, level)); const btVector3 shift = Misc::Convert::toBullet(getWaterShift3d(cellPosition, cellSize, level));
getTilesPositions(cellSize, shift, mSettings, [&] (const TilePosition& tilePosition) getTilesPositions(cellSize, shift, mSettings, [&] (const TilePosition& tilePosition)
{ {
const auto tiles = mTiles.lock(); const std::lock_guard lock(mMutex);
auto tile = tiles->find(tilePosition); auto tile = mTiles.find(tilePosition);
if (tile == tiles->end()) if (tile == mTiles.end())
{ {
const TileBounds tileBounds = makeRealTileBoundsWithBorder(mSettings, tilePosition); const TileBounds tileBounds = makeRealTileBoundsWithBorder(mSettings, tilePosition);
tile = tiles->emplace(tilePosition, tile = mTiles.emplace_hint(tile, tilePosition,
std::make_shared<CachedRecastMeshManager>(tileBounds, mTilesGeneration)).first; std::make_shared<CachedRecastMeshManager>(tileBounds, mTilesGeneration));
} }
if (tile->second->addWater(cellPosition, cellSize, level)) if (tile->second->addWater(cellPosition, cellSize, level))
{ {
@ -107,14 +122,14 @@ namespace DetourNavigator
std::optional<Water> result; std::optional<Water> result;
for (const auto& tilePosition : object->second) for (const auto& tilePosition : object->second)
{ {
const auto tiles = mTiles.lock(); const std::lock_guard lock(mMutex);
const auto tile = tiles->find(tilePosition); const auto tile = mTiles.find(tilePosition);
if (tile == tiles->end()) if (tile == mTiles.end())
continue; continue;
const auto tileResult = tile->second->removeWater(cellPosition); const auto tileResult = tile->second->removeWater(cellPosition);
if (tile->second->isEmpty()) if (tile->second->isEmpty())
{ {
tiles->erase(tile); mTiles.erase(tile);
++mTilesGeneration; ++mTilesGeneration;
} }
if (tileResult && !result) if (tileResult && !result)
@ -135,13 +150,13 @@ namespace DetourNavigator
getTilesPositions(cellSize, shift, mSettings, [&] (const TilePosition& tilePosition) getTilesPositions(cellSize, shift, mSettings, [&] (const TilePosition& tilePosition)
{ {
const auto tiles = mTiles.lock(); const std::lock_guard lock(mMutex);
auto tile = tiles->find(tilePosition); auto tile = mTiles.find(tilePosition);
if (tile == tiles->end()) if (tile == mTiles.end())
{ {
const TileBounds tileBounds = makeRealTileBoundsWithBorder(mSettings, tilePosition); const TileBounds tileBounds = makeRealTileBoundsWithBorder(mSettings, tilePosition);
tile = tiles->emplace(tilePosition, tile = mTiles.emplace_hint(tile, tilePosition,
std::make_shared<CachedRecastMeshManager>(tileBounds, mTilesGeneration)).first; std::make_shared<CachedRecastMeshManager>(tileBounds, mTilesGeneration));
} }
if (tile->second->addHeightfield(cellPosition, cellSize, shape)) if (tile->second->addHeightfield(cellPosition, cellSize, shape))
{ {
@ -164,14 +179,14 @@ namespace DetourNavigator
std::optional<SizedHeightfieldShape> result; std::optional<SizedHeightfieldShape> result;
for (const auto& tilePosition : object->second) for (const auto& tilePosition : object->second)
{ {
const auto tiles = mTiles.lock(); const std::lock_guard lock(mMutex);
const auto tile = tiles->find(tilePosition); const auto tile = mTiles.find(tilePosition);
if (tile == tiles->end()) if (tile == mTiles.end())
continue; continue;
const auto tileResult = tile->second->removeHeightfield(cellPosition); const auto tileResult = tile->second->removeHeightfield(cellPosition);
if (tile->second->isEmpty()) if (tile->second->isEmpty())
{ {
tiles->erase(tile); mTiles.erase(tile);
++mTilesGeneration; ++mTilesGeneration;
} }
if (tileResult && !result) if (tileResult && !result)
@ -182,20 +197,27 @@ namespace DetourNavigator
return result; return result;
} }
std::shared_ptr<RecastMesh> TileCachedRecastMeshManager::getMesh(const TilePosition& tilePosition) const std::shared_ptr<RecastMesh> 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 manager->getMesh();
return nullptr; return nullptr;
} }
std::shared_ptr<RecastMesh> TileCachedRecastMeshManager::getCachedMesh(const TilePosition& tilePosition) const std::shared_ptr<RecastMesh> 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 manager->getCachedMesh();
return nullptr; return nullptr;
} }
std::shared_ptr<RecastMesh> 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 std::size_t TileCachedRecastMeshManager::getRevision() const
{ {
return mRevision; return mRevision;
@ -203,9 +225,9 @@ namespace DetourNavigator
void TileCachedRecastMeshManager::reportNavMeshChange(const TilePosition& tilePosition, Version recastMeshVersion, Version navMeshVersion) const void TileCachedRecastMeshManager::reportNavMeshChange(const TilePosition& tilePosition, Version recastMeshVersion, Version navMeshVersion) const
{ {
const auto tiles = mTiles.lockConst(); const std::lock_guard lock(mMutex);
const auto it = tiles->find(tilePosition); const auto it = mTiles.find(tilePosition);
if (it == tiles->end()) if (it == mTiles.end())
return; return;
it->second->reportNavMeshChange(recastMeshVersion, navMeshVersion); it->second->reportNavMeshChange(recastMeshVersion, navMeshVersion);
} }
@ -218,8 +240,8 @@ namespace DetourNavigator
if (tile == tiles.end()) if (tile == tiles.end())
{ {
const TileBounds tileBounds = makeRealTileBoundsWithBorder(mSettings, tilePosition); const TileBounds tileBounds = makeRealTileBoundsWithBorder(mSettings, tilePosition);
tile = tiles.emplace(tilePosition, tile = tiles.emplace_hint(tile, tilePosition,
std::make_shared<CachedRecastMeshManager>(tileBounds, mTilesGeneration)).first; std::make_shared<CachedRecastMeshManager>(tileBounds, mTilesGeneration));
} }
return tile->second->addObject(id, shape, transform, areaType); return tile->second->addObject(id, shape, transform, areaType);
} }
@ -246,11 +268,14 @@ namespace DetourNavigator
return tileResult; return tileResult;
} }
std::shared_ptr<CachedRecastMeshManager> TileCachedRecastMeshManager::getManager(const TilePosition& tilePosition) const std::shared_ptr<CachedRecastMeshManager> TileCachedRecastMeshManager::getManager(std::string_view worldspace,
const TilePosition& tilePosition) const
{ {
const auto tiles = mTiles.lockConst(); const std::lock_guard lock(mMutex);
const auto it = tiles->find(tilePosition); if (mWorldspace != worldspace)
if (it == tiles->end()) return nullptr;
const auto it = mTiles.find(tilePosition);
if (it == mTiles.end())
return nullptr; return nullptr;
return it->second; return it->second;
} }

@ -8,8 +8,6 @@
#include "version.hpp" #include "version.hpp"
#include "heightfieldshape.hpp" #include "heightfieldshape.hpp"
#include <components/misc/guarded.hpp>
#include <algorithm> #include <algorithm>
#include <map> #include <map>
#include <mutex> #include <mutex>
@ -20,7 +18,11 @@ namespace DetourNavigator
class TileCachedRecastMeshManager class TileCachedRecastMeshManager
{ {
public: 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, bool addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform,
const AreaType areaType); const AreaType areaType);
@ -36,19 +38,19 @@ namespace DetourNavigator
bool changed = false; bool changed = false;
std::vector<TilePosition> newTiles; std::vector<TilePosition> newTiles;
{ {
auto tiles = mTiles.lock(); const std::lock_guard lock(mMutex);
const auto onTilePosition = [&] (const TilePosition& tilePosition) const auto onTilePosition = [&] (const TilePosition& tilePosition)
{ {
if (std::binary_search(currentTiles.begin(), currentTiles.end(), tilePosition)) if (std::binary_search(currentTiles.begin(), currentTiles.end(), tilePosition))
{ {
newTiles.push_back(tilePosition); newTiles.push_back(tilePosition);
if (updateTile(id, transform, areaType, tilePosition, tiles.get())) if (updateTile(id, transform, areaType, tilePosition, mTiles))
{ {
onChangedTile(tilePosition); onChangedTile(tilePosition);
changed = true; 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); newTiles.push_back(tilePosition);
onChangedTile(tilePosition); onChangedTile(tilePosition);
@ -59,7 +61,7 @@ namespace DetourNavigator
std::sort(newTiles.begin(), newTiles.end()); std::sort(newTiles.begin(), newTiles.end());
for (const auto& tile : currentTiles) 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); onChangedTile(tile);
changed = true; changed = true;
@ -84,14 +86,17 @@ namespace DetourNavigator
std::optional<SizedHeightfieldShape> removeHeightfield(const osg::Vec2i& cellPosition); std::optional<SizedHeightfieldShape> removeHeightfield(const osg::Vec2i& cellPosition);
std::shared_ptr<RecastMesh> getMesh(const TilePosition& tilePosition) const; std::shared_ptr<RecastMesh> getMesh(std::string_view worldspace, const TilePosition& tilePosition) const;
std::shared_ptr<RecastMesh> getCachedMesh(std::string_view worldspace, const TilePosition& tilePosition) const;
std::shared_ptr<RecastMesh> getCachedMesh(const TilePosition& tilePosition) const; std::shared_ptr<RecastMesh> getNewMesh(std::string_view worldspace, const TilePosition& tilePosition) const;
template <class Function> template <class Function>
void forEachTile(Function&& function) const 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); function(tilePosition, *recastMeshManager);
} }
@ -102,8 +107,10 @@ namespace DetourNavigator
private: private:
using TilesMap = std::map<TilePosition, std::shared_ptr<CachedRecastMeshManager>>; using TilesMap = std::map<TilePosition, std::shared_ptr<CachedRecastMeshManager>>;
const Settings& mSettings; const RecastSettings& mSettings;
Misc::ScopeGuarded<TilesMap> mTiles; mutable std::mutex mMutex;
std::string mWorldspace;
TilesMap mTiles;
std::unordered_map<ObjectId, std::vector<TilePosition>> mObjectsTilesPositions; std::unordered_map<ObjectId, std::vector<TilePosition>> mObjectsTilesPositions;
std::map<osg::Vec2i, std::vector<TilePosition>> mWaterTilesPositions; std::map<osg::Vec2i, std::vector<TilePosition>> mWaterTilesPositions;
std::map<osg::Vec2i, std::vector<TilePosition>> mHeightfieldTilesPositions; std::map<osg::Vec2i, std::vector<TilePosition>> mHeightfieldTilesPositions;
@ -119,7 +126,8 @@ namespace DetourNavigator
std::optional<RemovedRecastMeshObject> removeTile(const ObjectId id, const TilePosition& tilePosition, std::optional<RemovedRecastMeshObject> removeTile(const ObjectId id, const TilePosition& tilePosition,
TilesMap& tiles); TilesMap& tiles);
inline std::shared_ptr<CachedRecastMeshManager> getManager(const TilePosition& tilePosition) const; inline std::shared_ptr<CachedRecastMeshManager> getManager(std::string_view worldspace,
const TilePosition& tilePosition) const;
}; };
} }

@ -3,6 +3,8 @@
#include <stdint.h> #include <stdint.h>
#include <tuple>
#include <osg/Vec3f> #include <osg/Vec3f>
namespace ESM namespace ESM
@ -59,6 +61,12 @@ struct Position
{ {
return osg::Vec3f(rot[0], rot[1], rot[2]); 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) #pragma pack(pop)

@ -67,6 +67,11 @@ namespace Convert
{ {
return makeBulletQuaternion(position.rot); return makeBulletQuaternion(position.rot);
} }
inline btTransform makeBulletTransform(const ESM::Position& position)
{
return btTransform(makeBulletQuaternion(position), toBullet(position.asVec3()));
}
} }
} }

@ -7,7 +7,8 @@
#include <QDebug> #include <QDebug>
#include <QCoreApplication> #include <QCoreApplication>
Process::ProcessInvoker::ProcessInvoker() Process::ProcessInvoker::ProcessInvoker(QObject* parent)
: QObject(parent)
{ {
mProcess = new QProcess(this); mProcess = new QProcess(this);
@ -56,6 +57,7 @@ bool Process::ProcessInvoker::startProcess(const QString &name, const QStringLis
// mProcess = new QProcess(this); // mProcess = new QProcess(this);
mName = name; mName = name;
mArguments = arguments; mArguments = arguments;
mIgnoreErrors = false;
QString path(name); QString path(name);
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
@ -151,6 +153,8 @@ bool Process::ProcessInvoker::startProcess(const QString &name, const QStringLis
void Process::ProcessInvoker::processError(QProcess::ProcessError error) void Process::ProcessInvoker::processError(QProcess::ProcessError error)
{ {
if (mIgnoreErrors)
return;
QMessageBox msgBox; QMessageBox msgBox;
msgBox.setWindowTitle(tr("Error running executable")); msgBox.setWindowTitle(tr("Error running executable"));
msgBox.setIcon(QMessageBox::Critical); msgBox.setIcon(QMessageBox::Critical);
@ -166,6 +170,8 @@ void Process::ProcessInvoker::processError(QProcess::ProcessError error)
void Process::ProcessInvoker::processFinished(int exitCode, QProcess::ExitStatus exitStatus) void Process::ProcessInvoker::processFinished(int exitCode, QProcess::ExitStatus exitStatus)
{ {
if (exitCode != 0 || exitStatus == QProcess::CrashExit) { if (exitCode != 0 || exitStatus == QProcess::CrashExit) {
if (mIgnoreErrors)
return;
QString error(mProcess->readAllStandardError()); QString error(mProcess->readAllStandardError());
error.append(tr("\nArguments:\n")); error.append(tr("\nArguments:\n"));
error.append(mArguments.join(" ")); error.append(mArguments.join(" "));
@ -181,3 +187,9 @@ void Process::ProcessInvoker::processFinished(int exitCode, QProcess::ExitStatus
msgBox.exec(); msgBox.exec();
} }
} }
void Process::ProcessInvoker::killProcess()
{
mIgnoreErrors = true;
mProcess->kill();
}

@ -13,7 +13,7 @@ namespace Process
public: public:
ProcessInvoker(); ProcessInvoker(QObject* parent = nullptr);
~ProcessInvoker(); ~ProcessInvoker();
// void setProcessName(const QString &name); // 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); } 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); bool startProcess(const QString &name, const QStringList &arguments, bool detached = false);
void killProcess();
private: private:
QProcess *mProcess; QProcess *mProcess;
QString mName; QString mName;
QStringList mArguments; QStringList mArguments;
bool mIgnoreErrors = false;
private slots: private slots:
void processError(QProcess::ProcessError error); void processError(QProcess::ProcessError error);
void processFinished(int exitCode, QProcess::ExitStatus exitStatus); void processFinished(int exitCode, QProcess::ExitStatus exitStatus);

@ -393,6 +393,8 @@ void StatsHandler::setUpScene(osgViewer::ViewerBase *viewer)
"NavMesh Waiting", "NavMesh Waiting",
"NavMesh Pushed", "NavMesh Pushed",
"NavMesh Processing", "NavMesh Processing",
"NavMesh DbJobs",
"NavMesh DbCacheHitRate",
"NavMesh CacheSize", "NavMesh CacheSize",
"NavMesh UsedTiles", "NavMesh UsedTiles",
"NavMesh CachedTiles", "NavMesh CachedTiles",

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save