将动画关键轨迹导入 Maya

Importing animation keytracks into Maya

我有一个人类双足动画文件格式,我想使用 C++ 以编程方式读入 Maya API。

动画文件格式与Open Asset Importer's per-node animation structure相似。

对于每个关节,都有一系列多达 60 个 3D 矢量键(用于描述关节的平移)和 60 个四元数键(用于描述关节的旋转)。每个关节都保证有相同数量的键(或根本没有键)。

可以指定或更改动画的长度(以秒为单位的时间)(例如,对于 30 FPS 的动画,您可以将 60 个键设置为在 2 秒内发生)。

关节的平移和旋转每帧沿着骨架树传播,产生动画。

这是一个示例。日志工具添加了关于数据结构的附加说明。为了简洁起见,我截断了键。

Bone Bip01
    Parent null
    60 Position Keys
    0 0.000000 4.903561 99.240829 -0.000000
    1 0.033333 4.541568 99.346550 -2.809127
    2 0.066667 4.182590 99.490318 -5.616183
    ... (truncated)
    57 1.366667 5.049816 99.042770 -116.122604
    58 1.400000 4.902135 99.241692 -118.754120
    59 1.400000 4.902135 99.241692 -118.754120

    60 Rotation Keys
    0 0.000000 -0.045869 0.777062 0.063631 0.624470
    1 0.033333 -0.043855 0.775018 0.061495 0.627400
    2 0.066667 -0.038545 0.769311 0.055818 0.635212
    ... (truncated)
    57 1.366667 -0.048372 0.777612 0.065493 0.623402
    58 1.400000 -0.045869 0.777062 0.063631 0.624470
    59 1.400000 -0.045869 0.777062 0.063631 0.624470

Bone Bip01_Spine
    Parent Bip01
    60 Position Keys
    ...
    60 Rotation Keys
    ...

在C++中,我目前的数据结构是这样对应的:

std::unordered_map<string, std::vector<Vector3>> TranslationKeyTrack用于将一组平移向量映射到相应的骨骼。

std::unordered_map<string, std::vector<Quaternion>> RotationKeyTrack用于将一组旋转四元数映射到相应的骨骼。

附加说明:有些骨骼不会相对于其父骨骼移动;这些骨骼根本没有键(但有一个带有 0 个键的条目)。 还有一些骨骼只有旋转,或者只有位置键。 骨架数据存储在一个单独的文件中,我已经可以使用 MFnIkJoint 将其读入 Maya。

动画文件中指定的骨骼 1:1 到骨骼数据中的骨骼。

现在我想将这个动画数据导入到Maya中。但是,我不明白Maya's way of accepting animation data through its C++ API

特别是,MFnAnimCurve 函数集 addKeyFrameaddKey 只接受绑定到时间键的单个浮点值,而我有一个向量和四元数列表。 MFnAnimCurve 也接受 'tangents';看完文档后,我仍然不确定如何将我拥有的数据转换为这些切线。

我的问题是:如何将我拥有的数据转换为 Maya 可以理解的内容?

我通过示例理解得更好,所以一些示例代码会有所帮助。

经过几天的反复试验和检查互联网上的少量代码片段,我设法想出了一些可行的方法。

鉴于上述 TranslationKeyTrackRotationKeyTrack,

遍历骨架。对于每个关节,

  1. 设置骨架的初始位置和方向。这是必需的,因为有些关节不会相对于其父节点移动;如果不设置初始位置和方向,整个骨架可能会乱动。
  2. 设置动画曲线关键帧。

迭代看起来像这样:

MStatus status;
MItDag dagIter(MItDag::kDepthFirst, MFn::kJoint, &status);
    for (; !dagIter.isDone(); dagIter.next()) {
        MDagPath dagPath;
        status = dagIter.getPath(dagPath);
        MFnIkJoint joint(dagPath);
        string name_key = joint.name().asChar();

        // Set initial position, and the translation AnimCurve keys.
        if (TranslationKeyTrack.find(name_key) != TranslationKeyTrack.end()) {
            auto pos = TranslationKeyTrack[name_key][0];
            joint.setTranslation(MVector(pos.x, pos.y, pos.z), MSpace::kTransform);
            setPositionAnimKeys(dagPath.node(), positionTracks[name_key]);
        }

        // Set initial orientation, and the rotation AnimCurve keys.
        if (RotationKeyTrack.find(name_key) != RotationKeyTrack.end()) {
            auto rot = rotationTracks[name_key][0];
            joint.setOrientation(rot.x, rot.y, rot.z, rot.w);
            setRotationAnimKeys(dagPath.node(), RotationKeyTrack[name_key]);
        }
}

为简洁起见,我将省略显示 setPositionAnimKeys,仅显示 setRotationAnimKeys。但是,两者的想法是相同的。请注意,我使用 kAnimCurveTL 作为翻译轨道。

void MayaImporter::setRotationAnimKeys(MObject joint, const vector<Quaternion>& rotationTrack) {
    if (rotationTrack.size() < 2) return; // Check for empty tracks.

    MFnAnimCurve rotX, rotY, rotZ;
    setAnimCurve(joint, "rotateX", rotX, MFnAnimCurve::kAnimCurveTA);
    setAnimCurve(joint, "rotateY", rotY, MFnAnimCurve::kAnimCurveTA);
    setAnimCurve(joint, "rotateZ", rotZ, MFnAnimCurve::kAnimCurveTA);

    MFnIkJoint j(joint);
    string name = j.name().asChar();

    for (int i = 0; i < rotationTrack.size(); i++) {
        auto rot = rotationTrack[i];
        MQuaternion rotation(rot.x, rot.y, rot.z, rot.w);

        // Depending on your input, you may have to do additional processing
        // to get the correct Euler rotation here.
        auto euler = rotation.asEulerRotation();
        MTime time(FPS*i, MTime::kSeconds); // FPS is a number defined elsewhere.

        rotX.addKeyframe(time, euler.x);
        rotY.addKeyframe(time, euler.y);
        rotZ.addKeyframe(time, euler.z);
    }
}

最后,我用于 setAnimCurve 的代码位。它本质上是将 AnimCurve 附加到关节。 This bit of code is adapted from a mocap file importer here.万岁开源!

void MayaImporter::setAnimCurve(const MObject& joint, const MString attr, MFnAnimCurve& curve, MFnAnimCurve::AnimCurveType type) {
    MStatus status;
    MPlug plug = MFnDependencyNode(joint).findPlug(attr, false, &status);

    if (!plug.isKeyable())
        plug.setKeyable(true);

    if (plug.isLocked())
        plug.setLocked(false);

    if (!plug.isConnected()) {
        curve.create(joint, plug, type, nullptr, &status);
        if (status != MStatus::kSuccess)
            cout << "Creating anim curve at joint failed!" << endl;
    } else {
        MFnAnimCurve animCurve(plug, &status);
        if (status == MStatus::kNotImplemented)
            cout << "Joint " << animCurve.name() << " has more than one anim curve." << endl;
        else if (status != MStatus::kSuccess)
            cout << "No anim curves found at joint " << animCurve.name() << endl;
        curve.setObject(animCurve.object(&status));
    }
}