AIDS: Joint ropes

The car was hit yesterday, the third vaccine will be given tomorrow, and the framework of building a new project will be revised next week.
In fact, there is no time, and Wuhan companies are very Buddhist. Once they arrive at the work building, they are empty, and the record of not working overtime for more than four years cannot be broken. Therefore, for the time being, you can directly use the joint joint provided by unity to complete the cable function.
It is required to build a cable rope function supporting physical properties, as follows:

We imagine that P0-P7 is a rigid bone node, and a rope is composed of n rigid segments. Firstly, this structure is constructed as follows:

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

public enum EBoneAlignType
{
    Front,
    Center,
    Back
}

public enum EBoneAxisType
{
    X,
    Y,
    Z
}

public class ElasticRigidBoneChain : MonoBehaviour
{
    [Header("Update bone chain")]
    public bool isUpdate = false;
    [Header("Number of bones")]
    public int boneCount = 100;
    [Header("Bone length")]
    public float boneLength = 1f;
    [Header("Bone chain alignment endpoint")]
    public EBoneAlignType boneAlignType = EBoneAlignType.Center;
    [Header("Bone chain orientation")]
    public EAxisType boneAxisType = EAxisType.X;
    [Header("Segmental preform")]
    public ABoneNodeBase boneNodePrefab;

    private Queue<ABoneNodeBase> boneNodeQueue = new Queue<ABoneNodeBase>();
    private List<ABoneNodeBase> boneNodeList = new List<ABoneNodeBase>();
    private List<Vector3> boneNodePosList = new List<Vector3>();

    private ChainRopeMeshSkin chainSkin;

    private void Awake()
    {
        chainSkin = GetComponent<ChainRopeMeshSkin>();
    }

    void Start()
    {
        UpdateBoneChain();
    }

    /// <summary>
    ///Update bone chain
    /// </summary>
    private void UpdateBoneChain()
    {
        float offset = 0f;
        float length = boneCount * boneLength;
        switch (boneAlignType)
        {
            case EBoneAlignType.Front:
                break;
            case EBoneAlignType.Center:
                offset = (-length / 2f);
                break;
            case EBoneAlignType.Back:
                offset = -length;
                break;
        }
        boneNodeList.Clear();
        boneNodePosList.Clear();
        for (int i = 0; i < boneCount; i++)
        {
            Vector3 npos = Vector3.zero;
            switch (boneAxisType)
            {
                case EAxisType.X:
                    npos = new Vector3(i * boneLength + offset, 0, 0);
                    break;
                case EAxisType.Y:
                    npos = new Vector3(0, i * boneLength + offset, 0);
                    break;
                case EAxisType.Z:
                    npos = new Vector3(0, 0, i * boneLength + offset);
                    break;
            }
            ABoneNodeBase bnode = AllocBoneNode();
            bnode.Initial(this, npos, true);
            boneNodeList.Add(bnode);
            boneNodePosList.Add(npos);
        }
    }
    /// <summary>
    ///Clean the bone chain
    /// </summary>
    private void ClearBoneChain()
    {
        for (int i = 0; i < boneNodeList.Count; i++)
        {
            ABoneNodeBase bnode = boneNodeList[i];
            RecycleBoneNode(bnode);
        }
        boneNodeList.Clear();
        boneNodePosList.Clear();
    }

    #region ///bonenode factory
    /// <summary>
    ///Generate a bone node
    /// </summary>
    /// <returns></returns>
    private ABoneNodeBase AllocBoneNode()
    {
        ABoneNodeBase bnode = null;
        if (boneNodeQueue.Count > 0)
        {
            bnode = boneNodeQueue.Dequeue();
        }
        if (bnode == null)
        {
            bnode = GameObject.Instantiate<ABoneNodeBase>(boneNodePrefab);
        }
        bnode.gameObject.SetActive(true);
        return bnode;
    }
    /// <summary>
    ///Reclaim a bone node
    /// </summary>
    /// <param name="bnode"></param>
    private void RecycleBoneNode(ABoneNodeBase bnode)
    {
        if (bnode != null)
        {
            bnode.gameObject.SetActive(false);
            boneNodeQueue.Enqueue(bnode);
        }
    }
    #endregion

