本文是基于前文 AEJoy —— 表达式之反向动力学(Inverse Kinematics)【四】【JS】 并结合我实际的使用进行补充的,所以建议先了解一下前文
- 前文的素材层级关系
- 我的素材层级关系
- IK 原理图
效果图
-
实际使用的效果(无 Leveling)
-
实际使用的效果(有 Leveling)
表达式代码详解
以下代码是用来计算逆运动学(IK)中关节旋转角度的表达式代码,既适用于上肢也适用于下肢的运动。
代码首先定义了一些布尔变量和层的名称(根据自己的素材进行修改),接着定义了辅助函数和计算过程。
cw = false; ///< true:顺时针 false: 逆时针
upper = true; ///< true: 上肢 false:下肢
/// @note 三个(上肢、下肢、肢端)层名
upperLimb = "l upper arm";
lowerLimb = "l lower arm";
extremity = "l hand";
/// @note 效应器的层名(引导层,解决肢端从下肢移得太远而脱离的问题)
effector = "l hand effector";
/// @note 返回世界坐标系下的层的位置
/// 这么做的好处:以防该层被作为其他层的子级
function getWorldPos(theLayerName) {
L = thisComp.layer(theLayerName);
return L.toWorld(L.anchorPoint);
}
/// @note 对应上图三角形各顶点的位置
A = getWorldPos(upperLimb);
B = getWorldPos(lowerLimb);
C = getWorldPos(extremity);
E = getWorldPos(effector); ///< 与 C 的位置相同
/// @note 上图三角形的边长
a = length(B, C);
b = length(E, A);
c = length(A, B);
x = (b * b + c * c - a * a) / (2 * b); ///< 利用余弦定理
alpha = Math.acos(clamp(x / c, -1, 1)); ///< 计算 alpha 角
if (upper) { ///< 应用于上肢部分
D = E - A;
delta = Math.atan2(D[1], D[0]);
result = radiansToDegrees(delta - (cw ? 1 : -1) * alpha); ///< 计算该关节的旋转 (1. 如果没有 IK 效果,则上肢会直接转到 D 的方向,即 delta 弧度; 2. 因为有 IK 效果,所以再转过 alpha 弧度)
V = B - A;
adj1 = radiansToDegrees(Math.atan2(V[1], V[0]));
result - adj1 + value; ///< 考虑到初始旋转方向,进行必要的调整
} else { ///< 应用于下肢部分
y = b - x;
gamma = Math.acos(clamp(y / a, -1, 1)); ///< 计算 gamma 角
result = (cw ? 1 : -1) * radiansToDegrees(gamma + alpha); ///< 计算该关节的旋转
V1 = B - A;
adj1 = radiansToDegrees(Math.atan2(V1[1], V1[0]));
V2 = C - B;
adj2 = radiansToDegrees(Math.atan2(V2[1], V2[0]));
result + adj1 - adj2 + value; ///< 考虑到初始旋转方向,进行必要的调整
}
以下是详细的解析:
变量定义
cw
: 一个布尔值,表示 旋转的方向偏好,true
表示顺时针,false
表示逆时针。upper
: 布尔值,指示 是处理上肢 (true
) 还是下肢(false
)。upperLimb
,lowerLimb
,extremity
,effector
: 分别定义了 上肢、下肢、肢端(例如手或脚)以及 效应器(目标位置)的层名称。
辅助函数
getWorldPos(theLayerName)
: 该函数接收一个层名称作为参数,然后找到该层并返回其在 ==世界坐标系== 下的锚点位置。这样做是因为图层可能作为其他图层的子级存在,直接获取世界坐标可以避免坐标变换带来的问题。
主体计算
-
获取顶点坐标:通过调用
getWorldPos
函数,获取上肢、下肢、肢端和效应器的锚点在 ==世界坐标系== 下的位置,分别存储在变量A
,B
,C
,E
中。 -
计算边长:利用
length
函数计算三角形 ABC(上肢、下肢、肢端组成)的三边长度,存储在变量a
,b
,c
中。 -
应用余弦定理计算角度:
- 计算变量
x
,它是根据 ==余弦定理== 用于计算角度 α 的一个中间变量。 - 使用
Math.acos
和clamp
函数计算角度 α,确保结果在有效范围内。
- 计算变量
-
分情况讨论:
-
上肢部分:
- 计算向量
D
,并利用Math.atan2
得到旋转角度的初始值delta
。 - 根据
cw
的值调整旋转方向后,减去初始角度调整量adj1
,再加上一个外部输入的value
来考虑初始旋转状态,最终得到旋转角度result
。
- 计算向量
-
下肢部分:
- 计算另一个中间变量
y
,并使用余弦定理计算角度 γ 。 - 同样根据
cw
调整旋转方向,但这次是将 γ 和 α 相加。 - 对于下肢,还需考虑两个调整量
adj1
和adj2
,以及外部输入的value
,来进行最终的旋转角度调整。
- 计算另一个中间变量
-
[!note]
在代码二中,加入
adj1
和adj2
是为了 ==更精确地调整最终的关节旋转角度,以考虑初始关节的初始旋转状态或特定的朝向==。具体来说:
adj1
是针对上肢(上肢部分或下肢)计算的调整,它基于从关节的起始位置到上臂(AB
)的向量(即V1
)与 x 轴的夹角。这有助于确定了关节的起始姿态,调整旋转以匹配动画的连贯性。- 而
adj2
是针对 下肢部分 的特别调整,计算,它是基于从下肢到下臂(BC
)的向量(即V2
)与 x 轴的夹角。这部分调整意在处理下肢的旋转时,确保了与上肢的运动逻辑一致,即从关节的起始姿态考虑,这样旋转后达到整体关节链的连续和自然。简言之,
adj1
和adj2
都是为了保证了关节旋转计算在逆运动学中的旋转角度时,考虑到了关节的初始旋转状态,这样确保了动画的流畅和关节链的连贯性,防止了突然的旋转突变或不自然的动作。
总结
这段代码通过分析肢体(上肢或下肢)的结构,利用逆运动学原理,计算出为了让肢体的末端(如手或脚)达到指定效应器位置所需的关节旋转角度。它综合运用了向量运算、三角函数以及 余弦定理 等数学工具,同时考虑了初始关节朝向和旋转方向偏好,以实现自然且准确的运动模拟。