Files
UnrealEngine/Engine/Source/Developer/NaniteUtilities/Public/TriangleUtil.h
2025-05-18 13:04:45 +08:00

255 lines
6.5 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Containers/HashTable.h"
namespace Nanite
{
FORCEINLINE uint32 HashPosition( const FVector3f& Position )
{
union { float f; uint32 i; } x;
union { float f; uint32 i; } y;
union { float f; uint32 i; } z;
x.f = Position.X;
y.f = Position.Y;
z.f = Position.Z;
return Murmur32( {
Position.X == 0.0f ? 0u : x.i,
Position.Y == 0.0f ? 0u : y.i,
Position.Z == 0.0f ? 0u : z.i
} );
}
FORCEINLINE uint32 Cycle3( uint32 Value )
{
uint32 ValueMod3 = Value % 3;
uint32 Value1Mod3 = ( 1 << ValueMod3 ) & 3;
return Value - ValueMod3 + Value1Mod3;
}
FORCEINLINE uint32 Cycle3( uint32 Value, uint32 Offset )
{
return Value - Value % 3 + ( Value + Offset ) % 3;
}
FORCEINLINE float EquilateralArea( float EdgeLength )
{
const float sqrt3_4 = 0.4330127f;
return sqrt3_4 * FMath::Square( EdgeLength * EdgeLength );
}
FORCEINLINE float EquilateralEdgeLength( float Area )
{
const float sqrt3_4 = 0.4330127f;
return FMath::Sqrt( Area / sqrt3_4 );
}
FORCEINLINE float TriangleArea( float a, float b, float c )
{
float AreaSqrTimes16 = FMath::Max( 0.0f,
( a + b + c ) *
(-a + b + c ) *
( a - b + c ) *
( a + b - c ) );
return FMath::Sqrt( AreaSqrTimes16 ) * 0.25f;
}
// a,b,c are tessellation factors for each edge
FORCEINLINE int32 ApproxNumTris( int32 a, int32 b, int32 c )
{
// Heron's formula divided by area of unit triangle
float s = 0.5f * float( a + b + c );
float NumTris = 4.0f * FMath::Sqrt( FMath::Max( 0.0625f, s * (s - (float)a) * (s - (float)b) * (s - (float)c) / 3.0f ) );
int32 MaxFactor = FMath::Max3( a, b, c );
return FMath::Max( FMath::RoundToInt( NumTris ), MaxFactor );
}
namespace Barycentric
{
// [ Schindler and Chen 2012, "Barycentric Coordinates in Olympiad Geometry" https://web.evanchen.cc/handouts/bary/bary-full.pdf ]
FORCEINLINE float LengthSquared( const FVector3f& Barycentrics0, const FVector3f& Barycentrics1, const FVector3f& EdgeLengthsSqr )
{
// Barycentric displacement vector:
// 0 = x + y + z
FVector3f Disp = Barycentrics0 - Barycentrics1;
/* TODO change edge order to match ariel coords
v0
/\
e2 / \ e0
/____\
v2 e1 v1
*/
// Length of displacement
return -Disp.X * Disp.Y * EdgeLengthsSqr[0]
-Disp.Y * Disp.Z * EdgeLengthsSqr[1]
-Disp.Z * Disp.X * EdgeLengthsSqr[2];
}
FORCEINLINE float SubtriangleArea( const FVector3f& Barycentrics0, const FVector3f& Barycentrics1, const FVector3f& Barycentrics2, float TriangleArea )
{
// Area * Determinant using triple product
return TriangleArea * FMath::Abs( Barycentrics0 | ( Barycentrics1 ^ Barycentrics2 ) );
}
// https://math.stackexchange.com/questions/3748903/closest-point-to-triangle-edge-with-barycentric-coordinates
FORCEINLINE float DistanceToEdge( float Barycentric, float EdgeLength, float TriangleArea )
{
return 2.0f * Barycentric * TriangleArea / EdgeLength;
}
// Angle at corner 0
FORCEINLINE float Cotangent(
const FVector3f& Barycentrics0,
const FVector3f& Barycentrics1,
const FVector3f& Barycentrics2,
const FVector3f& EdgeLengthsSqr,
float TriangleArea )
{
FVector3f LengthsSqr;
LengthsSqr[0] = LengthSquared( Barycentrics1, Barycentrics2, EdgeLengthsSqr );
LengthsSqr[1] = LengthSquared( Barycentrics2, Barycentrics0, EdgeLengthsSqr );
LengthsSqr[2] = LengthSquared( Barycentrics0, Barycentrics1, EdgeLengthsSqr );
float Area = SubtriangleArea( Barycentrics0, Barycentrics1, Barycentrics2, TriangleArea );
return 0.25f * ( -LengthsSqr.X + LengthsSqr.Y + LengthsSqr.Z ) / Area;
}
}
FORCEINLINE void SubtriangleBarycentrics( uint32 TriX, uint32 TriY, uint32 FlipTri, uint32 NumSubdivisions, FVector3f Barycentrics[3] )
{
/*
Vert order:
1 0__1
|\ \ |
| \ \ | <= flip triangle
|__\ \|
0 2 2
*/
uint32 VertXY[3][2] =
{
{ TriX, TriY },
{ TriX, TriY + 1},
{ TriX + 1, TriY },
};
VertXY[0][1] += FlipTri;
VertXY[1][0] += FlipTri;
for( int Corner = 0; Corner < 3; Corner++ )
{
Barycentrics[ Corner ][0] = (float)VertXY[ Corner ][0];
Barycentrics[ Corner ][1] = (float)VertXY[ Corner ][1];
Barycentrics[ Corner ][2] = float(NumSubdivisions - VertXY[ Corner ][0] - VertXY[ Corner ][1]);
Barycentrics[ Corner ] /= (float)NumSubdivisions;
}
}
// Find edge with opposite direction that shares these 2 verts.
/*
/\
/ \
o-<<-o
o->>-o
\ /
\/
*/
class FEdgeHash
{
public:
FHashTable HashTable;
FEdgeHash( int32 Num )
: HashTable( 1 << FMath::FloorLog2( Num ), Num )
{}
template< typename FGetPosition >
void Add_Concurrent( int32 EdgeIndex, FGetPosition&& GetPosition )
{
const FVector3f& Position0 = GetPosition( EdgeIndex );
const FVector3f& Position1 = GetPosition( Cycle3( EdgeIndex ) );
uint32 Hash0 = HashPosition( Position0 );
uint32 Hash1 = HashPosition( Position1 );
uint32 Hash = Murmur32( { Hash0, Hash1 } );
HashTable.Add_Concurrent( Hash, EdgeIndex );
}
template< typename FGetPosition, typename FuncType >
void ForAllMatching( int32 EdgeIndex, bool bAdd, FGetPosition&& GetPosition, FuncType&& Function )
{
const FVector3f& Position0 = GetPosition( EdgeIndex );
const FVector3f& Position1 = GetPosition( Cycle3( EdgeIndex ) );
uint32 Hash0 = HashPosition( Position0 );
uint32 Hash1 = HashPosition( Position1 );
uint32 Hash = Murmur32( { Hash1, Hash0 } );
for( uint32 OtherEdgeIndex = HashTable.First( Hash ); HashTable.IsValid( OtherEdgeIndex ); OtherEdgeIndex = HashTable.Next( OtherEdgeIndex ) )
{
if( Position0 == GetPosition( Cycle3( OtherEdgeIndex ) ) &&
Position1 == GetPosition( OtherEdgeIndex ) )
{
// Found matching edge.
Function( EdgeIndex, OtherEdgeIndex );
}
}
if( bAdd )
HashTable.Add( Murmur32( { Hash0, Hash1 } ), EdgeIndex );
}
};
struct FAdjacency
{
TArray< int32 > Direct;
TMultiMap< int32, int32 > Extended;
FAdjacency( int32 Num )
{
Direct.AddUninitialized( Num );
}
void Link( int32 EdgeIndex0, int32 EdgeIndex1 )
{
if( Direct[ EdgeIndex0 ] < 0 &&
Direct[ EdgeIndex1 ] < 0 )
{
Direct[ EdgeIndex0 ] = EdgeIndex1;
Direct[ EdgeIndex1 ] = EdgeIndex0;
}
else
{
Extended.AddUnique( EdgeIndex0, EdgeIndex1 );
Extended.AddUnique( EdgeIndex1, EdgeIndex0 );
}
}
template< typename FuncType >
void ForAll( int32 EdgeIndex, FuncType&& Function ) const
{
int32 AdjIndex = Direct[ EdgeIndex ];
if( AdjIndex >= 0 )
{
Function( EdgeIndex, AdjIndex );
}
for( auto Iter = Extended.CreateConstKeyIterator( EdgeIndex ); Iter; ++Iter )
{
Function( EdgeIndex, Iter.Value() );
}
}
};
} // namespace Nanite