mirror of https://github.com/OpenMW/openmw.git
Merge branch 'navmeshtool' into 'master'
Navmesh disk cache (#6189) Closes #6189 See merge request OpenMW/openmw!1058pull/3225/head
commit
2988ab55d5
@ -0,0 +1,22 @@
|
||||
set(NAVMESHTOOL
|
||||
worldspacedata.cpp
|
||||
navmesh.cpp
|
||||
main.cpp
|
||||
)
|
||||
source_group(apps\\navmeshtool FILES ${NAVMESHTOOL})
|
||||
|
||||
openmw_add_executable(openmw-navmeshtool ${NAVMESHTOOL})
|
||||
|
||||
target_link_libraries(openmw-navmeshtool
|
||||
${Boost_PROGRAM_OPTIONS_LIBRARY}
|
||||
components
|
||||
)
|
||||
|
||||
if (BUILD_WITH_CODE_COVERAGE)
|
||||
add_definitions(--coverage)
|
||||
target_link_libraries(openmw-navmeshtool gcov)
|
||||
endif()
|
||||
|
||||
if (WIN32)
|
||||
install(TARGETS openmw-navmeshtool RUNTIME DESTINATION ".")
|
||||
endif()
|
@ -0,0 +1,209 @@
|
||||
#include "worldspacedata.hpp"
|
||||
#include "navmesh.hpp"
|
||||
|
||||
#include <components/debug/debugging.hpp>
|
||||
#include <components/detournavigator/navmeshdb.hpp>
|
||||
#include <components/detournavigator/recastglobalallocator.hpp>
|
||||
#include <components/esm/esmreader.hpp>
|
||||
#include <components/esm/variant.hpp>
|
||||
#include <components/esmloader/esmdata.hpp>
|
||||
#include <components/esmloader/load.hpp>
|
||||
#include <components/fallback/fallback.hpp>
|
||||
#include <components/fallback/validate.hpp>
|
||||
#include <components/files/configurationmanager.hpp>
|
||||
#include <components/resource/bulletshapemanager.hpp>
|
||||
#include <components/resource/imagemanager.hpp>
|
||||
#include <components/resource/niffilemanager.hpp>
|
||||
#include <components/resource/scenemanager.hpp>
|
||||
#include <components/settings/settings.hpp>
|
||||
#include <components/to_utf8/to_utf8.hpp>
|
||||
#include <components/version/version.hpp>
|
||||
#include <components/vfs/manager.hpp>
|
||||
#include <components/vfs/registerarchives.hpp>
|
||||
|
||||
#include <osg/Vec3f>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/program_options.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace NavMeshTool
|
||||
{
|
||||
namespace
|
||||
{
|
||||
namespace bpo = boost::program_options;
|
||||
|
||||
using StringsVector = std::vector<std::string>;
|
||||
|
||||
bpo::options_description makeOptionsDescription()
|
||||
{
|
||||
using Fallback::FallbackMap;
|
||||
|
||||
bpo::options_description result;
|
||||
|
||||
result.add_options()
|
||||
("help", "print help message")
|
||||
|
||||
("version", "print version information and quit")
|
||||
|
||||
("data", bpo::value<Files::MaybeQuotedPathContainer>()->default_value(Files::MaybeQuotedPathContainer(), "data")
|
||||
->multitoken()->composing(), "set data directories (later directories have higher priority)")
|
||||
|
||||
("data-local", bpo::value<Files::MaybeQuotedPathContainer::value_type>()->default_value(Files::MaybeQuotedPathContainer::value_type(), ""),
|
||||
"set local data directory (highest priority)")
|
||||
|
||||
("fallback-archive", bpo::value<StringsVector>()->default_value(StringsVector(), "fallback-archive")
|
||||
->multitoken()->composing(), "set fallback BSA archives (later archives have higher priority)")
|
||||
|
||||
("resources", bpo::value<Files::MaybeQuotedPath>()->default_value(Files::MaybeQuotedPath(), "resources"),
|
||||
"set resources directory")
|
||||
|
||||
("content", bpo::value<StringsVector>()->default_value(StringsVector(), "")
|
||||
->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon/omwscripts")
|
||||
|
||||
("fs-strict", bpo::value<bool>()->implicit_value(true)
|
||||
->default_value(false), "strict file system handling (no case folding)")
|
||||
|
||||
("encoding", bpo::value<std::string>()->
|
||||
default_value("win1252"),
|
||||
"Character encoding used in OpenMW game messages:\n"
|
||||
"\n\twin1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages\n"
|
||||
"\n\twin1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages\n"
|
||||
"\n\twin1252 - Western European (Latin) alphabet, used by default")
|
||||
|
||||
("fallback", bpo::value<Fallback::FallbackMap>()->default_value(Fallback::FallbackMap(), "")
|
||||
->multitoken()->composing(), "fallback values")
|
||||
|
||||
("threads", bpo::value<std::size_t>()->default_value(std::max<std::size_t>(std::thread::hardware_concurrency() - 1, 1)),
|
||||
"number of threads for parallel processing")
|
||||
|
||||
("process-interior-cells", bpo::value<bool>()->implicit_value(true)
|
||||
->default_value(false), "build navmesh for interior cells")
|
||||
;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void loadSettings(const Files::ConfigurationManager& config, Settings::Manager& settings)
|
||||
{
|
||||
const std::string localDefault = (config.getLocalPath() / "defaults.bin").string();
|
||||
const std::string globalDefault = (config.getGlobalPath() / "defaults.bin").string();
|
||||
|
||||
if (boost::filesystem::exists(localDefault))
|
||||
settings.loadDefault(localDefault);
|
||||
else if (boost::filesystem::exists(globalDefault))
|
||||
settings.loadDefault(globalDefault);
|
||||
else
|
||||
throw std::runtime_error("No default settings file found! Make sure the file \"defaults.bin\" was properly installed.");
|
||||
|
||||
const std::string settingsPath = (config.getUserConfigPath() / "settings.cfg").string();
|
||||
if (boost::filesystem::exists(settingsPath))
|
||||
settings.loadUser(settingsPath);
|
||||
}
|
||||
|
||||
int runNavMeshTool(int argc, char *argv[])
|
||||
{
|
||||
bpo::options_description desc = makeOptionsDescription();
|
||||
|
||||
bpo::parsed_options options = bpo::command_line_parser(argc, argv)
|
||||
.options(desc).allow_unregistered().run();
|
||||
bpo::variables_map variables;
|
||||
|
||||
bpo::store(options, variables);
|
||||
bpo::notify(variables);
|
||||
|
||||
if (variables.find("help") != variables.end())
|
||||
{
|
||||
getRawStdout() << desc << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
Files::ConfigurationManager config;
|
||||
|
||||
bpo::variables_map composingVariables = Files::separateComposingVariables(variables, desc);
|
||||
config.readConfiguration(variables, desc);
|
||||
Files::mergeComposingVariables(variables, composingVariables, desc);
|
||||
|
||||
const std::string encoding(variables["encoding"].as<std::string>());
|
||||
Log(Debug::Info) << ToUTF8::encodingUsingMessage(encoding);
|
||||
ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(encoding));
|
||||
|
||||
Files::PathContainer dataDirs(asPathContainer(variables["data"].as<Files::MaybeQuotedPathContainer>()));
|
||||
|
||||
auto local = variables["data-local"].as<Files::MaybeQuotedPathContainer::value_type>();
|
||||
if (!local.empty())
|
||||
dataDirs.push_back(std::move(local));
|
||||
|
||||
config.processPaths(dataDirs);
|
||||
|
||||
const auto fsStrict = variables["fs-strict"].as<bool>();
|
||||
const auto resDir = variables["resources"].as<Files::MaybeQuotedPath>();
|
||||
Version::Version v = Version::getOpenmwVersion(resDir.string());
|
||||
Log(Debug::Info) << v.describe();
|
||||
dataDirs.insert(dataDirs.begin(), resDir / "vfs");
|
||||
const auto fileCollections = Files::Collections(dataDirs, !fsStrict);
|
||||
const auto archives = variables["fallback-archive"].as<StringsVector>();
|
||||
const auto contentFiles = variables["content"].as<StringsVector>();
|
||||
const std::size_t threadsNumber = variables["threads"].as<std::size_t>();
|
||||
|
||||
if (threadsNumber < 1)
|
||||
{
|
||||
std::cerr << "Invalid threads number: " << threadsNumber << ", expected >= 1";
|
||||
return -1;
|
||||
}
|
||||
|
||||
const bool processInteriorCells = variables["process-interior-cells"].as<bool>();
|
||||
|
||||
Fallback::Map::init(variables["fallback"].as<Fallback::FallbackMap>().mMap);
|
||||
|
||||
VFS::Manager vfs(fsStrict);
|
||||
|
||||
VFS::registerArchives(&vfs, fileCollections, archives, true);
|
||||
|
||||
Settings::Manager settings;
|
||||
loadSettings(config, settings);
|
||||
|
||||
const osg::Vec3f agentHalfExtents = Settings::Manager::getVector3("default actor pathfind half extents", "Game");
|
||||
|
||||
DetourNavigator::NavMeshDb db((config.getUserDataPath() / "navmesh.db").string());
|
||||
|
||||
std::vector<ESM::ESMReader> readers(contentFiles.size());
|
||||
EsmLoader::Query query;
|
||||
query.mLoadActivators = true;
|
||||
query.mLoadCells = true;
|
||||
query.mLoadContainers = true;
|
||||
query.mLoadDoors = true;
|
||||
query.mLoadGameSettings = true;
|
||||
query.mLoadLands = true;
|
||||
query.mLoadStatics = true;
|
||||
const EsmLoader::EsmData esmData = EsmLoader::loadEsmData(query, contentFiles, fileCollections, readers, &encoder);
|
||||
|
||||
Resource::ImageManager imageManager(&vfs);
|
||||
Resource::NifFileManager nifFileManager(&vfs);
|
||||
Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager);
|
||||
Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager);
|
||||
DetourNavigator::RecastGlobalAllocator::init();
|
||||
DetourNavigator::Settings navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager();
|
||||
navigatorSettings.mRecast.mSwimHeightScale = EsmLoader::getGameSetting(esmData.mGameSettings, "fSwimHeightScale").getFloat();
|
||||
|
||||
WorldspaceData cellsData = gatherWorldspaceData(navigatorSettings, readers, vfs, bulletShapeManager,
|
||||
esmData, processInteriorCells);
|
||||
|
||||
generateAllNavMeshTiles(agentHalfExtents, navigatorSettings, threadsNumber, cellsData, std::move(db));
|
||||
|
||||
Log(Debug::Info) << "Done";
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
return wrapApplication(NavMeshTool::runNavMeshTool, argc, argv, "NavMeshTool");
|
||||
}
|
@ -0,0 +1,212 @@
|
||||
#include "navmesh.hpp"
|
||||
|
||||
#include "worldspacedata.hpp"
|
||||
|
||||
#include <components/bullethelpers/aabb.hpp>
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/detournavigator/generatenavmeshtile.hpp>
|
||||
#include <components/detournavigator/gettilespositions.hpp>
|
||||
#include <components/detournavigator/navmeshdb.hpp>
|
||||
#include <components/detournavigator/navmeshdbutils.hpp>
|
||||
#include <components/detournavigator/offmeshconnection.hpp>
|
||||
#include <components/detournavigator/offmeshconnectionsmanager.hpp>
|
||||
#include <components/detournavigator/preparednavmeshdata.hpp>
|
||||
#include <components/detournavigator/recastmesh.hpp>
|
||||
#include <components/detournavigator/recastmeshprovider.hpp>
|
||||
#include <components/detournavigator/serialization.hpp>
|
||||
#include <components/detournavigator/tilecachedrecastmeshmanager.hpp>
|
||||
#include <components/detournavigator/tileposition.hpp>
|
||||
#include <components/esm/loadcell.hpp>
|
||||
#include <components/misc/guarded.hpp>
|
||||
#include <components/misc/progressreporter.hpp>
|
||||
#include <components/sceneutil/workqueue.hpp>
|
||||
#include <components/sqlite3/transaction.hpp>
|
||||
|
||||
#include <DetourNavMesh.h>
|
||||
|
||||
#include <osg/Vec3f>
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace NavMeshTool
|
||||
{
|
||||
namespace
|
||||
{
|
||||
using DetourNavigator::GenerateNavMeshTile;
|
||||
using DetourNavigator::NavMeshDb;
|
||||
using DetourNavigator::NavMeshTileInfo;
|
||||
using DetourNavigator::PreparedNavMeshData;
|
||||
using DetourNavigator::RecastMeshProvider;
|
||||
using DetourNavigator::MeshSource;
|
||||
using DetourNavigator::Settings;
|
||||
using DetourNavigator::ShapeId;
|
||||
using DetourNavigator::TileId;
|
||||
using DetourNavigator::TilePosition;
|
||||
using DetourNavigator::TileVersion;
|
||||
using Sqlite3::Transaction;
|
||||
|
||||
void logGeneratedTiles(std::size_t provided, std::size_t expected)
|
||||
{
|
||||
Log(Debug::Info) << provided << "/" << expected << " ("
|
||||
<< (static_cast<double>(provided) / static_cast<double>(expected) * 100)
|
||||
<< "%) navmesh tiles are generated";
|
||||
}
|
||||
|
||||
struct LogGeneratedTiles
|
||||
{
|
||||
void operator()(std::size_t provided, std::size_t expected) const
|
||||
{
|
||||
logGeneratedTiles(provided, expected);
|
||||
}
|
||||
};
|
||||
|
||||
class NavMeshTileConsumer final : public DetourNavigator::NavMeshTileConsumer
|
||||
{
|
||||
public:
|
||||
std::atomic_size_t mExpected {0};
|
||||
|
||||
explicit NavMeshTileConsumer(NavMeshDb db)
|
||||
: mDb(std::move(db))
|
||||
, mTransaction(mDb.startTransaction())
|
||||
, mNextTileId(mDb.getMaxTileId() + 1)
|
||||
, mNextShapeId(mDb.getMaxShapeId() + 1)
|
||||
{}
|
||||
|
||||
std::size_t getProvided() const { return mProvided.load(); }
|
||||
|
||||
std::size_t getInserted() const { return mInserted.load(); }
|
||||
|
||||
std::size_t getUpdated() const { return mUpdated.load(); }
|
||||
|
||||
std::int64_t resolveMeshSource(const MeshSource& source) override
|
||||
{
|
||||
const std::lock_guard lock(mMutex);
|
||||
return DetourNavigator::resolveMeshSource(mDb, source, mNextShapeId);
|
||||
}
|
||||
|
||||
std::optional<NavMeshTileInfo> find(const std::string& worldspace, const TilePosition &tilePosition,
|
||||
const std::vector<std::byte> &input) override
|
||||
{
|
||||
std::optional<NavMeshTileInfo> result;
|
||||
std::lock_guard lock(mMutex);
|
||||
if (const auto tile = mDb.findTile(worldspace, tilePosition, input))
|
||||
{
|
||||
NavMeshTileInfo info;
|
||||
info.mTileId = tile->mTileId;
|
||||
info.mVersion = tile->mVersion;
|
||||
result.emplace(info);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void ignore() override { report(); }
|
||||
|
||||
void insert(const std::string& worldspace, const TilePosition& tilePosition, std::int64_t version,
|
||||
const std::vector<std::byte>& input, PreparedNavMeshData& data) override
|
||||
{
|
||||
data.mUserId = static_cast<unsigned>(mNextTileId);
|
||||
{
|
||||
std::lock_guard lock(mMutex);
|
||||
mDb.insertTile(mNextTileId, worldspace, tilePosition, TileVersion {version}, input, serialize(data));
|
||||
++mNextTileId.t;
|
||||
}
|
||||
++mInserted;
|
||||
report();
|
||||
}
|
||||
|
||||
void update(std::int64_t tileId, std::int64_t version, PreparedNavMeshData& data) override
|
||||
{
|
||||
data.mUserId = static_cast<unsigned>(tileId);
|
||||
{
|
||||
std::lock_guard lock(mMutex);
|
||||
mDb.updateTile(TileId {tileId}, TileVersion {version}, serialize(data));
|
||||
}
|
||||
++mUpdated;
|
||||
report();
|
||||
}
|
||||
|
||||
void wait()
|
||||
{
|
||||
constexpr std::size_t tilesPerTransaction = 3000;
|
||||
std::unique_lock lock(mMutex);
|
||||
while (mProvided < mExpected)
|
||||
{
|
||||
mHasTile.wait(lock);
|
||||
if (mProvided % tilesPerTransaction == 0)
|
||||
{
|
||||
mTransaction.commit();
|
||||
mTransaction = mDb.startTransaction();
|
||||
}
|
||||
}
|
||||
logGeneratedTiles(mProvided, mExpected);
|
||||
}
|
||||
|
||||
void commit() { mTransaction.commit(); }
|
||||
|
||||
private:
|
||||
std::atomic_size_t mProvided {0};
|
||||
std::atomic_size_t mInserted {0};
|
||||
std::atomic_size_t mUpdated {0};
|
||||
std::mutex mMutex;
|
||||
NavMeshDb mDb;
|
||||
Transaction mTransaction;
|
||||
TileId mNextTileId;
|
||||
std::condition_variable mHasTile;
|
||||
Misc::ProgressReporter<LogGeneratedTiles> mReporter;
|
||||
ShapeId mNextShapeId;
|
||||
|
||||
void report()
|
||||
{
|
||||
mReporter(mProvided + 1, mExpected);
|
||||
++mProvided;
|
||||
mHasTile.notify_one();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void generateAllNavMeshTiles(const osg::Vec3f& agentHalfExtents, const Settings& settings,
|
||||
const std::size_t threadsNumber, WorldspaceData& data, NavMeshDb&& db)
|
||||
{
|
||||
Log(Debug::Info) << "Generating navmesh tiles by " << threadsNumber << " parallel workers...";
|
||||
|
||||
SceneUtil::WorkQueue workQueue(threadsNumber);
|
||||
auto navMeshTileConsumer = std::make_shared<NavMeshTileConsumer>(std::move(db));
|
||||
std::size_t tiles = 0;
|
||||
|
||||
for (const std::unique_ptr<WorldspaceNavMeshInput>& input : data.mNavMeshInputs)
|
||||
{
|
||||
DetourNavigator::getTilesPositions(
|
||||
Misc::Convert::toOsg(input->mAabb.m_min), Misc::Convert::toOsg(input->mAabb.m_max), settings.mRecast,
|
||||
[&] (const TilePosition& tilePosition)
|
||||
{
|
||||
workQueue.addWorkItem(new GenerateNavMeshTile(
|
||||
input->mWorldspace,
|
||||
tilePosition,
|
||||
RecastMeshProvider(input->mTileCachedRecastMeshManager),
|
||||
agentHalfExtents,
|
||||
settings,
|
||||
navMeshTileConsumer
|
||||
));
|
||||
|
||||
++tiles;
|
||||
});
|
||||
|
||||
navMeshTileConsumer->mExpected = tiles;
|
||||
}
|
||||
|
||||
navMeshTileConsumer->wait();
|
||||
navMeshTileConsumer->commit();
|
||||
|
||||
Log(Debug::Info) << "Generated navmesh for " << navMeshTileConsumer->getProvided() << " tiles, "
|
||||
<< navMeshTileConsumer->getInserted() << " are inserted and "
|
||||
<< navMeshTileConsumer->getUpdated() << " updated";
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
#ifndef OPENMW_NAVMESHTOOL_NAVMESH_H
|
||||
#define OPENMW_NAVMESHTOOL_NAVMESH_H
|
||||
|
||||
#include <osg/Vec3f>
|
||||
|
||||
#include <cstddef>
|
||||
#include <string_view>
|
||||
|
||||
namespace DetourNavigator
|
||||
{
|
||||
class NavMeshDb;
|
||||
struct Settings;
|
||||
}
|
||||
|
||||
namespace NavMeshTool
|
||||
{
|
||||
struct WorldspaceData;
|
||||
|
||||
void generateAllNavMeshTiles(const osg::Vec3f& agentHalfExtents, const DetourNavigator::Settings& settings,
|
||||
const std::size_t threadsNumber, WorldspaceData& cellsData, DetourNavigator::NavMeshDb&& db);
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,201 @@
|
||||
#include "settings.hpp"
|
||||
|
||||
#include <components/detournavigator/asyncnavmeshupdater.hpp>
|
||||
#include <components/detournavigator/makenavmesh.hpp>
|
||||
#include <components/detournavigator/serialization.hpp>
|
||||
#include <components/loadinglistener/loadinglistener.hpp>
|
||||
#include <components/detournavigator/navmeshdbutils.hpp>
|
||||
#include <components/detournavigator/dbrefgeometryobject.hpp>
|
||||
|
||||
#include <DetourNavMesh.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace
|
||||
{
|
||||
using namespace testing;
|
||||
using namespace DetourNavigator;
|
||||
using namespace DetourNavigator::Tests;
|
||||
|
||||
void addHeightFieldPlane(TileCachedRecastMeshManager& recastMeshManager)
|
||||
{
|
||||
const osg::Vec2i cellPosition(0, 0);
|
||||
const int cellSize = 8192;
|
||||
recastMeshManager.addHeightfield(cellPosition, cellSize, HeightfieldPlane {0});
|
||||
}
|
||||
|
||||
struct DetourNavigatorAsyncNavMeshUpdaterTest : Test
|
||||
{
|
||||
Settings mSettings = makeSettings();
|
||||
TileCachedRecastMeshManager mRecastMeshManager {mSettings.mRecast};
|
||||
OffMeshConnectionsManager mOffMeshConnectionsManager {mSettings.mRecast};
|
||||
const osg::Vec3f mAgentHalfExtents {29, 29, 66};
|
||||
const TilePosition mPlayerTile {0, 0};
|
||||
const std::string mWorldspace = "sys::default";
|
||||
Loading::Listener mListener;
|
||||
};
|
||||
|
||||
TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, for_all_jobs_done_when_empty_wait_should_terminate)
|
||||
{
|
||||
AsyncNavMeshUpdater updater {mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr};
|
||||
updater.wait(mListener, WaitConditionType::allJobsDone);
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, for_required_tiles_present_when_empty_wait_should_terminate)
|
||||
{
|
||||
AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr);
|
||||
updater.wait(mListener, WaitConditionType::requiredTilesPresent);
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, post_should_generate_navmesh_tile)
|
||||
{
|
||||
mRecastMeshManager.setWorldspace(mWorldspace);
|
||||
addHeightFieldPlane(mRecastMeshManager);
|
||||
AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr);
|
||||
const auto navMeshCacheItem = std::make_shared<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), 1);
|
||||
const std::map<TilePosition, ChangeType> changedTiles {{TilePosition {0, 0}, ChangeType::add}};
|
||||
updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
|
||||
updater.wait(mListener, WaitConditionType::allJobsDone);
|
||||
EXPECT_NE(navMeshCacheItem->lockConst()->getImpl().getTileRefAt(0, 0, 0), 0);
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, repeated_post_should_lead_to_cache_hit)
|
||||
{
|
||||
mRecastMeshManager.setWorldspace(mWorldspace);
|
||||
addHeightFieldPlane(mRecastMeshManager);
|
||||
AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr);
|
||||
const auto navMeshCacheItem = std::make_shared<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), 1);
|
||||
const std::map<TilePosition, ChangeType> changedTiles {{TilePosition {0, 0}, ChangeType::add}};
|
||||
updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
|
||||
updater.wait(mListener, WaitConditionType::allJobsDone);
|
||||
{
|
||||
const auto stats = updater.getStats();
|
||||
ASSERT_EQ(stats.mCache.mGetCount, 1);
|
||||
ASSERT_EQ(stats.mCache.mHitCount, 0);
|
||||
}
|
||||
updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
|
||||
updater.wait(mListener, WaitConditionType::allJobsDone);
|
||||
{
|
||||
const auto stats = updater.getStats();
|
||||
EXPECT_EQ(stats.mCache.mGetCount, 2);
|
||||
EXPECT_EQ(stats.mCache.mHitCount, 1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, post_for_update_change_type_should_not_update_cache)
|
||||
{
|
||||
mRecastMeshManager.setWorldspace(mWorldspace);
|
||||
addHeightFieldPlane(mRecastMeshManager);
|
||||
AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr);
|
||||
const auto navMeshCacheItem = std::make_shared<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), 1);
|
||||
const std::map<TilePosition, ChangeType> changedTiles {{TilePosition {0, 0}, ChangeType::update}};
|
||||
updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
|
||||
updater.wait(mListener, WaitConditionType::allJobsDone);
|
||||
{
|
||||
const auto stats = updater.getStats();
|
||||
ASSERT_EQ(stats.mCache.mGetCount, 1);
|
||||
ASSERT_EQ(stats.mCache.mHitCount, 0);
|
||||
}
|
||||
updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
|
||||
updater.wait(mListener, WaitConditionType::allJobsDone);
|
||||
{
|
||||
const auto stats = updater.getStats();
|
||||
EXPECT_EQ(stats.mCache.mGetCount, 2);
|
||||
EXPECT_EQ(stats.mCache.mHitCount, 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, post_should_write_generated_tile_to_db)
|
||||
{
|
||||
mRecastMeshManager.setWorldspace(mWorldspace);
|
||||
addHeightFieldPlane(mRecastMeshManager);
|
||||
auto db = std::make_unique<NavMeshDb>(":memory:");
|
||||
NavMeshDb* const dbPtr = db.get();
|
||||
AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, std::move(db));
|
||||
const auto navMeshCacheItem = std::make_shared<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), 1);
|
||||
const TilePosition tilePosition {0, 0};
|
||||
const std::map<TilePosition, ChangeType> changedTiles {{tilePosition, ChangeType::add}};
|
||||
updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
|
||||
updater.wait(mListener, WaitConditionType::allJobsDone);
|
||||
const auto recastMesh = mRecastMeshManager.getMesh(mWorldspace, tilePosition);
|
||||
ASSERT_NE(recastMesh, nullptr);
|
||||
ShapeId nextShapeId {1};
|
||||
const std::vector<DbRefGeometryObject> objects = makeDbRefGeometryObjects(recastMesh->getMeshSources(),
|
||||
[&] (const MeshSource& v) { return resolveMeshSource(*dbPtr, v, nextShapeId); });
|
||||
const auto tile = dbPtr->findTile(mWorldspace, tilePosition, serialize(mSettings.mRecast, *recastMesh, objects));
|
||||
ASSERT_TRUE(tile.has_value());
|
||||
EXPECT_EQ(tile->mTileId, 1);
|
||||
EXPECT_EQ(tile->mVersion, mSettings.mNavMeshVersion);
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, post_when_writing_to_db_disabled_should_not_write)
|
||||
{
|
||||
mRecastMeshManager.setWorldspace(mWorldspace);
|
||||
addHeightFieldPlane(mRecastMeshManager);
|
||||
auto db = std::make_unique<NavMeshDb>(":memory:");
|
||||
NavMeshDb* const dbPtr = db.get();
|
||||
mSettings.mWriteToNavMeshDb = false;
|
||||
AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, std::move(db));
|
||||
const auto navMeshCacheItem = std::make_shared<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), 1);
|
||||
const TilePosition tilePosition {0, 0};
|
||||
const std::map<TilePosition, ChangeType> changedTiles {{tilePosition, ChangeType::add}};
|
||||
updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
|
||||
updater.wait(mListener, WaitConditionType::allJobsDone);
|
||||
const auto recastMesh = mRecastMeshManager.getMesh(mWorldspace, tilePosition);
|
||||
ASSERT_NE(recastMesh, nullptr);
|
||||
ShapeId nextShapeId {1};
|
||||
const std::vector<DbRefGeometryObject> objects = makeDbRefGeometryObjects(recastMesh->getMeshSources(),
|
||||
[&] (const MeshSource& v) { return resolveMeshSource(*dbPtr, v, nextShapeId); });
|
||||
const auto tile = dbPtr->findTile(mWorldspace, tilePosition, serialize(mSettings.mRecast, *recastMesh, objects));
|
||||
ASSERT_FALSE(tile.has_value());
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, post_should_read_from_db_on_cache_miss)
|
||||
{
|
||||
mRecastMeshManager.setWorldspace(mWorldspace);
|
||||
addHeightFieldPlane(mRecastMeshManager);
|
||||
mSettings.mMaxNavMeshTilesCacheSize = 0;
|
||||
AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, std::make_unique<NavMeshDb>(":memory:"));
|
||||
const auto navMeshCacheItem = std::make_shared<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), 1);
|
||||
const std::map<TilePosition, ChangeType> changedTiles {{TilePosition {0, 0}, ChangeType::add}};
|
||||
updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
|
||||
updater.wait(mListener, WaitConditionType::allJobsDone);
|
||||
{
|
||||
const auto stats = updater.getStats();
|
||||
ASSERT_EQ(stats.mCache.mGetCount, 1);
|
||||
ASSERT_EQ(stats.mCache.mHitCount, 0);
|
||||
ASSERT_TRUE(stats.mDb.has_value());
|
||||
ASSERT_EQ(stats.mDb->mGetTileCount, 1);
|
||||
ASSERT_EQ(stats.mDbGetTileHits, 0);
|
||||
}
|
||||
updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
|
||||
updater.wait(mListener, WaitConditionType::allJobsDone);
|
||||
{
|
||||
const auto stats = updater.getStats();
|
||||
EXPECT_EQ(stats.mCache.mGetCount, 2);
|
||||
EXPECT_EQ(stats.mCache.mHitCount, 0);
|
||||
ASSERT_TRUE(stats.mDb.has_value());
|
||||
EXPECT_EQ(stats.mDb->mGetTileCount, 2);
|
||||
EXPECT_EQ(stats.mDbGetTileHits, 1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, on_changing_player_tile_post_should_remove_tiles_out_of_range)
|
||||
{
|
||||
mRecastMeshManager.setWorldspace(mWorldspace);
|
||||
addHeightFieldPlane(mRecastMeshManager);
|
||||
AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr);
|
||||
const auto navMeshCacheItem = std::make_shared<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), 1);
|
||||
const std::map<TilePosition, ChangeType> changedTilesAdd {{TilePosition {0, 0}, ChangeType::add}};
|
||||
updater.post(mAgentHalfExtents, navMeshCacheItem, mPlayerTile, mWorldspace, changedTilesAdd);
|
||||
updater.wait(mListener, WaitConditionType::allJobsDone);
|
||||
ASSERT_NE(navMeshCacheItem->lockConst()->getImpl().getTileRefAt(0, 0, 0), 0);
|
||||
const std::map<TilePosition, ChangeType> changedTilesRemove {{TilePosition {0, 0}, ChangeType::remove}};
|
||||
const TilePosition playerTile(100, 100);
|
||||
updater.post(mAgentHalfExtents, navMeshCacheItem, playerTile, mWorldspace, changedTilesRemove);
|
||||
updater.wait(mListener, WaitConditionType::allJobsDone);
|
||||
EXPECT_EQ(navMeshCacheItem->lockConst()->getImpl().getTileRefAt(0, 0, 0), 0);
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
#ifndef OPENMW_TEST_SUITE_DETOURNAVIGATOR_GENERATE_H
|
||||
#define OPENMW_TEST_SUITE_DETOURNAVIGATOR_GENERATE_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
#include <random>
|
||||
#include <type_traits>
|
||||
|
||||
namespace DetourNavigator
|
||||
{
|
||||
namespace Tests
|
||||
{
|
||||
template <class T, class Random>
|
||||
inline auto generateValue(T& value, Random& random)
|
||||
-> std::enable_if_t<sizeof(T) >= 2>
|
||||
{
|
||||
using Distribution = std::conditional_t<
|
||||
std::is_floating_point_v<T>,
|
||||
std::uniform_real_distribution<T>,
|
||||
std::uniform_int_distribution<T>
|
||||
>;
|
||||
Distribution distribution(std::numeric_limits<T>::min(), std::numeric_limits<T>::max());
|
||||
value = distribution(random);
|
||||
}
|
||||
|
||||
template <class T, class Random>
|
||||
inline auto generateValue(T& value, Random& random)
|
||||
-> std::enable_if_t<sizeof(T) == 1>
|
||||
{
|
||||
unsigned short v;
|
||||
generateValue(v, random);
|
||||
value = static_cast<T>(v % 256);
|
||||
}
|
||||
|
||||
template <class Random>
|
||||
inline void generateValue(unsigned char& value, Random& random)
|
||||
{
|
||||
unsigned short v;
|
||||
generateValue(v, random);
|
||||
value = static_cast<unsigned char>(v % 256);
|
||||
}
|
||||
|
||||
template <class I, class Random>
|
||||
inline void generateRange(I begin, I end, Random& random)
|
||||
{
|
||||
std::for_each(begin, end, [&] (auto& v) { generateValue(v, random); });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
@ -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));
|
||||
}
|
||||
}
|
@ -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
|
@ -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
|
||||
{
|
@ -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
|
@ -0,0 +1,95 @@
|
||||
#include "generatenavmeshtile.hpp"
|
||||
|
||||
#include "dbrefgeometryobject.hpp"
|
||||
#include "makenavmesh.hpp"
|
||||
#include "offmeshconnectionsmanager.hpp"
|
||||
#include "preparednavmeshdata.hpp"
|
||||
#include "serialization.hpp"
|
||||
#include "settings.hpp"
|
||||
#include "tilecachedrecastmeshmanager.hpp"
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
|
||||
#include <osg/Vec3f>
|
||||
#include <osg/io_utils>
|
||||
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include <functional>
|
||||
|
||||
namespace DetourNavigator
|
||||
{
|
||||
namespace
|
||||
{
|
||||
struct Ignore
|
||||
{
|
||||
std::shared_ptr<NavMeshTileConsumer> mConsumer;
|
||||
|
||||
~Ignore() noexcept
|
||||
{
|
||||
if (mConsumer != nullptr)
|
||||
mConsumer->ignore();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
GenerateNavMeshTile::GenerateNavMeshTile(std::string worldspace, const TilePosition& tilePosition,
|
||||
RecastMeshProvider recastMeshProvider, const osg::Vec3f& agentHalfExtents,
|
||||
const DetourNavigator::Settings& settings, std::weak_ptr<NavMeshTileConsumer> consumer)
|
||||
: mWorldspace(std::move(worldspace))
|
||||
, mTilePosition(tilePosition)
|
||||
, mRecastMeshProvider(recastMeshProvider)
|
||||
, mAgentHalfExtents(agentHalfExtents)
|
||||
, mSettings(settings)
|
||||
, mConsumer(std::move(consumer)) {}
|
||||
|
||||
void GenerateNavMeshTile::doWork()
|
||||
{
|
||||
impl();
|
||||
}
|
||||
|
||||
void GenerateNavMeshTile::impl() noexcept
|
||||
{
|
||||
const auto consumer = mConsumer.lock();
|
||||
|
||||
if (consumer == nullptr)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
Ignore ignore {consumer};
|
||||
|
||||
const std::shared_ptr<RecastMesh> recastMesh = mRecastMeshProvider.getMesh(mWorldspace, mTilePosition);
|
||||
|
||||
if (recastMesh == nullptr || isEmpty(*recastMesh))
|
||||
return;
|
||||
|
||||
const std::vector<DbRefGeometryObject> objects = makeDbRefGeometryObjects(recastMesh->getMeshSources(),
|
||||
[&] (const MeshSource& v) { return consumer->resolveMeshSource(v); });
|
||||
std::vector<std::byte> input = serialize(mSettings.mRecast, *recastMesh, objects);
|
||||
const std::optional<NavMeshTileInfo> info = consumer->find(mWorldspace, mTilePosition, input);
|
||||
|
||||
if (info.has_value() && info->mVersion == mSettings.mNavMeshVersion)
|
||||
return;
|
||||
|
||||
const auto data = prepareNavMeshTileData(*recastMesh, mTilePosition, mAgentHalfExtents, mSettings.mRecast);
|
||||
|
||||
if (data == nullptr)
|
||||
return;
|
||||
|
||||
if (info.has_value())
|
||||
consumer->update(info->mTileId, mSettings.mNavMeshVersion, *data);
|
||||
else
|
||||
consumer->insert(mWorldspace, mTilePosition, mSettings.mNavMeshVersion, input, *data);
|
||||
|
||||
ignore.mConsumer = nullptr;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
Log(Debug::Warning) << "Failed to generate navmesh for worldspace \"" << mWorldspace
|
||||
<< "\" tile " << mTilePosition << ": " << e.what();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_GENERATENAVMESHTILE_H
|
||||
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_GENERATENAVMESHTILE_H
|
||||
|
||||
#include "recastmeshprovider.hpp"
|
||||
#include "tileposition.hpp"
|
||||
|
||||
#include <components/sceneutil/workqueue.hpp>
|
||||
|
||||
#include <osg/Vec3f>
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace DetourNavigator
|
||||
{
|
||||
class OffMeshConnectionsManager;
|
||||
class RecastMesh;
|
||||
struct NavMeshTileConsumer;
|
||||
struct OffMeshConnection;
|
||||
struct PreparedNavMeshData;
|
||||
struct Settings;
|
||||
|
||||
struct NavMeshTileInfo
|
||||
{
|
||||
std::int64_t mTileId;
|
||||
std::int64_t mVersion;
|
||||
};
|
||||
|
||||
struct NavMeshTileConsumer
|
||||
{
|
||||
virtual ~NavMeshTileConsumer() = default;
|
||||
|
||||
virtual std::int64_t resolveMeshSource(const MeshSource& source) = 0;
|
||||
|
||||
virtual std::optional<NavMeshTileInfo> find(const std::string& worldspace, const TilePosition& tilePosition,
|
||||
const std::vector<std::byte>& input) = 0;
|
||||
|
||||
virtual void ignore() = 0;
|
||||
|
||||
virtual void insert(const std::string& worldspace, const TilePosition& tilePosition,
|
||||
std::int64_t version, const std::vector<std::byte>& input, PreparedNavMeshData& data) = 0;
|
||||
|
||||
virtual void update(std::int64_t tileId, std::int64_t version, PreparedNavMeshData& data) = 0;
|
||||
};
|
||||
|
||||
class GenerateNavMeshTile final : public SceneUtil::WorkItem
|
||||
{
|
||||
public:
|
||||
GenerateNavMeshTile(std::string worldspace, const TilePosition& tilePosition,
|
||||
RecastMeshProvider recastMeshProvider, const osg::Vec3f& agentHalfExtents, const Settings& settings,
|
||||
std::weak_ptr<NavMeshTileConsumer> consumer);
|
||||
|
||||
void doWork() final;
|
||||
|
||||
private:
|
||||
const std::string mWorldspace;
|
||||
const TilePosition mTilePosition;
|
||||
const RecastMeshProvider mRecastMeshProvider;
|
||||
const osg::Vec3f mAgentHalfExtents;
|
||||
const Settings& mSettings;
|
||||
std::weak_ptr<NavMeshTileConsumer> mConsumer;
|
||||
|
||||
inline void impl() noexcept;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,296 @@
|
||||
#include "navmeshdb.hpp"
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/misc/compression.hpp>
|
||||
#include <components/sqlite3/db.hpp>
|
||||
#include <components/sqlite3/request.hpp>
|
||||
|
||||
#include <DetourAlloc.h>
|
||||
|
||||
#include <sqlite3.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace DetourNavigator
|
||||
{
|
||||
namespace
|
||||
{
|
||||
constexpr const char schema[] = R"(
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tiles (
|
||||
tile_id INTEGER PRIMARY KEY,
|
||||
revision INTEGER NOT NULL DEFAULT 1,
|
||||
worldspace TEXT NOT NULL,
|
||||
tile_position_x INTEGER NOT NULL,
|
||||
tile_position_y INTEGER NOT NULL,
|
||||
version INTEGER NOT NULL,
|
||||
input BLOB,
|
||||
data BLOB
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS index_unique_tiles_by_worldspace_and_tile_position_and_input
|
||||
ON tiles (worldspace, tile_position_x, tile_position_y, input);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS shapes (
|
||||
shape_id INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
type INTEGER NOT NULL,
|
||||
hash BLOB NOT NULL
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS index_unique_shapes_by_name_and_type_and_hash
|
||||
ON shapes (name, type, hash);
|
||||
|
||||
COMMIT;
|
||||
)";
|
||||
|
||||
constexpr std::string_view getMaxTileIdQuery = R"(
|
||||
SELECT max(tile_id) FROM tiles
|
||||
)";
|
||||
|
||||
constexpr std::string_view findTileQuery = R"(
|
||||
SELECT tile_id, version
|
||||
FROM tiles
|
||||
WHERE worldspace = :worldspace
|
||||
AND tile_position_x = :tile_position_x
|
||||
AND tile_position_y = :tile_position_y
|
||||
AND input = :input
|
||||
)";
|
||||
|
||||
constexpr std::string_view getTileDataQuery = R"(
|
||||
SELECT tile_id, version, data
|
||||
FROM tiles
|
||||
WHERE worldspace = :worldspace
|
||||
AND tile_position_x = :tile_position_x
|
||||
AND tile_position_y = :tile_position_y
|
||||
AND input = :input
|
||||
)";
|
||||
|
||||
constexpr std::string_view insertTileQuery = R"(
|
||||
INSERT INTO tiles ( tile_id, worldspace, version, tile_position_x, tile_position_y, input, data)
|
||||
VALUES (:tile_id, :worldspace, :version, :tile_position_x, :tile_position_y, :input, :data)
|
||||
)";
|
||||
|
||||
constexpr std::string_view updateTileQuery = R"(
|
||||
UPDATE tiles
|
||||
SET version = :version,
|
||||
data = :data,
|
||||
revision = revision + 1
|
||||
WHERE tile_id = :tile_id
|
||||
)";
|
||||
|
||||
constexpr std::string_view getMaxShapeIdQuery = R"(
|
||||
SELECT max(shape_id) FROM shapes
|
||||
)";
|
||||
|
||||
constexpr std::string_view findShapeIdQuery = R"(
|
||||
SELECT shape_id
|
||||
FROM shapes
|
||||
WHERE name = :name
|
||||
AND type = :type
|
||||
AND hash = :hash
|
||||
)";
|
||||
|
||||
constexpr std::string_view insertShapeQuery = R"(
|
||||
INSERT INTO shapes ( shape_id, name, type, hash)
|
||||
VALUES (:shape_id, :name, :type, :hash)
|
||||
)";
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, ShapeType value)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case ShapeType::Collision: return stream << "collision";
|
||||
case ShapeType::Avoid: return stream << "avoid";
|
||||
}
|
||||
return stream << "unknown shape type (" << static_cast<std::underlying_type_t<ShapeType>>(value) << ")";
|
||||
}
|
||||
|
||||
NavMeshDb::NavMeshDb(std::string_view path)
|
||||
: mDb(Sqlite3::makeDb(path, schema))
|
||||
, mGetMaxTileId(*mDb, DbQueries::GetMaxTileId {})
|
||||
, mFindTile(*mDb, DbQueries::FindTile {})
|
||||
, mGetTileData(*mDb, DbQueries::GetTileData {})
|
||||
, mInsertTile(*mDb, DbQueries::InsertTile {})
|
||||
, mUpdateTile(*mDb, DbQueries::UpdateTile {})
|
||||
, mGetMaxShapeId(*mDb, DbQueries::GetMaxShapeId {})
|
||||
, mFindShapeId(*mDb, DbQueries::FindShapeId {})
|
||||
, mInsertShape(*mDb, DbQueries::InsertShape {})
|
||||
{
|
||||
}
|
||||
|
||||
Sqlite3::Transaction NavMeshDb::startTransaction()
|
||||
{
|
||||
return Sqlite3::Transaction(*mDb);
|
||||
}
|
||||
|
||||
TileId NavMeshDb::getMaxTileId()
|
||||
{
|
||||
TileId tileId {0};
|
||||
request(*mDb, mGetMaxTileId, &tileId, 1);
|
||||
return tileId;
|
||||
}
|
||||
|
||||
std::optional<Tile> NavMeshDb::findTile(const std::string& worldspace,
|
||||
const TilePosition& tilePosition, const std::vector<std::byte>& input)
|
||||
{
|
||||
Tile result;
|
||||
auto row = std::tie(result.mTileId, result.mVersion);
|
||||
const std::vector<std::byte> compressedInput = Misc::compress(input);
|
||||
if (&row == request(*mDb, mFindTile, &row, 1, worldspace, tilePosition, compressedInput))
|
||||
return {};
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<TileData> NavMeshDb::getTileData(const std::string& worldspace,
|
||||
const TilePosition& tilePosition, const std::vector<std::byte>& input)
|
||||
{
|
||||
TileData result;
|
||||
auto row = std::tie(result.mTileId, result.mVersion, result.mData);
|
||||
const std::vector<std::byte> compressedInput = Misc::compress(input);
|
||||
if (&row == request(*mDb, mGetTileData, &row, 1, worldspace, tilePosition, compressedInput))
|
||||
return {};
|
||||
result.mData = Misc::decompress(result.mData);
|
||||
return result;
|
||||
}
|
||||
|
||||
int NavMeshDb::insertTile(TileId tileId, const std::string& worldspace, const TilePosition& tilePosition,
|
||||
TileVersion version, const std::vector<std::byte>& input, const std::vector<std::byte>& data)
|
||||
{
|
||||
const std::vector<std::byte> compressedInput = Misc::compress(input);
|
||||
const std::vector<std::byte> compressedData = Misc::compress(data);
|
||||
return execute(*mDb, mInsertTile, tileId, worldspace, tilePosition, version, compressedInput, compressedData);
|
||||
}
|
||||
|
||||
int NavMeshDb::updateTile(TileId tileId, TileVersion version, const std::vector<std::byte>& data)
|
||||
{
|
||||
const std::vector<std::byte> compressedData = Misc::compress(data);
|
||||
return execute(*mDb, mUpdateTile, tileId, version, compressedData);
|
||||
}
|
||||
|
||||
ShapeId NavMeshDb::getMaxShapeId()
|
||||
{
|
||||
ShapeId shapeId {0};
|
||||
request(*mDb, mGetMaxShapeId, &shapeId, 1);
|
||||
return shapeId;
|
||||
}
|
||||
|
||||
std::optional<ShapeId> NavMeshDb::findShapeId(const std::string& name, ShapeType type,
|
||||
const Sqlite3::ConstBlob& hash)
|
||||
{
|
||||
ShapeId shapeId;
|
||||
if (&shapeId == request(*mDb, mFindShapeId, &shapeId, 1, name, type, hash))
|
||||
return {};
|
||||
return shapeId;
|
||||
}
|
||||
|
||||
int NavMeshDb::insertShape(ShapeId shapeId, const std::string& name, ShapeType type,
|
||||
const Sqlite3::ConstBlob& hash)
|
||||
{
|
||||
return execute(*mDb, mInsertShape, shapeId, name, type, hash);
|
||||
}
|
||||
|
||||
namespace DbQueries
|
||||
{
|
||||
std::string_view GetMaxTileId::text() noexcept
|
||||
{
|
||||
return getMaxTileIdQuery;
|
||||
}
|
||||
|
||||
std::string_view FindTile::text() noexcept
|
||||
{
|
||||
return findTileQuery;
|
||||
}
|
||||
|
||||
void FindTile::bind(sqlite3& db, sqlite3_stmt& statement, const std::string& worldspace,
|
||||
const TilePosition& tilePosition, const std::vector<std::byte>& input)
|
||||
{
|
||||
Sqlite3::bindParameter(db, statement, ":worldspace", worldspace);
|
||||
Sqlite3::bindParameter(db, statement, ":tile_position_x", tilePosition.x());
|
||||
Sqlite3::bindParameter(db, statement, ":tile_position_y", tilePosition.y());
|
||||
Sqlite3::bindParameter(db, statement, ":input", input);
|
||||
}
|
||||
|
||||
std::string_view GetTileData::text() noexcept
|
||||
{
|
||||
return getTileDataQuery;
|
||||
}
|
||||
|
||||
void GetTileData::bind(sqlite3& db, sqlite3_stmt& statement, const std::string& worldspace,
|
||||
const TilePosition& tilePosition, const std::vector<std::byte>& input)
|
||||
{
|
||||
Sqlite3::bindParameter(db, statement, ":worldspace", worldspace);
|
||||
Sqlite3::bindParameter(db, statement, ":tile_position_x", tilePosition.x());
|
||||
Sqlite3::bindParameter(db, statement, ":tile_position_y", tilePosition.y());
|
||||
Sqlite3::bindParameter(db, statement, ":input", input);
|
||||
}
|
||||
|
||||
std::string_view InsertTile::text() noexcept
|
||||
{
|
||||
return insertTileQuery;
|
||||
}
|
||||
|
||||
void InsertTile::bind(sqlite3& db, sqlite3_stmt& statement, TileId tileId, const std::string& worldspace,
|
||||
const TilePosition& tilePosition, TileVersion version, const std::vector<std::byte>& input,
|
||||
const std::vector<std::byte>& data)
|
||||
{
|
||||
Sqlite3::bindParameter(db, statement, ":tile_id", tileId);
|
||||
Sqlite3::bindParameter(db, statement, ":worldspace", worldspace);
|
||||
Sqlite3::bindParameter(db, statement, ":tile_position_x", tilePosition.x());
|
||||
Sqlite3::bindParameter(db, statement, ":tile_position_y", tilePosition.y());
|
||||
Sqlite3::bindParameter(db, statement, ":version", version);
|
||||
Sqlite3::bindParameter(db, statement, ":input", input);
|
||||
Sqlite3::bindParameter(db, statement, ":data", data);
|
||||
}
|
||||
|
||||
std::string_view UpdateTile::text() noexcept
|
||||
{
|
||||
return updateTileQuery;
|
||||
}
|
||||
|
||||
void UpdateTile::bind(sqlite3& db, sqlite3_stmt& statement, TileId tileId, TileVersion version,
|
||||
const std::vector<std::byte>& data)
|
||||
{
|
||||
Sqlite3::bindParameter(db, statement, ":tile_id", tileId);
|
||||
Sqlite3::bindParameter(db, statement, ":version", version);
|
||||
Sqlite3::bindParameter(db, statement, ":data", data);
|
||||
}
|
||||
|
||||
std::string_view GetMaxShapeId::text() noexcept
|
||||
{
|
||||
return getMaxShapeIdQuery;
|
||||
}
|
||||
|
||||
std::string_view FindShapeId::text() noexcept
|
||||
{
|
||||
return findShapeIdQuery;
|
||||
}
|
||||
|
||||
void FindShapeId::bind(sqlite3& db, sqlite3_stmt& statement, const std::string& name,
|
||||
ShapeType type, const Sqlite3::ConstBlob& hash)
|
||||
{
|
||||
Sqlite3::bindParameter(db, statement, ":name", name);
|
||||
Sqlite3::bindParameter(db, statement, ":type", static_cast<int>(type));
|
||||
Sqlite3::bindParameter(db, statement, ":hash", hash);
|
||||
}
|
||||
|
||||
std::string_view InsertShape::text() noexcept
|
||||
{
|
||||
return insertShapeQuery;
|
||||
}
|
||||
|
||||
void InsertShape::bind(sqlite3& db, sqlite3_stmt& statement, ShapeId shapeId, const std::string& name,
|
||||
ShapeType type, const Sqlite3::ConstBlob& hash)
|
||||
{
|
||||
Sqlite3::bindParameter(db, statement, ":shape_id", shapeId);
|
||||
Sqlite3::bindParameter(db, statement, ":name", name);
|
||||
Sqlite3::bindParameter(db, statement, ":type", static_cast<int>(type));
|
||||
Sqlite3::bindParameter(db, statement, ":hash", hash);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,153 @@
|
||||
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHDB_H
|
||||
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHDB_H
|
||||
|
||||
#include "tileposition.hpp"
|
||||
|
||||
#include <components/sqlite3/db.hpp>
|
||||
#include <components/sqlite3/statement.hpp>
|
||||
#include <components/sqlite3/transaction.hpp>
|
||||
#include <components/sqlite3/types.hpp>
|
||||
|
||||
#include <boost/serialization/strong_typedef.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
struct sqlite3;
|
||||
struct sqlite3_stmt;
|
||||
|
||||
namespace DetourNavigator
|
||||
{
|
||||
BOOST_STRONG_TYPEDEF(std::int64_t, TileId)
|
||||
BOOST_STRONG_TYPEDEF(std::int64_t, TileRevision)
|
||||
BOOST_STRONG_TYPEDEF(std::int64_t, TileVersion)
|
||||
BOOST_STRONG_TYPEDEF(std::int64_t, ShapeId)
|
||||
|
||||
struct Tile
|
||||
{
|
||||
TileId mTileId;
|
||||
TileVersion mVersion;
|
||||
};
|
||||
|
||||
struct TileData
|
||||
{
|
||||
TileId mTileId;
|
||||
TileVersion mVersion;
|
||||
std::vector<std::byte> mData;
|
||||
};
|
||||
|
||||
enum class ShapeType
|
||||
{
|
||||
Collision = 1,
|
||||
Avoid = 2,
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, ShapeType value);
|
||||
|
||||
namespace DbQueries
|
||||
{
|
||||
struct GetMaxTileId
|
||||
{
|
||||
static std::string_view text() noexcept;
|
||||
static void bind(sqlite3&, sqlite3_stmt&) {}
|
||||
};
|
||||
|
||||
struct FindTile
|
||||
{
|
||||
static std::string_view text() noexcept;
|
||||
static void bind(sqlite3& db, sqlite3_stmt& statement, const std::string& worldspace,
|
||||
const TilePosition& tilePosition, const std::vector<std::byte>& input);
|
||||
};
|
||||
|
||||
struct GetTileData
|
||||
{
|
||||
static std::string_view text() noexcept;
|
||||
static void bind(sqlite3& db, sqlite3_stmt& statement, const std::string& worldspace,
|
||||
const TilePosition& tilePosition, const std::vector<std::byte>& input);
|
||||
};
|
||||
|
||||
struct InsertTile
|
||||
{
|
||||
static std::string_view text() noexcept;
|
||||
static void bind(sqlite3& db, sqlite3_stmt& statement, TileId tileId, const std::string& worldspace,
|
||||
const TilePosition& tilePosition, TileVersion version, const std::vector<std::byte>& input,
|
||||
const std::vector<std::byte>& data);
|
||||
};
|
||||
|
||||
struct UpdateTile
|
||||
{
|
||||
static std::string_view text() noexcept;
|
||||
static void bind(sqlite3& db, sqlite3_stmt& statement, TileId tileId, TileVersion version,
|
||||
const std::vector<std::byte>& data);
|
||||
};
|
||||
|
||||
struct GetMaxShapeId
|
||||
{
|
||||
static std::string_view text() noexcept;
|
||||
static void bind(sqlite3&, sqlite3_stmt&) {}
|
||||
};
|
||||
|
||||
struct FindShapeId
|
||||
{
|
||||
static std::string_view text() noexcept;
|
||||
static void bind(sqlite3& db, sqlite3_stmt& statement, const std::string& name,
|
||||
ShapeType type, const Sqlite3::ConstBlob& hash);
|
||||
};
|
||||
|
||||
struct InsertShape
|
||||
{
|
||||
static std::string_view text() noexcept;
|
||||
static void bind(sqlite3& db, sqlite3_stmt& statement, ShapeId shapeId, const std::string& name,
|
||||
ShapeType type, const Sqlite3::ConstBlob& hash);
|
||||
};
|
||||
}
|
||||
|
||||
class NavMeshDb
|
||||
{
|
||||
public:
|
||||
explicit NavMeshDb(std::string_view path);
|
||||
|
||||
Sqlite3::Transaction startTransaction();
|
||||
|
||||
TileId getMaxTileId();
|
||||
|
||||
std::optional<Tile> findTile(const std::string& worldspace,
|
||||
const TilePosition& tilePosition, const std::vector<std::byte>& input);
|
||||
|
||||
std::optional<TileData> getTileData(const std::string& worldspace,
|
||||
const TilePosition& tilePosition, const std::vector<std::byte>& input);
|
||||
|
||||
int insertTile(TileId tileId, const std::string& worldspace, const TilePosition& tilePosition,
|
||||
TileVersion version, const std::vector<std::byte>& input, const std::vector<std::byte>& data);
|
||||
|
||||
int updateTile(TileId tileId, TileVersion version, const std::vector<std::byte>& data);
|
||||
|
||||
ShapeId getMaxShapeId();
|
||||
|
||||
std::optional<ShapeId> findShapeId(const std::string& name, ShapeType type, const Sqlite3::ConstBlob& hash);
|
||||
|
||||
int insertShape(ShapeId shapeId, const std::string& name, ShapeType type, const Sqlite3::ConstBlob& hash);
|
||||
|
||||
private:
|
||||
Sqlite3::Db mDb;
|
||||
Sqlite3::Statement<DbQueries::GetMaxTileId> mGetMaxTileId;
|
||||
Sqlite3::Statement<DbQueries::FindTile> mFindTile;
|
||||
Sqlite3::Statement<DbQueries::GetTileData> mGetTileData;
|
||||
Sqlite3::Statement<DbQueries::InsertTile> mInsertTile;
|
||||
Sqlite3::Statement<DbQueries::UpdateTile> mUpdateTile;
|
||||
Sqlite3::Statement<DbQueries::GetMaxShapeId> mGetMaxShapeId;
|
||||
Sqlite3::Statement<DbQueries::FindShapeId> mFindShapeId;
|
||||
Sqlite3::Statement<DbQueries::InsertShape> mInsertShape;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,40 @@
|
||||
#include "navmeshdbutils.hpp"
|
||||
#include "navmeshdb.hpp"
|
||||
#include "recastmesh.hpp"
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace DetourNavigator
|
||||
{
|
||||
namespace
|
||||
{
|
||||
ShapeId getShapeId(NavMeshDb& db, const std::string& name, ShapeType type, const std::string& hash, ShapeId& nextShapeId)
|
||||
{
|
||||
const Sqlite3::ConstBlob hashData {hash.data(), static_cast<int>(hash.size())};
|
||||
if (const auto existingShapeId = db.findShapeId(name, type, hashData))
|
||||
return *existingShapeId;
|
||||
const ShapeId newShapeId = nextShapeId;
|
||||
db.insertShape(newShapeId, name, type, hashData);
|
||||
Log(Debug::Verbose) << "Added " << name << " " << type << " shape to navmeshdb with id " << newShapeId;
|
||||
++nextShapeId.t;
|
||||
return newShapeId;
|
||||
}
|
||||
}
|
||||
|
||||
ShapeId resolveMeshSource(NavMeshDb& db, const MeshSource& source, ShapeId& nextShapeId)
|
||||
{
|
||||
switch (source.mAreaType)
|
||||
{
|
||||
case AreaType_null:
|
||||
return getShapeId(db, source.mShape->mFileName, ShapeType::Avoid, source.mShape->mFileHash, nextShapeId);
|
||||
case AreaType_ground:
|
||||
return getShapeId(db, source.mShape->mFileName, ShapeType::Collision, source.mShape->mFileHash, nextShapeId);
|
||||
default:
|
||||
Log(Debug::Warning) << "Trying to resolve recast mesh source with unsupported area type: " << source.mAreaType;
|
||||
assert(false);
|
||||
return ShapeId(0);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHDBUTILS_H
|
||||
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHDBUTILS_H
|
||||
|
||||
#include "navmeshdb.hpp"
|
||||
|
||||
namespace DetourNavigator
|
||||
{
|
||||
struct MeshSource;
|
||||
|
||||
ShapeId resolveMeshSource(NavMeshDb& db, const MeshSource& source, ShapeId& nextShapeId);
|
||||
}
|
||||
|
||||
#endif
|
@ -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
|
@ -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));
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHPROVIDER_H
|
||||
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHPROVIDER_H
|
||||
|
||||
#include "tileposition.hpp"
|
||||
#include "recastmesh.hpp"
|
||||
#include "tilecachedrecastmeshmanager.hpp"
|
||||
#include "version.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
namespace DetourNavigator
|
||||
{
|
||||
class RecastMesh;
|
||||
|
||||
class RecastMeshProvider
|
||||
{
|
||||
public:
|
||||
RecastMeshProvider(TileCachedRecastMeshManager& impl)
|
||||
: mImpl(impl)
|
||||
{}
|
||||
|
||||
std::shared_ptr<RecastMesh> getMesh(std::string_view worldspace, const TilePosition& tilePosition) const
|
||||
{
|
||||
return mImpl.get().getNewMesh(worldspace, tilePosition);
|
||||
}
|
||||
|
||||
private:
|
||||
std::reference_wrapper<TileCachedRecastMeshManager> mImpl;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,272 @@
|
||||
#include "serialization.hpp"
|
||||
|
||||
#include "dbrefgeometryobject.hpp"
|
||||
#include "preparednavmeshdata.hpp"
|
||||
#include "recast.hpp"
|
||||
#include "recastmesh.hpp"
|
||||
#include "settings.hpp"
|
||||
|
||||
#include <components/serialization/binaryreader.hpp>
|
||||
#include <components/serialization/binarywriter.hpp>
|
||||
#include <components/serialization/format.hpp>
|
||||
#include <components/serialization/sizeaccumulator.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace DetourNavigator
|
||||
{
|
||||
namespace
|
||||
{
|
||||
template <Serialization::Mode mode>
|
||||
struct Format : Serialization::Format<mode, Format<mode>>
|
||||
{
|
||||
using Serialization::Format<mode, Format<mode>>::operator();
|
||||
|
||||
template <class Visitor>
|
||||
void operator()(Visitor&& visitor, const osg::Vec2i& value) const
|
||||
{
|
||||
visitor(*this, value.ptr(), 2);
|
||||
}
|
||||
|
||||
template <class Visitor>
|
||||
void operator()(Visitor&& visitor, const osg::Vec2f& value) const
|
||||
{
|
||||
visitor(*this, value.ptr(), 2);
|
||||
}
|
||||
|
||||
template <class Visitor>
|
||||
void operator()(Visitor&& visitor, const osg::Vec3f& value) const
|
||||
{
|
||||
visitor(*this, value.ptr(), 3);
|
||||
}
|
||||
|
||||
template <class Visitor>
|
||||
void operator()(Visitor&& visitor, const Water& value) const
|
||||
{
|
||||
visitor(*this, value.mCellSize);
|
||||
visitor(*this, value.mLevel);
|
||||
}
|
||||
|
||||
template <class Visitor>
|
||||
void operator()(Visitor&& visitor, const CellWater& value) const
|
||||
{
|
||||
visitor(*this, value.mCellPosition);
|
||||
visitor(*this, value.mWater);
|
||||
}
|
||||
|
||||
template <class Visitor>
|
||||
void operator()(Visitor&& visitor, const RecastSettings& value) const
|
||||
{
|
||||
visitor(*this, value.mCellHeight);
|
||||
visitor(*this, value.mCellSize);
|
||||
visitor(*this, value.mDetailSampleDist);
|
||||
visitor(*this, value.mDetailSampleMaxError);
|
||||
visitor(*this, value.mMaxClimb);
|
||||
visitor(*this, value.mMaxSimplificationError);
|
||||
visitor(*this, value.mMaxSlope);
|
||||
visitor(*this, value.mRecastScaleFactor);
|
||||
visitor(*this, value.mSwimHeightScale);
|
||||
visitor(*this, value.mBorderSize);
|
||||
visitor(*this, value.mMaxEdgeLen);
|
||||
visitor(*this, value.mMaxVertsPerPoly);
|
||||
visitor(*this, value.mRegionMergeArea);
|
||||
visitor(*this, value.mRegionMinArea);
|
||||
visitor(*this, value.mTileSize);
|
||||
}
|
||||
|
||||
template <class Visitor>
|
||||
void operator()(Visitor&& visitor, const TileBounds& value) const
|
||||
{
|
||||
visitor(*this, value.mMin);
|
||||
visitor(*this, value.mMax);
|
||||
}
|
||||
|
||||
template <class Visitor>
|
||||
void operator()(Visitor&& visitor, const Heightfield& value) const
|
||||
{
|
||||
visitor(*this, value.mCellPosition);
|
||||
visitor(*this, value.mCellSize);
|
||||
visitor(*this, value.mLength);
|
||||
visitor(*this, value.mMinHeight);
|
||||
visitor(*this, value.mMaxHeight);
|
||||
visitor(*this, value.mHeights);
|
||||
visitor(*this, value.mOriginalSize);
|
||||
visitor(*this, value.mMinX);
|
||||
visitor(*this, value.mMinY);
|
||||
}
|
||||
|
||||
template <class Visitor>
|
||||
void operator()(Visitor&& visitor, const FlatHeightfield& value) const
|
||||
{
|
||||
visitor(*this, value.mCellPosition);
|
||||
visitor(*this, value.mCellSize);
|
||||
visitor(*this, value.mHeight);
|
||||
}
|
||||
|
||||
template <class Visitor>
|
||||
void operator()(Visitor&& visitor, const RecastMesh& value) const
|
||||
{
|
||||
visitor(*this, value.getWater());
|
||||
visitor(*this, value.getHeightfields());
|
||||
visitor(*this, value.getFlatHeightfields());
|
||||
}
|
||||
|
||||
template <class Visitor>
|
||||
void operator()(Visitor&& visitor, const ESM::Position& value) const
|
||||
{
|
||||
visitor(*this, value.pos);
|
||||
visitor(*this, value.rot);
|
||||
}
|
||||
|
||||
template <class Visitor>
|
||||
void operator()(Visitor&& visitor, const ObjectTransform& value) const
|
||||
{
|
||||
visitor(*this, value.mPosition);
|
||||
visitor(*this, value.mScale);
|
||||
}
|
||||
|
||||
template <class Visitor>
|
||||
void operator()(Visitor&& visitor, const DbRefGeometryObject& value) const
|
||||
{
|
||||
visitor(*this, value.mShapeId);
|
||||
visitor(*this, value.mObjectTransform);
|
||||
}
|
||||
|
||||
template <class Visitor>
|
||||
void operator()(Visitor&& visitor, const RecastSettings& settings, const RecastMesh& recastMesh,
|
||||
const std::vector<DbRefGeometryObject>& dbRefGeometryObjects) const
|
||||
{
|
||||
visitor(*this, DetourNavigator::recastMeshMagic);
|
||||
visitor(*this, DetourNavigator::recastMeshVersion);
|
||||
visitor(*this, settings);
|
||||
visitor(*this, recastMesh);
|
||||
visitor(*this, dbRefGeometryObjects);
|
||||
}
|
||||
|
||||
template <class Visitor, class T>
|
||||
auto operator()(Visitor&& visitor, T& value) const
|
||||
-> std::enable_if_t<std::is_same_v<std::decay_t<T>, rcPolyMesh>>
|
||||
{
|
||||
visitor(*this, value.nverts);
|
||||
visitor(*this, value.npolys);
|
||||
visitor(*this, value.maxpolys);
|
||||
visitor(*this, value.nvp);
|
||||
visitor(*this, value.bmin);
|
||||
visitor(*this, value.bmax);
|
||||
visitor(*this, value.cs);
|
||||
visitor(*this, value.ch);
|
||||
visitor(*this, value.borderSize);
|
||||
visitor(*this, value.maxEdgeError);
|
||||
if constexpr (mode == Serialization::Mode::Read)
|
||||
{
|
||||
if (value.verts == nullptr)
|
||||
permRecastAlloc(value.verts, getVertsLength(value));
|
||||
if (value.polys == nullptr)
|
||||
permRecastAlloc(value.polys, getPolysLength(value));
|
||||
if (value.regs == nullptr)
|
||||
permRecastAlloc(value.regs, getRegsLength(value));
|
||||
if (value.flags == nullptr)
|
||||
permRecastAlloc(value.flags, getFlagsLength(value));
|
||||
if (value.areas == nullptr)
|
||||
permRecastAlloc(value.areas, getAreasLength(value));
|
||||
}
|
||||
visitor(*this, value.verts, getVertsLength(value));
|
||||
visitor(*this, value.polys, getPolysLength(value));
|
||||
visitor(*this, value.regs, getRegsLength(value));
|
||||
visitor(*this, value.flags, getFlagsLength(value));
|
||||
visitor(*this, value.areas, getAreasLength(value));
|
||||
}
|
||||
|
||||
template <class Visitor, class T>
|
||||
auto operator()(Visitor&& visitor, T& value) const
|
||||
-> std::enable_if_t<std::is_same_v<std::decay_t<T>, rcPolyMeshDetail>>
|
||||
{
|
||||
visitor(*this, value.nmeshes);
|
||||
if constexpr (mode == Serialization::Mode::Read)
|
||||
if (value.meshes == nullptr)
|
||||
permRecastAlloc(value.meshes, getMeshesLength(value));
|
||||
visitor(*this, value.meshes, getMeshesLength(value));
|
||||
visitor(*this, value.nverts);
|
||||
if constexpr (mode == Serialization::Mode::Read)
|
||||
if (value.verts == nullptr)
|
||||
permRecastAlloc(value.verts, getVertsLength(value));
|
||||
visitor(*this, value.verts, getVertsLength(value));
|
||||
visitor(*this, value.ntris);
|
||||
if constexpr (mode == Serialization::Mode::Read)
|
||||
if (value.tris == nullptr)
|
||||
permRecastAlloc(value.tris, getTrisLength(value));
|
||||
visitor(*this, value.tris, getTrisLength(value));
|
||||
}
|
||||
|
||||
template <class Visitor, class T>
|
||||
auto operator()(Visitor&& visitor, T& value) const
|
||||
-> std::enable_if_t<std::is_same_v<std::decay_t<T>, PreparedNavMeshData>>
|
||||
{
|
||||
if constexpr (mode == Serialization::Mode::Write)
|
||||
{
|
||||
visitor(*this, DetourNavigator::preparedNavMeshDataMagic);
|
||||
visitor(*this, DetourNavigator::preparedNavMeshDataVersion);
|
||||
}
|
||||
else
|
||||
{
|
||||
static_assert(mode == Serialization::Mode::Read);
|
||||
char magic[std::size(DetourNavigator::preparedNavMeshDataMagic)];
|
||||
visitor(*this, magic);
|
||||
if (std::memcmp(magic, DetourNavigator::preparedNavMeshDataMagic, sizeof(magic)) != 0)
|
||||
throw std::runtime_error("Bad PreparedNavMeshData magic");
|
||||
std::uint32_t version = 0;
|
||||
visitor(*this, version);
|
||||
if (version != DetourNavigator::preparedNavMeshDataVersion)
|
||||
throw std::runtime_error("Bad PreparedNavMeshData version");
|
||||
}
|
||||
visitor(*this, value.mUserId);
|
||||
visitor(*this, value.mCellSize);
|
||||
visitor(*this, value.mCellHeight);
|
||||
visitor(*this, value.mPolyMesh);
|
||||
visitor(*this, value.mPolyMeshDetail);
|
||||
}
|
||||
};
|
||||
}
|
||||
} // namespace DetourNavigator
|
||||
|
||||
namespace DetourNavigator
|
||||
{
|
||||
std::vector<std::byte> serialize(const RecastSettings& settings, const RecastMesh& recastMesh,
|
||||
const std::vector<DbRefGeometryObject>& dbRefGeometryObjects)
|
||||
{
|
||||
constexpr Format<Serialization::Mode::Write> format;
|
||||
Serialization::SizeAccumulator sizeAccumulator;
|
||||
format(sizeAccumulator, settings, recastMesh, dbRefGeometryObjects);
|
||||
std::vector<std::byte> result(sizeAccumulator.value());
|
||||
format(Serialization::BinaryWriter(result.data(), result.data() + result.size()),
|
||||
settings, recastMesh, dbRefGeometryObjects);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::byte> serialize(const PreparedNavMeshData& value)
|
||||
{
|
||||
constexpr Format<Serialization::Mode::Write> format;
|
||||
Serialization::SizeAccumulator sizeAccumulator;
|
||||
format(sizeAccumulator, value);
|
||||
std::vector<std::byte> result(sizeAccumulator.value());
|
||||
format(Serialization::BinaryWriter(result.data(), result.data() + result.size()), value);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool deserialize(const std::vector<std::byte>& data, PreparedNavMeshData& value)
|
||||
{
|
||||
try
|
||||
{
|
||||
constexpr Format<Serialization::Mode::Read> format;
|
||||
format(Serialization::BinaryReader(data.data(), data.data() + data.size()), value);
|
||||
return true;
|
||||
}
|
||||
catch (const std::exception&)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SERIALIZATION_H
|
||||
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_SERIALIZATION_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace DetourNavigator
|
||||
{
|
||||
class RecastMesh;
|
||||
struct DbRefGeometryObject;
|
||||
struct PreparedNavMeshData;
|
||||
struct RecastSettings;
|
||||
|
||||
constexpr char recastMeshMagic[] = {'r', 'c', 's', 't'};
|
||||
constexpr std::uint32_t recastMeshVersion = 1;
|
||||
|
||||
constexpr char preparedNavMeshDataMagic[] = {'p', 'n', 'a', 'v'};
|
||||
constexpr std::uint32_t preparedNavMeshDataVersion = 1;
|
||||
|
||||
std::vector<std::byte> serialize(const RecastSettings& settings, const RecastMesh& value,
|
||||
const std::vector<DbRefGeometryObject>& dbRefGeometryObjects);
|
||||
|
||||
std::vector<std::byte> serialize(const PreparedNavMeshData& value);
|
||||
|
||||
bool deserialize(const std::vector<std::byte>& data, PreparedNavMeshData& value);
|
||||
}
|
||||
|
||||
#endif
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue