ARTICLE · 1036238
航迹能不能边走边改:从源码看 AFSIM 的动态航迹规划与避障
导语:航迹不是一条写死的线
在静态想定中,航迹规划看起来并不复杂:给平台写几个位置点,设置速度和高度,仿真开始后让平台依次飞过这些点。
但真实的仿真问题往往不是“能不能从 A 点到 B 点”,而是:
途中出现新的威胁或规避对象,原来的航路还能不能走? 临时禁飞区扩大后,平台能不能重新计算绕行路线? 同一组障碍存在多个可行方向时,能不能保留备选航路? 重新规划出来的航路,如何交回正在执行的 mover? 算法找不到可行路径时,系统如何报告,而不是悄悄穿过障碍?
这就是动态航迹规划的核心:在仿真运行期间,根据新的态势或约束重新生成一条 route,并让平台继续执行。

这里需要先区分两个概念。AFSIM 中的 route 是有序的航路点集合,描述“计划怎么走”;平台实际飞过的轨迹,则是 mover 根据航路点、速度、转弯、爬升和运动状态逐步生成的结果。重新生成 route,不等于修改已经飞过的轨迹,也不等于完成了任务级作战规划。
从 AFSIM 2.9.0 的源码和资料看,动态航迹规划不是一个孤立的“智能规划器”,而是由三层能力组成:
航路描述层:用 WsfRoute和WsfWaypoint表达位置、速度、高度、转弯和到点动作。 航路搜索层:用 WsfRouteFinder、WsfZoneRouteFinder、path finder 或 route network,根据不同约束计算路线。 航路执行层:由 WsfRouteMover及其子类接收并执行 route。
理解这三层的边界,才能判断 AFSIM 的航迹规划能力到底覆盖到哪里。
一、先看“航路”是什么:route 是 waypoint 的有序集合
AFSIM 的 route 输入块可以写在平台定义中,也可以在平台外定义为命名 route 类型,供平台或 route network 引用。
一个最基本的航路可以抽象为:
routeposition <位置1> altitude <高度> speed <速度>position <位置2>position <位置3>end_route
源码中的 WsfRoute 本质上是 WsfWaypoint 对象的集合。它不仅保存一组经纬高点,还提供航路变换、距离计算、沿航路投影、交叉检测、子航路提取和旅行时间计算等能力。
每个 WsfWaypoint 可以携带多种运动约束和到点行为,包括:
经纬度、MGRS 坐标、相对偏移或转向命令; 高度、深度、高度参考系和爬升/俯冲速率; 速度、线加速度、径向加速度、最大滚转角或转弯过载; 转弯方向、通过点切换或接近点切换; 暂停时间、到点执行脚本、到达末点后的继续、停止或移除行为; 标签、跳转目标和辅助数据。
这意味着 route 不只是几何折线。它同时携带了平台执行每一段航路时所需的部分运动语义。
例如,switch_on_passing 和 switch_on_approach 的区别,会影响平台何时认为自己已经完成当前航路点并开始转向下一点;radial_acceleration、bank_angle_limit 和 turn_g_limit 则会影响转弯段如何被执行。这些参数决定的是航路如何被平台执行,而不是搜索算法如何选择障碍两侧的道路。
另一个容易被忽略的能力是航路组合。insert_route、insert_offset_route 和 transform_absolute_route 可以把命名 route 变换后插入当前航路,用于表达盘旋、往返等模式。它解决的是航路模板复用和几何变换问题,不等于运行期动态避障。
二、谁在执行航路:route mover 把计划变成运动
航路搜索只负责返回一条 route。平台能否真正沿着它运动,还要看 mover 的执行链路。

AFSIM 文档将 WSF_AIR_MOVER、WSF_GROUND_MOVER、WSF_SURFACE_MOVER 和 WSF_ROAD_MOVER 归为能够沿路径运动的 route mover。源码中的 WsfRouteMover 继承自 WsfMover,负责维护 route 的读取、设置、推进和重置。
从接口上看,WsfRouteMover 提供了几个与动态航路有关的关键位置:
GetRoute:获取当前航路;SetRoute/SetRoutePoint:设置新的航路或从指定航路点开始执行;ReturnToRoute:在偏离后尝试返回当前航路;GetRouteComputer/SetRouteComputer:连接路径计算器;WaypointReached:处理航路点到达;Pause/Unpause:控制运动暂停和恢复。
具体的航路执行由子类和 guidance 组件完成,例如 WsfWaypointMover、WsfIterativeRouteMover 以及 WsfPathGuidance、WsfWaypointPathGuidance。因此,动态规划链路不是“计算函数直接移动平台”,而更接近下面的关系:
障碍与目标状态|v航路搜索器 -> WsfRoute|vmover.SetRoute / SetRoutePoint|vroute mover -> guidance -> 平台运动状态
如果只验证了搜索器能够返回一组点,而没有验证 mover 能否在当前状态下接收和执行这组点,就不能把“规划成功”写成“平台已经完成动态重规划”。
三、圆域障碍:WsfRouteFinder 如何绕过去
在 AFSIM 2.9.0 中,WsfRouteFinder 是动态避障最直接的一条脚本接口。它可以把平台或静态地理位置登记为带半径的规避对象,然后从起点到终点计算绕过这些圆形规避区的 route。

