Merge branch 'navigator_rtree' into 'master'

Optimize updating navmesh from the main thread primarily on cell loading

See merge request OpenMW/openmw!2382
crashfix_debugdraw
psi29a 2 years ago
commit 95f9f00bcc

@ -276,11 +276,13 @@ namespace NavMeshTool
{ {
it = navMeshInputs.emplace(cell.mCellId.mWorldspace, it = navMeshInputs.emplace(cell.mCellId.mWorldspace,
std::make_unique<WorldspaceNavMeshInput>(cell.mCellId.mWorldspace, settings.mRecast)).first; std::make_unique<WorldspaceNavMeshInput>(cell.mCellId.mWorldspace, settings.mRecast)).first;
it->second->mTileCachedRecastMeshManager.setWorldspace(cell.mCellId.mWorldspace); it->second->mTileCachedRecastMeshManager.setWorldspace(cell.mCellId.mWorldspace, nullptr);
} }
return *it->second; return *it->second;
} (); } ();
const TileCachedRecastMeshManager::UpdateGuard guard(navMeshInput.mTileCachedRecastMeshManager);
if (exterior) if (exterior)
{ {
const auto it = std::lower_bound(esmData.mLands.begin(), esmData.mLands.end(), cellPosition, LessByXY {}); const auto it = std::lower_bound(esmData.mLands.begin(), esmData.mLands.end(), cellPosition, LessByXY {});
@ -292,14 +294,14 @@ namespace NavMeshTool
mergeOrAssign(getAabb(cellPosition, minHeight, maxHeight), mergeOrAssign(getAabb(cellPosition, minHeight, maxHeight),
navMeshInput.mAabb, navMeshInput.mAabbInitialized); navMeshInput.mAabb, navMeshInput.mAabbInitialized);
navMeshInput.mTileCachedRecastMeshManager.addHeightfield(cellPosition, ESM::Land::REAL_SIZE, heightfieldShape); navMeshInput.mTileCachedRecastMeshManager.addHeightfield(cellPosition, ESM::Land::REAL_SIZE, heightfieldShape, &guard);
navMeshInput.mTileCachedRecastMeshManager.addWater(cellPosition, ESM::Land::REAL_SIZE, -1); navMeshInput.mTileCachedRecastMeshManager.addWater(cellPosition, ESM::Land::REAL_SIZE, -1, &guard);
} }
else else
{ {
if ((cell.mData.mFlags & ESM::Cell::HasWater) != 0) if ((cell.mData.mFlags & ESM::Cell::HasWater) != 0)
navMeshInput.mTileCachedRecastMeshManager.addWater(cellPosition, std::numeric_limits<int>::max(), cell.mWater); navMeshInput.mTileCachedRecastMeshManager.addWater(cellPosition, std::numeric_limits<int>::max(), cell.mWater, &guard);
} }
forEachObject(cell, esmData, vfs, bulletShapeManager, readers, forEachObject(cell, esmData, vfs, bulletShapeManager, readers,
@ -318,13 +320,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); DetourNavigator::AreaType_ground, &guard);
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); DetourNavigator::AreaType_null, &guard);
} }
data.mObjects.emplace_back(std::move(object)); data.mObjects.emplace_back(std::move(object));

@ -17,6 +17,7 @@
#include <components/detournavigator/agentbounds.hpp> #include <components/detournavigator/agentbounds.hpp>
#include <components/misc/convert.hpp> #include <components/misc/convert.hpp>
#include <components/detournavigator/heightfieldshape.hpp> #include <components/detournavigator/heightfieldshape.hpp>
#include <components/detournavigator/navigatorimpl.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
@ -135,7 +136,7 @@ namespace
} }
void addObject(const MWWorld::Ptr& ptr, const MWWorld::World& world, const MWPhysics::PhysicsSystem& physics, void addObject(const MWWorld::Ptr& ptr, const MWWorld::World& world, const MWPhysics::PhysicsSystem& physics,
DetourNavigator::Navigator& navigator) DetourNavigator::Navigator& navigator, const DetourNavigator::UpdateGuard* navigatorUpdateGuard = nullptr)
{ {
if (const auto object = physics.getObject(ptr)) if (const auto object = physics.getObject(ptr))
{ {
@ -173,7 +174,8 @@ namespace
navigator.addObject( navigator.addObject(
DetourNavigator::ObjectId(object), DetourNavigator::ObjectId(object),
DetourNavigator::DoorShapes(object->getShapeInstance(), objectTransform, connectionStart, connectionEnd), DetourNavigator::DoorShapes(object->getShapeInstance(), objectTransform, connectionStart, connectionEnd),
transform transform,
navigatorUpdateGuard
); );
} }
else if (object->getShapeInstance()->mVisualCollisionType == Resource::VisualCollisionType::None) else if (object->getShapeInstance()->mVisualCollisionType == Resource::VisualCollisionType::None)
@ -181,7 +183,8 @@ namespace
navigator.addObject( navigator.addObject(
DetourNavigator::ObjectId(object), DetourNavigator::ObjectId(object),
DetourNavigator::ObjectShapes(object->getShapeInstance(), objectTransform), DetourNavigator::ObjectShapes(object->getShapeInstance(), objectTransform),
object->getTransform() object->getTransform(),
navigatorUpdateGuard
); );
} }
} }
@ -320,7 +323,7 @@ namespace MWWorld
preloadCells(duration); preloadCells(duration);
} }
void Scene::unloadCell(CellStore* cell) void Scene::unloadCell(CellStore* cell, const DetourNavigator::UpdateGuard* navigatorUpdateGuard)
{ {
if (mActiveCells.find(cell) == mActiveCells.end()) if (mActiveCells.find(cell) == mActiveCells.end())
return; return;
@ -335,7 +338,7 @@ namespace MWWorld
if (const auto object = mPhysics->getObject(ptr)) if (const auto object = mPhysics->getObject(ptr))
{ {
if (object->getShapeInstance()->mVisualCollisionType == Resource::VisualCollisionType::None) if (object->getShapeInstance()->mVisualCollisionType == Resource::VisualCollisionType::None)
mNavigator.removeObject(DetourNavigator::ObjectId(object)); mNavigator.removeObject(DetourNavigator::ObjectId(object), navigatorUpdateGuard);
mPhysics->remove(ptr); mPhysics->remove(ptr);
ptr.mRef->mData.mPhysicsPostponed = false; ptr.mRef->mData.mPhysicsPostponed = false;
} }
@ -354,13 +357,13 @@ namespace MWWorld
if (cell->getCell()->isExterior()) if (cell->getCell()->isExterior())
{ {
if (mPhysics->getHeightField(cellX, cellY) != nullptr) if (mPhysics->getHeightField(cellX, cellY) != nullptr)
mNavigator.removeHeightfield(osg::Vec2i(cellX, cellY)); mNavigator.removeHeightfield(osg::Vec2i(cellX, cellY), navigatorUpdateGuard);
mPhysics->removeHeightField(cellX, cellY); mPhysics->removeHeightField(cellX, cellY);
} }
if (cell->getCell()->hasWater()) if (cell->getCell()->hasWater())
mNavigator.removeWater(osg::Vec2i(cellX, cellY)); mNavigator.removeWater(osg::Vec2i(cellX, cellY), navigatorUpdateGuard);
if (const auto pathgrid = mWorld.getStore().get<ESM::Pathgrid>().search(*cell->getCell())) if (const auto pathgrid = mWorld.getStore().get<ESM::Pathgrid>().search(*cell->getCell()))
mNavigator.removePathgrid(*pathgrid); mNavigator.removePathgrid(*pathgrid);
@ -379,7 +382,8 @@ namespace MWWorld
mRendering.notifyWorldSpaceChanged(); mRendering.notifyWorldSpaceChanged();
} }
void Scene::loadCell(CellStore *cell, Loading::Listener* loadingListener, bool respawn, const osg::Vec3f& position) void Scene::loadCell(CellStore *cell, Loading::Listener* loadingListener, bool respawn, const osg::Vec3f& position,
const DetourNavigator::UpdateGuard* navigatorUpdateGuard)
{ {
using DetourNavigator::HeightfieldShape; using DetourNavigator::HeightfieldShape;
@ -428,7 +432,7 @@ namespace MWWorld
return heights; return heights;
} }
} (); } ();
mNavigator.addHeightfield(cellPosition, ESM::Land::REAL_SIZE, shape); mNavigator.addHeightfield(cellPosition, ESM::Land::REAL_SIZE, shape, navigatorUpdateGuard);
} }
} }
@ -442,7 +446,7 @@ namespace MWWorld
if (respawn) if (respawn)
cell->respawn(); cell->respawn();
insertCell(*cell, loadingListener); insertCell(*cell, loadingListener, navigatorUpdateGuard);
mRendering.addCell(cell); mRendering.addCell(cell);
@ -458,18 +462,18 @@ namespace MWWorld
if (cell->getCell()->isExterior()) if (cell->getCell()->isExterior())
{ {
if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) if (const auto heightField = mPhysics->getHeightField(cellX, cellY))
mNavigator.addWater(osg::Vec2i(cellX, cellY), ESM::Land::REAL_SIZE, waterLevel); mNavigator.addWater(osg::Vec2i(cellX, cellY), ESM::Land::REAL_SIZE, waterLevel,
navigatorUpdateGuard);
} }
else else
{ {
mNavigator.addWater(osg::Vec2i(cellX, cellY), std::numeric_limits<int>::max(), waterLevel); mNavigator.addWater(osg::Vec2i(cellX, cellY), std::numeric_limits<int>::max(), waterLevel,
navigatorUpdateGuard);
} }
} }
else else
mPhysics->disableWater(); mPhysics->disableWater();
mNavigator.update(position);
if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx)) if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx))
mRendering.configureAmbient(cell->getCell()); mRendering.configureAmbient(cell->getCell());
@ -478,11 +482,13 @@ namespace MWWorld
void Scene::clear() void Scene::clear()
{ {
auto navigatorUpdateGuard = mNavigator.makeUpdateGuard();
for (auto iter = mActiveCells.begin(); iter!=mActiveCells.end(); ) for (auto iter = mActiveCells.begin(); iter!=mActiveCells.end(); )
{ {
auto* cell = *iter++; auto* cell = *iter++;
unloadCell (cell); unloadCell(cell, navigatorUpdateGuard.get());
} }
navigatorUpdateGuard.reset();
assert(mActiveCells.empty()); assert(mActiveCells.empty());
mCurrentCell = nullptr; mCurrentCell = nullptr;
@ -525,6 +531,8 @@ namespace MWWorld
void Scene::changeCellGrid (const osg::Vec3f &pos, int playerCellX, int playerCellY, bool changeEvent) void Scene::changeCellGrid (const osg::Vec3f &pos, int playerCellX, int playerCellY, bool changeEvent)
{ {
auto navigatorUpdateGuard = mNavigator.makeUpdateGuard();
for (auto iter = mActiveCells.begin(); iter != mActiveCells.end(); ) for (auto iter = mActiveCells.begin(); iter != mActiveCells.end(); )
{ {
auto* cell = *iter++; auto* cell = *iter++;
@ -533,14 +541,15 @@ namespace MWWorld
const auto dx = std::abs(playerCellX - cell->getCell()->getGridX()); const auto dx = std::abs(playerCellX - cell->getCell()->getGridX());
const auto dy = std::abs(playerCellY - cell->getCell()->getGridY()); const auto dy = std::abs(playerCellY - cell->getCell()->getGridY());
if (dx > mHalfGridSize || dy > mHalfGridSize) if (dx > mHalfGridSize || dy > mHalfGridSize)
unloadCell(cell); unloadCell(cell, navigatorUpdateGuard.get());
} }
else else
unloadCell (cell); unloadCell(cell, navigatorUpdateGuard.get());
} }
mNavigator.setWorldspace(mWorld.getExterior(playerCellX, playerCellY)->getCell()->mCellId.mWorldspace); mNavigator.setWorldspace(mWorld.getExterior(playerCellX, playerCellY)->getCell()->mCellId.mWorldspace,
mNavigator.updateBounds(pos); navigatorUpdateGuard.get());
mNavigator.updateBounds(pos, navigatorUpdateGuard.get());
mCurrentGridCenter = osg::Vec2i(playerCellX, playerCellY); mCurrentGridCenter = osg::Vec2i(playerCellX, playerCellY);
osg::Vec4i newGrid = gridCenterToBounds(mCurrentGridCenter); osg::Vec4i newGrid = gridCenterToBounds(mCurrentGridCenter);
@ -601,10 +610,14 @@ namespace MWWorld
if (!isCellInCollection(x, y, mActiveCells)) if (!isCellInCollection(x, y, mActiveCells))
{ {
CellStore *cell = mWorld.getExterior(x, y); CellStore *cell = mWorld.getExterior(x, y);
loadCell(cell, loadingListener, changeEvent, pos); loadCell(cell, loadingListener, changeEvent, pos, navigatorUpdateGuard.get());
} }
} }
mNavigator.update(pos, navigatorUpdateGuard.get());
navigatorUpdateGuard.reset();
CellStore* current = mWorld.getExterior(playerCellX, playerCellY); CellStore* current = mWorld.getExterior(playerCellX, playerCellY);
MWBase::Environment::get().getWindowManager()->changeCell(current); MWBase::Environment::get().getWindowManager()->changeCell(current);
@ -653,15 +666,21 @@ namespace MWWorld
MWWorld::Store<ESM::Cell>::iterator it = cells.extBegin(); MWWorld::Store<ESM::Cell>::iterator it = cells.extBegin();
int i = 1; int i = 1;
auto navigatorUpdateGuard = mNavigator.makeUpdateGuard();
for (; it != cells.extEnd(); ++it) for (; it != cells.extEnd(); ++it)
{ {
loadingListener->setLabel("Testing exterior cells ("+std::to_string(i)+"/"+std::to_string(cells.getExtSize())+")..."); loadingListener->setLabel("Testing exterior cells ("+std::to_string(i)+"/"+std::to_string(cells.getExtSize())+")...");
CellStore *cell = mWorld.getExterior(it->mData.mX, it->mData.mY); CellStore *cell = mWorld.getExterior(it->mData.mX, it->mData.mY);
mNavigator.setWorldspace(cell->getCell()->mCellId.mWorldspace); mNavigator.setWorldspace(cell->getCell()->mCellId.mWorldspace, navigatorUpdateGuard.get());
const osg::Vec3f position = osg::Vec3f(it->mData.mX + 0.5f, it->mData.mY + 0.5f, 0) * Constants::CellSizeInUnits; const osg::Vec3f position = osg::Vec3f(it->mData.mX + 0.5f, it->mData.mY + 0.5f, 0) * Constants::CellSizeInUnits;
mNavigator.updateBounds(position); mNavigator.updateBounds(position, navigatorUpdateGuard.get());
loadCell(cell, nullptr, false, position); loadCell(cell, nullptr, false, position, navigatorUpdateGuard.get());
mNavigator.update(position, navigatorUpdateGuard.get());
navigatorUpdateGuard.reset();
mNavigator.wait(DetourNavigator::WaitConditionType::requiredTilesPresent, nullptr);
navigatorUpdateGuard = mNavigator.makeUpdateGuard();
auto iter = mActiveCells.begin(); auto iter = mActiveCells.begin();
while (iter != mActiveCells.end()) while (iter != mActiveCells.end())
@ -669,7 +688,7 @@ namespace MWWorld
if (it->isExterior() && it->mData.mX == (*iter)->getCell()->getGridX() && if (it->isExterior() && it->mData.mX == (*iter)->getCell()->getGridX() &&
it->mData.mY == (*iter)->getCell()->getGridY()) it->mData.mY == (*iter)->getCell()->getGridY())
{ {
unloadCell(*iter); unloadCell(*iter, navigatorUpdateGuard.get());
break; break;
} }
@ -701,16 +720,22 @@ namespace MWWorld
int i = 1; int i = 1;
MWWorld::Store<ESM::Cell>::iterator it = cells.intBegin(); MWWorld::Store<ESM::Cell>::iterator it = cells.intBegin();
auto navigatorUpdateGuard = mNavigator.makeUpdateGuard();
for (; it != cells.intEnd(); ++it) for (; it != cells.intEnd(); ++it)
{ {
loadingListener->setLabel("Testing interior cells ("+std::to_string(i)+"/"+std::to_string(cells.getIntSize())+")..."); loadingListener->setLabel("Testing interior cells ("+std::to_string(i)+"/"+std::to_string(cells.getIntSize())+")...");
CellStore *cell = mWorld.getInterior(it->mName); CellStore *cell = mWorld.getInterior(it->mName);
mNavigator.setWorldspace(cell->getCell()->mCellId.mWorldspace); mNavigator.setWorldspace(cell->getCell()->mCellId.mWorldspace, navigatorUpdateGuard.get());
ESM::Position position; ESM::Position position;
mWorld.findInteriorPosition(it->mName, position); mWorld.findInteriorPosition(it->mName, position);
mNavigator.updateBounds(position.asVec3()); mNavigator.updateBounds(position.asVec3(), navigatorUpdateGuard.get());
loadCell(cell, nullptr, false, position.asVec3()); loadCell(cell, nullptr, false, position.asVec3(), navigatorUpdateGuard.get());
mNavigator.update(position.asVec3(), navigatorUpdateGuard.get());
navigatorUpdateGuard.reset();
mNavigator.wait(DetourNavigator::WaitConditionType::requiredTilesPresent, nullptr);
navigatorUpdateGuard = mNavigator.makeUpdateGuard();
auto iter = mActiveCells.begin(); auto iter = mActiveCells.begin();
while (iter != mActiveCells.end()) while (iter != mActiveCells.end())
@ -719,7 +744,7 @@ namespace MWWorld
if (it->mName == (*iter)->getCell()->mName) if (it->mName == (*iter)->getCell()->mName)
{ {
unloadCell(*iter); unloadCell(*iter, navigatorUpdateGuard.get());
break; break;
} }
@ -838,22 +863,26 @@ namespace MWWorld
Log(Debug::Info) << "Changing to interior"; Log(Debug::Info) << "Changing to interior";
auto navigatorUpdateGuard = mNavigator.makeUpdateGuard();
// unload // unload
for (auto iter = mActiveCells.begin(); iter!=mActiveCells.end(); ) for (auto iter = mActiveCells.begin(); iter!=mActiveCells.end(); )
{ {
auto* cellToUnload = *iter++; auto* cellToUnload = *iter++;
unloadCell(cellToUnload); unloadCell(cellToUnload, navigatorUpdateGuard.get());
} }
assert(mActiveCells.empty()); assert(mActiveCells.empty());
loadingListener->setProgressRange(cell->count()); loadingListener->setProgressRange(cell->count());
mNavigator.setWorldspace(cell->getCell()->mCellId.mWorldspace); mNavigator.setWorldspace(cell->getCell()->mCellId.mWorldspace, navigatorUpdateGuard.get());
mNavigator.updateBounds(position.asVec3()); mNavigator.updateBounds(position.asVec3(), navigatorUpdateGuard.get());
// Load cell. // Load cell.
mPagedRefs.clear(); mPagedRefs.clear();
loadCell(cell, loadingListener, changeEvent, position.asVec3()); loadCell(cell, loadingListener, changeEvent, position.asVec3(), navigatorUpdateGuard.get());
navigatorUpdateGuard.reset();
changePlayerCell(cell, position, adjustPlayerPos); changePlayerCell(cell, position, adjustPlayerPos);
@ -904,12 +933,13 @@ namespace MWWorld
mCellChanged = false; mCellChanged = false;
} }
void Scene::insertCell (CellStore &cell, Loading::Listener* loadingListener) void Scene::insertCell(CellStore &cell, Loading::Listener* loadingListener,
const DetourNavigator::UpdateGuard* navigatorUpdateGuard)
{ {
InsertVisitor insertVisitor(cell, loadingListener); InsertVisitor insertVisitor(cell, loadingListener);
cell.forEach (insertVisitor); cell.forEach (insertVisitor);
insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, mWorld, mPagedRefs, *mPhysics, mRendering); }); insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, mWorld, mPagedRefs, *mPhysics, mRendering); });
insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, mWorld, *mPhysics, mNavigator); }); insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, mWorld, *mPhysics, mNavigator, navigatorUpdateGuard); });
} }
void Scene::addObjectToScene (const Ptr& ptr) void Scene::addObjectToScene (const Ptr& ptr)
@ -938,7 +968,7 @@ namespace MWWorld
if (const auto object = mPhysics->getObject(ptr)) if (const auto object = mPhysics->getObject(ptr))
{ {
if (object->getShapeInstance()->mVisualCollisionType == Resource::VisualCollisionType::None) if (object->getShapeInstance()->mVisualCollisionType == Resource::VisualCollisionType::None)
mNavigator.removeObject(DetourNavigator::ObjectId(object)); mNavigator.removeObject(DetourNavigator::ObjectId(object), nullptr);
} }
else if (mPhysics->getActor(ptr)) else if (mPhysics->getActor(ptr))
{ {

@ -38,6 +38,7 @@ namespace Loading
namespace DetourNavigator namespace DetourNavigator
{ {
struct Navigator; struct Navigator;
class UpdateGuard;
} }
namespace MWRender namespace MWRender
@ -110,7 +111,9 @@ namespace MWWorld
std::optional<ChangeCellGridRequest> mChangeCellGridRequest; std::optional<ChangeCellGridRequest> mChangeCellGridRequest;
void insertCell(CellStore &cell, Loading::Listener* loadingListener); void insertCell(CellStore &cell, Loading::Listener* loadingListener,
const DetourNavigator::UpdateGuard* navigatorUpdateGuard);
osg::Vec2i mCurrentGridCenter; osg::Vec2i mCurrentGridCenter;
// Load and unload cells as necessary to create a cell grid with "X" and "Y" in the center // Load and unload cells as necessary to create a cell grid with "X" and "Y" in the center
@ -128,8 +131,9 @@ namespace MWWorld
osg::Vec4i gridCenterToBounds(const osg::Vec2i &centerCell) const; osg::Vec4i gridCenterToBounds(const osg::Vec2i &centerCell) const;
osg::Vec2i getNewGridCenter(const osg::Vec3f &pos, const osg::Vec2i *currentGridCenter = nullptr) const; osg::Vec2i getNewGridCenter(const osg::Vec3f &pos, const osg::Vec2i *currentGridCenter = nullptr) const;
void unloadCell(CellStore* cell); void unloadCell(CellStore* cell, const DetourNavigator::UpdateGuard* navigatorUpdateGuard);
void loadCell(CellStore *cell, Loading::Listener* loadingListener, bool respawn, const osg::Vec3f& position); void loadCell(CellStore *cell, Loading::Listener* loadingListener, bool respawn, const osg::Vec3f& position,
const DetourNavigator::UpdateGuard* navigatorUpdateGuard);
public: public:

@ -41,6 +41,7 @@
#include <components/detournavigator/settings.hpp> #include <components/detournavigator/settings.hpp>
#include <components/detournavigator/agentbounds.hpp> #include <components/detournavigator/agentbounds.hpp>
#include <components/detournavigator/stats.hpp> #include <components/detournavigator/stats.hpp>
#include <components/detournavigator/navigatorimpl.hpp>
#include <components/loadinglistener/loadinglistener.hpp> #include <components/loadinglistener/loadinglistener.hpp>
@ -1541,28 +1542,32 @@ namespace MWWorld
void World::updateNavigator() void World::updateNavigator()
{ {
auto navigatorUpdateGuard = mNavigator->makeUpdateGuard();
mPhysics->forEachAnimatedObject([&] (const auto& pair) mPhysics->forEachAnimatedObject([&] (const auto& pair)
{ {
const auto [object, changed] = pair; const auto [object, changed] = pair;
if (changed) if (changed)
updateNavigatorObject(*object); updateNavigatorObject(*object, navigatorUpdateGuard.get());
}); });
for (const auto& door : mDoorStates) for (const auto& door : mDoorStates)
if (const auto object = mPhysics->getObject(door.first)) if (const auto object = mPhysics->getObject(door.first))
updateNavigatorObject(*object); updateNavigatorObject(*object, navigatorUpdateGuard.get());
mNavigator->update(getPlayerPtr().getRefData().getPosition().asVec3()); mNavigator->update(getPlayerPtr().getRefData().getPosition().asVec3(), navigatorUpdateGuard.get());
} }
void World::updateNavigatorObject(const MWPhysics::Object& object) void World::updateNavigatorObject(const MWPhysics::Object& object,
const DetourNavigator::UpdateGuard* navigatorUpdateGuard)
{ {
if (object.getShapeInstance()->mVisualCollisionType != Resource::VisualCollisionType::None) if (object.getShapeInstance()->mVisualCollisionType != Resource::VisualCollisionType::None)
return; return;
const MWWorld::Ptr ptr = object.getPtr(); const MWWorld::Ptr ptr = object.getPtr();
const DetourNavigator::ObjectShapes shapes(object.getShapeInstance(), const DetourNavigator::ObjectShapes shapes(object.getShapeInstance(),
DetourNavigator::ObjectTransform {ptr.getRefData().getPosition(), ptr.getCellRef().getScale()}); DetourNavigator::ObjectTransform {ptr.getRefData().getPosition(), ptr.getCellRef().getScale()});
mNavigator->updateObject(DetourNavigator::ObjectId(&object), shapes, object.getTransform()); mNavigator->updateObject(DetourNavigator::ObjectId(&object), shapes, object.getTransform(),
navigatorUpdateGuard);
} }
const MWPhysics::RayCastingInterface* World::getRayCasting() const const MWPhysics::RayCastingInterface* World::getRayCasting() const
@ -1854,8 +1859,8 @@ namespace MWWorld
if (mWorldScene->hasCellLoaded()) if (mWorldScene->hasCellLoaded())
{ {
mNavigator->wait(*MWBase::Environment::get().getWindowManager()->getLoadingScreen(), mNavigator->wait(DetourNavigator::WaitConditionType::requiredTilesPresent,
DetourNavigator::WaitConditionType::requiredTilesPresent); MWBase::Environment::get().getWindowManager()->getLoadingScreen());
mWorldScene->resetCellLoaded(); mWorldScene->resetCellLoaded();
} }
} }

