/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

Additional permissions under GNU GPL version 3 section 7

If you modify this Program, or any covered work, by linking or
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
(or a modified version of those libraries),
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
licensors of this Program grant you additional
permission to convey the resulting work. Corresponding Source for a
non-source form of such a combination shall include the source code for
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
work.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/

#include "HeadSpin.h"
#include <commdlg.h>
#include <math.h>

//#include "Max.h"
#include "MaxMain/plMaxNode.h"
#include "stdmat.h"
#include "bmmlib.h"
#include "istdplug.h"
#include "texutil.h"
#include "iparamb2.h"
#include "modstack.h"
#include "keyreduc.h"

#include "hsMaxLayerBase.h"
#include "plInterp/plController.h"
#include "plInterp/hsInterp.h"
#include "MaxExport/plErrorMsg.h"
#include "UserPropMgr.h"
#include "hsConverterUtils.h"
#include "hsControlConverter.h"
#include "hsMaterialConverter.h"
#include "hsExceptionStack.h"
#include "MaxExport/plErrorMsg.h"
#include "MaxComponent/plNoteTrackAnim.h"
#include "MaxComponent/plCameraComponents.h"
#include "MaxComponent/plAnimComponent.h"
#include "pnSceneObject/plSceneObject.h"
#include "pnSceneObject/plCoordinateInterface.h"

extern UserPropMgr gUserPropMgr;

hsControlConverter& hsControlConverter::Instance()
{
    static hsControlConverter the_instance;

    return the_instance;
}

hsControlConverter::hsControlConverter() : fConverterUtils(hsConverterUtils::Instance())
{
}

void hsControlConverter::Init(plErrorMsg* msg)
{
    hsGuardBegin("hsControlConverter::Init");

    fInterface = GetCOREInterface();
    fErrorMsg = msg;

    fTicksPerFrame  = ::GetTicksPerFrame(); /*160*/
    fFrameRate      = ::GetFrameRate();     /*30*/
    fTicksPerSec        = fTicksPerFrame*fFrameRate;

    Interval interval = fInterface->GetAnimRange();
    fStartFrame = interval.Start()/fTicksPerFrame;
    fEndFrame   = interval.End()/fTicksPerFrame;
    fNumFrames  = fEndFrame-fStartFrame+1;
    fAnimLength = (float)(fNumFrames-1)/fFrameRate;

    fWarned = false;

    fForceLocal = false;

    fSegStart = fSegEnd = -1;

    hsGuardEnd;
}

void hsControlConverter::DeInit()
{
}

// dummy class that ApplyKeyReduction needs
class KRStatus : public KeyReduceStatus
{
    void Init(int total) {}
    int Progress(int p) { return KEYREDUCE_CONTINUE; }
};

void hsControlConverter::ReduceKeys(Control *control, float threshold)
{
    if (control == nil || threshold <= 0)
        return;

    KRStatus status;
    if (control->IsLeaf())
    {
        if (control->IsKeyable())
        {
            IKeyControl *keyCont = GetKeyControlInterface(control);
            if (keyCont->GetNumKeys() > 2)
            {
                IKey *key1 = (IKey*)new uint8_t[keyCont->GetKeySize()];
                IKey *key2 = (IKey*)new uint8_t[keyCont->GetKeySize()];
                keyCont->GetKey(0, key1);
                keyCont->GetKey(keyCont->GetNumKeys() - 1, key2);

                // We want the interval to be one frame past the start and one frame 
                // before the end, to guarantee we leave the first and last keys 
                // alone. This will make sure a looping anim still lines up, and 
                // also prevents us from removing the controller entirely and thinking
                // this channel just isn't animated at all.
                //
                // Also, I think this is a Max bug (since we're using Max's key reduce 
                // function, and the same error happens without our plugins), but if 
                // your range is only one frame short of the end of the anim, some 
                // bones get flipped on that 2nd-to-last frame. So you get a single 
                // frame with something like your arm pointing in the opposite 
                // direction at the elbow.
                TimeValue start = key1->time + GetTicksPerFrame();
                TimeValue end = key2->time - 2 * GetTicksPerFrame();
                if (start < end)
                {
                    Interval interval(start, end);
                    ApplyKeyReduction(control, interval, threshold, GetTicksPerFrame(), &status);
                }
                delete [] (uint8_t*)key1;
                delete [] (uint8_t*)key2;
            }
        }
    }
    else
    {
        int i;
        for (i = 0; i < control->NumSubs(); i++)
            ReduceKeys((Control*)control->SubAnim(i), threshold);
    }
}

plController *hsControlConverter::ConvertTMAnim(plSceneObject *obj, plMaxNode *node, hsAffineParts *parts, 
                                                float start /* = -1 */, float end /* = -1 */)
{
    Control* maxTm = node->GetTMController();
    plController *tmc = hsControlConverter::Instance().MakeTransformController(maxTm, node, start, end);

    if (tmc)
    {
        const plCoordinateInterface *ci = obj->GetCoordinateInterface();
        if(ci)
        {
            const hsMatrix44& loc2Par = ci->GetLocalToParent();

            gemAffineParts ap;
            decomp_affine(loc2Par.fMap, &ap); 

            AP_SET((*parts), ap);
        }
    }

    return tmc;
}

hsBool hsControlConverter::HasKeyTimes(Control* ctl)
{
    hsGuardBegin("hsControlConverter::HasKeyTimes");

    if( !ctl )
    {
        return false;
    }

    return (ctl->NumKeys() > 1);
    hsGuardEnd;
}


plLeafController* hsControlConverter::MakeMatrix44Controller(StdUVGen* uvGen, const char* nodeName)
{
    hsGuardBegin("hsControlConverter::MakeMatrix44Controller");

    if( !uvGen )
        return nil;

    ISetSegRange(-1, -1);

    Tab<TimeValue> kTimes;
    kTimes.ZeroCount();
    Control* uScaleCtl = nil;
    Control* vScaleCtl = nil;
    Control* uOffCtl= nil;
    Control* vOffCtl = nil;
    Control* rotCtl = nil;
    GetControllerByName(uvGen, TSTR("U Offset"), uOffCtl);
    GetControllerByName(uvGen, TSTR("V Offset"), vOffCtl);
    GetControllerByName(uvGen, TSTR("U Tiling"), uScaleCtl);
    GetControllerByName(uvGen, TSTR("V Tiling"), vScaleCtl);
    GetControllerByName(uvGen, TSTR("Angle"), rotCtl);

    // new with Max R2, replacing "Angle", but it doesn't hurt to look...
    Control* uAngCtl = nil;
    Control* vAngCtl = nil;
    Control* wAngCtl = nil; 
    GetControllerByName(uvGen, TSTR("U Angle"), uAngCtl);
    GetControllerByName(uvGen, TSTR("V Angle"), vAngCtl);
    GetControllerByName(uvGen, TSTR("W Angle"), wAngCtl);

    int i;

    CompositeKeyTimes(uOffCtl, kTimes);
    CompositeKeyTimes(vOffCtl, kTimes);
    CompositeKeyTimes(uScaleCtl, kTimes);
    CompositeKeyTimes(vScaleCtl, kTimes);
    CompositeKeyTimes(rotCtl, kTimes);
    CompositeKeyTimes(uAngCtl, kTimes);
    CompositeKeyTimes(vAngCtl, kTimes);
    CompositeKeyTimes(wAngCtl, kTimes);

    const float kMaxRads = 30.f * M_PI / 180.f;
    MaxSampleAngles(nodeName, uAngCtl, kTimes, kMaxRads);
    MaxSampleAngles(nodeName, vAngCtl, kTimes, kMaxRads);
    MaxSampleAngles(nodeName, wAngCtl, kTimes, kMaxRads);

    if( kTimes.Count()<2 )
    {
        return nil;
    }

    plLeafController* ctrl = new plLeafController;
    ctrl->AllocKeys(kTimes.Count(), hsKeyFrame::kMatrix44KeyFrame);
    TimeValue resetTime = fConverterUtils.GetTime(fInterface);
    for( i=0; i < kTimes.Count(); i++)
    {
        Interval v;
        uvGen->Update(kTimes[i], v);

        // Get key
        float secs  = (float)kTimes[i]/fTicksPerSec;
        int frameNum= kTimes[i]/fTicksPerFrame;
        hsAssert(frameNum <= hsKeyFrame::kMaxFrameNumber, "Anim is too long.");

        fErrorMsg->Set((frameNum < fStartFrame || frameNum > fEndFrame), nodeName, 
            "Warning: Skipping keyframes outside of animation interval").CheckAndAsk();
        
        hsMatrix44Key *key = ctrl->GetMatrix44Key(i);
        StdUVGenToHsMatrix44(&key->fValue, uvGen, true);    
        key->fFrame = frameNum;
    }

    return ctrl;
    hsGuardEnd;
}

