#include "polygon.h"

double sintable[360]; // Trig lookup tables are far faster than the functions
extern unsigned _stklen = 51200U; // We need a larger stack for this one...

void maketables (void)
 {
  //  Purpose: This function creates the global trig lookup table.
  int angle;

  for (angle = 0; angle < 360; ++angle)
   sintable[angle] = sin((((double)angle)*2.0*3.1415927)/360.0);
 }

double sint(int X)
 {
  //   Purpose: This function takes an angle (0 to 359) in an integral number of degrees
  // and returns the sine of the angle.
  if (X < 0)
   return -(sintable[(-X)%360]);
  return (sintable[X%360]);
 }

double cost(int X)
 {
  //   Purpose: This function takes an angle (0 to 359) in an integral number of degrees
  // and returns the cosine of the angle.
  X += 90;
  if (X < 0)
   X = -X;
  X = X%360;
  return (sintable[X]);
 }

screen::screen(int m)
 {
  open(m);
 }

screen::screen()
 {
  active = 0;
 }

void screen::open(int m)
 {
  int gdriver = VGA, gmode = VGAMED, errorcode;

  initgraph(&gdriver, &gmode, ""); // Init graphics.
  errorcode = graphresult(); // Get the error code,
  if (m != 0)
   {
    setactivepage(0);
    setvisualpage(1);
    vispage = 1;
   }
  else
   vispage = -1;
  if (errorcode != grOk)  // an error occurred
   {
    printf("Graphics error: %s\n", grapherrormsg(errorcode));
    printf("Press any key to halt:");
    getch();
    exit(1);             // return with error code
   }
  active = 1;
 }

void screen::swap()
 {
  if ((!active) || (vispage == -1))
   return;
  if (vispage == 0)
   {
    setactivepage(0);
    setvisualpage(1);
    vispage = 1;
   }
  else
   {
    setactivepage(1);
    setvisualpage(0);
    vispage = 0;
   }
 }

screen::~screen()
 {
  if (!active)
   return;
  close(); // Close the graphics system.
  active = 0;
 }

void screen::close(void)
 {
  if (!active)
   return;
  closegraph(); // Shut down the graphics system, and return to text mode.
  active = 0;
 }

void screen::plot(long x, long y, int c)
 {
  putpixel((int)x,(int)y,(int)c); // Plot a pixel.
 }

void screen::sline(long x1, long y1, long x2, long y2, int c)
 {
  setcolor(c); // Set the color,
  line((int)x1,(int)y1,(int)x2,(int)y2); // and draw a line.
 }

void screen::sfillpoly(int num_points, int *points, int fc, int bc)
 {
  setcolor(bc);
  setlinestyle(SOLID_LINE,1,1);
  setfillstyle(SOLID_FILL,fc);
  fillpoly(num_points,points);
  drawpoly(num_points,points); // This is needed to correct for a bug in TC++ 3.0
 }

void screen::clear(int c)
 {
  setbkcolor(c);
  cleardevice();
 }

void point::normalize(void)
 {
  float veclen;

  veclen = sqrt(x*x+y*y+z*z);
  x = x/veclen;
  y = y/veclen;
  z = z/veclen;
 }

polygon::polygon()
 {
  list=NULL;
  color=15;
 }

void polygon::dellist(void)
 {
  polyNODE *pt, *lpt;

  pt = list;
  while(pt != NULL)
   {
    lpt = pt;
    pt = pt->Next;
    delete lpt;
   }
  list = NULL;
 }

polygon::~polygon()
 {
  dellist();
 }

int polygon::addpoint(point &P)
 {
  polyNODE *pt, *lpt, *npt;

  lpt = pt = list; // Start at the beginning of the list.
  while (pt != NULL) // Find the end of the list.
   {
    lpt = pt;
    pt = pt->Next;
   }
  npt = new polyNODE; // Create the new point.
  if (npt == NULL) // If memory problem, indicate it.
   return 0;
  if (list == NULL) // Add the point to the list.
   list = npt;
  else
   lpt->Next = npt;
  npt->P = P;     // Copy the value into the new point.
  npt->Next = NULL; // Ground the end.
  return 1; // return success.
 }

void polygon::translate(float x, float y, float z)
 {
  polyNODE *pt;
  pt=list;
  while (pt!= NULL)
   {
    pt->P.x += x;
    pt->P.y += y;
    pt->P.z += z;
    pt = pt->Next;
   }
 }

void polygon::rotate(int t, int p, int a)
 {
  polyNODE *pt;
  float TmpX,
	TmpY,
	TmpZ;
  float cosv, sinv;

  pt = list;
  while (pt != NULL)
   {
    if (a != 0)         // Z axis rotation
     {
      cosv = cost(a);
      sinv = sint(a);
      TmpX = ((pt->P.x * cosv) - (pt->P.y * sinv));
      TmpY = ((pt->P.x * sinv) + (pt->P.y * cosv));
      pt->P.x = TmpX;
      pt->P.y = TmpY;
     }
    if (t != 0)          // X Axis rotation.
     {
      cosv = cost(t);
      sinv = sint(t);
      TmpY = ((pt->P.y * cosv)-(pt->P.z*sinv));
      TmpZ = ((pt->P.y * sinv)+(pt->P.z*cosv));
      pt->P.y = TmpY;
      pt->P.z = TmpZ;
     }
    if (p != 0)         // Y axiz rotation
     {
      cosv = cost(p);
      sinv = sint(p);
      TmpX = ((pt->P.x * cosv) - (pt->P.z*sinv));
      TmpZ = ((pt->P.x * sinv) + (pt->P.z*cosv));
      pt->P.x = TmpX;
      pt->P.z = TmpZ;
     }
    pt = pt->Next;
   }
 }

void polygon::scale(float s)
 {
  polyNODE *pt;

  pt=list;
  while (pt!= NULL)
   {
    pt->P.x *= s;
    pt->P.y *= s;
    pt->P.z *= s;
    pt = pt->Next;
   }
 }

polygon &polygon::operator =(polygon &P)
 {
  polyNODE *pt;

  dellist(); // Delete the current list.
  pt = P.list; // Start at the beginning of the other list.
  while (pt != NULL) // continue until the end of the other list.
   {
    if (!addpoint(pt->P)) // Try to add the point.
     {
      closegraph(); // If there was a problem, indicate it. (manual close of graphics system)
      printf("Memory allocation error in polygon::operator =(point &P)\n");
      exit(1);
     }
    pt = pt->Next; // Go to the next point.
   }
  color = P.color; // Make sure the color's the same.
  return *this;
 }

 void polygon::getcenter(point &C)
 {
  polyNODE *pt;
  float Xavg = 0.0,
	Yavg = 0.0,
	Zavg = 0.0,
	fpoints;
  int points = 0;

  pt = list; // From the beginning of the list
  while (pt != NULL) // to the end of the list
   {
    ++points;
    Xavg += pt->P.x; // Add the value of the points x,y,z to the avg counters.
    Yavg += pt->P.y;
    Zavg += pt->P.z;
    pt = pt->Next;
   }
  if (points > 1)  // If there was more than 1 point,
   {
    fpoints = (float)points;
    Xavg = Xavg/fpoints; // Calculate the average.
    Yavg = Yavg/fpoints;
    Zavg = Zavg/fpoints;
   }
  C.x = Xavg; // Return the values to the caller.
  C.y = Yavg;
  C.z = Zavg;
 }

float polygon::getdist(point &P)
 {
  point Cntr;
  float dist,
	dx,
	dy,
	dz;

  getcenter(Cntr);
  dx = Cntr.x - P.x;
  dy = Cntr.y - P.y;
  dz = Cntr.z - P.z;
  dist = sqrt(dx*dx+dy*dy+dz*dz);
  return dist;
 }

void polygon::getnormal(point &N)
 {
  point v1(0,0,0),
	v2(0,0,0);
  polyNODE *currnode, *nextnode;

  // Step 1. find a vector from point 2 to point 1 on the polygon.
  currnode = list;
  if (currnode == NULL)
   return;
  nextnode = currnode->Next;
  if (nextnode == NULL)
   return;
  v1.x = currnode->P.x - nextnode->P.x;
  v1.y = currnode->P.y - nextnode->P.y;
  v1.z = currnode->P.z - nextnode->P.z;
  // Step 3. find a vector from point 2 to point 3 on the polygon
  currnode = nextnode;
  nextnode = currnode->Next;
  if (nextnode == NULL)
   return;
  v2.x = nextnode->P.x - currnode->P.x;
  v2.y = nextnode->P.y - currnode->P.y;
  v2.z = nextnode->P.z - currnode->P.z;
  // Step 5. N = V1 X V2.
  N.x = v1.y*v2.z-v1.z*v2.y;
  N.y = v1.z*v2.x-v1.x*v2.z;
  N.z = v1.x*v2.y-v1.y*v2.x;
  // Step 6. normalize N.
  N.normalize();
 }

void player::delorderlist(orderNODE *list)
 {
  orderNODE *currnode, *prevnode;

  currnode = list;
  while (currnode != NULL)
   {
    prevnode = currnode;
    currnode = currnode->next;
    delete(prevnode);
   }
 }

void player::dellist(void)
 {
  playerNODE *currnode, *prevnode;

  currnode = list;
  while (currnode != NULL)
   {
    prevnode = currnode;
    currnode = currnode->Next;
    delete(prevnode);
   }
  list = NULL;
 }

