/* Copyright (c) 2002-2012 Croteam Ltd. 
This program is free software; you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License as published by
the Free Software Foundation


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, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */

#include "stdh.h"
#include <Engine/Ska/ModelInstance.h>
#include <Engine/Ska/Skeleton.h>
#include <Engine/Ska/Render.h>
#include <Engine/Ska/StringTable.h>
#include <Engine/Ska/ParsingSmbs.h>
#include <Engine/Base/ErrorReporting.h>
#include <Engine/Base/Stream.h>
#include <Engine/Base/Console.h>
#include <Engine/Math/Quaternion.h>
#include <Engine/Templates/DynamicStackArray.cpp>
#include <Engine/Templates/DynamicContainer.cpp>
#include <Engine/Templates/Stock_CMesh.h>
#include <Engine/Templates/Stock_CSkeleton.h>
#include <Engine/Templates/Stock_CAnimSet.h>
#include <Engine/Templates/Stock_CTextureData.h>
#include <Engine/Templates/Stock_CShader.h>

// does parser remember smc source files?
BOOL bRememberSourceFN = FALSE;

// pointer to model instance for parser
CModelInstance *_yy_mi = NULL;

// calculate fade factor of animation list in animqueue
FLOAT CalculateFadeFactor(AnimList &alList)
{
  if(alList.al_fFadeTime==0) {
    return 1.0f;
  }

  FLOAT fFadeFactor = (_pTimer->GetLerpedCurrentTick() - alList.al_fStartTime) / alList.al_fFadeTime;
  return Clamp(fFadeFactor,0.0f,1.0f);
}
// create model instance
CModelInstance *CreateModelInstance(CTString strName)
{
  CModelInstance *pmi = new CModelInstance;
  pmi->SetName(strName);
  return pmi;
}
void DeleteModelInstance(CModelInstance *pmi)
{
  ASSERT(pmi!=NULL);
  // if model instance is valid
  if(pmi!=NULL) {
    // Clear model instance
    pmi->Clear();
    // Delete model instance
    delete pmi;
    pmi = NULL;
  }
}

// Parse smc file in existing model instance
void ParseSmcFile_t(CModelInstance &mi, const CTString &fnSmcFile)
{
  // Clear given model instance before parsing
  mi.Clear();

  CTFileName fnFileName = fnSmcFile;
  try {
    fnFileName.RemoveApplicationPath_t();
  } catch (char *) {
  }

  CTString strIncludeFile;
  strIncludeFile.Load_t(fnFileName);

  _yy_mi = &mi;
  SMCPushBuffer(fnFileName, strIncludeFile, TRUE);
  syyparse();
}

// Create model instance and parse smc file in it
CModelInstance *ParseSmcFile_t(const CTString &fnSmcFile)
{
  _yy_mi = NULL;
  // Create new model instance for parser
  _yy_mi = CreateModelInstance("Temp");
  // Parse given smc file
  ParseSmcFile_t(*_yy_mi,fnSmcFile);

  return _yy_mi;
}

CModelInstance::CModelInstance()
{
  mi_psklSkeleton = NULL;
  mi_iParentBoneID = -1;
  mi_colModelColor = 0;
  mi_vStretch = FLOAT3D(1,1,1);
  mi_colModelColor = 0xFFFFFFFF;
  mi_aqAnims.aq_Lists.SetAllocationStep(1);
  mi_cmiChildren.SetAllocationStep(1);
  memset(&mi_qvOffset,0,sizeof(QVect));
  mi_qvOffset.qRot.q_w = 1;
  mi_iCurentBBox = -1;
  // set default all frames bbox
//  mi_cbAllFramesBBox.SetName("All Frames Bounding box");
  mi_cbAllFramesBBox.SetMin(FLOAT3D(-0.5,0,-0.5));
  mi_cbAllFramesBBox.SetMax(FLOAT3D(0.5,2,0.5));
  // Set default model instance name
//  SetName("Noname");
}

CModelInstance::~CModelInstance()
{
}
// copy constructor
CModelInstance::CModelInstance(CModelInstance &miOther)
{
  // Forbiden
  ASSERT(FALSE);
}
void CModelInstance::operator=(CModelInstance &miOther)
{
  // Forbiden
  ASSERT(FALSE);
}

void CModelInstance::GetAllFramesBBox(FLOATaabbox3D &aabbox)
{
  aabbox = FLOATaabbox3D(mi_cbAllFramesBBox.Min(),mi_cbAllFramesBBox.Max());
}

// fills curent colision box info
void CModelInstance::GetCurrentColisionBox(FLOATaabbox3D &aabbox)
{
  ColisionBox &cb = GetCurrentColisionBox();
  aabbox = FLOATaabbox3D(cb.Min(),cb.Max());
}

ColisionBox &CModelInstance::GetCurrentColisionBox()
{
  ASSERT(mi_iCurentBBox>=0);
  ASSERT(mi_iCurentBBox<mi_cbAABox.Count());
  ASSERT(mi_cbAABox.Count()>0);

  return mi_cbAABox[mi_iCurentBBox];
}


