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

/// Purpose
//
//   Demonstrate use of coordinate and vector classes, and basic transforms.


/// What Code Does

// Simulates a ball bouncing over a platform. The platform consists of
// tiles, some are purple-tinted mirrors (showing a reflection of the
// ball), the others show the course syllabus. The ball can be
// manipulated by the user.


///  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.
 //   'b': Move ball. (Change position but not velocity.)
 //   'B': Push ball. (Add 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.

 /// Text Zoom and Screenshot
 //  'Ctrl' '+'  or  'Ctrl' '=',  and  'Ctrl' '-'  or  'Ctrl' '_', 
 //         Increase and decrease green text size.
 //  'F12'  Write screenshot to file.

 /// Simulation Options
 //  (Also see variables below.)
 //
 //  'p'    Pause simulation. (Press again to resume.)
 //  's'    Stop ball.
 //  'S'    Freeze ball. (Set velocity of all vertices to zero.)
 //  'g'    Turn gravity on and off.
 //  '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.
 //  VAR Gravity - Gravitational acceleration. (Turn on/off using 'g'.)


// Include handy classes for coordinates (pCoor), vectors (pVect), etc.
//
#include <gp/coord.h>


// Object Holding Ball State
//
class Ball {
public:

  // Note: pCoor is a coordinate, pVect is a vector.

  pCoor position;  // Ball Position.
  pVect velocity;  // Ball Velocity.

  // Member functions to manipulate state of ball.
  //
  void push(pVect amt);
  void translate(pVect amt);
  void stop();
};


//
// Include file containing graphical code for ball, something to be
// skipped in this simple example.
//
#include "demo-1c-coord-graphics.cc"


void
World::init()
{
  ///
  /// Physical Model Initialization
  ///

  // Set initial position to a visibly interesting point.
  //

  ball.position = pCoor( 12.1, 22, 11.4 );
  ball.velocity = pVect(0,0,0);

  // Time in our simulated world.
  //
  world_time = 0;

  //
  // Set acceleration vector based on scalar.
  //

  // A user-adjustable scalar.
  //
  opt_gravity_accel = 9.8;

  // The vector needed for calculations.
  //
  gravity_accel = pVect( 0, -opt_gravity_accel, 0 );

  // Make gravity adjustable using user interface.
  //
  variable_control.insert(opt_gravity_accel,"Gravity");

  // User interface control for turning gravity on and off.
  //
  opt_gravity = true;       // If false, gravity doesn't act on ball.

  // Position of platform that the ball bounces on.
  //
  platform_xmin = -40; platform_xmax = 40;
  platform_zmin = -40; platform_zmax = 40;


  /// Create Card 1 -- Set corners individually.
  //
  card1 = new Card();   // Instantiate card.
  cards += card1;       // Put card in list of cards to render.

  // Set upper left from a constant coordinate (pos1).
  pCoor pos1( 12, 10, 21.4 );
  card1->upper_left = pos1;
  card1->upper_right = pos1;
  card1->upper_right.x += 5;
  card1->lower_left = card1->upper_left + pVect(0,-1,0);
  card1->lower_right = card1->upper_right + pVect(0,-1,0);

  /// Create Card 2 -- Position card 2 just to left of card1.
  //  Demonstrates vector addition to translate points.
  //
  card2 = new Card(card1);
  cards += card2;
  card2->upper_right = card1->upper_left; // Cards will touch.
  // Compute distance between respective corners.
  pVect card_offset = card2->upper_right - card1->upper_right;
  // Use distance to set other three corners with vector addition.
  card2->upper_left = card1->upper_left + card_offset;
  card2->lower_right = card1->lower_right + card_offset;
  card2->lower_left = card1->lower_left + card_offset;


  /// Create Card 3 -- Position further to left of card 2 and a little up.
  //
  pVect card_offset2 = card_offset * 1.1 + pVect(0,0.2,0);
  Card* const card3 = new Card();
  cards += card3;
  // Use vector addition to translate card2 coordinates.
  card3->upper_left  = card2->upper_left + card_offset2;
  card3->upper_right = card2->upper_right + card_offset2;
  card3->lower_right = card2->lower_right + card_offset2;
  card3->lower_left  = card2->lower_left + card_offset2;

  /// Create Card 4 
  //  Demonstrates use of matrix multiplication for translation.
  //
  Card* const card4 = new Card();
  cards += card4;

  // Construct a translation matrix based on vector card_offset2.
  pMatrix_Translate trans_offset3(card_offset2);

  // Use matrix multiplication to translate card3 coordinates.
  card4->upper_left  = trans_offset3 * card3->upper_left;
  card4->upper_right = trans_offset3 * card3->upper_right;
  card4->lower_right = trans_offset3 * card3->lower_right;
  card4->lower_left  = trans_offset3 * card3->lower_left;

  /// Create Card Live
  //  Demonstrates scaling and composition of transformations.
  //  This is just the initialization of card_live, see time step code
  //  fort he rest.
  //
  card_live = new Card();
  cards += card_live;
  pVect card_live_offset(6,1.3,0);
  pMatrix_Translate trans_c1_to_clive(card_live_offset);
  card_live->upper_left  = trans_c1_to_clive * card1->upper_left;
  card_live->upper_right = trans_c1_to_clive * card1->upper_right;
  card_live->lower_right = trans_c1_to_clive * card1->lower_right;
  card_live->lower_left  = trans_c1_to_clive * card1->lower_left;

  /// User interface for interactive scaling.
  //  Each key press will change size slightly.
  //
  pressed_key_c = pressed_key_C = false;


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

  init_graphics();  // Our own code for initializing graphics model.
}


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

 /// Advance Simulation State by delta_t Seconds