plLeafController* hsControlConverter::MakeMatrix44Controller(Control* prsControl)
{
    hsGuardBegin("hsControlConverter::MakeMatrix44Controller");

    ISetSegRange(-1, -1);

    Tab<TimeValue> kTimes;
    kTimes.ZeroCount();

    Control* posCtl = nil;
    Control* scaleCtl = nil;
    Control* rotCtl = nil;
    posCtl = prsControl->GetPositionController();
    rotCtl = prsControl->GetRotationController();
    scaleCtl = prsControl->GetScaleController();
    int i;

    CompositeKeyTimes(posCtl, kTimes);
    CompositeKeyTimes(scaleCtl, kTimes);
    CompositeKeyTimes(rotCtl, kTimes);

    if( kTimes.Count()<2 )
    {
        return nil;
    }

    plLeafController* ctrl = new plLeafController;
    ctrl->AllocKeys(kTimes.Count(), hsKeyFrame::kMatrix44KeyFrame);
    TimeValue resetTime = fConverterUtils.GetTime(fInterface);;
    for( i=0; i < kTimes.Count(); i++)
    {
        // Get key
        float secs  = (float)kTimes[i]/fTicksPerSec;
        int frameNum= kTimes[i]/fTicksPerFrame;
        hsAssert(frameNum <= hsKeyFrame::kMaxFrameNumber, "Anim is too long.");
    
        Matrix3 maxXform;
        maxXform.IdentityMatrix();
        Interval valid = FOREVER;
        prsControl->GetValue(fConverterUtils.GetTime(fInterface), &maxXform, valid, CTRL_RELATIVE);

        hsMatrix44Key *key = ctrl->GetMatrix44Key(i);
        Matrix3ToHsMatrix44(&maxXform, &key->fValue);
        key->fFrame = frameNum;
    }

    return ctrl;
    hsGuardEnd;
}


//
// Create a plScalarController and store the nodes parm behavior in it.
//
plLeafController* hsControlConverter::MakeScalarController(Control* control, plMaxNode* node, 
                                                           float start /* = -1 */, float end /* = -1 */)
{
    hsGuardBegin("hsControlConverter::MakeScalarController");

    if (control == NULL)
        return NULL;

    ISetSegRange(start, end);

    return ICreateScalarController(node, control);

    hsGuardEnd;
}

plController* hsControlConverter::MakeColorController(Control* control, plMaxNode* node, 
                                                      float start /* = -1 */, float end /* = -1 */)
{
    return MakePosController(control, node, start, end);
}
//
// Create a plPosController and store the nodes parm behavior in it.
//
plController* hsControlConverter::MakePosController(Control* control, plMaxNode* node, 
                                                    float start /* = -1 */, float end /* = -1 */)
{
    hsGuardBegin("hsControlConverter::MakePosController");

    if (control == NULL)
        return NULL;

    ISetSegRange(start, end);

    plController* hsCont;

    if (control->IsLeaf())
    {
        hsCont = ICreateSimplePosController(node, control);
    }
    else
    {
        hsCont = new plCompoundController;
        fErrorMsg->Set(control->NumSubs()!=3, node->GetName(), "compound should have 3 subs").Check();

        if (control->ClassID() == Class_ID(POSITIONNOISE_CONTROL_CLASS_ID,0) )
        {
            MessageBox(GetActiveWindow(), node->GetName(), 
            "Warning: Noise position controller not supported.  Ignoring.", MB_OK);
            return hsCont;
        }
        
        hsBool keep = false;
        for (int i=0; i<3; i++)
        {
            Control* sub = (Control*)control->SubAnim(i);
            plLeafController* sc = ICreateScalarController(node, sub);
            ((plCompoundController*)hsCont)->SetController(i, sc);
            if (sc)
            {
                keep = true;
            }
        }
        if (!keep)
        {
            delete hsCont;
            hsCont = nil;
        }
    }

    return hsCont;
    hsGuardEnd;
}

plController *hsControlConverter::MakeScaleController(Control *control, plMaxNode* node, 
                                                      float start /* = -1 */, float end /* = -1 */)
{
    ISetSegRange(start, end);

    if (control->IsLeaf())
    {
        // Simple scale: linear, bezier, tcb
        plLeafController* sc = ICreateSimpleScaleController(node, control);
        return sc;
    }
    else
    {
        // compound scale: noise
        if (control->ClassID() == Class_ID(SCALENOISE_CONTROL_CLASS_ID,0) )
        {
            MessageBox(GetActiveWindow(), node->GetName(), 
                       "Warning: Noise scale controller not supported.  Ignoring.", MB_OK);
        }
    }
    return NULL;
}

plController *hsControlConverter::MakeRotController(Control *control, plMaxNode *node, hsBool camRot /* = false */, 
                                                    float start /* = -1 */, float end /* = -1 */)
{
    ISetSegRange(start, end);

    if (control->IsLeaf())
    {
        // simple rot: linear, smooth, tcb
        plLeafController* rc = ICreateSimpleRotController(node, control, camRot);
        return rc;
    }
    else
    {
        // compound rot: euler or noise
        if (control->NumSubs())
        {
            if (control->ClassID() == Class_ID(ROTATIONNOISE_CONTROL_CLASS_ID,0) )
            {
                MessageBox(GetActiveWindow(), node->GetName(), 
                    "Warning: Noise rotation controller not supported.  Ignoring.", MB_OK);
                return nil;
            }
            if (fErrorMsg->Set(control->ClassID() != Class_ID(EULER_CONTROL_CLASS_ID,0), 
                node->GetName(), "Expecting euler rot ctrler").CheckAndAsk())
                return nil;

            if (fErrorMsg->Set(control->NumSubs() != 3, node->GetName(), "Rot compound controller should have 3 subcontrollers").CheckAndAsk())
                return nil;

            plCompoundController* rc = new plCompoundController;
            int i;
            for (i=0; i<3; i++)
            {
                Control* sub = (Control*)control->SubAnim(i);
                plLeafController* sc = ICreateScalarController(node, sub);
                rc->SetController(i, sc);
            }

            //
            // Check if we need to fixup euler due to missing subcontrollers
            //
            int numRotConts;
            for(numRotConts=0,i=0; i<3; i++)
                if (rc->GetController(i))
                    numRotConts++;
            if (numRotConts>0 && numRotConts<3)
            {
                // Someone has deleted 1 or 2 of the subcontrollers
                // Add a key at the start and end of the missing tracks
                Interval interval = fInterface->GetAnimRange();
                TimeValue startTime = interval.Start(); // in ticks
                TimeValue endTime = interval.End();     // in ticks

                hsStatusMessage("Fixing up euler controller due to missing subcontrollers\n");
                for(i=0; i<3; i++)
                {
                    if (!rc->GetController(i))
                    {
                        Control* sub = (Control*)control->SubAnim(i);
                        if (!sub)
                            continue;
                        sub->AddNewKey(startTime, ADDKEY_INTERP);
                        sub->AddNewKey(endTime, ADDKEY_INTERP);
                        plLeafController* sc = ICreateScalarController(node, sub);
                        if (sc)
                            rc->SetController(i, sc);
                        else
                        {
                            fErrorMsg->Set(true, "Scalar Controller Error", "nil plScalar controller").Show();
                            fErrorMsg->Set();
                            return rc;
                        }
                                        
                    }
                }
                for(numRotConts=0,i=0; i<3; i++)
                    if (rc->GetController(i))
                        numRotConts++;
        
                if(numRotConts != 3)
                {
                    fErrorMsg->Set(true, "Euler Fixup Error", "Euler fixup failed.").Show();
                    fErrorMsg->Set();
                    return rc;
                }
            }
            else if (numRotConts == 0) // No sub controllers, no point in having the compound controller then
            {
                delete rc;
                rc = nil;
            }

            return rc;
        }
    }
    return NULL;
}

void hsControlConverter::ScalePositionController(plController* ctl, float scale)
{
    plLeafController* simp = plLeafController::ConvertNoRef(ctl);
    plCompoundController* comp;
    int i;
    if( simp )
    {
        for( i = 0; i < simp->GetNumKeys(); i++ )
        {
            hsPoint3Key* key = simp->GetPoint3Key(i);
            if (key)
            {
                key->fValue *= scale;
            }

            hsBezPoint3Key* bezKey = simp->GetBezPoint3Key(i);
            if (bezKey)
            {
                bezKey->fInTan *= scale;
                bezKey->fOutTan *= scale;
                bezKey->fValue *= scale;
            }
        }
    }
    else if( comp = plCompoundController::ConvertNoRef(ctl) )
    {
        for( i = 0; i < 3; i++ )
        {
            ScalePositionController(comp->GetController(i), scale);
        }
    }
}

