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

//-------------------------------------------------------- Includes
#include "cameracontroller.h"
#include "gameobj.h"
#include "SyScene.h"
#include "registry.h"
#include "TiCameraDirector.h"
#include "tuning.h"
#include "levelobj.h"
#include "graphic.h"
#include "debugoverlay.h"
//---------------------------------------------- Class Declarations

class cCameraAI
{
public:
  cCameraAI() : mTitan(NULL){};
  virtual ~cCameraAI(){};

  virtual SyVect3 GetLoc()=0;
  virtual SyVect3 GetDir()=0;
  virtual float   GetViewAngle()=0; 
  virtual SyVect3 GetTargetLoc(){return CalcTargetLocation();};

          void    SetTitan(Titan *titan) {mTitan = titan;};

  virtual void    RegisterTuningVars(const char *name){};
          
  virtual void    Update(float time){};
  virtual void    SetTarget(tGameID id){};
  virtual tGameID GetTarget(){return ID_NONE;};
  virtual void    SetHeading(float heading){};
  virtual float   GetHeading(){return 0;};
  virtual void    RequestHeading(float heading){};
  virtual void    Pan(float amount){};

  virtual void    SetTarget2(tGameID id){};
  virtual void    SetCamera(SyCamera *camera){};

  virtual void    Transition(cCameraAI *other){};
  virtual void    Cut(){};
  virtual int     GetVariables(float *distance,
                               float *pitch, 
                               float *yOffset, 
                               float *leadDistance, 
                               float *fov){return false;}

  virtual bool    HitTether(tGameID id, SyVect3 &start, SyVect3 &end,float radius){return false;};
  virtual void    Override(const SyVect3 &loc,const SyVect3 &dir,float viewAngle){};

  virtual SyVect3 CalcTargetLocation()=0;
  virtual SyVect3 CalcListenerLocation() { SyVect3 loc = CalcTargetLocation(); loc.Y += 1.0f; return loc; }
  virtual void    SetMaxDistance(float maxDistance){};

protected:
  Titan         *mTitan;

};



class cCameraAI_Overhead1P : public cCameraAI 
{

public:
  cCameraAI_Overhead1P();
  ~cCameraAI_Overhead1P();

  virtual SyVect3 GetLoc(){return mLoc;};
  virtual SyVect3 GetDir(){return mDir;};
  virtual float   GetViewAngle(){return mFOV.GetValue();}; 

  virtual void    RegisterTuningVars(const char *name);

  virtual void    Update(float time);
  virtual void    SetTarget(tGameID id);
  virtual tGameID GetTarget(){return mTarget;};
  virtual void    SetHeading(float heading);
  virtual float   GetHeading(){return mHeading.GetValue();};
  virtual void    RequestHeading(float heading);
  virtual void    Pan(float amount);

  virtual void    Transition(cCameraAI *other);
  virtual void    Cut();

  virtual int     GetVariables(float *distance,float *pitch, float *yOffset, float *leadDistance, float *fov);
  virtual SyVect3 CalcTargetLocation();
  virtual SyVect3 GetTargetLoc();

protected:
  virtual bool    _UpdateHeading(float time);
  virtual void    _CalcDesiredLoc(const SyVect3 &target_loc,SyVect3 *cam_loc,float heading,float distance);
  virtual void    _CalcDesiredDir(SyVect3 target_loc,const SyVect3 &cam_loc,SyVect3 *Cam_Dir);
  virtual void    _DeterminePitch(float time);
  virtual float   _CheckPitchLOS(tGameID target);
  virtual bool    _Collide(tGameID targetID,SyVect3 *col_point,SyVect3 start,SyVect3 end,float radius = 0.25f);
  

  virtual void    _GetCameraLocation(SyVect3 *result,
                             const SyVect3 &start,
                             float distance, 
                             float pitch, 
                             float heading, 
                             float lead_distance,
                             float y_offset);


  tGameID   mTarget;
  SyVect3   mLoc;
  SyVect3   mDir;
  //float     mCurPitch;

  float     mDistanceSetting;
  float     mPitchSetting;
  float     mFOVSetting;
  float     mLeadDistanceSetting;
  float     mYOffsetSetting;

  cTransientAngle   mHeading;
  float     mPitchResetCountdown;

  cTransient        mDistance;
  cTransientAngle   mPitch;
  cTransient        mYOffset;
  cTransient        mLeadDistance;
  cTransient        mFOV;
  float             mPanSpeed;

};

class cCameraAI_Overhead2P : public cCameraAI_Overhead1P
{
public:

  cCameraAI_Overhead2P();
  ~cCameraAI_Overhead2P(){};

  virtual void    Update(float time);
  virtual void    SetTarget2(tGameID id){mTarget2 = id;};
  virtual void    SetCamera(SyCamera *camera){mCamera = camera;};
  virtual bool    HitTether(tGameID id, SyVect3 &start, SyVect3 &end,float radius);
  virtual SyVect3 CalcTargetLocation();

private:
  virtual void    _DeterminePitch(float time);

  tGameID   mTarget2;
  SyCamera *mCamera;
  float     mfPlayerDistance;

};

class cCameraAI_Cutscene : public cCameraAI
{
public:
  cCameraAI_Cutscene(){};
  virtual ~cCameraAI_Cutscene(){};

  virtual SyVect3 GetLoc(){return mLoc;}
  virtual SyVect3 GetDir(){return mDir;}
  virtual float   GetViewAngle(){return mViewAngle;} 

  virtual void Override(const SyVect3 &loc,const SyVect3 &dir,float viewAngle);
  virtual SyVect3 CalcTargetLocation() {return mLoc;}

protected:
  SyVect3     mLoc;
  SyVect3     mDir;
  float       mViewAngle;
};

class cCameraAI_Follow : public cCameraAI_Overhead1P
{
public:
  cCameraAI_Follow():mPush(0.0f),mPopTime(0.0f),mbGoOver(false),mYPush(0.0f){};
  virtual ~cCameraAI_Follow(){};


  virtual SyVect3 GetLoc();
  virtual void    Update(float time);
  virtual void    Transition(cCameraAI *other);
  virtual void    Cut();

  virtual void    RequestHeading(float heading);
  virtual SyVect3 GetTargetLoc();
  virtual void    SetHeading(float heading);

protected:
  virtual bool    _UpdateHeading(float time);
  virtual void    _CheckLOS();
  virtual void    _DragHeading(float time);
  virtual float   _TestHeading(float heading);
  virtual void    _ForceUp(float time);
  virtual void    _FindValidLoc();

          bool    mCollided;
          bool    mDistanceSwivel;
          float   mCurDistance;
          float   mPush;
          float   mPopTime;
          float   mMoveDistance;
          SyVect3 mPrevTargetLoc;
          bool    mbGoOver;
          float   mYPush; // because of collision 


};

class cCameraAI_ScriptLookAt : public cCameraAI
{
public:
  cCameraAI_ScriptLookAt():mViewAngle(45.0f),mMaxDistance(300.0f){};
  virtual ~cCameraAI_ScriptLookAt(){};

  virtual SyVect3 GetLoc();
  virtual SyVect3 GetDir();
  virtual float   GetViewAngle(){return mViewAngle;}; 

  virtual SyVect3 CalcTargetLocation();
  virtual void    SetTarget(tGameID id);
  virtual void    SetSource(tGameID id);
  virtual void    SetViewAngle(float view_angle){mViewAngle = view_angle;};
  virtual void    SetTargetYOffset(float offset){mTargetYOffset = offset;};
  virtual void    SetSourceYOffset(float offset){mSourceYOffset = offset;};
  virtual void    SetMaxDistance(float maxDistance){mMaxDistance = maxDistance;};

  virtual tGameID  GetSource() { return mSourceID; }
  virtual tGameID  GetTarget() { return mTargetID; }
  virtual float    GetTargetYOffset(){ return mTargetYOffset; };
  virtual float    GetSourceYOffset(){ return mSourceYOffset;};
  virtual float    GetMaxDistance(){ return mMaxDistance; };

protected:

  float       mViewAngle;
  tGameID     mTargetID;
  tGameID     mSourceID;
  float       mTargetYOffset;
  float       mSourceYOffset;
  float       mMaxDistance;
};

class cCameraAI_Transition : public cCameraAI
{
public:
  cCameraAI_Transition(){};
  virtual ~cCameraAI_Transition(){};

          void    SetOwner(cCameraController *owner);

          void    Init(eCameraSetting from,eCameraSetting to);

  virtual void    Update(float time);

  virtual SyVect3 GetLoc();
  virtual SyVect3 GetDir();
  virtual float   GetViewAngle();
  virtual SyVect3 CalcTargetLocation(){return mTarget;}

  
protected:

  cCameraController *mpOwner;
  SyVect3         mLocFrom;
  SyVect3         mLookAtFrom;
  float           mViewAngleFrom;

  SyVect3         mLoc;
  SyVect3         mDir;
  SyVect3         mTarget;
  float           mViewAngle;

  eCameraSetting  mSettingTo;
  eCameraSetting  mSettingFrom;
  cTransient      mPercentage;

};

class cCameraAI_Free : public cCameraAI
{
public:
  cCameraAI_Free():mViewAngle(45.0f){};
  virtual ~cCameraAI_Free(){};

  void    SetOwner(cCameraController *owner);

  virtual void    Update(float time);
  virtual SyVect3 CalcTargetLocation();

  virtual SyVect3 GetLoc(){return mLoc;}
  virtual SyVect3 GetDir(){return mDir;}
  virtual float   GetViewAngle(){return mViewAngle;} 

  virtual void    Transition(cCameraAI *other);

protected:
  cCameraController *mpOwner;

  SyVect3     mLoc;
  SyVect3     mDir;
  float       mViewAngle;
};

//--------------------------------------------------------- Globals
//----------------------------------------- Functions Declarations

