代码创建网格和2D多边形碰撞体

发表于2018-12-06
评论0 1.9k浏览

主要实现四个功能:

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();
        }
    }
}

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引

标签: