unity模型制作:绘制一个凹多边形

Posted 左右...

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了unity模型制作:绘制一个凹多边形相关的知识,希望对你有一定的参考价值。

(不重要的前言:该博文为系列博文,每一篇有前后文关系,例如基类、组件的集成,如果发现有陌生组件和基类,请查看前面文章,本系列文章单纯应用unity的mesh来绘制模型,并未使用任何三方插件,文章内容、代码都是纯手打,望支持)

1.凹多边形相对凸多边形要复杂很多,画出一个凹多边形需要分两步:

        1.1.在平面几何上拆解凹多边形,分成若干个三角形(耳朵),和一个凸多边形。

        1.2.将所有耳朵视为一个三角面,加上最后一个凸多边形,合并成图形

2.名词说明

        耳朵:一个点和前后两点相连构成的三角形不和多边形其他边线相交,且该点是凸点

        凹多边形:包含凹点的多边形就是凹多边形

        凹点:此点在(多边形删除此点后的)新多边形的内部

        凸点:此点在(多边形删除此点后的)新多边形的外部

3.详解1.1拆解流程:

         3.1.判断多边形是一个凹多边形,否则返回凸多边形算法

         3.2.遍历多边形每一个点,判断该点是不是凸点,不是就判断下一个点

         3.3.如果是凸点,且和前后相连是一个耳朵,则从多边形中去除该点,回到1

        可以看到,此算法必然是一个递归,每一层会切掉一个耳朵,直到只剩下一个凸多边形

再按照1.2处理,就可以拼接出一个凹多边形。

4.举个例子:

如图12345是一个凹多边形,完成以下拆解步骤:

        a. 12345是一个凹多边形,1号点是凸点,但15线会与34线相交,因此125不是一个耳朵

        b. 2号点是凸点,且123是一个耳朵,删除2号点,剩余1345

        c. 1345是一个凹多边形,1号点是凸点,且135是一个耳朵删除1号点剩余345

        d. 345是一个凸多边形,结束

ok,我们获得123、135两个耳朵和345一个凸多边形,将他们拼接起来就可以得到预期图形了

5.代码:

核心算法,在GetTriangles()中返回递归结果

        public override List<int> GetTriangles()
        
            triangles = new List<int>();
            return GetTriangles(positions, triangles);
        

        List<int> GetTriangles(List<Vector3> points, List<int> result) 

            Debug.Log("count = " + points.Count);

            List<int> triangles = new List<int>();

            //凹多边形
            if (IsConcave(points))
            
                Debug.Log("凹多边形");

                for (int i = 0; i < points.Count; i++)
                
                    List<Vector3> list = new List<Vector3>(points);
                    list.RemoveAt(i);

                    int indexStart = i - 1;
                    int indexEnd = i + 1;
                    if (i == 0) indexStart = points.Count - 1;
                    if (i == points.Count - 1) indexEnd = 0;

                    Vector3 start = points[indexStart];
                    Vector3 end = points[indexEnd];
                    Vector3 point = points[i];

                    //该点是凸点,而且切割线不与剩下多边形相交,则取出该顶点
                    bool isConverx = !IsInsidePoint(point, list);
                    bool isIntersect = IsIntersect(start, end, list);

                    if (isConverx && !isIntersect)
                    
                        int index0 = positions.IndexOf(point);
                        int index1 = positions.IndexOf(start);
                        int index2 = positions.IndexOf(end);

                        Debug.Log("triangle:" + index0 + "," + index1 + "," + index2);

                        triangles.Add(index0);
                        triangles.Add(index1);
                        triangles.Add(index2);

                        result.AddRange(triangles);
                        return GetTriangles(list, result);
                    
                
            
            else//凸多边形
            
                Debug.Log("凸多边形");
                result.AddRange(GetConverxTriangles(points));
                return result;
            

            Debug.LogError("error");
            return null;
        

功能算法:

//凸多边形算法
        public List<int> GetConverxTriangles(List<Vector3> points)
        
            List<int> triangles = new List<int>();

            for (int i = 1; i < points.Count - 1; i++)
            
                int index0 = i;
                int index1 = i + 1;

                if (up)
                
                    triangles.Add(positions.IndexOf(points[0]));
                    triangles.Add(positions.IndexOf(points[index0]));
                    triangles.Add(positions.IndexOf(points[index1]));
                
                else
                
                    triangles.Add(positions.IndexOf(points[index0]));
                    triangles.Add(positions.IndexOf(points[0]));
                    triangles.Add(positions.IndexOf(points[index1]));
                
            

            return triangles;
        

        //是否是凹多边形
        bool IsConcave(List<Vector3> points) 

            for (int i = 0; i < points.Count; i++)
            
                List<Vector3> list = new List<Vector3>(points);
                list.RemoveAt(i);

                if (IsInsidePoint(points[i], list)) return true;
            

            return false;
        

        //一个点是否在一个多边形内部
        bool IsInsidePoint(Vector3 point,List<Vector3> points)
        
            int count = 0;

            for (int i = 1; i < points.Count; i++)
            
                if (IsIntersect(point, point + Vector3.forward * 100, points[i - 1], points[i])) count++;
            

            if (IsIntersect(point, point + Vector3.forward * 100, points[0], points[points.Count - 1])) count++;

            bool r = count % 2 != 0;
            return r;
        

        //线段与多边形是否相交
        bool IsIntersect(Vector3 start, Vector3 end,List<Vector3> points) 
            for (int i = 1; i < points.Count; i++)
            
                if (IsIntersect(start, end, points[i - 1], points[i])) return true;
            

            return false;
        

        //两个线段是否相交
        bool IsIntersect(Vector3 p11, Vector3 p12, Vector3 p21, Vector3 p22)
        
            if (p11 == null || p12 == null || p21 == null || p22 == null) return false;

            if (Mathf.Max(p21.x, p22.x) <= Mathf.Min(p11.x, p12.x)) return false;
            if (Mathf.Max(p11.x, p12.x) <= Mathf.Min(p21.x, p22.x)) return false;
            if (Mathf.Max(p21.z, p22.z) <= Mathf.Min(p11.z, p12.z)) return false;
            if (Mathf.Max(p11.z, p12.z) <= Mathf.Min(p21.z, p22.z)) return false;

            float y2221 = p22.z - p21.z;
            float x2211 = p22.x - p11.x;
            float y2211 = p22.z - p11.z;
            float x2212 = p22.x - p12.x;
            float y2212 = p22.z - p12.z;
            float x2221 = p22.x - p21.x;
            if ((x2211 * y2221 - y2211 * x2221) * (x2212 * y2221 - y2212 * x2221) >= 0) return false;

            float x2111 = p21.x - p11.x;
            float y1211 = p12.z - p11.z;
            float y2111 = p21.z - p11.z;
            float x1211 = p12.x - p11.x;
            if ((x2111 * y1211 - y2111 * x1211) * (x2211 * y1211 - y2211 * x1211) >= 0) return false;

            return true;
        

6.补充:

简单解释一下IsInsidePoint算法,如果判断一个点在多边形内部

如图,任意一点A,发出任意一条射线l,如果l与多边形边线的交点是奇数个,则该点在多边形内部,反之在外部。

通过这行代码可以看出来,我用的forward方向发射一跟100米的线段当l,跟多边形的每一个线段做相交计算,统计交点个数得出结果。

这里有两个小bug

        1.如果l和多边形的交点恰巧再顶点上,会多计算一个交点

        2.如果多边形比100米还要大,会算不到交点

大家可以根据实际需求相应的完善算法,由于是做demo我就不深入写了,当然欢迎同学们自行优化了算法私信给我,成品:

 

以上是关于unity模型制作:绘制一个凹多边形的主要内容,如果未能解决你的问题,请参考以下文章

unity模型制作:绘制一个凹多边形

unity模型制作:绘制一个凸多边形

unity模型制作:绘制一个凸多边形

unity模型制作:绘制一个多边形组合

unity模型制作:绘制一个多边形组合

unity模型制作:绘制一个多边形组合