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

问题背景:

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

方案:

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

具体实现:

割耳算法类:

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

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

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

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

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

            /// <summary>
            ///  vertex coordinates
            /// </summary>
            public float x = 0.0f;
            public float z = 0.0f;

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

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

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

            /// <summary>
            /// indicates whether this is a steiner point
            /// </summary>
            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

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

            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 < outerLength; i++)
                {
                    float x = data[i].x;
                    float z = data[i].z;
                    if (x < minX) minX = x;
                    if (z < minZ) minZ = 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

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

            if (clockwise == (SignedArea(data, start, end) >= 0.0))
            {
                for (int i = start; i < end; 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;
        }

        /// <summary>
        /// “割耳”主循环
        /// </summary>
        /// <remarks>
        /// main ear slicing loop which triangulates a polygon (given as a linked list)
        /// </remarks>
        private static void EarCutLinked(Node ear, List<int> 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;
                }
            }
        }

        /// <summary>
        /// 尝试将多边形分割成两个,并分别进行三角化
        /// </summary>
        private static void SplitEarCut(Node start, List<int> 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);
        }

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

            for (int i = 0, len = holeIndices.Count; i < len; i++)
            {
                var start = holeIndices[i];
                var end = i < len - 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 < queue.Count; i++)
            {
                var node = EliminateHole(queue[i], outerNode);
                if (node != null)
                {
                    outerNode = FilterPoints(node, node.next);
                }
            }

            return outerNode;
        }

        /// <summary>
        /// find a bridge between vertices that connects hole with an outer ring and and link it
        /// </summary>
        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;
        }

        /// <summary>
        /// 遍历多边形所有结点,校正局部自相交情形
        /// </summary>
        private static Node CureLocalIntersections(Node start, List<int> 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;
        }

        /// <summary>
        /// 插入一个结点
        /// </summary>
        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;
        }

        /// <summary>
        /// 移除一个结点
        /// </summary>
        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;
            }
        }

        /// <summary>
        /// 判断两个结点是否相等
        /// </summary>
        /// <returns>true相等,false不相等</returns>
        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;
        }

        /// <summary>
        /// 判断是否是“耳朵”
        /// </summary>
        /// <param name="ear"></param>
        /// <returns></returns>
        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;
        }

        /// <summary>
        /// 判断是否是“耳朵”散列?
        /// </summary>
        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 ? (a.x < c.x ? a.x : c.x) : (b.x < c.x ? b.x : c.x);
            var minTZ = a.z < b.z ? (a.z < c.z ? a.z : c.z) : (b.z < c.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;
        }

        /// <summary>
        /// 通过对角线分割多边形
        /// </summary>
        /// <remarks>
        /// 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
        /// </remarks>
        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;
        }

        /// <summary>
        /// 对结点进行排序
        /// </summary>
        /// <remarks>
        /// Simon Tatham's linked list merge sort algorithm
        /// </remarks>
        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 < inSize; 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;
        }

        /// <summary>
        /// 相邻多边形节点次序(interlink polygon nodes in z-order)
        /// </summary>
        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);
        }

        /// <summary>
        /// 判断两条线段是否相交
        /// </summary>
        /// <remarks>
        /// </remarks>
        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);
        }

        /// <summary>
        /// 检测多边形的对角线是否与多边形的边相交
        /// </summary>
        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;
        }

        /// <summary>
        /// 查找多边形最坐标结点
        /// </summary>
        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;
        }

        /// <summary>
        /// 查找多边形内部洞与外边的连接点
        /// </summary>
        /// <remarks>David Eberly's algorithm</remarks>
        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.z && hz >= p.next.z) ||
                    (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 <= hx && 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.next.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 < mz ? hx : qx, hz, mx, mz, hz < mz ? qx : hx, hz, p.x, p.z))
                {
                    var tan = Mathf.Abs(hz - p.z) / (hx - p.x); // tangential

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

                p = p.next;
            }

            return m;
        }

        /// <summary>
        /// 检测多边形的对角线是否在多边形内部
        /// </summary>
        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;
        }

        /// <summary>
        /// 检测多边形对角线中心点是否在多边形内部
        /// </summary>
        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.next.x - px) * (pz - p.z) / (p.next.z - p.z) + p.x)))
                {
                    inside = !inside;
                }

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

            return inside;
        }

        /// <summary>
        /// 判断多边形中的两点是否构成有效对角线
        /// </summary>
        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);
        }

        /// <summary>
        /// 过滤掉共线或重复的结点
        /// </summary>
        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;
        }

        /// <summary>
        /// 计算给定坐标点和外包大小的结点z-order(z-order of a point given coords and size of the data bounding box)
        /// </summary>
        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);
        }

        /// <summary>
        /// 判断一个点是否在三角形内
        /// </summary>
        /// <returns>true在,false不在</returns>
        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;
        }

        /// <summary>
        /// 判断一个点是否在三角形内
        /// </summary>
        /// <returns>true在,false不在</returns>
        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;
        }

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

        /// <summary>
        /// 计算三角形有向面积(三角形面积2倍)
        /// </summary>
        /// <returns>三角形有向面积</returns>
        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;
        }

        /// <summary>
        /// 计算多边形有向面积(多边形面积的2倍)
        /// </summary>
        /// <param name="data">顶点数据</param>
        /// <param name="start">起始顶点索引</param>
        /// <param name="end">结束顶点索引</param>
        /// <remarks>
        /// 结果大于等于0.0,多边形顶点按顺时针排序
        /// 结果小于0.0,多边形顶点按逆时针排序
        /// </remarks>
        /// <returns>多边形有向面积</returns>
        private static float SignedArea(List<Vector3> data, int start, int end)
        {
            var sum = 0.0f;

            for (int i = start; i < end; i++)
            {
                var next = (i + 1) == end ? start : i + 1;
                var dx = data[next].x - data[i].x;
                var dz = data[next].z + data[i].z;
                sum += (dx * dz);
            }

            return sum;
        }

        #endregion
    }
}
View Code

 

