/// LSU EE 4702-1 (Fall 2021), GPU Programming
//
 /// CPU-Only Rendering
//
//   Perform all rasterization steps using CPU code.


#include <stdio.h>
#include <string.h>
#include <vector>
#include <Magick++.h>
using namespace Magick;

#include <coord.h>
#include <colors.h>

// Utility function to print coordinates.
//
void
print_coors(vector<pCoor>& coors, const char* pf, int max_v = 3)
{
  printf("%6s",pf);
  for ( pCoor c: coors )
    if ( max_v-- ) printf("  (%6.2f,%6.2f,%6.2f)",c.x,c.y,c.z);
    else           break;
  printf("\n");
};

vector<pCoor>
operator * (pMatrix& m, vector<pCoor>& cv)
{
  vector<pCoor> rv(cv.size()); // Pre-allocate to avoid waste.
  for ( size_t i=0; i<cv.size(); i++ ) rv[i] = m * cv[i];
  return rv;
}


void
render_simple()
{
  // Object-Space Coordinates of a Triangle
  //
  pCoor p0 = { 0, 0,  0 };
  pCoor p1 = { 9, 6, -9 };
  pCoor p2 = { 0, 5, -5 };
  //
  // This coordinate space is convenient for us.

  // Put coordinates in a vector.
  //
  vector<pCoor> coors_os = { p0, p1, p2 };

  print_coors(coors_os,"Obj");

  // Location of User and Direction of Gaze
  //
  pCoor eye_location = pCoor(1,.5,10.2);
  pVect eye_direction = pVect(0,0,-1);

  // Specifications of User's Monitor
  //
  const int win_width = 900;
  const int win_height = 660;
  const float aspect = 1.0 * win_width / win_height;
  const float width_m = 1.6;
  const float height_m = width_m / aspect;


  // Transform from Object to Eye Space.
  //
  pMatrix_Translate center_eye(-eye_location);
  pMatrix_Rotation rotate_eye(eye_direction,pVect(0,0,-1));

  pMatrix eye_from_object = rotate_eye * center_eye; // Modelview

  // Convert triangle coordinates from object space to eye space.
  //
  vector<pCoor> coors_es;
  for ( pCoor& c: coors_os ) coors_es.push_back( eye_from_object * c );

  print_coors(coors_es,"Eye");

  // Transform from Eye to Clip
  //
  pMatrix_Frustum clip_from_eye
    ( -width_m/2, width_m/2,  -height_m/2, height_m/2,  1, 5000 );

  vector<pCoor> coors_clip;
  for ( pCoor& c: coors_es ) coors_clip.push_back( clip_from_eye * c );

  print_coors(coors_clip,"Clip U");

  for ( pCoor& c: coors_clip ) c = c / c.w;
  print_coors(coors_clip,"Clip N");

  // Transform from Clip to Pixel
  //
  pMatrix_Translate recenter(pVect(1,1,0));
  pMatrix_Scale scale( win_width/2, win_height/2, 1 );

  pMatrix pixel_from_clip = scale * recenter;

  vector<pCoor> coors_window = pixel_from_clip* coors_clip;

  print_coors(coors_window,"Window");

  pMatrix pixel_from_obj = pixel_from_clip * clip_from_eye * eye_from_object;
  vector<pCoor> coors_w2;
  for ( pCoor& c: coors_os ) coors_w2.push_back( pixel_from_obj * c );
  for ( pCoor& c: coors_w2 ) c = c / c.w;
  print_coors(coors_w2,"Win N");

  vector<uint32_t> f_buffer( win_width * win_height, 0xff222222 );

  pCoor w0 = coors_w2[0];
  pCoor w1 = coors_w2[1];
  pCoor w2 = coors_w2[2];

  pVect bx(w2,w0), by(w2,w1);
  const float dx = 1/bx.mag(), dy = 1/by.mag();

  for ( float x=0; x<=1; x += dx )
    for ( float y=0; y<=1-x; y += dy )
      if ( pCoor c = w2 + x * bx + y * by;
           c.x >= 0 || c.y >= 0 || c.x < win_width || c.y < win_height )
        f_buffer[ c.x + int(c.y) * win_width ] = 0xffffffff;

  Image image( win_width, win_height, "RGBA", CharPixel, f_buffer.data() );
  image.write( "demo-0-coor-simple.png" );
}


