animFileExport.cpp

//-
// ==========================================================================
// Copyright 1995,2006,2008 Autodesk, Inc. All rights reserved.
//
// Use of this software is subject to the terms of the Autodesk
// license agreement provided at the time of installation or download,
// or which otherwise accompanies this software in either electronic
// or hard copy form.
// ==========================================================================
//+

//
//  File Name: animFileExport.cpp
//
//  Description:
//      Utility classes to write .anim files.
//
#include <maya/MGlobal.h>

#include <maya/MIOStream.h>
#include <maya/MFStream.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include "animFileExport.h"
#include <maya/MString.h>
#include <maya/MFnAnimCurve.h>

//-------------------------------------------------------------------------
//  Class animUnitNames
//-------------------------------------------------------------------------

//  String names for units.
//
const char *kMmString =         "mm";
const char *kCmString =         "cm";
const char *kMString =          "m";
const char *kKmString =         "km";
const char *kInString =         "in";
const char *kFtString =         "ft";
const char *kYdString =         "yd";
const char *kMiString =         "mi";

const char *kMmLString =        "millimeter";
const char *kCmLString =        "centimeter";
const char *kMLString =         "meter";
const char *kKmLString =        "kilometer";
const char *kInLString =        "inch";
const char *kFtLString =        "foot";
const char *kYdLString =        "yard";
const char *kMiLString =        "mile";

const char *kRadString =        "rad";
const char *kDegString =        "deg";
const char *kMinString =        "min";
const char *kSecString =        "sec";

const char *kRadLString =       "radian";
const char *kDegLString =       "degree";
const char *kMinLString =       "minute";
const char *kSecLString =       "second";

const char *kHourTString =      "hour";
const char *kMinTString =       "min";
const char *kSecTString =       "sec";
const char *kMillisecTString =  "millisec";

const char *kGameTString =      "game";
const char *kFileTString =      "film";
const char *kPalTString =       "pal";
const char *kNtscTString =      "ntsc";
const char *kShowTString =      "show";
const char *kPalFTString =      "palf";
const char *kNtscFTString =     "ntscf";

const char *kUnitlessString = "unitless";
const char *kUnknownTimeString =    "Unknown Time Unit";
const char *kUnknownAngularString = "Unknown Angular Unit";
const char *kUnknownLinearString =  "Unknown Linear Unit";

animUnitNames::animUnitNames()
//
//  Description:
//      Class constructor.
//
{
}

animUnitNames::~animUnitNames()
//
//  Description:
//      Class destructor.
//
{
}

/* static */
void animUnitNames::setToLongName(const MAngle::Unit& unit, MString& name)
//
//  Description:
//      Sets the string with the long text name of the angle unit.
//
{
    switch(unit) {
        case MAngle::kDegrees:
            name.set(kDegLString);
            break;
        case MAngle::kRadians:
            name.set(kRadLString);
            break;
        case MAngle::kAngMinutes:
            name.set(kMinLString);
            break;
        case MAngle::kAngSeconds:
            name.set(kSecLString);
            break;
        default:
            name.set(kUnknownAngularString);
            break;
    }
}

/* static */
void animUnitNames::setToShortName(const MAngle::Unit& unit, MString& name)
//
//  Description:
//      Sets the string with the short text name of the angle unit.
//
{
    switch(unit) {
        case MAngle::kDegrees:
            name.set(kDegString);
            break;
        case MAngle::kRadians:
            name.set(kRadString);
            break;
        case MAngle::kAngMinutes:
            name.set(kMinString);
            break;
        case MAngle::kAngSeconds:
            name.set(kSecString);
            break;
        default:
            name.set(kUnknownAngularString);
            break;
    }
}

/* static */
void animUnitNames::setToLongName(const MDistance::Unit& unit, MString& name)
//
//  Description:
//      Sets the string with the long text name of the distance unit.
//
{
    switch(unit) {
        case MDistance::kInches:
            name.set(kInLString);
            break;
        case MDistance::kFeet:
            name.set(kFtLString);
            break;
        case MDistance::kYards:
            name.set(kYdLString);
            break;
        case MDistance::kMiles:
            name.set(kMiLString);
            break;
        case MDistance::kMillimeters:
            name.set(kMmLString);
            break;
        case MDistance::kCentimeters:
            name.set(kCmLString);
            break;
        case MDistance::kKilometers:
            name.set(kKmLString);
            break;
        case MDistance::kMeters:
            name.set(kMLString);
            break;
        default:
            name.set(kUnknownLinearString);
            break;
    }
}

/* static */
void animUnitNames::setToShortName(const MDistance::Unit& unit, MString& name)
//
//  Description:
//      Sets the string with the short text name of the distance unit.
//
{
    switch(unit) {
        case MDistance::kInches:
            name.set(kInString);
            break;
        case MDistance::kFeet:
            name.set(kFtString);
            break;
        case MDistance::kYards:
            name.set(kYdString);
            break;
        case MDistance::kMiles:
            name.set(kMiString);
            break;
        case MDistance::kMillimeters:
            name.set(kMmString);
            break;
        case MDistance::kCentimeters:
            name.set(kCmString);
            break;
        case MDistance::kKilometers:
            name.set(kKmString);
            break;
        case MDistance::kMeters:
            name.set(kMString);
            break;
        default:
            name.set(kUnknownLinearString);
            break;
    }
}

/* static */
void animUnitNames::setToLongName(const MTime::Unit &unit, MString &name)
//
//  Description:
//      Sets the string with the long text name of the time unit.
//
{
    switch(unit) {
        case MTime::kHours:
            name.set(kHourTString);
            break;
        case MTime::kMinutes:
            name.set(kMinTString);
            break;
        case MTime::kSeconds:
            name.set(kSecTString);
            break;
        case MTime::kMilliseconds:
            name.set(kMillisecTString);
            break;
        case MTime::kGames:
            name.set(kGameTString);
            break;
        case MTime::kFilm:
            name.set(kFileTString);
            break;
        case MTime::kPALFrame:
            name.set(kPalTString);
            break;
        case MTime::kNTSCFrame:
            name.set(kNtscTString);
            break;
        case MTime::kShowScan:
            name.set(kShowTString);
            break;
        case MTime::kPALField:
            name.set(kPalFTString);
            break;
        case MTime::kNTSCField:
            name.set(kNtscFTString);
            break;
        default:
            name.set(kUnknownTimeString);
            break;
    }
}

/* static */
void animUnitNames::setToShortName(const MTime::Unit &unit, MString &name)
//
//  Description:
//      Sets the string with the short text name of the time unit.
//
{
    setToLongName(unit, name);
}

//-------------------------------------------------------------------------
//  Class animBase
//-------------------------------------------------------------------------

// Tangent type words
//
const char *kWordTangentGlobal = "global";
const char *kWordTangentFixed = "fixed";
const char *kWordTangentLinear = "linear";
const char *kWordTangentFlat = "flat";
const char *kWordTangentSmooth = "spline";
const char *kWordTangentStep = "step";  // Maintains current value until next key
const char *kWordTangentStepNext = "stepnext";  // Immediately steps to next key's value
const char *kWordTangentSlow = "slow";
const char *kWordTangentFast = "fast";
const char *kWordTangentClamped = "clamped";
const char *kWordTangentPlateau = "plateau";
const char *kWordTangentAuto = "auto";

// Infinity type words
//
const char *kWordConstant = "constant";
const char *kWordLinear = "linear";
const char *kWordCycle = "cycle";
const char *kWordCycleRelative = "cycleRelative";
const char *kWordOscillate = "oscillate";

//  Param Curve types
//
const char *kWordTypeUnknown = "unknown";
const char *kWordTypeLinear = "linear";
const char *kWordTypeAngular = "angular";
const char *kWordTypeTime = "time";
const char *kWordTypeUnitless = "unitless";

