/// LSU EE 4702-1 (Fall 2021), GPU Programming
//
 /// Simple Demo of Dynamic Simulation, Graphics Code

 // This file includes graphics code needed by the main file. The code
 // in this file does not need to be understood early in the semester.

#define MAIN_INCLUDE
#include <vhelper.h>
#include <vstroke.h>
#include <gp/coord.h>
#include <gp/pstring.h>
#include <gp/misc.h>

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

#include "shapes.h"


enum Render_Option { RO_Normally, RO_Simple, RO_Mirrored, RO_Shadow_Volumes };

#include <gp/colors.h>

class World {
public:
  World(pVulkan_Helper &fb)
    :vh(fb), ff_state(fb.qs),
     frame_timer(vh.frame_timer),
     shapes(ff_state), sphere(ff_state), cone(ff_state)
  {init();}
  World() = delete;
  void init();
  void init_graphics();
  void run();
  void frame_callback();
  void render(vk::CommandBuffer& cb);
  void render_objects(vk::CommandBuffer& cb, Render_Option render_option);
  void cb_keyboard();
  void modelview_update();
  void finish();

  pVulkan_Helper& vh;
  VFixed_Function_State_Manager ff_state;
  pFrame_Timer& frame_timer;
  Shapes shapes;

  Sphere sphere;
  Cone cone;

  pVariable_Control variable_control;
  double world_time;
  double last_frame_wall_time;
  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_head_lock, opt_tail_lock;
  bool opt_time_step_easy;

  // Tiled platform for ball.
  //
  float platform_xmin, platform_xmax, platform_zmin, platform_zmax;
  VVertex_Buffer_Set bset_platform_tex, bset_platform_mir;
  VPipeline pipe_platform_mir, pipe_platform_tex;

  vk::Sampler sampler;
  VTexture texid_syl;
  VTexture texid_emacs;

  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;

  VTransform transform;
  VBufferV<Uni_Lighting> uni_light, uni_light_ambient, uni_light_all;
  VBufferV<Uni_Lighting> uni_light_reflected;
  VBufferV<Uni_Lighting> *puni_light_curr;

  pMatrix modelview;

  void ball_setup_1();
  void ball_setup_2();
  void ball_setup_3();
  void ball_setup_4();
  void ball_setup_5();
  void time_step_cpu_easy(double);
  void time_step_cpu_full(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);

  float opt_spring_constant;
  float opt_air_resistance;
  float distance_relaxed;
  int chain_length;
  Ball *balls;
};


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

  ///
  /// Graphical Model Initialization
  ///

  opt_head_lock = false;
  opt_tail_lock = false;

  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.init( vh.qs, P_Image_Read("gpup.png",255) );
  texid_emacs.init( vh.qs, P_Image_Read("mult.png",-1) );

  // Common to all textures.
  //
  sampler = vh.qs.dev.createSampler
    ( { {},
        vk::Filter::eLinear, vk::Filter::eLinear,
        vk::SamplerMipmapMode::eLinear,
        // Also: eRepeat, eMirroredRepeat, eClampToEdge, etc.
        vk::SamplerAddressMode::eRepeat,
        vk::SamplerAddressMode::eRepeat,
        vk::SamplerAddressMode::eRepeat,
        0.0f,
        false, // anisotropyEnable,
        16.0f,
        false,
        vk::CompareOp::eNever,
        0.0f, VK_LOD_CLAMP_NONE,  // min and max LOD
        vk::BorderColor::eFloatOpaqueBlack } );

  opt_light_intensity = 100.2;
  light_location = pCoor(platform_xmax,platform_xmax,platform_zmin);
  for ( auto& ul: { &uni_light, &uni_light_all,
                    &uni_light_ambient, &uni_light_reflected } )
    ul->init(vh.qs,vk::BufferUsageFlagBits::eUniformBuffer);

  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.transform = &transform;
  sphere.ppuni_light = &puni_light_curr;
  sphere.texture_set(sampler, texid_emacs);
  sphere.init(40);

  cone.transform = &transform;
  cone.ppuni_light = &puni_light_curr;

  platform_update();
  modelview_update();
}

