```/// LSU EE 4702-1 (Fall 2012), GPU Programming
//

/// Homework 3 -- SOLUTION
//
// Assignment in: http://www.ece.lsu.edu/koppel/gpup/2012/hw03.pdf

///  Note: Requires OpenGL 4.3

///  Keyboard Commands
//
/// Object (Eye, Light, Ball) Location or Push
//   Arrows, Page Up, Page Down
//   Will move object or push ball, depending on mode:
//   'e': Move eye.
//   'l': Move light.
//
/// 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.

/// Simulation Options
//  (Also see variables below.)
//
//  's'    Switch between different shaders in forward direction.
//  'S'    Switch between different shaders in reverse direction.
//  'F11'  Change size of text.
//  'F12'  Write screenshot to file.

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

//  'Tab' Cycle to next variable.
//  '`'   Cycle to previous variable.
//  '+'   Increase variable value.
//  '-'   Decrease variable value.
//
//  -- Documented Variables --
//
//     Door Angle
//     - Angle of doors in surface of wire.
//       Behavior when Problem 3 is correctly solved:
//       When zero the surface is flat, when Pi/2 the doors point straight out.
//
//     Segs Per Helix Rev
//     - The number of segments in one revolution of helix.
//       A smaller number means fewer primitives.
//
//     Segs Per Wire Rev
//     - The number of segments in 1 revolution around wire.
//       A smaller number means fewer primitives.
//
//     Light Intensity
//     - The light intensity.
//

#define GL_GLEXT_PROTOTYPES
#define GLX_GLXEXT_PROTOTYPES

#define GL_GLEXT_LEGACY
#include <GL/gl.h>
#include <GL/glx.h>
#include <GL/glext.h>
#include <GL/glxext.h>

// NVIDIA has not yet updated their include files. :-(
#endif

#include <GL/glu.h>
#include <GL/freeglut.h>

// Include files provided for this course.
//
#include <gp/util.h>
#include <gp/glextfuncs.h>
#include <gp/coord.h>
#include <gp/pstring.h>
#include <gp/misc.h>
#include <gp/gl-buffer.h>
#include <gp/texture-util.h>
#include "shapes.h"

// Define storage buffer binding indices and attribute locations.
//
#define UNIF_IDX_BULDGE_LOC 0
#define UNIF_IDX_BULDGE_DIST_THRESH 1
#define ATTR_IDX_HELIX_INDICES 1
#define SB_COORD 1

class World {
public:
World(pOpenGL_Helper &fb):ogl_helper(fb){init();}
void init();
static void render_w(void *moi){ ((World*)moi)->render(); }
void render();
void cb_keyboard();
void modelview_update();

// Class providing utilities, such as showing text.
//
pOpenGL_Helper& ogl_helper;

// Class for easy keyboard control of variables.
//
pVariable_Control variable_control;

// Class for showing frame timing.
//
pFrame_Timer frame_timer;

float opt_door_angle;

pCoor light_location;
float opt_light_intensity;

pCoor helix_location;
int seg_per_helix_revolution;
int seg_per_wire_revolution;

bool coords_stale;
bool buffer_objects_stale;

PStack<int> helix_indices;
GLuint helix_indices_bo;

// Coordinates of helix. (Helix runs through center of wire.)
//
PStack<pCoor> helix_coords;
GLuint helix_coords_bo;
int helix_coords_size;

PStack<int> wire_surface_indices;
GLuint wire_surface_indices_bo;
int wire_surface_indices_size;

// Wire normals.
//
PStack<pVect> helix_normals;
GLuint helix_normals_bo;

int helix_indices_size;

enum { MI_Eye, MI_Light, MI_Ball, MI_Ball_V, MI_COUNT } opt_move_item;

pCoor eye_location;
pVect eye_direction;
pMatrix modelview;

GLuint texture_id_syllabus;
};

