//****************************************************************************//
// model.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/model.h"
#include "cal3d/skeleton.h"
#include "cal3d/bone.h"
#include "cal3d/mixer.h"
#include "cal3d/renderer.h"
#include "cal3d/coremodel.h"
#include "cal3d/coreskeleton.h"
#include "cal3d/coremesh.h"
#include "cal3d/coresubmesh.h"
#include "cal3d/mesh.h"
#include "cal3d/morphtargetmixer.h"
#include "cal3d/physique.h"
#include "cal3d/springsystem.h"

 /*****************************************************************************/
/** Constructs the model instance.
  *
  * This function is the default constructor of the model instance.
  *****************************************************************************/

CalModel::CalModel ():m_pCoreModel (0), m_pSkeleton (0), m_pMixer (0), m_pPhysique (0),
m_pSpringSystem (0), m_pRenderer (0), m_userData (0),
m_pMorphTargetMixer (0)
{
}

 /*****************************************************************************/
/** Destructs the model instance.
  *
  * This function is the destructor of the model instance.
  *****************************************************************************/

CalModel::~CalModel ()
{
  assert (m_vectorMesh.empty ());
}

 /*****************************************************************************/
/** Attachs a mesh.
  *
  * This function attachs a mesh to the model instance.
  *
  * @param coreMeshId The ID of the mesh that should be attached.
  *
  * @return One of the following values:
  *         \li \b true if successful
  *         \li \b false if an error happend
  *****************************************************************************/

bool CalModel::attachMesh (int coreMeshId)
{
  // check if the id is valid
  if ((coreMeshId < 0) || (coreMeshId >= m_pCoreModel->getCoreMeshCount ()))
      {
        CalError::setLastError (CalError::INVALID_HANDLE, __FILE__, __LINE__);
        return false;
      }

  // get the core mesh
  CalCoreMesh *pCoreMesh;
  pCoreMesh = m_pCoreModel->getCoreMesh (coreMeshId);

  // check if the mesh is already attached
  int meshId;
  for (meshId = 0; meshId < (int) m_vectorMesh.size (); ++meshId)
      {
        // check if we found the matching mesh
        if (m_vectorMesh[meshId]->getCoreMesh () == pCoreMesh)
            {
              // mesh is already active -> do nothing
              return true;
            }
      }

  // allocate a new mesh instance
  CalMesh *pMesh;
  pMesh = new CalMesh ();
  if (pMesh == 0)
      {
        CalError::setLastError (CalError::MEMORY_ALLOCATION_FAILED, __FILE__,
                                __LINE__);
        return false;
      }

  // create the new mesh instance
  if (!pMesh->create (pCoreMesh))
      {
        delete pMesh;
        return false;
      }

  // set model in the mesh instance
  pMesh->setModel (this);

  // insert the new mesh into the active list
  m_vectorMesh.push_back (pMesh);

  return true;
}

 /*****************************************************************************/
