UDocumentation UE5.7 10.02.2026 (Source)
API documentation for Unreal Engine 5.7
SparseDynamicPointOctree3.h
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2
3#pragma once
4
5
6#include "CoreMinimal.h"
8#include "GeometryTypes.h"
9#include "BoxTypes.h"
10#include "Util/DynamicVector.h"
11#include "Util/RefCountVector.h"
12#include "Util/SparseListSet.h"
13#include "Spatial/SparseGrid3.h"
15#include "Async/ParallelFor.h"
16
17namespace UE
18{
19namespace Geometry
20{
21
22
27{
30
33
36
39
42
49
56
57 inline bool IsExistingCell() const
58 {
59 return CellID != InvalidID;
60 }
61
62 inline bool HasChild(int ChildIndex) const
63 {
64 return Children[ChildIndex] != InvalidID;
65 }
66
67 inline uint32 GetChildCellID(int ChildIndex) const
68 {
69 return Children[ChildIndex];
70 }
71
72 inline FSparsePointOctreeCell MakeChildCell(int ChildIndex) const
73 {
74 FVector3i IndexOffset(
75 ((ChildIndex & 1) != 0) ? 1 : 0,
76 ((ChildIndex & 2) != 0) ? 1 : 0,
77 ((ChildIndex & 4) != 0) ? 1 : 0);
78 return FSparsePointOctreeCell(Level + 1, 2*Index + IndexOffset);
79 }
80
81 inline void SetChild(uint32 ChildIndex, const FSparsePointOctreeCell& ChildCell)
82 {
83 Children[ChildIndex] = ChildCell.CellID;
84 }
85
86};
87
88
107{
108 // potential optimizations/improvements
109 // - Cell sizes are known at each level...can keep a lookup table? will this improve performance?
110 // - Store cells for each level in separate TDynamicVectors. CellID is then [Level:8 | Index:24].
111 // This would allow level-grids to be processed separately / in-parallel (for example a cut at given level would be much faster)
112 // - Currently insertion is max-depth but we do not dynamically expand more than once. So early
113 // insertions end up in very large buckets. When a child expands we should check if any of its parents would fit.
114 // - Currently insertion is max-depth so we end up with a huge number of single-Point cells. Should only go down a level
115 // if enough Points exist in current cell. Can do this in a greedy fashion, less optimal but still acceptable...
116
117public:
118
119 //
120 // Tree configuration parameters. It is not safe to change these after tree initialization!
121 //
122
126 double RootDimension = 1000.0;
127
131 int MaxTreeDepth = 10;
132
138
139public:
140
147
153 inline bool ContainsPoint(int32 PointID) const;
154
162 inline void InsertPoint(int32 PointID, const FVector3d& Position);
163
169 inline void InsertPoint_DynamicExpand(int32 PointID, TFunctionRef<FVector3d(int)> GetPositionFunc);
170
180 inline void ParallelInsertDensePointSet(int32 MaxPointID, TFunctionRef<FVector3d(int)> GetPositionFunc);
181
182
188 inline bool RemovePoint(int32 PointID);
189
194 inline void RemovePointUnsafe(int32 PointID);
195
201 inline void ReinsertPoint(int32 PointID, const FVector3d& NewPosition);
202
210 inline int32 FindNearestHitPoint(const FRay3d& Ray,
211 TFunctionRef<double(int, const FRay3d&)> HitPointDistFunc,
213
224 GEOMETRYCORE_API int32 FindClosestPoint(FVector3d QueryPt, double DistanceThreshold,
226 TArray<const FSparsePointOctreeCell*>* TempBuffer = nullptr) const;
227
239 void FindKClosestPoints(FVector3d QueryPt, double DistanceThreshold, int32 NumToFind,
241 TArray<const FSparsePointOctreeCell*>* TempBuffer = nullptr) const;
242
251 inline void RangeQuery(const FAxisAlignedBox3d& Bounds,
252 TFunctionRef<bool(int)> PredicateFunc,
254 TArray<const FSparsePointOctreeCell*>* TempBuffer = nullptr) const;
255
265 inline void ParallelRangeQuery(const FAxisAlignedBox3d& Bounds,
266 TFunctionRef<bool(int)> PredicateFunc,
268 TArray<const FSparsePointOctreeCell*>* TempBuffer = nullptr) const;
269
270
271
280 inline void CheckValidity(
284 bool bVerbose = false,
285 bool bFailOnMissingPoints = false) const;
286
299
303 inline void ComputeStatistics(FStatistics& StatsOut) const;
304
305protected:
306 // this identifier is used for unknown cells
308
309 // reference counts for Cells list. We don't actually need reference counts here, but we need a free
310 // list and iterators, and FRefCountVector provides this
312
313 // list of cells. Note that some cells may be unused, depending on CellRefCounts
315
316 TSparseListSet<int32> CellPointLists; // per-cell Point ID lists
317
318 TDynamicVector<uint32> PointIDToCellMap; // map from external Point IDs to which cell the Point is in (or InvalidCellID)
319
320 // RootCells are the top-level cells of the octree, of size RootDimension.
321 // So the elements of this sparse grid are CellIDs
323
324
325 // calculate the base width of a cell at a given level
326 inline double GetCellWidth(uint32 Level) const
327 {
328 double Divisor = (double)( (uint64)1 << (Level & 0x1F) );
329 double CellWidth = RootDimension / Divisor;
330 return CellWidth;
331 }
332
333
335 {
336 double CellWidth = GetCellWidth(Level);
337 double MinX = (CellWidth * (double)Index.X);
338 double MinY = (CellWidth * (double)Index.Y);
339 double MinZ = (CellWidth * (double)Index.Z);
340 return FAxisAlignedBox3d(
341 FVector3d(MinX, MinY, MinZ),
342 FVector3d(MinX + CellWidth, MinY + CellWidth, MinZ + CellWidth));
343 }
345 {
346 return GetCellBox(Cell.Level, Cell.Index);
347 }
348
349 inline FVector3d GetCellCenter(uint32 Level, const FVector3i& Index) const
350 {
351 double CellWidth = GetCellWidth(Level);
352 double MinX = CellWidth * (double)Index.X;
353 double MinY = CellWidth * (double)Index.Y;
354 double MinZ = CellWidth * (double)Index.Z;
355 CellWidth *= 0.5;
356 return FVector3d(MinX + CellWidth, MinY + CellWidth, MinZ + CellWidth);
357 }
359 {
360 return GetCellCenter(Cell.Level, Cell.Index);
361 }
362
363
364 inline FVector3i PointToIndex(uint32 Level, const FVector3d& Position) const
365 {
366 double CellWidth = GetCellWidth(Level);
367
368 // Converts to an int32 in such a way that values outside of the representable
369 // range ends up at the min and max values of the representable range.
370 auto SafeDoubleToInt32Floor = [](double Value)
371 {
372 return static_cast<int32>(FMath::Clamp(
374 static_cast<double>(TNumericLimits<int32>::Lowest()),
375 static_cast<double>(TNumericLimits<int32>::Max())));
376 };
377
381 return FVector3i(i, j, k);
382 }
383
384 int ToChildCellIndex(uint32 Level, const FVector3i& Index, const FVector3d& Position) const
385 {
387 int ChildIndex =
388 ((Position.X < Center.X) ? 0 : 1) +
389 ((Position.Y < Center.Y) ? 0 : 2) +
390 ((Position.Z < Center.Z) ? 0 : 4);
391 return ChildIndex;
392 }
394 {
395 return ToChildCellIndex(Cell.Level, Cell.Index, Position);
396 }
397
399 {
401 return CellBox.Contains(Position);
402 }
403
405 {
406 if (PointID >= 0 && size_t(PointID) < PointIDToCellMap.Num())
407 {
408 return PointIDToCellMap[PointID];
409 }
410 return InvalidCellID;
411 }
412
414
417 inline void Insert_ToCell(int32 PointID, const FVector3d& Position, const FSparsePointOctreeCell& ExistingCell);
419
420 inline double FindNearestRayCellIntersection(const FSparsePointOctreeCell& Cell, const FRay3d& Ray) const;
421
422private:
423 // Helper to iterate over the root cells within a radius. Note the RootFn can return a refined search radius squared, which will let the iteration visit fewer root cells.
424 // RootFn is passed the current squared search radius, and returns the (possibly smaller) squared search radius.
425 void IterateRootsInRadius(FVector3d QueryPt, double InitialRadius, TFunctionRef<double(double RadSq, const FSparsePointOctreeCell& Root)> RootFn) const;
426};
427
428
429
431{
432 // These are some rough values collected from some basic profiling. At some point it will
433 // probably be a good idea to do a more comprehensive study and perhaps come up with some
434 // regression formulas.
435
436 this->MaxTreeDepth = 2;
437 int RootCellDivisions = 6;
438 if (CountEstimate > 500000)
439 {
440 this->MaxTreeDepth = 3;
442 }
443 if (CountEstimate > 5000000)
444 {
445 this->MaxTreeDepth = 4;
447 }
448 if (CountEstimate > 50000000)
449 {
450 this->MaxTreeDepth = 5;
452 }
453 this->RootDimension = MaxBoundsDimension / (double)RootCellDivisions;
454
455 // maybe not necessarily a good estimate?
456 this->MaxPointsPerCell = 250;
457}
458
459
461{
462 return (PointID < PointIDToCellMap.Num() && PointIDToCellMap[PointID] != InvalidCellID);
463}
464
465
467{
468 if (ContainsPoint(PointID))
469 {
470 checkSlow(false);
471 return;
472 }
473
476
477 // if we found a containing root cell but it doesn't exist, create it and insert
478 if (CurrentCell.Level == 0 && CurrentCell.IsExistingCell() == false)
479 {
481 return;
482 }
483
485 // if current cell does not have this child we might fit there so try it
486 if (CurrentCell.HasChild(PotentialChildIdx) == false)
487 {
488 // todo can we do a fast check based on level and dimensions??
491 {
493 return;
494 }
495 }
496
497 // insert into current cell if
498 // 1) child cell exists (in which case we didn't fit or FindCurrentContainingCell would have returned)
499 // 2) we tried to fit in child cell and failed
501}
502
503
523
524
525
537
538
539
547
564
565
566
568{
569 // TODO: this current implementation could be adapted to handle non-dense point sets
570
571 // ParallelInsertDensePointSet can currently only be called if the tree is empty
572 if (ensure(Cells.IsEmpty()) == false)
573 {
574 return;
575 }
576
577 // information for an input point
578 struct FPointInfo
579 {
580 int32 PointID; // ID of this point
581 FVector3i RootCell; // index of Root cell this point is in
582 uint32 LeafCellIndex; // index of Leaf Cell this point should go into. This is an index into the RootCell-specific LevelCells array used inside the ParallelFor below.
583 };
584
585 // populate initial list of points
588 ParallelFor(MaxPointID, [&](int32 k)
589 {
590 PointInfos[k] = FPointInfo{ k, PointToIndex(0, GetPositionFunc(k)), InvalidCellID };
591 });
592
593 // build a linear list of root cells, and sort all points into a list for each root cell
597 {
598 //TRACE_CPUPROFILER_EVENT_SCOPE(InitializeRootCellLists);
599 int LinearIndexCounter = 0;
600 for (int32 k = 0; k < MaxPointID; ++k)
601 {
602 int UseLinearIndex = 0;
604 if (FoundLinearIndex == nullptr)
605 {
610 }
611 else
612 {
614 }
615
616 // try doing these Adds in a second per-root-cell ParallelFor? (parallel-scan seems wasteful but might still be faster...)
618 }
619 }
620
621 // create all the Root Cells
623 {
624 //TRACE_CPUPROFILER_EVENT_SCOPE(CreateRootCells);
625 // Note RootCellPtrs holds pointers to elements of Cells; this should be safe
626 // thanks to the block storage of the TDynamicVector used for Cells
628 for (int32 Index = 0; Index < RootCellIndexes.Num(); ++Index)
629 {
630 FVector3i RootIndex = RootCellIndexes[Index];
634 }
635 RootCellIndexes.Empty(); // discard this index array; we will only use the pointer array below
636 }
637
638 // make sure the PointIDToCellMap is large enough for all the PointIDs, then we never have to insert/resize
639 PointIDToCellMap.SetNum(MaxPointID);
640
641 // FParentChildCell represents a child-cell-of-a-parent that needs to be created. We will make lists of these below.
642 // This strategy is maybe over-complicated, and possibly we can just create all the leaf cells and then figure out
643 // the necessary parents that need to exist above it?
644 struct FParentChildCell
645 {
646 FVector3i ParentCellIndex; // XYZ index of parent cell
647 FVector3i ChildCellIndex; // XYZ index of child cell that needs to exist (in it's layer)
648 int ChildNum; // index of child cell in parent cell's 8-children list
649 int NumPoints; // number of points that will be inserted in this cell (only set for leaf cells)
650 uint32 NewCellID; // ID of cell after it is created. This is set in the code below
651 bool operator==(const FParentChildCell& OtherCell) const { return ChildCellIndex == OtherCell.ChildCellIndex; }
652 };
653
654 // Parallel iteration over the Root cells. For each Root cell, we iterate over its points, and for each point,
655 // descend down to the max depth to figure out which cells need to be created. Then we create those cells, and
656 // then we insert the points into the leaf cells.
659 {
660 FSparsePointOctreeCell& RootCell = *RootCellPtrs[Index];
661
662 // array of new child cells at each level
663 TArray<TArray<FParentChildCell>> LevelCells;
664 LevelCells.SetNum(MaxTreeDepth);
665
666 // descend the non-existent octree cells mathematically, to determine which cells need to be instantiated
667 TDynamicVector<int>& PointIndices = *RootCellPointIndices[Index];
668 for ( int32 PointIndex : PointIndices)
669 {
670 int32 PointID = PointInfos[PointIndex].PointID;
671 FVector3d Position = GetPositionFunc(PointID);
672
673 int32 CurLevel = 0;
674 int32 LastInsertIndex = 0;
675 FSparsePointOctreeCell CurrentCell = RootCell;
676 while (CurLevel < MaxTreeDepth)
677 {
678 int ChildIndex = ToChildCellIndex(CurrentCell, Position);
679 FSparsePointOctreeCell NextLevelCell = CurrentCell.MakeChildCell(ChildIndex);
680 LastInsertIndex = LevelCells[CurLevel].AddUnique( FParentChildCell{ CurrentCell.Index, NextLevelCell.Index, ChildIndex, 0, InvalidCellID } );
681 CurLevel++;
682 CurrentCell = NextLevelCell;
683 }
684 // keep track of which leaf cell this point needs to be inserted into, since we computed it in the last iteration of the above loop
685 PointInfos[PointIndex].LeafCellIndex = LastInsertIndex;
686 LevelCells[CurLevel-1][LastInsertIndex].NumPoints++;
687 }
688
689 // sorted CellIDs of all the Leaf Cells we create in the loop below
691
692 // instantiate all the cells at each level. This is a bit messy because at level/depth N+1 we
693 // do not know our parent Cells/CellIDs, only their indices, and that our parent must be one
694 // of the cells that were created at level N. So that takes a linear search. Unclear how to
695 // track that mapping, except maybe a TMap? (note that in profiling this is not remotely a bottleneck...)
698 for (int32 Level = 0; Level < MaxTreeDepth; ++Level)
699 {
701 {
702 FSparsePointOctreeCell** ParentCell = LastParents.FindByPredicate( [&](FSparsePointOctreeCell* Cell) { return Cell->Index == ParentChild.ParentCellIndex; } );
703 checkSlow(ParentCell != nullptr);
704 checkSlow( (*ParentCell)->HasChild(ParentChild.ChildNum) == false );
706 checkSlow( NewChildCell.Index == ParentChild.ChildCellIndex );
707
708 // only this block accesses data structures shared between root cells
709 SharedDataLock.Lock();
711 Cells.InsertAt(NewChildCell, NewChildCell.CellID);
712 if ( Level == MaxTreeDepth-1 )
713 {
714 LeafCellIDs.Add(NewChildCell.CellID);
715 }
717 SharedDataLock.Unlock();
718
719 (*ParentCell)->SetChild(ParentChild.ChildNum, NewChildCell);
720 ParentChild.NewCellID = NewChildCell.CellID;
721
723 }
725 NextParents.Reset();
726 }
727
729 int NumLeafCells = LeafCells.Num();
732
733 // initialize the cell-point-lists for each new leaf cell. This has to be thread-safe, but
734 // it will return handles to the lists that can be used in parallel below
735 // (remember we are in a big ParallelFor over root cells here, that are accessing CellPointLists simultaneously)
736 CellPointListsLock.Lock();
737 for (int32 k = 0; k < NumLeafCells; ++k)
738 {
740 }
741 CellPointListsLock.Unlock();
742
743 // It is faster to batch-insert points into the leaf cell lists, even though we have
744 // to build up new arrays for it. So first we collect per-leaf-cell lists,
745 // then we send those lists to the leaf cells
746 TArray<TArray<int32>> LeafCellPointLists; // todo could convert to a single array with offsets?
748 for (int32 k = 0; k < NumLeafCells; ++k)
749 {
750 LeafCellPointLists.Reserve( LeafCells[k].NumPoints );
751 }
753 {
754 int PointID = PointInfos[PointIndex].PointID;
755 int LeafIndex = PointInfos[PointIndex].LeafCellIndex;
756 LeafCellPointLists[LeafIndex].Add(PointID);
757 PointIDToCellMap[PointID] = LeafCells[LeafIndex].NewCellID;
758 }
759 for ( int32 k = 0; k < NumLeafCells; ++k)
760 {
761 // TODO: doing this step in parallel did not help, but perhaps the entire above block could be done in parallel (with repeat iterations over PointIndices)
763 }
764
766
767}
768
769
770void FSparseDynamicPointOctree3::InsertPoint_DynamicExpand(
771 int32 PointID,
772 TFunctionRef<FVector3d(int)> GetPositionFunc)
773{
774 if (ContainsPoint(PointID))
775 {
776 checkSlow(false);
777 return;
778 }
779
780 FVector3d Position = GetPositionFunc(PointID);
781
782 FSparsePointOctreeCell CurrentCell = FindCurrentContainingCell(Position);
783 checkSlow(CurrentCell.Level != FSparsePointOctreeCell::InvalidLevel);
784
785 // if we found a containing root cell but it doesn't exist, create it and insert
786 if (CurrentCell.Level == 0 && CurrentCell.IsExistingCell() == false)
787 {
788 Insert_NewRoot(PointID, Position, CurrentCell);
789 return;
790 }
791
792 // insert into current cell if there is space, or if we hit max depth
793 int NumPointsInCell = CellPointLists.GetCount(CurrentCell.CellID);
794 if (NumPointsInCell + 1 < MaxPointsPerCell || CurrentCell.Level == MaxTreeDepth)
795 {
796 Insert_ToCell(PointID, Position, CurrentCell);
797 return;
798 }
799
800 const FSparsePointOctreeCell* ParentCell = &Cells[CurrentCell.CellID];
801
802 // this function inserts a point into one of the child cells of CurrentCell
803 auto InsertToChildLocalFunc = [this,&GetPositionFunc,ParentCell](int InsertPointID)
804 {
805 FVector3d MovePosition = GetPositionFunc(InsertPointID);
806 int ChildIdx = this->ToChildCellIndex(*ParentCell, MovePosition);
807 if (ParentCell->HasChild(ChildIdx))
808 {
809 uint32 ChildCellID = ParentCell->GetChildCellID(ChildIdx);
811 this->Insert_ToCell(InsertPointID, MovePosition, *ChildCell);
812 }
813 else
814 {
816 this->Insert_NewChildCell(InsertPointID, MovePosition, ParentCell->CellID, NewChildCell, ChildIdx);
817 }
818 };
819
820 // otherwise current cell got too big, so move points to child cells
821 CellPointLists.Enumerate(ParentCell->CellID, [&](int MovePointID)
822 {
823 InsertToChildLocalFunc(MovePointID);
824 });
825
826 // we removed all the points from ParentCell
827 CellPointLists.Clear(ParentCell->CellID);
828
829 // now insert ourself into one of these child cells
830 InsertToChildLocalFunc(PointID);
831}
832
833
834
835
836
837bool FSparseDynamicPointOctree3::RemovePoint(int32 PointID)
838{
839 if (ContainsPoint(PointID) == false)
840 {
841 return false;
842 }
843
844 uint32 CellID = GetCellForPoint(PointID);
845 if (CellID == InvalidCellID)
846 {
847 return false;
848 }
849
850 PointIDToCellMap[PointID] = InvalidCellID;
851
852 bool bInList = CellPointLists.Remove(CellID, PointID);
854 return true;
855}
856
857
858void FSparseDynamicPointOctree3::RemovePointUnsafe(int32 PointID)
859{
860 uint32 CellID = PointIDToCellMap[PointID];
861 if (CellID != InvalidCellID)
862 {
863 PointIDToCellMap[PointID] = InvalidCellID;
864 CellPointLists.Remove(CellID, PointID);
865 }
866}
867
868
869
870
871void FSparseDynamicPointOctree3::ReinsertPoint(int32 PointID, const FVector3d& NewPosition)
872{
873 if (ContainsPoint(PointID))
874 {
875 uint32 CellID = GetCellForPoint(PointID);
876 if (CellID != InvalidCellID)
877 {
878 FSparsePointOctreeCell& CurrentCell = Cells[CellID];
879 if (CellContains(CurrentCell, NewPosition))
880 {
881 return; // everything is fine
882 }
883
884 }
885 }
886
887 RemovePoint(PointID);
888 InsertPoint(PointID, NewPosition);
889}
890
891
892
893
894
895
896double FSparseDynamicPointOctree3::FindNearestRayCellIntersection(const FSparsePointOctreeCell& Cell, const FRay3d& Ray) const
897{
898 FAxisAlignedBox3d Box = GetCellBox(Cell);
900 if (FIntrRay3AxisAlignedBox3d::FindIntersection(Ray, Box, ray_t))
901 {
902 return ray_t;
903 }
904 else
905 {
907 }
908}
909
910
911
912
913int32 FSparseDynamicPointOctree3::FindNearestHitPoint(const FRay3d& Ray,
914 TFunctionRef<double(int, const FRay3d&)> HitPointDistFunc,
915 double MaxDistance) const
916{
917 // this should take advantage of raster!
918
919 // we use queue instead of recursion
921 Queue.Reserve(64);
922
923 // push all root cells onto queue if they are hit by ray
924 RootCells.AllocatedIteration([&](const uint32* RootCellID)
925 {
927 double RayHitParam = FindNearestRayCellIntersection(*RootCell, Ray);
929 {
930 Queue.Add(&Cells[*RootCellID]);
931 }
932 });
933
934 // test cells until the queue is empty
935 int32 HitPointID = -1;
936 while (Queue.Num() > 0)
937 {
939
940 // process elements
941 CellPointLists.Enumerate(CurCell->CellID, [&](int32 PointID)
942 {
943 double HitDist = HitPointDistFunc(PointID, Ray);
944 if (HitDist < MaxDistance)
945 {
947 HitPointID = PointID;
948 }
949 });
950
951 // descend to child cells
952 // sort by distance? use DDA?
953 for (int k = 0; k < 8; ++k)
954 {
955 if (CurCell->HasChild(k))
956 {
957 const FSparsePointOctreeCell* ChildCell = &Cells[CurCell->GetChildCellID(k)];
958 double RayHitParam = FindNearestRayCellIntersection(*ChildCell, Ray);
960 {
961 Queue.Add(ChildCell);
962 }
963 }
964 }
965 }
966
967 return HitPointID;
968}
969
970
971
972
973
974
975
976void FSparseDynamicPointOctree3::RangeQuery(
977 const FAxisAlignedBox3d& Bounds,
978 TFunctionRef<bool(int)> PredicateFunc,
981{
984 (TempBuffer == nullptr) ? InternalBuffer : *TempBuffer;
985 Queue.Reset();
986 Queue.Reserve(128);
987
988 FVector3i RootMinIndex = PointToIndex(0, Bounds.Min);
989 FVector3i RootMaxIndex = PointToIndex(0, Bounds.Max);
991 {
993 Queue.Add(RootCell);
994 });
995
996 while (Queue.Num() > 0)
997 {
999
1000 // process elements
1001 if (CellPointLists.IsAllocated(CurCell->CellID))
1002 {
1003 CellPointLists.Enumerate(CurCell->CellID, [&](int32 PointID)
1004 {
1005 if (PredicateFunc(PointID))
1006 {
1007 PointIDs.Add(PointID);
1008 }
1009 });
1010 }
1011
1012 for (int k = 0; k < 8; ++k)
1013 {
1014 if (CurCell->HasChild(k))
1015 {
1016 const FSparsePointOctreeCell* ChildCell = &Cells[CurCell->GetChildCellID(k)];
1017 if (GetCellBox(*ChildCell).Intersects(Bounds))
1018 {
1019 Queue.Add(ChildCell);
1020 }
1021 }
1022 }
1023 }
1024}
1025
1026
1027
1028
1029
1030
1031void FSparseDynamicPointOctree3::ParallelRangeQuery(
1032 const FAxisAlignedBox3d& Bounds,
1033 TFunctionRef<bool(int)> PredicateFunc,
1035 TArray<const FSparsePointOctreeCell*>* TempBuffer) const
1036{
1039 (TempBuffer == nullptr) ? InternalBuffer : *TempBuffer;
1041 ProcessRootCells.Reserve(128);
1042
1043 FCriticalSection OutputLock;
1044
1045 FVector3i RootMinIndex = PointToIndex(0, Bounds.Min);
1046 FVector3i RootMaxIndex = PointToIndex(0, Bounds.Max);
1048 {
1051 });
1052
1053 ParallelFor(ProcessRootCells.Num(), [&](int idx)
1054 {
1055 const FSparsePointOctreeCell* RootCell = ProcessRootCells[idx];
1056 TArray<const FSparsePointOctreeCell*, TInlineAllocator<128>> Queue;
1057 Queue.Add(RootCell);
1058
1059 TArray<int, TInlineAllocator<256>> LocalPointIDs;
1060
1061 while (Queue.Num() > 0)
1062 {
1063 const FSparsePointOctreeCell* CurCell = Queue.Pop(EAllowShrinking::No);
1064
1065 // process elements
1066 CellPointLists.Enumerate(CurCell->CellID, [&](int PointID)
1067 {
1068 if (PredicateFunc(PointID))
1069 {
1070 LocalPointIDs.Add(PointID);
1071 }
1072 });
1073
1074 for (int k = 0; k < 8; ++k)
1075 {
1076 if (CurCell->HasChild(k))
1077 {
1078 const FSparsePointOctreeCell* ChildCell = &Cells[CurCell->GetChildCellID(k)];
1079 if (GetCellBox(*ChildCell).Intersects(Bounds))
1080 {
1081 Queue.Add(ChildCell);
1082 }
1083 }
1084 }
1085 }
1086
1087 if (LocalPointIDs.Num() > 0)
1088 {
1089 OutputLock.Lock();
1090 for (int ObjID : LocalPointIDs )
1091 {
1092 PointIDs.Add(ObjID);
1093 }
1094 OutputLock.Unlock();
1095 }
1096 });
1097
1098
1099
1100}
1101
1102
1103
1104
1105
1106
1107FSparsePointOctreeCell FSparseDynamicPointOctree3::FindCurrentContainingCell(const FVector3d& Position) const
1108{
1109 // look up root cell, which may not exist
1110 FVector3i RootIndex = PointToIndex(0, Position);
1111 const uint32* RootCellID = RootCells.Get(RootIndex);
1112 if (RootCellID == nullptr)
1113 {
1114 return FSparsePointOctreeCell(0, RootIndex);
1115 }
1116 checkSlow(CellRefCounts.IsValid(*RootCellID));
1117
1118 // check if point is contained in root cell
1119 // (should we do this before checking for existence? we can...)
1120 const FSparsePointOctreeCell* RootCell = &Cells[*RootCellID];
1121 if (CellContains(*RootCell, Position) == false)
1122 {
1123 return FSparsePointOctreeCell(FSparsePointOctreeCell::InvalidLevel, FVector3i::Zero());
1124 }
1125
1127 do
1128 {
1129 int ChildIdx = ToChildCellIndex(*CurrentCell, Position);
1130 if (CurrentCell->HasChild(ChildIdx))
1131 {
1132 int32 ChildCellID = CurrentCell->GetChildCellID(ChildIdx);
1133 checkSlow(CellRefCounts.IsValid(ChildCellID));
1135 if (CellContains(*ChildCell, Position))
1136 {
1138 continue;
1139 }
1140 }
1141
1142 return *CurrentCell;
1143
1144 } while (true); // loop will always terminate
1145
1146 return FSparsePointOctreeCell(FSparsePointOctreeCell::InvalidLevel, FVector3i::Zero());
1147}
1148
1149
1150
1151void FSparseDynamicPointOctree3::CheckValidity(
1155 bool bVerbose,
1156 bool bFailOnMissingPoints) const
1157{
1158 bool is_ok = true;
1159 TFunction<void(bool)> CheckOrFailF = [&](bool b)
1160 {
1161 is_ok = is_ok && b;
1162 };
1163 if (FailMode == EValidityCheckFailMode::Check)
1164 {
1165 CheckOrFailF = [&](bool b)
1166 {
1167 checkf(b, TEXT("FSparseDynamicPointOctree3::CheckValidity failed!"));
1168 is_ok = is_ok && b;
1169 };
1170 }
1171 else if (FailMode == EValidityCheckFailMode::Ensure)
1172 {
1173 CheckOrFailF = [&](bool b)
1174 {
1175 ensureMsgf(b, TEXT("FSparseDynamicPointOctree3::CheckValidity failed!"));
1176 is_ok = is_ok && b;
1177 };
1178 }
1179
1181 CellsAtLevels.Init(0, 32);
1182 PointsAtLevel.Init(0, 32);
1184 uint8 MaxLevel = 0;
1185
1186 // check that all Point IDs in per-cell Point lists is valid
1187 for (int32 CellID : CellRefCounts.Indices())
1188 {
1189 CellPointLists.Enumerate(CellID, [&](int32 PointID)
1190 {
1192 });
1193 }
1194
1195 uint32 NumPointIDs = PointIDToCellMap.Num();
1196 for (uint32 PointID = 0; PointID < NumPointIDs; PointID++)
1197 {
1198 bool IsValidPointID = IsValidPointIDFunc(PointID);
1199 if (IsValidPointID)
1200 {
1201 FVector3d Position = GetPointFunc(PointID);
1202
1203 CheckOrFailF(PointID < PointIDToCellMap.Num());
1204 uint32 CellID = PointIDToCellMap[PointID];
1205
1207 {
1208 CheckOrFailF(CellID != InvalidCellID);
1209 }
1210
1211 if (CellID == InvalidCellID)
1212 {
1214 }
1215 else
1216 {
1217 CheckOrFailF(CellRefCounts.IsValid(CellID));
1218 FSparsePointOctreeCell Cell = Cells[CellID];
1219 FAxisAlignedBox3d CellBounds = GetCellBox(Cell);
1220 CheckOrFailF(CellBounds.Contains(Position));
1221 CheckOrFailF(CellPointLists.Contains(CellID, PointID));
1222
1223 PointsAtLevel[Cell.Level]++;
1224 }
1225 }
1226 }
1227
1228
1229 for (int32 CellID : CellRefCounts.Indices())
1230 {
1231 const FSparsePointOctreeCell& Cell = Cells[CellID];
1232 CellsAtLevels[Cell.Level]++;
1233 MaxLevel = FMath::Max(MaxLevel, Cell.Level);
1234 }
1235
1236 if (bVerbose)
1237 {
1238 UE_LOG(LogGeometry, Warning, TEXT("FSparseDynamicPointOctree3::CheckValidity: MaxLevel %d MissingCount %d"), MaxLevel, MissingPointCount);
1239 for (uint32 k = 0; k <= MaxLevel; ++k)
1240 {
1241 UE_LOG(LogGeometry, Warning, TEXT(" Level %4d Cells %4d Points %4d"), k, CellsAtLevels[k], PointsAtLevel[k]);
1242 }
1243 }
1244}
1245
1246
1247
1248
1249void FSparseDynamicPointOctree3::ComputeStatistics(FStatistics& StatsOut) const
1250{
1251 StatsOut.Levels = 0;
1252 for (int32 CellID : CellRefCounts.Indices())
1253 {
1254 const FSparsePointOctreeCell& Cell = Cells[CellID];
1255 StatsOut.Levels = FMath::Max(StatsOut.Levels, (int32)Cell.Level);
1256 }
1257 StatsOut.Levels++;
1258 StatsOut.LevelObjCounts.Init(0, StatsOut.Levels);
1259 StatsOut.LevelMaxObjCounts.Init(0, StatsOut.Levels);
1260 StatsOut.LevelBoxCounts.Init(0, StatsOut.Levels);
1261 StatsOut.LevelCellSizes.Init(0, StatsOut.Levels);
1262 for (int32 CellID : CellRefCounts.Indices())
1263 {
1264 const FSparsePointOctreeCell& Cell = Cells[CellID];
1265 StatsOut.LevelBoxCounts[Cell.Level]++;
1266 int ObjCount = CellPointLists.IsAllocated(CellID) ? CellPointLists.GetCount(CellID) : 0;
1267 StatsOut.LevelObjCounts[Cell.Level] += ObjCount;
1268 StatsOut.LevelMaxObjCounts[Cell.Level] = FMath::Max(StatsOut.LevelMaxObjCounts[Cell.Level], ObjCount);
1269 StatsOut.LevelCellSizes[Cell.Level] = float(GetCellWidth(Cell.Level));
1270 }
1271}
1272
1273
1274FString FSparseDynamicPointOctree3::FStatistics::ToString() const
1275{
1276 FString Result = FString::Printf(
1277 TEXT("Levels %2d\r\n"), Levels);
1278 for (int k = 0; k < Levels; ++k)
1279 {
1280 Result += FString::Printf(TEXT(" Level %2d: Cells %8d Objs %8d AvgObjs %5.3f MaxObjs %8d Dimension %5.5f\r\n"),
1281 k, LevelBoxCounts[k], LevelObjCounts[k], ((float)LevelObjCounts[k] / (float)LevelBoxCounts[k]), LevelMaxObjCounts[k], LevelCellSizes[k] );
1282 }
1283 return Result;
1284}
1285
1286} // end namespace UE::Geometry
1287} // end namespace UE
OODEFFUNC typedef void(OODLE_CALLBACK t_fp_OodleCore_Plugin_Free)(void *ptr)
#define checkSlow(expr)
Definition AssertionMacros.h:332
#define ensureMsgf( InExpression, InFormat,...)
Definition AssertionMacros.h:465
#define ensure( InExpression)
Definition AssertionMacros.h:464
#define checkf(expr, format,...)
Definition AssertionMacros.h:315
void ParallelFor(int32 Num, TFunctionRef< void(int32)> Body, bool bForceSingleThread, bool bPumpRenderingThread=false)
Definition ParallelFor.h:481
#define TEXT(x)
Definition Platform.h:1272
FPlatformTypes::int32 int32
A 32-bit signed integer.
Definition Platform.h:1125
FPlatformTypes::uint64 uint64
A 64-bit unsigned integer.
Definition Platform.h:1117
UE_FORCEINLINE_HINT TSharedRef< CastToType, Mode > StaticCastSharedRef(TSharedRef< CastFromType, Mode > const &InSharedRef)
Definition SharedPointer.h:127
UE::FPlatformRecursiveMutex FCriticalSection
Definition CriticalSection.h:53
#define UE_LOG(CategoryName, Verbosity, Format,...)
Definition LogMacros.h:270
UE::Math::TVector< double > FVector3d
Definition MathFwd.h:60
USkinnedMeshComponent float
Definition SkinnedMeshComponent.h:60
UE_FORCEINLINE_HINT TUniquePtr< T > MakeUnique(TArgs &&... Args)
Definition UniquePtr.h:918
if(Failed) console_printf("Failed.\n")
uint8_t uint8
Definition binka_ue_file_header.h:8
uint32_t uint32
Definition binka_ue_file_header.h:6
Definition Array.h:670
UE_REWRITE SizeType Num() const
Definition Array.h:1144
void Reset(SizeType NewSize=0)
Definition Array.h:2246
void SetNum(SizeType NewNum, EAllowShrinking AllowShrinking=UE::Core::Private::AllowShrinkingByDefault< AllocatorType >())
Definition Array.h:2308
UE_NODEBUG UE_FORCEINLINE_HINT SizeType Add(ElementType &&Item)
Definition Array.h:2696
void Init(const ElementType &Element, SizeType Number)
Definition Array.h:3043
UE_NODEBUG UE_FORCEINLINE_HINT bool Find(const ElementType &Item, SizeType &Index) const
Definition Array.h:1302
ElementType Pop(EAllowShrinking AllowShrinking=UE::Core::Private::AllowShrinkingByDefault< AllocatorType >())
Definition Array.h:1196
void SetNumUninitialized(SizeType NewNum, EAllowShrinking AllowShrinking=UE::Core::Private::AllowShrinkingByDefault< AllocatorType >())
Definition Array.h:2369
UE_FORCEINLINE_HINT void Reserve(SizeType Number)
Definition Array.h:3016
Definition AssetRegistryState.h:50
Definition AndroidPlatformMisc.h:14
Definition UnrealString.h.inl:34
static RealType Floor(const RealType Value)
Definition MathUtil.h:384
Definition StaticArray.h:26
Definition RefCountVector.h:25
bool IsValid(int Index) const
Definition RefCountVector.h:66
int Allocate()
Definition RefCountVector.h:102
IndexEnumerable Indices() const
Definition RefCountVector.h:458
Definition SparseDynamicPointOctree3.h:107
FSparsePointOctreeCell FindCurrentContainingCell(const FVector3d &Position) const
Definition SparseDynamicPointOctree3.h:1107
FRefCountVector CellRefCounts
Definition SparseDynamicPointOctree3.h:311
int MaxPointsPerCell
Definition SparseDynamicPointOctree3.h:137
void RemovePointUnsafe(int32 PointID)
Definition SparseDynamicPointOctree3.h:858
FAxisAlignedBox3d GetCellBox(uint32 Level, const FVector3i &Index) const
Definition SparseDynamicPointOctree3.h:334
TDynamicVector< uint32 > PointIDToCellMap
Definition SparseDynamicPointOctree3.h:318
FVector3d GetCellCenter(const FSparsePointOctreeCell &Cell) const
Definition SparseDynamicPointOctree3.h:358
int ToChildCellIndex(const FSparsePointOctreeCell &Cell, const FVector3d &Position) const
Definition SparseDynamicPointOctree3.h:393
uint32 GetCellForPoint(int32 PointID) const
Definition SparseDynamicPointOctree3.h:404
double GetCellWidth(uint32 Level) const
Definition SparseDynamicPointOctree3.h:326
TDynamicVector< FSparsePointOctreeCell > Cells
Definition SparseDynamicPointOctree3.h:314
void ParallelInsertDensePointSet(int32 MaxPointID, TFunctionRef< FVector3d(int)> GetPositionFunc)
Definition SparseDynamicPointOctree3.h:567
void ConfigureFromPointCountEstimate(double MaxBoundsDimension, int CountEstimate)
Definition SparseDynamicPointOctree3.h:430
GEOMETRYCORE_API int32 FindClosestPoint(FVector3d QueryPt, double DistanceThreshold, TFunctionRef< bool(int32)> PredicateFunc, TFunctionRef< double(int32)> DistSqFunc, TArray< const FSparsePointOctreeCell * > *TempBuffer=nullptr) const
Definition SparseDynamicPointOctree3.cpp:154
TSparseListSet< int32 > CellPointLists
Definition SparseDynamicPointOctree3.h:316
int ToChildCellIndex(uint32 Level, const FVector3i &Index, const FVector3d &Position) const
Definition SparseDynamicPointOctree3.h:384
FVector3d GetCellCenter(uint32 Level, const FVector3i &Index) const
Definition SparseDynamicPointOctree3.h:349
void ComputeStatistics(FStatistics &StatsOut) const
Definition SparseDynamicPointOctree3.h:1249
void InsertPoint(int32 PointID, const FVector3d &Position)
Definition SparseDynamicPointOctree3.h:466
void CheckValidity(TFunctionRef< bool(int)> IsValidPointIDFunc, TFunctionRef< FVector3d(int)> GetPointFunc, EValidityCheckFailMode FailMode=EValidityCheckFailMode::Check, bool bVerbose=false, bool bFailOnMissingPoints=false) const
Definition SparseDynamicPointOctree3.h:1151
static constexpr uint32 InvalidCellID
Definition SparseDynamicPointOctree3.h:307
void FindKClosestPoints(FVector3d QueryPt, double DistanceThreshold, int32 NumToFind, TArray< TPair< int32, double > > &FoundPoints, TFunctionRef< bool(int32)> PredicateFunc, TFunctionRef< double(int32)> DistSqFunc, TArray< const FSparsePointOctreeCell * > *TempBuffer=nullptr) const
Definition SparseDynamicPointOctree3.cpp:66
double FindNearestRayCellIntersection(const FSparsePointOctreeCell &Cell, const FRay3d &Ray) const
Definition SparseDynamicPointOctree3.h:896
bool ContainsPoint(int32 PointID) const
Definition SparseDynamicPointOctree3.h:460
FAxisAlignedBox3d GetCellBox(const FSparsePointOctreeCell &Cell) const
Definition SparseDynamicPointOctree3.h:344
void RangeQuery(const FAxisAlignedBox3d &Bounds, TFunctionRef< bool(int)> PredicateFunc, TArray< int > &PointIDsOut, TArray< const FSparsePointOctreeCell * > *TempBuffer=nullptr) const
Definition SparseDynamicPointOctree3.h:976
void ReinsertPoint(int32 PointID, const FVector3d &NewPosition)
Definition SparseDynamicPointOctree3.h:871
double RootDimension
Definition SparseDynamicPointOctree3.h:126
uint32 CreateNewRootCell(FSparsePointOctreeCell NewRootCell, bool bInitializeCellPointList)
Definition SparseDynamicPointOctree3.h:548
void Insert_ToCell(int32 PointID, const FVector3d &Position, const FSparsePointOctreeCell &ExistingCell)
Definition SparseDynamicPointOctree3.h:526
int MaxTreeDepth
Definition SparseDynamicPointOctree3.h:131
void ParallelRangeQuery(const FAxisAlignedBox3d &Bounds, TFunctionRef< bool(int)> PredicateFunc, TArray< int > &PointIDsOut, TArray< const FSparsePointOctreeCell * > *TempBuffer=nullptr) const
Definition SparseDynamicPointOctree3.h:1031
bool RemovePoint(int32 PointID)
Definition SparseDynamicPointOctree3.h:837
int32 FindNearestHitPoint(const FRay3d &Ray, TFunctionRef< double(int, const FRay3d &)> HitPointDistFunc, double MaxDistance=TNumericLimits< double >::Max()) const
Definition SparseDynamicPointOctree3.h:913
FVector3i PointToIndex(uint32 Level, const FVector3d &Position) const
Definition SparseDynamicPointOctree3.h:364
void InsertPoint_DynamicExpand(int32 PointID, TFunctionRef< FVector3d(int)> GetPositionFunc)
Definition SparseDynamicPointOctree3.h:770
void Insert_NewRoot(int32 PointID, const FVector3d &Position, FSparsePointOctreeCell NewRootCell)
Definition SparseDynamicPointOctree3.h:540
bool CellContains(const FSparsePointOctreeCell &Cell, const FVector3d &Position) const
Definition SparseDynamicPointOctree3.h:398
void Insert_NewChildCell(int32 PointID, const FVector3d &Position, int ParentCellID, FSparsePointOctreeCell NewChildCell, int ChildIdx)
Definition SparseDynamicPointOctree3.h:504
TSparseGrid3< uint32 > RootCells
Definition SparseDynamicPointOctree3.h:322
Definition DynamicVector.h:27
void InsertAt(const Type &Data, unsigned int Index)
Definition DynamicVector.h:747
size_t Num() const
Definition DynamicVector.h:147
void SetNum(unsigned int Count)
Definition DynamicVector.h:143
Definition SparseGrid3.h:27
void AllocatedIteration(Func ElementFunc) const
Definition SparseGrid3.h:176
const ElemType * Get(const FVector3i &Index) const
Definition SparseGrid3.h:85
void RangeIteration(FVector3i MinIndex, FVector3i MaxIndex, Func ElementFunc) const
Definition SparseGrid3.h:190
bool Has(const FVector3i &Index) const
Definition SparseGrid3.h:74
TAxisAlignedBox3< double > FAxisAlignedBox3d
Definition BoxTypes.h:885
EValidityCheckFailMode
Definition GeometryTypes.h:72
Definition AdvancedWidgetsModule.cpp:13
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 NumericLimits.h:41
Definition Tuple.h:652
Definition SparseDynamicPointOctree3.h:291
FString ToString() const
Definition SparseDynamicPointOctree3.h:1274
int32 Levels
Definition SparseDynamicPointOctree3.h:292
TArray< int32 > LevelMaxObjCounts
Definition SparseDynamicPointOctree3.h:295
TArray< int32 > LevelObjCounts
Definition SparseDynamicPointOctree3.h:294
TArray< float > LevelCellSizes
Definition SparseDynamicPointOctree3.h:296
TArray< int32 > LevelBoxCounts
Definition SparseDynamicPointOctree3.h:293
Definition SparseDynamicPointOctree3.h:27
bool HasChild(int ChildIndex) const
Definition SparseDynamicPointOctree3.h:62
uint8 Level
Definition SparseDynamicPointOctree3.h:35
FSparsePointOctreeCell MakeChildCell(int ChildIndex) const
Definition SparseDynamicPointOctree3.h:72
bool IsExistingCell() const
Definition SparseDynamicPointOctree3.h:57
FVector3i Index
Definition SparseDynamicPointOctree3.h:38
static constexpr uint32 InvalidID
Definition SparseDynamicPointOctree3.h:28
TStaticArray< uint32, 8 > Children
Definition SparseDynamicPointOctree3.h:41
static constexpr uint8 InvalidLevel
Definition SparseDynamicPointOctree3.h:29
void SetChild(uint32 ChildIndex, const FSparsePointOctreeCell &ChildCell)
Definition SparseDynamicPointOctree3.h:81
FSparsePointOctreeCell(uint8 LevelIn, const FVector3i &IndexIn)
Definition SparseDynamicPointOctree3.h:50
FSparsePointOctreeCell()
Definition SparseDynamicPointOctree3.h:43
uint32 CellID
Definition SparseDynamicPointOctree3.h:32
uint32 GetChildCellID(int ChildIndex) const
Definition SparseDynamicPointOctree3.h:67
Definition IntVectorTypes.h:252
bool Contains(const TVector< RealType > &V) const
Definition BoxTypes.h:492
TVector< RealType > Max
Definition BoxTypes.h:249
TVector< RealType > Min
Definition BoxTypes.h:248
Definition SparseListSet.h:39
bool Remove(int32 ListIndex, ElementType Value)
Definition SparseListSet.h:325
void SetValues(int32 ListIndex, const TArray< ElementType > &Values)
Definition SparseListSet.h:111
void Clear(int32 ListIndex)
Definition SparseListSet.h:138
FListHandle AllocateAt(int32 ListIndex)
Definition SparseListSet.h:79
void Insert(int32 ListIndex, ElementType Value)
Definition SparseListSet.h:92
bool IsAllocated(int32 ListIndex) const
Definition SparseListSet.h:64
int32 GetCount(int32 ListIndex) const
Definition SparseListSet.h:149
bool Contains(int32 ListIndex, ElementType Value) const
Definition SparseListSet.h:386
void Enumerate(int32 ListIndex, FuncType Func) const
Definition SparseListSet.h:168