//****************************************************************************//
// loader.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/loader.h"
#include "cal3d/error.h"
#include "cal3d/matrix.h"
#include "cal3d/vector.h"
#include "cal3d/quaternion.h"
#include "cal3d/coremodel.h"
#include "cal3d/coreskeleton.h"
#include "cal3d/corebone.h"
#include "cal3d/coreanimation.h"
#include "cal3d/coreanimatedmorph.h"
#include "cal3d/coretrack.h"
#include "cal3d/corekeyframe.h"
#include "cal3d/coremesh.h"
#include "cal3d/coremorphtrack.h"
#include "cal3d/coremorphkeyframe.h"
#include "cal3d/coresubmesh.h"
#include "cal3d/coresubmorphtarget.h"
#include "cal3d/corematerial.h"
#include "cal3d/tinyxml.h"
#include "cal3d/streamsource.h"
#include "cal3d/buffersource.h"
#include "cal3d/xmlformat.h"

#ifdef WIN32
#include "cal3d/calxmlbindings.h"
#else
#include "cal3d/calxmlbindings.cpp"
#endif

int CalLoader::loadingMode;
double CalLoader::translationTolerance = 0.25;
double CalLoader::rotationToleranceDegrees = 0.1;
bool CalLoader::loadingCompressionOn = false;
bool CalLoader::collapseSequencesOn = false;
int CalLoader::numEliminatedKeyframes = 0;
int CalLoader::numKeptKeyframes = 0;
int CalLoader::numCompressedAnimations = 0;
int CalLoader::numRoundedKeyframes = 0;

// Quat format: 
//
//  axis selection (2), 
//  asign (1), afixed (n), 
//  bsign (1), bfixed (n), 
//  csign (1), cfixed (n), 
//  time (in 1/30th second steps)
unsigned int const CalLoader::keyframeBitsPerOriComponent = 11;
unsigned int const CalLoader::keyframeBitsPerTime = 10;
//unsigned int const keyframeTimeMax = ( 1 << CalLoader::keyframeBitsPerTime );

// My resolution is in 1/4 units which is 1/2mm if 1 unit is 2mm.
// My large range is 2^23 units, or 2^24mm, which is 2^4km, or 16km.
// My small range is 2^7 units, or 2^8mm, which is 2.56m.
// The large pos has X, Y, Z, having 25 bits plus a sign bit, totaling 78 bits, leaving two bits padding.
// The small pos has X, Y, Z, having 9 bits plus a sign bit, totaling 30 bits, leaving two bits padding.
unsigned int const CalLoader::keyframeBitsPerUnsignedPosComponent = 25;
unsigned int const CalLoader::keyframeBitsPerPosPadding = 2;
unsigned int const CalLoader::keyframePosBytes = 10;
float const CalLoader::keyframePosRange = ( 1 << ( CalLoader::keyframeBitsPerUnsignedPosComponent - 2 ) ); // In units.

unsigned int const CalLoader::keyframeBitsPerUnsignedPosComponentSmall = 9;
unsigned int const CalLoader::keyframeBitsPerPosPaddingSmall = 2;
unsigned int const CalLoader::keyframePosBytesSmall = 4;
float const CalLoader::keyframePosRangeSmall = ( 1 << ( CalLoader::keyframeBitsPerUnsignedPosComponentSmall - 2 ) );




bool CAL3D_API CalVectorFromDataSrc( CalDataSource & dataSrc, CalVector * calVec )
{
  return dataSrc.readFloat(calVec->x) &&
         dataSrc.readFloat(calVec->y) &&
         dataSrc.readFloat(calVec->z);
}


bool
TranslationWritten( CalCoreKeyframe * lastCoreKeyframe, bool translationRequired, bool translationIsDynamic )
{
  return ( translationRequired && ( !lastCoreKeyframe || translationIsDynamic ) );
}


 /*****************************************************************************/
/** Sets optional flags which affect how the model is loaded into memory.
  *
  * This function sets the loading mode for all future loader calls.
  *
  * @param flags A boolean OR of any of the following flags
  *         \li LOADER_ROTATE_X_AXIS will rotate the mesh 90 degrees about the X axis,
  *             which has the effect of swapping Y/Z coordinates.
  *         \li LOADER_INVERT_V_COORD will substitute (1-v) for any v texture coordinate
  *             to eliminate the need for texture inversion after export.
  *
  *****************************************************************************/
void CalLoader::setLoadingMode(int flags)
{
    loadingMode = flags;
}

 /*****************************************************************************/
/** Loads a core animation instance.
  *
  * This function loads a core animation instance from a file.
  *
  * @param strFilename The file to load the core animation instance from.
  *
  * @return One of the following values:
  *         \li a pointer to the core animation
  *         \li \b 0 if an error happened
  *****************************************************************************/

CalCoreAnimation *CalLoader::loadCoreAnimation(const std::string& strFilename, CalCoreSkeleton *skel)
{
  if(strFilename.size() >= 3 &&
    stricmp(strFilename.substr(strFilename.size() - 3, 3).c_str(), Cal::ANIMATION_XMLFILE_EXTENSION) == 0)
    return loadXmlCoreAnimation(strFilename, skel);

  // open the file
  std::ifstream file(strFilename.c_str(), std::ios::in | std::ios::binary);

  //make sure it was opened properly
  if(!file)
  {
    CalError::setLastError(CalError::FILE_NOT_FOUND, __FILE__, __LINE__, strFilename);
    return 0;
  }

  //make a new stream data source and use it to load the animation
  CalStreamSource streamSrc( file );
  
  CalCoreAnimation* coreanim = loadCoreAnimation( streamSrc,skel );

  //close the file
  file.close();

  return coreanim;
}

 /*****************************************************************************/
/** Loads a core animatedMorph instance.
  *
  * This function loads a core animatedMorph instance from a file.
  *
  * @param strFilename The file to load the core animatedMorph instance from.
  *
  * @return One of the following values:
  *         \li a pointer to the core animatedMorph
  *         \li \b 0 if an error happened
  *****************************************************************************/

CalCoreAnimatedMorph *CalLoader::loadCoreAnimatedMorph(const std::string& strFilename)
{
  if(strFilename.size()>= 3 && stricmp(strFilename.substr(strFilename.size()-3,3).c_str(),Cal::ANIMATEDMORPH_XMLFILE_EXTENSION)==0)
    return loadXmlCoreAnimatedMorph(strFilename);
  else
    return loadCoreAnimatedMorph(strFilename);
}

 /*****************************************************************************/
/** Loads a core material instance.
  *
  * This function loads a core material instance from a file.
  *
  * @param strFilename The file to load the core material instance from.
  *
  * @return One of the following values:
  *         \li a pointer to the core material
  *         \li \b 0 if an error happened
  *****************************************************************************/

CalCoreMaterial *CalLoader::loadCoreMaterial(const std::string& strFilename)
{

  if(strFilename.size()>= 3 && stricmp(strFilename.substr(strFilename.size()-3,3).c_str(),Cal::MATERIAL_XMLFILE_EXTENSION)==0)
    return loadXmlCoreMaterial(strFilename);

  // open the file
  std::ifstream file;
  file.open(strFilename.c_str(), std::ios::in | std::ios::binary);

  // make sure it opened properly
  if(!file)
  {
    CalError::setLastError(CalError::FILE_NOT_FOUND, __FILE__, __LINE__, strFilename);
    return 0;
  }

  //make a new stream data source and use it to load the material
  CalStreamSource streamSrc( file );
  
  CalCoreMaterial* coremat = loadCoreMaterial( streamSrc );

  //close the file
  file.close();

  return coremat;

}

 /*****************************************************************************/
/** Loads a core mesh instance.
  *
  * This function loads a core mesh instance from a file.
  *
  * @param strFilename The file to load the core mesh instance from.
  *
  * @return One of the following values:
  *         \li a pointer to the core mesh
  *         \li \b 0 if an error happened
  *****************************************************************************/

CalCoreMesh *CalLoader::loadCoreMesh(const std::string& strFilename)
{

  if(strFilename.size()>= 3 && stricmp(strFilename.substr(strFilename.size()-3,3).c_str(),Cal::MESH_XMLFILE_EXTENSION)==0)
    return loadXmlCoreMesh(strFilename);

  // open the file
  std::ifstream file;
  file.open(strFilename.c_str(), std::ios::in | std::ios::binary);

  // make sure it opened properly
  if(!file)
  {
    CalError::setLastError(CalError::FILE_NOT_FOUND, __FILE__, __LINE__, strFilename);
    return 0;
  }

  //make a new stream data source and use it to load the mesh
  CalStreamSource streamSrc( file );
  
  CalCoreMesh* coremesh = loadCoreMesh( streamSrc );

  //close the file
  file.close();

  return coremesh;

}

 /*****************************************************************************/
/** Loads a core skeleton instance.
  *
  * This function loads a core skeleton instance from a file.
  *
  * @param strFilename The file to load the core skeleton instance from.
  *
  * @return One of the following values:
  *         \li a pointer to the core skeleton
  *         \li \b 0 if an error happened
  *****************************************************************************/

CalCoreSkeleton *CalLoader::loadCoreSkeleton(const std::string& strFilename)
{

  if(strFilename.size()>= 3 && stricmp(strFilename.substr(strFilename.size()-3,3).c_str(),Cal::SKELETON_XMLFILE_EXTENSION)==0)
    return loadXmlCoreSkeletonFromFile(strFilename);

  // open the file
  std::ifstream file;
  file.open(strFilename.c_str(), std::ios::in | std::ios::binary);

  //make sure it opened properly
  if(!file)
  {
    CalError::setLastError(CalError::FILE_NOT_FOUND, __FILE__, __LINE__, strFilename);
    return 0;
  }

  //make a new stream data source and use it to load the skeleton
  CalStreamSource streamSrc( file );
  
  CalCoreSkeleton* coreskeleton = loadCoreSkeleton( streamSrc );

  //close the file
  file.close();

  return coreskeleton;

}


 /*****************************************************************************/
