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
variables: &engine-targets
targets: "openmw,openmw-iniimporter,openmw-launcher,openmw-wizard"
targets: "openmw,openmw-iniimporter,openmw-launcher,openmw-wizard,openmw-navmeshtool"
package: "Engine"
variables: &cs-targets

@ -108,6 +108,7 @@
Feature #6128: Soft Particles
Feature #6161: Refactor Sky to use shaders and GLES/GL3 friendly
Feature #6162: Refactor GUI to use shaders and to be GLES and GL3+ friendly
Feature #6189: Navigation mesh disk cache
Feature #6199: Support FBO Rendering
Feature #6248: Embedded error marker mesh
Feature #6249: Alpha testing support for Collada

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

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

@ -37,6 +37,7 @@ option(BUILD_DOCS "Build documentation." OFF )
option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF)
option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest" OFF)
option(BUILD_BENCHMARKS "Build benchmarks with Google Benchmark" OFF)
option(BUILD_NAVMESHTOOL "Build navmesh tool" ON)
set(OpenGL_GL_PREFERENCE LEGACY) # Use LEGACY as we use GL2; GLNVD is for GL3 and up.
@ -603,6 +604,10 @@ if (BUILD_BENCHMARKS)
add_subdirectory(apps/benchmarks)
endif()
if (BUILD_NAVMESHTOOL)
add_subdirectory(apps/navmeshtool)
endif()
if (WIN32)
if (MSVC)
if (OPENMW_MP_BUILD)
@ -702,6 +707,10 @@ if (WIN32)
if (BUILD_BENCHMARKS)
set_target_properties(openmw_detournavigator_navmeshtilescache_benchmark PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}")
endif()
if (BUILD_NAVMESHTOOL)
set_target_properties(openmw-navmeshtool PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}")
endif()
endif(MSVC)
# TODO: At some point release builds should not use the console but rather write to a log file
@ -944,6 +953,9 @@ elseif(NOT APPLE)
IF(BUILD_WIZARD)
INSTALL(PROGRAMS "${INSTALL_SOURCE}/openmw-wizard" DESTINATION "${BINDIR}" )
ENDIF(BUILD_WIZARD)
if(BUILD_NAVMESHTOOL)
install(PROGRAMS "${INSTALL_SOURCE}/openmw-navmeshtool" DESTINATION "${BINDIR}" )
endif()
# Install licenses
INSTALL(FILES "files/mygui/DejaVuFontLicense.txt" DESTINATION "${LICDIR}" )

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

@ -1,4 +1,5 @@
#include "datafilespage.hpp"
#include "maindialog.hpp"
#include <QDebug>
@ -8,6 +9,7 @@
#include <QSortFilterProxyModel>
#include <thread>
#include <mutex>
#include <algorithm>
#include <apps/launcher/utils/cellnameloader.hpp>
#include <components/files/configurationmanager.hpp>
@ -24,11 +26,14 @@
const char *Launcher::DataFilesPage::mDefaultContentListName = "Default";
Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings, Config::LauncherSettings &launcherSettings, QWidget *parent)
Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings,
Config::LauncherSettings &launcherSettings, MainDialog *parent)
: QWidget(parent)
, mMainDialog(parent)
, mCfgMgr(cfg)
, mGameSettings(gameSettings)
, mLauncherSettings(launcherSettings)
, mNavMeshToolInvoker(new Process::ProcessInvoker(this))
{
ui.setupUi (this);
setObjectName ("DataFilesPage");
@ -57,8 +62,6 @@ Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config:
void Launcher::DataFilesPage::buildView()
{
ui.verticalLayout->insertWidget (0, mSelector->uiWidget());
QToolButton * refreshButton = mSelector->refreshButton();
//tool buttons
@ -89,6 +92,13 @@ void Launcher::DataFilesPage::buildView()
this, SLOT (slotProfileChangedByUser(QString, QString)));
connect(ui.refreshDataFilesAction, SIGNAL(triggered()),this, SLOT(slotRefreshButtonClicked()));
connect(ui.updateNavMeshButton, SIGNAL(clicked()), this, SLOT(startNavMeshTool()));
connect(ui.cancelNavMeshButton, SIGNAL(clicked()), this, SLOT(killNavMeshTool()));
connect(mNavMeshToolInvoker->getProcess(), SIGNAL(readyReadStandardOutput()), this, SLOT(updateNavMeshProgress()));
connect(mNavMeshToolInvoker->getProcess(), SIGNAL(readyReadStandardError()), this, SLOT(updateNavMeshProgress()));
connect(mNavMeshToolInvoker->getProcess(), SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(navMeshToolFinished(int, QProcess::ExitStatus)));
}
bool Launcher::DataFilesPage::loadSettings()
@ -411,3 +421,55 @@ void Launcher::DataFilesPage::reloadCells(QStringList selectedFiles)
std::sort(cellNamesList.begin(), cellNamesList.end());
emit signalLoadedCellsChanged(cellNamesList);
}
void Launcher::DataFilesPage::startNavMeshTool()
{
mMainDialog->writeSettings();
ui.navMeshLogPlainTextEdit->clear();
ui.navMeshProgressBar->setValue(0);
ui.navMeshProgressBar->setMaximum(1);
if (!mNavMeshToolInvoker->startProcess(QLatin1String("openmw-navmeshtool")))
return;
ui.cancelNavMeshButton->setEnabled(true);
ui.navMeshProgressBar->setEnabled(true);
}
void Launcher::DataFilesPage::killNavMeshTool()
{
mNavMeshToolInvoker->killProcess();
}
void Launcher::DataFilesPage::updateNavMeshProgress()
{
QProcess& process = *mNavMeshToolInvoker->getProcess();
QString text;
while (process.canReadLine())
{
const QByteArray line = process.readLine();
const auto end = std::find_if(line.rbegin(), line.rend(), [] (auto v) { return v != '\n' && v != '\r'; });
text = QString::fromUtf8(line.mid(0, line.size() - (end - line.rbegin())));
ui.navMeshLogPlainTextEdit->appendPlainText(text);
}
const QRegularExpression pattern(R"([\( ](\d+)/(\d+)[\) ])");
QRegularExpressionMatch match = pattern.match(text);
if (!match.hasMatch())
return;
int maximum = match.captured(2).toInt();
if (text.contains("cell"))
maximum *= 100;
ui.navMeshProgressBar->setMaximum(std::max(ui.navMeshProgressBar->maximum(), maximum));
ui.navMeshProgressBar->setValue(match.captured(1).toInt());
}
void Launcher::DataFilesPage::navMeshToolFinished(int exitCode, QProcess::ExitStatus exitStatus)
{
updateNavMeshProgress();
ui.navMeshLogPlainTextEdit->appendPlainText(QString::fromUtf8(mNavMeshToolInvoker->getProcess()->readAll()));
if (exitCode == 0 && exitStatus == QProcess::ExitStatus::NormalExit)
ui.navMeshProgressBar->setValue(ui.navMeshProgressBar->maximum());
ui.cancelNavMeshButton->setEnabled(false);
ui.navMeshProgressBar->setEnabled(false);
}

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

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

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

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

