/* 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 "Engine/StdH.h"

#include <Engine/Models/ModelObject.h>
#include <Engine/Models/ModelData.h>
#include <Engine/Models/EditModel.h>
#include <Engine/Models/MipMaker.h>
#include <Engine/Math/Geometry.inl>
#include <Engine/Models/Model_internal.h>
#include <Engine/Base/Stream.h>
#include <Engine/Base/ListIterator.inl>
#include <Engine/Models/Normals.h>
#include <Engine/Graphics/DrawPort.h>

#include <Engine/Templates/StaticArray.cpp>
#include <Engine/Templates/DynamicArray.cpp>
#include <Engine/Templates/Stock_CTextureData.h>

// Globally instanciated object containing routines for dealing with progres messages
CProgressRoutines ProgresRoutines;

// constants important to this module
#define MAX_ALLOWED_DISTANCE 0.0001f

#define	PC_ALLWAYS_ON (1UL << 30)
#define	PC_ALLWAYS_OFF (1UL << 31)


// origin triangle for transforming object
INDEX aiTransVtx[3] = {-1,-1,-1};

class CExtractSurfaceVertex
{
public:
	INDEX esv_Surface;
	SLONG esv_TextureVertexRemap;
	INDEX esv_MipGlobalIndex;
};

CThumbnailSettings::CThumbnailSettings( void)
{
  ts_bSet = FALSE;
}

void CThumbnailSettings::Read_t( CTStream *strFile)
{
  *strFile>>ts_bSet;
  *strFile>>ts_plLightPlacement;
  *strFile>>ts_plModelPlacement;
	*strFile>>ts_fTargetDistance;
	*strFile>>ts_vTarget;
	*strFile>>ts_angViewerOrientation;
  *strFile>>ts_LightDistance;
  *strFile>>ts_LightColor;
  *strFile>>ts_colAmbientColor;
	*strFile>>ts_PaperColor;
	*strFile>>ts_InkColor;
	*strFile>>ts_IsWinBcgTexture;
	*strFile>>ts_WinBcgTextureName;
  ts_RenderPrefs.Read_t( strFile);
}

void CThumbnailSettings::Write_t( CTStream *strFile)
{
  *strFile<<ts_bSet;
  *strFile<<ts_plLightPlacement;
  *strFile<<ts_plModelPlacement;
	*strFile<<ts_fTargetDistance;
	*strFile<<ts_vTarget;
	*strFile<<ts_angViewerOrientation;
  *strFile<<ts_LightDistance;
  *strFile<<ts_LightColor;
  *strFile<<ts_colAmbientColor;
	*strFile<<ts_PaperColor;
	*strFile<<ts_InkColor;
	*strFile<<ts_IsWinBcgTexture;
	*strFile<<ts_WinBcgTextureName;
  ts_RenderPrefs.Write_t( strFile);
}

CEditModel::CEditModel()
{
  edm_md.md_bIsEdited = TRUE; // this model data is edited
  edm_iActiveCollisionBox = 0;
}

CEditModel::~CEditModel()
{
  FORDELETELIST( CTextureDataInfo, tdi_ListNode, edm_WorkingSkins, litDel3)
  {
    ASSERT( litDel3->tdi_TextureData != NULL);
    _pTextureStock->Release( litDel3->tdi_TextureData);
    delete &litDel3.Current();
  }
}

CProgressRoutines::CProgressRoutines()
{
  SetProgressMessage = NULL;
  SetProgressRange = NULL;
  SetProgressState = NULL;
}


//----------------------------------------------------------------------------------------------
/*
 * This routine loads animation data from opened model script file and converts loaded data
 * to model's frame vertices format
 */

struct VertexNeighbors { CStaticStackArray<INDEX> vp_aiNeighbors; };

void CEditModel::LoadModelAnimationData_t( CTStream *pFile, const FLOATmatrix3D &mStretch) // throw char *
{
  try {
  CObject3D::BatchLoading_t(TRUE);

  INDEX i;
	CObject3D OB3D;
	CListHead FrameNamesList;
	FLOATaabbox3D OneFrameBB;
	FLOATaabbox3D AllFramesBB;

  INDEX ctFramesBefore = edm_md.md_FramesCt;
  edm_md.ClearAnimations();

	OB3D.ob_aoscSectors.Lock();
	// there must be at least one mip model loaded, throw if not
	if( edm_md.md_VerticesCt == 0) {
		throw( "Trying to update model's animations, but model doesn't exists!");
	}
	edm_md.LoadFromScript_t( pFile, &FrameNamesList);		// load model's animation data from script

  // if recreating animations, frame count must be the same
  if( (ctFramesBefore != 0) && (FrameNamesList.Count() != ctFramesBefore) )
  {
		throw( "If you are updating animations, you can't change number of frames. \
      If you want to add or remove some frames or animations, please recreate the model.");
  }
	edm_md.md_FramesCt = FrameNamesList.Count();

	/*
	 * Now we will allocate frames and frames info array and array od 3D objects,
	 * one for each frame.
	 */

  if( ProgresRoutines.SetProgressMessage != NULL) {
    ProgresRoutines.SetProgressMessage( "Calculating bounding boxes ...");
  }
  if( ProgresRoutines.SetProgressRange != NULL) {
    ProgresRoutines.SetProgressRange( FrameNamesList.Count());
  }

  edm_md.md_FrameInfos.New( edm_md.md_FramesCt);
	if( edm_md.md_Flags & MF_COMPRESSED_16BIT) {
    edm_md.md_FrameVertices16.New( edm_md.md_FramesCt * edm_md.md_VerticesCt);
  } else {
    edm_md.md_FrameVertices8.New( edm_md.md_FramesCt * edm_md.md_VerticesCt);
  }

	INDEX iO3D = 0;                        // index used for progress dialog
  CStaticStackArray<FLOAT3D> avVertices; // for caching all vertices in all frames

  BOOL bOrigin = FALSE;
  FLOATmatrix3D mOrientation;
  // set bOrigin if aiTransVtx is valid
  if((aiTransVtx[0] >=0) && (aiTransVtx[1] >=0) && (aiTransVtx[2] >=0))
  {
    bOrigin = TRUE;
  }

  {FOREACHINLIST( CFileNameNode, cfnn_Node, FrameNamesList, itFr)
	{
    CFileNameNode &fnnFileNameNode = itFr.Current();
    if( ProgresRoutines.SetProgressState != NULL) ProgresRoutines.SetProgressState(iO3D);
		OB3D.Clear();
    OB3D.LoadAny3DFormat_t( CTString(itFr->cfnn_FileName), mStretch);
    if( edm_md.md_VerticesCt != OB3D.ob_aoscSectors[0].osc_aovxVertices.Count()) {
			ThrowF_t( "File %s, one of animation frame files has wrong number of points.", 
        (const char *) (CTString)fnnFileNameNode.cfnn_FileName);
		}
    if(bOrigin)
    {
      // calc matrix for vertex transform
      FLOAT3D vY = DOUBLEtoFLOAT(OB3D.ob_aoscSectors[0].osc_aovxVertices[aiTransVtx[2]]-OB3D.ob_aoscSectors[0].osc_aovxVertices[aiTransVtx[0]]);
      FLOAT3D vZ = DOUBLEtoFLOAT(OB3D.ob_aoscSectors[0].osc_aovxVertices[aiTransVtx[0]]-OB3D.ob_aoscSectors[0].osc_aovxVertices[aiTransVtx[1]]);
      FLOAT3D vX = vY*vZ;
      vY = vZ*vX;
      // make a rotation matrix from those vectors
      vX.Normalize();
      vY.Normalize();
      vZ.Normalize();

      mOrientation(1,1) = vX(1); mOrientation(1,2) = vY(1); mOrientation(1,3) = vZ(1);
      mOrientation(2,1) = vX(2); mOrientation(2,2) = vY(2); mOrientation(2,3) = vZ(2);
      mOrientation(3,1) = vX(3); mOrientation(3,2) = vY(3); mOrientation(3,3) = vZ(3);
      mOrientation = !mOrientation;
    }

    // normalize (clear) our Bounding Box
		OB3D.ob_aoscSectors[0].LockAll();
    OneFrameBB = FLOATaabbox3D();				 		 
    // Bounding Box makes union with all points in this frame
    for( i=0; i<edm_md.md_VerticesCt; i++)	{ 
      FLOAT3D vVtx = DOUBLEtoFLOAT( OB3D.ob_aoscSectors[0].osc_aovxVertices[i]);
      if(bOrigin)
      {
        // transform vertex
        vVtx -= DOUBLEtoFLOAT(OB3D.ob_aoscSectors[0].osc_aovxVertices[aiTransVtx[0]]);
        vVtx *= mOrientation;
      }
      OneFrameBB |= FLOATaabbox3D(vVtx);
      avVertices.Push() = vVtx;  // cache vertex
    }
		OB3D.ob_aoscSectors[0].UnlockAll();
    // remember this frame's Bounding Box
		edm_md.md_FrameInfos[iO3D].mfi_Box = OneFrameBB;	
    // make union with Bounding Box of all frames
		AllFramesBB |= OneFrameBB;							
    // load next frame
		iO3D++;																	
  }}

  // calculate stretch vector
	edm_md.md_Stretch = AllFramesBB.Size()/2.0f;       // get size of bounding box

  // correct invalid stretch factors
  if( edm_md.md_Stretch(1) == 0.0f) edm_md.md_Stretch(1) = 1.0f;
  if( edm_md.md_Stretch(2) == 0.0f) edm_md.md_Stretch(2) = 1.0f;
  if( edm_md.md_Stretch(3) == 0.0f) edm_md.md_Stretch(3) = 1.0f;

  // build links from vertices to polygons
  CStaticArray<VertexNeighbors> avnVertices;
  avnVertices.New( edm_md.md_VerticesCt);
  
  // lost 1st frame (one frame is enough because all frames has same poly->edge->vertex links)
	OB3D.Clear();
  const CTString &fnmFirstFrame = LIST_HEAD( FrameNamesList, CFileNameNode, cfnn_Node)->cfnn_FileName;
  OB3D.LoadAny3DFormat_t( fnmFirstFrame, mStretch);
	OB3D.ob_aoscSectors[0].LockAll();

  // loop thru polygons
  INDEX iPolyNo=0;
  {FOREACHINDYNAMICARRAY( OB3D.ob_aoscSectors[0].osc_aopoPolygons, CObjectPolygon, itPoly)
  {
    CObjectPolygon &opo = *itPoly;
    // only triangles are supported!
    ASSERT( opo.opo_PolygonEdges.Count() == 3);  
    if( opo.opo_PolygonEdges.Count() != 3) {
  		ThrowF_t( "Non-triangle polygon encountered in model file %s !", (const char *) fnmFirstFrame);
    }
    // get all 3 vetrices of current polygon and sorted them
    opo.opo_PolygonEdges.Lock();
    CObjectPolygonEdge &opeCurr = opo.opo_PolygonEdges[0];
    CObjectPolygonEdge &opeNext = opo.opo_PolygonEdges[1];
    CObjectVertex *povxCurr, *povxPrev, *povxNext;
    if( !opeCurr.ope_Backward) {
      povxCurr = opeCurr.ope_Edge->oed_Vertex1;
      povxPrev = opeCurr.ope_Edge->oed_Vertex0;
      ASSERT( opeNext.ope_Edge->oed_Vertex0 == povxCurr);
    } else {
      povxCurr = opeCurr.ope_Edge->oed_Vertex0;
      povxPrev = opeCurr.ope_Edge->oed_Vertex1;
      ASSERT( opeNext.ope_Edge->oed_Vertex1 == povxCurr);
    }
    if( !opeNext.ope_Backward) {
      povxNext = opeNext.ope_Edge->oed_Vertex1;
      ASSERT( opeNext.ope_Edge->oed_Vertex0 == povxCurr);
    } else {
      povxNext = opeNext.ope_Edge->oed_Vertex0;
      ASSERT( opeNext.ope_Edge->oed_Vertex1 == povxCurr);
    }
    INDEX iVtx0 = OB3D.ob_aoscSectors[0].osc_aovxVertices.Index(povxPrev);
    INDEX iVtx1 = OB3D.ob_aoscSectors[0].osc_aovxVertices.Index(povxCurr);
    INDEX iVtx2 = OB3D.ob_aoscSectors[0].osc_aovxVertices.Index(povxNext);
    // add neighbor vertices for each of this vertices
    avnVertices[iVtx0].vp_aiNeighbors.Push() = iVtx2;
    avnVertices[iVtx0].vp_aiNeighbors.Push() = iVtx1;
    avnVertices[iVtx1].vp_aiNeighbors.Push() = iVtx0;
    avnVertices[iVtx1].vp_aiNeighbors.Push() = iVtx2;
    avnVertices[iVtx2].vp_aiNeighbors.Push() = iVtx1;
    avnVertices[iVtx2].vp_aiNeighbors.Push() = iVtx0;
    // advance to next poly
    opo.opo_PolygonEdges.Unlock();
    iPolyNo++;
  }}
  // vertex->polygons links created
	OB3D.ob_aoscSectors[0].UnlockAll();


  // cache strecthing reciprocal for faster calc
  FLOAT f1oStretchX, f1oStretchY, f1oStretchZ;
  if( edm_md.md_Flags & MF_COMPRESSED_16BIT) {
    f1oStretchX = 32767.0f / edm_md.md_Stretch(1);
    f1oStretchY = 32767.0f / edm_md.md_Stretch(2);
    f1oStretchZ = 32767.0f / edm_md.md_Stretch(3);
  } else {
    f1oStretchX = 127.0f / edm_md.md_Stretch(1);
    f1oStretchY = 127.0f / edm_md.md_Stretch(2);
    f1oStretchZ = 127.0f / edm_md.md_Stretch(3);
  }

  // remember center vector
  FLOAT3D vCenter = AllFramesBB.Center();  // obtain bbox center
  edm_md.md_vCenter = vCenter;
	
  // prepare progress bar
  if( ProgresRoutines.SetProgressMessage != NULL) {
    ProgresRoutines.SetProgressMessage( "Calculating gouraud normals and stretching vertices ...");
  }
  if( ProgresRoutines.SetProgressRange != NULL) {
    ProgresRoutines.SetProgressRange( edm_md.md_FramesCt);
  }

  // loop thru frames
  iO3D=0;  // index for progress
	INDEX iFVtx=0;  // count for all vertices in all frames
  for( INDEX iFr=0; iFr<edm_md.md_FramesCt; iFr++)
  {
    // calculate all polygon normals for this frame
    if( ProgresRoutines.SetProgressState != NULL) ProgresRoutines.SetProgressState(iO3D);
		for( INDEX iVtx=0; iVtx<edm_md.md_VerticesCt; iVtx++)	// for all vertices in one frame
		{
      FLOAT3D &vVtx = avVertices[iFVtx];
    	if( edm_md.md_Flags & MF_COMPRESSED_16BIT) {
        edm_md.md_FrameVertices16[iFVtx].mfv_SWPoint = SWPOINT3D(
			    FloatToInt( (vVtx(1) - vCenter(1)) * f1oStretchX),
			    FloatToInt( (vVtx(2) - vCenter(2)) * f1oStretchY),
			    FloatToInt( (vVtx(3) - vCenter(3)) * f1oStretchZ) );
      } else {                                                              
        edm_md.md_FrameVertices8[iFVtx].mfv_SBPoint = SBPOINT3D(           
			    FloatToInt( (vVtx(1) - vCenter(1)) * f1oStretchX),
			    FloatToInt( (vVtx(2) - vCenter(2)) * f1oStretchY),
			    FloatToInt( (vVtx(3) - vCenter(3)) * f1oStretchZ) );
      }

			// calculate vector of gouraud normal in this vertice
      ANGLE   aSum = 0;
			FLOAT3D vSum( 0.0f, 0.0f, 0.0f);
      INDEX iFrOffset = edm_md.md_VerticesCt * iFr;
      VertexNeighbors &vnCurr = avnVertices[iVtx];
      for( INDEX iNVtx=0; iNVtx<vnCurr.vp_aiNeighbors.Count(); iNVtx+=2) { // loop thru neighbors
        INDEX iPrev = vnCurr.vp_aiNeighbors[iNVtx+0];
        INDEX iNext = vnCurr.vp_aiNeighbors[iNVtx+1];
        FLOAT3D v0  = avVertices[iPrev+iFrOffset] - vVtx;
        FLOAT3D v1  = avVertices[iNext+iFrOffset] - vVtx;
        v0.Normalize();
        v1.Normalize();
        FLOAT3D v = v1*v0;
        FLOAT fLength = v.Length();
        ANGLE a = ASin(fLength);
        //ASSERT( a>=0 && a<=180);
        aSum += a;
        vSum += (v/fLength) * a;
			}

      // normalize sum of polygon normals
      //ASSERT( aSum>=0);
      vSum /= aSum;
      vSum.Normalize();

      // save compressed gouraud normal
    	if( edm_md.md_Flags & MF_COMPRESSED_16BIT) {
        CompressNormal_HQ( vSum, edm_md.md_FrameVertices16[iFVtx].mfv_ubNormH,
                                 edm_md.md_FrameVertices16[iFVtx].mfv_ubNormP);
      } else {
			  edm_md.md_FrameVertices8[iFVtx].mfv_NormIndex = (UBYTE)GouraudNormal(vSum);
      }

      // advance to next vertex in model
			iFVtx++;
		}

    // advance to next frame
    iO3D++;
  }

  // list of filenames is no longer needed
	FORDELETELIST( CFileNameNode, cfnn_Node, FrameNamesList, litDel) delete &litDel.Current();

  // create compressed vector center that will be used for setting object handle
  edm_md.md_vCompressedCenter(1) = -edm_md.md_vCenter(1) * f1oStretchX;
  edm_md.md_vCompressedCenter(2) = -edm_md.md_vCenter(2) * f1oStretchY;
  edm_md.md_vCompressedCenter(3) = -edm_md.md_vCenter(3) * f1oStretchZ;

  // adjust stretching for compressed format
  if( edm_md.md_Flags & MF_COMPRESSED_16BIT) {
    edm_md.md_Stretch(1) /= 32767.0f; 
    edm_md.md_Stretch(2) /= 32767.0f;
    edm_md.md_Stretch(3) /= 32767.0f;
  } else {
    edm_md.md_Stretch(1) /= 127.0f; 
    edm_md.md_Stretch(2) /= 127.0f;
    edm_md.md_Stretch(3) /= 127.0f;
  }

  // all done
	OB3D.ob_aoscSectors.Unlock();

  CObject3D::BatchLoading_t(FALSE);
  } catch (char*) {
  CObject3D::BatchLoading_t(FALSE);
  throw;
  }
}