void hsControlConverter::MaxSampleAngles(const char* nodeName, Control* ctl, Tab<TimeValue>& kTimes, float maxRads)
{
    hsGuardBegin("hsControlConverter::MaxSampleAngles");

    if( !ctl )
    {
        return;
    }

    Tab<TimeValue> rTimes;
    Tab<TimeValue> sTimes;
    rTimes.ZeroCount();
    IGetControlSampleTimes(ctl, 0, ctl->NumKeys(), rTimes, maxRads);

    int iR;
    for( iR = 0; iR < rTimes.Count(); iR++ )
    {
        int iK;
        for( iK = 0; iK < kTimes.Count(); iK++ )
        {
            if( kTimes[iK] >= rTimes[iR] )
                break;
        }
        if( kTimes[iK] != rTimes[iR] )
            kTimes.Insert(iK, 1, rTimes.Addr(iR));
    }

    hsGuardEnd;
}

plCompoundController *hsControlConverter::MakeTransformController(Control *control, plMaxNode *node, 
                                                                  float start /* = -1 */, float end /* = -1 */)
{
    hsGuardBegin("hsControlConverter::MakeTransformController");

    if (!control)
        return NULL;

    ISetSegRange(start, end);

    Class_ID cid = control->ClassID();
    if (cid == Class_ID(PRS_CONTROL_CLASS_ID,0) ||
        cid == Class_ID(LOOKAT_CONTROL_CLASS_ID,0))
    {
        int n = control->NumSubs();
        if(n != 3)
        {
            fErrorMsg->Set(true, "Transform Controller Error", "Transform controller doesn't have 3 sub controllers").Show();
            fErrorMsg->Set();
            return NULL;
        }

        plCompoundController *tmc = new plCompoundController;
        for (int i=0; i<n; i++)
        {
            Control* sub = (Control*)control->SubAnim(i);
            if (sub)
            {
                IConvertSubTransform(sub, control->SubAnimName(i), node, tmc, start, end);
            }
        }

        if (cid == Class_ID(LOOKAT_CONTROL_CLASS_ID,0))
        {
            hsTArray<hsG3DSMaxKeyFrame> kfArray;
            IAddPartsKeys(control, &kfArray, node);
            hsBool ignoreFOV = false;
            for (int i = 0; i < node->NumAttachedComponents(); i++)
            {
                if (node->GetAttachedComponent(i)->ClassID() == ANIMCAM_CMD_CID)
                {
                    plCameraAnimCmdComponent* pAnimComp = (plCameraAnimCmdComponent*)node->GetAttachedComponent(i);
                    ignoreFOV = pAnimComp->IgnoreFOV();
                    break;
                }
            }
            if (!ignoreFOV)
                IExportAnimatedCameraFOV(node, &kfArray);   
        }

        if (tmc->GetPosController() || tmc->GetRotController() || tmc->GetScaleController())
            return tmc;
        else
        {
            delete tmc;
            return NULL;
        }
    }
    return NULL;

    hsGuardEnd;
}

void hsControlConverter::ISetSegRange(float start, float end)
{
    fSegStart = (start >= 0 ? fTicksPerSec * start : fInterface->GetAnimRange().Start());
    fSegEnd = (end >= 0 ? fTicksPerSec * end : fInterface->GetAnimRange().End());
}


void hsControlConverter::IConvertSubTransform(Control *control, char *ctlName, plMaxNode *node, plCompoundController *tmc,
                                              float start, float end)
{
    if (control)
    {
        ControllerType ct = IGetControlType(ctlName);

        switch(ct)
        {
        case ctrlTypePosition:
            {
                if(tmc->GetPosController() != nil)
                {
                    fErrorMsg->Set(true, "Position Controller Error", "Non-nil position controller").Show();
                    fErrorMsg->Set();
                    return;
                }
                tmc->SetPosController(MakePosController(control, node, start, end));
            }
            break;
        case ctrlTypeRollAngle:
        case ctrlTypeRotation:
            {
                if(tmc->GetRotController() != nil)
                {
                    fErrorMsg->Set(true, "Position Controller Error", "Non-nil Rotation controller").Show();
                    fErrorMsg->Set();
                    return;
                }
                hsBool camRot = (ct == ctrlTypeRollAngle);
                tmc->SetRotController(MakeRotController(control, node, camRot, start, end));
            }
            break;
        case ctrlTypeScale:
            {
                if(tmc->GetScaleController() != nil)
                {
                    fErrorMsg->Set(true, "Scale Controller Error", "Non-nil Scale Controller").Show();
                    fErrorMsg->Set();
                    return;
                }
                tmc->SetScaleController(MakeScaleController(control, node, start, end));                            
            }
            break;
        default:
            /*
            if (plExp.GetLogFile())
                fprintf(plExp.GetLogFile(),"%s unknown ctrl type=%d\n", node->GetName(), (int)ct);
               */
            break;
        }
    }
}

//
//
//
plLeafController* hsControlConverter::ICreateSimpleRotController(plMaxNode* node, Control* control, hsBool camRot)
{
    hsGuardBegin("hsControlConverter::ICreateSimpleRotController");

    return ICreateQuatController(node, control, true, camRot);

    hsGuardEnd;
}

plLeafController* hsControlConverter::ICreateSimpleScaleController(plMaxNode* node, Control* control)
{
    hsGuardBegin("hsControlConverter::ICreateSimpleScaleController");

    return ICreateScaleValueController(node, control);

    hsGuardEnd;
}


//
//
//
plLeafController* hsControlConverter::ICreateQuatController(plMaxNode* node, Control* control, bool rotation, hsBool camRot)
{
    hsGuardBegin("hsControlConverter::ICreateQuatController");

    int32_t startIdx, endIdx;
    IKeyControl* ikeys = GetKeyControlInterface(control);
    if ( ikeys && IGetRangeCoverKeyIndices(node ? node->GetName() : nil, control, startIdx, endIdx)>1 )
    {
        if(!(control->IsKeyable()))
        {
            fErrorMsg->Set(true, "Quat Controller Creation Error", "Control is not keyable.").Show();
            fErrorMsg->Set();
            return NULL;
        }

        IKey* key=(IKey*)(new uint8_t[ikeys->GetKeySize()]);
        plLeafController* pc = new plLeafController;

        uint8_t compressLevel = node->GetAnimCompress();
        uint8_t keyType;
        if (compressLevel == plAnimCompressComp::kCompressionHigh)
            keyType = hsKeyFrame::kCompressedQuatKeyFrame32;
        else if (compressLevel == plAnimCompressComp::kCompressionLow)
            keyType = hsKeyFrame::kCompressedQuatKeyFrame64;
        else
            keyType = hsKeyFrame::kQuatKeyFrame;

        pc->AllocKeys(endIdx - startIdx + 1, keyType);
        for(int i = startIdx; i <= endIdx; i++)
        {
            // Get key
            ikeys->GetKey(i, key);
            const float kMaxRads = M_PI*  0.5f;
            Tab<TimeValue> kTimes;
            kTimes.ZeroCount();
            if( rotation )
                IGetControlSampleTimes(control, i, i, kTimes, kMaxRads);
            else
                kTimes.Append(1, &key->time);

            int k;
            for( k = 0; k < kTimes.Count(); k++ )
            {
                if (keyType == hsKeyFrame::kQuatKeyFrame)
                {
                    hsQuatKey *hsKey = pc->GetQuatKey(i - startIdx);
                    ICreateHSInterpKey(control, key, kTimes[k], hsKey, node, camRot);
                }
                else if (keyType == hsKeyFrame::kCompressedQuatKeyFrame64)
                {
                    hsQuatKey tempKey;
                    ICreateHSInterpKey(control, key, kTimes[k], &tempKey, node, camRot);
                    hsCompressedQuatKey64 *compKey = pc->GetCompressedQuatKey64(i - startIdx);
                    compKey->fFrame = tempKey.fFrame;
                    compKey->SetQuat(tempKey.fValue);
                }
                else
                {
                    hsQuatKey tempKey;
                    ICreateHSInterpKey(control, key, kTimes[k], &tempKey, node, camRot);
                    hsCompressedQuatKey32 *compKey = pc->GetCompressedQuatKey32(i - startIdx);
                    compKey->fFrame = tempKey.fFrame;
                    compKey->SetQuat(tempKey.fValue);
                }
            }
        }
        delete [] key;

        return pc;
    }

    return nil;
    hsGuardEnd;
}


//
//
//
plLeafController* hsControlConverter::ICreateScaleValueController(plMaxNode* node, Control* control)
{
    hsGuardBegin("hsControlConverter::ICreateScaleValueController");

    //plMaxNode* xformParent = GetXformParent(node);
    int32_t startIdx, endIdx;
    IKeyControl* ikeys = GetKeyControlInterface(control);
    if ( ikeys && IGetRangeCoverKeyIndices(node ? node->GetName() : nil, control, startIdx, endIdx)>1 )
    {
        if(!(control->IsKeyable()))
        {
            fErrorMsg->Set(true, "Scale Value Controller Creation Error", "Control is not keyable").Show();
            fErrorMsg->Set();
            return NULL;
        }

        IKey* key=(IKey*)(new uint8_t [ikeys->GetKeySize()]);
        plLeafController* pc = new plLeafController;
        pc->AllocKeys(endIdx - startIdx + 1, GetKeyType(control));
        for(int i = startIdx; i <= endIdx; i++)
        {
            // Get key
            ikeys->GetKey(i, key);
            hsScaleKey *hsKey = pc->GetScaleKey(i - startIdx);
            if (hsKey)
                ICreateHSInterpKey(control, key, key->time, hsKey, node);

            hsBezScaleKey *bezKey = pc->GetBezScaleKey(i - startIdx);
            if (bezKey)
                ICreateHSInterpKey(control, key, key->time, bezKey, node);
        }
        delete [] key;
        return pc;
    }
    
    return nil;
    hsGuardEnd;
}

