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

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

/// References
//

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

// :ogl46: Chapter 8
// :v1.2: Chapter 13
//

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

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

/// This material not yet updated for Vulkan.

//  Vulkan provides capabilities that are similar to those provided
//  by OpenGL, but with a more flexible API.

// :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.
//
//
//   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.
//
//           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.
//
//   Typical:         12  & 0xff   <    34  & 0xff   (Test passes)
//   Typical:         12  & 0xff   <     5  & 0xff   (Test fails)

// :Example::
//
glStencilFunc(GL_EQUAL, 4,    5    );
//
//  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.

// Include files provided for this course.
//
#define MAIN_INCLUDE
#include <vhelper.h>

#include <vstroke.h>
#include <gp/coord.h>
#include <gp/pstring.h>
#include <gp/misc.h>
#include <gp/colors.h>

#include <vutil-texture.h>
#include <vutil-pipeline.h>
#include "shapes.h"

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

const int FL_NONE = 0;

#define ENUM_LABEL(c) { #c, c }
template <typename T>
struct pEnum_Label { const char *label; T value; };
pEnum_Label<int> texture_env_modes[] = {
ENUM_LABEL(FL_NONE),
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.
{NULL,0}
};

struct Uni_TMode {
vec4 tex_env_color;
int texture_mode;
};

pEnum_Label<vk::SamplerMipmapMode> texture_mipmap_mode[] = {
ENUM_LABEL( vk::SamplerMipmapMode::eNearest ),
ENUM_LABEL( vk::SamplerMipmapMode::eLinear ),
{NULL,{}}
};

pEnum_Label<vk::Filter> texture_min_filters[] = {
ENUM_LABEL( vk::Filter::eNearest ),
ENUM_LABEL( vk::Filter::eLinear ),
{NULL,{}}
};

pEnum_Label<vk::Filter> texture_mag_filters[] = {
ENUM_LABEL( vk::Filter::eNearest ),
ENUM_LABEL( vk::Filter::eLinear ),
{NULL,{}}
};

// A similar routine is defined in gp/texture-util.h
//
#if 0
GLuint
rpBuild_Texture_File
(const char *name, bool invert = false, int transp = 256 )
{
//
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;
}
#endif

class World {
public:
World(pVulkan_Helper &vh)
:vh(vh),ff_state(vh.qs),shapes(ff_state),frame_timer(vh.frame_timer),
transform(vh.qs){ init(); }
void init();
void run();
void render(vk::CommandBuffer& cb);
void cb_keyboard();
void modelview_update();

// Class providing utilities, such as showing text.
//
pVulkan_Helper& vh;
VFixed_Function_State_Manager ff_state;

Shapes shapes;

// 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;
enum { MI_Eye, MI_Light, MI_Ball, MI_Ball_V, MI_COUNT } opt_move_item;

bool global_transform_stale;

VTransform transform;
VBufferV<Uni_Lighting> uni_light;
VPipeline pipe_lonely;
VPipeline pipe_sphere;
VVertex_Buffer_Set bset_lonely, bset_sphere;

pCoor sphere_location;
float sphere_size;

pCoor eye_location;
pVect eye_direction;
pMatrix modelview;

int opt_method;
bool opt_recompute;

bool lonely_triangle_stale;
float tex_scale;  // Amount by which to scale the texture on the triangle.
float tri_tilt;   // Amount by which to tilt single triangle.

vk::Sampler sampler;
VTexture texture_id_syllabus;
VTexture texture_id_image;

VBufferV<Uni_TMode> uni_texture_mode;

bool opt_blend;
bool opt_alpha;
int opt_texture_env_mode;
int opt_texture_min_filter;
int opt_texture_mag_filter;
int opt_texture_mipmap_mode;
bool opt_lod_zero;
bool sampler_dirty;

};