int player::setorder(int view, int *ordering)
 {
  //   Takes the array of ints in ordering, creates an array of pointers to the
  // polygons in the order specified by ordering, and stores it in the orderings
  // array. The array of ints is terminated by a value of 0.
  orderNODE *newnode,
	    *currordernode;
  playerNODE *currplayernode;
  int count;

  if ((view < 1) || (view > 8)) // Make sure our order index is valid.
   return 0;// Indicate error.
  --view;
  // Delete any previous ordernig for that view.
  delorderlist(orderings[view]);
  orderings[view] = NULL;
  currordernode = NULL;
  // Now sort the list, and create a new orderings[view].
  while (*ordering != 0)
   {
    // Set up to find the next polygon in the list, in order.
    count = 1;
    currplayernode = list;
    // Find the polygon.
    while ((count != *ordering) && (currplayernode != NULL))
     {
      currplayernode = currplayernode->Next;
      ++count;
     };
    if (currplayernode == NULL) // Did we actually find a polygon?
     return 0;                 //    If not, indicate an error.
    newnode = new orderNODE;    //    If so, Add the node to the order list.
    if (newnode == NULL)
     {
      delorderlist(orderings[view]); // If alloc error, clean up,
      return -1;                     // and indicate it.
     }
    newnode->p = &(currplayernode->P);
    newnode->next = NULL;
    // Insert newnode into the linked list.
    if (currordernode == NULL)
     orderings[view] = newnode;
    else
     currordernode->next = newnode;
    currordernode = newnode;
    // Now look for the next item in the ordering list.
    ++ordering;
   }
  return 1;
 }

int player::getorder(point &Origin)
 {
  //   Computes the vextor from the object to the viewpoint, and returns the number
  // of the orderings array to use in displaying the player.
  float TmpX, TmpY, TmpZ;
  float x, y, z;
  float sinv, cosv;
  int dir;

  // Compute the vector from the player to the viewpoint.
  x = Origin.x - X;
  y = Origin.y - Y;
  z = Origin.z - Z;
  // Rotate the vector to take into account the Yaw, Pitch, and Roll of the player.
  if (A != 0)         // Z axis rotation
   {
    cosv = cost(A);
    sinv = sint(A);
    TmpX = ((x * cosv) + (y * sinv));
    TmpY = (-(x * sinv) + (y * cosv));
    x = TmpX;
    y = TmpY;
   }
  if (T != 0)          // X Axis rotation.
   {
    sinv = cost(T);
    cosv = sint(T);
    TmpY = ((y * cosv)+(z*sinv));
    TmpZ = (-(y * sinv)+(z*cosv));
    y = TmpY;
    z = TmpZ;
   }
  if (P != 0)         // Y axiz rotation
   {
    cosv = cost(P);
    sinv = sint(P);
    TmpX = ((x * cosv) + (z*sinv));
    TmpZ = (-(x * sinv) + (z*cosv));
    x = TmpX;
    z = TmpZ;
   }
  // Determine the octant of the vector.
  if (y >= 0)
   {
    if (x < 0)
     {
      if (z < 0)
       dir = 4;
      else
       dir = 1;
     }
    else
     {
      if (z < 0)
       dir = 3;
      else
       dir = 2;
     }
   }
  else
   {
    if (x < 0)
     {
      if (z < 0)
       dir = 8;
      else
       dir = 5;
     }
    else
     {
      if (z < 0)
       dir = 7;
      else
       dir = 6;
     }
   }
  return dir;
 }

player::player(float x, float y, float z)
 {
  X=x;Y=y;Z=z;
  Dx = Dy = Dz = 0;
  T = P = A = 0;
  list=NULL;
  orderings[0] = orderings[1] = orderings[2] = orderings[3] =
  orderings[4] = orderings[5] = orderings[6] = orderings[7] = NULL;
 }

player::~player()
 {
  dellist();
  delorderlist(orderings[0]);
  delorderlist(orderings[1]);
  delorderlist(orderings[2]);
  delorderlist(orderings[3]);
  delorderlist(orderings[4]);
  delorderlist(orderings[5]);
  delorderlist(orderings[6]);
  delorderlist(orderings[7]);
 }

int player::addpoly(polygon &P)
 {
  playerNODE *newnode, *currnode;

  newnode = new playerNODE;
  if (newnode == NULL)
   return 0;
  newnode->P = P;
  newnode->Next = NULL;
  currnode = list;
  if (currnode == NULL)
   {
    list = newnode;
    return 1;
   }
  while(currnode->Next != NULL)
   {
    currnode = currnode->Next;
   }
  currnode->Next = newnode;
  return 1;
 }

void player::move()
 {
  X += Dx;
  Y += Dy;
  Z += Dz;
 }

void player::chgvel(float ddX, float ddY, float ddZ)
 {
  Dx += ddX;
  Dy += ddY;
  Dz += ddZ;
 }

void player::moveto(float x, float y, float z)
 {
  X = x;
  Y = y;
  Z = z;
 }

void player::translate(float x, float y, float z)
 {
  X += x;
  Y += y;
  Z += z;
 }

