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


This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */

#include "stdh.h"
#include "Mesh.h"

#define MESH_VERSION  12
#define MESH_ID       "MESH"

#include <Engine/Base/Stream.h>
#include <Engine/Base/Console.h>
#include <Engine/Ska/StringTable.h>
#include <Engine/Math/Projection.h>
#include <Engine/Graphics/DrawPort.h>
#include <Engine/Graphics/Shader.h>
#include <Engine/Templates/StaticArray.h>
#include <Engine/Templates/StaticArray.cpp>
#include <Engine/Templates/Stock_CShader.h>

INDEX AreVerticesDiferent(INDEX iCurentIndex, INDEX iLastIndex);

struct VertexLocator
{
  INDEX vl_iIndex;
  INDEX vl_iSubIndex;
};

struct SortArray
{
  INDEX sa_iNewIndex;
  INDEX sa_iSurfaceIndex;
  CStaticArray<struct VertexLocator> sa_aWeightMapList;
  CStaticArray<struct VertexLocator> sa_aMorphMapList;
};

CStaticArray <struct SortArray> _aSortArray;
CStaticArray <INDEX> _aiOptimizedIndex;
CStaticArray <INDEX> _aiSortedIndex;

MeshLOD *pMeshLOD;// curent mesh lod (for quick sort)
MeshLOD mshOptimized;

CMesh::CMesh()
{
}

CMesh::~CMesh()
{
}

// release old shader and obtain new shader for mesh surface (expand ShaderParams if needed)
void ChangeSurfaceShader_t(MeshSurface &msrf,CTString fnNewShader)
{
  CShader *pShaderNew = _pShaderStock->Obtain_t(fnNewShader);
  ASSERT(pShaderNew!=NULL);
  if(msrf.msrf_pShader!=NULL) _pShaderStock->Release(msrf.msrf_pShader);
  msrf.msrf_pShader = pShaderNew;
  // get new shader description
  ShaderDesc shDesc;
  msrf.msrf_pShader->GetShaderDesc(shDesc);
  // if needed expand size of arrays for new shader
  // reset new values!!!!
  INDEX ctOldTextureIDs = msrf.msrf_ShadingParams.sp_aiTextureIDs.Count();
  INDEX ctNewTextureIDs = shDesc.sd_astrTextureNames.Count();
  INDEX ctOldUVMaps     = msrf.msrf_ShadingParams.sp_aiTexCoordsIndex.Count();
  INDEX ctNewUVMaps     = shDesc.sd_astrTexCoordNames.Count();
  INDEX ctOldColors     = msrf.msrf_ShadingParams.sp_acolColors.Count();
  INDEX ctNewColors     = shDesc.sd_astrColorNames.Count();
  INDEX ctOldFloats     = msrf.msrf_ShadingParams.sp_afFloats.Count();
  INDEX ctNewFloats     = shDesc.sd_astrFloatNames.Count();
  if(ctOldTextureIDs<ctNewTextureIDs) {
    // expand texture IDs array
    msrf.msrf_ShadingParams.sp_aiTextureIDs.Expand(ctNewTextureIDs);
    // set new texture IDs to 0
    for(INDEX itx=ctOldTextureIDs;itx<ctNewTextureIDs;itx++) {
      msrf.msrf_ShadingParams.sp_aiTextureIDs[itx] = -1;
    }
  }
  // expand array of uvmaps if needed
  if(ctOldUVMaps<ctNewUVMaps) {
    // expand uvmaps IDs array
    msrf.msrf_ShadingParams.sp_aiTexCoordsIndex.Expand(ctNewUVMaps);
    // set new uvmaps indices to 0
    for(INDEX itxc=ctOldUVMaps;itxc<ctNewUVMaps;itxc++) {
      msrf.msrf_ShadingParams.sp_aiTexCoordsIndex[itxc] = 0;
    }
  }
  // expand array of colors if needed
  if(ctOldColors<ctNewColors) {
    // expand color array
    msrf.msrf_ShadingParams.sp_acolColors.Expand(ctNewColors);
    // set new colors indices white
    for(INDEX icol=ctOldUVMaps;icol<ctNewColors;icol++) {
      msrf.msrf_ShadingParams.sp_acolColors[icol] = 0xFFFFFFFF;
    }
  }
  // expand array of floats if needed
  if(ctOldFloats<ctNewFloats) {
    // expand float array
    msrf.msrf_ShadingParams.sp_afFloats.Expand(ctNewFloats);
    // set new floats to 0
    for(INDEX ifl=ctOldFloats;ifl<ctNewFloats;ifl++) {
      msrf.msrf_ShadingParams.sp_afFloats[ifl] = 1;
    }
  }
}

