#ifndef __linux
#define VECTREX 1
#endif

/*

    After many previous attempts, I am having one final effort to create a C version
    of Tailgunner!  (and so far I'm delighted to report that it is going well...)

    The code here is not the actual tailgunner program, it's primarily me testing and
    debugging components that will be needed for that program.  This source file contains
    both Vectrex drawing code and linux-based code for precomputing the flight paths of
    attacking enemy ships.  Eventually those flight paths will be written out as a
    C header file and used as data by the Vectrex compilation to actually run the
    graphics in real time.  The flightpath code uses floating point which is approximated
    by (primarily, 8 bit) integer code in the Vectrex.  Currently there may be a need
    for a small amount of 16 bit code but I am fairly confident that the 16 bit
    requirement can be avoided by the time I finish this.

    This source file will compile on linux for the 3.5inch SPI display which uses
    a framebuffer driver (it copies whatever is written to the framebuffer over to
    the LCD at regular intervals, but not fast enough (with the current driver) for
    full 30Hz updates.) However it is primarily intended for compiling for the Vectrex.

    On my Pi Zero with an HDMi display enabled and the 3.5 inch LCD on SPI being used for
    the game, you need to use the map:01 option in cmdline.txt:
    console=serial0,115200 console=tty1 root=PARTUUID=9cb46872-02 rootfstype=ext4 fsck.repair=yes rootwait cfg80211.ieee80211_regdom=US fbcon=map:01

 */

#ifdef VECTREX
// Compile with -O2 option
#include <vectrex.h>
#define set_scale(A) dp_VIA_t1_cnt_lo = A
#define int8_t int
#define uint8_t unsigned int
#define int16_t long

#else

// #################################### Linux graphics code section ########################################
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
#include <math.h>

static int debug = 0;

#define FPS 30

#define SHOW_FPS 1

//#define USE_FB_320X480 1  // Really ought to pass this in on the cc command line with cc -DUSE_FB_320X480
#ifdef USE_FB_320X480
#define USE_FB 1
#endif

#ifdef USE_FB
#define FBGL_IMPLEMENTATION 1
#include "fbgl.h"
fbgl_psf1_font_t *font;
#else
#warning No graphics code defined.
#endif

#ifdef USE_FB
static fbgl_t fb;
static uint32_t fbcolor = 0xFFFFFF;

void fbDrawColor(uint8_t r, uint8_t g, uint8_t b, uint32_t c) {
  fbcolor = (c<<16) | (c<<8) | c;
}

void fbDrawPoint(int32_t x0, int32_t y0) {
  if (debug) fprintf(stdout, "fbDrawPoint(%d,%d);\r\n", x0, y0);
  int sx = (x0*fb.width/128)+fb.width/2;
  int sy = (y0*fb.width/128)+fb.height/2;
  if (debug) fprintf(stdout, "fbgl_put_pixel(%d,%d, %06x, %p;\r\n", sx, sy,  fbcolor, &fb);
  fbgl_put_pixel(sx, (fb.height-1)-sy, fbcolor, &fb);
}

void fbDrawLine(int32_t x0, int32_t y0, int32_t x1, int32_t y1) {
  if (debug) fprintf(stdout, "fbDrawLine(%d,%d, %d,%d);\r\n", x0, y0,  x1, y1);
  int sx0 = (x0*fb.width/128)+fb.width/2;
  int sy0 = (y0*fb.width/128)+fb.height/2;
  int sx1 = (x1*fb.width/128)+fb.width/2;
  int sy1 = (y1*fb.width/128)+fb.height/2;
  if (debug) fprintf(stdout, "fbgl_draw_line(%d,%d, %d,%d, %06x, %p);\r\n", sx0, sy0,  sx1, sy1,  fbcolor, &fb);
  fbgl_draw_line((fbgl_point_t){.x=sx0,.y=(fb.height-1)-sy0},
                 (fbgl_point_t){.x=sx1,.y=(fb.height-1)-sy1},
                 fbcolor, &fb);
}
#endif // USE_FB

static int8_t vscale = 127;
static int8_t vbright = 0x7F;
static int8_t vx = 0;
static int8_t vy = 0;

static int fromx = 0, fromy = 0;
static void G_MoveAbs(int8_t x, int8_t y, uint8_t scale) {
  if (debug) fprintf(stdout, "G_MoveAbs(%d,%d, scale=%u);\r\n", x, y, scale);
  fromx = (x * scale) / 256; fromy = (y * scale) / 256;
  if (debug) fprintf(stdout, "fromx = %d; fromy = %d;\r\n", fromx, fromy);
}
static void G_MoveRel(int8_t x, int8_t y, uint8_t scale) {
  if (debug) fprintf(stdout, "G_MoveRel(%d,%d, scale=%u);\r\n", x, y, scale);
  fromx += (x * scale) / 256; fromy += (y * scale) / 256;
  if (debug) fprintf(stdout, "fromx = %d; fromy = %d;\r\n", fromx, fromy);
}
static void G_LineRel(int8_t x, int8_t y, uint8_t intensity, uint8_t scale) {
  if (debug) fprintf(stdout, "G_LineRel(%d,%d, intensity=%d, scale=%d);\r\n", x, y, intensity, scale);
  int tox, toy;
  tox = (x * scale) / 256; toy = (y * scale) / 256;
  // draw [from,fromy] to [tox,toy]
#ifdef USE_FB
  fbDrawColor(intensity, intensity, intensity, intensity);
  fbDrawLine(fromx, fromy, fromx+tox, fromy+toy);
#endif
  fromx += tox; fromy += toy;
}
static void Clear(void) {
  // clear screen
  if (debug) fprintf(stdout, "clear();\r\n");
#ifdef USE_FB
  fbgl_clear(&fb);
#endif
}
static int InitGraphics(char *FRAMEBUFFER_DEV) {
#ifdef USE_FB
  if (debug) fprintf(stderr, "InitGraphics();\r\n");
  // Load PSF1 font
  font = fbgl_load_psf1_font("font-16.psf");
  if (!font) {
    fprintf(stderr, "Failed to load PSF1 font-16.psf\r\n");
    return 1;
  }

  if (fbgl_init(FRAMEBUFFER_DEV, &fb) == -1) {
    fprintf(stdout, "Error: could not open framebuffer device\r\n");
    return -1;
  }
  // Initialize keyboard
  if (fbgl_keyboard_init() != 0) {
    fprintf(stderr, "Failed to initialize keyboard\r\n");
    fbgl_destroy(&fb);
    return 1;
  }
  fbgl_set_fps(FPS);
#endif // USE_FB
  return 0;
}
static void set_scale(uint8_t scale) {
  vscale = scale;
}
static void Wait_Recal(void) {
  if (debug) fprintf(stdout, "Wait_Recal();\r\n");
  usleep(1000000/FPS);
  Clear();
}
static void Intensity_7F(void) {
  vbright = 0x7F;
}
static void Intensity_a(uint8_t i) {
  vbright = i;
}
static void Reset0Ref(void) {
  if (debug) fprintf(stdout, "Reset0Ref();\r\n");
  G_MoveAbs(vx = 0, vy = 0, vscale);
}
static void Moveto_d(int8_t y, int8_t x) {
  if (debug) fprintf(stdout, "Moveto_d(y=%d, x=%d);\r\n", y, x);
  G_MoveRel(vx = x, vy = y, vscale);
}
static void Draw_Line_d(int8_t y, int8_t x) {
  if (debug) fprintf(stdout, "Draw_Line_d(y=%d, x=%d);\r\n", y, x);
  G_LineRel(x, y, vbright, vscale);
}
#endif // not VECTREX