void player::rotate(int p, int y, int r)
 {
  T += p;
  P += y;
  A += r;
  if (T >= 360) T -= 360;
  if (T < 0) T += 360;
  if (P >= 360) P -= 360;
  if (P < 0) P += 360;
  if (A >= 360) A -= 360;
  if (A < 0) A += 360;
 }

void player::rotateto(int p, int y, int r)
 {
  rotate(360-T,360-P,360-A);
  rotate(p,y,r);
 }

void player::scale(float pcnt)
 {
  playerNODE *currnode;

  currnode = list;
  while(currnode != NULL)
   {
    currnode->P.scale(pcnt);
    currnode = currnode->Next;
   }
 }

point player::getpos(void)
 {
  static point Centr;

  Centr.x = X;
  Centr.y = Y;
  Centr.z = Z;
  return Centr;
 }

player &player::operator = (player &P)
 {
  playerNODE *currnode;

  dellist();
  currnode = P.list;
  while(currnode != NULL)
   {
    if (!addpoly(currnode->P))
     {
      closegraph();
      printf("Memory allocation error in player::operator =(player &P)\n");
      exit(1);
     }
    currnode = currnode->Next;
   }
  return *this;
 }

Viewport3D::Viewport3D (int mode) : screen(mode)
 {
  Rp.x = 0.0;
  Rp.y = 0.0;
  Rp.z = 0.0;
  X1 = Rp;
  X1.x -= 1.0;
  X2 = Rp;
  X2.x += 1.0;
  Vp.x = 0.0;
  Vp.y = 10000.0;
  Vp.z = -20000.0;
  Ud.x = 0.0;
  Ud.y = 1.0;
  Ud.z = 0.0;
  No.x = Rp.x - Vp.x;
  No.y = Rp.y - Vp.y;
  No.z = Rp.z - Vp.z;
  Dist = sqrt(No.x*No.x+No.y*No.y+No.z*No.z);
  make_vpt_matrix();
 }

Viewport3D::~Viewport3D()
 {
  clear(0);
 }

float Identity[4][4] = {{1,0,0,0},{0,1,0,0},{0,0,1,0},{0,0,0,1}}; // The identity matrix.

void Viewport3D::make_identity(float matrix[4][4])
 {
  // Purpose: Copies Identity into matrix.

  int i,j;

  for (i=0;i<4;++i)
   for (j=0;j<4;++j)
    matrix[i][j] = Identity[i][j];
 }

void Viewport3D::matrix_multiply (float matrix1[4][4], float matrix2[4][4])
 {
  // Purpose: Cross multiplies mareix1 and matrix2, and places the result back into matrix1.

  float h[4][4];
  int i,j,k;
  float sum;

  for (i=0; i<4; ++i)
   for (j=0; j<4; ++j)
    h[i][j] = matrix1[i][j];
  for (i=0; i<4; ++i)
   for (j=0; j<4; ++j)
    {
     sum = 0;
     for (k=0; k<4; ++k)
      sum += h[i][k] * matrix2[k][j];
     matrix1[i][j] = sum;
    }
 }

void Viewport3D::make_vpt_matrix(void)
 {
  //    Purpose: Takes Rp, No, Ud, and Dist and creates the transformation matrix for the
  //  view plane tranform.

  float M[4][4];
  float p,w;
  point N,U;
  float D;

  N = No;
  U = Ud;
  D = Dist;

  p = sqrt(N.y*N.y+N.z*N.z);
  if (p > 0.00001)
   {
    U.x = U.x*p/D-N.x/(D*p)*(U.y*N.y+U.z*N.z);
    U.y = (U.y*N.z+U.z*N.y)/p;
   }
  else
   U.x = -U.z*N.x/D;
  w = sqrt(U.x*U.x+U.y*U.y);
  make_identity(VPT_Matrix);
  VPT_Matrix[3][0] = -Rp.x+N.x;
  VPT_Matrix[3][1] = -Rp.y+N.y;
  VPT_Matrix[3][2] = -Rp.z+N.z;
  if (p > 0.00001)
   {
    make_identity(M);
    M[1][1] = N.z/p;
    M[1][2] = N.y/p;
    M[2][1] = -N.y/p;
    M[2][2] = N.z/p;
    matrix_multiply(VPT_Matrix,M);
   }
  make_identity(M);
  M[0][0] = p/D;
  M[0][2] = N.x/D;
  M[2][0] = -N.x/D;
  M[2][2] = p/D;
  matrix_multiply(VPT_Matrix,M);
  if (w > 0.00001)
   {
    make_identity(M);
    M[0][0] = U.y/w;
    M[0][1] = U.x/w;
    M[1][0] = -U.x/w;
    M[1][1] = U.y/w;
    matrix_multiply(VPT_Matrix,M);
   }
 }