INDEX CModelInstance::GetColisionBoxIndex(INDEX iBoxID)
{
  INDEX ctcb = mi_cbAABox.Count();
  // for each existing box
  for(INT icb=0;icb<ctcb;icb++) {
    ColisionBox &cb = mi_cbAABox[icb];
    // if this is searched box
    if(cb.GetID() == iBoxID) {
      // return index of box
      return icb;
    }
  }
  // colision box was not found, return default (0)
  SKAASSERT(FALSE);
  return 0;
}

ColisionBox &CModelInstance::GetColisionBox(INDEX icb)
{
  ASSERT(icb>=0);
  ASSERT(icb<mi_cbAABox.Count());
  return mi_cbAABox[icb];
}

FLOAT3D CModelInstance::GetCollisionBoxMin(INDEX iCollisionBox/*=0*/)
{
  INDEX iCollisionBoxClamped = Clamp(iCollisionBox, 0L, mi_cbAABox.Count()-1L);
  FLOAT3D vMin = mi_cbAABox[ iCollisionBoxClamped].Min();
  return vMin;
};

FLOAT3D CModelInstance::GetCollisionBoxMax(INDEX iCollisionBox/*=0*/)
{
  INDEX iCollisionBoxClamped = Clamp(iCollisionBox, 0L, mi_cbAABox.Count()-1L);
  FLOAT3D vMax = mi_cbAABox[ iCollisionBoxClamped].Max();
  return vMax;
};

// returns HEIGHT_EQ_WIDTH, LENGHT_EQ_WIDTH or LENGHT_EQ_HEIGHT
INDEX CModelInstance::GetCollisionBoxDimensionEquality(INDEX iCollisionBox/*=0*/)
{
  // if colision box does not exists
  if(iCollisionBox>=mi_cbAABox.Count()) {
    // give last colision box
    iCollisionBox = mi_cbAABox.Count()-1;
  }
  // check if error is fixed
  ASSERT(mi_cbAABox.Count()>iCollisionBox);

  ColisionBox &cb = this->mi_cbAABox[iCollisionBox];
  FLOAT fWeigth = cb.Max()(1) - cb.Min()(1);
  FLOAT fHeight = cb.Max()(2) - cb.Min()(2);
  FLOAT fLength = cb.Max()(3) - cb.Min()(3);
  if(fLength == fHeight) {
    return SKA_LENGTH_EQ_HEIGHT;
  } else if(fHeight == fWeigth) {
    return SKA_HEIGHT_EQ_WIDTH;
  // default fLength == fWeight
  } else {
    return SKA_LENGTH_EQ_WIDTH;
  }
};

// add colision box to model instance
void CModelInstance::AddColisionBox(CTString strName,FLOAT3D vMin,FLOAT3D vMax)
{
  INDEX ctcb = mi_cbAABox.Count();
  mi_cbAABox.Expand(ctcb+1);
  
  ColisionBox &cb = mi_cbAABox[ctcb];
  cb.SetName(strName);
  cb.SetMin(vMin);
  cb.SetMax(vMax);
  mi_iCurentBBox = 0;
}
// remove colision box from model instance
void CModelInstance::RemoveColisionBox(INDEX iIndex)
{
  INDEX ctcb = mi_cbAABox.Count();
  INDEX icbNew = 0;
  CStaticArray<struct ColisionBox> aColisionBoxesTemp;
  aColisionBoxesTemp.New(ctcb-1);
  for(INDEX icb=0;icb<ctcb;icb++) {
    if(iIndex != icb) { 
      aColisionBoxesTemp[icbNew] = mi_cbAABox[icb];
      icbNew++;
    }
  }
  mi_cbAABox = aColisionBoxesTemp;
}

// add child to modelinstance
void CModelInstance::AddChild(CModelInstance *pmi, INDEX iParentBoneID /* = -1 */)
{
  SKAASSERT(pmi!=NULL);
  if(pmi==NULL) return;
  mi_cmiChildren.Add(pmi);
  if (iParentBoneID>0) {
    pmi->SetParentBone(iParentBoneID);
  }
}

// remove model instance child
void CModelInstance::RemoveChild(CModelInstance *pmi)
{
  ASSERT(pmi!=this);
  SKAASSERT(pmi!=NULL);
  // aditional check
  if(pmi==NULL) return;
  if(pmi==this) return;

  mi_cmiChildren.Remove(pmi);
}
// set new parent bone index
void CModelInstance::SetParentBone(INDEX iParentBoneID)
{
  mi_iParentBoneID = iParentBoneID;
}

// Model instance offsets from parent model
void CModelInstance::SetOffset(FLOAT fOffset[6])
{
  FLOAT3D fRot(fOffset[3],fOffset[4],fOffset[5]);
  mi_qvOffset.qRot.FromEuler(fRot);
  mi_qvOffset.vPos = FLOAT3D(fOffset[0],fOffset[1],fOffset[2]);
}

void CModelInstance::SetOffsetPos(FLOAT3D vPos)
{
  mi_qvOffset.vPos = vPos;
}

void CModelInstance::SetOffsetRot(ANGLE3D aRot)
{
  mi_qvOffset.qRot.FromEuler(aRot);
}

FLOAT3D CModelInstance::GetOffsetPos()
{
  return mi_qvOffset.vPos;
}

