Unity 凹多邊形三角剖分

遊戲中須要實現一個小功能,顯示一個玩家的能力圖,這個圖是一個有6個頂點任意擺放組合的多邊形。而繪製多邊形主要用到的知識就是Mesh構建,mesh的構建主要須要頂點列表,三角形列表,法線列表、uv列表等等等等,在這裏咱們只考慮頂點列表和三角形列表。那麼咱們須要作的就是給定一組頂點以後,如何用三角形進行劃分,以便繪製。html

如下討論的多邊形:1.三角形頂點列表爲順時針順序。2.多邊形不能包含空洞。git

參照文章:https://blog.csdn.net/huangzengman/article/details/77114082,表示感謝算法

 

1.凸多邊形ide

凸多邊形三角剖分比較簡單,可使用第一個點依次鏈接後面的其餘點來組成一組共同頂點的三角形。固然這是最簡單的凸多邊形剖分算法,還有其餘最優剖分算法,須要考慮權重等,但咱們不須要。工具

   

                                            a                                                                                                     bui

圖a爲凸多邊形,標出了頂點序號。this

圖b爲三角剖分後,unity中所顯示的三角形網格。spa

 

2.凹多邊形.net

凹多邊形的剖分則須要判斷點與多邊形的關係,以此來肯定可劃分頂點與不可劃分頂點。code

 

                                                             a                                                                                                                                                b

圖a爲凹多邊形(沒有空洞),標出了頂點序號。

圖b爲三角剖分後,unity中所顯示的三角形網格。

代碼以下,參考上述鏈接:

1.MeshDrawBase.cs           基礎類

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

public abstract class MeshDrawBase : MonoBehaviour {

    protected MeshFilter targetFilter;
    protected Mesh mesh;
    protected int[] tris;
    protected Vector2[] uvs;
    protected Vector3[] normals;

    // Use this for initialization
    void Awake () {
        targetFilter = GetComponent<MeshFilter>();
    }
    
    // Update is called once per frame
    protected virtual void Update () {
        DrawMesh();
    }

    protected abstract void DrawMesh();
}
View Code

2.Polygon.cs                       用於繪製多邊形

using System.Collections.Generic;
using UnityEngine;

public class Polygon : MeshDrawBase
{
    public List<Vector3> points = new List<Vector3>();
    public List<int> indexes = new List<int>();
    int index = 0;
    Color[] colors;

    void Start()
    {
        /*points.Add(new Vector3(0, 0, 0));
        points.Add(new Vector3(0, 1, 0));
        points.Add(new Vector3(1, 1, 0));
        points.Add(new Vector3(0.7f, 0.8f, 0));
        points.Add(new Vector3(1, 0.5f, 0));
        points.Add(new Vector3(0.7f, 0.3f, 0));
        points.Add(new Vector3(1, 0, 0));
        indexes.Add(0);
        indexes.Add(1);
        indexes.Add(2);
        indexes.Add(3);
        indexes.Add(4);
        indexes.Add(5);
        indexes.Add(6);*/
    }

    protected override void DrawMesh()
    {
        if(Input.GetKeyDown(KeyCode.D))
        {
            DrawPolygon();
        }
    }

    private void DrawPolygon()
    {
        mesh = new Mesh();
        mesh.name = "Polygon";
        mesh.vertices = points.ToArray();
        
        tris = Triangulation.WidelyTriangleIndex(new List<Vector3>(points), indexes).ToArray();

        mesh.triangles = tris;

        normals = new Vector3[mesh.vertices.Length];
        for(int i = 0; i < mesh.vertices.Length; ++i)
        {
            normals[i] = new Vector3(0, 0, 1);
        }

        mesh.normals = normals;
        
        mesh.RecalculateBounds();
        mesh.RecalculateTangents();

        targetFilter.mesh = mesh;
    }

    protected override void Update()
    {
        base.Update();
        if(Input.GetMouseButtonDown(0))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if(Physics.Raycast(ray, out hit, Mathf.Infinity))
            {
                var worldHitPos = hit.point;
                var localHitPos = transform.InverseTransformPoint(worldHitPos);

                points.Add(localHitPos);
                indexes.Add(index++);
            }
        }

