본문 바로가기
개발/Unity

유니티 - triangles로 다각형의 원래 좌표 복구하기 (Recover the Original Coordinates of a Polygon with Triangles)

by 피로물든딸기 2022. 11. 8.
반응형

Unity 전체 링크

 

절차적 메시와 삼각분할로 임의의 다각형을 만들고 저장하자.

MyMesh.asset
0.00MB

 

빈 오브젝트에 Mesh Filter와 Mesh Renderer를 추가한 후 MyMesh를 추가해보자.

 

이제 위의 오브젝트에 아래의 스크립트를 추가하고 실행하자.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MakeClockWiseCoordinate : MonoBehaviour
{
    Mesh mesh;
    List<int> triangles = new List<int>();

    void init()
    {
        mesh = this.GetComponent<MeshFilter>().mesh;
        vertices = new List<Vector3>(mesh.vertices);
        triangles = new List<int>(mesh.triangles);
    }

    void makeSphereForVertices(List<Vector3> positions)
    {
        int index = 0;
        foreach(Vector3 v in positions)
        {
            GameObject go = GameObject.CreatePrimitive(PrimitiveType.Sphere);
            go.name = index.ToString();
            go.transform.position = v;
            go.transform.localScale = new Vector3(1.0f, 1.0f, 1.0f) * 5.0f;
            index++;
        }
    }

    void Start()
    {
        init();
        makeSphereForVertices(vertices);
    }
}

 

다각형의 좌표인 vertices 순서대로 sphere가 생성된다.

 

위의 다각형을 점의 순서대로 체크하면 아래와 같다.

vertices 좌표가 시계방향으로 정렬되어 있다.


시계방향으로 정렬되어 있지 않은 Mesh의 vertices

 

다시 아래의 Mesh를 설정해서 코드를 그대로 실행해보자.

AnotherMesh.asset
0.00MB

 

처음과 달리 정점의 순서가 시계방향이 아니고, 순서도 엉망이다.

 

번호를 매겨보면 다음과 같다.

 

같은 모양의 Mesh라도 vertices 순서는 다를 수 있다.

왜냐하면 vertices의 순서는 중요하지 않고, vertices를 보고 있는 triangles를 보고 메시를 그리기 때문인다.

 

여기서 triangles의 좌표만 보고 위의 vertices를 시계방향으로 정렬해보자.

(ex, 0 > 1 > 3 > 4 > 8 > 7 > 2 > 5 > 9 > 6이 return 되도록 해보자.)


알고리즘 원리

 

먼저 mesh의 triangles를 출력해보자.

    void showTriangles(List<int> triangles)
    {
        string log = string.Empty;
        for(int i = 0; i < triangles.Count; i++)
        {
            log += triangles[i] + ", ";
            
            if(i % 3 == 2)
            {
                Debug.Log(log);
                log = string.Empty;
            }
        }
    }

 

현재 보이는 방향(시계방향) 순서대로 8개로 삼각형이 분할되어 있으므로 8개의 좌표를 얻는다.

5, 9, 6
5, 6, 0
5, 0, 1
3, 4, 8
3, 8, 7
1, 3, 7
1, 7, 2
5, 1, 2

 

다시 그림을 보면, 5 → 9 → 6다각형을 만드는 시계 방향의 좌표 중 일부이지만,

3 → 8 → 7은 중 3 → 8은 내부의 선이므로 불필요한 정보다.

 

하지만 3 → 8 → 73 → 8 4 → 8 → 3 (◀ 3 → 4 → 8 과 동일)의 8 → 3 과 쌍을 이룬다.

즉, 다각형 외곽의 시계방향을 만드는 선들은 반드시 한 짝이고, 내부의 선들은 한 쌍이다.

 

따라서 알고리즘은 triangles를 분석하여 쌍을 이루는 정보를 지우고, 남은 정보로 index를 정렬한다.


구현

 

triangles 좌표와 vertices의 length로 시계방향 index를 구한다.

List<int> getClockWiseCoordinates(List<int> triangles, int vLength)

 

coordinates는 return할 list이며, mapping은 triangles를 2차원 배열로 mapping할 배열이다.

mapping1D는 쌍을 지운 mapping의 결과를 정리한다.

mapping1D[0]에는 0 다음에 어떤 숫자가 오는지 저장되어있다. (위의 그림대로면 0 다음은 1이다.)

    List<int> coordinates = new List<int>();
    int[,] mapping = new int[vLength, vLength];
    int[] mapping1D = new int[vLength];

 

triangles를 3개씩 순회하면서 mapping에 체크한다. 

    for (int i = 0; i < triangles.Count; i += 3)
    {
        int t0 = triangles[i + 0];
        int t1 = triangles[i + 1];
        int t2 = triangles[i + 2];

        mapping[t0,t1]++;
        mapping[t1,t2]++;
        mapping[t2,t0]++;
    }

 

