#include <conio.h>
#include <math.h>
#include <stdlib.h>
#include <time.h>
#include <dos.h>
#include <wgt5.h>
#include <wgtvesa.h>

/* WGT 3D Rendering Library
   Copyright 1995 Egerter Software */

/* Triangle Rendering Methods */
#define WIREFRAME              0
#define SOLID                  1
#define GOURAUD                2
#define TEXTURE                3
#define FLAT_SHADED_TEXTURE    4
#define GOURAUD_SHADED_TEXTURE 5
#define TRANSLUCENT_TEXTURE    6
#define TRANSLUCENT_GOURAUD    7

#define M_PI 3.1415926535897932384626

#define POLYBUF  10000           /* Maximum polys per frame */
#define OBJBUF     100          /* Maximum objects in world */
#define MAXKEYS 100             /* Maximum animation keys per object */
#define MAXCHILDREN 10          /* Maximum child objects per parent */
#define MAX_TEXTURES 10         /* Maximum textures loaded at once */

float render_shades = 64;       /* Number of colors reserved for shading a
				   single color */
int render_all = 0;             /* 0 = backface removal */


/* Our 3D system structures */
typedef struct
{
  float         x, y, z;
} point3d;                      /* A single 3D point */


typedef struct
{
  point3d       local;
  point3d       world;
  point3d       normal;
  int           screenx, screeny;
  unsigned char connected;
  unsigned char rotated;
} vertex;                       /* A vertex of a polygon */


typedef struct
{
  int           vertex1;
  int           vertex2;
  int           vertex3;
  int           type;
  point3d       normal;
  point3d       center;
  short         status;
  unsigned char color;
} triangle_3d;                   /* A 3D triangle */


typedef struct
{
 float          sx, sy, sz; /* Scale */
 float          tx, ty, tz; /* Translation */
 float          rx, ry, rz; /* Rotation */
 int            framenumber;
 char           update_scale;
 char           update_rotation;
 char           update_translation;
} keydata;                      /* Key animation data */


typedef struct
{
  int           start_poly;
  int           end_poly;
  int           startpoints;
  int           points;

  float         minx;
  float         maxx;
  float         miny;
  float         maxy;
  float         minz;
  float         maxz;

  /* Added for animation */
  float         translatex;
  float         translatey;  /* Added to each vertex each frame */
  float         translatez;

  float         pivotx;
  float         pivoty;  /* Pivot location for this object */
  float         pivotz;

  float         scalex;
  float         scaley;  /* Multiplied by each vertex based on the distance */
  float         scalez;  /* from the pivot location */

  float         rotatex;
  float         rotatey;  /* Each vertex is rotated by this amount */
  float         rotatez;

  /* Hierarchy */
  short int     level;
  short         maxchild;               /* Number of children */
  unsigned short int children[MAXCHILDREN];
  short         parent;                 /* Parent of this object */

  keydata       keylist[MAXKEYS];       /* Array of key frames */
  int           maxframe;               /* Max animation frame */
  int           currentframe;           /* Current animation frame */
  
  int           tstepsleft;             /* Translation steps left */
  int           rstepsleft;             /* Rotation steps left */
 
  char          name[15];               /* Name of object */
} object_3d;


struct {
  float lens;
  float fov;
} cameras[9] = { 15, 115, 20, 94.28571, 24, 84, 28, 76.36, 35, 63,
		 50, 46, 85, 28, 135, 18, 200, 12 };

/* For shaded texture mapping */
unsigned char *render_shadetable;

tpoint          renderpoly[3];  /* Used to draw one triangle at a time */

typedef struct {
  int           number;                 /* Sorted polygon # */
  float         depth;                  /* Center depth of polygon */
} sort;

sort sorted_polys[POLYBUF];


typedef struct
{
 long           sx[3];
 long           sy[3];
} polytexture;

polytexture     ptext[POLYBUF];         /* Holds texture coordinates for each
					   triangle in the polylist */

point3d         camera;                 /* Camera position */
point3d         camera_norm;            /* Camera position */
point3d         focus;                  /* Target position */
point3d         tvect;                  /* Eye vector */

struct {
  point3d       world;
  point3d       normal;
} light;                                /* Light position */

float           fov;                    /* Field of view in degrees */
float           dist;                   /* Distance from viewer */
float           m11, m12, m21, m22, m23, m31, m32, m33;

triangle_3d     polylist[POLYBUF];      /* Polygon list for world */
object_3d       objectlist[OBJBUF];     /* Object list for world */

block           textures[MAX_TEXTURES];

float           camera_x, camera_y, camera_z;
float           focus_x, focus_y, focus_z;
float           light_x, light_y, light_z;
float           camera_angle;
int             worldpoints;
int             totalpoly = 0;
int             totalobjects = 0;


/* The following are defined in wrendani.c, used for moving, scaling, and 
   rotating objects. */

extern void translate_object (object_3d *obj, vertex *pts, float x, float y, 
			      float z);
/* Adds x,y,z to each 3D point in the object */


extern void scale_object     (object_3d *obj, vertex *pts, 
			      float x, float y, float z,
			      float px, float py, float pz);
/* Scales by x,y,z, based on the distance from px,py,pz */


extern void rotate_object    (object_3d *obj, vertex *pts, 
			      float x, float y, float z,
			      float px, float py, float pz);
/* Rotates the object by x,y,z around the center point px,py,pz */


extern void set_object_pivot (object_3d *obj, float x, float y, float z);
/* Sets x,y,z as the center of the object */


extern void rotate_point (float *ptx, float *pty, float *ptz,
			  float x, float y, float z,
			  float px, float py, float pz);
/* Rotates a single point by x,y,z around px,py,pz */


/* The following are defined in this file: */

void map_plane (float x, float y, float z, long *u, long *v, float scale);
/* Given a 3D point, maps the point onto the x/y plane */

float dot_product (point3d *a, point3d *b);
/* Compute the dot product between two 3D vectors */

void wset_focus (float a, float b, float c);
/* Sets where the camera is looking at */

void wset_camera (float a, float b, float c);
/* Sets the position of the camera */

void wset_light (float a, float b, float c);
/* Sets the position of the light */

void wset_view (void);
/* Sets up the viewing matrix based on the camera position and direction */

void wworld_2_view (object_3d *obj, int *drawtotal, vertex *vlist);
/* Transform the 3D coordinates to 2D coordinates */

void calc_normal (vertex *wp1, vertex *wp2, vertex *wp3, point3d *norm);
/* Calculates a normal vector given three vertices */

void map_points (short obj, vertex *pts, float scale);
/* Map all the texture coordinates onto a plane, given an object and a scale */

int sortkeys (keydata *a, keydata *b);
/* Sorts the animation keys by their frame number */

void adjust_object (object_3d *objp, float rx, float ry, float rz, 
		    float px, float py, float pz);
/* Modify an object's animation data if it has a parent */

void duplicate_frame (object_3d *obj, int frame, int framenumber);
/* Copies one animation key to another */

void build_hierarchy (void);
/* Connects children and parents together */

void add_key_translate (object_3d *obj, int framenumber, float x, float y, 
			float z);
/* Adds a translation animation key */

void add_key_scale (object_3d *obj, int framenumber, float x, float y,
		    float z);
/* Adds a scale animation key */

void add_key_rotate (object_3d *obj, int framenumber, float x, float y, 
		    float z);
/* Adds a rotation animation key */

int find_object (char *name);
/* Given the name of an object, returns the index in objectlist */

void add_child (int parent, int child);
/* Adds a child to a parent object */

void load_3ds (char *filename, vertex *pts);
/* Loads a 3D Studio file */

int compare (sort *a, sort *b);
/* Used for sorting */

void draw_polys (vertex *pts, int drawtotal);
/* Draws the polygons */

void wset_object_color (int obj, unsigned char col);
/* Sets the color of each triangle in an object, used for solid, gouraud, and
   texture.  For textured objects, the color represents the texture number
   from the textures array. */

void wset_object_type (int obj, int type);
/* Sets the rendering method for an object */

void wreset_3d_system (void);
/* Resets the engine so you can load in a new object */

/***************************************************************************/
float dot_product (point3d *a, point3d *b)
/* Compute the dot product between two 3D vectors */
{
  return ( (a->x * b->x) + (a->y * b->y) + (a->z * b->z) );
}


void wset_focus (float a, float b, float c)
/* Sets where the camera is looking at */
{
  focus.x = a;
  focus.y = b;
  focus.z = c;
}


void wset_camera (float a, float b, float c)
/* Sets the position of the camera */
{
float norm;

  camera.x = a;
  camera.y = b;
  camera.z = c;
  norm = sqrt( (a * a) + (b * b) + (c * c) );
  camera_norm.x = a / norm;
  camera_norm.y = b / norm;
  camera_norm.z = c / norm;
}


void wset_light (float a, float b, float c)
/* Sets the position of the light */
{
float norm;

  light.world.x = a;
  light.world.y = b;
  light.world.z = c;
  norm = sqrt( (a * a) + (b * b) + (c * c) );
  light.normal.x = a / norm;
  light.normal.y = b / norm;
  light.normal.z = c / norm;
}


void wset_view (void)
/* Sets up the viewing matrix based on the camera position and direction */
{
float lambda;
float tlength;

  lambda = ((fov / 180.0) * 3.1415);
  dist = 100.0 / sin(lambda / 2);

  tvect.x = focus.x - camera.x;
  tvect.y = focus.y - camera.y;
  tvect.z = focus.z - camera.z;
  lambda = sqrt ( (tvect.x * tvect.x) + (tvect.y * tvect.y) );
  tlength = sqrt ( (tvect.x * tvect.x) + (tvect.y * tvect.y) + (tvect.z * tvect.z) );

  m11 = tvect.y / lambda;
  m12 = -tvect.x / lambda;
  m21 = (tvect.x * tvect.z) / (lambda * tlength);
  m22 = (tvect.y * tvect.z) / (lambda * tlength);
  m23 = -lambda / tlength;
  m31 = tvect.x / tlength;
  m32 = tvect.y / tlength;
  m33 = tvect.z / tlength;
}


