/*
   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 "qd_fmodel.h"
#include "animcomp.h"
#include "qd_skeletons.h"
#include "skeletons.h"
#include "qdata.h"
#include "flex.h"
#include "reference.h"

#include <assert.h>

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

   .FM triangle flexible model file format

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

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

#define NUMVERTEXNORMALS    162

extern float avertexnormals[NUMVERTEXNORMALS][3];

#define MAX_GROUPS  128

typedef struct
{
	triangle_t triangle;
	int group;
} trigroup_t;

#define TRIVERT_DIST    .1

typedef struct
{
	int start_frame;
	int num_frames;
	int degrees;
	char *mat;
	char *ccomp;
	char *cbase;
	float *cscale;
	float *coffset;
	float trans[3];
	float scale[3];
	float bmin[3];
	float bmax[3];
} fmgroup_t;

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

// Initial
fmheader_t fmheader;

// Skin
extern char g_skins[MAX_FM_SKINS][64];

// ST Coord
extern fmstvert_t base_st[MAX_FM_VERTS];

// Triangles
extern fmtriangle_t triangles[MAX_FM_TRIANGLES];

// Frames
extern fmframe_t g_frames[MAX_FM_FRAMES];
//fmframe_t	*g_FMframes;

// GL Commands
extern int commands[16384];
extern int numcommands;


//
// varibles set by commands
//
extern float scale_up;                              // set by $scale
extern vec3_t adjust;                               // set by $origin
extern int g_fixedwidth, g_fixedheight;             // set by $skinsize
extern char modelname[64];                          // set by $modelname


extern char        *g_outputDir;


// Mesh Nodes
mesh_node_t *pmnodes = NULL;
fmmeshnode_t mesh_nodes[MAX_FM_MESH_NODES];

fmgroup_t groups[MAX_GROUPS];
int num_groups;
int frame_to_group[MAX_FM_FRAMES];

//
// variables set by command line arguments
//
qboolean g_no_opimizations = false;


//
// base frame info
//
static int triangle_st[MAX_FM_TRIANGLES][3][2];


// number of gl vertices
extern int numglverts;
// indicates if a triangle has already been used in a glcmd
extern int used[MAX_FM_TRIANGLES];
// indicates if a triangle has translucency in it or not
static qboolean translucent[MAX_FM_TRIANGLES];

// main output file handle
extern FILE         *headerouthandle;
// output sizes of buildst()
static int skin_width, skin_height;


// statistics
static int total_skin_pixels;
static int skin_pixels_used;

int ShareVertex( trigroup_t trione, trigroup_t tritwo );
float DistBetween( vec3_t point1, vec3_t point2 );
int GetNumTris( trigroup_t *tris, int group );
void GetOneGroup( trigroup_t *tris, int grp, triangle_t* triangles );
void ScaleTris( vec3_t min, vec3_t max, int Width, int Height, float* u, float* v, int verts );
void NewDrawLine( int x1, int y1, int x2, int y2, unsigned char* picture, int width, int height );

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

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

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

	if ( pmnodes ) {
		free( pmnodes );
		pmnodes = NULL;
	}

	ClearSkeletalModel();
}


extern void H_printf( char *fmt, ... );


void WriteHeader( FILE *FH, char *Ident, int Version, int Size, void *Data ){
	header_t header;
	static long pos = -1;
	long CurrentPos;

	if ( Size == 0 ) { // Don't write out empty packets
		return;
	}

	if ( pos != -1 ) {
		CurrentPos = ftell( FH );
		Size = CurrentPos - pos + sizeof( header_t );
		fseek( FH, pos, SEEK_SET );
		pos = -2;
	}
	else if ( Size == -1 ) {
		pos = ftell( FH );
	}

	memset( &header,0,sizeof( header ) );
	strcpy( header.ident,Ident );
	header.version = Version;
	header.size = Size;

	SafeWrite( FH, &header, sizeof( header ) );

	if ( Data ) {
		SafeWrite( FH, Data, Size );
	}

	if ( pos == -2 ) {
		pos = -1;
		fseek( FH, 0, SEEK_END );
	}
}

/*
   ============
   WriteModelFile
   ============
 */
static void WriteModelFile( FILE *modelouthandle ){
	int i;
	int j, k;
	fmframe_t       *in;
	fmaliasframe_t  *out;
	byte buffer[MAX_FM_VERTS * 4 + 128];
	float v;
	int c_on, c_off;
	IntListNode_t   *current, *toFree;
	qboolean framesWritten = false;
	size_t temp,size = 0;

	// probably should do this dynamically one of these days
	struct
	{
		float scale[3];         // multiply byte verts by this
		float translate[3];         // then add this
	} outFrames[MAX_FM_FRAMES];

#define DATA_SIZE 0x60000       // 384K had better be enough, particularly for the reference points
	byte data[DATA_SIZE];
	byte data2[DATA_SIZE];

	fmheader.num_glcmds = numcommands;
	fmheader.framesize = (int)&( (fmaliasframe_t *)0 )->verts[fmheader.num_xyz];

	WriteHeader( modelouthandle, FM_HEADER_NAME, FM_HEADER_VER, sizeof( fmheader ), &fmheader );

	//
	// write out the skin names
	//

	WriteHeader( modelouthandle, FM_SKIN_NAME, FM_SKIN_VER, fmheader.num_skins * MAX_FM_SKINNAME, g_skins );

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

	WriteHeader( modelouthandle, FM_ST_NAME, FM_ST_VER, fmheader.num_st * sizeof( base_st[0] ), base_st );

	//
	// write out the triangles
	//
	WriteHeader( modelouthandle, FM_TRI_NAME, FM_TRI_VER, fmheader.num_tris * sizeof( fmtriangle_t ), NULL );

	for ( i = 0 ; i < fmheader.num_tris ; i++ )
	{
		int j;
		fmtriangle_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 ) );
	}

	if ( !num_groups ) {
		//
		// write out the frames
		//
		WriteHeader( modelouthandle, FM_FRAME_NAME, FM_FRAME_VER, fmheader.num_frames * fmheader.framesize, NULL );
		//	WriteHeader(modelouthandle, FM_FRAME_NAME, FM_FRAME_VER, -1, NULL);

		for ( i = 0 ; i < fmheader.num_frames ; i++ )
		{
			in = &g_frames[i];
			out = (fmaliasframe_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];

				outFrames[i].scale[j] = out->scale[j];
				outFrames[i].translate[j] = out->translate[j];
			}

			for ( j = 0 ; j < fmheader.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, fmheader.framesize );
		}

		// Go back and finish the header
		//	WriteHeader(modelouthandle, FM_FRAME_NAME, FM_FRAME_VER, -1, NULL);
	}
	else
	{
		WriteHeader( modelouthandle, FM_SHORT_FRAME_NAME, FM_SHORT_FRAME_VER,FRAME_NAME_LEN * fmheader.num_frames, NULL );
		for ( i = 0 ; i < fmheader.num_frames ; i++ )
		{
			in = &g_frames[i];
			SafeWrite( modelouthandle,in->name,FRAME_NAME_LEN );
		}
		WriteHeader( modelouthandle, FM_NORMAL_NAME, FM_NORMAL_VER,fmheader.num_xyz, NULL );
		in = &g_frames[0];
		for ( j = 0 ; j < fmheader.num_xyz ; j++ )
			SafeWrite( modelouthandle,&in->v[j].lightnormalindex,1 );
	}

	//
	// write out glcmds
	//
	WriteHeader( modelouthandle, FM_GLCMDS_NAME, FM_GLCMDS_VER, numcommands * 4, commands );

	//
	// write out mesh nodes
	//
	for ( i = 0; i < fmheader.num_mesh_nodes; i++ )
	{
		memcpy( mesh_nodes[i].tris, pmnodes[i].tris, sizeof( mesh_nodes[i].tris ) );
		memcpy( mesh_nodes[i].verts, pmnodes[i].verts, sizeof( mesh_nodes[i].verts ) );
		mesh_nodes[i].start_glcmds = LittleShort( (short)pmnodes[i].start_glcmds );
		mesh_nodes[i].num_glcmds = LittleShort( (short)pmnodes[i].num_glcmds );
	}

	WriteHeader( modelouthandle, FM_MESH_NAME, FM_MESH_VER, sizeof( fmmeshnode_t ) * fmheader.num_mesh_nodes, mesh_nodes );

	if ( num_groups ) {

/*
   typedef struct
   {
    int			start_frame;
    int			num_frames;
    int			degrees;
    char *mat; fmheader.num_xyz*3*g->degrees*sizeof(char)
    char *ccomp; g->num_frames*g->degrees*sizeof(char)
    char *cbase; fmheader.num_xyz*3*sizeof(unsigned char)
    float *cscale; g->degrees*sizeof(float)
    float *coffset; g->degrees*sizeof(float)
    float trans[3]; 3*sizeof(float)
    float scale[3]; 3*sizeof(float)
   } fmgroup_t;
 */
		int tmp,k;
		fmgroup_t *g;
		size = sizeof( int ) + fmheader.num_frames * sizeof( int );
		for ( k = 0; k < num_groups; k++ )
		{
			g = &groups[k];
			size += sizeof( int ) * 3;
			size += fmheader.num_xyz * 3 * g->degrees * sizeof( char );
			size += g->num_frames * g->degrees * sizeof( char );
			size += fmheader.num_xyz * 3 * sizeof( unsigned char );
			size += g->degrees * sizeof( float );
			size += g->degrees * sizeof( float );
			size += 12 * sizeof( float );
		}
		WriteHeader( modelouthandle, FM_COMP_NAME, FM_COMP_VER,size, NULL );
		SafeWrite( modelouthandle,&num_groups,sizeof( int ) );
		SafeWrite( modelouthandle,frame_to_group,sizeof( int ) * fmheader.num_frames );

		for ( k = 0; k < num_groups; k++ )
		{
			g = &groups[k];
			tmp = LittleLong( g->start_frame );
			SafeWrite( modelouthandle,&tmp,sizeof( int ) );
			tmp = LittleLong( g->num_frames );
			SafeWrite( modelouthandle,&tmp,sizeof( int ) );
			tmp = LittleLong( g->degrees );
			SafeWrite( modelouthandle,&tmp,sizeof( int ) );

			SafeWrite( modelouthandle,g->mat,fmheader.num_xyz * 3 * g->degrees * sizeof( char ) );
			SafeWrite( modelouthandle,g->ccomp,g->num_frames * g->degrees * sizeof( char ) );
			SafeWrite( modelouthandle,g->cbase,fmheader.num_xyz * 3 * sizeof( unsigned char ) );
			SafeWrite( modelouthandle,g->cscale,g->degrees * sizeof( float ) );
			SafeWrite( modelouthandle,g->coffset,g->degrees * sizeof( float ) );
			SafeWrite( modelouthandle,g->trans,3 * sizeof( float ) );
			SafeWrite( modelouthandle,g->scale,3 * sizeof( float ) );
			SafeWrite( modelouthandle,g->bmin,3 * sizeof( float ) );
			SafeWrite( modelouthandle,g->bmax,3 * sizeof( float ) );
			free( g->mat );
			free( g->ccomp );
			free( g->cbase );
			free( g->cscale );
			free( g->coffset );
		}
	}

	// write the skeletal info
	if ( g_skelModel.type != SKEL_NULL ) {
		size = 0;

		temp = sizeof( int );     // change this to a byte
		memcpy( data + size, &g_skelModel.type, temp );
		size += temp;

		// number of joints
		temp = sizeof( int );     // change this to a byte
		memcpy( data + size, &numJointsInSkeleton[g_skelModel.type], temp );
		size += temp;

		// number of verts in each joint cluster
		temp = sizeof( int ) * numJointsInSkeleton[g_skelModel.type]; // change this to shorts
		memcpy( data + size, &g_skelModel.new_num_verts[1], temp );
		size += temp;

		// cluster verts
		for ( i = 0; i < numJointsInSkeleton[g_skelModel.type]; ++i )
		{
			current = g_skelModel.vertLists[i];
			while ( current )
			{
				temp = sizeof( int ); // change this to a short
				memcpy( data + size, &current->data, temp );
				size += temp;
				toFree = current;
				current = current->next;
				free( toFree );  // freeing of memory allocated in ReplaceClusterIndex called in Cmd_Base
			}
		}

		if ( !num_groups ) { // joints are stored with regular verts for compressed models
			framesWritten = true;

			temp = sizeof( int ); // change this to a byte
			memcpy( data + size, &framesWritten, temp );
			size += temp;

			for ( i = 0; i < fmheader.num_frames; ++i )
			{
				in = &g_frames[i];

				for ( j = 0 ; j < numJointsInSkeleton[g_skelModel.type]; ++j )
				{
					for ( k = 0 ; k < 3 ; k++ )
					{
						// scale to byte values & min/max check
						v = Q_rint( ( in->joints[j].placement.origin[k] - outFrames[i].translate[k] ) / outFrames[i].scale[k] );

						// write out origin as a float since they arn't clamped
						temp = sizeof( float );   // change this to a short
						assert( size + temp < DATA_SIZE );
						memcpy( data + size, &v, temp );
						size += temp;
					}

					for ( k = 0 ; k < 3 ; k++ )
					{
						v = Q_rint( ( in->joints[j].placement.direction[k] - outFrames[i].translate[k] ) / outFrames[i].scale[k] );

						// write out origin as a float since they arn't clamped
						temp = sizeof( float );   // change this to a short
						assert( size + temp < DATA_SIZE );
						memcpy( data + size, &v, temp );
						size += temp;
					}

					for ( k = 0 ; k < 3 ; k++ )
					{
						v = Q_rint( ( in->joints[j].placement.up[k] - outFrames[i].translate[k] ) / outFrames[i].scale[k] );

						// write out origin as a float since they arn't clamped
						temp = sizeof( float );   // change this to a short
						assert( size + temp < DATA_SIZE );
						memcpy( data + size, &v, temp );
						size += temp;
					}
				}
			}

		}
		else
		{
			temp = sizeof( int ); // change this to a byte
			memcpy( data + size, &framesWritten, temp );
			size += temp;
		}

		WriteHeader( modelouthandle, FM_SKELETON_NAME, FM_SKELETON_VER, size, data );
	}

	if ( g_skelModel.references != REF_NULL ) {
		int refnum;

		size = 0;
		if ( RefPointNum <= 0 ) { // Hard-coded labels
			refnum = numReferences[g_skelModel.references];
		}
		else
		{   // Labels indicated in QDT
			refnum = RefPointNum;
		}

		temp = sizeof( int ); // change this to a byte
		memcpy( data2 + size, &g_skelModel.references, temp );
		size += temp;

		if ( !num_groups ) {
			framesWritten = true;

			temp = sizeof( int ); // change this to a byte
			memcpy( data2 + size, &framesWritten, temp );
			size += temp;

			for ( i = 0; i < fmheader.num_frames; ++i )
			{
				in = &g_frames[i];

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

						// write out origin as a float since they arn't clamped
						temp = sizeof( float );   // change this to a short
						assert( size + temp < DATA_SIZE );
						memcpy( data2 + size, &v, temp );
						size += temp;
					}

					for ( k = 0 ; k < 3 ; k++ )
					{
						v = Q_rint( ( in->references[j].placement.direction[k] - outFrames[i].translate[k] ) / outFrames[i].scale[k] );

						// write out origin as a float since they arn't clamped
						temp = sizeof( float );   // change this to a short
						assert( size + temp < DATA_SIZE );
						memcpy( data2 + size, &v, temp );
						size += temp;
					}

					for ( k = 0 ; k < 3 ; k++ )
					{
						v = Q_rint( ( in->references[j].placement.up[k] - outFrames[i].translate[k] ) / outFrames[i].scale[k] );

						// write out origin as a float since they arn't clamped
						temp = sizeof( float );   // change this to a short
						assert( size + temp < DATA_SIZE );
						memcpy( data2 + size, &v, temp );
						size += temp;
					}
				}
			}
		}
		else    // FINISH ME: references need to be stored with regular verts for compressed models
		{
			framesWritten = false;

			temp = sizeof( int ); // change this to a byte
			memcpy( data2 + size, &framesWritten, temp );
			size += temp;
		}

		WriteHeader( modelouthandle, FM_REFERENCES_NAME, FM_REFERENCES_VER, size, data2 );
	}
}

static void CompressFrames(){
	fmgroup_t *g;
	int i,j,k;
	fmframe_t   *in;

	j = 0;
	for ( i = 0; i < fmheader.num_frames; i++ )
	{
		while ( i >= groups[j].start_frame + groups[j].num_frames && j < num_groups - 1 )
			j++;
		frame_to_group[i] = j;
	}

	for ( k = 0; k < num_groups; k++ )
	{
		g = &groups[k];

		printf( "\nCompressing Frames for group %i...\n", k );
		AnimCompressInit( g->num_frames,fmheader.num_xyz,g->degrees );
		for ( i = 0; i < g->num_frames; i++ )
		{
			in = &g_frames[i + g->start_frame];
			for ( j = 0; j < fmheader.num_xyz; j++ )
				AnimSetFrame( i,j,in->v[j].v[0],in->v[j].v[1],in->v[j].v[2] );
		}
		AnimCompressDoit();
		g->mat = (char *) SafeMalloc( fmheader.num_xyz * 3 * g->degrees * sizeof( char ), "CompressFrames" );
		g->ccomp = (char *) SafeMalloc( g->num_frames * g->degrees * sizeof( char ), "CompressFrames" );
		g->cbase = (char *) SafeMalloc( fmheader.num_xyz * 3 * sizeof( unsigned char ), "CompressFrames" );
		g->cscale = (float *) SafeMalloc( g->degrees * sizeof( float ), "CompressFrames" );
		g->coffset = (float *) SafeMalloc( g->degrees * sizeof( float ), "CompressFrames" );
		AnimCompressToBytes( g->trans,g->scale,g->mat,g->ccomp,g->cbase,g->cscale,g->coffset,g->bmin,g->bmax );
		AnimCompressEnd();
	}
}

