Merge branch 'navigator_changed_tiles' into 'master'

Store changed tiles in TileCachedRecastMeshManager

See merge request OpenMW/openmw!2367
crashfix_debugdraw
psi29a 2 years ago
commit 8b19424cf6

@ -318,13 +318,13 @@ namespace NavMeshTool
const CollisionShape shape(object.getShapeInstance(), *object.getCollisionObject().getCollisionShape(), object.getObjectTransform()); const CollisionShape shape(object.getShapeInstance(), *object.getCollisionObject().getCollisionShape(), object.getObjectTransform());
navMeshInput.mTileCachedRecastMeshManager.addObject(objectId, shape, transform, navMeshInput.mTileCachedRecastMeshManager.addObject(objectId, shape, transform,
DetourNavigator::AreaType_ground, [] (const auto&) {}); DetourNavigator::AreaType_ground);
if (const btCollisionShape* avoid = object.getShapeInstance()->mAvoidCollisionShape.get()) if (const btCollisionShape* avoid = object.getShapeInstance()->mAvoidCollisionShape.get())
{ {
const CollisionShape avoidShape(object.getShapeInstance(), *avoid, object.getObjectTransform()); const CollisionShape avoidShape(object.getShapeInstance(), *avoid, object.getObjectTransform());
navMeshInput.mTileCachedRecastMeshManager.addObject(objectId, avoidShape, transform, navMeshInput.mTileCachedRecastMeshManager.addObject(objectId, avoidShape, transform,
DetourNavigator::AreaType_null, [] (const auto&) {}); DetourNavigator::AreaType_null);
} }
data.mObjects.emplace_back(std::move(object)); data.mObjects.emplace_back(std::move(object));

@ -8,6 +8,7 @@
#include <components/resource/bulletshape.hpp> #include <components/resource/bulletshape.hpp>
#include <BulletCollision/CollisionDispatch/btCollisionObject.h> #include <BulletCollision/CollisionDispatch/btCollisionObject.h>
#include <BulletCollision/Gimpact/btBoxCollision.h>
#include <LinearMath/btVector3.h> #include <LinearMath/btVector3.h>
#include <memory> #include <memory>

@ -42,7 +42,7 @@ namespace
osg::ref_ptr<Resource::BulletShapeInstance>(new Resource::BulletShapeInstance(bulletShape)), osg::ref_ptr<Resource::BulletShapeInstance>(new Resource::BulletShapeInstance(bulletShape)),
shape, objectTransform shape, objectTransform
); );
recastMeshManager.addObject(id, collisionShape, btTransform::getIdentity(), AreaType_ground, [] (auto) {}); recastMeshManager.addObject(id, collisionShape, btTransform::getIdentity(), AreaType_ground);
} }
struct DetourNavigatorAsyncNavMeshUpdaterTest : Test struct DetourNavigatorAsyncNavMeshUpdaterTest : Test