void
render_less_simple()
{
  // Object-Space Coordinates of a Triangle
  //
  pCoor p0 = { 0, 0,  0 };
  pCoor p1 = { 9, 6, -9 };
  pCoor p2 = { 0, 5, -5 };
  //
  // This coordinate space is convenient for us.

  // Put coordinates in a vector.
  //
  vector<pCoor> coors_os = { p0, p1, p2 };
  vector<uint32_t> colors = { 0xffffffff };

  // Add another Triangle
  //
  coors_os.insert
    ( coors_os.end(), { pCoor(7,4,2), pCoor(-6,5,3), pCoor(9,3,2) } );
  colors.push_back( 0xffff0000 );

  print_coors(coors_os,"Obj");


  // Location of User and Direction of Gaze
  //
  pCoor eye_location = pCoor(1,.5,10.2);
  pVect eye_direction = pVect(0,0,-1);

  // Specifications of User's Monitor
  //
  const int win_width = 900;
  const int win_height = 660;
  const float aspect = 1.0 * win_width / win_height;
  const float width_m = 1.6;
  const float height_m = width_m / aspect;


  // Transform from Object to Eye Space.
  //
  pMatrix_Translate center_eye(-eye_location);
  pMatrix_Rotation rotate_eye(eye_direction,pVect(0,0,-1));

  pMatrix eye_from_object = rotate_eye * center_eye; // Modelview

  // Convert triangle coordinates from object space to eye space.
  //
  vector<pCoor> coors_es = eye_from_object * coors_os;

  print_coors(coors_es,"Eye");

  // Transform from Eye to Clip
  //
  pMatrix_Frustum clip_from_eye
    ( -width_m/2, width_m/2,  -height_m/2, height_m/2,  1, 5000 );

  vector<pCoor> coors_clip;
  for ( pCoor& c: coors_es ) coors_clip.push_back( clip_from_eye * c );

  print_coors(coors_clip,"Clip U");

  for ( pCoor& c: coors_clip ) c = c / c.w;
  print_coors(coors_clip,"Clip N");

  // Transform from Clip to Pixel
  //
  pMatrix_Translate recenter(pVect(1,1,0));
  pMatrix_Scale scale( win_width/2, win_height/2, 1 );

  pMatrix pixel_from_clip = scale * recenter;

  vector<pCoor> coors_window = pixel_from_clip* coors_clip;

  print_coors(coors_window,"Window");

  pMatrix pixel_from_obj = pixel_from_clip * clip_from_eye * eye_from_object;
  vector<pCoor> coors_w2;
  for ( pCoor& c: coors_os ) coors_w2.push_back( pixel_from_obj * c );
  for ( pCoor& c: coors_w2 ) c = c / c.w;
  print_coors(coors_w2,"Win N");

  vector<uint32_t> f_buffer( win_width * win_height, 0xff222222 );

  auto ic = colors.begin();
  for ( auto it = coors_window.begin(); it != coors_window.end(); )
    {
      pCoor w0 = *it++,  w1 = *it++,  w2 = *it++;
      uint32_t color = *ic++;

      pVect bx(w2,w0), by(w2,w1);
      const float dx = 1/bx.mag(), dy = 1/by.mag();

      for ( float x=0; x<=1; x += dx )
        for ( float y=0; y<=1-x; y += dy )
          {
            pCoor c = w2 + x * bx + y * by;
            if ( c.x < 0 || c.y < 0 || c.x >= win_width || c.y >= win_height )
              continue;

            // Fragment shader code. (None shown here.)
            if ( int(c.x) & 1 ) continue;

            f_buffer[ c.x + int(c.y) * win_width ] = color;
          }
    }

  Image image( win_width, win_height, "RGBA", CharPixel, f_buffer.data() );
  image.write( "demo-3-cpu-only-less-simple.png" );
}



int
main(int argc, char **argv)
{
  InitializeMagick(nullptr);
  render_simple();
  render_less_simple();
}