Recast工程

相关概念

  1. AABB(Axis-aligned bounding box)
  2. 高度场(Heightfield)
  3. 区间(Span)
  4. 紧缩高度场(CompactHeightfield)
  5. 紧缩区间(CompactSpan)
  6. BVH(包围体层次结构 Bounding volume hierarchy)

源码观看顺序

Sample_SoloMesh.cpp中的handleBuild()

总体过程

可在Sample_SoloMesh.cpp中的handleBuild()函数中的注释中看到

  1. Initialize build config. (初始化参数)

  2. Rasterize input polygon soup. (光栅化)

  3. Filter walkables surfaces. (筛选可走表面)

  4. Partition walkable surface to simple regions.(将可走表面划分为简单区域)

  5. Trace and simplify region contours.(跟踪并简化区域轮廓)

  6. Build polygons mesh from contours. (根据轮廓构建多边形网格)

  7. Create detail mesh which allows to access approximate height on each polygon.

    (创建详细网格,允许在每个多边形上访问近似高度。)

一、光栅化(Rasterization)

源码文件在Recast/Source/RecastRasterization.cpp中

1. 标记可行走的三角形

根据三角形的倾斜度判断三角形是否可走

// 源码
const float walkableThr = cosf(walkableSlopeAngle/180.0f*RC_PI);

float norm[3];

for (int i = 0; i < nt; ++i)
{
    const int* tri = &tris[i*3];
    //标准化
    calcTriNormal(&verts[tri[0]*3], &verts[tri[1]*3], &verts[tri[2]*3], norm);
    // Check if the face is walkable.
    if (norm[1] > walkableThr)
        areas[i] = RC_WALKABLE_AREA;
}

2. 光栅化

循环遍历足迹内的所有高度场网格列,并得出与该列相交的源多边形部分。如果发生相交,则派生一个新的“修剪”多边形。然后确定修剪的多边形的最小-最大高度。这代表由源多边形遮挡的列部分。

* span添加到HeightField中的各种情况

  1. 没有与任何现有span相交 -> 直接添加
  2. 与现有span相交 -> 合并

当新的span与现有span合并时,必须对两者合并后的span是否可走进行判断。此“可走标志”仅适用于span的顶面。如果设置,则表示span的顶部表示斜率足够低以可遍历的多边形。

span在合并的时候,如果同一个span,有的面是可行走,有的是不可行走,在合并之后会变成可行走。细节逻辑可以看源码

(合并部分源码)

// Merge spans.
if (cur->smin < s->smin)
    s->smin = cur->smin;
if (cur->smax > s->smax)
    s->smax = cur->smax;

// Merge flags.
if (rcAbs((int)s->smax - (int)cur->smax) <= flagMergeThr)
    s->area = rcMax(s->area, cur->area);

// Remove current span.
rcSpan* next = cur->next;
freeSpan(hf, cur);
if (prev)
    prev->next = next;
else
    hf.spans[idx] = next;
cur = next;

二、筛选可走表面

源码文件在Recast/Source/RecastFilter.cpp中

根据walkableClimb和walkableHeight这两个参数对span的可行走性做一些修正
将一些原本是不可走面,但可以通过其他可行走的地方爬上去的span标记为可行走(比如楼梯台阶垂直面对应的span)
将一些原本是可行走面,但span高度不够的标记为不可行走(比如床底下)

  1. 对于当前区间,如果当前区间是不可走的,但下面一个区间可走的。并且,两个区间顶部之间的距离能跨过去(即小于walkableClimb),则当前区间也是可走的。

    见函数rcFilterLowHangingWalkableObstacles

  2. 壁架(ledge)检测(可以理解为过滤边界),如果从span的顶部向下到轴邻域的步进超过可配置的值,则span将被视为壁架并且不可遍历。可视化如下图,蓝色为ledge。

    见函数rcFilterLedgeSpans

ledge

  1. 如果span的顶面上方有一个太近的障碍物,则该span的顶面是不可遍历的。可视化放在地板上的桌子。桌子下方地板的表面是平坦的,但由于不能在上面行走,因此不被认为是可移动的。

    见函数rcFilterWalkableLowHeightSpans