void
World::init()
{
coords_stale = true;

opt_door_angle = 0.0;
variable_control.insert_linear(opt_door_angle,"Door Angle",M_PI/40.0);

seg_per_helix_revolution = 80;
seg_per_wire_revolution = 20;
variable_control.insert(seg_per_helix_revolution,"Seg Per Helix Rev");
variable_control.insert(seg_per_wire_revolution,"Seg Per Wire Rev");

buffer_objects_stale = true;
helix_normals_bo = 0;
helix_coords_bo = 0;
wire_surface_indices_bo = 0;

eye_location = pCoor(2.6,5.7,3.8);
eye_direction = pVect(0,0,-1);

opt_light_intensity = 1.5;
light_location = pCoor(12.2,4.0,6.9);

helix_location = pCoor(0,0,-5);

//  variable_control.insert(opt_light_intensity,"Light Intensity");

opt_move_item = MI_Eye;

texture_id_syllabus = pBuild_Texture_File("hw03.png",false,255);

// Declared like a programmable shader, but used for fixed-functionality.
//

const char* const file = "hw3-shdr-sol.cc";

(file,           // File holding shader program.
"vs_main_helix();",      // Name of vertex shader main routine.
"gs_main_helix();",
"fs_main_phong();"       // Name of fragment shader main routine.
);

(file,  // File holding shader program.
"vs_main_helix();",      // Name of vertex shader main routine.
"gs_main_helix2();",
"fs_main_phong();"       // Name of fragment shader main routine.
);

modelview_update();
}

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

void
World::render()
{
// This routine called whenever window needs to be updated.

// Get any waiting keyboard commands.
//
cb_keyboard();

// Start a timer object used for tuning this code.
//
frame_timer.frame_start();

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

glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);

ogl_helper.fbprintf("%s\n",frame_timer.frame_rate_text_get());

ogl_helper.fbprintf
("Eye location: [%5.1f, %5.1f, %5.1f]  "
"Eye direction: [%+.2f, %+.2f, %+.2f]\n",
eye_location.x, eye_location.y, eye_location.z,
eye_direction.x, eye_direction.y, eye_direction.z);

pVariable_Control_Elt* const cvar = variable_control.current;
ogl_helper.fbprintf("VAR %s = %.5f  (TAB or '`' to change, +/- to adjust)\n",
cvar->name,cvar->get_val());

ogl_helper.fbprintf
("Light location: [%5.1f, %5.1f, %5.1f]  "
"Helix Location[%5.1f, %5.1f, %5.1f]\n",
light_location.x, light_location.y, light_location.z,
helix_location.x, helix_location.y, helix_location.z
);

