/**********************************************************************
 *<
	FILE: miniexp.cpp

	DESCRIPTION:	Appwizard generated plugin

	CREATED BY: 

	HISTORY: 

 *>	Copyright (c) 2000, All Rights Reserved.
 **********************************************************************/

#include "miniexp.h"
#include "stdmat.h"

#define MINIEXP_CLASS_ID	Class_ID(0x5bffb56, 0x320aefb2)


void __cdecl Report3D(const char *pFormat,... )
{
	char debugText[2048];
	va_list	parameter;
	va_start(parameter,pFormat);
	vsprintf(debugText,pFormat,parameter);
	OutputDebugString(debugText);
}

struct SuperFile
{
	FILE *f;
	bool writing;
	int chunksize;
	int chunkstart;
	int inchunk;

	bool Open(char *fname,bool forwrite);
	void Close();

	int ReadNextChunk(int &id, int &ver);
	
	int StartChunk(int id, int ver);
	void EndChunk();
	void Write(void *data, int size);
	void Read(void *data, int size);

	SuperFile()
	{
		inchunk=0;
		f=NULL;
		writing=0;
		chunksize=chunkstart=0;	
	}

};

bool SuperFile::Open(char *fname, bool forwrite)
{
	if (f) Close();
	writing=forwrite;
	f=fopen(fname,forwrite?"wb":"rb");
	return f!=NULL;
}

void SuperFile::Close()
{
	if (f) fclose(f);
	f=NULL;
}

int SuperFile::StartChunk(int id, int ver)
{
	if (writing && f)
	{	
		if (inchunk) EndChunk();
		inchunk=1;
		chunksize=0;
		chunkstart=ftell(f);		
		Write(&id,4);
		Write(&ver,4);
		Write(&chunksize,4);
	}
	return 0;
}

void SuperFile::EndChunk()
{
	if (f && inchunk)
	{
		inchunk=0;
		if (writing)
		{
			int cur=ftell(f);
			chunksize=cur-chunkstart;
			fseek(f,chunkstart+8,SEEK_SET);
			fwrite(&chunksize,1,4,f);
			fseek(f,cur,SEEK_SET);
		}
		else
		{
			fseek(f,chunkstart+chunksize,SEEK_SET);
		}
	}
}

int SuperFile::ReadNextChunk(int &id, int &ver)
{
	if (!writing && f)
	{
		if (inchunk) EndChunk();
		chunkstart=ftell(f);
		Read(&id,4);
		Read(&ver,4);
		Read(&chunksize,4);
		inchunk=1;
	}
	return chunksize;
}

void SuperFile::Write(void *data, int size)
{
	if (f && writing) fwrite(data,1,size,f);
}

void SuperFile::Read(void *data, int size)
{
	if (f && !writing) fread(data,1,size,f);
}


struct MEVert
{
	float x,y,z,nx,ny,nz;
	float u,v;
	DWORD vcol;

	int idx;
	int idx2;

	static int key;

	MEVert(Point3 p, Point3 n, float uu, float vv, DWORD vc)
	{
		vcol=vc;
		x=p.x;
		y=p.y;
		z=p.z;
		nx=n.x;
		ny=n.y;
		nz=n.z;
		u=uu;
		v=vv;
		idx2=key++;
	}

	bool operator < (const MEVert &a) const
	{		
		if (x!=a.x	) return x<a.x	; else
		if (y!=a.y	) return y<a.y	; else
		if (z!=a.z	) return z<a.z	; else
		if (nx!=a.nx	) return nx<a.nx	; else
		if (ny!=a.ny	) return ny<a.ny	; else
		if (nz!=a.nz	) return nz<a.nz	; else
		if (u!=a.u	) return u<a.u	; else
		if (v!=a.v	) return v<a.v	; else
		if (vcol!=a.vcol) return vcol<a.vcol; else		
		return 0;
	}
};

int MEVert::key=0;

typedef std::set<MEVert> VertSet;

struct MEFace
{
	int a,b,c;

	MEFace(int aa, int bb, int cc)
	{
		a=aa;
		b=bb;
		c=cc;
	}
};


struct MEEdge
{
	int a,b;
	int xa,xb;
	
	MEEdge(int ya, int yb, int aa, int bb)
	{
		a=aa;
		b=bb;
		xa=ya;
		xb=yb;
	}
};


typedef std::vector<MEFace> FaceList;
typedef std::vector<MEEdge> EdgeList;

