Unity中创建多边形并计算面积

2021-02-06 16:14

阅读:339

标签:list   --   count   end   索引数据   描述   名称   rtl   问题   

问题背景:

我这边最近需要实现动态去画多边形(不规则的),类似于高德地图中那种面积测量工具一般。

方案:

”割耳“算法实现三角化平面。

具体实现:

割耳算法类:

技术图片技术图片
/*
 *******************************************************
 * 
 * 文件名称:EarCut
 * 文件描述:三角化相关算法集合
 * 
 * 版本:V1.0.0
 *  支持带洞的多边形,需要保证多边形为顺时针,而洞为逆时针顺序
 * *****************************************************
 */

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

namespace Tx3d.Framework
{
    public class EarCut
    {
        #region Sub Class

        /// 
        /// “割耳”点
        /// 
        private class Node : IComparable
        {
            #region Members & Properties

            /// 
            /// vertice index in coordinates array
            /// 
            public int i = -1;

            /// 
            ///  vertex coordinates
            /// 
            public float x = 0.0f;
            public float z = 0.0f;

            /// 
            /// previous and next vertice nodes in a polygon ring
            /// 
            public Node prev = null;
            public Node next = null;

            /// 
            /// z-order curve value
            /// 
            public int zOrder = -1;

            /// 
            /// previous and next nodes in z-order
            /// 
            public Node prevZ = null;
            public Node nextZ = null;

            /// 
            /// indicates whether this is a steiner point
            /// 
            public bool steiner = false;

            #endregion

            #region IComparable Implemention

            public int CompareTo(object obj)
            {
                try
                {
                    Node node = obj as Node;

                    if (this.x > node.x)
                    {
                        return 1;
                    }
                    else
                    {
                        return 0;
                    }
                }
                catch (Exception ex)
                {
                    throw new Exception(ex.Message);
                }
            }

            #endregion
        }

        #endregion

        #region Members & Properties

        private static float EPSINON = 0.1f;

        #endregion

        #region Public Methods

        /// 
        /// “割耳”
        /// 
        public static Listint> CutEar(List data, Listint> holeIndices)
        {
            var triangles = new Listint>();

            bool hasHoles = holeIndices != null && holeIndices.Count > 0;
            int outerLength = hasHoles ? holeIndices[0] : data.Count;
            Node outerNode = LinkedList(data, 0, outerLength, true);

            if (outerNode == null)
            {
                return triangles;
            }

            if (hasHoles)
            {
                outerNode = EliminateHoles(data, holeIndices, outerNode);
            }

            float minX = 0.0f;
            float minZ = 0.0f;
            float maxX = 0.0f;
            float maxZ = 0.0f;
            float size = 0.0f;

            // if the shape is not too simple, we‘ll use z-order curve hash later; calculate polygon bbox
            // if (data.Count > 80)
            if (data.Count > 100)
            {
                minX = maxX = data[0].x;
                minZ = maxZ = data[0].z;

                for (int i = 1; i )
                {
                    float x = data[i].x;
                    float z = data[i].z;
                    if (x  x;
                    if (z  z;
                    if (x > maxX) maxX = x;
                    if (z > maxZ) maxZ = z;
                }

                // minX, minY and size are later used to transform coords into integers for z-order calculation
                size = Mathf.Max(maxX - minX, maxZ - minZ);
            }

            EarCutLinked(outerNode, triangles, minX, minZ, size, 0);

            return triangles;
        }

        #endregion

        #region Private Methods

        /// 
        /// 使用多边形顶点按照指定顺序创建一个双向循环链表
        /// 
        private static Node LinkedList(List data, int start, int end, bool clockwise)
        {
            Node last = null;

            if (clockwise == (SignedArea(data, start, end) >= 0.0))
            {
                for (int i = start; i )
                {
                    last = InsertNode(i, data[i].x, data[i].z, last);
                }
            }
            else
            {
                for (int i = end - 1; i >= start; i--)
                {
                    last = InsertNode(i, data[i].x, data[i].z, last);
                }
            }

            if (last != null && Equals(last, last.next))
            {
                var next = last.next;
                RemoveNode(last);
                last = next;
            }

            return last;
        }

        /// 
        /// “割耳”主循环
        /// 
        /// 
        /// main ear slicing loop which triangulates a polygon (given as a linked list)
        /// 
        private static void EarCutLinked(Node ear, Listint> triangles, float minX, float minZ, float size, int pass)
        {
            if (ear == null) return;

            // interlink polygon nodes in z-order
            if (pass == 0 && size > 0.0f)
            {
                IndexCurve(ear, minX, minZ, size);
            }

            Node stop = ear;
            Node prev = null;
            Node next = null;

            // iterate through ears, slicing them one by one
            while (ear.prev != ear.next)
            {
                prev = ear.prev;
                next = ear.next;

                if (size > 0.0f ? IsEarHashed(ear, minX, minZ, size) : IsEar(ear))
                {
                    // cut off the triangle
                    triangles.Add(prev.i);
                    triangles.Add(ear.i);
                    triangles.Add(next.i);

                    RemoveNode(ear);

                    // skipping the next vertice leads to less sliver triangles
                    ear = next.next;
                    stop = next.next;

                    continue;
                }

                ear = next;

                // if we looped through the whole remaining polygon and can‘t find any more ears
                if (ear == stop)
                {
                    // try filtering points and slicing again
                    if (pass == 0)
                    {
                        EarCutLinked(FilterPoints(ear, null), triangles, minX, minZ, size, 1);
                    }
                    else if (pass == 1) // if this didn‘t work, try curing all small self-intersections locally
                    {
                        ear = CureLocalIntersections(ear, triangles);
                        EarCutLinked(ear, triangles, minX, minZ, size, 2);
                    }
                    else if (pass == 2) // as a last resort, try splitting the remaining polygon into two
                    {
                        SplitEarCut(ear, triangles, minX, minZ, size);
                    }

                    return;
                }
            }
        }

        /// 
        /// 尝试将多边形分割成两个,并分别进行三角化
        /// 
        private static void SplitEarCut(Node start, Listint> triangles, float minX, float minZ, float size)
        {
            // look for a valid diagonal that divides the polygon into two
            var a = start;

            do
            {
                var b = a.next.next;

                while (b != a.prev)
                {
                    if (a.i != b.i && IsValidDiagonal(a, b))
                    {
                        // split the polygon in two by the diagonal
                        var c = SplitPolygon(a, b);

                        // filter colinear points around the cuts
                        a = FilterPoints(a, a.next);
                        c = FilterPoints(c, c.next);

                        // run earcut on each half
                        EarCutLinked(a, triangles, minX, minZ, size, 0);
                        EarCutLinked(c, triangles, minX, minZ, size, 0);

                        return;
                    }

                    b = b.next;
                }

                a = a.next;
            } while (a != start);
        }

        /// 
        /// link every hole into the outer loop, producing a single-ring polygon without holes
        /// 
        private static Node EliminateHoles(List data, Listint> holeIndices, Node outerNode)
        {
            var queue = new List();

            for (int i = 0, len = holeIndices.Count; i )
            {
                var start = holeIndices[i];
                var end = i 1 ? holeIndices[i + 1] : data.Count;

                var list = LinkedList(data, start, end, false);

                if (list == list.next)
                {
                    list.steiner = true;
                }

                queue.Add(GetLeftmost(list));
            }

            // Sort
            queue.Sort();

            // process holes from left to right
            for (int i = 0; i )
            {
                var node = EliminateHole(queue[i], outerNode);
                if (node != null)
                {
                    outerNode = FilterPoints(node, node.next);
                }
            }

            return outerNode;
        }

        /// 
        /// find a bridge between vertices that connects hole with an outer ring and and link it
        /// 
        private static Node EliminateHole(Node hole, Node outerNode)
        {
            outerNode = FindHoleBridge(hole, outerNode);
            if (outerNode != null)
            {
                var b = SplitPolygon(outerNode, hole);
                return FilterPoints(b, b.next);
            }

            return null;
        }

        /// 
        /// 遍历多边形所有结点,校正局部自相交情形
        /// 
        private static Node CureLocalIntersections(Node start, Listint> triangles)
        {
            var p = start;

            do
            {
                var a = p.prev;
                var b = p.next.next;

                if (!Equals(a, b) &&
                    Intersects(a, p, p.next, b) &&
                    LocallyInside(a, b) &&
                    LocallyInside(b, a))
                {
                    triangles.Add(a.i);
                    triangles.Add(p.i);
                    triangles.Add(b.i);

                    var next = p.next;

                    // remove two nodes involved
                    RemoveNode(p);
                    RemoveNode(next);

                    p = start = b;
                }

                p = p.next;
            } while (p != start);

            return p;
        }

        /// 
        /// 插入一个结点
        /// 
        private static Node InsertNode(int i, float x, float z, Node last)
        {
            var p = new Node
            {
                i = i,
                x = x,
                z = z
            };

            if (last == null)
            {
                p.prev = p;
                p.next = p;
            }
            else
            {
                p.next = last.next;
                p.prev = last;

                last.next.prev = p;
                last.next = p;
            }

            return p;
        }

        /// 
        /// 移除一个结点
        /// 
        private static void RemoveNode(Node p)
        {
            p.next.prev = p.prev;
            p.prev.next = p.next;

            if (p.prevZ != null)
            {
                p.prevZ.nextZ = p.nextZ;
            }

            if (p.nextZ != null)
            {
                p.nextZ.prevZ = p.prevZ;
            }
        }

        /// 
        /// 判断两个结点是否相等
        /// 
        /// true相等,false不相等
        private static bool Equals(Node p1, Node p2)
        {
            if (p1 == null || p2 == null)
            {
                Debug.Log("null");
            }

            return p1.x == p2.x && p1.z == p2.z;
        }

        /// 
        /// 判断是否是“耳朵”
        /// 
        /// 
        /// 
        private static bool IsEar(Node ear)
        {
            var a = ear.prev;
            var b = ear;
            var c = ear.next;

            if (Area(a, b, c) >= 0.0f)
            {
                // reflex, can‘t be an ear
                return false;
            }

            // now make sure we don‘t have other points inside the potential ear
            var p = ear.next.next;
            while (p != ear.prev)
            {
                if (PointInTriangle(a, b, c, p) &&
                    (Area(p.prev, p, p.next) >= 0.0f))
                {
                    return false;
                }

                p = p.next;
            }

            return true;
        }

        /// 
        /// 判断是否是“耳朵”散列?
        /// 
        private static bool IsEarHashed(Node ear, float minX, float minZ, float size)
        {
            var a = ear.prev;
            var b = ear;
            var c = ear.next;

            if (Area(a, b, c) >= 0.0f)
            {
                // reflex, can‘t be an ear
                return false;
            }

            // triangle bbox; min & max are calculated like this for speed
            var minTX = a.x  b.x : c.x);
            var minTZ = a.z  b.z : c.z);
            var maxTX = a.x > b.x ? (a.x > c.x ? a.x : c.x) : (b.x > c.x ? b.x : c.x);
            var maxTZ = a.z > b.z ? (a.z > c.z ? a.z : c.z) : (b.z > c.z ? b.z : c.z);

            // z-order range for the current triangle bbox;
            int minZOrder = ZOrder(minTX, minTZ, minX, minZ, size);
            int maxZOrder = ZOrder(maxTX, maxTZ, minX, minZ, size);

            // first look for points inside the triangle in increasing z-order
            var p = ear.nextZ;

            while (p != null && p.zOrder  maxZOrder)
            {
                if (p != ear.prev && p != ear.next &&
                    PointInTriangle(a, b, c, p) &&
                    Area(p.prev, p, p.next) >= 0.0f)
                {
                    return false;
                }

                p = p.nextZ;
            }

            // then look for points in decreasing z-order
            p = ear.prevZ;
            while (p != null && p.zOrder >= minZOrder)
            {
                if (p != ear.prev && p != ear.next &&
                    PointInTriangle(a, b, c, p) &&
                    Area(p.prev, p, p.next) >= 0.0f)
                {
                    return false;
                }

                p = p.prevZ;
            }

            return true;
        }

