/// LSU EE 7700-1 (Sp 2009), Graphics Processors
//
/// Homework 5  SOLUTION


// $Id:$  <- This is not your student ID, don't change this line!

/// Instructions

// If necessary, follow the class account setup instructions linked to
// the class procedures page,
// http://www.ece.lsu.edu/koppel/gp/proc.html

// For instructions on how to check out edit, compile, and debug, see
// the "Programming Homework Work Flow" entry on the procedures page,
// http://www.ece.lsu.edu/koppel/gp/proc.html.
//
// For those instructions you need to know that:
//
//  This assignment is at SVN URI https://svn.ece.lsu.edu/svn/gp/hw/2009/hw5
//
//  The assignment instructions are in file hw4.cc. (This file.)

// The OpenGL spec, needed for this assignment, is at
//   http://www.ece.lsu.edu/koppel/gp/refs/glspec21.pdf
//   The relevant sections are 2.7, 2.8, and 2.9. (Vertex specification,
//   vertex arrays, and buffer objects.)

// For the solutions to the problems below edit this file, even if
// it makes more sense to edit others (namely, coord.h). If it seems
// that coord.h must be edited, contact me.

// The main code is in routine Tube::render().


/// Overview

 //  The code in this file renders the undulating tube. In this
 //  assignment add a visual indicator of surface normals, we will
 //  call them arrows. The goal of this assignment is to determine the
 //  overhead of switching transformation matrices in the GPU, the
 //  transformation matrices will be used to rotate and translate an
 //  arrow into position.  Arrows can also be rendered the "old-fashioned"
 //  way, by computing each arrow's vertex coordinates on the CPU.

///  Keyboard Commands
 //
 //  'v'  Switch method of specifying arrow vertices:
 //       (Implementing these options is part of the assignment.)
 //       0:  Use glMultiDrawArrays. (One call for all arrows.)
 //       1:  Use glDrawArrays within a loop.
 //       2:  Within a loop change transform and send same arrow.
 //
 //  'b'  Toggle between using client arrays and buffer objects for arrows.
 //       Implemented as part of this assignment.
 //
 //  'p'  Pause tube. When paused vertex information is not re-computed
 //       every frame.

 /// Variables
 //   Selected program variables can be modified using the keyboard.
 //   Use "Tab" to cycle through the variable to be modified, the
 //   name of the variable is displayed next to "VAR" on the bottom
 //   line of green text.
 //   Use '+' (or '=') and '-' (or '_') to change the variable.
 //   (Search for 'variable_control.insert' to find examples of how to
 //   add your own variables.)
 //
 //  VAR Light Intensity - The light intensity.
 //  VAR Pattern Levels - The number of rings of triangles (along the z axis).
 //  VAR Arrow Detail - Amount of detail shown in arrow.

 //
 /// Eye and Light Location
 //   Arrows, Page Up, Page Down
 //   Move either the light or the eye.
 //   After pressing 'l' the keys move the light, after pressing 'e'
 //   they move the eye (viewer location). The eye and light location
 //   coordinates are displayed in the upper left.
 //
 /// Eye Direction
 //   Home, End, Delete, Insert
 //   Turn the eye direction.
 //   Home should rotate eye direction up, End should rotate eye
 //   down, Delete should rotate eye left, Insert should rotate eye
 //   right.  The eye direction vector is displayed in the upper left.



/// Problem 1

 // Modify the code below so that a pyramid (which will be called an
 // arrow) is displayed at each tube vertex, with the base at the
 // vertex and the apex pointing in the direction of the vertex normal
 // (or in the opposite direction it that's more visually
 // appealing).

 // [ ] The arrows should be rendered using GL_TRIANGLE_FAN.
 // [ ] The number of sides should be about opt_arrow_detail.
 // [ ] Make sure num_bytes is updated properly.
 // [ ] Use the MTrig class to re-use trigonometric function values.


