255 lines
6.5 KiB
C++
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
|