//****************************************************************************//
// morphtargetmixer.cpp                                                       //
// Copyright (C) 2001, 2002 Bruno 'Beosil' Heidelberger                       //
//****************************************************************************//
// This library is free software; you can redistribute it and/or modify it    //
// under the terms of the GNU Lesser General Public License as published by   //
// the Free Software Foundation; either version 2.1 of the License, or (at    //
// your option) any later version.                                            //
//****************************************************************************//

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

//****************************************************************************//
// Includes                                                                   //
//****************************************************************************//

#include "cal3d/error.h"
#include "cal3d/morphtargetmixer.h"
#include "cal3d/model.h"
#include "cal3d/mesh.h"
#include "cal3d/submesh.h"
#include "cal3d/coremorphanimation.h"
#include "cal3d/coremodel.h"
#include "cal3d/coremesh.h"
#include "cal3d/coresubmesh.h"

 /*****************************************************************************/
/** Constructs the morph target mixer instance.
  *
  * This function is the default constructor of the morph target mixer instance.
  *****************************************************************************/

CalMorphTargetMixer::CalMorphTargetMixer ():m_pModel (0)
{
}

 /*****************************************************************************/
/** Destructs the morph target mixer instance.
  *
  * This function is the destructor of the morph target mixer instance.
  *****************************************************************************/

CalMorphTargetMixer::~CalMorphTargetMixer ()
{
}

 /*****************************************************************************/
/** Interpolates the weight of a morph target.
  *
  * This function interpolates the weight of a morph target a new value
  * in a given amount of time.
  *
  * @param id The ID of the morph target that should be blended.
  * @param weight The weight to interpolate the morph target to.
  * @param delay The time in seconds until the new weight should be reached.
  *
  * @return One of the following values:
  *         \li \b true if successful
  *         \li \b false if an error happend
  *****************************************************************************/
bool CalMorphTargetMixer::blend (int id, float weight, float delay)
{
  if ((id < 0) || (id >= (int) m_vectorCurrentWeight.size ()))
      {
        CalError::setLastError (CalError::INVALID_HANDLE, __FILE__, __LINE__);
        return false;
      }
  m_vectorEndWeight[id] = weight;
  m_vectorDuration[id] = delay;
  return true;
}

 /*****************************************************************************/
/** Fades a morph target out.
  *
  * This function fades a morph target out in a given amount of time.
  *
  * @param id The ID of the morph target that should be faded out.
  * @param delay The time in seconds until the the morph target is
  *              completely removed.
  *
  * @return One of the following values:
  *         \li \b true if successful
  *         \li \b false if an error happend
  *****************************************************************************/

bool CalMorphTargetMixer::clear (int id, float delay)
{
  if ((id < 0) || (id >= (int) m_vectorCurrentWeight.size ()))
      {
        CalError::setLastError (CalError::INVALID_HANDLE, __FILE__, __LINE__);
        return false;
      }
  m_vectorEndWeight[id] = 0.0f;
  m_vectorDuration[id] = delay;
  return true;
}

 /*****************************************************************************/
/** Get the weight of a morph target.
  *
  * @param id The id of the morph target which weight you want.
  *
  * @return The weight of the morph target with the given id.
  *****************************************************************************/
float CalMorphTargetMixer::getCurrentWeight (int id)
{
  if ((id < 0) || (id >= (int) m_vectorCurrentWeight.size ()))
      {
        CalError::setLastError (CalError::INVALID_HANDLE, __FILE__, __LINE__);
        return false;
      }
  return m_vectorCurrentWeight[id];
}

 /*****************************************************************************/
/** Get the weight of the base vertices.
  *
  * @return The weight of the base vertices.
  *****************************************************************************/
float CalMorphTargetMixer::getCurrentWeightBase ()
{
  float currentWeight = 1.0f;
  std::vector < float >::iterator iteratorCurrentWeight =
    m_vectorCurrentWeight.begin ();
  while (iteratorCurrentWeight != m_vectorCurrentWeight.end ())
      {
        currentWeight -= (*iteratorCurrentWeight);
        ++iteratorCurrentWeight;
      }
  return currentWeight;
}

 /*****************************************************************************/
/** Creates the morph target mixer instance.
  *
  * This function creates the mixer instance.
  *
  * @param pModel A pointer to the model that should be managed with this 
  *               morph target mixer instance.
  *
  * @return One of the following values:
  *         \li \b true if successful
  *         \li \b false if an error happend
  *****************************************************************************/