        /// 
        /// 通过对角线分割多边形
        /// 
        /// 
        /// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two;
        /// if one belongs to the outer ring and another to a hole, it merges it into a single ring
        /// 
        private static Node SplitPolygon(Node a, Node b)
        {
            var a2 = new Node
            {
                i = a.i,
                x = a.x,
                z = a.z
            };

            var b2 = new Node
            {
                i = b.i,
                x = b.x,
                z = b.z
            };

            var an = a.next;
            var bp = b.prev;

            a.next = b;
            b.prev = a;

            a2.next = an;
            an.prev = a2;

            b2.next = a2;
            a2.prev = b2;

            bp.next = b2;
            b2.prev = bp;

            return b2;
        }

        /// 
        /// 对结点进行排序
        /// 
        /// 
        /// Simon Tatham‘s linked list merge sort algorithm
        /// 
        private static Node SortLinked(Node list)
        {
            int numMerges = 0;
            int pSize = 0;
            int qSize = 0;
            int inSize = 1;

            Node p = null;
            Node q = null;
            Node e = null;
            Node tail = null;

            do
            {
                p = list;
                list = null;
                tail = null;
                numMerges = 0;

                while (p != null)
                {
                    numMerges++;

                    q = p;
                    pSize = 0;

                    for (int i = 0; i )
                    {
                        pSize++;
                        q = q.nextZ;

                        if (q == null)
                            break;
                    }

                    qSize = inSize;

                    while (pSize > 0 || (qSize > 0 && q != null))
                    {
                        if (pSize == 0)
                        {
                            e = q;
                            q = q.nextZ;
                            qSize--;
                        }
                        else if (qSize == 0 || q == null)
                        {
                            e = p;
                            p = p.nextZ;
                            pSize--;
                        }
                        else if (p.zOrder  q.zOrder)
                        {
                            e = p;
                            p = p.nextZ;
                            pSize--;
                        }
                        else
                        {
                            e = q;
                            q = q.nextZ;
                            qSize--;
                        }

                        if (tail != null)
                        {
                            tail.nextZ = e;
                        }
                        else
                        {
                            list = e;
                        }

                        e.prevZ = tail;
                        tail = e;
                    }

                    p = q;
                }

                tail.nextZ = null;
                inSize *= 2;
            } while (numMerges > 1);

            return list;
        }

