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

  Module: TiCameraDirector.cpp

  Author: Bijan Forutanpour

  Description:   Game camera control interface.

  1) Use LoadCameraFile () to load in a .cam file. A .cam file is
     generated using the camExport Maya file exporter. 

  2) Given a 3d point (player, usually), return camera info.


  NOTES: Camera impelementations (work in progress)
     a) stationary camera
     b) stationary panning camera, following player
     c) stationary panning camera, lookat player  (and follow at
        fixed distance)
     d) Player-relative cam: fixed offset from player, fixed angle
     e) keyframed cameras.

  MORE NOTES:  The *transition* between these different types of
     cameras mid-game, mid-animation will be the next step of the
     implementation. UPDATE: Cutting is OK for hard cases.

  Copyright 2005 Sony Online Entertainment.  All rights reserved.

*******************************************************************/
#include "TiCameraDirector.h"
#include <SyObjFile.h>
#include <SyCurve.h>
#include <SyMath.h>

#define CAM_STATIC        0
#define CAM_LOOKAT        1
#define CAM_PLAYER        2
#define CAM_PLAYBACK      3


#define TI_CUBE_ID   111
#define TI_CAMERA_ID 222
#define TI_CURVE_ID  333

#define TI_MAX_PATH_SEGS 300

/***********************************************************************
 * TiCameraDirector - Used both by application (game) as 
 * well as artist camera simulation tool. The difference
 * in usage between the two sides is:
 * 
 * 1) Art side runs simulation first, then does playback of 
 *    the results at a constant frame rate (e.g. in Maya). 
 *    The simulation needs to index animation on a per-frame 
 *    basis.
 * 
 * 2) The game side runs in milliseconds, not frames. 
 *    So at render time the milliseconds are calculated first,
 *    then converted to frames for use in animation evaluation.
 *
 * The default is playback mode, ie, milliseconds.
 **********************************************************/
TiCameraDirector::TiCameraDirector ()
{
  mCamZones = NULL;
  mNumCamZones = 0;
  mLastZone = -1;
  mCurrentZone = -1;
  mTimeStep = 1;
  mUseDefaultCamera = 1;  // In case player isn't in any zone
  mFrameIndexing = false;
  mCurrentTime = 0;
}

/***********************************************************************
 * Destructor
 ***********************************************************************/
TiCameraDirector::~TiCameraDirector ()
{
  delete [] mCamZones;
}

/***********************************************************************
 * UpdateTime - deltaTime is a float, in seconds. We store milliseconds
 * internally.
 ***********************************************************************/
void TiCameraDirector::UpdateTime (float deltaTime)
{
  mCurrentTime = (SyTime)(deltaTime*1000) + mCurrentTime;
}

/***********************************************************************
 * LoadCameraFile - The .cam file being loaded comes from Maya (camExport). 
 * Zones and cameras.
 ***********************************************************************/
int32 TiCameraDirector::LoadCameraFile (const char8* camFileName)
{
  uint16 x, y;
  int32 i, id;
  char8 name [256];
  SyObjFile syFile;

  /*
   * Clear out any previous memory usage
   */
  if (mNumCamZones > 0)
  {
    delete [] mCamZones;
    mCamZones = NULL;
    mNumCamZones = 0;
    mLastZone = -1;
    mCurrentZone = -1;
    mUseDefaultCamera = 1;  // In case player isn't in any zone
  }


  /*
   * Open file for reading
   */
  int32 result = syFile.Open (camFileName, SyObjFile::ROnly);

  if (result<0)
  {
    SyAssertf(0,"Camera File %s not found",camFileName);
    return 0;
  }

  syFile.ReadBegin (x, y);

  /*
   * Read in number of cameras, allocate structures
   */
  syFile.Read (mNumCamZones);
  mCamZones = new TiCameraZone  [mNumCamZones];
  
  // read the number of camzones to load
  if (mNumCamZones > 0)
  {
    syFile.Read (id);
  }

  /*
   * More information about the camera orientation is
   * read in that is actually used. Leaving it in, in
   * case of future expansion and/or simplification.
   */
  for (i=0; i < mNumCamZones; i++)
  {
    /*
     * Make sure we're reading a Cube
     */
    if (id != TI_CUBE_ID)
    {
      SyAssertf(0,"Cube identifier not found");
      return 0;
    }

    /*
     * Read in a bounding box
     */
    syFile.ReadString (name, 256);

    syFile.Read (mCamZones[i].mZone.Min.X);
    syFile.Read (mCamZones[i].mZone.Max.X);
    syFile.Read (mCamZones[i].mZone.Min.Y);
    syFile.Read (mCamZones[i].mZone.Max.Y);
    syFile.Read (mCamZones[i].mZone.Min.Z);
    syFile.Read (mCamZones[i].mZone.Max.Z);

    /*
     * Make sure we're reading a Camera
     */
    syFile.Read (id);
    if (id != TI_CAMERA_ID)
    {
      SyAssertf(0,"Camera identifier not found");
      return 0;
    }
    /*
     * Read in a Camera
     */
    int len = -1;
    syFile.ReadString (name, 256);

    /*
     * Read translate/rotation animation
     */
    LoadCurve (&syFile, mCamZones[i].mCamera.GetTranslateX());
    LoadCurve (&syFile, mCamZones[i].mCamera.GetTranslateY());
    LoadCurve (&syFile, mCamZones[i].mCamera.GetTranslateZ());
    LoadCurve (&syFile, mCamZones[i].mCamera.GetRotateX());
    LoadCurve (&syFile, mCamZones[i].mCamera.GetRotateY());
    LoadCurve (&syFile, mCamZones[i].mCamera.GetRotateZ());

    // get anim start-end frames from data and set our
    // internal structures.
    mCamZones[i].mCamera.SetStartEndFrames();

    // position of camera
    SyVect3 pos;
    syFile.Read (pos.X);
    syFile.Read (pos.Y);
    syFile.Read (pos.Z);
    // interest point
    SyVect3 interest;
    syFile.Read (interest.X);
    syFile.Read (interest.Y);
    syFile.Read (interest.Z);
    
    // lookat vector
    SyVect3 at;
    syFile.Read (at.X);
    syFile.Read (at.Y);
    syFile.Read (at.Z);
    // up vector
    SyVect3 up;
    syFile.Read (up.X);
    syFile.Read (up.Y);
    syFile.Read (up.Z);
    // right vector
    SyVect3 right;
    syFile.Read (right.X);
    syFile.Read (right.Y);
    syFile.Read (right.Z);
    // aspect, near, far, horiz fov, vert fov,, focal length
    float32 aspect, nearClip, farClip, horizFov, vertFov, focalLength;
    syFile.Read (aspect);
    syFile.Read (nearClip);
    syFile.Read (farClip);
    syFile.Read (horizFov);
    syFile.Read (vertFov);
    syFile.Read (focalLength);

    mCamZones[i].mCamera.SetNear (nearClip);
    mCamZones[i].mCamera.SetFar (farClip);
    mCamZones[i].mCamera.SetAspect (aspect);
    mCamZones[i].mCamera.SetVertFOV (SY_RAD_TO_DEG (vertFov));
    mCamZones[i].mCamera.SetHorizFOV (SY_RAD_TO_DEG (horizFov));

    mCamZones[i].mCamera.mInterest = interest;

    /*
     * Custom attributes
     */
    syFile.Read (mCamZones[i].mCamera.mCamType);
    syFile.Read (mCamZones[i].mCamera.mCamTransition);

    /*
     * camera offset info - camera position and intrerest point
     */
    LoadCurve (&syFile, &(mCamZones[i].mCamera.mPlayerOffsetX));
    LoadCurve (&syFile, &(mCamZones[i].mCamera.mPlayerOffsetY));
    LoadCurve (&syFile, &(mCamZones[i].mCamera.mPlayerOffsetZ));
    LoadCurve (&syFile, &(mCamZones[i].mCamera.mPlayerOffsetInterestX));
    LoadCurve (&syFile, &(mCamZones[i].mCamera.mPlayerOffsetInterestY));
    LoadCurve (&syFile, &(mCamZones[i].mCamera.mPlayerOffsetInterestZ));

    /*
     * Min & Max distance, for the lookat cam. 
     */
    syFile.Read (mCamZones[i].mCamera.mUseMinMax);
    syFile.Read (mCamZones[i].mCamera.mMinDist);
    syFile.Read (mCamZones[i].mCamera.mMaxDist);
    syFile.Read (mCamZones[i].mCamera.mUseRail);
    syFile.Read (mCamZones[i].mCamera.mPlayOnce);
    syFile.Read (mCamZones[i].mCamera.mRangeMarker);

    /*
     * Test to see if we should read in a curve
     */
    syFile.Read (id);
    if (id == TI_CURVE_ID)
    {
      /*
       * Read in curve
       */
      syFile.ReadString (name, 256);
      /*
       * Read in number of verts
       */
        float32 fval;
        int32 numVerts;
        syFile.Read (numVerts);
        for (int32 vIndex=0; vIndex < numVerts; vIndex++)
        {
          syFile.Read (fval);
          mCamZones[i].mPathX.AddKey ((float32)vIndex, fval);
          syFile.Read (fval);
          mCamZones[i].mPathY.AddKey ((float32)vIndex, fval);
          syFile.Read (fval);
          mCamZones[i].mPathZ.AddKey ((float32)vIndex, fval);
        }

      /*
       * Read the next id in, if we have one
       */
      if (i < mNumCamZones - 1)
      {
        syFile.Read (id);
      }
    }
  }

  syFile.ReadEnd();

  return 1;
}

