//===============================================================================
// @ Player.cpp
// ------------------------------------------------------------------------------
// Player
//
// Copyright (C) 2004 by Elsevier, Inc. All rights reserved.
//
// This demo implements a simple physical simulation using only linear forces.
// The cyan sphere is being pulled towards the origin by a spring.  It is also 
// being affected by gravity and a slight drag force, as well as forces created
// by the user.  You can toggle between three different integration methods:
// Euler's method, midpoint method, and velocity Verlet.  Note how the Euler
// becomes more unstable as the spring force increases, and midpoint as well but
// less so.  The Verlet method is most tolerant of all three.
//
// The key commands are:
//
// I,J,K,L: generate forces in the yz directions to pull the sphere around.
// W,S: increase and decrease the spring force
// M: switch integration method
// SPACE: reset
//
//===============================================================================

//-------------------------------------------------------------------------------
//-- Dependencies ---------------------------------------------------------------
//-------------------------------------------------------------------------------

#include <IvDisplay.h>
#include <IvGLHelp.h>
#include <IvEventHandler.h>
#include <IvMatrix44.h>
#include <IvDebugger.h>

#include "Player.h"
#include "Game.h"

//-------------------------------------------------------------------------------
//-- Static Members -------------------------------------------------------------
//-------------------------------------------------------------------------------

//-------------------------------------------------------------------------------
//-- Methods --------------------------------------------------------------------
//-------------------------------------------------------------------------------

//-------------------------------------------------------------------------------
// @ Player::Player()
//-------------------------------------------------------------------------------
// Constructor
//-------------------------------------------------------------------------------
Player::Player()
{
    mTranslate.Zero();

    mVelocity.Zero();
    mMass = 10.0f;
    mConstantForce.Zero();

    mSpringConstant = 100.0f;

    // if you switch this to use Verlet first, don't forget to prime the pump
    // with one pass of Euler
    mMode = 0;
    glutSetWindowTitle("Euler");

}   // End of Player::Player()


//-------------------------------------------------------------------------------
// @ Player::~Player()
//-------------------------------------------------------------------------------
// Destructor
//-------------------------------------------------------------------------------
Player::~Player()
{
}   // End of Player::~Player()


//-------------------------------------------------------------------------------
// @ Player::Update()
//-------------------------------------------------------------------------------
// Main update loop
//-------------------------------------------------------------------------------
void
Player::Update( float dt )
{
    // add up part of forces and torques which are constant over interval
    mConstantForce.Zero();
    if (IvGame::mGame->mEventHandler->IsKeyDown('k'))
    {
        mConstantForce -= 75.0f*IvVector3::zAxis;
    }
    if (IvGame::mGame->mEventHandler->IsKeyDown('i'))
    {
        mConstantForce += 75.0f*IvVector3::zAxis;
    }
    if (IvGame::mGame->mEventHandler->IsKeyDown('l'))
    {
        mConstantForce -= 75.0f*IvVector3::yAxis;
    }
    if (IvGame::mGame->mEventHandler->IsKeyDown('j'))
    {
        mConstantForce += 75.0f*IvVector3::yAxis;
    }

    // adjust spring force
    if (IvGame::mGame->mEventHandler->IsKeyDown('w'))
    {
        mSpringConstant += 10.0f;
    }
    if (IvGame::mGame->mEventHandler->IsKeyDown('s'))
    {
        mSpringConstant -= 10.0f;
    }

    // change integration technique
    if (IvGame::mGame->mEventHandler->IsKeyDown('m'))
    {
        mMode = (mMode+1)%3;
        IvGame::mGame->mEventHandler->KeyUp('m');

        if ( mMode == 0 )
        {
            glutSetWindowTitle("Euler");
        }
        else if ( mMode == 1 )
        {
            glutSetWindowTitle("Midpoint");
        }
        else if ( mMode == 2 )
        {
            glutSetWindowTitle("Verlet");
        }
    }

    // reset simulation
    if (IvGame::mGame->mEventHandler->IsKeyDown(' '))
    {
        mTranslate.Zero();
        mVelocity.Zero();
        mMode = 0;
    }

    // integrate
    if ( mMode == 0 )
    {
        Euler( dt );
    }
    else if ( mMode == 1 )
    {
        Midpoint( dt );
    }
    else if ( mMode == 2 )
    {
        Verlet( dt );
    }

}   // End of Player::Update()