//  Keywords
//
const char *kAnim = "anim";
const char *kAnimData = "animData";
const char *kMovData = "movData";
const char *kMayaVersion = "mayaVersion";
const char *kAnimVersion = "animVersion";

const char *kTimeUnit = "timeUnit";
const char *kLinearUnit = "linearUnit";
const char *kAngularUnit = "angularUnit";
const char *kStartTime = "startTime";
const char *kEndTime = "endTime";
const char *kStartUnitless = "startUnitless";
const char *kEndUnitless = "endUnitless";

// animVersions:
//
const char *kAnimVersionString = "1.1";

const char *kTwoSpace = "  ";

//  animData keywords
//
const char *kInputString = "input";
const char *kOutputString = "output";
const char *kWeightedString = "weighted";
const char *kPreInfinityString = "preInfinity";
const char *kPostInfinityString = "postInfinity";
const char *kInputUnitString = "inputUnit";
const char *kOutputUnitString = "outputUnit";
const char *kTanAngleUnitString = "tangentAngleUnit";
const char *kKeysString = "keys";

//  special characters
//
const char kSemiColonChar   = ';';
const char kSpaceChar       = ' ';
const char kBraceLeftChar   = '{';
const char kBraceRightChar  = '}';

animBase::animBase ()
//
//  Description:
//      The constructor.
//
{
    resetUnits();
}
    
animBase::~animBase()
//
//  Description:
//      The destructor.
//
{
}

void animBase::resetUnits()
//
//  Description:
//      Reset the units used by this class to the ui units.
//
{
    timeUnit = MTime::uiUnit();
    linearUnit = MDistance::uiUnit();
    angularUnit = MAngle::uiUnit();
}

const char *
animBase::tangentTypeAsWord(MFnAnimCurve::TangentType type)
//
//  Description:
//      Returns a string with a test based desription of the passed
//      MFnAnimCurve::TangentType. 
//
{
    switch (type) {
        case MFnAnimCurve::kTangentGlobal:
            return (kWordTangentGlobal);
        case MFnAnimCurve::kTangentFixed:
            return (kWordTangentFixed);
        case MFnAnimCurve::kTangentLinear:
            return (kWordTangentLinear);
        case MFnAnimCurve::kTangentFlat:
            return (kWordTangentFlat);
        case MFnAnimCurve::kTangentSmooth:
            return (kWordTangentSmooth);
        case MFnAnimCurve::kTangentStep:
            return (kWordTangentStep);
        case MFnAnimCurve::kTangentSlow:
            return (kWordTangentSlow);
        case MFnAnimCurve::kTangentFast:
            return (kWordTangentFast);
        case MFnAnimCurve::kTangentClamped:
            return (kWordTangentClamped);
        case MFnAnimCurve::kTangentPlateau:
            return (kWordTangentPlateau);
        case MFnAnimCurve::kTangentStepNext:
            return (kWordTangentStepNext);
        case MFnAnimCurve::kTangentAuto:
            return (kWordTangentAuto);
        default:
            break;
    }
    return (kWordTangentGlobal);
}

const char *
animBase::infinityTypeAsWord(MFnAnimCurve::InfinityType type)
//  
//  Description:
//      Returns a string containing the name of the passed 
//      MFnAnimCurve::InfinityType type.
//
{
    switch (type) {
        case MFnAnimCurve::kConstant:
            return (kWordConstant);
        case MFnAnimCurve::kLinear:
            return (kWordLinear);
        case MFnAnimCurve::kCycle:
            return (kWordCycle);
        case MFnAnimCurve::kCycleRelative:
            return (kWordCycleRelative);
        case MFnAnimCurve::kOscillate:
            return (kWordOscillate);
        default:
            break;
    }
    return (kWordConstant);
}

