Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 15 additions & 13 deletions .github/workflows/Build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ jobs:
- name: Download & install premake
working-directory: RecastDemo
run: |
curl -L -o premake.tar.gz https://github.com/premake/premake-core/releases/download/v5.0.0-beta2/premake-5.0.0-beta2-macosx.tar.gz
curl -L -o premake.tar.gz https://github.com/premake/premake-core/releases/download/v5.0.0-beta8/premake-5.0.0-beta8-macosx.tar.gz
tar -xzf premake.tar.gz
rm premake.tar.gz
chmod +x ./premake5

- name: Run premake
working-directory: RecastDemo
Expand Down Expand Up @@ -98,16 +99,17 @@ jobs:
- name: Download & Install premake
working-directory: RecastDemo
run: |
curl -L -o premake.tar.gz https://github.com/premake/premake-core/releases/download/v5.0.0-beta2/premake-5.0.0-beta2-linux.tar.gz
curl -L -o premake.tar.gz https://github.com/premake/premake-core/releases/download/v5.0.0-beta8/premake-5.0.0-beta8-linux.tar.gz
tar -xzf premake.tar.gz
rm premake.tar.gz
chmod +x ./premake5

- name: Run premake
working-directory: RecastDemo
run: ./premake5 --cc=${{matrix.compiler}} gmake2
run: ./premake5 --cc=${{matrix.compiler}} gmake

- name: Build
working-directory: RecastDemo/Build/gmake2
working-directory: RecastDemo/Build/gmake
run: make config=${{matrix.conf}} verbose=true

linux-cmake:
Expand Down Expand Up @@ -140,15 +142,15 @@ jobs:
- Debug
- Release
vs-version:
- vs2019
- vs2022
- vs2026
include:
- vs-version: vs2019
version-range: '16.0'
runner: windows-2019
- vs-version: vs2022
version-range: '17.0'
runner: windows-2022
- vs-version: vs2026
version-range: '18.0'
runner: windows-2025

runs-on: ${{matrix.runner}}

Expand All @@ -173,7 +175,7 @@ jobs:
working-directory: RecastDemo
shell: pwsh
run: |
(new-object System.Net.WebClient).DownloadFile("https://github.com/premake/premake-core/releases/download/v5.0.0-beta2/premake-5.0.0-beta2-windows.zip","${{github.workspace}}/RecastDemo/premake.zip")
(new-object System.Net.WebClient).DownloadFile("https://github.com/premake/premake-core/releases/download/v5.0.0-beta8/premake-5.0.0-beta8-windows.zip","${{github.workspace}}/RecastDemo/premake.zip")
tar -xf premake.zip
del premake.zip

Expand All @@ -192,15 +194,15 @@ jobs:
- Debug
- Release
vs-version:
- vs2019
- vs2022
- vs2026
include:
- vs-version: vs2019
cmake-generator: Visual Studio 16 2019
runner: windows-2019
- vs-version: vs2022
cmake-generator: Visual Studio 17 2022
runner: windows-2022
- vs-version: vs2026
cmake-generator: Visual Studio 18 2026
runner: windows-2025

runs-on: ${{matrix.runner}}

Expand Down
12 changes: 7 additions & 5 deletions .github/workflows/Tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ jobs:
- name: Download & install premake
working-directory: RecastDemo
run: |
curl -L -o premake.tar.gz https://github.com/premake/premake-core/releases/download/v5.0.0-beta2/premake-5.0.0-beta2-macosx.tar.gz
curl -L -o premake.tar.gz https://github.com/premake/premake-core/releases/download/v5.0.0-beta8/premake-5.0.0-beta8-macosx.tar.gz
tar -xzf premake.tar.gz
rm premake.tar.gz
chmod +x ./premake5

- name: Run premake
working-directory: RecastDemo
Expand Down Expand Up @@ -55,16 +56,17 @@ jobs:
- name: Download & Install premake
working-directory: RecastDemo
run: |
curl -L -o premake.tar.gz https://github.com/premake/premake-core/releases/download/v5.0.0-beta2/premake-5.0.0-beta2-linux.tar.gz
curl -L -o premake.tar.gz https://github.com/premake/premake-core/releases/download/v5.0.0-beta8/premake-5.0.0-beta8-linux.tar.gz
tar -xzf premake.tar.gz
rm premake.tar.gz
chmod +x ./premake5

- name: Run premake
working-directory: RecastDemo
run: ./premake5 --cc=clang gmake2
run: ./premake5 --cc=clang gmake

