/*
 *    Example program for the Allegro library, by Dave Thomson.
 *
 *    This program draws a 3D star field (depth-cued) and a polygon
 *    starship (controllable with the keyboard cursor keys), using
 *    the Allegro math functions.
 */


#include <allegro.h>

/* star field system */
typedef struct VECTOR 
{
   fixed x, y, z;
} VECTOR;


#define NUM_STARS          512

#define Z_NEAR             24
#define Z_FAR              1024
#define XY_CUBE            2048

#define SPEED_LIMIT        20

VECTOR stars[NUM_STARS];

fixed star_x[NUM_STARS];
fixed star_y[NUM_STARS];

VECTOR delta;


/* polygonal models */
#define NUM_VERTS          4
#define NUM_FACES          4

#define ENGINE             3     /* which face is the engine */
#define ENGINE_ON          64    /* colour index */
#define ENGINE_OFF         32


typedef struct FACE              /* for triangular models */
{
   int v1, v2, v3;
   int colour, range;
   VECTOR normal, rnormal;
} FACE;


typedef struct MODEL 
{
   VECTOR points[NUM_VERTS];
   FACE faces[NUM_FACES];
   fixed x, y, z;
   fixed rx, ry, rz;
   int minx, miny, maxx, maxy;
   VECTOR aim;
   int velocity;
} MODEL;


MODEL ship;
VECTOR direction;


BITMAP *buffer;



/* initialises the star field system */
void init_stars(void)
{
   int i;

   for (i=0; i<NUM_STARS; i++) {
      stars[i].x = itofix((AL_RAND() % XY_CUBE) - (XY_CUBE >> 1));
      stars[i].y = itofix((AL_RAND() % XY_CUBE) - (XY_CUBE >> 1));
      stars[i].z = itofix((AL_RAND() % (Z_FAR - Z_NEAR)) + Z_NEAR);
   }

   delta.x = 0;
   delta.y = 0;
   delta.z = 0;
}



/* draws the star field */
void draw_stars(void)
{
   int i, c;
   MATRIX m;
   VECTOR outs[NUM_STARS];

   for (i=0; i<NUM_STARS; i++) {
      get_translation_matrix(&m, delta.x, delta.y, delta.z);
      apply_matrix(&m, stars[i].x, stars[i].y, stars[i].z,
		   &outs[i].x, &outs[i].y, &outs[i].z);
      persp_project(outs[i].x, outs[i].y, outs[i].z, &star_x[i], &star_y[i]);
      c = (fixtoi(outs[i].z) >> 8) + 16;
      putpixel(buffer, fixtoi(star_x[i]), fixtoi(star_y[i]),
	       palette_color[c]);
   }
}



/* deletes the stars from the screen */
void erase_stars(void)
{
   int i;

   for (i=0; i<NUM_STARS; i++)
      putpixel(buffer, fixtoi(star_x[i]), fixtoi(star_y[i]),
	       palette_color[0]);
}



/* moves the stars */
void move_stars(void)
{
   int i;

   for (i=0; i<NUM_STARS; i++) {
      stars[i].x += delta.x;
      stars[i].y += delta.y;
      stars[i].z += delta.z;

      if (stars[i].x > itofix(XY_CUBE >> 1))
	 stars[i].x = itofix(-(XY_CUBE >> 1));
      else if (stars[i].x < itofix(-(XY_CUBE >> 1)))
	 stars[i].x = itofix(XY_CUBE >> 1);

      if (stars[i].y > itofix(XY_CUBE >> 1))
	 stars[i].y = itofix(-(XY_CUBE >> 1));
      else if (stars[i].y < itofix(-(XY_CUBE >> 1)))
	 stars[i].y = itofix(XY_CUBE >> 1);

      if (stars[i].z > itofix(Z_FAR))
	 stars[i].z = itofix(Z_NEAR);
      else if (stars[i].z < itofix(Z_NEAR))
	 stars[i].z = itofix(Z_FAR);
   }
}



