/// LSU EE 4702-1 (Fall 2019), GPU Programming
//
 /// Textures, Blending, the Alpha Test, and Stencils

/// Purpose
//
//   Demonstrate use of Textures and Frame Buffer Update Operations

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

#if 0
/// Background -- Textures
//

// :ogl46: Chapter 8

//  :Def: Texture
//   An image applied to a primitive, like wallpaper.
//   Has many uses.
//
//   Textures are used to show the course syllabus on the platform
//   floor in the classroom examples.
//
//  Textures use lots of time, resources.
//
//  :Def: Texel
//  A point on a texture image. (Name is mix of texture and pixel.)

 /// Basic Procedure
//
//     During Initialization
//     - Specify a texture, perhaps from an image file like myhouse.jpeg.
//     - Specify how texels should be blended with primitive's color.
//
//     During a Rendering Pass
//     - Provide a texture coordinate for each vertex, indicating
//       what part of the texture image corresponds to the vertex location.
//       :Practice Problems:
//        Fall 2017 Midterm Exam Problem 3
//        https://www.ece.lsu.edu/koppel/gpup/2017/mt.pdf
//        https://www.ece.lsu.edu/koppel/gpup/2017/mt_sol.pdf
//
 /// What Happens (In a Simple Case)
//
//     - Texture coordinates are passed along as vertex attributes.
//
//     - Fragment shader uses texture coordinates to retrieve a texel.
//       (Texel is retrieved and filtered by fixed-function hardware.)
//
//     - Fragment shader combines texel with lighted color. If
//       the fragment survives, the result will be written to
//       the frame buffer.

 /// Texturing Concepts
//
//   :Def: Texel Fetch
//   The retrieval of a texel value at some coordinates.
//
//   OpenGL Shading Language texel fetch function:
     vec4 texel = texture( tex_unit_0, texture_coor );
//
//   texture_coor is a 2-D vector of floats. Coordinates space is [0,1]
//
//   :Example:  Very simple texture fetch.
//      Suppose original texture image is 1000 by 1000 pixels.
//      Suppose texture_coor = { 0.50, 0.070 }.
//      Fetch would return value of image texel at 50,70.
//
//      Suppose an adjacent fragment had texture_coor = { 0.150, 0.070 }
//      Fetch would return value of image texel at 150,070 ---
//        --- SKIPPING 100 image texels on the horizontal axis.

//
//   :Def: Texture Filtering
//   The computing of a texel value at some coordinate using some
//   combination of image texel values.
//
//   Texture filtering has a very strong impact on image appearance.
//
//   Texture filtering is compute intensive, and is one of the few
//   graphics operations that still (in 2019) uses special-purpose GPU
//   hardware.


 /// OpenGL use of Textures
//
//   :Def: Texture Object
//   An object that holds a particular texture plus settings for the
//   texture.
//
//   Texture objects typically are prepared during initialization.
//
//   Commands:
//
//    Generate a new texture object name. (Returned in second argument.)
      glGenTextures(1,&tid);

//    Set texture object for subsequent commands.
      glBindTexture(GL_TEXTURE_2D,tid);

//    Set a parameter for texture object.
      glTexParameteri(GL_TEXTURE_2D, PNAME, PVAL);

//    Load data into the texture object.
      glTexImage2D
      ( GL_TEXTURE_2D,    // There are also 1-d and 3-d textures.
        MIPMAP_LEVEL,     // An integer. Base image is zero.
        INTERNAL_FORMAT,  // Specifies order of colors. Eg GL_RGBA.
        WIDTH, HEIGHT,
        0,
        FORMAT,           // Specifies order of colors of input data ..
        // .. some possible values: GL_RGBA, GL_BGRA, GL_RED,..

        TYPE,             // Specifies data type of input data ..
        // .. some possible values: GL_UNSIGNED_SHORT, GL_FLOAT, ..

        IMAGE_ARRAY       // A pointer to the image data.
        );



//   :Def: Texture Unit
//   A named (numbered) collection of settings for handling textures.
//   A rendering pass can use zero or more texture units.
//
//   Texture units are set up before a rendering pass.



 /// Typical Steps to Prepare a Texture
//
 /// Start With
//
//   An image in some standard format ..
//   .. or an array containing a custom-drawn image.
//
 /// Finish With
//
//   An OpenGL texture object corresponding to the image ..
//   .. with MIPMAP levels computed by OpenGL ..
//   .. that can be used with texture commands.
//
 /// Steps