ANGLE3D CModelInstance::GetOffsetRot()
{
  ANGLE3D aRot;
  FLOATmatrix3D mat;
  mi_qvOffset.qRot.ToMatrix(mat);
  DecomposeRotationMatrix(aRot,mat);
  return aRot;
}

// Stretch model instance
void CModelInstance::StretchModel(FLOAT3D &vStretch)
{
  mi_vStretch = vStretch;
}

// Stretch model instance without attachments
void CModelInstance::StretchSingleModel(FLOAT3D &vStretch)
{
  mi_vStretch = vStretch;
  // for each child of model instance
  INDEX ctch = mi_cmiChildren.Count();
  for(INDEX ich=0;ich<ctch;ich++) {
    // set new stretch of model instance
    CModelInstance &chmi = mi_cmiChildren[ich];
    chmi.StretchSingleModel(FLOAT3D(1/vStretch(1),1/vStretch(2),1/vStretch(3)));
  }
}

// Add mesh to ModelInstance
void CModelInstance::AddMesh_t(CTFileName fnMesh)
{
  int ctMeshInst = mi_aMeshInst.Count();
  mi_aMeshInst.Expand(ctMeshInst+1);
  memset(&mi_aMeshInst[ctMeshInst],0,sizeof(mi_aMeshInst[ctMeshInst]));
  mi_aMeshInst[ctMeshInst].mi_pMesh = _pMeshStock->Obtain_t(fnMesh);
}

// Add skeleton to ModelInstance
void CModelInstance::AddSkeleton_t(CTFileName fnSkeleton)
{
  mi_psklSkeleton = _pSkeletonStock->Obtain_t(fnSkeleton);
}

// Add AnimSet to ModelInstance
void CModelInstance::AddAnimSet_t(CTFileName fnAnimSet)
{
  CAnimSet *Anim = _pAnimSetStock->Obtain_t(fnAnimSet);
  mi_aAnimSet.Add(Anim);
}

// Add texture to ModelInstance (if no mesh instance given, add texture to last mesh instance)
void CModelInstance::AddTexture_t(CTFileName fnTexture, CTString strTexID,MeshInstance *pmshi)
{
  if(pmshi == NULL) {
    INDEX ctMeshInst = mi_aMeshInst.Count();
    if(ctMeshInst<=0) throw("Error adding texture\nMesh instance does not exists");
    pmshi = &mi_aMeshInst[ctMeshInst-1];
  }

  INDEX ctTextInst = pmshi->mi_tiTextures.Count();
  pmshi->mi_tiTextures.Expand(ctTextInst+1);
  pmshi->mi_tiTextures[ctTextInst].ti_toTexture.SetData_t(fnTexture);
  pmshi->mi_tiTextures[ctTextInst].SetName(strTexID);
}

// Remove one texture from model instance
void CModelInstance::RemoveTexture(TextureInstance *ptiRemove,MeshInstance *pmshi)
{
  ASSERT(pmshi!=NULL);
  CStaticArray<struct TextureInstance> atiTextures;
  INDEX ctti=pmshi->mi_tiTextures.Count();
  atiTextures.New(ctti-1);
  // for each texture instance in mesh instance
  INDEX iIndexSrc=0;
  for(INDEX iti=0;iti<ctti;iti++)
  {
    TextureInstance *pti = &pmshi->mi_tiTextures[iti];
    // if texture instance is different from selected one 
    if(pti != ptiRemove) {
      // copy it to new array of texture isntances
      atiTextures[iIndexSrc] = pmshi->mi_tiTextures[iti];
      iIndexSrc++;
    }
  }
  // copy new texture instances array in mesh instance
  pmshi->mi_tiTextures.CopyArray(atiTextures);
  // clear temp texture isntances array
  atiTextures.Clear();
}

// Find texture instance in all mesh instances in model instance
TextureInstance *CModelInstance::FindTexureInstance(INDEX iTexID)
{
  // for each mesh instance
  INDEX ctmshi = mi_aMeshInst.Count();
  for(INDEX imshi=0;imshi<ctmshi;imshi++) {
    MeshInstance &mshi = mi_aMeshInst[imshi];
    // for each texture instance in meshinstance
    INDEX ctti = mshi.mi_tiTextures.Count();
    for(INDEX iti=0;iti<ctti;iti++) {
      TextureInstance &ti = mshi.mi_tiTextures[iti];
      // if this is texinstance that is beeing serched for
      if(ti.GetID() == iTexID) {
        // return it
        return &ti;
      }
    }
  }
  // texture instance wasn't found
  return NULL;
}

// Find texture instance in given mesh instance
TextureInstance *CModelInstance::FindTexureInstance(INDEX iTexID, MeshInstance &mshi)
{
  // for each texture instance in given mesh instance
  INDEX ctti = mshi.mi_tiTextures.Count();
  for(INDEX iti=0;iti<ctti;iti++) {
    TextureInstance &ti = mshi.mi_tiTextures[iti];
    // if this is texinstance that is beeing serched for
    if(ti.GetID() == iTexID) {
      // return it
      return &ti;
    }
  }
  // texture instance wasn't found
  return NULL;
}

