/*
   Copyright (C) 1999-2007 id Software, Inc. and contributors.
   For a list of contributors, see the accompanying CONTRIBUTORS file.

   This file is part of GtkRadiant.

   GtkRadiant is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   GtkRadiant 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 GtkRadiant; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "qdata.h"
#include "inout.h"

//=================================================================

typedef struct
{
	int numnormals;
	vec3_t normalsum;
} vertexnormals_t;

typedef struct
{
	vec3_t v;
	int lightnormalindex;
} trivert_t;

typedef struct
{
	vec3_t mins, maxs;
	char name[16];
	trivert_t v[MAX_VERTS];
} frame_t;

//================================================================

frame_t g_frames[MAX_FRAMES];

dmdl_t model;


float scale_up;                 // set by $scale
vec3_t adjust;                  // set by $origin
int g_fixedwidth, g_fixedheight;            // set by $skinsize


//
// base frame info
//
vec3_t base_xyz[MAX_VERTS];
dstvert_t base_st[MAX_VERTS];
dtriangle_t triangles[MAX_TRIANGLES];

int triangle_st[MAX_TRIANGLES][3][2];

// the command list holds counts, s/t values, and xyz indexes
// that are valid for every frame
int commands[16384];
int numcommands;
int numglverts;
int used[MAX_TRIANGLES];

char g_skins[MAX_MD2SKINS][64];

char cdarchive[1024];
char cdpartial[1024];
char cddir[1024];

char modelname[64];         // empty unless $modelname issued (players)

#define NUMVERTEXNORMALS    162

float avertexnormals[NUMVERTEXNORMALS][3] = {
#include "anorms.h"
};

FILE    *headerouthandle = NULL;

//==============================================================

/*
   ===============
   ClearModel
   ===============
 */
void ClearModel( void ){
	memset( &model, 0, sizeof( model ) );

	modelname[0] = 0;
	scale_up = 1.0;
	VectorCopy( vec3_origin, adjust );
	g_fixedwidth = g_fixedheight = 0;
	g_skipmodel = false;
}


void H_printf( char *fmt, ... ){
	va_list argptr;
	char name[1024];

	if ( !headerouthandle ) {
		sprintf( name, "%s/tris.h", cddir );
		headerouthandle = SafeOpenWrite( name );
		fprintf( headerouthandle, "// %s\n\n", cddir );
		fprintf( headerouthandle, "// This file generated by qdata - Do NOT Modify\n\n" );
	}

	va_start( argptr, fmt );
	vfprintf( headerouthandle, fmt, argptr );
	va_end( argptr );
}


/*
   ============
   WriteModelFile
   ============
 */
void WriteModelFile( FILE *modelouthandle ){
	int i;
	dmdl_t modeltemp;
	int j, k;
	frame_t         *in;
	daliasframe_t   *out;
	byte buffer[MAX_VERTS * 4 + 128];
	float v;
	int c_on, c_off;

	model.ident = IDALIASHEADER;
	model.version = ALIAS_VERSION;
	model.framesize = (int)&( (daliasframe_t *)0 )->verts[model.num_xyz];
	model.num_glcmds = numcommands;
	model.ofs_skins = sizeof( dmdl_t );
	model.ofs_st = model.ofs_skins + model.num_skins * MAX_SKINNAME;
	model.ofs_tris = model.ofs_st + model.num_st * sizeof( dstvert_t );
	model.ofs_frames = model.ofs_tris + model.num_tris * sizeof( dtriangle_t );
	model.ofs_glcmds = model.ofs_frames + model.num_frames * model.framesize;
	model.ofs_end = model.ofs_glcmds + model.num_glcmds * 4;

	//
	// write out the model header
	//
	for ( i = 0 ; i < sizeof( dmdl_t ) / 4 ; i++ )
		( (int *)&modeltemp )[i] = LittleLong( ( (int *)&model )[i] );

	SafeWrite( modelouthandle, &modeltemp, sizeof( modeltemp ) );

	//
	// write out the skin names
	//
	SafeWrite( modelouthandle, g_skins, model.num_skins * MAX_SKINNAME );

	//
	// write out the texture coordinates
	//
	c_on = c_off = 0;
	for ( i = 0 ; i < model.num_st ; i++ )
	{
		base_st[i].s = LittleShort( base_st[i].s );
		base_st[i].t = LittleShort( base_st[i].t );
	}

	SafeWrite( modelouthandle, base_st, model.num_st * sizeof( base_st[0] ) );

	//
	// write out the triangles
	//
	for ( i = 0 ; i < model.num_tris ; i++ )
	{
		int j;
		dtriangle_t tri;

		for ( j = 0 ; j < 3 ; j++ )
		{
			tri.index_xyz[j] = LittleShort( triangles[i].index_xyz[j] );
			tri.index_st[j] = LittleShort( triangles[i].index_st[j] );
		}

		SafeWrite( modelouthandle, &tri, sizeof( tri ) );
	}

	//
	// write out the frames
	//
	for ( i = 0 ; i < model.num_frames ; i++ )
	{
		in = &g_frames[i];
		out = (daliasframe_t *)buffer;

		strcpy( out->name, in->name );
		for ( j = 0 ; j < 3 ; j++ )
		{
			out->scale[j] = ( in->maxs[j] - in->mins[j] ) / 255;
			out->translate[j] = in->mins[j];
		}

		for ( j = 0 ; j < model.num_xyz ; j++ )
		{
			// all of these are byte values, so no need to deal with endianness
			out->verts[j].lightnormalindex = in->v[j].lightnormalindex;

			for ( k = 0 ; k < 3 ; k++ )
			{
				// scale to byte values & min/max check
				v = Q_rint( ( in->v[j].v[k] - out->translate[k] ) / out->scale[k] );

				// clamp, so rounding doesn't wrap from 255.6 to 0
				if ( v > 255.0 ) {
					v = 255.0;
				}
				if ( v < 0 ) {
					v = 0;
				}
				out->verts[j].v[k] = v;
			}
		}

		for ( j = 0 ; j < 3 ; j++ )
		{
			out->scale[j] = LittleFloat( out->scale[j] );
			out->translate[j] = LittleFloat( out->translate[j] );
		}

		SafeWrite( modelouthandle, out, model.framesize );
	}

	//
	// write out glcmds
	//
	SafeWrite( modelouthandle, commands, numcommands * 4 );
}


/*
   ===============
   FinishModel
   ===============
 */
void FinishModel( void ){
	FILE        *modelouthandle;
	int i;
	char name[1024];

	if ( !model.num_frames ) {
		return;
	}

//
// copy to release directory tree if doing a release build
//
	if ( g_release ) {
		if ( modelname[0] ) {
			sprintf( name, "%s", modelname );
		}
		else{
			sprintf( name, "%s/tris.md2", cdpartial );
		}
		ReleaseFile( name );

		for ( i = 0 ; i < model.num_skins ; i++ )
		{
			ReleaseFile( g_skins[i] );
		}
		model.num_frames = 0;
		return;
	}

//
// write the model output file
//
	if ( modelname[0] ) {
		sprintf( name, "%s%s", gamedir, modelname );
	}
	else{
		sprintf( name, "%s/tris.md2", cddir );
	}
	printf( "saving to %s\n", name );
	CreatePath( name );
	modelouthandle = SafeOpenWrite( name );

	WriteModelFile( modelouthandle );

	printf( "%3dx%3d skin\n", model.skinwidth, model.skinheight );
	printf( "%4d vertexes\n", model.num_xyz );
	printf( "%4d triangles\n", model.num_tris );
	printf( "%4d frame\n", model.num_frames );
	printf( "%4d glverts\n", numglverts );
	printf( "%4d glcmd\n", model.num_glcmds );
	printf( "%4d skins\n", model.num_skins );
	printf( "file size: %d\n", (int)ftell( modelouthandle ) );
	printf( "---------------------\n" );

	fclose( modelouthandle );

	// finish writing header file
	H_printf( "\n" );

	// scale_up is usefull to allow step distances to be adjusted
	H_printf( "#define MODEL_SCALE\t\t%f\n", scale_up );

	fclose( headerouthandle );
	headerouthandle = NULL;
}


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

   ALIAS MODEL DISPLAY LIST GENERATION

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

int strip_xyz[128];
int strip_st[128];
int strip_tris[128];
int stripcount;

/*
   ================
   StripLength
   ================
 */
