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

#include <pr.h>

#ifdef __3DFX__
#include <pr3dfx.h>
#include <glide.h>
#endif

#ifdef WIN32
#include <winutil.h>
#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 */


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

PR_OBJECT *morph_object;    /* The shape loaded */
PR_ENTITY *morph_entity;    /* 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 nextframe = 0;
PR_VERTEX *frame_list[6];
PR_REAL morphvalue = 0;
PR_REAL morphstep = 0.2;






/* ---------------------------------------------------------------------- */
/* Timer Interrupt */
/* ---------------------------------------------------------------------- */
void timerproc (void)
{
  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)
  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 */
    }


  /* 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    = morph_object->bbox.radius / (speeddiv*10);
     MAX_TRANSLATE_SPEED= morph_object->bbox.radius / speeddiv;
     TRANSLATE_DRAG     = morph_object->bbox.radius / (speeddiv*5);
    }
  else if (kbdon[KEY_GREY_PLUS])
    {
     speeddiv -= 5;
     if (speeddiv < 2)
       speeddiv = 1;
     TRANSLATE_SPEED    = morph_object->bbox.radius / (speeddiv*10);
     MAX_TRANSLATE_SPEED= morph_object->bbox.radius / speeddiv;
     TRANSLATE_DRAG     = morph_object->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 LoadVerts (char *filename, PR_DWORD frame)
/* Loads a vertex list into an array. Assumes the object has only 1
   segment */
{
FILE *mesh;
PR_DWORD numverts;
PR_DWORD numsegs;

  mesh = fopen (filename, "rb");
  if (mesh == NULL)
     PR_FatalError ("Could not open mesh file\n", "ex09");

  fread (&numsegs, sizeof (PR_DWORD), 1, mesh);

//  for (segnum = 0; segnum < obj->num_segments; segnum++)
/* assume 1 segment */

  fread (&numverts, sizeof (PR_DWORD), 1, mesh);
  frame_list[frame] = malloc (sizeof (PR_VERTEX) * numverts);
  fread (frame_list[frame], sizeof (PR_VERTEX) * numverts, 1, mesh);

  fclose (mesh);
}




void LoadFiles (void)
{
  morph_object = PR_LoadPRO ("morph1.pro", LOAD_NORMAL);
  
  #if !defined(MSGLIDE) && !defined (WTGLIDE)
     wsetpalette (0, 255, global_palette);
  #endif

  LoadVerts ("morph1.msh", 0);
  LoadVerts ("morph2.msh", 1);
  LoadVerts ("morph3.msh", 2);
  LoadVerts ("morph4.msh", 3);
  LoadVerts ("morph5.msh", 4);
  LoadVerts ("morph1.msh", 5);  /* Morph back to the original */

  morph_entity = PR_CreateEntity (morph_object, "Morpher");
  PR_ScaleEntityAbs (morph_entity, 1, 1, 1);
}







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

#if !defined (MSGLIDE) && !defined (WTGLIDE)
  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 __3DFX__
  if (device == DEVICE_3DFX)
    {
     PR_Initialize3Dfx ();
     atexit (PR_Shutdown3Dfx);
    }
#endif

#if !defined (MSGLIDE) && !defined (WTGLIDE)
  if (device == DEVICE_SVGA)
    {
     PR_InitializeSVGA ();
     atexit (PR_ShutdownSVGA);
    }
  else if (device == DEVICE_VGA)
    {
     PR_InitializeVGA ();
    }
#endif
}


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

#if defined (MSGLIDE) || defined (WTGLIDE)
  device = DEVICE_3DFX;
#else
  device = DEVICE_VGA;
#endif


  loadmode = LOAD_IGNORE_TEXTURES;

  PRGUI_InitPath (argv[0]);
  PRGUI_SetUserPath ();

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

#ifndef WIN32
  while (kbhit ())                       /* Clear the keyboard buffer */
    getch ();

  if (device != DEVICE_3DFX)
    {
   #ifdef __3DFX__
     printf ("Use 3Dfx? (y/n)\n");
     device = getch ();

     if (device == 'y' || device == 'Y')
       {
        device = DEVICE_3DFX;
       }
     else
  #endif
       {
        if (force_vga)
          device = DEVICE_VGA;
        else
          device = DEVICE_SVGA;
       }
    }
#endif

  PRGUI_GoStartPath ();
  PR_Initialize (7500);

#ifndef 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_Settings.HardwareMipmaps = MIPMAP_ALL_LEVELS;

  PRGUI_GoUserPath ();
  LoadFiles ();

  /* Get colors closest to white and black */
  PR_BackgroundColor = PR_ClosestColor (0, 0, 0, global_palette);
  brightcolor = PR_ClosestColor (63, 63, 63, global_palette);
  greycolor = PR_ClosestColor (24, 24, 24, global_palette);

  /* Initialize the camera */
  newcam = PR_AllocCamera ();
  PR_InitializeCamera (newcam);
  PR_PositionCameraSource (newcam, 0, 0, morph_object->bbox.radius * 2);
  newcam->rotation.y = 0;
  PR_SetCameraMode (newcam, CAMFLAG_ANGLE_BASED);

  /* 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    = morph_object->bbox.radius / (speeddiv*10);
  MAX_TRANSLATE_SPEED= morph_object->bbox.radius / speeddiv;
  TRANSLATE_DRAG     = morph_object->bbox.radius / (speeddiv*5);


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

  PR_OpenScreen (PR_BACKBUFFER);

  PRGFX_SetTextForeground (PRGFX_MakeColor (63,63,63));
  PRGFX_SetTextBackground (PRGFX_MakeColor (32,32,32));
  PRGFX_SetTextTransparent (TEXTFGBG);


  nextframe = 1;  /* Morph from frame 0 to frame 1 */

  /* main program loop */
  while (!kbdon[KEY_ESC])
    {
     #ifdef WIN32
       UpdateMessages ();
     #endif
          
     UserInput ();
     PR_NewFrame ();                         /* Begin a new frame */

     PRGFX_SetColor (PR_BackgroundColor);
     PR_OpenScreen (PR_BACKBUFFER);

     PRGFX_ClearScreen ();

     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 (morph_entity, dx, dy, dz);
     PR_TransformEntity (morph_entity);
     PR_RenderEntity (morph_entity);

     PR_RenderFrame ();

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


     /* Now the morphing part */
     morphvalue += morphstep;
     if (morphvalue > 1)
       {
        morphvalue = 0;

        nextframe++;
        if (nextframe > 5)
          nextframe = 1;
       }
      PR_MorphWithNormals (frame_list[nextframe - 1],
                           frame_list[nextframe],
                           morph_object->segment_list->vertex_list,
                           morph_object->segment_list->num_vertices,
                           morphvalue);

    }

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

  wstoptimer ();
  wdonetimer ();


#ifndef WIN32
  wsetmode (3);
#endif
}