@ -186,8 +186,8 @@ namespace MWWorld
if (Settings::Manager::getBool("enable", "Navigator"))
{
auto navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager();
navigatorSettings.mSwimHeightScale = mSwimHeightScale;
mNavigator = DetourNavigator::makeNavigator(navigatorSettings);
navigatorSettings.mRecast.mSwimHeightScale = mSwimHeightScale;
mNavigator = DetourNavigator::makeNavigator(navigatorSettings, userDataPath);
}
else
{
@ -1524,16 +1524,19 @@ namespace MWWorld
if (const auto object = mPhysics->getObject(door.first))
updateNavigatorObject(*object);
if (mShouldUpdateNavigator)
auto player = getPlayerPtr();
if (mShouldUpdateNavigator && player.getCell() != nullptr)
{
mNavigator->update(getPlayerPtr().getRefData().getPosition().asVec3());
mNavigator->update(player.getRefData().getPosition().asVec3());
mShouldUpdateNavigator = false;
}
}
void World::updateNavigatorObject(const MWPhysics::Object& object)
{
const DetourNavigator::ObjectShapes shapes(object.getShapeInstance());
const MWWorld::Ptr ptr = object.getPtr();
const DetourNavigator::ObjectShapes shapes(object.getShapeInstance(),
DetourNavigator::ObjectTransform {ptr.getRefData().getPosition(), ptr.getCellRef().getScale()});
mShouldUpdateNavigator = mNavigator->updateObject(DetourNavigator::ObjectId(&object), shapes, object.getTransform())
|| mShouldUpdateNavigator;
}

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