封装三角形算法集合:

/*
 *******************************************************
 * 
 * 文件名称:TriangulateUtil
 * 文件描述:三角化相关算法集合
 * *****************************************************
 */

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

namespace Tx3d.Framework
{
    /// <summary>
    /// 三角化相关算法集合
    /// </summary>
    public class TriangulateUtil
    {
        #region Public Methods       

        /// <summary>
        /// 多边形三角化
        /// </summary>
        /// <param name="polygon"></param>
        /// <returns>返回顺时针方向的三角形数据</returns>
        public static PolygonData GeneratePolygon(List<Vector3> polygon)
        {
            PolygonData polygonData = new PolygonData();

            //保证是顺时针队列
            if (!IsClockwise(polygon))
            {
                //排序
                polygonData.Vertices.AddRange(Reverse(polygon));
            }
            else
                polygonData.Vertices.AddRange(polygon);

            //不带洞的多边形
            polygonData.Indices = EarCut.CutEar(polygonData.Vertices, new List<int>());
            return polygonData;
        }

        /// <summary>
        /// 带洞多边形三角化
        /// </summary>
        /// <param name="polygon">多边形</param>
        /// <param name="indices">孔洞数组</param>
        /// <returns>返回顺时针方向的三角形数据</returns>
        public static PolygonData GeneratePolygon(List<Vector3> polygon, List<Vector3>[] holes)
        {
            PolygonData polygonData = new PolygonData();

            //保证是顺时针队列
            if (!IsClockwise(polygon))
            {
                //排序
                polygonData.Vertices.AddRange(Reverse(polygon));
            }
            else
                polygonData.Vertices.AddRange(polygon);

            var holeIndices = new List<int>();
            int offset = polygon.Count;

            //孔洞需要逆时针
            foreach (var hole in holes)
            {
                if (IsClockwise(hole))
                {
                    //排序
                    polygonData.Vertices.AddRange(Reverse(hole));
                }
                else
                    polygonData.Vertices.AddRange(hole);

                holeIndices.Add(offset);
                offset += hole.Count;
            }

            //带洞的多边形
            polygonData.Indices = EarCut.CutEar(polygonData.Vertices, holeIndices);
            return polygonData;
        }
        /// <summary>
        /// 判断坐标序列是否是顺时针排列
        /// </summary>
        /// <param name="ring">坐标序列</param>
        /// <returns>true顺时针,false逆时针</returns>
        public static bool IsClockwise(List<Vector3> ring)
        {
            var val = 0.0;

            for (int i = 0, il = ring.Count; i < il; i++)
            {
                var next = (i + 1) % il;
                var point = ring[i];
                var nextPoint = ring[next];

                val += (nextPoint.x - point.x) * (nextPoint.z + point.z);
            }

            return val > 0.0;
        }

