问题背景:

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

方案:

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

具体实现:

割耳算法类:

/*
*******************************************************
*
* 文件名称: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中创建多边形并计算面积的更多相关文章

  1. 在Unity中创建攻击Slot系统

    http://www.manew.com/thread-109310-1-1.html 马上注册,结交更多好友,享用更多功能,让你轻松玩转社区. 您需要 登录 才可以下载或查看,没有帐号?注册帐号  ...

  2. 在Unity中创建可远程加载的.unity3d包

    在一个Unity项目中,发布包本身不一定要包括所有的Asset(译为资产或组件),其它的部分可以单独发布为.unity3d,再由程序从本地/远程加载执行,这部分不在本文讨论范围.虽然Unity并没有直 ...

  3. Unity中创建二维码

    在网络上发现了一个可以把字符串转换成二维码的dll,但是我们要怎么使用他呢.不废话,直接进入主题. 用到的引用 using UnityEngine;using ZXing;using ZXing.Qr ...

  4. 在Unity中创建VR游戏

    添加VR插件为了为您选择的平台创建VR游戏,我们需要下载几个插件.出于本教程的目的,我将向您展示如何上传到Android平台.要上传到iOS,您需要下载 Xcode. 现在让我们下载Unity的Goo ...

  5. unity 中UGUI制作滚动条视图效果(按钮)

    1.在unity中创建一个Image作为滚动条视图的背景: 2.在Image下创建一个空物体,在空物体下创建unity自带的Scroll View组件: 3.对滑动条视图的子物体进行调整: 4.添加滚 ...

  6. Unity中通过深度优先算法和广度优先算法打印游戏物体名

    前言:又是一个月没写博客了,每次下班都懒得写,觉得浪费时间.... 深度优先搜索和广度优先搜索的定义,网络上已经说的很清楚了,我也是看了网上的才懂的,所以就不在这里赘述了.今天讲解的实例,主要是通过自 ...

  7. Unity中的3D数学

    3D数学(2022.11.25) 三角函数 Unity中会运用到角度制(Deg)和弧度制(Rad)的转换,弧度制是用圆的弧长来衡量角度的大小,π对应180度.这种转换在Unity中对应有两个方法: 角 ...

  8. Unity中动态创建Mesh

    什么是Mesh? Mesh是指的模型的网格,3D模型是由多边形拼接而成,而多边形实际上又是由多个三角形拼接而成的.即一个3D模型的表面其实是由多个彼此相连的三角面构成.三维空间中,构成这些三角形的点和 ...

  9. SAP HANA中创建计算视图(Calculation View)

    [Step By Step]SAP HANA中创建计算视图(Calculation View) Demo Instruction: 该视图将两个表AUDIOBOOKS和BOOKS中的数据进行连接,并作 ...

  10. 【Unity Shaders】Reflecting Your World —— 在Unity3D中创建Cubemaps

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

随机推荐

  1. 【2023知乎爬虫】我用Python爬虫爬了2386条知乎评论!

    目录 一.爬取目标 二.展示爬取结果 三.爬虫代码讲解 3.1 分析知乎页面 3.2 爬虫代码 四.同步视频 五.完整源码 您好,我是 @马哥python说,一枚10年程序猿. 一.爬取目标 前些天我 ...

  2. Docker 笔记汇总

    一.名词说明 Dockerfile 镜像构建文件 Docker Images 镜像:生成容器 Docker Containers 容器:微型系统 Docker Volumes 卷:存放容器运行数据 D ...

  3. 探索Django:从项目创建到图片上传的全方位指南

    Django是什么 Django 是一个流行的 Python Web 开发框架,它提供了一系列工具和库,用于帮助开发人员构建高效.可扩展的 Web 应用程序.Django 的目标是让开发者能够以快速和 ...

  4. 记一次Nacos漏洞的复现 --> 身份认证绕过漏洞(QVD-2023-6271)

    前记 端午前两天,遇到公司某客户的站点是Nacos,随后就是网上搜一波漏洞,搜到 QVD-2023-6271,故做以下记录 漏洞复现 漏洞描述 漏洞原理为开源服务管理平台 Nacos在默认配置下未对 ...

  5. CentOS7配置NFS服务并设置客户端自动挂载

    在CentOS7中配置NFS服务并设置客户端自动挂载的步骤如下: NFS服务端配置 安装NFS服务: 首先,你需要在CentOS 7服务器上安装NFS服务.你可以使用yum命令来安装: yum ins ...

  6. C# 泛型里使用四则运算的办法,委托的妙用

    直接上代码 public static class TestGenricCalc { public static T Clac<T>(T t1, T t2,Func<T,T,T> ...

  7. 对比使用IConfigurationSectionHandler和ConfigurationSection自定义节点和自定义处理程序

    使用自定义处理程序处理节点的好处我认为是,可以在业务代码中,不必再写一些读取配置文件的非业务代码了,只管取值.如果多处使用该配置节点,其优势更能体现出来.然后突然要增删配置,也只管改节点处理程序的代码 ...

  8. 源码分析——MyBatis与Spring整合后如何保证SqlSession线程安全

    在MyBatis架构中SqlSession是提供给外层调用的顶层接口,它是MyBatis对外暴露的最重要的接口,用户通过该接口即可完成数据库的全部操作.在上文中我们明白了我们常用的Mybatis动态代 ...

  9. Nginx 修饰符 Location 详解

    概述 location 指令可以用在虚拟服务器 server 部分,并且意味着提供来自客户端的 URI 或者内部重定向访问. location 的定义如下: location [modifier] u ...

  10. Mybatis-Plus update不存在的数据返回值一定为零?

    MP update不存在的数据返回值一定为零? 本文分为以下几个部分: 前言 验证过程 结论 前言 ​ MP(mybatis-plus),在 MyBatis 的基础上只做增强不做改变,为简化开发.提高 ...