recast navigation
简介
recast navigation是基于三角形网格的寻路系统,他相比四边形网格可以减少网格数量从而提高网格速度。它具体可以分为两部分,recast和detour。recast是构建寻路网格的过程,detour是利用网格寻路的过程
recast
具体构建过程是根据Sample_SoloMesh.cpp中的handleBuild
体素化
在体素化阶段,mesh将被转化成体素(一个个小的长方体)。
体素化的目的是填充heightfield的span区域,span是一个二维数组,长和宽是包围盒内体素的长和宽。而每一个span是rcSpan*类型,他是一个链表,链接竖直方向的体素
构建过程:
rcAllocHeightfield:new一个rcHeightfield实例
rcCreateHeightfield: 初始化rcHeightfield
rcMarkWalkableTriangles: 根据传进来的mesh(顶点和三角形)计算出哪个三角形可以通过。具体方法为计算每个三角形的法线,如果法线角超过设定值则认为不可通过
rcRasterizeTriangles(rcContext ctx, const float verts, const int nv, const unsigned short tris, const unsigned char areas, const int nt, rcHeightfield& solid, const int flagMergeThr): 体素化三角形。其中areas就是上一步计算出来的可通过区域。
// 其中最重要的一个函数为rasterizeTri,用来体素化每一个三角形
// v0 v1 v2是三角形三个顶点
static bool rasterizeTri(const float* v0, const float* v1, const float* v2,
const unsigned char area, rcHeightfield& hf,
const float* bmin, const float* bmax,
const float cs, const float ics, const float ich,
const int flagMergeThr)
{
const int w = hf.width;
const int h = hf.height;
float tmin[3], tmax[3];
const float by = bmax[1] - bmin[1];
// Calculate the bounding box of the triangle.
rcVcopy(tmin, v0);
rcVcopy(tmax, v0);
rcVmin(tmin, v1);
rcVmin(tmin, v2);
rcVmax(tmax, v1);
rcVmax(tmax, v2);
// If the triangle does not touch the bbox of the heightfield, skip the triagle.
if (!overlapBounds(bmin, bmax, tmin, tmax))
return true;
// Calculate the footprint of the triangle on the grid's y-axis
int y0 = (int)((tmin[2] - bmin[2])*ics); // y1-y0表示y坐标上占据了多少个体素
int y1 = (int)((tmax[2] - bmin[2])*ics);
// use -1 rather than 0 to cut the polygon properly at the start of the tile
y0 = rcClamp(y0, -1, h-1);
y1 = rcClamp(y1, 0, h-1);
// Clip the triangle into all grid cells it touches.
float buf[7*3*4];
float *in = buf, *inrow = buf+7*3, *p1 = inrow+7*3, *p2 = p1+7*3;
rcVcopy(&in[0], v0);
rcVcopy(&in[1*3], v1);
rcVcopy(&in[2*3], v2);
int nvrow, nvIn = 3;
for (int y = y0; y <= y1; ++y)
{
// Clip polygon to row. Store the remaining polygon as well
const float cz = bmin[2] + y*cs;
// 将三角形裁减到z=[cz, cz+cs]平面之间。
dividePoly(in, nvIn, inrow, &nvrow, p1, &nvIn, cz+cs, 2);
...
for (int x = x0; x <= x1; ++x)
{
// Clip polygon to column. store the remaining polygon as well
const float cx = bmin[0] + x*cs;
// 将三角形裁减到x=[cx, cx+cs]平面之间。
dividePoly(inrow, nv2, p1, &nv, p2, &nv2, cx+cs, 0);
...
// Calculate min and max of the span.
float smin = p1[1], smax = p1[1];
for (int i = 1; i < nv; ++i)
{
smin = rcMin(smin, p1[i*3+1]);
smax = rcMax(smax, p1[i*3+1]);
}
smin -= bmin[1];
smax -= bmin[1];
// Skip the span if it is outside the heightfield bbox
if (smax < 0.0f) continue;
if (smin > by) continue;
// Clamp the span to the heightfield bbox.
if (smin < 0.0f) smin = 0;
if (smax > by) smax = by;
//此时,in中存贮的为裁减后的多边形。
//计算新多边形y轴的最大和最小值
// Snap the span to the heightfield height grid.
unsigned short ismin = (unsigned short)rcClamp((int)floorf(smin * ich), 0, RC_SPAN_MAX_HEIGHT);
unsigned short ismax = (unsigned short)rcClamp((int)ceilf(smax * ich), (int)ismin+1, RC_SPAN_MAX_HEIGHT);
// addSpan中还有合并操作,是因为之前的三角形可能已经占据了这个span,但是由于它包括的[ismin, ismax]和当前加入的[ismin, ismax]有重合,则两个区间相与
if (!addSpan(hf, x, y, ismin, ismax, area, flagMergeThr))
return false;
}
}
return true;
}
生成区域
生成区域是将一个个的体素划分成若干区域,这些区域最终可以化为简单的多边形。
步骤:
- rcFilterLowHangingWalkableObstacles:处理之前不可行走的区域,因为有些区域是可以翻越过去的,因此当两个体素的高度差小于climbHeight并且前面一个区域可以行走时认为后面一个区域也可以行走
- rcFilterLedgeSpans:这个是将高度差过大的两个区域认定为不可行走(如悬崖)
- rcFilterWalkableLowHeightSpans:将高于某个高度的span认定为不可行走
- rcBuildCompactHeightfield: 构建开放高度场, 其中heightfield中的span转变成了compatHeightField中的cell和span。cell是一个二维数组,每一项有index和count两个变量,index表示该位置的体素在span中的起始位置,count表示该位置的体素数量(y坐标体素数量)
- rcErodeWalkableArea:过于接近障碍物的span被认定为不可行走
- 进行划分,划分有三种方法:
- 分水岭算法(Watershed partitioning): 传统的划分方法,通常最慢但是划分出来的区域最好
- Monotone partioning: 划分速度最快,但是效果可能不好
- Layer partitoining:速度居于两者中间,并且比较依赖初始数据
生成轮廓
上面的区域是体素的集合,这一步就是将体素转化成多边形。
步骤:
- rcBuildContours: 构建轮廓类
- rcBuildPolyMesh:构建多边形类
All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Comment