// ################################### End of linux-specific graphics code. #######################################

#define MAX_VERTICES 15

typedef struct {
  int8_t x, y;
} screen;

static inline int8_t trig_mul(int8_t a, int8_t b) {
  // On the 6809 this could be a very short sequence of inline assembly code
  // if the code that GCC creates is not optimal.  I expect these calls to
  // be in the critical path.  However, not urgent.
  return (int8_t)(((int16_t)a * (int16_t)b) >> 6L);
}

// Although this table could be shortened, a full table is faster...
static const int8_t sin_table[256] = {
  0, 2, 3, 5, 6, 8, 9, 11, 12, 14, 16, 17, 19, 20, 22, 23,
  24, 26, 27, 29, 30, 32, 33, 34, 36, 37, 38, 39, 41, 42, 43, 44,
  45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 56, 57, 58, 59,
  59, 60, 60, 61, 61, 62, 62, 62, 63, 63, 63, 64, 64, 64, 64, 64,
  64, 64, 64, 64, 64, 64, 63, 63, 63, 62, 62, 62, 61, 61, 60, 60,
  59, 59, 58, 57, 56, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46,
  45, 44, 43, 42, 41, 39, 38, 37, 36, 34, 33, 32, 30, 29, 27, 26,
  24, 23, 22, 20, 19, 17, 16, 14, 12, 11, 9, 8, 6, 5, 3, 2,
  0, -2, -3, -5, -6, -8, -9, -11, -12, -14, -16, -17, -19, -20, -22, -23,
  -24, -26, -27, -29, -30, -32, -33, -34, -36, -37, -38, -39, -41, -42, -43, -44,
  -45, -46, -47, -48, -49, -50, -51, -52, -53, -54, -55, -56, -56, -57, -58, -59,
  -59, -60, -60, -61, -61, -62, -62, -62, -63, -63, -63, -64, -64, -64, -64, -64,
  -64, -64, -64, -64, -64, -64, -63, -63, -63, -62, -62, -62, -61, -61, -60, -60,
  -59, -59, -58, -57, -56, -56, -55, -54, -53, -52, -51, -50, -49, -48, -47, -46,
  -45, -44, -43, -42, -41, -39, -38, -37, -36, -34, -33, -32, -30, -29, -27, -26,
  -24, -23, -22, -20, -19, -17, -16, -14, -12, -11, -9, -8, -6, -5, -3, -2,
};

static inline int8_t sin_fixed(uint8_t angle) {
  return sin_table[angle&0xFF];
}

static inline int8_t cos_fixed(uint8_t angle) {
  return sin_table[(angle + 64U)&0xFF]; // &0xFF only needed on linux
}

// Objects are stored as 8-bit coordinates centered on 0,0,0

// A model is a list of vertices, plus a list of lines to be drawn from vertex to vertex.

// Sin and Cos tables are lookups returning 8 bit values in the range -64:64, where
// 64 represents 1.0f and -64 is -1.0f  This is to avoid the need for an extra byte
// in calculations if -128:128 were used, or expensive divides if -127:127 were used.

// We will apply the transformations to the vertices first, then draw the ship using
// the transformed vertices.  This differs from the original code where each line was
// being transformed resulting in redundant transformations being calculated any time
// two lines met at a point.  Drawing is optimised by only issuing a move when the
// next line does not follow the proceding line.  In those cases a reset0ref is also
// performed.

typedef struct {
  int8_t x, y, z;
} point3d;

typedef struct {
  int8_t from, to;
} edge_t;

#ifdef __linux
typedef struct Vector { // sometimes known as gl::vec3 ...
  float x;
  float y;
  float z;
} Vector;

static int sign(float v) {
  return v >= 0 ? 1 : -1;
}

static int iround(float f) {
  if (f < 0.0) return -(int)(-f+0.5); // round away from zero so converted extremes are -128 and 128.
  return (int)(f+0.5);
}

// I may be able to make these faster on the Vectrex
// by passing pointers rather than the 3 bytes.  But
// actual testing would be required to confirm that
// the code is faster.  And anyway we're never actually
// running this code on the Vectrex unless a whole lot
// of other code is written too...
static inline Vector cross(Vector v1, Vector v2){
   static Vector r;
   r.x = ((v1.y * v2.z) - (v1.z * v2.y));
   r.y = ((v1.z * v2.x) - (v1.x * v2.z));
   r.z = ((v1.x * v2.y) - (v1.y * v2.x));
   return r;
}

static inline float dot(Vector a, Vector b) {
  return a.x*b.x + a.y*b.y + a.z*b.z;
}

static inline Vector normalize(Vector v) {
  // Our input data is already normalized and this is not needed,
  // but since this doesn't run on the Vectrex, no need to optimize.
  // If we ever do run this on the Vectrex then testing is needed
  // to confirm that avoiding the normalization doesn't break anything.
  // // return v;

  static Vector r;
  float len = sqrtf(dot(v, v));
  // This is very expensive on the Vectrex.  Divides really hurt!
  // The rest of the orientation code could probably be converted
  // from floating point to fixed point relatively easily, but it
  // still wouldn't be fast enough to run smoothly on the Vectrex
  r.x = v.x/len; r.y = v.y/len; r.z = v.z/len;
  return r;
}