/***********************************************************************
 * XYZtoHPR - Go from Maya (Euler) rotations to wacky Sony rotations. - NOT USED
 ***********************************************************************/
void TiCameraDirector::XYZtoHPR (SyVect3 euler, SyVect3* hpr)
{
  float32 sx,sy,sz,cx,cy,cz;
  float32 r1z, r2x, r2y, r2z, r3z;

  sx = sinf(euler.X);     
  cx = cosf(euler.X);
  sy = sinf(euler.Y);     
  cy = cosf(euler.Y);
  sz = sinf(euler.Z);     
  cz = cosf(euler.Z);

  r1z = -sy;

  r2x = sx*sy*cz-cx*sz;
  r2y = sx*sy*sz+cx*cz;
  r2z = sx*cy;

  r3z = cx*cy;

  /*
   * Get YXZ rotation out
   */
   hpr->Y = (float32)asinf(r2z); 
   hpr->X = -1.f * (float32)atan2f(r1z, r3z);  
   hpr->Z = -1.f * (float32)atan2f(r2x, r2y);  
}


/****************************************************************************
 * getCamInfo - given a 3d point (player pos), return camera postion and 
 * Camera direction vector.
 * RETURNS:
 *     1 - if data is valid, i.e., playerPosition is within TiCameraDirector territory
 *     0 - if data is NOT valid.  Game Camera should be used.
 ****************************************************************************/
int32 TiCameraDirector::GetCamInfo (SyVect3 playerPosition, SyVect3* camPos, SyVect3* camDir, float32* camFov)
{
  SyVect3 rot, cam2player;
  SyMatrix44 rotMatrixX, rotMatrixY, rotMatrixZ, rotMatrixXYZ, rotMatrixYZ;
  SyVect4 unitCamDirection (0.f, 0.f, -1.f, 1.f); // Maya camera direction, down negative Z
  SyVect4 finalCamDirection;

  int32 orientType, haveCamera;
  
  haveCamera = GetCamInfoInternal (playerPosition, camPos, &rot, &orientType, camFov);
  if (haveCamera == NULL)
  {
    return NULL;
  }


  /*
   * Do the right orientation conversion. If orientType == 0,
   * rot is the camera's interest point.
   * If orientType == 1 then rot is XYZ euler rotation.
   */
  if (orientType == 0)
  {
    *camDir = rot - (*camPos);
    camDir->Normalize();
  }
  else  // (orientType == 1)
  {
    /*
     * rot is XYZ euler rotation in degrees,
     * convert to radians.
     */
    rot.X = (SY_DEG_TO_RAD (rot.X));
    rot.Y = (SY_DEG_TO_RAD (rot.Y));
    rot.Z = (SY_DEG_TO_RAD (rot.Z));

    /*
     * build rotation matrix (reverse order)
     */
    rotMatrixX.RotateX (rot.X);
    rotMatrixY.RotateY (rot.Y);
    rotMatrixZ.RotateZ (rot.Z);

    rotMatrixYZ.Mul (rotMatrixY, rotMatrixZ);
    rotMatrixXYZ.Mul (rotMatrixX, rotMatrixYZ);

    /*
     * maya camera is at zero, looking at 0, 0, -1.  
     * Transform that vector by the camera rotation to get
     * camera direction vector.
     */
    rotMatrixXYZ.Mul (unitCamDirection, finalCamDirection);
    camDir->X = finalCamDirection.X;
    camDir->Y = finalCamDirection.Y;
    camDir->Z = finalCamDirection.Z;
  }
  return 1;
}


