Merge pull request #1633 from elsid/pathfinder_detour

Use recastnavigation for pathfinding (#2229)
This commit is contained in:
Bret Curtis 2018-10-30 20:44:13 +01:00 committed by GitHub
commit d6c674660a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
124 changed files with 8610 additions and 727 deletions

View file

@ -51,7 +51,7 @@ add_component_dir (shader
add_component_dir (sceneutil
clone attach visitor util statesetupdater controller skeleton riggeometry morphgeometry lightcontroller
lightmanager lightutil positionattitudetransform workqueue unrefqueue pathgridutil waterutil writescene serialize optimizer
actorutil
actorutil detourdebugdraw navmesh agentpath
)
add_component_dir (nif
@ -156,6 +156,23 @@ if(NOT WIN32 AND NOT ANDROID)
)
endif()
add_component_dir(detournavigator
debug
makenavmesh
findsmoothpath
recastmeshbuilder
recastmeshmanager
cachedrecastmeshmanager
navmeshmanager
navigator
asyncnavmeshupdater
chunkytrimesh
recastmesh
tilecachedrecastmeshmanager
recastmeshobject
navmeshtilescache
)
set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui
)
@ -196,6 +213,10 @@ include_directories(${Bullet_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR})
add_library(components STATIC ${COMPONENT_FILES} ${MOC_SRCS} ${ESM_UI_HDR})
find_package(RecastNavigation COMPONENTS DebugUtils Detour Recast REQUIRED)
include_directories(SYSTEM ${RecastNavigation_INCLUDE_DIRS})
target_link_libraries(components
${Boost_SYSTEM_LIBRARY}
${Boost_FILESYSTEM_LIBRARY}
@ -214,6 +235,7 @@ target_link_libraries(components
${SDL2_LIBRARIES}
${OPENGL_gl_LIBRARY}
${MyGUI_LIBRARIES}
${RecastNavigation_LIBRARIES}
)
if (WIN32)

View file

@ -0,0 +1,71 @@
#ifndef OPENMW_COMPONENTS_BULLETHELPERS_OPERATORS_H
#define OPENMW_COMPONENTS_BULLETHELPERS_OPERATORS_H
#include <iomanip>
#include <limits>
#include <ostream>
#include <BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h>
#include <BulletCollision/CollisionShapes/btCompoundShape.h>
#include <BulletCollision/CollisionShapes/btBoxShape.h>
#include <LinearMath/btTransform.h>
inline std::ostream& operator <<(std::ostream& stream, const btVector3& value)
{
return stream << "btVector3(" << std::setprecision(std::numeric_limits<float>::max_exponent10) << value.x()
<< ", " << std::setprecision(std::numeric_limits<float>::max_exponent10) << value.y()
<< ", " << std::setprecision(std::numeric_limits<float>::max_exponent10) << value.z()
<< ')';
}
inline std::ostream& operator <<(std::ostream& stream, BroadphaseNativeTypes value)
{
switch (value)
{
#ifndef SHAPE_NAME
#define SHAPE_NAME(name) case name: return stream << #name;
SHAPE_NAME(BOX_SHAPE_PROXYTYPE)
SHAPE_NAME(TRIANGLE_SHAPE_PROXYTYPE)
SHAPE_NAME(TETRAHEDRAL_SHAPE_PROXYTYPE)
SHAPE_NAME(CONVEX_TRIANGLEMESH_SHAPE_PROXYTYPE)
SHAPE_NAME(CONVEX_HULL_SHAPE_PROXYTYPE)
SHAPE_NAME(CONVEX_POINT_CLOUD_SHAPE_PROXYTYPE)
SHAPE_NAME(CUSTOM_POLYHEDRAL_SHAPE_TYPE)
SHAPE_NAME(IMPLICIT_CONVEX_SHAPES_START_HERE)
SHAPE_NAME(SPHERE_SHAPE_PROXYTYPE)
SHAPE_NAME(MULTI_SPHERE_SHAPE_PROXYTYPE)
SHAPE_NAME(CAPSULE_SHAPE_PROXYTYPE)
SHAPE_NAME(CONE_SHAPE_PROXYTYPE)
SHAPE_NAME(CONVEX_SHAPE_PROXYTYPE)
SHAPE_NAME(CYLINDER_SHAPE_PROXYTYPE)
SHAPE_NAME(UNIFORM_SCALING_SHAPE_PROXYTYPE)
SHAPE_NAME(MINKOWSKI_SUM_SHAPE_PROXYTYPE)
SHAPE_NAME(MINKOWSKI_DIFFERENCE_SHAPE_PROXYTYPE)
SHAPE_NAME(BOX_2D_SHAPE_PROXYTYPE)
SHAPE_NAME(CONVEX_2D_SHAPE_PROXYTYPE)
SHAPE_NAME(CUSTOM_CONVEX_SHAPE_TYPE)
SHAPE_NAME(CONCAVE_SHAPES_START_HERE)
SHAPE_NAME(TRIANGLE_MESH_SHAPE_PROXYTYPE)
SHAPE_NAME(SCALED_TRIANGLE_MESH_SHAPE_PROXYTYPE)
SHAPE_NAME(FAST_CONCAVE_MESH_PROXYTYPE)
SHAPE_NAME(TERRAIN_SHAPE_PROXYTYPE)
SHAPE_NAME(GIMPACT_SHAPE_PROXYTYPE)
SHAPE_NAME(MULTIMATERIAL_TRIANGLE_MESH_PROXYTYPE)
SHAPE_NAME(EMPTY_SHAPE_PROXYTYPE)
SHAPE_NAME(STATIC_PLANE_PROXYTYPE)
SHAPE_NAME(CUSTOM_CONCAVE_SHAPE_TYPE)
SHAPE_NAME(CONCAVE_SHAPES_END_HERE)
SHAPE_NAME(COMPOUND_SHAPE_PROXYTYPE)
SHAPE_NAME(SOFTBODY_SHAPE_PROXYTYPE)
SHAPE_NAME(HFFLUID_SHAPE_PROXYTYPE)
SHAPE_NAME(HFFLUID_BUOYANT_CONVEX_SHAPE_PROXYTYPE)
SHAPE_NAME(INVALID_SHAPE_PROXYTYPE)
SHAPE_NAME(MAX_BROADPHASE_COLLISION_TYPES)
#undef SHAPE_NAME
#endif
default:
return stream << "undefined(" << static_cast<int>(value) << ")";
}
}
#endif

View file

@ -320,6 +320,9 @@ namespace Compiler
extensions.registerInstruction ("removefromlevitem", "ccl", opcodeRemoveFromLevItem);
extensions.registerInstruction ("tb", "", opcodeToggleBorders);
extensions.registerInstruction ("toggleborders", "", opcodeToggleBorders);
extensions.registerInstruction ("togglenavmesh", "", opcodeToggleNavMesh);
extensions.registerInstruction ("toggleactorspaths", "", opcodeToggleActorsPaths);
extensions.registerInstruction ("setnavmeshnumber", "l", opcodeSetNavMeshNumberToRender);
}
}

View file

@ -296,6 +296,9 @@ namespace Compiler
const int opcodeShowSceneGraph = 0x2002f;
const int opcodeShowSceneGraphExplicit = 0x20030;
const int opcodeToggleBorders = 0x2000307;
const int opcodeToggleNavMesh = 0x2000308;
const int opcodeToggleActorsPaths = 0x2000309;
const int opcodeSetNavMeshNumberToRender = 0x200030a;
}
namespace Sky

View file

@ -0,0 +1,16 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_AREATYPE_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_AREATYPE_H
#include <Recast.h>
namespace DetourNavigator
{
enum AreaType : unsigned char
{
AreaType_null = RC_NULL_AREA,
AreaType_water,
AreaType_ground = RC_WALKABLE_AREA,
};
}
#endif

View file

@ -0,0 +1,198 @@
#include "asyncnavmeshupdater.hpp"
#include "debug.hpp"
#include "makenavmesh.hpp"
#include "settings.hpp"
#include <components/debug/debuglog.hpp>
#include <iostream>
namespace
{
using DetourNavigator::ChangeType;
using DetourNavigator::TilePosition;
int getManhattanDistance(const TilePosition& lhs, const TilePosition& rhs)
{
return std::abs(lhs.x() - rhs.x()) + std::abs(lhs.y() - rhs.y());
}
std::tuple<ChangeType, int, int> makePriority(const TilePosition& position, const ChangeType changeType,
const TilePosition& playerTile)
{
return std::make_tuple(
changeType,
getManhattanDistance(position, playerTile),
getManhattanDistance(position, TilePosition {0, 0})
);
}
}
namespace DetourNavigator
{
static std::ostream& operator <<(std::ostream& stream, UpdateNavMeshStatus value)
{
switch (value)
{
case UpdateNavMeshStatus::ignore:
return stream << "ignore";
case UpdateNavMeshStatus::removed:
return stream << "removed";
case UpdateNavMeshStatus::add:
return stream << "add";
case UpdateNavMeshStatus::replaced:
return stream << "replaced";
}
return stream << "unknown";
}
AsyncNavMeshUpdater::AsyncNavMeshUpdater(const Settings& settings, TileCachedRecastMeshManager& recastMeshManager,
OffMeshConnectionsManager& offMeshConnectionsManager)
: mSettings(settings)
, mRecastMeshManager(recastMeshManager)
, mOffMeshConnectionsManager(offMeshConnectionsManager)
, mShouldStop()
, mNavMeshTilesCache(settings.mMaxNavMeshTilesCacheSize)
{
for (std::size_t i = 0; i < mSettings.get().mAsyncNavMeshUpdaterThreads; ++i)
mThreads.emplace_back([&] { process(); });
}
AsyncNavMeshUpdater::~AsyncNavMeshUpdater()
{
mShouldStop = true;
std::unique_lock<std::mutex> lock(mMutex);
mJobs = decltype(mJobs)();
mHasJob.notify_all();
lock.unlock();
for (auto& thread : mThreads)
thread.join();
}
void AsyncNavMeshUpdater::post(const osg::Vec3f& agentHalfExtents,
const SharedNavMeshCacheItem& navMeshCacheItem, const TilePosition& playerTile,
const std::map<TilePosition, ChangeType>& changedTiles)
{
*mPlayerTile.lock() = playerTile;
if (changedTiles.empty())
return;
const std::lock_guard<std::mutex> lock(mMutex);
for (const auto& changedTile : changedTiles)
{
if (mPushed[agentHalfExtents].insert(changedTile.first).second)
mJobs.push(Job {agentHalfExtents, navMeshCacheItem, changedTile.first,
makePriority(changedTile.first, changedTile.second, playerTile)});
}
log("posted ", mJobs.size(), " jobs");
mHasJob.notify_all();
}
void AsyncNavMeshUpdater::wait()
{
std::unique_lock<std::mutex> lock(mMutex);
mDone.wait(lock, [&] { return mJobs.empty(); });
}
void AsyncNavMeshUpdater::process() throw()
{
log("start process jobs");
while (!mShouldStop)
{
try
{
if (const auto job = getNextJob())
processJob(*job);
}
catch (const std::exception& e)
{
DetourNavigator::log("AsyncNavMeshUpdater::process exception: ", e.what());
}
}
log("stop process jobs");
}
void AsyncNavMeshUpdater::processJob(const Job& job)
{
log("process job for agent=", job.mAgentHalfExtents);
const auto start = std::chrono::steady_clock::now();
const auto firstStart = setFirstStart(start);
const auto recastMesh = mRecastMeshManager.get().getMesh(job.mChangedTile);
const auto playerTile = *mPlayerTile.lockConst();
const auto offMeshConnections = mOffMeshConnectionsManager.get().get(job.mChangedTile);
const auto status = updateNavMesh(job.mAgentHalfExtents, recastMesh.get(), job.mChangedTile, playerTile,
offMeshConnections, mSettings, job.mNavMeshCacheItem, mNavMeshTilesCache);
const auto finish = std::chrono::steady_clock::now();
writeDebugFiles(job, recastMesh.get());
using FloatMs = std::chrono::duration<float, std::milli>;
const auto locked = job.mNavMeshCacheItem.lockConst();
log("cache updated for agent=", job.mAgentHalfExtents, " status=", status,
" generation=", locked->getGeneration(),
" revision=", locked->getNavMeshRevision(),
" time=", std::chrono::duration_cast<FloatMs>(finish - start).count(), "ms",
" total_time=", std::chrono::duration_cast<FloatMs>(finish - firstStart).count(), "ms");
}
boost::optional<AsyncNavMeshUpdater::Job> AsyncNavMeshUpdater::getNextJob()
{
std::unique_lock<std::mutex> lock(mMutex);
if (mJobs.empty())
mHasJob.wait_for(lock, std::chrono::milliseconds(10));
if (mJobs.empty())
{
mFirstStart.lock()->reset();
mDone.notify_all();
return boost::none;
}
log("got ", mJobs.size(), " jobs");
const auto job = mJobs.top();
mJobs.pop();
const auto pushed = mPushed.find(job.mAgentHalfExtents);
pushed->second.erase(job.mChangedTile);
if (pushed->second.empty())
mPushed.erase(pushed);
return job;
}
void AsyncNavMeshUpdater::writeDebugFiles(const Job& job, const RecastMesh* recastMesh) const
{
std::string revision;
std::string recastMeshRevision;
std::string navMeshRevision;
if ((mSettings.get().mEnableWriteNavMeshToFile || mSettings.get().mEnableWriteRecastMeshToFile)
&& (mSettings.get().mEnableRecastMeshFileNameRevision || mSettings.get().mEnableNavMeshFileNameRevision))
{
revision = "." + std::to_string((std::chrono::steady_clock::now()
- std::chrono::steady_clock::time_point()).count());
if (mSettings.get().mEnableRecastMeshFileNameRevision)
recastMeshRevision = revision;
if (mSettings.get().mEnableNavMeshFileNameRevision)
navMeshRevision = revision;
}
if (recastMesh && mSettings.get().mEnableWriteRecastMeshToFile)
writeToFile(*recastMesh, mSettings.get().mRecastMeshPathPrefix + std::to_string(job.mChangedTile.x())
+ "_" + std::to_string(job.mChangedTile.y()) + "_", recastMeshRevision);
if (mSettings.get().mEnableWriteNavMeshToFile)
writeToFile(job.mNavMeshCacheItem.lockConst()->getValue(), mSettings.get().mNavMeshPathPrefix, navMeshRevision);
}
std::chrono::steady_clock::time_point AsyncNavMeshUpdater::setFirstStart(const std::chrono::steady_clock::time_point& value)
{
const auto locked = mFirstStart.lock();
if (!*locked)
*locked = value;
return *locked.get();
}
}

View file

@ -0,0 +1,88 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_ASYNCNAVMESHUPDATER_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_ASYNCNAVMESHUPDATER_H
#include "navmeshcacheitem.hpp"
#include "offmeshconnectionsmanager.hpp"
#include "tilecachedrecastmeshmanager.hpp"
#include "tileposition.hpp"
#include "navmeshtilescache.hpp"
#include <osg/Vec3f>
#include <boost/optional.hpp>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <memory>
#include <mutex>
#include <queue>
#include <set>
#include <thread>
class dtNavMesh;
namespace DetourNavigator
{
enum class ChangeType
{
remove = 0,
mixed = 1,
add = 2,
};
class AsyncNavMeshUpdater
{
public:
AsyncNavMeshUpdater(const Settings& settings, TileCachedRecastMeshManager& recastMeshManager,
OffMeshConnectionsManager& offMeshConnectionsManager);
~AsyncNavMeshUpdater();
void post(const osg::Vec3f& agentHalfExtents, const SharedNavMeshCacheItem& mNavMeshCacheItem,
const TilePosition& playerTile, const std::map<TilePosition, ChangeType>& changedTiles);
void wait();
private:
struct Job
{
osg::Vec3f mAgentHalfExtents;
SharedNavMeshCacheItem mNavMeshCacheItem;
TilePosition mChangedTile;
std::tuple<ChangeType, int, int> mPriority;
friend inline bool operator <(const Job& lhs, const Job& rhs)
{
return lhs.mPriority > rhs.mPriority;
}
};
using Jobs = std::priority_queue<Job, std::deque<Job>>;
std::reference_wrapper<const Settings> mSettings;
std::reference_wrapper<TileCachedRecastMeshManager> mRecastMeshManager;
std::reference_wrapper<OffMeshConnectionsManager> mOffMeshConnectionsManager;
std::atomic_bool mShouldStop;
std::mutex mMutex;
std::condition_variable mHasJob;
std::condition_variable mDone;
Jobs mJobs;
std::map<osg::Vec3f, std::set<TilePosition>> mPushed;
Misc::ScopeGuarded<TilePosition> mPlayerTile;
Misc::ScopeGuarded<boost::optional<std::chrono::steady_clock::time_point>> mFirstStart;
NavMeshTilesCache mNavMeshTilesCache;
std::vector<std::thread> mThreads;
void process() throw();
void processJob(const Job& job);
boost::optional<Job> getNextJob();
void writeDebugFiles(const Job& job, const RecastMesh* recastMesh) const;
std::chrono::steady_clock::time_point setFirstStart(const std::chrono::steady_clock::time_point& value);
};
}
#endif

View file

@ -0,0 +1,20 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_BOUNDS_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_BOUNDS_H
#include <osg/Vec3f>
namespace DetourNavigator
{
struct Bounds
{
osg::Vec3f mMin;
osg::Vec3f mMax;
};
inline bool isEmpty(const Bounds& value)
{
return value.mMin == value.mMax;
}
}
#endif

View file

@ -0,0 +1,63 @@
#include "cachedrecastmeshmanager.hpp"
#include "debug.hpp"
namespace DetourNavigator
{
CachedRecastMeshManager::CachedRecastMeshManager(const Settings& settings, const TileBounds& bounds)
: mImpl(settings, bounds)
{}
bool CachedRecastMeshManager::addObject(const ObjectId id, const btCollisionShape& shape,
const btTransform& transform, const AreaType areaType)
{
if (!mImpl.addObject(id, shape, transform, areaType))
return false;
mCached.reset();
return true;
}
bool CachedRecastMeshManager::updateObject(const ObjectId id, const btTransform& transform, const AreaType areaType)
{
if (!mImpl.updateObject(id, transform, areaType))
return false;
mCached.reset();
return true;
}
boost::optional<RemovedRecastMeshObject> CachedRecastMeshManager::removeObject(const ObjectId id)
{
const auto object = mImpl.removeObject(id);
if (object)
mCached.reset();
return object;
}
bool CachedRecastMeshManager::addWater(const osg::Vec2i& cellPosition, const int cellSize,
const btTransform& transform)
{
if (!mImpl.addWater(cellPosition, cellSize, transform))
return false;
mCached.reset();
return true;
}
boost::optional<RecastMeshManager::Water> CachedRecastMeshManager::removeWater(const osg::Vec2i& cellPosition)
{
const auto water = mImpl.removeWater(cellPosition);
if (water)
mCached.reset();
return water;
}
std::shared_ptr<RecastMesh> CachedRecastMeshManager::getMesh()
{
if (!mCached)
mCached = mImpl.getMesh();
return mCached;
}
bool CachedRecastMeshManager::isEmpty() const
{
return mImpl.isEmpty();
}
}

View file

@ -0,0 +1,36 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_CACHEDRECASTMESHMANAGER_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_CACHEDRECASTMESHMANAGER_H
#include "recastmeshmanager.hpp"
#include <boost/optional.hpp>
namespace DetourNavigator
{
class CachedRecastMeshManager
{
public:
CachedRecastMeshManager(const Settings& settings, const TileBounds& bounds);
bool addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform,
const AreaType areaType);
bool updateObject(const ObjectId id, const btTransform& transform, const AreaType areaType);
bool addWater(const osg::Vec2i& cellPosition, const int cellSize, const btTransform& transform);
boost::optional<RecastMeshManager::Water> removeWater(const osg::Vec2i& cellPosition);
boost::optional<RemovedRecastMeshObject> removeObject(const ObjectId id);
std::shared_ptr<RecastMesh> getMesh();
bool isEmpty() const;
private:
RecastMeshManager mImpl;
std::shared_ptr<RecastMesh> mCached;
};
}
#endif

View file

@ -0,0 +1,179 @@
#include "chunkytrimesh.hpp"
#include "exceptions.hpp"
#include <osg/Vec2f>
#include <algorithm>
namespace DetourNavigator
{
namespace
{
struct BoundsItem
{
Rect mBounds;
std::ptrdiff_t mOffset;
unsigned char mAreaTypes;
};
template <std::size_t axis>
struct LessBoundsItem
{
bool operator ()(const BoundsItem& lhs, const BoundsItem& rhs) const
{
return lhs.mBounds.mMinBound[axis] < rhs.mBounds.mMinBound[axis];
}
};
void calcExtends(const std::vector<BoundsItem>& items, const std::size_t imin, const std::size_t imax,
Rect& bounds)
{
bounds = items[imin].mBounds;
std::for_each(
items.begin() + static_cast<std::ptrdiff_t>(imin) + 1,
items.begin() + static_cast<std::ptrdiff_t>(imax),
[&] (const BoundsItem& item)
{
for (int i = 0; i < 2; ++i)
{
bounds.mMinBound[i] = std::min(bounds.mMinBound[i], item.mBounds.mMinBound[i]);
bounds.mMaxBound[i] = std::max(bounds.mMaxBound[i], item.mBounds.mMaxBound[i]);
}
});
}
void subdivide(std::vector<BoundsItem>& items, const std::size_t imin, const std::size_t imax,
const std::size_t trisPerChunk, const std::vector<int>& inIndices, const std::vector<AreaType>& inAreaTypes,
std::size_t& curNode, std::vector<ChunkyTriMeshNode>& nodes, std::size_t& curTri,
std::vector<int>& outIndices, std::vector<AreaType>& outAreaTypes)
{
const auto inum = imax - imin;
const auto icur = curNode;
if (curNode > nodes.size())
return;
ChunkyTriMeshNode& node = nodes[curNode++];
if (inum <= trisPerChunk)
{
// Leaf
calcExtends(items, imin, imax, node.mBounds);
// Copy triangles.
node.mOffset = static_cast<std::ptrdiff_t>(curTri);
node.mSize = inum;
for (std::size_t i = imin; i < imax; ++i)
{
std::copy(
inIndices.begin() + items[i].mOffset * 3,
inIndices.begin() + items[i].mOffset * 3 + 3,
outIndices.begin() + static_cast<std::ptrdiff_t>(curTri) * 3
);
outAreaTypes[curTri] = inAreaTypes[static_cast<std::size_t>(items[i].mOffset)];
curTri++;
}
}
else
{
// Split
calcExtends(items, imin, imax, node.mBounds);
if (node.mBounds.mMaxBound.x() - node.mBounds.mMinBound.x()
>= node.mBounds.mMaxBound.y() - node.mBounds.mMinBound.y())
{
// Sort along x-axis
std::sort(
items.begin() + static_cast<std::ptrdiff_t>(imin),
items.begin() + static_cast<std::ptrdiff_t>(imax),
LessBoundsItem<0> {}
);
}
else
{
// Sort along y-axis
std::sort(
items.begin() + static_cast<std::ptrdiff_t>(imin),
items.begin() + static_cast<std::ptrdiff_t>(imax),
LessBoundsItem<1> {}
);
}
const auto isplit = imin + inum / 2;
// Left
subdivide(items, imin, isplit, trisPerChunk, inIndices, inAreaTypes, curNode, nodes, curTri, outIndices, outAreaTypes);
// Right
subdivide(items, isplit, imax, trisPerChunk, inIndices, inAreaTypes, curNode, nodes, curTri, outIndices, outAreaTypes);
const auto iescape = static_cast<std::ptrdiff_t>(curNode) - static_cast<std::ptrdiff_t>(icur);
// Negative index means escape.
node.mOffset = -iescape;
}
}
}
ChunkyTriMesh::ChunkyTriMesh(const std::vector<float>& verts, const std::vector<int>& indices,
const std::vector<AreaType>& flags, const std::size_t trisPerChunk)
: mMaxTrisPerChunk(0)
{
const auto trianglesCount = indices.size() / 3;
if (trianglesCount == 0)
return;
const auto nchunks = (trianglesCount + trisPerChunk - 1) / trisPerChunk;
mNodes.resize(nchunks * 4);
mIndices.resize(trianglesCount * 3);
mAreaTypes.resize(trianglesCount);
// Build tree
std::vector<BoundsItem> items(trianglesCount);
for (std::size_t i = 0; i < trianglesCount; i++)
{
auto& item = items[i];
item.mOffset = static_cast<std::ptrdiff_t>(i);
item.mAreaTypes = flags[i];
// Calc triangle XZ bounds.
const auto baseIndex = static_cast<std::size_t>(indices[i * 3]) * 3;
item.mBounds.mMinBound.x() = item.mBounds.mMaxBound.x() = verts[baseIndex + 0];
item.mBounds.mMinBound.y() = item.mBounds.mMaxBound.y() = verts[baseIndex + 2];
for (std::size_t j = 1; j < 3; ++j)
{
const auto index = static_cast<std::size_t>(indices[i * 3 + j]) * 3;
item.mBounds.mMinBound.x() = std::min(item.mBounds.mMinBound.x(), verts[index + 0]);
item.mBounds.mMinBound.y() = std::min(item.mBounds.mMinBound.y(), verts[index + 2]);
item.mBounds.mMaxBound.x() = std::max(item.mBounds.mMaxBound.x(), verts[index + 0]);
item.mBounds.mMaxBound.y() = std::max(item.mBounds.mMaxBound.y(), verts[index + 2]);
}
}
std::size_t curTri = 0;
std::size_t curNode = 0;
subdivide(items, 0, trianglesCount, trisPerChunk, indices, flags, curNode, mNodes, curTri, mIndices, mAreaTypes);
items.clear();
mNodes.resize(curNode);
// Calc max tris per node.
for (auto& node : mNodes)
{
const bool isLeaf = node.mOffset >= 0;
if (!isLeaf)
continue;
if (node.mSize > mMaxTrisPerChunk)
mMaxTrisPerChunk = node.mSize;
}
}
}

View file

@ -0,0 +1,99 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_CHUNKYTRIMESH_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_CHUNKYTRIMESH_H
#include "areatype.hpp"
#include <osg/Vec2f>
#include <array>
#include <vector>
namespace DetourNavigator
{
struct Rect
{
osg::Vec2f mMinBound;
osg::Vec2f mMaxBound;
};
struct ChunkyTriMeshNode
{
Rect mBounds;
std::ptrdiff_t mOffset;
std::size_t mSize;
};
struct Chunk
{
const int* const mIndices;
const AreaType* const mAreaTypes;
const std::size_t mSize;
};
inline bool checkOverlapRect(const Rect& lhs, const Rect& rhs)
{
bool overlap = true;
overlap = (lhs.mMinBound.x() > rhs.mMaxBound.x() || lhs.mMaxBound.x() < rhs.mMinBound.x()) ? false : overlap;
overlap = (lhs.mMinBound.y() > rhs.mMaxBound.y() || lhs.mMaxBound.y() < rhs.mMinBound.y()) ? false : overlap;
return overlap;
}
class ChunkyTriMesh
{
public:
/// Creates partitioned triangle mesh (AABB tree),
/// where each node contains at max trisPerChunk triangles.
ChunkyTriMesh(const std::vector<float>& verts, const std::vector<int>& tris,
const std::vector<AreaType>& flags, const std::size_t trisPerChunk);
ChunkyTriMesh(const ChunkyTriMesh&) = delete;
ChunkyTriMesh& operator=(const ChunkyTriMesh&) = delete;
/// Returns the chunk indices which overlap the input rectable.
template <class OutputIterator>
void getChunksOverlappingRect(const Rect& rect, OutputIterator out) const
{
// Traverse tree
for (std::size_t i = 0; i < mNodes.size(); )
{
const ChunkyTriMeshNode* node = &mNodes[i];
const bool overlap = checkOverlapRect(rect, node->mBounds);
const bool isLeafNode = node->mOffset >= 0;
if (isLeafNode && overlap)
*out++ = i;
if (overlap || isLeafNode)
i++;
else
{
const auto escapeIndex = -node->mOffset;
i += static_cast<std::size_t>(escapeIndex);
}
}
}
Chunk getChunk(const std::size_t chunkId) const
{
const auto& node = mNodes[chunkId];
return Chunk {
mIndices.data() + node.mOffset * 3,
mAreaTypes.data() + node.mOffset,
node.mSize
};
}
std::size_t getMaxTrisPerChunk() const
{
return mMaxTrisPerChunk;
}
private:
std::vector<ChunkyTriMeshNode> mNodes;
std::vector<int> mIndices;
std::vector<AreaType> mAreaTypes;
std::size_t mMaxTrisPerChunk;
};
}
#endif

View file

@ -0,0 +1,102 @@
#include "debug.hpp"
#include "exceptions.hpp"
#include "recastmesh.hpp"
#include <DetourNavMesh.h>
#include <fstream>
namespace DetourNavigator
{
void writeToFile(const RecastMesh& recastMesh, const std::string& pathPrefix, const std::string& revision)
{
const auto path = pathPrefix + "recastmesh" + revision + ".obj";
std::ofstream file(path);
if (!file.is_open())
throw NavigatorException("Open file failed: " + path);
file.exceptions(std::ios::failbit | std::ios::badbit);
file.precision(std::numeric_limits<float>::max_exponent10);
std::size_t count = 0;
for (auto v : recastMesh.getVertices())
{
if (count % 3 == 0)
{
if (count != 0)
file << '\n';
file << 'v';
}
file << ' ' << v;
++count;
}
file << '\n';
count = 0;
for (auto v : recastMesh.getIndices())
{
if (count % 3 == 0)
{
if (count != 0)
file << '\n';
file << 'f';
}
file << ' ' << (v + 1);
++count;
}
file << '\n';
}
void writeToFile(const dtNavMesh& navMesh, const std::string& pathPrefix, const std::string& revision)
{
const int navMeshSetMagic = 'M' << 24 | 'S' << 16 | 'E' << 8 | 'T'; //'MSET';
const int navMeshSetVersion = 1;
struct NavMeshSetHeader
{
int magic;
int version;
int numTiles;
dtNavMeshParams params;
};
struct NavMeshTileHeader
{
dtTileRef tileRef;
int dataSize;
};
const auto path = pathPrefix + "all_tiles_navmesh" + revision + ".bin";
std::ofstream file(path, std::ios::binary);
if (!file.is_open())
throw NavigatorException("Open file failed: " + path);
file.exceptions(std::ios::failbit | std::ios::badbit);
NavMeshSetHeader header;
header.magic = navMeshSetMagic;
header.version = navMeshSetVersion;
header.numTiles = 0;
for (int i = 0; i < navMesh.getMaxTiles(); ++i)
{
const auto tile = navMesh.getTile(i);
if (!tile || !tile->header || !tile->dataSize)
continue;
header.numTiles++;
}
header.params = *navMesh.getParams();
using const_char_ptr = const char*;
file.write(const_char_ptr(&header), sizeof(header));
for (int i = 0; i < navMesh.getMaxTiles(); ++i)
{
const auto tile = navMesh.getTile(i);
if (!tile || !tile->header || !tile->dataSize)
continue;
NavMeshTileHeader tileHeader;
tileHeader.tileRef = navMesh.getTileRef(tile);
tileHeader.dataSize = tile->dataSize;
file.write(const_char_ptr(&tileHeader), sizeof(tileHeader));
file.write(const_char_ptr(tile->data), tile->dataSize);
}
}
}

View file

@ -0,0 +1,133 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_DEBUG_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_DEBUG_H
#include "tilebounds.hpp"
#include <components/bullethelpers/operators.hpp>
#include <components/misc/guarded.hpp>
#include <components/osghelpers/operators.hpp>
#include <chrono>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <memory>
#include <sstream>
#include <string>
#include <thread>
class dtNavMesh;
namespace DetourNavigator
{
inline std::ostream& operator <<(std::ostream& stream, const TileBounds& value)
{
return stream << "TileBounds {" << value.mMin << ", " << value.mMax << "}";
}
class RecastMesh;
inline std::ostream& operator <<(std::ostream& stream, const std::chrono::steady_clock::time_point& value)
{
using float_s = std::chrono::duration<float, std::ratio<1>>;
return stream << std::fixed << std::setprecision(4)
<< std::chrono::duration_cast<float_s>(value.time_since_epoch()).count();
}
struct Sink
{
virtual ~Sink() = default;
virtual void write(const std::string& text) = 0;
};
class FileSink final : public Sink
{
public:
FileSink(std::string path)
: mPath(std::move(path))
{
mFile.exceptions(std::ios::failbit | std::ios::badbit);
}
void write(const std::string& text) override
{
if (!mFile.is_open())
{
mFile.open(mPath);
}
mFile << text << std::flush;
}
private:
std::string mPath;
std::ofstream mFile;
};
class StdoutSink final : public Sink
{
public:
void write(const std::string& text) override
{
std::cout << text << std::flush;
}
};
class Log
{
public:
void setSink(std::unique_ptr<Sink> sink)
{
*mSink.lock() = std::move(sink);
}
bool isEnabled()
{
return bool(*mSink.lockConst());
}
void write(const std::string& text)
{
const auto sink = mSink.lock();
if (*sink)
sink.get()->write(text);
}
static Log& instance()
{
static Log value;
return value;
}
private:
Misc::ScopeGuarded<std::unique_ptr<Sink>> mSink;
};
inline void write(std::ostream& stream)
{
stream << '\n';
}
template <class Head, class ... Tail>
void write(std::ostream& stream, const Head& head, const Tail& ... tail)
{
stream << head;
write(stream, tail ...);
}
template <class ... Ts>
void log(Ts&& ... values)
{
auto& log = Log::instance();
if (!log.isEnabled())
return;
std::ostringstream stream;
stream << '[' << std::chrono::steady_clock::now() << "] [" << std::this_thread::get_id() << "] ";
write(stream, std::forward<Ts>(values) ...);
log.write(stream.str());
}
void writeToFile(const RecastMesh& recastMesh, const std::string& pathPrefix, const std::string& revision);
void writeToFile(const dtNavMesh& navMesh, const std::string& pathPrefix, const std::string& revision);
}
#endif

View file

@ -0,0 +1,38 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_DTSTATUS_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_DTSTATUS_H
#include <DetourStatus.h>
#include <sstream>
#include <vector>
namespace DetourNavigator
{
struct WriteDtStatus
{
dtStatus status;
};
static const std::vector<std::pair<const dtStatus, const char* const>> dtStatuses {
{DT_FAILURE, "DT_FAILURE"},
{DT_SUCCESS, "DT_SUCCESS"},
{DT_IN_PROGRESS, "DT_IN_PROGRESS"},
{DT_WRONG_MAGIC, "DT_WRONG_MAGIC"},
{DT_WRONG_VERSION, "DT_WRONG_VERSION"},
{DT_OUT_OF_MEMORY, "DT_OUT_OF_MEMORY"},
{DT_INVALID_PARAM, "DT_INVALID_PARAM"},
{DT_BUFFER_TOO_SMALL, "DT_BUFFER_TOO_SMALL"},
{DT_OUT_OF_NODES, "DT_OUT_OF_NODES"},
{DT_PARTIAL_RESULT, "DT_PARTIAL_RESULT"},
};
inline std::ostream& operator <<(std::ostream& stream, const WriteDtStatus& value)
{
for (const auto& status : dtStatuses)
if (value.status & status.first)
stream << status.second << " ";
return stream;
}
}
#endif

View file

@ -0,0 +1,21 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_EXCEPTIONS_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_EXCEPTIONS_H
#include <stdexcept>
namespace DetourNavigator
{
struct NavigatorException : std::runtime_error
{
NavigatorException(const std::string& message) : std::runtime_error(message) {}
NavigatorException(const char* message) : std::runtime_error(message) {}
};
struct InvalidArgument : std::invalid_argument
{
InvalidArgument(const std::string& message) : std::invalid_argument(message) {}
InvalidArgument(const char* message) : std::invalid_argument(message) {}
};
}
#endif

View file

@ -0,0 +1,143 @@
#include "findsmoothpath.hpp"
#include <algorithm>
#include <array>
namespace DetourNavigator
{
std::vector<dtPolyRef> fixupCorridor(const std::vector<dtPolyRef>& path, const std::vector<dtPolyRef>& visited)
{
std::vector<dtPolyRef>::const_reverse_iterator furthestVisited;
// Find furthest common polygon.
const auto it = std::find_if(path.rbegin(), path.rend(), [&] (dtPolyRef pathValue)
{
const auto it = std::find(visited.rbegin(), visited.rend(), pathValue);
if (it == visited.rend())
return false;
furthestVisited = it;
return true;
});
// If no intersection found just return current path.
if (it == path.rend())
return path;
const auto furthestPath = it.base() - 1;
// Concatenate paths.
// visited: a_1 ... a_n x b_1 ... b_n
// furthestVisited ^
// path: C x D
// ^ furthestPath
// result: x b_n ... b_1 D
std::vector<dtPolyRef> result;
result.reserve(static_cast<std::size_t>(furthestVisited - visited.rbegin())
+ static_cast<std::size_t>(path.end() - furthestPath) - 1);
std::copy(visited.rbegin(), furthestVisited + 1, std::back_inserter(result));
std::copy(furthestPath + 1, path.end(), std::back_inserter(result));
return result;
}
// This function checks if the path has a small U-turn, that is,
// a polygon further in the path is adjacent to the first polygon
// in the path. If that happens, a shortcut is taken.
// This can happen if the target (T) location is at tile boundary,
// and we're (S) approaching it parallel to the tile edge.
// The choice at the vertex can be arbitrary,
// +---+---+
// |:::|:::|
// +-S-+-T-+
// |:::| | <-- the step can end up in here, resulting U-turn path.
// +---+---+
std::vector<dtPolyRef> fixupShortcuts(const std::vector<dtPolyRef>& path, const dtNavMeshQuery& navQuery)
{
if (path.size() < 3)
return path;
// Get connected polygons
const dtMeshTile* tile = 0;
const dtPoly* poly = 0;
if (dtStatusFailed(navQuery.getAttachedNavMesh()->getTileAndPolyByRef(path[0], &tile, &poly)))
return path;
const std::size_t maxNeis = 16;
std::array<dtPolyRef, maxNeis> neis;
std::size_t nneis = 0;
for (unsigned int k = poly->firstLink; k != DT_NULL_LINK; k = tile->links[k].next)
{
const dtLink* link = &tile->links[k];
if (link->ref != 0)
{
if (nneis < maxNeis)
neis[nneis++] = link->ref;
}
}
// If any of the neighbour polygons is within the next few polygons
// in the path, short cut to that polygon directly.
const std::size_t maxLookAhead = 6;
std::size_t cut = 0;
for (std::size_t i = std::min(maxLookAhead, path.size()) - 1; i > 1 && cut == 0; i--)
{
for (std::size_t j = 0; j < nneis; j++)
{
if (path[i] == neis[j])
{
cut = i;
break;
}
}
}
if (cut <= 1)
return path;
std::vector<dtPolyRef> result;
const auto offset = cut - 1;
result.reserve(1 + path.size() - offset);
result.push_back(path.front());
std::copy(path.begin() + std::ptrdiff_t(offset), path.end(), std::back_inserter(result));
return result;
}
boost::optional<SteerTarget> getSteerTarget(const dtNavMeshQuery& navQuery, const osg::Vec3f& startPos,
const osg::Vec3f& endPos, const float minTargetDist, const std::vector<dtPolyRef>& path)
{
// Find steer target.
SteerTarget result;
const int MAX_STEER_POINTS = 3;
std::array<float, MAX_STEER_POINTS * 3> steerPath;
std::array<unsigned char, MAX_STEER_POINTS> steerPathFlags;
std::array<dtPolyRef, MAX_STEER_POINTS> steerPathPolys;
int nsteerPath = 0;
navQuery.findStraightPath(startPos.ptr(), endPos.ptr(), path.data(), int(path.size()), steerPath.data(),
steerPathFlags.data(), steerPathPolys.data(), &nsteerPath, MAX_STEER_POINTS);
assert(nsteerPath >= 0);
if (!nsteerPath)
return boost::none;
// Find vertex far enough to steer to.
std::size_t ns = 0;
while (ns < static_cast<std::size_t>(nsteerPath))
{
// Stop at Off-Mesh link or when point is further than slop away.
if ((steerPathFlags[ns] & DT_STRAIGHTPATH_OFFMESH_CONNECTION) ||
!inRange(makeOsgVec3f(&steerPath[ns * 3]), startPos, minTargetDist, 1000.0f))
break;
ns++;
}
// Failed to find good point to steer to.
if (ns >= static_cast<std::size_t>(nsteerPath))
return boost::none;
dtVcopy(result.steerPos.ptr(), &steerPath[ns * 3]);
result.steerPos.y() = startPos[1];
result.steerPosFlag = steerPathFlags[ns];
result.steerPosRef = steerPathPolys[ns];
return result;
}
}

View file