@ -5,6 +5,8 @@
#include <BulletCollision/CollisionShapes/btBoxShape.h> #include <BulletCollision/CollisionShapes/btBoxShape.h>
#include <osg/io_utils>
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <gmock/gmock.h> #include <gmock/gmock.h>
@ -16,8 +18,6 @@ namespace
struct DetourNavigatorTileCachedRecastMeshManagerTest : Test struct DetourNavigatorTileCachedRecastMeshManagerTest : Test
{ {
RecastSettings mSettings; RecastSettings mSettings;
std::vector<TilePosition> mAddedTiles;
std::vector<std::pair<TilePosition, ChangeType>> mChangedTiles;
const ObjectTransform mObjectTransform {ESM::Position {{0, 0, 0}, {0, 0, 0}}, 0.0f}; const ObjectTransform mObjectTransform {ESM::Position {{0, 0, 0}, {0, 0, 0}}, 0.0f};
const osg::ref_ptr<const Resource::BulletShape> mShape = new Resource::BulletShape; const osg::ref_ptr<const Resource::BulletShape> mShape = new Resource::BulletShape;
const osg::ref_ptr<const Resource::BulletShapeInstance> mInstance = new Resource::BulletShapeInstance(mShape); const osg::ref_ptr<const Resource::BulletShapeInstance> mInstance = new Resource::BulletShapeInstance(mShape);
@ -29,16 +29,6 @@ namespace
mSettings.mRecastScaleFactor = 0.017647058823529415f; mSettings.mRecastScaleFactor = 0.017647058823529415f;
mSettings.mTileSize = 64; mSettings.mTileSize = 64;
} }
void onAddedTile(const TilePosition& tilePosition)
{
mAddedTiles.push_back(tilePosition);
}
void onChangedTile(const TilePosition& tilePosition, ChangeType changeType)
{
mChangedTiles.emplace_back(tilePosition, changeType);
}
}; };
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_empty_should_return_nullptr) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_empty_should_return_nullptr)
@ -66,7 +56,7 @@ namespace
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
const btBoxShape boxShape(btVector3(20, 20, 100)); const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform); const CollisionShape shape(mInstance, boxShape, mObjectTransform);
EXPECT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {})); EXPECT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground));
} }
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_for_existing_object_should_return_false) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_for_existing_object_should_return_false)
@ -74,8 +64,8 @@ namespace
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
const btBoxShape boxShape(btVector3(20, 20, 100)); const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform); const CollisionShape shape(mInstance, boxShape, mObjectTransform);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
EXPECT_FALSE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {})); EXPECT_FALSE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground));
} }
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_should_add_tiles) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_should_add_tiles)
@ -84,13 +74,13 @@ namespace
manager.setWorldspace("worldspace"); manager.setWorldspace("worldspace");
const btBoxShape boxShape(btVector3(20, 20, 100)); const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform); const CollisionShape shape(mInstance, boxShape, mObjectTransform);
ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {})); ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground));
for (int x = -1; x < 1; ++x) for (int x = -1; x < 1; ++x)
for (int y = -1; y < 1; ++y) for (int y = -1; y < 1; ++y)
ASSERT_NE(manager.getMesh("worldspace", TilePosition(x, y)), nullptr); ASSERT_NE(manager.getMesh("worldspace", TilePosition(x, y)), nullptr);
} }
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_should_return_added_tiles) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_should_return_add_changed_tiles)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
const btBoxShape boxShape(btVector3(20, 20, 100)); const btBoxShape boxShape(btVector3(20, 20, 100));
@ -99,12 +89,11 @@ namespace
bounds.mMin = osg::Vec2f(182, 182); bounds.mMin = osg::Vec2f(182, 182);
bounds.mMax = osg::Vec2f(1000, 1000); bounds.mMax = osg::Vec2f(1000, 1000);
manager.setBounds(bounds); manager.setBounds(bounds);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
[&] (const auto& v) { onAddedTile(v); }); EXPECT_THAT(manager.takeChangedTiles(), ElementsAre(std::pair(TilePosition(0, 0), ChangeType::add)));
EXPECT_THAT(mAddedTiles, ElementsAre(TilePosition(0, 0)));
} }
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, update_object_for_changed_object_should_return_changed_tiles) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, update_object_for_changed_object_should_add_changed_tiles)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
const btBoxShape boxShape(btVector3(20, 20, 100)); const btBoxShape boxShape(btVector3(20, 20, 100));
@ -114,10 +103,10 @@ namespace
bounds.mMin = osg::Vec2f(-1000, -1000); bounds.mMin = osg::Vec2f(-1000, -1000);
bounds.mMax = osg::Vec2f(1000, 1000); bounds.mMax = osg::Vec2f(1000, 1000);
manager.setBounds(bounds); manager.setBounds(bounds);
manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground, [] (auto) {}); manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground);
EXPECT_TRUE(manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, manager.takeChangedTiles();
[&] (const auto& ... v) { onChangedTile(v ...); })); EXPECT_TRUE(manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground));
EXPECT_THAT(mChangedTiles, ElementsAre( EXPECT_THAT(manager.takeChangedTiles(), ElementsAre(
std::pair(TilePosition(-1, -1), ChangeType::add), std::pair(TilePosition(-1, -1), ChangeType::add),
std::pair(TilePosition(-1, 0), ChangeType::add), std::pair(TilePosition(-1, 0), ChangeType::add),
std::pair(TilePosition(0, -1), ChangeType::update), std::pair(TilePosition(0, -1), ChangeType::update),
@ -127,15 +116,30 @@ namespace
)); ));
} }
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, update_object_for_not_changed_object_should_return_empty) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, update_object_for_not_changed_object_should_not_add_changed_tiles)
{
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.takeChangedTiles();
EXPECT_FALSE(manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground));
EXPECT_THAT(manager.takeChangedTiles(), IsEmpty());
}
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_object_should_return_add_changed_tiles)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
const btBoxShape boxShape(btVector3(20, 20, 100)); const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform); const CollisionShape shape(mInstance, boxShape, mObjectTransform);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); TileBounds bounds;
EXPECT_FALSE(manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, bounds.mMin = osg::Vec2f(182, 182);
[&] (const auto& ... v) { onChangedTile(v ...); })); bounds.mMax = osg::Vec2f(1000, 1000);
EXPECT_THAT(mChangedTiles, IsEmpty()); manager.setBounds(bounds);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
manager.takeChangedTiles();
manager.removeObject(ObjectId(&boxShape));
EXPECT_THAT(manager.takeChangedTiles(), ElementsAre(std::pair(TilePosition(0, 0), ChangeType::remove)));
} }
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_after_add_object_should_return_recast_mesh_for_each_used_tile) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_after_add_object_should_return_recast_mesh_for_each_used_tile)
@ -144,7 +148,7 @@ namespace
manager.setWorldspace("worldspace"); manager.setWorldspace("worldspace");
const btBoxShape boxShape(btVector3(20, 20, 100)); const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform); const CollisionShape shape(mInstance, boxShape, mObjectTransform);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr);
EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, 0)), 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, -1)), nullptr);
@ -157,7 +161,7 @@ namespace
manager.setWorldspace("worldspace"); manager.setWorldspace("worldspace");
const btBoxShape boxShape(btVector3(20, 20, 100)); const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform); const CollisionShape shape(mInstance, boxShape, mObjectTransform);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
EXPECT_EQ(manager.getMesh("worldspace", TilePosition(1, 0)), nullptr); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(1, 0)), nullptr);
} }
@ -174,13 +178,13 @@ namespace
const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0));
const CollisionShape shape(mInstance, boxShape, mObjectTransform); const CollisionShape shape(mInstance, boxShape, mObjectTransform);
manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground, [] (auto) {}); manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground);
EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr);
EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, 0)), 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, 0)), nullptr);
EXPECT_NE(manager.getMesh("worldspace", TilePosition(1, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(1, -1)), nullptr);
manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto, auto) {}); manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr);
EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, 0)), 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, -1)), nullptr);
@ -196,11 +200,11 @@ namespace
const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0));
const CollisionShape shape(mInstance, boxShape, mObjectTransform); const CollisionShape shape(mInstance, boxShape, mObjectTransform);
manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground, [] (auto) {}); manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground);
EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr);
EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr);
manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto, auto) {}); manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
EXPECT_EQ(manager.getMesh("worldspace", TilePosition(1, 0)), nullptr); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(1, 0)), nullptr);
EXPECT_EQ(manager.getMesh("worldspace", TilePosition(1, -1)), nullptr); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(1, -1)), nullptr);
} }
@ -211,7 +215,7 @@ namespace
manager.setWorldspace("worldspace"); manager.setWorldspace("worldspace");
const btBoxShape boxShape(btVector3(20, 20, 100)); const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform); const CollisionShape shape(mInstance, boxShape, mObjectTransform);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
manager.removeObject(ObjectId(&boxShape)); manager.removeObject(ObjectId(&boxShape));
EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr);
EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr);
@ -226,13 +230,13 @@ namespace
const btBoxShape boxShape(btVector3(20, 20, 100)); const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform); const CollisionShape shape(mInstance, boxShape, mObjectTransform);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr);
EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, 0)), 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, -1)), nullptr);
EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, 0)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, 0)), nullptr);
manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto, auto) {}); manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr); EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr);
EXPECT_NE(manager.getMesh("worldspace", TilePosition(-1, 0)), 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, -1)), nullptr);
@ -245,7 +249,7 @@ namespace
const auto initialRevision = manager.getRevision(); const auto initialRevision = manager.getRevision();
const btBoxShape boxShape(btVector3(20, 20, 100)); const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform); const CollisionShape shape(mInstance, boxShape, mObjectTransform);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
EXPECT_EQ(manager.getRevision(), initialRevision + 1); EXPECT_EQ(manager.getRevision(), initialRevision + 1);
} }
@ -254,9 +258,9 @@ namespace
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
const btBoxShape boxShape(btVector3(20, 20, 100)); const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform); const CollisionShape shape(mInstance, boxShape, mObjectTransform);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
const auto beforeAddRevision = manager.getRevision(); const auto beforeAddRevision = manager.getRevision();
EXPECT_FALSE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {})); EXPECT_FALSE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground));
EXPECT_EQ(manager.getRevision(), beforeAddRevision); EXPECT_EQ(manager.getRevision(), beforeAddRevision);
} }
@ -266,9 +270,9 @@ namespace
const btBoxShape boxShape(btVector3(20, 20, 100)); const btBoxShape boxShape(btVector3(20, 20, 100));
const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0));
const CollisionShape shape(mInstance, boxShape, mObjectTransform); const CollisionShape shape(mInstance, boxShape, mObjectTransform);
manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground, [] (auto) {}); manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground);
const auto beforeUpdateRevision = manager.getRevision(); const auto beforeUpdateRevision = manager.getRevision();
manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto, auto) {}); manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
EXPECT_EQ(manager.getRevision(), beforeUpdateRevision + 1); EXPECT_EQ(manager.getRevision(), beforeUpdateRevision + 1);
} }
@ -278,9 +282,9 @@ namespace
manager.setWorldspace("worldspace"); manager.setWorldspace("worldspace");
const btBoxShape boxShape(btVector3(20, 20, 100)); const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform); const CollisionShape shape(mInstance, boxShape, mObjectTransform);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
const auto beforeUpdateRevision = manager.getRevision(); const auto beforeUpdateRevision = manager.getRevision();
manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto, auto) {}); manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
EXPECT_EQ(manager.getRevision(), beforeUpdateRevision); EXPECT_EQ(manager.getRevision(), beforeUpdateRevision);
} }
@ -289,7 +293,7 @@ namespace
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
const btBoxShape boxShape(btVector3(20, 20, 100)); const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform); const CollisionShape shape(mInstance, boxShape, mObjectTransform);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
const auto beforeRemoveRevision = manager.getRevision(); const auto beforeRemoveRevision = manager.getRevision();
manager.removeObject(ObjectId(&boxShape)); manager.removeObject(ObjectId(&boxShape));
EXPECT_EQ(manager.getRevision(), beforeRemoveRevision + 1); EXPECT_EQ(manager.getRevision(), beforeRemoveRevision + 1);
@ -303,12 +307,17 @@ namespace
EXPECT_EQ(manager.getRevision(), beforeRemoveRevision); EXPECT_EQ(manager.getRevision(), beforeRemoveRevision);
} }
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_water_for_new_water_should_return_true) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_water_for_new_water_should_add_changed_tiles)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
const osg::Vec2i cellPosition(0, 0); const osg::Vec2i cellPosition(0, 0);
const int cellSize = 8192; const int cellSize = 8192;
EXPECT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f)); manager.addWater(cellPosition, cellSize, 0.0f);
const auto changedTiles = manager.takeChangedTiles();
EXPECT_EQ(changedTiles.begin()->first, TilePosition(-1, -1));
EXPECT_EQ(changedTiles.rbegin()->first, TilePosition(11, 11));
for (const auto& [k, v] : changedTiles)
EXPECT_EQ(v, ChangeType::add) << k;
} }
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_water_for_not_max_int_should_add_new_tiles) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_water_for_not_max_int_should_add_new_tiles)
@ -317,7 +326,7 @@ namespace
manager.setWorldspace("worldspace"); manager.setWorldspace("worldspace");
const osg::Vec2i cellPosition(0, 0); const osg::Vec2i cellPosition(0, 0);
const int cellSize = 8192; const int cellSize = 8192;
ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f)); manager.addWater(cellPosition, cellSize, 0.0f);
for (int x = -1; x < 12; ++x) for (int x = -1; x < 12; ++x)
for (int y = -1; y < 12; ++y) for (int y = -1; y < 12; ++y)
ASSERT_NE(manager.getMesh("worldspace", TilePosition(x, y)), nullptr); ASSERT_NE(manager.getMesh("worldspace", TilePosition(x, y)), nullptr);
@ -329,30 +338,35 @@ namespace
manager.setWorldspace("worldspace"); manager.setWorldspace("worldspace");
const btBoxShape boxShape(btVector3(20, 20, 100)); const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform); const CollisionShape shape(mInstance, boxShape, mObjectTransform);
ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {})); ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground));
const osg::Vec2i cellPosition(0, 0); const osg::Vec2i cellPosition(0, 0);
const int cellSize = std::numeric_limits<int>::max(); const int cellSize = std::numeric_limits<int>::max();
ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f)); manager.addWater(cellPosition, cellSize, 0.0f);
for (int x = -6; x < 6; ++x) for (int x = -6; x < 6; ++x)
for (int y = -6; y < 6; ++y) for (int y = -6; y < 6; ++y)
ASSERT_EQ(manager.getMesh("worldspace", TilePosition(x, y)) != nullptr, -1 <= x && x <= 0 && -1 <= y && y <= 0); ASSERT_EQ(manager.getMesh("worldspace", TilePosition(x, y)) != nullptr, -1 <= x && x <= 0 && -1 <= y && y <= 0);
} }
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_absent_cell_should_return_nullopt) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_absent_cell_should_not_add_changed_tiles)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
EXPECT_EQ(manager.removeWater(osg::Vec2i(0, 0)), std::nullopt); manager.removeWater(osg::Vec2i(0, 0));
EXPECT_THAT(manager.takeChangedTiles(), ElementsAre());
} }
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_existing_cell_should_return_removed_water) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_existing_cell_should_add_changed_tiles)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
const osg::Vec2i cellPosition(0, 0); const osg::Vec2i cellPosition(0, 0);
const int cellSize = 8192; const int cellSize = 8192;
ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f)); manager.addWater(cellPosition, cellSize, 0.0f);
const auto result = manager.removeWater(cellPosition); manager.takeChangedTiles();
ASSERT_TRUE(result.has_value()); manager.removeWater(cellPosition);
EXPECT_EQ(result->mCellSize, cellSize); const auto changedTiles = manager.takeChangedTiles();
EXPECT_EQ(changedTiles.begin()->first, TilePosition(-1, -1));
EXPECT_EQ(changedTiles.rbegin()->first, TilePosition(11, 11));
for (const auto& [k, v] : changedTiles)
EXPECT_EQ(v, ChangeType::remove) << k;
} }
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_existing_cell_should_remove_empty_tiles) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_existing_cell_should_remove_empty_tiles)
@ -361,8 +375,8 @@ namespace
manager.setWorldspace("worldspace"); manager.setWorldspace("worldspace");
const osg::Vec2i cellPosition(0, 0); const osg::Vec2i cellPosition(0, 0);
const int cellSize = 8192; const int cellSize = 8192;
ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f)); manager.addWater(cellPosition, cellSize, 0.0f);
ASSERT_TRUE(manager.removeWater(cellPosition)); manager.removeWater(cellPosition);
for (int x = -6; x < 6; ++x) for (int x = -6; x < 6; ++x)
for (int y = -6; y < 6; ++y) for (int y = -6; y < 6; ++y)
ASSERT_EQ(manager.getMesh("worldspace", TilePosition(x, y)), nullptr); ASSERT_EQ(manager.getMesh("worldspace", TilePosition(x, y)), nullptr);
@ -374,11 +388,11 @@ namespace
manager.setWorldspace("worldspace"); manager.setWorldspace("worldspace");
const btBoxShape boxShape(btVector3(20, 20, 100)); const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform); const CollisionShape shape(mInstance, boxShape, mObjectTransform);
ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {})); ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground));
const osg::Vec2i cellPosition(0, 0); const osg::Vec2i cellPosition(0, 0);
const int cellSize = 8192; const int cellSize = 8192;
ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f)); manager.addWater(cellPosition, cellSize, 0.0f);
ASSERT_TRUE(manager.removeWater(cellPosition)); manager.removeWater(cellPosition);
for (int x = -6; x < 6; ++x) for (int x = -6; x < 6; ++x)
for (int y = -6; y < 6; ++y) for (int y = -6; y < 6; ++y)
ASSERT_EQ(manager.getMesh("worldspace", TilePosition(x, y)) != nullptr, -1 <= x && x <= 0 && -1 <= y && y <= 0); ASSERT_EQ(manager.getMesh("worldspace", TilePosition(x, y)) != nullptr, -1 <= x && x <= 0 && -1 <= y && y <= 0);
@ -392,9 +406,9 @@ namespace
const int cellSize = 8192; const int cellSize = 8192;
const btBoxShape boxShape(btVector3(20, 20, 100)); const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform); const CollisionShape shape(mInstance, boxShape, mObjectTransform);
ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {})); ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground));
ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f)); manager.addWater(cellPosition, cellSize, 0.0f);
ASSERT_TRUE(manager.removeObject(ObjectId(&boxShape))); manager.removeObject(ObjectId(&boxShape));
for (int x = -1; x < 12; ++x) for (int x = -1; x < 12; ++x)
for (int y = -1; y < 12; ++y) for (int y = -1; y < 12; ++y)
ASSERT_NE(manager.getMesh("worldspace", TilePosition(x, y)), nullptr); ASSERT_NE(manager.getMesh("worldspace", TilePosition(x, y)), nullptr);
@ -406,14 +420,14 @@ namespace
manager.setWorldspace("worldspace"); manager.setWorldspace("worldspace");
const btBoxShape boxShape(btVector3(20, 20, 100)); const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(nullptr, boxShape, mObjectTransform); const CollisionShape shape(nullptr, boxShape, mObjectTransform);
ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {})); ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground));
manager.setWorldspace("other"); manager.setWorldspace("other");
for (int x = -1; x < 1; ++x) for (int x = -1; x < 1; ++x)
for (int y = -1; y < 1; ++y) for (int y = -1; y < 1; ++y)
ASSERT_EQ(manager.getMesh("other", TilePosition(x, y)), nullptr); ASSERT_EQ(manager.getMesh("other", TilePosition(x, y)), nullptr);
} }
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, set_bounds_should_return_changed_tiles) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, set_bounds_should_add_changed_tiles)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
const btBoxShape boxShape(btVector3(20, 20, 100)); const btBoxShape boxShape(btVector3(20, 20, 100));
@ -422,10 +436,12 @@ namespace
bounds.mMin = osg::Vec2f(182, 0); bounds.mMin = osg::Vec2f(182, 0);
bounds.mMax = osg::Vec2f(1000, 1000); bounds.mMax = osg::Vec2f(1000, 1000);
manager.setBounds(bounds); manager.setBounds(bounds);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
bounds.mMin = osg::Vec2f(-1000, -1000); bounds.mMin = osg::Vec2f(-1000, -1000);
bounds.mMax = osg::Vec2f(0, -182); bounds.mMax = osg::Vec2f(0, -182);
EXPECT_THAT(manager.setBounds(bounds), ElementsAre( manager.takeChangedTiles();
manager.setBounds(bounds);
EXPECT_THAT(manager.takeChangedTiles(), ElementsAre(
std::pair(TilePosition(-1, -1), ChangeType::add), std::pair(TilePosition(-1, -1), ChangeType::add),
std::pair(TilePosition(0, 0), ChangeType::remove) std::pair(TilePosition(0, 0), ChangeType::remove)
)); ));