        /// 
        /// 相邻多边形节点次序(interlink polygon nodes in z-order)
        /// 
        private static void IndexCurve(Node start, float minX, float minZ, float size)
        {
            var p = start;

            do
            {
                if (p.zOrder == -1)
                {
                    p.zOrder = ZOrder(p.x, p.z, minX, minZ, size);
                }

                p.prevZ = p.prev;
                p.nextZ = p.next;
                p = p.next;
            } while (p != start);

            p.prevZ.nextZ = null;
            p.prevZ = null;

            SortLinked(p);
        }

        /// 
        /// 判断两条线段是否相交
        /// 
        /// 
        /// 
        private static bool Intersects(Node p1, Node q1, Node p2, Node q2)
        {
            if ((Equals(p1, q1) && Equals(p2, q2)) ||
                (Equals(p1, q2) && Equals(p2, q1)))
            {
                return true;
            }

            return (Area(p1, q1, p2) > 0.0 != Area(p1, q1, q2) > 0.0) &&
                (Area(p2, q2, p1) > 0.0 != Area(p2, q2, q1) > 0.0);
        }

        /// 
        /// 检测多边形的对角线是否与多边形的边相交
        /// 
        private static bool IntersectsPolygon(Node a, Node b)
        {
            var p = a;

            do
            {
                if (p.i == a.i && p.next.i != a.i &&
                    p.i != b.i && p.next.i != b.i &&
                    Intersects(p, p.next, a, b))
                {
                    return true;
                }

                p = p.next;
            } while (p != a);

            return false;
        }