// note that all these variables are modified by the tuning system.
float l_Distance[NUM_CAMERA_TYPES];
float l_Pitch[NUM_CAMERA_TYPES];
float l_FOV[NUM_CAMERA_TYPES];
float l_LeadDistance[NUM_CAMERA_TYPES];
float l_Y_Offset[NUM_CAMERA_TYPES];

float l_ZoomSpeed   = 10.0f; // meters/sec
float l_PanSpeed    = SY_DEG_TO_RAD(180.0f); // rads/sec
float l_ZoomAcceleration = 1.0f;
float l_PitchSpeed  = SY_DEG_TO_RAD(90.0f);
float l_PitchAcceleration = SY_DEG_TO_RAD(9.0f);
float l_PitchResetSpeed  = SY_DEG_TO_RAD(90.0f);
float l_PitchResetAcceleration = SY_DEG_TO_RAD(9.0f);
float l_Drift = SY_DEG_TO_RAD(10.0f);
float l_TetherDistance = 13.0f;
float l_Zoom2P_Distance   = 10.0f; // meters/sec
float l_Zoom2P_Min   = 12.0f; // meters/sec
float l_NearDistanceMin = 10.0f; // distance where we start pushing "up"
int   l_Swivel_Near = 0;
float l_PitchResetDelay = 1.0f; // in secs
float l_NearZoomOutAcceleration = 1.0f;
float l_NearZoomOutVelocity   = 20.0f; // meters/sec

float l_SwivelAcceleration = 1.0f; // 
float l_SwivelDeceleration = 1.0f; // 
float l_SwivelMaxVelocity  = 1.0f; // 

float l_PanAcceleration = 1.0f; // 
float l_PanMaxVelocity  = 1.0f; // 
float l_PushAcceleration  = 1.0f; // 
float l_PushMaxVelocity  = 1.0f; // 

float l_TransitionVel = 1.0f;
float l_TransitionAccel = 1.0f;

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

class CameraCollideFilter : public SySceneFilter
{
public:
  virtual int FilterActor( SyScene& Scene, SyActorHandle ActorHandle );
};

//------------------------------------ CameraCollideFilter

int 
CameraCollideFilter::FilterActor(SyScene &Scene, SyActorHandle actorhandle)
{
  return 1; // for now
};

//------------------------------------ cCamera


//------------------------------------ cCamera_Overhead1P

cCameraAI_Overhead1P::cCameraAI_Overhead1P() :
  mTarget(ID_NONE),
  mLoc(0,0,0),
  mDir(0,0,1.0f),
  mDistanceSetting(5.0f),
  //mCurPitch(15.0f),
  mPitchSetting(15.0f),
  mFOVSetting(45.0f),
  mLeadDistanceSetting(1.0f),
  mYOffsetSetting(0.0f),
  mPitchResetCountdown(0),
  mPanSpeed(0)

{
};

cCameraAI_Overhead1P::~cCameraAI_Overhead1P()
{
};

void 
cCameraAI_Overhead1P::Update(float time)
{
  if (mTarget == ID_NONE)
  {
    return;
  }
  mDistance.SetConstants(l_ZoomSpeed,l_ZoomAcceleration);
  mYOffset.SetConstants(l_ZoomSpeed,l_ZoomAcceleration);
  mLeadDistance.SetConstants(l_ZoomSpeed,l_ZoomAcceleration);
  mFOV.SetConstants(l_PitchSpeed,l_PitchAcceleration);
  mHeading.SetConstants(l_SwivelMaxVelocity,l_SwivelAcceleration);


  mDistance.Update(time);
  mPitch.Update(time);
  mYOffset.Update(time);
  mLeadDistance.Update(time);
  mFOV.Update(time);

  _UpdateHeading(time);
  _DeterminePitch(time);

  SyVect3 TargetLoc = CalcTargetLocation();
  _CalcDesiredLoc(TargetLoc,&mLoc,mHeading.GetValue(),mDistance.GetValue());
  _CalcDesiredDir(TargetLoc,mLoc,&mDir);

};

void 
cCameraAI_Overhead1P::SetTarget(tGameID id)
{  
  mTarget = id; 
}

void
cCameraAI_Overhead1P::RegisterTuningVars(const char *camera_name)
{
  char buffer[512];
  sprintf(buffer,"Camera_Distance_%s",camera_name);
  gTuningSys.AddFloat(&mDistanceSetting,buffer);

  sprintf(buffer,"Camera_Pitch_%s",camera_name);
  gTuningSys.AddAngle(&mPitchSetting,buffer);

  // stored as degrees, not radians, so we don't use "AddAngle"
  sprintf(buffer,"Camera_FieldOfView_%s",camera_name);
  gTuningSys.AddFloat(&mFOVSetting,buffer);

  sprintf(buffer,"Camera_LeadDistance_%s",camera_name);
  gTuningSys.AddFloat(&mLeadDistanceSetting,buffer);

  sprintf(buffer,"Camera_Y_Offset_%s",camera_name);
  gTuningSys.AddFloat(&mYOffsetSetting,buffer);
}

void    
cCameraAI_Overhead1P::Transition(cCameraAI *other)
{
  float distance,pitch,yoffset,leadDistance,fov;

  if (other!=NULL && other->GetVariables(&distance,&pitch,&yoffset,&leadDistance,&fov))
  {
    mDistance.SetValue(distance);
    mPitch.SetValue(pitch);
    mYOffset.SetValue(yoffset);
    mLeadDistance.SetValue(leadDistance);
    mFOV.SetValue(fov);

    mDistance.SetTarget(mDistanceSetting);
    mPitch.SetTarget(mPitchSetting);
    mYOffset.SetTarget(mYOffsetSetting);
    mLeadDistance.SetTarget(mLeadDistanceSetting);
    mFOV.SetTarget(mFOVSetting);
  }
  else
  {
    Cut();
  }
}

void    
cCameraAI_Overhead1P::Cut()
{
  mDistance.SetTarget(mDistanceSetting);
  mYOffset.SetTarget(mYOffsetSetting);
  mLeadDistance.SetTarget(mLeadDistanceSetting);
  mFOV.SetTarget(mFOVSetting);
  mPitch.SetTarget(mPitchSetting);

  mDistance.SetValue(mDistanceSetting);
  mYOffset.SetValue(mYOffsetSetting);
  mLeadDistance.SetValue(mLeadDistanceSetting);
  mFOV.SetValue(mFOVSetting);
  mPitch.SetValue(mPitchSetting);
}

int     
cCameraAI_Overhead1P::GetVariables(float *distance,float *pitch, float *yOffset, float *leadDistance, float *fov)
{
  *distance = mDistance.GetValue();
  *pitch = mPitch.GetValue();
  *yOffset = mYOffset.GetValue();
  *leadDistance = mLeadDistance.GetValue();
  *fov = mFOV.GetValue();
  return true;
};
void 
cCameraAI_Overhead1P::Pan(float amount)
{
  mPanSpeed = amount;
}

void 
cCameraAI_Overhead1P::SetHeading(float heading)
{
  mHeading.SetValue(heading);
  mHeading.ClearTarget();
}

void 
cCameraAI_Overhead1P::RequestHeading(float heading)
{
  mHeading.SetTarget(heading);
}

bool
cCameraAI_Overhead1P::_UpdateHeading(float time)
{

  DEBUGOVERLAY_DRAWSCREENTEXT(0, "AI", 100, 130, ("Spd:%f", mPanSpeed), cDebugOverlay::WHITE);

  float amount = mPanSpeed *time * l_PanSpeed;
  float maxSpeed = mPanSpeed * l_PanMaxVelocity;
  if (maxSpeed < 0) maxSpeed = -maxSpeed;
  mHeading.Push(amount,maxSpeed);
  mPanSpeed = 0.0f;
  mHeading.Update(time);
  return true;
};

SyVect3
cCameraAI_Overhead1P::CalcTargetLocation()
{
  if (mTarget == ID_NONE) {return SyVect3(0,0,0);};
  cGameObject *target = mTitan->GetRegistry()->Fetch(mTarget);
  return target->GetLocation();
}

void 
cCameraAI_Overhead1P::_CalcDesiredLoc(const SyVect3 &target_loc,
                                           SyVect3 *Cam_Loc,
                                            float heading,
                                            float distance)
{

  SyVect3 Forward;
  float pitch = mPitch.GetValue();
  if (pitch < SY_DEG_TO_RAD(10.0f)) pitch = SY_DEG_TO_RAD(10.0f);
  if (pitch > SY_DEG_TO_RAD(70.0f)) pitch = SY_DEG_TO_RAD(70.0f);

  SyVect3 away;
  Forward(-SY_SIN(heading),0.0f,-SY_COS(heading));
  away(SY_SIN(pitch)* Forward.X, -SY_COS(pitch),SY_SIN(pitch)*Forward.Z);

  *Cam_Loc = target_loc;
  SyVect3 offset;
  offset.Mul(away,distance);
  *Cam_Loc -= offset; // meters
  Cam_Loc->Y += mYOffset.GetValue();
};

void 
cCameraAI_Overhead1P::_CalcDesiredDir(SyVect3 target_loc,const SyVect3 &cam_loc,SyVect3 *Cam_Dir)
{
  target_loc.X += -SY_SIN(mHeading.GetValue()) * mLeadDistance.GetValue(); 
  target_loc.Z += -SY_COS(mHeading.GetValue()) * mLeadDistance.GetValue();

  Cam_Dir->Sub(target_loc,cam_loc);
  Cam_Dir->Normalize();
}

