/*=========================================================================

	EXPMODEL.CPP

	Author:  Tim Swann @ CLIMAX
	Created:
	Project:
	Purpose:

	Copyright (c) 1998 Climax Development Ltd

===========================================================================*/

/*----------------------------------------------------------------------
	Includes
	-------- */

#include "AsciiExp.h"


/*	Std Lib
	------- */

/*	Glib
	---- */

/*	Local
	----- */

/*	Graphics
	-------- */

/*----------------------------------------------------------------------
	Tyepdefs && Defines
	------------------- */

/*----------------------------------------------------------------------
	Structure defintions
	-------------------- */

/*----------------------------------------------------------------------
	Positional Vars
	--------------- */

/*----------------------------------------------------------------------
	Function Prototypes
	------------------- */

/*----------------------------------------------------------------------
	Vars
	---- */

/*----------------------------------------------------------------------
	Data
	---- */

/*----------------------------------------------------------------------
	Function:	
	Purpose:
	Params:
	Returns:
  ---------------------------------------------------------------------- */

void AsciiExp::ExportModel(INode* node)
{
	ObjectState os = node->EvalWorldState( ip->GetAnimRange().Start() );
	char nodeName[256];

	// Targets are actually geomobjects, but we will export them
	// from the camera and light objects, so we skip them here.
	if (os.obj->ClassID() == Class_ID(TARGET_CLASS_ID, 0))		return;
	ip->ProgressUpdate(0,0,"Export Model0");

	sprintf(nodeName, "%s", node->GetName());
	strupr(nodeName);
	if (nodeName[0] != 'R' ||
		nodeName[1] != 'O' ||
		nodeName[2] != 'O' ||
		nodeName[3] != 'T')
	{
	ip->ProgressUpdate(0,0,"Export ModelRoot");
		WriteChunkHdr( (char*)OBJ_ID, 0 );
		fwrite(&totFrames, sizeof(int), 1, expStream);
		fwrite(nodeName, MAX_NAME_LENGTH, 1, expStream);

	ip->ProgressUpdate(0,0,"Export Model-Mesh");
		ExportMesh( node );
	}
	
}


/*----------------------------------------------------------------------
	Function:	
	Purpose:
	Params:
	Returns:
  ---------------------------------------------------------------------- */

void AsciiExp::ExportMesh( INode* node )
{

	Matrix3		tm = node->GetObjTMAfterWSM( ip->GetAnimRange().Start() );
	Uint32		EvalFlags = 0;
	Point3		ObjPos;
	Point3		ObjRot;
	Point3		ObjSca;
	Point3      center = Point3(0.f, 0.f, 0.f);
	AffineParts	ap;
	Mesh *		mesh;
	ObjectState	os;
	Object *	obj;
	BOOL		needDel;
	TriObject *	tri;

	os = node->EvalWorldState( ip->GetAnimRange().Start() );
	obj = node->EvalWorldState( ip->GetAnimRange().Start() ).obj;

	Mtl *		nodeMtl = node->GetMtl();
	if (!nodeMtl) return;

	if (!os.obj)				return;
	if (!obj->IsRenderable())	return;
	if (node->IsNodeHidden())	return;
	if (!obj || os.obj->SuperClassID() != GEOMOBJECT_CLASS_ID)	return; // Safety net. This shouldn't happen.

	tri = GetTriObjectFromNode(node, ip->GetAnimRange().Start(), needDel);
	if (!tri)	return;

	mesh = &tri->mesh;

	mtlList.AddMtl(nodeMtl);

	ip->ProgressUpdate(0,0,"ExportMeshList");
	ExportMeshList( node, mesh );
	ip->ProgressUpdate(0,0,"ExportPoints");
	ExportPoints( node, mesh );
	ip->ProgressUpdate(0,0,"ExportVectors");
	ExportVectors( node, mesh);
	ip->ProgressUpdate(0,0,"ExportVectorsDone");

	if (needDel)	delete tri;

	tri = GetTriObjectFromNode(node, ip->GetAnimRange().End(), needDel);
	if (!tri)	return;

	mesh = &tri->mesh;
	ip->ProgressUpdate(0,0,"ExportNormals");
	ExportNormals( node, mesh );
	if (needDel)	delete tri;
	ip->ProgressUpdate(0,0,"ExportNormalsDone");

	tm = node->GetObjTMAfterWSM(ip->GetAnimRange().Start());// * Inverse(node->GetParentTM(t));
	decomp_affine(tm, &ap);

	ip->ProgressUpdate(0,0,"WriteHeader0");
	WriteChunkHdr( (char*)MODEL_ID, 0 );
	fwrite( &nCurObj, sizeof(long), 1, expStream );
char	NodeName[MAX_NAME_LENGTH];
	sprintf(NodeName,"%s",node->GetName());
//	fwrite( node->GetName(), MAX_NAME_LENGTH, 1, expStream);
	fwrite( NodeName, MAX_NAME_LENGTH, 1, expStream);
	fwrite( &radius, sizeof(float), 1, expStream );
	fwrite( &center.x, sizeof(float), 1, expStream );
	fwrite( &center.y, sizeof(float), 1, expStream );
	fwrite( &center.z, sizeof(float), 1, expStream );
	fwrite( &ap.t.x, sizeof(float), 1, expStream );
	fwrite( &ap.t.z, sizeof(float), 1, expStream );
	fwrite( &ap.t.y, sizeof(float), 1, expStream );
//ProgressUpdate(int pct, BOOL showPct = TRUE, TCHAR *title = NULL)=0

	ip->ProgressUpdate(0,0,"WriteHeaderDone");
	
	ip->ProgressUpdate(0,0,"FindPhysiqueModifier");
	if (FindPhysiqueModifier(node)) 
		{
		ip->ProgressUpdate(0,0,"ExportWeights");
		ExportWeights(node);
		} 
	else 
	ip->ProgressUpdate(0,0,"FindFFDModifier");
	if (FindFFDModifier(node)) 
		{
		} 
	else
		{
		ip->ProgressUpdate(0,0,"ExportNodeAnimMesh");
		ExportNodeAnimMesh( node );
		}
	
//	ExportAnimMesh( node );
	ip->ProgressUpdate(0,0,"FindEditableMesh");
	FindEditableMesh(node);
	ip->ProgressUpdate(0,0,"ExportVisTrack");
	ExportVisTrack( node );
	nCurObj++;
}