        /// 
        /// 查找多边形最坐标结点
        /// 
        private static Node GetLeftmost(Node start)
        {
            var p = start;
            var leftmost = start;

            do
            {
                if (p.x  leftmost.x)
                {
                    leftmost = p;
                }

                p = p.next;
            } while (p != start);

            return leftmost;
        }

        /// 
        /// 查找多边形内部洞与外边的连接点
        /// 
        /// David Eberly‘s algorithm
        private static Node FindHoleBridge(Node hole, Node outerNode)
        {
            var p = outerNode;

            var hx = hole.x;
            var hz = hole.z;
            var qx = float.NegativeInfinity;

            Node m = null;

            // find a segment intersected by a ray from the hole‘s leftmost point to the left;
            // segment‘s endpoint with lesser x will be potential connection point
            do
            {
                if ((hz = p.next.z) ||
                    (hz = p.z))
                {
                    var x = p.x + (hz - p.z) * (p.next.x - p.x) / (p.next.z - p.z);
                    if (x  qx)
                    {
                        qx = x;

                        if (x == hx)
                        {
                            if (hz == p.z)
                            {
                                return p;
                            }

                            if (hz == p.next.z)
                            {
                                return p.next;
                            }
                        }

                        m = p.x  p : p.next;
                    }
                }

            } while (p != outerNode);

            if (m == null)
            {
                return null;
            }

            // hole touches outer segment; pick lower endpoint
            if (hx == qx)
            {
                return m.prev;
            }

            // look for points inside the triangle of hole point, segment intersection and endpoint;
            // if there are no points found, we have a valid connection;
            // otherwise choose the point of the minimum angle with the ray as connection point

            var stop = m;

            var mx = m.x;
            var mz = m.z;
            var tanMin = float.PositiveInfinity;

            p = m.next;

            while (p != stop)
            {
                if (hx >= p.x && p.x >= mx &&
                    PointInTriangle(hz  qx : hx, hz, p.x, p.z))
                {
                    var tan = Mathf.Abs(hz - p.z) / (hx - p.x); // tangential

                    if ((tan  m.x)) &&
                        LocallyInside(p, hole))
                    {
                        m = p;
                        tanMin = tan;
                    }
                }

                p = p.next;
            }

            return m;
        }

        /// 
        /// 检测多边形的对角线是否在多边形内部
        /// 
        private static bool LocallyInside(Node a, Node b)
        {
            return Area(a.prev, a, a.next) != 0.0f ?
                Area(a, b, a.next) >= 0.0f && Area(a, a.prev, b) >= 0.0f :
                Area(a, b, a.prev) >= 0.0f && Area(a, a.next, b) 0.0f;
        }

        /// 
        /// 检测多边形对角线中心点是否在多边形内部
        /// 
        private static bool MiddleInside(Node a, Node b)
        {
            var p = a;
            var inside = false;
            var px = (a.x + b.x) * 0.5f;
            var pz = (a.z + b.z) * 0.5f;

            do
            {
                if (((p.z > pz) != (p.next.z > pz)) &&
                    (px  p.x)))
                {
                    inside = !inside;
                }

                p = p.next;
            } while (p != a);

