FWN을 빠르게 계산하기 위해 사용하는 클래스. 테일러 전개를 통한 근사값을 이용해 삼각형들을 그룹으로 처리하여 빠르게 FWN을 계산한다.



Input


template< class TriangleMeshType >
class TFastWindingTree
{
	TMeshAABBTree3< TriangleMeshType >* Tree;
};


Output


struct FWNInfo
{
    FVector3d Center;
    double R;
    FVector3d Order1Vec;
    FMatrix3d Order2Mat;
};
 
TMap< int, FWNInfo > FastWindingCache;

생성되는 FastWindingCache는 테일러 전개를 이용하기 위한 계수들의 구조체FWNInfo맵이다.



Property


프로퍼티설명
FastWindingCacheMeshChangeStampFWN을 구축하는 메쉬의 변경 여부를 추적하기 위한 TimeStamp


Algorithm


Build



1. FMeshTriInfoCache 생성

FMeshTriInfoCache TriCache = FMeshTriInfoCache::BuildTriInfoCache( *Tree->Mesh );

각 삼각형을 처리하기 위해 우선적으로 FMeshTriInfoCache를 생성한다.


2. 트리의 재귀 순회

int build_fast_winding_cache( int IBox, int Depth, int TriCountThresh, TOptional< TSet< int > >& TriHash, const FMeshTriInfoCache& TriCache )
{
    if ( idx < Tree->TrianglesEnd )
    {
        // 리프 노드: 삼각형 개수만 반환
        int num_tris = Tree->IndexList[ idx ];
        return num_tris;
    }
    else
    {
        // 내부 노드: 자식들 처리
        if ( iChild1 < 0 )
         {
            // 1개 자식
            int num_child_tris = build_fast_winding_cache( iChild1, Depth + 1, TriCountThresh, TriHash, TriCache );
            
            return num_child_tris;
        }
        else 
        {
            // 2개 자식
            int num_tris_1 = build_fast_winding_cache( iChild1, Depth + 1, TriCountThresh, TriHash,    TriCache );            
            int num_tris_2 = build_fast_winding_cache( iChild2, Depth + 1, TriCountThresh, child2_hash, TriCache );
 
            bool build_cache = ( num_tris_1 + num_tris_2 > TriCountThresh );
 
            if ( build_cache ) 
            {
                // 충분한 삼각형이 있으면 캐시 생성
                collect_triangles( iChild1, TriHash.GetValue() );  // 모든 하위 삼각형 수집
                collect_triangles( iChild2, TriHash.GetValue() );
                
                make_box_fast_winding_cache( IBox, TriHash.GetValue(), TriCache );
            }
 
            return ( num_tris_1 + num_tris_2 );
        }
    }
}

트리의 노드를 재귀 순회 하면서 FastWindingCache를 생성한다.


3. 테일러 전개를 위한 계수 계산

void make_box_fast_winding_cache( int IBox, const TSet< int >& Triangles, const FMeshTriInfoCache& TriCache )
	{
		check(FastWindingCache.Find(IBox) == nullptr);
 
		// construct cache
		FWNInfo cacheInfo;
		FastTriWinding::ComputeCoeffs
		(
    		*Tree->Mesh, 
    		Triangles, TriCache, 
    		cacheInfo.Center, 
    		cacheInfo.R, 
    		cacheInfo.Order1Vec, 
    		cacheInfo.Order2Mat
        );
 
		FastWindingCache.Add( IBox, cacheInfo );
	}

FastTriWinding::ComputeCoeffs()를 통해 계산된 계수들은 FWNInfo에 캐슁된다.
각 계수들은 다음과 같은 의미를 가진다.

  • P : 전개 중심점
  • R : 전개 반지름 (모든 정점 중 P로부터 최대 거리)
  • Order1 : 1차 계수 벡터 =
  • Order2 : 2차 계수 행렬 =

중심점 P의 계산
// 면적 가중 중심점 계산 (Expansion Center)
FVector3d P = FVector3d::Zero();
double sum_area = 0;
 
for ( int tid : Triangles )
{
    double Area = TriCache.Areas[ tid ];
    sum_area += Area;
    P += Area * TriCache.Centroids[ tid ];  // 면적 × 중심점
}
 
P /= sum_area;  // P = 면적 가중 평균 중심점

반지름 R, 1차 계수 벡터 Order1, 2차 계수 벡터 Order2의 계산
FVector3d Order1;
FMatrix3d Order2;
double RSq;
 
for ( int tid : Triangles )
{
    Mesh.GetTriVertices( tid, P0, P1, P2 );
    TriCache.GetTriInfo( tid, n, a, c ); // 법선, 면적, 중심점
 
    Order1 += a * n; // 1차 계수: 면적 × 법선 벡터
 
    FVector3d dcp = c - P; // 삼각형 중심점 - 전개 중심점
    Order2 += a * FMatrix3d( dcp, n ); // 2차 계수: 면적 × 외적 행렬
 
    // 반지름 R 계산 (최대 거리)
    double MaxDistSq = FMath::Max3( DistanceSquared( P0, P ), DistanceSquared( P1, P ), DistanceSquared( P2, P ) );
    RSq = FMath::Max( RSq, MaxDistSq );
}
 
R = FMath::Sqrt( RSq );