        /// <summary>
        /// 计算三角面法向量
        /// </summary>
        /// <param name="a">顶点a</param>
        /// <param name="b">顶点b</param>
        /// <param name="c">顶点c</param>
        /// <returns>单位化的法向量</returns>
        public static Vector3 CalculateFaceNormal(Vector3 a, Vector3 b, Vector3 c)
        {
            var side1 = b - a;
            var side2 = c - a;
            var n = Vector3.Cross(side1, side2);

            return n.normalized;
        }
        /// <summary>
        /// 射线和三角面相交检测
        /// </summary>
        /// <param name="ray"></param>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <param name="c"></param>
        /// <param name="distance"></param>
        /// <param name="positiveSide">是否检测正面</param>
        /// <param name="negativeSide">是否检测反面</param>
        /// <returns></returns>
        public static bool Raycast(Ray ray,Vector3 a, Vector3 b, Vector3 c,out float distance, bool positiveSide = true, bool negativeSide = true)
        {
            distance = 0;

            Vector3 normal = CalculateFaceNormal(a, b, c);
            float t;
            {
                float denom = Vector3.Dot(normal, ray.direction);

                // Check intersect side
                if (denom > +float.Epsilon)
                {
                    if (!negativeSide)
                        return false;
                }
                else if (denom < -float.Epsilon)
                {
                    if (!positiveSide)
                        return false;
                }
                else
                {
                    // Parallel or triangle area is close to zero when
                    // the plane normal not normalised.
                    return false;
                }

                t = Vector3.Dot(normal, a - ray.origin) / denom;

                if (t < 0)
                {
                    // Intersection is behind origin
                    return false;
                }
            }

            //
            // Calculate the largest area projection plane in X, Y or Z.
            //
            int i0, i1;
            {
                float n0 = Mathf.Abs(normal[0]);
                float n1 = Mathf.Abs(normal[1]);
                float n2 = Mathf.Abs(normal[2]);

                i0 = 1; i1 = 2;
                if (n1 > n2)
                {
                    if (n1 > n0) i0 = 0;
                }
                else
                {
                    if (n2 > n0) i1 = 0;
                }
            }

            //
            // Check the intersection point is inside the triangle.
            //
            {
                float u1 = b[i0] - a[i0];
                float v1 = b[i1] - a[i1];
                float u2 = c[i0] - a[i0];
                float v2 = c[i1] - a[i1];
                float u0 = t * ray.direction[i0] + ray.origin[i0] - a[i0];
                float v0 = t * ray.direction[i1] + ray.origin[i1] - a[i1];

                float alpha = u0 * v2 - u2 * v0;
                float beta = u1 * v0 - u0 * v1;
                float area = u1 * v2 - u2 * v1;

                // epsilon to avoid float precision error
                const float EPSILON = 1e-6f;

                float tolerance = -EPSILON * area;

                if (area > 0)
                {
                    if (alpha < tolerance || beta < tolerance || alpha + beta > area - tolerance)
                        return false;
                }
                else
                {
                    if (alpha > tolerance || beta > tolerance || alpha + beta < area - tolerance)
                        return false;
                }
            }
            distance = t;

            return true;
        }
        /// <summary>
        /// 反转坐标序列
        /// </summary>
        /// <param name="coords"></param>
        /// <returns></returns>
        private static List<Vector3> Reverse(List<Vector3> coords)
        {
            List<Vector3> result = new List<Vector3>();
            for (int i = coords.Count - 1; i >= 0; i--)
            {
                result.Add(coords[i]);
            }
            return result;
        }

        #endregion
    }

    /// <summary>
    /// 多边形数据
    /// </summary>
    public class PolygonData
    {
        /// <summary>
        /// 顶点数据
        /// </summary>
        public List<Vector3> Vertices = new List<Vector3>();
        /// <summary>
        /// 索引数据
        /// </summary>
        public List<int> Indices = new List<int>();
    }
}
View Code

 