/* initialises the ship model */
void init_ship(void)
{
   VECTOR v1, v2, *pts;
   FACE *face;
   int i;

   ship.points[0].x = itofix(0);
   ship.points[0].y = itofix(0);
   ship.points[0].z = itofix(32);

   ship.points[1].x = itofix(16);
   ship.points[1].y = itofix(-16);
   ship.points[1].z = itofix(-32);

   ship.points[2].x = itofix(-16);
   ship.points[2].y = itofix(-16);
   ship.points[2].z = itofix(-32);

   ship.points[3].x = itofix(0);
   ship.points[3].y = itofix(16);
   ship.points[3].z = itofix(-32);

   ship.faces[0].v1 = 3;
   ship.faces[0].v2 = 0;
   ship.faces[0].v3 = 1;
   pts = &ship.points[0];
   face = &ship.faces[0];
   v1.x = (pts[face->v2].x - pts[face->v1].x);
   v1.y = (pts[face->v2].y - pts[face->v1].y);
   v1.z = (pts[face->v2].z - pts[face->v1].z);
   v2.x = (pts[face->v3].x - pts[face->v1].x);
   v2.y = (pts[face->v3].y - pts[face->v1].y);
   v2.z = (pts[face->v3].z - pts[face->v1].z);
   cross_product(v1.x, v1.y, v1.z, v2.x, v2.y, v2.z,
		 &(face->normal.x), &(face->normal.y), &(face->normal.z));

   ship.faces[1].v1 = 2;
   ship.faces[1].v2 = 0;
   ship.faces[1].v3 = 3;
   face = &ship.faces[1];
   v1.x = (pts[face->v2].x - pts[face->v1].x);
   v1.y = (pts[face->v2].y - pts[face->v1].y);
   v1.z = (pts[face->v2].z - pts[face->v1].z);
   v2.x = (pts[face->v3].x - pts[face->v1].x);
   v2.y = (pts[face->v3].y - pts[face->v1].y);
   v2.z = (pts[face->v3].z - pts[face->v1].z);
   cross_product(v1.x, v1.y, v1.z, v2.x, v2.y, v2.z, &(face->normal.x),
		 &(face->normal.y), &(face->normal.z));

   ship.faces[2].v1 = 1;
   ship.faces[2].v2 = 0;
   ship.faces[2].v3 = 2;
   face = &ship.faces[2];
   v1.x = (pts[face->v2].x - pts[face->v1].x);
   v1.y = (pts[face->v2].y - pts[face->v1].y);
   v1.z = (pts[face->v2].z - pts[face->v1].z);
   v2.x = (pts[face->v3].x - pts[face->v1].x);
   v2.y = (pts[face->v3].y - pts[face->v1].y);
   v2.z = (pts[face->v3].z - pts[face->v1].z);
   cross_product(v1.x, v1.y, v1.z, v2.x, v2.y, v2.z,
		 &(face->normal.x), &(face->normal.y), &(face->normal.z));

   ship.faces[3].v1 = 2;
   ship.faces[3].v2 = 3;
   ship.faces[3].v3 = 1;
   face = &ship.faces[3];
   v1.x = (pts[face->v2].x - pts[face->v1].x);
   v1.y = (pts[face->v2].y - pts[face->v1].y);
   v1.z = (pts[face->v2].z - pts[face->v1].z);
   v2.x = (pts[face->v3].x - pts[face->v1].x);
   v2.y = (pts[face->v3].y - pts[face->v1].y);
   v2.z = (pts[face->v3].z - pts[face->v1].z);
   cross_product(v1.x, v1.y, v1.z, v2.x, v2.y, v2.z,
		 &(face->normal.x), &(face->normal.y), &(face->normal.z));

   for (i=0; i<NUM_FACES; i++) {
      ship.faces[i].colour = 32;
      ship.faces[i].range = 15;
      normalize_vector(&ship.faces[i].normal.x, &ship.faces[i].normal.y,
		       &ship.faces[i].normal.z);
      ship.faces[i].rnormal.x = ship.faces[i].normal.x;
      ship.faces[i].rnormal.y = ship.faces[i].normal.y;
      ship.faces[i].rnormal.z = ship.faces[i].normal.z;
   }

   ship.x = ship.y = 0;
   ship.z = itofix(192);
   ship.rx = ship.ry = ship.rz = 0;

   ship.aim.x = direction.x = 0;
   ship.aim.y = direction.y = 0;
   ship.aim.z = direction.z = itofix(-1);
   ship.velocity = 0;
}



/* draws the ship model */
void draw_ship(void)
{
   VECTOR outs[NUM_VERTS];
   MATRIX m;
   int i, col;

   ship.minx = SCREEN_W;
   ship.miny = SCREEN_H;
   ship.maxx = ship.maxy = 0;

   get_rotation_matrix(&m, ship.rx, ship.ry, ship.rz);
   apply_matrix(&m, ship.aim.x, ship.aim.y, ship.aim.z,
		&outs[0].x, &outs[0].y, &outs[0].z);
   direction.x = outs[0].x;
   direction.y = outs[0].y;
   direction.z = outs[0].z;

   for (i=0; i<NUM_FACES; i++)
      apply_matrix(&m, ship.faces[i].normal.x, ship.faces[i].normal.y,
		   ship.faces[i].normal.z, &ship.faces[i].rnormal.x,
		   &ship.faces[i].rnormal.y, &ship.faces[i].rnormal.z);

   get_transformation_matrix(&m, itofix(1), ship.rx, ship.ry, ship.rz,
			     ship.x, ship.y, ship.z);

   for (i=0; i<NUM_VERTS; i++) {
      apply_matrix(&m, ship.points[i].x, ship.points[i].y, ship.points[i].z,
		   &outs[i].x, &outs[i].y, &outs[i].z);
      persp_project(outs[i].x, outs[i].y, outs[i].z, &outs[i].x, &outs[i].y);
      if (fixtoi(outs[i].x) < ship.minx)
	 ship.minx = fixtoi(outs[i].x);
      if (fixtoi(outs[i].x) > ship.maxx)
	 ship.maxx = fixtoi(outs[i].x);
      if (fixtoi(outs[i].y) < ship.miny)
	 ship.miny = fixtoi(outs[i].y);
      if (fixtoi(outs[i].y) > ship.maxy)
	 ship.maxy = fixtoi(outs[i].y);
   }

   for (i=0; i<NUM_FACES; i++) {
      if (fixtof(ship.faces[i].rnormal.z) < 0.0) {
	 col = fixtoi(fixmul(dot_product(ship.faces[i].rnormal.x,
					 ship.faces[i].rnormal.y,
					 ship.faces[i].rnormal.z, 0, 0,
					 itofix(1)),
			     itofix(ship.faces[i].range)));
	 if (col < 0)
	    col = -col + ship.faces[i].colour;
	 else
	    col = col + ship.faces[i].colour;

	 triangle(buffer, fixtoi(outs[ship.faces[i].v1].x),
		  fixtoi(outs[ship.faces[i].v1].y),
		  fixtoi(outs[ship.faces[i].v2].x),
		  fixtoi(outs[ship.faces[i].v2].y),
		  fixtoi(outs[ship.faces[i].v3].x),
		  fixtoi(outs[ship.faces[i].v3].y), palette_color[col]);
      }
   }
}



/* removes the ship model from the screen */
void erase_ship(void)
{
   rectfill(buffer, ship.minx, ship.miny, ship.maxx, ship.maxy,
	    palette_color[0]);
}



