/// LSU EE 4702-X (Fall 2026), GPU Programming
//
 /// Homework 3 -- SOLUTION
 //
 //  Assignment: https://www.ece.lsu.edu/gpup/2026/hw03.pdf
 //
 /// Modified this file only.
 //
 //  Search for SOLUTION to find the places where the solution
 //  appears.
 //
 // Based on cpu-only/demo-06-rend-pipe.cc.

//
 /// Rendering Pipeline
//

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

 ///
 /// Our_3D: Low-Level 3D Graphics Library.
 ///

enum TopologyE { Topology_Individual, Topology_Strip, Topology_Ring,
  Topology_ENUM_SIZE };

typedef int Topology;

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& topology_set( Topology topo = Topology_Individual )
  {
    topology = topo;
    return *this;
  }

  /// SOLUTION -- Problem 2
  //
  //  Provide a way of specifying a number of vertices. This number
  //  is used for Topology_Ring and ignored for other topologies.
  //
  Our_3D& topology_set( Topology topo, size_t n_vtx )
  {
    topology = topo;
    ring_n_elt = n_vtx;
    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;
  }

  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;

  /// SOLUTION -- Problem 2
  //
  size_t ring_n_elt;
};


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

  Topology opt_hw3_topology;

};

void
World::init_scene()
{
  eye_location = pCoor(-1.2,.5,10.2);
  light_location = pCoor(-2.2,1.5,10.2);
  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;

  opt_hw3_topology = Topology_Strip;
}

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 '1':
    opt_hw3_topology++;
    if ( opt_hw3_topology >= Topology_ENUM_SIZE ) opt_hw3_topology = 0;
    break;
  case '!':
    if ( opt_hw3_topology == 0 ) opt_hw3_topology = Topology_ENUM_SIZE;
    opt_hw3_topology--;
    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 '+': case '=':
    if ( !frame_buffer.keyboard_control ) light_color *= 1.1;
    break;
  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 Topo: %s ('1')  Normals: %s ('v')  "
     "Tryout1 %s ('y')  Tryout2 %s ('Y')\n",
     opt_hw3_topology == Topology_Individual ? "I-TRIS" :
     opt_hw3_topology == Topology_Strip ? "STRIP" :
     opt_hw3_topology == Topology_Ring ? "RING " : "?????",
     gc.opt_tri_normals_always ? "TRIANGLE" : "VERTEX  ",
     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;

  // Insert First Triangle
  //
  // Insert coordinates of a triangle into coordinate list,
  // and insert its color into a color list.
  //
  coors_os << pCoor( 0, 0, 0 ) << pCoor( 9, 6, -9 ) << pCoor( 0, 7, -5 );
  colors << color_white << color_white << color_white;

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

  // Insert Two More Triangles
  //
  // Add a square consisting of a gold and multi-colored triangle.
  //
  colors << color_lsu_spirit_gold << color_lsu_spirit_gold
         << color_lsu_spirit_gold;
  coors_os << pCoor(-7,0,-2) << pCoor(-7,2,-2) << pCoor(-5,0,-2);

  colors   << color_red      << color_blue     << color_green;
  coors_os << pCoor(-7,2,-2) << pCoor(-5,2,-2) << pCoor(-5,0,-2);

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

  ///
  /// 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 a Helix Tube
  ///
  //  A helix tube is a cylinder with its axis following a helix.
  //  Could be called a helixial cylinder.

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

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

  // Specifications of Helix Tube
  //
  pCoor helix_start(-4,-1,-3);

  //
  /// Compute vertices of a ring.
  //
  // The ring center is at the origin, and its normal is along z axis.
  //
  int n_ring_vtx = 20;  // Number of vertices in ring.
  float delta_theta = 2 * numbers::pi / n_ring_vtx;
  float tube_radius = 0.75;  // Tube radius is the ring radius.

  vector<pCoor> coors_ring;
  vector<pVect4> norms_ring;
  vector<pColor> colors_ring;

  // Local coordinates of ring.
  pVect gx(1,0,0), gy(0,1,0), gz(0,0,1);

  for ( int i=0; i<n_ring_vtx; i++ )
    {
      float theta = i * delta_theta + numbers::pi/2;
      pVect4 n = gx * cosf(theta) + gy * sinf(theta);
      pCoor c = tube_radius * n;
      coors_ring << c;
      norms_ring << n;
      colors_ring << ( i ? color_cyan : color_yellow );
    }

  // For convenience, append a copy of first vertex, so that the first
  // and last vertex in the lists are the same.
  //
  if ( opt_hw3_topology != Topology_Ring )
    {
      coors_ring << coors_ring[0];
      norms_ring << norms_ring[0];
      colors_ring << colors_ring.back();
    }

  //
  /// Compute Vertices of Helix Tube
  //
  int n_ssegs = 80;
  float s_delta_eta = 7 * numbers::pi / n_ssegs;
  float s_ht = 3;
  float s_wd = 10;
  float s_delta_x = s_wd / n_ssegs;
  int n_rev = 10;
  float delta_rho = n_rev * 2 * numbers::pi / n_ssegs;

  vector <pCoor> tr_co_prev;
  vector <pVect4> tr_n_prev;
  pNorm helix_ax = gz, helix_ay = gy, helix_az = gx;
  for ( int i=0; i<=n_ssegs; i++ )
    {
      // Compute coordinates of vertices at position i along the helix.
      //
      // This is done by computing transformation matrices and using
      // them to transform coordinates in coors_ring and normals_ring.
      //
      // The transformed coordinates and matrices are placed in
      // tr_co_curr and tr_n_curr.
      //
      // The transformed coordinates and matrices from the previous
      // iteration are in tr_co_prev and tr_n_prev.
      //
      // Finally, insert coordinates, normals, and colors into the
      // containers passed to Our_3D: coors_os, normals_os, colors.
      //
      // The order in which coordinates, normals, and colors are
      // inserted into coors_os, normals_os, and colors is determined
      // by opt_hw3_topology, which is set to the topology that Our_3D
      // expects.


      float eta = i * s_delta_eta;
      pVect tube_ay = cosf(eta) * helix_ax + sinf(eta) * helix_ay;

      // Coordinate of point on the helix.
      pCoor tube_axis = helix_start + s_ht * tube_ay + i * s_delta_x * helix_az;

      // Vector pointing in direction of helix at this point. (The
      // derivative with respect to i of the parametric formula for
      // tube_axis.)
      //
      pNorm tube_az =
        -s_ht * s_delta_eta * sinf(eta) * helix_ax
        + s_ht * s_delta_eta * cosf(eta) * helix_ay
        + s_delta_x * helix_az;
      pNorm tube_ax = cross(tube_ay,tube_az);

      // Rotate ring to point on helix.
      pMatrix_Cols srot(tube_ax,tube_ay,tube_az);

      // Twist ring (so that yellow strip forms a spiral).
      pMatrix_Rotation arot( gz, i * delta_rho );

      pMatrix rot = srot * arot;
      pMatrix_Translate to_helix(tube_axis);
      pMatrix m = to_helix * rot;

      // Compute coordinates and normals of ring at this point on the helix.
      //
      vector <pCoor> tr_co_curr;  // Transformed Ring Coordinate CURRent.
      vector <pVect4> tr_n_curr;  // Transformed Ring Normal CURRent.
      for ( auto& c: coors_ring ) tr_co_curr << m * c;
      for ( auto& n: norms_ring ) tr_n_curr << pVect4(rot * n);

      switch ( opt_hw3_topology ) {

      case Topology_Ring:
        // Vertex ordering used for Problem 2.
        //
        // Study but don't modify code in this case.

        // For the ring topology, just append the new coordinates and
        // normals to the containers. The colors don't change along
        // the helix, so just append the initial list.
        //
        // Note that for the ring topology each vertex appears once.

        // DO NOT modify code below.
        for ( auto c: tr_co_curr ) coors_os << c;
        for ( auto n: tr_n_curr ) normals_os << n;
        for ( auto c: colors_ring ) colors << c;
        // DO NOT modify code above.
        break;

      case Topology_Strip:
        // DO NOT modify code for his case.

        // Insert vertices in triangle-strip order.
        //
        // tr_co_curr holds coordinates of upper ring.
        // tr_co_prev holds coordinates of lower ring.

        // Note: Loops don't execute if tr_co_prev or tr_n_prev empty.
        for ( auto [co_prev,co_curr]: views::zip( tr_co_prev, tr_co_curr ) )
          coors_os << co_prev << co_curr;

        for ( auto [ n_prev, n_curr ]: views::zip( tr_n_prev, tr_n_curr ) )
          normals_os << n_prev << n_curr;

        for ( auto [co, _]: views::zip(colors_ring,tr_n_prev) )
          colors << co << co;

        break;

      case Topology_Individual:

        // Problem 1:
        //
        // [✔] Insert vertices for individual triangle ordering.
        // [✔] Most changes should be in this case.
        //
        // The unmodified code inserts vertices in triangle-strip order.

        for ( ssize_t j = 0;  j < ssize_t(tr_co_prev.size())-1;  j++ )
          {
            // Note: j current position on the ring.
            size_t jn = j + 1;  // Compute next position on the ring.
            pColor& color = colors_ring[j];  // For convenience.

            // Problem 1 solution goes here.

            if ( !opt_tryout1 )
              {
                /// SOLUTION -- Problem 1
                //
                //  Use current vertex (j) and next vertex (jn).
                //
                //  Insert two triangles instead of one.
                //
                coors_os << tr_co_prev[j]  << tr_co_curr[j] << tr_co_prev[jn];
                coors_os << tr_co_prev[jn] << tr_co_curr[j] << tr_co_curr[jn];

                normals_os << tr_n_prev[j]  << tr_n_curr[j] << tr_n_prev[jn];
                normals_os << tr_n_prev[jn] << tr_n_curr[j] << tr_n_curr[jn];

                // Just use the color from vertex j. Our_3D will use
                // this color for the entire triangle for
                // triangle-strip ordering, so do the same for
                // individual triangles.
                //
                colors << color << color << color << color << color << color;
              }
            else
              {
                /// Original (unsolved) Code
                //
                // The code below is for a triangle strip order.
                // [ ] Remove code below when you get started.
                // Note: Left code to make it easy to compare.
                //
                coors_os   << tr_co_prev[j] << tr_co_curr[j];
                normals_os << tr_n_prev[j]  << tr_n_curr[j];
                colors << color << color;
              }

          }

        break;
      }

      // Note: Lines below not needed for the ring topology.
      swap(tr_co_prev,tr_co_curr); swap(tr_n_prev,tr_n_curr);
    }

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

  // Problem 2: Modify some code below.

  // Set topology (vertex ordering) that Our_3D should use.
  //
  /// SOLUTION -- Problem 2
  //
  //  Specify number of vertices in ring. This is needed for the
  //  ring topology and harmless in others.
  //
  gc.topology_set(opt_hw3_topology,coors_ring.size());

  // Provide a pointer to the coordinates, normals, and colors to be used.
  gc.vtx_coors_set(coors_os);
  gc.vtx_normals_set(normals_os);
  gc.vtx_colors_set(colors);

  // Tell Our_3D to now rendering using the vertices and topologies
  // specified above.
  //
  gc.draw_rasterization();

  // Print a helpful message to the green text.
  // Feel free to modify if it might help.
  gc.frame_buffer.fbprintf
    ("Helix Tube: %ld vertices, %ld triangles, %ld fragments.\n",
     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 topo_tris [[maybe_unused]] = topology == Topology_Individual;
  const bool topo_strip [[maybe_unused]] = topology == Topology_Strip;
  const bool topo_ring [[maybe_unused]] = topology == Topology_Ring;

  size_t start = 0;

  /// SOLUTION -- Problem 2 -- Plan
  //
  //  Consider the way vertices v0, v1,..., v299 form triangles when
  //  ring_n_elt = 100 with 300 vertices, and pardon my non-standard
  //  Roman numerals:
  //
  //  v200   v201   v202    v298   v299   v200
  //  +------+------+ ...   +------+------+
  //  |\CCII |\CCIV |       |\     |\ CCC |
  //  |  \   |  \   |       |  \   |  \   |
  //  |    \ |    \ |       |    \ |    \ |
  //  | CCI \|CCIII\|       |     \|CCIC \|
  //  |      |      |       |      |      |
  //  v100   v101   v102    v198   v199   v100
  //  +------+------+ ...   +------+------+
  //  |\  II |\  IV |       |\ CIIC|\  CC |
  //  |  \   |  \   |       |  \   |  \   |
  //  |    \ |    \ |       |    \ |    \ |
  //  | I   \| III \|       |CIIIC\| CIC \|
  //  +------+------+ ...   +------+------+
  //  v0     v1     v2      v98    v99    v0
  //
  //  In the i loop below when i=0, we emit Triangle I (v0,v1,v100),
  //  when i=1, we emit Triangle II (v1,v101,v100). When i=2 we emit
  //  Triangle III, and when i=3 we emit Triangle IV, and so on.
  //
  //  To do this the number of iterations in the i loop is 2(V-n),
  //  where V is the number of vertices (coors_os.size()) and n is the
  //  number of vertices in a ring (value of ring_n_elt). See the
  //  initialization of variable stop_ring below.
  //
  //  In an i loop iteration indices i0, i1, and i2, are used for the
  //  three vertices of the triangle being rendered. For strips and
  //  individual triangles. they are set to i0=i, i1=i+1, i2=i+2, but
  //  for rings they are not sequential.
  //
  //  Consider Triangle III. That's emitted for i=2. The index i0 of
  //  v1 is i0=i/2=1, the index i1 of v2 is i1=i/2+1=2, and the index
  //  i2 of v101 is i2=i/2+100=101. The indices i0 and i1 are computed
  //  differently for Triangle IV. A special case is wrap-around, seen
  //  in Triangle CIC, where v99+1 wraps around to v0. For details see
  //  the solution code in the i loop.
  //
  //  Another special case is the color. Both triangles in a pair must
  //  be the same color and it must be possible to set each pair to a
  //  different color. To achieve this the color for triangles I and
  //  II are both retrieved using index i/2, not index i0. Had i0 been
  //  used the second triangle of a pair would be a different color.
  //  It would have been convenient to use i2 as the index of the
  //  color since i2 is the same for both triangles of a pair, but
  //  that coloring would not match the way they are done for a
  //  triangle strip here.
  //
  //  BTW, The correct way to represent 199 in Roman numerals is
  //  CXCIX. The Romans *did* think in powers of 10 (a mile is 1000
  //  paces) and for that reason each power of 10 in a Roman numeral
  //  is grouped separately: C + XC + IX. Maybe some Roman did see
  //  that it could be written as I * C + (IX) * X + IX * I but just
  //  could not convince anyone that it was better to write numbers
  //  that way. Or maybe an Indian beat him (or her) to it.


  /// SOLUTION -- Problem 2
  //
  //  Set increment to 1 for ring ..
  //
  size_t inc = topo_tris ? 3 : 1;
  //
  //  .. and use most vertices twice for a ring.
  //
  size_t stop_ring = 2 * ( coors_os.size() - ring_n_elt ) - 1;
  size_t stop = topo_ring ? stop_ring : coors_os.size() - 3;

  stats_n_vertices += coors_os.size();

  ///
  /// Main Rendering Loop
  ///

#pragma omp parallel for reduction(+:stats_n_triangles,stats_n_fragments)
  for ( size_t i=start; i<=stop; i += inc )
    {
      // Problem 2: Some of solution goes below.

      // Compute indices of the next triangle.
      //
      size_t i0 = i,  i1 = i+1,  i2 = i+2;
      //
      // In unmodified code works for strips and individual triangles,
      // but not rings.

      /// SOLUTION -- Problem 2
      //
      //  Set indices as described in the Plan above.
      //
      if ( topo_ring )
        {
          // If true, we are at the lower-left triangle of the pair.
          // See diagram in Plan, above.
          //
          bool triLL = ( i & 1 ) == 0;

          size_t ir0 = i >> 1;
          size_t ir1m = ir0 + 1;  // Value before checking for wrap-around.
          size_t ir1 = ir1m % ring_n_elt  ?  ir1m  :  ir1m - ring_n_elt;

          i0 = triLL ?  ir0  :  ir1;
          i1 = triLL ?  ir1  :  ir1 + ring_n_elt;
          i2 = ir0 + ring_n_elt;
        }

      // Get next triangle's object space coordinates ..
      //
      pCoor o0 = coors_os[i0],  o1 = coors_os[i1],  o2 = coors_os[i2];
      //
      // .. and transform them to window space.
      //
      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 );

      // Get colors.
      //
      pColor c0 = colors[i0], c1 = colors[i1], c2 = colors[i2];

      /// SOLUTION -- Problem 2
      //
      if ( topo_ring ) c0 = colors[i>>1];

      stats_n_triangles++;

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

      // If true, use normals from vtx_normals container. If false,
      // use triangle normals.
      //
      const bool want_normals = !opt_tri_normals_always && vtx_normals;
      vector<pVect4> np;
      if ( want_normals ) for ( auto ix: {i0,i1,i2} ) np << vtx_normals[ix];

      pVect w20(w2,w0), w21(w2,w1), w01(w0,w1);
      const float db0 =
        0.5/ min( max(fabs(w20.x),fabs(w20.y)), max(fabs(w01.x),fabs(w01.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++;

            // Scissor (discard) fragment if it's outside the window.
            //
            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.
            //
            pColor color = topo_tris ? bc0 * c0 + bc1 * c1 + bc2 * c2 : c0;

            // If provided, blend vertex normals, otherwise use triangle normal.
            //
            pVect4 n = want_normals ? 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;
}