1. 程式人生 > >伺服器3D場景建模(九):RecastNavigation之Detour資料結構

伺服器3D場景建模(九):RecastNavigation之Detour資料結構

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圖解

圖1

(橙色的線是我補上去的)

  • 整個地圖被劃分為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找到凸多邊形節點,並與起點到終點的中心點距離做比較,最好距離的, 壓入棧,否則被淘汰。儲存最好距離的那個,並做好父子關係,方便回溯路徑。
  • 沒有鄰接多邊形(即牆壁),則計算起點與之的距離,並與當前最好距離作比較。儲存最好距離的那個,並做好父子關係,方便回溯路徑。
  • 一直棧迴圈,直到沒有節點。
  • 出迴圈後,根據記錄的最好距離的節點。並回溯可以得出路過節點的路徑。