            return inside;
        }

        /// 
        /// 判断多边形中的两点是否构成有效对角线
        /// 
        private static bool IsValidDiagonal(Node a, Node b)
        {
            return a.next.i != b.i &&
                a.prev.i != b.i &&
                !IntersectsPolygon(a, b) &&
                LocallyInside(a, b) &&
                LocallyInside(b, a) &&
                MiddleInside(a, b);
        }

        /// 
        /// 过滤掉共线或重复的结点
        /// 
        private static Node FilterPoints(Node start, Node end)
        {
            if (start == null) return start;

            if (end == null) end = start;

            var p = start;
            var again = false;

            do
            {
                again = false;

                if (!p.steiner && (Equals(p, p.next) || Area(p.prev, p, p.next) == 0.0f))
                {
                    var prev = p.prev;
                    RemoveNode(p);
                    p = end = prev;
                    if (p == p.next)
                    {
                        return null;
                    }

                    again = true;
                }
                else
                {
                    p = p.next;
                }
            } while (again || p != end);

            return end;
        }

        /// 
        /// 计算给定坐标点和外包大小的结点z-order(z-order of a point given coords and size of the data bounding box)
        /// 
        private static int ZOrder(float x, float z, float minX, float minZ, float size)
        {
            // coords are transformed into non-negative 15-bit integer range
            int _x = (int)(32767 * (x - minX) / size);
            int _z = (int)(32767 * (z - minZ) / size);

            _x = (_x | (_x 8)) & 0x00FF00FF;
            _x = (_x | (_x 4)) & 0x0F0F0F0F;
            _x = (_x | (_x 2)) & 0x33333333;
            _x = (_x | (_x 1)) & 0x55555555;

            _z = (_z | (_z 8)) & 0x00FF00FF;
            _z = (_z | (_z 4)) & 0x0F0F0F0F;
            _z = (_z | (_z 2)) & 0x33333333;
            _z = (_z | (_z 1)) & 0x55555555;

            return _x | (_z 1);
        }

        /// 
        /// 判断一个点是否在三角形内
        /// 
        /// true在,false不在
        private static bool PointInTriangle(Node a, Node b, Node c, Node d)
        {
            var SABC = Mathf.Abs(Area(a, b, c)) * 0.5f;
            var SADB = Mathf.Abs(Area(a, d, b)) * 0.5f;
            var SBDC = Mathf.Abs(Area(b, d, c)) * 0.5f;
            var SADC = Mathf.Abs(Area(a, d, c)) * 0.5f;

            var S = SABC - (SADB + SBDC + SADC);
            if (S > -EPSINON && S  EPSINON)
            {
                return true;
            }

            return false;
        }

        /// 
        /// 判断一个点是否在三角形内
        /// 
        /// true在,false不在
        private static bool PointInTriangle(float x0, float y0,
            float x1, float y1,
            float x2, float y2,
            float x3, float y3)
        {
            var SABC = Mathf.Abs(Area(x0, y0, x1, y1, x2, y2)) * 0.5f;
            var SADB = Mathf.Abs(Area(x0, y0, x3, y3, x1, y1)) * 0.5f;
            var SBDC = Mathf.Abs(Area(x1, y1, x3, y3, x2, y2)) * 0.5f;
            var SADC = Mathf.Abs(Area(x0, y0, x3, y3, x2, y2)) * 0.5f;

            var S = SABC - (SADB + SBDC + SADC);
            if (S > -EPSINON && S  EPSINON)
            {
                return true;
            }

            return false;
        }

        /// 
        /// 计算三角形有向面积(三角形面积的2倍)
        /// 
        /// 
        /// 结果大于0.0,p、q、r按逆时针排列
        /// 结果等于0.0,p、q、r在一条直线上
        /// 结果小于0.0,p、q、r按顺时针排列
        /// 
        /// 三角形有向面积
        private static float Area(Node p, Node q, Node r)
        {
            return Area(p.x, p.z, q.x, q.z, r.x, r.z);
        }

        /// 
        /// 计算三角形有向面积(三角形面积2倍)
        /// 
        /// 三角形有向面积
        private static float Area(float x0, float y0,
            float x1, float y1,
            float x2, float y2)
        {
            return x0 * y1 + x2 * y0 + x1 * y2 - x2 * y1 - x0 * y2 - x1 * y0;
        }

        /// 
        /// 计算多边形有向面积(多边形面积的2倍)
        /// 
        /// 顶点数据
        /// 起始顶点索引
        /// 结束顶点索引
        /// 
        /// 结果大于等于0.0,多边形顶点按顺时针排序
        /// 结果小于0.0,多边形顶点按逆时针排序
        /// 
        /// 多边形有向面积
        private static float SignedArea(List data, 


评论


亲,登录后才可以留言!