void  
cCameraAI_Overhead1P::_DeterminePitch(float time)
{

  float pitch;

  pitch = _CheckPitchLOS(mTarget);

  if (mPitch.GetTarget() < pitch )
  {
    // we're going down

    if (mPitchResetCountdown <= 0.0)
    {
      mPitch.SetConstants(l_PitchResetSpeed,l_PitchResetAcceleration);
    }
    else
    {
      mPitch.SetConstants(0,l_PitchResetAcceleration);
      mPitchResetCountdown -= time;
    }
  }
  else
  {
    mPitchResetCountdown = l_PitchResetDelay;

    // we're going up
    mPitch.SetConstants(l_PitchSpeed,l_PitchAcceleration);
  }
  mPitch.SetTarget(pitch);
}

float  
cCameraAI_Overhead1P::_CheckPitchLOS(tGameID target)
{
  float fudge = SY_DEG_TO_RAD(5.0f);
  SyVect3 cam_loc;
  float pitch = mPitchSetting + fudge;
  SyVect3 start = mTitan->GetRegistry()->Fetch(target)->GetLocation();
  _GetCameraLocation(&cam_loc,
                    start,
                    mDistance.GetValue(),
                    pitch,
                    mHeading.GetValue(),
                    mLeadDistance.GetValue(),
                    mYOffset.GetValue());

  // collision test from player to camera to see if that's a valid location...

  SyVect3 collision_point;
  SyVect3 target_loc = start;
  target_loc.Y += 1.5f;

  static const float PITCH_INC = SY_DEG_TO_RAD(2.5f);
  static const float MIN_PITCH = SY_DEG_TO_RAD(1.0f);
  

  DEBUGOVERLAY_DRAWLINE(target, "CAMERA",target_loc,cam_loc,SyColor32F(1.0f,0.5f,0.5f,1.0f));
  DEBUGOVERLAY_DRAWSPHERE(target, "CAMERA", target_loc,1.0f, SyColor32F(0.5f,0.5f,1.0f,0.8f));

  while (_Collide(target,&collision_point,target_loc,cam_loc))
  {
    pitch -= PITCH_INC;
    if (pitch < MIN_PITCH) 
    {
      pitch = MIN_PITCH;
      break;
    }
    
    _GetCameraLocation(&cam_loc,
                      start,
                      mDistance.GetValue(),
                      pitch,
                      mHeading.GetValue(),
                      mLeadDistance.GetValue(),
                      mYOffset.GetValue());

  }
  return pitch-fudge;
}


bool
cCameraAI_Overhead1P::_Collide(tGameID targetID,SyVect3 *col_point,SyVect3 start,SyVect3 end, float radius)
{
  cGameObject *target = mTitan->GetRegistry()->Fetch(targetID);
  cGraphicCharacter* graphic = prop_cast<cGraphicCharacter *>(target->GetGraphic());
  SyAssert(graphic);

  SyActorHandle handle = graphic->GetActorHandle();
  CameraCollideFilter Filter;
  
  SyScene *scene = mTitan->GetScene();

  Filter.Init( handle);

  SyCollSphere CollSphere;
  CollSphere.Init( start, end, radius ); 

  /* Collide with the world */

  if (scene->Collide( CollSphere, Filter ))
  {
    *col_point = CollSphere.GetHitPoint();
    DEBUGOVERLAY_DRAWSPHERE(targetID, "CAMERA", *col_point,1.0f, SyColor32F(1.0f,0.3f,0.0f,0.8f));
     return true;
  }
  return false;
};

void
cCameraAI_Overhead1P::_GetCameraLocation(SyVect3 *result,
                                       const SyVect3 &start,
                                       float distance, 
                                       float pitch, 
                                       float heading, 
                                       float lead_distance,
                                       float y_offset)
{

  SyVect3 Cam_Loc, Cam_Dir,Forward;


  Forward(-SY_SIN(heading), 0.0f,-SY_COS(heading));

  Cam_Dir(SY_SIN(pitch)* Forward.X, -SY_COS(pitch),SY_SIN(pitch)*Forward.Z);


  *result = start;

  SyVect3 offset;

  offset.Mul(Cam_Dir,distance);
  *result -= offset; // meters
  result->X += -SY_SIN(heading) * lead_distance;
  result->Y += y_offset;
  result->Z += -SY_COS(heading) * lead_distance;
};

SyVect3 
cCameraAI_Overhead1P::GetTargetLoc()
{
  SyVect3 base = CalcTargetLocation();
  base.X += -SY_SIN(mHeading.GetValue()) * mLeadDistance.GetValue(); 
  base.Z += -SY_COS(mHeading.GetValue()) * mLeadDistance.GetValue();
  //base.Y += mYOffset.GetValue();
  return base;
}

//------------------------------------ cCamera_Overhead2P

cCameraAI_Overhead2P::cCameraAI_Overhead2P() :
  mTarget2(ID_NONE)
{
}
bool 
cCameraAI_Overhead2P::HitTether(tGameID id, SyVect3 &Start, SyVect3 &End, float radius)
{
  if (mTarget2==ID_NONE) return false;
  static float WALK_EDGE = 1.0f;
  static float far_plane = 35.0f; 

  if (id == mTarget || id == mTarget2)
  {
    const SyFrustum &frustum = mCamera->GetFrustum();

    bool hitTether = false;
    for (int ii=0;ii < 6;++ii)
    {
      SyPlane plane = frustum.Plane(ii);

      if (ii==SyFrustumFar)// bring far plane in really close so we can't walk too far away
      {
        SyVect3 loc;
        loc.Mul(plane.N,far_plane);
        loc.Add(loc,mCamera->GetLocation());
        plane(plane.N,loc);
      }

      float dist = plane * End;
      float edge = WALK_EDGE;

      if (dist > -(radius + edge)) // we're not far enough on the inside...
      {

        hitTether = true;
        SyVect3 delta;
        delta.Sub(End,Start);
        // remove all motion in direction of normal
        SyVect3 overlap = delta;
        float dot = overlap ^ plane.N;

        if (dot < 0) continue; // were moving away from the edge, it's ok.

        overlap.Mul(plane.N,dot);
        delta.Sub(delta,overlap);
        End.Add(Start,delta);
      }
    }

    return hitTether;
  }

  return false; 
}

SyVect3 
cCameraAI_Overhead2P::CalcTargetLocation()
{
  if (mTarget2 != ID_NONE)
  {
    cGameObject *obj = mTitan->GetRegistry()->Fetch(mTarget);
    cGameObject *obj2 = mTitan->GetRegistry()->Fetch(mTarget2);

    // get clip space location
    const SyMatrix44 &view = mCamera->GetViewProjection();
    SyVect4 obj_view  = obj->GetLocation() * view;
    SyVect4 obj2_view = obj2->GetLocation() * view;

    float factor1 = 1.0f/obj_view.W;
    float factor2 = 1.0f/obj2_view.W;

    SyVect3 delta;
    delta.Sub(obj->GetLocation(),obj2->GetLocation());
    delta.Mul(delta,factor1/(factor1+factor2));

    SyVect3 target;
    target.Add(obj2->GetLocation(),delta);

    return target;
  }

  cGameObject *target = mTitan->GetRegistry()->Fetch(mTarget);
  return target->GetLocation();
}

void  
cCameraAI_Overhead2P::_DeterminePitch(float time)
{

  float pitch;

  pitch = _CheckPitchLOS(mTarget);

  if (mTarget2!=ID_NONE)
  {
    float pitch2 = _CheckPitchLOS(mTarget2);
    if (pitch2 < pitch) pitch = pitch2;
  }

  if (mPitch.GetTarget() < pitch )
  {
    // we're going down

    if (mPitchResetCountdown <= 0.0)
    {
      mPitch.SetConstants(l_PitchResetSpeed,l_PitchResetAcceleration);
    }
    else
    {
      mPitch.SetConstants(0,l_PitchResetAcceleration);
      mPitchResetCountdown -= time;
    }
  }
  else
  {
    mPitchResetCountdown = l_PitchResetDelay;

    // we're going up
    mPitch.SetConstants(l_PitchSpeed,l_PitchAcceleration);
  }

  if (mTarget2 != ID_NONE && mfPlayerDistance < l_Zoom2P_Distance)
  {
    float frac = mfPlayerDistance/l_Zoom2P_Distance; 
    float distance = (mDistanceSetting-l_Zoom2P_Min) * frac + l_Zoom2P_Min;
    mDistance.SetTarget(distance);
  }
  else
  {
    mDistance.SetTarget(mDistanceSetting);
  }

  mPitch.SetTarget(pitch);
}
                                     
void    
cCameraAI_Overhead2P::Update(float time)
{
  mDistance.SetConstants(l_ZoomSpeed,l_ZoomAcceleration);
  mYOffset.SetConstants(l_ZoomSpeed,l_ZoomAcceleration);
  mLeadDistance.SetConstants(l_ZoomSpeed,l_ZoomAcceleration);
  mFOV.SetConstants(l_PitchSpeed,l_PitchAcceleration);

  mHeading.SetConstants(l_SwivelMaxVelocity,l_SwivelAcceleration);

  cCameraAI_Overhead1P::Update(time);
  if (mTarget2 != ID_NONE)
  {
    // calc distance of the players from each other...

    cGameObject *target = mTitan->GetRegistry()->Fetch(mTarget);
    cGameObject *target2 = mTitan->GetRegistry()->Fetch(mTarget2);
    SyVect3 loc = target->GetLocation();
    SyVect3 loc2 = target2->GetLocation();

    mfPlayerDistance = loc.Distance(loc2);
  }
};




//------------------------------------ cCameraAI_Follow

