通常项目中为了游戏运行的顺畅会对模型进行相应的减面操作,但也有特例。这次就研究下给模型加面,尤其是给模型水平方向加面,类似于水平切割模型。 一般的加面算法,是在三角形内部取各条线的中点连接成新三角进行加面。
如下图:
项目中实现如下:
但项目中需要始终水平切割三角面的线,此种加面方法不太可行,只能另找方法。
主要思路是考虑切割线与三角形的位置关系,先获取某一面的所有三角形,然后通过切割线进行位置比较最后再进行切割三角形。
首先几种情况下无需进行分割:
需要进行切割的情况如下:
逐个分析下,第一种是切割线过三角形的某一点并且和另两点所成线段相交。 第二种是切割线不经过任一点且与三角形两条边相交。
第一种:原本的三角形序列为 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
}
运行结果如图:
这种方式只是提供一种思路,当模型不是很规整,或者是需要对多个面进行加面操作时,局限性比较大。下一篇试着用切割插件实现对模型的完整加面。