布料缝合--(二)绘制缝线

181 阅读3分钟

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

布料缝合--(一)选取Mesh缝合边界顶点 - 掘金 (juejin.cn)

布料缝合--(二)绘制缝线

布料缝合--(三)重置Mesh顶点、三角面 - 掘金 (juejin.cn)

缝线类:

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

/// <summary>
/// 显示缝线的类
///
/// 共5种类型的缝线: EdgeA、EdgeB为两个缝边上的, OuterOne、OuterOther为两端显示封边范围, Inner为内部缝线
/// </summary>
public class SewingLine : MonoBehaviour
{
    public Color EdgeColor;
    public Color BorderColor;
    public Color InnerColor;

    public float EdgeThickness;
    public float BorderThickness;
    public float InnerThickness;


    private Transform m_edgeA;
    private Transform m_edgeB;
    private Vector3 lastEdgeAPos;
    private Vector3 lastEdgeBPos;

    private int lastSewCountA;
    private int lastSewCountB;
    private VrGizmos.DrawCommand m_cmdEdgeA;
    private VrGizmos.DrawCommand m_cmdEdgeB;
    private VrGizmos.DrawCommand m_cmdBorderA;
    private VrGizmos.DrawCommand m_cmdBorderB;
    private VrGizmos.DrawCommand m_cmdInner;

    private Vector3[] m_vertsA;
    private Vector3[] m_vertsB;
    private List<int> m_sewingIndexA;
    private List<int> m_sewingIndexB;

    //初始化
    public void Init()
    {
        lastSewCountA = 0;
        lastSewCountB = 0;
        m_cmdEdgeA = null;
        m_cmdEdgeB = null;
        m_cmdBorderA = null;
        m_cmdBorderB = null;
        m_cmdInner = null;

        m_vertsA = null;
        m_vertsB = null;
        m_sewingIndexA = null;
        m_sewingIndexB = null;
    }

    private void Update()
    {
        var needRefreshInnere = false;
        if (m_edgeA != null && lastEdgeAPos != m_edgeA.position)
        {
            needRefreshInnere = true;
            UpdateDrawCommand(m_edgeA, m_vertsA, m_sewingIndexA, ref m_cmdEdgeA, ref m_cmdBorderA);
            lastEdgeAPos = m_edgeA.position;
        }

        if (m_edgeB != null && lastEdgeBPos != m_edgeB.position)
        {
            needRefreshInnere = true;
            UpdateDrawCommand(m_edgeB, m_vertsB, m_sewingIndexB, ref m_cmdEdgeB, ref m_cmdBorderB);
            lastEdgeBPos = m_edgeB.position;
        }

        if (needRefreshInnere)
        {
            UpdateSewingLines(m_edgeA, m_edgeB,
                m_vertsA, m_vertsB,
                m_sewingIndexA, m_sewingIndexB,
                true
            );
        }
    }

    private bool edgeHasChanged = false;

    public void UpdateSewingEdgeA(Transform _edgeA, Vector3[] verticesA, List<int> sewingIndexA)
    {
        if (m_edgeA == null)
        {
            m_edgeA = _edgeA;
            lastEdgeAPos = m_edgeA.position;
        }

        var count = sewingIndexA.Count;
        if (count != 0 && lastSewCountA != count)
        {
            m_vertsA = verticesA;
            m_sewingIndexA = sewingIndexA;
            lastSewCountA = count;
            edgeHasChanged = true;

            UpdateDrawCommand(_edgeA, verticesA, sewingIndexA, ref m_cmdEdgeA, ref m_cmdBorderA);
        }

        if (m_cmdEdgeA != null)
            VrGizmos.AddDraw(m_cmdEdgeA);
        if (m_cmdBorderA != null)
            VrGizmos.AddDraw(m_cmdBorderA);
    }

    public void UpdateSewingEdgeB(Transform _edgeB, Vector3[] verticesB, List<int> sewingIndexB)
    {
        if (m_edgeB == null)
        {
            m_edgeB = _edgeB;
            lastEdgeBPos = m_edgeB.position;
        }

        var count = sewingIndexB.Count;
        if (count != 0 && lastSewCountB != count)
        {
            m_vertsB = verticesB;
            m_sewingIndexB = sewingIndexB;
            lastSewCountB = count;
            edgeHasChanged = true;

            UpdateDrawCommand(_edgeB, verticesB, sewingIndexB, ref m_cmdEdgeB, ref m_cmdBorderB);
        }

        if (m_cmdEdgeB != null)
            VrGizmos.AddDraw(m_cmdEdgeB);
        if (m_cmdBorderB != null)
            VrGizmos.AddDraw(m_cmdBorderB);
    }

    private void UpdateDrawCommand(Transform edge, Vector3[] verts, List<int> sewingIndex,
        ref VrGizmos.DrawCommand EdgeCmd, ref VrGizmos.DrawCommand BorderCmd)
    {
        if (EdgeCmd != null)
            VrGizmos.RemoveDraw(EdgeCmd);

        var count = sewingIndex.Count;
        VrGizmos.Segment[] segments = new VrGizmos.Segment[count - 1];
        for (int i = 0; i < count - 1; i++)
        {
            var start = edge.TransformPoint(verts[sewingIndex[i]]);
            var end = edge.TransformPoint(verts[sewingIndex[i + 1]]);
            segments[i] = new VrGizmos.Segment(start, end, EdgeColor);
        }

        EdgeCmd = VrGizmos.DrawSegments(segments, EdgeThickness);
        VrGizmos.AddDraw(EdgeCmd);

        //两端的端点
        if (BorderCmd != null)
            VrGizmos.RemoveDraw(BorderCmd);

        Vector3[] borderPoints = new Vector3[2];
        borderPoints[0] = edge.TransformPoint(verts[sewingIndex[0]]);
        borderPoints[1] = edge.TransformPoint(verts[sewingIndex[sewingIndex.Count - 1]]);
        Quaternion[] borderPointRotas = new Quaternion[2];
        borderPointRotas[0] = Quaternion.FromToRotation(Vector3.up,
            edge.TransformPoint(verts[sewingIndex[1]]) - borderPoints[0]);
        ;
        borderPointRotas[1] = Quaternion.FromToRotation(Vector3.up,
            borderPoints[1] - edge.TransformPoint(verts[sewingIndex[sewingIndex.Count - 2]]));
        ;
        BorderCmd = VrGizmos.DrawBox(borderPoints, borderPointRotas, Vector3.one * BorderThickness, BorderColor);
    }

    public void UpdateSewingLines(
        Transform _edgeA, Transform _edgeB,
        Vector3[] verticesA, Vector3[] verticesB,
        List<int> sewingIndexA, List<int> sewingIndexB,
        bool refresh)
    {
        if (refresh || (edgeHasChanged && (sewingIndexA.Count >= 2 && sewingIndexB.Count >= 2)))
        {
            /*
            //两端的缝线
            if (_cmdBorder != null)
            {
                VrGizmos.RemoveDraw(_cmdBorder);
            }

            VrGizmos.Segment[] borderSegs = new VrGizmos.Segment[2];

            var start0 = _edgeA.TransformPoint(verticesA[sewingIndexA[0]]);
            var end0 = _edgeB.TransformPoint(verticesB[sewingIndexB[0]]);
            var start1 = _edgeA.TransformPoint(verticesA[sewingIndexA[sewingIndexA.Count - 1]]);
            var end1 = _edgeB.TransformPoint(verticesB[sewingIndexB[sewingIndexB.Count - 1]]);

            borderSegs[0] = new VrGizmos.Segment(start0, end0, BorderColor);
            borderSegs[1] = new VrGizmos.Segment(start1, end1, BorderColor);
            _cmdBorder = VrGizmos.DrawSegments(borderSegs, BorderThickness);
            */

            //如果A、B都只有两个点,则没有内部线
            if (!(sewingIndexA.Count == 2 && sewingIndexB.Count == 2))
            {
                //内部的缝线
                if (m_cmdInner != null)
                    VrGizmos.RemoveDraw(m_cmdInner);

                List<int> _long, _short;
                Transform _longTrans, _shortTrans;
                Vector3[] _longVerts, _shortVerts;
                if (sewingIndexA.Count >= sewingIndexB.Count)
                {
                    _long = sewingIndexA;
                    _short = sewingIndexB;
                    _longTrans = _edgeA;
                    _shortTrans = _edgeB;
                    _longVerts = verticesA;
                    _shortVerts = verticesB;
                }
                else
                {
                    _long = sewingIndexB;
                    _short = sewingIndexA;
                    _longTrans = _edgeB;
                    _shortTrans = _edgeA;
                    _longVerts = verticesB;
                    _shortVerts = verticesA;
                }

                float factor = (float) _long.Count / _short.Count;
                VrGizmos.Segment[] innerSegs = new VrGizmos.Segment[_long.Count];
                for (int i = 0; i < _long.Count; i++)
                {
                    var start = _longTrans.TransformPoint(_longVerts[_long[i]]);
                    var end = _shortTrans.TransformPoint(_shortVerts[_short[(int) (i / factor)]]);
                    innerSegs[i] = new VrGizmos.Segment(start, end, InnerColor);
                }

                m_cmdInner = VrGizmos.DrawSegments(innerSegs, InnerThickness);
                edgeHasChanged = false;
            }
        }

        if (m_cmdInner != null)
            VrGizmos.AddDraw(m_cmdInner);
    }
}

