#include <windows.h>
#include <stdio.h>

#include "Types.h"
#include "File.h"
#include "Math.h"
#include "Misc.h"
#include "Model.h"
#include "List.h"
#include "Node.h"
#include "Mesh.h"
#include "TreeFile.h"


#define elif else if

#define MAX_VERT_CACHE_SIZE 64

//#define SCALE 25.4F


typedef enum
{
	CMD_END,
	CMD_TEXTURE,
	CMD_VERTICES,
	CMD_FACES,
	CMD_ALPHA,
	CMD_OPAQUE,
	CMD_NEGVERTS,
	CMD_ZTEST,
	CMD_ZUPDATE
} ECommand;

class CHead
{
	public:

		char
			Name[MAX_PATH];
		CList<CNode>
			Nodes;
		CList<CMesh>
			Meshes;
		s32
			Size,
			Offset;
};


extern "C"
{
	void HeadGeom_Init();
	s32 HeadGeom_Process( const char *folder );
	void HeadGeom_ToN64( const char *filename );
}


CModel
	Model;


static CList<CGeomTex>
	Textures;

static CList<CHead>
	Heads;

static CNode
	*Node = NULL;

static CMesh
	*Mesh = NULL;

static CHead
	*Head = NULL;

static s32
	VertSpace,
	NumMeshTypes = 0,
	MaxNodeVerts[NODE_COUNT],
	VertStarts[NODE_COUNT];


/*****************************************************************************/

static void AdjustUVs( SUV *uv, s32 tnum )
{
	CGeomTex
		*tex;

	if (tnum >= 0)
	{
		tex = Textures[tnum];

		if (stricmp( tex->Name, "back" ) == 0)
			uv->u = (uv->u * 128.0F * (float)tex->Width) - 32.0F;
		else
			uv->u = (uv->u * 64.0F * (float)tex->Width) - 32.0F;

		uv->v = (uv->v * 64.0F * (float)tex->Height) - 32.0F;
	}
}


typedef struct
{
	s32
		vtx;
	SUV
		uv;
} Vertex;

typedef struct
{
	ECommand
		Type;
	s32
		Tex,
		Vtx[3];
} SCommand;

static Vertex
	VertCache[MAX_VERT_CACHE_SIZE];

static SCommand
	CmdCache[256];

static s32
	CachedVerts,
	CachedFaces,
	CachedCmds,
	VertCacheSize,
	MaxCacheSize = 0;

static BOOL
	Reflect = FALSE;


static void ClearCache()
{
	CachedVerts = 0;
	CachedFaces = 0;
	CachedCmds = 0;
}


static s32 FindCacheVert( s32 vtx, SUV *uv )
{
	s32
		i;

	for (i=0; i<CachedVerts; i++)
	{
		if (VertCache[i].vtx == vtx &&
				(s32)VertCache[i].uv.u == (s32)uv->u &&
				(s32)VertCache[i].uv.v == (s32)uv->v)
			return( i );
	}

	return( -1 );
}


static s32 CountCachedVerts( SFace *face )
{
	s32
		i,
		count = 0;

	for (i=0; i<3; i++)
	{
		if (FindCacheVert( face->vtx[i], &face->uv[i] ) >= 0)
			count++;
	}

	return( count );
}


static void AddTexToCache( s32 tex )
{
	CmdCache[CachedCmds].Type = CMD_TEXTURE;
	CmdCache[CachedCmds].Tex = tex;
	CachedCmds++;
}


static void AddCmdToCache( ECommand cmd )
{
	CmdCache[CachedCmds].Type = cmd;
	CachedCmds++;
}


static void AddFaceToCache( SFace *face )
{
	s32
		i,
		v;

	CmdCache[CachedCmds].Type = CMD_FACES;

	for (i=0; i<3; i++)
	{
		v = FindCacheVert( face->vtx[i], &face->uv[i] );

		if (v < 0)
		{
			v = CachedVerts++;
			VertCache[v].vtx = face->vtx[i];
			VertCache[v].uv = face->uv[i];
		}

		CmdCache[CachedCmds].Vtx[i] = v;
	}

	CachedCmds++;
	CachedFaces++;
}