/****************************************************************************
 * getCamInfoInternal - given a 3d point (player pos), return camera postion and 
 * camera orientation.
 * 
 * RETURN: 
 * if orientType == 0  camCoi is the camera interest point position
 * if oreintType == 1  camCoi is the camera XYZ rotation (degrees)
 * The orientation is done this way because of interaction from/to Maya tools.
 * A HPR yaw/pitch/roll version will be made available for the engine side.
 *
 * NOTES: 
 *  It is possible for regular camSpace bounding box zones to have gaps between them,
 * and so a player location can be outside any camSpace.  In this case, usually the
 * camera from the last known camSpace is used.  The problem is that the A.I.
 * game camera will never be used.  SO, we use a fake camSpace marked as "RangeMarker" which
 * engulfs all other camSpaces, (along with their gaps) so that we know
 * when to provide a camera vs let the game cam take over.
 ******************************************************************************/
int32 TiCameraDirector::GetCamInfoInternal (SyVect3 playerPosition, SyVect3* camPos, SyVect3* camCoi, int *orientType, float32* camFov)
{
  int32 isInside = 0;
  int32 useRail;
  int32 camType;
  SyVect3 tiCameraDir, camRot, p1, p2;
  SyVect3 tiCameraOffsetPos, tiCameraOffsetInterest;
  float32 cameraToPlayerDistance;
  float32 animFrame, miniRailTime, startFrame, endFrame;
  SyMatrix44 rotMat;
  SyVect3 playerOffsetPos;
  SyVect3 P1, P2;
  int32 zoneIndex;
  int32 isInRange = 0;
  // set default values
  SyVect3 tiCameraPos (-11.13f, 2.62f, -0.06f);
  SyVect3 tiCoiPoint (0.f, .914f, 0.f);

  *orientType = 0;

  /*
   * FIRST PASS - Go through the Zones, see if the player is inside a "special" zone
   * that is a "rangeMarker" zone. What it means is, TiCameraDirector should return a
   * valid result. Otherwise, return 0, and tell the game to use its own camera.
   */
  for (zoneIndex=0; zoneIndex < mNumCamZones; zoneIndex++)
  {
    isInside = mCamZones[zoneIndex].mZone.Contains (playerPosition);
    if (isInside && mCamZones[zoneIndex].mCamera.mRangeMarker == 1)
    {
      isInRange = 1;
      break;
    }
  }

  /*
   * TiCameraDirector does not need to process this request. It is not
   * within a "valid" range bounding box.
   */
  if (isInRange == 0)
  {
    return 0;
  }

  /*
   * Go through the Zones, see if the player is inside it.
   */
  for (zoneIndex=0; zoneIndex < mNumCamZones; zoneIndex++)
  {
    isInside = (mCamZones[zoneIndex].mZone.Contains (playerPosition)  && mCamZones[zoneIndex].mCamera.mRangeMarker == 0);
    // are we inside the bounding box?
    if (isInside )
    {
      mCurrentZone = zoneIndex;
      /*
       * If we switchd zones, reset clocks, mark animations
       */
      if (mCurrentZone != mLastZone)
      {
        mCamZones[zoneIndex].mCamera.mZoneClock = 0; // frames
        mCamZones[zoneIndex].mCamera.SetStartTime ( mCurrentTime ); //milliseconds

        if (mLastZone >= 0) // tag animation so we don't replay (if mPlayOnce)
        {
          mCamZones[mLastZone].mCamera.mAnimPlayed = 1;
        }
      }
      else
      {
        /*
         * Still in the same ole zone, just tick the clock.
         */
        mCamZones[zoneIndex].mCamera.mZoneClock += mTimeStep;
      }

      mLastZone = mCurrentZone;
      mUseDefaultCamera = 0;
      break;
    }
  }

  /*
   * If we're in no-mans-land, the rules from the last known
   * zone apply. UNLESS the mUseDefaultCamera flag is set. This
   * is the case when there IS no last-known-zone, at the very
   * beginning of the client application.
   */
  if (!isInside)
  {
    if (mUseDefaultCamera == 0)
    {
      zoneIndex = mLastZone;
    }
    else
    {
      // use default camera
      *camPos = (playerPosition + tiCameraPos);
      *camCoi = (playerPosition + tiCoiPoint);
      *camFov = 45.f;
      return 1; // means: use our camera info, its valid.
    }
  }

  /*
   * If player is inside a region, apply the camera rules.
   */
  if (zoneIndex >= 0)
  {

    camType = mCamZones[zoneIndex].mCamera.mCamType;
    /*
     * Determine camera position & interest based on camera type
     */
    switch (camType)
    {
    case CAM_STATIC:
      mCamZones[zoneIndex].mCamera.GetTranslation (0, &tiCameraPos); // location at frame 0
      tiCoiPoint  = mCamZones[zoneIndex].mCamera.mInterest; // center of interest
      *camFov = mCamZones[zoneIndex].mCamera.GetVertFOV(0);
      break;

    case CAM_LOOKAT:
      *camFov = mCamZones[zoneIndex].mCamera.GetVertFOV(0); 
      mCamZones[zoneIndex].mCamera.GetTranslation (0, &tiCameraPos); // location at frame 0
      tiCoiPoint  = playerPosition;
      /*
       * Do we have to keep player within a min-max distance?
       */
      if (mCamZones[zoneIndex].mCamera.mUseMinMax)
      {
        tiCameraDir = tiCameraPos - playerPosition;
        cameraToPlayerDistance = tiCameraDir.Magnitude();
        if (cameraToPlayerDistance > mCamZones[zoneIndex].mCamera.mMaxDist)
        {
          tiCameraDir.Normalize();
          tiCameraDir *=  mCamZones[zoneIndex].mCamera.mMaxDist;
          tiCameraPos = playerPosition + tiCameraDir;
        }
        if (cameraToPlayerDistance < mCamZones[zoneIndex].mCamera.mMinDist)
        {
          tiCameraDir.Normalize();
          tiCameraDir *= (mCamZones[zoneIndex].mCamera.mMinDist - cameraToPlayerDistance);
          tiCameraPos = tiCameraPos - tiCameraDir;
        }
      }
      break;

    case CAM_PLAYER:

      /*
       * Are we using the mini-rail?
       */
      if (mCamZones[zoneIndex].mCamera.mUseRail && mCamZones[zoneIndex].mPathX.GetNumKeys() >= 2)
       {
        useRail = 1;
       }
      else
       {
        useRail = 0;
       }

      if (useRail)
       {
        /*
         * Use multi-point rail to figure out animation frame index
         */
        miniRailTime = getPointOnCurveU (playerPosition, mCamZones[zoneIndex].mPathX, mCamZones[zoneIndex].mPathY, mCamZones[zoneIndex].mPathZ);
        startFrame = mCamZones[zoneIndex].mCamera.GetStartFrame();
        endFrame = mCamZones[zoneIndex].mCamera.GetEndFrame();
        animFrame = startFrame + (miniRailTime * (endFrame - startFrame));
       }
      else
       {
        /*
         * If the animation is a "Play Once" animation, then just use last frame.
         */
        if (mCamZones[zoneIndex].mCamera.mPlayOnce && mCamZones[zoneIndex].mCamera.mAnimPlayed)
         {
          animFrame = mCamZones[zoneIndex].mCamera.GetEndFrame();
         }
        else
         {
          /*
           * Use zone clock as playback frame index
           */
          animFrame = mCamZones[zoneIndex].mCamera.mZoneClock;
         }
       }

      /*
       * Use frames or milliseconds? If we're indexing the animation off a mini-rail,
       * or (typically) running a simulation in Maya, we are frame based.
       */
      if (mFrameIndexing  || useRail || mCamZones[zoneIndex].mCamera.mAnimPlayed)
      {
        /*
         * Use frames
         */
        mCamZones[zoneIndex].mCamera.GetOffsetPos (animFrame, &tiCameraOffsetPos);
        mCamZones[zoneIndex].mCamera.GetOffsetInterest (animFrame, &tiCameraOffsetInterest);
        *camFov = mCamZones[zoneIndex].mCamera.GetVertFOV(animFrame);
      }
      else
      {
        /*
         * Use Milliseconds - typically for straight time based animation (non-rail),
         * in the game.  Maya uses frame based animation indexing for simulation baking
         * purposes.
         */
        mCamZones[zoneIndex].mCamera.GetOffsetPos (mCurrentTime, &tiCameraOffsetPos);
        mCamZones[zoneIndex].mCamera.GetOffsetInterest (mCurrentTime, &tiCameraOffsetInterest);
        *camFov = mCamZones[zoneIndex].mCamera.GetVertFOV(0);
      }
      
      tiCameraPos = playerPosition + tiCameraOffsetPos;
      tiCoiPoint = playerPosition + tiCameraOffsetInterest;
      break;

    case CAM_PLAYBACK:
     /*
      * Use world space camera
      */
      /*
       * Are we using the palyer curve rail to index into camera animation?
       */
      if (mCamZones[zoneIndex].mCamera.mUseRail && mCamZones[zoneIndex].mPathX.GetNumKeys() >= 2)
       {
        useRail = 1;
       }
      else
       {
        useRail = 0;
       }

      if (useRail)
      {
        /*
         * Use multi-point rail to figure out animation frame index
         */
        miniRailTime = getPointOnCurveU (playerPosition, mCamZones[zoneIndex].mPathX, mCamZones[zoneIndex].mPathY, mCamZones[zoneIndex].mPathZ);
        startFrame = mCamZones[zoneIndex].mCamera.GetStartFrame();
        endFrame = mCamZones[zoneIndex].mCamera.GetEndFrame();
        animFrame = startFrame + (miniRailTime * (endFrame - startFrame));
      }
      else
      {
        animFrame = mCamZones[zoneIndex].mCamera.mZoneClock;
      }

      mCamZones[zoneIndex].mCamera.GetTranslation (animFrame, &tiCameraPos);
      mCamZones[zoneIndex].mCamera.GetRotation (animFrame, &tiCoiPoint);
      *camFov = mCamZones[zoneIndex].mCamera.GetVertFOV(0);
      *orientType = 1;
      break;
     
    default:
     //use tiCameraPos and toCoiPoint, set above
     break;
    };
    
    
    *camPos = tiCameraPos;
    *camCoi = tiCoiPoint;
  }

  return 1;
}