int StripLength( int starttri, int startv ){
	int m1, m2;
	int st1, st2;
	int j;
	dtriangle_t *last, *check;
	int k;

	used[starttri] = 2;

	last = &triangles[starttri];

	strip_xyz[0] = last->index_xyz[( startv ) % 3];
	strip_xyz[1] = last->index_xyz[( startv + 1 ) % 3];
	strip_xyz[2] = last->index_xyz[( startv + 2 ) % 3];
	strip_st[0] = last->index_st[( startv ) % 3];
	strip_st[1] = last->index_st[( startv + 1 ) % 3];
	strip_st[2] = last->index_st[( startv + 2 ) % 3];

	strip_tris[0] = starttri;
	stripcount = 1;

	m1 = last->index_xyz[( startv + 2 ) % 3];
	st1 = last->index_st[( startv + 2 ) % 3];
	m2 = last->index_xyz[( startv + 1 ) % 3];
	st2 = last->index_st[( startv + 1 ) % 3];

	// look for a matching triangle
nexttri:
	for ( j = starttri + 1, check = &triangles[starttri + 1]
		  ; j < model.num_tris ; j++, check++ )
	{
		for ( k = 0 ; k < 3 ; k++ )
		{
			if ( check->index_xyz[k] != m1 ) {
				continue;
			}
			if ( check->index_st[k] != st1 ) {
				continue;
			}
			if ( check->index_xyz[ ( k + 1 ) % 3 ] != m2 ) {
				continue;
			}
			if ( check->index_st[ ( k + 1 ) % 3 ] != st2 ) {
				continue;
			}

			// this is the next part of the fan

			// if we can't use this triangle, this tristrip is done
			if ( used[j] ) {
				goto done;
			}

			// the new edge
			if ( stripcount & 1 ) {
				m2 = check->index_xyz[ ( k + 2 ) % 3 ];
				st2 = check->index_st[ ( k + 2 ) % 3 ];
			}
			else
			{
				m1 = check->index_xyz[ ( k + 2 ) % 3 ];
				st1 = check->index_st[ ( k + 2 ) % 3 ];
			}

			strip_xyz[stripcount + 2] = check->index_xyz[ ( k + 2 ) % 3 ];
			strip_st[stripcount + 2] = check->index_st[ ( k + 2 ) % 3 ];
			strip_tris[stripcount] = j;
			stripcount++;

			used[j] = 2;
			goto nexttri;
		}
	}
done:

	// clear the temp used flags
	for ( j = starttri + 1 ; j < model.num_tris ; j++ )
		if ( used[j] == 2 ) {
			used[j] = 0;
		}

	return stripcount;
}


/*
   ===========
   FanLength
   ===========
 */
int FanLength( int starttri, int startv ){
	int m1, m2;
	int st1, st2;
	int j;
	dtriangle_t *last, *check;
	int k;

	used[starttri] = 2;

	last = &triangles[starttri];

	strip_xyz[0] = last->index_xyz[( startv ) % 3];
	strip_xyz[1] = last->index_xyz[( startv + 1 ) % 3];
	strip_xyz[2] = last->index_xyz[( startv + 2 ) % 3];
	strip_st[0] = last->index_st[( startv ) % 3];
	strip_st[1] = last->index_st[( startv + 1 ) % 3];
	strip_st[2] = last->index_st[( startv + 2 ) % 3];

	strip_tris[0] = starttri;
	stripcount = 1;

	m1 = last->index_xyz[( startv + 0 ) % 3];
	st1 = last->index_st[( startv + 0 ) % 3];
	m2 = last->index_xyz[( startv + 2 ) % 3];
	st2 = last->index_st[( startv + 2 ) % 3];


	// look for a matching triangle
nexttri:
	for ( j = starttri + 1, check = &triangles[starttri + 1]
		  ; j < model.num_tris ; j++, check++ )
	{
		for ( k = 0 ; k < 3 ; k++ )
		{
			if ( check->index_xyz[k] != m1 ) {
				continue;
			}
			if ( check->index_st[k] != st1 ) {
				continue;
			}
			if ( check->index_xyz[ ( k + 1 ) % 3 ] != m2 ) {
				continue;
			}
			if ( check->index_st[ ( k + 1 ) % 3 ] != st2 ) {
				continue;
			}

			// this is the next part of the fan

			// if we can't use this triangle, this tristrip is done
			if ( used[j] ) {
				goto done;
			}

			// the new edge
			m2 = check->index_xyz[ ( k + 2 ) % 3 ];
			st2 = check->index_st[ ( k + 2 ) % 3 ];

			strip_xyz[stripcount + 2] = m2;
			strip_st[stripcount + 2] = st2;
			strip_tris[stripcount] = j;
			stripcount++;

			used[j] = 2;
			goto nexttri;
		}
	}
done:

	// clear the temp used flags
	for ( j = starttri + 1 ; j < model.num_tris ; j++ )
		if ( used[j] == 2 ) {
			used[j] = 0;
		}

	return stripcount;
}