@ -0,0 +1,326 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_FINDSMOOTHPATH_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_FINDSMOOTHPATH_H
#include "dtstatus.hpp"
#include "exceptions.hpp"
#include "flags.hpp"
#include "settings.hpp"
#include "settingsutils.hpp"
#include "debug.hpp"
#include <DetourCommon.h>
#include <DetourNavMesh.h>
#include <DetourNavMeshQuery.h>
#include <LinearMath/btVector3.h>
#include <boost/optional.hpp>
#include <osg/Vec3f>
#include <vector>
class dtNavMesh;
namespace DetourNavigator
{
struct Settings;
inline osg::Vec3f makeOsgVec3f(const float* values)
{
return osg::Vec3f(values[0], values[1], values[2]);
}
inline osg::Vec3f makeOsgVec3f(const btVector3& value)
{
return osg::Vec3f(value.x(), value.y(), value.z());
}
inline bool inRange(const osg::Vec3f& v1, const osg::Vec3f& v2, const float r, const float h)
{
const auto d = v2 - v1;
return (d.x() * d.x() + d.z() * d.z()) < r * r && std::abs(d.y()) < h;
}
std::vector<dtPolyRef> fixupCorridor(const std::vector<dtPolyRef>& path, const std::vector<dtPolyRef>& visited);
// This function checks if the path has a small U-turn, that is,
// a polygon further in the path is adjacent to the first polygon
// in the path. If that happens, a shortcut is taken.
// This can happen if the target (T) location is at tile boundary,
// and we're (S) approaching it parallel to the tile edge.
// The choice at the vertex can be arbitrary,
// +---+---+
// |:::|:::|
// +-S-+-T-+
// |:::| | <-- the step can end up in here, resulting U-turn path.
// +---+---+
std::vector<dtPolyRef> fixupShortcuts(const std::vector<dtPolyRef>& path, const dtNavMeshQuery& navQuery);
struct SteerTarget
{
osg::Vec3f steerPos;
unsigned char steerPosFlag;
dtPolyRef steerPosRef;
};
boost::optional<SteerTarget> getSteerTarget(const dtNavMeshQuery& navQuery, const osg::Vec3f& startPos,
const osg::Vec3f& endPos, const float minTargetDist, const std::vector<dtPolyRef>& path);
template <class OutputIterator>
class OutputTransformIterator
{
public:
OutputTransformIterator(OutputIterator& impl, const Settings& settings)
: mImpl(impl), mSettings(settings)
{
}
OutputTransformIterator& operator *()
{
return *this;
}
OutputTransformIterator& operator ++(int)
{
mImpl++;
return *this;
}
OutputTransformIterator& operator =(const osg::Vec3f& value)
{
*mImpl = fromNavMeshCoordinates(mSettings, value);
return *this;
}
private:
OutputIterator& mImpl;
const Settings& mSettings;
};
inline void initNavMeshQuery(dtNavMeshQuery& value, const dtNavMesh& navMesh, const int maxNodes)
{
const auto status = value.init(&navMesh, maxNodes);
if (!dtStatusSucceed(status))
throw NavigatorException("Failed to init navmesh query");
}
struct MoveAlongSurfaceResult
{
osg::Vec3f mResultPos;
std::vector<dtPolyRef> mVisited;
};
inline MoveAlongSurfaceResult moveAlongSurface(const dtNavMeshQuery& navMeshQuery, const dtPolyRef startRef,
const osg::Vec3f& startPos, const osg::Vec3f& endPos, const dtQueryFilter& filter,
const std::size_t maxVisitedSize)
{
MoveAlongSurfaceResult result;
result.mVisited.resize(maxVisitedSize);
int visitedNumber = 0;
const auto status = navMeshQuery.moveAlongSurface(startRef, startPos.ptr(), endPos.ptr(),
&filter, result.mResultPos.ptr(), result.mVisited.data(), &visitedNumber, static_cast<int>(maxVisitedSize));
if (!dtStatusSucceed(status))
{
std::ostringstream message;
message << "Failed to move along surface from " << startPos << " to " << endPos;
throw NavigatorException(message.str());
}
assert(visitedNumber >= 0);
assert(visitedNumber <= static_cast<int>(maxVisitedSize));
result.mVisited.resize(static_cast<std::size_t>(visitedNumber));
return result;
}
inline std::vector<dtPolyRef> findPath(const dtNavMeshQuery& navMeshQuery, const dtPolyRef startRef,
const dtPolyRef endRef, const osg::Vec3f& startPos, const osg::Vec3f& endPos, const dtQueryFilter& queryFilter,
const std::size_t maxSize)
{
int pathLen = 0;
std::vector<dtPolyRef> result(maxSize);
const auto status = navMeshQuery.findPath(startRef, endRef, startPos.ptr(), endPos.ptr(), &queryFilter,
result.data(), &pathLen, static_cast<int>(maxSize));
if (!dtStatusSucceed(status))
{
std::ostringstream message;
message << "Failed to find path over polygons from " << startRef << " to " << endRef;
throw NavigatorException(message.str());
}
assert(pathLen >= 0);
assert(static_cast<std::size_t>(pathLen) <= maxSize);
result.resize(static_cast<std::size_t>(pathLen));
return result;
}
inline float getPolyHeight(const dtNavMeshQuery& navMeshQuery, const dtPolyRef ref, const osg::Vec3f& pos)
{
float result = 0.0f;
const auto status = navMeshQuery.getPolyHeight(ref, pos.ptr(), &result);
if (!dtStatusSucceed(status))
{
std::ostringstream message;
message << "Failed to get polygon height ref=" << ref << " pos=" << pos;
throw NavigatorException(message.str());
}
return result;
}
template <class OutputIterator>
OutputIterator makeSmoothPath(const dtNavMesh& navMesh, const dtNavMeshQuery& navMeshQuery,
const dtQueryFilter& filter, const osg::Vec3f& start, const osg::Vec3f& end,
std::vector<dtPolyRef> polygonPath, std::size_t maxSmoothPathSize, OutputIterator out)
{
// Iterate over the path to find smooth path on the detail mesh surface.
osg::Vec3f iterPos;
navMeshQuery.closestPointOnPoly(polygonPath.front(), start.ptr(), iterPos.ptr(), 0);
osg::Vec3f targetPos;
navMeshQuery.closestPointOnPoly(polygonPath.back(), end.ptr(), targetPos.ptr(), 0);
const float STEP_SIZE = 0.5f;
const float SLOP = 0.01f;
*out++ = iterPos;
std::size_t smoothPathSize = 1;
// Move towards target a small advancement at a time until target reached or
// when ran out of memory to store the path.
while (!polygonPath.empty() && smoothPathSize < maxSmoothPathSize)
{
// Find location to steer towards.
const auto steerTarget = getSteerTarget(navMeshQuery, iterPos, targetPos, SLOP, polygonPath);
if (!steerTarget)
break;
const bool endOfPath = bool(steerTarget->steerPosFlag & DT_STRAIGHTPATH_END);
const bool offMeshConnection = bool(steerTarget->steerPosFlag & DT_STRAIGHTPATH_OFFMESH_CONNECTION);
// Find movement delta.
const osg::Vec3f delta = steerTarget->steerPos - iterPos;
float len = delta.length();
// If the steer target is end of path or off-mesh link, do not move past the location.
if ((endOfPath || offMeshConnection) && len < STEP_SIZE)
len = 1;
else
len = STEP_SIZE / len;
const osg::Vec3f moveTgt = iterPos + delta * len;
const auto result = moveAlongSurface(navMeshQuery, polygonPath.front(), iterPos, moveTgt, filter, 16);
polygonPath = fixupCorridor(polygonPath, result.mVisited);
polygonPath = fixupShortcuts(polygonPath, navMeshQuery);
float h = 0;
navMeshQuery.getPolyHeight(polygonPath.front(), result.mResultPos.ptr(), &h);
iterPos = result.mResultPos;
iterPos.y() = h;
// Handle end of path and off-mesh links when close enough.
if (endOfPath && inRange(iterPos, steerTarget->steerPos, SLOP, 1.0f))
{
// Reached end of path.
iterPos = targetPos;
*out++ = iterPos;
++smoothPathSize;
break;
}
else if (offMeshConnection && inRange(iterPos, steerTarget->steerPos, SLOP, 1.0f))
{
// Advance the path up to and over the off-mesh connection.
dtPolyRef prevRef = 0;
dtPolyRef polyRef = polygonPath.front();
std::size_t npos = 0;
while (npos < polygonPath.size() && polyRef != steerTarget->steerPosRef)
{
prevRef = polyRef;
polyRef = polygonPath[npos];
++npos;
}
std::copy(polygonPath.begin() + std::ptrdiff_t(npos), polygonPath.end(), polygonPath.begin());
polygonPath.resize(polygonPath.size() - npos);
// Reached off-mesh connection.
osg::Vec3f startPos;
osg::Vec3f endPos;
// Handle the connection.
if (dtStatusSucceed(navMesh.getOffMeshConnectionPolyEndPoints(prevRef, polyRef,
startPos.ptr(), endPos.ptr())))
{
*out++ = startPos;
++smoothPathSize;
// Hack to make the dotted path not visible during off-mesh connection.
if (smoothPathSize & 1)
{
*out++ = startPos;
++smoothPathSize;
}
// Move position at the other side of the off-mesh link.
iterPos = endPos;
iterPos.y() = getPolyHeight(navMeshQuery, polygonPath.front(), iterPos);
}
}
// Store results.
*out++ = iterPos;
++smoothPathSize;
}
return out;
}
template <class OutputIterator>
OutputIterator findSmoothPath(const dtNavMesh& navMesh, const osg::Vec3f& halfExtents,
const osg::Vec3f& start, const osg::Vec3f& end, const Flags includeFlags,
const Settings& settings, OutputIterator out)
{
dtNavMeshQuery navMeshQuery;
initNavMeshQuery(navMeshQuery, navMesh, settings.mMaxNavMeshQueryNodes);
dtQueryFilter queryFilter;
queryFilter.setIncludeFlags(includeFlags);
dtPolyRef startRef = 0;
osg::Vec3f startPolygonPosition;
for (int i = 0; i < 3; ++i)
{
const auto status = navMeshQuery.findNearestPoly(start.ptr(), (halfExtents * (1 << i)).ptr(), &queryFilter,
&startRef, startPolygonPosition.ptr());
if (!dtStatusFailed(status) && startRef != 0)
break;
}
if (startRef == 0)
throw NavigatorException("Navmesh polygon for start point is not found");
dtPolyRef endRef = 0;
osg::Vec3f endPolygonPosition;
for (int i = 0; i < 3; ++i)
{
const auto status = navMeshQuery.findNearestPoly(end.ptr(), (halfExtents * (1 << i)).ptr(), &queryFilter,
&endRef, endPolygonPosition.ptr());
if (!dtStatusFailed(status) && endRef != 0)
break;
}
if (endRef == 0)
throw NavigatorException("Navmesh polygon for end polygon is not found");
const auto polygonPath = findPath(navMeshQuery, startRef, endRef, start, end, queryFilter,
settings.mMaxPolygonPathSize);
if (polygonPath.empty() || polygonPath.back() != endRef)
return out;
makeSmoothPath(navMesh, navMeshQuery, queryFilter, start, end, std::move(polygonPath),
settings.mMaxSmoothPathSize, OutputTransformIterator<OutputIterator>(out, settings));
return out;
}
}
#endif

View file

@ -0,0 +1,62 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_FLAGS_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_FLAGS_H
#include <ostream>
namespace DetourNavigator
{
using Flags = unsigned short;
enum Flag : Flags
{
Flag_none = 0,
Flag_walk = 1 << 0,
Flag_swim = 1 << 1,
Flag_openDoor = 1 << 2,
};
inline std::ostream& operator <<(std::ostream& stream, const Flag value)
{
switch (value) {
case Flag_none:
return stream << "none";
case Flag_walk:
return stream << "walk";
case Flag_swim:
return stream << "swim";
case Flag_openDoor:
return stream << "openDoor";
}
}
struct WriteFlags
{
Flags mValue;
friend inline std::ostream& operator <<(std::ostream& stream, const WriteFlags& value)
{
if (value.mValue == Flag_none)
{
return stream << Flag_none;
}
else
{
bool first = true;
for (const auto flag : {Flag_walk, Flag_swim, Flag_openDoor})
{
if (value.mValue & flag)
{
if (!first)
stream << " | ";
first = false;
stream << flag;
}
}
return stream;
}
}
};
}
#endif

View file

@ -0,0 +1,73 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_GETTILESPOSITIONS_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_GETTILESPOSITIONS_H
#include "settings.hpp"
#include "settingsutils.hpp"
#include "tileposition.hpp"
#include <BulletCollision/CollisionShapes/btCollisionShape.h>
#include <osg/Vec3f>
namespace DetourNavigator
{
inline osg::Vec3f makeOsgVec3f(const btVector3& value)
{
return osg::Vec3f(value.x(), value.y(), value.z());
}
template <class Callback>
void getTilesPositions(const osg::Vec3f& aabbMin, const osg::Vec3f& aabbMax,
const Settings& settings, Callback&& callback)
{
auto min = toNavMeshCoordinates(settings, aabbMin);
auto max = toNavMeshCoordinates(settings, aabbMax);
const auto border = getBorderSize(settings);
min -= osg::Vec3f(border, border, border);
max += osg::Vec3f(border, border, border);
auto minTile = getTilePosition(settings, min);
auto maxTile = getTilePosition(settings, max);
if (minTile.x() > maxTile.x())
std::swap(minTile.x(), maxTile.x());
if (minTile.y() > maxTile.y())
std::swap(minTile.y(), maxTile.y());
for (int tileX = minTile.x(); tileX <= maxTile.x(); ++tileX)
for (int tileY = minTile.y(); tileY <= maxTile.y(); ++tileY)
callback(TilePosition {tileX, tileY});
}
template <class Callback>
void getTilesPositions(const btCollisionShape& shape, const btTransform& transform,
const Settings& settings, Callback&& callback)
{
btVector3 aabbMin;
btVector3 aabbMax;
shape.getAabb(transform, aabbMin, aabbMax);
getTilesPositions(makeOsgVec3f(aabbMin), makeOsgVec3f(aabbMax), settings, std::forward<Callback>(callback));
}
template <class Callback>
void getTilesPositions(const int cellSize, const btTransform& transform,
const Settings& settings, Callback&& callback)
{
const auto halfCellSize = cellSize / 2;
auto aabbMin = transform(btVector3(-halfCellSize, -halfCellSize, 0));
auto aabbMax = transform(btVector3(halfCellSize, halfCellSize, 0));
aabbMin.setX(std::min(aabbMin.x(), aabbMax.x()));
aabbMin.setY(std::min(aabbMin.y(), aabbMax.y()));
aabbMax.setX(std::max(aabbMin.x(), aabbMax.x()));
aabbMax.setY(std::max(aabbMin.y(), aabbMax.y()));
getTilesPositions(makeOsgVec3f(aabbMin), makeOsgVec3f(aabbMax), settings, std::forward<Callback>(callback));
}
}
#endif

View file