- name: Build
working-directory: RecastDemo/Build/gmake2
working-directory: RecastDemo/Build/gmake
run: make config=debug verbose=true

- name: Run Tests
Expand Down Expand Up @@ -93,7 +95,7 @@ jobs:
working-directory: RecastDemo
shell: pwsh
run: |
(new-object System.Net.WebClient).DownloadFile("https://github.com/premake/premake-core/releases/download/v5.0.0-beta2/premake-5.0.0-beta2-windows.zip","${{github.workspace}}/RecastDemo/premake.zip")
(new-object System.Net.WebClient).DownloadFile("https://github.com/premake/premake-core/releases/download/v5.0.0-beta8/premake-5.0.0-beta8-windows.zip","${{github.workspace}}/RecastDemo/premake.zip")
tar -xf premake.zip
del premake.zip

Expand Down
118 changes: 101 additions & 17 deletions Recast/Source/RecastFilter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,22 @@
//

#include "Recast.h"
#include "RecastAlloc.h"
#include "RecastAssert.h"

#include <stdlib.h>

namespace
{
const int MAX_HEIGHTFIELD_HEIGHT = 0xffff; // TODO (graham): Move this to a more visible constant and update usages.

struct FilterSpan
{
rcSpan* source;
unsigned short smin;
unsigned short smax;
unsigned char area;
};
}