// quck sort func for comparing vertices
static int qsort_CompareArray(const void *pVx1, const void *pVx2)
{
  INDEX *n1 = ((INDEX*)pVx1);
  INDEX *n2 = ((INDEX*)pVx2);
  return AreVerticesDiferent(*n1,*n2);
}
// clear array of sort vertices
void ClearSortArray(INDEX ctOldVertices)
{
  for(int iv=0;iv<ctOldVertices;iv++)
  {
    _aSortArray[iv].sa_aWeightMapList.Clear();
    _aSortArray[iv].sa_aMorphMapList.Clear();
  }
  _aiOptimizedIndex.Clear();
  _aiSortedIndex.Clear();
  _aSortArray.Clear();
}
// optimize mesh
void CMesh::Optimize(void)
{
  INDEX ctmshlods = msh_aMeshLODs.Count();
  for(int imshlod=0;imshlod<ctmshlods;imshlod++)
  {
    // optimize each lod in mesh
    OptimizeLod(msh_aMeshLODs[imshlod]);
  }
}
// optimize lod of mesh
void CMesh::OptimizeLod(MeshLOD &mLod)
{
  INDEX ctVertices  = mLod.mlod_aVertices.Count();
  INDEX ctSurfaces  = mLod.mlod_aSurfaces.Count();
  INDEX ctUVMaps    = mLod.mlod_aUVMaps.Count();
  INDEX ctWeightMaps = mLod.mlod_aWeightMaps.Count();
  INDEX ctMorphMaps = mLod.mlod_aMorphMaps.Count();

  if(ctVertices<=0) return;
  
  // create array for sorting
  _aSortArray.New(ctVertices);
  _aiSortedIndex.New(ctVertices);
  _aiOptimizedIndex.New(ctVertices);
  // put original vertex indices in SortArray
  for(int iv=0;iv<ctVertices;iv++)
  {
    _aiSortedIndex[iv] = iv;
  }
  // loop each surface and expand SurfaceList in SortArray
  int is=0;
  for(;is<ctSurfaces;is++)
  {
    INDEX ctts=mLod.mlod_aSurfaces[is].msrf_aTriangles.Count();
    for(int its=0;its<ctts;its++)
    {
      MeshTriangle &mtTriangle = mLod.mlod_aSurfaces[is].msrf_aTriangles[its];

      _aSortArray[mtTriangle.iVertex[0]].sa_iSurfaceIndex = is;
      _aSortArray[mtTriangle.iVertex[1]].sa_iSurfaceIndex = is;
      _aSortArray[mtTriangle.iVertex[2]].sa_iSurfaceIndex = is;
    }
  }
  // loop each weightmap and expand sa_aWeightMapList in SortArray
  for(INDEX iw=0;iw<ctWeightMaps;iw++)
  {
    // loop each wertex weight array in weight map array
    INDEX ctwm = mLod.mlod_aWeightMaps[iw].mwm_aVertexWeight.Count();
    for(INDEX iwm=0;iwm<ctwm;iwm++)
    {
      MeshVertexWeight &mwwWeight = mLod.mlod_aWeightMaps[iw].mwm_aVertexWeight[iwm];
      // get curent list num of weightmaps  
      INDEX ctWeightMapList = _aSortArray[mwwWeight.mww_iVertex].sa_aWeightMapList.Count();
      // expand array of sufrace lists for 1
      _aSortArray[mwwWeight.mww_iVertex].sa_aWeightMapList.Expand(ctWeightMapList+1);
      // set vl_iIndex to index of surface
      // set vl_iSubIndex to index in triangle set
      VertexLocator &vxLoc = _aSortArray[mwwWeight.mww_iVertex].sa_aWeightMapList[ctWeightMapList];
      vxLoc.vl_iIndex = iw;
      vxLoc.vl_iSubIndex = iwm;
    }
  }
  // loop each morphmap and expand sa_aMorphMapList in SortArray
  for(INDEX im=0;im<ctMorphMaps;im++)
  {
    // loop each morph map in array
    INDEX ctmm = mLod.mlod_aMorphMaps[im].mmp_aMorphMap.Count();
    for(INDEX imm=0;imm<ctmm;imm++)
    {
      MeshVertexMorph &mwmMorph = mLod.mlod_aMorphMaps[im].mmp_aMorphMap[imm];
      // get curent list num of morphmaps  
      INDEX ctMorphMapList = _aSortArray[mwmMorph.mwm_iVxIndex].sa_aMorphMapList.Count();
      // expand array of sufrace lists for 1
      _aSortArray[mwmMorph.mwm_iVxIndex].sa_aMorphMapList.Expand(ctMorphMapList+1);
      // set vl_iIndex to index of surface
      // set vl_iSubIndex to index in triangle set
      VertexLocator &vxLoc = _aSortArray[mwmMorph.mwm_iVxIndex].sa_aMorphMapList[ctMorphMapList];
      vxLoc.vl_iIndex = im;
      vxLoc.vl_iSubIndex = imm;
    }
  }
  // set global pMeshLOD pointer used by quicksort
  pMeshLOD = &mLod;
  // sort array
  qsort(&_aiSortedIndex[0],ctVertices,sizeof(&_aiSortedIndex[0]),qsort_CompareArray);
  
  // compare vertices
  INDEX iDiferentVertices = 1;
  INDEX iLastIndex = _aiSortedIndex[0];
  _aSortArray[iLastIndex].sa_iNewIndex = 0;
  _aiOptimizedIndex[0] = iLastIndex;
  
  for(INDEX isa=1;isa<ctVertices;isa++)
  {
    INDEX iCurentIndex = _aiSortedIndex[isa];
    // check if vertices are diferent
    if(AreVerticesDiferent(iLastIndex,iCurentIndex))
    {
      // add Curent index to Optimized index array
      _aiOptimizedIndex[iDiferentVertices] = iCurentIndex;
      iDiferentVertices++;
      iLastIndex = iCurentIndex;
    }
    _aSortArray[iCurentIndex].sa_iNewIndex = iDiferentVertices-1;
  }

  // create new mesh
  INDEX ctNewVertices = iDiferentVertices;
  mshOptimized.mlod_aVertices.New(ctNewVertices);
  mshOptimized.mlod_aNormals.New(ctNewVertices);
  mshOptimized.mlod_aUVMaps.New(ctUVMaps);
  for(INDEX iuvm=0;iuvm<ctUVMaps;iuvm++)
  {
    mshOptimized.mlod_aUVMaps[iuvm].muv_aTexCoords.New(ctNewVertices);
  }

  // add new vertices and normals to mshOptimized
  for(INDEX iNewVx=0;iNewVx<ctNewVertices;iNewVx++)
  {
    mshOptimized.mlod_aVertices[iNewVx] = mLod.mlod_aVertices[_aiOptimizedIndex[iNewVx]];
    mshOptimized.mlod_aNormals[iNewVx] = mLod.mlod_aNormals[_aiOptimizedIndex[iNewVx]];
    for(INDEX iuvm=0;iuvm<ctUVMaps;iuvm++)
    {
      //???
      mshOptimized.mlod_aUVMaps[iuvm].muv_iID = mLod.mlod_aUVMaps[iuvm].muv_iID;
      mshOptimized.mlod_aUVMaps[iuvm].muv_aTexCoords[iNewVx] = mLod.mlod_aUVMaps[iuvm].muv_aTexCoords[_aiOptimizedIndex[iNewVx]];
    }
  }
  // remap surface triangles
  for(is=0;is<ctSurfaces;is++)
  {
    MeshSurface &msrf = mLod.mlod_aSurfaces[is];
    INDEX iMinIndex = ctNewVertices+1;
    INDEX iMaxIndex = -1;
    INDEX ctts=msrf.msrf_aTriangles.Count();
    // for each triangle in this surface
    INDEX its=0;
    for(;its<ctts;its++)
    {
      MeshTriangle &mtTriangle = msrf.msrf_aTriangles[its];
      // for each vertex in triangle
      for(INDEX iv=0;iv<3;iv++)
      {
        mtTriangle.iVertex[iv] = _aSortArray[mtTriangle.iVertex[iv]].sa_iNewIndex;
        // find first index in this surface
        if(mtTriangle.iVertex[iv]<iMinIndex) iMinIndex = mtTriangle.iVertex[iv];
        // find last index in this surface
        if(mtTriangle.iVertex[iv]>iMaxIndex) iMaxIndex = mtTriangle.iVertex[iv];
      }
    }
    // remember first index in vertices array
    msrf.msrf_iFirstVertex = iMinIndex;
    // remember vertices count
    msrf.msrf_ctVertices = iMaxIndex-iMinIndex+1;

    // for each triangle in surface
    for(its=0;its<ctts;its++)
    {
      MeshTriangle &mtTriangle = msrf.msrf_aTriangles[its];
      // for each vertex in triangle
      for(INDEX iv=0;iv<3;iv++)
      {
        // substract vertex index in triangle with first vertex in surface
        mtTriangle.iVertex[iv] -= msrf.msrf_iFirstVertex;
        ASSERT(mtTriangle.iVertex[iv]<msrf.msrf_ctVertices);
      }
    }
  }

  // remap weightmaps
  mshOptimized.mlod_aWeightMaps.New(ctWeightMaps);
  // expand wertex veights array for each vertex
  INDEX ivx=0;
  for(;ivx<ctNewVertices;ivx++)
  {
    INDEX ioptVx = _aiOptimizedIndex[ivx];
    for(INDEX iwl=0;iwl<_aSortArray[ioptVx].sa_aWeightMapList.Count();iwl++)
    {
      VertexLocator &wml = _aSortArray[ioptVx].sa_aWeightMapList[iwl];
      INDEX wmIndex = wml.vl_iIndex;
      INDEX wwIndex = wml.vl_iSubIndex;
      INDEX ctww = mshOptimized.mlod_aWeightMaps[wmIndex].mwm_aVertexWeight.Count();
      MeshWeightMap &mwm = mshOptimized.mlod_aWeightMaps[wmIndex];
      MeshVertexWeight &mww = mLod.mlod_aWeightMaps[wmIndex].mwm_aVertexWeight[wwIndex];

      mwm.mwm_iID = mLod.mlod_aWeightMaps[wmIndex].mwm_iID;
      mwm.mwm_aVertexWeight.Expand(ctww+1);
      mwm.mwm_aVertexWeight[ctww].mww_fWeight = mww.mww_fWeight;
      mwm.mwm_aVertexWeight[ctww].mww_iVertex = ivx;
    }
  }

  // remap morphmaps
  mshOptimized.mlod_aMorphMaps.New(ctMorphMaps);
  // expand morph maps array for each vertex
  for(ivx=0;ivx<ctNewVertices;ivx++)
  {
    INDEX ioptVx = _aiOptimizedIndex[ivx];
    for(INDEX iml=0;iml<_aSortArray[ioptVx].sa_aMorphMapList.Count();iml++)
    {
      VertexLocator &mml = _aSortArray[ioptVx].sa_aMorphMapList[iml];
      INDEX mmIndex = mml.vl_iIndex;
      INDEX mwmIndex = mml.vl_iSubIndex;
      INDEX ctmwm = mshOptimized.mlod_aMorphMaps[mmIndex].mmp_aMorphMap.Count();
      MeshMorphMap &mmm = mshOptimized.mlod_aMorphMaps[mmIndex];
      MeshVertexMorph &mwm = mLod.mlod_aMorphMaps[mmIndex].mmp_aMorphMap[mwmIndex];

      mmm.mmp_iID = mLod.mlod_aMorphMaps[mmIndex].mmp_iID;
      mmm.mmp_bRelative = mLod.mlod_aMorphMaps[mmIndex].mmp_bRelative;

      mmm.mmp_aMorphMap.Expand(ctmwm+1);
      mmm.mmp_aMorphMap[ctmwm].mwm_iVxIndex = ivx;
      mmm.mmp_aMorphMap[ctmwm].mwm_x = mwm.mwm_x;
      mmm.mmp_aMorphMap[ctmwm].mwm_y = mwm.mwm_y;
      mmm.mmp_aMorphMap[ctmwm].mwm_z = mwm.mwm_z;
      mmm.mmp_aMorphMap[ctmwm].mwm_nx = mwm.mwm_nx;
      mmm.mmp_aMorphMap[ctmwm].mwm_ny = mwm.mwm_ny;
      mmm.mmp_aMorphMap[ctmwm].mwm_nz = mwm.mwm_nz;
    }
  }

  mLod.mlod_aVertices.CopyArray(mshOptimized.mlod_aVertices);
  mLod.mlod_aNormals.CopyArray(mshOptimized.mlod_aNormals);
  mLod.mlod_aMorphMaps.CopyArray(mshOptimized.mlod_aMorphMaps);
  mLod.mlod_aWeightMaps.CopyArray(mshOptimized.mlod_aWeightMaps);
  mLod.mlod_aUVMaps.CopyArray(mshOptimized.mlod_aUVMaps);

  // clear memory
  ClearSortArray(ctVertices);
  mshOptimized.mlod_aVertices.Clear();
  mshOptimized.mlod_aNormals.Clear();
  mshOptimized.mlod_aWeightMaps.Clear();
  mshOptimized.mlod_aMorphMaps.Clear();
  mshOptimized.mlod_aUVMaps.Clear();
}