void wworld_2_view (object_3d *obj, int *drawtotal, vertex *vlist)
/* Transform the 3D coordinates to 2D coordinates */
{
int poly, start, finish;
float xo, yo, zo;
float xo2, yo2, zo2;
float xo3, yo3, zo3;
float min_depth, max_depth, temp;
triangle_3d *ptr;
point3d lineofsite;
vertex *vptr;
vertex *vptr2;
vertex *vptr3;
float xres, yres;

  xres = (float)WGT_SYS.xres / 2.0;
  yres = (float)WGT_SYS.yres / 2.0;
  start = obj->start_poly;
  finish = obj->end_poly;

  for (poly = start; poly <= finish; poly++)
  {
    ptr = &polylist[poly];
    vptr = &vlist[ptr->vertex1];
    vptr2 = &vlist[ptr->vertex2];
    vptr3 = &vlist[ptr->vertex3];

    lineofsite.x = vptr->local.x - camera.x;
    lineofsite.y = vptr->local.y - camera.y;
    lineofsite.z = vptr->local.z - camera.z;
    if ((dot_product (&lineofsite, &ptr->normal) >= 0) || (render_all))
    {
      if (vptr->rotated == 0)
      {
       xo = vptr->local.x - camera.x;
       yo = vptr->local.y - camera.y;
       zo = vptr->local.z - camera.z;

       vptr->world.z = (xo * m31) + (yo * m32) + (zo * m33);
      }
      if (vptr2->rotated == 0)
      {
       xo2 = vptr2->local.x - camera.x;
       yo2 = vptr2->local.y - camera.y;
       zo2 = vptr2->local.z - camera.z;

       vptr2->world.z = (xo2 * m31) + (yo2 * m32) + (zo2 * m33);
      }
      if (vptr3->rotated == 0)
      {
       xo3 = vptr3->local.x - camera.x;
       yo3 = vptr3->local.y - camera.y;
       zo3 = vptr3->local.z - camera.z;

       vptr3->world.z = (xo3 * m31) + (yo3 * m32) + (zo3 * m33);
      }
      if ( (vptr->world.z > 0) || (vptr2->world.z > 0) || (vptr3->world.z > 0))
      {
	if (vptr->rotated == 0) 
	{
	  vptr->world.x = (xo * m11) + (yo * m12);
	  vptr->world.y = (xo * m21) + (yo * m22) + (zo * m23);
	  /* PROJECTION */
	  temp = dist / vptr->world.z;
	  vptr->screenx = (vptr->world.x * temp) + xres;
	  vptr->screeny = (vptr->world.y * temp) + yres;
	  vptr->rotated = 1;
	}
	if (vptr2->rotated == 0)
	{
	  vptr2->world.x = (xo2 * m11) + (yo2 * m12);
	  vptr2->world.y = (xo2 * m21) + (yo2 * m22) + (zo2 * m23);
	  /* PROJECTION */
	  temp = dist / vptr2->world.z;
	  vptr2->screenx = (vptr2->world.x * temp) + xres;
	  vptr2->screeny = (vptr2->world.y * temp) + yres;
	  vptr2->rotated = 1;
	}
	if (vptr3->rotated == 0)
	{
	  vptr3->world.x = (xo3 * m11) + (yo3 * m12);
	  vptr3->world.y = (xo3 * m21) + (yo3 * m22) + (zo3 * m23);
	  /* PROJECTION */
	  temp = dist / vptr3->world.z;
	  vptr3->screenx = (vptr3->world.x * temp) + xres;
	  vptr3->screeny = (vptr3->world.y * temp) + yres;
	  vptr3->rotated = 1;
	}
	if ( ((vptr->screenx >= 0) || (vptr2->screenx >= 0) || (vptr3->screenx >= 0))
	  && ((vptr->world.z > 1) && (vptr2->world.z > 1) && (vptr3->world.z > 1))
	  && ((vptr->screeny >= 0) || (vptr2->screeny >= 0) || (vptr3->screeny >= 0))
	  && ((vptr->screenx < WGT_SYS.xres) || (vptr2->screenx < WGT_SYS.xres) || (vptr3->screenx < WGT_SYS.xres))
	  && ((vptr->screeny < WGT_SYS.yres) || (vptr2->screeny < WGT_SYS.yres) || (vptr3->screeny < WGT_SYS.yres)) )
	{
	  sorted_polys[*drawtotal].number = poly;
	  /* Rotate center of polygon */

	  /* Center of polygon, Z sorting */
	  /* xo = ptr->center.x - camera.x;
	  yo = ptr->center.y - camera.y;
	  zo = ptr->center.z - camera.z;
	  sorted_polys[*drawtotal].depth = (xo * m31) + (yo * m32) + (zo * m33); */
	  
	  /* Farthest Z Sorting */
	  /* zo = vptr->world.z;
	  if (vptr2->world.z > zo)
	    zo = vptr2->world.z;
	  if (vptr3->world.z > zo)
	    zo = vptr3->world.z;
	  sorted_polys[*drawtotal].depth = zo;*/

	  /* Average Z Sorting */
	  sorted_polys[*drawtotal].depth = (vptr->world.z + vptr2->world.z +
	    vptr3->world.z) / 3.0;

	  *drawtotal = *drawtotal + 1;
	}
      }
    }
  }
}


void calc_normal (vertex *wp1, vertex *wp2, vertex *wp3, point3d *norm)
/* Calculates a normal vector given three vertices */
{
point3d a, b;

  a.x = wp2->local.x - wp1->local.x;
  a.y = wp2->local.y - wp1->local.y;
  a.z = wp2->local.z - wp1->local.z;

  b.x = wp3->local.x - wp1->local.x;
  b.y = wp3->local.y - wp1->local.y;
  b.z = wp3->local.z - wp1->local.z;

  norm->x = (a.y * b.z) - (a.z * b.y);
  norm->y = (a.z * b.x) - (a.x * b.z);
  norm->z = (a.x * b.y) - (a.y * b.x);
  
  /* Normalize it */
  a.x = sqrt((norm->x * norm->x) + (norm->y * norm->y) + (norm->z * norm->z));
  norm->x /= a.x;
  norm->y /= a.x;
  norm->z /= a.x;
}





void map_points (int obj, vertex *pts, float scale)
/* Map all the texture coordinates onto a plane, given an object and a scale */
{
int ply;
int vert;
triangle_3d *polyptr;

 
  for (ply = objectlist[obj].start_poly; ply <= objectlist[obj].end_poly; ply++)
   { 
    polyptr = &polylist[ply];
    map_plane (pts[polyptr->vertex1].local.x, pts[polyptr->vertex1].local.y, 
	       pts[polyptr->vertex1].local.z, &ptext[ply].sx[0], 
	       &ptext[ply].sy[0], scale);
    map_plane (pts[polyptr->vertex2].local.x, pts[polyptr->vertex2].local.y, 
	       pts[polyptr->vertex2].local.z, &ptext[ply].sx[1], 
	       &ptext[ply].sy[1], scale);
    map_plane (pts[polyptr->vertex3].local.x, pts[polyptr->vertex3].local.y, 
	       pts[polyptr->vertex3].local.z, &ptext[ply].sx[2], 
	       &ptext[ply].sy[2], scale);
   }
}



int sortkeys (keydata *a, keydata *b)
/* Sorts the animation keys by their frame number */
{
 if (a->framenumber < b->framenumber)
   return -1;
 else if (a->framenumber > b->framenumber)
   return 1;
 else return 0;
}




void adjust_object (object_3d *objp, float rx, float ry, float rz, 
		    float px, float py, float pz)   
