mirror of
https://github.com/recastnavigation/recastnavigation.git
synced 2026-09-07 11:03:16 +00:00
614 lines
17 KiB
C++
614 lines
17 KiB
C++
#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);
|
|
}
|
|
|
|
}
|
|
|
|
TEST_CASE("allocSpan", "[recast][rasterization]")
|
|
{
|
|
rcContext ctx(false);
|
|
|
|
constexpr int xSize = 50;
|
|
constexpr int zSize = 50;
|
|
constexpr int ySize = 10;
|
|
|
|
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("Attempting to add more spans than the span pool size allocates a new page")
|
|
{
|
|
for (int x = 0; x < xSize; x++)
|
|
{
|
|
for (int z = 0; z < zSize; z++)
|
|
{
|
|
REQUIRE(rcAddSpan(&ctx, hf, x, z, 0, 1, area, flagMergeThr));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|