Files
UnrealEngine/Engine/Source/Developer/NaniteUtilities/Private/AdaptiveTessellator.cpp
2025-05-18 13:04:45 +08:00

946 lines
26 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "AdaptiveTessellator.h"
#include "TriangleUtil.h"
#include "LerpVert.h"
#include "Async/ParallelFor.h"
#include "Tessellation/AdaptiveTessellator.h" // only for DM3_NANITE_COMPATIBILITY
namespace Nanite
{
// [ Garland and Heckbert 1995, "Fast Polygonal Approximation of Terrains and Height Fields" ]
FAdaptiveTessellator::FAdaptiveTessellator(
TArray< FLerpVert >& InVerts,
TArray< uint32 >& InIndexes,
TArray< int32 >& InMaterialIndexes,
float InTargetError,
float InSampleRate,
bool bCrackFree,
FDispFunc InGetDisplacement,
FBoundsFunc InGetErrorBounds,
FNumFunc InGetNumSamples,
bool bApplyDisplacement)
: GetDisplacement( InGetDisplacement )
, GetErrorBounds( InGetErrorBounds )
, GetNumSamples( InGetNumSamples )
, TargetError( InTargetError * InTargetError ) // squared distance
, SampleRate( InSampleRate )
, Verts( InVerts )
, Indexes( InIndexes )
, MaterialIndexes( InMaterialIndexes )
{
uint32 NumTris = Indexes.Num() / 3;
AdjEdges.Init( -1, Indexes.Num() );
Triangles.AddUninitialized( NumTris );
Displacements.AddUninitialized( Verts.Num() );
for( int32 TriIndex = 0; TriIndex < MaterialIndexes.Num(); TriIndex++ )
{
for( int k = 0; k < 3; k++ )
{
uint32 i = Indexes[ TriIndex * 3 + k ];
Displacements[i] = GetDisplacement( FVector3f( 1.0f, 0.0f, 0.0f ), Verts[i], Verts[i], Verts[i], MaterialIndexes[ TriIndex ] );
}
}
{
FEdgeHash EdgeHash( Indexes.Num() );
for( int32 EdgeIndex = 0; EdgeIndex < Indexes.Num(); EdgeIndex++ )
{
EdgeHash.ForAllMatching( EdgeIndex, true,
[ this ]( int32 CornerIndex )
{
return Verts[ Indexes[ CornerIndex ] ].Position;
},
[&]( int32 EdgeIndex0, int32 EdgeIndex1 )
{
if( AdjEdges[ EdgeIndex0 ] < 0 &&
AdjEdges[ EdgeIndex1 ] < 0 )
{
AdjEdges[ EdgeIndex0 ] = EdgeIndex1;
AdjEdges[ EdgeIndex1 ] = EdgeIndex0;
}
} );
}
}
SplitRequests.SetNum( Triangles.Num() );
NumSplits = 0;
ParallelFor( TEXT("FAdaptiveTessellator.FindSplitBVH.PF"), NumTris, 32,
[&]( uint32 TriIndex )
{
FindSplitBVH( TriIndex );
} );
while( NumSplits )
{
// Size to atomic count and sort for deterministic order
SplitRequests.SetNum( NumSplits, EAllowShrinking::No );
SplitRequests.Sort();
for( int32 i = 0; i < SplitRequests.Num(); i++ )
Triangles[ SplitRequests[i] ].RequestIndex = i;
while( SplitRequests.Num() )
SplitTriangle( SplitRequests.Pop() );
SplitRequests.SetNum( Triangles.Num() );
NumSplits = 0;
ParallelFor( TEXT("FAdaptiveTessellator.FindSplitBVH.PF"), FindRequests.Num(), 32,
[&]( uint32 i )
{
uint32 TriIndex = FindRequests[i];
FindSplitBVH( TriIndex );
} );
FindRequests.Reset();
}
if( bCrackFree )
{
TArray< FVector3f > OldDisplacements;
OldDisplacements.AddUninitialized( Displacements.Num() );
Swap( Displacements, OldDisplacements );
FHashTable HashTable( 1 << FMath::FloorLog2( Verts.Num() ), Verts.Num() );
ParallelFor( TEXT("FAdaptiveTessellator.HashVerts.PF"), Verts.Num(), 4096,
[&]( int32 i )
{
HashTable.Add_Concurrent( HashPosition( Verts[i].Position ), i );
} );
ParallelFor( TEXT("FAdaptiveTessellator.HashVerts.PF"), Verts.Num(), 4096,
[&]( int32 i )
{
FVector3f Average( 0.0f );
int32 Count = 0;
uint32 Hash = HashPosition( Verts[i].Position );
for( uint32 OtherIndex = HashTable.First( Hash ); HashTable.IsValid( OtherIndex ); OtherIndex = HashTable.Next( OtherIndex ) )
{
if( Verts[i].Position == Verts[ OtherIndex ].Position )
{
Average += OldDisplacements[ OtherIndex ];
Count++;
}
}
Displacements[i] = Average / Count;
} );
}
if (bApplyDisplacement)
{
ParallelFor( TEXT("FAdaptiveTessellator.Displace.PF"), Verts.Num(), 4096,
[&]( int32 i )
{
auto& Vertex = Verts[i];
Vertex.TangentZ.Normalize();
Vertex.Position += Displacements[i];
} );
}
}
void FAdaptiveTessellator::FindSplit( uint32 TriIndex )
{
Triangles[ TriIndex ].RequestIndex = -1;
FLerpVert& Vert0 = Verts[ Indexes[ TriIndex * 3 + 0 ] ];
FLerpVert& Vert1 = Verts[ Indexes[ TriIndex * 3 + 1 ] ];
FLerpVert& Vert2 = Verts[ Indexes[ TriIndex * 3 + 2 ] ];
FVector3f Edge01 = Vert1.Position - Vert0.Position;
FVector3f Edge12 = Vert2.Position - Vert1.Position;
FVector3f Edge20 = Vert0.Position - Vert2.Position;
FVector3f EdgeLengths(
Edge01.Length(),
Edge12.Length(),
Edge20.Length() );
float TriArea = 0.5f * ( Edge01 ^ Edge20 ).Size();
float SampleArea = EquilateralArea( SampleRate );
FVector3f& Displacement0 = Displacements[ Indexes[ TriIndex * 3 + 0 ] ];
FVector3f& Displacement1 = Displacements[ Indexes[ TriIndex * 3 + 1 ] ];
FVector3f& Displacement2 = Displacements[ Indexes[ TriIndex * 3 + 2 ] ];
FVector3f BestSplit = FVector3f::ZeroVector;
float BestError = -1.0f;
auto GetError = [&]( const FVector3f& Barycentrics )
{
FVector3f NewDisplacement = GetDisplacement( Barycentrics, Vert0, Vert1, Vert2, MaterialIndexes[ TriIndex ] );
FVector3f LerpedDisplacement;
LerpedDisplacement = Displacement0 * Barycentrics.X;
LerpedDisplacement += Displacement1 * Barycentrics.Y;
LerpedDisplacement += Displacement2 * Barycentrics.Z;
return ( NewDisplacement - LerpedDisplacement ).SizeSquared();
};
bool bCouldFlipEdge[3];
for( uint32 EdgeIndex = 0; EdgeIndex < 3; EdgeIndex++ )
bCouldFlipEdge[ EdgeIndex ] = CouldFlipEdge( TriIndex * 3 + EdgeIndex );
// Sample edges
for( uint32 EdgeIndex = 0; EdgeIndex < 3; EdgeIndex++ )
{
// Only consider splitting edges that can't be solved with edge flips
if( bCouldFlipEdge[ EdgeIndex ] )
continue;
if( EdgeLengths[ EdgeIndex ] > SampleRate )
{
const uint32 e0 = EdgeIndex;
const uint32 e1 = (1 << e0) & 3;
int32 NumSamples = FMath::CeilToInt( EdgeLengths[ EdgeIndex ] / SampleRate ) - 1;
for( int32 i = 0; i < NumSamples; i++ )
{
FVector3f Barycentrics( 0.0f );
Barycentrics[ e0 ] = float( i + 1 ) / ( NumSamples + 2 );
Barycentrics[ e1 ] = float( NumSamples + 1 - i ) / ( NumSamples + 2 );
float Error = GetError( Barycentrics );
if( BestError < Error )
{
BestSplit = Barycentrics;
BestError = Error;
}
}
}
}
if( TriArea > SampleArea )
{
int32 NumSamples = FMath::CeilToInt( TriArea / SampleArea );
// Sample internal area using quasi-random sequence instead of rasterizing
for( int32 i = 0; i < NumSamples; i++ )
{
// Hammersley sequence
uint32 Reverse = ReverseBits< uint32 >( i + 1 );
Reverse = Reverse ^ (Reverse >> 1);
FVector2f Random;
Random.X = float( i + 1 ) / ( NumSamples + 1 );
Random.Y = float( Reverse >> 8 ) * 0x1p-24f;
// Square to triangle
if( Random.X < Random.Y )
{
Random.X *= 0.5f;
Random.Y -= Random.X;
}
else
{
Random.Y *= 0.5f;
Random.X -= Random.Y;
}
FVector3f Barycentrics;
Barycentrics.X = Random.X;
Barycentrics.Y = Random.Y;
Barycentrics.Z = 1.0f - Barycentrics.X - Barycentrics.Y;
bool bTooCloseToEdge = false;
for( uint32 k = 0; k < 3; k++ )
{
if( bCouldFlipEdge[k] )
continue;
float Dist = Barycentric::DistanceToEdge( Barycentrics[k], EdgeLengths[ (1 << k) & 3 ], TriArea );
if( Dist < 0.5f * SampleRate )
bTooCloseToEdge = true;
}
if( bTooCloseToEdge )
continue;
float Error = GetError( Barycentrics );
if( BestError < Error )
{
BestSplit = Barycentrics;
BestError = Error;
}
}
}
if( BestError > TargetError )
{
Triangles[ TriIndex ].SplitBarycentrics = BestSplit;
Triangles[ TriIndex ].RequestIndex = NumSplits++;
SplitRequests[ Triangles[ TriIndex ].RequestIndex ] = TriIndex;
}
}
void FAdaptiveTessellator::FindSplitBVH( uint32 TriIndex )
{
Triangles[ TriIndex ].RequestIndex = -1;
FLerpVert& Vert0 = Verts[ Indexes[ TriIndex * 3 + 0 ] ];
FLerpVert& Vert1 = Verts[ Indexes[ TriIndex * 3 + 1 ] ];
FLerpVert& Vert2 = Verts[ Indexes[ TriIndex * 3 + 2 ] ];
FVector3f Edge01 = Vert1.Position - Vert0.Position;
FVector3f Edge12 = Vert2.Position - Vert1.Position;
FVector3f Edge20 = Vert0.Position - Vert2.Position;
FVector3f EdgeLengths(
Edge01.Length(),
Edge12.Length(),
Edge20.Length() );
if( EdgeLengths[0] < SampleRate &&
EdgeLengths[1] < SampleRate &&
EdgeLengths[2] < SampleRate )
return;
float TriArea = 0.5f * ( Edge01 ^ Edge20 ).Size();
float SampleArea = EquilateralArea( SampleRate );
FVector3f& Displacement0 = Displacements[ Indexes[ TriIndex * 3 + 0 ] ];
FVector3f& Displacement1 = Displacements[ Indexes[ TriIndex * 3 + 1 ] ];
FVector3f& Displacement2 = Displacements[ Indexes[ TriIndex * 3 + 2 ] ];
bool bCouldFlipEdge[3];
for( uint32 EdgeIndex = 0; EdgeIndex < 3; EdgeIndex++ )
bCouldFlipEdge[ EdgeIndex ] = CouldFlipEdge( TriIndex * 3 + EdgeIndex );
FVector3f BestSplit = FVector3f::ZeroVector;
float BestError = -1.0f;
struct FNode
{
float ErrorMin;
float ErrorMax;
uint32 TriX : 13;
uint32 TriY : 13;
uint32 FlipTri : 1;
uint32 Level : 4;
};
TArray< FNode, TInlineAllocator<256> > Candidates;
FNode Node;
Node.ErrorMin = 0.0f;
Node.ErrorMax = MAX_flt;
Node.TriX = 0;
Node.TriY = 0;
Node.FlipTri = 0;
Node.Level = 0;
{
FVector3f Barycentrics[3] =
{
{ 1.0f, 0.0f, 0.0f },
{ 0.0f, 1.0f, 0.0f },
{ 0.0f, 0.0f, 1.0f },
};
FVector2f ErrorBounds = GetErrorBounds(
Barycentrics,
Vert0,
Vert1,
Vert2,
Displacement0,
Displacement1,
Displacement2,
MaterialIndexes[ TriIndex ] );
Node.ErrorMin = ErrorBounds.X;
Node.ErrorMax = ErrorBounds.Y;
}
float ErrorMinimum = TargetError;
while( Node.ErrorMax >= ErrorMinimum &&
Node.ErrorMax > BestError * 1.25f + TargetError )
{
for( uint32 ChildIndex = 0; ChildIndex < 4; ChildIndex++ )
{
FNode ChildNode;
ChildNode.Level = Node.Level + 1;
ChildNode.FlipTri = Node.FlipTri;
/*
|\ \|\|
|\|\ \|
*/
ChildNode.TriX = Node.TriX * 2 + ( ChildIndex & 1 );
ChildNode.TriY = Node.TriY * 2 + ( ChildIndex >> 1 );
if( Node.FlipTri )
{
ChildNode.TriX ^= 1;
ChildNode.TriY ^= 1;
}
if( ChildIndex == 3 )
{
ChildNode.TriX ^= 1;
ChildNode.TriY ^= 1;
ChildNode.FlipTri ^= 1;
}
FVector3f Barycentrics[3];
SubtriangleBarycentrics( ChildNode.TriX, ChildNode.TriY, ChildNode.FlipTri, 1 << ChildNode.Level, Barycentrics );
{
FVector2f ErrorBounds = GetErrorBounds(
Barycentrics,
Vert0,
Vert1,
Vert2,
Displacement0,
Displacement1,
Displacement2,
MaterialIndexes[ TriIndex ] );
ChildNode.ErrorMin = ErrorBounds.X;
ChildNode.ErrorMax = ErrorBounds.Y;
// Clamp bounds just in case float precision results in them not perfectly nesting
ChildNode.ErrorMin = FMath::Max( ChildNode.ErrorMin, Node.ErrorMin );
ChildNode.ErrorMax = FMath::Min( ChildNode.ErrorMax, Node.ErrorMax );
}
if( ErrorMinimum > ChildNode.ErrorMax )
continue;
if( ErrorMinimum < ChildNode.ErrorMin )
ErrorMinimum = ChildNode.ErrorMin;
if( ChildNode.ErrorMax > BestError * 1.25f + TargetError )
{
int32 NumSamples = 64;
bool bStopTraversal = ChildNode.Level == 12 || ChildNode.ErrorMax - ChildNode.ErrorMin < TargetError;
if( !bStopTraversal )
{
#if 0
bool bSmallEnough =
LengthSquared( Barycentrics[0], Barycentrics[1], EdgeLengths * EdgeLengths ) < SampleRate * SampleRate &&
LengthSquared( Barycentrics[1], Barycentrics[2], EdgeLengths * EdgeLengths ) < SampleRate * SampleRate &&
LengthSquared( Barycentrics[2], Barycentrics[0], EdgeLengths * EdgeLengths ) < SampleRate * SampleRate;
#else
float ChildArea = Barycentric::SubtriangleArea( Barycentrics[0], Barycentrics[1], Barycentrics[2], TriArea );
bool bSmallEnough = ChildArea < SampleArea * 16.0f;
NumSamples = FMath::Min( NumSamples, FMath::CeilToInt( ChildArea / SampleArea ) );
#endif
bStopTraversal = bSmallEnough;
}
if( !bStopTraversal )
{
NumSamples = FMath::Min( NumSamples, GetNumSamples( Barycentrics, Vert0, Vert1, Vert2, MaterialIndexes[ TriIndex ] ) );
bStopTraversal = NumSamples <= 16;
}
if( bStopTraversal )
{
for( int32 i = 0; i < NumSamples; i++ )
{
// Hammersley sequence
uint32 Reverse = ReverseBits< uint32 >( i + 1 );
Reverse = Reverse ^ (Reverse >> 1);
FVector2f Random;
Random.X = float( i + 1 ) / ( NumSamples + 1 );
Random.Y = float( Reverse >> 8 ) * 0x1p-24f;
// Square to triangle
if( Random.X < Random.Y )
{
Random.X *= 0.5f;
Random.Y -= Random.X;
}
else
{
Random.Y *= 0.5f;
Random.X -= Random.Y;
}
FVector3f Split;
Split.X = Random.X;
Split.Y = Random.Y;
Split.Z = 1.0f - Split.X - Split.Y;
float DistToEdge[3];
DistToEdge[0] = Barycentric::DistanceToEdge( Split[2], EdgeLengths[0], TriArea );
DistToEdge[1] = Barycentric::DistanceToEdge( Split[0], EdgeLengths[1], TriArea );
DistToEdge[2] = Barycentric::DistanceToEdge( Split[1], EdgeLengths[2], TriArea );
uint32 e0 = FMath::Min3Index( DistToEdge[0], DistToEdge[1], DistToEdge[2] );
uint32 e1 = (1 << e0) & 3;
uint32 e2 = (1 << e1) & 3;
CA_ASSUME(e1 <= 2);
CA_ASSUME(e2 <= 2);
bool bTooCloseToEdge = DistToEdge[ e0 ] < 0.5f * SampleRate;
if( bTooCloseToEdge && !bCouldFlipEdge[ e0 ] )
{
Split[ e0 ] = Split[ e0 ] / ( Split[ e0 ] + Split[ e1 ] );
Split[ e1 ] = 1.0f - Split[ e0 ];
Split[ e2 ] = 0.0f;
bool bTooCloseToEdge1 = !bCouldFlipEdge[ e1 ] && Barycentric::DistanceToEdge( Split[ e0 ], EdgeLengths[ e1 ], TriArea ) < 0.5f * SampleRate;
bool bTooCloseToEdge2 = !bCouldFlipEdge[ e2 ] && Barycentric::DistanceToEdge( Split[ e1 ], EdgeLengths[ e2 ], TriArea ) < 0.5f * SampleRate;
bTooCloseToEdge = bTooCloseToEdge1 || bTooCloseToEdge2;
}
if( !bTooCloseToEdge )
{
FVector3f NewDisplacement = GetDisplacement( Split, Vert0, Vert1, Vert2, MaterialIndexes[ TriIndex ] );
FVector3f LerpedDisplacement;
LerpedDisplacement = Displacement0 * Split.X;
LerpedDisplacement += Displacement1 * Split.Y;
LerpedDisplacement += Displacement2 * Split.Z;
float Error = ( NewDisplacement - LerpedDisplacement ).SizeSquared();
if( BestError < Error )
{
BestSplit = Split;
BestError = Error;
}
}
}
}
else
{
Candidates.HeapPush( ChildNode,
[&]( const FNode& Node0, const FNode& Node1 )
{
return Node0.ErrorMax > Node1.ErrorMax;
} );
}
}
}
if( Candidates.IsEmpty() )
break;
Candidates.HeapPop( Node,
[&]( const FNode& Node0, const FNode& Node1 )
{
return Node0.ErrorMax > Node1.ErrorMax;
}, EAllowShrinking::No );
}
if( BestError > TargetError )
{
Triangles[ TriIndex ].SplitBarycentrics = BestSplit;
Triangles[ TriIndex ].RequestIndex = NumSplits++;
SplitRequests[ Triangles[ TriIndex ].RequestIndex ] = TriIndex;
}
}
void FAdaptiveTessellator::SplitTriangle( uint32 TriIndex )
{
FVector3f Barycentrics = Triangles[ TriIndex ].SplitBarycentrics;
Triangles[ TriIndex ].RequestIndex = -1;
ensure( FMath::Abs( Barycentrics.X + Barycentrics.Y + Barycentrics.Z - 1.0f ) < 1e-4f );
ensure( 0.0f <= Barycentrics.X && Barycentrics.X < 1.0f );
ensure( 0.0f <= Barycentrics.Y && Barycentrics.Y < 1.0f );
ensure( 0.0f <= Barycentrics.Z && Barycentrics.Z < 1.0f );
FLerpVert& Vert0 = Verts[ Indexes[ TriIndex * 3 + 0 ] ];
FLerpVert& Vert1 = Verts[ Indexes[ TriIndex * 3 + 1 ] ];
FLerpVert& Vert2 = Verts[ Indexes[ TriIndex * 3 + 2 ] ];
FLerpVert NewVert;
NewVert = Vert0 * Barycentrics.X;
NewVert += Vert1 * Barycentrics.Y;
NewVert += Vert2 * Barycentrics.Z;
FVector3f NewDisplacment = GetDisplacement( Barycentrics, Vert0, Vert1, Vert2, MaterialIndexes[ TriIndex ] );
uint32 NewIndex = Verts.Add( NewVert );
Displacements.Add( NewDisplacment );
for( uint32 EdgeIndex = 0; EdgeIndex < 3; EdgeIndex++ )
{
if( Barycentrics[ ( EdgeIndex + 2 ) % 3 ] == 0.0f )
{
// Split edge
int32 Edge[2];
Edge[0] = TriIndex * 3 + EdgeIndex;
Edge[1] = AdjEdges[ TriIndex * 3 + EdgeIndex ];
int32 NumNewTris = Edge[1] < 0 ? 1 : 2;
int32 OldTriIndex[2];
OldTriIndex[0] = TriIndex;
OldTriIndex[1] = Edge[1] / 3;
int32 NewTriIndex[2];
NewTriIndex[0] = Triangles.AddDefaulted( NumNewTris );
NewTriIndex[1] = NewTriIndex[0] + 1;
if( Edge[1] < 0 )
{
OldTriIndex[1] = -1;
NewTriIndex[1] = -1;
}
else
{
ensure( Verts[ Indexes[ Edge[0] ] ].Position == Verts[ Indexes[ Cycle3( Edge[1] ) ] ].Position );
ensure( Verts[ Indexes[ Edge[1] ] ].Position == Verts[ Indexes[ Cycle3( Edge[0] ) ] ].Position );
}
/*
v2
/|\
e1/ | \e2
/ | \
v1/ 0|1 \v0
o====+====o
v0\ 1|0 /v1
\ | /
e2\ | /e1
\|/
v2
*/
for( int32 j = 0; j < NumNewTris; j++ )
{
const uint32 e0 = Edge[j];
const uint32 e1 = Cycle3( Edge[j] );
const uint32 e2 = Cycle3( Edge[j], 2 );
uint32 OldIndex0 = Indexes[ e0 ];
uint32 OldIndex1 = Indexes[ e1 ];
uint32 OldIndex2 = Indexes[ e2 ];
int32 OldAdjEdge1 = AdjEdges[ e1 ];
int32 OldAdjEdge2 = AdjEdges[ e2 ];
if( j == 1 )
{
NewVert = Verts[ OldIndex0 ] * Barycentrics[ ( EdgeIndex + 1 ) % 3 ];
NewVert += Verts[ OldIndex1 ] * Barycentrics[ EdgeIndex ];
NewDisplacment = GetDisplacement( FVector3f( 1.0f, 0.0f, 0.0f ), NewVert, NewVert, NewVert, MaterialIndexes[ OldTriIndex[j] ] );
NewIndex = Verts.Add( NewVert );
Displacements.Add( NewDisplacment );
}
Indexes.AddUninitialized(3);
AdjEdges.AddUninitialized(3);
MaterialIndexes.Add( CopyTemp( MaterialIndexes[ OldTriIndex[j] ] ) );
// replace v0
uint32 i = OldTriIndex[j] * 3;
Indexes[ i + 0 ] = NewIndex;
Indexes[ i + 1 ] = OldIndex1;
Indexes[ i + 2 ] = OldIndex2;
AdjEdges[ i + 0 ] = NewTriIndex[j^1] * 3;
LinkEdge( i + 1, OldAdjEdge1 );
AdjEdges[ i + 2 ] = NewTriIndex[j] * 3 + 1;
// replace v1
i = NewTriIndex[j] * 3;
Indexes[ i + 0 ] = OldIndex0;
Indexes[ i + 1 ] = NewIndex;
Indexes[ i + 2 ] = OldIndex2;
AdjEdges[ i + 0 ] = OldTriIndex[j^1] * 3;
AdjEdges[ i + 1 ] = OldTriIndex[j] * 3 + 2;
LinkEdge( i + 2, OldAdjEdge2 );
}
for( int32 j = 0; j < NumNewTris; j++ )
{
RemoveSplitRequest( OldTriIndex[j] );
AddFindRequest( OldTriIndex[j] );
AddFindRequest( NewTriIndex[j] );
TryDelaunayFlip( OldTriIndex[j] * 3 + 1 );
TryDelaunayFlip( NewTriIndex[j] * 3 + 2 );
}
return;
}
}
{
// Poke triangle
uint32 OldIndexes[3];
OldIndexes[0] = Indexes[ TriIndex * 3 + 0 ];
OldIndexes[1] = Indexes[ TriIndex * 3 + 1 ];
OldIndexes[2] = Indexes[ TriIndex * 3 + 2 ];
int32 OldAdjEdges[3];
OldAdjEdges[0] = AdjEdges[ TriIndex * 3 + 0 ];
OldAdjEdges[1] = AdjEdges[ TriIndex * 3 + 1 ];
OldAdjEdges[2] = AdjEdges[ TriIndex * 3 + 2 ];
uint32 NewTriIndex[3];
NewTriIndex[0] = TriIndex;
NewTriIndex[1] = Triangles.AddDefaulted(2);
NewTriIndex[2] = NewTriIndex[1] + 1;
Indexes.AddUninitialized(6);
AdjEdges.AddUninitialized(6);
MaterialIndexes.AddUninitialized(2);
for( uint32 EdgeIndex = 0; EdgeIndex < 3; EdgeIndex++ )
{
const uint32 e0 = EdgeIndex;
const uint32 e1 = (1 << e0) & 3;
const uint32 e2 = (1 << e1) & 3;
uint32 i = NewTriIndex[ EdgeIndex ] * 3;
Indexes[ i + 0 ] = OldIndexes[ e0 ];
Indexes[ i + 1 ] = OldIndexes[ e1 ];
Indexes[ i + 2 ] = NewIndex;
LinkEdge( i + 0, OldAdjEdges[ e0 ] );
AdjEdges[ i + 1 ] = NewTriIndex[ e1 ] * 3 + 2;
AdjEdges[ i + 2 ] = NewTriIndex[ e2 ] * 3 + 1;
MaterialIndexes[ NewTriIndex[ EdgeIndex ] ] = MaterialIndexes[ TriIndex ];
}
for( uint32 EdgeIndex = 0; EdgeIndex < 3; EdgeIndex++ )
{
AddFindRequest( NewTriIndex[ EdgeIndex ] );
TryDelaunayFlip( NewTriIndex[ EdgeIndex ] * 3 );
}
}
}
void FAdaptiveTessellator::AddFindRequest( uint32 TriIndex )
{
int32& RequestIndex = Triangles[ TriIndex ].RequestIndex;
if( RequestIndex != -2 )
{
check( RequestIndex == -1 );
FindRequests.Add( TriIndex );
RequestIndex = -2;
}
}
void FAdaptiveTessellator::RemoveSplitRequest( uint32 TriIndex )
{
int32& RequestIndex = Triangles[ TriIndex ].RequestIndex;
if( RequestIndex >= 0 )
{
Triangles[ SplitRequests.Last() ].RequestIndex = RequestIndex;
SplitRequests.RemoveAtSwap( RequestIndex, EAllowShrinking::No );
RequestIndex = -1;
}
}
void FAdaptiveTessellator::LinkEdge( int32 EdgeIndex0, int32 EdgeIndex1 )
{
AdjEdges[ EdgeIndex0 ] = EdgeIndex1;
if( EdgeIndex1 >= 0 )
{
AdjEdges[ EdgeIndex1 ] = EdgeIndex0;
check( Verts[ Indexes[ EdgeIndex0 ] ].Position == Verts[ Indexes[ Cycle3( EdgeIndex1 ) ] ].Position );
check( Verts[ Indexes[ EdgeIndex1 ] ].Position == Verts[ Indexes[ Cycle3( EdgeIndex0 ) ] ].Position );
}
}
FVector3f FAdaptiveTessellator::GetTriNormal( uint32 TriIndex ) const
{
FVector3f& p0 = Verts[ Indexes[ TriIndex * 3 + 0 ] ].Position;
FVector3f& p1 = Verts[ Indexes[ TriIndex * 3 + 1 ] ].Position;
FVector3f& p2 = Verts[ Indexes[ TriIndex * 3 + 2 ] ].Position;
FVector3f Edge01 = p1 - p0;
FVector3f Edge12 = p2 - p1;
FVector3f Edge20 = p0 - p2;
return ( Edge01 ^ Edge20 ).GetSafeNormal();
}
bool FAdaptiveTessellator::CouldFlipEdge( uint32 EdgeIndex ) const
{
int32 AdjEdgeIndex = AdjEdges[ EdgeIndex ];
if( AdjEdgeIndex < 0 )
return false;
if( Indexes[ EdgeIndex ] != Indexes[ Cycle3( AdjEdgeIndex ) ] ||
Indexes[ Cycle3( EdgeIndex ) ] != Indexes[ AdjEdgeIndex ] )
{
return false;
}
uint32 TriIndex = EdgeIndex / 3;
uint32 AdjTriIndex = AdjEdgeIndex / 3;
if( MaterialIndexes[ TriIndex ] != MaterialIndexes[ AdjTriIndex ] )
return false;
#if 1
FVector3f TriNormal = GetTriNormal( TriIndex );
FVector3f AdjNormal = GetTriNormal( AdjTriIndex );
return ( TriNormal | AdjNormal ) > 0.999f;
#else
const FVector3f& a0 = Verts[ Indexes[ AdjTriIndex * 3 + 0 ] ].Position;
const FVector3f& a1 = Verts[ Indexes[ AdjTriIndex * 3 + 1 ] ].Position;
const FVector3f& a2 = Verts[ Indexes[ AdjTriIndex * 3 + 2 ] ].Position;
float AdjArea = 0.5f * ( (a2 - a0) ^ (a1 - a0) ).Size();
const FVector3f& p0 = Verts[ Indexes[ TriIndex * 3 + 0 ] ].Position;
const FVector3f& p1 = Verts[ Indexes[ TriIndex * 3 + 1 ] ].Position;
const FVector3f& p2 = Verts[ Indexes[ TriIndex * 3 + 2 ] ].Position;
const FVector3f& p3 = Verts[ Indexes[ Cycle3( AdjEdgeIndex, 2 ) ] ].Position;
FVector3f Cross = (p2 - p0) ^ (p1 - p0);
// Tetrahedron
float Volume = (1.0f / 6.0f) * FMath::Abs( (p3 - p0) | Cross );
float Area = FMath::Max( 0.5f * Cross.Size(), AdjArea );
float Height = 3.0f * Volume / Area;
return Height < 1e-2f;
#endif
}
void FAdaptiveTessellator::TryDelaunayFlip( uint32 EdgeIndex )
{
if( !CouldFlipEdge( EdgeIndex ) )
return;
auto ComputeCotangent = [this]( uint32 EdgeIndex )
{
const uint32 e0 = EdgeIndex;
const uint32 e1 = Cycle3( EdgeIndex );
const uint32 e2 = Cycle3( EdgeIndex, 2 );
FLerpVert& Vert0 = Verts[ Indexes[ e0 ] ];
FLerpVert& Vert1 = Verts[ Indexes[ e1 ] ];
FLerpVert& Vert2 = Verts[ Indexes[ e2 ] ];
FVector3f Edge01 = Vert1.Position - Vert0.Position;
FVector3f Edge12 = Vert2.Position - Vert1.Position;
FVector3f Edge20 = Vert0.Position - Vert2.Position;
FVector3f EdgeLengthsSqr(
Edge01.SizeSquared(),
Edge12.SizeSquared(),
Edge20.SizeSquared() );
float TriArea = 0.5f * ( Edge01 ^ Edge20 ).Size();
return 0.25f * ( -EdgeLengthsSqr.X + EdgeLengthsSqr.Y + EdgeLengthsSqr.Z ) / TriArea;
};
float LaplacianWeight;
LaplacianWeight = ComputeCotangent( EdgeIndex );
LaplacianWeight += ComputeCotangent( AdjEdges[ EdgeIndex ] );
LaplacianWeight *= 0.5f;
bool bFlipEdge = LaplacianWeight < -1e-6f;
if( bFlipEdge )
{
int32 AdjEdge = AdjEdges[ EdgeIndex ];
uint32 TriIndex = EdgeIndex / 3;
uint32 AdjTriIndex = AdjEdge / 3;
/*
v2 v0,v1 vA
/\ /|\ /\
e1/ \e2 e2/ | \e1 eD/ \eA
/ \ / | \ / \
v1/ e0 \v0 / | \ / \
o========o => v2 e0|e0 v2 vD vB
v0\ e0 /v1 \ | / \ /
\ / \ | / \ /
e2\ /e1 e1\ | /e2 eC\ /eB
\/ \|/ \/
v2 v1'v0 vC
*/
const uint32 eA = TriIndex * 3 + ( EdgeIndex + 2 ) % 3;
const uint32 eB = AdjTriIndex * 3 + ( AdjEdge + 1 ) % 3;
const uint32 eC = AdjTriIndex * 3 + ( AdjEdge + 2 ) % 3;
const uint32 eD = TriIndex * 3 + ( EdgeIndex + 1 ) % 3;
uint32 IndexA = Indexes[ eA ];
uint32 IndexB = Indexes[ eB ];
uint32 IndexC = Indexes[ eC ];
uint32 IndexD = Indexes[ eD ];
uint32 AdjEdgeA = AdjEdges[ eA ];
uint32 AdjEdgeB = AdjEdges[ eB ];
uint32 AdjEdgeC = AdjEdges[ eC ];
uint32 AdjEdgeD = AdjEdges[ eD ];
#ifndef DM3_NANITE_COMPATIBILITY
RemoveSplitRequest( TriIndex );
RemoveSplitRequest( AdjTriIndex );
AddFindRequest( TriIndex );
AddFindRequest( AdjTriIndex );
Indexes[ TriIndex * 3 + 0 ] = IndexC;
Indexes[ TriIndex * 3 + 1 ] = IndexA;
Indexes[ TriIndex * 3 + 2 ] = IndexB;
Indexes[ AdjTriIndex * 3 + 0 ] = IndexA;
Indexes[ AdjTriIndex * 3 + 1 ] = IndexC;
Indexes[ AdjTriIndex * 3 + 2 ] = IndexD;
LinkEdge( TriIndex * 3, AdjTriIndex * 3 );
LinkEdge( TriIndex * 3 + 1, AdjEdgeA );
LinkEdge( TriIndex * 3 + 2, AdjEdgeB );
LinkEdge( AdjTriIndex * 3 + 1, AdjEdgeC );
LinkEdge( AdjTriIndex * 3 + 2, AdjEdgeD );
TryDelaunayFlip( TriIndex * 3 + 1 );
TryDelaunayFlip( TriIndex * 3 + 2 );
TryDelaunayFlip( AdjTriIndex * 3 + 1 );
TryDelaunayFlip( AdjTriIndex * 3 + 2 );
#else
RemoveSplitRequest( AdjTriIndex );
RemoveSplitRequest( TriIndex );
AddFindRequest( AdjTriIndex );
AddFindRequest( TriIndex );
Indexes[ AdjTriIndex * 3 + 0 ] = IndexC;
Indexes[ AdjTriIndex * 3 + 1 ] = IndexA;
Indexes[ AdjTriIndex * 3 + 2 ] = IndexB;
Indexes[ TriIndex * 3 + 0 ] = IndexA;
Indexes[ TriIndex * 3 + 1 ] = IndexC;
Indexes[ TriIndex * 3 + 2 ] = IndexD;
LinkEdge( AdjTriIndex * 3, TriIndex * 3 );
LinkEdge( TriIndex * 3 + 1, AdjEdgeC );
LinkEdge( TriIndex * 3 + 2, AdjEdgeD );
LinkEdge( AdjTriIndex * 3 + 1, AdjEdgeA );
LinkEdge( AdjTriIndex * 3 + 2, AdjEdgeB );
TryDelaunayFlip( TriIndex * 3 + 1 );
TryDelaunayFlip( TriIndex * 3 + 2 );
TryDelaunayFlip( AdjTriIndex * 3 + 1 );
TryDelaunayFlip( AdjTriIndex * 3 + 2 );
#endif
}
}
} // namespace Nanite