布料缝合--(三)重置Mesh顶点、三角面

226 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

布料缝合--(一)选取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;
                }
            }
        }

重置前

image.png

重置后

image.png