// PXDTUT4 - C++ Pmode version
#include <iostream.h>
#include <dos.h>
#include <conio.h>
#include <math.h>
#include <stdlib.h>


/* Some constants for our 3d engine */
#define Num_of_points 8
#define Num_of_faces 6
#define YTopClip 0
#define YBotClip 200+
#define Xofs 160
#define Yofs 100
#define Zeye -250

// some type definitions
typedef unsigned char UCHAR;

typedef struct
           {
            int X,Y,Z;
            } PointT;

typedef struct
           {
            float X,Y,Z;
            } RealPointT;

typedef struct
           {
            int X,Y;
            } ScrPointT;

typedef struct
           {
            int P1,P2,P3,P4;
            char color;
            } FaceT;



 int lookup[360+1][2];                     // Sin/Cos lookup table

 int leftpoly[200];
 int rightpoly[200];                       // Buffers for polygon drawing
 char polydata[200][4];                    // buffer for gouraud and texture


 PointT     Baseobj[Num_of_points+1];      // Original Object
 FaceT      Faces[Num_of_faces+1];         // Data for how faces are defined
 PointT     Points[Num_of_points+1];       // Rotated 3d object
 ScrPointT  Translated[Num_of_points+1];   // The 2d-screenpoints for drawing
 int        Centers[Num_of_faces+1];       // Z-val of centers for depth sort
 int        OrderTable[Num_of_faces+1];    // How to handle faces correct
 PointT     Normals[Num_of_faces+1];       // Original normalized normals
 PointT     RotNormals[Num_of_faces+1];    // Rotated normals
 RealPointT LightVector;                   // Where is our lightsource


 // Some additional buffers needed for graphics
 UCHAR back_buffer[64000];                 // Our virtual screen
 UCHAR texture_buffer[256*256];            // A Screen for our texture


 // Set up pointers to our screens  (assuming protected mode)
 UCHAR *VGA   = (UCHAR *)0x0a0000;         // the standard video mem..
 UCHAR *VADDR = &back_buffer[0];           // the backbuffer
 UCHAR *TADDR = &texture_buffer[0];        // the texturebuffer



// *****************************************************************
// **      Some graphical (mode 13h) functions                    **
// **                                                             **
// **  NOTE : The assembler routines uses the new _asm syntax!    **
// **         Older versions of Watcom might have to use the old  **
// **         #pragma syntax. Other C++ compilers might have to   **
// **         use something totally different - go figure!        **
// *****************************************************************

// Set graphics mode $13
void SetMcga()
 {
  __asm
   {
    mov eax,13h
    int 10h
   }
 }

// Set back textmode
void SetTextMode()
 {
  __asm
   {
    mov eax,03h
    int 10h
   }
 }


void Setpal(char Number,char R,char G,char B)
 {
  __asm
   {
    mov dx, 0x3C8
    mov al, Number
    out dx, al
    inc dx
    mov al, R
    out dx, al
    mov al, G
    out dx, al
    mov al, B
    out dx, al
   }
 }


// Creates a pallette with a purple shade.
void PurplePal()
 {
   for (char counter = 0; counter <= 63;counter++)
    Setpal(counter,counter,0,counter);
 }

// Creates a greyscale pallette
void GreyScale()
 {
  for (char counter = 0; counter <= 63; counter++)
   Setpal(counter, counter, counter, counter);
  }


// Creates a blue light pallette
void FakePhongPal()
 {
  for (char counter = 0; counter <= 63; counter++)
   Setpal(counter,counter,10+char(counter/1.4),20+char(counter/1.6));
 Setpal(0,0,0,0); // black out color 0
 }


// Wait for vertical retrace to occur - to avoid flicker on screen
void WaitRetrace()
 {
  __asm
    {
     mov dx,0x3DA
//    @l1:
     in al,dx
     and al,0x08
//     jnz @l1
//    @l2:
     in al,dx
     and al,0x08
//     jz @l2
    }
 }


// Quickly move 64000 bytes from source to dest
void FlipScreen(UCHAR *source, UCHAR *dest)
{
 __asm
  {
   mov esi,source
   mov edi,dest
   mov ecx,16000
   rep movsd
  }
}

// Quickly clear out 64000 bytes in buffer 'where' with color 'color'
void Clear(char color,UCHAR *where)
 {
  __asm
   {
    mov edi,where
    xor ecx,ecx

    mov al,color
    mov ah,color
    rol eax,16
    mov al,color
    mov ah,color
    mov ecx,16000
    rep stosd
    }
 }


// Plot a pixel of color 'col' to buffer 'where'
void PutPixel(int x, int y, char col,UCHAR *where)
 {
  __asm
    {
     mov eax,where
     mov ebx,y
     shl ebx,8
     mov ecx,ebx
     shr ecx,2
     add ecx,ebx
     add ecx,x
     mov bl,col
     mov [eax+ecx],bl
    }
 }



// Horizontal line with constant color
void Horline(int Xbeg,int Xend,int y, char col,UCHAR *buffer)
 {
  if (Xbeg > Xend) // make sure Xbeg is left of Xend
   {
    int temp = Xbeg;
    Xbeg = Xend;
    Xend = temp;
    }
  int length = Xend - Xbeg;
  int startofs = (y << 8) + (y << 6) + Xbeg;
  for (int i = startofs; i <= startofs + length; i++)
    buffer[i] = col;
 }


