/**********************************************************************
*<
FILE: ImportAnimation.cpp

DESCRIPTION:	Animation Import Routines

CREATED BY: tazpn (Theo)

HISTORY: 

*>	Copyright (c) 2006, All Rights Reserved.
**********************************************************************/
#include "pch.h"
#if VERSION_3DSMAX > ((5000<<16)+(15<<8)+0) // Version 5
#include <IFrameTagManager.h>
#endif
#include <notetrck.h>
#include <set>
#include "NifExport.h"
#include "AnimKey.h"
#ifdef USE_BIPED
#  include <cs/BipedApi.h>
#endif

#include <obj/NiControllerSequence.h>
#include <obj/NiControllerManager.h>
#include <obj/NiInterpolator.h>
#include <obj/NiTransformInterpolator.h>
#include <obj/NiTransformData.h>
#include <obj/NiTimeController.h>
#include <obj/NiTransformController.h>
#include <obj/NiTextKeyExtraData.h>
#include <obj/NiKeyframeController.h>
#include <obj/NiKeyframeData.h>
#include <obj/NiStringPalette.h>
#include <obj/NiBSplineCompTransformInterpolator.h>
#include <obj/NiDefaultAVObjectPalette.h>
#include <obj/NiMultiTargetTransformController.h>
using namespace Niflib;

const Class_ID IPOS_CONTROL_CLASS_ID = Class_ID(0x118f7e02,0xffee238a);
enum {
   IPOS_X_REF	=	0,
   IPOS_Y_REF	=	1,
   IPOS_Z_REF	=	2,
   IPOS_W_REF	=	3,
};

struct AnimationExport
{
   AnimationExport(Exporter& parent) : ne(parent) {}

   INode * findTrackedNode(INode *root);

   bool doExport(NiControllerSequenceRef seq);
   bool doExport(NiControllerManagerRef ctrl, INode *node);
   bool exportController(INode *node, bool hasAccum);
   Control *GetTMController(INode* node);
   NiTimeControllerRef exportController(INode *node, Interval range, bool setTM );
   bool GetTextKeys(INode *node, vector<StringKey>& textKeys);

   Exporter &ne;
   Interval range;
   NiControllerSequenceRef seq;

   set<NiAVObjectRef> objRefs;
   map<NiControllerSequenceRef, Interval> ranges;
};

bool GetTextKeys(INode *node, vector<StringKey>& textKeys, Interval range)
{
   // Populate Text keys and Sequence information from note tracks
   if (Exporter::mUseTimeTags) {
#if VERSION_3DSMAX > ((5000<<16)+(15<<8)+0) // Version 5
      if (IFrameTagManager *tagMgr = (IFrameTagManager*)GetCOREInterface(FRAMETAGMANAGER_INTERFACE)) {
         int n = tagMgr->GetTagCount();
         for (int i = 0; i<n; i++) {
            UINT id = tagMgr->GetTagID(i);
            TimeValue t = tagMgr->GetTimeByID(id, FALSE);
            TSTR name = tagMgr->GetNameByID(id);

            StringKey strkey;
            strkey.time = FrameToTime( Interval(range.Start(), t).Duration()-1 );
            strkey.data = name;
            textKeys.push_back(strkey);
         }
      }
#endif
   } else {
      for (int i=0, n=node->NumNoteTracks(); i<n; ++i) {
         if ( NoteTrack *nt = node->GetNoteTrack(i) ) {
            if ( nt->ClassID() == Class_ID(NOTETRACK_CLASS_ID,0) ) {
               DefNoteTrack *defNT = (DefNoteTrack *)nt;
               if ( defNT->NumKeys() > 0 ) {
                  bool stop = false;
                  for (int j=0, m=defNT->keys.Count(); j<m && !stop; ++j) {
                     NoteKey* key = defNT->keys[j];

                     if (wildmatch("start*", key->note)) {
                        stringlist args = TokenizeCommandLine(key->note, true);
                        if (args.empty()) continue;

                        range.SetStart( key->time );
                        for (stringlist::iterator itr = args.begin(); itr != args.end(); ++itr) {
                           if (strmatch("-name", *itr)) {
                              if (++itr == args.end()) break;
                           }
                        }
                     } else if ( wildmatch("end*", key->note) ) {
                        range.SetEnd( key->time );
                        stop = true;
                     }
                     StringKey strkey;
                     strkey.time = FrameToTime( Interval(range.Start(), key->time).Duration()-1 );
                     strkey.data = key->note;
                     textKeys.push_back(strkey);
                  }
               }
            }
         }
      }
   }
   return !textKeys.empty();
}

void Exporter::InitializeTimeController(NiTimeControllerRef ctrl, NiNodeRef parent)
{
   ctrl->SetFrequency(1.0f);
   ctrl->SetStartTime(FloatINF);
   ctrl->SetStopTime(FloatNegINF);
   ctrl->SetPhase(0.0f);
   ctrl->SetFlags(0x0C);
   ctrl->SetTarget( parent );
   parent->AddController(DynamicCast<NiTimeController>(ctrl));
}

NiNodeRef Exporter::createAccumNode(NiNodeRef parent, INode *node)
{
   NiNodeRef accumNode;
   bool isTracked = isNodeTracked(node);
   if (!Exporter::mAllowAccum || (!isTracked && !isSkeletonRoot(node)))
   {
      accumNode = parent;
   }
   else
   {
      accumNode = getNode( FormatString("%s NonAccum", node->GetName()) );
      accumNode->SetLocalTransform(Matrix44::IDENTITY);
      parent->AddChild(DynamicCast<NiAVObject>(accumNode));
   }

   // add multi target controller to parent if exporting with animation
   if ( mExportType == NIF_WO_KF ){

      if ( Exporter::mAllowAccum && isTracked ) {
         // transfer controllers to accum
         list<NiTimeControllerRef> ctlrs = parent->GetControllers();
         for (list<NiTimeControllerRef>::iterator it = ctlrs.begin(); it != ctlrs.end(); ++it) {
            parent->RemoveController(*it);
            accumNode->AddController(*it);
         }
      }

   } else if ( Exporter::mExportType != Exporter::NIF_WO_ANIM ) 
   {
      // NiMultiTargetTransformController causes crashes in old formats
      if (Exporter::mNifVersionInt >= VER_10_0_1_0)
      {
         NiMultiTargetTransformControllerRef ctrl = new NiMultiTargetTransformController();
         vector<NiNodeRef> children;
         getChildNodes(node, children);
         ctrl->SetExtraTargets(children);
         Exporter::InitializeTimeController(ctrl, parent);
      }
      NiControllerManagerRef mgr = new NiControllerManager();
      Exporter::InitializeTimeController(mgr, parent);

      // Export Animation now
      doAnimExport(mgr, node);
   }
   return accumNode;
}
Exporter::Result Exporter::doAnimExport(NiControllerSequenceRef root)
{
   AnimationExport animExporter(*this);
   return animExporter.doExport(root) ? Exporter::Ok : Exporter::Abort ;
}

Exporter::Result Exporter::doAnimExport(NiControllerManagerRef mgr, INode *node)
{
   AnimationExport animExporter(*this);
   return animExporter.doExport(mgr, node) ? Exporter::Ok : Exporter::Abort ;
}

bool Exporter::isNodeTracked(INode *node)
{
   if (Exporter::mUseTimeTags) {
      // Assume only one top level node has animation
      if (mI->GetRootNode() == node->GetParentNode() && isNodeKeyed(node)) {
         return true;
      }
   }
   else if (node->HasNoteTracks()) {
      for (int i=0, n=node->NumNoteTracks(); i<n; ++i) {
         if ( NoteTrack *nt = node->GetNoteTrack(i) ) {
            if ( nt->ClassID() == Class_ID(NOTETRACK_CLASS_ID,0) ) {
               DefNoteTrack *defNT = (DefNoteTrack *)nt;
               if ( defNT->NumKeys() > 0 ) {
                  for (int j=0, m=defNT->keys.Count(); j<m; ++j) {
                     NoteKey* key = defNT->keys[j];
                     if (wildmatch("start*", key->note)) {
                        return true;
                     }
                  }
               }
            }
         }
      }
   }
   return false;
}


static bool HasKeys(Control *c)
{
   bool rv = false;
   if (c != NULL)
   {
      if (c->IsColorController())
         return false;

      if (IKeyControl *ikeys = GetKeyControlInterface(c)){
         if (ikeys->GetNumKeys() > 0)
            return true;
      }
#if VERSION_3DSMAX > ((5000<<16)+(15<<8)+0) // Version 5
      if (Control *sc = c->GetWController()) { 
         if (sc != c && HasKeys(sc)) 
            return true;
      }
#endif
      if (Control *sc = c->GetXController()) { 
         if (sc != c && HasKeys(sc)) 
            return true;
      }
      if (Control *sc = c->GetYController()) { 
         if (sc != c && HasKeys(sc)) 
            return true;
      }
      if (Control *sc = c->GetZController()) { 
         if (sc != c && HasKeys(sc)) 
            return true;
      }
      if (Control *sc = c->GetRotationController()) { 
         if (sc != c && HasKeys(sc)) 
            return true;
      }
      if (Control *sc = c->GetPositionController()) { 
         if (sc != c && HasKeys(sc)) 
            return true;
      }
      if (Control *sc = c->GetScaleController()) { 
         if (sc != c && HasKeys(sc)) 
            return true;
      }
   }
   return false;
}

