diff --git a/apps/navmeshtool/worldspacedata.cpp b/apps/navmeshtool/worldspacedata.cpp index 742e6d775d..81bbd857f5 100644 --- a/apps/navmeshtool/worldspacedata.cpp +++ b/apps/navmeshtool/worldspacedata.cpp @@ -298,12 +298,14 @@ namespace NavMeshTool const ObjectId objectId(++objectsCounter); const CollisionShape shape(object.getShapeInstance(), *object.getCollisionObject().getCollisionShape(), object.getObjectTransform()); - navMeshInput.mTileCachedRecastMeshManager.addObject(objectId, shape, transform, DetourNavigator::AreaType_ground); + navMeshInput.mTileCachedRecastMeshManager.addObject(objectId, shape, transform, + DetourNavigator::AreaType_ground, [] (const auto&) {}); if (const btCollisionShape* avoid = object.getShapeInstance()->mAvoidCollisionShape.get()) { const CollisionShape avoidShape(object.getShapeInstance(), *avoid, object.getObjectTransform()); - navMeshInput.mTileCachedRecastMeshManager.addObject(objectId, avoidShape, transform, DetourNavigator::AreaType_null); + navMeshInput.mTileCachedRecastMeshManager.addObject(objectId, avoidShape, transform, + DetourNavigator::AreaType_null, [] (const auto&) {}); } data.mObjects.emplace_back(std::move(object)); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 3b2f9cd608..1cb80389ce 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -534,6 +534,8 @@ namespace MWWorld unloadCell (cell); } + mNavigator.updateBounds(pos); + mCurrentGridCenter = osg::Vec2i(playerCellX, playerCellY); osg::Vec4i newGrid = gridCenterToBounds(mCurrentGridCenter); mRendering.setActiveGrid(newGrid); @@ -821,6 +823,8 @@ namespace MWWorld loadingListener->setProgressRange(cell->count()); + mNavigator.updateBounds(position.asVec3()); + // Load cell. mPagedRefs.clear(); loadCell(cell, loadingListener, changeEvent, position.asVec3()); diff --git a/apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp b/apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp index b23446cea7..71c013d9f4 100644 --- a/apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp +++ b/apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp @@ -40,7 +40,7 @@ namespace osg::ref_ptr(new Resource::BulletShapeInstance(bulletShape)), shape, objectTransform ); - recastMeshManager.addObject(id, collisionShape, btTransform::getIdentity(), AreaType_ground); + recastMeshManager.addObject(id, collisionShape, btTransform::getIdentity(), AreaType_ground, [] (auto) {}); } struct DetourNavigatorAsyncNavMeshUpdaterTest : Test diff --git a/apps/openmw_test_suite/detournavigator/operators.hpp b/apps/openmw_test_suite/detournavigator/operators.hpp index fb6fcc5c39..60e1400764 100644 --- a/apps/openmw_test_suite/detournavigator/operators.hpp +++ b/apps/openmw_test_suite/detournavigator/operators.hpp @@ -12,14 +12,6 @@ #include -namespace DetourNavigator -{ - static inline bool operator ==(const TileBounds& lhs, const TileBounds& rhs) - { - return lhs.mMin == rhs.mMin && lhs.mMax == rhs.mMax; - } -} - namespace { template diff --git a/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp index c637d35424..4803f452b3 100644 --- a/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp @@ -16,7 +16,8 @@ namespace struct DetourNavigatorTileCachedRecastMeshManagerTest : Test { RecastSettings mSettings; - std::vector mChangedTiles; + std::vector mAddedTiles; + std::vector> mChangedTiles; const ObjectTransform mObjectTransform {ESM::Position {{0, 0, 0}, {0, 0, 0}}, 0.0f}; const osg::ref_ptr mShape = new Resource::BulletShape; const osg::ref_ptr mInstance = new Resource::BulletShapeInstance(mShape); @@ -29,9 +30,14 @@ namespace mSettings.mTileSize = 64; } - void onChangedTile(const TilePosition& tilePosition) + void onAddedTile(const TilePosition& tilePosition) { - mChangedTiles.push_back(tilePosition); + mAddedTiles.push_back(tilePosition); + } + + void onChangedTile(const TilePosition& tilePosition, ChangeType changeType) + { + mChangedTiles.emplace_back(tilePosition, changeType); } }; @@ -60,16 +66,16 @@ namespace TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); - EXPECT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); + EXPECT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {})); } - TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_for_existing_object_should_throw_exception) + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_for_existing_object_should_return_false) { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); - manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); - EXPECT_FALSE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); + manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); + EXPECT_FALSE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {})); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_should_add_tiles) @@ -78,26 +84,47 @@ namespace manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); - ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); + ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {})); for (int x = -1; x < 1; ++x) for (int y = -1; y < 1; ++y) ASSERT_NE(manager.getMesh("worldspace", TilePosition(x, y)), nullptr); } + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_should_return_added_tiles) + { + TileCachedRecastMeshManager manager(mSettings); + const btBoxShape boxShape(btVector3(20, 20, 100)); + const CollisionShape shape(mInstance, boxShape, mObjectTransform); + TileBounds bounds; + bounds.mMin = osg::Vec2f(182, 182); + bounds.mMax = osg::Vec2f(1000, 1000); + manager.setBounds(bounds); + manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, + [&] (const auto& v) { onAddedTile(v); }); + EXPECT_THAT(mAddedTiles, ElementsAre(TilePosition(0, 0))); + } + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, update_object_for_changed_object_should_return_changed_tiles) { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); - manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground); + TileBounds bounds; + bounds.mMin = osg::Vec2f(-1000, -1000); + bounds.mMax = osg::Vec2f(1000, 1000); + manager.setBounds(bounds); + manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground, [] (auto) {}); EXPECT_TRUE(manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, - [&] (const auto& v) { onChangedTile(v); })); - EXPECT_THAT( - mChangedTiles, - ElementsAre(TilePosition(-1, -1), TilePosition(-1, 0), TilePosition(0, -1), TilePosition(0, 0), - TilePosition(1, -1), TilePosition(1, 0)) - ); + [&] (const auto& ... v) { onChangedTile(v ...); })); + EXPECT_THAT(mChangedTiles, ElementsAre( + std::pair(TilePosition(-1, -1), ChangeType::add), + std::pair(TilePosition(-1, 0), ChangeType::add), + std::pair(TilePosition(0, -1), ChangeType::update), + std::pair(TilePosition(0, 0), ChangeType::update), + std::pair(TilePosition(1, -1), ChangeType::remove), + std::pair(TilePosition(1, 0), ChangeType::remove) + )); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, update_object_for_not_changed_object_should_return_empty) @@ -105,10 +132,10 @@ namespace TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); - manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); + manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); EXPECT_FALSE(manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, - [&] (const auto& v) { onChangedTile(v); })); - EXPECT_EQ(mChangedTiles, std::vector()); + [&] (const auto& ... v) { onChangedTile(v ...); })); + EXPECT_THAT(mChangedTiles, IsEmpty()); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_after_add_object_should_return_recast_mesh_for_each_used_tile) @@ -117,7 +144,7 @@ namespace manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); - manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); + manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); 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); @@ -130,26 +157,30 @@ namespace manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); - manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); + manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(1, 0)), nullptr); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_moved_object_should_return_recast_mesh_for_each_used_tile) { TileCachedRecastMeshManager manager(mSettings); + TileBounds bounds; + bounds.mMin = osg::Vec2f(-1000, -1000); + bounds.mMax = osg::Vec2f(1000, 1000); + manager.setBounds(bounds); manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); - manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground); + manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground, [] (auto) {}); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, 0)), nullptr); 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, [] (auto) {}); + manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto, auto) {}); 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); @@ -165,11 +196,11 @@ namespace const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); - manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground); + manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground, [] (auto) {}); 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, [] (auto) {}); + manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto, auto) {}); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(1, 0)), nullptr); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(1, -1)), nullptr); } @@ -180,7 +211,7 @@ namespace manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); - manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); + manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); manager.removeObject(ObjectId(&boxShape)); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr); @@ -195,13 +226,13 @@ namespace const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); - manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); + manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); 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); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, 0)), nullptr); - manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); + manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto, auto) {}); 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); @@ -214,7 +245,7 @@ namespace const auto initialRevision = manager.getRevision(); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); - manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); + manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); EXPECT_EQ(manager.getRevision(), initialRevision + 1); } @@ -223,9 +254,9 @@ namespace TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); - manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); + manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); const auto beforeAddRevision = manager.getRevision(); - EXPECT_FALSE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); + EXPECT_FALSE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {})); EXPECT_EQ(manager.getRevision(), beforeAddRevision); } @@ -235,9 +266,9 @@ namespace const btBoxShape boxShape(btVector3(20, 20, 100)); const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); - manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground); + manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground, [] (auto) {}); const auto beforeUpdateRevision = manager.getRevision(); - manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); + manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto, auto) {}); EXPECT_EQ(manager.getRevision(), beforeUpdateRevision + 1); } @@ -247,9 +278,9 @@ namespace manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); - manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); + manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); const auto beforeUpdateRevision = manager.getRevision(); - manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); + manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto, auto) {}); EXPECT_EQ(manager.getRevision(), beforeUpdateRevision); } @@ -258,7 +289,7 @@ namespace TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); - manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); + manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); const auto beforeRemoveRevision = manager.getRevision(); manager.removeObject(ObjectId(&boxShape)); EXPECT_EQ(manager.getRevision(), beforeRemoveRevision + 1); @@ -298,7 +329,7 @@ namespace manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); - ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); + ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {})); const osg::Vec2i cellPosition(0, 0); const int cellSize = std::numeric_limits::max(); ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f)); @@ -343,7 +374,7 @@ namespace manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); - ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); + ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {})); const osg::Vec2i cellPosition(0, 0); const int cellSize = 8192; ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f)); @@ -361,7 +392,7 @@ namespace const int cellSize = 8192; const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(mInstance, boxShape, mObjectTransform); - ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); + ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {})); ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f)); ASSERT_TRUE(manager.removeObject(ObjectId(&boxShape))); for (int x = -1; x < 12; ++x) @@ -375,10 +406,28 @@ namespace manager.setWorldspace("worldspace"); const btBoxShape boxShape(btVector3(20, 20, 100)); const CollisionShape shape(nullptr, boxShape, mObjectTransform); - ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); + ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {})); manager.setWorldspace("other"); for (int x = -1; x < 1; ++x) for (int y = -1; y < 1; ++y) ASSERT_EQ(manager.getMesh("other", TilePosition(x, y)), nullptr); } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, set_bounds_should_return_changed_tiles) + { + TileCachedRecastMeshManager manager(mSettings); + const btBoxShape boxShape(btVector3(20, 20, 100)); + const CollisionShape shape(mInstance, boxShape, mObjectTransform); + TileBounds bounds; + bounds.mMin = osg::Vec2f(182, 0); + bounds.mMax = osg::Vec2f(1000, 1000); + manager.setBounds(bounds); + manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); + bounds.mMin = osg::Vec2f(-1000, -1000); + bounds.mMax = osg::Vec2f(0, -182); + EXPECT_THAT(manager.setBounds(bounds), ElementsAre( + std::pair(TilePosition(-1, -1), ChangeType::add), + std::pair(TilePosition(0, 0), ChangeType::remove) + )); + } } diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index 31778f8c26..fa8cba03af 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -8,6 +8,7 @@ #include "navmeshtilescache.hpp" #include "waitconditiontype.hpp" #include "navmeshdb.hpp" +#include "changetype.hpp" #include @@ -32,29 +33,6 @@ namespace Loading namespace DetourNavigator { - enum class ChangeType - { - remove = 0, - mixed = 1, - add = 2, - update = 3, - }; - - inline std::ostream& operator <<(std::ostream& stream, ChangeType value) - { - switch (value) { - case ChangeType::remove: - return stream << "ChangeType::remove"; - case ChangeType::mixed: - return stream << "ChangeType::mixed"; - case ChangeType::add: - return stream << "ChangeType::add"; - case ChangeType::update: - return stream << "ChangeType::update"; - } - return stream << "ChangeType::" << static_cast(value); - } - enum class JobState { Initial, diff --git a/components/detournavigator/changetype.hpp b/components/detournavigator/changetype.hpp new file mode 100644 index 0000000000..1ede6aec13 --- /dev/null +++ b/components/detournavigator/changetype.hpp @@ -0,0 +1,33 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_CHANGETYPE_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_CHANGETYPE_H + +#include + +namespace DetourNavigator +{ + enum class ChangeType + { + remove = 0, + mixed = 1, + add = 2, + update = 3, + }; + + inline std::ostream& operator <<(std::ostream& stream, ChangeType value) + { + switch (value) + { + case ChangeType::remove: + return stream << "ChangeType::remove"; + case ChangeType::mixed: + return stream << "ChangeType::mixed"; + case ChangeType::add: + return stream << "ChangeType::add"; + case ChangeType::update: + return stream << "ChangeType::update"; + } + return stream << "ChangeType::" << static_cast(value); + } +} + +#endif diff --git a/components/detournavigator/gettilespositions.cpp b/components/detournavigator/gettilespositions.cpp index 7a179aff24..2d42ec25bb 100644 --- a/components/detournavigator/gettilespositions.cpp +++ b/components/detournavigator/gettilespositions.cpp @@ -39,6 +39,14 @@ namespace DetourNavigator return makeTilesPositionsRange(bounds.mMin, bounds.mMax, settings); } + TilesPositionsRange makeTilesPositionsRange(const btCollisionShape& shape, const btTransform& transform, + const TileBounds& bounds, const RecastSettings& settings) + { + if (const auto intersection = getIntersection(bounds, makeObjectTileBounds(shape, transform))) + return makeTilesPositionsRange(intersection->mMin, intersection->mMax, settings); + return {}; + } + TilesPositionsRange makeTilesPositionsRange(const int cellSize, const btVector3& shift, const RecastSettings& settings) { @@ -55,4 +63,17 @@ namespace DetourNavigator return makeTilesPositionsRange(Misc::Convert::toOsgXY(aabbMin), Misc::Convert::toOsgXY(aabbMax), settings); } + + TilesPositionsRange getIntersection(const TilesPositionsRange& a, const TilesPositionsRange& b) noexcept + { + const int beginX = std::max(a.mBegin.x(), b.mBegin.x()); + const int endX = std::min(a.mEnd.x(), b.mEnd.x()); + if (beginX > endX) + return {}; + const int beginY = std::max(a.mBegin.y(), b.mBegin.y()); + const int endY = std::min(a.mEnd.y(), b.mEnd.y()); + if (beginY > endY) + return {}; + return TilesPositionsRange {TilePosition(beginX, beginY), TilePosition(endX, endY)}; + } } diff --git a/components/detournavigator/gettilespositions.hpp b/components/detournavigator/gettilespositions.hpp index db88708314..79188868dc 100644 --- a/components/detournavigator/gettilespositions.hpp +++ b/components/detournavigator/gettilespositions.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_GETTILESPOSITIONS_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_GETTILESPOSITIONS_H +#include "tilebounds.hpp" #include "tileposition.hpp" class btVector3; @@ -28,6 +29,9 @@ namespace DetourNavigator TilesPositionsRange makeTilesPositionsRange(const btCollisionShape& shape, const btTransform& transform, const RecastSettings& settings); + TilesPositionsRange makeTilesPositionsRange(const btCollisionShape& shape, + const btTransform& transform, const TileBounds& bounds, const RecastSettings& settings); + TilesPositionsRange makeTilesPositionsRange(const int cellSize, const btVector3& shift, const RecastSettings& settings); @@ -38,6 +42,19 @@ namespace DetourNavigator for (int tileY = range.mBegin.y(); tileY < range.mEnd.y(); ++tileY) callback(TilePosition {tileX, tileY}); } + + inline bool isInTilesPositionsRange(int begin, int end, int coordinate) + { + return begin <= coordinate && coordinate < end; + } + + inline bool isInTilesPositionsRange(const TilesPositionsRange& range, const TilePosition& position) + { + return isInTilesPositionsRange(range.mBegin.x(), range.mEnd.x(), position.x()) + && isInTilesPositionsRange(range.mBegin.y(), range.mEnd.y(), position.y()); + } + + TilesPositionsRange getIntersection(const TilesPositionsRange& a, const TilesPositionsRange& b) noexcept; } #endif diff --git a/components/detournavigator/navigator.hpp b/components/detournavigator/navigator.hpp index 14731fcc5b..65f7c43fc1 100644 --- a/components/detournavigator/navigator.hpp +++ b/components/detournavigator/navigator.hpp @@ -83,6 +83,12 @@ namespace DetourNavigator */ virtual void setWorldspace(std::string_view worldspace) = 0; + /** + * @brief updateBounds should be called before adding object from loading cell + * @param playerPosition corresponds to the bounds center + */ + virtual void updateBounds(const osg::Vec3f& playerPosition) = 0; + /** * @brief addObject is used to add complex object with allowed to walk and avoided to walk shapes * @param id is used to distinguish different objects diff --git a/components/detournavigator/navigatorimpl.cpp b/components/detournavigator/navigatorimpl.cpp index 4f6ec79328..42cd8bd9d1 100644 --- a/components/detournavigator/navigatorimpl.cpp +++ b/components/detournavigator/navigatorimpl.cpp @@ -38,6 +38,11 @@ namespace DetourNavigator mNavMeshManager.setWorldspace(worldspace); } + void NavigatorImpl::updateBounds(const osg::Vec3f& playerPosition) + { + mNavMeshManager.updateBounds(playerPosition); + } + bool NavigatorImpl::addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) { const CollisionShape collisionShape(shapes.mShapeInstance, *shapes.mShapeInstance->mCollisionShape, shapes.mTransform); @@ -158,6 +163,7 @@ namespace DetourNavigator const TilePosition tilePosition = getTilePosition(mSettings.mRecast, toNavMeshCoordinates(mSettings.mRecast, playerPosition)); if (mLastPlayerPosition.has_value() && *mLastPlayerPosition == tilePosition) return; + mNavMeshManager.updateBounds(playerPosition); update(playerPosition); mLastPlayerPosition = tilePosition; } diff --git a/components/detournavigator/navigatorimpl.hpp b/components/detournavigator/navigatorimpl.hpp index 116817395c..58c4c6c05d 100644 --- a/components/detournavigator/navigatorimpl.hpp +++ b/components/detournavigator/navigatorimpl.hpp @@ -24,6 +24,8 @@ namespace DetourNavigator void setWorldspace(std::string_view worldspace) override; + void updateBounds(const osg::Vec3f& playerPosition) override; + bool addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) override; bool addObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform) override; diff --git a/components/detournavigator/navigatorstub.hpp b/components/detournavigator/navigatorstub.hpp index 6a320c1a08..466ff5c912 100644 --- a/components/detournavigator/navigatorstub.hpp +++ b/components/detournavigator/navigatorstub.hpp @@ -72,6 +72,8 @@ namespace DetourNavigator void update(const osg::Vec3f& /*playerPosition*/) override {} + void updateBounds(const osg::Vec3f& /*playerPosition*/) override {} + void updatePlayerPosition(const osg::Vec3f& /*playerPosition*/) override {}; void setUpdatesEnabled(bool /*enabled*/) override {} diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index 9fba4ad611..bbaf010807 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -42,6 +42,18 @@ namespace namespace DetourNavigator { + namespace + { + TileBounds makeBounds(const RecastSettings& settings, const osg::Vec2f& center, int maxTiles) + { + const float radius = fromNavMeshCoordinates(settings, std::ceil(std::sqrt(static_cast(maxTiles) / osg::PIf) + 1) * getTileSize(settings)); + TileBounds result; + result.mMin = center - osg::Vec2f(radius, radius); + result.mMax = center + osg::Vec2f(radius, radius); + return result; + } + } + NavMeshManager::NavMeshManager(const Settings& settings, std::unique_ptr&& db) : mSettings(settings) , mRecastMeshManager(settings.mRecast) @@ -59,21 +71,37 @@ namespace DetourNavigator mWorldspace = worldspace; } + void NavMeshManager::updateBounds(const osg::Vec3f& playerPosition) + { + const TileBounds bounds = makeBounds(mSettings.mRecast, osg::Vec2f(playerPosition.x(), playerPosition.y()), + mSettings.mMaxTilesNumber); + const auto changedTiles = mRecastMeshManager.setBounds(bounds); + for (const auto& [agent, cache] : mCache) + { + auto& tiles = mChangedTiles[agent]; + for (const auto& [tilePosition, changeType] : changedTiles) + { + auto tile = tiles.find(tilePosition); + if (tile == tiles.end()) + tiles.emplace_hint(tile, tilePosition, changeType); + else + tile->second = addChangeType(tile->second, changeType); + } + } + } + bool NavMeshManager::addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType) { - const btCollisionShape& collisionShape = shape.getShape(); - if (!mRecastMeshManager.addObject(id, shape, transform, areaType)) - return false; - addChangedTiles(collisionShape, transform, ChangeType::add); - return true; + return mRecastMeshManager.addObject(id, shape, transform, areaType, + [&] (const TilePosition& tile) { addChangedTile(tile, ChangeType::add); }); } bool NavMeshManager::updateObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType) { return mRecastMeshManager.updateObject(id, shape, transform, areaType, - [&] (const TilePosition& tile) { addChangedTile(tile, ChangeType::update); }); + [&] (const TilePosition& tile, ChangeType changeType) { addChangedTile(tile, changeType); }); } bool NavMeshManager::removeObject(const ObjectId id) @@ -263,7 +291,8 @@ namespace DetourNavigator void NavMeshManager::addChangedTiles(const btCollisionShape& shape, const btTransform& transform, const ChangeType changeType) { - getTilesPositions(makeTilesPositionsRange(shape, transform, mSettings.mRecast), + const auto bounds = mRecastMeshManager.getBounds(); + getTilesPositions(makeTilesPositionsRange(shape, transform, bounds, mSettings.mRecast), [&] (const TilePosition& v) { addChangedTile(v, changeType); }); } diff --git a/components/detournavigator/navmeshmanager.hpp b/components/detournavigator/navmeshmanager.hpp index 3fd2d28d74..01937d22bd 100644 --- a/components/detournavigator/navmeshmanager.hpp +++ b/components/detournavigator/navmeshmanager.hpp @@ -26,6 +26,8 @@ namespace DetourNavigator void setWorldspace(std::string_view worldspace); + void updateBounds(const osg::Vec3f& playerPosition); + bool addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType); diff --git a/components/detournavigator/settingsutils.hpp b/components/detournavigator/settingsutils.hpp index a4c68e3cd5..c1e611aaf8 100644 --- a/components/detournavigator/settingsutils.hpp +++ b/components/detournavigator/settingsutils.hpp @@ -37,6 +37,11 @@ namespace DetourNavigator }; } + inline float fromNavMeshCoordinates(const RecastSettings& settings, float value) + { + return value / settings.mRecastScaleFactor; + } + inline osg::Vec3f fromNavMeshCoordinates(const RecastSettings& settings, osg::Vec3f position) { const auto factor = 1.0f / settings.mRecastScaleFactor; diff --git a/components/detournavigator/tilebounds.hpp b/components/detournavigator/tilebounds.hpp index 79f6370ff2..2dc32292e8 100644 --- a/components/detournavigator/tilebounds.hpp +++ b/components/detournavigator/tilebounds.hpp @@ -21,9 +21,24 @@ namespace DetourNavigator osg::Vec2f mMax; }; + inline auto tie(const TileBounds& value) noexcept + { + return std::tie(value.mMin, value.mMax); + } + inline bool operator<(const TileBounds& lhs, const TileBounds& rhs) noexcept { - return std::tie(lhs.mMin, lhs.mMax) < std::tie(rhs.mMin, rhs.mMax); + return tie(lhs) < tie(rhs); + } + + inline bool operator ==(const TileBounds& lhs, const TileBounds& rhs) noexcept + { + return tie(lhs) == tie(rhs); + } + + inline bool operator !=(const TileBounds& lhs, const TileBounds& rhs) noexcept + { + return !(lhs == rhs); } inline std::optional getIntersection(const TileBounds& a, const TileBounds& b) noexcept diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index 314e7641e3..c7bd3bc867 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -2,19 +2,93 @@ #include "makenavmesh.hpp" #include "gettilespositions.hpp" #include "settingsutils.hpp" +#include "changetype.hpp" #include #include #include #include +#include namespace DetourNavigator { + namespace + { + const TileBounds infiniteTileBounds { + osg::Vec2f(-std::numeric_limits::max(), -std::numeric_limits::max()), + osg::Vec2f(std::numeric_limits::max(), std::numeric_limits::max()) + }; + } + TileCachedRecastMeshManager::TileCachedRecastMeshManager(const RecastSettings& settings) : mSettings(settings) + , mBounds(infiniteTileBounds) + , mRange(makeTilesPositionsRange(mBounds.mMin, mBounds.mMax, mSettings)) {} + TileBounds TileCachedRecastMeshManager::getBounds() const + { + return mBounds; + } + + std::vector> TileCachedRecastMeshManager::setBounds(const TileBounds& bounds) + { + std::vector> changedTiles; + + if (mBounds == bounds) + return changedTiles; + + const auto newRange = makeTilesPositionsRange(bounds.mMin, bounds.mMax, mSettings); + + if (mBounds != infiniteTileBounds) + { + const std::lock_guard lock(mMutex); + for (auto& object : mObjects) + { + const ObjectId id = object.first; + ObjectData& data = object.second; + const TilesPositionsRange objectRange = makeTilesPositionsRange(data.mShape.getShape(), data.mTransform, mSettings); + + const auto onOldTilePosition = [&] (const TilePosition& position) + { + 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, mTiles)) + changedTiles.emplace_back(position, ChangeType::remove); + }; + getTilesPositions(getIntersection(mRange, objectRange), onOldTilePosition); + + const auto onNewTilePosition = [&] (const TilePosition& position) + { + if (data.mTiles.find(position) != data.mTiles.end()) + return; + if (addTile(id, data.mShape, data.mTransform, data.mAreaType, position, mTiles)) + { + data.mTiles.insert(position); + changedTiles.emplace_back(position, ChangeType::add); + } + }; + getTilesPositions(getIntersection(newRange, objectRange), onNewTilePosition); + } + + std::sort(changedTiles.begin(), changedTiles.end()); + changedTiles.erase(std::unique(changedTiles.begin(), changedTiles.end()), changedTiles.end()); + } + + if (!changedTiles.empty()) + ++mRevision; + + mBounds = bounds; + mRange = newRange; + + return changedTiles; + } + std::string TileCachedRecastMeshManager::getWorldspace() const { const std::lock_guard lock(mMutex); @@ -30,47 +104,22 @@ namespace DetourNavigator mWorldspace = worldspace; } - bool TileCachedRecastMeshManager::addObject(const ObjectId id, const CollisionShape& shape, - const btTransform& transform, const AreaType areaType) - { - const auto it = mObjectsTilesPositions.find(id); - if (it != mObjectsTilesPositions.end()) - return false; - std::vector tilesPositions; - { - const TilesPositionsRange range = makeTilesPositionsRange(shape.getShape(), transform, mSettings); - const std::lock_guard lock(mMutex); - getTilesPositions(range, - [&] (const TilePosition& tilePosition) - { - if (addTile(id, shape, transform, areaType, tilePosition, mTiles)) - tilesPositions.push_back(tilePosition); - }); - } - if (tilesPositions.empty()) - return false; - std::sort(tilesPositions.begin(), tilesPositions.end()); - mObjectsTilesPositions.emplace_hint(it, id, std::move(tilesPositions)); - ++mRevision; - return true; - } - std::optional TileCachedRecastMeshManager::removeObject(const ObjectId id) { - const auto object = mObjectsTilesPositions.find(id); - if (object == mObjectsTilesPositions.end()) + const auto object = mObjects.find(id); + if (object == mObjects.end()) return std::nullopt; std::optional result; { const std::lock_guard lock(mMutex); - for (const auto& tilePosition : object->second) + for (const auto& tilePosition : object->second.mTiles) { const auto removed = removeTile(id, tilePosition, mTiles); if (removed && !result) result = removed; } } - mObjectsTilesPositions.erase(object); + mObjects.erase(object); if (result) ++mRevision; return result; diff --git a/components/detournavigator/tilecachedrecastmeshmanager.hpp b/components/detournavigator/tilecachedrecastmeshmanager.hpp index efd70477d3..906355524e 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.hpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.hpp @@ -7,11 +7,13 @@ #include "gettilespositions.hpp" #include "version.hpp" #include "heightfieldshape.hpp" +#include "changetype.hpp" #include #include #include #include +#include namespace DetourNavigator { @@ -20,58 +22,85 @@ namespace DetourNavigator public: explicit TileCachedRecastMeshManager(const RecastSettings& settings); + TileBounds getBounds() const; + + std::vector> setBounds(const TileBounds& bounds); + std::string getWorldspace() const; void setWorldspace(std::string_view worldspace); + template bool addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, - const AreaType areaType); + const AreaType areaType, OnChangedTile&& onChangedTile) + { + 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 std::lock_guard lock(mMutex); + getTilesPositions(range, + [&] (const TilePosition& tilePosition) + { + if (addTile(id, shape, transform, areaType, tilePosition, mTiles)) + tilesPositions.insert(tilePosition); + }); + } + it = mObjects.emplace_hint(it, id, ObjectData {shape, transform, areaType, std::move(tilesPositions)}); + std::for_each(it->second.mTiles.begin(), it->second.mTiles.end(), std::forward(onChangedTile)); + ++mRevision; + return true; + } template bool updateObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType, OnChangedTile&& onChangedTile) { - const auto object = mObjectsTilesPositions.find(id); - if (object == mObjectsTilesPositions.end()) + const auto object = mObjects.find(id); + if (object == mObjects.end()) return false; - auto& currentTiles = object->second; + auto& data = object->second; bool changed = false; - std::vector newTiles; + std::set newTiles; { const auto onTilePosition = [&] (const TilePosition& tilePosition) { - if (std::binary_search(currentTiles.begin(), currentTiles.end(), tilePosition)) + if (data.mTiles.find(tilePosition) != data.mTiles.end()) { - newTiles.push_back(tilePosition); + newTiles.insert(tilePosition); if (updateTile(id, transform, areaType, tilePosition, mTiles)) { - onChangedTile(tilePosition); + onChangedTile(tilePosition, ChangeType::update); changed = true; } } else if (addTile(id, shape, transform, areaType, tilePosition, mTiles)) { - newTiles.push_back(tilePosition); - onChangedTile(tilePosition); + newTiles.insert(tilePosition); + onChangedTile(tilePosition, ChangeType::add); changed = true; } }; - const TilesPositionsRange range = makeTilesPositionsRange(shape.getShape(), transform, mSettings); + const TilesPositionsRange objectRange = makeTilesPositionsRange(shape.getShape(), transform, mSettings); + const TilesPositionsRange range = getIntersection(mRange, objectRange); const std::lock_guard lock(mMutex); getTilesPositions(range, onTilePosition); - std::sort(newTiles.begin(), newTiles.end()); - for (const auto& tile : currentTiles) + for (const auto& tile : data.mTiles) { - if (!std::binary_search(newTiles.begin(), newTiles.end(), tile) && removeTile(id, tile, mTiles)) + if (newTiles.find(tile) == newTiles.end() && removeTile(id, tile, mTiles)) { - onChangedTile(tile); + onChangedTile(tile, ChangeType::remove); changed = true; } } } if (changed) { - currentTiles = std::move(newTiles); + data.mTiles = std::move(newTiles); ++mRevision; } return changed; @@ -108,11 +137,21 @@ namespace DetourNavigator private: using TilesMap = std::map>; + struct ObjectData + { + const CollisionShape mShape; + const btTransform mTransform; + const AreaType mAreaType; + std::set mTiles; + }; + const RecastSettings& mSettings; mutable std::mutex mMutex; + TileBounds mBounds; + TilesPositionsRange mRange; std::string mWorldspace; TilesMap mTiles; - std::unordered_map> mObjectsTilesPositions; + std::unordered_map mObjects; std::map> mWaterTilesPositions; std::map> mHeightfieldTilesPositions; std::size_t mRevision = 0;