多边形实体类

  1 /// <summary>
  2 /// 矢量面实体
  3 /// </summary>
  4 public class PloygonEntity
  5 {
  6     #region 字段
  7 
  8     /// <summary>
  9     /// 点集
 10     /// </summary>
 11     private List<Vector3> points = new List<Vector3>();
 12 
 13     /// <summary>
 14     /// 顶点集合
 15     /// </summary>
 16     private List<Vector3> vertexs = new List<Vector3>();
 17 
 18     /// <summary>
 19     /// 索引集合
 20     /// </summary>
 21     private List<int> triangles = new List<int>();
 22 
 23     /// <summary>
 24     /// 实体
 25     /// </summary>
 26     private GameObject obj;
 27 
 28     private MeshFilter meshFilter;
 29 
 30     private MeshRenderer meshRenderer;
 31 
 32     private Material material;
 33 
 34     #endregion
 35 
 36 
 37     #region Methods
 38 
 39     #region Public
 40 
 41     /// <summary>
 42     /// 创建矢量面实体
 43     /// </summary>
 44     /// <param name="points"></param>
 45     public PloygonEntity(List<Vector3> points)
 46     {
 47         this.points = points;
 48          SetVertexTrianglesData();
 49          RenderPloygon();
 50     }
 51 
 52     public void AddPoint(Vector3 point)
 53     {
 54         point.y = 0;
 55         points.Add(point);
 56         SetVertexTrianglesData();
 57         RenderPloygon();
 58     }
 59 
 60     public void ChangePoint(Vector3 vector3)
 61     {
 62         if (points.Count>0)
 63         {
 64             vector3.y = 0;
 65             points[points.Count - 1] = vector3;
 66             SetVertexTrianglesData();
 67             RenderPloygon();
 68         }
 69     }
 70 
 71     #endregion
 72 
 73 
 74     #region Private
 75 
 76     /// <summary>
 77     /// 设置顶点三角形数据
 78     /// </summary>
 79     private void SetVertexTrianglesData()
 80     {
 81         for (int i = 0; i < points.Count; i++)
 82         {
 83             Debug.LogError(points[i]);
 84         }
 85         PolygonData data = GetPolygonData(points);
 86         vertexs = data.Vertices;
 87         triangles = data.Indices;
 88     }
 89 
 90     /// <summary>
 91     /// 渲染面
 92     /// </summary>
 93     private void RenderPloygon()
 94     {
 95         if (obj == null)
 96             obj = new GameObject();
 97 
 98         if (meshFilter == null)
 99             meshFilter = obj.AddComponent<MeshFilter>();
100 
101         meshFilter.mesh = new Mesh
102         {
103             vertices = vertexs.ToArray(),
104             triangles = triangles.ToArray(),
105         };
106 
107         meshFilter.mesh.RecalculateNormals();
108 
109         if (meshRenderer == null)
110             meshRenderer = obj.AddComponent<MeshRenderer>();
111 
112         SetMatraial();
113         meshRenderer.material = material;
114 
115         if (points.Count > 2)
116         {
117             UpdateLineGameObject();
118         }
119     }
120 
121     /// <summary>
122     /// 设置材质
123     /// </summary>
124     private void SetMatraial()
125     {
126         if (material==null)
127         {
128             material = new Material(Resources.Load<Shader>("shader/Ploygon"));
129         }
130         material.SetColor("_PloygonColor", Color.yellow);
131         material.SetFloat("_Scale", 1.2f);
132         material.SetVector("_CenterPointPosition", GetCenterPointPos());
133 
134     }
135 
136     // <summary>
137     /// 根据顶点集合获取矢量面数据
138     /// </summary>
139     private PolygonData GetPolygonData(List<Vector3> points)
140     {
141         return TriangulateUtil.GeneratePolygon(points);
142     }
143 
144     private Vector3 GetCenterPointPos()
145     {
146         Vector3 point = Vector3.zero;
147         for (int i = 0; i < points.Count; i++)
148         {
149             point += Camera.main.WorldToScreenPoint(points[i]);
150         }
151         point /= points.Count;
152         return point;
153     }
154 
155 
156     /// <summary>
157     /// 创建边框线Mesh
158     /// </summary>
159     /// <param name="start">线起点</param>
160     /// <param name="end">线终点</param>
161     /// <returns>Mesh对象</returns>
162     private Mesh CreateLineMesh()
163     {
164         List<Vector3> vertex =new List<Vector3> ();
165         var indices = new List<int>();
166         for (int i = 0; i < points.Count; i++)
167         {
168             vertex .Add(points[i]);
169             indices.Add(i);
170             if (i > 0 && i < points.Count - 1)
171             {
172                 indices.Add(i);
173             }
174         }
175         for (int i = points.Count - 1; i >= 0; i--)
176         {
177             indices.Add(i);
178             if (i > 0 && i < points.Count - 1)
179             {
180                 indices.Add(i);
181             }
182         }
183 
184         Mesh mesh = new Mesh();
185         mesh.SetVertices(points);
186         mesh.SetIndices(indices.ToArray(), MeshTopology.Lines, 0);
187 
188         return mesh;
189     }
190 
191 
192     MeshFilter goMeshFilter;
193     MeshRenderer goMeshRenderer;
194     GameObject go;
195 
196     private void UpdateLineGameObject()
197     {
198         if (go==null)
199         {
200             go = new GameObject("line"); 
201         }
202 
203         if (goMeshFilter==null)
204         {
205             goMeshFilter = go.AddComponent<MeshFilter>();
206         }
207         goMeshFilter.mesh = CreateLineMesh();
208 
209         if (goMeshRenderer==null)
210         {
211             goMeshRenderer = go.AddComponent<MeshRenderer>();
212         }
213         goMeshRenderer.material.color = Color.red;
214     }
215 
216     #endregion
217 
218     #endregion
219 
220 }
View Code

 

核心代码就这些,创建测试自己调调试试吧。

posted @ 2020-04-26 18:12  望景思心  阅读(1790)  评论(0编辑  收藏  举报