@ -166,7 +166,8 @@ namespace MWWorld
void updateNavigator(); void updateNavigator();
void updateNavigatorObject(const MWPhysics::Object& object); void updateNavigatorObject(const MWPhysics::Object& object,
const DetourNavigator::UpdateGuard* navigatorUpdateGuard = nullptr);
void ensureNeededRecords(); void ensureNeededRecords();

@ -25,7 +25,7 @@ namespace
void addHeightFieldPlane(TileCachedRecastMeshManager& recastMeshManager, const osg::Vec2i cellPosition = osg::Vec2i(0, 0)) void addHeightFieldPlane(TileCachedRecastMeshManager& recastMeshManager, const osg::Vec2i cellPosition = osg::Vec2i(0, 0))
{ {
const int cellSize = 8192; const int cellSize = 8192;
recastMeshManager.addHeightfield(cellPosition, cellSize, HeightfieldPlane {0}); recastMeshManager.addHeightfield(cellPosition, cellSize, HeightfieldPlane {0}, nullptr);
} }
void addObject(const btBoxShape& shape, TileCachedRecastMeshManager& recastMeshManager) void addObject(const btBoxShape& shape, TileCachedRecastMeshManager& recastMeshManager)
@ -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); recastMeshManager.addObject(id, collisionShape, btTransform::getIdentity(), AreaType_ground, nullptr);
} }
struct DetourNavigatorAsyncNavMeshUpdaterTest : Test struct DetourNavigatorAsyncNavMeshUpdaterTest : Test
@ -60,43 +60,43 @@ namespace
TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, for_all_jobs_done_when_empty_wait_should_terminate) TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, for_all_jobs_done_when_empty_wait_should_terminate)
{ {
AsyncNavMeshUpdater updater {mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr}; AsyncNavMeshUpdater updater {mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr};
updater.wait(mListener, WaitConditionType::allJobsDone); updater.wait(WaitConditionType::allJobsDone, &mListener);
} }
TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, for_required_tiles_present_when_empty_wait_should_terminate) TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, for_required_tiles_present_when_empty_wait_should_terminate)
{ {
AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr); AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr);
updater.wait(mListener, WaitConditionType::requiredTilesPresent); updater.wait(WaitConditionType::requiredTilesPresent, &mListener);
} }
TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, post_should_generate_navmesh_tile) TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, post_should_generate_navmesh_tile)
{ {
mRecastMeshManager.setWorldspace(mWorldspace); mRecastMeshManager.setWorldspace(mWorldspace, nullptr);
addHeightFieldPlane(mRecastMeshManager); addHeightFieldPlane(mRecastMeshManager);
AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr); AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr);
const auto navMeshCacheItem = std::make_shared<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), 1); const auto navMeshCacheItem = std::make_shared<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), 1);
const std::map<TilePosition, ChangeType> changedTiles {{TilePosition {0, 0}, ChangeType::add}}; const std::map<TilePosition, ChangeType> changedTiles {{TilePosition {0, 0}, ChangeType::add}};
updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
updater.wait(mListener, WaitConditionType::allJobsDone); updater.wait(WaitConditionType::allJobsDone, &mListener);
EXPECT_NE(navMeshCacheItem->lockConst()->getImpl().getTileRefAt(0, 0, 0), 0u); EXPECT_NE(navMeshCacheItem->lockConst()->getImpl().getTileRefAt(0, 0, 0), 0u);
} }
TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, repeated_post_should_lead_to_cache_hit) TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, repeated_post_should_lead_to_cache_hit)
{ {
mRecastMeshManager.setWorldspace(mWorldspace); mRecastMeshManager.setWorldspace(mWorldspace, nullptr);
addHeightFieldPlane(mRecastMeshManager); addHeightFieldPlane(mRecastMeshManager);
AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr); AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr);
const auto navMeshCacheItem = std::make_shared<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), 1); const auto navMeshCacheItem = std::make_shared<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), 1);
const std::map<TilePosition, ChangeType> changedTiles {{TilePosition {0, 0}, ChangeType::add}}; const std::map<TilePosition, ChangeType> changedTiles {{TilePosition {0, 0}, ChangeType::add}};
updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
updater.wait(mListener, WaitConditionType::allJobsDone); updater.wait(WaitConditionType::allJobsDone, &mListener);
{ {
const auto stats = updater.getStats(); const auto stats = updater.getStats();
ASSERT_EQ(stats.mCache.mGetCount, 1); ASSERT_EQ(stats.mCache.mGetCount, 1);
ASSERT_EQ(stats.mCache.mHitCount, 0); ASSERT_EQ(stats.mCache.mHitCount, 0);
} }
updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
updater.wait(mListener, WaitConditionType::allJobsDone); updater.wait(WaitConditionType::allJobsDone, &mListener);
{ {
const auto stats = updater.getStats(); const auto stats = updater.getStats();
EXPECT_EQ(stats.mCache.mGetCount, 2); EXPECT_EQ(stats.mCache.mGetCount, 2);
@ -106,20 +106,20 @@ namespace
TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, post_for_update_change_type_should_not_update_cache) TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, post_for_update_change_type_should_not_update_cache)
{ {
mRecastMeshManager.setWorldspace(mWorldspace); mRecastMeshManager.setWorldspace(mWorldspace, nullptr);
addHeightFieldPlane(mRecastMeshManager); addHeightFieldPlane(mRecastMeshManager);
AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr); AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr);
const auto navMeshCacheItem = std::make_shared<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), 1); const auto navMeshCacheItem = std::make_shared<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), 1);
const std::map<TilePosition, ChangeType> changedTiles {{TilePosition {0, 0}, ChangeType::update}}; const std::map<TilePosition, ChangeType> changedTiles {{TilePosition {0, 0}, ChangeType::update}};
updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
updater.wait(mListener, WaitConditionType::allJobsDone); updater.wait(WaitConditionType::allJobsDone, &mListener);
{ {
const auto stats = updater.getStats(); const auto stats = updater.getStats();
ASSERT_EQ(stats.mCache.mGetCount, 1); ASSERT_EQ(stats.mCache.mGetCount, 1);
ASSERT_EQ(stats.mCache.mHitCount, 0); ASSERT_EQ(stats.mCache.mHitCount, 0);
} }
updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
updater.wait(mListener, WaitConditionType::allJobsDone); updater.wait(WaitConditionType::allJobsDone, &mListener);
{ {
const auto stats = updater.getStats(); const auto stats = updater.getStats();
EXPECT_EQ(stats.mCache.mGetCount, 2); EXPECT_EQ(stats.mCache.mGetCount, 2);
@ -129,7 +129,7 @@ namespace
TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, post_should_write_generated_tile_to_db) TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, post_should_write_generated_tile_to_db)
{ {
mRecastMeshManager.setWorldspace(mWorldspace); mRecastMeshManager.setWorldspace(mWorldspace, nullptr);
addHeightFieldPlane(mRecastMeshManager); addHeightFieldPlane(mRecastMeshManager);
addObject(mBox, mRecastMeshManager); addObject(mBox, mRecastMeshManager);
auto db = std::make_unique<NavMeshDb>(":memory:", std::numeric_limits<std::uint64_t>::max()); auto db = std::make_unique<NavMeshDb>(":memory:", std::numeric_limits<std::uint64_t>::max());
@ -139,7 +139,7 @@ namespace
const TilePosition tilePosition {0, 0}; const TilePosition tilePosition {0, 0};
const std::map<TilePosition, ChangeType> changedTiles {{tilePosition, ChangeType::add}}; const std::map<TilePosition, ChangeType> changedTiles {{tilePosition, ChangeType::add}};
updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
updater.wait(mListener, WaitConditionType::allJobsDone); updater.wait(WaitConditionType::allJobsDone, &mListener);
updater.stop(); updater.stop();
const auto recastMesh = mRecastMeshManager.getMesh(mWorldspace, tilePosition); const auto recastMesh = mRecastMeshManager.getMesh(mWorldspace, tilePosition);
ASSERT_NE(recastMesh, nullptr); ASSERT_NE(recastMesh, nullptr);
@ -155,7 +155,7 @@ namespace
TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, post_when_writing_to_db_disabled_should_not_write_tiles) TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, post_when_writing_to_db_disabled_should_not_write_tiles)
{ {
mRecastMeshManager.setWorldspace(mWorldspace); mRecastMeshManager.setWorldspace(mWorldspace, nullptr);
addHeightFieldPlane(mRecastMeshManager); addHeightFieldPlane(mRecastMeshManager);
addObject(mBox, mRecastMeshManager); addObject(mBox, mRecastMeshManager);
auto db = std::make_unique<NavMeshDb>(":memory:", std::numeric_limits<std::uint64_t>::max()); auto db = std::make_unique<NavMeshDb>(":memory:", std::numeric_limits<std::uint64_t>::max());
@ -166,7 +166,7 @@ namespace
const TilePosition tilePosition {0, 0}; const TilePosition tilePosition {0, 0};
const std::map<TilePosition, ChangeType> changedTiles {{tilePosition, ChangeType::add}}; const std::map<TilePosition, ChangeType> changedTiles {{tilePosition, ChangeType::add}};
updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
updater.wait(mListener, WaitConditionType::allJobsDone); updater.wait(WaitConditionType::allJobsDone, &mListener);
updater.stop(); updater.stop();
const auto recastMesh = mRecastMeshManager.getMesh(mWorldspace, tilePosition); const auto recastMesh = mRecastMeshManager.getMesh(mWorldspace, tilePosition);
ASSERT_NE(recastMesh, nullptr); ASSERT_NE(recastMesh, nullptr);
@ -180,7 +180,7 @@ namespace
TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, post_when_writing_to_db_disabled_should_not_write_shapes) TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, post_when_writing_to_db_disabled_should_not_write_shapes)
{ {
mRecastMeshManager.setWorldspace(mWorldspace); mRecastMeshManager.setWorldspace(mWorldspace, nullptr);
addHeightFieldPlane(mRecastMeshManager); addHeightFieldPlane(mRecastMeshManager);
addObject(mBox, mRecastMeshManager); addObject(mBox, mRecastMeshManager);
auto db = std::make_unique<NavMeshDb>(":memory:", std::numeric_limits<std::uint64_t>::max()); auto db = std::make_unique<NavMeshDb>(":memory:", std::numeric_limits<std::uint64_t>::max());
@ -191,7 +191,7 @@ namespace
const TilePosition tilePosition {0, 0}; const TilePosition tilePosition {0, 0};
const std::map<TilePosition, ChangeType> changedTiles {{tilePosition, ChangeType::add}}; const std::map<TilePosition, ChangeType> changedTiles {{tilePosition, ChangeType::add}};
updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
updater.wait(mListener, WaitConditionType::allJobsDone); updater.wait(WaitConditionType::allJobsDone, &mListener);
updater.stop(); updater.stop();
const auto recastMesh = mRecastMeshManager.getMesh(mWorldspace, tilePosition); const auto recastMesh = mRecastMeshManager.getMesh(mWorldspace, tilePosition);
ASSERT_NE(recastMesh, nullptr); ASSERT_NE(recastMesh, nullptr);
@ -202,7 +202,7 @@ namespace
TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, post_should_read_from_db_on_cache_miss) TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, post_should_read_from_db_on_cache_miss)
{ {
mRecastMeshManager.setWorldspace(mWorldspace); mRecastMeshManager.setWorldspace(mWorldspace, nullptr);
addHeightFieldPlane(mRecastMeshManager); addHeightFieldPlane(mRecastMeshManager);
mSettings.mMaxNavMeshTilesCacheSize = 0; mSettings.mMaxNavMeshTilesCacheSize = 0;
AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager,
@ -210,7 +210,7 @@ namespace
const auto navMeshCacheItem = std::make_shared<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), 1); const auto navMeshCacheItem = std::make_shared<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), 1);
const std::map<TilePosition, ChangeType> changedTiles {{TilePosition {0, 0}, ChangeType::add}}; const std::map<TilePosition, ChangeType> changedTiles {{TilePosition {0, 0}, ChangeType::add}};
updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
updater.wait(mListener, WaitConditionType::allJobsDone); updater.wait(WaitConditionType::allJobsDone, &mListener);
{ {
const auto stats = updater.getStats(); const auto stats = updater.getStats();
ASSERT_EQ(stats.mCache.mGetCount, 1); ASSERT_EQ(stats.mCache.mGetCount, 1);
@ -220,7 +220,7 @@ namespace
ASSERT_EQ(stats.mDbGetTileHits, 0); ASSERT_EQ(stats.mDbGetTileHits, 0);
} }
updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
updater.wait(mListener, WaitConditionType::allJobsDone); updater.wait(WaitConditionType::allJobsDone, &mListener);
{ {
const auto stats = updater.getStats(); const auto stats = updater.getStats();
EXPECT_EQ(stats.mCache.mGetCount, 2); EXPECT_EQ(stats.mCache.mGetCount, 2);
@ -233,24 +233,24 @@ namespace
TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, on_changing_player_tile_post_should_remove_tiles_out_of_range) TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, on_changing_player_tile_post_should_remove_tiles_out_of_range)
{ {
mRecastMeshManager.setWorldspace(mWorldspace); mRecastMeshManager.setWorldspace(mWorldspace, nullptr);
addHeightFieldPlane(mRecastMeshManager); addHeightFieldPlane(mRecastMeshManager);
AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr); AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr);
const auto navMeshCacheItem = std::make_shared<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), 1); const auto navMeshCacheItem = std::make_shared<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), 1);
const std::map<TilePosition, ChangeType> changedTilesAdd {{TilePosition {0, 0}, ChangeType::add}}; const std::map<TilePosition, ChangeType> changedTilesAdd {{TilePosition {0, 0}, ChangeType::add}};
updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTilesAdd); updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTilesAdd);
updater.wait(mListener, WaitConditionType::allJobsDone); updater.wait(WaitConditionType::allJobsDone, &mListener);
ASSERT_NE(navMeshCacheItem->lockConst()->getImpl().getTileRefAt(0, 0, 0), 0u); ASSERT_NE(navMeshCacheItem->lockConst()->getImpl().getTileRefAt(0, 0, 0), 0u);
const std::map<TilePosition, ChangeType> changedTilesRemove {{TilePosition {0, 0}, ChangeType::remove}}; const std::map<TilePosition, ChangeType> changedTilesRemove {{TilePosition {0, 0}, ChangeType::remove}};
const TilePosition playerTile(100, 100); const TilePosition playerTile(100, 100);
updater.post(mAgentBounds, navMeshCacheItem, playerTile, mWorldspace, changedTilesRemove); updater.post(mAgentBounds, navMeshCacheItem, playerTile, mWorldspace, changedTilesRemove);
updater.wait(mListener, WaitConditionType::allJobsDone); updater.wait(WaitConditionType::allJobsDone, &mListener);
EXPECT_EQ(navMeshCacheItem->lockConst()->getImpl().getTileRefAt(0, 0, 0), 0u); EXPECT_EQ(navMeshCacheItem->lockConst()->getImpl().getTileRefAt(0, 0, 0), 0u);
} }
TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, should_stop_writing_to_db_when_size_limit_is_reached) TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, should_stop_writing_to_db_when_size_limit_is_reached)
{ {
mRecastMeshManager.setWorldspace(mWorldspace); mRecastMeshManager.setWorldspace(mWorldspace, nullptr);
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)
addHeightFieldPlane(mRecastMeshManager, osg::Vec2i(x, y)); addHeightFieldPlane(mRecastMeshManager, osg::Vec2i(x, y));
@ -264,7 +264,7 @@ namespace
for (int y = -5; y <= 5; ++y) for (int y = -5; y <= 5; ++y)
changedTiles.emplace(TilePosition {x, y}, ChangeType::add); changedTiles.emplace(TilePosition {x, y}, ChangeType::add);
updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles);
updater.wait(mListener, WaitConditionType::allJobsDone); updater.wait(WaitConditionType::allJobsDone, &mListener);
updater.stop(); updater.stop();
const std::set<TilePosition> present { const std::set<TilePosition> present {
TilePosition(-2, 0), TilePosition(-2, 0),

@ -155,9 +155,11 @@ namespace
const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1);
mNavigator->addAgent(mAgentBounds); mNavigator->addAgent(mAgentBounds);
mNavigator->addHeightfield(mCellPosition, cellSize, surface); auto updateGuard = mNavigator->makeUpdateGuard();
mNavigator->update(mPlayerPosition); mNavigator->addHeightfield(mCellPosition, cellSize, surface, updateGuard.get());
mNavigator->wait(mListener, WaitConditionType::requiredTilesPresent); mNavigator->update(mPlayerPosition, updateGuard.get());
updateGuard.reset();
mNavigator->wait(WaitConditionType::requiredTilesPresent, &mListener);
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
Status::Success); Status::Success);
@ -207,9 +209,9 @@ namespace
compound.shape().addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(0, 0, 0)), new btBoxShape(btVector3(20, 20, 100))); compound.shape().addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(0, 0, 0)), new btBoxShape(btVector3(20, 20, 100)));
mNavigator->addAgent(mAgentBounds); mNavigator->addAgent(mAgentBounds);
mNavigator->addHeightfield(mCellPosition, cellSize, surface); mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr);
mNavigator->update(mPlayerPosition); mNavigator->update(mPlayerPosition, nullptr);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
Status::Success); Status::Success);
@ -239,9 +241,12 @@ namespace
Vec3fEq(460, 56.66666412353515625, 1.99998295307159423828125) Vec3fEq(460, 56.66666412353515625, 1.99998295307159423828125)
)) << mPath; )) << mPath;
mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance(), mObjectTransform), mTransform); {
mNavigator->update(mPlayerPosition); auto updateGuard = mNavigator->makeUpdateGuard();
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance(), mObjectTransform), mTransform, updateGuard.get());
mNavigator->update(mPlayerPosition, updateGuard.get());
}
mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
mPath.clear(); mPath.clear();
mOut = std::back_inserter(mPath); mOut = std::back_inserter(mPath);
@ -291,10 +296,10 @@ namespace
compound.shape().addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(0, 0, 0)), new btBoxShape(btVector3(20, 20, 100))); compound.shape().addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(0, 0, 0)), new btBoxShape(btVector3(20, 20, 100)));
mNavigator->addAgent(mAgentBounds); mNavigator->addAgent(mAgentBounds);
mNavigator->addHeightfield(mCellPosition, cellSize, surface); mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr);
mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance(), mObjectTransform), mTransform); mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance(), mObjectTransform), mTransform, nullptr);
mNavigator->update(mPlayerPosition); mNavigator->update(mPlayerPosition, nullptr);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
Status::Success); Status::Success);
@ -327,9 +332,9 @@ namespace
compound.shape().updateChildTransform(0, btTransform(btMatrix3x3::getIdentity(), btVector3(1000, 0, 0))); compound.shape().updateChildTransform(0, btTransform(btMatrix3x3::getIdentity(), btVector3(1000, 0, 0)));
mNavigator->updateObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance(), mObjectTransform), mTransform); mNavigator->updateObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance(), mObjectTransform), mTransform, nullptr);
mNavigator->update(mPlayerPosition); mNavigator->update(mPlayerPosition, nullptr);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
mPath.clear(); mPath.clear();
mOut = std::back_inserter(mPath); mOut = std::back_inserter(mPath);
@ -385,10 +390,10 @@ namespace
heightfield2.shape().setLocalScaling(btVector3(128, 128, 1)); heightfield2.shape().setLocalScaling(btVector3(128, 128, 1));
mNavigator->addAgent(mAgentBounds); mNavigator->addAgent(mAgentBounds);
mNavigator->addObject(ObjectId(&heightfield1.shape()), ObjectShapes(heightfield1.instance(), mObjectTransform), mTransform); mNavigator->addObject(ObjectId(&heightfield1.shape()), ObjectShapes(heightfield1.instance(), mObjectTransform), mTransform, nullptr);
mNavigator->addObject(ObjectId(&heightfield2.shape()), ObjectShapes(heightfield2.instance(), mObjectTransform), mTransform); mNavigator->addObject(ObjectId(&heightfield2.shape()), ObjectShapes(heightfield2.instance(), mObjectTransform), mTransform, nullptr);
mNavigator->update(mPlayerPosition); mNavigator->update(mPlayerPosition, nullptr);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
Status::Success); Status::Success);
@ -442,15 +447,15 @@ namespace
const int cellSize2 = mHeightfieldTileSize * (surface2.mSize - 1); const int cellSize2 = mHeightfieldTileSize * (surface2.mSize - 1);
mNavigator->addAgent(mAgentBounds); mNavigator->addAgent(mAgentBounds);
mNavigator->addHeightfield(mCellPosition, cellSize1, surface1); mNavigator->addHeightfield(mCellPosition, cellSize1, surface1, nullptr);
mNavigator->update(mPlayerPosition); mNavigator->update(mPlayerPosition, nullptr);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
const Version version = mNavigator->getNavMesh(mAgentBounds)->lockConst()->getVersion(); const Version version = mNavigator->getNavMesh(mAgentBounds)->lockConst()->getVersion();
mNavigator->addHeightfield(mCellPosition, cellSize2, surface2); mNavigator->addHeightfield(mCellPosition, cellSize2, surface2, nullptr);
mNavigator->update(mPlayerPosition); mNavigator->update(mPlayerPosition, nullptr);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
EXPECT_EQ(mNavigator->getNavMesh(mAgentBounds)->lockConst()->getVersion(), version); EXPECT_EQ(mNavigator->getNavMesh(mAgentBounds)->lockConst()->getVersion(), version);
} }
@ -484,9 +489,9 @@ namespace
osg::ref_ptr<const Resource::BulletShapeInstance> instance(new Resource::BulletShapeInstance(bulletShape)); osg::ref_ptr<const Resource::BulletShapeInstance> instance(new Resource::BulletShapeInstance(bulletShape));
mNavigator->addAgent(mAgentBounds); mNavigator->addAgent(mAgentBounds);
mNavigator->addObject(ObjectId(instance->mCollisionShape.get()), ObjectShapes(instance, mObjectTransform), mTransform); mNavigator->addObject(ObjectId(instance->mCollisionShape.get()), ObjectShapes(instance, mObjectTransform), mTransform, nullptr);
mNavigator->update(mPlayerPosition); mNavigator->update(mPlayerPosition, nullptr);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
Status::Success); Status::Success);
@ -531,10 +536,10 @@ namespace
const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1);
mNavigator->addAgent(mAgentBounds); mNavigator->addAgent(mAgentBounds);
mNavigator->addWater(mCellPosition, cellSize, 300); mNavigator->addWater(mCellPosition, cellSize, 300, nullptr);
mNavigator->addHeightfield(mCellPosition, cellSize, surface); mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr);
mNavigator->update(mPlayerPosition); mNavigator->update(mPlayerPosition, nullptr);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
mStart.x() = 256; mStart.x() = 256;
mStart.z() = 300; mStart.z() = 300;
@ -579,10 +584,10 @@ namespace
const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1);
mNavigator->addAgent(mAgentBounds); mNavigator->addAgent(mAgentBounds);
mNavigator->addWater(mCellPosition, cellSize, -25); mNavigator->addWater(mCellPosition, cellSize, -25, nullptr);
mNavigator->addHeightfield(mCellPosition, cellSize, surface); mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr);
mNavigator->update(mPlayerPosition); mNavigator->update(mPlayerPosition, nullptr);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
mStart.x() = 256; mStart.x() = 256;
mEnd.x() = 256; mEnd.x() = 256;
@ -625,10 +630,10 @@ namespace
const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1);
mNavigator->addAgent(mAgentBounds); mNavigator->addAgent(mAgentBounds);
mNavigator->addHeightfield(mCellPosition, cellSize, surface); mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr);
mNavigator->addWater(mCellPosition, std::numeric_limits<int>::max(), -25); mNavigator->addWater(mCellPosition, std::numeric_limits<int>::max(), -25, nullptr);
mNavigator->update(mPlayerPosition); mNavigator->update(mPlayerPosition, nullptr);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
mStart.x() = 256; mStart.x() = 256;
mEnd.x() = 256; mEnd.x() = 256;
@ -671,10 +676,10 @@ namespace
const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1);
mNavigator->addAgent(mAgentBounds); mNavigator->addAgent(mAgentBounds);
mNavigator->addWater(mCellPosition, cellSize, -25); mNavigator->addWater(mCellPosition, cellSize, -25, nullptr);
mNavigator->addHeightfield(mCellPosition, cellSize, surface); mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr);
mNavigator->update(mPlayerPosition); mNavigator->update(mPlayerPosition, nullptr);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
mStart.x() = 256; mStart.x() = 256;
mEnd.x() = 256; mEnd.x() = 256;
@ -715,17 +720,17 @@ namespace
heightfield.shape().setLocalScaling(btVector3(128, 128, 1)); heightfield.shape().setLocalScaling(btVector3(128, 128, 1));
mNavigator->addAgent(mAgentBounds); mNavigator->addAgent(mAgentBounds);
mNavigator->addObject(ObjectId(&heightfield.shape()), ObjectShapes(heightfield.instance(), mObjectTransform), mTransform); mNavigator->addObject(ObjectId(&heightfield.shape()), ObjectShapes(heightfield.instance(), mObjectTransform), mTransform, nullptr);
mNavigator->update(mPlayerPosition); mNavigator->update(mPlayerPosition, nullptr);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
mNavigator->removeObject(ObjectId(&heightfield.shape())); mNavigator->removeObject(ObjectId(&heightfield.shape()), nullptr);
mNavigator->update(mPlayerPosition); mNavigator->update(mPlayerPosition, nullptr);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
mNavigator->addObject(ObjectId(&heightfield.shape()), ObjectShapes(heightfield.instance(), mObjectTransform), mTransform); mNavigator->addObject(ObjectId(&heightfield.shape()), ObjectShapes(heightfield.instance(), mObjectTransform), mTransform, nullptr);
mNavigator->update(mPlayerPosition); mNavigator->update(mPlayerPosition, nullptr);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
Status::Success); Status::Success);
@ -769,17 +774,17 @@ namespace
const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1);
mNavigator->addAgent(mAgentBounds); mNavigator->addAgent(mAgentBounds);
mNavigator->addHeightfield(mCellPosition, cellSize, surface); mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr);
mNavigator->update(mPlayerPosition); mNavigator->update(mPlayerPosition, nullptr);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
mNavigator->removeHeightfield(mCellPosition); mNavigator->removeHeightfield(mCellPosition, nullptr);
mNavigator->update(mPlayerPosition); mNavigator->update(mPlayerPosition, nullptr);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
mNavigator->addHeightfield(mCellPosition, cellSize, surface); mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr);
mNavigator->update(mPlayerPosition); mNavigator->update(mPlayerPosition, nullptr);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
Status::Success); Status::Success);
@ -824,9 +829,9 @@ namespace
const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1);
mNavigator->addAgent(mAgentBounds); mNavigator->addAgent(mAgentBounds);
mNavigator->addHeightfield(mCellPosition, cellSize, surface); mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr);
mNavigator->update(mPlayerPosition); mNavigator->update(mPlayerPosition, nullptr);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
Misc::Rng::init(42); Misc::Rng::init(42);
@ -862,12 +867,12 @@ namespace
mNavigator->addAgent(mAgentBounds); mNavigator->addAgent(mAgentBounds);
mNavigator->addHeightfield(mCellPosition, cellSize, surface); mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr);
for (std::size_t i = 0; i < boxes.size(); ++i) for (std::size_t i = 0; i < boxes.size(); ++i)
{ {
const btTransform transform(btMatrix3x3::getIdentity(), btVector3(shift.x() + i * 10, shift.y() + i * 10, i * 10)); const btTransform transform(btMatrix3x3::getIdentity(), btVector3(shift.x() + i * 10, shift.y() + i * 10, i * 10));
mNavigator->addObject(ObjectId(&boxes[i].shape()), ObjectShapes(boxes[i].instance(), mObjectTransform), transform); mNavigator->addObject(ObjectId(&boxes[i].shape()), ObjectShapes(boxes[i].instance(), mObjectTransform), transform, nullptr);
} }
std::this_thread::sleep_for(std::chrono::microseconds(1)); std::this_thread::sleep_for(std::chrono::microseconds(1));
@ -875,11 +880,11 @@ namespace
for (std::size_t i = 0; i < boxes.size(); ++i) for (std::size_t i = 0; i < boxes.size(); ++i)
{ {
const btTransform transform(btMatrix3x3::getIdentity(), btVector3(shift.x() + i * 10 + 1, shift.y() + i * 10 + 1, i * 10 + 1)); const btTransform transform(btMatrix3x3::getIdentity(), btVector3(shift.x() + i * 10 + 1, shift.y() + i * 10 + 1, i * 10 + 1));
mNavigator->updateObject(ObjectId(&boxes[i].shape()), ObjectShapes(boxes[i].instance(), mObjectTransform), transform); mNavigator->updateObject(ObjectId(&boxes[i].shape()), ObjectShapes(boxes[i].instance(), mObjectTransform), transform, nullptr);
} }
mNavigator->update(mPlayerPosition); mNavigator->update(mPlayerPosition, nullptr);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
Status::Success); Status::Success);
@ -921,27 +926,27 @@ namespace
for (std::size_t i = 0; i < shapes.size(); ++i) for (std::size_t i = 0; i < shapes.size(); ++i)
{ {
const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 32, i * 32, i * 32)); const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 32, i * 32, i * 32));
mNavigator->addObject(ObjectId(&shapes[i].shape()), ObjectShapes(shapes[i].instance(), mObjectTransform), transform); mNavigator->addObject(ObjectId(&shapes[i].shape()), ObjectShapes(shapes[i].instance(), mObjectTransform), transform, nullptr);
} }
mNavigator->update(mPlayerPosition); mNavigator->update(mPlayerPosition, nullptr);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
const auto start = std::chrono::steady_clock::now(); const auto start = std::chrono::steady_clock::now();
for (std::size_t i = 0; i < shapes.size(); ++i) for (std::size_t i = 0; i < shapes.size(); ++i)
{ {
const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 32 + 1, i * 32 + 1, i * 32 + 1)); const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 32 + 1, i * 32 + 1, i * 32 + 1));
mNavigator->updateObject(ObjectId(&shapes[i].shape()), ObjectShapes(shapes[i].instance(), mObjectTransform), transform); mNavigator->updateObject(ObjectId(&shapes[i].shape()), ObjectShapes(shapes[i].instance(), mObjectTransform), transform, nullptr);
} }
mNavigator->update(mPlayerPosition); mNavigator->update(mPlayerPosition, nullptr);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
for (std::size_t i = 0; i < shapes.size(); ++i) for (std::size_t i = 0; i < shapes.size(); ++i)
{ {
const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 32 + 2, i * 32 + 2, i * 32 + 2)); const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 32 + 2, i * 32 + 2, i * 32 + 2));
mNavigator->updateObject(ObjectId(&shapes[i].shape()), ObjectShapes(shapes[i].instance(), mObjectTransform), transform); mNavigator->updateObject(ObjectId(&shapes[i].shape()), ObjectShapes(shapes[i].instance(), mObjectTransform), transform, nullptr);
} }
mNavigator->update(mPlayerPosition); mNavigator->update(mPlayerPosition, nullptr);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
const auto duration = std::chrono::steady_clock::now() - start; const auto duration = std::chrono::steady_clock::now() - start;
@ -962,9 +967,9 @@ namespace
const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1);
mNavigator->addAgent(mAgentBounds); mNavigator->addAgent(mAgentBounds);
mNavigator->addHeightfield(mCellPosition, cellSize, surface); mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr);
mNavigator->update(mPlayerPosition); mNavigator->update(mPlayerPosition, nullptr);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
const osg::Vec3f start(57, 460, 1); const osg::Vec3f start(57, 460, 1);
const osg::Vec3f end(460, 57, 1); const osg::Vec3f end(460, 57, 1);
@ -991,14 +996,14 @@ namespace
CollisionShapeInstance borderBox(std::make_unique<btBoxShape>(btVector3(50, 50, 50))); CollisionShapeInstance borderBox(std::make_unique<btBoxShape>(btVector3(50, 50, 50)));
mNavigator->addAgent(mAgentBounds); mNavigator->addAgent(mAgentBounds);
mNavigator->addHeightfield(mCellPosition, cellSize, surface); mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr);
mNavigator->addObject(ObjectId(&oscillatingBox.shape()), ObjectShapes(oscillatingBox.instance(), mObjectTransform), mNavigator->addObject(ObjectId(&oscillatingBox.shape()), ObjectShapes(oscillatingBox.instance(), mObjectTransform),
btTransform(btMatrix3x3::getIdentity(), oscillatingBoxShapePosition)); btTransform(btMatrix3x3::getIdentity(), oscillatingBoxShapePosition), nullptr);
// add this box to make navmesh bound box independent from oscillatingBoxShape rotations // add this box to make navmesh bound box independent from oscillatingBoxShape rotations
mNavigator->addObject(ObjectId(&borderBox.shape()), ObjectShapes(borderBox.instance(), mObjectTransform), mNavigator->addObject(ObjectId(&borderBox.shape()), ObjectShapes(borderBox.instance(), mObjectTransform),
btTransform(btMatrix3x3::getIdentity(), oscillatingBoxShapePosition + btVector3(0, 0, 200))); btTransform(btMatrix3x3::getIdentity(), oscillatingBoxShapePosition + btVector3(0, 0, 200)), nullptr);
mNavigator->update(mPlayerPosition); mNavigator->update(mPlayerPosition, nullptr);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
const Version expectedVersion {1, 4}; const Version expectedVersion {1, 4};
@ -1010,9 +1015,9 @@ namespace
{ {
const btTransform transform(btQuaternion(btVector3(0, 0, 1), n * 2 * osg::PI / 10), const btTransform transform(btQuaternion(btVector3(0, 0, 1), n * 2 * osg::PI / 10),
oscillatingBoxShapePosition); oscillatingBoxShapePosition);
mNavigator->updateObject(ObjectId(&oscillatingBox.shape()), ObjectShapes(oscillatingBox.instance(), mObjectTransform), transform); mNavigator->updateObject(ObjectId(&oscillatingBox.shape()), ObjectShapes(oscillatingBox.instance(), mObjectTransform), transform, nullptr);
mNavigator->update(mPlayerPosition); mNavigator->update(mPlayerPosition, nullptr);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
} }
ASSERT_EQ(navMeshes.size(), 1); ASSERT_EQ(navMeshes.size(), 1);
@ -1025,9 +1030,9 @@ namespace
const int cellSize = mHeightfieldTileSize * 4; const int cellSize = mHeightfieldTileSize * 4;
mNavigator->addAgent(mAgentBounds); mNavigator->addAgent(mAgentBounds);
mNavigator->addHeightfield(mCellPosition, cellSize, plane); mNavigator->addHeightfield(mCellPosition, cellSize, plane, nullptr);
mNavigator->update(mPlayerPosition); mNavigator->update(mPlayerPosition, nullptr);
mNavigator->wait(mListener, WaitConditionType::requiredTilesPresent); mNavigator->wait(WaitConditionType::requiredTilesPresent, &mListener);
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
Status::Success); Status::Success);
@ -1075,10 +1080,10 @@ namespace
new btBoxShape(btVector3(200, 200, 1000))); new btBoxShape(btVector3(200, 200, 1000)));
mNavigator->addAgent(mAgentBounds); mNavigator->addAgent(mAgentBounds);
mNavigator->addHeightfield(mCellPosition, cellSize, surface); mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr);
mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance(), mObjectTransform), mTransform); mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance(), mObjectTransform), mTransform, nullptr);
mNavigator->update(mPlayerPosition); mNavigator->update(mPlayerPosition, nullptr);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut), EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mEndTolerance, mOut),
Status::PartialPath); Status::PartialPath);
@ -1114,10 +1119,10 @@ namespace
new btBoxShape(btVector3(100, 100, 1000))); new btBoxShape(btVector3(100, 100, 1000)));
mNavigator->addAgent(mAgentBounds); mNavigator->addAgent(mAgentBounds);
mNavigator->addHeightfield(mCellPosition, cellSize, surface); mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr);
mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance(), mObjectTransform), mTransform); mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance(), mObjectTransform), mTransform, nullptr);
mNavigator->update(mPlayerPosition); mNavigator->update(mPlayerPosition, nullptr);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
const float endTolerance = 1000.0f; const float endTolerance = 1000.0f;
@ -1154,15 +1159,15 @@ namespace
const float level2 = 2; const float level2 = 2;
mNavigator->addAgent(mAgentBounds); mNavigator->addAgent(mAgentBounds);
mNavigator->addWater(mCellPosition, cellSize1, level1); mNavigator->addWater(mCellPosition, cellSize1, level1, nullptr);
mNavigator->update(mPlayerPosition); mNavigator->update(mPlayerPosition, nullptr);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
const Version version = mNavigator->getNavMesh(mAgentBounds)->lockConst()->getVersion(); const Version version = mNavigator->getNavMesh(mAgentBounds)->lockConst()->getVersion();
mNavigator->addWater(mCellPosition, cellSize2, level2); mNavigator->addWater(mCellPosition, cellSize2, level2, nullptr);
mNavigator->update(mPlayerPosition); mNavigator->update(mPlayerPosition, nullptr);
mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->wait(WaitConditionType::allJobsDone, &mListener);
EXPECT_EQ(mNavigator->getNavMesh(mAgentBounds)->lockConst()->getVersion(), version); EXPECT_EQ(mNavigator->getNavMesh(mAgentBounds)->lockConst()->getVersion(), version);
} }

