Unity 凹多边形三角剖分

Wesley13
• 阅读 631

游戏中需要实现一个小功能,显示一个玩家的能力图,这个图是一个有6个顶点任意摆放组合的多边形。而绘制多边形主要用到的知识就是Mesh构建,mesh的构建主要需要顶点列表,三角形列表,法线列表、uv列表等等等等,在这里我们只考虑顶点列表和三角形列表。那么我们需要做的就是给定一组顶点之后,如何用三角形进行划分,以便绘制。

以下讨论的多边形:1.三角形顶点列表为顺时针顺序。2.多边形不能包含空洞。

参照文章:https://blog.csdn.net/huangzengman/article/details/77114082,表示感谢

1.凸多边形

凸多边形三角剖分比较简单,可以使用第一个点依次连接后面的其他点来组成一组共同顶点的三角形。当然这是最简单的凸多边形剖分算法,还有其他最优剖分算法,需要考虑权重等,但我们不需要。

Unity 凹多边形三角剖分    Unity 凹多边形三角剖分

a                                                                                                     b

图a为凸多边形,标出了顶点序号。

图b为三角剖分后,unity中所显示的三角形网格。

2.凹多边形

凹多边形的剖分则需要判断点与多边形的关系,以此来确定可划分顶点与不可划分顶点。

Unity 凹多边形三角剖分  Unity 凹多边形三角剖分

a                                                                                                                                                b

图a为凹多边形(没有空洞),标出了顶点序号。

图b为三角剖分后,unity中所显示的三角形网格。

代码如下,参考上述连接:

1.MeshDrawBase.cs           基础类

Unity 凹多边形三角剖分 Unity 凹多边形三角剖分

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                       用于绘制多边形

Unity 凹多边形三角剖分 Unity 凹多边形三角剖分

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               三角剖分工具类

Unity 凹多边形三角剖分 Unity 凹多边形三角剖分

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

参照论文:

三维模型重建中的凹多边形三角剖分

Unity 凹多边形三角剖分 Unity 凹多边形三角剖分 Unity 凹多边形三角剖分

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
java将前端的json数组字符串转换为列表
记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Wesley13 Wesley13
3年前
janusgraph
精确查询语句含义测试语句执行时间查询顶点标签为FALV的顶点数量g.V().hasLabel('FALV').count()2400s查询顶点属性中id为19012201clockWithResult(1){g.V().has('id','19012201')}0.18540099999999998s查询顶点属性中
Easter79 Easter79
3年前
Three.js加载3D模型
  3D模型由顶点(vertex)组成,顶点之间连成三角形或四边形(在一个平面上),多个三角形或者四边形就能够组成复杂的立体模型.一、模型在three.js的表示  模型是由面组成,面分为三角形和四边形面。三角形和四边形面组成了网格模型。在Three.js中用THREE.Mesh来表示网格模型。THREE.Mesh可
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
Unity实现瓦片地图tile map
Unity自定义mesh绘制(https://my.oschina.net/kkkkkkkkkkkkk/blog/1545422) 基于上篇的mesh修改,实现tilemap第一步,修改mesh顶点和三角片信息,生成方格!(https://static.oschina.net/uploads/space/2017/1002/230643
Wesley13 Wesley13
3年前
Unity自定义mesh绘制
有些时候需要自定义mesh来绘制目标模型图形什么的,可以代码控制,也可以通过shader去控制,这里介绍代码控制的方法:基本思路是修改mesh的定点,三角达到自定义的目的,和上上篇垂直UI.Text显示异曲同工之处。由于一个mesh是有顶点信息,和对应三角形组合而成。!(https://static.oschina.net/uploads
Wesley13 Wesley13
3年前
ThinkPHP 根据关联数据查询 hasWhere 的使用实例
很多时候,模型关联后需要根据关联的模型做查询。场景:广告表(ad),广告类型表(ad\_type),现在需要筛选出广告类型表中id字段为1且广告表中status为1的列表先看关联的设置部分 publicfunctionadType(){return$thisbelongsTo('A
Wesley13 Wesley13
3年前
Unity Mesh基础系列(一)生成网格(程序生成)
目录1渲染事物2创建顶点网格3创建Mesh4生成附加顶点数据本文主要内容:1、创建一个点阵网格2、用协程分析点阵网格的位置3、用三角形定义表面4、自动生成法线5、增加纹理坐标和切线在本教程中,我们将创建一个由顶点和三角形组成