/******************************************************************
  
  Module:  transient.cpp
  
  Author: Sean Craig
  
  Copyright 2005 Sony Online Entertainment.  All rights reserved.
  
*******************************************************************/

//-------------------------------------------------------- Includes
#include "transient.h"
#include "animcontroller.h" // for AngleNormalize
#include "syassert.h"
//---------------------------------------------- Class Declarations
//----------------------------------------- Functions Declarations


//------------------------------------ Member Functions Definitions

//------------------------------------ cTransient

cTransient::cTransient() :
  mValue(0.0f),
  mTargetSet(false),
  mTarget(0.0f),
  mVelocity(0.0f),
  mMaxVelocity(1.0f),
  mAcceleration(1.0f),
  mPushed(false)
{
};
 

void
cTransient::_CheckCloseEnough(float time)
{

  if (!mTargetSet) return;

  float delta = mTarget - mValue;
  float distance = mVelocity * time;

  float past = false;
  if (delta < 0)
  {
    past = distance < delta;
  }
  else
  {
    past = distance > delta;
  }
  
  if (past)
  {
    mVelocity = 0.0f;
    mValue = mTarget;
    mTargetSet = false;
  }
  
}

void
cTransient::Push(float amount,float maxvel)
{
  if (amount > 0.0001f) 
  {
    if (mVelocity + amount < maxvel)
    {
      mVelocity += amount;
      mPushed = true;
    }
    else if (mVelocity < maxvel)
    {
      mVelocity = maxvel;
      mPushed = true;
    }
  }
  else if ( amount <-0.00001f)
  {
    if (mVelocity + amount > -maxvel)
    {
      mVelocity += amount;
      mPushed = true;
    }
    else if (mVelocity > -maxvel)
    {
      mVelocity = -maxvel;
      mPushed = true;
    }
  }
}

void
cTransient::Update(float time) 
{
  if (mVelocity > mMaxVelocity) mVelocity = mMaxVelocity;
  if (mVelocity < -mMaxVelocity) mVelocity = -mMaxVelocity;

  if (mTargetSet)
  {
    // push towards target
    if (mTarget < mValue)
    {
      Push(-mAcceleration * time,mMaxVelocity);
    }
    else
    {
      Push(mAcceleration * time,mMaxVelocity);
    }
  }
  else if (!mPushed) // we slow down
  {
    float amount = mAcceleration * time;
    if (mVelocity < -amount)
    {
      mVelocity += amount;
    }
    else if (mVelocity > amount)
    {
      mVelocity -= amount;
    }
    else
    {
      mVelocity = 0;
    }
  }

  mPushed = false;

  if (mTargetSet)
  {
    float delta = mTarget - mValue;
    float max_decel_vel = 2.0f * mAcceleration *delta;
    if (max_decel_vel < 0.0f) max_decel_vel = -max_decel_vel;
    max_decel_vel = SY_SQRT(max_decel_vel);

    if (delta > 0)
    {
      if (mVelocity > max_decel_vel) {mVelocity = max_decel_vel;};
    }
    else
    {
      if (mVelocity < -max_decel_vel) {mVelocity = -max_decel_vel;};
    }
  }
  _CheckCloseEnough(time);

  mValue += mVelocity * time;

};


//--------------------------------------------------------------------------------- cTransientAngle
cTransientAngle::cTransientAngle() :
  mValue(0.0f),
  mTargetSet(false),
  mTarget(0.0f),
  mVelocity(0.0f),
  mMaxVelocity(1.0f),
  mAcceleration(1.0f),
  mPushed(false)
{
};
 

void
cTransientAngle::_CheckCloseEnough(float time)
{

  if (!mTargetSet) return;

  float delta = AngleDifference(mTarget,mValue);
  float distance = mVelocity * time;

  float past = false;
  if (delta < 0)
  {
    past = distance < delta;
  }
  else
  {
    past = distance > delta;
  }
  
  if (past)
  {
    mVelocity = 0.0f;
    mValue = mTarget;
    mTargetSet = false;
  }
}

void
cTransientAngle::Push(float amount,float maxvel)
{
  if (amount > 0.0001f) 
  {
    if (mVelocity + amount < maxvel)
    {
      mVelocity += amount;
      mPushed = true;
    }
    else if (mVelocity < maxvel)
    {
      mVelocity = maxvel;
      mPushed = true;
    }
  }
  else if ( amount <-0.00001f)
  {
    if (mVelocity + amount > -maxvel)
    {
      mVelocity += amount;
      mPushed = true;
    }
    else if (mVelocity > -maxvel)
    {
      mVelocity = -maxvel;
      mPushed = true;
    }
  }
}

void
cTransientAngle::Update(float time) 
{
  if (mVelocity > mMaxVelocity) mVelocity = mMaxVelocity;
  if (mVelocity < -mMaxVelocity) mVelocity = -mMaxVelocity;

  float delta = AngleDifference(mTarget,mValue);
  if (mTargetSet)
  {
    // push towards target
    if (delta < 0.0f)
    {
      Push(-mAcceleration * time,mMaxVelocity);
    }
    else
    {
      Push(mAcceleration * time,mMaxVelocity);
    }
  }
  else if (!mPushed)// we slow down
  {
    float amount = mAcceleration * time;
    if (mVelocity < -amount)
    {
      mVelocity += amount;
    }
    else if (mVelocity > amount)
    {
      mVelocity -= amount;
    }
    else
    {
      mVelocity = 0;
    }
  }

  mPushed = false;

  _CheckCloseEnough(time);

  if (mTargetSet)
  {
    float delta = AngleDifference(mTarget,mValue);
    float max_decel_vel = 2.0f * mAcceleration *delta;
    if (max_decel_vel < 0.0f) max_decel_vel = -max_decel_vel;
    max_decel_vel = SY_SQRT(max_decel_vel);

    if (delta > 0)
    {
      if (mVelocity > max_decel_vel) {mVelocity = max_decel_vel;};
    }
    else
    {
      if (mVelocity < -max_decel_vel) {mVelocity = -max_decel_vel;};
    }
  }

  mValue += mVelocity * time;
  
  mValue = AngleNormalize(mValue);

};

void  
cTransientAngle::SetValue(float value)
{
  mValue = AngleNormalize(value);
}
void  
cTransientAngle::SetTarget(float target)
{
  mTarget = AngleNormalize(target);
  mTargetSet = true;
}

// EOF