/** Loads a core animation instance.
  *
  * This function loads a core animation instance from an input stream.
  *
  * @param inputStream The stream to load the core animation instance from. This
  *                    stream should be initialized and ready to be read from.
  *
  * @return One of the following values:
  *         \li a pointer to the core animation
  *         \li \b 0 if an error happened
  *****************************************************************************/

CalCoreAnimation *CalLoader::loadCoreAnimation(std::istream& inputStream, CalCoreSkeleton *skel )
{
   //Create a new istream data source and pass it on
   CalStreamSource streamSrc(inputStream);
   return loadCoreAnimation(streamSrc, skel);
}

CalCoreAnimatedMorph *CalLoader::loadCoreAnimatedMorph(std::istream& inputStream)
{
   //Create a new istream data source and pass it on
   CalStreamSource streamSrc(inputStream);
   return loadCoreAnimatedMorph(streamSrc);
}

 /*****************************************************************************/
/** Loads a core material instance.
  *
  * This function loads a core material instance from an input stream.
  *
  * @param inputStream The stream to load the core material instance from. This
  *                    stream should be initialized and ready to be read from.
  *
  * @return One of the following values:
  *         \li a pointer to the core material
  *         \li \b 0 if an error happened
  *****************************************************************************/

CalCoreMaterial *CalLoader::loadCoreMaterial(std::istream& inputStream)
{
   //Create a new istream data source and pass it on
   CalStreamSource streamSrc(inputStream);
   return loadCoreMaterial(streamSrc);
}

 /*****************************************************************************/
/** Loads a core mesh instance.
  *
  * This function loads a core mesh instance from an input stream.
  *
  * @param inputStream The stream to load the core mesh instance from. This
  *                    stream should be initialized and ready to be read from.
  *
  * @return One of the following values:
  *         \li a pointer to the core mesh
  *         \li \b 0 if an error happened
  *****************************************************************************/

CalCoreMesh *CalLoader::loadCoreMesh(std::istream& inputStream)
{
   //Create a new istream data source and pass it on
   CalStreamSource streamSrc(inputStream);
   return loadCoreMesh(streamSrc);
}

 /*****************************************************************************/
/** Loads a core skeleton instance.
  *
  * This function loads a core skeleton instance from an input stream.
  *
  * @param inputStream The stream to load the core skeleton instance from. This
  *                    stream should be initialized and ready to be read from.
  *
  * @return One of the following values:
  *         \li a pointer to the core skeleton
  *         \li \b 0 if an error happened
  *****************************************************************************/

CalCoreSkeleton *CalLoader::loadCoreSkeleton(std::istream& inputStream)
{
   //Create a new istream data source and pass it on
   CalStreamSource streamSrc(inputStream);
   return loadCoreSkeleton(streamSrc);
}




 /*****************************************************************************/
/** Loads a core animation instance.
  *
  * This function loads a core animation instance from a memory buffer.
  *
  * @param inputBuffer The memory buffer to load the core animation instance 
  *                    from. This buffer should be initialized and ready to 
  *                    be read from.
  *
  * @return One of the following values:
  *         \li a pointer to the core animation
  *         \li \b 0 if an error happened
  *****************************************************************************/

CalCoreAnimation *CalLoader::loadCoreAnimationFromBuffer(const void* inputBuffer, unsigned int len, CalCoreSkeleton *skel)
{
   //Create a new buffer data source and pass it on
   CalBufferSource bufferSrc(inputBuffer, len);
   CalCoreAnimation * result = loadCoreAnimation(bufferSrc,skel);
   if( result ) {
     return result;
   } else {
     if (CalError::getLastErrorCode() == CalError::INVALID_FILE_FORMAT) {
       // Assumes inputBuffer is zero-terminated, which may not be the case.
       return loadXmlCoreAnimation(static_cast<const char*>(inputBuffer), skel);
     } else {
       return 0;
     }
     return 0;
   }
}

 /*****************************************************************************/
/** Loads a core animatedMorph instance.
  *
  * This function loads a core animatedMorph instance from a memory buffer.
  *
  * @param inputBuffer The memory buffer to load the core animatedMorph instance 
  *                    from. This buffer should be initialized and ready to 
  *                    be read from.
  *
  * @return One of the following values:
  *         \li a pointer to the core animatedMorph
  *         \li \b 0 if an error happened
  *****************************************************************************/

CalCoreAnimatedMorph *CalLoader::loadCoreAnimatedMorphFromBuffer(const void* inputBuffer, unsigned int len )
{
   //Create a new buffer data source and pass it on
   CalBufferSource bufferSrc(inputBuffer, len);
   CalCoreAnimatedMorph * result = loadCoreAnimatedMorph(bufferSrc);
   if( result ) {
     return result;
   } else {
     return loadXmlCoreAnimatedMorph(inputBuffer);
   }

}

 /*****************************************************************************/
/** Loads a core material instance.
  *
  * This function loads a core material instance from a memory buffer.
  *
  * @param inputBuffer The memory buffer to load the core material instance 
  *                    from. This buffer should be initialized and ready to 
  *                    be read from.
  *
  * @return One of the following values:
  *         \li a pointer to the core material
  *         \li \b 0 if an error happened
  *****************************************************************************/

CalCoreMaterial *CalLoader::loadCoreMaterialFromBuffer(const void* inputBuffer, unsigned int len)
{
   //Create a new buffer data source and pass it on
   CalBufferSource bufferSrc(inputBuffer, len);
   CalCoreMaterial * result = loadCoreMaterial(bufferSrc);
   if( result ) {
     return result;
   } else {
     return loadXmlCoreMaterial(inputBuffer);
   }
}

 /*****************************************************************************/
/** Loads a core mesh instance.
  *
  * This function loads a core mesh instance from a memory buffer.
  *
  * @param inputBuffer The memory buffer to load the core mesh instance from. 
  *                    This buffer should be initialized and ready to be 
  *                    read from.
  *
  * @return One of the following values:
  *         \li a pointer to the core mesh
  *         \li \b 0 if an error happened
  *****************************************************************************/

CalCoreMesh *CalLoader::loadCoreMeshFromBuffer(const void* inputBuffer, unsigned int len)
{
   //Create a new buffer data source and pass it on
   CalBufferSource bufferSrc(inputBuffer, len);
   CalCoreMesh * result = loadCoreMesh(bufferSrc);
   if( result ) {
     return result;
   } else {
     return loadXmlCoreMesh(inputBuffer);
   }
}

 /*****************************************************************************/
/** Loads a core skeleton instance.
  *
  * This function loads a core skeleton instance from a memory buffer.
  *
  * @param inputBuffer The memory buffer to load the core skeleton instance 
  *                    from. This buffer should be initialized and ready to 
  *                    be read from.
  *
  * @return One of the following values:
  *         \li a pointer to the core skeleton
  *         \li \b 0 if an error happened
  *****************************************************************************/

CalCoreSkeleton *CalLoader::loadCoreSkeletonFromBuffer(const void* inputBuffer, unsigned int len)
{
   //Create a new buffer data source and pass it on
   CalBufferSource bufferSrc(inputBuffer, len);
   CalCoreSkeleton * result = loadCoreSkeleton(bufferSrc);
   if( result ) {
     return result;
   } else {
     return loadXmlCoreSkeleton(inputBuffer);
   }
}

 /*****************************************************************************/
/** Loads a core animation instance.
  *
  * This function loads a core animation instance from a data source.
  *
  * @param dataSrc The data source to load the core animation instance from.
  *
  * @return One of the following values:
  *         \li a pointer to the core animation
  *         \li \b 0 if an error happened
  *****************************************************************************/

CalCoreAnimation *CalLoader::loadCoreAnimation(CalDataSource& dataSrc, CalCoreSkeleton *skel)
{

  // check if this is a valid file
  char magic[4];
  if(!dataSrc.readBytes(&magic[0], 4) || (memcmp(&magic[0], Cal::ANIMATION_FILE_MAGIC, 4) != 0))
  {
    CalError::setLastError(CalError::INVALID_FILE_FORMAT, __FILE__, __LINE__);
    return 0;
  }

  // check if the version is compatible with the library
  int version;
  if(!dataSrc.readInteger(version) || (version < Cal::EARLIEST_COMPATIBLE_FILE_VERSION) || (version > Cal::CURRENT_FILE_VERSION))
  {
    CalError::setLastError(CalError::INCOMPATIBLE_FILE_VERSION, __FILE__, __LINE__);
    return 0;
  }

  bool useAnimationCompression = CalLoader::usesAnimationCompression(version);
  if (Cal::versionHasCompressionFlag(version)) {
    int compressionFlag = 0;
    if (!dataSrc.readInteger(compressionFlag)) {
      CalError::setLastError(CalError::INVALID_FILE_FORMAT, __FILE__, __LINE__);
      return 0;
    }
    // Only really need the first bit.
    useAnimationCompression = (compressionFlag != 0);
  }


  // allocate a new core animation instance
  CalCoreAnimation *pCoreAnimation = new CalCoreAnimation();

  // create the core animation instance
  if(!pCoreAnimation->create())
  {
    delete pCoreAnimation;
    return 0;
  }

  // get the duration of the core animation
  float duration;
  if(!dataSrc.readFloat(duration))
  {
    CalError::setLastError(CalError::INVALID_FILE_FORMAT, __FILE__, __LINE__);
    pCoreAnimation->destroy();
    delete pCoreAnimation;
    return 0;
  }

  // check for a valid duration
  if(duration <= 0.0f)
  {
    CalError::setLastError(CalError::INVALID_ANIMATION_DURATION, __FILE__, __LINE__);
    pCoreAnimation->destroy();
    delete pCoreAnimation;
    return 0;
  }

  // set the duration in the core animation instance
  pCoreAnimation->setDuration(duration);

  // read the number of tracks
  int trackCount;
  if(!dataSrc.readInteger(trackCount) || (trackCount <= 0))
  {
    CalError::setLastError(CalError::INVALID_FILE_FORMAT, __FILE__, __LINE__);
    return 0;
  }

  // load all core bones
  for(int trackId = 0; trackId < trackCount; ++trackId)
  {
    // load the core track
    CalCoreTrack *pCoreTrack;
    pCoreTrack = loadCoreTrack(dataSrc,skel, version, useAnimationCompression);
    if(pCoreTrack == 0)
    {
      pCoreAnimation->destroy();
      delete pCoreAnimation;
      return 0;
    }

    // add the core track to the core animation instance
    pCoreAnimation->addCoreTrack(pCoreTrack);
  }

  return pCoreAnimation;
}



