FMeshSurfacePath에서 메쉬 표면 경로를 실제 메시에 반영하는 함수이다. AddViaPlanarWalk로 찾은 경로의 각 점을 실제 메시 정점으로 변환하여 메시 토폴로지를 수정한다. 경로상의 점들이 실제 메시의 정점이 되도록 엣지 분할(Edge Split)과 삼각형 찔러넣기(Triangle Poke)를 수행한다.
Input
| 타입 | 이름 | 설명 |
|---|---|---|
bool | bUpdatePath | Path 배열 업데이트 여부 (현재 미구현) |
TArray<int>& | PathVertices | 출력: 경로의 정점 ID들 |
bool | bDoNotDuplicateFirstVertexID | 첫 정점 중복 방지 여부 (여러 경로 연결 시 사용) |
double | SnapElementThresholdSq | 정점/엣지 스냅 임계값 (거리 제곱) |
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: 이 경우도 처리 필요
}
}중간 점들을 처리하면서 ESurfacePointType이 Edge타입인 경우 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 );
}
}중복 방지 로직: 다음 조건 중 하나라도 만족하면 정점 추가:
bDoNotDuplicateFirstVertexID = false(중복 허용)- 이번이 첫 정점이 아님
PathVertices가 비어있음- 마지막 정점과 다름
여러 경로 연결 예시:
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 중복 방지