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

//-------------------------------------------------------- Includes
#include "physics.h"
#include "gameobj.h"
#include <stddef.h>
#include "graphic.h"
#include "SyScene.h"
#include "SySceneFilter.h"
#include "Titan.h"
#include "animcontroller.h"
#include "registry.h"
#include "database.h"
#include "script.h"
#include "stats.h"
#include "intel.h"

//---------------------------------------------- Class Declarations

/* Floor Collision Filter */

class cFloorFilter : public SySceneFilter
{
public:

  /* Filtering */

  virtual int FilterActor( SyScene& Scene, SyActorHandle ActorHandle );  
};
//--------------------------------------------------------- Globals
//--------------------------------------------------------- LOCALS

static const float RUBBER_BAND_TIME = 0.2f;
const float cPhysics::GRAVITY = -9.8f; // meters/sec
//----------------------------------------- Functions Declarations
//------------------------------------ Member Functions Definitions

//------------------------------------ cPhysics
cPhysics::cPhysics() : 
  mCollideable(true), 
  mOwner(NULL)
{
  InitPropObject( mCLASSID );
}

void 
cPhysics::SetOwner(cGameObject *owner)
{
  mOwner = owner;
}

int           
cPhysics::InitPropClass()
{

/* Add the class */

  AddClass( mCLASSID, 
            "cPhysics", 
            NULL,       // CRO: abstract base class has no creator
            mCLASSID, 
            0 ); 
  return 0;
}

SyPropObject* 
cPhysics::Creator()
{
  SyAssertf(0,"Trying to object-factory an abstract base class");

  return(NULL);
}

void 
cPhysics::Init()
{
  SyActorHandle handle = mOwner->GetGraphic()->GetActorHandle();

  SyScene *scene = mOwner->GetTitan()->GetScene();
  if (handle == SyActorNull)
  {
    return;
  }
  scene->SetActorCollideable(handle, mCollideable);
}


const SyVect3 &
cPhysics::GetVelocity() 
{
  static SyVect3 vel(0,0,0);
  return vel;
};

void
cPhysics::SetVelocity(const SyVect3& velocity)
{
  SyAssertf(false, "Calling SetVelocity on a cPhysics subclass that hasn't implemented it");
}

void 
cPhysics::NetworkSetLocation(const SyVect3 &location)
{
  mOwner->SetLocation(location);
};


void 
cPhysics::NetworkSetHeading(float heading)
{
  mOwner->SetHeading(heading);
};



const SyVect3 &
cPhysics::GetRenderLocation()
{
  return mOwner->GetLocation();
};

float          
cPhysics::GetRenderHeading()
{
  return mOwner->GetHeading();
};


bool           
cPhysics::SetSafeLocation(const SyVect3 &loc)// finds a safe location near this spot
{
  mOwner->SetLocation(loc);
  return true;
}


void
cPhysics::SetCollideable(bool collide)
{
  mCollideable = collide;
  cPhysics::Init();
}

//------------------------------------ cPhysicsStatic

cPhysicsStatic::cPhysicsStatic()
{
  InitPropObject( mCLASSID );
}
int           
cPhysicsStatic::InitPropClass()
{

/* Add the class */

  AddSubClass( mCLASSID, 
               cPhysics::mCLASSID,
               mCLASSID,
               "cPhysicsStatic", 
               Creator, 
               mCLASSID, 
               0 ); 
  return 0;
}

SyPropObject* 
cPhysicsStatic::Creator()
{
  SyPropObject *pObject;

  pObject = SyNew cPhysicsStatic();
  if(pObject == NULL)
  {                            
    SyAssert(0);
    return(NULL);
  }

  return(pObject);
}

//------------------------------------ cPhysicsDynamic

cPhysicsDynamic::cPhysicsDynamic() :
mIsMoving(false)
{
  InitPropObject( mCLASSID );
}

int           
cPhysicsDynamic::InitPropClass()
{

/* Add the class */

  AddSubClass( mCLASSID, 
               cPhysics::mCLASSID,
               mCLASSID,
               "cPhysicsDynamic", 
               Creator, 
               mCLASSID, 
               0 ); 
  return 0;
}

SyPropObject* 
cPhysicsDynamic::Creator()
{
  SyPropObject *pObject;

  pObject = SyNew cPhysicsDynamic();
  if(pObject == NULL)
  {                            
    SyAssert(0);
    return(NULL);
  }

  return(pObject);
}


bool                 
cPhysicsDynamic::Push(float time,SyVect3 &displacement)
{
  if (!mOwner->GetStats()->IsPushable())
  {
    return false;
  }
  CollisionTest(time,displacement);

  SyScene *scene = mOwner->GetRegistry()->GetTitan()->GetScene();
  scene->SetActorLocation(mOwner->GetGraphic()->GetActorHandle(),mOwner->GetLocation());
  mIsMoving = true;

  return true;
};