static void WriteCache( CTreeFile *cfile, CTreeFile *vfile )
{
	s32
		i,
		j;

	if (CachedVerts)
	{

		if (Reflect)
		{
//			printf( "Neg Verts = %d\n", CachedVerts );
			cfile->WriteWord( CMD_NEGVERTS );
		}
		else
		{
//			printf( "Verts = %d\n", CachedVerts );
			cfile->WriteWord( CMD_VERTICES );
		}

		cfile->WriteWord( (u16)CachedVerts );

		for (i=0; i<CachedVerts; i++)
		{
			cfile->WriteWord( VertCache[i].vtx );

			vfile->WriteWord( (u16)VertCache[i].uv.u );
			vfile->WriteWord( (u16)VertCache[i].uv.v );

			VertCacheSize += 16;
		}
	}


	for (i=0; i<CachedCmds; i++)
	{
		cfile->WriteWord( CmdCache[i].Type );

		if   (CmdCache[i].Type == CMD_TEXTURE)
		{
//			printf( "Texture: %d\n", CmdCache[i].Tex );
			cfile->WriteWord( CmdCache[i].Tex );
		}
		elif (CmdCache[i].Type == CMD_FACES)
		{
			j = i;
			while (j < CachedCmds && CmdCache[j].Type == CMD_FACES)
				j++;

//			printf( "Faces: %d\n", (j-i) );
			cfile->WriteWord( (j-i) );

			while (i < j)
			{
				cfile->WriteWord( CmdCache[i].Vtx[0] );
				cfile->WriteWord( CmdCache[i].Vtx[2] );
				cfile->WriteWord( CmdCache[i].Vtx[1] );

				i++;
			}

			i--;
		}
		else
		{
			cfile->WriteWord( 0 );
		}
	}
}


/*****************************************************************************/

static void GetNthArg( char *text, s32 nth, char *arg )
{
	s32
		i = 0,
		j,
		count = 0;

	arg[0] = 0;

	while (count < nth)
	{
		// Skip leading whitespace...

		while (isspace( text[i] ))
			i++;


		// Find end of this arg...

		if (text[i] == '<')
		{
			i++;
			j = i;

			// If first char is a <, search for ending >...

			while (text[j] && text[j] != '>' )
				j++;
		}
		else
		{
			j = i;
			while (text[j] && !isspace( text[j] ))
				j++;
		}


		// If this is the arg we want, copy it...

		count++;
		if (count == nth)
		{
			memcpy( arg, &text[i], j-i );
			arg[j-i] = 0;

			// If last char is a >, ignore it...

			if (arg[j-i-1] == '>')
				arg[j-i-1] = 0;
		}

		i = j;
	}
}


static void Script_Texture( char *text )
{
	CGeomTex
		*tex;
	char
		filename[64];

	GetNthArg( text, 2, filename );

	tex = new CGeomTex;
	tex->Load( filename );
	Textures.Add( tex );
}


static void Script_LoadP3D( char *text )
{
	char
		filename[64];

	GetNthArg( text, 2, filename );
	Model.LoadP3D( filename );
}


static void Script_Node( char *text )
{
	ENode
		type;
	char
		name[64];

	Mesh = NULL;
	GetNthArg( text, 2, name );

	type = FindNode( name );
	if (type < 0)
	{
		Node = NULL;
		Error( "'%s' is not a node.", name );
	}
	else
	{
		Node = new CNode;
		Node->Type = type;
		Head->Nodes.Add( Node );
	}
}


static void Script_Base( char *text )
{
	char
		name[64];

	GetNthArg( text, 2, name );

	if (Node)
		Node->SetBase( name );
}


static void Script_Box( char *text )
{
	char
		name[64];

	GetNthArg( text, 2, name );

	if (Node)
		Node->Tag( name );
}


static void Script_Shared( char *text )
{
	char
		temp[64],
		name[64];
	s32
		i,
		j,
		k;
	bool32
		found;

	GetNthArg( text, 2, temp );


	i = 0;
	found = FALSE;
	while (temp[i])
	{
		if (temp[i] == '?')
			found = TRUE;

		i++;
	}

	if (!found)
	{
		if (Node)
			Node->AddShared( temp );

		return;
	}


	i = 1;
	found = TRUE;
	while (found)
	{
		k = i;
		strcpy( name, temp );

		j = strlen( name ) - 1;
		while (j >= 0)
		{
			if (name[j] == '?')
			{
				name[j] = '0' + (k % 10);
				k /= 10;
			}

			j--;
		}

		if (Model.Find( name ))
		{
			if (Node)
				Node->AddShared( name );
		}
		else
			found = FALSE;

		i++;
	}
}