它暴露的主要能力包括:
Avoid(platform, radius)Avoid(geo_point, radius)ClearAvoidances()Route(time, from, to, speed, branchIndex)RouteAvoidances()SetImpossibleRouteResponse(...)SetMaxArcLength(...)DrawRoute(...)DrawGraph(...)DrawAvoidances(...)
其中,Avoid(platform, radius) 记录的是平台对象和规避半径。这使得移动平台可以被纳入一次航路计算的规避集合中。但这并不意味着 route finder 会自动持续监听所有平台并持续重规划。调用方仍需要在合适的脚本、回调或处理逻辑中更新规避集合并再次调用 Route;具体触发时机和运行期开销需要通过场景验证,当前为 NOT RUN。
WsfRouteFinder.cpp 的实现将规避对象转换为局部二维圆域。随后,它会计算起点、终点、规避圆之间的切线关系,并把可行的切线段和圆弧段组织成图结构。
从源码中的内部结构可以看到:
Avoidance 表示一个规避圆及其与当前路线的距离关系;GraphNode表示图中的规避节点;GraphBranch表示连接图节点的候选分支;OuterTangents、InnerTangents和TangentPoints用于生成切线几何;ArcLength用于计算沿圆弧绕行的距离;GenerateGraph生成图;FindBestGraphPath和递归搜索函数寻找候选路径。
因此,它解决的不是“在规则网格上逐格试探”,而是将圆形规避区周围的可行几何路径转化为图搜索问题。最短路线通常由若干直线切线段和规避圆上的弧段组成。
这也解释了为什么 SetMaxArcLength 会影响输出。它用于控制规避圆弧上航路点的最大弧长,弧段切得越细,输出点可能越多;点数、几何近似和后续 mover 执行之间需要权衡。源码和示例能够证明该参数存在及其用途,但不能仅凭静态代码证明某个场景下的实时性或最优点数。
Route 接口支持 branchIndex。索引 0 表示默认的最短路线;后续索引可以选择与最短路线首段不同的其他候选路线。
这对于仿真中的“不能只看距离”很有意义。例如,两条路线长度接近,但一条更靠近威胁区,或者一条更适合后续任务衔接,调用方可以保留多条候选路径再做上层选择。
但这里必须把能力边界说清楚:branchIndex 只是路径候选分支,不是多平台协同,也不是全局任务代价优化。它不会自动替调用方判断燃油、威胁暴露、编队关系、任务优先级或交战规则。
如果起点或终点已经处于规避区内,或者多个规避区把点包围,搜索问题可能没有直接解。WsfRouteFinder 提供三种处理策略:
SHRINK:收缩造成问题的规避区;SHIFT:将相关起点或终点移到规避区外;IGNORE:忽略导致问题的规避区。
源码显示默认策略是 SHRINK_AVOIDANCES,脚本接口对应字符串 SHRINK、SHIFT 和 IGNORE。
这三种策略不是“算法自动修复现实世界冲突”,而是对无解输入做出的建模选择。比如,忽略规避区可能让数学路径重新存在,但也可能违反上层安全约束;移动起点可能改变平台当前状态与规划状态之间的语义。生产系统应把采用哪种策略记录为可审计的决策,而不能把返回了非空 route 当成约束全部满足。
四、多边形禁入区:WsfZoneRouteFinder 解决什么问题
圆形规避区适合用平台安全半径或简单威胁半径表达障碍,但很多想定约束更接近区域:禁飞区、海域边界、训练区域或其他多边形地理限制。
WsfZoneRouteFinder 提供了面向 polygon zone 的另一条路线。它支持:
按 zone 名称或 zone 对象登记避让区; 计算起点到终点的绕行 route; 读取规避区数量和对象; 清除规避区; 绘制规避区和搜索图。
文档明确限定了输入条件:被避让的 zone 应是 polygonal、lat/lon、internal 类型。源码还显示,它针对多个 zone 的重叠、包含和分离关系做了处理,并在必要时使用凸包方式组织局部几何。
这和 WsfRouteFinder 的差异可以概括为:
两者都能返回 WsfRoute,但不能因此把它们说成同一个算法,也不能把圆域避障的结论自动推广到任意复杂地理障碍。
五、规划结果如何回到平台
AFSIM demo 给出了一个很重要的使用模式:脚本先根据当前场景收集规避对象,调用 route finder 计算路径,再把选中的 WsfRoute 通过 PLATFORM.SetRoute(...) 交回平台。
以 3d_route_finder.txt 的结构为例,流程大致是:
读取或识别需要规避的平台-> ClearAvoidances()-> 对每个规避对象调用 Avoid(...)-> 调用 Route(..., branchIndex)-> 比较候选路线-> PLATFORM.SetRoute(selectedRoute)-> mover 继续执行
这个流程体现了一个关键工程边界:AFSIM 提供了重新计算 route 和把 route 设置回平台的接口,但“什么时候重算、哪些对象纳入规避、如何比较候选、重算失败后怎么办”,仍然需要上层逻辑组织。
因此,一套完整的运行期重规划逻辑至少应包含:
触发条件:发现新障碍、目标位置变化、原航路失效或周期性检查。 态势快照:固定本次规划使用的时间、平台状态、目标状态和障碍集合。 约束转换:把平台、zone 或其他信息转换为 finder 可接受的输入。 路径搜索:计算默认路径和必要的备选路径。 结果检查:检查 route 是否为空、点数是否合理、关键段是否越界。 执行切换:确定从当前平台状态的哪一点接入新 route,再调用
SetRoute 或相关接口。失败处理:暂停、保持原航路、返航、上报错误或进入上层决策。 诊断记录:保存触发原因、障碍集合、策略、候选路径和最终选择。
源码和示例支持上述链路中的对象关系,但尚未通过运行测试证明所有 mover 类型在任意运行时状态下都能无缝切换。因此,“支持 SetRoute”不能直接写成“任意时刻平滑重规划已验证”。
六、不要把几条“路径能力”混成一个规划器
AFSIM 还提供了几类与航迹规划相关、但问题定义不同的机制。

