```/// LSU EE 4702-1 (Fall 2015), GPU Programming
//
/// Homework 4 -- SOLUTION
//

/// Instructions
//
//  Assignment: http://www.ece.lsu.edu/koppel/gpup/2015/hw04.pdf
//  Solution:   http://www.ece.lsu.edu/koppel/gpup/2015/hw04_sol.pdf

/// Purpose
//
//   Demonstrate simulation of point masses connected by springs.

/// What Code Does

// Simulates balls connected by springs over a platform. Balls and
// springs can be initialized in different arrangements (called
// scenes). Currently scene 1 is a simple string of beads, and scenes
// 2, 3, and 4 are trusses. The platform consists of tiles, some are
// purple-tinted mirrors (showing a reflection of the ball), the
// others show the course syllabus.

///  Keyboard Commands
//
/// Object (Eye, Light, Ball) Location or Push
//   Arrows, Page Up, Page Down
//        Move object or push ball, depending on mode.
//        With shift key pressed, motion is 5x faster.
//   'e': Move eye.
//   'l': Move light.
//   'b': Move head (first) ball. (Change position but not velocity.)
//
/// 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.)
//
//  'v'    Cycle through display of links/balls (SKEL) and triangles (SKIN).
//  'w'    Twirl balls around axis formed by head and tail. (Prob 2 soln).
//  '1'    Set up scene 1.
//  '2'    Set up scene 2.
//  '3'    Set up scene 3.
//  'p'    Pause simulation. (Press again to resume.)
//  ' '    (Space bar.) Advance simulation by 1/30 second.
//  'S- '  (Shift-space bar.) Advance simulation by one time step.
//  'h'    Freeze position of first (head) ball. (Press again to release.)
//  't'    Freeze position of last (tail) ball. (Press again to release.)
//  's'    Stop balls.
//  'g'    Turn gravity on and off.
//  'y'    Toggle value of opt_tryout1. Intended for experiments and debugging.
//  'Y'    Toggle value of opt_tryout2. Intended for experiments and debugging.
//  'F11'  Change size of green text (in upper left).
//  '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 Spring Constant - Set spring constant.
//  VAR Time Step Duration - Set physics time step.
//  VAR Air Resistance - Set air resistance.
//  VAR Light Intensity - The light intensity.
//  VAR Gravity - Gravitational acceleration. (Turn on/off using 'g'.)

#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 <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 <gp/colors.h>

#include "util-containers.h"
#include "shapes.h"

///
/// Main Data Structures
///
//
// class World: All data about scene.

class World;

// Object Holding Ball State
//
class Ball {
public:
Ball():velocity(pVect(0,0,0)),locked(false),
color(color_lsu_spirit_gold),contact(false){};
pCoor position;
pVect velocity;
int idx; // Position in balls_pos_rad;

float mass;
float mass_min; // Mass below which simulation is unstable.

bool locked;

pVect force;
pColor color;
bool contact;                 // When true, ball rendered in gray.
float spring_constant_sum;    // Used to compute minimum mass.

void push(pVect amt);
void translate(pVect amt);
void stop();
void freeze();
};

public:
distance_relaxed(pDistance(b1->position,b2->position)), snapped(false),
natural_color(color_lsu_spirit_purple),color(color_lsu_spirit_purple){}
Link(Ball *b1, Ball *b2, pColor colorp):ball1(b1),ball2(b2),
distance_relaxed(pDistance(b1->position,b2->position)), snapped(false),
natural_color(colorp),color(colorp){}
Ball* const ball1;
Ball* const ball2;
float distance_relaxed;
bool snapped;
pColor natural_color;
pColor color;
};

// Declare containers and iterators for Balls and Links.
// (See util_container.h.)
//
typedef pVectorI<Ball> Balls;
typedef pVectorI_Iter<Ball> BIter;

//
//   Use this class to define variables and member functions.
//
class My_Piece_Of_The_World {
public:

float link_stressed_thd;  // Amount of stretch to be considered stressed.

pColor color_stressed;
};

struct Truss_Info {

// See make_truss for a description of what the members are for.

// Inputs
PStack<pCoor> base_coors;  // Coordinates of first set of balls.
pVect unit_length;
int num_units;

// Output
Balls balls;
};

enum Render_Option { RO_Normally, RO_Simple, RO_Shadow_Volumes };
enum Shader_Option { SO_Fixed, SO_Phong, SO_Instance, SO_ENUM_SIZE };

struct ivec2 { int x; int y; };

class World {
public:
World(pOpenGL_Helper &fb):ogl_helper(fb){init();}
void init();
void init_graphics();
static void frame_callback_w(void *moi){((World*)moi)->frame_callback();}
void frame_callback();
void render();
void render_objects(Render_Option render_option);
void render_my_piece();
void objects_erase();
void cb_keyboard();
void modelview_update();

My_Piece_Of_The_World mp;

pOpenGL_Helper& ogl_helper;
pVariable_Control variable_control;
pFrame_Timer frame_timer;
double world_time;
double last_frame_wall_time;
float opt_time_step_duration;
int time_step_count;
float opt_gravity_accel;      // Value chosen by user.
pVect gravity_accel;          // Set to zero when opt_gravity is false;
bool opt_gravity;
bool opt_ride; // When true, move eye to ball_eye.
int opt_tri_cull;  // =0, don't cull; =1, cull back, =2, cull front.
bool opt_tryout1, opt_tryout2;  // For ad-hoc experiments.
int opt_sides;  // Number of arms on thing in scene 3.

bool opt_ball_texture;

// Tiled platform for ball.
//
float platform_xmin, platform_xmax, platform_zmin, platform_zmax;
float platform_pi_xwidth_inv;
pBuffer_Object<pVect> platform_tile_coords;
pBuffer_Object<float> platform_tex_coords;
pVect platform_normal;
GLuint texid_syl;
GLuint texid_emacs;
bool opt_platform_texture;
void platform_update();
bool platform_collision_possible(pCoor pos);

pCoor light_location;
float opt_light_intensity;
enum { MI_Eye, MI_Light, MI_Ball, MI_Ball_V, MI_COUNT } opt_move_item;
bool opt_pause;
bool opt_single_frame;      // Simulate for one frame.
bool opt_single_time_step;  // Simulate for one time step.

pCoor eye_location;
pVect eye_direction;
pMatrix modelview;
pMatrix transform_mirror;

GLuint balls_color_bo;

/// SOLUTION -- Problem 2
//
//  Variables used for transferring link colors to OpenGL.

/// SOLUTION -- Problem 3
//
//  Variables for transferring ball velocity to OpenGL.
pCoor *balls_velocity;    // Note: Alignment problems with 3-elt type.
GLuint balls_velocity_bo;

pColor *balls_color;
size_t balls_size;
int last_setup; // Last scene set up.

void ball_setup_1();
void ball_setup_2();
void ball_setup_3();
void ball_setup_4();
void ball_setup_5();
void time_step_cpu(double);
void balls_stop();
void balls_freeze();
void balls_translate(pVect amt, int idx);
void balls_translate(pVect amt);
void balls_push(pVect amt, int idx);
void balls_push(pVect amt);
void balls_twirl();
void lock_update();

Ball *make_marker(pCoor pos, pColor col);
void make_truss(Truss_Info *ti);

Ball *ball_eye, *ball_gaze, *ball_down;

float opt_spring_constant;
float opt_air_resistance;
float distance_relaxed;
int chain_length;
Balls balls;
Sphere sphere;
Cylinder cyl;
};

void
World::init_graphics()
{
///
/// Graphical Model Initialization
///

balls_color = NULL;
balls_color_bo = 0;
balls_size = 0;

opt_platform_texture = true;
opt_tail_lock = false;
opt_ball_texture = false;
opt_tryout1 = opt_tryout2 = false;

opt_sides = 8;

eye_location = pCoor(24.2,11.6,-38.7);
eye_direction = pVect(-0.42,-0.09,0.9);

platform_xmin = -40; platform_xmax = 40;
platform_zmin = -40; platform_zmax = 40;
texid_syl = pBuild_Texture_File("gpup.png",false,255);
texid_emacs = pBuild_Texture_File("mult.png", false,-1);

opt_light_intensity = 100.2;
light_location = pCoor(platform_xmax,platform_xmax,platform_zmin);

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

opt_move_item = MI_Eye;
opt_pause = false;
opt_single_time_step = false;
opt_single_frame = false;

sphere.init(15);

platform_update();
modelview_update();

}

void
World::platform_update()
{
const float tile_count = 19;
const float ep = 1.00001;
const float delta_x = ( platform_xmax - platform_xmin ) / tile_count * ep;
const float zdelta = ( platform_zmax - platform_zmin ) / tile_count * ep;

const float trmin = 0.05;
const float trmax = 0.7;
const float tsmin = 0;
const float tsmax = 0.4;

platform_normal = pVect(0,1,0);

PStack<pVect> p_tile_coords;
PStack<pVect> p1_tile_coords;
PStack<float> p_tex_coords;
bool even = true;

for ( int i = 0; i < tile_count; i++ )
{
const float x0 = platform_xmin + i * delta_x;
const float x1 = x0 + delta_x;
const float y = 0;
for ( float z = platform_zmin; z < platform_zmax; z += zdelta )
{
PStack<pVect>& t_coords = even ? p_tile_coords : p1_tile_coords;
p_tex_coords += trmax; p_tex_coords += tsmax;
t_coords += pVect(x0,y,z);
p_tex_coords += trmax; p_tex_coords += tsmin;
t_coords += pVect(x0,y,z+zdelta);
p_tex_coords += trmin; p_tex_coords += tsmin;
t_coords += pVect(x1,y,z+zdelta);
p_tex_coords += trmin; p_tex_coords += tsmax;
t_coords += pVect(x1,y,z);
even = !even;
}
}

while ( pVect* const v = p1_tile_coords.iterate() ) p_tile_coords += *v;

platform_tile_coords.re_take(p_tile_coords);
platform_tile_coords.to_gpu();
platform_tex_coords.re_take(p_tex_coords);
platform_tex_coords.to_gpu();
}

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;
pMatrix reflect; reflect.set_identity(); reflect.rc(1,1) = -1;
transform_mirror = modelview * reflect * invert(modelview);
}

void
World::render_objects(Render_Option option)
{
const float shininess_ball = 5;
pColor spec_color(0.2,0.2,0.2);

if ( option == RO_Shadow_Volumes )

if ( option == RO_Normally )
{
glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 1.0);
if ( opt_ball_texture )
{
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D,texid_emacs);
}
else
{
glDisable(GL_TEXTURE_2D);
}
glMaterialf(GL_FRONT_AND_BACK,GL_SHININESS,shininess_ball);
glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR,spec_color);
glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR);
glEnable(GL_COLOR_SUM);
}

if ( option == RO_Shadow_Volumes )
{
#ifdef DB_SV
glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 1.0);
glEnable(GL_COLOR_SUM);
glColor3f(0.5,0,0);
#endif
cyl.light_pos = light_location;
sphere.light_pos = light_location;

for ( BIter ball(balls); ball; )

{
ball2->position-ball1->position);
}
}
else
{
sphere.opt_texture = opt_ball_texture;

if ( opt_shader != SO_Instance )
{
if ( opt_shader == SO_Phong )
sp_phong->use();
else if ( opt_shader == SO_Fixed )
sp_fixed->use();

if ( opt_shader != SO_Fixed )
{
glUniform1i(2, opt_tri_cull);
glUniform2i(3, opt_tryout1, opt_tryout2);
}

for ( BIter ball(balls); ball; )
{
if ( ball->contact )
sphere.color = color_gray;
else if ( ball->mass > 0 && ball->mass < ball->mass_min )
sphere.color = color_red;
else if ( ball->mass > 0 && ball->locked )
sphere.color = color_pale_green;
else
sphere.color = ball->color;
}
{
ball2->position-ball1->position);
}
sp_fixed->use();
}
else
{
sp_instances->use();

if ( balls_pos_rad_bo == 0 )
{
glGenBuffers(1,&balls_color_bo);

/// SOLUTION -- Problem 2

/// SOLUTION -- Problem 3
glGenBuffers(1,&balls_velocity_bo);
}

if ( balls_size < balls.size() )
{
delete balls_color;

/// SOLUTION -- Problem 2
balls_color = new pColor[balls.size()];

/// SOLUTION -- Problem 3
//  Note that a four-component type, pCoor, used even
//  though velocity has three components.
balls_velocity = new pCoor[balls.size()];

balls_size = balls.size();
}

{

/// SOLUTION -- Problem 2
}

int color_change = 0;

for ( BIter ball(balls); ball; )
{
pColor color;
if ( ball->contact )
color = color_gray;
else if ( ball->mass > 0 && ball->mass < ball->mass_min )
color = color_red;
else if ( ball->mass > 0 && ball->locked )
color = color_pale_green;
else
color = ball->color;
if ( balls_color[ball] != color ) color_change++;
ball->idx = ball; // Position of ball in balls.
balls_color[ball->idx] = color;

/// SOLUTION -- Problem 3
balls_velocity[ball->idx] = ball->velocity;
}
{
/// SOLUTION -- Problem 2
}

GE();
glBufferData
// Amount of data (bytes) to copy.
GL_DYNAMIC_DRAW); // Hint about who, when, how accessed.

/// SOLUTION -- Problem 3
glBufferData
// Amount of data (bytes) to copy.
balls.size() * sizeof(balls_velocity[0]),
balls_velocity,
GL_DYNAMIC_DRAW); // Hint about who, when, how accessed.

{
glBufferData
(GL_ARRAY_BUFFER,
// Amount of data (bytes) to copy.
GL_STATIC_DRAW); // Hint about who, when, how accessed.

/// SOLUTION -- Problem 2
glBufferData
(GL_ARRAY_BUFFER,
// Amount of data (bytes) to copy.
GL_STATIC_DRAW); // Hint about who, when, how accessed.
}
if ( color_change )
{
glBufferData
// Amount of data (bytes) to copy.
balls.size() * sizeof(balls_color[0]),
balls_color,
GL_STATIC_DRAW); // Hint about who, when, how accessed.
}
GE();

glUniform1i(2, opt_tri_cull);
glUniform2i(3, opt_tryout1, opt_tryout2);

/// SOLUTION -- Problem 3

sphere.points_bo.bind();
glVertexPointer(3,GL_FLOAT,0,0);
glEnableClientState(GL_VERTEX_ARRAY);
GE();
glDrawArraysInstanced
(GL_TRIANGLE_STRIP,0,sphere.points_bo.elements,balls);
GE();

glBindBuffer(GL_ARRAY_BUFFER,0);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);

/// Homework 4 Problem 2 Solution Here (and other places).

/// SOLUTION -- Problem 2

glVertexPointer(2,GL_INT,0,0);
glEnableClientState(GL_VERTEX_ARRAY);
glBindBuffer(GL_ARRAY_BUFFER,0);
glDisableClientState(GL_VERTEX_ARRAY);
}

sp_fixed->use();
}

glDisable(GL_COLOR_SUM);
glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SINGLE_COLOR);
glLightfv(GL_LIGHT0, GL_SPECULAR, color_black);

//
// Render Platform
//
const int half_elements = platform_tile_coords.elements >> 3 << 2;

glEnable(GL_TEXTURE_2D);

// Set up attribute (vertex, normal, etc.) arrays.
//
glBindTexture(GL_TEXTURE_2D,texid_syl);
platform_tile_coords.bind();
glVertexPointer(3, GL_FLOAT, sizeof(platform_tile_coords.data[0]), 0);
glEnableClientState(GL_VERTEX_ARRAY);
glNormal3fv(platform_normal);
platform_tex_coords.bind();
glTexCoordPointer(2, GL_FLOAT,2*sizeof(float), 0);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);

// Write lighter-colored, textured tiles.
//
glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR,spec_color);
glMaterialf(GL_FRONT_AND_BACK,GL_SHININESS,2.0);
glColor3f(0.35,0.35,0.35);
glColor3f(0.55,0.55,0.55);

glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glBindBuffer(GL_ARRAY_BUFFER,0);
}

void
World::render()
{
// Get any waiting keyboard commands.
//
cb_keyboard();

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

/// Emit a Graphical Representation of Simulation State
//

// Understanding of the code below not required for introductory
// lectures.

// That said, much of the complexity of the code is to show
// the ball shadow and reflection.

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

glViewport(0, 0, win_width, win_height);
pError_Check();

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

glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
glDisable(GL_BLEND);

glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,1);
glLightfv(GL_LIGHT0, GL_POSITION, light_location);

glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION, 0.5);
glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 1.0);

pColor ambient_color(0x555555);

glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient_color);
glLightfv(GL_LIGHT0, GL_DIFFUSE, color_white * opt_light_intensity);
glLightfv(GL_LIGHT0, GL_AMBIENT, color_black);
glLightfv(GL_LIGHT0, GL_SPECULAR, color_white * opt_light_intensity);

glEnable(GL_LIGHT0);
glEnable(GL_LIGHTING);

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

// Common to all textures.
//
glActiveTexture(GL_TEXTURE0);
glEnable(GL_TEXTURE_2D);
glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,
GL_LINEAR_MIPMAP_LINEAR);
glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);
glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);

glEnable(GL_RESCALE_NORMAL);
glEnable(GL_NORMALIZE);

const double time_now = time_wall_fp();
const bool blink_visible = int64_t(time_now*3) & 1;

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

ogl_helper.fbprintf
("Code Compiled: %s\n",
#ifdef __OPTIMIZE__
"WITH OPTIMIZATION"
#else
#endif
);

const int cyl_sides = 20;
const int cyl_vtx = cyl_sides * 2 + 2;
const int cyl_vtx_tot = cyl_vtx * links.size();
const int ball_vtx_tot = sphere.points_bo.elements * balls.size();
const int vtx_total = cyl_vtx_tot + ball_vtx_tot;

ogl_helper.fbprintf
sphere.points_bo.elements,
cyl_vtx,
vtx_total);

ogl_helper.fbprintf
("Time Step: %8d  World Time: %11.6f  %s\n",
time_step_count, world_time,
opt_pause ? BLINK("PAUSED, 'p' to unpause, SPC or S-SPC to step.","") :
"Press 'p' to pause."
);

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

Ball& ball = *balls[0];

if ( 0 )
ogl_helper.fbprintf
("Head Ball Pos  [%5.1f,%5.1f,%5.1f] Vel [%+5.1f,%+5.1f,%+5.1f]\n",
ball.position.x,ball.position.y,ball.position.z,
ball.velocity.x,ball.velocity.y,ball.velocity.z );

ogl_helper.fbprintf
("Shader: %s  ('v' to change)  "
"Tryout 1: %s  ('y' to change)  Tryout 2: %s  ('Y' to change)\n",
opt_shader == SO_Fixed ? "FIXED" :
opt_shader == SO_Phong ? "PLAIN" :
opt_shader == SO_Instance ? "INSTANCE" : "other",
opt_tryout1 ? BLINK("ON","  ") : "OFF",
opt_tryout2 ? BLINK("ON","  ") : "OFF");

ogl_helper.fbprintf
("Cull %d-%s ('c' to change)  "
opt_tri_cull,
opt_tri_cull == 0 ? "OFF  " : opt_tri_cull == 1 ? "BACK " :
opt_tri_cull == 2 ? "FRONT" : "???",
opt_shadows ? "ON" : "OFF" );

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

const int half_elements = platform_tile_coords.elements >> 3 << 2;

//
// Render ball reflection.  (Will be blended with dark tiles.)
//

// Write stencil at location of dark (mirrored) tiles.
//
glDisable(GL_LIGHTING);
glEnable(GL_STENCIL_TEST);
glStencilFunc(GL_NEVER,2,2);
glStencilOp(GL_REPLACE,GL_KEEP,GL_KEEP);
platform_tile_coords.bind();
glVertexPointer(3, GL_FLOAT, sizeof(platform_tile_coords.data[0]), 0);
glEnableClientState(GL_VERTEX_ARRAY);
glEnable(GL_LIGHTING);
glDisableClientState(GL_VERTEX_ARRAY);
glBindBuffer(GL_ARRAY_BUFFER,0);

// Prepare to write only stenciled locations.
//
glStencilFunc(GL_EQUAL,2,2);
glStencilOp(GL_KEEP,GL_KEEP,GL_KEEP);

// Use a transform that reflects objects to other side of platform.
//
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glMultTransposeMatrixf(transform_mirror);

// Reflected front face should still be treated as the front face.
//
glFrontFace(GL_CW);

render_objects(RO_Normally);

glFrontFace(GL_CCW);
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glDisable(GL_STENCIL_TEST);

// Setup texture for platform.
//
glBindTexture(GL_TEXTURE_2D,texid_syl);

// Blend dark tiles with existing ball reflection.
//
glEnable(GL_STENCIL_TEST);
glBlendFunc(GL_CONSTANT_ALPHA,GL_ONE_MINUS_CONSTANT_ALPHA); // src, dst
glBlendColor(0,0,0,0.5);

glDepthFunc(GL_ALWAYS);

if ( opt_platform_texture )
{
glEnable(GL_TEXTURE_2D);
platform_tex_coords.bind();
glTexCoordPointer(2, GL_FLOAT,2*sizeof(float), 0);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
}

platform_tile_coords.bind();
glVertexPointer
(3, GL_FLOAT,sizeof(platform_tile_coords.data[0]), 0);
glEnableClientState(GL_VERTEX_ARRAY);
glNormal3fv(platform_normal);

if ( opt_platform_texture ) glEnable(GL_TEXTURE_2D);

// Write lighter-colored, textured tiles.
//
glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR,color_gray);
glMaterialf(GL_FRONT_AND_BACK,GL_SHININESS,2.0);
glColor3f(0.35,0.35,0.35);

// Write darker-colored, untextured, mirror tiles.
//
glEnable(GL_BLEND);
glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR,color_white);
glMaterialf(GL_FRONT_AND_BACK,GL_SHININESS,20);
glDisable(GL_TEXTURE_2D);
glColor3fv(color_lsu_spirit_purple);
glDisable(GL_BLEND);

glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
glBindBuffer(GL_ARRAY_BUFFER,0);
glDisable(GL_STENCIL_TEST);
glDepthFunc(GL_LESS);

glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient_color);

{
// Render.
//
render_objects(RO_Normally);
}
else
{
//
// First pass, render using only ambient light.
//
glDisable(GL_LIGHT0);

// Send balls, tiles, and platform to opengl.
// Do occlusion test too.
//
render_objects(RO_Normally);

//
// Second pass, add on light0.
//

// Turn off ambient light, turn on light 0.
//
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, color_black);
glEnable(GL_LIGHT0);

glClear(GL_STENCIL_BUFFER_BIT);

// Make sure that only stencil buffer written.
//
#ifndef DB_SV

// Don't waste time computing lighting.
//
glDisable(GL_LIGHTING);
#endif
glDisable(GL_TEXTURE_2D);

// Set up stencil test to count shadow volume surfaces: plus 1 for
// volume.
//
glEnable(GL_STENCIL_TEST);
// sfail, dfail, dpass
glStencilOpSeparate(GL_FRONT,GL_KEEP,GL_KEEP,GL_INCR_WRAP);
glStencilOpSeparate(GL_BACK,GL_KEEP,GL_KEEP,GL_DECR_WRAP);

// cast by light0 (light_location).  Shadowed locations will
// have a positive stencil value.  Routine will set viewer_shadow_volume
// to the number of view volumes containing the eye.
//

glEnable(GL_LIGHTING);

// Use stencil test to prevent writes to shadowed areas.
//
glStencilOp(GL_KEEP,GL_KEEP,GL_KEEP);

// Allow pixels to be re-written.
//
glDepthFunc(GL_LEQUAL);
glEnable(GL_BLEND);
glBlendFunc(GL_ONE,GL_ONE);

// Render.
//
render_objects(RO_Normally);

glDisable(GL_BLEND);
glDisable(GL_STENCIL_TEST);

}

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

pError_Check();

glColor3f(0.5,1,0.5);

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 bool shift = ogl_helper.keyboard_shift;
const float move_amt = shift ? 2.0 : 0.4;
const int opt_sides_before = opt_sides;

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 '1': ball_setup_1(); break;
case '2': ball_setup_2(); break;
case '3': ball_setup_3(); break;
case '4': ball_setup_4(); break;
case '5': ball_setup_5(); break;
case 'b': opt_move_item = MI_Ball; break;
case 'B': opt_move_item = MI_Ball_V; break;
case 'c': case 'C':
opt_tri_cull++; if ( opt_tri_cull == 3 ) opt_tri_cull = 0;
break;
case 'e': case 'E': opt_move_item = MI_Eye; break;
case 'g': case 'G': opt_gravity = !opt_gravity; break;
case 't': case 'T': opt_tail_lock = !opt_tail_lock; break;
case 'l': case 'L': opt_move_item = MI_Light; break;
case 'n': case 'N': opt_platform_texture = !opt_platform_texture; break;
case 'p': case 'P': opt_pause = !opt_pause; break;
case 'r': case 'R': opt_ride = !opt_ride; break;
case 's': case 'S': balls_stop(); break;
case 'w': case 'W': balls_twirl(); break;
case 'y': opt_tryout1 = !opt_tryout1; break;
case 'Y': opt_tryout2 = !opt_tryout2; break;
case ' ':
if ( shift ) opt_single_time_step = true; else opt_single_frame = true;
opt_pause = true;
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;
}

if ( opt_sides_before != opt_sides && last_setup == 3 )
ball_setup_3();

gravity_accel.y = opt_gravity ? -opt_gravity_accel : 0;

lock_update();

// 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_Ball:
if ( adj_t_stop == 0 )
break;
case MI_Light: light_location += adjustment; break;
case MI_Eye: eye_location += adjustment; break;
default: break;
}
modelview_update();
}

}

void
World::init()
{
mp.color_stressed = pColor(1,0,0); // Red, what else?

chain_length = 14;

opt_sides = 8;
variable_control.insert(opt_sides,"Scene 3 Object Number of Sides",1,3);

opt_time_step_duration = 0.0003;
variable_control.insert(opt_time_step_duration,"Time Step Duration");

distance_relaxed = 15.0 / chain_length;
opt_spring_constant = 15000;
variable_control.insert(opt_spring_constant,"Spring Constant");

opt_gravity_accel = 9.8;
opt_gravity = true;
gravity_accel = pVect(0,-opt_gravity_accel,0);
variable_control.insert(opt_gravity_accel,"Gravity");

opt_air_resistance = 0.04;
variable_control.insert(opt_air_resistance,"Air Resistance");

world_time = 0;
time_step_count = 0;
last_frame_wall_time = time_wall_fp();
frame_timer.work_unit_set("Steps / s");

ball_eye = NULL;
opt_ride = false;

opt_tri_cull = 0;

init_graphics();

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

"vs_main(); ",     // Name of vertex shader main routine.
"gs_main_simple();",
"fs_main();"       // Name of fragment shader main routine.
);
"vs_main_instances(); ",
"gs_main_simple();",
"fs_main();"
);

"vs_main(); ",     // Name of vertex shader main routine.
"fs_main();"       // Name of fragment shader main routine.
);

ball_setup_3();
lock_update();
}

Ball*
World::make_marker(pCoor position, pColor color)
{
Ball* const ball = new Ball;
ball->position = position;
ball->locked = true;
ball->velocity = pVect(0,0,0);
ball->mass = 0;
ball->contact = false;
ball->color = color;
return ball;
}

void
World::lock_update()
{
// This routine called when options like opt_head_lock might have
// changed.

// Update locked status.
//
if ( tail_ball ) tail_ball->locked = opt_tail_lock;

// Re-compute minimum mass needed for stability.
//
for ( BIter ball(balls); ball; ) ball->spring_constant_sum = 0;
const double dtis = pow( opt_time_step_duration, 2 );
{
b1->spring_constant_sum += opt_spring_constant;
b2->spring_constant_sum += opt_spring_constant;
}
for ( BIter ball(balls); ball; )
ball->mass_min = ball->spring_constant_sum * dtis;
}

void
World::balls_twirl()
{
if ( !head_ball || !tail_ball ) return;

for ( BIter ball(balls); ball; )
{
const float dist_along_axis = dot(b_to_top,axis);
const float lsq = b_to_top.mag_sq() - dist_along_axis * dist_along_axis;
if ( lsq <= 1e-5 ) { ball->velocity = pVect(0,0,0); continue; }
const float dist_to_axis = sqrt(lsq);
pNorm rot_dir = cross(b_to_top,axis);
ball->velocity += 2 * dist_to_axis * rot_dir;
}
}

void
World::objects_erase()
{
ball_eye = NULL;
balls.erase();
}

///
/// Physical Simulation Code
///

/// Initialize Simulation
//

void
World::ball_setup_1()
{
// Arrange and size balls to form a pendulum.
last_setup = 1;

pCoor first_pos(13.4,17.8,-9.2);
pVect delta_pos = pVect(distance_relaxed,0,0);

// Remove objects from the simulated objects lists, balls and links.
// The delete operator is used on objects in the lists.
//
objects_erase();

for ( int i=0; i<chain_length; i++ )
{
// Construct a new ball and add it to the simulated objects list (balls).
//
Ball* const ball = balls += new Ball;

// Initialize position and other information.
//
ball->position = first_pos + i * delta_pos;
ball->locked = false;
ball->velocity = pVect(0,0,0);
ball->mass = 4/3.0 * M_PI * pow(ball->radius,3);
ball->contact = false;

// If it's not the first ball link it to the previous ball.
if ( i > 0 ) links += new Link( ball, balls[i-1] );
}

// The balls pointed to by head_ball and tail_ball can be manipulated
// using the user interface (by pressing 'h' or 't', for example).
// Set these variables.
//
tail_ball = balls[balls-1];

opt_tail_lock = false;   // Tail ball can move freely.
}

void
World::make_truss(Truss_Info *truss_info)
{
/// Construct a truss based on members of truss_info.
//

//            <---- num_units (=9) ----------->
//  j
//  0         O---O---O---O---O---O---O---O---O     ^
//            |   |   |   |   |   |   |   |   |     |
//  1         O---O---O---O---O---O---O---O---O   num_sides (=3)
//            |   |   |   |   |   |   |   |   |     |
//  2         O---O---O---O---O---O---O---O---O     v
//
//      i ->  0   1   2   3   4   5   6   7   8
//
//  Note: Not all links are shown in the diagram above.

/// Truss_Info Inputs
//
//  truss_info->num_units
//    The number of sections in the truss (see diagram above).
//
//  truss_info->base_coors
//    A list containing num_sides coordinates, the coordinates of
//    balls at i=0 (see diagram above). (num_sides is the number of
//    elements in this list.)  These coordinates should all be
//    in the same plane.
//
//  truss_info->unit_length
//    A vector pointing from the ball at (i,j) to the ball at (i+1,j).
//
/// Truss_Info Outputs
//
//  truss_info->balls
//    A list that should be filled with the balls making up the truss.
//
//    A list that should be filled with the links making up the truss.

const int num_sides = truss_info->base_coors.occ();
const int num_units = truss_info->num_units;

// Lists to hold balls and links created for truss.
//
Balls& bprep = truss_info->balls;

// Create balls for truss.
//
for ( int i=0; i<num_units; i++ )
for ( pCoor bcoor; truss_info->base_coors.iterate(bcoor); )
{
Ball* const ball = bprep += new Ball;
ball->position = bcoor + i * truss_info->unit_length;
ball->locked = false;
ball->velocity = pVect(0,0,0);
ball->mass = 4/3.0 * M_PI * pow(ball->radius,3);
ball->contact = false;
}

//
for ( int i=0; i<num_units; i++ )
for ( int j=0; j<num_sides; j++ )
{
const int idx = j + num_sides * i;

// Retrieve the ball corresponding to (i,j).
//
Ball* const ball = bprep[idx];

// Compute the index of the ball at (i, (j-1) mod num_sides ).
//
const int pn_idx = idx + ( j == 0 ? num_sides - 1 : -1 );

// Insert link to neighbor ball with name i.
//
lprep += new Link( ball, bprep[pn_idx], color_gray );

// Insert links to balls with same i that are not neighbors.
//
if ( j == i % num_sides )
for ( int k = 2; k < num_sides-1; k++ )
( ball, bprep[ idx + (k+j)%num_sides - j ], color_white );

if ( i == 0 ) continue;

// Insert link to ball at (i-1,j).
//
lprep +=
new Link( ball, bprep[idx-num_sides], color_lsu_official_purple );

// Insert link to ball at (i-1, j-1 mod num_sides ).
//
lprep += new Link( ball, bprep[pn_idx-num_sides], color_green );
}
}

void
World::ball_setup_2()
{
last_setup = 2;

pCoor first_pos(13.4,17.8,-9.2);
const float spacing = distance_relaxed;
pVect delta_pos = pVect(spacing*0.05,-spacing,0);
pNorm loc_y = delta_pos;
pNorm loc_x = pVect(0,0,1);
pNorm loc_z = cross(loc_y,loc_x);

// Erase the existing balls and links.
//
objects_erase();

Truss_Info truss_info;

truss_info.num_units = chain_length;
truss_info.unit_length = delta_pos;

const int sides = 4;

for ( int j=0; j<sides; j++ )
{
const double angle = double(j)/sides*2*M_PI;
pCoor chain_first_pos =
first_pos
+ 0.5 * spacing * cos(angle) * loc_x
+ 0.5 * spacing * sin(angle) * loc_z;

truss_info.base_coors += chain_first_pos;
}

make_truss(&truss_info);

// Insert links to balls at either end.
//
head_ball = balls += new Ball;
for ( int j=0; j<sides; j++ )

tail_ball = balls += new Ball;
tail_ball->position = first_pos + chain_length * delta_pos;

const int bsize = truss_info.balls.size();

for ( int j=0; j<sides; j++ )
color_chocolate );

for ( BIter ball(balls); ball; )
{
ball->locked = false;
ball->velocity = pVect(0,0,0);
ball->mass = 4/3.0 * M_PI * pow(ball->radius,3);
ball->contact = false;
}

balls += truss_info.balls;

opt_tail_lock = false;
}

void
World::ball_setup_3()
{
last_setup = 3;

pCoor first_pos(13.4,17.8,-9.2);
const int factor = 2;
const float spacing = distance_relaxed / factor;
pVect delta_pos = pVect(spacing*0.05,-spacing,0);
pNorm delta_dir = delta_pos;
pNorm tan_dir = pVect(0,0,1);
pNorm um_dir = cross(tan_dir,delta_dir);

// Erase the existing balls and links.
//
objects_erase();

Truss_Info truss_info;

const int length = chain_length * factor;

truss_info.num_units = length;
truss_info.unit_length = delta_pos;

const int sides = opt_sides;

for ( int j=0; j<sides; j++ )
{
const double angle = double(j)/sides*2*M_PI;
pCoor chain_first_pos =
first_pos
+ 0.5 * spacing * cos(angle) * tan_dir
+ 0.5 * spacing * sin(angle) * um_dir;

truss_info.base_coors += chain_first_pos;
}

make_truss(&truss_info);

const int idx_center = length / 2 * sides;

for ( int i=0; i<sides; i++ )
{
Truss_Info ti;
ti.num_units = length / 2;
const int idx_1 = idx_center + ( i == 0 ? sides - 1 : i - 1 );
const int idx_2 = idx_center + i;

Ball* const b0 = truss_info.balls[idx_1];
Ball* const b1 = truss_info.balls[idx_1 - sides];
Ball* const b2 = truss_info.balls[idx_2 - sides];
Ball* const b3 = truss_info.balls[idx_2];
ti.base_coors += b0->position;
ti.base_coors += b1->position;
ti.base_coors += b2->position;
ti.base_coors += b3->position;

make_truss(&ti);
balls += ti.balls;

int tsz = ti.balls.size();

if ( i == 2 )
{
ball_eye = ti.balls[tsz-2];
ball_down = ti.balls[tsz-1];
}
else if ( i == 1 )
{
ball_gaze = ti.balls[tsz/2];
}

}

// Insert links to balls at either end.
//
head_ball = balls += new Ball;
for ( int j=0; j<sides; j++ )

tail_ball = balls += new Ball;
tail_ball->position = first_pos + length * delta_pos;

const int bsize = truss_info.balls.size();

for ( int j=0; j<sides; j++ )
color_chocolate );

for ( BIter ball(balls); ball; )
{
ball->locked = false;
ball->velocity = pVect(0,0,0);
ball->mass = 4/3.0 * M_PI * pow(ball->radius,3);
ball->contact = false;
}

balls += truss_info.balls;

opt_tail_lock = false;
}

void
World::ball_setup_4()
{
//  last_setup = 4;
}

void
World::ball_setup_5()
{
//  last_setup = 5;
}

/// Advance Simulation State by delta_t Seconds
//
void
World::time_step_cpu(double delta_t)
{
time_step_count++;

// Smoothly move ball in response to user input.
//
{
}

for ( BIter ball(balls); ball; )
ball->force = ball->mass * gravity_accel;

{

// Spring Force from Neighbor Balls
//

// Construct a normalized (Unit) Vector from ball to neighbor.
//
pNorm ball_to_neighbor(ball1->position,ball2->position);

const float distance_between_balls = ball_to_neighbor.magnitude;

// Compute the speed of ball towards neighbor_ball.
//
pVect delta_v = ball2->velocity - ball1->velocity;
float delta_s = dot( delta_v, ball_to_neighbor );

// Compute by how much the spring is stretched (positive value)
// or compressed (negative value).
//
const float spring_stretch =

&& distance_between_balls
{
if ( distance_between_balls
const float factor =
/ ( mp.link_snap_thd - 1 );
+ factor * mp.color_stressed;
}
else
{
}

// Determine whether spring is gaining energy (whether its length
// is getting further from its relaxed length).
//
const bool gaining_e = ( delta_s > 0.0 ) == ( spring_stretch > 0 );

// Use a smaller spring constant when spring is loosing energy,
// a quick and dirty way of simulating energy loss due to spring
// friction.
//
const float spring_constant =
gaining_e ? opt_spring_constant : opt_spring_constant * 0.7;

ball1->force += spring_constant * spring_stretch * ball_to_neighbor;
ball2->force -= spring_constant * spring_stretch * ball_to_neighbor;
}

///
/// Update Position of Each Ball
///

for ( BIter ball(balls); ball; )
{
if ( ball->locked )
{
ball->velocity = pVect(0,0,0);
continue;
}

// Update Velocity
//
// This code assumes that force on ball is constant over time
// step. This is clearly wrong when balls are moving with
// respect to each other because the springs are changing
// length. This inaccuracy will make the simulation unstable
// when spring constant is large for the time step.
//
const float mass = max( ball->mass, ball->mass_min );

pVect delta_v = ( ball->force / mass ) * delta_t;

if ( platform_collision_possible(ball->position) && ball->position.y < 0 )
{
const float spring_constant_plat =
ball->velocity.y < 0 ? 100000 : 50000;
const float fric_coefficient = 0.1;
const float force_up = -ball->position.y * spring_constant_plat;
const float delta_v_up = force_up / mass * delta_t;
const float fric_force_mag = fric_coefficient * force_up;
pNorm surface_v(ball->velocity.x,0,ball->velocity.z);
const float delta_v_surf = fric_force_mag / mass * delta_t;

if ( delta_v_surf > surface_v.magnitude )
{
// Ignoring other forces?
delta_v =
pVect(-ball->velocity.x,delta_v.y,-ball->velocity.z);
}
else
{
delta_v -= delta_v_surf * surface_v;
}
delta_v.y += delta_v_up;
}

ball->velocity += delta_v;

// Air Resistance
//
const double fs = pow(1+opt_air_resistance,-delta_t);
ball->velocity *= fs;

// Update Position
//
// Assume that velocity is constant.
//
ball->position += ball->velocity * delta_t;
}
}

bool
World::platform_collision_possible(pCoor pos)
{
// Assuming no motion in x or z axes.
//
return pos.x >= platform_xmin && pos.x <= platform_xmax
&& pos.z >= platform_zmin && pos.z <= platform_zmax;
}

/// External Modifications to State
//
//   These allow the user to play with state while simulation
//   running.

// Move the ball.
//
void Ball::translate(pVect amt) {position += amt;}

// Add velocity to the ball.
//
void Ball::push(pVect amt) {velocity += amt;}

// Set the velocity to zero.
//
void Ball::stop() {velocity = pVect(0,0,0); }

// Set the velocity and rotation (not yet supported) to zero.
//
void Ball::freeze() {velocity = pVect(0,0,0); }

void World::balls_translate(pVect amt)
{ for ( BIter ball(balls); ball; ) ball->translate(amt); }
void World::balls_push(pVect amt)
{ for ( BIter ball(balls); ball; ) ball->push(amt); }
void World::balls_stop()
{ for ( BIter ball(balls); ball; ) ball->stop(); }
void World::balls_freeze(){balls_stop();}

void
World::render_my_piece()
{
}

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

frame_timer.phys_start();
const double time_now = time_wall_fp();

if ( !opt_pause || opt_single_frame || opt_single_time_step )
{

// Amount of time since the user saw the last frame.
//
const double wall_delta_t = time_now - last_frame_wall_time;

// Compute amount by which to advance simulation state for this frame.
//
const double duration =
opt_single_time_step ? opt_time_step_duration :
opt_single_frame ? 1/30.0 :
wall_delta_t;

const double world_time_target = world_time + duration;
const double wall_time_limit = time_now + 0.05;

while ( world_time < world_time_target )
{
time_step_cpu(opt_time_step_duration);
world_time += opt_time_step_duration;
const double time_right_now = time_wall_fp();
if ( time_right_now > wall_time_limit ) break;
}

// Reset these, just in case they were set.
//
opt_single_frame = opt_single_time_step = false;
}

last_frame_wall_time = time_now;

if ( opt_ride && ball_eye )
{
pNorm b_eye_down(ball_eye->position,ball_down->position);
pVect b_eye_up = -b_eye_down;
pCoor eye_pos = ball_eye->position + 2.2 * ball_eye->radius * b_eye_up;
pNorm b_eye_direction(eye_pos,ball_gaze->position);

pNorm b_eye_left = cross(b_eye_direction,b_eye_up);
pMatrix_Translate center_eye(-eye_pos);
pMatrix rotate; rotate.set_identity();
for ( int i=0; i<3; i++ ) rotate.rc(0,i) = b_eye_left.elt(i);
for ( int i=0; i<3; i++ ) rotate.rc(1,i) = b_eye_up.elt(i);
for ( int i=0; i<3; i++ ) rotate.rc(2,i) = -b_eye_direction.elt(i);
modelview = rotate * center_eye;
pMatrix reflect; reflect.set_identity(); reflect.rc(1,1) = -1;
transform_mirror = modelview * reflect * invert(modelview);
}

render();
}

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

glDisable(GL_DEBUG_OUTPUT);
glDebugMessageControl(GL_DONT_CARE,GL_DONT_CARE,