#include "Vector3.h"
#include "lwo2mesh.h"
#include "Ogre.h"
#include "OgreMesh.h"
#include "OgreStringConverter.h"
#include "OgreDefaultHardwareBufferManager.h"

#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX
#include <math.h>
#include <float.h>  // FLT_MIN, FLT_MAX
#include <libgen.h> // dirname(), basename().
#include <string.h> // strtok();
#include <string>   // string class
#endif

#define POLYLIMIT 0x5555
#define POINTLIMIT 0x5555

extern Mesh::LodDistanceList distanceList;
extern Real reduction;
extern bool flags[NUMFLAGS];
extern MaterialSerializer* materialSerializer;
extern char *matPrefix;

extern ostream& nl(ostream& os);

#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX

/* We expect the caller to provide an arrays of chars for the output. */
void _splitpath( const char *_fn, char *_drive, char *_dir, char *_node, char *_ext ) {
       
   /* A crazy mix of both c and c++.. */
       
   const char *delimiters = ".";
   char buf[ _MAX_DRIVE+_MAX_DIR+_MAX_FNAME+_MAX_EXT + 5 ];
   char *exte, *ddir, *fnam, *_ext_tmp;

   strcpy( buf, _fn );
   strcpy( _drive, "" ); // _drive is always empth on linux.

   if ( String( buf ).empty() ) {
       strcpy( _node, "" );
       strcpy( _dir, "" );
       strcpy( _ext, "" );
       return;
   } 
   
   fnam = basename( buf );
   strcpy( _node, fnam );
   ddir = dirname( buf );
   strcpy( _dir, ddir );
   
   _ext_tmp = strtok( fnam, delimiters );
   while ( ( _ext_tmp = strtok( NULL, delimiters ) ) != NULL ) exte = _ext_tmp;
   strcpy( _ext, exte );

   _node[ strlen(_node) - strlen(_ext) - 1 ] = '\0';

}

/* We expect the caller to provide an array of chars for the output. */
void _makepath( char *_fn, const char *_drive, const char *_dir,
       const char *_node, const char *_ext ) {
   
   /* A crazy mix of both c and c++.. */

   std::string buf("");

   if ( _drive != NULL ) // On Linux, this is usually empty.
       buf += _drive;
       
   if ( _dir != NULL ) { // The directory part.
       if ( std::string(_dir).empty() )
           buf += ".";
       buf += _dir;
       buf += "/";
   }
   
   if ( _node != NULL ) // The filename without the extension.
       buf += _node;
   
   if ( _ext != NULL ) { // The extension.
       if ( std::string( _ext ).compare( 0, 1, "." ) != 0 )
           buf += ".";
       buf += _ext;
   }

   strcpy( _fn, buf.c_str() );
}

#endif

