多边形的创建
Unity中使用Mesh类创建多边形,一般是将多边形划分为三角形后逐一拼接构成。
凸多边形的划分较为简单:
在多边形的任意一边上任取一点P,连接P点与其不相邻的其它各顶点的线段,可以把多边形边形分成N个三角形。
凹多边形划分时上述方法不再适用,因此可以使用耳切法进行划分,耳切法较为详细的讲述:
1.原篇:
Triangulation by Ear Clipping (geometrictools.com)
2.翻译:Triangulation by Ear Clipping(耳切法处理多边形三角划分) - 行走_ - 博客园 (cnblogs.com)
耳切法划分多边形为三角形
ref:(21条消息) 判断多边形的顺逆性_Leopard-C的博客-优快云博客
ref:(21条消息) 使用耳切法将多边形三角化_贪玩的孩纸时代的博客-优快云博客
using System;
using System.Collections.Generic;
using UnityEngine;
namespace PolygonTool
{
/// <summary>
/// 判断凹点,凸点,耳朵的比较轴
/// </summary>
public enum CompareAxle
{
X,
YT,
YF,
Z
}
/// <summary>
/// 对多边形处理
/// </summary>
public class Triangulation
{
/// <summary>
/// 判断凹凸的时候的比对轴
/// </summary>
private CompareAxle _compareAxle = CompareAxle.YT;
/// <summary>
/// 多边形顶点
/// </summary>
private List<Vector3> _polygonVertexs = new List<Vector3>();
/// <summary>
/// 顶点序列
/// </summary>
private List<int> _vertexsSequence = new List<int>();
/// <summary>
/// 节点管理器
/// </summary>
private NodeManager _nodeManager = new NodeManager();
/// <summary>
/// 初始化
/// </summary>
/// <param name="polygonVertexs">多边形顶点</param>
public Triangulation(List<Vector3> polygonVertexs)
{
this._polygonVertexs = polygonVertexs;
_nodeManager.Init(polygonVertexs);
}
/// <summary>
/// 设置比较轴
/// </summary>
/// <param name="compareAxle"></param>
public void SetCompareAxle(CompareAxle compareAxle)
{
this._compareAxle = compareAxle;
}
/// <summary>
/// 设置比较轴
/// </summary>
/// <param name="polygonVertexs"></param>
public void SetCompareAxle(bool clockwise)
{
if (clockwise)
{
this._compareAxle = CompareAxle.YT;
}
else
{
this._compareAxle = CompareAxle.YF;
}
}
/// <summary>
/// 获取三角形的顶点序列
/// </summary>
public int[] GetTriangles()
{
while (_nodeManager.LinkedListLength >= 3)
{
SplitResult sr = SplitPolygon();
//
if (sr == null)
{
Debug.Log("null");
return null;
}
}
return _vertexsSequence.ToArray();
}
/// <summary>
/// 计算凹顶点,凸顶点,耳朵
/// </summary>
private SplitResult SplitPolygon()
{
//凹点
List<Node> _concaveVertexs = new List<Node>();
//凸点
List<Node> _raisedVertexs = new List<Node>();
//耳朵
List<Node> _polygonEars = new List<Node>();
//起始节点
Node currentNode = _nodeManager.FirstNode;
#region 计算凹顶点,凸顶点
for (int i = 0; i < _nodeManager.LinkedListLength; i++)
{
Vector3 one = currentNode.vertex - currentNode.lastNode.vertex;
Vector3 two = currentNode.nextNode.vertex - currentNode.vertex;
Vector3 crossRes = Vector3.Cross(one, two);
if (_compareAxle == CompareAxle.YT)
{
if (crossRes.y < 0)
{
_concaveVertexs.Add(currentNode);
}
else
{
_raisedVertexs.Add(currentNode);
}
}
if (_compareAxle == CompareAxle.YF)
{
if (crossRes.y > 0)
{
_concaveVertexs.Add(currentNode);
}
else
{
_raisedVertexs.Add(currentNode);
}
}
if (_compareAxle == CompareAxle.X)
{
if (crossRes.x > 0)
{
_concaveVertexs.Add(currentNode);
}
else
{
_raisedVertexs.Add(currentNode);
}
}
if (_compareAxle == CompareAxle.Z)
{
if (crossRes.z > 0)
{
_concaveVertexs.Add(currentNode);
}
else
{
_raisedVertexs.Add(currentNode);
}
}
_polygonEars.Add(currentNode);
currentNode = currentNode.nextNode;
}
for (int i = 0; i < _concaveVertexs.Count; i++)
{
_polygonEars.Remove(_concaveVertexs[i]);
}
#endregion
#region 计算耳朵
List<int> needRemoveIdList = new List<int>();
for (int i = 0; i < _polygonEars.Count; i++)
{
Node earNode = _polygonEars[i];
Node compareNode = earNode.nextNode.nextNode;
while (compareNode != earNode.lastNode)
{
bool isIn = IsIn(compareNode.vertex, earNode.lastNode.vertex, earNode.vertex,
earNode.nextNode.vertex);
if (isIn == true)
{
if (_polygonEars.Contains(_polygonEars[i]))
{
needRemoveIdList.Add(_polygonEars[i].id);
}
break;
}
compareNode = compareNode.nextNode;
}
}
for (int j = 0; j < needRemoveIdList.Count; j++)
{
for (int i = 0; i < _polygonEars.Count; i++)
{
if (_polygonEars[i].id == needRemoveIdList[j])
{
_polygonEars.RemoveAt(i);
}
}
}
#endregion
#region 打印初始化结果
//Debug.Log("凸点");
//for (int i = 0; i < _raisedVertexs.Count; i++)
//{
// Debug.Log(_raisedVertexs[i].id);
//}
//Debug.Log("凹点");
//for (int i = 0; i < _concaveVertexs.Count; i++)
//{
// Debug.Log(_concaveVertexs[i].id);
//}
//Debug.Log("耳朵");
//for (int i = 0; i < _polygonEars.Count; i++)
//{
// Debug.Log(_polygonEars[i].id);
//}
//Debug.Log("-----------------------------------------------");
#endregion
//耳朵为空说明不是简单多边形 多边形三角化失败
if (_polygonEars.Count == 0)
{
return null;
}
_vertexsSequence.Add(_polygonEars[0].lastNode.id);
_vertexsSequence.Add(_polygonEars[0].id);
_vertexsSequence.Add(_polygonEars[0].nextNode.id);
_nodeManager.RemoveNode(_polygonEars[0]);
return new SplitResult(_raisedVertexs, _concaveVertexs, _polygonEars);
}
/// <summary>
/// 判断一点是否在三角形内
/// </summary>
/// <param name="p">一点</param>
/// <param name="a"></param>
/// <param name="b"></param>
/// <param name="c"></param>
/// <returns></returns>
public bool IsIn(Vector3 p, Vector3 a, Vector3 b, Vector3 c)
{
Vector3 pa = p - a;
Vector3 pb = p - b;
Vector3 pc = p - c;
Vector3 t1 = Vector3.Cross(pa, pb);
Vector3 t2 = Vector3.Cross(pb, pc);
Vector3 t3 = Vector3.Cross(pc, pa);
bool isIn2 = t1.y >= 0 && t2.y >= 0 && t3.y >= 0 || t1.y <= 0 && t2.y <= 0 && t3.y <= 0;
return isIn2;
}
public bool IsClockwise_1(List<Vector3> points)
{
List<MYPOINT> pts = new List<MYPOINT>();
for (int i = 0; i < points.Count; i++)
{
MYPOINT m = new MYPOINT(points[i].x, points[i].z);
pts.Add(m);
}
double d = 0;
int size = pts.Count;
for (int i = 0; i < size - 1; i++)
{
d += -0.5 * (pts[i + 1].y + pts[i].y) * (pts[i + 1].x - pts[i].x);
}
// 注意,这个容易忽略!最后一个点与第一个点构成的边
d += -0.5 * (pts[0].y + pts[size - 1].y) * (pts[0].x - pts[size - 1].x);
if (d < 0)
return true;
else
return false;
}
/// <summary>
/// 管理多边形 构成一个双向链表
/// </summary>
public class NodeManager
{
private List<Node> _nodeList = new List<Node>();
public int LinkedListLength
{
get { return _nodeList.Count; }
}
public Node FirstNode
{
get { return _nodeList[0]; }
}
public void Init(List<Vector3> vertexs)
{
for (int i = 0; i < vertexs.Count; i++)
{
Node node = new Node(i, vertexs[i]);
_nodeList.Add(node);
}
for (int i = 0; i < LinkedListLength; i++)
{
if (i == 0)
{
_nodeList[i].lastNode = _nodeList[LinkedListLength - 1];
_nodeList[i].nextNode = _nodeList[1];
}
else if (i == LinkedListLength - 1)
{
_nodeList[i].lastNode = _nodeList[LinkedListLength - 2];
_nodeList[i].nextNode = _nodeList[0];
}
else
{
_nodeList[i].lastNode = _nodeList[i - 1];
_nodeList[i].nextNode = _nodeList[i + 1];
}
}
}
public void RemoveNode(Node node)
{
_nodeList.Remove(node);
node.lastNode.nextNode = node.nextNode;
node.nextNode.lastNode = node.lastNode;
}
}
public class Node
{
public int id;
public Vector3 vertex;
public Node lastNode;
public Node nextNode;
public Node(int id, Vector3 vertex)
{
this.id = id;
this.vertex = vertex;
}
public Node(int id, Vector3 vertex, Node lastNode, Node nextNode)
{
this.id = id;
this.vertex = vertex;
this.lastNode = lastNode;
this.nextNode = nextNode;
}
}
public class SplitResult
{
/// <summary>
/// 凸顶点
/// </summary>
public List<Node> raisedVertexs;
/// <summary>
/// 凹顶点
/// </summary>
public List<Node> concaveVertexs;
/// <summary>
/// 耳朵
/// </summary>
public List<Node> polygonEars;
public SplitResult(List<Node> raisedVertexs, List<Node> concaveVertexs, List<Node> polygonEars)
{
this.raisedVertexs = raisedVertexs;
this.concaveVertexs = concaveVertexs;
this.polygonEars = polygonEars;
}
}
//自定义MyPoint类 多边形顶点顺逆时针方向判断
public class MYPOINT
{
public double x;
public double y;
public MYPOINT(double xx, double yy)
{
x = xx;
y = yy;
}
}
}
}
多边形创建(立方体)
points为多边形的顶点集合链表
vert为多边形顶点位置链表
indices为多边形顶点绘制序号链表
private GameObject CreateMesh()
{
#region 底面添加
Triangulation triangulation = new Triangulation(points);
bool clockwise = triangulation.IsClockwise_1(points);
triangulation.SetCompareAxle(clockwise);
int[] trisIndex_Bottom = triangulation.GetTriangles();
for (int i = 0; i < trisIndex_Bottom.Length; i++)
{
verts.Add(points[trisIndex_Bottom[i]]);
}
for (int i = 0; i < trisIndex_Bottom.Length; i += 3)
{
if (clockwise)
{
indices.Add(i);
indices.Add(i + 2);
indices.Add(i + 1);
}
else
{
indices.Add(i + 1);
indices.Add(i + 2);
indices.Add(i);
}
}
#endregion
#region 顶面添加
for (int i = 0; i < points.Count; i++)
{
points_top.Add(points[i] + new Vector3(0, 1, 0));
}
Triangulation triangulation_top = new Triangulation(points_top);
triangulation_top.SetCompareAxle(clockwise);
int[] trisIndex_Top = triangulation_top.GetTriangles();
for (int i = 0; i < trisIndex_Top.Length; i++)
{
verts.Add(points_top[trisIndex_Top[i]]);
}
for (int i = 0; i < trisIndex_Top.Length; i += 3)
{
if (clockwise)
{
indices.Add(i + trisIndex_Bottom.Length + 1);
indices.Add(i + trisIndex_Bottom.Length + 2);
indices.Add(i + trisIndex_Bottom.Length);
}
else
{
indices.Add(i + trisIndex_Bottom.Length);
indices.Add(i + trisIndex_Bottom.Length + 2);
indices.Add(i + trisIndex_Bottom.Length + 1);
}
}
#endregion
#region 侧面添加
for (int i = 0; i < points.Count; i++)
{
verts.Add(points[i]);
verts.Add(points[i + 1 == points.Count ? 0 : i + 1]);
verts.Add(points_top[i]);
verts.Add(points_top[i + 1 == points.Count ? 0 : i + 1]);
}
for (int i = 0; i < points.Count; i++)
{
indices.Add(clockwise? trisIndex_Bottom.Length * 2 + (i * 4) + 1: trisIndex_Bottom.Length * 2 + (i * 4));
indices.Add(trisIndex_Bottom.Length * 2 + (i * 4) + 3);
indices.Add(clockwise ? trisIndex_Bottom.Length * 2 + (i * 4): trisIndex_Bottom.Length * 2 + (i * 4) + 1);
indices.Add(clockwise ? trisIndex_Bottom.Length * 2 + (i * 4) + 3: trisIndex_Bottom.Length * 2 + (i * 4));
indices.Add(trisIndex_Bottom.Length * 2 + (i * 4) + 2);
indices.Add(clockwise ? trisIndex_Bottom.Length * 2 + (i * 4): trisIndex_Bottom.Length * 2 + (i * 4) + 3);
}
#endregion
#region 网格数据整理
Mesh mesh = new Mesh();
mesh.SetVertices(verts);
mesh.SetIndices(indices, MeshTopology.Triangles, 0);
mesh.RecalculateNormals();
mesh.RecalculateBounds();
#endregion
#region 对象创建
GameObject meshObj = new GameObject("mesh");
MeshFilter filter = meshObj.AddComponent<MeshFilter>();
MeshRenderer renderer = meshObj.AddComponent<MeshRenderer>();
renderer.material = material;
filter.mesh = mesh;
return meshObj;
#endregion
}