/// LSU EE 7700-1 (Sp 2009), Graphics Processors
//
 /// Texturing Demonstration

// $Id:$

 /// Purpose
//
//   Demonstrate texturing techniques.

 /// More Information
//
//   OpenGL documentation.
//     http://www.ece.lsu.edu/koppel/gp/refs/glspec21.pdf


 /// What Code Does

// The code shows an undulating tube (use p to pause) pierced by a
// purple quadrilateral. After turning on texturing (use m) the course
// syllabus will appear on the quadrilateral and tube.

// Relevant User Interface:

//  m: Change texture application modes. Initially texturing off.
//  a: Change texture magnification filtering options.
//  i: Change texture minification filtering options.
//  b: Turn blending on and off. Affects appearance of syllabus on rectangle.
//  p: Pause/resume.


/// Texturing and this Code

 /// Initialization

 //  Textures loaded from image files by the procedure pTexture_From_PNM.
 //    The procedure sets basic options for the texture, loads the data,
 //    possibly modifies it, and passes it to OpenGL. It returns
 //    a texture id that is used to refer to the texture.

 //  Two textures are loaded, one for the tube and one for the
 //  quadrilateral. (As of this writing they are the same image with
 //  different options.)


 /// Texture Coordinates

 //  Regardless of its size the coordinate space for a texture is from
 //  (0,0), the upper left, to (1,1), the lower right. The symbol s is
 //  used for the horizontal axis (akin to x) and t is used for the
 //  vertical axis (akin to y).

 //  The pTexture_From_PNM procedure sets texturing so that
 //  coordinates outside the valid range wrap, so that both (0.2,0.2)
 //  and (0.2,1.2) refer to the same texture location.

 //  Texture coordinates are one of the attributes specified with a
 //  vertex. The Tube::render routine now collects texture coordinates
 //  for the tube and individually specifies them for the
 //  quadrilateral (which was a triangle in earlier versions). The
 //  texture coordinates are passed as an array using
 //  glTexCoordPointer or individually using commands such as
 //  glTexCoord2f.

 //  For the tube the texture s coordinate is a scaled version of theta
 //  and the t coordinate is a scaled version of z.


 /// Texture Filtering
 //  OpenGL 2.1 3.8.8

 //  The user interface can be used to select minification filtering
 //  (the i key) and magnification filtering (the a key).  The chosen
 //  filter options are passed to OpenGL using a glTexParameterf call
 //  placed before vertices are sent to OpenGL.


 /// Texture Mode (Application Options)
 //  OpenGL 2.1 3.8.13

 //  There are many useful ways of combining a texel with the lighted
 //  color (or other texture's texels). These are selected in part by
 //  specifying a /mode/ in a call to glTexEnvi. Search for
 //  GL_TEXTURE_ENV_MODE and try user interface key m.


 /// Texture Use

 //  Commands such as glTexEnvi operate on the /bound texture/, that
 //  is, the texture specified in the last call to
 //  glBindTexture. (Search for this.)

 //  Texturing (2D) is turned on using glEnable(GL_TEXTURE_2D) and
 //  turned off with a correspondence glDisable.


 /// Blending

 //  The quadrilateral is displayed with blending turned on, meaning,
 //  its pixel values will be blended with any pixels already in the
 //  frame buffer. This blending improves anti-aliased text appearance
 //  by allowing the gray edges of text to blend with colors in back
 //  of the text.

 //  Blending is turned on using glEnable(GL_BLEND) and is set to
 //  blend using the alpha channel of the "source" meaning the textured
 //  quadrilateral.



#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"
#include "image.h"


 ///
 ///  Create and Initialize a Texture Object from a File
 ///
enum { PT_Invert = 1, PT_To_Alpha = 2 };