void
World::init()
{
vh.init();
vh.opt_record_every_time = true;
vh.display_cb_set([&](){});
vh.cbs_cmd_record.push_back( [&](vk::CommandBuffer& cb){ render(cb); });

frame_timer.work_unit_set("Steps / s");

#define ENUM_TO_INT(e) \
shader_gl_defines += pStringF("const int %s = %d;\n", #e, e).ss();
ENUM_TO_INT(FL_NONE);
ENUM_TO_INT(GL_REPLACE);
ENUM_TO_INT(GL_MODULATE);
ENUM_TO_INT(GL_DECAL);
ENUM_TO_INT(GL_BLEND);
ENUM_TO_INT(GL_COMBINE);
#undef ENUM_TO_INT

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;

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

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

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

opt_move_item = MI_Eye;

uni_light.init(vh.qs,vk::BufferUsageFlagBits::eUniformBuffer);
uni_texture_mode.init(vh.qs,vk::BufferUsageFlagBits::eUniformBuffer);
uni_texture_mode->texture_mode = opt_texture_env_mode = 0;
uni_texture_mode->tex_env_color = vec4(0,0,0,0);
opt_texture_min_filter = 0;
opt_texture_mag_filter = 0;
opt_texture_mipmap_mode = 0;
opt_lod_zero = false;
// Type: vk::Sampler
sampler = nullptr;
sampler_dirty = true;
opt_blend = false;
opt_alpha = false;

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::run()
{
vh.message_loop_spin();

uni_light.destroy();
uni_texture_mode.destroy();

bset_sphere.destroy();
bset_lonely.destroy();
pipe_sphere.destroy();
pipe_lonely.destroy();

texture_id_syllabus.destroy();
texture_id_image.destroy();
vh.dev.destroySampler(sampler);
shapes.destroy();

vh.finish();
}

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

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

if ( opt_recompute ) bset_sphere.reset();

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

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

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

vh.fbprintf("Texture Mode: %s  ('m')\n",
texture_env_modes[opt_texture_env_mode].label);
vh.fbprintf("Use Mipmap Levels: %s ('d')\n",
opt_lod_zero ? " NO" : "YES" );
vh.fbprintf("Min Filter: %s  ('i')\n",
texture_min_filters[opt_texture_min_filter].label);
vh.fbprintf("Mipmap Mode: %s ('I')\n",
texture_mipmap_mode[opt_texture_mipmap_mode].label);
vh.fbprintf("Mag Filter: %s  ('a')\n",
texture_mag_filters[opt_texture_mag_filter].label);
vh.fbprintf("Blending %s ('b')  Alpha Test %s ('p')\n",
opt_blend ? "ON" : "OFF", opt_alpha ? "ON" : "OFF");

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

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

transform.eye_from_global_set( modelview );
transform.clip_from_eye_set
( pMatrix_Scale(1,-1,1)
* pMatrix_Frustum(-.8,.8,-.8/aspect,.8/aspect,1,5000) );

pColor white(1,1,1);
pColor red(1,0,0);
pColor gray(0x303030);
pColor dark(0);
pColor ambient_color(0x555555);
const pColor lsu_spirit_gold(0xf9b237);

/// Lighting
//
auto& light0 = uni_light->cgl_LightSource[0];
uni_light->cgl_LightModel.ambient = ambient_color;
light0.diffuse = white * opt_light_intensity;
light0.position = transform.eye_from_global * light_location;
light0.constantAttenuation = 0;
light0.linearAttenuation = 1;
light0.ambient = color_black;
uni_light.to_dev();
uni_texture_mode.to_dev();

#if 0
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);

#endif

if ( sampler_dirty )
{
sampler_dirty = false;
if ( sampler ) vh.dev.destroySampler(sampler);

// Type: vk::Sampler
// See :v1.2: Section 13
//
sampler = vh.qs.dev.createSampler
( { {},
// Mag filter, Min filter, Mipmap Mode
texture_mag_filters[opt_texture_mag_filter].value,
texture_min_filters[opt_texture_min_filter].value,
texture_mipmap_mode[opt_texture_mipmap_mode].value,
// Also: eRepeat, eMirroredRepeat, eClampToEdge, etc.
0.0f,
false, // anisotropyEnable,
16.0f,
false,
vk::CompareOp::eNever,
// min and max LOD
0.0f, opt_lod_zero ? 0.0f : VK_LOD_CLAMP_NONE,
vk::BorderColor::eFloatOpaqueBlack } );

// Since the sampler has been recreated, we need to destroy the
// pipelines before any attempt is made to use the old sampler.
//
if ( pipe_lonely ) pipe_lonely.destroy();
if ( pipe_sphere ) pipe_sphere.destroy();
}

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

if ( !pipe_lonely )
{
pipe_lonely
.init( vh.qs )
.texture_on()
.use_uni_light( uni_light )
.uniform_bind( uni_texture_mode, "BIND_TEXTURE_MODE" )
("demo-08-shdr.cc", "vs_main();", nullptr, "fs_main();",
.topology_set( vk::PrimitiveTopology::eTriangleList )
.create();
}

if ( !bset_lonely || lonely_triangle_stale )
{
bset_lonely.reset( pipe_lonely );
lonely_triangle_stale = false;

// Specify vertices for a triangle.
//
pCoor p1( 9.5, -4,  -12. + tri_tilt );
pCoor p2( 9,    6,  -7 );
pCoor p3( 0,    5,  -7 );

pTCoor tc1( tex_scale * 1.0, tex_scale * 1.0 );
pTCoor tc2( tex_scale * 1.0, tex_scale * 0.0 );
pTCoor tc3( tex_scale * 0.0, tex_scale * 0.0 );

bset_lonely << p1          << p2        << p3;
bset_lonely << tc1         << tc2       << tc3;
bset_lonely << color_green << color_red << color_blue;

pNorm tnorm = cross(p1,p2,p3);
bset_lonely << tnorm << tnorm << tnorm;

bset_lonely.to_dev();
}

transform.use_global_for(pipe_lonely);
pipe_lonely.use_texture(sampler, texture_id_syllabus );
pipe_lonely.record_draw(cb, bset_lonely);

if ( !pipe_sphere )
{
pipe_sphere
.init( vh.qs )
.texture_on()
.color_uniform_set(lsu_spirit_gold,color_red)
.use_uni_light( uni_light )
.uniform_bind( uni_texture_mode, "BIND_TEXTURE_MODE" )
("demo-08-shdr.cc", "vs_main_sphere();", nullptr, "fs_main();",
.topology_set( vk::PrimitiveTopology::eTriangleStrip )
.create();
}

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

if ( !bset_sphere )
{
bset_sphere.reset( pipe_sphere );

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;

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

// Add first two vertices of triangle strip.
//

// 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
//
bset_sphere << pCoor( slice_r1, y1, 0 )
<< pTCoor( 1, t1 );

// Vertex 2
//
bset_sphere << pCoor( slice_r0, y0, 0 )
<< pTCoor( 1, t0 );

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

// Vertex 3  (Used for three triangles.)
//
bset_sphere
<< pCoor( slice_r1 * cos(theta1), y1, slice_r1 * sin(theta1) )
<< pTCoor( 1 - theta1 / ( 2 * M_PI ), t1 );

// Vertex 4  (Used for three triangles.)
//
bset_sphere
<< pCoor( slice_r0 * cos(theta1), y0, slice_r0 * sin(theta1) )
<< pTCoor( 1 - theta1 / ( 2 * M_PI ), t0 );
}
}
bset_sphere.to_dev();
}

///
/// Paint a Sphere
///
pipe_sphere.use_texture(sampler, texture_id_image );

transform.global_from_local_set_for
( pMatrix_Translate( sphere_location )
* pMatrix_Scale(sphere_size)
* pMatrix_Rotation( pVect(0,1,0), M_PI / 3 ),
pipe_sphere );

pipe_sphere.record_draw( cb, bset_sphere );

// Render Marker for Light Source
//
shapes.record_tetrahedron(cb,transform,light_location,0.5);
}

