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

//-------------------------------------------------------- Includes
#include "debris.h"
#include "SyCollSphere.h"
#include "SyScene.h"
#include "Titan.h"
#include "database.h"
#include "tuning.h"
//---------------------------------------------- Class Declarations
//----------------------------------------- Functions Declarations

float AngleNormalize(float start);

static float l_DEBRIS_GRAVITY = -9.8f; // meters/sec
static float l_DEBRIS_ROTATION_SCALE_FACTOR = 0.5f;
static float l_DEBRIS_BOUNCE_FACTOR = 1.5f; // 1.0 = inelastic, 2.0 = perfectly elastic
static float l_DEBRIS_FRICTION = 0.2f; 
static float l_DEBRIS_XZ_VELOCITY = 3.0f; 
static float l_DEBRIS_Y_VELOCITY_MIN = 3.0f; 
static float l_DEBRIS_Y_VELOCITY_MAX = 6.0f; 
static float l_DEBRIS_AGE = 10.0f; // seconds



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

//------------------------------------------------------------- cDebrisShard
cDebrisShard::cDebrisShard(Titan *titan) :
  mActorHandle(NULL),
  mpTitan(titan)
{
};

cDebrisShard::~cDebrisShard()
{
  if (mActorHandle != NULL)
  {
    SyScene *scene = mpTitan->GetScene();

    scene->ReleaseActor(mActorHandle);

    mActorHandle = NULL;
  }
};
  
void 
cDebrisShard::Init(const SyVect3 &loc, const SyVect3& start_vel,const cDebrisShardMaster &master)
{

  mVelocity = start_vel;

  // todo: rotation
  SyVect3 ActorHPR;

  SyScene *scene = mpTitan->GetScene();

  mHeading = SY_ATAN2(start_vel.X,start_vel.Z);

  mRotSpeed = start_vel.Magnitude() * l_DEBRIS_ROTATION_SCALE_FACTOR;
  
  mRotAngle = SY_DEG_TO_RAD(0.0f);

  ActorHPR(mHeading,mRotAngle,0.0f);
  mActorHandle   = scene->CreateActor( loc, ActorHPR, master.mScale, master.mSpriteTemplateHandle );
  scene->SetActorCollideable(mActorHandle, false);
  mLoc = loc;
  mbIsMoving = true;
  mRadius = master.mRadius;
  mHeight = master.mHeight;
  mBounces = 0;
};

void 
cDebrisShard::Update(float time)
{
  SyScene *scene = mpTitan->GetScene();
  if (mbIsMoving)
  {
    SyVect3 velocity = mVelocity;
    SyVect3 displacement;

    displacement.MulAdd(velocity,time);

    float accel = l_DEBRIS_GRAVITY * time;    
    mVelocity.Y += accel;
    _CollisionTest(displacement);
    scene->SetActorLocation(mActorHandle,mLoc);


    float old_rot = mRotAngle;
    mRotAngle += mRotSpeed * time;
    if (mBounces > 1 && ((mRotAngle < 0)!= (old_rot< 0)))
    {
      mRotAngle = 0;
      mRotSpeed = 0;
    }
    mRotAngle = AngleNormalize(mRotAngle);

    SyVect3 hpr;
    hpr(mHeading,mRotAngle,0);
    scene->SetActorHPR(mActorHandle,hpr);
  }


};

void 
cDebrisShard::_CollisionTest(const SyVect3 &displacement)
{
  SyVect3       Start, End;
  SyCollSphere  CollSphere;

  SyScene *scene = mpTitan->GetScene();

  SySceneFilter Filter;
  Filter.Init(0);

  Start = mLoc;
  Start.Y += mHeight;
  End = Start +  displacement;

  
  CollSphere.Init( Start, End,mRadius);
  /* Collide with the world */

  if (scene->Collide( CollSphere, Filter ))
  {
    // dot product
    SyVect3 normal = CollSphere.GetHitNormal();
    float dot = mVelocity ^ normal; 
    SyVect3 restitution;
    restitution.Mul(normal,(l_DEBRIS_BOUNCE_FACTOR * dot));
    mVelocity -= restitution; 

    // finish out motion?

    End = CollSphere.GetHitPoint();
    float MIN_VELOCITY = 0.1f;
    float MIN_FLATNESS = 0.6f;
    
    if (normal.Y > MIN_FLATNESS)
    {
      float XZSpeed = mVelocity.X * mVelocity.X + mVelocity.Z * mVelocity.Z;
      if (XZSpeed < MIN_VELOCITY)
      {
        mbIsMoving = false;
      }

      mBounces++;
      if (mBounces == 2)
      {
        mRotSpeed = -mRotSpeed;
      }
      else if (mBounces == 3)
      {
        if ((mRotAngle > 0 && mRotSpeed > 0) ||
            (mRotAngle < 0 && mRotSpeed < 0))
        {
          mRotSpeed = -mRotSpeed;
        }
      }
    }

    // friction
    mVelocity *= (1.0f - l_DEBRIS_FRICTION);
    End.Y -= mHeight;
    mLoc = End;

    return;
    
  }

  /* Retrieve the result of the collision/skitters */

  End = CollSphere.GetHitPoint();
  End.Y -= mHeight;
  mLoc = End;
  return;
};
//----------------------------------------------------------- cDebris

cDebris::cDebris(Titan *pTitan) :
  mpTitan(pTitan),
  mAge(0.0f)
{
};

cDebris::~cDebris()
{
  for (int ii=mShards.Begin();ii!=mShards.End();ii=mShards.Next(ii))
  {
    delete mShards(ii);
  }
  mShards.Clear();
};