bool Exporter::isNodeKeyed(INode *node) {
   if (node->HasNoteTracks()) {
      return true;
   }
   if (node->NumKeys() > 0) {
      return true;
   }
   if (Control *tmCont = node->GetTMController()) {
      if (HasKeys(tmCont))
         return true;
   }
   return false;
}


INode * AnimationExport::findTrackedNode(INode *node)
{
   if (ne.isNodeTracked(node))
      return node;

   // locate START in note track before assuming all is well
   for (int i=0; i < node->NumberOfChildren(); ++i ){
      if ( INode *root = findTrackedNode( node->GetChildNode(i) ) ) {
         return root;
      }
   }
   return NULL;
}

bool AnimationExport::doExport(NiControllerSequenceRef seq)
{
   INode *node = findTrackedNode(ne.mI->GetRootNode());
   if (node == NULL)
      throw runtime_error("No Actor Roots have been selected in the Animation Manager. Cannot continue.");

   this->seq = seq;

   vector<StringKey> textKeys;

   this->range.SetInstant(0);

   seq->SetStartTime(FloatINF);
   seq->SetStopTime(FloatINF);
   seq->SetFrequency(1.0f);
   seq->SetCycleType( CYCLE_CLAMP );
   seq->SetTargetName( node->GetName() );

   NiTextKeyExtraDataRef textKeyData = new NiTextKeyExtraData();
   seq->SetTextKey(textKeyData);

   // Populate Text keys and Sequence information from note tracks
   if (Exporter::mUseTimeTags) {
#if VERSION_3DSMAX > ((5000<<16)+(15<<8)+0) // Version 5
      if (IFrameTagManager *tagMgr = (IFrameTagManager*)GetCOREInterface(FRAMETAGMANAGER_INTERFACE)) {
         int n = tagMgr->GetTagCount();
         for (int i = 0; i<n; i++) {
            UINT id = tagMgr->GetTagID(i);
            TimeValue t = tagMgr->GetTimeByID(id, FALSE);
            TSTR name = tagMgr->GetNameByID(id);

            StringKey strkey;
            strkey.time = FrameToTime( Interval(range.Start(), t).Duration()-1 );
            strkey.data = name;
            textKeys.push_back(strkey);
         }
      }
#endif
   } else {
      for (int i=0, n=node->NumNoteTracks(); i<n; ++i) {
         if ( NoteTrack *nt = node->GetNoteTrack(i) ) {
            if ( nt->ClassID() == Class_ID(NOTETRACK_CLASS_ID,0) ) {
               DefNoteTrack *defNT = (DefNoteTrack *)nt;
               if ( defNT->NumKeys() > 0 ) {
                  bool stop = false;
                  for (int j=0, m=defNT->keys.Count(); j<m && !stop; ++j) {
                     NoteKey* key = defNT->keys[j];

                     if (wildmatch("start*", key->note)) {
                        stringlist args = TokenizeCommandLine(key->note, true);
                        if (args.empty()) continue;

                        seq->SetStartTime(0.0f);
                        range.SetStart( key->time );
                        for (stringlist::iterator itr = args.begin(); itr != args.end(); ++itr) {
                           if (strmatch("-name", *itr)) {
                              if (++itr == args.end()) break;
                              seq->SetName(*itr);
                           } else if (strmatch("-loop", *itr)) {
                              seq->SetCycleType(CYCLE_LOOP);
                           }
                        }
                     } else if ( wildmatch("end*", key->note) ) {
                        range.SetEnd( key->time );
                        seq->SetStopTime( FrameToTime( range.Duration()-1 ) );
                        stop = true;
                     }
                     StringKey strkey;
                     strkey.time = FrameToTime( Interval(range.Start(), key->time).Duration()-1 );
                     strkey.data = key->note;
                     textKeys.push_back(strkey);
                  }
               }
            }
         }
      }
   }
   textKeyData->SetKeys(textKeys);

   // Now let the fun begin.

   return exportController(node, true);
}

