1、watershed

rcBuildDistanceField()//通过calculateDistanceField ,求取距离,与前面的erode前部分操作类似,与erode不同的是,这一步将只保存到short类型的chf.dist中了,并且是在erode做完操作的基础上进行计算的,所以与erode操作是一个承接的关系。再通过boxBlur将距离做一个平滑。
rcBuildRegion()//

参考下面的代码可以发现dir与坐标的对应关系:0(-1,0),1(0,1),2(1,0),3(0,-1)

inline int rcGetDirOffsetX(int dir)
{static const int offset[4] = { -1, 0, 1, 0, };return offset[dir&0x03];
}
inline int rcGetDirOffsetY(int dir)
{static const int offset[4] = { 0, 1, 0, -1 };return offset[dir&0x03];
}

rcBuildRegions

sortCellsByLevel()//通过离边距离(distance)设置level,每隔单位2 就作为一个level,后面计算region ,从最大distance开始逐层的提取level ,可能是从内存和效率的综合考虑,将nbstack设置成8, 设置成2也应该是没有问题的

expandRegions,对上一级level的范围向当前level进行扩张一圈span。第一次进入的时候,因为没有上一级level所以这个函数基本上只是填充了一下stack。当对第二级level进行处理的时候,会将第一级level与第二级level相邻的在第二级level范围内的span的region值设置成第一级level的region值,达到对第一级level扩张的目的。对于后面的级别中的span,如果与夸级的region相邻,会被设置成对应的相邻的region值。这个扩张还会将distance做个记录。

        const int w = chf.width;const int h = chf.height;if (fillStack){// Find cells revealed by the raised level.stack.clear();for (int y = 0; y < h; ++y){for (int x = 0; x < w; ++x){const rcCompactCell& c = chf.cells[x+y*w];for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i){if (chf.dist[i] >= level && srcReg[i] == 0 && chf.areas[i] != RC_NULL_AREA){stack.push_back(LevelStackEntry(x, y, i));}}}}}else // use cells in the input stack{// mark all cells which already have a regionfor (int j=0; j<stack.size(); j++){int i = stack[j].index;if (srcReg[i] != 0)stack[j].index = -1;}}rcTempVector<DirtyEntry> dirtyEntries;int iter = 0;while (stack.size() > 0){int failed = 0;dirtyEntries.clear();for (int j = 0; j < stack.size(); j++){int x = stack[j].x;int y = stack[j].y;int i = stack[j].index;if (i < 0){failed++;continue;}unsigned short r = srcReg[i];unsigned short d2 = 0xffff;const unsigned char area = chf.areas[i];const rcCompactSpan& s = chf.spans[i];
//找到边缘span,计算出新的distsrc,使用最小distsrc对应的region,做上级level(可能有多个level)在本级level的span的膨胀操作,
//有最高迭代次数限制,从高的level span往低的level span计算for (int dir = 0; dir < 4; ++dir)          {if (rcGetCon(s, dir) == RC_NOT_CONNECTED) continue;const int ax = x + rcGetDirOffsetX(dir);const int ay = y + rcGetDirOffsetY(dir);const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir);if (chf.areas[ai] != area) continue;if (srcReg[ai] > 0 && (srcReg[ai] & RC_BORDER_REG) == 0){//srcReg[ai]>0说明srcReg是之前迭代过程计算出来的结果,之前计算的都是上级的level中的spanif ((int)srcDist[ai]+2 < (int)d2){r = srcReg[ai];d2 = srcDist[ai]+2;}}}if (r){stack[j].index = -1; // mark as useddirtyEntries.push_back(DirtyEntry(i, r, d2));}else{failed++;}}// Copy entries that differ between src and dst to keep them in sync.for (int i = 0; i < dirtyEntries.size(); i++) {
//将有边缘reg及新计算的distance 传递给对应的记录出来的span,level是当前的level,
//region却是上一级的region,区别就是dist被设置为非0值 int idx = dirtyEntries[i].index;srcReg[idx] = dirtyEntries[i].region;srcDist[idx] = dirtyEntries[i].distance2;}if (failed == stack.size())break;if (level > 0){++iter;if (iter >= maxIter)//最大迭代次数???break;}}
                        for (int j = 0; j<lvlStacks[sId].size(); j++) //对当前的level中的span逐个做的floodregion操作。{LevelStackEntry current = lvlStacks[sId][j];int x = current.x;int y = current.y;int i = current.index;if (i >= 0 && srcReg[i] == 0)//如果region值为0,结合上面的expandregions来理解,这里不会对新的level边缘的span做floodregion操作。{if (floodRegion(x, y, i, level, regionId, chf, srcReg, srcDist, stack)){if (regionId == 0xFFFF){ctx->log(RC_LOG_ERROR, "rcBuildRegions: Region ID overflow");return false;}regionId++;}}}

floodregion//从最高的level开始,进行淹没

        。。。。。srcReg[i] = r; //最开始的时候就将要操作的span的srcregion和stcdist进行了设置srcDist[i] = 0;。。。。。while (stack.size() > 0){LevelStackEntry& back = stack.back();int cx = back.x;int cy = back.y;int ci = back.index; //current indexstack.pop_back();const rcCompactSpan& cs = chf.spans[ci];// Check if any of the neighbours already have a valid region set.unsigned short ar = 0;for (int dir = 0; dir < 4; ++dir) //顺时针方向找到第一个不同region的span就break出来,
//表示该span是一个region的边缘{// 8 connected ,顺序分别为(左,左上,上,右上,右,右下,下,左下)if (rcGetCon(cs, dir) != RC_NOT_CONNECTED){const int ax = cx + rcGetDirOffsetX(dir);const int ay = cy + rcGetDirOffsetY(dir);const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(cs, dir);if (chf.areas[ai] != area)continue;unsigned short nr = srcReg[ai];if (nr & RC_BORDER_REG) // Do not take borders into account.continue;if (nr != 0 && nr != r) //相邻的span的region有值,且当前span的region值与这个相邻的
//span的region值不相等,才会记录下相邻的span的region值,这表示当前span是一个region边缘
//结合前面的expandregions理解,其实这样的span也属于这个level的{ar = nr;break;}const rcCompactSpan& as = chf.spans[ai];const int dir2 = (dir+1) & 0x3;if (rcGetCon(as, dir2) != RC_NOT_CONNECTED){const int ax2 = ax + rcGetDirOffsetX(dir2);const int ay2 = ay + rcGetDirOffsetY(dir2);const int ai2 = (int)chf.cells[ax2+ay2*w].index + rcGetCon(as, dir2);if (chf.areas[ai2] != area)continue;unsigned short nr2 = srcReg[ai2];if (nr2 != 0 && nr2 != r){ar = nr2;break;}}               }}if (ar != 0)//如果span被判定为region边缘span,做如下操作。
//说明:经过expandregions,可能不在上级level(比上一个level低)的span在
//expandRegions操作时也被划分到上级level所在的region去了。也就是说region的边是用低级的level的span组成的{srcReg[ci] = 0; //先设置该region边缘span的region值为0,
//如果有region边缘则会进入这里,并且经过几次迭代之后该span的值最终会在后面的for循环中被设置r。continue;}count++;// Expand neighbours.for (int dir = 0; dir < 4; ++dir) //通过level的判定,标记出新的region 。
//有当前span向四周扩散,找出满足条件的进行迭代计算。{if (rcGetCon(cs, dir) != RC_NOT_CONNECTED){const int ax = cx + rcGetDirOffsetX(dir);const int ay = cy + rcGetDirOffsetY(dir);const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(cs, dir);if (chf.areas[ai] != area)continue;if (chf.dist[ai] >= lev && srcReg[ai] == 0)//通过lev这个筛选条件到当前位置的四周的span,
//并对满足条件的span进行迭代,直到没有,可以想象这个stack可能会非常大,所以前面设置128,
//可能只是一个简单测试用的比较稳定的大小。{srcReg[ai] = r;srcDist[ai] = 0;stack.push_back(LevelStackEntry(ax, ay, ai));}}}}

mergeAndFilterRegions ,修改合并region,里面采用了一个rcRegion的结构体,该结构体用于辅助计算

walkContour,沿着边缘走,找出当前region有交界的region的值,并做记录。主要思路是贴边走,类似走迷宫的通用方法。 理解这里主要是需要有一个棋盘场景,如下:

假设由2,2切入,在walkContour之前 已经通过下面这段代码判断了2,2是边缘点,并且找出了一个边缘方向,传入进RecastRegion 中的walkContour,也就是只有找到边缘span才会进入walkContour。并且经过前面的reg.connections.size() > 0 ,将已经做过该操作的region 的其他span直接快速过滤掉了。这里需要注意,walkContour对多边形采用的是顺时针方向存放顶点的,这个对后面很重要。

for (int dir = 0; dir < 4; ++dir)//判断是否是边缘span
{if (isSolidEdge(chf, srcReg, x, y, i, dir)){ndir = dir;break;}
}

通过上面插图中的0~16 步,和下面的代码注解,大概可以理解边缘的识别和获取相邻区域的region的过程。

if (isSolidEdge(chf, srcReg, x, y, i, dir))//拿出当前边缘span的不同region的span的region值。做个保存,并将方向向顺时针方向转动一下
{//这里有一点需要注意的是,当前span所在的region,是不会被保存到数组中的。结合上面的动态图可以看出,因为这里永远操作的是不在当前region的span。// Choose the edge cornerunsigned short r = 0;if (rcGetCon(s, dir) != RC_NOT_CONNECTED){const int ax = x + rcGetDirOffsetX(dir);const int ay = y + rcGetDirOffsetY(dir);const int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(s, dir);r = srcReg[ai];}if (r != curReg){curReg = r;cont.push(curReg);}dir = (dir+1) & 0x3;  // Rotate CW
}
else//对非边缘节点。先向指定方向进1 ,在向逆时针方向转动一下。
{int ni = -1;const int nx = x + rcGetDirOffsetX(dir);const int ny = y + rcGetDirOffsetY(dir);if (rcGetCon(s, dir) != RC_NOT_CONNECTED){const rcCompactCell& nc = chf.cells[nx+ny*chf.width];ni = (int)nc.index + rcGetCon(s, dir);}if (ni == -1){// Should not happen.return;}x = nx;y = ny;i = ni;dir = (dir+3) & 0x3;    // Rotate CCW
}

canMergeWithRegion ,过滤了几种情况,一个是相邻两region某一方或双方重复出现在对方connections中的情况(>1),一个是相邻两region出现在对方floor中(floor在生成的时候就不包含第一层)。保证mergeRegion阶段做的merge region操作能顺利正确完成。

recast 3 partition相关推荐

  1. recast SoloMesh生成过程详解

    最近游戏中需要使用navmesh,阅读recast过程中顺手把学习记录写下来,方便以后查看. 使用过程主要需要理解每个数据含义,至于生成过程,不做深入研究. 相关资料 recast源码 很好的参考资料 ...

  2. RecastNavigation源码阅读之Recast工程

    Recast工程 相关概念 AABB(Axis-aligned bounding box) 高度场(Heightfield) 区间(Span) 紧缩高度场(CompactHeightfield) 紧缩 ...

  3. [CareerCup] 2.4 Partition List 划分链表

    2.4 Write code to partition a linked list around a value x, such that all nodes less than x come bef ...

  4. min聚合函数查询带有额外字段sql|dense_rank()over(partition)|+班级学生成绩最高

    oracle爱好者和群snowg的问题 上面的这个,有站点stationid,year,month,day和每天记录的day_tmin字段. 现在要求统计处每个stationid下面每月每日的最小da ...

  5. 使用ROW_NUMBER 和partition by 解决报表中的查询问题

    在报表中遇到一个查询问题: 原始数据如下: Id cust_id call_date call_result 1 1 2012-03-15 09:00:00 fail 2 1 2012-03-15 0 ...

  6. D - Triangle Partition HDU - 6300 sort(cmp)

    D - Triangle Partition HDU - 6300 题解 由于三点不共线,且三角形不相交,则对坐标排序,输出 #include<bits/stdc++.h> using n ...

  7. mysql col与row_使用mysql实现row_number() over(partition by col1 order by col2)函数

    [color=red]row_number() OVER (PARTITION BY COL1 ORDER BY COL2)[/color] 表示根据COL1分组,在分组内部根据 COL2排序,而此函 ...

  8. Leetcode PHP题解--D14 561. Array Partition I

    561. Array Partition I 题目链接 561. Array Partition I 题目分析 本题给了一个数组,要求将数组分为n个只有2个元素的一对. 使得每对数字中最小的数加起来的 ...

  9. 【leetcode】86. Partition List

    题目如下: Given a linked list and a value x, partition it such that all nodes less than x come before no ...

最新文章

  1. 运放电路复习,放大器、加法器、积分器、差分放大电路等
  2. Java数据库 高级查询
  3. python 文件处理1:将某一目录下的文件合并
  4. java中将对象转为基本数据类型
  5. 基于MATLAB的LS-SVM实现方法以及SVM的一些知识点
  6. .gitignore失效 无法忽略node_modules问题
  7. git的常用的使用方法
  8. 11、OAuth和OpenID服务
  9. vue小demo易错点总结
  10. asp.net DataGridTree表格树控件 下拉树 DropTree c# .net
  11. mysql 逐行读取文件_PHP fgets()和fgetss():逐行读取文件
  12. Gromacs动力学模拟
  13. 单目标跟踪、多目标跟踪、单目标跟踪发展现状、多目标跟踪发展现状
  14. 计算机mod函数,MOD函数的公式语法及使用方法实例
  15. rss阅读器保存html文件,4款在线RSS阅读器使用体验
  16. 微信小程序快速上手(学习笔记总结)
  17. 区块链网络端口及证书
  18. c5绑定steam显示服务器内部错误,C5GAME实现Steam非正常交易自动拦截,轻松规避诈骗...
  19. Python + kivy
  20. 大学“电路分析基础”试题合集第三章

热门文章

  1. hexo 菜单_Hexo 自定义主题和菜单
  2. wordpress伪静态如何支持中文(目前不支持分类目录中文)
  3. 【会助力】多样化签到方式,让会议灵活高效
  4. 项目配置Swagger2生成API接口文档
  5. 外卖APP,别跟我谈什么用户粘度,他们有吗?
  6. JAVA编程环境搭建 JDK与环境变量、Eclipse
  7. div高度固定,内容区不换行,不要滚动条
  8. 博科光纤交换机常用指令
  9. python2 gb2312编码文件批量转成utf-8
  10. golang 3DES 加密