/* Modify an object's animation data if it has a parent */
{
int child; 
int frame;
object_3d *objc; 
keydata *key;
     
 
 if (objp->maxchild > -1)
  {
   for (child = 0; child <= objp->maxchild; child++)
    {
     objc = &objectlist[objp->children[child]];
     rotate_point (&objc->pivotx, &objc->pivoty, &objc->pivotz, 
		    -rx, -ry, -rz, 0, 0, 0);
     rotate_point (&objc->pivotx, &objc->pivoty, &objc->pivotz, 
		   objc->keylist[0].rx, objc->keylist[0].ry, objc->keylist[0].rz, 
		   0, 0, 0);
     objc->pivotx += px;
     objc->pivoty += py;
     objc->pivotz += pz;
      
     if (objc->maxframe > -1)
     for (frame = 1; frame <= objc->maxframe; frame++)
      {
       key = &objc->keylist[frame];
       rotate_point (&key->rx, &key->ry, &key->rz, -rx, -ry, -rz, 0, 0, 0);
//       rotate_point (&key->rx, &key->rx, &key->rx, 
		   //objc->keylist[0].rx, objc->keylist[0].ry, objc->keylist[0].rz, 
		   //0, 0, 0);
	 
       rotate_point (&key->tx, &key->ty, &key->tz, rx, ry, rz, 0, 0, 0);
      }

      
    
     adjust_object (objc, rx, ry, rz, px, py, pz);
    }
 }
}


void duplicate_frame (object_3d *obj, int frame, int framenumber)
/* Copies one animation key to another */
{
int lastkey; 

 lastkey = obj->maxframe+1;
 memcpy (&obj->keylist[lastkey], &obj->keylist[frame], sizeof (keydata));
 obj->keylist[lastkey].framenumber = framenumber;
 obj->maxframe++;
 obj->keylist[lastkey].rx = 0;
 obj->keylist[lastkey].ry = 0;
 obj->keylist[lastkey].rz = 0;
 obj->keylist[lastkey].update_rotation = 1;
 obj->keylist[lastkey].update_translation = 0;
}



#include <stddef.h>

void qsort (void  *base,
	    size_t nel,
	    size_t width,
	    int (*comp)(const void *, const void *))
{
      size_t wnel, gap, wgap, i, j, k;
      char *a, *b, tmp;

      wnel = width * nel;
      for (gap = 0; ++gap < nel;)
	    gap *= 3;
      while ( gap /= 3 )
      {
	    wgap = width * gap;
	    for (i = wgap; i < wnel; i += width)
	    {
		  for (j = i - wgap; ;j -= wgap)
		  {
			a = j + (char *)base;
			b = a + wgap;
			if ( (*comp)(a, b) <= 0 )
			      break;
			k = width;
			do
			{
			      tmp = *a;
			      *a++ = *b;
			      *b++ = tmp;
			} while ( --k );
			if (j < wgap)
			      break;
		  }
	    }
      }
}


void build_hierarchy (void)
/* Connects children and parents together */
{
object_3d *objp;
object_3d *objc;

int obj, obj2;
int level;
float rx, ry, rz;
float crx, cry, crz;

int lastframe, testframe;

 
 lastframe = 0;
 /* Sort keyframes */
 for (obj = 0; obj < totalobjects; obj++)
  {
   objp = &objectlist[obj];
   qsort (objp->keylist, objp->maxframe+1, sizeof (keydata), sortkeys);          
   
   testframe = objp->keylist[objp->maxframe].framenumber;
   if (testframe > lastframe)
     lastframe = testframe;
  }
 
 for (obj = 0; obj < totalobjects; obj++)
  {
   objp = &objectlist[obj];
   objp->pivotx = objp->keylist[0].tx; 
   objp->pivoty = objp->keylist[0].ty; 
   objp->pivotz = objp->keylist[0].tz; 
   
   testframe = objp->keylist[objp->maxframe].framenumber;
   if ((testframe != lastframe) && (objp->maxframe > 0))
     duplicate_frame (objp, objp->maxframe, lastframe);
  }

 
 for (obj = 0; obj < totalobjects; obj++)
  {
   objp = &objectlist[obj];
   
   rx = objp->keylist[0].rx;
   ry = objp->keylist[0].ry;
   rz = objp->keylist[0].rz;
   
   crx = objp->pivotx;
   cry = objp->pivoty;
   crz = objp->pivotz;
      
   adjust_object (objp, rx, ry, rz, crx, cry, crz);
  }
}
			 
			 
void add_key_translate (object_3d *obj, int framenumber, float x, float y, float z)
/* Adds a translation animation key */
{
int key;
int foundkey;

 foundkey = -1;
 for (key = 0; key <= obj->maxframe; key++)
  if (obj->keylist[key].framenumber == framenumber)
    foundkey = key;

 if (foundkey == -1)
  { 
   obj->maxframe++;
   foundkey = obj->maxframe;
   obj->keylist[foundkey].framenumber = framenumber;
  }

 obj->keylist[foundkey].tx = x;
 obj->keylist[foundkey].ty = y;
 obj->keylist[foundkey].tz = z;
 obj->keylist[foundkey].update_translation = 1;
 
}


void add_key_scale (object_3d *obj, int framenumber, float x, float y, float z)
/* Adds a scale animation key */
{
int key;
int foundkey;

 foundkey = -1;
 for (key = 0; key <= obj->maxframe; key++)
  if (obj->keylist[key].framenumber == framenumber)
    foundkey = key;

 if (foundkey == -1)
  { 
   obj->maxframe++;
   foundkey = obj->maxframe;
   obj->keylist[foundkey].framenumber = framenumber;
  }

 obj->keylist[foundkey].sx = x;
 obj->keylist[foundkey].sy = y;
 obj->keylist[foundkey].sz = z;
 obj->keylist[foundkey].update_scale = 1;
}