struct SubMesh
{
	FaceList f;
	EdgeList e;
	char texname[256];
	char envname[256];
	DWORD diff,flags;
	float selfillum;

	Mtl *mtl;

	void AddEdge(int a, int b, int aa, int bb)
	{
		if (a>b)
		{
			int c=a;a=b;b=c;
		}
		for (int c1=0;c1<e.size();c1++) if (e[c1].xa==a && e[c1].xb==b) return;
		e.push_back(MEEdge(a,b,aa,bb));
	}

	bool operator < (const SubMesh &a) const
	{
		/*
		int i=stricmp(texname,a.texname);
		if (i<0) return 1;
		i=stricmp(envname,a.envname);
		if (i<0) return 1;
		*/
		return mtl<a.mtl;
		return 0;
	}

	SubMesh(Mtl *m)
	{
		mtl=m;
		texname[0]=0;
		envname[0]=0;
		flags=0;
		selfillum=0;
		diff=0;
		if (m)
		{

			selfillum=m->GetSelfIllum();
			diff=DWORD(m->GetDiffuse());	
			flags=0;		
			if (m->ClassID()==Class_ID(DMTL_CLASS_ID,0))	// bitmap textures only
			{
				StdMat* std = (StdMat *)m;
				if (std->GetTwoSided()) flags|=1;
				if (std->GetWire()) flags|=2;
				
				Texmap *tx = NULL;
				tx = std->GetSubTexmap(ID_DI);			
				if (tx && tx->ClassID()==Class_ID(BMTEX_CLASS_ID,0))
				{	
					strcpy(texname,((BitmapTex*)tx)->GetMapName());
				}
				tx = std->GetSubTexmap(ID_RL);			
				if (tx && tx->ClassID()==Class_ID(BMTEX_CLASS_ID,0))
				{	
					strcpy(envname,((BitmapTex*)tx)->GetMapName());
				}
			}
		}
	}
};

typedef std::set<SubMesh> SubMeshSet;


struct MiniCamFrame
{
	float mat[4][4];	
	float fov;
	Point3 pos,foc,up;	
	
	MiniCamFrame()
	{
		fov=PI/2;
		pos=Point3(1,0,0);
		foc=Point3(0,0,0);
		up=Point3(0,1,0);
		memset(mat,0,sizeof(mat));
		mat[0][0]=mat[1][1]=mat[2][2]=mat[3][3]=1;
	}
};


TimeValue timecur;
TimeValue timestart;
TimeValue timeend;
TimeValue ticksperframe;
int fps;
int numframes;
BOOL exportSelected ;


struct MiniCam
{
	int numframes;
	std::vector<MiniCamFrame> data;
	INode *maxnode;
	
	MiniCam(INode *n)
	{
		maxnode=n;
		numframes=::numframes;
		data.resize(numframes);
		for (int fr=0;fr<numframes;fr++)
		{
			int tt= timestart+fr*ticksperframe;
			Object *obj = n->EvalWorldState(tt).obj;
			Matrix3 mat=n->GetNodeTM(tt);		
			data[fr].pos=mat.GetRow(3);			
			data[fr].foc=mat.GetRow(3) - mat.GetRow(2);
			mat.RotateX(PI); // flip the z direction. maybe we should spin about x/y??!!
			memcpy(data[fr].mat[0],&mat.GetRow(0),3*4);
			memcpy(data[fr].mat[1],&mat.GetRow(1),3*4);
			memcpy(data[fr].mat[2],&mat.GetRow(2),3*4);
			memcpy(data[fr].mat[3],&mat.GetRow(3),3*4);
			data[fr].fov=((CameraObject*)obj)->GetFOV(tt);

			if (n->GetTarget())
			{
				INode *no=n->GetTarget();
				INode* ln = no->GetLookatNode();
				if (ln)
				{
					Object *lobj = ln->EvalWorldState(tt).obj;
					if (lobj->SuperClassID()==CAMERA_CLASS_ID) 
					{
						// camera target...... node/obj				
						Object *lobj = ln->EvalWorldState(tt).obj;
						if (lobj->SuperClassID()==CAMERA_CLASS_ID) 
						{
							// camera target...... node/obj				
							data[fr].foc = no->GetNodeTM(tt).GetRow(3);
						}										
					}
				}
			
			}
			
			
		}
		
	}

	~MiniCam()
	{		
	}
};

typedef std::vector<MiniCam> MiniCamList;