//--------------------------------------------------------------------------------------------
/*
 * Routine saves model's .h file (#define ......)
 */
void CEditModel::SaveIncludeFile_t( CTFileName fnFileName, CTString strDefinePrefix) // throw char *
{
  CTFileStream strmHFile;
  char line[ 1024];
  INDEX i;

  strmHFile.Create_t( fnFileName, CTStream::CM_TEXT);
  strcpy( line, strDefinePrefix);
  strupr( line);
  strDefinePrefix = CTString( line);

  sprintf( line, "// Animation names\n");
  strmHFile.Write_t( line, strlen( line));
  // force animation prefix string to be upper case
  char achrUprName[ 256];
  strcpy( achrUprName, strDefinePrefix);
  strcat( achrUprName, "_ANIM_");
  CTString strAnimationPrefix = achrUprName;
  edm_md.ExportAnimationNames_t( &strmHFile, achrUprName);

  sprintf( line, "\n// Color names\n");
  strmHFile.Write_t( line, strlen( line));

  for( i=0; i<MAX_COLOR_NAMES; i++)
  {
    if( edm_md.md_ColorNames[ i] != "")
    {
      sprintf( line, "#define %s_PART_%s ((1L) << %d)\n", (const char *) strDefinePrefix, (const char *) edm_md.md_ColorNames[ i], i);
      strmHFile.Write_t( line, strlen( line));
    }
  }
  sprintf( line, "\n// Patch names\n");
  strmHFile.Write_t( line, strlen( line));

  for( INDEX iPatch=0; iPatch<MAX_TEXTUREPATCHES; iPatch++)
  {
    CTString strPatchName = edm_md.md_mpPatches[ iPatch].mp_strName;
    if( strPatchName != "")
    {
      sprintf( line, "#define %s_PATCH_%s %d\n", (const char *) strDefinePrefix, (const char *) strPatchName, i);
      strmHFile.Write_t( line, strlen( line));
    }
  }
  // save collision box names
  sprintf( line, "\n// Names of collision boxes\n");
  strmHFile.Write_t( line, strlen( line));

  edm_md.md_acbCollisionBox.Lock();
  // save all collision boxes
  for( INDEX iCollisionBox=0; iCollisionBox<edm_md.md_acbCollisionBox.Count(); iCollisionBox++)
  {
    // prepare collision box name as define
    sprintf( line, "#define %s_COLLISION_BOX_%s %d\n", (const char *) strDefinePrefix, (const char *) GetCollisionBoxName( iCollisionBox),
      iCollisionBox);
    strmHFile.Write_t( line, strlen( line));
  }
  edm_md.md_acbCollisionBox.Unlock();

  // save all attaching positions
  sprintf( line, "\n// Attaching position names\n");
  strmHFile.Write_t( line, strlen( line));
  INDEX iAttachingPlcement = 0;
  FOREACHINDYNAMICARRAY(edm_aamAttachedModels, CAttachedModel, itam)
  {
    char achrUpper[ 256];
    strcpy( achrUpper, itam->am_strName);
    strupr( achrUpper);
    sprintf( line, "#define %s_ATTACHMENT_%s %d\n", (const char *) strDefinePrefix, achrUpper, iAttachingPlcement);
    strmHFile.Write_t( line, strlen( line));
    iAttachingPlcement++;
  }
  sprintf( line, "\n// Sound names\n");
  strmHFile.Write_t( line, strlen( line));

  for( INDEX iSound=0; iSound<edm_aasAttachedSounds.Count(); iSound++)
  {
    if( edm_aasAttachedSounds[iSound].as_fnAttachedSound != "")
    {
      CTString strLooping;
      if( edm_aasAttachedSounds[iSound].as_bLooping) strLooping = "L";
      else                                           strLooping = "NL";

      CTString strDelay = "";
      if( edm_aasAttachedSounds[iSound].as_fDelay == 0.0f)
        strDelay = "0.0f";
      else
        strDelay.PrintF( "%gf", edm_aasAttachedSounds[iSound].as_fDelay);

      CAnimInfo aiInfo;
      edm_md.GetAnimInfo( iSound, aiInfo);

      CTString strWithQuotes;
      strWithQuotes.PrintF( "\"%s\",", (const char *) CTString(edm_aasAttachedSounds[iSound].as_fnAttachedSound));

      sprintf( line, "//sound SOUND_%s_%-16s %-32s // %s, %s, %s\n",
        (const char *) strDefinePrefix,
        aiInfo.ai_AnimName,
        (const char *) strWithQuotes,
        (const char *) (strAnimationPrefix+aiInfo.ai_AnimName),
        (const char *) strLooping,
        (const char *) strDelay);
      strmHFile.Write_t( line, strlen( line));
    }
  }

  strmHFile.Close();
}

// overloaded save function
void CEditModel::Save_t( CTFileName fnFileName) // throw char *
{
  CTFileName fnMdlFileName = fnFileName.FileDir() + fnFileName.FileName() + ".mdl";
  edm_md.Save_t( fnMdlFileName);

  CTFileName fnHFileName = fnFileName.FileDir() + fnFileName.FileName() + ".h";
  CTString strPrefix = fnFileName.FileName();
  if (strPrefix.Length()>0 && !isalpha(strPrefix[0]) && strPrefix[0]!='_') {
    strPrefix="_"+strPrefix;
  }
  SaveIncludeFile_t( fnHFileName, strPrefix);

  CTFileName fnIniFileName = fnFileName.FileDir() + fnFileName.FileName() + ".ini";
  CSerial::Save_t( fnIniFileName);
}

// overloaded load function
void CEditModel::Load_t( CTFileName fnFileName)
{
  CTFileName fnMdlFileName = fnFileName.FileDir() + fnFileName.FileName() + ".mdl";
  edm_md.Load_t( fnMdlFileName);

  CTFileName fnIniFileName = fnFileName.FileDir() + fnFileName.FileName() + ".ini";
  // try to load ini file
  try
  {
    CSerial::Load_t( fnIniFileName);
  }
  catch(char *strError)
  {
    // ignore errors
    (void) strError;
    CreateEmptyAttachingSounds();
  }
}

CTextureDataInfo *CEditModel::AddTexture_t(const CTFileName &fnFileName, const MEX mexWidth,
                            const MEX mexHeight)
{
  CTextureDataInfo *pNewTDI = new CTextureDataInfo;
  pNewTDI->tdi_FileName = fnFileName;

  try
  {
    pNewTDI->tdi_TextureData = _pTextureStock->Obtain_t( pNewTDI->tdi_FileName);
  }
  catch(char *strError)
  {
    (void) strError;
    delete pNewTDI;
    return NULL;
  }

  // reload the texture
  pNewTDI->tdi_TextureData->Reload();

  edm_WorkingSkins.AddTail( pNewTDI->tdi_ListNode);
  return pNewTDI;
}

CAttachedModel::CAttachedModel(void)
{
  am_strName = "No name";
  am_iAnimation = 0;
  am_bVisible = TRUE;
}

CAttachedModel::~CAttachedModel(void)
{
  Clear();
}

void CAttachedModel::Clear(void)
{
  am_moAttachedModel.mo_toTexture.SetData(NULL);
  am_moAttachedModel.mo_toReflection.SetData(NULL);
  am_moAttachedModel.mo_toSpecular.SetData(NULL);
  am_moAttachedModel.mo_toBump.SetData(NULL);
  am_moAttachedModel.SetData(NULL);
}

void CAttachedModel::Read_t( CTStream *pstrmFile) // throw char *
{
  *pstrmFile >> am_bVisible;
  *pstrmFile >> am_strName;
  // this data is used no more
  CTFileName fnModel, fnDummy;
  *pstrmFile >> fnModel;
  
  // new attached model format has saved index of animation
  if( pstrmFile->PeekID_t() == CChunkID("AMAN"))
  {
    pstrmFile->ExpectID_t( CChunkID( "AMAN"));
    *pstrmFile >> am_iAnimation;
  }
  else
  {
    *pstrmFile >> fnDummy; // ex model's texture
  }

  try
  {
    SetModel_t( fnModel);
  }
  catch(char *strError)
  {
    (void) strError;
    try
    {
      SetModel_t( CTFILENAME("Models\\Editor\\Axis.mdl"));
    }
    catch(char *strError)
    {
      FatalError( strError);
    }
  }
}

void CAttachedModel::Write_t( CTStream *pstrmFile) // throw char *
{
  *pstrmFile << am_bVisible;
  *pstrmFile << am_strName;
  *pstrmFile << am_moAttachedModel.GetName();

  // new attached model format has saved index of animation
  pstrmFile->WriteID_t( CChunkID("AMAN"));
  *pstrmFile << am_iAnimation;
}

void CAttachedModel::SetModel_t(CTFileName fnModel)
{
  am_moAttachedModel.SetData_t(fnModel);
  am_moAttachedModel.AutoSetTextures();
}

CAttachedSound::CAttachedSound( void)
{
  as_fDelay = 0.0f;
  as_fnAttachedSound = CTString("");
  as_bLooping = FALSE;
  as_bPlaying = TRUE;
}

void CAttachedSound::Read_t(CTStream *strFile)
{
  *strFile>>as_bLooping;
  *strFile>>as_bPlaying;
  *strFile>>as_fnAttachedSound;
  *strFile>>as_fDelay;
}

void CAttachedSound::Write_t(CTStream *strFile)
{
  *strFile<<as_bLooping;
  *strFile<<as_bPlaying;
  *strFile<<as_fnAttachedSound;
  *strFile<<as_fDelay;
}

void CEditModel::CreateEmptyAttachingSounds( void)
{
  ASSERT( edm_md.GetAnimsCt() > 0);
  edm_aasAttachedSounds.Clear();
  edm_aasAttachedSounds.New( edm_md.GetAnimsCt());
}

void CEditModel::Read_t( CTStream *pFile) // throw char *
{
  CTFileName fnFileName;
  INDEX i, iWorkingTexturesCt;

  pFile->ExpectID_t( CChunkID( "WTEX"));
  *pFile >> iWorkingTexturesCt;

  for( i=0; i<iWorkingTexturesCt; i++)
  {
    *pFile >> fnFileName;
    try
    {
      AddTexture_t( fnFileName, edm_md.md_Width, edm_md.md_Height);
    }
    // This is here because we want to load model even if its texture is not valid
    catch( char *err_str){ (char *) err_str;}
  }

  // skip patches saved in old format (patches do not exist inside EditModel any more)
  if( pFile->PeekID_t() == CChunkID("PATM"))
  {
    pFile->GetID_t();
    ULONG ulDummySizeOfLong;
    ULONG ulOldExistingPatches;

    *pFile >> ulDummySizeOfLong;
    *pFile >> ulOldExistingPatches;
    for( i=0; i<MAX_TEXTUREPATCHES; i++)
    {
      if( ((1UL << i) & ulOldExistingPatches) != 0)
      {
        CTFileName fnPatchName;
        *pFile >> fnPatchName;
      }
    }
  }

  // try to load attached models
  try
  {
    pFile->ExpectID_t( CChunkID( "ATTM"));
    INDEX ctSavedModels;
    *pFile >> ctSavedModels;

    // clamp no of saved attachments to no of model's data attached positions
    INDEX ctMDAttachments = edm_md.md_aampAttachedPosition.Count();
    INDEX ctToLoad = ClampUp( ctSavedModels, ctMDAttachments);
    INDEX ctToSkip = ctSavedModels - ctToLoad;

    // add attached models
    edm_aamAttachedModels.Clear();
    if( ctToLoad != 0)
    {
      edm_aamAttachedModels.New( ctSavedModels);
      // read all attached models
      FOREACHINDYNAMICARRAY(edm_aamAttachedModels, CAttachedModel, itam)
      {
        itam->Read_t(pFile);
      }
    }

    // skip unused attached models
    for( INDEX iSkip=0; iSkip<ctToSkip; iSkip++)
    {
      CAttachedModel atmDummy;
      atmDummy.Read_t(pFile);
    }
  }
  catch( char *strError)
  {
    (void) strError;
    // clear attached models
    edm_aamAttachedModels.Clear();
    edm_md.md_aampAttachedPosition.Clear();
  }

  CreateEmptyAttachingSounds();
  // try to load attached sounds
  try
  {
    pFile->ExpectID_t( CChunkID( "ATSD"));
    INDEX ctAttachedSounds;
    *pFile >> ctAttachedSounds;
    INDEX ctExisting = edm_aasAttachedSounds.Count();
    INDEX ctToRead = ClampUp( ctAttachedSounds, ctExisting);

    // read all saved attached sounds
    for( INDEX iSound=0; iSound<ctToRead; iSound++)
    {
      CAttachedSound &as = edm_aasAttachedSounds[ iSound];
      as.Read_t(pFile);
    }

    // skipped saved but now obsolite
    INDEX ctToSkip = ctAttachedSounds - ctToRead;
    for( INDEX iSkip=0; iSkip<ctToSkip; iSkip++)
    {
      CAttachedSound asDummy;
      asDummy.Read_t(pFile);
    }
  }
  catch( char *strError)
  {
    (void) strError;
  }

  try
  {
    // load last taken thumbnail settings
    pFile->ExpectID_t( CChunkID( "TBST"));
    edm_tsThumbnailSettings.Read_t( pFile);
  }
  catch( char *strError)
  {
    // ignore errors
    (void) strError;
  }

  // load names of effect textures
  // --- specular texture
  try {
    pFile->ExpectID_t( CChunkID( "FXTS"));
    *pFile >> edm_fnSpecularTexture;
  } catch( char *strError) { (void) strError; }

  // --- reflection texture
  try {
    pFile->ExpectID_t( CChunkID( "FXTR"));
    *pFile >> edm_fnReflectionTexture;
  } catch( char *strError) { (void) strError; }

  // --- bump texture
  try {
    pFile->ExpectID_t( CChunkID( "FXTB"));
    *pFile >> edm_fnBumpTexture;
  } catch( char *strError) { (void) strError; }
}

