]> git.xonotic.org Git - xonotic/netradiant.git/blobdiff - tools/quake3/q3data/models.c
set eol-style
[xonotic/netradiant.git] / tools / quake3 / q3data / models.c
index 05d65df75d4a658947fe454b468e49c206b26fa2..1d2d6904b7f9e54d83b83daf205c22c7fb02b6e3 100644 (file)
-#include <assert.h>\r
-#include "q3data.h"\r
-\r
-//=================================================================\r
-\r
-static void OrderSurfaces( void );\r
-static void LoadBase( const char *filename );\r
-static int     LoadModelFile( const char *filename, polyset_t **ppsets, int *pnumpolysets );\r
-\r
-#define MAX_SURFACE_TRIS       (SHADER_MAX_INDEXES / 3)\r
-#define MAX_SURFACE_VERTS      SHADER_MAX_VERTEXES\r
-\r
-#define MD3_TYPE_UNKNOWN 0\r
-#define MD3_TYPE_BASE3DS 1\r
-#define MD3_TYPE_SPRITE  2\r
-#define MD3_TYPE_ASE    3\r
-\r
-#define MAX_ANIM_FRAMES                512\r
-#define MAX_ANIM_SURFACES      32\r
-\r
-typedef struct\r
-{\r
-       polyset_t *frames;\r
-       int numFrames;\r
-} SurfaceAnimation_t;\r
-\r
-typedef struct\r
-{\r
-       polyset_t *surfaces[MAX_ANIM_SURFACES];\r
-       int numSurfaces;\r
-} ObjectAnimationFrame_t;\r
-\r
-typedef struct {\r
-       vec3_t          xyz;\r
-       vec3_t          normal;\r
-       vec3_t          color;\r
-       float           st[2];\r
-       int                     index;\r
-} baseVertex_t;\r
-       \r
-typedef struct {\r
-       baseVertex_t    v[3];\r
-} baseTriangle_t;\r
-\r
-//================================================================\r
-\r
-typedef struct\r
-{\r
-       md3Surface_t    header;\r
-       md3Shader_t             shaders[MD3_MAX_SHADERS];\r
-       // all verts (xyz_normal)\r
-       float   *verts[MD3_MAX_FRAMES];\r
-\r
-       baseTriangle_t  baseTriangles[MD3_MAX_TRIANGLES];\r
-\r
-       // the triangles will be sorted so that they form long generalized tristrips\r
-       int                             orderedTriangles[MD3_MAX_TRIANGLES][3];\r
-       int                             lodTriangles[MD3_MAX_TRIANGLES][3];\r
-       baseVertex_t    baseVertexes[MD3_MAX_VERTS];\r
-\r
-} md3SurfaceData_t;\r
-\r
-typedef struct\r
-{\r
-       int                     skinwidth, skinheight;\r
-       \r
-       md3SurfaceData_t surfData[MD3_MAX_SURFACES];\r
-\r
-       md3Tag_t                tags[MD3_MAX_FRAMES][MD3_MAX_TAGS];\r
-       md3Frame_t              frames[MD3_MAX_FRAMES];\r
-\r
-       md3Header_t     model;\r
-       float           scale_up;                       // set by $scale\r
-       vec3_t          adjust;                         // set by $origin\r
-       vec3_t          aseAdjust;\r
-       int                     fixedwidth, fixedheight;        // set by $skinsize\r
-\r
-       int                     maxSurfaceTris;\r
-\r
-       int                     lowerSkipFrameStart, lowerSkipFrameEnd;\r
-       int                     maxUpperFrames;\r
-       int                     maxHeadFrames;\r
-       int                     currentLod;\r
-       float           lodBias;\r
-\r
-       int                     type;           // MD3_TYPE_BASE, MD3_TYPE_OLDBASE, MD3_TYPE_ASE, or MD3_TYPE_SPRITE\r
-\r
-} q3data;\r
-\r
-q3data g_data;\r
-\r
-// the command list holds counts, the count * 3 xyz, st, normal indexes\r
-// that are valid for every frame\r
-char           g_cddir[1024];\r
-char           g_modelname[1024];\r
-\r
-//==============================================================\r
-\r
-/*\r
-===============\r
-ClearModel\r
-===============\r
-*/\r
-void ClearModel (void)\r
-{\r
-       int i;\r
-\r
-       g_data.type = MD3_TYPE_UNKNOWN;\r
-\r
-       for ( i = 0; i < MD3_MAX_SURFACES; i++ )\r
-       {\r
-               memset( &g_data.surfData[i].header, 0, sizeof( g_data.surfData[i].header ) );\r
-               memset( &g_data.surfData[i].shaders, 0, sizeof( g_data.surfData[i].shaders ) );\r
-               memset( &g_data.surfData[i].verts, 0, sizeof( g_data.surfData[i].verts ) );\r
-       }\r
-\r
-       memset( g_data.tags, 0, sizeof( g_data.tags ) );\r
-\r
-       for ( i = 0; i < g_data.model.numSurfaces; i++ )\r
-       {\r
-               int j;\r
-\r
-               for ( j = 0; j < g_data.surfData[i].header.numShaders; j++ )\r
-               {\r
-                       memset( &g_data.surfData[i].shaders[j], 0, sizeof( g_data.surfData[i].shaders[j] ) );\r
-               }\r
-       }\r
-       memset (&g_data.model, 0, sizeof(g_data.model));\r
-       memset (g_cddir, 0, sizeof(g_cddir));\r
-\r
-       g_modelname[0] = 0;\r
-       g_data.scale_up = 1.0;  \r
-       memset( &g_data.model, 0, sizeof( g_data.model ) );\r
-       VectorCopy (vec3_origin, g_data.adjust);\r
-       g_data.fixedwidth = g_data.fixedheight = 0;\r
-       g_skipmodel = qfalse;\r
-}\r
-\r
-/*\r
-** void WriteModelSurface( FILE *modelouthandle, md3SurfaceData_t *pSurfData )\r
-**\r
-** This routine assumes that the file position has been adjusted\r
-** properly prior to entry to point at the beginning of the surface.\r
-**\r
-** Since surface header information is completely relative, we can't\r
-** just randomly seek to an arbitrary surface location right now.  Is\r
-** this something we should add?\r
-*/\r
-void WriteModelSurface( FILE *modelouthandle, md3SurfaceData_t *pSurfData )\r
-{\r
-       md3Surface_t    *pSurf = &pSurfData->header;\r
-       md3Shader_t             *pShader = pSurfData->shaders;\r
-       baseVertex_t    *pBaseVertex = pSurfData->baseVertexes;\r
-       float                   **verts = pSurfData->verts;\r
-\r
-       short xyznormals[MD3_MAX_VERTS][4];\r
-\r
-       float base_st[MD3_MAX_VERTS][2];\r
-       md3Surface_t surftemp;\r
-\r
-       int f, i, j, k;\r
-\r
-       if ( strstr( pSurf->name, "tag_" ) == pSurf->name )\r
-               return;\r
-\r
-       //\r
-       // write out the header\r
-       //\r
-       surftemp = *pSurf;\r
-       surftemp.ident = LittleLong( MD3_IDENT );\r
-       surftemp.flags = LittleLong( pSurf->flags );\r
-       surftemp.numFrames = LittleLong( pSurf->numFrames );\r
-       surftemp.numShaders = LittleLong( pSurf->numShaders );\r
-\r
-       surftemp.ofsShaders = LittleLong( pSurf->ofsShaders );\r
-\r
-       surftemp.ofsTriangles = LittleLong( pSurf->ofsTriangles );\r
-       surftemp.numTriangles = LittleLong( pSurf->numTriangles );\r
-\r
-       surftemp.ofsSt = LittleLong( pSurf->ofsSt );\r
-       surftemp.ofsXyzNormals = LittleLong( pSurf->ofsXyzNormals );\r
-       surftemp.ofsEnd = LittleLong( pSurf->ofsEnd );\r
-\r
-       SafeWrite( modelouthandle, &surftemp, sizeof( surftemp ) );\r
-\r
-       if ( g_verbose )\r
-       {\r
-               printf( "surface '%s'\n", pSurf->name );\r
-               printf( "...num shaders: %d\n", pSurf->numShaders );\r
-       }\r
-\r
-       //\r
-       // write out shaders\r
-       //\r
-       for ( i = 0; i < pSurf->numShaders; i++ )\r
-       {\r
-               md3Shader_t shadertemp;\r
-\r
-               if ( g_verbose )\r
-                       printf( "......'%s'\n", pShader[i].name );\r
-\r
-               shadertemp = pShader[i];\r
-               shadertemp.shaderIndex = LittleLong( shadertemp.shaderIndex );\r
-               SafeWrite( modelouthandle, &shadertemp, sizeof( shadertemp ) );\r
-       }\r
-\r
-       //\r
-       // write out the triangles\r
-       //\r
-       for ( i = 0 ; i < pSurf->numTriangles ; i++ ) \r
-       {\r
-               for (j = 0 ; j < 3 ; j++) \r
-               {\r
-                       int ivalue = LittleLong( pSurfData->orderedTriangles[i][j] );\r
-                       pSurfData->orderedTriangles[i][j] = ivalue;\r
-               }\r
-       }\r
-\r
-       SafeWrite( modelouthandle, pSurfData->orderedTriangles, pSurf->numTriangles * sizeof( g_data.surfData[0].orderedTriangles[0] ) );\r
-\r
-       if ( g_verbose )\r
-       {\r
-               printf( "\n...num verts: %d\n", pSurf->numVerts );\r
-               printf( "...TEX COORDINATES\n" );\r
-       }\r
-\r
-       //\r
-       // write out the texture coordinates\r
-       //\r
-       for ( i = 0; i < pSurf->numVerts ; i++) {\r
-               base_st[i][0] = LittleFloat( pBaseVertex[i].st[0] );\r
-               base_st[i][1] = LittleFloat( pBaseVertex[i].st[1] );\r
-               if ( g_verbose )\r
-                       printf( "......%d: %f,%f\n", i, base_st[i][0], base_st[i][1] );\r
-       }\r
-       SafeWrite( modelouthandle, base_st, pSurf->numVerts * sizeof(base_st[0]));\r
-\r
-       //\r
-       // write the xyz_normal\r
-       //\r
-       if ( g_verbose )\r
-               printf( "...XYZNORMALS\n" );\r
-       for ( f = 0; f < g_data.model.numFrames; f++ )\r
-       {\r
-               for (j=0 ; j< pSurf->numVerts; j++) \r
-               {\r
-                       short value;\r
-\r
-                       for (k=0 ; k < 3 ; k++) \r
-                       {\r
-                               value = ( short ) ( verts[f][j*6+k] / MD3_XYZ_SCALE );\r
-                               xyznormals[j][k] = LittleShort( value );\r
-                       }\r
-                       NormalToLatLong( &verts[f][j*6+3], (byte *)&xyznormals[j][3] );\r
-               }\r
-               SafeWrite( modelouthandle, xyznormals, pSurf->numVerts * sizeof( short ) * 4 );\r
-       }\r
-}\r
-\r
-/*\r
-** void WriteModelFile( FILE *modelouthandle )\r
-**\r
-** CHUNK                       SIZE\r
-** header                      sizeof( md3Header_t )\r
-** frames                      sizeof( md3Frame_t ) * numFrames\r
-** tags                                sizeof( md3Tag_t ) * numFrames * numTags\r
-** surfaces                    surfaceSum\r
-*/\r
-void WriteModelFile( FILE *modelouthandle )\r
-{\r
-       int                             f;\r
-       int                             i, j;\r
-       md3Header_t             modeltemp;\r
-       long                    surfaceSum = 0;\r
-       int                             numRealSurfaces = 0;\r
-       int                             numFrames = g_data.model.numFrames;\r
-\r
-       // compute offsets for all surfaces, sum their total size\r
-       for ( i = 0; i < g_data.model.numSurfaces; i++ )\r
-       {\r
-               if ( strstr( g_data.surfData[i].header.name, "tag_" ) != g_data.surfData[i].header.name )\r
-               {\r
-                       md3Surface_t *psurf = &g_data.surfData[i].header;\r
-\r
-                       if ( psurf->numTriangles == 0 || psurf->numVerts == 0 )\r
-                               continue;\r
-\r
-                       //\r
-                       // the triangle and vertex split threshold is controlled by a parameter\r
-                       // to $base, a la $base blah.3ds 1900, where "1900" determines the number\r
-                       // of triangles to split on\r
-                       //\r
-                       else if ( psurf->numVerts > MAX_SURFACE_VERTS )\r
-                       {\r
-                               Error( "too many vertices\n" );\r
-                       }\r
-\r
-                       psurf->numFrames = numFrames;\r
-\r
-                       psurf->ofsShaders = sizeof( md3Surface_t );\r
-\r
-                       if ( psurf->numTriangles > MAX_SURFACE_TRIS  ) \r
-                       {\r
-                               Error( "too many faces\n" );\r
-                       }\r
-\r
-                       psurf->ofsTriangles = psurf->ofsShaders + psurf->numShaders * sizeof( md3Shader_t );\r
-\r
-                       psurf->ofsSt = psurf->ofsTriangles + psurf->numTriangles * sizeof( md3Triangle_t );\r
-                       psurf->ofsXyzNormals = psurf->ofsSt + psurf->numVerts * sizeof( md3St_t );\r
-                       psurf->ofsEnd = psurf->ofsXyzNormals + psurf->numFrames * psurf->numVerts * ( sizeof( short ) * 4 );\r
-\r
-                       surfaceSum += psurf->ofsEnd;\r
-\r
-                       numRealSurfaces++;\r
-               }\r
-       }\r
-\r
-       g_data.model.ident = MD3_IDENT;\r
-       g_data.model.version = MD3_VERSION;\r
-\r
-       g_data.model.ofsFrames = sizeof(md3Header_t);\r
-       g_data.model.ofsTags = g_data.model.ofsFrames + numFrames*sizeof(md3Frame_t);\r
-       g_data.model.ofsSurfaces = g_data.model.ofsTags + numFrames*g_data.model.numTags*sizeof(md3Tag_t);\r
-       g_data.model.ofsEnd = g_data.model.ofsSurfaces + surfaceSum;\r
-\r
-       //\r
-       // write out the model header\r
-       //\r
-       modeltemp = g_data.model;\r
-       modeltemp.ident = LittleLong( modeltemp.ident );\r
-       modeltemp.version = LittleLong( modeltemp.version );\r
-       modeltemp.numFrames = LittleLong( modeltemp.numFrames );\r
-       modeltemp.numTags = LittleLong( modeltemp.numTags );\r
-       modeltemp.numSurfaces = LittleLong( numRealSurfaces );\r
-       modeltemp.ofsFrames = LittleLong( modeltemp.ofsFrames );\r
-       modeltemp.ofsTags = LittleLong( modeltemp.ofsTags );\r
-       modeltemp.ofsSurfaces = LittleLong( modeltemp.ofsSurfaces );\r
-       modeltemp.ofsEnd = LittleLong( modeltemp.ofsEnd );\r
-\r
-       SafeWrite (modelouthandle, &modeltemp, sizeof(modeltemp));\r
-\r
-       //\r
-       // write out the frames\r
-       //\r
-       for (i=0 ; i < numFrames ; i++) \r
-       {\r
-               vec3_t tmpVec;\r
-               float maxRadius = 0;\r
-\r
-               //\r
-               // compute localOrigin and radius\r
-               //\r
-               g_data.frames[i].localOrigin[0] =\r
-               g_data.frames[i].localOrigin[1] =\r
-               g_data.frames[i].localOrigin[2] = 0;\r
-\r
-               for ( j = 0; j < 8; j++ )\r
-               {\r
-                       tmpVec[0] = g_data.frames[i].bounds[(j&1)!=0][0];\r
-                       tmpVec[1] = g_data.frames[i].bounds[(j&2)!=0][1];\r
-                       tmpVec[2] = g_data.frames[i].bounds[(j&4)!=0][2];\r
-\r
-                       if ( VectorLength( tmpVec ) > maxRadius )\r
-                               maxRadius = VectorLength( tmpVec );\r
-               }\r
-\r
-               g_data.frames[i].radius = LittleFloat( maxRadius );\r
-\r
-               // swap\r
-               for (j=0 ; j<3 ; j++) {\r
-                       g_data.frames[i].bounds[0][j] = LittleFloat( g_data.frames[i].bounds[0][j] );\r
-                       g_data.frames[i].bounds[1][j] = LittleFloat( g_data.frames[i].bounds[1][j] );\r
-                       g_data.frames[i].localOrigin[j] = LittleFloat( g_data.frames[i].localOrigin[j] );\r
-               }\r
-       }\r
-       fseek (modelouthandle, g_data.model.ofsFrames, SEEK_SET);\r
-       SafeWrite( modelouthandle, g_data.frames, numFrames * sizeof(g_data.frames[0]) );\r
-\r
-       //\r
-       // write out the tags\r
-       //\r
-       fseek( modelouthandle, g_data.model.ofsTags, SEEK_SET );\r
-       for (f=0 ; f<g_data.model.numFrames; f++) \r
-       {\r
-               int t;\r
-\r
-               for ( t = 0; t < g_data.model.numTags; t++ )\r
-               {\r
-                       g_data.tags[f][t].origin[0] = LittleFloat(g_data.tags[f][t].origin[0]);\r
-                       g_data.tags[f][t].origin[1] = LittleFloat(g_data.tags[f][t].origin[1]);\r
-                       g_data.tags[f][t].origin[2] = LittleFloat(g_data.tags[f][t].origin[2]);\r
-\r
-                       for (j=0 ; j<3 ; j++) \r
-                       {\r
-                               g_data.tags[f][t].axis[0][j] = LittleFloat(g_data.tags[f][t].axis[0][j]);\r
-                               g_data.tags[f][t].axis[1][j] = LittleFloat(g_data.tags[f][t].axis[1][j]);\r
-                               g_data.tags[f][t].axis[2][j] = LittleFloat(g_data.tags[f][t].axis[2][j]);\r
-                       }\r
-               }\r
-               SafeWrite( modelouthandle, g_data.tags[f], g_data.model.numTags * sizeof(md3Tag_t) );\r
-       }\r
-\r
-       //\r
-       // write out the surfaces\r
-       //\r
-       fseek( modelouthandle, g_data.model.ofsSurfaces, SEEK_SET );\r
-       for ( i = 0; i < g_data.model.numSurfaces; i++ )\r
-       {\r
-               WriteModelSurface( modelouthandle, &g_data.surfData[i] );\r
-       }\r
-}\r
-\r
-\r
-/*\r
-===============\r
-FinishModel\r
-===============\r
-*/\r
-void FinishModel ( int type )\r
-{\r
-       FILE            *modelouthandle;\r
-       FILE            *defaultSkinHandle;\r
-       char            name[1024];\r
-       int                     i;\r
-\r
-       if (!g_data.model.numFrames)\r
-               return;\r
-\r
-       //\r
-       // build generalized triangle strips\r
-       //\r
-       OrderSurfaces();\r
-\r
-       if ( type == TYPE_PLAYER )\r
-       {\r
-               sprintf( name, "%s%s", writedir, g_modelname );\r
-               *strrchr( name, '.' ) = 0;\r
-               strcat( name, "_default.skin" );\r
-\r
-               defaultSkinHandle = fopen( name, "wt" );\r
-               for ( i = 0; i < g_data.model.numSurfaces; i++ )\r
-               {\r
-                       fprintf( defaultSkinHandle, "%s,%s\n", g_data.surfData[i].header.name, g_data.surfData[i].shaders[0].name );\r
-               }\r
-               fclose( defaultSkinHandle );\r
-       }\r
-\r
-       sprintf (name, "%s%s", writedir, g_modelname);\r
-\r
-       //\r
-       // copy the model and its shaders to release directory tree \r
-       // if doing a release build\r
-       //\r
-       if ( g_release ) {\r
-               int                     i, j;\r
-               md3SurfaceData_t *pSurf;\r
-\r
-               ReleaseFile( g_modelname );\r
-\r
-               for ( i = 0; i < g_data.model.numSurfaces; i++ ) {\r
-                       pSurf = &g_data.surfData[i];\r
-                       for ( j = 0; j < g_data.model.numSkins; j++ ) {\r
-                               ReleaseShader( pSurf->shaders[j].name );\r
-                       }\r
-               }               \r
-               return;\r
-       }\r
-       \r
-       //\r
-       // write the model output file\r
-       //\r
-       printf ("saving to %s\n", name);\r
-       CreatePath (name);\r
-       modelouthandle = SafeOpenWrite (name);\r
-\r
-       WriteModelFile (modelouthandle);\r
-       \r
-       printf ("%4d surfaces\n", g_data.model.numSurfaces);\r
-       printf ("%4d frames\n", g_data.model.numFrames);\r
-       printf ("%4d tags\n", g_data.model.numTags);\r
-       printf ("file size: %d\n", (int)ftell (modelouthandle) );\r
-       printf ("---------------------\n");\r
-       \r
-       fclose (modelouthandle);\r
-}\r
-\r
-/*\r
-** OrderSurfaces\r
-**\r
-** Reorders triangles in all the surfaces.\r
-*/\r
-static void OrderSurfaces( void )\r
-{\r
-       int s;\r
-       extern qboolean g_stripify;\r
-\r
-       // go through each surface and find best strip/fans possible\r
-       for ( s = 0; s < g_data.model.numSurfaces; s++ )\r
-       {\r
-               int mesh[MD3_MAX_TRIANGLES][3];\r
-               int i;\r
-\r
-               printf( "stripifying surface %d/%d with %d tris\n", s, g_data.model.numSurfaces, g_data.surfData[s].header.numTriangles );\r
-\r
-               for ( i = 0; i < g_data.surfData[s].header.numTriangles; i++ )\r
-               {\r
-                       mesh[i][0] = g_data.surfData[s].lodTriangles[i][0];\r
-                       mesh[i][1] = g_data.surfData[s].lodTriangles[i][1];\r
-                       mesh[i][2] = g_data.surfData[s].lodTriangles[i][2];\r
-               }\r
-\r
-               if ( g_stripify )\r
-               {\r
-                       OrderMesh( mesh,                                                                        // input\r
-                                          g_data.surfData[s].orderedTriangles,         // output\r
-                                          g_data.surfData[s].header.numTriangles );\r
-               }\r
-               else\r
-               {\r
-                       memcpy( g_data.surfData[s].orderedTriangles, mesh, sizeof( int ) * 3 * g_data.surfData[s].header.numTriangles );\r
-               }\r
-       }\r
-}\r
-\r
-\r
-/*\r
-===============================================================\r
-\r
-BASE FRAME SETUP\r
-\r
-===============================================================\r
-*/\r
-/*\r
-============\r
-CopyTrianglesToBaseTriangles\r
-\r
-============\r
-*/\r
-static void CopyTrianglesToBaseTriangles(triangle_t *ptri, int numtri, baseTriangle_t *bTri )\r
-{\r
-       int                     i;\r
-//     int                     width, height, iwidth, iheight, swidth;\r
-//     float           s_scale, t_scale;\r
-//     float           scale;\r
-//     vec3_t          mins, maxs;\r
-       float           *pbasevert;\r
-\r
-/*\r
-       //\r
-       // find bounds of all the verts on the base frame\r
-       //\r
-       ClearBounds (mins, maxs);\r
-       \r
-       for (i=0 ; i<numtri ; i++)\r
-               for (j=0 ; j<3 ; j++)\r
-                       AddPointToBounds (ptri[i].verts[j], mins, maxs);\r
-       \r
-       for (i=0 ; i<3 ; i++)\r
-       {\r
-               mins[i] = floor(mins[i]);\r
-               maxs[i] = ceil(maxs[i]);\r
-       }\r
-       \r
-       width = maxs[0] - mins[0];\r
-       height = maxs[2] - mins[2];\r
-\r
-       if (!g_data.fixedwidth)\r
-       {       // old style\r
-               scale = 8;\r
-               if (width*scale >= 150)\r
-                       scale = 150.0 / width;  \r
-               if (height*scale >= 190)\r
-                       scale = 190.0 / height;\r
-\r
-               s_scale = t_scale = scale;\r
-\r
-               iwidth = ceil(width*s_scale);\r
-               iheight = ceil(height*t_scale);\r
-\r
-               iwidth += 4;\r
-               iheight += 4;\r
-       }\r
-       else\r
-       {       // new style\r
-               iwidth = g_data.fixedwidth / 2;\r
-               iheight = g_data.fixedheight;\r
-\r
-               s_scale = (float)(iwidth-4) / width;\r
-               t_scale = (float)(iheight-4) / height;\r
-       }\r
-\r
-       // make the width a multiple of 4; some hardware requires this, and it ensures\r
-       // dword alignment for each scan\r
-       swidth = iwidth*2;\r
-       g_data.skinwidth = (swidth + 3) & ~3;\r
-       g_data.skinheight = iheight;\r
-*/\r
-\r
-       for (i=0; i<numtri ; i++, ptri++, bTri++)\r
-       {\r
-               int j;\r
-\r
-               for (j=0 ; j<3 ; j++) \r
-               {\r
-                       pbasevert = ptri->verts[j];\r
-\r
-                       VectorCopy( ptri->verts[j], bTri->v[j].xyz);\r
-                       VectorCopy( ptri->normals[j], bTri->v[j].normal );\r
-\r
-                       bTri->v[j].st[0] = ptri->texcoords[j][0];\r
-                       bTri->v[j].st[1] = ptri->texcoords[j][1];\r
-               }\r
-       }\r
-}\r
-\r
-static void BuildBaseFrame( const char *filename, ObjectAnimationFrame_t *pOAF )\r
-{\r
-       baseTriangle_t  *bTri;\r
-       baseVertex_t    *bVert;\r
-       int i, j;\r
-\r
-       // calculate the base triangles\r
-       for ( i = 0; i < g_data.model.numSurfaces; i++ )\r
-       {\r
-               CopyTrianglesToBaseTriangles( pOAF->surfaces[i]->triangles, \r
-                                                       pOAF->surfaces[i]->numtriangles,\r
-                                                                       g_data.surfData[i].baseTriangles );\r
-\r
-               strcpy( g_data.surfData[i].header.name, pOAF->surfaces[i]->name );\r
-\r
-               g_data.surfData[i].header.numTriangles = pOAF->surfaces[i]->numtriangles;\r
-               g_data.surfData[i].header.numVerts = 0;\r
-\r
-/*\r
-               if ( strstr( filename, gamedir + 1 ) )\r
-               {\r
-                       strcpy( shaderName, strstr( filename, gamedir + 1 ) + strlen( gamedir ) - 1 );\r
-               }\r
-               else\r
-               {\r
-                       strcpy( shaderName, filename );\r
-               }\r
-\r
-               if ( strrchr( shaderName, '/' ) )\r
-                       *( strrchr( shaderName, '/' ) + 1 ) = 0;\r
-\r
-\r
-               strcpy( shaderName, pOAF->surfaces[i]->materialname );\r
-*/\r
-               strcpy( g_data.surfData[i].shaders[g_data.surfData[i].header.numShaders].name, pOAF->surfaces[i]->materialname );\r
-\r
-               g_data.surfData[i].header.numShaders++;\r
-       }\r
-\r
-       //\r
-       // compute unique vertices for each polyset\r
-       //\r
-       for ( i = 0; i < g_data.model.numSurfaces; i++ )\r
-       {\r
-               int t;\r
-\r
-               for ( t = 0; t < pOAF->surfaces[i]->numtriangles; t++ )\r
-               {\r
-                       bTri = &g_data.surfData[i].baseTriangles[t];\r
-\r
-                       for (j=0 ; j<3 ; j++)\r
-                       {\r
-                               int k;\r
-\r
-                               bVert = &bTri->v[j];\r
-\r
-                               // get the xyz index\r
-                               for ( k = 0; k < g_data.surfData[i].header.numVerts; k++ )\r
-                               {\r
-                                       if ( ( g_data.surfData[i].baseVertexes[k].st[0] == bVert->st[0] ) &&\r
-                                                ( g_data.surfData[i].baseVertexes[k].st[1] == bVert->st[1] ) &&\r
-                                                ( VectorCompare (bVert->xyz, g_data.surfData[i].baseVertexes[k].xyz) ) &&\r
-                                                ( VectorCompare (bVert->normal, g_data.surfData[i].baseVertexes[k].normal) ) )\r
-                                       {\r
-                                               break;  // this vertex is already in the base vertex list\r
-                                       }\r
-                               }\r
-\r
-                               if (k == g_data.surfData[i].header.numVerts)    { // new index\r
-                                       g_data.surfData[i].baseVertexes[g_data.surfData[i].header.numVerts] = *bVert;\r
-                                       g_data.surfData[i].header.numVerts++;\r
-                               }\r
-\r
-                               bVert->index = k;\r
-\r
-                               g_data.surfData[i].lodTriangles[t][j] = k;\r
-                       }\r
-               }\r
-       }\r
-\r
-       //\r
-       // find tags\r
-       //\r
-       for ( i = 0; i < g_data.model.numSurfaces; i++ )\r
-       {\r
-               if ( strstr( pOAF->surfaces[i]->name, "tag_" ) == pOAF->surfaces[i]->name )\r
-               {\r
-                       if ( pOAF->surfaces[i]->numtriangles != 1 )\r
-                       {\r
-                               Error( "tag polysets must consist of only one triangle" );\r
-                       }\r
-                       if ( strstr( filename, "_flash.md3" ) && !strcmp( pOAF->surfaces[i]->name, "tag_parent" ) )\r
-                               continue;\r
-                       printf( "found tag '%s'\n", pOAF->surfaces[i]->name );\r
-                       g_data.model.numTags++;\r
-               }\r
-       }\r
-\r
-}\r
-\r
-static int LoadModelFile( const char *filename, polyset_t **psets, int *numpolysets )\r
-{\r
-       int                     time1;\r
-       char            file1[1024];\r
-       const char                      *frameFile;\r
-\r
-       printf ("---------------------\n");\r
-       if ( filename[1] != ':' )\r
-       {\r
-               frameFile = filename;\r
-               sprintf( file1, "%s/%s", g_cddir, frameFile );\r
-       }\r
-       else\r
-       {\r
-               strcpy( file1, filename );\r
-       }\r
-\r
-       time1 = FileTime (file1);\r
-       if (time1 == -1)\r
-               Error ("%s doesn't exist", file1);\r
-\r
-       //\r
-       // load the base triangles\r
-       //\r
-       *psets = Polyset_LoadSets( file1, numpolysets, g_data.maxSurfaceTris );\r
-\r
-       //\r
-       // snap polysets\r
-       //\r
-       Polyset_SnapSets( *psets, *numpolysets );\r
-\r
-       if ( strstr( file1, ".3ds" ) || strstr( file1, ".3DS" ) )\r
-               return MD3_TYPE_BASE3DS;\r
-\r
-       Error( "Unknown model file type" );\r
-\r
-       return MD3_TYPE_UNKNOWN;\r
-}\r
-\r
-/*\r
-=================\r
-Cmd_Base\r
-=================\r
-*/\r
-void Cmd_Base( void )\r
-{\r
-       char filename[1024];\r
-\r
-       GetToken( qfalse );\r
-       sprintf( filename, "%s/%s", g_cddir, token );\r
-       LoadBase( filename );\r
-}\r
-\r
-static void LoadBase( const char *filename )\r
-{\r
-       int numpolysets;\r
-       polyset_t *psets;\r
-       int                     i;\r
-       ObjectAnimationFrame_t oaf;\r
-\r
-       // determine polyset splitting threshold\r
-       if ( TokenAvailable() )\r
-       {\r
-               GetToken( qfalse );\r
-               g_data.maxSurfaceTris = atoi( token );\r
-       }\r
-       else\r
-       {\r
-               g_data.maxSurfaceTris = MAX_SURFACE_TRIS - 1;\r
-       }\r
-\r
-       g_data.type = LoadModelFile( filename, &psets, &numpolysets );\r
-\r
-       Polyset_ComputeNormals( psets, numpolysets );\r
-\r
-       g_data.model.numSurfaces = numpolysets;\r
-\r
-       memset( &oaf, 0, sizeof( oaf ) );\r
-\r
-       for ( i = 0; i < numpolysets; i++ )\r
-       {\r
-               oaf.surfaces[i] = &psets[i];\r
-               oaf.numSurfaces = numpolysets;\r
-       }\r
-\r
-       BuildBaseFrame( filename, &oaf );\r
-\r
-       free( psets[0].triangles );\r
-       free( psets );\r
-}\r
-\r
-/*\r
-=================\r
-Cmd_SpriteBase\r
-\r
-$spritebase xorg yorg width height\r
-\r
-Generate a single square for the model\r
-=================\r
-*/\r
-void Cmd_SpriteBase (void)\r
-{\r
-       float           xl, yl, width, height;\r
-\r
-       g_data.type = MD3_TYPE_SPRITE;\r
-\r
-       GetToken (qfalse);\r
-       xl = atof(token);\r
-       GetToken (qfalse);\r
-       yl = atof(token);\r
-       GetToken (qfalse);\r
-       width = atof(token);\r
-       GetToken (qfalse);\r
-       height = atof(token);\r
-\r
-//     if (g_skipmodel || g_release || g_archive)\r
-//             return;\r
-\r
-       printf ("---------------------\n");\r
-\r
-       g_data.surfData[0].verts[0] = ( float * ) calloc( 1, sizeof( float ) * 6 * 4 );\r
-\r
-       g_data.surfData[0].header.numVerts = 4;\r
-\r
-       g_data.surfData[0].verts[0][0+0] = 0;\r
-       g_data.surfData[0].verts[0][0+1] = -xl;\r
-       g_data.surfData[0].verts[0][0+2] = yl + height;\r
-\r
-       g_data.surfData[0].verts[0][0+3] = -1;\r
-       g_data.surfData[0].verts[0][0+4] = 0;\r
-       g_data.surfData[0].verts[0][0+5] = 0;\r
-       g_data.surfData[0].baseVertexes[0].st[0] = 0;\r
-       g_data.surfData[0].baseVertexes[0].st[1] = 0;\r
-\r
-\r
-       g_data.surfData[0].verts[0][6+0] = 0;\r
-       g_data.surfData[0].verts[0][6+1] = -xl - width;\r
-       g_data.surfData[0].verts[0][6+2] = yl + height;\r
-       \r
-       g_data.surfData[0].verts[0][6+3] = -1;\r
-       g_data.surfData[0].verts[0][6+4] = 0;\r
-       g_data.surfData[0].verts[0][6+5] = 0;\r
-       g_data.surfData[0].baseVertexes[1].st[0] = 1;\r
-       g_data.surfData[0].baseVertexes[1].st[1] = 0;\r
-\r
-\r
-       g_data.surfData[0].verts[0][12+0] = 0;\r
-       g_data.surfData[0].verts[0][12+1] = -xl - width;\r
-       g_data.surfData[0].verts[0][12+2] = yl;\r
-\r
-       g_data.surfData[0].verts[0][12+3] = -1;\r
-       g_data.surfData[0].verts[0][12+4] = 0;\r
-       g_data.surfData[0].verts[0][12+5] = 0;\r
-       g_data.surfData[0].baseVertexes[2].st[0] = 1;\r
-       g_data.surfData[0].baseVertexes[2].st[1] = 1;\r
-\r
-\r
-       g_data.surfData[0].verts[0][18+0] = 0;\r
-       g_data.surfData[0].verts[0][18+1] = -xl;\r
-       g_data.surfData[0].verts[0][18+2] = yl;\r
-\r
-       g_data.surfData[0].verts[0][18+3] = -1;\r
-       g_data.surfData[0].verts[0][18+4] = 0;\r
-       g_data.surfData[0].verts[0][18+5] = 0;\r
-       g_data.surfData[0].baseVertexes[3].st[0] = 0;\r
-       g_data.surfData[0].baseVertexes[3].st[1] = 1;\r
-\r
-       g_data.surfData[0].lodTriangles[0][0] = 0;\r
-       g_data.surfData[0].lodTriangles[0][1] = 1;\r
-       g_data.surfData[0].lodTriangles[0][2] = 2;\r
-\r
-       g_data.surfData[0].lodTriangles[1][0] = 2;\r
-       g_data.surfData[0].lodTriangles[1][1] = 3;\r
-       g_data.surfData[0].lodTriangles[1][2] = 0;\r
-\r
-       g_data.model.numSurfaces = 1;\r
-\r
-       g_data.surfData[0].header.numTriangles = 2;\r
-       g_data.surfData[0].header.numVerts = 4;\r
-\r
-       g_data.model.numFrames = 1;\r
-}\r
-\r
-/*\r
-===========================================================================\r
-\r
-  FRAME GRABBING\r
-\r
-===========================================================================\r
-*/\r
-\r
-/*\r
-===============\r
-GrabFrame\r
-===============\r
-*/\r
-void GrabFrame (const char *frame)\r
-{\r
-       int                     i, j, k;\r
-       char            file1[1024];\r
-       md3Frame_t              *fr;\r
-       md3Tag_t                tagParent;\r
-       float           *frameXyz;\r
-       float           *frameNormals;\r
-       const char      *framefile;\r
-       polyset_t               *psets;\r
-       qboolean         parentTagExists = qfalse;\r
-       int                      numpolysets;\r
-       int                     numtags = 0;\r
-       int                     tagcount;\r
-\r
-       // the frame 'run1' will be looked for as either\r
-       // run.1 or run1.tri, so the new alias sequence save\r
-       // feature an be used\r
-       if ( frame[1] != ':' )\r
-       {\r
-//             framefile = FindFrameFile (frame);\r
-               framefile = frame;\r
-               sprintf (file1, "%s/%s",g_cddir, framefile);\r
-       }\r
-       else\r
-       {\r
-               strcpy( file1, frame );\r
-       }\r
-       printf ("grabbing %s\n", file1);\r
-\r
-       if (g_data.model.numFrames >= MD3_MAX_FRAMES)\r
-               Error ("model.numFrames >= MD3_MAX_FRAMES");\r
-       fr = &g_data.frames[g_data.model.numFrames];\r
-\r
-       strcpy (fr->name, frame);\r
-\r
-       psets = Polyset_LoadSets( file1, &numpolysets, g_data.maxSurfaceTris );\r
-\r
-       //\r
-       // snap polysets\r
-       //\r
-       Polyset_SnapSets( psets, numpolysets );\r
-\r
-       //\r
-       // compute vertex normals\r
-       //\r
-       Polyset_ComputeNormals( psets, numpolysets );\r
-\r
-       //\r
-       // flip everything to compensate for the alias coordinate system\r
-       // and perform global scale and adjust\r
-       //\r
-       for ( i = 0; i < g_data.model.numSurfaces; i++ )\r
-       {\r
-               triangle_t *ptri = psets[i].triangles;\r
-               int t;\r
-\r
-               for ( t = 0; t < psets[i].numtriangles; t++ )\r
-               {\r
-\r
-                       for ( j = 0; j < 3; j++ )\r
-                       {\r
-\r
-                               // scale and adjust\r
-                               for ( k = 0 ; k < 3 ; k++ ) {\r
-                                       ptri[t].verts[j][k] = ptri[t].verts[j][k] * g_data.scale_up +\r
-                                               g_data.adjust[k];\r
-\r
-                                       if ( ptri[t].verts[j][k] > 1023 ||\r
-                                                ptri[t].verts[j][k] < -1023 )\r
-                                       {\r
-                                               Error( "Model extents too large" );\r
-                                       }\r
-                               }\r
-                       }\r
-               }\r
-       }\r
-\r
-       //\r
-       // find and count tags, locate parent tag\r
-       //\r
-       for ( i = 0; i < numpolysets; i++ )\r
-       {\r
-               if ( strstr( psets[i].name, "tag_" ) == psets[i].name )\r
-               {\r
-                       if ( strstr( psets[i].name, "tag_parent" ) == psets[i].name )\r
-                       {\r
-                               if ( strstr( psets[i].name, "tag_parent" ) )\r
-                               {\r
-                                       float tri[3][3];\r
-\r
-                                       if ( parentTagExists )\r
-                                               Error( "Multiple parent tags not allowed" );\r
-\r
-                                       memcpy( tri[0], psets[i].triangles[0].verts[0], sizeof( float ) * 3 );\r
-                                       memcpy( tri[1], psets[i].triangles[0].verts[1], sizeof( float ) * 3 );\r
-                                       memcpy( tri[2], psets[i].triangles[0].verts[2], sizeof( float ) * 3 );\r
-\r
-                                       MD3_ComputeTagFromTri( &tagParent, tri );\r
-                                       strcpy( tagParent.name, psets[i].name );\r
-                                       g_data.tags[g_data.model.numFrames][numtags] = tagParent;\r
-                                       parentTagExists = qtrue;\r
-\r
-                               }\r
-                       }\r
-                       numtags++;\r
-               }\r
-\r
-               if ( strcmp( psets[i].name, g_data.surfData[i].header.name ) )\r
-               {\r
-                       Error( "Mismatched surfaces from base('%s') to frame('%s') in model '%s'\n", g_data.surfData[i].header.name, psets[i].name, g_modelname );\r
-               }\r
-       }\r
-\r
-       if ( numtags != g_data.model.numTags )\r
-       {\r
-               Error( "mismatched number of tags in frame(%d) vs. base(%d)", numtags, g_data.model.numTags );\r
-       }\r
-\r
-       if ( numpolysets != g_data.model.numSurfaces )\r
-       {\r
-               Error( "mismatched number of surfaces in frame(%d) vs. base(%d)", numpolysets-numtags, g_data.model.numSurfaces );\r
-       }\r
-       \r
-       //\r
-       // prepare to accumulate bounds and normals\r
-       //\r
-       ClearBounds( fr->bounds[0], fr->bounds[1] );\r
-\r
-       //\r
-       // store the frame's vertices in the same order as the base. This assumes the\r
-       // triangles and vertices in this frame are in exactly the same order as in the\r
-       // base\r
-       //\r
-       for ( i = 0, tagcount = 0; i < numpolysets; i++ )\r
-       {\r
-               int t;\r
-               triangle_t *pTris = psets[i].triangles;\r
-\r
-               strcpy( g_data.surfData[i].header.name, psets[i].name );\r
-\r
-               //\r
-               // parent tag adjust\r
-               //\r
-               if ( parentTagExists ) {\r
-                       for ( t = 0; t < psets[i].numtriangles; t++ )\r
-                       {\r
-                               for ( j = 0; j < 3 ; j++ )\r
-                               {\r
-                                       vec3_t tmp;\r
-                                       \r
-                                       VectorSubtract( pTris[t].verts[j], tagParent.origin, tmp );\r
-\r
-                                       pTris[t].verts[j][0] = DotProduct( tmp, tagParent.axis[0] );\r
-                                       pTris[t].verts[j][1] = DotProduct( tmp, tagParent.axis[1] );\r
-                                       pTris[t].verts[j][2] = DotProduct( tmp, tagParent.axis[2] );\r
-\r
-                                       VectorCopy( pTris[t].normals[j], tmp );\r
-                                       pTris[t].normals[j][0] = DotProduct( tmp, tagParent.axis[0] );\r
-                                       pTris[t].normals[j][1] = DotProduct( tmp, tagParent.axis[1] );\r
-                                       pTris[t].normals[j][2] = DotProduct( tmp, tagParent.axis[2] );\r
-                               }\r
-                       }\r
-               }\r
-\r
-               //\r
-               // compute tag data\r
-               //\r
-               if ( strstr( psets[i].name, "tag_" ) == psets[i].name )\r
-               {\r
-                       md3Tag_t *pTag = &g_data.tags[g_data.model.numFrames][tagcount];\r
-                       float tri[3][3];\r
-\r
-                       strcpy( pTag->name, psets[i].name );\r
-\r
-                       memcpy( tri[0], pTris[0].verts[0], sizeof( float ) * 3 );\r
-                       memcpy( tri[1], pTris[0].verts[1], sizeof( float ) * 3 );\r
-                       memcpy( tri[2], pTris[0].verts[2], sizeof( float ) * 3 );\r
-\r
-                       MD3_ComputeTagFromTri( pTag, tri );\r
-                       tagcount++;\r
-               }\r
-               else\r
-               {\r
-                       if ( g_data.surfData[i].verts[g_data.model.numFrames] )\r
-                               free( g_data.surfData[i].verts[g_data.model.numFrames] );\r
-                       frameXyz = g_data.surfData[i].verts[g_data.model.numFrames] = calloc( 1, sizeof( float ) * 6 * g_data.surfData[i].header.numVerts );\r
-                       frameNormals = frameXyz + 3;\r
-\r
-                       for ( t = 0; t < psets[i].numtriangles; t++ )\r
-                       {\r
-                               for ( j = 0; j < 3 ; j++ )\r
-                               {\r
-                                       int index;\r
-\r
-                                       index = g_data.surfData[i].baseTriangles[t].v[j].index;\r
-                                       frameXyz[index*6+0] = pTris[t].verts[j][0];\r
-                                       frameXyz[index*6+1] = pTris[t].verts[j][1];\r
-                                       frameXyz[index*6+2] = pTris[t].verts[j][2];\r
-                                       frameNormals[index*6+0] =  pTris[t].normals[j][0];\r
-                                       frameNormals[index*6+1] =  pTris[t].normals[j][1];\r
-                                       frameNormals[index*6+2] =  pTris[t].normals[j][2];\r
-                                       AddPointToBounds (&frameXyz[index*6], fr->bounds[0], fr->bounds[1] );\r
-                               }\r
-                       }\r
-               }\r
-       }\r
-\r
-       g_data.model.numFrames++;\r
-\r
-       // only free the first triangle array, all of the psets in this array share the\r
-       // same triangle pool!!!\r
-//     free( psets[0].triangles );\r
-//     free( psets );\r
-}\r
-\r
-//===========================================================================\r
-\r
-\r
-\r
-/*\r
-===============\r
-Cmd_Frame      \r
-===============\r
-*/\r
-void Cmd_Frame (void)\r
-{\r
-       while (TokenAvailable())\r
-       {\r
-               GetToken (qfalse);\r
-               if (g_skipmodel)\r
-                       continue;\r
-               if (g_release || g_archive)\r
-               {\r
-                       g_data.model.numFrames = 1;     // don't skip the writeout\r
-                       continue;\r
-               }\r
-\r
-               GrabFrame( token );\r
-       }\r
-}\r
-\r
-\r
-/*\r
-===============\r
-Cmd_Skin\r
-\r
-===============\r
-*/\r
-void SkinFrom3DS( const char *filename )\r
-{\r
-       polyset_t *psets;\r
-       char name[1024];\r
-       int numPolysets;\r
-       int i;\r
-\r
-       _3DS_LoadPolysets( filename, &psets, &numPolysets, g_verbose );\r
-\r
-       for ( i = 0; i < numPolysets; i++ )\r
-       {\r
-/*\r
-               if ( strstr( filename, gamedir + 1 ) )\r
-               {\r
-                       strcpy( name, strstr( filename, gamedir + 1 ) + strlen( gamedir ) - 1 );\r
-               }\r
-               else\r
-               {\r
-                       strcpy( name, filename );\r
-               }\r
-\r
-               if ( strrchr( name, '/' ) )\r
-                       *( strrchr( name, '/' ) + 1 ) = 0;\r
-*/\r
-               strcpy( name, psets[i].materialname );\r
-               strcpy( g_data.surfData[i].shaders[g_data.surfData[i].header.numShaders].name, name );\r
-\r
-               g_data.surfData[i].header.numShaders++;\r
-       }\r
-\r
-       free( psets[0].triangles );\r
-       free( psets );\r
-}\r
-\r
-void Cmd_Skin (void)\r
-{\r
-       char skinfile[1024];\r
-\r
-       if ( g_data.type == MD3_TYPE_BASE3DS )\r
-       {\r
-               GetToken( qfalse );\r
-\r
-               sprintf( skinfile, "%s/%s", g_cddir, token );\r
-\r
-               if ( strstr( token, ".3ds" ) || strstr( token, ".3DS" ) )\r
-               {\r
-                       SkinFrom3DS( skinfile );\r
-               }\r
-               else\r
-               {\r
-                       Error( "Unknown file format for $skin '%s'\n", skinfile );\r
-               }\r
-       }\r
-       else\r
-       {\r
-               Error( "invalid model type while processing $skin" );\r
-       }\r
-\r
-       g_data.model.numSkins++;\r
-}\r
-\r
-/*\r
-=================\r
-Cmd_SpriteShader\r
-=================\r
-\r
-This routine is also called for $oldskin\r
-\r
-*/\r
-void Cmd_SpriteShader()\r
-{\r
-       GetToken( qfalse );\r
-       strcpy( g_data.surfData[0].shaders[g_data.surfData[0].header.numShaders].name, token );\r
-       g_data.surfData[0].header.numShaders++;\r
-       g_data.model.numSkins++;\r
-}\r
-\r
-/*\r
-=================\r
-Cmd_Origin\r
-=================\r
-*/\r
-void Cmd_Origin (void)\r
-{\r
-       // rotate points into frame of reference so model points down the\r
-       // positive x axis\r
-       // FIXME: use alias native coordinate system\r
-       GetToken (qfalse);\r
-       g_data.adjust[1] = -atof (token);\r
-\r
-       GetToken (qfalse);\r
-       g_data.adjust[0] = atof (token);\r
-\r
-       GetToken (qfalse);\r
-       g_data.adjust[2] = -atof (token);\r
-}\r
-\r
-\r
-/*\r
-=================\r
-Cmd_ScaleUp\r
-=================\r
-*/\r
-void Cmd_ScaleUp (void)\r
-{\r
-       GetToken (qfalse);\r
-       g_data.scale_up = atof (token);\r
-       if (g_skipmodel || g_release || g_archive)\r
-               return;\r
-\r
-       printf ("Scale up: %f\n", g_data.scale_up);\r
-}\r
-\r
-\r
-/*\r
-=================\r
-Cmd_Skinsize\r
-\r
-Set a skin size other than the default\r
-QUAKE3: not needed\r
-=================\r
-*/\r
-void Cmd_Skinsize (void)\r
-{\r
-       GetToken (qfalse);\r
-       g_data.fixedwidth = atoi(token);\r
-       GetToken (qfalse);\r
-       g_data.fixedheight = atoi(token);\r
-}\r
-\r
-/*\r
-=================\r
-Cmd_Modelname\r
-\r
-Begin creating a model of the given name\r
-=================\r
-*/\r
-void Cmd_Modelname (void)\r
-{\r
-       FinishModel ( TYPE_UNKNOWN );\r
-       ClearModel ();\r
-\r
-       GetToken (qfalse);\r
-       strcpy (g_modelname, token);\r
-       StripExtension (g_modelname);\r
-       strcat (g_modelname, ".md3");\r
-       strcpy (g_data.model.name, g_modelname);\r
-}\r
-\r
-/*\r
-===============\r
-fCmd_Cd\r
-===============\r
-*/\r
-void Cmd_Cd (void)\r
-{\r
-       if ( g_cddir[0]) {\r
-               Error ("$cd command without a $modelname");\r
-       }\r
-\r
-       GetToken (qfalse);\r
-\r
-       sprintf ( g_cddir, "%s%s", gamedir, token);\r
-\r
-       // if -only was specified and this cd doesn't match,\r
-       // skip the model (you only need to match leading chars,\r
-       // so you could regrab all monsters with -only models/monsters)\r
-       if (!g_only[0])\r
-               return;\r
-       if (strncmp(token, g_only, strlen(g_only)))\r
-       {\r
-               g_skipmodel = qtrue;\r
-               printf ("skipping %s\n", token);\r
-       }\r
-}\r
-\r
-void Convert3DStoMD3( const char *file )\r
-{\r
-       LoadBase( file );\r
-       GrabFrame( file );\r
-       SkinFrom3DS( file );\r
-\r
-       strcpy( g_data.model.name, g_modelname );\r
-\r
-       FinishModel( TYPE_UNKNOWN );\r
-       ClearModel();\r
-}\r
-\r
-/*\r
-** Cmd_3DSConvert\r
-*/\r
-void Cmd_3DSConvert()\r
-{\r
-       char file[1024];\r
-\r
-       FinishModel( TYPE_UNKNOWN );\r
-       ClearModel();\r
-\r
-       GetToken( qfalse );\r
-\r
-       sprintf( file, "%s%s", gamedir, token );\r
-       strcpy( g_modelname, token );\r
-       if ( strrchr( g_modelname, '.' ) )\r
-               *strrchr( g_modelname, '.' ) = 0;\r
-       strcat( g_modelname, ".md3" );\r
-\r
-       if ( FileTime( file ) == -1 )\r
-               Error( "%s doesn't exist", file );\r
-\r
-       if ( TokenAvailable() )\r
-       {\r
-               GetToken( qfalse );\r
-               g_data.scale_up = atof( token );\r
-       }\r
-\r
-       Convert3DStoMD3( file );\r
-}\r
-\r
-static void ConvertASE( const char *filename, int type, qboolean grabAnims );\r
-\r
-/*\r
-** Cmd_ASEConvert\r
-*/\r
-void Cmd_ASEConvert( qboolean grabAnims )\r
-{\r
-       char filename[1024];\r
-       int type = TYPE_ITEM;\r
-\r
-       FinishModel( TYPE_UNKNOWN );\r
-       ClearModel();\r
-\r
-       GetToken( qfalse );\r
-       sprintf( filename, "%s%s", gamedir, token );\r
-\r
-       strcpy (g_modelname, token);\r
-       StripExtension (g_modelname);\r
-       strcat (g_modelname, ".md3");\r
-       strcpy (g_data.model.name, g_modelname);\r
-\r
-       if ( !strstr( filename, ".ase" ) && !strstr( filename, ".ASE" ) )\r
-               strcat( filename, ".ASE" );\r
-\r
-       g_data.maxSurfaceTris = MAX_SURFACE_TRIS - 1;\r
-\r
-       while ( TokenAvailable() )\r
-       {\r
-               GetToken( qfalse );\r
-               if ( !strcmp( token, "-origin" ) )\r
-               {\r
-                       if ( !TokenAvailable() )\r
-                               Error( "missing parameter for -origin" );\r
-                       GetToken( qfalse );\r
-                       g_data.aseAdjust[1] = -atof( token );\r
-\r
-                       if ( !TokenAvailable() )\r
-                               Error( "missing parameter for -origin" );\r
-                       GetToken( qfalse );\r
-                       g_data.aseAdjust[0] = atof (token);\r
-\r
-                       if ( !TokenAvailable() )\r
-                               Error( "missing parameter for -origin" );\r
-                       GetToken( qfalse );\r
-                       g_data.aseAdjust[2] = -atof (token);\r
-               }\r
-               else if ( !strcmp( token, "-lod" ) )\r
-               {\r
-                       if ( !TokenAvailable() )\r
-                               Error( "No parameter for -lod" );\r
-                       GetToken( qfalse );\r
-                       g_data.currentLod = atoi( token );\r
-                       if ( g_data.currentLod > MD3_MAX_LODS - 1 )\r
-                       {\r
-                               Error( "-lod parameter too large! (%d)\n", g_data.currentLod );\r
-                       }\r
-\r
-                       if ( !TokenAvailable() )\r
-                               Error( "No second parameter for -lod" );\r
-                       GetToken( qfalse );\r
-                       g_data.lodBias = atof( token );\r
-               }\r
-               else if ( !strcmp( token, "-maxtris" ) )\r
-               {\r
-                       if ( !TokenAvailable() )\r
-                               Error( "No parameter for -maxtris" );\r
-                       GetToken( qfalse );\r
-                       g_data.maxSurfaceTris = atoi( token );\r
-               }\r
-               else if ( !strcmp( token, "-playerparms" ) )\r
-               {\r
-                       if ( !TokenAvailable() )\r
-                               Error( "missing skip start parameter for -playerparms" );\r
-                       GetToken( qfalse );\r
-                       g_data.lowerSkipFrameStart = atoi( token );\r
-\r
-#if 0\r
-                       if ( !TokenAvailable() )\r
-                               Error( "missing skip end parameter for -playerparms" );\r
-                       GetToken( qfalse );\r
-                       g_data.lowerSkipFrameEnd = atoi( token );\r
-#endif\r
-\r
-                       if ( !TokenAvailable() )\r
-                               Error( "missing upper parameter for -playerparms" );\r
-                       GetToken( qfalse );\r
-                       g_data.maxUpperFrames = atoi( token );\r
-\r
-                       g_data.lowerSkipFrameEnd = g_data.maxUpperFrames - 1;\r
-\r
-#if 0\r
-                       if ( !TokenAvailable() )\r
-                               Error( "missing head parameter for -playerparms" );\r
-                       GetToken( qfalse );\r
-                       g_data.maxHeadFrames = atoi( token );\r
-#endif\r
-                       g_data.maxHeadFrames = 1;\r
-\r
-                       if ( type != TYPE_ITEM )\r
-                               Error( "invalid argument" );\r
-\r
-                       type = TYPE_PLAYER;\r
-               }\r
-               else if ( !strcmp( token, "-weapon" ) )\r
-               {\r
-                       if ( type != TYPE_ITEM )\r
-                               Error( "invalid argument" );\r
-\r
-                       type = TYPE_WEAPON;\r
-               }\r
-       }\r
-\r
-       g_data.type = MD3_TYPE_ASE;\r
-\r
-       if ( type == TYPE_WEAPON && grabAnims )\r
-       {\r
-               Error( "can't grab anims with weapon models" );\r
-       }\r
-       if ( type == TYPE_PLAYER && !grabAnims )\r
-       {\r
-               Error( "player models must be converted with $aseanimconvert" );\r
-       }\r
-\r
-       if ( type == TYPE_WEAPON )\r
-       {\r
-               ConvertASE( filename, type, qfalse );\r
-               ConvertASE( filename, TYPE_HAND, qtrue );\r
-       }\r
-       else\r
-       {\r
-               ConvertASE( filename, type, grabAnims );\r
-       }\r
-}\r
-\r
-static int GetSurfaceAnimations( SurfaceAnimation_t sanims[MAX_ANIM_SURFACES], \r
-                                                                 const char *part,\r
-                                                                 int skipFrameStart,\r
-                                                                 int skipFrameEnd,\r
-                                                                 int maxFrames )\r
-\r
-{\r
-       int numSurfaces;\r
-       int numValidSurfaces;\r
-       int i;\r
-       int numFrames = -1;\r
-\r
-       if ( ( numSurfaces = ASE_GetNumSurfaces() ) > MAX_ANIM_SURFACES )\r
-       {\r
-               Error( "Too many surfaces in ASE" );\r
-       }\r
-\r
-       for ( numValidSurfaces = 0, i = 0; i < numSurfaces; i++ )\r
-       {\r
-               polyset_t *splitSets;\r
-               int numNewFrames;\r
-               const char *surfaceName = ASE_GetSurfaceName( i );\r
-\r
-               if ( !surfaceName )\r
-               {\r
-                       continue;\r
-//                     Error( "Missing animation frames in model" );\r
-               }\r
-\r
-               if ( strstr( surfaceName, "tag_" ) || \r
-                        !strcmp( part, "any" ) || \r
-                        ( strstr( surfaceName, part ) == surfaceName ) )\r
-               {\r
-\r
-                       // skip this if it's an inappropriate tag\r
-                       if ( strcmp( part, "any" ) )\r
-                       {\r
-                               // ignore non-"tag_head" tags if this is the head\r
-                               if ( !strcmp( part, "h_" ) && strstr( surfaceName, "tag_" ) && strcmp( surfaceName, "tag_head" ) )\r
-                                       continue;\r
-                               // ignore "tag_head" if this is the legs\r
-                               if ( !strcmp( part, "l_" ) && !strcmp( surfaceName, "tag_head" ) )\r
-                                       continue;\r
-                               // ignore "tag_weapon" if this is the legs\r
-                               if ( !strcmp( part, "l_" ) && !strcmp( surfaceName, "tag_weapon" ) )\r
-                                       continue;\r
-                       }\r
-\r
-                       if ( ( sanims[numValidSurfaces].frames = ASE_GetSurfaceAnimation( i, &sanims[numValidSurfaces].numFrames, skipFrameStart, skipFrameEnd, maxFrames ) ) != 0 )\r
-                       {\r
-                               splitSets = Polyset_SplitSets( sanims[numValidSurfaces].frames, sanims[numValidSurfaces].numFrames, &numNewFrames, g_data.maxSurfaceTris );\r
-                               \r
-                               if ( numFrames == -1 )\r
-                                       numFrames = sanims[numValidSurfaces].numFrames;\r
-                               else if ( numFrames != sanims[numValidSurfaces].numFrames )\r
-                                       Error( "Different number of animation frames on surfaces" );\r
-                               \r
-                               if ( sanims[numValidSurfaces].frames != splitSets )\r
-                               {\r
-                                       int j;\r
-\r
-                                       // free old data if we split the surfaces\r
-                                       for ( j = 0; j < sanims[numValidSurfaces].numFrames; j++ )\r
-                                       {\r
-                                               free( sanims[numValidSurfaces].frames[j].triangles );\r
-                                               free( sanims[numValidSurfaces].frames );\r
-                                       }\r
-                                       \r
-                                       sanims[numValidSurfaces].frames = splitSets;\r
-                                       sanims[numValidSurfaces].numFrames = numNewFrames;\r
-                               }\r
-                               Polyset_SnapSets( sanims[numValidSurfaces].frames, sanims[numValidSurfaces].numFrames );\r
-                               Polyset_ComputeNormals( sanims[numValidSurfaces].frames, sanims[numValidSurfaces].numFrames );\r
-\r
-                               numValidSurfaces++;\r
-                       }\r
-               }\r
-       }\r
-\r
-       return numValidSurfaces;\r
-}\r
-\r
-static int SurfaceOrderToFrameOrder( SurfaceAnimation_t sanims[], ObjectAnimationFrame_t oanims[], int numSurfaces )\r
-{\r
-       int i, s;\r
-       int numFrames = -1;\r
-\r
-       /*\r
-       ** we have the data here arranged in surface order, now we need to convert it to \r
-       ** frame order\r
-       */\r
-       for ( i = 0, s = 0; i < numSurfaces; i++ )\r
-       {\r
-               int j;\r
-               \r
-               if ( sanims[i].frames )\r
-               {\r
-                       if ( numFrames == -1 )\r
-                               numFrames = sanims[i].numFrames;\r
-                       else if ( numFrames != sanims[i].numFrames )\r
-                               Error( "numFrames != sanims[i].numFrames (%d != %d)\n", numFrames, sanims[i].numFrames );\r
-\r
-                       for ( j = 0; j < sanims[i].numFrames; j++ )\r
-                       {\r
-                               oanims[j].surfaces[s] = &sanims[i].frames[j];\r
-                               oanims[j].numSurfaces = numSurfaces;\r
-                       }\r
-                       s++;\r
-               }\r
-       }\r
-\r
-       return numFrames;\r
-}\r
-\r
-static void WriteMD3( const char *_filename, ObjectAnimationFrame_t oanims[], int numFrames )\r
-{\r
-       char filename[1024];\r
-\r
-       strcpy( filename, _filename );\r
-       if ( strchr( filename, '.' ) )\r
-               *strchr( filename, '.' ) = 0;\r
-       strcat( filename, ".md3" );\r
-}\r
-\r
-static void BuildAnimationFromOAFs( const char *filename, ObjectAnimationFrame_t oanims[], int numFrames, int type )\r
-{\r
-       int f, i, j, tagcount;\r
-       float *frameXyz;\r
-       float *frameNormals;\r
-\r
-       g_data.model.numSurfaces = oanims[0].numSurfaces;\r
-       g_data.model.numFrames = numFrames;\r
-       if ( g_data.model.numFrames < 0)\r
-               Error ("model.numFrames < 0");\r
-       if ( g_data.model.numFrames >= MD3_MAX_FRAMES)\r
-               Error ("model.numFrames >= MD3_MAX_FRAMES");\r
-\r
-       // build base frame\r
-       BuildBaseFrame( filename, &oanims[0] );\r
-       \r
-       // build animation frames\r
-       for ( f = 0; f < numFrames; f++ )\r
-       {\r
-               ObjectAnimationFrame_t *pOAF = &oanims[f];\r
-               qboolean        parentTagExists = qfalse;\r
-               md3Tag_t        tagParent;\r
-               int                     numtags = 0;\r
-               md3Frame_t              *fr;\r
-               \r
-               fr = &g_data.frames[f];\r
-               \r
-               strcpy( fr->name, "(from ASE)" );\r
-               \r
-               // scale and adjust frame\r
-               for ( i = 0; i < pOAF->numSurfaces; i++ )\r
-               {\r
-                       triangle_t *pTris = pOAF->surfaces[i]->triangles;\r
-                       int t;\r
-                       \r
-                       for ( t = 0; t < pOAF->surfaces[i]->numtriangles; t++ )\r
-                       {\r
-                               for ( j = 0; j < 3; j++ )\r
-                               {\r
-                                       int k;\r
-                                       \r
-                                       // scale and adjust\r
-                                       for ( k = 0 ; k < 3 ; k++ ) {\r
-                                               pTris[t].verts[j][k] = pTris[t].verts[j][k] * g_data.scale_up +\r
-                                                       g_data.aseAdjust[k];\r
-                                               \r
-                                               if ( pTris[t].verts[j][k] > 1023 ||\r
-                                                       pTris[t].verts[j][k] < -1023 )\r
-                                               {\r
-                                                       Error( "Model extents too large" );\r
-                                               }\r
-                                       }\r
-                               }\r
-                       }\r
-               }\r
-               \r
-               //\r
-               // find and count tags, locate parent tag\r
-               //\r
-               for ( i = 0; i < pOAF->numSurfaces; i++ )\r
-               {\r
-                       if ( strstr( pOAF->surfaces[i]->name, "tag_" ) == pOAF->surfaces[i]->name )\r
-                       {\r
-                               // ignore parent tags when grabbing a weapon model and this is the flash portion\r
-                               if ( !strcmp( pOAF->surfaces[i]->name, "tag_parent" ) && strstr( filename, "_flash.md3" ) )\r
-                               {\r
-                                       continue;\r
-                               }\r
-                               else if ( !strstr( filename, "_hand.md3" ) && (\r
-                                        ( !strcmp( pOAF->surfaces[i]->name, "tag_parent" ) && !strstr( filename, "_flash.md3" ) ) ||\r
-                                        ( !strcmp( pOAF->surfaces[i]->name, "tag_torso" ) && ( strstr( filename, "upper_" ) || strstr( filename, "upper.md3" ) ) ) ||\r
-                                        ( !strcmp( pOAF->surfaces[i]->name, "tag_head" ) && ( strstr( filename, "head.md3" ) || strstr( filename, "head_" ) ) ) ||\r
-                                        ( !strcmp( pOAF->surfaces[i]->name, "tag_flash" ) && strstr( filename, "_flash.md3" ) )|| \r
-                                        ( !strcmp( pOAF->surfaces[i]->name, "tag_weapon" ) && type == TYPE_WEAPON ) ) )\r
-                               {\r
-                                       float tri[3][3];\r
-                                       \r
-                                       if ( parentTagExists )\r
-                                               Error( "Multiple parent tags not allowed" );\r
-                                       \r
-                                       memcpy( tri[0], pOAF->surfaces[i]->triangles[0].verts[0], sizeof( float ) * 3 );\r
-                                       memcpy( tri[1], pOAF->surfaces[i]->triangles[0].verts[1], sizeof( float ) * 3 );\r
-                                       memcpy( tri[2], pOAF->surfaces[i]->triangles[0].verts[2], sizeof( float ) * 3 );\r
-                                       \r
-                                       MD3_ComputeTagFromTri( &tagParent, tri );\r
-                                       strcpy( tagParent.name, "tag_parent" );\r
-                                       g_data.tags[f][numtags] = tagParent;\r
-                                       parentTagExists = qtrue;\r
-                               }\r
-                               else\r
-                               {\r
-                                       float tri[3][3];\r
-                               \r
-                                       memcpy( tri[0], pOAF->surfaces[i]->triangles[0].verts[0], sizeof( float ) * 3 );\r
-                                       memcpy( tri[1], pOAF->surfaces[i]->triangles[0].verts[1], sizeof( float ) * 3 );\r
-                                       memcpy( tri[2], pOAF->surfaces[i]->triangles[0].verts[2], sizeof( float ) * 3 );\r
-                                       \r
-                                       MD3_ComputeTagFromTri( &g_data.tags[f][numtags], tri );\r
-                                       strcpy( g_data.tags[f][numtags].name, pOAF->surfaces[i]->name );\r
-                                       if ( strstr( g_data.tags[f][numtags].name, "tag_flash" ) )\r
-                                               * ( strstr( g_data.tags[f][numtags].name, "tag_flash" ) + strlen( "tag_flash" ) ) = 0;\r
-                               }\r
-\r
-                               numtags++;\r
-                       }\r
-                       \r
-                       if ( strcmp( pOAF->surfaces[i]->name, g_data.surfData[i].header.name ) )\r
-                       {\r
-                               Error( "Mismatched surfaces from base('%s') to frame('%s') in model '%s'\n", g_data.surfData[i].header.name, pOAF->surfaces[i]->name, filename );\r
-                       }\r
-               }\r
-               \r
-               if ( numtags != g_data.model.numTags )\r
-               {\r
-                       Error( "mismatched number of tags in frame(%d) vs. base(%d)", numtags, g_data.model.numTags );\r
-               }\r
-               \r
-               //\r
-               // prepare to accumulate bounds and normals\r
-               //\r
-               ClearBounds( fr->bounds[0], fr->bounds[1] );\r
-               \r
-               //\r
-               // store the frame's vertices in the same order as the base. This assumes the\r
-               // triangles and vertices in this frame are in exactly the same order as in the\r
-               // base\r
-               //\r
-               for ( i = 0, tagcount = 0; i < pOAF->numSurfaces; i++ )\r
-               {\r
-                       int t;\r
-                       triangle_t *pTris = pOAF->surfaces[i]->triangles;\r
-                       \r
-                       //\r
-                       // parent tag adjust\r
-                       //\r
-                       if ( parentTagExists ) \r
-                       {\r
-                               for ( t = 0; t < pOAF->surfaces[i]->numtriangles; t++ )\r
-                               {\r
-                                       for ( j = 0; j < 3 ; j++ )\r
-                                       {\r
-                                               vec3_t tmp;\r
-                                               \r
-                                               VectorSubtract( pTris[t].verts[j], tagParent.origin, tmp );\r
-                                               \r
-                                               pTris[t].verts[j][0] = DotProduct( tmp, tagParent.axis[0] );\r
-                                               pTris[t].verts[j][1] = DotProduct( tmp, tagParent.axis[1] );\r
-                                               pTris[t].verts[j][2] = DotProduct( tmp, tagParent.axis[2] );\r
-                                               \r
-                                               VectorCopy( pTris[t].normals[j], tmp );\r
-                                               pTris[t].normals[j][0] = DotProduct( tmp, tagParent.axis[0] );\r
-                                               pTris[t].normals[j][1] = DotProduct( tmp, tagParent.axis[1] );\r
-                                               pTris[t].normals[j][2] = DotProduct( tmp, tagParent.axis[2] );\r
-                                       }\r
-                               }\r
-                       }\r
-                       \r
-                       //\r
-                       // compute tag data\r
-                       //\r
-                       if ( strstr( pOAF->surfaces[i]->name, "tag_" ) == pOAF->surfaces[i]->name )\r
-                       {\r
-                               md3Tag_t *pTag = &g_data.tags[f][tagcount];\r
-                               float tri[3][3];\r
-                               \r
-                               strcpy( pTag->name, pOAF->surfaces[i]->name );\r
-                               \r
-                               memcpy( tri[0], pTris[0].verts[0], sizeof( float ) * 3 );\r
-                               memcpy( tri[1], pTris[0].verts[1], sizeof( float ) * 3 );\r
-                               memcpy( tri[2], pTris[0].verts[2], sizeof( float ) * 3 );\r
-                               \r
-                               MD3_ComputeTagFromTri( pTag, tri );\r
-                               tagcount++;\r
-                       }\r
-                       else\r
-                       {\r
-                               if ( g_data.surfData[i].verts[f] )\r
-                                       free( g_data.surfData[i].verts[f] );\r
-                               frameXyz = g_data.surfData[i].verts[f] = calloc( 1, sizeof( float ) * 6 * g_data.surfData[i].header.numVerts );\r
-                               frameNormals = frameXyz + 3;\r
-                               \r
-                               for ( t = 0; t < pOAF->surfaces[i]->numtriangles; t++ )\r
-                               {\r
-                                       for ( j = 0; j < 3 ; j++ )\r
-                                       {\r
-                                               int index;\r
-                                               \r
-                                               index = g_data.surfData[i].baseTriangles[t].v[j].index;\r
-                                               frameXyz[index*6+0] = pTris[t].verts[j][0];\r
-                                               frameXyz[index*6+1] = pTris[t].verts[j][1];\r
-                                               frameXyz[index*6+2] = pTris[t].verts[j][2];\r
-                                               frameNormals[index*6+0] =  pTris[t].normals[j][0];\r
-                                               frameNormals[index*6+1] =  pTris[t].normals[j][1];\r
-                                               frameNormals[index*6+2] =  pTris[t].normals[j][2];\r
-                                               AddPointToBounds (&frameXyz[index*6], fr->bounds[0], fr->bounds[1] );\r
-                                       }\r
-                               }\r
-                       }\r
-               }\r
-       }\r
-\r
-       if ( strstr( filename, gamedir + 1 ) )\r
-       {\r
-               strcpy( g_modelname, strstr( filename, gamedir + 1 ) + strlen( gamedir ) - 1 );\r
-       }\r
-       else\r
-       {\r
-               strcpy( g_modelname, filename );\r
-       }\r
-\r
-       FinishModel( type );\r
-       ClearModel();\r
-}\r
-\r
-static void ConvertASE( const char *filename, int type, qboolean grabAnims )\r
-{\r
-       int i, j;\r
-       int numSurfaces;\r
-       int numFrames = -1;\r
-       SurfaceAnimation_t surfaceAnimations[MAX_ANIM_SURFACES];\r
-       ObjectAnimationFrame_t objectAnimationFrames[MAX_ANIM_FRAMES];\r
-       char outfilename[1024];\r
-\r
-       /*\r
-       ** load ASE into memory\r
-       */\r
-       ASE_Load( filename, g_verbose, grabAnims );\r
-\r
-       /*\r
-       ** process parts\r
-       */\r
-       if ( type == TYPE_ITEM )\r
-       {\r
-               numSurfaces = GetSurfaceAnimations( surfaceAnimations, "any", -1, -1, -1 );\r
-\r
-               if ( numSurfaces <= 0 )\r
-                       Error( "numSurfaces <= 0" );\r
-\r
-               numFrames = SurfaceOrderToFrameOrder( surfaceAnimations, objectAnimationFrames, numSurfaces );\r
-\r
-               if ( numFrames <= 0 )\r
-                       Error( "numFrames <= 0" );\r
-\r
-               strcpy( outfilename, filename );\r
-               if ( strrchr( outfilename, '.' ) )\r
-                       *( strrchr( outfilename, '.' ) + 1 ) = 0;\r
-               strcat( outfilename, "md3" );\r
-               BuildAnimationFromOAFs( outfilename, objectAnimationFrames, numFrames, type );\r
-\r
-               // free memory\r
-               for ( i = 0; i < numSurfaces; i++ )\r
-               {\r
-                       if ( surfaceAnimations[i].frames )\r
-                       {\r
-                               for ( j = 0; j < surfaceAnimations[i].numFrames; j++ )\r
-                               {\r
-                                       free( surfaceAnimations[i].frames[j].triangles );\r
-                               }\r
-                               free( surfaceAnimations[i].frames );\r
-                               surfaceAnimations[i].frames = 0;\r
-                       }\r
-               }\r
-       }\r
-       else if ( type == TYPE_PLAYER )\r
-       {\r
-               qboolean tagTorso = qfalse;\r
-               qboolean tagHead = qfalse;\r
-               qboolean tagWeapon = qfalse;\r
-\r
-               //\r
-               // verify that all necessary tags exist\r
-               //\r
-               numSurfaces = ASE_GetNumSurfaces();\r
-               for ( i = 0; i < numSurfaces; i++ )\r
-               {\r
-                       if ( !strcmp( ASE_GetSurfaceName( i ), "tag_head" ) )\r
-                       {\r
-                               tagHead = qtrue;\r
-                       }\r
-                       if ( !strcmp( ASE_GetSurfaceName( i ), "tag_torso" ) )\r
-                       {\r
-                               tagTorso = qtrue;\r
-                       }\r
-                       if ( !strcmp( ASE_GetSurfaceName( i ), "tag_weapon" ) )\r
-                       {\r
-                               tagWeapon = qtrue;\r
-                       }\r
-               }\r
-\r
-               if ( !tagWeapon )\r
-               {\r
-                       Error( "Missing tag_weapon!" );\r
-               }\r
-               if ( !tagTorso )\r
-               {\r
-                       Error( "Missing tag_torso!" );\r
-               }\r
-               if ( !tagWeapon )\r
-               {\r
-                       Error( "Missing tag_weapon!" );\r
-               }\r
-\r
-               // get all upper body surfaces\r
-               numSurfaces = GetSurfaceAnimations( surfaceAnimations, "u_", -1, -1, g_data.maxUpperFrames );\r
-               numFrames = SurfaceOrderToFrameOrder( surfaceAnimations, objectAnimationFrames, numSurfaces );\r
-               strcpy( outfilename, filename );\r
-               if ( strrchr( outfilename, '/' ) )\r
-                       *( strrchr( outfilename, '/' ) + 1 ) = 0;\r
-\r
-               if ( g_data.currentLod == 0 )\r
-               {\r
-                       strcat( outfilename, "upper.md3" );\r
-               }\r
-               else\r
-               {\r
-                       char temp[128];\r
-\r
-                       sprintf( temp, "upper_%d.md3", g_data.currentLod );\r
-                       strcat( outfilename, temp );\r
-               }\r
-               \r
-               BuildAnimationFromOAFs( outfilename, objectAnimationFrames, numFrames, type );\r
-\r
-               // free memory\r
-               for ( i = 0; i < numSurfaces; i++ )\r
-               {\r
-                       if ( surfaceAnimations[i].frames )\r
-                       {\r
-                               for ( j = 0; j < surfaceAnimations[i].numFrames; j++ )\r
-                               {\r
-                                       free( surfaceAnimations[i].frames[j].triangles );\r
-                               }\r
-                               free( surfaceAnimations[i].frames );\r
-                               surfaceAnimations[i].frames = 0;\r
-                       }\r
-               }\r
-\r
-               // get lower body surfaces\r
-               numSurfaces = GetSurfaceAnimations( surfaceAnimations, "l_", g_data.lowerSkipFrameStart, g_data.lowerSkipFrameEnd, -1 );\r
-               numFrames = SurfaceOrderToFrameOrder( surfaceAnimations, objectAnimationFrames, numSurfaces );\r
-               strcpy( outfilename, filename );\r
-               if ( strrchr( outfilename, '/' ) )\r
-                       *( strrchr( outfilename, '/' ) + 1 ) = 0;\r
-\r
-               if ( g_data.currentLod == 0 )\r
-               {\r
-                       strcat( outfilename, "lower.md3" );\r
-               }\r
-               else\r
-               {\r
-                       char temp[128];\r
-\r
-                       sprintf( temp, "lower_%d.md3", g_data.currentLod );\r
-                       strcat( outfilename, temp );\r
-               }\r
-               BuildAnimationFromOAFs( outfilename, objectAnimationFrames, numFrames, type );\r
-\r
-               // free memory\r
-               for ( i = 0; i < numSurfaces; i++ )\r
-               {\r
-                       if ( surfaceAnimations[i].frames )\r
-                       {\r
-                               for ( j = 0; j < surfaceAnimations[i].numFrames; j++ )\r
-                               {\r
-                                       free( surfaceAnimations[i].frames[j].triangles );\r
-                               }\r
-                               free( surfaceAnimations[i].frames );\r
-                               surfaceAnimations[i].frames = 0;\r
-                       }\r
-               }\r
-\r
-               // get head surfaces\r
-               numSurfaces = GetSurfaceAnimations( surfaceAnimations, "h_", -1, -1, g_data.maxHeadFrames );\r
-               numFrames = SurfaceOrderToFrameOrder( surfaceAnimations, objectAnimationFrames, numSurfaces );\r
-               strcpy( outfilename, filename );\r
-               if ( strrchr( outfilename, '/' ) )\r
-                       *( strrchr( outfilename, '/' ) + 1 ) = 0;\r
-\r
-               if ( g_data.currentLod == 0 )\r
-               {\r
-                       strcat( outfilename, "head.md3" );\r
-               }\r
-               else\r
-               {\r
-                       char temp[128];\r
-\r
-                       sprintf( temp, "head_%d.md3", g_data.currentLod );\r
-                       strcat( outfilename, temp );\r
-               }\r
-               BuildAnimationFromOAFs( outfilename, objectAnimationFrames, numFrames, type );\r
-\r
-               // free memory\r
-               for ( i = 0; i < numSurfaces; i++ )\r
-               {\r
-                       if ( surfaceAnimations[i].frames )\r
-                       {\r
-                               for ( j = 0; j < surfaceAnimations[i].numFrames; j++ )\r
-                               {\r
-                                       free( surfaceAnimations[i].frames[j].triangles );\r
-                               }\r
-                               free( surfaceAnimations[i].frames );\r
-                               surfaceAnimations[i].frames = 0;\r
-                       }\r
-               }\r
-       }\r
-       else if ( type == TYPE_WEAPON )\r
-       {\r
-               // get the weapon surfaces\r
-               numSurfaces = GetSurfaceAnimations( surfaceAnimations, "w_", -1, -1, -1 );\r
-               numFrames = SurfaceOrderToFrameOrder( surfaceAnimations, objectAnimationFrames, numSurfaces );\r
-\r
-               strcpy( outfilename, filename );\r
-               if ( strrchr( outfilename, '.' ) )\r
-                       *( strrchr( outfilename, '.' ) + 1 ) = 0;\r
-               strcat( outfilename, "md3" );\r
-               BuildAnimationFromOAFs( outfilename, objectAnimationFrames, numFrames, type );\r
-\r
-               // free memory\r
-               for ( i = 0; i < numSurfaces; i++ )\r
-               {\r
-                       if ( surfaceAnimations[i].frames )\r
-                       {\r
-                               for ( j = 0; j < surfaceAnimations[i].numFrames; j++ )\r
-                               {\r
-                                       free( surfaceAnimations[i].frames[j].triangles );\r
-                               }\r
-                               free( surfaceAnimations[i].frames );\r
-                               surfaceAnimations[i].frames = 0;\r
-                       }\r
-               }\r
-\r
-               // get the flash surfaces\r
-               numSurfaces = GetSurfaceAnimations( surfaceAnimations, "f_", -1, -1, -1 );\r
-               numFrames = SurfaceOrderToFrameOrder( surfaceAnimations, objectAnimationFrames, numSurfaces );\r
-\r
-               strcpy( outfilename, filename );\r
-               if ( strrchr( outfilename, '.' ) )\r
-                       *strrchr( outfilename, '.' ) = 0;\r
-               strcat( outfilename, "_flash.md3" );\r
-               BuildAnimationFromOAFs( outfilename, objectAnimationFrames, numFrames, TYPE_ITEM );\r
-\r
-               // free memory\r
-               for ( i = 0; i < numSurfaces; i++ )\r
-               {\r
-                       if ( surfaceAnimations[i].frames )\r
-                       {\r
-                               for ( j = 0; j < surfaceAnimations[i].numFrames; j++ )\r
-                               {\r
-                                       free( surfaceAnimations[i].frames[j].triangles );\r
-                               }\r
-                               free( surfaceAnimations[i].frames );\r
-                               surfaceAnimations[i].frames = 0;\r
-                       }\r
-               }\r
-       }\r
-       else if ( type == TYPE_HAND )\r
-       {\r
-               // get the hand tags\r
-               numSurfaces = GetSurfaceAnimations( surfaceAnimations, "tag_", -1, -1, -1 );\r
-               numFrames = SurfaceOrderToFrameOrder( surfaceAnimations, objectAnimationFrames, numSurfaces );\r
-\r
-               strcpy( outfilename, filename );\r
-               if ( strrchr( outfilename, '.' ) )\r
-                       *strrchr( outfilename, '.' ) = 0;\r
-               strcat( outfilename, "_hand.md3" );\r
-               BuildAnimationFromOAFs( outfilename, objectAnimationFrames, numFrames, TYPE_HAND );\r
-\r
-               // free memory\r
-               for ( i = 0; i < numSurfaces; i++ )\r
-               {\r
-                       if ( surfaceAnimations[i].frames )\r
-                       {\r
-                               for ( j = 0; j < surfaceAnimations[i].numFrames; j++ )\r
-                               {\r
-                                       free( surfaceAnimations[i].frames[j].triangles );\r
-                               }\r
-                               free( surfaceAnimations[i].frames );\r
-                               surfaceAnimations[i].frames = 0;\r
-                       }\r
-               }\r
-       }\r
-       else\r
-       {\r
-               Error( "Unknown type passed to ConvertASE()" );\r
-       }\r
-\r
-       g_data.currentLod = 0;\r
-       g_data.lodBias = 0;\r
-       g_data.maxHeadFrames = 0;\r
-       g_data.maxUpperFrames = 0;\r
-       g_data.lowerSkipFrameStart = 0;\r
-       g_data.lowerSkipFrameEnd = 0;\r
-       VectorCopy( vec3_origin, g_data.aseAdjust );\r
-\r
-       // unload ASE from memory\r
-       ASE_Free();\r
-}\r
+#include <assert.h>
+#include "q3data.h"
+
+//=================================================================
+
+static void OrderSurfaces( void );
+static void LoadBase( const char *filename );
+static int     LoadModelFile( const char *filename, polyset_t **ppsets, int *pnumpolysets );
+
+#define MAX_SURFACE_TRIS       (SHADER_MAX_INDEXES / 3)
+#define MAX_SURFACE_VERTS      SHADER_MAX_VERTEXES
+
+#define MD3_TYPE_UNKNOWN 0
+#define MD3_TYPE_BASE3DS 1
+#define MD3_TYPE_SPRITE  2
+#define MD3_TYPE_ASE    3
+
+#define MAX_ANIM_FRAMES                512
+#define MAX_ANIM_SURFACES      32
+
+typedef struct
+{
+       polyset_t *frames;
+       int numFrames;
+} SurfaceAnimation_t;
+
+typedef struct
+{
+       polyset_t *surfaces[MAX_ANIM_SURFACES];
+       int numSurfaces;
+} ObjectAnimationFrame_t;
+
+typedef struct {
+       vec3_t          xyz;
+       vec3_t          normal;
+       vec3_t          color;
+       float           st[2];
+       int                     index;
+} baseVertex_t;
+       
+typedef struct {
+       baseVertex_t    v[3];
+} baseTriangle_t;
+
+//================================================================
+
+typedef struct
+{
+       md3Surface_t    header;
+       md3Shader_t             shaders[MD3_MAX_SHADERS];
+       // all verts (xyz_normal)
+       float   *verts[MD3_MAX_FRAMES];
+
+       baseTriangle_t  baseTriangles[MD3_MAX_TRIANGLES];
+
+       // the triangles will be sorted so that they form long generalized tristrips
+       int                             orderedTriangles[MD3_MAX_TRIANGLES][3];
+       int                             lodTriangles[MD3_MAX_TRIANGLES][3];
+       baseVertex_t    baseVertexes[MD3_MAX_VERTS];
+
+} md3SurfaceData_t;
+
+typedef struct
+{
+       int                     skinwidth, skinheight;
+       
+       md3SurfaceData_t surfData[MD3_MAX_SURFACES];
+
+       md3Tag_t                tags[MD3_MAX_FRAMES][MD3_MAX_TAGS];
+       md3Frame_t              frames[MD3_MAX_FRAMES];
+
+       md3Header_t     model;
+       float           scale_up;                       // set by $scale
+       vec3_t          adjust;                         // set by $origin
+       vec3_t          aseAdjust;
+       int                     fixedwidth, fixedheight;        // set by $skinsize
+
+       int                     maxSurfaceTris;
+
+       int                     lowerSkipFrameStart, lowerSkipFrameEnd;
+       int                     maxUpperFrames;
+       int                     maxHeadFrames;
+       int                     currentLod;
+       float           lodBias;
+
+       int                     type;           // MD3_TYPE_BASE, MD3_TYPE_OLDBASE, MD3_TYPE_ASE, or MD3_TYPE_SPRITE
+
+} q3data;
+
+q3data g_data;
+
+// the command list holds counts, the count * 3 xyz, st, normal indexes
+// that are valid for every frame
+char           g_cddir[1024];
+char           g_modelname[1024];
+
+//==============================================================
+
+/*
+===============
+ClearModel
+===============
+*/
+void ClearModel (void)
+{
+       int i;
+
+       g_data.type = MD3_TYPE_UNKNOWN;
+
+       for ( i = 0; i < MD3_MAX_SURFACES; i++ )
+       {
+               memset( &g_data.surfData[i].header, 0, sizeof( g_data.surfData[i].header ) );
+               memset( &g_data.surfData[i].shaders, 0, sizeof( g_data.surfData[i].shaders ) );
+               memset( &g_data.surfData[i].verts, 0, sizeof( g_data.surfData[i].verts ) );
+       }
+
+       memset( g_data.tags, 0, sizeof( g_data.tags ) );
+
+       for ( i = 0; i < g_data.model.numSurfaces; i++ )
+       {
+               int j;
+
+               for ( j = 0; j < g_data.surfData[i].header.numShaders; j++ )
+               {
+                       memset( &g_data.surfData[i].shaders[j], 0, sizeof( g_data.surfData[i].shaders[j] ) );
+               }
+       }
+       memset (&g_data.model, 0, sizeof(g_data.model));
+       memset (g_cddir, 0, sizeof(g_cddir));
+
+       g_modelname[0] = 0;
+       g_data.scale_up = 1.0;  
+       memset( &g_data.model, 0, sizeof( g_data.model ) );
+       VectorCopy (vec3_origin, g_data.adjust);
+       g_data.fixedwidth = g_data.fixedheight = 0;
+       g_skipmodel = qfalse;
+}
+
+/*
+** void WriteModelSurface( FILE *modelouthandle, md3SurfaceData_t *pSurfData )
+**
+** This routine assumes that the file position has been adjusted
+** properly prior to entry to point at the beginning of the surface.
+**
+** Since surface header information is completely relative, we can't
+** just randomly seek to an arbitrary surface location right now.  Is
+** this something we should add?
+*/
+void WriteModelSurface( FILE *modelouthandle, md3SurfaceData_t *pSurfData )
+{
+       md3Surface_t    *pSurf = &pSurfData->header;
+       md3Shader_t             *pShader = pSurfData->shaders;
+       baseVertex_t    *pBaseVertex = pSurfData->baseVertexes;
+       float                   **verts = pSurfData->verts;
+
+       short xyznormals[MD3_MAX_VERTS][4];
+
+       float base_st[MD3_MAX_VERTS][2];
+       md3Surface_t surftemp;
+
+       int f, i, j, k;
+
+       if ( strstr( pSurf->name, "tag_" ) == pSurf->name )
+               return;
+
+       //
+       // write out the header
+       //
+       surftemp = *pSurf;
+       surftemp.ident = LittleLong( MD3_IDENT );
+       surftemp.flags = LittleLong( pSurf->flags );
+       surftemp.numFrames = LittleLong( pSurf->numFrames );
+       surftemp.numShaders = LittleLong( pSurf->numShaders );
+
+       surftemp.ofsShaders = LittleLong( pSurf->ofsShaders );
+
+       surftemp.ofsTriangles = LittleLong( pSurf->ofsTriangles );
+       surftemp.numTriangles = LittleLong( pSurf->numTriangles );
+
+       surftemp.ofsSt = LittleLong( pSurf->ofsSt );
+       surftemp.ofsXyzNormals = LittleLong( pSurf->ofsXyzNormals );
+       surftemp.ofsEnd = LittleLong( pSurf->ofsEnd );
+
+       SafeWrite( modelouthandle, &surftemp, sizeof( surftemp ) );
+
+       if ( g_verbose )
+       {
+               printf( "surface '%s'\n", pSurf->name );
+               printf( "...num shaders: %d\n", pSurf->numShaders );
+       }
+
+       //
+       // write out shaders
+       //
+       for ( i = 0; i < pSurf->numShaders; i++ )
+       {
+               md3Shader_t shadertemp;
+
+               if ( g_verbose )
+                       printf( "......'%s'\n", pShader[i].name );
+
+               shadertemp = pShader[i];
+               shadertemp.shaderIndex = LittleLong( shadertemp.shaderIndex );
+               SafeWrite( modelouthandle, &shadertemp, sizeof( shadertemp ) );
+       }
+
+       //
+       // write out the triangles
+       //
+       for ( i = 0 ; i < pSurf->numTriangles ; i++ ) 
+       {
+               for (j = 0 ; j < 3 ; j++) 
+               {
+                       int ivalue = LittleLong( pSurfData->orderedTriangles[i][j] );
+                       pSurfData->orderedTriangles[i][j] = ivalue;
+               }
+       }
+
+       SafeWrite( modelouthandle, pSurfData->orderedTriangles, pSurf->numTriangles * sizeof( g_data.surfData[0].orderedTriangles[0] ) );
+
+       if ( g_verbose )
+       {
+               printf( "\n...num verts: %d\n", pSurf->numVerts );
+               printf( "...TEX COORDINATES\n" );
+       }
+
+       //
+       // write out the texture coordinates
+       //
+       for ( i = 0; i < pSurf->numVerts ; i++) {
+               base_st[i][0] = LittleFloat( pBaseVertex[i].st[0] );
+               base_st[i][1] = LittleFloat( pBaseVertex[i].st[1] );
+               if ( g_verbose )
+                       printf( "......%d: %f,%f\n", i, base_st[i][0], base_st[i][1] );
+       }
+       SafeWrite( modelouthandle, base_st, pSurf->numVerts * sizeof(base_st[0]));
+
+       //
+       // write the xyz_normal
+       //
+       if ( g_verbose )
+               printf( "...XYZNORMALS\n" );
+       for ( f = 0; f < g_data.model.numFrames; f++ )
+       {
+               for (j=0 ; j< pSurf->numVerts; j++) 
+               {
+                       short value;
+
+                       for (k=0 ; k < 3 ; k++) 
+                       {
+                               value = ( short ) ( verts[f][j*6+k] / MD3_XYZ_SCALE );
+                               xyznormals[j][k] = LittleShort( value );
+                       }
+                       NormalToLatLong( &verts[f][j*6+3], (byte *)&xyznormals[j][3] );
+               }
+               SafeWrite( modelouthandle, xyznormals, pSurf->numVerts * sizeof( short ) * 4 );
+       }
+}
+
+/*
+** void WriteModelFile( FILE *modelouthandle )
+**
+** CHUNK                       SIZE
+** header                      sizeof( md3Header_t )
+** frames                      sizeof( md3Frame_t ) * numFrames
+** tags                                sizeof( md3Tag_t ) * numFrames * numTags
+** surfaces                    surfaceSum
+*/
+void WriteModelFile( FILE *modelouthandle )
+{
+       int                             f;
+       int                             i, j;
+       md3Header_t             modeltemp;
+       long                    surfaceSum = 0;
+       int                             numRealSurfaces = 0;
+       int                             numFrames = g_data.model.numFrames;
+
+       // compute offsets for all surfaces, sum their total size
+       for ( i = 0; i < g_data.model.numSurfaces; i++ )
+       {
+               if ( strstr( g_data.surfData[i].header.name, "tag_" ) != g_data.surfData[i].header.name )
+               {
+                       md3Surface_t *psurf = &g_data.surfData[i].header;
+
+                       if ( psurf->numTriangles == 0 || psurf->numVerts == 0 )
+                               continue;
+
+                       //
+                       // the triangle and vertex split threshold is controlled by a parameter
+                       // to $base, a la $base blah.3ds 1900, where "1900" determines the number
+                       // of triangles to split on
+                       //
+                       else if ( psurf->numVerts > MAX_SURFACE_VERTS )
+                       {
+                               Error( "too many vertices\n" );
+                       }
+
+                       psurf->numFrames = numFrames;
+
+                       psurf->ofsShaders = sizeof( md3Surface_t );
+
+                       if ( psurf->numTriangles > MAX_SURFACE_TRIS  ) 
+                       {
+                               Error( "too many faces\n" );
+                       }
+
+                       psurf->ofsTriangles = psurf->ofsShaders + psurf->numShaders * sizeof( md3Shader_t );
+
+                       psurf->ofsSt = psurf->ofsTriangles + psurf->numTriangles * sizeof( md3Triangle_t );
+                       psurf->ofsXyzNormals = psurf->ofsSt + psurf->numVerts * sizeof( md3St_t );
+                       psurf->ofsEnd = psurf->ofsXyzNormals + psurf->numFrames * psurf->numVerts * ( sizeof( short ) * 4 );
+
+                       surfaceSum += psurf->ofsEnd;
+
+                       numRealSurfaces++;
+               }
+       }
+
+       g_data.model.ident = MD3_IDENT;
+       g_data.model.version = MD3_VERSION;
+
+       g_data.model.ofsFrames = sizeof(md3Header_t);
+       g_data.model.ofsTags = g_data.model.ofsFrames + numFrames*sizeof(md3Frame_t);
+       g_data.model.ofsSurfaces = g_data.model.ofsTags + numFrames*g_data.model.numTags*sizeof(md3Tag_t);
+       g_data.model.ofsEnd = g_data.model.ofsSurfaces + surfaceSum;
+
+       //
+       // write out the model header
+       //
+       modeltemp = g_data.model;
+       modeltemp.ident = LittleLong( modeltemp.ident );
+       modeltemp.version = LittleLong( modeltemp.version );
+       modeltemp.numFrames = LittleLong( modeltemp.numFrames );
+       modeltemp.numTags = LittleLong( modeltemp.numTags );
+       modeltemp.numSurfaces = LittleLong( numRealSurfaces );
+       modeltemp.ofsFrames = LittleLong( modeltemp.ofsFrames );
+       modeltemp.ofsTags = LittleLong( modeltemp.ofsTags );
+       modeltemp.ofsSurfaces = LittleLong( modeltemp.ofsSurfaces );
+       modeltemp.ofsEnd = LittleLong( modeltemp.ofsEnd );
+
+       SafeWrite (modelouthandle, &modeltemp, sizeof(modeltemp));
+
+       //
+       // write out the frames
+       //
+       for (i=0 ; i < numFrames ; i++) 
+       {
+               vec3_t tmpVec;
+               float maxRadius = 0;
+
+               //
+               // compute localOrigin and radius
+               //
+               g_data.frames[i].localOrigin[0] =
+               g_data.frames[i].localOrigin[1] =
+               g_data.frames[i].localOrigin[2] = 0;
+
+               for ( j = 0; j < 8; j++ )
+               {
+                       tmpVec[0] = g_data.frames[i].bounds[(j&1)!=0][0];
+                       tmpVec[1] = g_data.frames[i].bounds[(j&2)!=0][1];
+                       tmpVec[2] = g_data.frames[i].bounds[(j&4)!=0][2];
+
+                       if ( VectorLength( tmpVec ) > maxRadius )
+                               maxRadius = VectorLength( tmpVec );
+               }
+
+               g_data.frames[i].radius = LittleFloat( maxRadius );
+
+               // swap
+               for (j=0 ; j<3 ; j++) {
+                       g_data.frames[i].bounds[0][j] = LittleFloat( g_data.frames[i].bounds[0][j] );
+                       g_data.frames[i].bounds[1][j] = LittleFloat( g_data.frames[i].bounds[1][j] );
+                       g_data.frames[i].localOrigin[j] = LittleFloat( g_data.frames[i].localOrigin[j] );
+               }
+       }
+       fseek (modelouthandle, g_data.model.ofsFrames, SEEK_SET);
+       SafeWrite( modelouthandle, g_data.frames, numFrames * sizeof(g_data.frames[0]) );
+
+       //
+       // write out the tags
+       //
+       fseek( modelouthandle, g_data.model.ofsTags, SEEK_SET );
+       for (f=0 ; f<g_data.model.numFrames; f++) 
+       {
+               int t;
+
+               for ( t = 0; t < g_data.model.numTags; t++ )
+               {
+                       g_data.tags[f][t].origin[0] = LittleFloat(g_data.tags[f][t].origin[0]);
+                       g_data.tags[f][t].origin[1] = LittleFloat(g_data.tags[f][t].origin[1]);
+                       g_data.tags[f][t].origin[2] = LittleFloat(g_data.tags[f][t].origin[2]);
+
+                       for (j=0 ; j<3 ; j++) 
+                       {
+                               g_data.tags[f][t].axis[0][j] = LittleFloat(g_data.tags[f][t].axis[0][j]);
+                               g_data.tags[f][t].axis[1][j] = LittleFloat(g_data.tags[f][t].axis[1][j]);
+                               g_data.tags[f][t].axis[2][j] = LittleFloat(g_data.tags[f][t].axis[2][j]);
+                       }
+               }
+               SafeWrite( modelouthandle, g_data.tags[f], g_data.model.numTags * sizeof(md3Tag_t) );
+       }
+
+       //
+       // write out the surfaces
+       //
+       fseek( modelouthandle, g_data.model.ofsSurfaces, SEEK_SET );
+       for ( i = 0; i < g_data.model.numSurfaces; i++ )
+       {
+               WriteModelSurface( modelouthandle, &g_data.surfData[i] );
+       }
+}
+
+
+/*
+===============
+FinishModel
+===============
+*/
+void FinishModel ( int type )
+{
+       FILE            *modelouthandle;
+       FILE            *defaultSkinHandle;
+       char            name[1024];
+       int                     i;
+
+       if (!g_data.model.numFrames)
+               return;
+
+       //
+       // build generalized triangle strips
+       //
+       OrderSurfaces();
+
+       if ( type == TYPE_PLAYER )
+       {
+               sprintf( name, "%s%s", writedir, g_modelname );
+               *strrchr( name, '.' ) = 0;
+               strcat( name, "_default.skin" );
+
+               defaultSkinHandle = fopen( name, "wt" );
+               for ( i = 0; i < g_data.model.numSurfaces; i++ )
+               {
+                       fprintf( defaultSkinHandle, "%s,%s\n", g_data.surfData[i].header.name, g_data.surfData[i].shaders[0].name );
+               }
+               fclose( defaultSkinHandle );
+       }
+
+       sprintf (name, "%s%s", writedir, g_modelname);
+
+       //
+       // copy the model and its shaders to release directory tree 
+       // if doing a release build
+       //
+       if ( g_release ) {
+               int                     i, j;
+               md3SurfaceData_t *pSurf;
+
+               ReleaseFile( g_modelname );
+
+               for ( i = 0; i < g_data.model.numSurfaces; i++ ) {
+                       pSurf = &g_data.surfData[i];
+                       for ( j = 0; j < g_data.model.numSkins; j++ ) {
+                               ReleaseShader( pSurf->shaders[j].name );
+                       }
+               }               
+               return;
+       }
+       
+       //
+       // write the model output file
+       //
+       printf ("saving to %s\n", name);
+       CreatePath (name);
+       modelouthandle = SafeOpenWrite (name);
+
+       WriteModelFile (modelouthandle);
+       
+       printf ("%4d surfaces\n", g_data.model.numSurfaces);
+       printf ("%4d frames\n", g_data.model.numFrames);
+       printf ("%4d tags\n", g_data.model.numTags);
+       printf ("file size: %d\n", (int)ftell (modelouthandle) );
+       printf ("---------------------\n");
+       
+       fclose (modelouthandle);
+}
+
+/*
+** OrderSurfaces
+**
+** Reorders triangles in all the surfaces.
+*/
+static void OrderSurfaces( void )
+{
+       int s;
+       extern qboolean g_stripify;
+
+       // go through each surface and find best strip/fans possible
+       for ( s = 0; s < g_data.model.numSurfaces; s++ )
+       {
+               int mesh[MD3_MAX_TRIANGLES][3];
+               int i;
+
+               printf( "stripifying surface %d/%d with %d tris\n", s, g_data.model.numSurfaces, g_data.surfData[s].header.numTriangles );
+
+               for ( i = 0; i < g_data.surfData[s].header.numTriangles; i++ )
+               {
+                       mesh[i][0] = g_data.surfData[s].lodTriangles[i][0];
+                       mesh[i][1] = g_data.surfData[s].lodTriangles[i][1];
+                       mesh[i][2] = g_data.surfData[s].lodTriangles[i][2];
+               }
+
+               if ( g_stripify )
+               {
+                       OrderMesh( mesh,                                                                        // input
+                                          g_data.surfData[s].orderedTriangles,         // output
+                                          g_data.surfData[s].header.numTriangles );
+               }
+               else
+               {
+                       memcpy( g_data.surfData[s].orderedTriangles, mesh, sizeof( int ) * 3 * g_data.surfData[s].header.numTriangles );
+               }
+       }
+}
+
+
+/*
+===============================================================
+
+BASE FRAME SETUP
+
+===============================================================
+*/
+/*
+============
+CopyTrianglesToBaseTriangles
+
+============
+*/
+static void CopyTrianglesToBaseTriangles(triangle_t *ptri, int numtri, baseTriangle_t *bTri )
+{
+       int                     i;
+//     int                     width, height, iwidth, iheight, swidth;
+//     float           s_scale, t_scale;
+//     float           scale;
+//     vec3_t          mins, maxs;
+       float           *pbasevert;
+
+/*
+       //
+       // find bounds of all the verts on the base frame
+       //
+       ClearBounds (mins, maxs);
+       
+       for (i=0 ; i<numtri ; i++)
+               for (j=0 ; j<3 ; j++)
+                       AddPointToBounds (ptri[i].verts[j], mins, maxs);
+       
+       for (i=0 ; i<3 ; i++)
+       {
+               mins[i] = floor(mins[i]);
+               maxs[i] = ceil(maxs[i]);
+       }
+       
+       width = maxs[0] - mins[0];
+       height = maxs[2] - mins[2];
+
+       if (!g_data.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_data.fixedwidth / 2;
+               iheight = g_data.fixedheight;
+
+               s_scale = (float)(iwidth-4) / width;
+               t_scale = (float)(iheight-4) / height;
+       }
+
+       // make the width a multiple of 4; some hardware requires this, and it ensures
+       // dword alignment for each scan
+       swidth = iwidth*2;
+       g_data.skinwidth = (swidth + 3) & ~3;
+       g_data.skinheight = iheight;
+*/
+
+       for (i=0; i<numtri ; i++, ptri++, bTri++)
+       {
+               int j;
+
+               for (j=0 ; j<3 ; j++) 
+               {
+                       pbasevert = ptri->verts[j];
+
+                       VectorCopy( ptri->verts[j], bTri->v[j].xyz);
+                       VectorCopy( ptri->normals[j], bTri->v[j].normal );
+
+                       bTri->v[j].st[0] = ptri->texcoords[j][0];
+                       bTri->v[j].st[1] = ptri->texcoords[j][1];
+               }
+       }
+}
+
+static void BuildBaseFrame( const char *filename, ObjectAnimationFrame_t *pOAF )
+{
+       baseTriangle_t  *bTri;
+       baseVertex_t    *bVert;
+       int i, j;
+
+       // calculate the base triangles
+       for ( i = 0; i < g_data.model.numSurfaces; i++ )
+       {
+               CopyTrianglesToBaseTriangles( pOAF->surfaces[i]->triangles, 
+                                                       pOAF->surfaces[i]->numtriangles,
+                                                                       g_data.surfData[i].baseTriangles );
+
+               strcpy( g_data.surfData[i].header.name, pOAF->surfaces[i]->name );
+
+               g_data.surfData[i].header.numTriangles = pOAF->surfaces[i]->numtriangles;
+               g_data.surfData[i].header.numVerts = 0;
+
+/*
+               if ( strstr( filename, gamedir + 1 ) )
+               {
+                       strcpy( shaderName, strstr( filename, gamedir + 1 ) + strlen( gamedir ) - 1 );
+               }
+               else
+               {
+                       strcpy( shaderName, filename );
+               }
+
+               if ( strrchr( shaderName, '/' ) )
+                       *( strrchr( shaderName, '/' ) + 1 ) = 0;
+
+
+               strcpy( shaderName, pOAF->surfaces[i]->materialname );
+*/
+               strcpy( g_data.surfData[i].shaders[g_data.surfData[i].header.numShaders].name, pOAF->surfaces[i]->materialname );
+
+               g_data.surfData[i].header.numShaders++;
+       }
+
+       //
+       // compute unique vertices for each polyset
+       //
+       for ( i = 0; i < g_data.model.numSurfaces; i++ )
+       {
+               int t;
+
+               for ( t = 0; t < pOAF->surfaces[i]->numtriangles; t++ )
+               {
+                       bTri = &g_data.surfData[i].baseTriangles[t];
+
+                       for (j=0 ; j<3 ; j++)
+                       {
+                               int k;
+
+                               bVert = &bTri->v[j];
+
+                               // get the xyz index
+                               for ( k = 0; k < g_data.surfData[i].header.numVerts; k++ )
+                               {
+                                       if ( ( g_data.surfData[i].baseVertexes[k].st[0] == bVert->st[0] ) &&
+                                                ( g_data.surfData[i].baseVertexes[k].st[1] == bVert->st[1] ) &&
+                                                ( VectorCompare (bVert->xyz, g_data.surfData[i].baseVertexes[k].xyz) ) &&
+                                                ( VectorCompare (bVert->normal, g_data.surfData[i].baseVertexes[k].normal) ) )
+                                       {
+                                               break;  // this vertex is already in the base vertex list
+                                       }
+                               }
+
+                               if (k == g_data.surfData[i].header.numVerts)    { // new index
+                                       g_data.surfData[i].baseVertexes[g_data.surfData[i].header.numVerts] = *bVert;
+                                       g_data.surfData[i].header.numVerts++;
+                               }
+
+                               bVert->index = k;
+
+                               g_data.surfData[i].lodTriangles[t][j] = k;
+                       }
+               }
+       }
+
+       //
+       // find tags
+       //
+       for ( i = 0; i < g_data.model.numSurfaces; i++ )
+       {
+               if ( strstr( pOAF->surfaces[i]->name, "tag_" ) == pOAF->surfaces[i]->name )
+               {
+                       if ( pOAF->surfaces[i]->numtriangles != 1 )
+                       {
+                               Error( "tag polysets must consist of only one triangle" );
+                       }
+                       if ( strstr( filename, "_flash.md3" ) && !strcmp( pOAF->surfaces[i]->name, "tag_parent" ) )
+                               continue;
+                       printf( "found tag '%s'\n", pOAF->surfaces[i]->name );
+                       g_data.model.numTags++;
+               }
+       }
+
+}
+
+static int LoadModelFile( const char *filename, polyset_t **psets, int *numpolysets )
+{
+       int                     time1;
+       char            file1[1024];
+       const char                      *frameFile;
+
+       printf ("---------------------\n");
+       if ( filename[1] != ':' )
+       {
+               frameFile = filename;
+               sprintf( file1, "%s/%s", g_cddir, frameFile );
+       }
+       else
+       {
+               strcpy( file1, filename );
+       }
+
+       time1 = FileTime (file1);
+       if (time1 == -1)
+               Error ("%s doesn't exist", file1);
+
+       //
+       // load the base triangles
+       //
+       *psets = Polyset_LoadSets( file1, numpolysets, g_data.maxSurfaceTris );
+
+       //
+       // snap polysets
+       //
+       Polyset_SnapSets( *psets, *numpolysets );
+
+       if ( strstr( file1, ".3ds" ) || strstr( file1, ".3DS" ) )
+               return MD3_TYPE_BASE3DS;
+
+       Error( "Unknown model file type" );
+
+       return MD3_TYPE_UNKNOWN;
+}
+
+/*
+=================
+Cmd_Base
+=================
+*/
+void Cmd_Base( void )
+{
+       char filename[1024];
+
+       GetToken( qfalse );
+       sprintf( filename, "%s/%s", g_cddir, token );
+       LoadBase( filename );
+}
+
+static void LoadBase( const char *filename )
+{
+       int numpolysets;
+       polyset_t *psets;
+       int                     i;
+       ObjectAnimationFrame_t oaf;
+
+       // determine polyset splitting threshold
+       if ( TokenAvailable() )
+       {
+               GetToken( qfalse );
+               g_data.maxSurfaceTris = atoi( token );
+       }
+       else
+       {
+               g_data.maxSurfaceTris = MAX_SURFACE_TRIS - 1;
+       }
+
+       g_data.type = LoadModelFile( filename, &psets, &numpolysets );
+
+       Polyset_ComputeNormals( psets, numpolysets );
+
+       g_data.model.numSurfaces = numpolysets;
+
+       memset( &oaf, 0, sizeof( oaf ) );
+
+       for ( i = 0; i < numpolysets; i++ )
+       {
+               oaf.surfaces[i] = &psets[i];
+               oaf.numSurfaces = numpolysets;
+       }
+
+       BuildBaseFrame( filename, &oaf );
+
+       free( psets[0].triangles );
+       free( psets );
+}
+
+/*
+=================
+Cmd_SpriteBase
+
+$spritebase xorg yorg width height
+
+Generate a single square for the model
+=================
+*/
+void Cmd_SpriteBase (void)
+{
+       float           xl, yl, width, height;
+
+       g_data.type = MD3_TYPE_SPRITE;
+
+       GetToken (qfalse);
+       xl = atof(token);
+       GetToken (qfalse);
+       yl = atof(token);
+       GetToken (qfalse);
+       width = atof(token);
+       GetToken (qfalse);
+       height = atof(token);
+
+//     if (g_skipmodel || g_release || g_archive)
+//             return;
+
+       printf ("---------------------\n");
+
+       g_data.surfData[0].verts[0] = ( float * ) calloc( 1, sizeof( float ) * 6 * 4 );
+
+       g_data.surfData[0].header.numVerts = 4;
+
+       g_data.surfData[0].verts[0][0+0] = 0;
+       g_data.surfData[0].verts[0][0+1] = -xl;
+       g_data.surfData[0].verts[0][0+2] = yl + height;
+
+       g_data.surfData[0].verts[0][0+3] = -1;
+       g_data.surfData[0].verts[0][0+4] = 0;
+       g_data.surfData[0].verts[0][0+5] = 0;
+       g_data.surfData[0].baseVertexes[0].st[0] = 0;
+       g_data.surfData[0].baseVertexes[0].st[1] = 0;
+
+
+       g_data.surfData[0].verts[0][6+0] = 0;
+       g_data.surfData[0].verts[0][6+1] = -xl - width;
+       g_data.surfData[0].verts[0][6+2] = yl + height;
+       
+       g_data.surfData[0].verts[0][6+3] = -1;
+       g_data.surfData[0].verts[0][6+4] = 0;
+       g_data.surfData[0].verts[0][6+5] = 0;
+       g_data.surfData[0].baseVertexes[1].st[0] = 1;
+       g_data.surfData[0].baseVertexes[1].st[1] = 0;
+
+
+       g_data.surfData[0].verts[0][12+0] = 0;
+       g_data.surfData[0].verts[0][12+1] = -xl - width;
+       g_data.surfData[0].verts[0][12+2] = yl;
+
+       g_data.surfData[0].verts[0][12+3] = -1;
+       g_data.surfData[0].verts[0][12+4] = 0;
+       g_data.surfData[0].verts[0][12+5] = 0;
+       g_data.surfData[0].baseVertexes[2].st[0] = 1;
+       g_data.surfData[0].baseVertexes[2].st[1] = 1;
+
+
+       g_data.surfData[0].verts[0][18+0] = 0;
+       g_data.surfData[0].verts[0][18+1] = -xl;
+       g_data.surfData[0].verts[0][18+2] = yl;
+
+       g_data.surfData[0].verts[0][18+3] = -1;
+       g_data.surfData[0].verts[0][18+4] = 0;
+       g_data.surfData[0].verts[0][18+5] = 0;
+       g_data.surfData[0].baseVertexes[3].st[0] = 0;
+       g_data.surfData[0].baseVertexes[3].st[1] = 1;
+
+       g_data.surfData[0].lodTriangles[0][0] = 0;
+       g_data.surfData[0].lodTriangles[0][1] = 1;
+       g_data.surfData[0].lodTriangles[0][2] = 2;
+
+       g_data.surfData[0].lodTriangles[1][0] = 2;
+       g_data.surfData[0].lodTriangles[1][1] = 3;
+       g_data.surfData[0].lodTriangles[1][2] = 0;
+
+       g_data.model.numSurfaces = 1;
+
+       g_data.surfData[0].header.numTriangles = 2;
+       g_data.surfData[0].header.numVerts = 4;
+
+       g_data.model.numFrames = 1;
+}
+
+/*
+===========================================================================
+
+  FRAME GRABBING
+
+===========================================================================
+*/
+
+/*
+===============
+GrabFrame
+===============
+*/
+void GrabFrame (const char *frame)
+{
+       int                     i, j, k;
+       char            file1[1024];
+       md3Frame_t              *fr;
+       md3Tag_t                tagParent;
+       float           *frameXyz;
+       float           *frameNormals;
+       const char      *framefile;
+       polyset_t               *psets;
+       qboolean         parentTagExists = qfalse;
+       int                      numpolysets;
+       int                     numtags = 0;
+       int                     tagcount;
+
+       // the frame 'run1' will be looked for as either
+       // run.1 or run1.tri, so the new alias sequence save
+       // feature an be used
+       if ( frame[1] != ':' )
+       {
+//             framefile = FindFrameFile (frame);
+               framefile = frame;
+               sprintf (file1, "%s/%s",g_cddir, framefile);
+       }
+       else
+       {
+               strcpy( file1, frame );
+       }
+       printf ("grabbing %s\n", file1);
+
+       if (g_data.model.numFrames >= MD3_MAX_FRAMES)
+               Error ("model.numFrames >= MD3_MAX_FRAMES");
+       fr = &g_data.frames[g_data.model.numFrames];
+
+       strcpy (fr->name, frame);
+
+       psets = Polyset_LoadSets( file1, &numpolysets, g_data.maxSurfaceTris );
+
+       //
+       // snap polysets
+       //
+       Polyset_SnapSets( psets, numpolysets );
+
+       //
+       // compute vertex normals
+       //
+       Polyset_ComputeNormals( psets, numpolysets );
+
+       //
+       // flip everything to compensate for the alias coordinate system
+       // and perform global scale and adjust
+       //
+       for ( i = 0; i < g_data.model.numSurfaces; i++ )
+       {
+               triangle_t *ptri = psets[i].triangles;
+               int t;
+
+               for ( t = 0; t < psets[i].numtriangles; t++ )
+               {
+
+                       for ( j = 0; j < 3; j++ )
+                       {
+
+                               // scale and adjust
+                               for ( k = 0 ; k < 3 ; k++ ) {
+                                       ptri[t].verts[j][k] = ptri[t].verts[j][k] * g_data.scale_up +
+                                               g_data.adjust[k];
+
+                                       if ( ptri[t].verts[j][k] > 1023 ||
+                                                ptri[t].verts[j][k] < -1023 )
+                                       {
+                                               Error( "Model extents too large" );
+                                       }
+                               }
+                       }
+               }
+       }
+
+       //
+       // find and count tags, locate parent tag
+       //
+       for ( i = 0; i < numpolysets; i++ )
+       {
+               if ( strstr( psets[i].name, "tag_" ) == psets[i].name )
+               {
+                       if ( strstr( psets[i].name, "tag_parent" ) == psets[i].name )
+                       {
+                               if ( strstr( psets[i].name, "tag_parent" ) )
+                               {
+                                       float tri[3][3];
+
+                                       if ( parentTagExists )
+                                               Error( "Multiple parent tags not allowed" );
+
+                                       memcpy( tri[0], psets[i].triangles[0].verts[0], sizeof( float ) * 3 );
+                                       memcpy( tri[1], psets[i].triangles[0].verts[1], sizeof( float ) * 3 );
+                                       memcpy( tri[2], psets[i].triangles[0].verts[2], sizeof( float ) * 3 );
+
+                                       MD3_ComputeTagFromTri( &tagParent, tri );
+                                       strcpy( tagParent.name, psets[i].name );
+                                       g_data.tags[g_data.model.numFrames][numtags] = tagParent;
+                                       parentTagExists = qtrue;
+
+                               }
+                       }
+                       numtags++;
+               }
+
+               if ( strcmp( psets[i].name, g_data.surfData[i].header.name ) )
+               {
+                       Error( "Mismatched surfaces from base('%s') to frame('%s') in model '%s'\n", g_data.surfData[i].header.name, psets[i].name, g_modelname );
+               }
+       }
+
+       if ( numtags != g_data.model.numTags )
+       {
+               Error( "mismatched number of tags in frame(%d) vs. base(%d)", numtags, g_data.model.numTags );
+       }
+
+       if ( numpolysets != g_data.model.numSurfaces )
+       {
+               Error( "mismatched number of surfaces in frame(%d) vs. base(%d)", numpolysets-numtags, g_data.model.numSurfaces );
+       }
+       
+       //
+       // prepare to accumulate bounds and normals
+       //
+       ClearBounds( fr->bounds[0], fr->bounds[1] );
+
+       //
+       // 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, tagcount = 0; i < numpolysets; i++ )
+       {
+               int t;
+               triangle_t *pTris = psets[i].triangles;
+
+               strcpy( g_data.surfData[i].header.name, psets[i].name );
+
+               //
+               // parent tag adjust
+               //
+               if ( parentTagExists ) {
+                       for ( t = 0; t < psets[i].numtriangles; t++ )
+                       {
+                               for ( j = 0; j < 3 ; j++ )
+                               {
+                                       vec3_t tmp;
+                                       
+                                       VectorSubtract( pTris[t].verts[j], tagParent.origin, tmp );
+
+                                       pTris[t].verts[j][0] = DotProduct( tmp, tagParent.axis[0] );
+                                       pTris[t].verts[j][1] = DotProduct( tmp, tagParent.axis[1] );
+                                       pTris[t].verts[j][2] = DotProduct( tmp, tagParent.axis[2] );
+
+                                       VectorCopy( pTris[t].normals[j], tmp );
+                                       pTris[t].normals[j][0] = DotProduct( tmp, tagParent.axis[0] );
+                                       pTris[t].normals[j][1] = DotProduct( tmp, tagParent.axis[1] );
+                                       pTris[t].normals[j][2] = DotProduct( tmp, tagParent.axis[2] );
+                               }
+                       }
+               }
+
+               //
+               // compute tag data
+               //
+               if ( strstr( psets[i].name, "tag_" ) == psets[i].name )
+               {
+                       md3Tag_t *pTag = &g_data.tags[g_data.model.numFrames][tagcount];
+                       float tri[3][3];
+
+                       strcpy( pTag->name, psets[i].name );
+
+                       memcpy( tri[0], pTris[0].verts[0], sizeof( float ) * 3 );
+                       memcpy( tri[1], pTris[0].verts[1], sizeof( float ) * 3 );
+                       memcpy( tri[2], pTris[0].verts[2], sizeof( float ) * 3 );
+
+                       MD3_ComputeTagFromTri( pTag, tri );
+                       tagcount++;
+               }
+               else
+               {
+                       if ( g_data.surfData[i].verts[g_data.model.numFrames] )
+                               free( g_data.surfData[i].verts[g_data.model.numFrames] );
+                       frameXyz = g_data.surfData[i].verts[g_data.model.numFrames] = calloc( 1, sizeof( float ) * 6 * g_data.surfData[i].header.numVerts );
+                       frameNormals = frameXyz + 3;
+
+                       for ( t = 0; t < psets[i].numtriangles; t++ )
+                       {
+                               for ( j = 0; j < 3 ; j++ )
+                               {
+                                       int index;
+
+                                       index = g_data.surfData[i].baseTriangles[t].v[j].index;
+                                       frameXyz[index*6+0] = pTris[t].verts[j][0];
+                                       frameXyz[index*6+1] = pTris[t].verts[j][1];
+                                       frameXyz[index*6+2] = pTris[t].verts[j][2];
+                                       frameNormals[index*6+0] =  pTris[t].normals[j][0];
+                                       frameNormals[index*6+1] =  pTris[t].normals[j][1];
+                                       frameNormals[index*6+2] =  pTris[t].normals[j][2];
+                                       AddPointToBounds (&frameXyz[index*6], fr->bounds[0], fr->bounds[1] );
+                               }
+                       }
+               }
+       }
+
+       g_data.model.numFrames++;
+
+       // only free the first triangle array, all of the psets in this array share the
+       // same triangle pool!!!
+//     free( psets[0].triangles );
+//     free( psets );
+}
+
+//===========================================================================
+
+
+
+/*
+===============
+Cmd_Frame      
+===============
+*/
+void Cmd_Frame (void)
+{
+       while (TokenAvailable())
+       {
+               GetToken (qfalse);
+               if (g_skipmodel)
+                       continue;
+               if (g_release || g_archive)
+               {
+                       g_data.model.numFrames = 1;     // don't skip the writeout
+                       continue;
+               }
+
+               GrabFrame( token );
+       }
+}
+
+
+/*
+===============
+Cmd_Skin
+
+===============
+*/
+void SkinFrom3DS( const char *filename )
+{
+       polyset_t *psets;
+       char name[1024];
+       int numPolysets;
+       int i;
+
+       _3DS_LoadPolysets( filename, &psets, &numPolysets, g_verbose );
+
+       for ( i = 0; i < numPolysets; i++ )
+       {
+/*
+               if ( strstr( filename, gamedir + 1 ) )
+               {
+                       strcpy( name, strstr( filename, gamedir + 1 ) + strlen( gamedir ) - 1 );
+               }
+               else
+               {
+                       strcpy( name, filename );
+               }
+
+               if ( strrchr( name, '/' ) )
+                       *( strrchr( name, '/' ) + 1 ) = 0;
+*/
+               strcpy( name, psets[i].materialname );
+               strcpy( g_data.surfData[i].shaders[g_data.surfData[i].header.numShaders].name, name );
+
+               g_data.surfData[i].header.numShaders++;
+       }
+
+       free( psets[0].triangles );
+       free( psets );
+}
+
+void Cmd_Skin (void)
+{
+       char skinfile[1024];
+
+       if ( g_data.type == MD3_TYPE_BASE3DS )
+       {
+               GetToken( qfalse );
+
+               sprintf( skinfile, "%s/%s", g_cddir, token );
+
+               if ( strstr( token, ".3ds" ) || strstr( token, ".3DS" ) )
+               {
+                       SkinFrom3DS( skinfile );
+               }
+               else
+               {
+                       Error( "Unknown file format for $skin '%s'\n", skinfile );
+               }
+       }
+       else
+       {
+               Error( "invalid model type while processing $skin" );
+       }
+
+       g_data.model.numSkins++;
+}
+
+/*
+=================
+Cmd_SpriteShader
+=================
+
+This routine is also called for $oldskin
+
+*/
+void Cmd_SpriteShader()
+{
+       GetToken( qfalse );
+       strcpy( g_data.surfData[0].shaders[g_data.surfData[0].header.numShaders].name, token );
+       g_data.surfData[0].header.numShaders++;
+       g_data.model.numSkins++;
+}
+
+/*
+=================
+Cmd_Origin
+=================
+*/
+void Cmd_Origin (void)
+{
+       // rotate points into frame of reference so model points down the
+       // positive x axis
+       // FIXME: use alias native coordinate system
+       GetToken (qfalse);
+       g_data.adjust[1] = -atof (token);
+
+       GetToken (qfalse);
+       g_data.adjust[0] = atof (token);
+
+       GetToken (qfalse);
+       g_data.adjust[2] = -atof (token);
+}
+
+
+/*
+=================
+Cmd_ScaleUp
+=================
+*/
+void Cmd_ScaleUp (void)
+{
+       GetToken (qfalse);
+       g_data.scale_up = atof (token);
+       if (g_skipmodel || g_release || g_archive)
+               return;
+
+       printf ("Scale up: %f\n", g_data.scale_up);
+}
+
+
+/*
+=================
+Cmd_Skinsize
+
+Set a skin size other than the default
+QUAKE3: not needed
+=================
+*/
+void Cmd_Skinsize (void)
+{
+       GetToken (qfalse);
+       g_data.fixedwidth = atoi(token);
+       GetToken (qfalse);
+       g_data.fixedheight = atoi(token);
+}
+
+/*
+=================
+Cmd_Modelname
+
+Begin creating a model of the given name
+=================
+*/
+void Cmd_Modelname (void)
+{
+       FinishModel ( TYPE_UNKNOWN );
+       ClearModel ();
+
+       GetToken (qfalse);
+       strcpy (g_modelname, token);
+       StripExtension (g_modelname);
+       strcat (g_modelname, ".md3");
+       strcpy (g_data.model.name, g_modelname);
+}
+
+/*
+===============
+fCmd_Cd
+===============
+*/
+void Cmd_Cd (void)
+{
+       if ( g_cddir[0]) {
+               Error ("$cd command without a $modelname");
+       }
+
+       GetToken (qfalse);
+
+       sprintf ( g_cddir, "%s%s", gamedir, token);
+
+       // 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 models/monsters)
+       if (!g_only[0])
+               return;
+       if (strncmp(token, g_only, strlen(g_only)))
+       {
+               g_skipmodel = qtrue;
+               printf ("skipping %s\n", token);
+       }
+}
+
+void Convert3DStoMD3( const char *file )
+{
+       LoadBase( file );
+       GrabFrame( file );
+       SkinFrom3DS( file );
+
+       strcpy( g_data.model.name, g_modelname );
+
+       FinishModel( TYPE_UNKNOWN );
+       ClearModel();
+}
+
+/*
+** Cmd_3DSConvert
+*/
+void Cmd_3DSConvert()
+{
+       char file[1024];
+
+       FinishModel( TYPE_UNKNOWN );
+       ClearModel();
+
+       GetToken( qfalse );
+
+       sprintf( file, "%s%s", gamedir, token );
+       strcpy( g_modelname, token );
+       if ( strrchr( g_modelname, '.' ) )
+               *strrchr( g_modelname, '.' ) = 0;
+       strcat( g_modelname, ".md3" );
+
+       if ( FileTime( file ) == -1 )
+               Error( "%s doesn't exist", file );
+
+       if ( TokenAvailable() )
+       {
+               GetToken( qfalse );
+               g_data.scale_up = atof( token );
+       }
+
+       Convert3DStoMD3( file );
+}
+
+static void ConvertASE( const char *filename, int type, qboolean grabAnims );
+
+/*
+** Cmd_ASEConvert
+*/
+void Cmd_ASEConvert( qboolean grabAnims )
+{
+       char filename[1024];
+       int type = TYPE_ITEM;
+
+       FinishModel( TYPE_UNKNOWN );
+       ClearModel();
+
+       GetToken( qfalse );
+       sprintf( filename, "%s%s", gamedir, token );
+
+       strcpy (g_modelname, token);
+       StripExtension (g_modelname);
+       strcat (g_modelname, ".md3");
+       strcpy (g_data.model.name, g_modelname);
+
+       if ( !strstr( filename, ".ase" ) && !strstr( filename, ".ASE" ) )
+               strcat( filename, ".ASE" );
+
+       g_data.maxSurfaceTris = MAX_SURFACE_TRIS - 1;
+
+       while ( TokenAvailable() )
+       {
+               GetToken( qfalse );
+               if ( !strcmp( token, "-origin" ) )
+               {
+                       if ( !TokenAvailable() )
+                               Error( "missing parameter for -origin" );
+                       GetToken( qfalse );
+                       g_data.aseAdjust[1] = -atof( token );
+
+                       if ( !TokenAvailable() )
+                               Error( "missing parameter for -origin" );
+                       GetToken( qfalse );
+                       g_data.aseAdjust[0] = atof (token);
+
+                       if ( !TokenAvailable() )
+                               Error( "missing parameter for -origin" );
+                       GetToken( qfalse );
+                       g_data.aseAdjust[2] = -atof (token);
+               }
+               else if ( !strcmp( token, "-lod" ) )
+               {
+                       if ( !TokenAvailable() )
+                               Error( "No parameter for -lod" );
+                       GetToken( qfalse );
+                       g_data.currentLod = atoi( token );
+                       if ( g_data.currentLod > MD3_MAX_LODS - 1 )
+                       {
+                               Error( "-lod parameter too large! (%d)\n", g_data.currentLod );
+                       }
+
+                       if ( !TokenAvailable() )
+                               Error( "No second parameter for -lod" );
+                       GetToken( qfalse );
+                       g_data.lodBias = atof( token );
+               }
+               else if ( !strcmp( token, "-maxtris" ) )
+               {
+                       if ( !TokenAvailable() )
+                               Error( "No parameter for -maxtris" );
+                       GetToken( qfalse );
+                       g_data.maxSurfaceTris = atoi( token );
+               }
+               else if ( !strcmp( token, "-playerparms" ) )
+               {
+                       if ( !TokenAvailable() )
+                               Error( "missing skip start parameter for -playerparms" );
+                       GetToken( qfalse );
+                       g_data.lowerSkipFrameStart = atoi( token );
+
+#if 0
+                       if ( !TokenAvailable() )
+                               Error( "missing skip end parameter for -playerparms" );
+                       GetToken( qfalse );
+                       g_data.lowerSkipFrameEnd = atoi( token );
+#endif
+
+                       if ( !TokenAvailable() )
+                               Error( "missing upper parameter for -playerparms" );
+                       GetToken( qfalse );
+                       g_data.maxUpperFrames = atoi( token );
+
+                       g_data.lowerSkipFrameEnd = g_data.maxUpperFrames - 1;
+
+#if 0
+                       if ( !TokenAvailable() )
+                               Error( "missing head parameter for -playerparms" );
+                       GetToken( qfalse );
+                       g_data.maxHeadFrames = atoi( token );
+#endif
+                       g_data.maxHeadFrames = 1;
+
+                       if ( type != TYPE_ITEM )
+                               Error( "invalid argument" );
+
+                       type = TYPE_PLAYER;
+               }
+               else if ( !strcmp( token, "-weapon" ) )
+               {
+                       if ( type != TYPE_ITEM )
+                               Error( "invalid argument" );
+
+                       type = TYPE_WEAPON;
+               }
+       }
+
+       g_data.type = MD3_TYPE_ASE;
+
+       if ( type == TYPE_WEAPON && grabAnims )
+       {
+               Error( "can't grab anims with weapon models" );
+       }
+       if ( type == TYPE_PLAYER && !grabAnims )
+       {
+               Error( "player models must be converted with $aseanimconvert" );
+       }
+
+       if ( type == TYPE_WEAPON )
+       {
+               ConvertASE( filename, type, qfalse );
+               ConvertASE( filename, TYPE_HAND, qtrue );
+       }
+       else
+       {
+               ConvertASE( filename, type, grabAnims );
+       }
+}
+
+static int GetSurfaceAnimations( SurfaceAnimation_t sanims[MAX_ANIM_SURFACES], 
+                                                                 const char *part,
+                                                                 int skipFrameStart,
+                                                                 int skipFrameEnd,
+                                                                 int maxFrames )
+
+{
+       int numSurfaces;
+       int numValidSurfaces;
+       int i;
+       int numFrames = -1;
+
+       if ( ( numSurfaces = ASE_GetNumSurfaces() ) > MAX_ANIM_SURFACES )
+       {
+               Error( "Too many surfaces in ASE" );
+       }
+
+       for ( numValidSurfaces = 0, i = 0; i < numSurfaces; i++ )
+       {
+               polyset_t *splitSets;
+               int numNewFrames;
+               const char *surfaceName = ASE_GetSurfaceName( i );
+
+               if ( !surfaceName )
+               {
+                       continue;
+//                     Error( "Missing animation frames in model" );
+               }
+
+               if ( strstr( surfaceName, "tag_" ) || 
+                        !strcmp( part, "any" ) || 
+                        ( strstr( surfaceName, part ) == surfaceName ) )
+               {
+
+                       // skip this if it's an inappropriate tag
+                       if ( strcmp( part, "any" ) )
+                       {
+                               // ignore non-"tag_head" tags if this is the head
+                               if ( !strcmp( part, "h_" ) && strstr( surfaceName, "tag_" ) && strcmp( surfaceName, "tag_head" ) )
+                                       continue;
+                               // ignore "tag_head" if this is the legs
+                               if ( !strcmp( part, "l_" ) && !strcmp( surfaceName, "tag_head" ) )
+                                       continue;
+                               // ignore "tag_weapon" if this is the legs
+                               if ( !strcmp( part, "l_" ) && !strcmp( surfaceName, "tag_weapon" ) )
+                                       continue;
+                       }
+
+                       if ( ( sanims[numValidSurfaces].frames = ASE_GetSurfaceAnimation( i, &sanims[numValidSurfaces].numFrames, skipFrameStart, skipFrameEnd, maxFrames ) ) != 0 )
+                       {
+                               splitSets = Polyset_SplitSets( sanims[numValidSurfaces].frames, sanims[numValidSurfaces].numFrames, &numNewFrames, g_data.maxSurfaceTris );
+                               
+                               if ( numFrames == -1 )
+                                       numFrames = sanims[numValidSurfaces].numFrames;
+                               else if ( numFrames != sanims[numValidSurfaces].numFrames )
+                                       Error( "Different number of animation frames on surfaces" );
+                               
+                               if ( sanims[numValidSurfaces].frames != splitSets )
+                               {
+                                       int j;
+
+                                       // free old data if we split the surfaces
+                                       for ( j = 0; j < sanims[numValidSurfaces].numFrames; j++ )
+                                       {
+                                               free( sanims[numValidSurfaces].frames[j].triangles );
+                                               free( sanims[numValidSurfaces].frames );
+                                       }
+                                       
+                                       sanims[numValidSurfaces].frames = splitSets;
+                                       sanims[numValidSurfaces].numFrames = numNewFrames;
+                               }
+                               Polyset_SnapSets( sanims[numValidSurfaces].frames, sanims[numValidSurfaces].numFrames );
+                               Polyset_ComputeNormals( sanims[numValidSurfaces].frames, sanims[numValidSurfaces].numFrames );
+
+                               numValidSurfaces++;
+                       }
+               }
+       }
+
+       return numValidSurfaces;
+}
+
+static int SurfaceOrderToFrameOrder( SurfaceAnimation_t sanims[], ObjectAnimationFrame_t oanims[], int numSurfaces )
+{
+       int i, s;
+       int numFrames = -1;
+
+       /*
+       ** we have the data here arranged in surface order, now we need to convert it to 
+       ** frame order
+       */
+       for ( i = 0, s = 0; i < numSurfaces; i++ )
+       {
+               int j;
+               
+               if ( sanims[i].frames )
+               {
+                       if ( numFrames == -1 )
+                               numFrames = sanims[i].numFrames;
+                       else if ( numFrames != sanims[i].numFrames )
+                               Error( "numFrames != sanims[i].numFrames (%d != %d)\n", numFrames, sanims[i].numFrames );
+
+                       for ( j = 0; j < sanims[i].numFrames; j++ )
+                       {
+                               oanims[j].surfaces[s] = &sanims[i].frames[j];
+                               oanims[j].numSurfaces = numSurfaces;
+                       }
+                       s++;
+               }
+       }
+
+       return numFrames;
+}
+
+static void WriteMD3( const char *_filename, ObjectAnimationFrame_t oanims[], int numFrames )
+{
+       char filename[1024];
+
+       strcpy( filename, _filename );
+       if ( strchr( filename, '.' ) )
+               *strchr( filename, '.' ) = 0;
+       strcat( filename, ".md3" );
+}
+
+static void BuildAnimationFromOAFs( const char *filename, ObjectAnimationFrame_t oanims[], int numFrames, int type )
+{
+       int f, i, j, tagcount;
+       float *frameXyz;
+       float *frameNormals;
+
+       g_data.model.numSurfaces = oanims[0].numSurfaces;
+       g_data.model.numFrames = numFrames;
+       if ( g_data.model.numFrames < 0)
+               Error ("model.numFrames < 0");
+       if ( g_data.model.numFrames >= MD3_MAX_FRAMES)
+               Error ("model.numFrames >= MD3_MAX_FRAMES");
+
+       // build base frame
+       BuildBaseFrame( filename, &oanims[0] );
+       
+       // build animation frames
+       for ( f = 0; f < numFrames; f++ )
+       {
+               ObjectAnimationFrame_t *pOAF = &oanims[f];
+               qboolean        parentTagExists = qfalse;
+               md3Tag_t        tagParent;
+               int                     numtags = 0;
+               md3Frame_t              *fr;
+               
+               fr = &g_data.frames[f];
+               
+               strcpy( fr->name, "(from ASE)" );
+               
+               // scale and adjust frame
+               for ( i = 0; i < pOAF->numSurfaces; i++ )
+               {
+                       triangle_t *pTris = pOAF->surfaces[i]->triangles;
+                       int t;
+                       
+                       for ( t = 0; t < pOAF->surfaces[i]->numtriangles; t++ )
+                       {
+                               for ( j = 0; j < 3; j++ )
+                               {
+                                       int k;
+                                       
+                                       // scale and adjust
+                                       for ( k = 0 ; k < 3 ; k++ ) {
+                                               pTris[t].verts[j][k] = pTris[t].verts[j][k] * g_data.scale_up +
+                                                       g_data.aseAdjust[k];
+                                               
+                                               if ( pTris[t].verts[j][k] > 1023 ||
+                                                       pTris[t].verts[j][k] < -1023 )
+                                               {
+                                                       Error( "Model extents too large" );
+                                               }
+                                       }
+                               }
+                       }
+               }
+               
+               //
+               // find and count tags, locate parent tag
+               //
+               for ( i = 0; i < pOAF->numSurfaces; i++ )
+               {
+                       if ( strstr( pOAF->surfaces[i]->name, "tag_" ) == pOAF->surfaces[i]->name )
+                       {
+                               // ignore parent tags when grabbing a weapon model and this is the flash portion
+                               if ( !strcmp( pOAF->surfaces[i]->name, "tag_parent" ) && strstr( filename, "_flash.md3" ) )
+                               {
+                                       continue;
+                               }
+                               else if ( !strstr( filename, "_hand.md3" ) && (
+                                        ( !strcmp( pOAF->surfaces[i]->name, "tag_parent" ) && !strstr( filename, "_flash.md3" ) ) ||
+                                        ( !strcmp( pOAF->surfaces[i]->name, "tag_torso" ) && ( strstr( filename, "upper_" ) || strstr( filename, "upper.md3" ) ) ) ||
+                                        ( !strcmp( pOAF->surfaces[i]->name, "tag_head" ) && ( strstr( filename, "head.md3" ) || strstr( filename, "head_" ) ) ) ||
+                                        ( !strcmp( pOAF->surfaces[i]->name, "tag_flash" ) && strstr( filename, "_flash.md3" ) )|| 
+                                        ( !strcmp( pOAF->surfaces[i]->name, "tag_weapon" ) && type == TYPE_WEAPON ) ) )
+                               {
+                                       float tri[3][3];
+                                       
+                                       if ( parentTagExists )
+                                               Error( "Multiple parent tags not allowed" );
+                                       
+                                       memcpy( tri[0], pOAF->surfaces[i]->triangles[0].verts[0], sizeof( float ) * 3 );
+                                       memcpy( tri[1], pOAF->surfaces[i]->triangles[0].verts[1], sizeof( float ) * 3 );
+                                       memcpy( tri[2], pOAF->surfaces[i]->triangles[0].verts[2], sizeof( float ) * 3 );
+                                       
+                                       MD3_ComputeTagFromTri( &tagParent, tri );
+                                       strcpy( tagParent.name, "tag_parent" );
+                                       g_data.tags[f][numtags] = tagParent;
+                                       parentTagExists = qtrue;
+                               }
+                               else
+                               {
+                                       float tri[3][3];
+                               
+                                       memcpy( tri[0], pOAF->surfaces[i]->triangles[0].verts[0], sizeof( float ) * 3 );
+                                       memcpy( tri[1], pOAF->surfaces[i]->triangles[0].verts[1], sizeof( float ) * 3 );
+                                       memcpy( tri[2], pOAF->surfaces[i]->triangles[0].verts[2], sizeof( float ) * 3 );
+                                       
+                                       MD3_ComputeTagFromTri( &g_data.tags[f][numtags], tri );
+                                       strcpy( g_data.tags[f][numtags].name, pOAF->surfaces[i]->name );
+                                       if ( strstr( g_data.tags[f][numtags].name, "tag_flash" ) )
+                                               * ( strstr( g_data.tags[f][numtags].name, "tag_flash" ) + strlen( "tag_flash" ) ) = 0;
+                               }
+
+                               numtags++;
+                       }
+                       
+                       if ( strcmp( pOAF->surfaces[i]->name, g_data.surfData[i].header.name ) )
+                       {
+                               Error( "Mismatched surfaces from base('%s') to frame('%s') in model '%s'\n", g_data.surfData[i].header.name, pOAF->surfaces[i]->name, filename );
+                       }
+               }
+               
+               if ( numtags != g_data.model.numTags )
+               {
+                       Error( "mismatched number of tags in frame(%d) vs. base(%d)", numtags, g_data.model.numTags );
+               }
+               
+               //
+               // prepare to accumulate bounds and normals
+               //
+               ClearBounds( fr->bounds[0], fr->bounds[1] );
+               
+               //
+               // 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, tagcount = 0; i < pOAF->numSurfaces; i++ )
+               {
+                       int t;
+                       triangle_t *pTris = pOAF->surfaces[i]->triangles;
+                       
+                       //
+                       // parent tag adjust
+                       //
+                       if ( parentTagExists ) 
+                       {
+                               for ( t = 0; t < pOAF->surfaces[i]->numtriangles; t++ )
+                               {
+                                       for ( j = 0; j < 3 ; j++ )
+                                       {
+                                               vec3_t tmp;
+                                               
+                                               VectorSubtract( pTris[t].verts[j], tagParent.origin, tmp );
+                                               
+                                               pTris[t].verts[j][0] = DotProduct( tmp, tagParent.axis[0] );
+                                               pTris[t].verts[j][1] = DotProduct( tmp, tagParent.axis[1] );
+                                               pTris[t].verts[j][2] = DotProduct( tmp, tagParent.axis[2] );
+                                               
+                                               VectorCopy( pTris[t].normals[j], tmp );
+                                               pTris[t].normals[j][0] = DotProduct( tmp, tagParent.axis[0] );
+                                               pTris[t].normals[j][1] = DotProduct( tmp, tagParent.axis[1] );
+                                               pTris[t].normals[j][2] = DotProduct( tmp, tagParent.axis[2] );
+                                       }
+                               }
+                       }
+                       
+                       //
+                       // compute tag data
+                       //
+                       if ( strstr( pOAF->surfaces[i]->name, "tag_" ) == pOAF->surfaces[i]->name )
+                       {
+                               md3Tag_t *pTag = &g_data.tags[f][tagcount];
+                               float tri[3][3];
+                               
+                               strcpy( pTag->name, pOAF->surfaces[i]->name );
+                               
+                               memcpy( tri[0], pTris[0].verts[0], sizeof( float ) * 3 );
+                               memcpy( tri[1], pTris[0].verts[1], sizeof( float ) * 3 );
+                               memcpy( tri[2], pTris[0].verts[2], sizeof( float ) * 3 );
+                               
+                               MD3_ComputeTagFromTri( pTag, tri );
+                               tagcount++;
+                       }
+                       else
+                       {
+                               if ( g_data.surfData[i].verts[f] )
+                                       free( g_data.surfData[i].verts[f] );
+                               frameXyz = g_data.surfData[i].verts[f] = calloc( 1, sizeof( float ) * 6 * g_data.surfData[i].header.numVerts );
+                               frameNormals = frameXyz + 3;
+                               
+                               for ( t = 0; t < pOAF->surfaces[i]->numtriangles; t++ )
+                               {
+                                       for ( j = 0; j < 3 ; j++ )
+                                       {
+                                               int index;
+                                               
+                                               index = g_data.surfData[i].baseTriangles[t].v[j].index;
+                                               frameXyz[index*6+0] = pTris[t].verts[j][0];
+                                               frameXyz[index*6+1] = pTris[t].verts[j][1];
+                                               frameXyz[index*6+2] = pTris[t].verts[j][2];
+                                               frameNormals[index*6+0] =  pTris[t].normals[j][0];
+                                               frameNormals[index*6+1] =  pTris[t].normals[j][1];
+                                               frameNormals[index*6+2] =  pTris[t].normals[j][2];
+                                               AddPointToBounds (&frameXyz[index*6], fr->bounds[0], fr->bounds[1] );
+                                       }
+                               }
+                       }
+               }
+       }
+
+       if ( strstr( filename, gamedir + 1 ) )
+       {
+               strcpy( g_modelname, strstr( filename, gamedir + 1 ) + strlen( gamedir ) - 1 );
+       }
+       else
+       {
+               strcpy( g_modelname, filename );
+       }
+
+       FinishModel( type );
+       ClearModel();
+}
+
+static void ConvertASE( const char *filename, int type, qboolean grabAnims )
+{
+       int i, j;
+       int numSurfaces;
+       int numFrames = -1;
+       SurfaceAnimation_t surfaceAnimations[MAX_ANIM_SURFACES];
+       ObjectAnimationFrame_t objectAnimationFrames[MAX_ANIM_FRAMES];
+       char outfilename[1024];
+
+       /*
+       ** load ASE into memory
+       */
+       ASE_Load( filename, g_verbose, grabAnims );
+
+       /*
+       ** process parts
+       */
+       if ( type == TYPE_ITEM )
+       {
+               numSurfaces = GetSurfaceAnimations( surfaceAnimations, "any", -1, -1, -1 );
+
+               if ( numSurfaces <= 0 )
+                       Error( "numSurfaces <= 0" );
+
+               numFrames = SurfaceOrderToFrameOrder( surfaceAnimations, objectAnimationFrames, numSurfaces );
+
+               if ( numFrames <= 0 )
+                       Error( "numFrames <= 0" );
+
+               strcpy( outfilename, filename );
+               if ( strrchr( outfilename, '.' ) )
+                       *( strrchr( outfilename, '.' ) + 1 ) = 0;
+               strcat( outfilename, "md3" );
+               BuildAnimationFromOAFs( outfilename, objectAnimationFrames, numFrames, type );
+
+               // free memory
+               for ( i = 0; i < numSurfaces; i++ )
+               {
+                       if ( surfaceAnimations[i].frames )
+                       {
+                               for ( j = 0; j < surfaceAnimations[i].numFrames; j++ )
+                               {
+                                       free( surfaceAnimations[i].frames[j].triangles );
+                               }
+                               free( surfaceAnimations[i].frames );
+                               surfaceAnimations[i].frames = 0;
+                       }
+               }
+       }
+       else if ( type == TYPE_PLAYER )
+       {
+               qboolean tagTorso = qfalse;
+               qboolean tagHead = qfalse;
+               qboolean tagWeapon = qfalse;
+
+               //
+               // verify that all necessary tags exist
+               //
+               numSurfaces = ASE_GetNumSurfaces();
+               for ( i = 0; i < numSurfaces; i++ )
+               {
+                       if ( !strcmp( ASE_GetSurfaceName( i ), "tag_head" ) )
+                       {
+                               tagHead = qtrue;
+                       }
+                       if ( !strcmp( ASE_GetSurfaceName( i ), "tag_torso" ) )
+                       {
+                               tagTorso = qtrue;
+                       }
+                       if ( !strcmp( ASE_GetSurfaceName( i ), "tag_weapon" ) )
+                       {
+                               tagWeapon = qtrue;
+                       }
+               }
+
+               if ( !tagWeapon )
+               {
+                       Error( "Missing tag_weapon!" );
+               }
+               if ( !tagTorso )
+               {
+                       Error( "Missing tag_torso!" );
+               }
+               if ( !tagWeapon )
+               {
+                       Error( "Missing tag_weapon!" );
+               }
+
+               // get all upper body surfaces
+               numSurfaces = GetSurfaceAnimations( surfaceAnimations, "u_", -1, -1, g_data.maxUpperFrames );
+               numFrames = SurfaceOrderToFrameOrder( surfaceAnimations, objectAnimationFrames, numSurfaces );
+               strcpy( outfilename, filename );
+               if ( strrchr( outfilename, '/' ) )
+                       *( strrchr( outfilename, '/' ) + 1 ) = 0;
+
+               if ( g_data.currentLod == 0 )
+               {
+                       strcat( outfilename, "upper.md3" );
+               }
+               else
+               {
+                       char temp[128];
+
+                       sprintf( temp, "upper_%d.md3", g_data.currentLod );
+                       strcat( outfilename, temp );
+               }
+               
+               BuildAnimationFromOAFs( outfilename, objectAnimationFrames, numFrames, type );
+
+               // free memory
+               for ( i = 0; i < numSurfaces; i++ )
+               {
+                       if ( surfaceAnimations[i].frames )
+                       {
+                               for ( j = 0; j < surfaceAnimations[i].numFrames; j++ )
+                               {
+                                       free( surfaceAnimations[i].frames[j].triangles );
+                               }
+                               free( surfaceAnimations[i].frames );
+                               surfaceAnimations[i].frames = 0;
+                       }
+               }
+
+               // get lower body surfaces
+               numSurfaces = GetSurfaceAnimations( surfaceAnimations, "l_", g_data.lowerSkipFrameStart, g_data.lowerSkipFrameEnd, -1 );
+               numFrames = SurfaceOrderToFrameOrder( surfaceAnimations, objectAnimationFrames, numSurfaces );
+               strcpy( outfilename, filename );
+               if ( strrchr( outfilename, '/' ) )
+                       *( strrchr( outfilename, '/' ) + 1 ) = 0;
+
+               if ( g_data.currentLod == 0 )
+               {
+                       strcat( outfilename, "lower.md3" );
+               }
+               else
+               {
+                       char temp[128];
+
+                       sprintf( temp, "lower_%d.md3", g_data.currentLod );
+                       strcat( outfilename, temp );
+               }
+               BuildAnimationFromOAFs( outfilename, objectAnimationFrames, numFrames, type );
+
+               // free memory
+               for ( i = 0; i < numSurfaces; i++ )
+               {
+                       if ( surfaceAnimations[i].frames )
+                       {
+                               for ( j = 0; j < surfaceAnimations[i].numFrames; j++ )
+                               {
+                                       free( surfaceAnimations[i].frames[j].triangles );
+                               }
+                               free( surfaceAnimations[i].frames );
+                               surfaceAnimations[i].frames = 0;
+                       }
+               }
+
+               // get head surfaces
+               numSurfaces = GetSurfaceAnimations( surfaceAnimations, "h_", -1, -1, g_data.maxHeadFrames );
+               numFrames = SurfaceOrderToFrameOrder( surfaceAnimations, objectAnimationFrames, numSurfaces );
+               strcpy( outfilename, filename );
+               if ( strrchr( outfilename, '/' ) )
+                       *( strrchr( outfilename, '/' ) + 1 ) = 0;
+
+               if ( g_data.currentLod == 0 )
+               {
+                       strcat( outfilename, "head.md3" );
+               }
+               else
+               {
+                       char temp[128];
+
+                       sprintf( temp, "head_%d.md3", g_data.currentLod );
+                       strcat( outfilename, temp );
+               }
+               BuildAnimationFromOAFs( outfilename, objectAnimationFrames, numFrames, type );
+
+               // free memory
+               for ( i = 0; i < numSurfaces; i++ )
+               {
+                       if ( surfaceAnimations[i].frames )
+                       {
+                               for ( j = 0; j < surfaceAnimations[i].numFrames; j++ )
+                               {
+                                       free( surfaceAnimations[i].frames[j].triangles );
+                               }
+                               free( surfaceAnimations[i].frames );
+                               surfaceAnimations[i].frames = 0;
+                       }
+               }
+       }
+       else if ( type == TYPE_WEAPON )
+       {
+               // get the weapon surfaces
+               numSurfaces = GetSurfaceAnimations( surfaceAnimations, "w_", -1, -1, -1 );
+               numFrames = SurfaceOrderToFrameOrder( surfaceAnimations, objectAnimationFrames, numSurfaces );
+
+               strcpy( outfilename, filename );
+               if ( strrchr( outfilename, '.' ) )
+                       *( strrchr( outfilename, '.' ) + 1 ) = 0;
+               strcat( outfilename, "md3" );
+               BuildAnimationFromOAFs( outfilename, objectAnimationFrames, numFrames, type );
+
+               // free memory
+               for ( i = 0; i < numSurfaces; i++ )
+               {
+                       if ( surfaceAnimations[i].frames )
+                       {
+                               for ( j = 0; j < surfaceAnimations[i].numFrames; j++ )
+                               {
+                                       free( surfaceAnimations[i].frames[j].triangles );
+                               }
+                               free( surfaceAnimations[i].frames );
+                               surfaceAnimations[i].frames = 0;
+                       }
+               }
+
+               // get the flash surfaces
+               numSurfaces = GetSurfaceAnimations( surfaceAnimations, "f_", -1, -1, -1 );
+               numFrames = SurfaceOrderToFrameOrder( surfaceAnimations, objectAnimationFrames, numSurfaces );
+
+               strcpy( outfilename, filename );
+               if ( strrchr( outfilename, '.' ) )
+                       *strrchr( outfilename, '.' ) = 0;
+               strcat( outfilename, "_flash.md3" );
+               BuildAnimationFromOAFs( outfilename, objectAnimationFrames, numFrames, TYPE_ITEM );
+
+               // free memory
+               for ( i = 0; i < numSurfaces; i++ )
+               {
+                       if ( surfaceAnimations[i].frames )
+                       {
+                               for ( j = 0; j < surfaceAnimations[i].numFrames; j++ )
+                               {
+                                       free( surfaceAnimations[i].frames[j].triangles );
+                               }
+                               free( surfaceAnimations[i].frames );
+                               surfaceAnimations[i].frames = 0;
+                       }
+               }
+       }
+       else if ( type == TYPE_HAND )
+       {
+               // get the hand tags
+               numSurfaces = GetSurfaceAnimations( surfaceAnimations, "tag_", -1, -1, -1 );
+               numFrames = SurfaceOrderToFrameOrder( surfaceAnimations, objectAnimationFrames, numSurfaces );
+
+               strcpy( outfilename, filename );
+               if ( strrchr( outfilename, '.' ) )
+                       *strrchr( outfilename, '.' ) = 0;
+               strcat( outfilename, "_hand.md3" );
+               BuildAnimationFromOAFs( outfilename, objectAnimationFrames, numFrames, TYPE_HAND );
+
+               // free memory
+               for ( i = 0; i < numSurfaces; i++ )
+               {
+                       if ( surfaceAnimations[i].frames )
+                       {
+                               for ( j = 0; j < surfaceAnimations[i].numFrames; j++ )
+                               {
+                                       free( surfaceAnimations[i].frames[j].triangles );
+                               }
+                               free( surfaceAnimations[i].frames );
+                               surfaceAnimations[i].frames = 0;
+                       }
+               }
+       }
+       else
+       {
+               Error( "Unknown type passed to ConvertASE()" );
+       }
+
+       g_data.currentLod = 0;
+       g_data.lodBias = 0;
+       g_data.maxHeadFrames = 0;
+       g_data.maxUpperFrames = 0;
+       g_data.lowerSkipFrameStart = 0;
+       g_data.lowerSkipFrameEnd = 0;
+       VectorCopy( vec3_origin, g_data.aseAdjust );
+
+       // unload ASE from memory
+       ASE_Free();
+}