요약

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);
}