//
//   - Create a Texture Object
//     In example below tid will contain id of texture object.
//
  GLuint tid;  // A texture object ID.
  glGenTextures(1,&tid);
//
//   - Set new Texture Object to Create MIPMAP levels.
//
  glBindTexture(GL_TEXTURE_2D,tid);
  glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, 1);
//
//   - Load image into memory.
//     Example below uses the Graphics Magick library
//
  Magick::Image image( "my_cool_picture.jpeg" );
//
//   - Put image in Texture Object tid.
//
  glTexImage2D
    (GL_TEXTURE_2D,
     0,                // Level of Detail (a.k.a. mipmap level, 0 is base).
     GL_RGBA,          // Internal format to be used for texture.
     image.columns(), image.rows(),
     0,                // Border Width
     GL_BGRA,          // GL_BGRA: Order of color components in image,
     // Below: Data type of each color component,
     sizeof(Magick::PixelPacket) == 8 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_BYTE,
     (void*)image.getPixels(0,0,image.columns(), image.rows()) );
//
// See: 
//   rpBuild_Texture_File in this file.
//   P_Image_Read in file ../include/texture-util.h



/// Background -- Fragment Tests, Blending, FB Update

 // :ogl46: Chapter 14: Fixed-Function Primitive Assembly and Rasterization
 // :ogl46: Chapter 17: Writing fragments and samples to the framebuffer.

 /// Major Steps
 //
 //  -- Early per-fragment tests.
 //  -- Fragment Processing: User-provided shader or compatibility routine.
 //  -- Late per-fragment tests.

 //  Early Per-Fragment Tests
 //
 //    -- Pixel Ownership Test
 //       Is it in window-manager assigned area?

 //    -- Scissoring.
 //       Is it in the view volume?
 //       Is it within an additional user-defined area?

 //  Late Per-Fragment Tests
 //
 //    Tests are performed in the order below.
 //    Not every test is listed.
 //    A fragment that fails a test is not processed further.
 //
 //    -- Alpha Test  (Deprecated)
 //    -- Stencil Test
 //    -- Depth Buffer (z) Test
 //    -- Blending

 ///  Depth Buffer Test
 //
 //   Operates on depth (z) layer of frame buffer.
 //
 //   Turn on and off depth test.
      glEnable(GL_DEPTH_TEST);
      glDisable(GL_DEPTH_TEST);

 //   Specify type of test:
      glDepthFunc(FUNC);
 //   FUNC -> GL_NEVER, GL_ALWAYS, GL_LESS, GL_LEQUAL, GL_EQUAL,
 //           GL_GEQUAL, GL_NOTEQUAL
 //
      glDepthFunc(GL_LESS); // Pass if fragment z < pixel z.


 ///  Blending
 //
 //   Method of combining fragment colors with pixel colors.
 //
 //   Turn on and off.
      glEnable(GL_BLEND);
      glDisable(GL_BLEND);
 //
 //   Specify blend operator (Equation) and blend factors (BF)
 //
      glBlendEquation( EQ );
 //   Specifies how fragment (source) and pixel (dest) should be combined.
 //
 //   EQ -> GL_FUNC_ADD, GL_FUNC_SUBTRACT, GL_MIN, GL_MAX
 //
      glBlendFunc( SRC_BF, DST_BF );
 //   Specifies how fragment and pixel components should be weighted.
 //   
 //   BF -> GL_ZERO, GL_ONE, GL_SRC_COLOR, GL_DST_COLOR, 
 //
 //
 //   Let s denote fragment being processed ..
 //   .. and d denote the current pixel value (the destination).
 //   Let s.a indicate the fragment alpha channel, etc.
 //
 //


///  Background -- Stencil Operations
//
//   :ogl46: Section 17.3.3
//
//   :Def: Stencil Buffer
//   A layer of the frame buffer holding one integer for each pixel.
//
//   :Def: Stencil Test
//   A test that a fragment must pass in order to be written to frame buffer.
//   The stencil test is some requirement on value in stencil buffer.
//   For example, if stencil buffer value = 0, test fails, discard fragment.
//
//   :Def: Stencil Operation
//   The conditions under which stencil buffer updated.


 /// Stencil Buffer
//
//   A frame buffer layer. (Other layers include color and depth.)
//   Holds an integer value (for each pixel).

//  To Initialize Stencil Buffer
//
//    Use glClearStencil to specify value ..
//    .. and glClear with GL_STENCIL_BUFFER_BIT to write stencil buffer.

  // Set value to init each pixel to. Doesn't have to be seve.
  //
  glClearStencil(7);
  //
  // This just prepares the value, it doesn't change the stencil buffer.

  // Change the stencil value of every pixel to value set previously with
  // glClearStencil, seven in the example above.
  //
  glClear( GL_STENCIL_BUFFER_BIT );


  // Multiple layers can be cleared at once.
  //
  glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT );



 /// Stencil Test
//
//   Stencil test is set up using glStencilFunc.
//   Typical test: stencil test passes if value in stencil buffer > 12.
   //
   glStencilFunc(FUNC, REF,  MASK);
   glStencilFuncSeparate(FACE, FUNC, REF,  MASK);
   //
   //   FACE -> GL_FRONT, GL_BACK, GL_FRONT_AND_BACK
   //           That side of the primitive that the test applies to.
   //           Can provide one test for the front, and a different
   //            test for the back.
   //
   //   FUNC -> GL_LESS, GL_LEQUAL, GL_EQUAL, GL_NOTEQUAL, GL_GEQUAL, 
   //           GL_NEVER, GL_ALWAYS
   //
   //   REF  -> An integer. 
   //           The stencil buffer contents will be compared to REF.
   //
   //   MASK -> An integer.
   //           Used to select which bits of the stencil value to use.
   //           For simpler uses, MASK = -1 (all 1s in binary).
   //
   //   VAL  -> The value currently in the stencil buffer at the
   //           fragment's location.
   //
   //   Test passes if:  REF & MASK  FUNC  VAL & MASK
   //   Typical:         12  & 0xff   <    34  & 0xff   (Test passes)
   //   Typical:         12  & 0xff   <     5  & 0xff   (Test fails)

   // :Example::
   //
   glStencilFunc(GL_EQUAL, 4,    5    );
   //            FUNC      REF   MASK
   //
   //  Suppose VAL = 12
   //  4 & 5  ==  12 & 5
   //  4      ==  4         --> Test passes.
   //
   //  Suppose VAL = 13
   //  4 & 5  ==  13 & 5
   //  4      ==  5         --> Test fails

 /// Stencil Operation, For Writing Stencil Buffer
//
//   Stencil Operation specifies conditions for writing stencil buffer ..
//   .. and how the stencil buffer should be changed (e.g., write, increment)
//
//   Typical operation: Write the stencil buffer if the depth test passes.
//
   glStencilOp(SFAIL, DFAIL, DPASS);
   glStencilOpSeparate(FACE, SFAIL, DFAIL, DPASS);
 //
 // Specify how stencil buffer value is modified in each of three situations:
 //
 //   SFAIL: How to modify if the stencil test fails.
 //   DPASS: How to modify if the depth test passes.  Easy to understand.
 //   DFAIL: How to modify if the depth test fails.
 //
 //   Values for SFAIL, DFAIL, DPASS:
 //
 //     GL_REPLACE:
 //       Set stencil value to reference value that was used in glStencilFunc.
 //
 //     GL_INCR_WRAP, GL_DECR_WRAP
 //       Add or subtract one to stencil value,
 //       if there is an overflow use low bits.
 //       E.g. assuming 4 bit buffer::  15 + 1 = 0
 //
 //     GL_INCR, GL_DECR
 //       Add or subtract one to stencil value,
 //       if there is an overflow keep prior value.
 //       E.g. assuming 4 bit buffer::  15 + 1 = 15
 //
 //     GL_KEEP: 
 //       Don't do anything. (Don't change stencil buffer value.)
 //
 //     GL_ZERO, GL_INVERT:
 //       Set to zero, flip bits.

 // :Example:
 //
 // Write stencil buffer with a 5.
 //
 //
    glStencilFunc(GL_NEVER, -1,  5);  // Set ref value to 5.
    glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); // Update stencil.








#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.)
 //
 //  'm'    Change method used to apply textures.
 //  'r'    Toggle vertex re-computation on and off.
 //  '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.
 //
 //  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/shader.h>
#include <gp/pstring.h>
#include <gp/misc.h>
#include <gp/gl-buffer.h>
#include <gp/texture-util.h>
#include "shapes.h"


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

#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),   // Use alpha channel of tex to select.
  ENUM_LABEL(GL_BLEND),   // Blend in a constant color 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 ),
  ENUM_LABEL( GL_LINEAR ),
  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}
};


// A similar routine is defined in gp/texture-util.h
//
GLuint
rpBuild_Texture_File
(const char *name, bool invert = false, int transp = 256 )
{
  // Read image from file.
  //
  P_Image_Read image(name,transp);
  if ( !image.image_loaded ) return 0;

  // Invert colors. (E.g., to show text as white on black.)
  //
  if ( invert ) image.color_invert();

  GLuint tid;  // A texture ID.

  // Generate a new texture ID.
  glGenTextures(1,&tid);

  // Tell OpenGL that subsequent 2D texture commands refer to texture TID.
  glBindTexture(GL_TEXTURE_2D,tid);

  // Tell OpenGL to automatically generate the MIPMAP levels for this texture.
  glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, 1);

  // Load data into the texture object.
  //
  glTexImage2D
    (GL_TEXTURE_2D,
     0,                // Level of Detail (a.k.a. mipmap level, 0 is base).
     GL_RGBA,          // Internal format to be used for texture.
     image.width, image.height,
     0,                // Border Width
     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 tid;
}


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;

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

  vector<pCoor> sphere_coords;
  vector<float> texture_coords;

  pCoor sphere_location;
  float sphere_size;

  pCoor eye_location;
  pVect eye_direction;
  pMatrix modelview;

  int opt_method;
  bool opt_recompute;

  float tex_scale;  // Amount by which to scale the texture on the triangle.
  float tri_tilt;   // Amount by which to tilt single triangle.
  GLuint texture_id_syllabus;
  GLuint texture_id_image;

  bool opt_blend;
  bool opt_alpha;
  int opt_texture_env_mode;
  int opt_texture_min_filter;
  int opt_texture_mag_filter;

};

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

  opt_method = 0;
  opt_recompute = false;

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

  opt_light_intensity = 7.2;
  light_location = pCoor(7,4.0,-0.3);

  sphere_location = pCoor(0,0,-5);
  sphere_size = 5;

  tex_scale = 1;
  variable_control.insert(tex_scale,"Texture Scaling");

  tri_tilt = 2;
  variable_control.insert(tri_tilt,"Triangle Tilt");

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

  opt_move_item = MI_Eye;

  opt_texture_env_mode = 0;
  opt_texture_min_filter = 0;
  opt_texture_mag_filter = 0;
  opt_blend = false;
  opt_alpha = false;

  texture_id_syllabus = pBuild_Texture_File("gpup.png",false,255);
  texture_id_image = pBuild_Texture_File("mult.png", false,255);

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

  glShadeModel(GL_SMOOTH);


  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->var[0]);


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

  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 ('b')  Alpha Test %s ('p')\n",
                      opt_blend ? "ON" : "OFF",
                      opt_alpha ? "ON" : "OFF"
                      );

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

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

  glEnable(GL_LIGHTING);

  pColor white(1,1,1);
  pColor red(1,0,0);
  pColor gray(0x303030);
  pColor dark(0);
  pColor ambient_color(0x555555);
  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);

  if ( opt_blend )
    {
      glEnable(GL_BLEND);
    }
  else
    {
      glDisable(GL_BLEND);
    }

  if ( opt_alpha )
    {
      glEnable(GL_ALPHA_TEST);
    }
  else
    {
      glDisable(GL_ALPHA_TEST);
    }

  glAlphaFunc(GL_GREATER,0.1);
  glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);

  pError_Check();

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

  // If 1, use back color and -normal if back side facing user.
  //
  glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,1);


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

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

  glEnable(GL_TEXTURE_2D);

  // Make Texture Unit 0 the current texture unit.
  //
  glActiveTexture(GL_TEXTURE0);

  // Attach texture object texture_id_image to current texture unit (0).
  //
  glBindTexture(GL_TEXTURE_2D,texture_id_syllabus);

  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);

  // Set parameters that apply to a texture (texture_id_syllabus).
  //
  glTexParameteri
    (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
     texture_min_filters[opt_texture_min_filter].value);

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

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

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

  // Specify vertices for a triangle.
  //

  glNormal3f(0,1,0); // Use the same normal for all vertices.
  glColor3f(0,1,0); // Green
  glTexCoord2f( tex_scale * 0.0, tex_scale * 0.0 );
  glVertex3f( 0,   5, -7 );
  glColor3f(1,0,0); // Red
  glTexCoord2f( tex_scale * 1.0, tex_scale * 0.0 );
  glVertex3f( 9,   6,  -7 );
  glColor3f(0,0,1); // Blue
  glTexCoord2f( tex_scale * 1.0, tex_scale * 1.0 );
  glVertex3f( 9.5, -4, -1.2 + tri_tilt );

  glEnd();

  glDisable(GL_TEXTURE_2D);


  ///
  /// Construct a Sphere
  ///

  if ( sphere_coords.empty() )
    {
      const int slices = 40;
      const double delta_eta = M_PI / slices;

      for ( double eta = 0; eta < M_PI - 0.0001 - delta_eta; eta += delta_eta )
        {
          const double eta1 = eta + delta_eta;
          const float  y0 = cos(eta),        y1 = cos(eta1);
          const double slice_r0 = sin(eta),  slice_r1 = sin(eta1);
          const double delta_theta = delta_eta * slice_r1;

          const float t0 = eta / M_PI;
          const float t1 = eta1 / M_PI;

          // Add first two vertices of triangle strip.
          //
          // Note that a texture coordinate is added to list one
          // component at a time.
          //

          // Mapping of Texture Coordinates to Position on Sphere
          //
          //  Assuming that the texture image is a rectangle ..
          //  .. there is no right way to assign a texture coordinate ..
          //  .. to a position on a sphere.
          //
          //  The method used below is simple.
          //
          //  See rectangular images in https://xkcd.com/977/

          // Vertex 1
          //
          sphere_coords.push_back( pCoor( slice_r1, y1, 0 ) );

          texture_coords.push_back( 1 );
          texture_coords.push_back( t1 );

          // Vertex 2
          //
          sphere_coords.push_back( pCoor( slice_r0, y0, 0 ) );

          texture_coords.push_back( 1 );
          texture_coords.push_back( t0 );

          for ( double theta = 0; theta < 2 * M_PI; theta += delta_theta )
            {
              const double theta1 = theta + delta_theta;

              // Vertex 3  (Used for three triangles.)
              //
              sphere_coords.push_back
                ( pCoor( slice_r1 * cos(theta1), y1, slice_r1 * sin(theta1) ) );

              texture_coords.push_back( 1 - theta1 / ( 2 * M_PI ) );
              texture_coords.push_back( t1 );

              // Vertex 4  (Used for three triangles.)
              //
              sphere_coords.push_back
                ( pCoor( slice_r0 * cos(theta1), y0, slice_r0 * sin(theta1) ) );

              texture_coords.push_back( 1 - theta1 / ( 2 * M_PI ) );
              texture_coords.push_back( t0 );
            }
        }
    }


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

  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  glTranslatef(sphere_location.x,sphere_location.y,sphere_location.z);
  glScalef(sphere_size,sphere_size,sphere_size);
  glRotatef(60,0,1,0);

  glColor3ub( 0x80, 0x80, 0x80); // Red, Green, Blue

  glDisable(GL_COLOR_MATERIAL);
  glMaterialfv(GL_BACK,GL_AMBIENT_AND_DIFFUSE, lsu_spirit_purple);
  glEnable(GL_COLOR_MATERIAL);

  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).
      //
      glTexParameteri
        (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
         texture_min_filters[opt_texture_min_filter].value);

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

  // Specify pointer into array to use for texture coordinates.
  //
  glTexCoordPointer(2,GL_FLOAT,0,texture_coords.data());
  glEnableClientState(GL_TEXTURE_COORD_ARRAY);

  // Specify pointer into array to use for normals.
  //
  glNormalPointer(GL_FLOAT,sizeof(sphere_coords[0]),sphere_coords.data());

  // Specify that normals should come from an array.
  //
  glEnableClientState(GL_NORMAL_ARRAY);

  // Ditto.
  //
  glVertexPointer(3,GL_FLOAT,sizeof(sphere_coords[0]),sphere_coords.data());
  glEnableClientState(GL_VERTEX_ARRAY);

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

  // Draw triangle strips using enabled arrays.
  // Start at element 0, render a total of coords_size/3 vertices.
  //
  glDrawArrays(GL_TRIANGLE_STRIP,0,sphere_coords.size());

  glDisableClientState(GL_NORMAL_ARRAY);
  glDisableClientState(GL_VERTEX_ARRAY);
  glDisableClientState(GL_TEXTURE_COORD_ARRAY);

  glDisable(GL_TEXTURE_2D);

  if ( opt_recompute )
    {
      sphere_coords.clear();
      texture_coords.clear();
    }

  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 adjustment(0,0,0);
  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_blend = !opt_blend; break;
  case 'p': case 'P': opt_alpha = !opt_alpha; 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 's': case 'S': 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 'r': case 'R': opt_recompute = !opt_recompute; 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(); break;
  case '+':case '=': variable_control.adjust_higher(); 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.
  //
  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;

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