#define MAX_VERTICES 15

typedef struct {
  int8_t x, y;
} screen;

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

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 my earlier code where each line was
// being transformed as it was drawn, resulting in redundant transformations being
// calculated.  Drawing is optimised by only issuing a reset0ref when the next line
// does not follow the proceding line.

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

typedef struct {
  int8_t from, to;
} edge_t;

typedef struct {
  const edge_t *edge;
  const point3d *vertex;
  uint8_t edge_count;
  uint8_t vertex_count;

#ifdef GENPATH
  int x,y; // position of center of ship on screen // we store in 32 bit int to detect when drawing goes off-screen but save 8 bit ints
  uint8_t scale; // scale to draw on screen
  uint8_t rx, ry, rz; // attitude of ship (rotations in x then y then z) relative to viewer derived from <forward, up>
  float speed;
  Vector forward, up, location;
#else // TG
  // These all point to arrays (up to 256 elements)
  const int8_t *x, *y;        // screen coords for center of rendered ship
  const uint8_t *scale;        // render this ship at this scale
  const uint8_t *rx, *ry, *rz;   // attitude of ship (rotations in x then y then z) relative to viewer derived from <forward, up>
  uint8_t steps; // actually 'last step'
#endif
} object_t;

// ########################################################################

#define MODEL1_POINTS 12
static const point3d model1_vertex[MODEL1_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 MODEL1_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 model1_edge[MODEL1_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 model1 = {
  model1_edge, model1_vertex, MODEL1_EDGES, MODEL1_POINTS, 0,0,0, 0,0,0, 0
};

#define MODEL2_POINTS 15
static const point3d model2_vertex[MODEL2_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 MODEL2_EDGES 21
static const edge_t model2_edge[MODEL2_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 model2 = {
  model2_edge, model2_vertex, MODEL2_EDGES, MODEL2_POINTS, 0,0,0, 0,0,0, 0
};

#define MODEL3_POINTS 11
static const point3d model3_vertex[MODEL3_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 MODEL3_EDGES 17
static const edge_t model3_edge[MODEL3_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 model3 = {
  model3_edge, model3_vertex, MODEL3_EDGES,MODEL3_POINTS, 0,0,0, 0,0,0, 0
};

#define MODEL4_POINTS 11
static const point3d model4_vertex[MODEL4_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 MODEL4_EDGES 23
static const edge_t model4_edge[MODEL4_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 model4 = {
  model4_edge, model4_vertex, MODEL4_EDGES, MODEL4_POINTS, 0,0,0, 0,0,0, 0
};


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;
  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) { // includes first entry
      Reset0Ref();
      set_scale(127);
      Moveto_d(oy, ox);
      set_scale(scale);
      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...
}

// Precomputed flight paths will be stored as x,y,scale, rx,ry,rz - which will
// be used by project_object below to render the ship on screen.  Missile hits
// remain to be done, and they'll be done hackily using the screen x,y rather
// than the world [x,y,z] which the Vectrex code will have no knowlege of.

static inline void project_object(const object_t *object,
                                  uint8_t rx, uint8_t ry, uint8_t rz,
                                  uint8_t sc,
                                  int8_t sx, int8_t sy) {
  if (sc > (255U-32U)) Intensity_a((255U-sc)<<2U); else Intensity_7F();
  for (uint8_t i = 0; i < object->vertex_count; i++) {
    project_vertex(&object->vertex[i], rx, ry, rz, &screen_vertex[i]);
  }
  draw_object(object, sc, sx, sy);
}