int main(int argc, char *argv[])
{
   PALETTE pal;
   int i;

   if (allegro_init() != 0)
      return 1;
   install_keyboard();
   install_timer();

   if (set_gfx_mode(GFX_AUTODETECT, 640, 480, 0, 0) != 0) {
      if (set_gfx_mode(GFX_SAFE, 640, 480, 0, 0) != 0) {
	 set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
	 allegro_message("Unable to set any graphic mode\n%s\n",
			 allegro_error);
	 return 1;
      }
   }

   for (i=0; i<16; i++)
      pal[i].r = pal[i].g = pal[i].b = 0;

   /* greyscale */
   pal[16].r = pal[16].g = pal[16].b = 63;
   pal[17].r = pal[17].g = pal[17].b = 48;
   pal[18].r = pal[18].g = pal[18].b = 32;
   pal[19].r = pal[19].g = pal[19].b = 16;
   pal[20].r = pal[20].g = pal[20].b = 8;

   /* red range */
   for (i=0; i<16; i++) { 
      pal[i+32].r = 31 + i*2;
      pal[i+32].g = 15;
      pal[i+32].b = 7;
   }

   /* a nice fire orange */
   for (i=64; i<68; i++) {
      pal[i].r = 63; 
      pal[i].g = 17 + (i-64)*3;
      pal[i].b = 0;
   }

   set_palette(pal);

   buffer = create_bitmap(SCREEN_W, SCREEN_H);
   clear_bitmap(buffer);

   set_projection_viewport(0, 0, SCREEN_W, SCREEN_H);

   init_stars();
   draw_stars();
   init_ship();
   draw_ship();

   for (;;) {
      erase_stars();
      erase_ship();

      move_stars();
      draw_stars();

      textprintf_centre_ex(buffer, font, SCREEN_W / 2, SCREEN_H-10,
			   palette_color[17], 0,
			   "     direction: [%f] [%f] [%f]     ",
			   fixtof(direction.x), fixtof(direction.y),
			   fixtof(direction.z));
      textprintf_centre_ex(buffer, font, SCREEN_W / 2, SCREEN_H-20,
			   palette_color[17], 0,
			   "   delta: [%f] [%f] [%f]   ", fixtof(delta.x),
			   fixtof(delta.y), fixtof(delta.z));
      textprintf_centre_ex(buffer, font, SCREEN_W / 2, SCREEN_H-30,
			   palette_color[17], 0, "   velocity: %d   ",
			   ship.velocity);

      textout_centre_ex(buffer, font, "Press ESC to exit", SCREEN_W/2, 16,
			palette_color[18], 0);
      textout_centre_ex(buffer, font, "Press CTRL to fire engine",
			SCREEN_W/2, 32, palette_color[18], 0);

      draw_ship();

      vsync();
      blit(buffer, screen, 0, 0, 0, 0, SCREEN_W, SCREEN_H);

      poll_keyboard();

      /* exit program */
      if (key[KEY_ESC]) 
	 break;

      /* rotates */
      if (key[KEY_UP]) 
	 ship.rx -= itofix(5);
      else if (key[KEY_DOWN])
	 ship.rx += itofix(5);

      if (key[KEY_LEFT])
	 ship.ry -= itofix(5);
      else if (key[KEY_RIGHT])
	 ship.ry += itofix(5);

      if (key[KEY_PGUP])
	 ship.rz -= itofix(5);
      else if (key[KEY_PGDN])
	 ship.rz += itofix(5);

      /* thrust */
      if ((key[KEY_LCONTROL]) || (key[KEY_RCONTROL])) { 
	 ship.faces[ENGINE].colour = ENGINE_ON;
	 ship.faces[ENGINE].range = 3;
	 if (ship.velocity < SPEED_LIMIT)
	    ship.velocity += 2;
      }
      else {
	 ship.faces[ENGINE].colour = ENGINE_OFF;
	 ship.faces[ENGINE].range = 15;
	 if (ship.velocity > 0)
	    ship.velocity -= 2;
      }

      ship.rx &= itofix(255);
      ship.ry &= itofix(255);
      ship.rz &= itofix(255);

      delta.x = fixmul(direction.x, itofix(ship.velocity));
      delta.y = fixmul(direction.y, itofix(ship.velocity));
      delta.z = fixmul(direction.z, itofix(ship.velocity));
   }

   destroy_bitmap(buffer);
   return 0;
}

END_OF_MAIN()