void CEditModel::Write_t( CTStream *pFile) // throw char *
{
  pFile->WriteID_t( CChunkID( "WTEX"));

  INDEX iWorkingTexturesCt = edm_WorkingSkins.Count();
  *pFile << iWorkingTexturesCt;

  FOREACHINLIST( CTextureDataInfo, tdi_ListNode, edm_WorkingSkins, it)
  {
    *pFile << it->tdi_FileName;
  }

  // CEditModel class has no patches in new patch data format

  pFile->WriteID_t( CChunkID( "ATTM"));
  INDEX ctAttachedModels = edm_aamAttachedModels.Count();
  *pFile << ctAttachedModels;
  // write all attached models
  FOREACHINDYNAMICARRAY(edm_aamAttachedModels, CAttachedModel, itam)
  {
    itam->Write_t(pFile);
  }

  pFile->WriteID_t( CChunkID( "ATSD"));
  INDEX ctAttachedSounds = edm_aasAttachedSounds.Count();
  *pFile << ctAttachedSounds;
  // write all attached models
  FOREACHINSTATICARRAY(edm_aasAttachedSounds, CAttachedSound, itas)
  {
    itas->Write_t(pFile);
  }

  // save last taken thumbnail settings
  pFile->WriteID_t( CChunkID( "TBST"));
  edm_tsThumbnailSettings.Write_t( pFile);

  // save names of effect textures
  // --- specular texture
  pFile->WriteID_t( CChunkID( "FXTS"));
  *pFile << edm_fnSpecularTexture;
  // --- reflection texture
  pFile->WriteID_t( CChunkID( "FXTR"));
  *pFile << edm_fnReflectionTexture;
  // --- bump texture
  pFile->WriteID_t( CChunkID( "FXTB"));
  *pFile << edm_fnBumpTexture;
}
//----------------------------------------------------------------------------------------------
/*
 * Routine saves defult script file containing only one animation with default data
 * Input file name is .LWO file name, not .SCR
 */
void CEditModel::CreateScriptFile_t(CTFileName &fnO3D) // throw char *
{
  CTFileName fnScriptName = fnO3D.FileDir() + fnO3D.FileName() + ".scr";
  CTFileStream File;
  char line[ 256];

  File.Create_t( fnScriptName, CTStream::CM_TEXT);
  File.PutLine_t( ";******* Creation settings");
  File.PutLine_t( "TEXTURE_DIM 2.0 2.0");
  File.PutLine_t( "SIZE 1.0");
  File.PutLine_t( "MAX_SHADOW 0");
  File.PutLine_t( "HI_QUALITY YES");
  File.PutLine_t( "FLAT NO");
  File.PutLine_t( "HALF_FLAT NO");
  File.PutLine_t( "STRETCH_DETAIL NO");
  File.PutLine_t( "");
  File.PutLine_t( ";******* Mip models");
  sprintf( line, "DIRECTORY %s", (const char *) (const CTString&)fnO3D.FileDir());
  File.PutLine_t( line);
  File.PutLine_t( "MIP_MODELS 1");
  sprintf( line, "    %s", (const char *) (const CTString&)(fnO3D.FileName() + fnO3D.FileExt()));
  File.PutLine_t( line);
  File.PutLine_t( "");
  File.PutLine_t( "ANIM_START");
  File.PutLine_t( ";******* Start of animation block");
  File.PutLine_t( "");
  sprintf( line, "DIRECTORY %s", (const char *) (const CTString&)fnO3D.FileDir());
  File.PutLine_t( line);
  File.PutLine_t( "ANIMATION Default");
  File.PutLine_t( "SPEED 0.1");
  sprintf( line, "    %s", (const char *) (const CTString&)(fnO3D.FileName() + fnO3D.FileExt()));
  File.PutLine_t( line);
  File.PutLine_t( "");
  File.PutLine_t( ";******* End of animation block");
  File.PutLine_t( "ANIM_END");
  File.PutLine_t( "");
  File.PutLine_t( "END");
  File.Close();
}
//----------------------------------------------------------------------------------------------
/*
 * This routine load lines from script file and executes appropriate actions
 */
#define EQUAL_SUB_STR( str) (strnicmp( ld_line, str, strlen(str)) == 0)

void CEditModel::LoadFromScript_t(CTFileName &fnScriptName) // throw char *
{
  try {
  CObject3D::BatchLoading_t(TRUE);

  INDEX i;
  CTFileStream File;
	CObject3D O3D;
  CTFileName fnOpened, fnClosed, fnUnwrapped, fnImportMapping;
	char ld_line[ 128];
	char flag_str[ 128];
	char base_path[ PATH_MAX] = "";
	char file_name[ PATH_MAX];
  char mapping_file_name[ PATH_MAX] = "";
	char full_path[ PATH_MAX];
  FLOATmatrix3D mStretch;
  mStretch.Diagonal(1.0f);
	BOOL bMappingDimFound ;
	BOOL bAnimationsFound;
  BOOL bLoadInitialMapping;

	O3D.ob_aoscSectors.Lock();
	File.Open_t( fnScriptName);				// open script file for reading

	// if these flags will not be TRUE at the end of script, throw error
	bMappingDimFound = FALSE;
	bAnimationsFound = FALSE;
  bLoadInitialMapping = FALSE;

  // to hold number of line's chars
  int iLineChars;
	FOREVER
	{
		do
    {
      File.GetLine_t(ld_line, 128);
      iLineChars = strlen( ld_line);
    }
		while( (iLineChars == 0) || (ld_line[0]==';') );

		// If key-word is "DIRECTORY", remember base path it and add "\" character at the
		// end of new path if it is not yet there
		if( EQUAL_SUB_STR( "DIRECTORY"))
		{
			_strupr( ld_line);
      sscanf( ld_line, "DIRECTORY %s", base_path);
			if( base_path[ strlen( base_path) - 1] != '\\')
				strcat( base_path,"\\");
		}
		// Key-word "SIZE" defines stretch factor
		else if( EQUAL_SUB_STR( "SIZE"))
		{
  	  _strupr( ld_line);
      FLOAT fStretch = 1.0f;
		  sscanf( ld_line, "SIZE %g", &fStretch);
      mStretch *= fStretch;
		}
		else if( EQUAL_SUB_STR( "TRANSFORM")) 
    {
  	  _strupr( ld_line);
      FLOATmatrix3D mTran;
      mTran.Diagonal(1.0f);
		  sscanf( ld_line, "TRANSFORM %g %g %g %g %g %g %g %g %g", 
        &mTran(1,1), &mTran(1,2), &mTran(1,3),
        &mTran(2,1), &mTran(2,2), &mTran(2,3),
        &mTran(3,1), &mTran(3,2), &mTran(3,3));
      mStretch *= mTran;
    }
		// Key-word "FLAT" means that model will be mapped as face - forward, using only
    // zooming of texture
		else if( EQUAL_SUB_STR( "FLAT"))
		{
  	  _strupr( ld_line);
		  sscanf( ld_line, "FLAT %s", flag_str);
      if( strcmp( flag_str, "YES") == 0)
      {
        edm_md.md_Flags |= MF_FACE_FORWARD;
        edm_md.md_Flags &= ~MF_HALF_FACE_FORWARD;
      }
		}
		else if( EQUAL_SUB_STR( "HALF_FLAT"))
		{
  	  _strupr( ld_line);
		  sscanf( ld_line, "HALF_FLAT %s", flag_str);
      if( strcmp( flag_str, "YES") == 0)
        edm_md.md_Flags |= MF_FACE_FORWARD|MF_HALF_FACE_FORWARD;
		}
    else if( EQUAL_SUB_STR( "STRETCH_DETAIL"))
    {
  	  _strupr( ld_line);
		  sscanf( ld_line, "STRETCH_DETAIL %s", flag_str);
      if( strcmp( flag_str, "YES") == 0)
      {
        edm_md.md_Flags |= MF_STRETCH_DETAIL;
      }
    }
		else if( EQUAL_SUB_STR( "HI_QUALITY"))
		{
  	  _strupr( ld_line);
		  sscanf( ld_line, "HI_QUALITY %s", flag_str);
      if( strcmp( flag_str, "YES") == 0)
      {
        edm_md.md_Flags |= MF_COMPRESSED_16BIT;
      }
		}
		// Key-word "REFLECTIONS" has been used in old reflections
		else if( EQUAL_SUB_STR( "REFLECTIONS"))
    {
    }
		// Key-word "MAX_SHADOW" determines maximum quality of shading that model can obtain
		else if( EQUAL_SUB_STR( "MAX_SHADOW"))
    {
  	  _strupr( ld_line);
		  INDEX iShadowQuality;
      sscanf( ld_line, "MAX_SHADOW %d", &iShadowQuality);
      edm_md.md_ShadowQuality = iShadowQuality;
    }
		// Key-word "MipModel" must follow name of this mipmodel file
    else if( EQUAL_SUB_STR( "MIP_MODELS"))
    {
			INDEX iMipCt;
      sscanf( ld_line, "MIP_MODELS %d", &iMipCt);
      if( (iMipCt <= 0) || (iMipCt >= MAX_MODELMIPS))
			{
				ThrowF_t("Invalid number of mip models. Number must range from 0 to %d.", MAX_MODELMIPS-1);
			}
      if( ProgresRoutines.SetProgressMessage != NULL)
        ProgresRoutines.SetProgressMessage( "Loading and creating mip-models ...");
      if( ProgresRoutines.SetProgressRange != NULL)
        ProgresRoutines.SetProgressRange( iMipCt);
      for( i=0; i<iMipCt; i++)
      {
        if( ProgresRoutines.SetProgressState != NULL)
          ProgresRoutines.SetProgressState( i);
		    do
        {
          File.GetLine_t(ld_line, 128);
        }
		    while( (strlen( ld_line)== 0) || (ld_line[0]==';'));
			  _strupr( ld_line);
			  sscanf( ld_line, "%s", file_name);
			  sprintf( full_path, "%s%s", base_path, file_name);
        // remember name of first mip to define UV mapping
        if( i==0)
        {
          fnImportMapping = CTString( full_path);
        }
			  O3D.Clear();                            // clear possible existing O3D's data
        O3D.LoadAny3DFormat_t( CTString(full_path), mStretch);
			  if( edm_md.md_VerticesCt == 0)					// If there are no vertices in model, call New Model
			  {
				  if( bMappingDimFound == FALSE)
				  {
					  ThrowF_t("Found key word \"MIP_MODELS\" but texture dimension wasn't found.\n"
					    "There must be key word \"TEXTURE_DIM\" before key word \"MIP_MODELS\" in script file.");
				  }
				  NewModel( &O3D);
			  }
        else
        {
          O3D.ob_aoscSectors[0].LockAll();
				  AddMipModel( &O3D);										// else this is one of model's mip definitions so call Add Mip Model
	        O3D.ob_aoscSectors[0].UnlockAll();
        }
		  }
      // set default mip factors
      // all mip models will be spreaded beetween distance 0 and default maximum distance
      edm_md.SpreadMipSwitchFactors( 0, 5.0f);
    }
		// Key-word "DEFINE_MAPPING" must follow three lines with names of files used to define mapping
    else if( EQUAL_SUB_STR( "DEFINE_MAPPING"))
    {
      if( edm_md.md_VerticesCt == 0)
			{
				ThrowF_t("Found key word \"DEFINE_MAPPING\" but model is not yet created.");
			}

      File.GetLine_t(ld_line, 128);
  	  sscanf( ld_line, "%s", file_name);
      sprintf( full_path, "%s%s", base_path, file_name);
      fnOpened = CTString( full_path);

      File.GetLine_t(ld_line, 128);
  	  sscanf( ld_line, "%s", file_name);
      sprintf( full_path, "%s%s", base_path, file_name);
      fnClosed = CTString( full_path);

      File.GetLine_t(ld_line, 128);
  	  sscanf( ld_line, "%s", file_name);
      sprintf( full_path, "%s%s", base_path, file_name);
      fnUnwrapped = CTString( full_path);
    }
    else if( EQUAL_SUB_STR( "IMPORT_MAPPING"))
    {
      if( edm_md.md_VerticesCt == 0)
			{
				ThrowF_t("Found key word \"IMPORT_MAPPING\" but model is not yet created.");
			}
      File.GetLine_t(ld_line, 128);
  	  sscanf( ld_line, "%s", file_name);
      sprintf( full_path, "%s%s", base_path, file_name);
      fnImportMapping = CTString( full_path);
    }
		/*
		 * Line containing key-word "TEXTURE_DIM" gives us texture dimensions
		 * so we can create default mapping
		 */
		else if( EQUAL_SUB_STR( "TEXTURE_DIM"))
		{
			_strupr( ld_line);
			FLOAT fWidth, fHeight;
      sscanf( ld_line, "TEXTURE_DIM %f %f", &fWidth, &fHeight);	// read given texture dimensions
			edm_md.md_Width = MEX_METERS( fWidth);
      edm_md.md_Height = MEX_METERS( fHeight);
      bMappingDimFound = TRUE;
		}
		// Key-word "ANIM_START" starts loading of Animation Data object
		else if( EQUAL_SUB_STR( "ANIM_START"))
		{
			LoadModelAnimationData_t( &File, mStretch);	// loads and sets model's animation data
      // add one collision box
      edm_md.md_acbCollisionBox.New();
      // reset attaching sounds
      CreateEmptyAttachingSounds();
			bAnimationsFound = TRUE;				// mark that we found animations section in script-file
		}
    else if( EQUAL_SUB_STR( "ORIGIN_TRI"))
    {
      sscanf( ld_line, "ORIGIN_TRI %d %d %d", &aiTransVtx[0], &aiTransVtx[1], &aiTransVtx[2]);	// read given vertices
    }
		// Key-word "END" ends infinite loop and script loading is over
		else if( EQUAL_SUB_STR( "END"))
		{
			break;
		}
    // ignore old key-words
    else if( EQUAL_SUB_STR( "MAPPING")) {}
		else if( EQUAL_SUB_STR( "TEXTURE_REFLECTION")) {}
		else if( EQUAL_SUB_STR( "TEXTURE_SPECULAR")) {}
		else if( EQUAL_SUB_STR( "TEXTURE_BUMP")) {}
		else if( EQUAL_SUB_STR( "TEXTURE")) {}
		// If none of known key-words isnt recognised, we have wierd key-word, so throw error
		else
		{
      ThrowF_t("Unrecognizible key-word found in line: \"%s\".", ld_line);
		}
	}
	/*
	 * At the end we check if we found animations in script file and if initial mapping was done
	 * during loading of script file what means that key-word 'TEXTURE_DIM' was found
	 */
	if( bAnimationsFound != TRUE)
		throw( "There are no animations defined for this model, and that can't be. Probable cause: script missing key-word \"ANIM_START\".");

	if( bMappingDimFound != TRUE)
		throw( "Initial mapping not done, and that can't be. Probable cause: script missing key-word \"TEXTURE_DIM\".");

  edm_md.LinkDataForSurfaces(TRUE);

  // try to
  try
  {
    // load mapping
    LoadMapping_t( CTString(fnScriptName.NoExt()+".map"));
  }
  // if not successful
  catch (char *strError)
  {
    // ignore error message
    (void)strError;
  }
  
  // import mapping
  if( (fnImportMapping != "") ||
      ((fnClosed != "") && (fnOpened != "") && (fnUnwrapped != "")) )
  {
    CObject3D o3dClosed, o3dOpened, o3dUnwrapped;
	  
    o3dClosed.Clear();
    o3dOpened.Clear();
    o3dUnwrapped.Clear();

    // if mapping is defined using three files
    if( (fnClosed != "") && (fnOpened != "") && (fnUnwrapped != "") )
    {
      o3dClosed.LoadAny3DFormat_t( fnOpened, mStretch);
      o3dOpened.LoadAny3DFormat_t( fnClosed, mStretch);
      o3dUnwrapped.LoadAny3DFormat_t( fnUnwrapped, mStretch);
    }
    // if mapping is defined using one file
    else
    {
      o3dClosed.LoadAny3DFormat_t( fnImportMapping, mStretch, CObject3D::LT_NORMAL);
      o3dOpened.LoadAny3DFormat_t( fnImportMapping, mStretch, CObject3D::LT_OPENED);
      o3dUnwrapped.LoadAny3DFormat_t( fnImportMapping, mStretch, CObject3D::LT_UNWRAPPED);

      // multiply coordinates with size of texture
      o3dUnwrapped.ob_aoscSectors.Lock();
      o3dUnwrapped.ob_aoscSectors[0].osc_aovxVertices.Lock();
      INDEX ctVertices = o3dUnwrapped.ob_aoscSectors[0].osc_aovxVertices.Count();
      for(INDEX ivtx=0; ivtx<ctVertices; ivtx++)
      {
        o3dUnwrapped.ob_aoscSectors[0].osc_aovxVertices[ivtx](1) *= edm_md.md_Width/1024.0f;
        o3dUnwrapped.ob_aoscSectors[0].osc_aovxVertices[ivtx](2) *= edm_md.md_Height/1024.0f;
      }
      o3dUnwrapped.ob_aoscSectors[0].osc_aovxVertices.Unlock();
      o3dUnwrapped.ob_aoscSectors.Unlock();
    }

    o3dClosed.ob_aoscSectors.Lock();
    o3dOpened.ob_aoscSectors.Lock();
    o3dUnwrapped.ob_aoscSectors.Lock();

    INDEX ctModelVertices = edm_md.md_VerticesCt;
    INDEX ctModelPolygons = edm_md.md_MipInfos[0].mmpi_PolygonsCt;

    o3dClosed.ob_aoscSectors[0].osc_aovxVertices.Lock();
    INDEX ctClosedVertices = o3dClosed.ob_aoscSectors[0].osc_aovxVertices.Count();
    INDEX ctClosedPolygons = o3dClosed.ob_aoscSectors[0].osc_aopoPolygons.Count();
    o3dClosed.ob_aoscSectors[0].osc_aovxVertices.Unlock();

    o3dOpened.ob_aoscSectors[0].osc_aovxVertices.Lock();
    INDEX ctOpenedVertices = o3dOpened.ob_aoscSectors[0].osc_aovxVertices.Count();
    INDEX ctOpenedPolygons = o3dOpened.ob_aoscSectors[0].osc_aopoPolygons.Count();
    o3dOpened.ob_aoscSectors[0].osc_aovxVertices.Unlock();

    o3dUnwrapped.ob_aoscSectors[0].osc_aovxVertices.Lock();
    INDEX ctUnwrappedVertices = o3dUnwrapped.ob_aoscSectors[0].osc_aovxVertices.Count();
    INDEX ctUnwrappedPolygons = o3dUnwrapped.ob_aoscSectors[0].osc_aopoPolygons.Count();
    o3dUnwrapped.ob_aoscSectors[0].osc_aovxVertices.Unlock();

    if((ctModelPolygons != ctClosedPolygons) ||
      (ctModelPolygons != ctOpenedPolygons) ||
      (ctModelPolygons != ctUnwrappedPolygons) )
    {
      ThrowF_t("ERROR: Object used to create model and some of objects used to define mapping don't have same number of polygons!");
    }

    if( ctModelVertices != ctClosedVertices)
    {
      ThrowF_t("ERROR: Object used to create model and object that defines closed mapping don't have same number of vertices!");
		}
      
    if( ctUnwrappedVertices != ctOpenedVertices)
    {
      ThrowF_t("ERROR: Objects that define opened and unwrapped mapping don't have same number of vertices!");
		}

    o3dClosed.ob_aoscSectors.Unlock();
    o3dOpened.ob_aoscSectors.Unlock();
    o3dUnwrapped.ob_aoscSectors.Unlock();

    CalculateUnwrappedMapping( o3dClosed, o3dOpened, o3dUnwrapped);
    CalculateMappingForMips();
  }
  else
  {
    ThrowF_t("ERROR: Mapping not defined!");    
  }

	O3D.ob_aoscSectors.Unlock();
	File.Close();

  if( edm_aasAttachedSounds.Count() == 0)
    CreateEmptyAttachingSounds();

  CObject3D::BatchLoading_t(FALSE);
  } catch (char*) {
  CObject3D::BatchLoading_t(FALSE);
  throw;
  }
}