void         
cPhysicsDynamic::Update(float time)
{
  if (mIsMoving)
  {
    SyVect3 velocity = mVelocity;
    SyVect3 displacement;

    displacement.MulAdd(velocity,time);

    float accel = GRAVITY * time;    
    mVelocity.Y += accel;
    CollisionTest(time,displacement);
  }
}


void                 
cPhysicsDynamic::SetVelocity(const SyVect3 &velocity)
{
  mVelocity = velocity;
  mIsMoving = true;
}


void                 
cPhysicsDynamic::CollisionTest(float time,SyVect3 &displacement)
{
  SyVect3       Start, End;
  SyCollSphere  CollSphere;

  cGraphicActor *graphic = prop_cast<cGraphicActor*>(mOwner->GetGraphic());
  SyAssert(graphic);

  SyActorHandle handle = graphic->GetActorHandle();
  SyScene *scene = mOwner->GetTitan()->GetScene();
  //cStatsProp *stats = (cStatsProjectile *) mOwner->GetStats();
  // TODO: get push collision data from object
  float OBJ_RADIUS = 0.3f;
  float OBJ_HEIGHT = OBJ_RADIUS;

  SySceneFilter Filter;
  Filter.Init( handle);

  SyVect3 location = mOwner->GetLocation();
  Start = location;
  Start.Y += OBJ_HEIGHT;
  End       = Start + displacement;

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

  if (scene->Collide( CollSphere, Filter ))
  {
    #if 0
    SyActorHandle collidingActor=Filter.GetActor();

    if (collidingActor != 0)
    {
      //if (mPushingTarget == NULL || mPushingTarget->GetGraphic()->GetActorHandle() != pushingHandle)
      //{
      cGameObject *collidingTarget = mOwner->GetRegistry()->FetchByActorHandle(collidingActor);
      SyAssert(collidingTarget != NULL);
      //}
    }
    #endif
    // dot product
    SyVect3 normal = CollSphere.GetHitNormal();
    float dot = mVelocity ^ normal; 
    float BOUNCE_FACTOR = 1.5f; // 1.0 = inelastic, 2.0 = perfectly elastic
    SyVect3 restitution;
    restitution.Mul(normal,(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)
      {
        mIsMoving = false;
      }
    }

    // friction
    mVelocity *= 0.8f;
    End.Y -= OBJ_HEIGHT;
    mOwner->SetLocation(End);
    return;
    
  }

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

  End = CollSphere.GetHitPoint();
  End.Y -= OBJ_HEIGHT;
  mOwner->SetLocation(End);

  return;
}

//------------------------------------ cPhysicsProp

cPhysicsProp::cPhysicsProp()
{
  InitPropObject( mCLASSID );
}

int           
cPhysicsProp::InitPropClass()
{
  /* Add the class */

  AddSubClass( mCLASSID, 
    cPhysicsDynamic::mCLASSID,
    mCLASSID,
    "cPhysicsProp", 
    Creator, 
    mCLASSID, 
    0 ); 
  return 0;
}

SyPropObject* 
cPhysicsProp::Creator()
{
  SyPropObject *pObject;

  pObject = SyNew cPhysicsProp();
  if(pObject == NULL)
  {                            
    SyAssert(0);
    return(NULL);
  }

  return(pObject);
}

void                 
cPhysicsProp::CollisionTest(float time,SyVect3 &displacement)
{
  SyVect3       Start, End;
  SyCollSphere  CollSphere;
  SyVect3        Floor, FloorNormal;
  SyCollSurfType FloorSurfaceType;

  cGraphicActor *graphic = prop_cast<cGraphicActor*>(mOwner->GetGraphic());
  SyAssert(graphic);

  SyActorHandle handle = graphic->GetActorHandle();
  SyScene *scene = mOwner->GetTitan()->GetScene();
  //cStatsProp *stats = (cStatsProjectile *) mOwner->GetStats();
  // TODO: get push collision data from object
  float PROP_HEIGHT = 1.5f;
  float PROP_RADIUS = 0.8f;

  SySceneFilter Filter;
  Filter.Init( handle);

  SyVect3 location = mOwner->GetLocation();
  Start = location;
  Start.Y += PROP_HEIGHT;
  End       = Start + displacement;

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

  scene->Collide( CollSphere, Filter );

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

  End = CollSphere.GetHitPoint();

  if(CalcFloor( End, *scene, Floor, FloorNormal, FloorSurfaceType,PROP_RADIUS,PROP_HEIGHT ))
  {
    /* Is the object touching the floor - move the object up to the floor level?  */
    if ((End.Y - Floor.Y) < PROP_HEIGHT && mVelocity.Y <= 0.0f)
    {
      End.Y = Floor.Y + PROP_HEIGHT;    
      mVelocity.Y = 0.0f;  
      float MIN_VELOCITY = 0.1f;
      if (mVelocity.Magnitude() < MIN_VELOCITY)
      {
        mIsMoving = false;
      }
    }       

  }

  End.Y -= PROP_HEIGHT;
  mOwner->SetLocation(End);

  return;
}

