946 lines
26 KiB
C++
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
|