bool AnimationExport::doExport(NiControllerManagerRef mgr, INode *node)
{
   int start = 0;
   NiDefaultAVObjectPaletteRef objPal = new NiDefaultAVObjectPalette();
   mgr->SetObjectPalette(objPal);


   vector<NiControllerSequenceRef> seqs;
   vector<StringKey> textKeys;
   NiControllerSequenceRef curSeq;

   // Populate Text keys and Sequence information from note tracks
   if (Exporter::mUseTimeTags) {
#if VERSION_3DSMAX > ((5000<<16)+(15<<8)+0) // Version 5
      if (IFrameTagManager *tagMgr = (IFrameTagManager*)GetCOREInterface(FRAMETAGMANAGER_INTERFACE)) {

         curSeq = new NiControllerSequence();
         curSeq->SetStartTime(FloatINF);
         curSeq->SetStopTime(FloatINF);
         curSeq->SetFrequency(1.0f);
         curSeq->SetCycleType( CYCLE_CLAMP );
         curSeq->SetTargetName( node->GetName() );
         seqs.push_back(curSeq);
         this->range.SetInstant(0);
         curSeq->SetStartTime(0.0f);

         int n = tagMgr->GetTagCount();
         for (int i = 0; i<n; i++) {
            UINT id = tagMgr->GetTagID(i);
            TimeValue t = tagMgr->GetTimeByID(id, FALSE);
            TSTR name = tagMgr->GetNameByID(id);

            if (t < range.Start())
               range.SetStart(t);

            if (t > range.End())
               range.SetEnd( t );

            StringKey strkey;
            strkey.time = FrameToTime( Interval(range.Start(), t).Duration()-1 );
            strkey.data = name;
            textKeys.push_back(strkey);
         }
         NiTextKeyExtraDataRef textKeyData = new NiTextKeyExtraData();
         curSeq->SetTextKey(textKeyData);
         textKeyData->SetKeys(textKeys);

         curSeq->SetStopTime( FrameToTime( range.Duration()-1 ) );
         this->ranges[curSeq] = range;
         curSeq = NULL;
      }
#endif
   } else {

      int nTracks = node->NumNoteTracks();

      // Populate Text keys and Sequence information from note tracks
      for (int i=0; i<nTracks; ++i) {
         if ( NoteTrack *nt = node->GetNoteTrack(i) ) {
            if ( nt->ClassID() == Class_ID(NOTETRACK_CLASS_ID,0) ) {
               DefNoteTrack *defNT = (DefNoteTrack *)nt;
               if ( defNT->NumKeys() > 0 ) {
                  for (int j=0, m=defNT->keys.Count(); j<m; ++j) {
                     NoteKey* key = defNT->keys[j];

                     if (wildmatch("start*", key->note)) {
                        stringlist args = TokenizeCommandLine(key->note, true);
                        textKeys.clear();

                        curSeq = new NiControllerSequence();
                        curSeq->SetStartTime(FloatINF);
                        curSeq->SetStopTime(FloatINF);
                        curSeq->SetFrequency(1.0f);
                        curSeq->SetCycleType( CYCLE_CLAMP );
                        curSeq->SetTargetName( node->GetName() );
                        seqs.push_back(curSeq);
                        this->range.SetInstant(0);

                        curSeq->SetStartTime(0.0f);
                        range.SetStart( key->time );
                        for (stringlist::iterator itr = args.begin(); itr != args.end(); ++itr) {
                           if (strmatch("-name", *itr)) {
                              if (++itr == args.end()) break;
                              curSeq->SetName(*itr);
                           } else if (strmatch("-loop", *itr)) {
                              curSeq->SetCycleType(CYCLE_LOOP);
                           }
                        }
                     }

                     StringKey strkey;
                     strkey.time = FrameToTime( Interval(range.Start(), key->time).Duration()-1 );
                     strkey.data = key->note;
                     textKeys.push_back(strkey);

                     if ( wildmatch("end*", key->note) ) {
                        range.SetEnd( key->time );

                        // add accumulated text keys to sequence
                        if (curSeq != NULL) {
                           curSeq->SetStopTime( FrameToTime( range.Duration()-1 ) );

                           this->ranges[curSeq] = range;

                           NiTextKeyExtraDataRef textKeyData = new NiTextKeyExtraData();
                           curSeq->SetTextKey(textKeyData);
                           textKeyData->SetKeys(textKeys);
                           textKeys.clear();
                           curSeq = NULL;
                        }
                     }
                  }
               }
            }
         }
      }
   }

   for (vector<NiControllerSequenceRef>::iterator itr = seqs.begin(); itr != seqs.end(); ++itr)
   {     
      // Hold temporary value
      this->seq = (*itr);
      
      //this->range.SetStart( TimeToFrame(seq->GetStartTime()) );
      //this->range.SetEnd( TimeToFrame(seq->GetStopTime()) );

      this->range = this->ranges[this->seq];

      // Now let the fun begin.
      bool ok = exportController(node, true);
   }

   // Set objects with animation
   vector<NiAVObjectRef> objs;
   objs.insert(objs.end(), objRefs.begin(), objRefs.end());
   objPal->SetObjs(objs);

   mgr->SetControllerSequences(seqs);

   return true;
}