@ -0,0 +1,633 @@
#include "makenavmesh.hpp"
#include "chunkytrimesh.hpp"
#include "debug.hpp"
#include "dtstatus.hpp"
#include "exceptions.hpp"
#include "recastmesh.hpp"
#include "settings.hpp"
#include "settingsutils.hpp"
#include "sharednavmesh.hpp"
#include "settingsutils.hpp"
#include "flags.hpp"
#include "navmeshtilescache.hpp"
#include <DetourNavMesh.h>
#include <DetourNavMeshBuilder.h>
#include <Recast.h>
#include <RecastAlloc.h>
#include <algorithm>
#include <iomanip>
#include <limits>
namespace
{
using namespace DetourNavigator;
static const int doNotTransferOwnership = 0;
void initPolyMeshDetail(rcPolyMeshDetail& value)
{
value.meshes = nullptr;
value.verts = nullptr;
value.tris = nullptr;
}
struct PolyMeshDetailStackDeleter
{
void operator ()(rcPolyMeshDetail* value) const
{
rcFree(value->meshes);
rcFree(value->verts);
rcFree(value->tris);
}
};
using PolyMeshDetailStackPtr = std::unique_ptr<rcPolyMeshDetail, PolyMeshDetailStackDeleter>;
osg::Vec3f makeOsgVec3f(const btVector3& value)
{
return osg::Vec3f(value.x(), value.y(), value.z());
}
struct WaterBounds
{
osg::Vec3f mMin;
osg::Vec3f mMax;
};
WaterBounds getWaterBounds(const RecastMesh::Water& water, const Settings& settings,
const osg::Vec3f& agentHalfExtents)
{
if (water.mCellSize == std::numeric_limits<int>::max())
{
const auto transform = getSwimLevelTransform(settings, water.mTransform, agentHalfExtents.z());
const auto min = toNavMeshCoordinates(settings, makeOsgVec3f(transform(btVector3(-1, -1, 0))));
const auto max = toNavMeshCoordinates(settings, makeOsgVec3f(transform(btVector3(1, 1, 0))));
return WaterBounds {
osg::Vec3f(-std::numeric_limits<float>::max(), min.y(), -std::numeric_limits<float>::max()),
osg::Vec3f(std::numeric_limits<float>::max(), max.y(), std::numeric_limits<float>::max())
};
}
else
{
const auto transform = getSwimLevelTransform(settings, water.mTransform, agentHalfExtents.z());
const auto halfCellSize = water.mCellSize / 2.0f;
return WaterBounds {
toNavMeshCoordinates(settings, makeOsgVec3f(transform(btVector3(-halfCellSize, -halfCellSize, 0)))),
toNavMeshCoordinates(settings, makeOsgVec3f(transform(btVector3(halfCellSize, halfCellSize, 0))))
};
}
}
std::vector<float> getOffMeshVerts(const std::vector<OffMeshConnection>& connections)
{
std::vector<float> result;
result.reserve(connections.size() * 6);
const auto add = [&] (const osg::Vec3f& v)
{
result.push_back(v.x());
result.push_back(v.y());
result.push_back(v.z());
};
for (const auto& v : connections)
{
add(v.mStart);
add(v.mEnd);
}
return result;
}
rcConfig makeConfig(const osg::Vec3f& agentHalfExtents, const osg::Vec3f& boundsMin, const osg::Vec3f& boundsMax,
const Settings& settings)
{
rcConfig config;
config.cs = settings.mCellSize;
config.ch = settings.mCellHeight;
config.walkableSlopeAngle = settings.mMaxSlope;
config.walkableHeight = static_cast<int>(std::ceil(getHeight(settings, agentHalfExtents) / config.ch));
config.walkableClimb = static_cast<int>(std::floor(getMaxClimb(settings) / config.ch));
config.walkableRadius = static_cast<int>(std::ceil(getRadius(settings, agentHalfExtents) / config.cs));
config.maxEdgeLen = static_cast<int>(std::round(settings.mMaxEdgeLen / config.cs));
config.maxSimplificationError = settings.mMaxSimplificationError;
config.minRegionArea = settings.mRegionMinSize * settings.mRegionMinSize;
config.mergeRegionArea = settings.mRegionMergeSize * settings.mRegionMergeSize;
config.maxVertsPerPoly = settings.mMaxVertsPerPoly;
config.detailSampleDist = settings.mDetailSampleDist < 0.9f ? 0 : config.cs * settings.mDetailSampleDist;
config.detailSampleMaxError = config.ch * settings.mDetailSampleMaxError;
config.borderSize = settings.mBorderSize;
config.width = settings.mTileSize + config.borderSize * 2;
config.height = settings.mTileSize + config.borderSize * 2;
rcVcopy(config.bmin, boundsMin.ptr());
rcVcopy(config.bmax, boundsMax.ptr());
config.bmin[0] -= getBorderSize(settings);
config.bmin[2] -= getBorderSize(settings);
config.bmax[0] += getBorderSize(settings);
config.bmax[2] += getBorderSize(settings);
return config;
}
void createHeightfield(rcContext& context, rcHeightfield& solid, int width, int height, const float* bmin,
const float* bmax, const float cs, const float ch)
{
const auto result = rcCreateHeightfield(&context, solid, width, height, bmin, bmax, cs, ch);
if (!result)
throw NavigatorException("Failed to create heightfield for navmesh");
}
bool rasterizeSolidObjectsTriangles(rcContext& context, const RecastMesh& recastMesh, const rcConfig& config,
rcHeightfield& solid)
{
const auto& chunkyMesh = recastMesh.getChunkyTriMesh();
std::vector<unsigned char> areas(chunkyMesh.getMaxTrisPerChunk(), AreaType_null);
const osg::Vec2f tileBoundsMin(config.bmin[0], config.bmin[2]);
const osg::Vec2f tileBoundsMax(config.bmax[0], config.bmax[2]);
std::vector<std::size_t> cids;
chunkyMesh.getChunksOverlappingRect(Rect {tileBoundsMin, tileBoundsMax}, std::back_inserter(cids));
if (cids.empty())
return false;
for (const auto cid : cids)
{
const auto chunk = chunkyMesh.getChunk(cid);
std::fill(
areas.begin(),
std::min(areas.begin() + static_cast<std::ptrdiff_t>(chunk.mSize),
areas.end()),
AreaType_null
);
rcMarkWalkableTriangles(
&context,
config.walkableSlopeAngle,
recastMesh.getVertices().data(),
static_cast<int>(recastMesh.getVerticesCount()),
chunk.mIndices,
static_cast<int>(chunk.mSize),
areas.data()
);
for (std::size_t i = 0; i < chunk.mSize; ++i)
areas[i] = chunk.mAreaTypes[i];
rcClearUnwalkableTriangles(
&context,
config.walkableSlopeAngle,
recastMesh.getVertices().data(),
static_cast<int>(recastMesh.getVerticesCount()),
chunk.mIndices,
static_cast<int>(chunk.mSize),
areas.data()
);
const auto trianglesRasterized = rcRasterizeTriangles(
&context,
recastMesh.getVertices().data(),
static_cast<int>(recastMesh.getVerticesCount()),
chunk.mIndices,
areas.data(),
static_cast<int>(chunk.mSize),
solid,
config.walkableClimb
);
if (!trianglesRasterized)
throw NavigatorException("Failed to create rasterize triangles from recast mesh for navmesh");
}
return true;
}
void rasterizeWaterTriangles(rcContext& context, const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh,
const Settings& settings, const rcConfig& config, rcHeightfield& solid)
{
const std::array<unsigned char, 2> areas {{AreaType_water, AreaType_water}};
for (const auto& water : recastMesh.getWater())
{
const auto bounds = getWaterBounds(water, settings, agentHalfExtents);
const osg::Vec2f tileBoundsMin(
std::min(config.bmax[0], std::max(config.bmin[0], bounds.mMin.x())),
std::min(config.bmax[2], std::max(config.bmin[2], bounds.mMin.z()))
);
const osg::Vec2f tileBoundsMax(
std::min(config.bmax[0], std::max(config.bmin[0], bounds.mMax.x())),
std::min(config.bmax[2], std::max(config.bmin[2], bounds.mMax.z()))
);
if (tileBoundsMax == tileBoundsMin)
continue;
const std::array<osg::Vec3f, 4> vertices {{
osg::Vec3f(tileBoundsMin.x(), bounds.mMin.y(), tileBoundsMin.y()),
osg::Vec3f(tileBoundsMin.x(), bounds.mMin.y(), tileBoundsMax.y()),
osg::Vec3f(tileBoundsMax.x(), bounds.mMin.y(), tileBoundsMax.y()),
osg::Vec3f(tileBoundsMax.x(), bounds.mMin.y(), tileBoundsMin.y()),
}};
std::array<float, 4 * 3> convertedVertices;
auto convertedVerticesIt = convertedVertices.begin();
for (const auto& vertex : vertices)
convertedVerticesIt = std::copy(vertex.ptr(), vertex.ptr() + 3, convertedVerticesIt);
const std::array<int, 6> indices {{
0, 1, 2,
0, 2, 3,
}};
const auto trianglesRasterized = rcRasterizeTriangles(
&context,
convertedVertices.data(),
static_cast<int>(convertedVertices.size() / 3),
indices.data(),
areas.data(),
static_cast<int>(areas.size()),
solid,
config.walkableClimb
);
if (!trianglesRasterized)
throw NavigatorException("Failed to create rasterize water triangles for navmesh");
}
}
bool rasterizeTriangles(rcContext& context, const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh,
const rcConfig& config, const Settings& settings, rcHeightfield& solid)
{
if (!rasterizeSolidObjectsTriangles(context, recastMesh, config, solid))
return false;
rasterizeWaterTriangles(context, agentHalfExtents, recastMesh, settings, config, solid);
return true;
}
void buildCompactHeightfield(rcContext& context, const int walkableHeight, const int walkableClimb,
rcHeightfield& solid, rcCompactHeightfield& compact)
{
const auto result = rcBuildCompactHeightfield(&context, walkableHeight,
walkableClimb, solid, compact);
if (!result)
throw NavigatorException("Failed to build compact heightfield for navmesh");
}
void erodeWalkableArea(rcContext& context, int walkableRadius, rcCompactHeightfield& compact)
{
const auto result = rcErodeWalkableArea(&context, walkableRadius, compact);
if (!result)
throw NavigatorException("Failed to erode walkable area for navmesh");
}
void buildDistanceField(rcContext& context, rcCompactHeightfield& compact)
{
const auto result = rcBuildDistanceField(&context, compact);
if (!result)
throw NavigatorException("Failed to build distance field for navmesh");
}
void buildRegions(rcContext& context, rcCompactHeightfield& compact, const int borderSize,
const int minRegionArea, const int mergeRegionArea)
{
const auto result = rcBuildRegions(&context, compact, borderSize, minRegionArea, mergeRegionArea);
if (!result)
throw NavigatorException("Failed to build distance field for navmesh");
}
void buildContours(rcContext& context, rcCompactHeightfield& compact, const float maxError, const int maxEdgeLen,
rcContourSet& contourSet, const int buildFlags = RC_CONTOUR_TESS_WALL_EDGES)
{
const auto result = rcBuildContours(&context, compact, maxError, maxEdgeLen, contourSet, buildFlags);
if (!result)
throw NavigatorException("Failed to build contours for navmesh");
}
void buildPolyMesh(rcContext& context, rcContourSet& contourSet, const int maxVertsPerPoly, rcPolyMesh& polyMesh)
{
const auto result = rcBuildPolyMesh(&context, contourSet, maxVertsPerPoly, polyMesh);
if (!result)
throw NavigatorException("Failed to build poly mesh for navmesh");
}
void buildPolyMeshDetail(rcContext& context, const rcPolyMesh& polyMesh, const rcCompactHeightfield& compact,
const float sampleDist, const float sampleMaxError, rcPolyMeshDetail& polyMeshDetail)
{
const auto result = rcBuildPolyMeshDetail(&context, polyMesh, compact, sampleDist, sampleMaxError,
polyMeshDetail);
if (!result)
throw NavigatorException("Failed to build detail poly mesh for navmesh");
}
void setPolyMeshFlags(rcPolyMesh& polyMesh)
{
for (int i = 0; i < polyMesh.npolys; ++i)
{
if (polyMesh.areas[i] == AreaType_ground)
polyMesh.flags[i] = Flag_walk;
else if (polyMesh.areas[i] == AreaType_water)
polyMesh.flags[i] = Flag_swim;
}
}
bool fillPolyMesh(rcContext& context, const rcConfig& config, rcHeightfield& solid, rcPolyMesh& polyMesh,
rcPolyMeshDetail& polyMeshDetail)
{
rcCompactHeightfield compact;
buildCompactHeightfield(context, config.walkableHeight, config.walkableClimb, solid, compact);
erodeWalkableArea(context, config.walkableRadius, compact);
buildDistanceField(context, compact);
buildRegions(context, compact, config.borderSize, config.minRegionArea, config.mergeRegionArea);
rcContourSet contourSet;
buildContours(context, compact, config.maxSimplificationError, config.maxEdgeLen, contourSet);
if (contourSet.nconts == 0)
return false;
buildPolyMesh(context, contourSet, config.maxVertsPerPoly, polyMesh);
buildPolyMeshDetail(context, polyMesh, compact, config.detailSampleDist, config.detailSampleMaxError,
polyMeshDetail);
setPolyMeshFlags(polyMesh);
return true;
}
NavMeshData makeNavMeshTileData(const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh,
const std::vector<OffMeshConnection>& offMeshConnections, const TilePosition& tile,
const osg::Vec3f& boundsMin, const osg::Vec3f& boundsMax, const Settings& settings)
{
rcContext context;
const auto config = makeConfig(agentHalfExtents, boundsMin, boundsMax, settings);
rcHeightfield solid;
createHeightfield(context, solid, config.width, config.height, config.bmin, config.bmax, config.cs, config.ch);
if (!rasterizeTriangles(context, agentHalfExtents, recastMesh, config, settings, solid))
return NavMeshData();
rcFilterLowHangingWalkableObstacles(&context, config.walkableClimb, solid);
rcFilterLedgeSpans(&context, config.walkableHeight, config.walkableClimb, solid);
rcFilterWalkableLowHeightSpans(&context, config.walkableHeight, solid);
rcPolyMesh polyMesh;
rcPolyMeshDetail polyMeshDetail;
initPolyMeshDetail(polyMeshDetail);
const PolyMeshDetailStackPtr polyMeshDetailPtr(&polyMeshDetail);
if (!fillPolyMesh(context, config, solid, polyMesh, polyMeshDetail))
return NavMeshData();
const auto offMeshConVerts = getOffMeshVerts(offMeshConnections);
const std::vector<float> offMeshConRad(offMeshConnections.size(), getRadius(settings, agentHalfExtents));
const std::vector<unsigned char> offMeshConDir(offMeshConnections.size(), DT_OFFMESH_CON_BIDIR);
const std::vector<unsigned char> offMeshConAreas(offMeshConnections.size(), AreaType_ground);
const std::vector<unsigned short> offMeshConFlags(offMeshConnections.size(), Flag_openDoor);
dtNavMeshCreateParams params;
params.verts = polyMesh.verts;
params.vertCount = polyMesh.nverts;
params.polys = polyMesh.polys;
params.polyAreas = polyMesh.areas;
params.polyFlags = polyMesh.flags;
params.polyCount = polyMesh.npolys;
params.nvp = polyMesh.nvp;
params.detailMeshes = polyMeshDetail.meshes;
params.detailVerts = polyMeshDetail.verts;
params.detailVertsCount = polyMeshDetail.nverts;
params.detailTris = polyMeshDetail.tris;
params.detailTriCount = polyMeshDetail.ntris;
params.offMeshConVerts = offMeshConVerts.data();
params.offMeshConRad = offMeshConRad.data();
params.offMeshConDir = offMeshConDir.data();
params.offMeshConAreas = offMeshConAreas.data();
params.offMeshConFlags = offMeshConFlags.data();
params.offMeshConUserID = nullptr;
params.offMeshConCount = static_cast<int>(offMeshConnections.size());
params.walkableHeight = getHeight(settings, agentHalfExtents);
params.walkableRadius = getRadius(settings, agentHalfExtents);
params.walkableClimb = getMaxClimb(settings);
rcVcopy(params.bmin, polyMesh.bmin);
rcVcopy(params.bmax, polyMesh.bmax);
params.cs = config.cs;
params.ch = config.ch;
params.buildBvTree = true;
params.userId = 0;
params.tileX = tile.x();
params.tileY = tile.y();
params.tileLayer = 0;
unsigned char* navMeshData;
int navMeshDataSize;
const auto navMeshDataCreated = dtCreateNavMeshData(&params, &navMeshData, &navMeshDataSize);
if (!navMeshDataCreated)
throw NavigatorException("Failed to create navmesh tile data");
return NavMeshData(navMeshData, navMeshDataSize);
}
UpdateNavMeshStatus makeUpdateNavMeshStatus(bool removed, bool add)
{
if (removed && add)
return UpdateNavMeshStatus::replaced;
else if (removed)
return UpdateNavMeshStatus::removed;
else if (add)
return UpdateNavMeshStatus::add;
else
return UpdateNavMeshStatus::ignore;
}
template <class T>
unsigned long getMinValuableBitsNumber(const T value)
{
unsigned long power = 0;
while (power < sizeof(T) * 8 && (static_cast<T>(1) << power) < value)
++power;
return power;
}
}
namespace DetourNavigator
{
NavMeshPtr makeEmptyNavMesh(const Settings& settings)
{
// Max tiles and max polys affect how the tile IDs are caculated.
// There are 22 bits available for identifying a tile and a polygon.
const int polysAndTilesBits = 22;
const auto polysBits = getMinValuableBitsNumber(settings.mMaxPolys);
if (polysBits >= polysAndTilesBits)
throw InvalidArgument("Too many polygons per tile");
const auto tilesBits = polysAndTilesBits - polysBits;
dtNavMeshParams params;
std::fill_n(params.orig, 3, 0.0f);
params.tileWidth = settings.mTileSize * settings.mCellSize;
params.tileHeight = settings.mTileSize * settings.mCellSize;
params.maxTiles = 1 << tilesBits;
params.maxPolys = 1 << polysBits;
NavMeshPtr navMesh(dtAllocNavMesh(), &dtFreeNavMesh);
const auto status = navMesh->init(&params);
if (!dtStatusSucceed(status))
throw NavigatorException("Failed to init navmesh");
return navMesh;
}
UpdateNavMeshStatus updateNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh* recastMesh,
const TilePosition& changedTile, const TilePosition& playerTile,
const std::vector<OffMeshConnection>& offMeshConnections, const Settings& settings,
const SharedNavMeshCacheItem& navMeshCacheItem, NavMeshTilesCache& navMeshTilesCache)
{
log("update NavMesh with mutiple tiles:",
" agentHeight=", std::setprecision(std::numeric_limits<float>::max_exponent10),
getHeight(settings, agentHalfExtents),
" agentMaxClimb=", std::setprecision(std::numeric_limits<float>::max_exponent10),
getMaxClimb(settings),
" agentRadius=", std::setprecision(std::numeric_limits<float>::max_exponent10),
getRadius(settings, agentHalfExtents),
" changedTile=", changedTile,
" playerTile=", playerTile,
" changedTileDistance=", getDistance(changedTile, playerTile));
const auto params = *navMeshCacheItem.lockConst()->getValue().getParams();
const osg::Vec3f origin(params.orig[0], params.orig[1], params.orig[2]);
const auto x = changedTile.x();
const auto y = changedTile.y();
const auto removeTile = [&] {
const auto locked = navMeshCacheItem.lock();
auto& navMesh = locked->getValue();
const auto tileRef = navMesh.getTileRefAt(x, y, 0);
const auto removed = dtStatusSucceed(navMesh.removeTile(tileRef, nullptr, nullptr));
if (removed)
locked->removeUsedTile(changedTile);
return makeUpdateNavMeshStatus(removed, false);
};
if (!recastMesh)
{
log("ignore add tile: recastMesh is null");
return removeTile();
}
auto recastMeshBounds = recastMesh->getBounds();
for (const auto& water : recastMesh->getWater())
{
const auto waterBounds = getWaterBounds(water, settings, agentHalfExtents);
recastMeshBounds.mMin.y() = std::min(recastMeshBounds.mMin.y(), waterBounds.mMin.y());
recastMeshBounds.mMax.y() = std::max(recastMeshBounds.mMax.y(), waterBounds.mMax.y());
}
if (isEmpty(recastMeshBounds))
{
log("ignore add tile: recastMesh is empty");
return removeTile();
}
if (!shouldAddTile(changedTile, playerTile, params.maxTiles))
{
log("ignore add tile: too far from player");
return removeTile();
}
auto cachedNavMeshData = navMeshTilesCache.get(agentHalfExtents, changedTile, *recastMesh, offMeshConnections);
if (!cachedNavMeshData)
{
const auto tileBounds = makeTileBounds(settings, changedTile);
const osg::Vec3f tileBorderMin(tileBounds.mMin.x(), recastMeshBounds.mMin.y() - 1, tileBounds.mMin.y());
const osg::Vec3f tileBorderMax(tileBounds.mMax.x(), recastMeshBounds.mMax.y() + 1, tileBounds.mMax.y());
auto navMeshData = makeNavMeshTileData(agentHalfExtents, *recastMesh, offMeshConnections, changedTile,
tileBorderMin, tileBorderMax, settings);
if (!navMeshData.mValue)
{
log("ignore add tile: NavMeshData is null");
return removeTile();
}
try
{
cachedNavMeshData = navMeshTilesCache.set(agentHalfExtents, changedTile, *recastMesh,
offMeshConnections, std::move(navMeshData));
}
catch (const InvalidArgument&)
{
cachedNavMeshData = navMeshTilesCache.get(agentHalfExtents, changedTile, *recastMesh,
offMeshConnections);
}
if (!cachedNavMeshData)
{
log("cache overflow");
const auto locked = navMeshCacheItem.lock();
auto& navMesh = locked->getValue();
const auto tileRef = navMesh.getTileRefAt(x, y, 0);
const auto removed = dtStatusSucceed(navMesh.removeTile(tileRef, nullptr, nullptr));
const auto addStatus = navMesh.addTile(navMeshData.mValue.get(), navMeshData.mSize,
doNotTransferOwnership, 0, 0);
if (dtStatusSucceed(addStatus))
{
locked->setUsedTile(changedTile, std::move(navMeshData));
return makeUpdateNavMeshStatus(removed, true);
}
else
{
if (removed)
locked->removeUsedTile(changedTile);
log("failed to add tile with status=", WriteDtStatus {addStatus});
return makeUpdateNavMeshStatus(removed, false);
}
}
}
const auto locked = navMeshCacheItem.lock();
auto& navMesh = locked->getValue();
const auto tileRef = navMesh.getTileRefAt(x, y, 0);
const auto removed = dtStatusSucceed(navMesh.removeTile(tileRef, nullptr, nullptr));
const auto addStatus = navMesh.addTile(cachedNavMeshData.get().mValue, cachedNavMeshData.get().mSize,
doNotTransferOwnership, 0, 0);
if (dtStatusSucceed(addStatus))
{
locked->setUsedTile(changedTile, std::move(cachedNavMeshData));
return makeUpdateNavMeshStatus(removed, true);
}
else
{
if (removed)
locked->removeUsedTile(changedTile);
log("failed to add tile with status=", WriteDtStatus {addStatus});
return makeUpdateNavMeshStatus(removed, false);
}
}
}

View file

@ -0,0 +1,55 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_MAKENAVMESH_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_MAKENAVMESH_H
#include "offmeshconnectionsmanager.hpp"
#include "settings.hpp"
#include "navmeshcacheitem.hpp"
#include "tileposition.hpp"
#include "tilebounds.hpp"
#include "sharednavmesh.hpp"
#include "navmeshtilescache.hpp"
#include <osg/Vec3f>
#include <memory>
class dtNavMesh;
namespace DetourNavigator
{
class RecastMesh;
struct Settings;
enum class UpdateNavMeshStatus
{
ignore,
removed,
add,
replaced
};
inline float getLength(const osg::Vec2i& value)
{
return std::sqrt(float(osg::square(value.x()) + osg::square(value.y())));
}
inline float getDistance(const TilePosition& lhs, const TilePosition& rhs)
{
return getLength(lhs - rhs);
}
inline bool shouldAddTile(const TilePosition& changedTile, const TilePosition& playerTile, int maxTiles)
{
const auto expectedTilesCount = std::ceil(osg::PI * osg::square(getDistance(changedTile, playerTile)));
return expectedTilesCount * 3 <= maxTiles;
}
NavMeshPtr makeEmptyNavMesh(const Settings& settings);
UpdateNavMeshStatus updateNavMesh(const osg::Vec3f& agentHalfExtents, const RecastMesh* recastMesh,
const TilePosition& changedTile, const TilePosition& playerTile,
const std::vector<OffMeshConnection>& offMeshConnections, const Settings& settings,
const SharedNavMeshCacheItem& navMeshCacheItem, NavMeshTilesCache& navMeshTilesCache);
}
#endif

View file

@ -0,0 +1,154 @@
#include "navigator.hpp"
#include "debug.hpp"
#include "settingsutils.hpp"
#include <Recast.h>
namespace DetourNavigator
{
Navigator::Navigator(const Settings& settings)
: mSettings(settings)
, mNavMeshManager(mSettings)
{
}
void Navigator::addAgent(const osg::Vec3f& agentHalfExtents)
{
++mAgents[agentHalfExtents];
mNavMeshManager.addAgent(agentHalfExtents);
}
void Navigator::removeAgent(const osg::Vec3f& agentHalfExtents)
{
const auto it = mAgents.find(agentHalfExtents);
if (it == mAgents.end() || --it->second)
return;
mAgents.erase(it);
mNavMeshManager.reset(agentHalfExtents);
}
bool Navigator::addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform)
{
return mNavMeshManager.addObject(id, shape, transform, AreaType_ground);
}
bool Navigator::addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform)
{
bool result = addObject(id, shapes.mShape, transform);
if (shapes.mAvoid)
{
const ObjectId avoidId(shapes.mAvoid);
if (mNavMeshManager.addObject(avoidId, *shapes.mAvoid, transform, AreaType_null))
{
updateAvoidShapeId(id, avoidId);
result = true;
}
}
return result;
}
bool Navigator::addObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform)
{
if (addObject(id, static_cast<const ObjectShapes&>(shapes), transform))
{
mNavMeshManager.addOffMeshConnection(
id,
toNavMeshCoordinates(mSettings, shapes.mConnectionStart),
toNavMeshCoordinates(mSettings, shapes.mConnectionEnd)
);
return true;
}
return false;
}
bool Navigator::updateObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform)
{
return mNavMeshManager.updateObject(id, shape, transform, AreaType_ground);
}
bool Navigator::updateObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform)
{
bool result = updateObject(id, shapes.mShape, transform);
if (shapes.mAvoid)
{
const ObjectId avoidId(shapes.mAvoid);
if (mNavMeshManager.updateObject(avoidId, *shapes.mAvoid, transform, AreaType_null))
{
updateAvoidShapeId(id, avoidId);
result = true;
}
}
return result;
}
bool Navigator::updateObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform)
{
return updateObject(id, static_cast<const ObjectShapes&>(shapes), transform);
}
bool Navigator::removeObject(const ObjectId id)
{
bool result = mNavMeshManager.removeObject(id);
const auto avoid = mAvoidIds.find(id);
if (avoid != mAvoidIds.end())
result = mNavMeshManager.removeObject(avoid->second) || result;
const auto water = mWaterIds.find(id);
if (water != mWaterIds.end())
result = mNavMeshManager.removeObject(water->second) || result;
mNavMeshManager.removeOffMeshConnection(id);
return result;
}
bool Navigator::addWater(const osg::Vec2i& cellPosition, const int cellSize, const btScalar level,
const btTransform& transform)
{
return mNavMeshManager.addWater(cellPosition, cellSize,
btTransform(transform.getBasis(), btVector3(transform.getOrigin().x(), transform.getOrigin().y(), level)));
}
bool Navigator::removeWater(const osg::Vec2i& cellPosition)
{
return mNavMeshManager.removeWater(cellPosition);
}
void Navigator::update(const osg::Vec3f& playerPosition)
{
for (const auto& v : mAgents)
mNavMeshManager.update(playerPosition, v.first);
}
void Navigator::wait()
{
mNavMeshManager.wait();
}
std::map<osg::Vec3f, SharedNavMeshCacheItem> Navigator::getNavMeshes() const
{
return mNavMeshManager.getNavMeshes();
}
const Settings& Navigator::getSettings() const
{
return mSettings;
}
void Navigator::updateAvoidShapeId(const ObjectId id, const ObjectId avoidId)
{
updateId(id, avoidId, mWaterIds);
}
void Navigator::updateWaterShapeId(const ObjectId id, const ObjectId waterId)
{
updateId(id, waterId, mWaterIds);
}
void Navigator::updateId(const ObjectId id, const ObjectId updateId, std::unordered_map<ObjectId, ObjectId>& ids)
{
auto inserted = ids.insert(std::make_pair(id, updateId));
if (!inserted.second)
{
mNavMeshManager.removeObject(inserted.first->second);
inserted.first->second = updateId;
}
}
}

View file