static void Script_Mesh( char *text )
{
	char
		name[64];
	s32
		i;

	Node = NULL;
	GetNthArg( text, 2, name );

	Mesh = new CMesh;
	strcpy( Mesh->Name, name );

	Mesh->NumNodes = Head->Nodes.Count;
	Mesh->NodeList = new CNode *[Head->Nodes.Count];
	for (i=0; i<Head->Nodes.Count; i++)
		Mesh->NodeList[i] = Head->Nodes[i];
	Head->Meshes.Add( Mesh );
}


static void Script_Object( char *text )
{
	char
		name[64];

	GetNthArg( text, 2, name );

	if (Mesh)
		Mesh->Object( name );
}


static void Script_Tag( char *text )
{
	char
		name[64];

	GetNthArg( text, 2, name );

	if (Node)
		Node->Tag( name );
	elif (Mesh)
		Mesh->Tag( name );
}


static void Script_UnTag( char *text )
{
	char
		name[64];

	GetNthArg( text, 2, name );

	if (Node)
		Node->UnTag( name );
	elif (Mesh)
		Mesh->UnTag( name );
}


static void Script_TagAll( char *text )
{
	if (Node)
		Node->TagAll();
	elif (Mesh)
		Mesh->TagAll();
}


static void Script_Grab( char *text )
{
	char
		name[64];

	if (Node)
	{
		GetNthArg( text, 2, name );
		Node->Grab( name );
	}
	elif (Mesh)
	{
		Mesh->Grab();
	}
}


static void Script_Process( char *text )
{
	static struct
	{
		char
			*Name;
		void
			(*Proc)( char *text );
	} list[] =
		{
			"Texture",			Script_Texture,
			"LoadP3D",			Script_LoadP3D,
			"Node",					Script_Node,
			"Base",					Script_Base,
			"Box",					Script_Box,
			"Shared",				Script_Shared,
			"Mesh",					Script_Mesh,
			"Object",				Script_Object,
			"Tag",					Script_Tag,
			"UnTag",				Script_UnTag,
			"TagAll",				Script_TagAll,
			"Grab",					Script_Grab,
			NULL,						NULL
		};
	static s32
		line = 0;
	s32
		i;
	char
		command[40];

	line++;

	GetNthArg( text, 1, command );

	i = 0;
	while (i >= 0 && list[i].Name)
	{
		if (!stricmp( command, list[i].Name ))
		{
			list[i].Proc( text );
			i = -1;
		}
		else
			i++;
	}

	if (i >= 0)
	{
		if (command[0] && command[0] != '#')
			Error( "Unknown command at line %d:\n%s", line, text );
	}
}


static s32 FindVertex( CMesh *mesh, Vector3 *v )
{
	CNode
		*node;
	CBox
		*box;
	s32
		i,
		n;

	for (n=(mesh->NumNodes-1); n>=0; n--)
	{
		node = mesh->NodeList[n];

		for (i=0; i<node->Shared.Count; i++)
		{
			box = node->Shared[i];

			if (box->ContainsPoint( v ))
				return( i + VertStarts[node->Type] );
		}
	}


	for (n=(mesh->NumNodes-1); n>=0; n--)
	{
		node = mesh->NodeList[n];

		for (i=0; i<node->NumVerts; i++)
		{
			if (*v == *node->GetVert( 0, i ))
				return( node->Reorder[i] + VertStarts[node->Type] );
		}
	}


	{
		char
			floats[64];

		sprintf( floats, "%f, %f, %f", v->x, v->y, v->z );
		Error( "Cannot find vertex:\n%s", floats );
	}
	return( -1 );
}


static void ProcessNodes()
{
	CHead
		*head;
	CNode
		*node;
	s32
		i,
		n,
		min,
		max,
		type;
	char
		*name;

	for (type=0; type<NODE_COUNT; type++)
		MaxNodeVerts[type] = 0;


	for (i=0; i<Heads.Count; i++)
	{
		head = Heads[i];

name = NULL;

		for (n=0; n<head->Nodes.Count; n++)
		{
			node = head->Nodes[n];
			type = node->Type;

			if (node->NumVerts > MaxNodeVerts[type])
				MaxNodeVerts[type] = node->NumVerts;

			switch (type)
			{
				case NODE_HEAD:
					min = 73;
					max = 73;
					break;

				case NODE_JAW:
					min = 16;
					max = 18;
					break;

				case NODE_MOUTH:
				case NODE_LOWER_LIP:
				case NODE_UPPER_LIP:
				case NODE_EYES:
					min = 2;
					max = 2;
					break;

				case NODE_BROW:
					min = 1;
					max = 1;
					break;
			}

			if (node->NumVerts < min ||  node->NumVerts > max)
				name = head->Name;
		}

if (name)
{
	printf( "\n%s bad verts!\n", name );

	for (n=0; n<head->Nodes.Count; n++)
	{
		node = head->Nodes[n];
		printf( "%d\n", node->NumVerts );
	}
	getchar();
}
	}


	i = 0;
	for (type=0; type<NODE_COUNT; type++)
	{
		VertStarts[type] = i;
		i += MaxNodeVerts[type];
	}
	VertSpace = i;


	for (i=0; i<Heads.Count; i++)
	{
		head = Heads[i];

		for (n=0; n<head->Nodes.Count; n++)
		{
			node = head->Nodes[n];

			ErrFile = head->Name;
			node->CalcReorder();
			ErrFile = NULL;
		}
	}
}


static void ProcessMeshes()
{
	CHead
		*head;
	CMesh
		*mesh;
	SFace
		*face;
	s32
		i,
		f,
		h,
		m;

	printf( "Processing meshes" );

	NumMeshTypes = 1;


	for (h=0; h<Heads.Count; h++)
	{
		head = Heads[h];

		for (m=0; m<head->Meshes.Count; m++)
		{
			putchar( '.' );
			fflush( stdout );

			mesh = head->Meshes[m];

			for (f=0; f<mesh->NumFaces; f++)
			{
				face = &mesh->FaceList[f];


				for (i=0; i<Textures.Count; i++)
				{
					if (!stricmp( face->TexName, Textures[i]->Name ))
						face->tex = i;
				}


				for (i=0; i<3; i++)
				{
					face->vtx[i] = FindVertex( mesh, mesh->GetVert( 0, face->vtx[i] ) );

					AdjustUVs( &face->uv[i], face->tex );
				}
			}
		}
	}

	putchar( '\n' );
}


static void WriteVert( CTreeFile *file, Vector3 *vert )
{
#if 0
	file->WriteWord( (u16)(vert->x * SCALE) );
	file->WriteWord( (u16)(vert->y * SCALE) );
	file->WriteWord( (u16)(vert->z * SCALE) );
#else
	file->WriteWord( (u16)(s16)(vert->x + 0.5F) );
	file->WriteWord( (u16)(s16)(vert->y + 0.5F) );
	file->WriteWord( (u16)(s16)(vert->z + 0.5F) );
#endif
	file->WriteWord( 0 );
}


static void Round( Vector3 &v )
{
	s32
		x,
		y,
		z;

	x = (s32)(v.x + 0.5F);
	y = (s32)(v.y + 0.5F);
	z = (s32)(v.z + 0.5F);

	v.x = (float)x;
	v.y = (float)y;
	v.z = (float)z;
}


static void SaveNode( CTreeFile *parent, CNode *node )
{
	static Vector3
		left,
		right;
	CTreeFile
		*file;
	Vector3
		v,
		base;
	s32
		i,
		j;

	base = *node->GetBase( 1 );
	Round( base );

	file = parent->NewTree();

	file->WriteLong( node->NumVerts );

	for (i=0; i<node->NumVerts; i++)
	{
		for (j=0; j<node->NumVerts; j++)
		{
			if (node->Reorder[j] == i)
			{
				v = *node->GetVert( 1, j );
				v -= base;

if (node->Type == NODE_UPPER_LIP)
{
	if (v.x < 0)
		v = left - base;
	else
		v = right - base;

	v.y -= 1;
}

				WriteVert( file, &v );

if (node->Type == NODE_LOWER_LIP)
{
	Round( v );
	v = v + base;

	if (v.x < 0)
		left = v;
	else
		right = v;
}
			}
		}
	}
}