// change parent of model instance
void CModelInstance::ChangeParent(CModelInstance *pmiOldParent, CModelInstance *pmiNewParent)
{
  SKAASSERT(pmiOldParent!=NULL);
  SKAASSERT(pmiNewParent!=NULL);

  if(pmiOldParent == NULL) {
    CPrintF("Model Instance doesn't have a parent\n");
    return;
  }
  if(pmiNewParent == NULL) {
    CPrintF("New parent of model instance is NULL\n");
    return;
  }
  pmiOldParent->mi_cmiChildren.Remove(this);
  pmiNewParent->mi_cmiChildren.Add(this);
}

// return parent of this model instance
// must suply first model instance in hierarchy cos model instance does not have its parent remembered
CModelInstance *CModelInstance::GetParent(CModelInstance *pmiStartFrom)
{
  ASSERT(pmiStartFrom!=NULL);
  // aditional check
  if(pmiStartFrom==NULL) return NULL;
  // if 'this' is member of pmiStartFrom return it
  if(pmiStartFrom->mi_cmiChildren.IsMember(this)) {
    return pmiStartFrom;
  }
  // count childrent of pmiStartFrom
  INDEX ctcmi = pmiStartFrom->mi_cmiChildren.Count();
  // for each child of pmiStartFrom
  for(INDEX icmi=0;icmi<ctcmi;icmi++) {
    // if some of children have 'this' as member return them as parent
    CModelInstance *pmiReturned = GetParent(&pmiStartFrom->mi_cmiChildren[icmi]);
    if(pmiReturned != NULL) {
      return pmiReturned;
    }
  }
  return NULL;
}

// returns child with specified id
/*CModelInstance *CModelInstance::GetChild(INDEX iChildID, BOOL bRecursive)
{
  INDEX ctcmi = mi_cmiChildren.Count();
  for(INDEX icmi=0;icmi<ctcmi;icmi++) {
    CModelInstance *pmi = &mi_cmiChildren[icmi];
    if(pmi->mi_iModelID == iChildID) {
      return pmi;
    }
  }
  return NULL;
}*/
CModelInstance *CModelInstance::GetChild(INDEX iChildID, BOOL bRecursive/*=FALSE*/)
{
  INDEX ctcmi = mi_cmiChildren.Count();
  for(INDEX icmi=0;icmi<ctcmi;icmi++) {
    CModelInstance *pmi = &mi_cmiChildren[icmi];
    if(pmi->mi_iModelID == iChildID) {
      return pmi;
    }
    // if child has own children, go recursive
    if(bRecursive && pmi->mi_cmiChildren.Count()>0) {      
      pmi = pmi->GetChild(iChildID, TRUE);
      if (pmi!=NULL) return pmi;
    }    
  }
  return NULL;
}

// returns parent that is not included in his parents smc file
CModelInstance *CModelInstance::GetFirstNonReferencedParent(CModelInstance *pmiRoot)
{
  ASSERT(this!=NULL);
  ASSERT(pmiRoot!=NULL);
  CModelInstance *pmiParent = this->GetParent(pmiRoot);
  CModelInstance *pmiLast = this;
  while(pmiParent != NULL)
  {
    if(pmiParent->mi_fnSourceFile != mi_fnSourceFile)
    {
      return pmiLast;
    }
    pmiLast = pmiParent;
    pmiParent = pmiParent->GetParent(pmiRoot);
  }
  return NULL;//return pmiRoot
}

// add animation to ModelInstance
void CModelInstance::AddAnimation(INDEX iAnimID, ULONG ulFlags, FLOAT fStrength, INDEX iGroupID, FLOAT fSpeedMul/*=1.0f*/)
{

#ifdef SKADEBUG
// see whether this animation even exists in the current skeleton
INDEX iDummy1, iDummy2;
if (!FindAnimationByID(iAnimID, &iDummy1, &iDummy2)) { 
  /*this ModelInstance does not contain the required animation!!!*/
  SKAASSERT(FALSE);
}
#endif

  fSpeedMul = 1/fSpeedMul;
  // if no restart flag was set
  if(ulFlags&AN_NORESTART) {
    // if given animtion is allready playing
    if(IsAnimationPlaying(iAnimID)) {
      if(ulFlags&AN_LOOPING) {
        AddFlagsToPlayingAnim(iAnimID,AN_LOOPING);
      }
      // return without adding animtion
      return;
    }
  }

  // if flag for new cleared state is set
  if(ulFlags&AN_CLEAR) {
    // do new clear state with default length
    NewClearState(CLEAR_STATE_LENGTH);
  // if flag for new cloned state is set
  } else if(ulFlags&AN_CLONE) {
    // do new clear state with default length
    NewClonedState(CLONED_STATE_LENGTH);
  }

  // if anim queue is empty 
  INDEX ctal = mi_aqAnims.aq_Lists.Count();
  if(ctal == 0) {
    // add new clear state
    NewClearState(0);
  }

  ctal = mi_aqAnims.aq_Lists.Count();
  AnimList &alList = mi_aqAnims.aq_Lists[ctal-1];

  // if flag is set not to sort anims
  if(ulFlags&AN_NOGROUP_SORT) {
    // just add new animations to end of list
    PlayedAnim &plAnim = alList.al_PlayedAnims.Push();
    plAnim.pa_iAnimID = iAnimID;
    plAnim.pa_fSpeedMul = fSpeedMul;
    plAnim.pa_fStartTime = _pTimer->CurrentTick();
    plAnim.pa_Strength = fStrength;
    plAnim.pa_ulFlags = ulFlags;
    plAnim.pa_GroupID = iGroupID;
  // no flag set, sort animation by groupID
  } else {
    // add one animation to anim list
    alList.al_PlayedAnims.Push();
    INDEX ctpa = alList.al_PlayedAnims.Count();

    INDEX ipa=ctpa-1;
    if(ipa>0) {
      // for each old animation from last to first
      for(;ipa>0;ipa--) {
        PlayedAnim &pa = alList.al_PlayedAnims[ipa-1];
        PlayedAnim &paNext = alList.al_PlayedAnims[ipa];
        // if anim group id is larger than new group id
        if(pa.pa_GroupID>iGroupID) {
          // move animation in array to right
          paNext = pa;
        } else break;
      }
    }
    // set new animation as current index in anim list
    PlayedAnim &plAnim = alList.al_PlayedAnims[ipa];
    plAnim.pa_iAnimID = iAnimID;
    plAnim.pa_fSpeedMul = fSpeedMul;
    plAnim.pa_fStartTime = _pTimer->CurrentTick();
    plAnim.pa_Strength = fStrength;
    plAnim.pa_ulFlags = ulFlags;
    plAnim.pa_GroupID = iGroupID;
  }
}

