Merge branch 'navmesh_ignore' into 'master'

Do not trigger NavMesh update when RecastMesh update should not change NavMesh (#4917)

See merge request OpenMW/openmw!762
pull/3072/head
psi29a 4 years ago
commit b645c1f4c0

@ -127,6 +127,7 @@
Feature #3983: Wizard: Add link to buy Morrowind
Feature #4894: Consider actors as obstacles for pathfinding
Feature #4899: Alpha-To-Coverage Anti-Aliasing for alpha testing
Feature #4917: Do not trigger NavMesh update when RecastMesh update should not change NavMesh
Feature #4977: Use the "default icon.tga" when an item's icon is not found
Feature #5043: Head Bobbing
Feature #5199: OpenMW-CS: Improve scene view colors

@ -835,4 +835,55 @@ namespace
ASSERT_THAT(result, Optional(Vec3fEq(mEnd.x(), mEnd.y(), 1.87719)));
}
TEST_F(DetourNavigatorNavigatorTest, update_for_oscillating_object_that_does_not_change_navmesh_should_not_trigger_navmesh_update)
{
const std::array<btScalar, 5 * 5> heightfieldData {{
0, 0, 0, 0, 0,
0, -25, -25, -25, -25,
0, -25, -100, -100, -100,
0, -25, -100, -100, -100,
0, -25, -100, -100, -100,
}};
btHeightfieldTerrainShape heightfieldShape = makeSquareHeightfieldTerrainShape(heightfieldData);
heightfieldShape.setLocalScaling(btVector3(128, 128, 1));
const btBoxShape oscillatingBoxShape(btVector3(20, 20, 20));
const btVector3 oscillatingBoxShapePosition(32, 32, 400);
const btBoxShape boderBoxShape(btVector3(50, 50, 50));
mNavigator->addAgent(mAgentHalfExtents);
mNavigator->addObject(ObjectId(&heightfieldShape), heightfieldShape, btTransform::getIdentity());
mNavigator->addObject(ObjectId(&oscillatingBoxShape), oscillatingBoxShape,
btTransform(btMatrix3x3::getIdentity(), oscillatingBoxShapePosition));
// add this box to make navmesh bound box independent from oscillatingBoxShape rotations
mNavigator->addObject(ObjectId(&boderBoxShape), boderBoxShape,
btTransform(btMatrix3x3::getIdentity(), oscillatingBoxShapePosition + btVector3(0, 0, 200)));
mNavigator->update(mPlayerPosition);
mNavigator->wait();
const auto navMeshes = mNavigator->getNavMeshes();
ASSERT_EQ(navMeshes.size(), 1);
{
const auto navMesh = navMeshes.begin()->second->lockConst();
ASSERT_EQ(navMesh->getGeneration(), 1);
ASSERT_EQ(navMesh->getNavMeshRevision(), 4);
}
for (int n = 0; n < 10; ++n)
{
const btTransform transform(btQuaternion(btVector3(0, 0, 1), n * 2 * osg::PI / 10),
oscillatingBoxShapePosition);
mNavigator->updateObject(ObjectId(&oscillatingBoxShape), oscillatingBoxShape, transform);
mNavigator->update(mPlayerPosition);
mNavigator->wait();
}
ASSERT_EQ(navMeshes.size(), 1);
{
const auto navMesh = navMeshes.begin()->second->lockConst();
ASSERT_EQ(navMesh->getGeneration(), 1);
ASSERT_EQ(navMesh->getNavMeshRevision(), 4);
}
}
}

@ -182,6 +182,8 @@ add_component_dir(detournavigator
navigator
findrandompointaroundcircle
raycast
navmeshtileview
oscillatingrecastmeshobject
)
set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui

@ -0,0 +1,29 @@
#ifndef OPENMW_COMPONENTS_BULLETHELPERS_AABB_H
#define OPENMW_COMPONENTS_BULLETHELPERS_AABB_H
#include <LinearMath/btVector3.h>
#include <LinearMath/btTransform.h>
#include <BulletCollision/CollisionShapes/btCollisionShape.h>
#include <BulletCollision/Gimpact/btBoxCollision.h>
inline bool operator==(const btAABB& lhs, const btAABB& rhs)
{
return lhs.m_min == rhs.m_min && lhs.m_max == rhs.m_max;
}
inline bool operator!=(const btAABB& lhs, const btAABB& rhs)
{
return !(lhs == rhs);
}
namespace BulletHelpers
{
inline btAABB getAabb(const btCollisionShape& shape, const btTransform& transform)
{
btAABB result;
shape.getAabb(transform, result.m_min, result.m_max);
return result;
}
}
#endif

