/// LSU EE 4702-1 (Fall 2023), GPU Programming
//
 /// CPU-Only Rendering -- Z (Depth) Test, Lighting
//
//

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

#if 0
 /// Changes
//
 /// Use pColor.
 //
 //  class pColor { float r, g, b, a; }
 //  Range of r, g, b, and a are from 0 to 1.
 //
 //  Lots of overloads to make working with colors easier. 
 pColor red(1,0,0);
 pColor pink(1,0.5,0.5);
 pColor Red( 0xff0000 );
 pColor Pink( 0xff8080 );
 pColor purg = 0.4 * color_purple + 0.6 * color_gold;

#endif

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

void
World_Z::render(bool first_time)
{
  ///
  /// Construct Scene to Render
  ///

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

  // Coordinates and a color completing the first triangle.
  //
  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_red;

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

  // Add a ring.
  //
  int n_segs = 20;
  pCoor center(1,-1,-3);
  pVect sz( 0, 0.5, 0.05 );
  pNorm ax( 0, -sz.z, sz.y ), ay = cross(sz,ax);
  vector<pCoor> strip_coor(n_segs+1);
  float r = 4, d_th = 2 * M_PI / n_segs;
  for ( int i=0; i<=n_segs; i++ )
    strip_coor[i] = center + r*ax * cosf(i*d_th) + r*ay * sinf(i*d_th);
  for ( int i=0; i<n_segs; i++ )
    {
      coors_os << strip_coor[i] << strip_coor[i+1] << strip_coor[i] + sz;
      coors_os << strip_coor[i] + sz << strip_coor[i+1] + sz << strip_coor[i+1];
      if ( i == 0 ) colors << color_red << color_green;
      else          colors << pColor(0xff00ff) << pColor(0xffff);
    }

  ///
  /// 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);
  pVect eye_to_light = pVect(0,1,0);
  pCoor light_location = eye_location + eye_to_light;
  pColor light_color = color_white * 100;

  // Specifications of User's Monitor in Object Space
  //
  const uint win_width = frame_buffer.width_get();
  const uint win_height = frame_buffer.height_get();
  //
  // win_width and win_height are window-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;

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

  ///
  /// Prepare Transformation to Frame Buffer (or window) Size
  ///
  //  Transformation is from clip space to window (pixel) space.

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

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

  // Get pointer to the depth (z) buffer.
  //
  float* const zbuffer = frame_buffer.buffer_depth_get();
  //
  // The depth buffer uses the same index as the frame buffer.

  auto ic = colors.begin();
  for ( auto it = coors_os.begin(); it != coors_os.end(); )
    {
      // Get next triangle's object space coordinates and convert to window sp.
      //
      pCoor o0 = *it++,  o1 = *it++,  o2 = *it++;
      pCoor_Homogenized
        w0( ws_from_os * o0 ), w1( ws_from_os * o1 ), w2( ws_from_os * o2 );

      pColor color = *ic++;

      // Compute triangle's normal. Used for lighting.
      pNorm n = cross(o0,o1,o2);

      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 )
          {
            // Each iteration operates on one fragment of triangle.

            // Compute window-space coordinate of fragment (of triangle).
            //
            pCoor wf = w2 + b0 * v20 + b1 * v21;

            if ( uint(wf.x) >= win_width || uint(wf.y) >= win_height ) continue;
            //
            // Scissor any remaining fragments outside of the view volume.

            // Compute the index into the frame and depth buffers.
            //
            const size_t idx = wf.x + int(wf.y) * win_width;

            /// Depth (Z) Test Code.
            //
            // - Get distance from eye.
            //
            float zinv = wf.z;
            //
            // Due to frustum transform values in the view volume are
            // from 0 (closest) to 1 from eye.
            //
            // - If we are outside of the view volume skip this fragment.
            //
            if ( zinv < 0 || zinv > 1 ) continue;
            //
            // Skip this fragment if frame buffer at this position has
            // something closer to the eye than us
            //
            if ( zbuffer[ idx ] < zinv ) continue;
            //
            //
            // Write the depth buffer with our distance.
            //
            zbuffer[ idx ] = zinv;

            /// Compute Lighted Color
            //

            // Find approximate object-space coordinate of fragment.
            //
            pCoor of = o0*b0 + o1*b1 + o2*(1-b0-b1); // Not perspective-correct.

            // Compute vector from fragment to light.
            //
            pNorm f_to_l(of,light_location);

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

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

            // Write the frame (color) buffer with the lighted color
            //
            frame_buffer[ idx ] = lighted_color.int_rgb();
          }
    }
}


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