void
CalLoader::compressCoreAnimation( CalCoreAnimation * anim, CalCoreSkeleton *skel )
{
  std::list<CalCoreTrack *>& listCoreTrack = anim->getListCoreTrack();
  std::list<CalCoreTrack *>::iterator iteratorCoreTrack;
  for(iteratorCoreTrack = listCoreTrack.begin(); iteratorCoreTrack != listCoreTrack.end(); ++iteratorCoreTrack)
  {
    CalCoreTrack *pCoreTrack=*iteratorCoreTrack;
    pCoreTrack->compress( translationTolerance, rotationToleranceDegrees, skel );
  }
}



 /*****************************************************************************/
/** Loads a core animatedMorph instance.
  *
  * This function loads a core animatedMorph instance from a data source.
  *
  * @param dataSrc The data source to load the core animatedMorph instance from.
  *
  * @return One of the following values:
  *         \li a pointer to the core animatedMorph
  *         \li \b 0 if an error happened
  *****************************************************************************/

CalCoreAnimatedMorph *CalLoader::loadCoreAnimatedMorph(CalDataSource& dataSrc)
{

  // check if this is a valid file
  char magic[4];
  if(!dataSrc.readBytes(&magic[0], 4) || (memcmp(&magic[0], Cal::ANIMATEDMORPH_FILE_MAGIC, 4) != 0))
  {
    CalError::setLastError(CalError::INVALID_FILE_FORMAT, __FILE__, __LINE__);
    return 0;
  }

  // check if the version is compatible with the library
  int version;
  if(!dataSrc.readInteger(version) || (version < Cal::EARLIEST_COMPATIBLE_FILE_VERSION) || (version > Cal::CURRENT_FILE_VERSION))
  {
    CalError::setLastError(CalError::INCOMPATIBLE_FILE_VERSION, __FILE__, __LINE__);
    return 0;
  }

  // allocate a new core animatedMorph instance
  CalCoreAnimatedMorph *pCoreAnimatedMorph;
  pCoreAnimatedMorph = new CalCoreAnimatedMorph();
  if(pCoreAnimatedMorph == 0)
  {
    CalError::setLastError(CalError::MEMORY_ALLOCATION_FAILED, __FILE__, __LINE__);
    return 0;
  }

  // create the core animatedMorph instance
  if(!pCoreAnimatedMorph->create())
  {
    delete pCoreAnimatedMorph;
    return 0;
  }

  // get the duration of the core animatedMorph
  float duration;
  if(!dataSrc.readFloat(duration))
  {
    CalError::setLastError(CalError::INVALID_FILE_FORMAT, __FILE__, __LINE__);
    pCoreAnimatedMorph->destroy();
    delete pCoreAnimatedMorph;
    return 0;
  }

  // check for a valid duration
  if(duration <= 0.0f)
  {
    CalError::setLastError(CalError::INVALID_ANIMATION_DURATION, __FILE__, __LINE__);
    pCoreAnimatedMorph->destroy();
    delete pCoreAnimatedMorph;
    return 0;
  }

  // set the duration in the core animatedMorph instance
  pCoreAnimatedMorph->setDuration(duration);

  // read the number of tracks
  int trackCount;
  if(!dataSrc.readInteger(trackCount) || (trackCount <= 0))
  {
    CalError::setLastError(CalError::INVALID_FILE_FORMAT, __FILE__, __LINE__);
    return 0;
  }

  // load all core bones
  int trackId;
  for(trackId = 0; trackId < trackCount; ++trackId)
  {
    // load the core track
    CalCoreMorphTrack *pCoreTrack;
    pCoreTrack = loadCoreMorphTrack(dataSrc);
    if(pCoreTrack == 0)
    {
      pCoreAnimatedMorph->destroy();
      delete pCoreAnimatedMorph;
      return 0;
    }

    // add the core track to the core animatedMorph instance
    pCoreAnimatedMorph->addCoreTrack(pCoreTrack);
  }

  return pCoreAnimatedMorph;
}


 /*****************************************************************************/
/** Loads a core material instance.
  *
  * This function loads a core material instance from a data source.
  *
  * @param dataSrc The data source to load the core material instance from.
  *
  * @return One of the following values:
  *         \li a pointer to the core material
  *         \li \b 0 if an error happened
  *****************************************************************************/

CalCoreMaterial *CalLoader::loadCoreMaterial(CalDataSource& dataSrc)
{

  // check if this is a valid file
  char magic[4];
  if(!dataSrc.readBytes(&magic[0], 4) || (memcmp(&magic[0], Cal::MATERIAL_FILE_MAGIC, 4) != 0))
  {
    CalError::setLastError(CalError::INVALID_FILE_FORMAT, __FILE__, __LINE__);
    return 0;
  }

  // check if the version is compatible with the library
  int version;
  if(!dataSrc.readInteger(version) || (version < Cal::EARLIEST_COMPATIBLE_FILE_VERSION) || (version > Cal::CURRENT_FILE_VERSION))
  {
    CalError::setLastError(CalError::INCOMPATIBLE_FILE_VERSION, __FILE__, __LINE__);
    return 0;
  }

  bool hasMaterialTypes = (version >= Cal::FIRST_FILE_VERSION_WITH_MATERIAL_TYPES);
  
  // allocate a new core material instance
  CalCoreMaterial *pCoreMaterial;
  pCoreMaterial = new CalCoreMaterial();
  if(pCoreMaterial == 0)
  {
    CalError::setLastError(CalError::MEMORY_ALLOCATION_FAILED, __FILE__, __LINE__);
    return 0;
  }

  // create the core material instance
  if(!pCoreMaterial->create())
  {
    delete pCoreMaterial;
    return 0;
  }

  // get the ambient color of the core material
  CalCoreMaterial::Color ambientColor;
  if( !dataSrc.readBytes(&ambientColor, sizeof(ambientColor)) ) {
    CalError::setLastError(CalError::INVALID_FILE_FORMAT, __FILE__, __LINE__);
    return NULL;
  }

  // get the diffuse color of the core material
  CalCoreMaterial::Color diffuseColor;
  if( !dataSrc.readBytes(&diffuseColor, sizeof(diffuseColor)) ) {
    CalError::setLastError(CalError::INVALID_FILE_FORMAT, __FILE__, __LINE__);
    return NULL;
  }

  // get the specular color of the core material
  CalCoreMaterial::Color specularColor;
  if( !dataSrc.readBytes(&specularColor, sizeof(specularColor)) ) {
    CalError::setLastError(CalError::INVALID_FILE_FORMAT, __FILE__, __LINE__);
    return NULL;
  }

  // get the shininess factor of the core material
  float shininess;
  dataSrc.readFloat(shininess);

  // check if an error happened
  if(!dataSrc.ok())
  {
    dataSrc.setError();
    pCoreMaterial->destroy();
    delete pCoreMaterial;
    return 0;
  }

  // set the colors and the shininess
  pCoreMaterial->setAmbientColor(ambientColor);
  pCoreMaterial->setDiffuseColor(diffuseColor);
  pCoreMaterial->setSpecularColor(specularColor);
  pCoreMaterial->setShininess(shininess);

  // read the number of maps
  int mapCount;
  if(!dataSrc.readInteger(mapCount) || (mapCount < 0))
  {
    CalError::setLastError(CalError::INVALID_FILE_FORMAT, __FILE__, __LINE__);
    return 0;
  }

  // reserve memory for all the material data
  if(!pCoreMaterial->reserve(mapCount))
  {
    CalError::setLastError(CalError::MEMORY_ALLOCATION_FAILED, __FILE__, __LINE__);
    pCoreMaterial->destroy();
    delete pCoreMaterial;
    return 0;
  }

  // load all maps
  int mapId;
  for(mapId = 0; mapId < mapCount; ++mapId)
  {
    CalCoreMaterial::Map map;

    // read the filename of the map
    std::string strName;
    dataSrc.readString(map.strFilename);

    // if we support map types, read the type of the map
    if( hasMaterialTypes ) {
      dataSrc.readString(map.mapType);
    } else {
      map.mapType = "";
    }
    
    // initialize the user data
    map.userData = 0;

    // check if an error happened
    if(!dataSrc.ok())
    {
      CalError::setLastError(CalError::INVALID_FILE_FORMAT, __FILE__, __LINE__);
      pCoreMaterial->destroy();
      delete pCoreMaterial;
      return 0;
    }

    // set map in the core material instance
    pCoreMaterial->setMap(mapId, map);
  }

  return pCoreMaterial;
}

 /*****************************************************************************/