예를 들어 3 → 8 → 7 의 경우 mapping[3, 8] = 1이 되지만, 반드시 mapping[8, 3]도 1이 될 것이다.

그런데 mapping[8, 7] = 1이지만, mapping[7, 8] = 0일 것이므로 8 → 7을 찾을 수 있게 된다.

 

mapping이 완료되면 위에 설명한대로 짝이 존재하는 경우를 지운다.

    for(int i = 0; i < vLength; i++)
    {
        for(int k = 0; k < vLength; k++)
        {
            if (mapping[i, k] == 1 && mapping[k, i] == 1)
                mapping[i, k] = mapping[k, i] = 0;
        }
    }

 

짝을 찾았으면 mapping1D에 답을 넣어준다.

    for(int i = 0; i < vLength; i++)
    {
        for(int k = 0; k < vLength; k++)
        {
            if(mapping[i, k] == 1)
            {
                mapping1D[i] = k;
                break;
            }
        }
    }

 

답을 넣었으니 0번 인덱스를 시작으로 순서대로 집어넣는다.

    int start = 0;
    coordinates.Add(start);
    for(int i = 0; i < vLength - 1; i++)
    {
        coordinates.Add(mapping1D[start]);
        start = mapping1D[start];
    }

 

위의 코드를 실행하면 출력 결과는 아래와 같다.

 

0 > 1 > 3 > 4 > 8 > 7 > 2 > 5 > 9 > 6 순서대로 좌표를 보면 아래와 같이 시계방향인 것을 알 수 있다. 


전체 코드는 다음과 같다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MakeClockWiseCoordinate : MonoBehaviour
{
    Mesh mesh;
    List<Vector3> vertices = new List<Vector3>();
    List<int> triangles = new List<int>();
    List<int> clockwiseCoordinates;

    void init()
    {
        mesh = this.GetComponent<MeshFilter>().mesh;
        vertices = new List<Vector3>(mesh.vertices);
        triangles = new List<int>(mesh.triangles);
    }

    void makeSphereForVertices(List<Vector3> positions)
    {
        int index = 0;
        foreach(Vector3 v in positions)
        {
            GameObject go = GameObject.CreatePrimitive(PrimitiveType.Sphere);
            go.name = index.ToString();
            go.transform.position = v;
            go.transform.localScale = new Vector3(1.0f, 1.0f, 1.0f) * 5.0f;
            index++;
        }
    }

    List<int> getClockWiseCoordinates(List<int> triangles, int vLength)
    {
        List<int> coordinates = new List<int>();
        int[,] mapping = new int[vLength, vLength];
        int[] mapping1D = new int[vLength];

        for (int i = 0; i < triangles.Count; i += 3)
        {
            int t0 = triangles[i + 0];
            int t1 = triangles[i + 1];
            int t2 = triangles[i + 2];

            mapping[t0,t1]++;
            mapping[t1,t2]++;
            mapping[t2,t0]++;
        }

        for(int i = 0; i < vLength; i++)
        {
            for(int k = 0; k < vLength; k++)
            {
                if (mapping[i, k] == 1 && mapping[k, i] == 1)
                    mapping[i, k] = mapping[k, i] = 0;
            }
        }      

        {
            for(int i = 0; i < vLength; i++)
            {
                string log = string.Empty;
                for (int k = 0; k < vLength; k++)
                    log += mapping[i, k].ToString() + " ";

                Debug.Log(log);
            }
        }
       
        for(int i = 0; i < vLength; i++)
        {
            for(int k = 0; k < vLength; k++)
            {
                if(mapping[i, k] == 1)
                {
                    mapping1D[i] = k;
                    break;
                }
            }
        }

        int start = 0;
        coordinates.Add(start);
        for(int i = 0; i < vLength - 1; i++)
        {
            coordinates.Add(mapping1D[start]);
            start = mapping1D[start];
        }

        return new List<int>(coordinates);
    }

    void showTriangles(List<int> triangles)
    {
        string log = string.Empty;
        for(int i = 0; i < triangles.Count; i++)
        {
            log += triangles[i] + ", ";
            
            if(i % 3 == 2)
            {
                Debug.Log(log);
                log = string.Empty;
            }
        }
    }

    void Start()
    {
        init();
        makeSphereForVertices(vertices);

        showTriangles(triangles);

        clockwiseCoordinates = getClockWiseCoordinates(triangles, vertices.Count);

        string log = "Clockwise : ";
        foreach (int c in clockwiseCoordinates)
            log += c.ToString() + " ";

        Debug.Log(log);
    }
}

 

위의 실행결과는 아래의 unitypackage에서 확인 가능하다.

ReverseTriangulation.unitypackage
0.01MB

 

Unity Plus:

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Unity Pro:

 

Unity Pro

The complete solutions for professionals to create and operate.

unity.com

 

Unity 프리미엄 학습:

 

Unity Learn

Advance your Unity skills with live sessions and over 750 hours of on-demand learning content designed for creators at every skill level.

unity.com

반응형

댓글