/// LSU EE 4702-1 (Fall 2025), GPU Programming
//
 /// Homework 3 -- SOLUTION
 //
 //  Assignment: https://www.ece.lsu.edu/gpup/2025/hw03.pdf
 //
 /// Based on Homework 2 and cpu-only/demo-06-rend-pipe.cc


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

 ///
 /// Our_3D: Low-Level 3D Graphics Library.
 ///
enum Topology { Topology_Individual, Topology_Strip, Topology_Fan };

class Our_3D {
public:
  Our_3D(pFrame_Buffer& fbp)
    :frame_buffer(fbp)
  {
    vtx_colors = nullptr;
    vtx_coors = nullptr;
    vtx_normals = nullptr;
    topology = Topology_Individual;
    opt_tri_normals_always = false;
  };

  Our_3D& transform_eye_from_object_set(const pMatrix& m)
  {
    eye_from_object = m;
    return *this;
  }
  Our_3D& transform_clip_from_eye_set(const pMatrix& m)
  {
    clip_from_eye = m;
    return *this;
  }
  Our_3D& light_location_set(const pCoor c)
  {
    light_location = c;
    return *this;
  }
  Our_3D& light_color_set(const pColor c)
  {
    light_color = c;
    return *this;
  }

  Our_3D& vtx_normals_set()
  {
    vtx_normals = nullptr;
    return *this;
  }

  Our_3D& vtx_normals_set(vector<pVect4>& c)
  {
    vtx_normals = c.data();
    return *this;
  }

  Our_3D& vtx_coors_set(vector<pCoor>& c)
  {
    vtx_coors = &c;
    return *this;
  }

  Our_3D& vtx_colors_set(vector<pColor>& c)
  {
    vtx_colors = &c;
    return *this;
  }

  // Specify whether vertices are grouped for individual triangles, a
  // triangle strip, or a triangle fan in the next call to
  // draw_rasterization.
  //
  Our_3D& topology_set
  ( Topology top = Topology_Individual, int n_vertices = 0 )
  {
    topology = top;
    topology_n_vertices = top == Topology_Individual ? 3 : n_vertices;
    return *this;
  }

  Our_3D& rendering_stats_reset()
  {
    stats_n_triangles = stats_n_vertices = stats_n_fragments = 0;
    return *this;
  }

  Our_3D& draw_rasterization();

  pFrame_Buffer& frame_buffer;
  pMatrix eye_from_object, clip_from_eye;
  pCoor light_location;
  pColor light_color;
  vector<pCoor> *vtx_coors;
  pVect4 *vtx_normals;
  vector<pColor> *vtx_colors;
  Topology topology;
  int topology_n_vertices;
  int64_t stats_n_triangles, stats_n_vertices, stats_n_fragments;

  bool opt_tri_normals_always;
  bool opt_tryout1, opt_tryout2;

};

enum HW3_Variation { HW3_Triangles, HW3_Fans, HW3_ENUM_SIZE };

 ///
 ///  World: An application.
 ///
class World : public World_Base {
public:
  World(pFrame_Buffer& fb):World_Base(fb),gc(fb){}
  Our_3D gc;

  // Called each time a frame needs to be rendered.
  virtual void render(bool first_time)
  {
    if ( first_time ) init_scene();
    render_scene();
  }

  // Called Once
  void init_scene();

  // Called for each frame.
  void render_scene();

  pCoor eye_location, light_location;
  pColor light_color;
  pCoor *adj_location;
  pVect eye_direction;
  bool opt_tryout1,opt_tryout2;

  int opt_hw3_variation;
  bool opt_hw3_p1_strips;
  float dod_angle1, dod_angle2;

};



const char* const hw3_variation_str[] =
  { "P2: TRIANGLES", "P3: FANS" };

void
World::init_scene()
{
  eye_location = pCoor(-2.6,.5,7.9);
  light_location = pCoor(-4.1,.9,5.1);
  eye_direction = pVect(.1,0,-1);
  adj_location = &eye_location;
  opt_tryout1 = opt_tryout2 = false;
  gc.opt_tryout1 = opt_tryout1;
  gc.opt_tryout2 = opt_tryout2;
  light_color = color_white * 100;

  dod_angle1 = dod_angle2 = 0;
  opt_hw3_variation = 0;
  opt_hw3_p1_strips = false;
}

