From 449fc597b5bfdc6e273c2225bed12cb9b796fe31 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 2 Feb 2026 19:21:45 +0100 Subject: [PATCH] Fix crash when using overlapping instances #3899 --- Source/Engine/Foliage/Foliage.cpp | 42 ++++++++++++++++++++---- Source/Engine/Foliage/FoliageCluster.cpp | 1 + Source/Engine/Foliage/FoliageCluster.h | 5 +++ 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/Source/Engine/Foliage/Foliage.cpp b/Source/Engine/Foliage/Foliage.cpp index ddc3468f7..f8b9c7b0f 100644 --- a/Source/Engine/Foliage/Foliage.cpp +++ b/Source/Engine/Foliage/Foliage.cpp @@ -44,20 +44,39 @@ void Foliage::AddToCluster(ChunkedArray ZeroTolerance); ASSERT(cluster->Bounds.Intersects(instance.Bounds)); - // Find target cluster - while (cluster->Children[0]) + // Minor clusters don't use bounds intersection but try to find the first free cluster instead + if (cluster->IsMinor) { + // Insert into the first non-full child cluster or subdivide 1st child +#define CHECK_CHILD(idx) \ + if (cluster->Children[idx]->Instances.Count() < FOLIAGE_CLUSTER_CAPACITY) \ + { \ + cluster->Children[idx]->Instances.Add(&instance); \ + return; \ + } + CHECK_CHILD(3); + CHECK_CHILD(2); + CHECK_CHILD(1); + cluster = cluster->Children[0]; +#undef CHECK_CHILD + } + else + { + // Find target cluster + while (cluster->Children[0]) + { #define CHECK_CHILD(idx) \ if (cluster->Children[idx]->Bounds.Intersects(instance.Bounds)) \ { \ cluster = cluster->Children[idx]; \ continue; \ } - CHECK_CHILD(0); - CHECK_CHILD(1); - CHECK_CHILD(2); - CHECK_CHILD(3); + CHECK_CHILD(0); + CHECK_CHILD(1); + CHECK_CHILD(2); + CHECK_CHILD(3); #undef CHECK_CHILD + } } // Check if it's not full @@ -79,11 +98,20 @@ void Foliage::AddToCluster(ChunkedArrayBounds.Minimum; const Vector3 max = cluster->Bounds.Maximum; - const Vector3 size = cluster->Bounds.GetSize(); + const Vector3 size = max - min; cluster->Children[0]->Init(BoundingBox(min, min + size * Vector3(0.5f, 1.0f, 0.5f))); cluster->Children[1]->Init(BoundingBox(min + size * Vector3(0.5f, 0.0f, 0.5f), max)); cluster->Children[2]->Init(BoundingBox(min + size * Vector3(0.5f, 0.0f, 0.0f), min + size * Vector3(1.0f, 1.0f, 0.5f))); cluster->Children[3]->Init(BoundingBox(min + size * Vector3(0.0f, 0.0f, 0.5f), min + size * Vector3(0.5f, 1.0f, 1.0f))); + if (cluster->IsMinor || size.MinValue() < 1.0f) + { + // Mark children as minor to avoid infinite subdivision + cluster->IsMinor = true; + cluster->Children[0]->IsMinor = true; + cluster->Children[1]->IsMinor = true; + cluster->Children[2]->IsMinor = true; + cluster->Children[3]->IsMinor = true; + } // Move instances to a proper cells for (int32 i = 0; i < cluster->Instances.Count(); i++) diff --git a/Source/Engine/Foliage/FoliageCluster.cpp b/Source/Engine/Foliage/FoliageCluster.cpp index 1f76e5086..fd4c0f753 100644 --- a/Source/Engine/Foliage/FoliageCluster.cpp +++ b/Source/Engine/Foliage/FoliageCluster.cpp @@ -9,6 +9,7 @@ void FoliageCluster::Init(const BoundingBox& bounds) Bounds = bounds; TotalBounds = bounds; MaxCullDistance = 0.0f; + IsMinor = false; Children[0] = nullptr; Children[1] = nullptr; diff --git a/Source/Engine/Foliage/FoliageCluster.h b/Source/Engine/Foliage/FoliageCluster.h index 55cbeb027..c55305c5d 100644 --- a/Source/Engine/Foliage/FoliageCluster.h +++ b/Source/Engine/Foliage/FoliageCluster.h @@ -33,6 +33,11 @@ public: /// float MaxCullDistance; + /// + /// Flag used by clusters that are not typical quad-tree nodes but have no volume (eg. lots of instances placed on top of each other). + /// + int32 IsMinor : 1; + /// /// The child clusters. If any element is valid then all are created. ///