TriObject* GetTriObjectFromNode(INode *node, Object *obj, TimeValue t, bool &deleteIt)
{
	deleteIt = FALSE;	
	if (obj->CanConvertToType(Class_ID(TRIOBJ_CLASS_ID, 0))) 
	{ 
		TriObject *tri = (TriObject *) obj->ConvertToType(t, Class_ID(TRIOBJ_CLASS_ID, 0));
		if (obj != tri) deleteIt = TRUE;
		return tri;
	}
	else {
		return NULL;
	}
}

void Quantize(Point3 &p)
{
	p.x=int(p.x*1000+0.5f)/1000.f;
	p.y=int(p.y*1000+0.5f)/1000.f;
	p.z=int(p.z*1000+0.5f)/1000.f;	
}

struct MEMesh
{
	Matrix3 nodetm,objecttm;
	SubMeshSet sm;
	VertSet vs;
	INode *node;

	MEMesh(INode *n)
	{
		nodetm=n->GetNodeTM(timecur);
		objecttm=n->GetObjectTM(timecur);

		Matrix3 normaltm=Inverse(nodetm);
		Matrix3 verttm=Inverse(nodetm);
		normaltm.NoTrans();
	
		node=n;
		Mtl *m=n->GetMtl();
		Mtl *subm[256];
		subm[0]=m;
		int numsm=1;
		if (m && m->ClassID()==Class_ID(MULTI_CLASS_ID,0))
		{
			numsm=m->NumSubMtls();
			if (numsm==0) numsm=1; else
			{
				for (int i=0; i<m->NumSubMtls(); i++)
				{
					subm[i]=m->GetSubMtl(i);
					while (subm[i] && subm[i]->ClassID()==Class_ID(MULTI_CLASS_ID,0))
					{
						int nsm = subm[i]->NumSubMtls();
						if (!nsm) nsm=1;
						subm[i]=subm[i]->GetSubMtl(i%nsm);
					}
				}			
			}
		}


		ObjectState objstate = n->EvalWorldState(timecur);	
		bool needDel;
		TriObject* tri = GetTriObjectFromNode(n, objstate.obj, timecur, needDel);

		if (tri)
		{
			Mesh* mesh = &tri->GetMesh();
			mesh->buildNormals();

			Tab<VNormal> vnorms;
			Tab<Point3> fnorms;

			ComputeVertexNormals(objecttm, mesh,vnorms,fnorms);

			// this is a face driven m'larkey
			int numf=mesh->getNumFaces();
			int numpoints=mesh->getNumVerts();
			for (int c1=0;c1<numf;c1++)
			{			
				int a,b,c;
				// find max material for this face
				Mtl *mtl=subm[mesh->faces[c1].getMatID()%numsm];
				// lets go
				int smg=mesh->faces[c1].smGroup;
				a=mesh->faces[c1].v[0];
				b=mesh->faces[c1].v[1];			
				c=mesh->faces[c1].v[2];
				int ed=mesh->faces[c1].flags;

				// add a submesh for this material. argh!
				SubMesh m(mtl);
				SubMeshSet::iterator submesh = sm.insert(m).first;

				Point3 p,n;
				p=(mesh->verts[a]*objecttm) * verttm;
				n=Normalize( Normalize(vnorms[a].GetNormal(smg,c1)) * normaltm );
				Quantize(p);Quantize(n);
				MEVert va(p,n,0,0,-1);
				p=(mesh->verts[b]*objecttm) * verttm;
				n=Normalize( Normalize(vnorms[b].GetNormal(smg,c1)) * normaltm );
				Quantize(p);Quantize(n);
				MEVert vb(p,n,0,0,-1);
				p=(mesh->verts[c]*objecttm) * verttm;
				n=Normalize( Normalize(vnorms[c].GetNormal(smg,c1)) * normaltm );
				Quantize(p);Quantize(n);
				MEVert vc(p,n,0,0,-1);

				UVVert *tVerts=NULL;
				TVFace *tvFace=NULL;
				TVFace *tcFace=NULL;
				VertColor *tcVerts=NULL;
				float u=0,v=0;		
				int chan=0;
				if (mesh->mapSupport(chan+1))
				{
					tvFace=mesh->mapFaces(chan+1);
					tVerts=mesh->mapVerts(chan+1);
					int ta,tb,tc;
					ta=tvFace?tvFace[c1].t[0]:0;
					tb=tvFace?tvFace[c1].t[1]:0;
					tc=tvFace?tvFace[c1].t[2]:0;					
					u=tVerts?tVerts[ta].x:0;v=tVerts?tVerts[ta].y:0;
					va.u=u;va.v=v;
					u=tVerts?tVerts[tb].x:0;v=tVerts?tVerts[tb].y:0;
					vb.u=u;vb.v=v;
					u=tVerts?tVerts[tc].x:0;v=tVerts?tVerts[tc].y:0;
					vc.u=u;vc.v=v;
				}

				tcVerts=mesh->numCVerts? mesh->vertCol : NULL;
				tcFace=mesh->numCVerts? mesh->vcFace : NULL;
#define bound(min,x,max) ((x)<(min)?(min):(x)>(max)?(max):(x))
				if (tcVerts && tcFace)
				{
					Point3 c;
					c = tcVerts[tcFace[c1].t[0]];					
					va.vcol = (int(bound(0,c.x,1)*255)<<0) | (int(bound(0,c.y,1)*255)<<8) | (int(bound(0,c.z,1)*255)<<16) | 0xff000000;
					c = tcVerts[tcFace[c1].t[1]];					
					vb.vcol = (int(bound(0,c.x,1)*255)<<0) | (int(bound(0,c.y,1)*255)<<8) | (int(bound(0,c.z,1)*255)<<16) | 0xff000000;
					c = tcVerts[tcFace[c1].t[2]];					
					vc.vcol = (int(bound(0,c.x,1)*255)<<0) | (int(bound(0,c.y,1)*255)<<8) | (int(bound(0,c.z,1)*255)<<16) | 0xff000000;
				}

				VertSet::iterator aa = vs.insert(va).first;
				VertSet::iterator bb = vs.insert(vb).first;
				VertSet::iterator cc = vs.insert(vc).first;
				if (!(aa==bb || bb==cc || aa==cc))
				{
					((SubMesh *) (&*submesh))->f.push_back(MEFace(aa->idx2,bb->idx2,cc->idx2));				
					int p1,p2;
					p1=aa->idx2;p2=bb->idx2;if (ed & EDGE_A) ((SubMesh *) (&*submesh))->AddEdge(a,b,p1,p2);
					p1=bb->idx2;p2=cc->idx2;if (ed & EDGE_B) ((SubMesh *) (&*submesh))->AddEdge(b,c,p1,p2);
					p1=cc->idx2;p2=aa->idx2;if (ed & EDGE_C) ((SubMesh *) (&*submesh))->AddEdge(c,a,p1,p2);
				}

			}

		}

		if (needDel) delete tri;

	}
};