ogl_helper.fbprintf("Active Shader Program: %s  (s TO CHANGE)\n",

ogl_helper.fbprintf
("Programmable GPU API: %savailable.  GPU Code: %s\n",

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

glMatrixMode(GL_PROJECTION);
// Frustum: left, right, bottom, top, near, far
glFrustum(-.8,.8,-.8/aspect,.8/aspect,1,5000);

glEnable(GL_LIGHTING);

const pColor white(1,1,1);
const pColor red(1,0,0);
const pColor blue(0,0,1);
const pColor lsu_spirit_purple(0x580da6);
const pColor lsu_spirit_gold(0xf9b237);

glEnable(GL_LIGHT0);
glLightfv(GL_LIGHT0, GL_POSITION, light_location);

glLightfv(GL_LIGHT0, GL_DIFFUSE, white * opt_light_intensity);

// Set lighting parameters for when shader is not used, as for the
// big triangle.
//
glEnable(GL_COLOR_MATERIAL);
glColorMaterial(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE);
glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,1);

pError_Check();

// Set parameters that apply to a texture (texture_id_syllabus).
//
glTexParameteri
(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
glTexParameteri
(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );

// Set parameter for texture unit.
//
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

sp_fixed->use();

///
/// Paint Single Triangle.
///

pColor color_tri(0x7815b6); // Red, Green, Blue
glColor3fv( color_tri );

glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D,texture_id_syllabus);

//  Indicate type of primitive.
//
glBegin(GL_TRIANGLES);

// Specify vertices for a triangle.
//

pCoor p1( 9.5, -5, -1.2 );
pCoor p2( 0,    5, -3 );
pCoor p3( 9,    6, -7 );
pNorm triangle_normal = cross(p3,p2,p1);

// Specify normal and vertex using course-defined objects pCoor and
// pNorm. OpenGL sees these as pointers to floats.

glNormal3fv(triangle_normal);
glTexCoord2f(0.95,1.0); glVertex3fv(p1);
glTexCoord2f(0.00,0.1); glVertex3fv(p2);
glTexCoord2f(0.90,0.0); glVertex3fv(p3);

glEnd();

glDisable(GL_TEXTURE_2D);

///
/// Construct a Helix
///

if ( coords_stale )
{
// Recompute helix coordinates, etc.

coords_stale = false;
buffer_objects_stale = true;

// Reset existing storage.
helix_coords.reset();
helix_normals.reset();
helix_indices.reset();
wire_surface_indices.reset();

// Number of times helix wraps around.
const int revolutions_per_helix = 6;

const int segments_per_helix =
revolutions_per_helix * seg_per_helix_revolution;

const double delta_eta = 2 * M_PI / seg_per_helix_revolution;
const double delta_y = 4 * wire_radius / seg_per_helix_revolution;
const double delta_theta = 2 * M_PI / seg_per_wire_revolution;

int wire_surface_idx = 0;

for ( int i = 0; i < segments_per_helix; i++ )
{
const bool last_i_iteration = i + 1 == segments_per_helix;

const double eta = i * delta_eta;
i * delta_y,

helix_coords += p0;

pNorm tangent( delta_eta * helix_radius * -sin(eta),
delta_y,
delta_eta * helix_radius * cos(eta) );
pVect b = cross(n0,tangent);

for ( int j = 0; j < seg_per_wire_revolution; j++ )
{
const int idx = wire_surface_idx++;
const double theta = j * delta_theta;

pVect norm0 = cos(theta) * n0 + sin(theta) * b;

helix_normals += norm0.normal();
helix_indices += i;  helix_indices += j;

if ( last_i_iteration ) continue;

// Insert indices for triangle with one vertex on eta.
wire_surface_indices += idx; // This vertex.
wire_surface_indices += idx + seg_per_wire_revolution;
}
}

wire_surface_indices_size = wire_surface_indices.occ();
helix_coords_size = helix_coords.occ();
helix_indices_size = helix_indices.occ();
}

// If necessary, update data in buffer objects.
if ( buffer_objects_stale )
{
buffer_objects_stale = false;

// Generate buffer id (name), if necessary.
//
glGenBuffers(1,&helix_indices_bo);
glGenBuffers(1,&helix_coords_bo);
glGenBuffers(1,&helix_normals_bo);
glGenBuffers(1,&wire_surface_indices_bo);

glBindBuffer(GL_ARRAY_BUFFER, helix_coords_bo);
glBufferData
(GL_ARRAY_BUFFER,
helix_coords_size*4*sizeof(helix_coords[0]),
helix_coords.get_storage(), GL_STATIC_DRAW);

glBindBuffer(GL_ARRAY_BUFFER, helix_indices_bo);
glBufferData
(GL_ARRAY_BUFFER,
2 * helix_indices_size * sizeof(helix_indices[0]),
helix_indices.get_storage(), GL_STATIC_DRAW);

glBindBuffer(GL_ARRAY_BUFFER, helix_normals_bo);
glBufferData
(GL_ARRAY_BUFFER,
helix_indices_size*3*sizeof(helix_normals[0]),
helix_normals.get_storage(), GL_STATIC_DRAW);

glBindBuffer(GL_ARRAY_BUFFER, wire_surface_indices_bo);
glBufferData
(GL_ARRAY_BUFFER,
wire_surface_indices_size*sizeof(wire_surface_indices[0]),
wire_surface_indices.get_storage(),GL_STATIC_DRAW);

// Tell GL that subsequent array pointers refer to host storage.
//
glBindBuffer(GL_ARRAY_BUFFER, 0);

pError_Check();
}

case SP_Fixed: break;
default: ASSERTS( false );
}

///
/// Paint a Helix
///

