/*
==============================================================================
		      WordUp Graphics Toolkit Version 5.0                     
			     Demonstration Program 54                         
									      
 This program demonstrates how to use the 3D library along with the polygon  
 routines.  It rotates a pyramid object using Gouraud shading.               
									      
 *** PROJECT ***                                                             
 This program requires the file WGT5_WC.LIB, and WGT3D_WC.LIB to be linked.  
									      
 *** DATA FILES ***                                                          
 You must have the MYTRI.3D and 3DDEMO.PAL files in your executable dir.     
									      
==============================================================================
*/

#include <conio.h>
#include <time.h>
#include <ctype.h>
#include <wgt5.h>
#include <wgt3d.h>

/*
This is an interactive program and requires user to use keys to rotate 
the object:
 Keys used (on keypad):
     7 8 : decrease/increase x rotation
     4 5 : decrease/increase y rotation
     1 2 : decrease/increase z rotation
     - + : decrease/increase distance
       q : quits program
*/

#define HOLLOW 0        /* These are the types of polygons we can draw */
#define SOLID 1         
#define GOURAUD 2       /* Lighted shading (not used in this demo) */
#define SETGOURAUD 3    /* Fixed shading (vertices has fixed colors) */

#define MAXPOINTS 100   /* Maximum number of points in the object */

void loadobject (char *);  /* Load a custom 3D object file format */
void update_screen (void); /* Uses a dirty rectangle approach to copy
			      the smallest area needed to update the
			      screen. */
void calculate_depth (void); /* Finds a depth value for each polygon, used
				to sort the polygons into a back to front
				order. */
void sort_polys (void);      /* Sorts the polygons by their depth value */   
void draw_polys (void);      /* Draws the polygons from back to front */
void clear_last (void);      /* Erases the previous frame */
void get_response (void);    /* Reads keys and responds accordingly */

point3d mypoints[9];         /* Contains original points of the object */
point3d finp[9];             /* Holds all point after they have bee
				rotated with wrotatepoints. */

tpolypoint mypoly[4];          /* Holds a 4 vertex polygon */

short curx = 0;                /* Current rotation values */
short cury = 180;
short curz = 180;

int  maxpoly = 300;            /* Holds the number of polygons in the object */   
char c;                        /* user's response from keyboard */

int sortpoly[MAXPOINTS];     /* The sorted polygon list */
int ztotal[MAXPOINTS];       /* Depth values for each polygon */
int faces[200][4];           /* Polygon face array.  This holds 4 vertices
				for each polygon.  All points are rotated
				and then the polygons are drawn from the
				resulting points using these vertices.
				This way polygons that share vertices will
				not have the same point rotated twice. */
int facetype[200];           /* Holds the type of polygon for each face */

block other;                   /* A background screen */
color pal[256];              

typedef struct {               /* Dirty rectangle structure */
  short x1;
  short y1;
  short x2;                /* Used to keep track of what portion of the */
  short y2;                /* screen has been changed, so we can update */
} rect;                    /* the least amount of video memory */

rect lastrect;             /* Dirty rectangle for the last frame */
rect thisrect;             /* Dirty rectangle for current frame */

short xdir = 1;    /* Rotation directions (1, 0, -1) */
short ydir = 1;
short zdir = 1;
short xpos = 0;    /* Object positions */
short ypos = 0;
short zpos = 0;
/* These are arrays to prepare for multiple objects in the next demo */


