```/// LSU EE 4702-1 (Fall 2020), GPU Programming
//
/// Simple Demo of OpenGL

/// Purpose
//
//   Demonstrate simple opengl.

/// What Code Does

// Shows a sphere and a triangle.

#if 0
/// Background
//

/// References
//
// :ogl46: OpenGL Specification Version 4.6
//         http://www.opengl.org/registry/doc/glspec46.compatibility.pdf

/// Coordinate Spaces
//
//  :ogl46: Section 12.1 - Coordinate space descriptions.
//
//  :Def: Object Space
//        Coordinate space of user's choosing.
//        E.g., Flight Simulator: y is feet, x and z are nautical miles.
glVertex3f(1,2,3); // These coordinates are in object space.
//
//  :Def: Eye Space
//        User's eye at origin, monitor facing -z.
//        Lighting calculations may be performed in eye space.
//
//  :Def: Clip Space
//        Visible space (view volume) inside
//         a cube with corners (-1,-1,-1) and (1,1,1)
//        Used by fixed-function hardware to clip.

/// Transformation Matrices
//
//   :ogl46: Section 12.1.1
//
//   Some Important Transformations:
//
//     :Def: Model View
//           Transform from object to eye space.
//
//     :Def: Projection
//           Transform from eye to clip.
//

///  Setting Matrices
//
//  -- First, specify which matrix:
//
glMatrixMode( matrix_name );
//
//       where matrix_name can be
//
glMatrixMode( GL_MODELVIEW );
glMatrixMode( GL_PROJECTION );
//
//      See Section 12.1.1 for the full list.

//  -- Second, modify the matrix using a variety of commands.
//     See Section 12.1.1 for the full list.
//
// Set current matrix to argument, which is in column-major order.
//
//
// Before: transform = some_matrix_x.
// After:  transform = my_matrix_a.
//

//
//
// Before: transform = some_matrix_x.
// After:  transform = Identity.

// Multiply the current transform by a new matrix in column-major order.
//
glMultMatrixf( my_matrix_b );
//
// Before: transform = my_matrix_a.
// After: transform = my_matrix_a * my_matrix_b.

// Multiply the current transform by a new matrix in row-major order.
//
glMultTransposeMatrixf( my_matrix_c );
//
// Before: transform = my_matrix_a * my_matrix_b.
// After: transform = my_matrix_a * my_matrix_b * my_matrix_c.
//
// Note: We are assuming that the layout of my_matrix_b is
// column-major and my_matrix_c is row-major.

// Multiply current transform by a right-hand rotation.
//
glRotatef( angle_deg, axis_x, axis_y, axis_z );

// Multiply current transform by a translation.
//
glTranslatef( tran_x, tran_y, tran_z );

// Multiply current transform by a scale.
//
glScalef( s_x, s_y, s_z );

// Multiply current transform by a perspective (frustum) projection.
//
glFrustum( left, right, bottom, top, near, far );
//
// Typically a perspective transform is used with the projection
// matrix.

//  -- Third, leave things the way they were.
//
//       Often a transformation change is temporary, and after you're
//       done you'd like to put things back the way they were.
//
//       In such cases use push and pop:
//
// Make a copy of the current matrix and push it on the stack.
//
glPushMatrix();

// Replace the current matrix with the top of stack, and pop stack.
//
glPopMatrix();

/// Vertex Attributes
//
//  :Def: Vertex Attribute
//
//  Data associated with a vertex. Also called associated data.
//
//  Attributes include:
//
//    - Vertex coordinate.
glVertex3f(1,2,3);
//
//    - Vertex color. (In the Compatibility Profile)
glColor3fv(color_orange);
//
//    - Vertex "normal". In the Compatibility Profile)
glNormal(1,0,0);
//

/// Attribute Values
//
//   Historically, a vertex attribute was a four-element vector
//   of single-precision (32b) floating point numbers.
//
//   Such a vector could easily represent two important quantities:
//
//     - A homogeneous coordinate: (x,y,z,w)
//     - A color with transparency:  (r,g,b,a)
//
//     Coordinates and colors make up a large fraction of the data
//     needed for 3D graphics.
//
//   Vectors
//
//     Yes, these only need three floats ...
//     ... but they still occupy a "slot" that can hold four floats ...
//     ... because avoiding the wasted space (one float) ...
//     ... is not worth the trouble of having two attribute sizes ...
//     ... especially if one of those sizes is not a power of 2.
//
//  Attribute Values in the year 2019
//
//     Performance is still best when attributes are 4-element vectors.
//     ( In fact, other kinds of values should also have a power-of-2 size.)

/// Vertex Attributes
//
//  :lindholm01: Description of an early GPU, showing origin of attrib limits.
//  https://www.ece.lsu.edu/koppel/gp/srefs/p149-lindholm.pdf
//
//  Older versions of OpenGL defined 16 attributes ..
//  .. each with a specific purpose.
//
//  This limit of 16 attributes is based on hardware constraints.
//    Note: 16 * 4 * sizeof(float) = 256 B.
//    So hardware had to move 256 B of data for each vertex,
//      and have at least that much storage in "shader" performing
//      vertex calculations.
//
//  Current versions of OpenGL define only a few attributes ..
//  .. such as coordinates.
//  The user is free to define others (to be used by user-written code).
//
//  The maximum number of vertex attributes that can be defined is
//  the value of:
GL_MAX_VERTEX_ATTRIBS;

/// glNormal
//
//  A normal is one of the 16 "classic" attributes of a vertex.
//
//  It is always a 3-element vector.
//
//  It is usually set to the geometric normal of the object being
//    modeled, not necessarily the primitive.
//
//  It is used by code computing lighting. (Covered later.)

/// Depth Test (z-Buffering)
//
//   :ogl45: Section 17.3.6
//

#endif

///  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.)
//
//  'p'    Pause simulation. (Press again to resume.)
//  '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.
//
//  VAR Light Intensity - The light intensity.

#define GL_GLEXT_PROTOTYPES

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

// Include files provided for this course.
//
#include <gp/util.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"

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

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

bool opt_show_lines; // Set normal to triangle normal.

pCoor light_location;
float opt_light_intensity;
enum { MI_Eye, MI_Light, MI_Ball, MI_Ball_V, MI_COUNT } opt_move_item;

int slices; // Level of detail for sphere.
pCoor sphere_location;
float sphere_size;

pCoor eye_location;
pVect eye_direction;
pMatrix modelview;

};

void
World::init()
{
frame_timer.work_unit_set("Steps / s");

eye_location = pCoor(1,0.5,3);
eye_direction = pVect(0,0,-1);

opt_light_intensity = 100.2;
light_location = pCoor(6.6,4.0,0.5);

sphere_location = pCoor(0,0,0);
sphere_size = 1;

slices = 20;
variable_control.insert(slices,"Slices in Sphere");

variable_control.insert(opt_light_intensity,"Light Intensity");
variable_control.insert(sphere_size,"Sphere Size");

opt_move_item = MI_Eye;

opt_show_lines = false;
}

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

/// Reset Frame Buffer
//
glClearColor(0,0,0,0);  // Set clear color to black.
glClearDepth(1.0);      // Set clear distance to all-the-way-back.
// Use current clear settings to rest frame buffer.
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

// Turn on Depth Test ( z-buffer test ).
//
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);     // FB written if z value to write < current z.

/// Lighting Setup
//
//  Covered in detail in demo-6.
//
//  Here, just pay attention to light_location.

glEnable(GL_LIGHTING);        // Turn on lighting.
glEnable(GL_LIGHT0);          // Turn on light number one.

glEnable(GL_COLOR_MATERIAL);
glColorMaterial(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE);
glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,1);

// Specify location of light number one.
//
glLightfv(GL_LIGHT0, GL_POSITION, light_location);

/// Frame Buffer Informational Messages
//
//  Print messages using utility functions provided for this course.
//

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

ogl_helper.fbprintf
("Light location: [%5.1f, %5.1f, %5.1f]\n",
light_location.x, light_location.y, light_location.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());

// -------------------------------------------------------------------------
///
/// Specification of Transformation Matrices
///

/// Setup Modelview Transformation:  Object Space -> Eye Space
//
glMatrixMode(GL_MODELVIEW);
// Note: This code does not account for eye direction.
glTranslatef(-eye_location.x,-eye_location.y,-eye_location.z);

/// Setup Projection Transformation:  Eye Space -> Clip Space
//
glMatrixMode(GL_PROJECTION);

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

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

/// Viewport Transformation
//
//  This is not set explicitly under ordinary circumstances.
//  The transformation is automatically set based on the window size.

//
// -------------------------------------------------------------------------

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

glColor3ub( 0x78, 0x8d, 0xf6); // Red, Green, Blue

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

/// Specify normal for triangle.
//
// Use cross product function (in coord.h) to find normal.
//
pNorm tri_norm = cross(pVect(0,5,-5),pVect(9,6,-0));
glNormal3fv(tri_norm);  // Set current normal.
// Note: pNorm, pVect, pCoor, and pColor objects can be used as
// arguments to OpenGL functions with names ending in 3fv.

// Specify vertices for a triangle.
//
glVertex3f( 0, 0, 0 );
glVertex3f( 0,   5, -5 );
glVertex3f( 9,   6, -9 );

glEnd();

///
/// Paint a Sphere
///

// In sphere's coordinate space the sphere center is at the origin
// and its radius is one. Therefore we need to adjust the modelview
// matrix so that sphere is at the value of coordinate
// sphere_location and its radius is sphere_size.
//
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glTranslatef(sphere_location.x,sphere_location.y,sphere_location.z);
glScalef(sphere_size,sphere_size,sphere_size);

// Construct color objects using hex RGB codes. See coord.h and colors.h.
//
const pColor lsu_spirit_purple(0x580da6);
const pColor lsu_spirit_gold(0xf9b237);

/// Start Sending Triangles
//
//  OpenGL state is now set up, start sending triangles!

glBegin(opt_show_lines ? GL_LINE_STRIP : GL_TRIANGLES);

// Set current color.  This is carried with vertex.
//
glColor3fv(lsu_spirit_gold);

const float delta_eta = M_PI / slices;

// Outer (eta) Loop: Iterate over longitude (north-to-south).
// Inner (theta) Loop: Iterate over latitude (east-to-west)

for ( int slice = 0; slice < slices - 1; slice++ )
{
const float eta0 = slice * delta_eta;
const float eta1 = eta0 + delta_eta;
const float y0 = cosf(eta0),        y1 = cosf(eta1);
const float slice_r0 = sinf(eta0),  slice_r1 = sinf(eta1);
const float delta_theta = delta_eta * slice_r1;

for ( float theta0 = 0; theta0 < 2 * M_PI; theta0 += delta_theta )
{
const float theta1 = theta0 + delta_theta;

/// Triangle 1

// Vertex 1
glNormal3f( slice_r1 * cosf(theta0), y1, slice_r1 * sinf(theta0) );
glVertex3f( slice_r1 * cosf(theta0), y1, slice_r1 * sinf(theta0) );

// Vertex 2
glNormal3f( slice_r0 * cosf(theta0), y0, slice_r0 * sinf(theta0) );
glVertex3f( slice_r0 * cosf(theta0), y0, slice_r0 * sinf(theta0) );

// Vertex 3
glNormal3f( slice_r1 * cosf(theta1), y1, slice_r1 * sinf(theta1) );
glVertex3f( slice_r1 * cosf(theta1), y1, slice_r1 * sinf(theta1) );

/// Triangle 2

// Vertex 3
glNormal3f( slice_r1 * cosf(theta1), y1, slice_r1 * sinf(theta1) );
glVertex3f( slice_r1 * cosf(theta1), y1, slice_r1 * sinf(theta1) );

// Vertex 2
glNormal3f( slice_r0 * cosf(theta0), y0, slice_r0 * sinf(theta0) );
glVertex3f( slice_r0 * cosf(theta0), y0, slice_r0 * sinf(theta0) );

// Vertex 4
glNormal3f( slice_r0 * cosf(theta1), y0, slice_r0 * sinf(theta1) );
glVertex3f( slice_r0 * cosf(theta1), y0, slice_r0 * sinf(theta1) );

}
}

glEnd();

/// Efficiency of Code Tesselation (Above)
//
//  The code is inefficient for the following reasons:
//
//  * Trigonometric functions are re-computed.  The compiler may
//    realize that two nearby calls of sinf(theta) will return the
//    same value, but don't bet on it.  Even so, they at least need
//    to be re-computed each frame. Instead the trig values (or better
//    the coordinates) should be saved in an array and re-used, only
//    to be recomputed if necessary, for example, if slices changes.
//
//  * There are vertices of three triangles at a single point. It
//    would be more efficient to compute and emit a vertex once and
//    share it. In demo-6 this is done using the triangle strip
//    feature.
//
//  * Even if vertex and normal coordinates were pre-computed, there
//    would still be substantial overhead in the individual function
//    calls (glVertex3f, glNormal3f).  To avoid this the OpenGL
//    vertex array feature is used.  This is done in demo-7.
//
//  * Even if vertices and normals are provided in arrays, they still
//    need to be sent from CPU to GPU each frame, even if there
//    is no change. To avoid this they can be stored on the GPU,
//    this is done using the OpenGL buffer objects feature. The
//    code in demo-7 uses buffer objects.
//
//  * Setting slices too low results in a sphere that looks like
//    a polyhedron, but setting it too high wastes CPU time.  The
//    appropriate value depends on the distance from the user.
//    With a geometry shader the CPU code can generate a sphere
//    with only a few slices, while the geometry shader code, which
//    runs on the GPU, can replace big triangles with multiple smaller
//    ones if the triangles are large in window space.  None of
//    the demos do that yet.

glPopMatrix();

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

pError_Check();

frame_timer.frame_end();
ogl_helper.user_text_reprint();
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 '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 'n': opt_show_lines = !opt_show_lines; break;
case 9: variable_control.switch_var_right(); break;
case 96: variable_control.switch_var_left(); break; // `, until S-TAB works.
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);
}

// 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: sphere_location += adjustment; break;
default: break;
}
}
}

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