@ -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
{
Settings mSettings;
RecastSettings mSettings;
std::vector<TilePosition> mTilesPositions;
CollectTilesPositions mCollect {mTilesPositions};

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

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

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

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

@ -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
{
Settings mSettings;
RecastSettings mSettings;
DetourNavigatorGetTilePositionTest()
{
@ -47,7 +47,7 @@ namespace
struct DetourNavigatorMakeTileBoundsTest : Test
{
Settings mSettings;
RecastSettings mSettings;
DetourNavigatorMakeTileBoundsTest()
{

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

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

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

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

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

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

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

@ -3,6 +3,8 @@
#include <Recast.h>
#include <ostream>
namespace DetourNavigator
{
enum AreaType : unsigned char
@ -21,6 +23,19 @@ namespace DetourNavigator
float mPathgrid = 1.0f;
float mGround = 1.0f;
};
inline std::ostream& operator<<(std::ostream& stream, AreaType value)
{
switch (value)
{
case AreaType_null: return stream << "null";
case AreaType_water: return stream << "water";
case AreaType_door: return stream << "door";
case AreaType_pathgrid: return stream << "pathgrid";
case AreaType_ground: return stream << "ground";
}
return stream << "unknown area type (" << static_cast<std::underlying_type_t<AreaType>>(value) << ")";
}
}
#endif

@ -3,6 +3,9 @@
#include "makenavmesh.hpp"
#include "settings.hpp"
#include "version.hpp"
#include "serialization.hpp"
#include "navmeshdbutils.hpp"
#include "dbrefgeometryobject.hpp"
#include <components/debug/debuglog.hpp>
#include <components/misc/thread.hpp>
@ -15,16 +18,12 @@
#include <algorithm>
#include <numeric>
#include <set>
#include <type_traits>
namespace
namespace DetourNavigator
{
using DetourNavigator::ChangeType;
using DetourNavigator::TilePosition;
using DetourNavigator::UpdateType;
using DetourNavigator::ChangeType;
using DetourNavigator::Job;
using DetourNavigator::JobIt;
namespace
{
int getManhattanDistance(const TilePosition& lhs, const TilePosition& rhs)
{
return std::abs(lhs.x() - rhs.x()) + std::abs(lhs.y() - rhs.y());
@ -41,16 +40,10 @@ namespace
return result;
}
UpdateType getUpdateType(ChangeType changeType) noexcept
{
if (changeType == ChangeType::update)
return UpdateType::Temporary;
return UpdateType::Persistent;
}
auto getPriority(const Job& job) noexcept
{
return std::make_tuple(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
@ -67,19 +60,62 @@ namespace
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
{
return std::make_tuple(job.mAgentHalfExtents, job.mChangedTile);
}
}
namespace DetourNavigator
{
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);
}
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,
const TilePosition& changedTile, ChangeType changeType, int distanceToPlayer,
std::string_view worldspace, const TilePosition& changedTile, ChangeType changeType, int distanceToPlayer,
std::chrono::steady_clock::time_point processTime)
: mAgentHalfExtents(agentHalfExtents)
: mId(getNextJobId())
, mAgentHalfExtents(agentHalfExtents)
, mNavMeshCacheItem(std::move(navMeshCacheItem))
, mWorldspace(worldspace)
, mChangedTile(changedTile)
, mProcessTime(processTime)
, mChangeType(changeType)
@ -89,12 +125,13 @@ namespace DetourNavigator
}
AsyncNavMeshUpdater::AsyncNavMeshUpdater(const Settings& settings, TileCachedRecastMeshManager& recastMeshManager,
OffMeshConnectionsManager& offMeshConnectionsManager)
OffMeshConnectionsManager& offMeshConnectionsManager, std::unique_ptr<NavMeshDb>&& db)
: mSettings(settings)
, mRecastMeshManager(recastMeshManager)
, mOffMeshConnectionsManager(offMeshConnectionsManager)
, mShouldStop()
, mNavMeshTilesCache(settings.mMaxNavMeshTilesCacheSize)
, mDbWorker(makeDbWorker(*this, std::move(db), mSettings))
{
for (std::size_t i = 0; i < mSettings.get().mAsyncNavMeshUpdaterThreads; ++i)
mThreads.emplace_back([&] { process(); });
@ -103,6 +140,8 @@ namespace DetourNavigator
AsyncNavMeshUpdater::~AsyncNavMeshUpdater()
{
mShouldStop = true;
if (mDbWorker != nullptr)
mDbWorker->stop();
std::unique_lock<std::mutex> lock(mMutex);
mWaiting.clear();
mHasJob.notify_all();
@ -111,8 +150,8 @@ namespace DetourNavigator
thread.join();
}
void AsyncNavMeshUpdater::post(const osg::Vec3f& agentHalfExtents,
const SharedNavMeshCacheItem& navMeshCacheItem, const TilePosition& playerTile,
void AsyncNavMeshUpdater::post(const osg::Vec3f& agentHalfExtents, const SharedNavMeshCacheItem& navMeshCacheItem,
const TilePosition& playerTile, std::string_view worldspace,
const std::map<TilePosition, ChangeType>& changedTiles)
{
bool playerTileChanged = false;
@ -126,18 +165,12 @@ namespace DetourNavigator
return;
const dtNavMeshParams params = *navMeshCacheItem->lockConst()->getImpl().getParams();
const int maxTiles = std::min(mSettings.get().mMaxTilesNumber, params.maxTiles);
const std::lock_guard<std::mutex> lock(mMutex);
std::unique_lock lock(mMutex);
if (playerTileChanged)
{
for (JobIt job : mWaiting)
{
job->mDistanceToPlayer = getManhattanDistance(job->mChangedTile, playerTile);
if (!shouldAddTile(job->mChangedTile, playerTile, std::min(mSettings.get().mMaxTilesNumber, params.maxTiles)))
job->mChangeType = ChangeType::remove;
}
}
updateJobs(mWaiting, playerTile, maxTiles);
for (const auto& [changedTile, changeType] : changedTiles)
{
@ -147,8 +180,11 @@ namespace DetourNavigator
? mLastUpdates[std::tie(agentHalfExtents, changedTile)] + mSettings.get().mMinUpdateInterval
: std::chrono::steady_clock::time_point();
const JobIt it = mJobs.emplace(mJobs.end(), agentHalfExtents, navMeshCacheItem, changedTile,
changeType, getManhattanDistance(changedTile, playerTile), processTime);
const JobIt it = mJobs.emplace(mJobs.end(), agentHalfExtents, navMeshCacheItem, worldspace,
changedTile, changeType, getManhattanDistance(changedTile, playerTile), processTime);
Log(Debug::Debug) << "Post job " << it->mId << " for agent=(" << it->mAgentHalfExtents << ")"
<< " changedTile=(" << it->mChangedTile << ")";
if (playerTileChanged)
mWaiting.push_back(it);
@ -164,6 +200,11 @@ namespace DetourNavigator
if (!mWaiting.empty())
mHasJob.notify_all();
lock.unlock();
if (playerTileChanged && mDbWorker != nullptr)
mDbWorker->updateJobs(playerTile, maxTiles);
}
void AsyncNavMeshUpdater::wait(Loading::Listener& listener, WaitConditionType waitConditionType)
@ -241,25 +282,40 @@ namespace DetourNavigator
mProcessingTiles.wait(mProcessed, [] (const auto& v) { return v.empty(); });
}
void AsyncNavMeshUpdater::reportStats(unsigned int frameNumber, osg::Stats& stats) const
AsyncNavMeshUpdater::Stats AsyncNavMeshUpdater::getStats() const
{
std::size_t jobs = 0;
std::size_t waiting = 0;
std::size_t pushed = 0;
Stats result;
{
const std::lock_guard<std::mutex> lock(mMutex);
jobs = mJobs.size();
waiting = mWaiting.size();
pushed = mPushed.size();
result.mJobs = mJobs.size();
result.mWaiting = mWaiting.size();
result.mPushed = mPushed.size();
}
result.mProcessing = mProcessingTiles.lockConst()->size();
if (mDbWorker != nullptr)
result.mDb = mDbWorker->getStats();
result.mCache = mNavMeshTilesCache.getStats();
result.mDbGetTileHits = mDbGetTileHits.load(std::memory_order_relaxed);
return result;
}
stats.setAttribute(frameNumber, "NavMesh Jobs", jobs);
stats.setAttribute(frameNumber, "NavMesh Waiting", waiting);
stats.setAttribute(frameNumber, "NavMesh Pushed", pushed);
stats.setAttribute(frameNumber, "NavMesh Processing", mProcessingTiles.lockConst()->size());
void reportStats(const AsyncNavMeshUpdater::Stats& stats, unsigned int frameNumber, osg::Stats& out)
{
out.setAttribute(frameNumber, "NavMesh Jobs", static_cast<double>(stats.mJobs));
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
@ -272,12 +328,26 @@ namespace DetourNavigator
{
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);
if (processed)
removeJob(job);
if (job->mGeneratedNavMeshData != nullptr)
mDbWorker->enqueueJob(job);
else
removeJob(job);
break;
case JobStatus::Fail:
repost(job);
break;
case JobStatus::MemoryCacheMiss:
{
mDbWorker->enqueueJob(job);
break;
}
}
}
else
cleanupLastUpdates();
@ -290,32 +360,155 @@ namespace DetourNavigator
Log(Debug::Debug) << "Stop navigator jobs processing by thread=" << std::this_thread::get_id();
}
bool AsyncNavMeshUpdater::processJob(const Job& job)
JobStatus AsyncNavMeshUpdater::processJob(Job& job)
{
Log(Debug::Debug) << "Process job for agent=(" << std::fixed << std::setprecision(2) << job.mAgentHalfExtents << ")"
" by thread=" << std::this_thread::get_id();
const auto start = std::chrono::steady_clock::now();
Log(Debug::Debug) << "Processing job " << job.mId << " by thread=" << std::this_thread::get_id();
const auto navMeshCacheItem = job.mNavMeshCacheItem.lock();
if (!navMeshCacheItem)
return true;
return JobStatus::Done;
const auto recastMesh = mRecastMeshManager.get().getMesh(job.mChangedTile);
const auto playerTile = *mPlayerTile.lockConst();
const auto params = *navMeshCacheItem->lockConst()->getImpl().getParams();
if (!shouldAddTile(job.mChangedTile, playerTile, std::min(mSettings.get().mMaxTilesNumber, params.maxTiles)))
{
Log(Debug::Debug) << "Ignore add tile by job " << job.mId << ": too far from player";
navMeshCacheItem->lock()->removeTile(job.mChangedTile);
return JobStatus::Done;
}
switch (job.mState)
{
case JobState::Initial:
return processInitialJob(job, *navMeshCacheItem);
case JobState::WithDbResult:
return processJobWithDbResult(job, *navMeshCacheItem);
}
return JobStatus::Done;
}
JobStatus AsyncNavMeshUpdater::processInitialJob(Job& job, GuardedNavMeshCacheItem& navMeshCacheItem)
{
Log(Debug::Debug) << "Processing initial job " << job.mId;
std::shared_ptr<RecastMesh> 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 status = updateNavMesh(job.mAgentHalfExtents, recastMesh.get(), job.mChangedTile, playerTile,
offMeshConnections, mSettings, navMeshCacheItem, mNavMeshTilesCache, getUpdateType(job.mChangeType));
const UpdateNavMeshStatus status = navMeshCacheItem.lock()->updateTile(job.mChangedTile, std::move(cachedNavMeshData),
makeNavMeshTileData(*preparedNavMeshDataPtr, offMeshConnections, job.mAgentHalfExtents, job.mChangedTile, mSettings.get().mRecast));
if (recastMesh != nullptr)
return handleUpdateNavMeshStatus(status, job, navMeshCacheItem, *recastMesh);
}
JobStatus AsyncNavMeshUpdater::processJobWithDbResult(Job& job, GuardedNavMeshCacheItem& navMeshCacheItem)
{
const Version navMeshVersion = navMeshCacheItem->lockConst()->getVersion();
Log(Debug::Debug) << "Processing job with db result " << job.mId;
std::unique_ptr<PreparedNavMeshData> preparedNavMeshData;
bool generatedNavMeshData = false;
if (job.mCachedTileData.has_value() && job.mCachedTileData->mVersion == mSettings.get().mNavMeshVersion)
{
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,
Version {recastMesh->getGeneration(), recastMesh->getRevision()},
Version {recastMesh.getGeneration(), recastMesh.getRevision()},
navMeshVersion);
}
if (status == UpdateNavMeshStatus::removed || status == UpdateNavMeshStatus::lost)
{
@ -328,23 +521,9 @@ namespace DetourNavigator
mPresentTiles.insert(std::make_tuple(job.mAgentHalfExtents, job.mChangedTile));
}
const auto finish = std::chrono::steady_clock::now();
writeDebugFiles(job, &recastMesh);
writeDebugFiles(job, recastMesh.get());
using FloatMs = std::chrono::duration<float, std::milli>;
const Version version = navMeshCacheItem->lockConst()->getVersion();
Log(Debug::Debug) << std::fixed << std::setprecision(2) <<
"Cache updated for agent=(" << job.mAgentHalfExtents << ")" <<
" tile=" << job.mChangedTile <<
" status=" << status <<
" generation=" << version.mGeneration <<
" revision=" << version.mRevision <<
" time=" << std::chrono::duration_cast<FloatMs>(finish - start).count() << "ms" <<
" thread=" << std::this_thread::get_id();
return isSuccess(status);
return isSuccess(status) ? JobStatus::Done : JobStatus::Fail;
}
JobIt AsyncNavMeshUpdater::getNextJob()
@ -373,8 +552,12 @@ namespace DetourNavigator
mWaiting.pop_front();
if (job->mRecastMesh != nullptr)
return job;
if (!lockTile(job->mAgentHalfExtents, job->mChangedTile))
{
Log(Debug::Debug) << "Failed to lock tile by " << job->mId;
++job->mTryNumber;
insertPrioritizedJob(job, mWaiting);
return mJobs.end();
@ -404,7 +587,7 @@ namespace DetourNavigator
}
if (recastMesh && mSettings.get().mEnableWriteRecastMeshToFile)
writeToFile(*recastMesh, mSettings.get().mRecastMeshPathPrefix + std::to_string(job.mChangedTile.x())
+ "_" + std::to_string(job.mChangedTile.y()) + "_", recastMeshRevision, mSettings);
+ "_" + std::to_string(job.mChangedTile.y()) + "_", recastMeshRevision, mSettings.get().mRecast);
if (mSettings.get().mEnableWriteNavMeshToFile)
if (const auto shared = job.mNavMeshCacheItem.lock())
writeToFile(shared->lockConst()->getImpl(), mSettings.get().mNavMeshPathPrefix, navMeshRevision);
@ -412,6 +595,8 @@ namespace DetourNavigator
void AsyncNavMeshUpdater::repost(JobIt job)
{
unlockTile(job->mAgentHalfExtents, job->mChangedTile);
if (mShouldStop || job->mTryNumber > 2)
return;
@ -430,17 +615,15 @@ namespace DetourNavigator
bool AsyncNavMeshUpdater::lockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile)
{
if (mSettings.get().mAsyncNavMeshUpdaterThreads <= 1)
return true;
Log(Debug::Debug) << "Locking tile agent=(" << agentHalfExtents << ") changedTile=(" << changedTile << ")";
return mProcessingTiles.lock()->emplace(agentHalfExtents, changedTile).second;
}
void AsyncNavMeshUpdater::unlockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile)
{
if (mSettings.get().mAsyncNavMeshUpdaterThreads <= 1)
return;
auto locked = mProcessingTiles.lock();
locked->erase(std::tie(agentHalfExtents, changedTile));
Log(Debug::Debug) << "Unlocked tile agent=(" << agentHalfExtents << ") changedTile=(" << changedTile << ")";
if (locked->empty())
mProcessed.notify_all();
}
@ -466,9 +649,201 @@ namespace DetourNavigator
}
}
void AsyncNavMeshUpdater::enqueueJob(JobIt job)
{
Log(Debug::Debug) << "Enqueueing job " << job->mId << " by thread=" << std::this_thread::get_id();
const std::lock_guard lock(mMutex);
insertPrioritizedJob(job, mWaiting);
mHasJob.notify_all();
}
void AsyncNavMeshUpdater::removeJob(JobIt job)
{
Log(Debug::Debug) << "Removing job " << job->mId << " by thread=" << std::this_thread::get_id();
const std::lock_guard lock(mMutex);
mJobs.erase(job);
}
void DbJobQueue::push(JobIt job)
{
const std::lock_guard lock(mMutex);
insertPrioritizedDbJob(job, mJobs);
mHasJob.notify_all();
}
std::optional<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 "navmeshtilescache.hpp"
#include "waitconditiontype.hpp"
#include "navmeshdb.hpp"
#include <osg/Vec3f>
@ -20,6 +21,7 @@
#include <thread>
#include <tuple>
#include <list>
#include <optional>
class dtNavMesh;
@ -53,37 +55,150 @@ namespace DetourNavigator
return stream << "ChangeType::" << static_cast<int>(value);
}
enum class JobState
{
Initial,
WithDbResult,
};
struct Job
{
const std::size_t mId;
const osg::Vec3f mAgentHalfExtents;
const std::weak_ptr<GuardedNavMeshCacheItem> mNavMeshCacheItem;
const std::string mWorldspace;
const TilePosition mChangedTile;
const std::chrono::steady_clock::time_point mProcessTime;
unsigned mTryNumber = 0;
ChangeType mChangeType;
int mDistanceToPlayer;
const int mDistanceToOrigin;
JobState mState = JobState::Initial;
std::vector<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,
const TilePosition& changedTile, ChangeType changeType, int distanceToPlayer,
std::string_view worldspace, const TilePosition& changedTile, ChangeType changeType, int distanceToPlayer,
std::chrono::steady_clock::time_point processTime);
};
using JobIt = std::list<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
{
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,
OffMeshConnectionsManager& offMeshConnectionsManager);
OffMeshConnectionsManager& offMeshConnectionsManager, std::unique_ptr<NavMeshDb>&& db);
~AsyncNavMeshUpdater();
void post(const osg::Vec3f& agentHalfExtents, const SharedNavMeshCacheItem& mNavMeshCacheItem,
const TilePosition& playerTile, const std::map<TilePosition, ChangeType>& changedTiles);
void post(const osg::Vec3f& agentHalfExtents, const SharedNavMeshCacheItem& navMeshCacheItem,
const TilePosition& playerTile, std::string_view worldspace,
const std::map<TilePosition, ChangeType>& changedTiles);
void wait(Loading::Listener& listener, WaitConditionType waitConditionType);
void reportStats(unsigned int frameNumber, osg::Stats& stats) const;
Stats getStats() const;
void enqueueJob(JobIt job);
void removeJob(JobIt job);
private:
std::reference_wrapper<const Settings> mSettings;
@ -103,14 +218,21 @@ namespace DetourNavigator
std::map<std::tuple<osg::Vec3f, TilePosition>, std::chrono::steady_clock::time_point> mLastUpdates;
std::set<std::tuple<osg::Vec3f, TilePosition>> mPresentTiles;
std::vector<std::thread> mThreads;
std::unique_ptr<DbWorker> mDbWorker;
std::atomic_size_t mDbGetTileHits {0};
void process() noexcept;
bool processJob(const Job& job);
JobStatus processJob(Job& job);
JobIt getNextJob();
inline JobStatus processInitialJob(Job& job, GuardedNavMeshCacheItem& navMeshCacheItem);
inline JobStatus processJobWithDbResult(Job& job, GuardedNavMeshCacheItem& navMeshCacheItem);
inline JobStatus handleUpdateNavMeshStatus(UpdateNavMeshStatus status, const Job& job,
const GuardedNavMeshCacheItem& navMeshCacheItem, const RecastMesh& recastMesh);
JobIt getJob(std::deque<JobIt>& jobs, bool changeLastUpdate);
JobIt getNextJob();
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);
void waitUntilAllJobsDone();
inline void removeJob(JobIt job);
};
void reportStats(const AsyncNavMeshUpdater::Stats& stats, unsigned int frameNumber, osg::Stats& out);
}
#endif

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