void
World::run()
{
  vh.message_loop_spin();
}


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;

  if ( !pipe_platform_mir )
    pipe_platform_mir
      .init( ff_state )
      .color_uniform_set( color_lsu_spirit_purple )
      .follow_uni_light( &puni_light_curr )
      .topology_set( vk::PrimitiveTopology::eTriangleList )
      .create( vh.qs.render_pass );
  bset_platform_mir.reset( pipe_platform_mir ).tolerate_tcoords();

  if ( !pipe_platform_tex )
    pipe_platform_tex
      .init( ff_state )
      .color_uniform_set( color_white )
      .follow_uni_light( &puni_light_curr )
      .topology_set( vk::PrimitiveTopology::eTriangleList )
      .use_texture( sampler, texid_syl )
      .create( vh.qs.render_pass );
  bset_platform_tex.reset( pipe_platform_tex );

  const pVect platform_normal(0,1,0);
  pTCoor t00(trmin,tsmin), t01(trmin,tsmax), t10(trmax,tsmin), t11(trmax,tsmax);
  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 )
        {
          auto& bset = even ? bset_platform_tex : bset_platform_mir;
          const float z1 = z+zdelta;
          pCoor p00(x0,y,z), p01(x0,y,z1), p10(x1,y,z), p11(x1,y,z1);

          bset << t11 << platform_normal << p00;
          bset << t10 << platform_normal << p01;
          bset << t00 << platform_normal << p11;

          bset << t11 << platform_normal << p00;
          bset << t00 << platform_normal << p11;
          bset << t01 << platform_normal << p10;

          even = !even;
        }
    }

  bset_platform_mir.to_dev();
  bset_platform_tex.to_dev();
}

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_objects(vk::CommandBuffer& cb, Render_Option option)
{
  //  const float shininess_ball = 5;
  pColor spec_color(0.2,0.2,0.2);

  cone.apex_radius = 1; cone.set_color(color_lsu_spirit_purple);
  if ( option == RO_Shadow_Volumes )
    {
      cone.light_pos = light_location;
      sphere.light_pos = light_location;

      for ( int i=0; i<chain_length; i++ )
        sphere.render_shadow_volume(cb,balls[i].radius,balls[i].position);
      for ( int i=1; i<chain_length; i++ )
        {
          Ball *const ball1 = &balls[i-1];
          Ball *const ball2 = &balls[i];
          cone.render_shadow_volume
            (cb,ball1->position,0.3*ball1->radius,
             ball2->position-ball1->position);
        }
    }
  else
    {
      for ( int i=0; i<chain_length; i++ )
        {
          if ( balls[i].contact )
            sphere.color = color_gray;
          else if ( i == 0 && opt_head_lock
                    || i == chain_length-1 && opt_tail_lock )
            sphere.color = color_pale_green;
          else
            sphere.color = color_lsu_spirit_gold;
          sphere.render(cb,balls[i].radius,balls[i].position);
        }
      for ( int i=1; i<chain_length; i++ )
        {
          Ball *const ball1 = &balls[i-1];
          Ball *const ball2 = &balls[i];
          cone.render(cb,ball1->position,0.3*ball1->radius,
                      ball2->position-ball1->position);
        }
    }

  if ( option == RO_Shadow_Volumes ) return;

  //
  // Render Platform
  //
  transform.use_global_for(pipe_platform_tex);
  pipe_platform_tex.record_draw(cb,bset_platform_tex);
}

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

  /// 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 = vh.get_width();
  const int win_height = vh.get_height();
  const float aspect = float(win_width) / win_height;

  transform.at_start();
  transform.eye_from_global_set( modelview );
  const float xr = .08;
  transform.clip_from_eye_set
    ( pMatrix_Scale(1,-1,1)
      * pMatrix_Frustum(-xr,xr,-xr/aspect,xr/aspect,.1,5000) );
  // pMatrix_Frustum: left, right, bottom, top, near, far

  vh.clearValues[0].color =
    vk::ClearColorValue( array<float, 4>( { { 0, 0, 0, 0.5 } } ) );

  /// Lighting
  //
  auto& light_all = uni_light_all->cgl_LightSource[0];

  light_all.position = transform.eye_from_global * light_location;

  light_all.constantAttenuation = 0.5;
  light_all.linearAttenuation = 1.0;
  light_all.quadraticAttenuation = 0;

  pColor ambient_color(0x555555);

  uni_light_all->cgl_LightModel.ambient = ambient_color;

  light_all.diffuse = opt_light_intensity * color_white;
  light_all.ambient = color_black;
  light_all.specular = opt_light_intensity * color_white;

  uni_light_reflected = uni_light_all.get_val();
  uni_light_reflected->cgl_LightSource[0].position =
    transform.eye_from_global * pMatrix_Scale(1,-1,1) * light_location;

  uni_light_ambient = uni_light_all.get_val();
  auto& light_amb = uni_light_ambient->cgl_LightSource[0];
  light_amb.diffuse = color_black;
  light_amb.specular = color_black;

  uni_light = uni_light_all.get_val();
  uni_light->cgl_LightModel.ambient = color_black;

  uni_light.to_dev();
  uni_light_ambient.to_dev();
  uni_light_all.to_dev();
  uni_light_reflected.to_dev();

  puni_light_curr = &uni_light_all;

  const double time_now = time_wall_fp();
  const bool blink_visible = int64_t(time_now*3) & 1;