INDEX AreVerticesDiferent(INDEX iCurentIndex, INDEX iLastIndex)
{
#define CHECK(x,y) if(((x)-(y))!=0) return ((x)-(y))
#define CHECKF(x,y) if(((x)-(y))!=0) return Sgn((x)-(y))
  
  // check surfaces
  CHECK(_aSortArray[iCurentIndex].sa_iSurfaceIndex,_aSortArray[iLastIndex].sa_iSurfaceIndex);
  // check vertices
  CHECKF(pMeshLOD->mlod_aVertices[iCurentIndex].y,pMeshLOD->mlod_aVertices[iLastIndex].y);
  CHECKF(pMeshLOD->mlod_aVertices[iCurentIndex].x,pMeshLOD->mlod_aVertices[iLastIndex].x);
  CHECKF(pMeshLOD->mlod_aVertices[iCurentIndex].z,pMeshLOD->mlod_aVertices[iLastIndex].z);
  // check normals
  CHECKF(pMeshLOD->mlod_aNormals[iCurentIndex].ny,pMeshLOD->mlod_aNormals[iLastIndex].ny);
  CHECKF(pMeshLOD->mlod_aNormals[iCurentIndex].nx,pMeshLOD->mlod_aNormals[iLastIndex].nx);
  CHECKF(pMeshLOD->mlod_aNormals[iCurentIndex].nz,pMeshLOD->mlod_aNormals[iLastIndex].nz);
  // check uvmaps
  INDEX ctUVMaps = pMeshLOD->mlod_aUVMaps.Count();
  for(INDEX iuvm=0;iuvm<ctUVMaps;iuvm++)
  {
    CHECKF(pMeshLOD->mlod_aUVMaps[iuvm].muv_aTexCoords[iCurentIndex].u,pMeshLOD->mlod_aUVMaps[iuvm].muv_aTexCoords[iLastIndex].u);
    CHECKF(pMeshLOD->mlod_aUVMaps[iuvm].muv_aTexCoords[iCurentIndex].v,pMeshLOD->mlod_aUVMaps[iuvm].muv_aTexCoords[iLastIndex].v);
  }
  // count weight and morph maps
  INDEX ctwmCurent  = _aSortArray[iCurentIndex].sa_aWeightMapList.Count();
  INDEX ctwmLast    = _aSortArray[iLastIndex].sa_aWeightMapList.Count();
  INDEX ctmmCurent  = _aSortArray[iCurentIndex].sa_aMorphMapList.Count();
  INDEX ctmmLast    = _aSortArray[iLastIndex].sa_aMorphMapList.Count();
  // check if vertices have same weight and morph maps count
  CHECK(ctwmCurent,ctwmLast);
  CHECK(ctmmCurent,ctmmLast);
  // check if vertices have same weight map factors
  for(INDEX iwm=0;iwm<ctwmCurent;iwm++)
  {
    // get weight map indices
    INDEX iwmCurent = _aSortArray[iCurentIndex].sa_aWeightMapList[iwm].vl_iIndex;
    INDEX iwmLast   = _aSortArray[iLastIndex].sa_aWeightMapList[iwm].vl_iIndex;
    // get wertex weight indices
    INDEX iwwCurent = _aSortArray[iCurentIndex].sa_aWeightMapList[iwm].vl_iSubIndex;
    INDEX iwwLast   = _aSortArray[iLastIndex].sa_aWeightMapList[iwm].vl_iSubIndex;
    // if weight map factors are diferent
    CHECKF(pMeshLOD->mlod_aWeightMaps[iwmCurent].mwm_aVertexWeight[iwwCurent].mww_fWeight,pMeshLOD->mlod_aWeightMaps[iwmLast].mwm_aVertexWeight[iwwLast].mww_fWeight);
  }

  // check if vertices have same morph map factors
  for(INDEX imm=0;imm<ctmmCurent;imm++)
  {
    // get morph map indices
    INDEX immCurent = _aSortArray[iCurentIndex].sa_aMorphMapList[imm].vl_iIndex;
    INDEX immLast   = _aSortArray[iLastIndex].sa_aMorphMapList[imm].vl_iIndex;
    // get mesh vertex morph indices
    INDEX imwmCurent  = _aSortArray[iCurentIndex].sa_aMorphMapList[imm].vl_iSubIndex;
    INDEX imwmLast    = _aSortArray[iLastIndex].sa_aMorphMapList[imm].vl_iSubIndex;
    
    // if mesh morph map params are diferent return
    CHECKF(pMeshLOD->mlod_aMorphMaps[immCurent].mmp_aMorphMap[imwmCurent].mwm_x,
      pMeshLOD->mlod_aMorphMaps[immLast].mmp_aMorphMap[imwmLast].mwm_x);
    CHECKF(pMeshLOD->mlod_aMorphMaps[immCurent].mmp_aMorphMap[imwmCurent].mwm_y,
      pMeshLOD->mlod_aMorphMaps[immLast].mmp_aMorphMap[imwmLast].mwm_y);
    CHECKF(pMeshLOD->mlod_aMorphMaps[immCurent].mmp_aMorphMap[imwmCurent].mwm_z,
      pMeshLOD->mlod_aMorphMaps[immLast].mmp_aMorphMap[imwmLast].mwm_z);
    CHECKF(pMeshLOD->mlod_aMorphMaps[immCurent].mmp_aMorphMap[imwmCurent].mwm_nx,
      pMeshLOD->mlod_aMorphMaps[immLast].mmp_aMorphMap[imwmLast].mwm_nx);
    CHECKF(pMeshLOD->mlod_aMorphMaps[immCurent].mmp_aMorphMap[imwmCurent].mwm_ny,
      pMeshLOD->mlod_aMorphMaps[immLast].mmp_aMorphMap[imwmLast].mwm_ny);
    CHECKF(pMeshLOD->mlod_aMorphMaps[immCurent].mmp_aMorphMap[imwmCurent].mwm_nz,
      pMeshLOD->mlod_aMorphMaps[immLast].mmp_aMorphMap[imwmLast].mwm_nz);
  }
  return 0;
}
// normalize weights in mlod
void CMesh::NormalizeWeightsInLod(MeshLOD &mlod)
{
  CStaticArray<float> aWeightFactors;
  int ctvtx = mlod.mlod_aVertices.Count();
  int ctwm = mlod.mlod_aWeightMaps.Count();
  // create array for weights
  aWeightFactors.New(ctvtx);
  memset(&aWeightFactors[0],0,sizeof(aWeightFactors[0])*ctvtx);
  int iwm=0;
  for(;iwm<ctwm;iwm++)
  {
    MeshWeightMap &mwm = mlod.mlod_aWeightMaps[iwm];
    for(int iww=0;iww<mwm.mwm_aVertexWeight.Count();iww++)
    {
      MeshVertexWeight &mwh = mwm.mwm_aVertexWeight[iww];
       aWeightFactors[mwh.mww_iVertex] += mwh.mww_fWeight;
    }
  }

  for(iwm=0;iwm<ctwm;iwm++)
  {
    MeshWeightMap &mwm = mlod.mlod_aWeightMaps[iwm];
    for(int iww=0;iww<mwm.mwm_aVertexWeight.Count();iww++)
    {
      MeshVertexWeight &mwh = mwm.mwm_aVertexWeight[iww];
      mwh.mww_fWeight /= aWeightFactors[mwh.mww_iVertex];
    }
  }
  // clear weight array
  aWeightFactors.Clear();
}
// normalize weights in mesh
void CMesh::NormalizeWeights()
{
  INDEX ctmlods = msh_aMeshLODs.Count();
  for(INDEX imlod=0;imlod<ctmlods;imlod++)
  {
    // normalize each lod
    NormalizeWeightsInLod(msh_aMeshLODs[imlod]);
  }
}
// add new mesh lod to mesh
void CMesh::AddMeshLod(MeshLOD &mlod)
{
  INDEX ctmlods = msh_aMeshLODs.Count();
  msh_aMeshLODs.Expand(ctmlods+1);
  msh_aMeshLODs[ctmlods] = mlod;
}
// remove mesh lod from mesh
void CMesh::RemoveMeshLod(MeshLOD *pmlodRemove)
{
  INDEX ctmlod = msh_aMeshLODs.Count();
  // create temp space for skeleton lods
  CStaticArray<struct MeshLOD> aTempMLODs;
  aTempMLODs.New(ctmlod-1);
  INDEX iIndexSrc=0;

  // for each skeleton lod in skeleton
  for(INDEX imlod=0;imlod<ctmlod;imlod++)
  {
    MeshLOD *pmlod = &msh_aMeshLODs[imlod];
    // copy all skeleton lods except the selected one
    if(pmlod != pmlodRemove)
    {
      aTempMLODs[iIndexSrc] = *pmlod;
      iIndexSrc++;
    }
  }
  // copy temp array of skeleton lods back in skeleton
  msh_aMeshLODs.CopyArray(aTempMLODs);
  // clear temp skleletons lods array
  aTempMLODs.Clear();
}
// write to stream
void CMesh::Write_t(CTStream *ostrFile)
{
  INDEX ctmlods = msh_aMeshLODs.Count();

  // write id
  ostrFile->WriteID_t(CChunkID(MESH_ID));
  // write version
  (*ostrFile)<<(INDEX)MESH_VERSION;
  // write mlod count
  (*ostrFile)<<ctmlods;
  // for each lod in mesh
  for(INDEX imlod=0;imlod<ctmlods;imlod++) {
    MeshLOD &mLod = msh_aMeshLODs[imlod];

    INDEX ctVx = mLod.mlod_aVertices.Count();   // vertex count
    INDEX ctUV = mLod.mlod_aUVMaps.Count();     // uvmaps count
    INDEX ctSf = mLod.mlod_aSurfaces.Count();   // surfaces count
    INDEX ctWM = mLod.mlod_aWeightMaps.Count(); // weight maps count
    INDEX ctMM = mLod.mlod_aMorphMaps.Count();  // morph maps count
    // write source file name
    (*ostrFile)<<mLod.mlod_fnSourceFile;
    // write max distance
    (*ostrFile)<<mLod.mlod_fMaxDistance;
    // write flags
    (*ostrFile)<<mLod.mlod_ulFlags;

    // write wertex count
    (*ostrFile)<<ctVx;
    // write wertices
    ostrFile->Write_t(&mLod.mlod_aVertices[0],sizeof(MeshVertex)*ctVx);
    // write normals
    ostrFile->Write_t(&mLod.mlod_aNormals[0],sizeof(MeshNormal)*ctVx);

    // write uvmaps count
    (*ostrFile)<<ctUV;
    // write uvmaps
    for(int iuv=0;iuv<ctUV;iuv++) {
      // write uvmap ID
      CTString strNameID = ska_GetStringFromTable(mLod.mlod_aUVMaps[iuv].muv_iID);
      (*ostrFile)<<strNameID;
      // write uvmaps texcordinates
      ostrFile->Write_t(&mLod.mlod_aUVMaps[iuv].muv_aTexCoords[0],sizeof(MeshTexCoord)*ctVx);
    }

    // write surfaces count
    ostrFile->Write_t(&ctSf,sizeof(INDEX));
    // write surfaces
    for(INDEX isf=0;isf<ctSf;isf++) {
      MeshSurface &msrf = mLod.mlod_aSurfaces[isf];
      INDEX ctTris = msrf.msrf_aTriangles.Count();
      CTString strSurfaceID = ska_GetStringFromTable(msrf.msrf_iSurfaceID);
      // write surface ID
      (*ostrFile)<<strSurfaceID;
      // write first vertex
      (*ostrFile)<<msrf.msrf_iFirstVertex;
      // write vertices count
      (*ostrFile)<<msrf.msrf_ctVertices;
      // write tris count
      (*ostrFile)<<ctTris;
      // write triangles
      ostrFile->Write_t(&mLod.mlod_aSurfaces[isf].msrf_aTriangles[0],sizeof(MeshTriangle)*ctTris);

      // write bool that this surface has a shader
      INDEX bShaderExists = (msrf.msrf_pShader!=NULL);
      (*ostrFile)<<bShaderExists;
      if(bShaderExists) {
        // get shader decription
        ShaderDesc shDesc;
        msrf.msrf_pShader->GetShaderDesc(shDesc);
        INDEX cttx=shDesc.sd_astrTextureNames.Count();
        INDEX cttc=shDesc.sd_astrTexCoordNames.Count();
        INDEX ctcol=shDesc.sd_astrColorNames.Count();
        INDEX ctfl=shDesc.sd_astrFloatNames.Count();
        // data count must be at same as size defined in shader or higher
        ASSERT(cttx<=msrf.msrf_ShadingParams.sp_aiTextureIDs.Count());
        ASSERT(cttc<=msrf.msrf_ShadingParams.sp_aiTexCoordsIndex.Count());
        ASSERT(ctcol<=msrf.msrf_ShadingParams.sp_acolColors.Count());
        ASSERT(ctfl<=msrf.msrf_ShadingParams.sp_afFloats.Count());
        ASSERT(msrf.msrf_pShader->GetShaderDesc!=NULL);
        // write texture count 
        (*ostrFile)<<cttx;
        // write texture coords count 
        (*ostrFile)<<cttc;
        // write color count 
        (*ostrFile)<<ctcol;
        // write float count 
        (*ostrFile)<<ctfl;

        ASSERT(msrf.msrf_pShader!=NULL);
        // write shader name
        CTString strShaderName;
        strShaderName = msrf.msrf_pShader->GetName();
        (*ostrFile)<<strShaderName;
        // write shader texture IDs
        for(INDEX itx=0;itx<cttx;itx++)
        {
          INDEX iTexID = msrf.msrf_ShadingParams.sp_aiTextureIDs[itx];
          (*ostrFile)<<ska_GetStringFromTable(iTexID);
        }
        // write shader texture coords indices
        for(INDEX itc=0;itc<cttc;itc++)
        {
          INDEX iTexCoorsIndex = msrf.msrf_ShadingParams.sp_aiTexCoordsIndex[itc];
          (*ostrFile)<<iTexCoorsIndex;
        }
        // write shader colors
        for(INDEX icol=0;icol<ctcol;icol++)
        {
          COLOR colColor = msrf.msrf_ShadingParams.sp_acolColors[icol];
          (*ostrFile)<<colColor;
        }
        // write shader floats
        for(INDEX ifl=0;ifl<ctfl;ifl++)
        {
          FLOAT fFloat = msrf.msrf_ShadingParams.sp_afFloats[ifl];
          (*ostrFile)<<fFloat;
        }
        // write shader flags
        ULONG ulFlags = msrf.msrf_ShadingParams.sp_ulFlags;
        (*ostrFile)<<ulFlags;
      }
    }

    // write weightmaps count
    (*ostrFile)<<ctWM;
    // for each weightmap in array
    for(INDEX iwm=0;iwm<ctWM;iwm++)
    {
      INDEX ctWw = mLod.mlod_aWeightMaps[iwm].mwm_aVertexWeight.Count();
      // write wertex weight map ID
      CTString pstrNameID = ska_GetStringFromTable(mLod.mlod_aWeightMaps[iwm].mwm_iID);
      (*ostrFile)<<pstrNameID;
      // write wertex weights count
      (*ostrFile)<<ctWw;
      // write wertex weights
      ostrFile->Write_t(&mLod.mlod_aWeightMaps[iwm].mwm_aVertexWeight[0],sizeof(MeshVertexWeight)*ctWw);
    }

    // write morphmaps count
    (*ostrFile)<<ctMM;
    for(INDEX imm=0;imm<ctMM;imm++)
    {
      INDEX ctms = mLod.mlod_aMorphMaps[imm].mmp_aMorphMap.Count();
      // write ID
      CTString pstrNameID = ska_GetStringFromTable(mLod.mlod_aMorphMaps[imm].mmp_iID);
      (*ostrFile)<<pstrNameID;
      // write bRelative
      (*ostrFile)<<mLod.mlod_aMorphMaps[imm].mmp_bRelative;
      //ostrFile->Write_t(&mLod.mlod_aMorphMaps[imm].mmp_bRelative,sizeof(BOOL));
      // write morph sets count
      ostrFile->Write_t(&ctms,sizeof(INDEX));
      // write morph sets
      ostrFile->Write_t(&mLod.mlod_aMorphMaps[imm].mmp_aMorphMap[0],sizeof(MeshVertexMorph)*ctms);
    }
  }
}

