/*==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 <functional>
#include <cmath>
#include <memory>

#include "HeadSpin.h"
#include "hsExceptionStack.h"
#include "hsWindows.h"

#include "MaxMain/MaxAPI.h"

#include "MaxMain/plMaxNode.h"

#include "hsMaxLayerBase.h"
#include "plInterp/plAnimEaseTypes.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 "MaxExport/plErrorMsg.h"
#include "MaxComponent/plNoteTrackAnim.h"
#include "MaxComponent/plCameraComponents.h"
#include "MaxComponent/plAnimComponent.h"
#include "pnSceneObject/plSceneObject.h"
#include "pnSceneObject/plCoordinateInterface.h"

typedef std::unique_ptr<IKey, std::function<void(IKey*)>> ikey_ptr;

/** Allocates a managed buffer to store derivatives of IKey */
static ikey_ptr IAllocKey(size_t size)
{
    return ikey_ptr(
        reinterpret_cast<IKey*>(new uint8_t[size]),
        [](IKey* ptr) { delete[] reinterpret_cast<uint8_t*>(ptr); });
}

//////////////////////////////////////////////////////////////////////////

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) override { }

    KeyReduceResult Progress(int p) override { return KEYREDUCE_CONTINUE; }
};

void hsControlConverter::ReduceKeys(Control *control, float threshold)
{
    if (control == nullptr || 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;
}

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

    if( !ctl )
    {
        return false;
    }

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


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

    if( !uvGen )
        return nullptr;

    ISetSegRange(-1, -1);

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

    // new with Max R2, replacing "Angle", but it doesn't hurt to look...
    Control* uAngCtl = nullptr;
    Control* vAngCtl = nullptr;
    Control* wAngCtl = nullptr;
    GetControllerByName(uvGen, _M("U Angle"), uAngCtl);
    GetControllerByName(uvGen, _M("V Angle"), vAngCtl);
    GetControllerByName(uvGen, _M("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);

    constexpr float kMaxRads = hsDegreesToRadians(30.f);
    MaxSampleAngles(nodeName, uAngCtl, kTimes, kMaxRads);
    MaxSampleAngles(nodeName, vAngCtl, kTimes, kMaxRads);
    MaxSampleAngles(nodeName, wAngCtl, kTimes, kMaxRads);

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

    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 = nullptr;
    Control* scaleCtl = nullptr;
    Control* rotCtl = nullptr;
    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 nullptr;
    }

    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 == nullptr)
        return nullptr;

    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 == nullptr)
        return nullptr;

    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) )
        {
            plMaxMessageBox(GetActiveWindow(), node->GetName(), 
                _T("Warning: Noise position controller not supported.  Ignoring."), MB_OK);
            return hsCont;
        }
        
        bool 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 = nullptr;
        }
    }

    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) )
        {
            plMaxMessageBox(GetActiveWindow(), node->GetName(), 
                            _T("Warning: Noise scale controller not supported.  Ignoring."), MB_OK);
        }
    }
    return nullptr;
}