static inline Vector add(Vector a, Vector b) {
  a.x += b.x; a.y += b.y; a.z += b.z;
  return a;
}

static inline Vector sub(Vector a, Vector b) {
  a.x -= b.x; a.y -= b.y; a.z -= b.z;
  return a;
}

static inline Vector rescale(Vector v, float s) {
  v.x *= s; v.y *= s; v.z *= s;
  return v;
}
#endif

typedef struct {
  const edge_t *edge;
  const point3d *vertex;
  const uint8_t edge_count;
  const uint8_t vertex_count;
  // These are for use on the Vectrex, but are not currently used yet:
  int16_t x,y,z; // position of center of ship in virtual universe (copied directly from floating point 'location')
  uint8_t rx, ry, rz; // attitude of ship (rotations in x then y then z) relative to viewer derived from <forward, up>

#ifdef __linux
  // I'm considering putting forward and up into a struct 'orientation' ...
  Vector forward, up, location;
#endif
} object_t;

#define SHIP1_POINTS 12
static const point3d ship1_vertex[SHIP1_POINTS] = {
  /*  0 */ { 0, 0, -100/2 },
  /*  1 */ { 0, 0, -20/2 },
  /*  2 */ { 0, -40/2, -60/2 },
  /*  3 */ { 0, 40/2, -60/2 },
  /*  4 */ { -100/2, -40/2, 80/2 },
  /*  5 */ { 100/2, -40/2, 80/2 },
  /*  6 */ { -20/2, 20/2, 120/2 },
  /*  7 */ { 20/2, 20/2, 120/2 },
  /*  8 */ { -30/2, 0, 120/2 },
  /*  9 */ { 30/2, 0, 120/2 },
  /* 10 */ { -40/2, 0, -60/2 },
  /* 11 */ { 40/2, 0, -60/2 },
};
#define SHIP1_EDGES 20
// Reordering the vectors to chain from one to the next has speeded up the graphics enough
// to actually be playable (though some more speed is still needed...)  However we lose the
// Reset0Ref that is inserted whenever there is a discontinuity, so we'll have to try it in
// a real Vectrex to see if the drift is too much.
static const edge_t ship1_edge[SHIP1_EDGES] = {
  {  0, 10 }, { 10,  1 }, {  1, 11 }, { 11,  0 }, {  0,  2 }, {  2,  1 }, {  1,  3 }, {  3,  0 },
  {  7,  1 }, {  1,  9 }, {  9,  7 }, {  7,  6 }, {  6,  8 }, {  8,  9 }, {  9,  5 }, {  5,  1 }, {  1,  4 }, {  4,  8 }, {8, 1}, {  1,  6 },
};
object_t ship1 = {
  ship1_edge, ship1_vertex, SHIP1_EDGES, SHIP1_POINTS, 0,0,0, 0,0,0
};

#define SHIP2_POINTS 15
static const point3d ship2_vertex[SHIP2_POINTS] = {
  /*  0 */ { 0, 0, -90/2 },
  /*  1 */ { 0, 0, 90/2 },
  /*  2 */ { 0, -34/2, /*-129*/ -128/2 },
  /*  3 */ { -45/2, 0, /*150*/ 127/2 },
  /*  4 */ { 45/2, 0, /*150*/ 127/2 },
  /*  5 */ { -45/2, 0, -60/2 },
  /*  6 */ { -45/2, 0, 60/2 },
  /*  7 */ { 45/2, 0, -60/2 },
  /*  8 */ { 45/2, 0, 60/2 },
  /*  9 */ { -60/2, 24/2, /*150*/ 127/2 },
  /* 10 */ { 60/2, 24/2, /*150*/ 127/2 },
  /* 11 */ { -60/2, 24/2, 60/2 },
  /* 12 */ { 60/2, 24/2, 60/2 },
  /* 13 */ { -60/2, -27/2, -90/2 },
  /* 14 */ { 60/2, -27/2, -90/2 },
};
#define SHIP2_EDGES 21
static const edge_t ship2_edge[SHIP2_EDGES] = {
  {  2,  0 }, {  0,  7 }, {  7,  8 }, {  8,  1 }, {  1,  6 }, {  6,  5 }, {  5,  0 },
  {  8, 12 }, { 12, 10 }, { 10,  4 }, {  4,  8 }, {  8, 14 }, { 14,  2 }, {  2, 13 },   { 13,  6 }, {  6,  3 }, {  3,  9 }, {  9, 11 }, { 11,  6 },
  {  5, 13 },
  { 14,  7 },
};

object_t ship2 = {
  ship2_edge, ship2_vertex, SHIP2_EDGES, SHIP2_POINTS, 0,0,0, 0,0,0
};

#define SHIP3_POINTS 11
static const point3d ship3_vertex[SHIP3_POINTS] = {
  /*  0 */ { 0, 0, -100/2 },
  /*  1 */ { 0, 0, 20/2 },
  /*  2 */ { 0, 40/2, 100/2 },
  /*  3 */ { 0, 40/2, -40/2 },
  /*  4 */ { 0, 80/2, 100/2 },
  /*  5 */ { -100/2, -40/2, 100/2 },
  /*  6 */ { 100/2, -40/2, 100/2 },
  /*  7 */ { -40/2, -20/2, 100/2 },
  /*  8 */ { 40/2, -20/2, 100/2 },
  /*  9 */ { -60/2, 0, -40/2 },
  /* 10 */ { 60/2, 0, -40/2 },
};

#define SHIP3_EDGES 17
static const edge_t ship3_edge[SHIP3_EDGES] = {
  {  0, 10}, { 10,  1}, {  1,  9}, {  9,  0}, {  0,  3}, {  3,  1}, {  1,  4}, {  4,  2}, {  2,  1}, {  1,  6}, {  6,  8}, {  8,  1}, {  1,  5}, {  5,  7}, {  7,  1},
  { 10,  3}, {  3,  9},
};
object_t ship3 = {
  ship3_edge, ship3_vertex, SHIP3_EDGES, SHIP3_POINTS, 0,0,0, 0,0,0
};