static void OptimizeVertices( void ){
	qboolean vert_used[MAX_FM_VERTS];
	short vert_replacement[MAX_FM_VERTS];
	int i,j,k,l,pos,bit,set_pos,set_bit;
	fmframe_t   *in;
	qboolean Found;
	int num_unique;
	static IntListNode_t *newVertLists[NUM_CLUSTERS];
	static int newNum_verts[NUM_CLUSTERS];
	IntListNode_t *current, *next;

	printf( "Optimizing vertices..." );

	memset( vert_used, 0, sizeof( vert_used ) );

	if ( g_skelModel.clustered == true ) {
		memset( newNum_verts, 0, sizeof( newNum_verts ) );
		memset( newVertLists, 0, sizeof( newVertLists ) );
	}

	num_unique = 0;

	// search for common points among all the frames
	for ( i = 0 ; i < fmheader.num_frames ; i++ )
	{
		in = &g_frames[i];

		for ( j = 0; j < fmheader.num_xyz; j++ )
		{
			for ( k = 0,Found = false; k < j; k++ )
			{   // starting from the beginning always ensures vert_replacement points to the first point in the array
				if ( in->v[j].v[0] == in->v[k].v[0] &&
					 in->v[j].v[1] == in->v[k].v[1] &&
					 in->v[j].v[2] == in->v[k].v[2] ) {
					Found = true;
					vert_replacement[j] = k;
					break;
				}

			}

			if ( !Found ) {
				if ( !vert_used[j] ) {
					num_unique++;
				}
				vert_used[j] = true;
			}
		}
	}

	// recompute the light normals
	for ( i = 0 ; i < fmheader.num_frames ; i++ )
	{
		in = &g_frames[i];

		for ( j = 0; j < fmheader.num_xyz; j++ )
		{
			if ( !vert_used[j] ) {
				k = vert_replacement[j];

				VectorAdd( in->v[j].vnorm.normalsum, in->v[k].vnorm.normalsum, in->v[k].vnorm.normalsum );
				in->v[k].vnorm.numnormals += in->v[j].vnorm.numnormals++;
			}
		}

		for ( j = 0 ; j < fmheader.num_xyz ; j++ )
		{
			vec3_t v;
			float maxdot;
			int maxdotindex;
			int c;

			c = in->v[j].vnorm.numnormals;
			if ( !c ) {
				Error( "Vertex with no triangles attached" );
			}

			VectorScale( in->v[j].vnorm.normalsum, 1.0 / c, v );
			VectorNormalize( v, v );

			maxdot = -999999.0;
			maxdotindex = -1;

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

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

			in->v[j].lightnormalindex = maxdotindex;
		}
	}

	// create substitution list
	num_unique = 0;
	for ( i = 0; i < fmheader.num_xyz; i++ )
	{
		if ( vert_used[i] ) {
			vert_replacement[i] = num_unique;
			num_unique++;
		}
		else
		{
			vert_replacement[i] = vert_replacement[vert_replacement[i]];
		}

		// vert_replacement[i] is the new index, i is the old index
		// need to add the new index to the cluster list if old index was in it
		if ( g_skelModel.clustered == true ) {
			for ( k = 0; k < numJointsInSkeleton[g_skelModel.type]; ++k )
			{
				for ( l = 0, current = g_skelModel.vertLists[k];
					  l < g_skelModel.new_num_verts[k + 1]; ++l, current = current->next )
				{
					if ( current->data == i ) {
						IntListNode_t *current2;
						int m;
						qboolean added = false;

						for ( m = 0, current2 = newVertLists[k]; m < newNum_verts[k + 1];
							  ++m, current2 = current2->next )
						{
							if ( current2->data == vert_replacement[i] ) {
								added = true;
								break;
							}
						}

						if ( !added ) {
							++newNum_verts[k + 1];

							next = newVertLists[k];

							newVertLists[k] = (IntListNode_t *) SafeMalloc( sizeof( IntListNode_t ), "OptimizeVertices" );
							// freed after model write out

							newVertLists[k]->data = vert_replacement[i];
							newVertLists[k]->next = next;
						}
						break;
					}
				}
			}
		}
	}

	// substitute
	for ( i = 0 ; i < fmheader.num_frames ; i++ )
	{
		in = &g_frames[i];

		for ( j = 0; j < fmheader.num_xyz; j++ )
		{
			in->v[vert_replacement[j]] = in->v[j];
		}

	}

	for ( i = 0; i < numJointsInSkeleton[g_skelModel.type]; ++i )
	{
		IntListNode_t *toFree;
		current = g_skelModel.vertLists[i];

		while ( current )
		{
			toFree = current;
			current = current->next;
			free( toFree );  // freeing of memory allocated in ReplaceClusterIndex called in Cmd_Base
		}

		g_skelModel.vertLists[i] = newVertLists[i];
		g_skelModel.new_num_verts[i + 1] = newNum_verts[i + 1];
	}

#ifndef NDEBUG
	for ( k = 0; k < numJointsInSkeleton[g_skelModel.type]; ++k )
	{
		for ( l = 0, current = g_skelModel.vertLists[k];
			  l < g_skelModel.new_num_verts[k + 1]; ++l, current = current->next )
		{
			IntListNode_t *current2;
			int m;

			for ( m = l + 1, current2 = current->next; m < newNum_verts[k + 1];
				  ++m, current2 = current2->next )
			{
				if ( current->data == current2->data ) {
					printf( "Warning duplicate vertex:  %d\n", current->data );
					break;
				}
			}
		}
	}
#endif

	for ( i = 0; i < fmheader.num_mesh_nodes; i++ )
	{   // reset the vert bits
		memset( pmnodes[i].verts,0,sizeof( pmnodes[i].verts ) );
	}

	// repleace the master triangle list vertex indexes and update the vert bits for each mesh node
	for ( i = 0 ; i < fmheader.num_tris ; i++ )
	{
		pos = i >> 3;
		bit = 1 << ( i & 7 );

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

			set_pos >>= 3;
			set_bit = 1 << ( set_bit & 7 );

			for ( k = 0; k < fmheader.num_mesh_nodes; k++ )
			{
				if ( !( pmnodes[k].tris[pos] & bit ) ) {
					continue;
				}
				pmnodes[k].verts[set_pos] |= set_bit;
			}
		}
	}

	for ( i = 0; i < numcommands; i++ )
	{
		j = commands[i];
		if ( !j ) {
			continue;
		}

		j = abs( j );
		for ( i++; j; j--,i += 3 )
		{
			commands[i + 2] = vert_replacement[commands[i + 2]];
		}
		i--;
	}

	printf( "Reduced by %d\n",fmheader.num_xyz - num_unique );

	fmheader.num_xyz = num_unique;
	if ( num_groups ) {
		// tack on the reference verts to the regular verts
		if ( g_skelModel.references != REF_NULL ) {
			fmframe_t   *in;
			int index;
			int refnum;

			if ( RefPointNum <= 0 ) { // Hard-coded labels
				refnum = numReferences[g_skelModel.references];
			}
			else
			{   // Labels indicated in QDT
				refnum = RefPointNum;
			}


			for ( i = 0; i < fmheader.num_frames; ++i )
			{
				in = &g_frames[i];
				index = fmheader.num_xyz;

				for ( j = 0 ; j < refnum; ++j )
				{
					VectorCopy( in->references[j].placement.origin, in->v[index].v );
					index++;

					VectorCopy( in->references[j].placement.direction, in->v[index].v );
					index++;

					VectorCopy( in->references[j].placement.up, in->v[index].v );
					index++;
				}
			}

			fmheader.num_xyz += refnum * 3;
		}

		// tack on the skeletal joint verts to the regular verts
		if ( g_skelModel.type != SKEL_NULL ) {
			fmframe_t   *in;
			int index;

			for ( i = 0; i < fmheader.num_frames; ++i )
			{
				in = &g_frames[i];
				index = fmheader.num_xyz;

				for ( j = 0 ; j < numJointsInSkeleton[g_skelModel.type]; ++j )
				{
					VectorCopy( in->joints[j].placement.origin, in->v[index].v );
					index++;

					VectorCopy( in->joints[j].placement.direction, in->v[index].v );
					index++;

					VectorCopy( in->joints[j].placement.up, in->v[index].v );
					index++;
				}
			}

			fmheader.num_xyz += numJointsInSkeleton[g_skelModel.type] * 3;
		}

		CompressFrames();
	}
}


/*
   ===============
   FinishModel
   ===============
 */
void FMFinishModel( void ){
	FILE        *modelouthandle;
	int i,j,length,tris,verts,bit,pos,total_tris,total_verts;
	char name[1024];
	int trans_count;

	if ( !fmheader.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.fm", cdpartial );
		}
		ReleaseFile( name );

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

	printf( "\n" );

	trans_count = 0;
	for ( i = 0; i < fmheader.num_tris; i++ )
		if ( translucent[i] ) {
			trans_count++;
		}

	if ( !g_no_opimizations ) {
		OptimizeVertices();
	}

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

	WriteModelFile( modelouthandle );

	printf( "%3dx%3d skin\n", fmheader.skinwidth, fmheader.skinheight );
	printf( "First frame boundaries:\n" );
	printf( "	minimum x: %3f\n", g_frames[0].mins[0] );
	printf( "	maximum x: %3f\n", g_frames[0].maxs[0] );
	printf( "	minimum y: %3f\n", g_frames[0].mins[1] );
	printf( "	maximum y: %3f\n", g_frames[0].maxs[1] );
	printf( "	minimum z: %3f\n", g_frames[0].mins[2] );
	printf( "	maximum z: %3f\n", g_frames[0].maxs[2] );
	printf( "%4d vertices\n", fmheader.num_xyz );
	printf( "%4d triangles, %4d of them translucent\n", fmheader.num_tris, trans_count );
	printf( "%4d frame\n", fmheader.num_frames );
	printf( "%4d glverts\n", numglverts );
	printf( "%4d glcmd\n", fmheader.num_glcmds );
	printf( "%4d skins\n", fmheader.num_skins );
	printf( "%4d mesh nodes\n", fmheader.num_mesh_nodes );
	printf( "wasted pixels: %d / %d (%5.2f Percent)\n",total_skin_pixels - skin_pixels_used,
			total_skin_pixels, (double)( total_skin_pixels - skin_pixels_used ) / (double)total_skin_pixels * 100.0 );

	printf( "file size: %d\n", (int)ftell( modelouthandle ) );
	printf( "---------------------\n" );

	if ( g_verbose ) {
		if ( fmheader.num_mesh_nodes ) {
			total_tris = total_verts = 0;
			printf( "Node Name                         Tris Verts\n" );
			printf( "--------------------------------- ---- -----\n" );
			for ( i = 0; i < fmheader.num_mesh_nodes; i++ )
			{
				tris = 0;
				verts = 0;
				for ( j = 0; j < MAXTRIANGLES; j++ )
				{
					pos = ( j ) >> 3;
					bit = 1 << ( ( j ) & 7 );
					if ( pmnodes[i].tris[pos] & bit ) {
						tris++;
					}
				}
				for ( j = 0; j < MAX_FM_VERTS; j++ )
				{
					pos = ( j ) >> 3;
					bit = 1 << ( ( j ) & 7 );
					if ( pmnodes[i].verts[pos] & bit ) {
						verts++;
					}
				}

				printf( "%-33s %4d %5d\n",pmnodes[i].name,tris,verts );

				total_tris += tris;
				total_verts += verts;
			}
			printf( "--------------------------------- ---- -----\n" );
			printf( "%-33s %4d %5d\n","TOTALS",total_tris,total_verts );
		}
	}
	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 );

	// mesh nodes
	if ( fmheader.num_mesh_nodes ) {
		H_printf( "\n" );
		H_printf( "#define NUM_MESH_NODES\t\t%d\n\n",fmheader.num_mesh_nodes );
		for ( i = 0; i < fmheader.num_mesh_nodes; i++ )
		{
			strcpy( name, pmnodes[i].name );
			strupr( name );
			length = strlen( name );
			for ( j = 0; j < length; j++ )
			{
				if ( name[j] == ' ' ) {
					name[j] = '_';
				}
			}
			H_printf( "#define MESH_%s\t\t%d\n", name, i );
		}
	}

	fclose( headerouthandle );
	headerouthandle = NULL;
	free( pmnodes );
}


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

   ALIAS MODEL DISPLAY LIST GENERATION

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

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

/*
   ================
   StripLength
   ================
 */
