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 }
核心代码就这些,创建测试自己调调试试吧。
欲戴王冠 必承其重