/// LSU EE 7700-2 (Sp 08), Graphics Processors
//
/// Homework 3 -- SOLUTION

 /// Name:  David Koppelman

// $Id:$

/// Problem 1

// Assignment details are given in the homework handout,
//  http://www.ece.lsu.edu/koppel/gp/2008/hw03.pdf
// For the solution one probably needs to refer to the OpenGL spec:
//  http://www.ece.lsu.edu/koppel/gp/refs/glspec21.pdf

// Very Briefly: Make the "t" command described below work without
// breaking "v" command:

// Some User Interface Commands
//
//  v:  Vertex Buffering
//      Change the way vertices are passed to OpenGL.
//      See uses of opt_v_buffering.
//      Note: The "Buffer Object" (2) setting won't work on some GPUs.
//
//  r:  Vertex Re-computation
//      Force unnecessary recomputation of vertices, perhaps to help
//      in finding performance bottlenecks.
//
//  t:  Triangle Specification: Individual or Strips
//      Changes variable opt_use_triangle_strips.
//      In homework template individual triangles are used
//      regardless of opt_use_triangle_strips value. In solved
//      assignment it should actually change how triangles are specified.


 /// Solution Hints

// This code is admittedly a bit turgid.

// Start by examining the code near "  switch ( opt_v_buffering ) ",
// this is where GL_TRIANGLE_STRIP will have to be used.

// Next examine the loop nest that builds the vertex array, after
// comment " Outer Loop: z axis (down axis of tube)." That code will
// have to be modified so that the vertex array (coor_buffer) is
// appropriate for triangle strips rather than individual triangles.

// A correct solution is possible by only:

//  o  A modification to the value of vertices_per_ring (it will depend
//     on opt_use_triangle_strips).
//     Just one line here.

//  o  A single added line to the loop nest (after comment "Outer Loop").

//  o  Most code needs to be added to the switch statement.

 /// SOLUTION

 // Note: This solution is not exactly correct because the appearance
 // does change slightly. However, there is enough similarity so that
 // performance comparisons are valid.

 // Search for the word SOLUTION to find the changes or see
 // the diff below:

#if 0
 
@@ -395,7 +396,6 @@ Tube::render()
   const int pattern_width = 3 * int( opt_pattern_width * 0.33333333 );
   const int pattern_levels = int( opt_pattern_levels + 0.5 );
 
-  //  const int vertices_per_ring = 3 * 2 * pattern_width;
   // SOLUTION
   // Number of vertices passed to OpenGL.
   const int vertices_per_ring =
