AEJoy —— 表达式之反向动力学(Inverse Kinematics)【四】【JS】

864 阅读2分钟

效果图

一个使用反向运动学表达式进行绑定的 “cut out” 人物的例子

099.gif

设计

在深入研究 IK 代码之前,我们还需要讨论另一件事。在 IK 表达式的这个人物中,小腿作为脚的父级。在之前的版本中(来自前文),脚没有父级。这意味着现在,当小腿旋转时,脚也会随之旋转。这通常是可以的,但它可能会让你的角色的脚趾下沉到 “地板” 以下。由于一些神秘的原因,在脚上使用反旋转的表达式并没有起作用 —— 显然这与创造了一串自我循环的表达式链条有关。在任何情况下,如果你需要保持脚水平,可以通过对每只脚应用变换(Transform)效果来做到。然后你应该将这个简单的(IK)表达式应用到变换效果的锚点和位置属性上:

锚点

然后将另外一个表达式应用到变换效果的旋转属性:

v = toWorldVec([1,0,0]);
-radiansToDegrees(Math.atan2(v[1],v[0])) 

下图展示了,平整(leveling)表达式在脚上应用前后的对比

image.png

现在我们准备看一看角色左臂上部的主 IK 代码。除了在前六行代码中定义的变量外,其他部分的代码是相同的。

表达式

绑定点示意图 image.png

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; ///< 考虑到初始旋转方向,进行必要的调整
}