@ -0,0 +1,205 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVIGATOR_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVIGATOR_H
#include "findsmoothpath.hpp"
#include "flags.hpp"
#include "navmeshmanager.hpp"
#include "settings.hpp"
#include "settingsutils.hpp"
namespace DetourNavigator
{
struct ObjectShapes
{
const btCollisionShape& mShape;
const btCollisionShape* mAvoid;
ObjectShapes(const btCollisionShape& shape, const btCollisionShape* avoid)
: mShape(shape), mAvoid(avoid)
{}
};
struct DoorShapes : ObjectShapes
{
osg::Vec3f mConnectionStart;
osg::Vec3f mConnectionEnd;
DoorShapes(const btCollisionShape& shape, const btCollisionShape* avoid,
const osg::Vec3f& connectionStart,const osg::Vec3f& connectionEnd)
: ObjectShapes(shape, avoid)
, mConnectionStart(connectionStart)
, mConnectionEnd(connectionEnd)
{}
};
/**
* @brief Top level class of detournavigator componenet. 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
* half extents. Each object has unique identifier and could be added, updated or removed. Water could be added once
* for each world cell at given level of height. Navmesh builds asynchronously in separate threads. To start build
* navmesh call update method.
*/
class Navigator
{
public:
/**
* @brief Navigator constructor initializes all internal data. Constructed object is ready to build a scene.
* @param settings allows to customize navigator work. Constructor is only place to set navigator settings.
*/
Navigator(const Settings& settings);
/**
* @brief addAgent should be called for each agent even if all of them has same half extents.
* @param agentHalfExtents allows to setup bounding cylinder for each agent, for each different half extents
* there is different navmesh.
*/
void addAgent(const osg::Vec3f& agentHalfExtents);
/**
* @brief removeAgent should be called for each agent even if all of them has same half extents
* @param agentHalfExtents allows determine which agent to remove
*/
void removeAgent(const osg::Vec3f& agentHalfExtents);
/**
* @brief addObject is used to add object represented by single btCollisionShape and btTransform.
* @param id is used to distinguish different objects.
* @param shape must live until object is updated by another shape removed from Navigator.
* @param transform allows to setup object geometry according to its world state.
* @return true if object is added, false if there is already object with given id.
*/
bool addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform);
/**
* @brief addObject is used to add complex object with allowed to walk and avoided to walk shapes
* @param id is used to distinguish different objects
* @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
* @return true if object is added, false if there is already object with given id
*/
bool addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform);
/**
* @brief addObject is used to add doors.
* @param id is used to distinguish different objects.
* @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.
* @return true if object is added, false if there is already object with given id.
*/
bool addObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform);
/**
* @brief updateObject replace object geometry by given data.
* @param id is used to find object.
* @param shape must live until object is updated by another shape removed from Navigator.
* @param transform allows to setup objects geometry according to its world state.
* @return true if object is updated, false if there is no object with given id.
*/
bool updateObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform);
/**
* @brief updateObject replace object geometry by given data.
* @param id is used to find object.
* @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.
* @return true if object is updated, false if there is no object with given id.
*/
bool updateObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform);
/**
* @brief updateObject replace object geometry by given data.
* @param id is used to find object.
* @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.
* @return true if object is updated, false if there is no object with given id.
*/
bool updateObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform);
/**
* @brief removeObject to make it no more available at the scene.
* @param id is used to find object.
* @return true if object is removed, false if there is no object with given id.
*/
bool removeObject(const ObjectId id);
/**
* @brief addWater is used to set water level at given world cell.
* @param cellPosition allows to distinguish cells if there is many in current world.
* @param cellSize set cell borders. std::numeric_limits<int>::max() disables cell borders.
* @param level set z coordinate of water surface at the scene.
* @param transform set global shift of cell center.
* @return true if there was no water at given cell if cellSize != std::numeric_limits<int>::max() or there is
* at least single object is added to the scene, false if there is already water for given cell or there is no
* any other objects.
*/
bool addWater(const osg::Vec2i& cellPosition, const int cellSize, const btScalar level,
const btTransform& transform);
/**
* @brief removeWater to make it no more available at the scene.
* @param cellPosition allows to find cell.
* @return true if there was water at given cell.
*/
bool removeWater(const osg::Vec2i& cellPosition);
/**
* @brief update start background navmesh update using current scene state.
* @param playerPosition setup initial point to order build tiles of navmesh.
*/
void update(const osg::Vec3f& playerPosition);
/**
* @brief wait locks thread until all tiles are updated from last update call.
*/
void wait();
/**
* @brief findPath fills output iterator with points of scene surfaces to be used for actor to walk through.
* @param agentHalfExtents allows to find navmesh for given actor.
* @param start path from given point.
* @param end path at given point.
* @param includeFlags setup allowed surfaces for actor to walk.
* @param out the beginning of the destination range.
* @return Output iterator to the element in the destination range, one past the last element of found path.
* Equal to out if no path is found.
* @throws InvalidArgument if there is no navmesh for given agentHalfExtents.
*/
template <class OutputIterator>
OutputIterator findPath(const osg::Vec3f& agentHalfExtents, const osg::Vec3f& start,
const osg::Vec3f& end, const Flags includeFlags, OutputIterator out) const
{
static_assert(
std::is_same<
typename std::iterator_traits<OutputIterator>::iterator_category,
std::output_iterator_tag
>::value,
"out is not an OutputIterator"
);
const auto navMesh = mNavMeshManager.getNavMesh(agentHalfExtents);
return findSmoothPath(navMesh.lock()->getValue(), toNavMeshCoordinates(mSettings, agentHalfExtents),
toNavMeshCoordinates(mSettings, start), toNavMeshCoordinates(mSettings, end), includeFlags,
mSettings, out);
}
/**
* @brief getNavMeshes returns all current navmeshes
* @return map of agent half extents to navmesh
*/
std::map<osg::Vec3f, SharedNavMeshCacheItem> getNavMeshes() const;
const Settings& getSettings() const;
private:
Settings mSettings;
NavMeshManager mNavMeshManager;
std::map<osg::Vec3f, std::size_t> mAgents;
std::unordered_map<ObjectId, ObjectId> mAvoidIds;
std::unordered_map<ObjectId, ObjectId> mWaterIds;
void updateAvoidShapeId(const ObjectId id, const ObjectId avoidId);
void updateWaterShapeId(const ObjectId id, const ObjectId waterId);
void updateId(const ObjectId id, const ObjectId waterId, std::unordered_map<ObjectId, ObjectId>& ids);
};
}
#endif

View file

@ -0,0 +1,70 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHCACHEITEM_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHCACHEITEM_H
#include "sharednavmesh.hpp"
#include "tileposition.hpp"
#include "navmeshtilescache.hpp"
#include <components/misc/guarded.hpp>
#include <map>
namespace DetourNavigator
{
class NavMeshCacheItem
{
public:
NavMeshCacheItem(const NavMeshPtr& value, std::size_t generation)
: mValue(value), mGeneration(generation), mNavMeshRevision(0)
{
}
const dtNavMesh& getValue() const
{
return *mValue;
}
dtNavMesh& getValue()
{
return *mValue;
}
std::size_t getGeneration() const
{
return mGeneration;
}
std::size_t getNavMeshRevision() const
{
return mNavMeshRevision;
}
void setUsedTile(const TilePosition& tilePosition, NavMeshTilesCache::Value value)
{
mUsedTiles[tilePosition] = std::make_pair(std::move(value), NavMeshData());
++mNavMeshRevision;
}
void setUsedTile(const TilePosition& tilePosition, NavMeshData value)
{
mUsedTiles[tilePosition] = std::make_pair(NavMeshTilesCache::Value(), std::move(value));
++mNavMeshRevision;
}
void removeUsedTile(const TilePosition& tilePosition)
{
mUsedTiles.erase(tilePosition);
++mNavMeshRevision;
}
private:
NavMeshPtr mValue;
std::size_t mGeneration;
std::size_t mNavMeshRevision;
std::map<TilePosition, std::pair<NavMeshTilesCache::Value, NavMeshData>> mUsedTiles;
};
using SharedNavMeshCacheItem = Misc::SharedGuarded<NavMeshCacheItem>;
}
#endif

View file

@ -0,0 +1,35 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHDATA_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHDATA_H
#include <DetourAlloc.h>
#include <algorithm>
#include <memory>
namespace DetourNavigator
{
struct NavMeshDataValueDeleter
{
void operator ()(unsigned char* value) const
{
dtFree(value);
}
};
using NavMeshDataValue = std::unique_ptr<unsigned char, NavMeshDataValueDeleter>;
struct NavMeshData
{
NavMeshDataValue mValue;
int mSize;
NavMeshData() = default;
NavMeshData(unsigned char* value, int size)
: mValue(value)
, mSize(size)
{}
};
}
#endif

View file

@ -0,0 +1,230 @@
#include "navmeshmanager.hpp"
#include "debug.hpp"
#include "exceptions.hpp"
#include "gettilespositions.hpp"
#include "makenavmesh.hpp"
#include "navmeshcacheitem.hpp"
#include "settings.hpp"
#include "sharednavmesh.hpp"
#include <DetourNavMesh.h>
#include <BulletCollision/CollisionShapes/btConcaveShape.h>
#include <iostream>
namespace
{
using DetourNavigator::ChangeType;
ChangeType addChangeType(const ChangeType current, const ChangeType add)
{
return current == add ? current : ChangeType::mixed;
}
}
namespace DetourNavigator
{
NavMeshManager::NavMeshManager(const Settings& settings)
: mSettings(settings)
, mRecastMeshManager(settings)
, mOffMeshConnectionsManager(settings)
, mAsyncNavMeshUpdater(settings, mRecastMeshManager, mOffMeshConnectionsManager)
{}
bool NavMeshManager::addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform,
const AreaType areaType)
{
if (!mRecastMeshManager.addObject(id, shape, transform, areaType))
return false;
addChangedTiles(shape, transform, ChangeType::add);
return true;
}
bool NavMeshManager::updateObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform,
const AreaType areaType)
{
if (!mRecastMeshManager.updateObject(id, transform, areaType))
return false;
addChangedTiles(shape, transform, ChangeType::mixed);
return true;
}
bool NavMeshManager::removeObject(const ObjectId id)
{
const auto object = mRecastMeshManager.removeObject(id);
if (!object)
return false;
addChangedTiles(object->mShape, object->mTransform, ChangeType::remove);
return true;
}
bool NavMeshManager::addWater(const osg::Vec2i& cellPosition, const int cellSize, const btTransform& transform)
{
if (!mRecastMeshManager.addWater(cellPosition, cellSize, transform))
return false;
addChangedTiles(cellSize, transform, ChangeType::add);
return true;
}
bool NavMeshManager::removeWater(const osg::Vec2i& cellPosition)
{
const auto water = mRecastMeshManager.removeWater(cellPosition);
if (!water)
return false;
addChangedTiles(water->mCellSize, water->mTransform, ChangeType::remove);
return true;
}
void NavMeshManager::addAgent(const osg::Vec3f& agentHalfExtents)
{
auto cached = mCache.find(agentHalfExtents);
if (cached != mCache.end())
return;
mCache.insert(std::make_pair(agentHalfExtents,
std::make_shared<NavMeshCacheItem>(makeEmptyNavMesh(mSettings), ++mGenerationCounter)));
log("cache add for agent=", agentHalfExtents);
}
void NavMeshManager::reset(const osg::Vec3f& agentHalfExtents)
{
mCache.erase(agentHalfExtents);
}
void NavMeshManager::addOffMeshConnection(const ObjectId id, const osg::Vec3f& start, const osg::Vec3f& end)
{
if (!mOffMeshConnectionsManager.add(id, OffMeshConnection {start, end}))
return;
const auto startTilePosition = getTilePosition(mSettings, start);
const auto endTilePosition = getTilePosition(mSettings, end);
addChangedTile(startTilePosition, ChangeType::add);
if (startTilePosition != endTilePosition)
addChangedTile(endTilePosition, ChangeType::add);
}
void NavMeshManager::removeOffMeshConnection(const ObjectId id)
{
if (const auto connection = mOffMeshConnectionsManager.remove(id))
{
const auto startTilePosition = getTilePosition(mSettings, connection->mStart);
const auto endTilePosition = getTilePosition(mSettings, connection->mEnd);
addChangedTile(startTilePosition, ChangeType::remove);
if (startTilePosition != endTilePosition)
addChangedTile(endTilePosition, ChangeType::remove);
}
}
void NavMeshManager::update(osg::Vec3f playerPosition, const osg::Vec3f& agentHalfExtents)
{
const auto playerTile = getTilePosition(mSettings, toNavMeshCoordinates(mSettings, playerPosition));
auto& lastRevision = mLastRecastMeshManagerRevision[agentHalfExtents];
auto lastPlayerTile = mPlayerTile.find(agentHalfExtents);
if (lastRevision >= mRecastMeshManager.getRevision() && lastPlayerTile != mPlayerTile.end()
&& lastPlayerTile->second == playerTile)
return;
lastRevision = mRecastMeshManager.getRevision();
if (lastPlayerTile == mPlayerTile.end())
lastPlayerTile = mPlayerTile.insert(std::make_pair(agentHalfExtents, playerTile)).first;
else
lastPlayerTile->second = playerTile;
std::map<TilePosition, ChangeType> tilesToPost;
const auto& cached = getCached(agentHalfExtents);
const auto changedTiles = mChangedTiles.find(agentHalfExtents);
{
const auto locked = cached.lock();
const auto& navMesh = locked->getValue();
if (changedTiles != mChangedTiles.end())
{
for (const auto& tile : changedTiles->second)
if (navMesh.getTileAt(tile.first.x(), tile.first.y(), 0))
{
auto tileToPost = tilesToPost.find(tile.first);
if (tileToPost == tilesToPost.end())
tilesToPost.insert(tile);
else
tileToPost->second = addChangeType(tileToPost->second, tile.second);
}
for (const auto& tile : tilesToPost)
changedTiles->second.erase(tile.first);
if (changedTiles->second.empty())
mChangedTiles.erase(changedTiles);
}
const auto maxTiles = navMesh.getParams()->maxTiles;
mRecastMeshManager.forEachTilePosition([&] (const TilePosition& tile)
{
if (tilesToPost.count(tile))
return;
const auto shouldAdd = shouldAddTile(tile, playerTile, maxTiles);
const auto presentInNavMesh = bool(navMesh.getTileAt(tile.x(), tile.y(), 0));
if (shouldAdd && !presentInNavMesh)
tilesToPost.insert(std::make_pair(tile, ChangeType::add));
else if (!shouldAdd && presentInNavMesh)
tilesToPost.insert(std::make_pair(tile, ChangeType::mixed));
});
}
mAsyncNavMeshUpdater.post(agentHalfExtents, cached, playerTile, tilesToPost);
log("cache update posted for agent=", agentHalfExtents,
" playerTile=", lastPlayerTile->second,
" recastMeshManagerRevision=", lastRevision);
}
void NavMeshManager::wait()
{
mAsyncNavMeshUpdater.wait();
}
SharedNavMeshCacheItem NavMeshManager::getNavMesh(const osg::Vec3f& agentHalfExtents) const
{
return getCached(agentHalfExtents);
}
std::map<osg::Vec3f, SharedNavMeshCacheItem> NavMeshManager::getNavMeshes() const
{
return mCache;
}
void NavMeshManager::addChangedTiles(const btCollisionShape& shape, const btTransform& transform,
const ChangeType changeType)
{
getTilesPositions(shape, transform, mSettings,
[&] (const TilePosition& v) { addChangedTile(v, changeType); });
}
void NavMeshManager::addChangedTiles(const int cellSize, const btTransform& transform,
const ChangeType changeType)
{
if (cellSize == std::numeric_limits<int>::max())
return;
getTilesPositions(cellSize, transform, mSettings,
[&] (const TilePosition& v) { addChangedTile(v, changeType); });
}
void NavMeshManager::addChangedTile(const TilePosition& tilePosition, const ChangeType changeType)
{
for (const auto& cached : mCache)
{
auto& tiles = mChangedTiles[cached.first];
auto tile = tiles.find(tilePosition);
if (tile == tiles.end())
tiles.insert(std::make_pair(tilePosition, changeType));
else
tile->second = addChangeType(tile->second, changeType);
}
}
const SharedNavMeshCacheItem& NavMeshManager::getCached(const osg::Vec3f& agentHalfExtents) const
{
const auto cached = mCache.find(agentHalfExtents);
if (cached != mCache.end())
return cached->second;
std::ostringstream stream;
stream << "Agent with half extents is not found: " << agentHalfExtents;
throw InvalidArgument(stream.str());
}
}

View file

@ -0,0 +1,74 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHMANAGER_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHMANAGER_H
#include "asyncnavmeshupdater.hpp"
#include "cachedrecastmeshmanager.hpp"
#include "offmeshconnectionsmanager.hpp"
#include "sharednavmesh.hpp"
#include <BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h>
#include <osg/Vec3f>
#include <map>
#include <memory>
class dtNavMesh;
namespace DetourNavigator
{
class NavMeshManager
{
public:
NavMeshManager(const Settings& settings);
bool addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform,
const AreaType areaType);
bool updateObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform,
const AreaType areaType);
bool removeObject(const ObjectId id);
void addAgent(const osg::Vec3f& agentHalfExtents);
bool addWater(const osg::Vec2i& cellPosition, const int cellSize, const btTransform& transform);
bool removeWater(const osg::Vec2i& cellPosition);
void reset(const osg::Vec3f& agentHalfExtents);
void addOffMeshConnection(const ObjectId id, const osg::Vec3f& start, const osg::Vec3f& end);
void removeOffMeshConnection(const ObjectId id);
void update(osg::Vec3f playerPosition, const osg::Vec3f& agentHalfExtents);
void wait();
SharedNavMeshCacheItem getNavMesh(const osg::Vec3f& agentHalfExtents) const;
std::map<osg::Vec3f, SharedNavMeshCacheItem> getNavMeshes() const;
private:
const Settings& mSettings;
TileCachedRecastMeshManager mRecastMeshManager;
OffMeshConnectionsManager mOffMeshConnectionsManager;
AsyncNavMeshUpdater mAsyncNavMeshUpdater;
std::map<osg::Vec3f, SharedNavMeshCacheItem> mCache;
std::map<osg::Vec3f, std::map<TilePosition, ChangeType>> mChangedTiles;
std::size_t mGenerationCounter = 0;
std::map<osg::Vec3f, TilePosition> mPlayerTile;
std::map<osg::Vec3f, std::size_t> mLastRecastMeshManagerRevision;
void addChangedTiles(const btCollisionShape& shape, const btTransform& transform, const ChangeType changeType);
void addChangedTiles(const int cellSize, const btTransform& transform, const ChangeType changeType);
void addChangedTile(const TilePosition& tilePosition, const ChangeType changeType);
const SharedNavMeshCacheItem& getCached(const osg::Vec3f& agentHalfExtents) const;
};
}
#endif

View file

@ -0,0 +1,158 @@
#include "navmeshtilescache.hpp"
#include "exceptions.hpp"
namespace DetourNavigator
{
namespace
{
inline std::string makeNavMeshKey(const RecastMesh& recastMesh,
const std::vector<OffMeshConnection>& offMeshConnections)
{
std::string result;
result.reserve(
recastMesh.getIndices().size() * sizeof(int)
+ recastMesh.getVertices().size() * sizeof(float)
+ recastMesh.getAreaTypes().size() * sizeof(AreaType)
+ recastMesh.getWater().size() * sizeof(RecastMesh::Water)
+ offMeshConnections.size() * sizeof(OffMeshConnection)
);
std::copy(
reinterpret_cast<const char*>(recastMesh.getIndices().data()),
reinterpret_cast<const char*>(recastMesh.getIndices().data() + recastMesh.getIndices().size()),
std::back_inserter(result)
);
std::copy(
reinterpret_cast<const char*>(recastMesh.getVertices().data()),
reinterpret_cast<const char*>(recastMesh.getVertices().data() + recastMesh.getVertices().size()),
std::back_inserter(result)
);
std::copy(
reinterpret_cast<const char*>(recastMesh.getAreaTypes().data()),
reinterpret_cast<const char*>(recastMesh.getAreaTypes().data() + recastMesh.getAreaTypes().size()),
std::back_inserter(result)
);
std::copy(
reinterpret_cast<const char*>(recastMesh.getWater().data()),
reinterpret_cast<const char*>(recastMesh.getWater().data() + recastMesh.getWater().size()),
std::back_inserter(result)
);
std::copy(
reinterpret_cast<const char*>(offMeshConnections.data()),
reinterpret_cast<const char*>(offMeshConnections.data() + offMeshConnections.size()),
std::back_inserter(result)
);
return result;
}
}
NavMeshTilesCache::NavMeshTilesCache(const std::size_t maxNavMeshDataSize)
: mMaxNavMeshDataSize(maxNavMeshDataSize), mUsedNavMeshDataSize(0), mFreeNavMeshDataSize(0) {}
NavMeshTilesCache::Value NavMeshTilesCache::get(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile,
const RecastMesh& recastMesh, const std::vector<OffMeshConnection>& offMeshConnections)
{
const std::lock_guard<std::mutex> lock(mMutex);
const auto agentValues = mValues.find(agentHalfExtents);
if (agentValues == mValues.end())
return Value();
const auto tileValues = agentValues->second.find(changedTile);
if (tileValues == agentValues->second.end())
return Value();
const auto tile = tileValues->second.find(makeNavMeshKey(recastMesh, offMeshConnections));
if (tile == tileValues->second.end())
return Value();
acquireItemUnsafe(tile->second);
return Value(*this, tile->second);
}
NavMeshTilesCache::Value NavMeshTilesCache::set(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile,
const RecastMesh& recastMesh, const std::vector<OffMeshConnection>& offMeshConnections,
NavMeshData&& value)
{
const auto navMeshSize = static_cast<std::size_t>(value.mSize);
const std::lock_guard<std::mutex> lock(mMutex);
if (navMeshSize > mMaxNavMeshDataSize)
return Value();
if (navMeshSize > mFreeNavMeshDataSize + (mMaxNavMeshDataSize - mUsedNavMeshDataSize))
return Value();
while (!mFreeItems.empty() && mUsedNavMeshDataSize + navMeshSize > mMaxNavMeshDataSize)
removeLeastRecentlyUsed();
const auto navMeshKey = makeNavMeshKey(recastMesh, offMeshConnections);
const auto iterator = mFreeItems.emplace(mFreeItems.end(), agentHalfExtents, changedTile, navMeshKey);
const auto emplaced = mValues[agentHalfExtents][changedTile].emplace(navMeshKey, iterator);
if (!emplaced.second)
{
mFreeItems.erase(iterator);
throw InvalidArgument("Set existing cache value");
}
iterator->mNavMeshData = std::move(value);
mUsedNavMeshDataSize += navMeshSize;
mFreeNavMeshDataSize += navMeshSize;
acquireItemUnsafe(iterator);
return Value(*this, iterator);
}
void NavMeshTilesCache::removeLeastRecentlyUsed()
{
const auto& item = mFreeItems.back();
const auto agentValues = mValues.find(item.mAgentHalfExtents);
if (agentValues == mValues.end())
return;
const auto tileValues = agentValues->second.find(item.mChangedTile);
if (tileValues == agentValues->second.end())
return;
const auto value = tileValues->second.find(item.mNavMeshKey);
if (value == tileValues->second.end())
return;
mUsedNavMeshDataSize -= static_cast<std::size_t>(item.mNavMeshData.mSize);
mFreeItems.pop_back();
tileValues->second.erase(value);
if (!tileValues->second.empty())
return;
agentValues->second.erase(tileValues);
if (!agentValues->second.empty())
return;
mValues.erase(agentValues);
}
void NavMeshTilesCache::acquireItemUnsafe(ItemIterator iterator)
{
if (++iterator->mUseCount > 1)
return;
mBusyItems.splice(mBusyItems.end(), mFreeItems, iterator);
mFreeNavMeshDataSize -= static_cast<std::size_t>(iterator->mNavMeshData.mSize);
}
void NavMeshTilesCache::releaseItem(ItemIterator iterator)
{
if (--iterator->mUseCount > 0)
return;
const std::lock_guard<std::mutex> lock(mMutex);
mFreeItems.splice(mFreeItems.begin(), mBusyItems, iterator);
mFreeNavMeshDataSize += static_cast<std::size_t>(iterator->mNavMeshData.mSize);
}
}

