zoukankan      html  css  js  c++  java
  • 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

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

  • 相关阅读:
    贡献15本经典C、C++、MFC、VC++教程,都是pdf完整版的
    雪花
    孙鑫C++视频教程 rmvb格式 全20CD完整版 精品分享
    mac上用VMWare虚拟机装win7
    阿里云如何解析域名,搭建云服务器环境
    2. Windows编程基础
    复制指定目录下的全部文件到另一个目录中
    Select查询命令
    使用OneNote2016发送博客
    Linux数字雨
  • 原文地址:https://www.cnblogs.com/answer-yj/p/12697378.html
Copyright © 2011-2022 走看看