mirror of
https://github.com/recastnavigation/recastnavigation.git
synced 2026-10-23 09:11:10 +00:00
Added more rcRasterizeTriangle unit tests and moved them to Tests_RecastRasterization.cpp
This commit is contained in:
@@ -511,163 +511,6 @@ TEST_CASE("rcClearUnwalkableTriangles", "[recast]")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("rcRasterizeTriangle", "[recast]")
|
||||
{
|
||||
rcContext ctx;
|
||||
float verts[] = {
|
||||
0, 0, 0,
|
||||
1, 0, 0,
|
||||
0, 0, -1
|
||||
};
|
||||
float bmin[3];
|
||||
float bmax[3];
|
||||
rcCalcBounds(verts, 3, bmin, bmax);
|
||||
|
||||
float cellSize = .5f;
|
||||
float cellHeight = .5f;
|
||||
|
||||
int width;
|
||||
int height;
|
||||
|
||||
rcCalcGridSize(bmin, bmax, cellSize, &width, &height);
|
||||
|
||||
rcHeightfield solid;
|
||||
REQUIRE(rcCreateHeightfield(&ctx, solid, width, height, bmin, bmax, cellSize, cellHeight));
|
||||
|
||||
unsigned char area = 42;
|
||||
int flagMergeThr = 1;
|
||||
|
||||
SECTION("Rasterize a triangle")
|
||||
{
|
||||
REQUIRE(rcRasterizeTriangle(&ctx, &verts[0], &verts[3], &verts[6], area, solid, flagMergeThr));
|
||||
|
||||
REQUIRE(solid.spans[0 + 0 * width]);
|
||||
REQUIRE(!solid.spans[1 + 0 * width]);
|
||||
REQUIRE(solid.spans[0 + 1 * width]);
|
||||
REQUIRE(solid.spans[1 + 1 * width]);
|
||||
|
||||
REQUIRE(solid.spans[0 + 0 * width]->smin == 0);
|
||||
REQUIRE(solid.spans[0 + 0 * width]->smax == 1);
|
||||
REQUIRE(solid.spans[0 + 0 * width]->area == area);
|
||||
REQUIRE(!solid.spans[0 + 0 * width]->next);
|
||||
|
||||
REQUIRE(solid.spans[0 + 1 * width]->smin == 0);
|
||||
REQUIRE(solid.spans[0 + 1 * width]->smax == 1);
|
||||
REQUIRE(solid.spans[0 + 1 * width]->area == area);
|
||||
REQUIRE(!solid.spans[0 + 1 * width]->next);
|
||||
|
||||
REQUIRE(solid.spans[1 + 1 * width]->smin == 0);
|
||||
REQUIRE(solid.spans[1 + 1 * width]->smax == 1);
|
||||
REQUIRE(solid.spans[1 + 1 * width]->area == area);
|
||||
REQUIRE(!solid.spans[1 + 1 * width]->next);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("rcRasterizeTriangle overlapping bb but non-overlapping triangle", "[recast]")
|
||||
{
|
||||
// This is a minimal repro case for the issue fixed in PR #476 (https://github.com/recastnavigation/recastnavigation/pull/476)
|
||||
rcContext ctx;
|
||||
|
||||
// create a heightfield
|
||||
float cellSize = 1;
|
||||
float cellHeight = 1;
|
||||
int width = 10;
|
||||
int height = 10;
|
||||
float bmin[] = { 0, 0, 0 };
|
||||
float bmax[] = { 10, 10, 10 };
|
||||
rcHeightfield heightfield;
|
||||
REQUIRE(rcCreateHeightfield(&ctx, heightfield, width, height, bmin, bmax, cellSize, cellHeight));
|
||||
|
||||
// rasterize a triangle outside of the heightfield.
|
||||
unsigned char area = 42;
|
||||
int flagMergeThr = 1;
|
||||
float verts[] =
|
||||
{
|
||||
-10.0, 5.5, -10.0,
|
||||
-10.0, 5.5, 3,
|
||||
3.0, 5.5, -10.0
|
||||
};
|
||||
REQUIRE(rcRasterizeTriangle(&ctx, &verts[0], &verts[3], &verts[6], area, heightfield, flagMergeThr));
|
||||
|
||||
// ensure that no spans were created
|
||||
for (int x = 0; x < width; ++x)
|
||||
{
|
||||
for (int z = 0; z < height; ++z)
|
||||
{
|
||||
rcSpan* span = heightfield.spans[x + z * heightfield.width];
|
||||
REQUIRE(span == NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("rcRasterizeTriangle smaller than half a voxel size in x", "[recast]")
|
||||
{
|
||||
SECTION("Skinny triangle along x axis")
|
||||
{
|
||||
rcContext ctx;
|
||||
float verts[] = {
|
||||
5, 0, 0.005f,
|
||||
5, 0, -0.005f,
|
||||
-5, 0, 0.005f,
|
||||
|
||||
-5, 0, 0.005f,
|
||||
5, 0, -0.005f,
|
||||
-5, 0, -0.005f,
|
||||
};
|
||||
float bmin[3];
|
||||
float bmax[3];
|
||||
rcCalcBounds(verts, 3, bmin, bmax);
|
||||
|
||||
float cellSize = 1;
|
||||
float cellHeight = 1;
|
||||
|
||||
int width;
|
||||
int height;
|
||||
|
||||
rcCalcGridSize(bmin, bmax, cellSize, &width, &height);
|
||||
|
||||
rcHeightfield solid;
|
||||
REQUIRE(rcCreateHeightfield(&ctx, solid, width, height, bmin, bmax, cellSize, cellHeight));
|
||||
|
||||
unsigned char areas[] = {42, 42};
|
||||
int flagMergeThr = 1;
|
||||
REQUIRE(rcRasterizeTriangles(&ctx, verts, areas, 2, solid, flagMergeThr));
|
||||
}
|
||||
|
||||
SECTION("Skinny triangle along z axis")
|
||||
{
|
||||
rcContext ctx;
|
||||
float verts[] = {
|
||||
0.005f, 0, 5,
|
||||
-0.005f, 0, 5,
|
||||
0.005f, 0, -5,
|
||||
|
||||
0.005f, 0, -5,
|
||||
-0.005f, 0, 5,
|
||||
-0.005f, 0, -5
|
||||
};
|
||||
float bmin[3];
|
||||
float bmax[3];
|
||||
rcCalcBounds(verts, 3, bmin, bmax);
|
||||
|
||||
float cellSize = 1;
|
||||
float cellHeight = 1;
|
||||
|
||||
int width;
|
||||
int height;
|
||||
|
||||
rcCalcGridSize(bmin, bmax, cellSize, &width, &height);
|
||||
|
||||
rcHeightfield solid;
|
||||
REQUIRE(rcCreateHeightfield(&ctx, solid, width, height, bmin, bmax, cellSize, cellHeight));
|
||||
|
||||
unsigned char areas[] = {42, 42};
|
||||
int flagMergeThr = 1;
|
||||
REQUIRE(rcRasterizeTriangles(&ctx, verts, areas, 2, solid, flagMergeThr));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("rcRasterizeTriangles", "[recast]")
|
||||
{
|
||||
rcContext ctx;
|
||||
|
||||
@@ -193,3 +193,388 @@ TEST_CASE("rcAddSpan", "[recast][rasterization]")
|
||||
REQUIRE(hf.spans[0]->next == nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("rcRasterizeTriangle", "[recast][rasterization]")
|
||||
{
|
||||
rcContext ctx(false);
|
||||
|
||||
constexpr int xSize = 10;
|
||||
constexpr int ySize = 10;
|
||||
constexpr int zSize = 10;
|
||||
|
||||
constexpr float cellSize = 1.0f;
|
||||
constexpr float cellHeight = 1.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));
|
||||
|
||||
SECTION("Simple triangle in XZ plane")
|
||||
{
|
||||
// Triangle in the XZ plane with vertices (0,0,0), (2,0,0), (0,0,2)
|
||||
//
|
||||
// Z ^
|
||||
// |
|
||||
// |\
|
||||
// |_\__> X
|
||||
//
|
||||
//
|
||||
// Z ....
|
||||
// X...
|
||||
// XX.. X
|
||||
//
|
||||
// Should fill cells: (0,0), (0,1), (1,0)
|
||||
|
||||
// Clockwise winding order so the normal points in the positive Y
|
||||
float v0[] = {0.0f, 0.0f, 0.0f};
|
||||
float v1[] = {2.0f, 0.0f, 0.0f};
|
||||
float v2[] = {0.0f, 0.0f, 2.0f};
|
||||
|
||||
REQUIRE(rcRasterizeTriangle(&ctx, v0, v1, v2, RC_WALKABLE_AREA, hf, 1));
|
||||
|
||||
// Check that only expected cells have spans
|
||||
for (int x = 0; x < xSize; x++)
|
||||
{
|
||||
for (int z = 0; z < zSize; z++)
|
||||
{
|
||||
rcSpan* span = hf.spans[x + z * hf.width];
|
||||
if ((x == 0 && z == 0) || (x == 0 && z == 1) || (x == 1 && z == 0))
|
||||
{
|
||||
REQUIRE(span != nullptr);
|
||||
REQUIRE(span->smin == 0);
|
||||
REQUIRE(span->smax == 1);
|
||||
REQUIRE(span->area == RC_WALKABLE_AREA);
|
||||
REQUIRE(span->next == nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
REQUIRE(span == nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Simple triangle inside a single voxel")
|
||||
{
|
||||
// Clockwise winding order so the normal points in the positive Y
|
||||
float v0[] = {0.0f, 0.0f, 0.0f};
|
||||
float v1[] = {1.0f, 0.0f, 0.0f};
|
||||
float v2[] = {0.0f, 0.0f, 1.0f};
|
||||
|
||||
REQUIRE(rcRasterizeTriangle(&ctx, v0, v1, v2, RC_WALKABLE_AREA, hf, 1));
|
||||
|
||||
// Check that only expected cells have spans
|
||||
for (int x = 0; x < xSize; x++)
|
||||
{
|
||||
for (int z = 0; z < zSize; z++)
|
||||
{
|
||||
rcSpan* span = hf.spans[x + z * hf.width];
|
||||
if (x == 0 && z == 0)
|
||||
{
|
||||
REQUIRE(span != nullptr);
|
||||
REQUIRE(span->smin == 0);
|
||||
REQUIRE(span->smax == 1);
|
||||
REQUIRE(span->area == RC_WALKABLE_AREA);
|
||||
REQUIRE(span->next == nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
REQUIRE(span == nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Triangles are clipped by the heightfield bounds")
|
||||
{
|
||||
// When clipped, this should be the same as the triangle in the previous test
|
||||
// Should fill cells: (0,0), (0,1), (1,0)
|
||||
|
||||
// Clockwise winding order so the normal points in the positive Y
|
||||
float v0[] = {-2.0f, 0.0f, -2.0f};
|
||||
float v1[] = {4.0f, 0.0f, -2.0f};
|
||||
float v2[] = {-2.0f, 0.0f, 4.0f};
|
||||
|
||||
REQUIRE(rcRasterizeTriangle(&ctx, v0, v1, v2, RC_WALKABLE_AREA, hf, 1));
|
||||
|
||||
// Check that only expected cells have spans
|
||||
for (int x = 0; x < xSize; x++)
|
||||
{
|
||||
for (int z = 0; z < zSize; z++)
|
||||
{
|
||||
rcSpan* span = hf.spans[x + z * hf.width];
|
||||
if ((x == 0 && z == 0) || (x == 0 && z == 1) || (x == 1 && z == 0))
|
||||
{
|
||||
REQUIRE(span != nullptr);
|
||||
REQUIRE(span->smin == 0);
|
||||
REQUIRE(span->smax == 1);
|
||||
REQUIRE(span->area == RC_WALKABLE_AREA);
|
||||
REQUIRE(span->next == nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
REQUIRE(span == nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Triangle outside of heightfield bounds rasterizes to nothing")
|
||||
{
|
||||
{ // Outside xz bounds
|
||||
float v0[] = {-5.0f, 0.0f, -5.0f};
|
||||
float v1[] = {-5.0f, 0.0f, 5.0f};
|
||||
float v2[] = {5.0f, 0.0f, -5.0f};
|
||||
REQUIRE(rcRasterizeTriangle(&ctx, v0, v1, v2, RC_WALKABLE_AREA, hf, 1));
|
||||
|
||||
// Check that no spans were added
|
||||
for (int x = 0; x < xSize; x++)
|
||||
{
|
||||
for (int z = 0; z < zSize; z++)
|
||||
{
|
||||
REQUIRE(hf.spans[x + z * hf.width] == nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{ // below y bounds
|
||||
float v0[] = {0.0f, -1.0f, 0.0f};
|
||||
float v1[] = {5.0f, -1.0f, 5.0f};
|
||||
float v2[] = {5.0f, -1.0f, 0.0f};
|
||||
REQUIRE(rcRasterizeTriangle(&ctx, v0, v1, v2, RC_WALKABLE_AREA, hf, 1));
|
||||
|
||||
// Check that no spans were added
|
||||
for (int x = 0; x < xSize; x++)
|
||||
{
|
||||
for (int z = 0; z < zSize; z++)
|
||||
{
|
||||
REQUIRE(hf.spans[x + z * hf.width] == nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{ // above y bounds
|
||||
float v0[] = {0.0f, 40.0f, 0.0f};
|
||||
float v1[] = {5.0f, 40.0f, 5.0f};
|
||||
float v2[] = {5.0f, 40.0f, 0.0f};
|
||||
REQUIRE(rcRasterizeTriangle(&ctx, v0, v1, v2, RC_WALKABLE_AREA, hf, 1));
|
||||
|
||||
// Check that no spans were added
|
||||
for (int x = 0; x < xSize; x++)
|
||||
{
|
||||
for (int z = 0; z < zSize; z++)
|
||||
{
|
||||
REQUIRE(hf.spans[x + z * hf.width] == nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Voxels are rasterized if the triangle overlaps any part of it at all")
|
||||
{
|
||||
// This triangle when clipped barely overlaps the area of the cell at 0,0
|
||||
|
||||
// Clockwise winding order so the normal points in the positive Y
|
||||
float v0[] = {-1.0f, 0.0f, -1.0f};
|
||||
float v1[] = {1.01f, 0.0f, -1.0f};
|
||||
float v2[] = {-1.0f, 0.0f, 1.01f};
|
||||
|
||||
REQUIRE(rcRasterizeTriangle(&ctx, v0, v1, v2, RC_WALKABLE_AREA, hf, 1));
|
||||
|
||||
// Check that only expected cells have spans
|
||||
for (int x = 0; x < xSize; x++)
|
||||
{
|
||||
for (int z = 0; z < zSize; z++)
|
||||
{
|
||||
rcSpan* span = hf.spans[x + z * hf.width];
|
||||
if (x == 0 && z == 0)
|
||||
{
|
||||
REQUIRE(span != nullptr);
|
||||
REQUIRE(span->smin == 0);
|
||||
REQUIRE(span->smax == 1);
|
||||
REQUIRE(span->area == RC_WALKABLE_AREA);
|
||||
REQUIRE(span->next == nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
REQUIRE(span == nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("A rasterized triangle vertical span includes all the voxels it is in any way part of.")
|
||||
{
|
||||
// Clockwise winding order so the normal points in the positive Y
|
||||
float v0[] = {0.0f, 0.0f, 0.0f};
|
||||
float v1[] = {0.5f, 0.0f, 0.5f};
|
||||
float v2[] = {0.5f, 2.01f, 0.5f};
|
||||
|
||||
REQUIRE(rcRasterizeTriangle(&ctx, v0, v1, v2, RC_WALKABLE_AREA, hf, 1));
|
||||
|
||||
// Check that only expected cells have spans
|
||||
for (int x = 0; x < xSize; x++)
|
||||
{
|
||||
for (int z = 0; z < zSize; z++)
|
||||
{
|
||||
rcSpan* span = hf.spans[x + z * hf.width];
|
||||
if (x == 0 && z == 0)
|
||||
{
|
||||
REQUIRE(span != nullptr);
|
||||
REQUIRE(span->smin == 0);
|
||||
REQUIRE(span->smax == 3);
|
||||
REQUIRE(span->area == RC_WALKABLE_AREA);
|
||||
REQUIRE(span->next == nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
REQUIRE(span == nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Sloped triangle produces varying span heights")
|
||||
{
|
||||
// Y ^
|
||||
// 2| /v2
|
||||
// | / \
|
||||
// 1| / \
|
||||
// | / \
|
||||
// --+-v0------v1----> X
|
||||
// | 0 2 4
|
||||
//
|
||||
// Should rasterize to...
|
||||
//
|
||||
// Y ^
|
||||
// 2| |--|--|
|
||||
// | | |
|
||||
// 1|--| + |--|
|
||||
// | |
|
||||
// --+--------------> X
|
||||
// |
|
||||
//
|
||||
|
||||
float v0[] = {0.0f, 0.0f, 0.5f};
|
||||
float v1[] = {4.0f, 0.0f, 0.5f};
|
||||
float v2[] = {2.0f, 2.0f, 0.5f};
|
||||
|
||||
REQUIRE(rcRasterizeTriangle(&ctx, v0, v1, v2, RC_WALKABLE_AREA, hf, 1));
|
||||
|
||||
// Check that only expected cells have spans
|
||||
for (int x = 0; x < xSize; x++)
|
||||
{
|
||||
for (int z = 0; z < zSize; z++)
|
||||
{
|
||||
rcSpan* span = hf.spans[x + z * hf.width];
|
||||
if ((x == 0 && z == 0) || (x == 3 && z == 0))
|
||||
{
|
||||
REQUIRE(span != nullptr);
|
||||
REQUIRE(span->smin == 0);
|
||||
REQUIRE(span->smax == 1);
|
||||
REQUIRE(span->area == RC_WALKABLE_AREA);
|
||||
REQUIRE(span->next == nullptr);
|
||||
}
|
||||
else if ((x == 1 && z == 0) || (x == 2 && z == 0))
|
||||
{
|
||||
REQUIRE(span != nullptr);
|
||||
REQUIRE(span->smin == 0);
|
||||
REQUIRE(span->smax == 2);
|
||||
REQUIRE(span->area == RC_WALKABLE_AREA);
|
||||
REQUIRE(span->next == nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
REQUIRE(span == nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Triangle crossing Y bounds gets clamped")
|
||||
{
|
||||
// This trangle should be clipped to both the max and min Y bounds of the heightfield.
|
||||
float v0[] = {0.0f, -5.0f, 0.0f};
|
||||
float v1[] = {1.0f, -5.0f, 1.0f};
|
||||
float v2[] = {0.5f, 15.0f, 0.5f};
|
||||
|
||||
REQUIRE(rcRasterizeTriangle(&ctx, v0, v1, v2, RC_WALKABLE_AREA, hf, 1));
|
||||
|
||||
// Check that only expected cells have spans
|
||||
for (int x = 0; x < xSize; x++)
|
||||
{
|
||||
for (int z = 0; z < zSize; z++)
|
||||
{
|
||||
rcSpan* span = hf.spans[x + z * hf.width];
|
||||
if (x == 0 && z == 0)
|
||||
{
|
||||
REQUIRE(span != nullptr);
|
||||
REQUIRE(span->smin == 0);
|
||||
REQUIRE(span->smax == 10);
|
||||
REQUIRE(span->area == RC_WALKABLE_AREA);
|
||||
REQUIRE(span->next == nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
REQUIRE(span == nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Degenerate triangles rasterize to nothing")
|
||||
{
|
||||
SKIP("Currently Recast rasterizes degenerate triangles as if they were a line or point of non-zero volume.");
|
||||
{ // Co-linear points
|
||||
float v0[] = {1.0f, 0.0f, 0.5f};
|
||||
float v1[] = {2.0f, 0.0f, 0.5f};
|
||||
float v2[] = {4.0f, 0.0f, 0.5f};
|
||||
REQUIRE(rcRasterizeTriangle(&ctx, v0, v1, v2, RC_WALKABLE_AREA, hf, 1));
|
||||
|
||||
// Check that no spans were added
|
||||
for (int x = 0; x < xSize; x++)
|
||||
{
|
||||
for (int z = 0; z < zSize; z++)
|
||||
{
|
||||
rcSpan* span = hf.spans[x + z * hf.width];
|
||||
REQUIRE(span == nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{ // All vertices are the same point
|
||||
float v0[] = {0.5f, 0.0f, 0.5f};
|
||||
REQUIRE(rcRasterizeTriangle(&ctx, v0, v0, v0, RC_WALKABLE_AREA, hf, 1));
|
||||
|
||||
// Check that no spans were added
|
||||
for (int x = 0; x < xSize; x++)
|
||||
{
|
||||
for (int z = 0; z < zSize; z++)
|
||||
{
|
||||
rcSpan* span = hf.spans[x + z * hf.width];
|
||||
REQUIRE(span == nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Triangles outside the heightfield with bounding boxes that overlap the heightfield rasterize nothing")
|
||||
{
|
||||
// This is a minimal repro case for the issue fixed in PR #476 (https://github.com/recastnavigation/recastnavigation/pull/476)
|
||||
float v0[] = {-10.0, 5.5, -10.0};
|
||||
float v1[] = {-10.0, 5.5, 3};
|
||||
float v2[] = {3.0, 5.5, -10.0};
|
||||
REQUIRE(rcRasterizeTriangle(&ctx, v0, v1, v2, RC_WALKABLE_AREA, hf, 1));
|
||||
|
||||
// Check that no spans were added
|
||||
for (int x = 0; x < xSize; x++)
|
||||
{
|
||||
for (int z = 0; z < zSize; z++)
|
||||
{
|
||||
REQUIRE(hf.spans[x + z * hf.width] == nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user