#define	LIBQTOOLS_CORE
#define	LIBQBUILD_CORE
#include "../include/libqtools.h"
#include "../include/libqbuild.h"

/*
 * 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/<scriptname>.mdl.
 */

struct mdlheader 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;

/*
 * base frame info
 */

char cdpartial[256];
char cddir[256];

bool cdset;
int degeneratetris;
int firstframe = 1;
float totsize, averagesize;

#define NUMVERTEXNORMALS	162

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

char outname[1024];

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

void ClearModel(void)
{
  __bzero(&model, sizeof(model));
  model.synctype = ST_RAND;					/* default */

  bspMem->numframes = bspMem->numskins = 0;

  scale = 0;
  scale_up = 1.0;

  VectorCopy(vec3_origin, adjust);
  VectorCopy(vec3_origin, mins);
  VectorCopy(vec3_origin, maxs);
  VectorCopy(vec3_origin, framesmins);
  VectorCopy(vec3_origin, framesmaxs);

  degeneratetris = 0;
  cdset = false;
  firstframe = 1;
  totsize = 0.0;
}

/*
 * ============
 * WriteFrame
 * ============
 */
void WriteFrame(HANDLE modelouthandle, int framenum)
{
  int j, k;
  struct trivert *pframe;
  struct daliasframe aframe;
  float v;

  pframe = bspMem->verts[framenum];

  strcpy(aframe.name, bspMem->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 */
    bspMem->tarray[j].lightnormalindex = pframe[j].lightnormalindex;

    if (bspMem->tarray[j].lightnormalindex > NUMVERTEXNORMALS)
      Error("invalid lightnormalindex %d\n", bspMem->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];

      bspMem->tarray[j].v[k] = v;

      if (bspMem->tarray[j].v[k] < aframe.bboxmin.v[k]) {
	aframe.bboxmin.v[k] = bspMem->tarray[j].v[k];
      }
      if (bspMem->tarray[j].v[k] > aframe.bboxmax.v[k]) {
	aframe.bboxmax.v[k] = bspMem->tarray[j].v[k];
      }

    }
  }

  __write(modelouthandle, &aframe, sizeof(aframe));
  __write(modelouthandle, &bspMem->tarray[0], model.numverts * sizeof(bspMem->tarray[0]));
}

/*
 * ============
 * WriteGroupBBox
 * ============
 */
void WriteGroupBBox(HANDLE modelouthandle, int numframes, int curframe)
{
  int i, j, k;
  struct daliasgroup dagroup;
  struct trivert *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 = (struct trivert *)bspMem->frames[curframe].pdata;

    for (j = 0; j < model.numverts; j++) {
      for (k = 0; k < 3; k++) {
	/* scale to byte values & min/max check */
	bspMem->tarray[j].v[k] = (pframe[j].v[k] - model.scale_origin[k]) /
	  model.scale[k];
	if (bspMem->tarray[j].v[k] < dagroup.bboxmin.v[k])
	  dagroup.bboxmin.v[k] = bspMem->tarray[j].v[k];
	if (bspMem->tarray[j].v[k] > dagroup.bboxmax.v[k])
	  dagroup.bboxmax.v[k] = bspMem->tarray[j].v[k];
      }
    }

    curframe++;
  }

  __write(modelouthandle, &dagroup, sizeof(dagroup));
}

/*
 * ============
 * WriteModel
 * ============
 */
bool WriteModelFile(HANDLE modelouthandle)
{
  int i, curframe, curskin;
  float dist[3];
  struct mdlheader modeltemp;

  /* Calculate the bounding box for this model */
  for (i = 0; i < 3; i++) {
    mprintf("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 = VectorLength(dist);

  /*
   * write out the model header
   */
  modeltemp.ident = LittleLong(MAGIC_MDL_Q1);
  modeltemp.version = LittleLong(MDL_VERSION);
  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);

  __write(modelouthandle, &modeltemp, sizeof(model));

  /*
   * write out the skins
   */
  curskin = 0;

  for (i = 0; i < model.numskins; i++) {
    __write(modelouthandle, &bspMem->skins[curskin].type, sizeof(bspMem->skins[curskin].type));
    __write(modelouthandle, bspMem->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 (bspMem->stverts[i].onseam == 3) {
      bspMem->stverts[i].onseam = LittleLong(MDL_ONSEAM);
    }
    else {
      bspMem->stverts[i].onseam = LittleLong(0);
    }

    bspMem->stverts[i].s = LittleLong(bspMem->stverts[i].s);
    bspMem->stverts[i].t = LittleLong(bspMem->stverts[i].t);
  }

  __write(modelouthandle, bspMem->stverts, model.numverts * sizeof(bspMem->stverts[0]));

  /*
   * write out the triangles
   */
  for (i = 0; i < model.numtris; i++) {
    int j;
    struct dtriangle tri;

    if (!bspMem->degenerate[i]) {
      tri.facesfront = LittleLong(bspMem->triangles[i].facesfront);

      for (j = 0; j < 3; j++) {
	tri.vertindex[j] = LittleLong(bspMem->triangles[i].vertindex[j]);
      }
      __write(modelouthandle, &tri, sizeof(tri));
    }
  }

  /*
   * write out the frames
   */
  curframe = 0;

  for (i = 0; i < model.numframes; i++) {
    __write(modelouthandle, &bspMem->frames[curframe].type, sizeof(bspMem->frames[curframe].type));

    if (bspMem->frames[curframe].type == MDL_SINGLE) {
      /* single (non-grouped) frame */
      WriteFrame(modelouthandle, curframe);
      curframe++;
    }
    else {
      int j, numframes, groupframe;
      float totinterval;

      groupframe = curframe;
      curframe++;
      numframes = bspMem->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 += bspMem->frames[groupframe + 1 + j].interval;
	temp.interval = LittleFloat(totinterval);

	__write(modelouthandle, &temp, sizeof(temp));
      }

      for (j = 0; j < numframes; j++) {
	WriteFrame(modelouthandle, curframe);
	curframe++;
      }
    }
  }
  
  return TRUE;
}

/*
 * ===============
 * WriteModel
 * ===============
 */
bool WriteModel(void)
{
  HANDLE modelouthandle;
  bool success = FALSE;

  /* write the model output file */
  if (!bspMem->numframes) {
    eprintf("no frames grabbed, no file generated\n");
  }
  else {
    if (bspMem->numskins) {
    ReplaceExt(outname, "mdl");

    mprintf("---------------------\n");
    mprintf("writing %s:\n", outname);
    if((modelouthandle = __open(outname, H_WRITE_BINARY)) > 0) {
      if(WriteModelFile(modelouthandle)) {
        mprintf("%4d frame(s)\n", model.numframes);
        mprintf("%4d ungrouped frame(s), including group headers\n", bspMem->numframes);
        mprintf("%4d skin(s)\n", model.numskins);
        mprintf("%4d degenerate triangles(s) removed\n", degeneratetris);
        mprintf("%4d triangles emitted\n", model.numtris - degeneratetris);
        mprintf("pixels per triangle %f\n", averagesize);

        mprintf("file size: %d\n", (int)ftell(modelouthandle));
        mprintf("---------------------\n");
        success = TRUE;
      }
      else
        eprintf("failed to save model to %s\n", outname);
      __close(modelouthandle);

      ClearModel();
    }
    else
      eprintf("failed to open file %s\n", outname);
    }
    else
      eprintf("frames with no skins\n");
  }
  
  return success;
}

/*
 * ============
 * SetSkinValues
 * 
 * Called for the base frame
 * ============
 */
void SetSkinValues(void)
{
  int i;
  float v;
  int width, height, iwidth, iheight, skinwidth;
  float basex, basey;

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

  for (i = 0; i < model.numverts; i++) {
    int j;

    bspMem->stverts[i].onseam = 0;

    for (j = 0; j < 3; j++) {
      v = bspMem->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];

  mprintf("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;

  mprintf("scale: %f\n", scale);
  mprintf("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(bspMem->baseverts[bspMem->triangles[i].vertindex[0]], bspMem->baseverts[bspMem->triangles[i].vertindex[1]], vtemp1);
    VectorSubtract(bspMem->baseverts[bspMem->triangles[i].vertindex[2]], bspMem->baseverts[bspMem->triangles[i].vertindex[1]], vtemp2);
    CrossProduct(vtemp1, vtemp2, normal);

    if (normal[1] > 0) {
      basex = iwidth + 2;
      bspMem->triangles[i].facesfront = 0;
    }
    else {
      basex = 2;
      bspMem->triangles[i].facesfront = 1;
    }
    basey = 2;

    for (j = 0; j < 3; j++) {
      float *pbasevert;
      struct stvert *pstvert;

      pbasevert = bspMem->baseverts[bspMem->triangles[i].vertindex[j]];
      pstvert = &bspMem->stverts[bspMem->triangles[i].vertindex[j]];

      if (bspMem->triangles[i].facesfront) {
	pstvert->onseam |= 1;
      }
      else {
	pstvert->onseam |= 2;
      }

      if ((bspMem->triangles[i].facesfront) || ((pstvert->onseam & 1) == 0)) {
	/* we want the front s value for seam vertices */
	pstvert->s = rint((pbasevert[0] - mins[0]) * scale + basex);
	pstvert->t = rint((maxs[2] - pbasevert[2]) * scale + basey);
      }
    }
  }

  /*
   * 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;

  mprintf("skin width: %i (unpadded width %i)  skin height: %i\n",
	  model.skinwidth, skinwidth, model.skinheight);
}

/*
 * =================
 * Cmd_Base
 * =================
 */
void Cmd_Base(void)
{
  struct triangle *ptri;
  int i, j, k;
  int time1;

  GetToken(false);
  strcpy(qbasename, token);
  sprintf(file1, "%s/%s.tri", cdpartial, token);

  ExpandPathAndArchive(file1);

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

  /* load the base triangles */
  LoadTriangleList(file1, &ptri, &model.numtris);
  mprintf("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++;
      bspMem->degenerate[i] = 1;
    }
    else {
      bspMem->degenerate[i] = 0;
    }

    for (j = 0; j < 3; j++) {
      for (k = 0; k < model.numverts; k++)
	if (VectorCompare(ptri[i].verts[j], bspMem->baseverts[k]))
	  break;						/* this vertex is already in the base vertex list */

      if (k == model.numverts) {
	/* new vertex */
	VectorCopy(ptri[i].verts[j], bspMem->baseverts[model.numverts]);
	model.numverts++;
      }

      bspMem->triangles[i].vertindex[j] = k;
    }
  }

  mprintf("NUMBER OF VERTEXES: %i\n", model.numverts);

  /* calculate s & t for each vertex, and set the skin width and height */
  SetSkinValues();
}

/*
 * ===============
 * Cmd_Skin
 * ===============
 */
void Cmd_Skin(void)
{
  struct palpic *pskinbitmap;
  byte *ptemp1, *ptemp2;
  int i;
  int time1;

  GetToken(false);
  strcpy(skinname, token);
  sprintf(file1, "%s/%s.lbm", cdpartial, token);

  ExpandPathAndArchive(file1);

  sprintf(file1, "%s/%s.lbm", cddir, token);
  time1 = FileTime(file1);
  if (time1 == -1)
    Error("%s not found", file1);

  if (TokenAvailable()) {
    GetToken(false);
    bspMem->skins[bspMem->numskins].interval = atof(token);
    if (bspMem->skins[bspMem->numskins].interval <= 0.0)
      Error("Non-positive interval");
  }
  else {
    bspMem->skins[bspMem->numskins].interval = 0.1;
  }

  /* load in the skin file */
  pskinbitmap = GetImage(file1, 0, -model.skinwidth, -model.skinheight);

  /*
   * now copy the part of the texture we care about, since LBMs are always
   * loaded as 320x200 bitmaps
   */
  bspMem->skins[bspMem->numskins].pdata = tmalloc(model.skinwidth * model.skinheight);

  if (!bspMem->skins[bspMem->numskins].pdata)
    Error("couldn't get memory for skin texture");

  ptemp1 = bspMem->skins[bspMem->numskins].pdata;
  ptemp2 = pskinbitmap;

  for (i = 0; i < model.skinheight; i++) {
    __memcpy(ptemp1, ptemp2, model.skinwidth);
    ptemp1 += model.skinwidth;
    ptemp2 += 320;
  }

  if (bspMem->numskins >= bspMem->max_numskins)
    EpandClusters(bspMem, MODEL_SKINS);
  bspMem->numskins++;
}

/*
 * ===============
 * GrabFrame
 * ===============
 */
void GrabFrame(char *frame, int isgroup)
{
  struct triangle *ptri;
  int i, j;
  struct trivert *ptrivert;
  int numtris;
  int time1;

  sprintf(file1, "%s/%s.tri", cdpartial, frame);
  ExpandPathAndArchive(file1);

  sprintf(file1, "%s/%s.tri", cddir, frame);
  time1 = FileTime(file1);
  if (time1 == -1)
    Error("%s does not exist", file1);

  mprintf("grabbing %s\n", file1);
  bspMem->frames[bspMem->numframes].interval = 0.1;
  strcpy(bspMem->frames[bspMem->numframes].name, frame);

  /* load the frame */
  LoadTriangleList(file1, &ptri, &numtris);

  if (numtris != model.numtris)
    Error("number of triangles doesn't match\n");

  /* set the intervals */
  if (isgroup && TokenAvailable()) {
    GetToken(false);
    bspMem->frames[bspMem->numframes].interval = atof(token);
    if (bspMem->frames[bspMem->numframes].interval <= 0.0)
      Error("Non-positive interval %s %f", token, bspMem->frames[bspMem->numframes].interval);
  }
  else {
    bspMem->frames[bspMem->numframes].interval = 0.1;
  }

  /* allocate storage for the frame's vertices */
  ptrivert = bspMem->verts[bspMem->numframes];

  bspMem->frames[bspMem->numframes].pdata = ptrivert;
  bspMem->frames[bspMem->numframes].type = MDL_SINGLE;

  for (i = 0; i < model.numverts; i++) {
    bspMem->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 (bspMem->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 += VectorLength(normal) / 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 = bspMem->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, bspMem->vnorms[vertindex].normals[bspMem->vnorms[vertindex].numnormals]);
      bspMem->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++) {
    int j;
    vec3_t v;
    float maxdot;
    int maxdotindex;

    if (bspMem->vnorms[i].numnormals > 0) {
      for (j = 0; j < 3; j++) {
	int k;

	v[j] = 0;

	for (k = 0; k < bspMem->vnorms[i].numnormals; k++) {
	  v[j] += bspMem->vnorms[i].normals[k][j];
	}

	v[j] /= bspMem->vnorms[i].numnormals;
      }
    }
    else {
      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;
  }

  if (bspMem->numframes >= bspMem->max_numframes)
    EpandClusters(bspMem, MODEL_FRAMES);
  bspMem->numframes++;

  tfree(ptri);
  firstframe = 0;
}

/*
 * ===============
 * Cmd_Frame    
 * ===============
 */
void Cmd_Frame(int isgroup)
{
  while (TokenAvailable()) {
    GetToken(false);
    GrabFrame(token, isgroup);

    if (!isgroup)
      model.numframes++;
  }
}

/*
 * ===============
 * Cmd_SkinGroupStart   
 * ===============
 */
void Cmd_SkinGroupStart(void)
{
  int groupskin;

  if (bspMem->numskins >= bspMem->max_numskins)
    EpandClusters(bspMem, MODEL_SKINS);
  groupskin = bspMem->numskins++;

  bspMem->skins[groupskin].type = MDL_SKIN_GROUP;
  bspMem->skins[groupskin].numgroupskins = 0;

  while (1) {
    GetToken(true);
    if (endofscript)
      Error("End of file during group");

    if (!strcmp(token, "$skin")) {
      Cmd_Skin();
      bspMem->skins[groupskin].numgroupskins++;
    }
    else if (!strcmp(token, "$skingroupend")) {
      break;
    }
    else {
      Error("$skin or $skingroupend expected\n");
    }

  }

  if (bspMem->skins[groupskin].numgroupskins == 0)
    Error("Empty group\n");
}

/*
 * ===============
 * Cmd_FrameGroupStart  
 * ===============
 */
void Cmd_FrameGroupStart(void)
{
  int groupframe;

  if (bspMem->numframes >= bspMem->max_numframes)
    EpandClusters(bspMem, MODEL_FRAMES);
  groupframe = bspMem->numframes++;

  bspMem->frames[groupframe].type = MDL_GROUP;
  bspMem->frames[groupframe].numgroupframes = 0;

  while (1) {
    GetToken(true);
    if (endofscript)
      Error("End of file during group");

    if (!strcmp(token, "$frame")) {
      Cmd_Frame(1);
    }
    else if (!strcmp(token, "$framegroupend")) {
      break;
    }
    else {
      Error("$frame or $framegroupend expected\n");
    }

  }

  bspMem->frames[groupframe].numgroupframes += bspMem->numframes - groupframe - 1;

  if (bspMem->frames[groupframe].numgroupframes == 0)
    Error("Empty group\n");
}

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

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

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

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

  GetToken(false);
  model.eyeposition[0] = -atof(token);

  GetToken(false);
  model.eyeposition[2] = atof(token);
}

/*
 * =================
 * Cmd_ScaleUp
 * =================
 */
void Cmd_ScaleUp(void)
{
  GetToken(false);
  scale_up = atof(token);
}

/*
 * =================
 * Cmd_Flags
 * =================
 */
void Cmd_Flags(void)
{
  GetToken(false);
  model.flags = atoi(token);
}

/*
 * =================
 * Cmd_Modelname
 * =================
 */
void Cmd_Modelname(void)
{
  WriteModel();
  GetToken(false);
  strcpy(outname, token);
}

/*
 * ===============
 * ParseScript
 * ===============
 */
void ParseScript(void)
{
  while (1) {
    do {							/* look for a line starting with a $ command */
      GetToken(true);
      if (endofscript)
	return;
      if (token[0] == '$')
	break;
      while (TokenAvailable())
	GetToken(false);
    } while (1);

    if (!strcmp(token, "$modelname")) {
      Cmd_Modelname();
    }
    else if (!strcmp(token, "$base")) {
      Cmd_Base();
    }
    else if (!strcmp(token, "$cd")) {
      if (cdset)
	Error("Two $cd in one model");
      cdset = true;
      GetToken(false);
      strcpy(cdpartial, token);

      strcpy(cddir, ExpandPath(token));

    }
    else if (!strcmp(token, "$sync")) {
      model.synctype = ST_SYNC;
    }
    else if (!strcmp(token, "$origin")) {
      Cmd_Origin();
    }
    else if (!strcmp(token, "$eyeposition")) {
      Cmd_Eyeposition();
    }
    else if (!strcmp(token, "$scale")) {
      Cmd_ScaleUp();
    }
    else if (!strcmp(token, "$flags")) {
      Cmd_Flags();
    }
    else if (!strcmp(token, "$frame")) {
      Cmd_Frame(0);
    }
    else if (!strcmp(token, "$skin")) {
      Cmd_Skin();
      model.numskins++;
    }
    else if (!strcmp(token, "$framegroupstart")) {
      Cmd_FrameGroupStart();
      model.numframes++;
    }
    else if (!strcmp(token, "$skingroupstart")) {
      Cmd_SkinGroupStart();
      model.numskins++;
    }
    else {
      Error("bad command %s\n", token);
    }
  }
}

/*
 * ==============
 * main
 * ==============
 */
int main(int argc, char **argv)
{
  int i;
  char path[1024];

  if (argc != 2 && argc != 4)
    Error("usage: modelgen [-archive directory] file.qc");

  if (!strcmp(argv[1], "-archive")) {
    archive = true;
    strcpy(archivedir, argv[2]);
    mprintf("Archiving source to: %s\n", archivedir);
    i = 3;
  }
  else
    i = 1;

  /*
   * load the script
   */
  strcpy(path, argv[i]);

  DefaultExtension(path, ".qc");
  SetQdirFromPath(path);

  LoadScriptFile(path);

  /*
   * parse it
   */
  __bzero(&model, sizeof(model));

  for (i = 0; i < 3; i++) {
    framesmins[i] = 9999999;
    framesmaxs[i] = -9999999;
  }

  ClearModel();
  strcpy(outname, argv[1]);

  ParseScript();
  WriteModel();

  return 0;
}