三、将可走表面划分为简单区域

  1. 将高度场转换为紧缩高度场

    两个紧缩span的联通条件是这样的:
    1.两个底面的高度差小于可爬行高度
    2.高底面与低顶面的高度差大于玩家模型高度

    img

  2. 通过座席半径裁剪可步行区域

    用dist存储span到障碍或边界的最小距离,最后通过dist筛选出所有小于指定距离的span并标记为不可走。

  3. 对高度场区域进行划分

    对高度场进行分区,以便以后可以使用简单算法对可步行区域进行三角剖分。

    有三种方法:

    1. 分水岭分区

      • 经典的Recast分区
      • 创建最好的细分
      • 通常最慢
      • 将Heightfield划分为没有孔或重叠的良好区域。
      • 在某些极端情况下,此方法创建会产生孔洞和重叠
        • 当小的障碍物靠近较大的开放区域时,可能会出现孔(三角剖分可以解决此问题)
        • 如果您有狭窄的螺旋形走廊(即楼梯),则可能会发生重叠,这会使三角剖分失败
      • 如果是预处理网格,通常是最佳选择,如果您有较大的开放区域,这种方法也适用。
    2. 单调分区

      • 最快的

      • 能将高度场划分为无孔和重叠的区域

      • 创建长而细的多边形,有时会导致路径走弯

      • 如果要快速生成导航网格,请使用此选项

    3. 按层分区

      • 较快
      • 将heighfield划分为非重叠区域
      • 依靠三角剖分来处理孔(因此比单调分区要慢)
      • 产生比单调分区更好的三角形
      • 没有分水岭分区的特殊情况
      • 速度可能很慢,并且会产生一些难看的镶嵌效果(仍然比单调效果更好),如果您的开放区域较大且障碍物较小(如果使用瓷砖则没有问题)
      • 用于中小型瓷砖的导航网格的好选择

四、跟踪并简化区域轮廓

  1. 标记边界

flag[i] 表示 i 这个紧缩span的边界情况。

flag[i] 用二进制表示状态,第 j 位为1则表示 j 这个方向的相邻span是不同的区域

  1. 跟踪轮廓

关键函数walkContour

以一个边界span作为起始位置,顺时针方向判断它的4条边:
若当前边是区域分界边,则将边的一个顶点加入到轮廓顶点集中,并继续判断下一条边
若当前边不是区域分界边,则移动到与这条边相邻的span中(这个span是在同一个区域内),重新判断新的span的边

walkContour的结果

walkContour中包含了函数getCornerHeight,用于计算轮廓点的高度,它做的事情是考虑以一个顶点为中心的4块相邻格子的span,取这4个span中高度最高的span作为顶点高度。

walkContour迭代完可能会出现两种类型的轮廓。一种是我们普通认识到的轮廓,另一种则是空洞。

在有障碍的情况下就会出现空洞。

  1. 简化轮廓

首先不同区域的过渡点需要保留,放到simplified中
然后对于simplified中的每个相邻点对(假设记为AB),检查points中位置在AB之间的点,若这些点到AB的距离大于某个值maxError
则将其中距离最远的点加入到simplified中,重复这个过程直到所有点距离简化的轮廓线都不超过maxError

img

img

五、根据轮廓构建多边形网格

这一步的目的是把轮廓变成多个相邻凸多边形的集合

  1. 三角化每一个轮廓
  2. 合并三角轮廓
  3. 计算轮廓间的邻接关系(链式前向星)

六、创建详细网格,允许在每个多边形上访问近似高度。

对于每一个多边形

  1. 获取height patch

以种子为起始点,通过BFS获取与多边形相同区域id的span的高度

  1. 判断采样点对应的高度点与边的距离是否超过sampleMaxError,若超过,则需要用这个点重新构造外轮廓
  2. 将新的外轮廓三角化

问题记录

  1. 为什么光栅化过程中用的空间直线进行切分,后面是怎么连成多边形的?

import time

tgt = "2021-04-01 10:00:00"

tgtTime = time.mktime(time.strptime(tgt,"%Y-%m-%d %H:%M:%S"))

curTime = time.time()

if curTime < tgtTime and curTime + 10.0 * 24 * 60 * 60 > tgtTime :

​ 报警

参考

http://www.critterai.org/projects/nmgen_study/

https://www.jianshu.com/p/64469a410b5d

https://blog.csdn.net/Windgs_YF/article/details/87805424

https://blog.csdn.net/u012138730/article/details/80009847

Q.E.D.