void add_key_rotate (object_3d *obj, int framenumber, float x, float y, float z)
/* Adds a rotation animation key */
{
int key;
int foundkey;

 foundkey = -1;
 for (key = 0; key <= obj->maxframe; key++)
  if (obj->keylist[key].framenumber == framenumber)
    foundkey = key;

 if (foundkey == -1)
  { 
   obj->maxframe++;
   foundkey = obj->maxframe;
   obj->keylist[foundkey].framenumber = framenumber;
  }

 obj->keylist[foundkey].rx = x;
 obj->keylist[foundkey].ry = y;
 obj->keylist[foundkey].rz = z;
 obj->keylist[foundkey].update_rotation = 1;
}


int find_object (char *name)
/* Given the name of an object, returns the index in objectlist */
{
int obj;
int found;

 found = -1;
 for (obj = 0; obj < totalobjects; obj++)
  {
   if (strcmp (objectlist[obj].name, name) == 0)
     found = obj;
  }

 return (found);
}



void add_child (int parent, int child)
/* Adds a child to a parent object */
{
 objectlist[child].parent = parent;
 objectlist[parent].maxchild++;
 objectlist[parent].children[objectlist[parent].maxchild] = child;
}



void load_3ds (char *filename, vertex *pts)
/* Loads a 3D Studio file */
{
FILE *in;
float camx, camy, camz;
float tarx, tary, tarz;
float lightx, lighty, lightz;
float mat11, mat12, mat13, mat21, mat22, mat23, mat31, mat32, mat33,
      mat41, mat42, mat43;
float lens;
float bank;
float min_depth, max_depth;
float x, y, z;
unsigned short int p1, p2, p3, p4;
char c;
char objname[20];
unsigned int nextobj;
unsigned short vertices;
unsigned short polys;
int vertcnt;
vertex *vptr, *vptr2, *vptr3;
char buffer[120];
int i, ctr;
int hiernum;
int prevobj;

long filelen;  

int numkeys;
short framenumber;
float rx, ry, rz;
float rangle;

short level;

struct {
  unsigned short chunk_id;
  unsigned int   next_chunk;
} chunk;

    
  hiernum = -1;

  
  vertcnt = 0;
  totalobjects = 0;
  totalpoly = 0;
  
  /* Library code */
  if  (wgtlibrary == NULL)
  {
    if  ((libf = fopen (filename, "rb")) == NULL)
      goto l3dsstop;
  }
  else
  {
    if  ((libf = fopen (wgtlibrary, "rb")) == NULL)
      goto l3dsstop;
    readheader ();
    findfile (filename);
    if  (lresult == 1)
      fseek (libf, lfpos, SEEK_SET);
    if  (checkpassword (password) == 0)
    {
      wsetmode (3);
      printf ("Incorrect password");
      exit (1);
    }
  }

  if  ((wgtlibrary != NULL) & (lresult == 0)) goto l3dsstop;
  /* Library code */
  in = libf;
  
  filelen = filelength (fileno(in));
  fread (&chunk, 6, 1, in);
  if (chunk.chunk_id == 0x4d4d)
    while (!feof (in) && (ftell(in) < filelen))
    {
      fread (&chunk, 6, 1, in);
      switch (chunk.chunk_id) {
	case 0x3d3d : // Loading mesh
		      break;
	case 0x4000 : // Found object
		      nextobj = ftell (in) + chunk.next_chunk - 6;
		      objectlist[totalobjects].points = 0;
		      objectlist[totalobjects].maxchild = -1;
		      c = 0;
		      
		      /* Read the object name */
		      do {
			objname[c] = fgetc (in);
			c++;
		      } while (objname[c-1] != 0);
		      strcpy (objectlist[totalobjects].name, objname);
		      break;
	case 0x4100 : // Triangular polygon object
		      break;
	case 0x4110 : // Loading vertices
		      fread (&vertices, 2, 1, in);
		      objectlist[totalobjects].points = vertices;
		      objectlist[totalobjects].startpoints = vertcnt;
		      for (i = 0; i < vertices; i++)
		      {
			fread (&x, 4, 1, in);
			fread (&y, 4, 1, in);
			fread (&z, 4, 1, in);
			pts[vertcnt].local.x = x;
			pts[vertcnt].local.y = y;
			pts[vertcnt].local.z = z;
			pts[vertcnt].normal.x = 0;
			pts[vertcnt].normal.y = 0;
			pts[vertcnt].normal.z = 0;
			pts[vertcnt].rotated = 0;
			pts[vertcnt].connected = 0;
			vertcnt++;
		      }
		      break;
	case 0x4120 : // Loading polygons
		      fread (&polys, 2, 1, in);
		      objectlist[totalobjects].maxframe = -1;
		      objectlist[totalobjects].maxchild = -1;
		      
		      objectlist[totalobjects].currentframe = 0;
		      objectlist[totalobjects].tstepsleft = -1;
		      objectlist[totalobjects].rstepsleft = -1;

		      objectlist[totalobjects].scalex = 1;
		      objectlist[totalobjects].scaley = 1;
		      objectlist[totalobjects].scalez = 1;
		      
		      objectlist[totalobjects].translatex = 0;
		      objectlist[totalobjects].translatey = 0;
		      objectlist[totalobjects].translatez = 0;

		      objectlist[totalobjects].start_poly = totalpoly;
		      for (i = 0; i < polys; i++)
		      {
			fread (&p1, 2, 1, in);
			fread (&p2, 2, 1, in);
			fread (&p3, 2, 1, in);
			fread (&p4, 2, 1, in);
			ctr = vertcnt - vertices;
			p1 += ctr;
			p2 += ctr;
			p3 += ctr;
			
			polylist[totalpoly].vertex1 = p1;
			polylist[totalpoly].vertex2 = p2;
			polylist[totalpoly].vertex3 = p3;
			polylist[totalpoly].type = 1;
			polylist[totalpoly].status = p4;
			polylist[totalpoly].color = 0;
			vptr = &pts[polylist[totalpoly].vertex1];
			vptr2 = &pts[polylist[totalpoly].vertex2];
			vptr3 = &pts[polylist[totalpoly].vertex3];
			calc_normal (&pts[p3], &pts[p2], &pts[p1],
				     &polylist[totalpoly].normal);
			vptr->normal.x += polylist[totalpoly].normal.x;
			vptr->normal.y += polylist[totalpoly].normal.y;
			vptr->normal.z += polylist[totalpoly].normal.z;
			vptr->connected++;
			vptr2->normal.x += polylist[totalpoly].normal.x;
			vptr2->normal.y += polylist[totalpoly].normal.y;
			vptr2->normal.z += polylist[totalpoly].normal.z;
			vptr2->connected++;
			vptr3->normal.x += polylist[totalpoly].normal.x;
			vptr3->normal.y += polylist[totalpoly].normal.y;
			vptr3->normal.z += polylist[totalpoly].normal.z;
			vptr3->connected++;
    
			/* Calculate center of polygon */
			min_depth = vptr->local.x;
			if (vptr2->local.x < min_depth)
			  min_depth = vptr2->local.x;
			if (vptr3->local.x < min_depth)
			  min_depth = vptr3->local.x;
			max_depth = vptr->local.x;
			if (vptr2->local.x > max_depth)
			max_depth = vptr2->local.x;
			if (vptr3->local.x > max_depth)
			max_depth = vptr3->local.x;
			polylist[totalpoly].center.x = (min_depth + max_depth) / 2.0;
	    
			min_depth = vptr->local.y;
			if (vptr2->local.y < min_depth)
			min_depth = vptr2->local.y;
			if (vptr3->local.y < min_depth)
			min_depth = vptr3->local.y;
			max_depth = vptr->local.y;
			if (vptr2->local.y > max_depth)
			max_depth = vptr2->local.y;
			if (vptr3->local.y > max_depth)
			max_depth = vptr3->local.y;
			polylist[totalpoly].center.y = (min_depth + max_depth) / 2.0;
    
			min_depth = vptr->local.z;
			if (vptr2->local.z < min_depth)
			min_depth = vptr2->local.z;
			if (vptr3->local.z < min_depth)
			min_depth = vptr3->local.z;
			max_depth = vptr->local.z;
			if (vptr2->local.z > max_depth)
			max_depth = vptr2->local.z;
			if (vptr3->local.z > max_depth)
			max_depth = vptr3->local.z;
			polylist[totalpoly].center.z = (min_depth + max_depth) / 2.0;
			totalpoly++;
		      }
		      objectlist[totalobjects].end_poly = totalpoly - 1;
		      totalobjects++;
		      fseek (in, nextobj, SEEK_SET);
		      break;
	case 0x4160 : // Loading Matrix
		      fread (&mat11, 4, 1, in);
		      fread (&mat12, 4, 1, in);
		      fread (&mat13, 4, 1, in);
		      fread (&mat21, 4, 1, in);
		      fread (&mat22, 4, 1, in);
		      fread (&mat23, 4, 1, in);
		      fread (&mat31, 4, 1, in);
		      fread (&mat32, 4, 1, in);
		      fread (&mat33, 4, 1, in);
		      fread (&mat41, 4, 1, in);
		      fread (&mat42, 4, 1, in);
		      fread (&mat43, 4, 1, in);
		      break;
	case 0x4600 : // Light
		      fread (&lightx, 4, 1, in);
		      fread (&lighty, 4, 1, in);
		      fread (&lightz, 4, 1, in);
		      light_x = lightx;
		      light_y = lighty;
		      light_z = lightz;
		      break;
	case 0x4700 : // Camera
		      fread (&camx, 4, 1, in);
		      fread (&camy, 4, 1, in);
		      fread (&camz, 4, 1, in);
		      camera_x = camx;
		      camera_y = camy;
		      camera_z = camz;
		      fread (&tarx, 4, 1, in);
		      fread (&tary, 4, 1, in);
		      fread (&tarz, 4, 1, in);
		      focus_x = tarx;
		      focus_y = tary;
		      focus_z = tarz;
		      fread (&bank, 4, 1, in);
		      camera_angle = bank;
		      fread (&lens, 4, 1, in);
		      fov = cameras[0].fov;
		      for (i = 1; i < 9; i++)
			if (cameras[i].lens < lens)
			  fov = cameras[i].fov;
		      break;
	case 0xb000 : // Keyframer data
		      break;
	case 0xb002 : // Object framedata
		      break;
	case 0xb010 : // Found object
		      nextobj = ftell (in) + chunk.next_chunk - 6;
		      
		      c = 0;  /* Read the name */
		      do {
			objname[c] = fgetc (in);
			c++;
		      } while (objname[c-1] != 0);

		      hiernum = find_object (objname);
		      
		      if (hiernum == -1)   /* Dummy object */
			hiernum = OBJBUF - 1;

		      fread (buffer, 4, 1, in);
		      fread (&level, 2, 1, in);

		      if (level == -1)
		       {
			prevobj = hiernum;
			objectlist[hiernum].parent = -1;
			objectlist[hiernum].level = -1;
		       }
		      else if (level > objectlist[prevobj].level)
		       {
			add_child(prevobj, hiernum);
			prevobj = hiernum;
		       }
		      else /* Need to back up the tree */
		       {
			while ((level <= objectlist[prevobj].level) && 
			   (objectlist[prevobj].level > -1))
			  prevobj = objectlist[prevobj].parent;
			add_child (prevobj, hiernum);
		       }

		      for (i = 0; i < MAXKEYS; i++)
		       objectlist[hiernum].keylist[i].framenumber = -1;
		      
		      objectlist[hiernum].maxframe = -1;
		      fseek (in, nextobj, SEEK_SET);
		      break;
	
	case 0xb020 : // Translation key
		      nextobj = ftell (in) + chunk.next_chunk - 6;
		      fread (buffer, 10, 1, in);
		      fread (&numkeys, 4, 1, in);

		      for (i = 0; i < numkeys; i++)
			{
			 fread (&framenumber, 2, 1, in);
			 fread (&buffer, 4, 1, in);
			 
			 fread (&x, 4, 1, in);
			 fread (&y, 4, 1, in);
			 fread (&z, 4, 1, in);
			 
			 add_key_translate (&objectlist[hiernum],
					framenumber, x, y, z);
		       }                        
		       
		      fseek (in, nextobj, SEEK_SET);
		      break;
	

	case 0xb021 : // Rotation key
		      nextobj = ftell (in) + chunk.next_chunk - 6;
		      fread (buffer, 10, 1, in);
		      fread (&numkeys, 4, 1, in);
		      
		      for (i = 0; i < numkeys; i++)
			{
			 fread (&framenumber, 2, 1, in);
			 fread (buffer, 4, 1, in);
			 
			 fread (&rangle, 4, 1, in);
			 fread (&rx, 4, 1, in);
			 fread (&ry, 4, 1, in);
			 fread (&rz, 4, 1, in);
			 rangle = rangle * 180.0 / 3.1415;
			 
			 add_key_rotate (&objectlist[hiernum], framenumber,
			  rx * rangle, ry * rangle, rz * rangle);
			}                        
		      
		      fseek (in, nextobj, SEEK_SET);
		      break;
	case 0xb022 : // Scale key
		      nextobj = ftell (in) + chunk.next_chunk - 6;
		      fread (buffer, 10, 1, in);
		      fread (&numkeys, 4, 1, in);
		      
		      for (i = 0; i < numkeys; i++)
			{
			 fread (&framenumber, 2, 1, in);
			 fread (buffer, 4, 1, in);
			 
			 fread (&rx, 4, 1, in);
			 fread (&ry, 4, 1, in);
			 fread (&rz, 4, 1, in);
		     
			 add_key_scale (&objectlist[hiernum], 
					framenumber, rx, ry, rz);
			}                        
		      
		      fseek (in, nextobj, SEEK_SET);
		      break;

	default     : fseek (in, chunk.next_chunk-6, SEEK_CUR);
      }
    if (chunk.chunk_id == 0xb001)
       break;
    
    }
  else printf ("Not a 3DS file. Invalid header.\n");
  worldpoints = vertcnt;
  for (i = 0; i < worldpoints; i++)
  {
    pts[i].normal.x /= (float)pts[i].connected;
    pts[i].normal.y /= (float)pts[i].connected;
    pts[i].normal.z /= (float)pts[i].connected;
  }
  fclose (in);
  if ((camera_x == focus_x) && (camera_y == focus_y) && (camera_z == focus_z))
  {
    printf ("No camera loaded. Default view position set.\n");
    camera_x = -400;
    camera_y = -400;
    camera_z = -400;
  }

 build_hierarchy ();   /* Not complete yet */

 l3dsstop:;
 if (libf != NULL)
   fclose (libf);
}


