On this page
article
Marching Squares
요약
Marching Squares의 기본 구현
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MarchingSquares.generated.h"
// vertex interpolation 예시용 좌표
struct FMarchingEdge
{
FVector2D Start;
FVector2D End;
};
UCLASS()
class AMarchingSquares : public AActor
{
GENERATED_BODY()
public:
AMarchingSquares();
protected:
virtual void BeginPlay() override;
private:
UPROPERTY()
class UProceduralMeshComponent* Mesh;
int32 GridWidth;
int32 GridHeight;
TArray<int32> GridData; // 1차원 배열로 2D 배열처럼 사용
float CellSize;
TMap<int32, TArray<FMarchingEdge>> EdgeTable;
void GenerateMarchingSquaresMesh();
};
#include "MarchingSquares.h"
#include "DrawDebugHelpers.h"
#include "ProceduralMeshComponent.h"
AMarchingSquares::AMarchingSquares()
{
PrimaryActorTick.bCanEverTick = true;
GridWidth = 10;
GridHeight = 10;
CellSize = 100.0f;
GridData.Init(0, GridWidth * GridHeight);
EdgeTable = {
{1, { { FVector2D(0, 0.5f), FVector2D(0.5f, 1) } }},
{2, { { FVector2D(0.5f, 1), FVector2D(1, 0.5f) } }},
{3, { { FVector2D(0, 0.5f), FVector2D(1, 0.5f) } }},
{4, { { FVector2D(1, 0.5f), FVector2D(0.5f, 0) } }},
{5, { { FVector2D(0, 0.5f), FVector2D(0.5f, 0) }, { FVector2D(0.5f,1), FVector2D(1,0.5f) } }},
{6, { { FVector2D(0.5f, 1), FVector2D(0.5f, 0) } }},
{7, { { FVector2D(0, 0.5f), FVector2D(0.5f, 0) } }},
{8, { { FVector2D(0.5f, 0), FVector2D(0, 0.5f) } }},
{9, { { FVector2D(0.5f, 1), FVector2D(0.5f, 0) } }},
{10, { { FVector2D(0, 0.5f), FVector2D(0.5f,1) }, { FVector2D(0.5f,0), FVector2D(1,0.5f) } }},
{11, { { FVector2D(1, 0.5f), FVector2D(0.5f, 0) } }},
{12, { { FVector2D(0, 0.5f), FVector2D(1, 0.5f) } }},
{13, { { FVector2D(0.5f, 1), FVector2D(1, 0.5f) } }},
{14, { { FVector2D(0, 0.5f), FVector2D(0.5f, 1) } }},
};
}
void AMarchingSquares::BeginPlay()
{
Super::BeginPlay();
// 랜덤 데이터 생성
for (int32 y = 0; y < GridHeight; ++y)
{
for (int32 x = 0; x < GridWidth; ++x)
{
int32 Index = y * GridWidth + x;
GridData[Index] = FMath::RandBool() ? 1 : 0;
if (GridData[Index] == 1)
{
FVector Pos = FVector(x * CellSize, y * CellSize, 0);
DrawDebugSphere(GetWorld(), Pos, 10.0f, 8, FColor::Blue, true, -1, 0);
}
}
}
// cell마다 caseIndex 계산
for (int32 y = 0; y < GridHeight - 1; ++y)
{
for (int32 x = 0; x < GridWidth - 1; ++x)
{
int32 topLeft = GridData[y * GridWidth + x];
int32 topRight = GridData[y * GridWidth + (x + 1)];
int32 bottomRight = GridData[(y + 1) * GridWidth + (x + 1)];
int32 bottomLeft = GridData[(y + 1) * GridWidth + x];
int32 caseIndex = 0;
if (topLeft == 1) caseIndex |= 8;
if (topRight == 1) caseIndex |= 4;
if (bottomRight == 1) caseIndex |= 2;
if (bottomLeft == 1) caseIndex |= 1;
FVector CellOrigin = FVector(x * CellSize, y * CellSize, 0);
// ✅ Cell 중앙 좌표
FVector CellCenter = CellOrigin + FVector(CellSize * 0.5f, CellSize * 0.5f, 0);
// ✅ Cell index 출력
DrawDebugString(GetWorld(), CellCenter, FString::Printf(TEXT("%d"), caseIndex), nullptr, FColor::White, 0.f, true);
// === ⭐️ Cell 경계 디버그 그리기 ===
FVector TL = CellOrigin;
FVector TR = CellOrigin + FVector(CellSize, 0, 0);
FVector BR = CellOrigin + FVector(CellSize, CellSize, 0);
FVector BL = CellOrigin + FVector(0, CellSize, 0);
DrawDebugLine(GetWorld(), TL, TR, FColor::Green, true, -1, 0, 1.0f);
DrawDebugLine(GetWorld(), TR, BR, FColor::Green, true, -1, 0, 1.0f);
DrawDebugLine(GetWorld(), BR, BL, FColor::Green, true, -1, 0, 1.0f);
DrawDebugLine(GetWorld(), BL, TL, FColor::Green, true, -1, 0, 1.0f);
if (EdgeTable.Contains(caseIndex))
{
for (const FMarchingEdge& Edge : EdgeTable[caseIndex])
{
FVector StartPos = CellOrigin + FVector(Edge.Start.X * CellSize, Edge.Start.Y * CellSize, 0);
FVector EndPos = CellOrigin + FVector(Edge.End.X * CellSize, Edge.End.Y * CellSize, 0);
DrawDebugLine(GetWorld(), StartPos, EndPos, FColor::Red, true, -1, 0, 5.0f);
}
}
}
}
// GenerateMarchingSquaresMesh();
}
void AMarchingSquares::GenerateMarchingSquaresMesh()
{
TArray<FVector> Vertices;
TArray<int32> Triangles;
TArray<FVector> Normals;
TArray<FVector2D> UVs;
TArray<FProcMeshTangent> Tangents;
TArray<FLinearColor> VertexColors;
int32 VertexIndex = 0;
for (int32 y = 0; y < GridHeight - 1; ++y)
{
for (int32 x = 0; x < GridWidth - 1; ++x)
{
int32 topLeft = GridData[y * GridWidth + x];
int32 topRight = GridData[y * GridWidth + (x + 1)];
int32 bottomRight = GridData[(y + 1) * GridWidth + (x + 1)];
int32 bottomLeft = GridData[(y + 1) * GridWidth + x];
int32 caseIndex = 0;
if (topLeft == 1) caseIndex |= 8;
if (topRight == 1) caseIndex |= 4;
if (bottomRight == 1) caseIndex |= 2;
if (bottomLeft == 1) caseIndex |= 1;
FVector CellOrigin = FVector(x * CellSize, y * CellSize, 0);
if (EdgeTable.Contains(caseIndex))
{
for (const FMarchingEdge& Edge : EdgeTable[caseIndex])
{
FVector Start = CellOrigin + FVector(Edge.Start.X * CellSize, Edge.Start.Y * CellSize, 0);
FVector End = CellOrigin + FVector(Edge.End.X * CellSize, Edge.End.Y * CellSize, 0);
// 선분을 두께가 있는 quad로 만듦
FVector Dir = (End - Start).GetSafeNormal();
FVector Right = FVector::CrossProduct(Dir, FVector(0, 0, 1));
float Thickness = 5.0f;
FVector A = Start + Right * Thickness;
FVector B = Start - Right * Thickness;
FVector C = End - Right * Thickness;
FVector D = End + Right * Thickness;
// 두 삼각형 추가 (A, B, C)와 (C, D, A)
Vertices.Append({ A, B, C, D });
Triangles.Append({ VertexIndex, VertexIndex + 1, VertexIndex + 2,
VertexIndex + 2, VertexIndex + 3, VertexIndex });
for (int i = 0; i < 4; ++i)
{
Normals.Add(FVector(0, 0, 1));
UVs.Add(FVector2D(0, 0));
Tangents.Add(FProcMeshTangent(1, 0, 0));
VertexColors.Add(FLinearColor::White);
}
VertexIndex += 4;
}
}
}
}
Mesh->CreateMeshSection_LinearColor(0, Vertices, Triangles, Normals, UVs, VertexColors, Tangents, true);
}