/** Loads a core mesh instance.
  *
  * This function loads a core mesh instance from a data source.
  *
  * @param dataSrc The data source to load the core mesh instance from.
  *
  * @return One of the following values:
  *         \li a pointer to the core mesh
  *         \li \b 0 if an error happened
  *****************************************************************************/

CalCoreMesh *CalLoader::loadCoreMesh(CalDataSource& dataSrc)
{

  // check if this is a valid file
  char magic[4];
  if(!dataSrc.readBytes(&magic[0], 4) || (memcmp(&magic[0], Cal::MESH_FILE_MAGIC, 4) != 0))
  {
    CalError::setLastError(CalError::INVALID_FILE_FORMAT, __FILE__, __LINE__);
    return 0;
  }

  // check if the version is compatible with the library
  int version;
  if(!dataSrc.readInteger(version) || (version < Cal::EARLIEST_COMPATIBLE_FILE_VERSION) || (version > Cal::CURRENT_FILE_VERSION))
  {
    CalError::setLastError(CalError::INCOMPATIBLE_FILE_VERSION, __FILE__, __LINE__);
    return 0;
  }

  bool hasVertexColors = (version >= Cal::FIRST_FILE_VERSION_WITH_VERTEX_COLORS);
  bool hasMorphTargetsInMorphFiles = (version >= Cal::FIRST_FILE_VERSION_WITH_MORPH_TARGETS_IN_MORPH_FILES);
  
  // get the number of submeshes
  int submeshCount;
  if(!dataSrc.readInteger(submeshCount))
  {
    CalError::setLastError(CalError::INVALID_FILE_FORMAT, __FILE__, __LINE__);
    return 0;
  }

  // allocate a new core mesh instance
  CalCoreMesh *pCoreMesh;
  pCoreMesh = new CalCoreMesh();
  if(pCoreMesh == 0)
  {
    CalError::setLastError(CalError::MEMORY_ALLOCATION_FAILED, __FILE__, __LINE__);
    return 0;
  }

  // create the core mesh instance
  if(!pCoreMesh->create())
  {
    delete pCoreMesh;
    return 0;
  }

  // load all core submeshes
  int submeshId;
  for(submeshId = 0; submeshId < submeshCount; ++submeshId)
  {
    // load the core submesh
    CalCoreSubmesh *pCoreSubmesh;
    pCoreSubmesh = loadCoreSubmesh(dataSrc, version);
    if(pCoreSubmesh == 0)
    {
      pCoreMesh->destroy();
      delete pCoreMesh;
      return 0;
    }

    // add the core submesh to the core mesh instance
    pCoreMesh->addCoreSubmesh(pCoreSubmesh);
  }

  return pCoreMesh;
}

 /*****************************************************************************/
/** Loads a core skeleton instance.
  *
  * This function loads a core skeleton instance from a data source.
  *
  * @param dataSrc The data source to load the core skeleton instance from.
  *
  * @return One of the following values:
  *         \li a pointer to the core skeleton
  *         \li \b 0 if an error happened
  *****************************************************************************/

CalCoreSkeleton *CalLoader::loadCoreSkeleton(CalDataSource& dataSrc)
{

  // check if this is a valid file
  char magic[4];
  if(!dataSrc.readBytes(&magic[0], 4) || (memcmp(&magic[0], Cal::SKELETON_FILE_MAGIC, 4) != 0))
  {
    CalError::setLastError(CalError::INVALID_FILE_FORMAT, __FILE__, __LINE__);
    return 0;
  }

  // check if the version is compatible with the library
  int version;
  if(!dataSrc.readInteger(version) || (version < Cal::EARLIEST_COMPATIBLE_FILE_VERSION) || (version > Cal::CURRENT_FILE_VERSION))
  {
    CalError::setLastError(CalError::INCOMPATIBLE_FILE_VERSION, __FILE__, __LINE__);
    return 0;
  }

  bool hasNodeLights = (version >= Cal::FIRST_FILE_VERSION_WITH_NODE_LIGHTS);
  
  // read the number of bones
  int boneCount;
  if(!dataSrc.readInteger(boneCount) || (boneCount <= 0))
  {
    CalError::setLastError(CalError::INVALID_FILE_FORMAT, __FILE__, __LINE__);
    return 0;
  }

  // allocate a new core skeleton instance
  CalCoreSkeleton *pCoreSkeleton = new CalCoreSkeleton();
  if(pCoreSkeleton == 0)
  {
    CalError::setLastError(CalError::MEMORY_ALLOCATION_FAILED, __FILE__, __LINE__);
    return 0;
  }

  // create the core skeleton instance
  if(!pCoreSkeleton->create())
  {
    delete pCoreSkeleton;
    return 0;
  }

  // load the scene ambient
  if( hasNodeLights ) {
    CalVector sceneColor;
    CalVectorFromDataSrc( dataSrc, &sceneColor );
    pCoreSkeleton->setSceneAmbientColor(sceneColor);
  }
  
  // load all core bones
  for(int boneId = 0; boneId < boneCount; ++boneId)
  {
    // load the core bone
    CalCoreBone *pCoreBone = loadCoreBones(dataSrc, version);
    if(pCoreBone == 0)
    {
      pCoreSkeleton->destroy();
      delete pCoreSkeleton;
      return 0;
    }

    // set the core skeleton of the core bone instance
    pCoreBone->setCoreSkeleton(pCoreSkeleton);

    // add the core bone to the core skeleton instance
    pCoreSkeleton->addCoreBone(pCoreBone);

    // add a core skeleton mapping of the bone's name for quick reference later
    pCoreSkeleton->mapCoreBoneName(boneId, pCoreBone->getName());

  }

  // calculate state of the core skeleton
  pCoreSkeleton->calculateState();

  return pCoreSkeleton;
}



 /*****************************************************************************/
/** Loads a core bone instance.
  *
  * This function loads a core bone instance from a data source.
  *
  * @param dataSrc The data source to load the core bone instance from.
  *
  * @return One of the following values:
  *         \li a pointer to the core bone
  *         \li \b 0 if an error happened
  *****************************************************************************/

CalCoreBone *CalLoader::loadCoreBones(CalDataSource& dataSrc, int version)
{
  bool hasNodeLights = (version >= Cal::FIRST_FILE_VERSION_WITH_NODE_LIGHTS);

  if(!dataSrc.ok())
  {
    dataSrc.setError();
    return 0;
  }

  // read the name of the bone
  std::string strName;
  dataSrc.readString(strName);

  // get the translation of the bone
  float tx, ty, tz;
  dataSrc.readFloat(tx);
  dataSrc.readFloat(ty);
  dataSrc.readFloat(tz);

  // get the rotation of the bone
  float rx, ry, rz, rw;
  dataSrc.readFloat(rx);
  dataSrc.readFloat(ry);
  dataSrc.readFloat(rz);
  dataSrc.readFloat(rw);

  // get the bone space translation of the bone
  float txBoneSpace, tyBoneSpace, tzBoneSpace;
  dataSrc.readFloat(txBoneSpace);
  dataSrc.readFloat(tyBoneSpace);
  dataSrc.readFloat(tzBoneSpace);

  // get the bone space rotation of the bone
  float rxBoneSpace, ryBoneSpace, rzBoneSpace, rwBoneSpace;
  dataSrc.readFloat(rxBoneSpace);
  dataSrc.readFloat(ryBoneSpace);
  dataSrc.readFloat(rzBoneSpace);
  dataSrc.readFloat(rwBoneSpace);

  // get the parent bone id
  int parentId;
  dataSrc.readInteger(parentId);

  // get the lgith type and light color
  int lightType = LIGHT_TYPE_NONE;
  CalVector lightColor;
  if(hasNodeLights){
    dataSrc.readInteger(lightType);
    CalVectorFromDataSrc(dataSrc, &lightColor);
  }

  
  CalQuaternion rot(rx,ry,rz,rw);
  CalQuaternion rotbs(rxBoneSpace, ryBoneSpace, rzBoneSpace, rwBoneSpace);
  CalVector trans(tx,ty,tz);

  if (loadingMode & LOADER_ROTATE_X_AXIS)
  {
    if (parentId == -1) // only root bone necessary
    {
      // Root bone must have quaternion rotated
      CalQuaternion x_axis_90(0.7071067811f,0.0f,0.0f,0.7071067811f);
      rot *= x_axis_90;
      // Root bone must have translation rotated also
      trans *= x_axis_90;
    }
  }
  

  // check if an error happened
  if(!dataSrc.ok())
  {
    dataSrc.setError();
    return 0;
  }

  // allocate a new core bone instance
  CalCoreBone *pCoreBone;
  pCoreBone = new CalCoreBone();
  if(pCoreBone == 0)
  {
    CalError::setLastError(CalError::MEMORY_ALLOCATION_FAILED, __FILE__, __LINE__);
    return 0;
  }

  // create the core bone instance
  if(!pCoreBone->createWithName(strName.c_str()))
  {
    delete pCoreBone;
    return 0;
  }

  // set the parent of the bone
  pCoreBone->setParentId(parentId);

  // set all attributes of the bone
  pCoreBone->setTranslation(trans);
  pCoreBone->setRotation(rot);
  pCoreBone->setTranslationBoneSpace(CalVector(txBoneSpace, tyBoneSpace, tzBoneSpace));
  pCoreBone->setRotationBoneSpace(rotbs);
  if( hasNodeLights ) {
    pCoreBone->setLightType( (CalLightType)lightType );
    pCoreBone->setLightColor( lightColor );
  }

  // read the number of children
  int childCount;
  if(!dataSrc.readInteger(childCount) || (childCount < 0))
  {
    pCoreBone->destroy();
    delete pCoreBone;
    CalError::setLastError(CalError::INVALID_FILE_FORMAT, __FILE__, __LINE__);
    return 0;
  }

  // load all children ids
  for(; childCount > 0; childCount--)
  {
    int childId;
    if(!dataSrc.readInteger(childId) || (childId < 0))
    {
      pCoreBone->destroy();
      delete pCoreBone;
      CalError::setLastError(CalError::INVALID_FILE_FORMAT, __FILE__, __LINE__);
      return 0;
    }

    pCoreBone->addChildId(childId);
  }

  return pCoreBone;
}