// remove played anim from stack
void CModelInstance::RemAnimation(INDEX iAnimID)
{
  INDEX ctal = mi_aqAnims.aq_Lists.Count();
  // if anim queue is empty
  if(ctal < 1) {
    SKAASSERT(FALSE);
    // no anim to remove
    return;
  }

  // get last anim list in queue
  AnimList &alList = mi_aqAnims.aq_Lists[ctal-1];
  // count played anims in anim list
  INDEX ctpa = alList.al_PlayedAnims.Count();
  // loop each played anim in anim list
  for(int ipa=0;ipa<ctpa;ipa++) {
    PlayedAnim &paAnim = alList.al_PlayedAnims[ipa];
    // remove if same ID
    if(paAnim.pa_iAnimID == iAnimID) {
      // copy all latter anims over this one
      for(int ira=ipa;ira<ctpa-1;ira++) {
        alList.al_PlayedAnims[ira] = alList.al_PlayedAnims[ira+1];
      }
      // decrease played anims count
      ctpa--;
      // remove last anim
      alList.al_PlayedAnims.Pop();
    }
  }
}

// Remove all anims with GroupID
void CModelInstance::RemAnimsWithID(INDEX iGroupID)
{
  INDEX ctal = mi_aqAnims.aq_Lists.Count();
  // if anim queue is empty
  if(ctal < 1) {
    SKAASSERT(FALSE);
    // no anim to remove
    return;
  }

  // get last anim list in queue
  AnimList &alList = mi_aqAnims.aq_Lists[ctal-1];
  // count played anims in anim list
  INDEX ctpa = alList.al_PlayedAnims.Count();
  // loop each played anim in anim list
  for(int ipa=0;ipa<ctpa;ipa++) {
    PlayedAnim &paAnim = alList.al_PlayedAnims[ipa];
    // remove if same Group ID
    if(paAnim.pa_GroupID == iGroupID) {
      // copy all latter anims over this one
      for(int ira=ipa;ira<ctpa-1;ira++) {
        alList.al_PlayedAnims[ira] = alList.al_PlayedAnims[ira+1];
      }
      // decrease played anims count
      ctpa--;
      // remove last anim
      alList.al_PlayedAnims.Pop();
    }
  }
}

// remove unused anims from queue
void CModelInstance::RemovePassedAnimsFromQueue()
{
  // count AnimLists
  INDEX ctal = mi_aqAnims.aq_Lists.Count();
  // find newes animlist that has fully faded in
  INDEX iFirstAnimList = -1;
  // for each anim list from last to first
  INDEX ial=ctal-1;
  for(;ial>=0;ial--)
  {
    AnimList &alList = mi_aqAnims.aq_Lists[ial];
    // calculate fade factor for this animlist
    FLOAT fFadeFactor = CalculateFadeFactor(alList);
    // if factor is 1 remove all animlists before this one
    if(fFadeFactor >= 1.0f) {
      iFirstAnimList = ial;
      break;
    }
  }
  if(iFirstAnimList <= 0) return;
  // move later anim lists to first pos
  for(ial=iFirstAnimList;ial<ctal;ial++)
  {
    mi_aqAnims.aq_Lists[ial-iFirstAnimList] = mi_aqAnims.aq_Lists[ial];
    mi_aqAnims.aq_Lists[ial].al_PlayedAnims.PopAll();
  }
  // remove all Anim list before iFirstAnimList
  mi_aqAnims.aq_Lists.PopUntil(ctal-iFirstAnimList-1);
}