@ -43,20 +43,12 @@ namespace
EXPECT_EQ(manager.getRevision(), 0); EXPECT_EQ(manager.getRevision(), 0);
} }
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, for_each_tile_position_for_empty_should_call_none)
{
TileCachedRecastMeshManager manager(mSettings);
std::size_t calls = 0;
manager.forEachTile([&] (const TilePosition&, const CachedRecastMeshManager&) { ++calls; });
EXPECT_EQ(calls, 0);
}
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_for_new_object_should_return_true) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_for_new_object_should_return_true)
{ {
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)); EXPECT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, nullptr));
} }
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_for_existing_object_should_return_false) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_for_existing_object_should_return_false)
@ -64,17 +56,17 @@ 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); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, nullptr);
EXPECT_FALSE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); EXPECT_FALSE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, nullptr));
} }
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_should_add_tiles) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_should_add_tiles)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
manager.setWorldspace("worldspace"); manager.setWorldspace("worldspace", nullptr);
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)); ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, nullptr));
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);
@ -88,9 +80,9 @@ namespace
TileBounds bounds; TileBounds bounds;
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, nullptr);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, nullptr);
EXPECT_THAT(manager.takeChangedTiles(), ElementsAre(std::pair(TilePosition(0, 0), ChangeType::add))); EXPECT_THAT(manager.takeChangedTiles(nullptr), ElementsAre(std::pair(TilePosition(0, 0), ChangeType::add)));
} }
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, update_object_for_changed_object_should_add_changed_tiles) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, update_object_for_changed_object_should_add_changed_tiles)
@ -102,11 +94,11 @@ namespace
TileBounds bounds; TileBounds bounds;
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, nullptr);
manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground); manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground, nullptr);
manager.takeChangedTiles(); manager.takeChangedTiles(nullptr);
EXPECT_TRUE(manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); EXPECT_TRUE(manager.updateObject(ObjectId(&boxShape), btTransform::getIdentity(), AreaType::AreaType_ground, nullptr));
EXPECT_THAT(manager.takeChangedTiles(), ElementsAre( EXPECT_THAT(manager.takeChangedTiles(nullptr), 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),
@ -121,10 +113,10 @@ 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); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, nullptr);
manager.takeChangedTiles(); manager.takeChangedTiles(nullptr);
EXPECT_FALSE(manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); EXPECT_FALSE(manager.updateObject(ObjectId(&boxShape), btTransform::getIdentity(), AreaType::AreaType_ground, nullptr));
EXPECT_THAT(manager.takeChangedTiles(), IsEmpty()); EXPECT_THAT(manager.takeChangedTiles(nullptr), IsEmpty());
} }
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_object_should_return_add_changed_tiles) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_object_should_return_add_changed_tiles)
@ -135,20 +127,20 @@ namespace
TileBounds bounds; TileBounds bounds;
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, nullptr);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, nullptr);
manager.takeChangedTiles(); manager.takeChangedTiles(nullptr);
manager.removeObject(ObjectId(&boxShape)); manager.removeObject(ObjectId(&boxShape), nullptr);
EXPECT_THAT(manager.takeChangedTiles(), ElementsAre(std::pair(TilePosition(0, 0), ChangeType::remove))); EXPECT_THAT(manager.takeChangedTiles(nullptr), 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)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
manager.setWorldspace("worldspace"); manager.setWorldspace("worldspace", nullptr);
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); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, nullptr);
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);
@ -158,10 +150,10 @@ namespace
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_after_add_object_should_return_nullptr_for_unused_tile) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_after_add_object_should_return_nullptr_for_unused_tile)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
manager.setWorldspace("worldspace"); manager.setWorldspace("worldspace", nullptr);
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); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, nullptr);
EXPECT_EQ(manager.getMesh("worldspace", TilePosition(1, 0)), nullptr); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(1, 0)), nullptr);
} }
@ -171,20 +163,20 @@ namespace
TileBounds bounds; TileBounds bounds;
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, nullptr);
manager.setWorldspace("worldspace"); manager.setWorldspace("worldspace", nullptr);
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); manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground, 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);
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); manager.updateObject(ObjectId(&boxShape), btTransform::getIdentity(), AreaType::AreaType_ground, nullptr);
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);
@ -194,17 +186,17 @@ namespace
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_moved_object_should_return_nullptr_for_unused_tile) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_moved_object_should_return_nullptr_for_unused_tile)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
manager.setWorldspace("worldspace"); manager.setWorldspace("worldspace", nullptr);
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); manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground, nullptr);
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); manager.updateObject(ObjectId(&boxShape), btTransform::getIdentity(), AreaType::AreaType_ground, nullptr);
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);
} }
@ -212,11 +204,11 @@ namespace
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_removed_object_should_return_nullptr_for_all_previously_used_tiles) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_removed_object_should_return_nullptr_for_all_previously_used_tiles)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
manager.setWorldspace("worldspace"); manager.setWorldspace("worldspace", nullptr);
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); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, nullptr);
manager.removeObject(ObjectId(&boxShape)); manager.removeObject(ObjectId(&boxShape), nullptr);
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);
EXPECT_EQ(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr); EXPECT_EQ(manager.getMesh("worldspace", TilePosition(0, -1)), nullptr);
@ -226,17 +218,17 @@ namespace
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_not_changed_object_after_update_should_return_recast_mesh_for_same_tiles) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_not_changed_object_after_update_should_return_recast_mesh_for_same_tiles)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
manager.setWorldspace("worldspace"); manager.setWorldspace("worldspace", nullptr);
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); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, nullptr);
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); manager.updateObject(ObjectId(&boxShape), btTransform::getIdentity(), AreaType::AreaType_ground, nullptr);
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);
@ -249,7 +241,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); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, nullptr);
EXPECT_EQ(manager.getRevision(), initialRevision + 1); EXPECT_EQ(manager.getRevision(), initialRevision + 1);
} }
@ -258,9 +250,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); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, nullptr);
const auto beforeAddRevision = manager.getRevision(); const auto beforeAddRevision = manager.getRevision();
EXPECT_FALSE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); EXPECT_FALSE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, nullptr));
EXPECT_EQ(manager.getRevision(), beforeAddRevision); EXPECT_EQ(manager.getRevision(), beforeAddRevision);
} }
@ -270,21 +262,21 @@ 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); manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground, nullptr);
const auto beforeUpdateRevision = manager.getRevision(); const auto beforeUpdateRevision = manager.getRevision();
manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); manager.updateObject(ObjectId(&boxShape), btTransform::getIdentity(), AreaType::AreaType_ground, nullptr);
EXPECT_EQ(manager.getRevision(), beforeUpdateRevision + 1); EXPECT_EQ(manager.getRevision(), beforeUpdateRevision + 1);
} }
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_revision_after_update_not_changed_object_should_return_same_value) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_revision_after_update_not_changed_object_should_return_same_value)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
manager.setWorldspace("worldspace"); manager.setWorldspace("worldspace", nullptr);
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); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, nullptr);
const auto beforeUpdateRevision = manager.getRevision(); const auto beforeUpdateRevision = manager.getRevision();
manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); manager.updateObject(ObjectId(&boxShape), btTransform::getIdentity(), AreaType::AreaType_ground, nullptr);
EXPECT_EQ(manager.getRevision(), beforeUpdateRevision); EXPECT_EQ(manager.getRevision(), beforeUpdateRevision);
} }
@ -293,9 +285,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); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, nullptr);
const auto beforeRemoveRevision = manager.getRevision(); const auto beforeRemoveRevision = manager.getRevision();
manager.removeObject(ObjectId(&boxShape)); manager.removeObject(ObjectId(&boxShape), nullptr);
EXPECT_EQ(manager.getRevision(), beforeRemoveRevision + 1); EXPECT_EQ(manager.getRevision(), beforeRemoveRevision + 1);
} }
@ -303,7 +295,7 @@ namespace
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
const auto beforeRemoveRevision = manager.getRevision(); const auto beforeRemoveRevision = manager.getRevision();
manager.removeObject(ObjectId(&manager)); manager.removeObject(ObjectId(&manager), nullptr);
EXPECT_EQ(manager.getRevision(), beforeRemoveRevision); EXPECT_EQ(manager.getRevision(), beforeRemoveRevision);
} }
@ -312,8 +304,8 @@ namespace
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;
manager.addWater(cellPosition, cellSize, 0.0f); manager.addWater(cellPosition, cellSize, 0.0f, nullptr);
const auto changedTiles = manager.takeChangedTiles(); const auto changedTiles = manager.takeChangedTiles(nullptr);
EXPECT_EQ(changedTiles.begin()->first, TilePosition(-1, -1)); EXPECT_EQ(changedTiles.begin()->first, TilePosition(-1, -1));
EXPECT_EQ(changedTiles.rbegin()->first, TilePosition(11, 11)); EXPECT_EQ(changedTiles.rbegin()->first, TilePosition(11, 11));
for (const auto& [k, v] : changedTiles) for (const auto& [k, v] : changedTiles)
@ -323,10 +315,10 @@ namespace
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)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
manager.setWorldspace("worldspace"); manager.setWorldspace("worldspace", nullptr);
const osg::Vec2i cellPosition(0, 0); const osg::Vec2i cellPosition(0, 0);
const int cellSize = 8192; const int cellSize = 8192;
manager.addWater(cellPosition, cellSize, 0.0f); manager.addWater(cellPosition, cellSize, 0.0f, nullptr);
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);
@ -335,13 +327,13 @@ namespace
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_water_for_max_int_should_not_add_new_tiles) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_water_for_max_int_should_not_add_new_tiles)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
manager.setWorldspace("worldspace"); manager.setWorldspace("worldspace", nullptr);
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)); ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, nullptr));
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();
manager.addWater(cellPosition, cellSize, 0.0f); manager.addWater(cellPosition, cellSize, 0.0f, nullptr);
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);
@ -350,8 +342,8 @@ namespace
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_absent_cell_should_not_add_changed_tiles) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_absent_cell_should_not_add_changed_tiles)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
manager.removeWater(osg::Vec2i(0, 0)); manager.removeWater(osg::Vec2i(0, 0), nullptr);
EXPECT_THAT(manager.takeChangedTiles(), ElementsAre()); EXPECT_THAT(manager.takeChangedTiles(nullptr), ElementsAre());
} }
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_existing_cell_should_add_changed_tiles) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_existing_cell_should_add_changed_tiles)
@ -359,10 +351,10 @@ namespace
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;
manager.addWater(cellPosition, cellSize, 0.0f); manager.addWater(cellPosition, cellSize, 0.0f, nullptr);
manager.takeChangedTiles(); manager.takeChangedTiles(nullptr);
manager.removeWater(cellPosition); manager.removeWater(cellPosition, nullptr);
const auto changedTiles = manager.takeChangedTiles(); const auto changedTiles = manager.takeChangedTiles(nullptr);
EXPECT_EQ(changedTiles.begin()->first, TilePosition(-1, -1)); EXPECT_EQ(changedTiles.begin()->first, TilePosition(-1, -1));
EXPECT_EQ(changedTiles.rbegin()->first, TilePosition(11, 11)); EXPECT_EQ(changedTiles.rbegin()->first, TilePosition(11, 11));
for (const auto& [k, v] : changedTiles) for (const auto& [k, v] : changedTiles)
@ -372,11 +364,11 @@ namespace
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_existing_cell_should_remove_empty_tiles) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_existing_cell_should_remove_empty_tiles)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
manager.setWorldspace("worldspace"); manager.setWorldspace("worldspace", nullptr);
const osg::Vec2i cellPosition(0, 0); const osg::Vec2i cellPosition(0, 0);
const int cellSize = 8192; const int cellSize = 8192;
manager.addWater(cellPosition, cellSize, 0.0f); manager.addWater(cellPosition, cellSize, 0.0f, nullptr);
manager.removeWater(cellPosition); manager.removeWater(cellPosition, nullptr);
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);
@ -385,14 +377,14 @@ namespace
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_existing_cell_should_leave_not_empty_tiles) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_existing_cell_should_leave_not_empty_tiles)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
manager.setWorldspace("worldspace"); manager.setWorldspace("worldspace", nullptr);
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)); ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, nullptr));
const osg::Vec2i cellPosition(0, 0); const osg::Vec2i cellPosition(0, 0);
const int cellSize = 8192; const int cellSize = 8192;
manager.addWater(cellPosition, cellSize, 0.0f); manager.addWater(cellPosition, cellSize, 0.0f, nullptr);
manager.removeWater(cellPosition); manager.removeWater(cellPosition, nullptr);
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);
@ -401,14 +393,14 @@ namespace
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_object_should_not_remove_tile_with_water) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_object_should_not_remove_tile_with_water)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
manager.setWorldspace("worldspace"); manager.setWorldspace("worldspace", nullptr);
const osg::Vec2i cellPosition(0, 0); const osg::Vec2i cellPosition(0, 0);
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)); ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, nullptr));
manager.addWater(cellPosition, cellSize, 0.0f); manager.addWater(cellPosition, cellSize, 0.0f, nullptr);
manager.removeObject(ObjectId(&boxShape)); manager.removeObject(ObjectId(&boxShape), nullptr);
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);
@ -417,11 +409,11 @@ namespace
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, set_new_worldspace_should_remove_tiles) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, set_new_worldspace_should_remove_tiles)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
manager.setWorldspace("worldspace"); manager.setWorldspace("worldspace", nullptr);
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)); ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, nullptr));
manager.setWorldspace("other"); manager.setWorldspace("other", nullptr);
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);
@ -435,13 +427,13 @@ namespace
TileBounds bounds; TileBounds bounds;
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, nullptr);
manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, nullptr);
bounds.mMin = osg::Vec2f(-1000, -1000); bounds.mMin = osg::Vec2f(-1000, -1000);
bounds.mMax = osg::Vec2f(0, -182); bounds.mMax = osg::Vec2f(0, -182);
manager.takeChangedTiles(); manager.takeChangedTiles(nullptr);
manager.setBounds(bounds); manager.setBounds(bounds, nullptr);
EXPECT_THAT(manager.takeChangedTiles(), ElementsAre( EXPECT_THAT(manager.takeChangedTiles(nullptr), 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)
)); ));

