/* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <assert.h>
#include <lwsurf.h>
#include <lwhost.h>
#include <lwserver.h>
#include <lwgeneric.h>
#include "vecmat.h"
#include "base.h"


static LWItemID _objid = NULL;
static LWSurfaceID *_asurSurfaces = NULL;
static char *_strFileName = NULL;

// arrays of all point and polygon ids
int _ctPntIDs = 0;
int _iPnt = 0;
LWPntID *_aidPntIDs = NULL;

int _ctPolIDs = 0;
int _iPol = 0;
LWPolID *_aidPolIDs = NULL;

// point coordinates
float (*_avPnts)[3] = NULL;
// polygon normals
float (*_avPolNormals)[3] = NULL;
// point normals
float (*_avPntNormals)[3] = NULL;

// uvmapnames
const char **_astrUVMapNames = NULL;
int _ctUVMapNames = 0;
int _ctUsedUVMapNames = 0;
// weightmapnames
const char **_astrWeightMapNames = NULL;
int *_actWeightMapCounts = NULL;
int _ctWeightMapNames = 0;
int _ctUsedWeightMapNames = 0;

// relmorphmapnames
const char **_astrRelMorphMapNames = NULL;
int *_actRelMorphMapCounts = NULL;
int _ctRelMorphMapNames = 0;
int _ctUsedRelMorphMapNames = 0;

// absmorphmapnames
const char **_astrAbsMorphMapNames = NULL;
int *_actAbsMorphMapCounts = NULL;
int _ctAbsMorphMapNames = 0;
int _ctUsedAbsMorphMapNames = 0;

// here we store ids for all point and polygon combinations in order by polygons and by points, effectively
// having all info needed to handle the unwelded object and info needed to calculate normals
int _ctPolPnts = 0;
PolPnt *_appPolPnts = NULL;
PolPnt *_appPntPols = NULL;

int _ctSurfs = 0;


extern int ReloadGlobalObjects();

// helper functions for ID sorting/searching
int __cdecl qsort_CompareIDs(const void *elem1, const void *elem2)
{
  return *(int*)elem1-*(int*)elem2;
}
int __cdecl qsort_ComparePolPntsByPnt(const void *elem1, const void *elem2)
{
  PolPnt *pp1 = (struct PolPnt *)elem1;
  PolPnt *pp2 = (struct PolPnt *)elem2;

  return (int)pp1->pp_idPnt-(int)pp2->pp_idPnt;
}
int GetPntIndex(LWPntID id)
{
  LWPntID *p = (LWPntID *)bsearch(&id, _aidPntIDs, _ctPntIDs, sizeof(id), qsort_CompareIDs);
  if (p==NULL) {
    assert(false);
    return 0;
  } else {
    return p-_aidPntIDs;
  }
}
int GetPolIndex(LWPolID id)
{
  LWPolID *p = (LWPolID *)bsearch(&id, _aidPolIDs, _ctPolIDs, sizeof(id), qsort_CompareIDs);
  if (p==NULL) {
    assert(false);
    return 0;
  } else {
    return p-_aidPolIDs;
  }
}

int EnumPnts(void *, LWPntID id)
{
  _aidPntIDs[_iPnt++] = id;
  return 0;
}

int EnumPols(void *, LWPolID id)
{
  _aidPolIDs[_iPol++] = id;
  return 0;
}

// fill base vertex coordinates (without morphing)
void FillOriginalVertexCoords(void)
{
  // for each vertex
  {for(int iPnt=0; iPnt<_ctPntIDs; iPnt++) {
    // get the coords
    _pmesh->pntBasePos(_pmesh, _aidPntIDs[iPnt], _avPnts[iPnt]);
  }}
}
// fill vertex coordinates with relative morphing
void FillRelativeMorphVertexCoords(const char *strRelMorphMapName)
{
  // fill base vertex coordinates (without morphing)
  FillOriginalVertexCoords();

  void *pMap = _pmesh->pntVLookup(_pmesh, LWVMAP_MORF, strRelMorphMapName);
  _pmesh->pntVSelect(_pmesh, pMap);
  // for each vertex
  {for(int iPnt=0; iPnt<_ctPntIDs; iPnt++) {
    // if morphed
    float v[3];
    if (_pmesh->pntVGet(_pmesh, _aidPntIDs[iPnt], v)) {
      // apply the morph
      _avPnts[iPnt][0]+=v[0];
      _avPnts[iPnt][1]+=v[1];
      _avPnts[iPnt][2]+=v[2];
    }
  }}
}
// fill vertex coordinates with absolute morphing
void FillAbsoluteMorphVertexCoords(const char *strAbsMorphMapName)
{
  // fill base vertex coordinates (without morphing)
  FillOriginalVertexCoords();

  void *pMap = _pmesh->pntVLookup(_pmesh, LWVMAP_SPOT, strAbsMorphMapName);
  _pmesh->pntVSelect(_pmesh, pMap);
  // for each vertex
  {for(int iPnt=0; iPnt<_ctPntIDs; iPnt++) {
    // if morphed
    float v[3];
    if (_pmesh->pntVGet(_pmesh, _aidPntIDs[iPnt], v)) {
      // apply the morph
      _avPnts[iPnt][0] = v[0];
      _avPnts[iPnt][1] = v[1];
      _avPnts[iPnt][2] = v[2];
    }
  }}
}

// calculate mesh normals using local vertex coordinates (to be able to calculate normals for morphmaps)
void MakeNormals(void)
{
  // generate polygon normals
  {for(int iPol=0; iPol<_ctPolIDs; iPol++) {
    LWPolID idPol = _aidPolIDs[iPol];
    int ctInThisPol = _pmesh->polSize(_pmesh, idPol);
    if (ctInThisPol<3) {
      _avPolNormals[iPol][0] = 0.0f;
      _avPolNormals[iPol][1] = 0.0f;
      _avPolNormals[iPol][2] = 0.0f;
    }
    LWPntID idPnt0 = _pmesh->polVertex(_pmesh, idPol, ctInThisPol-1);
    LWPntID idPnt1 = _pmesh->polVertex(_pmesh, idPol, 0);
    LWPntID idPnt2 = _pmesh->polVertex(_pmesh, idPol, 1);
    float v0[3], v1[3], v2[3];
    _pmesh->pntBasePos(_pmesh, idPnt0, v0);
    _pmesh->pntBasePos(_pmesh, idPnt1, v1);
    _pmesh->pntBasePos(_pmesh, idPnt2, v2);
    float d1[3], d2[3];
    for (int i=0; i<3; i++) {
      d1[i] = v2[i] - v1[i];
      d2[i] = v0[i] - v1[i];
    }
    cross(d1, d2, _avPolNormals[iPol]);
    normalize( _avPolNormals[iPol]);
  }}

  // generate point normals
  memset(_avPntNormals, 0, _ctPntIDs*sizeof(float)*3);

  float vNormalSum[3];
  LWPntID idLastPnt = NULL;

  // for each point polygon
  {for(int iPntPol=0; iPntPol<_ctPolPnts; iPntPol++) {
    LWPntID idThis = _appPntPols[iPntPol].pp_idPnt;
    // if new point
    if (idThis!=idLastPnt) {
      // store value for the last point (unless it was the first one)
      if (idLastPnt!=NULL) {
        int iLastPnt = GetPntIndex(idLastPnt);
        normalize(vNormalSum);
        _avPntNormals[iLastPnt][0] = vNormalSum[0];
        _avPntNormals[iLastPnt][1] = vNormalSum[1];
        _avPntNormals[iLastPnt][2] = vNormalSum[2];
      }
      // reset averaging values
      vNormalSum[0] = 0.0f;
      vNormalSum[1] = 0.0f;
      vNormalSum[2] = 0.0f;
    }
    // add the polygon normal to the averaging values
    int iPol = GetPolIndex(_appPntPols[iPntPol].pp_idPol);
    vNormalSum[0] += _avPolNormals[iPol][0];
    vNormalSum[1] += _avPolNormals[iPol][1];
    vNormalSum[2] += _avPolNormals[iPol][2];
    
    idLastPnt=idThis;
  }}

  int iLastPnt = GetPntIndex(idLastPnt);
  normalize(vNormalSum);
  _avPntNormals[iLastPnt][0] = vNormalSum[0];
  _avPntNormals[iLastPnt][1] = vNormalSum[1];
  _avPntNormals[iLastPnt][2] = vNormalSum[2];
}


// extract polygon and point info
void ExtractMeshData(void)
{
  // extract point and poly ids, so we can walk them later
  _ctPntIDs = _pmesh->numPoints(_pmesh);
  _aidPntIDs = (LWPntID*)malloc(_ctPntIDs*sizeof(LWPntID));
  _iPnt = 0;
  _pmesh->scanPoints(_pmesh, EnumPnts, NULL);
  qsort(_aidPntIDs, _ctPntIDs, sizeof(LWPntID), qsort_CompareIDs);

  _ctPolIDs = _pmesh->numPolygons(_pmesh);
  _aidPolIDs = (LWPolID*)malloc(_ctPolIDs*sizeof(LWPolID));
  _iPol = 0;
  _pmesh->scanPolys(_pmesh, EnumPols, NULL);
  qsort(_aidPolIDs, _ctPolIDs, sizeof(LWPolID), qsort_CompareIDs);

  // find the total number of points in all polygons
  _ctPolPnts = 0;
  for(int iPol=0; iPol<_ctPolIDs; iPol++) {
    int ct = _pmesh->polSize(_pmesh, _aidPolIDs[iPol]);
    if (ct!=3) {
      _msg->error("All objects must be triangles!", NULL);
    }
    _ctPolPnts += ct;
  }
  _appPolPnts = (PolPnt *)malloc(_ctPolPnts*sizeof(PolPnt));
  _appPntPols = (PolPnt *)malloc(_ctPolPnts*sizeof(PolPnt));
  // fill in all point and polygon combinations
  {int iPolPnt = 0;
  for(int iPol=0; iPol<_ctPolIDs; iPol++) {
    LWPolID idPol = _aidPolIDs[iPol];
    int ctInThisPol = _pmesh->polSize(_pmesh, idPol);
    for (int iPnt=0; iPnt<ctInThisPol; iPnt++) {
      LWPntID idPnt = _pmesh->polVertex(_pmesh, idPol, iPnt);
      _appPolPnts[iPolPnt].pp_idPol = idPol;
      _appPolPnts[iPolPnt].pp_idPnt = idPnt;
      iPolPnt++;
    }
  }}
  // copy to per-point array and sort by points
  memcpy(_appPntPols, _appPolPnts, _ctPolPnts*sizeof(PolPnt));
  qsort(_appPntPols, _ctPolPnts, sizeof(PolPnt), qsort_ComparePolPntsByPnt);

  // allocate point coords and polygon and point normals
  _avPnts = (float(*)[3])malloc(_ctPntIDs*sizeof(float)*3);
  _avPolNormals = (float(*)[3])malloc(_ctPolIDs*sizeof(float)*3);
  _avPntNormals = (float(*)[3])malloc(_ctPntIDs*sizeof(float)*3);

  // calculate mesh normals using base vertex coordinates (without morphing)
  FillOriginalVertexCoords();
  MakeNormals();

  // list all the uvmaps used by this object
  _ctUVMapNames = _obf->numVMaps(LWVMAP_TXUV);
  _astrUVMapNames = (const char**) malloc(_ctUVMapNames*sizeof(char*));
  memset(_astrUVMapNames, 0, _ctUVMapNames*sizeof(char*));
  _ctUsedUVMapNames = 0;
  {for(int iUVMap=0; iUVMap<_ctUVMapNames; iUVMap++) {
    const char *strName = _obf->vmapName(LWVMAP_TXUV, iUVMap);
    void *pMap = _pmesh->pntVLookup(_pmesh, LWVMAP_TXUV, strName);
    _pmesh->pntVSelect(_pmesh, pMap);
    bool bExists = false;
    for(int iPnt=0; iPnt<_ctPntIDs; iPnt++) {
      float v[2];
      if (_pmesh->pntVGet(_pmesh, _aidPntIDs[iPnt], v)) {
        bExists = true;
        break;
      }
    }
    if (bExists) {
      _astrUVMapNames[_ctUsedUVMapNames++] = strName;
    }
  }}

  // list all the weightmaps used by this object
  _ctWeightMapNames = _obf->numVMaps(LWVMAP_WGHT);
  _astrWeightMapNames = (const char**) malloc(_ctWeightMapNames*sizeof(char*));
  _actWeightMapCounts = (int *) malloc(_ctWeightMapNames*sizeof(int));
  memset(_astrWeightMapNames, 0, _ctWeightMapNames*sizeof(char*));
  memset(_actWeightMapCounts, 0, _ctWeightMapNames*sizeof(int));
  _ctUsedWeightMapNames = 0;
  {for(int iWeightMap=0; iWeightMap<_ctWeightMapNames; iWeightMap++) {
    const char *strName = _obf->vmapName(LWVMAP_WGHT, iWeightMap);
    void *pMap = _pmesh->pntVLookup(_pmesh, LWVMAP_WGHT, strName);
    _pmesh->pntVSelect(_pmesh, pMap);
    int ct = 0;
    // for each polygonvertex
    for(int iPolPnt=0; iPolPnt<_ctPolPnts; iPolPnt++) {
      // get the weight
      float v[1];
      if (_pmesh->pntVGet(_pmesh, _appPolPnts[iPolPnt].pp_idPnt, v) && v[0]!=0.0f) {
        ct++;
      }
    }
    if (ct>0)
    {
      _astrWeightMapNames[_ctUsedWeightMapNames] = strName;
      _actWeightMapCounts[_ctUsedWeightMapNames] = ct;
      _ctUsedWeightMapNames++;
    }
  }}

  // list all the relmorphmaps used by this object
  _ctRelMorphMapNames = _obf->numVMaps(LWVMAP_MORF);
  _astrRelMorphMapNames = (const char**) malloc(_ctRelMorphMapNames*sizeof(char*));
  _actRelMorphMapCounts = (int *) malloc(_ctRelMorphMapNames*sizeof(int));
  memset(_astrRelMorphMapNames, 0, _ctRelMorphMapNames*sizeof(char*));
  memset(_actRelMorphMapCounts, 0, _ctRelMorphMapNames*sizeof(int));
  _ctUsedRelMorphMapNames = 0;
  {for(int iRelMorphMap=0; iRelMorphMap<_ctRelMorphMapNames; iRelMorphMap++) {
    const char *strName = _obf->vmapName(LWVMAP_MORF, iRelMorphMap);
    void *pMap = _pmesh->pntVLookup(_pmesh, LWVMAP_MORF, strName);
    _pmesh->pntVSelect(_pmesh, pMap);
    int ct = 0;
    // for each polygonvertex
    for(int iPolPnt=0; iPolPnt<_ctPolPnts; iPolPnt++) {
      // get the morphpos
      float v[3];
      if (_pmesh->pntVGet(_pmesh, _appPolPnts[iPolPnt].pp_idPnt, v)) {
        ct++;
      }
    }
    if (ct>0) {
      _astrRelMorphMapNames[_ctUsedRelMorphMapNames] = strName;
    }
    _actRelMorphMapCounts[_ctUsedRelMorphMapNames] = ct;
    _ctUsedRelMorphMapNames++;
  }}

  // list all the absmorphmaps used by this object
  _ctAbsMorphMapNames = _obf->numVMaps(LWVMAP_SPOT);
  _astrAbsMorphMapNames = (const char**) malloc(_ctAbsMorphMapNames*sizeof(char*));
  _actAbsMorphMapCounts = (int *) malloc(_ctAbsMorphMapNames*sizeof(int));
  memset(_astrAbsMorphMapNames, 0, _ctAbsMorphMapNames*sizeof(char*));
  memset(_actAbsMorphMapCounts, 0, _ctAbsMorphMapNames*sizeof(int));
  _ctUsedAbsMorphMapNames = 0;
  {for(int iAbsMorphMap=0; iAbsMorphMap<_ctAbsMorphMapNames; iAbsMorphMap++) {
    const char *strName = _obf->vmapName(LWVMAP_SPOT, iAbsMorphMap);
    void *pMap = _pmesh->pntVLookup(_pmesh, LWVMAP_SPOT, strName);
    _pmesh->pntVSelect(_pmesh, pMap);
    int ct = 0;
    // for each polygonvertex
    for(int iPolPnt=0; iPolPnt<_ctPolPnts; iPolPnt++) {
      // get the morphpos
      float v[3];
      if (_pmesh->pntVGet(_pmesh, _appPolPnts[iPolPnt].pp_idPnt, v)) {
        ct++;
      }
    }
    if (ct>0) {
      _astrAbsMorphMapNames[_ctUsedAbsMorphMapNames] = strName;
    }
    _actAbsMorphMapCounts[_ctUsedAbsMorphMapNames] = ct;
    _ctUsedAbsMorphMapNames++;
  }}
}

FILE *_f = NULL;

int ExportMesh(LWXPanelID pan)
{
  // is mesh face forward
  int iFaceForward = *(int*)_xpanf->formGet( pan, ID_FACEFORWARD);

  // !!!! make it work with a selected object, not the first one in scene
  ReloadGlobalObjects();
  bool bExportOnlySelected = false;
  // count selected objects
  int ctSelectedMeshed = 0;
  int ctMeshes=0;
  _objid = _iti->first(LWI_OBJECT,0);
  while(_objid != LWITEM_NULL)
  {
    if(_iti->type(_objid) == LWI_OBJECT)
    {
      _pmesh = _obi->meshInfo(_objid, 0);
      if(_pmesh != NULL)
      {
        if(_ifi->itemFlags(_objid) & LWITEMF_SELECTED)
        {
          ctSelectedMeshed++;
        }
      }
      ctMeshes++;
    }
    _objid = _iti->next(_objid);
  }

  // get the first object in the scene
  _objid = _iti->first(LWI_OBJECT,0);
  if (!_objid)
  {
    _msg->error("No object in the scene.", NULL);
    return AFUNC_OK;
  }

  // if some objects are selected export only them
  if(ctSelectedMeshed > 0) bExportOnlySelected = true;
  // dont ask to export all meshes if only one mesh in the scene
  if(ctSelectedMeshed == 0)
  {
    if(ctMeshes > 1)
    {
      if(_msg->yesNo("No objects selected","Export all meshes?",NULL) == 0)
        return AFUNC_OK;
      bExportOnlySelected = false;
    }
  }

  // loop each mesh in scene
  while(_objid != LWITEM_NULL)
  {
    // get its mesh
    _pmesh = _obi->meshInfo(_objid, 0);
    if(_pmesh == NULL)
    {
      _objid = _iti->next(_objid);
      continue;
    }
    if(bExportOnlySelected)
    {
      if(!(_ifi->itemFlags(_objid) & LWITEMF_SELECTED))
      {
        _objid = _iti->next(_objid);
        continue;
      }
    }

    // get mesh name
    _strFileName = strdup(_obi->filename(_objid));

    // extract polygon and point info
    ExtractMeshData();

    // open the file to print into
    char fnmOut[256];
    strcpy(fnmOut, _strFileName);
    char *pchDot = strrchr(fnmOut, '.');
    if (pchDot!=NULL) {
      strcpy(pchDot, ".am");
    }
    _f = fopen(fnmOut, "w");
    if (_f==NULL) {
      _msg->error("Can't open file", fnmOut);
      goto end;
    }

    // write the mesh header
    fprintf(_f, "SE_MESH %s;\n\n",SE_ANIM_VER);
    if(iFaceForward==ML_HALF_FACE_FORWARD) {
      fprintf(_f, "HALF_FACE_FORWARD TRUE;\n\n");
    } else if(iFaceForward==ML_FULL_FACE_FORWARD) {
      fprintf(_f, "FULL_FACE_FORWARD TRUE;\n\n");
    }


    // write the vertex header
    fprintf(_f, "VERTICES %d\n", _ctPolPnts);
    fprintf(_f, "{\n");
    // for each polygonvertex
    {for(int iPolPnt=0; iPolPnt<_ctPolPnts; iPolPnt++) {
      // get the coords
      float v[3];
      _pmesh->pntBasePos(_pmesh, _appPolPnts[iPolPnt].pp_idPnt, v);
      fprintf(_f, "  %g, %g, %g;\n", v[0], v[1], -v[2]);
    }}
    fprintf(_f, "}\n\n");

    // write the normal header
    fprintf(_f, "NORMALS %d\n", _ctPolPnts);
    fprintf(_f, "{\n");
    // for each polygonvertex
    {for(int iPolPnt=0; iPolPnt<_ctPolPnts; iPolPnt++) {
      // get the normal
      int iPnt = GetPntIndex(_appPolPnts[iPolPnt].pp_idPnt);
      fprintf(_f, "  %g, %g, %g;\n", _avPntNormals[iPnt][0], _avPntNormals[iPnt][1], -_avPntNormals[iPnt][2]);
    }}
    fprintf(_f, "}\n\n");

    // write the uvmaps header
    fprintf(_f, "UVMAPS %d\n", _ctUsedUVMapNames);
    fprintf(_f, "{\n");

    // for each uvmap

    if (_ctUsedUVMapNames == 0) {
      _msg->info("No UV maps in the scene!",NULL);
    }
    {for(int iUVMap=0; iUVMap<_ctUsedUVMapNames; iUVMap++) {
      fprintf(_f, "  {\n");
      const char *strUVMap = _astrUVMapNames[iUVMap];
      fprintf(_f, "    NAME \"%s\";\n", strUVMap);
      void *pMap = _pmesh->pntVLookup(_pmesh, LWVMAP_TXUV, strUVMap);
      _pmesh->pntVSelect(_pmesh, pMap);
      fprintf(_f, "    TEXCOORDS %d\n", _ctPolPnts);
      fprintf(_f, "    {\n");

      // for each polygonvertex
      {for(int iPolPnt=0; iPolPnt<_ctPolPnts; iPolPnt++) {
        // get the coords
        float v[2];
        if (_pmesh->pntVPGet(_pmesh, _appPolPnts[iPolPnt].pp_idPnt, _appPolPnts[iPolPnt].pp_idPol, v)) {
        } else if (_pmesh->pntVGet(_pmesh, _appPolPnts[iPolPnt].pp_idPnt, v)) {
        } else {
          v[0] = 0.0f;
          v[1] = 0.0f;
        }
        fprintf(_f, "      %g, %g;\n", v[0], 1.0f-v[1]);
      }}
      fprintf(_f, "    }\n");
      fprintf(_f, "  }\n");
    }}
    fprintf(_f, "}\n\n");

    // get surfaces
    _asurSurfaces = _srf->byObject(_strFileName);

    // count the surfaces
    _ctSurfs = 0;
    while(_asurSurfaces[_ctSurfs]!=NULL) {
      _ctSurfs++;
    }

    // write the surfaces header
    fprintf(_f, "SURFACES %d\n", _ctSurfs);
    fprintf(_f, "{\n");
    // for each surface
    {for(int iSurf=0; iSurf<_ctSurfs; iSurf++) {
      fprintf(_f, "  {\n");
      const char *strSurf = _srf->name(_asurSurfaces[iSurf]);
      fprintf(_f, "    NAME \"%s\";\n", strSurf);
      // count the polygons
      int iSurfPols = 0;
      {for(int i=0; i<_ctPolIDs; i++) {
        if (strcmp(_pmesh->polTag(_pmesh, _aidPolIDs[i], LWPTAG_SURF), strSurf)==0) {
          iSurfPols++;
        }
      }}
      // write the polygon set header
      fprintf(_f, "    TRIANGLE_SET %d\n", iSurfPols);
      fprintf(_f, "    {\n");
      // for each polygon
      for(int iTri=0; iTri<_ctPolIDs; iTri++) {
        LWPolID idPol = _aidPolIDs[iTri];
        // if not in this surface
        if (strcmp(_pmesh->polTag(_pmesh, idPol, LWPTAG_SURF), strSurf)!=0) {
          // skip it
          continue;
        }
  //      assert(_appPolPnts[iTri*3+0].pp_idPol == idPol);
  //      assert(_appPolPnts[iTri*3+1].pp_idPol == idPol);
  //      assert(_appPolPnts[iTri*3+2].pp_idPol == idPol);
        fprintf(_f, "    %d, %d, %d;\n", iTri*3+2, iTri*3+1, iTri*3+0);
  //        GetPntIndex(_appPolPnts[iTri*3+0].pp_idPnt),
  //        GetPntIndex(_appPolPnts[iTri*3+1].pp_idPnt),
  //        GetPntIndex(_appPolPnts[iTri*3+2].pp_idPnt));
      }
      fprintf(_f, "    }\n");
      fprintf(_f, "  }\n");
    }}
    fprintf(_f, "}\n");

    // write the weightmaps header
    fprintf(_f, "WEIGHTS %d\n", _ctUsedWeightMapNames);
    fprintf(_f, "{\n");

    // for each weightmap
    {for(int iWeightMap=0; iWeightMap<_ctUsedWeightMapNames; iWeightMap++) {
      fprintf(_f, "  {\n");
      const char *strWeightMap = _astrWeightMapNames[iWeightMap];
      fprintf(_f, "    NAME \"%s\";\n", strWeightMap);
      void *pMap = _pmesh->pntVLookup(_pmesh, LWVMAP_WGHT, strWeightMap);
      _pmesh->pntVSelect(_pmesh, pMap);
      fprintf(_f, "    WEIGHT_SET %d\n", _actWeightMapCounts[iWeightMap]);
      fprintf(_f, "    {\n");

      // for each polygonvertex
      {for(int iPolPnt=0; iPolPnt<_ctPolPnts; iPolPnt++) {
        // get the coords
        float v[1];
        if (_pmesh->pntVGet(_pmesh, _appPolPnts[iPolPnt].pp_idPnt, v) && v[0]!=0.0f) {
          if (v[0] < 0) {
            _msg->error("Weight map value lesser than zero!",NULL);
          } else {
            fprintf(_f, "      { %d; %g; }\n", iPolPnt, v[0]);
          }
        }
      }}
      fprintf(_f, "    }\n");
      fprintf(_f, "  }\n");
    }}
    fprintf(_f, "}\n\n");

    // write the morphmaps header
    fprintf(_f, "MORPHS %d\n", _ctUsedRelMorphMapNames+_ctUsedAbsMorphMapNames);
    fprintf(_f, "{\n");

    // for each relmorphmap
    {for(int iRelMorphMap=0; iRelMorphMap<_ctUsedRelMorphMapNames; iRelMorphMap++) {
      const char *strRelMorphMap = _astrRelMorphMapNames[iRelMorphMap];
      // calculate mesh normals using the given morphmap
      FillRelativeMorphVertexCoords(strRelMorphMap);
      MakeNormals();

      fprintf(_f, "  {\n");
      fprintf(_f, "    NAME \"%s\";\n", strRelMorphMap);
      fprintf(_f, "    RELATIVE TRUE;\n");
      void *pMap = _pmesh->pntVLookup(_pmesh, LWVMAP_MORF, strRelMorphMap);
      _pmesh->pntVSelect(_pmesh, pMap);
      fprintf(_f, "    MORPH_SET %d\n", _actRelMorphMapCounts[iRelMorphMap]);
      fprintf(_f, "    {\n");

      // for each polygonvertex
      {for(int iPolPnt=0; iPolPnt<_ctPolPnts; iPolPnt++) {
        // if morphed here
        float vRel[3];
        if (_pmesh->pntVGet(_pmesh, _appPolPnts[iPolPnt].pp_idPnt, vRel)) {
          // write the coords and normal
          int iPnt = GetPntIndex(_appPolPnts[iPolPnt].pp_idPnt);
          fprintf(_f, "      { %d; %g, %g, %g; %g, %g, %g; }\n", iPolPnt, 
            _avPnts[iPnt][0], _avPnts[iPnt][1], -_avPnts[iPnt][2], 
            _avPntNormals[iPnt][0], _avPntNormals[iPnt][1], -_avPntNormals[iPnt][2]);
        }
      }}
      fprintf(_f, "    }\n");
      fprintf(_f, "  }\n");
    }}
    // for each absmorphmap
    {for(int iAbsMorphMap=0; iAbsMorphMap<_ctUsedAbsMorphMapNames; iAbsMorphMap++) {
      const char *strAbsMorphMap = _astrAbsMorphMapNames[iAbsMorphMap];
      // calculate mesh normals using the given morphmap
      FillAbsoluteMorphVertexCoords(strAbsMorphMap);
      MakeNormals();

      fprintf(_f, "  {\n");
      fprintf(_f, "    NAME \"%s\";\n", strAbsMorphMap);
      fprintf(_f, "    RELATIVE FALSE;\n");
      void *pMap = _pmesh->pntVLookup(_pmesh, LWVMAP_SPOT, strAbsMorphMap);
      _pmesh->pntVSelect(_pmesh, pMap);
      fprintf(_f, "    MORPH_SET %d\n", _actAbsMorphMapCounts[iAbsMorphMap]);
      fprintf(_f, "    {\n");

      // for each polygonvertex
      {for(int iPolPnt=0; iPolPnt<_ctPolPnts; iPolPnt++) {
        // if morphed here
        float vRel[3];
        if (_pmesh->pntVGet(_pmesh, _appPolPnts[iPolPnt].pp_idPnt, vRel)) {
          // write the coords and normal
          int iPnt = GetPntIndex(_appPolPnts[iPolPnt].pp_idPnt);
          fprintf(_f, "      { %d; %g, %g, %g; %g, %g, %g; }\n", iPolPnt, 
            _avPnts[iPnt][0], _avPnts[iPnt][1], -_avPnts[iPnt][2], 
            _avPntNormals[iPnt][0], _avPntNormals[iPnt][1], -_avPntNormals[iPnt][2]);
        }
      }}
      fprintf(_f, "    }\n");
      fprintf(_f, "  }\n");
    }}
    fprintf(_f, "}\n\n");
    fprintf(_f, "SE_MESH_END;\n");

    _msg->info("Saved:", fnmOut);

  end:
    // close and free everything
    if (_f!=NULL) {
      fclose(_f);
      _f=NULL;
    }
    if (_aidPntIDs!=NULL) {
      free(_aidPntIDs);
      _aidPntIDs = NULL;
    }
    if (_aidPolIDs!=NULL) {
      free(_aidPolIDs);
      _aidPolIDs = NULL;
    }
    if (_appPolPnts!=NULL) {
      free(_appPolPnts);
      _appPolPnts = NULL;
    }
    if (_appPntPols!=NULL) {
      free(_appPntPols);
      _appPntPols = NULL;
    }
    if (_avPnts!=NULL) {
      free(_avPnts);
      _avPnts = NULL;
    }
    if (_avPolNormals!=NULL) {
      free(_avPolNormals);
      _avPolNormals = NULL;
    }
    if (_avPntNormals!=NULL) {
      free(_avPntNormals);
      _avPntNormals = NULL;
    }
    if (_strFileName!=NULL) {
      free(_strFileName);
      _strFileName = NULL;
    }
    if (_astrUVMapNames!=NULL) {
      free(_astrUVMapNames);
      _astrUVMapNames = NULL;
    }
    if (_astrWeightMapNames!=NULL) {
      free(_astrWeightMapNames);
      _astrWeightMapNames = NULL;
    }
    // get next mesh obj
    _objid = _iti->next(_objid);
  }

  return AFUNC_OK;
}