/* Loads a custom 3D object file format. */
/* WGT 3D Object File Format
The following is a description of the file format used in the 3D demos.

The first line contains the identification string
"WGT 3D Object File"

Then the number of points in the file is on a separate line.
Each point is then stated, with the following format:
x_coord y_coord z_coord sx_value sy_value

With hollow and solid polygons, you can use sx as the color of the polygon.
With Gouraud shading, sx is the color at that point.
With Texture mapping, sx,sy is the offset into the texture bitmap.

A blank line follows, and then the number of faces is on a new line.
Each face is described as a 4 point sequence, using the points stated above.
Point numbers start at 0.
On the same line, a polygon type number is given.
0 means hollow
1 means solid
2 means lighted Gouraud (brighter closer to you)
3 means set Gouraud (uses value stored by the vertices above)
4 means texture mapped (not used in this demo)

For example, to make a face with points 0-3, with set Gouraud shading,
the line would be
0 1 2 3 3

Here is an example image with one polygon:

WGT 3D Object File
4
-25 -25 50 140 0
25 -25 50 195 0
25 25 50 140 0
-25 25 50 195 0

1
0 1 2 3 3
*/
void loadobject (char *fname)
{
  short i;
  short e;
  FILE *objfile;
  char blank[80];

  objfile = fopen (fname, "rt");

  fscanf (objfile, "%s %s %s %s\n", blank, blank, blank, blank);

  /* Number of points in this object */
  fscanf (objfile, "%d\n", &maxpoly);


  /* Write the points */
  for (i = 0; i < maxpoly; i++)
    fscanf (objfile, "%d %d %d %d %d\n", &mypoints[i].x, &mypoints[i].y, &mypoints[i].z,
	    &mypoints[i].sx, &mypoints[i].sy);

  /* Number of faces in this object */
  fscanf (objfile, "\n%d\n", &maxpoly);
  for (i = 0; i < maxpoly; i++)
  {
    for (e = 0; e < 4; e++)
       fscanf (objfile, "%d ", &faces[i][e]);
    fscanf (objfile, "%d\n", &facetype[i]);
  }
  fclose (objfile);
}


void clear_last (void)
{
  /* Do clipping for smallest area update */
  if (lastrect.x1 < 0)
    lastrect.x1 = 0;
  if (lastrect.x2 > 319)
    lastrect.x2 = 319;
  if (lastrect.y1 < 0)
    lastrect.y1 = 0;
  if (lastrect.y2 > 199)
    lastrect.y2 = 199;
  wsetcolor (0);
  wbar (lastrect.x1, lastrect.y1, lastrect.x2, lastrect.y2);
  /* Clear out the area that was drawn in last frame */
}


void update_screen (void)
{
  if (lastrect.x1 > thisrect.x1)
    lastrect.x1 = thisrect.x1;
  if (lastrect.x2 < thisrect.x2)
    lastrect.x2 = thisrect.x2;
  if (lastrect.y1 > thisrect.y1)
    lastrect.y1 = thisrect.y1;
  if (lastrect.y2 < thisrect.y2)
    lastrect.y2 = thisrect.y2;
  /* See if the previous frame was larger in any direction. If it is, enlarge
     the area so it will copy black over the previous frame. */

  /* Do clipping */
  if (lastrect.x1 < 0)
    lastrect.x1 = 0;
  if (lastrect.x2 > 319)
    lastrect.x2 = 319;
  if (lastrect.y1 < 0)
    lastrect.y1 = 0;
  if (lastrect.y2 > 199)
    lastrect.y2 = 199;

  wcopyscreen(lastrect.x1, lastrect.y1, lastrect.x2, lastrect.y2, other,
	      lastrect.x1, lastrect.y1, NULL);
  /* Copy from our second page to the visual page. */

  lastrect.x1 = thisrect.x1;
  lastrect.y1 = thisrect.y1;
  lastrect.x2 = thisrect.x2;
  lastrect.y2 = thisrect.y2;
  /* Make the last rectangle = current rectangle */
}



void calculate_depth (void)
{
  short d;
  short e;
  short points;    /* Total number of unique points */

  for (d = 0; d < maxpoly; d++)
  {
    sortpoly[d] = d;
    ztotal[d] = 0;
    ztotal[d] += finp[faces[d][0]].z;
    points = 1;
    for (e = 1; e < 4; e++)
      if (faces[d][e - 1] != faces[d][e]) /* Don't count duplicate points */
      {
	points++;
	ztotal[d] += finp[faces[d][e]].z;
      }
    ztotal[d] /= points;
  }
}


void sort_polys(void)
{
  short d;
  short e;
  short temp;

  for (d = 0; d < maxpoly - 1; d++)
    for (e = d + 1; e < maxpoly; e++)
    {
      if (ztotal[sortpoly[e]] > ztotal[sortpoly[d]])
      {
	temp = sortpoly[e];
	sortpoly[e] = sortpoly[d];
	sortpoly[d] = temp;
      }
    }
}