// create new state, copy last state in it and give it a fade time
void CModelInstance::NewClonedState(FLOAT fFadeTime)
{
  RemovePassedAnimsFromQueue();
  INDEX ctal = mi_aqAnims.aq_Lists.Count();
  if(ctal == 0) 
  {
  // if anim queue is empty add new clear state
    NewClearState(fFadeTime);
    ctal = 1;
  }
  // create new Anim list
  AnimList &alNewList = mi_aqAnims.aq_Lists.Push();
  alNewList.al_PlayedAnims.SetAllocationStep(1);
  AnimList &alList = mi_aqAnims.aq_Lists[ctal-1];
  // copy anims to new List
  alNewList.al_PlayedAnims = alList.al_PlayedAnims;
  alNewList.al_fFadeTime = fFadeTime;
  alNewList.al_fStartTime = _pTimer->CurrentTick();
}

// create new cleared state and give it a fade time
void CModelInstance::NewClearState(FLOAT fFadeTime)
{
  RemovePassedAnimsFromQueue();
  // add new empty list
  AnimList &alNewList = mi_aqAnims.aq_Lists.Push();
  alNewList.al_PlayedAnims.SetAllocationStep(1);
  alNewList.al_fFadeTime = fFadeTime;
  alNewList.al_fStartTime = _pTimer->CurrentTick();
  alNewList.al_PlayedAnims.PopAll();
}

// stop all animations in this model instance and its children
void CModelInstance::StopAllAnimations(FLOAT fFadeTime)
{
  INDEX ctmi = mi_cmiChildren.Count();
  for(INDEX imi=0;imi<ctmi;imi++)
  {
    CModelInstance &cmi = mi_cmiChildren[imi];
    cmi.StopAllAnimations(fFadeTime);
  }
  NewClearState(fFadeTime);
}

// Offset all animations in anim queue
void CModelInstance::OffSetAnimationQueue(TIME fOffsetTime)
{
  // for each anim list in anim queue
  INDEX ctal = mi_aqAnims.aq_Lists.Count();
  for(INDEX ial=0;ial<ctal;ial++) {
    AnimList &al = mi_aqAnims.aq_Lists[ial];
    // Modify anim list start time
    al.al_fStartTime +=fOffsetTime;
  }
}

// Find animation by ID
BOOL CModelInstance::FindAnimationByID(int iAnimID,INDEX *piAnimSetIndex,INDEX *piAnimIndex)
{
  INDEX ctas = mi_aAnimSet.Count();
  if (ctas<=0) return FALSE;
  // for each animset
  for(int ias=ctas-1;ias>=0;ias--) {
    CAnimSet &asAnimSet = mi_aAnimSet[ias];
    INDEX ctan = asAnimSet.as_Anims.Count();
    // for each animation
    for(int ian=0;ian<ctan;ian++) {
      Animation &an = asAnimSet.as_Anims[ian];
      // if this is animation to find
      if(an.an_iID == iAnimID) {
        // set pointers of indices to animset and animation
        *piAnimSetIndex = ias;
        *piAnimIndex = ian;
        // retrun succesfully
        return TRUE;
      }
    }
  }
  // animation was't found
  return FALSE;
}

// Find animation by ID
INDEX CModelInstance::FindFirstAnimationID()
{
  INDEX ctas = mi_aAnimSet.Count();
  // for each animset
  for(int ias=0; ias<ctas; ias--) {
    CAnimSet &asAnimSet = mi_aAnimSet[ias];
    INDEX ctan = asAnimSet.as_Anims.Count();
    // for each animation
    for(int ian=0;ian<ctan;ian++) {
      Animation &an = asAnimSet.as_Anims[ian];
      return an.an_iID;      
    }
  }
  // never should get here
  return -1;
}

// get animation length
FLOAT CModelInstance::GetAnimLength(INDEX iAnimID)
{
  INDEX iAnimSetIndex,iAnimIndex;
  FindAnimationByID(iAnimID,&iAnimSetIndex,&iAnimIndex);
  CAnimSet &as = mi_aAnimSet[iAnimSetIndex];
  Animation &an = as.as_Anims[iAnimIndex];
  return an.an_fSecPerFrame * an.an_iFrames;
}

// Check if given animation is currently playing
BOOL CModelInstance::IsAnimationPlaying(INDEX iAnimID)
{
  // check last anim list if animation iAnimID exists in it
  INDEX ctal = mi_aqAnims.aq_Lists.Count();
  // if there are anim lists in animqueue
  if(ctal>0) {
    // check last one
    AnimList &al = mi_aqAnims.aq_Lists[ctal-1];
    INDEX ctpa = al.al_PlayedAnims.Count();
    for(INDEX ipa=0;ipa<ctpa;ipa++) {
      PlayedAnim &pa = al.al_PlayedAnims[ipa];
      if(pa.pa_iAnimID == iAnimID) {
        // found it
        return TRUE;
      }
    }
  }
  // this animation is currently not playing
  return FALSE;
}

// Add flags to animation playing in anim queue
BOOL CModelInstance::AddFlagsToPlayingAnim(INDEX iAnimID, ULONG ulFlags)
{
  // check last anim list if animation iAnimID exists in it
  INDEX ctal = mi_aqAnims.aq_Lists.Count();
  // if there are anim lists in animqueue
  if(ctal>0) {
    // check last one
    AnimList &al = mi_aqAnims.aq_Lists[ctal-1];
    INDEX ctpa = al.al_PlayedAnims.Count();
    for(INDEX ipa=0;ipa<ctpa;ipa++) {
      PlayedAnim &pa = al.al_PlayedAnims[ipa];
      if(pa.pa_iAnimID == iAnimID) {
        pa.pa_ulFlags |= ulFlags;
        // found it
        return TRUE;
      }
    }
  }
  // this animation is currently not playing
  return FALSE;
}

