#include <math.h>
#include <malloc.h>
#include <stdlib.h>
#include <direct.h>
#include <string.h>
#include <conio.h>

#include <pr.h>

#if defined (MSGLIDE) || defined (WTGLIDE)
 #include <glide.h>
 #include <pr3dfx.h>
 #include "winutil.h"
#elif defined (MSDD) || defined (WTDD)
 #include "winutil.h"
#else
 #ifdef __3DFX__
  #include <pr3dfx.h>
  #include <glide.h>
 #endif

 #ifdef __MYST__
  #include <msidos.h>
  #include <prmyst.h>
 #endif

#endif

#include <prgui.h>

PR_DWORD     device;
PR_DWORD     current_mode;          /* Current video mode */
PR_DWORD     vwidth;                /* Viewport size */
PR_DWORD     vheight;
PR_DWORD     view_opened = 0;       /* 0 for first initialization */
PR_VIEWPORT  viewport;           /* Our viewport structure */
PR_LIGHTLIST userlights;        /* We only use 1 light */
PR_CAMERA *  newcam;              /* One camera */


char pakfile[80];

PR_REAL speeddiv = 350;         /* Speed divisor */
PR_REAL dx, dy, dz;             /* Delta rotation */
PR_REAL scale;                  
PR_REAL ambient_light = 0;
PR_REAL gamma_correction = 1.3;
PR_DWORD force_vga = 0;

PR_OBJECT *test;                /* The shape loaded */
PR_ENTITY *obj1;                /* A single entity of the shape */


/* Colors for text */
char brightcolor;
char greycolor;

/* Frame Rate Control  */
PR_DWORD ticks;                 /* Total number of ticks passed */
PR_DWORD updates;               /* Total number of updates */
PR_DWORD framerate;             /* Resulting frame rate (updates/ticks/tickrate) */

PR_DWORD kf_delay = 1;
PR_DWORD kf_ticks = 0;



/* MDL/PAK structures (Thanks to Trey Harrison) */
typedef struct
{
  float x, y, z;
} vec3_t;

typedef struct
{
  long id;                     /* 0x4F504449 = "IDPO"                  */
  long version;                /* Version = 3                          */
  vec3_t scale;                /* Scale factor, for x,y,z              */
  vec3_t origin;               /* Model origin: point for x=0,y=0,z=0  */
  float radius;                /* Model radius, maybe useless now.     */
  vec3_t eye;                  /* Position of the eyes                 */
  long numskins ;              /* the number of skin textures          */
  long skinwidth;              /* Width of skin texture                */
                               /*           must be multiple of 8      */
  long skinheight;             /* Height of skin texture               */
                               /*           must be multiple of 8      */
  long numverts;               /* Number of vertices                   */
  long numtris;                /* Number of triangles surfaces         */
  long numframes;              /* Number of frames                     */
  long sync;                   /* Sync type                            */
  long flags;                  /* duh                                  */
  float size;                  /* dunno. pixels / triangle ??          */
} mdlheader_t;


typedef struct
{
  long        type;            /* Group type(1) or regular(0)          */
  long        numgroupskins;   /* only used on group skins             */
  float       *interval;       /* only used on group skins             */
  unsigned char **skin;        /* the skin picture, length = skinwidth*skinheight */
} skin_t;                      /* numskins pictures                    */
   

typedef struct
{
  long onseam;                 /* 0 or 1                               */
  long s;                      /* position, horizontally               */
                               /*  in range [0,skinwidth]              */
  long t;                      /* position, vertically                 */
                               /*  in range [0,skinheight]             */
} stvert_t;


typedef struct
{
  long facesfront;             /* boolean                              */
  long vertices[3];            /* Index of 3 triangle vertices         */
                               /* in range [0,numverts]                */
} itriangle_t;


typedef struct
{
  unsigned char x;
  unsigned char y;             /* X,Y,Z coordinate, packed on 0-255    */
  unsigned char z;
  unsigned char lightnormalindex;  /* index of the vertex normal       */
} trivertx_t;


typedef struct
{
  trivertx_t min;               
  trivertx_t max;               
  char name[16];               /* name of frame                        */ 
} frameinfo_t;

typedef struct { 
  long type;                   /* single frame(0) or group frame(1)    */
  long numgroupframes;         /* used for groups only, duh            */
  trivertx_t groupmin;         /* minimum values of X,Y,Z for group    */
  trivertx_t groupmax;         /* maximum values of X,Y,Z for group    */
  frameinfo_t *info;           /* info on each frame                   */
  float *interval;             /* interval crap, dunno what it is      */
  trivertx_t **data;           /* length of this array is numverts*sizeof(trivertx_t) */
} frame_t;

typedef struct
{
  char            classname[40];
  vec3_t          scale;
  vec3_t          origin;
  long            numskins;
  skin_t          *skins;
  long            numverts;
  stvert_t        *verts;
  long            numtris;
  itriangle_t     *triangles;
  long            numframes;
  frame_t         *frames;
  long            skinwidth;
  long            skinheight;
  long            scaledwidth;
  long            scaledheight;
} mdl_t;




typedef struct
{
  unsigned char magic[4];      /* Name of the new WAD format = "PACK" */
  long diroffset;              /* Position of WAD directory from start of file */
  long dirsize;                /* Number of entries * 0x40 (64 char) */
} pakheader_t;

typedef struct
{
  unsigned char filename[0x38]; /* Name of the file, Unix style, with extension, */
                                /* 56 chars, padded with '\0'. */
  long offset;                  /* Position of the entry in PACK file */
  long size;                    /* Size of the entry in PACK file */
} pakentry_t;




PR_DWORD previous_frame = 0, frame_number = 0;
PR_VERTEX *frame_list[256];
mdl_t     *testmdl;
PR_REAL morphvalue = 0;
PR_REAL morphstep = 0.2;
PR_DWORD morphplay = 1;
PR_DWORD mdl_mode = 0;