@ -2,6 +2,7 @@
#include "debug.hpp"
#include "makenavmesh.hpp"
#include "settings.hpp"
#include "version.hpp"
#include <components/debug/debuglog.hpp>
#include <components/misc/thread.hpp>
@ -180,6 +181,19 @@ namespace DetourNavigator
const auto status = updateNavMesh(job.mAgentHalfExtents, recastMesh.get(), job.mChangedTile, playerTile,
offMeshConnections, mSettings, navMeshCacheItem, mNavMeshTilesCache);
if (recastMesh != nullptr)
{
Version navMeshVersion;
{
const auto locked = navMeshCacheItem->lockConst();
navMeshVersion.mGeneration = locked->getGeneration();
navMeshVersion.mRevision = locked->getNavMeshRevision();
}
mRecastMeshManager.get().reportNavMeshChange(job.mChangedTile,
Version {recastMesh->getGeneration(), recastMesh->getRevision()},
navMeshVersion);
}
const auto finish = std::chrono::steady_clock::now();
writeDebugFiles(job, recastMesh.get());

@ -61,4 +61,9 @@ namespace DetourNavigator
{
return mImpl.isEmpty();
}
void CachedRecastMeshManager::reportNavMeshChange(Version recastMeshVersion, Version navMeshVersion)
{
mImpl.reportNavMeshChange(recastMeshVersion, navMeshVersion);
}
}

@ -2,6 +2,7 @@
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_CACHEDRECASTMESHMANAGER_H
#include "recastmeshmanager.hpp"
#include "version.hpp"
namespace DetourNavigator
{
@ -25,6 +26,8 @@ namespace DetourNavigator
bool isEmpty() const;
void reportNavMeshChange(Version recastMeshVersion, Version navMeshVersion);
private:
RecastMeshManager mImpl;
std::shared_ptr<RecastMesh> mCached;

@ -5,6 +5,7 @@
#include "tileposition.hpp"
#include "navmeshtilescache.hpp"
#include "dtstatus.hpp"
#include "navmeshtileview.hpp"
#include <components/misc/guarded.hpp>
@ -141,6 +142,12 @@ namespace DetourNavigator
template <class T>
UpdateNavMeshStatus updateTile(const TilePosition& position, T&& navMeshData)
{
const dtMeshTile* currentTile = getTile(position);
if (currentTile != nullptr
&& asNavMeshTileConstView(*currentTile) == asNavMeshTileConstView(getRawData(navMeshData)))
{
return UpdateNavMeshStatus::ignored;
}
const auto removed = removeTileImpl(position);
const auto addStatus = addTileImpl(getRawData(navMeshData), getSize(navMeshData));
if (dtStatusSucceed(addStatus))
@ -206,6 +213,12 @@ namespace DetourNavigator
int* const dataSize = nullptr;
return dtStatusSucceed(mImpl->removeTile(tileRef, data, dataSize));
}
const dtMeshTile* getTile(const TilePosition& position) const
{
const int layer = 0;
return mImpl->getTileAt(position.x(), position.y(), layer);
}
};
using GuardedNavMeshCacheItem = Misc::ScopeGuarded<NavMeshCacheItem>;

