最终效果
思路
- 确定切面
- 根据切面将原模型顶点分为切面上下两类
- 将交面进行特殊处理,根据角度或距离插值运算交点,并添加顶点索引
- 对剖面顶点进行重新排序,连接,UV映射
实现
因为代码量大只写局部伪代码说明。
关于网格的基础教程,请参阅 Mesh网格篇(一)代码生成圆柱Mesh
为了方便这里我们拿BOX举例。
顶点站队
当我们确定切面裁切一个box时,我们首先需要做的是把顶点分类,通过向量的点乘将顶点0145和2376分为切面的ab两组,同时记录下ab的顶点进入排序和他在切面上下的状态。
bool[] above = new bool[MeshInfo.vertices.Count];
int[] newTriangles = new int[MeshInfo.vertices.Count];
for (int i = 0; i < newTriangles.Length; i++)
{
Vector3 vert = MeshInfo.vertices[i];
above[i] = Vector3.Dot(vert - point, normal) >= 0f;
if (above[i])
{
newTriangles[i] = a.vertices.Count;
a.Add(vert, MeshInfo.uvs[i], MeshInfo.normals[i], MeshInfo.tangents[i]);
}
else
{
newTriangles[i] = b.vertices.Count;
b.Add(vert, MeshInfo.uvs[i], MeshInfo.normals[i], MeshInfo.tangents[i]);
}
}
接下来我们按照原模型的连接方式逐个连接ab中的顶点
for (int i = 0; i < triangleCount; i++)
{
int _i0 = MeshInfo.triangles[i * 3];
int _i1 = MeshInfo.triangles[i * 3 + 1];
int _i2 = MeshInfo.triangles[i * 3 + 2];
bool _a0 = above[_i0];
bool _a1 = above[_i1];
bool _a2 = above[_i2];
if (_a0 && _a1 && _a2)
{
a.triangles.Add(newTriangles[_i0]);
a.triangles.Add(newTriangles[_i1]);
a.triangles.Add(newTriangles[_i2]);
}
else if (!_a0 && !_a1 && !_a2)
{
b.triangles.Add(newTriangles[_i0]);
b.triangles.Add(newTriangles[_i1]);
b.triangles.Add(newTriangles[_i2]);
}
else
{
//....
}
通过与原模型顶点索引的比较,我们将得到
切面补间
这样2个模型,很明显中间网格需要我们在else中进行补点补线。
这里讲下我们如果去切割一个平面。
一个平面被切割时,也就两个三角面被切割。
假如△abc被坐标轴x切割,我们如何确定点d和e的位置呢。
求角度比scale = ∠yba/ ∠yoa;(float scale = Vector.Dot(a-o,y-o)/Vector.Dot(a-b,y-o);)
d = b+(a-b)*scale;
e = c+(a-c)*scale;
然后根据网格的连接顺序连接,aed,bdc,cde。
然后把顶点和a顶点索引加入到a面中,顶点和b顶点索引加入到b面中。
我们就能得到类似上面的2个模型。
剖面填充
这时我们就要填充剖面,但是剖面的顶点顺序我们并不知道。
所以要对顶点进行排序。
局限性排序
幸运的是我们的顶点都是两两相连,每个顶点的位置其实有2个面的顶点重叠。
根据这个特性,我们就可以以此为根据寻找下个连接顶点的位置,然后删除重复的顶点。
for (int i = 0; i < edges.Count - 3; i++)
{
Vector3 t = edges[i + 1];
Vector3 temp = edges[i + 3];
for (int j = i + 2; j < edges.Count - 1; j += 2)
{
if ((edges[j] - t).sqrMagnitude < 1e-6)
{
edges[j] = edges[i + 2];
edges[i + 3] = edges[j + 1];
edges[j + 1] = temp;
break;
}
if ((edges[j + 1] - t).sqrMagnitude < 1e-6)
{
edges[j + 1] = edges[i + 2];
edges[i + 3] = edges[j];
edges[j] = temp;
break;
}
}
edges.RemoveAt(i + 2);
}
edges.RemoveAt(edges.Count - 1);
这样我们就得到了一个按剖面形状连接的顺序顶点。
只要按012,023,034。。。。的顺序连接下去就能把剖面填充。
常规排序
那如何对凸多边形上内任意若干的顶点进行连接呢。
这时候我们需要根据凸多边形的外切线和角度来确定下个顶点的位置。
这里只提供思路,代码太长就不贴了,而且在模型切割上效率不如上面的高。
首先将顶点都转换到切线寻找凸多边形的外切线向量。
然后逐顶点对比角度。
如果两个顶点与切线共线,我们就比较距离,一个顶点只有2个顶点连接他。第一次共线距离为近的排序,第二次共线距离为远的排序。
凹多边形
有人问凹多边形如何填充,呵呵,如果填充凹多边形,要先把凹多边形分割成若干个凸多边形进行填充。
UV映射
接下来就是对剖面的顶点UV进行映射。根据情况赋值的方法不同。
拿BOX举例,
我们需要把box的bound的size传入,然后根据 point/size 来确定uv偏移值,根据切面的法线方向决定取xy?xz?zy?
int count = triangles.Count / 3;
for (int i = 0; i < count; i++)
{
int _i0 = triangles[i * 3];
int _i1 = triangles[i * 3 + 1];
int _i2 = triangles[i * 3 + 2];
Vector3 v0 = vertices[_i0] - center + size / 2f;
Vector3 v1 = vertices[_i1] - center + size / 2f;
Vector3 v2 = vertices[_i2] - center + size / 2f;
v0 = new Vector3(v0.x / size.x, v0.y / size.y, v0.z / size.z);
v1 = new Vector3(v1.x / size.x, v1.y / size.y, v1.z / size.z);
v2 = new Vector3(v2.x / size.x, v2.y / size.y, v2.z / size.z);
Vector3 a = v0 - v1;
Vector3 b = v2 - v1;
Vector3 dir = Vector3.Cross(a, b);
float x = Mathf.Abs(Vector3.Dot(dir, Vector3.right));
float y = Mathf.Abs(Vector3.Dot(dir, Vector3.up));
float z = Mathf.Abs(Vector3.Dot(dir, Vector3.forward));
if (x > y && x > z)
{
uvs[_i0] = new Vector2(v0.z, v0.y);
uvs[_i1] = new Vector2(v1.z, v1.y);
uvs[_i2] = new Vector2(v2.z, v2.y);
}
else if (y > x && y > z)
{
uvs[_i0] = new Vector2(v0.x, v0.z);
uvs[_i1] = new Vector2(v1.x, v1.z);
uvs[_i2] = new Vector2(v2.x, v2.z);
}
else if (z > x && z > y)
{
uvs[_i0] = new Vector2(v0.x, v0.y);
uvs[_i1] = new Vector2(v1.x, v1.y);
uvs[_i2] = new Vector2(v2.x, v2.y);
}
}