辅助工具:关节绳(2)

      需要做一个仿真功能,找到以前的圆柱体和关节绳网格构建的算法做点修改。
      修改了网格构建参数的计算方式,如下:
在这里插入图片描述
      以每个骨骼节点的本地坐标系进行网格参数计算,相应修改:
      1.圆周坐标计算按start本地坐标系进行计算(start本地坐标系的xy轴位于平面G,z轴为平面G的法向量)
      2.考虑一系列仿真计算在世界坐标系进行的,所以顶点坐标以世界坐标系计算,再转换到本地坐标
      修改代码如下:

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

namespace CurveRope
{
    [RequireComponent(typeof(MeshRenderer))]
    [RequireComponent(typeof(MeshFilter))]
    public class CurveRopeMesh : MonoBehaviour
    {
        [Header("圆半径")]
        [Range(0, 2f)]
        public float circleRadius = 0.1f;
        [Header("圆扇形数量")]
        [Range(4, 50)]
        public int circleSegement = 20;
        [Header("是否更新网格")]
        public bool isUpdate = false;

        private MeshRenderer meshRender;
        private MeshFilter meshFilter;

        private Mesh mesh;

        private bool isInited = false;
        private List<BoneBase> boneBaseList;
        private Vector3[] buildBonePoses;
        private Vector3[] buildBoneAngles;

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

        void Start()
        {

        }

        public void Init(List<BoneBase> bonelist)
        {
            isInited = true;
            boneBaseList = bonelist;
            BuildRopeMesh();
        }

        void Update()
        {
            if (isInited)
            {
                if (CheckRequestRebuild())
                {
                    RebuildRopeMesh();
                }
                if (isUpdate)
                {
                    RebuildRopeMesh();
                    isUpdate = false;
                }
            }
        }

        #region ///重构建判断