@ -0,0 +1,183 @@
#include "navmeshtileview.hpp"
#include <DetourCommon.h>
#include <DetourNavMesh.h>
#include <algorithm>
#include <cassert>
#include <stdexcept>
#include <tuple>
namespace
{
template <typename T>
struct Ref
{
T& mRef;
explicit Ref(T& ref) : mRef(ref) {}
friend bool operator==(const Ref& lhs, const Ref& rhs)
{
return lhs.mRef == rhs.mRef;
}
};
template <typename T, std::size_t size>
struct ArrayRef
{
T (&mRef)[size];
explicit ArrayRef(T (&ref)[size]) : mRef(ref) {}
friend bool operator==(const ArrayRef& lhs, const ArrayRef& rhs)
{
return std::equal(std::begin(lhs.mRef), std::end(lhs.mRef), std::begin(rhs.mRef));
}
};
template <typename T>
struct Span
{
T* mBegin;
T* mEnd;
explicit Span(T* data, int size) : mBegin(data), mEnd(data + static_cast<std::size_t>(size)) {}
friend bool operator==(const Span& lhs, const Span& rhs)
{
// size is already equal if headers are equal
assert((lhs.mEnd - lhs.mBegin) == (rhs.mEnd - rhs.mBegin));
return std::equal(lhs.mBegin, lhs.mEnd, rhs.mBegin);
}
};
auto makeTuple(const dtMeshHeader& v)
{
return std::tuple(
v.x,
v.y,
v.layer,
v.userId,
v.polyCount,
v.vertCount,
v.maxLinkCount,
v.detailMeshCount,
v.detailVertCount,
v.detailTriCount,
v.bvNodeCount,
v.offMeshConCount,
v.offMeshBase,
v.walkableHeight,
v.walkableRadius,
v.walkableClimb,
v.detailVertCount,
ArrayRef(v.bmin),
ArrayRef(v.bmax),
v.bvQuantFactor
);
}
auto makeTuple(const dtPoly& v)
{
return std::tuple(ArrayRef(v.verts), ArrayRef(v.neis), v.flags, v.vertCount, v.areaAndtype);
}
auto makeTuple(const dtPolyDetail& v)
{
return std::tuple(v.vertBase, v.triBase, v.vertCount, v.triCount);
}
auto makeTuple(const dtBVNode& v)
{
return std::tuple(ArrayRef(v.bmin), ArrayRef(v.bmax), v.i);
}
auto makeTuple(const dtOffMeshConnection& v)
{
return std::tuple(ArrayRef(v.pos), v.rad, v.poly, v.flags, v.side, v.userId);
}
auto makeTuple(const DetourNavigator::NavMeshTileConstView& v)
{
return std::tuple(
Ref(*v.mHeader),
Span(v.mPolys, v.mHeader->polyCount),
Span(v.mVerts, v.mHeader->vertCount),
Span(v.mDetailMeshes, v.mHeader->detailMeshCount),
Span(v.mDetailVerts, v.mHeader->detailVertCount),
Span(v.mDetailTris, v.mHeader->detailTriCount),
Span(v.mBvTree, v.mHeader->bvNodeCount),
Span(v.mOffMeshCons, v.mHeader->offMeshConCount)
);
}
}
template <class T>
inline auto operator==(const T& lhs, const T& rhs)
-> std::enable_if_t<std::is_same_v<std::void_t<decltype(makeTuple(lhs))>, void>, bool>
{
return makeTuple(lhs) == makeTuple(rhs);
}
namespace DetourNavigator
{
NavMeshTileConstView asNavMeshTileConstView(const unsigned char* data)
{
const dtMeshHeader* header = reinterpret_cast<const dtMeshHeader*>(data);
if (header->magic != DT_NAVMESH_MAGIC)
throw std::logic_error("Invalid navmesh magic");
if (header->version != DT_NAVMESH_VERSION)
throw std::logic_error("Invalid navmesh version");
// Similar code to https://github.com/recastnavigation/recastnavigation/blob/c5cbd53024c8a9d8d097a4371215e3342d2fdc87/Detour/Source/DetourNavMesh.cpp#L978-L996
const int headerSize = dtAlign4(sizeof(dtMeshHeader));
const int vertsSize = dtAlign4(sizeof(float) * 3 * header->vertCount);
const int polysSize = dtAlign4(sizeof(dtPoly) * header->polyCount);
const int linksSize = dtAlign4(sizeof(dtLink) * (header->maxLinkCount));
const int detailMeshesSize = dtAlign4(sizeof(dtPolyDetail) * header->detailMeshCount);
const int detailVertsSize = dtAlign4(sizeof(float) * 3 * header->detailVertCount);
const int detailTrisSize = dtAlign4(sizeof(unsigned char) * 4 * header->detailTriCount);
const int bvtreeSize = dtAlign4(sizeof(dtBVNode) * header->bvNodeCount);
const int offMeshLinksSize = dtAlign4(sizeof(dtOffMeshConnection) * header->offMeshConCount);
const unsigned char* ptr = data + headerSize;
NavMeshTileConstView view;
view.mHeader = header;
view.mVerts = dtGetThenAdvanceBufferPointer<const float>(ptr, vertsSize);
view.mPolys = dtGetThenAdvanceBufferPointer<const dtPoly>(ptr, polysSize);
ptr += linksSize;
view.mDetailMeshes = dtGetThenAdvanceBufferPointer<const dtPolyDetail>(ptr, detailMeshesSize);
view.mDetailVerts = dtGetThenAdvanceBufferPointer<const float>(ptr, detailVertsSize);
view.mDetailTris = dtGetThenAdvanceBufferPointer<const unsigned char>(ptr, detailTrisSize);
view.mBvTree = dtGetThenAdvanceBufferPointer<const dtBVNode>(ptr, bvtreeSize);
view.mOffMeshCons = dtGetThenAdvanceBufferPointer<const dtOffMeshConnection>(ptr, offMeshLinksSize);
return view;
}
NavMeshTileConstView asNavMeshTileConstView(const dtMeshTile& tile)
{
NavMeshTileConstView view;
view.mHeader = tile.header;
view.mPolys = tile.polys;
view.mVerts = tile.verts;
view.mDetailMeshes = tile.detailMeshes;
view.mDetailVerts = tile.detailVerts;
view.mDetailTris = tile.detailTris;
view.mBvTree = tile.bvTree;
view.mOffMeshCons = tile.offMeshCons;
return view;
}
bool operator==(const NavMeshTileConstView& lhs, const NavMeshTileConstView& rhs)
{
return makeTuple(lhs) == makeTuple(rhs);
}
}

@ -0,0 +1,31 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHTILEVIEW_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHTILEVIEW_H
struct dtMeshHeader;
struct dtPoly;
struct dtPolyDetail;
struct dtBVNode;
struct dtOffMeshConnection;
struct dtMeshTile;
namespace DetourNavigator
{
struct NavMeshTileConstView
{
const dtMeshHeader* mHeader;
const dtPoly* mPolys;
const float* mVerts;
const dtPolyDetail* mDetailMeshes;
const float* mDetailVerts;
const unsigned char* mDetailTris;
const dtBVNode* mBvTree;
const dtOffMeshConnection* mOffMeshCons;
friend bool operator==(const NavMeshTileConstView& lhs, const NavMeshTileConstView& rhs);
};
NavMeshTileConstView asNavMeshTileConstView(const unsigned char* data);
NavMeshTileConstView asNavMeshTileConstView(const dtMeshTile& tile);
}
#endif

@ -0,0 +1,35 @@
#include "oscillatingrecastmeshobject.hpp"
#include <components/bullethelpers/aabb.hpp>
namespace DetourNavigator
{
OscillatingRecastMeshObject::OscillatingRecastMeshObject(RecastMeshObject impl, std::size_t lastChangeRevision)
: mImpl(std::move(impl))
, mLastChangeRevision(lastChangeRevision)
, mAabb(BulletHelpers::getAabb(mImpl.getShape(), mImpl.getTransform()))
{
}
bool OscillatingRecastMeshObject::update(const btTransform& transform, const AreaType areaType,
std::size_t lastChangeRevision)
{
const btTransform oldTransform = mImpl.getTransform();
if (!mImpl.update(transform, areaType))
return false;
if (transform == oldTransform)
return true;
if (mLastChangeRevision != lastChangeRevision)
{
mLastChangeRevision = lastChangeRevision;
// btAABB doesn't have copy-assignment operator
const btAABB aabb = BulletHelpers::getAabb(mImpl.getShape(), transform);
mAabb.m_min = aabb.m_min;
mAabb.m_max = aabb.m_max;
return true;
}
const btAABB currentAabb = mAabb;
mAabb.merge(BulletHelpers::getAabb(mImpl.getShape(), transform));
return currentAabb != mAabb;
}
}

@ -0,0 +1,28 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_OSCILLATINGRECASTMESHOBJECT_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_OSCILLATINGRECASTMESHOBJECT_H
#include "areatype.hpp"
#include "recastmeshobject.hpp"
#include <LinearMath/btTransform.h>
#include <BulletCollision/Gimpact/btBoxCollision.h>
namespace DetourNavigator
{
class OscillatingRecastMeshObject
{
public:
explicit OscillatingRecastMeshObject(RecastMeshObject impl, std::size_t lastChangeRevision);
bool update(const btTransform& transform, const AreaType areaType, std::size_t lastChangeRevision);
const RecastMeshObject& getImpl() const { return mImpl; }
private:
RecastMeshObject mImpl;
std::size_t mLastChangeRevision;
btAABB mAabb;
};
}
#endif