# define BLINK(txt,pad) ( blink_visible ? txt : pad )

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

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

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

  Ball& ball = balls[0];

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

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


  transform.use_global_for(pipe_platform_mir);

  vk::ColorComponentFlags clr_all
    ( vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG |
      vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA );

  const bool hide_platform = false;
  const bool opt_mirror = true;
  const bool opt_shadows = true;
  const bool opt_shadow_volumes = false;

  if ( !hide_platform )
    {
      // Save current state of fixed-functionality state.
      //
      ff_state.push();

      if ( opt_mirror )
        {
          /// Mirror Effect
          //
          //  - Step 1:  Write location of mirrors into stencil buffer.
          //
          //  - Step 2a: Prepare a matrix that transforms to mirrored location.
          //  - Step 2b: Set up blending to blend mirrored objects with mirrors.
          //
          //  - Step 3:  Render all objects except mirrors.

          ///  Step 1: Write location of mirrors into stencil buffer.
          //
          //   Write stencil value 2 at location of dark (mirrored) tiles.
          //

          vk::StencilOpState s_op_apply
            ( vk::StencilOp::eReplace, vk::StencilOp::eKeep,
              vk::StencilOp::eKeep, vk::CompareOp::eNever, 2, 2, 2 );
          // sfail, spass, dfail, compare;  cmask, wmask, ref

          ff_state.pipelineDepthStencilStateCreateInfo_get()
            .setStencilTestEnable(true)
            .setFront(s_op_apply).setBack(s_op_apply);

          // Render mirrored tiles (but only to write stencil buffer).
          //
          pipe_platform_mir.record_draw(cb,bset_platform_mir);

          cb.nextSubpass(vk::SubpassContents::eInline); vh.qs.subpass++;

          // Prepare to write only stenciled locations.
          //
          vk::StencilOpState s_op_use
            ( vk::StencilOp::eKeep, vk::StencilOp::eKeep,
              vk::StencilOp::eKeep, vk::CompareOp::eEqual, 2, 2, 2 );
          // sfail, spass, dfail, compare;  cmask, wmask, ref
          //
          // Stencil test passes if stencil value = 2.
          // Don't change stencil buffer.

          ff_state.pipelineDepthStencilStateCreateInfo_get()
            .setFront(s_op_use).setBack(s_op_use);

          ///  Step 2a: Prepare a matrix that transforms to mirrored location.
          //   Use a transform that reflects objects to other side of platform.
          //
          const pMatrix prev = transform.eye_from_global;
          const pMatrix_Scale reflect(1,-1,1);
          transform.eye_from_global = prev * reflect;

          // Reflected front face should still be treated as the front face.
          //
          ff_state.pipelineRasterizationStateCreateInfo_get()
            .setFrontFace(vk::FrontFace::eClockwise);

          puni_light_curr = &uni_light_reflected;

          /// Step 3:  Render all objects.

          render_objects(cb,RO_Mirrored);

          transform.eye_from_global = prev;

          ff_state.pipelineRasterizationStateCreateInfo_get()
            .setFrontFace(vk::FrontFace::eCounterClockwise);
        }

      // blend operation, blend factors, blend constants
      ff_state.pipelineColorBlendStateCreateInfo_get()
        .setBlendConstants( array<float,4>{.5,.5,.5,.5} );

      ff_state.pipelineColorBlendAttachmentState_get() =
        vk::PipelineColorBlendAttachmentState
        ( true,                   // blendEnable
          vk::BlendFactor::eConstantAlpha,  // srcColorBlendFactor
          vk::BlendFactor::eConstantAlpha,  // dstColorBlendFactor
          vk::BlendOp::eAdd,       // colorBlendOp
          vk::BlendFactor::eOne,   // srcAlphaBlendFactor
          vk::BlendFactor::eZero,  // dstAlphaBlendFactor
          vk::BlendOp::eAdd,       // alphaBlendOp
          clr_all      // colorWriteMask
          );

      puni_light_curr = &uni_light_all;;
      pipe_platform_mir.record_draw(cb,bset_platform_mir);

      ff_state.pop();
    }

  if ( !opt_shadows )
    {
      // Render.
      //
      render_objects(cb,RO_Normally);
    }
  else
    {
      //
      // First pass, render using only ambient light.
      //
      puni_light_curr = &uni_light_ambient;

      // Send balls, tiles, and platform to opengl.
      //
      render_objects(cb,RO_Normally);

      //
      // Second pass, find un-shadowed areas and add on light0.
      //


      /// Write Shadow Volumes
      //
      vk::ClearAttachment clr_att
        ( vk::ImageAspectFlagBits::eStencil, 0,
          vk::ClearDepthStencilValue(0,0) );
      vk::ClearRect clr_rect( vk::Rect2D({}, vh.s_extent), 0, 1 );
      cb.clearAttachments( clr_att, clr_rect );

      ff_state.push();

      vk::StencilOpState s_op_apply_f
        ( vk::StencilOp::eKeep, vk::StencilOp::eIncrementAndWrap,
          vk::StencilOp::eKeep, vk::CompareOp::eAlways, -1, -1, 0 );
      vk::StencilOpState s_op_apply_b
        ( vk::StencilOp::eKeep, vk::StencilOp::eDecrementAndWrap,
          vk::StencilOp::eKeep, vk::CompareOp::eAlways, -1, -1, 0 );
      // sfail, spass, dfail, compare; c-mask, w-mask, ref
      // ref is -1 (all 1's) because we want to operate on all bits in
      // stencil value.

      ff_state.pipelineDepthStencilStateCreateInfo_get()
        .setStencilTestEnable(true)
        .setDepthWriteEnable(false)
        .setFront(s_op_apply_f).setBack(s_op_apply_b);

      if ( !opt_shadow_volumes )
        {
          ff_state.pipelineColorBlendAttachmentState_get()
            .setColorWriteMask( {} );
        }
      else
        {
          ff_state.pipelineColorBlendAttachmentState_get() =
            vk::PipelineColorBlendAttachmentState
            ( true,                   // blendEnable
              vk::BlendFactor::eConstantAlpha,  // srcColorBlendFactor
              vk::BlendFactor::eConstantAlpha,  // dstColorBlendFactor
              vk::BlendOp::eAdd,       // colorBlendOp
              vk::BlendFactor::eOne,   // srcAlphaBlendFactor
              vk::BlendFactor::eZero,  // dstAlphaBlendFactor
              vk::BlendOp::eAdd,       // alphaBlendOp
              clr_all      // colorWriteMask
              );

          ff_state.pipelineColorBlendStateCreateInfo_get()
            .setBlendConstants( array<float,4>{.5,.5,.5,.5} );

          ff_state.pipelineDepthStencilStateCreateInfo_get()
            .setDepthWriteEnable(true);
        }

      // Write stencil with shadow locations based on shadow volumes
      // cast by light0 (light_location).  Shadowed locations will
      // have a positive stencil value.
      //
      render_objects(cb,RO_Shadow_Volumes);

      cb.nextSubpass(vk::SubpassContents::eInline); vh.qs.subpass++;

      // Use stencil test to prevent writes to shadowed areas.
      //
      vk::StencilOpState s_op_apply_fb
        ( vk::StencilOp::eKeep, vk::StencilOp::eKeep,
          vk::StencilOp::eKeep, vk::CompareOp::eEqual, -1, -1, 0 );
      // sfail, spass, dfail, compare;  c-mask, w-mask, ref
      //
      // Stencil test passes if stencil value == 0.

      ff_state.pipelineDepthStencilStateCreateInfo_get()
        .setFront(s_op_apply_fb).setBack(s_op_apply_fb)
        .setDepthCompareOp(vk::CompareOp::eEqual);

      // Allow pixels to be re-written.
      //
      vk::ColorComponentFlags cmask_all
        ( vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG |
          vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA );

      ff_state.pipelineColorBlendAttachmentState_get() =
        vk::PipelineColorBlendAttachmentState
        ( true,                   // blendEnable
          vk::BlendFactor::eOne,  // srcColorBlendFactor
          vk::BlendFactor::eOne,  // dstColorBlendFactor
          vk::BlendOp::eAdd,       // colorBlendOp
          vk::BlendFactor::eOne,   // srcAlphaBlendFactor
          vk::BlendFactor::eOne,  // dstAlphaBlendFactor
          vk::BlendOp::eAdd,       // alphaBlendOp
          cmask_all      // colorWriteMask
          );

      puni_light_curr = &uni_light;

      // Render.
      //
      render_objects(cb,RO_Normally);

      ff_state.pop();
    }

  puni_light_curr = &uni_light_all;

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