@ -35,6 +35,8 @@ namespace DetourNavigator
std::shared_ptr<RecastMesh> getCachedMesh() const;
std::shared_ptr<RecastMesh> getNewMesh() const;
bool isEmpty() const;
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
{
void writeToFile(const RecastMesh& recastMesh, const std::string& pathPrefix, const std::string& revision, const Settings& settings)
void writeToFile(const RecastMesh& recastMesh, const std::string& pathPrefix,
const std::string& revision, const RecastSettings& settings)
{
const auto path = pathPrefix + "recastmesh" + revision + ".obj";
boost::filesystem::ofstream file(boost::filesystem::path(path), std::ios::out);

@ -3,6 +3,7 @@
#include "tilebounds.hpp"
#include "status.hpp"
#include "recastmesh.hpp"
#include <osg/io_utils>
@ -39,10 +40,40 @@ namespace DetourNavigator
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;
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);
}

@ -10,7 +10,7 @@
namespace DetourNavigator
{
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;
if (!initNavMeshQuery(navMeshQuery, navMesh, settings.mMaxNavMeshQueryNodes))

@ -10,10 +10,10 @@ class dtNavMesh;
namespace DetourNavigator
{
struct Settings;
struct DetourSettings;
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

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

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

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

@ -5,10 +5,15 @@
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();
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()

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

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

@ -5,6 +5,7 @@
#include "navmeshmanager.hpp"
#include <set>
#include <memory>
namespace DetourNavigator
{
@ -15,12 +16,14 @@ namespace DetourNavigator
* @brief Navigator constructor initializes all internal data. Constructed object is ready to build a scene.
* @param settings allows to customize navigator work. Constructor is only place to set navigator settings.
*/
explicit NavigatorImpl(const Settings& settings);
explicit NavigatorImpl(const Settings& settings, std::unique_ptr<NavMeshDb>&& db);
void addAgent(const osg::Vec3f& agentHalfExtents) override;
void removeAgent(const osg::Vec3f& agentHalfExtents) override;
void setWorldspace(std::string_view worldspace) override;
bool addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) override;
bool addObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform) override;

@ -19,6 +19,8 @@ namespace DetourNavigator
void removeAgent(const osg::Vec3f& /*agentHalfExtents*/) override {}
void setWorldspace(std::string_view /*worldspace*/) override {}
bool addObject(const ObjectId /*id*/, const ObjectShapes& /*shapes*/, const btTransform& /*transform*/) override
{
return false;

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

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

@ -50,7 +50,8 @@ namespace DetourNavigator
{
return UpdateNavMeshStatus::ignored;
}
const auto removed = ::removeTile(*mImpl, position);
bool removed = ::removeTile(*mImpl, position);
removed = mEmptyTiles.erase(position) > 0 || removed;
const auto addStatus = addTile(*mImpl, navMeshData.mValue.get(), navMeshData.mSize);
if (dtStatusSucceed(addStatus))
{
@ -82,7 +83,8 @@ namespace DetourNavigator
UpdateNavMeshStatus NavMeshCacheItem::removeTile(const TilePosition& position)
{
const auto removed = ::removeTile(*mImpl, position);
bool removed = ::removeTile(*mImpl, position);
removed = mEmptyTiles.erase(position) > 0 || removed;
if (removed)
{
mUsedTiles.erase(position);
@ -90,4 +92,21 @@ namespace DetourNavigator
}
return UpdateNavMeshStatusBuilder().removed(removed).getResult();
}
UpdateNavMeshStatus NavMeshCacheItem::markAsEmpty(const TilePosition& position)
{
bool removed = ::removeTile(*mImpl, position);
removed = mEmptyTiles.insert(position).second || removed;
if (removed)
{
mUsedTiles.erase(position);
++mVersion.mRevision;
}
return UpdateNavMeshStatusBuilder().removed(removed).getResult();
}
bool NavMeshCacheItem::isEmptyTile(const TilePosition& position) const
{
return mEmptyTiles.find(position) != mEmptyTiles.end();
}
}

@ -12,6 +12,7 @@
#include <map>
#include <ostream>
#include <set>
struct dtMeshTile;
@ -147,6 +148,10 @@ namespace DetourNavigator
UpdateNavMeshStatus removeTile(const TilePosition& position);
UpdateNavMeshStatus markAsEmpty(const TilePosition& position);
bool isEmptyTile(const TilePosition& position) const;
template <class Function>
void forEachUsedTile(Function&& function) const
{
@ -166,6 +171,7 @@ namespace DetourNavigator
NavMeshPtr mImpl;
Version mVersion;
std::map<TilePosition, Tile> mUsedTiles;
std::set<TilePosition> mEmptyTiles;
};
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
{
NavMeshManager::NavMeshManager(const Settings& settings)
NavMeshManager::NavMeshManager(const Settings& settings, std::unique_ptr<NavMeshDb>&& db)
: mSettings(settings)
, mRecastMeshManager(settings)
, mOffMeshConnectionsManager(settings)
, mAsyncNavMeshUpdater(settings, mRecastMeshManager, mOffMeshConnectionsManager)
, mRecastMeshManager(settings.mRecast)
, mOffMeshConnectionsManager(settings.mRecast)
, mAsyncNavMeshUpdater(settings, mRecastMeshManager, mOffMeshConnectionsManager, std::move(db))
{}
void NavMeshManager::setWorldspace(std::string_view worldspace)
{
if (worldspace == mWorldspace)
return;
mRecastMeshManager.setWorldspace(worldspace);
for (auto& [agent, cache] : mCache)
cache = std::make_shared<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), ++mGenerationCounter);
mWorldspace = worldspace;
}
bool NavMeshManager::addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform,
const AreaType areaType)
{
@ -140,8 +150,8 @@ namespace DetourNavigator
{
mOffMeshConnectionsManager.add(id, OffMeshConnection {start, end, areaType});
const auto startTilePosition = getTilePosition(mSettings, start);
const auto endTilePosition = getTilePosition(mSettings, end);
const auto startTilePosition = getTilePosition(mSettings.mRecast, start);
const auto endTilePosition = getTilePosition(mSettings.mRecast, end);
addChangedTile(startTilePosition, ChangeType::add);
@ -158,7 +168,7 @@ namespace DetourNavigator
void NavMeshManager::update(const osg::Vec3f& playerPosition, const osg::Vec3f& agentHalfExtents)
{
const auto playerTile = getTilePosition(mSettings, toNavMeshCoordinates(mSettings, playerPosition));
const auto playerTile = getTilePosition(mSettings.mRecast, toNavMeshCoordinates(mSettings.mRecast, playerPosition));
auto& lastRevision = mLastRecastMeshManagerRevision[agentHalfExtents];
auto lastPlayerTile = mPlayerTile.find(agentHalfExtents);
if (lastRevision == mRecastMeshManager.getRevision() && lastPlayerTile != mPlayerTile.end()
@ -201,14 +211,14 @@ namespace DetourNavigator
const auto shouldAdd = shouldAddTile(tile, playerTile, maxTiles);
const auto presentInNavMesh = bool(navMesh.getTileAt(tile.x(), tile.y(), 0));
if (shouldAdd && !presentInNavMesh)
tilesToPost.insert(std::make_pair(tile, ChangeType::add));
tilesToPost.insert(std::make_pair(tile, locked->isEmptyTile(tile) ? ChangeType::update : ChangeType::add));
else if (!shouldAdd && presentInNavMesh)
tilesToPost.insert(std::make_pair(tile, ChangeType::mixed));
else
recastMeshManager.reportNavMeshChange(recastMeshManager.getVersion(), Version {0, 0});
});
}
mAsyncNavMeshUpdater.post(agentHalfExtents, cached, playerTile, tilesToPost);
mAsyncNavMeshUpdater.post(agentHalfExtents, cached, playerTile, mRecastMeshManager.getWorldspace(), tilesToPost);
if (changedTiles != mChangedTiles.end())
changedTiles->second.clear();
Log(Debug::Debug) << "Cache update posted for agent=" << agentHalfExtents <<
@ -233,7 +243,7 @@ namespace DetourNavigator
void NavMeshManager::reportStats(unsigned int frameNumber, osg::Stats& stats) const
{
mAsyncNavMeshUpdater.reportStats(frameNumber, stats);
DetourNavigator::reportStats(mAsyncNavMeshUpdater.getStats(), frameNumber, stats);
}
RecastMeshTiles NavMeshManager::getRecastMeshTiles() const
@ -241,9 +251,10 @@ namespace DetourNavigator
std::vector<TilePosition> tiles;
mRecastMeshManager.forEachTile(
[&tiles] (const TilePosition& tile, const CachedRecastMeshManager&) { tiles.push_back(tile); });
const std::string worldspace = mRecastMeshManager.getWorldspace();
RecastMeshTiles result;
for (const TilePosition& tile : tiles)
if (auto mesh = mRecastMeshManager.getCachedMesh(tile))
if (auto mesh = mRecastMeshManager.getCachedMesh(worldspace, tile))
result.emplace(tile, std::move(mesh));
return result;
}
@ -251,7 +262,7 @@ namespace DetourNavigator
void NavMeshManager::addChangedTiles(const btCollisionShape& shape, const btTransform& transform,
const ChangeType changeType)
{
getTilesPositions(shape, transform, mSettings,
getTilesPositions(shape, transform, mSettings.mRecast,
[&] (const TilePosition& v) { addChangedTile(v, changeType); });
}
@ -261,7 +272,7 @@ namespace DetourNavigator
if (cellSize == std::numeric_limits<int>::max())
return;
getTilesPositions(cellSize, shift, mSettings,
getTilesPositions(cellSize, shift, mSettings.mRecast,
[&] (const TilePosition& v) { addChangedTile(v, changeType); });
}

@ -22,7 +22,9 @@ namespace DetourNavigator
class NavMeshManager
{
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,
const AreaType areaType);
@ -62,6 +64,7 @@ namespace DetourNavigator
private:
const Settings& mSettings;
std::string mWorldspace;
TileCachedRecastMeshManager mRecastMeshManager;
OffMeshConnectionsManager mOffMeshConnectionsManager;
AsyncNavMeshUpdater mAsyncNavMeshUpdater;

@ -79,12 +79,11 @@ namespace DetourNavigator
return result;
}
void NavMeshTilesCache::reportStats(unsigned int frameNumber, osg::Stats& out) const
void reportStats(const NavMeshTilesCache::Stats& stats, unsigned int frameNumber, osg::Stats& out)
{
const Stats stats = getStats();
out.setAttribute(frameNumber, "NavMesh CacheSize", stats.mNavMeshCacheSize);
out.setAttribute(frameNumber, "NavMesh UsedTiles", stats.mUsedNavMeshTiles);
out.setAttribute(frameNumber, "NavMesh CachedTiles", stats.mCachedNavMeshTiles);
out.setAttribute(frameNumber, "NavMesh CacheSize", static_cast<double>(stats.mNavMeshCacheSize));
out.setAttribute(frameNumber, "NavMesh UsedTiles", static_cast<double>(stats.mUsedNavMeshTiles));
out.setAttribute(frameNumber, "NavMesh CachedTiles", static_cast<double>(stats.mCachedNavMeshTiles));
if (stats.mGetCount > 0)
out.setAttribute(frameNumber, "NavMesh CacheHitRate", static_cast<double>(stats.mHitCount) / stats.mGetCount * 100.0);
}

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