/*******************************/
/* CalcFoor                    */
/*******************************/

int cPhysicsDynamic:: CalcFloor( 
                                SyVect3&        Point, 
                                SyScene&        Scene, 
                                SyVect3&        Floor, 
                                SyVect3&        FloorNormal,
                                SyCollSurfType& FloorSurfaceType,
                                float radius,
                                float height)
{
  cFloorFilter        Filter;
  SyCollSphere        CollSphere;
  SyVect3             Start, End;

  /* Initialize the filter */

  cGraphicActor *graphic = (cGraphicActor*) mOwner->GetGraphic();
  SyActorHandle handle = graphic->GetActorHandle();
  Filter.Init( handle );

  /* Initialize the collision */

  Start     = Point;
  End       = Point;
  End.Y    -= radius;

  CollSphere.Init( Start, End, radius );

  /* Perform the floor collision */

  if(Scene.Collide( CollSphere, Filter ))
  {
    Floor     = CollSphere.GetHitPoint();
    Floor.Y  -= radius;

    FloorNormal      = CollSphere.GetHitNormal();
    FloorSurfaceType = CollSphere.GetHitSurfaceType();

    return(1);
  }

  Floor = Point;
  FloorNormal( 0.0f, 0.0f, 0.0f );
  FloorSurfaceType = SYCOLLSURFTYPE_NONE;

  return(0);
}

//------------------------------------ cPhysicsAnimated


cPhysicsAnimated::cPhysicsAnimated() :
      mCollisionHeight(1.2f),
      mCollisionRadius(0.5f),
      mHeight(1.8f),               // The height of the character in meters
      mFloorCollRadius(0.001f),     // Radius of collision sphere used during floor/ceiling checks
      mMinStepSize(0.1f),              // Minimum distance to step up or down
      mMaxStepSize(0.5),              // Maximum distance to step up or down
      mPushing(false),
      mPushingTarget(NULL)
{
  InitPropObject( mCLASSID );
  Reset();
}

int           
cPhysicsAnimated::InitPropClass()
{            
/* Add the class */

  AddSubClass( mCLASSID, 
               cPhysicsDynamic::mCLASSID,
               mCLASSID,
               "cPhysicsAnimated", 
               Creator, 
               mCLASSID, 
               0 ); 
  return 0;
}

SyPropObject* 
cPhysicsAnimated::Creator()
{
  SyPropObject *pObject;

  pObject = SyNew cPhysicsAnimated();
  if(pObject == NULL)
  {                            
    SyAssert(0);
    return(NULL);
  }

  return(pObject);
}

void 
cPhysicsAnimated::Reset()
{
  mVelocity(0,0,0);

}


void
cPhysicsAnimated::Init()
{
  cPhysics::Init();
  if (mOwner->IsRemote())
  {
    mNetworkUpdateTime = RUBBER_BAND_TIME;
    mRenderLocation = mOwner->GetLocation();
    mRenderHeading   = mOwner->GetHeading();
    mRenderOffset(0,0,0);
  }
  meLocomotion = LOCO_ANIMATION;

  // store separate radius in object in case it changes from anim or AI?
  mCollisionRadius = ((cStatsCharacter *) mOwner->GetStats())->GetMaster()->mCollisionRadius;
}


void 
cPhysicsAnimated::Update(float time)
{
  cGraphicCharacter *graphic = prop_cast<cGraphicCharacter*>(mOwner->GetGraphic());
  SyAssert(graphic);

  SyVect3 displacement(0,0,0);


  switch (meLocomotion)
  {
    case LOCO_ANIMATION:
      displacement = graphic->GetDisplacement();
      displacement.Y += mVelocity.Y * time + 0.5f * time * time * GRAVITY;
      mVelocity.Y += GRAVITY * time;
      break;
    case LOCO_JUMP:
      {
        SyVect3 velocity = mVelocity;
        velocity += mJumpStartVel;
        velocity += mJumpPushVel;
        displacement.MulAdd(velocity,time);
        mVelocity.Y += GRAVITY * time;
      }
      break;
    case LOCO_CUTSCENE:
      {
        SyScene *scene = mOwner->GetTitan()->GetScene();
        SyActorHandle actorHandle = graphic->GetActorHandle();
        SyCSprite* cSprite = (SyCSprite*) scene->GetActorSpritePtr (actorHandle);
        SyMatrix44 transform;
        SyVect3 Rotation,Scale, Translation;
        cSprite->CalcMotionAbsTransform(*scene,transform);
        transform.ConvertTo(Rotation, Scale, Translation);
        mOwner->SetLocation (Translation);
        mOwner->SetHPR (Rotation);
        return;
      }
    default:
      break;
  }

  CollisionTest(time,displacement);

  if (mOwner->IsRemote())
  {
    mNetworkUpdateTime += time;
    float frac = mNetworkUpdateTime/RUBBER_BAND_TIME;
    if (frac > 1.0f)
    {
      frac = 1.0f;
    }
    frac =  1.0f - frac;
    SyVect3 offset(0.0f,0.0f,0.0f);

    offset.MulAdd(mRenderOffset,frac);
    mRenderLocation = mOwner->GetLocation()+offset;
    mRenderHeading = mOwner->GetHeading();
  }
}

