效果图
一个使用反向运动学表达式进行绑定的 “cut out” 人物的例子
设计
在深入研究 IK 代码之前,我们还需要讨论另一件事。在 IK 表达式的这个人物中,小腿作为脚的父级。在之前的版本中(来自前文),脚没有父级。这意味着现在,当小腿旋转时,脚也会随之旋转。这通常是可以的,但它可能会让你的角色的脚趾下沉到 “地板” 以下。由于一些神秘的原因,在脚上使用反旋转的表达式并没有起作用 —— 显然这与创造了一串自我循环的表达式链条有关。在任何情况下,如果你需要保持脚水平,可以通过对每只脚应用变换(Transform)效果来做到。然后你应该将这个简单的(IK)表达式应用到变换效果的锚点和位置属性上:
锚点
然后将另外一个表达式应用到变换效果的旋转属性:
v = toWorldVec([1,0,0]);
-radiansToDegrees(Math.atan2(v[1],v[0]))
下图展示了,平整(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);
/// @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); ///< 计算该关节的旋转
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; ///< 考虑到初始旋转方向,进行必要的调整
}