void AsciiExp::FindEditableMesh(INode *node) 
{
	Object *o = node->GetObjectRef();
	while (o && o->SuperClassID() == GEN_DERIVOB_CLASS_ID) 
		{
		o = ((IDerivedObject*)o)->GetObjRef();
		}
	if (o && o->ClassID() == Class_ID(EDITTRIOBJ_CLASS_ID,0)) 
		{
		ExportMeshVertAnim(node, (EditTriObject*)o);
		}
}

void AsciiExp::ExportMeshVertAnim(INode *node, EditTriObject *e)
{
	TimeValue	start = ip->GetAnimRange().Start();
	TimeValue	end = ip->GetAnimRange().End();
	int			delta = GetTicksPerFrame();

	int Controls = e->cont.Count();

	
	Matrix3		tm = node->GetObjTMAfterWSM( ip->GetAnimRange().Start() );
	Point3      p;

	Controls = 0;
	for (int i=0; i<e->cont.Count(); i++) {
		if (e->cont[i]) {
			Controls++;
		}
	}

	ITCBPoint3Key tcbPosKey;
	IBezPoint3Key bezPosKey;

	WriteChunkHdr( (char*)KEY_ID, 0);
	fwrite( &CHANNEL_TYPE_VERTEX, sizeof( Uint32 ), 1, expStream );
	fwrite( &CHANNEL_INFO_POSITION, sizeof( Uint32) , 1, expStream);
	fwrite( &nCurObj, sizeof( Uint32 ), 1, expStream );
	fwrite( &Controls, sizeof(int), 1, expStream);

	for (i=0; i<e->cont.Count(); i++) {
		if (e->cont[i]) {
			Control *c = GetControlInterface(e->cont[i]);
			IKeyControl *ikeys;
			TimeList tl;
			TimeValue time;
			tl.Clear();
			if (c) {
				ikeys = GetKeyControlInterface(c);
				int num = ikeys->GetNumKeys();
				for (int j = 0; j<num; j++) 
					{
					if (c->ClassID() == Class_ID(TCBINTERP_POINT3_CLASS_ID, 0)) {
						ikeys->GetKey(j, &tcbPosKey);
						time = tcbPosKey.time;
					} 
					else 
					if (c->ClassID() == Class_ID(HYBRIDINTERP_POINT3_CLASS_ID, 0)) 
					{
						ikeys->GetKey(j, &bezPosKey);
						time = bezPosKey.time;
					}
					tl.Add(time);
				}
				num = tl.Count();
				fwrite(&i, sizeof(int), 1, expStream);
				fwrite(&num, sizeof(int), 1, expStream);
				for (j=0; j<num; j++) {
					time = tl.Get(j);
					tm = node->GetObjTMAfterWSM(time);
					e->cont[i]->GetValue(time,&p,FOREVER);
					p  = tm * p;
					time = (time-start) / delta;
					fwrite(&time, sizeof(int), 1, expStream);
					fwrite(&p.x, sizeof(float), 1, expStream);
					fwrite(&p.z, sizeof(float), 1, expStream);
					fwrite(&p.y, sizeof(float), 1, expStream);
				}
			}
		}
	}
}