// Horizontal line using glenzing
void GlenzHorline(int Xbeg,int Xend,int y, char col,UCHAR *buffer)
 {
  if (Xbeg > Xend) // make sure Xbeg is left of Xend
   {
    int temp = Xbeg;
    Xbeg = Xend;
    Xend = temp;
    }
  int length = Xend - Xbeg;
  int startofs = (y << 8) + (y << 6) + Xbeg;

  for (int i = startofs; i <= startofs + length; i++)
    {
     UCHAR back_color = buffer[i];
     buffer[i] = back_color + col;
     }
 }


// Horizontal line using the gouraud algorithm to interpolate
// between colors 'c1' and 'c2'
void GouraudHorline(int Xbeg, int Xend, int y,
                    char c1,char c2, UCHAR *buffer)
{
  if (Xbeg > Xend) // make sure Xbeg is left of Xend
   {
    int temp = Xbeg;
    Xbeg = Xend;
    Xend = temp;
    }
  int length = Xend - Xbeg;
  int startofs = (y << 8) + (y << 6) + Xbeg;

  int ColorAdd = 0;
  if ((Xend-Xbeg) != 0)
   ColorAdd = ((c2-c1) << 8) / (Xend-Xbeg);

  int CurrentColor = c1 << 8;  // starting color in fixed point

  for (int i = startofs; i <= startofs + length; i++)
   {
    buffer[i] = (CurrentColor >> 8);
    CurrentColor += ColorAdd;
   }
}


// Horizontal line using texturemapping
void TextureMapHorline(int Xbeg, int Xend,int y,
                       int u1,int v1,int u2,int v2,
                       UCHAR *source, UCHAR *dest)
{
  if (Xbeg > Xend) // make sure Xbeg is left of Xend
   {
    int temp = Xbeg;
    Xbeg = Xend;
    Xend = temp;
    }
  int length = Xend - Xbeg;
  int startofs = (y << 8) + (y << 6) + Xbeg; // equals y*320 + Xbeg

  int DeltaX = 0; int DeltaY = 0;
  if ((Xend-Xbeg) != 0)
   {
     DeltaX = ((u2-u1) * 256) / (Xend-Xbeg);
     DeltaY = ((v2-v1) * 256) / (Xend-Xbeg);  // 8.8 fixed-p
    }

  int TextureXpos = u1 << 8;
  int TextureYpos = v1 << 8;

  for (int i = startofs; i <= startofs + length; i++)
   {
    dest[i] = source[(TextureYpos/256)*256 + (TextureXpos/256)];
    TextureXpos += DeltaX;
    TextureYpos += DeltaY;
    }
}




// *************************************************
// **            Some polygon routines            **
// *************************************************


void ScanPolySide(int X1,int Y1,int X2,int Y2)
 {
  if (Y1 != Y2)
   {                                           // exit if Y1 = Y2
     if(Y1 > Y2)
       {
         int temp = Y1;
         Y1 = Y2;
         Y2 = temp;
         temp = X1;
         X1 = X2;
         X2 = temp;
       }                                     // make sure X1,Y1 is top

     int deltaX = ((X2-X1) << 7) / (Y2-Y1);      // deltaX in 9.7 fixed-p
     int XposFixed = X1 << 7;                    // start fixed-p value
     int Xpos = 0;

     for(int temp = Y1;temp <= Y2;temp++)
       {
        Xpos = XposFixed >> 7;
        if (Xpos < leftpoly[temp]) leftpoly[temp] = Xpos;
        if (Xpos > rightpoly[temp]) rightpoly[temp] = Xpos;
        XposFixed += deltaX;
       }
    }
  }


void Polygon(int X1, int Y1,int X2, int Y2,
             int X3, int Y3,int X4, int Y4,
             char color,UCHAR *buffer)
{
 for(int counter = 0;counter <= 200;counter++)
   {
    leftpoly[counter] = 32000;
    rightpoly[counter] = -32000;
   }                                // reset polygon variable

 int miny = Y1;
 if (Y2 < miny) miny = Y2;
 if (Y3 < miny) miny = Y3;
 if (Y4 < miny) miny = Y4;
 int maxy = Y1;
 if (Y2 > maxy) maxy = Y2;
 if (Y3 > maxy) maxy = Y3;
 if (Y4 > maxy) maxy = Y4;

 if (miny < 0) miny = 0;
 if (maxy > 200) maxy = 200;  // find minimum and maximum y-value in poly

 ScanPolySide(X1,Y1,X2,Y2);
 ScanPolySide(X2,Y2,X3,Y3);
 ScanPolySide(X3,Y3,X4,Y4);
 ScanPolySide(X4,Y4,X1,Y1);   // update leftpoly and rightpoly for drawing

 for (counter=miny;counter <= maxy;counter++)
  Horline(leftpoly[counter],rightpoly[counter],counter,color,buffer);
 }