//read from stream
void CMesh::Read_t(CTStream *istrFile)
{
  INDEX ctmlods;
  INDEX iFileVersion;
  // read chunk id
  istrFile->ExpectID_t(CChunkID(MESH_ID));
  // check file version
  (*istrFile)>>iFileVersion;
  
  // if file version is not 11 nor 12
  if(iFileVersion != 11 && iFileVersion!=12) {
		ThrowF_t(TRANS("File '%s'.\nInvalid Mesh file version.\nExpected Ver \"%d\" but found \"%d\"\n"),
      (const char*)istrFile->GetDescription(),MESH_VERSION,iFileVersion);
    return;
  }

  // read mlod count
  (*istrFile)>>ctmlods;
  // for each lod in mesh
  for(INDEX imlod=0;imlod<ctmlods;imlod++) {
    // expand mlod count for one 
    INDEX ctMeshLODs = msh_aMeshLODs.Count();
    msh_aMeshLODs.Expand(ctMeshLODs+1);
    MeshLOD &mLod = msh_aMeshLODs[ctMeshLODs];

    INDEX ctVx;   // vertex count
    INDEX ctUV;   // uvmaps count
    INDEX ctSf;   // surfaces count
    INDEX ctWM;   // weight maps count
    INDEX ctMM;   // morph maps count
    
    // read source file name
    (*istrFile)>>mLod.mlod_fnSourceFile;
    // read max distance
    (*istrFile)>>mLod.mlod_fMaxDistance;
    // read flags
    (*istrFile)>>mLod.mlod_ulFlags;

    // :)
    if(iFileVersion<=11) {
      mLod.mlod_ulFlags = 0;
    }
    if(mLod.mlod_ulFlags==0xCDCDCDCD) {
      mLod.mlod_ulFlags = 0;
    }


    // read vertex count
    (*istrFile)>>ctVx;
    // create vertex and normal arrays
    mLod.mlod_aVertices.New(ctVx);
    mLod.mlod_aNormals.New(ctVx);
    // read wertices
    istrFile->Read_t(&mLod.mlod_aVertices[0],sizeof(MeshVertex)*ctVx);
    // read normals
    istrFile->Read_t(&mLod.mlod_aNormals[0],sizeof(MeshNormal)*ctVx);

    // read uvmaps count
    (*istrFile)>>ctUV;
    // create array for uvmaps
    mLod.mlod_aUVMaps.New(ctUV);
    // read uvmaps
    for(int iuv=0;iuv<ctUV;iuv++) {
      // read uvmap ID
      CTString strNameID;
      (*istrFile)>>strNameID;
      mLod.mlod_aUVMaps[iuv].muv_iID = ska_GetIDFromStringTable(strNameID);
      // create array for uvmaps texcordinates
      mLod.mlod_aUVMaps[iuv].muv_aTexCoords.New(ctVx);
      // read uvmap texcordinates
      istrFile->Read_t(&mLod.mlod_aUVMaps[iuv].muv_aTexCoords[0],sizeof(MeshTexCoord)*ctVx);
    }
    // read surfaces count
    (*istrFile)>>ctSf;
    // create array for surfaces
    mLod.mlod_aSurfaces.New(ctSf);
    // read surfaces
    for(INDEX isf=0;isf<ctSf;isf++) {
      INDEX ctTris;
      MeshSurface &msrf = mLod.mlod_aSurfaces[isf];
      // read surface ID
      CTString strSurfaceID;
      (*istrFile)>>strSurfaceID;
      msrf.msrf_iSurfaceID = ska_GetIDFromStringTable(strSurfaceID);
      // read first vertex
      (*istrFile)>>msrf.msrf_iFirstVertex;
      // read vertices count
      (*istrFile)>>msrf.msrf_ctVertices;
      // read tris count
      (*istrFile)>>ctTris;
      // create triangles array
      mLod.mlod_aSurfaces[isf].msrf_aTriangles.New(ctTris);
      // read triangles
      istrFile->Read_t(&mLod.mlod_aSurfaces[isf].msrf_aTriangles[0],sizeof(MeshTriangle)*ctTris);

      // read bool that this surface has a shader
      INDEX bShaderExists;
      (*istrFile)>>bShaderExists;
      // if shader exists read its params
      if(bShaderExists) {
        INDEX cttx,cttc,ctcol,ctfl;
        // read texture count
        (*istrFile)>>cttx;
        // read texture coords count
        (*istrFile)>>cttc;
        // read color count
        (*istrFile)>>ctcol;
        // read float count
        (*istrFile)>>ctfl;

        CShader *pshMeshShader = NULL;
        ShaderParams *pshpShaderParams = NULL;
        CShader shDummyShader;            // dummy shader if shader is not found
        ShaderParams shpDummyShaderParams;// dummy shader params if shader is not found
        // read shader name
        CTString strShaderName;
        (*istrFile)>>strShaderName;
        // try to load shader
        try{
          msrf.msrf_pShader = _pShaderStock->Obtain_t(strShaderName);
          pshMeshShader = msrf.msrf_pShader;
          pshpShaderParams = &msrf.msrf_ShadingParams;
        } catch(char *strErr) {
          CPrintF("%s\n",strErr);
          msrf.msrf_pShader = NULL;
          pshMeshShader = &shDummyShader;
          pshpShaderParams = &shpDummyShaderParams;
        }

        // if mesh shader exisits
        if(msrf.msrf_pShader!=NULL) {
          // get shader description
          ShaderDesc shDesc;
          msrf.msrf_pShader->GetShaderDesc(shDesc);
          // check if saved params count match shader params count
          if(shDesc.sd_astrTextureNames.Count() != cttx) ThrowF_t("File '%s'\nWrong texture count %d",(const char*)GetName(),cttx);
          if(shDesc.sd_astrTexCoordNames.Count() != cttc) ThrowF_t("File '%s'\nWrong uvmaps count %d",(const char*)GetName(),cttc);
          if(shDesc.sd_astrColorNames.Count() != ctcol) ThrowF_t("File '%s'\nWrong colors count %d",(const char*)GetName(),ctcol);
          if(shDesc.sd_astrFloatNames.Count() != ctfl) ThrowF_t("File '%s'\nWrong floats count %d",(const char*)GetName(),ctfl);
        }

        // create arrays for shader params
        pshpShaderParams->sp_aiTextureIDs.New(cttx);
        pshpShaderParams->sp_aiTexCoordsIndex.New(cttc);
        pshpShaderParams->sp_acolColors.New(ctcol);
        pshpShaderParams->sp_afFloats.New(ctfl);

        // read shader texture IDs
        for(INDEX itx=0;itx<cttx;itx++) {
          CTString strTexID;
          (*istrFile)>>strTexID;
          INDEX iTexID = ska_GetIDFromStringTable(strTexID);
           pshpShaderParams->sp_aiTextureIDs[itx] = iTexID;
        }
        // read shader texture coords indices
        for(INDEX itc=0;itc<cttc;itc++) {
          INDEX iTexCoorsIndex;
          (*istrFile)>>iTexCoorsIndex;
          pshpShaderParams->sp_aiTexCoordsIndex[itc] = iTexCoorsIndex;
        }
        // read shader colors
        for(INDEX icol=0;icol<ctcol;icol++) {
          COLOR colColor;
          (*istrFile)>>colColor;
          pshpShaderParams->sp_acolColors[icol] = colColor;
        }
        // read shader floats
        for(INDEX ifl=0;ifl<ctfl;ifl++) {
          FLOAT fFloat;
          (*istrFile)>>fFloat;
          pshpShaderParams->sp_afFloats[ifl] = fFloat;
        }
        // there were no flags in shader before ver 12
        if(iFileVersion>11) {
          ULONG ulFlags;
          (*istrFile)>>ulFlags;
          pshpShaderParams->sp_ulFlags = ulFlags;
        } else {
          pshpShaderParams->sp_ulFlags = 0;
        }
      } else {
        // this surface does not have shader
        msrf.msrf_pShader=NULL;
      }
    }

    // read weightmaps count
    (*istrFile)>>ctWM;
    // create weightmap array
     mLod.mlod_aWeightMaps.New(ctWM);
     // read each weightmap
    for(INDEX iwm=0;iwm<ctWM;iwm++) {
      // read weightmap ID
      CTString pstrNameID;
      (*istrFile)>>pstrNameID;
      mLod.mlod_aWeightMaps[iwm].mwm_iID = ska_GetIDFromStringTable(pstrNameID);
      // read wertex weight count
      INDEX ctWw;
      (*istrFile)>>ctWw;
      // create wertex weight array
      mLod.mlod_aWeightMaps[iwm].mwm_aVertexWeight.New(ctWw);
      // read wertex weights
      istrFile->Read_t(&mLod.mlod_aWeightMaps[iwm].mwm_aVertexWeight[0],sizeof(MeshVertexWeight)*ctWw);
    }

    // read morphmap count
    (*istrFile)>>ctMM;
    // create morphmaps array
    mLod.mlod_aMorphMaps.New(ctMM);
    // read morphmaps
    for(INDEX imm=0;imm<ctMM;imm++) {
      // read morphmap ID
      CTString pstrNameID;
      (*istrFile)>>pstrNameID;
      mLod.mlod_aMorphMaps[imm].mmp_iID = ska_GetIDFromStringTable(pstrNameID);
      // read bRelative
      (*istrFile)>>mLod.mlod_aMorphMaps[imm].mmp_bRelative;
      // read morph sets count
      INDEX ctms;
      (*istrFile)>>ctms;
      // create morps sets array
      mLod.mlod_aMorphMaps[imm].mmp_aMorphMap.New(ctms);
      // read morph sets
      istrFile->Read_t(&mLod.mlod_aMorphMaps[imm].mmp_aMorphMap[0],sizeof(MeshVertexMorph)*ctms);
    }
  }
}
// clear mesh
void CMesh::Clear(void)
{
  // for each LOD
  INDEX ctmlod = msh_aMeshLODs.Count();
  for (INDEX imlod=0; imlod<ctmlod; imlod++)
  {
    // for each surface, clear the triangles list
    MeshLOD &mlod = msh_aMeshLODs[imlod];
    INDEX ctsrf = mlod.mlod_aSurfaces.Count();
    for (INDEX isrf=0;isrf<ctsrf;isrf++)
    {
      MeshSurface &msrf = mlod.mlod_aSurfaces[isrf];
      msrf.msrf_aTriangles.Clear();
      // release shader form stock
      if(msrf.msrf_pShader!=NULL) _pShaderStock->Release(msrf.msrf_pShader);
      msrf.msrf_pShader = NULL;
    }
    // clear the surfaces array
    mlod.mlod_aSurfaces.Clear();
    // for each uvmap, clear the texcord list
    INDEX ctuvm = mlod.mlod_aUVMaps.Count();
    for (INDEX iuvm=0;iuvm<ctuvm;iuvm++)
    {
      mlod.mlod_aUVMaps[iuvm].muv_aTexCoords.Clear();
    }
    // clear the uvmaps array
    mlod.mlod_aUVMaps.Clear();
    // clear the vertices array
    mlod.mlod_aVertices.Clear();
    // clear the normals array
    mlod.mlod_aNormals.Clear();
  }
  // in the end, clear all LODs
  msh_aMeshLODs.Clear();
}

