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

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

class World_RT : public World_Base {
public:
  World_RT(pFrame_Buffer& fb):World_Base(fb){};
  virtual void render(bool first_time);
};

void
World_RT::render(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<pColor> 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 << color_white;

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

  // Add a square consisting of a red and green triangle.
  //
  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);


  ///
  /// Prepare Transformations for User's Eye and "Monitor"
  ///
  //  Transformations are from object space to clip space.

  // Location of User
  //
  if ( first_time ) eye_location = pCoor(1,.5,10.2);

  // Use keyboard to change eye location.
  //
  switch ( frame_buffer.keyboard_key ) {
    case FB_KEY_LEFT: eye_location.x += 0.1; break;
    case FB_KEY_RIGHT: eye_location.x -= 0.1; break;
    case FB_KEY_PAGE_UP: eye_location.y += 0.1; break;
    case FB_KEY_PAGE_DOWN: eye_location.y -= 0.1; break;
    case FB_KEY_UP: eye_location.z -= 0.1; break;
    case FB_KEY_DOWN: eye_location.z += 0.1; break;
  }

  pVect eye_direction = pVect(0,0,-1);


  ///
  /// Prepare Information on Light
  ///
  pVect eye_to_light = pVect(0,1,0);
  pCoor light_location = eye_location + eye_to_light;
  pColor light_color = color_white * 100;


  ///
  /// Prepare Transformations
  ///

  /// Specifications of User's Monitor
  //
  //  First, the width of the user's monitor in object space coordinates.
  //
  const float width_m = 1.6;  // Wow, that's a big monitor! (m is for meter.)
  const float qn = 1;         // Distance from eye to monitor along z axis.
  //
  //  To determine the height of the user's monitor we need to know the
  //  aspect ratio of the window we are painting.
  //
  const uint win_width = frame_buffer.width_get();
  const uint win_height = frame_buffer.height_get();
  //
  //  win_width and win_height are pixel-space coordinates.
  //
  const float aspect = 1.0 * win_width / win_height;
  //
  //  Using the aspect ratio and the width we can compute the height.
  //
  const float height_m = width_m / aspect;
  //
  //  width_m and height_m are in object-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;

  pCoor light_location_e = eye_from_object * light_location;

  // 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;

  pCoor eye_location_e(0,0,0); // Just here to help us understand.

  ///
  /// Render Pixels
  ///

  /// Outer Loops (yw, xw): Iterate over each window 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(eye_location_e,px_e);

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

        /// Inner Loop (it): Iterate over each triangle.
        //
        //  Note: In practical RT would only need to look at a small
        //        subset of triangles.
        //
        for ( auto it = coors_es.begin(); it != coors_es.end(); )
          {
            // Get Triangle and its Color
            //
            pCoor e0 = *it++,  e1 = *it++,  e2 = *it++;
            pColor color = *ic++;
            //
            // Notice that coordinates are in eye space.
            //
            //
            //        e0        Possible position of triangle vertices.
            //        / \       It's also possible that e1 or e2 is the
            //       /   \      top-most vertex.
            //      e1----e2    

            // Find where ray intercepts plane defined by triangle.
            //
            pVect v01(e0,e1), v02(e0,e2);
            pVect nt = cross(v01,v02);      // Plane Normal
            float t = dot( pVect(eye_location_e,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 (b1,b2) of s.
            //
            // Note: s = e0 + b1 v01 + b2 v02.
            //
            pVect v0s(e0,s);
            //
            // Note: v0s = b1 v01 + b2 v02.

            // Vector orthogonal to v01 (but not v02).
            //
            pVect Ov01 = cross(nt,v01);
            //
            // Note: dot( Ov01, v01 ) = 0, so:
            //
            //       dot( Ov01,v0s ) = b1 dot( Ov01,v01 ) + b2 dot( Ov01,v02 )
            //       dot( Ov01,v0s ) = b2 dot( Ov01,v02 )
            //
            float b2 = dot( v0s, Ov01 ) / dot( v02, Ov01 );

            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.
            //
            float b1 = dot( pVect( v0s - b2 * v02 ), v01 ) / dot( v01, v01 );

            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;

            /// Compute Lighted Color
            //
            // Compute vector from fragment to light.
            //
            pNorm f_to_l(s,light_location_e);

            // Compute how much triangle is facing light.
            //
            float phase = fabs(dot(pNorm(nt),f_to_l));

            // Using these, compute the lighted color.
            //
            pColor lighted_color = phase / f_to_l.mag_sq * light_color * color;

            frame_buffer[ xw + yw * win_width ] = lighted_color.int_rgb();
          }
      }
}

int
main(int argc, char **argv)
{
  pFrame_Buffer demo_frame_buffer(argc,argv);
  World_RT worldrt(demo_frame_buffer);
  WRender render(demo_frame_buffer);
  WRENDER_INSERT(render, worldrt);
  render.run();
  return 0;
}