void GlenzPolygon(int X1, int Y1,int X2, int Y2,
                  int X3, int Y3,int X4, int Y4,
                  char color, UCHAR *buffer)
{
 for(int counter = 0;counter <= 200;counter++)
   {
    leftpoly[counter] = 32000;
    rightpoly[counter] = -32000;
   }                                // reset polygon variable

 int miny = Y1;
 if (Y2 < miny) miny = Y2;
 if (Y3 < miny) miny = Y3;
 if (Y4 < miny) miny = Y4;
 int maxy = Y1;
 if (Y2 > maxy) maxy = Y2;
 if (Y3 > maxy) maxy = Y3;
 if (Y4 > maxy) maxy = Y4;

 if (miny < 0) miny = 0;
 if (maxy > 200) maxy = 200;  // find minimum and maximum y-value in poly

 ScanPolySide(X1,Y1,X2,Y2);
 ScanPolySide(X2,Y2,X3,Y3);
 ScanPolySide(X3,Y3,X4,Y4);
 ScanPolySide(X4,Y4,X1,Y1);   // update leftpoly and rightpoly for drawing

 for (counter=miny;counter <= maxy;counter++)
  GlenzHorline(leftpoly[counter],rightpoly[counter],counter,color,buffer);
 }



// This scans the side of a polygon and updates the poly variable
// updates the polydata variable for gouraud shading
void GouraudScanSide(int x1,int y1,int x2,int y2, char c1,char c2)
{
 if (y1!=y2)
 {
  if (y2<y1)
   {
    int temp = y2;
    y2   = y1;
    y1   = temp;
    temp = x2;
    x2   = x1;
    x1   = temp;
    temp = c2;
    c2   = c1;
    c1   = temp;
   } // make sure y1 is top and y2 bottom

  int dcol  = ((c2-c1) << 8) / (y2-y1);    // delta color pr. y-line
  int color = c1 << 8;                     // startcolor in fixed-p

  int xinc = ((x2-x1) << 8) / (y2-y1);     // xinc in fixed point
  int xfixed = x1 << 8;
  for (int loop1 = y1; loop1 <= y2; loop1++)
   {
    if ( (loop1>(YTopClip)) & (loop1<(YBotClip)) )
      {
        int x = xfixed >> 8;
        if (x < leftpoly[loop1])
         {
           leftpoly[loop1] = x;
           polydata[loop1][0] = color >> 8;
         }
        if (x > rightpoly[loop1])
         {
           rightpoly[loop1] = x;
           polydata[loop1][1] = color >> 8;
         }
      }
    xfixed += xinc;
    color  += dcol;
  }
 }
}




void GouraudPolygon(int x1,int y1,int x2,int y2,int x3,int y3,
                    int x4,int y4, char c1,char c2,char c3,char c4,
                    UCHAR *buffer)
{
 for (int loop1 = 0; loop1 <= 199; loop1++)
  {
    leftpoly[loop1]  = 32766;
    rightpoly[loop1] = -32767;
    } // set minx og maxx to extremes

  int miny = y1;
  int maxy = y1;

  if (y2<miny) miny = y2;
  if (y3<miny) miny = y3;
  if (y4<miny) miny = y4;

  if (y2>maxy) maxy = y2;
  if (y3>maxy) maxy = y3;
  if (y4>maxy) maxy = y4; // Min Y and Max Y for drawing later on

  if (miny<YTopClip) miny = YTopClip;
  if (maxy>YBotClip) maxy = YBotClip;  // clipping

  if ((miny<199) & (maxy>0)) // is poly completely of screen?
   {
    GouraudScanSide(x1,y1,x2,y2,c1,c2);
    GouraudScanSide(x2,y2,x3,y3,c2,c3);
    GouraudScanSide(x3,y3,x4,y4,c3,c4);
    GouraudScanSide(x4,y4,x1,y1,c4,c1);

    for (int i = miny; i <= maxy; i++)
      GouraudHorline(leftpoly[i],rightpoly[i],i,
                     polydata[i][0],polydata[i][1],buffer);
   }
}






void TextureScanSide(int x1,int y1,int x2,int y2,
                     char u1,char v1,char u2,char v2)
{
  if (y1!=y2)
 {
  if (y2<y1)
   {
     int temp = y2;
     y2       = y1;
     y1       = temp;
     temp     = x2;
     x2       = x1;
     x1       = temp;
     temp     = u2;
     u2       = u1;
     u1       = temp;
     temp     = v2;
     v2       = v1;
     v1       = temp;
   } // make sure y1 is top and y2 bottom

  int DeltaX = ((u2-u1) << 8) / (y2-y1);  // steps through texture in 8.8
  int DeltaY = ((v2-v1) << 8) / (y2-y1);  // fixed-point
  int Xpos = u1 << 8;
  int Ypos = v1 << 8;     // starting texture positions

  int xinc = ((x2-x1) << 8) / (y2-y1); // xinc in fixed point
  int xfixed = x1 << 8;
  for (int loop1 = y1; loop1 <= y2; loop1++)
   {
    if ((loop1>(YTopClip)) & (loop1<(YBotClip)))
     {
       int x = xfixed >> 8;
        if (x<leftpoly[loop1])
         {
           leftpoly[loop1] = x;
           polydata[loop1][0] = Xpos >> 8;
           polydata[loop1][1] = Ypos >> 8;
         }
        if (x>rightpoly[loop1])
         {
           rightpoly[loop1] = x;
           polydata[loop1][2] = Xpos >> 8;
           polydata[loop1][3] = Ypos >> 8;
         }
    }
    xfixed += xinc;
    Xpos += DeltaX;
    Ypos += DeltaY;
  }
 }
}