/// Problem 2

 // In this problem modify the code so that the arrow is drawn in
 // different ways, depending on the value of opt_v_buffering and
 // opt_buffer_objects. For opt_v_buffering = 0, use a single call to
 // glMultiDrawArrays to send the arrow vertices; for opt_v_buffering
 // = 1 call glDrawArrays inside a loop, once for each tube
 // vertex. For opt_v_buffering = 2 use a transformation matrix to
 // position an arrow. That is, construct an arrow in some standard
 // location and orientation. Then send the same arrow over and over,
 // each time changing the MODELVIEW transformation so that the arrow
 // is correctly positioned.

 // [ ] If opt_v_buffering = 0, use glMultiDrawArrays (one call).
 // [ ] If opt_v_buffering = 1, use glDrawArrays (in a loop).
 // [ ] If opt_v_buffering = 2, change the transformation in a loop.

 // [ ] If opt_buffer_objects is true the arrow data should be in a buffer obj.
 // [ ] If opt_buffer_objects is false the arrow data should be send from cpu.

 // [ ] Do not re-compute data if opt_pause is true.
 // [ ] Store transformation matrices in an array and re-use when possible.
 // [ ] Do not re-compute or resend data when it is not necessary.

 // [ ] Make sure that num_bytes is updated correctly.


/// Problem 3

 /// Answer the following questions:

 // Under which circumstances does it make sense to use transformations
 // to move objects?

 // Provide an estimate for the overhead of changing transformations
 // and for the rate at which the GPU can render arrow primitives.

 /// To answer these questions:

 // - Run optimized versions of this code. (hw5, not hw5-debug).
 // - Vary Arrow Detail
 // - Consider performance with different opt_v_buffering options.
 // - Consider performance when paused and with and without buffer objects.
 // - Look at GPU time and CPU time.


 /// SOLUTION

 // Using transformation matrices has these potential benefits:

 // Reduce the amount of computation performed by the CPU. The host CPU
 // only needs to compute the transformation matrix, the GPU can then
 // apply it to each vertex in the arrow. The benefit depends on the GPU
 // speed and the number of vertices in the arrow.

 // Reduce the amount of data sent from the GPU to CPU. This benefit is
 // realized when buffer objects are used to hold an arrow, since only one
 // arrow needs to be sent. (If the arrow was sent from client memory
 // instead of a buffer object then it would have to be re-sent for every
 // tube normal, even though the same sets of vertex coordinates would be
 // sent for each tube normal.) This benefit is proportional to the number
 // of tube normals.

 // Reduce the size of the vertex arrays operated on. Even if client
 // memory is used for the arrow, the same arrow would be repeatedly
 // sent. The first time the arrow is sent it might need to be flushed
 // from the host CPU's cache, whereas without transformation matrices,
 // many arrows would have to be flushed from the host CPU cache.

 // Using transformation matrices can potentially slow execution because
 // of the time needed to compute the matrix, the time needed to transfer
 // the matrix, and the time needed for the GPU to install the matrix. If
 // the transformation matrix is computed by constructing and multiplying
 // a rotation and translation matrix then the amount of calculation will
 // be much larger than computing the position of each arrow vertex. The
 // size of the matrix, 16 floats, is about a quarter the size of 1
 // 10-sided arrow (about 66 floats), a significant fraction.

 // The data below show the results of running the program at two arrow
 // details: 10 (the default) and 96. The GPU times appearing below
 // include some CPU time (a problem discovered too late). At arrow detail
 // 10 with or without a buffer object the code runs slower when using a
 // transformation matrix. At arrow detail 96 things run a bit faster in
 // each configuration when using transformation matrices. The largest
 // benefit occurs when the arrow is not paused and buffer objects are in
 // use because of the reduction in data transfer. If the animation isn't
 // paused or if client arrays are used then transformation matrices don't
 // reduce the amount of data transfer.  The reduction seen in those
 // cases may have to do with repeatedly accessing the same arrow.

 // Data collected running this solution on a GeForce 8800 GT

 // P: Paused, R: Running
 // MDA: Use glMultiDrawArrays, DA, gl DrawArrays, T change transform.
 // BO: Buffer Object, CA Client array.

 // Note: GPU timing includes some cpu time.

 // Arrow detail: 96.4

 // GPU / CPU: P / MDA / CA:  15.9 / 3.91  131.5  ( 30 f/s)
 // GPU / CPU: P /  DA / CA:  16.0 / 4.09  131.5  ( 30 f/s)
 // GPU / CPU: P /  T  / CA:  14.8 / 4.0   269.6  ( 60 f/s)

 // GPU / CPU: P / MDA / BO:  15.9 / 0.74   7.6
 // GPU / CPU: P /  DA / BO:  16.0 / 0.88   7.6
 // GPU / CPU: P /  T  / BO:  15.0 / 2.42  21.6 ( 60 f/s)

 // GPU / CPU: R / MDA / CA:  18.0 /  7.2  132  ( 30 f/s)
 // GPU / CPU: R /  DA / CA:  18.0 /  7.3  132  ( 30 f/s)
 // GPU / CPU: R /  T  / CA:  16.2 /  6.3  135  ( 30 f/s)

 // GPU / CPU: R / MDA / BO:  22.0 /  6.8  152  ( 30 f/s)
 // GPU / CPU: R /  DA / BO:  22.4 /  6.9  152  ( 30 f/s)
 // GPU / CPU: R /  T  / BO:  17.6 /  4.91  11  ( 30 f/s)

 // Arrow detail: 10.0

 // GPU / CPU: P / MDA / CA:   1.8 /  1.1   52
 // GPU / CPU: P /  DA / CA:   1.8 /  1.1   52  ( 60 f/s)
 // GPU / CPU: P /  T  / CA:   2.2 /  2.5   58  ( 60 f/s)

 // GPU / CPU: R / MDA / CA:   2.4 /  1.6   51  ( 60 f/s)
 // GPU / CPU: R /  DA / CA:   2.4 /  1.6   51  ( 60 f/s)
 // GPU / CPU: R /  T  / CA:   4.8 /  5.0   57  ( 60 f/s)

 // GPU / CPU: R / MDA / BO:   2.4 /  1.5   58  ( 60 f/s)
 // GPU / CPU: R /  DA / BO:   2.6 /  1.6   58  ( 60 f/s)
 // GPU / CPU: R /  T  / BO:   4.7 /  4.9   21  ( 60 f/s)