//-------------------------------------------------------------------------------
// @ Player::Euler()
//-------------------------------------------------------------------------------
// Move to next time step using Euler's method
//-------------------------------------------------------------------------------
void 
Player::Euler( float dt )                                   
{   
    // compute acceleration
    IvVector3 accel = CurrentForce( mTranslate, mVelocity ) / mMass;
    // clear small values
    accel.Clean();          

    // compute new position, velocity
    mTranslate += dt*mVelocity;
    mVelocity += dt*accel;
    // clear small values
    mVelocity.Clean();      
    
}   // End of Player::Euler()


//-------------------------------------------------------------------------------
// @ Player::Midpoint()
//-------------------------------------------------------------------------------
// Move to next time step using Midpoint method
//-------------------------------------------------------------------------------
void 
Player::Midpoint( float dt )                                    
{   
    // compute acceleration
    IvVector3 accel = CurrentForce( mTranslate, mVelocity ) / mMass;
    // clear small values
    accel.Clean();          

    // compute new position, velocity
    float h2 = 0.5f*dt;
    IvVector3 midPosition = mTranslate + h2*mVelocity;
    IvVector3 midVelocity = mVelocity + h2*accel;
    // clear small values
    midVelocity.Clean();        

    accel = CurrentForce( midPosition, midVelocity ) / mMass;
    // clear small values
    accel.Clean();          

    // compute new position, velocity
    mTranslate += dt*mVelocity;
    mVelocity += dt*accel;
    // clear small values
    mVelocity.Clean();      
    
}   // End of Player::Midpoint()


//-------------------------------------------------------------------------------
// @ Player::Verlet()
//-------------------------------------------------------------------------------
// Move to next time step using velocity Verlet method
//-------------------------------------------------------------------------------
void 
Player::Verlet( float dt )                                  
{   
    // compute acceleration
    IvVector3 accel = CurrentForce( mTranslate, mVelocity ) / mMass;
    // clear small values
    accel.Clean();          

    // compute new position
    IvVector3 halfStepVelocity = mVelocity + 0.5f*dt*accel;
    mTranslate += dt*halfStepVelocity;

    // recompute acceleration
    // we really shouldn't use velocity in computing our forces, 
    // but it's pretty close
    accel = CurrentForce( mTranslate, halfStepVelocity ) / mMass;
    // clear small values
    accel.Clean();          

    // compute new velocity
    mVelocity = halfStepVelocity + 0.5f*dt*accel;
    // clear small values
    mVelocity.Clean();      
    
}   // End of Player::Verlet()


//-------------------------------------------------------------------------------
// @ Player::CurrentForce()
//-------------------------------------------------------------------------------
// Compute force, given forces constant over interval and variable params 
//-------------------------------------------------------------------------------
IvVector3 
Player::CurrentForce( const IvVector3& position, const IvVector3& velocity )                                
{   
    IvVector3 totalForce = mConstantForce;

    // add in spring force
    totalForce -= mSpringConstant*position;

    // add in gravity
    totalForce.z -= 9.8f*mMass;

    // add some drag
    float speed = velocity.Length();
    totalForce -= 0.1f*mMass*speed*velocity;

    return totalForce;

}   // End of Player::CurrentForce()


//-------------------------------------------------------------------------------
// @ Player::Render()
//-------------------------------------------------------------------------------
// Render stuff
//-------------------------------------------------------------------------------
void 
Player::Render()                                    
{   
    // build 4x4 matrix
    IvMatrix44 transform;
    transform.Identity();
    transform(0,3) = mTranslate.x;
    transform(1,3) = mTranslate.y;
    transform(2,3) = mTranslate.z;
    
    ::IvSetWorldMatrix(transform);
    ::IvSetColor( 0.0f, 1.0f, 1.0f );
    ::IvDrawSphere(0.5f);
                    
    // draw force line
    ::IvSetWorldMatrix(transform);
    ::IvSetColor( 1.0f, 0.5f, 0.0f );
    ::IvDrawLine( IvVector3::origin, mConstantForce );

    // draw line from origin to ball, to show spring
    ::IvSetWorldIdentity();
    ::IvSetColor( 1.0f, 1.0f, 1.0f );
    ::IvDrawLine( IvVector3::origin, mTranslate );

}