/*----------------------------------------------------------------------
  ---------------------------------------------------------------------- */
int  GetSubMtlCount(Mtl* mtl, bool recurse = false)
{
	int Count = 0;
	if (!mtl)
		return 0;
	if (mtl->ClassID() == Class_ID(DMTL_CLASS_ID, 0))
		Count ++;

	if (mtl->NumSubMtls() > 0)
	{
		int c = (recurse) ? 1 : mtl->NumSubMtls();
		for (int i=0; i<c; i++)
		{
			Mtl* subMtl = mtl->GetSubMtl(i);
			if (subMtl)
			{
				Count += GetSubMtlCount( subMtl, true);
			}
		}
	}
	return Count;
}

void AsciiExp::ExportMeshList(INode * node, Mesh * mesh)
{
	MtlID		matid;
	MtlID		matref;
	Mtl *		nodeMtl = node->GetMtl();
	int			m;
	ObjectState	os;
	Object *	obj;
	Matrix3		tm = node->GetObjTMAfterWSM( ip->GetAnimRange().Start() );
	BOOL		negScale = TMNegParity(tm);
	int			vx1, vx2, vx3;
	const char * name = node->GetName();


//	fprintf( tempStream, "MESHLIST = %s\n\n", name );

	obj = node->EvalWorldState( ip->GetAnimRange().Start() ).obj;
	mesh->buildNormals();

	// Order of the vertices. Get 'em counter clockwise if the objects is
	// negatively scaled.
	if (negScale)
	{
		vx1 = 2;
		vx2 = 1;
		vx3 = 0;
	}
	else
	{
		vx1 = 0;
		vx2 = 1;
		vx3 = 2;
	}

	int MtlCount = GetSubMtlCount(nodeMtl);
	if (MtlCount == 0) return;
	// Find all mesh materials used
	nbMeshs = 0;
	for (int i=0; i<mesh->getNumFaces(); i++)
	{
		Bool found = FALSE;

		matid = ((unsigned long)mesh->getFaceMtlIndex(i)) % MtlCount;

//		fprintf( tempStream, "MATID = %d - FACEID = %d\n", mesh->faces[i].getMatID(), mesh->getFaceMtlIndex(i) );

		for (int f=0;f<nbMeshs;f++)
		{
			if (matid == MeshSet[f].MatId)
			{
				MeshSet[f].nbFaces++;
				found = TRUE;
				break;
			}
		}
		if (!found)
		{
			MeshSet[nbMeshs].MatId = matid;
			MeshSet[nbMeshs].nbFaces = 1;
			nbMeshs++;
			if(nbMeshs>= MAXCOUNT) MessageBox( MAX_hWnd, "nbMeshs Overflow", "ERROR", MB_OK);

		}
	}


	// Sort material id's for ease of access

	for (m=0;m<nbMeshs;m++)
	{
		for (int m2=0;m2<(nbMeshs-1);m2++)
		{
			if (MeshSet[m2].MatId > MeshSet[m2+1].MatId)
			{
				MESH_SET	meshset = MeshSet[m2];
				MeshSet[m2] = MeshSet[m2+1];
				MeshSet[m2+1] = meshset;
			}
		}
	}


	for (m=0;m<nbMeshs;m++)
	{
		int		nbFaces;

		// EXPORT POLY CONNECT LIST - PER MESH
		WriteChunkHdr( (char*)POLY_ID, 0 );
		fwrite( &nCurObj, sizeof(Uint16), 1, expStream );
		fwrite( &m, sizeof(Uint16), 1, expStream );
		nbFaces = MeshSet[m].nbFaces;
		fwrite( &nbFaces, sizeof(Uint32), 1, expStream );

		for (i=0; i<mesh->getNumFaces(); i++)
		{
			matid = ((unsigned long)mesh->getFaceMtlIndex(i)) % MtlCount;
			if (matid == MeshSet[m].MatId)
			{
				fwrite( &mesh->faces[i].v[vx1], sizeof(long), 1, expStream );
				fwrite( &mesh->faces[i].v[vx2], sizeof(long), 1, expStream );
				fwrite( &mesh->faces[i].v[vx3], sizeof(long), 1, expStream );
long		HF0=mesh->faces[i].getEdgeVis(vx1);
long		HF1=mesh->faces[i].getEdgeVis(vx2);
long		HF2=mesh->faces[i].getEdgeVis(vx3);
				fwrite( &HF0, sizeof(long), 1, expStream );
				fwrite( &HF1, sizeof(long), 1, expStream );
				fwrite( &HF2, sizeof(long), 1, expStream );
//		fprintf( tempStream, "Face = %d - HF= %d %d %d\n", i,HF0,HF1,HF2);
			}
		}


		// EXPORT FACE MAP TEXCOORDS IF WE HAVE THEM...
		if (obj->HasUVW())
		{
			if (!CheckForAndExportFaceMap(nodeMtl, mesh, m))
			{
				// If not, export standard tverts
				int numTVx = mesh->getNumTVerts();

				if (numTVx)
				{
					int		nbFaces;

					WriteChunkHdr( (char*)UV_ID, 0 );
					fwrite( &nCurObj, sizeof(Uint16), 1, expStream );
					fwrite( &m, sizeof(Uint16), 1, expStream );
					nbFaces = MeshSet[m].nbFaces;
					fwrite( &nbFaces, sizeof(Uint32), 1, expStream );

					for (i=0; i<mesh->getNumFaces(); i++)
					{
						matid = ((unsigned long)mesh->getFaceMtlIndex(i)) % MtlCount;

						if (matid == MeshSet[m].MatId)
						{
							UVVert		tv;
							int			idx;

							idx = mesh->tvFace[i].t[vx1];
							tv = mesh->tVerts[idx];
							fwrite(&tv.x, sizeof(float), 1, expStream);
							fwrite(&tv.y, sizeof(float), 1, expStream);

							idx = mesh->tvFace[i].t[vx2];
							tv = mesh->tVerts[idx];
							fwrite(&tv.x, sizeof(float), 1, expStream);
							fwrite(&tv.y, sizeof(float), 1, expStream);

							idx = mesh->tvFace[i].t[vx3];
							tv = mesh->tVerts[idx];
							fwrite(&tv.x, sizeof(float), 1, expStream);
							fwrite(&tv.y, sizeof(float), 1, expStream);
						}
					}
				}
			}
		}
		{
			int		nbFaces;

			WriteChunkHdr( (char*)FACENORMAL_ID, 0 );
			fwrite( &nCurObj, sizeof(Uint16), 1, expStream );
			fwrite( &m, sizeof(Uint16), 1, expStream );
			nbFaces = MeshSet[m].nbFaces;
			fwrite( &nbFaces, sizeof(Uint32), 1, expStream );

			for (i=0; i<mesh->getNumFaces(); i++)
			{
				matid = ((unsigned long)mesh->getFaceMtlIndex(i)) % MtlCount;
				if (matid == MeshSet[m].MatId)
				{
					Point3 n=GetVertexNormal(mesh, i,mesh->getRVertPtr(mesh->faces[i].v[vx1]));
					fwrite(&n.x, sizeof(float), 1, expStream);
					fwrite(&n.y, sizeof(float), 1, expStream);
					fwrite(&n.z, sizeof(float), 1, expStream);
					
					n=GetVertexNormal(mesh, i,mesh->getRVertPtr(mesh->faces[i].v[vx2]));
					fwrite(&n.x, sizeof(float), 1, expStream);
					fwrite(&n.y, sizeof(float), 1, expStream);
					fwrite(&n.z, sizeof(float), 1, expStream);

					n=GetVertexNormal(mesh, i,mesh->getRVertPtr(mesh->faces[i].v[vx3]));
					fwrite(&n.x, sizeof(float), 1, expStream);
					fwrite(&n.y, sizeof(float), 1, expStream);
					fwrite(&n.z, sizeof(float), 1, expStream);
				}
			}
		}
//		tris[result].VertNormals[s] = Normalize(transpose * (m * get_vertex_normal(&triobj->mesh, j, triobj->mesh.getRVertPtr(fces[j].v[s]))));
		if (mesh->getNumVertCol())
		{
			Point3 vc;
			WriteChunkHdr( (char*)VCOL_ID, 0 );
			fwrite( &nCurObj, sizeof(Uint32), 1, expStream );
			fwrite( &m      , sizeof(Uint32), 1, expStream );
			int nbFaces = MeshSet[m].nbFaces;
			fwrite( &nbFaces, sizeof(Uint32), 1, expStream );
			for (i=0; i<mesh->getNumFaces(); i++)
			{
				matid = ((unsigned long)mesh->getFaceMtlIndex(i)) % MtlCount;

				if (matid == MeshSet[m].MatId)
				{
					TVFace *f = &mesh->vcFace[i];
					int vert = f->t[vx1];
					vc = mesh->vertCol[vert];
					fwrite(&vc.x, sizeof(float), 1, expStream);
					fwrite(&vc.y, sizeof(float), 1, expStream);
					fwrite(&vc.z, sizeof(float), 1, expStream);

					vert = f->t[vx2];
					vc = mesh->vertCol[vert];
					fwrite(&vc.x, sizeof(float), 1, expStream);
					fwrite(&vc.y, sizeof(float), 1, expStream);
					fwrite(&vc.z, sizeof(float), 1, expStream);

					vert = f->t[vx3];
					vc = mesh->vertCol[vert];
					fwrite(&vc.x, sizeof(float), 1, expStream);
					fwrite(&vc.y, sizeof(float), 1, expStream);
					fwrite(&vc.z, sizeof(float), 1, expStream);
				}
			}
		}
		
	}


	// EXPORT MESHSET INFO
	WriteChunkHdr( (char*)MESH_ID, 0 );
	fwrite( &nCurObj, sizeof(long), 1, expStream );
	fwrite( &nbMeshs, sizeof(long), 1, expStream );

	int NodeMatRef = mtlList.GetMtlID(nodeMtl);
	for (m=0;m<nbMeshs;m++)
	{
		matref = MeshSet[m].MatId + NodeMatRef;
//		fprintf( tempStream, "matIdx = %d %d\n", MeshSet[m].MatId, matref );

		fwrite(&matref, sizeof(Uint16), 1, expStream);
		fwrite(&MeshSet[m].nbFaces, sizeof(Uint16), 1, expStream);

		Uint32 TempStore;
		TempStore = m;
		fwrite(&TempStore, sizeof(Uint32), 1, expStream);	 // polt list
		TempStore = -1;
		fwrite(&TempStore, sizeof(Uint32), 1, expStream);    // attr   list
		fwrite(&TempStore, sizeof(Uint32), 1, expStream);	 // normal list
		TempStore = -1;
		if (!mesh->vertCol)
			TempStore = m;
		fwrite(&TempStore, sizeof(Uint32), 1, expStream);	 // vertex col list
		TempStore = -1;
		if (obj->HasUVW())
			TempStore = m;
		fwrite(&TempStore, sizeof(Uint32), 1, expStream);	 // texture list
	}
}