Control *AnimationExport::GetTMController(INode *n)
{
   Control *c = n->GetTMController();
   if (NULL == c)
      return NULL;

#ifdef USE_BIPED
   // ignore bipeds for now.
   if ( (c->ClassID() == BIPSLAVE_CONTROL_CLASS_ID) 
      ||(c->ClassID() == BIPBODY_CONTROL_CLASS_ID) 
      ||(c->ClassID() == FOOTPRINT_CLASS_ID))
      return NULL;
#endif

   return c;
}

NiTimeControllerRef Exporter::CreateController(INode *node, Interval range)
{
   AnimationExport ae(*this);
   if ( NiTimeControllerRef tc = ae.exportController(node, range, false) ) {
      if (Exporter::mExportType == Exporter::NIF_WO_KF && isNodeTracked(node)) {
         NiNodeRef ninode = getNode(node->GetName());
         vector<StringKey> textKeys;
         if (GetTextKeys(node, textKeys, range)) {
            NiTextKeyExtraDataRef textKeyData = new NiTextKeyExtraData();
            ninode->AddExtraData(StaticCast<NiExtraData>(textKeyData), Exporter::mNifVersionInt);
            textKeyData->SetKeys(textKeys);
         }
      }
      return tc;
   }
   return NiTimeControllerRef();
}