缝线笔触:

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

namespace Hologarment.Core
{
    /// <summary>
    /// 
    /// </summary>namespace Hologarment.Core
    public class SewingPen : MonoBehaviour
    {
        public AnchoredCursor anchoredCursor;

        public MeshFilter meshA;
        public MeshFilter meshB;
        public GameObject currObj;
        public List<EdgeHelpers.Edge> gatherA;
        public List<EdgeHelpers.Edge> gatherB;

        private bool _isReverse;
        private bool _isRefresh;
        private SewingLine currSewingLine;

        //存放经过补充顶点后的edge信息
        public readonly struct MyEdge
        {
            public readonly int _v1;
            public readonly List<int> _extra; //补充的点
            public readonly int _v2;

            public MyEdge(int v1, int v2)
            {
                this._v1 = v1;
                _extra = new List<int>();
                this._v2 = v2;
            }

            public void PushFront(int ex)
            {
                _extra.Insert(0, ex);
            }

            public void PushBack(int ex)
            {
                _extra.Add(ex);
            }
        }

        public List<MyEdge> gatherIndexA;
        public List<MyEdge> gatherIndexB;
        public List<MyEdge> gatherIndex;

        //用于存放缝合顶点索引
        public List<int> sewingIndexA;
        public List<int> sewingIndexB;

        // Start is called before the first frame update
        private void Start()
        {
            Init();
        }

        private void Init()
        {
            anchoredCursor.SetCursorActiveTo(false);

            meshA = null;
            meshB = null;
            gatherA = new List<EdgeHelpers.Edge>();
            gatherB = new List<EdgeHelpers.Edge>();

            gatherIndexA = new List<MyEdge>();
            gatherIndexB = new List<MyEdge>();

            sewingIndexA = new List<int>();
            sewingIndexB = new List<int>();

            _isReverse = false;
            _isRefresh = false;

            currSewingLine = Instantiate<SewingLine>(Resources.Load<SewingLine>("Prefabs/SewingLine"));
            currSewingLine.Init();
        }

        private Ray ray;
        RaycastHit hitInfo;

        // Update is called once per frame
        private void Update()
        {
            Raycast();

            DrawSewingLine();
            
            if (Input.GetKeyDown(KeyCode.R))
            {
                //逆序重新排列缝线顺序
                ReverseSewingPoint();
                _isRefresh = true;
            }
        }

        private void OnGUI()
        {
            GUIStyle style = new GUIStyle();
            style.normal.background = null;
            style.normal.textColor = Color.white;
            style.fontSize = 30;
            string text1 = "1.鼠标左键点击面片,确定缝合区间";
            string text2 = "2.空格按键对缝合区域进行mesh重构";
            string text3 = "3.R键翻转一侧的缝合区域";
            GUI.Label(new Rect(30, 30, 200, 30), text1, style);
            GUI.Label(new Rect(30, 80, 200, 30), text2, style);
            GUI.Label(new Rect(30, 130, 200, 30), text3, style);
        }

