/******************************************************************

  Module: SyDamageNumMgr.cpp

  Author: John C. Buckley

  Description:	

   Damage Number Manager Class

  Copyright 2004 Sony Online Entertainment.  All rights reserved.

*******************************************************************/

/*
**
**	 Includes
**
*/

#include "SyDamageNumMgr.h"
#include "SyTypes.h"
#include "SyScene.h"
#include "SyVect3.h"
#include "SyESFParse.h"
#include "Titan.h"
#include "registry.h"
#include "graphic.h"

/**/


/*
**
**	 Defines
**
*/

#define SYDAMAGENUMMGR_OFFDISTANCE 50.0f

#define SYDAMAGENUMMGR_SURFACEID    (SyHashResourceID("R:\\PS3\\ULPS3\\game_assets\\UI\\DamageNumbers.tga"))

#define SYDAMAGENUMMGR_ENDFADEIN    150
#define SYDAMAGENUMMGR_STARTFADEOUT 400
#define SYDAMAGENUMMGR_LIFESPAN     700

#define SYDAMAGENUMMGR_SCALEMIN     0.15f
#define SYDAMAGENUMMGR_SCALEMAX     0.65f

#define SYDAMAGENUMMGR_JITTER       0.2f
#define SYDAMAGENUMMGR_JITTER_STOP  0.5f

#define SYDAMAGENUMMGR_INITIALHEIGHT 2.0f
#define SYDAMAGENUMMGR_INITIALVEL    3.25f
#define SYDAMAGENUMMGR_GRAVITY       7.0f

#define SYDAMAGENUMMGR_NUMRADIUS 0.5f

#define SYDAMAGENUMMGR_CONEANGLERADIANS ((3.0f)*(SY_PI / 180.0f))

#define SYDAMAGENUMMGR_RAND() (((float32)((rand() % 2000) - 1000)) * 0.001f)
 
/**/


/*
**
**	 Constants
**
*/

/**/


/******************************************************************/
/* Constructor                                                    */
/******************************************************************/

SyDamageNumMgr :: SyDamageNumMgr(Titan* pTitan)
{
  mMaterialHandle = -1;
  mpTitan         = pTitan;
  SyAssert(mpTitan != NULL);
}

SyDamageNumMgr::~SyDamageNumMgr()
{
  SyAssertf(mMaterialHandle == -1, "Did not properly Close() SyDamageNumMgr");
  
  if (mMaterialHandle != -1)
  {
    Close();
  }

  mpTitan     = NULL;
}

/******************************************************************/
/* Init                                                           */
/******************************************************************/

int SyDamageNumMgr::Init()
 {
  int32             I;  
  int32             SurfaceHandle;
  SySimpleMaterial *pMaterial = NULL;
  SySurface        *pSurface = NULL;

  /* Initialize */

  /* Locate resources */

  SyScene* pScene = mpTitan->GetScene();
  SyRaster* pRaster = pScene->GetRasterDev();
  SyESFParse Parser;

  if (Parser.Parse( "programming/fx/dmgnum.esf", *pScene ) < 0 )
  {
    SyAssert(!"Cannot Load dmgnum.esf"); // unable to find scene???
  }

  if(!pScene->GetDictionary()->FindTyped(SYDAMAGENUMMGR_SURFACEID, 
                                         SYRESOURCETYPE_SURFACE, 
                                         SurfaceHandle))
  {
    SyAssert(0);
    return(-1);
  }
  
  pSurface = pRaster->Surface( SurfaceHandle );

  /* Create the material */

  mMaterialHandle = pRaster->CreateMaterial( SYMATERIALTYPE_SIMPLE, 0 );
  if(mMaterialHandle < 0)
  {
    SyAssert(0);
    return(-1);
  }

  pMaterial = (SySimpleMaterial*)(pRaster->Material( mMaterialHandle ));
  pMaterial->SetBlendMode( SYALPHABLENDMODE_AVG );
  pMaterial->SetBlendAlpha( 1.0f );
  pMaterial->SetTexture(
              SurfaceHandle, 
              (*pRaster), 
              (*pScene->GetDictionary()) );

  /* Initialize physics parameters */

  mGravity( 0.0f, -SYDAMAGENUMMGR_GRAVITY, 0.0f );

  /* Initialize the digit blits */

  mDigitBlits[0].mOffset( 432, 11 );
  mDigitBlits[0].mSize( 33, 41 );
  mDigitBlits[1].mOffset( 11, 11 );
  mDigitBlits[1].mSize( 31, 41 );
  mDigitBlits[2].mOffset( 53, 11 );
  mDigitBlits[2].mSize( 33, 41 );
  mDigitBlits[3].mOffset( 101, 11 );
  mDigitBlits[3].mSize( 35, 41 );
  mDigitBlits[4].mOffset( 147, 11 );
  mDigitBlits[4].mSize( 35, 41 );
  mDigitBlits[5].mOffset( 195, 11 );
  mDigitBlits[5].mSize( 32, 41 );
  mDigitBlits[6].mOffset( 243, 11 );
  mDigitBlits[6].mSize( 32, 41 );
  mDigitBlits[7].mOffset( 291, 11 );
  mDigitBlits[7].mSize( 33, 41 );
  mDigitBlits[8].mOffset( 337, 11 );
  mDigitBlits[8].mSize( 32, 41 );
  mDigitBlits[9].mOffset( 382, 11 );
  mDigitBlits[9].mSize( 33, 41 );

  for(I = 0; I < 10; I++)
  {
    mDigitBlits[I].mUVs[0]( (float32)(mDigitBlits[I].mOffset(0)) / (float32)(pSurface->Width()),
                            (float32)(mDigitBlits[I].mOffset(1)) / (float32)(pSurface->Height()) );
    mDigitBlits[I].mUVs[1]( (float32)(mDigitBlits[I].mOffset(0) + mDigitBlits[I].mSize(0)) / (float32)(pSurface->Width()),
                            (float32)(mDigitBlits[I].mOffset(1) + mDigitBlits[I].mSize(1)) / (float32)(pSurface->Height()) );
  }

#if 0
  for(int I = 0; I < 10; I++)
  {
    mDigitBlits[I].mOffset( 429, 12 );
    mDigitBlits[I].mSize( 32, 36 );
  }
#endif

  return(0);
}

/******************************************************************/
/* Close                                                          */
/******************************************************************/

int SyDamageNumMgr :: Close()
{
  if(mDamageNums.Clear() < 0)
  {
    return(-1);
  }

  SyScene* pScene = mpTitan->GetScene();
  SyRaster* pRaster = pScene->GetRasterDev();

  if(mMaterialHandle != -1)
  {
    pRaster->ReleaseMaterial( mMaterialHandle, (*pScene->GetDictionary()) );
    mMaterialHandle = -1;
  }

  return(0);
}

/******************************************************************/
/* Add                                                            */
/******************************************************************/

int SyDamageNumMgr :: Add( tGameObjectID id,
                           int DamageAmount,
                           const SyColor32F& Color,
                           const SyVect3& initialVel )
{
  int     I;
  int32   Index;
  float32 BaseAngle;
  SyVect3 Dir;
  int32   NumDigits;
  int32   Digits[SYDAMAGENUMMGR_MAXDIGITS];

  /* Add the number */

  Index = mDamageNums.Add();
  if(Index < 0)
  {
    return(-1);
  }

  /* Limit the number */

  DamageAmount = SY_MIN( DamageAmount, SYDAMAGENUMMGR_LIMIT );

  /* Set the data members */

  mDamageNums(Index).mID           = id;
  mDamageNums(Index).mStartTime    = mpTitan->GetGameTime();
  mDamageNums(Index).mColor        = Color;
  mDamageNums(Index).mLastUpdate   = mpTitan->GetGameTime();
  mDamageNums(Index).mRandScale    = SYDAMAGENUMMGR_RAND();

  /* Initialize the physics */

  BaseAngle = ((rand() % 1000) * 0.001f) * (2.0f * SY_PI);
  Dir(0) = SY_COS(BaseAngle);
  Dir(1) = 1.0f / SY_TAN( SYDAMAGENUMMGR_CONEANGLERADIANS );
  Dir(2) = SY_SIN(BaseAngle);
  Dir.Normalize();
  
  mDamageNums(Index).mLocation = 0.0f;
  mDamageNums(Index).mLocation(1) = SYDAMAGENUMMGR_INITIALHEIGHT;
  mDamageNums(Index).mVel.Mul( Dir, SYDAMAGENUMMGR_INITIALVEL ); 
  mDamageNums(Index).mVel += initialVel;
  /* Convert the damage amount to character digits */
  
  NumDigits = 0;
  for(I = 0; I < SYDAMAGENUMMGR_MAXDIGITS; I++)
  {
    if(DamageAmount == 0)
    {
      break;  
    }
    Digits[I] = DamageAmount % 10;
    NumDigits++;
    DamageAmount -= Digits[I];
    DamageAmount /= 10;
  }
  if(NumDigits == 0)
  {
    Digits[0] = 0;
    NumDigits++;
  }
  for(I = 0; I < NumDigits; I++)
  {
    mDamageNums(Index).mDigits[NumDigits - I - 1] = Digits[I];    
  }
  mDamageNums(Index).mNumDigits = NumDigits;

  return(0);
}