NiTimeControllerRef AnimationExport::exportController(INode *node, Interval range, bool setTM )
{
   bool skip = false;
   NiTimeControllerRef timeControl;

   ne.ProgressUpdate(Exporter::Animation, FormatText("'%s' Animation", node->GetName()));

   // Primary recursive decent routine

   ObjectState os = node->EvalWorldState(range.Start()); 

   if (!Exporter::mExportCameras && os.obj && os.obj->SuperClassID()==CAMERA_CLASS_ID)
   {
      skip = true;
   }
   else if (!Exporter::mExportLights && os.obj && os.obj->SuperClassID()==LIGHT_CLASS_ID)
   {
      skip = true;
   }

   if (!skip && Exporter::mExportTransforms)
   {
      float timeOffset = -FrameToTime(range.Start());
      if (Control *tmCont = GetTMController(node))
      {
         Interval validity; validity.SetEmpty();
         Matrix3 tm = node->GetObjTMAfterWSM(range.Start());
         if (INode *parent = node->GetParentNode()) {
            Matrix3 pm = Inverse(parent->GetObjTMAfterWSM(range.Start()));
            tm *= pm;
         }

         bool keepData = false;

         // Set default transform to NaN except for root node
         //Vector3 trans(FloatNegINF, FloatNegINF, FloatNegINF);
         //Quaternion rot(FloatNegINF, FloatNegINF, FloatNegINF, FloatNegINF);
         float scale = FloatNegINF;
         Vector3 trans = TOVECTOR3(tm.GetTrans());
         Quaternion rot = TOQUAT( Quat(tm), true );

         NiNodeRef ninode = ne.getNode( node->GetName() );
         if (setTM) {
            trans = TOVECTOR3(tm.GetTrans());
            rot = TOQUAT( Quat(tm), true );
         }

         NiKeyframeDataRef data;

         if (Exporter::mNifVersionInt < VER_10_2_0_0)
         {
            NiKeyframeControllerRef control = new NiKeyframeController();
            Exporter::InitializeTimeController(control, ninode);
            data = new NiKeyframeData();
            control->SetData(data);
            timeControl = StaticCast<NiTimeController>(control);
         }
         else
         {
            NiTransformControllerRef control = new NiTransformController();
            Exporter::InitializeTimeController(control, ninode);

            NiTransformInterpolatorRef interp = new NiTransformInterpolator();
            data = new NiTransformData();
            control->SetInterpolator(StaticCast<NiInterpolator>(interp));

            interp->SetTranslation(trans);
            interp->SetScale(scale);
            interp->SetRotation(rot);
            interp->SetData(data);

            timeControl = StaticCast<NiTimeController>(control);
         }
         timeControl->SetStartTime( 0.0f );
         timeControl->SetStopTime( FrameToTime( range.Duration()-1 ) );

         //if (validity.InInterval(range))
         //{
         //   // Valid for entire interval.  i.e. no changes
         //   interp->SetTranslation( TOVECTOR3(tm.GetTrans()) );
         //   interp->SetScale( Average(GetScale(tm)) );
         //   interp->SetRotation( TOQUAT( Quat(tm) ) );
         //   seq->AddInterpolator(StaticCast<NiSingleInterpolatorController>(control));
         //}
         //else
         {
            if (Control *c = tmCont->GetPositionController()) 
            {
               int nkeys = 0;
               // separate xyz
               if (c->ClassID() == IPOS_CONTROL_CLASS_ID) 
               { 
                  KeyType kType = QUADRATIC_KEY;
                  vector<FloatKey> xkeys, ykeys, zkeys;
                  if (Control *x = c->GetXController()){
                     if (x->ClassID() == Class_ID(LININTERP_FLOAT_CLASS_ID,0)) {
                        kType = LINEAR_KEY;
                        nkeys += GetKeys<FloatKey, ILinFloatKey>(x, xkeys, range);
                     } else if (x->ClassID() == Class_ID(HYBRIDINTERP_FLOAT_CLASS_ID,0)) {
                        kType = QUADRATIC_KEY;
                        nkeys += GetKeys<FloatKey, IBezFloatKey>(x, xkeys, range);
                     } else if (x->ClassID() == Class_ID(TCBINTERP_FLOAT_CLASS_ID,0)) {
                        kType = TBC_KEY;
                        nkeys += GetKeys<FloatKey, ITCBFloatKey>(x, xkeys, range);
                     } else {
                        kType = QUADRATIC_KEY;
                        nkeys += GetKeys<FloatKey, IBezFloatKey>(x, xkeys, range);
                     }
                  }
                  if (Control *y = c->GetYController()){
                     if (y->ClassID() == Class_ID(LININTERP_FLOAT_CLASS_ID,0)) {
                        nkeys += GetKeys<FloatKey, ILinFloatKey>(y, ykeys, range);
                     } else if (y->ClassID() == Class_ID(HYBRIDINTERP_FLOAT_CLASS_ID,0)) {
                        nkeys += GetKeys<FloatKey, IBezFloatKey>(y, ykeys, range);
                     } else if (y->ClassID() == Class_ID(TCBINTERP_FLOAT_CLASS_ID,0)) {
                        nkeys += GetKeys<FloatKey, ITCBFloatKey>(y, ykeys, range);
                     } else {
                        nkeys += GetKeys<FloatKey, IBezFloatKey>(y, ykeys, range);
                     }
                  }
                  if (Control *z = c->GetZController()){
                     if (z->ClassID() == Class_ID(LININTERP_FLOAT_CLASS_ID,0)) {
                        nkeys += GetKeys<FloatKey, ILinFloatKey>(z, zkeys, range);
                     } else if (z->ClassID() == Class_ID(HYBRIDINTERP_FLOAT_CLASS_ID,0)) {
                        nkeys += GetKeys<FloatKey, IBezFloatKey>(z, zkeys, range);
                     } else if (z->ClassID() == Class_ID(TCBINTERP_FLOAT_CLASS_ID,0)) {
                        nkeys += GetKeys<FloatKey, ITCBFloatKey>(z, zkeys, range);
                     } else {
                        nkeys += GetKeys<FloatKey, IBezFloatKey>(z, zkeys, range);
                     }
                  }
                  vector<Vector3Key> keys;
                  JoinKeys(keys, xkeys, ykeys, zkeys);
                  data->SetTranslateType(kType);
                  data->SetTranslateKeys(keys);
               } else {
                  vector<Vector3Key> keys;
                  if (c->ClassID() == Class_ID(LININTERP_FLOAT_CLASS_ID,0)) {
                     data->SetTranslateType(LINEAR_KEY);
                     nkeys += GetKeys<Vector3Key, ILinPoint3Key>(c, keys, range);
                  } else if (c->ClassID() == Class_ID(HYBRIDINTERP_FLOAT_CLASS_ID,0)) {
                     data->SetTranslateType(QUADRATIC_KEY);
                     nkeys += GetKeys<Vector3Key, IBezPoint3Key>(c, keys, range);
                  } else if (c->ClassID() == Class_ID(TCBINTERP_FLOAT_CLASS_ID,0)) {
                     data->SetTranslateType(TBC_KEY);
                     nkeys += GetKeys<Vector3Key, ITCBPoint3Key>(c, keys, range);
                  } else {
                     data->SetTranslateType(QUADRATIC_KEY);
                     nkeys += GetKeys<Vector3Key, IBezPoint3Key>(c, keys, range);
                  }
                  data->SetTranslateKeys(keys);
               }
               if (nkeys != 0) { // if no changes set the base transform
                  keepData = true;
                  //trans = TOVECTOR3(tm.GetTrans());
               }
            }

            // Rotations
            if (Control *c = tmCont->GetRotationController()) 
            {
               int nkeys = 0;
               if (c->ClassID() == Class_ID(LININTERP_ROTATION_CLASS_ID,0)) {
                  vector<QuatKey> keys;
                  data->SetRotateType(LINEAR_KEY);
                  nkeys += GetKeys<QuatKey, ILinRotKey>(c, keys, range);
                  data->SetQuatRotateKeys(keys);
               } else if (c->ClassID() == Class_ID(HYBRIDINTERP_ROTATION_CLASS_ID,0)) {
                  vector<QuatKey> keys;
                  data->SetRotateType(QUADRATIC_KEY);
                  nkeys += GetKeys<QuatKey, IBezQuatKey>(c, keys, range);
                  data->SetQuatRotateKeys(keys);
               } else if (c->ClassID() == Class_ID(EULER_CONTROL_CLASS_ID,0)) {
                  data->SetRotateType(XYZ_ROTATION_KEY);
                  if (Control *x = c->GetXController()){
                     vector<FloatKey> keys;
                     if (x->ClassID() == Class_ID(LININTERP_FLOAT_CLASS_ID,0)) {
                        nkeys += GetKeys<FloatKey, ILinFloatKey>(x, keys, range);
                        data->SetXRotateType(LINEAR_KEY);
                     } else if (x->ClassID() == Class_ID(HYBRIDINTERP_FLOAT_CLASS_ID,0)) {
                        nkeys += GetKeys<FloatKey, IBezFloatKey>(x, keys, range);
                        data->SetXRotateType(QUADRATIC_KEY);
                     } else if (x->ClassID() == Class_ID(TCBINTERP_FLOAT_CLASS_ID,0)) {
                        nkeys += GetKeys<FloatKey, ITCBFloatKey>(x, keys, range);
                        data->SetXRotateType(TBC_KEY);
                     } else {
                        nkeys += GetKeys<FloatKey, IBezFloatKey>(x, keys, range);
                        data->SetXRotateType(QUADRATIC_KEY);
                     }
                     data->SetXRotateKeys(keys);
                  }
                  if (Control *y = c->GetYController()) {
                     vector<FloatKey> keys;
                     if (y->ClassID() == Class_ID(LININTERP_FLOAT_CLASS_ID,0)) {
                        nkeys += GetKeys<FloatKey, ILinFloatKey>(y, keys, range);
                        data->SetYRotateType(LINEAR_KEY);
                     } else if (y->ClassID() == Class_ID(HYBRIDINTERP_FLOAT_CLASS_ID,0)) {
                        nkeys += GetKeys<FloatKey, IBezFloatKey>(y, keys, range);
                        data->SetYRotateType(QUADRATIC_KEY);
                     } else if (y->ClassID() == Class_ID(TCBINTERP_FLOAT_CLASS_ID,0)) {
                        nkeys += GetKeys<FloatKey, ITCBFloatKey>(y, keys, range);
                        data->SetYRotateType(TBC_KEY);
                     } else {
                        nkeys += GetKeys<FloatKey, IBezFloatKey>(y, keys, range);
                        data->SetYRotateType(QUADRATIC_KEY);
                     }
                     data->SetYRotateKeys(keys);
                  }
                  if (Control *z = c->GetZController()) {
                     vector<FloatKey> keys;
                     if (z->ClassID() == Class_ID(LININTERP_FLOAT_CLASS_ID,0)) {
                        nkeys += GetKeys<FloatKey, ILinFloatKey>(z, keys, range);
                        data->SetZRotateType(LINEAR_KEY);
                     } else if (z->ClassID() == Class_ID(HYBRIDINTERP_FLOAT_CLASS_ID,0)) {
                        nkeys += GetKeys<FloatKey, IBezFloatKey>(z, keys, range);
                        data->SetZRotateType(QUADRATIC_KEY);
                     } else if (z->ClassID() == Class_ID(TCBINTERP_FLOAT_CLASS_ID,0)) {
                        nkeys += GetKeys<FloatKey, ITCBFloatKey>(z, keys, range);
                        data->SetZRotateType(TBC_KEY);
                     } else {
                        nkeys += GetKeys<FloatKey, IBezFloatKey>(z, keys, range);
                        data->SetZRotateType(QUADRATIC_KEY);
                     }
                     data->SetZRotateKeys(keys);
                  }
               } else if (c->ClassID() == Class_ID(TCBINTERP_ROTATION_CLASS_ID,0)) {
                  vector<QuatKey> keys;
                  data->SetRotateType(TBC_KEY);
                  nkeys += GetKeys<QuatKey, ITCBRotKey>(c, keys, range);
                  data->SetQuatRotateKeys(keys);
               }
               if (nkeys != 0) { // if no changes set the base transform
                  keepData = true;
                  //rot = TOQUAT( Quat(tm) );
               }
            }

            // Scale
            if (Control *c = tmCont->GetScaleController()) 
            {
               int nkeys = 0;
               if (c->ClassID() == Class_ID(LININTERP_SCALE_CLASS_ID,0)) {
                  vector<FloatKey> keys;
                  data->SetScaleType(LINEAR_KEY);
                  nkeys += GetKeys<FloatKey, ILinFloatKey>(c, keys, range);
                  data->SetScaleKeys(keys);
               } else if (c->ClassID() == Class_ID(HYBRIDINTERP_SCALE_CLASS_ID,0)) {
                  vector<FloatKey> keys;
                  data->SetScaleType(QUADRATIC_KEY);
                  nkeys += GetKeys<FloatKey, IBezFloatKey>(c, keys, range);
                  data->SetScaleKeys(keys);
               } else if (c->ClassID() == Class_ID(TCBINTERP_SCALE_CLASS_ID,0)) {
                  vector<FloatKey> keys;
                  data->SetScaleType(TBC_KEY);
                  nkeys += GetKeys<FloatKey, ITCBFloatKey>(c, keys, range);
                  data->SetScaleKeys(keys);
               }
               if (nkeys != 0) { // if no changes set the base transform
                  keepData = true;
                  //scale = Average(GetScale(tm));
               }           
            }

            // only add transform data object if data actually is present
            if (!keepData) {
               ninode->RemoveController(timeControl);
               timeControl = NULL;
            } else {
               objRefs.insert( StaticCast<NiAVObject>(ninode) );
            }
         }
      }
   }
   return timeControl;
}