void TextureMapPolygon(int x1, int y1, int x2, int y2, int x3, int y3,
                       int x4, int y4, char u1, char v1, char u2, char v2,
                       char u3, char v3, char u4, char v4,
                       UCHAR *source, UCHAR *dest)
{
 for (int loop1 = 0; loop1 <= 199; loop1++)
  {
    leftpoly[loop1]  = 32766;
    rightpoly[loop1] = -32767;
  }  // set minx og maxx to extremes

  int miny = y1;
  int maxy = y1;

  if (y2<miny) miny = y2;
  if (y3<miny) miny = y3;
  if (y4<miny) miny = y4;

  if (y2>maxy) maxy = y2;
  if (y3>maxy) maxy = y3;
  if (y4>maxy) maxy = y4; // Min Y and Max Y for drawing later on

  if (miny<YTopClip) miny = YTopClip;
  if (maxy>YBotClip) maxy = YBotClip;  // clipping

  if ((miny<199) & (maxy>0))     //is poly completely of screen?
  {
    TextureScanSide (x1,y1,x2,y2,u1,v1,u2,v2);
    TextureScanSide (x2,y2,x3,y3,u2,v2,u3,v3);
    TextureScanSide (x3,y3,x4,y4,u3,v3,u4,v4);
    TextureScanSide (x4,y4,x1,y1,u4,v4,u1,v1);

    for (int i = miny; i <= maxy; i++)
     TextureMapHorline (leftpoly[i],rightpoly[i],i,
                        polydata[i][0],polydata[i][1],
                        polydata[i][2],polydata[i][3],
                        source,dest);
   }
}








// *************************************************
// **        The initialization functions         **
// *************************************************


// Calculate a simple highlight map - use sine wave to make max.
// values at x = pi/2 and y = pi/2.  Divide i and j by 81.487 to
// get values in the range [0, pi]
// This has turned out very ugly in C++ ?!?  Way too bright.. make
// a new one if you care..
void CalcFakePhongMap(UCHAR *buffer)
{
 for (int i = 0; i <= 255; i++)
  for (int j = 0; j <= 255; j++)
    buffer[(256*i)+j] = UCHAR(sin(i/81.487)*sin(j/81.487)*63)+1;
 }



// Convert an angle from degree to radian
float rad(float theta)
 {
  float temp = theta * 3.141592654 / 180;
  return(temp);
 }



// Create sine & cosine lookup tables
void Calc_Cos_Sin()
 {
  for(int loop1 = 0; loop1 <= 360;loop1++)
   {
    lookup[loop1][0] = int(sin(rad(loop1)) * 16384);
    lookup[loop1][1] = int(cos(rad(loop1)) * 16384);
   }
 }



// Initializa the object (cube) and calculate face normals
void Init_Object()
{
  int counter;

  Baseobj[1].X = -50; Baseobj[1].Y = -50; Baseobj[1].Z = -50;
  Baseobj[2].X =  50; Baseobj[2].Y = -50; Baseobj[2].Z = -50;
  Baseobj[3].X = -50; Baseobj[3].Y =  50; Baseobj[3].Z = -50;
  Baseobj[4].X =  50; Baseobj[4].Y =  50; Baseobj[4].Z = -50;
  Baseobj[5].X = -50; Baseobj[5].Y = -50; Baseobj[5].Z =  50;
  Baseobj[6].X =  50; Baseobj[6].Y = -50; Baseobj[6].Z =  50;
  Baseobj[7].X = -50; Baseobj[7].Y =  50; Baseobj[7].Z =  50;
  Baseobj[8].X =  50; Baseobj[8].Y =  50; Baseobj[8].Z =  50;

  Faces[1].P1 = 1; Faces[1].P2 = 2; Faces[1].P3 = 4; Faces[1].P4 = 3;
  Faces[2].P1 = 2; Faces[2].P2 = 6; Faces[2].P3 = 8; Faces[2].P4 = 4;
  Faces[3].P1 = 5; Faces[3].P2 = 7; Faces[3].P3 = 8; Faces[3].P4 = 6;
  Faces[4].P1 = 1; Faces[4].P2 = 3; Faces[4].P3 = 7; Faces[4].P4 = 5;
  Faces[5].P1 = 1; Faces[5].P2 = 5; Faces[5].P3 = 6; Faces[5].P4 = 2;
  Faces[6].P1 = 3; Faces[6].P2 = 4; Faces[6].P3 = 8; Faces[6].P4 = 7;

  for (counter = 1;counter <= Num_of_faces;counter++)
     Faces[counter].color =  0 + counter * 3;


  // calculate starting normals
  for (int count = 1; count <= Num_of_faces; count++)
    {
      int Ax = (Baseobj[Faces[count].P2].X - Baseobj[Faces[count].P1].X);
      int Ay = (Baseobj[Faces[count].P2].Y - Baseobj[Faces[count].P1].Y);
      int Az = (Baseobj[Faces[count].P2].Z - Baseobj[Faces[count].P1].Z);

      int Bx = (Baseobj[Faces[count].P4].X - Baseobj[Faces[count].P1].X);
      int By = (Baseobj[Faces[count].P4].Y - Baseobj[Faces[count].P1].Y);
      int Bz = (Baseobj[Faces[count].P4].Z - Baseobj[Faces[count].P1].Z);

      int Nx = (Ay*Bz) - (Az*By);
      int Ny = (Az*Bx) - (Ax*Bz);
      int Nz = (Ax*By) - (Ay*Bx);

      float length = sqrt(Nx*Nx + Ny*Ny + Nz*Nz);

      Normals[count].X = int((Nx/length) * 256);
      Normals[count].Y = int((Ny/length) * 256);
      Normals[count].Z = int((Nz/length) * 256);
      RotNormals[count].X = int((Nx/length) * 256);
      RotNormals[count].Y = int((Ny/length) * 256);
      RotNormals[count].Z = int((Nz/length) * 256);
    }
  }