    void Update()
    {
#if UNITY_EDITOR
        for (int i = 0; i < (boneNodeList.Count - 1); i++)
        {
            ABoneNodeBase fbnode = boneNodeList[i];
            ABoneNodeBase tbnode = boneNodeList[i + 1];
            Debug.DrawLine(fbnode.GetWorldPos(), tbnode.GetWorldPos(), Color.black);
        }
#endif
        if (isUpdate)
        {
            ClearBoneChain();
            UpdateBoneChain();
            isUpdate = false;
        }
    }
}

Bone node base class code:

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

public enum EBoneNodeType
{
    Sphere,
    Cube,
}

public abstract class ABoneNodeBase : MonoBehaviour
{
    [Header("Nodal initial coordinates")]
    public Vector3 origNodePos;
    [Header("Editable")]
    public bool isNodeEdit = true;
    [Header("Nodal type")]
    protected EBoneNodeType nodeType;
    [Header("Nodal size")]
    [Range(0.01f, 1f)]
    public float boneNodeSize = 0.1f;

    protected ElasticRigidBoneChain boneChain;

    protected virtual void Awake()
    {
  
    }

    public virtual void Initial(ElasticRigidBoneChain bc, Vector3 pos, bool edit)
    {
        boneChain = bc;
        origNodePos = pos;
        isNodeEdit = edit;
    }

    protected virtual void Start()
    {

    }

    public virtual Vector3 GetWorldPos()
    {
        return transform.position;
    }

    public virtual Vector3 GetLocalPos()
    {
        return transform.localPosition;
    }

    protected virtual void Update()
    {

    }

    protected virtual void OnDestroy()
    {

    }
}

The effects are as follows:

Next, add a joint component to each bone node. Of course, the first node and the tail node have fixed coordinates as constraint nodes, as follows:

//Configure dynamic joint information
        ABoneNodeBase startbnode = boneNodeList[0];
        startbnode.SetConstrain(RigidbodyConstraints.FreezePosition);
        startbnode.SetKinematic(false);
        for (int i = 1; i < boneCount; i++)
        {
            ABoneNodeBase prevbnode = boneNodeList[i - 1];
            ABoneNodeBase jointbnode = boneNodeList[i];
            jointbnode.SetJoint(EJointType.Fixed, prevbnode.rigid);
        }
        ABoneNodeBase endbnode = boneNodeList[boneCount - 1];
        endbnode.SetConstrain(RigidbodyConstraints.FreezePosition);

Add Joint function to bone node:

public enum EJointType
{
    Hinge,                  //chain
    Fixed,                  //fixed
    Spring,                 //Spring
    Chara,                  //role
    Config,                 //to configure
}

#region / / physical joint

    public virtual void SetGravity(bool enab)
    {
        rigid.useGravity = enab;
    }

    public virtual void SetKinematic(bool enab)
    {
        rigid.isKinematic = enab;
    }

    public virtual void SetConstrain(RigidbodyConstraints cst)
    {
        rigid.constraints = cst;
    }
    /// <summary>
    ///Set joint
    /// </summary>
    /// <param name="jtype"></param>
    /// <param name="ctrigid"></param>
    public virtual void SetJoint(EJointType jtype, Rigidbody ctrigid)
    {
        jointType = jtype;
        switch (jtype)
        {
            case EJointType.Hinge:
                {
                    joint = gameObject.AddComponent<HingeJoint>();
                }
                break;
            case EJointType.Fixed:
                {
                    joint = gameObject.AddComponent<FixedJoint>();
                }
                break;
            case EJointType.Spring:
                {
                    joint = gameObject.AddComponent<SpringJoint>();
                }
                break;
            case EJointType.Chara:
                {
                    joint = gameObject.AddComponent<CharacterJoint>();
                }
                break;
            case EJointType.Config:
                {
                    joint = gameObject.AddComponent<ConfigurableJoint>();
                }
                break;
        }
        joint.connectedBody = ctrigid;
        rigid.useGravity = true;
        rigid.isKinematic = false;
    }
    /// <summary>
    ///Reset joint
    /// </summary>
    public virtual void ResetJoint()
    {
        Joint.Destroy(joint);
        rigid.useGravity = false;
        rigid.isKinematic = true;
    }
    #endregion

The effects are as follows:

The physical effect can barely fit together. Of course, the joint configuration parameters provided by unity can also be adjusted to the appropriate effect (of course, I can't adjust it).
Next, we will start to skin the skeleton node chain. Here, we suggest to see the explanation on cylinder mesh construction first.
The rope mesh here can be derived from the cylinder as follows:

The rope structure can be imagined as an n-segment cylinder connected, so we must reconstruct the mesh vertices, normal vectors and triangular data, as follows:

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

[RequireComponent(typeof(MeshRenderer))]
[RequireComponent(typeof(MeshFilter))]
public class ChainRopeMeshSkin : MonoBehaviour
{
    [Range(0, 2f)]
    public float circleRadius = 0.1f;
    [Range(3, 50)]
    public int circleSegement = 20;
    [Range(0, 90f)]
    public float rotateAngle = 30f;

    private MeshRenderer meshRender;
    private MeshFilter meshFilter;

    private Mesh mesh;

    private bool isInited = false;
    private List<ABoneNodeBase> boneNodeList = new List<ABoneNodeBase>();
    private Vector3[] buildBonePoses;

    private void Awake()
    {
        meshRender = GetComponent<MeshRenderer>();
        meshFilter = GetComponent<MeshFilter>();
        mesh = new Mesh();
    }

    void Start()
    {

    }

    public void InitParams(List<ABoneNodeBase> bonelist)
    {
        isInited = true;
        boneNodeList = bonelist;
        BuildRopeMesh();
    }

    private void Update()
    {
        if (isInited)
        {
            if (CheckRequestRebuild())
            {
                RebuildRopeMesh();
            }
        }
    }

    #region / / reconstruction judgment

    /// <summary>
    ///Get bonepos array
    /// </summary>
    /// <returns></returns>
    private Vector3[] GetBoneNodePosArray()
    {
        Vector3[] poses = new Vector3[boneNodeList.Count];
        for (int i = 0; i < boneNodeList.Count; i++)
        {
            poses[i] = boneNodeList[i].GetWorldPos();
        }
        return poses;
    }
    /// <summary>
    ///Determine whether to rebuild the mesh
    /// </summary>
    /// <returns></returns>
    private bool CheckRequestRebuild()
    {
        Vector3[] poses = GetBoneNodePosArray();
        for (int i = 0; i < poses.Length; i++)
        {
            Vector3 a = poses[i];
            Vector3 b = buildBonePoses[i];
            if (!CheckVector3Approximate(a, b))
            {
                return true;
            }
        }
        return false;
    }

    private bool CheckVector3Approximate(Vector3 a, Vector3 b)
    {
        if (!Mathf.Approximately(a.x, b.x)
            || !Mathf.Approximately(a.y, b.y)
            || !Mathf.Approximately(a.z, b.z))
        {
            return false;
        }
        return true;
    }

    #endregion

    /// <summary>
    ///Building a rope mesh
    ///Initial build once
    /// </summary>
    public void BuildRopeMesh()
    {
        //Less than 3 bone nodes
        //It doesn't meet the needs of joint chain
        if (boneNodeList.Count < 3)
        {
#if UNITY_EDITOR
            Debug.LogErrorFormat("ChainRopeMeshSkin UpdateRopeMesh boneNodeList.Count = {0}", boneNodeList.Count);
#endif
            isInited = false;
            return;
        }
        CreateMeshVertices();
        CreateMeshNormals();
        CreateMeshTriangles();
        meshFilter.sharedMesh = mesh;
        //Record the currently built boneposes
        //For reconstruction after dynamic motion
        buildBonePoses = GetBoneNodePosArray();
    }
    /// <summary>
    ///Reconstruction of cable mesh
    ///Dynamic post motion reconstruction
    ///Reconstruct only vertices and normal vectors
    /// </summary>
    public void RebuildRopeMesh()
    {
        CreateMeshVertices();
        CreateMeshNormals();
        buildBonePoses = GetBoneNodePosArray();
    }
    /// <summary>
    ///Create mesh vertices
    /// </summary>
    private void CreateMeshVertices()
    {
        int ncount = boneNodeList.Count;
        List<Vector3> vertlist = new List<Vector3>();
        Vector3 fpos = boneNodeList[0].GetWorldPos();
        vertlist.Add(fpos);
        //Start tangent of cylinder in sequence
        for (int i = 0; i < ncount - 1; i++)
        {
            Vector3 start = boneNodeList[i].GetWorldPos();
            Vector3 end = boneNodeList[i + 1].GetWorldPos();
            Vector3[] sposarr = CalculateCirclePoints(start, end);
            if (i == 0)
            {
                vertlist.AddRange(sposarr);
            }
            vertlist.AddRange(sposarr);
            if (i == (ncount - 2))
            {
                Vector3[] eposarr = CalculateBiasPoints(start, end, sposarr);
                vertlist.AddRange(eposarr);
                vertlist.AddRange(eposarr);
            }
        }
        Vector3 tpos = boneNodeList[ncount - 1].GetWorldPos();
        vertlist.Add(tpos);

        mesh.vertices = vertlist.ToArray();
    }
    /// <summary>
    ///Create mesh normals
    /// </summary>
    private void CreateMeshNormals()
    {
        List<Vector3> normlist = new List<Vector3>();
        int ncount = boneNodeList.Count;
        //Starting surface
        Vector3 nf = (boneNodeList[0].GetWorldPos() - boneNodeList[1].GetWorldPos()).normalized;
        Vector3[] nfs = new Vector3[circleSegement];
        for (int i = 0; i < circleSegement; i++)
        {
            nfs[i] = nf;
        }
        normlist.Add(nf);
        normlist.AddRange(nfs);
        //Cylinder section
        for (int i = 0; i < ncount - 1; i++)
        {
            Vector3 start = boneNodeList[i].GetWorldPos();
            Vector3 end = boneNodeList[i + 1].GetWorldPos();
            Vector3[] sposarr = CalculateCirclePoints(start, end);
            Vector3[] nms = new Vector3[circleSegement];
            for (int k = 0; k < circleSegement; k++)
            {
                nms[k] = (sposarr[k] - start).normalized;
            }
            normlist.AddRange(nms);
            if (i == (ncount - 2))
            {
                Vector3[] eposarr = CalculateBiasPoints(start, end, sposarr);
                for (int k = 0; k < circleSegement; k++)
                {
                    nms[k] = (eposarr[k] - end).normalized;
                }
                normlist.AddRange(nms);
            }
        }
        //End face
        Vector3 nt = (boneNodeList[ncount - 1].GetWorldPos() - boneNodeList[ncount - 2].GetWorldPos()).normalized;
        Vector3[] nts = new Vector3[circleSegement];
        for (int i = 0; i < circleSegement; i++)
        {
            nts[i] = nt;
        }
        normlist.AddRange(nts);
        normlist.Add(nt);

        mesh.normals = normlist.ToArray();
    }
        /// <summary>
    ///Create mesh triangles
    /// </summary>
    private void CreateMeshTriangles()
    {
        int ncount = boneNodeList.Count;
        List<int> trilist = new List<int>();
        //Starting point circle
        int startindex = 0;     //Start point index
        for (int i = 0; i < circleSegement; i++)
        {
            int[] tris = new int[]
            {
                startindex,
                i+2>circleSegement?(i+2)%circleSegement:i+2,
                i+1
            };
            trilist.AddRange(tris);
        }
        //Middle section
        for (int i = 0; i < (ncount - 1); i++)
        {
            int findex = (i + 1) * circleSegement + 1;      //Start index in start interface
            int tindex = (i + 2) * circleSegement + 1;      //End point interface start index
            for (int k = 0; k < circleSegement; k++)
            {
                int[] tris = new int[]
                {
                    findex+k,
                    tindex+k+1>(tindex+circleSegement-1)?tindex:tindex+k+1,
                    tindex+k,
                };
                trilist.AddRange(tris);
                tris = new int[]
                {
                    findex+k,
                    findex+k+1>(findex+circleSegement-1)?findex:findex+k+1,
                    tindex+k+1>(tindex+circleSegement-1)?tindex:tindex+k+1,
                };
                trilist.AddRange(tris);
            }
        }
        //End circle
        int endindex = (ncount + 2) * circleSegement + 1;       //Endpoint index
        int eindex = (ncount + 1) * circleSegement + 1;         //End circle start index
        for (int i = 0; i < circleSegement; i++)
        {
            int[] tris = new int[]
            {
                endindex,
                eindex+i,
                eindex+i+1>(eindex+circleSegement-1)?eindex:eindex+i+1
            };
            trilist.AddRange(tris);
        }

        mesh.triangles = trilist.ToArray();
    }
    /// <summary>
    ///Create mesh uvs
    /// </summary>
    private void CreateMeshUVs()
    {
        List<Vector2> uvlist = new List<Vector2>();

        mesh.uv = uvlist.ToArray();
    }

    /// <summary>
    ///Clean the rope grid
    /// </summary>
    public void ClearRopeMesh()
    {
        isInited = false;
        mesh.Clear();
        meshFilter.sharedMesh = null;
        boneNodeList.Clear();
        buildBonePoses = null;
    }
    #region / / calculate space circle parameters
    /// <summary>
    ///Calculate start point
    ///End end
    ///Spatial circular coordinate array
    /// </summary>
    /// <param name="start"></param>
    /// <param name="end"></param>
    /// <returns></returns>
    private Vector3[] CalculateCirclePoints(Vector3 start, Vector3 end)
    {
        Vector3 p2 = RotateAroundMatchAxis(start, end, rotateAngle * Mathf.Deg2Rad);
        Vector3 p1 = RayLineCrossPanel(start, end, p2);
        Vector3 p = start + (p1 - start).normalized * circleRadius;
        Vector3[] posarr = new Vector3[circleSegement];
        posarr[0] = p;
        Vector3 naxis = (end - start).normalized;
        float segerad = 2f * Mathf.PI / (float)circleSegement;
        for (int i = 1; i < circleSegement; i++)
        {
            float rad = segerad * i;
            Vector3 segepos = RotateAroundAnyAxis(start, p, naxis, rad);
            posarr[i] = segepos;
        }
        return posarr;
    }
    /// <summary>
    ///Calculate the spatial circular coordinates of start
    ///Offset the coordinate array according to the end point
    /// </summary>
    /// <param name="start"></param>
    /// <param name="end"></param>
    /// <param name="sposarr"></param>
    /// <returns></returns>
    private Vector3[] CalculateBiasPoints(Vector3 start, Vector3 end, Vector3[] sposarr)
    {
        Vector3[] eposarr = new Vector3[sposarr.Length];
        Vector3 offset = end - start;
        for (int i = 0; i < sposarr.Length; i++)
        {
            Vector3 spos = sposarr[i];
            Vector3 epos = spos + offset;
            eposarr[i] = epos;
        }
        return eposarr;
    }

    /// <summary>
    ///The coordinate of point p(x,y,z) after rotating around any coordinate axis with start as the starting point
    /// </summary>
    /// <param name="start"></param>
    /// <param name="naxis"></param>
    /// <param name="rad"></param>
    /// <returns></returns>
    private Vector3 RotateAroundAnyAxis(Vector3 start, Vector3 p, Vector3 naxis, float rad)
    {
        float n1 = naxis.x;
        float n2 = naxis.y;
        float n3 = naxis.z;

        //Gets the local coordinates of p relative to start
        p -= start;

        float sin = Mathf.Sin(rad);
        float cos = Mathf.Cos(rad);

        Matrix3x3 mat = new Matrix3x3();

        mat.m00 = n1 * n1 * (1 - cos) + cos;
        mat.m01 = n1 * n2 * (1 - cos) - n3 * sin;
        mat.m02 = n1 * n3 * (1 - cos) + n2 * sin;

        mat.m10 = n1 * n2 * (1 - cos) + n3 * sin;
        mat.m11 = n2 * n2 * (1 - cos) + cos;
        mat.m12 = n2 * n3 * (1 - cos) - n1 * sin;

        mat.m20 = n1 * n3 * (1 - cos) - n2 * sin;
        mat.m21 = n2 * n3 * (1 - cos) + n1 * sin;
        mat.m22 = n3 * n3 * (1 - cos) + cos;

        //After rotating around the axis, it is processed into world coordinates
        Vector3 px = mat * p + start;

        return px;
    }

    /// <summary>
    ///Calculate the F equation of the plane where start is located through start end
    ///Calculate the intersection p1 of ray and plane F through end p2
    /// </summary>
    /// <param name="start"></param>
    /// <param name="end"></param>
    /// <param name="p2"></param>
    /// <returns></returns>
    private Vector3 RayLineCrossPanel(Vector3 start, Vector3 end, Vector3 p2)
    {
        //start = from
        //end = to
        //Constructing the parameters of plane F equation
        Vector3 ft = end - start;
        float u = ft.x, v = ft.y, w = ft.z;
        float a = start.x, b = start.y, c = start.z;
        //Build ray tp2 parameters
        float sx = end.x;
        float sy = end.y;
        float sz = end.z;
        Vector3 ntp2 = (p2 - end).normalized;
        float dx = ntp2.x;
        float dy = ntp2.y;
        float dz = ntp2.z;
        //Calculate p1
        float n = ((u * a + v * b + w * c) - (u * sx + v * sy + w * sz)) / (u * dx + v * dy + w * dz);
        Vector3 p1 = end + n * ntp2;
        return p1;
    }

    /// <summary>
    ///According to the end - > start unit vector orientation
    ///Selective rotation according to xyz axis
    ///Avoid that ret and start are in common
    /// </summary>
    /// <param name="start"></param>
    /// <param name="end"></param>
    /// <param name="rad"></param>
    /// <returns></returns>
    private Vector3 RotateAroundMatchAxis(Vector3 start, Vector3 end, float rad)
    {
        Vector3 dir = (start - end).normalized;
        Vector3 ret;
        if (CheckVector3Approximate(dir, Vector3.right) || CheckVector3Approximate(dir, Vector3.left))
        {
            ret = RotateAroundYAxis(start, end, rad);
            return ret;
        }
        if (CheckVector3Approximate(dir, Vector3.up) || CheckVector3Approximate(dir, Vector3.down))
        {
            ret = RotateAroundZAxis(start, end, rad);
            return ret;
        }
        if (CheckVector3Approximate(dir, Vector3.forward) || CheckVector3Approximate(dir, Vector3.back))
        {
            ret = RotateAroundXAxis(start, end, rad);
            return ret;
        }
        ret = RotateAroundXAxis(start, end, rad);
        return ret;
    }

    private Vector3 RotateAroundXAxis(Vector3 start, Vector3 end, float rad)
    {
        Matrix3x3 mat = new Matrix3x3();

        float cos = Mathf.Cos(rad);
        float sin = Mathf.Sin(rad);

        mat.m00 = 1;
        mat.m01 = 0;
        mat.m02 = 0;
        mat.m10 = 0;
        mat.m11 = cos;
        mat.m12 = -sin;
        mat.m20 = 0;
        mat.m21 = sin;
        mat.m22 = cos;

        Vector3 ret = mat * (start - end) + end;
        return ret;
    }

    private Vector3 RotateAroundYAxis(Vector3 start, Vector3 end, float rad)
    {
        Matrix3x3 mat = new Matrix3x3();

        float cos = Mathf.Cos(rad);
        float sin = Mathf.Sin(rad);

        mat.m00 = cos;
        mat.m01 = 0;
        mat.m02 = sin;
        mat.m10 = 0;
        mat.m11 = 1;
        mat.m12 = 0;
        mat.m20 = -sin;
        mat.m21 = 0;
        mat.m22 = cos;

        Vector3 ret = mat * (start - end) + end;
        return ret;
    }

    private Vector3 RotateAroundZAxis(Vector3 start, Vector3 end, float rad)
    {
        Matrix3x3 mat = new Matrix3x3();

        float cos = Mathf.Cos(rad);
        float sin = Mathf.Sin(rad);

        mat.m00 = cos;
        mat.m01 = -sin;
        mat.m02 = 0;
        mat.m10 = sin;
        mat.m11 = cos;
        mat.m12 = 0;
        mat.m20 = 0;
        mat.m21 = 0;
        mat.m22 = 1;

        Vector3 ret = mat * (start - end) + end;
        return ret;
    }

    #endregion
}

By the way, the problem that I didn't pay attention to in the previous article is modified: if the unit normal vector of start end coincides with the x, y and z axes, it needs to be changed to different axial rotation, otherwise the result is Co located with start, and the spatial circle cannot be calculated.
At the same time, I separate the calculation of Vertices, Normals, Triangles and UVs. On the one hand, it is convenient to understand. On the other hand, the reconstruction of mesh only needs to reconstruct Vertices and Normals (UVs can not be reconstructed because I use fixed length bone nodes). Therefore, separating them for reconstruction can save a little amount of calculation.
Here, we can also merge the reconstruction of Vertices and Normals, as follows:

/// <summary>
    ///Create mesh vertices and normals
    /// </summary>
    private void CreateMeshVerticesAndNormals()
    {
        int ncount = boneNodeList.Count;
        List<Vector3> vertlist = new List<Vector3>();
        List<Vector3> normlist = new List<Vector3>();
        //Starting surface
        Vector3 fpos = boneNodeList[0].GetWorldPos();
        vertlist.Add(fpos);
        Vector3 nf = (boneNodeList[0].GetWorldPos() - boneNodeList[1].GetWorldPos()).normalized;
        Vector3[] nfs = new Vector3[circleSegement];
        for (int i = 0; i < circleSegement; i++)
        {
            nfs[i] = nf;
        }
        normlist.Add(nf);
        normlist.AddRange(nfs);
        //Middle section
        for (int i = 0; i < ncount - 1; i++)
        {
            Vector3 start = boneNodeList[i].GetWorldPos();
            Vector3 end = boneNodeList[i + 1].GetWorldPos();
            Vector3[] sposarr = CalculateCirclePoints(start, end);
            if (i == 0)
            {
                vertlist.AddRange(sposarr);
            }
            vertlist.AddRange(sposarr);
            if (i == (ncount - 2))
            {
                Vector3[] eposarr = CalculateBiasPoints(start, end, sposarr);
                vertlist.AddRange(eposarr);
                vertlist.AddRange(eposarr);
            }
            Vector3[] nms = new Vector3[circleSegement];
            for (int k = 0; k < circleSegement; k++)
            {
                nms[k] = (sposarr[k] - start).normalized;
            }
            normlist.AddRange(nms);
            if (i == (ncount - 2))
            {
                Vector3[] eposarr = CalculateBiasPoints(start, end, sposarr);
                for (int k = 0; k < circleSegement; k++)
                {
                    nms[k] = (eposarr[k] - end).normalized;
                }
                normlist.AddRange(nms);
            }
        }
        //End face
        Vector3 tpos = boneNodeList[ncount - 1].GetWorldPos();
        vertlist.Add(tpos);
        Vector3 nt = (boneNodeList[ncount - 1].GetWorldPos() - boneNodeList[ncount - 2].GetWorldPos()).normalized;
        Vector3[] nts = new Vector3[circleSegement];
        for (int i = 0; i < circleSegement; i++)
        {
            nts[i] = nt;
        }
        normlist.AddRange(nts);
        normlist.Add(nt);

        mesh.vertices = vertlist.ToArray();
        mesh.normals = normlist.ToArray();
    }

A little bit of operation performance is saved, and the effects are as follows:

Next, the uv mapping calculation is constructed as follows:

We split the rope into circles at both ends, corresponding to the white circles of start and end, and the expanded rectangular section in the middle, corresponding to the black rectangle of 123456789.
Next, the uv mapping is calculated as follows:

    /// <summary>
    ///Create mesh uvs
    /// </summary>
    private void CreateMeshUVs()
    {
        float segrad = 2f * Mathf.PI / (float)circleSegement;
        float uvcircleradius = 0.25f;
        int ncount = boneNodeList.Count;
        List<Vector2> uvlist = new List<Vector2>();
        //Starting point circle
        Vector2 suv = new Vector2(0.25f, 0.75f);
        uvlist.Add(suv);
        Vector2[] suvarr = new Vector2[circleSegement];
        for (int i = 0; i < circleSegement; i++)
        {
            float rad = segrad * i;
            suvarr[i] = GetCircleUV(suv, uvcircleradius, rad);
        }
        uvlist.AddRange(suvarr);
        //Middle section
        for (int i = 0; i < ncount; i++)
        {
            Vector2[] muvarr = new Vector2[circleSegement];
            for (int k = 0; k < circleSegement; k++)
            {
                float mu = (float)i / (float)(ncount - 1);
                float mv = (float)k / (float)(circleSegement) * 0.5f;
                muvarr[k] = new Vector2(mu, mv);
            }
            uvlist.AddRange(muvarr);
        }
        //End circle
        Vector2[] tuvarr = new Vector2[circleSegement];
        for (int i = 0; i < circleSegement; i++)
        {
            tuvarr[i] = suvarr[i] + new Vector2(0.5f, 0);
        }
        uvlist.AddRange(tuvarr);
        Vector2 tuv = new Vector2(0.75f, 0.75f);
        uvlist.Add(tuv);
        mesh.uv = uvlist.ToArray();
    }

    private Vector2 GetCircleUV(Vector2 center, float radius, float rad)
    {
        float u = center.x + Mathf.Cos(rad) * radius;
        float v = center.y + Mathf.Sin(rad) * radius;
        Vector2 uv = new Vector2(u, v);
        return uv;
    }

The effects are as follows:

Two problems can be seen:
      1. The "front and back" of the end circle is wrong because the "front" faces the Start starting face
      2. There is a "gap" in the cylinder section, because the starting point and end point of our cylinder section are not at the same point, so there will be a "gap" in the uv mapping, as follows:

There is a gap between p0 and pn, which leads to the problem of uv mapping. The solution here is to treat p0 and pn as common points in the vertices calculation.
The final revision is as follows:
Processing end circle reverse

/// <summary>
    ///Create mesh uvs
    /// </summary>
    private void CreateMeshUVs()
    {
        float segrad = 2f * Mathf.PI / (float)(circleSegement - 1);  //uv treatment of P0 and PN
        float uvcircleradius = 0.25f;
        int ncount = boneNodeList.Count;
        List<Vector2> uvlist = new List<Vector2>();
        //Starting point circle
        Vector2 suv = new Vector2(0.25f, 0.75f);
        uvlist.Add(suv);
        Vector2[] suvarr = new Vector2[circleSegement];
        for (int i = 0; i < circleSegement; i++)
        {
            float rad = segrad * i;
            suvarr[i] = GetCircleUV(suv, uvcircleradius, rad);
        }
        uvlist.AddRange(suvarr);
        //Middle section
        for (int i = 0; i < ncount; i++)
        {
            Vector2[] muvarr = new Vector2[circleSegement];
            for (int k = 0; k < circleSegement; k++)
            {
                float mu = (float)i / (float)(ncount - 1);
                float mv = (float)k / (float)circleSegement * 0.5f;
                muvarr[k] = new Vector2(mu, mv);
            }
            uvlist.AddRange(muvarr);
        }
        //End circle
        Vector2[] tuvarr = new Vector2[circleSegement];
        for (int i = 0; i < circleSegement; i++)
        {
            tuvarr[i] = suvarr[circleSegement - i - 1] + new Vector2(0.5f, 0);  //Reverse uv processing
        }
        uvlist.AddRange(tuvarr);
        Vector2 tuv = new Vector2(0.75f, 0.75f);
        uvlist.Add(tuv);
        mesh.uv = uvlist.ToArray();
    }

Processing section start and end coordinates in common

private Vector3[] CalculateCirclePoints(Vector3 start, Vector3 end)
    {
        Vector3 p2 = RotateAroundMatchAxis(start, end, rotateAngle * Mathf.Deg2Rad);
        Vector3 p1 = RayLineCrossPanel(start, end, p2);
        Vector3 p = start + (p1 - start).normalized * circleRadius;
        Vector3[] posarr = new Vector3[circleSegement];
        posarr[0] = p;
        Vector3 naxis = (end - start).normalized;
        float segerad = 2f * Mathf.PI / (float)(circleSegement - 1);  //Processing P0 and PN in common
        for (int i = 1; i < circleSegement; i++)
        {
            float rad = segerad * i;
            Vector3 segepos = RotateAroundAnyAxis(start, p, naxis, rad);
            posarr[i] = segepos;
        }
        return posarr;
    }

The effects are as follows:

The processing method is quite simple. You only need to extend rad calculation by one unit.
Next, I need to complete the required special effects, which is why I need to calculate uv. Otherwise, I don't even need a map, just adjust the color value, as follows:

Shader "ElasticRope/ElasticRopeElactricUnlitShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _MainColor("Color",Color) = (1,1,1,1)
        _Speed("Speed",Range(0,2)) = 1
        _Pow("Pow",Range(0,500)) = 10
        [Toggle]_IsEffect("Effect",int) = 0
        [Toggle]_IsInverse("Inverse",int) = 0
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue"="Transparent" }
        LOD 100

        Pass
        {
            Blend SrcAlpha OneMinusSrcAlpha
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float4 _MainColor;
            float _Speed;
            float _Pow;
            int _IsEffect;
            int _IsInverse;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {  
                fixed4 col = _MainColor;
                if(_IsEffect == 1)
                {
                    if(i.uv.y<=0.5)
                    {
                        if(_IsInverse == 0){
                            i.uv.x += _Time.y*_Speed;
                        }else{
                            i.uv.x -= _Time.y*_Speed;
                        }
                        float s = sin(i.uv.x*_Pow);
                        col.a = saturate(s);
                    }else{
                        col.a = 0;
                    }
                }else{
                    col.a = 0;
                }
                return col;
            }
            ENDCG
        }
    }
}

The effects are as follows:

The demand is temporarily met. The project is in a hurry at the end of the year, and the time budget is insufficient. Later, there is time to rewrite a mass spring model, because there are so many problems in the actual use of the joint provided by unity.

Keywords: Unity Shader

Added by kooks on Thu, 23 Dec 2021 16:07:57 +0200