/*
==============================================================================
		      WordUp Graphics Toolkit Version 5.0                     
			     Demonstration Program 55                         
									      
 This program rotates 3 individual objects at the same time and uses Gouraud 
 shading to display them.  It is a more complex version of the first 3D demo 
 since each object has a number of independant variables, and the area each  
 occupies on the screen is dealt with for each object for maximum speed.     
									      
 *** PROJECT ***                                                             
 This program requires the file WGT5_WC.LIB, and WGT3D_WC.LIB to be linked.  
									      
 *** DATA FILES ***                                                          
 You must have the MYTRI.3D, MYCUBE.3D, MYCYL.3D, and 3DDEMO.PAL files in    
 your executable dir.                                                        
							   WATCOM C++ VERSION 
==============================================================================
*/
#include <conio.h>
#include <math.h>
#include <stdlib.h>
#include <dos.h>
#include <wgt3d.h>
#include <wgt5.h>

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

#define HOLLOW 0
#define SOLID 1
#define GOURAUD 2
#define SETGOURAUD 3

/* Routines used in this program */
void loadobject(char *,point3d *,short);
void clear_last(void);
void update_screen(void);
void calculate_depth(void);
void sort_polys(void);
void draw_polys(void);
void rotate_objects(void);


point3d mycube[9];              /* Store data points for each object */
point3d mytri[9];
point3d mycyl[9];

point3d finp[90];               /* Stores the final, rotated points */
short numpts[3];                  /* Number of points in each object */
tpolypoint mypoly[4];           /* Used to draw one polygon at a time */

short rendermethod;
char c;

short sortpoly[100];
short ztotal[100];

short maxpoly = 300;
short totalpoly = 0;
short rot;

short faces[200][4];
short facetype[200];
short faceowner[200];

block other;
color pal[256];
short d, e;

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


typedef struct
{
  rect lastrect, thisrect;
  short addx, addy, addz;
  short curx, cury, curz;
} object3d;

object3d myobject[3];

short xdir[3] = {1, -1, 1};
short ydir[3] = {1, 1, -1};
short zdir[3] = {-1, 1, 1};
short xpos[3] = {0, 200, -200};
short ypos[3] = {0, 0, 0};
short zpos[3] = {0, 0, 0};

int updates, timer;

void loadobject (char *fname, point3d *mypoints, short obj)
/* Loads a 3D object file */
{
  short i;
  FILE *objfile;
  char blank[80];
  short addnum = 0;

  for (i = 0; i < obj; i++)
    addnum += numpts[i];

  objfile = fopen (fname, "rt");

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

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

  numpts[obj] = maxpoly;

  /* Load the points */
  for (i = 0; i < maxpoly; i++)
    fscanf(objfile, "%hd %hd %hd %hd %hd\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%hd\n", &maxpoly);
  for (i = 0; i < maxpoly; i++)
  {
    for (e = 0; e < 4; e++)
    {
      fscanf (objfile, "%hd ", &faces[i + totalpoly][e]);
      faces[i + totalpoly][e] += addnum;
    }
    fscanf (objfile, "%hd\n", &facetype[i + totalpoly]);
    faceowner[i + totalpoly] = obj;
  }
  totalpoly += maxpoly;
  fclose (objfile);
}


void clear_last (void)
/* Clears out the last regions of the screen where the objects were */
{
  short i;
  rect *lr;

  for (i = 0; i < 3; i++)
  {
    lr = &myobject[i].lastrect;

    /* Do clipping for smallest area update */
    if (lr->x1 < 0)
      lr->x1 = 0;
    if (lr->x2 > 319)
      lr->x2 = 319;
    if (lr->y1 < 0)
      lr->y1 = 0;
    if (lr->y2 > 199)
      lr->y2 = 199;

    wsetcolor (0);
    /* Change the previous line to the one below in order to see the updating
       rectangular regions */

    wbar (lr->x1, lr->y1, lr->x2, lr->y2);
    /* Clear out the area that was drawn in last frame */
  }
}


