본문 바로가기
개발/Unity

유니티 - 절차적 메시로 만든 정다각형의 삼각분할 (Regular Polygon Triangulation with Procedural Mesh)

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

Unity 전체 링크

 

절차적 메시로 정다각형을 만들면 아래와 같은 결과를 얻는다.

 

그런데 N각형의 삼각형은 N - 2개면 충분한다.

위의 경우는 코드를 쉽게 적용하기 위해 N개의 삼각형을 만들었다.

 

따라서 오목 다각형의 삼각분할 알고리즘을 정다각형에도 적용해보자.


절차적 메시를 적용한 다각형의 삼각분할 알고리즘을 반영하여 ProceduralRegular.cs를 수정하자.

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

[RequireComponent(typeof(MeshRenderer), typeof(MeshFilter))]
public class ProceduralRegular : MonoBehaviour
{
    public int polygon = 3;
    public float size = 1.0f;
    public Vector3 offset = new Vector3(0, 0, 0);

    Mesh mesh;
    List<Vector3> vertices = new List<Vector3>();
    List<int> triangles = new List<int>();
    Dictionary<Vector3, int> dic = new Dictionary<Vector3, int>();

    void OnValidate()
    {
        if (mesh == null) return;

        if (size > 0 || offset.magnitude > 0 || polygon >= 3)
        {
            triangluation(size, polygon);
            createProceduralMesh();
        }
    }

    void Start()
    {
        mesh = GetComponent<MeshFilter>().mesh;

        triangluation(size, polygon);
        createProceduralMesh();
    }

    //void setMeshData(float size, int polygon) { ... }

    float CCWby2D(Vector3 a, Vector3 b, Vector3 c)
    {
        Vector3 p = b - a;
        Vector3 q = c - b;

        return Vector3.Cross(p, q).y;
    }

    float getAreaOfTriangle(Vector3 dot1, Vector3 dot2, Vector3 dot3)
    {
        Vector3 a = dot2 - dot1;
        Vector3 b = dot3 - dot1;
        Vector3 cross = Vector3.Cross(a, b);

        return cross.magnitude / 2.0f;
    }

    bool checkTriangleInPoint(Vector3 dot1, Vector3 dot2, Vector3 dot3, Vector3 checkPoint)
    {
        float area = getAreaOfTriangle(dot1, dot2, dot3);
        float dot12 = getAreaOfTriangle(dot1, dot2, checkPoint);
        float dot23 = getAreaOfTriangle(dot2, dot3, checkPoint);
        float dot31 = getAreaOfTriangle(dot3, dot1, checkPoint);

        return (dot12 + dot23 + dot31) <= area + 0.1f /* 오차 허용 */;
    }

    bool CrossCheckAll(List<Vector3> list, int index)
    {
        Vector3 a = list[index];
        Vector3 b = list[index + 1];
        Vector3 c = list[index + 2];

        for (int i = index + 3; i < list.Count; i++)
        {
            if (checkTriangleInPoint(a, b, c, list[i]) == true) return true;
        }

        return false;
    }

    void triangluation(float size, int polygon)
    {
        vertices.Clear();
        triangles.Clear();
        dic.Clear();

        List<Vector3> dotList = new List<Vector3>();

        dotList.Clear();

        for (int i = 1; i <= polygon; i++)
        {
            float angle = -i * (Mathf.PI * 2.0f) / polygon;
            dotList.Add((new Vector3(Mathf.Cos(angle), 0, Mathf.Sin(angle)) * size) + offset);
        }

        if (polygon > dotList.Count - 2) polygon = dotList.Count - 2;
        for (int i = 0; i < polygon; i++)
        {
            List<Vector3> copy = new List<Vector3>(dotList);

            for (int k = 0; k < copy.Count - 2; k++)
            {
                bool ccw = (CCWby2D(copy[k], copy[k + 1], copy[k + 2]) > 0);
                bool cross = CrossCheckAll(copy, k);

                if (ccw == true && cross == false)
                {
                    /* triangle[0]은 부모의 LineRenderer */
                    //makeTriangle(lineForTriangles[i + 1], copy[k], copy[k + 1], copy[k + 2]);

                    for (int c = 0; c < 3; c++)
                    {
                        if (dic.ContainsKey(copy[k + c])) continue;

                        dic[copy[k + c]] = vertices.Count;
                        vertices.Add(copy[k + c]);
                    }

                    for (int c = 0; c < 3; c++)
                        triangles.Add(dic[copy[k + c]]);

                    copy.RemoveAt(k + 1);
                    dotList = new List<Vector3>(copy);

                    break;
                }
            }
        }
    }

    void createProceduralMesh()
    {
        mesh.Clear();
        mesh.vertices = vertices.ToArray();
        mesh.triangles = triangles.ToArray();
        mesh.RecalculateNormals();

        Destroy(this.GetComponent<MeshCollider>());
        this.gameObject.AddComponent<MeshCollider>();
    }
}

 

setMeshData를 지우고 triangluation 함수가 역할을 대신하였다.

 

저대로 사용해도 상관없지만, 정다각형이라는 조건때문에 아래의 bool값은 더 이상 필요가 없다.

bool ccw = (CCWby2D(copy[k], copy[k + 1], copy[k + 2]) > 0);
bool cross = CrossCheckAll(copy, k);

 

ccw, cross를 체크할 필요가 없어졌으므로 관련 함수를 모두 지우자.

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

