dtNavMesh
dtNavMesh是Detour中表达3D场景的数据结构。
作者给的注释为:
/// A navigation mesh based on tiles of convex polygons.
/// @ingroup detour
class dtNavMesh
{
// ... (代码略)...
}
A navigation mesh based on tiles of convex polygons.
百度翻译下:基于凸多边形瓦片的导航网格
非常精准的表达了 dtNavMesh 要构建的数据。
下面先看图,直观感受下
Detour图解
(橙色的线是我补上去的)
- 整个地图被划分为Tile,如橙色方块。
- Tile上有多个凸多边形,如橙色方块中有 A、C、D、F、G、H
- 每个凸多边形有
顶点
、边
构成。(图中就不画了) - 每个凸多边形有内接
邻居凸多边形
。如A内接C - 每个凸多边形可能有外接
邻居凸多边形
。如A外接B、E - 每个凸多边形可能没
相邻凸多边形
。如A接D,D是墙壁
- D、G是墙壁,则不需要数据结构,或储存什么,节省内存
- 每个凸多边形都是一个Node。(图中无法表现,寻路算法中就是基于凸多边形节点寻路)
dtNavMesh就是有效的组织了这些信息。
本质上,Tile信息是不需要的,只需要凸多边形及相关信息即可。如Solo Mesh一张地图就是一个Tile
基于Tile,可以方便动态改变地形;快速定位凸多边形等。
寻路过程
寻路接口有很多,思想都是类似。
下面分析下,dtNavMeshQuery::moveAlongSurface接口。
代码如下:(可以直接看后面代码分析)
dtStatus dtNavMeshQuery::moveAlongSurface(dtPolyRef startRef, const float* startPos, const float* endPos,
const dtQueryFilter* filter,
float* resultPos, dtPolyRef* visited, int* visitedCount, const int maxVisitedSize,
bool& bHit) const
{
dtAssert(m_nav);
dtAssert(m_tinyNodePool);
*visitedCount = 0;
// Validate input
if (!startRef)
return DT_FAILURE | DT_INVALID_PARAM;
if (!m_nav->isValidPolyRef(startRef))
return DT_FAILURE | DT_INVALID_PARAM;
dtStatus status = DT_SUCCESS;
static const int MAX_STACK = 48;
dtNode* stack[MAX_STACK];
int nstack = 0;
m_tinyNodePool->clear();
dtNode* startNode = m_tinyNodePool->getNode(startRef);
startNode->pidx = 0;
startNode->cost = 0;
startNode->total = 0;
startNode->id = startRef;
startNode->flags = DT_NODE_CLOSED;
stack[nstack++] = startNode;
float bestPos[3];
float bestDist = FLT_MAX;
dtNode* bestNode = 0;
dtVcopy(bestPos, startPos);
// Search constraints
float searchPos[3], searchRadSqr;
dtVlerp(searchPos, startPos, endPos, 0.5f);
searchRadSqr = dtSqr(dtVdist(startPos, endPos) / 2.0f + 0.001f);
float verts[DT_VERTS_PER_POLYGON * 3];
dtNode* wallNode = 0;
while (nstack)
{
// Pop front.
dtNode* curNode = stack[0];
for (int i = 0; i < nstack - 1; ++i)
stack[i] = stack[i + 1];
nstack--;
// Get poly and tile.
// The API input has been cheked already, skip checking internal data.
const dtPolyRef curRef = curNode->id;
const dtMeshTile* curTile = 0;
const dtPoly* curPoly = 0;
m_nav->getTileAndPolyByRefUnsafe(curRef, &curTile, &curPoly);
// Collect vertices.
const int nverts = curPoly->vertCount;
for (int i = 0; i < nverts; ++i)
dtVcopy(&verts[i * 3], &curTile->verts[curPoly->verts[i] * 3]);
// If target is inside the poly, stop search.
if (dtPointInPolygon(endPos, verts, nverts))
{
bestNode = curNode;
dtVcopy(bestPos, endPos);
break;
}
// Find wall edges and find nearest point inside the walls.
for (int i = 0, j = (int)curPoly->vertCount - 1; i < (int)curPoly->vertCount; j = i++)
{
// Find links to neighbours.
static const int MAX_NEIS = 8;
int nneis = 0;
dtPolyRef neis[MAX_NEIS];
if (curPoly->neis[j] & DT_EXT_LINK)
{
// Tile border.
for (unsigned int k = curPoly->firstLink; k != DT_NULL_LINK; k = curTile->links[k].next)
{
const dtLink* link = &curTile->links[k];
if (link->edge == j)
{
if (link->ref != 0)
{
const dtMeshTile* neiTile = 0;
const dtPoly* neiPoly = 0;
m_nav->getTileAndPolyByRefUnsafe(link->ref, &neiTile, &neiPoly);
if (filter->passFilter(link->ref, neiTile, neiPoly))
{
if (nneis < MAX_NEIS)
neis[nneis++] = link->ref;
}
}
}
}
}
else if (curPoly->neis[j])
{
const unsigned int idx = (unsigned int)(curPoly->neis[j] - 1);
const dtPolyRef ref = m_nav->getPolyRefBase(curTile) | idx;
if (filter->passFilter(ref, curTile, &curTile->polys[idx]))
{
// Internal edge, encode id.
neis[nneis++] = ref;
}
}
if (!nneis)
{
// Wall edge, calc distance.
const float* vj = &verts[j * 3];
const float* vi = &verts[i * 3];
float tseg;
const float distSqr = dtDistancePtSegSqr2D(endPos, vj, vi, tseg);
if (distSqr < bestDist)
{
// Update nearest distance.
dtVlerp(bestPos, vj, vi, tseg);
bestDist = distSqr;
bestNode = curNode;
wallNode = curNode;
}
}
else
{
for (int k = 0; k < nneis; ++k)
{
// Skip if no node can be allocated.
dtNode* neighbourNode = m_tinyNodePool->getNode(neis[k]);
if (!neighbourNode)
continue;
// Skip if already visited.
if (neighbourNode->flags & DT_NODE_CLOSED)
continue;
// Skip the link if it is too far from search constraint.
// TODO: Maybe should use getPortalPoints(), but this one is way faster.
const float* vj = &verts[j * 3];
const float* vi = &verts[i * 3];
float tseg;
float distSqr = dtDistancePtSegSqr2D(searchPos, vj, vi, tseg);
if (distSqr > searchRadSqr)
continue;
// Mark as the node as visited and push to queue.
if (nstack < MAX_STACK)
{
neighbourNode->pidx = m_tinyNodePool->getNodeIdx(curNode);
neighbourNode->flags |= DT_NODE_CLOSED;
stack[nstack++] = neighbourNode;
}
}
}
}
}
int n = 0;
if (bestNode)
{
// Reverse the path.
dtNode* prev = 0;
dtNode* node = bestNode;
do
{
dtNode* next = m_tinyNodePool->getNodeAtIdx(node->pidx);
node->pidx = m_tinyNodePool->getNodeIdx(prev);
prev = node;
node = next;
} while (node);
// Store result
node = prev;
do
{
visited[n++] = node->id;
if (n >= maxVisitedSize)
{
status |= DT_BUFFER_TOO_SMALL;
break;
}
node = m_tinyNodePool->getNodeAtIdx(node->pidx);
} while (node);
}
bHit = (wallNode != nullptr && wallNode == bestNode);
dtVcopy(resultPos, bestPos);
*visitedCount = n;
return status;
}
这段代码意思如下:
- 将待考察的节点压栈,第一个压栈的自然是开始节点A
- 开始遍历栈中元素
- 如果目的点在该凸多边形,则找到目的点以及目的点所在的凸多边形了。跳出栈循环。
- 如果目的点不在该凸多边形。则查看它的邻接多边形。
- 邻接多边形有3种,内接邻接多边形、外接邻居多边形、没有邻接多边形(即墙壁)。
- 内接邻接多边形、外接邻居多边形的处理,都是通过Tile找到凸多边形节点,并与
起点到终点的中心点
距离做比较,最好距离的, 压入栈,否则被淘汰。保存最好距离的那个,并做好父子关系,方便回溯路径。 - 没有邻接多边形(即墙壁),则计算起点与之的距离,并与当前最好距离作比较。保存最好距离的那个,并做好父子关系,方便回溯路径。
- 一直栈循环,直到没有节点。
- 出循环后,根据记录的最好距离的节点。并回溯可以得出路过节点的路径。