void update_screen (void)
/* Copy the regions of the background screen onto the visual screen */
{
  rect *tr;
  rect *lr;
  short i;

  for (i = 0; i < 3; i++)
  {
    tr = &myobject[i].thisrect;
    lr = &myobject[i].lastrect;

    if (lr->x1 > tr->x1)
      lr->x1 = tr->x1;
    if (lr->x2 < tr->x2)
      lr->x2 = tr->x2;
    if (lr->y1 > tr->y1)
      lr->y1 = tr->y1;
    if (lr->y2 < tr->y2)
      lr->y2 = tr->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 (lr->x1 < 0)
      lr->x1 = 0;
    if (lr->x2 > 319)
      lr->x2 = 319;
    if (lr->y1 < 0)
      lr->y1 = 0;
    if (lr->y2 > 199)
      lr->y2 = 199;

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

    lr->x1 = tr->x1;
    lr->y1 = tr->y1;
    lr->x2 = tr->x2;
    lr->y2 = tr->y2;

    /* Make the last rectangle = current rectangle */
  }
}


void calculate_depth (void)
/* Prepare the ztotal array for sorting polygons by z value */
{
  short d, e;

  for (d = 0; d < maxpoly; d++)
  {
    sortpoly[d] = d;
    ztotal[d] = 0;
    for (e = 0; e < 4; e++)
      ztotal[d] += finp[faces[d][e]].z;
    ztotal[d] /= 4;
  }
}


void sort_polys (void)
/* Sort those polys using slow bubble sort */
/* There are only about 20 polys max so it probably doesn't matter what
   sorting method you use here. */
{
  short d, e, 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 check_rectangle (short obj, short x, short y)
/* Enlarge the update rectangle if needed */
{
  rect *tr;

  tr = &myobject[obj].thisrect;

  if (x < tr->x1)
    tr->x1 = x;
  if (x > tr->x2)
    tr->x2 = x;

  if (y < tr->y1)
    tr->y1 = y;
  if (y > tr->y2)
    tr->y2 = y;
  /* See if the polygon is larger than the current area to update.
     If it is, enlarge the area so all polygons fit inside. */
}


void draw_polys (void)
/* Draw all of the polygons in order, using our rendering method */
{
  short d, 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] != GOURAUD)
      {
	/* ARGH! This is very inflexible this way! */
	if (cp < 6)
	  mypoly[e].sx = mycube[faces[cp][e]].sx;
	else if (cp < 11)
	  mypoly[e].sx = mytri[faces[cp][e] - 8].sx;
	else mypoly[e].sx = mycyl[faces[cp][e] - 13].sx;
      }
	else
      {
	mypoly[e].sx = (255 - finp[faces[cp][e]].z / 7);
	if (mypoly[e].sx < 2) 
	  mypoly[e].sx = 2;
	if (mypoly[e].sx > 250) 
	  mypoly[e].sx = 250;
      }
      check_rectangle (faceowner[cp], mypoly[e].x, mypoly[e].y);
    }

    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);
    if ((facetype[cp] == GOURAUD) || (facetype[cp] == SETGOURAUD))
      wgouraudpoly (mypoly, 4, 0, 0);
  }
}


void rotate_objects (void)
/* Make objects spin and rotate all the points */
{
  short i;

  for (i = 0; i < 3; i++)
  {
    myobject[i].curx += myobject[i].addx;
    myobject[i].cury += myobject[i].addy;
    myobject[i].curz += myobject[i].addz;

    if (myobject[i].curx > 359) 
      myobject[i].curx -= 360;
    if (myobject[i].cury > 359) 
      myobject[i].cury -= 360;
    if (myobject[i].curz > 359) 
      myobject[i].curz -= 360;
    if (myobject[i].curx < 0) 
      myobject[i].curx += 360;
    if (myobject[i].cury < 0) 
      myobject[i].cury += 360;
    if (myobject[i].curz < 0) 
      myobject[i].curz += 360;

    wsetrotation (myobject[i].curx, myobject[i].cury, myobject[i].curz);

    move_x = xpos[i];
    move_y = ypos[i];
    move_z = zpos[i];

    switch (i)
    {
      case 0:  wrotatepoints (mycube, finp, numpts[0]); 
	       break;
      case 1:  wrotatepoints (mytri, &finp[numpts[0]], numpts[1]); 
	       break;
      case 2:  wrotatepoints (mycyl, &finp[numpts[0]+numpts[1]], numpts[2]);
	       break;
    }
  }
}


