Files
UnrealEngine/Engine/Source/Developer/NaniteBuilder/Private/BVHCluster.h
2025-05-18 13:04:45 +08:00

164 lines
4.1 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Math/Bounds.h"
class FBVHCluster
{
public:
FBVHCluster( uint32 InNumElements, uint32 InMinPartitionSize, uint32 InMaxPartitionSize );
template< typename FGetBounds >
void Build( const FGetBounds& GetBounds );
// Inclusive
struct FRange
{
uint32 Begin;
uint32 End;
bool operator<( const FRange& Other) const { return Begin < Other.Begin; }
};
TArray< FRange > Ranges;
TArray< uint32 > Indexes;
TArray< uint32 > SortedTo;
private:
template< typename FGetBounds >
void Build( uint32 Offset, uint32 Num, const FGetBounds& GetBounds );
template< typename FGetBounds >
uint32 Split( uint32 Offset, uint32 Num, const FGetBounds& GetBounds );
template< typename FGetBounds >
void Sort( uint32* RESTRICT Dst, uint32* RESTRICT Src, uint32 Num, int32 Dim, const FGetBounds& GetBounds );
uint32 NumElements;
uint32 MinPartitionSize;
uint32 MaxPartitionSize;
TArray< float > CostLeft;
TArray< float > CostRight;
};
template< typename FGetBounds >
void FBVHCluster::Build( const FGetBounds& GetBounds )
{
Build( 0, NumElements, GetBounds );
for( uint32 i = 0; i < NumElements; i++ )
SortedTo[ Indexes[i] ] = i;
}
template< typename FGetBounds >
void FBVHCluster::Build( uint32 Offset, uint32 Num, const FGetBounds& GetBounds )
{
if( Num <= MaxPartitionSize )
{
Ranges.Add( { Offset, Offset + Num } );
return;
}
uint32 SplitIndex = Split( Offset, Num, GetBounds );
uint32 Num0 = SplitIndex + 1;
uint32 Num1 = Num - Num0;
check( Num1 > 0 );
Build( Offset, Num0, GetBounds );
Build( Offset + Num0, Num1, GetBounds );
}
template< typename FGetBounds >
uint32 FBVHCluster::Split( uint32 Offset, uint32 Num, const FGetBounds& GetBounds )
{
FVector3f LeastCost( MAX_flt );
FIntVector LeastCostSplit( -1 );
uint32* Unsorted = &Indexes[ Offset ];
uint32* Sorted = &SortedTo[ Offset ];
for( int32 Dim = 0; Dim < 3; Dim++ )
{
Sort( Sorted, Unsorted, Num, Dim, GetBounds );
FBounds3f Bounds;
// SAH sweep forward
for( uint32 i = 0; i < Num; i++ )
{
Bounds += GetBounds( Sorted[i] );
FVector3f Size = Bounds.Max - Bounds.Min;
float HalfSurfaceArea = Size.X * Size.Y + Size.X * Size.Z + Size.Y * Size.Z;
int32 Count = i + 1;
//CostLeft[ Offset + i ] = HalfSurfaceArea * (float)Count;
CostLeft[ Offset + i ] = (float)Count * Size.SizeSquared();
}
Bounds = FBounds3f();
// SAH sweep back
for( int32 i = Num - 1; i >= 0; i-- )
{
Bounds += GetBounds( Sorted[i] );
FVector3f Size = Bounds.Max - Bounds.Min;
float HalfSurfaceArea = Size.X * Size.Y + Size.X * Size.Z + Size.Y * Size.Z;
int32 Count = Num - i - 1;
//CostRight[ Offset + i ] = HalfSurfaceArea * (float)Count;
CostRight[ Offset + i ] = (float)Count * Size.SizeSquared();
}
uint32 NumPartitions = FMath::DivideAndRoundUp( Num, MaxPartitionSize );
// Find least SAH cost
for( uint32 i = 0; i < Num - 1; i++ )
{
uint32 Num0 = i + 1;
uint32 Num1 = Num - Num0;
uint32 NumPartitions0 = FMath::DivideAndRoundUp( Num0, MaxPartitionSize );
uint32 NumPartitions1 = FMath::DivideAndRoundUp( Num1, MaxPartitionSize );
if( NumPartitions0 + NumPartitions1 == NumPartitions )
{
float Cost = CostLeft[ Offset + i ] + CostRight[ Offset + i + 1 ];
if( Cost < LeastCost[ Dim ] )
{
LeastCost[ Dim ] = Cost;
LeastCostSplit[ Dim ] = i;
}
}
}
Swap( Sorted, Unsorted );
}
uint32 BestDim = FMath::Min3Index( LeastCost[0], LeastCost[1], LeastCost[2] );
check( LeastCost[ BestDim ] > 0.0f );
Sort( Sorted, Unsorted, Num, BestDim, GetBounds );
// Even number of sorts means final sorted values are in the original array.
return LeastCostSplit[ BestDim ];
}
template< typename FGetBounds >
void FBVHCluster::Sort( uint32* RESTRICT Dst, uint32* RESTRICT Src, uint32 Num, int32 Dim, const FGetBounds& GetBounds )
{
RadixSort32( Dst, Src, Num,
[ this, &GetBounds, Dim ]( uint32 Index )
{
FBounds3f Bounds = GetBounds( Index );
union { float f; uint32 i; } Center;
Center.f = 0.5f * ( Bounds.Min[ Dim ] + Bounds.Max[ Dim ] );
uint32 Mask = -int32( Center.i >> 31 ) | 0x80000000;
return Center.i ^ Mask;
} );
}