        /// <summary>
        /// 获取bonepos数组
        /// 检测本地坐标发生变化
        /// </summary>
        /// <returns></returns>
        private Vector3[] GetBonePosArr()
        {
            Vector3[] poses = new Vector3[boneBaseList.Count];
            for (int i = 0; i < boneBaseList.Count; i++)
            {
                poses[i] = boneBaseList[i].GetLocalPos();
            }
            return poses;
        }
        /// <summary>
        /// 获取boneangle数组
        /// 检测本地旋转变化
        /// </summary>
        /// <returns></returns>
        private Vector3[] GetBoneAngleArr()
        {
            Vector3[] angles = new Vector3[boneBaseList.Count];
            for (int i = 0; i < boneBaseList.Count; i++)
            {
                angles[i] = boneBaseList[i].GetLocalAngles();
            }
            return angles;
        }
        /// <summary>
        /// 判断是否需要重建网格
        /// 判断本地坐标或者旋转改变才重建网格
        /// </summary>
        /// <returns></returns>
        private bool CheckRequestRebuild()
        {
            Vector3[] poses = GetBonePosArr();
            Vector3[] angles = GetBoneAngleArr();
            for (int i = 0; i < poses.Length; i++)
            {
                Vector3 a = poses[i];
                Vector3 b = buildBonePoses[i];
                if (!CheckVector3Approximate(a, b))
                {
                    return true;
                }
                a = angles[i];
                b = buildBoneAngles[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>
        /// 构建管道网格
        /// 初始构建一次
        /// </summary>
        public void BuildRopeMesh()
        {
            //小于3个骨骼节点
            //就不满足骨骼管道的需求
            if (boneBaseList.Count < 3)
            {
#if UNITY_EDITOR
                Debug.LogErrorFormat("CurveRopeMesh BuildRopeMesh boneBaseList.Count = {0}", boneBaseList.Count);
#endif
                isInited = false;
                return;
            }
            CreateMeshVerticesAndNormals();
            CreateMeshTriangles();
            CreateMeshUVs();
            meshFilter.sharedMesh = mesh;
            //记录当前构建的boneposes
            //用于运动后的重建
            buildBonePoses = GetBonePosArr();
            buildBoneAngles = GetBoneAngleArr();
        }
        /// <summary>
        /// 重构建管道网格
        /// 只构建顶点坐标
        /// </summary>
        public void RebuildRopeMesh()
        {
            CreateMeshVertices();
            //重建后再次记录boneposes
            buildBonePoses = GetBonePosArr();
            buildBoneAngles = GetBoneAngleArr();
        }
        /// <summary>
        /// 创建mesh vertices
        /// 刷新网格只需要重建顶点
        /// </summary>
        private void CreateMeshVertices()
        {
            int ncount = boneBaseList.Count;
            List<Vector3> vertlist = new List<Vector3>();
            //起点
            Vector3 wfpos = boneBaseList[0].GetWorldPos();
            vertlist.Add(wfpos);
            //中间截面
            for (int i = 0; i < ncount - 1; i++)
            {
                BoneBase startbone = boneBaseList[i];
                BoneBase endbone = boneBaseList[i + 1];
                Vector3 wstart = startbone.GetWorldPos();
                Vector3 wend = endbone.GetWorldPos();
                Vector3[] wsposarr = CalculateCirclePoints(startbone.GetWorldPos(), startbone.GetRight(), startbone.GetForward());
                //起点面坐标
                if (i == 0)
                {
                    vertlist.AddRange(wsposarr);
                }
                //中间面坐标
                vertlist.AddRange(wsposarr);
                //终点面坐标
                if (i == (ncount - 2))
                {
                    Vector3[] eposarr = CalculateBiasPoints(wstart, wend, wsposarr);
                    vertlist.AddRange(eposarr);
                    vertlist.AddRange(eposarr);
                }
            }
            //终点
            Vector3 tpos = boneBaseList[ncount - 1].GetWorldPos();
            vertlist.Add(tpos);

            mesh.vertices = WorldToLocalPoses(vertlist.ToArray());
        }
        /// <summary>
        /// 创建mesh vertices和normals
        /// 计算世界坐标系顶点坐标转本地坐标
        /// 计算本地坐标法向量
        /// </summary>
        private void CreateMeshVerticesAndNormals()
        {
            int ncount = boneBaseList.Count;
            List<Vector3> vertlist = new List<Vector3>();
            List<Vector3> normlist = new List<Vector3>();
            //起点
            Vector3 wfpos = boneBaseList[0].GetWorldPos();
            vertlist.Add(wfpos);
            //起点法向量
            Vector3 nf = (boneBaseList[0].GetLocalPos() - boneBaseList[1].GetLocalPos()).normalized;
            Vector3[] nfs = new Vector3[circleSegement];
            for (int i = 0; i < circleSegement; i++)
            {
                nfs[i] = nf;
            }
            normlist.Add(nf);
            normlist.AddRange(nfs);
            //中间截面
            for (int i = 0; i < ncount - 1; i++)
            {
                BoneBase startbone = boneBaseList[i];
                BoneBase endbone = boneBaseList[i + 1];
                Vector3 wstart = startbone.GetWorldPos();
                Vector3 wend = endbone.GetWorldPos();
                Vector3 lstart = startbone.GetLocalPos();
                Vector3 lend = endbone.GetLocalPos();
                Vector3[] wsposarr = CalculateCirclePoints(startbone.GetWorldPos(), startbone.GetRight(), startbone.GetForward());
                Vector3[] lsposarr = CalculateCirclePoints(startbone.GetLocalPos(), startbone.GetRight(), startbone.GetForward());
                //起点面坐标
                if (i == 0)
                {
                    vertlist.AddRange(wsposarr);
                }
                //中间面坐标
                vertlist.AddRange(wsposarr);
                //终点面坐标
                if (i == (ncount - 2))
                {
                    Vector3[] eposarr = CalculateBiasPoints(wstart, wend, wsposarr);
                    vertlist.AddRange(eposarr);
                    vertlist.AddRange(eposarr);
                }
                //法向量
                Vector3[] nms = new Vector3[circleSegement];
                for (int k = 0; k < circleSegement; k++)
                {
                    nms[k] = (lsposarr[k] - lstart).normalized;
                }
                normlist.AddRange(nms);
                if (i == (ncount - 2))
                {
                    Vector3[] eposarr = CalculateBiasPoints(lstart, lend, lsposarr);
                    for (int k = 0; k < circleSegement; k++)
                    {
                        nms[k] = (eposarr[k] - lend).normalized;
                    }
                    normlist.AddRange(nms);
                }
            }
            //终点
            Vector3 tpos = boneBaseList[ncount - 1].GetWorldPos();
            vertlist.Add(tpos);
            //终点法向量
            Vector3 nt = (boneBaseList[ncount - 1].GetLocalPos() - boneBaseList[ncount - 2].GetLocalPos()).normalized;
            Vector3[] nts = new Vector3[circleSegement];
            for (int i = 0; i < circleSegement; i++)
            {
                nts[i] = nt;
            }
            normlist.AddRange(nts);
            normlist.Add(nt);

            mesh.vertices = WorldToLocalPoses(vertlist.ToArray());
            mesh.normals = normlist.ToArray();
        }
        /// <summary>
        /// 世界坐标转本地坐标
        /// 用于网格顶点世界坐标系仿真计算后转到本地坐标
        /// </summary>
        /// <param name="wposarr"></param>
        /// <returns></returns>
        private Vector3[] WorldToLocalPoses(Vector3[] wposarr)
        {
            Vector3[] lposarr = new Vector3[wposarr.Length];
            for (int i = 0; i < wposarr.Length; i++)
            {
                Vector3 wpos = wposarr[i];
                lposarr[i] = transform.InverseTransformPoint(wpos);
            }
            return lposarr;
        }

        /// <summary>
        /// 创建mesh triangles
        /// </summary>
        private void CreateMeshTriangles()
        {
            int ncount = boneBaseList.Count;
            List<int> trilist = new List<int>();
            //起点圆
            int startindex = 0;     //起始点索引
            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);
            }
            //中间截面
            for (int i = 0; i < (ncount - 1); i++)
            {
                int findex = (i + 1) * circleSegement + 1;      //起点界面开始索引
                int tindex = (i + 2) * circleSegement + 1;      //终点界面开始索引
                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);
                }
            }
            //终点圆
            int endindex = (ncount + 2) * circleSegement + 1;       //终止点索引
            int eindex = (ncount + 1) * circleSegement + 1;         //终点圆起点索引
            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>
        /// 创建mesh uvs
        /// </summary>
        private void CreateMeshUVs()
        {
            float segrad = 2f * Mathf.PI / (float)(circleSegement - 1);  //处理p0 pn的uv
            float uvcircleradius = 0.25f;
            int ncount = boneBaseList.Count;
            List<Vector2> uvlist = new List<Vector2>();
            //起点圆
            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);
            //中间截面
            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);
            }
            //终点圆
            Vector2[] tuvarr = new Vector2[circleSegement];
            for (int i = 0; i < circleSegement; i++)
            {
                tuvarr[i] = suvarr[circleSegement - i - 1] + new Vector2(0.5f, 0);  //处理end圆uv反向
            }
            uvlist.AddRange(tuvarr);
            Vector2 tuv = new Vector2(0.75f, 0.75f);
            uvlist.Add(tuv);
            mesh.uv = uvlist.ToArray();
        }

        /// <summary>
        /// (0,0)-(1,1)uv系数
        /// uv中心,uv长度,uv弧度
        /// 计算绕圆周的uv
        /// </summary>
        /// <param name="uvcenter"></param>
        /// <param name="uvradius"></param>
        /// <param name="rad"></param>
        /// <returns></returns>
        private Vector2 GetCircleUV(Vector2 uvcenter, float uvradius, float rad)
        {
            float u = uvcenter.x + Mathf.Cos(rad) * uvradius;
            float v = uvcenter.y + Mathf.Sin(rad) * uvradius;
            Vector2 uv = new Vector2(u, v);
            return uv;
        }

        /// <summary>
        /// 清理管道网格
        /// </summary>
        public void ClearRopeMesh()
        {
            isInited = false;
            mesh.Clear();
            meshFilter.sharedMesh = null;
            boneBaseList.Clear();
            buildBonePoses = null;
        }

        #region ///计算空间圆参数
        /// <summary>
        /// 根据start坐标、x轴向量和z轴向量,计算得到空间圆
        /// </summary>
        /// <param name="start"></param>
        /// <param name="nforward"></param>
        /// <returns></returns>
        private Vector3[] CalculateCirclePoints(Vector3 start, Vector3 nright, Vector3 nforward)
        {
            Vector3 p = start + nright * circleRadius;
            Vector3[] posarr = new Vector3[circleSegement];
            posarr[0] = p;
            Vector3 naxis = nforward;
            float segerad = 2f * Mathf.PI / (float)(circleSegement - 1);  //处理p0 pn共点
            for (int i = 1; i < circleSegement; i++)
            {
                float rad = segerad * i;
                Vector3 segepos = RotateAroundAnyAxis(start, p, naxis, rad);
                posarr[i] = segepos;
            }
            return posarr;
        }
        /// <summary>
        /// 根据start的空间圆坐标
        /// 根据end终点偏移出坐标数组
        /// </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>
        /// p(x,y,z)点绕start为起点的任意坐标轴旋转后的坐标
        /// </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;

            //获取p相对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;

            //绕轴旋转后,处理成世界坐标
            Vector3 px = mat * p + start;

            return px;
        }
        #endregion
    }
}

      效果如下:
在这里插入图片描述

      好,有时间继续。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值