//----------------------------------------------------------------------------------------------
/*
 * Routine takes Object 3D class as input and creates new model (model data)
 * with its polygons, vertices, surfaces
 */
void CEditModel::NewModel(CObject3D *pO3D)
{
  pO3D->ob_aoscSectors.Lock();
  pO3D->ob_aoscSectors[0].LockAll();
  edm_md.md_VerticesCt = pO3D->ob_aoscSectors[0].osc_aovxVertices.Count();	// see how many vertices we will have
	edm_md.md_TransformedVertices.New( edm_md.md_VerticesCt); // create buffer for rotated vertices
  edm_md.md_MainMipVertices.New( edm_md.md_VerticesCt); // create buffer for main mip vertices
	edm_md.md_VertexMipMask.New( edm_md.md_VerticesCt);	// create buffer for vertex masks

  for( INDEX i=0; i<edm_md.md_VerticesCt; i++)
	{
    // copy vertex coordinates into md_MainMipVertices array so we colud make
		// mip-models later (we will search original coordinates in this array)
		edm_md.md_MainMipVertices[ i] =
      DOUBLEtoFLOAT(pO3D->ob_aoscSectors[0].osc_aovxVertices[ i]);
    edm_md.md_VertexMipMask[ i] = 0L; // mark to all vertices that they don't exist in any mip-model
	}

  AddMipModel( pO3D);								 // we add main model, first mip-model
	pO3D->ob_aoscSectors[0].UnlockAll();
  pO3D->ob_aoscSectors.Unlock();
}

//----------------------------------------------------------------------------------------------
/*
 * Routine takes 3D object as input and adds one mip model
 * The main idea is: for every vertice get distances to all vertices in md_MainMipVertices
 * array. If minimum distance is found, set that this vertice exists. Loop for all vertices.
 * Throw error if minimum distance isn't found. Set also new mip-model polygons info.
 */
void CEditModel::AddMipModel(	CObject3D *pO3D)
{
	INDEX i, j;
	BOOL same_found;

  // this is mask for vertices in current mip level
  ULONG mip_vtx_mask = (1L) << edm_md.md_MipCt;

	struct ModelMipInfo *pmmpi = &edm_md.md_MipInfos[ edm_md.md_MipCt]; // point to mip model that we will create

  // for each vertex
  for( INDEX iVertex=0; iVertex<edm_md.md_VerticesCt; iVertex++)
  {
    // mark that it is not in this mip model
    edm_md.md_VertexMipMask[ iVertex] &= ~mip_vtx_mask;
  }

	INDEX o3dvct = pO3D->ob_aoscSectors[0].osc_aovxVertices.Count();
	/*
	 * For each vertex in 3D object we calculate distances to all vertices in main mip-model.
	 * If distance (size of vector that is result of substraction of two vertice vectors) is
	 * less than some minimal float number, we assume that these vertices are the same.
	 * Processed vertex of 3D object gets its main-mip-model-vertex-friend's index as tag and
	 * mask value showing that it exists in this mip-model.
	 */
	for( i=0; i<o3dvct; i++)
	{
		same_found = FALSE;
		for( j=0; j<edm_md.md_VerticesCt; j++)
		{
			FLOAT3D vVertex = DOUBLEtoFLOAT(pO3D->ob_aoscSectors[0].osc_aovxVertices[ i]);
      FLOAT fAbsoluteDistance = Abs( (vVertex - edm_md.md_MainMipVertices[ j]).Length() );
			if( fAbsoluteDistance < MAX_ALLOWED_DISTANCE)
			{
				edm_md.md_VertexMipMask[ j] |= mip_vtx_mask;// we mark that this vertice exists in this mip model
				pO3D->ob_aoscSectors[0].osc_aovxVertices[ i].ovx_Tag = j;// remapping verice index must be remembered
				same_found = TRUE;								// mark that this vertex's remap is found
				break;
			}
		}
		if( same_found == FALSE)	// if no vertice close enough is found, we have error
		{
			ThrowF_t("Vertex from mip model %d with number %d, coordinates (%f,%f,%f), can't be found in main mip model.\n"
			  "There can't be new vertices in rougher mip-models,"
			  "but only vertices from main mip model can be removed and polygons reorganized.\n",
				edm_md.md_MipCt, i,
        pO3D->ob_aoscSectors[0].osc_aovxVertices[ i](1), pO3D->ob_aoscSectors[0].osc_aovxVertices[ i](2), pO3D->ob_aoscSectors[0].osc_aovxVertices[ i](3));
		}
	}

	/*
	 * We will create three arays for this mip polygon info:
	 *	1) array for polygons
	 *	2) array for mapping surfaces
	 *	3) array for polygon vertices
	 *	4) array for texture vertices
	 */

	/*
	 * First we create array large enough to accept object 3D's polygons.
	 */
	pmmpi->mmpi_PolygonsCt = pO3D->ob_aoscSectors[0].osc_aopoPolygons.Count();
	pmmpi->mmpi_Polygons.New( pmmpi->mmpi_PolygonsCt);

	/*
	 * Then we will create array for mapping surfaces and set their names
	 */
  pmmpi->mmpi_MappingSurfaces.New( pO3D->ob_aoscSectors[0].osc_aomtMaterials.Count());	// create array for mapping surfaces
	for( i=0; i<pO3D->ob_aoscSectors[0].osc_aomtMaterials.Count(); i++)
	{
    MappingSurface &ms = pmmpi->mmpi_MappingSurfaces[ i];
    ms.ms_ulOnColor = PC_ALLWAYS_ON;							// set default ON and OFF masking colors
		ms.ms_ulOffColor = PC_ALLWAYS_OFF;
		ms.ms_Name = CTFileName( pO3D->ob_aoscSectors[0].osc_aomtMaterials[ i].omt_Name);
    ms.ms_vSurface2DOffset = FLOAT3D( 1.0f, 1.0f, 1.0f);
    ms.ms_HPB = FLOAT3D( 0.0f, 0.0f, 0.0f);
    ms.ms_Zoom = 1.0f;

    ms.ms_colColor =
      pO3D->ob_aoscSectors[0].osc_aomtMaterials[ i].omt_Color | CT_OPAQUE; // copy surface color, set no alpha
    ms.ms_sstShadingType = SST_MATTE;
    ms.ms_sttTranslucencyType = STT_OPAQUE;
    ms.ms_ulRenderingFlags = SRF_DIFFUSE|SRF_NEW_TEXTURE_FORMAT;
	}

	/*
	 * Then we will count how many ModelPolygonVertices we need and create array for them.
	 * This number is equal to sum of all vertices used by all object 3D's polygons
	 */
	INDEX pvct = 0;
	for( i=0; i<pmmpi->mmpi_PolygonsCt; i++)
	{
		pvct += pO3D->ob_aoscSectors[0].osc_aopoPolygons[ i].opo_PolygonEdges.Count();	// we have vertices as many as edges
	}
	/*
	 * Now we will create an static array of temporary structures used for extracting
	 * vertice-surface connection. We need this because we have to set for all model vertices:
	 *	1) their texture vertices
	 *	2) their transformed vertices.
	 * First we will set surface and transformed indexes to every polygon vertice
	 */
	INDEX esvct = 0;			// for counting polygon vertices
	CStaticArray< CExtractSurfaceVertex> aesv;
	aesv.New( pvct);			// array with same number of members as polygon vertex array

  {FOREACHINDYNAMICARRAY( pO3D->ob_aoscSectors[0].osc_aopoPolygons, CObjectPolygon, it1)
	{
		INDEX iPolySurface = pO3D->ob_aoscSectors[0].osc_aomtMaterials.Index( it1->opo_Material);	// this polygon's surface index
	  FOREACHINDYNAMICARRAY( it1->opo_PolygonEdges, CObjectPolygonEdge, it2)
		{
			aesv[ esvct].esv_Surface = iPolySurface; // all these vertices are members of same polygon so they have same surface index
			aesv[ esvct].esv_MipGlobalIndex = pO3D->ob_aoscSectors[0].osc_aovxVertices.Index( it2->ope_Edge->oed_Vertex0); // global index
			esvct++;
		}
	}}


	/*
	 * Then we will choose one verice from this array and see if there is any vertice
	 * processed until now that have same surface and global index. If souch
	 * vertice exists, copy its remap value, if it doesn't exists, set its remap value
	 * to value of current texture vertex counter. After counting souch surface-dependent
	 * vertices (texture vertices, tvct) we will create array for them
	 */
	BOOL same_vtx_found;
	INDEX tvct = 0;
	for( i=0; i<pvct; i++)
	{
		same_vtx_found = FALSE;
		for( INDEX j=0; j<i; j++)
		{
			if( (aesv[ j].esv_Surface == aesv[ i].esv_Surface) &&				// if surface and global
				  (aesv[ j].esv_MipGlobalIndex == aesv[ i].esv_MipGlobalIndex)) // vertex index are the same
			{
					same_vtx_found = TRUE;									// if yes, copy remap value
					aesv[ i].esv_TextureVertexRemap = aesv[ j].esv_TextureVertexRemap;
					break;
			}
		}
		if( same_vtx_found == FALSE)									// if not, set value to current counter
		{
			aesv[ i].esv_TextureVertexRemap = tvct;
			tvct ++;
		}
	}
	pmmpi->mmpi_TextureVertices.New( tvct);						// create array for texture vertices

	/*
	 * Now we will set texture vertex data for all surface unique vertices. We will do it by
	 * looping this to all polygon vertices: copy coordinates of vertex from global vertex array
	 * to UVW coordinates of texture vertex. That way we will have little overhead (some
	 * vertices will be copied many times) but it doesn't really matter.
	 */
  for( i=0; i<pvct; i++)
	{
    pmmpi->mmpi_TextureVertices[ aesv[ i].esv_TextureVertexRemap].mtv_UVW =
		  DOUBLEtoFLOAT(pO3D->ob_aoscSectors[0].osc_aovxVertices[ aesv[ i].esv_MipGlobalIndex]);
	}


  /*
	 * Now we intend to create data for all polygons (that includes setting polygon's
	 * texture and transformed vertex ptrs)
	 */
	INDEX mpvct = 0;																// start polygon vertex counter
	for( i=0; i<pmmpi->mmpi_PolygonsCt; i++)				// loop all model polygons
	{
    struct ModelPolygon *pmp = &pmmpi->mmpi_Polygons[ i];		// ptr to activ model polygon
		pmp->mp_Surface = pO3D->ob_aoscSectors[0].osc_aomtMaterials.Index( pO3D->ob_aoscSectors[0].osc_aopoPolygons[ i].opo_Material); // copy surface index
		pmp->mp_ColorAndAlpha =
      pO3D->ob_aoscSectors[0].osc_aopoPolygons[ i].opo_Material->omt_Color | CT_OPAQUE; // copy surface color, set no alpha
		INDEX ctVertices = pO3D->ob_aoscSectors[0].osc_aopoPolygons[ i].opo_PolygonEdges.Count(); // set no of polygon's vertices
		pmp->mp_PolygonVertices.New( ctVertices); // create array for them
		for( j=0; j<ctVertices; j++)				// fill data for this polygon's vertices
		{
			/*
			 * Here we really remap one mip models's vertex in a way that we set its transformed
			 * vertex ptr after remapping it using link (tag) to its original mip-model's vertex
			 */
			ULONG trans_vtx_idx = pO3D->ob_aoscSectors[0].osc_aovxVertices[ aesv[ mpvct].esv_MipGlobalIndex].ovx_Tag;

			pmp->mp_PolygonVertices[ j].mpv_ptvTransformedVertex =
				&edm_md.md_TransformedVertices[ (INDEX) trans_vtx_idx ]; // remapped ptr to transformed vertex
			pmp->mp_PolygonVertices[ j].mpv_ptvTextureVertex =
				&pmmpi->mmpi_TextureVertices[ aesv[ mpvct].esv_TextureVertexRemap];	// ptr to unique vertex in surface
			mpvct ++;
		}
	}

	edm_md.md_MipCt ++;	// finally, this mip-model is done.
}