//
//
//
plLeafController* hsControlConverter::ICreateScalarController(plMaxNode* node, Control* control)
{
    hsGuardBegin("hsControlConverter::ICreateScalarController");

    int32_t startIdx, endIdx;
    IKeyControl* ikeys = GetKeyControlInterface(control);
    if ( ikeys && IGetRangeCoverKeyIndices(node ? node->GetName() : nil, control, startIdx, endIdx)>1 )
    {
        if(!(control->IsKeyable()))
        {
            fErrorMsg->Set(true, "Scale Value Controller Creation Error", "Control is not keyable").Show();
            fErrorMsg->Set();
            return NULL;
        }

        IKey* key=(IKey*)(new uint8_t [ikeys->GetKeySize()]);
        plLeafController* pc = new plLeafController;
        pc->AllocKeys(endIdx - startIdx + 1, GetKeyType(control));
        for(int i = startIdx; i <= endIdx; i++)
        {
            // Get key
            ikeys->GetKey(i, key);
            hsScalarKey *hsKey = pc->GetScalarKey(i - startIdx);
            if (hsKey)
                ICreateHSInterpKey(control, key, key->time, hsKey);

            hsBezScalarKey *bezKey = pc->GetBezScalarKey(i - startIdx);
            if (bezKey)
                ICreateHSInterpKey(control, key, key->time, bezKey);
        }

        delete [] key;
        return pc;
    }
    return nil;
    hsGuardEnd;
}


//
//
//
plLeafController* hsControlConverter::ICreateSimplePosController(plMaxNode* node, Control* control)
{
    hsGuardBegin("hsControlConverter::ICreateSimplePosController");

    IKeyControl* ikeys = GetKeyControlInterface(control);
    int32_t startIdx, endIdx;
    if ( ikeys && IGetRangeCoverKeyIndices(node ? node->GetName() : nil, control, startIdx, endIdx)>1 )
    {
        if(!(control->IsKeyable()))
        {
            fErrorMsg->Set(true, "Simple Position Controller Creation Error", "Control is not keyable").Show();
            fErrorMsg->Set();
            return NULL;
        }

        IKey* key=(IKey*)(new uint8_t [ikeys->GetKeySize()]);
        plLeafController* pc = new plLeafController;
        pc->AllocKeys(endIdx - startIdx + 1, GetKeyType(control));
        for(int i = startIdx; i <= endIdx; i++)
        {
            // Get key
            ikeys->GetKey(i, key);
            hsPoint3Key *hsKey = pc->GetPoint3Key(i - startIdx);
            if (hsKey)
                ICreateHSInterpKey(control, key, key->time, hsKey);

            hsBezPoint3Key *bezKey = pc->GetBezPoint3Key(i - startIdx);
            if (bezKey)
                ICreateHSInterpKey(control, key, key->time, bezKey);
        }
        delete [] key;
        return pc;
    }

    return nil;
    hsGuardEnd;
}

//
// Create a hsKey and store the nodes LTM in it.
// Recurses along all subcontrollers.
//
int hsControlConverter::IAddPartsKeys(Control* control, 
                  hsTArray <hsG3DSMaxKeyFrame>* kfArray, 
                  plMaxNode* node)
{
    hsGuardBegin("hsControlConverter::IAddPartsKeys");

    int32_t startIdx, endIdx;
    if (control->IsLeaf())
    {
        IKeyControl* ikeys = GetKeyControlInterface(control);
        int num = ikeys ? IGetRangeCoverKeyIndices(node ? node->GetName() : nil, control, startIdx, endIdx) : 0;
        
        if (num<2)
        {
            return 0;
        }

        if(!control->IsKeyable())
        {
            fErrorMsg->Set(true, "Add Parts Keys Creation Error", "Control is not keyable").Show();
            fErrorMsg->Set();
            return 0;
        }

        int i,j;

        //
        // Traverse all keys of controller
        //
        IKey* key=(IKey*)(new uint8_t [ikeys->GetKeySize()]);
        hsBool mb=false;
        plMaxNode* xformParent = GetXformParent(node);
        for(i = startIdx; i <= endIdx; i++)
        {
            // Get key
            ikeys->GetKey(i, key);
            float frameTime = key->time / GetTicksPerSec();
            int frameNum = key->time / GetTicksPerFrame();
            hsAssert(frameNum <= hsKeyFrame::kMaxFrameNumber, "Anim is too long.");

            // Check if we already have a hsG3dsMaxKey at this frameNum
            int found=FALSE;
            for(j=0; j<kfArray->GetCount(); j++)
            {
                hsG3DSMaxKeyFrame* k = &(*kfArray)[j];
                if (k->fFrame == frameNum)
                {
                    found = TRUE;
                    break;
                }
            }
            if (found==TRUE)
                // Skip this key, there's one already there
                continue;

            //
            // Compute AffineParts
            //
            hsMatrix44 tXform = node->GetLocalToParent44(key->time);

            gemAffineParts ap;
            decomp_affine(tXform.fMap, &ap); 
            hsAffineParts parts;
            AP_SET(parts, ap);

            // Init new keyframe
            hsG3DSMaxKeyFrame hKey;
            hKey.fParts = parts;
            hKey.fFrame = frameNum;

            // Add key to list
            kfArray->Append(hKey);
        }
        delete [] key;
    }
    else
    {
        int i;
        for (i = 0; i < control->NumSubs(); i++)
            IAddPartsKeys((Control *)control->SubAnim(i), kfArray, node);
    }

    return kfArray->GetCount();
    hsGuardEnd;
}

Matrix3 hsControlConverter::StdUVGenToMatrix3(StdUVGen* uvGen)
{
    Matrix3 retVal(true);
    if( uvGen )
        uvGen->GetUVTransform(retVal);

    retVal = Inverse(IFlipY()) * retVal * IFlipY();

    return retVal;
}

//
//
// returns 0 if identity, 1 otherwise
// takes into account the implicit transform of v -> 1-v in meshconvert:setuvs()
//      
bool hsControlConverter::StdUVGenToHsMatrix44(hsMatrix44* hsMat, StdUVGen* uvGen, bool preserveOffset)
{
    hsGuardBegin("hsControlConverter::StdUVGenToHsMatrix44");

    Matrix3 uvXform;
    uvGen->GetUVTransform(uvXform);

    uvXform = Inverse(IFlipY()) * uvXform * IFlipY();
    Matrix3ToHsMatrix44(&uvXform, hsMat);

    if( !preserveOffset )
    {
        int i;
        for( i = 0; i < 2; i++ )
        {
            if( fabsf(hsMat->fMap[i][3]) > 1.f )
                hsMat->fMap[i][3] -= float(int(hsMat->fMap[i][3]));
        }
    }

    return ( !hsMat->IsIdentity() );
    hsGuardEnd;
}