@ -290,8 +290,6 @@ add_component_dir(detournavigator
makenavmesh makenavmesh
findsmoothpath findsmoothpath
recastmeshbuilder recastmeshbuilder
recastmeshmanager
cachedrecastmeshmanager
navmeshmanager navmeshmanager
navigatorimpl navigatorimpl
asyncnavmeshupdater asyncnavmeshupdater
@ -304,7 +302,6 @@ add_component_dir(detournavigator
findrandompointaroundcircle findrandompointaroundcircle
raycast raycast
navmeshtileview navmeshtileview
oscillatingrecastmeshobject
offmeshconnectionsmanager offmeshconnectionsmanager
preparednavmeshdata preparednavmeshdata
navmeshcacheitem navmeshcacheitem
@ -317,6 +314,7 @@ add_component_dir(detournavigator
gettilespositions gettilespositions
collisionshapetype collisionshapetype
stats stats
commulativeaabb
) )
add_component_dir(loadinglistener add_component_dir(loadinglistener

@ -227,7 +227,7 @@ namespace DetourNavigator
mDbWorker->updateJobs(playerTile, maxTiles); mDbWorker->updateJobs(playerTile, maxTiles);
} }
void AsyncNavMeshUpdater::wait(Loading::Listener& listener, WaitConditionType waitConditionType) void AsyncNavMeshUpdater::wait(WaitConditionType waitConditionType, Loading::Listener* listener)
{ {
switch (waitConditionType) switch (waitConditionType)
{ {
@ -254,7 +254,7 @@ namespace DetourNavigator
thread.join(); thread.join();
} }
void AsyncNavMeshUpdater::waitUntilJobsDoneForNotPresentTiles(Loading::Listener& listener) void AsyncNavMeshUpdater::waitUntilJobsDoneForNotPresentTiles(Loading::Listener* listener)
{ {
const int maxDistanceToPlayer = mSettings.get().mWaitUntilMinDistanceToPlayer; const int maxDistanceToPlayer = mSettings.get().mWaitUntilMinDistanceToPlayer;
if (maxDistanceToPlayer <= 0) if (maxDistanceToPlayer <= 0)
@ -276,23 +276,28 @@ namespace DetourNavigator
if (!isAbsentTileTooClose(playerPosition, maxDistanceToPlayer, mPushed, mPresentTiles, mProcessingTiles) if (!isAbsentTileTooClose(playerPosition, maxDistanceToPlayer, mPushed, mPresentTiles, mProcessingTiles)
|| mJobs.empty()) || mJobs.empty())
return; return;
Loading::ScopedLoad load(&listener); const Loading::ScopedLoad load(listener);
listener.setLabel("#{Navigation:BuildingNavigationMesh}"); if (listener != nullptr)
listener.setProgressRange(maxProgress); {
listener->setLabel("#{Navigation:BuildingNavigationMesh}");
listener->setProgressRange(maxProgress);
}
while (!mDone.wait_for(lock, std::chrono::milliseconds(20), isDone)) while (!mDone.wait_for(lock, std::chrono::milliseconds(20), isDone))
{ {
if (listener == nullptr)
continue;
if (maxProgress < jobsLeft) if (maxProgress < jobsLeft)
{ {
maxProgress = jobsLeft; maxProgress = jobsLeft;
listener.setProgressRange(maxProgress); listener->setProgressRange(maxProgress);
listener.setProgress(jobsDone); listener->setProgress(jobsDone);
} }
else if (jobsLeft < prevJobsLeft) else if (jobsLeft < prevJobsLeft)
{ {
const std::size_t newJobsDone = prevJobsLeft - jobsLeft; const std::size_t newJobsDone = prevJobsLeft - jobsLeft;
jobsDone += newJobsDone; jobsDone += newJobsDone;
prevJobsLeft = jobsLeft; prevJobsLeft = jobsLeft;
listener.increaseProgress(newJobsDone); listener->increaseProgress(newJobsDone);
} }
} }
} }

@ -151,7 +151,7 @@ namespace DetourNavigator
const TilePosition& playerTile, std::string_view worldspace, const TilePosition& playerTile, std::string_view worldspace,
const std::map<TilePosition, ChangeType>& changedTiles); const std::map<TilePosition, ChangeType>& changedTiles);
void wait(Loading::Listener& listener, WaitConditionType waitConditionType); void wait(WaitConditionType waitConditionType, Loading::Listener* listener);
void stop(); void stop();
@ -209,7 +209,7 @@ namespace DetourNavigator
void cleanupLastUpdates(); void cleanupLastUpdates();
inline void waitUntilJobsDoneForNotPresentTiles(Loading::Listener& listener); inline void waitUntilJobsDoneForNotPresentTiles(Loading::Listener* listener);
inline void waitUntilAllJobsDone(); inline void waitUntilAllJobsDone();
}; };

@ -1,100 +0,0 @@
#include "cachedrecastmeshmanager.hpp"
namespace DetourNavigator
{
CachedRecastMeshManager::CachedRecastMeshManager(const TileBounds& bounds, std::size_t generation)
: mImpl(bounds, generation)
{}
bool CachedRecastMeshManager::addObject(const ObjectId id, const CollisionShape& shape,
const btTransform& transform, const AreaType areaType)
{
if (!mImpl.addObject(id, shape, transform, areaType))
return false;
mOutdatedCache = true;
return true;
}
bool CachedRecastMeshManager::updateObject(const ObjectId id, const btTransform& transform, const AreaType areaType)
{
if (!mImpl.updateObject(id, transform, areaType))
return false;
mOutdatedCache = true;
return true;
}
bool CachedRecastMeshManager::removeObject(const ObjectId id)
{
const bool result = mImpl.removeObject(id);
if (result)
mOutdatedCache = true;
return result;
}
bool CachedRecastMeshManager::addWater(const osg::Vec2i& cellPosition, int cellSize, float level)
{
if (!mImpl.addWater(cellPosition, cellSize, level))
return false;
mOutdatedCache = true;
return true;
}
bool CachedRecastMeshManager::removeWater(const osg::Vec2i& cellPosition)
{
const bool result = mImpl.removeWater(cellPosition);
if (result)
mOutdatedCache = true;
return result;
}
bool CachedRecastMeshManager::addHeightfield(const osg::Vec2i& cellPosition, int cellSize,
const HeightfieldShape& shape)
{
if (!mImpl.addHeightfield(cellPosition, cellSize, shape))
return false;
mOutdatedCache = true;
return true;
}
bool CachedRecastMeshManager::removeHeightfield(const osg::Vec2i& cellPosition)
{
const bool result = mImpl.removeHeightfield(cellPosition);
if (result)
mOutdatedCache = true;
return result;
}
std::shared_ptr<RecastMesh> CachedRecastMeshManager::getMesh()
{
bool outdated = true;
if (!mOutdatedCache.compare_exchange_strong(outdated, false))
{
std::shared_ptr<RecastMesh> cached = getCachedMesh();
if (cached != nullptr)
return cached;
}
std::shared_ptr<RecastMesh> mesh = mImpl.getMesh();
*mCached.lock() = mesh;
return mesh;
}
std::shared_ptr<RecastMesh> CachedRecastMeshManager::getCachedMesh() const
{
return *mCached.lockConst();
}
std::shared_ptr<RecastMesh> CachedRecastMeshManager::getNewMesh() const
{
return mImpl.getMesh();
}
bool CachedRecastMeshManager::isEmpty() const
{
return mImpl.isEmpty();
}
void CachedRecastMeshManager::reportNavMeshChange(const Version& recastMeshVersion, const Version& navMeshVersion)
{
mImpl.reportNavMeshChange(recastMeshVersion, navMeshVersion);
}
}

@ -1,53 +0,0 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_CACHEDRECASTMESHMANAGER_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_CACHEDRECASTMESHMANAGER_H
#include "recastmeshmanager.hpp"
#include "version.hpp"
#include "heightfieldshape.hpp"
#include <components/misc/guarded.hpp>
#include <atomic>
namespace DetourNavigator
{
class CachedRecastMeshManager
{
public:
explicit CachedRecastMeshManager(const TileBounds& bounds, std::size_t generation);
bool addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform,
const AreaType areaType);
bool updateObject(const ObjectId id, const btTransform& transform, const AreaType areaType);
bool removeObject(const ObjectId id);
bool addWater(const osg::Vec2i& cellPosition, int cellSize, float level);
bool removeWater(const osg::Vec2i& cellPosition);
bool addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape);
bool removeHeightfield(const osg::Vec2i& cellPosition);
std::shared_ptr<RecastMesh> getMesh();
std::shared_ptr<RecastMesh> getCachedMesh() const;
std::shared_ptr<RecastMesh> getNewMesh() const;
bool isEmpty() const;
void reportNavMeshChange(const Version& recastMeshVersion, const Version& navMeshVersion);
Version getVersion() const { return mImpl.getVersion(); }
private:
RecastMeshManager mImpl;
Misc::ScopeGuarded<std::shared_ptr<RecastMesh>> mCached;
std::atomic_bool mOutdatedCache {true};
};
}
#endif

@ -0,0 +1,27 @@
#include "commulativeaabb.hpp"
#include <components/bullethelpers/aabb.hpp>
namespace DetourNavigator
{
CommulativeAabb::CommulativeAabb(std::size_t lastChangeRevision, const btAABB& aabb)
: mLastChangeRevision(lastChangeRevision)
, mAabb(aabb)
{
}
bool CommulativeAabb::update(std::size_t lastChangeRevision, const btAABB& aabb)
{
if (mLastChangeRevision != lastChangeRevision)
{
mLastChangeRevision = lastChangeRevision;
// btAABB doesn't have copy-assignment operator
mAabb.m_min = aabb.m_min;
mAabb.m_max = aabb.m_max;
return true;
}
const btAABB currentAabb = mAabb;
mAabb.merge(aabb);
return currentAabb != mAabb;
}
}

@ -0,0 +1,24 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_COMMULATIVEAABB_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_COMMULATIVEAABB_H
#include <BulletCollision/Gimpact/btBoxCollision.h>
#include <LinearMath/btTransform.h>
#include <cstdint>
namespace DetourNavigator
{
class CommulativeAabb
{
public:
explicit CommulativeAabb(std::size_t lastChangeRevision, const btAABB& aabb);
bool update(std::size_t lastChangeRevision, const btAABB& aabb);
private:
std::size_t mLastChangeRevision;
btAABB mAabb;
};
}
#endif

