Skip to content

Commit

Permalink
Some unit tests for heightfield filtering functions (recastnavigation…
Browse files Browse the repository at this point in the history
…#682)

This adds some unit tests for the functions in RecastFilter.cpp, and updates docs around these functions. This also splits up the Tests_Recast.cpp file into a few smaller, more focused files.
  • Loading branch information
grahamboree authored Dec 31, 2023
1 parent 3e94c3b commit fc18ed8
Show file tree
Hide file tree
Showing 6 changed files with 872 additions and 467 deletions.
22 changes: 12 additions & 10 deletions Recast/Include/Recast.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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: <tt>rcAbs(currentSpan.smax - neighborSpan.smax) < walkableClimb</tt>
/// Obstacle spans are marked walkable if: <tt>obstacleSpan.smax - walkableSpan.smax < walkableClimb</tt>
///
/// @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.
Expand All @@ -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
Expand Down
32 changes: 15 additions & 17 deletions Recast/Source/RecastFilter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@

#include <stdlib.h>

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);
Expand Down Expand Up @@ -59,16 +64,14 @@ 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);

rcScopedTimer timer(context, RC_TIMER_FILTER_BORDER);

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)
Expand All @@ -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;
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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;
Expand All @@ -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)
Expand All @@ -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;
Expand Down
168 changes: 168 additions & 0 deletions Tests/Recast/Bench_rcVector.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
#include <stdio.h>
#include <string.h>

#include "catch2/catch_all.hpp"

#include "Recast.h"
#include "RecastAlloc.h"
#include "RecastAssert.h"
#include <vector>

// TODO: Implement benchmarking for platforms other than posix.
#ifdef __unix__
#include <unistd.h>
#ifdef _POSIX_TIMERS
#include <time.h>
#include <stdint.h>

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 <typename T>
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<int> v;
for (int j = 0; j < kNumInserts; j++) {
v.push_back(2);
}
DoNotOptimize(v.data());
}
BM(rcVector_PushPreallocated, kNumLoops)
{
rcTempVector<int> v;
v.reserve(kNumInserts);
for (int j = 0; j < kNumInserts; j++) {
v.push_back(2);
}
DoNotOptimize(v.data());
}
BM(rcVector_Assign, kNumLoops)
{
rcTempVector<int> v;
v.assign(kNumInserts, 2);
DoNotOptimize(v.data());
}
BM(rcVector_AssignIndices, kNumLoops)
{
rcTempVector<int> v;
v.resize(kNumInserts);
for (int j = 0; j < kNumInserts; j++) {
v[j] = 2;
}
DoNotOptimize(v.data());
}
BM(rcVector_Resize, kNumLoops)
{
rcTempVector<int> v;
v.resize(kNumInserts, 2);
DoNotOptimize(v.data());
}

BM(stdvector_Push, kNumLoops)
{
std::vector<int> v;
for (int j = 0; j < kNumInserts; j++) {
v.push_back(2);
}
DoNotOptimize(v.data());
}
BM(stdvector_PushPreallocated, kNumLoops)
{
std::vector<int> v;
v.reserve(kNumInserts);
for (int j = 0; j < kNumInserts; j++) {
v.push_back(2);
}
DoNotOptimize(v.data());
}
BM(stdvector_Assign, kNumLoops)
{
std::vector<int> v;
v.assign(kNumInserts, 2);
DoNotOptimize(v.data());
}
BM(stdvector_AssignIndices, kNumLoops)
{
std::vector<int> v;
v.resize(kNumInserts);
for (int j = 0; j < kNumInserts; j++) {
v[j] = 2;
}
DoNotOptimize(v.data());
}
BM(stdvector_Resize, kNumLoops)
{
std::vector<int> v;
v.resize(kNumInserts, 2);
DoNotOptimize(v.data());
}

#undef BM
#endif // _POSIX_TIMERS
#endif // __unix__
Loading

0 comments on commit fc18ed8

Please sign in to comment.