@ -15,6 +15,11 @@ namespace DetourNavigator
{
}
explicit ObjectId(std::size_t value) noexcept
: mValue(value)
{
}
std::size_t value() const noexcept
{
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
{
OffMeshConnectionsManager::OffMeshConnectionsManager(const Settings& settings)
OffMeshConnectionsManager::OffMeshConnectionsManager(const RecastSettings& settings)
: mSettings(settings)
{}
@ -65,11 +65,11 @@ namespace DetourNavigator
return removed;
}
std::vector<OffMeshConnection> OffMeshConnectionsManager::get(const TilePosition& tilePosition)
std::vector<OffMeshConnection> OffMeshConnectionsManager::get(const TilePosition& tilePosition) const
{
std::vector<OffMeshConnection> result;
const auto values = mValues.lock();
const auto values = mValues.lockConst();
const auto itByTilePosition = values->mByTilePosition.find(tilePosition);

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

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

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

@ -10,7 +10,7 @@
namespace DetourNavigator
{
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;
if (!initNavMeshQuery(navMeshQuery, navMesh, settings.mMaxNavMeshQueryNodes))

@ -10,10 +10,10 @@ class dtNavMesh;
namespace DetourNavigator
{
struct Settings;
struct DetourSettings;
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

@ -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
#include <Recast.h>
#include <RecastAlloc.h>
#include <cstddef>
#include <type_traits>
namespace DetourNavigator
{
@ -46,6 +48,25 @@ namespace DetourNavigator
{
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

@ -19,13 +19,15 @@ namespace DetourNavigator
}
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)
, mRevision(revision)
, mMesh(std::move(mesh))
, mWater(std::move(water))
, mHeightfields(std::move(heightfields))
, mFlatHeightfields(std::move(flatHeightfields))
, mMeshSources(std::move(meshSources))
{
mWater.shrink_to_fit();
mHeightfields.shrink_to_fit();

@ -4,8 +4,10 @@
#include "areatype.hpp"
#include "bounds.hpp"
#include "tilebounds.hpp"
#include "objecttransform.hpp"
#include <components/bullethelpers/operators.hpp>
#include <components/resource/bulletshape.hpp>
#include <osg/Vec3f>
#include <osg/Vec2i>
@ -119,11 +121,19 @@ namespace DetourNavigator
return tie(lhs) < tie(rhs);
}
struct MeshSource
{
osg::ref_ptr<const Resource::BulletShape> mShape;
ObjectTransform mObjectTransform;
AreaType mAreaType;
};
class RecastMesh
{
public:
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
{
@ -152,6 +162,8 @@ namespace DetourNavigator
return mFlatHeightfields;
}
const std::vector<MeshSource>& getMeshSources() const noexcept { return mMeshSources; }
private:
std::size_t mGeneration;
std::size_t mRevision;
@ -159,6 +171,7 @@ namespace DetourNavigator
std::vector<CellWater> mWater;
std::vector<Heightfield> mHeightfields;
std::vector<FlatHeightfield> mFlatHeightfields;
std::vector<MeshSource> mMeshSources;
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,
const AreaType areaType)
{
@ -261,7 +268,8 @@ namespace DetourNavigator
std::sort(mWater.begin(), mWater.end());
Mesh mesh = makeMesh(std::move(mTriangles));
return std::make_shared<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,

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

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

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

@ -2,6 +2,9 @@
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHOBJECT_H
#include "areatype.hpp"
#include "objecttransform.hpp"
#include <components/resource/bulletshape.hpp>
#include <LinearMath/btTransform.h>
@ -19,17 +22,21 @@ namespace DetourNavigator
class CollisionShape
{
public:
CollisionShape(osg::ref_ptr<const osg::Referenced> holder, const btCollisionShape& shape)
: mHolder(std::move(holder))
CollisionShape(osg::ref_ptr<const Resource::BulletShapeInstance> instance, const btCollisionShape& shape,
const ObjectTransform& transform)
: mInstance(std::move(instance))
, 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 ObjectTransform& getObjectTransform() const { return mObjectTransform; }
private:
osg::ref_ptr<const osg::Referenced> mHolder;
osg::ref_ptr<const Resource::BulletShapeInstance> mInstance;
std::reference_wrapper<const btCollisionShape> mShape;
ObjectTransform mObjectTransform;
};
class ChildRecastMeshObject
@ -60,7 +67,7 @@ namespace DetourNavigator
bool update(const btTransform& transform, const AreaType areaType) { return mImpl.update(transform, areaType); }
const osg::ref_ptr<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(); }
@ -68,8 +75,11 @@ namespace DetourNavigator
AreaType getAreaType() const { return mImpl.getAreaType(); }
const ObjectTransform& getObjectTransform() const { return mObjectTransform; }
private:
osg::ref_ptr<const osg::Referenced> mHolder;
osg::ref_ptr<const Resource::BulletShapeInstance> mInstance;
ObjectTransform mObjectTransform;
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/misc/constants.hpp>
#include <algorithm>
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 navigatorSettings;
navigatorSettings.mBorderSize = ::Settings::Manager::getInt("border size", "Navigator");
navigatorSettings.mCellHeight = ::Settings::Manager::getFloat("cell height", "Navigator");
navigatorSettings.mCellSize = ::Settings::Manager::getFloat("cell size", "Navigator");
navigatorSettings.mDetailSampleDist = ::Settings::Manager::getFloat("detail sample dist", "Navigator");
navigatorSettings.mDetailSampleMaxError = ::Settings::Manager::getFloat("detail sample max error", "Navigator");
navigatorSettings.mMaxClimb = Constants::sStepSizeUp;
navigatorSettings.mMaxSimplificationError = ::Settings::Manager::getFloat("max simplification error", "Navigator");
navigatorSettings.mMaxSlope = Constants::sMaxSlope;
navigatorSettings.mRecastScaleFactor = ::Settings::Manager::getFloat("recast scale factor", "Navigator");
navigatorSettings.mSwimHeightScale = 0;
navigatorSettings.mMaxEdgeLen = ::Settings::Manager::getInt("max edge len", "Navigator");
navigatorSettings.mMaxNavMeshQueryNodes = ::Settings::Manager::getInt("max nav mesh query nodes", "Navigator");
navigatorSettings.mMaxPolys = ::Settings::Manager::getInt("max polygons per tile", "Navigator");
navigatorSettings.mMaxTilesNumber = ::Settings::Manager::getInt("max tiles number", "Navigator");
navigatorSettings.mMaxVertsPerPoly = ::Settings::Manager::getInt("max verts per poly", "Navigator");
navigatorSettings.mRegionMergeSize = ::Settings::Manager::getInt("region merge size", "Navigator");
navigatorSettings.mRegionMinSize = ::Settings::Manager::getInt("region min size", "Navigator");
navigatorSettings.mTileSize = ::Settings::Manager::getInt("tile size", "Navigator");
navigatorSettings.mWaitUntilMinDistanceToPlayer = ::Settings::Manager::getInt("wait until min distance to player", "Navigator");
navigatorSettings.mAsyncNavMeshUpdaterThreads = static_cast<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;
Settings result;
result.mRecast = makeRecastSettingsFromSettingsManager();
result.mDetour = makeDetourSettingsFromSettingsManager();
result.mMaxTilesNumber = std::max(0, ::Settings::Manager::getInt("max tiles number", "Navigator"));
result.mWaitUntilMinDistanceToPlayer = ::Settings::Manager::getInt("wait until min distance to player", "Navigator");
result.mAsyncNavMeshUpdaterThreads = static_cast<std::size_t>(std::max(0, ::Settings::Manager::getInt("async nav mesh updater threads", "Navigator")));
result.mMaxNavMeshTilesCacheSize = static_cast<std::size_t>(std::max(std::int64_t {0}, ::Settings::Manager::getInt64("max nav mesh tiles cache size", "Navigator")));
result.mEnableWriteRecastMeshToFile = ::Settings::Manager::getBool("enable write recast mesh to file", "Navigator");
result.mEnableWriteNavMeshToFile = ::Settings::Manager::getBool("enable write nav mesh to file", "Navigator");
result.mRecastMeshPathPrefix = ::Settings::Manager::getString("recast mesh path prefix", "Navigator");
result.mNavMeshPathPrefix = ::Settings::Manager::getString("nav mesh path prefix", "Navigator");
result.mEnableRecastMeshFileNameRevision = ::Settings::Manager::getBool("enable recast mesh file name revision", "Navigator");
result.mEnableNavMeshFileNameRevision = ::Settings::Manager::getBool("enable nav mesh file name revision", "Navigator");
result.mMinUpdateInterval = std::chrono::milliseconds(::Settings::Manager::getInt("min update interval ms", "Navigator"));
result.mNavMeshVersion = ::Settings::Manager::getInt("nav mesh version", "Navigator");
result.mEnableNavMeshDiskCache = ::Settings::Manager::getBool("enable nav mesh disk cache", "Navigator");
result.mWriteToNavMeshDb = ::Settings::Manager::getBool("write to navmeshdb", "Navigator");
return result;
}
}

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

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

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

@ -8,8 +8,6 @@
#include "version.hpp"
#include "heightfieldshape.hpp"
#include <components/misc/guarded.hpp>
#include <algorithm>
#include <map>
#include <mutex>
@ -20,7 +18,11 @@ namespace DetourNavigator
class TileCachedRecastMeshManager
{
public:
TileCachedRecastMeshManager(const Settings& settings);
explicit TileCachedRecastMeshManager(const RecastSettings& settings);
std::string getWorldspace() const;
void setWorldspace(std::string_view worldspace);
bool addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform,
const AreaType areaType);
@ -36,19 +38,19 @@ namespace DetourNavigator
bool changed = false;
std::vector<TilePosition> newTiles;
{
auto tiles = mTiles.lock();
const std::lock_guard lock(mMutex);
const auto onTilePosition = [&] (const TilePosition& tilePosition)
{
if (std::binary_search(currentTiles.begin(), currentTiles.end(), tilePosition))
{
newTiles.push_back(tilePosition);
if (updateTile(id, transform, areaType, tilePosition, tiles.get()))
if (updateTile(id, transform, areaType, tilePosition, mTiles))
{
onChangedTile(tilePosition);
changed = true;
}
}
else if (addTile(id, shape, transform, areaType, tilePosition, tiles.get()))
else if (addTile(id, shape, transform, areaType, tilePosition, mTiles))
{
newTiles.push_back(tilePosition);
onChangedTile(tilePosition);
@ -59,7 +61,7 @@ namespace DetourNavigator
std::sort(newTiles.begin(), newTiles.end());
for (const auto& tile : currentTiles)
{
if (!std::binary_search(newTiles.begin(), newTiles.end(), tile) && removeTile(id, tile, tiles.get()))
if (!std::binary_search(newTiles.begin(), newTiles.end(), tile) && removeTile(id, tile, mTiles))
{
onChangedTile(tile);
changed = true;
@ -84,14 +86,17 @@ namespace DetourNavigator
std::optional<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>
void forEachTile(Function&& function) const
{
for (auto& [tilePosition, recastMeshManager] : *mTiles.lockConst())
const std::lock_guard lock(mMutex);
for (auto& [tilePosition, recastMeshManager] : mTiles)
function(tilePosition, *recastMeshManager);
}
@ -102,8 +107,10 @@ namespace DetourNavigator
private:
using TilesMap = std::map<TilePosition, std::shared_ptr<CachedRecastMeshManager>>;
const Settings& mSettings;
Misc::ScopeGuarded<TilesMap> mTiles;
const RecastSettings& mSettings;
mutable std::mutex mMutex;
std::string mWorldspace;
TilesMap mTiles;
std::unordered_map<ObjectId, std::vector<TilePosition>> mObjectsTilesPositions;
std::map<osg::Vec2i, std::vector<TilePosition>> mWaterTilesPositions;
std::map<osg::Vec2i, std::vector<TilePosition>> mHeightfieldTilesPositions;
@ -119,7 +126,8 @@ namespace DetourNavigator
std::optional<RemovedRecastMeshObject> removeTile(const ObjectId id, const TilePosition& tilePosition,
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 <tuple>
#include <osg/Vec3f>
namespace ESM
@ -59,6 +61,12 @@ struct Position
{
return osg::Vec3f(rot[0], rot[1], rot[2]);
}
friend inline bool operator<(const Position& l, const Position& r)
{
const auto tuple = [] (const Position& v) { return std::tuple(v.asVec3(), v.asRotationVec3()); };
return tuple(l) < tuple(r);
}
};
#pragma pack(pop)

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

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

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

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

Loading…
Cancel
Save