void rcFilterLowHangingWalkableObstacles(rcContext* context, const int walkableClimb, rcHeightfield& heightfield)
Expand Down Expand Up @@ -72,49 +81,101 @@ void rcFilterLedgeSpans(rcContext* context, const int walkableHeight, const int

const int xSize = heightfield.width;
const int zSize = heightfield.height;
const int columnCount = xSize * zSize;

// Ledge filtering repeatedly scans the four neighbouring columns for every
// span. Flatten the linked lists once so those scans read contiguous data.
rcTempVector<int> columnOffsets(columnCount + 1, 0);
int spanCount = 0;
for (int columnIndex = 0; columnIndex < columnCount; ++columnIndex)
{
columnOffsets[columnIndex] = spanCount;
for (const rcSpan* span = heightfield.spans[columnIndex]; span != NULL; span = span->next)
{
++spanCount;
}
}
columnOffsets[columnCount] = spanCount;

rcTempVector<FilterSpan> spans(spanCount);
int spanIndex = 0;
for (int columnIndex = 0; columnIndex < columnCount; ++columnIndex)
{
for (rcSpan* span = heightfield.spans[columnIndex]; span != NULL; span = span->next)
{
FilterSpan& filterSpan = spans[spanIndex++];
filterSpan.source = span;
filterSpan.smin = (unsigned short)span->smin;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this used?

filterSpan.smax = (unsigned short)span->smax;
filterSpan.area = (unsigned char)span->area;
}
}

// Mark spans that are adjacent to a ledge as unwalkable..
for (int z = 0; z < zSize; ++z)
{
for (int x = 0; x < xSize; ++x)
{
for (rcSpan* span = heightfield.spans[x + z * xSize]; span; span = span->next)
const int columnIndex = x + z * xSize;
const int columnEnd = columnOffsets[columnIndex + 1];
int neighborColumnStarts[4];
int neighborColumnEnds[4];
int firstHigherNeighborCeilings[4];
for (int direction = 0; direction < 4; ++direction)
{
const int neighborX = x + rcGetDirOffsetX(direction);
const int neighborZ = z + rcGetDirOffsetY(direction);
if (neighborX < 0 || neighborZ < 0 || neighborX >= xSize || neighborZ >= zSize)
{
neighborColumnStarts[direction] = -1;
neighborColumnEnds[direction] = -1;
firstHigherNeighborCeilings[direction] = -1;
continue;
}

const int neighborColumnIndex = neighborX + neighborZ * xSize;
neighborColumnStarts[direction] = columnOffsets[neighborColumnIndex];
neighborColumnEnds[direction] = columnOffsets[neighborColumnIndex + 1];
firstHigherNeighborCeilings[direction] = neighborColumnStarts[direction] + 1;
}
for (int currentSpanIndex = columnOffsets[columnIndex]; currentSpanIndex < columnEnd; ++currentSpanIndex)
{
FilterSpan& span = spans[currentSpanIndex];
// Skip non-walkable spans.
if (span->area == RC_NULL_AREA)
if (span.area == RC_NULL_AREA)
{
continue;
}

const int floor = (int)(span->smax);
const int ceiling = span->next ? (int)(span->next->smin) : MAX_HEIGHTFIELD_HEIGHT;
const int floor = (int)(span.smax);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even though this code pretty much looks like C it's C++ and it has static_cast.

const int ceiling = currentSpanIndex + 1 < columnEnd
? (int)(spans[currentSpanIndex + 1].smin) : MAX_HEIGHTFIELD_HEIGHT;

// The difference between this walkable area and the lowest neighbor walkable area.
// This is the difference between the current span and all neighbor spans that have
// enough space for an agent to move between, but not accounting at all for surface slope.
int lowestNeighborFloorDifference = MAX_HEIGHTFIELD_HEIGHT;

// Min and max height of accessible neighbours.
int lowestTraversableNeighborFloor = span->smax;
int highestTraversableNeighborFloor = span->smax;
int lowestTraversableNeighborFloor = span.smax;
int highestTraversableNeighborFloor = span.smax;

for (int direction = 0; direction < 4; ++direction)
{
const int neighborX = x + rcGetDirOffsetX(direction);
const int neighborZ = z + rcGetDirOffsetY(direction);

// Skip neighbours which are out of bounds.
if (neighborX < 0 || neighborZ < 0 || neighborX >= xSize || neighborZ >= zSize)
if (neighborColumnStarts[direction] < 0)
{
lowestNeighborFloorDifference = -walkableClimb - 1;
break;
}

const rcSpan* neighborSpan = heightfield.spans[neighborX + neighborZ * xSize];
int neighborSpanIndex = neighborColumnStarts[direction];
const int neighborColumnEnd = neighborColumnEnds[direction];

// The most we can step down to the neighbor is the walkableClimb distance.
// Start with the area under the neighbor span
int neighborCeiling = neighborSpan ? (int)neighborSpan->smin : MAX_HEIGHTFIELD_HEIGHT;
int neighborCeiling = neighborSpanIndex < neighborColumnEnd
? (int)spans[neighborSpanIndex].smin : MAX_HEIGHTFIELD_HEIGHT;

// Skip neighbour if the gap between the spans is too small.
if (rcMin(ceiling, neighborCeiling) - floor > walkableHeight)
Expand All @@ -123,11 +184,32 @@ void rcFilterLedgeSpans(rcContext* context, const int walkableHeight, const int
break;
}

// Spans are sorted by height. Any neighbour whose next ceiling is at or below
// floor + walkableHeight will take the overlap test's continue branch below.
// Seek past that prefix instead of rescanning it for every higher span.
const int minNeighborCeiling = floor + walkableHeight;
if (ceiling <= minNeighborCeiling)
{
continue;
}
int& firstHigherCeiling = firstHigherNeighborCeilings[direction];
while (firstHigherCeiling < neighborColumnEnd
&& (int)spans[firstHigherCeiling].smin <= minNeighborCeiling)
{
++firstHigherCeiling;
}
neighborSpanIndex = firstHigherCeiling - 1;

// For each span in the neighboring column...
for (; neighborSpan != NULL; neighborSpan = neighborSpan->next)
for (; neighborSpanIndex < neighborColumnEnd; ++neighborSpanIndex)
{
const int neighborFloor = (int)neighborSpan->smax;
neighborCeiling = neighborSpan->next ? (int)neighborSpan->next->smin : MAX_HEIGHTFIELD_HEIGHT;
const int neighborFloor = (int)spans[neighborSpanIndex].smax;
if (neighborFloor + walkableHeight >= ceiling)
{
break;
}
neighborCeiling = neighborSpanIndex + 1 < neighborColumnEnd
? (int)spans[neighborSpanIndex + 1].smin : MAX_HEIGHTFIELD_HEIGHT;

// Only consider neighboring areas that have enough overlap to be potentially traversable.
if (rcMin(ceiling, neighborCeiling) - rcMax(floor, neighborFloor) <= walkableHeight)
Expand Down Expand Up @@ -161,12 +243,14 @@ void rcFilterLedgeSpans(rcContext* context, const int walkableHeight, const int
// the magnitude of the delta)
if (lowestNeighborFloorDifference < -walkableClimb)
{
span->area = RC_NULL_AREA;
span.area = RC_NULL_AREA;
span.source->area = RC_NULL_AREA;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is the only use case for source. FilterSpan could just store a pointer to it.

}
// If the difference between all neighbor floors is too large, this is a steep slope, so mark the span as an unwalkable ledge.
else if (highestTraversableNeighborFloor - lowestTraversableNeighborFloor > walkableClimb)
{
span->area = RC_NULL_AREA;
span.area = RC_NULL_AREA;
span.source->area = RC_NULL_AREA;
}
}
}
Expand Down
Loading