/// LSU EE 4702-1 (Fall 2022), GPU Programming
//
 /// CPU-Only Rendering -- Rasterization, Illustration of Coordinate Spaces
//

#include <gp/coord.h>
#include <gp/colors.h>
#include "frame_buffer.h"

/// Background
//

 /// References
//
// :ogl46: OpenGL Specification Version 4.6
//         http://www.opengl.org/registry/doc/glspec46.compatibility.pdf

// :vl12:  Vulkan Specification 1.2
//         https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/

 /// Coordinate Spaces
//
//  :ogl46: Section 12.1 - Coordinate space descriptions.
//
//  :Def: World Space
//        A coordinate space used throughout an application.
//        It is usually convenient for the application.
//              
//  :Def: Object Space
//        Coordinate space used for vertices as given to rendering system.
//        Object Space is often the same as World Space, but it doesn't
//        have to be.
//
//  :Def: Eye Space
//        User's eye at origin, monitor facing -z.
//        Typically, a projection is described on eye-space coordinates.
//
//  :Def: Clip Space
//        Visible space (view volume) inside 
//         a cube with corners (-1,-1,0) and (1,1,1).
//        These bounds are defined by OpenGL and Vulkan.
//
//  :Def: Pixel (Window) Space
//        Coordinates relative to a frame buffer or window.
//        Coordinate 0,0 might can be upper-left or lower-left.


void
render_simple(pFrame_Buffer& demo_frame_buffer, bool first_time)
{
  ///
  /// Prepare Triangle
  ///

  // 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 standard C++ library vector.
  //
  vector<pCoor> coors_os;
  coors_os << p0 << p1 << p2;  // This appends the three coordinates.
  if ( first_time ) pCoors_print(coors_os,"Obj");

  ///
  /// Prepare Transformations
  ///

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

  // Specifications of User's Monitor
  //
  const uint win_width = demo_frame_buffer.width_get();
  const uint win_height = demo_frame_buffer.height_get();
  //
  // win_width and win_height are pixel-space coordinates.
  //
  const float aspect = 1.0 * win_width / win_height;
  const float width_m = 1.6;  // Width of user's monitor.
  const float height_m = width_m / aspect;
  //
  // width_m and height_m are in eye-space coordinates.

  // Specify Transformation from Object to Eye Space: eye_from_object.
  //
  pMatrix_Translate center_eye(-eye_location_os);
  pMatrix_Rotation rotate_eye(eye_direction,pVect(0,0,-1));
  pMatrix eye_from_object = rotate_eye * center_eye;

  // Specify Transformation from Eye space to Clip space: clip_from_eye.
  //
  pMatrix_Frustum clip_from_eye
    ( -width_m/2, width_m/2,  -height_m/2, height_m/2,  1, 5000 );
  //   l          r           b            t            n  f
  //   The letters l,r,b,t,n,f are from a frustum transform definition.

  // Specify Transformation from Clip space to Pixel space: pixel_from_clip.
  //
  pMatrix_Translate recenter(pVect(1,1,0));
  pMatrix_Scale scale( win_width/2, win_height/2, 1 );
  pMatrix pixel_from_clip = scale * recenter;


  ///
  /// Transform Triangle Coordinates to Pixel Space
  ///

  // Transform triangle coordinates from Object space to Eye space.
  //
  vector<pCoor> coors_es = eye_from_object * coors_os;
  if ( first_time ) pCoors_print(coors_es,"Eye");

  vector<pCoor> coors_clip = clip_from_eye * coors_es;
  if ( first_time ) pCoors_print(coors_clip,"Clip U");

  coors_clip = homogenize( coors_clip );
  if ( first_time ) pCoors_print(coors_clip,"Clip N");

  vector<pCoor> coors_window = pixel_from_clip * coors_clip;
  if ( first_time ) pCoors_print(coors_window,"Window");

  pMatrix pixel_from_obj = pixel_from_clip * clip_from_eye * eye_from_object;
  vector<pCoor> coors_w2 = homogenize( pixel_from_obj * coors_os );
  if ( first_time ) pCoors_print(coors_w2,"Win N");

  ///
  /// Rasterize Triangle and Write Frame Buffer
  ///
  //
  //  This routine rasterizes just one triangle.

  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;
           uint(c.x) < win_width || uint(c.y) < win_height )
        demo_frame_buffer[ c.x + int(c.y) * win_width ] = 0xffffffff;
  //
  // The x and y iterators are the barycentric coordinates of a point
  // in the triangle.  Note that 0 <= x+y <= 1.

}