void hsControlConverter::IGetControlSampleTimes(Control* control, int iLo, int iHi, Tab<TimeValue>& kTimes, float maxRads)
{
    hsGuardBegin("hsControlConverter::IGetControlSampleTimes");

    kTimes.ZeroCount();

    if( !control )
    {
        return;
    }

    Class_ID cID = control->ClassID();
    SClass_ID sID = control->SuperClassID();

    if( iLo < 0 )
        iLo = 0;
    int num = control->NumKeys();
    iHi++;
    if( iHi > num )
        iHi = num;


    IKeyControl* ikeys = GetKeyControlInterface(control);
    IKey* key=(IKey*)(new uint8_t [ikeys->GetKeySize()]);
    IKey* lastKey=(IKey*)(new uint8_t [ikeys->GetKeySize()]);

    int i;
    for( i = iLo; i < iHi; i++ )
    {
        TimeValue t = control->GetKeyTime(i);

        if( !i )
        {
            kTimes.Append(1, &t);
            continue;
        }
        int nSamp = 1;
        float rads = 0;
        // following code will work, except that rotations are stored
        // relative to previous key, so we'd need to end off with something
        // like for i = 1; i < n; i++ )
        //      key[i] = key[i-1] * key[i]
        // or pass in the previous key and do it here.
        ///////////////////////////////////////
        ikeys->GetKey(i-1, lastKey);
        ikeys->GetKey(i, key);
        if( cID == Class_ID(TCBINTERP_ROTATION_CLASS_ID, 0) )  
        {
            ITCBRotKey* tcbRotKey = (ITCBRotKey*)key;
            rads = tcbRotKey->val.angle;
        }
        else 
        if( cID == Class_ID(LININTERP_ROTATION_CLASS_ID, 0) )  
        {
            ILinRotKey* linRotKey = (ILinRotKey*)key;

            Point3 axis;
            AngAxisFromQ(linRotKey->val, &rads, axis);
        }
        else 
        if( cID == Class_ID(HYBRIDINTERP_ROTATION_CLASS_ID, 0) )  
        {
            IBezQuatKey* bezRotKey = (IBezQuatKey*)key;

            Point3 axis;
            AngAxisFromQ(bezRotKey->val, &rads, axis);
        }
        else 
        if( cID == Class_ID(TCBINTERP_FLOAT_CLASS_ID, 0) )
        {
            ITCBFloatKey* fKey = (ITCBFloatKey*)key;

            rads = fKey->val;
            fKey = (ITCBFloatKey*)lastKey;
            rads -= fKey->val;
        }
        else
        if( cID == Class_ID(LININTERP_FLOAT_CLASS_ID, 0) )
        {
            ILinFloatKey* fKey = (ILinFloatKey*)key;
            rads = fKey->val;
            fKey = (ILinFloatKey*)lastKey;
            rads -= fKey->val;
        }
        else
        if( cID == Class_ID(HYBRIDINTERP_FLOAT_CLASS_ID, 0) )
        {
            IBezFloatKey* fKey = (IBezFloatKey*)key;
            rads = fKey->val;
            fKey = (IBezFloatKey*)lastKey;
            rads -= fKey->val;
        }

        nSamp = int(fabs(rads / maxRads) + 0.9f);
        if( nSamp < 2 )
        {
            kTimes.Append(1, &t);
            continue;
        }


        TimeValue t0 = control->GetKeyTime(i-1);
        int j;
        for( j = 0; j < nSamp; j++ )
        {
            float p = float(j+1) / float(nSamp);
            TimeValue ti = t0 + TimeValue(p*  (t - t0));
            kTimes.Append(1, &ti);
        }
        ///////////////////////////////////////
    }

    delete [] key;
    delete [] lastKey;
    
    hsGuardEnd;
}

#if 0
        // following code will work, except that TCB (but not Euler) rotations are stored
        // relative to previous key, so we'd need to end off with something
        // like for i = 1; i < n; i++ )
        //      key[i] = key[i-1]*  key[i]
        // or pass in the previous key and do it here.
        Quat quat;
        ///////////////////////////////////////
        if( cID == Class_ID(TCBINTERP_ROTATION_CLASS_ID, 0) )  
        {
            ITCBRotKey* tcbRotKey = (ITCBRotKey*)mKey;
            quat = QFromAngAxis(tcbRotKey->val.angle, tcbRotKey->val.axis);
        }
        else if( cID == Class_ID(HYBRIDINTERP_ROTATION_CLASS_ID, 0) ) 
        {
            IBezQuatKey* bezRotKey = (IBezQuatKey*)mKey;
            quat = bezRotKey->val;
        }
        else if( cID == Class_ID(LININTERP_ROTATION_CLASS_ID, 0) ) 
        {
            ILinRotKey* linRotKey = (ILinRotKey*)mKey;
            quat = linRotKey->val;
        }
        else if( cID == Class_ID(EULER_CONTROL_CLASS_ID, 0) )
        {
            float eul[3];

            int i;
            for( i = 0; i < 3; i++ )
            {
                Control* subCntl = (Control*)control->SubAnim(i);
                if( fErrorMsg->Set(!(subCntl && (subCntl->ClassID() == Class_ID(TCBINTERP_FLOAT_CLASS_ID, 0))), node->GetName(), "Bad sub-controller type for animation").CheckAndAsk() )
                {
                    eul[i] = 0.f;
                    continue;
                }

                ITCBFloatKey* fKey = (ITCBFloatKey*)mKey;
                eul[i] = fKey->val;
            }
            
            EulerToQuat(eul, quat);
        }
        hbKey->fValue.Set(-quat.x, -quat.y, -quat.z, quat.w);

        /////////////////////////////////////// 
#endif // try getting from key

//
// Create an hsKeyFrame from a 3DSMax key
//
int32_t hsControlConverter::ICreateHSInterpKey(Control* control, IKey* mKey, TimeValue keyTime, hsKeyFrame* baseKey, plMaxNode* node, hsBool rotQuat)
{
    hsGuardBegin("hsControlConverter::ICreateHSInterpKey");

    Class_ID cID = control->ClassID();
    SClass_ID sID = control->SuperClassID();
    char* nodeName = node ? node->GetName() : nil;

    // BEZ
    if (cID == Class_ID(HYBRIDINTERP_POSITION_CLASS_ID,0) ||
        cID == Class_ID(HYBRIDINTERP_COLOR_CLASS_ID,0) ||
        cID == Class_ID(HYBRIDINTERP_POINT3_CLASS_ID,0) )
    {
        IBezPoint3Key*bKey = (IBezPoint3Key*)mKey;              
        hsBezPoint3Key* hbKey = (hsBezPoint3Key*)baseKey;
        hbKey->fValue.Set(bKey->val.x, bKey->val.y, bKey->val.z);   // color should be 0 to 1
        hbKey->fInTan.Set(bKey->intan.x, bKey->intan.y, bKey->intan.z);
        hbKey->fOutTan.Set(bKey->outtan.x, bKey->outtan.y, bKey->outtan.z);
    }
    else if (cID == Class_ID(HYBRIDINTERP_SCALE_CLASS_ID,0))
    {
        IBezScaleKey*bKey = (IBezScaleKey*)mKey;
        hsBezScaleKey* hbKey = (hsBezScaleKey*)baseKey;
        hsMatrix44 tXform;
        IGetUnEasedLocalTM(node, control, &tXform, keyTime);
        gemAffineParts ap;
        decomp_affine(tXform.fMap, &ap); 

        hbKey->fValue.fS.Set(ap.k.x, ap.k.y, ap.k.z);
        hbKey->fValue.fQ.Set(ap.u.x, ap.u.y, ap.u.z, ap.u.w);
        hbKey->fInTan.Set(bKey->intan.x, bKey->intan.y, bKey->intan.z);
        hbKey->fOutTan.Set(bKey->outtan.x, bKey->outtan.y, bKey->outtan.z);
    }

    else if (cID == Class_ID(HYBRIDINTERP_FLOAT_CLASS_ID,0) && !rotQuat)
    {
        IBezFloatKey* bKey = (IBezFloatKey*)mKey;                   
        hsBezScalarKey* hbKey = (hsBezScalarKey*)baseKey;
        hbKey->fValue = bKey->val;
        hbKey->fInTan = bKey->intan;
        hbKey->fOutTan= bKey->outtan;
    }

    else
    // LIN
    if (cID == Class_ID(LININTERP_POSITION_CLASS_ID,0))
    {
        ILinPoint3Key*bKey = (ILinPoint3Key*)mKey;              
        hsPoint3Key* hbKey = (hsPoint3Key*)baseKey;
        hbKey->fValue.Set(bKey->val.x, bKey->val.y, bKey->val.z);   
    }
    else if (sID == SClass_ID(CTRL_ROTATION_CLASS_ID) || (cID == Class_ID(HYBRIDINTERP_FLOAT_CLASS_ID,0) && rotQuat))   // all rotations
    {
        hsQuatKey* hbKey = (hsQuatKey*)baseKey;

        // get rot values from Matrix and use quat slerp.
        // could try getting rot values from key 
        hsMatrix44 tXform;
        IGetUnEasedLocalTM(node, control, &tXform, keyTime);
        gemAffineParts ap;
        decomp_affine(tXform.fMap, &ap); 

        hbKey->fValue.Set(ap.q.x, ap.q.y, ap.q.z, ap.q.w);

        IEnableEaseCurves(control, true);   // re-enable
    }
    else if (cID == Class_ID(LININTERP_SCALE_CLASS_ID,0) )
    {
        ILinScaleKey*bKey = (ILinScaleKey*)mKey;
        hsScaleKey* hbKey = (hsScaleKey*)baseKey;
        hsMatrix44 tXform;
        IGetUnEasedLocalTM(node, control, &tXform, keyTime);
        gemAffineParts ap;
        decomp_affine(tXform.fMap, &ap); 

        hbKey->fValue.fS.Set(ap.k.x, ap.k.y, ap.k.z);
        hbKey->fValue.fQ.Set(ap.u.x, ap.u.y, ap.u.z, ap.u.w);   
    }
    else
    if (cID == Class_ID(LININTERP_FLOAT_CLASS_ID,0) )
    {
        ILinFloatKey* bKey = (ILinFloatKey*)mKey;                   
        hsScalarKey* hbKey = (hsScalarKey*)baseKey;
        hbKey->fValue = bKey->val;
    }
    else
    // TCB
    if (cID == Class_ID(TCBINTERP_POSITION_CLASS_ID,0) ||
        cID == Class_ID(TCBINTERP_POINT3_CLASS_ID, 0)       )
    {
        ITCBPoint3Key*bKey = (ITCBPoint3Key*)mKey;              
        hsPoint3Key* hbKey = (hsPoint3Key*)baseKey;
        hbKey->fValue.Set(bKey->val.x, bKey->val.y, bKey->val.z);
    }
    else
    if (cID == Class_ID(TCBINTERP_FLOAT_CLASS_ID,0) )
    {
        ITCBFloatKey* bKey = (ITCBFloatKey*)mKey;                   
        hsScalarKey* hbKey = (hsScalarKey*)baseKey;
        hbKey->fValue = bKey->val;
    }
    else if (cID == Class_ID(TCBINTERP_SCALE_CLASS_ID,0) )
    {
        ITCBScaleKey*bKey = (ITCBScaleKey*)mKey;
        hsScaleKey* hbKey = (hsScaleKey*)baseKey;
        hsMatrix44 tXform;
        IGetUnEasedLocalTM(node, control, &tXform, keyTime);
        gemAffineParts ap;
        decomp_affine(tXform.fMap, &ap); 

        hbKey->fValue.fS.Set(ap.k.x, ap.k.y, ap.k.z);
        hbKey->fValue.fQ.Set(ap.u.x, ap.u.y, ap.u.z, ap.u.w);   
    }
    else
    {
        fErrorMsg->Set(true, nodeName, "unknown controller type?").Check();
        return 0;   // failed
    }

    int frameNum = keyTime / GetTicksPerFrame();
    hsAssert(frameNum <= hsKeyFrame::kMaxFrameNumber, "Anim is too long.");
    baseKey->fFrame = frameNum;
    
    return 1;       // did it
    hsGuardEnd;
}