#define SHIP4_POINTS 11
static const point3d ship4_vertex[SHIP4_POINTS] = {
  /*  0 */ { 0, 10/2, -20/2 },
  /*  1 */ { 0, -25/2, -45/2 },
  /*  2 */ { 0, 2/2, -92/2 },
  /*  3 */ { -20/2, -10/2, 110/2 },
  /*  4 */ { 20/2, -10/2, 110/2 },
  /*  5 */ { -22/2, 15/2, -60/2 },
  /*  6 */ { 22/2, 15/2, -60/2 },
  /*  7 */ { -30/2, 2/2, -70/2 },
  /*  8 */ { 30/2, 2/2, -70/2 },
  /*  9 */ { -90/2, 20/2, 90/2 },
  /* 10 */ { 90/2, 20/2, 90/2 },
};

#define SHIP4_EDGES 23
static const edge_t ship4_edge[SHIP4_EDGES] = {
  {  2,  8 }, {  8,  0 }, {  0,  7 }, {  7,  2 }, {  2,  1 }, {  1,  0 }, {  0, 10 }, { 10,  4 }, {  4,  3 }, {  3,  0 }, {  0,  4 }, {  4,  1 }, {  1,  3 }, {  3,  9 }, {  9,  0 },
  {  8,  6 }, {  6,  5 }, {  5,  7 }, {  7,  1 }, {  1,  8 }, {  8,  7 },
  {  6,  0 }, {  0,  5 },
};
object_t ship4 = {
  ship4_edge, ship4_vertex, SHIP4_EDGES, SHIP4_POINTS, 0,0,0, 0,0,0
};

static object_t *ship[4] = { &ship1, &ship2, &ship3, &ship4 };

// these need to be moved into the object_t ...
//static int8_t x[4] = {0, 0, 0, 0}, y[4] = {0, 0, 0, 0};  //int8_t x[4] = {-120, 0, 120, 0}, y[4] = {-40, 92, -40, -80};

// rotational values for 4 ships. (Though TG only needs 3 copies of the same ship)
static uint8_t angle_x[4] = {5,5,5,0}, angle_y[4] = {5,0,4,0}, angle_z[4] = {5,0,250,0};  // 0:255 angles make a circle.
static uint8_t scale = 150; // 5;  // We'll use hardware scaling and adjust it separately from the viewing angle transformation


static screen screen_vertex[MAX_VERTICES]; // As many vertices as largest object requires.

static inline void draw_object(const object_t *object, uint8_t scale, int8_t ox, int8_t oy) {
  const edge_t *edge = object->edge;
  int8_t i = 0, from, to, last_to = -1, vfx, vfy;
  set_scale(scale);
  for (;;) {
    if (i == (int8_t)object->edge_count) return;
    from = edge[i].from; to = edge[i].to;
    vfx = screen_vertex[from].x; vfy = screen_vertex[from].y;
    if (from != last_to) { Reset0Ref(); Moveto_d(oy, ox);  Moveto_d(vfy, vfx); }
    last_to = to;
    Draw_Line_d(screen_vertex[to].y - vfy, screen_vertex[to].x - vfx);
    i += 1;
  }
}

static inline void project_vertex(const point3d *v, uint8_t ax, uint8_t ay, uint8_t az, screen *vec) {
  // This is basically the same code as my first attempt at Tailgunner,
  // but simplified and tidied up a lot, and using 8- instead of 16-bit fixed point...
  int8_t x = v->x, y = v->y, z = v->z, x1, y1, cosx, sinx, cosy, siny, cosz, sinz;

  // trig lookups:
  cosx = cos_fixed(ay); sinx = sin_fixed(ay);
  cosy = cos_fixed(ax); siny = sin_fixed(ax);
  cosz = cos_fixed(az); sinz = sin_fixed(az);

  // Rotate around X
  y1 = trig_mul(y, cosy) -  trig_mul(z, siny);
  z  = trig_mul(y, siny) +  trig_mul(z, cosy);
  y  = y1;

  // Rotate around Y
  x1 =  trig_mul(x, cosx) +  trig_mul(z, sinx);
  z  = -trig_mul(x, sinx) +  trig_mul(z, cosx);
  x  =  x1;

  // Rotate around Z
  vec->x = trig_mul(x, cosz) -  trig_mul(y, sinz);
  vec->y = trig_mul(x, sinz) +  trig_mul(y, cosz);

  // A simple x,y projection will suffice!
  // However if we really needed perspective to be applied, we could map the X value
  // using a lookup table based on the point's Z coordinate.  I have suitable code
  // somewhere from another vectrex program...
}

/*
     Calculating the orientation and flight path of a ship on the fly is expensive, and
     made worse by the fact that we're not actually going to work with a true 3D model of
     the world using 16-bit 3D spatial coordinates and bezier-determined flightpaths, where
     the ship's orientation along the flight path matches what the human expects.  These
     turn out to be sufficiently expensive to compute that doing so is not feasible while
     also computing the 3D view of the ship.  So either the flight paths or the ship drawing
     or both will be pre-computed for speed.  A previous attempt saved vector information
     for drawing the entire flight but the space requirements were so large that only one
     wave was practical.  Drawing of the rotated ship was also too expensive in previous
     iterations but this code takes the shortcut of using the hardware scaling to remove
     some of the computation (especially expensive divides), so our assumption is that for
     a known spatial position and orientation of a ship, we ony need to pre-compute the ship's
     orientation, screen position, and scale to draw at - we'll assume that with that information
     we can draw a ship correctly.  The precomputed data can be found using 32-bit arithmetic
     and a faster processor.

     So to preconstruct a flight path, we need to record the ship number, and a series of
       x,y for center of ship
       rx,ry,rz for rotation of ship around center on each axis
       scale

     which will require 6 bytes per position, so at 30 fps and say 3 seconds per flight, that's
     approximately 6*30*3 = 540 bytes per flightpath.  Three ships per wave comes to 1620 bytes
     so if we assume 8K for code and 24K for data we can expect roughly 15 waves of precomputed
     flight paths for 24K of Eprom.  More if we have smaller code or a 48K Eprom.  This seems
     to be ballpark so on that basis I am going to continue the project using those assumptions.

     (It would be nice if an entire flight could take place in 256 frames because then the
     lookup tables could all be byte offsets into separate tables.  At 30FPS we can afford
     8 seconds worth of frames so this is definitely a plausible optimisation!)

     Note that the flight path code (that uses floats for its maths) which adjusts the model's
     orientation per frame allows us to store a flight path using just 6 bits per frame if all
     we store are the bits for up/down/left/right/cw/ccw! (not 3 bits because each one can
     alo be 'do nothing', i.e. left/straight/right, so 2 bits per axis)

     However that code is currently too expensive to call on every frame, hence the stored
     flightpaths need 3 rotations, a screen x,y, and a scale.
 */