//----------------------------------------------------------------------------------------------
/*
 * Routine sets unwrapped mapping from given three objects
 */
void CEditModel::CalculateUnwrappedMapping( CObject3D &o3dClosed, CObject3D &o3dOpened, CObject3D &o3dUnwrapped)
{
  o3dOpened.ob_aoscSectors.Lock();
  o3dClosed.ob_aoscSectors.Lock();
  o3dUnwrapped.ob_aoscSectors.Lock();
  // get first mip model
  struct ModelMipInfo *pMMI = &edm_md.md_MipInfos[ 0];
  // for each surface in first mip model
  for( INDEX iSurface = 0; iSurface < pMMI->mmpi_MappingSurfaces.Count(); iSurface++)
  {
    MappingSurface *pmsSurface = &pMMI->mmpi_MappingSurfaces[iSurface];
    // for each texture vertex in surface
    for(INDEX iSurfaceTextureVertex=0; iSurfaceTextureVertex<pmsSurface->ms_aiTextureVertices.Count(); iSurfaceTextureVertex++)
    {
      INDEX iGlobalTextureVertex = pmsSurface->ms_aiTextureVertices[iSurfaceTextureVertex];
      ModelTextureVertex *pmtvTextureVertex = &pMMI->mmpi_TextureVertices[iGlobalTextureVertex];
      // obtain index of model vertex
      INDEX iModelVertex = pmtvTextureVertex->mtv_iTransformedVertex;
      // for each polygon in opened with same surface
      for(INDEX iOpenedPolygon=0; iOpenedPolygon< o3dOpened.ob_aoscSectors[0].osc_aopoPolygons.Count(); iOpenedPolygon++)
      {
        DOUBLE3D vClosedVertex;
        DOUBLE3D vOpenedVertex;

        // get coordinate from model vertex in closed
        o3dClosed.ob_aoscSectors[0].osc_aovxVertices.Lock();
        vClosedVertex = o3dClosed.ob_aoscSectors[0].osc_aovxVertices[ iModelVertex];
        o3dClosed.ob_aoscSectors[0].osc_aovxVertices.Unlock();

        // find vertex in opened with same coordinate
        o3dOpened.ob_aoscSectors[0].osc_aopoPolygons.Lock();
        CObjectPolygon *popoOpenedPolygon = &o3dOpened.ob_aoscSectors[0].osc_aopoPolygons[ iOpenedPolygon];
        o3dOpened.ob_aoscSectors[0].osc_aopoPolygons.Unlock();
        if( popoOpenedPolygon->opo_Material->omt_Name != pmsSurface->ms_Name) continue;
        for( INDEX iOpenedPolyEdge=0; iOpenedPolyEdge<popoOpenedPolygon->opo_PolygonEdges.Count(); iOpenedPolyEdge++)
        {
          popoOpenedPolygon->opo_PolygonEdges.Lock();
          CObjectVertex *povOpenedVertex;
          if( !popoOpenedPolygon->opo_PolygonEdges[iOpenedPolyEdge].ope_Backward)
          {
            povOpenedVertex = popoOpenedPolygon->opo_PolygonEdges[iOpenedPolyEdge].ope_Edge->oed_Vertex0;
          }
          else
          {
            povOpenedVertex = popoOpenedPolygon->opo_PolygonEdges[iOpenedPolyEdge].ope_Edge->oed_Vertex1;
          }
          popoOpenedPolygon->opo_PolygonEdges.Unlock();
          
          vOpenedVertex = *povOpenedVertex;

          // if these two vertices have same coordinates
          FLOAT fAbsoluteDistance = Abs( (vClosedVertex - vOpenedVertex).Length());
			    if( fAbsoluteDistance < MAX_ALLOWED_DISTANCE)
          {
            o3dClosed.ob_aoscSectors[0].osc_aovxVertices.Lock();
            o3dUnwrapped.ob_aoscSectors[0].osc_aovxVertices.Lock();
            // find index in opened
            o3dOpened.ob_aoscSectors[0].osc_aovxVertices.Lock();
            INDEX iOpenedModelVertex = o3dOpened.ob_aoscSectors[0].osc_aovxVertices.Index( povOpenedVertex);
            o3dOpened.ob_aoscSectors[0].osc_aovxVertices.Unlock();
            // get coordinate from unwrapped using index
            DOUBLE3D vMappingCoordinate = o3dUnwrapped.ob_aoscSectors[0].osc_aovxVertices[ iOpenedModelVertex];
            // set new mapping coordinates
            pmtvTextureVertex->mtv_UVW = DOUBLEtoFLOAT( vMappingCoordinate);
            pmtvTextureVertex->mtv_UVW(2) = -pmtvTextureVertex->mtv_UVW(2);
            MEX2D mexUV;
            mexUV(1) = MEX_METERS(pmtvTextureVertex->mtv_UVW(1));
            mexUV(2) = MEX_METERS(pmtvTextureVertex->mtv_UVW(2));
            pmtvTextureVertex->mtv_UV = mexUV;
            o3dClosed.ob_aoscSectors[0].osc_aovxVertices.Unlock();
            o3dUnwrapped.ob_aoscSectors[0].osc_aovxVertices.Unlock();
          }
        }
      }
      // reset surface position, rotation and zoom
      pmsSurface->ms_HPB = FLOAT3D( 0.0f, 0.0f, 0.0f);
      pmsSurface->ms_Zoom = 1.0f;
      pmsSurface->ms_vSurface2DOffset = FLOAT3D( 0.0f, 0.0f, 0.0f);
    }
  }
  o3dOpened.ob_aoscSectors.Unlock();
  o3dClosed.ob_aoscSectors.Unlock();
  o3dUnwrapped.ob_aoscSectors.Unlock();
}
//----------------------------------------------------------------------------------------------
/*
 * Routine calculate mapping for mip models (except for main mip)
 */
void CEditModel::CalculateMappingForMips( void)
{
  // for each mip model except first
  for( INDEX iCurMip = 1; iCurMip< edm_md.md_MipCt; iCurMip++)
  {
    // get current mip model
    struct ModelMipInfo *pMMICur = &edm_md.md_MipInfos[ iCurMip];
    // get previous mip model
    struct ModelMipInfo *pMMIPrev = &edm_md.md_MipInfos[ iCurMip-1];
    // for each surface in current mip model
    for( INDEX iSurfaceCur = 0; iSurfaceCur < pMMICur->mmpi_MappingSurfaces.Count(); iSurfaceCur++)
    {
      MappingSurface *pmsSurfCur = &pMMICur->mmpi_MappingSurfaces[iSurfaceCur];
      // for each texture vertex in surface
      for(INDEX iSurfCurTV=0; iSurfCurTV<pmsSurfCur->ms_aiTextureVertices.Count(); iSurfCurTV++)
      {
        INDEX iCurGlobalTV = pmsSurfCur->ms_aiTextureVertices[iSurfCurTV];
        ModelTextureVertex *pmtvCur = &pMMICur->mmpi_TextureVertices[iCurGlobalTV];
        // obtain index of model vertex
        INDEX iCurMV = pmtvCur->mtv_iTransformedVertex;
        
        // get 3D coordinate of vertex from main mip
        FLOAT3D vMainMipCoordCur = edm_md.md_MainMipVertices[ iCurMV];

        // -------- Find closest vertex (using 3D coordinate) in previous mip
 
        // in previous mip model find surface with same name
        MappingSurface *pmsSurfPrev = NULL;
        for( INDEX iSurfacePrev = 0; iSurfacePrev < pMMIPrev->mmpi_MappingSurfaces.Count(); iSurfacePrev++)
        {
          pmsSurfPrev = &pMMIPrev->mmpi_MappingSurfaces[iSurfacePrev];
          if( pmsSurfCur->ms_Name == pmsSurfPrev->ms_Name)
          {
            break;
          }
        }
          
        // new surfaces can't appear 
        ASSERT(pmsSurfPrev != NULL);
        if( pmsSurfPrev == NULL)
        {
          WarningMessage( "Mip model %d has surface that does not exist in previous mip. That is not allowed.", iCurMip);
          break;
        }

        // set hudge distance as current minimum
        FLOAT fMinDistance = 99999999.0f;
        ModelTextureVertex *pmtvClosestPrev = NULL;

        // for each texture vertex in previous mip's surface with same name
        for(INDEX iSurfPrevTV=0; iSurfPrevTV<pmsSurfPrev->ms_aiTextureVertices.Count(); iSurfPrevTV++)
        {
          INDEX iPrevGlobalTV = pmsSurfPrev->ms_aiTextureVertices[iSurfPrevTV];
          ModelTextureVertex *pmtvPrev = &pMMIPrev->mmpi_TextureVertices[iPrevGlobalTV];
          // obtain index of model vertex
          INDEX iPrevMV = pmtvPrev->mtv_iTransformedVertex;
          // get 3D coordinate of vertex from main mip
          FLOAT3D vMainMipCoordPrev = edm_md.md_MainMipVertices[ iPrevMV];
          // get distance of these two vertices
          FLOAT fAbsoluteDistance = Abs( (vMainMipCoordPrev - vMainMipCoordCur).Length());
			    if( fAbsoluteDistance < fMinDistance)
          {
            // remember current texture vertex as closest one
            fMinDistance = fAbsoluteDistance;
            pmtvClosestPrev = pmtvPrev;
          }
        }
        ASSERT( pmtvClosestPrev != NULL);
        // copy mapping coordinates from closest mapping vertex in previous mip
        pmtvCur->mtv_UVW = pmtvClosestPrev->mtv_UVW;
        pmtvCur->mtv_UV  = pmtvClosestPrev->mtv_UV;
        pmtvCur->mtv_vU  = pmtvClosestPrev->mtv_vU;
        pmtvCur->mtv_vV  = pmtvClosestPrev->mtv_vV;
      }
    }
  }
}

/*
 * This routine opens last script file loaded, repeats reading key-words until it finds
 * key-word "ANIM_START". Then it calls animation data load from script routine.
 */
void CEditModel::UpdateAnimations_t(CTFileName &fnScriptName) // throw char *
{
	CTFileStream File;
	char ld_line[ 128];
	CListHead FrameNamesList;
  FLOATmatrix3D mStretch;
  mStretch.Diagonal(1.0f);

	File.Open_t( fnScriptName); // open script file for reading

  FOREVER
	{
		do
    {
      File.GetLine_t(ld_line, 128);
    }
		while( (strlen( ld_line)== 0) || (ld_line[0]==';'));

		if( EQUAL_SUB_STR( "SIZE"))
    {
  	  _strupr( ld_line);
      FLOAT fStretch = 1.0f;
		  sscanf( ld_line, "SIZE %g", &fStretch);
      mStretch *= fStretch;
    }
		else if( EQUAL_SUB_STR( "TRANSFORM")) 
    {
  	  _strupr( ld_line);
      FLOATmatrix3D mTran;
      mTran.Diagonal(1.0f);
		  sscanf( ld_line, "TRANSFORM %g %g %g %g %g %g %g %g %g", 
        &mTran(1,1), &mTran(1,2), &mTran(1,3),
        &mTran(2,1), &mTran(2,2), &mTran(2,3),
        &mTran(3,1), &mTran(3,2), &mTran(3,3));
      mStretch *= mTran;
    }
		else if( EQUAL_SUB_STR( "ANIM_START"))
		{
			LoadModelAnimationData_t( &File, mStretch);	// load and set model's animation data
			break;													// we found our animations, we loaded them so we will stop forever loop
		}
    else if( EQUAL_SUB_STR( "ORIGIN_TRI"))
    {
      sscanf( ld_line, "ORIGIN_TRI %d %d %d", &aiTransVtx[0], &aiTransVtx[1], &aiTransVtx[2]);	// read given vertices
    }
	}
  File.Close();

  CreateEmptyAttachingSounds();
}
//----------------------------------------------------------------------------------------------
void CEditModel::CreateMipModels_t(CObject3D &objRestFrame, CObject3D &objMipSourceFrame,
         INDEX iVertexRemoveRate, INDEX iSurfacePreservingFactor)
{
  // free possible mip-models except main mip model
  INDEX iMipModel;
  for( iMipModel=1; iMipModel<edm_md.md_MipCt; iMipModel++)
	{
		edm_md.md_MipInfos[ iMipModel].Clear();
	}
	edm_md.md_MipCt = 1;

  // create mip model structure
  CMipModel mmMipModel;
  mmMipModel.FromObject3D_t( objRestFrame, objMipSourceFrame);

  if( ProgresRoutines.SetProgressMessage != NULL)
    ProgresRoutines.SetProgressMessage( "Calculating mip models ...");
  INDEX ctVerticesInRestFrame = mmMipModel.mm_amvVertices.Count();
  if( ProgresRoutines.SetProgressRange != NULL)
    ProgresRoutines.SetProgressRange(ctVerticesInRestFrame);
  // create maximum 32 mip models
  for( iMipModel=0; iMipModel<31; iMipModel++)
  {
    // if unable to create mip models
    if( !mmMipModel.CreateMipModel_t( iVertexRemoveRate, iSurfacePreservingFactor))
    {
      // stop creating more mip models
      break;
    }
    if( ProgresRoutines.SetProgressState != NULL)
      ProgresRoutines.SetProgressState(ctVerticesInRestFrame - mmMipModel.mm_amvVertices.Count());
    CObject3D objMipModel;
    mmMipModel.ToObject3D( objMipModel);

    objMipModel.ob_aoscSectors.Lock();
    objMipModel.ob_aoscSectors[0].LockAll();
    AddMipModel( &objMipModel);
    objMipModel.ob_aoscSectors[0].UnlockAll();
    objMipModel.ob_aoscSectors.Unlock();
  }
  ProgresRoutines.SetProgressState(ctVerticesInRestFrame);
  edm_md.SpreadMipSwitchFactors( 0, 5.0f);
  edm_md.LinkDataForSurfaces(FALSE);
  CalculateMappingForMips();
}
//----------------------------------------------------------------------------------------------
/*
 * This routine discards all mip-models except main (mip-model 0). Then it opens script file
 * with file name of last script loaded. Then it repeats reading key-words until it counts two
 * key-words "MIPMODEL". For second and all other key-words routine calls add mip-map routine
 */