route_network 用多个 route 和 node_id 组织道路、航路或水路网络。共享 node_id 的航路点被视为连接点,test 和 test_nodes 可以在初始化阶段检查网络路径并输出 Cost 与 Path。
资料显示,网络无法连通时会输出 Cost = -1,路径为空;底层使用 WsfShortestPath,其文档注释说明采用 Dijkstra 算法。
它适合回答“在预先定义好的网络上,从节点 A 到节点 B 走哪条代价更小”,不直接回答“当前移动威胁出现后,如何在任意空间中实时绕行”。
源码中可以看到 pathfinder、terrainpathfinder 和 navigationmesh 的注册链。WsfPathFinder 面向二维加权节点环境,WsfTerrainPathFinder 扩展到三维地形,另有导航网格实现。
WsfPathFinder 继承 WsfRouteComputer,并实现 ComputeFindPath、ComputeSetRoute、ComputeUpdateRoute、ComputeExtrapolate 和 ReturnToRoute 等接口。这说明 path finder 可以参与 mover 的路径计算链路。
但它与脚本构造的 WsfRouteFinder 仍然不是同一个东西:
WsfRouteFinder/ WsfZoneRouteFinder主要通过脚本 API 组织动态规避输入;pathfinder/terrainpathfinder/navigationmesh是场景中的 path finder 类型对象;二者的障碍表达、代价模型、配置方式和适用环境不同。
当前文章只依据源码和文档说明这种职责差异,不对具体 terrain path finder 场景的运行效果、性能和规模作结论,相关内容为 NOT RUN。
WsfPathComputer 根据当前状态和目标状态计算 WsfPathList,其中包含直飞目标、相对转向、转向航向等操作。它更接近机动和引导层的路径计算,不等同于带障碍搜索的 route finder。
一个实用的判断方法是:
问题是“障碍之间从哪里绕” -> route finder / path finder问题是“到下一个目标如何转弯、爬升、减速” -> path computer / guidance问题是“固定网络中走哪条边” -> route network / shortest path
实际工程中这些能力可以串接,但不能用其中一个的证据替代另一个的验证。
七、动态重规划最容易被忽略的四个边界
Avoid 和 Route 提供了计算接口,但何时调用它们仍由脚本、处理器或其他上层逻辑决定。周期调用、事件触发、状态变化触发都可能是方案,但不同触发机制对重复计算、时间一致性和结果抖动的影响需要分别验证。
圆半径、多边形 zone、格网代价都是仿真抽象。它们可以表达安全距离或地理约束,但不能自动代表传感器探测概率、武器杀伤区、电子干扰效果或真实平台不可达区域。
搜索算法返回了几何 route,并不意味着当前 mover 一定能在其速度、转弯半径、爬升率和加速度约束下安全执行。航路点参数和 mover guidance 还需要对结果进行执行层验证。
最短距离只是一个代价维度。仿真任务还可能关心威胁暴露、燃油、时间窗、通信覆盖、编队保持和交战规则。如何把这些因素转成代价、约束和上层决策,不能由 route finder 的接口存在自动推出。
八、如何验证一条动态航迹规划链路
如果要把本文的静态源码结论推进到工程结论,建议按证据阶梯开展验证,而不是只运行一个能返回 route 的示例。
检查输入和输出结构:
起点、终点和障碍数据是否有明确坐标语义和单位; route 是否为空; waypoint 是否包含预期的经纬高和速度; 规避策略、branchIndex 和最大弧长是否被记录; 无解时是否有明确错误或状态输出。
建议使用固定起点、固定终点和单个静态规避圆构造最小场景,逐步增加多个规避圆、移动平台和重叠 zone。至少检查:
直线路径被障碍阻断时,返回 route 是否出现绕行段; 绕行点是否位于规避区外; 设置不同 branchIndex时是否得到不同候选;SetRoute后平台是否从合理的当前状态继续运动; 无解策略是否产生预期结果。
在仿真运行中改变障碍集合或目标位置,记录每次重规划的:
仿真时间当前平台状态障碍集合版本规划策略候选路线摘要最终选择SetRoute 时刻执行结果
需要特别观察:重复重算是否导致路线在两个候选之间频繁切换,重算期间平台是否继续使用旧路线,新的第一航路点是否与当前平台状态不连续,以及规划失败时是否有可解释的降级行为。
最后才适合评估大量障碍、多平台、长时间运行、外部态势输入和跨进程调用。计算耗时、吞吐、内存、并发安全和跨节点一致性都必须有独立测试数据。
结语:能重算航路,不等于拥有自主规划能力
从 AFSIM 2.9.0 的源码、随包文档和示例看,AFSIM 已经提供了较完整的航路表达、航路执行和多类型路径计算机制。
其中,WsfRouteFinder 通过圆域规避、切线和圆弧建图,支持在静态点状或平台规避对象之间寻找绕行路径;WsfZoneRouteFinder 面向多边形地理区域;route_network 解决固定网络上的节点最短路;path finder 则面向格网、地形或导航网格条件。计算结果最终还要通过 mover、route computer 和 guidance 才能转化为平台运动。
这组能力支持一种清晰的工程路径:
态势变化-> 形成可计算的障碍或区域约束-> 选择匹配的路径搜索机制-> 生成并检查 WsfRoute-> 交回 mover 执行-> 记录结果并在必要时再次规划
但它仍然不等于一个天然具备任务理解、威胁评估、多机协同和全局决策能力的自主规划器。真正可交付的动态航迹规划系统,还需要在 AFSIM 机制之上补齐触发管理、约束转换、候选评价、执行切换、失败降级、诊断记录和验证体系。
因此,判断一个系统“支持动态航迹规划”,至少要继续追问:支持什么障碍表达?如何触发重算?结果如何接回平台?几何路径是否满足运动约束?无解如何处理?证据来自源码、示例,还是已经完成了真实运行和联调?
只有把这些问题逐项回答,航迹规划才不只是一个看起来能返回路径的函数,而是一个可解释、可验证、可集成的仿真能力。
主要来源
D:\AFSIM\swdev\src\core\wsf\doc\route.rstD:\AFSIM\swdev\src\core\wsf\doc\route_mover.rstD:\AFSIM\swdev\src\core\wsf\doc\route_network.rstD:\AFSIM\swdev\src\core\wsf\doc\script\wsfroutefinder.rstD:\AFSIM\swdev\src\core\wsf\doc\script\wsfzoneroutefinder.rstD:\AFSIM\swdev\src\core\wsf\source\mover\WsfRoute.hppD:\AFSIM\swdev\src\core\wsf\source\mover\WsfWaypoint.hppD:\AFSIM\swdev\src\core\wsf\source\mover\WsfRouteMover.hppD:\AFSIM\swdev\src\core\wsf\source\mover\WsfRouteFinder.hpp/ WsfRouteFinder.cppD:\AFSIM\swdev\src\core\wsf\source\WsfZoneRouteFinder.hpp/ WsfZoneRouteFinder.cppD:\AFSIM\swdev\src\core\wsf\source\mover\WsfPathFinder.hpp/ WsfPathFinder.cppD:\AFSIM\demos\route_finder_demos\3d_route_finder.txtD:\AFSIM\demos\route_finder_demos\scenario_route_finder_1.txtD:\AFSIM\demos\route_finder_demos\zone_route_finder_1.txt