void 
cDebris::Init(const SyVect3 &loc, const SyVect3& force,const cDebrisMaster &master)
{
  for (int ii=master.mShardMasters.Begin();ii!=master.mShardMasters.End();ii=master.mShardMasters.Next(ii))
  {
    cDebrisShardMaster *shard_master = master.mShardMasters(ii);

    for (int shard_index = 0;shard_index<shard_master->mQuantity;++shard_index)
    {
      SyVect3 start_loc = loc;
      start_loc.X += (mpTitan->RandomFloat() * 2.0f * master.mRadius) - master.mRadius;
      start_loc.Y += (mpTitan->RandomFloat() * 2.0f * master.mRadius) - master.mRadius;
      start_loc.Z += (mpTitan->RandomFloat() * 2.0f * master.mRadius) - master.mRadius;

      // randomize start vel...
      static const float l_DEBRIS_XZ_VELOCITY = 2.5f;
      static const float l_DEBRIS_Y_VELOCITY_MIN = 2.5f;
      static const float l_DEBRIS_Y_VELOCITY_MAX = 2.5f;

      SyVect3 start_vel = force;
      start_vel.X += (mpTitan->RandomFloat() * l_DEBRIS_XZ_VELOCITY * 2) - l_DEBRIS_XZ_VELOCITY;
      start_vel.Y += (mpTitan->RandomFloat() * (l_DEBRIS_Y_VELOCITY_MAX - l_DEBRIS_Y_VELOCITY_MIN)) + l_DEBRIS_Y_VELOCITY_MIN;
      start_vel.Z += (mpTitan->RandomFloat() * l_DEBRIS_XZ_VELOCITY * 2) - l_DEBRIS_XZ_VELOCITY;

      cDebrisShard *newshard = new cDebrisShard(mpTitan);
      newshard->Init(start_loc,start_vel,*shard_master);
      mShards.Add(newshard);
    }
  }
};

void 
cDebris::Update(float time)
{
  for (int ii=mShards.Begin();ii!=mShards.End();ii=mShards.Next(ii))
  {
    mShards(ii)->Update(time);
  }
  mAge += time;
};

bool 
cDebris::IsDead()
{

  return (mAge > l_DEBRIS_AGE);
};

//----------------------------------------------------------- cDebrisShardMaster


cDebrisShardMaster::cDebrisShardMaster() :
  mModelName(NULL),
  mSpriteTemplateHandle(NULL),
  mQuantity(1),
  mRadius(0.25f),
  mScale(1.0f),
  mHeight(0.25f)
{
};

cDebrisShardMaster::  ~cDebrisShardMaster()
{
  delete mModelName;
};



//----------------------------------------------------------- cDebrisMaster

cDebrisMaster::cDebrisMaster()
{
}

cDebrisMaster::~cDebrisMaster()
{
  for (int ii=mShardMasters.Begin();ii!=mShardMasters.End();ii=mShardMasters.Next(ii))
  {
    delete mShardMasters(ii);
  }
  mShardMasters.Clear();
};

//----------------------------------------------------------- cDebrisSys


cDebrisSys::cDebrisSys(Titan *pTitan) :
  mpTitan(pTitan)
{
};

cDebrisSys::~cDebrisSys()
{
  Clear();

}

void    
cDebrisSys::Init()
{
 
}

void    
cDebrisSys::Clear()
{
  for (int ii=mDebris.Begin();ii!=mDebris.End();ii=mDebris.Next(ii))
  {
    delete mDebris(ii);
  }
  mDebris.Clear();
};

void    
cDebrisSys::Update(float time)
{
  for (int ii=mDebris.Begin();ii!=mDebris.End();ii=mDebris.Next(ii))
  {
    mDebris(ii)->Update(time);
  }
  int curIndex;
  for (curIndex = mDebris.Begin();curIndex != mDebris.End(); )
  {
    cDebris *debris = mDebris(curIndex);
    if (debris->IsDead())
    {
      delete debris;
      curIndex = mDebris.ReplaceLast(curIndex);
    }
    else 
    {
      curIndex = mDebris.Next(curIndex);
    }
  }
};

void    
cDebrisSys::Spawn(const SyVect3 &loc, const SyVect3 &force,tGameID debrisID)
{
  const cDebrisMaster *master = mpTitan->GetDatabaseSys()->GetDebrisMaster(debrisID);

  if (master == NULL)
  {
    SyAssertf(0,"Unknown debris type");
    return;
  }

  cDebris *newdebris = new cDebris(mpTitan);

  newdebris->Init(loc,force,*master);
  mDebris.Add(newdebris);
};


void 
Debris_RegisterTuningVariables()
{
  gTuningSys.AddFloat(&l_DEBRIS_GRAVITY,"Debris_Gravity");
  gTuningSys.AddFloat(&l_DEBRIS_ROTATION_SCALE_FACTOR,"Debris_Rotation");
  gTuningSys.AddFloat(&l_DEBRIS_BOUNCE_FACTOR,"Debris_BounceFactor");
  gTuningSys.AddFloat(&l_DEBRIS_FRICTION,"Debris_Friction");
  gTuningSys.AddFloat(&l_DEBRIS_XZ_VELOCITY,"Debris_XZ_Velocity");
  gTuningSys.AddFloat(&l_DEBRIS_Y_VELOCITY_MIN,"Debris_Y_Velocity_Min");
  gTuningSys.AddFloat(&l_DEBRIS_Y_VELOCITY_MAX,"Debris_Y_Velocity_Max");
  gTuningSys.AddFloat(&l_DEBRIS_AGE,"Debris_Y_Age");

}

// EOF