static int  StripLength( int starttri, int startv, int num_tris, int node ){
	int m1, m2;
	int st1, st2;
	int j;
	fmtriangle_t    *last, *check;
	int k;
	int pos, bit;

	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 < num_tris ; j++, check++ )
	{
		pos = j >> 3;
		bit = 1 << ( j & 7 );
		if ( !( pmnodes[node].tris[pos] & bit ) ) {
			continue;
		}
		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] || translucent[j] != translucent[starttri] ) {
				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 < num_tris ; j++ )
		if ( used[j] == 2 ) {
			used[j] = 0;
		}

	return stripcount;
}


/*
   ===========
   FanLength
   ===========
 */
static int  FanLength( int starttri, int startv, int num_tris, int node ){
	int m1, m2;
	int st1, st2;
	int j;
	fmtriangle_t    *last, *check;
	int k;
	int pos, bit;

	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 < num_tris ; j++, check++ )
	{
		pos = j >> 3;
		bit = 1 << ( j & 7 );
		if ( !( pmnodes[node].tris[pos] & bit ) ) {
			continue;
		}
		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] || translucent[j] != translucent[starttri] ) {
				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 < 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
   ================
 */
static void BuildGlCmds( void ){
	int i, j, k, l;
	int startv;
	float s, t;
	int len, bestlen, besttype;
	int best_xyz[1024];
	int best_st[1024];
	int best_tris[1024];
	int type;
	int trans_check;
	int bit,pos;

	//
	// build tristrips
	//
	numcommands = 0;
	numglverts = 0;


	for ( l = 0; l < fmheader.num_mesh_nodes; l++ )
	{
		memset( used, 0, sizeof( used ) );

		pmnodes[l].start_glcmds = numcommands;

		for ( trans_check = 0; trans_check < 2; trans_check++ )
		{
			for ( i = 0 ; i < fmheader.num_tris ; i++ )
			{
				pos = i >> 3;
				bit = 1 << ( i & 7 );
				if ( !( pmnodes[l].tris[pos] & bit ) ) {
					continue;
				}

				// pick an unused triangle and start the trifan
				if ( used[i] || trans_check != translucent[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, fmheader.num_tris, l );
						}
						else{
							len = FanLength( i, startv, fmheader.num_tris, l );
						}
						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  ) / fmheader.skinwidth;
					t = ( t  ) / fmheader.skinheight;

					*(float *)&commands[numcommands++] = s;
					*(float *)&commands[numcommands++] = t;
					*(int *)&commands[numcommands++] = best_xyz[j];
				}
			}
		}
		commands[numcommands++] = 0;        // end of list marker
		pmnodes[l].num_glcmds = numcommands - pmnodes[l].start_glcmds;
	}
}


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

   BASE FRAME SETUP

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


#define LINE_NORMAL 1
#define LINE_FAT 2
#define LINE_DOTTED 3


#define ASCII_SPACE 32

int LineType = LINE_NORMAL;
extern unsigned char pic[SKINPAGE_HEIGHT * SKINPAGE_WIDTH], pic_palette[768];
unsigned char LineColor = 255;
int ScaleWidth, ScaleHeight;


static char *CharDefs[] =
{
	"-------------------------",
	"-------------------------", // !
	"-------------------------", // "
	"-------------------------", // #
	"-------------------------", // $
	"-------------------------", // %
	"-------------------------", // &
	"--*----*-----------------", // '
	"-*---*----*----*-----*---", // (
	"*-----*----*----*---*----", // )
	"-----*--*--**---**--*--*-", // *
	"-------------------------", // +
	"----------------**--**---", // ,
	"-------------------------", // -
	"----------------**---**--", // .
	"-------------------------", // /
	" *** *  *** * ***  * *** ", // 0
	"   *   **    *    *    * ",
	"****     * *** *    *****",
	"****     * ***     ***** ",
	"  **  * * *  * *****   * ",
	"**** *    ****     ***** ",
	" *** *    **** *   * *** ",
	"*****    *   *   *    *  ",
	" *** *   * *** *   * *** ",
	" *** *   * ****    * *** ", // 9
	"-**---**--------**---**--", // :
	"-------------------------", // ;
	"-------------------------", // <
	"-------------------------", // =
	"-------------------------", // >
	"-------------------------", // ?
	"-------------------------", // @
	"-***-*---*******---**---*", // A
	"****-*---*****-*---*****-",
	"-*****----*----*-----****",
	"****-*---**---**---*****-",
	"******----****-*----*****",
	"******----****-*----*----",
	"-*****----*--***---*-****",
	"*---**---*******---**---*",
	"-***---*----*----*---***-",
	"----*----*----**---*-***-",
	"-*--*-*-*--**---*-*--*--*",
	"-*----*----*----*----****",
	"*---***-***-*-**---**---*",
	"*---***--**-*-**--***---*",
	"-***-*---**---**---*-***-",
	"****-*---*****-*----*----",
	"-***-*---**---*-***----**",
	"****-*---*****-*-*--*--**",
	"-*****-----***-----*****-",
	"*****--*----*----*----*--",
	"*---**---**---**---******",
	"*---**---**---*-*-*---*--",
	"*---**---**-*-***-***---*",
	"*---*-*-*---*---*-*-*---*",
	"*---**---*-*-*---*----*--",
	"*****---*---*---*---*****" // Z
};

void DrawLine( int x1, int y1, int x2, int y2 ){
	int dx, dy;
	int adx, ady;
	int count;
	float xfrac, yfrac, xstep, ystep;
	unsigned sx, sy;
	float u, v;

	dx = x2 - x1;
	dy = y2 - y1;
	adx = abs( dx );
	ady = abs( dy );

	count = adx > ady ? adx : ady;
	count++;

	if ( count > 300 ) {
		printf( "Bad count\n" );
		return; // don't ever hang up on bad data
	}

	xfrac = x1;
	yfrac = y1;

	xstep = (float)dx / count;
	ystep = (float)dy / count;

	switch ( LineType )
	{
	case LINE_NORMAL:
		do
		{
			if ( xfrac < SKINPAGE_WIDTH && yfrac < SKINPAGE_HEIGHT ) {
				pic[(int)yfrac * SKINPAGE_WIDTH + (int)xfrac] = LineColor;
			}
			xfrac += xstep;
			yfrac += ystep;
			count--;
		} while ( count > 0 );
		break;
	case LINE_FAT:
		do
		{
			for ( u = -0.1 ; u <= 0.9 ; u += 0.999 )
			{
				for ( v = -0.1 ; v <= 0.9 ; v += 0.999 )
				{
					sx = xfrac + u;
					sy = yfrac + v;
					if ( sx < SKINPAGE_WIDTH && sy < SKINPAGE_HEIGHT ) {
						pic[sy * SKINPAGE_WIDTH + sx] = LineColor;
					}
				}
			}
			xfrac += xstep;
			yfrac += ystep;
			count--;
		} while ( count > 0 );
		break;
	case LINE_DOTTED:
		do
		{
			if ( count & 1 && xfrac < SKINPAGE_WIDTH &&
				 yfrac < SKINPAGE_HEIGHT ) {
				pic[(int)yfrac * SKINPAGE_WIDTH + (int)xfrac] = LineColor;
			}
			xfrac += xstep;
			yfrac += ystep;
			count--;
		} while ( count > 0 );
		break;
	default:
		Error( "Unknown <linetype> %d.\n", LineType );
	}
}

//==========================================================================
//
// DrawCharacter
//
//==========================================================================

static void DrawCharacter( int x, int y, int character ){
	int r, c;
	char *def;

	character = toupper( character );
	if ( character < ASCII_SPACE || character > 'Z' ) {
		character = ASCII_SPACE;
	}
	character -= ASCII_SPACE;
	for ( def = CharDefs[character], r = 0; r < 5; r++ )
	{
		for ( c = 0; c < 5; c++ )
		{
			pic[( y + r ) * SKINPAGE_WIDTH + x + c] = *def++ == '*' ? 255 : 0;
		}
	}
}

//==========================================================================
//
// DrawTextChar
//
//==========================================================================

void DrawTextChar( int x, int y, char *text ){
	int c;

	while ( ( c = *text++ ) != '\0' )
	{
		DrawCharacter( x, y, c );
		x += 6;
	}
}


extern void DrawScreen( float s_scale, float t_scale, float iwidth, float iheight );

//==========================================================================
// ExtractDigit

static int ExtractDigit( byte *pic, int x, int y ){
	int i;
	int r, c;
	char digString[32];
	char    *buffer;
	byte backColor;
	char    **DigitDefs;

	backColor = pic[( SKINPAGE_HEIGHT - 1 ) * SKINPAGE_WIDTH];
	DigitDefs = &CharDefs['0' - ASCII_SPACE];

	buffer = digString;
	for ( r = 0; r < 5; r++ )
	{
		for ( c = 0; c < 5; c++ )
		{
			*buffer++ = ( pic[( y + r ) * SKINPAGE_WIDTH + x + c] == backColor ) ? ' ' : '*';
		}
	}
	*buffer = '\0';
	for ( i = 0; i < 10; i++ )
	{
		if ( strcmp( DigitDefs[i], digString ) == 0 ) {
			return i;
		}
	}

	Error( "Unable to extract scaling info from skin PCX." );
	return 0;
}

//==========================================================================
// ExtractNumber

int ExtractNumber( byte *pic, int x, int y ){
	return ExtractDigit( pic, x, y ) * 100 + ExtractDigit( pic, x + 6, y ) * 10 + ExtractDigit( pic, x + 12, y );
}





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

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

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

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

	if ( ptri[0].HasUV ) { // if we have the uv already, we don't want to double up or scale
		iwidth = ScaleWidth;
		iheight = ScaleHeight;

		t_scale = s_scale = 1.0;
	}
	else
	{
		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];

		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 ) {
				backface_flag = true;
				break;
			}
		}
		scWidth = ScaleWidth * SCALE_ADJUST_FACTOR;
		if ( backface_flag ) {  //we are doubling
			scWidth /= 2;
		}

		scHeight = ScaleHeight * SCALE_ADJUST_FACTOR;

		scale = scWidth / width;

		if ( height * scale >= scHeight ) {
			scale = scHeight / height;
		}

		iwidth = ceil( width * scale ) + 4;
		iheight = ceil( height * scale ) + 4;

		s_scale = (float)( iwidth - 4 ) / width;
		t_scale = (float)( iheight - 4 ) / height;
		t_scale = s_scale;
	}
	if ( DrawSkin ) {
		if ( backface_flag ) {
			DrawScreen( s_scale, t_scale, iwidth * 2, iheight );
		}
		else{
			DrawScreen( s_scale, t_scale, iwidth, iheight );
		}
	}
	if ( backface_flag ) {
		skinwidth = iwidth * 2;
	}
	else{
		skinwidth = iwidth;
	}
	skinheight = iheight;