// Sets name of model instance
void CModelInstance::SetName(CTString strName)
{
  mi_strName = strName;
  mi_iModelID = ska_GetIDFromStringTable(strName);
}
// Gets name of model instance
const CTString &CModelInstance::GetName()
{
  return mi_strName;
}
// Gets id of model instance
const INDEX &CModelInstance::GetID()
{
  return mi_iModelID;
}

// Get vertex positions in absolute space
void CModelInstance::GetModelVertices( CStaticStackArray<FLOAT3D> &avVertices, FLOATmatrix3D &mRotation,
                                     FLOAT3D &vPosition, FLOAT fNormalOffset, FLOAT fDistance)
{
  RM_SetObjectPlacement(mRotation,vPosition);
  RM_GetModelVertices(*this,avVertices, mRotation, vPosition, fNormalOffset, fDistance);
}

// Model color
COLOR &CModelInstance::GetModelColor(void)
{
  return mi_colModelColor;
}
void CModelInstance::SetModelColor(COLOR colNewColor)
{
  mi_colModelColor = colNewColor;
  INDEX ctch = mi_cmiChildren.Count();
  // for each child
  for(INDEX ich=0;ich<ctch;ich++) {
    CModelInstance &cmi = mi_cmiChildren[ich];
    // set child model color 
    cmi.SetModelColor(colNewColor);
  }
}
// test it the model has alpha blending
BOOL CModelInstance::HasAlpha(void)
{
  return (GetModelColor()&0xFF)!=0xFF;
}

BOOL CModelInstance::IsModelVisible( FLOAT fMipFactor)
{
  #pragma message(">> IsModelVisible")
  return TRUE;
}

BOOL CModelInstance::HasShadow(FLOAT fMipFactor)
{
  #pragma message(">> HasShadow")
  return TRUE;
}

// simple shadow rendering
void CModelInstance::AddSimpleShadow( const FLOAT fIntensity, const FLOATplane3D &plShadowPlane)
{

  // if shadows are not rendered for current mip, model is half/full face-forward,
  // intensitiy is too low or projection is not perspective - do nothing!
  //if( !HasShadow(1) || fIntensity<0.01f || !_aprProjection.IsPerspective()
  // || (rm.rm_pmdModelData->md_Flags&(MF_FACE_FORWARD|MF_HALF_FACE_FORWARD))) return;
  // ASSERT( _iRenderingType==1);
  ASSERT( fIntensity>0 && fIntensity<=1);
  // do some rendering
  // _pfModelProfile.StartTimer( CModelProfile::PTI_RENDERSIMPLESHADOW);
  // _pfModelProfile.IncrementTimerAveragingCounter( CModelProfile::PTI_RENDERSIMPLESHADOW);
  // _sfStats.IncrementCounter( CStatForm::SCI_MODELSHADOWS);
  // calculate projection model bounding box in object space (if needed)
  // CalculateBoundingBox( this, rm);
  // add one simple shadow to batch list
  RM_SetObjectMatrices(*this);
  RM_AddSimpleShadow_View(*this, fIntensity, plShadowPlane);
  // all done
  // _pfModelProfile.StopTimer( CModelProfile::PTI_RENDERSIMPLESHADOW);

}

// Copy mesh instance for other model instance
void CModelInstance::CopyMeshInstance(CModelInstance &miOther)
{
  INDEX ctmshi = miOther.mi_aMeshInst.Count();
  // for each mesh insntace
  for(INDEX imshi=0;imshi<ctmshi;imshi++) {
    MeshInstance &mshiOther = miOther.mi_aMeshInst[imshi];
    // add its mesh
    AddMesh_t(mshiOther.mi_pMesh->GetName());
    MeshInstance *pmshi = &mi_aMeshInst[imshi];

    INDEX ctti = mshiOther.mi_tiTextures.Count();
    // for each texture in mesh instance
    for(INDEX iti=0;iti<ctti;iti++) {
      TextureInstance &tiOther = mshiOther.mi_tiTextures[iti];
      CTString strTexID = ska_GetStringFromTable(tiOther.GetID());
      // CTextureData *ptd = (CTextureData*)tiOther.ti_toTexture.GetData();
      AddTexture_t(tiOther.ti_toTexture.GetName(),strTexID,pmshi);
    }
  }
}