#include <stdio.h>
#include <strings.h>
#include <stdlib.h>
#include <deque>

#define GL_GLEXT_PROTOTYPES
#define GLX_GLXEXT_PROTOTYPES
#include <GL/gl.h>
#include <GL/glext.h>
#include <GL/glx.h>
#include <GL/glxext.h>
#include <GL/glu.h>
#include <GL/freeglut.h>

#include "util.h"
#include "coord.h"

// Display a tetrahedron, used to indicate light position.
//
void
insert_tetrahedron(pCoor& loc, float size)
{
  pCoor v0(loc.x,loc.y,loc.z);
  pCoor v1(loc.x,loc.y-size,loc.z+size);
  pCoor v2(loc.x-.866*size,loc.y-size,loc.z-0.5*size);
  pCoor v3(loc.x+.866*size,loc.y-size,loc.z-0.5*size);
  static pColor c1(0xffffff);
  static pColor c2(0xff00);

  glDisable(GL_LIGHTING);

#define TRI(va,vb,vc)                                                         \
  {                                                                           \
    pVect n = cross(va,vb,vc);                                                \
    glNormal3fv(n);                                                           \
    glColor3fv(c1);  glVertex3fv(va);                                         \
    glColor3fv(c2);  glVertex3fv(vb);                                         \
    glVertex3fv(vc);                                                          \
  }

  glBegin(GL_TRIANGLES);
  TRI(v0,v1,v2); TRI(v0,v2,v3); TRI(v0,v3,v1);
  glEnd();

# undef TRI

  glEnable(GL_LIGHTING);
}


// Class for re-using sine and cosine values.
//
class MTrig {
public:
  MTrig():size(0),storage(NULL){}
  void init(int sizep){
    size = sizep;
    if ( storage ) delete storage;
    storage = new float[size];
    idx = 0;
    full = false;
  }
  float sin(float theta){ return trig(theta,::sin); }
  float cos(float theta){ return trig(theta,::cos); }
private:
  float trig(float theta,double (*func)(double))
  {
    //  return func(theta);
    if ( !full ) { storage[idx] = func(theta);  full = idx == size - 1; }
    if ( idx == size ) idx = 0;
    return storage[idx++];
  }
  int size;
  float* storage;
  int idx;
  bool full;
};