static inline void project_object(const object_t *object,
                                  uint8_t angle_x, uint8_t angle_y, uint8_t angle_z,
                                  uint8_t scale,
                                  int8_t x, int8_t y) {
  Intensity_a(((255U-scale)>>2)+48U);
  for (uint8_t i = 0; i < object->vertex_count; i++) {
    project_vertex(&object->vertex[i], angle_x, angle_y, angle_z, &screen_vertex[i]);
  }
  draw_object(object, scale, x, y);
}

/* The UpDown LeftRight CwCCw code below is derived from:
 * pyr.c - implementation of Pitch-Yaw-Roll
 * (c)opyright Benjamin 'blindcoder' Schieder <blindcoder@scavenger.homeip.net>
 * Copyright (C) 2006 Benjamin Schieder
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 * Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 * Code found at http://www.happypenguin.org/forums/viewtopic.php?f=7&t=3425
 *
 */

// (The terms above refer to the UpDown LeftRight CwCCw code below.)

#ifdef __linux

// DEGREES: #define angles_to_radians (M_PI/180.0)
// 25 ANGLE CIRCLES:
#define angles_to_radians (M_PI/128.0)

// Currently testing replacing these 3 procedures with new code:
void UpDown(Vector *forward, Vector *up, float degree){
   /* here we move the ships nose up or down depending on the value of degree.
    * The only two vectors we change here are forward and up
    */
   Vector new_forward, new_up; // , right, new_right;
   float radians, c, s; // if we move to the Vectrex we'll be using 256 angles per circle,
   // and our own cos/sin tables, so neither degrees nor radians will be needed.
   // in the mean time for the sake of consistency it might be preferable to
   // use 256 angles per circle and at least do away with 'degrees'.
   radians = degree*angles_to_radians;
   c = cos(radians);
   s = sin(radians);

   //right = cross(*up, *forward);

   new_forward.x = forward->x * c-up->x * s;
   new_forward.y = forward->y * c-up->y * s;
   new_forward.z = forward->z * c-up->z * s;

   new_up.x = forward->x * s + up->x * c;
   new_up.y = forward->y * s + up->y * c;
   new_up.z = forward->z * s + up->z * c;

   //new_right = cross(*up, *forward);

   up->x = new_up.x;
   up->y = new_up.y;
   up->z = new_up.z;
   forward->x = new_forward.x;
   forward->y = new_forward.y;
   forward->z = new_forward.z;
   //right = new_right;
}

void LeftRight(Vector *forward, Vector *up, float degree){
   /* here we move the ships nose left or right depending on the value of degree.
    * The only two vectors we change here are forward and right (and so, only
    * forward, because we calculate right from forward and up vectors)
    */
   Vector new_forward, /*new_up,*/ right, new_right;
   float radians;
   float c, s;

   radians = degree * angles_to_radians;
   c = cos(radians);
   s = sin(radians);

   right = cross(*up, *forward);

   new_forward.x = forward->x * c-right.x * s;
   new_forward.y = forward->y * c-right.y * s;
   new_forward.z = forward->z * c-right.z * s;

   new_right.x = forward->x * s + right.x * c;
   new_right.y = forward->y * s + right.y * c;
   new_right.z = forward->z * s + right.z * c;

   right = new_right;

   //new_up=cross(*forward, right);

   forward->x = new_forward.x;
   forward->y = new_forward.y;
   forward->z = new_forward.z;
   //up->x = new_up.x;
   //up->y = new_up.y;
   //up->z = new_up.z;
}

void CwCCw(Vector *forward, Vector *up, float degree){
   /* Here we rotate the ship clockwise or counter-clockwise.  The only vectors we need
      to change here are up and right (and so, only up, because we can calculate right
      from forward and up vectors when we need it)
    */
   // Vector new_forward;
   Vector new_up, right, new_right;
   float radians;
   float c, s;

   radians=degree*angles_to_radians;
   c=cos(radians);
   s=sin(radians);

   right = cross(*up, *forward);

   new_right.x = right.x * c-up->x * s;
   new_right.y = right.y * c-up->y * s;
   new_right.z = right.z * c-up->z * s;

   new_up.x = right.x * s + up->x * c;
   new_up.y = right.y * s + up->y * c;
   new_up.z = right.z * s + up->z * c;

   //new_forward = cross(right, *up);

   right = new_right;
   up->x = new_up.x;
   up->y = new_up.y;
   up->z = new_up.z;

   //forward->x = new_forward.x;
   //forward->y = new_forward.y;
   //forward->z = new_forward.z;
}
#endif // __linux

#ifdef __linux

// Convert the orientation as defined by the forward and up vectors to an Euler
// rotation rx,ry,rz performed in that order as per the Vectrex display code.
void get_rotation_parameters(Vector forward, Vector up, float *rx, float *ry, float *rz)
{
    Vector f = normalize(forward);
    Vector u = normalize(up);
    Vector r = cross(u, f);
    r = normalize(r);
    u = cross(f, r);  // be sure that the up vector is consistent...
    // - this is paranoia and can probably be removed for speed, after checking.

    *ry = asinf(-f.x);
    if (fabsf(cosf(*ry)) > 1e-6) {
        *rx = atan2f(f.y, f.z);
        *rz = atan2f(u.x, r.x);
    } else {
        // Gimbal lock
        *rx = 0.0f;
        *rz = atan2f(-r.y, u.y);
    }
}
#endif // __linux