void PR_ImportMDLTextures (mdl_t *mdl)
{
PR_MATERIAL *m;
PR_DWORD matnum;
skin_t *skin;
block skinpic;
char texname[20];
unsigned char *bits;
PR_DWORD pitch, y;

  wloadpalette ("quake.pal", global_palette);

  PR_LoadTable ("quake.tab");
  PR_SetTextureFormat (TEXTURE_NORMAL);

  for (matnum = 0; matnum < mdl->numskins; matnum++)
    {
     skin = &mdl->skins[matnum];

     /* Create the texture */
     skinpic = wallocblock (mdl->skinwidth, mdl->skinheight);
 
     #if defined (MSDD) || defined (WTDD)
     bits = wgetblocksurface (skinpic, &pitch);
     if (bits == NULL)
        return;
     #else
     bits = skinpic + 4;
     pitch = mdl->skinwidth;
     #endif

     for (y = 0; y < mdl->skinheight; y++)
       {
        memcpy (bits, skin->skin[0] + y * mdl->skinwidth, mdl->skinwidth);
        bits += pitch;
       }
     
     #if defined (MSDD) || defined (WTDD)
        wunlocksurface (skinpic);
     #endif
     
     sprintf (texname, "skin%i.pcx", matnum);
  //   wsavepcx (texname, skinpic, global_palette);

     
     m = &PR_ObjectMaterialList[matnum];

     PR_SetMaterialName (m, texname);
     PR_SetMaterialMethod (m, T_GOURAUD_TEXTURED8);
     PR_SetMaterialBaseColor (m, 0);
     PR_SetMaterialColor (m, 255, 255, 255);
     PR_SetMaterialTexture (m, PR_AddTexture (texname, skinpic));
     PR_SetMaterialTable (m, 0);
     PR_SetMaterialShades (m, 31);
     PR_SetMaterialEnvironmentMap (m, 0);
     PR_SetMaterialMipMapState (m, FALSE);
     m->a = 255;

     PR_AddMaterial (m);
    }
}



PR_OBJECT *PR_ImportMDL (mdl_t *mdl)
/* Converts an MDL structure to a PR_OBJECT structure */
{
PR_OBJECT *obj;
PR_SEGMENT *seg;
PR_FACE_DATA_REAL *datareal;
PR_FACE_DATA *datafix;
PR_DWORD texnum;
PR_DWORD width, height;
PR_DWORD i, frame;
frame_t *frameptr;
PR_VERTEX *vptr;

PR_DWORD facesfront, v1OnSeam, v2OnSeam, v3OnSeam;
PR_REAL sadd;
PR_REAL xs,ys;

  PR_ImportMDLTextures (mdl);


  /* Allocate the object */
  obj = PR_AllocObject ();

  /* Allocate the segment */
  obj->segment_list = PR_AllocSegment ();
  obj->num_segments = 1;
  obj->num_frames = -1;

  /* Allocate the faces */
  seg = obj->segment_list;
  seg->num_faces = mdl->numtris;
  seg->face_list = malloc (seg->num_faces * sizeof (PR_FACE));
  memset (seg->face_list, 0, seg->num_faces * sizeof (PR_FACE));

  seg->num_vertices = mdl->numverts;
  seg->vertex_list = malloc (seg->num_vertices * sizeof (PR_VERTEX));
  seg->vertex_data = malloc (seg->num_vertices * sizeof (PR_VERTEX_DATA));
  memset (seg->vertex_list, 0, seg->num_vertices * sizeof (PR_VERTEX));
  memset (seg->vertex_data, 0, seg->num_vertices * sizeof (PR_VERTEX_DATA));

  seg->flags = 0;

  /* Set up vertices, normals, and face data */

  for (frame = 0; frame < mdl->numframes; frame++)
    {
     frame_list[frame] = malloc (seg->num_vertices * sizeof (PR_VERTEX));
     memset (frame_list[frame], 0, seg->num_vertices * sizeof (PR_VERTEX));

     frameptr = &mdl->frames[frame];
     vptr = frame_list[frame];

     for (i = 0; i < seg->num_vertices; i++)
       {
        vptr[i].z = frameptr->data[0][i].x * mdl->scale.x;
        vptr[i].x = frameptr->data[0][i].y * mdl->scale.y;
        vptr[i].y = frameptr->data[0][i].z * mdl->scale.z;
        vptr[i].vdata = &seg->vertex_data[i];
       }
    }


   memcpy (seg->vertex_list, frame_list[0], seg->num_vertices * sizeof (PR_VERTEX));

   frame = 0;
   frameptr = &mdl->frames[0];
   for (i = 0; i < seg->num_faces; i++)
     {
     seg->face_list[i].vertex1 = PR_GetVertexPointer (seg->vertex_list, mdl->triangles[i].vertices[0]);
     seg->face_list[i].vertex2 = PR_GetVertexPointer (seg->vertex_list, mdl->triangles[i].vertices[1]);
     seg->face_list[i].vertex3 = PR_GetVertexPointer (seg->vertex_list, mdl->triangles[i].vertices[2]);

     seg->face_list[i].material = 0;
     seg->face_list[i].backmaterial = 0;

     seg->face_list[i].flags = FFLAG_FRONT_VISIBLE; 


     /* Set up the texture coordinates */
     texnum = PR_ObjectMaterialList[0].texture_number;
     width = PR_WorldTextures[texnum].width;
     height = PR_WorldTextures[texnum].height;

     xs = ((PR_REAL)width  / (PR_REAL)mdl->skinwidth);
     ys = ((PR_REAL)height / (PR_REAL)mdl->skinheight);


     if (PR_Settings.RealUV)
       {
        datareal = (PR_FACE_DATA_REAL *)&seg->face_list[i].face_data;

        facesfront = mdl->triangles[i].facesfront;
        v1OnSeam = mdl->verts[mdl->triangles[i].vertices[0]].onseam;
        v2OnSeam = mdl->verts[mdl->triangles[i].vertices[1]].onseam;
        v3OnSeam = mdl->verts[mdl->triangles[i].vertices[2]].onseam;

        if (PR_OutputDevice.devicetype == DEVICE_D3D)
          {
           xs /= width;
           ys /= height;
          }
        if (PR_OutputDevice.devicetype == DEVICE_D3D)
          sadd = (v1OnSeam && !facesfront)?(0.5f):(0.0f);
        else 
          sadd = (v1OnSeam && !facesfront)?((PR_REAL)PR_WorldTextures[texnum].width/2.0f):(0.0f);

        datareal->u[0] = ((PR_REAL)mdl->verts[mdl->triangles[i].vertices[0]].s * xs + sadd);
        datareal->v[0] = ((PR_REAL)mdl->verts[mdl->triangles[i].vertices[0]].t * ys);

        if (PR_OutputDevice.devicetype == DEVICE_D3D)
          sadd = (v2OnSeam && !facesfront)?(0.5f):(0.0f);
        else 
          sadd = (v2OnSeam && !facesfront)?((PR_REAL)PR_WorldTextures[texnum].width/2.0f):(0.0f);
        datareal->u[1] = ((PR_REAL)mdl->verts[mdl->triangles[i].vertices[1]].s * xs + sadd);
        datareal->v[1] = ((PR_REAL)mdl->verts[mdl->triangles[i].vertices[1]].t * ys);

        if (PR_OutputDevice.devicetype == DEVICE_D3D)
          sadd = (v3OnSeam && !facesfront)?(0.5f):(0.0f);
        else 
          sadd = (v3OnSeam && !facesfront)?((PR_REAL)PR_WorldTextures[texnum].width/2.0f):(0.0f);
        datareal->u[2] = ((PR_REAL)mdl->verts[mdl->triangles[i].vertices[2]].s * xs + sadd);
        datareal->v[2] = ((PR_REAL)mdl->verts[mdl->triangles[i].vertices[2]].t * ys);
       }
      else
       {
        datafix = &seg->face_list[i].face_data;

        facesfront = mdl->triangles[i].facesfront;
        v1OnSeam = mdl->verts[mdl->triangles[i].vertices[0]].onseam;
        v2OnSeam = mdl->verts[mdl->triangles[i].vertices[1]].onseam;
        v3OnSeam = mdl->verts[mdl->triangles[i].vertices[2]].onseam;

        sadd = (v1OnSeam && !facesfront)?((PR_REAL)PR_WorldTextures[texnum].width/2.f):(0);
        datafix->u[0] = (PR_DWORD)(mdl->verts[mdl->triangles[i].vertices[0]].s + sadd) << 16;
        datafix->v[0] = (PR_DWORD)(mdl->verts[mdl->triangles[i].vertices[0]].t) << 16;

        sadd = (v2OnSeam && !facesfront)?((PR_REAL)PR_WorldTextures[texnum].width/2.f):(0);
        datafix->u[1] = (PR_DWORD)(mdl->verts[mdl->triangles[i].vertices[1]].s + sadd) << 16;
        datafix->v[1] = (PR_DWORD)(mdl->verts[mdl->triangles[i].vertices[1]].t) << 16;

        sadd = (v3OnSeam && !facesfront)?((PR_REAL)PR_WorldTextures[texnum].width/2.f):(0);
        datafix->u[2] = (PR_DWORD)(mdl->verts[mdl->triangles[i].vertices[2]].s + sadd) << 16;
        datafix->v[2] = (PR_DWORD)(mdl->verts[mdl->triangles[i].vertices[2]].t) << 16;
       }
    }

  PR_ObjectCalcBoundingBox (obj);
  PR_FlipFaceNormals (obj);
  PR_InitializeFaceNormals (obj);
  PR_InitializeVertexNormals (obj);
  return (obj);
}


void RemoveDirs (char *basestr, char *newstr)
/*
Used to remove any directories in front of the filename
/ and \ are treated as the same thing
Ex: /progs\blah\misc/a.html would return a.html
*/
{
PR_DWORD i;
PR_DWORD modify;

  modify = -1;
  for (i = strlen (basestr)-1; i >= 0; i--)
    {
     if ((basestr[i] == '/') || (basestr[i] == '\\'))
        {
         modify = i+1;
         break;
        }
    }

  if (modify == -1)
    {
     strcpy (newstr, basestr);
     return;
    }

  for (i = modify; i < strlen (basestr); i++)
     newstr[i-modify] = basestr[i];
  newstr[i-modify] = 0;
  return;
}




mdl_t *PR_LoadMDLFromPak (char *fname,char *pakname)
{
FILE        *f;
mdlheader_t mdlheader;
pakheader_t pakheader;
pakentry_t  pakentry;
PR_DWORD    numentries; 
char        cmpname[64];
PR_DWORD    i,j;
PR_DWORD    FOUND;
mdl_t   *mdl;


  if (pakname == NULL)
    {
     /* Library code */
     if  (wgtlibrary == NULL)
       {
        libf = fopen (fname, "rb");
        if (libf == NULL)
          return (NULL);
        f = libf;
       }
     else
       {
        if ((libf = fopen (wgtlibrary, "rb")) == NULL)
          return (NULL);
        readheader ();
        findfile (fname);
        if (lresult == 1)
          fseek (libf, lfpos, SEEK_SET);
        else
          {
           fclose (libf);
           return (NULL);
          }
        f = libf;
       }
    }
  else
    {
     f = fopen (pakname, "rb");
     if (f == NULL)
        PRGUI_Error ("Unable to open pakfile");
    
	
     fread (&pakheader, sizeof(pakheader_t), 1, f);
     numentries = pakheader.dirsize / (sizeof(pakentry_t));

     fseek (f, pakheader.diroffset, SEEK_SET);
     FOUND = 0;

     for (i = 0; i < numentries; i++)
       {
        fread (&pakentry, sizeof(pakentry_t), 1, f);
        if (strstr(pakentry.filename, ".mdl") != NULL)
          {
           RemoveDirs (pakentry.filename, cmpname);
           if (stricmp (fname, cmpname) == 0)
             {
              FOUND = 1;
              break;
             }
          }
       }
	
     if (!FOUND)
       {
        fclose (f);
        PRGUI_Error ("Couldnt find model in the pakfile");
       }
	
     fseek (f, pakentry.offset, SEEK_SET); 
    }

  mdl = (mdl_t *)malloc (sizeof (mdl_t)); 
  memset (mdl, 0, sizeof (mdl_t));        

  fread (&mdlheader, sizeof(mdlheader_t), 1, f);

  mdl->origin     = mdlheader.origin;
  mdl->scale      = mdlheader.scale;
  mdl->numverts   = mdlheader.numverts;
  mdl->numtris    = mdlheader.numtris;
  mdl->numskins   = mdlheader.numskins;
  mdl->numframes  = mdlheader.numframes;
  mdl->skinwidth  = mdlheader.skinwidth;
  mdl->skinheight = mdlheader.skinheight;

  /* Skin Loading */
  mdl->skins = (skin_t *) malloc (mdlheader.numskins * sizeof(skin_t)); 
  memset (mdl->skins, 0, mdlheader.numskins * sizeof(skin_t));

  for (i = 0;i < mdlheader.numskins; i++)
    {
     /* Get skin type (group or single) */
     fread (&(mdl->skins[i].type), sizeof(long), 1, f);   
     if (mdl->skins[i].type == 0)
       {
        /* Single skin */
        mdl->skins[i].numgroupskins = 1;
       
        /* Init the skin memory */
        mdl->skins[i].skin    = (unsigned char **)malloc (sizeof(unsigned char *)); 
        mdl->skins[i].skin[0] = (unsigned char *)malloc (mdlheader.skinwidth * mdlheader.skinheight);          
			
        /* read skin data */
        fread (mdl->skins[i].skin[0], mdlheader.skinwidth * mdlheader.skinheight, 1, f);

        /* init interval crap. dunno what its for */
        mdl->skins[i].interval = (float *)malloc (sizeof(float));             
        *(mdl->skins[i].interval) = 0.f;
       }
     else
       {
        /* Group skin */
        fread (&(mdl->skins[i].numgroupskins), sizeof(long), 1, f);

        /* load group skin interval data */
        mdl->skins[i].interval = (float *)malloc (sizeof(float) * (mdl->skins[i].numgroupskins));
        for (j = 0; j < mdl->skins[i].numgroupskins; j++)
           fread (&(mdl->skins[i].interval[j]), sizeof(float), 1, f);
			
        /* load group skin data */
        mdl->skins[i].skin = (unsigned char **)malloc (sizeof(unsigned char *) * (mdl->skins[i].numgroupskins));
						
        for (j = 0; j < mdl->skins[i].numgroupskins; j++)
          {
           mdl->skins[i].skin[j] = (unsigned char *)malloc (mdlheader.skinwidth*mdlheader.skinheight);
           fread (mdl->skins[i].skin[j], mdlheader.skinwidth * mdlheader.skinheight, 1, f);
          }
       }
    }


  /* Vertex loading (texture vertices, not spatial vertices) */
  mdl->verts = (stvert_t *)malloc (mdlheader.numverts * sizeof(stvert_t));
  fread (mdl->verts, mdlheader.numverts * sizeof(stvert_t), 1, f);

  /* Triangle loading */
  mdl->triangles = (itriangle_t *)malloc (mdlheader.numtris * sizeof(itriangle_t));
  fread (mdl->triangles, mdlheader.numtris * sizeof(itriangle_t), 1, f);

  /* Frame loading (spatial vertex information)	*/
  mdl->frames = (frame_t *)malloc (mdlheader.numframes * sizeof(frame_t)); 
  for (i = 0;i < mdlheader.numframes; i++)
    {
     /* Read the frame type (group or single) */
     fread (&(mdl->frames[i].type), sizeof(long), 1, f);
		
     if (mdl->frames[i].type == 0)
        {
         /* Single frame */               
         mdl->frames[i].numgroupframes = 1;

         /* Get frame info */
         mdl->frames[i].info = (frameinfo_t *)malloc (sizeof(frameinfo_t));            
         fread (mdl->frames[i].info, sizeof(frameinfo_t), 1, f);

         /* Get frame data */
         mdl->frames[i].data = (trivertx_t **)malloc (sizeof(unsigned char *));               
         mdl->frames[i].data[0] = (trivertx_t *)malloc (mdlheader.numverts * sizeof(trivertx_t));
         fread (mdl->frames[i].data[0], mdlheader.numverts * sizeof(trivertx_t), 1, f);

         /* make fake "interval" data */
         mdl->frames[i].interval = (float *)malloc (sizeof(float));            
         *(mdl->frames[i].interval) = 0.f;
        }
      else
        {
         /* Group frame */
         /* get group information */
         fread (&(mdl->frames[i].numgroupframes), sizeof(long), 1, f);
         fread (&(mdl->frames[i].groupmin), sizeof(trivertx_t), 1, f);
         fread (&(mdl->frames[i].groupmax), sizeof(trivertx_t), 1, f);
			
         /* init necessary data memory */
         mdl->frames[i].data = (trivertx_t **)malloc (sizeof(unsigned char *) * (mdl->frames[i].numgroupframes));
			
         /* init necessary info memory */
         mdl->frames[i].info = (frameinfo_t *)malloc(sizeof(frameinfo_t) * (mdl->frames[i].numgroupframes));
			
         /* load group frame interval data */
         mdl->frames[i].interval = (float *)malloc(sizeof(float) * (mdl->frames[i].numgroupframes));
			
         for (j = 0; j < mdl->frames[i].numgroupframes; j++)
           fread (&(mdl->frames[i].interval[j]), sizeof(float), 1, f);

         for (j = 0;j < mdl->frames[i].numgroupframes; j++)
           {
            /* read in the frame info */
            fread (&(mdl->frames[i].info[j]), sizeof(frameinfo_t), 1, f);

            /* allocate and read in the frame data */
            mdl->frames[i].data[j] = (trivertx_t *)malloc (mdlheader.numverts * sizeof(trivertx_t));                        
            fread (mdl->frames[i].data[j], mdlheader.numverts * sizeof(trivertx_t), 1, f);
           }
        }
    }


  /* Check if everything was read ok. We should be at the end of the
     pak entry right now */
  if (pakname != NULL)
    {
     if ((ftell (f) - pakentry.offset) != pakentry.size)
       {
        fclose (f);
        PRGUI_Error ("Error reading model from pakfile");
       }
    }
  fclose (f);
  return(mdl);
}


void FixMDLNormals (void)
{
PR_DWORD i, frame;
PR_VERTEX *frameptr;
PR_REAL cenx, ceny, cenz;
PR_SEGMENT *seg;

  PR_CenterObject (test, &cenx, &ceny, &cenz);
  seg = test->segment_list;

  for (frame = 0; frame < testmdl->numframes; frame++)
    {
     for (i = 0; i < test->segment_list->num_vertices; i++)
       {
        frameptr = frame_list[frame];
        frameptr[i].x -= cenx;
        frameptr[i].y -= ceny;
        frameptr[i].z -= cenz;
       }

     memcpy (seg->vertex_list, frame_list[frame], seg->num_vertices * sizeof (PR_VERTEX));
     PR_InitializeFaceNormals (test);
     PR_InitializeVertexNormals (test);
     memcpy (frame_list[frame], seg->vertex_list, seg->num_vertices * sizeof (PR_VERTEX));
    }
}








/* ---------------------------------------------------------------------- */
/* Timer Interrupt */
/* ---------------------------------------------------------------------- */
void timerproc (void)
{
  ticks++;
  if (ticks > kf_delay)
    kf_ticks++;
}                


/* ---------------------------------------------------------------------- */
/* Cycles through 3 video modes (320x200, 640x480, 800x600) */
/* ---------------------------------------------------------------------- */
PR_DWORD SetVideoMode (void)
{
  /* Cycle through all the modes */

  do {
   current_mode++;
   if (PR_VideoModes[current_mode].mode_number == -1)
     current_mode = 0;
   } while ((PR_VideoModes[current_mode].pages <= 1) && (force_vga == 0));

  vwidth = PR_VideoModes[current_mode].width;
  vheight = PR_VideoModes[current_mode].height;

  PR_SetMode (vwidth, vheight, 60);

  if (view_opened)
    PR_CloseViewport (&viewport);

  PR_OpenViewport (&viewport, 0, 0, vwidth-1, vheight-1, VIEW_PLAIN);

  PR_SetViewport (&viewport);
  PRGFX_Clip (active_viewport.topx,
              active_viewport.topy,
              active_viewport.bottomx,
              active_viewport.bottomy);
  view_opened = 1;

#if !defined (MSGLIDE) && !defined (WTGLIDE)
  if ((device == DEVICE_VGA) || (device == DEVICE_SVGA))
    wsetpalette (0, 255, global_palette);
#endif

  return (1);
}




/* ---------------------------------------------------------------------- */
/* Move the camera and rotate the object based on the user input */
/* ---------------------------------------------------------------------- */
void UserInput (void)
{
PR_DWORD dummy = 0;

  PR_ReadInput ();              

  /* This uses the inspect model.  Below we have some additional input
     controls */

  PR_CameraDirection (newcam);
  PR_DollyCamera (newcam, currentspeed);

  /* Object Rotation */
  if (mouse.but == 1)
    {
     dx = (PR_REAL)(mouse.my - 100) / 5;
     dy = (PR_REAL)(mouse.mx - 160) / 5;
     msetxy (160, 100);
    }
  else if (mouse.but == 2)
    {
     dz = (PR_REAL)(mouse.mx - 160) / 5;
     msetxy (160, 100);
    }

  msetxy (160, 100);
  mouse.mx = 160;
  mouse.my = 100;

  /* Keyboard input */
  if (kbdon[KEY_F1])
    {
     if (device != DEVICE_3DFX)
       while (!SetVideoMode ());
     while (kbdon[KEY_F1]) 
       { 
        dummy++;
        #ifdef WIN32
          UpdateMessages ();
        #endif
       }           /* Wait for user to release key */
    }


  if (kbdon[KEY_SPACE])
    {
     morphplay = !morphplay;
     while (kbdon[KEY_SPACE])
       { 
        dummy++;
        #ifdef WIN32
          UpdateMessages ();
        #endif
       }           /* Wait for user to release key */
    }


  /* Change ambient lighting */
  if (kbdon[KEY_COMMA])
    {
     ambient_light -= 0.1;
     if (ambient_light < 0.0)
       ambient_light = 0;

     PR_SetAmbientLight (ambient_light);
     PR_AmbientRed   = ambient_light * 255;
     PR_AmbientGreen = ambient_light * 255;
     PR_AmbientBlue  = ambient_light * 255;
    }

  if (kbdon[KEY_DOT])
    {
     ambient_light += 0.1;
     if (ambient_light > 1)
       ambient_light = 1;

     PR_SetAmbientLight (ambient_light);
     PR_AmbientRed   = ambient_light * 255;
     PR_AmbientGreen = ambient_light * 255;
     PR_AmbientBlue  = ambient_light * 255;
    }


  /* Change gamma correction (3Dfx only) */
  if (device == DEVICE_3DFX)
  {
  if (kbdon[KEY_SEMICOLON])
    {
     gamma_correction -= 0.05;
     if (gamma_correction < 0.0)
       gamma_correction = 0;

     #ifdef __3DFX__
     grGammaCorrectionValue (gamma_correction);
     #endif
    }

  if (kbdon[KEY_DQUOTE])
    {
     gamma_correction += 0.05;
     if (gamma_correction > 2)
       gamma_correction = 2;

     #ifdef __3DFX__
     grGammaCorrectionValue (gamma_correction);
     #endif
    }
   }


  /* Change animation speed */
  if (kbdon[KEY_LBRACE])
    {
     morphstep -= 0.002;
     if (morphstep < 0.001)
       morphstep = 0.001;
    }

  if (kbdon[KEY_RBRACE])
    {
     morphstep += 0.002;
     if (morphstep > 1)
       morphstep = 1;
    }


  /* Change movement speed */
  if (kbdon[KEY_GREY_MINUS])
    {
     speeddiv += 5;
     TRANSLATE_SPEED    = test->bbox.radius / (speeddiv*10);
     MAX_TRANSLATE_SPEED= test->bbox.radius / speeddiv;
     TRANSLATE_DRAG     = test->bbox.radius / (speeddiv*5);
    }
  else if (kbdon[KEY_GREY_PLUS])
    {
     speeddiv -= 5;
     if (speeddiv < 2)
       speeddiv = 1;
     TRANSLATE_SPEED    = test->bbox.radius / (speeddiv*10);
     MAX_TRANSLATE_SPEED= test->bbox.radius / speeddiv;
     TRANSLATE_DRAG     = test->bbox.radius / (speeddiv*5);
    }
}



/* ---------------------------------------------------------------------- */
/* Initialize Power Render and allocate room for data */
/* ---------------------------------------------------------------------- */
void InitializeProgram (void)
{
  PR_AllocMaterials (512);
  PR_AllocTextures (256);
  PR_AllocShadeTables (32);
}



/* ---------------------------------------------------------------------- */
/* Make one light which will follow the camera */
/* ---------------------------------------------------------------------- */
void InitializeLights (void)
{
  /* Initialize the lights */
  PR_AllocLights (&userlights, 1);      /* User lights */
  PR_AllocLights (&scenelights, 1);     /* Master scene light list */

  PR_SetLightPosition (&userlights, 0, 0, 0, 10000);
  /* Doesn't matter because we set it to the camera location every frame */

  PR_SetLightOn (&userlights, 0);
  PR_SetLightType (&userlights, 0, DIRECTIONAL_LIGHT);
  PR_SetLightStrength (&userlights, 0, 1.0);
  PR_SetLightColor (&userlights, 0, 1.0, 1.0, 1.0);

  userlights.NumLights = 1;     /* This sets how many lights are actually used */
}



void Fix_EnvMap (PR_OBJECT *obj)
{
PR_SEGMENT *segptr;
PR_MATERIAL *matptr;
PR_FACE *faceptr;
PR_DWORD seg, face;
  
  for (seg = 0; seg < obj->num_segments; seg++)
    { /* For every segment */

     segptr = &obj->segment_list[seg];
     segptr->flags &= ~FLAG_NORMALS;    /* Clear out the normals flag */

     for (face = 0; face < segptr->num_faces; face++)
       { /* For every face */

        faceptr = &segptr->face_list[face];
        matptr = &PR_ObjectMaterialList[faceptr->material];


        if (matptr->environment_map)
          segptr->flags |= FLAG_NORMALS;

        matptr = &PR_ObjectMaterialList[faceptr->backmaterial];
        if (matptr->environment_map)
          segptr->flags |= FLAG_NORMALS;
       }
    }

}



/* ---------------------------------------------------------------------- */
/* Loads a 3DS LWO, or PRO file */
/* ---------------------------------------------------------------------- */
void LoadFile (char *filename, PR_DWORD loadmode)
{
char path[_MAX_PATH];
#if defined (WIN32)
char drive[_MAX_DRIVE],dir[_MAX_DIR],name[_MAX_FNAME],ext[_MAX_EXT];
#else
char drive[_MAX_DRIVE],dir[_MAX_DIR],name[_MAX_NAME],ext[_MAX_EXT];
#endif

  strupr (filename);
  _makepath (path, drive, dir, filename,"");
  _splitpath (path, drive, dir, name, ext);

  if (!strcmp(ext,".3DS"))
    {
     PR_BaseMaterialNumber = 0;
     test = PR_Load3DS (filename, scale, loadmode);

     PR_Settings.FrontToBack = 0;
     if (test == NULL)
       {
		#ifndef WIN32
		  wsetmode (3);
		#endif

		PR_FatalError ("Could not open the 3DS file", "PRVIEW");
       }

     PR_CreateMaterialPalette ();
#if !defined (MSGLIDE) && !defined (WTGLIDE)
     if ((device == DEVICE_VGA) || (device == DEVICE_SVGA))
        wsetpalette (0, 255, global_palette);
#endif
     PR_SetObjectFaceFlags (test, FFLAG_FRONT_VISIBLE, FLAG_SET);
     PR_InitializeFaceNormals (test);
     PR_InitializeVertexNormals (test);
     Fix_EnvMap (test);
    }
  else if (!strcmp(ext,".LWO"))
    {
     PR_BaseMaterialNumber = 0;
     test = PR_LoadLWO (filename, scale, loadmode);

     PR_Settings.FrontToBack = 0;
     if (test == NULL)
       {
		#ifndef WIN32
		  wsetmode (3);
		#endif

		PR_FatalError ("Could not open the LWO file", "PRVIEW");
       }

     PR_CreateMaterialPalette ();
    #if !defined (MSGLIDE) && !defined (WTGLIDE)
    if ((device == DEVICE_VGA) || (device == DEVICE_SVGA))
       wsetpalette (0, 255, global_palette);
    #endif
     PR_InitializeFaceNormals (test);
     PR_InitializeVertexNormals (test);
     Fix_EnvMap (test);
    }
  else if (!strcmp(ext,".PRO"))
    {
     test = (PR_OBJECT *)PR_LoadPRO (filename, LOAD_NORMAL);

     PR_Settings.FrontToBack = 0;
     if (test == NULL)
       {
		#ifndef WIN32
		  wsetmode (3);
		#endif

		PR_FatalError ("Could not open the PRO file", "PRVIEW");
       }
     
     #if !defined (MSGLIDE) && !defined (WTGLIDE)
       if ((device == DEVICE_VGA) || (device == DEVICE_SVGA))
	 wsetpalette (0, 255, global_palette);
     #endif
    }
  else if (!strcmp(ext,".MDL"))
    {
     if (pakfile[0]==0)
       testmdl = PR_LoadMDLFromPak (filename, NULL);
     else
       testmdl = PR_LoadMDLFromPak (filename, pakfile);
     test = PR_ImportMDL (testmdl);

     PR_Settings.FrontToBack = 0;
     if (test == NULL)
       {
		#ifndef WIN32
		  wsetmode (3);
		#endif

		PR_FatalError ("Could not open the MDL file", "PRVIEW");
       }

     #if !defined (MSGLIDE) && !defined (WTGLIDE)
       if ((device == DEVICE_VGA) || (device == DEVICE_SVGA))
         wsetpalette (0, 255, global_palette);
     #endif

     FixMDLNormals ();
     mdl_mode = 1;
    }
   else
     {
      #ifndef WIN32
	   wsetmode (3);
	  #endif

	  PR_FatalError ("Unknown file format", "PRVIEW");
     }

  obj1 = PR_CreateEntity (test, "OBJ1");
  PR_ScaleEntityAbs (obj1, 1, 1, 1);
}




void InitializeDevices (void)
{
#ifdef __MYST__
  if ((device == DEVICE_MYSTIQUE) || (device == DEVICE_ANY))
    device = PR_DetectMystique ();
#endif


#ifdef __3DFX__
  if ((device == DEVICE_3DFX) || (device == DEVICE_ANY))
    device = PR_Detect3Dfx ();
#endif

#if !defined (MSGLIDE) && !defined (WTGLIDE)
  #if defined (MSDD) || defined (WTDD)
    if ((device == DEVICE_D3D) || (device == DEVICE_ANY))
      device = PR_DetectD3D ();   /* Attempt to find the device */
  #endif

  if ((device == DEVICE_SVGA) || (device == DEVICE_ANY))
    device = PR_DetectSVGA ();   /* Attempt to find the device */

  if ((device == DEVICE_VGA) || (device == DEVICE_ANY))
    device = PR_DetectVGA ();   /* Attempt to find the device */
#endif

#ifdef __MYST__
  if (device == DEVICE_MYSTIQUE)
    {
     PR_InitializeMystique ();
     atexit (PR_ShutdownMystique);
    }
#endif

#ifdef __3DFX__
  if (device == DEVICE_3DFX)
    {
     PR_Initialize3Dfx ();
     atexit (PR_Shutdown3Dfx);
    }
#endif

#if !defined (MSGLIDE) && !defined (WTGLIDE)
  #if defined (MSDD) || defined (WTDD)
  if (device == DEVICE_D3D)
    {
     PR_InitializeD3D ();
     atexit (PR_ShutdownD3D);
    }
  #endif
  if (device == DEVICE_SVGA)
    {
     PR_InitializeSVGA ();
     atexit (PR_ShutdownSVGA);
    }
  else if (device == DEVICE_VGA)
    {
     PR_InitializeVGA ();
    }
#endif
}


#if defined (MSDD) || defined (WTDD)
void PR_RestoreAll (void)
{
HRESULT ddrval;

 ddrval = IDirectDrawSurface2_Restore (wgtpdds);

 if (ddrval != DD_OK)
   return;

  PR_LostSurface = 0;
}
#endif



/* ---------------------------------------------------------------------- */
/* Main program */
/* ---------------------------------------------------------------------- */
void main (int argc, char *argv[])
{
PR_DWORD argnum;
PR_DWORD loadmode = 0;
PR_DWORD sky_color;
PR_DWORD flip_delay = 0;
PR_DWORD frame = 1;
PR_DWORD camframe = 1;

  scale = 1;

  loadmode = LOAD_IGNORE_TEXTURES;

  PRGUI_InitPath (argv[0]);
  PRGUI_SetUserPath ();
  
//  PR_SetDebugFile ("debug.log", REPORT_ERRORS | REPORT_ACTIONS);

  printf ("PRVIEW Utility    version ");
  printf (PR_VERSION_NUMBER);
  printf ("\nCopyright 1997 Egerter Software\n\n");

  if (argc < 2)
    {
	 PR_FatalError ("Usage: \n"
             "PRVIEW filename.[PRO/3DS/MDL/LWO] [options]\n"
             "\n"
             "Options:\n"
             "-s scale      Changes scaling factor\n"
             "-t ext        Imports textures from 3DS, using extension\n"
             "              ext = [PCX, 3DF, IFF, BLK, PAK]\n"
             "-m pakfile    Specifies a PAK file (ie pak1.pak)\n"
             "              If none specified, it loads from the file directly\n"
             "-v            Uses standard 320x200 VGA mode instead of VESA\n", "PRVIEW");
    }


  /* Default pak file */
  pakfile[0] = 0;

  argnum = 2;
  while (argnum < argc)
    {
     if (!strcmp (argv[argnum], "-s"))
       if (argc > argnum)
         {
          scale = atof (argv[argnum+1]);
          printf ("Scaling Factor: %f\n", scale);
          argnum++;
         }

     if (!strcmp (argv[argnum], "-v"))
       if (argc > argnum)
         {
          force_vga = 1;
          argnum++;
         }

     if (!strcmp (argv[argnum], "-t"))
       if (argc > argnum)
         {
          strcpy (L3DS_TextureExtension, argv[argnum+1]);
          if (strcmp (L3DS_TextureExtension, "3DF") == 0)
            device = DEVICE_3DFX;
          printf ("Textures Enabled\nTexture Extension: %s\n", L3DS_TextureExtension);
          loadmode = LOAD_NORMAL;
          argnum++;
         }

     if (!strcmp (argv[argnum], "-m"))
       if (argc > argnum)
         {
          strcpy (pakfile, argv[argnum+1]);
          printf ("Pak file: %s\n", pakfile);
          argnum++;
         }
     argnum++;
    }


#if !defined (WIN32)
  while (kbhit ())                       /* Clear the keyboard buffer */
    getch ();

  #if defined (__3DFX__) || defined (__MYST__)
    printf ("Use 3D hardware? (y/n)\n");
    device = getch ();
  #endif

  if (device == 'y' || device == 'Y')
    device = DEVICE_ANY;
  else
    {
     if (force_vga)
       device = DEVICE_VGA;
     else
       device = DEVICE_SVGA;
    }
#else /* We are in windows */

  /* First check for Direct3D/DDraw */
  #if defined(MSDD) || defined(WTDD)
    device = MessageBox(NULL, "Use Direct3D Hardware?", "Power Render Initialization", MB_YESNO);
    if (device == IDYES)
      {
       device = DEVICE_D3D;
       PR_Settings.Hardware = 1;
      }
    else
      { 
       device = DEVICE_SVGA;
       PR_Settings.Hardware = 0;
      }
 
  /* Next look for specific hardware */
  #elif defined (MSGLIDE) || defined (WTGLIDE)
    device = DEVICE_3DFX;
  #elif defined (MSMYST) || defined (WTMYST)
    device = DEVICE_MYST;
  #endif
#endif

  PRGUI_GoStartPath ();

  PR_Initialize (7500);
  
#if !defined (WIN32)
  vga256 ();
  minit ();
#endif

  InitializeDevices ();
  InitializeProgram ();
  InitializeLights ();

  /* Initialize the graphics mode */
  current_mode = -1;
  while (!SetVideoMode ());

  if (PR_VideoModes[current_mode].pages > 1)
    flip_delay = 1;

  PR_SetTextureFormat (TEXTURE_XRAY);

  PR_Settings.HardwareMipmaps = MIPMAP_ALL_LEVELS;
  
  PRGUI_GoUserPath ();
  LoadFile (argv[1], loadmode);
  
  /* Get colors closest to white and black */
  PR_BackgroundColor = PR_ClosestColor (24, 24, 24, global_palette);
  brightcolor = PR_ClosestColor (63, 63, 63, global_palette);
  greycolor = PR_ClosestColor (24, 24, 24, global_palette);

  newcam = PR_GetFirstCamera ();
  if (newcam == NULL)
    {
     /* Initialize the camera */
     newcam = PR_AllocCamera ();
     PR_InitializeCamera (newcam);
     PR_PositionCameraSource (newcam, 0, 0, test->bbox.radius * 2);
     newcam->rotation.y = 0;
     PR_SetCameraMode (newcam, CAMFLAG_ANGLE_BASED);
    }
  else
    {
     PR_AttachCameraEntity (newcam, obj1);
    }

  /* Initialize the input devices */
  installkbd ();
  winittimer ();
  wstarttimer (timerproc, TICKS(60));
  msetxy (160, 100);

  PR_SetInputDevice (INPUT_MOUSE);
  PR_SetInputModel (MODEL_INSPECT);
  PR_SetInputEntity (NO_ENTITY, NO_SEGMENT);
  PR_SetInputCamera (newcam);

  TRANSLATE_SPEED    = test->bbox.radius / (speeddiv*10);
  MAX_TRANSLATE_SPEED= test->bbox.radius / speeddiv;
  TRANSLATE_DRAG     = test->bbox.radius / (speeddiv*5);


  /* Number of perspective texture subdivisions */
#if !defined (MSGLIDE) && !defined (WTGLIDE)
  PR_SetPerspectiveDivisions (TEXTURE_DIVISION_16);
#endif



  PR_OpenScreen (PR_BACKBUFFER);

  //gui_font = NULL;
  PRGFX_SetTextForeground (PRGFX_MakeColor (63,63,63));
  PRGFX_SetTextBackground (PRGFX_MakeColor (32,32,32));
  PRGFX_SetTextTransparent (TEXTFGBG);

  sky_color = PRGFX_MakeColor (0, 0, 0);
//  PR_SetFogState (TRUE);
//  PR_SetFogRange (10000, 40000);
//  PR_SetFogColor (sky_color);


  /* main program loop */
  while (!kbdon[KEY_ESC])
    {
     #ifdef WIN32
      UpdateMessages ();
     #endif

     UserInput ();

     #if defined (MSDD) || defined (WTDD)
      if (PR_LostSurface)
        PR_RestoreAll ();
     #endif
	  
     PRGFX_SetColor (sky_color);
     PR_OpenScreen (PR_BACKBUFFER);

#if !defined (MSGLIDE) && !defined (WTGLIDE)
     if (PR_Settings.FrontToBack && device != DEVICE_3DFX)
       PR_ResetHoles ();
     else
#endif
     PRGFX_ClearScreen ();

     PR_NewFrame ();                         /* Begin a new frame */

     PR_SetLightPosition (&userlights, 0,
                          newcam->source.x,
                          newcam->source.y,
                          newcam->source.z);
     PR_AddLightsToScene (&userlights);

     PR_SetActiveCamera (newcam);

     PRGFX_Clip (active_viewport.topx,
                 active_viewport.topy,
                 active_viewport.bottomx,
                 active_viewport.bottomy);

     PR_RotateEntity (obj1, dx, dy, dz);

     if (obj1->shape->num_frames > 0)
       {
        //frame += kf_ticks;
        frame++;
        kf_ticks = 0;
        if (frame >= obj1->shape->num_frames)
          frame = 0;
        PR_AnimateEntity (obj1, frame);
       }

     if (newcam->num_frames > 0)
       {
        camframe++;
        if (camframe >= newcam->num_frames)
          camframe = 0;
        PR_AnimateCamera (newcam, camframe);
       }

     PR_TransformEntity (obj1);
     PR_RenderEntity (obj1);

     PR_RenderFrame ();

     if (device != DEVICE_3DFX)
       {

        /* If we are using front to back, we have to fill the empty
           spans with a color */
#if !defined (MSGLIDE) && !defined (WTGLIDE)
        if (PR_Settings.FrontToBack)
          PR_FillHoles (PR_BackgroundColor);
#endif
       }

     /* Calculate the frame rate */
     if (ticks > 60)
       {
        framerate = updates;
        ticks = 0;
        updates = 0;
       }
     else
       updates++; 


     PRGUI_printf (10, 10, "%i", framerate);

     PR_Flip (flip_delay);

     if (mdl_mode)
       {
        morphvalue += morphstep * morphplay;
        if (morphvalue > 1)
          {
           morphvalue = 0;

           previous_frame = frame_number;
           frame_number++;
           if (frame_number >= testmdl->numframes)
             frame_number = 0;
          }
        PR_MorphWithNormals (frame_list[previous_frame],
                             frame_list[frame_number],
                             test->segment_list->vertex_list,
                             test->segment_list->num_vertices, morphvalue);
       }

   }

  uninstallkbd ();
#if !defined (WIN32)
  mdeinit ();
#endif

  wstoptimer ();
  wdonetimer ();


#if !defined (WIN32)
  wsetmode (3);
#endif

}