void Viewport3D::transform(point &P)
 {
  // Actually transforms the point P using the transformation matrix.
  float sumx, sumy, sumz;

  sumx = (P.x*VPT_Matrix[0][0] + P.y*VPT_Matrix[1][0] + P.z*VPT_Matrix[2][0] + VPT_Matrix[3][0]);
  sumy = (P.x*VPT_Matrix[0][1] + P.y*VPT_Matrix[1][1] + P.z*VPT_Matrix[2][1] + VPT_Matrix[3][1]);
  sumz = (P.x*VPT_Matrix[0][2] + P.y*VPT_Matrix[1][2] + P.z*VPT_Matrix[2][2] + VPT_Matrix[3][2]);
  P.x = sumx;
  P.y = sumy;
  P.z = sumz;
 }

int Viewport3D::poly_visible(polygon &P)
 {
  //
  //  This is a temporary implementation. It checks the center of each polygon to see if it maps within the viewplane.
  // It should check for other things.
  //
  point Cntr;

  P.getcenter(Cntr);
  transform(Cntr);
  if ((Cntr.z) <= 0)
   return 0;
  if (((Cntr.x*512)/(Cntr.z+512) > 320) || ((Cntr.x*512)/(Cntr.z+512) < -320))
   return 0;
  if (((Cntr.y*512)/(Cntr.z+512) > 175) || ((Cntr.y*512)/(Cntr.z+512) < -175))
   return 0;
  return 1;
 }

int Viewport3D::player_visible(player &P)
 {
  //
  //  This is a temporary implementation. It checks the center of each player to see if it maps within the viewplane.
  // It should check for other things.
  //
  point Pt(P.X,P.Y,P.Z);

  transform(Pt);
  if  ((Pt.z) <= 0)
   return 0;
  if ((((Pt.x*512)/(Pt.z+512)) > 320) || (((Pt.x*512)/(Pt.z+512)) < -320))
   return 0;
  if ((((Pt.y*512)/(Pt.z+512)) > 175) || (((Pt.y*512)/(Pt.z+512)) < -175))
   return 0;
  return 1;
 }
char Viewport3D::GetRCode(point &P)
 {
  char Rcode;

  Rcode = 0x00;
  if (P.x < floor(-320-0.6237816764*(P.z-1.0)))
   Rcode |= 0x20;
  if (P.x > ceil(320.0+0.6237816764*(P.z-1.0)))
   Rcode |= 0x10;
  if (P.y < floor(-175.0-0.3411306043*(P.z-1.0)))
   Rcode |= 0x08;
  if (P.y > ceil(175.0+0.3411306043*(P.z-1.0)))
   Rcode |= 0x04;
  if (P.z < 1)
   Rcode |= 0x02;
  return Rcode;
 }

int Viewport3D::Clip_Line(point &P1, point &P2)
 {
  double x,y,z,s;
  char Rcode,
       Rcode1,
       Rcode2;

  // Determine region codes for end points.
  Rcode1 = GetRCode(P1);
  Rcode2 = GetRCode(P2);
  while ((Rcode1 != 0x00) || (Rcode2 != 0x00))
   {
    if ((Rcode1 & Rcode2) != 0x00)
     return 0;
    if ((Rcode1 | Rcode2) == 0x00)
     return 1;
    if (Rcode1 != 0x00)
     Rcode = Rcode1;
    else
     Rcode = Rcode2;
    if (Rcode & 0x02)
     {
      s=((P1.z-1.0)/(P1.z-P2.z));
      x = P1.x+s*(P2.x-P1.x);
      y = P1.y+s*(P2.y-P1.y);
      z = 1;
     }
    else if (Rcode & 0x04)
     {
      s = (P1.y-175.0+0.3411306043*(1.0-P1.z))/(P1.y-P2.y+0.3411306043*(P2.z-P1.z));
      x = floor(P1.x+s*(P2.x-P1.x));
      y = floor(P1.y+s*(P2.y-P1.y));
      z = floor(P1.z+s*(P2.z-P1.z));
     }
    else if (Rcode & 0x08)
     {
      s = (P1.y+175.0+(-0.3411306043)*(1.0-P1.z))/(P1.y-P2.y+(-0.3411306043)*(P2.z-P1.z));
      x = ceil(P1.x+s*(P2.x-P1.x));
      y = ceil(P1.y+s*(P2.y-P1.y));
      z = ceil(P1.z+s*(P2.z-P1.z));
     }
    else if (Rcode & 0x10)
     {
      s = (P1.x-320.0+0.6237816764*(1.0-P1.z))/(P1.x-P2.x+0.6237816764*(P2.z-P1.z));
      x = floor(P1.x+s*(P2.x-P1.x));
      y = floor(P1.y+s*(P2.y-P1.y));
      z = floor(P1.z+s*(P2.z-P1.z));
     }
    else if (Rcode & 0x20)
     {
      s = (P1.x+320.0+(-0.6237816764)*(1.0-P1.z))/(P1.x-P2.x+(-0.6237816764)*(P2.z-P1.z));
      x = ceil(P1.x+s*(P2.x-P1.x));
      y = ceil(P1.y+s*(P2.y-P1.y));
      z = ceil(P1.z+s*(P2.z-P1.z));
     }
    if (Rcode1 != 0x00)
     {
      P1.x = x;
      P1.y = y;
      P1.z = z;
     }
    else
     {
      P2.x = x;
      P2.y = y;
      P2.z = z;
     }
    Rcode1 = GetRCode(P1);
    Rcode2 = GetRCode(P2);
   }
  return 1;
 }