void    
cCameraAI_Follow::Update(float time)
{
  if (mTarget == ID_NONE)
  {
    return;
  }

  if (time == 0.0f)
  {
    return;
  }

  mDistance.SetConstants(l_NearZoomOutVelocity,l_NearZoomOutAcceleration);
  mYOffset.SetConstants(l_ZoomSpeed,l_ZoomAcceleration);
  mLeadDistance.SetConstants(l_ZoomSpeed,l_ZoomAcceleration);
  mFOV.SetConstants(l_PitchSpeed,l_PitchAcceleration);
  mPitch.SetConstants(l_PitchSpeed,l_PitchAcceleration);
  mHeading.SetConstants(l_SwivelMaxVelocity,l_SwivelAcceleration);

  mDistance.Update(time);
  mPitch.Update(time);
  mYOffset.Update(time);
  mLeadDistance.Update(time);
  mFOV.Update(time);

  /*
  mDistance.SetValue(mDistanceSetting);
  mYOffset.SetValue(mYOffsetSetting);
  mLeadDistance.SetValue(mLeadDistanceSetting);
  mFOV.SetValue(mFOVSetting);
  */

  if (mbGoOver)
  {
     
    cCameraAI_Overhead1P::_UpdateHeading(time);
    cCameraAI_Overhead1P::_DeterminePitch(time);
    SyVect3 TargetLoc = CalcTargetLocation();
    TargetLoc.Y += mYOffset.GetValue();

    mCurDistance= TargetLoc.Distance(mLoc);

    //if (mCurDistance < mDistanceSetting) 
    //{
    mDistance.SetTarget(mDistanceSetting);
    //}

    if (mPitch.GetTarget() == mPitchSetting &&
        mPitch.GetValue() == mPitchSetting &&
        mDistance.GetValue()==mDistanceSetting)
    {
      mbGoOver = false;
    }

    TargetLoc = CalcTargetLocation();
    cCameraAI_Overhead1P::_CalcDesiredLoc(TargetLoc,&mLoc,mHeading.GetValue(),mDistance.GetValue());
    _CalcDesiredDir(TargetLoc,mLoc,&mDir);
    //_ForceUp();
  }
  else
  {
    SyVect3 TargetLoc = CalcTargetLocation();
    TargetLoc.Y += mYOffset.GetValue();

    _DragHeading(time);
    _UpdateHeading(time);

    _CheckLOS();
    // check distance...
    float dist = TargetLoc.Distance(mLoc);

    if (dist > mDistanceSetting +0.0001f)
    {
      // shouldn't be too far from player!
      SyVect3 delta;
      delta.Sub(mLoc,TargetLoc);
      delta.Normalize();
      delta.Mul(delta,mDistanceSetting);
      mLoc.Add(TargetLoc,delta);
    }

    #if 0
    if (dist < l_NearDistanceMin  || mCollided)
    {
      _PickNewHeading();
    }
    #endif


    if (/*dist < l_NearDistanceMin  || (mDistanceSwivel && dist < mDistance.GetValue()) ||*/ mCollided )
    {
      // ok, we're getting a little TOO close to the player now, spin out...

      float push = l_PushAcceleration * time;
      float dir_push = mPush * time;
      if (mMoveDistance < 0.0001f) 
      {
        push = 0.0f;
        dir_push = 0.0f;
      }

      if (mHeading.GetVelocity() < 0.0001f && mHeading.GetVelocity() > -0.0001f)
      {
        if (dir_push < 0.0001f || dir_push > 0.0001f)
        {
          mHeading.Push(dir_push,l_PushMaxVelocity);
        }
        else
        {
          cGameObject *target = mTitan->GetRegistry()->Fetch(mTarget);
          float dir = target->GetHeading()+ SY_DEG_TO_RAD(180.0f);
          dir = AngleNormalize(dir);
          dir = AngleDifference(mHeading.GetValue(), dir);
          if (dir > 0.0f)
          {
            mHeading.Push(-push ,l_PushMaxVelocity);
          }
          else
          {
            mHeading.Push(push ,l_PushMaxVelocity);
          }

          if (dist < l_NearDistanceMin)
          {
            mDistanceSwivel = true;
            mHeading.ClearTarget();
          }
        }
      }
      else
      {
        if (mHeading.GetVelocity() > 0.0f) 
        {
          mHeading.Push(push,l_PushMaxVelocity);
        }
        else
        {
          mHeading.Push(-push,l_PushMaxVelocity);
        }
      }
    }
    else
    {
      if (mHeading.GetVelocity() < 0.001f)
      {
        mDistanceSwivel = false;  
      }
    }

    TargetLoc = CalcTargetLocation();
    _CalcDesiredDir(TargetLoc,GetLoc(),&mDir);
  }
};


#if 0
static const int NUM_TESTS = 24;
static const float TEST_INTERVAL = SY_DEG_TO_RAD(5.0f);
static const float TEST_DISTANCE = TEST_INTERVAL * NUM_TESTS;

void
cCameraAI_Follow::_PickNewHeading()
{
 

  bool sweep_left = true;
  bool sweep_right = true;

  float best_angle = mHeading.GetValue();
  float distance = TEST_DISTANCE;

  if (mHeading.GetVelocity() > 0.0001f)
  {
    _TestInterval(TEST_INTERVAL,&best_heading,&distance);
  }
  else if (mHeading.GetVelocity() < -0.0001f)
  {
    _TestInterval(-TEST_INTERVAL,&best_heading,&distance);
  }
  else
  {
    _TestInterval(-TEST_INTERVAL,&best_heading,&distance);
    _TestInterval(TEST_DISTANCE,&best_heading,&distance);
  }

  mHeading.SetTarget(best_heading);

};


void
cCameraAI_Follow::_TestInterval(float interval, float &best_heading, float &best_distance)
{
  float cur_angle = mHeading.GetValue();

  for (int ii=0;ii<NUM_TESTS;++ii)
  {

  }
};
#endif


void    
cCameraAI_Follow::Transition(cCameraAI *other)
{
  cCameraAI_Overhead1P::Transition(other);
  SyVect3 TargetLoc = CalcTargetLocation();
  _FindValidLoc();
  _CalcDesiredDir(TargetLoc,mLoc,&mDir);

  
  
  mPrevTargetLoc = TargetLoc;
 // mbGoOver =true;
};

void
cCameraAI_Follow::SetHeading(float value)
{
  cCameraAI_Overhead1P::SetHeading(value);
  Cut();
}

void    
cCameraAI_Follow::Cut()
{
  cCameraAI_Overhead1P::Cut();
  SyVect3 TargetLoc = CalcTargetLocation();
  _FindValidLoc();
  _CalcDesiredDir(TargetLoc,mLoc,&mDir);


  mPrevTargetLoc = TargetLoc;
  //mbGoOver = false;
}

void
cCameraAI_Follow::_FindValidLoc()
{
  SyVect3 TargetLoc = CalcTargetLocation();
  float total_dist = mDistance.GetValue();
  _CalcDesiredLoc(TargetLoc,&mLoc,mHeading.GetValue(),total_dist);
  TargetLoc.Y += mYOffset.GetValue() ;
  SyVect3 col_point1;
  if (_Collide(mTarget,&col_point1,TargetLoc,mLoc))
  {
    mLoc = col_point1;
  }
  
}
void 
cCameraAI_Follow::RequestHeading(float heading)
{
  //if (_TestHeading(heading))
  //{
    cCameraAI_Overhead1P::RequestHeading(heading);
  //}
}

void
cCameraAI_Follow::_DragHeading(float time)
{
  // adjusting heading based on character movement
  float pitch = mPitch.GetValue();
  //float pitch = mPitch.GetTarget();
  if (pitch < SY_DEG_TO_RAD(10.0f)) pitch = SY_DEG_TO_RAD(10.0f);
  if (pitch > SY_DEG_TO_RAD(70.0f)) pitch = SY_DEG_TO_RAD(70.0f);


  float height = SY_COS(pitch) * mDistance.GetValue();
  float xzdistance = SY_SIN(pitch) * mDistance.GetValue();

  //float height = SY_COS(pitch) * l_NearDistanceMin;
  //float xzdistance = SY_SIN(pitch) * l_NearDistanceMin;

  SyVect3 TargetLoc = CalcTargetLocation();

  mMoveDistance = TargetLoc.Distance(mPrevTargetLoc);
  mPrevTargetLoc = TargetLoc;
  TargetLoc.Y += mYOffset.GetValue();

  SyVect3 delta;
  SyVect3 dir;
  delta.Sub(mLoc,TargetLoc);
  delta.Y = 0.0f;
  delta.Normalize();
  dir = delta;
  delta.Mul(delta,xzdistance);
  delta.Y = height;
  SyVect3 newLoc;
  newLoc.Add(TargetLoc,delta);

  mCurDistance= TargetLoc.Distance(mLoc);



  if ( mCurDistance < l_NearDistanceMin)
  {
    // collision vs world...
    SyVect3 col_point;
    if (_Collide(mTarget,&col_point,mLoc,newLoc))
    {
      // we've bumped into a wall...don't move there? 
      newLoc = col_point;
      // adjust a little
      //mbGoOver = true;
    }

    mLoc = newLoc;
    mCurDistance= TargetLoc.Distance(mLoc);
    mDistance.SetValue(mCurDistance);
    mDistance.SetTarget(l_NearDistanceMin);
  }
  else
  {
    mDistance.SetValue(mCurDistance);
  }

  mHeading.SetValue(SY_ATAN2(delta.X,delta.Z));
  _ForceUp(time);

};

void
cCameraAI_Follow::_ForceUp(float time)
{

  SyVect3 TargetLoc = CalcTargetLocation();
  TargetLoc.Y += mYOffset.GetValue();

  SyVect3 delta;
  delta.Sub(mLoc,TargetLoc);
  float height = delta.Y;
  delta.Y = 0.0f;
  float curXZDistance = delta.Magnitude();

  //static const float Y_Push_Restore = 8.0f;
  //float push_restore = Y_Push_Restore * time;

  //if (mYPush > push_restore)
 // {
 //   mYPush -= push_restore;
 // }
  //else
  //{
    mYPush = 0.0f;
  //}

  if (mCurDistance < l_NearDistanceMin)
  {
    float desired_height = l_NearDistanceMin * l_NearDistanceMin  - curXZDistance * curXZDistance;
    desired_height = SY_SQRT(desired_height);
    if (height < desired_height)
    {

      //float push = desired_height-height;
      //if (mYPush < push) mYPush = push;

      mYPush = desired_height - height;

      //mPitch.SetValue(SY_ATAN2(curXZDistance,desired_height));
      //mPitch.SetTarget(mPitchSetting);
    }
  }
};

