Files
UnrealEngine/Engine/Source/Runtime/AIModule/Public/SimpleCellGrid.h
2025-05-18 13:04:45 +08:00

388 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Serialization/Archive.h"
#include "Math/NumericLimits.h"
struct FGridSize2D
{
uint32 Width;
uint32 Height;
explicit FGridSize2D(uint32 InWidth = 0, uint32 InHeight = 0)
: Width(InWidth), Height(InHeight)
{
}
bool operator==(const FGridSize2D& Other) const
{
return Width == Other.Width && Height == Other.Height;
}
};
/** No virtuals on purpose */
template<typename CellType, int InvalidCellValue = 0>
struct TSimpleCellGrid
{
typedef CellType FCellType;
float GridCellSize;
FBox WorldBounds;
FVector Origin;
FVector BoundsSize;
FGridSize2D GridSize;
protected:
TArray<FCellType> Cells;
public:
TSimpleCellGrid()
: GridCellSize(0.0f)
, WorldBounds(ForceInitToZero)
, Origin(FLT_MAX)
, BoundsSize(0)
{
}
/** Initialize the grid from a bounding box
* @param InCellSize Size of a cell
* @param Bounds Bounding box the grid needs to encapsulate
* @return True if the grid was initialized properly
*/
bool Init(const float InCellSize, const FBox& Bounds)
{
if (InCellSize <= 0.0f || !Bounds.IsValid)
{
return false;
}
GridCellSize = InCellSize;
const FVector RealBoundsSize = Bounds.GetSize();
GridSize = FGridSize2D(IntCastChecked<uint32>(FMath::CeilToInt(RealBoundsSize.X / InCellSize)), IntCastChecked<uint32>(FMath::CeilToInt(RealBoundsSize.Y / InCellSize)));
BoundsSize = FVector(GridSize.Width * InCellSize, GridSize.Height * InCellSize, RealBoundsSize.Z);
Origin = FVector(Bounds.Min.X, Bounds.Min.Y, (Bounds.Min.Z + Bounds.Max.Z) * 0.5f);
UpdateWorldBounds();
const uint64 TempCellCount = GridSize.Width * GridSize.Height;
const typename TArray<FCellType>::SizeType CellCount = FMath::Min(TempCellCount, (uint64)TNumericLimits<typename TArray<FCellType>::SizeType>::Max());
ensureMsgf(CellCount == TempCellCount, TEXT("Grid width and height are too big."));
Cells.AddDefaulted(CellCount);
return true;
}
/** Initialize the grid
* @param InCellSize Size of a cell
* @param InGridSize Amount of cells needed in each direction
* @param InOrigin World location of the grid
* @param VerticalBoundSize Size of the grid above and under the Origin
* @return True if the grid was initialized properly
*/
bool Init(const float InCellSize, const FGridSize2D& InGridSize, const FVector& InOrigin, const float VerticalBoundSize)
{
if (InCellSize < 0 || InGridSize.Height == 0 || InGridSize.Width == 0 || VerticalBoundSize < 0)
{
return false;
}
GridCellSize = InCellSize;
GridSize = InGridSize;
BoundsSize = FVector(GridSize.Width * InCellSize, GridSize.Height * InCellSize, VerticalBoundSize);
Origin = InOrigin;
UpdateWorldBounds();
const uint64 TempCellCount = GridSize.Width * GridSize.Height;
const typename TArray<FCellType>::SizeType CellCount = FMath::Min(TempCellCount, (uint64)TNumericLimits<typename TArray<FCellType>::SizeType>::Max());
ensureMsgf(CellCount == TempCellCount, TEXT("Grid width and height are too big."));
Cells.AddDefaulted(CellCount);
return true;
}
/** Change the vertical position of the grid by providing an interval */
void SetVerticalInterval(const FFloatInterval& VerticalInterval)
{
if (VerticalInterval.IsValid())
{
BoundsSize.Z = VerticalInterval.Size();
Origin.Z = VerticalInterval.Interpolate(0.5);
UpdateWorldBounds();
}
}
void UpdateWorldBounds()
{
WorldBounds = FBox(Origin - FVector(0, 0, BoundsSize.Z / 2), Origin + FVector(BoundsSize.X, BoundsSize.Y, BoundsSize.Z / 2));
}
FORCEINLINE bool IsValid() const
{
return Cells.Num() && GridCellSize > 0;
}
FORCEINLINE bool IsValidIndex(const int32 CellIndex) const
{
return Cells.IsValidIndex(CellIndex);
}
FORCEINLINE bool IsValidCoord(int32 LocationX, int32 LocationY) const
{
return (LocationX >= 0) && (LocationX < (int32)GridSize.Width) && (LocationY >= 0) && (LocationY < (int32)GridSize.Height);
}
FORCEINLINE bool IsValidCoord(const FIntVector& CellCoords) const
{
return IsValidCoord(CellCoords.X, CellCoords.Y);
}
FORCEINLINE uint32 GetAllocatedSize() const
{
return IntCastChecked<uint32>(Cells.GetAllocatedSize());
}
/** Convert world location to (X,Y) coords on grid, result can be outside grid */
FORCEINLINE FIntVector GetCellCoordsUnsafe(const FVector& WorldLocation) const
{
return FIntVector(
IntCastChecked<int32>(FMath::TruncToInt((WorldLocation.X - Origin.X) / GridCellSize)),
IntCastChecked<int32>(FMath::TruncToInt((WorldLocation.Y - Origin.Y) / GridCellSize)),
0);
}
/** Convert world location to (X,Y) coords on grid, result is clamped to grid */
FIntVector GetCellCoords(const FVector& WorldLocation) const
{
const FIntVector UnsafeCoords = GetCellCoordsUnsafe(WorldLocation);
return FIntVector(FMath::Clamp(UnsafeCoords.X, 0, (int32)GridSize.Width - 1), FMath::Clamp(UnsafeCoords.Y, 0, (int32)GridSize.Height - 1), 0);
}
/** Convert cell index to coord X on grid, result can be invalid */
FORCEINLINE int32 GetCellCoordX(int32 CellIndex) const
{
return CellIndex / GridSize.Height;
}
/** Convert cell index to coord Y on grid, result can be invalid */
FORCEINLINE int32 GetCellCoordY(int32 CellIndex) const
{
return CellIndex % GridSize.Height;
}
/** Convert cell index to (X,Y) coords on grid */
FORCEINLINE FIntVector GetCellCoords(int32 CellIndex) const
{
return FIntVector(GetCellCoordX(CellIndex), GetCellCoordY(CellIndex), 0);
}
/** Convert world location to cell index, result can be invalid */
int32 GetCellIndexUnsafe(const FVector& WorldLocation) const
{
const FIntVector CellCoords = GetCellCoordsUnsafe(WorldLocation);
return GetCellIndexUnsafe(CellCoords.X, CellCoords.Y);
}
/** Convert (X,Y) coords on grid to cell index, result can be invalid */
FORCEINLINE int32 GetCellIndexUnsafe(const FIntVector& CellCoords) const
{
return GetCellIndexUnsafe(CellCoords.X, CellCoords.Y);
}
/** Convert (X,Y) coords on grid to cell index, result can be invalid */
FORCEINLINE int32 GetCellIndexUnsafe(int32 LocationX, int32 LocationY) const
{
return (LocationX * GridSize.Height) + LocationY;
}
/** Convert (X,Y) coords on grid to cell index, returns -1 for position outside grid */
FORCEINLINE int32 GetCellIndex(int32 LocationX, int32 LocationY) const
{
return IsValidCoord(LocationX, LocationY) ? GetCellIndexUnsafe(LocationX, LocationY) : INDEX_NONE;
}
/** Convert world location to cell index, returns -1 for position outside grid */
int32 GetCellIndex(const FVector& WorldLocation) const
{
const FIntVector CellCoords = GetCellCoordsUnsafe(WorldLocation);
return GetCellIndex(CellCoords.X, CellCoords.Y);
}
/** Return the bounding box of a cell. */
FORCEINLINE FBox GetWorldCellBox(int32 CellIndex) const
{
return GetWorldCellBox(GetCellCoordX(CellIndex), GetCellCoordY(CellIndex));
}
/** Return the bounding box of a cell. */
FORCEINLINE FBox GetWorldCellBox(int32 LocationX, int32 LocationY) const
{
return FBox(
Origin + FVector(LocationX * GridCellSize, LocationY * GridCellSize, -BoundsSize.Z * 0.5f),
Origin + FVector((LocationX + 1) * GridCellSize, (LocationY + 1) * GridCellSize, BoundsSize.Z * 0.5f)
);
}
/** Return the 2D bounding box of a cell. */
FORCEINLINE FBox2D GetWorldCellBox2D(int32 CellIndex) const
{
return GetWorldCellBox2D(GetCellCoordX(CellIndex), GetCellCoordY(CellIndex));
}
/** Return the 2D bounding box of a cell. */
FORCEINLINE FBox2D GetWorldCellBox2D(int32 LocationX, int32 LocationY) const
{
return FBox2D(
FVector2D(Origin) + FVector2D(LocationX * GridCellSize, LocationY * GridCellSize),
FVector2D(Origin) + FVector2D((LocationX + 1) * GridCellSize, (LocationY + 1) * GridCellSize)
);
}
/** Return the world bounding box of all cells included in the given rectangle. */
FORCEINLINE FBox GetWorldCellRectangleBox(const FIntRect& CellRect) const
{
return FBox(
Origin + FVector(CellRect.Min.X * GridCellSize, CellRect.Min.Y * GridCellSize, -BoundsSize.Z * 0.5f),
Origin + FVector((CellRect.Max.X+1) * GridCellSize, (CellRect.Max.Y+1) * GridCellSize, BoundsSize.Z * 0.5f)
);
}
/** Compute a rectangle of cells overlapping the given WorldBox. */
FORCEINLINE FIntRect GetCellRectangleFromBox(const FBox& WorldBox) const
{
if (!WorldBox.IsValid)
{
return FIntRect();
}
const FIntVector CellMin = GetCellCoordsUnsafe(WorldBox.Min);
const FIntVector CellMax = GetCellCoordsUnsafe(WorldBox.Max);
return FIntRect(CellMin.X, CellMin.Y, CellMax.X, CellMax.Y);
}
/** Return an IntRect that includes all the cells of the grid. Max is inclusive. */
FORCEINLINE FIntRect GetGridRectangle() const
{
return FIntRect(0, 0, GridSize.Width-1, GridSize.Height-1);
}
FORCEINLINE FVector GetWorldCellCenter(int32 CellIndex) const
{
return GetWorldCellCenter(GetCellCoordX(CellIndex), GetCellCoordY(CellIndex));
}
FORCEINLINE FVector GetWorldCellCenter(int32 LocationX, int32 LocationY) const
{
return Origin + FVector((LocationX + 0.5f) * GridCellSize, (LocationY + 0.5f) * GridCellSize, 0);
}
const FCellType& GetCellAtWorldLocationUnsafe(const FVector& WorldLocation) const
{
const int32 CellIndex = GetCellIndexUnsafe(WorldLocation);
return Cells[CellIndex];
}
const FCellType& GetCellAtWorldLocation(const FVector& WorldLocation) const
{
static FCellType InvalidCellInstance = FCellType(InvalidCellValue);
const int32 CellIndex = GetCellIndex(WorldLocation);
return (CellIndex == INDEX_NONE) ? InvalidCellInstance : Cells[CellIndex];
}
FORCEINLINE FCellType& operator[](int32 CellIndex) { return Cells[CellIndex]; }
FORCEINLINE const FCellType& operator[](int32 CellIndex) const { return Cells[CellIndex]; }
FORCEINLINE FCellType& GetCellAtIndexUnsafe(int32 CellIndex) { return Cells.GetData()[CellIndex]; }
FORCEINLINE const FCellType& GetCellAtIndexUnsafe(int32 CellIndex) const { return Cells.GetData()[CellIndex]; }
FCellType& GetCellAtCoordsUnsafe(int32 LocationX, int32 LocationY) { return GetCellAtIndexUnsafe(GetCellIndexUnsafe(LocationX, LocationY)); }
const FCellType& GetCellAtCoordsUnsafe(int32 LocationX, int32 LocationY) const { return GetCellAtIndexUnsafe(GetCellIndexUnsafe(LocationX, LocationY)); }
FORCEINLINE int32 GetCellsCount() const
{
return Cells.Num();
}
FORCEINLINE int32 Num() const
{
return Cells.Num();
}
void Serialize(FArchive& Ar)
{
uint32 VersionNum = MAX_uint32;
Ar << VersionNum;
if (Ar.IsLoading())
{
if (VersionNum == MAX_uint32)
{
Ar << GridCellSize;
}
else
{
GridCellSize = VersionNum * 1.0f;
}
}
else
{
Ar << GridCellSize;
}
Ar << Origin;
Ar << BoundsSize;
Ar << GridSize.Width << GridSize.Height;
UpdateWorldBounds();
if (VersionNum == MAX_uint32)
{
Ar << Cells;
}
else
{
uint32 DataBytesCount = GetAllocatedSize();
Ar << DataBytesCount;
if (DataBytesCount > 0)
{
if (Ar.IsLoading())
{
const uint64 TempCellCount = GridSize.Width * GridSize.Height;
const typename TArray<FCellType>::SizeType CellCount = FMath::Min(TempCellCount, (uint64)TNumericLimits<typename TArray<FCellType>::SizeType>::Max());
ensureMsgf(CellCount == TempCellCount, TEXT("Grid width and height are too big."));
Cells.SetNum(CellCount);
DataBytesCount = FMath::Clamp(DataBytesCount, 0, CellCount * sizeof(FCellType));
}
Ar.Serialize(Cells.GetData(), DataBytesCount);
}
}
}
void AllocateMemory()
{
Cells.SetNum(GridSize.Width * GridSize.Height);
}
void FreeMemory()
{
Cells.Empty();
}
void Zero()
{
Cells.Reset();
Cells.AddZeroed(GridSize.Width * GridSize.Height);
}
void CleanUp()
{
Cells.Empty();
GridCellSize = 0;
Origin = FVector(FLT_MAX);
}
};