/***********************************************************
 * loadCurve - used for translations & rotations. The format
 * of the curve data really comes from the SOEGetSampledAnimCurve
 * MEL script. The CamTranslator::WriteTransform() function
 * in CamExport.cpp blindly writes out what it gets from MEL.
 ***********************************************************/
void TiCameraDirector::LoadCurve (SyObjFile* syFile,  SyCurve *theCurve)
{
  float32 keyTime, keyVal, numKeys, k;
  theCurve->Init (SyCurve::CURVE_LINEAR);
  /*
   * transform animation
   */
    syFile->Read (numKeys);
    for (k = 0; k < numKeys; k++)
    {
      syFile->Read (keyTime);
      syFile->Read (keyVal);
      theCurve->AddKey (keyTime, keyVal);
    }
}
    

/***********************************************************
 * SetTimeStep
 ***********************************************************/
void  TiCameraDirector::SetTimeStep (float timeStep)
{
  mTimeStep = timeStep;
}

/**********************************************************
 * SetSimulation
 **********************************************************/
void TiCameraDirector::SetFrameIndexing (bool doFrames)
{
  mFrameIndexing = doFrames;
}

/***********************************************************
 * DistancePointLine - given a point P in 3d space and a line
 * defined by P1 and P2, find the point P3 on the line closest 
 * to the point P.   
 * Return: the position of P3 as a float between 0->1 specifying
 * it position on the line. If P3 == 0, then P3 = P1. 
 * If P3 == 1, then P3 = P2.
 * The value return is used to figure out which frame along
 * The animating camera (start/end) timeline should be used to 
 * index the camera's animation.
 ***********************************************************/
