服务器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找到凸多边形节点,并与起点到终点的中心点距离做比较,最好距离的, 压入栈,否则被淘汰。保存最好距离的那个,并做好父子关系,方便回溯路径。
  • 没有邻接多边形(即墙壁),则计算起点与之的距离,并与当前最好距离作比较。保存最好距离的那个,并做好父子关系,方便回溯路径。
  • 一直栈循环,直到没有节点。
  • 出循环后,根据记录的最好距离的节点。并回溯可以得出路过节点的路径。
  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

fananchong2

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值