/*
   ================
   BuildGlCmds

   Generate a list of trifans or strips
   for the model, which holds for all frames
   ================
 */
void BuildGlCmds( void ){
	int i, j, k;
	int startv;
	float s, t;
	int len, bestlen, besttype;
	int best_xyz[1024];
	int best_st[1024];
	int best_tris[1024];
	int type;

	//
	// build tristrips
	//
	numcommands = 0;
	numglverts = 0;
	memset( used, 0, sizeof( used ) );
	for ( i = 0 ; i < model.num_tris ; i++ )
	{
		// pick an unused triangle and start the trifan
		if ( used[i] ) {
			continue;
		}

		bestlen = 0;
		for ( type = 0 ; type < 2 ; type++ )
//	type = 1;
		{
			for ( startv = 0 ; startv < 3 ; startv++ )
			{
				if ( type == 1 ) {
					len = StripLength( i, startv );
				}
				else{
					len = FanLength( i, startv );
				}
				if ( len > bestlen ) {
					besttype = type;
					bestlen = len;
					for ( j = 0 ; j < bestlen + 2 ; j++ )
					{
						best_st[j] = strip_st[j];
						best_xyz[j] = strip_xyz[j];
					}
					for ( j = 0 ; j < bestlen ; j++ )
						best_tris[j] = strip_tris[j];
				}
			}
		}

		// mark the tris on the best strip/fan as used
		for ( j = 0 ; j < bestlen ; j++ )
			used[best_tris[j]] = 1;

		if ( besttype == 1 ) {
			commands[numcommands++] = ( bestlen + 2 );
		}
		else{
			commands[numcommands++] = -( bestlen + 2 );
		}

		numglverts += bestlen + 2;

		for ( j = 0 ; j < bestlen + 2 ; j++ )
		{
			// emit a vertex into the reorder buffer
			k = best_st[j];

			// emit s/t coords into the commands stream
			s = base_st[k].s;
			t = base_st[k].t;

			s = ( s + 0.5 ) / model.skinwidth;
			t = ( t + 0.5 ) / model.skinheight;

			*(float *)&commands[numcommands++] = s;
			*(float *)&commands[numcommands++] = t;
			*(int *)&commands[numcommands++] = best_xyz[j];
		}
	}

	commands[numcommands++] = 0;        // end of list marker
}


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

   BASE FRAME SETUP

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

/*
   ============
   BuildST

   Builds the triangle_st array for the base frame and
   model.skinwidth / model.skinheight

   FIXME: allow this to be loaded from a file for
   arbitrary mappings
   ============
 */
void BuildST( triangle_t *ptri, int numtri ){
	int i, j;
	int width, height, iwidth, iheight, swidth;
	float basex, basey;
	float s_scale, t_scale;
	float scale;
	vec3_t mins, maxs;
	float       *pbasevert;
	vec3_t vtemp1, vtemp2, normal;

	//
	// find bounds of all the verts on the base frame
	//
	ClearBounds( mins, maxs );

	for ( i = 0 ; i < numtri ; i++ )
		for ( j = 0 ; j < 3 ; j++ )
			AddPointToBounds( ptri[i].verts[j], mins, maxs );

	for ( i = 0 ; i < 3 ; i++ )
	{
		mins[i] = floor( mins[i] );
		maxs[i] = ceil( maxs[i] );
	}

	width = maxs[0] - mins[0];
	height = maxs[2] - mins[2];

	if ( !g_fixedwidth ) { // old style
		scale = 8;
		if ( width * scale >= 150 ) {
			scale = 150.0 / width;
		}
		if ( height * scale >= 190 ) {
			scale = 190.0 / height;
		}

		s_scale = t_scale = scale;

		iwidth = ceil( width * s_scale );
		iheight = ceil( height * t_scale );

		iwidth += 4;
		iheight += 4;
	}
	else
	{   // new style
		iwidth = g_fixedwidth / 2;
		iheight = g_fixedheight;

		s_scale = (float)( iwidth - 4 ) / width;
		t_scale = (float)( iheight - 4 ) / height;
	}

//
// determine which side of each triangle to map the texture to
//
	for ( i = 0 ; i < numtri ; i++ )
	{
		VectorSubtract( ptri[i].verts[0], ptri[i].verts[1], vtemp1 );
		VectorSubtract( ptri[i].verts[2], ptri[i].verts[1], vtemp2 );
		CrossProduct( vtemp1, vtemp2, normal );

		if ( normal[1] > 0 ) {
			basex = iwidth + 2;
		}
		else
		{
			basex = 2;
		}
		basey = 2;

		for ( j = 0 ; j < 3 ; j++ )
		{
			pbasevert = ptri[i].verts[j];

			triangle_st[i][j][0] = Q_rint( ( pbasevert[0] - mins[0] ) * s_scale + basex );
			triangle_st[i][j][1] = Q_rint( ( maxs[2] - pbasevert[2] ) * t_scale + basey );
		}
	}

// make the width a multiple of 4; some hardware requires this, and it ensures
// dword alignment for each scan
	swidth = iwidth * 2;
	model.skinwidth = ( swidth + 3 ) & ~3;
	model.skinheight = iheight;
}


