#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_FINDSMOOTHPATH_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_FINDSMOOTHPATH_H #include "areatype.hpp" #include "flags.hpp" #include "settings.hpp" #include "settingsutils.hpp" #include "status.hpp" #include #include #include #include #include #include #include #include #include namespace DetourNavigator { template OutputIterator> class FromNavMeshCoordinatesIterator { public: using iterator_category = std::output_iterator_tag; using value_type = osg::Vec3f; using difference_type = std::ptrdiff_t; using pointer = osg::Vec3f*; using reference = osg::Vec3f&; explicit FromNavMeshCoordinatesIterator(OutputIterator& impl, const RecastSettings& settings) : mImpl(impl) , mSettings(settings) { } FromNavMeshCoordinatesIterator& operator*() { return *this; } FromNavMeshCoordinatesIterator& operator++() { ++mImpl.get(); return *this; } FromNavMeshCoordinatesIterator operator++(int) { const auto copy = *this; ++(*this); return copy; } FromNavMeshCoordinatesIterator& operator=(const osg::Vec3f& value) { *mImpl.get() = fromNavMeshCoordinates(mSettings, value); return *this; } private: std::reference_wrapper mImpl; std::reference_wrapper mSettings; }; template class ToNavMeshCoordinatesSpan { public: explicit ToNavMeshCoordinatesSpan(std::span span, const RecastSettings& settings) : mSpan(span) , mSettings(settings) { } std::size_t size() const noexcept { return mSpan.size(); } T operator[](std::size_t i) const noexcept { return toNavMeshCoordinates(mSettings, mSpan[i]); } private: std::span mSpan; const RecastSettings& mSettings; }; inline std::optional findPolygonPath(const dtNavMeshQuery& navMeshQuery, const dtPolyRef startRef, const dtPolyRef endRef, const osg::Vec3f& startPos, const osg::Vec3f& endPos, const dtQueryFilter& queryFilter, std::span pathBuffer) { int pathLen = 0; const auto status = navMeshQuery.findPath(startRef, endRef, startPos.ptr(), endPos.ptr(), &queryFilter, pathBuffer.data(), &pathLen, static_cast(pathBuffer.size())); if (!dtStatusSucceed(status)) return {}; assert(pathLen >= 0); assert(static_cast(pathLen) <= pathBuffer.size()); return static_cast(pathLen); } Status makeSmoothPath(const dtNavMeshQuery& navMeshQuery, const osg::Vec3f& start, const osg::Vec3f& end, std::span polygonPath, std::size_t polygonPathSize, std::size_t maxSmoothPathSize, bool skipFirst, std::output_iterator auto& out) { assert(polygonPathSize <= polygonPath.size()); std::vector cornerVertsBuffer(maxSmoothPathSize * 3); std::vector cornerFlagsBuffer(maxSmoothPathSize); std::vector cornerPolysBuffer(maxSmoothPathSize); int cornersCount = 0; constexpr int findStraightPathOptions = DT_STRAIGHTPATH_AREA_CROSSINGS | DT_STRAIGHTPATH_ALL_CROSSINGS; if (const dtStatus status = navMeshQuery.findStraightPath(start.ptr(), end.ptr(), polygonPath.data(), static_cast(polygonPathSize), cornerVertsBuffer.data(), cornerFlagsBuffer.data(), cornerPolysBuffer.data(), &cornersCount, static_cast(maxSmoothPathSize), findStraightPathOptions); dtStatusFailed(status)) return Status::FindStraightPathFailed; for (int i = skipFirst ? 1 : 0; i < cornersCount; ++i) *out++ = Misc::Convert::makeOsgVec3f(&cornerVertsBuffer[static_cast(i) * 3]); return Status::Success; } Status findSmoothPath(const dtNavMeshQuery& navMeshQuery, const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end, const Flags includeFlags, const AreaCosts& areaCosts, const DetourSettings& settings, float endTolerance, const ToNavMeshCoordinatesSpan& checkpoints, std::output_iterator auto out) { dtQueryFilter queryFilter; queryFilter.setIncludeFlags(includeFlags); queryFilter.setAreaCost(AreaType_water, areaCosts.mWater); queryFilter.setAreaCost(AreaType_door, areaCosts.mDoor); queryFilter.setAreaCost(AreaType_pathgrid, areaCosts.mPathgrid); queryFilter.setAreaCost(AreaType_ground, areaCosts.mGround); constexpr float polyDistanceFactor = 4; const osg::Vec3f polyHalfExtents = halfExtents * polyDistanceFactor; osg::Vec3f startNavMeshPos; dtPolyRef startRef = 0; if (const dtStatus status = navMeshQuery.findNearestPoly( start.ptr(), polyHalfExtents.ptr(), &queryFilter, &startRef, startNavMeshPos.ptr()); dtStatusFailed(status) || startRef == 0) return Status::StartPolygonNotFound; osg::Vec3f endNavMeshPos; const osg::Vec3f endPolyHalfExtents = polyHalfExtents + osg::Vec3f(endTolerance, endTolerance, endTolerance); dtPolyRef endRef; if (const dtStatus status = navMeshQuery.findNearestPoly( end.ptr(), endPolyHalfExtents.ptr(), &queryFilter, &endRef, endNavMeshPos.ptr()); dtStatusFailed(status) || endRef == 0) return Status::EndPolygonNotFound; std::vector polygonPath(settings.mMaxPolygonPathSize); std::span polygonPathBuffer = polygonPath; dtPolyRef currentRef = startRef; osg::Vec3f currentNavMeshPos = startNavMeshPos; bool skipFirst = false; for (std::size_t i = 0; i < checkpoints.size(); ++i) { const osg::Vec3f checkpointPos = checkpoints[i]; osg::Vec3f checkpointNavMeshPos; dtPolyRef checkpointRef; if (const dtStatus status = navMeshQuery.findNearestPoly(checkpointPos.ptr(), polyHalfExtents.ptr(), &queryFilter, &checkpointRef, checkpointNavMeshPos.ptr()); dtStatusFailed(status) || checkpointRef == 0) continue; const std::optional toCheckpointPathSize = findPolygonPath(navMeshQuery, currentRef, checkpointRef, currentNavMeshPos, checkpointNavMeshPos, queryFilter, polygonPath); if (!toCheckpointPathSize.has_value()) continue; if (*toCheckpointPathSize == 0) continue; if (polygonPath[*toCheckpointPathSize - 1] != checkpointRef) continue; const Status smoothStatus = makeSmoothPath(navMeshQuery, currentNavMeshPos, checkpointNavMeshPos, polygonPath, *toCheckpointPathSize, settings.mMaxSmoothPathSize, skipFirst, out); if (smoothStatus != Status::Success) return smoothStatus; currentRef = checkpointRef; currentNavMeshPos = checkpointNavMeshPos; skipFirst = true; } const std::optional toEndPathSize = findPolygonPath( navMeshQuery, currentRef, endRef, currentNavMeshPos, endNavMeshPos, queryFilter, polygonPathBuffer); if (!toEndPathSize.has_value()) return Status::FindPathOverPolygonsFailed; if (*toEndPathSize == 0) return currentRef == endRef ? Status::Success : Status::PartialPath; osg::Vec3f targetNavMeshPos; if (const dtStatus status = navMeshQuery.closestPointOnPoly( polygonPath[*toEndPathSize - 1], end.ptr(), targetNavMeshPos.ptr(), nullptr); dtStatusFailed(status)) return Status::TargetPolygonNotFound; const Status smoothStatus = makeSmoothPath(navMeshQuery, currentNavMeshPos, targetNavMeshPos, polygonPath, *toEndPathSize, settings.mMaxSmoothPathSize, skipFirst, out); if (smoothStatus != Status::Success) return smoothStatus; return polygonPath[*toEndPathSize - 1] == endRef ? Status::Success : Status::PartialPath; } } #endif