
#ifdef LINUX
#include <stdio.h>
// Vectrex uses 8 and 16-bit variables.  We'll do the same on linux.
typedef char int8_t;
typedef short int16_t;
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef signed char sint8_t;
typedef signed short sint16_t;
typedef signed int sint32_t;
#else
typedef int int8_t;
typedef long int16_t;
#endif

#include "vectrex.h"
#define set_scale(s) do { VIA_t1_cnt_lo = s; } while (0)

typedef int8_t screen_coord;
#include "models.h"

// Define fixed-point arithmetic precision
#define FP_SHIFT 8L
#define FP_SCALE (1L << FP_SHIFT)
#define CAMERA_DEPTH (-64) 
#define FOCAL_LENGTH 5  // 1<<5

// At the moment, we are only looking at *objects* whose dimensions fit comfortably
// into byte-sized variables.  But these objects move around in a larger world space
// and so projection will probably have to take place using 16-bit coordinates unless
// we get really sneaky.

// Objects in the game will most likely need 16 bit x,y,z coordinates for the origin
// point of the object (usually its center) in conjunction with a rotation matrix and
// of course a pointer to the object data in rom itself.

// The x,y,x and rotation data will probably all be stored in a single matrix, per object.
// - I haven't got that far yet, it's not even worth looking at until I can perform the
// perspective calculation and display much faster.

void project_point(const Point3D *p, Point2D *result) {

    // Translate point relative to camera
    int16_t dz = p->z - CAMERA_DEPTH;

    // Perform perspective projection
    if (dz != 0) {
        // Use 16-bit intermediate calculations
        int16_t x_proj = (p->x << FOCAL_LENGTH) / dz; // DIVIDES ARE EXPENSIVE. A shift would be better.
        int16_t y_proj = (p->y << FOCAL_LENGTH) / dz;

        // Scale and convert back to 8-bit
        result->x = (screen_coord)((x_proj * FP_SCALE) >> FP_SHIFT);
        result->y = (screen_coord)((y_proj * FP_SCALE) >> FP_SHIFT);
    } else {
        // Handle division by zero - should never be needed
        result->x = 127;
        result->y = 127;
    }

}

// Example object.
#define CUBE_POINTS (sizeof(CUBE_p)/sizeof(CUBE_p[0]))
const Point3D CUBE_p[] = {
  { 0,0,0 },
  // front face, clockwise
  {-10, -10, -10},
  {-10, 10, -10},
  {-10, 10, 10},
  {-10, -10, 10},
  // back face, clockwise
  {10, -10, -10},
  {10, 10, -10},
  {10, 10, 10},
  {10, -10, 10},
};

const Point_index CUBE[] = {    // test CUBE
  BD, 1,2,3,4,1,
  BD, 5,6,7,8,5,
  BD, 1,5,
  BD, 2,6,
  BD, 3,7,
  BD, 4,8,
  END
};

void draw_model(int points, const Point3D *p, const Point_index *v) {
  screen_coord X, Y, OX=0, OY=0;
  Vertex_index V;
  Point_index point;
  Point2D Projected[points]; // not worth caching per-object because all objects move every frame.

  // First we cache the projected points in model.

#ifdef DEBUG
  fprintf(stderr, "Cube has %d points. [1:%d]\n", CUBE_POINTS, CUBE_POINTS-1);
#endif
  for (point = 1; point < points; point++) {
    project_point(&p[point], &Projected[point]);
#ifdef DEBUG
    fprintf(stderr, "Projected point z=%d y=%d x=%d -> screen y=%d x=%d\n", p[point].z, p[point].y, p[point].x,  Projected[point].y, Projected[point].x);
#endif
  }
  
  // Then we draw the model using the list of vertices:

  V = 0;
  for (;;) {
    if (v[V] > 0) { // Data point, so handle these without delay
      Point_index point = v[V];
      Y = Projected[point].y; X = Projected[point].x;
      Draw_Line_d(Y-OY, X-OX);
      OX = X; OY = Y;
      V += 1;
    } else {
      if (v[V] == BD) {
        V += 1;
        Point_index point = v[V];
        Reset0Ref();
        Moveto_d(OY=Projected[point].y, OX=Projected[point].x);
        V += 1;
      } else if (v[V] == END) {
        break;
      } else {
        // LD, so skip (unless I'm misunderstanding the LD code)
        V += 1;
      }
    }
  }
}

int main(void) {
  while (1) {
    Wait_Recal();
    //Print_Str_d(100, -90, "STARWARS MODELS\x80");
    Intensity_7F();
    set_scale(0xFF); // large scales are expensive.  This is only until debugged.
    // A simple cube drawing test (12 vectors) draws in just under 30,000 cycles...
    draw_model(CUBE_POINTS, CUBE_p, CUBE);
    // ...  but a more complex model (the Star Wars X Wing) does not remotely draw fast enough, even
    // when compiled with gcc's -O3 option for speed (and that is before we apply translations
    // and rotations to the model - that's just with a simple perspective display...)
    //draw_model(XW_POINTS, XW_p, XW);

    // Now it is true that this test code recalculates everything on every frame, rather than
    // cacheing the values when the objects are not moving, but that reflects reality - in a
    // game, everything is moving all the time and there's no advantage to be gained by cacheing.

#ifdef LINUX
    break;
#endif
  }
  return 0;
}
