From d15e1dca848ff66d19bf27d30e0fddab511cbf48 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 28 Aug 2022 16:43:43 +0200 Subject: [PATCH] Use R-tree for objects to be used for navmesh generation Instead of storing a set of objects per tile. --- .../tilecachedrecastmeshmanager.cpp | 22 +- components/CMakeLists.txt | 4 +- .../cachedrecastmeshmanager.cpp | 100 --- .../cachedrecastmeshmanager.hpp | 53 -- .../detournavigator/commulativeaabb.cpp | 27 + .../detournavigator/commulativeaabb.hpp | 24 + components/detournavigator/debug.cpp | 6 + components/detournavigator/debug.hpp | 3 + components/detournavigator/navigatorimpl.cpp | 6 +- components/detournavigator/navmeshmanager.cpp | 41 +- components/detournavigator/navmeshmanager.hpp | 6 +- .../oscillatingrecastmeshobject.cpp | 57 -- .../oscillatingrecastmeshobject.hpp | 31 - .../detournavigator/recastmeshbuilder.cpp | 2 + .../detournavigator/recastmeshmanager.cpp | 167 ----- .../detournavigator/recastmeshmanager.hpp | 79 --- .../tilecachedrecastmeshmanager.cpp | 628 ++++++++++-------- .../tilecachedrecastmeshmanager.hpp | 110 ++- .../detournavigator/tilespositionsrange.hpp | 12 + 19 files changed, 524 insertions(+), 854 deletions(-) delete mode 100644 components/detournavigator/cachedrecastmeshmanager.cpp delete mode 100644 components/detournavigator/cachedrecastmeshmanager.hpp create mode 100644 components/detournavigator/commulativeaabb.cpp create mode 100644 components/detournavigator/commulativeaabb.hpp delete mode 100644 components/detournavigator/oscillatingrecastmeshobject.cpp delete mode 100644 components/detournavigator/oscillatingrecastmeshobject.hpp delete mode 100644 components/detournavigator/recastmeshmanager.cpp delete mode 100644 components/detournavigator/recastmeshmanager.hpp diff --git a/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp index 4ccd4c6712..71ef657e22 100644 --- a/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp @@ -43,14 +43,6 @@ namespace EXPECT_EQ(manager.getRevision(), 0); } - TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, for_each_tile_position_for_empty_should_call_none) - { - TileCachedRecastMeshManager manager(mSettings); - std::size_t calls = 0; - manager.forEachTile([&] (const TilePosition&, const CachedRecastMeshManager&) { ++calls; }); - EXPECT_EQ(calls, 0); - } - TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_for_new_object_should_return_true) { TileCachedRecastMeshManager manager(mSettings); @@ -105,7 +97,7 @@ namespace manager.setBounds(bounds); manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground); manager.takeChangedTiles(); - EXPECT_TRUE(manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); + EXPECT_TRUE(manager.updateObject(ObjectId(&boxShape), btTransform::getIdentity(), AreaType::AreaType_ground)); EXPECT_THAT(manager.takeChangedTiles(), ElementsAre( std::pair(TilePosition(-1, -1), ChangeType::add), std::pair(TilePosition(-1, 0), ChangeType::add), @@ -123,7 +115,7 @@ namespace const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); manager.takeChangedTiles(); - EXPECT_FALSE(manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); + EXPECT_FALSE(manager.updateObject(ObjectId(&boxShape), btTransform::getIdentity(), AreaType::AreaType_ground)); EXPECT_THAT(manager.takeChangedTiles(), IsEmpty()); } @@ -184,7 +176,7 @@ namespace EXPECT_NE(manager.getMesh("worldspace", TilePosition(1, 0)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(1, -1)), nullptr); - manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); + manager.updateObject(ObjectId(&boxShape), btTransform::getIdentity(), AreaType::AreaType_ground); EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr); @@ -204,7 +196,7 @@ namespace EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr); - manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); + manager.updateObject(ObjectId(&boxShape), btTransform::getIdentity(), AreaType::AreaType_ground); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(1, 0)), nullptr); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(1, -1)), nullptr); } @@ -236,7 +228,7 @@ namespace EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, 0)), nullptr); - manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); + manager.updateObject(ObjectId(&boxShape), btTransform::getIdentity(), AreaType::AreaType_ground); EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr); @@ -272,7 +264,7 @@ namespace const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground); const auto beforeUpdateRevision = manager.getRevision(); - manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); + manager.updateObject(ObjectId(&boxShape), btTransform::getIdentity(), AreaType::AreaType_ground); EXPECT_EQ(manager.getRevision(), beforeUpdateRevision + 1); } @@ -284,7 +276,7 @@ namespace const CollisionShape shape(mInstance, boxShape, mObjectTransform); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); const auto beforeUpdateRevision = manager.getRevision(); - manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); + manager.updateObject(ObjectId(&boxShape), btTransform::getIdentity(), AreaType::AreaType_ground); EXPECT_EQ(manager.getRevision(), beforeUpdateRevision); } diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 9dc752e4aa..cbfcece2c2 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -290,8 +290,6 @@ add_component_dir(detournavigator makenavmesh findsmoothpath recastmeshbuilder - recastmeshmanager - cachedrecastmeshmanager navmeshmanager navigatorimpl asyncnavmeshupdater @@ -304,7 +302,6 @@ add_component_dir(detournavigator findrandompointaroundcircle raycast navmeshtileview - oscillatingrecastmeshobject offmeshconnectionsmanager preparednavmeshdata navmeshcacheitem @@ -317,6 +314,7 @@ add_component_dir(detournavigator gettilespositions collisionshapetype stats + commulativeaabb ) add_component_dir(loadinglistener diff --git a/components/detournavigator/cachedrecastmeshmanager.cpp b/components/detournavigator/cachedrecastmeshmanager.cpp deleted file mode 100644 index 65f99fae1e..0000000000 --- a/components/detournavigator/cachedrecastmeshmanager.cpp +++ /dev/null @@ -1,100 +0,0 @@ -#include "cachedrecastmeshmanager.hpp" - -namespace DetourNavigator -{ - CachedRecastMeshManager::CachedRecastMeshManager(const TileBounds& bounds, std::size_t generation) - : mImpl(bounds, generation) - {} - - bool CachedRecastMeshManager::addObject(const ObjectId id, const CollisionShape& shape, - const btTransform& transform, const AreaType areaType) - { - if (!mImpl.addObject(id, shape, transform, areaType)) - return false; - mOutdatedCache = true; - return true; - } - - bool CachedRecastMeshManager::updateObject(const ObjectId id, const btTransform& transform, const AreaType areaType) - { - if (!mImpl.updateObject(id, transform, areaType)) - return false; - mOutdatedCache = true; - return true; - } - - bool CachedRecastMeshManager::removeObject(const ObjectId id) - { - const bool result = mImpl.removeObject(id); - if (result) - mOutdatedCache = true; - return result; - } - - bool CachedRecastMeshManager::addWater(const osg::Vec2i& cellPosition, int cellSize, float level) - { - if (!mImpl.addWater(cellPosition, cellSize, level)) - return false; - mOutdatedCache = true; - return true; - } - - bool CachedRecastMeshManager::removeWater(const osg::Vec2i& cellPosition) - { - const bool result = mImpl.removeWater(cellPosition); - if (result) - mOutdatedCache = true; - return result; - } - - bool CachedRecastMeshManager::addHeightfield(const osg::Vec2i& cellPosition, int cellSize, - const HeightfieldShape& shape) - { - if (!mImpl.addHeightfield(cellPosition, cellSize, shape)) - return false; - mOutdatedCache = true; - return true; - } - - bool CachedRecastMeshManager::removeHeightfield(const osg::Vec2i& cellPosition) - { - const bool result = mImpl.removeHeightfield(cellPosition); - if (result) - mOutdatedCache = true; - return result; - } - - std::shared_ptr CachedRecastMeshManager::getMesh() - { - bool outdated = true; - if (!mOutdatedCache.compare_exchange_strong(outdated, false)) - { - std::shared_ptr cached = getCachedMesh(); - if (cached != nullptr) - return cached; - } - std::shared_ptr mesh = mImpl.getMesh(); - *mCached.lock() = mesh; - return mesh; - } - - std::shared_ptr CachedRecastMeshManager::getCachedMesh() const - { - return *mCached.lockConst(); - } - - std::shared_ptr CachedRecastMeshManager::getNewMesh() const - { - return mImpl.getMesh(); - } - - bool CachedRecastMeshManager::isEmpty() const - { - return mImpl.isEmpty(); - } - - void CachedRecastMeshManager::reportNavMeshChange(const Version& recastMeshVersion, const Version& navMeshVersion) - { - mImpl.reportNavMeshChange(recastMeshVersion, navMeshVersion); - } -} diff --git a/components/detournavigator/cachedrecastmeshmanager.hpp b/components/detournavigator/cachedrecastmeshmanager.hpp deleted file mode 100644 index df4a5b3319..0000000000 --- a/components/detournavigator/cachedrecastmeshmanager.hpp +++ /dev/null @@ -1,53 +0,0 @@ -#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_CACHEDRECASTMESHMANAGER_H -#define OPENMW_COMPONENTS_DETOURNAVIGATOR_CACHEDRECASTMESHMANAGER_H - -#include "recastmeshmanager.hpp" -#include "version.hpp" -#include "heightfieldshape.hpp" - -#include - -#include - -namespace DetourNavigator -{ - class CachedRecastMeshManager - { - public: - explicit CachedRecastMeshManager(const TileBounds& bounds, std::size_t generation); - - bool addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, - const AreaType areaType); - - bool updateObject(const ObjectId id, const btTransform& transform, const AreaType areaType); - - bool removeObject(const ObjectId id); - - bool addWater(const osg::Vec2i& cellPosition, int cellSize, float level); - - bool removeWater(const osg::Vec2i& cellPosition); - - bool addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape); - - bool removeHeightfield(const osg::Vec2i& cellPosition); - - std::shared_ptr getMesh(); - - std::shared_ptr getCachedMesh() const; - - std::shared_ptr getNewMesh() const; - - bool isEmpty() const; - - void reportNavMeshChange(const Version& recastMeshVersion, const Version& navMeshVersion); - - Version getVersion() const { return mImpl.getVersion(); } - - private: - RecastMeshManager mImpl; - Misc::ScopeGuarded> mCached; - std::atomic_bool mOutdatedCache {true}; - }; -} - -#endif diff --git a/components/detournavigator/commulativeaabb.cpp b/components/detournavigator/commulativeaabb.cpp new file mode 100644 index 0000000000..1274c3203d --- /dev/null +++ b/components/detournavigator/commulativeaabb.cpp @@ -0,0 +1,27 @@ +#include "commulativeaabb.hpp" + +#include + +namespace DetourNavigator +{ + CommulativeAabb::CommulativeAabb(std::size_t lastChangeRevision, const btAABB& aabb) + : mLastChangeRevision(lastChangeRevision) + , mAabb(aabb) + { + } + + bool CommulativeAabb::update(std::size_t lastChangeRevision, const btAABB& aabb) + { + if (mLastChangeRevision != lastChangeRevision) + { + mLastChangeRevision = lastChangeRevision; + // btAABB doesn't have copy-assignment operator + mAabb.m_min = aabb.m_min; + mAabb.m_max = aabb.m_max; + return true; + } + const btAABB currentAabb = mAabb; + mAabb.merge(aabb); + return currentAabb != mAabb; + } +} diff --git a/components/detournavigator/commulativeaabb.hpp b/components/detournavigator/commulativeaabb.hpp new file mode 100644 index 0000000000..3559d3b000 --- /dev/null +++ b/components/detournavigator/commulativeaabb.hpp @@ -0,0 +1,24 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_COMMULATIVEAABB_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_COMMULATIVEAABB_H + +#include +#include + +#include + +namespace DetourNavigator +{ + class CommulativeAabb + { + public: + explicit CommulativeAabb(std::size_t lastChangeRevision, const btAABB& aabb); + + bool update(std::size_t lastChangeRevision, const btAABB& aabb); + + private: + std::size_t mLastChangeRevision; + btAABB mAabb; + }; +} + +#endif diff --git a/components/detournavigator/debug.cpp b/components/detournavigator/debug.cpp index 740be10337..2c3cb998d7 100644 --- a/components/detournavigator/debug.cpp +++ b/components/detournavigator/debug.cpp @@ -4,6 +4,7 @@ #include "settings.hpp" #include "settingsutils.hpp" #include "version.hpp" +#include "tilespositionsrange.hpp" #include @@ -196,6 +197,11 @@ namespace DetourNavigator return stream << "Version {" << value.mGeneration << ", " << value.mRevision << "}"; } + std::ostream& operator<<(std::ostream& stream, const TilesPositionsRange& value) + { + return stream << "TilesPositionsRange {" << value.mBegin << ", " << value.mEnd << "}"; + } + void writeToFile(const RecastMesh& recastMesh, const std::string& pathPrefix, const std::string& revision, const RecastSettings& settings) { diff --git a/components/detournavigator/debug.hpp b/components/detournavigator/debug.hpp index 5dcbe55ffb..328eafa514 100644 --- a/components/detournavigator/debug.hpp +++ b/components/detournavigator/debug.hpp @@ -19,6 +19,7 @@ class dtNavMesh; namespace DetourNavigator { struct Version; + struct TilesPositionsRange; std::ostream& operator<<(std::ostream& stream, const TileBounds& value); @@ -58,6 +59,8 @@ namespace DetourNavigator std::ostream& operator<<(std::ostream& stream, const Version& value); + std::ostream& operator<<(std::ostream& stream, const TilesPositionsRange& value); + class RecastMesh; struct RecastSettings; diff --git a/components/detournavigator/navigatorimpl.cpp b/components/detournavigator/navigatorimpl.cpp index 6a194a7615..575e7c7e7b 100644 --- a/components/detournavigator/navigatorimpl.cpp +++ b/components/detournavigator/navigatorimpl.cpp @@ -77,13 +77,11 @@ namespace DetourNavigator void NavigatorImpl::updateObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) { - const CollisionShape collisionShape(shapes.mShapeInstance, *shapes.mShapeInstance->mCollisionShape, shapes.mTransform); - mNavMeshManager.updateObject(id, collisionShape, transform, AreaType_ground); + mNavMeshManager.updateObject(id, transform, AreaType_ground); if (const btCollisionShape* const avoidShape = shapes.mShapeInstance->mAvoidCollisionShape.get()) { const ObjectId avoidId(avoidShape); - const CollisionShape avoidCollisionShape(shapes.mShapeInstance, *avoidShape, shapes.mTransform); - if (mNavMeshManager.updateObject(avoidId, avoidCollisionShape, transform, AreaType_null)) + if (mNavMeshManager.updateObject(avoidId, transform, AreaType_null)) updateAvoidShapeId(id, avoidId); } } diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index 07035b17cc..a6734d41e3 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -7,7 +7,6 @@ #include "settings.hpp" #include "waitconditiontype.hpp" #include "settingsutils.hpp" -#include "cachedrecastmeshmanager.hpp" #include #include @@ -81,10 +80,9 @@ namespace DetourNavigator return mRecastMeshManager.addObject(id, shape, transform, areaType); } - bool NavMeshManager::updateObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, - const AreaType areaType) + bool NavMeshManager::updateObject(const ObjectId id, const btTransform& transform, const AreaType areaType) { - return mRecastMeshManager.updateObject(id, shape, transform, areaType); + return mRecastMeshManager.updateObject(id, transform, areaType); } void NavMeshManager::removeObject(const ObjectId id) @@ -165,36 +163,33 @@ namespace DetourNavigator mLastRecastMeshManagerRevision = mRecastMeshManager.getRevision(); mPlayerTile = playerTile; const auto changedTiles = mRecastMeshManager.takeChangedTiles(); + const TilesPositionsRange range = mRecastMeshManager.getRange(); for (const auto& [agentBounds, cached] : mCache) - update(agentBounds, playerTile, cached, changedTiles); + update(agentBounds, playerTile, range, cached, changedTiles); } void NavMeshManager::update(const AgentBounds& agentBounds, const TilePosition& playerTile, - const SharedNavMeshCacheItem& cached, const std::map& changedTiles) + const TilesPositionsRange& range, const SharedNavMeshCacheItem& cached, + const std::map& changedTiles) { - std::map tilesToPost; + std::map tilesToPost = changedTiles; { const auto locked = cached->lockConst(); const auto& navMesh = locked->getImpl(); - for (const auto& [tilePosition, changeType] : changedTiles) - if (navMesh.getTileAt(tilePosition.x(), tilePosition.y(), 0)) - tilesToPost.emplace(tilePosition, changeType); const auto maxTiles = std::min(mSettings.mMaxTilesNumber, navMesh.getParams()->maxTiles); - mRecastMeshManager.forEachTile([&] (const TilePosition& tile, CachedRecastMeshManager& recastMeshManager) + getTilesPositions(range, [&] (const TilePosition& tile) { - if (tilesToPost.count(tile)) + if (changedTiles.find(tile) != changedTiles.end()) return; - const auto shouldAdd = shouldAddTile(tile, playerTile, maxTiles); - const auto presentInNavMesh = bool(navMesh.getTileAt(tile.x(), tile.y(), 0)); + const bool shouldAdd = shouldAddTile(tile, playerTile, maxTiles); + const bool presentInNavMesh = navMesh.getTileAt(tile.x(), tile.y(), 0) != nullptr; if (shouldAdd && !presentInNavMesh) tilesToPost.emplace(tile, locked->isEmptyTile(tile) ? ChangeType::update : ChangeType::add); else if (!shouldAdd && presentInNavMesh) tilesToPost.emplace(tile, ChangeType::mixed); - else - recastMeshManager.reportNavMeshChange(recastMeshManager.getVersion(), Version {0, 0}); }); } - mAsyncNavMeshUpdater.post(agentBounds, cached, playerTile, mRecastMeshManager.getWorldspace(), tilesToPost); + mAsyncNavMeshUpdater.post(agentBounds, cached, playerTile, mWorldspace, tilesToPost); Log(Debug::Debug) << "Cache update posted for agent=" << agentBounds << " playerTile=" << playerTile << " recastMeshManagerRevision=" << mLastRecastMeshManagerRevision; } @@ -221,14 +216,12 @@ namespace DetourNavigator RecastMeshTiles NavMeshManager::getRecastMeshTiles() const { - std::vector tiles; - mRecastMeshManager.forEachTile( - [&tiles] (const TilePosition& tile, const CachedRecastMeshManager&) { tiles.push_back(tile); }); - const std::string worldspace = mRecastMeshManager.getWorldspace(); RecastMeshTiles result; - for (const TilePosition& tile : tiles) - if (auto mesh = mRecastMeshManager.getCachedMesh(worldspace, tile)) - result.emplace(tile, std::move(mesh)); + getTilesPositions(mRecastMeshManager.getRange(), [&] (const TilePosition& v) + { + if (auto mesh = mRecastMeshManager.getCachedMesh(mWorldspace, v)) + result.emplace(v, std::move(mesh)); + }); return result; } diff --git a/components/detournavigator/navmeshmanager.hpp b/components/detournavigator/navmeshmanager.hpp index adaefb14c4..b9c3bc7c21 100644 --- a/components/detournavigator/navmeshmanager.hpp +++ b/components/detournavigator/navmeshmanager.hpp @@ -29,8 +29,7 @@ namespace DetourNavigator bool addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType); - bool updateObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, - const AreaType areaType); + bool updateObject(ObjectId id, const btTransform& transform, AreaType areaType); void removeObject(const ObjectId id); @@ -76,7 +75,8 @@ namespace DetourNavigator inline SharedNavMeshCacheItem getCached(const AgentBounds& agentBounds) const; inline void update(const AgentBounds& agentBounds, const TilePosition& playerTile, - const SharedNavMeshCacheItem& cached, const std::map& changedTiles); + const TilesPositionsRange& range, const SharedNavMeshCacheItem& cached, + const std::map& changedTiles); }; } diff --git a/components/detournavigator/oscillatingrecastmeshobject.cpp b/components/detournavigator/oscillatingrecastmeshobject.cpp deleted file mode 100644 index fbe4b77ffd..0000000000 --- a/components/detournavigator/oscillatingrecastmeshobject.cpp +++ /dev/null @@ -1,57 +0,0 @@ -#include "oscillatingrecastmeshobject.hpp" -#include "tilebounds.hpp" - -#include - -#include - -namespace DetourNavigator -{ - namespace - { - void limitBy(btAABB& aabb, const TileBounds& bounds) - { - aabb.m_min.setX(std::max(aabb.m_min.x(), static_cast(bounds.mMin.x()))); - aabb.m_min.setY(std::max(aabb.m_min.y(), static_cast(bounds.mMin.y()))); - aabb.m_max.setX(std::min(aabb.m_max.x(), static_cast(bounds.mMax.x()))); - aabb.m_max.setY(std::min(aabb.m_max.y(), static_cast(bounds.mMax.y()))); - } - } - - OscillatingRecastMeshObject::OscillatingRecastMeshObject(RecastMeshObject&& impl, std::size_t lastChangeRevision) - : mImpl(std::move(impl)) - , mLastChangeRevision(lastChangeRevision) - , mAabb(BulletHelpers::getAabb(mImpl.getShape(), mImpl.getTransform())) - { - } - - OscillatingRecastMeshObject::OscillatingRecastMeshObject(const RecastMeshObject& impl, std::size_t lastChangeRevision) - : mImpl(impl) - , mLastChangeRevision(lastChangeRevision) - , mAabb(BulletHelpers::getAabb(mImpl.getShape(), mImpl.getTransform())) - { - } - - bool OscillatingRecastMeshObject::update(const btTransform& transform, const AreaType areaType, - std::size_t lastChangeRevision, const TileBounds& bounds) - { - 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)); - limitBy(mAabb, bounds); - return currentAabb != mAabb; - } -} diff --git a/components/detournavigator/oscillatingrecastmeshobject.hpp b/components/detournavigator/oscillatingrecastmeshobject.hpp deleted file mode 100644 index f8aabce628..0000000000 --- a/components/detournavigator/oscillatingrecastmeshobject.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_OSCILLATINGRECASTMESHOBJECT_H -#define OPENMW_COMPONENTS_DETOURNAVIGATOR_OSCILLATINGRECASTMESHOBJECT_H - -#include "areatype.hpp" -#include "recastmeshobject.hpp" -#include "tilebounds.hpp" - -#include -#include - -namespace DetourNavigator -{ - class OscillatingRecastMeshObject - { - public: - explicit OscillatingRecastMeshObject(RecastMeshObject&& impl, std::size_t lastChangeRevision); - explicit OscillatingRecastMeshObject(const RecastMeshObject& impl, std::size_t lastChangeRevision); - - bool update(const btTransform& transform, const AreaType areaType, std::size_t lastChangeRevision, - const TileBounds& bounds); - - const RecastMeshObject& getImpl() const { return mImpl; } - - private: - RecastMeshObject mImpl; - std::size_t mLastChangeRevision; - btAABB mAabb; - }; -} - -#endif diff --git a/components/detournavigator/recastmeshbuilder.cpp b/components/detournavigator/recastmeshbuilder.cpp index ff216b0b0e..b8e455c732 100644 --- a/components/detournavigator/recastmeshbuilder.cpp +++ b/components/detournavigator/recastmeshbuilder.cpp @@ -277,6 +277,8 @@ namespace DetourNavigator mTriangles.erase(std::remove_if(mTriangles.begin(), mTriangles.end(), isNan), mTriangles.end()); std::sort(mTriangles.begin(), mTriangles.end()); std::sort(mWater.begin(), mWater.end()); + std::sort(mHeightfields.begin(), mHeightfields.end()); + std::sort(mFlatHeightfields.begin(), mFlatHeightfields.end()); Mesh mesh = makeMesh(std::move(mTriangles)); return std::make_shared(version, std::move(mesh), std::move(mWater), std::move(mHeightfields), std::move(mFlatHeightfields), diff --git a/components/detournavigator/recastmeshmanager.cpp b/components/detournavigator/recastmeshmanager.cpp deleted file mode 100644 index a883f7839b..0000000000 --- a/components/detournavigator/recastmeshmanager.cpp +++ /dev/null @@ -1,167 +0,0 @@ -#include "recastmeshmanager.hpp" -#include "recastmeshbuilder.hpp" -#include "heightfieldshape.hpp" - -#include -#include - -#include - -namespace -{ - struct AddHeightfield - { - osg::Vec2i mCellPosition; - int mCellSize; - DetourNavigator::RecastMeshBuilder& mBuilder; - - void operator()(const DetourNavigator::HeightfieldSurface& v) - { - mBuilder.addHeightfield(mCellPosition, mCellSize, v.mHeights, v.mSize, v.mMinHeight, v.mMaxHeight); - } - - void operator()(DetourNavigator::HeightfieldPlane v) - { - mBuilder.addHeightfield(mCellPosition, mCellSize, v.mHeight); - } - }; -} - -namespace DetourNavigator -{ - RecastMeshManager::RecastMeshManager(const TileBounds& bounds, std::size_t generation) - : mGeneration(generation) - , mTileBounds(bounds) - { - } - - bool RecastMeshManager::addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, - const AreaType areaType) - { - const std::lock_guard lock(mMutex); - const auto object = mObjects.lower_bound(id); - if (object != mObjects.end() && object->first == id) - return false; - mObjects.emplace_hint(object, id, - OscillatingRecastMeshObject(RecastMeshObject(shape, transform, areaType), mRevision + 1)); - ++mRevision; - return true; - } - - bool RecastMeshManager::updateObject(const ObjectId id, const btTransform& transform, const AreaType areaType) - { - const std::lock_guard lock(mMutex); - const auto object = mObjects.find(id); - if (object == mObjects.end()) - return false; - const std::size_t lastChangeRevision = mLastNavMeshReportedChange.has_value() - ? mLastNavMeshReportedChange->mRevision : mRevision; - if (!object->second.update(transform, areaType, lastChangeRevision, mTileBounds)) - return false; - ++mRevision; - return true; - } - - bool RecastMeshManager::removeObject(const ObjectId id) - { - const std::lock_guard lock(mMutex); - const auto object = mObjects.find(id); - if (object == mObjects.end()) - return false; - mObjects.erase(object); - ++mRevision; - return true; - } - - bool RecastMeshManager::addWater(const osg::Vec2i& cellPosition, int cellSize, float level) - { - const std::lock_guard lock(mMutex); - if (!mWater.emplace(cellPosition, Water {cellSize, level}).second) - return false; - ++mRevision; - return true; - } - - bool RecastMeshManager::removeWater(const osg::Vec2i& cellPosition) - { - const std::lock_guard lock(mMutex); - const auto water = mWater.find(cellPosition); - if (water == mWater.end()) - return false; - ++mRevision; - mWater.erase(water); - return true; - } - - bool RecastMeshManager::addHeightfield(const osg::Vec2i& cellPosition, int cellSize, - const HeightfieldShape& shape) - { - const std::lock_guard lock(mMutex); - if (!mHeightfields.emplace(cellPosition, SizedHeightfieldShape {cellSize, shape}).second) - return false; - ++mRevision; - return true; - } - - bool RecastMeshManager::removeHeightfield(const osg::Vec2i& cellPosition) - { - const std::lock_guard lock(mMutex); - const auto it = mHeightfields.find(cellPosition); - if (it == mHeightfields.end()) - return false; - ++mRevision; - mHeightfields.erase(it); - return true; - } - - std::shared_ptr RecastMeshManager::getMesh() const - { - RecastMeshBuilder builder(mTileBounds); - using Object = std::tuple< - osg::ref_ptr, - ObjectTransform, - std::reference_wrapper, - btTransform, - AreaType - >; - std::vector objects; - std::size_t revision; - { - const std::lock_guard lock(mMutex); - for (const auto& [k, v] : mWater) - builder.addWater(k, v); - for (const auto& [cellPosition, v] : mHeightfields) - std::visit(AddHeightfield {cellPosition, v.mCellSize, builder}, v.mShape); - objects.reserve(mObjects.size()); - for (const auto& [k, object] : mObjects) - { - const RecastMeshObject& impl = object.getImpl(); - objects.emplace_back(impl.getInstance(), impl.getObjectTransform(), impl.getShape(), - impl.getTransform(), impl.getAreaType()); - } - revision = mRevision; - } - for (const auto& [instance, objectTransform, shape, transform, areaType] : objects) - builder.addObject(shape, transform, areaType, instance->getSource(), objectTransform); - return std::move(builder).create(Version {.mGeneration = mGeneration, .mRevision = revision}); - } - - bool RecastMeshManager::isEmpty() const - { - const std::lock_guard lock(mMutex); - return mObjects.empty() && mWater.empty() && mHeightfields.empty(); - } - - void RecastMeshManager::reportNavMeshChange(const Version& recastMeshVersion, const Version& navMeshVersion) - { - if (recastMeshVersion.mGeneration != mGeneration) - return; - const std::lock_guard lock(mMutex); - if (mLastNavMeshReport.has_value() && navMeshVersion < mLastNavMeshReport->mNavMeshVersion) - return; - mLastNavMeshReport = {recastMeshVersion.mRevision, navMeshVersion}; - if (!mLastNavMeshReportedChange.has_value() - || mLastNavMeshReportedChange->mNavMeshVersion < mLastNavMeshReport->mNavMeshVersion) - mLastNavMeshReportedChange = mLastNavMeshReport; - } -} diff --git a/components/detournavigator/recastmeshmanager.hpp b/components/detournavigator/recastmeshmanager.hpp deleted file mode 100644 index ad27933807..0000000000 --- a/components/detournavigator/recastmeshmanager.hpp +++ /dev/null @@ -1,79 +0,0 @@ -#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHMANAGER_H -#define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHMANAGER_H - -#include "oscillatingrecastmeshobject.hpp" -#include "objectid.hpp" -#include "version.hpp" -#include "recastmesh.hpp" -#include "heightfieldshape.hpp" - -#include - -#include - -#include -#include -#include -#include - -class btCollisionShape; - -namespace DetourNavigator -{ - struct Settings; - class RecastMesh; - - struct SizedHeightfieldShape - { - int mCellSize; - HeightfieldShape mShape; - }; - - class RecastMeshManager - { - public: - explicit RecastMeshManager(const TileBounds& bounds, std::size_t generation); - - bool addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, - const AreaType areaType); - - bool updateObject(const ObjectId id, const btTransform& transform, const AreaType areaType); - - bool removeObject(const ObjectId id); - - bool addWater(const osg::Vec2i& cellPosition, int cellSize, float level); - - bool removeWater(const osg::Vec2i& cellPosition); - - bool addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape); - - bool removeHeightfield(const osg::Vec2i& cellPosition); - - std::shared_ptr getMesh() const; - - bool isEmpty() const; - - void reportNavMeshChange(const Version& recastMeshVersion, const Version& navMeshVersion); - - Version getVersion() const { return Version {mGeneration, mRevision}; } - - private: - struct Report - { - std::size_t mRevision; - Version mNavMeshVersion; - }; - - const std::size_t mGeneration; - const TileBounds mTileBounds; - mutable std::mutex mMutex; - std::size_t mRevision = 0; - std::map mObjects; - std::map mWater; - std::map mHeightfields; - std::optional mLastNavMeshReportedChange; - std::optional mLastNavMeshReport; - }; -} - -#endif diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index 18c8ef26ca..3c2049942a 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -2,10 +2,12 @@ #include "gettilespositions.hpp" #include "settingsutils.hpp" #include "changetype.hpp" -#include "cachedrecastmeshmanager.hpp" +#include "recastmeshbuilder.hpp" -#include #include +#include + +#include #include #include @@ -20,11 +22,26 @@ namespace DetourNavigator osg::Vec2f(std::numeric_limits::max(), std::numeric_limits::max()) }; - bool updateTile(const ObjectId id, const btTransform& transform, const AreaType areaType, - const TilePosition& tilePosition, std::map>& tiles) + struct AddHeightfield + { + osg::Vec2i mCellPosition; + int mCellSize; + RecastMeshBuilder& mBuilder; + + void operator()(const HeightfieldSurface& v) + { + mBuilder.addHeightfield(mCellPosition, mCellSize, v.mHeights, v.mSize, v.mMinHeight, v.mMaxHeight); + } + + void operator()(HeightfieldPlane v) + { + mBuilder.addHeightfield(mCellPosition, mCellSize, v.mHeight); + } + }; + + TilePosition makeTilePosition(const boost::geometry::model::point& v) { - const auto tile = tiles.find(tilePosition); - return tile != tiles.end() && tile->second->updateObject(id, transform, areaType); + return TilePosition(v.get<0>(), v.get<1>()); } } @@ -41,345 +58,318 @@ namespace DetourNavigator bool changed = false; const auto newRange = makeTilesPositionsRange(bounds.mMin, bounds.mMax, mSettings); - if (mBounds != infiniteTileBounds) { - const auto locked = mWorldspaceTiles.lock(); - for (auto& object : mObjects) + for (const auto& [id, data] : mObjects) { - const ObjectId id = object.first; - ObjectData& data = object.second; - const TilesPositionsRange objectRange = makeTilesPositionsRange(data.mShape.getShape(), data.mTransform, mSettings); + const TilesPositionsRange objectRange = makeTilesPositionsRange(data->mObject.getShape(), + data->mObject.getTransform(), mSettings); - const auto onOldTilePosition = [&] (const TilePosition& position) + getTilesPositions(getIntersection(mRange, objectRange), [&] (const TilePosition& v) { - if (isInTilesPositionsRange(newRange, position)) - return; - const auto it = data.mTiles.find(position); - if (it == data.mTiles.end()) - return; - data.mTiles.erase(it); - if (removeTile(id, position, locked->mTiles)) + if (!isInTilesPositionsRange(newRange, v)) { - addChangedTile(position, ChangeType::remove); + addChangedTile(v, ChangeType::remove); changed = true; } - }; - getTilesPositions(getIntersection(mRange, objectRange), onOldTilePosition); + }); - const auto onNewTilePosition = [&] (const TilePosition& position) + getTilesPositions(getIntersection(newRange, objectRange), [&] (const TilePosition& v) { - if (data.mTiles.find(position) != data.mTiles.end()) - return; - if (addTile(id, data.mShape, data.mTransform, data.mAreaType, position, locked->mTiles)) + if (!isInTilesPositionsRange(mRange, v)) { - data.mTiles.insert(position); - addChangedTile(position, ChangeType::add); + addChangedTile(v, ChangeType::add); + changed = true; } - }; - getTilesPositions(getIntersection(newRange, objectRange), onNewTilePosition); + }); } } if (changed) + { + const std::lock_guard lock(mMutex); ++mRevision; + } mBounds = bounds; mRange = newRange; } - std::string TileCachedRecastMeshManager::getWorldspace() const + TilesPositionsRange TileCachedRecastMeshManager::getRange() const { - return mWorldspaceTiles.lockConst()->mWorldspace; + const auto bounds = mObjectIndex.bounds(); + return TilesPositionsRange { + .mBegin = makeTilePosition(bounds.min_corner()), + .mEnd = makeTilePosition(bounds.max_corner()) + TilePosition(1, 1), + }; } void TileCachedRecastMeshManager::setWorldspace(std::string_view worldspace) { - const auto locked = mWorldspaceTiles.lock(); - if (locked->mWorldspace == worldspace) + const std::lock_guard lock(mMutex); + if (mWorldspace == worldspace) return; - locked->mTiles.clear(); - locked->mWorldspace = worldspace; + mWorldspace = worldspace; + ++mGeneration; + ++mRevision; + mObjectIndex.clear(); + mObjects.clear(); + mWater.clear(); + mHeightfields.clear(); + mCache.clear(); } - bool TileCachedRecastMeshManager::addObject(const ObjectId id, const CollisionShape& shape, - const btTransform& transform, const AreaType areaType) + bool TileCachedRecastMeshManager::addObject(ObjectId id, const CollisionShape& shape, const btTransform& transform, + const AreaType areaType) { - auto it = mObjects.find(id); - if (it != mObjects.end()) - return false; - const TilesPositionsRange objectRange = makeTilesPositionsRange(shape.getShape(), transform, mSettings); - const TilesPositionsRange range = getIntersection(mRange, objectRange); - std::set tilesPositions; - if (range.mBegin != range.mEnd) + const TilesPositionsRange range = makeTilesPositionsRange(shape.getShape(), transform, mSettings); { - const auto locked = mWorldspaceTiles.lock(); - getTilesPositions(range, - [&] (const TilePosition& tilePosition) - { - if (addTile(id, shape, transform, areaType, tilePosition, locked->mTiles)) - { - tilesPositions.insert(tilePosition); - addChangedTile(tilePosition, ChangeType::add); - } - }); + const std::lock_guard lock(mMutex); + const auto it = mObjects.find(id); + if (it != mObjects.end()) + return false; + const std::size_t revision = mRevision + 1; + ObjectData* const dataPtr = mObjects.emplace_hint(it, id, std::unique_ptr(new ObjectData { + .mObject = RecastMeshObject(shape, transform, areaType), + .mRange = range, + .mAabb = CommulativeAabb(revision, BulletHelpers::getAabb(shape.getShape(), transform)), + .mGeneration = mGeneration, + .mRevision = revision, + .mLastNavMeshReportedChange = {}, + .mLastNavMeshReport = {}, + }))->second.get(); + assert(range.mBegin != range.mEnd); + mObjectIndex.insert(makeObjectIndexValue(range, dataPtr)); + mRevision = revision; } - mObjects.emplace_hint(it, id, ObjectData {shape, transform, areaType, std::move(tilesPositions)}); - ++mRevision; + getTilesPositions(getIntersection(range, mRange), + [&] (const TilePosition& v) { addChangedTile(v, ChangeType::add); }); return true; } - bool TileCachedRecastMeshManager::updateObject(const ObjectId id, const CollisionShape& shape, - const btTransform& transform, AreaType areaType) + bool TileCachedRecastMeshManager::updateObject(ObjectId id, const btTransform& transform, const AreaType areaType) { - const auto object = mObjects.find(id); - if (object == mObjects.end()) - return false; - auto& data = object->second; - bool changed = false; - std::set newTiles; + TilesPositionsRange newRange; + TilesPositionsRange oldRange; { - const TilesPositionsRange objectRange = makeTilesPositionsRange(shape.getShape(), transform, mSettings); - const TilesPositionsRange range = getIntersection(mRange, objectRange); - const auto locked = mWorldspaceTiles.lock(); - const auto onTilePosition = [&] (const TilePosition& tilePosition) - { - if (data.mTiles.find(tilePosition) != data.mTiles.end()) - { - newTiles.insert(tilePosition); - if (updateTile(id, transform, areaType, tilePosition, locked->mTiles)) - { - addChangedTile(tilePosition, ChangeType::update); - changed = true; - } - } - else if (addTile(id, shape, transform, areaType, tilePosition, locked->mTiles)) - { - newTiles.insert(tilePosition); - addChangedTile(tilePosition, ChangeType::add); - changed = true; - } - }; - getTilesPositions(range, onTilePosition); - for (const auto& tile : data.mTiles) + const std::lock_guard lock(mMutex); + const auto it = mObjects.find(id); + if (it == mObjects.end()) + return false; + if (!it->second->mObject.update(transform, areaType)) + return false; + const std::size_t lastChangeRevision = it->second->mLastNavMeshReportedChange.has_value() + ? it->second->mLastNavMeshReportedChange->mRevision : mRevision; + const btCollisionShape& shape = it->second->mObject.getShape(); + if (!it->second->mAabb.update(lastChangeRevision, BulletHelpers::getAabb(shape, transform))) + return false; + newRange = makeTilesPositionsRange(shape, transform, mSettings); + oldRange = it->second->mRange; + if (newRange != oldRange) { - if (newTiles.find(tile) == newTiles.end() && removeTile(id, tile, locked->mTiles)) - { - addChangedTile(tile, ChangeType::remove); - changed = true; - } + mObjectIndex.remove(makeObjectIndexValue(oldRange, it->second.get())); + mObjectIndex.insert(makeObjectIndexValue(newRange, it->second.get())); + it->second->mRange = newRange; } + ++mRevision; + it->second->mRevision = mRevision; } - if (changed) + if (newRange == oldRange) { - data.mTiles = std::move(newTiles); - ++mRevision; + getTilesPositions(getIntersection(newRange, mRange), + [&] (const TilePosition& v) { addChangedTile(v, ChangeType::update); }); + } + else + { + getTilesPositions(getIntersection(newRange, mRange), [&] (const TilePosition& v) + { + const ChangeType changeType = isInTilesPositionsRange(oldRange, v) + ? ChangeType::update : ChangeType::add; + addChangedTile(v, changeType); + }); + getTilesPositions(getIntersection(oldRange, mRange), [&] (const TilePosition& v) + { + if (!isInTilesPositionsRange(newRange, v)) + addChangedTile(v, ChangeType::remove); + }); } - return changed; + return true; } - void TileCachedRecastMeshManager::removeObject(const ObjectId id) + void TileCachedRecastMeshManager::removeObject(ObjectId id) { - const auto object = mObjects.find(id); - if (object == mObjects.end()) - return; - bool changed = false; + TilesPositionsRange range; { - const auto locked = mWorldspaceTiles.lock(); - for (const auto& tilePosition : object->second.mTiles) - { - if (removeTile(id, tilePosition, locked->mTiles)) - { - addChangedTile(tilePosition, ChangeType::remove); - changed = true; - } - } - } - mObjects.erase(object); - if (changed) + const std::lock_guard lock(mMutex); + const auto it = mObjects.find(id); + if (it == mObjects.end()) + return; + range = it->second->mRange; + mObjectIndex.remove(makeObjectIndexValue(range, it->second.get())); + mObjects.erase(it); ++mRevision; + } + getTilesPositions(getIntersection(range, mRange), + [&] (const TilePosition& v) { addChangedTile(v, ChangeType::remove); }); } void TileCachedRecastMeshManager::addWater(const osg::Vec2i& cellPosition, const int cellSize, const float level) { - const auto it = mWaterTilesPositions.find(cellPosition); - if (it != mWaterTilesPositions.end()) - return; - - std::vector& tilesPositions = mWaterTilesPositions.emplace_hint( - it, cellPosition, std::vector())->second; - - bool changed = false; - - if (cellSize == std::numeric_limits::max()) + const btVector3 shift = Misc::Convert::toBullet(getWaterShift3d(cellPosition, cellSize, level)); + const std::optional range = cellSize == std::numeric_limits::max() + ? std::optional() + : makeTilesPositionsRange(cellSize, shift, mSettings); { - const auto locked = mWorldspaceTiles.lock(); - for (auto& [tilePosition, data] : locked->mTiles) - { - if (data->addWater(cellPosition, cellSize, level)) - { - tilesPositions.push_back(tilePosition); - addChangedTile(tilePosition, ChangeType::add); - changed = true; - } - } + const std::lock_guard lock(mMutex); + auto it = mWater.find(cellPosition); + if (it != mWater.end()) + return; + const std::size_t revision = mRevision + 1; + it = mWater.emplace_hint(it, cellPosition, WaterData { + .mWater = Water {.mCellSize = cellSize, .mLevel = level}, + .mRange = range, + .mRevision = revision, + }); + if (range.has_value()) + mWaterIndex.insert(makeWaterIndexValue(*range, it)); + else + mInfiniteWater = it; + mRevision = revision; } - else - { - const btVector3 shift = Misc::Convert::toBullet(getWaterShift3d(cellPosition, cellSize, level)); - const auto worldspaceTiles = mWorldspaceTiles.lock(); - getTilesPositions(makeTilesPositionsRange(cellSize, shift, mSettings), - [&] (const TilePosition& tilePosition) - { - auto tile = worldspaceTiles->mTiles.find(tilePosition); - if (tile == worldspaceTiles->mTiles.end()) - { - const TileBounds tileBounds = makeRealTileBoundsWithBorder(mSettings, tilePosition); - tile = worldspaceTiles->mTiles.emplace_hint(tile, tilePosition, - std::make_shared(tileBounds, mTilesGeneration)); - } - if (tile->second->addWater(cellPosition, cellSize, level)) - { - tilesPositions.push_back(tilePosition); - addChangedTile(tilePosition, ChangeType::add); - changed = true; - } - }); - } - - if (changed) - ++mRevision; + addChangedTiles(range, ChangeType::add); } void TileCachedRecastMeshManager::removeWater(const osg::Vec2i& cellPosition) { - const auto object = mWaterTilesPositions.find(cellPosition); - if (object == mWaterTilesPositions.end()) - return; - bool changed = false; + std::optional range; { - const auto worldspaceTiles = mWorldspaceTiles.lock(); - for (const auto& tilePosition : object->second) - { - const auto tile = worldspaceTiles->mTiles.find(tilePosition); - if (tile == worldspaceTiles->mTiles.end()) - continue; - if (tile->second->removeWater(cellPosition)) - { - addChangedTile(tilePosition, ChangeType::remove); - changed = true; - } - if (tile->second->isEmpty()) - { - worldspaceTiles->mTiles.erase(tile); - ++mTilesGeneration; - } - } - } - mWaterTilesPositions.erase(object); - if (changed) + const std::lock_guard lock(mMutex); + const auto it = mWater.find(cellPosition); + if (it == mWater.end()) + return; + range = it->second.mRange; + if (range.has_value()) + mWaterIndex.remove(makeWaterIndexValue(*range, it)); + else + mInfiniteWater = mWater.end(); + mWater.erase(it); ++mRevision; + } + addChangedTiles(range, ChangeType::remove); } void TileCachedRecastMeshManager::addHeightfield(const osg::Vec2i& cellPosition, const int cellSize, const HeightfieldShape& shape) { - const auto it = mHeightfieldTilesPositions.find(cellPosition); - if (it != mHeightfieldTilesPositions.end()) - return; - - std::vector& tilesPositions = mHeightfieldTilesPositions.emplace_hint( - it, cellPosition, std::vector())->second; const btVector3 shift = getHeightfieldShift(shape, cellPosition, cellSize); - - bool changed = false; - + const std::optional range = cellSize == std::numeric_limits::max() + ? std::optional() + : makeTilesPositionsRange(cellSize, shift, mSettings); { - const auto worldspaceTiles = mWorldspaceTiles.lock(); - getTilesPositions(makeTilesPositionsRange(cellSize, shift, mSettings), - [&] (const TilePosition& tilePosition) - { - auto tile = worldspaceTiles->mTiles.find(tilePosition); - if (tile == worldspaceTiles->mTiles.end()) - { - const TileBounds tileBounds = makeRealTileBoundsWithBorder(mSettings, tilePosition); - tile = worldspaceTiles->mTiles.emplace_hint(tile, tilePosition, - std::make_shared(tileBounds, mTilesGeneration)); - } - if (tile->second->addHeightfield(cellPosition, cellSize, shape)) - { - tilesPositions.push_back(tilePosition); - addChangedTile(tilePosition, ChangeType::add); - changed = true; - } - }); + const std::lock_guard lock(mMutex); + auto it = mHeightfields.find(cellPosition); + if (it != mHeightfields.end()) + return; + const std::size_t revision = mRevision + 1; + it = mHeightfields.emplace_hint(it, cellPosition, HeightfieldData { + .mCellSize = cellSize, + .mShape = shape, + .mRange = range, + .mRevision = revision, + }); + if (range.has_value()) + mHeightfieldIndex.insert(makeHeightfieldIndexValue(*range, it)); + else + mInfiniteHeightfield = it; + mRevision = revision; } - - if (changed) - ++mRevision; + addChangedTiles(range, ChangeType::add); } void TileCachedRecastMeshManager::removeHeightfield(const osg::Vec2i& cellPosition) { - const auto object = mHeightfieldTilesPositions.find(cellPosition); - if (object == mHeightfieldTilesPositions.end()) - return; - bool changed = false; + std::optional range; { - const auto worldspaceTiles = mWorldspaceTiles.lock(); - for (const auto& tilePosition : object->second) - { - const auto tile = worldspaceTiles->mTiles.find(tilePosition); - if (tile == worldspaceTiles->mTiles.end()) - continue; - if (tile->second->removeHeightfield(cellPosition)) - { - addChangedTile(tilePosition, ChangeType::remove); - changed = true; - } - if (tile->second->isEmpty()) - { - worldspaceTiles->mTiles.erase(tile); - ++mTilesGeneration; - } - } - } - mHeightfieldTilesPositions.erase(object); - if (changed) + const std::lock_guard lock(mMutex); + const auto it = mHeightfields.find(cellPosition); + if (it == mHeightfields.end()) + return; + range = it->second.mRange; + if (range.has_value()) + mHeightfieldIndex.remove(makeHeightfieldIndexValue(*range, it)); + else + mInfiniteHeightfield = mHeightfields.end(); + mHeightfields.erase(it); ++mRevision; + } + addChangedTiles(range, ChangeType::remove); } - std::shared_ptr TileCachedRecastMeshManager::getMesh(std::string_view worldspace, const TilePosition& tilePosition) const + std::shared_ptr TileCachedRecastMeshManager::getMesh(std::string_view worldspace, + const TilePosition& tilePosition) { - if (const auto manager = getManager(worldspace, tilePosition)) - return manager->getMesh(); - return nullptr; + { + const std::lock_guard lock(mMutex); + if (mWorldspace != worldspace) + return nullptr; + const auto it = mCache.find(tilePosition); + if (it != mCache.end() && it->second.mRecastMesh->getVersion() == it->second.mVersion) + return it->second.mRecastMesh; + } + auto result = makeMesh(tilePosition); + if (result != nullptr) + { + const std::lock_guard lock(mMutex); + mCache.insert_or_assign(tilePosition, CachedTile { + .mVersion = result->getVersion(), + .mRecastMesh = result, + }); + } + return result; } - std::shared_ptr TileCachedRecastMeshManager::getCachedMesh(std::string_view worldspace, const TilePosition& tilePosition) const + std::shared_ptr TileCachedRecastMeshManager::getCachedMesh(std::string_view worldspace, + const TilePosition& tilePosition) const { - if (const auto manager = getManager(worldspace, tilePosition)) - return manager->getCachedMesh(); - return nullptr; + const std::lock_guard lock(mMutex); + if (mWorldspace != worldspace) + return nullptr; + const auto it = mCache.find(tilePosition); + if (it == mCache.end()) + return nullptr; + return it->second.mRecastMesh; } - std::shared_ptr TileCachedRecastMeshManager::getNewMesh(std::string_view worldspace, const TilePosition& tilePosition) const + std::shared_ptr TileCachedRecastMeshManager::getNewMesh(std::string_view worldspace, + const TilePosition& tilePosition) const { - if (const auto manager = getManager(worldspace, tilePosition)) - return manager->getNewMesh(); - return nullptr; + { + const std::lock_guard lock(mMutex); + if (mWorldspace != worldspace) + return nullptr; + } + return makeMesh(tilePosition); } - void TileCachedRecastMeshManager::reportNavMeshChange(const TilePosition& tilePosition, Version recastMeshVersion, Version navMeshVersion) const + void TileCachedRecastMeshManager::reportNavMeshChange(const TilePosition& tilePosition, + Version recastMeshVersion, Version navMeshVersion) { - const auto locked = mWorldspaceTiles.lockConst(); - const auto it = locked->mTiles.find(tilePosition); - if (it == locked->mTiles.end()) - return; - it->second->reportNavMeshChange(recastMeshVersion, navMeshVersion); + const std::lock_guard lock(mMutex); + for (auto it = mObjectIndex.qbegin(makeIndexQuery(tilePosition)); it != mObjectIndex.qend(); ++it) + { + ObjectData& object = *it->second; + if (recastMeshVersion.mGeneration != object.mGeneration) + continue; + if (object.mLastNavMeshReport.has_value() && navMeshVersion < object.mLastNavMeshReport->mNavMeshVersion) + continue; + object.mLastNavMeshReport = {recastMeshVersion.mRevision, navMeshVersion}; + if (!object.mLastNavMeshReportedChange.has_value() + || object.mLastNavMeshReportedChange->mNavMeshVersion < object.mLastNavMeshReport->mNavMeshVersion) + object.mLastNavMeshReportedChange = object.mLastNavMeshReport; + } } - void TileCachedRecastMeshManager::addChangedTile(const TilePosition& tilePosition, ChangeType changeType) + void TileCachedRecastMeshManager::addChangedTile(const TilePosition& tilePosition, const ChangeType changeType) { auto tile = mChangedTiles.find(tilePosition); if (tile == mChangedTiles.end()) @@ -388,44 +378,112 @@ namespace DetourNavigator tile->second = addChangeType(tile->second, changeType); } - bool TileCachedRecastMeshManager::addTile(const ObjectId id, const CollisionShape& shape, - const btTransform& transform, const AreaType areaType, const TilePosition& tilePosition, - TilesMap& tiles) + std::map TileCachedRecastMeshManager::takeChangedTiles() { - auto tile = tiles.find(tilePosition); - if (tile == tiles.end()) { - const TileBounds tileBounds = makeRealTileBoundsWithBorder(mSettings, tilePosition); - tile = tiles.emplace_hint(tile, tilePosition, - std::make_shared(tileBounds, mTilesGeneration)); + const std::lock_guard lock(mMutex); + for (const auto& [tilePosition, changeType] : mChangedTiles) + if (const auto it = mCache.find(tilePosition); it != mCache.end()) + ++it->second.mVersion.mRevision; } - return tile->second->addObject(id, shape, transform, areaType); + return std::move(mChangedTiles); + } + + TileCachedRecastMeshManager::IndexPoint TileCachedRecastMeshManager::makeIndexPoint(const TilePosition& tilePosition) + { + return IndexPoint(tilePosition.x(), tilePosition.y()); + } + + TileCachedRecastMeshManager::IndexBox TileCachedRecastMeshManager::makeIndexBox(const TilesPositionsRange& range) + { + assert(range.mBegin != range.mEnd); + return IndexBox(makeIndexPoint(range.mBegin), makeIndexPoint(range.mEnd - TilePosition(1, 1))); + } + + TileCachedRecastMeshManager::ObjectIndexValue TileCachedRecastMeshManager::makeObjectIndexValue( + const TilesPositionsRange& range, ObjectData* id) + { + return {makeIndexBox(range), id}; + } + + TileCachedRecastMeshManager::WaterIndexValue TileCachedRecastMeshManager::makeWaterIndexValue( + const TilesPositionsRange& range, std::map::const_iterator it) + { + return {makeIndexBox(range), it}; + } + + TileCachedRecastMeshManager::HeightfieldIndexValue TileCachedRecastMeshManager::makeHeightfieldIndexValue( + const TilesPositionsRange& range, std::map::const_iterator it) + { + return {makeIndexBox(range), it}; + } + + auto TileCachedRecastMeshManager::makeIndexQuery(const TilePosition& tilePosition) + -> decltype(boost::geometry::index::intersects(IndexBox())) + { + const IndexPoint point = makeIndexPoint(tilePosition); + return boost::geometry::index::intersects(IndexBox(point, point)); } - bool TileCachedRecastMeshManager::removeTile(const ObjectId id, - const TilePosition& tilePosition, TilesMap& tiles) + std::shared_ptr TileCachedRecastMeshManager::makeMesh(const TilePosition& tilePosition) const { - const auto tile = tiles.find(tilePosition); - if (tile == tiles.end()) - return false; - const bool result = tile->second->removeObject(id); - if (tile->second->isEmpty()) + RecastMeshBuilder builder(makeRealTileBoundsWithBorder(mSettings, tilePosition)); + using Object = std::tuple< + osg::ref_ptr, + ObjectTransform, + std::reference_wrapper, + btTransform, + AreaType + >; + std::vector objects; + Version version; + bool hasInput = false; { - tiles.erase(tile); - ++mTilesGeneration; + const std::lock_guard lock(mMutex); + for (auto it = mWaterIndex.qbegin(makeIndexQuery(tilePosition)); it != mWaterIndex.qend(); ++it) + { + const auto& [cellPosition, data] = *it->second; + builder.addWater(cellPosition, data.mWater); + hasInput = true; + } + for (auto it = mHeightfieldIndex.qbegin(makeIndexQuery(tilePosition)); it != mHeightfieldIndex.qend(); ++it) + { + const auto& [cellPosition, data] = *it->second; + std::visit(AddHeightfield {cellPosition, data.mCellSize, builder}, data.mShape); + hasInput = true; + } + objects.reserve(mObjects.size()); + for (auto it = mObjectIndex.qbegin(makeIndexQuery(tilePosition)); it != mObjectIndex.qend(); ++it) + { + const auto& object = it->second->mObject; + objects.emplace_back(object.getInstance(), object.getObjectTransform(), object.getShape(), + object.getTransform(), object.getAreaType()); + hasInput = true; + } + if (hasInput) + { + if (mInfiniteWater != mWater.end()) + builder.addWater(mInfiniteWater->first, mInfiniteWater->second.mWater); + if (mInfiniteHeightfield != mHeightfields.end()) + std::visit( + AddHeightfield {mInfiniteHeightfield->first, mInfiniteHeightfield->second.mCellSize, builder}, + mInfiniteHeightfield->second.mShape + ); + version.mGeneration = mGeneration; + version.mRevision = mRevision; + } } - return result; + if (!hasInput) + return nullptr; + for (const auto& [instance, objectTransform, shape, transform, areaType] : objects) + builder.addObject(shape, transform, areaType, instance->getSource(), objectTransform); + return std::move(builder).create(version); } - std::shared_ptr TileCachedRecastMeshManager::getManager(std::string_view worldspace, - const TilePosition& tilePosition) const + void TileCachedRecastMeshManager::addChangedTiles(const std::optional& range, + ChangeType changeType) { - const auto locked = mWorldspaceTiles.lockConst(); - if (locked->mWorldspace != worldspace) - return nullptr; - const auto it = locked->mTiles.find(tilePosition); - if (it == locked->mTiles.end()) - return nullptr; - return it->second; + if (range.has_value()) + getTilesPositions(*range, [&] (const TilePosition& v) { addChangedTile(v, changeType); }); } } diff --git a/components/detournavigator/tilecachedrecastmeshmanager.hpp b/components/detournavigator/tilecachedrecastmeshmanager.hpp index b1bcfa9319..42d79fe383 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.hpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.hpp @@ -9,17 +9,23 @@ #include "objectid.hpp" #include "areatype.hpp" #include "recastmeshobject.hpp" +#include "commulativeaabb.hpp" +#include "version.hpp" +#include "recastmesh.hpp" #include +#include +#include +#include + #include #include #include -#include +#include namespace DetourNavigator { - class CachedRecastMeshManager; class RecastMesh; class TileCachedRecastMeshManager @@ -29,13 +35,13 @@ namespace DetourNavigator void setBounds(const TileBounds& bounds); - std::string getWorldspace() const; + TilesPositionsRange getRange() const; void setWorldspace(std::string_view worldspace); bool addObject(ObjectId id, const CollisionShape& shape, const btTransform& transform, AreaType areaType); - bool updateObject(ObjectId id, const CollisionShape& shape, const btTransform& transform, AreaType areaType); + bool updateObject(ObjectId id, const btTransform& transform, AreaType areaType); void removeObject(ObjectId id); @@ -47,63 +53,101 @@ namespace DetourNavigator void removeHeightfield(const osg::Vec2i& cellPosition); - std::shared_ptr getMesh(std::string_view worldspace, const TilePosition& tilePosition) const; + std::shared_ptr getMesh(std::string_view worldspace, const TilePosition& tilePosition); std::shared_ptr getCachedMesh(std::string_view worldspace, const TilePosition& tilePosition) const; std::shared_ptr getNewMesh(std::string_view worldspace, const TilePosition& tilePosition) const; - template - void forEachTile(Function&& function) const - { - const auto& locked = mWorldspaceTiles.lockConst(); - for (const auto& [tilePosition, recastMeshManager] : locked->mTiles) - function(tilePosition, *recastMeshManager); - } - std::size_t getRevision() const { return mRevision; } - void reportNavMeshChange(const TilePosition& tilePosition, Version recastMeshVersion, Version navMeshVersion) const; + void reportNavMeshChange(const TilePosition& tilePosition, Version recastMeshVersion, Version navMeshVersion); void addChangedTile(const TilePosition& tilePosition, ChangeType changeType); - std::map takeChangedTiles() { return std::move(mChangedTiles); } + std::map takeChangedTiles(); private: - using TilesMap = std::map>; + struct Report + { + std::size_t mRevision; + Version mNavMeshVersion; + }; struct ObjectData { - const CollisionShape mShape; - const btTransform mTransform; - const AreaType mAreaType; - std::set mTiles; + RecastMeshObject mObject; + TilesPositionsRange mRange; + CommulativeAabb mAabb; + std::size_t mGeneration = 0; + std::size_t mRevision = 0; + std::optional mLastNavMeshReportedChange; + std::optional mLastNavMeshReport; + }; + + struct WaterData + { + Water mWater; + std::optional mRange; + std::size_t mRevision; }; - struct WorldspaceTiles + struct HeightfieldData { - std::string mWorldspace; - TilesMap mTiles; + int mCellSize; + HeightfieldShape mShape; + std::optional mRange; + std::size_t mRevision; }; + struct CachedTile + { + Version mVersion; + std::shared_ptr mRecastMesh; + }; + + using IndexPoint = boost::geometry::model::point; + using IndexBox = boost::geometry::model::box; + using ObjectIndexValue = std::pair; + using WaterIndexValue = std::pair::const_iterator>; + using HeightfieldIndexValue = std::pair::const_iterator>; + const RecastSettings& mSettings; TileBounds mBounds; TilesPositionsRange mRange; - Misc::ScopeGuarded mWorldspaceTiles; - std::unordered_map mObjects; - std::map> mWaterTilesPositions; - std::map> mHeightfieldTilesPositions; + std::string mWorldspace; + std::unordered_map> mObjects; + boost::geometry::index::rtree> mObjectIndex; + std::map mWater; + std::map::const_iterator mInfiniteWater = mWater.end(); + boost::geometry::index::rtree> mWaterIndex; + std::map mHeightfields; + std::map::const_iterator mInfiniteHeightfield = mHeightfields.end(); + boost::geometry::index::rtree> mHeightfieldIndex; std::map mChangedTiles; + std::map mCache; + std::size_t mGeneration = 0; std::size_t mRevision = 0; - std::size_t mTilesGeneration = 0; + mutable std::mutex mMutex; + + inline static IndexPoint makeIndexPoint(const TilePosition& tilePosition); + + inline static IndexBox makeIndexBox(const TilesPositionsRange& range); + + inline static ObjectIndexValue makeObjectIndexValue(const TilesPositionsRange& range, ObjectData* data); + + inline static WaterIndexValue makeWaterIndexValue(const TilesPositionsRange& range, + std::map::const_iterator it); + + inline static HeightfieldIndexValue makeHeightfieldIndexValue(const TilesPositionsRange& range, + std::map::const_iterator it); - inline bool addTile(ObjectId id, const CollisionShape& shape, const btTransform& transform, - AreaType areaType, const TilePosition& tilePosition, TilesMap& tiles); + inline static auto makeIndexQuery(const TilePosition& tilePosition) + -> decltype(boost::geometry::index::intersects(IndexBox())); - inline bool removeTile(ObjectId id, const TilePosition& tilePosition, TilesMap& tiles); + inline std::shared_ptr makeMesh(const TilePosition& tilePosition) const; - inline std::shared_ptr getManager(std::string_view worldspace, - const TilePosition& tilePosition) const; + inline void addChangedTiles(const std::optional& range, ChangeType changeType); }; } diff --git a/components/detournavigator/tilespositionsrange.hpp b/components/detournavigator/tilespositionsrange.hpp index d5f2622ba1..0d92dd0e6d 100644 --- a/components/detournavigator/tilespositionsrange.hpp +++ b/components/detournavigator/tilespositionsrange.hpp @@ -3,6 +3,8 @@ #include "tileposition.hpp" +#include + namespace DetourNavigator { struct TilesPositionsRange @@ -10,6 +12,16 @@ namespace DetourNavigator TilePosition mBegin; TilePosition mEnd; }; + + inline auto tie(const TilesPositionsRange& value) + { + return std::tie(value.mBegin, value.mEnd); + } + + inline bool operator==(const TilesPositionsRange& lhs, const TilesPositionsRange& rhs) + { + return tie(lhs) == tie(rhs); + } } #endif