/*
   The main() below will become a flightpath generator for tailgunner ships.
   To reduce the runtime overhead in a Vectrex implementation of Tailgunner,
   we need to copy the original arcade version in pre-computing the flight paths.

   Coordinate space is X:-512:511 Y:-512:511 Z:0:1023  Viewer is at [0,0,0]

   Tailgunner ships initially fly over a 2D plane placed at maximum distance
   from the player (Z=1023).  At some point they then turn out of that plane
   and fly towards the viewer, having reoriented the ship so that it is
   facing us, with the ship's 'up' being the world's 'up'.  The ships at
   this stage only ever approach us (decreasing Z) but can turn up/down and
   left/right.  Generally the left/right displacement is in one direction
   only, but they can weave up or down at random.

   This code currently implements only the transition from the XY plane to
   the XZ/YZ planes.  It does so by causing them to fly around a quarter
   circle placed on a sphere starting at the back of the sphere and ending
   on one of the edges where Z is the center of the sphere so that when the
   ship departs from the sphere at a tangent, it is flying directly at the
   viewer/player.

   Although we know the 16 bit world X,Y,Z coordinates when generating the path,
   the data that is saved to the arrays in the Vectrex rom are only the screen
   X and Y positions of the center of the ship, a scale factor for the ship to
   be drawn at using hardware scaling, and the rx,ry,rz rotations needed to draw
   the ship at the correct rotational orientation.  The Vectrex does not need to
   know the 16 bit 3D world positions of the ship.  Missile targetting is not
   true 3D, it is primarily 2D with a fudge factor for the distance to the
   center of the ship based on the scale value.
   
   We may also want to take into account the viewing angle from the viewer to
   the ship when working out the rotation to use when rendering the ship.  But
   this is a calculation that can be done offline, so a more expensive solution
   such as converting between spherical (i.e. 3D polar) and Cartesian (XYZ)
   coordinates is not unreasonable.  I may have some suitable code that would help
   in:    linux:~/src/3d/spherical/   and   linux:~/src/3d/3dmath/

*/

#ifdef __linux
// Function to output the manoeuver trajectory of the ship
void manoeuver_arc(object_t ship, int N, float R) {
    // Get signs of the ship's 2D forward motion
    int sx = sign(ship.forward.x);
    int sy = sign(ship.forward.y);

    // Set sphere center C such that the ship is touching the farther point on the sphere
    float Cx = ship.location.x;
    float Cy = ship.location.y;
    float Cz = ship.location.z-R;

    // Vector from starting point A to center of sphere
    float ax = ship.location.x - Cx;
    float ay = ship.location.y - Cy;
    float az = ship.location.z - Cz;

    // Rotation axis is normalized 2D forward vector projected on XY plane
    Vector axis = normalize((Vector){ ship.forward.x, ship.forward.y, 0.0f });

    // A great circle around the sphere that touches the ship will have a
    // point 90 degrees round the circle that lies on the circumference of
    // the sphere when projected in X,Y as viewed by the player.  If the
    // ship departs from the arc across the sphere at this point it will
    // be heading directly to the viewer, with decreasing Z.
    float theta_total = M_PI * 0.5f;
    float dtheta = theta_total / N;

    // Loop over N intermediate steps, a step per frame, so choose N such that
    // the turn out of the rear Z plane takes an appropriate length of time.
    // Initial guess being about half a second.
    for (int i = 1; i <= N; ++i) {
        float theta = i * dtheta;

        // Rodrigues' rotation formula to rotate vector A around axis by angle theta
        Vector v = { ax, ay, az };
        Vector k = axis;

        float cos_t = cosf(theta);
        float sin_t = sinf(theta);
        float k_dot_v = k.x * v.x + k.y * v.y + k.z * v.z;

        // Cross product k x v
        Vector k_cross_v = {
            k.y * v.z - k.z * v.y,
            k.z * v.x - k.x * v.z,
            k.x * v.y - k.y * v.x
        };

        // Rodrigues rotated vector p = v*cosθ + (k×v)*sinθ + k*(k·v)*(1 - cosθ)
        Vector p = {
            v.x * cos_t + k_cross_v.x * sin_t + k.x * k_dot_v * (1 - cos_t),
            v.y * cos_t + k_cross_v.y * sin_t + k.y * k_dot_v * (1 - cos_t),
            v.z * cos_t + k_cross_v.z * sin_t + k.z * k_dot_v * (1 - cos_t)
        };

        // Compute ship position at this step
        float x = Cx + p.x;
        float y = Cy + p.y;
        float z = Cz + p.z;

        // Tangent at point p is axis x radius vector p
        Vector tangent = {
            axis.y * p.z - axis.z * p.y,
            axis.z * p.x - axis.x * p.z,
            axis.x * p.y - axis.y * p.x
        };

        // Normalize tangent to get current 'forward' vector
        Vector forward = normalize(tangent);

        // 'up' vector points toward circle center from current position
        Vector up = normalize(sub((Vector){ Cx, Cy, Cz }, (Vector){ x, y, z }));

        // Output the position and orientation vectors for this step
        printf("Step %2d: Pos(%8.3f, %8.3f, %8.3f) Forward(%6.3f, %6.3f, %6.3f) Up(%6.3f, %6.3f, %6.3f)\r\n",
               i, x, y, z, forward.x, forward.y, forward.z, up.x, up.y, up.z);

        // TO DO: write parameters to array or output file or just draw the ship
        // for feedback while testing by updating the ship's object_t struct
    }

    // TO DO: After finishing the quarter-circle arc, add code to right the ship's 'up' vector 
    // as it approaches the viewer (facing along -Z).
}

// Rotate v by angle (radians) around axis
static Vector rotate_vector(Vector v, Vector axis, float angle) {
    float c = cosf(angle), s = sinf(angle);
    Vector axis_n = normalize(axis);
    float dotp = dot(axis_n, v);
    Vector crossp = cross(axis_n, v);

    Vector term1 = rescale(v, c);
    Vector term2 = rescale(crossp, s);
    Vector term3 = rescale(axis_n, dotp * (1 - c));
    return add(add(term1, term2), term3);
}





// ##########################################################################################
// # These don't work as advertised: they appear to remain relative to universe coordinates #
// # The names do make sense when the ship is facing the viewer, but if it is at 90 degrees #
// # (eg facing sideways or in the rear Z plane) the names don't correspond.                #
// ##########################################################################################

// Yaw: rotate around model's local up axis
void yaw(float angle, Vector *forward, Vector *up) {
    *forward = rotate_vector(*forward, *up, angle);
    *forward = normalize(*forward);

    // adjust up to maintain orthogonality, optional small drift correction
    Vector right = normalize(cross(*forward, *up));
    *up = normalize(cross(right, *forward));
}

// Pitch: rotate around model's local right axis
void pitch(float angle, Vector *forward, Vector *up) {
    Vector right = normalize(cross(*forward, *up));
    *forward = rotate_vector(*forward, right, angle);
    *forward = normalize(*forward);

    *up = normalize(cross(right, *forward));
}