bool
cPhysicsAnimated::PushTarget(float time, SyVect3 &destination, SyCollSphere &CollSphere)
{
  SyVect3 contact = CollSphere.GetHitPoint();
  SyVect3 remainingDisplacement = destination - contact;

  float distance = remainingDisplacement.Magnitude();
  SyVect3 pushVect;
  SyVect3 targetLoc = mPushingTarget->GetLocation();
  targetLoc.Y += mCollisionHeight;
  SyVect3 hitNormal=  targetLoc - contact;
  
  hitNormal.Normalize();
  pushVect.Mul(hitNormal, distance);

  // todo: scale push vect for mass differential
  // todo: Check to see if we're pushing out of the path of this object (may be able to push less)

  return prop_cast<cPhysicsDynamic*>(mPushingTarget->GetPhysics())->Push(time,pushVect);
}

void 
cPhysicsAnimated::_CollisionTestWorld(float time, SyVect3 &Start,SyVect3 &End)
{
  SyCollSphere   CollSphere;
  int32          Skitter, MaxSkitters = 5;
  SyVect3        AntiSkitter;
  SySceneFilter  Filter;
  cGraphicCharacter *graphic = prop_cast<cGraphicCharacter*>(mOwner->GetGraphic());
  SyAssert(graphic);
  SyActorHandle handle = graphic->GetActorHandle();

  Filter.Init( handle);

  CollSphere.Init( Start, End, mCollisionRadius );

  SyScene *scene = mOwner->GetTitan()->GetScene();
  /* Collide with the world */

  if (scene->Collide( CollSphere, Filter ))
  {
    if (mOwner->GetIntel() &&
        (SY_FABS(CollSphere.GetHitNormal().X) > 0.0f || SY_FABS(CollSphere.GetHitNormal().Z) > 0.0f))
    {
      mOwner->GetIntel()->OnWallCollision(CollSphere.GetHitNormal());
    }

    /* Skitter off of surfaces */

    if(0)//meLocomotion != LOCO_ANIMATION) //Curr.Flags & VIMVWALKPHYSICS_FLAG_ONFLOOR)
    {
      AntiSkitter( 0.0f, 1.0f, 0.0f );
    }
    else
    {
      AntiSkitter( 0.0f, 0.0f, 0.0f );
    }

    Skitter = 0;
    while(Skitter < MaxSkitters)
    {
      CollSphere.Skitter( AntiSkitter );
      if(!scene->Collide( CollSphere, Filter ))
      {
        break;
      }
      Skitter++;
    }
    


    SyActorHandle pushingHandle=Filter.GetActor();

    if (pushingHandle != 0)
    {
      //if (mPushingTarget == NULL || mPushingTarget->GetGraphic()->GetActorHandle() != pushingHandle)
      //{
      mPushingTarget = mOwner->GetRegistry()->FetchByActorHandle(pushingHandle);
      //}

      // tell target we've collided (once)
      if (mPushingTarget != NULL)
      {
        cScript* script = mPushingTarget->GetScript();
        if (script != NULL)
        {
          script->Run( mOwner, SE_COLLIDE );
        }

        SyAssertf(mPushingTarget != mOwner,"Pushing self???"); 

        if ((mPushingTarget->GetType() == cGameObject::OBJ_NPC ||
          mPushingTarget->GetType() == cGameObject::OBJ_PROP))
        {

          static const int MAX_RECURSION_DEPTH = 4;
          mRecursionDepth++;
          if (PushTarget(time,End,CollSphere) && mRecursionDepth < MAX_RECURSION_DEPTH)
          {
            _CollisionTestWorld(time,Start,End);
            mPushing = true;
            return;
          }
        }
      }
    }
  }
  else
  {
    mPushing=false;
  }

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

  End = CollSphere.GetHitPoint();
}

