// 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 struct TSimpleCellGrid { typedef CellType FCellType; float GridCellSize; FBox WorldBounds; FVector Origin; FVector BoundsSize; FGridSize2D GridSize; protected: TArray 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(FMath::CeilToInt(RealBoundsSize.X / InCellSize)), IntCastChecked(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::SizeType CellCount = FMath::Min(TempCellCount, (uint64)TNumericLimits::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::SizeType CellCount = FMath::Min(TempCellCount, (uint64)TNumericLimits::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(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(FMath::TruncToInt((WorldLocation.X - Origin.X) / GridCellSize)), IntCastChecked(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::SizeType CellCount = FMath::Min(TempCellCount, (uint64)TNumericLimits::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); } };