struct Vertex_Info {
  pCoor pos;
  pVect nor;
};

 ///
 /// Tube Class
 ///

class Tube {
public:
  Tube(pOpenGL_Helper &fb):ogl_helper(fb){ init(); }
  static void render_w(void *moi){ ((Tube*)moi)->render(); }
  void init();
  void modelview_update();
  void render();
private:
  pOpenGL_Helper &ogl_helper;
  pVariable_Control variable_control;
  pFrame_Timer frame_timer;

  pCoor eye_location;
  pVect eye_direction;
  pMatrix modelview;
  bool opt_move_light;
  bool opt_pause;

  float r0;
  float x_shift;
  float pattern_pitch_z;
  float opt_pattern_levels;
  float opt_pattern_width;

  int opt_v_buffering;
  bool opt_buffer_objects;      // If true use buffer objects for arrow.
  float opt_arrow_detail;  // It's a float so it can work with variable_control.

  // Some items that might be needed for solution.
  //
  Vertex_Info *arrow_info;
  pMatrix *arrow_transforms;
  GLuint arrow_info_buffer;

  // SOLUTION
  //
  int arrow_detail_alloc;
  int *arrow_count, *arrow_first;
  int arrow_vtx_count;
  int arrow_vtx;
  int arrow_info_v_opt;
  bool arrow_info_stale;
  bool arrow_buffer_stale;
  Vertex_Info tube_0;

  float opt_light_intensity;
  pCoor opt_light_location;

  double time_app_start;

  Vertex_Info *tube_info;
  int *index_array;

  int num_vtx_alloc;
  int num_indices;
  MTrig tarray;
  MTrig tarray_arrow;  // Might be needed for solution.

};

void
Tube::init()
{
  time_app_start = time_wall_fp();

  // Tell frame timer that work unit is "MB/s" and how should be scaled.
  //
  frame_timer.work_unit_set("MB/s",1e-6);

  r0 = 2;                 // Tube radius.
  x_shift = 0.4;          // Tube x offset.
  pattern_pitch_z = 0.25; // Triangle size (z axis).

  opt_pattern_levels = 30;    // Tube depth (z direction.)
  opt_pattern_width = 60;     // Triangle size (circumferential).

  // Amount of Detail in Arrow (Showing Normal Direction)
  //
  // The solution can have a different initial value.
  //
  opt_arrow_detail = 10;

  // SOLUTION
  //
  arrow_detail_alloc = 0;  // Size of pre-computed arrow. (0 means none)
  arrow_info = NULL;
  arrow_info_stale = true;
  arrow_buffer_stale = true;
  glGenBuffers(1,&arrow_info_buffer);
  tube_0.pos = pCoor(0,0,0);
  tube_0.nor = pCoor(0,1,0);

  opt_light_intensity = 8;
  opt_v_buffering = 0;
  opt_buffer_objects = false;
  opt_light_location.set(1.3,0.1,-1.4);
  opt_pause = false;

  eye_location.set(0,0,2.5);
  eye_direction.set(0,0,-1);
  modelview_update();

  // Arrange that variables below can be modified from the keyboard.
  //
  variable_control.insert(opt_arrow_detail,"Arrow Detail");
  variable_control.insert(opt_light_intensity,"Light Intensity");
  variable_control.insert(opt_pattern_width,"Vertices per Ring");

  tube_info = NULL;

  index_array = 0;
  num_vtx_alloc = 0;
  num_indices = 0;

}

void
Tube::modelview_update()
{
  pMatrix_Translate center_eye(-eye_location);
  pMatrix_Rotation rotate_eye(eye_direction,pVect(0,0,-1));
  modelview = rotate_eye * center_eye;
}