void 
cPhysicsAnimated::CollisionTest(float time, SyVect3 &displacement)
{
  SyVect3        Floor, FloorNormal;
  float32        FloorDiff;  
  SyCollSurfType FloorSurfaceType;
  SyVect3        Start, End;

  cGraphicCharacter *graphic = prop_cast<cGraphicCharacter*>(mOwner->GetGraphic());
  SyAssert(graphic);

  cAnimCharControllerInput *input = graphic->GetAnimInput();
/* Perform a main-sphere collision test */
  SyScene *scene = mOwner->GetTitan()->GetScene();



  SyVect3 location = mOwner->GetLocation();
  Start = location;

  Start.Y +=  mCollisionHeight;
  End       = Start + displacement;

  mRecursionDepth = 0;
  _CollisionTestWorld(time,Start,End);

  /* Perform a collision against the floor - where is it, feet touching it? */

  input->mHeight = 20.0f;
  if(CalcFloor( End, *scene, Floor, FloorNormal, FloorSurfaceType ))
  {
    /* Is the player touching the floor - move the player up to the floor level?  */


    if ((End.Y - Floor.Y) < mCollisionHeight && mVelocity.Y < 0.0f)
    {
        /* Landed? */

        /* Move the player up to the floor level */

        End.Y = Floor.Y + mCollisionHeight;    

        mVelocity.Y = 0.0f;  

        /* Stairs? */

        if(FloorNormal.Y > 0.9f)
        {
          FloorDiff = location.Y- Floor.Y;
        }

      input->mHeight = End.Y-Floor.Y-mCollisionHeight;
      input->mFall = false;
    }       

    /* Is the floor close enough to snap the player down to it? */

    else if((End.Y - Floor.Y) < (mCollisionHeight + mMaxStepSize) && mVelocity.Y < 0.0f)
    {
      if (1) 
      {
        /* Landed? */

        /* Snap player down to the floor level */

        End.Y = Floor.Y + mCollisionHeight;    
        mVelocity.Y = 0.0f;  
        meLocomotion = LOCO_ANIMATION;
        input->mHeight = End.Y-Floor.Y-mCollisionHeight;
        input->mFall = false;


        /* Stairs? */

        if(FloorNormal.Y > 0.9f)
        {
          FloorDiff = location.Y - Floor.Y;
          if((FloorDiff > mMinStepSize) && (FloorDiff < mMaxStepSize))
          {
          }
        }
      }
    }
  }
  else 
  {
    // player falling?
    if (meLocomotion != LOCO_JUMP)
    {
      meLocomotion = LOCO_JUMP;
      input->mFall = true;
      mJumpStartVel(0.0f,0.0f,0.0f);
      mJumpPushVel(0.0f,0.0f,0.0f);
    }
   }
  

 
  input->mYVel   = mVelocity.Y;
  /* Update the environment flags */


  /* Compute the location,  */

   
  End.Y -= mCollisionHeight;
  mOwner->SetLocation(End);


  /* Set the surface type */

  return;
}

void 
cPhysicsAnimated::Jump(const SyVect3 &force)
{
  meLocomotion= LOCO_JUMP;
  mJumpStartVel= force;
  mVelocity.Y = force.Y;
  mJumpStartVel.Y = 0.0f;
  mJumpPushVel(0.0f,0.0f,0.0f);
}

void 
cPhysicsAnimated::Impulse(const SyVect3 &force)
{
  mJumpPushVel = force;
}

void 
cPhysicsAnimated::NetworkSetLocation(const SyVect3 &location)
{
  mRenderOffset.Sub(mOwner->GetLocation(),location);
  mOwner->SetLocation(location);
  mNetworkUpdateTime = 0.0f;
}

bool           
cPhysicsAnimated::SetSafeLocation(const SyVect3 &loc)// finds a safe location near this spot
{
  if (CheckLocation(loc))
  {
    // that was too easy.
    mOwner->SetLocation(loc);
    return true;
  }
  // ok, search around a couple times
  Titan *titan = mOwner->GetTitan();

  float angle_offset = (float)titan->Random(0,360); // randomize starting location to keep exploits out

  angle_offset = SY_DEG_TO_RAD(angle_offset);
  float ANGLE_INC = SY_DEG_TO_RAD(60.0f);
  float radius = 1.5f * mCollisionRadius;
  for (int ii=0;ii<3;++ii)
  {
    for (float angle = SY_DEG_TO_RAD(-180.0f);angle < SY_DEG_TO_RAD(180.0f); angle += ANGLE_INC)
    {
      float angle_adj = AngleNormalize(angle + angle_offset);
      SyVect3 attempt(loc);

      attempt.X += SY_SIN(angle_adj) * radius;
      attempt.Z += SY_COS(angle_adj) * radius;

      if (CheckLocation(attempt))
      {
        mOwner->SetLocation(attempt);
        return true;
      }
    }

    radius += mCollisionRadius;
    angle_offset += ANGLE_INC/3;
  }
  return false;
}