bool
CalLoader::usesAnimationCompression( int version )
{
  return (version >= Cal::FIRST_FILE_VERSION_WITH_ANIMATION_COMPRESSION);
}



#if 0
void
TestAnimationCompression( CalCoreBone * coreboneOrNull, int version, 
                         CalCoreKeyframe * prevCoreKeyframe,
                         bool translationRequired, bool highRangeRequired, bool translationIsDynamic, 
                         CalVector * translationInOut, 
                         CalQuaternion * rotationInOut, 
                         float * calTimeInOut )
{
  highRangeRequired = true;
  bool needTranslation = true;
  unsigned char buf[ 100 ];
  unsigned char * p = buf;
  std::string strFilename( "test" );
  unsigned int bytesWritten = CalLoader::writeCompressedKeyframe( buf, 100, strFilename, * translationInOut, * rotationInOut, * calTimeInOut, 
    Cal::CURRENT_FILE_VERSION, needTranslation, highRangeRequired );
  assert( bytesWritten != 0 );
  unsigned int bytesRead = CalLoader::readCompressedKeyframe( buf, bytesWritten, coreboneOrNull, 
    translationInOut, rotationInOut, calTimeInOut,
    prevCoreKeyframe,
    translationRequired, highRangeRequired, translationIsDynamic,
    useAnimationCompression);
  assert( bytesWritten == bytesRead );
}
#endif


 /*****************************************************************************/
/** Loads a core keyframe instance.
  *
  * This function loads a core keyframe instance from a data source.
  *
  * @param dataSrc The data source to load the core keyframe instance from.
  *
  * @return One of the following values:
  *         \li a pointer to the core keyframe
  *         \li \b 0 if an error happened
  *****************************************************************************/

CalCoreKeyframe *CalLoader::loadCoreKeyframe(
  CalDataSource& dataSrc, CalCoreBone * coreboneOrNull, int version, 
  CalCoreKeyframe * prevCoreKeyframe,
  bool translationRequired, bool highRangeRequired, bool translationIsDynamic,
  bool useAnimationCompression)
{
  if(!dataSrc.ok())
  {
    dataSrc.setError();
    return 0;
  }

  float time;
  float tx, ty, tz;
  float rx, ry, rz, rw;
  if( useAnimationCompression ) {
    unsigned int bytesRequired = compressedKeyframeRequiredBytes( prevCoreKeyframe, translationRequired, highRangeRequired, translationIsDynamic );
    assert( bytesRequired < 100 );
    unsigned char buf[ 100 ];
    if( !dataSrc.readBytes( buf, bytesRequired ) ) {
      CalError::setLastError(CalError::INVALID_FILE_FORMAT, __FILE__, __LINE__);
      return NULL;
    }
    CalVector vec;
    CalQuaternion quat;
    unsigned int bytesRead = readCompressedKeyframe( buf, bytesRequired, coreboneOrNull, 
      & vec, & quat, & time, prevCoreKeyframe,
      translationRequired, highRangeRequired, translationIsDynamic,
      useAnimationCompression);
    if( bytesRead != bytesRequired ) {
      CalError::setLastError(CalError::INVALID_FILE_FORMAT, __FILE__, __LINE__);
      return NULL;
    }
    tx = vec.x;
    ty = vec.y;
    tz = vec.z;
    rx = quat.x;
    ry = quat.y;
    rz = quat.z;
    rw = quat.w;
    if(version < Cal::FIRST_FILE_VERSION_WITH_ANIMATION_COMPRESSION6 ) {
      if(version >= Cal::FIRST_FILE_VERSION_WITH_ANIMATION_COMPRESSION4 ) {
        if( version >= Cal::FIRST_FILE_VERSION_WITH_ANIMATION_COMPRESSION5 ) {
          if( TranslationWritten( prevCoreKeyframe, translationRequired, translationIsDynamic ) ) {
            dataSrc.readFloat(tx);
            dataSrc.readFloat(ty);
            dataSrc.readFloat(tz);
          }
        }
        
        // get the rotation of the bone
        dataSrc.readFloat(rx);
        dataSrc.readFloat(ry);
        dataSrc.readFloat(rz);
        dataSrc.readFloat(rw);
      }
    }
  } else {
    dataSrc.readFloat(time);
    
    // get the translation of the bone
    dataSrc.readFloat(tx);
    dataSrc.readFloat(ty);
    dataSrc.readFloat(tz);

    if (coreboneOrNull && TranslationInvalid(CalVector(tx, ty, tz))) {
      CalVector tv = coreboneOrNull->getTranslation();
      tx = tv.x;
      ty = tv.y;
      tz = tv.z;
    }
    
    // get the rotation of the bone
    dataSrc.readFloat(rx);
    dataSrc.readFloat(ry);
    dataSrc.readFloat(rz);
    dataSrc.readFloat(rw);
  }  


  // check if an error happened
  if(!dataSrc.ok())
  {
    dataSrc.setError();
    return 0;
  }

  // allocate a new core keyframe instance
  CalCoreKeyframe *pCoreKeyframe;
  pCoreKeyframe = new CalCoreKeyframe();
  if(pCoreKeyframe == 0)
  {
    CalError::setLastError(CalError::MEMORY_ALLOCATION_FAILED, __FILE__, __LINE__);
    return 0;
  }

  // create the core keyframe instance
  if(!pCoreKeyframe->create())
  {
    delete pCoreKeyframe;
    return 0;
  }

  // set all attributes of the keyframe
  pCoreKeyframe->setTime(time);
  pCoreKeyframe->setTranslation(CalVector(tx, ty, tz));
  pCoreKeyframe->setRotation(CalQuaternion(rx, ry, rz, rw));

  return pCoreKeyframe;
}


// Return the number of bytes required by the compressed binary format of a keyframe with these attributes.
unsigned int
CalLoader::compressedKeyframeRequiredBytes( CalCoreKeyframe * lastCoreKeyframe, bool translationRequired, bool highRangeRequired, bool translationIsDynamic )
{
  unsigned int bytes = 0;
  if( translationRequired ) {
    
    // If I am not the first keyframe in the track, and the translations are not dynamic (meaning
    // they are the same for all the keyframes in the track, though different from the skeleton), then
    // just use the translation from the last keyframe.
    if( lastCoreKeyframe && !translationIsDynamic ) {
    } else {
      if( highRangeRequired ) {
        bytes += keyframePosBytes;
      } else {
        bytes += keyframePosBytesSmall;
      }
    }
  }
  bytes += 6;
  return bytes;
}



static float const InvalidCoord = 1e10;

void
SetTranslationInvalid( float * xResult, float * yResult, float * zResult )
{
  * xResult = InvalidCoord;
  * yResult = InvalidCoord;
  * zResult = InvalidCoord;
}

void
SetTranslationInvalid( CalVector * result )
{
  result->set( InvalidCoord, InvalidCoord, InvalidCoord );
}

bool
TranslationInvalid(float x, float y, float z)
{
  return x == InvalidCoord
      && y == InvalidCoord
      && z == InvalidCoord;
}

bool
TranslationInvalid( CalVector const & result )
{
  return result.x == InvalidCoord
      && result.y == InvalidCoord
      && result.z == InvalidCoord;
}


// Pass in the number of bytes that are valid.
// Returns number of byts read.
unsigned int
CalLoader::readCompressedKeyframe(
  unsigned char * buf, unsigned int bytes, CalCoreBone * coreboneOrNull, 
  CalVector * vecResult, CalQuaternion * quatResult, float * timeResult,
  CalCoreKeyframe * lastCoreKeyframe,
  bool translationRequired, bool highRangeRequired, bool translationIsDynamic,
  bool useAnimationCompression)
{
  unsigned char * bufStart = buf;
  
  // Read in the translation or get it from the skeleton or zero it out as a last resort.
  if( translationRequired ) {
    
    // If I am not the first keyframe in the track, and the translations are not dynamic (meaning
    // they are the same for all the keyframes in the track, though different from the skeleton), then
    // just use the translation from the last keyframe.
    if( lastCoreKeyframe && !translationIsDynamic ) {
      * vecResult = lastCoreKeyframe->getTranslation();
    } else {
      unsigned int data;
      float tx, ty, tz;
      if( highRangeRequired ) {
        BitReader br( buf );
        
        // Read x.
        br.read( & data, keyframeBitsPerUnsignedPosComponent );
        tx = FixedPointToFloatZeroToOne( data, keyframeBitsPerUnsignedPosComponent ) * keyframePosRange;
        br.read( & data, 1 );
        if( data ) tx = - tx;
        
        // Read y.
        br.read( & data, keyframeBitsPerUnsignedPosComponent );
        ty = FixedPointToFloatZeroToOne( data, keyframeBitsPerUnsignedPosComponent ) * keyframePosRange;
        br.read( & data, 1 );
        if( data ) ty = - ty;
        
        // Read z.
        br.read( & data, keyframeBitsPerUnsignedPosComponent );
        tz = FixedPointToFloatZeroToOne( data, keyframeBitsPerUnsignedPosComponent ) * keyframePosRange;
        br.read( & data, 1 );
        if( data ) tz = - tz;
        
        // Now even it off to n bytes.
        br.read( & data, keyframeBitsPerPosPadding );
        assert( br.bytesRead() == keyframePosBytes );
        buf += keyframePosBytes;
      } else {
        BitReader br( buf );
        
        // Read x.
        br.read( & data, keyframeBitsPerUnsignedPosComponentSmall );
        tx = FixedPointToFloatZeroToOne( data, keyframeBitsPerUnsignedPosComponentSmall ) * keyframePosRangeSmall;
        br.read( & data, 1 );
        if( data ) tx = - tx;
        
        // Read y.
        br.read( & data, keyframeBitsPerUnsignedPosComponentSmall );
        ty = FixedPointToFloatZeroToOne( data, keyframeBitsPerUnsignedPosComponentSmall ) * keyframePosRangeSmall;
        br.read( & data, 1 );
        if( data ) ty = - ty;
        
        // Read z.
        br.read( & data, keyframeBitsPerUnsignedPosComponentSmall );
        tz = FixedPointToFloatZeroToOne( data, keyframeBitsPerUnsignedPosComponentSmall ) * keyframePosRangeSmall;
        br.read( & data, 1 );
        if( data ) tz = - tz;
        
        // Now even it off to n bytes.
        br.read( & data, keyframeBitsPerPosPaddingSmall );
        assert( br.bytesRead() == keyframePosBytesSmall );
        buf += keyframePosBytesSmall;
      }
      vecResult->set( tx, ty, tz );
    }
  } else {
    SetTranslationInvalid( vecResult );
    if( coreboneOrNull ) {
      *vecResult = coreboneOrNull->getTranslation();
    }
  }
  
  // Read in the quat and time.
  float quat[ 4 ];
  unsigned int steps;
  unsigned int bytesRead = ReadQuatAndExtra( buf, quat, & steps, keyframeBitsPerOriComponent, keyframeBitsPerTime );
  buf += 6;
  assert( bytesRead == 6 );
  quatResult->set( quat[ 0 ], quat[ 1 ], quat[ 2 ], quat[ 3 ] );
  * timeResult = steps / 30.0f;
  return buf - bufStart;
}



unsigned int
CalLoader::writeCompressedKeyframe( unsigned char * buf, unsigned int bufLen, const std::string& strFilename, 
                            CalVector const & translation, CalQuaternion const & rotation, float caltime,
                            int version, 
                            bool translationWritten, bool highRangeRequired )
{
  assert( CalLoader::usesAnimationCompression( version ) );
  assert( bufLen >= CalLoader::keyframePosBytes );
  
  // Write the translation iff necessary.
  float posRange;
  unsigned int posBits;
  unsigned int padBits;
  unsigned int bytesRequired;
  unsigned int bytesWritten = 0;
  if( highRangeRequired ) {
    padBits = CalLoader::keyframeBitsPerPosPadding;
    posBits = CalLoader::keyframeBitsPerUnsignedPosComponent;
    posRange = CalLoader::keyframePosRange;
    bytesRequired = CalLoader::keyframePosBytes;
  } else {
    padBits = CalLoader::keyframeBitsPerPosPaddingSmall;
    posBits = CalLoader::keyframeBitsPerUnsignedPosComponentSmall;
    posRange = CalLoader::keyframePosRangeSmall;
    bytesRequired = CalLoader::keyframePosBytesSmall;
  }
  if( translationWritten ) {
    BitWriter bw( buf );
    float len;
    unsigned int sign;
    unsigned int data;
    unsigned int i;
    for( i = 0; i < 3; i++ ) {
      sign = 0;
      len = translation[ i ] / posRange;
      if( len < 0.0f ) {
        sign = 1;
        len = - len;
      }
      if( len > 1.0f ) {
        CalError::setLastError(CalError::FILE_WRITING_FAILED, __FILE__, __LINE__, strFilename);
        return 0;
      }
      data = FloatZeroToOneToFixedPoint( len, posBits );
      bw.write( data, posBits );
      bw.write( sign, 1 );
    }
    
    // Now even it off to 4 or 8 bytes, depending on highRangeRequired.
    bw.write( data, padBits );
    assert( bw.bytesWritten() == bytesRequired );
    buf += bytesRequired;
    bytesWritten += bytesRequired;
  }
  
  // Write the quat and time.
  float wquat[] = { rotation.x, rotation.y, rotation.z, rotation.w };

  unsigned int steps = ( unsigned int ) floor( caltime * 30 + 0.5 );

  /*
  Removed the animation time limit so Matt can do his ice skating room.

  if( steps >= keyframeTimeMax ) {
    CalError::setLastError(CalError::FILE_WRITING_FAILED, __FILE__, __LINE__, strFilename);
    return 0;
  }
  */

  unsigned int bw = WriteQuatAndExtra( buf, wquat, steps, CalLoader::keyframeBitsPerOriComponent, CalLoader::keyframeBitsPerTime );
  assert( bw == 6 );
  buf += 6;
  bytesWritten += 6;
  return bytesWritten;
}



 /*****************************************************************************/
/** Loads a core morphKeyframe instance.
  *
  * This function loads a core morphKeyframe instance from a data source.
  *
  * @param dataSrc The data source to load the core morphKeyframe instance from.
  *
  * @return One of the following values:
  *         \li a pointer to the core morphKeyframe
  *         \li \b 0 if an error happened
  *****************************************************************************/

CalCoreMorphKeyframe *CalLoader::loadCoreMorphKeyframe(CalDataSource& dataSrc)
{
  if(!dataSrc.ok())
  {
    dataSrc.setError();
    return 0;
  }

  // get the time of the morphKeyframe
  float time;
  dataSrc.readFloat(time);

  // get the translation of the bone
  float weight;
  dataSrc.readFloat(weight);

  // check if an error happened
  if(!dataSrc.ok())
  {
    dataSrc.setError();
    return 0;
  }

  // allocate a new core morphKeyframe instance
  CalCoreMorphKeyframe *pCoreMorphKeyframe;
  pCoreMorphKeyframe = new CalCoreMorphKeyframe();
  if(pCoreMorphKeyframe == 0)
  {
    CalError::setLastError(CalError::MEMORY_ALLOCATION_FAILED, __FILE__, __LINE__);
    return 0;
  }

  // create the core morphKeyframe instance
  if(!pCoreMorphKeyframe->create())
  {
    delete pCoreMorphKeyframe;
    return 0;
  }

  // set all attributes of the morphKeyframe
  pCoreMorphKeyframe->setTime(time);
  pCoreMorphKeyframe->setWeight(weight);

  return pCoreMorphKeyframe;
}


 /*****************************************************************************/
/** Loads a core submesh instance.
  *
  * This function loads a core submesh instance from a data source.
  *
  * @param dataSrc The data source to load the core submesh instance from.
  *
  * @return One of the following values:
  *         \li a pointer to the core submesh
  *         \li \b 0 if an error happened
  *****************************************************************************/

CalCoreSubmesh *CalLoader::loadCoreSubmesh(CalDataSource& dataSrc, int version)
{
  if(!dataSrc.ok())
  {
    dataSrc.setError();
    return 0;
  }

  bool hasVertexColors = (version >= Cal::FIRST_FILE_VERSION_WITH_VERTEX_COLORS);
  bool hasMorphTargetsInMorphFiles = (version >= Cal::FIRST_FILE_VERSION_WITH_MORPH_TARGETS_IN_MORPH_FILES);

  // get the material thread id of the submesh
  int coreMaterialThreadId;
  dataSrc.readInteger(coreMaterialThreadId);

  // get the number of vertices, faces, level-of-details and springs
  int vertexCount;
  dataSrc.readInteger(vertexCount);

  int faceCount;
  dataSrc.readInteger(faceCount);

  int lodCount;
  dataSrc.readInteger(lodCount);

  int springCount;
  dataSrc.readInteger(springCount);

  // get the number of texture coordinates per vertex
  int textureCoordinateCount;
  dataSrc.readInteger(textureCoordinateCount);

  int morphCount = 0;
  if( hasMorphTargetsInMorphFiles ) {
    dataSrc.readInteger(morphCount);
  }

  // check if an error happened
  if(!dataSrc.ok())
  {
    dataSrc.setError();
    return 0;
  }

  // allocate a new core submesh instance
  CalCoreSubmesh *pCoreSubmesh;
  pCoreSubmesh = new CalCoreSubmesh();
  if(pCoreSubmesh == 0)
  {
    CalError::setLastError(CalError::MEMORY_ALLOCATION_FAILED, __FILE__, __LINE__);
    return 0;
  }

  // create the core submesh instance
  if(!pCoreSubmesh->create())
  {
    delete pCoreSubmesh;
    return 0;
  }

  // set the LOD step count
  pCoreSubmesh->setLodCount(lodCount);

  // set the core material id
  pCoreSubmesh->setCoreMaterialThreadId(coreMaterialThreadId);

  // reserve memory for all the submesh data
  if(!pCoreSubmesh->reserve(vertexCount, textureCoordinateCount, faceCount, springCount))
  {
    CalError::setLastError(CalError::MEMORY_ALLOCATION_FAILED, __FILE__, __LINE__);
    pCoreSubmesh->destroy();
    delete pCoreSubmesh;
    return 0;
  }

  // load the tangent space enable flags.
  int textureCoordinateId;
  for (textureCoordinateId = 0; textureCoordinateId < textureCoordinateCount; textureCoordinateId++)
  {
	  pCoreSubmesh->enableTangents(textureCoordinateId, false);
  }

  // load all vertices and their influences
  pCoreSubmesh->setHasNonWhiteVertexColors( false );
  int vertexId;
  for(vertexId = 0; vertexId < vertexCount; ++vertexId)
  {
    CalCoreSubmesh::Vertex vertex;

    // load data of the vertex
    dataSrc.readFloat(vertex.position.x);
    dataSrc.readFloat(vertex.position.y);
    dataSrc.readFloat(vertex.position.z);
    dataSrc.readFloat(vertex.normal.x);
    dataSrc.readFloat(vertex.normal.y);
    dataSrc.readFloat(vertex.normal.z);
    vertex.vertexColor.x = 1.0f;
    vertex.vertexColor.y = 1.0f;
    vertex.vertexColor.z = 1.0f;
    if( hasVertexColors ) {
      dataSrc.readFloat(vertex.vertexColor.x);
      dataSrc.readFloat(vertex.vertexColor.y);
      dataSrc.readFloat(vertex.vertexColor.z);
      if( vertex.vertexColor.x != 1.0f
        || vertex.vertexColor.y != 1.0f
        || vertex.vertexColor.z != 1.0f ) {
        pCoreSubmesh->setHasNonWhiteVertexColors( true );
      }
    }
    dataSrc.readInteger(vertex.collapseId);
    dataSrc.readInteger(vertex.faceCollapseCount);

    // check if an error happened
    if(!dataSrc.ok())
    {
      dataSrc.setError();
      pCoreSubmesh->destroy();
      delete pCoreSubmesh;
      return 0;
    }

    // load all texture coordinates of the vertex
    int textureCoordinateId;
    for(textureCoordinateId = 0; textureCoordinateId < textureCoordinateCount; ++textureCoordinateId)
    {
      CalCoreSubmesh::TextureCoordinate textureCoordinate;

      // load data of the influence
      dataSrc.readFloat(textureCoordinate.u);
      dataSrc.readFloat(textureCoordinate.v);

      if (loadingMode & LOADER_INVERT_V_COORD)
      {
          textureCoordinate.v = 1.0f - textureCoordinate.v;
      }

      // check if an error happened
      if(!dataSrc.ok())
      {
        dataSrc.setError();
        pCoreSubmesh->destroy();
        delete pCoreSubmesh;
        return 0;
      }

      // set texture coordinate in the core submesh instance
      pCoreSubmesh->setTextureCoordinate(vertexId, textureCoordinateId, textureCoordinate);
    }

    // get the number of influences
    int influenceCount;
    if(!dataSrc.readInteger(influenceCount) || (influenceCount < 0))
    {
      dataSrc.setError();
      pCoreSubmesh->destroy();
      delete pCoreSubmesh;
      return 0;
    }

    // reserve memory for the influences in the vertex
    vertex.vectorInfluence.reserve(influenceCount);
    vertex.vectorInfluence.resize(influenceCount);

    // load all influences of the vertex
    int influenceId;
    for(influenceId = 0; influenceId < influenceCount; ++influenceId)
    {
      // load data of the influence
      dataSrc.readInteger(vertex.vectorInfluence[influenceId].boneId),
      dataSrc.readFloat(vertex.vectorInfluence[influenceId].weight);

      // check if an error happened
      if(!dataSrc.ok())
      {
        dataSrc.setError();
        pCoreSubmesh->destroy();
        delete pCoreSubmesh;
        return 0;
      }
    }

    // set vertex in the core submesh instance
    pCoreSubmesh->setVertex(vertexId, vertex);

    // load the physical property of the vertex if there are springs in the core submesh
    if(springCount > 0)
    {
      CalCoreSubmesh::PhysicalProperty physicalProperty;

      // load data of the physical property
      dataSrc.readFloat(physicalProperty.weight);

      // check if an error happened
      if(!dataSrc.ok())
      {
        dataSrc.setError();
        pCoreSubmesh->destroy();
        delete pCoreSubmesh;
        return 0;
      }

      // set the physical property in the core submesh instance
      pCoreSubmesh->setPhysicalProperty(vertexId, physicalProperty);
    }
  }

  // load all springs
  int springId;
  for(springId = 0; springId < springCount; ++springId)
  {
    CalCoreSubmesh::Spring spring;

    // load data of the spring
    dataSrc.readInteger(spring.vertexId[0]);
    dataSrc.readInteger(spring.vertexId[1]);
    dataSrc.readFloat(spring.springCoefficient);
    dataSrc.readFloat(spring.idleLength);

    // check if an error happened
    if(!dataSrc.ok())
    {
      dataSrc.setError();
      pCoreSubmesh->destroy();
      delete pCoreSubmesh;
      return 0;
    }

    // set spring in the core submesh instance
    pCoreSubmesh->setSpring(springId, spring);
  }

  for( int morphId = 0; morphId < morphCount; morphId++ ) {
    CalCoreSubMorphTarget * morphTarget = new CalCoreSubMorphTarget();
    if( !morphTarget->create() ) {
      pCoreSubmesh->destroy();
      delete pCoreSubmesh;
      dataSrc.setError();
      return false;
    }
    if( !morphTarget->reserve(vertexCount) ) {
      pCoreSubmesh->destroy();
      delete pCoreSubmesh;
      dataSrc.setError();
      return false;
    }

    std::string morphName;
    dataSrc.readString(morphName);
    morphTarget->setName(morphName);
           
    int blendVertId;
    dataSrc.readInteger(blendVertId);
    
    for( int blendVertI = 0; blendVertI < vertexCount; blendVertI++ )
    {
      CalCoreSubMorphTarget::BlendVertex Vertex;
      Vertex.textureCoords.clear();
      Vertex.textureCoords.reserve(textureCoordinateCount);

      bool copyOrig;

      if( blendVertI < blendVertId ) {
        copyOrig = true;
      } else {
        copyOrig = false;
      }
      
      if( !copyOrig ) {
        CalVectorFromDataSrc(dataSrc, &Vertex.position);
        CalVectorFromDataSrc(dataSrc, &Vertex.normal);
        int textureCoordinateId;
        for(textureCoordinateId = 0; textureCoordinateId < textureCoordinateCount; ++textureCoordinateId)
        {
          CalCoreSubmesh::TextureCoordinate textureCoordinate;
          dataSrc.readFloat(textureCoordinate.u);
          dataSrc.readFloat(textureCoordinate.v);

          if (loadingMode & LOADER_INVERT_V_COORD)
          {
            textureCoordinate.v = 1.0f - textureCoordinate.v;
          }
          Vertex.textureCoords.push_back(textureCoordinate);
        }
        if( ! dataSrc.ok() ) {
          pCoreSubmesh->destroy();
          delete pCoreSubmesh;
          morphTarget->destroy();
          delete morphTarget;
          dataSrc.setError();
          return false;
        }

        morphTarget->setBlendVertex(blendVertI, Vertex);
        dataSrc.readInteger(blendVertId);
     } 
    }
    pCoreSubmesh->addCoreSubMorphTarget(morphTarget);
  }
    
  // load all faces
  int faceId;
  int justOnce = 0;
  bool flipModel = false;
  for(faceId = 0; faceId < faceCount; ++faceId)
  {
    CalCoreSubmesh::Face face;

    // load data of the face

	int tmp[4];
	dataSrc.readInteger(tmp[0]);
	dataSrc.readInteger(tmp[1]);
	dataSrc.readInteger(tmp[2]);

	if(sizeof(CalIndex)==2)
	{
		if(tmp[0]>65535 || tmp[1]>65535 || tmp[2]>65535)
		{			
			CalError::setLastError(CalError::INVALID_FILE_FORMAT, __FILE__, __LINE__);
			pCoreSubmesh->destroy();
			delete pCoreSubmesh;
			return 0;
		}
	}
	face.vertexId[0]=tmp[0];
	face.vertexId[1]=tmp[1];
	face.vertexId[2]=tmp[2];
	
    // check if an error happened
    if(!dataSrc.ok())
    {
      dataSrc.setError();
      pCoreSubmesh->destroy();
      delete pCoreSubmesh;
      return 0;
    }

    // check if left-handed coord system is used by the object
    // can be done only once since the object has one system for all faces
    if (justOnce==0)
    {
      // get vertexes of first face
      std::vector<CalCoreSubmesh::Vertex>& vectorVertex = pCoreSubmesh->getVectorVertex();
      CalCoreSubmesh::Vertex& v1 = vectorVertex[tmp[0]];
      CalCoreSubmesh::Vertex& v2 = vectorVertex[tmp[1]];
      CalCoreSubmesh::Vertex& v3 = vectorVertex[tmp[2]];

      CalVector point1 = CalVector(v1.position.x, v1.position.y, v1.position.z);
      CalVector point2 = CalVector(v2.position.x, v2.position.y, v2.position.z);
      CalVector point3 = CalVector(v3.position.x, v3.position.y, v3.position.z);

      // gets vectors (v1-v2) and (v3-v2)
      CalVector vect1 = point1 - point2;
      CalVector vect2 = point3 - point2;

      // calculates normal of face
      CalVector cross = vect1 % vect2;
      CalVector faceNormal = cross / cross.length();

      // compare the calculated normal with the normal of a vertex
      CalVector maxNorm = v1.normal;

      // if the two vectors point to the same direction then the poly needs flipping
      // so if the dot product > 0 it needs flipping
      if (faceNormal*maxNorm>0)
		  flipModel = true;

      justOnce = 1;
    }

    // flip if needed
    if (flipModel) {
      tmp[3] = face.vertexId[1];
      face.vertexId[1]=face.vertexId[2];
	  face.vertexId[2]=tmp[3];
    }

    // set face in the core submesh instance
    pCoreSubmesh->setFace(faceId, face);
  }

  return pCoreSubmesh;
}

 /*****************************************************************************/