/*----------------------------------------------------------------------
  ---------------------------------------------------------------------- */

BOOL AsciiExp::CheckForAndExportFaceMap(Mtl* mtl, Mesh* mesh, int meshno)
{
	MtlID	matid;
	ULONG	matreq;

	return FALSE;

	if (!mtl || !mesh)		return FALSE;

	matreq = mtl->Requirements(-1);

	// ARE WE USING FACE MAPPING?
	if (!(matreq & MTLREQ_FACEMAP))		return FALSE;


	// OK, WE HAVE A FACEMAP SITUATION HERE...

	WriteChunkHdr( (char*)UV_ID, 0 );
	fwrite( &nCurObj, sizeof(Uint16), 1, expStream );
	fwrite( &meshno, sizeof(Uint16), 1, expStream );
	Uint32 mnf = MeshSet[meshno].nbFaces;
	fwrite( &mnf, sizeof(Uint32), 1, expStream );

	for (int i=0; i<mesh->getNumFaces(); i++)
	{
		matid = mesh->getFaceMtlIndex(i);//faces[i].getMatID();

		if (matid == MeshSet[meshno].MatId)
		{
			Point3		tv[3];
			Face *		f;
//			NJS_COLOR	uv;
			
			f = &mesh->faces[i];
			make_face_uv( f, tv );

			fwrite(&tv[0].x, sizeof(float), 1, expStream);
			fwrite(&tv[0].y, sizeof(float), 1, expStream);
			fwrite(&tv[1].x, sizeof(float), 1, expStream);
			fwrite(&tv[1].y, sizeof(float), 1, expStream);
			fwrite(&tv[2].x, sizeof(float), 1, expStream);
			fwrite(&tv[2].y, sizeof(float), 1, expStream);
		}
	}

	return TRUE;
}


/*----------------------------------------------------------------------
  ---------------------------------------------------------------------- */

void AsciiExp::ExportPoints(INode * node, Mesh * mesh)
{
	Matrix3		tm = node->GetObjTMAfterWSM( ip->GetAnimRange().Start() );
	Point3		Trans;
	int			nbVerts;
	
	radius = 0.f;
	nbVerts = mesh->getNumVerts();

	// EXPORT MODEL POINTS
	WriteChunkHdr( (char*)POINTS_ID, 0 );
	fwrite( &nCurObj, sizeof(Uint32), 1, expStream );
	fwrite( &nbVerts, sizeof(Uint32), 1, expStream );

	for (int i=0; i<nbVerts; i++)
	{
		Point3 v1;
		Point3 v2;

		tm = node->GetObjTMAfterWSM( ip->GetAnimRange().Start() );
		v1 = mesh->verts[i];
		//v2 = VectorTransform( tm, v1 );
		//v2 += tm.GetTrans();
		v2 = tm * v1;
		Trans = v2;

		//Trans.y = Trans.y;
		fwrite( &Trans.x, sizeof(float), 1, expStream );
		fwrite( &Trans.z, sizeof(float), 1, expStream );
		fwrite( &Trans.y, sizeof(float), 1, expStream );

		if (radius < fabs(Trans.x))	radius = (float)fabs(Trans.x);
		if (radius < fabs(Trans.y))	radius = (float)fabs(Trans.y);
		if (radius < fabs(Trans.z))	radius = (float)fabs(Trans.z);
	}
}

void AsciiExp::ExportVectors(INode * node, Mesh * mesh)
{
	Matrix3		tm = node->GetObjTMAfterWSM( ip->GetAnimRange().Start() );
	Point3		Trans;
	int			nbVerts;
	
	radius = 0.f;
	nbVerts = mesh->getNumVerts();

	// EXPORT MODEL POINTS
	WriteChunkHdr( (char*)VECT_ID, 0 );
	fwrite( &nCurObj, sizeof(Uint32), 1, expStream );
	fwrite( &nbVerts, sizeof(Uint32), 1, expStream );

	for (int i=0; i<nbVerts; i++)
	{
		Point3 v1;

		v1 = mesh->verts[i];

		fwrite( &v1.x, sizeof(float), 1, expStream );
		fwrite( &v1.z, sizeof(float), 1, expStream );
		fwrite( &v1.y, sizeof(float), 1, expStream );
	}
}

/*----------------------------------------------------------------------
  ---------------------------------------------------------------------- */

void AsciiExp::ExportNormals(INode * node, Mesh * mesh)
{
	int			nbVerts;
	Point3 *	vnorms;
	Point3		fn;  // Face normal
	Point3		vn;  // Vertex normal

	int			i;
	Matrix3		tm = node->GetObjTMAfterWSM( ip->GetAnimRange().End() );
	BOOL		negScale = TMNegParity(tm);
	int			vx1, vx2, vx3;


	if (negScale)
	{
		vx1 = 2;
		vx2 = 1;
		vx3 = 0;
	}
	else
	{
		vx1 = 0;
		vx2 = 1;
		vx3 = 2;
	}

	
	mesh->buildNormals();

	nbVerts = mesh->getNumVerts();
	vnorms = new Point3[nbVerts];

	for (i=0; i<mesh->getNumVerts(); i++)
	{
		vnorms[i] = GetAverageVertNormal(node, mesh, i);
	}


	// EXPORT MODEL NORMALS
	WriteChunkHdr( (char*)NORMAL_ID, 0 );
	fwrite( &nCurObj, sizeof(Uint32), 1, expStream );
	fwrite( &nbVerts, sizeof(Uint32), 1, expStream );

	for (i=0; i<nbVerts; i++)
	{
		vn = vnorms[i];
		fwrite( &vn.x, sizeof(float), 1, expStream );
		fwrite( &vn.z, sizeof(float), 1, expStream );
		fwrite( &vn.y, sizeof(float), 1, expStream );
	}

	delete vnorms;
}