@@ -554,8 +554,27 @@ Tube::render()
     // Individual Vertex Specification (Buffering)
     //
     {
+      int idx_v = phase_v;
       const int end_v = phase_v + num_v;
 
+      // SOLUTION
+      if ( opt_use_triangle_strips )
+	{
+	  while ( idx_v < end_v )
+	    {
+	      glBegin(GL_TRIANGLE_STRIP);
+	      for ( int c=0; c<vertices_per_ring; c++ )
+		{
+		  glNormal3fv(norm_buffer[idx_v]);
+		  glVertex3fv(coor_buffer[idx_v]);
+		  num_bytes += sizeof(float) * 6;
+		  idx_v++;
+		}
+	      glEnd();
+	    }
+	  break;
+	}
+
       glBegin(GL_TRIANGLES);
       for ( int i=phase_v; i<end_v; i++ )
 	{
@@ -577,7 +596,19 @@ Tube::render()
       glVertexPointer(3,GL_FLOAT,sizeof(pCoor),coor_buffer);
       glEnableClientState(GL_NORMAL_ARRAY);
       glEnableClientState(GL_VERTEX_ARRAY);
-      glDrawArrays(GL_TRIANGLES,phase_v,num_v);
+
+      // SOLUTION
+      if ( opt_use_triangle_strips )
+	{
+	  const int end_v = phase_v + num_v;
+	  for( int idx_v = phase_v; idx_v < end_v; idx_v += vertices_per_ring )
+	    glDrawArrays(GL_TRIANGLE_STRIP,idx_v,vertices_per_ring);
+	}
+      else
+	{
+	  glDrawArrays(GL_TRIANGLES,phase_v,num_v);
+	}
+
       glDisableClientState(GL_NORMAL_ARRAY);
       glDisableClientState(GL_VERTEX_ARRAY);
       num_bytes += sizeof(float) * 6 * num_v;
@@ -599,7 +630,21 @@ Tube::render()
       glBindBuffer(GL_ARRAY_BUFFER,0);
       glEnableClientState(GL_NORMAL_ARRAY);
       glEnableClientState(GL_VERTEX_ARRAY);
-      glDrawArrays(GL_TRIANGLES,phase_v,num_v);
+
+      // SOLUTION
+      if ( opt_use_triangle_strips )
+	{
+	  const int end_v = phase_v + num_v;
+	  for( int idx_v = phase_v; idx_v < end_v; idx_v += vertices_per_ring )
+	    glDrawArrays(GL_TRIANGLE_STRIP,idx_v,vertices_per_ring);
+	}
+      else
+	{
+	  glDrawArrays(GL_TRIANGLES,phase_v,num_v);
+	}
+
       glDisableClientState(GL_NORMAL_ARRAY);
       glDisableClientState(GL_VERTEX_ARRAY);
     }

#endif




/// Problem 2,...

// See assignment for Problems 2, ...
//  http://www.ece.lsu.edu/koppel/gp/2008/hw03.pdf
// See solution for solutions of Problems 2, ...
//  http://www.ece.lsu.edu/koppel/gp/2008/hw03_sol.pdf




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


//
 /// 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 render();
private:
  pOpenGL_Helper &ogl_helper;
  pVariable_Control variable_control;
  pFrame_Timer frame_timer;

  pVect to_eye_vector;

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

  float opt_light_intensity;
  int opt_v_buffering;
  bool opt_recompute;
  pCoor opt_light_location;

  bool buffer_data_0;

  double time_app_start;

  pCoor* coor_buffer;
  pVect* norm_buffer;

  int num_coor_alloc;
  MTrig tarray;

  GLuint gpu_coor_buffer;
  GLuint gpu_norm_buffer;

  bool opt_use_triangle_strips;
  bool opt_animation;           // If true tube undulates.
  bool use_triangle_strips_alloc;

};

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 = 50;    // Tube depth (z direction.)
  opt_pattern_width = 200;    // Number of triangles along circumference.

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

  to_eye_vector.set(-1,-0.5,-3);

  // Arrange that variables below can be modified from the keyboard.
  //
  variable_control.insert(opt_light_intensity,"Light Intensity");
  variable_control.insert(opt_pattern_levels,"Pattern Levels");
  variable_control.insert(to_eye_vector.x,"Viewer X");

  buffer_data_0 = false;
  coor_buffer = NULL;
  norm_buffer = NULL;
  num_coor_alloc = 0;

  // Get names (just names) for GL-managed buffers.
  //
  glGenBuffers(1,&gpu_norm_buffer);
  glGenBuffers(1,&gpu_coor_buffer);

  use_triangle_strips_alloc = opt_use_triangle_strips = false;

  glEnable(GL_NORMALIZE);

}