void Lwo2MeshWriter::doExportMaterials()
{
	char
		drive[ _MAX_DRIVE ],
		dir[ _MAX_DIR ],
		node[ _MAX_FNAME ],
		ext[ _MAX_EXT ],
		texname [128];

	unsigned int slength = 0;

	if (flags[RenameMaterials])
	{
		if (flags[UseInteractiveMethod])
		{
			for (unsigned int i = 0; i < object->surfaces.size(); ++i)
			{		
				lwSurface *surface = object->surfaces[i];
				cout << "Rename surface " << surface->name << " to: ";
				cin >> texname;
				surface->setname(texname);
			}
		} else {
			_splitpath( dest, drive, dir, node, ext );

			for (unsigned int i = 0; i < object->surfaces.size(); ++i)
			{
				lwSurface *surface = object->surfaces[i];
				if (flags[UsePrefixMethod])
					strcpy(texname,matPrefix);
				else
				{
					strcpy(texname,node);
					strcat(texname,"_");
				}

				strcat(texname, surface->name);
				surface->setname(texname);
			}
		}
	}

	for (unsigned int i = 0; i < object->surfaces.size(); ++i)
	{		
		lwSurface *surface = object->surfaces[i];

		// Create deferred material so no load
		MaterialPtr ogreMat = MaterialManager::getSingleton().getByName(surface->name);
		
		if (ogreMat.isNull())
		{
			ogreMat = MaterialManager::getSingleton().create(surface->name, 
                ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
			
			ogreMat->setAmbient
			(
				surface->color.rgb[0],
				surface->color.rgb[1],
				surface->color.rgb[2]
			);
			
			ogreMat->setDiffuse
			(
				surface->diffuse.val * surface->color.rgb[0],
				surface->diffuse.val * surface->color.rgb[1],
				surface->diffuse.val * surface->color.rgb[2],
				1.0f
			);
			
			ogreMat->setSpecular
			(
				surface->specularity.val * surface->color.rgb[0],
				surface->specularity.val * surface->color.rgb[1],
				surface->specularity.val * surface->color.rgb[2],
				1.0f
			);
			
			ogreMat->setShininess(surface->glossiness.val);
			
			ogreMat->setSelfIllumination
			(
				surface->luminosity.val * surface->color.rgb[0],
				surface->luminosity.val * surface->color.rgb[1],
				surface->luminosity.val * surface->color.rgb[2]
			);

			unsigned int j;
			lwTexture *tex;
			int cindex;
			lwClip *clip;
			
			for (j = 0; j < surface->color.textures.size(); j++)
			{
				tex = surface->color.textures[j];
				cindex = tex->param.imap->cindex;
				clip = object->lwFindClip(cindex);
				
				if (clip)
				{
					_splitpath( clip->source.still->name, drive, dir, node, ext );
					_makepath( texname, 0, 0, node, ext );					
					ogreMat->getTechnique(0)->getPass(0)->createTextureUnitState(texname);
				}
			}

			for (j = 0; j < surface->transparency.val.textures.size(); j++)
			{
				tex = surface->transparency.val.textures[j];
				cindex = tex->param.imap->cindex;
				clip = object->lwFindClip(cindex);
				
				if (clip)
				{
					_splitpath( clip->source.still->name, drive, dir, node, ext );
					_makepath( texname, 0, 0, node, ext );					
					ogreMat->getTechnique(0)->getPass(0)->createTextureUnitState(texname);
				}
			}
			materialSerializer->queueForExport(ogreMat);
		}
	}
}

Skeleton *Lwo2MeshWriter::doExportSkeleton(const String &skelName, int l)
{
	vpolygons bones;
	bones.clear();
	bones.reserve(256);

	vpoints bonepoints;
	bonepoints.clear();
	bonepoints.reserve(512);

	if (l == -1)
	{
		for (l = 0; l < object->layers.size(); ++l)
		{
			copyPoints(-1, ID_BONE, object->layers[l]->points, bonepoints);
			copyPolygons(-1, ID_BONE, object->layers[l]->polygons, bones);
		}
	}
	else
	{
		copyPoints(-1, ID_BONE, object->layers[l]->points, bonepoints);
		copyPolygons(-1, ID_BONE, object->layers[l]->polygons, bones);
	}

	if (!bones.size()) return NULL; // no bones means no skeleton

	SkeletonPtr ogreskel = Ogre::SkeletonManager::getSingleton().create(skelName, Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);

	unsigned int i;
	// Create all the bones in turn
	for (i = 0; i < bones.size(); ++i)
	{
		lwPolygon* bone = bones[i];
		if (bone->vertices.size() != 2) continue; // a bone has only 2 sides

		Bone* ogreBone = ogreskel->createBone("Bone");

		Ogre::Vector3 bonePos(bone->vertices[0]->point->x, bone->vertices[0]->point->y, bone->vertices[0]->point->z);

		ogreBone->setPosition(bonePos);
		// Hmm, Milkshape has chosen a Euler angle representation of orientation which is not smart
		// Rotation Matrix or Quaternion would have been the smarter choice
		// Might we have Gimbal lock here? What order are these 3 angles supposed to be applied?
		// Grr, we'll try our best anyway...
		Quaternion qx, qy, qz, qfinal;
/*
		qx.FromAngleAxis(msBoneRot[0], Vector3::UNIT_X);
		qy.FromAngleAxis(msBoneRot[1], Vector3::UNIT_Y);
		qz.FromAngleAxis(msBoneRot[2], Vector3::UNIT_Z);
*/
		// Assume rotate by x then y then z
		qfinal = qz * qy * qx;
		ogreBone->setOrientation(qfinal);
	}
/*
	for (i = 0; i < numBones; ++i)
	{
		msBone* bone = msModel_GetBoneAt(pModel, i);

		if (strlen(bone->szParentName) == 0)
		{
		}
		else
		{
			Bone* ogrechild = ogreskel->getBone(bone->szName);
			Bone* ogreparent = ogreskel->getBone(bone->szParentName);

			if (ogrechild == 0)
			{
				continue;
			}
			if (ogreparent == 0)
			{
				continue;
			}
			// Make child
			ogreparent->addChild(ogrechild);
		}


	}

	// Create the Animation(s)
	doExportAnimations(pModel, ogreskel);

	// Create skeleton serializer & export
	SkeletonSerializer serializer;
	serializer.exportSkeleton(ogreskel, szFile);

	ogreMesh->_notifySkeleton(ogreskel);

	return ogreskel;
*/
    if (!ogreskel.isNull())
        Ogre::SkeletonManager::getSingleton().remove(ogreskel->getHandle());

	return NULL;
}

#define POSITION_BINDING 0
#define NORMAL_BINDING 1
#define TEXCOORD_BINDING 2

VertexData *Lwo2MeshWriter::setupVertexData(unsigned short vertexCount, VertexData *oldVertexData, bool deleteOldVertexData)
{
	VertexData *vertexData = new VertexData();

	if (oldVertexData)
	{
        // Basic vertex info
        vertexData->vertexStart = oldVertexData->vertexStart;
		vertexData->vertexCount = oldVertexData->vertexCount + vertexCount;

		const VertexBufferBinding::VertexBufferBindingMap bindings = oldVertexData->vertexBufferBinding->getBindings();
		VertexBufferBinding::VertexBufferBindingMap::const_iterator vbi, vbend;
		vbend = bindings.end();

		for (vbi = bindings.begin(); vbi != vbend; ++vbi)
		{
			HardwareVertexBufferSharedPtr srcbuf = vbi->second;
			// create new buffer with the same settings
			HardwareVertexBufferSharedPtr dstBuf = 
				HardwareBufferManager::getSingleton().createVertexBuffer(
					srcbuf->getVertexSize(), srcbuf->getNumVertices() + vertexCount, srcbuf->getUsage(), srcbuf->isSystemMemory());

			// copy data
			dstBuf->copyData(*srcbuf, 0, 0, srcbuf->getSizeInBytes(), true);

			// Copy binding
			vertexData->vertexBufferBinding->setBinding(vbi->first, dstBuf);
		}

        // Copy elements
        const VertexDeclaration::VertexElementList elems = oldVertexData->vertexDeclaration->getElements();
        VertexDeclaration::VertexElementList::const_iterator ei, eiend;
        eiend = elems.end();
        for (ei = elems.begin(); ei != eiend; ++ei)
        {
            vertexData->vertexDeclaration->addElement(
                ei->getSource(),
                ei->getOffset(),
                ei->getType(),
                ei->getSemantic(),
                ei->getIndex() );
        }
		if (deleteOldVertexData) delete oldVertexData;
	}
	else
	{
		vertexData->vertexCount = vertexCount;
		
		VertexBufferBinding* bind = vertexData->vertexBufferBinding;
		VertexDeclaration* decl = vertexData->vertexDeclaration;
		
		decl->addElement(POSITION_BINDING, 0, VET_FLOAT3, VES_POSITION);
		HardwareVertexBufferSharedPtr pbuf = HardwareBufferManager::getSingleton().createVertexBuffer(decl->getVertexSize(POSITION_BINDING), vertexData->vertexCount, HardwareBuffer::HBU_DYNAMIC, false);
		bind->setBinding(POSITION_BINDING, pbuf);
		
		decl->addElement(NORMAL_BINDING, 0, VET_FLOAT3, VES_NORMAL);
		HardwareVertexBufferSharedPtr nbuf = HardwareBufferManager::getSingleton().createVertexBuffer(decl->getVertexSize(NORMAL_BINDING), vertexData->vertexCount, HardwareBuffer::HBU_DYNAMIC, false);
		bind->setBinding(NORMAL_BINDING, nbuf);
		
		decl->addElement(TEXCOORD_BINDING, 0, VET_FLOAT2, VES_TEXTURE_COORDINATES);
		HardwareVertexBufferSharedPtr tbuf = HardwareBufferManager::getSingleton().createVertexBuffer(decl->getVertexSize(TEXCOORD_BINDING), vertexData->vertexCount, HardwareBuffer::HBU_DYNAMIC, false);
		bind->setBinding(TEXCOORD_BINDING, tbuf);
	}	
	return vertexData;
}

void Lwo2MeshWriter::copyPoints(int surfaceIndex, unsigned long polygontype, vpoints &sourcepoints, vpoints &destpoints)
{
	for (unsigned int i = 0; i < sourcepoints.size(); i++)
	{
		lwPoint *point = sourcepoints[i];
		
		for (unsigned int j = 0; j < point->polygons.size(); j++)
		{
			lwPolygon *polygon = point->polygons[j];
			if (polygon->type == polygontype)
				if (surfaceIndex == -1 || surfaceIndex == polygon->surfidx)
				{
					destpoints.push_back(point);
					break;
				}
		}
	}
}

void Lwo2MeshWriter::copyPolygons(int surfaceIndex, unsigned long polygontype, vpolygons &sourcepolygons, vpolygons &destpolygons)
{
	for (unsigned int i = 0; i < sourcepolygons.size(); i++)
	{
		lwPolygon *polygon = sourcepolygons[i];
		if (polygon->type == polygontype)
			if (surfaceIndex == -1 || surfaceIndex == polygon->surfidx)
				destpolygons.push_back(polygon);
	}
}

void Lwo2MeshWriter::copyDataToVertexData(vpoints &points,
										vpolygons &polygons,
										vvmaps &vmaps,
										IndexData *indexData,
										VertexData *vertexData,
										unsigned short vertexDataOffset)
{
	lwVMap *vmap = 0;
	unsigned int ni;
	
	HardwareVertexBufferSharedPtr pbuf = vertexData->vertexBufferBinding->getBuffer(POSITION_BINDING);
	HardwareVertexBufferSharedPtr nbuf = vertexData->vertexBufferBinding->getBuffer(NORMAL_BINDING);
	HardwareVertexBufferSharedPtr tbuf = vertexData->vertexBufferBinding->getBuffer(TEXCOORD_BINDING);
	HardwareIndexBufferSharedPtr ibuf = indexData->indexBuffer;

	float* pPos = static_cast<float*>(pbuf->lock(HardwareBuffer::HBL_DISCARD));
	float* pNor = static_cast<float*>(nbuf->lock(HardwareBuffer::HBL_DISCARD));
	float* pTex = static_cast<float*>(tbuf->lock(HardwareBuffer::HBL_DISCARD));
	unsigned short *pIdx = static_cast<unsigned short*>(ibuf->lock(HardwareBuffer::HBL_DISCARD));

	for (unsigned int p = 0; p < polygons.size(); p++)
	{
		lwPolygon *polygon = polygons[p];
		
		if (polygon->vertices.size() != 3) continue; // only copy triangles;

		for (unsigned int v = 0; v < polygon->vertices.size(); v++)
		{
			lwVertex *vertex = polygon->vertices[v];
			lwPoint *point = vertex->point;
			unsigned short i = getPointIndex(point, points);

			pIdx[p*3 + v] = vertexDataOffset + i;
			
			ni = (vertexDataOffset + i) * 3;
			
			pPos[ni] = vertex->point->x;
			pPos[ni + 1] = vertex->point->y;
			pPos[ni + 2] = vertex->point->z;
			
			pNor[ni] = vertex->normal.x;
			pNor[ni + 1] = vertex->normal.y;
			pNor[ni + 2] = vertex->normal.z;
			
			bool found = false;
			
			ni = (vertexDataOffset + i) * 2;
			
			for (unsigned int v = 0; v < point->vmaps.size(); v++)
			{
				for (unsigned int vr = 0; vr < vmaps.size(); vr++)
				{
					vmap = vmaps[vr];
					if (point->vmaps[v].vmap == vmap)
					{
						int n = point->vmaps[v].index;
						
						pTex[ni] = vmap->val[n][0];
						pTex[ni + 1] = 1.0f - vmap->val[n][1];
						found = true;
						break;
					}
				}
				if (found) break;
			}
		}
	}
    pbuf->unlock();
    nbuf->unlock();
    tbuf->unlock();
	ibuf->unlock();
}

void Lwo2MeshWriter::prepLwObject(void)
{
	unsigned int l, p;
	
	for (l = 0; l < object->layers.size(); l++)
	{
		lwLayer *layer = object->layers[l];
		
#ifdef _DEBUG
		cout << "Triangulating layer " << l << ", Polygons before: " << layer->polygons.size();
#endif
		layer->triangulatePolygons();
#ifdef _DEBUG
		cout << ", Polygons after: " << layer->polygons.size() << endl;
#endif
		
		// Mirror x-coord for Ogre;
		for (p = 0; p < layer->points.size(); p++)
		{
			layer->points[p]->x *= -1.0f;
			layer->points[p]->polygons.clear();
		}
		// Unscrew the bounding box
		float x = layer->bboxmin.x * -1.0f;
		layer->bboxmin.x = layer->bboxmax.x * -1.0f;
		layer->bboxmax.x = x;
		
		for ( p = 0; p < layer->polygons.size(); p++ )
		{
			lwPolygon *polygon = layer->polygons[ p ];
			for (unsigned int j = 0; j < polygon->vertices.size(); j++ )
				polygon->vertices[ j ]->point->polygons.push_back(polygon);
		}	
		
		for (p = 0; p < layer->polygons.size(); p++)
			layer->polygons[p]->flip();
		
		layer->calculatePolygonNormals();
		layer->calculateVertexNormals();
	}
}

inline int Lwo2MeshWriter::getPointIndex(lwPoint *point, vpoints &points)
{
	for (unsigned int i = 0; i < points.size(); ++i)
		if (points[i] == point) return i;
		
	return -1;
}

inline String Lwo2MeshWriter::makeLayerFileName(char* dest, unsigned int l, char *layername)
{
	char
		drive[ _MAX_DRIVE ],
		dir[ _MAX_DIR ],
		node[ _MAX_FNAME ],
		ext[ _MAX_EXT ],
		buf[ _MAX_DRIVE + _MAX_DIR + _MAX_FNAME + _MAX_EXT + 5 ];

	String LayerFileName;
	String TempName;

	_splitpath( dest, drive, dir, node, ext );

	TempName = String( node );
   
	if (layername) {
		TempName += ".";
		TempName += layername;
	} else {
		TempName += ".layer" + StringConverter::toString(l);
	}

	_makepath( buf, drive, dir, TempName.c_str(), ext );
	LayerFileName = String( buf );

	return LayerFileName;
}

inline String Lwo2MeshWriter::makeMaterialFileName(char* dest)
{
	char
		drive[ _MAX_DRIVE ],
		dir[ _MAX_DIR ],
		node[ _MAX_FNAME ],
		ext[ _MAX_EXT ],
		buf[ _MAX_DRIVE + _MAX_DIR + _MAX_FNAME + _MAX_EXT + 5 ];

	String MaterialFileName;

	_splitpath( dest, drive, dir, node, ext );
	_makepath( buf, drive, dir, node, ".material" );
 
	const char *test = MaterialFileName.c_str();
	MaterialFileName = String( buf );

	return MaterialFileName;
}

inline void Lwo2MeshWriter::getTextureVMaps(vtextures &textures, vvmaps &svmaps, vvmaps &dvmaps)
{
	for (unsigned int i = 0; i < textures.size(); i++)
	{
		lwTexture *texture = textures[i];
		
		if (texture->type == ID_IMAP && texture->param.imap)
		{
			char *mapname = texture->param.imap->vmap_name;
			if (mapname)
				for (unsigned int v = 0; v < svmaps.size(); v++)
				{
					lwVMap *vmap = svmaps[v];
					if (strcmp(mapname, vmap->name) == 0) dvmaps.push_back(vmap);
				}
		}
	}
	return;
}

bool Lwo2MeshWriter::writeLwo2Mesh(lwObject *nobject, char *ndest)
{
	object = nobject;
	dest = ndest;
	
	if (!object) return false;
	if (!object->layers.size()) return false;
	
	prepLwObject();
	
	vpoints points;
	vpolygons polygons;
	vvmaps vmaps;
	
	MeshSerializer meshserializer;

	if (flags[ExportMaterials])
	{
		doExportMaterials();
		materialSerializer->exportQueued(makeMaterialFileName(dest));
	}

	unsigned int ml = object->layers.size();

	bool SeparateLayers = flags[UseSeparateLayers] && ml > 1;

    if (!SeparateLayers) ogreMesh = Ogre::MeshManager::getSingleton().create(ndest, Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);

	Ogre::Vector3 boundingBoxMin(FLT_MAX, FLT_MAX, FLT_MAX);
	Ogre::Vector3 boundingBoxMax(FLT_MIN, FLT_MIN, FLT_MIN);


	for( unsigned int ol = 0; ol < ml; ++ol )
	{
		if (!object->layers[ol]->polygons.size())
			continue;

		Ogre::Vector3 currentMin(object->layers[ol]->bboxmin.x,
								 object->layers[ol]->bboxmin.y,
								 object->layers[ol]->bboxmin.z);
		Ogre::Vector3 currentMax(object->layers[ol]->bboxmax.x,
								 object->layers[ol]->bboxmax.y,
								 object->layers[ol]->bboxmax.z);

		if (SeparateLayers)
		{	
			ogreMesh = Ogre::MeshManager::getSingleton().create(ndest, Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
			ogreMesh->_setBounds(Ogre::AxisAlignedBox(currentMin, currentMax));
			ogreMesh->_setBoundingSphereRadius(Ogre::Math::Sqrt(std::max(currentMin.squaredLength(), currentMax.squaredLength())));
		}
		else
		{
			boundingBoxMin.makeFloor(currentMin);
			boundingBoxMax.makeCeil(currentMax);
		}
		
		for (unsigned int s = 0; s < object->surfaces.size(); s++)
		{
			lwSurface *surface = object->surfaces[s];
			
			points.clear();
			polygons.clear();
			vmaps.clear();
			
			unsigned int l = ol;

			for( unsigned int il = 0; il < ml; ++il )
			{
				if (!SeparateLayers) l = il;

				copyPoints(s, ID_FACE, object->layers[l]->points, points);
				copyPolygons(s, ID_FACE, object->layers[l]->polygons, polygons);
				getTextureVMaps(surface->color.textures, object->layers[l]->vmaps, vmaps);

				if (SeparateLayers) break;
			}

			if (!polygons.size()) continue; 			
			
			SubMesh *ogreSubMesh = ogreMesh->createSubMesh();
			ogreSubMesh->useSharedVertices = flags[UseSharedVertexData] && points.size() < POINTLIMIT;

			ogreSubMesh->indexData->indexCount = polygons.size() * 3;
			ogreSubMesh->indexData->indexBuffer = HardwareBufferManager::getSingleton().createIndexBuffer(HardwareIndexBuffer::IT_16BIT, ogreSubMesh->indexData->indexCount, HardwareBuffer::HBU_STATIC_WRITE_ONLY);
			ogreSubMesh->setMaterialName(surface->name);
			

			if (ogreSubMesh->useSharedVertices)
			{
				unsigned short vertexDataOffset = 0;
				if (ogreMesh->sharedVertexData) vertexDataOffset = ogreMesh->sharedVertexData->vertexCount;
				ogreMesh->sharedVertexData = setupVertexData(points.size(), ogreMesh->sharedVertexData);
				copyDataToVertexData(points, polygons, vmaps, ogreSubMesh->indexData, ogreMesh->sharedVertexData, vertexDataOffset);
			}
			else
			{
				ogreSubMesh->vertexData = setupVertexData(points.size());
				copyDataToVertexData(points, polygons, vmaps, ogreSubMesh->indexData, ogreSubMesh->vertexData);
			}
		}

		if (!SeparateLayers)
		{	
			ogreMesh->_setBounds(Ogre::AxisAlignedBox(boundingBoxMin, boundingBoxMax));
			ogreMesh->_setBoundingSphereRadius(Ogre::Math::Sqrt(std::max(boundingBoxMin.squaredLength(), boundingBoxMax.squaredLength())));
		}
		
		String fname = SeparateLayers ? makeLayerFileName(dest, ol, object->layers[ol]->name) : dest;

		Skeleton *skeleton = 0;

		if (flags[ExportSkeleton])
			if (SeparateLayers)
				skeleton = doExportSkeleton(fname, ol);
			else
				if (!ol) skeleton = doExportSkeleton(fname, -1);

		if (flags[GenerateLOD])
		{
			ProgressiveMesh::VertexReductionQuota quota;

			if (flags[UseFixedMethod])
				quota = ProgressiveMesh::VRQ_CONSTANT;
			else
				quota = ProgressiveMesh::VRQ_PROPORTIONAL;
					
			ogreMesh->generateLodLevels(distanceList, quota, reduction);
		}

		if (flags[GenerateEdgeLists])
		{
			ogreMesh->buildEdgeList();
		}

		if (flags[GenerateTangents])
		{
			ogreMesh->buildTangentVectors();
		}
		
		try
		{
			meshserializer.exportMesh(ogreMesh.getPointer(), fname);
		}
		catch (...)
		{
			cout << "Could not export to file: " << fname << endl;
		}

		ogreMesh->unload();

		Ogre::MeshManager::getSingleton().remove(ogreMesh->getHandle());
		if (flags[ExportSkeleton] && skeleton) delete skeleton;

		if (!SeparateLayers) break;
	}

	return true;
}
