637 lines
21 KiB
C++
637 lines
21 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#pragma once
|
|
|
|
#if UE_ENABLE_INCLUDE_ORDER_DEPRECATED_IN_5_6
|
|
#include "CoreMinimal.h"
|
|
#endif // UE_ENABLE_INCLUDE_ORDER_DEPRECATED_IN_5_6
|
|
#include "Containers/StaticArray.h"
|
|
#include "Math/Box.h"
|
|
|
|
/**
|
|
* Hierarchical Hash Grid in 2D.
|
|
*
|
|
* Items are added to the "infinite" grid at specific level based on their size. The grid size, and size ratio between levels
|
|
* are defined as the template parameter. This allows some computation to be optimized based on the values.
|
|
*
|
|
* Items that cannot be fitted in the grid are added to a spill list, which contents are included in all queries.
|
|
* Each item is added to just one grid cell and thus can overlap some neighbour cells up to half cell size at that grid level.
|
|
* This is compensated during the query by expanding the query box.
|
|
*
|
|
* Potential optimizations:
|
|
* - (X, Y, Level) could probably be int16.
|
|
* - Level ratio could be a shift so LevelDown() could become just shift.
|
|
* - FloorToInt ends up calling floorf, which will be a function call.
|
|
* int FloorToInt(float a) { return (int)a + ((int)a > a ? -1 : 0); }
|
|
* auto vectorizes nicely on clang, but not on VC.
|
|
*
|
|
* - Add helper function to allow to tweak the cell size to reset the grid when spill list gets too large
|
|
*/
|
|
|
|
// LWC_TODO_AI Note we are using int32 here for X and Y which does mean that we could overflow the limit of an int for LWCoords
|
|
// unless fairly large grid cell sizes are used.As WORLD_MAX is currently in flux until we have a better idea of what we are
|
|
// going to be able to support its probably not worth investing time in this potential issue right now.
|
|
template <int32 InNumLevels = 3, int32 InLevelRatio = 4, typename InItemIDType = uint32>
|
|
class THierarchicalHashGrid2D
|
|
{
|
|
public:
|
|
|
|
typedef InItemIDType ItemIDType;
|
|
static const int32 NumLevels = InNumLevels; /** Number of grid levels in the grid. */
|
|
static const int32 LevelRatio = InLevelRatio; /** Ratio in cells between consecutive levels. */
|
|
|
|
/** Cells are located based on their hash. Each cell stores linked list of items at that location and how many children
|
|
* are under the same location in the finer grid levels.
|
|
*/
|
|
struct FCell
|
|
{
|
|
FCell() : X(0), Y(0), Level(0) {}
|
|
FCell(const int32 InX, const int32 InY, const int32 InLevel) : X(InX), Y(InY), Level(InLevel) {}
|
|
|
|
int32 X, Y, Level; /** Location of the cell */
|
|
int32 First = INDEX_NONE; /** First item in the item linked list. (index to Items array, or INDEX_NONE) */
|
|
int32 ChildItemCount = 0; /** Num of children in the child cells. */
|
|
|
|
/** Returns hash based on cell location. The hash function is called a lot, favor speed over quality. */
|
|
friend uint32 GetTypeHash(const FCell& Cell)
|
|
{
|
|
constexpr uint32 H1 = 0x8da6b343; // Arbitrary big primes.
|
|
constexpr uint32 H2 = 0xd8163841;
|
|
constexpr uint32 H3 = 0xcb1ab31f;
|
|
return (H1 * uint32(Cell.X) + H2 * uint32(Cell.Y) + H3 * uint32(Cell.Level));
|
|
}
|
|
|
|
// Need for TSet<>
|
|
bool operator==(const FCell& RHS) const
|
|
{
|
|
return X == RHS.X && Y == RHS.Y && Level == RHS.Level;
|
|
}
|
|
};
|
|
|
|
/** Specifies cell location within the grid at specific level. */
|
|
struct FCellLocation
|
|
{
|
|
FCellLocation() = default;
|
|
FCellLocation(const int32 InX, const int32 InY, const int32 InLevel) : X(InX), Y(InY), Level(InLevel) {}
|
|
|
|
bool operator==(const FCellLocation& RHS) const
|
|
{
|
|
return X == RHS.X && Y == RHS.Y && Level == RHS.Level;
|
|
}
|
|
|
|
bool operator!=(const FCellLocation& RHS) const
|
|
{
|
|
return !(*this == RHS);
|
|
}
|
|
|
|
int32 X = 0;
|
|
int32 Y = 0;
|
|
int32 Level = INDEX_NONE;
|
|
};
|
|
|
|
/** Item stored in a grid cell. */
|
|
struct FItem
|
|
{
|
|
ItemIDType ID = 0; /** External item ID */
|
|
int32 Next = INDEX_NONE; /** Next item in the item linked list (index to Items array, or INDEX_NONE). */
|
|
};
|
|
|
|
/** Rectangle bounds, coordinates inclusive. */
|
|
struct FCellRect
|
|
{
|
|
FCellRect() = default;
|
|
FCellRect(const int32 InMinX, const int32 InMinY, const int32 InMaxX, const int32 InMaxY) : MinX(InMinX), MinY(InMinY), MaxX(InMaxX), MaxY(InMaxY) {}
|
|
|
|
int32 MinX = 0;
|
|
int32 MinY = 0;
|
|
int32 MaxX = 0;
|
|
int32 MaxY = 0;
|
|
};
|
|
|
|
/** Iterator state over a rectangle */
|
|
struct FCellRectIterator
|
|
{
|
|
// Use int64 to prevent overflow when FCellRect::MaxX or FCellRect::MaxY are equal to the maximum value of int32.
|
|
int64 X = 0; /** Current X */
|
|
int64 Y = 0; /** Current Y */
|
|
int32 Level = 0; /** Current level */
|
|
FCellRect Rect; /** Area to iterate over */
|
|
};
|
|
|
|
|
|
/** Constructor, initializes the grid for specific cell size.
|
|
* @param InCellSize - new finest level cell size of the grid
|
|
*/
|
|
THierarchicalHashGrid2D(const float InCellSize = 500.f)
|
|
: SpillList(INDEX_NONE)
|
|
{
|
|
check(InCellSize > 0.0f);
|
|
|
|
float CurrCellSize = InCellSize;
|
|
for (int32 i = 0; i < NumLevels; i++)
|
|
{
|
|
CellSize[i] = CurrCellSize;
|
|
InvCellSize[i] = 1.0f / CurrCellSize;
|
|
CurrCellSize *= (float)(LevelRatio);
|
|
}
|
|
|
|
for (int32 i = 0; i < NumLevels; i++)
|
|
{
|
|
LevelItemCount[i] = 0;
|
|
}
|
|
}
|
|
|
|
/** Resets and initializes the grid for specific cell size.
|
|
* @param InCellSize - new finest level cell size of the grid
|
|
*/
|
|
void Initialize(const float InCellSize)
|
|
{
|
|
check(InCellSize > 0.0f);
|
|
|
|
Reset();
|
|
|
|
float CurrCellSize = InCellSize;
|
|
for (int32 i = 0; i < NumLevels; i++)
|
|
{
|
|
CellSize[i] = CurrCellSize;
|
|
InvCellSize[i] = 1.0f / CurrCellSize;
|
|
CurrCellSize *= (float)(LevelRatio);
|
|
}
|
|
}
|
|
|
|
/** Reset the grid to empty. */
|
|
void Reset()
|
|
{
|
|
SpillList = INDEX_NONE;
|
|
Cells.Reset();
|
|
Items.Reset();
|
|
|
|
for (int32 i = 0; i < NumLevels; i++)
|
|
{
|
|
LevelItemCount[i] = 0;
|
|
}
|
|
}
|
|
|
|
/** Adds item to the grid.
|
|
* @param ID - External ID used to identify the item.
|
|
* @param Bounds - Bounding box of the item.
|
|
* @return Cell location of the item, can be used later to remove the item.
|
|
*/
|
|
FCellLocation Add(const ItemIDType ID, const FBox& Bounds)
|
|
{
|
|
const FCellLocation Location = CalcCellLocation(Bounds);
|
|
Add(ID, Location);
|
|
return Location;
|
|
}
|
|
|
|
/** Adds item to the grid.
|
|
* @param ID - External ID used to identify the item.
|
|
* @param Location - Cell location where the item should be added.
|
|
* @return Cell location of the item, can be used later to remove the item.
|
|
*/
|
|
void Add(const ItemIDType ID, const FCellLocation& Location)
|
|
{
|
|
const int32 Idx = Items.AddUninitialized().Index;
|
|
FItem& Item = Items[Idx];
|
|
Item.ID = ID;
|
|
|
|
if (Location.Level == INDEX_NONE)
|
|
{
|
|
// Could not fit into any of the grids, add to spill list.
|
|
Item.Next = SpillList;
|
|
SpillList = Idx;
|
|
}
|
|
else
|
|
{
|
|
// Add to cell at specific level
|
|
FCell& Cell = FindOrAddCell(Location.X, Location.Y, Location.Level);
|
|
Item.Next = Cell.First;
|
|
Cell.First = Idx;
|
|
|
|
// Update per level counts
|
|
LevelItemCount[Location.Level]++;
|
|
|
|
// Update child counts
|
|
FCellLocation ParentLocation = Location;
|
|
while (ParentLocation.Level < NumLevels - 1)
|
|
{
|
|
ParentLocation.X = LevelDown(ParentLocation.X);
|
|
ParentLocation.Y = LevelDown(ParentLocation.Y);
|
|
ParentLocation.Level++;
|
|
FCell& ParentCell = FindOrAddCell(ParentLocation.X, ParentLocation.Y, ParentLocation.Level);
|
|
ParentCell.ChildItemCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Removes item based on the bounding box it was added with.
|
|
* @param ID - External ID used to identify the item.
|
|
* @param OldBounds - The same bounding box the item was previously added or moved with.
|
|
*/
|
|
void Remove(const ItemIDType ID, const FBox& OldBounds)
|
|
{
|
|
const FCellLocation OldLocation = CalcCellLocation(OldBounds);
|
|
Remove(ID, OldLocation);
|
|
}
|
|
|
|
/** Removes item based on the cell location it was added with.
|
|
* @param ID - External ID used to identify the item.
|
|
* @param OldLocation - The same cell location the item was previously added or moved with.
|
|
*/
|
|
void Remove(const ItemIDType ID, const FCellLocation& OldLocation)
|
|
{
|
|
if (OldLocation.Level == INDEX_NONE)
|
|
{
|
|
// Remove from spill list.
|
|
int32 PrevIdx = INDEX_NONE;
|
|
for (int32 Idx = SpillList; Idx != INDEX_NONE; PrevIdx = Idx, Idx = Items[Idx].Next)
|
|
{
|
|
if (Items[Idx].ID == ID)
|
|
{
|
|
if (PrevIdx == INDEX_NONE)
|
|
{
|
|
SpillList = Items[Idx].Next;
|
|
}
|
|
else
|
|
{
|
|
Items[PrevIdx].Next = Items[Idx].Next;
|
|
}
|
|
Items.RemoveAtUninitialized(Idx);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Remove from cell
|
|
if (FCell* Cell = FindCellMutable(OldLocation.X, OldLocation.Y, OldLocation.Level))
|
|
{
|
|
int32 PrevIdx = INDEX_NONE;
|
|
for (int32 Idx = Cell->First; Idx != INDEX_NONE; PrevIdx = Idx, Idx = Items[Idx].Next)
|
|
{
|
|
if (Items[Idx].ID == ID)
|
|
{
|
|
if (PrevIdx == INDEX_NONE)
|
|
{
|
|
Cell->First = Items[Idx].Next;
|
|
}
|
|
else
|
|
{
|
|
Items[PrevIdx].Next = Items[Idx].Next;
|
|
}
|
|
Items.RemoveAtUninitialized(Idx);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Update per level counts
|
|
LevelItemCount[OldLocation.Level]--;
|
|
|
|
// Update child counts
|
|
FCellLocation ParentLocation = OldLocation;
|
|
while (ParentLocation.Level < NumLevels - 1)
|
|
{
|
|
ParentLocation.X = LevelDown(ParentLocation.X);
|
|
ParentLocation.Y = LevelDown(ParentLocation.Y);
|
|
ParentLocation.Level++;
|
|
FCell* ParentCell = FindCellMutable(ParentLocation.X, ParentLocation.Y, ParentLocation.Level);
|
|
if (ParentCell)
|
|
{
|
|
ParentCell->ChildItemCount--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Moves item based on previous bounding box and new bounding box.
|
|
* @param ID - External ID used to identify the item.
|
|
* @param OldBounds - The same bounding box the item was previously added or moved with.
|
|
* @param NewBounds - New bounds of the item
|
|
* @return The cell location where the item was added to.
|
|
*/
|
|
FCellLocation Move(const ItemIDType ID, const FBox& OldBounds, const FBox& NewBounds)
|
|
{
|
|
const FCellLocation OldLocation = CalcCellLocation(OldBounds);
|
|
return Move(ID, OldLocation, NewBounds);
|
|
}
|
|
|
|
/** Moves item based on previous cell location and new bounding box.
|
|
* @param ID - External ID used to identify the item.
|
|
* @param OldLocation - The same cell location the item was previously added or moved with.
|
|
* @param NewBounds - New bounds of the item
|
|
* @return The cell location where the item was added to.
|
|
*/
|
|
FCellLocation Move(const ItemIDType ID, const FCellLocation& OldLocation, const FBox& NewBounds)
|
|
{
|
|
const FCellLocation NewLocation = CalcCellLocation(NewBounds);
|
|
if (NewLocation != OldLocation)
|
|
{
|
|
Remove(ID, OldLocation);
|
|
Add(ID, NewLocation);
|
|
}
|
|
return NewLocation;
|
|
}
|
|
|
|
/** Returns items that potentially touch the bounds. Operates on grid level, can have false positives.
|
|
* This can be faster than Query() for small query box sizes (i.e. max dimension CellSize*4) due to simpler logic.
|
|
* @param Bounds - Query bounding box.
|
|
* @param OutResults - Result of the query, IDs of potentially overlapping items.
|
|
*/
|
|
template <typename OutT>
|
|
void QuerySmall(const FBox& Bounds, OutT& OutResults) const
|
|
{
|
|
// Return items from buckets on all levels
|
|
for (int32 Level = 0; Level < NumLevels; Level++)
|
|
{
|
|
// Update per level counts
|
|
if (LevelItemCount[Level] == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Finest level query rect
|
|
FCellRect Rect = CalcQueryBounds(Bounds, Level);
|
|
|
|
// Use int64 to prevent overflow when FCellRect::MaxX or FCellRect::MaxY are equal to the maximum value of int32.
|
|
for (int64 Y = Rect.MinY; Y <= Rect.MaxY; Y++)
|
|
{
|
|
for (int64 X = Rect.MinX; X <= Rect.MaxX; X++)
|
|
{
|
|
if (const FCell* Cell = FindCell(IntCastChecked<int32>(X), IntCastChecked<int32>(Y), Level))
|
|
{
|
|
for (int32 Idx = Cell->First; Idx != INDEX_NONE; Idx = Items[Idx].Next)
|
|
{
|
|
OutResults.Add(Items[Idx].ID);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Everything from spill list
|
|
for (int32 Idx = SpillList; Idx != INDEX_NONE; Idx = Items[Idx].Next)
|
|
{
|
|
OutResults.Add(Items[Idx].ID);
|
|
}
|
|
}
|
|
|
|
/** Returns items that potentially touch the bounds. Operates on grid level, can have false positives.
|
|
* @param Bounds - Query bounding box.
|
|
* @param OutResults - Result of the query, IDs of potentially overlapping items.
|
|
*/
|
|
template <typename OutT>
|
|
void Query(const FBox& Bounds, OutT& OutResults) const
|
|
{
|
|
FCellRect Rects[NumLevels];
|
|
FCellRectIterator Iters[NumLevels];
|
|
int32 IterIdx = 0;
|
|
|
|
// Calculate cell bounds for each level, keep track of the coarsest level that has any items, we'll start from that.
|
|
for (int32 Level = 0; Level < NumLevels; Level++)
|
|
{
|
|
Rects[Level] = CalcQueryBounds(Bounds, Level);
|
|
}
|
|
|
|
// The idea of the iterator below is that it iterates over rectangle cells recursively towards finer levels, depth first.
|
|
// The previous level's iterator is kept in the Iters stack, and we can pop and continue that once the finer level is completed.
|
|
// Finer iterator rectangles is clamped against that levels tight bounds so that unnecessary cells are not visited.
|
|
// Coarser levels of the grid also store data if the finer levels under them has any items. This is used to skip iterating
|
|
// lower levels at certain locations completely. This can be big advantage in larger query boxes, compared to iterating all cells as in QuerySmall().
|
|
|
|
// Init coarsest iterator
|
|
const int32 StartLevel = NumLevels - 1;
|
|
Iters[IterIdx].Level = StartLevel;
|
|
Iters[IterIdx].Rect = Rects[StartLevel];
|
|
Iters[IterIdx].X = Iters[IterIdx].Rect.MinX;
|
|
Iters[IterIdx].Y = Iters[IterIdx].Rect.MinY;
|
|
IterIdx++;
|
|
|
|
while (IterIdx > 0)
|
|
{
|
|
FCellRectIterator& Iter = Iters[IterIdx - 1];
|
|
// Check if the iterator has finished
|
|
if (Iter.X > Iter.Rect.MaxX)
|
|
{
|
|
Iter.X = Iter.Rect.MinX;
|
|
Iter.Y++;
|
|
if (Iter.Y > Iter.Rect.MaxY)
|
|
{
|
|
IterIdx--;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (const FCell* Cell = FindCell( IntCastChecked<int32>(Iter.X) , IntCastChecked<int32>(Iter.Y), Iter.Level))
|
|
{
|
|
// Collect items from this cell.
|
|
for (int32 Idx = Cell->First; Idx != INDEX_NONE; Idx = Items[Idx].Next)
|
|
{
|
|
OutResults.Add(Items[Idx].ID);
|
|
}
|
|
|
|
// Advance to region at finer level if it has any items.
|
|
if (Cell->ChildItemCount > 0)
|
|
{
|
|
check(Iter.Level > 0);
|
|
const int FinerLevel = Iter.Level - 1;
|
|
// The iteration rect is intersection between current coarse cell and finer level bounds (which is more accurate).
|
|
const FCellRect& FinerLevelRect = Rects[FinerLevel];
|
|
const int32 MinX = ClampInt32(Iter.X * LevelRatio);
|
|
const int32 MinY = ClampInt32(Iter.Y * LevelRatio);
|
|
const int32 MaxX = ClampInt32(Iter.X * LevelRatio + LevelRatio - 1);
|
|
const int32 MaxY = ClampInt32(Iter.Y * LevelRatio + LevelRatio - 1);
|
|
FCellRect CurrentRect(MinX, MinY, MaxX, MaxY);
|
|
FCellRect NewIterRect = IntersectRect(CurrentRect, FinerLevelRect);
|
|
// Advance if rect is not empty.
|
|
if (NewIterRect.MaxX >= NewIterRect.MinX && NewIterRect.MaxY >= NewIterRect.MinY)
|
|
{
|
|
FCellRectIterator& FinerIter = Iters[IterIdx];
|
|
FinerIter.Rect = NewIterRect;
|
|
FinerIter.X = NewIterRect.MinX;
|
|
FinerIter.Y = NewIterRect.MinY;
|
|
FinerIter.Level = FinerLevel;
|
|
IterIdx++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Advance iteration
|
|
Iter.X++;
|
|
}
|
|
|
|
// Everything from spill list
|
|
for (int32 Idx = SpillList; Idx != INDEX_NONE; Idx = Items[Idx].Next)
|
|
{
|
|
OutResults.Add(Items[Idx].ID);
|
|
}
|
|
}
|
|
|
|
/** Calculates cell based query rectangle. The bounds are expanded by half grid cell size because the items are stored for only one cell
|
|
* based on their center and side. For that reason the items can overlap the neighbor cells by half the cell size.
|
|
* @param Bounds - Query bounding box to quantize.
|
|
* @param Level - Which level of the tree the to calculate the bounds for
|
|
* @return Quantized rectangle representing the cell bounds at specific level of the tree, coordinates inclusive.
|
|
*/
|
|
FCellRect CalcQueryBounds(const FBox& Bounds, const int32 Level) const
|
|
{
|
|
FCellRect Result;
|
|
Result.MinX = ClampInt32(FMath::FloorToInt(Bounds.Min.X * InvCellSize[Level] - 0.5f));
|
|
Result.MinY = ClampInt32(FMath::FloorToInt(Bounds.Min.Y * InvCellSize[Level] - 0.5f));
|
|
Result.MaxX = ClampInt32(FMath::FloorToInt(Bounds.Max.X * InvCellSize[Level] + 0.5f));
|
|
Result.MaxY = ClampInt32(FMath::FloorToInt(Bounds.Max.Y * InvCellSize[Level] + 0.5f));
|
|
return Result;
|
|
}
|
|
|
|
/** Returns intersection of the two cell bounding rectangles.
|
|
* @param Left - left hand side rectangle
|
|
* @param Right - right hand side rectangle
|
|
* @return Intersecting are between left and right.
|
|
*/
|
|
FCellRect IntersectRect(const FCellRect& Left, const FCellRect& Right) const
|
|
{
|
|
FCellRect Result;
|
|
Result.MinX = FMath::Max(Left.MinX, Right.MinX);
|
|
Result.MinY = FMath::Max(Left.MinY, Right.MinY);
|
|
Result.MaxX = FMath::Min(Left.MaxX, Right.MaxX);
|
|
Result.MaxY = FMath::Min(Left.MaxY, Right.MaxY);
|
|
return Result;
|
|
}
|
|
|
|
/** Levels down a coordinate using floor rounding.
|
|
* @param X - Coordinate to level down
|
|
* @return Coordinate at coarser level.
|
|
*/
|
|
static int32 LevelDown(int32 X)
|
|
{
|
|
X -= X < 0 ? (LevelRatio - 1) : 0;
|
|
return X / LevelRatio;
|
|
}
|
|
|
|
/** Finds grid level where the bounds fit inside a cell.
|
|
* @param Bounds - Bounding box of the item.
|
|
* @return Returns cell location of the item, or location at Level == INDEX_NONE if the item cannot fit in the grid.
|
|
*/
|
|
FCellLocation CalcCellLocation(const FBox& Bounds) const
|
|
{
|
|
FCellLocation Location(0, 0, 0);
|
|
|
|
const FVector Center = Bounds.GetCenter();
|
|
const FVector::FReal Diameter = FMath::Max(Bounds.Max.X - Bounds.Min.X, Bounds.Max.Y - Bounds.Min.Y);
|
|
for (Location.Level = 0; Location.Level < NumLevels; Location.Level++)
|
|
{
|
|
const int32 DiameterCells = ClampInt32(FMath::CeilToInt(Diameter * InvCellSize[Location.Level]));
|
|
// note that it's fine for DiameterCells to equal 0 - that would happen for 0-sized items (valid location, no extent).
|
|
if (DiameterCells <= 1)
|
|
{
|
|
Location.X = ClampInt32(FMath::FloorToInt(Center.X * InvCellSize[Location.Level]));
|
|
Location.Y = ClampInt32(FMath::FloorToInt(Center.Y * InvCellSize[Location.Level]));
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (Location.Level == NumLevels)
|
|
{
|
|
// Could not fit into any of the levels.
|
|
Location.X = 0;
|
|
Location.Y = 0;
|
|
Location.Level = INDEX_NONE;
|
|
}
|
|
|
|
return Location;
|
|
}
|
|
|
|
/** Finds the world box from a cell.
|
|
* @params CellLocation - Cell location.
|
|
* @return Returns the bounds of the cell.
|
|
*/
|
|
FBox CalcCellBounds(const FCellLocation& CellLocation) const
|
|
{
|
|
const FVector::FReal Size = CellSize[CellLocation.Level];
|
|
const FVector::FReal X = static_cast<FVector::FReal>(CellLocation.X) * Size;
|
|
const FVector::FReal Y = static_cast<FVector::FReal>(CellLocation.Y) * Size;
|
|
return FBox(FVector(X, Y, 0.0), FVector(X + Size, Y + Size, 0.0));
|
|
}
|
|
|
|
/** Returns a cell for specific location and level, creates new cell if it does not exist.
|
|
* @param X - Cell X coordinate.
|
|
* @param Y - Cell Y coordinate.
|
|
* @param Level - Grid Level.
|
|
* @return Reference to cell at specified location.
|
|
*/
|
|
FCell& FindOrAddCell(const int X, const int Y, const int Level)
|
|
{
|
|
FCell CellToFind(X, Y, Level);
|
|
const uint32 Hash = GetTypeHash(CellToFind);
|
|
FCell* Cell = Cells.FindByHash(Hash, CellToFind);
|
|
if (Cell != nullptr)
|
|
{
|
|
return *Cell;
|
|
}
|
|
FSetElementId Index = Cells.AddByHash(Hash, FCell(X, Y, Level));
|
|
return Cells[Index];
|
|
}
|
|
|
|
/** Returns a cell for specific location and level.
|
|
* @param X - Cell X coordinate.
|
|
* @param Y - Cell Y coordinate.
|
|
* @param Level - Grid Level.
|
|
* @return Pointer to cell at specified location, or return nullptr if the cell does not exist.
|
|
*/
|
|
FCell* FindCellMutable(const int X, const int Y, const int Level)
|
|
{
|
|
FCell CellToFind(X, Y, Level);
|
|
const uint32 Hash = GetTypeHash(CellToFind);
|
|
return Cells.FindByHash(Hash, CellToFind);
|
|
}
|
|
|
|
/** Returns a cell for specific location and level.
|
|
* @param X - Cell X coordinate.
|
|
* @param Y - Cell Y coordinate.
|
|
* @param Level - Grid Level.
|
|
* @return Pointer to cell at specified location, or return nullptr if the cell does not exist.
|
|
*/
|
|
const FCell* FindCell(const int X, const int Y, const int Level) const
|
|
{
|
|
FCell CellToFind(X, Y, Level);
|
|
const uint32 Hash = GetTypeHash(CellToFind);
|
|
return Cells.FindByHash(Hash, CellToFind);
|
|
}
|
|
|
|
/** @return Cell size at specific grid level. */
|
|
float GetCellSize(const int32 Level) const { return CellSize[Level]; }
|
|
|
|
/** @return 1/cellSize at specific grid level. */
|
|
float GetInvCellSize(const int32 Level) const { return InvCellSize[Level]; }
|
|
|
|
/** @return Array containing all the items. */
|
|
int32 GetLevelItemCount(const int32 Level) const { return LevelItemCount[Level]; }
|
|
|
|
/** @return Set containing all the cells. */
|
|
const TSet<FCell>& GetCells() const { return Cells; }
|
|
|
|
/** @return Sparse array containing all the items. */
|
|
const TSparseArray<FItem>& GetItems() const { return Items; }
|
|
|
|
/** @return Index in items of the first item on the spill list. */
|
|
int32 GetFirstSpillListItem() const { return SpillList; }
|
|
|
|
protected:
|
|
|
|
/** @return given int64 clamped in int32 range. */
|
|
static constexpr int32 ClampInt32(const int64 Value)
|
|
{
|
|
return static_cast<int32>(FMath::Clamp(
|
|
Value,
|
|
static_cast<int64>(std::numeric_limits<int32>::lowest()),
|
|
static_cast<int64>(std::numeric_limits<int32>::max())));
|
|
}
|
|
|
|
TStaticArray<float, NumLevels> CellSize; /** Lowest level cell size */
|
|
TStaticArray<float, NumLevels> InvCellSize; /** 1 / CellSize */
|
|
TStaticArray<int32, NumLevels> LevelItemCount; /** Number items per level, can be used to skip certain levels. */
|
|
TSet<FCell> Cells; /** TSet uses hash buckets to locate items. */
|
|
TSparseArray<FItem> Items; /** TSparseArray uses freelist to recycled used items. */
|
|
int32 SpillList = INDEX_NONE; /** Linked list of items that did not fit into any of the levels */
|
|
};
|