1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-01-22 03:53:54 +00:00
openmw/apps/navmeshtool/navmesh.cpp
elsid 1a12c453d6
Support different agent collision shape type for pathfinding
Actors may have different collision shapes. Currently there are axis-aligned
bounding boxes and rotating bounding boxes. With AABB it's required to use
bounding cylinder for navmesh agent to avoid providing paths where actor can't
pass. But for rotating bounding boxes cylinder with diameter equal to the front
face width should be used to not reduce of available paths. For example rats
have rotating bounding box as collision shape because of the difference between
front and side faces width.

* Add agent bounds to navmesh tile db cache key. This is required to distinguish
  tiles for agents with different bounds.
* Increase navmesh version because navmesh tile db cache key and data has changed.
* Move navmesh version to the code to avoid misconfiguration by users.
* Fix all places where wrong half extents were used for pathfinding.
2022-06-21 12:57:32 +02:00

322 lines
12 KiB
C++

#include "navmesh.hpp"
#include "worldspacedata.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/preparednavmeshdata.hpp>
#include <components/detournavigator/recastmesh.hpp>
#include <components/detournavigator/recastmeshprovider.hpp>
#include <components/detournavigator/serialization.hpp>
#include <components/detournavigator/tileposition.hpp>
#include <components/misc/progressreporter.hpp>
#include <components/sceneutil/workqueue.hpp>
#include <components/sqlite3/transaction.hpp>
#include <components/debug/debugging.hpp>
#include <components/navmeshtool/protocol.hpp>
#include <osg/Vec3f>
#include <atomic>
#include <chrono>
#include <cstddef>
#include <utility>
#include <vector>
#include <random>
#include <string_view>
namespace NavMeshTool
{
namespace
{
using DetourNavigator::AgentBounds;
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 DetourNavigator::TilesPositionsRange;
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";
}
template <class T>
void serializeToStderr(const T& value)
{
const std::vector<std::byte> data = serialize(value);
getLockedRawStderr()->write(reinterpret_cast<const char*>(data.data()), static_cast<std::streamsize>(data.size()));
}
void logGeneratedTilesMessage(std::size_t number)
{
serializeToStderr(GeneratedTiles {static_cast<std::uint64_t>(number)});
}
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, bool removeUnusedTiles, bool writeBinaryLog)
: mDb(std::move(db))
, mRemoveUnusedTiles(removeUnusedTiles)
, mWriteBinaryLog(writeBinaryLog)
, mTransaction(mDb.startTransaction(Sqlite3::TransactionMode::Immediate))
, 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::size_t getDeleted() const
{
const std::lock_guard lock(mMutex);
return mDeleted;
}
std::int64_t resolveMeshSource(const MeshSource& source) override
{
const std::lock_guard lock(mMutex);
return DetourNavigator::resolveMeshSource(mDb, source, mNextShapeId);
}
std::optional<NavMeshTileInfo> find(std::string_view 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(std::string_view worldspace, const TilePosition& tilePosition) override
{
if (mRemoveUnusedTiles)
{
std::lock_guard lock(mMutex);
mDeleted += static_cast<std::size_t>(mDb.deleteTilesAt(worldspace, tilePosition));
}
report();
}
void identity(std::string_view worldspace, const TilePosition& tilePosition, std::int64_t tileId) override
{
if (mRemoveUnusedTiles)
{
std::lock_guard lock(mMutex);
mDeleted += static_cast<std::size_t>(mDb.deleteTilesAtExcept(worldspace, tilePosition, TileId {tileId}));
}
report();
}
void insert(std::string_view worldspace, const TilePosition& tilePosition,
std::int64_t version, const std::vector<std::byte>& input, PreparedNavMeshData& data) override
{
{
std::lock_guard lock(mMutex);
if (mRemoveUnusedTiles)
mDeleted += static_cast<std::size_t>(mDb.deleteTilesAt(worldspace, tilePosition));
data.mUserId = static_cast<unsigned>(mNextTileId);
mDb.insertTile(mNextTileId, worldspace, tilePosition, TileVersion {version}, input, serialize(data));
++mNextTileId;
}
++mInserted;
report();
}
void update(std::string_view worldspace, const TilePosition& tilePosition,
std::int64_t tileId, std::int64_t version, PreparedNavMeshData& data) override
{
data.mUserId = static_cast<unsigned>(tileId);
{
std::lock_guard lock(mMutex);
if (mRemoveUnusedTiles)
mDeleted += static_cast<std::size_t>(mDb.deleteTilesAtExcept(worldspace, tilePosition, TileId {tileId}));
mDb.updateTile(TileId {tileId}, TileVersion {version}, serialize(data));
}
++mUpdated;
report();
}
void cancel(std::string_view reason) override
{
std::unique_lock lock(mMutex);
if (reason.find("database or disk is full") != std::string_view::npos)
mStatus = Status::NotEnoughSpace;
else
mStatus = Status::Cancelled;
mHasTile.notify_one();
}
Status wait()
{
constexpr std::chrono::seconds transactionInterval(1);
std::unique_lock lock(mMutex);
auto start = std::chrono::steady_clock::now();
while (mProvided < mExpected && mStatus == Status::Ok)
{
mHasTile.wait(lock);
const auto now = std::chrono::steady_clock::now();
if (now - start > transactionInterval)
{
mTransaction.commit();
mTransaction = mDb.startTransaction(Sqlite3::TransactionMode::Immediate);
start = now;
}
}
logGeneratedTiles(mProvided, mExpected);
if (mWriteBinaryLog)
logGeneratedTilesMessage(mProvided);
return mStatus;
}
void commit()
{
const std::lock_guard lock(mMutex);
mTransaction.commit();
}
void vacuum()
{
const std::lock_guard lock(mMutex);
mDb.vacuum();
}
void removeTilesOutsideRange(std::string_view worldspace, const TilesPositionsRange& range)
{
const std::lock_guard lock(mMutex);
mTransaction.commit();
Log(Debug::Info) << "Removing tiles outside processed range for worldspace \"" << worldspace << "\"...";
mDeleted += static_cast<std::size_t>(mDb.deleteTilesOutsideRange(worldspace, range));
mTransaction = mDb.startTransaction(Sqlite3::TransactionMode::Immediate);
}
private:
std::atomic_size_t mProvided {0};
std::atomic_size_t mInserted {0};
std::atomic_size_t mUpdated {0};
std::size_t mDeleted = 0;
Status mStatus = Status::Ok;
mutable std::mutex mMutex;
NavMeshDb mDb;
const bool mRemoveUnusedTiles;
const bool mWriteBinaryLog;
Transaction mTransaction;
TileId mNextTileId;
std::condition_variable mHasTile;
Misc::ProgressReporter<LogGeneratedTiles> mReporter;
ShapeId mNextShapeId;
std::mutex mReportMutex;
void report()
{
const std::size_t provided = mProvided.fetch_add(1, std::memory_order_relaxed) + 1;
mReporter(provided, mExpected);
mHasTile.notify_one();
if (mWriteBinaryLog)
logGeneratedTilesMessage(provided);
}
};
}
Status generateAllNavMeshTiles(const AgentBounds& agentBounds, const Settings& settings,
std::size_t threadsNumber, bool removeUnusedTiles, bool writeBinaryLog, 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), removeUnusedTiles, writeBinaryLog);
std::size_t tiles = 0;
std::mt19937_64 random;
for (const std::unique_ptr<WorldspaceNavMeshInput>& input : data.mNavMeshInputs)
{
const auto range = DetourNavigator::makeTilesPositionsRange(
Misc::Convert::toOsgXY(input->mAabb.m_min),
Misc::Convert::toOsgXY(input->mAabb.m_max),
settings.mRecast
);
if (removeUnusedTiles)
navMeshTileConsumer->removeTilesOutsideRange(input->mWorldspace, range);
std::vector<TilePosition> worldspaceTiles;
DetourNavigator::getTilesPositions(range,
[&] (const TilePosition& tilePosition) { worldspaceTiles.push_back(tilePosition); });
tiles += worldspaceTiles.size();
if (writeBinaryLog)
serializeToStderr(ExpectedTiles {static_cast<std::uint64_t>(tiles)});
navMeshTileConsumer->mExpected = tiles;
std::shuffle(worldspaceTiles.begin(), worldspaceTiles.end(), random);
for (const TilePosition& tilePosition : worldspaceTiles)
workQueue.addWorkItem(new GenerateNavMeshTile(
input->mWorldspace,
tilePosition,
RecastMeshProvider(input->mTileCachedRecastMeshManager),
agentBounds,
settings,
navMeshTileConsumer
));
}
const Status status = navMeshTileConsumer->wait();
if (status == Status::Ok)
navMeshTileConsumer->commit();
const auto inserted = navMeshTileConsumer->getInserted();
const auto updated = navMeshTileConsumer->getUpdated();
const auto deleted = navMeshTileConsumer->getDeleted();
Log(Debug::Info) << "Generated navmesh for " << navMeshTileConsumer->getProvided() << " tiles, "
<< inserted << " are inserted, "
<< updated << " updated and "
<< deleted << " deleted";
if (inserted + updated + deleted > 0)
{
Log(Debug::Info) << "Vacuuming the database...";
navMeshTileConsumer->vacuum();
}
return status;
}
}