mirror of
https://github.com/OpenMW/openmw.git
synced 2025-02-28 11:09:42 +00:00
Merge branch 'navmeshtool' into 'master'
Navmesh disk cache (#6189) Closes #6189 See merge request OpenMW/openmw!1058
This commit is contained in:
commit
2988ab55d5
118 changed files with 4355 additions and 945 deletions
|
@ -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 ¤t, bool savePrevious);
|
||||
|
|
22
apps/navmeshtool/CMakeLists.txt
Normal file
22
apps/navmeshtool/CMakeLists.txt
Normal file
|
@ -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()
|
209
apps/navmeshtool/main.cpp
Normal file
209
apps/navmeshtool/main.cpp
Normal file
|
@ -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");
|
||||
}
|
212
apps/navmeshtool/navmesh.cpp
Normal file
212
apps/navmeshtool/navmesh.cpp
Normal file
|
@ -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";
|
||||
}
|
||||
}
|
23
apps/navmeshtool/navmesh.hpp
Normal file
23
apps/navmeshtool/navmesh.hpp
Normal file
|
@ -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
|
330
apps/navmeshtool/worldspacedata.cpp
Normal file
330
apps/navmeshtool/worldspacedata.cpp
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
97
apps/navmeshtool/worldspacedata.hpp
Normal file
97
apps/navmeshtool/worldspacedata.hpp
Normal file
|
@ -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
|
||||
|
||||
|
|
201
apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp
Normal file
201
apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
51
apps/openmw_test_suite/detournavigator/generate.hpp
Normal file
51
apps/openmw_test_suite/detournavigator/generate.hpp
Normal file
|
@ -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);
|
||||
|
||||
|
|
112
apps/openmw_test_suite/detournavigator/navmeshdb.cpp
Normal file
112
apps/openmw_test_suite/detournavigator/navmeshdb.cpp
Normal file
|
@ -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()
|
||||
{
|
||||
|
|
50
apps/openmw_test_suite/detournavigator/settings.hpp
Normal file
50
apps/openmw_test_suite/detournavigator/settings.hpp
Normal file
|
@ -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>
|
||||
|
||||
namespace
|
||||
{
|
||||
using DetourNavigator::ChangeType;
|
||||
using DetourNavigator::TilePosition;
|
||||
using DetourNavigator::UpdateType;
|
||||
using DetourNavigator::ChangeType;
|
||||
using DetourNavigator::Job;
|
||||
using DetourNavigator::JobIt;
|
||||
|
||||
int getManhattanDistance(const TilePosition& lhs, const TilePosition& rhs)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
UpdateType getUpdateType(ChangeType changeType) noexcept
|
||||
{
|
||||
if (changeType == ChangeType::update)
|
||||
return UpdateType::Temporary;
|
||||
return UpdateType::Persistent;
|
||||
}
|
||||
|
||||
auto getPriority(const Job& job) noexcept
|
||||
{
|
||||
return std::make_tuple(job.mProcessTime, job.mChangeType, job.mTryNumber, job.mDistanceToPlayer, job.mDistanceToOrigin);
|
||||
}
|
||||
|
||||
struct LessByJobPriority
|
||||
{
|
||||
bool operator()(JobIt lhs, JobIt rhs) const noexcept
|
||||
{
|
||||
return getPriority(*lhs) < getPriority(*rhs);
|
||||
}
|
||||
};
|
||||
|
||||
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 getAgentAndTile(const Job& job) noexcept
|
||||
{
|
||||
return std::make_tuple(job.mAgentHalfExtents, job.mChangedTile);
|
||||
}
|
||||
}
|
||||
#include <type_traits>
|
||||
|
||||
namespace DetourNavigator
|
||||
{
|
||||
namespace
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
struct LessByJobPriority
|
||||
{
|
||||
bool operator()(JobIt lhs, JobIt rhs) const noexcept
|
||||
{
|
||||
return getPriority(*lhs) < getPriority(*rhs);
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void reportStats(const AsyncNavMeshUpdater::Stats& stats, unsigned int frameNumber, osg::Stats& out)
|
||||
{
|
||||
out.setAttribute(frameNumber, "NavMesh Jobs", static_cast<double>(stats.mJobs));
|
||||
out.setAttribute(frameNumber, "NavMesh Waiting", static_cast<double>(stats.mWaiting));
|
||||
out.setAttribute(frameNumber, "NavMesh Pushed", static_cast<double>(stats.mPushed));
|
||||
out.setAttribute(frameNumber, "NavMesh Processing", static_cast<double>(stats.mProcessing));
|
||||
|
||||
if (stats.mDb.has_value())
|
||||
{
|
||||
out.setAttribute(frameNumber, "NavMesh DbJobs", static_cast<double>(stats.mDb->mJobs));
|
||||
|
||||
if (stats.mDb->mGetTileCount > 0)
|
||||
out.setAttribute(frameNumber, "NavMesh DbCacheHitRate", static_cast<double>(stats.mDbGetTileHits)
|
||||
/ static_cast<double>(stats.mDb->mGetTileCount) * 100.0);
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
mNavMeshTilesCache.reportStats(frameNumber, stats);
|
||||
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));
|
||||
|
||||
if (recastMesh != nullptr)
|
||||
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)
|
||||
{
|
||||
const Version navMeshVersion = navMeshCacheItem->lockConst()->getVersion();
|
||||
mRecastMeshManager.get().reportNavMeshChange(job.mChangedTile,
|
||||
Version {recastMesh->getGeneration(), recastMesh->getRevision()},
|
||||
navMeshVersion);
|
||||
preparedNavMeshData = std::make_unique<PreparedNavMeshData>();
|
||||
if (deserialize(job.mCachedTileData->mData, *preparedNavMeshData))
|
||||
++mDbGetTileHits;
|
||||
else
|
||||
preparedNavMeshData = nullptr;
|
||||
}
|
||||
|
||||
if (preparedNavMeshData == nullptr)
|
||||
{
|
||||
preparedNavMeshData = prepareNavMeshTileData(*job.mRecastMesh, job.mChangedTile, job.mAgentHalfExtents, mSettings.get().mRecast);
|
||||
generatedNavMeshData = true;
|
||||
}
|
||||
|
||||
if (preparedNavMeshData == nullptr)
|
||||
{
|
||||
Log(Debug::Debug) << "Null navmesh data for job " << job.mId;
|
||||
navMeshCacheItem.lock()->markAsEmpty(job.mChangedTile);
|
||||
return JobStatus::Done;
|
||||
}
|
||||
|
||||
auto cachedNavMeshData = mNavMeshTilesCache.set(job.mAgentHalfExtents, job.mChangedTile, *job.mRecastMesh,
|
||||
std::move(preparedNavMeshData));
|
||||
|
||||
const auto offMeshConnections = mOffMeshConnectionsManager.get().get(job.mChangedTile);
|
||||
|
||||
const PreparedNavMeshData* preparedNavMeshDataPtr = cachedNavMeshData ? &cachedNavMeshData.get() : preparedNavMeshData.get();
|
||||
const UpdateNavMeshStatus status = navMeshCacheItem.lock()->updateTile(job.mChangedTile, std::move(cachedNavMeshData),
|
||||
makeNavMeshTileData(*preparedNavMeshDataPtr, offMeshConnections, job.mAgentHalfExtents, job.mChangedTile, mSettings.get().mRecast));
|
||||
|
||||
const JobStatus result = handleUpdateNavMeshStatus(status, job, navMeshCacheItem, *job.mRecastMesh);
|
||||
|
||||
if (result == JobStatus::Done && job.mChangeType != ChangeType::update
|
||||
&& mDbWorker != nullptr && mSettings.get().mWriteToNavMeshDb && generatedNavMeshData)
|
||||
job.mGeneratedNavMeshData = std::make_unique<PreparedNavMeshData>(*preparedNavMeshDataPtr);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
JobStatus AsyncNavMeshUpdater::handleUpdateNavMeshStatus(UpdateNavMeshStatus status,
|
||||
const Job& job, const GuardedNavMeshCacheItem& navMeshCacheItem, const RecastMesh& recastMesh)
|
||||
{
|
||||
const Version navMeshVersion = navMeshCacheItem.lockConst()->getVersion();
|
||||
mRecastMeshManager.get().reportNavMeshChange(job.mChangedTile,
|
||||
Version {recastMesh.getGeneration(), recastMesh.getRevision()},
|
||||
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);
|
||||
|
||||
writeDebugFiles(job, recastMesh.get());
|
||||
|
||||
using FloatMs = std::chrono::duration<float, std::milli>;
|
||||
|
||||
const Version version = navMeshCacheItem->lockConst()->getVersion();
|
||||
Log(Debug::Debug) << std::fixed << std::setprecision(2) <<
|
||||
"Cache updated for agent=(" << job.mAgentHalfExtents << ")" <<
|
||||
" tile=" << job.mChangedTile <<
|
||||
" status=" << status <<
|
||||
" generation=" << version.mGeneration <<
|
||||
" revision=" << version.mRevision <<
|
||||
" time=" << std::chrono::duration_cast<FloatMs>(finish - start).count() << "ms" <<
|
||||
" thread=" << std::this_thread::get_id();
|
||||
|
||||
return isSuccess(status);
|
||||
return isSuccess(status) ? JobStatus::Done : JobStatus::Fail;
|
||||
}
|
||||
|
||||
JobIt AsyncNavMeshUpdater::getNextJob()
|
||||
|
@ -373,8 +552,12 @@ namespace DetourNavigator
|
|||
|
||||
mWaiting.pop_front();
|
||||
|
||||
if (job->mRecastMesh != nullptr)
|
||||
return job;
|
||||
|
||||
if (!lockTile(job->mAgentHalfExtents, job->mChangedTile))
|
||||
{
|
||||
Log(Debug::Debug) << "Failed to lock tile by " << job->mId;
|
||||
++job->mTryNumber;
|
||||
insertPrioritizedJob(job, mWaiting);
|
||||
return mJobs.end();
|
||||
|
@ -404,7 +587,7 @@ namespace DetourNavigator
|
|||
}
|
||||
if (recastMesh && mSettings.get().mEnableWriteRecastMeshToFile)
|
||||
writeToFile(*recastMesh, mSettings.get().mRecastMeshPathPrefix + std::to_string(job.mChangedTile.x())
|
||||
+ "_" + std::to_string(job.mChangedTile.y()) + "_", recastMeshRevision, mSettings);
|
||||
+ "_" + std::to_string(job.mChangedTile.y()) + "_", recastMeshRevision, mSettings.get().mRecast);
|
||||
if (mSettings.get().mEnableWriteNavMeshToFile)
|
||||
if (const auto shared = job.mNavMeshCacheItem.lock())
|
||||
writeToFile(shared->lockConst()->getImpl(), mSettings.get().mNavMeshPathPrefix, navMeshRevision);
|
||||
|
@ -412,6 +595,8 @@ namespace DetourNavigator
|
|||
|
||||
void AsyncNavMeshUpdater::repost(JobIt job)
|
||||
{
|
||||
unlockTile(job->mAgentHalfExtents, job->mChangedTile);
|
||||
|
||||
if (mShouldStop || job->mTryNumber > 2)
|
||||
return;
|
||||
|
||||
|
@ -430,17 +615,15 @@ namespace DetourNavigator
|
|||
|
||||
bool AsyncNavMeshUpdater::lockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile)
|
||||
{
|
||||
if (mSettings.get().mAsyncNavMeshUpdaterThreads <= 1)
|
||||
return true;
|
||||
Log(Debug::Debug) << "Locking tile agent=(" << agentHalfExtents << ") changedTile=(" << changedTile << ")";
|
||||
return mProcessingTiles.lock()->emplace(agentHalfExtents, changedTile).second;
|
||||
}
|
||||
|
||||
void AsyncNavMeshUpdater::unlockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile)
|
||||
{
|
||||
if (mSettings.get().mAsyncNavMeshUpdaterThreads <= 1)
|
||||
return;
|
||||
auto locked = mProcessingTiles.lock();
|
||||
locked->erase(std::tie(agentHalfExtents, changedTile));
|
||||
Log(Debug::Debug) << "Unlocked tile agent=(" << agentHalfExtents << ") changedTile=(" << changedTile << ")";
|
||||
if (locked->empty())
|
||||
mProcessed.notify_all();
|
||||
}
|
||||
|
@ -466,9 +649,201 @@ namespace DetourNavigator
|
|||
}
|
||||
}
|
||||
|
||||
void AsyncNavMeshUpdater::enqueueJob(JobIt job)
|
||||
{
|
||||
Log(Debug::Debug) << "Enqueueing job " << job->mId << " by thread=" << std::this_thread::get_id();
|
||||
const std::lock_guard lock(mMutex);
|
||||
insertPrioritizedJob(job, mWaiting);
|
||||
mHasJob.notify_all();
|
||||
}
|
||||
|
||||
void AsyncNavMeshUpdater::removeJob(JobIt job)
|
||||
{
|
||||
Log(Debug::Debug) << "Removing job " << job->mId << " by thread=" << std::this_thread::get_id();
|
||||
const std::lock_guard lock(mMutex);
|
||||
mJobs.erase(job);
|
||||
}
|
||||
|
||||
void DbJobQueue::push(JobIt job)
|
||||
{
|
||||
const std::lock_guard lock(mMutex);
|
||||
insertPrioritizedDbJob(job, mJobs);
|
||||
mHasJob.notify_all();
|
||||
}
|
||||
|
||||
std::optional<JobIt> DbJobQueue::pop()
|
||||
{
|
||||
std::unique_lock lock(mMutex);
|
||||
mHasJob.wait(lock, [&] { return mShouldStop || !mJobs.empty(); });
|
||||
if (mJobs.empty())
|
||||
return std::nullopt;
|
||||
const JobIt job = mJobs.front();
|
||||
mJobs.pop_front();
|
||||
return job;
|
||||
}
|
||||
|
||||
void DbJobQueue::update(TilePosition playerTile, int maxTiles)
|
||||
{
|
||||
const std::lock_guard lock(mMutex);
|
||||
updateJobs(mJobs, playerTile, maxTiles);
|
||||
std::sort(mJobs.begin(), mJobs.end(), LessByJobDbPriority {});
|
||||
}
|
||||
|
||||
void DbJobQueue::stop()
|
||||
{
|
||||
const std::lock_guard lock(mMutex);
|
||||
mJobs.clear();
|
||||
mShouldStop = true;
|
||||
mHasJob.notify_all();
|
||||
}
|
||||
|
||||
std::size_t DbJobQueue::size() const
|
||||
{
|
||||
const std::lock_guard lock(mMutex);
|
||||
return mJobs.size();
|
||||
}
|
||||
|
||||
DbWorker::DbWorker(AsyncNavMeshUpdater& updater, std::unique_ptr<NavMeshDb>&& db,
|
||||
TileVersion version, const RecastSettings& recastSettings)
|
||||
: mUpdater(updater)
|
||||
, mRecastSettings(recastSettings)
|
||||
, mDb(std::move(db))
|
||||
, mVersion(version)
|
||||
, mNextTileId(mDb->getMaxTileId() + 1)
|
||||
, mNextShapeId(mDb->getMaxShapeId() + 1)
|
||||
, mThread([this] { run(); })
|
||||
{
|
||||
}
|
||||
|
||||
DbWorker::~DbWorker()
|
||||
{
|
||||
stop();
|
||||
mThread.join();
|
||||
}
|
||||
|
||||
void DbWorker::enqueueJob(JobIt job)
|
||||
{
|
||||
Log(Debug::Debug) << "Enqueueing db job " << job->mId << " by thread=" << std::this_thread::get_id();
|
||||
mQueue.push(job);
|
||||
}
|
||||
|
||||
DbWorker::Stats DbWorker::getStats() const
|
||||
{
|
||||
Stats result;
|
||||
result.mJobs = mQueue.size();
|
||||
result.mGetTileCount = mGetTileCount.load(std::memory_order::memory_order_relaxed);
|
||||
return result;
|
||||
}
|
||||
|
||||
void DbWorker::stop()
|
||||
{
|
||||
mShouldStop = true;
|
||||
mQueue.stop();
|
||||
}
|
||||
|
||||
void DbWorker::run() noexcept
|
||||
{
|
||||
constexpr std::size_t writesPerTransaction = 100;
|
||||
auto transaction = mDb->startTransaction();
|
||||
while (!mShouldStop)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (const auto job = mQueue.pop())
|
||||
processJob(*job);
|
||||
if (mWrites > writesPerTransaction)
|
||||
{
|
||||
mWrites = 0;
|
||||
transaction.commit();
|
||||
transaction = mDb->startTransaction();
|
||||
}
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
Log(Debug::Error) << "DbWorker exception: " << e.what();
|
||||
}
|
||||
}
|
||||
transaction.commit();
|
||||
}
|
||||
|
||||
void DbWorker::processJob(JobIt job)
|
||||
{
|
||||
const auto process = [&] (auto f)
|
||||
{
|
||||
try
|
||||
{
|
||||
f(job);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
Log(Debug::Error) << "DbWorker exception while processing job " << job->mId << ": " << e.what();
|
||||
}
|
||||
};
|
||||
|
||||
if (job->mGeneratedNavMeshData != nullptr)
|
||||
{
|
||||
process([&] (JobIt job) { processWritingJob(job); });
|
||||
mUpdater.removeJob(job);
|
||||
return;
|
||||
}
|
||||
|
||||
process([&] (JobIt job) { processReadingJob(job); });
|
||||
job->mState = JobState::WithDbResult;
|
||||
mUpdater.enqueueJob(job);
|
||||
}
|
||||
|
||||
void DbWorker::processReadingJob(JobIt job)
|
||||
{
|
||||
Log(Debug::Debug) << "Processing db read job " << job->mId;
|
||||
|
||||
if (job->mInput.empty())
|
||||
{
|
||||
Log(Debug::Debug) << "Serializing input for job " << job->mId;
|
||||
const ShapeId shapeId = mNextShapeId;
|
||||
const std::vector<DbRefGeometryObject> objects = makeDbRefGeometryObjects(job->mRecastMesh->getMeshSources(),
|
||||
[&] (const MeshSource& v) { return resolveMeshSource(*mDb, v, mNextShapeId); });
|
||||
if (shapeId != mNextShapeId)
|
||||
++mWrites;
|
||||
job->mInput = serialize(mRecastSettings, *job->mRecastMesh, objects);
|
||||
}
|
||||
|
||||
job->mCachedTileData = mDb->getTileData(job->mWorldspace, job->mChangedTile, job->mInput);
|
||||
++mGetTileCount;
|
||||
}
|
||||
|
||||
void DbWorker::processWritingJob(JobIt job)
|
||||
{
|
||||
++mWrites;
|
||||
|
||||
Log(Debug::Debug) << "Processing db write job " << job->mId;
|
||||
|
||||
if (job->mInput.empty())
|
||||
{
|
||||
Log(Debug::Debug) << "Serializing input for job " << job->mId;
|
||||
const std::vector<DbRefGeometryObject> objects = makeDbRefGeometryObjects(job->mRecastMesh->getMeshSources(),
|
||||
[&] (const MeshSource& v) { return resolveMeshSource(*mDb, v, mNextShapeId); });
|
||||
job->mInput = serialize(mRecastSettings, *job->mRecastMesh, objects);
|
||||
}
|
||||
|
||||
if (const auto& cachedTileData = job->mCachedTileData)
|
||||
{
|
||||
Log(Debug::Debug) << "Update db tile by job " << job->mId;
|
||||
job->mGeneratedNavMeshData->mUserId = cachedTileData->mTileId;
|
||||
mDb->updateTile(cachedTileData->mTileId, mVersion, serialize(*job->mGeneratedNavMeshData));
|
||||
return;
|
||||
}
|
||||
|
||||
const auto cached = mDb->findTile(job->mWorldspace, job->mChangedTile, job->mInput);
|
||||
if (cached.has_value() && cached->mVersion == mVersion)
|
||||
{
|
||||
Log(Debug::Debug) << "Ignore existing db tile by job " << job->mId;
|
||||
return;
|
||||
}
|
||||
|
||||
job->mGeneratedNavMeshData->mUserId = mNextTileId;
|
||||
Log(Debug::Debug) << "Insert db tile by job " << job->mId;
|
||||
mDb->insertTile(mNextTileId, job->mWorldspace, job->mChangedTile,
|
||||
mVersion, job->mInput, serialize(*job->mGeneratedNavMeshData));
|
||||
++mNextTileId.t;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include "tileposition.hpp"
|
||||
#include "navmeshtilescache.hpp"
|
||||
#include "waitconditiontype.hpp"
|
||||
#include "navmeshdb.hpp"
|
||||
|
||||
#include <osg/Vec3f>
|
||||
|
||||
|
@ -20,6 +21,7 @@
|
|||
#include <thread>
|
||||
#include <tuple>
|
||||
#include <list>
|
||||
#include <optional>
|
||||
|
||||
class dtNavMesh;
|
||||
|
||||
|
@ -53,37 +55,150 @@ namespace DetourNavigator
|
|||
return stream << "ChangeType::" << static_cast<int>(value);
|
||||
}
|
||||
|
||||
enum class JobState
|
||||
{
|
||||
Initial,
|
||||
WithDbResult,
|
||||
};
|
||||
|
||||
struct Job
|
||||
{
|
||||
const std::size_t mId;
|
||||
const osg::Vec3f mAgentHalfExtents;
|
||||
const std::weak_ptr<GuardedNavMeshCacheItem> mNavMeshCacheItem;
|
||||
const std::string mWorldspace;
|
||||
const TilePosition mChangedTile;
|
||||
const std::chrono::steady_clock::time_point mProcessTime;
|
||||
unsigned mTryNumber = 0;
|
||||
ChangeType mChangeType;
|
||||
int mDistanceToPlayer;
|
||||
const int mDistanceToOrigin;
|
||||
JobState mState = JobState::Initial;
|
||||
std::vector<std::byte> mInput;
|
||||
std::shared_ptr<RecastMesh> mRecastMesh;
|
||||
std::optional<TileData> mCachedTileData;
|
||||
std::unique_ptr<PreparedNavMeshData> mGeneratedNavMeshData;
|
||||
|
||||
Job(const osg::Vec3f& agentHalfExtents, std::weak_ptr<GuardedNavMeshCacheItem> navMeshCacheItem,
|
||||
const TilePosition& changedTile, ChangeType changeType, int distanceToPlayer,
|
||||
std::string_view worldspace, const TilePosition& changedTile, ChangeType changeType, int distanceToPlayer,
|
||||
std::chrono::steady_clock::time_point processTime);
|
||||
};
|
||||
|
||||
using JobIt = std::list<Job>::iterator;
|
||||
|
||||
enum class JobStatus
|
||||
{
|
||||
Done,
|
||||
Fail,
|
||||
MemoryCacheMiss,
|
||||
};
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& stream, JobStatus value)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case JobStatus::Done: return stream << "JobStatus::Done";
|
||||
case JobStatus::Fail: return stream << "JobStatus::Fail";
|
||||
case JobStatus::MemoryCacheMiss: return stream << "JobStatus::MemoryCacheMiss";
|
||||
}
|
||||
return stream << "JobStatus::" << static_cast<std::underlying_type_t<JobState>>(value);
|
||||
}
|
||||
|
||||
class DbJobQueue
|
||||
{
|
||||
public:
|
||||
void push(JobIt job);
|
||||
|
||||
std::optional<JobIt> pop();
|
||||
|
||||
void update(TilePosition playerTile, int maxTiles);
|
||||
|
||||
void stop();
|
||||
|
||||
std::size_t size() const;
|
||||
|
||||
private:
|
||||
mutable std::mutex mMutex;
|
||||
std::condition_variable mHasJob;
|
||||
std::deque<JobIt> mJobs;
|
||||
bool mShouldStop = false;
|
||||
};
|
||||
|
||||
class AsyncNavMeshUpdater;
|
||||
|
||||
class DbWorker
|
||||
{
|
||||
public:
|
||||
struct Stats
|
||||
{
|
||||
std::size_t mJobs = 0;
|
||||
std::size_t mGetTileCount = 0;
|
||||
};
|
||||
|
||||
DbWorker(AsyncNavMeshUpdater& updater, std::unique_ptr<NavMeshDb>&& db,
|
||||
TileVersion version, const RecastSettings& recastSettings);
|
||||
|
||||
~DbWorker();
|
||||
|
||||
Stats getStats() const;
|
||||
|
||||
void enqueueJob(JobIt job);
|
||||
|
||||
void updateJobs(TilePosition playerTile, int maxTiles) { mQueue.update(playerTile, maxTiles); }
|
||||
|
||||
void stop();
|
||||
|
||||
private:
|
||||
AsyncNavMeshUpdater& mUpdater;
|
||||
const RecastSettings& mRecastSettings;
|
||||
const std::unique_ptr<NavMeshDb> mDb;
|
||||
const TileVersion mVersion;
|
||||
TileId mNextTileId;
|
||||
ShapeId mNextShapeId;
|
||||
DbJobQueue mQueue;
|
||||
std::atomic_bool mShouldStop {false};
|
||||
std::atomic_size_t mGetTileCount {0};
|
||||
std::size_t mWrites = 0;
|
||||
std::thread mThread;
|
||||
|
||||
inline void run() noexcept;
|
||||
|
||||
inline void processJob(JobIt job);
|
||||
|
||||
inline void processReadingJob(JobIt job);
|
||||
|
||||
inline void processWritingJob(JobIt job);
|
||||
};
|
||||
|
||||
class AsyncNavMeshUpdater
|
||||
{
|
||||
public:
|
||||
struct Stats
|
||||
{
|
||||
std::size_t mJobs = 0;
|
||||
std::size_t mWaiting = 0;
|
||||
std::size_t mPushed = 0;
|
||||
std::size_t mProcessing = 0;
|
||||
std::size_t mDbGetTileHits = 0;
|
||||
std::optional<DbWorker::Stats> mDb;
|
||||
NavMeshTilesCache::Stats mCache;
|
||||
};
|
||||
|
||||
AsyncNavMeshUpdater(const Settings& settings, TileCachedRecastMeshManager& recastMeshManager,
|
||||
OffMeshConnectionsManager& offMeshConnectionsManager);
|
||||
OffMeshConnectionsManager& offMeshConnectionsManager, std::unique_ptr<NavMeshDb>&& db);
|
||||
~AsyncNavMeshUpdater();
|
||||
|
||||
void post(const osg::Vec3f& agentHalfExtents, const SharedNavMeshCacheItem& mNavMeshCacheItem,
|
||||
const TilePosition& playerTile, const std::map<TilePosition, ChangeType>& changedTiles);
|
||||
void post(const osg::Vec3f& agentHalfExtents, const SharedNavMeshCacheItem& navMeshCacheItem,
|
||||
const TilePosition& playerTile, std::string_view worldspace,
|
||||
const std::map<TilePosition, ChangeType>& changedTiles);
|
||||
|
||||
void wait(Loading::Listener& listener, WaitConditionType waitConditionType);
|
||||
|
||||
void reportStats(unsigned int frameNumber, osg::Stats& stats) const;
|
||||
Stats getStats() const;
|
||||
|
||||
void enqueueJob(JobIt job);
|
||||
|
||||
void removeJob(JobIt job);
|
||||
|
||||
private:
|
||||
std::reference_wrapper<const Settings> mSettings;
|
||||
|
@ -103,15 +218,22 @@ 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);
|
||||
|
||||
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 getNextJob();
|
||||
|
||||
JobIt getJob(std::deque<JobIt>& jobs, bool changeLastUpdate);
|
||||
|
||||
void postThreadJob(JobIt job, std::deque<JobIt>& queue);
|
||||
|
||||
void writeDebugFiles(const Job& job, const RecastMesh* recastMesh) const;
|
||||
|
@ -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);
|
||||
|
|
46
components/detournavigator/dbrefgeometryobject.hpp
Normal file
46
components/detournavigator/dbrefgeometryobject.hpp
Normal file
|
@ -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);
|
||||
}
|
||||
|
||||
class RecastMesh;
|
||||
struct Settings;
|
||||
inline std::ostream& operator<<(std::ostream& s, const Water& v)
|
||||
{
|
||||
return s << "Water {" << v.mCellSize << ", " << v.mLevel << "}";
|
||||
}
|
||||
|
||||
void writeToFile(const RecastMesh& recastMesh, const std::string& pathPrefix, const std::string& revision, const Settings& settings);
|
||||
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 RecastSettings;
|
||||
|
||||
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;
|
||||
|
|
95
components/detournavigator/generatenavmeshtile.cpp
Normal file
95
components/detournavigator/generatenavmeshtile.cpp
Normal file
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
71
components/detournavigator/generatenavmeshtile.hpp
Normal file
71
components/detournavigator/generatenavmeshtile.hpp
Normal file
|
@ -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)
|
||||
float getHeight(const RecastSettings& settings,const osg::Vec3f& agentHalfExtents)
|
||||
{
|
||||
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;
|
||||
return 2.0f * agentHalfExtents.z() * settings.mRecastScaleFactor;
|
||||
}
|
||||
|
||||
void createHeightfield(rcContext& context, rcHeightfield& solid, int width, int height, const float* bmin,
|
||||
const float* bmax, const float cs, const float ch)
|
||||
float getMaxClimb(const RecastSettings& settings)
|
||||
{
|
||||
const auto result = rcCreateHeightfield(&context, solid, width, height, bmin, bmax, cs, ch);
|
||||
return settings.mMaxClimb * settings.mRecastScaleFactor;
|
||||
}
|
||||
|
||||
float getRadius(const RecastSettings& settings, const osg::Vec3f& agentHalfExtents)
|
||||
{
|
||||
return std::max(agentHalfExtents.x(), agentHalfExtents.y()) * std::sqrt(2) * settings.mRecastScaleFactor;
|
||||
}
|
||||
|
||||
float getSwimLevel(const RecastSettings& settings, const float waterLevel, const float agentHalfExtentsZ)
|
||||
{
|
||||
return waterLevel - settings.mSwimHeightScale * agentHalfExtentsZ - agentHalfExtentsZ;;
|
||||
}
|
||||
|
||||
struct RecastParams
|
||||
{
|
||||
float mSampleDist = 0;
|
||||
float mSampleMaxError = 0;
|
||||
int mMaxEdgeLen = 0;
|
||||
int mWalkableClimb = 0;
|
||||
int mWalkableHeight = 0;
|
||||
int mWalkableRadius = 0;
|
||||
};
|
||||
|
||||
RecastParams makeRecastParams(const RecastSettings& settings, const osg::Vec3f& agentHalfExtents)
|
||||
{
|
||||
RecastParams result;
|
||||
|
||||
result.mWalkableHeight = static_cast<int>(std::ceil(getHeight(settings, agentHalfExtents) / settings.mCellHeight));
|
||||
result.mWalkableClimb = static_cast<int>(std::floor(getMaxClimb(settings) / settings.mCellHeight));
|
||||
result.mWalkableRadius = static_cast<int>(std::ceil(getRadius(settings, agentHalfExtents) / settings.mCellSize));
|
||||
result.mMaxEdgeLen = static_cast<int>(std::round(static_cast<float>(settings.mMaxEdgeLen) / settings.mCellSize));
|
||||
result.mSampleDist = settings.mDetailSampleDist < 0.9f ? 0 : settings.mCellSize * settings.mDetailSampleDist;
|
||||
result.mSampleMaxError = settings.mCellHeight * settings.mDetailSampleMaxError;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void initHeightfield(rcContext& context, const TilePosition& tilePosition, float minZ, float maxZ,
|
||||
const RecastSettings& settings, rcHeightfield& solid)
|
||||
{
|
||||
const int size = settings.mTileSize + settings.mBorderSize * 2;
|
||||
const int width = size;
|
||||
const int height = size;
|
||||
const float halfBoundsSize = size * settings.mCellSize * 0.5f;
|
||||
const osg::Vec2f shift = osg::Vec2f(tilePosition.x() + 0.5f, tilePosition.y() + 0.5f) * getTileSize(settings);
|
||||
const osg::Vec3f bmin(shift.x() - halfBoundsSize, minZ, shift.y() - halfBoundsSize);
|
||||
const osg::Vec3f bmax(shift.x() + halfBoundsSize, maxZ, shift.y() + halfBoundsSize);
|
||||
|
||||
const auto result = rcCreateHeightfield(&context, solid, width, height, bmin.ptr(), bmax.ptr(),
|
||||
settings.mCellSize, settings.mCellHeight);
|
||||
|
||||
if (!result)
|
||||
throw NavigatorException("Failed to create heightfield for navmesh");
|
||||
}
|
||||
|
||||
bool rasterizeTriangles(rcContext& context, const Mesh& mesh, const Settings& settings, const rcConfig& config,
|
||||
rcHeightfield& solid)
|
||||
bool rasterizeTriangles(rcContext& context, const Mesh& mesh, const RecastSettings& settings,
|
||||
const RecastParams& params, rcHeightfield& solid)
|
||||
{
|
||||
std::vector<unsigned char> areas(mesh.getAreaTypes().begin(), mesh.getAreaTypes().end());
|
||||
std::vector<float> vertices = mesh.getVertices();
|
||||
|
@ -179,7 +177,7 @@ namespace
|
|||
|
||||
rcClearUnwalkableTriangles(
|
||||
&context,
|
||||
config.walkableSlopeAngle,
|
||||
settings.mMaxSlope,
|
||||
vertices.data(),
|
||||
static_cast<int>(mesh.getVerticesCount()),
|
||||
mesh.getIndices().data(),
|
||||
|
@ -195,30 +193,18 @@ namespace
|
|||
areas.data(),
|
||||
static_cast<int>(areas.size()),
|
||||
solid,
|
||||
config.walkableClimb
|
||||
params.mWalkableClimb
|
||||
);
|
||||
}
|
||||
|
||||
bool rasterizeTriangles(rcContext& context, const Rectangle& rectangle, const rcConfig& config,
|
||||
AreaType areaType, rcHeightfield& solid)
|
||||
bool rasterizeTriangles(rcContext& context, const Rectangle& rectangle, AreaType areaType,
|
||||
const RecastParams& params, rcHeightfield& solid)
|
||||
{
|
||||
const osg::Vec2f tileBoundsMin(
|
||||
std::clamp(rectangle.mBounds.mMin.x(), config.bmin[0], config.bmax[0]),
|
||||
std::clamp(rectangle.mBounds.mMin.y(), config.bmin[2], config.bmax[2])
|
||||
);
|
||||
const osg::Vec2f tileBoundsMax(
|
||||
std::clamp(rectangle.mBounds.mMax.x(), config.bmin[0], config.bmax[0]),
|
||||
std::clamp(rectangle.mBounds.mMax.y(), config.bmin[2], config.bmax[2])
|
||||
);
|
||||
|
||||
if (tileBoundsMax == tileBoundsMin)
|
||||
return true;
|
||||
|
||||
const std::array vertices {
|
||||
tileBoundsMin.x(), rectangle.mHeight, tileBoundsMin.y(),
|
||||
tileBoundsMin.x(), rectangle.mHeight, tileBoundsMax.y(),
|
||||
tileBoundsMax.x(), rectangle.mHeight, tileBoundsMax.y(),
|
||||
tileBoundsMax.x(), rectangle.mHeight, tileBoundsMin.y(),
|
||||
rectangle.mBounds.mMin.x(), rectangle.mHeight, rectangle.mBounds.mMin.y(),
|
||||
rectangle.mBounds.mMin.x(), rectangle.mHeight, rectangle.mBounds.mMax.y(),
|
||||
rectangle.mBounds.mMax.x(), rectangle.mHeight, rectangle.mBounds.mMax.y(),
|
||||
rectangle.mBounds.mMax.x(), rectangle.mHeight, rectangle.mBounds.mMin.y(),
|
||||
};
|
||||
|
||||
const std::array indices {
|
||||
|
@ -236,31 +222,42 @@ namespace
|
|||
areas.data(),
|
||||
static_cast<int>(areas.size()),
|
||||
solid,
|
||||
config.walkableClimb
|
||||
params.mWalkableClimb
|
||||
);
|
||||
}
|
||||
|
||||
bool rasterizeTriangles(rcContext& context, const osg::Vec3f& agentHalfExtents, const std::vector<CellWater>& water,
|
||||
const Settings& settings, const rcConfig& config, rcHeightfield& solid)
|
||||
const RecastSettings& settings, const RecastParams& params, const TileBounds& realTileBounds, rcHeightfield& solid)
|
||||
{
|
||||
for (const CellWater& cellWater : water)
|
||||
{
|
||||
const Rectangle rectangle = getSwimRectangle(cellWater, settings, agentHalfExtents);
|
||||
if (!rasterizeTriangles(context, rectangle, config, AreaType_water, solid))
|
||||
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)
|
||||
{
|
||||
rcContext context;
|
||||
|
||||
const auto [minZ, maxZ] = getBoundsByZ(recastMesh, agentHalfExtents, settings);
|
||||
|
||||
rcContext context;
|
||||
const auto config = makeConfig(agentHalfExtents, tilePosition, toNavMeshCoordinates(settings, minZ),
|
||||
toNavMeshCoordinates(settings, maxZ), 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>;
|
||||
|
|
296
components/detournavigator/navmeshdb.cpp
Normal file
296
components/detournavigator/navmeshdb.cpp
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
153
components/detournavigator/navmeshdb.hpp
Normal file
153
components/detournavigator/navmeshdb.hpp
Normal file
|
@ -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
|
40
components/detournavigator/navmeshdbutils.cpp
Normal file
40
components/detournavigator/navmeshdbutils.cpp
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
13
components/detournavigator/navmeshdbutils.hpp
Normal file
13
components/detournavigator/navmeshdbutils.hpp
Normal file
|
@ -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;
|
||||
|
|
27
components/detournavigator/objecttransform.hpp
Normal file
27
components/detournavigator/objecttransform.hpp
Normal file
|
@ -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
|
||||
|
|
80
components/detournavigator/recast.cpp
Normal file
80
components/detournavigator/recast.cpp
Normal file
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
33
components/detournavigator/recastmeshprovider.hpp
Normal file
33
components/detournavigator/recastmeshprovider.hpp
Normal file
|
@ -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
|
272
components/detournavigator/serialization.cpp
Normal file
272
components/detournavigator/serialization.cpp
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
29
components/detournavigator/serialization.hpp
Normal file
29
components/detournavigator/serialization.hpp
Normal file
|
@ -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;
|
||||
Settings result;
|
||||
|
||||
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"));
|
||||
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 navigatorSettings;
|
||||
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;
|
||||
int mWaitUntilMinDistanceToPlayer = 0;
|
||||
std::size_t mAsyncNavMeshUpdaterThreads = 0;
|
||||
std::size_t mMaxNavMeshTilesCacheSize = 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::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(const TilePosition& tilePosition) const;
|
||||
std::shared_ptr<RecastMesh> getCachedMesh(std::string_view worldspace, 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…
Reference in a new issue