uint8_t hsControlConverter::GetKeyType(Control* control, hsBool rotQuat)
{
    Class_ID cID = control->ClassID();
    SClass_ID sID = control->SuperClassID();

    if (cID == Class_ID(HYBRIDINTERP_POSITION_CLASS_ID,0) ||
        cID == Class_ID(HYBRIDINTERP_COLOR_CLASS_ID,0) ||
        cID == Class_ID(HYBRIDINTERP_POINT3_CLASS_ID,0) )
    {
        return hsKeyFrame::kBezPoint3KeyFrame;
    }
    else if (cID == Class_ID(HYBRIDINTERP_SCALE_CLASS_ID,0))
    {
        return hsKeyFrame::kBezScaleKeyFrame;
    }
    else if (cID == Class_ID(HYBRIDINTERP_FLOAT_CLASS_ID,0) && !rotQuat)
    {
        return hsKeyFrame::kBezScalarKeyFrame;
    }
    else if (cID == Class_ID(LININTERP_POSITION_CLASS_ID,0))
    {
        return hsKeyFrame::kPoint3KeyFrame;
    }
    else if (sID == SClass_ID(CTRL_ROTATION_CLASS_ID) || (cID == Class_ID(HYBRIDINTERP_FLOAT_CLASS_ID,0) && rotQuat))   // all rotations
    {
        return hsKeyFrame::kQuatKeyFrame;
    }
    else if (cID == Class_ID(LININTERP_SCALE_CLASS_ID,0) )
    {
        return hsKeyFrame::kScaleKeyFrame;
    }
    else if (cID == Class_ID(LININTERP_FLOAT_CLASS_ID,0) )
    {
        return hsKeyFrame::kScalarKeyFrame;
    }
    else if (cID == Class_ID(TCBINTERP_POSITION_CLASS_ID,0) ||
             cID == Class_ID(TCBINTERP_POINT3_CLASS_ID, 0))
    {
        return hsKeyFrame::kPoint3KeyFrame;
    }
    else if (cID == Class_ID(TCBINTERP_FLOAT_CLASS_ID,0) )
    {
        return hsKeyFrame::kScalarKeyFrame;
    }
    else if (cID == Class_ID(TCBINTERP_SCALE_CLASS_ID,0) )
    {
        return hsKeyFrame::kScaleKeyFrame;
    }
    else
    {
        return hsKeyFrame::kUnknownKeyFrame;
    }
}

//
//
//
int32_t hsControlConverter::IGetRangeCoverKeyIndices(char* nodeName, Control* cont, int32_t &start, int32_t &end)
{
    hsGuardBegin("hsControlConverter::IGetRangeCoverKeyIndices");

    if (!cont)
    {
        return 0;
    }

    IKeyControl* keys = GetKeyControlInterface(cont);
    int numKeys=keys->GetNumKeys();
    if (numKeys == 0)
        return 0;

    IKey* key=(IKey*)(new uint8_t [keys->GetKeySize()]);

    start = numKeys;
    for (int i=0; i<numKeys; i++)
    {
        keys->GetKey(i, key);
        if (IIsKeyInRange(key))
        {
            if (start > i)
                start = i;
            end = i;
        }
    }

    // If the keys aren't on the exact endpoints of our range, we need to include the next or previous
    // one so that we have data for any time within our range.

    if (start == numKeys) // No keys inside the range
    {
        for (int i = 0; i < numKeys; i++)
        {
            keys->GetKey(i, key);
            if (key->time < fSegStart)
                start = i;
        }

        if ((start == numKeys) ||       // no keys before the start time
            (start == numKeys - 1))     // no keys after end (since the latest key is before start)
        {
            delete [] key;
            return 0;
        }

        end = start + 1;
    }
    else
    {
        keys->GetKey(start, key);
        if (key->time > fSegStart && start > 0)
            start -= 1;

        keys->GetKey(end, key);
        if (key->time < fSegEnd && end < numKeys - 1)
            end += 1;
    }

    delete [] key;

    //fErrorMsg->Set(numInRange>1 && numInRange!=numKeys, nodeName ? nodeName : "?", 
//          "Warning: Object has controller with keyframes outside of animation interval").CheckAndAsk();


    return end - start + 1;
    hsGuardEnd;
}

//
// find the closest ancestor (if any) that is animated.
// this node's space will be our local space.
//
plMaxNode* hsControlConverter::GetXformParent(plMaxNode* node)
{
    hsGuardBegin("hsControlConverter::GetXformParent");

    while( node && (node = (plMaxNode *)node->GetParentNode()) &&
        !(ForceOrigin(node) || ForceLocal(node) || IsAnimated(node)) );

    return node;
    hsGuardEnd;
}

// ###########################################################################
// Note that ForceWorldSpace Overrides ForceOrigin which Overrides ForceLocal
// ###########################################################################
hsBool hsControlConverter::ForceWorldSpace(plMaxNode* node)
{
    hsGuardBegin("hsControlConverter::ForceWorldSpace");

    return false;
    
    hsGuardEnd;
}

// ###########################################################################
// Note that ForceWorldSpace Overrides ForceOrigin which Overrides ForceLocal
// ###########################################################################
hsBool hsControlConverter::ForceOrigin(plMaxNode* node)
{
    hsGuardBegin("hsControlConverter::ForceOrigin");

    char* nn = node->GetName();

    if (node->IsRootNode())
    {
        return false;
    }

    if (ForceWorldSpace(node))
    {
        return false;
    }

    return false;
    hsGuardEnd;
}

// ###########################################################################
// Note that ForceWorldSpace Overrides ForceOrigin which Overrides ForceLocal
// This is significant because things that require ForceLocal because they are
// animated or what-not, are still okay with ForceOrigin, but not v.v.
// ###########################################################################
hsBool hsControlConverter::ForceLocal(plMaxNode* node)
{
    hsGuardBegin("hsControlConverter::ForceLocal");


    const char* nn = node->GetName();

    if( !node->CanConvert() )
        return false;

    if (node->IsRootNode())
    {
        return false;
    }

    if( node->GetForceLocal() )
        return true;

    if( ISkinNode((plMaxNode*)node->GetParentNode()) )
    {
        node->SetForceLocal(true);
        return true;
    }

    Object* objectRef = node->GetObjectRef();
    if (fConverterUtils.IsInstanced(objectRef) && 
        gUserPropMgr.UserPropExists(node,"AllowInstancing"))
    {
        node->SetForceLocal(true);
        return true;
    }


    return false;
    
    hsGuardEnd;
}


hsBool hsControlConverter::IsAnimated(plMaxNode* node)
{
    hsGuardBegin("hsControlConverter::IsAnimated");

    return node->IsAnimated();
    
    hsGuardEnd;
}

hsBool hsControlConverter::OwnsMaterialCopy(plMaxNode* node)
{
    hsGuardBegin("hsControlConverter::OwnsMaterialCopy");

    return false;
    hsGuardEnd;
}