float32 TiCameraDirector::DistancePointLine (SyVect3 Point, SyVect3 P1, SyVect3 P2, float32* dist)
{
  float32 U, length;
  SyVect3 line;
  SyVect3 result;

  *dist = -1.f;

  float32 distSquared = P1.DistanceSquared (P2);
  length = SY_SQRT (distSquared);
 
  U = ( ( ( Point.X - P1.X ) * ( P2.X - P1.X ) ) +
        ( ( Point.Y - P1.Y ) * ( P2.Y - P1.Y ) ) +
        ( ( Point.Z - P1.Z ) * ( P2.Z - P1.Z ) ) ) /
    distSquared;

    if( U < 0.0f)
    {
      U = 0.f;
    }

    if (U > 1.f)
    {
      U = 1.f;
    }
 
    /*
     * Location of point on line. We don't care about this.
     * Code is here, just in case.
     */
    result.X = P1.X + U * (P2.X - P1.X );
    result.Y = P1.Y + U * (P2.Y - P1.Y );
    result.Z = P1.Z + U * (P2.Z - P1.Z );

    *dist = Point.Distance (result);

    return U;
}



/**********************************************************
 * getPointOnCurveU 
 * Given a 3D point and a 3D curve, find the U value of the 
 * point on the curve
 **********************************************************/
