Unity模型加面算法实验

通常项目中为了游戏运行的顺畅会对模型进行相应的减面操作,但也有特例。这次就研究下给模型加面,尤其是给模型水平方向加面,类似于水平切割模型。 一般的加面算法,是在三角形内部取各条线的中点连接成新三角进行加面。
如下图:
在这里插入图片描述
项目中实现如下:
在这里插入图片描述
但项目中需要始终水平切割三角面的线,此种加面方法不太可行,只能另找方法。
在这里插入图片描述
主要思路是考虑切割线与三角形的位置关系,先获取某一面的所有三角形,然后通过切割线进行位置比较最后再进行切割三角形。
首先几种情况下无需进行分割:
在这里插入图片描述
需要进行切割的情况如下:
在这里插入图片描述
逐个分析下,第一种是切割线过三角形的某一点并且和另两点所成线段相交。 第二种是切割线不经过任一点且与三角形两条边相交。
第一种:原本的三角形序列为 abc,经过切割线切割后得到bda 和 dca两个三角形。
第二种:原本的三角形序列为 abc,经过切割线切割后得到bda、dea和dce三个三角形。
处理数据时加入新的三角形序列,同时删除掉原有的三角形即可。下面以一个面为例实验:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MeshSubdivide : MonoBehaviour
{
    private List<Vector3> vertices;
    private List<Vector3> normals;
    private List<Color> colors;
    private List<Vector2> uv;
    private List<Vector2> uv1;
    private List<Vector2> uv2;
    private List<int> trangles;

    private MeshFilter meshfilter;
    private List<Vector3> inVertices = new List<Vector3>();
    /// <summary>
    /// 所有三角面的索引列表
    /// </summary>
    private List<Vector3> trianglesIndexList = new List<Vector3>();
    private List<Vector3> inIndexList = new List<Vector3>();

    void Start()
    {
        meshfilter = GetComponent<MeshFilter>();

        StartSubdivide(meshfilter.mesh);
    }


    private void InitArrays(Mesh mesh)
    {
        vertices = new List<Vector3>(mesh.vertices);
        normals = new List<Vector3>(mesh.normals);
        colors = new List<Color>(mesh.colors);
        uv = new List<Vector2>(mesh.uv);
        uv1 = new List<Vector2>(mesh.uv2);
        uv2 = new List<Vector2>(mesh.uv2);
        trangles = new List<int>();
    }

    private void CleanUp()
    {
        vertices = null;
        normals = null;
        colors = null;
        uv = null;
        uv1 = null;
        uv2 = null;
    }

    #region Subdivide4 (2x2)
    private int GetNewVertex4(int i1, int i2, float rate)
    {
        int newIndex = vertices.Count;
        //vertices.Add((vertices[i1] + vertices[i2]) * rate);
        if (normals.Count > 0)
            normals.Add((normals[i1] + normals[i2]).normalized);
        if (colors.Count > 0)
            colors.Add((colors[i1] + colors[i2]) * rate);
        if (uv.Count > 0)
            uv.Add((uv[i1] + uv[i2]) * rate);
        if (uv1.Count > 0)
            uv1.Add((uv1[i1] + uv1[i2]) * rate);
        if (uv2.Count > 0)
            uv2.Add((uv2[i1] + uv2[i2]) * rate);

        return newIndex;
    }


    /// <summary>
    /// 横向加面算法
    /// </summary>
    /// <param name="mesh"></param>
    public void StartSubdivide(Mesh mesh)
    {
        //初始化Mesh信息
        InitArrays(mesh);
        //获得最外层顶点序列
        GetInVertices();
        //获得初始三角形序列
        GetInitTriangles(mesh);
        //开始切割增加三角形
        StartSliceAddTriangles();

        mesh.vertices = vertices.ToArray();
        if (normals.Count > 0)
            mesh.normals = normals.ToArray();
        if (colors.Count > 0)
            mesh.colors = colors.ToArray();
        if (uv.Count > 0)
            mesh.uv = uv.ToArray();
        if (uv1.Count > 0)
            mesh.uv2 = uv1.ToArray();
        if (uv2.Count > 0)
            mesh.uv2 = uv2.ToArray();

        for (int i= 0; i< trianglesIndexList.Count; i++)
        {
            Vector3 indexVec = trianglesIndexList[i];
            trangles.Add((int)indexVec.x);
            trangles.Add((int)indexVec.y);
            trangles.Add((int)indexVec.z);
        }
        mesh.triangles = trangles.ToArray();

    }

    /// <summary>
    /// 对面进行切割
    /// </summary>
    public void StartSliceAddTriangles()
    {
        float inMaxY = 0f;
        for (int i = 0; i < inVertices.Count; i++)
        {
            if (Mathf.Abs(inVertices[i].y) > inMaxY)
            {
                inMaxY = Mathf.Abs(inVertices[i].y);
            }
        }
        float iy = inMaxY;
        while (iy > -inMaxY)
        {
            Vector3[] vecInIndexs = inIndexList.ToArray();
            SliceTrianglesByIndexList(vecInIndexs, iy);
            iy -= 0.1f;
        }
    }


    /// <summary>
    /// 获取里外两个面的顶点集合
    /// </summary>
    public void GetInVertices()
    {
        inVertices.Clear();

        float min = 100f;
        for (int i = 0; i < vertices.Count; i++)
        {
            float z = vertices[i].z;
            Vector3 vtc = vertices[i];
            if (z < min)
            {
                inVertices.Clear();
                min = z;
                if (!inVertices.Contains(vtc))
                {
                    inVertices.Add(vtc);
                }
            }
            else if (z == min)
            {
                if (!inVertices.Contains(vtc))
                {
                    inVertices.Add(vtc);
                }
            }
        }
    }

    /// <summary>
    /// 获得最外层面的三角面
    /// </summary>
    /// <param name="mesh"></param>
    public void GetInitTriangles(Mesh mesh)
    {
        int[] triangles = mesh.triangles;
        for (int i = 0; i < triangles.Length; i += 3)
        {
            int i1 = triangles[i + 0];
            int i2 = triangles[i + 1];
            int i3 = triangles[i + 2];
            Vector3 triVect = new Vector3(i1, i2, i3);
            trianglesIndexList.Add(triVect);

            //获取当前面包含三角形索引
            if (inVertices.Contains(vertices[i1]) && inVertices.Contains(vertices[i2]) && inVertices.Contains(vertices[i3]))
            {
                inIndexList.Add(triVect);
                continue;
            }
        }
    }


    /// <summary>
    /// 根据某一面的三角形序列点进行切割三角形
    /// </summary>
    /// <param name="vecIndexs"></param>
    /// <param name="y"></param>
    public void SliceTrianglesByIndexList(Vector3[] vecIndexs, float y)
    {
        for (int i = 0; i < vecIndexs.Length; i++)
        {
            Vector3 indexVec = vecIndexs[i];
            Vector3 p1 = vertices[(int)indexVec.x];
            Vector3 p2 = vertices[(int)indexVec.y];
            Vector3 p3 = vertices[(int)indexVec.z];

            //三个点都在y之上
            if (p1.y > y && p2.y > y && p3.y > y)
            {
                continue;
            }
            //三个点都在y之下
            if (p1.y < y && p2.y < y && p3.y < y)
            {
                continue;
            }
            //其中两个点等于y
            if ((p1.y == y && p2.y == y) || (p1.y == y && p3.y == y) || (p2.y == y && p3.y == y))
            {
                continue;
            }
            //其中一个点等于y,其余两个点组成线段均不与y相交 
            if ((p1.y == y && p2.y > y && p3.y > y) || (p1.y == y && p2.y < y && p3.y < y) ||
                (p2.y == y && p1.y > y && p3.y > y) || (p2.y == y && p1.y < y && p3.y < y) ||
                (p3.y == y && p1.y > y && p2.y > y) || (p3.y == y && p1.y < y && p2.y < y))
            {
                continue;
            }

            //其中一个点等于y,其余两个点组成线段与y相交 
            if (p1.y == y)
            {
                HandleLinePassPoint(indexVec, p1);
                continue;
            }
            if (p2.y == y)
            {
                HandleLinePassPoint(indexVec, p2);
                continue;
            }
            if (p3.y == y)
            {
                HandleLinePassPoint(indexVec, p3);
                continue;
            }

            //三点均不在y上,且三条线段有两条与y相交
            if ((p1.y > y && p2.y < y && p3.y < y) || (p1.y < y && p2.y > y && p3.y > y))
            {
                HandleLineNoPassPoint(indexVec, p1, y);
                continue;
            }
            if ((p2.y > y && p1.y < y && p3.y < y) || (p2.y < y && p1.y > y && p3.y > y))
            {
                HandleLineNoPassPoint(indexVec, p2, y);
                continue;
            }
            if ((p3.y > y && p2.y < y && p1.y < y) || (p3.y < y && p2.y > y && p1.y > y))
            {
                HandleLineNoPassPoint(indexVec, p3, y);
                continue;
            }
        }
    }

    /// <summary>
    /// 处理线经过三角形某一点, point是经过的点
    /// </summary>
    public void HandleLinePassPoint(Vector3 indexVec, Vector3 point)
    {
        Vector3 p1 = vertices[(int)indexVec.x];
        Vector3 p2 = vertices[(int)indexVec.y];
        Vector3 p3 = vertices[(int)indexVec.z];

        if (point == p1)
        {
            Vector3 newPoint = GetPointByLineFunc(p2, p3, point.y);
            float rate = Vector3.Distance(p2, newPoint) / Vector3.Distance(p2, p3);
            int newIndex = GetNewVertex4((int)indexVec.y, (int)indexVec.z, rate);   //vertices.Count;  
            vertices.Add(newPoint);

            Vector3 triangle1 = new Vector3(indexVec.y, newIndex, indexVec.x);
            Vector3 triangle2 = new Vector3(newIndex, indexVec.z, indexVec.x);
            inIndexList.Add(triangle1);
            inIndexList.Add(triangle2);
            inIndexList.Remove(indexVec);
            trianglesIndexList.Add(triangle1);
            trianglesIndexList.Add(triangle2);
            trianglesIndexList.Remove(indexVec);
        }
        else if (point == p2)
        {
            Vector3 newPoint = GetPointByLineFunc(p1, p3, point.y);
            float rate = Vector3.Distance(p1, newPoint) / Vector3.Distance(p1, p3);
            int newIndex = GetNewVertex4((int)indexVec.x, (int)indexVec.z, rate);   //vertices.Count;  
            vertices.Add(newPoint);

            Vector3 triangle1 = new Vector3(indexVec.z, newIndex, indexVec.y);
            Vector3 triangle2 = new Vector3(newIndex, indexVec.x, indexVec.y);
            inIndexList.Add(triangle1);
            inIndexList.Add(triangle2);
            inIndexList.Remove(indexVec);
            trianglesIndexList.Add(triangle1);
            trianglesIndexList.Add(triangle2);
            trianglesIndexList.Remove(indexVec);
        }
        else if(point == p3)
        {
            Vector3 newPoint = GetPointByLineFunc(p1, p2, point.y);
            float rate = Vector3.Distance(p1, newPoint) / Vector3.Distance(p1, p2);
            int newIndex = GetNewVertex4((int)indexVec.x, (int)indexVec.y, rate);   //vertices.Count;  
            vertices.Add(newPoint);

            Vector3 triangle1 = new Vector3(indexVec.x, newIndex, indexVec.z);
            Vector3 triangle2 = new Vector3(newIndex, indexVec.y, indexVec.z);
            inIndexList.Add(triangle1);
            inIndexList.Add(triangle2);
            inIndexList.Remove(indexVec);
                
            trianglesIndexList.Add(triangle1);
            trianglesIndexList.Add(triangle2);
            trianglesIndexList.Remove(indexVec);
        }
    }

    /// <summary>
    /// 处理线不经过三角形任一点, point是自己在y一侧的点
    /// </summary>
    /// <param name="indexVec"></param>
    /// <param name="ponit"></param>
    public void HandleLineNoPassPoint(Vector3 indexVec, Vector3 ponit, float y)
    {
        Vector3 p1 = vertices[(int)indexVec.x];
        Vector3 p2 = vertices[(int)indexVec.y];
        Vector3 p3 = vertices[(int)indexVec.z];


        if (ponit == p1)
        {
            Vector3 newPoint4 = GetPointByLineFunc(p1, p3, y);
            float rate4 = Vector3.Distance(p1, newPoint4) / Vector3.Distance(p1, p3);
            int newIndex4 = GetNewVertex4((int)indexVec.x, (int)indexVec.z, rate4);   //vertices.Count;  
            vertices.Add(newPoint4);

            Vector3 newPoint5 = GetPointByLineFunc(p1, p2, y);
            float rate5 = Vector3.Distance(p1, newPoint4) / Vector3.Distance(p1, p2);
            int newIndex5 = GetNewVertex4((int)indexVec.x, (int)indexVec.y, rate5);   //vertices.Count;  
            vertices.Add(newPoint5);

            Vector3 triangle1 = new Vector3(indexVec.z, newIndex4, indexVec.y);
            Vector3 triangle2 = new Vector3(newIndex4, newIndex5, indexVec.y);
            Vector3 triangle3 = new Vector3(newIndex4, indexVec.x, newIndex5);
            inIndexList.Add(triangle1);
            inIndexList.Add(triangle2);
            inIndexList.Add(triangle3);
            inIndexList.Remove(indexVec);
            trianglesIndexList.Add(triangle1);
            trianglesIndexList.Add(triangle2);
            trianglesIndexList.Add(triangle3);
            trianglesIndexList.Remove(indexVec);
        }
        else if (ponit == p2)
        {
            Vector3 newPoint4 = GetPointByLineFunc(p1, p2, y);
            float rate4 = Vector3.Distance(p1, newPoint4) / Vector3.Distance(p1, p2);
            int newIndex4 = GetNewVertex4((int)indexVec.x, (int)indexVec.y, rate4);   //vertices.Count;  
            vertices.Add(newPoint4);

            Vector3 newPoint5 = GetPointByLineFunc(p2, p3, y);
            float rate5 = Vector3.Distance(p2, newPoint4) / Vector3.Distance(p2, p3);
            int newIndex5 = GetNewVertex4((int)indexVec.y, (int)indexVec.z, rate5);   //vertices.Count; 
            vertices.Add(newPoint5);

            Vector3 triangle1 = new Vector3(indexVec.x, newIndex4, indexVec.z);
            Vector3 triangle2 = new Vector3(newIndex4, newIndex5, indexVec.z);
            Vector3 triangle3 = new Vector3(newIndex4, indexVec.y, newIndex5);
            inIndexList.Add(triangle1);
            inIndexList.Add(triangle2);
            inIndexList.Add(triangle3);
            inIndexList.Remove(indexVec);
            trianglesIndexList.Add(triangle1);
            trianglesIndexList.Add(triangle2);
            trianglesIndexList.Add(triangle3);
            trianglesIndexList.Remove(indexVec);
        }
        else if (ponit == p3)
        {
            Vector3 newPoint4 = GetPointByLineFunc(p3, p2, y);
            float rate4 = Vector3.Distance(p3, newPoint4) / Vector3.Distance(p3, p2);
            int newIndex4 = GetNewVertex4((int)indexVec.z, (int)indexVec.y, rate4);   //vertices.Count;  
            vertices.Add(newPoint4);
            Vector3 newPoint5 = GetPointByLineFunc(p1, p3, y);
            float rate5 = Vector3.Distance(p1, newPoint4) / Vector3.Distance(p1, p3);
            int newIndex5 = GetNewVertex4((int)indexVec.x, (int)indexVec.z, rate5);   //vertices.Count; 
            vertices.Add(newPoint5);

            Vector3 triangle1 = new Vector3(indexVec.y, newIndex4, indexVec.x);
            Vector3 triangle2 = new Vector3(newIndex4, newIndex5, indexVec.x);
            Vector3 triangle3 = new Vector3(newIndex4, indexVec.z, newIndex5);
            inIndexList.Add(triangle1);
            inIndexList.Add(triangle2);
            inIndexList.Add(triangle3);
            inIndexList.Remove(indexVec);
            trianglesIndexList.Add(triangle1);
            trianglesIndexList.Add(triangle2);
            trianglesIndexList.Add(triangle3);
            trianglesIndexList.Remove(indexVec);
        }
    }


    /// <summary>
    /// 通过两点求得的直线,然后代入y求得x, 即获得某直线上的交点
    /// </summary>
    /// <param name="p1"></param>
    /// <param name="p2"></param>
    /// <param name="y"></param>
    /// <returns></returns>
    public Vector3 GetPointByLineFunc(Vector3 p1, Vector3 p2, float y)
    {
        //已知直线上的两点P1(X1, Y1) P2(X2, Y2), P1 P2两点不重合。则直线的一般式方程AX + BY + C = 0中,A B C分别等于:
        //A = Y2 - Y1
        //B = X1 - X2
        //C = X2 * Y1 - X1 * Y2
        float a = p2.y - p1.y;
        float b = p1.x - p2.x;
        float c = p2.x * p1.y - p1.x * p2.y;
        return new Vector3((-b * y - c)/ a, y, p1.z);
    }

    #endregion Subdivide

}

运行结果如图:
在这里插入图片描述
这种方式只是提供一种思路,当模型不是很规整,或者是需要对多个面进行加面操作时,局限性比较大。下一篇试着用切割插件实现对模型的完整加面。

猜你喜欢

转载自blog.csdn.net/Gao_sun/article/details/106911162
今日推荐