/*
   =================
   Cmd_Base
   =================
 */
void Cmd_Base( void ){
	triangle_t  *ptri;
	int i, j, k;
	int time1;
	char file1[1024];

	GetToken( false );

	if ( g_skipmodel || g_release || g_archive ) {
		return;
	}

	printf( "---------------------\n" );
	sprintf( file1, "%s/%s.%s", cdarchive, token, trifileext );
	printf( "%s\n", file1 );

	ExpandPathAndArchive( file1 );

	sprintf( file1, "%s/%s.%s", cddir, token, trifileext );

	time1 = FileTime( file1 );
	if ( time1 == -1 ) {
		Error( "%s doesn't exist", file1 );
	}

//
// load the base triangles
//
	if ( do3ds ) {
		Load3DSTriangleList( file1, &ptri, &model.num_tris );
	}
	else{
		LoadTriangleList( file1, &ptri, &model.num_tris );
	}

//
// get the ST values
//
	BuildST( ptri, model.num_tris );

//
// run through all the base triangles, storing each unique vertex in the
// base vertex list and setting the indirect triangles to point to the base
// vertices
//
	for ( i = 0 ; i < model.num_tris ; i++ )
	{
		for ( j = 0 ; j < 3 ; j++ )
		{
			// get the xyz index
			for ( k = 0 ; k < model.num_xyz ; k++ )
				if ( VectorCompare( ptri[i].verts[j], base_xyz[k] ) ) {
					break;
				}           // this vertex is already in the base vertex list

			if ( k == model.num_xyz ) { // new index
				VectorCopy( ptri[i].verts[j], base_xyz[model.num_xyz] );
				model.num_xyz++;
			}

			triangles[i].index_xyz[j] = k;

			// get the st index
			for ( k = 0 ; k < model.num_st ; k++ )
				if ( triangle_st[i][j][0] == base_st[k].s
					 && triangle_st[i][j][1] == base_st[k].t ) {
					break;
				}           // this vertex is already in the base vertex list

			if ( k == model.num_st ) { // new index
				base_st[model.num_st].s = triangle_st[i][j][0];
				base_st[model.num_st].t = triangle_st[i][j][1];
				model.num_st++;
			}

			triangles[i].index_st[j] = k;
		}
	}

	// build triangle strips / fans
	BuildGlCmds();
}

//===============================================================

char    *FindFrameFile( char *frame ){
	int time1;
	char file1[1024];
	static char retname[1024];
	char base[32];
	char suffix[32];
	char    *s;

	if ( strstr( frame, "." ) ) {
		return frame;       // allready in dot format

	}
	// split 'run1' into 'run' and '1'
	s = frame + strlen( frame ) - 1;

	while ( s != frame && *s >= '0' && *s <= '9' )
		s--;

	strcpy( suffix, s + 1 );
	strcpy( base, frame );
	base[s - frame + 1] = 0;

	// check for 'run1.tri'
	sprintf( file1, "%s/%s%s.%s",cddir, base, suffix, trifileext );
	time1 = FileTime( file1 );
	if ( time1 != -1 ) {
		sprintf( retname, "%s%s.%s", base, suffix, trifileext );
		return retname;
	}

	// check for 'run.1'
	sprintf( file1, "%s/%s.%s",cddir, base, suffix );
	time1 = FileTime( file1 );
	if ( time1 != -1 ) {
		sprintf( retname, "%s.%s", base, suffix );
		return retname;
	}

	Error( "frame %s could not be found",frame );
	return NULL;
}

/*
   ===============
   GrabFrame
   ===============
 */
void GrabFrame( char *frame ){
	triangle_t  *ptri;
	int i, j;
	trivert_t   *ptrivert;
	int num_tris;
	char file1[1024];
	frame_t     *fr;
	vertexnormals_t vnorms[MAX_VERTS];
	int index_xyz;
	char    *framefile;

	// the frame 'run1' will be looked for as either
	// run.1 or run1.tri, so the new alias sequence save
	// feature an be used
	framefile = FindFrameFile( frame );

	sprintf( file1, "%s/%s", cdarchive, framefile );
	ExpandPathAndArchive( file1 );

	sprintf( file1, "%s/%s",cddir, framefile );

	printf( "grabbing %s\n", file1 );

	if ( model.num_frames >= MAX_FRAMES ) {
		Error( "model.num_frames >= MAX_FRAMES" );
	}
	fr = &g_frames[model.num_frames];
	model.num_frames++;

	strcpy( fr->name, frame );

//
// load the frame
//
	if ( do3ds ) {
		Load3DSTriangleList( file1, &ptri, &num_tris );
	}
	else{
		LoadTriangleList( file1, &ptri, &num_tris );
	}

	if ( num_tris != model.num_tris ) {
		Error( "%s: number of triangles doesn't match base frame\n", file1 );
	}

//
// allocate storage for the frame's vertices
//
	ptrivert = fr->v;

	for ( i = 0 ; i < model.num_xyz ; i++ )
	{
		vnorms[i].numnormals = 0;
		VectorClear( vnorms[i].normalsum );
	}
	ClearBounds( fr->mins, fr->maxs );

//
// store the frame's vertices in the same order as the base. This assumes the
// triangles and vertices in this frame are in exactly the same order as in the
// base
//
	for ( i = 0 ; i < num_tris ; i++ )
	{
		vec3_t vtemp1, vtemp2, normal;
		float ftemp;

		VectorSubtract( ptri[i].verts[0], ptri[i].verts[1], vtemp1 );
		VectorSubtract( ptri[i].verts[2], ptri[i].verts[1], vtemp2 );
		CrossProduct( vtemp1, vtemp2, normal );

		VectorNormalize( normal, normal );

		// rotate the normal so the model faces down the positive x axis
		ftemp = normal[0];
		normal[0] = -normal[1];
		normal[1] = ftemp;

		for ( j = 0 ; j < 3 ; j++ )
		{
			index_xyz = triangles[i].index_xyz[j];

			// rotate the vertices so the model faces down the positive x axis
			// also adjust the vertices to the desired origin
			ptrivert[index_xyz].v[0] = ( ( -ptri[i].verts[j][1] ) * scale_up ) +
									   adjust[0];
			ptrivert[index_xyz].v[1] = ( ptri[i].verts[j][0] * scale_up ) +
									   adjust[1];
			ptrivert[index_xyz].v[2] = ( ptri[i].verts[j][2] * scale_up ) +
									   adjust[2];

			AddPointToBounds( ptrivert[index_xyz].v, fr->mins, fr->maxs );

			VectorAdd( vnorms[index_xyz].normalsum, normal, vnorms[index_xyz].normalsum );
			vnorms[index_xyz].numnormals++;
		}
	}

//
// calculate the vertex normals, match them to the template list, and store the
// index of the best match
//
	for ( i = 0 ; i < model.num_xyz ; i++ )
	{
		int j;
		vec3_t v;
		float maxdot;
		int maxdotindex;
		int c;

		c = vnorms[i].numnormals;
		if ( !c ) {
			Error( "Vertex with no triangles attached" );
		}

		VectorScale( vnorms[i].normalsum, 1.0 / c, v );
		VectorNormalize( v, v );

		maxdot = -999999.0;
		maxdotindex = -1;

		for ( j = 0 ; j < NUMVERTEXNORMALS ; j++ )
		{
			float dot;

			dot = DotProduct( v, avertexnormals[j] );
			if ( dot > maxdot ) {
				maxdot = dot;
				maxdotindex = j;
			}
		}

		ptrivert[i].lightnormalindex = maxdotindex;
	}

	free( ptri );
}