pVect vec_rand() {return pVect(drand48(),drand48(),drand48());}

void
World::render_scene()
{
  pFrame_Buffer& frame_buffer = gc.frame_buffer;

  switch ( frame_buffer.keyboard_key ) {
  case FB_KEY_LEFT: adj_location->x -= 0.1; break;
  case FB_KEY_RIGHT: adj_location->x += 0.1; break;
  case FB_KEY_PAGE_UP: adj_location->y += 0.1; break;
  case FB_KEY_PAGE_DOWN: adj_location->y -= 0.1; break;
  case FB_KEY_UP: adj_location->z -= 0.1; break;
  case FB_KEY_DOWN: adj_location->z += 0.1; break;
  case 'a': case 'A': opt_hw3_variation++;
    if ( opt_hw3_variation == HW3_ENUM_SIZE ) opt_hw3_variation = 0;
    break;
  case '1': opt_hw3_p1_strips = !opt_hw3_p1_strips; break;
  case 'e': case 'E': adj_location = &eye_location; break;
  case 'l': case 'L': adj_location = &light_location; break;
  case 'v': case 'V': gc.opt_tri_normals_always = !gc.opt_tri_normals_always;
    break;
  case 'y': gc.opt_tryout1 = opt_tryout1 = !opt_tryout1; break;
  case 'Y': gc.opt_tryout2 = opt_tryout2 = !opt_tryout2; break;
  case 'w': dod_angle1 += numbers::pi/30; break;
  case 'W': dod_angle1 -= numbers::pi/30; break;
  case 'q': dod_angle2 += numbers::pi/30; break;
  case 'Q': dod_angle2 -= numbers::pi/30; break;
  case '+': case '=':
    if ( !frame_buffer.keyboard_control ) light_color *= 1.1;
    break;
  case '-': case '_':
    if ( !frame_buffer.keyboard_control ) light_color *= 1/1.1;
    break;

  }

  gc.frame_buffer.fbprintf
    ( "Light Location [ %.2f, %.2f, %.2f ],  Light Intensity %.2f ('+-')  "
      "Eye Location [ %.2f, %.2f, %.2f ]\n",
      light_location.x, light_location.y, light_location.z,
      light_color.r,
      eye_location.x, eye_location.y, eye_location.z );
  frame_buffer.fbprintf
    ("HW3 P1: %s ('1')  HW3 Dodec %s ('a')  Spin ('wWqQ')  "
     "Tryout1 %s ('y')  Tryout2 %s ('Y')\n",
     opt_hw3_p1_strips ? "STR" : "TRI",
     hw3_variation_str[opt_hw3_variation],
     opt_tryout1 ? "ON " : "OFF", opt_tryout2 ? "ON " : "OFF");

  gc.light_location_set( light_location ).light_color_set( light_color );

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

  // Compute transformation from Object Space 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;
  //
  // .. and give the transformation to Our3D:
  //
  gc.transform_eye_from_object_set(eye_from_object);

  // Compute transformation from Eye Space to Clip Space ..
  //
  pMatrix_Frustum clip_from_eye
    ( -width_m/2, width_m/2,  -height_m/2, height_m/2,  qn, 5000 );
  //   l          r           b            t            n   f
  //   The letters l,r,b,t,n,f are from a frustum transform definition.
  //
  // .. and give the transformation to Our3D:
  //
  gc.transform_clip_from_eye_set(clip_from_eye);


  ///
  /// Prepare Triangles
  ///

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

  /// Triangle to Mark Light Source
  //
  // Insert a triangle at light location, facing user. Light is top point.
  //
  pVect light_to_eye( light_location, eye_location );
  float l_sz = 0.1;
  pVect lax = cross( pVect( 0, 1, 0 ), light_to_eye ).normal() * l_sz;
  pVect lay = cross( light_to_eye, lax ).normal() * l_sz;
  coors_os << light_location
           << light_location - lax - lay << light_location + lax - lay;
  colors << color_yellow << color_yellow << color_yellow;

  /// Yellow and Multicolored Triangles
  //
  coors_os << pCoor(-7,2,-2) << pCoor(-7,4,-2) << pCoor(-5,2,-2);
  colors   << color_red      << color_green    << color_blue;

  coors_os << pCoor(-7,4,-2) << pCoor(-5,4,-2) << pCoor(-5,2,-2);
  colors << color_lsu_spirit_gold << color_lsu_spirit_gold
         << color_lsu_spirit_gold;

  ///
  /// Send Triangles to Our3D for Rendering
  ///
  //
  gc.vtx_normals_set();            // Tell Our3D to compute triangle normals.
  gc.topology_set(Topology_Individual);  // Use individual triangle grouping.
  gc.vtx_coors_set(coors_os);      // Coordinates to render.
  gc.vtx_colors_set(colors);       // Colors to render.
  gc.draw_rasterization();         // Tell Our3D to render now.


  ///
  /// Render the Problem 1 Shape
  ///

  // First remove the coordinates and colors inserted above.
  //
  coors_os.clear(); colors.clear();

  if ( opt_hw3_p1_strips )
    {
      // Render shape using a triangle strip. (When Problem 1 solved.)

      gc.topology_set( Topology_Strip );

      /// Homework 3
      //
      //  Problem 1 part a solution goes here.

      /// SOLUTION -- Problem 1a
      //
      // Emit the first two vertices of the strip.
      //
      coors_os << pCoor(-5,1,-2)          << pCoor(-7,1,-2);
      colors   << color_lsu_spirit_purple << color_lsu_spirit_purple;

      // Complete first triangle.
      //
      coors_os << pCoor(-5,-1,-2);
      colors   << color_lsu_spirit_purple;

      // Complete second triangle.
      //
      coors_os << pCoor(-7,-1,-2);
      colors << color_lsu_spirit_gold;

      // Complete third triangle.
      //
      coors_os << pCoor(-6,-2,-2);
      colors << color_green;

      // Complete fourth triangle, ending the strip.
      //
      coors_os << pCoor(-7,-2,-2);
      colors << color_salmon;

#if 0
      // Original code.
      coors_os << pCoor(-6,-2,-2) << pCoor(-7,-2,-2) << pCoor(-7,-1,-2);
      colors   << color_salmon    << color_salmon    << color_salmon;

      coors_os << pCoor(-7,1,-2) << pCoor(-5,1,-2) << pCoor(-5,-1,-2);
      colors << color_lsu_spirit_purple << color_lsu_spirit_purple
             << color_lsu_spirit_purple;

      coors_os << pCoor(-7,-1,-2) << pCoor(-7,1,-2) << pCoor(-5,-1,-2);
      colors << color_lsu_spirit_gold << color_lsu_spirit_gold
             << color_lsu_spirit_gold;

      coors_os << pCoor(-7,-1,-2) << pCoor(-6,-2,-2) << pCoor(-5,-1,-2);
      colors   << color_green     << color_green     << color_green;
#endif

    }
  else
    {
      // Render shape using individual triangles.

      gc.topology_set( Topology_Individual );

      /// Homework 3
      //
      //  This block does not have to be modified.

      coors_os << pCoor(-6,-2,-2) << pCoor(-7,-2,-2) << pCoor(-7,-1,-2);
      colors   << color_salmon    << color_salmon    << color_salmon;

      coors_os << pCoor(-7,1,-2) << pCoor(-5,1,-2) << pCoor(-5,-1,-2);
      colors << color_lsu_spirit_purple << color_lsu_spirit_purple
             << color_lsu_spirit_purple;

      coors_os << pCoor(-7,-1,-2) << pCoor(-7,1,-2) << pCoor(-5,-1,-2);
      colors << color_lsu_spirit_gold << color_lsu_spirit_gold
             << color_lsu_spirit_gold;

      coors_os << pCoor(-7,-1,-2) << pCoor(-6,-2,-2) << pCoor(-5,-1,-2);
      colors   << color_green     << color_green     << color_green;
    }


  gc.vtx_normals_set();            // Tell Our3D to compute triangle normals.
  gc.vtx_coors_set(coors_os);      // Coordinates to render.
  gc.vtx_colors_set(colors);       // Colors to render.
  gc.rendering_stats_reset();
  gc.draw_rasterization();         // Tell Our3D to render now.
  pStringF
    p1stats("P1 Shape: %ld vertices, %ld triangles, %ld fragments.",
     gc.stats_n_vertices, gc.stats_n_triangles, gc.stats_n_fragments);


  // First remove the coordinates and colors inserted above.
  //
  coors_os.clear(); colors.clear();

  ///
  /// Render a Cylinder
  ///

  // Container for vertex normals.
  vector<pVect4> normals_os;

  // Specifications of Cylinder
  //
  pCoor cyl_center(1,-1,-3);
  pVect cyl_axis( 0, 0.5, 0.05 );
  float cyl_radius = 4;
  //
  // Compute axes of circle. (Base of cylinder, top of cylinder.)
  //
  pNorm ax(0,-cyl_axis.z,cyl_axis.y);  // Find an axis orthogonal to cyl_axis.
  pNorm ay = cross( cyl_axis, ax );
  //
  int n_segs = 20;  // Number of faces in prism (approximating a cylinder.)
  //
  float delta_theta = 2 * M_PI / n_segs;

  for ( int i=0; i<=n_segs; i++ )
    {
      float theta = i * delta_theta;
      pVect4 n = ax * cosf(theta) + ay * sinf(theta);
      pCoor c = cyl_center + cyl_radius * n;

      coors_os << c + cyl_axis << c;
      normals_os << n << n;
      colors << color_cyan << color_cyan;
    }

  ///
  /// Send Triangles to Our3D for Rendering
  ///
  //
  gc.vtx_normals_set(normals_os);  // This time we're providing normals.
  gc.topology_set(Topology_Strip); // Use a triangle strip grouping.
  gc.vtx_coors_set(coors_os);
  gc.vtx_colors_set(colors);
  gc.draw_rasterization();

  gc.rendering_stats_reset();

  ///
  /// Homework 3
  ///
  {
    // Dodecahedron Center
    //
    pCoor center = cyl_center + pVect(0,1.5,0);
    pMatrix_Rotation dod_spin1( pNorm(0.1,1,0.1), dod_angle1 );
    pMatrix_Rotation dod_spin2( pNorm(1,-0.1,0), dod_angle2 );
    pMatrix dod_spin = dod_spin1 * dod_spin2;
    pVect ax = dod_spin.col_get(0);
    pVect ay = dod_spin.col_get(1);
    pVect az = dod_spin.col_get(2);

    // Edge Length
    //
    const float len_edge = 2;

    // Distance from pentagon center to pentagon vertex.
    //
    const float rc_pentagon = len_edge / ( 2 * sinf(numbers::pi/5) );

    // Inscribed Sphere Radius
    //
    float ri = len_edge * sqrtf( sqrt(5) * pow(numbers::phi,5) / 20 );

    // Use same coordinate axes as the cylinder.
    pCoor center_pentagon_0 = center - ri * az;

    // Dihedral Angle
    //
    float theta_dihedral [[maybe_unused]] = 2 * atanf( numbers::phi );

    // Dodecahedron Vertex Coordinates -- Including Duplicates
    //
    // The number of items in coors will be a multiple of 5.
    //
    vector< pCoor > coors;
    //
    // First pentagon:  coors[0] through coors[4].
    // Second pentagon: coors[5] through coors[9].
    // And so on.

    ///
    /// Compute Coordinates of First Pentagon
    ///
    const float d_theta = 2 * numbers::pi / 5;
    for ( int i=0; i<5; i++ )
      {
        const float theta = d_theta * i;
        pVect dir = cosf(theta) * ax + sinf(theta) * ay;
        pCoor pos = center_pentagon_0 + rc_pentagon * dir;
        coors << pos;
      }

    ///
    /// Compute Coordinates of the Next Five Pentagons
    ///
    for ( int i=0; i<5; i++ )
      {
        pCoor p0 = coors[i], p1 = coors[(i+1)%5];
        pMatrix_Translate to_p0( p0 );
        pMatrix_Rotation rot2( pNorm(p0,p1), theta_dihedral );
        pMatrix_Translate to_Origin( pVect( p0, pCoor(0,0,0) ) );
        pMatrix m = to_p0 * rot2 * to_Origin;
        for ( int j=0; j<5; j++ ) coors << m * coors[j];
      }

    ///
    /// Compute Coordinates of Last Six Pentagons
    ///

    pMatrix_Translate from_ctr( pVect(center,pCoor(0,0,0)) );
    pMatrix_Translate to_ctr( center );
    pMatrix_Rotation rot3(ax,numbers::pi);
    pMatrix_Rotation rot4(az,0.2*numbers::pi);
    pMatrix ms = to_ctr * rot4 * rot3 * from_ctr;
    const int n_vtx_demi = coors.size();
    for ( int i=0; i<n_vtx_demi; i++ ) coors << ms * coors[i];

    ///
    /// Send Triangles to Our3D for Rendering
    ///

    coors_os.clear();
    colors.clear();
    gc.vtx_normals_set();

    switch ( opt_hw3_variation ){

    case HW3_Triangles:

      /// SOLUTION -- Problem 2
      //
      for ( size_t i=0; i<coors.size(); i+=5 )

        // Render the pentagon by emitting three triangles, all
        // share pentagon vertex 0 (index i).
        //
        for ( int j=1; j<4; j++ )
          coors_os << coors[ i ] << coors[ i + j ] << coors[ i + j + 1 ];

      gc.topology_set(Topology_Individual);

      break;

    case HW3_Fans:

      // Problem 3
      // Don't change this code, modify the code in Our_3D.

      gc.topology_set(Topology_Fan,5); // Use a fan grouping.
      coors_os = coors;

      break;

    default:
      assert( false );
    }

    //  for ( auto _: coors_os ) colors << color_lsu_spirit_purple;
    for ( auto _: coors_os ) colors << color_light_slate_gray;
    gc.vtx_coors_set(coors_os);
    gc.vtx_colors_set(colors);
    gc.draw_rasterization();

  }

  gc.frame_buffer.fbprintf
    ("%s   Dodecahedron: %ld vertices, %ld triangles, %ld fragments.\n",
     p1stats.s,
     gc.stats_n_vertices, gc.stats_n_triangles, gc.stats_n_fragments);

}