void
World::cb_keyboard()
{
  const int key = vh.keyboard_key_get();
  if ( !key ) return;

  pVect adjustment(0,0,0);
  pVect user_rot_axis(0,0,0);
  const bool shift = vh.keyboard_shift;
  const float move_amt = shift ? 2.0 : 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 '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 'e': case 'E': opt_move_item = MI_Eye; break;
  case 'g': case 'G': opt_gravity = !opt_gravity; break;
  case 'h': case 'H': opt_head_lock = !opt_head_lock; break;
  case 't': case 'T': opt_tail_lock = !opt_tail_lock; break;
  case 'l': case 'L': opt_move_item = MI_Light; break;
  case 'p': case 'P': opt_pause = !opt_pause; break;
  case 's': case 'S': balls_stop(); break;
  case 'v': case 'V': opt_time_step_easy = !opt_time_step_easy; break;
  case ' ': 
    if ( shift ) opt_single_time_step = true; else opt_single_frame = true;
    opt_pause = true; 
    break;
  case FB_KEY_TAB:
    if ( !shift ) { variable_control.switch_var_right(); break; }
  case 96: variable_control.switch_var_left(); break;
  case '-':case '_': variable_control.adjust_lower(); break;
  case '+':case '=': variable_control.adjust_higher(); break;
  default: printf("Unknown key, %d\n",key); break;
  }

  gravity_accel.y = opt_gravity ? -opt_gravity_accel : 0;

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

}

void
World::finish()
{
  uni_light.destroy();
  uni_light_all.destroy();
  uni_light_ambient.destroy();
  uni_light_reflected.destroy();
  sphere.destroy();
  pipe_platform_tex.destroy();
  pipe_platform_mir.destroy();
  bset_platform_mir.destroy();
  bset_platform_tex.destroy();
  texid_syl.destroy();
  texid_emacs.destroy();
  vh.dev.destroySampler(sampler);
  shapes.destroy();
  cone.destroy();

  vh.finish();
}