@ -23,12 +23,12 @@ namespace DetourNavigator
return true; return true;
} }
std::optional<RemovedRecastMeshObject> CachedRecastMeshManager::removeObject(const ObjectId id) bool CachedRecastMeshManager::removeObject(const ObjectId id)
{ {
auto object = mImpl.removeObject(id); const bool result = mImpl.removeObject(id);
if (object) if (result)
mOutdatedCache = true; mOutdatedCache = true;
return object; return result;
} }
bool CachedRecastMeshManager::addWater(const osg::Vec2i& cellPosition, int cellSize, float level) bool CachedRecastMeshManager::addWater(const osg::Vec2i& cellPosition, int cellSize, float level)
@ -39,12 +39,12 @@ namespace DetourNavigator
return true; return true;
} }
std::optional<Water> CachedRecastMeshManager::removeWater(const osg::Vec2i& cellPosition) bool CachedRecastMeshManager::removeWater(const osg::Vec2i& cellPosition)
{ {
const auto water = mImpl.removeWater(cellPosition); const bool result = mImpl.removeWater(cellPosition);
if (water) if (result)
mOutdatedCache = true; mOutdatedCache = true;
return water; return result;
} }
bool CachedRecastMeshManager::addHeightfield(const osg::Vec2i& cellPosition, int cellSize, bool CachedRecastMeshManager::addHeightfield(const osg::Vec2i& cellPosition, int cellSize,
@ -56,12 +56,12 @@ namespace DetourNavigator
return true; return true;
} }
std::optional<SizedHeightfieldShape> CachedRecastMeshManager::removeHeightfield(const osg::Vec2i& cellPosition) bool CachedRecastMeshManager::removeHeightfield(const osg::Vec2i& cellPosition)
{ {
const auto heightfield = mImpl.removeHeightfield(cellPosition); const bool result = mImpl.removeHeightfield(cellPosition);
if (heightfield) if (result)
mOutdatedCache = true; mOutdatedCache = true;
return heightfield; return result;
} }
std::shared_ptr<RecastMesh> CachedRecastMeshManager::getMesh() std::shared_ptr<RecastMesh> CachedRecastMeshManager::getMesh()

@ -21,15 +21,15 @@ namespace DetourNavigator
bool updateObject(const ObjectId id, const btTransform& transform, const AreaType areaType); bool updateObject(const ObjectId id, const btTransform& transform, const AreaType areaType);
std::optional<RemovedRecastMeshObject> removeObject(const ObjectId id); bool removeObject(const ObjectId id);
bool addWater(const osg::Vec2i& cellPosition, int cellSize, float level); bool addWater(const osg::Vec2i& cellPosition, int cellSize, float level);
std::optional<Water> removeWater(const osg::Vec2i& cellPosition); bool removeWater(const osg::Vec2i& cellPosition);
bool addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape); bool addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape);
std::optional<SizedHeightfieldShape> removeHeightfield(const osg::Vec2i& cellPosition); bool removeHeightfield(const osg::Vec2i& cellPosition);
std::shared_ptr<RecastMesh> getMesh(); std::shared_ptr<RecastMesh> getMesh();