typedef std::vector<MEMesh> MeshList;

class Miniexp : public SceneExport {
	public:


		MeshList m;
		MiniCamList c;
		static HWND hParams;


		BOOL	nodeEnum(INode* node, int explvl);

		int				ExtCount();					// Number of extensions supported
		const TCHAR *	Ext(int n);					// Extension #n (i.e. "3DS")
		const TCHAR *	LongDesc();					// Long ASCII description (i.e. "Autodesk 3D Studio File")
		const TCHAR *	ShortDesc();				// Short ASCII description (i.e. "3D Studio")
		const TCHAR *	AuthorName();				// ASCII Author name
		const TCHAR *	CopyrightMessage();			// ASCII Copyright message
		const TCHAR *	OtherMessage1();			// Other message #1
		const TCHAR *	OtherMessage2();			// Other message #2
		unsigned int	Version();					// Version number * 100 (i.e. v3.01 = 301)
		void			ShowAbout(HWND hWnd);		// Show DLL's "About..." box

		BOOL SupportsOptions(int ext, DWORD options);
		int				DoExport(const TCHAR *name,ExpInterface *ei,Interface *i, BOOL suppressPrompts=FALSE, DWORD options=0);


		
		//Constructor/Destructor

		Miniexp();
		~Miniexp();		

};


class MiniexpClassDesc:public ClassDesc2 {
	public:
	int 			IsPublic() { return TRUE; }
	void *			Create(BOOL loading = FALSE) { return new Miniexp(); }
	const TCHAR *	ClassName() { return GetString(IDS_CLASS_NAME); }
	SClass_ID		SuperClassID() { return SCENE_EXPORT_CLASS_ID; }
	Class_ID		ClassID() { return MINIEXP_CLASS_ID; }
	const TCHAR* 	Category() { return GetString(IDS_CATEGORY); }

	const TCHAR*	InternalName() { return _T("Miniexp"); }	// returns fixed parsable name (scripter-visible name)
	HINSTANCE		HInstance() { return hInstance; }				// returns owning module handle

};



static MiniexpClassDesc MiniexpDesc;
ClassDesc2* GetMiniexpDesc() { return &MiniexpDesc; }