        if(Input.GetKeyDown(KeyCode.R))
        {
            this.Reset();
        }
    }

    private void Reset()
    {
        points.Clear();

        targetFilter.mesh = null;
        Destroy(mesh);
    }

    private void OnGUI()
    {
        if (points.Count == 0) return;

        GUI.color = Color.red;

        for(int i = 0; i < points.Count; ++i)
        {
            var worldPos = transform.TransformPoint(points[i]);
            var screenPos = Camera.main.WorldToScreenPoint(worldPos);
            var uiPos = new Vector3(screenPos.x, Camera.main.pixelHeight - screenPos.y, screenPos.z);

            GUI.Label(new Rect(uiPos, new Vector2(100, 80)), i.ToString());
        }
    }

    private void OnDrawGizmos()
    {
        if (points.Count == 0) return;

        Gizmos.color = Color.cyan;
        foreach(var pos in points)
        {
            var worldPos = transform.TransformPoint(pos);
            Gizmos.DrawWireSphere(worldPos, .2f);
        }
    }
}
View Code

3.Triangulation.cs               三角剖分工具類

using UnityEngine;
using System.Collections.Generic;

/// <summary>
/// 三維模型重建中的凹多邊形三角剖分,適用於不帶空洞的凹多邊形
/// ref: https://blog.csdn.net/huangzengman/article/details/77114082
/// </summary>
public static class Triangulation
{
    const double epsilon = 1e-7;

    static bool floatLess(float value, float other)
    {
        return (other - value) > epsilon;
    }

    static bool floatGreat(float value, float other)
    {
        return (value - other) > epsilon;
    }

    static bool floatEqual(float value, float other)
    {
        return Mathf.Abs(value - other) < epsilon;
    }

    static bool Vector3Equal(Vector3 a, Vector3 b)
    {
        return floatEqual(a.x, b.x) && floatEqual(a.y, b.y) && floatEqual(a.z, b.z);
    }

    /// <summary>
    /// 凸多邊形,順時針序列,以第1個點來剖分三角形,以下:
    /// 0---1
    /// |   |
    /// 3---2  -->  (0, 1, 2)、(0, 2, 3)
    /// </summary>
    /// <param name="verts">順時針排列的頂點列表</param>
    /// <param name="indexes">頂點索引列表</param>
    /// <returns>三角形列表</returns>
    public static List<int> ConvexTriangleIndex(List<Vector3> verts, List<int> indexes)
    {
        int len = verts.Count;
        //如果閉環去除最後一點
        if (len > 1 && Vector3Equal(verts[0], verts[len - 1]))
        {
            len--;
        }
        int triangleNum = len - 2;
        List<int> triangles = new List<int>(triangleNum * 3);
        for (int i = 0; i < triangleNum; i++)
        {
            triangles.Add(indexes[0]);
            triangles.Add(indexes[i + 1]);
            triangles.Add(indexes[i + 2]);
        }
        return triangles;
    }

    /// <summary>
    /// 三角剖分
    /// 1.尋找一個可劃分頂點
    /// 2.分割出新的多邊形和三角形
    /// 3.新多邊形若爲凸多邊形,結束;不然繼續剖分
    /// 
    /// 尋找可劃分頂點
    /// 1.頂點是否爲凸頂點:頂點在剩餘頂點組成的圖形外
    /// 2.新的多邊形沒有頂點在分割的三角形內
    /// </summary>
    /// <param name="verts">順時針排列的頂點列表</param>
    /// <param name="indexes">頂點索引列表</param>
    /// <returns>三角形列表</returns>
    public static List<int> WidelyTriangleIndex(List<Vector3> verts, List<int> indexes)
    {
        int len = verts.Count;
        if (len <= 3) return ConvexTriangleIndex(verts, indexes);

        int searchIndex = 0;
        List<int> covexIndex = new List<int>();
        bool isCovexPolygon = true;//判斷多邊形是不是凸多邊形

        for (searchIndex = 0; searchIndex < len; searchIndex++)
        {
            List<Vector3> polygon = new List<Vector3>(verts.ToArray());
            polygon.RemoveAt(searchIndex);
            if (IsPointInsidePolygon(verts[searchIndex], polygon))
            {
                isCovexPolygon = false;
                break;
            }
            else
            {
                covexIndex.Add(searchIndex);
            }
        }

        if (isCovexPolygon) return ConvexTriangleIndex(verts, indexes);

        //查找可劃分頂點
        int canFragementIndex = -1;//可劃分頂點索引
        for (int i = 0; i < len; i++)
        {
            if (i > searchIndex)
            {
                List<Vector3> polygon = new List<Vector3>(verts.ToArray());
                polygon.RemoveAt(i);
                if (!IsPointInsidePolygon(verts[i], polygon) && IsFragementIndex(i, verts))
                {
                    canFragementIndex = i;
                    break;
                }
            }
            else
            {
                if (covexIndex.IndexOf(i) != -1 && IsFragementIndex(i, verts))
                {
                    canFragementIndex = i;
                    break;
                }
            }
        }

        if (canFragementIndex < 0)
        {
            Debug.LogError("數據有誤找不到可劃分頂點");
            return new List<int>();
        }

        //用可劃分頂點將凹多邊形劃分爲一個三角形和一個多邊形
        List<int> tTriangles = new List<int>();
        int next = (canFragementIndex == len - 1) ? 0 : canFragementIndex + 1;
        int prev = (canFragementIndex == 0) ? len - 1 : canFragementIndex - 1;
        tTriangles.Add(indexes[prev]);
        tTriangles.Add(indexes[canFragementIndex]);
        tTriangles.Add(indexes[next]);
        //剔除可劃分頂點及索引
        verts.RemoveAt(canFragementIndex);
        indexes.RemoveAt(canFragementIndex);

        //遞歸劃分
        List<int> leaveTriangles = WidelyTriangleIndex(verts, indexes);
        tTriangles.AddRange(leaveTriangles);

        return tTriangles;
    }

