/// LSU EE 4702-1 (Fall 2023), GPU Programming
//
 /// CPU-Only Rendering -- Ray Tracing
//
//   Simple Illustration of Ray Tracing

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

void
render_ray_trace(pFrame_Buffer& demo_frame_buffer, bool first_time)
{
  ///
  /// Insert Triangles
  ///
  //
  // Note: Each triangle has three vertices and one color.
  // (In other code each triangle vertex can have its own color.)

  vector<pCoor> coors_os;   // Triangles' object-space coordinates.
  vector<uint32_t> colors;  // Triangles' colors.

  // Insert coordinates of first triangle, and the triangle's color.
  //
  coors_os << pCoor( 0, 0, 0 ) << pCoor( 9, 6, -9 ) << pCoor( 0, 7, -5 );
  colors << 0xffffffff;  // White.

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

  // Add a square consisting of a red and green triangle.
  //
  uint32_t color_red = 0xff;
  uint32_t color_green = 0xff00ff00;
  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);


  ///
  /// Transform Triangle Coordinates from Object Space to Eye Space
  ///

  // 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 (Or Window)
  //
  const uint win_width = demo_frame_buffer.width_get();
  const uint win_height = demo_frame_buffer.height_get();
  const float aspect = 1.0 * win_width / win_height;
  //
  // win_width and win_height are in window (pixel) space.

  // These are the size of the monitor in eye space.
  //
  const float width_m = 1.6;
  const float height_m = width_m / aspect;
  const float qn = 1; // Distance from eye to monitor along z axis.
  //
  // width_m and height_m are in eye space.

  // Prepare a transformation matrix that will transform object-space
  // coordinates to eye-space coordinates.
  //
  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: There is no need for clip-space coordinates.

  // Coordinates of Corners of User Monitor Window in Eye Space
  //
  pCoor window_ll_e( -width_m/2, -height_m/2, -qn);  // Lower-Left
  pCoor window_ul_e( -width_m/2,  height_m/2, -qn);  // Upper-Left
  pCoor window_lr_e( +width_m/2, -height_m/2, -qn);  // Lower-Right

  // Pixel size in eye-space coordinates.
  //
  pVect window_dx_e = pVect( window_ll_e, window_lr_e ) / win_width;
  pVect window_dy_e = pVect( window_ll_e, window_ul_e ) / win_height;

  ///
  /// Render Pixels
  ///

  // Outer Loops (yw, xw): Iterate over each pixel.
  //
  for ( uint yw = 0;  yw < win_height; yw++ )
    for ( uint xw = 0;  xw < win_width; xw++ )
      {
        // Eye-Space Coordinate of Pixel.
        //
        pCoor px_e = window_ll_e + window_dx_e * xw + window_dy_e * yw;

        // Ray From Eye to Pixel.
        //
        pVect ray(px_e);

        float tmin = 1e10;  // Distance to closest triangle found so far.
        auto ic = colors.begin();

        // Inner Loop (it): Iterate over each triangle.
        //
        for ( auto it = coors_es.begin(); it != coors_es.end(); )
          {
            // Get Triangle and its Color
            //
            pCoor e0 = *it++,  e1 = *it++,  e2 = *it++;
            uint32_t color = *ic++;

            // Find where ray intercepts plane defined by triangle.
            //
            pVect v01(e0,e1), v02(e0,e2);
            pVect nt = cross(v01,v02);
            float t = dot( pVect(e0), nt ) / dot( ray, nt );
            //
            // t indicates distance from eye to intercept point in
            // units of ray lengths.

            // Skip this triangle if a closer triangle already found ..
            // ..or if it is too close to the eye.
            //
            if ( t >= tmin || t < 1 ) continue;

            // Compute the coordinate of the line/plane intercept.
            //
            pCoor s = t * ray;
            //
            // Is s inside the triangle? We don't know yet.

            // Compute a barycentric coordinate of s.
            //
            pVect v0s(e0,s);
            pVect v01i = cross(nt,v01);  // Vector normal to edge e0,e1
            float b2 = dot(v0s,v01i) / dot( v02,v01i );
            if ( b2 < 0 || b2 > 1 ) continue;
            //
            // Note: If b2 < 0 then s is on the wrong side of edge e0,e1.
            // If b2 > 1 then s is on the right side, but too far away.

            // Compute another barycentric coordinate of s.
            //
            pVect v02i = cross(nt,v02);
            float b1 = dot( v0s, v02i) / dot(v01,v02i);
            if ( b1 < 0 || b1 + b2 > 1 ) continue;

            // At this point we know that s is in the triangle.

            // Update the minimum distance and write the frame buffer.
            //
            tmin = t;
#           ifdef __cpp_multidimensional_subscript
            demo_frame_buffer[ xw, yw ] = color;  // Ready for C++23!
#           else
            demo_frame_buffer[ xw + yw * win_width ] = color;
#           endif
          }
      }
}

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