bool CalMorphTargetMixer::create (CalModel * pModel)
{
  if (pModel == 0)
      {
        CalError::setLastError (CalError::INVALID_HANDLE, __FILE__, __LINE__);
        return false;
      }
  m_pModel = pModel;

  if (pModel->getCoreModel ()->getCoreMorphAnimationCount () != 0)
      {
        int morphAnimationCount =
          pModel->getCoreModel ()->getCoreMorphAnimationCount ();
        // reserve the space needed in all the vectors
        m_vectorCurrentWeight.reserve (morphAnimationCount);
        m_vectorCurrentWeight.resize (morphAnimationCount);
        m_vectorEndWeight.reserve (morphAnimationCount);
        m_vectorEndWeight.resize (morphAnimationCount);
        m_vectorDuration.reserve (morphAnimationCount);
        m_vectorDuration.resize (morphAnimationCount);
        std::vector < float >::iterator iteratorCurrentWeight =
          m_vectorCurrentWeight.begin ();
        std::vector < float >::iterator iteratorEndWeight =
          m_vectorEndWeight.begin ();
        std::vector < float >::iterator iteratorDuration =
          m_vectorDuration.begin ();
        while (iteratorCurrentWeight != m_vectorCurrentWeight.end ())
            {
              (*iteratorCurrentWeight) = 0.0f;
              (*iteratorEndWeight) = 0.0f;
              (*iteratorDuration) = 0.0f;
              ++iteratorCurrentWeight;
              ++iteratorEndWeight;
              ++iteratorDuration;
            }
      }

  return true;
}

 /*****************************************************************************/
/** Destroys the morph target mixer instance.
  *
  * This function destroys all data stored in the mixer instance and frees all
  * allocated memory.
  *****************************************************************************/

void CalMorphTargetMixer::destroy ()
{
  m_pModel = 0;
}

 /*****************************************************************************/
/** Updates all morph targets.
  *
  * This function updates all morph targets of the mixer instance for a
  * given amount of time.
  *
  * @param deltaTime The elapsed time in seconds since the last update.
  *****************************************************************************/

void CalMorphTargetMixer::update (float deltaTime)
{
  std::vector < float >::iterator iteratorCurrentWeight =
    m_vectorCurrentWeight.begin ();
  std::vector < float >::iterator iteratorEndWeight =
    m_vectorEndWeight.begin ();
  std::vector < float >::iterator iteratorDuration =
    m_vectorDuration.begin ();
  while (iteratorCurrentWeight != m_vectorCurrentWeight.end ())
      {
        if (deltaTime >= (*iteratorDuration))
            {
              (*iteratorCurrentWeight) = (*iteratorEndWeight);
              (*iteratorDuration) = 0.0f;
            }
        else
            {
              (*iteratorCurrentWeight) +=
                ((*iteratorEndWeight) -
                 (*iteratorCurrentWeight)) * deltaTime / (*iteratorDuration);
              (*iteratorDuration) -= deltaTime;
            }
        ++iteratorCurrentWeight;
        ++iteratorEndWeight;
        ++iteratorDuration;
      }
  int morphAnimationID = 0;
  while (morphAnimationID < getMorphTargetCount ())
      {
        CalCoreMorphAnimation *pCoreMorphAnimation =
          m_pModel->getCoreModel ()->getCoreMorphAnimation (morphAnimationID);
        std::vector < int >&vectorCoreMeshID =
          pCoreMorphAnimation->getVectorCoreMeshID ();
        std::vector < int >&vectorMorphTargetID =
          pCoreMorphAnimation->getVectorMorphTargetID ();
        size_t meshIterator = 0;
        while (meshIterator < vectorCoreMeshID.size ())
            {
              std::vector < CalSubmesh * >&vectorSubmesh =
                m_pModel->getMesh (vectorCoreMeshID[meshIterator])->
                getVectorSubmesh ();
              int submeshCount = vectorSubmesh.size ();
              int submeshId;
              for (submeshId = 0; submeshId < submeshCount; ++submeshId)
                  {
                    vectorSubmesh[submeshId]->setMorphTargetWeight
                      (vectorMorphTargetID[meshIterator],
                       m_vectorCurrentWeight[morphAnimationID]);
                  }
              ++meshIterator;
            }
        ++morphAnimationID;
      }
}

 /*****************************************************************************/
/** Returns the number of morph targets this morph target mixer mixes.
  *
  * @return The number of morph targets this morph target mixer mixes.
  *****************************************************************************/

int CalMorphTargetMixer::getMorphTargetCount ()
{
  return m_vectorCurrentWeight.size ();
}

//****************************************************************************//
