1
votes

I have a human bipedal animation file format that I would like to programmatically read into Maya using the C++ API.

The animation file format is similar to that of the Open Asset Importer's per-node animation structure.

For every joint, there is a series of up to 60 3D vector keys (to describe the translation of the joint) and 60 quaternion keys (to describe the rotation of the joint). Every joint is guaranteed to have the same number of keys (or no keys at all).

The length (time in seconds) of the animation can be specified or changed (so that you can set the 60 keys to happen over 2 seconds for a 30 FPS animation, for example).

The translations and rotations of the joints propagates down the skeleton tree every frame, producing the animation.

Here's a sample. Additional remarks about the data structure are added by the logging facility. I have truncated the keys for brevity.

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
    ...

In C++, the data structure I currently have corresponds to this:

std::unordered_map<string, std::vector<Vector3>> TranslationKeyTrack is used to map a set of translation vectors to the corresponding bone.

std::unordered_map<string, std::vector<Quaternion>> RotationKeyTrack is used to map a set of rotation quaternions to the corresponding bone.

Additional notes: There are some bones that do not move relative to its parent bone; these bones have no keys at all (but has an entry with 0 keys). There are also some bones that have only rotation, or only position keys. The skeleton data is stored in a separate file that I can already read into Maya using MFnIkJoint.

The bones specified in the animation file is 1:1 to the bones in that skeleton data.

Now I would like to import this animation data into Maya. However, I do not understand Maya's way of accepting animation data through its C++ API.

In particular, the MFnAnimCurve function set addKeyFrame or addKey accepts only a single floating point value tied to a time key, while I have a list of vectors and quaternions. MFnAnimCurve also accepts 'tangents'; after reading the documentation, I am still unsure of how to convert the data I have into these tangents.

My question is: How do I convert the data I have into something Maya understands?

I understand better with examples, so some sample code will be helpful.

1

1 Answers

1
votes

So after a few days of trial-and-error and examining the few fragments of code around the Internet, I have managed to come up with something that works.

Given the abovementioned TranslationKeyTrack and RotationKeyTrack,

Iterate through the skeleton. For each joint,

  1. Set the initial positions and orientations of the skeleton. This is needed because there are some joints that do not move relative to its parent; if the initial positions and orientations are not set, the entire skeleton may move erratically.
  2. Set the AnimCurve keys.

The iteration looks like this:

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]);
        }
}

For brevity, I will omit showing setPositionAnimKeys, and show only setRotationAnimKeys only. However, the ideas for both are the same. Note that I used kAnimCurveTL for translation tracks.

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);
    }
}

Finally, the bit of code I used for setAnimCurve. It essentially attaches the AnimCurve to the joint. This bit of code is adapted from a mocap file importer here. Hooray open source!

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));
    }
}