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

@ -788,7 +788,7 @@ namespace MWMechanics
MWBase::World* world = MWBase::Environment::get().getWorld();
world->getNavigator()->setUpdatesEnabled(mAI);
if (mAI)
world->getNavigator()->update(world->getPlayerPtr().getRefData().getPosition().asVec3());
world->getNavigator()->update(world->getPlayerPtr().getRefData().getPosition().asVec3());
return mAI;
}

@ -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,8 +890,11 @@ namespace MWWorld
addObject(ptr, *mPhysics, mRendering, mPagedRefs);
addObject(ptr, *mPhysics, mNavigator);
MWBase::Environment::get().getWorld()->scaleObject(ptr, ptr.getCellRef().getScale());
const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr();
mNavigator.update(player.getRefData().getPosition().asVec3());
if (mCurrentCell != nullptr)
{
const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr();
mNavigator.update(player.getRefData().getPosition().asVec3());
}
}
catch (std::exception& e)
{
@ -892,8 +910,11 @@ namespace MWWorld
if (const auto object = mPhysics->getObject(ptr))
{
mNavigator.removeObject(DetourNavigator::ObjectId(object));
const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr();
mNavigator.update(player.getRefData().getPosition().asVec3());
if (mCurrentCell != nullptr)
{
const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr();
mNavigator.update(player.getRefData().getPosition().asVec3());
}
}
else if (mPhysics->getActor(ptr))
{

@ -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,71 +18,104 @@
#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;
int getManhattanDistance(const TilePosition& lhs, const TilePosition& rhs)
namespace
{
return std::abs(lhs.x() - rhs.x()) + std::abs(lhs.y() - rhs.y());
}
int getManhattanDistance(const TilePosition& lhs, const TilePosition& rhs)
{
return std::abs(lhs.x() - rhs.x()) + std::abs(lhs.y() - rhs.y());
}
int getMinDistanceTo(const TilePosition& position, int maxDistance,
const std::set<std::tuple<osg::Vec3f, TilePosition>>& pushedTiles,
const std::set<std::tuple<osg::Vec3f, TilePosition>>& presentTiles)
{
int result = maxDistance;
for (const auto& [halfExtents, tile] : pushedTiles)
if (presentTiles.find(std::tie(halfExtents, tile)) == presentTiles.end())
result = std::min(result, getManhattanDistance(position, tile));
return result;
}
int getMinDistanceTo(const TilePosition& position, int maxDistance,
const std::set<std::tuple<osg::Vec3f, TilePosition>>& pushedTiles,
const std::set<std::tuple<osg::Vec3f, TilePosition>>& presentTiles)
{
int result = maxDistance;
for (const auto& [halfExtents, tile] : pushedTiles)
if (presentTiles.find(std::tie(halfExtents, tile)) == presentTiles.end())
result = std::min(result, getManhattanDistance(position, tile));
return result;
}
UpdateType getUpdateType(ChangeType changeType) noexcept
{
if (changeType == ChangeType::update)
return UpdateType::Temporary;
return UpdateType::Persistent;
}
auto getPriority(const Job& job) noexcept
{
return std::make_tuple(-static_cast<std::underlying_type_t<JobState>>(job.mState), job.mProcessTime,
job.mChangeType, job.mTryNumber, job.mDistanceToPlayer, job.mDistanceToOrigin);
}
auto getPriority(const Job& job) noexcept
{
return std::make_tuple(job.mProcessTime, job.mChangeType, job.mTryNumber, job.mDistanceToPlayer, job.mDistanceToOrigin);
}
struct LessByJobPriority
{
bool operator()(JobIt lhs, JobIt rhs) const noexcept
{
return getPriority(*lhs) < getPriority(*rhs);
}
};
struct LessByJobPriority
{
bool operator()(JobIt lhs, JobIt rhs) const noexcept
void insertPrioritizedJob(JobIt job, std::deque<JobIt>& queue)
{
return getPriority(*lhs) < getPriority(*rhs);
const auto it = std::upper_bound(queue.begin(), queue.end(), job, LessByJobPriority {});
queue.insert(it, job);
}
};
void insertPrioritizedJob(JobIt job, std::deque<JobIt>& queue)
{
const auto it = std::upper_bound(queue.begin(), queue.end(), job, LessByJobPriority {});
queue.insert(it, job);
}
auto getDbPriority(const Job& job) noexcept
{
return std::make_tuple(static_cast<std::underlying_type_t<JobState>>(job.mState),
job.mChangeType, job.mDistanceToPlayer, job.mDistanceToOrigin);
}
auto getAgentAndTile(const Job& job) noexcept
{
return std::make_tuple(job.mAgentHalfExtents, job.mChangedTile);
struct LessByJobDbPriority
{
bool operator()(JobIt lhs, JobIt rhs) const noexcept
{
return getDbPriority(*lhs) < getDbPriority(*rhs);
}
};
void insertPrioritizedDbJob(JobIt job, std::deque<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);
}
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);
}
}
}
namespace DetourNavigator
{
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));
mNavMeshTilesCache.reportStats(frameNumber, stats);
if (stats.mDb.has_value())
{
out.setAttribute(frameNumber, "NavMesh DbJobs", static_cast<double>(stats.mDb->mJobs));
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);
unlockTile(job->mAgentHalfExtents, job->mChangedTile);
if (processed)
removeJob(job);
else
repost(job);
const JobStatus status = processJob(*job);
Log(Debug::Debug) << "Processed job " << job->mId << " with status=" << status;
switch (status)
{
case JobStatus::Done:
unlockTile(job->mAgentHalfExtents, job->mChangedTile);
if (job->mGeneratedNavMeshData != nullptr)
mDbWorker->enqueueJob(job);
else
removeJob(job);
break;
case JobStatus::Fail:
repost(job);
break;
case JobStatus::MemoryCacheMiss:
{
mDbWorker->enqueueJob(job);
break;
}
}
}
else
cleanupLastUpdates();
@ -290,33 +360,156 @@ namespace DetourNavigator
Log(Debug::Debug) << "Stop navigator jobs processing by thread=" << std::this_thread::get_id();
}
bool AsyncNavMeshUpdater::processJob(const Job& job)
JobStatus AsyncNavMeshUpdater::processJob(Job& job)
{
Log(Debug::Debug) << "Process job for agent=(" << std::fixed << std::setprecision(2) << job.mAgentHalfExtents << ")"
" by thread=" << std::this_thread::get_id();
const auto start = std::chrono::steady_clock::now();
Log(Debug::Debug) << "Processing job " << job.mId << " by thread=" << std::this_thread::get_id();
const auto navMeshCacheItem = job.mNavMeshCacheItem.lock();
if (!navMeshCacheItem)
return true;
return JobStatus::Done;
const auto recastMesh = mRecastMeshManager.get().getMesh(job.mChangedTile);
const auto playerTile = *mPlayerTile.lockConst();
const auto params = *navMeshCacheItem->lockConst()->getImpl().getParams();
if (!shouldAddTile(job.mChangedTile, playerTile, std::min(mSettings.get().mMaxTilesNumber, params.maxTiles)))
{
Log(Debug::Debug) << "Ignore add tile by job " << job.mId << ": too far from player";
navMeshCacheItem->lock()->removeTile(job.mChangedTile);
return JobStatus::Done;
}
switch (job.mState)
{
case JobState::Initial:
return processInitialJob(job, *navMeshCacheItem);
case JobState::WithDbResult:
return processJobWithDbResult(job, *navMeshCacheItem);
}
return JobStatus::Done;
}
JobStatus AsyncNavMeshUpdater::processInitialJob(Job& job, GuardedNavMeshCacheItem& navMeshCacheItem)
{
Log(Debug::Debug) << "Processing initial job " << job.mId;
std::shared_ptr<RecastMesh> 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));
return handleUpdateNavMeshStatus(status, job, navMeshCacheItem, *recastMesh);
}
JobStatus AsyncNavMeshUpdater::processJobWithDbResult(Job& job, GuardedNavMeshCacheItem& navMeshCacheItem)
{
Log(Debug::Debug) << "Processing job with db result " << job.mId;
std::unique_ptr<PreparedNavMeshData> preparedNavMeshData;
bool generatedNavMeshData = false;
if (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 (recastMesh != nullptr)
if (preparedNavMeshData == nullptr)
{
const Version navMeshVersion = navMeshCacheItem->lockConst()->getVersion();
mRecastMeshManager.get().reportNavMeshChange(job.mChangedTile,
Version {recastMesh->getGeneration(), recastMesh->getRevision()},
navMeshVersion);
Log(Debug::Debug) << "Null navmesh data for job " << job.mId;
navMeshCacheItem.lock()->markAsEmpty(job.mChangedTile);
return JobStatus::Done;
}
auto cachedNavMeshData = mNavMeshTilesCache.set(job.mAgentHalfExtents, job.mChangedTile, *job.mRecastMesh,
std::move(preparedNavMeshData));
const auto offMeshConnections = mOffMeshConnectionsManager.get().get(job.mChangedTile);
const PreparedNavMeshData* preparedNavMeshDataPtr = cachedNavMeshData ? &cachedNavMeshData.get() : preparedNavMeshData.get();
const UpdateNavMeshStatus status = navMeshCacheItem.lock()->updateTile(job.mChangedTile, std::move(cachedNavMeshData),
makeNavMeshTileData(*preparedNavMeshDataPtr, offMeshConnections, job.mAgentHalfExtents, job.mChangedTile, mSettings.get().mRecast));
const JobStatus result = handleUpdateNavMeshStatus(status, job, navMeshCacheItem, *job.mRecastMesh);
if (result == JobStatus::Done && job.mChangeType != ChangeType::update
&& mDbWorker != nullptr && mSettings.get().mWriteToNavMeshDb && generatedNavMeshData)
job.mGeneratedNavMeshData = std::make_unique<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()},
navMeshVersion);
if (status == UpdateNavMeshStatus::removed || status == UpdateNavMeshStatus::lost)
{
const std::scoped_lock lock(mMutex);
@ -328,23 +521,9 @@ namespace DetourNavigator
mPresentTiles.insert(std::make_tuple(job.mAgentHalfExtents, job.mChangedTile));
}
const auto finish = std::chrono::steady_clock::now();
writeDebugFiles(job, recastMesh.get());
using FloatMs = std::chrono::duration<float, std::milli>;
writeDebugFiles(job, &recastMesh);
const Version version = navMeshCacheItem->lockConst()->getVersion();
Log(Debug::Debug) << std::fixed << std::setprecision(2) <<
"Cache updated for agent=(" << job.mAgentHalfExtents << ")" <<
" tile=" << job.mChangedTile <<
" status=" << status <<
" generation=" << version.mGeneration <<
" revision=" << version.mRevision <<
" time=" << std::chrono::duration_cast<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;
float getHeight(const RecastSettings& settings,const osg::Vec3f& agentHalfExtents)
{
return 2.0f * agentHalfExtents.z() * settings.mRecastScaleFactor;
}
float getMaxClimb(const RecastSettings& settings)
{
return settings.mMaxClimb * settings.mRecastScaleFactor;
}
float getRadius(const RecastSettings& settings, const osg::Vec3f& agentHalfExtents)
{
return std::max(agentHalfExtents.x(), agentHalfExtents.y()) * std::sqrt(2) * settings.mRecastScaleFactor;
}
float getSwimLevel(const RecastSettings& settings, const float waterLevel, const float agentHalfExtentsZ)
{
return waterLevel - settings.mSwimHeightScale * agentHalfExtentsZ - agentHalfExtentsZ;;
}
void createHeightfield(rcContext& context, rcHeightfield& solid, int width, int height, const float* bmin,
const float* bmax, const float cs, const float ch)
struct RecastParams
{
const auto result = rcCreateHeightfield(&context, solid, width, height, bmin, bmax, cs, ch);
float mSampleDist = 0;
float mSampleMaxError = 0;
int mMaxEdgeLen = 0;
int mWalkableClimb = 0;
int mWalkableHeight = 0;
int mWalkableRadius = 0;
};
RecastParams makeRecastParams(const RecastSettings& settings, const osg::Vec3f& agentHalfExtents)
{
RecastParams result;
result.mWalkableHeight = static_cast<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))
return false;
const TileBounds cellTileBounds = maxCellTileBounds(cellWater.mCellPosition, cellWater.mWater.mCellSize);
if (auto intersection = getIntersection(realTileBounds, cellTileBounds))
{
const Rectangle rectangle {
toNavMeshCoordinates(settings, *intersection),
toNavMeshCoordinates(settings, getSwimLevel(settings, cellWater.mWater.mLevel, agentHalfExtents.z()))
};
if (!rasterizeTriangles(context, rectangle, AreaType_water, params, solid))
return false;
}
}
return true;
}
bool rasterizeTriangles(rcContext& context, const TileBounds& tileBounds, const std::vector<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);

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

Loading…
Cancel
Save