bool
cCameraAI_Follow::_UpdateHeading(float time)
{
  if (cCameraAI_Overhead1P::_UpdateHeading(time))
  {
    // adjust location based on heading

    SyVect3 dir;
    SyVect3 TargetLoc = CalcTargetLocation();
    SyVect3 newLoc;
    _CalcDesiredLoc(TargetLoc,&newLoc,mHeading.GetValue(),mCurDistance);
    TargetLoc.Y += mYOffset.GetValue() ;
    TargetLoc.Y += 1.0f;
    SyVect3 col_point1;
    SyVect3 col_point2;
    if (_Collide(mTarget,&col_point1,mLoc,newLoc))
    {
      if (_Collide(mTarget,&col_point2,TargetLoc,newLoc))
      {
        
        SyVect3 test_vect = col_point2;
        test_vect.Y = mLoc.Y;
        if (test_vect.Distance(mLoc) > 1.0f)
        {
          // this is a pop.
          mPopTime += time;
          if (mPopTime > 0.25f)
          {
            // go ahead and pop it
            mLoc = col_point2;
          }
          else
          {
            // figure out a more valid location (even w/out being able to see target)
            // guess partway between target and screen
            SyVect3 new_test = newLoc;
            new_test -= TargetLoc;
            new_test.Mul(new_test,0.75f);
            new_test += TargetLoc;
            SyVect3 col_point3;
            
            if (!_Collide(mTarget,&col_point3,mLoc,new_test)) // new test point is colliding, too
            {
              if (_Collide(mTarget,&col_point3,new_test,newLoc))
              {
                mLoc = col_point3;
              }
              else
              {
                mLoc = newLoc;
              }
            }
            else
            {
              //mLoc = col_point3;
              //mPopTime = 0.0f;
            }
            //mbGoOver = true;

          }
          //mLoc = col_point1;
          //mHeading = old_heading;
        }
        else
        {
          mPopTime = 0.0f;
          mLoc = col_point2;
        }
      }
      else
      {
        mPopTime = 0.0f;
        mLoc = newLoc;//col_point1;
      }
    }
    else
    {
      mPopTime = 0.0f;
      mLoc = newLoc;
    }
    return true;
  }
  return false;
}

void
cCameraAI_Follow::_CheckLOS()
{
  static const float OFFSET_ANGLE = SY_DEG_TO_RAD(15.0f);

  mPush = 0.0f;
  mCollided = false;
  float dist1 = _TestHeading(AngleNormalize(mHeading.GetValue() + OFFSET_ANGLE));

  float threshold = 1.0f - 0.0001f;
  if (dist1 < threshold)
  {
    mCollided = true;
    mPush = -l_PushAcceleration; 
  }

  float dist2 = _TestHeading(mHeading.GetValue());

  if (dist2 < threshold)
  {
    mCollided = true;
  }

  float dist3 = _TestHeading(AngleNormalize(mHeading.GetValue() - OFFSET_ANGLE));

  if (dist3 < threshold && dist3 < dist1)
  {
    mCollided = true;
    mPush = l_PushAcceleration; 
  }
}

float
cCameraAI_Follow::_TestHeading(float heading)
{
  SyVect3 TargetLoc = CalcTargetLocation();
  SyVect3 newLoc;
  float total_dist = mDistance.GetValue();
  if (total_dist > l_NearDistanceMin) total_dist = l_NearDistanceMin;
  _CalcDesiredLoc(TargetLoc,&newLoc,heading,total_dist);
  TargetLoc.Y += mYOffset.GetValue() ;
  // todo : collision vs world...
  SyVect3 col_point1;
  if (_Collide(mTarget,&col_point1,TargetLoc,newLoc))
  {
    col_point1.Y = TargetLoc.Y;
    return col_point1.Distance(TargetLoc)/total_dist;
  }
  return 1.0f;
}

SyVect3 
cCameraAI_Follow::GetLoc()
{
  SyVect3 result = mLoc;
  result.Y += mYPush;
  return result;
};

SyVect3 
cCameraAI_Follow::GetTargetLoc()
{
  SyVect3 base = CalcTargetLocation();
  base.X += -SY_SIN(mHeading.GetValue()) * mLeadDistance.GetValue(); 
  base.Z += -SY_COS(mHeading.GetValue()) * mLeadDistance.GetValue();
  //base.Y += mYOffset.GetValue();
  return base;
}
//------------------------------------ cCameraAI_Cutscene

void 
cCameraAI_Cutscene::Override(const SyVect3 &loc,const SyVect3 &dir,float viewAngle)
{
  mLoc = loc;
  mDir = dir;
  mViewAngle = viewAngle;
};

//------------------------------------ cCameraAI_ScriptLookAt

SyVect3 
cCameraAI_ScriptLookAt::GetLoc()
{
  cGameObject *source = mTitan->GetRegistry()->Fetch(mSourceID);
  if (source==NULL)
  {
    return SyVect3(0.0f, 0.0f, 0.0f);
  }

  SyVect3 loc = source->GetLocation();
  loc.Y += mSourceYOffset;
  
  SyVect3 target_loc = CalcTargetLocation();
  float dist = loc.Distance(target_loc);
  if (dist > mMaxDistance)
  {
    float frac = mMaxDistance/dist;
    SyVect3 delta;
    delta.Sub(loc,target_loc);
    delta.Mul(delta,frac);
    loc.Add(target_loc,delta);
  }

  return loc;
  
}

SyVect3 
cCameraAI_ScriptLookAt::GetDir()
{
  SyVect3 target_loc = CalcTargetLocation();
  SyVect3 Cam_Dir;
  Cam_Dir.Sub(target_loc,GetLoc());
  Cam_Dir.Normalize();

  return Cam_Dir;
}


SyVect3 
cCameraAI_ScriptLookAt::CalcTargetLocation()
{
  cGameObject *target = mTitan->GetRegistry()->Fetch(mTargetID);
  if (target==NULL)
  {
    target = mTitan->GetRegistry()->BeginType(cGameObject::OBJ_PLAYER);

    if (target == NULL)
    {
      return SyVect3(0.0f, 0.0f, 0.0f);
    }
  }
  SyVect3 loc = target->GetLocation();
  
  loc.Y += mTargetYOffset;
  return loc;
}

void    
cCameraAI_ScriptLookAt::SetTarget(tGameID id)
{
  mTargetID = id;
}

void    
cCameraAI_ScriptLookAt::SetSource(tGameID id)
{
  mSourceID = id;
}

//------------------------------------ cCameraAI_Transition

void    
cCameraAI_Transition::SetOwner(cCameraController *owner)
{
  mpOwner = owner;
};


void    
cCameraAI_Transition::Init(eCameraSetting from,eCameraSetting to)
{
  assert(from != CAMERA_TRANSITION);
  mSettingFrom = from;
  mSettingTo = to;
  mPercentage.SetValue(0);
  mPercentage.SetTarget(1.0f);
  mPercentage.SetConstants(l_TransitionVel,l_TransitionAccel);

  Update(0);
};

void    
cCameraAI_Transition::Update(float time)
{
  mPercentage.Update(time);
  if (mPercentage.GetValue() >= 1.0f)
  {
    mpOwner->Cut(mSettingTo);
    return;
  }

  cCameraAI *fromAI = mpOwner->GetCameraAI(mSettingFrom);
  fromAI->Update(time);
  mLocFrom = fromAI->GetLoc();
  mLookAtFrom = fromAI->GetTargetLoc();
  mViewAngleFrom = fromAI->GetViewAngle();

  cCameraAI *toAI = mpOwner->GetCameraAI(mSettingTo);
  toAI->Update(time);
  SyVect3 delta;
  delta.Sub(toAI->GetLoc(),mLocFrom);
  delta.Mul(delta,mPercentage.GetValue());
  mLoc.Add(mLocFrom,delta);

  delta.Sub(toAI->GetTargetLoc(),mLookAtFrom);
  delta.Mul(delta,mPercentage.GetValue());
  mTarget.Add(mLookAtFrom,delta);
  mDir.Sub(mTarget,mLoc);
  mDir.Normalize();

  mViewAngle = mPercentage.GetValue() * (toAI->GetViewAngle() - mViewAngleFrom);
  mViewAngle += mViewAngleFrom;
  
};


SyVect3 
cCameraAI_Transition::GetLoc()
{
  return mLoc;
};

SyVect3 
cCameraAI_Transition::GetDir()
{
  return mDir;
};

float   
cCameraAI_Transition::GetViewAngle()
{
  return mViewAngle;
};

//------------------------------------ cCameraFree
  
void    
cCameraAI_Free::SetOwner(cCameraController *owner)
{
  mpOwner = owner;
};

