mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-05-09 03:57:51 +03:00
Merge pull request #1633 from elsid/pathfinder_detour
Use recastnavigation for pathfinding (#2229)
This commit is contained in:
commit
d6c674660a
124 changed files with 8610 additions and 727 deletions
|
@ -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)
|
||||
|
|
71
components/bullethelpers/operators.hpp
Normal file
71
components/bullethelpers/operators.hpp
Normal 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
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
16
components/detournavigator/areatype.hpp
Normal file
16
components/detournavigator/areatype.hpp
Normal 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
|
198
components/detournavigator/asyncnavmeshupdater.cpp
Normal file
198
components/detournavigator/asyncnavmeshupdater.cpp
Normal 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();
|
||||
}
|
||||
}
|
88
components/detournavigator/asyncnavmeshupdater.hpp
Normal file
88
components/detournavigator/asyncnavmeshupdater.hpp
Normal 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
|
20
components/detournavigator/bounds.hpp
Normal file
20
components/detournavigator/bounds.hpp
Normal 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
|
63
components/detournavigator/cachedrecastmeshmanager.cpp
Normal file
63
components/detournavigator/cachedrecastmeshmanager.cpp
Normal 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();
|
||||
}
|
||||
}
|
36
components/detournavigator/cachedrecastmeshmanager.hpp
Normal file
36
components/detournavigator/cachedrecastmeshmanager.hpp
Normal 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
|
179
components/detournavigator/chunkytrimesh.cpp
Normal file
179
components/detournavigator/chunkytrimesh.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
99
components/detournavigator/chunkytrimesh.hpp
Normal file
99
components/detournavigator/chunkytrimesh.hpp
Normal 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
|
102
components/detournavigator/debug.cpp
Normal file
102
components/detournavigator/debug.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
133
components/detournavigator/debug.hpp
Normal file
133
components/detournavigator/debug.hpp
Normal 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
|
38
components/detournavigator/dtstatus.hpp
Normal file
38
components/detournavigator/dtstatus.hpp
Normal 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
|
21
components/detournavigator/exceptions.hpp
Normal file
21
components/detournavigator/exceptions.hpp
Normal 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
|
143
components/detournavigator/findsmoothpath.cpp
Normal file
143
components/detournavigator/findsmoothpath.cpp
Normal 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;
|
||||
}
|
||||
}
|
326
components/detournavigator/findsmoothpath.hpp
Normal file
326
components/detournavigator/findsmoothpath.hpp
Normal 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
|
62
components/detournavigator/flags.hpp
Normal file
62
components/detournavigator/flags.hpp
Normal 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
|
73
components/detournavigator/gettilespositions.hpp
Normal file
73
components/detournavigator/gettilespositions.hpp
Normal 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
|
633
components/detournavigator/makenavmesh.cpp
Normal file
633
components/detournavigator/makenavmesh.cpp
Normal 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(¶ms, &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(¶ms);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
55
components/detournavigator/makenavmesh.hpp
Normal file
55
components/detournavigator/makenavmesh.hpp
Normal 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
|
154
components/detournavigator/navigator.cpp
Normal file
154
components/detournavigator/navigator.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
205
components/detournavigator/navigator.hpp
Normal file
205
components/detournavigator/navigator.hpp
Normal 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
|
70
components/detournavigator/navmeshcacheitem.hpp
Normal file
70
components/detournavigator/navmeshcacheitem.hpp
Normal 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
|
35
components/detournavigator/navmeshdata.hpp
Normal file
35
components/detournavigator/navmeshdata.hpp
Normal 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
|
230
components/detournavigator/navmeshmanager.cpp
Normal file
230
components/detournavigator/navmeshmanager.cpp
Normal 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());
|
||||
}
|
||||
}
|
74
components/detournavigator/navmeshmanager.hpp
Normal file
74
components/detournavigator/navmeshmanager.hpp
Normal 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
|
158
components/detournavigator/navmeshtilescache.cpp
Normal file
158
components/detournavigator/navmeshtilescache.cpp
Normal 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);
|
||||
}
|
||||
}
|
127
components/detournavigator/navmeshtilescache.hpp
Normal file
127
components/detournavigator/navmeshtilescache.hpp
Normal 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
|
50
components/detournavigator/objectid.hpp
Normal file
50
components/detournavigator/objectid.hpp
Normal 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
|
15
components/detournavigator/offmeshconnection.hpp
Normal file
15
components/detournavigator/offmeshconnection.hpp
Normal 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
|
115
components/detournavigator/offmeshconnectionsmanager.hpp
Normal file
115
components/detournavigator/offmeshconnectionsmanager.hpp
Normal 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
|
22
components/detournavigator/recastmesh.cpp
Normal file
22
components/detournavigator/recastmesh.cpp
Normal 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());
|
||||
}
|
||||
}
|
80
components/detournavigator/recastmesh.hpp
Normal file
80
components/detournavigator/recastmesh.hpp
Normal 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
|
183
components/detournavigator/recastmeshbuilder.cpp
Normal file
183
components/detournavigator/recastmeshbuilder.cpp
Normal 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());
|
||||
}
|
||||
}
|
57
components/detournavigator/recastmeshbuilder.hpp
Normal file
57
components/detournavigator/recastmeshbuilder.hpp
Normal 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
|
97
components/detournavigator/recastmeshmanager.cpp
Normal file
97
components/detournavigator/recastmeshmanager.cpp
Normal 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;
|
||||
}
|
||||
}
|
66
components/detournavigator/recastmeshmanager.hpp
Normal file
66
components/detournavigator/recastmeshmanager.hpp
Normal 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
|
68
components/detournavigator/recastmeshobject.cpp
Normal file
68
components/detournavigator/recastmeshobject.cpp
Normal 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;
|
||||
}
|
||||
}
|
53
components/detournavigator/recastmeshobject.hpp
Normal file
53
components/detournavigator/recastmeshobject.hpp
Normal 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
|
41
components/detournavigator/settings.hpp
Normal file
41
components/detournavigator/settings.hpp
Normal 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
|
89
components/detournavigator/settingsutils.hpp
Normal file
89
components/detournavigator/settingsutils.hpp
Normal 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
|
13
components/detournavigator/sharednavmesh.hpp
Normal file
13
components/detournavigator/sharednavmesh.hpp
Normal 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
|
15
components/detournavigator/tilebounds.hpp
Normal file
15
components/detournavigator/tilebounds.hpp
Normal 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
|
175
components/detournavigator/tilecachedrecastmeshmanager.cpp
Normal file
175
components/detournavigator/tilecachedrecastmeshmanager.cpp
Normal 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;
|
||||
}
|
||||
}
|
52
components/detournavigator/tilecachedrecastmeshmanager.hpp
Normal file
52
components/detournavigator/tilecachedrecastmeshmanager.hpp
Normal 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
|
11
components/detournavigator/tileposition.hpp
Normal file
11
components/detournavigator/tileposition.hpp
Normal 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
109
components/misc/guarded.hpp
Normal 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
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
35
components/osghelpers/operators.hpp
Normal file
35
components/osghelpers/operators.hpp
Normal 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
|
|
@ -13,12 +13,14 @@ namespace Resource
|
|||
|
||||
BulletShape::BulletShape()
|
||||
: mCollisionShape(nullptr)
|
||||
, mAvoidCollisionShape(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
BulletShape::BulletShape(const BulletShape ©, const osg::CopyOp ©op)
|
||||
: 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 ©, const osg::CopyOp ©op)
|
|||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
71
components/sceneutil/agentpath.cpp
Normal file
71
components/sceneutil/agentpath.cpp
Normal 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;
|
||||
}
|
||||
}
|
26
components/sceneutil/agentpath.hpp
Normal file
26
components/sceneutil/agentpath.hpp
Normal 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
|
128
components/sceneutil/detourdebugdraw.cpp
Normal file
128
components/sceneutil/detourdebugdraw.cpp
Normal 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
|
50
components/sceneutil/detourdebugdraw.hpp
Normal file
50
components/sceneutil/detourdebugdraw.hpp
Normal 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);
|
||||
};
|
||||
}
|
22
components/sceneutil/navmesh.cpp
Normal file
22
components/sceneutil/navmesh.cpp
Normal 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;
|
||||
}
|
||||
}
|
23
components/sceneutil/navmesh.hpp
Normal file
23
components/sceneutil/navmesh.hpp
Normal 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
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue