代码创建网格和2D多边形碰撞体
发表于2018-12-06
主要实现四个功能:
1.利用PolygonCollider2D碰撞体创建Mesh网格
2.利用Mesh网格创建单一PolygonCollider2D碰撞体
3.利用Mesh网格创建复合PolygonCollider2D碰撞体
4.根据Mesh网格大小形状计算刚体质量
通过Mesh与PolygonCollider2D的数据转化,完成创建,注释有详细说明:
using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine; public class AdjustColliderOrMesh : MonoBehaviour { [SerializeField] bool useMass; [SerializeField] float massCoefficient = 1; [HideInInspector] public Vector3 centerPoint; //按照PolygonCollider2D碰撞体的大小形状,创建MeshFilter网格 public void ApplyColliderToMesh() { MeshFilter meshFilter = GetComponent<MeshFilter>(); PolygonCollider2D polygon = GetComponent<PolygonCollider2D>(); if (polygon == null || meshFilter == null) return; ApplyColliderToMesh(polygon, meshFilter); RecaculateMass(); SaveInEditor(); } //按照Mesh网格的大小形状,创建PolygonCollider2D碰撞体 //只能是由一条闭合折线围成的网格,即不可以有内部孔或嵌套多个多边形 public void ApplyMeshToCollider() { MeshFilter meshFilter = GetComponent<MeshFilter>(); PolygonCollider2D polygon = GetComponent<PolygonCollider2D>(); if (polygon == null || meshFilter == null) return; ApplyMeshToCollider(meshFilter, polygon); RecaculateMass(); SaveInEditor(); } //按照Mesh网格的大小形状,创建PolygonCollider2D碰撞体 //可以是由多条独立的闭合折线围成的网格,即可以有内部孔或嵌套多个多边形 public void ApplyPathToCollider() { MeshFilter meshFilter = GetComponent<MeshFilter>(); PolygonCollider2D polygon = GetComponent<PolygonCollider2D>(); if (polygon == null || meshFilter == null) return; ApplyPathToCollider(meshFilter, polygon); RecaculateMass(); SaveInEditor(); } //根据网格形状重新计算质量 public void RecaculateMass() { if (useMass) { Rigidbody2D rigidBody = GetComponent<Rigidbody2D>(); MeshFilter meshFilter = GetComponent<MeshFilter>(); if (rigidBody == null || meshFilter == null) return; RecaculateMass(rigidBody, meshFilter); } } // ---------- void SaveInEditor() { #if UNITY_EDITOR //调用SetDirty方法保存修改部分 EditorUtility.SetDirty(gameObject); #endif } //按照面积重新计算质量 void RecaculateMass(Rigidbody2D rigidBody, MeshFilter meshFilter) { float area = CalculateArea(meshFilter); rigidBody.mass = area * massCoefficient; SaveInEditor(); } //计算多边形面积 float CalculateArea(MeshFilter meshFilter) { float area = 0; //三角形顶点 Vector3[] vertices = meshFilter.mesh.vertices; //三角形顶点索引 int[] indices = meshFilter.mesh.GetIndices(0); //依次计算三角形面积, 累加 //每三个顶点确定一个三角形 for (int i = 0; i < indices.Length; i += 3) { //获取三个顶点, 计算三角形面积, 累加 area += CalculateTriangleArea(vertices[indices[i]], vertices[indices[i + 1]], vertices[indices[i + 2]]); } return area; } //计算三角形面积 float CalculateTriangleArea(Vector3 p0, Vector3 p1, Vector3 p2) { float tmpArea = 0; tmpArea += p0.x * p1.y - p1.x * p0.y; tmpArea += p1.x * p2.y - p2.x * p1.y; tmpArea += p2.x * p0.y - p0.x * p2.y; tmpArea /= 2; tmpArea = Mathf.Abs(tmpArea); return tmpArea; } //计算三角形面积 float CalculateTriangleArea02(Vector3 p0, Vector3 p1, Vector3 p2) { return p0.x * (p1.y - p2.y) + p1.x * (p2.y - p0.y) + p2.x * (p0.y - p1.y); } //计算三角形面积, 该方法在部分情况会有微小误差 float CalculateTriangleArea03(Vector3 p0, Vector3 p1, Vector3 p2) { float a = (p1 - p0).magnitude; float b = (p2 - p1).magnitude; float c = (p0 - p2).magnitude; float p = (a + b + c) * 0.5f; return Mathf.Sqrt(p * (p - a) * (p - b) * (p - c)); } // ----- //按照PolygonCollider2D碰撞体的大小形状修改MeshFilter的显示网格 void ApplyColliderToMesh(PolygonCollider2D polygon, MeshFilter meshFilter) { //坐标点转换 Vector3[] newVertices = ConvertVertex(polygon.points); //创建网格 Mesh newMesh = new Mesh(); //重命名 newMesh.name = transform.name; //设置顶点 newMesh.vertices = newVertices; //获取三角形顶点索引数组 Triangulator tr = new Triangulator(newVertices); //赋值 newMesh.triangles = tr.Triangulate(); //修改物体Mesh //mesh是值传递, 只会修改本物体的mesh meshFilter.mesh = newMesh; //sharedMesh是因用户传递, 会修改所有引用该mesh的物体 //meshFilter.sharedMesh = mesh; #if UNITY_EDITOR EditorUtility.SetDirty(meshFilter); EditorUtility.SetDirty(meshFilter.sharedMesh); EditorUtility.SetDirty(gameObject); #endif } //Vector2转Vector3, 生成顶点, 中心点 Vector3 tmpPoint; Vector3[] ConvertVertex(Vector2[] vertices) { tmpPoint = Vector3.zero; //Vector2转Vector3 Vector3[] newVertices = new Vector3[vertices.Length]; for (int i = 0; i < vertices.Length; i++) { newVertices[i] = vertices[i]; centerPoint += newVertices[i]; } //中心点, 取所有点的平均值 centerPoint = tmpPoint / vertices.Length + transform.position; //返回 return newVertices; } //----- //可以是凹多边形, 但不可以有内部孔或嵌套多个多边形, 用一段闭合折线记录 void ApplyMeshToCollider(MeshFilter meshFilter, PolygonCollider2D polygon) { Vector3[] vertices = meshFilter.sharedMesh.vertices; Vector2[] points = new Vector2[vertices.Length]; //读取网格数据 for (int i = 0; i < vertices.Length; i++) { points[i] = vertices[i]; } //赋值到碰撞体points polygon.points = points; } //----- //可以是凹多边形, 可以有内部孔或嵌套多个多边形, 用多段独立的闭合折线记录 void ApplyPathToCollider(MeshFilter meshFilter, PolygonCollider2D polygon) { //解析网格数据 List<Vector2[]> paths = MeshToColloderPaths.CreatePolygon2DColliderPoints(meshFilter.sharedMesh); if (paths == null) return; //赋值到碰撞体path polygon.pathCount = paths.Count; for (int i = 0; i < paths.Count; i++) { polygon.SetPath(i, paths[i]); } } // ------ public static bool IsZero(float n) { return Mathf.Approximately(n, 0); } }
解析网格数据信息,转化为多段独立的闭合折线
using UnityEngine; using System.Collections.Generic; [RequireComponent(typeof(MeshFilter))] [RequireComponent(typeof(PolygonCollider2D))] [ExecuteInEditMode] public class MeshToColloderPaths { // class Edge2D { public Vector2 A { get; private set; } public Vector2 B { get; private set; } public Edge2D (Vector2 pointA, Vector2 pointB) { A = pointA; B = pointB; } //重写Equals public override bool Equals(object obj) { if (obj is Edge2D) { var edge = (Edge2D)obj; return (edge.A == A && edge.B == B) || (edge.B == A && edge.A == B); } return false; } public override int GetHashCode() { return A.GetHashCode() ^ B.GetHashCode(); } } // public static List<Vector2[]> CreatePolygon2DColliderPoints(Mesh mesh) { Dictionary<Edge2D, int> edges = BuildEdgesFromMesh(mesh); List<Vector2[]> paths = BuildColliderPaths(edges); return paths; } // ------ //获取mesh的三角形边 static Dictionary<Edge2D, int> BuildEdgesFromMesh(Mesh mesh) { if (mesh == null) return null; //mesh的顶点 Vector3[] tmpVerts = mesh.vertices; //mesh内包含的所有三角形 int[] tmpTris = mesh.triangles; //用以保存mesh内所有的三角形边及数量 Dictionary<Edge2D, int> edgeDic = new Dictionary<Edge2D, int>(); // Vector3 tmpPoint0; Vector3 tmpPoint1; Vector3 tmpPoint2; for (int i = 0; i < tmpTris.Length - 2; i += 3) { //三角形顶点 tmpPoint0 = tmpVerts[tmpTris[i]]; tmpPoint1 = tmpVerts[tmpTris[i + 1]]; tmpPoint2 = tmpVerts[tmpTris[i + 2]]; //三角形边 Edge2D[] tmpEdges = new Edge2D[] { new Edge2D(tmpPoint0, tmpPoint1), new Edge2D(tmpPoint1, tmpPoint2), new Edge2D(tmpPoint2, tmpPoint0) }; //记录每条边及出现的次数 for (int j = 0; j < tmpEdges.Length; j++) { //判断重复, 加入字典, 需要重写Edge2D的Equals函数 if (edgeDic.ContainsKey(tmpEdges[j])) edgeDic[tmpEdges[j]]++; else edgeDic.Add(tmpEdges[j], 1); } } return edgeDic; } //计算轮廓点坐标 static List<Vector2[]> BuildColliderPaths(Dictionary<Edge2D, int> allEdges) { if (allEdges == null) return null; //获取mesh外轮廓(三角形边没有重复使用, 不是公共边, 即视为外轮廓边) List<Edge2D> outEdges = new List<Edge2D>(); foreach (var edge in allEdges.Keys) { if (allEdges[edge] == 1) outEdges.Add(edge); } //path列表, 对应Collider的paths, 包含多个path List<List<Edge2D>> pathList = new List<List<Edge2D>>(); //单个path, 是一条(闭合的)折线 List<Edge2D> path = null; //循环所有外轮廓边, 每个轮廓边都是一条线段, 若两条线段端点重合, 则可以连接这两条线段为一条折线 while (outEdges.Count > 0) { //新建一个path if (path == null) { path = new List<Edge2D>(); //加入首个线段到path, 并从外轮廓(线段)列表中移除 path.Add(outEdges[0]); outEdges.RemoveAt(0); //将path添加到pathList pathList.Add(path); } //至少找到一条可连接线段 bool foundAtLeastOneEdge = false; int i = 0; Edge2D tmeEdge; //循环判断所有线段 while (outEdges.Count > i) { //依次取一条线段 tmeEdge = outEdges[i]; //如果该线段的终点与path折线的起点重合 if (tmeEdge.B == path[0].A) { //插入到path首位, 并从外轮廓(线段)列表中移除 path.Insert(0, tmeEdge); outEdges.RemoveAt(i); foundAtLeastOneEdge = true; } //如果该线段的起点与path折线的终点重合 else if (tmeEdge.A == path[path.Count - 1].B) { //添加到path的末位, 并从外轮廓(线段)列表中移除 path.Add(tmeEdge); outEdges.RemoveAt(i); foundAtLeastOneEdge = true; } //该线段不能连接到path, 跳过 else { i++; } } //如果没有找到能与首个线段连接的其他线段, 说明该线段是孤立的(不可用的线段), 将path置空 if (!foundAtLeastOneEdge) path = null; } //整理pathList List<Vector2[]> cleanedPaths = new List<Vector2[]>(); //循环 for (int i = 0; i < pathList.Count; i++) { //判断空值 if (pathList[i] == null) continue; //获取折线的折点 List<Vector2> coords = new List<Vector2>(); for (int j = 0; j < pathList[i].Count; j++) { coords.Add(pathList[i][j].A); } //去除多余点 cleanedPaths.Add(RemoveUnusedCoords(coords)); } //返回paths return cleanedPaths; } //去除多余坐标点,如果三点共线,去除中间点 static Vector2[] RemoveUnusedCoords(List<Vector2> oldCoordsList) { //保存筛选之后的点 List<Vector2> newCoordsList = new List<Vector2>(); //添加第一个点 newCoordsList.Add(oldCoordsList[0]); //标记下表 int lastAddedIndex = 0; //循环判断 for (int i = 1; i < oldCoordsList.Count; i++) { //取点 Vector2 curCoords = oldCoordsList[i]; Vector2 lastAddedCoords = oldCoordsList[lastAddedIndex]; Vector2 nextCoords = (i + 1 >= oldCoordsList.Count) ? oldCoordsList[0] : oldCoordsList[i + 1]; //判断共线 if (!PointPosionOfLine(lastAddedCoords, nextCoords, curCoords)) { newCoordsList.Add(curCoords); lastAddedIndex = i; } } return newCoordsList.ToArray(); } //判断三点共线, 即点M在线段AB上 static bool PointPosionOfLine(Vector2 A, Vector2 B, Vector2 M) { //只是判断位置, 这种算法比较简单, 还可以计算三角形面积(相对繁琐一些), 面积为0, 则三点共线 return Mathf.Approximately((B.y - M.y) * (A.x - M.x) - (A.y - M.y) * (B.x - M.x), 0); //公式计算过程 //直线公式:a * X + b * Y + c = 0 //将线段端点代入公式 // a * A.x + b * A.y + c = 0 // a * B.x + b * B.y + c = 0 //两式分别相加、相减 // (A.x + B.x) * a + (A.y + B.y) * b + 2c = 0 // (A.x - B.x) * a + (A.y - B.y) * b = 0 //化简得 // b = (B.x - A.x) / (A.y - B.y) * a // c = -a * (A.y * B.x - A.x * B.y) / (A.y - B.y) //原直线公式用a表示为 // a * X + (B.x - A.x) / (A.y - B.y) * a * Y - a * (A.y * B.x - A.x * B.y) / (A.y - B.y) = 0 //公式两边同时除a,直线公式用点A/B表示为 // X + (B.x - A.x) / (A.y - B.y) * Y - (A.y * B.x - A.x * B.y) / (A.y - B.y) = 0 //再次化简 // (A.y - B.y) * X + (B.x - A.x) * Y - (A.y * B.x - A.x * B.y) = 0 //公式左侧==0,点在直线上,公式左侧>0,点在直线右侧,工作左侧<0,点在直线左侧 //将目标点M代入公式 // (A.y - B.y) * M.x + (B.x - A.x) * M.y - (A.y * B.x - A.x * B.y) // A.y * M.x - B.y * M.x + B.x * M.y - A.x * M.y - A.y * B.x + A.x * B.y //整理为, 值 > 0 在右侧, = 0 在线上, < 0 在左侧 // (B.y - M.y) * (A.x - M.x) - (A.y - M.y) * (B.x - M.x) } }
可视为工具类,用于将多边形切割为最小三角形
using UnityEngine; using System.Collections.Generic; // http://wiki.unity3d.com/index.php?title=Triangulator //将2D多边形Polygon切割为多个三角形 //可以是凹多边形, 但不可以有内部孔或嵌套多个多边形 public class Triangulator { private List<Vector2> m_points = new List<Vector2>(); public Triangulator(Vector3[] points) { m_points = new List<Vector2>(); for (int i = 0; i < points.Length; i++) { m_points.Add(points[i]); } } public Triangulator(Vector2[] points) { m_points = new List<Vector2>(points); } public int[] Triangulate() { List<int> indices = new List<int>(); int n = m_points.Count; if (n < 3) return indices.ToArray(); int[] V = new int[n]; if (Area() > 0) { for (int v = 0; v < n; v++) V[v] = v; } else { for (int v = 0; v < n; v++) V[v] = (n - 1) - v; } int nv = n; int count = 2 * nv; for (int m = 0, v = nv - 1; nv > 2;) { if ((count--) <= 0) return indices.ToArray(); int u = v; if (nv <= u) u = 0; v = u + 1; if (nv <= v) v = 0; int w = v + 1; if (nv <= w) w = 0; if (Snip(u, v, w, nv, V)) { int a, b, c, s, t; a = V[u]; b = V[v]; c = V[w]; indices.Add(a); indices.Add(b); indices.Add(c); m++; for (s = v, t = v + 1; t < nv; s++, t++) V[s] = V[t]; nv--; count = 2 * nv; } } indices.Reverse(); return indices.ToArray(); } private float Area() { int n = m_points.Count; float A = 0.0f; for (int p = n - 1, q = 0; q < n; p = q++) { Vector2 pval = m_points[p]; Vector2 qval = m_points[q]; A += pval.x * qval.y - qval.x * pval.y; } return (A * 0.5f); } private bool Snip(int u, int v, int w, int n, int[] V) { int p; Vector2 A = m_points[V[u]]; Vector2 B = m_points[V[v]]; Vector2 C = m_points[V[w]]; if (Mathf.Epsilon > (((B.x - A.x) * (C.y - A.y)) - ((B.y - A.y) * (C.x - A.x)))) return false; for (p = 0; p < n; p++) { if ((p == u) || (p == v) || (p == w)) continue; Vector2 P = m_points[V[p]]; if (InsideTriangle(A, B, C, P)) return false; } return true; } private bool InsideTriangle(Vector2 A, Vector2 B, Vector2 C, Vector2 P) { float ax, ay, bx, by, cx, cy, apx, apy, bpx, bpy, cpx, cpy; float cCROSSap, bCROSScp, aCROSSbp; ax = C.x - B.x; ay = C.y - B.y; bx = A.x - C.x; by = A.y - C.y; cx = B.x - A.x; cy = B.y - A.y; apx = P.x - A.x; apy = P.y - A.y; bpx = P.x - B.x; bpy = P.y - B.y; cpx = P.x - C.x; cpy = P.y - C.y; aCROSSbp = ax * bpy - ay * bpx; cCROSSap = cx * apy - cy * apx; bCROSScp = bx * cpy - by * cpx; return ((aCROSSbp >= 0.0f) && (bCROSScp >= 0.0f) && (cCROSSap >= 0.0f)); } }
结合扩展Inspector面板功能,可以实现在编辑器环境下修改Mesh和多边形碰撞体~ 扩展自定义类在Inspector面板的显示/在Inspector面板显示自定义类方法
[CustomEditor(typeof(AdjustColliderOrMesh))] public class AdjustColliderOrMeshEditor : Editor { public override void OnInspectorGUI() { base.OnInspectorGUI(); AdjustColliderOrMesh myScript = (AdjustColliderOrMesh)target; if (GUILayout.Button("Apply Collider To Mesh")) { myScript.ApplyColliderToMesh(); } else if (GUILayout.Button("Appl Mesh To Collider")) { myScript.ApplyMeshToCollider(); } else if (GUILayout.Button("Apply Path To Collider")) { myScript.ApplyPathToCollider(); } else if (GUILayout.Button("Recaculate Mass")) { myScript.RecaculateMass(); } } }