허용 오차 계산
double DegenerateEdgeTolSq = DegenerateEdgeTolFactor * DegenerateEdgeTolFactor *
SnapTolerance * SnapTolerance; 최적화를 위해 squared 기준으로 연산한다.
짧은 엣지 찾기
TArray< int > EIDs;
// 절단으로 새로 생성된 버텍스 체인들을 순회
for ( int ChainIdx = 0; ChainIdx < Cut.VertexChains[ MeshIdx ].Num(); )
{
int ChainLen = Cut.VertexChains[ MeshIdx ][ ChainIdx ]; // 체인 길이
int ChainEnd = ChainIdx + 1 + ChainLen; // 체인 끝 위치
// 체인 내 연속된 버텍스 쌍들 검사
for ( int ChainSubIdx = ChainIdx + 1; ChainSubIdx + 1 < ChainEnd; ChainSubIdx++ )
{
int VID[ 2 ]
{
Cut.VertexChains[ MeshIdx ][ ChainSubIdx ],
Cut.VertexChains[ MeshIdx ][ ChainSubIdx + 1 ]
};
float distanceSquared = DistanceSquared
(
CutMesh[ MeshIdx ]->GetVertex( VID[ 0 ] ),
CutMesh[ MeshIdx ]->GetVertex( VID[ 1 ] )
)
// 두 버텍스 사이 거리가 허용 오차보다 작으면 퇴화 에지로 판정
if ( distanceSquared < DegenerateEdgeTolSq )
{
EIDs.Add( CutMesh[ MeshIdx ]->FindEdge( VID[ 0 ], VID[ 1 ] ) );
}
}
ChainIdx = ChainEnd;
} 엣지 병합
TSet< int > AllEIDs( EIDs );
for ( int Idx = 0; Idx < EIDs.Num(); Idx++ )
{
int EID = EIDs[ Idx ];
if ( !CutMesh[ MeshIdx ]->IsEdge( EID ) ) // 이미 제거된 에지
{
continue;
}
FVector3d A, B;
CutMesh[ MeshIdx ]->GetEdgeV( EID, A, B );
if (DistanceSquared( A, B ) > DegenerateEdgeTolSq ) // 재검사
{
continue;
}
FIndex2i EV = CutMesh[ MeshIdx ]->GetEdgeV( EID );
// UV Seam에 있는 버텍스는 함부로 제거하면 안됨
if ( CutMesh[ MeshIdx ]->HasAttributes() &&
CutMesh[ MeshIdx ]->Attributes()->IsSeamVertex( EV.B, false ) )
{
Swap( EV.A, EV.행
FDynamicMesh3::FEdgeCollapseInfo CollapseInfo;
EMeshResult CollapseResult = CutMesh[ MeshIdx ]->CollapseEdge( EV.A, EV.B, .5, CollapseInfo );
if ( CollapseResult == EMeshResult::Ok )
{
// 성공시 새로 생성된 엣지들도 후보에 추가
for ( int i = 0; i < 2; i++ )
{
if ( AllEIDs.Contains( CollapseInfo.RemovedEdges[ i ] ) )
{
int ToAdd = CollapseInfo.KeptEdges[ i ];
bool bWasPresent;
AllEIDs.Add( ToAdd, &bWasPresent );
if ( !bWasPresent )
{
EIDs.Add( ToAdd ); // 새 엣지도 검사 대상에 추가
}
}
}
}
}