plController *hsControlConverter::MakeRotController(Control *control, plMaxNode *node, bool 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) )
            {
                plMaxMessageBox(GetActiveWindow(), node->GetName(), 
                                _T("Warning: Noise rotation controller not supported.  Ignoring."), MB_OK);
                return nullptr;
            }
            if (fErrorMsg->Set(control->ClassID() != Class_ID(EULER_CONTROL_CLASS_ID,0), 
                node->GetName(), "Expecting euler rot ctrler").CheckAndAsk())
                return nullptr;

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

            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 = nullptr;
            }

            return rc;
        }
    }
    return nullptr;
}

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 MCHAR* 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 nullptr;

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

        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))
        {
            std::vector<hsG3DSMaxKeyFrame> kfArray;
            IAddPartsKeys(control, &kfArray, node);
            bool 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 nullptr;
        }
    }
    return nullptr;

    hsGuardEnd;
}

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


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

        switch(ct)
        {
        case ctrlTypePosition:
            {
                if (tmc->GetPosController() != nullptr)
                {
                    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() != nullptr)
                {
                    fErrorMsg->Set(true, "Position Controller Error", "Non-nil Rotation controller").Show();
                    fErrorMsg->Set();
                    return;
                }
                bool camRot = (ct == ctrlTypeRollAngle);
                tmc->SetRotController(MakeRotController(control, node, camRot, start, end));
            }
            break;
        case ctrlTypeScale:
            {
                if (tmc->GetScaleController() != nullptr)
                {
                    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, bool 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, bool camRot)
{
    hsGuardBegin("hsControlConverter::ICreateQuatController");

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

        ikey_ptr key = IAllocKey(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.get());
            constexpr float kMaxRads = hsConstants::half_pi<float>;
            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.get(), kTimes[k], hsKey, node, camRot);
                }
                else if (keyType == hsKeyFrame::kCompressedQuatKeyFrame64)
                {
                    hsQuatKey tempKey;
                    ICreateHSInterpKey(control, key.get(), 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.get(), kTimes[k], &tempKey, node, camRot);
                    hsCompressedQuatKey32 *compKey = pc->GetCompressedQuatKey32(i - startIdx);
                    compKey->fFrame = tempKey.fFrame;
                    compKey->SetQuat(tempKey.fValue);
                }
            }
        }

        return pc;
    }

    return nullptr;
    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() : nullptr, control, startIdx, endIdx) > 1)
    {
        if(!(control->IsKeyable()))
        {
            fErrorMsg->Set(true, "Scale Value Controller Creation Error", "Control is not keyable").Show();
            fErrorMsg->Set();
            return nullptr;
        }

        ikey_ptr key = IAllocKey(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.get());
            hsScaleKey *hsKey = pc->GetScaleKey(i - startIdx);
            if (hsKey)
                ICreateHSInterpKey(control, key.get(), key->time, hsKey, node);

            hsBezScaleKey *bezKey = pc->GetBezScaleKey(i - startIdx);
            if (bezKey)
                ICreateHSInterpKey(control, key.get(), key->time, bezKey, node);
        }
        return pc;
    }
    
    return nullptr;
    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() : nullptr, control, startIdx, endIdx) > 1)
    {
        if(!(control->IsKeyable()))
        {
            fErrorMsg->Set(true, "Scale Value Controller Creation Error", "Control is not keyable").Show();
            fErrorMsg->Set();
            return nullptr;
        }

        ikey_ptr key = IAllocKey(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.get());
            hsScalarKey *hsKey = pc->GetScalarKey(i - startIdx);
            if (hsKey)
                ICreateHSInterpKey(control, key.get(), key->time, hsKey);

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

        return pc;
    }
    return nullptr;
    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() : nullptr, control, startIdx, endIdx) > 1)
    {
        if(!(control->IsKeyable()))
        {
            fErrorMsg->Set(true, "Simple Position Controller Creation Error", "Control is not keyable").Show();
            fErrorMsg->Set();
            return nullptr;
        }

        ikey_ptr key = IAllocKey(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.get());
            hsPoint3Key *hsKey = pc->GetPoint3Key(i - startIdx);
            if (hsKey)
                ICreateHSInterpKey(control, key.get(), key->time, hsKey);

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

    return nullptr;
    hsGuardEnd;
}

