/*
Copyright (C) 1999-2006 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
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);

#ifndef _WIN32

void strupr(char *string)
{
	int i;

	for (i=0 ; i<strlen(string); i++)
		toupper(string[i]);

	return;
}

#endif
//==============================================================

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