void
Tube::render()
{
  frame_timer.frame_start();

  ///
  /// Reset Frame and Z Buffers
  ///

  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 Matrices Setup
  ///

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  glTranslatef(to_eye_vector.x,to_eye_vector.y,to_eye_vector.z);

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


  ///
  /// Light Location and Lighting Options
  ///

  // Adjust options based on user input.
  //
  switch ( ogl_helper.keyboard_key ) {
  case FB_KEY_LEFT: opt_light_location.x -= 0.1; break;
  case FB_KEY_RIGHT: opt_light_location.x += 0.1; break;
  case FB_KEY_UP: opt_light_location.y += 0.1; break;
  case FB_KEY_DOWN: opt_light_location.y -= 0.1; break;
  case FB_KEY_PAGE_DOWN: opt_light_location.z += 0.2; break;
  case FB_KEY_PAGE_UP: opt_light_location.z -= 0.2; break;
  case 9: variable_control.switch_var_right(); break;
  case '-':case '_': variable_control.adjust_lower(); break;
  case '+':case '=': variable_control.adjust_higher(); break;
  case 'r':case 'R': opt_recompute = !opt_recompute; break;
  case 'v': case 'V':
    opt_v_buffering++;
    if ( opt_v_buffering == 3 ) opt_v_buffering = 0;
    break;
  case 't': case 'T':
    opt_use_triangle_strips = !opt_use_triangle_strips;
    break;
  default: break;
  }

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

  const char* const v_buffering_str[] =
    { "Individual", "Client Array", "Buffer Object" };

  ogl_helper.fbprintf
    ("Vertex buffering:  %s  (v to change)\n",
     v_buffering_str[opt_v_buffering]);
  ogl_helper.fbprintf
    ("Tube recomputation: %d (r to change).\n",
     opt_recompute);
  ogl_helper.fbprintf
    ("Triangle Specification: %s (t to change)\n",
     opt_use_triangle_strips ? "Triangle Strips" : "Individual Triangles");

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


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

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

  // SOLUTION
  // Number of vertices passed to OpenGL.
  const int vertices_per_ring =
    ( opt_use_triangle_strips ? 2 : 3 * 2 ) * pattern_width;

  const int num_coor = pattern_levels * vertices_per_ring;

  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 double phase_01 = 1.0 - ( phase_n - floor(phase_n) );

  const float wavelength_z = 4.8;
  const float wavelength_rings = wavelength_z / pattern_pitch_z;
  const float radians_per_z = 2.0 * M_PI / wavelength_z;
  const int wavelength_v = int(wavelength_rings * vertices_per_ring + 0.5);

  const int phase_rings = int( phase_01 * wavelength_rings + 0.5 );

  const float phase_z = opt_recompute ? 0 : wavelength_z * phase_01;
  const int phase_v = opt_recompute ? 0 : phase_rings * vertices_per_ring;

  if ( phase_v > wavelength_v || phase_v < 0 ) pError_Exit();

  const int num_v = num_coor - wavelength_v;
  int num_bytes = 0;

  const float ampl = 0.4;

  glColor3fv( color_gold );

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

  if ( num_coor_alloc != num_coor
       || use_triangle_strips_alloc != opt_use_triangle_strips )
    {
      if ( coor_buffer ) { delete coor_buffer; delete norm_buffer; }
      coor_buffer = new pCoor[num_coor];
      norm_buffer = new pVect[num_coor];
      tarray.init( vertices_per_ring * 4 );
      num_coor_alloc = num_coor;
      use_triangle_strips_alloc = opt_use_triangle_strips;
      buffer_data_0 = false;  // Remember that GPU buffer needs updating.
    }

  pCoor* cptr = coor_buffer;
  pVect* nptr = norm_buffer;

  // Outer Loop: z axis (down axis of tube).
  //
  if ( opt_recompute || !buffer_data_0 )
    {
      const float phase_use = opt_recompute ? phase : 0;
      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_use + radians_per_z * z;
	  const float angle_nz = phase_use + radians_per_z * next_z;
	  const float angle_lz = phase_use + 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);

	  pCoor* const cptr_stop = cptr + vertices_per_ring;

	  // Inner Loop: around circumference of tube.
	  //
	  while ( cptr < cptr_stop )
	    {
	      // This loop iterates for two trips around the ring,
	      // on the first trip first_round is true and triangles point
	      // away from the viewer, on the second round they point towards
	      // the viewer.

	      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

	      // First vertex of triangle.
	      //
	      *cptr++ = pCoor(x_shift + r * cos_theta, r * sin_theta, z);
	      *nptr++ = pVect(-cos_theta,-sin_theta,cos_z);

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

	      // Second vertex of triangle.
	      //
	      *cptr++ = pCoor(x_shift + rz1 * cos_theta, rz1 * sin_theta, z1);
	      *nptr++ = pVect(-cos_theta,-sin_theta,cos_z1);

	      theta += delta_theta;

	      // SOLUTION
	      if ( opt_use_triangle_strips ) continue;

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

	      // Third vertex of triangle.
	      //
	      *cptr++ = pCoor(x_shift + r * cos_theta, r * sin_theta, z);
	      *nptr++ = pVect(-cos_theta,-sin_theta,cos_z);

	    }
	  z = next_z;
	}

      //
      // Write vertices to buffer object, if necessary.
      //
      if ( !opt_recompute || opt_v_buffering == 2 )
	{
	  const int v_bytes = num_coor * sizeof(pVect);
	  const int c_bytes = num_coor * sizeof(pCoor);
	  glBindBuffer(GL_ARRAY_BUFFER, gpu_norm_buffer);
	  glBufferData
	    (GL_ARRAY_BUFFER, v_bytes, norm_buffer, GL_STATIC_DRAW);
	  glBindBuffer(GL_ARRAY_BUFFER, gpu_coor_buffer);
	  glBufferData
	    (GL_ARRAY_BUFFER, c_bytes, coor_buffer, GL_STATIC_DRAW);
	  glBindBuffer(GL_ARRAY_BUFFER, 0);
	  num_bytes += v_bytes + c_bytes;
	}

      buffer_data_0 = !opt_recompute;
    }


  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  glTranslatef(0,0,phase_z);

  //
  // Specify Vertices to GL in one of Three Ways
  //

  switch ( opt_v_buffering ) {

  case 0:
    // Individual Vertex Specification (Buffering)
    //
    {
      int idx_v = phase_v;
      const int end_v = phase_v + num_v;

      // SOLUTION
      if ( opt_use_triangle_strips )
	{
	  while ( idx_v < end_v )
	    {
	      glBegin(GL_TRIANGLE_STRIP);
	      for ( int c=0; c<vertices_per_ring; c++ )
		{
		  glNormal3fv(norm_buffer[idx_v]);
		  glVertex3fv(coor_buffer[idx_v]);
		  num_bytes += sizeof(float) * 6;
		  idx_v++;
		}
	      glEnd();
	    }
	  break;
	}

      glBegin(GL_TRIANGLES);
      for ( int i=phase_v; i<end_v; i++ )
	{
	  glNormal3fv(norm_buffer[i]);
	  glVertex3fv(coor_buffer[i]);
	  num_bytes += sizeof(float) * 6;
	}
      glEnd();

    }
    break;

  case 1:
    // Client Array Vertex Buffering
    //
    // Vertices given to OpenGL as a single array each time this code reached.
    {
      glNormalPointer(GL_FLOAT,0,norm_buffer);
      glVertexPointer(3,GL_FLOAT,sizeof(pCoor),coor_buffer);
      glEnableClientState(GL_NORMAL_ARRAY);
      glEnableClientState(GL_VERTEX_ARRAY);

      // SOLUTION
      if ( opt_use_triangle_strips )
	{
	  const int end_v = phase_v + num_v;
	  for( int idx_v = phase_v; idx_v < end_v; idx_v += vertices_per_ring )
	    glDrawArrays(GL_TRIANGLE_STRIP,idx_v,vertices_per_ring);
	}
      else
	{
	  glDrawArrays(GL_TRIANGLES,phase_v,num_v);
	}

      glDisableClientState(GL_NORMAL_ARRAY);
      glDisableClientState(GL_VERTEX_ARRAY);
      num_bytes += sizeof(float) * 6 * num_v;
    }
    break;

  case 2:
    {
      // Buffer Object Vertex Buffering
      //
      // Vertices given to OpenGL earlier (using a buffer object, see
      // glBufferData above). Hopefully the vertices are now on the
      // GPU. Code below merely refers to that data.
      //
      glBindBuffer(GL_ARRAY_BUFFER,gpu_coor_buffer);
      glVertexPointer(3,GL_FLOAT,sizeof(pCoor),NULL);
      glBindBuffer(GL_ARRAY_BUFFER,gpu_norm_buffer);
      glNormalPointer(GL_FLOAT,0,NULL);
      glBindBuffer(GL_ARRAY_BUFFER,0);
      glEnableClientState(GL_NORMAL_ARRAY);
      glEnableClientState(GL_VERTEX_ARRAY);

      // SOLUTION
      if ( opt_use_triangle_strips )
	{
	  const int end_v = phase_v + num_v;
	  for( int idx_v = phase_v; idx_v < end_v; idx_v += vertices_per_ring )
	    glDrawArrays(GL_TRIANGLE_STRIP,idx_v,vertices_per_ring);
	}
      else
	{
	  glDrawArrays(GL_TRIANGLES,phase_v,num_v);
	}

      glDisableClientState(GL_NORMAL_ARRAY);
      glDisableClientState(GL_VERTEX_ARRAY);
    }
    break;
  }

  glPopMatrix();

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