/******************************************************************/
/* Render                                                         */
/******************************************************************/

int SyDamageNumMgr :: Render( SyCamera& Camera )
 {
  int     I, Next;
  SyTime  Delta;
 
  /* If not damage numbers, exit */

  if(mDamageNums.Size() == 0)
  {
    return(0);
  }

  /* Process each damage number */

  I = mDamageNums.Begin();
  while(I != mDamageNums.End())
  {
    /* Retain the next */

    Next = mDamageNums.Next(I);

    /* Is this damage number complete? */

    Delta  = mpTitan->GetGameTime();
    Delta -= mDamageNums(I).mStartTime;
    if(Delta < SYDAMAGENUMMGR_LIFESPAN)
    {
      /* Update the physics */

      UpdateNumber( mDamageNums(I) );

      /* Display the number */

      DrawNumber( Camera, Delta, mDamageNums(I) );
    }
    else
    {
      /* Lifespan not complete, erase */

      if(mDamageNums.Erase(I) < 0)
       {
        return(-1);
       }
    }

    /* Advance */

    I = Next;
  }

  return(0);
 }

/******************************************************************/
/* UpdateNumber                                                   */
/******************************************************************/

int SyDamageNumMgr :: UpdateNumber( SyDamageNum& DamageNum )
 {
  SyTime  Delta;
  float32 DeltaSec;

  /* Compute a delta time */
  Delta  = mpTitan->GetGameTime();
  Delta -= DamageNum.mLastUpdate;

  if (Delta > 0)
  {
    /* Update the object-space location of the number */

    DeltaSec = (float32)(Delta) * (0.001f);
    DamageNum.mLocation.MulAdd( DamageNum.mVel, DeltaSec );
    DamageNum.mLocation.MulAdd( mGravity, DeltaSec * DeltaSec * 0.5f );
    DamageNum.mVel.MulAdd( mGravity, DeltaSec );

    /* Set the last update time */

    DamageNum.mLastUpdate = mpTitan->GetGameTime();
    DamageNum.mRandScale = SYDAMAGENUMMGR_RAND();
  }

  return(0);
 }

/******************************************************************/
/* DrawNumber                                                     */
/******************************************************************/