void
World::cb_keyboard()
{
const int key = vh.keyboard_key_get();
if ( !key ) return;
pVect user_rot_axis(0,0,0);
const bool kb_mod_s = vh.keyboard_shift;
const bool kb_mod_c = vh.keyboard_control;
const float move_amt = kb_mod_s ? 2.0 : kb_mod_c ? 0.08 : 0.4;

switch ( 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 'd': case 'D': opt_lod_zero = !opt_lod_zero; sampler_dirty = true; break;

case 'm': opt_texture_env_mode++;
if ( !texture_env_modes[opt_texture_env_mode].label )
opt_texture_env_mode = 0;
uni_texture_mode->texture_mode
= texture_env_modes[opt_texture_env_mode].value;
break;
case 'i': opt_texture_min_filter++;
sampler_dirty = true;
if ( !texture_min_filters[opt_texture_min_filter].label )
opt_texture_min_filter = 0;
break;
case 'I': opt_texture_mipmap_mode++;
sampler_dirty = true;
if ( !texture_mipmap_mode[opt_texture_mipmap_mode].label )
opt_texture_mipmap_mode = 0;
break;
case 'a': opt_texture_mag_filter++;
sampler_dirty = true;
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 FB_KEY_TAB:
if ( !kb_mod_s ) { variable_control.switch_var_right(); break; }
case 96: variable_control.switch_var_left(); break;
default: printf("Unknown key, %d\n",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: sphere_location += adjustment; break;
default: break;
}
modelview_update();
}
}

int
main(int argv, char **argc)
{
pVulkan_Helper pvulkan_helper(argv,argc);
World world(pvulkan_helper);

world.run();

return 0;
}
```