hsBool hsControlConverter::HasFrameEvents(plMaxNode *node)
{
    hsGuardBegin("hsSceneConverter::HasFrameEvents");
    
    if (!node)
    {
        return false;
    }
    
    TSTR sdata;
    if (gUserPropMgr.GetUserPropString(node,"FESound",sdata) ||
        gUserPropMgr.GetUserPropString(node,"FESoundEmitter",sdata) ||
        gUserPropMgr.GetUserPropString(node,"FEGrab",sdata) ||
        gUserPropMgr.GetUserPropString(node,"FEDrop",sdata) ||
        gUserPropMgr.GetUserPropString(node,"FEEventOn",sdata) ||
        gUserPropMgr.GetUserPropString(node,"FEEventOnPermanent",sdata) ||
        gUserPropMgr.GetUserPropString(node,"FEEventOff",sdata) ||
        gUserPropMgr.GetUserPropString(node,"FEActor",sdata)) 
    {
        return false;
    }
    
    return false;
    hsGuardEnd; 
}

hsBool hsControlConverter::GetControllerByName(Animatable* anim, TSTR &name, Control* &ctl)
{
    hsGuardBegin("hsControlConverter::GetControllerByName");

    if( anim )
    {
        int nSub = anim->NumSubs();
        int i;
        for( i = 0; i < nSub; i++ )
        {
            if (anim->SubAnim(i)==nil)
                continue;
            TSTR subName = anim->SubAnimName(i);
            if( subName == name )
            {
                fErrorMsg->Set(!anim->SubAnim(i), name, "Found controller by name, but nobody home").Check();
                ctl = GetControlInterface(anim->SubAnim(i));
                return true;
            }
            else if( GetControllerByName(anim->SubAnim(i), name, ctl) )
            {
                return true;
            }
        }
    }
    ctl = nil;

    return false;
    hsGuardEnd;
}

Control *hsControlConverter::GetControllerByID(IParamBlock2 *pblock, int paramID)
{
    hsGuardBegin("hsControlConverter::GetControllerByID");

    if (pblock)
    {
        int animIdx = pblock->GetAnimNum(paramID);
        if (animIdx != -1)
        {
            Animatable* anim = pblock->SubAnim(animIdx);
            if (anim)
                return GetControlInterface(anim);
        }
    }

    return NULL;
    hsGuardEnd;
}

void hsControlConverter::CompositeKeyTimes(Control* ctl, Tab<TimeValue> &time)
{
    hsGuardBegin("hsControlConverter::CompositeKeyTimes");

    if( !ctl )
    {
        return;
    }

    int curTime = 0;
    int i;
    for( i = 0; i < ctl->NumKeys(); i++ )
    {
        TimeValue t = ctl->GetKeyTime(i);
        // advance times
        while( (curTime < time.Count())&&(t > time[curTime]) )
            curTime++;
        // if past end, append it
        if( curTime >= time.Count() )
            time.Append(1, &t);
        else // if less 
        if( t < time[curTime] )
            time.Insert(curTime++, 1, &t);
        // already there, skip
    }
    
    hsGuardEnd;
}

//
//
//
ControllerType hsControlConverter::IGetControlType(TSTR ctrlName)
{
    hsGuardBegin("hsControlConverter::IGetControlType");

    ControllerType ct = ctrlTypeUnknown;
    if (ctrlName && !strcmp(ctrlName, "Ease Curve"))
    {
        ct = ctrlTypeEase;
    }
    else if (ctrlName && !strcmp(ctrlName, "Mult Curve"))
    {
        ct = ctrlTypeMult;
    } 
    else if (ctrlName && !strcmp(ctrlName, "Position"))
    {
        ct = ctrlTypePosition;
    }
    else if (ctrlName && !strcmp(ctrlName, "Rotation"))
    {
        ct = ctrlTypeRotation;
    }
    else if (ctrlName && !strcmp(ctrlName, "Scale"))
    {
        ct = ctrlTypeScale;
    } 
    else if (ctrlName && !strcmp(ctrlName, "Transform"))
    {
        ct = ctrlTypeTransform;
    }
    else if (ctrlName && !strcmp(ctrlName, "Roll Angle"))
    {
        ct = ctrlTypeRollAngle;
    }
#if 0
    // biped controllers are good for nothing
    else if (ctrlName && !strcmp(ctrlName, "Vertical"))
    {
        ct = ctrlTypeVert;
    }
    else if (ctrlName && !strcmp(ctrlName, "Horizontal"))
    {
        ct = ctrlTypeHoriz;
    }
    else if (ctrlName && !strcmp(ctrlName, "Turning"))
    {
        ct = ctrlTypeTurn;
    }
#endif

    return ct;
    hsGuardEnd;
}

bool hsControlConverter::IIsKeyTimeInRange(TimeValue time)
{
    hsGuardBegin("hsControlConverter::IIsKeyTimeInRange");

    Interval interval = fInterface->GetAnimRange();
    TimeValue startTime = interval.Start(); // in ticks
    TimeValue endTime = interval.End();     // in ticks


    return (time >= startTime && time <= endTime) && // Max's range
           (time >= fSegStart && time <= fSegEnd);

    hsGuardEnd;
}

bool hsControlConverter::IIsKeyInRange(IKey* key)
{
    hsGuardBegin("hsControlConverter::IIsKeyInRange");

    return IIsKeyTimeInRange(key->time);
    hsGuardEnd;
}

void hsControlConverter::IGetUnEasedLocalTM(plMaxNode* node, Control* control, hsMatrix44* out, TimeValue time)
{
    hsGuardBegin("hsControlConverter::IGetUnEasedLocalTM");

    // disable easing so that GetTM won't give us an eased answer.
    // we want the uneased "key" value, so that we can do the easing ourselves
    IEnableEaseCurves(control, false);  

    // Make scale key match nodeTM
    fErrorMsg->Set(!node, "ICreateHSInterpKey", "nil node").Check();
    *out = node->GetLocalToParent44(time);

    IEnableEaseCurves(control, true);   // re-enable

    hsGuardEnd;
}

//
//
//
void hsControlConverter::IEnableEaseCurves(Animatable* control, bool enable)
{   
    hsGuardBegin("hsControlConverter::IEnableEaseCurves");

    if (control)
    {
        int n = control->NumSubs();
        for (int i=0; i<n; i++)
            IEnableEaseCurves(control->SubAnim(i), enable);

        EaseCurveList* el = GetEaseListInterface(control);
        if (el)
        {
            for(int i=0; i<el->NumEaseCurves(); i++)
            {
                if (enable)
                    el->EnableEaseCurve(i);
                else
                    el->DisableEaseCurve(i);
            }
        }
    }

    hsGuardEnd;
}

// We don't actually use this ID for a plugin, just to keep track of our AppData chunks
#define CONTROL_CONVERTER_CID Class_ID(0xae807d2, 0x523808c7)

Matrix3 hsControlConverter::IFlipY()
{
    hsGuardBegin("hsControlConverter::IFlipY");

    Matrix3 xfm = ScaleMatrix(Point3(1.0, -1.0, 1.0)) * TransMatrix(Point3(0.0, 1.0, 0.0));

    return xfm;
    hsGuardEnd;
}

bool hsControlConverter::ISkinNode(plMaxNode* node)
{
    hsGuardBegin("hsControlConverter::ISkinNode");

    /*
    if( fForceSkinning )
        return true;
    if( fForceNoSkinning )
        return false;
    */
    if (gUserPropMgr.UserPropExists(node,"MATSkin")) 
    {
        return true;
    }

    if (gUserPropMgr.UserPropExists(node,"MATSkinColor")) 
    {
        return true;
    }

    if( node && node->GetName() && strstr(node->GetName(), "%skin") )
    {
        return true;
    }

    return false;
    hsGuardEnd;
}

void hsControlConverter::Matrix3ToHsMatrix44(Matrix3* m3, hsMatrix44* hsM)
{
    hsGuardBegin("hsControlConverter::Matrix3ToHsMatrix44");

    MRow* m = m3->GetAddr();
    
    hsM->Reset();
    hsM->fMap[0][0] = m[0][0];
    hsM->fMap[0][1] = m[1][0];
    hsM->fMap[0][2] = m[2][0];
    hsM->fMap[0][3] = m[3][0];
    
    hsM->fMap[1][0] = m[0][1];
    hsM->fMap[1][1] = m[1][1];
    hsM->fMap[1][2] = m[2][1];
    hsM->fMap[1][3] = m[3][1];
    
    hsM->fMap[2][0] = m[0][2];
    hsM->fMap[2][1] = m[1][2];
    hsM->fMap[2][2] = m[2][2];
    hsM->fMap[2][3] = m[3][2];
    
    hsM->NotIdentity();

    hsGuardEnd;
}

//// IGetEditableMeshKeyTimes ////////////////////////////////////////////////
//  Moved here after hsMeshConverter was obliterated. Only used in this class
//  anyway...

hsBool  hsControlConverter::IGetEditableMeshKeyTimes( plMaxNode *node, Tab<TimeValue> &times )
{
    hsGuardBegin( "hsControlConverter::GetEditableMeshKeyTimes" );

    Animatable *anim;
    if( IGetSubAnimByName(node, TSTR("Object (Editable Mesh)"), anim) )
    {
        fErrorMsg->Set(!anim, node->GetName(), "First she says yes, then she says no.").Check();
        
        int i;
        int nSub = anim->NumSubs();
        for( i = 0; i < nSub; i++ )
        {
            if( anim->SubAnim(i) )
            {
                Control *ctl = GetControlInterface(anim->SubAnim(i));
                hsControlConverter::Instance().CompositeKeyTimes(ctl, times);
            }
        }
    }
    return times.Count() > 0;
    hsGuardEnd; 
}