@ -10,6 +10,11 @@ namespace DetourNavigator
add = 2, add = 2,
update = 3, update = 3,
}; };
inline ChangeType addChangeType(const ChangeType current, const ChangeType add)
{
return current == add ? current : ChangeType::mixed;
}
} }
#endif #endif

@ -149,8 +149,7 @@ namespace DetourNavigator
void NavigatorImpl::update(const osg::Vec3f& playerPosition) void NavigatorImpl::update(const osg::Vec3f& playerPosition)
{ {
removeUnusedNavMeshes(); removeUnusedNavMeshes();
for (const auto& v : mAgents) mNavMeshManager.update(playerPosition);
mNavMeshManager.update(playerPosition, v.first);
} }
void NavigatorImpl::wait(Loading::Listener& listener, WaitConditionType waitConditionType) void NavigatorImpl::wait(Loading::Listener& listener, WaitConditionType waitConditionType)

@ -7,6 +7,7 @@
#include "settings.hpp" #include "settings.hpp"
#include "waitconditiontype.hpp" #include "waitconditiontype.hpp"
#include "settingsutils.hpp" #include "settingsutils.hpp"
#include "cachedrecastmeshmanager.hpp"
#include <components/debug/debuglog.hpp> #include <components/debug/debuglog.hpp>
#include <components/bullethelpers/heightfield.hpp> #include <components/bullethelpers/heightfield.hpp>
@ -20,13 +21,6 @@
namespace namespace
{ {
using DetourNavigator::ChangeType;
ChangeType addChangeType(const ChangeType current, const ChangeType add)
{
return current == add ? current : ChangeType::mixed;
}
/// Safely reset shared_ptr with definite underlying object destrutor call. /// Safely reset shared_ptr with definite underlying object destrutor call.
/// Assuming there is another thread holding copy of this shared_ptr or weak_ptr to this shared_ptr. /// Assuming there is another thread holding copy of this shared_ptr or weak_ptr to this shared_ptr.
template <class T> template <class T>
@ -78,75 +72,44 @@ namespace DetourNavigator
{ {
const TileBounds bounds = makeBounds(mSettings.mRecast, osg::Vec2f(playerPosition.x(), playerPosition.y()), const TileBounds bounds = makeBounds(mSettings.mRecast, osg::Vec2f(playerPosition.x(), playerPosition.y()),
mSettings.mMaxTilesNumber); mSettings.mMaxTilesNumber);
const auto changedTiles = mRecastMeshManager.setBounds(bounds); 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, bool NavMeshManager::addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform,
const AreaType areaType) const AreaType areaType)
{ {
return mRecastMeshManager.addObject(id, shape, transform, areaType, 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, bool NavMeshManager::updateObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform,
const AreaType areaType) const AreaType areaType)
{ {
return mRecastMeshManager.updateObject(id, shape, transform, areaType, return mRecastMeshManager.updateObject(id, shape, transform, areaType);
[&] (const TilePosition& tile, ChangeType changeType) { addChangedTile(tile, changeType); });
} }
void NavMeshManager::removeObject(const ObjectId id) void NavMeshManager::removeObject(const ObjectId id)
{ {
const auto object = mRecastMeshManager.removeObject(id); mRecastMeshManager.removeObject(id);
if (!object)
return;
addChangedTiles(object->mShape, object->mTransform, ChangeType::remove);
} }
void NavMeshManager::addWater(const osg::Vec2i& cellPosition, int cellSize, float level) void NavMeshManager::addWater(const osg::Vec2i& cellPosition, int cellSize, float level)
{ {
if (!mRecastMeshManager.addWater(cellPosition, cellSize, level)) mRecastMeshManager.addWater(cellPosition, cellSize, level);
return;
const btVector3 shift = Misc::Convert::toBullet(getWaterShift3d(cellPosition, cellSize, level));
addChangedTiles(cellSize, shift, ChangeType::add);
} }
void NavMeshManager::removeWater(const osg::Vec2i& cellPosition) void NavMeshManager::removeWater(const osg::Vec2i& cellPosition)
{ {
const auto water = mRecastMeshManager.removeWater(cellPosition); mRecastMeshManager.removeWater(cellPosition);
if (!water)
return;
const btVector3 shift = Misc::Convert::toBullet(getWaterShift3d(cellPosition, water->mCellSize, water->mLevel));
addChangedTiles(water->mCellSize, shift, ChangeType::remove);
} }
void NavMeshManager::addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape) void NavMeshManager::addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape)
{ {
if (!mRecastMeshManager.addHeightfield(cellPosition, cellSize, shape)) mRecastMeshManager.addHeightfield(cellPosition, cellSize, shape);
return;
const btVector3 shift = getHeightfieldShift(shape, cellPosition, cellSize);
addChangedTiles(cellSize, shift, ChangeType::add);
} }
void NavMeshManager::removeHeightfield(const osg::Vec2i& cellPosition) void NavMeshManager::removeHeightfield(const osg::Vec2i& cellPosition)
{ {
const auto heightfield = mRecastMeshManager.removeHeightfield(cellPosition); mRecastMeshManager.removeHeightfield(cellPosition);
if (!heightfield)
return;
const btVector3 shift = getHeightfieldShift(heightfield->mShape, cellPosition, heightfield->mCellSize);
addChangedTiles(heightfield->mCellSize, shift, ChangeType::remove);
} }
void NavMeshManager::addAgent(const AgentBounds& agentBounds) void NavMeshManager::addAgent(const AgentBounds& agentBounds)
@ -156,6 +119,7 @@ namespace DetourNavigator
return; return;
mCache.insert(std::make_pair(agentBounds, mCache.insert(std::make_pair(agentBounds,
std::make_shared<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), ++mGenerationCounter))); std::make_shared<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), ++mGenerationCounter)));
mPlayerTile.reset();
Log(Debug::Debug) << "cache add for agent=" << agentBounds; Log(Debug::Debug) << "cache add for agent=" << agentBounds;
} }
@ -167,9 +131,7 @@ namespace DetourNavigator
if (!resetIfUnique(it->second)) if (!resetIfUnique(it->second))
return false; return false;
mCache.erase(agentBounds); mCache.erase(agentBounds);
mChangedTiles.erase(agentBounds); mPlayerTile.reset();
mPlayerTile.erase(agentBounds);
mLastRecastMeshManagerRevision.erase(agentBounds);
return true; return true;
} }
@ -180,55 +142,50 @@ namespace DetourNavigator
const auto startTilePosition = getTilePosition(mSettings.mRecast, start); const auto startTilePosition = getTilePosition(mSettings.mRecast, start);
const auto endTilePosition = getTilePosition(mSettings.mRecast, end); const auto endTilePosition = getTilePosition(mSettings.mRecast, end);
addChangedTile(startTilePosition, ChangeType::add); mRecastMeshManager.addChangedTile(startTilePosition, ChangeType::add);
if (startTilePosition != endTilePosition) if (startTilePosition != endTilePosition)
addChangedTile(endTilePosition, ChangeType::add); mRecastMeshManager.addChangedTile(endTilePosition, ChangeType::add);
} }
void NavMeshManager::removeOffMeshConnections(const ObjectId id) void NavMeshManager::removeOffMeshConnections(const ObjectId id)
{ {
const auto changedTiles = mOffMeshConnectionsManager.remove(id); const auto changedTiles = mOffMeshConnectionsManager.remove(id);
for (const auto& tile : changedTiles) for (const auto& tile : changedTiles)
addChangedTile(tile, ChangeType::update); mRecastMeshManager.addChangedTile(tile, ChangeType::update);
} }
void NavMeshManager::update(const osg::Vec3f& playerPosition, const AgentBounds& agentBounds) void NavMeshManager::update(const osg::Vec3f& playerPosition)
{ {
const auto playerTile = getTilePosition(mSettings.mRecast, toNavMeshCoordinates(mSettings.mRecast, playerPosition)); const auto playerTile = getTilePosition(mSettings.mRecast,
auto& lastRevision = mLastRecastMeshManagerRevision[agentBounds]; toNavMeshCoordinates(mSettings.mRecast, playerPosition));
auto lastPlayerTile = mPlayerTile.find(agentBounds); if (mLastRecastMeshManagerRevision == mRecastMeshManager.getRevision()
if (lastRevision == mRecastMeshManager.getRevision() && lastPlayerTile != mPlayerTile.end() && mPlayerTile.has_value() && *mPlayerTile == playerTile)
&& lastPlayerTile->second == playerTile)
return; return;
lastRevision = mRecastMeshManager.getRevision(); mLastRecastMeshManagerRevision = mRecastMeshManager.getRevision();
if (lastPlayerTile == mPlayerTile.end()) mPlayerTile = playerTile;
lastPlayerTile = mPlayerTile.insert(std::make_pair(agentBounds, playerTile)).first; const auto changedTiles = mRecastMeshManager.takeChangedTiles();
else for (const auto& [agentBounds, cached] : mCache)
lastPlayerTile->second = playerTile; update(agentBounds, playerTile, cached, changedTiles);
}
void NavMeshManager::update(const AgentBounds& agentBounds, const TilePosition& playerTile,
const SharedNavMeshCacheItem& cached, const std::map<osg::Vec2i, ChangeType>& changedTiles)
{
std::map<TilePosition, ChangeType> tilesToPost; std::map<TilePosition, ChangeType> tilesToPost;
const auto cached = getCached(agentBounds);
if (!cached)
{
std::ostringstream stream;
stream << "Agent with half extents is not found: " << agentBounds;
throw InvalidArgument(stream.str());
}
const auto changedTiles = mChangedTiles.find(agentBounds);
{ {
const auto locked = cached->lockConst(); const auto locked = cached->lockConst();
const auto& navMesh = locked->getImpl(); const auto& navMesh = locked->getImpl();
if (changedTiles != mChangedTiles.end()) for (const auto& [tilePosition, changeType] : changedTiles)
{ {
for (const auto& tile : changedTiles->second) if (navMesh.getTileAt(tilePosition.x(), tilePosition.y(), 0))
if (navMesh.getTileAt(tile.first.x(), tile.first.y(), 0)) {
{ auto tileToPost = tilesToPost.find(tilePosition);
auto tileToPost = tilesToPost.find(tile.first); if (tileToPost == tilesToPost.end())
if (tileToPost == tilesToPost.end()) tilesToPost.emplace(tilePosition, changeType);
tilesToPost.insert(tile); else
else tileToPost->second = addChangeType(tileToPost->second, changeType);
tileToPost->second = addChangeType(tileToPost->second, tile.second); }
}
} }
const auto maxTiles = std::min(mSettings.mMaxTilesNumber, navMesh.getParams()->maxTiles); const auto maxTiles = std::min(mSettings.mMaxTilesNumber, navMesh.getParams()->maxTiles);
mRecastMeshManager.forEachTile([&] (const TilePosition& tile, CachedRecastMeshManager& recastMeshManager) mRecastMeshManager.forEachTile([&] (const TilePosition& tile, CachedRecastMeshManager& recastMeshManager)
@ -238,19 +195,16 @@ namespace DetourNavigator
const auto shouldAdd = shouldAddTile(tile, playerTile, maxTiles); const auto shouldAdd = shouldAddTile(tile, playerTile, maxTiles);
const auto presentInNavMesh = bool(navMesh.getTileAt(tile.x(), tile.y(), 0)); const auto presentInNavMesh = bool(navMesh.getTileAt(tile.x(), tile.y(), 0));
if (shouldAdd && !presentInNavMesh) if (shouldAdd && !presentInNavMesh)
tilesToPost.insert(std::make_pair(tile, locked->isEmptyTile(tile) ? ChangeType::update : ChangeType::add)); tilesToPost.emplace(tile, locked->isEmptyTile(tile) ? ChangeType::update : ChangeType::add);
else if (!shouldAdd && presentInNavMesh) else if (!shouldAdd && presentInNavMesh)
tilesToPost.insert(std::make_pair(tile, ChangeType::mixed)); tilesToPost.emplace(tile, ChangeType::mixed);
else else
recastMeshManager.reportNavMeshChange(recastMeshManager.getVersion(), Version {0, 0}); recastMeshManager.reportNavMeshChange(recastMeshManager.getVersion(), Version {0, 0});
}); });
} }
mAsyncNavMeshUpdater.post(agentBounds, cached, playerTile, mRecastMeshManager.getWorldspace(), tilesToPost); mAsyncNavMeshUpdater.post(agentBounds, cached, playerTile, mRecastMeshManager.getWorldspace(), tilesToPost);
if (changedTiles != mChangedTiles.end())
changedTiles->second.clear();
Log(Debug::Debug) << "Cache update posted for agent=" << agentBounds << Log(Debug::Debug) << "Cache update posted for agent=" << agentBounds <<
" playerTile=" << lastPlayerTile->second << " playerTile=" << playerTile << " recastMeshManagerRevision=" << mLastRecastMeshManagerRevision;
" recastMeshManagerRevision=" << lastRevision;
} }
void NavMeshManager::wait(Loading::Listener& listener, WaitConditionType waitConditionType) void NavMeshManager::wait(Loading::Listener& listener, WaitConditionType waitConditionType)
@ -286,37 +240,6 @@ namespace DetourNavigator
return result; return result;
} }
void NavMeshManager::addChangedTiles(const btCollisionShape& shape, const btTransform& transform,
const ChangeType changeType)
{
const auto bounds = mRecastMeshManager.getBounds();
getTilesPositions(makeTilesPositionsRange(shape, transform, bounds, mSettings.mRecast),
[&] (const TilePosition& v) { addChangedTile(v, changeType); });
}
void NavMeshManager::addChangedTiles(const int cellSize, const btVector3& shift,
const ChangeType changeType)
{
if (cellSize == std::numeric_limits<int>::max())
return;
getTilesPositions(makeTilesPositionsRange(cellSize, shift, mSettings.mRecast),
[&] (const TilePosition& v) { addChangedTile(v, changeType); });
}
void NavMeshManager::addChangedTile(const TilePosition& tilePosition, const ChangeType changeType)
{
for (const auto& cached : mCache)
{
auto& tiles = mChangedTiles[cached.first];
auto tile = tiles.find(tilePosition);
if (tile == tiles.end())
tiles.insert(std::make_pair(tilePosition, changeType));
else
tile->second = addChangeType(tile->second, changeType);
}
}
SharedNavMeshCacheItem NavMeshManager::getCached(const AgentBounds& agentBounds) const SharedNavMeshCacheItem NavMeshManager::getCached(const AgentBounds& agentBounds) const
{ {
const auto cached = mCache.find(agentBounds); const auto cached = mCache.find(agentBounds);

@ -50,7 +50,7 @@ namespace DetourNavigator
void removeOffMeshConnections(const ObjectId id); void removeOffMeshConnections(const ObjectId id);
void update(const osg::Vec3f& playerPosition, const AgentBounds& agentBounds); void update(const osg::Vec3f& playerPosition);
void wait(Loading::Listener& listener, WaitConditionType waitConditionType); void wait(Loading::Listener& listener, WaitConditionType waitConditionType);
@ -69,18 +69,14 @@ namespace DetourNavigator
OffMeshConnectionsManager mOffMeshConnectionsManager; OffMeshConnectionsManager mOffMeshConnectionsManager;
AsyncNavMeshUpdater mAsyncNavMeshUpdater; AsyncNavMeshUpdater mAsyncNavMeshUpdater;
std::map<AgentBounds, SharedNavMeshCacheItem> mCache; std::map<AgentBounds, SharedNavMeshCacheItem> mCache;
std::map<AgentBounds, std::map<TilePosition, ChangeType>> mChangedTiles;
std::size_t mGenerationCounter = 0; std::size_t mGenerationCounter = 0;
std::map<AgentBounds, TilePosition> mPlayerTile; std::optional<TilePosition> mPlayerTile;
std::map<AgentBounds, std::size_t> mLastRecastMeshManagerRevision; std::size_t mLastRecastMeshManagerRevision = 0;
void addChangedTiles(const btCollisionShape& shape, const btTransform& transform, const ChangeType changeType); inline SharedNavMeshCacheItem getCached(const AgentBounds& agentBounds) const;
void addChangedTiles(const int cellSize, const btVector3& shift, const ChangeType changeType); inline void update(const AgentBounds& agentBounds, const TilePosition& playerTile,
const SharedNavMeshCacheItem& cached, const std::map<osg::Vec2i, ChangeType>& changedTiles);
void addChangedTile(const TilePosition& tilePosition, const ChangeType changeType);
SharedNavMeshCacheItem getCached(const AgentBounds& agentBounds) const;
}; };
} }