const char *
animBase::outputTypeAsWord (MFnAnimCurve::AnimCurveType type)
//
//  Description:
//      Returns a string identifying the output type of the
//      passed MFnAnimCurve::AnimCurveType.
//
{
    switch (type) {
        case MFnAnimCurve::kAnimCurveTL:
        case MFnAnimCurve::kAnimCurveUL:
            return (kWordTypeLinear);
        case MFnAnimCurve::kAnimCurveTA:
        case MFnAnimCurve::kAnimCurveUA:
            return (kWordTypeAngular);
        case MFnAnimCurve::kAnimCurveTT:
        case MFnAnimCurve::kAnimCurveUT:
            return (kWordTypeTime);
        case MFnAnimCurve::kAnimCurveTU:
        case MFnAnimCurve::kAnimCurveUU:
            return (kWordTypeUnitless);
        case MFnAnimCurve::kAnimCurveUnknown:
            return (kWordTypeUnitless);
    }
    return (kWordTypeUnknown);
}

const char *
animBase::boolInputTypeAsWord(bool isUnitless) 
//
//  Description:
//      Returns a string based on a bool. 
//
{
    if (isUnitless) {
        return (kWordTypeUnitless);
    } else {
        return (kWordTypeTime);
    }
}

//-------------------------------------------------------------------------
//  Class animWriter
//-------------------------------------------------------------------------

animWriter::animWriter()
//
//  Description:
//      Class constructor.
//
{
}

animWriter::~animWriter()
//
//  Description:
//      Class destructor.
//
{
}

bool animWriter::writeHeader(ofstream& clip,
                            double startTime, double endTime,
                            double startUnitless, double endUnitless
)
//
//  Description:
//      Writes the header for the file. The header contains the clipboard
//      specific data. 
//
{
    if (!clip) {
        return false;
    }

    clip << kAnimVersion << kSpaceChar << kAnimVersionString << kSemiColonChar << endl;
    clip << kMayaVersion << kSpaceChar << MGlobal::mayaVersion() << kSemiColonChar << endl;

    if (startTime != endTime) {
        MString unit;
        animUnitNames::setToShortName(timeUnit, unit);
        clip << kTimeUnit << kSpaceChar << unit << kSemiColonChar << endl;
        animUnitNames::setToShortName(linearUnit, unit);
        clip << kLinearUnit << kSpaceChar << unit << kSemiColonChar << endl;
        animUnitNames::setToShortName(angularUnit, unit);
        clip << kAngularUnit << kSpaceChar << unit << kSemiColonChar << endl;
        clip << kStartTime << kSpaceChar << startTime << kSemiColonChar << endl;
        clip << kEndTime << kSpaceChar << endTime << kSemiColonChar << endl;
    }

    if (startUnitless != endUnitless) {
        clip << kStartUnitless << kSpaceChar << startUnitless << 
                kSemiColonChar << endl;
        clip << kEndUnitless << kSpaceChar << endUnitless << 
                kSemiColonChar << endl;
    }

    return true;
}

bool animWriter::writeAnimCurve(ofstream &clip, 
                                const MObject *animCurveObj,
                                bool verboseUnits /* false */)