bool 
cPhysicsAnimated::CheckLocation(const SyVect3 &loc)
{
  SyCollSphere   CollSphere;
  SySceneFilter  Filter;

  cGraphicCharacter *graphic = prop_cast<cGraphicCharacter*>(mOwner->GetGraphic());
  SyAssert(graphic);

/* Perform a main-sphere collision test */

  SyActorHandle handle = graphic->GetActorHandle();
  SyScene *scene = mOwner->GetTitan()->GetScene();

  Filter.Init( handle);
  SyVect3 Start = loc;

  Start.Y +=  mCollisionHeight;
  SyVect3 End       = Start;
  End.Y += 0.3f;

  CollSphere.Init( Start, End, mCollisionRadius );

  /* Collide with the world */

  if (scene->Collide( CollSphere, Filter ))
  {
    return false;
  }
  return true;
}


  /*******************************/
  /* CalcFoor                    */
  /*******************************/
  
  int cPhysicsAnimated:: CalcFloor( 
                                  SyVect3&        Point, 
                                  SyScene&        Scene, 
                                  SyVect3&        Floor, 
                                  SyVect3&        FloorNormal,
                                  SyCollSurfType& FloorSurfaceType )
{
  cFloorFilter        Filter;
  SyCollSphere        CollSphere;
  SyVect3             Start, End;

  /* Initialize the filter */

  cGraphicActor *graphic = (cGraphicActor*) mOwner->GetGraphic();
  SyActorHandle handle = graphic->GetActorHandle();
  Filter.Init( handle );

  /* Initialize the collision */

  Start     = Point;
  End       = Point;
  End.Y    -= mHeight;

  CollSphere.Init( Start, End, mFloorCollRadius );

  /* Perform the floor collision */

  if(Scene.Collide( CollSphere, Filter ))
  {
    Floor     = CollSphere.GetHitPoint();
    Floor.Y  -= mFloorCollRadius;

    FloorNormal      = CollSphere.GetHitNormal();
    FloorSurfaceType = CollSphere.GetHitSurfaceType();

    return(1);
  }

  Floor = Point;
  FloorNormal( 0.0f, 0.0f, 0.0f );
  FloorSurfaceType = SYCOLLSURFTYPE_NONE;

  return(0);
}



void                
cPhysicsAnimated::Knockback(const SyVect3 &source_loc,float XZamount,float Yamount)
{
  // figure out angle to attacker
  
  float towards = mOwner->GetHeading();

  towards = mOwner->GetHeadingTowards(source_loc);

  // figure out facing

  float difference = AngleDifference(mOwner->GetHeading(),towards);

  cGraphicCharacter *graphic = prop_cast<cGraphicCharacter*>(mOwner->GetGraphic());

  cAnimCharControllerInput *input = graphic->GetAnimInput();

  if (input == NULL)
  {
    return;
  }

  if (difference < SY_DEG_TO_RAD(90) && difference > SY_DEG_TO_RAD(-90))
  {
    input->mbKnockforward = true;
    input->mKnockbackHeading = towards;

  }
  else
  {
    input->mbKnockback = true;
    input->mKnockbackHeading = AngleNormalize(towards + SY_DEG_TO_RAD(180.0f));
  }

  SyVect3 away;
  away.X = SY_SIN(towards) * -XZamount; 
  away.Y = Yamount;
  away.Z = SY_COS(towards) * -XZamount; 

  input->mKnockbackVect = away;

};

/*******************************/
/* FilterActor                 */
/*******************************/

int cFloorFilter :: FilterActor( SyScene& Scene, SyActorHandle ActorHandle )
{
  if((Scene.GetActorSpriteType(ActorHandle) == SYSPRITETYPE_CSPRITE) || (ActorHandle == Self))
  {
    return(1);
  }

  return(0);
}

//--------------------------------------------------------------------------------------------- cPhysicsProjectile

cPhysicsProjectile::cPhysicsProjectile()
: mSourceID(ID_NONE),
  mAge(0.0f),
  mHitGround(false),
  mHeadingTowards(0),
  mLastHit(NULL)
{
  InitPropObject( mCLASSID );
  mCollideable = false;
};


int           
cPhysicsProjectile::InitPropClass()
{

/* Add the class */

  AddSubClass( mCLASSID, 
               cPhysics::mCLASSID,
               mCLASSID,
               "cPhysicsProjectile", 
               Creator, 
               mCLASSID, 
               0 ); 
  return 0;

};


SyPropObject* 
cPhysicsProjectile::Creator()
{
  SyPropObject *pObject;

  pObject = SyNew cPhysicsProjectile();
  if(pObject == NULL)
  {                            
    SyAssert(0);
    return(NULL);
  }

  return(pObject);
};


