Add Fill2DRegionWithRectangles function

This commit is contained in:
Steve Streeting 2024-01-11 15:32:59 +00:00
parent 0ccd90cdd8
commit df40ca92e5
2 changed files with 154 additions and 0 deletions

View File

@ -106,3 +106,128 @@ float StevesMathHelpers::GetDistanceToConvex2D(const TArray<FVector2f>& ConvexPo
return bInside ? -ClosestInside : ClosestOutside;
}
int StevesMathHelpers::Fill2DRegionWithRectangles(int StartX,
int StartY,
int Width,
int Height,
std::function<bool(int, int)> CellIncludeFunc,
TArray<FIntRect>& OutRects)
{
int RectCount = 0;
const int Len = Width*Height;
TArray<bool> bDoneMarkers;
bDoneMarkers.SetNumUninitialized(Len);
int CellsTodo = 0;
int StartN = 0;
const int EndX = StartX + Width - 1;
const int EndY = StartY + Height - 1;
// Initialise done markers based on func
for (int y = 0; y < Height; ++y)
{
for (int x = 0; x < Width; ++x)
{
const bool Include = CellIncludeFunc(x+StartX, y+StartY);
bDoneMarkers[y*Width + x] = !Include;
if (Include)
{
if (++CellsTodo == 1)
{
// This is the one we'll start with, might as well calculate it while we're here
StartN = y*Width + x;
}
}
}
}
while (CellsTodo > 0)
{
// Find next starting point, from last one, until not done
for (; StartN < Len && bDoneMarkers[StartN]; ++StartN) {}
// Shouldn't happen, but just in case
if (StartN >= Len)
break;
// NOTE: this X/Y is local (based at 0,0 not StartX/StartY, for use with DoneMarkers)
const int LocalStartX = StartN % Width;
const int LocalStartY = StartN / Width;
// We try not to create long & thin rects if we can help it, unlike greedy meshing
// We're greedy in alternate dims to try to make fatter quads
bool bCanExtendX = true, bCanExtendY = true;
bool bExtendingX = true;
int W = 1;
int H = 1;
while (bCanExtendX || bCanExtendY)
{
// Try extending right
if (bExtendingX)
{
bool ExtendOK = LocalStartX+W < Width;
for (int TestY = LocalStartY; ExtendOK && TestY < LocalStartY+H; ++TestY)
{
if (bDoneMarkers[TestY*Width + LocalStartX+W])
{
// No good
ExtendOK = false;
}
}
if (ExtendOK)
{
++W;
}
else
{
bCanExtendX = false;
}
// Flip extending axis if possible
if (bCanExtendY)
bExtendingX = false;
}
else
{
// Try extending down
bool ExtendOK = LocalStartY+H < Height;
for (int TestX = LocalStartX; ExtendOK && TestX < LocalStartX+W; ++TestX)
{
if (bDoneMarkers[(LocalStartY+H)*Width + TestX])
{
// No good
ExtendOK = false;
}
}
if (ExtendOK)
{
++H;
}
else
{
bCanExtendY = false;
}
// Flip extending axis if possible
if (bCanExtendX)
bExtendingX = true;
}
}
// We've calculated the max extension
for (int y = LocalStartY; y < LocalStartY+H; ++y)
{
for (int x = LocalStartX; x < LocalStartX+W; ++x)
{
bDoneMarkers[y*Width+x] = true;
--CellsTodo;
}
}
OutRects.Add(FIntRect(StartX+LocalStartX, StartY+LocalStartY, StartX+LocalStartX+W-1, StartY+LocalStartY+H-1));
++RectCount;
}
return RectCount;
}

View File

@ -1,4 +1,5 @@
#pragma once
#include <functional>
struct FKConvexElem;
@ -147,4 +148,32 @@ public:
const float d = (v2.X - v1.X) * (p.Y - v1.Y) - (v2.Y - v1.Y) * (p.X - v1.X);
return d == 0 || (d < 0) == (s + t <= 0);
}
/**
* Function that tries to fill a 2D area with the largest rectangles it can. The area is abstractly defined as a boundary
* index area with start X/Y and width/height, and will call back the CellIncludeFunc to determine whether a given
* cell index X/Y should be considered valid to include in a rectangle. This means you can define irregular grids of
* "valid" cells, and this function will fill the area with the largest rectangles it can while staying out of "invalid"
* cells.
* If you return "true" from every call to your CellIncludeFunc then the result will be a single rectangle covering
* the entire area. It's expected that you will return "false" for some X/Y combinations and that will cause the area
* to be split into multiple rectangles.
* The returned rectangles will not overlap, and the entire valid area will be filled.
* @param StartX The start X index. This is defined by your own data, so you can address a subset if you want.
* @param StartY The start Y index.This is defined by your own data, so you can address a subset if you want.
* @param Width The width of the area to fill. This is defined by your own data, so you can address a subset if you want.
* @param Height The height of the area to fill. This is defined by your own data, so you can address a subset if you want.
* @param CellIncludeFunc Your function which given an X/Y cell index, must return true if that cell is valid to be
* included in a rectangle.
* @param OutRects Array of rectangles which this function should append results to. Will not be cleared before adding.
* @return The number of rectangles added by this call. Each rectangle is a min/max inclusive X/Y value.
*/
static int Fill2DRegionWithRectangles(int StartX,
int StartY,
int Width,
int Height,
std::function<bool(int, int)> CellIncludeFunc,
TArray<FIntRect>& OutRects);
};