@ -62,16 +62,15 @@ namespace DetourNavigator
return true; return true;
} }
std::optional<RemovedRecastMeshObject> RecastMeshManager::removeObject(const ObjectId id) bool RecastMeshManager::removeObject(const ObjectId id)
{ {
const std::lock_guard lock(mMutex); const std::lock_guard lock(mMutex);
const auto object = mObjects.find(id); const auto object = mObjects.find(id);
if (object == mObjects.end()) if (object == mObjects.end())
return std::nullopt; return false;
const RemovedRecastMeshObject result {object->second.getImpl().getShape(), object->second.getImpl().getTransform()};
mObjects.erase(object); mObjects.erase(object);
++mRevision; ++mRevision;
return result; return true;
} }
bool RecastMeshManager::addWater(const osg::Vec2i& cellPosition, int cellSize, float level) bool RecastMeshManager::addWater(const osg::Vec2i& cellPosition, int cellSize, float level)
@ -83,16 +82,15 @@ namespace DetourNavigator
return true; return true;
} }
std::optional<Water> RecastMeshManager::removeWater(const osg::Vec2i& cellPosition) bool RecastMeshManager::removeWater(const osg::Vec2i& cellPosition)
{ {
const std::lock_guard lock(mMutex); const std::lock_guard lock(mMutex);
const auto water = mWater.find(cellPosition); const auto water = mWater.find(cellPosition);
if (water == mWater.end()) if (water == mWater.end())
return std::nullopt; return false;
++mRevision; ++mRevision;
Water result = water->second;
mWater.erase(water); mWater.erase(water);
return result; return true;
} }
bool RecastMeshManager::addHeightfield(const osg::Vec2i& cellPosition, int cellSize, bool RecastMeshManager::addHeightfield(const osg::Vec2i& cellPosition, int cellSize,
@ -105,16 +103,15 @@ namespace DetourNavigator
return true; return true;
} }
std::optional<SizedHeightfieldShape> RecastMeshManager::removeHeightfield(const osg::Vec2i& cellPosition) bool RecastMeshManager::removeHeightfield(const osg::Vec2i& cellPosition)
{ {
const std::lock_guard lock(mMutex); const std::lock_guard lock(mMutex);
const auto it = mHeightfields.find(cellPosition); const auto it = mHeightfields.find(cellPosition);
if (it == mHeightfields.end()) if (it == mHeightfields.end())
return std::nullopt; return false;
++mRevision; ++mRevision;
auto result = std::make_optional(it->second);
mHeightfields.erase(it); mHeightfields.erase(it);
return result; return true;
} }
std::shared_ptr<RecastMesh> RecastMeshManager::getMesh() const std::shared_ptr<RecastMesh> RecastMeshManager::getMesh() const

