Added unit tests file to match RecastRasterization.cpp. Moved rcAddSpan tests there.

Also added a bunch of new tests that should cover most cases.  The test that checks invalid spans is currently failing
This commit is contained in:
Graham Pentheny
2026-02-08 15:44:44 -05:00
parent 13f4334438
commit 996553fc5a
3 changed files with 196 additions and 88 deletions

View File

@@ -22,6 +22,7 @@ target_sources(Tests PRIVATE
Recast/Tests_Alloc.cpp
Recast/Tests_Recast.cpp
Recast/Tests_RecastFilter.cpp
Recast/Tests_RecastRasterization.cpp
)
target_link_libraries(Tests PRIVATE Recast Detour DetourCrowd)

View File

@@ -511,94 +511,6 @@ TEST_CASE("rcClearUnwalkableTriangles", "[recast]")
}
}
TEST_CASE("rcAddSpan", "[recast]")
{
rcContext ctx(false);
float verts[] = {
1, 2, 3,
0, 2, 6
};
float bmin[3];
float bmax[3];
rcCalcBounds(verts, 2, bmin, bmax);
float cellSize = 1.5f;
float cellHeight = 2;
int width;
int height;
rcCalcGridSize(bmin, bmax, cellSize, &width, &height);
rcHeightfield hf;
REQUIRE(rcCreateHeightfield(&ctx, hf, width, height, bmin, bmax, cellSize, cellHeight));
int x = 0;
int y = 0;
unsigned short smin = 0;
unsigned short smax = 1;
unsigned char area = 42;
int flagMergeThr = 1;
SECTION("Add a span to an empty heightfield.")
{
bool result = rcAddSpan(&ctx, hf, x, y, smin, smax, area, flagMergeThr);
REQUIRE(result);
REQUIRE(hf.spans[0] != 0);
REQUIRE(hf.spans[0]->smin == smin);
REQUIRE(hf.spans[0]->smax == smax);
REQUIRE(hf.spans[0]->area == area);
}
SECTION("Add a span that gets merged with an existing span.")
{
bool result = rcAddSpan(&ctx, hf, x, y, smin, smax, area, flagMergeThr);
REQUIRE(result);
REQUIRE(hf.spans[0] != 0);
REQUIRE(hf.spans[0]->smin == smin);
REQUIRE(hf.spans[0]->smax == smax);
REQUIRE(hf.spans[0]->area == area);
smin = 1;
smax = 2;
result = rcAddSpan(&ctx, hf, x, y, smin, smax, area, flagMergeThr);
REQUIRE(result);
REQUIRE(hf.spans[0] != 0);
REQUIRE(hf.spans[0]->smin == 0);
REQUIRE(hf.spans[0]->smax == 2);
REQUIRE(hf.spans[0]->area == area);
}
SECTION("Add a span that merges with two spans above and below.")
{
smin = 0;
smax = 1;
REQUIRE(rcAddSpan(&ctx, hf, x, y, smin, smax, area, flagMergeThr));
REQUIRE(hf.spans[0] != 0);
REQUIRE(hf.spans[0]->smin == smin);
REQUIRE(hf.spans[0]->smax == smax);
REQUIRE(hf.spans[0]->area == area);
REQUIRE(hf.spans[0]->next == 0);
smin = 2;
smax = 3;
REQUIRE(rcAddSpan(&ctx, hf, x, y, smin, smax, area, flagMergeThr));
REQUIRE(hf.spans[0]->next != 0);
REQUIRE(hf.spans[0]->next->smin == smin);
REQUIRE(hf.spans[0]->next->smax == smax);
REQUIRE(hf.spans[0]->next->area == area);
smin = 1;
smax = 2;
REQUIRE(rcAddSpan(&ctx, hf, x, y, smin, smax, area, flagMergeThr));
REQUIRE(hf.spans[0] != 0);
REQUIRE(hf.spans[0]->smin == 0);
REQUIRE(hf.spans[0]->smax == 3);
REQUIRE(hf.spans[0]->area == area);
REQUIRE(hf.spans[0]->next == 0);
}
}
TEST_CASE("rcRasterizeTriangle", "[recast]")
{

View File

@@ -0,0 +1,195 @@
#include "Recast.h"
#include "catch2/catch_amalgamated.hpp"
TEST_CASE("rcAddSpan", "[recast][rasterization]")
{
rcContext ctx(false);
constexpr int xSize = 4;
constexpr int ySize = 10;
constexpr int zSize = 4;
constexpr float cellSize = 1.0f;
constexpr float cellHeight = 2.0f;
constexpr float minBounds[3] {0.0f, 0.0f, 0.0f};
constexpr float maxBounds[3] {cellSize * xSize, cellHeight * ySize, cellSize * zSize};
rcHeightfield hf;
REQUIRE(rcCreateHeightfield(&ctx, hf, xSize, zSize, minBounds, maxBounds, cellSize, cellHeight));
constexpr unsigned char area = 42;
constexpr int flagMergeThr = 1;
SECTION("Add a span to an empty heightfield.")
{
REQUIRE(rcAddSpan(&ctx, hf, 0, 0, 0, 1, area, flagMergeThr));
REQUIRE(hf.spans[0] != nullptr);
REQUIRE(hf.spans[0]->smin == 0);
REQUIRE(hf.spans[0]->smax == 1);
REQUIRE(hf.spans[0]->area == area);
REQUIRE(hf.spans[0]->next == nullptr);
}
SECTION("Adding invalid or zero-size spans does nothing.")
{
// min == max
REQUIRE(rcAddSpan(&ctx, hf, 0, 0, 0, 0, area, flagMergeThr));
REQUIRE(hf.spans[0] == nullptr);
// min > maxs
REQUIRE(rcAddSpan(&ctx, hf, 0, 0, 1, 0, area, flagMergeThr));
REQUIRE(hf.spans[0] == nullptr);
}
SECTION("Two spans that are not touching are not merged.")
{
REQUIRE(rcAddSpan(&ctx, hf, 0, 0, 0, 1, area, flagMergeThr));
REQUIRE(hf.spans[0] != nullptr);
REQUIRE(hf.spans[0]->smin == 0);
REQUIRE(hf.spans[0]->smax == 1);
REQUIRE(hf.spans[0]->area == area);
REQUIRE(hf.spans[0]->next == nullptr);
REQUIRE(rcAddSpan(&ctx, hf, 0, 0, 2, 3, area, flagMergeThr));
REQUIRE(hf.spans[0] != nullptr);
REQUIRE(hf.spans[0]->next != nullptr);
REQUIRE(hf.spans[0]->next->smin == 2);
REQUIRE(hf.spans[0]->next->smax == 3);
REQUIRE(hf.spans[0]->next->area == area);
REQUIRE(hf.spans[0]->next->next == nullptr);
}
SECTION("Two spans with different area ids within the flag merge threshold are merged and the highest area ID is used.")
{
REQUIRE(rcAddSpan(&ctx, hf, 0, 0, 0, 1, 42, flagMergeThr));
REQUIRE(hf.spans[0] != nullptr);
REQUIRE(hf.spans[0]->smin == 0);
REQUIRE(hf.spans[0]->smax == 1);
REQUIRE(hf.spans[0]->area == 42);
REQUIRE(hf.spans[0]->next == nullptr);
REQUIRE(rcAddSpan(&ctx, hf, 0, 0, 1, 2, 24, flagMergeThr));
REQUIRE(hf.spans[0] != nullptr);
REQUIRE(hf.spans[0]->smin == 0);
REQUIRE(hf.spans[0]->smax == 2);
REQUIRE(hf.spans[0]->area == 42); // Higher area ID takes precedent
REQUIRE(hf.spans[0]->next == nullptr);
}
SECTION("Two spans with different area ids outside the flag merge threshold are merged and the area ID of the last span added is used.")
{
REQUIRE(rcAddSpan(&ctx, hf, 0, 0, 0, 1, 42, flagMergeThr));
REQUIRE(hf.spans[0] != nullptr);
REQUIRE(hf.spans[0]->smin == 0);
REQUIRE(hf.spans[0]->smax == 1);
REQUIRE(hf.spans[0]->area == 42);
REQUIRE(hf.spans[0]->next == nullptr);
REQUIRE(rcAddSpan(&ctx, hf, 0, 0, 1, 8, 24, flagMergeThr));
REQUIRE(hf.spans[0] != nullptr);
REQUIRE(hf.spans[0]->smin == 0);
REQUIRE(hf.spans[0]->smax == 8);
REQUIRE(hf.spans[0]->area == 24); // Area ID of the last-added span takes precedent
REQUIRE(hf.spans[0]->next == nullptr);
}
SECTION("Add a span that gets merged with an existing span.")
{
REQUIRE(rcAddSpan(&ctx, hf, 0, 0, 0, 1, area, flagMergeThr));
REQUIRE(hf.spans[0] != nullptr);
REQUIRE(hf.spans[0]->smin == 0);
REQUIRE(hf.spans[0]->smax == 1);
REQUIRE(hf.spans[0]->area == area);
REQUIRE(hf.spans[0]->next == nullptr);
REQUIRE(rcAddSpan(&ctx, hf, 0, 0, 1, 2, area, flagMergeThr));
REQUIRE(hf.spans[0] != nullptr);
REQUIRE(hf.spans[0]->smin == 0);
REQUIRE(hf.spans[0]->smax == 2);
REQUIRE(hf.spans[0]->area == area);
REQUIRE(hf.spans[0]->next == nullptr);
}
SECTION("Add a span that merges with two spans above and below.")
{
REQUIRE(rcAddSpan(&ctx, hf, 0, 0, 0, 1, area, flagMergeThr));
REQUIRE(hf.spans[0] != nullptr);
REQUIRE(hf.spans[0]->smin == 0);
REQUIRE(hf.spans[0]->smax == 1);
REQUIRE(hf.spans[0]->area == area);
REQUIRE(hf.spans[0]->next == nullptr);
REQUIRE(rcAddSpan(&ctx, hf, 0, 0, 2, 3, area, flagMergeThr));
REQUIRE(hf.spans[0]->next != nullptr);
REQUIRE(hf.spans[0]->next->smin == 2);
REQUIRE(hf.spans[0]->next->smax == 3);
REQUIRE(hf.spans[0]->next->area == area);
REQUIRE(hf.spans[0]->next->next == nullptr);
// After adding the third span, they should all get merged into a single span.
REQUIRE(rcAddSpan(&ctx, hf, 0, 0, 1, 2, area, flagMergeThr));
REQUIRE(hf.spans[0] != nullptr);
REQUIRE(hf.spans[0]->smin == 0);
REQUIRE(hf.spans[0]->smax == 3);
REQUIRE(hf.spans[0]->area == area);
REQUIRE(hf.spans[0]->next == nullptr);
}
SECTION("Spans are insertion-sorted in ascending order of Y value.")
{
REQUIRE(rcAddSpan(&ctx, hf, 0, 0, 2, 3, area, flagMergeThr));
REQUIRE(rcAddSpan(&ctx, hf, 0, 0, 0, 1, area, flagMergeThr));
REQUIRE(rcAddSpan(&ctx, hf, 0, 0, 6, 7, area, flagMergeThr));
REQUIRE(hf.spans[0] != nullptr);
REQUIRE(hf.spans[0]->smin == 0);
REQUIRE(hf.spans[0]->smax == 1);
REQUIRE(hf.spans[0]->area == area);
REQUIRE(hf.spans[0]->next != nullptr);
REQUIRE(hf.spans[0]->next->smin == 2);
REQUIRE(hf.spans[0]->next->smax == 3);
REQUIRE(hf.spans[0]->next->area == area);
REQUIRE(hf.spans[0]->next->next != nullptr);
REQUIRE(hf.spans[0]->next->next->smin == 6);
REQUIRE(hf.spans[0]->next->next->smax == 7);
REQUIRE(hf.spans[0]->next->next->area == area);
REQUIRE(hf.spans[0]->next->next->next == nullptr);
}
SECTION("Adding a span inside another span merges them.")
{
REQUIRE(rcAddSpan(&ctx, hf, 0, 0, 0, 8, area, flagMergeThr));
REQUIRE(hf.spans[0] != nullptr);
REQUIRE(hf.spans[0]->smin == 0);
REQUIRE(hf.spans[0]->smax == 8);
REQUIRE(hf.spans[0]->area == area);
REQUIRE(hf.spans[0]->next == nullptr);
REQUIRE(rcAddSpan(&ctx, hf, 0, 0, 2, 3, area, flagMergeThr));
REQUIRE(hf.spans[0] != nullptr);
REQUIRE(hf.spans[0]->smin == 0);
REQUIRE(hf.spans[0]->smax == 8);
REQUIRE(hf.spans[0]->area == area);
REQUIRE(hf.spans[0]->next == nullptr);
}
SECTION("Overlapping spans are merged.")
{
REQUIRE(rcAddSpan(&ctx, hf, 0, 0, 0, 4, area, flagMergeThr));
REQUIRE(hf.spans[0] != nullptr);
REQUIRE(hf.spans[0]->smin == 0);
REQUIRE(hf.spans[0]->smax == 4);
REQUIRE(hf.spans[0]->area == area);
REQUIRE(hf.spans[0]->next == nullptr);
REQUIRE(rcAddSpan(&ctx, hf, 0, 0, 2, 6, area, flagMergeThr));
REQUIRE(hf.spans[0] != nullptr);
REQUIRE(hf.spans[0]->smin == 0);
REQUIRE(hf.spans[0]->smax == 6);
REQUIRE(hf.spans[0]->area == area);
REQUIRE(hf.spans[0]->next == nullptr);
}
}