// copy from another object of same class
void CModelInstance::Copy(CModelInstance &miOther)
{
  // clear this instance - otherwise it won't work
  Clear();

  mi_aqAnims.aq_Lists = miOther.mi_aqAnims.aq_Lists;
  mi_iCurentBBox  = miOther.mi_iCurentBBox;
  mi_colModelColor = miOther.mi_colModelColor;
  mi_iParentBoneID = miOther.mi_iParentBoneID;
  mi_qvOffset = miOther.mi_qvOffset;
  mi_strName = miOther.mi_strName;
  mi_iModelID = miOther.mi_iModelID;
  mi_cbAABox = miOther.mi_cbAABox;
  mi_fnSourceFile = miOther.mi_fnSourceFile;
  mi_vStretch = miOther.mi_vStretch;

  // copt mesh instance
  CopyMeshInstance(miOther);

  // if skeleton exists 
  if(miOther.mi_psklSkeleton!=NULL) {
    // copy skeleton
    AddSkeleton_t(miOther.mi_psklSkeleton->GetName());
  }

  // copy animsets
  INDEX ctas = miOther.mi_aAnimSet.Count();
  // for each animset
  for(INDEX ias=0;ias<ctas;ias++) {
    // add animset to this model instance
    CAnimSet &asOther = miOther.mi_aAnimSet[ias];
    AddAnimSet_t(asOther.GetName());
  }

  // copy children
  INDEX ctch = miOther.mi_cmiChildren.Count();
  // for each child in other model instance
  for(INDEX ich=0;ich<ctch;ich++) {
    CModelInstance &chmiOther = miOther.mi_cmiChildren[ich];
    CModelInstance *pchmi = CreateModelInstance("Temp");
    pchmi->Copy(chmiOther);
    AddChild(pchmi);
  }
}

// Synchronize with another model (copy animations/attachments positions etc from there)
void CModelInstance::Synchronize(CModelInstance &miOther)
{
  // Sync animations
  mi_aqAnims.aq_Lists = miOther.mi_aqAnims.aq_Lists;
  // Sync misc params
  mi_qvOffset      = miOther.mi_qvOffset;
  mi_iParentBoneID = miOther.mi_iParentBoneID;
  mi_colModelColor = miOther.mi_colModelColor;
  mi_vStretch      = miOther.mi_vStretch;

  // for each child in model instance
  INDEX ctchmi=mi_cmiChildren.Count();
  for(INDEX ichmi=0;ichmi<ctchmi;ichmi++) {
    CModelInstance &chmi = mi_cmiChildren[ichmi];
    CModelInstance *pchmiOther = miOther.GetChild(chmi.GetID(),FALSE);
    // if both model instance have this child 
    if(pchmiOther!=NULL) {
      // sync child
      chmi.Synchronize(*pchmiOther);
    }
  }
}

// clear model instance
void CModelInstance::Clear(void)
{
  // for each child of this model instance
  INDEX ctcmi = mi_cmiChildren.Count();
  for(INDEX icmi=0; icmi<ctcmi; icmi++) {
    // delete child
    CModelInstance *pcmi = &mi_cmiChildren[0];
    mi_cmiChildren.Remove(pcmi);
    DeleteModelInstance(pcmi);
  }

  // release all meshes in stock used by mi
  INDEX ctmshi = mi_aMeshInst.Count();
  for(INDEX imshi=0; imshi<ctmshi; imshi++) {
    MeshInstance &mshi = mi_aMeshInst[imshi];
    CMesh *pmesh = mshi.mi_pMesh;
    if(pmesh != NULL) {
      _pMeshStock->Release(pmesh);
    }
    // release all textures in stock used by mesh
    INDEX ctti = mshi.mi_tiTextures.Count();
    for(INDEX iti=0;iti<ctti;iti++) {
      TextureInstance &ti = mshi.mi_tiTextures[iti];
      ti.ti_toTexture.SetData(NULL);
    }
  }
  mi_aMeshInst.Clear();
  // release skeleton in stock used by mi(if it exist)
  if(mi_psklSkeleton != NULL) {
    _pSkeletonStock->Release(mi_psklSkeleton);
    mi_psklSkeleton = NULL;
  }

  // release all animsets in stock used by mi
  INDEX ctas = mi_aAnimSet.Count();
  for(INDEX ias=0;ias<ctas;ias++) {
    _pAnimSetStock->Release(&mi_aAnimSet[ias]);  
  }
  mi_aAnimSet.Clear();

  // clear all colision boxes 
  mi_cbAABox.Clear();
  // clear anim list
  mi_aqAnims.aq_Lists.Clear();
}

// Count used memory
SLONG CModelInstance::GetUsedMemory(void)
{
  SLONG slMemoryUsed = sizeof(*this);
  // Count mesh instances
  INDEX ctmshi = mi_aMeshInst.Count();
  for(INDEX imshi=0;imshi<ctmshi;imshi++) {
    MeshInstance &mshi = mi_aMeshInst[imshi];
    slMemoryUsed += mshi.mi_tiTextures.Count() * sizeof(TextureInstance);
  }
  slMemoryUsed += mi_aMeshInst.Count() * sizeof(MeshInstance);
  // Count bounding boxes
  slMemoryUsed += mi_cbAABox.Count() * sizeof(ColisionBox);
  // Cound child model instances
  INDEX ctcmi = mi_cmiChildren.Count();
  for(INDEX icmi=0;icmi<ctcmi;icmi++) {
    CModelInstance &cmi = mi_cmiChildren[icmi];
    slMemoryUsed += cmi.GetUsedMemory();
  }
  return slMemoryUsed;
}

// set parser to remember file names for modelinstances
void CModelInstance::EnableSrcRememberFN(BOOL bEnable)
{
  bRememberSourceFN = bEnable;
}