/// SOLUTION - Problem 3 - Send door angle.
//
glUniform1f(3,opt_door_angle);  GE();

/// SOLUTION - Problem 1 - Send scale factors for textures.
//  Twenty texture copies per revolution.
const float scale_x = - 20.0 / seg_per_helix_revolution;
//  One texture copy around wire.
const float scale_y = 1.0f / seg_per_wire_revolution;
glUniform2f(4,scale_x,scale_y); GE();

glMatrixMode(GL_MODELVIEW);
glPushMatrix();

glTranslatef(helix_location.x,helix_location.y,helix_location.z);
glRotatef(60,0,1,0);

// Specify color. Since it's not an array the same color
// will be used for all vertices, which is what we want.
// If we wanted to vary vertex colors we could have created
// and used a color array.
//
glColor3fv(lsu_spirit_gold);

// Specify buffer object to use for normals.
//
glBindBuffer(GL_ARRAY_BUFFER, helix_normals_bo);
glNormalPointer(GL_FLOAT,0,0);
glEnableClientState(GL_NORMAL_ARRAY);

glBindBuffer(GL_ARRAY_BUFFER, helix_indices_bo);
glEnableVertexAttribArray(ATTR_IDX_HELIX_INDICES);
glVertexAttribIPointer
(ATTR_IDX_HELIX_INDICES,
2, // Two components (as in a 2-element vector).
GL_INT,
0, // Tightly packed.
0);

glBindBuffer(GL_ARRAY_BUFFER, 0); // Avoid surprises.

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, wire_surface_indices_bo);

glDrawElements(GL_TRIANGLE_STRIP,wire_surface_indices_size,GL_UNSIGNED_INT,0);

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

glDisableClientState(GL_NORMAL_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableVertexAttribArray(ATTR_IDX_HELIX_INDICES);

glPopMatrix();

sp_fixed->use();

// Render Marker for Light Source
//
insert_tetrahedron(light_location,0.5);

pError_Check();

glColor3f(0.5,1,0.5);

glDisable(GL_LIGHTING);
glDisable(GL_DEPTH_TEST);
frame_timer.frame_end();

glutSwapBuffers();
}

void
World::cb_keyboard()
{
if ( !ogl_helper.keyboard_key ) return;
pVect user_rot_axis(0,0,0);
const float move_amt = 0.4;

switch ( ogl_helper.keyboard_key ) {
case FB_KEY_LEFT: adjustment.x = -move_amt; break;
case FB_KEY_RIGHT: adjustment.x = move_amt; break;
case FB_KEY_PAGE_UP: adjustment.y = move_amt; break;
case FB_KEY_PAGE_DOWN: adjustment.y = -move_amt; break;
case FB_KEY_DOWN: adjustment.z = move_amt; break;
case FB_KEY_UP: adjustment.z = -move_amt; 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 's':
break;
case 'S':
break;

case 'b': case 'B': opt_move_item = MI_Ball; break;
case 'e': case 'E': opt_move_item = MI_Eye; break;
case 'l': case 'L': opt_move_item = MI_Light; break;

case 9: variable_control.switch_var_right(); break;
case 96: variable_control.switch_var_left(); break; // `, until S-TAB works.
case '-':case '_': variable_control.adjust_lower();  coords_stale=true; break;
case '+':case '=': variable_control.adjust_higher(); coords_stale=true; break;
default: printf("Unknown key, %d\n",ogl_helper.keyboard_key); break;
}

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

// Update eye_location based on keyboard command.
//
{
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);

switch ( opt_move_item ){
case MI_Light: light_location += adjustment; break;
case MI_Eye: eye_location += adjustment; break;
case MI_Ball: helix_location += adjustment; break;
default: break;
}
modelview_update();
}
}

int
main(int argv, char **argc)
{
pOpenGL_Helper popengl_helper(argv,argc);
World world(popengl_helper);

// Specify default frame update rate.
//
// Default rate used if API won't allow updating on each
// display device frame.
//
popengl_helper.rate_set(30);

// Start
//
popengl_helper.display_cb_set(world.render_w,&world);
}
```