void    
cCameraAI_Free::Update(float time)
{
  int controllerID = 0;
  TitanControllerI *controller = mpOwner->GetTitan()->GetTitanUI()->GetController(controllerID);

  if (!controller)
  {
    return;
  }

  float forward = (float)controller->GetForward() / TitanControllerI::DIRECTION_MAX;
  static const float MAX_SPEED = 40.0f;

  SyVect3 delta;
  SyVect3 move_dir;
  move_dir = mDir;
  move_dir.Y = 0;
  move_dir.Normalize();
  delta.Mul(move_dir, MAX_SPEED * forward * time);
  mLoc += delta;

  float right = controller->Normalize(controller->GetRight());
  SyVect3 up(0,1.0f,0);
  SyVect3 side;
  side.Cross(mDir, up);
  side.Normalize();
  delta.Mul(side, right*MAX_SPEED * time);
  mLoc += delta;

  float turn = controller->Normalize(controller->GetRightStickRight());

  float MAX_ROTATION_SPEED = SY_DEG_TO_RAD(180.0f);
  if (turn != 0.0f)
  {
    SyVect3 newDir;
    SyMatrix44 mat;
    mat.RotateY(-turn * time * MAX_ROTATION_SPEED);
    mat.Mul(mDir, newDir);
    mDir = newDir;
  }

  float pitch = (float)controller->GetRightStickForward() / TitanControllerI::DIRECTION_MAX;
  
  if (pitch != 0.0f)
  {
    SyVect3 newDir;
    SyMatrix44 mat;
    mat.Rotate(side, pitch * time * MAX_ROTATION_SPEED);
    mat.Mul(mDir, newDir);
    mDir = newDir;
  }

  static const float MAX_STRAFE_SPEED = 20.0f;
  if (controller->GetButtonState(ButtonBlock))
  {
    mLoc.Y += MAX_STRAFE_SPEED * time;
  }
  if (controller->GetButtonState(ButtonDodge))
  {
    mLoc.Y -= MAX_STRAFE_SPEED * time;
  }

  if (controller->GetButtonState(ButtonJump))
  {
    cGameObjectRegistry *registry;

    registry = mpOwner->GetTitan()->GetRegistry();
    
    cGameObject *obj = registry->BeginType(cGameObject::OBJ_PLAYER);

    while(obj != NULL)
    {
      SyVect3 dest;
      dest.Mul(mDir, 10.0f);
      dest.Add(dest,mLoc);
      obj->SetLocation(dest);
      obj = registry->NextType(obj);
    }
  }
}


SyVect3 
cCameraAI_Free::CalcTargetLocation()
{
  SyVect3 target;
  SyVect3 offset;

  offset.Mul(mDir,10.0);
  target.Add(mLoc,offset);
  return target;
}

void    
cCameraAI_Free::Transition(cCameraAI *other)
{
  mLoc = other->GetLoc();
  mDir = other->GetDir();
};
//------------------------------------ cCameraController

cCameraController::cCameraController(Titan *titan) :
  mTitan(titan),
  mDirector(NULL),
  mCutTime(0.0f),
  mCutDir(0,0,0),
  mCutStored(false),
  mbTwoPlayer(false),
  mbReverse(false),
  mPrevDirStored(false),
  mPrevDir(1,0,0),
  mbCutsceneCam(false),
  mUserSetting(CAMERA_NEAR), 
  mScriptSetting(CAMERA_NONE), // choice from scripting camera
  mShakeMagnitude(0.1f),
  mShakeDuration(0.0f),
  mShakeSpeed(20.0f),
  mShakeDelay(0.0f),
  mfPlayerDistance(0),
  mSettingChangeState(SETTING_CHANGE_NONE),
  mSettingChangeTime(0.0f),
  mSetting(CAMERA_NEAR)
{
  for (int ii=0;ii<NUM_CAMERA_TYPES;++ii)
  {
    mCameraAIs[ii] = NULL;
  }
}

cCameraController::~cCameraController()
{
  OnExit();
}

void 
cCameraController::Shake(float delay, float shakeMagnitude, float oscSpeed, float durationSeconds)
{
  mShakeMagnitude = shakeMagnitude;
  mShakeSpeed = oscSpeed;
  mShakeDelay = delay;
  mShakeDuration = durationSeconds;
  mLastShake = mTitan->GetTime();
}
 
  /// Initializes the application
  const char *camera_names[] = 
  {
    "Near",
    //"Medium",
    "Far",
    "TwoP",
    "Cutscene",
    "Script_LookAt1",
    "Transition",
    "Script_LookAt2",
    "Script_LookAt3",
    "Free",

  };

bool 
cCameraController::OnInit()
{
  // set up scene
  SyVect3 Loc( 0.0f, 0.0f, 0.0f );
  SyVect3 Dir( 0.0f, 0.0f, 1.0f );

  float32 ViewAngle = 45.0f;                


  float32 fNear( 0.1f );
  float32 fFar( 100.0f );
  SyScene *scene = mTitan->GetScene();
  mfAspectRatio = GetAspectRatio();
  mCamera.Init(*scene->GetRasterDev(), Loc, Dir, ViewAngle, mfAspectRatio, fNear, fFar );

  mDirector = new TiCameraDirector; 
  //mDirector->LoadCameraFile ("data/art/level/prototype.cam"); // remove comment to test AI / scripted cam cuts.

  RegisterTuningVariables();

  Shake( 0.0f, 0.0f, 0.0f, 0.0f );



  mCameraAIs[CAMERA_NEAR] = new cCameraAI_Follow;
  //mCameraAIs[CAMERA_MEDIUM] = new cCameraAI_Overhead1P;
  mCameraAIs[CAMERA_FAR] = new cCameraAI_Overhead1P;
  mCameraAIs[CAMERA_TWOPLAYER] = new cCameraAI_Overhead2P;
  mCameraAIs[CAMERA_CUTSCENE] = new cCameraAI_Cutscene;
  mCameraAIs[CAMERA_SCRIPT_LOOKAT1] = new cCameraAI_ScriptLookAt;
  mCameraAIs[CAMERA_SCRIPT_LOOKAT2] = new cCameraAI_ScriptLookAt;
  mCameraAIs[CAMERA_SCRIPT_LOOKAT3] = new cCameraAI_ScriptLookAt;
  cCameraAI_Transition *transition = new cCameraAI_Transition;
  transition->SetOwner(this);
  mCameraAIs[CAMERA_TRANSITION] = transition;
  cCameraAI_Free *freeCam = new cCameraAI_Free;
  freeCam->SetOwner(this);
  mCameraAIs[CAMERA_FREE] = freeCam;

  #define NUM_CAMERA_NAMES (sizeof(camera_names)/sizeof(camera_names[0]))
  SyAssert(NUM_CAMERA_NAMES == NUM_CAMERA_TYPES);
  for (int ii=0;ii<NUM_CAMERA_TYPES;++ii)
  {
    if (mCameraAIs[ii] != NULL)
    {
      mCameraAIs[ii]->SetTitan(mTitan);
      mCameraAIs[ii]->RegisterTuningVars(camera_names[ii]);
    }
  }

  return true;
}

  /// Called on exit
void 
cCameraController::OnExit()
{
  delete mDirector;
  mDirector = NULL;

  for (int ii=0;ii<NUM_CAMERA_TYPES;++ii)
  {
    delete mCameraAIs[ii];
  }
}


void
cCameraController::RegisterTuningVariables()
{
  gTuningSys.AddAngle(&l_PanSpeed,"Camera_PanSpeed");
  gTuningSys.AddFloat(&l_ZoomSpeed,"Camera_ZoomSpeed");
  gTuningSys.AddFloat(&l_ZoomAcceleration,"Camera_ZoomAcceleration");
  gTuningSys.AddAngle(&l_PitchSpeed,"Camera_PitchSpeed");
  gTuningSys.AddAngle(&l_PitchAcceleration,"Camera_PitchAcceleration");

  gTuningSys.AddAngle(&l_PitchResetSpeed,"Camera_PitchResetSpeed");
  gTuningSys.AddAngle(&l_PitchResetAcceleration,"Camera_PitchResetAcceleration");

  gTuningSys.AddAngle(&l_Drift,"Camera_Drift");
  gTuningSys.AddFloat(&l_TetherDistance,"Camera_Tether");
  gTuningSys.AddFloat(&l_Zoom2P_Distance,"Camera_Zoom2P_Distance");
  gTuningSys.AddFloat(&l_Zoom2P_Min,"Camera_Zoom2P_Min");
  gTuningSys.AddFloat(&l_PitchResetDelay,"Camera_PitchResetDelay");
  gTuningSys.AddFloat(&l_NearDistanceMin,"Camera_DistanceMin_Near");
  gTuningSys.AddFloat(&l_SwivelAcceleration,"Camera_SwivelAcceleration");
  gTuningSys.AddFloat(&l_SwivelDeceleration,"Camera_SwivelDeceleration");
  gTuningSys.AddFloat(&l_SwivelMaxVelocity,"Camera_SwivelMaxVelocity");
  gTuningSys.AddFloat(&l_PanAcceleration,"Camera_PanAcceleration");
  gTuningSys.AddFloat(&l_PanMaxVelocity,"Camera_PanMaxVelocity");
  gTuningSys.AddFloat(&l_PushAcceleration,"Camera_PushAcceleration");
  gTuningSys.AddFloat(&l_PushMaxVelocity,"Camera_PushMaxVelocity");
  gTuningSys.AddFloat(&l_NearZoomOutVelocity,"Camera_NearZoomOutVelocity");
  gTuningSys.AddFloat(&l_NearZoomOutAcceleration,"Camera_NearZoomOutAcceleration");
}

void 
cCameraController::LoadCameraFile(const char *camFileName)
{
//  SyAssertf(mDirector!=NULL,"OnInit not called, or called after OnExit");
 // mDirector->LoadCameraFile(camFileName); // BIJAN - TEMP comment. Need to generate new .CAM file (format changed).
  mCutStored = false;
//  mPrevDirStored = false;
  
}

void 
cCameraController::SetTarget(tGameID id)
{
  if (mCameraAIs[mSetting]->GetTarget() != id || mbTwoPlayer)
  {
    mbTwoPlayer = false;
    mCutStored = false;
    //mPrevDirStored = false;

    mCameraAIs[CAMERA_NEAR]->SetTarget(id);
    //mCameraAIs[CAMERA_MEDIUM]->SetTarget(id);
    mCameraAIs[CAMERA_FAR]->SetTarget(id);
    mCameraAIs[CAMERA_TWOPLAYER]->SetTarget(id);
    mCameraAIs[mUserSetting]->Cut();
  }
}