    /// <summary>
    /// 是不是可劃分頂點:新的多邊形沒有頂點在分割的三角形內
    /// </summary>
    private static bool IsFragementIndex(int index, List<Vector3> verts)
    {
        int len = verts.Count;
        List<Vector3> triangleVert = new List<Vector3>();
        int next = (index == len - 1) ? 0 : index + 1;
        int prev = (index == 0) ? len - 1 : index - 1;
        triangleVert.Add(verts[prev]);
        triangleVert.Add(verts[index]);
        triangleVert.Add(verts[next]);
        for (int i = 0; i < len; i++)
        {
            if (i != index && i != prev && i != next)
            {
                if (IsPointInsidePolygon(verts[i], triangleVert))
                {
                    return false;
                }
            }
        }
        return true;
    }

    /// <summary>
    /// 射線與線段相交性判斷
    /// </summary>
    /// <param name="ray">射線</param>
    /// <param name="p1">線段頭</param>
    /// <param name="p2">線段尾</param>
    /// <returns></returns>
    private static bool IsDetectIntersect(Ray2D ray, Vector3 p1, Vector3 p2)
    {
        float pointY;//交點Y座標,x固定值
        if (floatEqual(p1.x, p2.x))
        {
            return false;
        }
        else if(floatEqual(p1.y, p2.y))
        {
            pointY = p1.y;
        }
        else
        {
            //直線兩點式方程:(y-y2)/(y1-y2) = (x-x2)/(x1-x2)
            float a = p1.x - p2.x;
            float b = p1.y - p2.y;
            float c = p2.y / b - p2.x / a;

            pointY = b / a * ray.origin.x + b * c;
        }
        
        if (floatLess(pointY, ray.origin.y))
        {
            //交點y小於射線起點y
            return false;
        }
        else
        {
            Vector3 leftP = floatLess(p1.x, p2.x) ? p1 : p2;//左端點
            Vector3 rightP = floatLess(p1.x, p2.x) ? p2 : p1;//右端點
            //交點x位於線段兩個端點x以外,相交與線段某個端點時,僅將射線L與左側多邊形一邊的端點記爲焦點(即就是:只將右端點記爲交點)
            if (!floatGreat(ray.origin.x, leftP.x) || floatGreat(ray.origin.x, rightP.x))
            {
                return false;
            }
        }

        return true;
    }

    /// <summary>
    /// 點與多邊形的位置關係
    /// </summary>
    /// <param name="point">斷定點</param>
    /// <param name="polygonVerts">剩餘頂點按順序排列的多邊形</param>
    /// <returns>true:點在多邊形以內,false:相反</returns>
    private static bool IsPointInsidePolygon(Vector3 point, List<Vector3> polygonVerts)
    {
        int len = polygonVerts.Count;
        Ray2D ray = new Ray2D(point, new Vector3(0, 1)); //y方向射線
        int interNum = 0;

        for (int i = 1; i < len; i++)
        {
            if (IsDetectIntersect(ray, polygonVerts[i - 1], polygonVerts[i]))
            {
                interNum++;
            }
        }

        //不是閉環
        if (!Vector3Equal(polygonVerts[0], polygonVerts[len - 1]))
        {
            if (IsDetectIntersect(ray, polygonVerts[len - 1], polygonVerts[0]))
            {
                interNum++;
            }
        }
        int remainder = interNum % 2;
        return remainder == 1;
    }
}
View Code

使用方法:新建一個Quad,將Polygon.cs腳本添加上,用鼠標點擊來肯定多邊形的個頂點位置,以後按D進行繪製。

git:https://gitee.com/planefight/RunSprite_Tri.git

 

 

參照論文:

三維模型重建中的凹多邊形三角剖分

相關文章
相關標籤/搜索