正向动力学:根据父骨骼的移动,一步步正向推导出子骨骼的位置。
反向动力学:根据骨骼末端位置的移动,反向推导出父骨骼及其余上级骨骼的位置。
CCD Ik(循环坐标下降反向动力学):
2D:以当前骨骼起点为轴,分别连接目标点与骨骼末端,保持目标点与骨骼起点的连线不动,旋转当前骨骼(注意子骨骼也会随之转动),使得骨骼末端位置与当前骨骼起点的连线重合。从子骨骼开始到全部父骨骼完成一次转动,视为一次迭代,迭代次数越多,效果越好。
其核心代码思想及相关注释如下:
//bones[i].a表示骨骼起点,bones[i].b表示骨骼终点,bones[i].len表示骨骼长度,bones.Length表示骨骼数目,target表示目标点
for (int n = 0; n<iterations; n++)//迭代次数
{
for (int i = arrows.Length - 1; i >= 0; i--)//遍历每根骨骼
{
bones[i].angle = (angle + Vector2.SignedAngle(bones[bones.Length - 1].b - a, target - a)) % 360f;//angle表示相对于父骨骼的角度,获取目标点到当前骨骼起点与末端位置到当前骨骼起点两条线间的夹角
int j = i;
Vector2 origin = bones[j].a;
if(j!=0)
Vector2 right = bones[i - 1].forward;
else
Vector2 right = Vector2.right; //(1,0)
for(; j<bones.Length ; j++)//当前骨骼旋转后,变更子骨骼的位置
{
bones[j].a = origin;
//变更骨骼位置
bones[j].b = bones[j].a + Vector2(Mathf.Cos(bones[j].angle* Math.Deg2Rad + Mathf.Atan2(right.y, right.x)),Mathf.Sin(bones[j].angle* Math.Deg2Rad + Mathf.Atan2(right.y, right.x)));
//修改以下信息以便更改后续子骨骼的位置
origin = bones[j].b;
right = bones[j].forward;
}
}
}
3D:将当前骨骼起点连接目标点和骨骼末端,再做这两条相交向量的法向量,以该法向量为轴,做上述旋转,迭代。
其核心代码思想及相关注释如下:
for (int n = 0; n < iterations; n++)//迭代次数
{
for (int i = bones.Length - 1; i > 0; i--)//遍历骨骼
{
Vector3 tolastbone = bones[bones.Length - 1].b - bones[i].a;//当前骨骼起点到末端位置
Vector3 totarget = target - bones[i].a;//当前骨骼起点到目标点
Vector3 axis = Vector3.Cross(tolastbone, totarget).normalized;//去上述二向量的法向量做旋转轴
float angle = Vector3.SignedAngle(tolastbone, totarget, axis);//以该旋转轴所需旋转的角度
for (int j = i; j < bones.Length; j++)//依次修改每根骨骼及其子骨骼的位置
{
bones[j].b = bones[i - 1].b + Quaternion.AngleAxis(angle, axis) * (bones[j].b - bones[i - 1].b);
}
}
}
参考文献: