recastnavigation / recastnavigation

Industry-standard navigation-mesh toolset for games

Home Page:http://recastnav.com/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

rcFilterLowHangingWalkableObstacles Study

liubai01 opened this issue · comments

This docs summarizes the discussion in my reading group over rcFilterLowHangingWalkableObstacles.
The purpose of this post is to figure out that:

  • What is filtering low hangs ?
  • Why we need filtering low hangs ?

What is filtering low hangs?

Intuitively, rcFilterLowHangingWalkableObstacles would like to make those surfaces "climbable from ground" to be marked as walkable, controlled by a parameter called climbable distance. Even if those 'artifacts' over ground looks like unwalkable to some degree (a weird-shape sharp stone, etc.) since they could be easily leaped over, the area could be still viewed as walkable.

image

The rasterization (voxelization) is the first step in Recast Navmesh.

image

  • Depending on slope angle of input triangles, the voxels are marked as walkable and unwalkable initially.
  • Unity above uses a vivid figure to show the configurable max slope angle

rcFilterLowHangingWalkableObstacles is the very first step after voxelization in Recast.

The general idea is that: if one voxel is walkable, then the voxel above it in climbable distance (user-defined as a parameter) should also be set to walkable.

  • This step enlarges walkable surface.

Source code:

rcSpan* previousSpan = NULL;
bool previousWasWalkable = false;
unsigned char previousArea = RC_NULL_AREA;

for (rcSpan* span = heightfield.spans[x + z * xSize]; span != NULL; previousSpan = span, span = span->next)
{
    const bool walkable = span->area != RC_NULL_AREA;
    // If current span is not walkable, but there is walkable
    // span just below it, mark the span above it walkable too.
    if (!walkable && previousWasWalkable)
    {
        if (rcAbs((int)span->smax - (int)previousSpan->smax) <= walkableClimb)
        {
            span->area = previousArea;
        }
    }
    // Copy walkable flag so that it cannot propagate
    // past multiple non-walkable objects.
    previousWasWalkable = walkable;
    previousArea = span->area;
}

Here is an example that turns a small hanging plane into walkable after rcFilterLowHangingWalkableObstacles .

image

Why we need filtering low hanging ?

Base Motivation: Filter out high-frequency noise over a walkable plane.

The rasterization could be viewed as removing voxel-wise wrinkles (cell size & cell height) over a walkable-surface.

For obstacles in a larger scale (stones on the ground) but is still climbable for agents, some techniques are adopted.

At rasterization, Addspan would only propagate walkable flag over spans in a column if

  • spans are continuous (overlap)
    • (span is an intermediate structure when rasterizing triangles that represents continuous voxels in a column)
  • addSpan merges span when the difference of max heights of two spans is smaller than a threshold (climbable distance)

image

rcFilterLowHangingWalkableObstacles is a complementary step of addSpan to deal with region of mesh intersections. It propagates flags when spans are not continuous (like cases at left in figure above).

What if we skip that step? See the ablation experiment below.

image

The partial voxels of that slope is still marked as walkable due to addSpan. But the higher ones fail, since there is room underneath it, which shows that rcFilterLowHangingWalkableObstacles is necessary in some cases.

Reference

  1. #232

Excellent summary, here's my blog post from around the time the feature was implemented for further reference: https://digestingduck.blogspot.com/2010/01/rough-fringes.html

This is awesome. I'd love to add it to the official documentation!