首页 > 代码库 > 关于 RecastNavigation 寻路结果异常的问题。

关于 RecastNavigation 寻路结果异常的问题。

  由于我们的项目采用的寻路解决方案是:客户端使用 unity 原生的寻路系统,服务器采用 RecastNavigation 系统,而服务器的寻路数据来自于从 unity 导出的,所以理论上两边的寻路结果应该是一样的,可事实上并非如此,unity 无论如何寻路,都能表现出比较自然的结果,但是服务器却有时会出现比较奇怪的结果。

技术分享

技术分享

技术分享

  由以上三张截图可以看到,起始点和终点稍微有点变化,结果就会出现较大的差异,寻路结果往往会“拐弯”,这种结果势必是不能接受的,unity 里不会出现这种情况,经过多次试验并仔细查看和分析,发现是因为寻路网格里出现了过于“细长”的三角形导致的,那如何修改呢,我在官方论坛里向作者(Mikko Mononen)提问(原帖地址点这里,自行解决fanqing问题):1.我觉得 unity 的寻路系统是基于原作者的 RecastNavigation 系统编写的;2.这种结果是否是由“细长”三角形导致的,该如何解决呢?

  很开心作者很快就恢复了:1.unity 的寻路系统确实是基于 RecastNavigation 编写的,但是 Detour 系统已经被深度改写了,(后来发现作者的 twitter 简介是 unity 的 ai 程序员);2.这个问题确实由于“细长”三角形引起的,NavMesh 系统当遇到特别宽或者三边长度比特别大的三角形时会出问题。而作者很久前就在自己的博客里叙述过这个问题,并给出了解决方案,博客地址:

http://digestingduck.blogspot.fi/2010/05/towards-better-navmesh-path-planning.html
http://digestingduck.blogspot.fi/2010/08/visibility-optimized-graph-experiment.html
 
解决方法是:改变A*寻路结果中节点的放置处理,NavMesh 系统里默认使用多边形边的中点来作为路径通过的点,如果想要好看的结果,应该使用距离最近的点,即你有 A, A 两个点在一条线段 L 的两边,那么这个点应该是线段 AB 和线段 L 的交点。
 
 1 if (neighbourNode->flags == 0) 2 { 3    float sa[3], sb[3]; 4    getPortalPoints(bestRef, bestPoly, bestTile, 5                    neighbourRef, neighbourPoly, neighbourTile, 6                    sa, sb); 7    float t = 0.5f; 8    dtDistancePtSegSqr2D(endPos, sa,sb, t); 9    t = dtClamp(t, 0.1f, 0.9f);10    dtVlerp(neighbourNode->pos, sa,sb, t);11 }

这段代码在:

https://github.com/memononen/recastnavigation/blob/master/Detour/Source/DetourNavMeshQuery.cpp#L1029
https://github.com/memononen/recastnavigation/blob/master/Detour/Source/DetourNavMeshQuery.cpp#L1321
 
也可以使用 "bestNode->pos" 替换 "endPos",这样就可以使用“最近”的点来替换当前 A* 里的“终点”,同时作者指出,像我这种客户端和服务器的解决方案都只能不断的调节调试去优化视觉上的结果,并不存在一个百分百准确的方案,不过很开心的是,这样修改完一实验,果然结果“正常”了,现在客户端和服务器的寻路90%的情况都能保持一致,极个别的情况还是会有差异,不过结果不会再特别“奇怪”,所以已经很满足了,太感谢原作者 mikko 同学了。
 
  题外话,上面这小段代码原来使用的是 getEdgeMidPoint 来求中点,作者用的 dtDistancePtSegSqr2D 这个函数是用来计算点 endPos 到线段 sa 和 sb 的垂直距离直线的交点 t,t 的数值就是线段 sa-t 和 线段 t-sb 的长度比值。当然这个结果不错了,但是还不是最好的,作者的原博客使用的是 dtIntersectSegSeg2D(ap, aq, bp, bq, s, t),计算的是线段 ap-aq 和线段 bp-bq 交点 m 在两条线段上的比值 s = ||ap-m|| / ||m-aq||,t = ||bp-m|| / ||m-bq||,这样一来计算出来的才是真正的最近距离,两点之间直线最短嘛;这个函数非常有意思,内部使用了 PertDotProduct 这个数学公式,这个才是最有意思的,但不太容易理解,就是向量 A 和向量 B 的 PertDotProduct 结果为:A 的法向量和向量B 的点乘积,几何意义是两条线段组成的平行四边形的面积(||a||.||b||.sina),而两条线段的交点分别在每个线段上的比值,正好可以通过计算 PertDotProduct 的比值来获得,因为他们组成了一个共底边的平行四边形,所以面积比等于高之比,而高正好分别就是其中一条线段的起点到交点的距离和起点到终点的距离。我自己的解决方案是选用的最后一种 dtIntersectSegSeg2D,效果也是最好的。
  最近一直太忙,都没更新,其实积累了很多心得在我的印象笔记里,慢慢补充到这里吧。
 
 

关于 RecastNavigation 寻路结果异常的问题。