//
// Create a hsKey and store the nodes LTM in it.
// Recurses along all subcontrollers.
//
void hsControlConverter::IAddPartsKeys(Control* control,
                  std::vector<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() : nullptr, control, startIdx, endIdx) : 0;
        
        if (num<2)
        {
            return;
        }

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

        //
        // Traverse all keys of controller
        //
        ikey_ptr key = IAllocKey(ikeys->GetKeySize());
        bool mb=false;
        plMaxNode* xformParent = GetXformParent(node);
        for (int32_t i = startIdx; i <= endIdx; i++)
        {
            // Get key
            ikeys->GetKey(i, key.get());
            float frameTime = float(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
            auto idx = std::find_if(kfArray->cbegin(), kfArray->cend(),
                                    [frameNum](const hsG3DSMaxKeyFrame& k) { return k.fFrame == frameNum; });
            if (idx != kfArray->cend())
                // 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->emplace_back(hKey);
        }
    }
    else
    {
        int i;
        for (i = 0; i < control->NumSubs(); i++)
            IAddPartsKeys((Control *)control->SubAnim(i), kfArray, node);
    }

    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_ptr key = IAllocKey(ikeys->GetKeySize());
    ikey_ptr lastKey = IAllocKey(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.get());
        ikeys->GetKey(i, key.get());
        if( cID == Class_ID(TCBINTERP_ROTATION_CLASS_ID, 0) )  
        {
            ITCBRotKey* tcbRotKey = (ITCBRotKey*)key.get();
            rads = tcbRotKey->val.angle;
        }
        else 
        if( cID == Class_ID(LININTERP_ROTATION_CLASS_ID, 0) )  
        {
            ILinRotKey* linRotKey = (ILinRotKey*)key.get();

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

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

            rads = fKey->val;
            fKey = (ITCBFloatKey*)lastKey.get();
            rads -= fKey->val;
        }
        else
        if( cID == Class_ID(LININTERP_FLOAT_CLASS_ID, 0) )
        {
            ILinFloatKey* fKey = (ILinFloatKey*)key.get();
            rads = fKey->val;
            fKey = (ILinFloatKey*)lastKey.get();
            rads -= fKey->val;
        }
        else
        if( cID == Class_ID(HYBRIDINTERP_FLOAT_CLASS_ID, 0) )
        {
            IBezFloatKey* fKey = (IBezFloatKey*)key.get();
            rads = fKey->val;
            fKey = (IBezFloatKey*)lastKey.get();
            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);
        }
        ///////////////////////////////////////
    }

    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, bool rotQuat)
{
    hsGuardBegin("hsControlConverter::ICreateHSInterpKey");

    Class_ID cID = control->ClassID();
    SClass_ID sID = control->SuperClassID();
    auto nodeName = node ? node->GetName() : nullptr;

    // 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, bool 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(const MCHAR* 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_ptr key = IAllocKey(keys->GetKeySize());

    start = numKeys;
    for (int i=0; i<numKeys; i++)
    {
        keys->GetKey(i, key.get());
        if (IIsKeyInRange(key.get()))
        {
            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.get());
            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)
        {
            return 0;
        }

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

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

    //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
// ###########################################################################
bool hsControlConverter::ForceWorldSpace(plMaxNode* node)
{
    hsGuardBegin("hsControlConverter::ForceWorldSpace");

    return false;
    
    hsGuardEnd;
}

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

    auto 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.
// ###########################################################################
bool hsControlConverter::ForceLocal(plMaxNode* node)
{
    hsGuardBegin("hsControlConverter::ForceLocal");


    auto 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,_M("AllowInstancing")))
    {
        node->SetForceLocal(true);
        return true;
    }


    return false;
    
    hsGuardEnd;
}


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

    return node->IsAnimated();
    
    hsGuardEnd;
}

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

    return false;
    hsGuardEnd;
}

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