void 
cPhysicsProjectile::Update(float time)
{
  SyVect3 displacement(0,0,0);
  mAge += time;

  if (mHitGround)
  {
    return;
  }

  displacement.MulAdd(mVelocity,time);

  cStatsProjectile *stats = (cStatsProjectile *) mOwner->GetStats();

  if (stats->GetMaster()->mSpins)
  {
    static const float SPIN_SPEED = SY_DEG_TO_RAD(360) * 5; // revs/sec
    float heading = mOwner->GetHeading();
    heading += SPIN_SPEED * time;
    mOwner->SetHeading(heading);
  }

  float gravity_mul = stats->GetMaster()->mGravity;
  if (gravity_mul > 0.0f)
  {
    float accel = gravity_mul * GRAVITY * time;    
    mVelocity.Y = mVelocity.Y + accel;

    
    float speed = stats->GetMaster()->mSpeed;
    float max_pitch =-SY_ATAN(mVelocity.Y/speed); // this is the pitch if the projectile were facing the target
    if (!stats->GetMaster()->mSpins)
    {
      mOwner->SetPitch(max_pitch);
    }
    else
    {
      float delta_heading = AngleDifference(mOwner->GetHeading(),mHeadingTowards);
      float pitch = max_pitch * SY_COS(delta_heading);
      mOwner->SetPitch(pitch);
    }
  }

  if (stats->GetMaster()->mAcceleration > 0.0001f || stats->GetMaster()->mAcceleration < -0.0001f)
  {
    SyVect3 accel(mVelocity);
    accel.Normalize();
    accel *= stats->GetMaster()->mAcceleration;
    mVelocity.MulAdd(accel, time);
  }

  CollisionTest(time,displacement);

#if 0
  // todo: derive from common cPhysicsMoving object and put this code in it.  Derive cPhysicsAnim from 
  // the same class
  if (mOwner->IsRemote())
  {
    mNetworkUpdateTime += time;
    float frac = mNetworkUpdateTime/RUBBER_BAND_TIME;
    if (frac > 1.0f)
    {
      frac = 1.0f;
    }
    frac =  1.0f - frac;
    SyVect3 offset(0.0f,0.0f,0.0f);

    offset.MulAdd(mRenderOffset,frac);
    mRenderLocation = mOwner->GetLocation()+offset;
    mRenderHeading = mOwner->GetHeading();
  }
#endif  
};

bool 
cPhysicsProjectile::CheckForDelete()
{
  static const float LIFESPAN = 5.0f; // seconds
  if (mAge > LIFESPAN)
  {
    return true;
  }
  return false;

};

void 
cPhysicsProjectile::Launch(cGameObject *src,const SyVect3 &target)
{
  cStatsProjectile *stats = (cStatsProjectile *) mOwner->GetStats();
  mSourceID = src->GetID();

  // todo: get launch location from hierarchy
  SyVect3 launch = src->GetLocation();
  SyVect3 adj_target = target;
  adj_target.Y += 0.75f; // one half height of target
  launch.Y += 0.75f; // approx. height of bow
  
  mOwner->SetLocation(launch);
  float heading = mOwner->GetHeadingTowards(target);
  mOwner->SetHeading(heading);
  mHeadingTowards = heading;

  SyVect3 delta; 
  delta.Sub(adj_target,launch);

  float speed = stats->GetMaster()->mSpeed;
  float gravity_mul = stats->GetMaster()->mGravity;

  if (gravity_mul > 0.0f)
  {
    float delta_height = delta.Y;
    delta.Y = 0.0f;
    float distance = delta.Magnitude();
    float target_time = distance/speed; 

    mVelocity.X = SY_SIN(heading) * speed;
    mVelocity.Y = delta_height/target_time -  target_time * gravity_mul * GRAVITY * 0.5f;
    mVelocity.Z = SY_COS(heading) * speed;
  }
  else
  {
    delta.Normalize();
    mVelocity.Mul(delta,speed);
  }

  //SyAssert(src->GetStats()->DynamicCast(STATSCHARACTER_CLASSID)!=NULL);
  SyAssert(prop_cast<cStatsCharacter *>(src->GetStats()) != NULL);
  //SyAssert(PROP_CAST(cStatsCharacter,src->GetStats())!=NULL);
  static_cast<cStatsCharacter*>(src->GetStats())->CalcRangedAttack(&mDmgPacket);
};