void draw_polys(void)
{
  short d;
  short e;
  short cp;

  for (d = 0; d < maxpoly; d++)
  {
    cp = sortpoly[d];
    for (e = 0; e < 4; e++)
    {
      mypoly[e].x = finp[faces[cp][e]].x;
      mypoly[e].y = finp[faces[cp][e]].y;
      if (facetype[cp] == SETGOURAUD)
	mypoly[e].sx = mypoints[faces[cp][e]].sx;
      else
      {
	mypoly[e].sx = 255 - (finp[faces[cp][e]].z + 80);
	if (mypoly[e].sx < 2)
	  mypoly[e].sx = 2;
	if (mypoly[e].sx > 250)
	  mypoly[e].sx = 250;
      }

      if (mypoly[e].x < thisrect.x1)
	thisrect.x1 = mypoly[e].x;
      if (mypoly[e].x > thisrect.x2)
	thisrect.x2 = mypoly[e].x;
      if (mypoly[e].y < thisrect.y1)
	thisrect.y1 = mypoly[e].y;
      if (mypoly[e].y > thisrect.y2)
	thisrect.y2 = mypoly[e].y;
      /* See if the polygon is larger than the current area to update.
	 If it is, enlarge the area so all polygons fit inside. */
    }
    wsetcolor (mypoly[0].sx);
    if (facetype[cp] == HOLLOW)
      whollowpoly (mypoly, 4, 0, 0,CLOSED_POLY);
    else if (facetype[cp] == SOLID)
      wsolidpoly (mypoly, 4, 0, 0,NULL);
    else if ((facetype[cp] == GOURAUD) || (facetype[cp] == SETGOURAUD))
      wgouraudpoly (mypoly, 4, 0, 0);
  }
}



void get_response (void)
{
  if (kbhit ())
    c = toupper (getch ());

  switch (c)
   {
   case '+': origin_z += 50;
	     if (origin_z > 3000) 
	       origin_z = 3000;
	     break;
   case '-': origin_z -= 50;
	     if (origin_z < 200) 
	       origin_z = 200;
	     break;
   case '8': curx += 5;
	     break;
   case '7': curx -= 5;      
	     break;
   case '5': cury += 5;      
	     break;
   case '4': cury -= 5;      
	     break;
   case '2': curz += 5;      
	     break;
   case '1': curz -= 5;      
	     break;
  }

  if (curx > 359) 
    curx -= 359;
  if (cury > 359) 
    cury -= 359;
  if (curz > 359) 
    curz -= 359;
  if (curx < 0) 
    curx += 359;
  if (cury < 0) 
    cury += 359;
  if (curz < 0) 
    curz += 359;
}



void bounce_object (void)
{
  xpos += xdir * 4;
  ypos += ydir * 4;
  zpos += zdir * 4;

  if (xpos > 400)
    xdir = -1;
  if (ypos > 200)
    ydir = -1;
  if (zpos > -100)
    zdir = -1;

  if (xpos < -400)
    xdir = 1;
  if (ypos < -200)
    ydir = 1;
  if (zpos < -900)
    zdir = 1;

  move_x = xpos;
  move_y = ypos;
  move_z = zpos;
}



void main(void)
{
  short oldmode;

  if ( !vgadetected () )
  {
    printf ("Error - VGA card required for any WGT program.\n");
    exit (0);
  }
  printf ("WGT Example #54\n\n");
  printf ("A simple 3D object is shown on the screen.\n");
  printf ("You can use the following keys:.\n");
  printf ("+  Move backwards\n");
  printf ("-  Move forwards\n");
  printf ("8  Increase X rotation\n");
  printf ("7  Decrease X rotation\n");
  printf ("5  Increase Y rotation\n");
  printf ("4  Decrease Y rotation\n");
  printf ("2  Increase Z rotation\n");
  printf ("1  Decrease Z rotation\n");
  printf ("q  Quits the program\n");
  printf ("Turn NUMLOCK on to use the numeric keypad.\n");
  printf ("\n\nPress any key to continue.\n");
  getch();
  
  
  oldmode = wgetmode ();
  vga256 ();

  winit3d ();
  winitpoly (WGT_SYS.yres);

  other = wnewblock (0, 0, 319, 199);
  wloadpalette ("3ddemo.pal", pal);
  wsetpalette (0, 255, pal);

  maxpoly = 0;
  loadobject ("mytri.3d");
  wsetscreen (other);

  /* Main loop */
  do {
    thisrect.x1 = 319;
    thisrect.y1 = 199;
    thisrect.x2 = 0;
    thisrect.y2 = 0;

    clear_last ();

    wsetrotation (curx, cury, curz);

    bounce_object ();
    wrotatepoints (&mypoints, &finp, 8);

    calculate_depth ();
    sort_polys ();
    draw_polys ();
    get_response ();
    wretrace ();
    update_screen ();
  } while (c != 'Q');
  wdeinitpoly ();
  wsetmode (oldmode);
}
