伺服器3D場景建模(九):RecastNavigation之Detour資料結構
阿新 • • 發佈:2019-01-04
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找到凸多邊形節點,並與
起點到終點的中心點
距離做比較,最好距離的, 壓入棧,否則被淘汰。儲存最好距離的那個,並做好父子關係,方便回溯路徑。 - 沒有鄰接多邊形(即牆壁),則計算起點與之的距離,並與當前最好距離作比較。儲存最好距離的那個,並做好父子關係,方便回溯路徑。
- 一直棧迴圈,直到沒有節點。
- 出迴圈後,根據記錄的最好距離的節點。並回溯可以得出路過節點的路徑。