bool hsControlConverter::GetControllerByName(Animatable* anim, const MSTR& name, Control* &ctl)
{
    hsGuardBegin("hsControlConverter::GetControllerByName");

    if( anim )
    {
        int nSub = anim->NumSubs();
        int i;
        for( i = 0; i < nSub; i++ )
        {
            if (anim->SubAnim(i) == nullptr)
                continue;
            MSTR subName = anim->SubAnimName(i);
            if( subName == name )
            {
                fErrorMsg->Set(!anim->SubAnim(i), M2ST(name.data()), "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 = nullptr;

    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 nullptr;
    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(const MSTR& ctrlName)
{
    hsGuardBegin("hsControlConverter::IGetControlType");

    ControllerType ct = ctrlTypeUnknown;
    if (ctrlName == MSTR(_M("Ease Curve")))
        ct = ctrlTypeEase;
    else if (ctrlName == MSTR(_M("Mult Curve")))
        ct = ctrlTypeMult;
    else if (ctrlName == MSTR(_M("Position")))
        ct = ctrlTypePosition;
    else if (ctrlName == MSTR(_M("Rotation")))
        ct = ctrlTypeRotation;
    else if (ctrlName == MSTR(_M("Scale")))
        ct = ctrlTypeScale;
    else if (ctrlName == MSTR(_M("Transform")))
        ct = ctrlTypeTransform;
    else if (ctrlName == MSTR(_M("Roll Angle")))
        ct = ctrlTypeRollAngle;
#if 0
    // biped controllers are good for nothing
    else if (ctrlName == MSTR(_M("Vertical")))
        ct = ctrlTypeVert;
    else if (ctrlName == MSTR(_M("Horizontal")))
        ct = ctrlTypeHoriz;
    else if (ctrlName == MSTR(_M("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, _M("MATSkin")))
    {
        return true;
    }

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

    if( node && node->GetName() && _tcsstr(node->GetName(), _T("%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...

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

    Animatable *anim;
    if( IGetSubAnimByName(node, _M("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...

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

    auto dgbNodeName = node->GetName();
    Object *obj = node->GetObjectRef();
    if( !obj )
        return false;
    IDerivedObject *derObj = nullptr;
    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);
        auto 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...

bool    hsControlConverter::IGetSubAnimByName( Animatable *anim, const MSTR& name, Animatable *&subAnim )
{
    hsGuardBegin( "hsControlConverter::IGetSubAnimByName" );

    if( anim )
    {
        int nSub = anim->NumSubs();
        int i;
        for( i = 0; i < nSub; i++ )
        {
            if (anim->SubAnim(i) == nullptr)
                continue;
            MSTR subName = anim->SubAnimName(i);
            if( subName == name )
            {
                fErrorMsg->Set(!anim->SubAnim(i), M2ST(name.data()), "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 = nullptr;
    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, std::vector<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 = nullptr;
    uint32_t count = node->NumAttachedComponents();
    for (uint32_t 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 = nullptr;
    for (uint32_t i = 0; i < count; i++)
    {
        plComponentBase *comp = node->GetAttachedComponent(i);
        if (comp->ClassID() == FIXEDCAM_CID)
        {
            pCamComp = (plCamera1Component*)comp;
            break;
        }
    }

    const plCameraModifier1* pCamMod = nullptr;
    size_t modCount = node->GetSceneObject()->GetNumModifiers();
    for (size_t i = 0; i < modCount; 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;
    std::vector<float> fovW;
    std::vector<float> fovH;
    for (const hsG3DSMaxKeyFrame& k : *kfArray)
    {
        TimeValue t = TimeValue(GetTicksPerFrame() * (k.fFrame));
        theCam = (GenCamera *) obj->ConvertToType(t, Class_ID(LOOKAT_CAM_CLASS_ID, 0));
        float FOVvalue = hsRadiansToDegrees(theCam->GetFOV(t));
        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.emplace_back(wDeg);
        fovH.emplace_back(hDeg);
    }
    for (size_t i = 0; i < kfArray->size(); i++)
    {
        plCameraMsg* pFOVMsg = new plCameraMsg;
        plCameraConfig* pCfg = pFOVMsg->GetConfig();

        if (i == kfArray->size() - 1)
        {
            pCfg->fFOVh = fovH[0];
            pCfg->fFOVw = fovW[0];
            pCfg->fAccel = (*kfArray)[0].fFrame / MAX_FRAMES_PER_SEC;
        }
        else
        {
            pCfg->fFOVh = fovH[i + 1];
            pCfg->fFOVw = fovW[i + 1];
            pCfg->fAccel = (*kfArray)[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)[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(ENTIRE_ANIMATION_NAME);
        pMsg->fTime = (*kfArray)[i].fFrame / MAX_FRAMES_PER_SEC;
        pMsg->AddCallback(pCall);
        hsRefCnt_SafeUnRef(pCall);
        plConvert::Instance().AddMessageToQueue(pFOVMsg);
        plConvert::Instance().AddMessageToQueue(pMsg);
    }
}