//
void
World::time_step_cpu_v0(double delta_t)
{
  /// Update Position and Velocity of Ball
  //
  //  Advance physical state by delta_t seconds.

  // New ball position, accounting for current speed and acceleration,
  // and assuming no collision.
  //
  ball.position +=
    ball.velocity * delta_t + 0.5 * gravity_accel * delta_t * delta_t;

  // New velocity, assuming no collision.
  //
  ball.velocity += gravity_accel * delta_t;

  /// Update Cards

  if ( pressed_key_c || pressed_key_C )
    {
      // This code executed when either c or C pressed.
      // Code should scale card_live.

      // Construct a scale matrix.
      //
      const float scale_factor = pressed_key_c ? 0.9 : 1.0 / 0.9; 
      pMatrix_Scale scale_card(scale_factor);

#if 0
      /// Incorrect Method
      //  Apply scale transformation without centering card at origin.
      //
      card_live->upper_left  = scale_card * card_live->upper_left;
      card_live->upper_right = scale_card * card_live->upper_right;
      card_live->lower_right = scale_card * card_live->lower_right;
      card_live->lower_left  = scale_card * card_live->lower_left;
#else
      /// Correct Method
      //  Move card to center, scale, then move back.

      // Construct a vector from upper_left to origin (center).
      // Note: vector constructor can take two coordinates.
      //
      pVect card_to_ctr(card_live->upper_left,pCoor(0,0,0));

      // Construct translation matrix to center.
      pMatrix_Translate trans_card_to_ctr(card_to_ctr);

      // Construct translation matrix from center.
      // Note use of negation operator on vector.
      pMatrix_Translate trans_card_to_pos(-card_to_ctr);


      /// Correct But Inefficient
      //  Transformation matrices are applied one at a time.
      //  Need to do 3 matrix multiplies per coordinate. How wasteful!!
      //
      card_live->upper_left  = trans_card_to_ctr * card_live->upper_left;
      card_live->upper_right = trans_card_to_ctr * card_live->upper_right;
      card_live->lower_right = trans_card_to_ctr * card_live->lower_right;
      card_live->lower_left  = trans_card_to_ctr * card_live->lower_left;

      card_live->upper_left  = scale_card * card_live->upper_left;
      card_live->upper_right = scale_card * card_live->upper_right;
      card_live->lower_right = scale_card * card_live->lower_right;
      card_live->lower_left  = scale_card * card_live->lower_left;

      card_live->upper_left  = trans_card_to_pos * card_live->upper_left;
      card_live->upper_right = trans_card_to_pos * card_live->upper_right;
      card_live->lower_right = trans_card_to_pos * card_live->lower_right;
      card_live->lower_left  = trans_card_to_pos * card_live->lower_left;

#if 0
      /// Correct and Efficient (Relatively).

      // Construct a single transformation matrix.
      //
      pMatrix full_scale_card =
        trans_card_to_pos * scale_card * trans_card_to_ctr;

      card_live->upper_left  = full_scale_card * card_live->upper_left;
      card_live->upper_right = full_scale_card * card_live->upper_right;
      card_live->lower_right = full_scale_card * card_live->lower_right;
      card_live->lower_left  = full_scale_card * card_live->lower_left;
#endif

#endif

      pressed_key_c = pressed_key_C = false;
    }


  /// Possible Collision with Platform

  // Position and velocity computation simplified: they do not account
  // for the exact time of collision. (In this particular case the
  // exact time of collision could have been easily found.)

  // Return quickly if collision impossible.
  //
  if ( !platform_collision_possible(ball.position) ) return;

  if ( ball.position.y < 0 )
    {
      const float yval = ball.position.y;

      // Snap ball position to surface.
      //
      ball.position.y = 0;

      if ( opt_time_step_alt )
        ball.velocity.y += sqrt( -2.0 * yval * opt_gravity_accel );

      // Reflect y (vertical) component of velocity, with a reduction
      // due to energy lost in the collision.
      //
      if ( ball.velocity.y < 0 )
        ball.velocity.y = - 0.9 * ball.velocity.y;
    }

}

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


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

  const double time_now = time_wall_fp();

  if ( opt_pause || world_time == 0 )
    {
      /// Don't change simulation state.
      //
      world_time = time_now;
    }
  else
    {
      /// Advance simulation state by wall clock time.
      //
      const double delta_t = time_now - world_time;
      time_step_cpu_v0(delta_t);
      world_time += delta_t;
    }

  // Emit graphical representation of the just-updated physical state.
  //
  render();  // Code in demo-1-simple-graphics.cc
}


int
main(int argv, char **argc)
{
  // A class that initializes graphics and provides handy
  // functionality; we'll call it the helper. Something written for
  // this class.
  //
  pOpenGL_Helper popengl_helper(argv,argc);

  // Instantiate our bouncing-ball world.
  // This calls World::init().
  //
  World world(popengl_helper);

  // Tell our helper to call our frame code 30 times per second, when
  // the time comes.
  //
  popengl_helper.rate_set(30);

  // Tell our helper code that we are done setting up and that
  // whenever we need to paint a frame to call world.frame_callback_w.
  // (It will be called 30 times per second.)
  //
  popengl_helper.display_cb_set(world.frame_callback_w,&world);

  // This point will only be reached when the program exits normally.

}