bool AnimationExport::exportController(INode *node, bool hasAccum)
{
   bool ok = true;

   bool keepTM = false;
   if (seq->GetTargetName() == node->GetName()) {
      keepTM = true;
   }

   NiTimeControllerRef control = exportController( node, range, keepTM );
   if (control != NULL)
   {
      NiSingleInterpolatorControllerRef interpControl = DynamicCast<NiSingleInterpolatorController>(control);
      if (interpControl) 
      {
         // Get Priority from node
         float priority;
         npGetProp(node, NP_ANM_PRI, priority, Exporter::mDefaultPriority);
         seq->AddInterpolator(StaticCast<NiSingleInterpolatorController>(control), priority);

         // Handle NonAccum 
         if (Exporter::mAllowAccum && hasAccum)
         {
            NiTransformInterpolatorRef interp = DynamicCast<NiTransformInterpolator>(interpControl->GetInterpolator());
            NiNodeRef accnode = ne.getNode( FormatString("%s NonAccum", node->GetName()) );
            objRefs.insert( StaticCast<NiAVObject>(accnode) );

            if (Exporter::mNifVersionInt >= VER_10_2_0_0)
            {
               NiTransformControllerRef acccontrol = new NiTransformController();
               NiTransformInterpolatorRef accinterp = new NiTransformInterpolator();
               accnode->AddController(StaticCast<NiTimeController>(acccontrol));
               acccontrol->SetInterpolator(StaticCast<NiInterpolator>(accinterp));

               accinterp->SetTranslation( Vector3(0.0f, 0.0f, 0.0f) );
               accinterp->SetScale( 1.0f );
               accinterp->SetRotation( Quaternion(1.0f, 0.0f, 0.0f, 0.0f) );

               // Transfer entire data to accum node
               if (interp != NULL) {
                  accinterp->SetData( interp->GetData() );
                  interp->SetData( NiTransformDataRef() );
               }
               seq->AddInterpolator(StaticCast<NiSingleInterpolatorController>(acccontrol), Exporter::mDefaultPriority);

               accnode->RemoveController(acccontrol);
            }
         }

      }
      else 
      {
         seq->AddController(control);
      }

      NiObjectNETRef target = control->GetTarget();
      // now remove temporary controller
      target->RemoveController(StaticCast<NiTimeController>(control));
   }

   for (int i=0, n=node->NumberOfChildren(); ok && i<n; ++i)
   {
      INode *child = node->GetChildNode(i);
      ok |= exportController(child, false);
   }
   return ok;
}