Our_3D&
Our_3D::draw_rasterization()
{
  ///
  /// Prepare Transformations
  ///

  // Specifications of User's Monitor
  //
  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.

  // 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 Triangle 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& coors_os = *vtx_coors;
  auto& colors = *vtx_colors;

  const bool topology_strip = topology == Topology_Strip;
  const bool topology_fan [[maybe_unused]] = topology == Topology_Fan;

  const ssize_t stop = coors_os.size() - 2;

  /// SOLUTION -- Problem 3
  //
  // Triangle Fan Plan:
  //
  //  - Set increment to 1 (as in triangle strips).
  //  - Vertex i Notes:
  //      Vertex i is in fan number i / topology_n_vertices
  //         which is i / 5 for the pentagons.
  //      Let j = i % topology_n_vertices 
  //         remember that % is the modulus operator.
  //      The first vertex of the fan is
  //         idx_00 = i / topology_n_vertices * topology_n_vertices ..
  //         .. remembering that integer division rounds down.
  //      When j < topology_n_vertices - 2 ..
  //      .. the following vertices form a triangle of the fan:
  //         idx_00,  i + 1,  i + 2.
  //      When j == topology_n_vertices - 2 and j == topology_n_vertices - 1
  //      .. should not render a triangle.
  //
  //  Code immediately below sets increment to 1 for fan (and keeps it
  //  at one for strip).
  //
  //  In the vertex loop idx_0 is computed as described above when the
  //  topology is a fan, and the triangle is skipped as described
  //  above.

  //  Increment by 1 for both strips and fans.
  //
  size_t inc = topology == Topology_Individual ? 3 : 1;

  stats_n_vertices += coors_os.size();

  for ( ssize_t i=0; i<stop; i += inc )
    {
      /// SOLUTION -- Problem 3
      //
      //  Compute idx_0 appropriately.
      //
      ssize_t idx_0 =
        topology_fan ? i / topology_n_vertices * topology_n_vertices : i;
      ssize_t idx_1 = i + 1;
      ssize_t idx_2 = i + 2;
      //
      // If triangle would be part of two fans, skip triangle.
      //
      if ( topology_fan && idx_2 - idx_0 >= topology_n_vertices ) continue;

      // Get next triangle's object space coordinates ..
      //
      pCoor o0 = coors_os[idx_0],  o1 = coors_os[idx_1],  o2 = coors_os[idx_2];
      pCoor_Homogenized_Keep_w w0( ws_from_os * o0 );
      pCoor_Homogenized_Keep_w w1( ws_from_os * o1 );
      pCoor_Homogenized_Keep_w w2( ws_from_os * o2 );

      pColor c0 = colors[idx_0], c1 = colors[idx_1], c2 = colors[idx_2];

      stats_n_triangles++;

      // Compute triangle normal (for lighting).
      //
      pNorm tn = cross(o0,o1,o2);

      // Non-null if user has provided vertex normals and wants to use them.
      vector<pVect4> npd;
      const bool use_normals = !opt_tri_normals_always && vtx_normals;
      if ( use_normals )
        for ( auto idx: { idx_0, idx_1, idx_2 } ) npd << vtx_normals[idx];
      pVect4* np = use_normals ? &npd[0] : nullptr;

      pVect w20(w2,w0), w21(w2,w1);
      const float db0 = 0.5/max(fabs(w20.x),fabs(w20.y));
      const float db1 = 0.5/max(fabs(w21.x),fabs(w21.y));

      // Quick and dirty method of skipping huge triangles.
      if ( db0 * db1 < 1e-7 ) continue;

      // Used for computing perspective-correct barycentric coordinates.
      const float w0ow2 = w0.w/w2.w, w0ow1 = w0.w/w1.w;
      const float w1ow2 = w1.w/w2.w, w1ow0 = w1.w/w0.w;

      // Rasterize the Triangle.
      // Iterate over triangle using barycentric coordinates.
      //
      for ( float b0=0; b0<=1; b0 += db0 )
        for ( float b1=0; b1<=1-b0; b1 += db1 )
          {
            const float b2 = 1 - b0 - b1;

            // Each iteration operates on one fragment of triangle.

            // Window-Space Coordinates of Fragment
            //
            pCoor wf = b0 * w0 + b1 * w1 + b2 * w2;

            stats_n_fragments++;

            if ( uint(wf.x) >= win_width || uint(wf.y) >= win_height ) continue;

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

            // Depth (Z) Test
            //
            float zinv = wf.z;
            if ( zinv < 0 || zinv > 1 ) continue;
            if ( zbuffer[ idx ] < zinv ) continue;

            zbuffer[ idx ] = zinv;

            // Compute perspective-correct barycentric coordinates.
            //
            float bc0 = b0 / ( b2 * w0ow2 + b1 * w0ow1 + b0 );
            float bc1 = b1 / ( b2 * w1ow2 + b0 * w1ow0 + b1 );
            float bc2 = 1 - bc0 - bc1;

            // Find object-space coordinate of fragment.
            //
            pCoor of =     bc0 * o0 + bc1 * o1 + bc2 * o2;

            // Blend color of three vertices together.
            //
            /// SOLUTION -- Problem 1b
            //
            // When rendering a strip set all colors equal to the
            // color of the last (provoking) vertex of the triangle.
            //
            pColor color = topology_strip ? c2 : bc0 * c0 + bc1 * c1 + bc2 * c2;
            //
            // It would also be correct to use the color of first
            // vertex of the triangle (c0), or even the middle vertex
            // color (c1), so long as the particular vertex position
            // (first, middle, last) is used consistently.

            // If provided, blend vertex normals, otherwise use triangle normal.
            //
            pVect4 n = np ? bc0 * np[0] + bc1 * np[1] + bc2 * np[2] : tn;

            // Compute lighted color.
            //
            pNorm f_to_l(of,light_location);
            float phase = fabs(dot(n,f_to_l));
            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();
          }
    }
  return *this;
}


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