int compare (sort *a, sort *b)
/* Used for sorting */
{
float comp;  
  
  comp = a->depth - b->depth;

  //if (abs(comp) < 0.0005)
    //return 0;
  if (comp < 0)
    return 1;
  else return -1;
}


void draw_polys (vertex *pts, int drawtotal)
/* Draw all of the polygons in order, using our rendering method */
{
char shade;
int d, e;
int first;
float dotprod;
triangle_3d *ptr;
vertex *vptr;
vertex *vptr2;
vertex *vptr3;
sort tempsort;
point3d *lightnorm;
float lnormx, lnormy, lnormz;  
int polynum;

  lightnorm = &light.normal;
  
  for (d = 0; d < worldpoints; d++)
    pts[d].rotated = 0;
  
  qsort (sorted_polys, drawtotal, sizeof (sort), compare);        

  lnormx = lightnorm->x;
  lnormy = lightnorm->y;
  lnormz = lightnorm->z;
  
  first = drawtotal - 1;
  for (d = 0; d <= first; d++)
  {
    polynum = sorted_polys[d].number;
    
    ptr = &polylist[polynum];
    vptr = &pts[ptr->vertex1];
    vptr2 = &pts[ptr->vertex2];
    vptr3 = &pts[ptr->vertex3];
    
    renderpoly[0].x = vptr->screenx;
    renderpoly[0].y = vptr->screeny;
    renderpoly[1].x = vptr2->screenx;
    renderpoly[1].y = vptr2->screeny;
    renderpoly[2].x = vptr3->screenx;
    renderpoly[2].y = vptr3->screeny;

    vptr->rotated = 0;
    vptr2->rotated = 0;
    vptr3->rotated = 0;
    
    if (ptr->type == WIREFRAME)
      {
	wsetcolor (ptr->color);
	if (ptr->status & 4)
	  wline (renderpoly[0].x, renderpoly[0].y, renderpoly[1].x, renderpoly[1].y);
	if (ptr->status & 2)
	  wline (renderpoly[1].x, renderpoly[1].y, renderpoly[2].x, renderpoly[2].y);
	if (ptr->status & 1)
	  wline (renderpoly[2].x, renderpoly[2].y, renderpoly[0].x, renderpoly[0].y);
      }
    else if (ptr->type == SOLID)
      {
	dotprod = (ptr->normal.x * lnormx) + (ptr->normal.y * lnormy) +
		  (ptr->normal.z * lnormz);
	
	if (dotprod < 0)
	  renderpoly[0].col = dotprod * render_shades;
	else renderpoly[0].col = 0;
	
	wsetcolor (ptr->color - renderpoly[0].col);
	wtriangle_solid (renderpoly);
      }
    else if (ptr->type == GOURAUD)
      {
	dotprod = (vptr->normal.x * lnormx) + (vptr->normal.y * lnormy) +
		  (vptr->normal.z * lnormz);
	if (dotprod > 0)
	  dotprod = 0;
	renderpoly[0].col = ptr->color - dotprod * render_shades;

	dotprod = (vptr2->normal.x * lnormx) + (vptr2->normal.y * lnormy) +
		  (vptr2->normal.z * lnormz);
	if (dotprod > 0)
	  dotprod = 0;
	renderpoly[1].col = ptr->color - dotprod * render_shades;
      
	dotprod = (vptr3->normal.x * lnormx) + (vptr3->normal.y * lnormy) +
		  (vptr3->normal.z * lnormz);
	if (dotprod > 0)
	  dotprod = 0;
	renderpoly[2].col = ptr->color - dotprod * render_shades;

	wtriangle_gouraud (renderpoly);
      }
    else if (ptr->type == TEXTURE)
      { 
       renderpoly[0].sx = ptext[polynum].sx[0];
       renderpoly[0].sy = ptext[polynum].sy[0];
       renderpoly[1].sx = ptext[polynum].sx[1];
       renderpoly[1].sy = ptext[polynum].sy[1];
       renderpoly[2].sx = ptext[polynum].sx[2];
       renderpoly[2].sy = ptext[polynum].sy[2];
     
       wtriangle_texture (renderpoly, textures[ptr->color]);
      }
    else if (ptr->type == FLAT_SHADED_TEXTURE)
      {
	dotprod = (ptr->normal.x * lnormx) + (ptr->normal.y * lnormy) +
		  (ptr->normal.z * lnormz);
	if (dotprod < 0)
	  shade = -dotprod * render_shades;
	else shade = 0;

	renderpoly[0].sx = ptext[polynum].sx[0];
	renderpoly[0].sy = ptext[polynum].sy[0];
	renderpoly[1].sx = ptext[polynum].sx[1];
	renderpoly[1].sy = ptext[polynum].sy[1];
	renderpoly[2].sx = ptext[polynum].sx[2];
	renderpoly[2].sy = ptext[polynum].sy[2];
       
	wtriangle_flat_shaded_texture (renderpoly, textures[ptr->color], 
		shade, render_shadetable);
      }
    else if (ptr->type == GOURAUD_SHADED_TEXTURE)
      {
	dotprod = (vptr->normal.x * lnormx) + (vptr->normal.y * lnormy) +
		  (vptr->normal.z * lnormz);
	if (dotprod > 0)
	  dotprod = 0;
	renderpoly[0].col = -dotprod * render_shades;
	 
	dotprod = (vptr2->normal.x * lnormx) + (vptr2->normal.y * lnormy) +
		  (vptr2->normal.z * lnormz);
	if (dotprod > 0)
	  dotprod = 0;
	renderpoly[1].col = -dotprod * render_shades;
      
	dotprod = (vptr3->normal.x * lnormx) + (vptr3->normal.y * lnormy) +
		  (vptr3->normal.z * lnormz);
	if (dotprod > 0)
	  dotprod = 0;
	renderpoly[2].col = -dotprod * render_shades;
     
	renderpoly[0].sx = ptext[polynum].sx[0];
	renderpoly[0].sy = ptext[polynum].sy[0];
	renderpoly[1].sx = ptext[polynum].sx[1];
	renderpoly[1].sy = ptext[polynum].sy[1];
	renderpoly[2].sx = ptext[polynum].sx[2];
	renderpoly[2].sy = ptext[polynum].sy[2];

	wtriangle_gouraud_shaded_texture (renderpoly, textures[ptr->color],
			render_shadetable);
      }
    else if (ptr->type == TRANSLUCENT_TEXTURE)
      { 
       renderpoly[0].sx = ptext[polynum].sx[0];
       renderpoly[0].sy = ptext[polynum].sy[0];
       renderpoly[1].sx = ptext[polynum].sx[1];
       renderpoly[1].sy = ptext[polynum].sy[1];
       renderpoly[2].sx = ptext[polynum].sx[2];
       renderpoly[2].sy = ptext[polynum].sy[2];
     
       wtriangle_translucent_texture (renderpoly, textures[ptr->color], 
			render_shadetable);
      }
    else if (ptr->type == TRANSLUCENT_GOURAUD)
      { 
	dotprod = (vptr->normal.x * lnormx) + (vptr->normal.y * lnormy) +
		  (vptr->normal.z * lnormz);
	if (dotprod > 0)
	  dotprod = 0;
	renderpoly[0].col = ptr->color - dotprod * render_shades;

	dotprod = (vptr2->normal.x * lnormx) + (vptr2->normal.y * lnormy) +
		  (vptr2->normal.z * lnormz);
	if (dotprod > 0)
	  dotprod = 0;
	renderpoly[1].col = ptr->color - dotprod * render_shades;
      
	dotprod = (vptr3->normal.x * lnormx) + (vptr3->normal.y * lnormy) +
		  (vptr3->normal.z * lnormz);
	if (dotprod > 0)
	  dotprod = 0;
	renderpoly[2].col = ptr->color - dotprod * render_shades;

	wtriangle_translucent_gouraud (renderpoly, render_shadetable);
      }

  }
}




void map_plane (float x, float y, float z, long *u, long *v, float scale)
/* Given a 3D point, maps the point onto the x/y plane */
{
 *u = x * scale;
 *v = y * scale;
}



void wset_object_color (int obj, unsigned char col)
/* Sets the color of each triangle in an object, used for solid, gouraud, and
   texture.  For textured objects, the color represents the texture number
   from the textures array. */
{
int poly;
object_3d *objp;

 objp = &objectlist[obj];

 for (poly = objp->start_poly; poly <= objp->end_poly; poly++)
   polylist[poly].color = col;
 
}


void wset_object_type (int obj, int type)
/* Sets the rendering method for an object */
{
int poly;
object_3d *objp;

 objp = &objectlist[obj];

 for (poly = objp->start_poly; poly <= objp->end_poly; poly++)
   polylist[poly].type = type;
 
}


void wreset_3d_system (void)
/* Resets the engine so you can load in a new object */
{
 totalpoly = 0;
 totalobjects = 0;
}


