Skip to content

Commit

Permalink
Optimization (breaking): pool RcSpans.
Browse files Browse the repository at this point in the history
  • Loading branch information
awgil authored and ikpil committed Mar 5, 2024
1 parent b67ebea commit 023d340
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 46 deletions.
15 changes: 7 additions & 8 deletions src/DotRecast.Recast/RcCompacts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,26 +91,25 @@ public static RcCompactHeightfield BuildCompactHeightfield(RcContext context, in
int numColumns = xSize * zSize;
for (int columnIndex = 0; columnIndex < numColumns; ++columnIndex)
{
RcSpan span = heightfield.spans[columnIndex];
uint spanIndex = heightfield.spans[columnIndex];

// If there are no spans at this cell, just leave the data to index=0, count=0.
if (span == null)
continue;

int tmpIdx = currentCellIndex;
int tmpCount = 0;
for (; span != null; span = span.next)
while (spanIndex != 0)
{
ref var span = ref heightfield.Span(spanIndex);
if (span.area != RC_NULL_AREA)
{
int bot = span.smax;
int top = span.next != null ? (int)span.next.smin : MAX_HEIGHT;
int top = span.next != 0 ? heightfield.Span(span.next).smin : MAX_HEIGHT;
tempSpans[currentCellIndex].y = Math.Clamp(bot, 0, MAX_HEIGHT);
tempSpans[currentCellIndex].h = Math.Clamp(top - bot, 0, MAX_HEIGHT);
compactHeightfield.areas[currentCellIndex] = span.area;
currentCellIndex++;
tmpCount++;
}
spanIndex = span.next;
}

compactHeightfield.cells[columnIndex] = new RcCompactCell(tmpIdx, tmpCount);
Expand Down Expand Up @@ -191,9 +190,9 @@ private static int GetHeightFieldSpanCount(RcContext context, RcHeightfield heig
int spanCount = 0;
for (int columnIndex = 0; columnIndex < numCols; ++columnIndex)
{
for (RcSpan span = heightfield.spans[columnIndex]; span != null; span = span.next)
for (uint span = heightfield.spans[columnIndex]; span != 0; span = heightfield.Span(span).next)
{
if (span.area != RC_NULL_AREA)
if (heightfield.Span(span).area != RC_NULL_AREA)
{
spanCount++;
}
Expand Down
31 changes: 17 additions & 14 deletions src/DotRecast.Recast/RcFilters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,25 +56,25 @@ public static void FilterLowHangingWalkableObstacles(RcContext context, int walk
{
for (int x = 0; x < xSize; ++x)
{
RcSpan previousSpan = null;
uint previousSpan = 0;
bool previousWasWalkable = false;
int previousAreaID = RC_NULL_AREA;

// For each span in the column...
for (RcSpan span = heightfield.spans[x + z * xSize]; span != null; previousSpan = span, span = span.next)
for (uint span = heightfield.spans[x + z * xSize]; span != 0; previousSpan = span, span = heightfield.Span(span).next)
{
bool walkable = span.area != RC_NULL_AREA;
bool walkable = heightfield.Span(span).area != RC_NULL_AREA;
// If current span is not walkable, but there is walkable span just below it and the height difference
// is small enough for the agent to walk over, mark the current span as walkable too.
if (!walkable && previousWasWalkable && span.smax - previousSpan.smax <= walkableClimb)
if (!walkable && previousWasWalkable && heightfield.Span(span).smax - heightfield.Span(previousSpan).smax <= walkableClimb)
{
span.area = previousAreaID;
heightfield.Span(span).area = previousAreaID;
}

// Copy the original walkable value regardless of whether we changed it.
// This prevents multiple consecutive non-walkable spans from being erroneously marked as walkable.
previousWasWalkable = walkable;
previousAreaID = span.area;
previousAreaID = heightfield.Span(span).area;
}
}
}
Expand Down Expand Up @@ -110,16 +110,17 @@ public static void FilterLedgeSpans(RcContext context, int walkableHeight, int w
{
for (int x = 0; x < xSize; ++x)
{
for (RcSpan span = heightfield.spans[x + z * xSize]; span != null; span = span.next)
for (uint spanIndex = heightfield.spans[x + z * xSize]; spanIndex != 0; spanIndex = heightfield.Span(spanIndex).next)
{
// Skip non-walkable spans.
ref var span = ref heightfield.Span(spanIndex);
if (span.area == RC_NULL_AREA)
{
continue;
}

int floor = (span.smax);
int ceiling = span.next != null ? span.next.smin : RC_SPAN_MAX_HEIGHT;
int ceiling = span.next != 0 ? heightfield.Span(span.next).smin : RC_SPAN_MAX_HEIGHT;

// The difference between this walkable area and the lowest neighbor walkable area.
// This is the difference between the current span and all neighbor spans that have
Expand All @@ -142,11 +143,11 @@ public static void FilterLedgeSpans(RcContext context, int walkableHeight, int w
break;
}

RcSpan neighborSpan = heightfield.spans[neighborX + neighborZ * xSize];
uint neighborSpanIndex = heightfield.spans[neighborX + neighborZ * xSize];

// The most we can step down to the neighbor is the walkableClimb distance.
// Start with the area under the neighbor span
int neighborCeiling = neighborSpan != null ? neighborSpan.smin : RC_SPAN_MAX_HEIGHT;
int neighborCeiling = neighborSpanIndex != 0 ? heightfield.Span(neighborSpanIndex).smin : RC_SPAN_MAX_HEIGHT;

// Skip neightbour if the gap between the spans is too small.
if (Math.Min(ceiling, neighborCeiling) - floor >= walkableHeight)
Expand All @@ -156,10 +157,11 @@ public static void FilterLedgeSpans(RcContext context, int walkableHeight, int w
}

// For each span in the neighboring column...
for (; neighborSpan != null; neighborSpan = neighborSpan.next)
for (; neighborSpanIndex != 0; neighborSpanIndex = heightfield.Span(neighborSpanIndex).next)
{
ref var neighborSpan = ref heightfield.Span(neighborSpanIndex);
int neighborFloor = neighborSpan.smax;
neighborCeiling = neighborSpan.next != null ? neighborSpan.next.smin : RC_SPAN_MAX_HEIGHT;
neighborCeiling = neighborSpan.next != 0 ? heightfield.Span(neighborSpan.next).smin : RC_SPAN_MAX_HEIGHT;

// Only consider neighboring areas that have enough overlap to be potentially traversable.
if (Math.Min(ceiling, neighborCeiling) - Math.Max(floor, neighborFloor) < walkableHeight)
Expand Down Expand Up @@ -232,10 +234,11 @@ public static void FilterWalkableLowHeightSpans(RcContext context, int walkableH
{
for (int x = 0; x < xSize; ++x)
{
for (RcSpan span = heightfield.spans[x + z * xSize]; span != null; span = span.next)
for (uint spanIndex = heightfield.spans[x + z * xSize]; spanIndex != 0; spanIndex = heightfield.Span(spanIndex).next)
{
ref var span = ref heightfield.Span(spanIndex);
int floor = (span.smax);
int ceiling = span.next != null ? span.next.smin : RC_SPAN_MAX_HEIGHT;
int ceiling = span.next != 0 ? heightfield.Span(span.next).smin : RC_SPAN_MAX_HEIGHT;
if ((ceiling - floor) < walkableHeight)
{
span.area = RC_NULL_AREA;
Expand Down
12 changes: 9 additions & 3 deletions src/DotRecast.Recast/RcHeightfield.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@ public class RcHeightfield
public readonly float ch;

/** Heightfield of spans (width*height). */
public readonly RcSpan[] spans;
public readonly uint[] spans;

/** Actual span storage. */
public readonly RcSpanPool spanPool;

/** Border size in cell units */
public readonly int borderSize;
Expand All @@ -58,7 +61,10 @@ public RcHeightfield(int width, int height, RcVec3f bmin, RcVec3f bmax, float cs
this.cs = cs;
this.ch = ch;
this.borderSize = borderSize;
spans = new RcSpan[width * height];
spans = new uint[width * height];
spanPool = new RcSpanPool();
}

public ref RcSpan Span(uint index) => ref spanPool.Span(index);
}
}
}
37 changes: 20 additions & 17 deletions src/DotRecast.Recast/RcRasterizations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,27 +56,29 @@ private static bool OverlapBounds(RcVec3f aMin, RcVec3f aMax, RcVec3f bMin, RcVe
public static void AddSpan(RcHeightfield heightfield, int x, int z, int min, int max, int areaID, int flagMergeThreshold)
{
// Create the new span.
RcSpan newSpan = new RcSpan();
uint newSpanIndex = heightfield.spanPool.Alloc();
ref RcSpan newSpan = ref heightfield.Span(newSpanIndex);
newSpan.smin = min;
newSpan.smax = max;
newSpan.area = areaID;
newSpan.next = null;
newSpan.next = 0;

int columnIndex = x + z * heightfield.width;

// Empty cell, add the first span.
if (heightfield.spans[columnIndex] == null)
if (heightfield.spans[columnIndex] == 0)
{
heightfield.spans[columnIndex] = newSpan;
heightfield.spans[columnIndex] = newSpanIndex;
return;
}

RcSpan previousSpan = null;
RcSpan currentSpan = heightfield.spans[columnIndex];
uint previousSpanIndex = 0;
uint currentSpanIndex = heightfield.spans[columnIndex];

// Insert the new span, possibly merging it with existing spans.
while (currentSpan != null)
while (currentSpanIndex != 0)
{
ref var currentSpan = ref heightfield.Span(currentSpanIndex);
if (currentSpan.smin > newSpan.smax)
{
// Current span is further than the new span, break.
Expand All @@ -86,8 +88,8 @@ public static void AddSpan(RcHeightfield heightfield, int x, int z, int min, int
if (currentSpan.smax < newSpan.smin)
{
// Current span is completely before the new span. Keep going.
previousSpan = currentSpan;
currentSpan = currentSpan.next;
previousSpanIndex = currentSpanIndex;
currentSpanIndex = currentSpan.next;
}
else
{
Expand All @@ -111,31 +113,32 @@ public static void AddSpan(RcHeightfield heightfield, int x, int z, int min, int

// Remove the current span since it's now merged with newSpan.
// Keep going because there might be other overlapping spans that also need to be merged.
RcSpan next = currentSpan.next;
if (previousSpan != null)
uint next = currentSpan.next;
if (previousSpanIndex != 0)
{
previousSpan.next = next;
heightfield.Span(previousSpanIndex).next = next;
}
else
{
heightfield.spans[columnIndex] = next;
}
heightfield.spanPool.Free(currentSpanIndex);

currentSpan = next;
currentSpanIndex = next;
}
}

// Insert new span after prev
if (previousSpan != null)
if (previousSpanIndex != 0)
{
newSpan.next = previousSpan.next;
previousSpan.next = newSpan;
newSpan.next = heightfield.Span(previousSpanIndex).next;
heightfield.Span(previousSpanIndex).next = newSpanIndex;
}
else
{
// This span should go before the others in the list
newSpan.next = heightfield.spans[columnIndex];
heightfield.spans[columnIndex] = newSpan;
heightfield.spans[columnIndex] = newSpanIndex;
}
}

Expand Down
56 changes: 52 additions & 4 deletions src/DotRecast.Recast/RcSpan.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/

using System;

namespace DotRecast.Recast
{
/** Represents a span in a heightfield. */
public class RcSpan
public struct RcSpan
{
/** The lower limit of the span. [Limit: &lt; smax] */
public int smin;
Expand All @@ -32,7 +34,53 @@ public class RcSpan
/** The area id assigned to the span. */
public int area;

/** The next span higher up in column. */
public RcSpan next;
/** The index next span higher up in column. For span in the free list, this is the index of the next free span. */
public uint next;
}

/** A pool of spans. Index 0 is reserved to mean 'null'. */
public class RcSpanPool
{
private RcSpan[] storage = new RcSpan[64 * 1024];
private uint firstUnalloc = 1; // storage element with index >= this is not initialized

public RcSpanPool()
{
storage[0].next = firstUnalloc;
}

public ref RcSpan Span(uint index) => ref storage[index];

public uint Alloc()
{
var index = storage[0].next;
if (index < firstUnalloc)
{
// get span from the free list
storage[0].next = storage[index].next;
storage[index].next = 0;
return index;
}

// free list is empty
if (storage.Length == firstUnalloc)
{
// and storage is full => grow
var oldStorage = storage;
storage = new RcSpan[oldStorage.Length * 2];
Array.Copy(oldStorage, storage, oldStorage.Length);
}

// index == firstUnalloc => storage[index] == 0 (never initialized)
storage[0].next = ++firstUnalloc;
return index;
}

public void Free(uint index)
{
// push span to the head of the free list
storage[index].next = storage[0].next;
storage[0].next = index;
}
}
}
}

0 comments on commit 023d340

Please sign in to comment.