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());
navMeshInput.mTileCachedRecastMeshManager.addObject(objectId, shape, transform,
DetourNavigator::AreaType_ground, [] (const auto&) {});
DetourNavigator::AreaType_ground);
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, [] (const auto&) {});
DetourNavigator::AreaType_null);
}
data.mObjects.emplace_back(std::move(object));

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

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

@ -5,6 +5,8 @@
#include <BulletCollision/CollisionShapes/btBoxShape.h>
#include <osg/io_utils>
#include <gtest/gtest.h>
#include <gmock/gmock.h>
@ -16,8 +18,6 @@ namespace
struct DetourNavigatorTileCachedRecastMeshManagerTest : Test
{
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 osg::ref_ptr<const Resource::BulletShape> mShape = new Resource::BulletShape;
const osg::ref_ptr<const Resource::BulletShapeInstance> mInstance = new Resource::BulletShapeInstance(mShape);
@ -29,16 +29,6 @@ namespace
mSettings.mRecastScaleFactor = 0.017647058823529415f;
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)
@ -66,7 +56,7 @@ 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, [] (auto) {}));
EXPECT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground));
}
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_for_existing_object_should_return_false)
@ -74,8 +64,8 @@ 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, [] (auto) {});
EXPECT_FALSE(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));
}
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_should_add_tiles)
@ -84,13 +74,13 @@ 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, [] (auto) {}));
ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground));
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)
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_should_return_add_changed_tiles)
{
TileCachedRecastMeshManager manager(mSettings);
const btBoxShape boxShape(btVector3(20, 20, 100));
@ -99,12 +89,11 @@ namespace
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)));
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
EXPECT_THAT(manager.takeChangedTiles(), ElementsAre(std::pair(TilePosition(0, 0), ChangeType::add)));
}
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);
const btBoxShape boxShape(btVector3(20, 20, 100));
@ -114,10 +103,10 @@ namespace
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(
manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground);
manager.takeChangedTiles();
EXPECT_TRUE(manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground));
EXPECT_THAT(manager.takeChangedTiles(), ElementsAre(
std::pair(TilePosition(-1, -1), ChangeType::add),
std::pair(TilePosition(-1, 0), ChangeType::add),
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);
const btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
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_THAT(mChangedTiles, IsEmpty());
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);
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)
@ -144,7 +148,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, [] (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, 0)), nullptr);
EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr);
@ -157,7 +161,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, [] (auto) {});
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
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 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, 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, 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, 0)), 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 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, 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, -1)), nullptr);
}
@ -211,7 +215,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, [] (auto) {});
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
manager.removeObject(ObjectId(&boxShape));
EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, -1)), nullptr);
EXPECT_EQ(manager.getMesh("worldspace", TilePosition(-1, 0)), nullptr);
@ -226,13 +230,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, [] (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, 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, 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, 0)), nullptr);
EXPECT_NE(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr);
@ -245,7 +249,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, [] (auto) {});
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
EXPECT_EQ(manager.getRevision(), initialRevision + 1);
}
@ -254,9 +258,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, [] (auto) {});
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
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);
}
@ -266,9 +270,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, [] (auto) {});
manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground);
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);
}
@ -278,9 +282,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, [] (auto) {});
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
const auto beforeUpdateRevision = manager.getRevision();
manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto, auto) {});
manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
EXPECT_EQ(manager.getRevision(), beforeUpdateRevision);
}
@ -289,7 +293,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, [] (auto) {});
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
const auto beforeRemoveRevision = manager.getRevision();
manager.removeObject(ObjectId(&boxShape));
EXPECT_EQ(manager.getRevision(), beforeRemoveRevision + 1);
@ -303,12 +307,17 @@ namespace
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);
const osg::Vec2i cellPosition(0, 0);
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)
@ -317,7 +326,7 @@ namespace
manager.setWorldspace("worldspace");
const osg::Vec2i cellPosition(0, 0);
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 y = -1; y < 12; ++y)
ASSERT_NE(manager.getMesh("worldspace", TilePosition(x, y)), nullptr);
@ -329,30 +338,35 @@ 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, [] (auto) {}));
ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground));
const osg::Vec2i cellPosition(0, 0);
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 y = -6; y < 6; ++y)
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);
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);
const osg::Vec2i cellPosition(0, 0);
const int cellSize = 8192;
ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f));
const auto result = manager.removeWater(cellPosition);
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result->mCellSize, cellSize);
manager.addWater(cellPosition, cellSize, 0.0f);
manager.takeChangedTiles();
manager.removeWater(cellPosition);
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)
@ -361,8 +375,8 @@ namespace
manager.setWorldspace("worldspace");
const osg::Vec2i cellPosition(0, 0);
const int cellSize = 8192;
ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f));
ASSERT_TRUE(manager.removeWater(cellPosition));
manager.addWater(cellPosition, cellSize, 0.0f);
manager.removeWater(cellPosition);
for (int x = -6; x < 6; ++x)
for (int y = -6; y < 6; ++y)
ASSERT_EQ(manager.getMesh("worldspace", TilePosition(x, y)), nullptr);
@ -374,11 +388,11 @@ 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, [] (auto) {}));
ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground));
const osg::Vec2i cellPosition(0, 0);
const int cellSize = 8192;
ASSERT_TRUE(manager.addWater(cellPosition, cellSize, 0.0f));
ASSERT_TRUE(manager.removeWater(cellPosition));
manager.addWater(cellPosition, cellSize, 0.0f);
manager.removeWater(cellPosition);
for (int x = -6; x < 6; ++x)
for (int y = -6; y < 6; ++y)
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 btBoxShape boxShape(btVector3(20, 20, 100));
const CollisionShape shape(mInstance, boxShape, mObjectTransform);
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)));
ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground));
manager.addWater(cellPosition, cellSize, 0.0f);
manager.removeObject(ObjectId(&boxShape));
for (int x = -1; x < 12; ++x)
for (int y = -1; y < 12; ++y)
ASSERT_NE(manager.getMesh("worldspace", TilePosition(x, y)), nullptr);
@ -406,14 +420,14 @@ 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, [] (auto) {}));
ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground));
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)
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, set_bounds_should_add_changed_tiles)
{
TileCachedRecastMeshManager manager(mSettings);
const btBoxShape boxShape(btVector3(20, 20, 100));
@ -422,10 +436,12 @@ namespace
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) {});
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground);
bounds.mMin = osg::Vec2f(-1000, -1000);
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(0, 0), ChangeType::remove)
));

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