GLuint
pTexture_From_Image
(char *file_name, int option = 0, int transp = 256 )
{
  P_Image_Read image(file_name,transp);
  if ( !image.image_loaded ) return 0;
  if ( option & PT_To_Alpha ) image.gray_to_alpha();
  if ( option & PT_Invert ) image.color_invert();
  GLuint texture_id;

  // Generate a texture id (name). (This doesn't create a texture object.)
  //
  glGenTextures(1,&texture_id);

  // Create a texture object for texture_id (since one doesn't yet exist).
  //
  glBindTexture(GL_TEXTURE_2D,texture_id);

  // Set some parameters for our new texture object.
  //
  glTexParameteri(GL_TEXTURE_2D,GL_GENERATE_MIPMAP,1);
  glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
  glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);

  // Load data into the texture object.
  //
  glTexImage2D
    (GL_TEXTURE_2D,
     0,                // Level of Detail (0 is base).
     GL_RGBA,          // Internal format to be used for texture.
     image.width, image.height,
     0,                // Border
     image.gl_fmt,     // GL_BGRA: Format of data read by this call.
     image.gl_type,    // GL_UNSIGNED_BYTE: Size of component.
     (void*)image.data);
  pError_Check();

  return texture_id;
}


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


#define ENUM_LABEL(c) { #c, c }
struct pEnum_Label { const char *label; int value; };
pEnum_Label texture_env_modes[] = {
  { "Texturing Off", 0 },
  ENUM_LABEL(GL_REPLACE),
  ENUM_LABEL(GL_MODULATE),
  ENUM_LABEL(GL_DECAL),   // Blend using alpha value of texture.
  ENUM_LABEL(GL_BLEND),   // Blend using separate alpha.
  ENUM_LABEL(GL_ADD),     // Sum of colors, product of alphas.
  ENUM_LABEL(GL_COMBINE), // Use separate combine function.
  {NULL,0}
};

pEnum_Label texture_min_filters[] = {
  ENUM_LABEL( GL_NEAREST_MIPMAP_NEAREST ),
  ENUM_LABEL( GL_LINEAR_MIPMAP_NEAREST ),
  ENUM_LABEL( GL_NEAREST_MIPMAP_LINEAR ),
  ENUM_LABEL( GL_LINEAR_MIPMAP_LINEAR ),
  {NULL,0}
};

pEnum_Label texture_mag_filters[] = {
  ENUM_LABEL( GL_NEAREST ),
  ENUM_LABEL( GL_LINEAR ),
  {NULL,0}
};

 ///
 /// Tube Object
 ///

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;

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

  float opt_light_intensity;
  pCoor opt_light_location;
  bool opt_pause;
  bool opt_blend;

  double time_last_frame;
  double time_app;

  pCoor* coor_buffer;
  pVect* norm_buffer;
  float* text_buffer;

  int num_coor_alloc;
  MTrig tarray;

  int opt_texture_env_mode;
  int opt_texture_min_filter;
  int opt_texture_mag_filter;

  GLuint texture_id_syllabus;
  GLuint texture_id_image;

};