void
render_less_simple(pFrame_Buffer& demo_frame_buffer, bool first_time)
{
  ///
  /// Prepare Triangles
  ///

  // List of coordinates in object space and list of colors.
  //
  vector<pCoor> coors_os;
  vector<uint32_t> colors;

  // Insert one coordinate (1/3 of a triangle) into coordinate list.
  //
  coors_os << pCoor( 0, 0, 0 );

  // Insert two more coordinates and a color completing the first triangle.
  //
  coors_os << pCoor( 9, 6, -9 ) << pCoor( 0, 7, -5 );
  colors << 0xffffff;  // Add color white.

  // Add coordinates and color of another triangle
  //
  coors_os << pCoor(7,4,-2) << pCoor(-3,5,-9) << pCoor(9,2,-2);
  colors << 0xff0000;  // Add color red.

  // Add a square consisting of a red and green triangle.
  //
  uint32_t color_red = 0xff0000;  // For readability, use variables for colors.
  uint32_t color_green = 0xff00;
  colors << color_red << color_green;
  coors_os << pCoor(-4,0,-2) << pCoor(-4,2,-2) << pCoor(-2,0,-2);
  coors_os << pCoor(-4,2,-2) << pCoor(-2,2,-2) << pCoor(-2,0,-2);

  if ( first_time ) pCoors_print(coors_os,"Obj");

  ///
  /// Prepare Transformations
  ///

  // 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 uint win_width = demo_frame_buffer.width_get();
  const uint win_height = demo_frame_buffer.height_get();
  //
  // win_width and win_height are pixel-space coordinates.
  //
  const float aspect = 1.0 * win_width / win_height;
  const float width_m = 1.6;  // Width of user's monitor.
  const float height_m = width_m / aspect;
  //
  // width_m and height_m are in eye-space coordinates.

  // Specify Transformation from Object to Eye Space: eye_from_object.
  //
  pMatrix_Translate center_eye(-eye_location);
  pMatrix_Rotation rotate_eye(eye_direction,pVect(0,0,-1));
  pMatrix eye_from_object = rotate_eye * center_eye;

  // Convert triangle coordinates from object space to eye space.
  //
  vector<pCoor> coors_es = eye_from_object * coors_os;
  //
  // Note: * is an overloaded operation.

  if ( first_time ) pCoors_print(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 = clip_from_eye * coors_es;

  if ( first_time ) pCoors_print(coors_clip,"Clip U");

  coors_clip = homogenize( coors_clip );
  if ( first_time ) pCoors_print(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;

  if ( first_time ) pCoors_print(coors_window,"Window");

  pMatrix pixel_from_obj = pixel_from_clip * clip_from_eye * eye_from_object;
  vector<pCoor> coors_w2 = homogenize( pixel_from_obj * coors_os );
  if ( first_time ) pCoors_print(coors_w2,"Win N");

  ///
  /// Rasterize Triangles and Write Frame Buffer
  ///

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

      pVect v20(w2,w0), v21(w2,w1);
      const float db0 = 1/max(fabs(v20.x),fabs(v20.y));
      const float db1 = 1/max(fabs(v21.x),fabs(v21.y));
      //
      // fabs: floating-point absolute value, a fabulous function.

      // Iterate over triangle using barycentric coordinates.
      //
      for ( float b0=0; b0<=1; b0 += db0 )
        for ( float b1=0; b1<=1-b0; b1 += db1 )
          {
            pCoor c = w2 + b0 * v20 + b1 * v21;  // Window-space coordinate.

            if ( uint(c.x) >= win_width || uint(c.y) >= win_height ) continue;
            //
            // Make sure that this fragment is inside of window.
            // Fragment: pixel candidate.

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

int
main(int argc, char **argv)
{
  pFrame_Buffer demo_frame_buffer(argc,argv);
  Render render( demo_frame_buffer );
  RENDER_INSERT(render, render_simple);
  RENDER_INSERT(render, render_less_simple);
  render.run();
  return 0;
}