@ -23,12 +23,6 @@ namespace DetourNavigator
struct Settings; struct Settings;
class RecastMesh; class RecastMesh;
struct RemovedRecastMeshObject
{
std::reference_wrapper<const btCollisionShape> mShape;
btTransform mTransform;
};
struct SizedHeightfieldShape struct SizedHeightfieldShape
{ {
int mCellSize; int mCellSize;
@ -45,15 +39,15 @@ namespace DetourNavigator
bool updateObject(const ObjectId id, const btTransform& transform, const AreaType areaType); bool updateObject(const ObjectId id, const btTransform& transform, const AreaType areaType);
std::optional<RemovedRecastMeshObject> removeObject(const ObjectId id); bool removeObject(const ObjectId id);
bool addWater(const osg::Vec2i& cellPosition, int cellSize, float level); bool addWater(const osg::Vec2i& cellPosition, int cellSize, float level);
std::optional<Water> removeWater(const osg::Vec2i& cellPosition); bool removeWater(const osg::Vec2i& cellPosition);
bool addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape); bool addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape);
std::optional<SizedHeightfieldShape> removeHeightfield(const osg::Vec2i& cellPosition); bool removeHeightfield(const osg::Vec2i& cellPosition);
std::shared_ptr<RecastMesh> getMesh() const; std::shared_ptr<RecastMesh> getMesh() const;

