UDocumentation UE5.7 10.02.2026 (Source)
API documentation for Unreal Engine 5.7
GenericQuadTree.h
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2
3#pragma once
4
5#include "CoreMinimal.h"
6
8
9template <typename ElementType, int32 NodeCapacity = 4>
11{
13public:
14
17
18 TQuadTree(const FBox2D& InBox, float InMinimumQuadSize = 100.f);
19
21 const FBox2D& GetTreeBox() const { return TreeBox; }
22
24 void Insert(const ElementType& Element, const FBox2D& Box, const TCHAR* DebugContext = nullptr);
25
27 template<typename ElementAllocatorType>
29
32 template<typename CallbackType>
33 inline void GetElements(const FBox2D& Box, CallbackType&& Callback) const;
34
36 bool Remove(const ElementType& Instance, const FBox2D& Box);
37
40
42 void Empty();
43
45
48
50
51private:
52 enum QuadNames
53 {
54 TopLeft = 0,
55 TopRight = 1,
56 BottomLeft = 2,
57 BottomRight = 3
58 };
59
61 struct FNode
62 {
63 FBox2D Box;
64 ElementType Element;
65
66 FNode() {};
67
68 FNode(const ElementType& InElement, const FBox2D& InBox)
69 : Box(InBox)
71 {}
72
74 {
75 return Ar << Node.Box << Node.Element;
76 }
77 };
78
80 int32 GetQuads(const FBox2D& Box, TreeType* Quads[4]) const;
81
83 void Split();
84
86 template<typename ElementAllocatorType>
87 void GetIntersectingElements(const FBox2D& Box, TArray<ElementType, ElementAllocatorType>& ElementsOut) const;
88
90 template<typename CallbackType>
91 bool GetIntersectingElements(const FBox2D& Box, CallbackType& Callback) const;
92
94 bool RemoveNodeForElement(const ElementType& Element);
95
97 void InsertElementRecursive(const ElementType& Element, const FBox2D& Box, const TCHAR* DebugContext);
98
99private:
100
106 TArray<FNode> Nodes;
107
109 TreeType* SubTrees[4] = {nullptr};
110
112 FBox2D TreeBox;
113
115 FVector2D Position;
116
118 float MinimumQuadSize;
119
121 bool bInternal;
122};
123
124template <typename ElementType, int32 NodeCapacity /*= 4*/>
129
130template <typename ElementType, int32 NodeCapacity /*= 4*/>
136
137template <typename ElementType, int32 NodeCapacity /*= 4*/>
139{
140 Ar << Nodes;
141
142 bool SubTreeFlags[4] = { SubTrees[0] != nullptr, SubTrees[1] != nullptr, SubTrees[2] != nullptr, SubTrees[3] != nullptr };
143 Ar << SubTreeFlags[0] << SubTreeFlags[1] << SubTreeFlags[2] << SubTreeFlags[3];
144
145 for (int32 Idx = 0; Idx < 4; ++Idx)
146 {
147 if (SubTreeFlags[Idx])
148 {
149 if (Ar.IsLoading())
150 {
151 SubTrees[Idx] = new TreeType(FBox2D(), MinimumQuadSize);
152 }
153
154 SubTrees[Idx]->Serialize(Ar);
155 }
156 }
157
158 Ar << TreeBox;
159 Ar << Position;
160 Ar << bInternal;
161}
162
163template <typename ElementType, int32 NodeCapacity>
165 : TreeBox(Box)
166 , Position(Box.GetCenter())
167 , MinimumQuadSize(InMinimumQuadSize)
168 , bInternal(false)
169{
170 SubTrees[0] = SubTrees[1] = SubTrees[2] = SubTrees[3] = nullptr;
171}
172
173template <typename ElementType, int32 NodeCapacity>
178
179template <typename ElementType, int32 NodeCapacity>
181{
182 for (TreeType* SubTree : SubTrees)
183 {
184 delete SubTree;
185 SubTree = nullptr;
186 }
187}
188
189template <typename ElementType, int32 NodeCapacity>
191{
192 check(bInternal == false);
193
194 const FVector2D Extent = TreeBox.GetExtent();
195 const FVector2D XExtent = FVector2D(Extent.X, 0.f);
196 const FVector2D YExtent = FVector2D(0.f, Extent.Y);
197
198 /************************************************************************
199 * ___________max
200 * | | |
201 * | | |
202 * |-----c------
203 * | | |
204 * min___|_____|
205 *
206 * We create new quads by adding xExtent and yExtent
207 ************************************************************************/
208
209 const FVector2D C = Position;
210 const FVector2D TM = C + YExtent;
211 const FVector2D ML = C - XExtent;
212 const FVector2D MR = C + XExtent;
213 const FVector2D BM = C - YExtent;
214 const FVector2D BL = TreeBox.Min;
215 const FVector2D TR = TreeBox.Max;
216
217 SubTrees[TopLeft] = new TreeType(FBox2D(ML, TM), MinimumQuadSize);
218 SubTrees[TopRight] = new TreeType(FBox2D(C, TR), MinimumQuadSize);
219 SubTrees[BottomLeft] = new TreeType(FBox2D(BL, C), MinimumQuadSize);
220 SubTrees[BottomRight] = new TreeType(FBox2D(BM, MR), MinimumQuadSize);
221
222 //mark as no longer a leaf
223 bInternal = true;
224
225 // Place existing nodes and place them into the new subtrees that contain them
226 // If a node overlaps multiple subtrees, we retain the reference to it here in this quad
228 for (const FNode& Node : Nodes)
229 {
230 TreeType* Quads[4];
231 const int32 NumQuads = GetQuads(Node.Box, Quads);
232 check(NumQuads > 0);
233
234 if (NumQuads == 1)
235 {
236 Quads[0]->Nodes.Add(Node);
237 }
238 else
239 {
240 OverlappingNodes.Add(Node);
241 }
242 }
243
244 // Hang onto the nodes that don't fit cleanly into a single subtree
245 Nodes = OverlappingNodes;
246}
247
248template <typename ElementType, int32 NodeCapacity>
249void TQuadTree<ElementType, NodeCapacity>::Insert(const ElementType& Element, const FBox2D& Box, const TCHAR* DebugContext)
250{
251#if !UE_BUILD_SHIPPING
252 if (!Box.Intersect(TreeBox))
253 {
254 // Elements shouldn't be added outside the bounds of the top-level quad
255 UE_LOG(LogQuadTree, Warning, TEXT("[%s] Adding element (%s) that is outside the bounds of the quadtree root (%s). Consider resizing."), DebugContext ? DebugContext : TEXT("Unknown Source"), *Box.ToString(), *TreeBox.ToString());
256 }
257#endif // !UE_BUILD_SHIPPING
258
259 InsertElementRecursive(Element, Box, DebugContext);
260}
261
262template <typename ElementType, int32 NodeCapacity>
263void TQuadTree<ElementType, NodeCapacity>::InsertElementRecursive(const ElementType& Element, const FBox2D& Box, const TCHAR* DebugContext)
264{
265 TreeType* Quads[4];
266 const int32 NumQuads = GetQuads(Box, Quads);
267 if (NumQuads == 0)
268 {
269 // This should only happen for leaves
270 check(!bInternal);
271
272 // It's possible that all elements in the leaf are bigger than the leaf or that more elements than NodeCapacity exist outside the top level quad
273 // In either case, we can get into an endless spiral of splitting
274 const bool bCanSplitTree = TreeBox.GetSize().SizeSquared() > FMath::Square(MinimumQuadSize);
275 if (!bCanSplitTree || Nodes.Num() < NodeCapacity)
276 {
277 Nodes.Add(FNode(Element, Box));
278
279 if (!bCanSplitTree)
280 {
281 UE_LOG(LogQuadTree, Verbose, TEXT("[%s] Minimum size %f reached for quadtree at %s. Filling beyond capacity %d to %d"), DebugContext ? DebugContext : TEXT("Unknown Source"), MinimumQuadSize, *Position.ToString(), NodeCapacity, Nodes.Num());
282 }
283 }
284 else
285 {
286 // This quad is at capacity, so split and try again
287 Split();
288 InsertElementRecursive(Element, Box, DebugContext);
289 }
290 }
291 else if (NumQuads == 1)
292 {
293 check(bInternal);
294
295 // Fully contained in a single subtree, so insert it there
296 Quads[0]->InsertElementRecursive(Element, Box, DebugContext);
297 }
298 else
299 {
300 // Overlaps multiple subtrees, store here
301 check(bInternal);
302 Nodes.Add(FNode(Element, Box));
303 }
304}
305
306template <typename ElementType, int32 NodeCapacity>
308{
310 for (int32 NodeIdx = 0, NumNodes = Nodes.Num(); NodeIdx < NumNodes; ++NodeIdx)
311 {
312 if (Nodes[NodeIdx].Element == Element)
313 {
314 ElementIdx = NodeIdx;
315 break;
316 }
317 }
318
319 if (ElementIdx != INDEX_NONE)
320 {
321 Nodes.RemoveAtSwap(ElementIdx, EAllowShrinking::No);
322 return true;
323 }
324
325 return false;
326}
327
328template <typename ElementType, int32 NodeCapacity>
329bool TQuadTree<ElementType, NodeCapacity>::Remove(const ElementType& Element, const FBox2D& Box)
330{
331 bool bElementRemoved = false;
332
333 TreeType* Quads[4];
334 const int32 NumQuads = GetQuads(Box, Quads);
335
336 // Remove from nodes referenced by this quad
337 bElementRemoved = RemoveNodeForElement(Element);
338
339 // Try to remove from subtrees if necessary
340 for (int32 QuadIndex = 0; QuadIndex < NumQuads && !bElementRemoved; QuadIndex++)
341 {
342 bElementRemoved = Quads[QuadIndex]->Remove(Element, Box);
343 }
344
345 return bElementRemoved;
346}
347
348template <typename ElementType, int32 NodeCapacity>
349template <typename ElementAllocatorType>
351{
352 TreeType* Quads[4];
353 const int32 NumQuads = GetQuads(Box, Quads);
354
355 // Always include any nodes contained in this quad
356 GetIntersectingElements(Box, ElementsOut);
357
358 // As well as all relevant subtrees
359 for (int32 QuadIndex = 0; QuadIndex < NumQuads; QuadIndex++)
360 {
362 }
363}
364
365template <typename ElementType, int32 NodeCapacity>
366template <typename ElementAllocatorType>
368{
369 ElementsOut.Reserve(ElementsOut.Num() + Nodes.Num());
370 for (const FNode& Node : Nodes)
371 {
372 if (Box.Intersect(Node.Box))
373 {
374 //Debug performance will be at least 1 order slower
375 checkSlow(!ElementsOut.Contains(Node.Element));
376 ElementsOut.Add(Node.Element);
377 }
378 }
379}
380
381template <typename ElementType, int32 NodeCapacity>
382template<typename CallbackType>
384{
385 TreeType* Quads[4];
386 const int32 NumQuads = GetQuads(Box, Quads);
387
388 // Always include any nodes contained in this quad
389 if (!GetIntersectingElements(Box, Callback))
390 {
391 return;
392 }
393
394 // As well as all relevant subtrees
395 for (int32 QuadIndex = 0; QuadIndex < NumQuads; QuadIndex++)
396 {
397 Quads[QuadIndex]->GetElements(Box, Callback);
398 }
399}
400
401template <typename ElementType, int32 NodeCapacity>
402template<typename CallbackType>
404{
405 for (const FNode& Node : Nodes)
406 {
407 // Only execute the callback if the boxes intersect
408 // Return if the Callback returns false, ending iteration
409 if (Box.Intersect(Node.Box) && !Callback(Node.Element))
410 {
411 return false;
412 }
413 }
414
415 return true;
416}
417
418template <typename ElementType, int32 NodeCapacity>
419int32 TQuadTree<ElementType, NodeCapacity>::GetQuads(const FBox2D& Box, TreeType* Quads[4]) const
420{
421 int32 QuadCount = 0;
422 if (bInternal)
423 {
424 bool bNegX = Box.Min.X <= Position.X;
425 bool bNegY = Box.Min.Y <= Position.Y;
426
427 bool bPosX = Box.Max.X >= Position.X;
428 bool bPosY = Box.Max.Y >= Position.Y;
429
430 if (bNegX && bNegY)
431 {
432 Quads[QuadCount++] = SubTrees[BottomLeft];
433 }
434
435 if (bPosX && bNegY)
436 {
437 Quads[QuadCount++] = SubTrees[BottomRight];
438 }
439
440 if (bNegX && bPosY)
441 {
442 Quads[QuadCount++] = SubTrees[TopLeft];
443 }
444
445 if (bPosX && bPosY)
446 {
447 Quads[QuadCount++] = SubTrees[TopRight];
448 }
449 }
450
451 return QuadCount;
452}
453
454template <typename ElementType, int32 NodeCapacity>
456{
457 for (int32 TreeIdx = 0; TreeIdx < 4; ++TreeIdx)
458 {
459 if (TreeType* SubTree = SubTrees[TreeIdx])
460 {
461 OutDuplicate.SubTrees[TreeIdx] = new TreeType(FBox2D(0, 0), MinimumQuadSize);
462 SubTree->Duplicate(*OutDuplicate.SubTrees[TreeIdx]); //duplicate sub trees
463 }
464
465 }
466
467 OutDuplicate.Nodes = Nodes;
468 OutDuplicate.TreeBox = TreeBox;
469 OutDuplicate.Position = Position;
470 OutDuplicate.MinimumQuadSize = MinimumQuadSize;
471 OutDuplicate.bInternal = bInternal;
472}
473
474template <typename ElementType, int32 NodeCapacity>
476{
477 for (int32 TreeIdx = 0; TreeIdx < 4; ++TreeIdx)
478 {
479 if (TreeType* SubTree = SubTrees[TreeIdx])
480 {
481 delete SubTree;
482 SubTrees[TreeIdx] = nullptr;
483 }
484
485 }
486
487 Nodes.Empty();
488 bInternal = false;
489}
#define checkSlow(expr)
Definition AssertionMacros.h:332
#define check(expr)
Definition AssertionMacros.h:314
@ INDEX_NONE
Definition CoreMiscDefines.h:150
void EnsureRetrievingVTablePtrDuringCtor(const TCHAR *CtorSignature)
Definition CoreMisc.cpp:436
#define TEXT(x)
Definition Platform.h:1272
FPlatformTypes::TCHAR TCHAR
Either ANSICHAR or WIDECHAR, depending on whether the platform supports wide characters or the requir...
Definition Platform.h:1135
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
FArchive & operator<<(FArchive &Ar, FEnvQueryDebugProfileData::FStep &Data)
Definition EnvQueryTypes.cpp:489
#define DECLARE_LOG_CATEGORY_EXTERN(CategoryName, DefaultVerbosity, CompileTimeVerbosity)
Definition LogMacros.h:361
#define UE_LOG(CategoryName, Verbosity, Format,...)
Definition LogMacros.h:270
UE::Math::TVector2< double > FVector2D
Definition MathFwd.h:48
UE::Math::TBox2< double > FBox2D
Definition MathFwd.h:56
@ Num
Definition MetalRHIPrivate.h:234
#define Split(a, ahi, alo)
Definition Predicates.inl:204
Definition Archive.h:1208
UE_FORCEINLINE_HINT bool IsLoading() const
Definition Archive.h:236
Definition Array.h:670
Definition GenericQuadTree.h:11
void Duplicate(TreeType &OutDuplicate) const
Definition GenericQuadTree.h:455
void Insert(const ElementType &Element, const FBox2D &Box, const TCHAR *DebugContext=nullptr)
Definition GenericQuadTree.h:249
void Serialize(FArchive &Ar)
Definition GenericQuadTree.h:138
TQuadTree()
Definition GenericQuadTree.h:174
TQuadTree & operator=(const TQuadTree &Other)
Definition GenericQuadTree.h:131
void Empty()
Definition GenericQuadTree.h:475
const FBox2D & GetTreeBox() const
Definition GenericQuadTree.h:21
~TQuadTree()
Definition GenericQuadTree.h:180
TQuadTree(const FBox2D &InBox, float InMinimumQuadSize=100.f)
Definition GenericQuadTree.h:164
void GetElements(const FBox2D &Box, TArray< ElementType, ElementAllocatorType > &ElementsOut) const
Definition GenericQuadTree.h:350
TQuadTree(const TQuadTree &)
Definition GenericQuadTree.h:125
bool Remove(const ElementType &Instance, const FBox2D &Box)
Definition GenericQuadTree.h:329
void GetElements(const FBox2D &Box, CallbackType &&Callback) const
Definition GenericQuadTree.h:383
@ Box
Definition EnvQueryTypes.h:244
@ Element
Definition Visu.h:18
@ false
Definition radaudio_common.h:23
static constexpr UE_FORCEINLINE_HINT T Square(const T A)
Definition UnrealMathUtility.h:578
static UE_FORCEINLINE_HINT TVector2< T > Min(const TVector2< T > &A, const TVector2< T > &B)
Definition Vector2D.h:959
T Y
Definition Vector2D.h:52
static UE_FORCEINLINE_HINT TVector2< T > Max(const TVector2< T > &A, const TVector2< T > &B)
Definition Vector2D.h:953
T X
Definition Vector2D.h:49