BOOL CALLBACK MiniexpOptionsDlgProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam) {
	static Miniexp *imp = NULL;

	switch(message) {
		case WM_INITDIALOG:
			imp = (Miniexp *)lParam;
			CenterWindow(hWnd,GetParent(hWnd));
			return TRUE;

		case WM_CLOSE:
			EndDialog(hWnd, 0);
			return TRUE;
	}
	return FALSE;
}


//--- Miniexp -------------------------------------------------------
Miniexp::Miniexp()
{

}

Miniexp::~Miniexp() 
{

}

int Miniexp::ExtCount()
{
	//TODO: Returns the number of file name extensions supported by the plug-in.
	return 1;
}

const TCHAR *Miniexp::Ext(int n)
{		
	//TODO: Return the 'i-th' file name extension (i.e. "3DS").
	return _T("me");
}

const TCHAR *Miniexp::LongDesc()
{
	//TODO: Return long ASCII description (i.e. "Targa 2.0 Image File")
	return _T("mini export");
}
	
const TCHAR *Miniexp::ShortDesc() 
{			
	//TODO: Return short ASCII description (i.e. "Targa")
	return _T("miniexp");
}

const TCHAR *Miniexp::AuthorName()
{			
	//TODO: Return ASCII Author name
	return _T("alex");
}

const TCHAR *Miniexp::CopyrightMessage() 
{	
	// Return ASCII Copyright message
	return _T("(c) alex evans 2002");
}

const TCHAR *Miniexp::OtherMessage1() 
{		
	//TODO: Return Other message #1 if any
	return _T("");
}

const TCHAR *Miniexp::OtherMessage2() 
{		
	//TODO: Return other message #2 in any
	return _T("");
}

unsigned int Miniexp::Version()
{				
	//TODO: Return Version number * 100 (i.e. v3.01 = 301)
	return 100;
}

void Miniexp::ShowAbout(HWND hWnd)
{			
	// Optional
}

BOOL Miniexp::SupportsOptions(int ext, DWORD options)
{
	// TODO Decide which options to support.  Simply return
	// true for each option supported by each Extension 
	// the exporter supports.

	return TRUE;
}

BOOL	Miniexp::nodeEnum(INode* node, int explvl)
{
	if(exportSelected && node->Selected() == FALSE)
		return TREE_CONTINUE;
	if(!exportSelected || node->Selected()) 
	{
		ObjectState os = node->EvalWorldState(0); 		
		if (os.obj && (node->IsHidden()==false) && (node->IsTarget()==false))
		{			
			//log << "exporting node "<<node->GetName()<<": ";
			switch(os.obj->SuperClassID()) 
			{			
			case GEOMOBJECT_CLASS_ID: 
				//AddMesh(node,nodelvl++); 
				m.push_back(MEMesh(node));
				break;
			case CAMERA_CLASS_ID:
				c.push_back(MiniCam(node));
				break;
			}
		}
	}
	for (int c = 0; c < node->NumberOfChildren(); c++) {
		if (!nodeEnum(node->GetChildNode(c),explvl+1))
			return FALSE;
	}	
	return 1;
}