/** Creates the model instance.
  *
  * This function creates the model instance based on a core model.
  *
  * @param pCoreModel A pointer to the core model on which this model instance
  *                   should be based on.
  *
  * @return One of the following values:
  *         \li \b true if successful
  *         \li \b false if an error happend
  *****************************************************************************/

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

  m_pCoreModel = pCoreModel;

  // allocate a new skeleton instance
  CalSkeleton *pSkeleton;
  pSkeleton = new CalSkeleton ();
  if (pSkeleton == 0)
      {
        CalError::setLastError (CalError::MEMORY_ALLOCATION_FAILED, __FILE__,
                                __LINE__);
        return false;
      }

  // create the skeleton from the core skeleton
  if (!pSkeleton->create (pCoreModel->getCoreSkeleton ()))
      {
        delete pSkeleton;
        return false;
      }

  m_pSkeleton = pSkeleton;

  // allocate a new mixer instance
  CalMixer *pMixer;
  pMixer = new CalMixer ();
  if (pMixer == 0)
      {
        CalError::setLastError (CalError::MEMORY_ALLOCATION_FAILED, __FILE__,
                                __LINE__);
        m_pSkeleton->destroy ();
        delete m_pSkeleton;
        return false;
      }

  // create the mixer from this model
  if (!pMixer->create (this))
      {
        m_pSkeleton->destroy ();
        delete m_pSkeleton;
        delete pMixer;
        return false;
      }

  m_pMixer = pMixer;

  // Create the morph target mixer from this model
  m_pMorphTargetMixer = new CalMorphTargetMixer ();
  if (!m_pMorphTargetMixer->create (this))
    return false;

  // allocate a new physqiue instance
  CalPhysique *pPhysique;
  pPhysique = new CalPhysique ();
  if (pPhysique == 0)
      {
        CalError::setLastError (CalError::MEMORY_ALLOCATION_FAILED, __FILE__,
                                __LINE__);
        m_pMixer->destroy ();
        delete m_pMixer;
        m_pSkeleton->destroy ();
        delete m_pSkeleton;
        return false;
      }

  // create the physique from this model
  if (!pPhysique->create (this))
      {
        m_pMixer->destroy ();
        delete m_pMixer;
        m_pSkeleton->destroy ();
        delete m_pSkeleton;
        delete pPhysique;
        return false;
      }

  m_pPhysique = pPhysique;

  // allocate a new spring system instance
  CalSpringSystem *pSpringSystem;
  pSpringSystem = new CalSpringSystem ();
  if (pSpringSystem == 0)
      {
        CalError::setLastError (CalError::MEMORY_ALLOCATION_FAILED, __FILE__,
                                __LINE__);
        m_pPhysique->destroy ();
        delete m_pPhysique;
        m_pMixer->destroy ();
        delete m_pMixer;
        m_pSkeleton->destroy ();
        delete m_pSkeleton;
        return false;
      }

  // create the spring system from this model
  if (!pSpringSystem->create (this))
      {
        m_pPhysique->destroy ();
        delete m_pPhysique;
        m_pMixer->destroy ();
        delete m_pMixer;
        m_pSkeleton->destroy ();
        delete m_pSkeleton;
        delete pSpringSystem;
        return false;
      }

  m_pSpringSystem = pSpringSystem;

  // allocate a new renderer instance
  CalRenderer *pRenderer;
  pRenderer = new CalRenderer ();
  if (pRenderer == 0)
      {
        CalError::setLastError (CalError::MEMORY_ALLOCATION_FAILED, __FILE__,
                                __LINE__);
        m_pSpringSystem->destroy ();
        delete m_pSpringSystem;
        m_pPhysique->destroy ();
        delete m_pPhysique;
        m_pMixer->destroy ();
        delete m_pMixer;
        m_pSkeleton->destroy ();
        delete m_pSkeleton;
        return false;
      }

  // create the renderer from this model
  if (!pRenderer->create (this))
      {
        m_pSpringSystem->destroy ();
        delete m_pSpringSystem;
        m_pPhysique->destroy ();
        delete m_pPhysique;
        m_pMixer->destroy ();
        delete m_pMixer;
        m_pSkeleton->destroy ();
        delete m_pSkeleton;
        delete pRenderer;
        return false;
      }

  m_pRenderer = pRenderer;

  // initialize the user data
  m_userData = 0;

  return true;
}

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

void CalModel::destroy ()
{
  // destroy all active meshes
  int meshId;
  for (meshId = 0; meshId < (int) m_vectorMesh.size (); ++meshId)
      {
        m_vectorMesh[meshId]->destroy ();
        delete m_vectorMesh[meshId];
      }
  m_vectorMesh.clear ();

  // destroy the renderer instance
  if (m_pRenderer != 0)
      {
        m_pRenderer->destroy ();
        delete m_pRenderer;
        m_pRenderer = 0;
      }

  // destroy the spring system instance
  if (m_pSpringSystem != 0)
      {
        m_pSpringSystem->destroy ();
        delete m_pSpringSystem;
        m_pSpringSystem = 0;
      }

  // destroy the physique instance
  if (m_pPhysique != 0)
      {
        m_pPhysique->destroy ();
        delete m_pPhysique;
        m_pPhysique = 0;
      }

  // destroy the mixer instance
  if (m_pMixer != 0)
      {
        m_pMixer->destroy ();
        delete m_pMixer;
        m_pMixer = 0;
      }

  // destroy the morph target mixer instance
  if (m_pMorphTargetMixer != 0)
      {
        m_pMorphTargetMixer->destroy ();
        delete m_pMorphTargetMixer;
        m_pMorphTargetMixer = 0;
      }

  // destroy the skeleton instance
  if (m_pSkeleton != 0)
      {
        m_pSkeleton->destroy ();
        delete m_pSkeleton;
        m_pSkeleton = 0;
      }

  m_pCoreModel = 0;
}

 /*****************************************************************************/
/** Detaches a mesh.
  *
  * This function detaches a mesh from the model instance.
  *
  * @param coreMeshId The ID of the mesh that should be detached.
  *
  * @return One of the following values:
  *         \li \b true if successful
  *         \li \b false if an error happend
  *****************************************************************************/

bool CalModel::detachMesh (int coreMeshId)
{
  // check if the id is valid
  if ((coreMeshId < 0) || (coreMeshId >= m_pCoreModel->getCoreMeshCount ()))
      {
        CalError::setLastError (CalError::INVALID_HANDLE, __FILE__, __LINE__);
        return false;
      }

  // get the core mesh
  CalCoreMesh *pCoreMesh;
  pCoreMesh = m_pCoreModel->getCoreMesh (coreMeshId);

  // find the mesh for the given id
  std::vector < CalMesh * >::iterator iteratorMesh;
  for (iteratorMesh = m_vectorMesh.begin ();
       iteratorMesh != m_vectorMesh.end (); ++iteratorMesh)
      {
        // get the mesh
        CalMesh *pMesh;
        pMesh = *iteratorMesh;

        // check if we found the matching mesh
        if (pMesh->getCoreMesh () == pCoreMesh)
            {
              // destroy the mesh
              pMesh->destroy ();
              delete pMesh;

              // erase the mesh out of the active mesh list
              m_vectorMesh.erase (iteratorMesh);

              return true;
            }
      }

  return false;
}

 /*****************************************************************************/
/** Provides access to the core model.
  *
  * This function returns the core model on which this model instance is based
  * on.
  *
  * @return One of the following values:
  *         \li a pointer to the core model
  *         \li \b 0 if an error happend
  *****************************************************************************/

CalCoreModel *CalModel::getCoreModel ()
{
  return m_pCoreModel;
}

 /*****************************************************************************/
/** Provides access to an attached mesh.
  *
  * This function returns the attached mesh with the given core mesh ID.
  *
  * @param coreMeshId The core mesh ID of the mesh that should be returned.
  *
  * @return One of the following values:
  *         \li a pointer to the mesh
  *         \li \b 0 if an error happend
  *****************************************************************************/

CalMesh *CalModel::getMesh (int coreMeshId)
{
  // check if the id is valid
  if ((coreMeshId < 0) || (coreMeshId >= m_pCoreModel->getCoreMeshCount ()))
      {
        CalError::setLastError (CalError::INVALID_HANDLE, __FILE__, __LINE__);
        return 0;
      }

  // get the core mesh
  CalCoreMesh *pCoreMesh;
  pCoreMesh = m_pCoreModel->getCoreMesh (coreMeshId);

  // search the mesh
  int meshId;
  for (meshId = 0; meshId < (int) m_vectorMesh.size (); ++meshId)
      {
        // check if we found the matching mesh
        if (m_vectorMesh[meshId]->getCoreMesh () == pCoreMesh)
            {
              return m_vectorMesh[meshId];
            }
      }

  return 0;
}

 /*****************************************************************************/
/** Provides access to the mixer.
  *
  * This function returns the mixer.
  *
  * @return One of the following values:
  *         \li a pointer to the mixer
  *         \li \b 0 if an error happend
  *****************************************************************************/

CalMixer *CalModel::getMixer ()
{
  return m_pMixer;
}

/*****************************************************************************/
/** Provides access to the morph target mixer.
  *
  * This function returns the morph target mixer.
  *
  * @return One of the following values:
  *         \li a pointer to the morph target mixer
  *         \li \b 0 if an error happend
  *****************************************************************************/

CalMorphTargetMixer *CalModel::getMorphTargetMixer ()
{
  return m_pMorphTargetMixer;
}

 /*****************************************************************************/
/** Provides access to the physique.
  *
  * This function returns the physique.
  *
  * @return One of the following values:
  *         \li a pointer to the physique
  *         \li \b 0 if an error happend
  *****************************************************************************/

CalPhysique *CalModel::getPhysique ()
{
  return m_pPhysique;
}

 /*****************************************************************************/
/** Provides access to the renderer.
  *
  * This function returns the renderer.
  *
  * @return One of the following values:
  *         \li a pointer to the renderer
  *         \li \b 0 if an error happend
  *****************************************************************************/

CalRenderer *CalModel::getRenderer ()
{
  return m_pRenderer;
}

 /*****************************************************************************/
/** Provides access to the skeleton.
  *
  * This function returns the skeleton.
  *
  * @return One of the following values:
  *         \li a pointer to the skeleton
  *         \li \b 0 if an error happend
  *****************************************************************************/

CalSkeleton *CalModel::getSkeleton ()
{
  return m_pSkeleton;
}

 /*****************************************************************************/
/** Provides access to the spring system.
  *
  * This function returns the spring system.
  *
  * @return One of the following values:
  *         \li a pointer to the spring system
  *         \li \b 0 if an error happend
  *****************************************************************************/

CalSpringSystem *CalModel::getSpringSystem ()
{
  return m_pSpringSystem;
}

 /*****************************************************************************/
/** Provides access to the user data.
  *
  * This function returns the user data stored in the model instance.
  *
  * @return The user data stored in the model instance.
  *****************************************************************************/

Cal::UserData CalModel::getUserData ()
{
  return m_userData;
}

 /*****************************************************************************/
/** Returns the mesh vector.
  *
  * This function returns the vector that contains all attached meshes of the
  * model instance.
  *
  * @return A reference to the mesh vector.
  *****************************************************************************/

std::vector < CalMesh * >&CalModel::getVectorMesh ()
{
  return m_vectorMesh;
}

 /*****************************************************************************/
/** Sets the LOD level.
  *
  * This function sets the LOD level of all attached meshes.
  *
  * @param lodLevel The LOD level in the range [0.0, 1.0].
  *****************************************************************************/

void CalModel::setLodLevel (float lodLevel)
{
  // set the lod level in all meshes
  std::vector < CalMesh * >::iterator iteratorMesh;
  for (iteratorMesh = m_vectorMesh.begin ();
       iteratorMesh != m_vectorMesh.end (); ++iteratorMesh)
      {
        // set the lod level in the mesh
        (*iteratorMesh)->setLodLevel (lodLevel);
      }
}

 /*****************************************************************************/
/** Sets the material set.
  *
  * This function sets the material set of all attached meshes.
  *
  * @param setId The ID of the material set.
  *****************************************************************************/

void CalModel::setMaterialSet (int setId)
{
  // set the lod level in all meshes
  std::vector < CalMesh * >::iterator iteratorMesh;
  for (iteratorMesh = m_vectorMesh.begin ();
       iteratorMesh != m_vectorMesh.end (); ++iteratorMesh)
      {
        // set the material set in the mesh
        (*iteratorMesh)->setMaterialSet (setId);
      }
}

 /*****************************************************************************/
/** Stores user data.
  *
  * This function stores user data in the model instance.
  *
  * @param userData The user data that should be stored.
  *****************************************************************************/

void CalModel::setUserData (Cal::UserData userData)
{
  m_userData = userData;
}

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

void CalModel::update (float deltaTime)
{
  m_pMixer->updateAnimation (deltaTime);
  m_pMixer->updateSkeleton ();
  std::vector < CalMesh * >::iterator iteratorVectorMesh =
    m_vectorMesh.begin ();
  // m_pMorpher->update(...);
  m_pMorphTargetMixer->update (deltaTime);
  m_pPhysique->update ();
  m_pSpringSystem->update (deltaTime);
}

/*****************************************************************************/
/** Disable internal data (and thus springs system)
  *
  *****************************************************************************/

void CalModel::disableInternalData ()
{
  // Disable internal data in all meshes
  std::vector < CalMesh * >::iterator iteratorMesh;
  for (iteratorMesh = m_vectorMesh.begin ();
       iteratorMesh != m_vectorMesh.end (); ++iteratorMesh)
      {
        // Disable internal data in the mesh
        (*iteratorMesh)->disableInternalData ();
      }
}

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