@ -21,15 +21,15 @@ namespace DetourNavigator
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);
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);
std::optional<SizedHeightfieldShape> removeHeightfield(const osg::Vec2i& cellPosition);
bool removeHeightfield(const osg::Vec2i& cellPosition);
std::shared_ptr<RecastMesh> getMesh();

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

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

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

@ -50,7 +50,7 @@ namespace DetourNavigator
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);
@ -69,18 +69,14 @@ namespace DetourNavigator
OffMeshConnectionsManager mOffMeshConnectionsManager;
AsyncNavMeshUpdater mAsyncNavMeshUpdater;
std::map<AgentBounds, SharedNavMeshCacheItem> mCache;
std::map<AgentBounds, std::map<TilePosition, ChangeType>> mChangedTiles;
std::size_t mGenerationCounter = 0;
std::map<AgentBounds, TilePosition> mPlayerTile;
std::map<AgentBounds, std::size_t> mLastRecastMeshManagerRevision;
std::optional<TilePosition> mPlayerTile;
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);
void addChangedTile(const TilePosition& tilePosition, const ChangeType changeType);
SharedNavMeshCacheItem getCached(const AgentBounds& agentBounds) const;
inline void update(const AgentBounds& agentBounds, const TilePosition& playerTile,
const SharedNavMeshCacheItem& cached, const std::map<osg::Vec2i, ChangeType>& changedTiles);
};
}

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