//
//  Description:
//      Write out the anim curve from the clipboard item into the
//      ofstream. The actual anim curve data is written out.
//
//      This method returns true if the write was successful.
//
{
    if (NULL == animCurveObj || animCurveObj->isNull() || !clip) {
        return true;
    }

    MStatus status = MS::kSuccess;
    MFnAnimCurve animCurve(*animCurveObj, &status);
    if (MS::kSuccess != status) {
        cerr << "Error: Could not read the anim curve for export." << endl;
        return false;
    }

    clip << kAnimData << kSpaceChar << kBraceLeftChar << endl;

    clip << kTwoSpace << kInputString << kSpaceChar <<
            boolInputTypeAsWord(animCurve.isUnitlessInput()) << 
            kSemiColonChar << endl;

    clip << kTwoSpace << kOutputString << kSpaceChar <<
            outputTypeAsWord(animCurve.animCurveType()) << kSemiColonChar << endl;

    clip << kTwoSpace << kWeightedString << kSpaceChar <<
            (animCurve.isWeighted() ? 1 : 0) << kSemiColonChar << endl;

    //  These units default to the units in the header of the file.
    //  
    if (verboseUnits) {
        clip << kTwoSpace << kInputUnitString << kSpaceChar;
        if (animCurve.isTimeInput()) {
            MString unitName;
            animUnitNames::setToShortName(timeUnit, unitName);
            clip << unitName;
        } else {
            //  The anim curve has unitless input.
            //
            clip << kUnitlessString;
        }
        clip << kSemiColonChar << endl;

        clip << kTwoSpace << kOutputUnitString << kSpaceChar;
    }

    double conversion = 1.0;
    MString unitName;
    switch (animCurve.animCurveType()) {
        case MFnAnimCurve::kAnimCurveTA:
        case MFnAnimCurve::kAnimCurveUA:
            animUnitNames::setToShortName(angularUnit, unitName);
            if (verboseUnits) clip << unitName;
            {
                MAngle angle(1.0);
                conversion = angle.as(angularUnit);
            }
            break;
        case MFnAnimCurve::kAnimCurveTL:
        case MFnAnimCurve::kAnimCurveUL:
            animUnitNames::setToShortName(linearUnit, unitName);
            if (verboseUnits) clip << unitName;
            {
                MDistance distance(1.0);
                conversion = distance.as(linearUnit);
            }
            break;
        case MFnAnimCurve::kAnimCurveTT:
        case MFnAnimCurve::kAnimCurveUT:
            animUnitNames::setToShortName(timeUnit, unitName);
            if (verboseUnits) clip << unitName;
            break;
        default:
            if (verboseUnits) clip << kUnitlessString;
            break;
    }
    if (verboseUnits) clip << kSemiColonChar << endl;

    if (verboseUnits) {
        MString angleUnitName;
        animUnitNames::setToShortName(angularUnit, angleUnitName);
        clip << kTwoSpace << kTanAngleUnitString << 
                kSpaceChar << angleUnitName << kSemiColonChar << endl;
    }

    clip << kTwoSpace << kPreInfinityString << kSpaceChar <<
            infinityTypeAsWord(animCurve.preInfinityType()) << 
            kSemiColonChar << endl;

    clip << kTwoSpace << kPostInfinityString << kSpaceChar <<
            infinityTypeAsWord(animCurve.postInfinityType()) << 
            kSemiColonChar << endl;

    clip << kTwoSpace << kKeysString << kSpaceChar << kBraceLeftChar << endl;

    // And then write out each keyframe
    //
    unsigned numKeys = animCurve.numKeyframes();
    for (unsigned i = 0; i < numKeys; i++) {
        clip << kTwoSpace << kTwoSpace;
        if (animCurve.isUnitlessInput()) {
            clip << animCurve.unitlessInput(i);
        }
        else {
            clip << animCurve.time(i).value();
        }

        clip << kSpaceChar << (conversion*animCurve.value(i));

        clip << kSpaceChar << tangentTypeAsWord(animCurve.inTangentType(i));
        clip << kSpaceChar << tangentTypeAsWord(animCurve.outTangentType(i));

        clip << kSpaceChar << (animCurve.tangentsLocked(i) ? 1 : 0);
        clip << kSpaceChar << (animCurve.weightsLocked(i) ? 1 : 0);
        clip << kSpaceChar << (animCurve.isBreakdown(i) ? 1 : 0);

        if (animCurve.inTangentType(i) == MFnAnimCurve::kTangentFixed) {
            MAngle angle;
            double weight;
            animCurve.getTangent(i, angle, weight, true);

            clip << kSpaceChar << angle.as(angularUnit);
            clip << kSpaceChar << weight;
        }
        if (animCurve.outTangentType(i) == MFnAnimCurve::kTangentFixed) {
            MAngle angle;
            double weight;
            animCurve.getTangent(i, angle, weight, false);

            clip << kSpaceChar << angle.as(angularUnit);
            clip << kSpaceChar << weight;
        }

        clip << kSemiColonChar << endl;
    }
    clip << kTwoSpace << kBraceRightChar << endl;

    clip << kBraceRightChar << endl;

    return true;
}