void CEditModel::UpdateMipModels_t(CTFileName &fnScriptName) // throw char *
{
  try {
  CObject3D::BatchLoading_t(TRUE);
	CTFileStream File;
	CObject3D O3D;
	char base_path[ PATH_MAX] = "";
	char file_name[ PATH_MAX];
	char full_path[ PATH_MAX];
	char ld_line[ 128];
  FLOATmatrix3D mStretch;
  mStretch.Diagonal(1.0f);

	O3D.ob_aoscSectors.Lock();

  ASSERT( edm_md.md_VerticesCt != 0);

	File.Open_t( fnScriptName); // open script file for reading

    INDEX i;
	for( i=1; i<edm_md.md_MipCt; i++)
	{
		edm_md.md_MipInfos[ i].Clear();	// free possible mip-models except main mip model
	}
	edm_md.md_MipCt = 1;

	FOREVER
	{
		do
    {
      File.GetLine_t(ld_line, 128);
    }
		while( (strlen( ld_line)== 0) || (ld_line[0]==';'));

		if( EQUAL_SUB_STR( "SIZE"))
    {
  	  _strupr( ld_line);
      FLOAT fStretch = 1.0f;
		  sscanf( ld_line, "SIZE %g", &fStretch);
      mStretch *= fStretch;
    }
		else if( EQUAL_SUB_STR( "TRANSFORM")) 
    {
  	  _strupr( ld_line);
      FLOATmatrix3D mTran;
      mTran.Diagonal(1.0f);
		  sscanf( ld_line, "TRANSFORM %g %g %g %g %g %g %g %g %g", 
        &mTran(1,1), &mTran(1,2), &mTran(1,3),
        &mTran(2,1), &mTran(2,2), &mTran(2,3),
        &mTran(3,1), &mTran(3,2), &mTran(3,3));
      mStretch *= mTran;
    }
		else if( EQUAL_SUB_STR( "DIRECTORY"))
		{
			_strupr( ld_line);
			sscanf( ld_line, "DIRECTORY %s", base_path);
			if( base_path[ strlen( base_path) - 1] != '\\')
				strcat( base_path,"\\");
		}
		else if( EQUAL_SUB_STR( "MIP_MODELS"))
		{
			_strupr( ld_line);
			INDEX no_of_mip_models;
      sscanf( ld_line, "MIP_MODELS %d", &no_of_mip_models);

      // Jump over first mip model file name
      File.GetLine_t(ld_line, 128);

			for( i=0; i<no_of_mip_models-1; i++)
			{
        File.GetLine_t(ld_line, 128);
  			_strupr( ld_line);
				sscanf( ld_line, "%s", file_name);
				sprintf( full_path, "%s%s", base_path, file_name);

			  O3D.Clear();                            // clear possible existing O3D's data
				O3D.LoadAny3DFormat_t( CTString(full_path), mStretch);

        if( edm_md.md_VerticesCt < O3D.ob_aoscSectors[0].osc_aovxVertices.Count())
        {
          ThrowF_t(
            "It is unlikely that mip-model \"%s\" is valid.\n"
            "It contains more vertices than main mip-model so it can't be mip-model.", 
            full_path);
        }

        if( edm_md.md_MipCt < MAX_MODELMIPS)
				{
	        O3D.ob_aoscSectors[0].LockAll();;
					AddMipModel( &O3D);	// else this is one of model's mip definitions so call Add Mip Model
	        O3D.ob_aoscSectors[0].UnlockAll();;
				}
				else
				{
          ThrowF_t("There are too many mip-models defined in script file. Maximum of %d mip-models allowed.", MAX_MODELMIPS-1);
				}
			}
		}
    else if( EQUAL_SUB_STR( "ORIGIN_TRI"))
    {
      sscanf( ld_line, "ORIGIN_TRI %d %d %d", &aiTransVtx[0], &aiTransVtx[1], &aiTransVtx[2]);	// read given vertices
    }
		else if( EQUAL_SUB_STR( "END"))
		{
			break;
		}
	}
	O3D.ob_aoscSectors.Unlock();
  edm_md.LinkDataForSurfaces(TRUE);

  CObject3D::BatchLoading_t(FALSE);
  } catch (char*) {
  CObject3D::BatchLoading_t(FALSE);
  throw;
  }
}

/*
 * Draws given surface in wire frame
 */
void CEditModel::DrawWireSurface( CDrawPort *pDP, INDEX iCurrentMip, INDEX iCurrentSurface,
                                  FLOAT fMagnifyFactor, PIX offx, PIX offy,
                                  COLOR clrVisible, COLOR clrInvisible)
{
  FLOAT3D f3dTr0, f3dTr1, f3dTr2;
  struct ModelTextureVertex *pVtx0, *pVtx1;

  // for each polygon
  for( INDEX iPoly=0; iPoly<edm_md.md_MipInfos[iCurrentMip].mmpi_PolygonsCt; iPoly++)
  {
    struct ModelPolygon *pPoly = &edm_md.md_MipInfos[iCurrentMip].mmpi_Polygons[iPoly];
    if( pPoly->mp_Surface == iCurrentSurface)
    { // readout poly vertices
      f3dTr0(1) = (FLOAT)pPoly->mp_PolygonVertices[0].mpv_ptvTextureVertex->mtv_UV(1);
      f3dTr0(2) = (FLOAT)pPoly->mp_PolygonVertices[0].mpv_ptvTextureVertex->mtv_UV(2);
      f3dTr0(3) = 0.0f;
      f3dTr1(1) = (FLOAT)pPoly->mp_PolygonVertices[1].mpv_ptvTextureVertex->mtv_UV(1);
      f3dTr1(2) = (FLOAT)pPoly->mp_PolygonVertices[1].mpv_ptvTextureVertex->mtv_UV(2);
      f3dTr1(3) = 0.0f;
      f3dTr2(1) = (FLOAT)pPoly->mp_PolygonVertices[2].mpv_ptvTextureVertex->mtv_UV(1);
      f3dTr2(2) = (FLOAT)pPoly->mp_PolygonVertices[2].mpv_ptvTextureVertex->mtv_UV(2);
      f3dTr2(3) = 0.0f;

      // determine line visibility
      FLOAT3D f3dNormal = (f3dTr2-f3dTr1)*(f3dTr0-f3dTr1);
      COLOR clrWire;
      ULONG ulLineType;
      if( f3dNormal(3) < 0) {
        clrWire = clrVisible;
        ulLineType = _FULL_;
      } else {
        clrWire = clrInvisible;
        ulLineType = _POINT_;
      }
      // draw lines
      PIX pixX0, pixY0, pixX1, pixY1;
      for( INDEX iVtx=0; iVtx<pPoly->mp_PolygonVertices.Count()-1; iVtx++) {
        pVtx0 = pPoly->mp_PolygonVertices[iVtx+0].mpv_ptvTextureVertex;
        pVtx1 = pPoly->mp_PolygonVertices[iVtx+1].mpv_ptvTextureVertex;
        pixX0 = (PIX)(pVtx0->mtv_UV(1) * fMagnifyFactor) - offx;
        pixY0 = (PIX)(pVtx0->mtv_UV(2) * fMagnifyFactor) - offy;
        pixX1 = (PIX)(pVtx1->mtv_UV(1) * fMagnifyFactor) - offx;
        pixY1 = (PIX)(pVtx1->mtv_UV(2) * fMagnifyFactor) - offy;
        pDP->DrawLine( pixX0, pixY0, pixX1, pixY1, clrWire|CT_OPAQUE, ulLineType);
      }
      // draw last line
      pVtx0 = pPoly->mp_PolygonVertices[0].mpv_ptvTextureVertex;
      pixX0 = (PIX)(pVtx0->mtv_UV(1) * fMagnifyFactor) - offx;
      pixY0 = (PIX)(pVtx0->mtv_UV(2) * fMagnifyFactor) - offy;
      pDP->DrawLine( pixX0, pixY0, pixX1, pixY1, clrWire|CT_OPAQUE, ulLineType);
    }
  }
}


/*
 * Flat fills given surface
 */
void CEditModel::DrawFilledSurface( CDrawPort *pDP, INDEX iCurrentMip, INDEX iCurrentSurface,
                                    FLOAT fMagnifyFactor, PIX offx, PIX offy,
                                    COLOR clrVisible, COLOR clrInvisible)
{
  FLOAT3D f3dTr0, f3dTr1, f3dTr2;
  struct ModelTextureVertex *pVtx0, *pVtx1, *pVtx2;

  // for each polygon
  for( INDEX iPoly=0; iPoly<edm_md.md_MipInfos[iCurrentMip].mmpi_PolygonsCt; iPoly++)
  {
    struct ModelPolygon *pPoly = &edm_md.md_MipInfos[iCurrentMip].mmpi_Polygons[iPoly];
    if( pPoly->mp_Surface == iCurrentSurface)
    { // readout poly vertices
      f3dTr0(1) = (FLOAT)pPoly->mp_PolygonVertices[0].mpv_ptvTextureVertex->mtv_UV(1);
      f3dTr0(2) = (FLOAT)pPoly->mp_PolygonVertices[0].mpv_ptvTextureVertex->mtv_UV(2);
      f3dTr0(3) = 0.0f;
      f3dTr1(1) = (FLOAT)pPoly->mp_PolygonVertices[1].mpv_ptvTextureVertex->mtv_UV(1);
      f3dTr1(2) = (FLOAT)pPoly->mp_PolygonVertices[1].mpv_ptvTextureVertex->mtv_UV(2);
      f3dTr1(3) = 0.0f;
      f3dTr2(1) = (FLOAT)pPoly->mp_PolygonVertices[2].mpv_ptvTextureVertex->mtv_UV(1);
      f3dTr2(2) = (FLOAT)pPoly->mp_PolygonVertices[2].mpv_ptvTextureVertex->mtv_UV(2);
      f3dTr2(3) = 0.0f;

      // determine poly visibility
      COLOR clrFill;
      FLOAT3D f3dNormal = (f3dTr2-f3dTr1)*(f3dTr0-f3dTr1);
      if( f3dNormal(3) < 0) clrFill = clrVisible|0xFF;
      else clrFill = clrInvisible|0xFF;

      // draw traingle(s) fan
      pDP->InitTexture( NULL);
      pVtx0 = pPoly->mp_PolygonVertices[0].mpv_ptvTextureVertex;
      PIX pixX0 = (PIX)(pVtx0->mtv_UV(1) * fMagnifyFactor) - offx;
      PIX pixY0 = (PIX)(pVtx0->mtv_UV(2) * fMagnifyFactor) - offy;
      for( INDEX iVtx=1; iVtx<pPoly->mp_PolygonVertices.Count()-1; iVtx++) {
        pVtx1 = pPoly->mp_PolygonVertices[iVtx+0].mpv_ptvTextureVertex;
        pVtx2 = pPoly->mp_PolygonVertices[iVtx+1].mpv_ptvTextureVertex;
        PIX pixX1 = (PIX)(pVtx1->mtv_UV(1) * fMagnifyFactor) - offx;
        PIX pixY1 = (PIX)(pVtx1->mtv_UV(2) * fMagnifyFactor) - offy;
        PIX pixX2 = (PIX)(pVtx2->mtv_UV(1) * fMagnifyFactor) - offx;
        PIX pixY2 = (PIX)(pVtx2->mtv_UV(2) * fMagnifyFactor) - offy;
        pDP->AddTriangle( pixX0,pixY0, pixX1,pixY1, pixX2,pixY2, clrFill);
      }
      // to buffer with it
      pDP->FlushRenderingQueue();
    }
  }
}


/*
 * Prints surface numbers
 */
void CEditModel::PrintSurfaceNumbers( CDrawPort *pDP, CFontData *pFont,
     INDEX iCurrentMip, FLOAT fMagnifyFactor, PIX offx, PIX offy, COLOR clrInk)
{
  char achrLine[ 256];

 	// clear Z-buffer
  pDP->FillZBuffer( ZBUF_BACK);

  // get mip model ptr
  struct ModelMipInfo *pMMI = &edm_md.md_MipInfos[ iCurrentMip];


  // for all surfaces
  for( INDEX iSurf=0;iSurf<pMMI->mmpi_MappingSurfaces.Count(); iSurf++)
  {
    MappingSurface *pms= &pMMI->mmpi_MappingSurfaces[iSurf];
    MEXaabbox2D boxSurface;
    // for each texture vertex in surface
    for(INDEX iSurfaceTextureVertex=0; iSurfaceTextureVertex<pms->ms_aiTextureVertices.Count(); iSurfaceTextureVertex++)
    {
      INDEX iGlobalTextureVertex = pms->ms_aiTextureVertices[iSurfaceTextureVertex];
      ModelTextureVertex *pmtv = &pMMI->mmpi_TextureVertices[iGlobalTextureVertex];
      boxSurface |= pmtv->mtv_UV;
    }
   
    MEX2D mexCenter = boxSurface.Center();
    PIX2D pixCenter = PIX2D(mexCenter(1)*fMagnifyFactor-offx, mexCenter(2)*fMagnifyFactor-offy);

    // print active surface's number into print line
    sprintf( achrLine, "%d", iSurf);

    // set font
    pDP->SetFont( pFont);
    // print line
    pDP->PutText( achrLine, pixCenter(1)-strlen(achrLine)*4, pixCenter(2)-6);
  }
}

/*
 * Exports surface names and numbers under given file name
 */
void CEditModel::ExportSurfaceNumbersAndNames( CTFileName fnFile)
{
  CTString strExport;
  // get mip model ptr
  struct ModelMipInfo *pMMI = &edm_md.md_MipInfos[ 0];

  // for all surfaces
  for( INDEX iSurf=0; iSurf<pMMI->mmpi_MappingSurfaces.Count(); iSurf++)
  {
    MappingSurface *pms= &pMMI->mmpi_MappingSurfaces[iSurf];
    CTString strExportLine;
    strExportLine.PrintF( "%d) %s\n", iSurf, (const char *) pms->ms_Name);
    strExport+=strExportLine;
  }

  try
  {
    strExport.Save_t( fnFile);
  }
  catch(char *strError)
  {
    // report error
    WarningMessage( strError);
  }
}

/*
 * Retrieves given surface's name
 */
const char *CEditModel::GetSurfaceName(INDEX iCurrentMip, INDEX iCurrentSurface)
{
  struct MappingSurface *pSurface;
  pSurface = &edm_md.md_MipInfos[ iCurrentMip].mmpi_MappingSurfaces[ iCurrentSurface];
  return( pSurface->ms_Name);
}
//--------------------------------------------------------------------------------------------
/*
 * Sets first empty position in existing patches mask
 */