int SyDamageNumMgr :: DrawNumber( 
                       SyCamera&    Camera, 
                       SyTime       Delta, 
                       SyDamageNum& DamageNum )
 {
  int32      I;
  float32    Fade;
  float32    Scale, Jitter, Radius;
  SyVect3    Location, HPRAngles, Offset;
  SyMatrix44 Matrix;
  SyColor32F Color;
  SyVect2    DigitSize, Size;  
  SyVect2    ScreenLoc, ScreenMin;
  
  /* If no digits, ignore */

  if(DamageNum.mNumDigits == 0)
   {
    return(0);
   }

  /* Calculate an location for the number in world space */

  SyScene* pScene = mpTitan->GetScene();
  cGameObject* pObj = mpTitan->GetRegistry()->Fetch(DamageNum.mID);

  if (pObj)
  {
    SyMatrix44 mat;
    if (pObj->GetGraphic()->GetActorHandle()!= SyActorNull &&
        pScene->GetActorTransform(pObj->GetGraphic()->GetActorHandle(), mat) < 0)
    {
      return(0);
    }

    mat.ConvertTo(HPRAngles, Scale, Location);
  }
  else
  {
    return(0);
  }

  /* Cull the drawing by distance */

  if(Location.DistanceSquared( Camera.GetLocation() ) > 
    (SYDAMAGENUMMGR_OFFDISTANCE * SYDAMAGENUMMGR_OFFDISTANCE))
   {
    return(0);
   }
  
  Matrix.Transform( HPRAngles, Scale, Location );
  Matrix.Mul( DamageNum.mLocation, Location );
  Radius = SYDAMAGENUMMGR_NUMRADIUS * Scale;

  /* Is the number culled by the frustum ? */

  if(Camera.GetFrustum().Cull( Location, Radius ))
   {
    return(0);
   }

  /* Set the world transform */
  SyRaster* pRaster = pScene->GetRasterDev();

  pRaster->SetWorld( Matrix );
  pRaster->CalcViewportLoc( DamageNum.mLocation, ScreenLoc );

  /* Compute a alpha value */

  Fade = 1.0f;
  if(Delta < SYDAMAGENUMMGR_ENDFADEIN) 
  {
    Fade = (float32)(Delta) / (float32)(SYDAMAGENUMMGR_ENDFADEIN);
  }
  else if(Delta >= SYDAMAGENUMMGR_STARTFADEOUT)
  {  
    Fade = 1.0f - (float32)(Delta - SYDAMAGENUMMGR_STARTFADEOUT) / 
                  (float32)(SYDAMAGENUMMGR_LIFESPAN - SYDAMAGENUMMGR_STARTFADEOUT);
  }

  Color    = DamageNum.mColor;
  Color(3) = Fade;

  /* Compute a scale factor */

  Scale = (float32)(Delta) / (float32)(SYDAMAGENUMMGR_LIFESPAN);
  Scale = SYDAMAGENUMMGR_SCALEMIN + (SYDAMAGENUMMGR_SCALEMAX - SYDAMAGENUMMGR_SCALEMIN) * Scale;

  /* Jitter the scale */

  Jitter = (float32)(Delta) / (float32)(SYDAMAGENUMMGR_LIFESPAN);
  if(Jitter < SYDAMAGENUMMGR_JITTER_STOP)
  {
    Jitter = (SYDAMAGENUMMGR_JITTER_STOP - Jitter) / SYDAMAGENUMMGR_JITTER_STOP;
  }
  else
  {
    Jitter = 0.0f;
  }

  Scale += (DamageNum.mRandScale * Jitter * SYDAMAGENUMMGR_JITTER);

  /* Calculate the screen extents */

  Size(0) = 0;
  Size(1) = (float32)(mDigitBlits[ DamageNum.mDigits[0] ].mSize(1));
  for(I = 0; I < DamageNum.mNumDigits; I++)
  {
    Size(0) += (float32)(mDigitBlits[ DamageNum.mDigits[I] ].mSize(0));
  }
  Size *= Scale;

  /* Raster each digit */

  ScreenMin = ScreenLoc;
  ScreenMin(0) -= (Size(0) * 0.5f);
  ScreenMin(1) -= (Size(1) * 0.5f);
  
  pRaster->Begin2D();
  pRaster->SetMaterial( mMaterialHandle );

  for(I = 0; I < DamageNum.mNumDigits; I++)
  {
    DigitSize(0) = (float32)(mDigitBlits[ DamageNum.mDigits[I] ].mSize(0)) * Scale;
    DigitSize(1) = (float32)(mDigitBlits[ DamageNum.mDigits[I] ].mSize(1)) * Scale;

    pRaster->DrawTexturedRect(SyRect((int32)ScreenMin(0), (int32)ScreenMin(1), (int32)(ScreenMin(0) + DigitSize(0)), (int32)(ScreenMin(1) + DigitSize(1))), 
                              Color, 
                              mDigitBlits[ DamageNum.mDigits[I] ].mUVs[0], 
                              mDigitBlits[ DamageNum.mDigits[I] ].mUVs[1]);

    ScreenMin(0) += DigitSize(0);       
  }
  pRaster->End2D();

  return(0);
 }

/* End of File */