@ -4,6 +4,7 @@
#include "settings.hpp" #include "settings.hpp"
#include "settingsutils.hpp" #include "settingsutils.hpp"
#include "version.hpp" #include "version.hpp"
#include "tilespositionsrange.hpp"
#include <components/bullethelpers/operators.hpp> #include <components/bullethelpers/operators.hpp>
@ -196,6 +197,11 @@ namespace DetourNavigator
return stream << "Version {" << value.mGeneration << ", " << value.mRevision << "}"; return stream << "Version {" << value.mGeneration << ", " << value.mRevision << "}";
} }
std::ostream& operator<<(std::ostream& stream, const TilesPositionsRange& value)
{
return stream << "TilesPositionsRange {" << value.mBegin << ", " << value.mEnd << "}";
}
void writeToFile(const RecastMesh& recastMesh, const std::string& pathPrefix, void writeToFile(const RecastMesh& recastMesh, const std::string& pathPrefix,
const std::string& revision, const RecastSettings& settings) const std::string& revision, const RecastSettings& settings)
{ {

@ -19,6 +19,7 @@ class dtNavMesh;
namespace DetourNavigator namespace DetourNavigator
{ {
struct Version; struct Version;
struct TilesPositionsRange;
std::ostream& operator<<(std::ostream& stream, const TileBounds& value); std::ostream& operator<<(std::ostream& stream, const TileBounds& value);
@ -58,6 +59,8 @@ namespace DetourNavigator
std::ostream& operator<<(std::ostream& stream, const Version& value); std::ostream& operator<<(std::ostream& stream, const Version& value);
std::ostream& operator<<(std::ostream& stream, const TilesPositionsRange& value);
class RecastMesh; class RecastMesh;
struct RecastSettings; struct RecastSettings;

@ -55,6 +55,8 @@ namespace DetourNavigator
{} {}
}; };
class UpdateGuard;
/** /**
* @brief Top level interface of detournavigator component. Navigator allows to build a scene with navmesh and find * @brief Top level interface of detournavigator component. Navigator allows to build a scene with navmesh and find
* a path for an agent there. Scene contains agents, geometry objects and water. Agent are distinguished only by * a path for an agent there. Scene contains agents, geometry objects and water. Agent are distinguished only by
@ -66,6 +68,8 @@ namespace DetourNavigator
{ {
virtual ~Navigator() = default; virtual ~Navigator() = default;
virtual std::unique_ptr<const UpdateGuard> makeUpdateGuard() = 0;
/** /**
* @brief addAgent should be called for each agent even if all of them has same half extents. * @brief addAgent should be called for each agent even if all of them has same half extents.
* @param agentBounds allows to setup bounding cylinder for each agent, for each different half extents * @param agentBounds allows to setup bounding cylinder for each agent, for each different half extents
@ -83,13 +87,13 @@ namespace DetourNavigator
* @brief setWorldspace should be called before adding object from new worldspace * @brief setWorldspace should be called before adding object from new worldspace
* @param worldspace * @param worldspace
*/ */
virtual void setWorldspace(std::string_view worldspace) = 0; virtual void setWorldspace(std::string_view worldspace, const UpdateGuard* guard) = 0;
/** /**
* @brief updateBounds should be called before adding object from loading cell * @brief updateBounds should be called before adding object from loading cell
* @param playerPosition corresponds to the bounds center * @param playerPosition corresponds to the bounds center
*/ */
virtual void updateBounds(const osg::Vec3f& playerPosition) = 0; virtual void updateBounds(const osg::Vec3f& playerPosition, const UpdateGuard* guard) = 0;
/** /**
* @brief addObject is used to add complex object with allowed to walk and avoided to walk shapes * @brief addObject is used to add complex object with allowed to walk and avoided to walk shapes
@ -97,7 +101,8 @@ namespace DetourNavigator
* @param shape members must live until object is updated by another shape removed from Navigator * @param shape members must live until object is updated by another shape removed from Navigator
* @param transform allows to setup objects geometry according to its world state * @param transform allows to setup objects geometry according to its world state
*/ */
virtual void addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) = 0; virtual void addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform,
const UpdateGuard* guard) = 0;
/** /**
* @brief addObject is used to add doors. * @brief addObject is used to add doors.
@ -105,7 +110,8 @@ namespace DetourNavigator
* @param shape members must live until object is updated by another shape or removed from Navigator. * @param shape members must live until object is updated by another shape or removed from Navigator.
* @param transform allows to setup objects geometry according to its world state. * @param transform allows to setup objects geometry according to its world state.
*/ */
virtual void addObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform) = 0; virtual void addObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform,
const UpdateGuard* guard) = 0;
/** /**
* @brief updateObject replace object geometry by given data. * @brief updateObject replace object geometry by given data.
@ -113,7 +119,8 @@ namespace DetourNavigator
* @param shape members must live until object is updated by another shape removed from Navigator. * @param shape members must live until object is updated by another shape removed from Navigator.
* @param transform allows to setup objects geometry according to its world state. * @param transform allows to setup objects geometry according to its world state.
*/ */
virtual void updateObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) = 0; virtual void updateObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform,
const UpdateGuard* guard) = 0;
/** /**
* @brief updateObject replace object geometry by given data. * @brief updateObject replace object geometry by given data.
@ -121,13 +128,14 @@ namespace DetourNavigator
* @param shape members must live until object is updated by another shape removed from Navigator. * @param shape members must live until object is updated by another shape removed from Navigator.
* @param transform allows to setup objects geometry according to its world state. * @param transform allows to setup objects geometry according to its world state.
*/ */
virtual void updateObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform) = 0; virtual void updateObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform,
const UpdateGuard* guard) = 0;
/** /**
* @brief removeObject to make it no more available at the scene. * @brief removeObject to make it no more available at the scene.
* @param id is used to find object. * @param id is used to find object.
*/ */
virtual void removeObject(const ObjectId id) = 0; virtual void removeObject(const ObjectId id, const UpdateGuard* guard) = 0;
/** /**
* @brief addWater is used to set water level at given world cell. * @brief addWater is used to set water level at given world cell.
@ -135,17 +143,19 @@ namespace DetourNavigator
* @param cellSize set cell borders. std::numeric_limits<int>::max() disables cell borders. * @param cellSize set cell borders. std::numeric_limits<int>::max() disables cell borders.
* @param shift set global shift of cell center. * @param shift set global shift of cell center.
*/ */
virtual void addWater(const osg::Vec2i& cellPosition, int cellSize, float level) = 0; virtual void addWater(const osg::Vec2i& cellPosition, int cellSize, float level,
const UpdateGuard* guard) = 0;
/** /**
* @brief removeWater to make it no more available at the scene. * @brief removeWater to make it no more available at the scene.
* @param cellPosition allows to find cell. * @param cellPosition allows to find cell.
*/ */
virtual void removeWater(const osg::Vec2i& cellPosition) = 0; virtual void removeWater(const osg::Vec2i& cellPosition, const UpdateGuard* guard) = 0;
virtual void addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape) = 0; virtual void addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape,
const UpdateGuard* guard) = 0;
virtual void removeHeightfield(const osg::Vec2i& cellPosition) = 0; virtual void removeHeightfield(const osg::Vec2i& cellPosition, const UpdateGuard* guard) = 0;
virtual void addPathgrid(const ESM::Cell& cell, const ESM::Pathgrid& pathgrid) = 0; virtual void addPathgrid(const ESM::Cell& cell, const ESM::Pathgrid& pathgrid) = 0;
@ -155,13 +165,14 @@ namespace DetourNavigator
* @brief update starts background navmesh update using current scene state. * @brief update starts background navmesh update using current scene state.
* @param playerPosition setup initial point to order build tiles of navmesh. * @param playerPosition setup initial point to order build tiles of navmesh.
*/ */
virtual void update(const osg::Vec3f& playerPosition) = 0; virtual void update(const osg::Vec3f& playerPosition, const UpdateGuard* guard) = 0;
/** /**
* @brief wait locks thread until tiles are updated from last update call based on passed condition type. * @brief wait locks thread until tiles are updated from last update call based on passed condition type.
* @param waitConditionType defines when waiting will stop * @param waitConditionType defines when waiting will stop
* @param listener optional listener for a progress bar
*/ */
virtual void wait(Loading::Listener& listener, WaitConditionType waitConditionType) = 0; virtual void wait(WaitConditionType waitConditionType, Loading::Listener* listener) = 0;
/** /**
* @brief getNavMesh returns navmesh for specific agent half extents * @brief getNavMesh returns navmesh for specific agent half extents

@ -32,41 +32,44 @@ namespace DetourNavigator
--it->second; --it->second;
} }
void NavigatorImpl::setWorldspace(std::string_view worldspace) void NavigatorImpl::setWorldspace(std::string_view worldspace, const UpdateGuard* guard)
{ {
mNavMeshManager.setWorldspace(worldspace); mNavMeshManager.setWorldspace(worldspace, getImpl(guard));
} }
void NavigatorImpl::updateBounds(const osg::Vec3f& playerPosition) void NavigatorImpl::updateBounds(const osg::Vec3f& playerPosition, const UpdateGuard* guard)
{ {
mNavMeshManager.updateBounds(playerPosition); mNavMeshManager.updateBounds(playerPosition, getImpl(guard));
} }
void NavigatorImpl::addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) void NavigatorImpl::addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform,
const UpdateGuard* guard)
{ {
addObjectImpl(id, shapes, transform); addObjectImpl(id, shapes, transform, guard);
} }
bool NavigatorImpl::addObjectImpl(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) bool NavigatorImpl::addObjectImpl(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform,
const UpdateGuard* guard)
{ {
const CollisionShape collisionShape(shapes.mShapeInstance, *shapes.mShapeInstance->mCollisionShape, shapes.mTransform); const CollisionShape collisionShape(shapes.mShapeInstance, *shapes.mShapeInstance->mCollisionShape, shapes.mTransform);
bool result = mNavMeshManager.addObject(id, collisionShape, transform, AreaType_ground); bool result = mNavMeshManager.addObject(id, collisionShape, transform, AreaType_ground, getImpl(guard));
if (const btCollisionShape* const avoidShape = shapes.mShapeInstance->mAvoidCollisionShape.get()) if (const btCollisionShape* const avoidShape = shapes.mShapeInstance->mAvoidCollisionShape.get())
{ {
const ObjectId avoidId(avoidShape); const ObjectId avoidId(avoidShape);
const CollisionShape avoidCollisionShape(shapes.mShapeInstance, *avoidShape, shapes.mTransform); const CollisionShape avoidCollisionShape(shapes.mShapeInstance, *avoidShape, shapes.mTransform);
if (mNavMeshManager.addObject(avoidId, avoidCollisionShape, transform, AreaType_null)) if (mNavMeshManager.addObject(avoidId, avoidCollisionShape, transform, AreaType_null, getImpl(guard)))
{ {
updateAvoidShapeId(id, avoidId); updateAvoidShapeId(id, avoidId, guard);
result = true; result = true;
} }
} }
return result; return result;
} }
void NavigatorImpl::addObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform) void NavigatorImpl::addObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform,
const UpdateGuard* guard)
{ {
if (addObjectImpl(id, static_cast<const ObjectShapes&>(shapes), transform)) if (addObjectImpl(id, static_cast<const ObjectShapes&>(shapes), transform, guard))
{ {
const osg::Vec3f start = toNavMeshCoordinates(mSettings.mRecast, shapes.mConnectionStart); const osg::Vec3f start = toNavMeshCoordinates(mSettings.mRecast, shapes.mConnectionStart);
const osg::Vec3f end = toNavMeshCoordinates(mSettings.mRecast, shapes.mConnectionEnd); const osg::Vec3f end = toNavMeshCoordinates(mSettings.mRecast, shapes.mConnectionEnd);
@ -75,54 +78,55 @@ namespace DetourNavigator
} }
} }
void NavigatorImpl::updateObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) void NavigatorImpl::updateObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform,
const UpdateGuard* guard)
{ {
const CollisionShape collisionShape(shapes.mShapeInstance, *shapes.mShapeInstance->mCollisionShape, shapes.mTransform); mNavMeshManager.updateObject(id, transform, AreaType_ground, getImpl(guard));
mNavMeshManager.updateObject(id, collisionShape, transform, AreaType_ground);
if (const btCollisionShape* const avoidShape = shapes.mShapeInstance->mAvoidCollisionShape.get()) if (const btCollisionShape* const avoidShape = shapes.mShapeInstance->mAvoidCollisionShape.get())
{ {
const ObjectId avoidId(avoidShape); const ObjectId avoidId(avoidShape);
const CollisionShape avoidCollisionShape(shapes.mShapeInstance, *avoidShape, shapes.mTransform); if (mNavMeshManager.updateObject(avoidId, transform, AreaType_null, getImpl(guard)))
if (mNavMeshManager.updateObject(avoidId, avoidCollisionShape, transform, AreaType_null)) updateAvoidShapeId(id, avoidId, guard);
updateAvoidShapeId(id, avoidId);
} }
} }
void NavigatorImpl::updateObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform) void NavigatorImpl::updateObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform,
const UpdateGuard* guard)
{ {
return updateObject(id, static_cast<const ObjectShapes&>(shapes), transform); return updateObject(id, static_cast<const ObjectShapes&>(shapes), transform, guard);
} }
void NavigatorImpl::removeObject(const ObjectId id) void NavigatorImpl::removeObject(const ObjectId id, const UpdateGuard* guard)
{ {
mNavMeshManager.removeObject(id); mNavMeshManager.removeObject(id, getImpl(guard));
const auto avoid = mAvoidIds.find(id); const auto avoid = mAvoidIds.find(id);
if (avoid != mAvoidIds.end()) if (avoid != mAvoidIds.end())
mNavMeshManager.removeObject(avoid->second); mNavMeshManager.removeObject(avoid->second, getImpl(guard));
const auto water = mWaterIds.find(id); const auto water = mWaterIds.find(id);
if (water != mWaterIds.end()) if (water != mWaterIds.end())
mNavMeshManager.removeObject(water->second); mNavMeshManager.removeObject(water->second, getImpl(guard));
mNavMeshManager.removeOffMeshConnections(id); mNavMeshManager.removeOffMeshConnections(id);
} }
void NavigatorImpl::addWater(const osg::Vec2i& cellPosition, int cellSize, float level) void NavigatorImpl::addWater(const osg::Vec2i& cellPosition, int cellSize, float level, const UpdateGuard* guard)
{ {
mNavMeshManager.addWater(cellPosition, cellSize, level); mNavMeshManager.addWater(cellPosition, cellSize, level, getImpl(guard));
} }
void NavigatorImpl::removeWater(const osg::Vec2i& cellPosition) void NavigatorImpl::removeWater(const osg::Vec2i& cellPosition, const UpdateGuard* guard)
{ {
mNavMeshManager.removeWater(cellPosition); mNavMeshManager.removeWater(cellPosition, getImpl(guard));
} }
void NavigatorImpl::addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape) void NavigatorImpl::addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape,
const UpdateGuard* guard)
{ {
mNavMeshManager.addHeightfield(cellPosition, cellSize, shape); mNavMeshManager.addHeightfield(cellPosition, cellSize, shape, getImpl(guard));
} }
void NavigatorImpl::removeHeightfield(const osg::Vec2i& cellPosition) void NavigatorImpl::removeHeightfield(const osg::Vec2i& cellPosition, const UpdateGuard* guard)
{ {
mNavMeshManager.removeHeightfield(cellPosition); mNavMeshManager.removeHeightfield(cellPosition, getImpl(guard));
} }
void NavigatorImpl::addPathgrid(const ESM::Cell& cell, const ESM::Pathgrid& pathgrid) void NavigatorImpl::addPathgrid(const ESM::Cell& cell, const ESM::Pathgrid& pathgrid)
@ -146,15 +150,15 @@ namespace DetourNavigator
mNavMeshManager.removeOffMeshConnections(ObjectId(&pathgrid)); mNavMeshManager.removeOffMeshConnections(ObjectId(&pathgrid));
} }
void NavigatorImpl::update(const osg::Vec3f& playerPosition) void NavigatorImpl::update(const osg::Vec3f& playerPosition, const UpdateGuard* guard)
{ {
removeUnusedNavMeshes(); removeUnusedNavMeshes();
mNavMeshManager.update(playerPosition); mNavMeshManager.update(playerPosition, getImpl(guard));
} }
void NavigatorImpl::wait(Loading::Listener& listener, WaitConditionType waitConditionType) void NavigatorImpl::wait(WaitConditionType waitConditionType, Loading::Listener* listener)
{ {
mNavMeshManager.wait(listener, waitConditionType); mNavMeshManager.wait(waitConditionType, listener);
} }
SharedNavMeshCacheItem NavigatorImpl::getNavMesh(const AgentBounds& agentBounds) const SharedNavMeshCacheItem NavigatorImpl::getNavMesh(const AgentBounds& agentBounds) const
@ -182,22 +186,18 @@ namespace DetourNavigator
return mNavMeshManager.getRecastMeshTiles(); return mNavMeshManager.getRecastMeshTiles();
} }
void NavigatorImpl::updateAvoidShapeId(const ObjectId id, const ObjectId avoidId) void NavigatorImpl::updateAvoidShapeId(const ObjectId id, const ObjectId avoidId, const UpdateGuard* guard)
{
updateId(id, avoidId, mWaterIds);
}
void NavigatorImpl::updateWaterShapeId(const ObjectId id, const ObjectId waterId)
{ {
updateId(id, waterId, mWaterIds); updateId(id, avoidId, mWaterIds, guard);
} }
void NavigatorImpl::updateId(const ObjectId id, const ObjectId updateId, std::unordered_map<ObjectId, ObjectId>& ids) void NavigatorImpl::updateId(const ObjectId id, const ObjectId updateId,
std::unordered_map<ObjectId, ObjectId>& ids, const UpdateGuard* guard)
{ {
auto inserted = ids.insert(std::make_pair(id, updateId)); auto inserted = ids.insert(std::make_pair(id, updateId));
if (!inserted.second) if (!inserted.second)
{ {
mNavMeshManager.removeObject(inserted.first->second); mNavMeshManager.removeObject(inserted.first->second, getImpl(guard));
inserted.first->second = updateId; inserted.first->second = updateId;
} }
} }

@ -18,39 +18,49 @@ namespace DetourNavigator
*/ */
explicit NavigatorImpl(const Settings& settings, std::unique_ptr<NavMeshDb>&& db); explicit NavigatorImpl(const Settings& settings, std::unique_ptr<NavMeshDb>&& db);
std::unique_ptr<const DetourNavigator::UpdateGuard> makeUpdateGuard() override
{
return std::make_unique<const UpdateGuard>(*this);
}
void addAgent(const AgentBounds& agentBounds) override; void addAgent(const AgentBounds& agentBounds) override;
void removeAgent(const AgentBounds& agentBounds) override; void removeAgent(const AgentBounds& agentBounds) override;
void setWorldspace(std::string_view worldspace) override; void setWorldspace(std::string_view worldspace, const UpdateGuard* guard) override;
void updateBounds(const osg::Vec3f& playerPosition) override; void updateBounds(const osg::Vec3f& playerPosition, const UpdateGuard* guard) override;
void addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) override; void addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform,
const UpdateGuard* guard) override;
void addObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform) override; void addObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform,
const UpdateGuard* guard) override;
void updateObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) override; void updateObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform,
const UpdateGuard* guard) override;
void updateObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform) override; void updateObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform,
const UpdateGuard* guard) override;
void removeObject(const ObjectId id) override; void removeObject(const ObjectId id, const UpdateGuard* guard) override;
void addWater(const osg::Vec2i& cellPosition, int cellSize, float level) override; void addWater(const osg::Vec2i& cellPosition, int cellSize, float level, const UpdateGuard* guard) override;
void removeWater(const osg::Vec2i& cellPosition) override; void removeWater(const osg::Vec2i& cellPosition, const UpdateGuard* guard) override;
void addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape) override; void addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape,
const UpdateGuard* guard) override;
void removeHeightfield(const osg::Vec2i& cellPosition) override; void removeHeightfield(const osg::Vec2i& cellPosition, const UpdateGuard* guard) override;
void addPathgrid(const ESM::Cell& cell, const ESM::Pathgrid& pathgrid) override; void addPathgrid(const ESM::Cell& cell, const ESM::Pathgrid& pathgrid) override;
void removePathgrid(const ESM::Pathgrid& pathgrid) override; void removePathgrid(const ESM::Pathgrid& pathgrid) override;
void update(const osg::Vec3f& playerPosition) override; void update(const osg::Vec3f& playerPosition, const UpdateGuard* guard) override;
void wait(Loading::Listener& listener, WaitConditionType waitConditionType) override; void wait(WaitConditionType waitConditionType, Loading::Listener* listener) override;
SharedNavMeshCacheItem getNavMesh(const AgentBounds& agentBounds) const override; SharedNavMeshCacheItem getNavMesh(const AgentBounds& agentBounds) const override;
@ -72,11 +82,31 @@ namespace DetourNavigator
std::unordered_map<ObjectId, ObjectId> mAvoidIds; std::unordered_map<ObjectId, ObjectId> mAvoidIds;
std::unordered_map<ObjectId, ObjectId> mWaterIds; std::unordered_map<ObjectId, ObjectId> mWaterIds;
inline bool addObjectImpl(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform); inline bool addObjectImpl(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform,
void updateAvoidShapeId(const ObjectId id, const ObjectId avoidId); const UpdateGuard* guard);
void updateWaterShapeId(const ObjectId id, const ObjectId waterId);
void updateId(const ObjectId id, const ObjectId waterId, std::unordered_map<ObjectId, ObjectId>& ids); inline void updateAvoidShapeId(const ObjectId id, const ObjectId avoidId, const UpdateGuard* guard);
void removeUnusedNavMeshes();
inline void updateId(const ObjectId id, const ObjectId waterId, std::unordered_map<ObjectId, ObjectId>& ids,
const UpdateGuard* guard);
inline void removeUnusedNavMeshes();
friend class UpdateGuard;
};
class UpdateGuard
{
public:
explicit UpdateGuard(NavigatorImpl& navigator) : mImpl(navigator.mNavMeshManager) {}
private:
NavMeshManager::UpdateGuard mImpl;
friend inline const NavMeshManager::UpdateGuard* getImpl(const UpdateGuard* guard)
{
return guard == nullptr ? nullptr : &guard->mImpl;
}
}; };
} }

@ -17,40 +17,47 @@ namespace DetourNavigator
public: public:
NavigatorStub() = default; NavigatorStub() = default;
std::unique_ptr<const UpdateGuard> makeUpdateGuard() override { return nullptr; }
void addAgent(const AgentBounds& /*agentBounds*/) override {} void addAgent(const AgentBounds& /*agentBounds*/) override {}
void removeAgent(const AgentBounds& /*agentBounds*/) override {} void removeAgent(const AgentBounds& /*agentBounds*/) override {}
void setWorldspace(std::string_view /*worldspace*/) override {} void setWorldspace(std::string_view /*worldspace*/, const UpdateGuard* /*guard*/) override {}
void addObject(const ObjectId /*id*/, const ObjectShapes& /*shapes*/, const btTransform& /*transform*/) override {} void addObject(const ObjectId /*id*/, const ObjectShapes& /*shapes*/, const btTransform& /*transform*/,
const UpdateGuard* /*guard*/) override {}
void addObject(const ObjectId /*id*/, const DoorShapes& /*shapes*/, const btTransform& /*transform*/) override {} void addObject(const ObjectId /*id*/, const DoorShapes& /*shapes*/, const btTransform& /*transform*/,
const UpdateGuard* /*guard*/) override {}
void updateObject(const ObjectId /*id*/, const ObjectShapes& /*shapes*/, const btTransform& /*transform*/) override {} void updateObject(const ObjectId /*id*/, const ObjectShapes& /*shapes*/, const btTransform& /*transform*/,
const UpdateGuard* /*guard*/) override {}
void updateObject(const ObjectId /*id*/, const DoorShapes& /*shapes*/, const btTransform& /*transform*/) override {} void updateObject(const ObjectId /*id*/, const DoorShapes& /*shapes*/, const btTransform& /*transform*/,
const UpdateGuard* /*guard*/) override {}
void removeObject(const ObjectId /*id*/) override {} void removeObject(const ObjectId /*id*/, const UpdateGuard* /*guard*/) override {}
void addWater(const osg::Vec2i& /*cellPosition*/, int /*cellSize*/, float /*level*/) override {} void addWater(const osg::Vec2i& /*cellPosition*/, int /*cellSize*/, float /*level*/,
const UpdateGuard* /*guard*/) override {}
void removeWater(const osg::Vec2i& /*cellPosition*/) override {} void removeWater(const osg::Vec2i& /*cellPosition*/, const UpdateGuard* /*guard*/) override {}
void addHeightfield(const osg::Vec2i& /*cellPosition*/, int /*cellSize*/, const HeightfieldShape& /*height*/) void addHeightfield(const osg::Vec2i& /*cellPosition*/, int /*cellSize*/, const HeightfieldShape& /*height*/,
override {} const UpdateGuard* /*guard*/) override {}
void removeHeightfield(const osg::Vec2i& /*cellPosition*/) override {} void removeHeightfield(const osg::Vec2i& /*cellPosition*/, const UpdateGuard* /*guard*/) override {}
void addPathgrid(const ESM::Cell& /*cell*/, const ESM::Pathgrid& /*pathgrid*/) override {} void addPathgrid(const ESM::Cell& /*cell*/, const ESM::Pathgrid& /*pathgrid*/) override {}
void removePathgrid(const ESM::Pathgrid& /*pathgrid*/) override {} void removePathgrid(const ESM::Pathgrid& /*pathgrid*/) override {}
void update(const osg::Vec3f& /*playerPosition*/) override {} void update(const osg::Vec3f& /*playerPosition*/, const UpdateGuard* /*guard*/) override {}
void updateBounds(const osg::Vec3f& /*playerPosition*/) override {} void updateBounds(const osg::Vec3f& /*playerPosition*/, const UpdateGuard* /*guard*/) override {}
void wait(Loading::Listener& /*listener*/, WaitConditionType /*waitConditionType*/) override {} void wait(WaitConditionType /*waitConditionType*/, Loading::Listener* /*listener*/) override {}
SharedNavMeshCacheItem getNavMesh(const AgentBounds& /*agentBounds*/) const override SharedNavMeshCacheItem getNavMesh(const AgentBounds& /*agentBounds*/) const override
{ {

@ -7,7 +7,6 @@
#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>
@ -58,58 +57,57 @@ namespace DetourNavigator
, mAsyncNavMeshUpdater(settings, mRecastMeshManager, mOffMeshConnectionsManager, std::move(db)) , mAsyncNavMeshUpdater(settings, mRecastMeshManager, mOffMeshConnectionsManager, std::move(db))
{} {}
void NavMeshManager::setWorldspace(std::string_view worldspace) void NavMeshManager::setWorldspace(std::string_view worldspace, const UpdateGuard* guard)
{ {
if (worldspace == mWorldspace) if (worldspace == mWorldspace)
return; return;
mRecastMeshManager.setWorldspace(worldspace); mRecastMeshManager.setWorldspace(worldspace, getImpl(guard));
for (auto& [agent, cache] : mCache) for (auto& [agent, cache] : mCache)
cache = std::make_shared<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), ++mGenerationCounter); cache = std::make_shared<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), ++mGenerationCounter);
mWorldspace = worldspace; mWorldspace = worldspace;
} }
void NavMeshManager::updateBounds(const osg::Vec3f& playerPosition) void NavMeshManager::updateBounds(const osg::Vec3f& playerPosition, const UpdateGuard* guard)
{ {
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);
mRecastMeshManager.setBounds(bounds); mRecastMeshManager.setBounds(bounds, getImpl(guard));
} }
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, const UpdateGuard* guard)
{ {
return mRecastMeshManager.addObject(id, shape, transform, areaType); return mRecastMeshManager.addObject(id, shape, transform, areaType, getImpl(guard));
} }
bool NavMeshManager::updateObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, bool NavMeshManager::updateObject(const ObjectId id, const btTransform& transform, const AreaType areaType, const UpdateGuard* guard)
const AreaType areaType)
{ {
return mRecastMeshManager.updateObject(id, shape, transform, areaType); return mRecastMeshManager.updateObject(id, transform, areaType, getImpl(guard));
} }
void NavMeshManager::removeObject(const ObjectId id) void NavMeshManager::removeObject(const ObjectId id, const UpdateGuard* guard)
{ {
mRecastMeshManager.removeObject(id); mRecastMeshManager.removeObject(id, getImpl(guard));
} }
void NavMeshManager::addWater(const osg::Vec2i& cellPosition, int cellSize, float level) void NavMeshManager::addWater(const osg::Vec2i& cellPosition, int cellSize, float level, const UpdateGuard* guard)
{ {
mRecastMeshManager.addWater(cellPosition, cellSize, level); mRecastMeshManager.addWater(cellPosition, cellSize, level, getImpl(guard));
} }
void NavMeshManager::removeWater(const osg::Vec2i& cellPosition) void NavMeshManager::removeWater(const osg::Vec2i& cellPosition, const UpdateGuard* guard)
{ {
mRecastMeshManager.removeWater(cellPosition); mRecastMeshManager.removeWater(cellPosition, getImpl(guard));
} }
void NavMeshManager::addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape) void NavMeshManager::addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape, const UpdateGuard* guard)
{ {
mRecastMeshManager.addHeightfield(cellPosition, cellSize, shape); mRecastMeshManager.addHeightfield(cellPosition, cellSize, shape, getImpl(guard));
} }
void NavMeshManager::removeHeightfield(const osg::Vec2i& cellPosition) void NavMeshManager::removeHeightfield(const osg::Vec2i& cellPosition, const UpdateGuard* guard)
{ {
mRecastMeshManager.removeHeightfield(cellPosition); mRecastMeshManager.removeHeightfield(cellPosition, getImpl(guard));
} }
void NavMeshManager::addAgent(const AgentBounds& agentBounds) void NavMeshManager::addAgent(const AgentBounds& agentBounds)
@ -155,7 +153,7 @@ namespace DetourNavigator
mRecastMeshManager.addChangedTile(tile, ChangeType::update); mRecastMeshManager.addChangedTile(tile, ChangeType::update);
} }
void NavMeshManager::update(const osg::Vec3f& playerPosition) void NavMeshManager::update(const osg::Vec3f& playerPosition, const UpdateGuard* guard)
{ {
const auto playerTile = getTilePosition(mSettings.mRecast, const auto playerTile = getTilePosition(mSettings.mRecast,
toNavMeshCoordinates(mSettings.mRecast, playerPosition)); toNavMeshCoordinates(mSettings.mRecast, playerPosition));
@ -164,44 +162,41 @@ namespace DetourNavigator
return; return;
mLastRecastMeshManagerRevision = mRecastMeshManager.getRevision(); mLastRecastMeshManagerRevision = mRecastMeshManager.getRevision();
mPlayerTile = playerTile; mPlayerTile = playerTile;
const auto changedTiles = mRecastMeshManager.takeChangedTiles(); const auto changedTiles = mRecastMeshManager.takeChangedTiles(getImpl(guard));
const TilesPositionsRange range = mRecastMeshManager.getRange();
for (const auto& [agentBounds, cached] : mCache) for (const auto& [agentBounds, cached] : mCache)
update(agentBounds, playerTile, cached, changedTiles); update(agentBounds, playerTile, range, cached, changedTiles);
} }
void NavMeshManager::update(const AgentBounds& agentBounds, const TilePosition& playerTile, void NavMeshManager::update(const AgentBounds& agentBounds, const TilePosition& playerTile,
const SharedNavMeshCacheItem& cached, const std::map<osg::Vec2i, ChangeType>& changedTiles) const TilesPositionsRange& range, const SharedNavMeshCacheItem& cached,
const std::map<osg::Vec2i, ChangeType>& changedTiles)
{ {
std::map<TilePosition, ChangeType> tilesToPost; std::map<osg::Vec2i, ChangeType> tilesToPost = changedTiles;
{ {
const auto locked = cached->lockConst(); const auto locked = cached->lockConst();
const auto& navMesh = locked->getImpl(); const auto& navMesh = locked->getImpl();
for (const auto& [tilePosition, changeType] : changedTiles)
if (navMesh.getTileAt(tilePosition.x(), tilePosition.y(), 0))
tilesToPost.emplace(tilePosition, changeType);
const auto maxTiles = std::min(mSettings.mMaxTilesNumber, navMesh.getParams()->maxTiles); const auto maxTiles = std::min(mSettings.mMaxTilesNumber, navMesh.getParams()->maxTiles);
mRecastMeshManager.forEachTile([&] (const TilePosition& tile, CachedRecastMeshManager& recastMeshManager) getTilesPositions(range, [&] (const TilePosition& tile)
{ {
if (tilesToPost.count(tile)) if (changedTiles.find(tile) != changedTiles.end())
return; return;
const auto shouldAdd = shouldAddTile(tile, playerTile, maxTiles); const bool shouldAdd = shouldAddTile(tile, playerTile, maxTiles);
const auto presentInNavMesh = bool(navMesh.getTileAt(tile.x(), tile.y(), 0)); const bool presentInNavMesh = navMesh.getTileAt(tile.x(), tile.y(), 0) != nullptr;
if (shouldAdd && !presentInNavMesh) if (shouldAdd && !presentInNavMesh)
tilesToPost.emplace(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.emplace(tile, ChangeType::mixed); tilesToPost.emplace(tile, ChangeType::mixed);
else
recastMeshManager.reportNavMeshChange(recastMeshManager.getVersion(), Version {0, 0});
}); });
} }
mAsyncNavMeshUpdater.post(agentBounds, cached, playerTile, mRecastMeshManager.getWorldspace(), tilesToPost); mAsyncNavMeshUpdater.post(agentBounds, cached, playerTile, mWorldspace, tilesToPost);
Log(Debug::Debug) << "Cache update posted for agent=" << agentBounds << Log(Debug::Debug) << "Cache update posted for agent=" << agentBounds <<
" playerTile=" << playerTile << " recastMeshManagerRevision=" << mLastRecastMeshManagerRevision; " playerTile=" << playerTile << " recastMeshManagerRevision=" << mLastRecastMeshManagerRevision;
} }
void NavMeshManager::wait(Loading::Listener& listener, WaitConditionType waitConditionType) void NavMeshManager::wait(WaitConditionType waitConditionType, Loading::Listener* listener)
{ {
mAsyncNavMeshUpdater.wait(listener, waitConditionType); mAsyncNavMeshUpdater.wait(waitConditionType, listener);
} }
SharedNavMeshCacheItem NavMeshManager::getNavMesh(const AgentBounds& agentBounds) const SharedNavMeshCacheItem NavMeshManager::getNavMesh(const AgentBounds& agentBounds) const
@ -221,14 +216,12 @@ namespace DetourNavigator
RecastMeshTiles NavMeshManager::getRecastMeshTiles() const RecastMeshTiles NavMeshManager::getRecastMeshTiles() const
{ {
std::vector<TilePosition> tiles;
mRecastMeshManager.forEachTile(
[&tiles] (const TilePosition& tile, const CachedRecastMeshManager&) { tiles.push_back(tile); });
const std::string worldspace = mRecastMeshManager.getWorldspace();
RecastMeshTiles result; RecastMeshTiles result;
for (const TilePosition& tile : tiles) getTilesPositions(mRecastMeshManager.getRange(), [&] (const TilePosition& v)
if (auto mesh = mRecastMeshManager.getCachedMesh(worldspace, tile)) {
result.emplace(tile, std::move(mesh)); if (auto mesh = mRecastMeshManager.getCachedMesh(mWorldspace, v))
result.emplace(v, std::move(mesh));
});
return result; return result;
} }

@ -20,29 +20,44 @@ namespace DetourNavigator
class NavMeshManager class NavMeshManager
{ {
public: public:
class UpdateGuard
{
public:
explicit UpdateGuard(NavMeshManager& manager) : mImpl(manager.mRecastMeshManager) {}
friend const TileCachedRecastMeshManager::UpdateGuard* getImpl(const UpdateGuard* guard)
{
return guard == nullptr ? nullptr : &guard->mImpl;
}
private:
const TileCachedRecastMeshManager::UpdateGuard mImpl;
};
explicit NavMeshManager(const Settings& settings, std::unique_ptr<NavMeshDb>&& db); explicit NavMeshManager(const Settings& settings, std::unique_ptr<NavMeshDb>&& db);
void setWorldspace(std::string_view worldspace); void setWorldspace(std::string_view worldspace, const UpdateGuard* guard);
void updateBounds(const osg::Vec3f& playerPosition); void updateBounds(const osg::Vec3f& playerPosition, const UpdateGuard* guard);
bool addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, bool addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform,
const AreaType areaType); const AreaType areaType, const UpdateGuard* guard);
bool updateObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, bool updateObject(ObjectId id, const btTransform& transform, AreaType areaType,
const AreaType areaType); const UpdateGuard* guard);
void removeObject(const ObjectId id); void removeObject(const ObjectId id, const UpdateGuard* guard);
void addAgent(const AgentBounds& agentBounds); void addAgent(const AgentBounds& agentBounds);
void addWater(const osg::Vec2i& cellPosition, int cellSize, float level); void addWater(const osg::Vec2i& cellPosition, int cellSize, float level, const UpdateGuard* guard);
void removeWater(const osg::Vec2i& cellPosition); void removeWater(const osg::Vec2i& cellPosition, const UpdateGuard* guard);
void addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape); void addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape,
const UpdateGuard* guard);
void removeHeightfield(const osg::Vec2i& cellPosition); void removeHeightfield(const osg::Vec2i& cellPosition, const UpdateGuard* guard);
bool reset(const AgentBounds& agentBounds); bool reset(const AgentBounds& agentBounds);
@ -50,9 +65,9 @@ namespace DetourNavigator
void removeOffMeshConnections(const ObjectId id); void removeOffMeshConnections(const ObjectId id);
void update(const osg::Vec3f& playerPosition); void update(const osg::Vec3f& playerPosition, const UpdateGuard* guard);
void wait(Loading::Listener& listener, WaitConditionType waitConditionType); void wait(WaitConditionType waitConditionType, Loading::Listener* listener);
SharedNavMeshCacheItem getNavMesh(const AgentBounds& agentBounds) const; SharedNavMeshCacheItem getNavMesh(const AgentBounds& agentBounds) const;
@ -76,7 +91,8 @@ namespace DetourNavigator
inline SharedNavMeshCacheItem getCached(const AgentBounds& agentBounds) const; inline SharedNavMeshCacheItem getCached(const AgentBounds& agentBounds) const;
inline void update(const AgentBounds& agentBounds, const TilePosition& playerTile, inline void update(const AgentBounds& agentBounds, const TilePosition& playerTile,
const SharedNavMeshCacheItem& cached, const std::map<osg::Vec2i, ChangeType>& changedTiles); const TilesPositionsRange& range, const SharedNavMeshCacheItem& cached,
const std::map<osg::Vec2i, ChangeType>& changedTiles);
}; };
} }

@ -1,57 +0,0 @@
#include "oscillatingrecastmeshobject.hpp"
#include "tilebounds.hpp"
#include <components/bullethelpers/aabb.hpp>
#include <algorithm>
namespace DetourNavigator
{
namespace
{
void limitBy(btAABB& aabb, const TileBounds& bounds)
{
aabb.m_min.setX(std::max(aabb.m_min.x(), static_cast<btScalar>(bounds.mMin.x())));
aabb.m_min.setY(std::max(aabb.m_min.y(), static_cast<btScalar>(bounds.mMin.y())));
aabb.m_max.setX(std::min(aabb.m_max.x(), static_cast<btScalar>(bounds.mMax.x())));
aabb.m_max.setY(std::min(aabb.m_max.y(), static_cast<btScalar>(bounds.mMax.y())));
}
}
OscillatingRecastMeshObject::OscillatingRecastMeshObject(RecastMeshObject&& impl, std::size_t lastChangeRevision)
: mImpl(std::move(impl))
, mLastChangeRevision(lastChangeRevision)
, mAabb(BulletHelpers::getAabb(mImpl.getShape(), mImpl.getTransform()))
{
}
OscillatingRecastMeshObject::OscillatingRecastMeshObject(const RecastMeshObject& impl, std::size_t lastChangeRevision)
: mImpl(impl)
, mLastChangeRevision(lastChangeRevision)
, mAabb(BulletHelpers::getAabb(mImpl.getShape(), mImpl.getTransform()))
{
}
bool OscillatingRecastMeshObject::update(const btTransform& transform, const AreaType areaType,
std::size_t lastChangeRevision, const TileBounds& bounds)
{
const btTransform oldTransform = mImpl.getTransform();
if (!mImpl.update(transform, areaType))
return false;
if (transform == oldTransform)
return true;
if (mLastChangeRevision != lastChangeRevision)
{
mLastChangeRevision = lastChangeRevision;
// btAABB doesn't have copy-assignment operator
const btAABB aabb = BulletHelpers::getAabb(mImpl.getShape(), transform);
mAabb.m_min = aabb.m_min;
mAabb.m_max = aabb.m_max;
return true;
}
const btAABB currentAabb = mAabb;
mAabb.merge(BulletHelpers::getAabb(mImpl.getShape(), transform));
limitBy(mAabb, bounds);
return currentAabb != mAabb;
}
}

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