void
Tube::render()
{
  frame_timer.frame_start();
  const int arrow_detail = int(opt_arrow_detail);  // Cast to integer.

  glClearColor(0,0,0.0,0.5);
  glClearDepth(1.0);
  glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

  // Have frame timer provide timing information for top of image.
  //
  ogl_helper.fbprintf("%s\n",frame_timer.frame_rate_text_get());


  ///
  /// Transformation Matrix Setup
  ///

  glMatrixMode(GL_MODELVIEW);
  glLoadTransposeMatrixf(modelview);

  const int win_width = ogl_helper.get_width();
  const int win_height = ogl_helper.get_height();
  const float aspect = float(win_width) / win_height;

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glFrustum(-0.8,+0.8,-0.8/aspect,0.8/aspect,1,5000);

  glViewport(0, 0, win_width, win_height);
  pError_Check();


  ///
  /// Adjust options based on user input.
  ///

  pVect adjustment(0,0,0);
  pVect user_rot_axis(0,0,0);

  switch ( ogl_helper.keyboard_key ) {
  case FB_KEY_LEFT: adjustment.x = -0.1; break;
  case FB_KEY_RIGHT: adjustment.x = 0.1; break;
  case FB_KEY_UP: adjustment.z = -0.1; break;
  case FB_KEY_DOWN: adjustment.z = 0.1; break;
  case FB_KEY_PAGE_DOWN: adjustment.y = -0.1; break;
  case FB_KEY_PAGE_UP: adjustment.y = 0.1; break;
  case FB_KEY_DELETE: user_rot_axis.y = 1; break;
  case FB_KEY_INSERT: user_rot_axis.y =  -1; break;
  case FB_KEY_HOME: user_rot_axis.x = 1; break;
  case FB_KEY_END: user_rot_axis.x = -1; break;
  case 'b': case 'B': opt_buffer_objects = !opt_buffer_objects; break;
  case 'p': case 'P': opt_pause = !opt_pause; break;
  case 'l': case 'L': opt_move_light = true; break;
  case 'e': case 'E': opt_move_light = false; break;
  case 'v': case 'V':
    opt_v_buffering++;
    if ( opt_v_buffering == 3 ) opt_v_buffering = 0;
    break;

  case 9: variable_control.switch_var_right(); break;
  case '-':case '_': variable_control.adjust_lower(); break;
  case '+':case '=': variable_control.adjust_higher(); break;

  default: break;
  }

  // Update eye_direction based on keyboard command.
  //
  if ( user_rot_axis.x || user_rot_axis.y )
    {
      pMatrix_Rotation rotall(pVect(0,0,-1),eye_direction);
      user_rot_axis *= rotall;
      eye_direction *= pMatrix_Rotation(user_rot_axis, M_PI * 0.03);
      modelview_update();
    }

  // Update eye_location based on keyboard command.
  //
  if ( adjustment.x || adjustment.y || adjustment.z )
    {
      const double angle =
        fabs(eye_direction.y) > 0.99
        ? 0 : atan2(eye_direction.x,-eye_direction.z);
      pMatrix_Rotation rotall(pVect(0,1,0),-angle);
      adjustment *= rotall;
      if ( opt_move_light ) opt_light_location += adjustment;
      else                  eye_location += adjustment;
      modelview_update();
    }

  //
  // User Messages  (Magically inserted into frame buffer.)
  //

  ogl_helper.fbprintf
    ("Eye location: [%.1f, %.1f, %.1f]  "
     "(%suse arrow and page keys to move).\n",
     eye_location.x, eye_location.y, eye_location.z,
     opt_move_light ? "press 'e' then " : "" );

  ogl_helper.fbprintf
    ("Light location: [%.1f, %.1f, %.1f]  "
     "(%suse arrow and page keys to move).\n",
     opt_light_location.x, opt_light_location.y, opt_light_location.z,
     opt_move_light ? "" : "press 'l' then ");

  ogl_helper.fbprintf
    ("Eye direction: [%.2f, %.2f, %.2f]  "
     "(use 'Home', 'End', 'Del', 'Insert' keys to turn).\n",
     eye_direction.x, eye_direction.y, eye_direction.z);

  glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,1);
  glLightfv(GL_LIGHT0, GL_POSITION, opt_light_location);

  const float light_intensity[4] =
    {opt_light_intensity, opt_light_intensity, opt_light_intensity, 1.0};
  const float light_off[4] = {0,0,0,0};
  const float light_dim[4] = {0.1,0.1,0.1,1};

  glLightModelfv(GL_LIGHT_MODEL_AMBIENT, &light_dim[0]);

  glLightfv(GL_LIGHT0, GL_DIFFUSE, &light_intensity[0]);
  glLightfv(GL_LIGHT0, GL_AMBIENT, &light_off[0]);

  glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION, 0.3);
  glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 0);
  glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, 0.25);

  glEnable(GL_LIGHT0);
  glEnable(GL_LIGHTING);

  glColorMaterial(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE);
  glEnable(GL_COLOR_MATERIAL);

  const char* const v_buffering_str[] =
    { "Send transformed arrows using glMultiDrawArrays",
      "Send transformed arrows using glDrawArrays",
      "Send same arrow, change transforms" };

  ogl_helper.fbprintf
    ("Vertex specification:  %s  (v to change)\n",
     v_buffering_str[opt_v_buffering]);
  ogl_helper.fbprintf
    ("Arrow data provided from %s\n",
     opt_buffer_objects ? "buffer objects" : "client arrays");
  if ( opt_pause )
    ogl_helper.fbprintf("** PAUSED ** (Press 'p' to unpause.)\n");
  else
    ogl_helper.fbprintf("Press 'p' to pause.\n");
  pVariable_Control_Elt* const cvar = variable_control.current;
  ogl_helper.fbprintf("VAR %s = %.3f\n",cvar->name,cvar->var[0]);

  glEnable(GL_DEPTH_TEST);
  glDepthFunc(GL_LESS);

  // Insert marker (green tetrahedron) to show light location.
  //
  insert_tetrahedron(opt_light_location,0.05);

  float z = -1;
  pColor color_purple(0x580da6);  // LSU Spirit Purple
  pColor color_gold(0xf9b237);    // LSU Spirit Gold

  //
  // Get Tube Specifications
  //

  const float ampl = 0.1;
  const int pattern_width = 3 * int( opt_pattern_width * 0.33333333 );
  const int pattern_levels = int( opt_pattern_levels + 0.5 );
  const int num_vtx = pattern_levels * pattern_width;

  const double cycles_per_second = 0.2;
  const double phase_n =
    ( time_wall_fp() - time_app_start ) * cycles_per_second;
  const double phase = phase_n * 2.0 * M_PI;
  const float wavelength_z = 4.8;
  const float radians_per_z = 2.0 * M_PI / wavelength_z;

  int num_bytes = 0;

  glEnable(GL_NORMALIZE);
  glEnable(GL_RESCALE_NORMAL);

  // If number of vertices has changed re-allocate our storage
  // (coor_buffer, norm_buffer) and MTrig object.
  //
  if ( num_vtx_alloc != num_vtx )
    {
      if ( tube_info )
        {
          delete tube_info;
          delete index_array;  index_array = NULL;
        }
      tube_info = new Vertex_Info[num_vtx];
      tarray.init( pattern_width * 2 * 2 );
      num_vtx_alloc = num_vtx;
    }

  // Outer Loop: z axis (down axis of tube).
  //
  if ( !opt_pause )
    {
      Vertex_Info *vip = tube_info;
      const float ep = 1.00001;
      const float two_pi = 2 * M_PI;
      const float delta_theta = ep * two_pi / pattern_width;
      const float delta_theta_half = 0.5 * delta_theta;

      for ( int i = 0; i < pattern_levels; i++ )
        {
          const float angle_z = phase + radians_per_z * z;
          const float cos_z = cos(angle_z);
          const float theta_0 = i & 1 ? delta_theta_half : 0;
          const float r = r0 * ( 1 + ampl * sin( angle_z ) );

          for ( float theta = theta_0;  theta < two_pi;
                theta += delta_theta )
            {
              const float cos_theta = tarray.cos(theta);
              const float sin_theta = tarray.sin(theta);

              Vertex_Info* const vi = vip++;

              vi->pos = pCoor(x_shift + r * cos_theta, r * sin_theta, z);
              vi->nor = pVect(-cos_theta,-sin_theta,cos_z);
            }

          z -= pattern_pitch_z;
        }
    }

  if ( !index_array )
    {
      index_array = new int[num_vtx*6];
      int *iptr = index_array;
      for ( int i = 0; i < pattern_levels - 1; i++ )
        {
          int vtx_a = pattern_width * ( i & 1 ? i + 1 : i );
          int vtx_b = pattern_width * ( i & 1 ? i : i + 1 );
          int *irevptr = iptr;

          for ( int j = 0; j < pattern_width; j++ )
            {
              const bool last = j == pattern_width - 1;
              *iptr++ = vtx_a;  *iptr++ = vtx_b;
              vtx_a++;  if ( last ) vtx_a -= pattern_width;
              *iptr++ = vtx_a;
              *iptr++ = vtx_a;  *iptr++ = vtx_b;
              vtx_b++;  if ( last ) vtx_b -= pattern_width;
              *iptr++ = vtx_b;
            }
          if ( i & 1 ) continue;

          // Flip these triangles so all have normals in same direction.
          //
          while ( irevptr < iptr )
            {
              const int t = irevptr[0];
              irevptr[0] = irevptr[2];  irevptr[2] = t;
              irevptr += 3;
            }
        }
      num_indices = iptr - index_array;
    }
  pError_Check();

  // Render Tube
  //
  glColor3fv( color_gold );
  glNormalPointer(GL_FLOAT,sizeof(Vertex_Info),&tube_info[0].nor);
  glVertexPointer(3,GL_FLOAT,sizeof(Vertex_Info),&tube_info[0].pos);
  glEnableClientState(GL_NORMAL_ARRAY);
  glEnableClientState(GL_VERTEX_ARRAY);
  glDrawElements(GL_TRIANGLES,num_indices,GL_UNSIGNED_INT,index_array);
  glDisableClientState(GL_NORMAL_ARRAY);
  glDisableClientState(GL_VERTEX_ARRAY);
  pError_Check();
  num_bytes += ( sizeof(float) * 6 ) * num_indices;


  /// SOLUTION BELOW
  //
  const bool per_arrow_transform = opt_v_buffering == 2;
  const double two_pi = 2 * M_PI;
  const int sides = 3 + arrow_detail;
  const float length = 0.5 * r0;
  const float base_radius = 0.1 * r0 / two_pi;
  const double delta_theta = 0.99999 * two_pi / sides;
  glColor3fv(color_purple);

  // Allocate or Re-allocate Arrow Storage, if necessary.
  //
  if ( arrow_detail != arrow_detail_alloc || arrow_vtx != num_vtx )
    {
      tarray_arrow.init( 2 * ( sides + 1 ) );
      arrow_detail_alloc = arrow_detail;
      if ( arrow_info )
        {
          free(arrow_info); free(arrow_count); free(arrow_first);
          free(arrow_transforms);
        }
      arrow_vtx_count = num_vtx * ( sides + 2 );
      arrow_info = new Vertex_Info[ arrow_vtx_count ];
      arrow_first = new int[num_vtx];
      arrow_count = new int[num_vtx];
      arrow_transforms = new pMatrix[num_vtx];
      arrow_info_stale = true;
      arrow_vtx = num_vtx;
    }

  // Update array of arrows, if necessary.
  //
  if ( !opt_pause
       || arrow_info_stale
       || arrow_buffer_stale && opt_buffer_objects
       || arrow_info_v_opt != opt_v_buffering )
    {
      Vertex_Info *aiptr = arrow_info;
      arrow_info_stale = false;
      arrow_info_v_opt = opt_v_buffering;
      const int n_vtx = per_arrow_transform ? 1 : num_vtx;
      for ( int i=0; i<n_vtx; i++ )
        {
          Vertex_Info* const vi =
            per_arrow_transform ? &tube_0 : &tube_info[i];
          pCoor base = vi->pos;
          pCoor pointy_part = base + length * vi->nor;
          pVect rad(vi->nor,pVect(0,0,1));
          pVect rad2(vi->nor,rad);
          rad.normalize(); rad *= base_radius;
          rad2.normalize(); rad2 *= base_radius;
          arrow_count[i] = sides + 2;
          arrow_first[i] = aiptr - arrow_info;
          aiptr->nor = vi->nor;
          aiptr->pos = pointy_part;
          aiptr++;
          for ( double theta = 0; theta <= two_pi; theta += delta_theta )
            {
              const float cos_theta = tarray_arrow.cos(theta);
              const float sin_theta = tarray_arrow.sin(theta);
              pVect cone_normal = cos_theta * rad + sin_theta * rad2;
              pCoor c = base + cone_normal;
              aiptr->nor = cone_normal;  // Needs a z-axis component.
              aiptr->pos = c;
              aiptr++;
            }
        }

      if ( per_arrow_transform )
        for ( int i=0; i<num_vtx; i++ )
          {
            Vertex_Info* const vi = &tube_info[i];
            pMatrix_Rotation r(pVect(0,1,0),vi->nor);
            pMatrix_Translate t(vi->pos);
            arrow_transforms[i] = modelview * t * r;
          }

      if ( opt_buffer_objects )
        {
          const int amt = n_vtx * ( sides + 2 ) * sizeof(Vertex_Info);
          glBindBuffer(GL_ARRAY_BUFFER, arrow_info_buffer);
          glBufferData(GL_ARRAY_BUFFER, amt, arrow_info, GL_STATIC_DRAW);
          glBindBuffer(GL_ARRAY_BUFFER, 0);
          num_bytes += amt;
        }
      arrow_buffer_stale = !opt_buffer_objects;
    }

  Vertex_Info* const ai_ptr = opt_buffer_objects ? NULL : arrow_info;

  if ( opt_buffer_objects )
    glBindBuffer(GL_ARRAY_BUFFER, arrow_info_buffer);

  glNormalPointer(GL_FLOAT,sizeof(Vertex_Info),&ai_ptr[0].nor);
  glVertexPointer(3,GL_FLOAT,sizeof(Vertex_Info),&ai_ptr[0].pos);
  glEnableClientState(GL_NORMAL_ARRAY);
  glEnableClientState(GL_VERTEX_ARRAY);

  // arrow_first (arrow array index) and arrow_count (number of vertices).
  //
  num_bytes += ( sizeof(int) * 2 ) * num_vtx;

  // Vertex and normal data.
  //
  if ( !opt_buffer_objects )
    num_bytes += ( sizeof(float) * 6 ) * arrow_vtx_count;

  switch ( opt_v_buffering ) {
  case 0:
    glMultiDrawArrays(GL_TRIANGLE_FAN,arrow_first,arrow_count,num_vtx);
    break;

  case 1:
    glMatrixMode(GL_MODELVIEW);
    for ( int i=0; i<num_vtx; i++ )
      glDrawArrays(GL_TRIANGLE_FAN,arrow_first[i],arrow_count[i]);
    break;

  case 2:
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    for ( int i=0; i<num_vtx; i++ )
      {
        glLoadTransposeMatrixf(arrow_transforms[i]);
        glDrawArrays(GL_TRIANGLE_FAN,arrow_first[0],arrow_count[0]);
      }
    glPopMatrix();
    num_bytes += ( sizeof(float) * 16 ) * num_vtx;
    break;

  default: pError_Msg("Unexpected case.");
  }

  glBindBuffer(GL_ARRAY_BUFFER, 0);

  glDisableClientState(GL_NORMAL_ARRAY);
  glDisableClientState(GL_VERTEX_ARRAY);
  pError_Check();
  //
  /// SOLUTION ABOVE

  frame_timer.work_amt_set(num_bytes);

  // Insert additional triangle.
  //
  {
    pCoor v0( 1.5, 0, -3.2 );
    pCoor v1( 0, 5, -5 );
    pCoor v2( 9, 6, -9 );
    pVect normal(cross(v0,v1,v2));

    glColor3fv( color_purple );

    glBegin(GL_TRIANGLES);

    glNormal3fv(normal);  glVertex3fv(v0);
    glNormal3fv(normal);  glVertex3fv(v1);
    glNormal3fv(normal);  glVertex3fv(v2);

    glEnd();

  }

  glColor3f(0,1,0); // This sets the text color. Don't know why.

  pError_Check();

  frame_timer.frame_end();

  glutSwapBuffers();

}

int
main(int argc, char **argv)
{
  pOpenGL_Helper popengl_helper(argc,argv);
  Tube tube(popengl_helper);

  popengl_helper.rate_set(30);
  popengl_helper.display_cb_set(tube.render_w,&tube);


  return 0;
}