int	Miniexp::DoExport(const TCHAR *name,ExpInterface *ei,Interface *i, BOOL suppressPrompts, DWORD options)
{
	MEVert::key=0;
	exportSelected = (options & SCENE_EXPORT_SELECTED) ? TRUE : FALSE;
	timecur = i->GetTime();
	timestart = i->GetAnimRange().Start();
	timeend = i->GetAnimRange().End();
	ticksperframe = GetTicksPerFrame();
	fps = GetFrameRate();
	numframes=ticksperframe?(timeend-timestart)/ticksperframe+1:0;
	if (numframes<=1) numframes=0;


	//TODO: Implement the actual file Export here and 
	//		return TRUE If the file is exported properly

	/*
	if(!suppressPrompts)
		DialogBoxParam(hInstance, 
				MAKEINTRESOURCE(IDD_PANEL), 
				GetActiveWindow(), 
				MiniexpOptionsDlgProc, (LPARAM)this);
	*/

	int numChildren = i->GetRootNode()->NumberOfChildren();	
	for (int idx=0; idx<numChildren; idx++) 
	{
		if (i->GetCancel())
			break;
		nodeEnum(i->GetRootNode()->GetChildNode(idx),0);
	}

#define MESH_CHUNK 0x1234
#define VERT_CHUNK 0x2345
#define FACE_CHUNK 0x3456
#define EDGE_CHUNK 0x4567
#define CAM_CHUNK 0x6567
#define LGT_CHUNK 0x7567

	SuperFile f;
	f.Open((char *)name,true);
	for (int c1=0;c1<m.size();c1++)
	{
		f.StartChunk(MESH_CHUNK,1);
		float ff=0;
		f.Write(&m[c1].nodetm.GetRow(0),12);ff=0;f.Write(&ff,4);
		f.Write(&m[c1].nodetm.GetRow(1),12);ff=0;f.Write(&ff,4);
		f.Write(&m[c1].nodetm.GetRow(2),12);ff=0;f.Write(&ff,4);
		f.Write(&m[c1].nodetm.GetRow(3),12);ff=1;f.Write(&ff,4);

		

		int i;
		i=m[c1].sm.size();
		Report3D("%d submeshes\n",i);
		f.Write(&i,4);				
		f.EndChunk();

		f.StartChunk(VERT_CHUNK,2);
		i=m[c1].vs.size();
		Report3D("submesh has %d vertices\n",i);
		f.Write(&i,4);
		int idx=0;
		VertSet::iterator *vremap=new VertSet::iterator [i + MEVert::key];
		for (VertSet::iterator ii=m[c1].vs.begin();ii!=m[c1].vs.end();++ii)
		{
			//Report3D("vertex %d %0.5f, %0.5f, %0.5f normal %0.5f, %0.5f, %0.5f uv %0.5f %0.5f\n",idx,ii->x,ii->y,ii->z,ii->nx,ii->ny,ii->nz,ii->u,ii->v);
			((MEVert*)(&*ii))->idx=idx++;
			vremap[ii->idx2] = ii;
			f.Write((void*)&(*ii),36);
		}
		f.EndChunk();

		for (SubMeshSet::iterator smit=m[c1].sm.begin();smit!=m[c1].sm.end();++smit)
		{
			SubMesh &sm = *((SubMesh*) &*smit);
			f.StartChunk(FACE_CHUNK,1);

			Report3D("material %s %s %x %d %0.2f\n",sm.texname,sm.envname,sm.diff,sm.flags,sm.selfillum);

			f.Write(sm.texname,256);
			f.Write(sm.envname,256);
			f.Write(&sm.diff,4);
			f.Write(&sm.flags,4);
			f.Write(&sm.selfillum,4);
			i=sm.f.size();
			Report3D("%d faces\n",i);
			f.Write(&i,4);			
			for (int j=0;j<i;j++)
			{
				//Report3D("%d,%d,%d\n",sm.f[j].a,sm.f[j].b,sm.f[j].c);
				VertSet::iterator ar;
				//for (ar=m[c1].vs.begin();ar!=m[c1].vs.end();++ar) if (ar->idx2==sm.f[j].a) break;
				ar=vremap[sm.f[j].a];
				f.Write((void*)&ar->idx,4);				
				//for (ar=m[c1].vs.begin();ar!=m[c1].vs.end();++ar) if (ar->idx2==sm.f[j].b) break;
				ar=vremap[sm.f[j].b];
				f.Write((void*)&ar->idx,4);				
				//for (ar=m[c1].vs.begin();ar!=m[c1].vs.end();++ar) if (ar->idx2==sm.f[j].c) break;
				ar=vremap[sm.f[j].c];
				f.Write((void*)&ar->idx,4);				
			}

			f.EndChunk();
			f.StartChunk(EDGE_CHUNK,1);
			i=sm.e.size();
			Report3D("%d edges\n",i);
			f.Write(&i,4);			
			for (j=0;j<i;j++)
			{
				VertSet::iterator ar;
				//for (ar=m[c1].vs.begin();ar!=m[c1].vs.end();++ar) if (ar->idx2==sm.e[j].a) break;
				ar=vremap[sm.e[j].a];
				f.Write((void*)&ar->idx,4);				
				//for (ar=m[c1].vs.begin();ar!=m[c1].vs.end();++ar) if (ar->idx2==sm.e[j].b) break;
				ar=vremap[sm.e[j].b];
				f.Write((void*)&ar->idx,4);				
			}
			f.EndChunk();
			

		}

		delete [] vremap;
	}
	for (c1=0;c1<c.size();c1++)
	{
		f.StartChunk(CAM_CHUNK,1);
		f.Write(&c[c1].numframes,4);
		f.Write(c[c1].data.begin(),sizeof(MiniCamFrame)*c[c1].numframes);
		f.EndChunk();
	}
	f.Close();

	return TRUE;
}