void 
cCameraController::SetTargetTwoPlayer(tGameID id,tGameID id2) // dual camera
{
  mbTwoPlayer = true;
  mCameraAIs[CAMERA_TWOPLAYER]->SetTarget(id);
  mCameraAIs[CAMERA_TWOPLAYER]->SetTarget2(id2);
  mCameraAIs[CAMERA_TWOPLAYER]->SetCamera(&mCamera);
  mUserSetting = CAMERA_TWOPLAYER; 

  if (CAMERA_NEAR == mSetting || CAMERA_FAR == mSetting)
  {
    mCameraAIs[CAMERA_TWOPLAYER]->Cut();
  }
}


void 
cCameraController::Update(float time)
{
  if (mCutStored)
  {
    mCutTime += time;
  }

  ChooseCamera();
  if (mCameraAIs[mSetting] == NULL) return;

  mDirector->UpdateTime(time);

  float32 ViewAngle;
  SyVect3 CamLoc;
  SyVect3 CamDir;

  float32 fNear( 0.1f );
  float32 fFar( 100.0f );
  SyScene *scene = mTitan->GetScene();
  SyVect3 TargetLoc = mCameraAIs[mSetting]->CalcTargetLocation();


  if (mDirector->GetCamInfo(TargetLoc,&CamLoc,&CamDir, &ViewAngle))
  {
    // use director camera
    mCamera.Init(*scene->GetRasterDev(), CamLoc, CamDir, ViewAngle, mfAspectRatio, fNear, fFar );
  }
  else
  {
    mCameraAIs[mSetting]->Update(time);

    ViewAngle = mCameraAIs[mSetting]->GetViewAngle(); 
    CamLoc = mCameraAIs[mSetting]->GetLoc();
    CamDir = mCameraAIs[mSetting]->GetDir();

    HandleCameraShake(mCameraAIs[mSetting]->GetTarget(),CamDir,CamLoc);

    mCamera.Init(*scene->GetRasterDev(), CamLoc, CamDir, ViewAngle, mfAspectRatio, fNear, fFar );

    // why isn't this value just passed in to the function above?
    float aspectRatio = GetAspectRatio();
    SyRaster* raster = scene->GetRasterDev();
    mCamera.SetAspectRatio( *raster, aspectRatio );

    scene->SetFarPlane( mCamera );

    if (mPrevDirStored)
    {
        // check for a cuts so that camera relative controls aren't thrown off 
        // sgc apparently % is the cross product operator

        float cross = (mPrevDir % CamDir).Magnitude();
        float max = SY_SIN(SY_DEG_TO_RAD(30));
        if (cross > max || cross < -max )
        {
        mCutStored = true;
        mCutDir = mPrevDir;
        mCutTime = 0.0f;
        }
    }

    mPrevDir = CamDir;
    mPrevDirStored = true;
  }

  const SyMatrix44& viewInverse = mCamera.GetViewInverse();
  SyVect3 xAxis, yAxis, zAxis, scale, translation;
  viewInverse.ConvertTo(xAxis, yAxis, zAxis, scale, translation);

#if 1   // hack to set the camera right on the target player
  translation = mCameraAIs[mSetting]->CalcListenerLocation();
#endif
  scene->Listen( translation, zAxis, yAxis );

#ifdef _DRAWDEBUGOVERLAY
  DEBUGOVERLAY_DRAWSCREENTEXT(0, 
                              "Anim", 30, 350, 
                              ("Camera: %s",
                               camera_names[mSetting]),
                              cDebugOverlay::WHITE);

  float h, p, r;
  CamDir.HPR(&h, &p, &r);

  DEBUGOVERLAY_DRAWSCREENTEXT(0, 
                              "Anim", 30, 370, 
                              ("CamLoc: %.2f, %.2f, %.2f H:%.0f P:%.0f",
                               CamLoc.X,
                               CamLoc.Y,
                               CamLoc.Z,
                               SY_RAD_TO_DEG(h),
                               SY_RAD_TO_DEG(p)),
                               cDebugOverlay::WHITE);

  SyVect3 targetLoc = mCameraAIs[mSetting]->GetTargetLoc();
  DEBUGOVERLAY_DRAWSCREENTEXT(0, 
                              "Anim", 30, 390, 
                              ("TargetLoc: %.2f, %.2f, %.2f",
                               targetLoc.X,
                               targetLoc.Y,
                               targetLoc.Z),
                               cDebugOverlay::WHITE);
#endif

}

void 
cCameraController::HandleCameraShake(tGameID id,SyVect3 &Cam_Dir,SyVect3 &Cam_Loc)
{
  cGameObject *target = mTitan->GetRegistry()->Fetch(id);
  if (target)
  {
    cGraphicCharacter* graphic = prop_cast<cGraphicCharacter *>(target->GetGraphic());
    if ( graphic )
    {
      SyActorHandle handle = graphic->GetActorHandle();
      SyCSprite* pCSprite = (SyCSprite*)(mTitan->GetScene()->GetActorSpritePtr( handle ));
      static int sfx1 = -1;
      if ( sfx1 == -1 )
      {
        pCSprite->FindTrigger( SyHashRefID( "Special FX 1"), sfx1 );
      }
      if ( sfx1 != -1 && pCSprite->HasTripped( sfx1 ) )
      {
        SyVect3 pos = mTitan->GetScene()->GetActorLocation( graphic->GetActorHandle() );
        SyWaterSystem * pWaterSystem = mTitan->GetScene()->GetWaterSystem();
        if ( pWaterSystem && SyWaterTile::FLUID_NOTFOUND != pWaterSystem->DemoPlunk( pos, 20.0f) )
        {
          Shake( 0.0f, 0.5f, 20.0f, 0.60f );
          pWaterSystem->DemoShudder( pos, 0.02f, 20.0f );
        }
        else
        {
          Shake( 0.0f, 0.3f, 20.0f, 0.33f );
        }
        pCSprite->ResetTrigger( sfx1 );
      }
    }
  }
  if (mShakeDuration > 0.0f)
  {
    SyVect3 shake(0,0,0);
    uint64 now = mTitan->GetTime();
    float diff =  fabs( ((float)(now - mLastShake))/1000.0f );
    mLastShake = now;

    if (mShakeDelay > 0.0f)
    {
      mShakeDelay -= diff;
    }

    if (mShakeDelay <= 0.0f)
    {
      mShakeDuration -= diff;
      SyVect3 worldShake;
      worldShake.X = ( SY_SIN( mShakeSpeed * mShakeDuration )        + 
                       SY_SIN( mShakeDuration * 4.6f * mShakeSpeed ) ) * 0.5f;
      worldShake.Y = ( SY_COS( mShakeSpeed * mShakeDuration * 0.9f ) + 
                       SY_SIN( mShakeDuration * 5.3f * mShakeSpeed ) ) * 0.5f;
      worldShake.Z = ( SY_SIN( mShakeSpeed * mShakeDuration * 0.9f ) + 
                       SY_COS( mShakeDuration * 4.2f * mShakeSpeed ) ) * 0.5f;
      worldShake *= mShakeMagnitude;
      SyPlane plane( Cam_Dir, 0.0f );
      shake = plane.Project( worldShake );
      Cam_Loc += shake;
    }
  }
}

float 
cCameraController::GetControlOffset(float time_held_down)
{
  // eventually, use time held down to see if a cut has occurred with that time frame...
  // (after MAXing time_held_down to something reasonable, say half a second
  // if so, use the old camera angle

  static const float MAX_CAMERA_LATENCY = 0.5f;
  if (time_held_down > MAX_CAMERA_LATENCY)
  {
    time_held_down = MAX_CAMERA_LATENCY;
  }
  const SyVect3 *dir;
  if (mCutStored && mCutTime < time_held_down)
  {
    dir = &mCutDir;
  }
  else
  {
    dir = &mCamera.GetDir();
  }
  float heading = SY_ATAN2(-dir->X,-dir->Z);  // left-right is +x, forward is -z (maya coord system)

  return heading;
}



void 
cCameraController::Zoom(float amount)
{
  float THRESHOLD_HIGH = 0.5f;
  float THRESHOLD_LOW = 0.2f;
  float SETTING_CHANGE_REPEAT = 0.3f;

  switch( mSettingChangeState)
  {
    case SETTING_CHANGE_DOWN:
    {
      if (amount > THRESHOLD_HIGH)
      {
        SettingUp();
        mSettingChangeState = SETTING_CHANGE_UP;
        mSettingChangeTime = 0.0f;
      }
      else if (amount > -THRESHOLD_LOW)
      {
        mSettingChangeState = SETTING_CHANGE_NONE;
        mSettingChangeTime = 0.0f;
      }
      else if (mSettingChangeTime > SETTING_CHANGE_REPEAT)
      {
        SettingDown();
        mSettingChangeTime = 0.0f;
      }
    }
    break;
    case SETTING_CHANGE_NONE:
    {
      if (amount > THRESHOLD_HIGH)
      {
        SettingUp();
        mSettingChangeState = SETTING_CHANGE_UP;
        mSettingChangeTime = 0.0f;
      }
      else if (amount < -THRESHOLD_HIGH)
      {
        SettingDown();
        mSettingChangeState = SETTING_CHANGE_DOWN;
        mSettingChangeTime = 0.0f;
      }
    }
    break;
    case SETTING_CHANGE_UP:
    {
      if (amount < -THRESHOLD_HIGH)
      {
        SettingDown();
        mSettingChangeState = SETTING_CHANGE_DOWN;
        mSettingChangeTime = 0.0f;
      }
      else if (amount < THRESHOLD_LOW)
      {
        mSettingChangeState = SETTING_CHANGE_NONE;
        mSettingChangeTime = 0.0f;
      }
      else if (mSettingChangeTime > SETTING_CHANGE_REPEAT)
      {
        SettingUp();
        mSettingChangeTime = 0.0f;
      }
    }
    break;
  }
}