@ -277,6 +277,8 @@ namespace DetourNavigator
mTriangles.erase(std::remove_if(mTriangles.begin(), mTriangles.end(), isNan), mTriangles.end()); mTriangles.erase(std::remove_if(mTriangles.begin(), mTriangles.end(), isNan), mTriangles.end());
std::sort(mTriangles.begin(), mTriangles.end()); std::sort(mTriangles.begin(), mTriangles.end());
std::sort(mWater.begin(), mWater.end()); std::sort(mWater.begin(), mWater.end());
std::sort(mHeightfields.begin(), mHeightfields.end());
std::sort(mFlatHeightfields.begin(), mFlatHeightfields.end());
Mesh mesh = makeMesh(std::move(mTriangles)); Mesh mesh = makeMesh(std::move(mTriangles));
return std::make_shared<RecastMesh>(version, std::move(mesh), std::move(mWater), return std::make_shared<RecastMesh>(version, std::move(mesh), std::move(mWater),
std::move(mHeightfields), std::move(mFlatHeightfields), std::move(mHeightfields), std::move(mFlatHeightfields),

@ -1,167 +0,0 @@
#include "recastmeshmanager.hpp"
#include "recastmeshbuilder.hpp"
#include "heightfieldshape.hpp"
#include <components/debug/debuglog.hpp>
#include <components/misc/convert.hpp>
#include <utility>
namespace
{
struct AddHeightfield
{
osg::Vec2i mCellPosition;
int mCellSize;
DetourNavigator::RecastMeshBuilder& mBuilder;
void operator()(const DetourNavigator::HeightfieldSurface& v)
{
mBuilder.addHeightfield(mCellPosition, mCellSize, v.mHeights, v.mSize, v.mMinHeight, v.mMaxHeight);
}
void operator()(DetourNavigator::HeightfieldPlane v)
{
mBuilder.addHeightfield(mCellPosition, mCellSize, v.mHeight);
}
};
}
namespace DetourNavigator
{
RecastMeshManager::RecastMeshManager(const TileBounds& bounds, std::size_t generation)
: mGeneration(generation)
, mTileBounds(bounds)
{
}
bool RecastMeshManager::addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform,
const AreaType areaType)
{
const std::lock_guard lock(mMutex);
const auto object = mObjects.lower_bound(id);
if (object != mObjects.end() && object->first == id)
return false;
mObjects.emplace_hint(object, id,
OscillatingRecastMeshObject(RecastMeshObject(shape, transform, areaType), mRevision + 1));
++mRevision;
return true;
}
bool RecastMeshManager::updateObject(const ObjectId id, const btTransform& transform, const AreaType areaType)
{
const std::lock_guard lock(mMutex);
const auto object = mObjects.find(id);
if (object == mObjects.end())
return false;
const std::size_t lastChangeRevision = mLastNavMeshReportedChange.has_value()
? mLastNavMeshReportedChange->mRevision : mRevision;
if (!object->second.update(transform, areaType, lastChangeRevision, mTileBounds))
return false;
++mRevision;
return true;
}
bool RecastMeshManager::removeObject(const ObjectId id)
{
const std::lock_guard lock(mMutex);
const auto object = mObjects.find(id);
if (object == mObjects.end())
return false;
mObjects.erase(object);
++mRevision;
return true;
}
bool RecastMeshManager::addWater(const osg::Vec2i& cellPosition, int cellSize, float level)
{
const std::lock_guard lock(mMutex);
if (!mWater.emplace(cellPosition, Water {cellSize, level}).second)
return false;
++mRevision;
return true;
}
bool RecastMeshManager::removeWater(const osg::Vec2i& cellPosition)
{
const std::lock_guard lock(mMutex);
const auto water = mWater.find(cellPosition);
if (water == mWater.end())
return false;
++mRevision;
mWater.erase(water);
return true;
}
bool RecastMeshManager::addHeightfield(const osg::Vec2i& cellPosition, int cellSize,
const HeightfieldShape& shape)
{
const std::lock_guard lock(mMutex);
if (!mHeightfields.emplace(cellPosition, SizedHeightfieldShape {cellSize, shape}).second)
return false;
++mRevision;
return true;
}
bool RecastMeshManager::removeHeightfield(const osg::Vec2i& cellPosition)
{
const std::lock_guard lock(mMutex);
const auto it = mHeightfields.find(cellPosition);
if (it == mHeightfields.end())
return false;
++mRevision;
mHeightfields.erase(it);
return true;
}
std::shared_ptr<RecastMesh> RecastMeshManager::getMesh() const
{
RecastMeshBuilder builder(mTileBounds);
using Object = std::tuple<
osg::ref_ptr<const Resource::BulletShapeInstance>,
ObjectTransform,
std::reference_wrapper<const btCollisionShape>,
btTransform,
AreaType
>;
std::vector<Object> objects;
std::size_t revision;
{
const std::lock_guard lock(mMutex);
for (const auto& [k, v] : mWater)
builder.addWater(k, v);
for (const auto& [cellPosition, v] : mHeightfields)
std::visit(AddHeightfield {cellPosition, v.mCellSize, builder}, v.mShape);
objects.reserve(mObjects.size());
for (const auto& [k, object] : mObjects)
{
const RecastMeshObject& impl = object.getImpl();
objects.emplace_back(impl.getInstance(), impl.getObjectTransform(), impl.getShape(),
impl.getTransform(), impl.getAreaType());
}
revision = mRevision;
}
for (const auto& [instance, objectTransform, shape, transform, areaType] : objects)
builder.addObject(shape, transform, areaType, instance->getSource(), objectTransform);
return std::move(builder).create(Version {.mGeneration = mGeneration, .mRevision = revision});
}
bool RecastMeshManager::isEmpty() const
{
const std::lock_guard lock(mMutex);
return mObjects.empty() && mWater.empty() && mHeightfields.empty();
}
void RecastMeshManager::reportNavMeshChange(const Version& recastMeshVersion, const Version& navMeshVersion)
{
if (recastMeshVersion.mGeneration != mGeneration)
return;
const std::lock_guard lock(mMutex);
if (mLastNavMeshReport.has_value() && navMeshVersion < mLastNavMeshReport->mNavMeshVersion)
return;
mLastNavMeshReport = {recastMeshVersion.mRevision, navMeshVersion};
if (!mLastNavMeshReportedChange.has_value()
|| mLastNavMeshReportedChange->mNavMeshVersion < mLastNavMeshReport->mNavMeshVersion)
mLastNavMeshReportedChange = mLastNavMeshReport;
}
}

@ -1,79 +0,0 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHMANAGER_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHMANAGER_H
#include "oscillatingrecastmeshobject.hpp"
#include "objectid.hpp"
#include "version.hpp"
#include "recastmesh.hpp"
#include "heightfieldshape.hpp"
#include <LinearMath/btTransform.h>
#include <osg/Vec2i>
#include <map>
#include <optional>
#include <memory>
#include <mutex>
class btCollisionShape;
namespace DetourNavigator
{
struct Settings;
class RecastMesh;
struct SizedHeightfieldShape
{
int mCellSize;
HeightfieldShape mShape;
};
class RecastMeshManager
{
public:
explicit RecastMeshManager(const TileBounds& bounds, std::size_t generation);
bool addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform,
const AreaType areaType);
bool updateObject(const ObjectId id, const btTransform& transform, const AreaType areaType);
bool removeObject(const ObjectId id);
bool addWater(const osg::Vec2i& cellPosition, int cellSize, float level);
bool removeWater(const osg::Vec2i& cellPosition);
bool addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape);
bool removeHeightfield(const osg::Vec2i& cellPosition);
std::shared_ptr<RecastMesh> getMesh() const;
bool isEmpty() const;
void reportNavMeshChange(const Version& recastMeshVersion, const Version& navMeshVersion);
Version getVersion() const { return Version {mGeneration, mRevision}; }
private:
struct Report
{
std::size_t mRevision;
Version mNavMeshVersion;
};
const std::size_t mGeneration;
const TileBounds mTileBounds;
mutable std::mutex mMutex;
std::size_t mRevision = 0;
std::map<ObjectId, OscillatingRecastMeshObject> mObjects;
std::map<osg::Vec2i, Water> mWater;
std::map<osg::Vec2i, SizedHeightfieldShape> mHeightfields;
std::optional<Report> mLastNavMeshReportedChange;
std::optional<Report> mLastNavMeshReport;
};
}
#endif

@ -2,10 +2,12 @@
#include "gettilespositions.hpp" #include "gettilespositions.hpp"
#include "settingsutils.hpp" #include "settingsutils.hpp"
#include "changetype.hpp" #include "changetype.hpp"
#include "cachedrecastmeshmanager.hpp" #include "recastmeshbuilder.hpp"
#include <components/debug/debuglog.hpp>
#include <components/misc/convert.hpp> #include <components/misc/convert.hpp>
#include <components/bullethelpers/aabb.hpp>
#include <boost/geometry/geometry.hpp>
#include <algorithm> #include <algorithm>
#include <vector> #include <vector>
@ -20,12 +22,39 @@ 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, struct AddHeightfield
const TilePosition& tilePosition, std::map<TilePosition, std::shared_ptr<CachedRecastMeshManager>>& tiles) {
osg::Vec2i mCellPosition;
int mCellSize;
RecastMeshBuilder& mBuilder;
void operator()(const HeightfieldSurface& v)
{
mBuilder.addHeightfield(mCellPosition, mCellSize, v.mHeights, v.mSize, v.mMinHeight, v.mMaxHeight);
}
void operator()(HeightfieldPlane v)
{
mBuilder.addHeightfield(mCellPosition, mCellSize, v.mHeight);
}
};
TilePosition makeTilePosition(const boost::geometry::model::point<int, 2, boost::geometry::cs::cartesian>& v)
{ {
const auto tile = tiles.find(tilePosition); return TilePosition(v.get<0>(), v.get<1>());
return tile != tiles.end() && tile->second->updateObject(id, transform, areaType);
} }
template <class Mutex>
class MaybeLockGuard
{
public:
explicit MaybeLockGuard(Mutex& mutex, const TileCachedRecastMeshManager::UpdateGuard* guard)
: mImpl(guard == nullptr ? std::optional<std::unique_lock<Mutex>>(mutex) : std::nullopt)
{}
private:
const std::optional<std::unique_lock<Mutex>> mImpl;
};
} }
TileCachedRecastMeshManager::TileCachedRecastMeshManager(const RecastSettings& settings) TileCachedRecastMeshManager::TileCachedRecastMeshManager(const RecastSettings& settings)
@ -34,352 +63,325 @@ namespace DetourNavigator
, mRange(makeTilesPositionsRange(mBounds.mMin, mBounds.mMax, mSettings)) , mRange(makeTilesPositionsRange(mBounds.mMin, mBounds.mMax, mSettings))
{} {}
void TileCachedRecastMeshManager::setBounds(const TileBounds& bounds) void TileCachedRecastMeshManager::setBounds(const TileBounds& bounds, const UpdateGuard* guard)
{ {
if (mBounds == bounds) if (mBounds == bounds)
return; return;
bool changed = false; 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)
{ {
const auto locked = mWorldspaceTiles.lock(); for (const auto& [id, data] : mObjects)
for (auto& object : mObjects)
{ {
const ObjectId id = object.first; const TilesPositionsRange objectRange = makeTilesPositionsRange(data->mObject.getShape(),
ObjectData& data = object.second; data->mObject.getTransform(), mSettings);
const TilesPositionsRange objectRange = makeTilesPositionsRange(data.mShape.getShape(), data.mTransform, mSettings);
const auto onOldTilePosition = [&] (const TilePosition& position) getTilesPositions(getIntersection(mRange, objectRange), [&] (const TilePosition& v)
{ {
if (isInTilesPositionsRange(newRange, position)) if (!isInTilesPositionsRange(newRange, v))
return;
const auto it = data.mTiles.find(position);
if (it == data.mTiles.end())
return;
data.mTiles.erase(it);
if (removeTile(id, position, locked->mTiles))
{ {
addChangedTile(position, ChangeType::remove); addChangedTile(v, ChangeType::remove);
changed = true; changed = true;
} }
}; });
getTilesPositions(getIntersection(mRange, objectRange), onOldTilePosition);
const auto onNewTilePosition = [&] (const TilePosition& position) getTilesPositions(getIntersection(newRange, objectRange), [&] (const TilePosition& v)
{ {
if (data.mTiles.find(position) != data.mTiles.end()) if (!isInTilesPositionsRange(mRange, v))
return;
if (addTile(id, data.mShape, data.mTransform, data.mAreaType, position, locked->mTiles))
{ {
data.mTiles.insert(position); addChangedTile(v, ChangeType::add);
addChangedTile(position, ChangeType::add); changed = true;
} }
}; });
getTilesPositions(getIntersection(newRange, objectRange), onNewTilePosition);
} }
} }
if (changed) if (changed)
{
const MaybeLockGuard lock(mMutex, guard);
++mRevision; ++mRevision;
}
mBounds = bounds; mBounds = bounds;
mRange = newRange; mRange = newRange;
} }
std::string TileCachedRecastMeshManager::getWorldspace() const TilesPositionsRange TileCachedRecastMeshManager::getRange() const
{ {
return mWorldspaceTiles.lockConst()->mWorldspace; const auto bounds = mObjectIndex.bounds();
return TilesPositionsRange {
.mBegin = makeTilePosition(bounds.min_corner()),
.mEnd = makeTilePosition(bounds.max_corner()) + TilePosition(1, 1),
};
} }
void TileCachedRecastMeshManager::setWorldspace(std::string_view worldspace) void TileCachedRecastMeshManager::setWorldspace(std::string_view worldspace, const UpdateGuard* guard)
{ {
const auto locked = mWorldspaceTiles.lock(); const MaybeLockGuard lock(mMutex, guard);
if (locked->mWorldspace == worldspace) if (mWorldspace == worldspace)
return; return;
locked->mTiles.clear(); mWorldspace = worldspace;
locked->mWorldspace = worldspace; ++mGeneration;
++mRevision;
mObjectIndex.clear();
mObjects.clear();
mWater.clear();
mHeightfields.clear();
mCache.clear();
} }
bool TileCachedRecastMeshManager::addObject(const ObjectId id, const CollisionShape& shape, bool TileCachedRecastMeshManager::addObject(ObjectId id, const CollisionShape& shape, const btTransform& transform,
const btTransform& transform, const AreaType areaType) const AreaType areaType, const UpdateGuard* guard)
{ {
auto it = mObjects.find(id); const TilesPositionsRange range = makeTilesPositionsRange(shape.getShape(), transform, mSettings);
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(); const MaybeLockGuard lock(mMutex, guard);
getTilesPositions(range, const auto it = mObjects.find(id);
[&] (const TilePosition& tilePosition) if (it != mObjects.end())
{ return false;
if (addTile(id, shape, transform, areaType, tilePosition, locked->mTiles)) const std::size_t revision = mRevision + 1;
{ ObjectData* const dataPtr = mObjects.emplace_hint(it, id, std::unique_ptr<ObjectData>(new ObjectData {
tilesPositions.insert(tilePosition); .mObject = RecastMeshObject(shape, transform, areaType),
addChangedTile(tilePosition, ChangeType::add); .mRange = range,
} .mAabb = CommulativeAabb(revision, BulletHelpers::getAabb(shape.getShape(), transform)),
}); .mGeneration = mGeneration,
.mRevision = revision,
.mLastNavMeshReportedChange = {},
.mLastNavMeshReport = {},
}))->second.get();
assert(range.mBegin != range.mEnd);
mObjectIndex.insert(makeObjectIndexValue(range, dataPtr));
mRevision = revision;
} }
mObjects.emplace_hint(it, id, ObjectData {shape, transform, areaType, std::move(tilesPositions)}); getTilesPositions(getIntersection(range, mRange),
++mRevision; [&] (const TilePosition& v) { addChangedTile(v, ChangeType::add); });
return true; return true;
} }
bool TileCachedRecastMeshManager::updateObject(const ObjectId id, const CollisionShape& shape, bool TileCachedRecastMeshManager::updateObject(ObjectId id, const btTransform& transform, const AreaType areaType, const UpdateGuard* guard)
const btTransform& transform, AreaType areaType)
{ {
const auto object = mObjects.find(id); TilesPositionsRange newRange;
if (object == mObjects.end()) TilesPositionsRange oldRange;
return false;
auto& data = object->second;
bool changed = false;
std::set<TilePosition> newTiles;
{ {
const TilesPositionsRange objectRange = makeTilesPositionsRange(shape.getShape(), transform, mSettings); const MaybeLockGuard lock(mMutex, guard);
const TilesPositionsRange range = getIntersection(mRange, objectRange); const auto it = mObjects.find(id);
const auto locked = mWorldspaceTiles.lock(); if (it == mObjects.end())
const auto onTilePosition = [&] (const TilePosition& tilePosition) return false;
if (!it->second->mObject.update(transform, areaType))
return false;
const std::size_t lastChangeRevision = it->second->mLastNavMeshReportedChange.has_value()
? it->second->mLastNavMeshReportedChange->mRevision : mRevision;
const btCollisionShape& shape = it->second->mObject.getShape();
if (!it->second->mAabb.update(lastChangeRevision, BulletHelpers::getAabb(shape, transform)))
return false;
newRange = makeTilesPositionsRange(shape, transform, mSettings);
oldRange = it->second->mRange;
if (newRange != oldRange)
{ {
if (data.mTiles.find(tilePosition) != data.mTiles.end()) mObjectIndex.remove(makeObjectIndexValue(oldRange, it->second.get()));
{ mObjectIndex.insert(makeObjectIndexValue(newRange, it->second.get()));
newTiles.insert(tilePosition); it->second->mRange = newRange;
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;
}
} }
++mRevision;
it->second->mRevision = mRevision;
} }
if (changed) if (newRange == oldRange)
{ {
data.mTiles = std::move(newTiles); getTilesPositions(getIntersection(newRange, mRange),
++mRevision; [&] (const TilePosition& v) { addChangedTile(v, ChangeType::update); });
} }
return changed; else
}
void TileCachedRecastMeshManager::removeObject(const ObjectId id)
{
const auto object = mObjects.find(id);
if (object == mObjects.end())
return;
bool changed = false;
{ {
const auto locked = mWorldspaceTiles.lock(); getTilesPositions(getIntersection(newRange, mRange), [&] (const TilePosition& v)
for (const auto& tilePosition : object->second.mTiles)
{ {
if (removeTile(id, tilePosition, locked->mTiles)) const ChangeType changeType = isInTilesPositionsRange(oldRange, v)
{ ? ChangeType::update : ChangeType::add;
addChangedTile(tilePosition, ChangeType::remove); addChangedTile(v, changeType);
changed = true; });
} getTilesPositions(getIntersection(oldRange, mRange), [&] (const TilePosition& v)
} {
if (!isInTilesPositionsRange(newRange, v))
addChangedTile(v, ChangeType::remove);
});
} }
mObjects.erase(object); return true;
if (changed)
++mRevision;
} }
void TileCachedRecastMeshManager::addWater(const osg::Vec2i& cellPosition, const int cellSize, const float level) void TileCachedRecastMeshManager::removeObject(ObjectId id, const UpdateGuard* guard)
{ {
const auto it = mWaterTilesPositions.find(cellPosition); TilesPositionsRange range;
if (it != mWaterTilesPositions.end())
return;
std::vector<TilePosition>& tilesPositions = mWaterTilesPositions.emplace_hint(
it, cellPosition, std::vector<TilePosition>())->second;
bool changed = false;
if (cellSize == std::numeric_limits<int>::max())
{ {
const auto locked = mWorldspaceTiles.lock(); const MaybeLockGuard lock(mMutex, guard);
for (auto& [tilePosition, data] : locked->mTiles) const auto it = mObjects.find(id);
{ if (it == mObjects.end())
if (data->addWater(cellPosition, cellSize, level)) return;
{ range = it->second->mRange;
tilesPositions.push_back(tilePosition); mObjectIndex.remove(makeObjectIndexValue(range, it->second.get()));
addChangedTile(tilePosition, ChangeType::add); mObjects.erase(it);
changed = true; ++mRevision;
}
}
} }
else getTilesPositions(getIntersection(range, mRange),
[&] (const TilePosition& v) { addChangedTile(v, ChangeType::remove); });
}
void TileCachedRecastMeshManager::addWater(const osg::Vec2i& cellPosition, const int cellSize, const float level, const UpdateGuard* guard)
{
const btVector3 shift = Misc::Convert::toBullet(getWaterShift3d(cellPosition, cellSize, level));
const std::optional<TilesPositionsRange> range = cellSize == std::numeric_limits<int>::max()
? std::optional<TilesPositionsRange>()
: makeTilesPositionsRange(cellSize, shift, mSettings);
{ {
const btVector3 shift = Misc::Convert::toBullet(getWaterShift3d(cellPosition, cellSize, level)); const MaybeLockGuard lock(mMutex, guard);
const auto worldspaceTiles = mWorldspaceTiles.lock(); auto it = mWater.find(cellPosition);
getTilesPositions(makeTilesPositionsRange(cellSize, shift, mSettings), if (it != mWater.end())
[&] (const TilePosition& tilePosition) return;
{ const std::size_t revision = mRevision + 1;
auto tile = worldspaceTiles->mTiles.find(tilePosition); it = mWater.emplace_hint(it, cellPosition, WaterData {
if (tile == worldspaceTiles->mTiles.end()) .mWater = Water {.mCellSize = cellSize, .mLevel = level},
{ .mRange = range,
const TileBounds tileBounds = makeRealTileBoundsWithBorder(mSettings, tilePosition); .mRevision = revision,
tile = worldspaceTiles->mTiles.emplace_hint(tile, tilePosition, });
std::make_shared<CachedRecastMeshManager>(tileBounds, mTilesGeneration)); if (range.has_value())
} mWaterIndex.insert(makeWaterIndexValue(*range, it));
if (tile->second->addWater(cellPosition, cellSize, level)) else
{ mInfiniteWater = it;
tilesPositions.push_back(tilePosition); mRevision = revision;
addChangedTile(tilePosition, ChangeType::add);
changed = true;
}
});
} }
addChangedTiles(range, ChangeType::add);
if (changed)
++mRevision;
} }
void TileCachedRecastMeshManager::removeWater(const osg::Vec2i& cellPosition) void TileCachedRecastMeshManager::removeWater(const osg::Vec2i& cellPosition, const UpdateGuard* guard)
{ {
const auto object = mWaterTilesPositions.find(cellPosition); std::optional<TilesPositionsRange> range;
if (object == mWaterTilesPositions.end())
return;
bool changed = false;
{ {
const auto worldspaceTiles = mWorldspaceTiles.lock(); const MaybeLockGuard lock(mMutex, guard);
for (const auto& tilePosition : object->second) const auto it = mWater.find(cellPosition);
{ if (it == mWater.end())
const auto tile = worldspaceTiles->mTiles.find(tilePosition); return;
if (tile == worldspaceTiles->mTiles.end()) range = it->second.mRange;
continue; if (range.has_value())
if (tile->second->removeWater(cellPosition)) mWaterIndex.remove(makeWaterIndexValue(*range, it));
{ else
addChangedTile(tilePosition, ChangeType::remove); mInfiniteWater = mWater.end();
changed = true; mWater.erase(it);
}
if (tile->second->isEmpty())
{
worldspaceTiles->mTiles.erase(tile);
++mTilesGeneration;
}
}
}
mWaterTilesPositions.erase(object);
if (changed)
++mRevision; ++mRevision;
}
addChangedTiles(range, ChangeType::remove);
} }
void TileCachedRecastMeshManager::addHeightfield(const osg::Vec2i& cellPosition, const int cellSize, void TileCachedRecastMeshManager::addHeightfield(const osg::Vec2i& cellPosition, const int cellSize,
const HeightfieldShape& shape) const HeightfieldShape& shape, const UpdateGuard* guard)
{ {
const auto it = mHeightfieldTilesPositions.find(cellPosition);
if (it != mHeightfieldTilesPositions.end())
return;
std::vector<TilePosition>& tilesPositions = mHeightfieldTilesPositions.emplace_hint(
it, cellPosition, std::vector<TilePosition>())->second;
const btVector3 shift = getHeightfieldShift(shape, cellPosition, cellSize); const btVector3 shift = getHeightfieldShift(shape, cellPosition, cellSize);
const std::optional<TilesPositionsRange> range = cellSize == std::numeric_limits<int>::max()
bool changed = false; ? std::optional<TilesPositionsRange>()
: makeTilesPositionsRange(cellSize, shift, mSettings);
{ {
const auto worldspaceTiles = mWorldspaceTiles.lock(); const MaybeLockGuard lock(mMutex, guard);
getTilesPositions(makeTilesPositionsRange(cellSize, shift, mSettings), auto it = mHeightfields.find(cellPosition);
[&] (const TilePosition& tilePosition) if (it != mHeightfields.end())
{ return;
auto tile = worldspaceTiles->mTiles.find(tilePosition); const std::size_t revision = mRevision + 1;
if (tile == worldspaceTiles->mTiles.end()) it = mHeightfields.emplace_hint(it, cellPosition, HeightfieldData {
{ .mCellSize = cellSize,
const TileBounds tileBounds = makeRealTileBoundsWithBorder(mSettings, tilePosition); .mShape = shape,
tile = worldspaceTiles->mTiles.emplace_hint(tile, tilePosition, .mRange = range,
std::make_shared<CachedRecastMeshManager>(tileBounds, mTilesGeneration)); .mRevision = revision,
} });
if (tile->second->addHeightfield(cellPosition, cellSize, shape)) if (range.has_value())
{ mHeightfieldIndex.insert(makeHeightfieldIndexValue(*range, it));
tilesPositions.push_back(tilePosition); else
addChangedTile(tilePosition, ChangeType::add); mInfiniteHeightfield = it;
changed = true; mRevision = revision;
}
});
} }
addChangedTiles(range, ChangeType::add);
if (changed)
++mRevision;
} }
void TileCachedRecastMeshManager::removeHeightfield(const osg::Vec2i& cellPosition) void TileCachedRecastMeshManager::removeHeightfield(const osg::Vec2i& cellPosition, const UpdateGuard* guard)
{ {
const auto object = mHeightfieldTilesPositions.find(cellPosition); std::optional<TilesPositionsRange> range;
if (object == mHeightfieldTilesPositions.end())
return;
bool changed = false;
{ {
const auto worldspaceTiles = mWorldspaceTiles.lock(); const MaybeLockGuard lock(mMutex, guard);
for (const auto& tilePosition : object->second) const auto it = mHeightfields.find(cellPosition);
{ if (it == mHeightfields.end())
const auto tile = worldspaceTiles->mTiles.find(tilePosition); return;
if (tile == worldspaceTiles->mTiles.end()) range = it->second.mRange;
continue; if (range.has_value())
if (tile->second->removeHeightfield(cellPosition)) mHeightfieldIndex.remove(makeHeightfieldIndexValue(*range, it));
{ else
addChangedTile(tilePosition, ChangeType::remove); mInfiniteHeightfield = mHeightfields.end();
changed = true; mHeightfields.erase(it);
}
if (tile->second->isEmpty())
{
worldspaceTiles->mTiles.erase(tile);
++mTilesGeneration;
}
}
}
mHeightfieldTilesPositions.erase(object);
if (changed)
++mRevision; ++mRevision;
}
addChangedTiles(range, ChangeType::remove);
} }
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)
{ {
if (const auto manager = getManager(worldspace, tilePosition)) {
return manager->getMesh(); const std::lock_guard lock(mMutex);
return nullptr; if (mWorldspace != worldspace)
return nullptr;
const auto it = mCache.find(tilePosition);
if (it != mCache.end() && it->second.mRecastMesh->getVersion() == it->second.mVersion)
return it->second.mRecastMesh;
}
auto result = makeMesh(tilePosition);
if (result != nullptr)
{
const std::lock_guard lock(mMutex);
mCache.insert_or_assign(tilePosition, CachedTile {
.mVersion = result->getVersion(),
.mRecastMesh = result,
});
}
return result;
} }
std::shared_ptr<RecastMesh> TileCachedRecastMeshManager::getCachedMesh(std::string_view worldspace, const TilePosition& tilePosition) const std::shared_ptr<RecastMesh> TileCachedRecastMeshManager::getCachedMesh(std::string_view worldspace,
const TilePosition& tilePosition) const
{ {
if (const auto manager = getManager(worldspace, tilePosition)) const std::lock_guard lock(mMutex);
return manager->getCachedMesh(); if (mWorldspace != worldspace)
return nullptr; return nullptr;
const auto it = mCache.find(tilePosition);
if (it == mCache.end())
return nullptr;
return it->second.mRecastMesh;
} }
std::shared_ptr<RecastMesh> TileCachedRecastMeshManager::getNewMesh(std::string_view worldspace, const TilePosition& tilePosition) const std::shared_ptr<RecastMesh> TileCachedRecastMeshManager::getNewMesh(std::string_view worldspace,
const TilePosition& tilePosition) const
{ {
if (const auto manager = getManager(worldspace, tilePosition)) {
return manager->getNewMesh(); const std::lock_guard lock(mMutex);
return nullptr; if (mWorldspace != worldspace)
return nullptr;
}
return makeMesh(tilePosition);
} }
void TileCachedRecastMeshManager::reportNavMeshChange(const TilePosition& tilePosition, Version recastMeshVersion, Version navMeshVersion) const void TileCachedRecastMeshManager::reportNavMeshChange(const TilePosition& tilePosition,
Version recastMeshVersion, Version navMeshVersion)
{ {
const auto locked = mWorldspaceTiles.lockConst(); const std::lock_guard lock(mMutex);
const auto it = locked->mTiles.find(tilePosition); for (auto it = mObjectIndex.qbegin(makeIndexQuery(tilePosition)); it != mObjectIndex.qend(); ++it)
if (it == locked->mTiles.end()) {
return; ObjectData& object = *it->second;
it->second->reportNavMeshChange(recastMeshVersion, navMeshVersion); if (recastMeshVersion.mGeneration != object.mGeneration)
continue;
if (object.mLastNavMeshReport.has_value() && navMeshVersion < object.mLastNavMeshReport->mNavMeshVersion)
continue;
object.mLastNavMeshReport = {recastMeshVersion.mRevision, navMeshVersion};
if (!object.mLastNavMeshReportedChange.has_value()
|| object.mLastNavMeshReportedChange->mNavMeshVersion < object.mLastNavMeshReport->mNavMeshVersion)
object.mLastNavMeshReportedChange = object.mLastNavMeshReport;
}
} }
void TileCachedRecastMeshManager::addChangedTile(const TilePosition& tilePosition, ChangeType changeType) void TileCachedRecastMeshManager::addChangedTile(const TilePosition& tilePosition, const ChangeType changeType)
{ {
auto tile = mChangedTiles.find(tilePosition); auto tile = mChangedTiles.find(tilePosition);
if (tile == mChangedTiles.end()) if (tile == mChangedTiles.end())
@ -388,44 +390,112 @@ namespace DetourNavigator
tile->second = addChangeType(tile->second, changeType); tile->second = addChangeType(tile->second, changeType);
} }
bool TileCachedRecastMeshManager::addTile(const ObjectId id, const CollisionShape& shape, std::map<osg::Vec2i, ChangeType> TileCachedRecastMeshManager::takeChangedTiles(const UpdateGuard* guard)
const btTransform& transform, const AreaType areaType, const TilePosition& tilePosition,
TilesMap& tiles)
{ {
auto tile = tiles.find(tilePosition);
if (tile == tiles.end())
{ {
const TileBounds tileBounds = makeRealTileBoundsWithBorder(mSettings, tilePosition); const MaybeLockGuard lock(mMutex, guard);
tile = tiles.emplace_hint(tile, tilePosition, for (const auto& [tilePosition, changeType] : mChangedTiles)
std::make_shared<CachedRecastMeshManager>(tileBounds, mTilesGeneration)); if (const auto it = mCache.find(tilePosition); it != mCache.end())
++it->second.mVersion.mRevision;
} }
return tile->second->addObject(id, shape, transform, areaType); return std::move(mChangedTiles);
} }
bool TileCachedRecastMeshManager::removeTile(const ObjectId id, TileCachedRecastMeshManager::IndexPoint TileCachedRecastMeshManager::makeIndexPoint(const TilePosition& tilePosition)
const TilePosition& tilePosition, TilesMap& tiles)
{ {
const auto tile = tiles.find(tilePosition); return IndexPoint(tilePosition.x(), tilePosition.y());
if (tile == tiles.end()) }
return false;
const bool result = tile->second->removeObject(id); TileCachedRecastMeshManager::IndexBox TileCachedRecastMeshManager::makeIndexBox(const TilesPositionsRange& range)
if (tile->second->isEmpty()) {
assert(range.mBegin != range.mEnd);
return IndexBox(makeIndexPoint(range.mBegin), makeIndexPoint(range.mEnd - TilePosition(1, 1)));
}
TileCachedRecastMeshManager::ObjectIndexValue TileCachedRecastMeshManager::makeObjectIndexValue(
const TilesPositionsRange& range, ObjectData* id)
{
return {makeIndexBox(range), id};
}
TileCachedRecastMeshManager::WaterIndexValue TileCachedRecastMeshManager::makeWaterIndexValue(
const TilesPositionsRange& range, std::map<osg::Vec2i, WaterData>::const_iterator it)
{
return {makeIndexBox(range), it};
}
TileCachedRecastMeshManager::HeightfieldIndexValue TileCachedRecastMeshManager::makeHeightfieldIndexValue(
const TilesPositionsRange& range, std::map<osg::Vec2i, HeightfieldData>::const_iterator it)
{
return {makeIndexBox(range), it};
}
auto TileCachedRecastMeshManager::makeIndexQuery(const TilePosition& tilePosition)
-> decltype(boost::geometry::index::intersects(IndexBox()))
{
const IndexPoint point = makeIndexPoint(tilePosition);
return boost::geometry::index::intersects(IndexBox(point, point));
}
std::shared_ptr<RecastMesh> TileCachedRecastMeshManager::makeMesh(const TilePosition& tilePosition) const
{
RecastMeshBuilder builder(makeRealTileBoundsWithBorder(mSettings, tilePosition));
using Object = std::tuple<
osg::ref_ptr<const Resource::BulletShapeInstance>,
ObjectTransform,
std::reference_wrapper<const btCollisionShape>,
btTransform,
AreaType
>;
std::vector<Object> objects;
Version version;
bool hasInput = false;
{ {
tiles.erase(tile); const std::lock_guard lock(mMutex);
++mTilesGeneration; for (auto it = mWaterIndex.qbegin(makeIndexQuery(tilePosition)); it != mWaterIndex.qend(); ++it)
{
const auto& [cellPosition, data] = *it->second;
builder.addWater(cellPosition, data.mWater);
hasInput = true;
}
for (auto it = mHeightfieldIndex.qbegin(makeIndexQuery(tilePosition)); it != mHeightfieldIndex.qend(); ++it)
{
const auto& [cellPosition, data] = *it->second;
std::visit(AddHeightfield {cellPosition, data.mCellSize, builder}, data.mShape);
hasInput = true;
}
objects.reserve(mObjects.size());
for (auto it = mObjectIndex.qbegin(makeIndexQuery(tilePosition)); it != mObjectIndex.qend(); ++it)
{
const auto& object = it->second->mObject;
objects.emplace_back(object.getInstance(), object.getObjectTransform(), object.getShape(),
object.getTransform(), object.getAreaType());
hasInput = true;
}
if (hasInput)
{
if (mInfiniteWater != mWater.end())
builder.addWater(mInfiniteWater->first, mInfiniteWater->second.mWater);
if (mInfiniteHeightfield != mHeightfields.end())
std::visit(
AddHeightfield {mInfiniteHeightfield->first, mInfiniteHeightfield->second.mCellSize, builder},
mInfiniteHeightfield->second.mShape
);
version.mGeneration = mGeneration;
version.mRevision = mRevision;
}
} }
return result; if (!hasInput)
return nullptr;
for (const auto& [instance, objectTransform, shape, transform, areaType] : objects)
builder.addObject(shape, transform, areaType, instance->getSource(), objectTransform);
return std::move(builder).create(version);
} }
std::shared_ptr<CachedRecastMeshManager> TileCachedRecastMeshManager::getManager(std::string_view worldspace, void TileCachedRecastMeshManager::addChangedTiles(const std::optional<TilesPositionsRange>& range,
const TilePosition& tilePosition) const ChangeType changeType)
{ {
const auto locked = mWorldspaceTiles.lockConst(); if (range.has_value())
if (locked->mWorldspace != worldspace) getTilesPositions(*range, [&] (const TilePosition& v) { addChangedTile(v, changeType); });
return nullptr;
const auto it = locked->mTiles.find(tilePosition);
if (it == locked->mTiles.end())
return nullptr;
return it->second;
} }
} }