[RequireComponent(typeof(MeshRenderer), typeof(MeshFilter))]
public class ProceduralRegular : MonoBehaviour
{
    public int polygon = 3;
    public float size = 1.0f;
    public Vector3 offset = new Vector3(0, 0, 0);

    Mesh mesh;
    List<Vector3> vertices = new List<Vector3>();
    List<int> triangles = new List<int>();
    Dictionary<Vector3, int> dic = new Dictionary<Vector3, int>();

    void OnValidate()
    {
        if (mesh == null) return;

        if (size > 0 || offset.magnitude > 0 || polygon >= 3)
        {
            triangluation(size, polygon);
            createProceduralMesh();
        }
    }

    void Start()
    {
        mesh = GetComponent<MeshFilter>().mesh;

        triangluation(size, polygon);
        createProceduralMesh();
    }

    void triangluation(float size, int polygon)
    {
        vertices.Clear();
        triangles.Clear();
        dic.Clear();

        List<Vector3> dotList = new List<Vector3>();

        dotList.Clear();

        for (int i = 1; i <= polygon; i++)
        {
            float angle = -i * (Mathf.PI * 2.0f) / polygon;
            dotList.Add((new Vector3(Mathf.Cos(angle), 0, Mathf.Sin(angle)) * size) + offset);
        }

        if (polygon > dotList.Count - 2) polygon = dotList.Count - 2;
        for (int i = 0; i < polygon; i++)
        {
            List<Vector3> copy = new List<Vector3>(dotList);

            for (int k = 0; k < copy.Count - 2; k++)
            {
                for (int c = 0; c < 3; c++)
                {
                    if (dic.ContainsKey(copy[k + c])) continue;

                    dic[copy[k + c]] = vertices.Count;
                    vertices.Add(copy[k + c]);
                }

                for (int c = 0; c < 3; c++)
                    triangles.Add(dic[copy[k + c]]);

                copy.RemoveAt(k + 1);
                dotList = new List<Vector3>(copy);
            }
        }
    }

    void createProceduralMesh()
    {
        mesh.Clear();
        mesh.vertices = vertices.ToArray();
        mesh.triangles = triangles.ToArray();
        mesh.RecalculateNormals();

        Destroy(this.GetComponent<MeshCollider>());
        this.gameObject.AddComponent<MeshCollider>();
    }
}

 

마지막으로 오목 다각형처럼 점 3개를 차례대로 이동하면서 삼각형을 만들 필요가 없으므로,

기준이 되는 점만 정하면 삼각형은 모두 정해진다.

따라서 triangluation은 아래와 같이 바꿀 수 있다.

    void triangluation(float size, int polygon)
    {
        vertices.Clear();
        triangles.Clear();
        dic.Clear();

        for (int i = 0; i < polygon; i++)
        {
            float angle = -i * (Mathf.PI * 2.0f) / polygon;
            vertices.Add((new Vector3(Mathf.Cos(angle), 0, Mathf.Sin(angle)) * size) + offset);
        }

        for (int i = 0; i < polygon - 2; i++)
        {
            triangles.Add(0);
            triangles.Add(i + 1);
            triangles.Add(i + 2);
        }
    }

 

게임을 실행하면 이전과 마찬가지로 정N각형이 만들어진다.

 

셰이딩 모드Shaded Wireframe으로 변경하면 N - 2개의 삼각형으로 나누어진 mesh도 같이 볼 수 있다.

 

Mesh Renderoff하면 콜라이더만 볼 수 있다.

 

전체 코드는 다음과 같다.

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

[RequireComponent(typeof(MeshRenderer), typeof(MeshFilter))]
public class ProceduralRegular : MonoBehaviour
{
    public int polygon = 3;
    public float size = 1.0f;
    public Vector3 offset = new Vector3(0, 0, 0);

    Mesh mesh;
    List<Vector3> vertices = new List<Vector3>();
    List<int> triangles = new List<int>();
    Dictionary<Vector3, int> dic = new Dictionary<Vector3, int>();

    void OnValidate()
    {
        if (mesh == null) return;

        if (size > 0 || offset.magnitude > 0 || polygon >= 3)
        {
            triangluation(size, polygon);
            createProceduralMesh();
        }
    }

    void Start()
    {
        mesh = GetComponent<MeshFilter>().mesh;

        triangluation(size, polygon);
        createProceduralMesh();
    }

    void triangluation(float size, int polygon)
    {
        vertices.Clear();
        triangles.Clear();
        dic.Clear();

        for (int i = 0; i < polygon; i++)
        {
            float angle = -i * (Mathf.PI * 2.0f) / polygon;
            vertices.Add((new Vector3(Mathf.Cos(angle), 0, Mathf.Sin(angle)) * size) + offset);
        }

        for (int i = 0; i < polygon - 2; i++)
        {
            triangles.Add(0);
            triangles.Add(i + 1);
            triangles.Add(i + 2);
        }
    }

    void createProceduralMesh()
    {
        mesh.Clear();
        mesh.vertices = vertices.ToArray();
        mesh.triangles = triangles.ToArray();
        mesh.RecalculateNormals();

        Destroy(this.GetComponent<MeshCollider>());
        this.gameObject.AddComponent<MeshCollider>();
    }
}

 

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

RegularPolygonTriangulation.unitypackage
0.00MB

 

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

반응형

댓글