mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-01-27 12:35:46 +00:00
Add a binary to generate navmesh from content files
Load content files based on the engine config files. Generate navmesh per cell for all cells and store into SQLite database.
This commit is contained in:
parent
b5c689976e
commit
953a4c5550
@ -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
|
||||
|
@ -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}" )
|
||||
|
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
|
329
apps/navmeshtool/worldspacedata.cpp
Normal file
329
apps/navmeshtool/worldspacedata.cpp
Normal file
@ -0,0 +1,329 @@
|
||||
#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;
|
||||
}
|
||||
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
|
@ -41,6 +41,8 @@ if (GTEST_FOUND AND GMOCK_FOUND)
|
||||
detournavigator/recastmeshobject.cpp
|
||||
detournavigator/navmeshtilescache.cpp
|
||||
detournavigator/tilecachedrecastmeshmanager.cpp
|
||||
detournavigator/navmeshdb.cpp
|
||||
detournavigator/serialization.cpp
|
||||
|
||||
serialization/binaryreader.cpp
|
||||
serialization/binarywriter.cpp
|
||||
|
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
|
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,6 +22,7 @@ namespace
|
||||
{
|
||||
using namespace testing;
|
||||
using namespace DetourNavigator;
|
||||
using namespace DetourNavigator::Tests;
|
||||
|
||||
void* permRecastAlloc(int size)
|
||||
{
|
||||
@ -30,14 +32,15 @@ namespace
|
||||
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,40 +48,43 @@ 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;
|
||||
}
|
||||
|
||||
|
@ -204,6 +204,9 @@ add_component_dir(detournavigator
|
||||
navmeshcacheitem
|
||||
navigatorutils
|
||||
generatenavmeshtile
|
||||
navmeshdb
|
||||
serialization
|
||||
navmeshdbutils
|
||||
)
|
||||
|
||||
add_component_dir(loadinglistener
|
||||
|
@ -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
|
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(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
|
@ -114,6 +114,30 @@ namespace
|
||||
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)
|
||||
{
|
||||
@ -399,27 +423,13 @@ namespace
|
||||
|
||||
namespace DetourNavigator
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
std::unique_ptr<PreparedNavMeshData> prepareNavMeshTileData(const RecastMesh& recastMesh,
|
||||
const TilePosition& tilePosition, const osg::Vec3f& agentHalfExtents, const RecastSettings& settings)
|
||||
{
|
||||
const auto [minZ, maxZ] = getBoundsByZ(recastMesh, agentHalfExtents, settings);
|
||||
|
||||
rcContext context;
|
||||
|
||||
const auto [minZ, maxZ] = getBoundsByZ(recastMesh, agentHalfExtents, settings);
|
||||
|
||||
rcHeightfield solid;
|
||||
initHeightfield(context, tilePosition, toNavMeshCoordinates(settings, minZ),
|
||||
toNavMeshCoordinates(settings, maxZ), settings, solid);
|
||||
@ -549,8 +559,7 @@ namespace DetourNavigator
|
||||
return navMeshCacheItem->lock()->removeTile(changedTile);
|
||||
}
|
||||
|
||||
if (recastMesh->getMesh().getIndices().empty() && recastMesh->getWater().empty()
|
||||
&& recastMesh->getHeightfields().empty() && recastMesh->getFlatHeightfields().empty())
|
||||
if (isEmpty(*recastMesh))
|
||||
{
|
||||
Log(Debug::Debug) << "Ignore add tile: recastMesh is empty";
|
||||
return navMeshCacheItem->lock()->removeTile(changedTile);
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include <vector>
|
||||
|
||||
class dtNavMesh;
|
||||
struct rcConfig;
|
||||
|
||||
namespace DetourNavigator
|
||||
{
|
||||
@ -22,16 +23,6 @@ namespace DetourNavigator
|
||||
struct PreparedNavMeshData;
|
||||
struct NavMeshData;
|
||||
|
||||
struct RecastParams
|
||||
{
|
||||
float mSampleDist = 0;
|
||||
float mSampleMaxError = 0;
|
||||
int mMaxEdgeLen = 0;
|
||||
int mWalkableClimb = 0;
|
||||
int mWalkableHeight = 0;
|
||||
int mWalkableRadius = 0;
|
||||
};
|
||||
|
||||
inline float getLength(const osg::Vec2i& value)
|
||||
{
|
||||
return std::sqrt(float(osg::square(value.x()) + osg::square(value.y())));
|
||||
@ -48,10 +39,16 @@ namespace DetourNavigator
|
||||
return expectedTilesCount <= maxTiles;
|
||||
}
|
||||
|
||||
RecastParams makeRecastParams(const RecastSettings& settings, const osg::Vec3f& agentHalfExtents);
|
||||
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& tile,
|
||||
const Bounds& bounds, const osg::Vec3f& agentHalfExtents, const Settings& settings);
|
||||
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,
|
||||
|
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
|
@ -15,6 +15,11 @@ namespace DetourNavigator
|
||||
{
|
||||
}
|
||||
|
||||
explicit ObjectId(std::size_t value) noexcept
|
||||
: mValue(value)
|
||||
{
|
||||
}
|
||||
|
||||
std::size_t value() const noexcept
|
||||
{
|
||||
return mValue;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -24,7 +24,7 @@ namespace DetourNavigator
|
||||
|
||||
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
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(const TilePosition& tilePosition) const
|
||||
{
|
||||
return mImpl.get().getNewMesh(tilePosition);
|
||||
}
|
||||
|
||||
private:
|
||||
std::reference_wrapper<TileCachedRecastMeshManager> mImpl;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
214
components/detournavigator/serialization.cpp
Normal file
214
components/detournavigator/serialization.cpp
Normal file
@ -0,0 +1,214 @@
|
||||
#include "serialization.hpp"
|
||||
|
||||
#include "dbrefgeometryobject.hpp"
|
||||
#include "preparednavmeshdata.hpp"
|
||||
#include "recastmesh.hpp"
|
||||
#include "settings.hpp"
|
||||
|
||||
#include <components/serialization/binarywriter.hpp>
|
||||
#include <components/serialization/format.hpp>
|
||||
#include <components/serialization/sizeaccumulator.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#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>
|
||||
auto operator()(Visitor&& visitor, const rcPolyMesh& value) const
|
||||
{
|
||||
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);
|
||||
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>
|
||||
auto operator()(Visitor&& visitor, const rcPolyMeshDetail& value) const
|
||||
{
|
||||
visitor(*this, value.nmeshes);
|
||||
visitor(*this, value.meshes, getMeshesLength(value));
|
||||
visitor(*this, value.nverts);
|
||||
visitor(*this, value.verts, getVertsLength(value));
|
||||
visitor(*this, value.ntris);
|
||||
visitor(*this, value.tris, getTrisLength(value));
|
||||
}
|
||||
|
||||
template <class Visitor>
|
||||
auto operator()(Visitor&& visitor, const PreparedNavMeshData& value) const
|
||||
{
|
||||
visitor(*this, DetourNavigator::preparedNavMeshDataMagic);
|
||||
visitor(*this, DetourNavigator::preparedNavMeshDataVersion);
|
||||
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;
|
||||
}
|
||||
}
|
27
components/detournavigator/serialization.hpp
Normal file
27
components/detournavigator/serialization.hpp
Normal file
@ -0,0 +1,27 @@
|
||||
#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);
|
||||
}
|
||||
|
||||
#endif
|
@ -61,6 +61,7 @@ namespace DetourNavigator
|
||||
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");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -48,6 +48,7 @@ namespace DetourNavigator
|
||||
std::string mRecastMeshPathPrefix;
|
||||
std::string mNavMeshPathPrefix;
|
||||
std::chrono::milliseconds mMinUpdateInterval;
|
||||
std::int64_t mNavMeshVersion = 0;
|
||||
};
|
||||
|
||||
RecastSettings makeRecastSettingsFromSettingsManager();
|
||||
|
@ -196,6 +196,13 @@ namespace DetourNavigator
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<RecastMesh> TileCachedRecastMeshManager::getNewMesh(const TilePosition& tilePosition) const
|
||||
{
|
||||
if (const auto manager = getManager(tilePosition))
|
||||
return manager->getNewMesh();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::size_t TileCachedRecastMeshManager::getRevision() const
|
||||
{
|
||||
return mRevision;
|
||||
|
@ -88,6 +88,8 @@ namespace DetourNavigator
|
||||
|
||||
std::shared_ptr<RecastMesh> getCachedMesh(const TilePosition& tilePosition) const;
|
||||
|
||||
std::shared_ptr<RecastMesh> getNewMesh(const TilePosition& tilePosition) const;
|
||||
|
||||
template <class Function>
|
||||
void forEachTile(Function&& function) const
|
||||
{
|
||||
|
@ -2,6 +2,7 @@
|
||||
#define OPENMW_COMPONENTS_SQLITE3_REQUEST_H
|
||||
|
||||
#include "statement.hpp"
|
||||
#include "types.hpp"
|
||||
|
||||
#include <sqlite3.h>
|
||||
|
||||
@ -53,6 +54,13 @@ namespace Sqlite3
|
||||
+ ": " + std::string(sqlite3_errmsg(&db)));
|
||||
}
|
||||
|
||||
inline void bindParameter(sqlite3& db, sqlite3_stmt& stmt, int index, const ConstBlob& value)
|
||||
{
|
||||
if (sqlite3_bind_blob(&stmt, index, value.mData, value.mSize, SQLITE_STATIC) != SQLITE_OK)
|
||||
throw std::runtime_error("Failed to bind blob to parameter " + std::to_string(index)
|
||||
+ ": " + std::string(sqlite3_errmsg(&db)));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void bindParameter(sqlite3& db, sqlite3_stmt& stmt, const char* name, const T& value)
|
||||
{
|
||||
|
15
components/sqlite3/types.hpp
Normal file
15
components/sqlite3/types.hpp
Normal file
@ -0,0 +1,15 @@
|
||||
#ifndef OPENMW_COMPONENTS_SQLITE3_TYPES_H
|
||||
#define OPENMW_COMPONENTS_SQLITE3_TYPES_H
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace Sqlite3
|
||||
{
|
||||
struct ConstBlob
|
||||
{
|
||||
const char* mData;
|
||||
int mSize;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
@ -464,3 +464,4 @@ default actor pathfind half extents
|
||||
:Default: 29.27999496459961 28.479997634887695 66.5
|
||||
|
||||
Actor half extents used for exterior cells to generate navmesh.
|
||||
Changing the value will invalidate navmesh disk cache.
|
||||
|
@ -206,6 +206,17 @@ Absent pieces usually mean a bug in recast mesh tiles building.
|
||||
Allows to do in-game debug.
|
||||
Potentially decreases performance.
|
||||
|
||||
nav mesh version
|
||||
----------------
|
||||
|
||||
:Type: integer
|
||||
:Range: > 0
|
||||
:Default: 1
|
||||
|
||||
Version of navigation mesh generation algorithm.
|
||||
Should be increased each time there is a difference between output of makeNavMeshTileData function for the same input.
|
||||
Changing the value will invalidate navmesh disk cache.
|
||||
|
||||
Expert settings
|
||||
***************
|
||||
|
||||
|
@ -930,6 +930,10 @@ min update interval ms = 250
|
||||
# Distance is measured in the number of tiles and can be only an integer value.
|
||||
wait until min distance to player = 5
|
||||
|
||||
# Version of navigation mesh generation algorithm.
|
||||
# Should be increased each time there is a difference between output of makeNavMeshTileData function for the same input.
|
||||
nav mesh version = 1
|
||||
|
||||
[Shadows]
|
||||
|
||||
# Enable or disable shadows. Bear in mind that this will force OpenMW to use shaders as if "[Shaders]/force shaders" was set to true.
|
||||
|
Loading…
x
Reference in New Issue
Block a user