走廊空间: DetourPathCorridor.h : class dtPathCorridor;
基本术语参考
- vertex: 多边形顶点
- Corridor : 多边形组成的连通区域 : 走廊, 对于更上层的pathfinding而言, poly被被抽象为点与点的连通关系. 这里展开后则为走廊.
- poly: 多边形: 一个多边形包含基础的顶点信息和link信息和基础类型数据等.
- Surface: 表面(行走面), 概念上为行走面, 在这里的实现则为poly组成的mesh表面
- mesh: 网, 概念上指的是指的由poly组成行走面的数据结构方式, 即mesh; 连通图.
- Corners: 挂点, 概念上是指的corridor走廊中的边上的顶点 通常为墙角(pathfinding近似最优解, 也是期望, 但未必总是 例如tile带来的边界)
- moveAlongSurface: 约束在行走面上的位置移动处理
- neis,eighbour: 邻居, 避让中的邻居为agent; corridor中的邻居则是poly
- node: 一般为脚手架, 即用于临时性的list的节点. navigation的实现封装层次较低, 不同结构中的node意义不同.
- edge: 概念上的边, 是link最主要的类型之一, 第一个边一般是指的下标0的顶点连向下一个顶点的边, 这里要注意 link是一个分离实现的链表, 一个当前poly的边可能有多个link标记属于当前边
- visited: 可认为是爬过(访问过)
- Off-mesh connections: 概念上是连线, 实现是poly: 只有两个边两个顶点的poly, 可双向连通; 即第一个link指向第一个边(顶点0)且该link指向的poly不是出发点点poly的的时候为连出, 否则为连入. 处于off-mesh移动的agent无法进行避让处理(不做避让处理). DT_OFFMESH_CON_BIDIR 标记为双向的offlink会同时添加在目标poly和源poly的link中. 而off-mesh poly本身没有双向还是单向的标记. 也不需要.
数据结构
1 | class dtPathCorridor |
1 | /** |
dtPathCorridor简述:
通过pathfinding给出来的poly path, 在pathfinding看来是一系列连通的点, 但是解开成poly后, 则是一系列相邻的poly组成的走廊空间.
corridor则是在走廊空间中进行局部移动规划(pathfinding是高级路径规划), 包括如何从一个poly走向另外一个poly, 移动转向, 避让带来的局部更新等. 比喻的话, pathfinding相当于导航地图给出来的路线, 而corridor则是通过雷达&摄像头等实时扫描绘制出来用于驾驶决策的现场交通环境.
dtPathCorridor
该函数设置新的路径和目标坐标点位置 但未处理起始点坐标.
1 | /// The current corridor position is expected to be within the first polygon in the path. The target |
fixPathStart
修复起始点坐标,
这里将会覆盖路径点中的第一个和第二个. 但是如果当前路径个数为1或者2 则会保留最后一个
0的作用略过.
1 | bool dtPathCorridor::fixPathStart(dtPolyRef safeRef, const float* safePos) |
movePosition
基于(约束于)navmesh行走面的内的位置变化(移动)处理, 新的位置仍然位于m_path[0]中.
- 这种移动可能导致组成走廊空间的拓扑非最优 以及 移动的路径非最优.
- 从旧的位置往新的位置沿着poly mesh行走面去找, 如果不在同一个poly则往相邻的poly去找, 如果不可达或者位置无效, 则得到的坐标和传入的期望位置可能会不同
- 找到新的有效位置后合并路径: 先通过moveAlongSurface找到从当前点到移动目标点的poly visited 路径, 然后用这个路径和现有路径进行合并.
- 例如如果新路径为已有路径的下个poly, 则减去当前poly(预期情况1);
- 例如当前移动仍然在m_path[0]的poly内, 则合并去重(预期情况2) (这里其实发生不必要的拷贝请求(memmove对相同地址和长度的请求应该不会执行));
- 如果避让跑偏了走到了新的poly, 则把当前path整体后移, 把当前移动带来的新的走廊path拼接起来, 拼接过程会尽可能的优化掉重复的的路径, 但是按照这个拼接 从走廊上来说是增加了新的走廊长度 因此需要通过局部visted重新优化局部路径.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21bool dtPathCorridor::movePosition(const float* npos, dtNavMeshQuery* navquery, const dtQueryFilter* filter)
{
// Move along navmesh and update new position.
float result[3];
static const int MAX_VISITED = 16;
dtPolyRef visited[MAX_VISITED];
int nvisited = 0;
dtStatus status = navquery->moveAlongSurface(m_path[0], m_pos, npos, filter,
result, visited, &nvisited, MAX_VISITED);
if (dtStatusSucceed(status)) {
m_npath = dtMergeCorridorStartMoved(m_path, m_npath, m_maxPath, visited, nvisited);
// Adjust the position to stay on top of the navmesh.
float h = m_pos[1];
navquery->getPolyHeight(m_path[0], result, &h);
result[1] = h;
dtVcopy(m_pos, result);
return true;
}
return false;
}
DT_VERTS_PER_POLYGON = 6; navigation中最大单个poly有6个顶点
沿着navmesh表面移动时, 最多stack MAX_STACK = 48
个poly节点, 超过后会丢弃.
每次移除当前poly并检查是否为目标点所在poly时, 如果没找到, 则添加所有符合条件的邻居到stack中,
每次stack邻居时, 这些邻居都通过pidx指向该poly(node) : 反向树结构 父节点不知道只节点, 但子节点知道父节点. 起始点所在poly为root node.
当我们找到目标点的所在的poly时候, 通过node->pidx 可以反向串起来完整的走廊空间. 逆序保存为 visited poly数组
每次visit一个poly时 会stack这个poly的所有邻居(过滤后)
1 | dtStatus dtNavMeshQuery::moveAlongSurface(dtPolyRef startRef, const float* startPos, const float* endPos, |
dtMergeCorridorStartMoved
合并path路径和visted路径. 见movePosition说明
1 | int dtMergeCorridorStartMoved(dtPolyRef* path, const int npath, const int maxPath, |
dtPathCorridor::findCorners
走廊的关键实现之一 其核心是findStraightPath, 即查找直线路径上的拐点. 是agent移动能力的核心支持.
该函数从起点到终点查找拐点数量 失败或超过最大数量或者遇到off-link poly后停止
optimizePathVisibility
g->params.updateFlags & DT_CROWD_OPTIMIZE_VIS
该函数通过局部可视化优化agent在走廊中的移动路线
其核心实现为朝目标点打一个射线, 如果可达则以可达走廊覆盖当前走廊
通常使用方式是, 从当前点打一个 跳过当前corner 到下一个corner的射线, 成功则拉直移动路径.
optimizePathTopology
DT_CROWD_OPTIMIZE_TOPO
和可视化不同, 这个是通过局部寻路进行修正和确认当前走廊是否为最优.
moveTargetPosition
和movePosition相似, 因目标发生变化而进行走廊调整 .