diff --git a/Recast/Include/Recast.h b/Recast/Include/Recast.h
index 2a5e075c..2104aa5e 100644
--- a/Recast/Include/Recast.h
+++ b/Recast/Include/Recast.h
@@ -321,6 +321,8 @@ struct rcHeightfield
float cs; ///< The size of each cell. (On the xz-plane.)
float ch; ///< The height of each cell. (The minimum increment along the y-axis.)
rcSpan** spans; ///< Heightfield of spans (width*height).
+
+ // memory pool for rcSpan instances.
rcSpanPool* pools; ///< Linked list of span pools.
rcSpan* freelist; ///< The next free span.
@@ -998,23 +1000,21 @@ bool rcRasterizeTriangles(rcContext* context,
const float* verts, const unsigned char* triAreaIDs, int numTris,
rcHeightfield& heightfield, int flagMergeThreshold = 1);
-/// Marks non-walkable spans as walkable if their maximum is within @p walkableClimb of a walkable neighbor.
+/// Marks non-walkable spans as walkable if their maximum is within @p walkableClimb of the span below them.
///
-/// Allows the formation of walkable regions that will flow over low lying
-/// objects such as curbs, and up structures such as stairways.
+/// This removes small obstacles that the agent would be able to walk over such as curbs, and also allows agents to move up structures such as stairs.
///
-/// Two neighboring spans are walkable if: rcAbs(currentSpan.smax - neighborSpan.smax) < walkableClimb
+/// Obstacle spans are marked walkable if: obstacleSpan.smax - walkableSpan.smax < walkableClimb
///
-/// @warning Will override the effect of #rcFilterLedgeSpans. So if both filters are used, call
-/// #rcFilterLedgeSpans after calling this filter.
+/// @warning Will override the effect of #rcFilterLedgeSpans. If both filters are used, call #rcFilterLedgeSpans only after applying this filter.
///
/// @see rcHeightfield, rcConfig
///
/// @ingroup recast
-/// @param[in,out] context The build context to use during the operation.
+/// @param[in,out] context The build context to use during the operation.
/// @param[in] walkableClimb Maximum ledge height that is considered to still be traversable.
/// [Limit: >=0] [Units: vx]
-/// @param[in,out] heightfield A fully built heightfield. (All spans have been added.)
+/// @param[in,out] heightfield A fully built heightfield. (All spans have been added.)
void rcFilterLowHangingWalkableObstacles(rcContext* context, int walkableClimb, rcHeightfield& heightfield);
/// Marks spans that are ledges as not-walkable.
@@ -1037,10 +1037,12 @@ void rcFilterLowHangingWalkableObstacles(rcContext* context, int walkableClimb,
/// @param[in,out] heightfield A fully built heightfield. (All spans have been added.)
void rcFilterLedgeSpans(rcContext* context, int walkableHeight, int walkableClimb, rcHeightfield& heightfield);
-/// Marks walkable spans as not walkable if the clearance above the span is less than the specified height.
+/// Marks walkable spans as not walkable if the clearance above the span is less than the specified walkableHeight.
///
/// For this filter, the clearance above the span is the distance from the span's
-/// maximum to the next higher span's minimum. (Same grid column.)
+/// maximum to the minimum of the next higher span in the same column.
+/// If there is no higher span in the column, the clearance is computed as the
+/// distance from the top of the span to the maximum heightfield height.
///
/// @see rcHeightfield, rcConfig
/// @ingroup recast
diff --git a/Recast/Source/RecastFilter.cpp b/Recast/Source/RecastFilter.cpp
index 8f3414b6..1ecf8588 100644
--- a/Recast/Source/RecastFilter.cpp
+++ b/Recast/Source/RecastFilter.cpp
@@ -21,6 +21,11 @@
#include
+namespace
+{
+ const int MAX_HEIGHTFIELD_HEIGHT = 0xffff; // TODO (graham): Move this to a more visible constant and update usages.
+}
+
void rcFilterLowHangingWalkableObstacles(rcContext* context, const int walkableClimb, rcHeightfield& heightfield)
{
rcAssert(context);
@@ -59,8 +64,7 @@ void rcFilterLowHangingWalkableObstacles(rcContext* context, const int walkableC
}
}
-void rcFilterLedgeSpans(rcContext* context, const int walkableHeight, const int walkableClimb,
- rcHeightfield& heightfield)
+void rcFilterLedgeSpans(rcContext* context, const int walkableHeight, const int walkableClimb, rcHeightfield& heightfield)
{
rcAssert(context);
@@ -68,7 +72,6 @@ void rcFilterLedgeSpans(rcContext* context, const int walkableHeight, const int
const int xSize = heightfield.width;
const int zSize = heightfield.height;
- const int MAX_HEIGHT = 0xffff; // TODO (graham): Move this to a more visible constant and update usages.
// Mark border spans.
for (int z = 0; z < zSize; ++z)
@@ -84,10 +87,10 @@ void rcFilterLedgeSpans(rcContext* context, const int walkableHeight, const int
}
const int bot = (int)(span->smax);
- const int top = span->next ? (int)(span->next->smin) : MAX_HEIGHT;
+ const int top = span->next ? (int)(span->next->smin) : MAX_HEIGHTFIELD_HEIGHT;
// Find neighbours minimum height.
- int minNeighborHeight = MAX_HEIGHT;
+ int minNeighborHeight = MAX_HEIGHTFIELD_HEIGHT;
// Min and max height of accessible neighbours.
int accessibleNeighborMinHeight = span->smax;
@@ -106,7 +109,7 @@ void rcFilterLedgeSpans(rcContext* context, const int walkableHeight, const int
// From minus infinity to the first span.
const rcSpan* neighborSpan = heightfield.spans[dx + dz * xSize];
- int neighborTop = neighborSpan ? (int)neighborSpan->smin : MAX_HEIGHT;
+ int neighborTop = neighborSpan ? (int)neighborSpan->smin : MAX_HEIGHTFIELD_HEIGHT;
// Skip neighbour if the gap between the spans is too small.
if (rcMin(top, neighborTop) - bot >= walkableHeight)
@@ -119,7 +122,7 @@ void rcFilterLedgeSpans(rcContext* context, const int walkableHeight, const int
for (neighborSpan = heightfield.spans[dx + dz * xSize]; neighborSpan; neighborSpan = neighborSpan->next)
{
int neighborBot = (int)neighborSpan->smax;
- neighborTop = neighborSpan->next ? (int)neighborSpan->next->smin : MAX_HEIGHT;
+ neighborTop = neighborSpan->next ? (int)neighborSpan->next->smin : MAX_HEIGHTFIELD_HEIGHT;
// Skip neighbour if the gap between the spans is too small.
if (rcMin(top, neighborTop) - rcMax(bot, neighborBot) >= walkableHeight)
@@ -137,19 +140,16 @@ void rcFilterLedgeSpans(rcContext* context, const int walkableHeight, const int
{
break;
}
-
}
}
}
- // The current span is close to a ledge if the drop to any
- // neighbour span is less than the walkableClimb.
+ // The current span is close to a ledge if the drop to any neighbour span is less than the walkableClimb.
if (minNeighborHeight < -walkableClimb)
{
span->area = RC_NULL_AREA;
}
- // If the difference between all neighbours is too large,
- // we are at steep slope, mark the span as ledge.
+ // If the difference between all neighbours is too large, we are at steep slope, mark the span as ledge.
else if ((accessibleNeighborMaxHeight - accessibleNeighborMinHeight) > walkableClimb)
{
span->area = RC_NULL_AREA;
@@ -162,13 +162,11 @@ void rcFilterLedgeSpans(rcContext* context, const int walkableHeight, const int
void rcFilterWalkableLowHeightSpans(rcContext* context, const int walkableHeight, rcHeightfield& heightfield)
{
rcAssert(context);
-
rcScopedTimer timer(context, RC_TIMER_FILTER_WALKABLE);
-
+
const int xSize = heightfield.width;
const int zSize = heightfield.height;
- const int MAX_HEIGHT = 0xffff;
-
+
// Remove walkable flag from spans which do not have enough
// space above them for the agent to stand there.
for (int z = 0; z < zSize; ++z)
@@ -178,7 +176,7 @@ void rcFilterWalkableLowHeightSpans(rcContext* context, const int walkableHeight
for (rcSpan* span = heightfield.spans[x + z*xSize]; span; span = span->next)
{
const int bot = (int)(span->smax);
- const int top = span->next ? (int)(span->next->smin) : MAX_HEIGHT;
+ const int top = span->next ? (int)(span->next->smin) : MAX_HEIGHTFIELD_HEIGHT;
if ((top - bot) < walkableHeight)
{
span->area = RC_NULL_AREA;
diff --git a/Tests/Recast/Bench_rcVector.cpp b/Tests/Recast/Bench_rcVector.cpp
new file mode 100644
index 00000000..46c1cee2
--- /dev/null
+++ b/Tests/Recast/Bench_rcVector.cpp
@@ -0,0 +1,168 @@
+#include
+#include
+
+#include "catch2/catch_all.hpp"
+
+#include "Recast.h"
+#include "RecastAlloc.h"
+#include "RecastAssert.h"
+#include
+
+// TODO: Implement benchmarking for platforms other than posix.
+#ifdef __unix__
+#include
+#ifdef _POSIX_TIMERS
+#include
+#include
+
+int64_t NowNanos() {
+ struct timespec tp;
+ clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &tp);
+ return tp.tv_nsec + 1000000000LL * tp.tv_sec;
+}
+
+#define BM(name, iterations) \
+ struct BM_ ## name { \
+ static void Run() { \
+ int64_t begin_time = NowNanos(); \
+ for (int i = 0 ; i < iterations; i++) { \
+ Body(); \
+ } \
+ int64_t nanos = NowNanos() - begin_time; \
+ printf("BM_%-35s %ld iterations in %10ld nanos: %10.2f nanos/it\n", #name ":", (int64_t)iterations, nanos, double(nanos) / iterations); \
+ } \
+ static void Body(); \
+ }; \
+ TEST_CASE(#name) { \
+ BM_ ## name::Run(); \
+ } \
+ void BM_ ## name::Body()
+
+const int64_t kNumLoops = 100;
+const int64_t kNumInserts = 100000;
+
+// Prevent compiler from eliding a calculation.
+// TODO: Implement for MSVC.
+template
+void DoNotOptimize(T* v) {
+ asm volatile ("" : "+r" (v));
+}
+
+BM(FlatArray_Push, kNumLoops)
+{
+ int cap = 64;
+ int* v = (int*)rcAlloc(cap * sizeof(int), RC_ALLOC_TEMP);
+ for (int j = 0; j < kNumInserts; j++) {
+ if (j == cap) {
+ cap *= 2;
+ int* tmp = (int*)rcAlloc(sizeof(int) * cap, RC_ALLOC_TEMP);
+ memcpy(tmp, v, j * sizeof(int));
+ rcFree(v);
+ v = tmp;
+ }
+ v[j] = 2;
+ }
+
+ DoNotOptimize(v);
+ rcFree(v);
+}
+BM(FlatArray_Fill, kNumLoops)
+{
+ int* v = (int*)rcAlloc(sizeof(int) * kNumInserts, RC_ALLOC_TEMP);
+ for (int j = 0; j < kNumInserts; j++) {
+ v[j] = 2;
+ }
+
+ DoNotOptimize(v);
+ rcFree(v);
+}
+BM(FlatArray_Memset, kNumLoops)
+{
+ int* v = (int*)rcAlloc(sizeof(int) * kNumInserts, RC_ALLOC_TEMP);
+ memset(v, 0, kNumInserts * sizeof(int));
+
+ DoNotOptimize(v);
+ rcFree(v);
+}
+
+BM(rcVector_Push, kNumLoops)
+{
+ rcTempVector v;
+ for (int j = 0; j < kNumInserts; j++) {
+ v.push_back(2);
+ }
+ DoNotOptimize(v.data());
+}
+BM(rcVector_PushPreallocated, kNumLoops)
+{
+ rcTempVector v;
+ v.reserve(kNumInserts);
+ for (int j = 0; j < kNumInserts; j++) {
+ v.push_back(2);
+ }
+ DoNotOptimize(v.data());
+}
+BM(rcVector_Assign, kNumLoops)
+{
+ rcTempVector v;
+ v.assign(kNumInserts, 2);
+ DoNotOptimize(v.data());
+}
+BM(rcVector_AssignIndices, kNumLoops)
+{
+ rcTempVector v;
+ v.resize(kNumInserts);
+ for (int j = 0; j < kNumInserts; j++) {
+ v[j] = 2;
+ }
+ DoNotOptimize(v.data());
+}
+BM(rcVector_Resize, kNumLoops)
+{
+ rcTempVector v;
+ v.resize(kNumInserts, 2);
+ DoNotOptimize(v.data());
+}
+
+BM(stdvector_Push, kNumLoops)
+{
+ std::vector v;
+ for (int j = 0; j < kNumInserts; j++) {
+ v.push_back(2);
+ }
+ DoNotOptimize(v.data());
+}
+BM(stdvector_PushPreallocated, kNumLoops)
+{
+ std::vector v;
+ v.reserve(kNumInserts);
+ for (int j = 0; j < kNumInserts; j++) {
+ v.push_back(2);
+ }
+ DoNotOptimize(v.data());
+}
+BM(stdvector_Assign, kNumLoops)
+{
+ std::vector v;
+ v.assign(kNumInserts, 2);
+ DoNotOptimize(v.data());
+}
+BM(stdvector_AssignIndices, kNumLoops)
+{
+ std::vector v;
+ v.resize(kNumInserts);
+ for (int j = 0; j < kNumInserts; j++) {
+ v[j] = 2;
+ }
+ DoNotOptimize(v.data());
+}
+BM(stdvector_Resize, kNumLoops)
+{
+ std::vector v;
+ v.resize(kNumInserts, 2);
+ DoNotOptimize(v.data());
+}
+
+#undef BM
+#endif // _POSIX_TIMERS
+#endif // __unix__
diff --git a/Tests/Recast/Tests_Alloc.cpp b/Tests/Recast/Tests_Alloc.cpp
new file mode 100644
index 00000000..2e7b2eb1
--- /dev/null
+++ b/Tests/Recast/Tests_Alloc.cpp
@@ -0,0 +1,260 @@
+#include
+#include
+
+#include "catch2/catch_all.hpp"
+
+#include "RecastAlloc.h"
+#include "RecastAssert.h"
+
+/// Used to verify that rcVector constructs/destroys objects correctly.
+struct Incrementor {
+ static int constructions;
+ static int destructions;
+ static int copies;
+ Incrementor() { constructions++; }
+ ~Incrementor() { destructions++; }
+ Incrementor(const Incrementor&) { copies++; }
+ Incrementor& operator=(const Incrementor&); // Deleted assignment.
+
+ static void Reset() {
+ constructions = 0;
+ destructions = 0;
+ copies = 0;
+ }
+};
+
+int Incrementor::constructions = 0;
+int Incrementor::destructions = 0;
+int Incrementor::copies = 0;
+
+const int kMaxAllocSize = 1024;
+const unsigned char kClearValue = 0xff;
+
+/// Simple alloc/free that clears the memory on free..
+void* AllocAndInit(size_t size, rcAllocHint) {
+ rcAssert(kMaxAllocSize >= size);
+ return memset(malloc(kMaxAllocSize), 0, kMaxAllocSize);
+}
+
+void FreeAndClear(void* mem) {
+ if (mem) {
+ memset(mem, kClearValue, kMaxAllocSize);
+ }
+ free(mem);
+}
+
+// Verifies that memory has been initialized by AllocAndInit, and not cleared by FreeAndClear.
+struct Copier {
+ const static int kAlive;
+ const static int kDead;
+ Copier() : value(kAlive) {}
+
+ // checks that the source of the copy is valid.
+ Copier(const Copier& other) : value(kAlive) {
+ other.Verify();
+ }
+ Copier& operator=(const Copier&);
+
+ // Marks the value as dead.
+ ~Copier() { value = kDead; }
+ void Verify() const {
+ REQUIRE(value == kAlive);
+ }
+ volatile int value;
+};
+
+const int Copier::kAlive = 0x1f;
+const int Copier::kDead = 0xde;
+
+struct NotDefaultConstructible {
+ NotDefaultConstructible(int) {}
+};
+
+TEST_CASE("rcVector", "[recast, alloc]")
+{
+ SECTION("Vector basics.")
+ {
+ rcTempVector vec;
+ REQUIRE(vec.size() == 0);
+ vec.push_back(10);
+ vec.push_back(12);
+ REQUIRE(vec.size() == 2);
+ REQUIRE(vec.capacity() >= 2);
+ REQUIRE(vec[0] == 10);
+ REQUIRE(vec[1] == 12);
+ vec.pop_back();
+ REQUIRE(vec.size() == 1);
+ REQUIRE(vec[0] == 10);
+ vec.pop_back();
+ REQUIRE(vec.size() == 0);
+ vec.resize(100, 5);
+ REQUIRE(vec.size() == 100);
+ for (int i = 0; i < 100; i++) {
+ REQUIRE(vec[i] == 5);
+ vec[i] = i;
+ }
+ for (int i = 0; i < 100; i++) {
+ REQUIRE(vec[i] == i);
+ }
+ }
+
+ SECTION("Constructors/Destructors")
+ {
+ Incrementor::Reset();
+ rcTempVector vec;
+ REQUIRE(Incrementor::constructions == 0);
+ REQUIRE(Incrementor::destructions == 0);
+ REQUIRE(Incrementor::copies == 0);
+ vec.push_back(Incrementor());
+ // push_back() may create and copy objects internally.
+ REQUIRE(Incrementor::constructions == 1);
+ REQUIRE(Incrementor::destructions >= 1);
+ // REQUIRE(Incrementor::copies >= 2);
+
+ vec.clear();
+ Incrementor::Reset();
+ vec.resize(100);
+ // Initialized with default instance. Temporaries may be constructed, then destroyed.
+ REQUIRE(Incrementor::constructions == 100);
+ REQUIRE(Incrementor::destructions == 0);
+ REQUIRE(Incrementor::copies == 0);
+
+ Incrementor::Reset();
+ for (int i = 0; i < 100; i++) {
+ REQUIRE(Incrementor::destructions == i);
+ vec.pop_back();
+ }
+ REQUIRE(Incrementor::constructions == 0);
+ REQUIRE(Incrementor::destructions == 100);
+ REQUIRE(Incrementor::copies == 0);
+
+ vec.resize(100);
+ Incrementor::Reset();
+ vec.clear();
+ // One temp object is constructed for the default argument of resize().
+ REQUIRE(Incrementor::constructions == 0);
+ REQUIRE(Incrementor::destructions == 100);
+ REQUIRE(Incrementor::copies == 0);
+
+ Incrementor::Reset();
+ vec.resize(100, Incrementor());
+ REQUIRE(Incrementor::constructions == 1);
+ REQUIRE(Incrementor::destructions == 1);
+ REQUIRE(Incrementor::copies == 100);
+ }
+
+ SECTION("Copying Contents")
+ {
+
+ // veriyf event counts after doubling size -- should require a lot of copying and destroying.
+ rcTempVector vec;
+ Incrementor::Reset();
+ vec.resize(100);
+ REQUIRE(Incrementor::constructions == 100);
+ REQUIRE(Incrementor::destructions == 0);
+ REQUIRE(Incrementor::copies == 0);
+ Incrementor::Reset();
+ vec.resize(200);
+ REQUIRE(vec.size() == vec.capacity());
+ REQUIRE(Incrementor::constructions == 100); // Construc new elements.
+ REQUIRE(Incrementor::destructions == 100); // Destroy old contents.
+ REQUIRE(Incrementor::copies == 100); // Copy old elements into new array.
+ }
+
+ SECTION("Swap")
+ {
+ rcTempVector a(10, 0xa);
+ rcTempVector b;
+
+ int* a_data = a.data();
+ int* b_data = b.data();
+
+ a.swap(b);
+ REQUIRE(a.size() == 0);
+ REQUIRE(b.size() == 10);
+ REQUIRE(b[0] == 0xa);
+ REQUIRE(b[9] == 0xa);
+ REQUIRE(a.data() == b_data);
+ REQUIRE(b.data() == a_data);
+ }
+
+ SECTION("Overlapping init")
+ {
+ rcAllocSetCustom(&AllocAndInit, &FreeAndClear);
+ rcTempVector vec;
+ // Force a realloc during push_back().
+ vec.resize(64);
+ REQUIRE(vec.capacity() == vec.size());
+ REQUIRE(vec.capacity() > 0);
+ REQUIRE(vec.size() == vec.capacity());
+
+ // Don't crash.
+ vec.push_back(vec[0]);
+ rcAllocSetCustom(NULL, NULL);
+ }
+
+ SECTION("Vector Destructor")
+ {
+ {
+ rcTempVector vec;
+ vec.resize(10);
+ Incrementor::Reset();
+ }
+ REQUIRE(Incrementor::destructions == 10);
+ }
+
+ SECTION("Assign")
+ {
+ rcTempVector a(10, 0xa);
+ a.assign(5, 0xb);
+ REQUIRE(a.size() == 5);
+ REQUIRE(a[0] == 0xb);
+ REQUIRE(a[4] == 0xb);
+ a.assign(15, 0xc);
+ REQUIRE(a.size() == 15);
+ REQUIRE(a[0] == 0xc);
+ REQUIRE(a[14] == 0xc);
+
+ rcTempVector b;
+ b.assign(a.data(), a.data() + a.size());
+ REQUIRE(b.size() == a.size());
+ REQUIRE(b[0] == a[0]);
+ }
+
+ SECTION("Copy")
+ {
+ rcTempVector a(10, 0xa);
+ rcTempVector b(a);
+ REQUIRE(a.size() == 10);
+ REQUIRE(a.size() == b.size());
+ REQUIRE(a[0] == b[0]);
+ REQUIRE(a.data() != b.data());
+ rcTempVector c(a.data(), a.data() + a.size());
+ REQUIRE(c.size() == a.size());
+ REQUIRE(c[0] == a[0]);
+
+ rcTempVector d(10);
+ Incrementor::Reset();
+ rcTempVector e(d);
+ REQUIRE(Incrementor::constructions == 0);
+ REQUIRE(Incrementor::destructions == 0);
+ REQUIRE(Incrementor::copies == 10);
+
+ Incrementor::Reset();
+ rcTempVector f(d.data(), d.data() + d.size());
+ REQUIRE(Incrementor::constructions == 0);
+ REQUIRE(Incrementor::destructions == 0);
+ REQUIRE(Incrementor::copies == 10);
+ }
+
+ SECTION("Type Requirements")
+ {
+ // This section verifies that we don't enforce unnecessary
+ // requirements on the types we hold.
+
+ // Implementing clear as resize(0) will cause this to fail
+ // as resize(0) requires T to be default constructible.
+ rcTempVector v;
+ v.clear();
+ }
+}
diff --git a/Tests/Recast/Tests_Recast.cpp b/Tests/Recast/Tests_Recast.cpp
index 34c61d02..62d15072 100644
--- a/Tests/Recast/Tests_Recast.cpp
+++ b/Tests/Recast/Tests_Recast.cpp
@@ -4,13 +4,8 @@
#include "catch2/catch_all.hpp"
#include "Recast.h"
-#include "RecastAlloc.h"
-#include "RecastAssert.h"
-// For comparing to rcVector in benchmarks.
-#include
-
-TEST_CASE("rcSwap")
+TEST_CASE("rcSwap", "[recast]")
{
SECTION("Swap two values")
{
@@ -22,7 +17,7 @@ TEST_CASE("rcSwap")
}
}
-TEST_CASE("rcMin")
+TEST_CASE("rcMin", "[recast]")
{
SECTION("Min returns the lowest value.")
{
@@ -36,7 +31,7 @@ TEST_CASE("rcMin")
}
}
-TEST_CASE("rcMax")
+TEST_CASE("rcMax", "[recast]")
{
SECTION("Max returns the greatest value.")
{
@@ -50,7 +45,7 @@ TEST_CASE("rcMax")
}
}
-TEST_CASE("rcAbs")
+TEST_CASE("rcAbs", "[recast]")
{
SECTION("Abs returns the absolute value.")
{
@@ -60,7 +55,7 @@ TEST_CASE("rcAbs")
}
}
-TEST_CASE("rcSqr")
+TEST_CASE("rcSqr", "[recast]")
{
SECTION("Sqr squares a number")
{
@@ -70,7 +65,7 @@ TEST_CASE("rcSqr")
}
}
-TEST_CASE("rcClamp")
+TEST_CASE("rcClamp", "[recast]")
{
SECTION("Higher than range")
{
@@ -88,7 +83,7 @@ TEST_CASE("rcClamp")
}
}
-TEST_CASE("rcSqrt")
+TEST_CASE("rcSqrt", "[recast]")
{
SECTION("Sqrt gets the sqrt of a number")
{
@@ -97,7 +92,7 @@ TEST_CASE("rcSqrt")
}
}
-TEST_CASE("rcVcross")
+TEST_CASE("rcVcross", "[recast]")
{
SECTION("Computes cross product")
{
@@ -121,7 +116,7 @@ TEST_CASE("rcVcross")
}
}
-TEST_CASE("rcVdot")
+TEST_CASE("rcVdot", "[recast]")
{
SECTION("Dot normalized vector with itself")
{
@@ -140,7 +135,7 @@ TEST_CASE("rcVdot")
}
}
-TEST_CASE("rcVmad")
+TEST_CASE("rcVmad", "[recast]")
{
SECTION("scaled add two vectors")
{
@@ -165,7 +160,7 @@ TEST_CASE("rcVmad")
}
}
-TEST_CASE("rcVadd")
+TEST_CASE("rcVadd", "[recast]")
{
SECTION("add two vectors")
{
@@ -179,7 +174,7 @@ TEST_CASE("rcVadd")
}
}
-TEST_CASE("rcVsub")
+TEST_CASE("rcVsub", "[recast]")
{
SECTION("subtract two vectors")
{
@@ -193,7 +188,7 @@ TEST_CASE("rcVsub")
}
}
-TEST_CASE("rcVmin")
+TEST_CASE("rcVmin", "[recast]")
{
SECTION("selects the min component from the vectors")
{
@@ -226,7 +221,7 @@ TEST_CASE("rcVmin")
}
}
-TEST_CASE("rcVmax")
+TEST_CASE("rcVmax", "[recast]")
{
SECTION("selects the max component from the vectors")
{
@@ -259,7 +254,7 @@ TEST_CASE("rcVmax")
}
}
-TEST_CASE("rcVcopy")
+TEST_CASE("rcVcopy", "[recast]")
{
SECTION("copies a vector into another vector")
{
@@ -275,7 +270,7 @@ TEST_CASE("rcVcopy")
}
}
-TEST_CASE("rcVdist")
+TEST_CASE("rcVdist", "[recast]")
{
SECTION("distance between two vectors")
{
@@ -296,7 +291,7 @@ TEST_CASE("rcVdist")
}
}
-TEST_CASE("rcVdistSqr")
+TEST_CASE("rcVdistSqr", "[recast]")
{
SECTION("squared distance between two vectors")
{
@@ -317,7 +312,7 @@ TEST_CASE("rcVdistSqr")
}
}
-TEST_CASE("rcVnormalize")
+TEST_CASE("rcVnormalize", "[recast]")
{
SECTION("normalizing reduces magnitude to 1")
{
@@ -331,7 +326,7 @@ TEST_CASE("rcVnormalize")
}
}
-TEST_CASE("rcCalcBounds")
+TEST_CASE("rcCalcBounds", "[recast]")
{
SECTION("bounds of one vector")
{
@@ -369,7 +364,7 @@ TEST_CASE("rcCalcBounds")
}
}
-TEST_CASE("rcCalcGridSize")
+TEST_CASE("rcCalcGridSize", "[recast]")
{
SECTION("computes the size of an x & z axis grid")
{
@@ -393,7 +388,7 @@ TEST_CASE("rcCalcGridSize")
}
}
-TEST_CASE("rcCreateHeightfield")
+TEST_CASE("rcCreateHeightfield", "[recast]")
{
SECTION("create a heightfield")
{
@@ -439,7 +434,7 @@ TEST_CASE("rcCreateHeightfield")
}
}
-TEST_CASE("rcMarkWalkableTriangles")
+TEST_CASE("rcMarkWalkableTriangles", "[recast]")
{
rcContext* ctx = 0;
float walkableSlopeAngle = 45;
@@ -481,7 +476,7 @@ TEST_CASE("rcMarkWalkableTriangles")
}
}
-TEST_CASE("rcClearUnwalkableTriangles")
+TEST_CASE("rcClearUnwalkableTriangles", "[recast]")
{
rcContext* ctx = 0;
float walkableSlopeAngle = 45;
@@ -516,7 +511,7 @@ TEST_CASE("rcClearUnwalkableTriangles")
}
}
-TEST_CASE("rcAddSpan")
+TEST_CASE("rcAddSpan", "[recast]")
{
rcContext ctx(false);
@@ -605,7 +600,7 @@ TEST_CASE("rcAddSpan")
}
}
-TEST_CASE("rcRasterizeTriangle")
+TEST_CASE("rcRasterizeTriangle", "[recast]")
{
rcContext ctx;
float verts[] = {
@@ -657,7 +652,7 @@ TEST_CASE("rcRasterizeTriangle")
}
}
-TEST_CASE("rcRasterizeTriangle overlapping bb but non-overlapping triangle")
+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;
@@ -694,7 +689,7 @@ TEST_CASE("rcRasterizeTriangle overlapping bb but non-overlapping triangle")
}
}
-TEST_CASE("rcRasterizeTriangle smaller than half a voxel size in x")
+TEST_CASE("rcRasterizeTriangle smaller than half a voxel size in x", "[recast]")
{
SECTION("Skinny triangle along x axis")
{
@@ -761,7 +756,7 @@ TEST_CASE("rcRasterizeTriangle smaller than half a voxel size in x")
}
}
-TEST_CASE("rcRasterizeTriangles")
+TEST_CASE("rcRasterizeTriangles", "[recast]")
{
rcContext ctx;
float verts[] = {
@@ -940,410 +935,3 @@ TEST_CASE("rcRasterizeTriangles")
REQUIRE(!solid.spans[1 + 2 * width]->next);
}
}
-
-// Used to verify that rcVector constructs/destroys objects correctly.
-struct Incrementor {
- static int constructions;
- static int destructions;
- static int copies;
- Incrementor() { constructions++; }
- ~Incrementor() { destructions++; }
- Incrementor(const Incrementor&) { copies++; }
- Incrementor& operator=(const Incrementor&); // Deleted assignment.
-
- static void Reset() {
- constructions = 0;
- destructions = 0;
- copies = 0;
- }
-};
-int Incrementor::constructions = 0;
-int Incrementor::destructions = 0;
-int Incrementor::copies = 0;
-
-const int kMaxAllocSize = 1024;
-const unsigned char kClearValue = 0xff;
-// Simple alloc/free that clears the memory on free..
-void* AllocAndInit(size_t size, rcAllocHint) {
- rcAssert(kMaxAllocSize >= size);
- return memset(malloc(kMaxAllocSize), 0, kMaxAllocSize);
-}
-void FreeAndClear(void* mem) {
- if (mem) {
- memset(mem, kClearValue, kMaxAllocSize);
- }
- free(mem);
-}
-// Verifies that memory has been initialized by AllocAndInit, and not cleared by FreeAndClear.
-struct Copier {
- const static int kAlive;
- const static int kDead;
- Copier() : value(kAlive) {}
-
- // checks that the source of the copy is valid.
- Copier(const Copier& other) : value(kAlive) {
- other.Verify();
- }
- Copier& operator=(const Copier&);
-
- // Marks the value as dead.
- ~Copier() { value = kDead; }
- void Verify() const {
- REQUIRE(value == kAlive);
- }
- volatile int value;
-};
-const int Copier::kAlive = 0x1f;
-const int Copier::kDead = 0xde;
-
-struct NotDefaultConstructible {
- NotDefaultConstructible(int) {}
-};
-
-TEST_CASE("rcVector")
-{
- SECTION("Vector basics.")
- {
- rcTempVector vec;
- REQUIRE(vec.size() == 0);
- vec.push_back(10);
- vec.push_back(12);
- REQUIRE(vec.size() == 2);
- REQUIRE(vec.capacity() >= 2);
- REQUIRE(vec[0] == 10);
- REQUIRE(vec[1] == 12);
- vec.pop_back();
- REQUIRE(vec.size() == 1);
- REQUIRE(vec[0] == 10);
- vec.pop_back();
- REQUIRE(vec.size() == 0);
- vec.resize(100, 5);
- REQUIRE(vec.size() == 100);
- for (int i = 0; i < 100; i++) {
- REQUIRE(vec[i] == 5);
- vec[i] = i;
- }
- for (int i = 0; i < 100; i++) {
- REQUIRE(vec[i] == i);
- }
- }
-
- SECTION("Constructors/Destructors")
- {
- Incrementor::Reset();
- rcTempVector vec;
- REQUIRE(Incrementor::constructions == 0);
- REQUIRE(Incrementor::destructions == 0);
- REQUIRE(Incrementor::copies == 0);
- vec.push_back(Incrementor());
- // push_back() may create and copy objects internally.
- REQUIRE(Incrementor::constructions == 1);
- REQUIRE(Incrementor::destructions >= 1);
- // REQUIRE(Incrementor::copies >= 2);
-
- vec.clear();
- Incrementor::Reset();
- vec.resize(100);
- // Initialized with default instance. Temporaries may be constructed, then destroyed.
- REQUIRE(Incrementor::constructions == 100);
- REQUIRE(Incrementor::destructions == 0);
- REQUIRE(Incrementor::copies == 0);
-
- Incrementor::Reset();
- for (int i = 0; i < 100; i++) {
- REQUIRE(Incrementor::destructions == i);
- vec.pop_back();
- }
- REQUIRE(Incrementor::constructions == 0);
- REQUIRE(Incrementor::destructions == 100);
- REQUIRE(Incrementor::copies == 0);
-
- vec.resize(100);
- Incrementor::Reset();
- vec.clear();
- // One temp object is constructed for the default argument of resize().
- REQUIRE(Incrementor::constructions == 0);
- REQUIRE(Incrementor::destructions == 100);
- REQUIRE(Incrementor::copies == 0);
-
- Incrementor::Reset();
- vec.resize(100, Incrementor());
- REQUIRE(Incrementor::constructions == 1);
- REQUIRE(Incrementor::destructions == 1);
- REQUIRE(Incrementor::copies == 100);
- }
-
- SECTION("Copying Contents")
- {
-
- // veriyf event counts after doubling size -- should require a lot of copying and destroying.
- rcTempVector vec;
- Incrementor::Reset();
- vec.resize(100);
- REQUIRE(Incrementor::constructions == 100);
- REQUIRE(Incrementor::destructions == 0);
- REQUIRE(Incrementor::copies == 0);
- Incrementor::Reset();
- vec.resize(200);
- REQUIRE(vec.size() == vec.capacity());
- REQUIRE(Incrementor::constructions == 100); // Construc new elements.
- REQUIRE(Incrementor::destructions == 100); // Destroy old contents.
- REQUIRE(Incrementor::copies == 100); // Copy old elements into new array.
- }
-
- SECTION("Swap")
- {
- rcTempVector a(10, 0xa);
- rcTempVector b;
-
- int* a_data = a.data();
- int* b_data = b.data();
-
- a.swap(b);
- REQUIRE(a.size() == 0);
- REQUIRE(b.size() == 10);
- REQUIRE(b[0] == 0xa);
- REQUIRE(b[9] == 0xa);
- REQUIRE(a.data() == b_data);
- REQUIRE(b.data() == a_data);
- }
-
- SECTION("Overlapping init")
- {
- rcAllocSetCustom(&AllocAndInit, &FreeAndClear);
- rcTempVector vec;
- // Force a realloc during push_back().
- vec.resize(64);
- REQUIRE(vec.capacity() == vec.size());
- REQUIRE(vec.capacity() > 0);
- REQUIRE(vec.size() == vec.capacity());
-
- // Don't crash.
- vec.push_back(vec[0]);
- rcAllocSetCustom(NULL, NULL);
- }
-
- SECTION("Vector Destructor")
- {
- {
- rcTempVector vec;
- vec.resize(10);
- Incrementor::Reset();
- }
- REQUIRE(Incrementor::destructions == 10);
- }
-
- SECTION("Assign")
- {
- rcTempVector a(10, 0xa);
- a.assign(5, 0xb);
- REQUIRE(a.size() == 5);
- REQUIRE(a[0] == 0xb);
- REQUIRE(a[4] == 0xb);
- a.assign(15, 0xc);
- REQUIRE(a.size() == 15);
- REQUIRE(a[0] == 0xc);
- REQUIRE(a[14] == 0xc);
-
- rcTempVector b;
- b.assign(a.data(), a.data() + a.size());
- REQUIRE(b.size() == a.size());
- REQUIRE(b[0] == a[0]);
- }
-
- SECTION("Copy")
- {
- rcTempVector a(10, 0xa);
- rcTempVector b(a);
- REQUIRE(a.size() == 10);
- REQUIRE(a.size() == b.size());
- REQUIRE(a[0] == b[0]);
- REQUIRE(a.data() != b.data());
- rcTempVector c(a.data(), a.data() + a.size());
- REQUIRE(c.size() == a.size());
- REQUIRE(c[0] == a[0]);
-
- rcTempVector d(10);
- Incrementor::Reset();
- rcTempVector e(d);
- REQUIRE(Incrementor::constructions == 0);
- REQUIRE(Incrementor::destructions == 0);
- REQUIRE(Incrementor::copies == 10);
-
- Incrementor::Reset();
- rcTempVector f(d.data(), d.data() + d.size());
- REQUIRE(Incrementor::constructions == 0);
- REQUIRE(Incrementor::destructions == 0);
- REQUIRE(Incrementor::copies == 10);
- }
-
- SECTION("Type Requirements")
- {
- // This section verifies that we don't enforce unnecessary
- // requirements on the types we hold.
-
- // Implementing clear as resize(0) will cause this to fail
- // as resize(0) requires T to be default constructible.
- rcTempVector v;
- v.clear();
- }
-}
-
-// TODO: Implement benchmarking for platforms other than posix.
-#ifdef __unix__
-#include
-#ifdef _POSIX_TIMERS
-#include
-#include
-
-int64_t NowNanos() {
- struct timespec tp;
- clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &tp);
- return tp.tv_nsec + 1000000000LL * tp.tv_sec;
-}
-
-#define BM(name, iterations) \
- struct BM_ ## name { \
- static void Run() { \
- int64_t begin_time = NowNanos(); \
- for (int i = 0 ; i < iterations; i++) { \
- Body(); \
- } \
- int64_t nanos = NowNanos() - begin_time; \
- printf("BM_%-35s %ld iterations in %10ld nanos: %10.2f nanos/it\n", #name ":", (int64_t)iterations, nanos, double(nanos) / iterations); \
- } \
- static void Body(); \
- }; \
- TEST_CASE(#name) { \
- BM_ ## name::Run(); \
- } \
- void BM_ ## name::Body()
-
-const int64_t kNumLoops = 100;
-const int64_t kNumInserts = 100000;
-
-// Prevent compiler from eliding a calculation.
-// TODO: Implement for MSVC.
-template
-void DoNotOptimize(T* v) {
- asm volatile ("" : "+r" (v));
-}
-
-BM(FlatArray_Push, kNumLoops)
-{
- int cap = 64;
- int* v = (int*)rcAlloc(cap * sizeof(int), RC_ALLOC_TEMP);
- for (int j = 0; j < kNumInserts; j++) {
- if (j == cap) {
- cap *= 2;
- int* tmp = (int*)rcAlloc(sizeof(int) * cap, RC_ALLOC_TEMP);
- memcpy(tmp, v, j * sizeof(int));
- rcFree(v);
- v = tmp;
- }
- v[j] = 2;
- }
-
- DoNotOptimize(v);
- rcFree(v);
-}
-BM(FlatArray_Fill, kNumLoops)
-{
- int* v = (int*)rcAlloc(sizeof(int) * kNumInserts, RC_ALLOC_TEMP);
- for (int j = 0; j < kNumInserts; j++) {
- v[j] = 2;
- }
-
- DoNotOptimize(v);
- rcFree(v);
-}
-BM(FlatArray_Memset, kNumLoops)
-{
- int* v = (int*)rcAlloc(sizeof(int) * kNumInserts, RC_ALLOC_TEMP);
- memset(v, 0, kNumInserts * sizeof(int));
-
- DoNotOptimize(v);
- rcFree(v);
-}
-
-BM(rcVector_Push, kNumLoops)
-{
- rcTempVector v;
- for (int j = 0; j < kNumInserts; j++) {
- v.push_back(2);
- }
- DoNotOptimize(v.data());
-}
-BM(rcVector_PushPreallocated, kNumLoops)
-{
- rcTempVector v;
- v.reserve(kNumInserts);
- for (int j = 0; j < kNumInserts; j++) {
- v.push_back(2);
- }
- DoNotOptimize(v.data());
-}
-BM(rcVector_Assign, kNumLoops)
-{
- rcTempVector v;
- v.assign(kNumInserts, 2);
- DoNotOptimize(v.data());
-}
-BM(rcVector_AssignIndices, kNumLoops)
-{
- rcTempVector v;
- v.resize(kNumInserts);
- for (int j = 0; j < kNumInserts; j++) {
- v[j] = 2;
- }
- DoNotOptimize(v.data());
-}
-BM(rcVector_Resize, kNumLoops)
-{
- rcTempVector v;
- v.resize(kNumInserts, 2);
- DoNotOptimize(v.data());
-}
-
-BM(stdvector_Push, kNumLoops)
-{
- std::vector v;
- for (int j = 0; j < kNumInserts; j++) {
- v.push_back(2);
- }
- DoNotOptimize(v.data());
-}
-BM(stdvector_PushPreallocated, kNumLoops)
-{
- std::vector v;
- v.reserve(kNumInserts);
- for (int j = 0; j < kNumInserts; j++) {
- v.push_back(2);
- }
- DoNotOptimize(v.data());
-}
-BM(stdvector_Assign, kNumLoops)
-{
- std::vector v;
- v.assign(kNumInserts, 2);
- DoNotOptimize(v.data());
-}
-BM(stdvector_AssignIndices, kNumLoops)
-{
- std::vector v;
- v.resize(kNumInserts);
- for (int j = 0; j < kNumInserts; j++) {
- v[j] = 2;
- }
- DoNotOptimize(v.data());
-}
-BM(stdvector_Resize, kNumLoops)
-{
- std::vector v;
- v.resize(kNumInserts, 2);
- DoNotOptimize(v.data());
-}
-
-#undef BM
-#endif // _POSIX_TIMERS
-#endif // __unix__
diff --git a/Tests/Recast/Tests_RecastFilter.cpp b/Tests/Recast/Tests_RecastFilter.cpp
new file mode 100644
index 00000000..1882adba
--- /dev/null
+++ b/Tests/Recast/Tests_RecastFilter.cpp
@@ -0,0 +1,389 @@
+#include
+#include
+#include
+
+#include "catch2/catch_all.hpp"
+
+#include "Recast.h"
+#include "RecastAlloc.h"
+
+TEST_CASE("rcFilterLowHangingWalkableObstacles", "[recast, filtering]")
+{
+ rcContext context;
+ int walkableHeight = 5;
+
+ rcHeightfield heightfield;
+ heightfield.width = 1;
+ heightfield.height = 1;
+ heightfield.bmin[0] = 0;
+ heightfield.bmin[1] = 0;
+ heightfield.bmin[2] = 0;
+ heightfield.bmax[0] = 1;
+ heightfield.bmax[1] = 1;
+ heightfield.bmax[2] = 1;
+ heightfield.cs = 1;
+ heightfield.ch = 1;
+ heightfield.spans = (rcSpan**)rcAlloc(heightfield.width * heightfield.height * sizeof(rcSpan*), RC_ALLOC_PERM);
+ heightfield.pools = NULL;
+ heightfield.freelist = NULL;
+
+ SECTION("Span with no spans above it is unchanged")
+ {
+ rcSpan* span = (rcSpan*)rcAlloc(sizeof(rcSpan), RC_ALLOC_PERM);
+ span->area = 1;
+ span->next = NULL;
+ span->smin = 0;
+ span->smax = 1;
+ heightfield.spans[0] = span;
+
+ rcFilterLowHangingWalkableObstacles(&context, walkableHeight, heightfield);
+
+ REQUIRE(heightfield.spans[0]->area == 1);
+
+ rcFree(span);
+ }
+
+ SECTION("Span with span above that is higher than walkableHeight is unchanged")
+ {
+ // Put the second span just above the first one.
+ rcSpan* secondSpan = (rcSpan*)rcAlloc(sizeof(rcSpan), RC_ALLOC_PERM);
+ secondSpan->area = 1;
+ secondSpan->next = NULL;
+ secondSpan->smin = 1 + walkableHeight;
+ secondSpan->smax = secondSpan->smin + 1;
+
+ rcSpan* span = (rcSpan*)rcAlloc(sizeof(rcSpan), RC_ALLOC_PERM);
+ span->area = 1;
+ span->next = secondSpan;
+ span->smin = 0;
+ span->smax = 1;
+
+ heightfield.spans[0] = span;
+
+ rcFilterLowHangingWalkableObstacles(&context, walkableHeight, heightfield);
+
+ // Check that nothing has changed.
+ REQUIRE(heightfield.spans[0]->area == 1);
+ REQUIRE(heightfield.spans[0]->next->area == 1);
+
+ // Check again but with a more clearance
+ secondSpan->smin += 10;
+ secondSpan->smax += 10;
+
+ rcFilterLowHangingWalkableObstacles(&context, walkableHeight, heightfield);
+
+ // Check that nothing has changed.
+ REQUIRE(heightfield.spans[0]->area == 1);
+ REQUIRE(heightfield.spans[0]->next->area == 1);
+
+ rcFree(span);
+ rcFree(secondSpan);
+ }
+
+ SECTION("Marks low obstacles walkable if they're below the walkableClimb")
+ {
+ // Put the second span just above the first one.
+ rcSpan* secondSpan = (rcSpan*)rcAlloc(sizeof(rcSpan), RC_ALLOC_PERM);
+ secondSpan->area = RC_NULL_AREA;
+ secondSpan->next = NULL;
+ secondSpan->smin = 1 + (walkableHeight - 1);
+ secondSpan->smax = secondSpan->smin + 1;
+
+ rcSpan* span = (rcSpan*)rcAlloc(sizeof(rcSpan), RC_ALLOC_PERM);
+ span->area = 1;
+ span->next = secondSpan;
+ span->smin = 0;
+ span->smax = 1;
+
+ heightfield.spans[0] = span;
+
+ rcFilterLowHangingWalkableObstacles(&context, walkableHeight, heightfield);
+
+ // Check that the second span was changed to walkable.
+ REQUIRE(heightfield.spans[0]->area == 1);
+ REQUIRE(heightfield.spans[0]->next->area == 1);
+
+ rcFree(span);
+ rcFree(secondSpan);
+ }
+
+ SECTION("Low obstacle that overlaps the walkableClimb distance is not changed")
+ {
+ // Put the second span just above the first one.
+ rcSpan* secondSpan = (rcSpan*)rcAlloc(sizeof(rcSpan), RC_ALLOC_PERM);
+ secondSpan->area = RC_NULL_AREA;
+ secondSpan->next = NULL;
+ secondSpan->smin = 2 + (walkableHeight - 1);
+ secondSpan->smax = secondSpan->smin + 1;
+
+ rcSpan* span = (rcSpan*)rcAlloc(sizeof(rcSpan), RC_ALLOC_PERM);
+ span->area = 1;
+ span->next = secondSpan;
+ span->smin = 0;
+ span->smax = 1;
+
+ heightfield.spans[0] = span;
+
+ rcFilterLowHangingWalkableObstacles(&context, walkableHeight, heightfield);
+
+ // Check that the second span was changed to walkable.
+ REQUIRE(heightfield.spans[0]->area == 1);
+ REQUIRE(heightfield.spans[0]->next->area == RC_NULL_AREA);
+
+ rcFree(span);
+ rcFree(secondSpan);
+ }
+
+ SECTION("Only the first of multiple, low obstacles are marked walkable")
+ {
+ rcSpan* span = (rcSpan*)rcAlloc(sizeof(rcSpan), RC_ALLOC_PERM);
+ span->area = 1;
+ span->next = NULL;
+ span->smin = 0;
+ span->smax = 1;
+ heightfield.spans[0] = span;
+
+ rcSpan* previousSpan = span;
+ for (int i = 0; i < 9; ++i)
+ {
+ rcSpan* nextSpan = (rcSpan*)rcAlloc(sizeof(rcSpan), RC_ALLOC_PERM);
+ nextSpan->area = RC_NULL_AREA;
+ nextSpan->next = NULL;
+ nextSpan->smin = previousSpan->smax + (walkableHeight - 1);
+ nextSpan->smax = nextSpan->smin + 1;
+ previousSpan->next = nextSpan;
+ previousSpan = nextSpan;
+ }
+
+ rcFilterLowHangingWalkableObstacles(&context, walkableHeight, heightfield);
+
+ rcSpan* currentSpan = heightfield.spans[0];
+ for (int i = 0; i < 10; ++i)
+ {
+ REQUIRE(currentSpan != NULL);
+ // only the first and second spans should be marked as walkabl
+ REQUIRE(currentSpan->area == (i <= 1 ? 1 : RC_NULL_AREA));
+ currentSpan = currentSpan->next;
+ }
+
+ std::vector toFree;
+ span = heightfield.spans[0];
+ for (int i = 0; i < 10; ++i)
+ {
+ toFree.push_back(span);
+ span = span->next;
+ }
+
+ for (int i = 0; i < 10; ++i)
+ {
+ rcFree(toFree[i]);
+ }
+ }
+}
+
+TEST_CASE("rcFilterLedgeSpans", "[recast, filtering]")
+{
+ rcContext context;
+ int walkableClimb = 5;
+ int walkableHeight = 10;
+
+ rcHeightfield heightfield;
+ heightfield.width = 10;
+ heightfield.height = 10;
+ heightfield.bmin[0] = 0;
+ heightfield.bmin[1] = 0;
+ heightfield.bmin[2] = 0;
+ heightfield.bmax[0] = 10;
+ heightfield.bmax[1] = 1;
+ heightfield.bmax[2] = 10;
+ heightfield.cs = 1;
+ heightfield.ch = 1;
+ heightfield.spans = (rcSpan**)rcAlloc(heightfield.width * heightfield.height * sizeof(rcSpan*), RC_ALLOC_PERM);
+ heightfield.pools = NULL;
+ heightfield.freelist = NULL;
+
+ SECTION("Edge spans are marked unwalkable")
+ {
+ // Create a flat plane.
+ for (int x = 0; x < heightfield.width; ++x)
+ {
+ for (int z = 0; z < heightfield.height; ++z)
+ {
+ rcSpan* span = (rcSpan*)rcAlloc(sizeof(rcSpan), RC_ALLOC_PERM);
+ span->area = 1;
+ span->next = NULL;
+ span->smin = 0;
+ span->smax = 1;
+ heightfield.spans[x + z * heightfield.width] = span;
+ }
+ }
+
+ rcFilterLedgeSpans(&context, walkableHeight, walkableClimb, heightfield);
+
+ for (int x = 0; x < heightfield.width; ++x)
+ {
+ for (int z = 0; z < heightfield.height; ++z)
+ {
+ rcSpan* span = heightfield.spans[x + z * heightfield.width];
+ REQUIRE(span != NULL);
+
+ if (x == 0 || z == 0 || x == 9 || z == 9)
+ {
+ REQUIRE(span->area == RC_NULL_AREA);
+ }
+ else
+ {
+ REQUIRE(span->area == 1);
+ }
+
+ REQUIRE(span->next == NULL);
+ REQUIRE(span->smin == 0);
+ REQUIRE(span->smax == 1);
+ }
+ }
+
+ // Free all the heightfield spans
+ for (int x = 0; x < heightfield.width; ++x)
+ {
+ for (int z = 0; z < heightfield.height; ++z)
+ {
+ rcFree(heightfield.spans[x + z * heightfield.width]);
+ }
+ }
+ }
+
+ SECTION("Edge spans are marked unwalkable")
+ {
+ // Create a flat plane.
+ for (int x = 0; x < heightfield.width; ++x)
+ {
+ for (int z = 0; z < heightfield.height; ++z)
+ {
+ rcSpan* span = (rcSpan*)rcAlloc(sizeof(rcSpan), RC_ALLOC_PERM);
+ span->area = 1;
+ span->next = NULL;
+ span->smin = 0;
+ span->smax = 1;
+ heightfield.spans[x + z * heightfield.width] = span;
+ }
+ }
+
+ rcFilterLedgeSpans(&context, walkableHeight, walkableClimb, heightfield);
+
+ for (int x = 0; x < heightfield.width; ++x)
+ {
+ for (int z = 0; z < heightfield.height; ++z)
+ {
+ rcSpan* span = heightfield.spans[x + z * heightfield.width];
+ REQUIRE(span != NULL);
+
+ if (x == 0 || z == 0 || x == 9 || z == 9)
+ {
+ REQUIRE(span->area == RC_NULL_AREA);
+ }
+ else
+ {
+ REQUIRE(span->area == 1);
+ }
+
+ REQUIRE(span->next == NULL);
+ REQUIRE(span->smin == 0);
+ REQUIRE(span->smax == 1);
+ }
+ }
+
+ // Free all the heightfield spans
+ for (int x = 0; x < heightfield.width; ++x)
+ {
+ for (int z = 0; z < heightfield.height; ++z)
+ {
+ rcFree(heightfield.spans[x + z * heightfield.width]);
+ }
+ }
+ }
+}
+
+TEST_CASE("rcFilterWalkableLowHeightSpans", "[recast, filtering]")
+{
+ rcContext context;
+ int walkableHeight = 5;
+
+ rcHeightfield heightfield;
+ heightfield.width = 1;
+ heightfield.height = 1;
+ heightfield.bmin[0] = 0;
+ heightfield.bmin[1] = 0;
+ heightfield.bmin[2] = 0;
+ heightfield.bmax[0] = 1;
+ heightfield.bmax[1] = 1;
+ heightfield.bmax[2] = 1;
+ heightfield.cs = 1;
+ heightfield.ch = 1;
+ heightfield.spans = (rcSpan**)rcAlloc(heightfield.width * heightfield.height * sizeof(rcSpan*), RC_ALLOC_PERM);
+ heightfield.pools = NULL;
+ heightfield.freelist = NULL;
+
+ SECTION("span nothing above is unchanged")
+ {
+ rcSpan* span = (rcSpan*)rcAlloc(sizeof(rcSpan), RC_ALLOC_PERM);
+ span->area = 1;
+ span->next = NULL;
+ span->smin = 0;
+ span->smax = 1;
+ heightfield.spans[0] = span;
+
+ rcFilterWalkableLowHeightSpans(&context, walkableHeight, heightfield);
+
+ REQUIRE(heightfield.spans[0]->area == 1);
+
+ rcFree(span);
+ }
+
+ SECTION("span with lots of room above is unchanged")
+ {
+ rcSpan* overheadSpan = (rcSpan*)rcAlloc(sizeof(rcSpan), RC_ALLOC_PERM);
+ overheadSpan->area = RC_NULL_AREA;
+ overheadSpan->next = NULL;
+ overheadSpan->smin = 10;
+ overheadSpan->smax = 11;
+
+ rcSpan* span = (rcSpan*)rcAlloc(sizeof(rcSpan), RC_ALLOC_PERM);
+ span->area = 1;
+ span->next = overheadSpan;
+ span->smin = 0;
+ span->smax = 1;
+ heightfield.spans[0] = span;
+
+ rcFilterWalkableLowHeightSpans(&context, walkableHeight, heightfield);
+
+ REQUIRE(heightfield.spans[0]->area == 1);
+ REQUIRE(heightfield.spans[0]->next->area == RC_NULL_AREA);
+
+ rcFree(overheadSpan);
+ rcFree(span);
+ }
+
+ SECTION("Span with low hanging obstacle is marked as unwalkable")
+ {
+ rcSpan* overheadSpan = (rcSpan*)rcAlloc(sizeof(rcSpan), RC_ALLOC_PERM);
+ overheadSpan->area = RC_NULL_AREA;
+ overheadSpan->next = NULL;
+ overheadSpan->smin = 3;
+ overheadSpan->smax = 4;
+
+ rcSpan* span = (rcSpan*)rcAlloc(sizeof(rcSpan), RC_ALLOC_PERM);
+ span->area = 1;
+ span->next = overheadSpan;
+ span->smin = 0;
+ span->smax = 1;
+ heightfield.spans[0] = span;
+
+ rcFilterWalkableLowHeightSpans(&context, walkableHeight, heightfield);
+
+ REQUIRE(heightfield.spans[0]->area == RC_NULL_AREA);
+ REQUIRE(heightfield.spans[0]->next->area == RC_NULL_AREA);
+
+ rcFree(overheadSpan);
+ rcFree(span);
+ }
+}
\ No newline at end of file