mirror of
https://github.com/OpenMW/openmw.git
synced 2025-01-22 03:53:54 +00:00
1a12c453d6
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.
322 lines
12 KiB
C++
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;
|
|
}
|
|
}
|