void                 
cPhysicsProjectile::CollisionTest(float time,SyVect3 &displacement)
{
  SyVect3       Start, End;
  SyCollSphere  CollSphere;

  cGraphicActor *graphic = prop_cast<cGraphicActor*>(mOwner->GetGraphic());
  SyAssert(graphic);

  SyActorHandle handle = graphic->GetActorHandle();
  SyScene *scene = mOwner->GetTitan()->GetScene();
  cStatsProjectile *stats = (cStatsProjectile *) mOwner->GetStats();

  SySceneFilter Filter;
  Filter.Init( handle);

  SyVect3 location = mOwner->GetLocation();
  Start = location;
  End       = Start + displacement;

  
  CollSphere.Init( Start, End,stats->GetMaster()->mCollisionRadius);

  /* Collide with the world */

  if (scene->Collide( CollSphere, Filter ))
  {
    SyActorHandle hitHandle=Filter.GetActor();

    // quick check to avoid a search of the registry
    if (hitHandle != 0)
    {
      cGameObject *hit = mOwner->GetRegistry()->FetchByActorHandle(hitHandle);

      if (hit != NULL && hit != mOwner && hit->GetID() != mSourceID && hit != mLastHit)
      {
        if (stats->GetMaster()->mAddCondition != NULL)
        {
          cell params[] = { stats->GetMaster()->mAddConditionTimeMS, ID_NONE };
          hit->AddCondition(stats->GetMaster()->mAddCondition, 2, params);
        }

        // damage target
        cDamagePacket dmg = mDmgPacket;
        hit->GetStats()->CalcRangedDefend(&dmg);

        if (!stats->GetMaster()->mPenetrates)
        {
          mAge = 100.0; // kill yourself later
          mVelocity(0.0f, 0.0f, 0.0f);
          stats->Die(mOwner->GetID());
        }
      }
      mOwner->SetLocation(End);
      return;
    }
    else
    {
      mHitGround = true;
      mVelocity(0.0f, 0.0f, 0.0f);
      stats->Die(mOwner->GetID());
    }
  }

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

  End = CollSphere.GetHitPoint();

  mOwner->SetLocation(End);
};

int                  
cPhysicsProjectile::CalcFloor( SyVect3&        Point, 
                               SyScene&        Scene, 
                               SyVect3&        Floor, 
                               SyVect3&        FloorNormal,
                               SyCollSurfType& FloorSurfaceType )
{
  return 0;
};

void cPhysicsProjectile::Shoot(tGameID projectileMasterID,
                               cGameObject* pOwner,
                               const SyVect3& targetPos,
                               cGameObject* pTarget)
{
  SyAssert(projectileMasterID!=ID_NONE && pOwner!=NULL);
  
  cGameObjectRegistry* pRegistry = pOwner->GetRegistry();
  const cStatsProjectileMaster* pStats = pOwner->GetTitan()->GetDatabaseSys()->GetProjectileMaster(projectileMasterID);

  SyAssert(pStats && pStats->mNum > 0);

  SyVect3 offsetDir, offsetHPR, dirToTarget, startLoc(pOwner->GetLocation());
  tGameObjectID  projID;
  cGameObject* pObj;

  float angle, distToTarget;
  float anglePerProjectile = 0.0f;
  float angleOffset = 0.0f;

  if (pStats->mNum > 1)
  {
    anglePerProjectile = pStats->mMaxArc / ((float)(pStats->mNum-1));
    angleOffset = pStats->mMaxArc*(-0.5f);
  }
  
  // semi-hack to make sure even projectile counts split with one projectile
  // going straight ahead of NPC (so AI can target it).
  if (((pStats->mNum%2) == 0) && anglePerProjectile > 20.0f)
  {
    angleOffset += anglePerProjectile*0.5f;
  }

  for (int i=0; i<pStats->mNum; ++i)
  {
    projID = pRegistry->Create(cGameObject::OBJ_PROJECTILE);
    pObj = pRegistry->Fetch(projID);
    static_cast<cStatsProjectile*>(pObj->GetStats())->SetMaster(projectileMasterID);

    if (pStats->mNum > 1)
    {
      angle = ((float)i)*anglePerProjectile + angleOffset;
      angle = SY_DEG_TO_RAD(angle);
      dirToTarget = targetPos - startLoc;
      distToTarget = dirToTarget.NormalizeMagn();
      dirToTarget.HPR(&offsetHPR.X, &offsetHPR.Y, &offsetHPR.Z);
      offsetHPR.X += angle;
      offsetDir.HPR(offsetHPR.X, offsetHPR.Y, offsetHPR.Z);
      offsetDir *= distToTarget;
      static_cast<cPhysicsProjectile*>(pObj->GetPhysics())->Launch(pOwner,startLoc+offsetDir);
    }
    else
    {
      static_cast<cPhysicsProjectile*>(pObj->GetPhysics())->Launch(pOwner,targetPos);
    }

    pRegistry->InitObject(projID, false);

    if (pTarget)
    {
      SyAssert(prop_cast<cIntelProjectile*>(pObj->GetIntel())!=NULL);
      static_cast<cIntelProjectile*>(pObj->GetIntel())->SetTarget(pTarget->GetID());
    }
  }
}
//////////////////////////////////////////////////////////////////////////////////////////// Global Funcs
void 
RegPropClasses_Physics()
{
  cPhysics::InitPropClass();
  cPhysicsStatic::InitPropClass();
  cPhysicsDynamic::InitPropClass();
  cPhysicsAnimated::InitPropClass();
  cPhysicsProjectile::InitPropClass();
  cPhysicsProp::InitPropClass();

}
// EOF

