漏斗算法的核心作用是压缩冗余顶点而非平滑路径,它通过几何裁剪处理Portal序列中的共线拐点,需正确输入朝向一致的portal边、合理初始化并动态更新左右边界,输出后须进行共线剔除与距离过滤。

漏斗算法的核心作用不是“平滑”,而是“压缩冗余顶点”
漏斗算法(Funnel Algorithm)常被误认为是路径平滑技术,其实它解决的是 NavMesh 寻路后路径上存在大量共线或近似共线的拐点问题——这些点来自 A* 在多边形序列上的逐边投影,导致路径锯齿、冗余、不自然。它本身不插值、不拟合曲线,只做几何裁剪:在左右边界构成的“漏斗”中推进,合并可直连的顶点。真正影响最终视觉平滑度的,是后续是否叠加样条插值或速度采样。
实现漏斗算法前必须确保输入是合法的 Portal 序列
漏斗算法不直接处理 NavMesh 多边形,而是处理相邻多边形之间的共享边(即 portal)。常见错误是把多边形顶点序列、A* 返回的 polygon ID 序列,甚至三角化后的顶点列表直接喂给漏斗逻辑,结果崩溃或输出空路径。正确输入必须是:
- 从起始位置到目标位置,穿过的一组 portal 边;每条 portal 是一个
std::pair<vec3 vec3></vec3>(左端点→右端点),按穿越顺序排列 - 首尾 portal 需特殊处理:起点需投影到第一个 portal 上,终点需投影到最后一个 portal 上(否则漏斗无法闭合)
- 所有 portal 必须朝向一致(例如统一为“从当前多边形指向下一多边形”的法向方向),否则左右边界会翻转,漏斗张角计算失效
若用 Recast Navigation,应调用 rcGetPortalPoints() 或自行从 rcPolyMeshDetail 中提取 portal,而非读取 poly->verts。
funnelLeft 和 funnelRight 的初始化与更新极易出错
漏斗维护两个动态边界点:funnelLeft 和 funnelRight,初始值不是起点和终点,而是第一个 portal 的两个端点(按行走方向确定左右)。关键细节:
立即学习“C++免费学习笔记(深入)”;
- 左右判定依赖行走方向:设当前 funnel apex 为
apex(初始为起点),下一个 portal 端点为left和right,需用叉积cross(right - apex, left - apex)判断顺逆时针,确保left始终在行进方向左侧 - 每次添加新 portal 后,必须检查新端点是否“穿出”当前漏斗——即新
left是否在apex→funnelRight右侧,或新right是否在apex→funnelLeft左侧;一旦穿出,就收缩 apex 并输出一个路径顶点 - 收缩时 apex 更新为上一个 portal 的对应端点(不是当前 portal 的!),且 funnelLeft / funnelRight 需回溯到上一有效 portal 的端点,否则边界错位
典型错误是忘记更新 apex 后重置 funnelLeft / funnelRight,导致后续叉积全乱。
输出路径顶点后,务必做共线剔除和距离阈值过滤
漏斗算法输出的顶点仍可能含数值误差导致的微小偏移(尤其浮点运算下),直接用于运动会产生抖动。不能跳过这步后处理:
- 对输出顶点序列,遍历检查三点
A-B-C是否共线:用abs(cross(B-A, C-B)) 判定,成立则删去 <code>B - 相邻顶点距离小于阈值(如
0.1f)时合并,避免导航系统因太密的点频繁转向 - 若后续要接 Catmull-Rom 样条,建议保留原始漏斗输出(含必要拐点),但把共线剔除放在样条采样前——过早删点可能导致曲率突变
最易被忽略的是:漏斗算法本身不保证起点和终点在输出序列中。你必须显式将起点 push_front、终点 push_back,再做共线检测,否则角色永远走不到目的地。


