View file

@ -0,0 +1,127 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHTILESCACHE_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVMESHTILESCACHE_H
#include "offmeshconnection.hpp"
#include "navmeshdata.hpp"
#include "recastmesh.hpp"
#include "tileposition.hpp"
#include <atomic>
#include <map>
#include <list>
#include <mutex>
namespace DetourNavigator
{
struct NavMeshDataRef
{
unsigned char* mValue;
int mSize;
};
class NavMeshTilesCache
{
public:
struct Item
{
std::atomic<std::int64_t> mUseCount;
osg::Vec3f mAgentHalfExtents;
TilePosition mChangedTile;
std::string mNavMeshKey;
NavMeshData mNavMeshData;
Item(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile, std::string navMeshKey)
: mUseCount(0)
, mAgentHalfExtents(agentHalfExtents)
, mChangedTile(changedTile)
, mNavMeshKey(std::move(navMeshKey))
{}
};
using ItemIterator = std::list<Item>::iterator;
class Value
{
public:
Value()
: mOwner(nullptr), mIterator() {}
Value(NavMeshTilesCache& owner, ItemIterator iterator)
: mOwner(&owner), mIterator(iterator)
{
}
Value(const Value& other) = delete;
Value(Value&& other)
: mOwner(other.mOwner), mIterator(other.mIterator)
{
other.mIterator = ItemIterator();
}
~Value()
{
if (mIterator != ItemIterator())
mOwner->releaseItem(mIterator);
}
Value& operator =(const Value& other) = delete;
Value& operator =(Value&& other)
{
if (mIterator == other.mIterator)
return *this;
if (mIterator != ItemIterator())
mOwner->releaseItem(mIterator);
mOwner = other.mOwner;
mIterator = other.mIterator;
other.mIterator = ItemIterator();
return *this;
}
NavMeshDataRef get() const
{
return NavMeshDataRef {mIterator->mNavMeshData.mValue.get(), mIterator->mNavMeshData.mSize};
}
operator bool() const
{
return mIterator != ItemIterator();
}
private:
NavMeshTilesCache* mOwner;
ItemIterator mIterator;
};
NavMeshTilesCache(const std::size_t maxNavMeshDataSize);
Value get(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile,
const RecastMesh& recastMesh, const std::vector<OffMeshConnection>& offMeshConnections);
Value set(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile,
const RecastMesh& recastMesh, const std::vector<OffMeshConnection>& offMeshConnections,
NavMeshData&& value);
private:
std::mutex mMutex;
std::size_t mMaxNavMeshDataSize;
std::size_t mUsedNavMeshDataSize;
std::size_t mFreeNavMeshDataSize;
std::list<Item> mBusyItems;
std::list<Item> mFreeItems;
std::map<osg::Vec3f, std::map<TilePosition, std::map<std::string, ItemIterator>>> mValues;
void removeLeastRecentlyUsed();
void acquireItemUnsafe(ItemIterator iterator);
void releaseItem(ItemIterator iterator);
};
}
#endif

View file

@ -0,0 +1,50 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_OBJECTID_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_OBJECTID_H
#include <cstddef>
#include <unordered_map>
namespace DetourNavigator
{
class ObjectId
{
public:
template <class T>
explicit ObjectId(const T value) throw()
: mValue(reinterpret_cast<std::size_t>(value))
{
}
std::size_t value() const throw()
{
return mValue;
}
friend bool operator <(const ObjectId lhs, const ObjectId rhs) throw()
{
return lhs.mValue < rhs.mValue;
}
friend bool operator ==(const ObjectId lhs, const ObjectId rhs) throw()
{
return lhs.mValue == rhs.mValue;
}
private:
std::size_t mValue;
};
}
namespace std
{
template <>
struct hash<DetourNavigator::ObjectId>
{
std::size_t operator ()(const DetourNavigator::ObjectId value) const throw()
{
return value.value();
}
};
}
#endif

View file

@ -0,0 +1,15 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_OFFMESHCONNECTION_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_OFFMESHCONNECTION_H
#include <osg/Vec3f>
namespace DetourNavigator
{
struct OffMeshConnection
{
osg::Vec3f mStart;
osg::Vec3f mEnd;
};
}
#endif

View file

@ -0,0 +1,115 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_OFFMESHCONNECTIONSMANAGER_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_OFFMESHCONNECTIONSMANAGER_H
#include "settings.hpp"
#include "settingsutils.hpp"
#include "tileposition.hpp"
#include "objectid.hpp"
#include "offmeshconnection.hpp"
#include <components/misc/guarded.hpp>
#include <osg/Vec3f>
#include <boost/optional.hpp>
#include <map>
#include <mutex>
#include <unordered_map>
#include <unordered_set>
#include <vector>
namespace DetourNavigator
{
class OffMeshConnectionsManager
{
public:
OffMeshConnectionsManager(const Settings& settings)
: mSettings(settings)
{}
bool add(const ObjectId id, const OffMeshConnection& value)
{
const auto values = mValues.lock();
if (!values->mById.insert(std::make_pair(id, value)).second)
return false;
const auto startTilePosition = getTilePosition(mSettings, value.mStart);
const auto endTilePosition = getTilePosition(mSettings, value.mEnd);
values->mByTilePosition[startTilePosition].insert(id);
if (startTilePosition != endTilePosition)
values->mByTilePosition[endTilePosition].insert(id);
return true;
}
boost::optional<OffMeshConnection> remove(const ObjectId id)
{
const auto values = mValues.lock();
const auto itById = values->mById.find(id);
if (itById == values->mById.end())
return boost::none;
const auto result = itById->second;
values->mById.erase(itById);
const auto startTilePosition = getTilePosition(mSettings, result.mStart);
const auto endTilePosition = getTilePosition(mSettings, result.mEnd);
removeByTilePosition(values->mByTilePosition, startTilePosition, id);
if (startTilePosition != endTilePosition)
removeByTilePosition(values->mByTilePosition, endTilePosition, id);
return result;
}
std::vector<OffMeshConnection> get(const TilePosition& tilePosition)
{
std::vector<OffMeshConnection> result;
const auto values = mValues.lock();
const auto itByTilePosition = values->mByTilePosition.find(tilePosition);
if (itByTilePosition == values->mByTilePosition.end())
return result;
std::for_each(itByTilePosition->second.begin(), itByTilePosition->second.end(),
[&] (const ObjectId v)
{
const auto itById = values->mById.find(v);
if (itById != values->mById.end())
result.push_back(itById->second);
});
return result;
}
private:
struct Values
{
std::unordered_map<ObjectId, OffMeshConnection> mById;
std::map<TilePosition, std::unordered_set<ObjectId>> mByTilePosition;
};
const Settings& mSettings;
Misc::ScopeGuarded<Values> mValues;
void removeByTilePosition(std::map<TilePosition, std::unordered_set<ObjectId>>& valuesByTilePosition,
const TilePosition& tilePosition, const ObjectId id)
{
const auto it = valuesByTilePosition.find(tilePosition);
if (it != valuesByTilePosition.end())
it->second.erase(id);
}
};
}
#endif

View file

@ -0,0 +1,22 @@
#include "recastmesh.hpp"
#include "exceptions.hpp"
#include <Recast.h>
namespace DetourNavigator
{
RecastMesh::RecastMesh(std::vector<int> indices, std::vector<float> vertices, std::vector<AreaType> areaTypes,
std::vector<Water> water, const std::size_t trianglesPerChunk)
: mIndices(std::move(indices))
, mVertices(std::move(vertices))
, mAreaTypes(std::move(areaTypes))
, mWater(std::move(water))
, mChunkyTriMesh(mVertices, mIndices, mAreaTypes, trianglesPerChunk)
{
if (getTrianglesCount() != mAreaTypes.size())
throw InvalidArgument("Number of flags doesn't match number of triangles: triangles="
+ std::to_string(getTrianglesCount()) + ", areaTypes=" + std::to_string(mAreaTypes.size()));
if (getVerticesCount())
rcCalcBounds(mVertices.data(), static_cast<int>(getVerticesCount()), mBounds.mMin.ptr(), mBounds.mMax.ptr());
}
}

View file

@ -0,0 +1,80 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESH_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESH_H
#include "areatype.hpp"
#include "chunkytrimesh.hpp"
#include "bounds.hpp"
#include <memory>
#include <string>
#include <vector>
#include <osg/Vec3f>
#include <LinearMath/btTransform.h>
namespace DetourNavigator
{
class RecastMesh
{
public:
struct Water
{
int mCellSize;
btTransform mTransform;
};
RecastMesh(std::vector<int> indices, std::vector<float> vertices, std::vector<AreaType> areaTypes,
std::vector<Water> water, const std::size_t trianglesPerChunk);
const std::vector<int>& getIndices() const
{
return mIndices;
}
const std::vector<float>& getVertices() const
{
return mVertices;
}
const std::vector<AreaType>& getAreaTypes() const
{
return mAreaTypes;
}
const std::vector<Water>& getWater() const
{
return mWater;
}
std::size_t getVerticesCount() const
{
return mVertices.size() / 3;
}
std::size_t getTrianglesCount() const
{
return mIndices.size() / 3;
}
const ChunkyTriMesh& getChunkyTriMesh() const
{
return mChunkyTriMesh;
}
const Bounds& getBounds() const
{
return mBounds;
}
private:
std::vector<int> mIndices;
std::vector<float> mVertices;
std::vector<AreaType> mAreaTypes;
std::vector<Water> mWater;
ChunkyTriMesh mChunkyTriMesh;
Bounds mBounds;
};
}
#endif

View file

@ -0,0 +1,183 @@
#include "recastmeshbuilder.hpp"
#include "chunkytrimesh.hpp"
#include "debug.hpp"
#include "settings.hpp"
#include "settingsutils.hpp"
#include "exceptions.hpp"
#include <components/bullethelpers/processtrianglecallback.hpp>
#include <BulletCollision/CollisionShapes/btBoxShape.h>
#include <BulletCollision/CollisionShapes/btCompoundShape.h>
#include <BulletCollision/CollisionShapes/btConcaveShape.h>
#include <BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h>
#include <LinearMath/btTransform.h>
#include <algorithm>
namespace
{
osg::Vec3f makeOsgVec3f(const btVector3& value)
{
return osg::Vec3f(value.x(), value.y(), value.z());
}
}
namespace DetourNavigator
{
using BulletHelpers::makeProcessTriangleCallback;
RecastMeshBuilder::RecastMeshBuilder(const Settings& settings, const TileBounds& bounds)
: mSettings(settings)
, mBounds(bounds)
{
mBounds.mMin /= mSettings.get().mRecastScaleFactor;
mBounds.mMax /= mSettings.get().mRecastScaleFactor;
}
void RecastMeshBuilder::addObject(const btCollisionShape& shape, const btTransform& transform,
const AreaType areaType)
{
if (shape.isCompound())
return addObject(static_cast<const btCompoundShape&>(shape), transform, areaType);
else if (shape.getShapeType() == TERRAIN_SHAPE_PROXYTYPE)
return addObject(static_cast<const btHeightfieldTerrainShape&>(shape), transform, areaType);
else if (shape.isConcave())
return addObject(static_cast<const btConcaveShape&>(shape), transform, areaType);
else if (shape.getShapeType() == BOX_SHAPE_PROXYTYPE)
return addObject(static_cast<const btBoxShape&>(shape), transform, areaType);
std::ostringstream message;
message << "Unsupported shape type: " << BroadphaseNativeTypes(shape.getShapeType());
throw InvalidArgument(message.str());
}
void RecastMeshBuilder::addObject(const btCompoundShape& shape, const btTransform& transform,
const AreaType areaType)
{
for (int i = 0, num = shape.getNumChildShapes(); i < num; ++i)
addObject(*shape.getChildShape(i), transform * shape.getChildTransform(i), areaType);
}
void RecastMeshBuilder::addObject(const btConcaveShape& shape, const btTransform& transform,
const AreaType areaType)
{
return addObject(shape, transform, makeProcessTriangleCallback([&] (btVector3* triangle, int, int)
{
for (std::size_t i = 3; i > 0; --i)
addTriangleVertex(transform(triangle[i - 1]));
mAreaTypes.push_back(areaType);
}));
}
void RecastMeshBuilder::addObject(const btHeightfieldTerrainShape& shape, const btTransform& transform,
const AreaType areaType)
{
return addObject(shape, transform, makeProcessTriangleCallback([&] (btVector3* triangle, int, int)
{
for (std::size_t i = 0; i < 3; ++i)
addTriangleVertex(transform(triangle[i]));
mAreaTypes.push_back(areaType);
}));
}
void RecastMeshBuilder::addObject(const btBoxShape& shape, const btTransform& transform, const AreaType areaType)
{
const auto indexOffset = static_cast<int>(mVertices.size() / 3);
for (int vertex = 0, count = shape.getNumVertices(); vertex < count; ++vertex)
{
btVector3 position;
shape.getVertex(vertex, position);
addVertex(transform(position));
}
const std::array<int, 36> indices {{
0, 2, 3,
3, 1, 0,
0, 4, 6,
6, 2, 0,
0, 1, 5,
5, 4, 0,
7, 5, 1,
1, 3, 7,
7, 3, 2,
2, 6, 7,
7, 6, 4,
4, 5, 7,
}};
std::transform(indices.begin(), indices.end(), std::back_inserter(mIndices),
[&] (int index) { return index + indexOffset; });
std::generate_n(std::back_inserter(mAreaTypes), 12, [=] { return areaType; });
}
void RecastMeshBuilder::addWater(const int cellSize, const btTransform& transform)
{
mWater.push_back(RecastMesh::Water {cellSize, transform});
}
std::shared_ptr<RecastMesh> RecastMeshBuilder::create() const
{
return std::make_shared<RecastMesh>(mIndices, mVertices, mAreaTypes, mWater, mSettings.get().mTrianglesPerChunk);
}
void RecastMeshBuilder::reset()
{
mIndices.clear();
mVertices.clear();
mAreaTypes.clear();
mWater.clear();
}
void RecastMeshBuilder::addObject(const btConcaveShape& shape, const btTransform& transform,
btTriangleCallback&& callback)
{
btVector3 aabbMin;
btVector3 aabbMax;
shape.getAabb(btTransform::getIdentity(), aabbMin, aabbMax);
aabbMin = transform(aabbMin);
aabbMax = transform(aabbMax);
aabbMin.setX(std::max(mBounds.mMin.x(), aabbMin.x()));
aabbMin.setX(std::min(mBounds.mMax.x(), aabbMin.x()));
aabbMin.setY(std::max(mBounds.mMin.y(), aabbMin.y()));
aabbMin.setY(std::min(mBounds.mMax.y(), aabbMin.y()));
aabbMax.setX(std::max(mBounds.mMin.x(), aabbMax.x()));
aabbMax.setX(std::min(mBounds.mMax.x(), aabbMax.x()));
aabbMax.setY(std::max(mBounds.mMin.y(), aabbMax.y()));
aabbMax.setY(std::min(mBounds.mMax.y(), aabbMax.y()));
const auto inversedTransform = transform.inverse();
aabbMin = inversedTransform(aabbMin);
aabbMax = inversedTransform(aabbMax);
aabbMin.setX(std::min(aabbMin.x(), aabbMax.x()));
aabbMin.setY(std::min(aabbMin.y(), aabbMax.y()));
aabbMin.setZ(std::min(aabbMin.z(), aabbMax.z()));
aabbMax.setX(std::max(aabbMin.x(), aabbMax.x()));
aabbMax.setY(std::max(aabbMin.y(), aabbMax.y()));
aabbMax.setZ(std::max(aabbMin.z(), aabbMax.z()));
shape.processAllTriangles(&callback, aabbMin, aabbMax);
}
void RecastMeshBuilder::addTriangleVertex(const btVector3& worldPosition)
{
mIndices.push_back(static_cast<int>(mVertices.size() / 3));
addVertex(worldPosition);
}
void RecastMeshBuilder::addVertex(const btVector3& worldPosition)
{
const auto navMeshPosition = toNavMeshCoordinates(mSettings, makeOsgVec3f(worldPosition));
mVertices.push_back(navMeshPosition.x());
mVertices.push_back(navMeshPosition.y());
mVertices.push_back(navMeshPosition.z());
}
}

View file

@ -0,0 +1,57 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHBUILDER_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHBUILDER_H
#include "recastmesh.hpp"
#include "tilebounds.hpp"
#include <LinearMath/btTransform.h>
class btBoxShape;
class btCollisionShape;
class btCompoundShape;
class btConcaveShape;
class btHeightfieldTerrainShape;
class btTriangleCallback;
namespace DetourNavigator
{
struct Settings;
class RecastMeshBuilder
{
public:
RecastMeshBuilder(const Settings& settings, const TileBounds& bounds);
void addObject(const btCollisionShape& shape, const btTransform& transform, const AreaType areaType);
void addObject(const btCompoundShape& shape, const btTransform& transform, const AreaType areaType);
void addObject(const btConcaveShape& shape, const btTransform& transform, const AreaType areaType);
void addObject(const btHeightfieldTerrainShape& shape, const btTransform& transform, const AreaType areaType);
void addObject(const btBoxShape& shape, const btTransform& transform, const AreaType areaType);
void addWater(const int mCellSize, const btTransform& transform);
std::shared_ptr<RecastMesh> create() const;
void reset();
private:
std::reference_wrapper<const Settings> mSettings;
TileBounds mBounds;
std::vector<int> mIndices;
std::vector<float> mVertices;
std::vector<AreaType> mAreaTypes;
std::vector<RecastMesh::Water> mWater;
void addObject(const btConcaveShape& shape, const btTransform& transform, btTriangleCallback&& callback);
void addTriangleVertex(const btVector3& worldPosition);
void addVertex(const btVector3& worldPosition);
};
}
#endif

View file

@ -0,0 +1,97 @@
#include "recastmeshmanager.hpp"
#include <BulletCollision/CollisionShapes/btCompoundShape.h>
#include <BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h>
namespace DetourNavigator
{
RecastMeshManager::RecastMeshManager(const Settings& settings, const TileBounds& bounds)
: mShouldRebuild(false)
, mMeshBuilder(settings, bounds)
{
}
bool RecastMeshManager::addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform,
const AreaType areaType)
{
const auto iterator = mObjectsOrder.emplace(mObjectsOrder.end(), RecastMeshObject(shape, transform, areaType));
if (!mObjects.emplace(id, iterator).second)
{
mObjectsOrder.erase(iterator);
return false;
}
mShouldRebuild = true;
return mShouldRebuild;
}
bool RecastMeshManager::updateObject(const ObjectId id, const btTransform& transform, const AreaType areaType)
{
const auto object = mObjects.find(id);
if (object == mObjects.end())
return false;
if (!object->second->update(transform, areaType))
return false;
mShouldRebuild = true;
return mShouldRebuild;
}
boost::optional<RemovedRecastMeshObject> RecastMeshManager::removeObject(const ObjectId id)
{
const auto object = mObjects.find(id);
if (object == mObjects.end())
return boost::none;
const RemovedRecastMeshObject result {object->second->getShape(), object->second->getTransform()};
mObjectsOrder.erase(object->second);
mObjects.erase(object);
mShouldRebuild = true;
return result;
}
bool RecastMeshManager::addWater(const osg::Vec2i& cellPosition, const int cellSize,
const btTransform& transform)
{
const auto iterator = mWaterOrder.emplace(mWaterOrder.end(), Water {cellSize, transform});
if (!mWater.emplace(cellPosition, iterator).second)
{
mWaterOrder.erase(iterator);
return false;
}
mShouldRebuild = true;
return true;
}
boost::optional<RecastMeshManager::Water> RecastMeshManager::removeWater(const osg::Vec2i& cellPosition)
{
const auto water = mWater.find(cellPosition);
if (water == mWater.end())
return boost::none;
mShouldRebuild = true;
const auto result = *water->second;
mWaterOrder.erase(water->second);
mWater.erase(water);
return result;
}
std::shared_ptr<RecastMesh> RecastMeshManager::getMesh()
{
rebuild();
return mMeshBuilder.create();
}
bool RecastMeshManager::isEmpty() const
{
return mObjects.empty();
}
void RecastMeshManager::rebuild()
{
if (!mShouldRebuild)
return;
mMeshBuilder.reset();
for (const auto& v : mWaterOrder)
mMeshBuilder.addWater(v.mCellSize, v.mTransform);
for (const auto& v : mObjectsOrder)
mMeshBuilder.addObject(v.getShape(), v.getTransform(), v.getAreaType());
mShouldRebuild = false;
}
}