FastWindingNumber



1. 트리의 재귀 순회

double branch_fast_winding_num( int IBox, FVector3d P ) const
{
    double branch_sum = 0;
    int idx = Tree->BoxToIndex[ IBox ];
 
    if ( idx < Tree->TrianglesEnd ) // 🍃 리프 노드: 정확한 solid angle 계산
    {
        int num_tris = Tree->IndexList[ idx ];
        
        for ( int i = 1; i <= num_tris; ++i )
        {
            FVector3d a, b, c;
            
            int ti = Tree->IndexList[ idx + i ];            
            Tree->Mesh->GetTriVertices( ti, a, b, c );
            
            double angle = VectorUtil::TriSolidAngle( a, b, c, P );  // 정확한 계산
            branch_sum += angle / FMathd::FourPi;
        }
    }
    else // 🌳 내부 노드: 캐시 사용 또는 재귀 호출
    {
        if ( iChild1 < 0 ) 
        {
            // 1개 자식
            iChild1 = (-iChild1) - 1;
            bool contained = Tree->box_contains( iChild1, P );
 
            if ( contained == false && can_use_fast_winding_cache( iChild1, P ) )
            {
                // ⚡ 캐시 사용 (빠른 근사)
                branch_sum += evaluate_box_fast_winding_cache( iChild1, P );
            }
            else
            {
                // 🔄 재귀 호출 (정확한 계산)
                branch_sum += branch_fast_winding_num( iChild1, P );
            }
        }
        else
        {
            // 2개 자식: 각각 처리
            // ... 유사한 로직
        }
    }
    return branch_sum;
}

메쉬를 이루는 TMeshAABBTree3의 노드를 재귀 순회 하면서 FWN을 계산한다. 이 때 테일러 전개를 통한 근사값으로 계산할 것인지 각 삼각형에 대해 정확하게 계산할 것인지 분기하게 된다.


2. 테일러 전개를 통한 근사값으로 FWN을 계산할지 여부

bool can_use_fast_winding_cache( int IBox, const FVector3d& Q ) const
{
    const FWNInfo* cacheInfo = FastWindingCache.Find( IBox );
    if ( !cacheInfo )
    {
        return false;  // 캐시가 없음
    }
 
    double dist_qp = Distance( cacheInfo->Center, Q );
    
    if ( dist_qp > FWNBeta * cacheInfo->R ) // 충분히 멀리 있음
    {
        return true;   
    }
    
    return false; // 너무 가까움
}
  • FWNBeta : 안전 계수 ( 기본값 : 2.0 )
  • cacheInfo->R : 삼각형 그룹의 반지름

점 Q의 거리가 충분히 멀면 테일러 전개를 통한 근사값으로 FWN을 계산한다. 반대로 가깝다면 직접 삼각형 그룹과의 거리를 정확하게 계산한다.


3. 테일러 전개를 통한 근사값 계산

1 / 2차 근사 분기
double evaluate_box_fast_winding_cache( int IBox, const FVector3d& Q ) const
{
    const FWNInfo& cacheInfo = FastWindingCache[ IBox ];
 
    if ( FWNApproxOrder == 2 )
    {
        // 2차 근사 (더 정확)
        return FastTriWinding::EvaluateOrder2Approx( cacheInfo.Center, cacheInfo.Order1Vec, cacheInfo.Order2Mat, Q );
    }
    else
    {
        // 1차 근사 (더 빠름)
        return FastTriWinding::EvaluateOrder1Approx( cacheInfo.Center, cacheInfo.Order1Vec, Q );
    }
}

1차 근사 공식 함수
inline double EvaluateOrder1Approx( const FVector3d& Center, const FVector3d& Order1Coeff, const FVector3d& Q )
{
    FVector3d dpq = Center - Q;
    double len = dpq.Length();
    
    return ( 1.0 / FMathd::FourPi ) * Order1Coeff.Dot( dpq / ( len * len * len ) );
}


2차 근사 공식 함수
inline double EvaluateOrder2Approx(const FVector3d& Center, const FVector3d& Order1Coeff, const FMatrix3d& Order2Coeff, const FVector3d& Q )
{
    FVector3d dpq = Center - Q;
    
    double len  = dpq.Length();
    double len3 = len * len * len;
    
    double fourPi_len3 = 1.0 / ( FMathd::FourPi * len3 );
 
    // 1차 항
    double Order1 = fourPi_len3 * Order1Coeff.Dot( dpq );
 
    // 2차 Hessian 계산
    double c = -3.0 / ( FMathd::FourPi * len3 * len * len );
    
    FMatrix3d hessian
    (
        fourPi_len3 + c * dpq.X * dpq.X, c * dpq.X * dpq.Y,               c * dpq.X * dpq.Z,
        c * dpq.Y * dpq.X,               fourPi_len3 + c * dpq.Y * dpq.Y, c * dpq.Y * dpq.Z,
        c * dpq.Z * dpq.X,               c * dpq.Z * dpq.Y,               fourPi_len3 + c * dpq.Z * dpq.Z
    );
 
    double Order2 = Order2Coeff.InnerProduct( hessian );
 
    return Order1 + Order2;
}

전개 중심점 에서 멀리 떨어진 점 에 대해 근사 계산을 수행하여 계산 속도를 향상시킨다.