        private void Raycast()
        {
            ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            Debug.DrawRay(ray.origin, ray.direction * 100);

            if (Physics.Raycast(ray, out hitInfo, 100) && anchoredCursor)
            {
                if (hitInfo.collider.name != "patch")
                    return;

                currObj = hitInfo.collider.gameObject;
                //设置目标身上的光标指示器
                anchoredCursor.SetCursorActiveTo(true);
                anchoredCursor.SetCursorToAnchorPosition(currObj, hitInfo.point); //此处只用于显示光标指示器的位置

                if (Input.GetMouseButton(0))
                {
                    //对象A、B不一样
                    if (meshA == null || currObj == meshA.gameObject)
                    {
                        meshA = currObj.GetComponent<MeshFilter>();
                    }
                    else if (meshB == null || currObj == meshB.gameObject)
                    {
                        meshB = currObj.GetComponent<MeshFilter>();
                    }

                    if (currObj == meshA.gameObject)
                    {
                        //只翻转第二个对象的edges,所以meshA的edge不需要翻转
                        var added = anchoredCursor.SetCursorToAnchorPosition(currObj, hitInfo.point, gatherA);
                        //更新A边上的缝线点索引集合sewingIndexA
                        if (added)
                        {
                            sewingIndexA.Clear();
                            foreach (var edge in gatherA)
                            {
                                sewingIndexA.Add(edge.v1);
                            }

                            sewingIndexA.Add(gatherA[gatherA.Count - 1].v2);
                        }
                    }
                    else if (currObj == meshB.gameObject)
                    {
                        var added = anchoredCursor.SetCursorToAnchorPosition(currObj, hitInfo.point, gatherB,
                            _isReverse);
                        //更新B边上的缝线点索引集合sewingIndexB
                        if (added)
                        {
                            sewingIndexB.Clear();
                            if (_isReverse)
                            {
                                foreach (var edge in gatherB)
                                {
                                    sewingIndexB.Add(edge.v1);
                                }

                                sewingIndexB.Add(gatherB[gatherB.Count - 1].v2);
                            }
                            else
                            {
                                sewingIndexB.Add(gatherB[0].v1);
                                foreach (var edge in gatherB)
                                {
                                    sewingIndexB.Insert(0, edge.v2);
                                }
                            }
                        }
                    }
                }

                if (Input.GetMouseButtonDown(1)) //
                {
                    if (currObj == meshA.gameObject)
                    {
                        gatherA.Clear();
                        meshA = null;
                    }

                    if (currObj == meshB.gameObject)
                    {
                        gatherB.Clear();
                        meshB = null;
                    }
                }

                if (Input.GetMouseButtonUp(0)) //
                {
                    currObj = null;
                }
            }
            else
            {
                if (anchoredCursor)
                {
                    anchoredCursor.SetCursorActiveTo(false);
                }
            }
        }


        private void ReverseSewingPoint()
        {
            _isReverse = !_isReverse;
            //颠倒一个缝线的顺序即可
            gatherB.Reverse();
            for (int i = 0; i < gatherB.Count; i++)
            {
                gatherB[i] = new EdgeHelpers.Edge() {v1 = gatherB[i].v2, v2 = gatherB[i].v1};
            }

            sewingIndexB.Reverse();
        }

        private void DrawSewingLine()
        {
            if (meshA == null || meshA.sharedMesh == null)
            {
                return;
            }

            //更新A边上的缝线区域
            var edgeA = meshA.transform;
            var verticesA = meshA.sharedMesh.vertices;
            currSewingLine.UpdateSewingEdgeA(edgeA, verticesA, sewingIndexA);


            if (meshB == null || meshB.sharedMesh == null)
            {
                return;
            }

            //更新B边上的缝线区域
            var edgeB = meshB.transform;
            var verticesB = meshB.sharedMesh.vertices;
            currSewingLine.UpdateSewingEdgeB(edgeB, verticesB, sewingIndexB);


            //更新AB边中间的缝线:
            currSewingLine.UpdateSewingLines(
                edgeA, edgeB,
                verticesA, verticesB,
                sewingIndexA, sewingIndexB,
                _isRefresh
            );
            if (_isRefresh)
            {
                _isRefresh = false;
            }
        }

    }
}

image.png

按R翻转

image.png