/*	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
//
	basey = 2;
	for ( i = 0 ; i < numtri ; i++ )
	{
		if ( ptri[i].HasUV ) {
			for ( j = 0 ; j < 3 ; j++ )
			{
				triangle_st[i][j][0] = Q_rint( ptri[i].uv[j][0] * skinwidth );
				triangle_st[i][j][1] = Q_rint( ( 1.0f - ptri[i].uv[j][1] ) * skinheight );
			}
		}
		else
		{
			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;
			}

			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 );
			}
		}

		if ( DrawSkin ) {
			DrawLine( triangle_st[i][0][0], triangle_st[i][0][1],
					  triangle_st[i][1][0], triangle_st[i][1][1] );
			DrawLine( triangle_st[i][1][0], triangle_st[i][1][1],
					  triangle_st[i][2][0], triangle_st[i][2][1] );
			DrawLine( triangle_st[i][2][0], triangle_st[i][2][1],
					  triangle_st[i][0][0], triangle_st[i][0][1] );
		}
	}

// make the width a multiple of 4; some hardware requires this, and it ensures
// dword alignment for each scan

	swidth = iwidth;
	if ( backface_flag ) {
		swidth *= 2;
	}
	fmheader.skinwidth = ( swidth + 3 ) & ~3;
	fmheader.skinheight = iheight;

	skin_width = iwidth;
	skin_height = iheight;
}


static void BuildNewST( triangle_t *ptri, int numtri, qboolean DrawSkin ){
	int i, j;

	for ( i = 0 ; i < numtri ; i++ )
	{
		if ( ptri[i].HasUV ) {
			for ( j = 0 ; j < 3 ; j++ )
			{
				triangle_st[i][j][0] = Q_rint( ptri[i].uv[j][0] * ( ScaleWidth - 1 ) );
				triangle_st[i][j][1] = Q_rint( ( 1.0f - ptri[i].uv[j][1] ) * ( ScaleHeight - 1 ) );
			}
		}

		if ( DrawSkin ) {
			DrawLine( triangle_st[i][0][0], triangle_st[i][0][1],
					  triangle_st[i][1][0], triangle_st[i][1][1] );
			DrawLine( triangle_st[i][1][0], triangle_st[i][1][1],
					  triangle_st[i][2][0], triangle_st[i][2][1] );
			DrawLine( triangle_st[i][2][0], triangle_st[i][2][1],
					  triangle_st[i][0][0], triangle_st[i][0][1] );
		}
	}

// make the width a multiple of 4; some hardware requires this, and it ensures
// dword alignment for each scan

	fmheader.skinwidth = ( ScaleWidth + 3 ) & ~3;
	fmheader.skinheight = ScaleHeight;

	skin_width = ScaleWidth;
	skin_height = ScaleHeight;
}




byte            *BasePalette;
byte            *BasePixels,*TransPixels;
int BaseWidth, BaseHeight, TransWidth, TransHeight;
qboolean BaseTrueColor;
static qboolean SetPixel = false;

int CheckTransRecursiveTri( int *lp1, int *lp2, int *lp3 ){
	int     *temp;
	int d;
	int new[2];

	d = lp2[0] - lp1[0];
	if ( d < -1 || d > 1 ) {
		goto split;
	}
	d = lp2[1] - lp1[1];
	if ( d < -1 || d > 1 ) {
		goto split;
	}

	d = lp3[0] - lp2[0];
	if ( d < -1 || d > 1 ) {
		goto split2;
	}
	d = lp3[1] - lp2[1];
	if ( d < -1 || d > 1 ) {
		goto split2;
	}

	d = lp1[0] - lp3[0];
	if ( d < -1 || d > 1 ) {
		goto split3;
	}
	d = lp1[1] - lp3[1];
	if ( d < -1 || d > 1 ) {
split3:
		temp = lp1;
		lp1 = lp3;
		lp3 = lp2;
		lp2 = temp;

		goto split;
	}

	return 0;           // entire tri is filled

split2:
	temp = lp1;
	lp1 = lp2;
	lp2 = lp3;
	lp3 = temp;

split:
// split this edge
	new[0] = ( lp1[0] + lp2[0] ) >> 1;
	new[1] = ( lp1[1] + lp2[1] ) >> 1;

// draw the point if splitting a leading edge
	if ( lp2[1] > lp1[1] ) {
		goto nodraw;
	}
	if ( ( lp2[1] == lp1[1] ) && ( lp2[0] < lp1[0] ) ) {
		goto nodraw;
	}

	if ( SetPixel ) {
		assert( ( new[1] * BaseWidth ) + new[0] < BaseWidth * BaseHeight );

		if ( BaseTrueColor ) {
			BasePixels[( ( new[1] * BaseWidth ) + new[0] ) * 4] = 1;
		}
		else
		{
			BasePixels[( new[1] * BaseWidth ) + new[0]] = 1;
		}
	}
	else
	{
		if ( TransPixels ) {
			if ( TransPixels[( new[1] * TransWidth ) + new[0]] != 255 ) {
				return 1;
			}
		}
		else if ( BaseTrueColor ) {
			if ( BasePixels[( ( ( new[1] * BaseWidth ) + new[0] ) * 4 ) + 3] != 255 ) {
				return 1;
			}
		}
		else
		{
//			pixel = BasePixels[(new[1]*BaseWidth) + new[0]];
		}
	}

nodraw:
// recursively continue
	if ( CheckTransRecursiveTri( lp3, lp1, new ) ) {
		return 1;
	}

	return CheckTransRecursiveTri( lp3, new, lp2 );
}

static void ReplaceClusterIndex( int newIndex, int oldindex, int **clusters,
								 IntListNode_t **vertLists, int *num_verts, int *new_num_verts ){
	int i, j;
	IntListNode_t *next;

	for ( j = 0; j < numJointsInSkeleton[g_skelModel.type]; ++j )
	{
		if ( !clusters[j] ) {
			continue;
		}

		for ( i = 0; i < num_verts[j + 1]; ++i )
		{
			if ( clusters[j][i] == oldindex ) {
				++new_num_verts[j + 1];

				next = vertLists[j];

				vertLists[j] = (IntListNode_t *) SafeMalloc( sizeof( IntListNode_t ), "ReplaceClusterIndex" );
				// Currently freed in WriteJointedModelFile only

				vertLists[j]->data = newIndex;
				vertLists[j]->next = next;
			}
		}
	}
}

#define FUDGE_EPSILON   0.002

qboolean VectorFudgeCompare( vec3_t v1, vec3_t v2 ){
	int i;

	for ( i = 0 ; i < 3 ; i++ )
		if ( fabs( v1[i] - v2[i] ) > FUDGE_EPSILON ) {
			return false;
		}

	return true;
}

/*
   =================
   Cmd_Base
   =================
 */