float32 TiCameraDirector::getPointOnCurveU (SyVect3 playerPosition, SyCurve& curveX, SyCurve& curveY, SyCurve& curveZ)
{
  float32 totalCurveLength, subtotalCurveLength, U, closestPointDistance;
  int32 numVerts, closestFound, numSegments, i, targetSegment; 
  SyVect3 P1, P2;

  float32 segmentLength[TI_MAX_PATH_SEGS];
  float32 pointSegmentDist[TI_MAX_PATH_SEGS];
  float32 segmentClosestU[TI_MAX_PATH_SEGS];

  closestFound = 0;
  totalCurveLength = 0.f;
  numVerts = curveX.GetNumKeys();
  numSegments = numVerts - 1;


  /*
   * First pass - collect information about point to each curve segment.
   */
  for (i=0; i < numSegments; i++)
  {
    P1.X = curveX.GetKeyValue(i);
    P1.Y = curveY.GetKeyValue(i);
    P1.Z = curveZ.GetKeyValue(i);

    P2.X = curveX.GetKeyValue(i+1);
    P2.Y = curveY.GetKeyValue(i+1);
    P2.Z = curveZ.GetKeyValue(i+1);
    
    segmentLength[i] = P1.Distance (P2); // get the curve segement length
    totalCurveLength += segmentLength[i];

    /*
     * Get the distance from the point to the line segment. 
     * Get the U value of the segment to the point.
     */
    segmentClosestU[i] = DistancePointLine (playerPosition, P1, P2, &(pointSegmentDist[i]));
  }

  /*
   * Second pass  - find the winning segment.
   */
  closestPointDistance = -1.f;
  targetSegment = -1;
  for (i=0; i < numSegments; i++)
  {
    if (pointSegmentDist[i] < closestPointDistance || closestPointDistance == -1)
    {
      targetSegment = i;
      closestPointDistance = pointSegmentDist[i];
    }
  }

  /*
   * Finally, figure out the U value.  Get segement distance subtotal, from 0 to target, divide by total curve length.
   */
  subtotalCurveLength = 0;
  for (i=0; i < targetSegment; i++)
  {
    subtotalCurveLength += segmentLength[i];
  }
  if (targetSegment != -1)
  {
    subtotalCurveLength += segmentLength[targetSegment] * segmentClosestU[targetSegment];
  }

  U = subtotalCurveLength / totalCurveLength;
  return U;
}
