Recast工程
相关概念
- AABB(Axis-aligned bounding box)
- 高度场(Heightfield)
- 区间(Span)
- 紧缩高度场(CompactHeightfield)
- 紧缩区间(CompactSpan)
- BVH(包围体层次结构 Bounding volume hierarchy)
源码观看顺序
Sample_SoloMesh.cpp中的handleBuild()
总体过程
可在Sample_SoloMesh.cpp中的handleBuild()函数中的注释中看到
-
Initialize build config. (初始化参数)
-
Rasterize input polygon soup. (光栅化)
-
Filter walkables surfaces. (筛选可走表面)
-
Partition walkable surface to simple regions.(将可走表面划分为简单区域)
-
Trace and simplify region contours.(跟踪并简化区域轮廓)
-
Build polygons mesh from contours. (根据轮廓构建多边形网格)
-
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中的各种情况
- 没有与任何现有span相交 -> 直接添加
- 与现有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高度不够的标记为不可行走(比如床底下)
-
对于当前区间,如果当前区间是不可走的,但下面一个区间可走的。并且,两个区间顶部之间的距离能跨过去(即小于walkableClimb),则当前区间也是可走的。
见函数rcFilterLowHangingWalkableObstacles
-
壁架(ledge)检测(可以理解为过滤边界),如果从span的顶部向下到轴邻域的步进超过可配置的值,则span将被视为壁架并且不可遍历。可视化如下图,蓝色为ledge。
见函数rcFilterLedgeSpans
-
如果span的顶面上方有一个太近的障碍物,则该span的顶面是不可遍历的。可视化放在地板上的桌子。桌子下方地板的表面是平坦的,但由于不能在上面行走,因此不被认为是可移动的。
见函数rcFilterWalkableLowHeightSpans
三、将可走表面划分为简单区域
-
将高度场转换为紧缩高度场
两个紧缩span的联通条件是这样的:
1.两个底面的高度差小于可爬行高度
2.高底面与低顶面的高度差大于玩家模型高度 -
通过座席半径裁剪可步行区域
用dist存储span到障碍或边界的最小距离,最后通过dist筛选出所有小于指定距离的span并标记为不可走。
-
对高度场区域进行划分
对高度场进行分区,以便以后可以使用简单算法对可步行区域进行三角剖分。
有三种方法:
-
分水岭分区
- 经典的Recast分区
- 创建最好的细分
- 通常最慢
- 将Heightfield划分为没有孔或重叠的良好区域。
- 在某些极端情况下,此方法创建会产生孔洞和重叠
- 当小的障碍物靠近较大的开放区域时,可能会出现孔(三角剖分可以解决此问题)
- 如果您有狭窄的螺旋形走廊(即楼梯),则可能会发生重叠,这会使三角剖分失败
- 如果是预处理网格,通常是最佳选择,如果您有较大的开放区域,这种方法也适用。
-
单调分区
-
最快的
-
能将高度场划分为无孔和重叠的区域
-
创建长而细的多边形,有时会导致路径走弯
-
如果要快速生成导航网格,请使用此选项
-
-
按层分区
- 较快
- 将heighfield划分为非重叠区域
- 依靠三角剖分来处理孔(因此比单调分区要慢)
- 产生比单调分区更好的三角形
- 没有分水岭分区的特殊情况
- 速度可能很慢,并且会产生一些难看的镶嵌效果(仍然比单调效果更好),如果您的开放区域较大且障碍物较小(如果使用瓷砖则没有问题)
- 用于中小型瓷砖的导航网格的好选择
-
四、跟踪并简化区域轮廓
- 标记边界
flag[i] 表示 i 这个紧缩span的边界情况。
flag[i] 用二进制表示状态,第 j 位为1则表示 j 这个方向的相邻span是不同的区域
- 跟踪轮廓
关键函数walkContour
以一个边界span作为起始位置,顺时针方向判断它的4条边:
若当前边是区域分界边,则将边的一个顶点加入到轮廓顶点集中,并继续判断下一条边
若当前边不是区域分界边,则移动到与这条边相邻的span中(这个span是在同一个区域内),重新判断新的span的边
walkContour的结果
walkContour中包含了函数getCornerHeight,用于计算轮廓点的高度,它做的事情是考虑以一个顶点为中心的4块相邻格子的span,取这4个span中高度最高的span作为顶点高度。
walkContour迭代完可能会出现两种类型的轮廓。一种是我们普通认识到的轮廓,另一种则是空洞。
在有障碍的情况下就会出现空洞。
- 简化轮廓
首先不同区域的过渡点需要保留,放到simplified中
然后对于simplified中的每个相邻点对(假设记为AB),检查points中位置在AB之间的点,若这些点到AB的距离大于某个值maxError
则将其中距离最远的点加入到simplified中,重复这个过程直到所有点距离简化的轮廓线都不超过maxError
五、根据轮廓构建多边形网格
这一步的目的是把轮廓变成多个相邻凸多边形的集合
- 三角化每一个轮廓
- 合并三角轮廓
- 计算轮廓间的邻接关系(链式前向星)
六、创建详细网格,允许在每个多边形上访问近似高度。
对于每一个多边形
- 获取height patch
以种子为起始点,通过BFS获取与多边形相同区域id的span的高度
- 判断采样点对应的高度点与边的距离是否超过sampleMaxError,若超过,则需要用这个点重新构造外轮廓
- 将新的外轮廓三角化
问题记录
- 为什么光栅化过程中用的空间直线进行切分,后面是怎么连成多边形的?
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.