void Viewport3D::drawline(float x1, float y1, float z1, float x2, float y2, float z2, int c)
 {
  float Tmpx1,
	Tmpy1,
	Tmpx2,
	Tmpy2;
  point P1,P2;

  P1.x = x1; P1.y = y1; P1.z = z1;
  P2.x = x2; P2.y = y2; P2.z = z2;
  transform(P1);
  transform(P2);
  if (Clip_Line(P1,P2))
   {
    Tmpx1 = ((P1.x*512.0)/(P1.z+512))+320.0; // Perspective Transformation, and map to physical screen.
    Tmpy1 = 175.0-((P1.y*512.0)/(P1.z+512));
    Tmpx2 = ((P2.x*512.0)/(P2.z+512))+320.0;
    Tmpy2 = 175.0-((P2.y*512.0)/(P2.z+512));
    sline( (long)Tmpx1,(long)Tmpy1,(long)Tmpx2,(long)Tmpy2,c); // Draw the line.
   }
 }

void Viewport3D::Clip_Poly_L (point &P, polygon &Po)
 {
  point I;
  double s;
  char R = 0;

  if (LastL.x >= (-320-0.625*LastL.z))
   R |= 0x10;
  if (P.x >= (-320-0.625*P.z))
   R |= 0x01;
  if ((R == 0x10) || (R == 0x01))
   {
    s = (LastL.x+320+0.625*LastL.z)/(LastL.x-P.x+(-0.625)*(P.z-LastL.z));
    I.x = (LastL.x+s*(P.x-LastL.x));
    I.y = (LastL.y+s*(P.y-LastL.y));
    I.z = (LastL.z+s*(P.z-LastL.z));
   }
  switch (R)
   {
    case 0x11: Clip_Poly_R(P,Po);break;
    case 0x10: Clip_Poly_R(I,Po);break;
    case 0x01: Clip_Poly_R(I,Po);Clip_Poly_R(P,Po); break;
    default: break;
   }
  LastL = P;
  return;
 }

void Viewport3D::Clip_Poly_R (point &P, polygon &Po)
 {
  point I;
  double s;
  char R = 0;

  if (P.x <= (320+0.625*P.z))
   R |= 0x01;
  if (LastR.x <= (320+0.625*LastR.z))
   R |= 0x02;
  if ((R == 0x02) || (R == 0x01))
   {
    s = (LastR.x-320-0.625*LastR.z)/(LastR.x-P.x+0.625*(P.z-LastR.z));
    I.x = (LastR.x+s*(P.x-LastR.x));
    I.y = (LastR.y+s*(P.y-LastR.y));
    I.z = (LastR.z+s*(P.z-LastR.z));
   }
  switch (R)
   {
    case 0x03: Clip_Poly_B(P,Po);break;
    case 0x02: Clip_Poly_B(I,Po);break;
    case 0x01: Clip_Poly_B(I,Po);Clip_Poly_B(P,Po); break;
    default: break;
   }
  LastR = P;
  return;
 }

void Viewport3D::Clip_Poly_B (point &P, polygon &Po)
 {
  point I;
  double s;
  char R = 0;

  if (P.y >= (-175-0.341796875*P.z))
   R |= 0x01;
  if (LastB.y >= (-175-0.341796875*LastB.z))
   R |= 0x02;
  if ((R == 0x02) || (R == 0x01))
   {
    s = (LastB.y+175+0.341796875*LastB.z)/(LastB.y-P.y+(-0.341796875)*(P.z-LastB.z));
    I.x = (LastB.x+s*(P.x-LastB.x));
    I.y = (LastB.y+s*(P.y-LastB.y));
    I.z = (LastB.z+s*(P.z-LastB.z));
   }
  switch (R)
   {
    case 0x03: Clip_Poly_T(P,Po);break;
    case 0x02: Clip_Poly_T(I,Po);break;
    case 0x01: Clip_Poly_T(I,Po);Clip_Poly_T(P,Po); break;
    default: break;
   }
  LastB = P;
  return;
 }

void Viewport3D::Clip_Poly_T (point &P, polygon &Po)
 {
  point I;
  double s;
  char R = 0;

  if (P.y <= (175+0.341796875*P.z))
   R |= 0x01;
  if (LastT.y <= (175+0.341796875*LastT.z))
   R |= 0x02;
  if ((R == 0x02) || (R == 0x01))
   {
    s = (LastT.y-175-0.341796875*LastT.z)/(LastT.y-P.y+0.341796875*(P.z-LastT.z));
    I.x = ceil(LastT.x+s*(P.x-LastT.x));
    I.y = ceil(LastT.y+s*(P.y-LastT.y));
    I.z = ceil(LastT.z+s*(P.z-LastT.z));
   }
  switch (R)
   {
    case 0x03: Clip_Poly_N(P,Po);break;
    case 0x02: Clip_Poly_N(I,Po);break;
    case 0x01: Clip_Poly_N(I,Po);Clip_Poly_N(P,Po); break;
    default: break;
   }
  LastT = P;
  return;
 }

