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
- }
- }
封装三角形算法集合:
- /*
- *******************************************************
- *
- * 文件名称: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>();
- }
- }
多边形实体类
- 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 }
核心代码就这些,创建测试自己调调试试吧。
Unity中创建多边形并计算面积的更多相关文章
- 在Unity中创建攻击Slot系统
http://www.manew.com/thread-109310-1-1.html 马上注册,结交更多好友,享用更多功能,让你轻松玩转社区. 您需要 登录 才可以下载或查看,没有帐号?注册帐号 ...
- 在Unity中创建可远程加载的.unity3d包
在一个Unity项目中,发布包本身不一定要包括所有的Asset(译为资产或组件),其它的部分可以单独发布为.unity3d,再由程序从本地/远程加载执行,这部分不在本文讨论范围.虽然Unity并没有直 ...
- Unity中创建二维码
在网络上发现了一个可以把字符串转换成二维码的dll,但是我们要怎么使用他呢.不废话,直接进入主题. 用到的引用 using UnityEngine;using ZXing;using ZXing.Qr ...
- 在Unity中创建VR游戏
添加VR插件为了为您选择的平台创建VR游戏,我们需要下载几个插件.出于本教程的目的,我将向您展示如何上传到Android平台.要上传到iOS,您需要下载 Xcode. 现在让我们下载Unity的Goo ...
- unity 中UGUI制作滚动条视图效果(按钮)
1.在unity中创建一个Image作为滚动条视图的背景: 2.在Image下创建一个空物体,在空物体下创建unity自带的Scroll View组件: 3.对滑动条视图的子物体进行调整: 4.添加滚 ...
- Unity中通过深度优先算法和广度优先算法打印游戏物体名
前言:又是一个月没写博客了,每次下班都懒得写,觉得浪费时间.... 深度优先搜索和广度优先搜索的定义,网络上已经说的很清楚了,我也是看了网上的才懂的,所以就不在这里赘述了.今天讲解的实例,主要是通过自 ...
- Unity中的3D数学
3D数学(2022.11.25) 三角函数 Unity中会运用到角度制(Deg)和弧度制(Rad)的转换,弧度制是用圆的弧长来衡量角度的大小,π对应180度.这种转换在Unity中对应有两个方法: 角 ...
- Unity中动态创建Mesh
什么是Mesh? Mesh是指的模型的网格,3D模型是由多边形拼接而成,而多边形实际上又是由多个三角形拼接而成的.即一个3D模型的表面其实是由多个彼此相连的三角面构成.三维空间中,构成这些三角形的点和 ...
- SAP HANA中创建计算视图(Calculation View)
[Step By Step]SAP HANA中创建计算视图(Calculation View) Demo Instruction: 该视图将两个表AUDIOBOOKS和BOOKS中的数据进行连接,并作 ...
- 【Unity Shaders】Reflecting Your World —— 在Unity3D中创建Cubemaps
本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...
随机推荐
- 密钥存储在过时的 trusted.gpg 密钥环中(/etc/apt/trusted.gpg)
密钥存储在过时的 trusted.gpg 密钥环中(/etc/apt/trusted.gpg) 问题: 解决: cd /etc/opt sudo cp trusted.gpg trusted.gpg. ...
- Kafka源码分析(四) - Server端-请求处理框架
系列文章目录 https://zhuanlan.zhihu.com/p/367683572 一. 总体结构 先给一张概览图: 服务端请求处理过程涉及到两个模块:kafka.network和kafka. ...
- docker / compose 的安装 和 体验
文档 官网文档 视频 视频 简介 课程内容 1.Docker Compose 容器编排 2.Docker Swarm #集群 热扩容 需要在阿里上买服务器,至少冲100+以上的人民币 文档: 集群方式 ...
- JavaScript 实现前端文件下载
A.download HTML5的A标签有一个download属性,可以告诉浏览器下载而非预览文件,很实用,参考链接:http://www.zhangxinxu.com/wordpress/2016/ ...
- Spring6 的JdbcTemplate的JDBC模板类的详细使用说明
1. Spring6 的JdbcTemplate的JDBC模板类的详细使用说明 @ 目录 1. Spring6 的JdbcTemplate的JDBC模板类的详细使用说明 每博一文案 2. 环境准备 3 ...
- FFmpeg开发笔记(二十二)FFmpeg中SAR与DAR的显示宽高比
<FFmpeg开发实战:从零基础到短视频上线>一书提到:通常情况下,在视频流解析之后,从AVCodecContext结构得到的宽高就是视频画面的宽高.然而有的视频文件并非如此,如果按照A ...
- 详解RocketMQ消息存储原理
本文基于RocketMQ 4.6.0进行源码分析 一. 存储概要设计 RocketMQ存储的文件主要包括CommitLog文件.ConsumeQueue文件.Index文件.RocketMQ将所有to ...
- Flutter(七):Flutter混合开发--接入现有原生工程(iOS+Android)
在上一篇文章Flutter(六):Flutter_Boost接入现有原生工程(iOS+Android)中介绍了Flutter_Boost的接入方法,这一篇将介绍Flutter自带的接入方法. 新建工程 ...
- 你唯一需要的是“Wide Events”,而非“Metrics、Logs、Traces”
Charity Majors 的这句话可能是对科技行业当前可观察性状态的最好总结--完全的.大规模的混乱.大家都很困惑.什么是 trace?什么是 span?一行日志就是一个 span 吗?如果我有日 ...
- CICD介绍
1.学习背景 当公司的服务器架构越来越复杂,需要频繁的发布新配置文件,以及新代码: 但是如果机器部署数量较多,发布的效率必然很低: 并且如果代码没有经过测试环境,预生产环境层层测试,最终才到生产环境, ...