本文已参与「新人创作礼」活动,一起开启掘金创作之路。
布料缝合--(一)选取Mesh缝合边界顶点 - 掘金 (juejin.cn)
布料缝合--(二)绘制缝线 - 掘金 (juejin.cn)
布料缝合--(三)重置Mesh顶点、三角面
思路:在前两步中,已经获取了要缝合的顶点索引,但两块mesh的顶点数量有可能不是1:1,因此需要重新对较少顶点的那一边所在的mesh进行顶点细分。
上代码:
private void AdjustSewingPoint()
{
//初始化gatherIndexA、gatherIndexB
for (var i = 0; i < gatherA.Count; i++) gatherIndexA.Add(new MyEdge(gatherA[i].v1, gatherA[i].v2));
for (var i = 0; i < gatherB.Count; i++) gatherIndexB.Add(new MyEdge(gatherB[i].v1, gatherB[i].v2));
FillShortEdges();
sewingIndexA.Clear();
sewingIndexB.Clear();
//对齐补充顶点后的缝线A
for (var i = 0; i < gatherIndexA.Count; i++)
{
sewingIndexA.Add(gatherIndexA[i]._v1);
foreach (var t in gatherIndexA[i]._extra)
{
sewingIndexA.Add(t);
}
}
sewingIndexA.Add(gatherIndexA[gatherIndexA.Count - 1]._v2);
//对齐补充顶点后的缝线B
sewingIndexB.Add(gatherIndexB[gatherIndexB.Count - 1]._v2);
for (var i = gatherIndexB.Count - 1; i >= 0; i--)
{
for (int j = gatherIndexB[i]._extra.Count - 1; j >= 0; j--)
{
sewingIndexB.Add(gatherIndexB[i]._extra[j]);
}
sewingIndexB.Add(gatherIndexB[i]._v1);
}
}
private void FillShortEdges()
{
//找到需要拆分的edges
List<EdgeHelpers.Edge> _long, _short;
MeshFilter _shortObi;
if (gatherA.Count >= gatherB.Count)
{
_long = gatherA;
_short = gatherB;
_shortObi = meshB;
gatherIndex = gatherIndexB;
}
else
{
_long = gatherB;
_short = gatherA;
_shortObi = meshA;
gatherIndex = gatherIndexA;
}
var factor = (float) _long.Count / _short.Count;
Mesh _shortMesh = _shortObi.mesh;
for (var i = 0; i < _short.Count; i++)
{
//对于当前第i个edge,需要增加fixNums个顶点
var fixNums = -1;
for (var j = 0; j < _long.Count; j++)
{
if (j >= i * factor && j < (i + 1) * factor)
{
fixNums++;
}
}
//Debug.Log($"第{i}个edge需要修补{fixNums}个顶点");
if (fixNums > 0)
{
//找到第i个edge所在的三角形
FindTriangleFromEdge(_shortObi.mesh.triangles, _short[i], out var trialgleFromEdge);
//先移除原来的三角形
List<int> tris = new List<int>(_shortObi.mesh.triangles);
tris.RemoveRange(trialgleFromEdge.triIndex, 3);
//_shortObi.mesh.triangles = tris.ToArray();
//补充顶点
List<Vector3> verts = new List<Vector3>(_shortObi.mesh.vertices);
//按照顺时针or逆时针顺序差值计算中间需要补充的顶点的坐标
Vector3 startVert, endVert;
if (trialgleFromEdge.isClockwise)
{
startVert = _shortMesh.vertices[_short[i].v2];
endVert = _shortMesh.vertices[_short[i].v1];
}
else
{
startVert = _shortMesh.vertices[_short[i].v1];
endVert = _shortMesh.vertices[_short[i].v2];
}
for (int k = 1; k <= fixNums; k++)
{
float lerp = (float) k / (fixNums + 1);
var newVert = Vector3.Lerp(startVert, endVert, lerp);
verts.Add(newVert);
//记录第i个edge中间增加的顶点索引
if (trialgleFromEdge.isClockwise)
gatherIndex[i].PushFront(verts.Count - 1);
else
gatherIndex[i].PushBack(verts.Count - 1);
}
//补充三角形
int endpoint1, endpoint2; //当前第i个edge,在顺时针状态下的两个端点
if (trialgleFromEdge.isClockwise)
{
endpoint1 = _short[i].v2;
endpoint2 = _short[i].v1;
}
else
{
endpoint1 = _short[i].v1;
endpoint2 = _short[i].v2;
}
//补充的顶点个数超过=1
if (fixNums == 1)
{
tris.Add(trialgleFromEdge.diagonal);
tris.Add(endpoint1);
tris.Add(verts.Count - 1);
tris.Add(trialgleFromEdge.diagonal);
tris.Add(verts.Count - 1);
tris.Add(endpoint2);
}
else
{
//补充的顶点个数超过≥2
tris.Add(trialgleFromEdge.diagonal);
tris.Add(endpoint1);
tris.Add(verts.Count - fixNums);
for (int k = 0; k < fixNums - 1; k++)
{
tris.Add(trialgleFromEdge.diagonal);
tris.Add(verts.Count - fixNums + k);
tris.Add(verts.Count - fixNums + k + 1);
}
tris.Add(trialgleFromEdge.diagonal);
tris.Add(verts.Count - 1);
tris.Add(endpoint2);
}
//补充uv (ps.道理和补充顶点一样,不再重复加注释)
List<Vector2> uvs = new List<Vector2>(_shortObi.mesh.uv);
//按照顺时针or逆时针顺序差值计算中间需要补充的顶点的坐标
Vector2 startUV, endUV;
if (trialgleFromEdge.isClockwise)
{
startUV = _shortMesh.uv[_short[i].v2];
endUV = _shortMesh.uv[_short[i].v1];
}
else
{
startUV = _shortMesh.uv[_short[i].v1];
endUV = _shortMesh.uv[_short[i].v2];
}
for (int k = 1; k <= fixNums; k++)
{
float lerp = (float) k / (fixNums + 1);
var newUV = Vector2.LerpUnclamped(startUV, endUV, lerp);
uvs.Add(newUV);
}
//重新计算Mesh各种信息
_shortObi.mesh.Clear();
_shortObi.mesh.vertices = verts.ToArray();
_shortObi.mesh.triangles = tris.ToArray();
_shortObi.mesh.uv = uvs.ToArray();
_shortObi.mesh.RecalculateBounds();
_shortObi.mesh.RecalculateNormals();
_shortObi.mesh.RecalculateTangents();
//Debug.Log($"增加{fixNums}个顶点,当前定点总个数为:{_shortObi.mesh.vertices.Length}");
}
}
}
private struct TrialgleFromEdge
{
public int triIndex; //第一个顶点在三角形数组中的下标
public bool isClockwise; //当前edge的端点是否为顺时针顺序
public int diagonal; //当前edge在三角形中所对应的对角顶点索引
}
private void FindTriangleFromEdge(int[] triangles, EdgeHelpers.Edge edge, out TrialgleFromEdge trialgleFromEdge)
{
trialgleFromEdge = new TrialgleFromEdge();
for (int i = 0; i < triangles.Length; i += 3)
{
int a = triangles[i];
int b = triangles[i + 1];
int c = triangles[i + 2];
if (edge.v1 == a && edge.v2 == b) //当前edge的端点顺序为逆时针,对角顶点为c
{
trialgleFromEdge.triIndex = i;
trialgleFromEdge.isClockwise = false;
trialgleFromEdge.diagonal = c;
}
else if (edge.v1 == b && edge.v2 == c) //当前edge的端点顺序为逆时针,对角顶点为a
{
trialgleFromEdge.triIndex = i;
trialgleFromEdge.isClockwise = false;
trialgleFromEdge.diagonal = a;
}
else if (edge.v1 == c && edge.v2 == a) //当前edge的端点顺序为逆时针,对角顶点为b
{
trialgleFromEdge.triIndex = i;
trialgleFromEdge.isClockwise = false;
trialgleFromEdge.diagonal = b;
}
else if (edge.v1 == b && edge.v2 == a) //当前edge的端点顺序为顺时针,对角顶点为c
{
trialgleFromEdge.triIndex = i;
trialgleFromEdge.isClockwise = true;
trialgleFromEdge.diagonal = c;
}
else if (edge.v1 == c && edge.v2 == b) //当前edge的端点顺序为顺时针,对角顶点为a
{
trialgleFromEdge.triIndex = i;
trialgleFromEdge.isClockwise = true;
trialgleFromEdge.diagonal = a;
}
else if (edge.v1 == a && edge.v2 == c) //当前edge的端点顺序为顺时针,对角顶点为b
{
trialgleFromEdge.triIndex = i;
trialgleFromEdge.isClockwise = true;
trialgleFromEdge.diagonal = b;
}
}
}
重置前
重置后