// ***********************************************************
// **     The 3D functions : Projection, Rotating, Sorting  **
// **                        and drawing                    **
// ***********************************************************


// Convert a 3d point to 2d using these two functions
int Xconv(int x, int z)
{
	return(Xofs+(x*((float)Zeye/(Zeye-z))));
}

int Yconv(int y, int z)
{
	return(Yofs+(y*((float)Zeye/(Zeye-z))));
}



// Rotate a point using standard trig rotation. No optimization
// here....(yet - we look at that stuff in another tutorial)
void RotatePoint(int Xrot, int Yrot, int Zrot,
                 int Xin, int Yin, int Zin,
                 int *Xout, int *Yout, int *Zout)
  {
   int TempY = (lookup[Xrot][1]*Yin - lookup[Xrot][0]*Zin) >> 14;
   int TempZ = (lookup[Xrot][0]*Yin + lookup[Xrot][1]*Zin) >> 14;

   int TempX = (lookup[Yrot][1]*Xin - lookup[Yrot][0]*TempZ) >> 14;
   *Zout = (lookup[Yrot][0]*Xin + lookup[Yrot][1]*TempZ) >> 14;

   *Xout = (lookup[Zrot][1]*TempX - lookup[Zrot][0]*TempY) >> 14;
   *Yout = (lookup[Zrot][0]*TempX + lookup[Zrot][1]*TempY) >> 14;
  }


// Rotates all points and calculates center Z-val for sorting
void Rotateobj(int x,int y,int z)
{
 for (int counter = 1;counter <= Num_of_points; counter++)
  RotatePoint(x,y,z,Baseobj[counter].X,Baseobj[counter].Y,Baseobj[counter].Z,
              &Points[counter].X,&Points[counter].Y,&Points[counter].Z);

 for (counter = 1; counter <= Num_of_faces; counter++)
  Centers[counter] =
     (Points[Faces[counter].P1].Z + Points[Faces[counter].P2].Z +
      Points[Faces[counter].P3].Z + Points[Faces[counter].P4].Z);

 // average Z-val for face. NOTE : SHOULD divide by 4.. but that is really
 // not nessesary. This way all the values will be the correct val times 4
 // As ALL values is 4 times too big they will still sort correct :)
}



// Rotate all normals
void RotateNormals(int x,int y,int z)
{
 for (int count = 1; count <= Num_of_faces; count++)
  RotatePoint(x,y,z,Normals[count].X,Normals[count].Y,Normals[count].Z,
              &RotNormals[count].X,&RotNormals[count].Y,
              &RotNormals[count].Z);
}



//Just a simple bubble-sort - not to fast but what the heck :)
//Faces with the HIGHEST Z-val is placed first in Order[]
//For big objects implement a quick-sort - or bucket sort if
//your object lies in a fixed z-range
void Sort_faces()
{
  for (int counter = 1; counter <= Num_of_faces; counter++)
    OrderTable[counter] = counter;
  // we resets the ordertable so that it matches the unsorted
  // 'centers' variable

int position = 1;

 while (position != Num_of_faces)
    {
     if (Centers[position] < Centers[position+1])
        {   // switch values in centers and ordertable
          int tempval = Centers[position+1];
          Centers[position+1] = Centers[position];
          Centers[position] = tempval;

          tempval = OrderTable[position+1];
          OrderTable[position+1] = OrderTable[position];
          OrderTable[position] = tempval;

          position = 1;   // start loop over
        };
      position++;
    }
}


// Translate all 3d points to 2d
void Project_Points()
{
 for (int counter = 1; counter <= Num_of_points; counter++)
    {
     Translated[counter].X = Xconv(Points[counter].X,Points[counter].Z);
     Translated[counter].Y = Yconv(Points[counter].Y,Points[counter].Z);
    }
}



