拖更了一年回来填坑😂这应该是这个系列正文的最后一篇,之前的内容可以从这里找到
本篇主要分析一下项目中关于动态阻挡的处理方式,顺带对比回顾一下静态导航网格的生成流程
recastnavigation中提供了3种模式的导航网格:
- SoloMesh
- TileMesh
- TempObstacles
其中SoloMesh模式是静态的导航网格,即对场景build一次之后,将导航网格缓存起来供寻路使用,后续不再允许场景的地形发生变化
TempObstacles模式可以支持向场景中动态添加或移除预设形状的阻挡物,导航网格也会随之更新(不过只支持添加阻挡物,而不支持添加新的可行走区域)
在处理动态阻挡时,由于单个阻挡对地图的影响区域是有限的,所以会采用将地图切割成多个固定大小的tile,以tile为单位进行网格的生成。这样在添加或移除阻挡时,只需要处理与阻挡相交的tile,而不需要处理整个地图
TileMesh也是静态的导航网格,只是与SoloMesh相比它按tile来处理地图,算是介于SoloMesh和TempObstacles之间的一种模式
在分析TempBostacles模式之前,我们先来回顾一下导航网格寻路的主体思路,这在任何模式下都是通用的:
1.将场景模型转化为可行走面
2.将可行走面分割成凸多边形集合
3.计算凸多边形之间的联通关系
4.在凸多边形集合上规划路径
我们再来回顾一下SoloMesh是怎么具体实现这几步的:
1.标记导入的场景3角网格的可行走面
2.将三角面体素化,得到高度域
3.将高度域转化成紧缩高度域
4.通过区域划分算法合并紧缩高度域,得到连续的区域
5.求出区域的外轮廓
6.由外轮廓得到凸多边形集合
TempObstacles与SoloMesh的差异主要体现在两个方面:
1.按tile划分处理地图
2.在由紧缩高度域生成连续区域这一步中,加入通过阻挡物体剔除部分可行走空间的步骤
当动态阻挡被添加或移除时,会找到与阻挡相交的tile,并重新计算它的导航网格。
值得注意的是,生成高度域这一步只做一次,中间结果会以压缩格式存储在内存中。
这么做的意义,项目原作者Mikko Mononen大神在博客中是这样解释的:
将三角面体素化生成高度域这一步,会占据整个生成过程70%的时间,所以采用缓存的方式可以极大的加快动态生成导航网格的速度。而直接存储高度域结果又会比较消耗内存,所以以压缩存储的方式来存储(项目中使用fastLZ算法进行数据压缩)。
Triangle voxelization is usually dominating facter when generating a tile.
For example it takes 71.5% of the time to generate the highlighted tile in the test scene.
所以TempObstacles的运作流程大体是这样的,分成预处理阶段和更新处理阶段:
Preprocess
for each tile
- rasterize static triangles
- find walkable cells
- mark areas
- store compressed heighfield
At Run Time
for a changed tile
- uncompressed heighfield
- mark obstacles
- build regions
- build contours
- build poly mesh
- build detail mesh
- store navmesh
如果之前已经对SoloMesh有比较好的掌握的话,理解起来TempObstacles应该还是没有什么障碍的。
代码中比较难读懂的可能是这个函数(毕竟写了快600行)
bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf,
const int borderSize, const int walkableHeight,
rcHeightfieldLayerSet& lset)
它的作用是得到层次区域化的紧缩高度域,实际上它与SoloMesh中区域划分算法中的Layer partitoining算法几乎一样(我抄我自己)。我在这篇中单独分析一下Layer partitoining算法,这里就不做赘述了。
最后再说一下tile对连通性的处理:
当一个tile发生变化时,会先从网格中移除这个tile,此时对每个与这个tile相邻的其他tile,遍历其中的多边形,删除与目标tile相关的连接
然后再将新的tile加入到目标位置,对该tile中的每个多边形,遍历周围tile,判断是否有邻接的多边形,若有则建立新的连接