// utility function that returns the aspect ratio of the display device
float
cCameraController::GetAspectRatio()
{
  SyScene *scene = mTitan->GetScene();
  SyRaster *raster = scene->GetRasterDev();
  int pixelWidth  = raster->GetScreenWidth();
  int pixelHeight = raster->GetScreenHeight();
  float aspectRatio;
  if ( 
    pixelWidth == 720 && 
    ( pixelHeight == 480 || pixelHeight == 576 )
    )
  {
    // assume 4:3 television
    aspectRatio = 4.0f/3.0f;
  }
  else
  {
    // assume square pixels
    aspectRatio = (float)pixelWidth/(float)pixelHeight;
  }
  return aspectRatio;
}

void
cCameraController::SettingUp()
{
  if (mbTwoPlayer) return;
  if ((mSetting > CAMERA_NEAR && mSetting <= CAMERA_TWOPLAYER) && mScriptSetting == CAMERA_NONE)
  {
    SyAssert(mSetting == mUserSetting || mSetting == CAMERA_TRANSITION); 
    if (mCameraAIs[mUserSetting+1] == NULL) return;

    eCameraSetting cur = mUserSetting;
    cCameraAI *prev = mCameraAIs[mUserSetting];
    mUserSetting = (eCameraSetting)(mUserSetting - 1);
    cCameraAI *next = mCameraAIs[mUserSetting];
    next->SetHeading(prev->GetHeading());
    next->Cut();
    mSetting = CAMERA_TRANSITION;
    cCameraAI_Transition *trans = (cCameraAI_Transition *)mCameraAIs[CAMERA_TRANSITION];
    trans->Init(cur,mUserSetting);
  }
  
}

void
cCameraController::SettingDown()
{
  if (mbTwoPlayer) return;
  if ((mSetting < CAMERA_TWOPLAYER && mSetting >= CAMERA_NEAR)&& mScriptSetting == CAMERA_NONE)
  {
    SyAssert(mSetting == mUserSetting || mSetting == CAMERA_TRANSITION); 
    if (mCameraAIs[mUserSetting+1] == NULL) return;

    eCameraSetting cur = mUserSetting;
    cCameraAI *prev = mCameraAIs[mUserSetting];
    mUserSetting = (eCameraSetting)(mUserSetting + 1);
    cCameraAI *next = mCameraAIs[mUserSetting];
    next->SetHeading(prev->GetHeading());
    next->Cut();
    mSetting = CAMERA_TRANSITION;
    cCameraAI_Transition *trans = (cCameraAI_Transition *)mCameraAIs[CAMERA_TRANSITION];
    trans->Init(cur,mUserSetting);   
  }
}

void  
cCameraController::ChooseCamera()
{
  if (mbCutsceneCam)
  {
    Cut(CAMERA_CUTSCENE);
  }
  else if (mSetting != CAMERA_TRANSITION && mScriptSetting != CAMERA_NONE)
  {
    Cut(mScriptSetting);
  }
  else if (mSetting != CAMERA_TRANSITION)
  {
    Cut(mUserSetting);
  }    
}


void
cCameraController::OverrideCamera (const SyVect3& cameraPosition, const SyVect3& cameraDirection, const float32& cameraFov)
{
  mCameraAIs[CAMERA_CUTSCENE]->Override(cameraPosition,cameraDirection,cameraFov);
  mbCutsceneCam = true;
}

void
cCameraController::ClearOverrideCamera ()
{
  mbCutsceneCam = false;
}

void 
cCameraController::Pan(float amount)
{
  if (mbReverse)
  {
    amount = -amount;
  }

  mCameraAIs[mSetting]->Pan(amount);
}

void 
cCameraController::SetHeading(float heading)
{
  // internally, heading is direction from player to the camera
  // but externally, it's the direction of camera to player.
  heading = AngleNormalize(heading+SY_DEG_TO_RAD(180));
  mCameraAIs[mSetting]->SetHeading(heading);
}

void 
cCameraController::RequestHeading(float heading)
{
  heading = AngleNormalize(heading+SY_DEG_TO_RAD(180));
  mCameraAIs[mSetting]->RequestHeading(heading);
}


void 
cCameraController::StartScriptCamLookAt(eCameraSetting setting,
                                        tGameID source, 
                                        tGameID target,
                                        float SourceYOffset,
                                        float TargetYOffset)
{
  assert(setting == CAMERA_SCRIPT_LOOKAT1 || 
         setting == CAMERA_SCRIPT_LOOKAT2 || 
         setting == CAMERA_SCRIPT_LOOKAT3);

  cCameraAI_ScriptLookAt *cam = (cCameraAI_ScriptLookAt*)(mCameraAIs[setting]);

  cam->SetSource(source);
  cam->SetTarget(target);

  cam->SetSourceYOffset(SourceYOffset);
  cam->SetTargetYOffset(TargetYOffset);
  cam->SetViewAngle(45.0f);
}

void 
cCameraController::ScriptCut(eCameraSetting setting)
{
  mScriptSetting = setting;
};

void 
cCameraController::ScriptTransition(eCameraSetting next_setting,float vel, float accel)
{
  if (mSetting == CAMERA_TRANSITION)
  {
    ScriptCut(next_setting);
    return;
  }

  eCameraSetting prev_setting = mSetting;
  mSetting = CAMERA_TRANSITION;
  mScriptSetting = next_setting;
  cCameraAI *prev = mCameraAIs[prev_setting];

  if (CAMERA_NONE == next_setting)
  {
    next_setting = GetUserSetting();
  }

  cCameraAI *next = mCameraAIs[next_setting];
  next->SetHeading(prev->GetHeading());
  next->Cut();
  cCameraAI_Transition *trans = (cCameraAI_Transition *)mCameraAIs[CAMERA_TRANSITION];
  trans->Init(prev_setting,next_setting);
}

void 
cCameraController::EndScriptCam()
{
  mScriptSetting = CAMERA_NONE;

  // reset max distance settings, so that they don't adversely affect subsiquent playback 
  for (int ii=0;ii<NUM_CAMERA_TYPES;++ii)
  {
    mCameraAIs[ii]->SetMaxDistance(300.0f); 
  }
}


bool 
cCameraController::HitTether(tGameID id, SyVect3 &start, SyVect3 &end,float radius)
{
  return mCameraAIs[mSetting]->HitTether(id,start,end,radius);
}

cCameraAI *
cCameraController::GetCameraAI(eCameraSetting setting)
{
  if (setting > CAMERA_NONE && setting < NUM_CAMERA_TYPES)
  {
    return mCameraAIs[setting];
  }
  return NULL;
}

void       
cCameraController::Cut(eCameraSetting setting)
{
  if(mSetting == setting)
  {
    return;
  }

  if (CAMERA_NONE == setting && CAMERA_NONE != mSetting)
  {
    mTitan->GetRegistry()->GetLevelObject()->DisableScriptCam();
  }

  mSetting = setting;

  // save settings to be transfered across network if another player joins
  if (!mTitan->GetRegistry()->GetLevelObject()->IsRemote() &&
      CAMERA_NONE != mSetting && CAMERA_NEAR != mSetting && CAMERA_FAR != mSetting && CAMERA_TWOPLAYER != mSetting)
  {
    tGameObjectID sourceID = ID_NONE;
    tGameObjectID targetID = ID_NONE;
    float sourceYOffset = 0.0f;
    float targetYOffset = 0.0f;
    float viewAngle = 45.0f;

    if (CAMERA_SCRIPT_LOOKAT1 == mSetting ||
        CAMERA_SCRIPT_LOOKAT2 == mSetting ||
        CAMERA_SCRIPT_LOOKAT3 == mSetting)
    {
      cCameraAI_ScriptLookAt *cam = (cCameraAI_ScriptLookAt*)(mCameraAIs[mSetting]);

      sourceID = cam->GetSource();
      targetID = cam->GetTarget();

      sourceYOffset = cam->GetSourceYOffset();
      targetYOffset = cam->GetTargetYOffset();
      viewAngle = cam->GetViewAngle();
    }

    mTitan->GetRegistry()->GetLevelObject()->EnableScriptCam(mSetting, sourceID, targetID, sourceYOffset, targetYOffset, viewAngle);
  }
  
  if (mSetting >= CAMERA_NEAR && mCameraAIs[mSetting])
  {
    mCameraAIs[mSetting]->Cut();
  }
}

SyVect3 
cCameraController::GetCameraLocation(eCameraSetting setting)
{
  return mCameraAIs[setting]->GetLoc();
}

float 
cCameraController::GetCameraHeading(eCameraSetting setting)
{
  float heading = mCameraAIs[setting]->GetHeading();
  heading = AngleNormalize(heading+SY_DEG_TO_RAD(180));
  return heading;

};

void  
cCameraController::SetCameraHeading(eCameraSetting setting,float heading)
{
  heading = AngleNormalize(heading+SY_DEG_TO_RAD(180));
  mCameraAIs[setting]->SetHeading(heading);
}

void  
cCameraController::SetMaxDistance(eCameraSetting setting,float max_distance)
{
  if (setting > CAMERA_NONE && setting < NUM_CAMERA_TYPES)
  {
    mCameraAIs[setting]->SetMaxDistance(max_distance);
  }
};

void  
cCameraController::EnableFreeCam()
{
  mCameraAIs[CAMERA_FREE]->Transition(mCameraAIs[mSetting]);
  mScriptSetting = CAMERA_FREE;
  mTitan->StartCutScene(0);
  mTitan->GetTitanUI()->EndLetterbox();
};

void  
cCameraController::DisableFreeCam()
{
  mScriptSetting = CAMERA_NONE;
  mTitan->StartCutScene(1);
};



// EOF