void Viewport3D::Clip_Poly_N (point &P, polygon &Po)
 {
  point I;
  float s;
  char R = 0;

  if (P.z >= 1)
   R |= 0x01;
  if (LastN.z >= 1)
   R |= 0x02;
  if ((R == 0x02) || (R == 0x01))
   {
    s=((LastN.z-1.0)/(LastN.z-P.z));
    I.x = LastN.x+s*(P.x-LastN.x);
    I.y = LastN.y+s*(P.y-LastN.y);
    I.z = 1;
   }
  switch (R)
   {
    case 0x03: Po.addpoint(P);break;
    case 0x02: Po.addpoint(I);break;
    case 0x01: Po.addpoint(I);Po.addpoint(P); break;
    default: break;
   }
  LastN = P;
  return;
 }

int Viewport3D::Clip_Poly(polygon &Pi, polygon &Po)
 {
  //
  // Clips the polygon Pi against the view frustrum, and returns the
  // clipped polygon in Po.
  //

  polyNODE *pt, *lpt;
  lpt = pt = Pi.list; // Initialize Pointers.
  while (pt != NULL) // Find the last point of the list.
   {
    lpt = pt;
    pt = pt->Next;
   }
  pt = Pi.list;
  LastL = LastR = LastT = LastB = LastN = lpt->P; // Initialize lastpoints.
  // Now, do a dry run to set the lastpoints for the real clip.
  Clip_Poly_L(pt->P, Po);
  Po.dellist();
  // Now, we do the real clipping...
  while(pt != NULL)
   {
    Clip_Poly_L(pt->P, Po); // Clip this edge.
    pt = pt->Next;
   }
  Clip_Poly_L((Pi.list)->P,Po);
  if (Po.list != NULL)
   return 1;
  else
   return 0;
 }

void Viewport3D::drawpoly(float x, float y, float z, polygon &P)
 {
  polyNODE *currnode, *nextnode;

  P.translate(x,y,z); // Translate it out [X,Y,Z]
  if (poly_visible(P)) // Is it visible?
   {
    currnode = P.list;
    while (currnode != NULL)// If so, go through all its points,
     {
      nextnode = currnode->Next;
      if (nextnode == NULL)
       nextnode = P.list;
      drawline(currnode->P.x,currnode->P.y,currnode->P.z,nextnode->P.x,nextnode->P.y,nextnode->P.z,P.color); // and draw the lines.
       currnode = currnode->Next;
     }
   }
  P.translate(-x,-y,-z); // Then translate it back.
 }

void Viewport3D::drawfillpoly(float x, float y, float z, polygon &P)
 {
  polyNODE *currnode;
  polygon Pi,Po;
  int points[30],
      index;
  point Pt;

  P.translate(x,y,z); // Translate it by [X,Y,Z].
  Pi=P;
  currnode = Pi.list;
  while (currnode != NULL) // If so, go through all its points,
   {
    transform(currnode->P); // Transform the point,
    currnode = currnode->Next;
   }
  if(Clip_Poly(Pi,Po))
   {
    currnode = Po.list;
    index = 0;
    while (currnode != NULL) // If so, go through all its points,
     {
      Pt = currnode->P;
      points[index++] = (int)(((Pt.x*512.0)/(Pt.z+512.0))+320.0); // Do the perspective transform,
      points[index++] = (int)(175.0-((Pt.y*512.0)/(Pt.z+512.0))); // and add it to a list of points.
      currnode = currnode->Next;
     }
    points[index++] = points[0]; // close the loop,
    points[index++] = points[1];
    sfillpoly(index/2, points, P.color, 0);
   }
  P.translate(-x,-y,-z); // translate it back.
 }


void Viewport3D::translateR(float x, float z)
 {
  point Vec;
  float tx,tz;

  Vec.x = X2.x-Rp.x;
  Vec.y = 0;
  Vec.z = X2.z-Rp.z;
  Vec.normalize();

  tx = x*Vec.x - z*Vec.z;
  tz = x*Vec.z + z*Vec.x;
  x = tx;
  z = tz;

  Rp.x += x;
  Rp.z += z;
  X1.x += x;
  X1.z += z;
  X2.x += x;
  X2.z += z;
  Vp.x += x;
  Vp.z += z;
  make_vpt_matrix();
 }