BOOL CEditModel::GetFirstEmptyPatchIndex( INDEX &iMaskBit)
{
  iMaskBit = 0;
  for( INDEX iPatch=0; iPatch<MAX_TEXTUREPATCHES; iPatch++)
  {
    CTextureData *pTD = (CTextureData *) edm_md.md_mpPatches[ iPatch].mp_toTexture.GetData();
    if( pTD == NULL)
    {
      iMaskBit = iPatch;
      return TRUE;
    }
  }
  return FALSE;
}
//--------------------------------------------------------------------------------------------
/*
 * Sets first occupied position in existing patches mask
 */
BOOL CEditModel::GetFirstValidPatchIndex( INDEX &iMaskBit)
{
  iMaskBit = 0;
  for( INDEX iPatch=0; iPatch<MAX_TEXTUREPATCHES; iPatch++)
  {
    CTextureData *pTD = (CTextureData *) edm_md.md_mpPatches[ iPatch].mp_toTexture.GetData();
    if( pTD != NULL)
    {
      iMaskBit = iPatch;
      return TRUE;
    }
  }
  return FALSE;
}
//--------------------------------------------------------------------------------------------
/*
 * Sets previous valid patch position in existing patches mask
 */
void CEditModel::GetPreviousValidPatchIndex( INDEX &iMaskBit)
{
  ASSERT( (iMaskBit>=0) && (iMaskBit<MAX_TEXTUREPATCHES) );
  for( INDEX iPatch=iMaskBit+MAX_TEXTUREPATCHES-1; iPatch>iMaskBit; iPatch--)
  {
    INDEX iCurrentPatch = iPatch%32;
    CTString strPatchName = edm_md.md_mpPatches[ iCurrentPatch].mp_strName;
    if( strPatchName != "")
    {
      iMaskBit = iCurrentPatch;
      return;
    }
  }
}
//--------------------------------------------------------------------------------------------
/*
 * Sets next valid patch position in existing patches mask
 */
void CEditModel::GetNextValidPatchIndex( INDEX &iMaskBit)
{
  ASSERT( (iMaskBit>=0) && (iMaskBit<MAX_TEXTUREPATCHES) );
  for( INDEX iPatch=iMaskBit+1; iPatch<iMaskBit+MAX_TEXTUREPATCHES; iPatch++)
  {
    INDEX iCurrentPatch = iPatch%32;
    CTString strPatchName = edm_md.md_mpPatches[ iCurrentPatch].mp_strName;
    if( strPatchName != "")
    {
      iMaskBit = iCurrentPatch;
      return;
    }
  }
}
//--------------------------------------------------------------------------------------------
/*
 * Moves patch relatively for given coordinates
 */
void CEditModel::MovePatchRelative( INDEX iMaskBit, MEX2D mexOffset)
{
  CTFileName fnPatch = edm_md.md_mpPatches[ iMaskBit].mp_toTexture.GetName();
  if( fnPatch == "") return;
  edm_md.md_mpPatches[ iMaskBit].mp_mexPosition += mexOffset;
  CalculatePatchesPerPolygon();
}
//--------------------------------------------------------------------------------------------
/*
 * Sets patch stretch
 */
void CEditModel::SetPatchStretch( INDEX iMaskBit, FLOAT fNewStretch)
{
  CTFileName fnPatch = edm_md.md_mpPatches[ iMaskBit].mp_toTexture.GetName();
  if( fnPatch == "") return;
  edm_md.md_mpPatches[ iMaskBit].mp_fStretch = fNewStretch;
  CalculatePatchesPerPolygon();
}
//--------------------------------------------------------------------------------------------
/*
 * Searches for first available empty patch position index and adds patch
 */
BOOL CEditModel::EditAddPatch( CTFileName fnPatchName, MEX2D mexPos, INDEX &iMaskBit)
{
  if( !GetFirstEmptyPatchIndex( iMaskBit))
    return FALSE;

  try
  {
    edm_md.md_mpPatches[ iMaskBit].mp_toTexture.SetData_t( fnPatchName);
  }
  catch (char *strError)
  {
    (void)strError;
    return FALSE;
  }
  edm_md.md_mpPatches[ iMaskBit].mp_mexPosition = mexPos;
  edm_md.md_mpPatches[ iMaskBit].mp_fStretch = 1.0f;
  edm_md.md_mpPatches[ iMaskBit].mp_strName.PrintF( "Patch%02d", iMaskBit);
  CalculatePatchesPerPolygon();
  return TRUE;
}
//--------------------------------------------------------------------------------------------
/*
 * Removes patch with given index from existing mask and erases its file name
 */
void CEditModel::EditRemovePatch( INDEX iMaskBit)
{
  edm_md.md_mpPatches[ iMaskBit].mp_toTexture.SetData(NULL);
  CalculatePatchesPerPolygon();
}

void CEditModel::EditRemoveAllPatches(void)
{
  for( INDEX iPatch=0; iPatch<MAX_TEXTUREPATCHES; iPatch++)
  {
    edm_md.md_mpPatches[ iPatch].mp_toTexture.SetData(NULL);
  }
  CalculatePatchesPerPolygon();
}

INDEX CEditModel::CountPatches(void)
{
  INDEX iResult = 0;
  for(INDEX iPatch=0; iPatch<MAX_TEXTUREPATCHES; iPatch++)
  {
    if( edm_md.md_mpPatches[ iPatch].mp_toTexture.GetName() != "")
    {
      iResult++;
    }
  }
  return iResult;
}

ULONG CEditModel::GetExistingPatchesMask(void)
{
  ULONG ulResult = 0;
  for(INDEX iPatch=0; iPatch<MAX_TEXTUREPATCHES; iPatch++)
  {
    if( edm_md.md_mpPatches[ iPatch].mp_toTexture.GetName() != "")
    {
      ulResult |= 1UL << iPatch;
    }
  }
  return ulResult;
}
//--------------------------------------------------------------------------------------------
void CEditModel::CalculatePatchesPerPolygon(void)
{
  // count existing patches
  INDEX ctPatches = CountPatches();

  // for each mip model
  for( INDEX iMip=0; iMip<edm_md.md_MipCt; iMip++)
  {
    ModelMipInfo *pMMI = &edm_md.md_MipInfos[ iMip];
    // clear previously existing array
    pMMI->mmpi_aPolygonsPerPatch.Clear();
    // if patches are visible in this mip model
    if( (pMMI->mmpi_ulFlags & MM_PATCHES_VISIBLE) && (ctPatches != 0) )
    {
      // add description member for each patch
      pMMI->mmpi_aPolygonsPerPatch.New( ctPatches);
      INDEX iExistingPatch = 0;
      // for each patch
      for(INDEX iPatch=0; iPatch<MAX_TEXTUREPATCHES; iPatch++)
      {
        // if patch exists
        if( edm_md.md_mpPatches[ iPatch].mp_toTexture.GetName() != "")
        {
          // allocate temporary array of indices for each polygon in mip model
          CStaticArray<INDEX> aiPolygons;
          aiPolygons.New( pMMI->mmpi_PolygonsCt);
          // clear counter of occupied polygons
          INDEX ctOccupiedPolygons = 0;
          // get patch occupying box
          CTextureData *pTD = (CTextureData *) edm_md.md_mpPatches[ iPatch].mp_toTexture.GetData();
          ASSERT( pTD != NULL);
          MEX2D mex2dPosition = edm_md.md_mpPatches[ iPatch].mp_mexPosition;
          FLOAT fStretch = edm_md.md_mpPatches[ iPatch].mp_fStretch;
          MEXaabbox2D boxPatch = MEXaabbox2D(
                  mex2dPosition, MEX2D( mex2dPosition(1)+pTD->GetWidth()*fStretch,
                                 mex2dPosition(2)+pTD->GetHeight()*fStretch) );
          // for each polygon
          for(INDEX iPolygon=0; iPolygon<pMMI->mmpi_PolygonsCt; iPolygon++)
          {
            ModelPolygon *pMP = &pMMI->mmpi_Polygons[iPolygon];
            // for all vertices in polygon
            MEXaabbox2D boxMapping;
            for( INDEX iVertex=0; iVertex<pMP->mp_PolygonVertices.Count(); iVertex++)
            {
              ModelTextureVertex *pMTV = pMP->mp_PolygonVertices[iVertex].mpv_ptvTextureVertex;
              // calculate bounding box of mapping coordinates
              boxMapping |= MEXaabbox2D(pMTV->mtv_UV);
            }
            // if bounding box of polygon's mapping coordinates touches patch
            if( boxPatch.HasContactWith( boxMapping))
            {
              // add polygon index to list of occupied polygons
              aiPolygons[ ctOccupiedPolygons] = iPolygon;
              ctOccupiedPolygons++;
            }
          }
          if( ctOccupiedPolygons != 0)
          {
            // copy temporary array of polygon indices to mip model's array of polygon indices
            pMMI->mmpi_aPolygonsPerPatch[ iExistingPatch].ppp_iPolygons.New( ctOccupiedPolygons);
            for( INDEX iOccupied=0; iOccupied<ctOccupiedPolygons; iOccupied++)
            {
              pMMI->mmpi_aPolygonsPerPatch[ iExistingPatch].ppp_iPolygons[iOccupied] =
                aiPolygons[ iOccupied];
            }
          }
          // count existing patches
          iExistingPatch++;
        }
      }
    }
  }
}
//--------------------------------------------------------------------------------------------
/*
 * Writes settings of given mip model into file
 */
void CEditModel::WriteMipSettings_t( CTStream *ostrFile, INDEX iMip)
{
  ASSERT( iMip < edm_md.md_MipCt);

  // write indetification of one mip's mapping info
  ostrFile->WriteID_t( CChunkID( "MIPS"));

  // get count
  INDEX iSurfacesCt = edm_md.md_MipInfos[ iMip].mmpi_MappingSurfaces.Count();
  // write count
  (*ostrFile) << iSurfacesCt;
  // for all surfaces
  FOREACHINSTATICARRAY(edm_md.md_MipInfos[ iMip].mmpi_MappingSurfaces, MappingSurface, itSurface)
  {
    // write setings for current surface
    itSurface->WriteSettings_t( ostrFile);
  }
}
//--------------------------------------------------------------------------------------------
/*
 * Reads settigns of given mip model from file
 */
void CEditModel::ReadMipSettings_t(CTStream *istrFile, INDEX iMip)
{
  MappingSurface msTmp;

  ASSERT( iMip < edm_md.md_MipCt);

  // check chunk
  istrFile->ExpectID_t( CChunkID( "MIPS"));
  // get count
  INDEX iSurfacesCt;
  *istrFile >> iSurfacesCt;

  // for all saved surfaces
  for( INDEX iSurface=0; iSurface<iSurfacesCt; iSurface++)
  {
    // read mapping surface settings
    msTmp.ReadSettings_t( istrFile);

    // for all surfaces in given mip
    for( INDEX i=0; i<edm_md.md_MipInfos[ iMip].mmpi_MappingSurfaces.Count(); i++)
    {
      MappingSurface &ms = edm_md.md_MipInfos[ iMip].mmpi_MappingSurfaces[ i];
      // are these surfaces the same?
      if( ms == msTmp)
      {
        // try to set new position and angles
        ms.ms_sstShadingType = msTmp.ms_sstShadingType;
        ms.ms_sttTranslucencyType = msTmp.ms_sttTranslucencyType;
        ms.ms_ulRenderingFlags = msTmp.ms_ulRenderingFlags;
        ms.ms_colDiffuse = msTmp.ms_colDiffuse;
        ms.ms_colReflections = msTmp.ms_colReflections;
        ms.ms_colSpecular = msTmp.ms_colSpecular;
        ms.ms_colBump = msTmp.ms_colBump;
        ms.ms_ulOnColor = msTmp.ms_ulOnColor;
        ms.ms_ulOffColor = msTmp.ms_ulOffColor;
      }
    }
  }
}
//--------------------------------------------------------------------------------------------
/*
 * Saves mapping data for whole model (iMip = -1) or for just one mip model
 */
void CEditModel::SaveMapping_t( CTFileName fnFileName, INDEX iMip /*=-1*/)
{
  CTFileStream strmMappingFile;

  // create file
  strmMappingFile.Create_t( fnFileName, CTStream::CM_BINARY);
  // write file ID
  strmMappingFile.WriteID_t( CChunkID( "MPNG"));
  // write version
  strmMappingFile.WriteID_t( CChunkID(MAPPING_VERSION));

  // set as we have only one mip
  INDEX iStartCt = iMip;
  INDEX iMaxCt = iMip+1;

  // if iMip is -1 means that we want all mips in model
  if( iMip == -1)
  {
    iStartCt = 0;
    iMaxCt = edm_md.md_MipCt;
  }

  // for wanted mip models
  for( INDEX iMipCt=iStartCt; iMipCt<iMaxCt; iMipCt++)
  {
    // write settings for current mip
    WriteMipSettings_t( &strmMappingFile, iMipCt);
  }

  // save attached sounds
  strmMappingFile<<edm_aasAttachedSounds.Count();
  for( INDEX iSound=0; iSound<edm_aasAttachedSounds.Count(); iSound++)
  {
    edm_aasAttachedSounds[iSound].Write_t( &strmMappingFile);
  }

  // save attached models
  INDEX ctAttachmentPositions = edm_aamAttachedModels.Count();
  ASSERT( edm_md.md_aampAttachedPosition.Count() == ctAttachmentPositions);
  strmMappingFile<<ctAttachmentPositions;
  FOREACHINDYNAMICARRAY(edm_aamAttachedModels, CAttachedModel, itam)
  {
    itam->Write_t( &strmMappingFile);
  }
  FOREACHINDYNAMICARRAY(edm_md.md_aampAttachedPosition, CAttachedModelPosition, itamp)
  {
    itamp->Write_t( &strmMappingFile);
  }

  // save collision boxes
  INDEX ctCollisionBoxes = edm_md.md_acbCollisionBox.Count();
  ASSERT( ctCollisionBoxes>0);
  if(ctCollisionBoxes == 0)
  {
    WarningMessage( "Trying to save 0 collision boxes into mapping file.");
  }
  strmMappingFile<<ctCollisionBoxes;
  FOREACHINDYNAMICARRAY(edm_md.md_acbCollisionBox, CModelCollisionBox, itcb)
  {
    itcb->Write_t( &strmMappingFile);
  }

  // save patches
  for( INDEX iPatch=0; iPatch<MAX_TEXTUREPATCHES; iPatch++)
  {
    edm_md.md_mpPatches[ iPatch].Write_t( &strmMappingFile);
  }
}
//--------------------------------------------------------------------------------------------
/*
 * Loads mapping data for whole model (iMip = -1) or just for one mip model
 */