void
Tube::init()
{
  time_app = 0;
  time_last_frame = 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 = 50;    // Tube depth (z direction.)
  opt_pattern_width = 50;     // Triangle size (circumferential).

  opt_light_intensity = 2;
  opt_light_location.set(( r0 - 0.1 ), 0, -3 );

  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_light_intensity,"Light Intensity");

  coor_buffer = NULL;
  norm_buffer = NULL;
  num_coor_alloc = 0;

  texture_id_syllabus =
    pTexture_From_Image("gp.png",PT_Invert | PT_To_Alpha,true);
  //  texture_id_image = pTexture_From_Image("ee_bdg_to_ur_ed.ppm",false,-1);
  texture_id_image = pTexture_From_Image("gp.png");

  opt_texture_env_mode = 0;
  opt_texture_min_filter = 0;
  opt_texture_mag_filter = 0;
  opt_blend = true;

  opt_pause = false;
}

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

  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_blend = !opt_blend; break;
  case 'l': case 'L': opt_move_light = true; break;
  case 'e': case 'E': opt_move_light = false; break;
  case 'p': case 'P': opt_pause = !opt_pause; break;
  case 'm': opt_texture_env_mode++;
    if ( !texture_env_modes[opt_texture_env_mode].label )
      opt_texture_env_mode = 0;
    break;
  case 'i': opt_texture_min_filter++;
    if ( !texture_min_filters[opt_texture_min_filter].label )
      opt_texture_min_filter = 0;
    break;
  case 'a': opt_texture_mag_filter++;
    if ( !texture_mag_filters[opt_texture_mag_filter].label )
      opt_texture_mag_filter = 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();
    }


  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);
  glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 1);
  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);

  pVariable_Control_Elt* const cvar = variable_control.current;
  ogl_helper.fbprintf("VAR %s = %.3f  (+/- to adjust)\n",
		      cvar->name,cvar->var[0]);
  ogl_helper.fbprintf("Texture Mode: %s  (m to change)\n",
		      texture_env_modes[opt_texture_env_mode].label);
  ogl_helper.fbprintf("Min Filter: %s  (i to change)\n",
		      texture_min_filters[opt_texture_min_filter].label);
  ogl_helper.fbprintf("Mag Filter: %s  (a to change)\n",
		      texture_mag_filters[opt_texture_mag_filter].label);
  ogl_helper.fbprintf("Blending %s\n", opt_blend ? "ON" : "OFF");
  if ( opt_pause )
    ogl_helper.fbprintf("Animation PAUSED  (p to resume)");
  else
    ogl_helper.fbprintf("Animation RUNNING  (p to pause)");

  glEnable(GL_DEPTH_TEST);
  glDepthFunc(GL_LESS);

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

  //
  // Insert a tessellated tube in the vertex list.
  //

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


  //
  // Compute Tube Specifications
  //

  const int pattern_width = 3 * int( opt_pattern_width * 0.33333333 );
  const int pattern_levels = int( opt_pattern_levels + 0.5 );

  // Number of vertices passed to OpenGL.
  const int vertices_per_ring = 3 * 2 * pattern_width;

  const int num_coor = pattern_levels * vertices_per_ring;

  const double cycles_per_second = 0.2;
  const double now = time_wall_fp();
  const double delta_t = now - time_last_frame;
  time_last_frame = now;
  if ( !opt_pause ) time_app += delta_t;
  const double phase_n = time_app * cycles_per_second;
  const double phase = phase_n * 2.0 * M_PI;

  const float wavelength_z = 2.8;
  const float radians_per_z = 2.0 * M_PI / wavelength_z;

  int num_bytes = 0;

  const float ampl = 0.2;

  glColor3fv( color_gold );

  // If number of vertices has changed re-allocate our storage
  // (coor_buffer, norm_buffer, text_buffer) and MTrig object and also
  // remember that gpu's buffer needs to be updated.

  if ( num_coor_alloc != num_coor )
    {
      if ( coor_buffer )
	{ delete coor_buffer; delete norm_buffer; delete text_buffer; }
      coor_buffer = new pCoor[num_coor];
      norm_buffer = new pVect[num_coor];
      text_buffer = new float[num_coor*2];

      tarray.init( vertices_per_ring * 4 );
      num_coor_alloc = num_coor;
    }

  // Outer Loop: z axis (down axis of tube).
  //
  {
    pCoor* cptr = coor_buffer;
    pVect* nptr = norm_buffer;
    float* tptr = text_buffer;

    const float delta_theta = M_PI / pattern_width;

    for ( int i = 0; i < pattern_levels; i++ )
      {
	const float next_z = z - pattern_pitch_z;
	const float last_z = z + pattern_pitch_z;
	float theta = i & 1 ? delta_theta : 0;
	const float angle_z = phase + radians_per_z * z;
	const float angle_nz = phase + radians_per_z * next_z;
	const float angle_lz = phase + radians_per_z * last_z;
	const float r = r0 * ( 1 + ampl * sin( angle_z ) );
	const float rnz = r0 * ( 1 + ampl * sin( angle_nz ) );
	const float rlz = r0 * ( 1 + ampl * sin( angle_lz ) );
	const float cos_z = cos(angle_z);
	const float cos_lz = cos(angle_lz);
	const float cos_nz = cos(angle_nz);
	const float z_scale = -0.125;
	const float th_scale = -1.0 / ( 2 * M_PI );

	// Inner Loop: around circumference of tube.
	//
	while ( theta < 4 * M_PI - delta_theta )
	  {
	    const bool first_round = theta < 2 * M_PI;
	    const float z1 = first_round ? next_z : last_z;
	    const float rz1 = first_round ? rnz : rlz;
	    const float cos_z1 = first_round ? cos_nz : cos_lz;

	    float cos_theta = tarray.cos(theta);  // Reassigned
	    float sin_theta = tarray.sin(theta);  // Reassigned

	    pCoor v0(x_shift + r * cos_theta, r * sin_theta, z);
	    pVect v0_normal(-cos_theta,-sin_theta,cos_z);

	    *tptr++ = theta * th_scale; *tptr++ = z * z_scale;

	    theta += delta_theta;

	    cos_theta = tarray.cos(theta);
	    sin_theta = tarray.sin(theta);

	    pCoor v1(x_shift + rz1 * cos_theta, rz1 * sin_theta, z1);
	    pVect v1_normal(-cos_theta,-sin_theta,cos_z1);

	    *tptr++ = theta * th_scale; *tptr++ = z1 * z_scale;

	    theta += delta_theta;

	    cos_theta = tarray.cos(theta);
	    sin_theta = tarray.sin(theta);

	    pCoor v2(x_shift + r * cos_theta, r * sin_theta, z);
	    pVect v2_normal(-cos_theta,-sin_theta,cos_z);

	    *tptr++ = theta * th_scale; *tptr++ = z * z_scale;

	    *nptr++ = v0_normal; *nptr++ = v1_normal; *nptr++ = v2_normal;

	    *cptr++ = v0; *cptr++ = v1; *cptr++ = v2;

	  }
	z = next_z;
      }
  }

  if ( opt_texture_env_mode )
    {
      // Make the Image texture the current 2D texture.
      //
      glBindTexture(GL_TEXTURE_2D,texture_id_image);

      // Set parameters in texture object (texture_id_image).
      //
      glTexParameterf
        (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
         texture_min_filters[opt_texture_min_filter].value);

      glTexParameterf
        (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
         texture_mag_filters[opt_texture_mag_filter].value);

      // Set parameter for texture unit.
      //
      glTexEnvi
        (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE,
         texture_env_modes[opt_texture_env_mode].value);

      glEnable(GL_TEXTURE_2D);
    }

  // Render Tube
  //
  // Note that now texture coordinates are also sent.
  //
  glNormalPointer(GL_FLOAT,0,norm_buffer);
  glVertexPointer(3,GL_FLOAT,sizeof(pCoor),coor_buffer);
  glTexCoordPointer(2,GL_FLOAT,0,text_buffer);
  glEnableClientState(GL_NORMAL_ARRAY);
  glEnableClientState(GL_VERTEX_ARRAY);
  glEnableClientState(GL_TEXTURE_COORD_ARRAY);
  glDrawArrays(GL_TRIANGLES,0,num_coor);
  glDisableClientState(GL_NORMAL_ARRAY);
  glDisableClientState(GL_VERTEX_ARRAY);
  glDisableClientState(GL_TEXTURE_COORD_ARRAY);
  num_bytes += sizeof(float) * 6 * num_coor;

  frame_timer.work_amt_set(num_bytes);

  // Insert additional quadrilateral.
  //
  {
    // Position of vertices:
    //
    //       v3        v2
    //
    //       v0        v1
    //
    pCoor v0( -1, -1, -5 );
    pCoor v1(  5, -1, -3 );
    pCoor v2(  5,  6, -1 );
    pCoor v3( -1,  6, -3 );

    pVect normal(cross(v2,v1,v0));

    glColor3fv( color_purple );
    // Make primitive more sensitive to light because purple is a dark color.
    glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 0.2);
    glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, 0.05);

    if ( opt_texture_env_mode )
      {
        // Make the Syllabus texture the current 2D texture.
        //
	glBindTexture(GL_TEXTURE_2D,texture_id_syllabus);

        // Enable Blending
        //
        // Blending helps with anti-aliasing the syllabus text.
        // (Blending is not a texture-specific feature.)
        // When blending is off an alpha test is used to make the syllabus
        // "paper" transparent.
        //
        if ( opt_blend )
          glEnable(GL_BLEND);
        else
          glEnable(GL_ALPHA_TEST);
        glAlphaFunc(GL_GREATER,0.1);
	glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
      }

    glBegin(GL_QUADS);

    // Note: Texture coordinate (0,0) is upper-left.
    //
    glTexCoord2f(0,1); glNormal3fv(normal);  glVertex3fv(v0);
    glTexCoord2f(1,1); glNormal3fv(normal);  glVertex3fv(v1);
    glTexCoord2f(1,0); glNormal3fv(normal);  glVertex3fv(v2);
    glTexCoord2f(0,0); glNormal3fv(normal);  glVertex3fv(v3);

    glEnd();

    glDisable(GL_TEXTURE_2D);
    glDisable(GL_BLEND);
    glDisable(GL_ALPHA_TEST);

  }

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