void Viewport3D::scaleD(float s)
 {
  // Translate the viewpoint by -Rp
  Vp.x -= Rp.x;
  Vp.y -= Rp.y;
  Vp.z -= Rp.z;
  // Scale it..
  Vp.x *= s;
  Vp.y *= s;
  Vp.z *= s;
  // Translate the viewpoint back;
  Vp.x += Rp.x;
  Vp.y += Rp.y;
  Vp.z += Rp.z;
  // Re-calculate the normal, and the transformation.
  No.x = Rp.x - Vp.x;
  No.y = Rp.y - Vp.y;
  No.z = Rp.z - Vp.z;;
  Dist = sqrt(No.x*No.x+No.y*No.y+No.z*No.z);
  make_vpt_matrix();
 }

void Viewport3D::rotateY(int P)
 {
  float TmpX,
	TmpZ;
  float costp, sintp;

  if (P == 0)
   return;
  P = -P;
  costp = cost(P);
  sintp = sint(P);
  // Translate the viewpoint, X1, and X2 by -Rp
  Vp.x -= Rp.x;
  Vp.y -= Rp.y;
  Vp.z -= Rp.z;
  X1.x -= Rp.x;
  X1.y -= Rp.y;
  X1.z -= Rp.z;
  X2.x -= Rp.x;
  X2.y -= Rp.y;
  X2.z -= Rp.z;

  // Rotate the Viewpoint.
  TmpX = ((Vp.x * costp) - (Vp.z*sintp));
  TmpZ = ((Vp.x * sintp) + (Vp.z*costp));
  Vp.x = TmpX;
  Vp.z = TmpZ;

  // Rotate X1
  TmpX = ((X1.x * costp) - (X1.z*sintp));
  TmpZ = ((X1.x * sintp) + (X1.z*costp));
  X1.x = TmpX;
  X1.z = TmpZ;

  // Rotate X2
  TmpX = ((X2.x * costp) - (X2.z*sintp));
  TmpZ = ((X2.x * sintp) + (X2.z*costp));
  X2.x = TmpX;
  X2.z = TmpZ;

  // Translate the viewpoint, X1, and X2 back;
  Vp.x += Rp.x;
  Vp.y += Rp.y;
  Vp.z += Rp.z;
  X1.x += Rp.x;
  X1.y += Rp.y;
  X1.z += Rp.z;
  X2.x += Rp.x;
  X2.y += Rp.y;
  X2.z += Rp.z;

  // Re-calculate the normal and the tranformation.
  No.x = Rp.x - Vp.x;
  No.y = Rp.y - Vp.y;
  No.z = Rp.z - Vp.z;
  Dist = sqrt(No.x*No.x+No.y*No.y+No.z*No.z);
  make_vpt_matrix();
 }

void Viewport3D::rotateUD(int t)
 {
  float TmpX,
	TmpY,
	TmpZ,
	A,
	B,
	C,
	L,
	P,
	costv,
	sintv;
  float pl, al;

  if (t == 0)
   return;

  A = X2.x-X1.x;
  B = X2.y-X1.y;
  C = X2.z-X1.z;
  L = sqrt(A*A+B*B+C*C);
  if (B*B+C*C == 0)
   P = 0;
  else
   P = sqrt(B*B+C*C);

  // Translate the viewpoint by -X1
  Vp.x -= X1.x;
  Vp.y -= X1.y;
  Vp.z -= X1.z;

  // Now Align with Z axis.
  pl = P/L;
  al = A/L;
  TmpX = Vp.x*pl - Vp.z*al;
  TmpZ = Vp.x*al + Vp.z*pl;
  Vp.x = TmpX;
  Vp.z = TmpZ;

  // Now rotate the point around Z axis.
  costv = cost(t);
  sintv = sint(t);
  TmpX = Vp.x*costv-Vp.y*sintv;
  TmpY = Vp.x*sintv+Vp.y*costv;
  Vp.x = TmpX;
  Vp.y = TmpY;

  // Now rotate back from the Z axis.
  TmpX = Vp.x*pl + Vp.z*al;
  TmpZ = -Vp.x*al + Vp.z*pl;
  Vp.x = TmpX;
  Vp.z = TmpZ;


  // Translate the viewpoint back;
  Vp.x += X1.x;
  Vp.y += X1.y;
  Vp.z += X1.z;

  // Re-calculate the normal and the tranformation.
  No.x = Rp.x - Vp.x;
  No.y = Rp.y - Vp.y;
  No.z = Rp.z - Vp.z;
  Dist = sqrt(No.x*No.x+No.y*No.y+No.z*No.z);
  make_vpt_matrix();
 }

void Viewport3D::drawplayer(player &P)
 {
  orderNODE*currnode;
  polygon current;


  if (!player_visible(P)) // Is the player visible?
   return;
  currnode = P.orderings[P.getorder(Vp)-1];
  while(currnode != NULL)
   {
    current = *(currnode->p);
    current.rotate(P.T,P.P,P.A);
    drawfillpoly(P.X,P.Y,P.Z,current); // Draw filled polygon.
    currnode = currnode->next;
   }
 }