需要做一个仿真功能,找到以前的圆柱体和关节绳网格构建的算法做点修改。
修改了网格构建参数的计算方式,如下:
以每个骨骼节点的本地坐标系进行网格参数计算,相应修改:
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
}
}
效果如下:
好,有时间继续。