//// IGetGeomKeyTimes ////////////////////////////////////////////////////////
//  Moved here after hsMeshConverter was obliterated. Only used in this class
//  anyway...

hsBool  hsControlConverter::IGetGeomKeyTimes( plMaxNode *node, Tab<TimeValue> &times )
{
    hsGuardBegin( "hsControlConverter::GetGeomKeyTimes" );

    char *dgbNodeName = node->GetName();
    Object *obj = node->GetObjectRef();
    if( !obj )
        return false;
    IDerivedObject *derObj = nil;
    if( obj->CanConvertToType(derivObjClassID) )
    {
        derObj = (IDerivedObject *)obj->ConvertToType(fConverterUtils.GetTime(fInterface), derivObjClassID);
    }
    else
    {
        SClass_ID objID = obj->SuperClassID();
        SClass_ID genID(GEN_DERIVOB_CLASS_ID);
        if( !(obj->SuperClassID() == SClass_ID(GEN_DERIVOB_CLASS_ID)) )
            return false;
        if( objID != genID )
            return false;
        derObj = (IDerivedObject *)obj;
    }

    int i;
    int nKeys = 0;
    for( i = 0; i < derObj->NumModifiers(); i++ )
    {
        Modifier *mod = derObj->GetModifier(i);
        char *dbgModName = mod->GetName();
        if( mod )
        {
            ChannelMask mask = mod->ChannelsChanged();
            if( mask & GEOM_CHANNEL )
            {
                IGetGeomKeyTimesRecur(mod, times);
            }
        }
    }

    return (times.Count() > 0);
    hsGuardEnd; 
}

//// IGetGeomKeyTimesRecur ///////////////////////////////////////////////////
//  Moved here after hsMeshConverter was obliterated. Only used in this class
//  anyway...

void    hsControlConverter::IGetGeomKeyTimesRecur( Animatable *anim, Tab<TimeValue> &times )
{
    hsGuardBegin( "hsControlConverter::IGetGeomKeyTimesRecur" );

    Control * ctl = GetControlInterface(anim);
    hsControlConverter::Instance().CompositeKeyTimes(ctl, times);

    int iSub;
    int nSub = anim->NumSubs();
    for( iSub = 0; iSub < nSub; iSub++ )
    {
        if( anim->SubAnim(iSub) )
            IGetGeomKeyTimesRecur(anim->SubAnim(iSub), times);
    }

    hsGuardEnd;
}

//// IGetGeomKeyTimesRecur ///////////////////////////////////////////////////
//  Moved here after hsMeshConverter was obliterated. Only used in this class
//  anyway...

hsBool  hsControlConverter::IGetSubAnimByName( Animatable *anim, TSTR &name, Animatable *&subAnim )
{
    hsGuardBegin( "hsControlConverter::IGetSubAnimByName" );

    if( anim )
    {
        int nSub = anim->NumSubs();
        int i;
        for( i = 0; i < nSub; i++ )
        {
            if (anim->SubAnim(i)==nil)
                continue;
            TSTR subName = anim->SubAnimName(i);
            if( subName == name )
            {
                fErrorMsg->Set(!anim->SubAnim(i), name, "Found controller by name, but nobody home").Check();
                subAnim = anim->SubAnim(i);
                return true;
            }
            else if( IGetSubAnimByName(anim->SubAnim(i), name, subAnim) )
            {
                return true;
            }
        }
    }
    subAnim = nil;
    return false;
    hsGuardEnd; 
}

// bad craziness, isolated here.
#include "plConvert.h"
#include "plgDispatch.h"
#include "MaxComponent/plAnimComponent.h"
#include "MaxComponent/plCameraComponents.h"
#include "pnMessage/plCameraMsg.h"
#include "plMessage/plAnimCmdMsg.h"
#include "pfCamera/plCameraModifier.h"
#include "pnSceneObject/plSceneObject.h"

void hsControlConverter::IExportAnimatedCameraFOV(plMaxNode* node, hsTArray <hsG3DSMaxKeyFrame>* kfArray)
{
    // grab the FOV settings at each keyframe here
    // create callback messages for the animation to send to the camera
    // to interpolate to the correct FOV at each keyframe
    
    plAnimComponentBase* pAnim = nil;
    int count = node->NumAttachedComponents();
    int i;
    for (i = 0; i < count; i++)
    {
        plComponentBase *comp = node->GetAttachedComponent(i);
        if (comp->ClassID() == ANIM_COMP_CID || comp->ClassID() == ANIM_GROUP_COMP_CID)
        {
            pAnim = (plAnimComponentBase*)comp;
            break;
        }
    }

    plCamera1Component* pCamComp = nil;
    for (i = 0; i < count; i++)
    {
        plComponentBase *comp = node->GetAttachedComponent(i);
        if (comp->ClassID() == FIXEDCAM_CID)
        {
            pCamComp = (plCamera1Component*)comp;
            break;
        }
    }

    const plCameraModifier1* pCamMod = nil;
    count = node->GetSceneObject()->GetNumModifiers();
    for (i=0; i < count; i++)
    {
        pCamMod = plCameraModifier1::ConvertNoRef(node->GetSceneObject()->GetModifier(i));
        if (pCamMod)
            break;
            
    }
    plCameraMsg* pCamMsg = new plCameraMsg;
    pCamMsg->SetCmd(plCameraMsg::kSetAnimated);
    pCamMsg->AddReceiver(pCamMod->GetKey());
    plConvert::Instance().AddMessageToQueue(pCamMsg);
    Object* obj = node->EvalWorldState(hsConverterUtils::Instance().GetTime(node->GetInterface())).obj;
    GenCamera* theCam;
    hsTArray<float> fovW;
    hsTArray<float> fovH;
    for (i=0; i < kfArray->Count(); i++)
    {
        TimeValue t = TimeValue(GetTicksPerFrame() * (kfArray[0][i].fFrame));
        theCam = (GenCamera *) obj->ConvertToType(t, Class_ID(LOOKAT_CAM_CLASS_ID, 0));
        float FOVvalue= 0.0;            //Currently in Radians
        // radians
        FOVvalue = theCam->GetFOV(t);
        // convert
        FOVvalue = FOVvalue*(180/3.141592);
        int FOVType = theCam->GetFOVType();
        float wDeg, hDeg;
        switch(FOVType)
        {
        case 0: // FOV_W
            {
                wDeg = FOVvalue;
                hDeg = (wDeg*3)/4;
            }
            break;
        case 1: // FOV_H
            {
                hDeg = FOVvalue;
                wDeg = (hDeg*4)/3;
            }
            break;
        }
        fovW.Append(wDeg);
        fovH.Append(hDeg);
            
    }
    for (i=0; i < kfArray->Count(); i++)
    {
        
        plCameraMsg* pFOVMsg = new plCameraMsg;
        plCameraConfig* pCfg = pFOVMsg->GetConfig();

        if (i == kfArray->Count() - 1)
        {
            pCfg->fFOVh = fovH[0];
            pCfg->fFOVw = fovW[0];
            pCfg->fAccel = kfArray[0][0].fFrame / MAX_FRAMES_PER_SEC;
        }
        else
        {
            pCfg->fFOVh = fovH[i + 1];
            pCfg->fFOVw = fovW[i + 1];
            pCfg->fAccel = kfArray[0][i + 1].fFrame / MAX_FRAMES_PER_SEC;
        }
        
        
        pFOVMsg->SetCmd(plCameraMsg::kAddFOVKeyframe);
        pFOVMsg->AddReceiver(pCamMod->GetKey());
        
        plEventCallbackMsg* pCall = new plEventCallbackMsg;
        pCall->fEvent = kTime;
        pCall->fEventTime = kfArray[0][i].fFrame / MAX_FRAMES_PER_SEC;
        pCall->fIndex = i;
        pCall->AddReceiver(pCamMod->GetKey());
        plAnimCmdMsg* pMsg = new plAnimCmdMsg;
        pMsg->AddReceiver(pCamMod->GetKey());
        pMsg->SetSender(pAnim->GetModKey(node));
        pMsg->SetCmd(plAnimCmdMsg::kAddCallbacks);
        pMsg->SetAnimName(_TEMP_CONVERT_FROM_LITERAL(ENTIRE_ANIMATION_NAME));
        pMsg->fTime = kfArray[0][i].fFrame / MAX_FRAMES_PER_SEC;
        pMsg->AddCallback(pCall);
        hsRefCnt_SafeUnRef(pCall);
        plConvert::Instance().AddMessageToQueue(pFOVMsg);
        plConvert::Instance().AddMessageToQueue(pMsg);
    }
}