void CEditModel::LoadMapping_t( CTFileName fnFileName, INDEX iMip /*=-1*/)
{
  CTFileStream strmMappingFile;

  BOOL bReadPolygonsPerSurface = FALSE;
  BOOL bReadSoundsAndAttachments = FALSE;
  BOOL bReadCollision = FALSE;
  BOOL bReadPatches = FALSE;
  BOOL bReadSurfaceColors = FALSE;
  // open binary file
  strmMappingFile.Open_t( fnFileName);
  // recognize file ID
  strmMappingFile.ExpectID_t( CChunkID( "MPNG"));
  // get version of mapping file
  CChunkID cidVersion = strmMappingFile.GetID_t();
  // act acording to version of mapping file
  if( cidVersion == CChunkID(MAPPING_VERSION_WITHOUT_POLYGONS_PER_SURFACE) )
  {
  }
  else if( cidVersion == CChunkID( MAPPING_VERSION_WITHOUT_SOUNDS_AND_ATTACHMENTS))
  {
    bReadPolygonsPerSurface = TRUE;
  }
  else if( cidVersion == CChunkID( MAPPING_VERSION_WITHOUT_COLLISION))
  {
    bReadPolygonsPerSurface = TRUE;
    bReadSoundsAndAttachments = TRUE;
  }
  else if( cidVersion == CChunkID( MAPPING_VERSION_WITHOUT_PATCHES))
  {
    bReadPolygonsPerSurface = TRUE;
    bReadSoundsAndAttachments = TRUE;
    bReadCollision = TRUE;
  }
  else if( cidVersion == CChunkID( MAPPING_WITHOUT_SURFACE_COLORS))
  {
    bReadPolygonsPerSurface = TRUE;
    bReadSoundsAndAttachments = TRUE;
    bReadCollision = TRUE;
    bReadPatches = TRUE;
  }
  else if( cidVersion == CChunkID( MAPPING_VERSION))
  {
    bReadPolygonsPerSurface = TRUE;
    bReadSoundsAndAttachments = TRUE;
    bReadCollision = TRUE;
    bReadPatches = TRUE;
    bReadSurfaceColors = TRUE;
  }
  else
  {
    throw( "Invalid version of mapping file.");
  }
  // set as we have only one mip
  INDEX iStartCt = iMip;
  INDEX iMaxCt = iMip+1;

  // if iMip is -1 means that we want all mips in model
  if( iMip == -1)
  {
    iStartCt = 0;
    iMaxCt = edm_md.md_MipCt;
  }

  // for wanted mip models
  for( INDEX iMipCt=iStartCt; iMipCt<iMaxCt; iMipCt++)
  {
    if( strmMappingFile.PeekID_t()==CChunkID("MIPS"))
    {
      // read mapping for current mip
      ReadMipSettings_t( &strmMappingFile, iMipCt);
    }
  }

  // skip data for mip models that were saved but haven't been
  // readed in previous loop
  while( strmMappingFile.PeekID_t()==CChunkID("MIPS"))
  {
    MappingSurface msDummy;
    strmMappingFile.ExpectID_t( CChunkID( "MIPS"));
    // for all saved surfaces
    INDEX iSurfacesCt;
    strmMappingFile >> iSurfacesCt;
    for( INDEX iSurface=0; iSurface<iSurfacesCt; iSurface++)
    {
      // skip mapping surface
      msDummy.ReadSettings_t( &strmMappingFile);
    }
  }

  if( bReadSoundsAndAttachments)
  {
    // load attached sounds
    INDEX ctSounds;
    strmMappingFile>>ctSounds;
    ASSERT(ctSounds > 0);
    edm_aasAttachedSounds.Clear();
    edm_aasAttachedSounds.New( ctSounds);
    for( INDEX iSound=0; iSound<edm_aasAttachedSounds.Count(); iSound++)
    {
      edm_aasAttachedSounds[iSound].Read_t( &strmMappingFile);
    }
    // if number of animations does not match number of sounds saved in map file, reset sounds
    if(ctSounds != edm_md.GetAnimsCt())
    {
      edm_aasAttachedSounds.Clear();
      edm_aasAttachedSounds.New( edm_md.GetAnimsCt());
    }

    // load attached models
    INDEX ctAttachmentPositions;
    strmMappingFile>>ctAttachmentPositions;
    edm_aamAttachedModels.Clear();
    edm_md.md_aampAttachedPosition.Clear();
    if( ctAttachmentPositions != 0)
    {
      edm_aamAttachedModels.New(ctAttachmentPositions);
      edm_md.md_aampAttachedPosition.New(ctAttachmentPositions);
      FOREACHINDYNAMICARRAY(edm_aamAttachedModels, CAttachedModel, itam)
      {
        try
        {
          itam->Read_t( &strmMappingFile);
        }
        catch( char *strError)
        {
          (void) strError;
          edm_aamAttachedModels.Clear();
          edm_md.md_aampAttachedPosition.Clear();
          ThrowF_t( "Error ocured while reading attahment model, maybe model does"
                    " not exist.");
        }
      }
      FOREACHINDYNAMICARRAY(edm_md.md_aampAttachedPosition, CAttachedModelPosition, itamp)
      {
        itamp->Read_t( &strmMappingFile);
      }
    }
  }

  if( bReadCollision)
  {
    // read collision boxes
    edm_md.md_acbCollisionBox.Clear();
    INDEX ctCollisionBoxes;
    strmMappingFile>>ctCollisionBoxes;
    ASSERT(ctCollisionBoxes>0);
    if( ctCollisionBoxes>0)
    {
      edm_md.md_acbCollisionBox.New( ctCollisionBoxes);
      FOREACHINDYNAMICARRAY(edm_md.md_acbCollisionBox, CModelCollisionBox, itcb)
      {
        itcb->Read_t( &strmMappingFile);
        itcb->ReadName_t( &strmMappingFile);
      }
    }
    else
    {
      edm_md.md_acbCollisionBox.New( 1);
      throw( "Trying to load 0 collision boxes from mapping file.");
    }
  }

  if( bReadPatches)
  {
    EditRemoveAllPatches();
    for( INDEX iPatch=0; iPatch<MAX_TEXTUREPATCHES; iPatch++)
    {
      edm_md.md_mpPatches[ iPatch].Read_t( &strmMappingFile);
    }
    CalculatePatchesPerPolygon();
  }
}

void CEditModel::AddCollisionBox(void)
{
  // add one collision box
  edm_md.md_acbCollisionBox.New();
  // select newly added collision box
  edm_iActiveCollisionBox = edm_md.md_acbCollisionBox.Count()-1;
}

void CEditModel::DeleteCurrentCollisionBox(void)
{
  INDEX ctCollisionBoxes = edm_md.md_acbCollisionBox.Count();
  // if we have more than 1 collision box
  if( ctCollisionBoxes != 1)
  {
    edm_md.md_acbCollisionBox.Lock();
    edm_md.md_acbCollisionBox.Delete( &edm_md.md_acbCollisionBox[ edm_iActiveCollisionBox]);
    edm_md.md_acbCollisionBox.Unlock();
    // if this was last collision box
    if( edm_iActiveCollisionBox == (ctCollisionBoxes-1) )
    {
      // select last collision box
      edm_iActiveCollisionBox = ctCollisionBoxes-2;
    }
  }
}

void CEditModel::ActivatePreviousCollisionBox(void)
{
  // get count of collision boxes
  INDEX ctCollisionBoxes = edm_md.md_acbCollisionBox.Count();
  if( edm_iActiveCollisionBox != 0)
  {
    edm_iActiveCollisionBox -= 1;
  }
}

void CEditModel::ActivateNextCollisionBox(void)
{
  // get count of collision boxes
  INDEX ctCollisionBoxes = edm_md.md_acbCollisionBox.Count();
  if( edm_iActiveCollisionBox != (ctCollisionBoxes-1) )
  {
    edm_iActiveCollisionBox += 1;
  }
}

void CEditModel::SetCollisionBox(FLOAT3D vMin, FLOAT3D vMax)
{
  edm_md.md_acbCollisionBox.Lock();
  edm_md.md_acbCollisionBox[ edm_iActiveCollisionBox].mcb_vCollisionBoxMin = vMin;
  edm_md.md_acbCollisionBox[ edm_iActiveCollisionBox].mcb_vCollisionBoxMax = vMax;
  edm_md.md_acbCollisionBox.Unlock();
  CorrectCollisionBoxSize();
}

CTString CEditModel::GetCollisionBoxName(INDEX iCollisionBox)
{
  // get count of collision boxes
  INDEX ctCollisionBoxes = edm_md.md_acbCollisionBox.Count();
  ASSERT( iCollisionBox < ctCollisionBoxes);
  if( iCollisionBox >= ctCollisionBoxes)
  {
    iCollisionBox = ctCollisionBoxes-1;
  }
  CTString strCollisionBoxName;
  edm_md.md_acbCollisionBox.Lock();
  strCollisionBoxName = edm_md.md_acbCollisionBox[ iCollisionBox].mcb_strName;
  edm_md.md_acbCollisionBox.Unlock();
  return strCollisionBoxName;
}

CTString CEditModel::GetCollisionBoxName(void)
{
  CTString strCollisionBoxName;
  edm_md.md_acbCollisionBox.Lock();
  strCollisionBoxName = edm_md.md_acbCollisionBox[ edm_iActiveCollisionBox].mcb_strName;
  edm_md.md_acbCollisionBox.Unlock();
  return strCollisionBoxName;
}

void CEditModel::SetCollisionBoxName(CTString strNewName)
{
  edm_md.md_acbCollisionBox.Lock();
  edm_md.md_acbCollisionBox[ edm_iActiveCollisionBox].mcb_strName = strNewName;
  edm_md.md_acbCollisionBox.Unlock();
}

void CEditModel::CorrectCollisionBoxSize(void)
{
  // no correction needed if colliding as cube
  if( edm_md.md_bCollideAsCube) return;
  edm_md.md_acbCollisionBox.Lock();
  // get equality radio initial value
  INDEX iEqualityType = GetCollisionBoxDimensionEquality();
  // get min and max vectors of currently active collision box
  FLOAT3D vMin = edm_md.md_acbCollisionBox[ edm_iActiveCollisionBox].mcb_vCollisionBoxMin;
  FLOAT3D vMax = edm_md.md_acbCollisionBox[ edm_iActiveCollisionBox].mcb_vCollisionBoxMax;
  FLOAT3D vOldCenter;

  vOldCenter(1) = (vMax(1)+vMin(1))/2.0f;
  vOldCenter(3) = (vMax(3)+vMin(3))/2.0f;

  // calculate vector of collision box diagonale
  FLOAT3D vCorrectedDiagonale = vMax-vMin;
  // apply minimal collision box conditions
  if( vCorrectedDiagonale(1) < 0.1f) vCorrectedDiagonale(1) = 0.01f;
  if( vCorrectedDiagonale(2) < 0.1f) vCorrectedDiagonale(2) = 0.01f;
  if( vCorrectedDiagonale(3) < 0.1f) vCorrectedDiagonale(3) = 0.01f;
  // according to equality type flag (which dimensions are same)
  switch( iEqualityType)
  {
    case HEIGHT_EQ_WIDTH:
    {
      // don't allow that unlocked dimension is smaller than locked ones
      if( vCorrectedDiagonale(3) < vCorrectedDiagonale(1) )
      {
        vCorrectedDiagonale(3) = vCorrectedDiagonale(1);
      }
      // height = width
      vCorrectedDiagonale(2) = vCorrectedDiagonale(1);
      break;
    }
    case LENGTH_EQ_WIDTH:
    {
      // don't allow that unlocked dimension is smaller than locked ones
      if( vCorrectedDiagonale(2) < vCorrectedDiagonale(1) )
      {
        vCorrectedDiagonale(2) = vCorrectedDiagonale(1);
      }
      // length = width
      vCorrectedDiagonale(3) = vCorrectedDiagonale(1);
      break;
    }
    case LENGTH_EQ_HEIGHT:
    {
      // don't allow that unlocked dimension is smaller than locked ones
      if( vCorrectedDiagonale(1) < vCorrectedDiagonale(2) )
      {
        vCorrectedDiagonale(1) = vCorrectedDiagonale(2);
      }
      // length = height
      vCorrectedDiagonale(3) = vCorrectedDiagonale(2);
      break;
    }
    default:
    {
      ASSERTALWAYS( "Invalid collision box dimension equality value found.");
    }
  }
  // set new, corrected max vector
  FLOAT3D vNewMin, vNewMax;
  vNewMin(1) = vOldCenter(1)-vCorrectedDiagonale(1)/2.0f;
  vNewMin(2) = vMin(2);
  vNewMin(3) = vOldCenter(3)-vCorrectedDiagonale(3)/2.0f;

  vNewMax(1) = vOldCenter(1)+vCorrectedDiagonale(1)/2.0f;
  vNewMax(2) = vMin(2)+vCorrectedDiagonale(2);
  vNewMax(3) = vOldCenter(3)+vCorrectedDiagonale(3)/2.0f;

  edm_md.md_acbCollisionBox[ edm_iActiveCollisionBox].mcb_vCollisionBoxMin = vNewMin;
  edm_md.md_acbCollisionBox[ edm_iActiveCollisionBox].mcb_vCollisionBoxMax = vNewMax;
  edm_md.md_acbCollisionBox.Unlock();
}
//---------------------------------------------------------------------------------------------
// collision box handling functions
FLOAT3D &CEditModel::GetCollisionBoxMin(void)
{
  edm_md.md_acbCollisionBox.Lock();
  FLOAT3D &vMin = edm_md.md_acbCollisionBox[edm_iActiveCollisionBox].mcb_vCollisionBoxMin;
  edm_md.md_acbCollisionBox.Unlock();
  return vMin;
};
FLOAT3D &CEditModel::GetCollisionBoxMax(void)
{
  edm_md.md_acbCollisionBox.Lock();
  FLOAT3D &vMax = edm_md.md_acbCollisionBox[edm_iActiveCollisionBox].mcb_vCollisionBoxMax;
  edm_md.md_acbCollisionBox.Unlock();
  return vMax;
};

// returns HEIGHT_EQ_WIDTH, LENGTH_EQ_WIDTH or LENGTH_EQ_HEIGHT
INDEX CEditModel::GetCollisionBoxDimensionEquality()
{
  return edm_md.GetCollisionBoxDimensionEquality(edm_iActiveCollisionBox);
};
// set new collision box equality value
void CEditModel::SetCollisionBoxDimensionEquality( INDEX iNewDimEqType)
{
  edm_md.md_acbCollisionBox.Lock();
  edm_md.md_acbCollisionBox[edm_iActiveCollisionBox].mcb_iCollisionBoxDimensionEquality =
    iNewDimEqType;
  edm_md.md_acbCollisionBox.Unlock();
  CorrectCollisionBoxSize();
};





#if 0
        // only triangles are supported!
        ASSERT( opo.opo_PolygonEdges.Count() == 3);  
        if( opo.opo_PolygonEdges.Count() != 3) {
  			  ThrowF_t( "Non-triangle polygon encountered in model file %s !", (CTString)itFr->cfnn_FileName);
        }

        CObjectPolygonEdge &ope0 = opo.opo_PolygonEdges[0];
        CObjectPolygonEdge &ope1 = opo.opo_PolygonEdges[1];
        if( ope0.ope_Backward) {
          povx0 = ope0.ope_Edge->oed_Vertex1;
          povx1 = ope0.ope_Edge->oed_Vertex0;
          povx2 = ope1.ope_Edge->oed_Vertex0;
          ASSERT( ope1.ope_Edge->oed_Vertex1 == povx1);
        } else {
          povx0 = ope0.ope_Edge->oed_Vertex0;
          povx1 = ope0.ope_Edge->oed_Vertex1;
          povx2 = ope1.ope_Edge->oed_Vertex1;
          ASSERT( ope1.ope_Edge->oed_Vertex0 == povx1);
        }

        if( povx1==&ovxThis || povx0==&ovxThis || povx2==&ovxThis) {
          DOUBLE3D v0 = (*povx0)-(*povx1);
          DOUBLE3D v1 = (*povx2)-(*povx1);
          v0.Normalize();
          v1.Normalize();
          ANGLE a = ASin( (v0*v1).Length());
          ASSERT( a>=0 && a<=180);
          aSum += a;
          vSum += DOUBLEtoFLOAT( (DOUBLE3D&)*(itPoly->opo_Plane)) * a;
          break;
        }
#endif