// Calculate a point normal. It's not possible to calculate a normal
// to a point of course - but see the doc for an explaination of how
// we define a point normal!
RealPointT PointNormal(int nr)
{
int Hits[26];
int NumOfHits = 0;
int SumX = 0;  int SumY = 0; int SumZ = 0;
RealPointT TempPoint;

 for (int counter = 1; counter <= Num_of_faces; counter++)
   if ((Faces[counter].P1 == nr) | (Faces[counter].P2 == nr) |
       (Faces[counter].P3 == nr) | (Faces[counter].P4 == nr))
          {
            NumOfHits++;
            Hits[NumOfHits] = counter;
            } // in which faces does the point appear

 for (counter = 1; counter <= NumOfHits; counter++)
   {
      SumX += RotNormals[Hits[counter]].X;
      SumY += RotNormals[Hits[counter]].Y;
      SumZ += RotNormals[Hits[counter]].Z;
    }

TempPoint.X = float(SumX / NumOfHits) / 256;
TempPoint.Y = float(SumY / NumOfHits) / 256;
TempPoint.Z = float(SumZ / NumOfHits) / 256;

float length = sqrt(TempPoint.X*TempPoint.X + TempPoint.Y * TempPoint.Y +
                    TempPoint.Z*TempPoint.Z);

TempPoint.X = TempPoint.X / length;
TempPoint.Y = TempPoint.Y / length;
TempPoint.Z = TempPoint.Z / length;

return (TempPoint);
// result is the average values of the normals to the faces in which
// the point appear
}



// Set the lightsource vector to a specific direction
void SetLightSource(int Xbeg,int Ybeg,int Zbeg,int Xend,int Yend,int Zend)
{
  int Ax = Xend - Xbeg;
  int Ay = Yend - Ybeg;
  int Az = Zend - Zbeg;   // vector from lightsource to lightdest
  float lenght = sqrt(Ax*Ax + Ay*Ay + Az*Az);
  LightVector.X = Ax/lenght;
  LightVector.Y = Ay/lenght;
  LightVector.Z = Az/lenght;
}



// *******************************************************
// **      THE DIFFERENT DRAWING LOOPS                  **
// *******************************************************

void BadFlatShade(UCHAR *buffer, int MinZ, int MaxZ, int Num_of_shades)
//********************************************************************
//**  MinZ, MaxZ : What is the minimum and maximum Z-values of the  **
//**               faces that is to be drawn ? You COULD set theese **
//**               values so that minZ is the minimum Z-val of the  **
//**               entire object and MaxZ the maximum value. However**
//**               consider the fact that half of the objects faces **
//**               is removed by hidden face removal. So, if you    **
//**               want to have bigger diference on the shown faces **
//**               just set minZ to minimum object Z-value and MaxZ **
//**               to the Z-value of the CENTER of the object.      **
//**               Experiment!!                                     **
//** Num_of_shades : shades used = color 0 to Num_of_shades         **
//********************************************************************
{
 for (int count = 1; count <= Num_of_faces; count++)
   {
     int polynr = OrderTable[count];
     int X1 = Translated[Faces[polynr].P1].X;
     int Y1 = Translated[Faces[polynr].P1].Y;
     int X2 = Translated[Faces[polynr].P2].X;
     int Y2 = Translated[Faces[polynr].P2].Y;
     int X3 = Translated[Faces[polynr].P3].X;
     int Y3 = Translated[Faces[polynr].P3].Y;
     int X4 = Translated[Faces[polynr].P4].X;
     int Y4 = Translated[Faces[polynr].P4].Y;

     // ***************** Z-shading *****************

     int span = abs(MinZ-MaxZ);   // Z span of object
     float shade = float(Centers[count] / 4 + abs(MinZ)) / span;

     char color = char(Num_of_shades -(Num_of_shades*shade));

     //*******************************************************
     //******* HIDDEN FACE REMOVAL - YES, THAT EASY ;) *******
     //*******************************************************
     //Z-Comp of normal to 2d-polygon
     int normal = (Y1-Y3)*(X2-X1) - (X1-X3)*(Y2-Y1);
       if (normal < 0) // pointing towards us
         Polygon(X1,Y1,X2,Y2,X3,Y3,X4,Y4,color,buffer);
     //*******************************************************
     //*******************************************************
     //*******************************************************
   }
}




void NiceFlatShade(UCHAR *buffer, int Num_of_shades)
{
 for (int count = 1; count <= Num_of_faces; count++)
   {
     int polynr = OrderTable[count];
     int X1 = Translated[Faces[polynr].P1].X;
     int Y1 = Translated[Faces[polynr].P1].Y;
     int X2 = Translated[Faces[polynr].P2].X;
     int Y2 = Translated[Faces[polynr].P2].Y;
     int X3 = Translated[Faces[polynr].P3].X;
     int Y3 = Translated[Faces[polynr].P3].Y;
     int X4 = Translated[Faces[polynr].P4].X;
     int Y4 = Translated[Faces[polynr].P4].Y;

     //*******************************************************
     //******* HIDDEN FACE REMOVAL - YES, THAT EASY ;) *******
     //*******************************************************
     //Z-Comp of normal to 2d-polygon
     int normal = (Y1-Y3)*(X2-X1) - (X1-X3)*(Y2-Y1);


       if (normal < 0) //pointing towards us
         {
           //************************************************************
           //**   LAMBERTS FLATSHADIG ACCORDING TO MOVING LIGHTSOURCE  **
           //************************************************************

           float Nx = float(RotNormals[polynr].X) / 256;
           float Ny = float(RotNormals[polynr].Y) / 256;
           float Nz = float(RotNormals[polynr].Z)  / 256;

           float dot = (Nx*LightVector.X) + (Ny*LightVector.Y) +
                       (Nz*LightVector.Z);
           if (dot > 1) dot = 1;
           if (dot < 0) dot = 0;
           char color = int(dot * Num_of_shades);
           Polygon(X1,Y1,X2,Y2,X3,Y3,X4,Y4,color,buffer);
         }
     //*******************************************************
     //*******************************************************
     //*******************************************************
   }
}