/** Loads a core track instance.
  *
  * This function loads a core track instance from a data source.
  *
  * @param dataSrc The data source to load the core track instance from.
  *
  * @return One of the following values:
  *         \li a pointer to the core track
  *         \li \b 0 if an error happened
  *****************************************************************************/

CalCoreTrack *CalLoader::loadCoreTrack(
  CalDataSource& dataSrc, CalCoreSkeleton *skel,
  int version, bool useAnimationCompression)
{
  if(!dataSrc.ok())
  {
    dataSrc.setError();
    return 0;
  }

  // Read the bone id.
  int coreBoneId;
  bool translationRequired = true;
  bool highRangeRequired = true;
  bool translationIsDynamic = true;
  int keyframeCount;
  static unsigned char buf[ 4 ];

  // If this file version supports animation compression, then I store the boneId in 15 bits,
  // and use the 16th bit to record if translation is required.
  if( useAnimationCompression ) {
    if( !dataSrc.readBytes( buf, 4 ) ) {
      CalError::setLastError(CalError::INVALID_FILE_FORMAT, __FILE__, __LINE__);
      return NULL;
    }
    
    // Stored low byte first.  Top 3 bits of coreBoneId are compression flags.
    coreBoneId = buf[ 0 ] + ( unsigned int ) ( buf[ 1 ] & 0x1f ) * 256;
    translationRequired = ( buf[ 1 ] & 0x80 ) ? true : false;
    highRangeRequired = ( buf[ 1 ] & 0x40 ) ? true : false;
    translationIsDynamic = ( buf[ 1 ] & 0x20 ) ? true : false;
    keyframeCount = buf[ 2 ] + ( unsigned int ) buf[ 3 ] * 256;
    //if( keyframeCount > keyframeTimeMax ) {
    //  CalError::setLastError(CalError::INVALID_FILE_FORMAT, __FILE__, __LINE__);
    //  return NULL;
    //}
  } else {
    if(!dataSrc.readInteger(coreBoneId) || (coreBoneId < 0)) {
      CalError::setLastError(CalError::INVALID_FILE_FORMAT, __FILE__, __LINE__);
      return 0;
    }

    // Read the number of keyframes.
    if(!dataSrc.readInteger(keyframeCount) || (keyframeCount <= 0))
    {
      CalError::setLastError(CalError::INVALID_FILE_FORMAT, __FILE__, __LINE__);
      return 0;
    }
  }

// allocate a new core track instance
  CalCoreTrack *pCoreTrack;
  pCoreTrack = new CalCoreTrack();
  if(pCoreTrack == 0)
  {
    CalError::setLastError(CalError::MEMORY_ALLOCATION_FAILED, __FILE__, __LINE__);
    return 0;
  }

  // create the core track instance
  pCoreTrack->create();

  // link the core track to the appropriate core bone instance
  pCoreTrack->setCoreBoneId(coreBoneId);
  CalCoreBone * cb = NULL;
  if( skel ) {
    cb = skel->getCoreBone( coreBoneId );
  }


  // load all core keyframes
  int keyframeId;
  CalCoreKeyframe * lastCoreKeyframe = NULL;
  for(keyframeId = 0; keyframeId < keyframeCount; ++keyframeId)
  {
    // load the core keyframe
    CalCoreKeyframe *pCoreKeyframe = loadCoreKeyframe(
      dataSrc, cb, version, lastCoreKeyframe, translationRequired, highRangeRequired, translationIsDynamic,
      useAnimationCompression);
    lastCoreKeyframe = pCoreKeyframe;
    if(pCoreKeyframe == 0)
    {
      pCoreTrack->destroy();
      delete pCoreTrack;
      return 0;
    }
    if (loadingMode & LOADER_ROTATE_X_AXIS)
    {
      // Check for anim rotation
      if (skel && skel->getCoreBone(coreBoneId)->getParentId() == -1)  // root bone
      {
        // rotate root bone quaternion
        CalQuaternion rot = pCoreKeyframe->getRotation();
        CalQuaternion x_axis_90(0.7071067811f,0.0f,0.0f,0.7071067811f);
        rot *= x_axis_90;
        pCoreKeyframe->setRotation(rot);
        // rotate root bone displacement
        CalVector vec = pCoreKeyframe->getTranslation();
	    vec *= x_axis_90;
        pCoreKeyframe->setTranslation(vec);
      }
    }    

    // add the core keyframe to the core track instance
    pCoreTrack->addCoreKeyframe(pCoreKeyframe);
  }

  // Whenever I load the track, I update its translationRequired status.  The status can
  // go from required to not required, but not the other way around.
  pCoreTrack->setTranslationRequired( translationRequired );
  pCoreTrack->setHighRangeRequired( highRangeRequired );
  pCoreTrack->setTranslationIsDynamic( translationIsDynamic );
  if( collapseSequencesOn ) {
    pCoreTrack->collapseSequences( translationTolerance, rotationToleranceDegrees );
  }
  if( loadingCompressionOn ) {
    
    // This function MIGHT call setTranslationRequired() on the track.
    // Alas, you may be passing me NULL for skel, in which case compress() won't update the 
    // translationRequired flag; instead it will leave it, as above.
    pCoreTrack->compress( translationTolerance, rotationToleranceDegrees, skel );
  }

  return pCoreTrack;
}


 /*****************************************************************************/
/** Loads a core morphTrack instance.
  *
  * This function loads a core morphTrack instance from a data source.
  *
  * @param dataSrc The data source to load the core morphTrack instance from.
  *
  * @return One of the following values:
  *         \li a pointer to the core morphTrack
  *         \li \b 0 if an error happened
  *****************************************************************************/

CalCoreMorphTrack *CalLoader::loadCoreMorphTrack(CalDataSource& dataSrc)
{
  if(!dataSrc.ok())
  {
    dataSrc.setError();
    return 0;
  }

  // read the morph name
  std::string morphName;
  if(!dataSrc.readString(morphName))
  {
    CalError::setLastError(CalError::INVALID_FILE_FORMAT, __FILE__, __LINE__);
    return 0;
  }

  // allocate a new core morphTrack instance
  CalCoreMorphTrack *pCoreMorphTrack;
  pCoreMorphTrack = new CalCoreMorphTrack();
  if(pCoreMorphTrack == 0)
  {
    CalError::setLastError(CalError::MEMORY_ALLOCATION_FAILED, __FILE__, __LINE__);
    return 0;
  }

  // create the core morphTrack instance
  if(!pCoreMorphTrack->create())
  {
    delete pCoreMorphTrack;
    return 0;
  }

  // link the core morphTrack to the appropriate morph name
  pCoreMorphTrack->setMorphName(morphName);

  // read the number of keyframes
  int keyframeCount;
  if(!dataSrc.readInteger(keyframeCount) || (keyframeCount <= 0))
  {
    CalError::setLastError(CalError::INVALID_FILE_FORMAT, __FILE__, __LINE__);
    return 0;
  }

  // load all core keyframes
  int keyframeId;
  for(keyframeId = 0; keyframeId < keyframeCount; ++keyframeId)
  {
    // load the core keyframe
    CalCoreMorphKeyframe *pCoreKeyframe;
    pCoreKeyframe = loadCoreMorphKeyframe(dataSrc);
    if(pCoreKeyframe == 0)
    {
      pCoreMorphTrack->destroy();
      delete pCoreMorphTrack;
      return 0;
    }

    // add the core keyframe to the core morphTrack instance
    pCoreMorphTrack->addCoreMorphKeyframe(pCoreKeyframe);
  }

  return pCoreMorphTrack;
}



void
BitWriter::write( unsigned int data, unsigned int numBits )
{
  // I write out full bytes, so the most bits I can have residually is 7.
  assert( bitsInBuf_ <= 7 );

  // 7 + 25 = 32.
  assert( numBits <= 25 );
  buf_ |= ( data << bitsInBuf_ );
  bitsInBuf_ += numBits;

  // Write it out low order byte first.
  while( bitsInBuf_ >= 8 ) {
    dest_[ bytesWritten_ ] = ( unsigned char ) buf_;
    buf_ >>= 8;
    bitsInBuf_ -= 8;
    bytesWritten_++;
  }
}

void
BitWriter::flush()
{
  if( bitsInBuf_ != 0 ) {
    dest_[ bytesWritten_ ] = ( unsigned char ) buf_;
    bytesWritten_++;
    bitsInBuf_ = 0;
    dest_ = NULL;
  }
}

void 
CalLoader::setAnimationCollapseSequencesOn( bool p ) 
{ 
  collapseSequencesOn = p; 
}
void 
CalLoader::setAnimationLoadingCompressionOn( bool p ) 
{ 
  loadingCompressionOn = p; 
}
void 
CalLoader::setAnimationTranslationTolerance( double p )
{ 
  translationTolerance = p; 
}
void 
CalLoader::setAnimationRotationToleranceDegrees( double p ) 
{ 
  rotationToleranceDegrees = p; 
}

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

