FMeshSurfacePath에서 메쉬 표면 경로를 실제 메시에 반영하는 함수이다. AddViaPlanarWalk로 찾은 경로의 각 점을 실제 메시 정점으로 변환하여 메시 토폴로지를 수정한다. 경로상의 점들이 실제 메시의 정점이 되도록 엣지 분할(Edge Split)과 삼각형 찔러넣기(Triangle Poke)를 수행한다.



Input


타입이름설명
boolbUpdatePathPath 배열 업데이트 여부 (현재 미구현)
TArray<int>&PathVertices출력: 경로의 정점 ID들
boolbDoNotDuplicateFirstVertexID첫 정점 중복 방지 여부 (여러 경로 연결 시 사용)
doubleSnapElementThresholdSq정점/엣지 스냅 임계값 (거리 제곱)


Simple Path 요구사항


이 함수는 Simple Path에 대해서만 동작한다. SimplePath는 다음과 같이 정의한다.

  • 경로가 정점과 엣지만 교차 (중간 점이 삼각형 내부에 있으면 안 됨)
  • 단, 시작점과 끝점만 예외적으로 삼각형 내부 가능
  • 삼각형은 한 번만 지나감 (중복 방문 없음)
  • Planar Walk로 만든 경로는 자동으로 이 조건 만족```
처리 방식
  • 엣지를 교차하는 점 → 엣지 분할 (Edge Split)
  • 삼각형 내부 점 → 삼각형 찔러넣기 (Triangle Poke)
  • 기존 정점 → 그대로 사용


초기화


기본 검증 및 변수 초기화

int32 InitialPathIdx = PathVertices.Num();  // 기존 경로 끝 위치 저장
 
if ( !Path.Num() )
{
    return true;  // 빈 경로는 성공
}
 
int32 PathNum = Path.Num();
const FMeshSurfacePoint& OrigEndPt = Path[ PathNum - 1 ].Key;  // 마지막 점

끝점 특수 처리 플래그

int StartProcessIdx = 0;                    // 처리 시작 인덱스
int EndSimpleProcessIdx = PathNum - 1;      // 처리 끝 인덱스
bool bEndPointSpecialProcess = false;
 
// 끝점이 삼각형 내부에 있으면 나중에 따로 처리
if ( PathNum > 1 && OrigEndPt.PointType == ESurfacePointType::Triangle )
{
    EndSimpleProcessIdx = PathNum - 2;  // 끝점 직전까지만 처리
    bEndPointSpecialProcess = true;
}
 
FMeshSurfacePoint EndPtUpdated = Path.Last().Key;
FVector3d EndPtPos = OrigEndPt.Pos( Mesh );  // 끝점의 3D 좌표

끝점을 따로 처리하는 이유:

  • 중간 작업(엣지 분할 등)으로 끝점이 속한 삼각형이 변경될 수 있음
  • 끝점 위치를 계속 추적하고 마지막에 재배치 필요


시작점 처리


if ( Path[ 0 ].Key.PointType == ESurfacePointType::Triangle )
{
    // 시작점이 삼각형 내부 → Triangle Poke
    FDynamicMesh3::FPokeTriangleInfo PokeInfo;
    Mesh->PokeTriangle
    (
        Path[ 0 ].Key.ElementID,    // 삼각형 ID
        Path[ 0 ].Key.BaryCoord,    // 무게중심 좌표
        PokeInfo                    // 결과 정보
    );
 
    // 끝점이 같은 삼각형에 있었다면 재배치 필요
    if ( EndPtUpdated.PointType == ESurfacePointType::Triangle && Path[ 0 ].Key.ElementID == EndPtUpdated.ElementID )
    {
        EndPtUpdated = RelocateTrianglePointAfterRefinement
        (
            Mesh,
            EndPtPos,
            { 
                PokeInfo.NewTriangles.A, 
                PokeInfo.NewTriangles.B, 
                PokeInfo.OriginalTriangle 
            },
            SnapElementThresholdSq
        );
    }
 
    PathVertices.Add( PokeInfo.NewVertex );  // 새 정점 추가
    StartProcessIdx = 1;  // 다음 점부터 처리
}

시작점이 삼각형 내부에 있는 타입이라면 FDynamicMesh3::PokeTriangle()를 통해 삼각형 내에 정점 추가를 한다.



메인 루프: 중간 점들 처리


루프 구조

for ( int32 PathIdx = StartProcessIdx; PathIdx <= EndSimpleProcessIdx; PathIdx++ )
{
    // 중간 점이 삼각형 내부면 에러!
    if ( !ensure( Path[ PathIdx ].Key.PointType != ESurfacePointType::Triangle ) )
    {
        return false;  // Simple Path 조건 위배
    }
 
    const FMeshSurfacePoint& Pt = Path[ PathIdx ].Key;
 
    // 엣지 교차 또는 정점 통과 처리
}

엣지 교차 처리

if ( Pt.PointType == ESurfacePointType::Edge )
{
    ensure( Mesh->IsEdge( Pt.ElementID ) );
 
    // 엣지 분할 (Edge Split)
    FDynamicMesh3::FEdgeSplitInfo SplitInfo;
    Mesh->SplitEdge
    (
        Pt.ElementID,       // 엣지 ID
        SplitInfo,          // 결과 정보
        Pt.BaryCoord[ 0 ]   // 분할 위치 (0~1)
    );
 
    PathVertices.Add( SplitInfo.NewVertex );  // 새 정점 추가
 
    // 끝점이 영향받은 삼각형에 있었는지 확인
    if ( EndPtUpdated.PointType == ESurfacePointType::Triangle && SplitInfo.OriginalTriangles.Contains( EndPtUpdated.ElementID ) )
    {
        // 끝점 재배치
        TArray< int > TriInds = { EndPtUpdated.ElementID };
        if ( SplitInfo.OriginalTriangles.A == EndPtUpdated.ElementID )
        {
            TriInds.Add( SplitInfo.NewTriangles.A );
        }
        else
        {
            TriInds.Add( SplitInfo.NewTriangles.B );
        }
 
        EndPtUpdated = RelocateTrianglePointAfterRefinement
        (
            Mesh, EndPtPos, TriInds, SnapElementThresholdSq
        );
    }
    // 끝점이 같은 엣지에 있었다면 문제!
    else if ( PathIdx != PathNum - 1 &&
             EndPtUpdated.PointType == ESurfacePointType::Edge &&
             Pt.ElementID == EndPtUpdated.ElementID )
    {
        ensure( false );  // TODO: 이 경우도 처리 필요
    }
}

중간 점들을 처리하면서 ESurfacePointTypeEdge타입인 경우 FDynamicMesh3::SplitEdge()를 통해 정점 추가를 한다. 그리이 추가된 정점을 통해 끝점이 영향받는 삼각형에 변경이 있었는지 확인 및 처리를 한다.


정점 통과 처리

else
{
    ensure( Pt.PointType == ESurfacePointType::Vertex );
    ensure( Mesh->IsVertex( Pt.ElementID ) );
 
    // 기존 정점 그대로 사용 (단, 중복 방지 체크)
    if ( !bDoNotDuplicateFirstVertexID ||
         PathVertices.Num() != InitialPathIdx ||
         PathVertices.Num() == 0 ||
         PathVertices.Last() != Pt.ElementID )
    {
        PathVertices.Add( Pt.ElementID );
    }
}

중복 방지 로직: 다음 조건 중 하나라도 만족하면 정점 추가:

  1. bDoNotDuplicateFirstVertexID = false (중복 허용)
  2. 이번이 첫 정점이 아님
  3. PathVertices가 비어있음
  4. 마지막 정점과 다름

여러 경로 연결 예시:

Path1: [A, B, C]
Path2: [C, D, E]  ← C가 중복
결과: [A, B, C, D, E]  (C 한 번만)


끝점 특수 처리


if ( bEndPointSpecialProcess )
{
    if ( EndPtUpdated.PointType == ESurfacePointType::Triangle )
    {
        // 끝점이 삼각형 내부 → Triangle Poke
        FDynamicMesh3::FPokeTriangleInfo PokeInfo;
        Mesh->PokeTriangle( EndPtUpdated.ElementID, EndPtUpdated.BaryCoord, PokeInfo );
        PathVertices.Add( PokeInfo.NewVertex );
    }
    else if ( EndPtUpdated.PointType == ESurfacePointType::Edge )
    {
        // 끝점이 엣지 위 → Edge Split
        FDynamicMesh3::FEdgeSplitInfo SplitInfo;
        Mesh->SplitEdge( EndPtUpdated.ElementID, SplitInfo, EndPtUpdated.BaryCoord[ 0 ] );
        PathVertices.Add( SplitInfo.NewVertex );
    }
    else
    {
        // 끝점이 정점 → 그대로 추가
        if ( PathVertices.Num() == 0 || PathVertices.Last() != EndPtUpdated.ElementID )
        {
            PathVertices.Add( EndPtUpdated.ElementID );
        }
    }
}

EndPtUpdated를 사용하는 이유:

  • 중간 과정에서 엣지 분할/삼각형 Poke로 메시가 변경됨
  • 원래 끝점(OrigEndPt)이 속한 삼각형이 여러 개로 쪼개졌을 수 있음
  • RelocateTrianglePointAfterRefinement로 계속 업데이트된 위치 추적


후처리


if ( bUpdatePath )
{
    ensure( false );  // TODO: 아직 미구현
    // Path 배열을 새 정점 위치로 업데이트하는 기능
}
 
return true;  // 성공!


전체 흐름 예시


입력: Path (AddViaPlanarWalk 결과)

Path[0]: Triangle(T1, bary=(0.5, 0.3, 0.2))  ← 삼각형 내부
Path[1]: Edge(E1, t=0.4)                     ← 엣지 교차
Path[2]: Vertex(V5)                          ← 정점 통과
Path[3]: Edge(E3, t=0.7)                     ← 엣지 교차
Path[4]: Triangle(T8, bary=(0.2, 0.6, 0.2))  ← 삼각형 내부

처리 과정

Step 1: Path[0] 처리
  T1.Poke() → NewVertex = V100
  PathVertices = [V100]

Step 2: Path[1] 처리
  E1.Split(0.4) → NewVertex = V101
  PathVertices = [V100, V101]

Step 3: Path[2] 처리
  기존 정점 사용
  PathVertices = [V100, V101, V5]

Step 4: Path[3] 처리
  E3.Split(0.7) → NewVertex = V102
  PathVertices = [V100, V101, V5, V102]

Step 5: Path[4] 처리 (특수)
  T8.Poke() → NewVertex = V103
  PathVertices = [V100, V101, V5, V102, V103]

출력: PathVertices

PathVertices = [V100, V101, V5, V102, V103]
                  ↓     ↓    ↓    ↓     ↓
모든 점이 실제 메시의 정점 ID
인접한 정점들은 엣지로 연결됨


핵심 포인트


1. 끝점 추적의 중요성

문제 상황:

시작: Path[0]이 T1 내부
      Path[4]도 T1 내부 (같은 삼각형!)

Step 1: T1.Poke() 실행
        T1이 T1a, T1b, T1c로 분할됨

문제: Path[4]가 T1, T1a, T1b, T1c 중 어디에?
해결: RelocateTrianglePointAfterRefinement로 추적

2. Simple Path의 제약

✅ OK: Simple Path
시작(Triangle) → Edge → Vertex → Edge → 끝(Triangle)

❌ Error: NOT Simple Path
시작(Triangle) → Triangle → Edge → ...
                  ↑ 중간에 삼각형 내부 교차 불가!

3. 중복 방지의 필요성

// 여러 경로를 이어붙일 때
Path1.EmbedSimplePath( false, PathVertices, true );
// PathVertices = [V1, V2, V3]
 
Path2.EmbedSimplePath( false, PathVertices, true );
// PathVertices = [V1, V2, V3, V4, V5]
//                           ↑ V3 중복 방지