@ -23,12 +23,6 @@ namespace DetourNavigator
struct Settings;
class RecastMesh;
struct RemovedRecastMeshObject
{
std::reference_wrapper<const btCollisionShape> mShape;
btTransform mTransform;
};
struct SizedHeightfieldShape
{
int mCellSize;
@ -45,15 +39,15 @@ namespace DetourNavigator
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);
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);
std::optional<SizedHeightfieldShape> removeHeightfield(const osg::Vec2i& cellPosition);
bool removeHeightfield(const osg::Vec2i& cellPosition);
std::shared_ptr<RecastMesh> getMesh() const;

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

@ -1,16 +1,17 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_TILECACHEDRECASTMESHMANAGER_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_TILECACHEDRECASTMESHMANAGER_H
#include "cachedrecastmeshmanager.hpp"
#include "tileposition.hpp"
#include "gettilespositions.hpp"
#include "version.hpp"
#include "heightfieldshape.hpp"
#include "changetype.hpp"
#include "objectid.hpp"
#include "areatype.hpp"
#include "recastmeshobject.hpp"
#include <components/misc/guarded.hpp>
#include <algorithm>
#include <map>
#include <mutex>
#include <vector>
@ -18,104 +19,33 @@
namespace DetourNavigator
{
class CachedRecastMeshManager;
class RecastMesh;
class TileCachedRecastMeshManager
{
public:
explicit TileCachedRecastMeshManager(const RecastSettings& settings);
TileBounds getBounds() const;
std::vector<std::pair<TilePosition, ChangeType>> setBounds(const TileBounds& bounds);
void setBounds(const TileBounds& bounds);
std::string getWorldspace() const;
void setWorldspace(std::string_view worldspace);
template <class OnChangedTile>
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;
}
bool addObject(ObjectId id, const CollisionShape& shape, const btTransform& transform, AreaType areaType);
template <class OnChangedTile>
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;
}
bool updateObject(ObjectId id, const CollisionShape& shape, const btTransform& transform, AreaType areaType);
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;
@ -131,10 +61,14 @@ namespace DetourNavigator
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 addChangedTile(const TilePosition& tilePosition, ChangeType changeType);
std::map<osg::Vec2i, ChangeType> takeChangedTiles() { return std::move(mChangedTiles); }
private:
using TilesMap = std::map<TilePosition, std::shared_ptr<CachedRecastMeshManager>>;
@ -159,17 +93,14 @@ namespace DetourNavigator
std::unordered_map<ObjectId, ObjectData> mObjects;
std::map<osg::Vec2i, std::vector<TilePosition>> mWaterTilesPositions;
std::map<osg::Vec2i, std::vector<TilePosition>> mHeightfieldTilesPositions;
std::map<osg::Vec2i, ChangeType> mChangedTiles;
std::size_t mRevision = 0;
std::size_t mTilesGeneration = 0;
bool addTile(const ObjectId id, const CollisionShape& shape, const btTransform& transform,
const AreaType areaType, const TilePosition& tilePosition, TilesMap& tiles);
static bool updateTile(const ObjectId id, const btTransform& transform, const AreaType areaType,
const TilePosition& tilePosition, TilesMap& tiles);
inline bool addTile(ObjectId id, const CollisionShape& shape, const btTransform& transform,
AreaType areaType, const TilePosition& tilePosition, TilesMap& tiles);
std::optional<RemovedRecastMeshObject> removeTile(const ObjectId id, const TilePosition& tilePosition,
TilesMap& tiles);
inline bool removeTile(ObjectId id, const TilePosition& tilePosition, TilesMap& tiles);
inline std::shared_ptr<CachedRecastMeshManager> getManager(std::string_view worldspace,
const TilePosition& tilePosition) const;

Loading…
Cancel
Save