void GouraudShade(UCHAR *buffer, int Num_of_shades)
{
 for (int count = 1; count <= Num_of_faces; count++)
   {
     int polynr = OrderTable[count];
     int X1 = Translated[Faces[polynr].P1].X;
     int Y1 = Translated[Faces[polynr].P1].Y;
     int X2 = Translated[Faces[polynr].P2].X;
     int Y2 = Translated[Faces[polynr].P2].Y;
     int X3 = Translated[Faces[polynr].P3].X;
     int Y3 = Translated[Faces[polynr].P3].Y;
     int X4 = Translated[Faces[polynr].P4].X;
     int Y4 = Translated[Faces[polynr].P4].Y;

     //*******************************************************
     //******* HIDDEN FACE REMOVAL - YES, THAT EASY ;) *******
     //*******************************************************
     //Z-Comp of normal to 2d-polygon
     int normal = (Y1-Y3)*(X2-X1) - (X1-X3)*(Y2-Y1);
       if (normal < 0) // pointing towards us
         {
           //************************************************************
           //**   GOURAUD SHADING ACCORDING TO MOVING LIGHTSOURCE      **
           //************************************************************

          RealPointT norm = PointNormal(Faces[polynr].P1);
          float dot = (norm.X*LightVector.X) + (norm.Y*LightVector.Y) +
                (norm.Z*LightVector.Z);
          if (dot > 1) dot = 1;
          if (dot < 0) dot = 0;
          char C1 = int(dot * Num_of_shades);

          norm = PointNormal(Faces[polynr].P2);
          dot = (norm.X*LightVector.X) + (norm.Y*LightVector.Y) +
                (norm.Z*LightVector.Z);
          if (dot > 1) dot = 1;
          if (dot < 0) dot = 0;
          char C2 = int(dot * Num_of_shades);

          norm = PointNormal(Faces[polynr].P3);
          dot = (norm.X*LightVector.X) + (norm.Y*LightVector.Y) +
                (norm.Z*LightVector.Z);
          if (dot > 1) dot = 1;
          if (dot < 0) dot = 0;
          char C3 = int(dot * Num_of_shades);

          norm = PointNormal(Faces[polynr].P4);
          dot  = (norm.X*LightVector.X) + (norm.Y*LightVector.Y) +
                 (norm.Z*LightVector.Z);
          if (dot > 1) dot = 1;
          if (dot < 0) dot = 0;
          char C4 = int(dot * Num_of_shades);

          GouraudPolygon(X1,Y1,X2,Y2,X3,Y3,X4,Y4,C1,C2,C3,C4,buffer);
         }
     //*******************************************************
     //*******************************************************
     //*******************************************************
   }
}



void EnvironmentMap(UCHAR *source, UCHAR *dest)
{
 for (int count = 1; count <= Num_of_faces; count++)
   {
     int polynr = OrderTable[count];
     int X1 = Translated[Faces[polynr].P1].X;
     int Y1 = Translated[Faces[polynr].P1].Y;
     int X2 = Translated[Faces[polynr].P2].X;
     int Y2 = Translated[Faces[polynr].P2].Y;
     int X3 = Translated[Faces[polynr].P3].X;
     int Y3 = Translated[Faces[polynr].P3].Y;
     int X4 = Translated[Faces[polynr].P4].X;
     int Y4 = Translated[Faces[polynr].P4].Y;


     //*******************************************************
     //******* HIDDEN FACE REMOVAL - YES, THAT EASY ;) *******
     //*******************************************************
     //Z-Component of normal to 2d-polygon
     int normal = (Y1-Y3)*(X2-X1) - (X1-X3)*(Y2-Y1);
       if (normal < 0)  // then it's pointing towards us
         {
           //************************************************************
           //**           ENVIRONMENT MAPPING / FAKE PHONG             **
           //************************************************************

          RealPointT norm = PointNormal(Faces[polynr].P1);
          int u1 = (int(norm.X*256) / 2) + 128;
          int v1 = (int(norm.Y*256) / 2) + 128;

          norm = PointNormal(Faces[polynr].P2);
          int u2 = (int(norm.X*256) / 2) + 128;
          int v2 = (int(norm.Y*256) / 2) + 128;

          norm = PointNormal(Faces[polynr].P3);
          int u3 = (int(norm.X*256) / 2) + 128;
          int v3 = (int(norm.Y*256) / 2) + 128;

          norm = PointNormal(Faces[polynr].P4);
          int u4 = (int(norm.X*256) / 2) + 128;
          int v4 = (int(norm.Y*256) / 2) + 128;

          TextureMapPolygon(X1,Y1,X2,Y2,X3,Y3,X4,Y4,
                            u1,v1,u2,v2,u3,v3,u4,v4,source,dest);
         }
     //*******************************************************
     //*******************************************************
     //*******************************************************
   }
}