// Roll: rotate around model's local forward axis
void roll(float angle, Vector *forward, Vector *up) {
    *up = rotate_vector(*up, *forward, angle);
    *up = normalize(*up);
    // Optional: orthogonalize up to ensure it's perpendicular to forward
    Vector right = normalize(cross(*forward, *up));
    *up = normalize(cross(right, *forward));
}





static Vector slerp(Vector v0, Vector v1, float t) {
  v0 = normalize(v0);
  v1 = normalize(v1);

  float dotp = dot(v0, v1);
  if (dotp > 1.0f) dotp = 1.0f;
  if (dotp < -1.0f) dotp = -1.0f;

  float theta = acosf(dotp) * t;
  Vector relative_vec = sub(v1, rescale(v0, dotp));
  relative_vec = normalize(relative_vec);

  Vector part1 = rescale(v0, cosf(theta));
  Vector part2 = rescale(relative_vec, sinf(theta));

  return add(part1, part2);
}

void set_orientation(Vector *forward, Vector *up, Vector target_forward, Vector target_up, int steps) {
  for (int step = 0; step <= steps; step++) {
    float t = (float)step / (float)steps;
    Vector f_interp = slerp(*forward, target_forward, t);
    Vector u_interp = slerp(*up, target_up, t);

    // Normalize results
    f_interp = normalize(f_interp);
    u_interp = normalize(u_interp);

    // Output the vectors after the current interpolation step

    //printf("Step %2d | Forward: (%+1.4f, %+1.4f, %+1.4f) | Up: (%+1.4f, %+1.4f, %+1.4f)\n",
    //       step, f_interp.x, f_interp.y, f_interp.z, u_interp.x, u_interp.y, u_interp.z);

    // Update forward and up for next step
    if (step < steps) {
      *forward = f_interp;
      *up = u_interp;
      printf("Pos(%8.3f, %8.3f, %8.3f) Forward(%6.3f, %6.3f, %6.3f) Up(%6.3f, %6.3f, %6.3f)\r\n",
             ship1.location.x, ship1.location.y, ship1.location.z,
             ship1.forward.x, ship1.forward.y, ship1.forward.z,
             ship1.up.x, ship1.up.y, ship1.up.z);
    }
  }
}
#endif // __linux


// #################################### Main entrypoint ########################################