/*----------------------------------------------------------------------
	Function:	
	Purpose:
	Params:
	Returns:
  ---------------------------------------------------------------------- */

Point3 AsciiExp::GetVertexNormal(Mesh* mesh, int faceNo, RVertex* rv)
{
	Face* f = &mesh->faces[faceNo];
	DWORD smGroup = f->smGroup;
	int numNormals;
	Point3 vertexNormal;

	// Is normal specified
	// SPCIFIED is not currently used, but may be used in future versions.
	if (rv->rFlags & SPECIFIED_NORMAL)
	{
//		fprintf( pStream, "SPECIFIED_NORMAL\n");
		vertexNormal = rv->rn.getNormal();
	}
	// If normal is not specified it's only available if the face belongs
	// to a smoothing group
	else if ((numNormals = rv->rFlags & NORCT_MASK) && smGroup)
	{
		// If there is only one vertex is found in the rn member.
		if (numNormals == 1)
		{
//			fprintf( pStream, "SINGLE SMOOTHING GROUP\n");
			vertexNormal = rv->rn.getNormal();
		}
		else
		{
//			fprintf( pStream, "SMOOTHING GROUP %d\n", numNormals);

			// If two or more vertices are there you need to step through them
			// and find the vertex with the same smoothing group as the current face.
			// You will find multiple normals in the ern member.
			for (int i = 0; i < numNormals; i++)
			{
				vertexNormal = rv->ern[i].getNormal();
			}
		}
	}
	else
	{
//		fprintf( pStream, "NO SMOOTHING GROUP\n");
		// Get the normal from the Face if no smoothing groups are there
		vertexNormal = mesh->getFaceNormal(faceNo);
	}

	return vertexNormal;
}

Point3 AsciiExp::GetAverageVertNormal(INode *node, Mesh* mesh, int VertNo)
{
	int i;
	int c=0;
	Matrix3 tm = node->GetNodeTM(ip->GetAnimRange().Start());
	tm.NoTrans();
	Point3 n= Point3(0.f,0.f,0.f);
	for (i=0; i<mesh->getNumFaces(); i++) {
		if (mesh->faces[i].v[0] == VertNo || mesh->faces[i].v[1] == VertNo || mesh->faces[i].v[2] == VertNo)
		{
			Point3 p0 = tm * mesh->verts[mesh->faces[i].v[0]];
			Point3 p1 = tm * mesh->verts[mesh->faces[i].v[1]];
			Point3 p2 = tm * mesh->verts[mesh->faces[i].v[2]];
			Point3 Nrm = Normalize(CrossProd(p0 - p1, p0 - p2));
			n += Nrm;
			c ++;
		}
	}
	n /= (float)c;
	return Normalize(n);
}

