UDocumentation UE5.7 10.02.2026 (Source)
API documentation for Unreal Engine 5.7
HierarchicalHashGrid2D.h
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2
3#pragma once
4
5#if UE_ENABLE_INCLUDE_ORDER_DEPRECATED_IN_5_6
6#include "CoreMinimal.h"
7#endif // UE_ENABLE_INCLUDE_ORDER_DEPRECATED_IN_5_6
9#include "Math/Box.h"
10
31// 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
32// 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
33// going to be able to support its probably not worth investing time in this potential issue right now.
34template <int32 InNumLevels = 3, int32 InLevelRatio = 4, typename InItemIDType = uint32>
36{
37public:
38
40 static const int32 NumLevels = InNumLevels;
41 static const int32 LevelRatio = InLevelRatio;
46 struct FCell
47 {
48 FCell() : X(0), Y(0), Level(0) {}
49 FCell(const int32 InX, const int32 InY, const int32 InLevel) : X(InX), Y(InY), Level(InLevel) {}
50
57 {
58 constexpr uint32 H1 = 0x8da6b343; // Arbitrary big primes.
59 constexpr uint32 H2 = 0xd8163841;
60 constexpr uint32 H3 = 0xcb1ab31f;
61 return (H1 * uint32(Cell.X) + H2 * uint32(Cell.Y) + H3 * uint32(Cell.Level));
62 }
63
64 // Need for TSet<>
65 bool operator==(const FCell& RHS) const
66 {
67 return X == RHS.X && Y == RHS.Y && Level == RHS.Level;
68 }
69 };
70
73 {
74 FCellLocation() = default;
75 FCellLocation(const int32 InX, const int32 InY, const int32 InLevel) : X(InX), Y(InY), Level(InLevel) {}
76
77 bool operator==(const FCellLocation& RHS) const
78 {
79 return X == RHS.X && Y == RHS.Y && Level == RHS.Level;
80 }
81
82 bool operator!=(const FCellLocation& RHS) const
83 {
84 return !(*this == RHS);
85 }
86
87 int32 X = 0;
88 int32 Y = 0;
90 };
91
93 struct FItem
94 {
97 };
98
101 {
102 FCellRect() = default;
104
109 };
110
113 {
114 // Use int64 to prevent overflow when FCellRect::MaxX or FCellRect::MaxY are equal to the maximum value of int32.
115 int64 X = 0;
116 int64 Y = 0;
119 };
120
121
127 {
128 check(InCellSize > 0.0f);
129
130 float CurrCellSize = InCellSize;
131 for (int32 i = 0; i < NumLevels; i++)
132 {
134 InvCellSize[i] = 1.0f / CurrCellSize;
136 }
137
138 for (int32 i = 0; i < NumLevels; i++)
139 {
140 LevelItemCount[i] = 0;
141 }
142 }
143
147 void Initialize(const float InCellSize)
148 {
149 check(InCellSize > 0.0f);
150
151 Reset();
152
153 float CurrCellSize = InCellSize;
154 for (int32 i = 0; i < NumLevels; i++)
155 {
157 InvCellSize[i] = 1.0f / CurrCellSize;
159 }
160 }
161
163 void Reset()
164 {
166 Cells.Reset();
167 Items.Reset();
168
169 for (int32 i = 0; i < NumLevels; i++)
170 {
171 LevelItemCount[i] = 0;
172 }
173 }
174
180 FCellLocation Add(const ItemIDType ID, const FBox& Bounds)
181 {
182 const FCellLocation Location = CalcCellLocation(Bounds);
183 Add(ID, Location);
184 return Location;
185 }
186
192 void Add(const ItemIDType ID, const FCellLocation& Location)
193 {
194 const int32 Idx = Items.AddUninitialized().Index;
195 FItem& Item = Items[Idx];
196 Item.ID = ID;
197
198 if (Location.Level == INDEX_NONE)
199 {
200 // Could not fit into any of the grids, add to spill list.
201 Item.Next = SpillList;
202 SpillList = Idx;
203 }
204 else
205 {
206 // Add to cell at specific level
207 FCell& Cell = FindOrAddCell(Location.X, Location.Y, Location.Level);
208 Item.Next = Cell.First;
209 Cell.First = Idx;
210
211 // Update per level counts
212 LevelItemCount[Location.Level]++;
213
214 // Update child counts
215 FCellLocation ParentLocation = Location;
216 while (ParentLocation.Level < NumLevels - 1)
217 {
220 ParentLocation.Level++;
222 ParentCell.ChildItemCount++;
223 }
224 }
225 }
226
231 void Remove(const ItemIDType ID, const FBox& OldBounds)
232 {
233 const FCellLocation OldLocation = CalcCellLocation(OldBounds);
234 Remove(ID, OldLocation);
235 }
236
241 void Remove(const ItemIDType ID, const FCellLocation& OldLocation)
242 {
243 if (OldLocation.Level == INDEX_NONE)
244 {
245 // Remove from spill list.
247 for (int32 Idx = SpillList; Idx != INDEX_NONE; PrevIdx = Idx, Idx = Items[Idx].Next)
248 {
249 if (Items[Idx].ID == ID)
250 {
251 if (PrevIdx == INDEX_NONE)
252 {
253 SpillList = Items[Idx].Next;
254 }
255 else
256 {
257 Items[PrevIdx].Next = Items[Idx].Next;
258 }
259 Items.RemoveAtUninitialized(Idx);
260 break;
261 }
262 }
263 }
264 else
265 {
266 // Remove from cell
267 if (FCell* Cell = FindCellMutable(OldLocation.X, OldLocation.Y, OldLocation.Level))
268 {
270 for (int32 Idx = Cell->First; Idx != INDEX_NONE; PrevIdx = Idx, Idx = Items[Idx].Next)
271 {
272 if (Items[Idx].ID == ID)
273 {
274 if (PrevIdx == INDEX_NONE)
275 {
276 Cell->First = Items[Idx].Next;
277 }
278 else
279 {
280 Items[PrevIdx].Next = Items[Idx].Next;
281 }
282 Items.RemoveAtUninitialized(Idx);
283 break;
284 }
285 }
286
287 // Update per level counts
288 LevelItemCount[OldLocation.Level]--;
289
290 // Update child counts
291 FCellLocation ParentLocation = OldLocation;
292 while (ParentLocation.Level < NumLevels - 1)
293 {
296 ParentLocation.Level++;
298 if (ParentCell)
299 {
300 ParentCell->ChildItemCount--;
301 }
302 }
303 }
304 }
305 }
306
314 {
315 const FCellLocation OldLocation = CalcCellLocation(OldBounds);
316 return Move(ID, OldLocation, NewBounds);
317 }
318
325 FCellLocation Move(const ItemIDType ID, const FCellLocation& OldLocation, const FBox& NewBounds)
326 {
328 if (NewLocation != OldLocation)
329 {
330 Remove(ID, OldLocation);
331 Add(ID, NewLocation);
332 }
333 return NewLocation;
334 }
335
341 template <typename OutT>
342 void QuerySmall(const FBox& Bounds, OutT& OutResults) const
343 {
344 // Return items from buckets on all levels
345 for (int32 Level = 0; Level < NumLevels; Level++)
346 {
347 // Update per level counts
348 if (LevelItemCount[Level] == 0)
349 {
350 continue;
351 }
352
353 // Finest level query rect
354 FCellRect Rect = CalcQueryBounds(Bounds, Level);
355
356 // Use int64 to prevent overflow when FCellRect::MaxX or FCellRect::MaxY are equal to the maximum value of int32.
357 for (int64 Y = Rect.MinY; Y <= Rect.MaxY; Y++)
358 {
359 for (int64 X = Rect.MinX; X <= Rect.MaxX; X++)
360 {
362 {
363 for (int32 Idx = Cell->First; Idx != INDEX_NONE; Idx = Items[Idx].Next)
364 {
365 OutResults.Add(Items[Idx].ID);
366 }
367 }
368 }
369 }
370 }
371
372 // Everything from spill list
373 for (int32 Idx = SpillList; Idx != INDEX_NONE; Idx = Items[Idx].Next)
374 {
375 OutResults.Add(Items[Idx].ID);
376 }
377 }
378
383 template <typename OutT>
384 void Query(const FBox& Bounds, OutT& OutResults) const
385 {
388 int32 IterIdx = 0;
389
390 // Calculate cell bounds for each level, keep track of the coarsest level that has any items, we'll start from that.
391 for (int32 Level = 0; Level < NumLevels; Level++)
392 {
393 Rects[Level] = CalcQueryBounds(Bounds, Level);
394 }
395
396 // The idea of the iterator below is that it iterates over rectangle cells recursively towards finer levels, depth first.
397 // The previous level's iterator is kept in the Iters stack, and we can pop and continue that once the finer level is completed.
398 // Finer iterator rectangles is clamped against that levels tight bounds so that unnecessary cells are not visited.
399 // Coarser levels of the grid also store data if the finer levels under them has any items. This is used to skip iterating
400 // lower levels at certain locations completely. This can be big advantage in larger query boxes, compared to iterating all cells as in QuerySmall().
401
402 // Init coarsest iterator
403 const int32 StartLevel = NumLevels - 1;
404 Iters[IterIdx].Level = StartLevel;
405 Iters[IterIdx].Rect = Rects[StartLevel];
406 Iters[IterIdx].X = Iters[IterIdx].Rect.MinX;
407 Iters[IterIdx].Y = Iters[IterIdx].Rect.MinY;
408 IterIdx++;
409
410 while (IterIdx > 0)
411 {
412 FCellRectIterator& Iter = Iters[IterIdx - 1];
413 // Check if the iterator has finished
414 if (Iter.X > Iter.Rect.MaxX)
415 {
416 Iter.X = Iter.Rect.MinX;
417 Iter.Y++;
418 if (Iter.Y > Iter.Rect.MaxY)
419 {
420 IterIdx--;
421 continue;
422 }
423 }
424
425 if (const FCell* Cell = FindCell( IntCastChecked<int32>(Iter.X) , IntCastChecked<int32>(Iter.Y), Iter.Level))
426 {
427 // Collect items from this cell.
428 for (int32 Idx = Cell->First; Idx != INDEX_NONE; Idx = Items[Idx].Next)
429 {
430 OutResults.Add(Items[Idx].ID);
431 }
432
433 // Advance to region at finer level if it has any items.
434 if (Cell->ChildItemCount > 0)
435 {
436 check(Iter.Level > 0);
437 const int FinerLevel = Iter.Level - 1;
438 // The iteration rect is intersection between current coarse cell and finer level bounds (which is more accurate).
440 const int32 MinX = ClampInt32(Iter.X * LevelRatio);
441 const int32 MinY = ClampInt32(Iter.Y * LevelRatio);
442 const int32 MaxX = ClampInt32(Iter.X * LevelRatio + LevelRatio - 1);
443 const int32 MaxY = ClampInt32(Iter.Y * LevelRatio + LevelRatio - 1);
444 FCellRect CurrentRect(MinX, MinY, MaxX, MaxY);
446 // Advance if rect is not empty.
447 if (NewIterRect.MaxX >= NewIterRect.MinX && NewIterRect.MaxY >= NewIterRect.MinY)
448 {
452 FinerIter.Y = NewIterRect.MinY;
453 FinerIter.Level = FinerLevel;
454 IterIdx++;
455 }
456 }
457 }
458
459 // Advance iteration
460 Iter.X++;
461 }
462
463 // Everything from spill list
464 for (int32 Idx = SpillList; Idx != INDEX_NONE; Idx = Items[Idx].Next)
465 {
466 OutResults.Add(Items[Idx].ID);
467 }
468 }
469
476 FCellRect CalcQueryBounds(const FBox& Bounds, const int32 Level) const
477 {
478 FCellRect Result;
479 Result.MinX = ClampInt32(FMath::FloorToInt(Bounds.Min.X * InvCellSize[Level] - 0.5f));
480 Result.MinY = ClampInt32(FMath::FloorToInt(Bounds.Min.Y * InvCellSize[Level] - 0.5f));
481 Result.MaxX = ClampInt32(FMath::FloorToInt(Bounds.Max.X * InvCellSize[Level] + 0.5f));
482 Result.MaxY = ClampInt32(FMath::FloorToInt(Bounds.Max.Y * InvCellSize[Level] + 0.5f));
483 return Result;
484 }
485
492 {
493 FCellRect Result;
494 Result.MinX = FMath::Max(Left.MinX, Right.MinX);
495 Result.MinY = FMath::Max(Left.MinY, Right.MinY);
496 Result.MaxX = FMath::Min(Left.MaxX, Right.MaxX);
497 Result.MaxY = FMath::Min(Left.MaxY, Right.MaxY);
498 return Result;
499 }
500
506 {
507 X -= X < 0 ? (LevelRatio - 1) : 0;
508 return X / LevelRatio;
509 }
510
516 {
517 FCellLocation Location(0, 0, 0);
518
519 const FVector Center = Bounds.GetCenter();
520 const FVector::FReal Diameter = FMath::Max(Bounds.Max.X - Bounds.Min.X, Bounds.Max.Y - Bounds.Min.Y);
521 for (Location.Level = 0; Location.Level < NumLevels; Location.Level++)
522 {
523 const int32 DiameterCells = ClampInt32(FMath::CeilToInt(Diameter * InvCellSize[Location.Level]));
524 // note that it's fine for DiameterCells to equal 0 - that would happen for 0-sized items (valid location, no extent).
525 if (DiameterCells <= 1)
526 {
527 Location.X = ClampInt32(FMath::FloorToInt(Center.X * InvCellSize[Location.Level]));
528 Location.Y = ClampInt32(FMath::FloorToInt(Center.Y * InvCellSize[Location.Level]));
529 break;
530 }
531 }
532
533 if (Location.Level == NumLevels)
534 {
535 // Could not fit into any of the levels.
536 Location.X = 0;
537 Location.Y = 0;
538 Location.Level = INDEX_NONE;
539 }
540
541 return Location;
542 }
543
549 {
551 const FVector::FReal X = static_cast<FVector::FReal>(CellLocation.X) * Size;
552 const FVector::FReal Y = static_cast<FVector::FReal>(CellLocation.Y) * Size;
553 return FBox(FVector(X, Y, 0.0), FVector(X + Size, Y + Size, 0.0));
554 }
555
562 FCell& FindOrAddCell(const int X, const int Y, const int Level)
563 {
564 FCell CellToFind(X, Y, Level);
565 const uint32 Hash = GetTypeHash(CellToFind);
566 FCell* Cell = Cells.FindByHash(Hash, CellToFind);
567 if (Cell != nullptr)
568 {
569 return *Cell;
570 }
571 FSetElementId Index = Cells.AddByHash(Hash, FCell(X, Y, Level));
572 return Cells[Index];
573 }
574
581 FCell* FindCellMutable(const int X, const int Y, const int Level)
582 {
583 FCell CellToFind(X, Y, Level);
584 const uint32 Hash = GetTypeHash(CellToFind);
585 return Cells.FindByHash(Hash, CellToFind);
586 }
587
594 const FCell* FindCell(const int X, const int Y, const int Level) const
595 {
596 FCell CellToFind(X, Y, Level);
597 const uint32 Hash = GetTypeHash(CellToFind);
598 return Cells.FindByHash(Hash, CellToFind);
599 }
600
602 float GetCellSize(const int32 Level) const { return CellSize[Level]; }
603
605 float GetInvCellSize(const int32 Level) const { return InvCellSize[Level]; }
606
608 int32 GetLevelItemCount(const int32 Level) const { return LevelItemCount[Level]; }
609
611 const TSet<FCell>& GetCells() const { return Cells; }
612
614 const TSparseArray<FItem>& GetItems() const { return Items; }
615
618
619protected:
620
622 static constexpr int32 ClampInt32(const int64 Value)
623 {
624 return static_cast<int32>(FMath::Clamp(
625 Value,
626 static_cast<int64>(std::numeric_limits<int32>::lowest()),
627 static_cast<int64>(std::numeric_limits<int32>::max())));
628 }
629
636};
#define check(expr)
Definition AssertionMacros.h:314
@ INDEX_NONE
Definition CoreMiscDefines.h:150
FPlatformTypes::int64 int64
A 64-bit signed integer.
Definition Platform.h:1127
FPlatformTypes::int32 int32
A 32-bit signed integer.
Definition Platform.h:1125
UE_FORCEINLINE_HINT TSharedRef< CastToType, Mode > StaticCastSharedRef(TSharedRef< CastFromType, Mode > const &InSharedRef)
Definition SharedPointer.h:127
#define X(Name, Desc)
Definition FormatStringSan.h:47
#define FVector
Definition IOSSystemIncludes.h:8
UE::Math::TBox< double > FBox
Definition MathFwd.h:55
USkinnedMeshComponent float
Definition SkinnedMeshComponent.h:60
uint32 Size
Definition VulkanMemory.cpp:4034
uint32_t uint32
Definition binka_ue_file_header.h:6
Definition SetUtilities.h:95
Definition HierarchicalHashGrid2D.h:36
const TSet< FCell > & GetCells() const
Definition HierarchicalHashGrid2D.h:611
void QuerySmall(const FBox &Bounds, OutT &OutResults) const
Definition HierarchicalHashGrid2D.h:342
FCellLocation Add(const ItemIDType ID, const FBox &Bounds)
Definition HierarchicalHashGrid2D.h:180
void Add(const ItemIDType ID, const FCellLocation &Location)
Definition HierarchicalHashGrid2D.h:192
FCellLocation Move(const ItemIDType ID, const FCellLocation &OldLocation, const FBox &NewBounds)
Definition HierarchicalHashGrid2D.h:325
FCell & FindOrAddCell(const int X, const int Y, const int Level)
Definition HierarchicalHashGrid2D.h:562
void Initialize(const float InCellSize)
Definition HierarchicalHashGrid2D.h:147
FCellRect CalcQueryBounds(const FBox &Bounds, const int32 Level) const
Definition HierarchicalHashGrid2D.h:476
FBox CalcCellBounds(const FCellLocation &CellLocation) const
Definition HierarchicalHashGrid2D.h:548
int32 GetLevelItemCount(const int32 Level) const
Definition HierarchicalHashGrid2D.h:608
FCellRect IntersectRect(const FCellRect &Left, const FCellRect &Right) const
Definition HierarchicalHashGrid2D.h:491
int32 SpillList
Definition HierarchicalHashGrid2D.h:635
FCellLocation CalcCellLocation(const FBox &Bounds) const
Definition HierarchicalHashGrid2D.h:515
const FCell * FindCell(const int X, const int Y, const int Level) const
Definition HierarchicalHashGrid2D.h:594
float GetInvCellSize(const int32 Level) const
Definition HierarchicalHashGrid2D.h:605
const TSparseArray< FItem > & GetItems() const
Definition HierarchicalHashGrid2D.h:614
static const int32 LevelRatio
Definition HierarchicalHashGrid2D.h:41
static constexpr int32 ClampInt32(const int64 Value)
Definition HierarchicalHashGrid2D.h:622
TStaticArray< float, NumLevels > InvCellSize
Definition HierarchicalHashGrid2D.h:631
TSparseArray< FItem > Items
Definition HierarchicalHashGrid2D.h:634
TStaticArray< int32, NumLevels > LevelItemCount
Definition HierarchicalHashGrid2D.h:632
THierarchicalHashGrid2D(const float InCellSize=500.f)
Definition HierarchicalHashGrid2D.h:125
int32 GetFirstSpillListItem() const
Definition HierarchicalHashGrid2D.h:617
TSet< FCell > Cells
Definition HierarchicalHashGrid2D.h:633
FCell * FindCellMutable(const int X, const int Y, const int Level)
Definition HierarchicalHashGrid2D.h:581
float GetCellSize(const int32 Level) const
Definition HierarchicalHashGrid2D.h:602
FCellLocation Move(const ItemIDType ID, const FBox &OldBounds, const FBox &NewBounds)
Definition HierarchicalHashGrid2D.h:313
TStaticArray< float, NumLevels > CellSize
Definition HierarchicalHashGrid2D.h:630
void Query(const FBox &Bounds, OutT &OutResults) const
Definition HierarchicalHashGrid2D.h:384
void Remove(const ItemIDType ID, const FBox &OldBounds)
Definition HierarchicalHashGrid2D.h:231
InItemIDType ItemIDType
Definition HierarchicalHashGrid2D.h:39
void Reset()
Definition HierarchicalHashGrid2D.h:163
void Remove(const ItemIDType ID, const FCellLocation &OldLocation)
Definition HierarchicalHashGrid2D.h:241
static int32 LevelDown(int32 X)
Definition HierarchicalHashGrid2D.h:505
static const int32 NumLevels
Definition HierarchicalHashGrid2D.h:40
Definition SparseArray.h:524
Definition StaticArray.h:26
U16 Index
Definition radfft.cpp:71
static constexpr UE_FORCEINLINE_HINT T Clamp(const T X, const T MinValue, const T MaxValue)
Definition UnrealMathUtility.h:592
Definition LinuxPlatformSplash.cpp:43
Definition HierarchicalHashGrid2D.h:73
int32 X
Definition HierarchicalHashGrid2D.h:87
bool operator!=(const FCellLocation &RHS) const
Definition HierarchicalHashGrid2D.h:82
bool operator==(const FCellLocation &RHS) const
Definition HierarchicalHashGrid2D.h:77
FCellLocation(const int32 InX, const int32 InY, const int32 InLevel)
Definition HierarchicalHashGrid2D.h:75
int32 Y
Definition HierarchicalHashGrid2D.h:88
int32 Level
Definition HierarchicalHashGrid2D.h:89
Definition HierarchicalHashGrid2D.h:113
int64 X
Definition HierarchicalHashGrid2D.h:115
int32 Level
Definition HierarchicalHashGrid2D.h:117
FCellRect Rect
Definition HierarchicalHashGrid2D.h:118
int64 Y
Definition HierarchicalHashGrid2D.h:116
Definition HierarchicalHashGrid2D.h:101
int32 MinX
Definition HierarchicalHashGrid2D.h:105
int32 MaxY
Definition HierarchicalHashGrid2D.h:108
int32 MinY
Definition HierarchicalHashGrid2D.h:106
FCellRect(const int32 InMinX, const int32 InMinY, const int32 InMaxX, const int32 InMaxY)
Definition HierarchicalHashGrid2D.h:103
int32 MaxX
Definition HierarchicalHashGrid2D.h:107
Definition HierarchicalHashGrid2D.h:47
int32 Level
Definition HierarchicalHashGrid2D.h:51
FCell()
Definition HierarchicalHashGrid2D.h:48
int32 ChildItemCount
Definition HierarchicalHashGrid2D.h:53
int32 Y
Definition HierarchicalHashGrid2D.h:51
bool operator==(const FCell &RHS) const
Definition HierarchicalHashGrid2D.h:65
FCell(const int32 InX, const int32 InY, const int32 InLevel)
Definition HierarchicalHashGrid2D.h:49
int32 X
Definition HierarchicalHashGrid2D.h:51
friend uint32 GetTypeHash(const FCell &Cell)
Definition HierarchicalHashGrid2D.h:56
int32 First
Definition HierarchicalHashGrid2D.h:52
Definition HierarchicalHashGrid2D.h:94
ItemIDType ID
Definition HierarchicalHashGrid2D.h:95
int32 Next
Definition HierarchicalHashGrid2D.h:96
TVector< T > GetCenter() const
Definition Box.h:375
TVector< T > Min
Definition Box.h:39
TVector< T > Max
Definition Box.h:42
T Y
Definition Vector.h:65
double FReal
Definition Vector.h:55
T X
Definition Vector.h:62