void Cmd_FMBase( qboolean GetST ){
	triangle_t  *ptri, *st_tri;
	int num_st_tris;
	int i, j, k, l;
	int x,y,z;
//	int			time1;
	char file1[1024],file2[1024],trans_file[1024], stfile[1024], extension[256];
	vec3_t base_xyz[MAX_FM_VERTS];
	FILE        *FH;
	int pos,bit;
	qboolean NewSkin;

	GetScriptToken( false );

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

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

	ExpandPathAndArchive( file1 );

	// Use the input filepath for this one.
	sprintf( file1, "%s/%s", cddir, token );

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

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

	if ( g_ignoreTriUV ) {
		for ( i = 0; i < fmheader.num_tris; i++ )
		{
			ptri[i].HasUV = 0;
		}
	}

	GetScriptToken( false );
	sprintf( file2, "%s/%s", cddir, token );
	sprintf( trans_file, "%s/!%s_a.pcx", cddir, token );

	ExtractFileExtension( file2, extension );
	if ( extension[0] == 0 ) {
		strcat( file2, ".pcx" );
	}
	printf( "skin: %s\n", file2 );

	BaseTrueColor = LoadAnyImage( file2, &BasePixels, &BasePalette, &BaseWidth, &BaseHeight );

	NewSkin = false;
	if ( BaseWidth != SKINPAGE_WIDTH || BaseHeight != SKINPAGE_HEIGHT ) {
		if ( g_allow_newskin ) {
			ScaleWidth = BaseWidth;
			ScaleHeight = BaseHeight;
			NewSkin = true;
		}
		else
		{
			Error( "Invalid skin page size: (%d,%d) should be (%d,%d)",
				   BaseWidth,BaseHeight,SKINPAGE_WIDTH,SKINPAGE_HEIGHT );
		}
	}
	else if ( !BaseTrueColor ) {
		ScaleWidth = (float)ExtractNumber( BasePixels, ENCODED_WIDTH_X,
										   ENCODED_WIDTH_Y );
		ScaleHeight = (float)ExtractNumber( BasePixels, ENCODED_HEIGHT_X,
											ENCODED_HEIGHT_Y );
	}
	else
	{
		Error( "Texture coordinates not supported on true color image" );
	}

	if ( GetST ) {
		GetScriptToken( false );

		sprintf( stfile, "%s/%s.%s", cdarchive, token, trifileext );
		printf( "ST: %s ", stfile );

		sprintf( stfile, "%s/%s", cddir, token );

		if ( do3ds ) {
			Load3DSTriangleList( stfile, &st_tri, &num_st_tris, NULL, NULL );
		}
		else{
			LoadTriangleList( stfile, &st_tri, &num_st_tris, NULL, NULL );
		}

		if ( num_st_tris != fmheader.num_tris ) {
			Error( "num st tris mismatch: st %d / base %d", num_st_tris, fmheader.num_tris );
		}

		printf( "   matching triangles...\n" );
		for ( i = 0; i < fmheader.num_tris; i++ )
		{
			k = -1;
			for ( j = 0; j < num_st_tris; j++ )
			{
				for ( x = 0; x < 3; x++ )
				{
					for ( y = 0; y < 3; y++ )
					{
						if ( x == y ) {
							continue;
						}
						for ( z = 0; z < 3; z++ )
						{
							if ( z == x || z == y ) {
								continue;
							}

							if ( VectorFudgeCompare( ptri[i].verts[0], st_tri[j].verts[x] ) &&
								 VectorFudgeCompare( ptri[i].verts[1], st_tri[j].verts[y] ) &&
								 VectorFudgeCompare( ptri[i].verts[2], st_tri[j].verts[z] ) ) {
								if ( k == -1 ) {
									k = j;
									ptri[i].HasUV = st_tri[k].HasUV;
									ptri[i].uv[0][0] = st_tri[k].uv[x][0];
									ptri[i].uv[0][1] = st_tri[k].uv[x][1];
									ptri[i].uv[1][0] = st_tri[k].uv[y][0];
									ptri[i].uv[1][1] = st_tri[k].uv[y][1];
									ptri[i].uv[2][0] = st_tri[k].uv[z][0];
									ptri[i].uv[2][1] = st_tri[k].uv[z][1];
									x = y = z = 999;
								}
								else if ( k != j ) {
									printf( "Duplicate triangle %d found in st file: %d and %d\n",i,k,j );
									printf( "   (%0.3f %0.3f %0.3f) (%0.3f %0.3f %0.3f) (%0.3f %0.3f %0.3f)\n",
											ptri[i].verts[0][0],ptri[i].verts[0][1],ptri[i].verts[0][2],
											ptri[i].verts[1][0],ptri[i].verts[1][1],ptri[i].verts[1][2],
											ptri[i].verts[2][0],ptri[i].verts[2][1],ptri[i].verts[2][2] );
									printf( "   (%0.3f %0.3f %0.3f) (%0.3f %0.3f %0.3f) (%0.3f %0.3f %0.3f)\n",
											st_tri[k].verts[0][0],st_tri[k].verts[0][1],st_tri[k].verts[0][2],
											st_tri[k].verts[1][0],st_tri[k].verts[1][1],st_tri[k].verts[1][2],
											st_tri[k].verts[2][0],st_tri[k].verts[2][1],st_tri[k].verts[2][2] );
									printf( "   (%0.3f %0.3f %0.3f) (%0.3f %0.3f %0.3f) (%0.3f %0.3f %0.3f)\n",
											st_tri[j].verts[0][0],st_tri[j].verts[0][1],st_tri[j].verts[0][2],
											st_tri[j].verts[1][0],st_tri[j].verts[1][1],st_tri[j].verts[1][2],
											st_tri[j].verts[2][0],st_tri[j].verts[2][1],st_tri[j].verts[2][2] );
								}
							}
						}
					}
				}
			}
			if ( k == -1 ) {
				printf( "No matching triangle %d\n",i );
			}
		}
		free( st_tri );
	}

//
// get the ST values
//
	if ( ptri && ptri[0].HasUV ) {
		if ( !NewSkin ) {
			Error( "Base has UVs with old style skin page\nMaybe you want to use -ignoreUV" );
		}
		else
		{
			BuildNewST( ptri, fmheader.num_tris, false );
		}
	}
	else
	{
		if ( NewSkin ) {
			Error( "Base has new style skin without UVs" );
		}
		else
		{
			BuildST( ptri, fmheader.num_tris, false );
		}
	}

	TransPixels = NULL;
	if ( !BaseTrueColor ) {
		FH = fopen( trans_file,"rb" );
		if ( FH ) {
			fclose( FH );
			Load256Image( trans_file, &TransPixels, NULL, &TransWidth, &TransHeight );
			if ( TransWidth != fmheader.skinwidth || TransHeight != fmheader.skinheight ) {
				Error( "source image %s dimensions (%d,%d) are not the same as alpha image (%d,%d)\n",file2,fmheader.skinwidth,fmheader.skinheight,TransWidth,TransHeight );
			}
		}
	}

//
// 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 ( l = 0; l < fmheader.num_mesh_nodes; l++ )
	{
		for ( i = 0 ; i < fmheader.num_tris ; i++ )
		{
			pos = i >> 3;
			bit = 1 << ( i & 7 );
			if ( !( pmnodes[l].tris[pos] & bit ) ) {
				continue;
			}

			for ( j = 0 ; j < 3 ; j++ )
			{
				// get the xyz index
				for ( k = 0 ; k < fmheader.num_xyz ; k++ )
				{
					if ( VectorCompare( ptri[i].verts[j], base_xyz[k] ) ) {
						break;  // this vertex is already in the base vertex list
					}
				}

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

					if ( pmnodes[l].clustered == true ) {
						ReplaceClusterIndex( k, ptri[i].indicies[j], (int **)&pmnodes[l].clusters, (IntListNode_t **)&g_skelModel.vertLists, (int *)&pmnodes[l].num_verts, (int *)&g_skelModel.new_num_verts );
					}

					fmheader.num_xyz++;
				}

				pos = k >> 3;
				bit = 1 << ( k & 7 );
				pmnodes[l].verts[pos] |= bit;

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

				// get the st index
				for ( k = 0 ; k < fmheader.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 == fmheader.num_st ) { // new index
					base_st[fmheader.num_st].s = triangle_st[i][j][0];
					base_st[fmheader.num_st].t = triangle_st[i][j][1];
					fmheader.num_st++;
				}

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

			if ( TransPixels || BaseTrueColor ) {
				translucent[i] = CheckTransRecursiveTri( triangle_st[i][0], triangle_st[i][1], triangle_st[i][2] );
			}
			else
			{
				translucent[i] = false;
			}
		}
	}

	if ( !BaseTrueColor ) {
		SetPixel = true;
		memset( BasePixels,0,BaseWidth * BaseHeight );
		for ( i = 0 ; i < fmheader.num_tris ; i++ )
		{
			CheckTransRecursiveTri( triangle_st[i][0], triangle_st[i][1], triangle_st[i][2] );
		}
		SetPixel = false;

		skin_pixels_used = 0;
		for ( i = 0; i < fmheader.skinheight; i++ )
		{
			for ( j = 0; j < fmheader.skinwidth; j++ )
			{
				skin_pixels_used += BasePixels[( i * BaseWidth ) + j];
			}
		}
		total_skin_pixels = fmheader.skinheight * fmheader.skinwidth;
	}
	else
	{
		SetPixel = true;
		memset( BasePixels,0,BaseWidth * BaseHeight * 4 );
		for ( i = 0 ; i < fmheader.num_tris ; i++ )
		{
			CheckTransRecursiveTri( triangle_st[i][0], triangle_st[i][1], triangle_st[i][2] );
		}
		SetPixel = false;

		skin_pixels_used = 0;
		for ( i = 0; i < fmheader.skinheight; i++ )
		{
			for ( j = 0; j < fmheader.skinwidth; j++ )
			{
				skin_pixels_used += BasePixels[( ( i * BaseWidth ) + j ) * 4];
			}
		}
		total_skin_pixels = fmheader.skinheight * fmheader.skinwidth;
	}

	// build triangle strips / fans
	BuildGlCmds();

	if ( TransPixels ) {
		free( TransPixels );
	}
	free( BasePixels );
	if ( BasePalette ) {
		free( BasePalette );
	}
	free( ptri );
}

void Cmd_FMNodeOrder( void ){
	mesh_node_t *newnodes, *pos;
	int i,j;

	if ( !pmnodes ) {
		Error( "Base has not been established yet" );
	}

	pos = newnodes = malloc( sizeof( mesh_node_t ) * fmheader.num_mesh_nodes );

	for ( i = 0; i < fmheader.num_mesh_nodes; i++ )
	{
		GetScriptToken( false );

		for ( j = 0; j < fmheader.num_mesh_nodes; j++ )
		{
			if ( strcmpi( pmnodes[j].name, token ) == 0 ) {
				*pos = pmnodes[j];
				pos++;
				break;
			}
		}
		if ( j >= fmheader.num_mesh_nodes ) {
			Error( "Node '%s' not in base list!\n", token );
		}
	}

	free( pmnodes );
	pmnodes = newnodes;
}

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

extern char *FindFrameFile( char *frame );


/*
   ===============
   GrabFrame
   ===============
 */
