/// LSU EE 4702-1 (Fall 2022), GPU Programming
//
 /// CPU-Only Rendering
//
//   Code emphasizing triangle rasterization.

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

pMatrix transform_construct(pFrame_Buffer& demo_frame_buffer);

void
render(pFrame_Buffer& demo_frame_buffer)
{
  // List for triangle coordinates, three coordinates per triangle.
  //
  vector<pCoor> coors_os;
  //
  // Coordinates are in object space. (See demo-03.)

  // List for triangle colors, one color per triangle.
  //
  vector<int32_t> colors;
  //
  // Bits 7:0 (least significant) is blue component.
  // Bits 15:8 is green component.
  // Bits 24:16 is red component.
  // Bits 31:25 are ignored.

   /// Insert First Triangle
  //
  // Insert one coordinate (1/3 of a triangle) into coordinate list.
  //
  coors_os << pCoor( 0, 0, 0 );
  //
  // Insert two more coordinates, completing a triangle.
  //
  coors_os << pCoor( 9, 6, -9 ) << pCoor( 0, 7, -5 );
  //
  // Set triangle to white.
  //
  colors << 0xffffff;

  /// Insert Second Triangle
  //
  coors_os << pCoor(7,4,-2) << pCoor(-3,5,-9) << pCoor(9,2,-2);
  colors << 0xff0000;  // Red.

  // Transform Coordinates from Object Space to Window Space
  //
  pMatrix win_from_object = transform_construct(demo_frame_buffer);
  //
  // win_from_object is a 4 by 4 matrix.
  // It is constructed based on the location of the user's eye,
  // and the location and size of the user's monitor.

  // The line below transforms each coordinate in coors_os from
  // object space to window (pixel) space and puts the transformed
  // coordinates in coors_window.
  //
  vector<pCoor> coors_window = homogenize( win_from_object * coors_os );
  //
  // See demo-03 for details on coordinate spaces.

  if ( demo_frame_buffer.frame_count_get() == 1 )
    {
      pCoors_print(coors_os,"Obj");
      pCoors_print(coors_window,"Win");
    }

  const int win_width = demo_frame_buffer.width_get();
  auto ic = colors.begin();
  for ( auto it = coors_window.begin(); it != coors_window.end(); )
    {
      // Get next triangle in list (three vertices, one 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));

      /// Rasterize Triangle
      //
      //  b0 and b1 are 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;
            demo_frame_buffer[ c.x + int(c.y) * win_width ] = color;
          }
    }
}

pMatrix
transform_construct(pFrame_Buffer& demo_frame_buffer)
{
  /// Construct a transformation matrix.
  //
  //  For more detail on what this routine does see demo-03-coord-space.cc.

  // 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 (Actually window shown in display.)
  //
  const int win_width = demo_frame_buffer.width_get();
  const int win_height = demo_frame_buffer.height_get();
  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;

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

  pMatrix clip_from_object = clip_from_eye * eye_from_object;

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

  pMatrix win_from_object = win_from_clip * clip_from_object;

  return win_from_object;
}

int
main(int argc, char **argv)
{
  pFrame_Buffer demo_frame_buffer(argc,argv);
  demo_frame_buffer.show(render);
  return 0;
}