View file

@ -0,0 +1,66 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHMANAGER_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHMANAGER_H
#include "recastmeshbuilder.hpp"
#include "recastmeshobject.hpp"
#include "objectid.hpp"
#include <LinearMath/btTransform.h>
#include <osg/Vec2i>
#include <boost/optional.hpp>
#include <map>
#include <unordered_map>
#include <list>
class btCollisionShape;
namespace DetourNavigator
{
struct RemovedRecastMeshObject
{
std::reference_wrapper<const btCollisionShape> mShape;
btTransform mTransform;
};
class RecastMeshManager
{
public:
struct Water
{
int mCellSize;
btTransform mTransform;
};
RecastMeshManager(const Settings& settings, const TileBounds& bounds);
bool addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform,
const AreaType areaType);
bool updateObject(const ObjectId id, const btTransform& transform, const AreaType areaType);
bool addWater(const osg::Vec2i& cellPosition, const int cellSize, const btTransform& transform);
boost::optional<Water> removeWater(const osg::Vec2i& cellPosition);
boost::optional<RemovedRecastMeshObject> removeObject(const ObjectId id);
std::shared_ptr<RecastMesh> getMesh();
bool isEmpty() const;
private:
bool mShouldRebuild;
RecastMeshBuilder mMeshBuilder;
std::list<RecastMeshObject> mObjectsOrder;
std::unordered_map<ObjectId, std::list<RecastMeshObject>::iterator> mObjects;
std::list<Water> mWaterOrder;
std::map<osg::Vec2i, std::list<Water>::iterator> mWater;
void rebuild();
};
}
#endif

View file

@ -0,0 +1,68 @@
#include "recastmeshobject.hpp"
#include <components/debug/debuglog.hpp>
#include <BulletCollision/CollisionShapes/btCompoundShape.h>
#include <cassert>
#include <numeric>
namespace DetourNavigator
{
RecastMeshObject::RecastMeshObject(const btCollisionShape& shape, const btTransform& transform,
const AreaType areaType)
: mShape(shape)
, mTransform(transform)
, mAreaType(areaType)
, mChildren(makeChildrenObjects(shape, mAreaType))
{
}
bool RecastMeshObject::update(const btTransform& transform, const AreaType areaType)
{
bool result = false;
if (!(mTransform == transform))
{
mTransform = transform;
result = true;
}
if (mAreaType != areaType)
{
mAreaType = areaType;
result = true;
}
if (mShape.get().isCompound())
result = updateCompoundObject(static_cast<const btCompoundShape&>(mShape.get()), mAreaType, mChildren)
|| result;
return result;
}
bool RecastMeshObject::updateCompoundObject(const btCompoundShape& shape,
const AreaType areaType, std::vector<RecastMeshObject>& children)
{
assert(static_cast<std::size_t>(shape.getNumChildShapes()) == children.size());
bool result = false;
for (int i = 0, num = shape.getNumChildShapes(); i < num; ++i)
{
assert(shape.getChildShape(i) == std::addressof(children[static_cast<std::size_t>(i)].mShape.get()));
result = children[static_cast<std::size_t>(i)].update(shape.getChildTransform(i), areaType) || result;
}
return result;
}
std::vector<RecastMeshObject> makeChildrenObjects(const btCollisionShape& shape, const AreaType areaType)
{
if (shape.isCompound())
return makeChildrenObjects(static_cast<const btCompoundShape&>(shape), areaType);
else
return std::vector<RecastMeshObject>();
}
std::vector<RecastMeshObject> makeChildrenObjects(const btCompoundShape& shape, const AreaType areaType)
{
std::vector<RecastMeshObject> result;
for (int i = 0, num = shape.getNumChildShapes(); i < num; ++i)
result.emplace_back(*shape.getChildShape(i), shape.getChildTransform(i), areaType);
return result;
}
}

View file

@ -0,0 +1,53 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHOBJECT_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHOBJECT_H
#include "areatype.hpp"
#include <LinearMath/btTransform.h>
#include <functional>
#include <vector>
class btCollisionShape;
class btCompoundShape;
namespace DetourNavigator
{
class RecastMeshObject
{
public:
RecastMeshObject(const btCollisionShape& shape, const btTransform& transform, const AreaType areaType);
bool update(const btTransform& transform, const AreaType areaType);
const btCollisionShape& getShape() const
{
return mShape;
}
const btTransform& getTransform() const
{
return mTransform;
}
AreaType getAreaType() const
{
return mAreaType;
}
private:
std::reference_wrapper<const btCollisionShape> mShape;
btTransform mTransform;
AreaType mAreaType;
std::vector<RecastMeshObject> mChildren;
static bool updateCompoundObject(const btCompoundShape& shape, const AreaType areaType,
std::vector<RecastMeshObject>& children);
};
std::vector<RecastMeshObject> makeChildrenObjects(const btCollisionShape& shape, const AreaType areaType);
std::vector<RecastMeshObject> makeChildrenObjects(const btCompoundShape& shape, const AreaType areaType);
}
#endif

View file

@ -0,0 +1,41 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SETTINGS_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_SETTINGS_H
#include <string>
namespace DetourNavigator
{
struct Settings
{
bool mEnableWriteRecastMeshToFile;
bool mEnableWriteNavMeshToFile;
bool mEnableRecastMeshFileNameRevision;
bool mEnableNavMeshFileNameRevision;
float mCellHeight;
float mCellSize;
float mDetailSampleDist;
float mDetailSampleMaxError;
float mMaxClimb;
float mMaxSimplificationError;
float mMaxSlope;
float mRecastScaleFactor;
float mSwimHeightScale;
int mBorderSize;
int mMaxEdgeLen;
int mMaxNavMeshQueryNodes;
int mMaxPolys;
int mMaxVertsPerPoly;
int mRegionMergeSize;
int mRegionMinSize;
int mTileSize;
std::size_t mAsyncNavMeshUpdaterThreads;
std::size_t mMaxNavMeshTilesCacheSize;
std::size_t mMaxPolygonPathSize;
std::size_t mMaxSmoothPathSize;
std::size_t mTrianglesPerChunk;
std::string mRecastMeshPathPrefix;
std::string mNavMeshPathPrefix;
};
}
#endif

View file

@ -0,0 +1,89 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SETTINGSUTILS_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_SETTINGSUTILS_H
#include "settings.hpp"
#include "tilebounds.hpp"
#include "tileposition.hpp"
#include "tilebounds.hpp"
#include <LinearMath/btTransform.h>
#include <osg/Vec2f>
#include <osg/Vec2i>
#include <osg/Vec3f>
#include <utility>
namespace DetourNavigator
{
inline float getHeight(const Settings& settings,const osg::Vec3f& agentHalfExtents)
{
return 2.0f * agentHalfExtents.z() * settings.mRecastScaleFactor;
}
inline float getMaxClimb(const Settings& settings)
{
return settings.mMaxClimb * settings.mRecastScaleFactor;
}
inline float getRadius(const Settings& settings, const osg::Vec3f& agentHalfExtents)
{
return agentHalfExtents.x() * settings.mRecastScaleFactor;
}
inline osg::Vec3f toNavMeshCoordinates(const Settings& settings, osg::Vec3f position)
{
std::swap(position.y(), position.z());
return position * settings.mRecastScaleFactor;
}
inline osg::Vec3f fromNavMeshCoordinates(const Settings& settings, osg::Vec3f position)
{
const auto factor = 1.0f / settings.mRecastScaleFactor;
position *= factor;
std::swap(position.y(), position.z());
return position;
}
inline float getTileSize(const Settings& settings)
{
return settings.mTileSize * settings.mCellSize;
}
inline TilePosition getTilePosition(const Settings& settings, const osg::Vec3f& position)
{
return TilePosition(
static_cast<int>(std::floor(position.x() / getTileSize(settings))),
static_cast<int>(std::floor(position.z() / getTileSize(settings)))
);
}
inline TileBounds makeTileBounds(const Settings& settings, const TilePosition& tilePosition)
{
return TileBounds {
osg::Vec2f(tilePosition.x(), tilePosition.y()) * getTileSize(settings),
osg::Vec2f(tilePosition.x() + 1, tilePosition.y() + 1) * getTileSize(settings),
};
}
inline float getBorderSize(const Settings& settings)
{
return settings.mBorderSize * settings.mCellSize;
}
inline float getSwimLevel(const Settings& settings, const float agentHalfExtentsZ)
{
return - settings.mSwimHeightScale * agentHalfExtentsZ;
}
inline btTransform getSwimLevelTransform(const Settings& settings, const btTransform& transform,
const float agentHalfExtentsZ)
{
return btTransform(
transform.getBasis(),
transform.getOrigin() + btVector3(0, 0, getSwimLevel(settings, agentHalfExtentsZ) - agentHalfExtentsZ)
);
}
}
#endif

View file

@ -0,0 +1,13 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SHAREDNAVMESH_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_SHAREDNAVMESH_H
#include <memory>
class dtNavMesh;
namespace DetourNavigator
{
using NavMeshPtr = std::shared_ptr<dtNavMesh>;
}
#endif

View file

@ -0,0 +1,15 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_TILEBOUNDS_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_TILEBOUNDS_H
#include <osg/Vec2f>
namespace DetourNavigator
{
struct TileBounds
{
osg::Vec2f mMin;
osg::Vec2f mMax;
};
}
#endif

View file

@ -0,0 +1,175 @@
#include "tilecachedrecastmeshmanager.hpp"
#include "makenavmesh.hpp"
#include "gettilespositions.hpp"
#include "settingsutils.hpp"
namespace DetourNavigator
{
TileCachedRecastMeshManager::TileCachedRecastMeshManager(const Settings& settings)
: mSettings(settings)
{}
bool TileCachedRecastMeshManager::addObject(const ObjectId id, const btCollisionShape& shape,
const btTransform& transform, const AreaType areaType)
{
bool result = false;
auto& tilesPositions = mObjectsTilesPositions[id];
const auto border = getBorderSize(mSettings);
getTilesPositions(shape, transform, mSettings, [&] (const TilePosition& tilePosition)
{
const auto tiles = mTiles.lock();
auto tile = tiles->find(tilePosition);
if (tile == tiles->end())
{
auto tileBounds = makeTileBounds(mSettings, tilePosition);
tileBounds.mMin -= osg::Vec2f(border, border);
tileBounds.mMax += osg::Vec2f(border, border);
tile = tiles->insert(std::make_pair(tilePosition,
CachedRecastMeshManager(mSettings, tileBounds))).first;
}
if (tile->second.addObject(id, shape, transform, areaType))
{
tilesPositions.push_back(tilePosition);
result = true;
}
});
if (result)
++mRevision;
return result;
}
bool TileCachedRecastMeshManager::updateObject(const ObjectId id, const btTransform& transform,
const AreaType areaType)
{
const auto object = mObjectsTilesPositions.find(id);
if (object == mObjectsTilesPositions.end())
return false;
bool result = false;
{
const auto tiles = mTiles.lock();
for (const auto& tilePosition : object->second)
{
const auto tile = tiles->find(tilePosition);
if (tile != tiles->end())
result = tile->second.updateObject(id, transform, areaType) || result;
}
}
if (result)
++mRevision;
return result;
}
boost::optional<RemovedRecastMeshObject> TileCachedRecastMeshManager::removeObject(const ObjectId id)
{
const auto object = mObjectsTilesPositions.find(id);
if (object == mObjectsTilesPositions.end())
return boost::none;
boost::optional<RemovedRecastMeshObject> result;
for (const auto& tilePosition : object->second)
{
const auto tiles = mTiles.lock();
const auto tile = tiles->find(tilePosition);
if (tile == tiles->end())
continue;
const auto tileResult = tile->second.removeObject(id);
if (tile->second.isEmpty())
tiles->erase(tile);
if (tileResult && !result)
result = tileResult;
}
if (result)
++mRevision;
return result;
}
bool TileCachedRecastMeshManager::addWater(const osg::Vec2i& cellPosition, const int cellSize,
const btTransform& transform)
{
const auto border = getBorderSize(mSettings);
auto& tilesPositions = mWaterTilesPositions[cellPosition];
bool result = false;
if (cellSize == std::numeric_limits<int>::max())
{
const auto tiles = mTiles.lock();
for (auto& tile : *tiles)
{
if (tile.second.addWater(cellPosition, cellSize, transform))
{
tilesPositions.push_back(tile.first);
result = true;
}
}
}
else
{
getTilesPositions(cellSize, transform, mSettings, [&] (const TilePosition& tilePosition)
{
const auto tiles = mTiles.lock();
auto tile = tiles->find(tilePosition);
if (tile == tiles->end())
{
auto tileBounds = makeTileBounds(mSettings, tilePosition);
tileBounds.mMin -= osg::Vec2f(border, border);
tileBounds.mMax += osg::Vec2f(border, border);
tile = tiles->insert(std::make_pair(tilePosition,
CachedRecastMeshManager(mSettings, tileBounds))).first;
}
if (tile->second.addWater(cellPosition, cellSize, transform))
{
tilesPositions.push_back(tilePosition);
result = true;
}
});
}
if (result)
++mRevision;
return result;
}
boost::optional<RecastMeshManager::Water> TileCachedRecastMeshManager::removeWater(const osg::Vec2i& cellPosition)
{
const auto object = mWaterTilesPositions.find(cellPosition);
if (object == mWaterTilesPositions.end())
return boost::none;
boost::optional<RecastMeshManager::Water> result;
for (const auto& tilePosition : object->second)
{
const auto tiles = mTiles.lock();
const auto tile = tiles->find(tilePosition);
if (tile == tiles->end())
continue;
const auto tileResult = tile->second.removeWater(cellPosition);
if (tile->second.isEmpty())
tiles->erase(tile);
if (tileResult && !result)
result = tileResult;
}
if (result)
++mRevision;
return result;
}
std::shared_ptr<RecastMesh> TileCachedRecastMeshManager::getMesh(const TilePosition& tilePosition)
{
const auto tiles = mTiles.lock();
const auto it = tiles->find(tilePosition);
if (it == tiles->end())
return nullptr;
return it->second.getMesh();
}
bool TileCachedRecastMeshManager::hasTile(const TilePosition& tilePosition)
{
return mTiles.lockConst()->count(tilePosition);
}
std::size_t TileCachedRecastMeshManager::getRevision() const
{
return mRevision;
}
}

View file

@ -0,0 +1,52 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_TILECACHEDRECASTMESHMANAGER_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_TILECACHEDRECASTMESHMANAGER_H
#include "cachedrecastmeshmanager.hpp"
#include "tileposition.hpp"
#include <components/misc/guarded.hpp>
#include <map>
#include <mutex>
namespace DetourNavigator
{
class TileCachedRecastMeshManager
{
public:
TileCachedRecastMeshManager(const Settings& settings);
bool addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform,
const AreaType areaType);
bool updateObject(const ObjectId id, const btTransform& transform, const AreaType areaType);
boost::optional<RemovedRecastMeshObject> removeObject(const ObjectId id);
bool addWater(const osg::Vec2i& cellPosition, const int cellSize, const btTransform& transform);
boost::optional<RecastMeshManager::Water> removeWater(const osg::Vec2i& cellPosition);
std::shared_ptr<RecastMesh> getMesh(const TilePosition& tilePosition);
bool hasTile(const TilePosition& tilePosition);
template <class Function>
void forEachTilePosition(Function&& function)
{
for (const auto& tile : *mTiles.lock())
function(tile.first);
}
std::size_t getRevision() const;
private:
const Settings& mSettings;
Misc::ScopeGuarded<std::map<TilePosition, CachedRecastMeshManager>> mTiles;
std::unordered_map<ObjectId, std::vector<TilePosition>> mObjectsTilesPositions;
std::map<osg::Vec2i, std::vector<TilePosition>> mWaterTilesPositions;
std::size_t mRevision = 0;
};
}
#endif

View file

@ -0,0 +1,11 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_TILEPOSITION_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_TILEPOSITION_H
#include <osg/Vec2i>
namespace DetourNavigator
{
using TilePosition = osg::Vec2i;
}
#endif

109
components/misc/guarded.hpp Normal file
View file

@ -0,0 +1,109 @@
#ifndef OPENMW_COMPONENTS_MISC_GUARDED_H
#define OPENMW_COMPONENTS_MISC_GUARDED_H
#include <mutex>
#include <memory>
namespace Misc
{
template <class T>
class Locked
{
public:
Locked(std::mutex& mutex, T& value)
: mLock(mutex), mValue(value)
{}
T& get() const
{
return mValue.get();
}
T* operator ->() const
{
return std::addressof(get());
}
T& operator *() const
{
return get();
}
private:
std::unique_lock<std::mutex> mLock;
std::reference_wrapper<T> mValue;
};
template <class T>
class ScopeGuarded
{
public:
ScopeGuarded() = default;
ScopeGuarded(const T& value)
: mValue(value)
{}
ScopeGuarded(T&& value)
: mValue(std::move(value))
{}
template <class ... Args>
ScopeGuarded(Args&& ... args)
: mValue(std::forward<Args>(args) ...)
{}
ScopeGuarded(const ScopeGuarded& other)
: mMutex()
, mValue(other.lock().get())
{}
ScopeGuarded(ScopeGuarded&& other)
: mMutex()
, mValue(std::move(other.lock().get()))
{}
Locked<T> lock()
{
return Locked<T>(mMutex, mValue);
}
Locked<const T> lockConst()
{
return Locked<const T>(mMutex, mValue);
}
private:
std::mutex mMutex;
T mValue;
};
template <class T>
class SharedGuarded
{
public:
SharedGuarded()
: mMutex(std::make_shared<std::mutex>())
{}
SharedGuarded(std::shared_ptr<T> value)
: mMutex(std::make_shared<std::mutex>()), mValue(std::move(value))
{}
Locked<T> lock() const
{
return Locked<T>(*mMutex, *mValue);
}
Locked<const T> lockConst() const
{
return Locked<const T>(*mMutex, *mValue);
}
private:
std::shared_ptr<std::mutex> mMutex;
std::shared_ptr<T> mValue;
};
}
#endif

View file