void GrabFrame( char *frame ){
	triangle_t      *ptri;
	int i, j;
	fmtrivert_t     *ptrivert;
	int num_tris;
	char file1[1024];
	fmframe_t       *fr;
	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  ", file1 );

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

	strcpy( fr->name, frame );

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

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

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

	for ( i = 0 ; i < fmheader.num_xyz ; i++ )
	{
		ptrivert[i].vnorm.numnormals = 0;
		VectorClear( ptrivert[i].vnorm.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( ptrivert[index_xyz].vnorm.normalsum, normal, ptrivert[index_xyz].vnorm.normalsum );
			ptrivert[index_xyz].vnorm.numnormals++;
		}
	}

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

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

		VectorScale( ptrivert[i].vnorm.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_FMFrame( void ){
	while ( ScriptTokenAvailable() )
	{
		GetScriptToken( false );
		if ( g_skipmodel ) {
			continue;
		}
		if ( g_release || g_archive ) {
			fmheader.num_frames = 1;    // don't skip the writeout
			continue;
		}

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

		if ( ( g_skelModel.type != SKEL_NULL ) || ( g_skelModel.references != REF_NULL ) ) {
			GrabModelTransform( token );
		}

		GrabFrame( token );

		if ( g_skelModel.type != SKEL_NULL ) {
			GrabSkeletalFrame( token );
		}

		if ( g_skelModel.references != REF_NULL ) {
			GrabReferencedFrame( token );
		}

		// need to add the up and dir points to the frame bounds here
		// using AddPointToBounds (ptrivert[index_xyz].v, fr->mins, fr->maxs);
		// then remove fudge in determining scale on frame write out
	}
}

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

   Skins aren't actually stored in the file, only a reference
   is saved out to the header file.
   ===============
 */
void Cmd_FMSkin( void ){
	byte        *palette;
	byte        *pixels;
	int width, height;
	byte        *cropped;
	int y;
	char name[1024], savename[1024], transname[1024], extension[256];
	miptex32_t  *qtex32;
	int size;
	FILE        *FH;
	qboolean TrueColor;

	GetScriptToken( false );

	if ( fmheader.num_skins == MAX_FM_SKINS ) {
		Error( "fmheader.num_skins == MAX_FM_SKINS" );
	}

	if ( g_skipmodel ) {
		return;
	}

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

	if ( ScriptTokenAvailable() ) {
		GetScriptToken( false );
		sprintf( g_skins[fmheader.num_skins], "!%s", token );
		sprintf( savename, "%s!%s", g_outputDir, token );
		sprintf( transname, "%s!%s_a.pcx", gamedir, token );
	}
	else
	{
		sprintf( g_skins[fmheader.num_skins], "%s/!%s", cdpartial, token );
		sprintf( savename, "%s/!%s", g_outputDir, token );
		sprintf( transname, "%s/!%s_a.pcx", cddir, token );
	}

	fmheader.num_skins++;

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

	// load the image
	printf( "loading %s\n", name );
	ExtractFileExtension( name, extension );
	if ( extension[0] == 0 ) {
		strcat( name, ".pcx" );
	}


	TrueColor = LoadAnyImage( name, &pixels, &palette, &width, &height );
//	RemapZero (pixels, palette, width, height);

	// crop it to the proper size

	if ( !TrueColor ) {
		cropped = (byte *) SafeMalloc( fmheader.skinwidth * fmheader.skinheight, "Cmd_FMSkin" );
		for ( y = 0 ; y < fmheader.skinheight ; y++ )
		{
			memcpy( cropped + y * fmheader.skinwidth,
					pixels + y * width, fmheader.skinwidth );
		}

		TransPixels = NULL;
		FH = fopen( transname,"rb" );
		if ( FH ) {
			fclose( FH );

			strcat( g_skins[fmheader.num_skins - 1],".pcx" );
			strcat( savename,".pcx" );

			// save off the new image
			printf( "saving %s\n", savename );
			CreatePath( savename );
			WritePCXfile( savename, cropped, fmheader.skinwidth, fmheader.skinheight, palette );
		}
		else
		{
	#if 1
			miptex_t    *qtex;
			qtex = CreateMip( cropped, fmheader.skinwidth, fmheader.skinheight, palette, &size, true );

			strcat( g_skins[fmheader.num_skins - 1],".m8" );
			strcat( savename,".m8" );

			printf( "saving %s\n", savename );
			CreatePath( savename );
			SaveFile( savename, (byte *)qtex, size );
			free( qtex );
	#else
			strcat( g_skins[fmheader.num_skins - 1],".pcx" );
			strcat( savename,".pcx" );

			// save off the new image
			printf( "saving %s\n", savename );
			CreatePath( savename );
			WritePCXfile( savename, cropped, fmheader.skinwidth, fmheader.skinheight, palette );
	#endif
		}
	}
	else
	{
		cropped = (byte *) SafeMalloc( fmheader.skinwidth * fmheader.skinheight * 4, "Cmd_FMSkin" );
		for ( y = 0 ; y < fmheader.skinheight ; y++ )
		{
			memcpy( cropped + ( ( y * fmheader.skinwidth ) * 4 ), pixels + ( y * width * 4 ), fmheader.skinwidth * 4 );
		}

		qtex32 = CreateMip32( (unsigned *)cropped, fmheader.skinwidth, fmheader.skinheight, &size, true );

		StripExtension( g_skins[fmheader.num_skins - 1] );
		strcat( g_skins[fmheader.num_skins - 1],".m32" );
		StripExtension( savename );
		strcat( savename,".m32" );

		printf( "saving %s\n", savename );
		CreatePath( savename );
		SaveFile( savename, (byte *)qtex32, size );
	}

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


/*
   ===============
   Cmd_Cd
   ===============
 */
void Cmd_FMCd( void ){
	char temp[256];

	FinishModel();
	ClearModel();

	GetScriptToken( 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 );

	// Since we also changed directories on the output side (for mirror) make sure the outputdir is set properly too.
	sprintf( temp, "%s%s", g_outputDir, cdpartial );
	strcpy( g_outputDir, temp );

	// 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 );
	}
}


/*

   //=======================
   //		NEW GEN
   //=======================

   void NewGen (char *ModelFile, char *OutputName, int width, int height)
   {
    trigroup_t  *triangles;
    triangle_t	*ptri;
    triangle_t	*grouptris;
    mesh_node_t	*pmnodes;

    vec3_t		*vertices;
    vec3_t		*uvs;
    vec3_t		aveNorm, crossvect;
    vec3_t		diffvect1, diffvect2;
    vec3_t		v0, v1, v2;
    vec3_t		n, u, v;
    vec3_t		base, zaxis, yaxis;
    vec3_t		uvwMin, uvwMax;
    vec3_t		groupMin, groupMax;
    vec3_t		uvw;

    float		*uFinal, *vFinal;
    unsigned char	*newpic;

    int			finalstart = 0, finalcount = 0;
    int			xbase = 0, xwidth = 0, ywidth = 0;
    int			*todo, *done, finished;
    int			i, j, k, l; //counters
    int			groupnum, numtris, numverts, num;
    int			count;
    FILE		*grpfile;
    long		datasize;

    for ( i = 0; i<3; i++)
    {
        aveNorm[i] = 0;
        uvwMin[i] = 1e30f;
        uvwMax[i] = -1e30f;
    }

    pmnodes = NULL;
    ptri = NULL;
    triangles = NULL;

    zaxis[0] = 0;
    zaxis[1] = 0;
    zaxis[2] = 1;

    yaxis[0] = 0;
    yaxis[1] = 1;
    yaxis[2] = 0;

    LoadTriangleList (ModelFile, &ptri, &fmheader.num_tris, &pmnodes, &fmheader.num_mesh_nodes);

    todo = (int*)SafeMalloc(fmheader.num_tris*sizeof(int), "NewGen");
    done = (int*)SafeMalloc(fmheader.num_tris*sizeof(int), "NewGen");
    triangles = (trigroup_t*)SafeMalloc(fmheader.num_tris*sizeof(trigroup_t), "NewGen");

    for ( i=0; i < fmheader.num_tris; i++)
    {
        todo[i] = false;
        done[i] = false;
        triangles[i].triangle = ptri[i];
        triangles[i].group = 0;
    }

    groupnum = 0;

   //  transitive closure algorithm follows
   //  put all triangles who transitively share vertices into separate groups

    while (1)
    {
        for ( i = 0; i < fmheader.num_tris; i++)
        {
            if (!done[i])
            {
                break;
            }
        }
        if ( i == fmheader.num_tris)
        {
            break;
        }
        finished = false;
        todo[i] = true;
        while (!finished)
        {
            finished = true;
            for ( i = 0; i < fmheader.num_tris; i++)
            {
                if (todo[i])
                {
                    done[i] = true;
                    triangles[i].group = groupnum;
                    todo[i] = false;
                    for ( j = 0; j < fmheader.num_tris; j++)
                    {
                        if ((!done[j]) && (ShareVertex(triangles[i],triangles[j])))
                        {
                            todo[j] = true;
                            finished = false;
                        }
                    }
                }
            }
        }
        groupnum++;
    }
        uFinal = (float*)SafeMalloc(3*fmheader.num_tris*sizeof(float), "NewGen");
        vFinal = (float*)SafeMalloc(3*fmheader.num_tris*sizeof(float), "NewGen");

    grpfile = fopen("grpdebug.txt","w");


    for (i = 0; i < groupnum; i++)
    {

        fprintf(grpfile,"Group Number: %d\n", i);

        numtris = GetNumTris(triangles, i); // number of triangles in group i
        numverts = numtris * 3;

        fprintf(grpfile,"%d triangles.\n", numtris);

        vertices = (vec3_t*)SafeMalloc(numverts*sizeof(vec3_t), "NewGen");
        uvs = (vec3_t*)SafeMalloc(numverts*sizeof(vec3_t), "NewGen");
        grouptris = (triangle_t*)SafeMalloc(numtris*sizeof(triangle_t), "NewGen");

        for (count = 0; count < fmheader.num_tris; count++)
        {
            if (triangles[count].group == i)
            {
                fprintf(grpfile,"Triangle %d\n", count);
            }
        }
        fprintf(grpfile,"\n");




        GetOneGroup(triangles, i, grouptris);

        num = 0;
        for (j = 0; j < numtris; j++)
        {
            VectorCopy(grouptris[j].verts[0], v0);
            VectorCopy(grouptris[j].verts[1], v1);
            VectorCopy(grouptris[j].verts[2], v2);
            VectorSubtract(v1, v0, diffvect1);
            VectorSubtract(v2, v1, diffvect2);
            CrossProduct( diffvect1, diffvect2, crossvect);
            VectorAdd(aveNorm, crossvect, aveNorm);
            VectorCopy(v0,vertices[num]);
            num++;					//  FIXME
            VectorCopy(v1,vertices[num]);
            num++;					//  add routine to add only verts that
            VectorCopy(v2,vertices[num]);
            num++;					// have not already been added
        }

        assert (num >= 3);
   // figure out the best plane projections
        DOsvdPlane ((float*)vertices, num, (float *)&n, (float *)&base);

        if (DotProduct(aveNorm,n) < 0.0f)
        {
            VectorScale(n, -1.0f, n);
        }
        VectorNormalize(n,n);
        if (fabs(n[2]) < .57)
        {
            CrossProduct( zaxis, n, crossvect);
            VectorCopy(crossvect, u);
        }
        else
        {
            CrossProduct( yaxis, n, crossvect);
            VectorCopy(crossvect, u);
        }
        VectorNormalize(u,u);
        CrossProduct( n, u, crossvect);
        VectorCopy(crossvect, v);
        VectorNormalize(v,v);

        num = 0;

        for ( j = 0; j < 3; j++)
        {
            groupMin[j] = 1e30f;
            groupMax[j] = -1e30f;
        }

        for ( j = 0; j < numtris; j++)
        {
            for ( k = 0; k < 3; k++)
            {
                VectorCopy(grouptris[j].verts[k],v0);
                VectorSubtract(v0, base, v0);
                uvw[0] = DotProduct(v0, u);
                uvw[1] = DotProduct(v0, v);
                uvw[2] = DotProduct(v0, n);
                VectorCopy(uvw,uvs[num]);
                num++;
                for ( l = 0; l < 3; l++)
                {
                    if (uvw[l] < groupMin[l])
                    {
                        groupMin[l] = uvw[l];
                    }
                    if (uvw[l] > groupMax[l])
                    {
                        groupMax[l] = uvw[l];
                    }
                }
            }
        }

        xwidth = ceil(0 - groupMin[0]) + 2; // move right of origin and avoid overlap
        ywidth = ceil(0 - groupMin[1]) + 2; // move "above" origin

        for ( j=0; j < numverts; j++)
        {
            uFinal[finalcount] = uvs[j][0] + xwidth + xbase;
            vFinal[finalcount] = uvs[j][1] + ywidth;
            if (uFinal[finalcount] < uvwMin[0])
            {
                uvwMin[0] = uFinal[finalcount];
            }
            if (uFinal[finalcount] > uvwMax[0])
            {
                uvwMax[0] = uFinal[finalcount];
            }
            if (vFinal[finalcount] < uvwMin[1])
            {
                uvwMin[1] = vFinal[finalcount];
            }
            if (vFinal[finalcount] > uvwMax[1])
            {
                uvwMax[1] = vFinal[finalcount];
            }
            finalcount++;
        }

        fprintf(grpfile,"svdPlaned Group min: ( %f , %f )\n",groupMin[0] + xwidth + xbase, groupMin[1] + ywidth);
        fprintf(grpfile,"svdPlaned Group max: ( %f , %f )\n",groupMax[0] + xwidth + xbase, groupMax[1] + ywidth);

        finalcount = finalstart;

        for ( count = 0; count < numverts; count++)
        {
            fprintf(grpfile,"Vertex %d: ( %f , %f , %f )\n",count,vertices[count][0],vertices[count][1],vertices[count][2]);
            fprintf(grpfile,"svdPlaned: ( %f , %f )\n",uFinal[finalcount],vFinal[finalcount++]);
        }

        finalstart = finalcount;

        fprintf(grpfile,"\n");

        free(vertices);
        free(uvs);
        free(grouptris);

        xbase += ceil(groupMax[0] - groupMin[0]) + 2;

    }

    fprintf(grpfile,"Global Min ( %f , %f )\n",uvwMin[0],uvwMin[1]);
    fprintf(grpfile,"Global Max ( %f , %f )\n",uvwMax[0],uvwMax[1]);


    ScaleTris(uvwMin, uvwMax, width, height, uFinal, vFinal, finalcount);

    for (k = 0; k < finalcount; k++)
    {
        fprintf(grpfile, "scaled vertex %d: ( %f , %f )\n",k,uFinal[k],vFinal[k]);
    }

    //  i've got the array of vertices in uFinal and vFinal.  Now I need to write them and draw lines

    datasize = width * height*sizeof(unsigned char);
    newpic = (unsigned char*)SafeMalloc(datasize, "NewGen");
    memset(newpic,0,datasize);
    memset(pic_palette,0,sizeof(pic_palette));
    pic_palette[767] = pic_palette[766] = pic_palette[765] = 255;

    k = 0;
    while (k < finalcount)
    {
        NewDrawLine(uFinal[k], vFinal[k], uFinal[k+1], vFinal[k+1], newpic, width, height);
        k++;
        NewDrawLine(uFinal[k], vFinal[k], uFinal[k+1], vFinal[k+1], newpic, width, height);
        k++;
        NewDrawLine(uFinal[k], vFinal[k], uFinal[k-2], vFinal[k-2], newpic, width, height);
        k++;
        fprintf(grpfile, "output tri with verts %d, %d, %d", k-2, k-1, k);
    }

    WritePCXfile (OutputName, newpic, width, height, pic_palette);

    fclose(grpfile);

    free(todo);
    free(done);
    free(triangles);
    free(newpic);
    return;
   }
   void NewDrawLine(int x1, int y1, int x2, int y2, unsigned char* picture, int width, int height)
   {
    long dx, dy;
    long adx, ady;
    long count;
    float xfrac, yfrac, xstep, ystep;
    unsigned long sx, sy;
    float u, v;

    dx = x2 - x1;
    dy = y2 - y1;
    adx = abs(dx);
    ady = abs(dy);

    count = adx > ady ? adx : ady;
    count++;

    if(count > 300)
    {
        printf("Bad count\n");
        return; // don't ever hang up on bad data
    }

    xfrac = x1;
    yfrac = y1;

    xstep = (float)dx/count;
    ystep = (float)dy/count;

    switch(LineType)
    {
        case LINE_NORMAL:
            do
            {
                if(xfrac < width && yfrac < height)
                {
                    picture[(long)yfrac*width+(long)xfrac] = LineColor;
                }
                xfrac += xstep;
                yfrac += ystep;
                count--;
            } while (count > 0);
            break;
        case LINE_FAT:
            do
            {
                for (u=-0.1 ; u<=0.9 ; u+=0.999)
                {
                    for (v=-0.1 ; v<=0.9 ; v+=0.999)
                    {
                        sx = xfrac+u;
                        sy = yfrac+v;
                        if(sx < width && sy < height)
                        {
                            picture[sy*width+sx] = LineColor;
                        }
                    }
                }
                xfrac += xstep;
                yfrac += ystep;
                count--;
            } while (count > 0);
            break;
        case LINE_DOTTED:
            do
            {
                if(count&1 && xfrac < width &&
                    yfrac < height)
                {
                    picture[(long)yfrac*width+(long)xfrac] = LineColor;
                }
                xfrac += xstep;
                yfrac += ystep;
                count--;
            } while (count > 0);
            break;
        default:
            Error("Unknown <linetype> %d.\n", LineType);
    }
   }
 */
void ScaleTris( vec3_t min, vec3_t max, int Width, int Height, float* u, float* v, int verts ){

	int i;
	float hscale, vscale;
	float scale;

	hscale = max[0];
	vscale = max[1];

	hscale = ( Width - 2 ) / max[0];
	vscale = ( Height - 2 ) / max[1];

	scale = hscale;
	if ( scale > vscale ) {
		scale = vscale;
	}
	for ( i = 0; i < verts; i++ )
	{
		u[i] *= scale;
		v[i] *= scale;
	}
	return;
}


void GetOneGroup( trigroup_t *tris, int grp, triangle_t* triangles ){
	int i;
	int j;

	j = 0;
	for ( i = 0; i < fmheader.num_tris; i++ )
	{
		if ( tris[i].group == grp ) {
			triangles[j++] = tris[i].triangle;
		}
	}
	return;
}


int GetNumTris( trigroup_t *tris, int grp ){
	int i;
	int verts;

	verts = 0;
	for ( i = 0; i < fmheader.num_tris; i++ )
	{
		if ( tris[i].group == grp ) {
			verts++;
		}
	}
	return verts;
}


int ShareVertex( trigroup_t trione, trigroup_t tritwo ){
	int i;
	int j;

	i = 1;
	j = 1;
	for ( i = 0; i < 3; i++ )
	{
		for ( j = 0; j < 3; j++ )
		{
			if ( DistBetween( trione.triangle.verts[i],tritwo.triangle.verts[j] ) < TRIVERT_DIST ) {
				return true;
			}
		}
	}
	return false;
}


float DistBetween( vec3_t point1, vec3_t point2 ){
	float dist;

	dist = ( point1[0] - point2[0] );
	dist *= dist;
	dist += ( point1[1] - point2[1] ) * ( point1[1] - point2[1] );
	dist += ( point1[2] - point2[2] ) * ( point1[2] - point2[2] );
	dist = sqrt( dist );
	return dist;
}


void GenSkin( char *ModelFile, char *OutputName, int Width, int Height ){
	triangle_t  *ptri;
	mesh_node_t *pmnodes;
	int i;

	pmnodes = NULL;
	ptri = NULL;

	LoadTriangleList( ModelFile, &ptri, &fmheader.num_tris, &pmnodes, &fmheader.num_mesh_nodes );
	if ( g_ignoreTriUV ) {
		for ( i = 0; i < fmheader.num_tris; i++ )
		{
			ptri[i].HasUV = 0;
		}
	}

	memset( pic,0,sizeof( pic ) );
	memset( pic_palette,0,sizeof( pic_palette ) );
	pic_palette[767] = pic_palette[766] = pic_palette[765] = 255;

	ScaleWidth = Width;
	ScaleHeight = Height;

	BuildST( ptri, fmheader.num_tris, true );

	WritePCXfile( OutputName, pic, SKINPAGE_WIDTH, SKINPAGE_HEIGHT, pic_palette );

	printf( "Gen Skin Stats:\n" );
	printf( "   Input Base: %s\n",ModelFile );
	printf( "   Input Dimensions: %d,%d\n",Width,Height );
	printf( "\n" );
	printf( "   Output File: %s\n",OutputName );
	printf( "   Output Dimensions: %d,%d\n",ScaleWidth,ScaleHeight );

	if ( fmheader.num_mesh_nodes ) {
		printf( "\nNodes:\n" );
		for ( i = 0; i < fmheader.num_mesh_nodes; i++ )
		{
			printf( "   %s\n",pmnodes[i].name );
		}
	}

	free( ptri );
	free( pmnodes );
}


void Cmd_FMBeginGroup( void ){
	GetScriptToken( false );

	g_no_opimizations = false;

	groups[num_groups].start_frame = fmheader.num_frames;
	groups[num_groups].num_frames = 0;

	groups[num_groups].degrees = atol( token );
	if ( groups[num_groups].degrees < 1 || groups[num_groups].degrees > 32 ) {
		Error( "Degrees of freedom out of range: %d",groups[num_groups].degrees );
	}
}

void Cmd_FMEndGroup( void ){
	groups[num_groups].num_frames = fmheader.num_frames - groups[num_groups].start_frame;

	if ( num_groups < MAX_GROUPS - 1 ) {
		num_groups++;
	}
	else
	{
		Error( "Number of compression groups exceded: %i\n", MAX_GROUPS );
	}
}