void AsciiExp::ExportNodeAnimMesh( INode* node )
{
	INode *parent;
	Matrix3 parentTM, nodeTM, localTM;
	parent = node->GetParentNode();

	TimeValue	start = ip->GetAnimRange().Start();
	TimeValue	end = ip->GetAnimRange().End();
	int			delta = GetTicksPerFrame();
	TimeValue	t;
	Matrix3		tm;
	Matrix3		StartM;
	AffineParts	ap;
	int			i;
	Quat		q;
	Quat		StartQ, StartU;
	Point3		StartP, StartS;

	nodeTM = node->GetNodeTM(start);
	parentTM = parent->GetNodeTM(start);
	StartM = nodeTM*Inverse(parentTM);

	for (t=start; t<=end; t+=delta) 
		{
		nodeTM = node->GetNodeTM(t);
		parentTM = parent->GetNodeTM(t);
		tm = nodeTM*Inverse(parentTM);
		for (i=0;i<4;i++) {
			Point3 m0 = StartM.GetRow(i);
			Point3 m1 = tm.GetRow(i);
			if (fabs(m0.x-m1.x)>0.001 ||
				fabs(m0.y-m1.y)>0.001 ||
				fabs(m0.z-m1.z)>0.001)
				break;

		}
		if (i!=4)
			break;
	}
	if (t==end)
		return;

	char text[250];
	sprintf(text,"%s", node->GetName());

	WriteChunkHdr( (char*)ANIM_ID, 0);
	fwrite( &CHANNEL_TYPE_NODE, sizeof( Uint32 ), 1, expStream );
	fwrite( &nCurObj, sizeof( Uint32 ), 1, expStream );
	fwrite( &totFrames, sizeof( Uint32 ), 1, expStream );
	for (t=start; t<=end; t+=delta) {
		// TRANSLATION
		tm = node->GetNodeTM(t) * Inverse(node->GetParentTM(t));
		decomp_affine(tm, &ap);

		fwrite( &ap.t.x, sizeof(float), 1, expStream );				// WRITE BONE X-POS
		fwrite( &ap.t.z, sizeof(float), 1, expStream );				// WRITE BONE Y-POS
		fwrite( &ap.t.y, sizeof(float), 1, expStream );				// WRITE BONE Z-POS

		fwrite( &ap.q.x, sizeof(float), 1, expStream );				// WRITE BONE X-POS
		fwrite( &ap.q.z, sizeof(float), 1, expStream );				// WRITE BONE Y-POS
		fwrite( &ap.q.y, sizeof(float), 1, expStream );				// WRITE BONE Z-POS
		fwrite( &ap.q.w, sizeof(float), 1, expStream );				// WRITE BONE Z-POS

		fwrite( &ap.k.x, sizeof(float), 1, expStream );				// WRITE BONE X-POS
		fwrite( &ap.k.z, sizeof(float), 1, expStream );				// WRITE BONE Y-POS
		fwrite( &ap.k.y, sizeof(float), 1, expStream );				// WRITE BONE Z-POS

		fwrite( &ap.u.x, sizeof(float), 1, expStream );				// WRITE BONE X-POS
		fwrite( &ap.u.z, sizeof(float), 1, expStream );				// WRITE BONE Y-POS
		fwrite( &ap.u.y, sizeof(float), 1, expStream );				// WRITE BONE Z-POS
		fwrite( &ap.u.w, sizeof(float), 1, expStream );				// WRITE BONE Z-POS
	}
}

void AsciiExp::ExportVisTrack( INode* node )
{
	Control *VisControl = node->GetVisController();
	if (VisControl) {
		float VisF;
		TimeValue	start = ip->GetAnimRange().Start();
		TimeValue	end = ip->GetAnimRange().End();
		int			delta = GetTicksPerFrame();
		TimeValue	t;
		WriteChunkHdr( (char*)VIS_ID, 0);
		fwrite( &nCurObj, sizeof( Uint32 ), 1, expStream );
		fwrite( &totFrames, sizeof( Uint32 ), 1, expStream );
		for (t=start; t<=end; t+=delta) {
			Interval I = Interval(t, t);
			VisControl->GetValue(t, &VisF, I, CTRL_ABSOLUTE);
			fwrite( &VisF, sizeof(float), 1, expStream);
		}
	}

}

void AsciiExp::ExportAnimMesh( INode* node )
{
	TimeValue	start = ip->GetAnimRange().Start();
	TimeValue	end = ip->GetAnimRange().End();
	int			delta = GetTicksPerFrame();
	BOOL		needDel;
	Mesh		*mesh;
	TriObject	*tri;
	TimeValue	t;

	WriteChunkHdr( (char*)ANIM_ID, 0);
	fwrite( &CHANNEL_TYPE_VERTEX, sizeof( Uint32 ), 1, expStream );
	fwrite( &nCurObj, sizeof( Uint32 ), 1, expStream );
	fwrite( &totFrames, sizeof( Uint32 ), 1, expStream );
	for (t=start; t<=end; t+=delta) 
		{
		tri = GetTriObjectFromNode(node, t, needDel);
		if (!tri)	return;
		mesh = &tri->mesh;
		Matrix3 tm = node->GetObjTMAfterWSM( t );
		for (int i=0; i<mesh->getNumVerts(); i++)
			{
			Point3 v1;
			Point3 v2;

			v1 = mesh->verts[i];
			v2 = tm * v1;

			fwrite( &v2.x, sizeof(float), 1, expStream );
			fwrite( &v2.z, sizeof(float), 1, expStream );
			fwrite( &v2.y, sizeof(float), 1, expStream );
			}
		if (needDel) tri->DeleteThis();
	}
}

/*===========================================================================
 end */