2018-03-13 22:49:08 +00:00
|
|
|
#include "navmeshmanager.hpp"
|
|
|
|
#include "debug.hpp"
|
2018-04-01 17:24:02 +00:00
|
|
|
#include "exceptions.hpp"
|
2018-04-15 19:54:45 +00:00
|
|
|
#include "gettilespositions.hpp"
|
2018-04-01 00:44:16 +00:00
|
|
|
#include "makenavmesh.hpp"
|
2018-04-15 22:07:18 +00:00
|
|
|
#include "navmeshcacheitem.hpp"
|
2018-04-01 00:44:16 +00:00
|
|
|
#include "settings.hpp"
|
2021-05-14 19:06:29 +00:00
|
|
|
#include "waitconditiontype.hpp"
|
2018-04-01 00:44:16 +00:00
|
|
|
|
2018-11-01 10:08:33 +00:00
|
|
|
#include <components/debug/debuglog.hpp>
|
2021-11-04 02:19:41 +00:00
|
|
|
#include <components/bullethelpers/heightfield.hpp>
|
2022-01-25 14:06:53 +00:00
|
|
|
#include <components/misc/convert.hpp>
|
2018-11-01 10:08:33 +00:00
|
|
|
|
2018-04-01 00:44:16 +00:00
|
|
|
#include <DetourNavMesh.h>
|
|
|
|
|
2021-05-07 07:30:10 +00:00
|
|
|
#include <iterator>
|
|
|
|
|
2018-07-14 12:05:28 +00:00
|
|
|
namespace
|
|
|
|
{
|
|
|
|
using DetourNavigator::ChangeType;
|
|
|
|
|
|
|
|
ChangeType addChangeType(const ChangeType current, const ChangeType add)
|
|
|
|
{
|
|
|
|
return current == add ? current : ChangeType::mixed;
|
|
|
|
}
|
2019-03-10 11:36:16 +00:00
|
|
|
|
|
|
|
/// Safely reset shared_ptr with definite underlying object destrutor call.
|
|
|
|
/// Assuming there is another thread holding copy of this shared_ptr or weak_ptr to this shared_ptr.
|
|
|
|
template <class T>
|
|
|
|
bool resetIfUnique(std::shared_ptr<T>& ptr)
|
|
|
|
{
|
2019-03-17 18:24:49 +00:00
|
|
|
const std::weak_ptr<T> weak(ptr);
|
|
|
|
ptr.reset();
|
2019-03-10 11:36:16 +00:00
|
|
|
if (auto shared = weak.lock())
|
|
|
|
{
|
|
|
|
ptr = std::move(shared);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
2018-07-14 12:05:28 +00:00
|
|
|
}
|
|
|
|
|
2018-03-13 22:49:08 +00:00
|
|
|
namespace DetourNavigator
|
|
|
|
{
|
2022-01-25 14:06:53 +00:00
|
|
|
namespace
|
|
|
|
{
|
|
|
|
TileBounds makeBounds(const RecastSettings& settings, const osg::Vec2f& center, int maxTiles)
|
|
|
|
{
|
|
|
|
const float radius = fromNavMeshCoordinates(settings, std::ceil(std::sqrt(static_cast<float>(maxTiles) / osg::PIf) + 1) * getTileSize(settings));
|
|
|
|
TileBounds result;
|
|
|
|
result.mMin = center - osg::Vec2f(radius, radius);
|
|
|
|
result.mMax = center + osg::Vec2f(radius, radius);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-09 20:51:42 +00:00
|
|
|
NavMeshManager::NavMeshManager(const Settings& settings, std::unique_ptr<NavMeshDb>&& db)
|
2018-04-01 00:44:16 +00:00
|
|
|
: mSettings(settings)
|
2021-07-09 20:51:42 +00:00
|
|
|
, mRecastMeshManager(settings.mRecast)
|
|
|
|
, mOffMeshConnectionsManager(settings.mRecast)
|
|
|
|
, mAsyncNavMeshUpdater(settings, mRecastMeshManager, mOffMeshConnectionsManager, std::move(db))
|
2018-07-12 08:44:11 +00:00
|
|
|
{}
|
2018-03-13 22:49:08 +00:00
|
|
|
|
2021-07-09 20:51:42 +00:00
|
|
|
void NavMeshManager::setWorldspace(std::string_view worldspace)
|
|
|
|
{
|
|
|
|
if (worldspace == mWorldspace)
|
|
|
|
return;
|
|
|
|
mRecastMeshManager.setWorldspace(worldspace);
|
|
|
|
for (auto& [agent, cache] : mCache)
|
|
|
|
cache = std::make_shared<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), ++mGenerationCounter);
|
|
|
|
mWorldspace = worldspace;
|
|
|
|
}
|
|
|
|
|
2021-08-01 00:13:55 +00:00
|
|
|
bool NavMeshManager::addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform,
|
2018-07-18 19:09:50 +00:00
|
|
|
const AreaType areaType)
|
2018-04-02 21:04:19 +00:00
|
|
|
{
|
2021-08-01 00:13:55 +00:00
|
|
|
const btCollisionShape& collisionShape = shape.getShape();
|
2018-07-18 19:09:50 +00:00
|
|
|
if (!mRecastMeshManager.addObject(id, shape, transform, areaType))
|
2018-04-02 21:04:19 +00:00
|
|
|
return false;
|
2021-08-01 00:13:55 +00:00
|
|
|
addChangedTiles(collisionShape, transform, ChangeType::add);
|
2018-04-02 21:04:19 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-08-01 00:13:55 +00:00
|
|
|
bool NavMeshManager::updateObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform,
|
2018-07-18 19:09:50 +00:00
|
|
|
const AreaType areaType)
|
2018-05-26 14:44:25 +00:00
|
|
|
{
|
2020-02-25 02:38:39 +00:00
|
|
|
return mRecastMeshManager.updateObject(id, shape, transform, areaType,
|
2020-04-29 11:54:48 +00:00
|
|
|
[&] (const TilePosition& tile) { addChangedTile(tile, ChangeType::update); });
|
2018-05-26 14:44:25 +00:00
|
|
|
}
|
|
|
|
|
2018-09-22 15:36:57 +00:00
|
|
|
bool NavMeshManager::removeObject(const ObjectId id)
|
2018-03-13 22:49:08 +00:00
|
|
|
{
|
2018-04-01 00:44:16 +00:00
|
|
|
const auto object = mRecastMeshManager.removeObject(id);
|
|
|
|
if (!object)
|
2018-03-13 22:49:08 +00:00
|
|
|
return false;
|
2018-07-14 12:05:28 +00:00
|
|
|
addChangedTiles(object->mShape, object->mTransform, ChangeType::remove);
|
2018-03-13 22:49:08 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-11-04 01:48:32 +00:00
|
|
|
bool NavMeshManager::addWater(const osg::Vec2i& cellPosition, int cellSize, float level)
|
2018-07-20 19:11:34 +00:00
|
|
|
{
|
2021-11-04 01:48:32 +00:00
|
|
|
if (!mRecastMeshManager.addWater(cellPosition, cellSize, level))
|
2018-07-20 19:11:34 +00:00
|
|
|
return false;
|
2021-11-04 02:19:41 +00:00
|
|
|
const btVector3 shift = Misc::Convert::toBullet(getWaterShift3d(cellPosition, cellSize, level));
|
2021-07-14 19:54:41 +00:00
|
|
|
addChangedTiles(cellSize, shift, ChangeType::add);
|
2018-07-20 19:11:34 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool NavMeshManager::removeWater(const osg::Vec2i& cellPosition)
|
|
|
|
{
|
|
|
|
const auto water = mRecastMeshManager.removeWater(cellPosition);
|
|
|
|
if (!water)
|
|
|
|
return false;
|
2021-11-04 02:19:41 +00:00
|
|
|
const btVector3 shift = Misc::Convert::toBullet(getWaterShift3d(cellPosition, water->mCellSize, water->mLevel));
|
2021-11-04 01:48:32 +00:00
|
|
|
addChangedTiles(water->mCellSize, shift, ChangeType::remove);
|
2018-07-20 19:11:34 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-11-04 02:19:41 +00:00
|
|
|
bool NavMeshManager::addHeightfield(const osg::Vec2i& cellPosition, int cellSize, const HeightfieldShape& shape)
|
2021-07-14 18:57:52 +00:00
|
|
|
{
|
2021-11-04 02:19:41 +00:00
|
|
|
if (!mRecastMeshManager.addHeightfield(cellPosition, cellSize, shape))
|
2021-07-14 18:57:52 +00:00
|
|
|
return false;
|
2021-11-04 02:19:41 +00:00
|
|
|
const btVector3 shift = getHeightfieldShift(shape, cellPosition, cellSize);
|
2021-07-14 18:57:52 +00:00
|
|
|
addChangedTiles(cellSize, shift, ChangeType::add);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool NavMeshManager::removeHeightfield(const osg::Vec2i& cellPosition)
|
|
|
|
{
|
|
|
|
const auto heightfield = mRecastMeshManager.removeHeightfield(cellPosition);
|
|
|
|
if (!heightfield)
|
|
|
|
return false;
|
2021-11-04 02:19:41 +00:00
|
|
|
const btVector3 shift = getHeightfieldShift(heightfield->mShape, cellPosition, heightfield->mCellSize);
|
|
|
|
addChangedTiles(heightfield->mCellSize, shift, ChangeType::remove);
|
2021-07-14 18:57:52 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-04-01 17:24:02 +00:00
|
|
|
void NavMeshManager::addAgent(const osg::Vec3f& agentHalfExtents)
|
|
|
|
{
|
|
|
|
auto cached = mCache.find(agentHalfExtents);
|
|
|
|
if (cached != mCache.end())
|
|
|
|
return;
|
|
|
|
mCache.insert(std::make_pair(agentHalfExtents,
|
2019-03-10 13:49:56 +00:00
|
|
|
std::make_shared<GuardedNavMeshCacheItem>(makeEmptyNavMesh(mSettings), ++mGenerationCounter)));
|
2018-11-01 10:08:33 +00:00
|
|
|
Log(Debug::Debug) << "cache add for agent=" << agentHalfExtents;
|
2018-04-01 17:24:02 +00:00
|
|
|
}
|
|
|
|
|
2019-03-10 11:36:16 +00:00
|
|
|
bool NavMeshManager::reset(const osg::Vec3f& agentHalfExtents)
|
2018-03-13 22:49:08 +00:00
|
|
|
{
|
2019-03-10 11:36:16 +00:00
|
|
|
const auto it = mCache.find(agentHalfExtents);
|
|
|
|
if (it == mCache.end())
|
|
|
|
return true;
|
|
|
|
if (!resetIfUnique(it->second))
|
|
|
|
return false;
|
2018-03-13 22:49:08 +00:00
|
|
|
mCache.erase(agentHalfExtents);
|
2019-02-17 22:29:11 +00:00
|
|
|
mChangedTiles.erase(agentHalfExtents);
|
|
|
|
mPlayerTile.erase(agentHalfExtents);
|
|
|
|
mLastRecastMeshManagerRevision.erase(agentHalfExtents);
|
2019-03-10 11:36:16 +00:00
|
|
|
return true;
|
2018-03-13 22:49:08 +00:00
|
|
|
}
|
|
|
|
|
2020-06-11 21:23:30 +00:00
|
|
|
void NavMeshManager::addOffMeshConnection(const ObjectId id, const osg::Vec3f& start, const osg::Vec3f& end, const AreaType areaType)
|
2018-08-26 20:27:38 +00:00
|
|
|
{
|
2020-06-11 21:23:30 +00:00
|
|
|
mOffMeshConnectionsManager.add(id, OffMeshConnection {start, end, areaType});
|
2018-08-26 20:27:38 +00:00
|
|
|
|
2021-11-06 12:46:43 +00:00
|
|
|
const auto startTilePosition = getTilePosition(mSettings.mRecast, start);
|
|
|
|
const auto endTilePosition = getTilePosition(mSettings.mRecast, end);
|
2018-08-26 20:27:38 +00:00
|
|
|
|
|
|
|
addChangedTile(startTilePosition, ChangeType::add);
|
|
|
|
|
|
|
|
if (startTilePosition != endTilePosition)
|
|
|
|
addChangedTile(endTilePosition, ChangeType::add);
|
|
|
|
}
|
|
|
|
|
2020-06-11 21:23:30 +00:00
|
|
|
void NavMeshManager::removeOffMeshConnections(const ObjectId id)
|
2018-08-26 20:27:38 +00:00
|
|
|
{
|
2020-06-11 21:23:30 +00:00
|
|
|
const auto changedTiles = mOffMeshConnectionsManager.remove(id);
|
|
|
|
for (const auto& tile : changedTiles)
|
|
|
|
addChangedTile(tile, ChangeType::update);
|
2018-08-26 20:27:38 +00:00
|
|
|
}
|
|
|
|
|
2021-06-23 21:13:59 +00:00
|
|
|
void NavMeshManager::update(const osg::Vec3f& playerPosition, const osg::Vec3f& agentHalfExtents)
|
2018-03-13 22:49:08 +00:00
|
|
|
{
|
2021-11-06 12:46:43 +00:00
|
|
|
const auto playerTile = getTilePosition(mSettings.mRecast, toNavMeshCoordinates(mSettings.mRecast, playerPosition));
|
2018-08-30 22:39:44 +00:00
|
|
|
auto& lastRevision = mLastRecastMeshManagerRevision[agentHalfExtents];
|
|
|
|
auto lastPlayerTile = mPlayerTile.find(agentHalfExtents);
|
2019-11-30 12:52:17 +00:00
|
|
|
if (lastRevision == mRecastMeshManager.getRevision() && lastPlayerTile != mPlayerTile.end()
|
2018-08-30 22:39:44 +00:00
|
|
|
&& lastPlayerTile->second == playerTile)
|
2018-04-20 23:57:01 +00:00
|
|
|
return;
|
2018-08-30 22:39:44 +00:00
|
|
|
lastRevision = mRecastMeshManager.getRevision();
|
|
|
|
if (lastPlayerTile == mPlayerTile.end())
|
|
|
|
lastPlayerTile = mPlayerTile.insert(std::make_pair(agentHalfExtents, playerTile)).first;
|
|
|
|
else
|
|
|
|
lastPlayerTile->second = playerTile;
|
2018-07-14 12:05:28 +00:00
|
|
|
std::map<TilePosition, ChangeType> tilesToPost;
|
2019-02-16 12:32:47 +00:00
|
|
|
const auto cached = getCached(agentHalfExtents);
|
|
|
|
if (!cached)
|
|
|
|
{
|
|
|
|
std::ostringstream stream;
|
|
|
|
stream << "Agent with half extents is not found: " << agentHalfExtents;
|
|
|
|
throw InvalidArgument(stream.str());
|
|
|
|
}
|
2018-04-01 00:44:16 +00:00
|
|
|
const auto changedTiles = mChangedTiles.find(agentHalfExtents);
|
|
|
|
{
|
2019-03-10 13:49:56 +00:00
|
|
|
const auto locked = cached->lockConst();
|
2019-03-10 12:48:12 +00:00
|
|
|
const auto& navMesh = locked->getImpl();
|
2018-04-20 23:57:01 +00:00
|
|
|
if (changedTiles != mChangedTiles.end())
|
|
|
|
{
|
|
|
|
for (const auto& tile : changedTiles->second)
|
2018-09-30 22:33:25 +00:00
|
|
|
if (navMesh.getTileAt(tile.first.x(), tile.first.y(), 0))
|
2018-07-14 12:05:28 +00:00
|
|
|
{
|
|
|
|
auto tileToPost = tilesToPost.find(tile.first);
|
|
|
|
if (tileToPost == tilesToPost.end())
|
|
|
|
tilesToPost.insert(tile);
|
|
|
|
else
|
|
|
|
tileToPost->second = addChangeType(tileToPost->second, tile.second);
|
|
|
|
}
|
2018-04-20 23:57:01 +00:00
|
|
|
}
|
2019-02-21 22:22:10 +00:00
|
|
|
const auto maxTiles = std::min(mSettings.mMaxTilesNumber, navMesh.getParams()->maxTiles);
|
2022-01-25 14:06:53 +00:00
|
|
|
mRecastMeshManager.setBounds(makeBounds(mSettings.mRecast, osg::Vec2f(playerPosition.x(), playerPosition.y()), maxTiles));
|
2021-05-26 23:09:58 +00:00
|
|
|
mRecastMeshManager.forEachTile([&] (const TilePosition& tile, CachedRecastMeshManager& recastMeshManager)
|
2018-04-20 23:57:01 +00:00
|
|
|
{
|
|
|
|
if (tilesToPost.count(tile))
|
|
|
|
return;
|
|
|
|
const auto shouldAdd = shouldAddTile(tile, playerTile, maxTiles);
|
2018-09-30 22:33:25 +00:00
|
|
|
const auto presentInNavMesh = bool(navMesh.getTileAt(tile.x(), tile.y(), 0));
|
2018-07-14 12:05:28 +00:00
|
|
|
if (shouldAdd && !presentInNavMesh)
|
2021-08-05 22:05:09 +00:00
|
|
|
tilesToPost.insert(std::make_pair(tile, locked->isEmptyTile(tile) ? ChangeType::update : ChangeType::add));
|
2018-07-14 12:05:28 +00:00
|
|
|
else if (!shouldAdd && presentInNavMesh)
|
|
|
|
tilesToPost.insert(std::make_pair(tile, ChangeType::mixed));
|
2021-05-26 23:09:58 +00:00
|
|
|
else
|
|
|
|
recastMeshManager.reportNavMeshChange(recastMeshManager.getVersion(), Version {0, 0});
|
2018-04-20 23:57:01 +00:00
|
|
|
});
|
2018-04-01 00:44:16 +00:00
|
|
|
}
|
2021-07-09 20:51:42 +00:00
|
|
|
mAsyncNavMeshUpdater.post(agentHalfExtents, cached, playerTile, mRecastMeshManager.getWorldspace(), tilesToPost);
|
2019-02-16 21:52:20 +00:00
|
|
|
if (changedTiles != mChangedTiles.end())
|
|
|
|
changedTiles->second.clear();
|
2020-02-22 21:03:44 +00:00
|
|
|
Log(Debug::Debug) << "Cache update posted for agent=" << agentHalfExtents <<
|
2018-11-01 10:08:33 +00:00
|
|
|
" playerTile=" << lastPlayerTile->second <<
|
|
|
|
" recastMeshManagerRevision=" << lastRevision;
|
2018-03-13 22:49:08 +00:00
|
|
|
}
|
|
|
|
|
2021-05-14 19:06:29 +00:00
|
|
|
void NavMeshManager::wait(Loading::Listener& listener, WaitConditionType waitConditionType)
|
2018-03-13 22:49:08 +00:00
|
|
|
{
|
2021-05-14 19:06:29 +00:00
|
|
|
mAsyncNavMeshUpdater.wait(listener, waitConditionType);
|
2018-03-13 22:49:08 +00:00
|
|
|
}
|
|
|
|
|
2018-09-30 22:33:25 +00:00
|
|
|
SharedNavMeshCacheItem NavMeshManager::getNavMesh(const osg::Vec3f& agentHalfExtents) const
|
2018-03-13 22:49:08 +00:00
|
|
|
{
|
2018-09-30 22:33:25 +00:00
|
|
|
return getCached(agentHalfExtents);
|
2018-03-13 22:49:08 +00:00
|
|
|
}
|
2018-04-01 00:44:16 +00:00
|
|
|
|
2018-09-30 22:33:25 +00:00
|
|
|
std::map<osg::Vec3f, SharedNavMeshCacheItem> NavMeshManager::getNavMeshes() const
|
2018-04-07 13:11:23 +00:00
|
|
|
{
|
|
|
|
return mCache;
|
|
|
|
}
|
|
|
|
|
2019-03-17 17:18:53 +00:00
|
|
|
void NavMeshManager::reportStats(unsigned int frameNumber, osg::Stats& stats) const
|
|
|
|
{
|
2021-08-05 22:05:09 +00:00
|
|
|
DetourNavigator::reportStats(mAsyncNavMeshUpdater.getStats(), frameNumber, stats);
|
2019-03-17 17:18:53 +00:00
|
|
|
}
|
|
|
|
|
2021-10-09 16:36:37 +00:00
|
|
|
RecastMeshTiles NavMeshManager::getRecastMeshTiles() const
|
2019-11-27 22:45:01 +00:00
|
|
|
{
|
|
|
|
std::vector<TilePosition> tiles;
|
2021-05-26 23:09:58 +00:00
|
|
|
mRecastMeshManager.forEachTile(
|
|
|
|
[&tiles] (const TilePosition& tile, const CachedRecastMeshManager&) { tiles.push_back(tile); });
|
2021-07-09 20:51:42 +00:00
|
|
|
const std::string worldspace = mRecastMeshManager.getWorldspace();
|
2019-11-27 22:45:01 +00:00
|
|
|
RecastMeshTiles result;
|
2021-10-09 16:36:37 +00:00
|
|
|
for (const TilePosition& tile : tiles)
|
2021-07-09 20:51:42 +00:00
|
|
|
if (auto mesh = mRecastMeshManager.getCachedMesh(worldspace, tile))
|
2021-10-09 16:36:37 +00:00
|
|
|
result.emplace(tile, std::move(mesh));
|
2019-11-27 22:45:01 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2018-07-14 12:05:28 +00:00
|
|
|
void NavMeshManager::addChangedTiles(const btCollisionShape& shape, const btTransform& transform,
|
2018-07-20 19:11:34 +00:00
|
|
|
const ChangeType changeType)
|
|
|
|
{
|
2022-01-25 14:06:53 +00:00
|
|
|
getTilesPositions(makeTilesPositionsRange(shape, transform, mRecastMeshManager.getBounds(), mSettings.mRecast),
|
2018-07-20 19:11:34 +00:00
|
|
|
[&] (const TilePosition& v) { addChangedTile(v, changeType); });
|
|
|
|
}
|
|
|
|
|
2021-11-04 02:19:41 +00:00
|
|
|
void NavMeshManager::addChangedTiles(const int cellSize, const btVector3& shift,
|
2018-07-20 19:11:34 +00:00
|
|
|
const ChangeType changeType)
|
|
|
|
{
|
|
|
|
if (cellSize == std::numeric_limits<int>::max())
|
|
|
|
return;
|
|
|
|
|
2022-01-25 14:06:53 +00:00
|
|
|
getTilesPositions(makeTilesPositionsRange(cellSize, shift, mSettings.mRecast),
|
2018-07-20 19:11:34 +00:00
|
|
|
[&] (const TilePosition& v) { addChangedTile(v, changeType); });
|
|
|
|
}
|
|
|
|
|
|
|
|
void NavMeshManager::addChangedTile(const TilePosition& tilePosition, const ChangeType changeType)
|
|
|
|
{
|
|
|
|
for (const auto& cached : mCache)
|
|
|
|
{
|
2018-09-30 22:33:25 +00:00
|
|
|
auto& tiles = mChangedTiles[cached.first];
|
|
|
|
auto tile = tiles.find(tilePosition);
|
|
|
|
if (tile == tiles.end())
|
|
|
|
tiles.insert(std::make_pair(tilePosition, changeType));
|
|
|
|
else
|
|
|
|
tile->second = addChangeType(tile->second, changeType);
|
2018-07-20 19:11:34 +00:00
|
|
|
}
|
2018-04-01 00:44:16 +00:00
|
|
|
}
|
2018-04-01 17:24:02 +00:00
|
|
|
|
2019-02-16 12:32:47 +00:00
|
|
|
SharedNavMeshCacheItem NavMeshManager::getCached(const osg::Vec3f& agentHalfExtents) const
|
2018-04-01 17:24:02 +00:00
|
|
|
{
|
|
|
|
const auto cached = mCache.find(agentHalfExtents);
|
|
|
|
if (cached != mCache.end())
|
|
|
|
return cached->second;
|
2019-02-16 12:32:47 +00:00
|
|
|
return SharedNavMeshCacheItem();
|
2018-04-01 17:24:02 +00:00
|
|
|
}
|
2018-03-13 22:49:08 +00:00
|
|
|
}
|