@ -13,7 +13,8 @@ namespace DetourNavigator
bool RecastMeshManager::addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform,
const AreaType areaType)
{
const auto iterator = mObjectsOrder.emplace(mObjectsOrder.end(), RecastMeshObject(shape, transform, areaType));
const auto iterator = mObjectsOrder.emplace(mObjectsOrder.end(),
OscillatingRecastMeshObject(RecastMeshObject(shape, transform, areaType), mRevision + 1));
if (!mObjects.emplace(id, iterator).second)
{
mObjectsOrder.erase(iterator);
@ -28,7 +29,9 @@ namespace DetourNavigator
const auto object = mObjects.find(id);
if (object == mObjects.end())
return false;
if (!object->second->update(transform, areaType))
const std::size_t lastChangeRevision = mLastNavMeshReportedChange.has_value()
? mLastNavMeshReportedChange->mRevision : mRevision;
if (!object->second->update(transform, areaType, lastChangeRevision))
return false;
++mRevision;
return true;
@ -39,7 +42,7 @@ namespace DetourNavigator
const auto object = mObjects.find(id);
if (object == mObjects.end())
return std::nullopt;
const RemovedRecastMeshObject result {object->second->getShape(), object->second->getTransform()};
const RemovedRecastMeshObject result {object->second->getImpl().getShape(), object->second->getImpl().getTransform()};
mObjectsOrder.erase(object->second);
mObjects.erase(object);
++mRevision;
@ -74,7 +77,7 @@ namespace DetourNavigator
std::shared_ptr<RecastMesh> RecastMeshManager::getMesh()
{
rebuild();
return mMeshBuilder.create(mGeneration, mLastBuildRevision);
return mMeshBuilder.create(mGeneration, mRevision);
}
bool RecastMeshManager::isEmpty() const
@ -82,15 +85,27 @@ namespace DetourNavigator
return mObjects.empty();
}
void RecastMeshManager::rebuild()
void RecastMeshManager::reportNavMeshChange(Version recastMeshVersion, Version navMeshVersion)
{
if (mLastBuildRevision == mRevision)
if (recastMeshVersion.mGeneration != mGeneration)
return;
if (mLastNavMeshReport.has_value() && navMeshVersion < mLastNavMeshReport->mNavMeshVersion)
return;
mLastNavMeshReport = {recastMeshVersion.mRevision, navMeshVersion};
if (!mLastNavMeshReportedChange.has_value()
|| mLastNavMeshReportedChange->mNavMeshVersion < mLastNavMeshReport->mNavMeshVersion)
mLastNavMeshReportedChange = mLastNavMeshReport;
}
void RecastMeshManager::rebuild()
{
mMeshBuilder.reset();
for (const auto& v : mWaterOrder)
mMeshBuilder.addWater(v.mCellSize, v.mTransform);
for (const auto& v : mObjectsOrder)
for (const auto& object : mObjectsOrder)
{
const RecastMeshObject& v = object.getImpl();
mMeshBuilder.addObject(v.getShape(), v.getTransform(), v.getAreaType());
mLastBuildRevision = mRevision;
}
}
}

@ -2,8 +2,9 @@
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHMANAGER_H
#include "recastmeshbuilder.hpp"
#include "recastmeshobject.hpp"
#include "oscillatingrecastmeshobject.hpp"
#include "objectid.hpp"
#include "version.hpp"
#include <LinearMath/btTransform.h>
@ -50,15 +51,24 @@ namespace DetourNavigator
bool isEmpty() const;
void reportNavMeshChange(Version recastMeshVersion, Version navMeshVersion);
private:
struct Report
{
std::size_t mRevision;
Version mNavMeshVersion;
};
std::size_t mRevision = 0;
std::size_t mLastBuildRevision = 0;
std::size_t mGeneration;
RecastMeshBuilder mMeshBuilder;
std::list<RecastMeshObject> mObjectsOrder;
std::unordered_map<ObjectId, std::list<RecastMeshObject>::iterator> mObjects;
std::list<OscillatingRecastMeshObject> mObjectsOrder;
std::unordered_map<ObjectId, std::list<OscillatingRecastMeshObject>::iterator> mObjects;
std::list<Water> mWaterOrder;
std::map<osg::Vec2i, std::list<Water>::iterator> mWater;
std::optional<Report> mLastNavMeshReportedChange;
std::optional<Report> mLastNavMeshReport;
void rebuild();
};