void bounce_object (void)
/* Make objects bounce in 3D space.  When they change direction, they also
   change their rotational motion */
{
  short i;

  for (i = 0; i < 3; i++)
  {
    xpos[i] += xdir[i] * 4;
    ypos[i] += ydir[i] * 4;
    zpos[i] += zdir[i] * 4;

    if (xpos[i] > 400)
    {
      xdir[i] = -1;
      myobject[i].addx = -4;
    }
    if (ypos[i] > 200)
    {
      ydir[i] = -1;
      myobject[i].addy = -4;
    }
    if (zpos[i] > -100)
    {
      zdir[i] = -1;
      myobject[i].addz = -4;
    }
    if (xpos[i] < -400)
    {
      xdir[i] = 1;
      myobject[i].addx = 4;
    }
    if (ypos[i] < -200)
    {
      ydir[i] = 1;
      myobject[i].addy = 4;
    }
    if (zpos[i] < -1900)
    {
      zdir[i] = 1;
      myobject[i].addz = 4;
    }
  }
}


void invert_rectangles (void)
{
  short i;

  for (i = 0; i < 3; i++)
  {
    myobject[i].thisrect.x1 = 319;
    myobject[i].thisrect.y1 = 199;
    myobject[i].thisrect.x2 = 0;
    myobject[i].thisrect.y2 = 0;
  }
}


void timerctr (void)
{
  timer++;  
}


void main (void)
{
  short i;
  short oldmode;

  oldmode = wgetmode ();
  
  if ( !vgadetected () )
  {
    printf ("Error - VGA card required for any WGT program.\n");
    exit (0);
  }
  printf ("WGT Example #55\n\n");
  
  printf ("3d Object Demo #2\n\n");
  printf ("Choose your rendering type:\n");
  printf ("0: Wireframe\n");
  printf ("1: Solid\n");
  printf ("2: Gouraud\n");
  printf ("3: Set Gouraud\n");

  scanf ("%i", &rendermethod);

  vga256 ();

  winit3d ();
  winitpoly (WGT_SYS.yres);

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

  if (rendermethod == GOURAUD)   /* Lighted Gouraud, so create a palette with
				    gradually changing colors. */
  {
   for (i = 0; i < 64; i++)
     wsetrgb (i, 0, 0, i, pal);
   for (i = 64; i < 256; i++)
     wsetrgb (i, (i - 64) / 2, (i - 64) / 2, 64, pal);
  }

  wsetpalette(0, 255, pal);

  maxpoly = 0;
  loadobject ("mycube.3d", mycube, 0);
  loadobject ("mytri.3d", mytri, 1);
  loadobject ("mycyl.3d", mycyl, 2);

  for (i = 0; i < totalpoly; i++)
    facetype[i] = rendermethod;

  for (i = 0; i < 3; i++)
  {
    xpos[i] = ((rand () % 400) - 200);
    ypos[i] = ((rand () % 200) - 100);
    zpos[i] = -((rand () % 1800) + 100);
  }

  wsetscreen (other);

  maxpoly = totalpoly;

  /* Main loop */
  timer = 0;
  winittimer ();
  wstarttimer (timerctr, TICKS(100));
  do {
    invert_rectangles ();

    clear_last ();

    bounce_object ();
    rotate_objects ();
    calculate_depth ();
    sort_polys ();
    draw_polys ();
    update_screen ();
    updates++;
  } while (!kbhit ());
  wstoptimer ();
  wdonetimer ();
  // get final time
  
  wdeinitpoly ();
  wsetmode (oldmode);
  
  printf ("\nElapsed time (hundredths of seconds): %i", timer);
  printf ("\n# updates: %d", updates);
  printf ("\nAverage frame rate: %2.2f frames/sec\n", (float)updates / (float)(timer / 100));
}