#ifdef __linux
int main(int argc, char **argv) {
#else
int main(void) {
#endif

#ifdef __linux
  Vector right;
  float speed;
  int print = 1;
  float prx, pry, prz;
#ifdef USE_FB
  fbgl_key_t key;
#endif
#endif

#ifndef VECTREX
  if (argv[1] != NULL && *argv[1] == '/') {
    (void)InitGraphics(argv[1]);
  } else {
    (void)InitGraphics("/dev/fb1"); // or NULL for default...
  }
#endif


#ifdef __linux
  //system("stty sane");
#endif

#ifdef __linux
  speed=1.0;
  ship[0]->location.x=0.0; ship[0]->location.y=0.0; ship[0]->location.z=0.0;
  ship[0]->forward.x=0.0; ship[0]->forward.y=0.0; ship[0]->forward.z=1.0; // our forward vector. Where the ships nose points to.
  ship[0]->up.x=0.0; ship[0]->up.y=1.0; ship[0]->up.z=0.0; // The up vector. What we see when we turn our head up.
#ifdef USE_FB
  key = 'H';
#endif

  right = cross(ship[0]->up, ship[0]->forward);

  // Manual test.  Will later develop into the flightpath generator with the
  // help of a few random numbers.

  // testing with ship upright and in center of screen approaching viewer.
  // yaw/pitch/roll commands do not yet work with the ship in the rear Z plane
  // entering from off-screen.
  
  ship1.location.x = 0;
  ship1.location.y = 0;
  ship1.location.z = 1023;
  
  ship1.forward.x =  0.0;
  ship1.forward.y =  0.0;
  ship1.forward.z = -1.0;
  
  ship1.up.x = 0.0;
  ship1.up.y = 1.0;
  ship1.up.z = 0.0;

  printf("Initial: Pos(%8.3f, %8.3f, %8.3f) Forward(%6.3f, %6.3f, %6.3f) Up(%6.3f, %6.3f, %6.3f)\r\n",
         ship1.location.x, ship1.location.y, ship1.location.z,
         ship1.forward.x, ship1.forward.y, ship1.forward.z,
         ship1.up.x, ship1.up.y, ship1.up.z);

  manoeuver_arc(ship1, 16, 50);
#endif

  
  for (;;) {

#ifdef __linux
    if (print == 2) {
      // apply movement if there was any.  forward vector holds delta x,y,z.
      ship[0]->location.x += ship[0]->forward.x*speed;
      ship[0]->location.y += ship[0]->forward.y*speed;
      ship[0]->location.z += ship[0]->forward.z*speed;
    }

    if (print){
      print = 0;

      right = cross(ship[0]->up, ship[0]->forward);

      // The pitch/roll/yaw calculations are being done in floating point on a real computer, then will be recorded
      // for use as predetermined flight paths in the Vectrex tailgunner remake, much as was done in the original
      // Cinematronic tailgunner arcade game.  The data to be saved for the Vectrex is all 8 bit -  even the location
      // of the ship in 3D space which is really a 1K cube, because all we're really saving are the x,y screen
      // coordinates of the ship center, and a scale factor for drawing it to represent z distance.  (missile
      // hit detection is really just 2D, also like the original game.)

      get_rotation_parameters(ship[0]->forward, ship[0]->up, &prx, &pry, &prz);

      // Convert to data suitable for storing and reuse as a precomputed fliht path on the Vectrex:
      angle_x[0] = iround(prx*128.0/M_PI);
      angle_y[0] = iround(pry*128.0/M_PI);
      angle_z[0] = iround(prz*128.0/M_PI);

      // Still to determine a suitable sx, sy, and scale...

      // ** NOTE ** I think x and y may be swapped in the 'Location' lines below!
      // Either that or the ship definitions are wrong.  Easy enough to hack it
      // but would be nice to be consistent throughout and have the x value decrease
      // when the ship is pointing left :-)  (not so clear about the sign of the
      // y value since conventions about y vary...)
      #ifdef FLOATING
      // debugging the orientation generation is easier using the original floats but
      // debugging the vectrex display code is easier using the converted integer values...
      fprintf(stderr, "Rotation: %f %f %f\r\n", prx, pry, prz);
      fprintf(stderr, "Location: [%f %f %f]\r\n", ship[0]->location.x, ship[0]->location.y, ship[0]->location.z);
      fprintf(stderr, "Forward:  x %0.3f y %0.3f z %0.3f\r\n", ship[0]->forward.x, ship[0]->forward.y, ship[0]->forward.z);
      fprintf(stderr, "Up:       x %0.3f y %0.3f z %0.3f\r\n", ship[0]->up.x, ship[0]->up.y, ship[0]->up.z);
      fprintf(stderr, "Right:    x %0.3f y %0.3f z %0.3f\r\n\r\n", right.x, right.y, right.z);
      #else
      fprintf(stderr, "Rotation: %d %d %d\r\n", iround(prx*128.0/M_PI), iround(pry*128.0/M_PI), iround(prz*128.0/M_PI));
      fprintf(stderr, "Location: [%d %d %d]\r\n", iround(ship[0]->location.x), iround(ship[0]->location.y), iround(ship[0]->location.z));
      fprintf(stderr, "Forward:  [%d %d %d]\r\n", iround(ship[0]->forward.x*128.0), iround(ship[0]->forward.y*128.0), iround(ship[0]->forward.z*128.0));
      fprintf(stderr, "Up:       [%d %d %d]\r\n", iround(ship[0]->up.x*128.0), iround(ship[0]->up.y*128.0), iround(ship[0]->up.z*128.0));
      fprintf(stderr, "Right:    [%d %d %d]\r\n\r\n", iround(right.x*128.0), iround(right.y*128.0), iround(right.z*128.0));
      #endif
    }

#endif // __linux

    Wait_Recal();
    Reset0Ref();
    Intensity_7F();

#ifdef USE_FB

    // TO DO: for flight path generation, I need some 'turn in the direction of' code to steer the ship.
    // which will be called here, instead of this manual keyboard data entry that was for debugging.
    // The steering will be towards given 'next target' points, which will be updated on specific
    // frame numbers.  The orientation corrections for the back plane phase and the approach phase
    // should be simple, but the transition between those phases will have to be done carefully
    // to avoid gimbal lock issues!

    // We are not affecting movement here, just orientation.
    // NOTE that up/down left/right roll are misnomers - the adjustments are *not* relative
    // to the frame of the spacecraft but to the fixed universe from the players viewpoint.
    // This is not ideal but it does avoid the need for expensive (and more importantly, difficult)
    // quaternion code that frankly I'm not sure I can write.  A simple x,y,z cartesian/euler
    // solution is not only doable, I think it's how the original arcade game worked.

    float degree256 = 2.0*M_PI/256.0;
    switch ((int)key){
      case 'I': // I: Pitch up          (rotate around model's X axis)\r\n");
        print = 1;
        //UpDown(&ship[0]->forward, &ship[0]->up, 1); break;
        pitch(-degree256*4, &ship1.forward, &ship1.up);
        break;
    case 'K': // K: Pitch down
        print = 1;
        //UpDown(&ship[0]->forward, &ship[0]->up,-1); break;
        pitch(degree256*4, &ship1.forward, &ship1.up);
        break;
        
    case 'U': // U: Yaw (turn) left   (rotate around model's Z axis)
        print = 1;
        //CwCCw(&ship[0]->forward, &ship[0]->up,-1); break;
        yaw(-degree256*4, &ship1.forward, &ship1.up);
        break;
    case 'O': // O: Yaw (turn) right
        print = 1;
        //CwCCw(&ship[0]->forward, &ship[0]->up, 1); break;
        yaw(degree256*4, &ship1.forward, &ship1.up);
        break;
        
      case 'J': // Roll CCW          (rotate around model's Y axis)
        print = 1;
        //LeftRight(&ship[0]->forward, &ship[0]->up,-1); break; // move nose right
        roll(degree256*4, &ship1.forward, &ship1.up);
        break;
      case 'L': // Roll CW
        print = 1;
        //LeftRight(&ship[0]->forward, &ship[0]->up, 1); break;
        roll(-degree256*4, &ship1.forward, &ship1.up);
        break;

      case 'H':
        fprintf(stderr, "      M:  Move\r\n");
        fprintf(stderr, "      M:  Print\r\n\r\n");

        // These descriptions only work when the ship is facing the player:
        fprintf(stderr, "      I: Pitch up          (rotate around model's X axis)\r\n");
        fprintf(stderr, "      K: Pitch down\r\n\r\n");
        
        fprintf(stderr, "      U: Yaw (turn) left   (rotate around model's Z axis)\r\n");
        fprintf(stderr, "      O: Yaw (turn) right\r\n\r\n");
        
        fprintf(stderr, "      J: Roll CCW          (rotate around model's Y axis)\r\n");
        fprintf(stderr, "      L: Roll CW\r\n\r\n");
        break;
      case 'M':
        print = 2;
        break;
      case 'P':
        print = 1;
        break;
      case 'Z':
        print = 1;

        // return ship to attack mode, facing the viewer/player
        ship[0]->forward.x=0.0; ship[0]->forward.y=0.0; ship[0]->forward.z=-1.0;
        ship[0]->up.x=0.0; ship[0]->up.y=1.0; ship[0]->up.z=0.0;


        // TO DO: return to orientation with slerp over several steps instead of instantly.
        
        break;
      default: print = 0; break;
    }

    key = fbgl_poll_key(); // poor library interface - short term hack
    if (key == FBGL_KEY_ESCAPE || key == 'Q') {
      fbgl_destroy(&fb);
      exit(EXIT_SUCCESS);
    }

    // TO DO: define scale as a function of z pos in expanded universe
#else
    scale += 1;
#endif // __linux

    // As a result of the above, I have a forward-pointing vector which can be used to
    // move the ship's x,y,z position in space, and an upward-pointing (and implicitly
    // a right-pointing) vector that defines its orientation.  The call below converts
    // these into an euler rotation that can be used to render the ship on the screen
    // at a plausible orientation.  Note that this does *not* take into account the
    // X or Y angle from the viewer to the ship (which is projected as if it were at 0,0)
    // but maybe that can be corrected by adding to angle_x and angle_y depending on
    // the x and y coordinates of the ship in world space and it's distance.  Which
    // if needed can hopefully be done using table lookups rather than an expensive divide.
    project_object(ship[0], angle_x[0], angle_y[0], angle_z[0], scale, 0, 0);

    if (scale == 255) scale = 10;
  }
}