@ -8,6 +8,22 @@
namespace DetourNavigator
{
namespace
{
bool updateCompoundObject(const btCompoundShape& shape, const AreaType areaType,
std::vector<RecastMeshObject>& children)
{
assert(static_cast<std::size_t>(shape.getNumChildShapes()) == children.size());
bool result = false;
for (int i = 0, num = shape.getNumChildShapes(); i < num; ++i)
{
assert(shape.getChildShape(i) == std::addressof(children[static_cast<std::size_t>(i)].getShape()));
result = children[static_cast<std::size_t>(i)].update(shape.getChildTransform(i), areaType) || result;
}
return result;
}
}
RecastMeshObject::RecastMeshObject(const btCollisionShape& shape, const btTransform& transform,
const AreaType areaType)
: mShape(shape)
@ -42,19 +58,6 @@ namespace DetourNavigator
return result;
}
bool RecastMeshObject::updateCompoundObject(const btCompoundShape& shape,
const AreaType areaType, std::vector<RecastMeshObject>& children)
{
assert(static_cast<std::size_t>(shape.getNumChildShapes()) == children.size());
bool result = false;
for (int i = 0, num = shape.getNumChildShapes(); i < num; ++i)
{
assert(shape.getChildShape(i) == std::addressof(children[static_cast<std::size_t>(i)].mShape.get()));
result = children[static_cast<std::size_t>(i)].update(shape.getChildTransform(i), areaType) || result;
}
return result;
}
std::vector<RecastMeshObject> makeChildrenObjects(const btCollisionShape& shape, const AreaType areaType)
{
if (shape.isCompound())

@ -41,9 +41,6 @@ namespace DetourNavigator
AreaType mAreaType;
btVector3 mLocalScaling;
std::vector<RecastMeshObject> mChildren;
static bool updateCompoundObject(const btCompoundShape& shape, const AreaType areaType,
std::vector<RecastMeshObject>& children);
};
std::vector<RecastMeshObject> makeChildrenObjects(const btCollisionShape& shape, const AreaType areaType);

@ -145,6 +145,15 @@ namespace DetourNavigator
return mRevision;
}
void TileCachedRecastMeshManager::reportNavMeshChange(const TilePosition& tilePosition, Version recastMeshVersion, Version navMeshVersion)
{
const auto tiles = mTiles.lock();
const auto it = tiles->find(tilePosition);
if (it == tiles->end())
return;
it->second.reportNavMeshChange(recastMeshVersion, navMeshVersion);
}
bool TileCachedRecastMeshManager::addTile(const ObjectId id, const btCollisionShape& shape,
const btTransform& transform, const AreaType areaType, const TilePosition& tilePosition, float border,
std::map<TilePosition, CachedRecastMeshManager>& tiles)

@ -5,6 +5,7 @@
#include "tileposition.hpp"
#include "settingsutils.hpp"
#include "gettilespositions.hpp"
#include "version.hpp"
#include <components/misc/guarded.hpp>
@ -88,6 +89,8 @@ namespace DetourNavigator
std::size_t getRevision() const;
void reportNavMeshChange(const TilePosition& tilePosition, Version recastMeshVersion, Version navMeshVersion);
private:
const Settings& mSettings;
Misc::ScopeGuarded<std::map<TilePosition, CachedRecastMeshManager>> mTiles;

@ -0,0 +1,21 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_VERSION_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_VERSION_H
#include <cstddef>
#include <tuple>
namespace DetourNavigator
{
struct Version
{
std::size_t mGeneration;
std::size_t mRevision;
friend inline bool operator<(const Version& lhs, const Version& rhs)
{
return std::tie(lhs.mGeneration, lhs.mRevision) < std::tie(rhs.mGeneration, rhs.mRevision);
}
};
}
#endif
Loading…
Cancel
Save