/*
   ===============
   Cmd_Frame
   ===============
 */
void Cmd_Frame( void ){
	while ( TokenAvailable() )
	{
		GetToken( false );
		if ( g_skipmodel ) {
			continue;
		}
		if ( g_release || g_archive ) {
			model.num_frames = 1;   // don't skip the writeout
			continue;
		}

		H_printf( "#define FRAME_%-16s\t%i\n", token, model.num_frames );

		GrabFrame( token );
	}
}


/*
   ===============
   Cmd_Skin

   Skins aren't actually stored in the file, only a reference
   is saved out to the header file.
   ===============
 */
void Cmd_Skin( void ){
	byte    *palette;
	byte    *pixels;
	int width, height;
	byte    *cropped;
	int y;
	char name[1024], savename[1024];

	GetToken( false );

	if ( model.num_skins == MAX_MD2SKINS ) {
		Error( "model.num_skins == MAX_MD2SKINS" );
	}

	if ( g_skipmodel ) {
		return;
	}

	sprintf( name, "%s/%s.lbm", cdarchive, token );
	strcpy( name, ExpandPathAndArchive( name ) );
//	sprintf (name, "%s/%s.lbm", cddir, token);

	if ( TokenAvailable() ) {
		GetToken( false );
		sprintf( g_skins[model.num_skins], "%s.pcx", token );
		sprintf( savename, "%s%s.pcx", gamedir, g_skins[model.num_skins] );
	}
	else
	{
		sprintf( savename, "%s/%s.pcx", cddir, token );
		sprintf( g_skins[model.num_skins], "%s/%s.pcx", cdpartial, token );
	}

	model.num_skins++;

	if ( g_skipmodel || g_release || g_archive ) {
		return;
	}

	// load the image
	printf( "loading %s\n", name );
	Load256Image( name, &pixels, &palette, &width, &height );
	RemapZero( pixels, palette, width, height );

	// crop it to the proper size
	cropped = malloc( model.skinwidth * model.skinheight );
	for ( y = 0 ; y < model.skinheight ; y++ )
	{
		memcpy( cropped + y * model.skinwidth,
				pixels + y * width, model.skinwidth );
	}

	// save off the new image
	printf( "saving %s\n", savename );
	CreatePath( savename );
	WritePCXfile( savename, cropped, model.skinwidth,
				  model.skinheight, palette );

	free( pixels );
	free( palette );
	free( cropped );
}


/*
   =================
   Cmd_Origin
   =================
 */
void Cmd_Origin( void ){
	// rotate points into frame of reference so model points down the
	// positive x axis
	GetToken( false );
	adjust[1] = -atof( token );

	GetToken( false );
	adjust[0] = atof( token );

	GetToken( false );
	adjust[2] = -atof( token );
}


/*
   =================
   Cmd_ScaleUp
   =================
 */
void Cmd_ScaleUp( void ){
	GetToken( false );
	scale_up = atof( token );
	if ( g_skipmodel || g_release || g_archive ) {
		return;
	}

	printf( "Scale up: %f\n", scale_up );
}


/*
   =================
   Cmd_Skinsize

   Set a skin size other than the default
   =================
 */
void Cmd_Skinsize( void ){
	GetToken( false );
	g_fixedwidth = atoi( token );
	GetToken( false );
	g_fixedheight = atoi( token );
}

/*
   =================
   Cmd_Modelname

   Gives a different name/location for the file, instead of the cddir
   =================
 */
void Cmd_Modelname( void ){
	GetToken( false );
	strcpy( modelname, token );
}

/*
   ===============
   Cmd_Cd
   ===============
 */
void Cmd_Cd( void ){
	FinishModel();
	ClearModel();

	GetToken( false );

	// this is a silly mess...
	sprintf( cdpartial, "models/%s", token );
	sprintf( cdarchive, "%smodels/%s", gamedir + strlen( qdir ), token );
	sprintf( cddir, "%s%s", gamedir, cdpartial );

	// if -only was specified and this cd doesn't match,
	// skip the model (you only need to match leading chars,
	// so you could regrab all monsters with -only monsters)
	if ( !g_only[0] ) {
		return;
	}
	if ( strncmp( token, g_only, strlen( g_only ) ) ) {
		g_skipmodel = true;
		printf( "skipping %s\n", cdpartial );
	}
}