void TexturemapCube(UCHAR *source, UCHAR *dest)
{
 for (int count = 1; count <= Num_of_faces; count++)
   {
     int polynr = OrderTable[count];
     int X1 = Translated[Faces[polynr].P1].X;
     int Y1 = Translated[Faces[polynr].P1].Y;
     int X2 = Translated[Faces[polynr].P2].X;
     int Y2 = Translated[Faces[polynr].P2].Y;
     int X3 = Translated[Faces[polynr].P3].X;
     int Y3 = Translated[Faces[polynr].P3].Y;
     int X4 = Translated[Faces[polynr].P4].X;
     int Y4 = Translated[Faces[polynr].P4].Y;

     //*******************************************************
     //******* HIDDEN FACE REMOVAL - YES, THAT EASY ;) *******
     //*******************************************************
     //Z-Comp of normal
     int normal = (Y1-Y3)*(X2-X1) - (X1-X3)*(Y2-Y1);
       if (normal < 0)
         TextureMapPolygon(X1,Y1,X2,Y2,X3,Y3,X4,Y4,
                  0,0,255,0,255,255,0,255,source,dest);

     //*******************************************************
     //*******************************************************
     //*******************************************************

   }
}






// *****************************************************
// **                MAIN - PXDTUT4                   **
// *****************************************************
main()
{
SetTextMode();

cout << "      ****************************************************************\n";
cout << "      *                                                              *\n";
cout << "      *                    3D BASIC OBJECT ENGINE                    *\n";
cout << "      *                        by : Telemachos                       *\n";
cout << "      *                                                              *\n";
cout << "      ****************************************************************\n";
cout << "\n";
cout << "\n";
cout << "      Hiya! \n";
cout << "      Welcome to the Peroxide Programming Tips #4 - C++ Pmode version\n";
cout << "      This one is on 3D objects - showing you how to shade those nice\n";
cout << "      3d objects you have been making since the last tut :)\n";
cout << "      This small demo contains five small parts.\n";
cout << "\n";
cout << "         1) Bad Z-shading\n";
cout << "         2) Nice Flat shading according to lightsource\n";
cout << "         3) Gouraud shaded cube according to lightsource..\n";
cout << "         4) Texturemapped Cube - I will just use the entire phong map as texture\n";
cout << "         5) Environmentmapping / FakePhong\n";
cout << "\n";
cout << "         Hit any key to switch between them....\n";
cout << "\n";
cout << "      Hit any key to start.....\n";

getch();

CalcFakePhongMap(TADDR);

SetMcga();
Calc_Cos_Sin();
Init_Object();

Clear(0,VGA);

int Xrot = 0;
int Yrot = 0;
int Zrot = 0;

SetLightSource(0,0,-100,0,0,0);  // points right to the screen now

//Set greyscale pallette for flat shading demos
GreyScale();

do {
 Rotateobj(Xrot,Yrot,Zrot);
 RotateNormals(Xrot,Yrot,Zrot);

 Xrot = (Xrot + 1) % 360;
 Yrot = (Yrot + 3) % 360;
 Zrot = (Zrot + 1) % 360;

 Project_Points();
 Sort_faces();
 Clear(0,VADDR);

 BadFlatShade(VADDR,-50,20,20);
 WaitRetrace();
 FlipScreen(VADDR,VGA);

} while (!kbhit());
getch();


do {
 Rotateobj(Xrot,Yrot,Zrot);
 RotateNormals(Xrot,Yrot,Zrot);

 Xrot = (Xrot + 1) % 360;
 Yrot = (Yrot + 3) % 360;
 Zrot = (Zrot + 1) % 360;
 Clear(0,VADDR);

 Project_Points();
 Sort_faces();

 NiceFlatShade(VADDR,30);

 WaitRetrace();
 FlipScreen(VADDR,VGA);

} while(!kbhit());
getch();

// Set the purple shade pallette for the gouraud shading demo
PurplePal();

do {
 Rotateobj(Xrot,Yrot,Zrot);
 RotateNormals(Xrot,Yrot,Zrot);

 Xrot = (Xrot + 1) % 360;
 Yrot = (Yrot + 3) % 360;
 Zrot = (Zrot + 1) % 360;

 Project_Points();
 Sort_faces();
 Clear(0,VADDR);

 GouraudShade(VADDR,63);

 WaitRetrace();
 FlipScreen(VADDR,VGA);

} while (!kbhit());
getch();


// Set the blue lightsource pallette for texturemapping and
// Environmentmapping / FakePhong demo
FakePhongPal();
do {
 Rotateobj(Xrot,Yrot,Zrot);
 RotateNormals(Xrot,Yrot,Zrot);

 Xrot = (Xrot + 1) % 360;
 Yrot = (Yrot + 3) % 360;
 Zrot = (Zrot + 1) % 360;

 Project_Points();
 Sort_faces();
 Clear(0,VADDR);

 TexturemapCube(TADDR,VADDR);

 WaitRetrace();
 FlipScreen(VADDR,VGA);

} while(!kbhit());
getch();


do {
 Rotateobj(Xrot,Yrot,Zrot);
 RotateNormals(Xrot,Yrot,Zrot);

 Xrot = (Xrot + 1) % 360;
 Yrot = (Yrot + 3) % 360;
 Zrot = (Zrot + 1) % 360;

 Project_Points();
 Sort_faces();
 Clear(0,VADDR);

 EnvironmentMap(TADDR,VADDR);
 WaitRetrace();
 FlipScreen(VADDR,VGA);

} while (!kbhit());
getch();

SetTextMode();
cout << "Bye bye... CU in another PXDTUT....";
}