@ -2,6 +2,7 @@
#include "gettilespositions.hpp" #include "gettilespositions.hpp"
#include "settingsutils.hpp" #include "settingsutils.hpp"
#include "changetype.hpp" #include "changetype.hpp"
#include "cachedrecastmeshmanager.hpp"
#include <components/debug/debuglog.hpp> #include <components/debug/debuglog.hpp>
#include <components/misc/convert.hpp> #include <components/misc/convert.hpp>
@ -18,6 +19,13 @@ namespace DetourNavigator
osg::Vec2f(-std::numeric_limits<float>::max(), -std::numeric_limits<float>::max()), osg::Vec2f(-std::numeric_limits<float>::max(), -std::numeric_limits<float>::max()),
osg::Vec2f(std::numeric_limits<float>::max(), std::numeric_limits<float>::max()) osg::Vec2f(std::numeric_limits<float>::max(), std::numeric_limits<float>::max())
}; };
bool updateTile(const ObjectId id, const btTransform& transform, const AreaType areaType,
const TilePosition& tilePosition, std::map<TilePosition, std::shared_ptr<CachedRecastMeshManager>>& tiles)
{
const auto tile = tiles.find(tilePosition);
return tile != tiles.end() && tile->second->updateObject(id, transform, areaType);
}
} }
TileCachedRecastMeshManager::TileCachedRecastMeshManager(const RecastSettings& settings) TileCachedRecastMeshManager::TileCachedRecastMeshManager(const RecastSettings& settings)
@ -26,18 +34,12 @@ namespace DetourNavigator
, mRange(makeTilesPositionsRange(mBounds.mMin, mBounds.mMax, mSettings)) , mRange(makeTilesPositionsRange(mBounds.mMin, mBounds.mMax, mSettings))
{} {}
TileBounds TileCachedRecastMeshManager::getBounds() const void TileCachedRecastMeshManager::setBounds(const TileBounds& bounds)
{
return mBounds;
}
std::vector<std::pair<TilePosition, ChangeType>> TileCachedRecastMeshManager::setBounds(const TileBounds& bounds)
{ {
std::vector<std::pair<TilePosition, ChangeType>> changedTiles;
if (mBounds == bounds) if (mBounds == bounds)
return changedTiles; return;
bool changed = false;
const auto newRange = makeTilesPositionsRange(bounds.mMin, bounds.mMax, mSettings); const auto newRange = makeTilesPositionsRange(bounds.mMin, bounds.mMax, mSettings);
if (mBounds != infiniteTileBounds) if (mBounds != infiniteTileBounds)
@ -58,7 +60,10 @@ namespace DetourNavigator
return; return;
data.mTiles.erase(it); data.mTiles.erase(it);
if (removeTile(id, position, locked->mTiles)) if (removeTile(id, position, locked->mTiles))
changedTiles.emplace_back(position, ChangeType::remove); {
addChangedTile(position, ChangeType::remove);
changed = true;
}
}; };
getTilesPositions(getIntersection(mRange, objectRange), onOldTilePosition); getTilesPositions(getIntersection(mRange, objectRange), onOldTilePosition);
@ -69,23 +74,18 @@ namespace DetourNavigator
if (addTile(id, data.mShape, data.mTransform, data.mAreaType, position, locked->mTiles)) if (addTile(id, data.mShape, data.mTransform, data.mAreaType, position, locked->mTiles))
{ {
data.mTiles.insert(position); data.mTiles.insert(position);
changedTiles.emplace_back(position, ChangeType::add); addChangedTile(position, ChangeType::add);
} }
}; };
getTilesPositions(getIntersection(newRange, objectRange), onNewTilePosition); getTilesPositions(getIntersection(newRange, objectRange), onNewTilePosition);
} }
std::sort(changedTiles.begin(), changedTiles.end());
changedTiles.erase(std::unique(changedTiles.begin(), changedTiles.end()), changedTiles.end());
} }
if (!changedTiles.empty()) if (changed)
++mRevision; ++mRevision;
mBounds = bounds; mBounds = bounds;
mRange = newRange; mRange = newRange;
return changedTiles;
} }
std::string TileCachedRecastMeshManager::getWorldspace() const std::string TileCachedRecastMeshManager::getWorldspace() const
@ -102,47 +102,125 @@ namespace DetourNavigator
locked->mWorldspace = worldspace; locked->mWorldspace = worldspace;
} }
std::optional<RemovedRecastMeshObject> TileCachedRecastMeshManager::removeObject(const ObjectId id) bool TileCachedRecastMeshManager::addObject(const 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<TilePosition> tilesPositions;
if (range.mBegin != range.mEnd)
{
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);
}
});
}
mObjects.emplace_hint(it, id, ObjectData {shape, transform, areaType, std::move(tilesPositions)});
++mRevision;
return true;
}
bool TileCachedRecastMeshManager::updateObject(const ObjectId id, const CollisionShape& shape,
const btTransform& transform, AreaType areaType)
{
const auto object = mObjects.find(id);
if (object == mObjects.end())
return false;
auto& data = object->second;
bool changed = false;
std::set<TilePosition> newTiles;
{
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)
{
if (newTiles.find(tile) == newTiles.end() && removeTile(id, tile, locked->mTiles))
{
addChangedTile(tile, ChangeType::remove);
changed = true;
}
}
}
if (changed)
{
data.mTiles = std::move(newTiles);
++mRevision;
}
return changed;
}
void TileCachedRecastMeshManager::removeObject(const ObjectId id)
{ {
const auto object = mObjects.find(id); const auto object = mObjects.find(id);
if (object == mObjects.end()) if (object == mObjects.end())
return std::nullopt; return;
std::optional<RemovedRecastMeshObject> result; bool changed = false;
{ {
const auto locked = mWorldspaceTiles.lock(); const auto locked = mWorldspaceTiles.lock();
for (const auto& tilePosition : object->second.mTiles) for (const auto& tilePosition : object->second.mTiles)
{ {
const auto removed = removeTile(id, tilePosition, locked->mTiles); if (removeTile(id, tilePosition, locked->mTiles))
if (removed && !result) {
result = removed; addChangedTile(tilePosition, ChangeType::remove);
changed = true;
}
} }
} }
mObjects.erase(object); mObjects.erase(object);
if (result) if (changed)
++mRevision; ++mRevision;
return result;
} }
bool TileCachedRecastMeshManager::addWater(const osg::Vec2i& cellPosition, int cellSize, float level) void TileCachedRecastMeshManager::addWater(const osg::Vec2i& cellPosition, const int cellSize, const float level)
{ {
const auto it = mWaterTilesPositions.find(cellPosition); const auto it = mWaterTilesPositions.find(cellPosition);
if (it != mWaterTilesPositions.end()) if (it != mWaterTilesPositions.end())
return false; return;
std::vector<TilePosition>& tilesPositions = mWaterTilesPositions.emplace_hint( std::vector<TilePosition>& tilesPositions = mWaterTilesPositions.emplace_hint(
it, cellPosition, std::vector<TilePosition>())->second; it, cellPosition, std::vector<TilePosition>())->second;
bool result = false; bool changed = false;
if (cellSize == std::numeric_limits<int>::max()) if (cellSize == std::numeric_limits<int>::max())
{ {
const auto locked = mWorldspaceTiles.lock(); const auto locked = mWorldspaceTiles.lock();
for (auto& tile : locked->mTiles) for (auto& [tilePosition, data] : locked->mTiles)
{ {
if (tile.second->addWater(cellPosition, cellSize, level)) if (data->addWater(cellPosition, cellSize, level))
{ {
tilesPositions.push_back(tile.first); tilesPositions.push_back(tilePosition);
result = true; addChangedTile(tilePosition, ChangeType::add);
changed = true;
} }
} }
} }
@ -163,23 +241,22 @@ namespace DetourNavigator
if (tile->second->addWater(cellPosition, cellSize, level)) if (tile->second->addWater(cellPosition, cellSize, level))
{ {
tilesPositions.push_back(tilePosition); tilesPositions.push_back(tilePosition);
result = true; addChangedTile(tilePosition, ChangeType::add);
changed = true;
} }
}); });
} }
if (result) if (changed)
++mRevision; ++mRevision;
return result;
} }
std::optional<Water> TileCachedRecastMeshManager::removeWater(const osg::Vec2i& cellPosition) void TileCachedRecastMeshManager::removeWater(const osg::Vec2i& cellPosition)
{ {
const auto object = mWaterTilesPositions.find(cellPosition); const auto object = mWaterTilesPositions.find(cellPosition);
if (object == mWaterTilesPositions.end()) if (object == mWaterTilesPositions.end())
return std::nullopt; return;
std::optional<Water> result; bool changed = false;
{ {
const auto worldspaceTiles = mWorldspaceTiles.lock(); const auto worldspaceTiles = mWorldspaceTiles.lock();
for (const auto& tilePosition : object->second) for (const auto& tilePosition : object->second)
@ -187,34 +264,35 @@ namespace DetourNavigator
const auto tile = worldspaceTiles->mTiles.find(tilePosition); const auto tile = worldspaceTiles->mTiles.find(tilePosition);
if (tile == worldspaceTiles->mTiles.end()) if (tile == worldspaceTiles->mTiles.end())
continue; continue;
const auto tileResult = tile->second->removeWater(cellPosition); if (tile->second->removeWater(cellPosition))
{
addChangedTile(tilePosition, ChangeType::remove);
changed = true;
}
if (tile->second->isEmpty()) if (tile->second->isEmpty())
{ {
worldspaceTiles->mTiles.erase(tile); worldspaceTiles->mTiles.erase(tile);
++mTilesGeneration; ++mTilesGeneration;
} }
if (tileResult && !result)
result = tileResult;
} }
} }
mWaterTilesPositions.erase(object); mWaterTilesPositions.erase(object);
if (result) if (changed)
++mRevision; ++mRevision;
return result;
} }
bool TileCachedRecastMeshManager::addHeightfield(const osg::Vec2i& cellPosition, int cellSize, void TileCachedRecastMeshManager::addHeightfield(const osg::Vec2i& cellPosition, const int cellSize,
const HeightfieldShape& shape) const HeightfieldShape& shape)
{ {
const auto it = mHeightfieldTilesPositions.find(cellPosition); const auto it = mHeightfieldTilesPositions.find(cellPosition);
if (it != mHeightfieldTilesPositions.end()) if (it != mHeightfieldTilesPositions.end())
return false; return;
std::vector<TilePosition>& tilesPositions = mHeightfieldTilesPositions.emplace_hint( std::vector<TilePosition>& tilesPositions = mHeightfieldTilesPositions.emplace_hint(
it, cellPosition, std::vector<TilePosition>())->second; it, cellPosition, std::vector<TilePosition>())->second;
const btVector3 shift = getHeightfieldShift(shape, cellPosition, cellSize); const btVector3 shift = getHeightfieldShift(shape, cellPosition, cellSize);
bool result = false; bool changed = false;
{ {
const auto worldspaceTiles = mWorldspaceTiles.lock(); const auto worldspaceTiles = mWorldspaceTiles.lock();
@ -231,23 +309,22 @@ namespace DetourNavigator
if (tile->second->addHeightfield(cellPosition, cellSize, shape)) if (tile->second->addHeightfield(cellPosition, cellSize, shape))
{ {
tilesPositions.push_back(tilePosition); tilesPositions.push_back(tilePosition);
result = true; addChangedTile(tilePosition, ChangeType::add);
changed = true;
} }
}); });
} }
if (result) if (changed)
++mRevision; ++mRevision;
return result;
} }
std::optional<SizedHeightfieldShape> TileCachedRecastMeshManager::removeHeightfield(const osg::Vec2i& cellPosition) void TileCachedRecastMeshManager::removeHeightfield(const osg::Vec2i& cellPosition)
{ {
const auto object = mHeightfieldTilesPositions.find(cellPosition); const auto object = mHeightfieldTilesPositions.find(cellPosition);
if (object == mHeightfieldTilesPositions.end()) if (object == mHeightfieldTilesPositions.end())
return std::nullopt; return;
std::optional<SizedHeightfieldShape> result; bool changed = false;
{ {
const auto worldspaceTiles = mWorldspaceTiles.lock(); const auto worldspaceTiles = mWorldspaceTiles.lock();
for (const auto& tilePosition : object->second) for (const auto& tilePosition : object->second)
@ -255,20 +332,21 @@ namespace DetourNavigator
const auto tile = worldspaceTiles->mTiles.find(tilePosition); const auto tile = worldspaceTiles->mTiles.find(tilePosition);
if (tile == worldspaceTiles->mTiles.end()) if (tile == worldspaceTiles->mTiles.end())
continue; continue;
const auto tileResult = tile->second->removeHeightfield(cellPosition); if (tile->second->removeHeightfield(cellPosition))
{
addChangedTile(tilePosition, ChangeType::remove);
changed = true;
}
if (tile->second->isEmpty()) if (tile->second->isEmpty())
{ {
worldspaceTiles->mTiles.erase(tile); worldspaceTiles->mTiles.erase(tile);
++mTilesGeneration; ++mTilesGeneration;
} }
if (tileResult && !result)
result = tileResult;
} }
} }
mHeightfieldTilesPositions.erase(object); mHeightfieldTilesPositions.erase(object);
if (result) if (changed)
++mRevision; ++mRevision;
return result;
} }
std::shared_ptr<RecastMesh> TileCachedRecastMeshManager::getMesh(std::string_view worldspace, const TilePosition& tilePosition) const std::shared_ptr<RecastMesh> TileCachedRecastMeshManager::getMesh(std::string_view worldspace, const TilePosition& tilePosition) const
@ -292,11 +370,6 @@ namespace DetourNavigator
return nullptr; return nullptr;
} }
std::size_t TileCachedRecastMeshManager::getRevision() const
{
return mRevision;
}
void TileCachedRecastMeshManager::reportNavMeshChange(const TilePosition& tilePosition, Version recastMeshVersion, Version navMeshVersion) const void TileCachedRecastMeshManager::reportNavMeshChange(const TilePosition& tilePosition, Version recastMeshVersion, Version navMeshVersion) const
{ {
const auto locked = mWorldspaceTiles.lockConst(); const auto locked = mWorldspaceTiles.lockConst();
@ -306,6 +379,15 @@ namespace DetourNavigator
it->second->reportNavMeshChange(recastMeshVersion, navMeshVersion); it->second->reportNavMeshChange(recastMeshVersion, navMeshVersion);
} }
void TileCachedRecastMeshManager::addChangedTile(const TilePosition& tilePosition, ChangeType changeType)
{
auto tile = mChangedTiles.find(tilePosition);
if (tile == mChangedTiles.end())
mChangedTiles.emplace(tilePosition, changeType);
else
tile->second = addChangeType(tile->second, changeType);
}
bool TileCachedRecastMeshManager::addTile(const ObjectId id, const CollisionShape& shape, bool TileCachedRecastMeshManager::addTile(const ObjectId id, const CollisionShape& shape,
const btTransform& transform, const AreaType areaType, const TilePosition& tilePosition, const btTransform& transform, const AreaType areaType, const TilePosition& tilePosition,
TilesMap& tiles) TilesMap& tiles)
@ -320,26 +402,19 @@ namespace DetourNavigator
return tile->second->addObject(id, shape, transform, areaType); return tile->second->addObject(id, shape, transform, areaType);
} }
bool TileCachedRecastMeshManager::updateTile(const ObjectId id, const btTransform& transform, bool TileCachedRecastMeshManager::removeTile(const ObjectId id,
const AreaType areaType, const TilePosition& tilePosition, TilesMap& tiles)
{
const auto tile = tiles.find(tilePosition);
return tile != tiles.end() && tile->second->updateObject(id, transform, areaType);
}
std::optional<RemovedRecastMeshObject> TileCachedRecastMeshManager::removeTile(const ObjectId id,
const TilePosition& tilePosition, TilesMap& tiles) const TilePosition& tilePosition, TilesMap& tiles)
{ {
const auto tile = tiles.find(tilePosition); const auto tile = tiles.find(tilePosition);
if (tile == tiles.end()) if (tile == tiles.end())
return std::optional<RemovedRecastMeshObject>(); return false;
auto tileResult = tile->second->removeObject(id); const bool result = tile->second->removeObject(id);
if (tile->second->isEmpty()) if (tile->second->isEmpty())
{ {
tiles.erase(tile); tiles.erase(tile);
++mTilesGeneration; ++mTilesGeneration;
} }
return tileResult; return result;
} }
std::shared_ptr<CachedRecastMeshManager> TileCachedRecastMeshManager::getManager(std::string_view worldspace, std::shared_ptr<CachedRecastMeshManager> TileCachedRecastMeshManager::getManager(std::string_view worldspace,

@ -1,16 +1,17 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_TILECACHEDRECASTMESHMANAGER_H #ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_TILECACHEDRECASTMESHMANAGER_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_TILECACHEDRECASTMESHMANAGER_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_TILECACHEDRECASTMESHMANAGER_H
#include "cachedrecastmeshmanager.hpp"
#include "tileposition.hpp" #include "tileposition.hpp"
#include "gettilespositions.hpp" #include "gettilespositions.hpp"
#include "version.hpp" #include "version.hpp"
#include "heightfieldshape.hpp" #include "heightfieldshape.hpp"
#include "changetype.hpp" #include "changetype.hpp"
#include "objectid.hpp"
#include "areatype.hpp"
#include "recastmeshobject.hpp"
#include <components/misc/guarded.hpp> #include <components/misc/guarded.hpp>
#include <algorithm>
#include <map> #include <map>
#include <mutex> #include <mutex>
#include <vector> #include <vector>
@ -18,104 +19,33 @@
namespace DetourNavigator namespace DetourNavigator
{ {
class CachedRecastMeshManager;
class RecastMesh;
class TileCachedRecastMeshManager class TileCachedRecastMeshManager
{ {
public: public:
explicit TileCachedRecastMeshManager(const RecastSettings& settings); explicit TileCachedRecastMeshManager(const RecastSettings& settings);
TileBounds getBounds() const; void setBounds(const TileBounds& bounds);
std::vector<std::pair<TilePosition, ChangeType>> setBounds(const TileBounds& bounds);
std::string getWorldspace() const; std::string getWorldspace() const;
void setWorldspace(std::string_view worldspace); void setWorldspace(std::string_view worldspace);
template <class OnChangedTile> bool addObject(ObjectId id, const CollisionShape& shape, const btTransform& transform, AreaType areaType);
bool addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform,
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<TilePosition> tilesPositions;
if (range.mBegin != range.mEnd)
{
const auto locked = mWorldspaceTiles.lock();
getTilesPositions(range,
[&] (const TilePosition& tilePosition)
{
if (addTile(id, shape, transform, areaType, tilePosition, locked->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>(onChangedTile));
++mRevision;
return true;
}
template <class OnChangedTile> bool updateObject(ObjectId id, const CollisionShape& shape, const btTransform& transform, AreaType areaType);
bool updateObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform,
const AreaType areaType, OnChangedTile&& onChangedTile)
{
const auto object = mObjects.find(id);
if (object == mObjects.end())
return false;
auto& data = object->second;
bool changed = false;
std::set<TilePosition> newTiles;
{
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))
{
onChangedTile(tilePosition, ChangeType::update);
changed = true;
}
}
else if (addTile(id, shape, transform, areaType, tilePosition, locked->mTiles))
{
newTiles.insert(tilePosition);
onChangedTile(tilePosition, ChangeType::add);
changed = true;
}
};
getTilesPositions(range, onTilePosition);
for (const auto& tile : data.mTiles)
{
if (newTiles.find(tile) == newTiles.end() && removeTile(id, tile, locked->mTiles))
{
onChangedTile(tile, ChangeType::remove);
changed = true;
}
}
}
if (changed)
{
data.mTiles = std::move(newTiles);
++mRevision;
}
return changed;
}
std::optional<RemovedRecastMeshObject> removeObject(const ObjectId id); void removeObject(ObjectId id);
bool addWater(const osg::Vec2i& cellPosition, int cellSize, float level); void addWater(const osg::Vec2i& cellPosition, int cellSize, float level);
std::optional<Water> removeWater(const osg::Vec2i& cellPosition); void removeWater(const osg::Vec2i& cellPosition);
bool addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape); void addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape);
std::optional<SizedHeightfieldShape> removeHeightfield(const osg::Vec2i& cellPosition); void removeHeightfield(const osg::Vec2i& cellPosition);
std::shared_ptr<RecastMesh> getMesh(std::string_view worldspace, const TilePosition& tilePosition) const; std::shared_ptr<RecastMesh> getMesh(std::string_view worldspace, const TilePosition& tilePosition) const;
@ -131,10 +61,14 @@ namespace DetourNavigator
function(tilePosition, *recastMeshManager); function(tilePosition, *recastMeshManager);
} }
std::size_t getRevision() const; 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) const;
void addChangedTile(const TilePosition& tilePosition, ChangeType changeType);
std::map<osg::Vec2i, ChangeType> takeChangedTiles() { return std::move(mChangedTiles); }
private: private:
using TilesMap = std::map<TilePosition, std::shared_ptr<CachedRecastMeshManager>>; using TilesMap = std::map<TilePosition, std::shared_ptr<CachedRecastMeshManager>>;
@ -159,17 +93,14 @@ namespace DetourNavigator
std::unordered_map<ObjectId, ObjectData> mObjects; std::unordered_map<ObjectId, ObjectData> mObjects;
std::map<osg::Vec2i, std::vector<TilePosition>> mWaterTilesPositions; std::map<osg::Vec2i, std::vector<TilePosition>> mWaterTilesPositions;
std::map<osg::Vec2i, std::vector<TilePosition>> mHeightfieldTilesPositions; std::map<osg::Vec2i, std::vector<TilePosition>> mHeightfieldTilesPositions;
std::map<osg::Vec2i, ChangeType> mChangedTiles;
std::size_t mRevision = 0; std::size_t mRevision = 0;
std::size_t mTilesGeneration = 0; std::size_t mTilesGeneration = 0;
bool addTile(const ObjectId id, const CollisionShape& shape, const btTransform& transform, inline bool addTile(ObjectId id, const CollisionShape& shape, const btTransform& transform,
const AreaType areaType, const TilePosition& tilePosition, TilesMap& tiles); AreaType areaType, const TilePosition& tilePosition, TilesMap& tiles);
static bool updateTile(const ObjectId id, const btTransform& transform, const AreaType areaType,
const TilePosition& tilePosition, TilesMap& tiles);
std::optional<RemovedRecastMeshObject> removeTile(const ObjectId id, const TilePosition& tilePosition, inline bool removeTile(ObjectId id, const TilePosition& tilePosition, TilesMap& tiles);
TilesMap& tiles);
inline std::shared_ptr<CachedRecastMeshManager> getManager(std::string_view worldspace, inline std::shared_ptr<CachedRecastMeshManager> getManager(std::string_view worldspace,
const TilePosition& tilePosition) const; const TilePosition& tilePosition) const;

Loading…
Cancel
Save