@ -8,7 +8,6 @@
#include <BulletCollision/CollisionShapes/btBoxShape.h>
#include <BulletCollision/CollisionShapes/btTriangleMesh.h>
#include <BulletCollision/CollisionShapes/btScaledBvhTriangleMeshShape.h>
#include <BulletCollision/CollisionShapes/btCompoundShape.h>
#include <components/debug/debuglog.hpp>
@ -42,27 +41,41 @@ bool pathFileNameStartsWithX(const std::string& path)
return letterPos < path.size() && (path[letterPos] == 'x' || path[letterPos] == 'X');
}
void fillTriangleMeshWithTransform(btTriangleMesh& mesh, const Nif::NiTriShapeData& data, const osg::Matrixf &transform)
{
mesh.preallocateVertices(static_cast<int>(data.vertices.size()));
mesh.preallocateIndices(static_cast<int>(data.triangles.size()));
const std::vector<osg::Vec3f> &vertices = data.vertices;
const std::vector<unsigned short> &triangles = data.triangles;
for (std::size_t i = 0; i < triangles.size(); i += 3)
{
mesh.addTriangle(
getbtVector(vertices[triangles[i + 0]] * transform),
getbtVector(vertices[triangles[i + 1]] * transform),
getbtVector(vertices[triangles[i + 2]] * transform)
);
}
}
void fillTriangleMesh(btTriangleMesh& mesh, const Nif::NiTriShapeData& data)
{
fillTriangleMeshWithTransform(mesh, data, osg::Matrixf());
}
}
namespace NifBullet
{
BulletNifLoader::BulletNifLoader()
: mCompoundShape()
, mStaticMesh()
{
}
BulletNifLoader::~BulletNifLoader()
{
}
osg::ref_ptr<Resource::BulletShape> BulletNifLoader::load(const Nif::File& nif)
{
mShape = new Resource::BulletShape;
mCompoundShape = nullptr;
mStaticMesh = nullptr;
mCompoundShape.reset();
mStaticMesh.reset();
mAvoidStaticMesh.reset();
if (nif.numRoots() < 1)
{
@ -111,7 +124,9 @@ osg::ref_ptr<Resource::BulletShape> BulletNifLoader::load(const Nif::File& nif)
{
btTransform trans;
trans.setIdentity();
mCompoundShape->addChildShape(trans, new Resource::TriangleMeshShape(mStaticMesh.get(), true));
std::unique_ptr<btCollisionShape> child(new Resource::TriangleMeshShape(mStaticMesh.get(), true));
mCompoundShape->addChildShape(trans, child.get());
child.release();
mStaticMesh.release();
}
mShape->mCollisionShape = mCompoundShape.release();
@ -122,22 +137,26 @@ osg::ref_ptr<Resource::BulletShape> BulletNifLoader::load(const Nif::File& nif)
mStaticMesh.release();
}
if (mAvoidStaticMesh)
{
mShape->mAvoidCollisionShape = new Resource::TriangleMeshShape(mAvoidStaticMesh.get(), false);
mAvoidStaticMesh.release();
}
return mShape;
}
}
// Find a boundingBox in the node hierarchy.
// Return: use bounding box for collision?
bool BulletNifLoader::findBoundingBox(const Nif::Node* node, int flags)
bool BulletNifLoader::findBoundingBox(const Nif::Node* node)
{
flags |= node->flags;
if (node->hasBounds)
{
mShape->mCollisionBoxHalfExtents = node->boundXYZ;
mShape->mCollisionBoxTranslate = node->boundPos;
if (flags & Nif::NiNode::Flag_BBoxCollision)
if (node->flags & Nif::NiNode::Flag_BBoxCollision)
{
return true;
}
@ -179,7 +198,7 @@ bool BulletNifLoader::hasAutoGeneratedCollision(const Nif::Node* rootNode)
}
void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node *node, int flags,
bool isCollisionNode, bool isAnimated, bool autogenerated)
bool isCollisionNode, bool isAnimated, bool autogenerated, bool avoid)
{
// Accumulate the flags from all the child nodes. This works for all
// the flags we currently use, at least.
@ -192,8 +211,7 @@ void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node *n
isCollisionNode = isCollisionNode || (node->recType == Nif::RC_RootCollisionNode);
// Don't collide with AvoidNode shapes
if(node->recType == Nif::RC_AvoidNode)
flags |= 0x800;
avoid = avoid || (node->recType == Nif::RC_AvoidNode);
// We encountered a RootCollisionNode inside autogenerated mesh. It is not right.
if (node->recType == Nif::RC_RootCollisionNode && autogenerated)
@ -234,7 +252,7 @@ void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node *n
// (occurs in tr_ex_imp_wall_arch_04.nif)
if(!node->hasBounds && node->recType == Nif::RC_NiTriShape)
{
handleNiTriShape(static_cast<const Nif::NiTriShape*>(node), flags, getWorldTransform(node), isAnimated);
handleNiTriShape(static_cast<const Nif::NiTriShape*>(node), flags, getWorldTransform(node), isAnimated, avoid);
}
}
@ -246,12 +264,13 @@ void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node *n
for(size_t i = 0;i < list.length();i++)
{
if(!list[i].empty())
handleNode(fileName, list[i].getPtr(), flags, isCollisionNode, isAnimated, autogenerated);
handleNode(fileName, list[i].getPtr(), flags, isCollisionNode, isAnimated, autogenerated, avoid);
}
}
}
void BulletNifLoader::handleNiTriShape(const Nif::NiTriShape *shape, int flags, const osg::Matrixf &transform, bool isAnimated)
void BulletNifLoader::handleNiTriShape(const Nif::NiTriShape *shape, int flags, const osg::Matrixf &transform,
bool isAnimated, bool avoid)
{
assert(shape != nullptr);
@ -277,21 +296,7 @@ void BulletNifLoader::handleNiTriShape(const Nif::NiTriShape *shape, int flags,
std::unique_ptr<btTriangleMesh> childMesh(new btTriangleMesh);
const Nif::NiTriShapeData *data = shape->data.getPtr();
childMesh->preallocateVertices(data->vertices.size());
childMesh->preallocateIndices(data->triangles.size());
const std::vector<osg::Vec3f> &vertices = data->vertices;
const std::vector<unsigned short> &triangles = data->triangles;
for(size_t i = 0;i < data->triangles.size();i+=3)
{
osg::Vec3f b1 = vertices[triangles[i+0]];
osg::Vec3f b2 = vertices[triangles[i+1]];
osg::Vec3f b3 = vertices[triangles[i+2]];
childMesh->addTriangle(getbtVector(b1), getbtVector(b2), getbtVector(b3));
}
fillTriangleMesh(*childMesh, shape->data.get());
std::unique_ptr<Resource::TriangleMeshShape> childShape(new Resource::TriangleMeshShape(childMesh.get(), true));
childMesh.release();
@ -314,27 +319,20 @@ void BulletNifLoader::handleNiTriShape(const Nif::NiTriShape *shape, int flags,
mCompoundShape->addChildShape(trans, childShape.get());
childShape.release();
}
else if (avoid)
{
if (!mAvoidStaticMesh)
mAvoidStaticMesh.reset(new btTriangleMesh(false));
fillTriangleMeshWithTransform(*mAvoidStaticMesh, shape->data.get(), transform);
}
else
{
if (!mStaticMesh)
mStaticMesh.reset(new btTriangleMesh(false));
// Static shape, just transform all vertices into position
const Nif::NiTriShapeData *data = shape->data.getPtr();
const std::vector<osg::Vec3f> &vertices = data->vertices;
const std::vector<unsigned short> &triangles = data->triangles;
mStaticMesh->preallocateVertices(data->vertices.size());
mStaticMesh->preallocateIndices(data->triangles.size());
size_t numtris = data->triangles.size();
for(size_t i = 0;i < numtris;i+=3)
{
osg::Vec3f b1 = vertices[triangles[i+0]]*transform;
osg::Vec3f b2 = vertices[triangles[i+1]]*transform;
osg::Vec3f b3 = vertices[triangles[i+2]]*transform;
mStaticMesh->addTriangle(getbtVector(b1), getbtVector(b2), getbtVector(b3));
}
fillTriangleMeshWithTransform(*mStaticMesh, shape->data.get(), transform);
}
}

View file

@ -11,6 +11,8 @@
#include <osg/ref_ptr>
#include <osg/Referenced>
#include <BulletCollision/CollisionShapes/btCompoundShape.h>
#include <components/debug/debuglog.hpp>
#include <components/nif/niffile.hpp>
#include <components/resource/bulletshape.hpp>
@ -35,10 +37,6 @@ namespace NifBullet
class BulletNifLoader
{
public:
BulletNifLoader();
virtual ~BulletNifLoader();
void warn(const std::string &msg)
{
Log(Debug::Warning) << "NIFLoader: Warn:" << msg;
@ -53,18 +51,21 @@ public:
osg::ref_ptr<Resource::BulletShape> load(const Nif::File& file);
private:
bool findBoundingBox(const Nif::Node* node, int flags = 0);
bool findBoundingBox(const Nif::Node* node);
void handleNode(const std::string& fileName, Nif::Node const *node, int flags, bool isCollisionNode, bool isAnimated=false, bool autogenerated=false);
void handleNode(const std::string& fileName, Nif::Node const *node, int flags, bool isCollisionNode,
bool isAnimated=false, bool autogenerated=false, bool avoid=false);
bool hasAutoGeneratedCollision(const Nif::Node *rootNode);
void handleNiTriShape(const Nif::NiTriShape *shape, int flags, const osg::Matrixf& transform, bool isAnimated);
void handleNiTriShape(const Nif::NiTriShape *shape, int flags, const osg::Matrixf& transform, bool isAnimated, bool avoid);
std::unique_ptr<btCompoundShape> mCompoundShape;
std::unique_ptr<btTriangleMesh> mStaticMesh;
std::unique_ptr<btTriangleMesh> mAvoidStaticMesh;
osg::ref_ptr<Resource::BulletShape> mShape;
};

View file

@ -0,0 +1,35 @@
#ifndef OPENMW_COMPONENTS_OSGHELPERS_OPERATORS_H
#define OPENMW_COMPONENTS_OSGHELPERS_OPERATORS_H
#include <iomanip>
#include <limits>
#include <ostream>
#include <osg/Vec2i>
#include <osg/Vec2f>
#include <osg/Vec3f>
namespace osg
{
inline std::ostream& operator <<(std::ostream& stream, const Vec2i& value)
{
return stream << "osg::Vec2i(" << value.x() << ", " << value.y() << ")";
}
inline std::ostream& operator <<(std::ostream& stream, const Vec2f& value)
{
return stream << "osg::Vec2f(" << std::setprecision(std::numeric_limits<float>::max_exponent10) << value.x()
<< ", " << std::setprecision(std::numeric_limits<float>::max_exponent10) << value.y()
<< ')';
}
inline std::ostream& operator <<(std::ostream& stream, const Vec3f& value)
{
return stream << "osg::Vec3f(" << std::setprecision(std::numeric_limits<float>::max_exponent10) << value.x()
<< ", " << std::setprecision(std::numeric_limits<float>::max_exponent10) << value.y()
<< ", " << std::setprecision(std::numeric_limits<float>::max_exponent10) << value.z()
<< ')';
}
}
#endif

View file

@ -13,12 +13,14 @@ namespace Resource
BulletShape::BulletShape()
: mCollisionShape(nullptr)
, mAvoidCollisionShape(nullptr)
{
}
BulletShape::BulletShape(const BulletShape &copy, const osg::CopyOp &copyop)
: mCollisionShape(duplicateCollisionShape(copy.mCollisionShape))
, mAvoidCollisionShape(duplicateCollisionShape(copy.mAvoidCollisionShape))
, mCollisionBoxHalfExtents(copy.mCollisionBoxHalfExtents)
, mCollisionBoxTranslate(copy.mCollisionBoxTranslate)
, mAnimatedShapes(copy.mAnimatedShapes)
@ -27,6 +29,7 @@ BulletShape::BulletShape(const BulletShape &copy, const osg::CopyOp &copyop)
BulletShape::~BulletShape()
{
deleteShape(mAvoidCollisionShape);
deleteShape(mCollisionShape);
}
@ -77,11 +80,23 @@ btCollisionShape* BulletShape::duplicateCollisionShape(const btCollisionShape *s
throw std::logic_error(std::string("Unhandled Bullet shape duplication: ")+shape->getName());
}
btCollisionShape *BulletShape::getCollisionShape()
btCollisionShape *BulletShape::getCollisionShape() const
{
return mCollisionShape;
}
btCollisionShape *BulletShape::getAvoidCollisionShape() const
{
return mAvoidCollisionShape;
}
void BulletShape::setLocalScaling(const btVector3& scale)
{
mCollisionShape->setLocalScaling(scale);
if (mAvoidCollisionShape)
mAvoidCollisionShape->setLocalScaling(scale);
}
osg::ref_ptr<BulletShapeInstance> BulletShape::makeInstance() const
{
osg::ref_ptr<BulletShapeInstance> instance (new BulletShapeInstance(this));
@ -99,6 +114,9 @@ BulletShapeInstance::BulletShapeInstance(osg::ref_ptr<const BulletShape> source)
if (source->mCollisionShape)
mCollisionShape = duplicateCollisionShape(source->mCollisionShape);
if (source->mAvoidCollisionShape)
mAvoidCollisionShape = duplicateCollisionShape(source->mAvoidCollisionShape);
}
}

View file

@ -25,6 +25,7 @@ namespace Resource
META_Object(Resource, BulletShape)
btCollisionShape* mCollisionShape;
btCollisionShape* mAvoidCollisionShape;
// Used for actors. mCollisionShape is used for actors only when we need to autogenerate collision box for creatures.
// For now, use one file <-> one resource for simplicity.
@ -41,7 +42,11 @@ namespace Resource
btCollisionShape* duplicateCollisionShape(const btCollisionShape* shape) const;
btCollisionShape* getCollisionShape();
btCollisionShape* getCollisionShape() const;
btCollisionShape* getAvoidCollisionShape() const;
void setLocalScaling(const btVector3& scale);
private:

View file

@ -0,0 +1,71 @@
#include "agentpath.hpp"
#include "detourdebugdraw.hpp"
#include <components/detournavigator/settings.hpp>
#include <components/esm/loadpgrd.hpp>
#include <algorithm>
namespace
{
void drawAgent(duDebugDraw& debugDraw, const osg::Vec3f& pos, const float radius, const float height,
const float climb, const unsigned color)
{
debugDraw.depthMask(false);
duDebugDrawCylinderWire(&debugDraw, pos.x() - radius, pos.z() + 0.02f, pos.y() - radius, pos.x() + radius,
pos.z() + height, pos.y() + radius, color, radius * 0.2f);
duDebugDrawCircle(&debugDraw, pos.x(), pos.z() + climb, pos.y(), radius, duRGBA(0, 0 , 0, 64), radius * 0.1f);
const auto colb = duRGBA(0, 0, 0, 196);
debugDraw.begin(DU_DRAW_LINES);
debugDraw.vertex(pos.x(), pos.z() - climb, pos.y(), colb);
debugDraw.vertex(pos.x(), pos.z() + climb, pos.y(), colb);
debugDraw.vertex(pos.x() - radius / 2, pos.z() + 0.02f, pos.y(), colb);
debugDraw.vertex(pos.x() + radius / 2, pos.z() + 0.02f, pos.y(), colb);
debugDraw.vertex(pos.x(), pos.z() + 0.02f, pos.y() - radius / 2, colb);
debugDraw.vertex(pos.x(), pos.z() + 0.02f, pos.y() + radius / 2, colb);
debugDraw.end();
debugDraw.depthMask(true);
}
}
namespace SceneUtil
{
osg::ref_ptr<osg::Group> createAgentPathGroup(const std::deque<osg::Vec3f>& path,
const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end,
const DetourNavigator::Settings& settings)
{
using namespace DetourNavigator;
const osg::ref_ptr<osg::Group> group(new osg::Group);
DebugDraw debugDraw(*group, osg::Vec3f(0, 0, 0), 1);
const auto agentRadius = halfExtents.x();
const auto agentHeight = 2.0f * halfExtents.z();
const auto agentClimb = settings.mMaxClimb;
const auto startColor = duRGBA(128, 25, 0, 192);
const auto endColor = duRGBA(51, 102, 0, 129);
drawAgent(debugDraw, start, agentRadius, agentHeight, agentClimb, startColor);
drawAgent(debugDraw, end, agentRadius, agentHeight, agentClimb, endColor);
const auto pathColor = duRGBA(0, 0, 0, 220);
debugDraw.depthMask(false);
debugDraw.begin(osg::PrimitiveSet::LINE_STRIP, agentRadius * 0.5f);
debugDraw.vertex(osg::Vec3f(start.x(), start.z() + agentClimb, start.y()).ptr(), startColor);
std::for_each(path.begin(), path.end(),
[&] (const osg::Vec3f& v) { debugDraw.vertex(osg::Vec3f(v.x(), v.z() + agentClimb, v.y()).ptr(), pathColor); });
debugDraw.vertex(osg::Vec3f(end.x(), end.z() + agentClimb, end.y()).ptr(), endColor);
debugDraw.end();
debugDraw.depthMask(true);
return group;
}
}

View file

@ -0,0 +1,26 @@
#ifndef OPENMW_COMPONENTS_SCENEUTIL_AGENTPATH_H
#define OPENMW_COMPONENTS_SCENEUTIL_AGENTPATH_H
#include <osg/ref_ptr>
#include <deque>
namespace osg
{
class Group;
class Vec3f;
}
namespace DetourNavigator
{
struct Settings;
}
namespace SceneUtil
{
osg::ref_ptr<osg::Group> createAgentPathGroup(const std::deque<osg::Vec3f>& path,
const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end,
const DetourNavigator::Settings& settings);
}
#endif

View file

@ -0,0 +1,128 @@
#include "detourdebugdraw.hpp"
#include "util.hpp"
#include <components/detournavigator/debug.hpp>
#include <osg/BlendFunc>
#include <osg/Group>
#include <osg/LineWidth>
#define OPENMW_TO_STRING(X) #X
#define OPENMW_LINE_STRING OPENMW_TO_STRING(__LINE__)
namespace
{
using DetourNavigator::operator<<;
osg::PrimitiveSet::Mode toOsgPrimitiveSetMode(duDebugDrawPrimitives value)
{
switch (value)
{
case DU_DRAW_POINTS:
return osg::PrimitiveSet::POINTS;
case DU_DRAW_LINES:
return osg::PrimitiveSet::LINES;
case DU_DRAW_TRIS:
return osg::PrimitiveSet::TRIANGLES;
case DU_DRAW_QUADS:
return osg::PrimitiveSet::QUADS;
}
throw std::logic_error("Can't convert duDebugDrawPrimitives to osg::PrimitiveSet::Mode, value="
+ std::to_string(value));
}
}
namespace SceneUtil
{
DebugDraw::DebugDraw(osg::Group& group, const osg::Vec3f& shift, float recastInvertedScaleFactor)
: mGroup(group)
, mShift(shift)
, mRecastInvertedScaleFactor(recastInvertedScaleFactor)
, mDepthMask(false)
, mMode(osg::PrimitiveSet::POINTS)
, mSize(1.0f)
{
}
void DebugDraw::depthMask(bool state)
{
mDepthMask = state;
}
void DebugDraw::texture(bool state)
{
if (state)
throw std::logic_error("DebugDraw does not support textures (at " __FILE__ ":" OPENMW_LINE_STRING ")");
}
void DebugDraw::begin(osg::PrimitiveSet::Mode mode, float size)
{
mMode = mode;
mVertices = new osg::Vec3Array;
mColors = new osg::Vec4Array;
mSize = size * mRecastInvertedScaleFactor;
}
void DebugDraw::begin(duDebugDrawPrimitives prim, float size)
{
begin(toOsgPrimitiveSetMode(prim), size);
}
void DebugDraw::vertex(const float* pos, unsigned color)
{
vertex(pos[0], pos[1], pos[2], color);
}
void DebugDraw::vertex(const float x, const float y, const float z, unsigned color)
{
addVertex(osg::Vec3f(x, y, z));
addColor(SceneUtil::colourFromRGBA(color));
}
void DebugDraw::vertex(const float* pos, unsigned color, const float* uv)
{
vertex(pos[0], pos[1], pos[2], color, uv[0], uv[1]);
}
void DebugDraw::vertex(const float, const float, const float, unsigned, const float, const float)
{
throw std::logic_error("Not implemented (at " __FILE__ ":" OPENMW_LINE_STRING ")");
}
void DebugDraw::end()
{
osg::ref_ptr<osg::StateSet> stateSet(new osg::StateSet);
stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
stateSet->setMode(GL_DEPTH, (mDepthMask ? osg::StateAttribute::ON : osg::StateAttribute::OFF));
stateSet->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
stateSet->setAttributeAndModes(new osg::LineWidth(mSize));
stateSet->setAttributeAndModes(new osg::BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
osg::ref_ptr<osg::Geometry> geometry(new osg::Geometry);
geometry->setStateSet(stateSet);
geometry->setUseDisplayList(false);
geometry->setVertexArray(mVertices);
geometry->setColorArray(mColors, osg::Array::BIND_PER_VERTEX);
geometry->addPrimitiveSet(new osg::DrawArrays(mMode, 0, static_cast<int>(mVertices->size())));
mGroup.addChild(geometry);
mColors.release();
mVertices.release();
}
void DebugDraw::addVertex(osg::Vec3f&& position)
{
std::swap(position.y(), position.z());
mVertices->push_back(position * mRecastInvertedScaleFactor + mShift);
}
void DebugDraw::addColor(osg::Vec4f&& value)
{
mColors->push_back(value);
}
}
#undef OPENMW_TO_STRING
#undef OPENMW_LINE_STRING

View file

@ -0,0 +1,50 @@
#include <DebugDraw.h>
#include <osg/Geometry>
namespace osg
{
class Group;
}
namespace SceneUtil
{
class DebugDraw : public duDebugDraw
{
public:
DebugDraw(osg::Group& group, const osg::Vec3f& shift, float recastInvertedScaleFactor);
void depthMask(bool state) override;
void texture(bool state) override;
void begin(osg::PrimitiveSet::Mode mode, float size);
void begin(duDebugDrawPrimitives prim, float size) override;
void vertex(const float* pos, unsigned int color) override;
void vertex(const float x, const float y, const float z, unsigned int color) override;
void vertex(const float* pos, unsigned int color, const float* uv) override;
void vertex(const float x, const float y, const float z, unsigned int color,
const float u, const float v) override;
void end() override;
private:
osg::Group& mGroup;
osg::Vec3f mShift;
float mRecastInvertedScaleFactor;
bool mDepthMask;
osg::PrimitiveSet::Mode mMode;
float mSize;
osg::ref_ptr<osg::Vec3Array> mVertices;
osg::ref_ptr<osg::Vec4Array> mColors;
void addVertex(osg::Vec3f&& position);
void addColor(osg::Vec4f&& value);
};
}

View file

@ -0,0 +1,22 @@
#include "navmesh.hpp"
#include "detourdebugdraw.hpp"
#include <components/detournavigator/settings.hpp>
#include <DetourDebugDraw.h>
#include <osg/Group>
namespace SceneUtil
{
osg::ref_ptr<osg::Group> createNavMeshGroup(const dtNavMesh& navMesh, const DetourNavigator::Settings& settings)
{
const osg::ref_ptr<osg::Group> group(new osg::Group);
DebugDraw debugDraw(*group, osg::Vec3f(0, 0, 10), 1.0f / settings.mRecastScaleFactor);
dtNavMeshQuery navMeshQuery;
navMeshQuery.init(&navMesh, settings.mMaxNavMeshQueryNodes);
duDebugDrawNavMeshWithClosedList(&debugDraw, navMesh, navMeshQuery,
DU_DRAWNAVMESH_OFFMESHCONS | DU_DRAWNAVMESH_CLOSEDLIST);
return group;
}
}

View file

@ -0,0 +1,23 @@
#ifndef OPENMW_COMPONENTS_SCENEUTIL_NAVMESH_H
#define OPENMW_COMPONENTS_SCENEUTIL_NAVMESH_H
#include <osg/ref_ptr>
class dtNavMesh;
namespace osg
{
class Group;
}
namespace DetourNavigator
{
struct Settings;
}
namespace SceneUtil
{
osg::ref_ptr<osg::Group> createNavMeshGroup(const dtNavMesh& navMesh, const DetourNavigator::Settings& settings);
}
#endif

View file

@ -42,4 +42,15 @@ osg::Vec4f colourFromRGB(unsigned int clr)
return colour;
}
osg::Vec4f colourFromRGBA(unsigned int value)
{
return osg::Vec4f(makeOsgColorComponent(value, 0), makeOsgColorComponent(value, 8),
makeOsgColorComponent(value, 16), makeOsgColorComponent(value, 24));
}
float makeOsgColorComponent(unsigned int value, unsigned int shift)
{
return float((value >> shift) & 0xFFu) / 255.0f;
}
}

View file

@ -15,6 +15,10 @@ namespace SceneUtil
osg::Vec4f colourFromRGB (unsigned int clr);
osg::Vec4f colourFromRGBA (unsigned int value);
float makeOsgColorComponent (unsigned int value, unsigned int shift);
}
#endif