/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA See file, 'COPYING', for details. */ // modelgen.c: generates a .mdl file from a base triangle file (.tri), a // texture containing front and back skins (.lbm), and a series of frame // triangle files (.tri). Result is stored in // /raid/quake/models/.mdl. //#include #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #ifdef HAVE_UNISTD_H # include #endif #ifdef _WIN32 # include #endif #include "QF/dstring.h" #include "QF/modelgen.h" #include "QF/qendian.h" #include "QF/quakefs.h" #include "QF/quakeio.h" #include "QF/script.h" #include "QF/sys.h" #include "lbmlib.h" #include "trilib.h" #include "compat.h" #define MAXVERTS 2048 #define MAXFRAMES 256 #define MAXSKINS 100 #define MAXTRIANGLES 2048 typedef struct { aliasframetype_t type; // single frame or group of frames void *pdata; // either a daliasframe_t or group info float interval; // only used for frames in groups int numgroupframes; // only used by group headers char name[16]; } aliaspackage_t; typedef struct { aliasskintype_t type; // single skin or group of skiins void *pdata; // either a daliasskinframe_t or group info float interval; // only used for skins in groups int numgroupskins; // only used by group headers } aliasskinpackage_t; typedef struct { int numnormals; float normals[20][3]; } vertexnormals; typedef struct { vec3_t v; int lightnormalindex; } trivert_t; //============================================================================ trivert_t verts[MAXFRAMES][MAXVERTS]; mdl_t model; char file1[1024]; char skinname[1024]; char qbasename[1024]; float scale, scale_up = 1.0; vec3_t mins, maxs; vec3_t framesmins, framesmaxs; vec3_t adjust; aliaspackage_t frames[MAXFRAMES]; aliasskinpackage_t skins[MAXSKINS]; // // base frame info // vec3_t baseverts[MAXVERTS]; stvert_t stverts[MAXVERTS]; dtriangle_t triangles[MAXTRIANGLES]; int degenerate[MAXTRIANGLES]; char cdpartial[256]; char cddir[256]; int framecount, skincount; qboolean cdset; int degeneratetris; int firstframe = 1; float totsize, averagesize; vertexnormals vnorms[MAXVERTS]; #define NUMVERTEXNORMALS 162 float avertexnormals[NUMVERTEXNORMALS][3] = { #include "anorms.h" //FIXME should this be moved to QF? }; trivertx_t tarray[MAXVERTS]; char outname[1024]; script_t scr; /* qdir will hold the path up to the quake directory, including the slash f:\quake\ /raid/quake/ gamedir will hold qdir + the game directory (id1, id2, etc) */ char qdir[1024]; char gamedir[1024]; static void SetQdirFromPath (char *path) { char temp[1024]; char *c; if (!(path[0] == '/' || path[0] == '\\' || path[1] == ':')) { // path is partial if (getcwd (temp, sizeof (temp)) == NULL) Sys_Error ("Can't get CWD"); strcat (temp, path); path = temp; } // search for "quake" in path for (c=path ; *c ; c++) if (!strncasecmp (c, "quake", 5)) { strncpy (qdir, path, c+6-path); printf ("qdir: %s\n", qdir); c += 6; while (*c) { if (*c == '/' || *c == '\\') { strncpy (gamedir, path, c+1-path); printf ("gamedir: %s\n", gamedir); return; } c++; } Sys_Error ("No gamedir in %s", path); return; } Sys_Error ("SetQdirFromPath: no 'quake' in %s", path); } static const char * ExpandPath (const char *path) { static char full[1024]; if (!qdir) Sys_Error ("ExpandPath called without qdir set"); if (path[0] == '/' || path[0] == '\\' || path[1] == ':') return path; sprintf (full, "%s%s", qdir, path); return full; } static void ClearModel (void) { memset (&model, 0, sizeof (model)); model.synctype = ST_RAND; // default framecount = skincount = 0; scale = 0; scale_up = 1.0; VectorZero (adjust); VectorZero (mins); VectorZero (maxs); VectorZero (framesmins); VectorZero (framesmaxs); degeneratetris = 0; cdset = false; firstframe = 1; totsize = 0.0; } static void WriteFrame (QFile *modelouthandle, int framenum) { int j, k; float v; daliasframe_t aframe; trivert_t *pframe; pframe = verts[framenum]; strcpy (aframe.name, frames[framenum].name); for (j = 0; j < 3; j++) { aframe.bboxmin.v[j] = 255; aframe.bboxmax.v[j] = 0; } for (j = 0; j < model.numverts; j++) { // all of these are byte values, so no need to deal with endianness tarray[j].lightnormalindex = pframe[j].lightnormalindex; if (tarray[j].lightnormalindex > NUMVERTEXNORMALS) Sys_Error ("invalid lightnormalindex %d\n", tarray[j].lightnormalindex); for (k = 0; k < 3; k++) { // scale to byte values & min/max check v = (pframe[j].v[k] - model.scale_origin[k]) / model.scale[k]; tarray[j].v[k] = v; if (tarray[j].v[k] < aframe.bboxmin.v[k]) aframe.bboxmin.v[k] = tarray[j].v[k]; if (tarray[j].v[k] > aframe.bboxmax.v[k]) aframe.bboxmax.v[k] = tarray[j].v[k]; } } Qwrite (modelouthandle, &aframe, sizeof (aframe)); Qwrite (modelouthandle, &tarray[0], model.numverts * sizeof(tarray[0])); } static void WriteGroupBBox (QFile *modelouthandle, int numframes, int curframe) { int i, j, k; daliasgroup_t dagroup; trivert_t *pframe; dagroup.numframes = LittleLong (numframes); for (i = 0; i < 3; i++) { dagroup.bboxmin.v[i] = 255; dagroup.bboxmax.v[i] = 0; } for (i = 0; i < numframes; i++) { pframe = (trivert_t *) frames[curframe].pdata; for (j = 0; j < model.numverts; j++) { for (k = 0; k < 3; k++) { // scale to byte values & min/max check tarray[j].v[k] = (pframe[j].v[k] - model.scale_origin[k]) / model.scale[k]; if (tarray[j].v[k] < dagroup.bboxmin.v[k]) dagroup.bboxmin.v[k] = tarray[j].v[k]; if (tarray[j].v[k] > dagroup.bboxmax.v[k]) dagroup.bboxmax.v[k] = tarray[j].v[k]; } } curframe++; } Qwrite (modelouthandle, &dagroup, sizeof(dagroup)); } static void WriteModelFile (QFile *modelouthandle) { int i, curframe, curskin; float dist[3]; mdl_t modeltemp; // Calculate the bounding box for this model for (i = 0; i < 3; i++) { printf ("framesmins[%d]: %f, framesmaxs[%d]: %f\n", i, framesmins[i], i, framesmaxs[i]); if (fabs (framesmins[i]) > fabs (framesmaxs[i])) dist[i] = framesmins[i]; else dist[i] = framesmaxs[i]; model.scale[i] = (framesmaxs[i] - framesmins[i]) / 255.9; model.scale_origin[i] = framesmins[i]; } model.boundingradius = sqrt (dist[0] * dist[0] + dist[1] * dist[1] + dist[2] * dist[2]); // write out the model header modeltemp.ident = LittleLong (IDHEADER_MDL); modeltemp.version = LittleLong (ALIAS_VERSION_MDL); modeltemp.boundingradius = LittleFloat (model.boundingradius); for (i = 0; i < 3; i++) { modeltemp.scale[i] = LittleFloat (model.scale[i]); modeltemp.scale_origin[i] = LittleFloat (model.scale_origin[i]); modeltemp.eyeposition[i] = LittleFloat (model.eyeposition[i] + adjust[i]); } modeltemp.flags = LittleLong (model.flags); modeltemp.numskins = LittleLong (model.numskins); modeltemp.skinwidth = LittleLong (model.skinwidth); modeltemp.skinheight = LittleLong (model.skinheight); modeltemp.numverts = LittleLong (model.numverts); modeltemp.numtris = LittleLong (model.numtris - degeneratetris); modeltemp.numframes = LittleLong (model.numframes); modeltemp.synctype = LittleFloat (model.synctype); averagesize = totsize / model.numtris; modeltemp.size = LittleFloat (averagesize); Qwrite (modelouthandle, &modeltemp, sizeof (model)); // write out the skins curskin = 0; for (i = 0; i < model.numskins; i++) { Qwrite (modelouthandle, &skins[curskin].type, sizeof (skins[curskin].type)); Qwrite (modelouthandle, skins[curskin].pdata, model.skinwidth * model.skinheight); curskin++; } // write out the base model (the s & t coordinates for the vertices) for (i = 0; i < model.numverts; i++) { if (stverts[i].onseam == 3) { stverts[i].onseam = LittleLong (ALIAS_ONSEAM); } else { stverts[i].onseam = LittleLong (0); } stverts[i].s = LittleLong (stverts[i].s); stverts[i].t = LittleLong (stverts[i].t); } Qwrite (modelouthandle, stverts, model.numverts * sizeof(stverts[0])); // write out the triangles for (i = 0; i < model.numtris; i++) { int j; dtriangle_t tri; if (!degenerate[i]) { tri.facesfront = LittleLong (triangles[i].facesfront); for (j = 0; j < 3; j++) { tri.vertindex[j] = LittleLong (triangles[i].vertindex[j]); } Qwrite (modelouthandle, &tri, sizeof(tri)); } } // write out the frames curframe = 0; for (i = 0; i < model.numframes; i++) { Qwrite (modelouthandle, &frames[curframe].type, sizeof (frames[curframe].type)); if (frames[curframe].type == ALIAS_SINGLE) { // single (non-grouped) frame WriteFrame (modelouthandle, curframe); curframe++; } else { int j, numframes, groupframe; float totinterval; groupframe = curframe; curframe++; numframes = frames[groupframe].numgroupframes; // set and write the group header WriteGroupBBox (modelouthandle, numframes, curframe); // write the interval array totinterval = 0.0; for (j = 0; j < numframes; j++) { daliasinterval_t temp; totinterval += frames[groupframe+1+j].interval; temp.interval = LittleFloat (totinterval); Qwrite (modelouthandle, &temp, sizeof(temp)); } for (j = 0; j < numframes; j++) { WriteFrame (modelouthandle, curframe); curframe++; } } } } static void WriteModel (void) { QFile *modelouthandle; // write the model output file if (!framecount) { printf ("no frames grabbed, no file generated\n"); return; } if (!skincount) Sys_Error ("frames with no skins\n"); QFS_StripExtension (outname, outname); strcat (outname, ".mdl"); printf ("---------------------\n"); printf ("writing %s:\n", outname); modelouthandle = Qopen (outname, "wb"); WriteModelFile (modelouthandle); printf ("%4d frame(s)\n", model.numframes); printf ("%4d ungrouped frame(s), including group headers\n", framecount); printf ("%4d skin(s)\n", model.numskins); printf ("%4d degenerate triangles(s) removed\n", degeneratetris); printf ("%4d triangles emitted\n", model.numtris - degeneratetris); printf ("pixels per triangle %f\n", averagesize); printf ("file size: %d\n", (int) Qtell (modelouthandle)); printf ("---------------------\n"); Qclose (modelouthandle); ClearModel (); } /* ============ SetSkinValues Called for the base frame ============ */ static void SetSkinValues (void) { float basex, basey, v; int width, height, iwidth, iheight, skinwidth, i; for (i = 0; i < 3; i++) { mins[i] = 9999999; maxs[i] = -9999999; } for (i = 0; i < model.numverts; i++) { int j; stverts[i].onseam = 0; for (j = 0; j < 3; j++) { v = baseverts[i][j]; if (v < mins[j]) mins[j] = v; if (v > maxs[j]) maxs[j] = v; } } 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]; printf ("width: %i height: %i\n", width, height); scale = 8; if (width*scale >= 150) scale = 150.0 / width; if (height*scale >= 190) scale = 190.0 / height; iwidth = ceil(width*scale) + 4; iheight = ceil(height*scale) + 4; printf ("scale: %f\n", scale); printf ("iwidth: %i iheight: %i\n", iwidth, iheight); // determine which side of each triangle to map the texture to for (i = 0; i < model.numtris; i++) { int j; vec3_t vtemp1, vtemp2, normal; VectorSubtract (baseverts[triangles[i].vertindex[0]], baseverts[triangles[i].vertindex[1]], vtemp1); VectorSubtract (baseverts[triangles[i].vertindex[2]], baseverts[triangles[i].vertindex[1]], vtemp2); CrossProduct (vtemp1, vtemp2, normal); if (normal[1] > 0) { basex = iwidth + 2; triangles[i].facesfront = 0; } else { basex = 2; triangles[i].facesfront = 1; } basey = 2; for (j = 0; j < 3; j++) { float *pbasevert; stvert_t *pstvert; pbasevert = baseverts[triangles[i].vertindex[j]]; pstvert = &stverts[triangles[i].vertindex[j]]; if (triangles[i].facesfront) { pstvert->onseam |= 1; } else { pstvert->onseam |= 2; } if ((triangles[i].facesfront) || ((pstvert->onseam & 1) == 0)) { // we want the front s value for seam vertices pstvert->s = ((pbasevert[0] - mins[0]) * scale + basex) + 0.5; pstvert->t = ((maxs[2] - pbasevert[2]) * scale + basey) + 0.5; } } } // make the width a multiple of 4; some hardware requires this, and it ensures // dword alignment for each scan skinwidth = iwidth*2; model.skinwidth = (skinwidth + 3) & ~3; model.skinheight = iheight; printf ("skin width: %i (unpadded width %i) skin height: %i\n", model.skinwidth, skinwidth, model.skinheight); } static void Cmd_Base (void) { triangle_t *ptri; int time1, i, j, k; Script_GetToken (&scr, false); strcpy (qbasename, scr.token->str); sprintf (file1, "%s/%s.tri", cdpartial, scr.token->str); ExpandPath/*AndArchive*/ (file1); sprintf (file1, "%s/%s.tri", cddir, scr.token->str); time1 = Sys_FileTime (file1); if (time1 == -1) Sys_Error ("%s doesn't exist", file1); // load the base triangles LoadTriangleList (file1, &ptri, &model.numtris); printf("NUMBER OF TRIANGLES (including degenerate triangles): %d\n", model.numtris); // run through all the base triangles, storing each unique vertex in the base // vertex list and setting the indirect triangles to point to the base vertices for (i = 0; i < model.numtris; i++) { if (_VectorCompare (ptri[i].verts[0], ptri[i].verts[1]) || _VectorCompare (ptri[i].verts[1], ptri[i].verts[2]) || _VectorCompare (ptri[i].verts[2], ptri[i].verts[0])) { degeneratetris++; degenerate[i] = 1; } else { degenerate[i] = 0; } for (j = 0; j < 3; j++) { for (k=0 ; kstr); sprintf (file1, "%s/%s.lbm", cdpartial, scr.token->str); ExpandPath/*AndArchive*/ (file1); sprintf (file1, "%s/%s.lbm", cddir, scr.token->str); time1 = Sys_FileTime (file1); if (time1 == -1) Sys_Error ("%s not found", file1); if (Script_TokenAvailable (&scr, false)) { Script_GetToken (&scr, false); skins[skincount].interval = atof (scr.token->str); if (skins[skincount].interval <= 0.0) Sys_Error ("Non-positive interval"); } else { skins[skincount].interval = 0.1; } // load in the skin .lbm file LoadLBM (file1, &pskinbitmap, &ppal); // now copy the part of the texture we care about, since LBMs are always // loaded as 320x200 bitmaps skins[skincount].pdata = malloc (model.skinwidth * model.skinheight); if (!skins[skincount].pdata) Sys_Error ("couldn't get memory for skin texture"); ptemp1 = skins[skincount].pdata; ptemp2 = pskinbitmap; for (i = 0; i < model.skinheight; i++) { memcpy (ptemp1, ptemp2, model.skinwidth); ptemp1 += model.skinwidth; ptemp2 += 320; } skincount++; if (skincount > MAXSKINS) Sys_Error ("Too many skins; increase MAXSKINS"); } static void GrabFrame (char *frame, int isgroup) { int numtris, time1, i, j; triangle_t *ptri; trivert_t *ptrivert; sprintf (file1, "%s/%s.tri", cdpartial, frame); ExpandPath/*AndArchive*/ (file1); sprintf (file1, "%s/%s.tri", cddir, frame); time1 = Sys_FileTime (file1); if (time1 == -1) Sys_Error ("%s does not exist", file1); printf ("grabbing %s\n", file1); frames[framecount].interval = 0.1; strcpy (frames[framecount].name, frame); // load the frame LoadTriangleList (file1, &ptri, &numtris); if (numtris != model.numtris) Sys_Error ("number of triangles doesn't match\n"); // set the intervals if (isgroup && Script_TokenAvailable (&scr, false)) { Script_GetToken (&scr, false); frames[framecount].interval = atof (scr.token->str); if (frames[framecount].interval <= 0.0) Sys_Error ("Non-positive interval %s %f", scr.token->str, frames[framecount].interval); } else { frames[framecount].interval = 0.1; } // allocate storage for the frame's vertices ptrivert = verts[framecount]; frames[framecount].pdata = ptrivert; frames[framecount].type = ALIAS_SINGLE; for (i = 0; i < model.numverts ; i++) { vnorms[i].numnormals = 0; } // store the frame's vertices in the same order as the base. This assumes the // triangles and vertices in this frame are in exactly the same order as in the // base for (i = 0; i < numtris; i++) { vec3_t vtemp1, vtemp2, normal; float ftemp; if (degenerate[i]) continue; if (firstframe) { VectorSubtract (ptri[i].verts[0], ptri[i].verts[1], vtemp1); VectorSubtract (ptri[i].verts[2], ptri[i].verts[1], vtemp2); VectorScale (vtemp1, scale_up, vtemp1); VectorScale (vtemp2, scale_up, vtemp2); CrossProduct (vtemp1, vtemp2, normal); totsize += sqrt (normal[0] * normal[0] + normal[1] * normal[1] + normal[2] * normal[2]) / 2.0; } VectorSubtract (ptri[i].verts[0], ptri[i].verts[1], vtemp1); VectorSubtract (ptri[i].verts[2], ptri[i].verts[1], vtemp2); CrossProduct (vtemp1, vtemp2, normal); VectorNormalize (normal); // rotate the normal so the model faces down the positive x axis ftemp = normal[0]; normal[0] = -normal[1]; normal[1] = ftemp; for (j = 0; j < 3; j++) { int k; int vertindex; vertindex = triangles[i].vertindex[j]; // rotate the vertices so the model faces down the positive x axis // also adjust the vertices to the desired origin ptrivert[vertindex].v[0] = ((-ptri[i].verts[j][1]) * scale_up) + adjust[0]; ptrivert[vertindex].v[1] = (ptri[i].verts[j][0] * scale_up) + adjust[1]; ptrivert[vertindex].v[2] = (ptri[i].verts[j][2] * scale_up) + adjust[2]; for (k = 0; k < 3; k++) { if (ptrivert[vertindex].v[k] < framesmins[k]) framesmins[k] = ptrivert[vertindex].v[k]; if (ptrivert[vertindex].v[k] > framesmaxs[k]) framesmaxs[k] = ptrivert[vertindex].v[k]; } VectorCopy (normal, vnorms[vertindex]. normals[vnorms[vertindex].numnormals]); vnorms[vertindex].numnormals++; } } // calculate the vertex normals, match them to the template list, and store the // index of the best match for (i = 0; i < model.numverts; i++) { vec3_t v; float maxdot; int maxdotindex, j; if (vnorms[i].numnormals > 0) { for (j = 0; j < 3; j++) { int k; v[j] = 0; for (k = 0; k < vnorms[i].numnormals; k++) { v[j] += vnorms[i].normals[k][j]; } v[j] /= vnorms[i].numnormals; } } else { Sys_Error ("Vertex with no non-degenerate triangles attached"); } VectorNormalize (v); maxdot = -999999.0; maxdotindex = -1; for (j = 0; j < NUMVERTEXNORMALS; j++) { float dot; dot = DotProduct (v, avertexnormals[j]); if (dot > maxdot) { maxdot = dot; maxdotindex = j; } } ptrivert[i].lightnormalindex = maxdotindex; } framecount++; if (framecount >= MAXFRAMES) Sys_Error ("Too many frames; increase MAXFRAMES"); free (ptri); firstframe = 0; } static void Cmd_Frame (int isgroup) { while (Script_TokenAvailable (&scr, false)) { Script_GetToken (&scr, false); GrabFrame (scr.token->str, isgroup); if (!isgroup) model.numframes++; } } static void Cmd_SkinGroupStart (void) { int groupskin; groupskin = skincount++; if (skincount >= MAXFRAMES) Sys_Error ("Too many skins; increase MAXSKINS"); skins[groupskin].type = ALIAS_SKIN_GROUP; skins[groupskin].numgroupskins = 0; while (1) { if (!Script_GetToken (&scr, true)) Sys_Error ("End of file during group"); if (!strcmp (scr.token->str, "$skin")) { Cmd_Skin (); skins[groupskin].numgroupskins++; } else if (!strcmp (scr.token->str, "$skingroupend")) { break; } else { Sys_Error ("$skin or $skingroupend expected\n"); } } if (skins[groupskin].numgroupskins == 0) Sys_Error ("Empty group\n"); } static void Cmd_FrameGroupStart (void) { int groupframe; groupframe = framecount++; if (framecount >= MAXFRAMES) Sys_Error ("Too many frames; increase MAXFRAMES"); frames[groupframe].type = ALIAS_GROUP; frames[groupframe].numgroupframes = 0; while (1) { if (!Script_GetToken (&scr, true)) Sys_Error ("End of file during group"); if (!strcmp (scr.token->str, "$frame")) { Cmd_Frame (1); } else if (!strcmp (scr.token->str, "$framegroupend")) { break; } else { Sys_Error ("$frame or $framegroupend expected\n"); } } frames[groupframe].numgroupframes += framecount - groupframe - 1; if (frames[groupframe].numgroupframes == 0) Sys_Error ("Empty group\n"); } static void Cmd_Origin (void) { // rotate points into frame of reference so model points down the positive x // axis Script_GetToken (&scr, false); adjust[1] = -atof (scr.token->str); Script_GetToken (&scr, false); adjust[0] = atof (scr.token->str); Script_GetToken (&scr, false); adjust[2] = -atof (scr.token->str); } static void Cmd_Eyeposition (void) { // rotate points into frame of reference so model points down the positive x // axis Script_GetToken (&scr, false); model.eyeposition[1] = atof (scr.token->str); Script_GetToken (&scr, false); model.eyeposition[0] = -atof (scr.token->str); Script_GetToken (&scr, false); model.eyeposition[2] = atof (scr.token->str); } static void Cmd_ScaleUp (void) { Script_GetToken (&scr, false); scale_up = atof (scr.token->str); } static void Cmd_Flags (void) { Script_GetToken (&scr, false); model.flags = atoi (scr.token->str); } static void Cmd_Modelname (void) { WriteModel (); Script_GetToken (&scr, false); strcpy (outname, scr.token->str); } static void ParseScript (void) { while (1) { do { // look for a line starting with a $ command if (!Script_GetToken (&scr, true)) return; if (scr.token->str[0] == '$') break; while (Script_TokenAvailable (&scr, false)) Script_GetToken (&scr, false); } while (1); if (!strcmp (scr.token->str, "$modelname")) { Cmd_Modelname (); } else if (!strcmp (scr.token->str, "$base")) { Cmd_Base (); } else if (!strcmp (scr.token->str, "$cd")) { if (cdset) Sys_Error ("Two $cd in one model"); cdset = true; Script_GetToken (&scr, false); strcpy (cdpartial, scr.token->str); strcpy (cddir, ExpandPath(scr.token->str)); } else if (!strcmp (scr.token->str, "$sync")) { model.synctype = ST_SYNC; } else if (!strcmp (scr.token->str, "$origin")) { Cmd_Origin (); } else if (!strcmp (scr.token->str, "$eyeposition")) { Cmd_Eyeposition (); } else if (!strcmp (scr.token->str, "$scale")) { Cmd_ScaleUp (); } else if (!strcmp (scr.token->str, "$flags")) { Cmd_Flags (); } else if (!strcmp (scr.token->str, "$frame")) { Cmd_Frame (0); } else if (!strcmp (scr.token->str, "$skin")) { Cmd_Skin (); model.numskins++; } else if (!strcmp (scr.token->str, "$framegroupstart")) { Cmd_FrameGroupStart (); model.numframes++; } else if (!strcmp (scr.token->str, "$skingroupstart")) { Cmd_SkinGroupStart (); model.numskins++; } else { Sys_Error ("bad command %s\n", scr.token->str); } } } int main (int argc, char **argv) { int i, bytes; char path[1024]; QFile *file; char *buf; if (argc != 2) Sys_Error ("usage: modelgen file.qc"); i = 1; // load the script strcpy (path, argv[i]); QFS_DefaultExtension (path, ".qc"); SetQdirFromPath (path); file = Qopen (path, "rt"); if (!file) Sys_Error ("couldn't open %s. %s", path, strerror(errno)); bytes = Qfilesize (file); buf = malloc (bytes + 1); bytes = Qread (file, buf, bytes); buf[bytes] = 0; Qclose (file); Script_Start (&scr, path, buf); // parse it memset (&model, 0, sizeof (model)); for (i = 0; i < 3; i++) { framesmins[i] = 9999999; framesmaxs[i] = -9999999; } ClearModel (); strcpy (outname, argv[1]); ParseScript (); WriteModel (); return 0; }