// Count used memory
SLONG CMesh::GetUsedMemory(void)
{
  SLONG slMemoryUsed = sizeof(*this);
  INDEX ctmlods = msh_aMeshLODs.Count();
  for(INDEX imlod=0;imlod<ctmlods;imlod++) {
    MeshLOD &mlod = msh_aMeshLODs[imlod];
    slMemoryUsed+=sizeof(mlod);
    slMemoryUsed+=mlod.mlod_aVertices.Count() * sizeof(MeshVertex);
    slMemoryUsed+=mlod.mlod_aNormals.Count() * sizeof(MeshNormal);

    // for each uvmap
    INDEX ctuvmaps = mlod.mlod_aUVMaps.Count();
    for(INDEX iuvm=0;iuvm<ctuvmaps;iuvm++) {
      MeshUVMap &uvmap = mlod.mlod_aUVMaps[iuvm];
      slMemoryUsed+=sizeof(uvmap);
      slMemoryUsed+=uvmap.muv_aTexCoords.Count() * sizeof(MeshTexCoord);
    }

    // for each surface
    INDEX ctmsrf = mlod.mlod_aSurfaces.Count();
    for(INDEX imsrf=0;imsrf<ctmsrf;imsrf++) {
      MeshSurface &msrf = mlod.mlod_aSurfaces[imsrf];
      slMemoryUsed+=sizeof(msrf);
      slMemoryUsed+=msrf.msrf_aTriangles.Count() * sizeof(MeshTriangle);
      slMemoryUsed+=sizeof(ShaderParams);
      slMemoryUsed+=sizeof(INDEX) * msrf.msrf_ShadingParams.sp_aiTextureIDs.Count();
      slMemoryUsed+=sizeof(INDEX) * msrf.msrf_ShadingParams.sp_aiTexCoordsIndex.Count();
      slMemoryUsed+=sizeof(COLOR) * msrf.msrf_ShadingParams.sp_acolColors.Count();
      slMemoryUsed+=sizeof(FLOAT) * msrf.msrf_ShadingParams.sp_afFloats.Count();
    }
    // for each weight map
    INDEX ctwm = mlod.mlod_aWeightMaps.Count();
    for(INDEX iwm=0;iwm<ctwm;iwm++) {
      MeshWeightMap &mwm = mlod.mlod_aWeightMaps[iwm];
      slMemoryUsed+=sizeof(mwm);
      slMemoryUsed+=mwm.mwm_aVertexWeight.Count() * sizeof(MeshVertexWeight);
    }
    // for each morphmap
    INDEX ctmm = mlod.mlod_aMorphMaps.Count();
    for(INDEX imm=0;imm<ctmm;imm++) {
      MeshMorphMap &mmm = mlod.mlod_aMorphMaps[imm];
      slMemoryUsed+=sizeof(mmm);
      slMemoryUsed+=mmm.mmp_aMorphMap.Count() * sizeof(MeshVertexMorph);
    }
  }
  return slMemoryUsed;
}