static void SaveFace( CFile &file, SFace *face )
{

#if 0

	s32
		i;

	for (i=0; i<3; i++)
		printf( "%f, %f\t", face->uv[i].u, face->uv[i].v );
	putchar( '\n' );

#endif

	file.Write( &face->tex, 4 );
	file.Write( &face->vtx, 3*4 );
	file.Write( &face->uv, 3*2*4 );
}


static void SaveNodeList( CTreeFile *parent, const ENode type )
{
	CTreeFile
		*file;
	CNode
		*node;
	s32
		n;

	file = parent->NewTree();

	for (n=0; n<Head->Nodes.Count; n++)
	{
		node = Head->Nodes[n];

		if (node->Type == type)
			SaveNode( file, node );
	}
}


static void SaveNodeTypes( CTreeFile *parent )
{
	CTreeFile
		*file;
	CNode
		*node;
	s32
		n,
		type,
		count;

	file = parent->NewTree();

	for (type=0; type<NODE_COUNT; type++)
	{
		if (MaxNodeVerts[type] > 0)
		{
			count = 0;

			for (n=0; n<Head->Nodes.Count; n++)
			{
				node = Head->Nodes[n];

				if (node->Type == (ENode)type)
					count++;
			}

			for (n=0; n<Head->Nodes.Count; n++)
			{
				if (Head->Nodes[n]->Type == (ENode)type)
					node = Head->Nodes[n];
			}

			file->WriteByte( (u8)type );
			file->WriteByte( (u8)MaxNodeVerts[type] );
			file->WriteByte( (u8)count );
			file->WriteByte( 0 );

			WriteVert( file, node->GetBase( 1 ) );

			SaveNodeList( file, (ENode)type );
		}
	}
}


static BOOL IsMouthFace( SFace *face )
{
	float
		u = 0,
		v = 0;
	s32
		i;

	if (stricmp( face->TexName, "TOP" ) != 0)
		return( FALSE );

	for (i=0; i<3; i++)
	{
		u += face->uv[i].u;
		v += face->uv[i].v;
	}

	u /= 3;
	v /= 3;

	u = (u + 32.0F) / 64.0F;
	v = (v + 32.0F) / 64.0F;

	if (v >= 48)
		return( TRUE );

	if (v >= 32 && u <= 20)
		return( TRUE );

	return( FALSE );
}


static void SaveMouth( CMesh *mesh, CTreeFile *cfile, CTreeFile *vfile )
{
	SFace
		*face;
	s32
		i,
		left,
		high,
		which,
		count,
		*list,
		num_faces;

	list = new s32[mesh->NumFaces];

	num_faces = 0;

	for (i=0; i<mesh->NumFaces; i++)
	{
		face = &mesh->FaceList[i];

		if (IsMouthFace( face ))
			list[num_faces++] = i;
	}

	if (num_faces > 0)
	{
		AddTexToCache( 0 );
		AddCmdToCache( CMD_ZTEST );

		left = num_faces;
		while (left > 0)
		{
			high = -1;
			which = -1;
			for (i=0; i<num_faces; i++)
			{
				if (list[i] >= 0)
				{
					count = CountCachedVerts( &mesh->FaceList[list[i]] );
					if (count > high)
					{
						high = count;
						which = i;
					}
				}
			}

			if ((CachedVerts + 3 - high) > MAX_VERT_CACHE_SIZE)
			{
				WriteCache( cfile, vfile );
				ClearCache();
			}

			AddFaceToCache( &mesh->FaceList[list[which]] );
			list[which] = -1;
			left--;
		}

		AddCmdToCache( CMD_ZUPDATE );
	}

	delete [] list;
}


static void SaveMesh( CTreeFile *parent, CMesh *mesh, const s32 type )
{
	CTreeFile
		*vfile,
		*cfile;
	SFace
		*face;
	s32
		i,
		t,
		left,
		high,
		which,
		count,
		*list,
		num_faces;

	vfile = parent->NewTree();
	cfile = parent->NewTree();


	VertCacheSize = 0;
	ClearCache();

	SaveMouth( mesh, cfile, vfile );


	list = new s32[mesh->NumFaces];

	for (t=0; t<Textures.Count; t++)
	{
		num_faces = 0;

		for (i=0; i<mesh->NumFaces; i++)
		{
			face = &mesh->FaceList[i];

			if (face->tex == t)
			{
				if (!IsMouthFace( face ))
					list[num_faces++] = i;
			}
		}

		if (num_faces > 0)
		{
			if (t != 0)
				AddTexToCache( t );

			left = num_faces;
			while (left > 0)
			{
				high = -1;
				which = -1;
				for (i=0; i<num_faces; i++)
				{
					if (list[i] >= 0)
					{
						count = CountCachedVerts( &mesh->FaceList[list[i]] );
						if (count > high)
						{
							high = count;
							which = i;
						}
					}
				}

				if ((CachedVerts + 3 - high) > MAX_VERT_CACHE_SIZE)
				{
					WriteCache( cfile, vfile );
					ClearCache();
				}

				AddFaceToCache( &mesh->FaceList[list[which]] );
				list[which] = -1;
				left--;
			}


			if (!strnicmp( &Textures[t]->Name[1], "calf", 4 ) ||
					!strnicmp( &Textures[t]->Name[1], "foot", 4 ))
			{
				WriteCache( cfile, vfile );
				ClearCache();

				AddCmdToCache( CMD_ALPHA );
				Reflect = TRUE;


				num_faces = 0;

				for (i=0; i<mesh->NumFaces; i++)
				{
					face = &mesh->FaceList[i];

					if (face->tex == t)
						list[num_faces++] = i;
				}

				left = num_faces;
				while (left > 0)
				{
					high = -1;
					which = -1;
					for (i=0; i<num_faces; i++)
					{
						if (list[i] >= 0)
						{
							count = CountCachedVerts( &mesh->FaceList[list[i]] );
							if (count > high)
							{
								high = count;
								which = i;
							}
						}
					}

					if ((CachedVerts + 3 - high) > MAX_VERT_CACHE_SIZE)
					{
						WriteCache( cfile, vfile );
						ClearCache();
					}

					AddFaceToCache( &mesh->FaceList[list[which]] );
					list[which] = -1;
					left--;
				}

				AddCmdToCache( CMD_OPAQUE );

				WriteCache( cfile, vfile );
				ClearCache();

				Reflect = FALSE;
			}
		}
	}

	WriteCache( cfile, vfile );
	ClearCache();

	cfile->WriteWord( CMD_END );
	cfile->WriteWord( 0 );

	cfile->Pad( 4 );

	parent->WriteLong( VertCacheSize );

	if (VertCacheSize > MaxCacheSize)
		MaxCacheSize = VertCacheSize;

	delete [] list;
}


static void SaveMeshLists( CTreeFile *parent, const s32 type )
{
	CTreeFile
		*file;
	CMesh
		*mesh;

	file = parent->NewTree();

	mesh = Head->Meshes[0];

	SaveMesh( file, mesh, type );
}


static void SaveMeshTypes( CTreeFile *parent )
{
	CTreeFile
		*file;
	s32
		m;

	file = parent->NewTree();

	for (m=0; m<NumMeshTypes; m++)
	{
		file->WriteWord( (u16)m );
		file->WriteWord( 1 );

		SaveMeshLists( file, m );
	}
}


/*****************************************************************************/

void HeadGeom_Init()
{
	Math_Setup();

	Script_Process( "Texture	TOP" );
	Script_Process( "Texture	FRONT" );
	Script_Process( "Texture	BACK" );
	Script_Process( "Texture	SIDE" );
}


s32 HeadGeom_Process( const char *folder )
{
	HANDLE
		handle;
	WIN32_FIND_DATA
		find;
	char
		pathname[MAX_PATH];

	strcpy( pathname, folder );
	strcat( pathname, "/*.P3D" );

	handle = FindFirstFile( pathname, &find );

	if (handle == INVALID_HANDLE_VALUE)
		return( FALSE );

	FindClose( handle );

	strcpy( pathname, folder );
	strcat( pathname, "/" );
	strcat( pathname, find.cFileName );

	Head = new CHead;
	Heads.Add( Head );

	strcpy( Head->Name, folder );

	Model.LoadP3D( pathname );

	Script_Process( "Node		Head" );
	Script_Process( "Base		<box extreme head>" );
	Script_Process( "TagAll" );
	Script_Process( "UnTag	<box Jaw 1>" );
	Script_Process( "UnTag	<box Jaw 2>" );
	Script_Process( "UnTag	<box Jaw 3>" );
	Script_Process( "UnTag	<box Mouth>" );
	Script_Process( "UnTag	<box LowerLip>" );
	Script_Process( "UnTag	<box UpperLip>" );
	Script_Process( "UnTag	<box Eyes>" );
	Script_Process( "UnTag	<box Brow>" );
	Script_Process( "Shared	Neck1" );
	Script_Process( "Shared	Neck2" );
	Script_Process( "Grab		<Extreme Head>" );

	Script_Process( "Node		Jaw" );
	Script_Process( "Base		Jaw" );
	Script_Process( "Box		<box Jaw 1>" );
	Script_Process( "Box		<box Jaw 2>" );
	Script_Process( "Box		<box Jaw 3>" );
	Script_Process( "Shared	Neck3" );
	Script_Process( "Shared	Neck4" );
	Script_Process( "Shared	Neck5" );
	Script_Process( "Grab		<Extreme Head>" );

	Script_Process( "Node		Mouth" );
	Script_Process( "Base		Mouth" );
	Script_Process( "Box		<box Mouth>" );
	Script_Process( "Grab		<Extreme Head>" );

	Script_Process( "Node		LowerLip" );
	Script_Process( "Base		LowerLip" );
	Script_Process( "Box		<box LowerLip>" );
	Script_Process( "Grab		<Extreme Head>" );

	Script_Process( "Node		UpperLip" );
	Script_Process( "Base		UpperLip" );
	Script_Process( "Box		<box UpperLip>" );
	Script_Process( "Grab		<Extreme Head>" );

	Script_Process( "Node		Eyes" );
	Script_Process( "Base		Eyes" );
	Script_Process( "Box		<box Eyes>" );
	Script_Process( "Grab		<Extreme Head>" );

	Script_Process( "Node		Brow" );
	Script_Process( "Base		Brow" );
	Script_Process( "Box		<box Brow>" );
	Script_Process( "Grab		<Extreme Head>" );


	Script_Process( "Mesh		Head" );
	Script_Process( "Object	<Extreme Head>" );
	Script_Process( "TagAll" );
	Script_Process( "Grab" );


	return( TRUE );
}


static s32 SaveHead( const char *filename )
{
	CTreeFile
		file;
	s32
		size;

	file.CreateFile( filename );

	file.Write( "MDL!", 4 );
	file.Write( "BB2!", 4 );
	file.Write( "N64!", 4 );
	file.WriteLong( 8 );			// Version.

	file.WriteLong( NODE_COUNT );
	SaveNodeTypes( &file );

	file.WriteLong( NumMeshTypes );
	SaveMeshTypes( &file );

	size = file.GetSize();

	file.CloseFile();


	file.Open( filename );
	size = file.GetSize();
	file.Close();

	return( size );
}


static void CopyFromFile( const char *filename, CTreeFile &dst )
{
	CFile
		src;
	s32
		part,
		size;
	u8
		buffer[256];

	src.Open( filename );

	size = src.GetSize();

	while (size > 0)
	{
		if (size > 256)
			part = 256;
		else
			part = size;

		src.Read( buffer, part );
		dst.Write( buffer, part );

		size -= part;
	}

	src.Close();
}


void HeadGeom_ToN64( const char *filename )
{
	CTreeFile
		file;
	s32
		i,
		size,
		offset;
	char
		tempname[32];

	ProcessNodes();
	ProcessMeshes();


	printf( "Saving Heads" );
	offset = 4 + (Heads.Count * 2 * 4);

	for (i=0; i<Heads.Count; i++)
	{
		putchar( '.' );
		fflush( stdout );

		Head = Heads[i];
		Head->Offset = offset;

		wsprintf( tempname, "HEAD%03d.TMP", i );
		size = SaveHead( tempname );

		Head->Size = size;
		offset += size;
	}


	file.CreateFile( filename );

	file.WriteLong( Heads.Count );

	for (i=0; i<Heads.Count; i++)
	{
		Head = Heads[i];

		file.WriteLong( Head->Offset );
		file.WriteLong( Head->Size );
	}

	for (i=0; i<Heads.Count; i++)
	{
		wsprintf( tempname, "HEAD%03d.TMP", i );
		CopyFromFile( tempname, file );
		DeleteFile( tempname );
	}

	file.CloseFile();


	printf( "\n%d max verts.\n", VertSpace );
	printf( "VertCacheSize = %d\n", MaxCacheSize/16 );


#ifdef _DEBUG
	getchar();
#endif
}