@ -9,101 +9,156 @@
#include "objectid.hpp" #include "objectid.hpp"
#include "areatype.hpp" #include "areatype.hpp"
#include "recastmeshobject.hpp" #include "recastmeshobject.hpp"
#include "commulativeaabb.hpp"
#include "version.hpp"
#include "recastmesh.hpp"
#include <components/misc/guarded.hpp> #include <components/misc/guarded.hpp>
#include <boost/geometry/geometries/box.hpp>
#include <boost/geometry/geometries/point.hpp>
#include <boost/geometry/index/rtree.hpp>
#include <map> #include <map>
#include <mutex> #include <mutex>
#include <vector> #include <vector>
#include <set> #include <optional>
namespace DetourNavigator namespace DetourNavigator
{ {
class CachedRecastMeshManager;
class RecastMesh; class RecastMesh;
class TileCachedRecastMeshManager class TileCachedRecastMeshManager
{ {
public: public:
class UpdateGuard
{
public:
explicit UpdateGuard(TileCachedRecastMeshManager& manager) : mImpl(manager.mMutex) {}
private:
const std::lock_guard<std::mutex> mImpl;
};
explicit TileCachedRecastMeshManager(const RecastSettings& settings); explicit TileCachedRecastMeshManager(const RecastSettings& settings);
void setBounds(const TileBounds& bounds); void setBounds(const TileBounds& bounds, const UpdateGuard* guard);
std::string getWorldspace() const; TilesPositionsRange getRange() const;
void setWorldspace(std::string_view worldspace); void setWorldspace(std::string_view worldspace, const UpdateGuard* guard);
bool addObject(ObjectId id, const CollisionShape& shape, const btTransform& transform, AreaType areaType); bool addObject(ObjectId id, const CollisionShape& shape, const btTransform& transform, AreaType areaType,
const UpdateGuard* guard);
bool updateObject(ObjectId id, const CollisionShape& shape, const btTransform& transform, AreaType areaType); bool updateObject(ObjectId id, const btTransform& transform, AreaType areaType, const UpdateGuard* guard);
void removeObject(ObjectId id); void removeObject(ObjectId id, const UpdateGuard* guard);
void addWater(const osg::Vec2i& cellPosition, int cellSize, float level); void addWater(const osg::Vec2i& cellPosition, int cellSize, float level, const UpdateGuard* guard);
void removeWater(const osg::Vec2i& cellPosition); void removeWater(const osg::Vec2i& cellPosition, const UpdateGuard* guard);
void addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape); void addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape,
const UpdateGuard* guard);
void removeHeightfield(const osg::Vec2i& cellPosition); void removeHeightfield(const osg::Vec2i& cellPosition, const UpdateGuard* guard);
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);
std::shared_ptr<RecastMesh> getCachedMesh(std::string_view worldspace, const TilePosition& tilePosition) const; std::shared_ptr<RecastMesh> getCachedMesh(std::string_view worldspace, const TilePosition& tilePosition) const;
std::shared_ptr<RecastMesh> getNewMesh(std::string_view worldspace, const TilePosition& tilePosition) const; std::shared_ptr<RecastMesh> getNewMesh(std::string_view worldspace, const TilePosition& tilePosition) const;
template <class Function>
void forEachTile(Function&& function) const
{
const auto& locked = mWorldspaceTiles.lockConst();
for (const auto& [tilePosition, recastMeshManager] : locked->mTiles)
function(tilePosition, *recastMeshManager);
}
std::size_t getRevision() const { return mRevision; } std::size_t getRevision() const { return mRevision; }
void reportNavMeshChange(const TilePosition& tilePosition, Version recastMeshVersion, Version navMeshVersion) const; void reportNavMeshChange(const TilePosition& tilePosition, Version recastMeshVersion, Version navMeshVersion);
void addChangedTile(const TilePosition& tilePosition, ChangeType changeType); void addChangedTile(const TilePosition& tilePosition, ChangeType changeType);
std::map<osg::Vec2i, ChangeType> takeChangedTiles() { return std::move(mChangedTiles); } std::map<osg::Vec2i, ChangeType> takeChangedTiles(const UpdateGuard* guard);
private: private:
using TilesMap = std::map<TilePosition, std::shared_ptr<CachedRecastMeshManager>>; struct Report
{
std::size_t mRevision;
Version mNavMeshVersion;
};
struct ObjectData struct ObjectData
{ {
const CollisionShape mShape; RecastMeshObject mObject;
const btTransform mTransform; TilesPositionsRange mRange;
const AreaType mAreaType; CommulativeAabb mAabb;
std::set<TilePosition> mTiles; std::size_t mGeneration = 0;
std::size_t mRevision = 0;
std::optional<Report> mLastNavMeshReportedChange;
std::optional<Report> mLastNavMeshReport;
};
struct WaterData
{
Water mWater;
std::optional<TilesPositionsRange> mRange;
std::size_t mRevision;
};
struct HeightfieldData
{
int mCellSize;
HeightfieldShape mShape;
std::optional<TilesPositionsRange> mRange;
std::size_t mRevision;
}; };
struct WorldspaceTiles struct CachedTile
{ {
std::string mWorldspace; Version mVersion;
TilesMap mTiles; std::shared_ptr<RecastMesh> mRecastMesh;
}; };
using IndexPoint = boost::geometry::model::point<int, 2, boost::geometry::cs::cartesian>;
using IndexBox = boost::geometry::model::box<IndexPoint>;
using ObjectIndexValue = std::pair<IndexBox, ObjectData*>;
using WaterIndexValue = std::pair<IndexBox, std::map<osg::Vec2i, WaterData>::const_iterator>;
using HeightfieldIndexValue = std::pair<IndexBox, std::map<osg::Vec2i, HeightfieldData>::const_iterator>;
const RecastSettings& mSettings; const RecastSettings& mSettings;
TileBounds mBounds; TileBounds mBounds;
TilesPositionsRange mRange; TilesPositionsRange mRange;
Misc::ScopeGuarded<WorldspaceTiles> mWorldspaceTiles; std::string mWorldspace;
std::unordered_map<ObjectId, ObjectData> mObjects; std::unordered_map<ObjectId, std::unique_ptr<ObjectData>> mObjects;
std::map<osg::Vec2i, std::vector<TilePosition>> mWaterTilesPositions; boost::geometry::index::rtree<ObjectIndexValue, boost::geometry::index::quadratic<16>> mObjectIndex;
std::map<osg::Vec2i, std::vector<TilePosition>> mHeightfieldTilesPositions; std::map<osg::Vec2i, WaterData> mWater;
std::map<osg::Vec2i, WaterData>::const_iterator mInfiniteWater = mWater.end();
boost::geometry::index::rtree<WaterIndexValue, boost::geometry::index::linear<4>> mWaterIndex;
std::map<osg::Vec2i, HeightfieldData> mHeightfields;
std::map<osg::Vec2i, HeightfieldData>::const_iterator mInfiniteHeightfield = mHeightfields.end();
boost::geometry::index::rtree<HeightfieldIndexValue, boost::geometry::index::linear<4>> mHeightfieldIndex;
std::map<osg::Vec2i, ChangeType> mChangedTiles; std::map<osg::Vec2i, ChangeType> mChangedTiles;
std::map<TilePosition, CachedTile> mCache;
std::size_t mGeneration = 0;
std::size_t mRevision = 0; std::size_t mRevision = 0;
std::size_t mTilesGeneration = 0; mutable std::mutex mMutex;
inline static IndexPoint makeIndexPoint(const TilePosition& tilePosition);
inline static IndexBox makeIndexBox(const TilesPositionsRange& range);
inline static ObjectIndexValue makeObjectIndexValue(const TilesPositionsRange& range, ObjectData* data);
inline static WaterIndexValue makeWaterIndexValue(const TilesPositionsRange& range,
std::map<osg::Vec2i, WaterData>::const_iterator it);
inline static HeightfieldIndexValue makeHeightfieldIndexValue(const TilesPositionsRange& range,
std::map<osg::Vec2i, HeightfieldData>::const_iterator it);
inline bool addTile(ObjectId id, const CollisionShape& shape, const btTransform& transform, inline static auto makeIndexQuery(const TilePosition& tilePosition)
AreaType areaType, const TilePosition& tilePosition, TilesMap& tiles); -> decltype(boost::geometry::index::intersects(IndexBox()));
inline bool removeTile(ObjectId id, const TilePosition& tilePosition, TilesMap& tiles); inline std::shared_ptr<RecastMesh> makeMesh(const TilePosition& tilePosition) const;
inline std::shared_ptr<CachedRecastMeshManager> getManager(std::string_view worldspace, inline void addChangedTiles(const std::optional<TilesPositionsRange>& range, ChangeType changeType);
const TilePosition& tilePosition) const;
}; };
} }

@ -3,6 +3,8 @@
#include "tileposition.hpp" #include "tileposition.hpp"
#include <tuple>
namespace DetourNavigator namespace DetourNavigator
{ {
struct TilesPositionsRange struct TilesPositionsRange
@ -10,6 +12,16 @@ namespace DetourNavigator
TilePosition mBegin; TilePosition mBegin;
TilePosition mEnd; TilePosition mEnd;
}; };
inline auto tie(const TilesPositionsRange& value)
{
return std::tie(value.mBegin, value.mEnd);
}
inline bool operator==(const TilesPositionsRange& lhs, const TilesPositionsRange& rhs)
{
return tie(lhs) == tie(rhs);
}
} }
#endif #endif

Loading…
Cancel
Save