/**
 * @file llfollowcam.cpp
 * @author Jeffrey Ventrella
 * @brief LLFollowCam class implementation
 *
 * $LicenseInfo:firstyear=2005&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#include "llviewerprecompiledheaders.h"
#include "llfollowcam.h"
#include "llagent.h"

//-------------------------------------------------------
// constants
//-------------------------------------------------------
const F32 FOLLOW_CAM_ZOOM_FACTOR            = 0.1f;
const F32 FOLLOW_CAM_MIN_ZOOM_AMOUNT        = 0.1f;
const F32 DISTANCE_EPSILON                  = 0.0001f;
const F32 DEFAULT_MAX_DISTANCE_FROM_SUBJECT = 1000.0;   // this will be correctly set on me by my caller

//----------------------------------------------------------------------------------------
// This is how slowly the camera position moves to its ideal position
//----------------------------------------------------------------------------------------
const F32 FOLLOW_CAM_MIN_POSITION_LAG       = 0.0f;
const F32 FOLLOW_CAM_DEFAULT_POSITION_LAG   = 0.1f;
const F32 FOLLOW_CAM_MAX_POSITION_LAG       = 3.0f;

//----------------------------------------------------------------------------------------
// This is how slowly the camera focus moves to its subject
//----------------------------------------------------------------------------------------
const F32 FOLLOW_CAM_MIN_FOCUS_LAG      = 0.0f;
const F32 FOLLOW_CAM_DEFAULT_FOCUS_LAG  = 0.1f;
const F32 FOLLOW_CAM_MAX_FOCUS_LAG      = 3.0f;

//----------------------------------------------------------------------------------------
// This is far the position can get from its IDEAL POSITION before it starts getting pulled
//----------------------------------------------------------------------------------------
const F32 FOLLOW_CAM_MIN_POSITION_THRESHOLD     = 0.0f;
const F32 FOLLOW_CAM_DEFAULT_POSITION_THRESHOLD = 1.0f;
const F32 FOLLOW_CAM_MAX_POSITION_THRESHOLD     = 4.0f;

//----------------------------------------------------------------------------------------
// This is far the focus can get from the subject before it starts getting pulled
//----------------------------------------------------------------------------------------
const F32 FOLLOW_CAM_MIN_FOCUS_THRESHOLD        = 0.0f;
const F32 FOLLOW_CAM_DEFAULT_FOCUS_THRESHOLD    = 1.0f;
const F32 FOLLOW_CAM_MAX_FOCUS_THRESHOLD        = 4.0f;

//----------------------------------------------------------------------------------------
// This is the distance the camera wants to be from the subject
//----------------------------------------------------------------------------------------
const F32 FOLLOW_CAM_MIN_DISTANCE       = 0.5f;
const F32 FOLLOW_CAM_DEFAULT_DISTANCE   = 3.0f;
//const F32 FOLLOW_CAM_MAX_DISTANCE     = 10.0f; // from now on I am using mMaxCameraDistantFromSubject

//----------------------------------------------------------------------------------------
// this is an angluar value
// It affects the angle that the camera rises (pitches) in relation
// to the horizontal plane
//----------------------------------------------------------------------------------------
const F32 FOLLOW_CAM_MIN_PITCH      = -45.0f;
const F32 FOLLOW_CAM_DEFAULT_PITCH  =   0.0f;
const F32 FOLLOW_CAM_MAX_PITCH      =  80.0f;   // keep under 90 degrees - avoid gimble lock!


//----------------------------------------------------------------------------------------
// how high or low the camera considers its ideal focus to be (relative to its subject)
//----------------------------------------------------------------------------------------
const F32 FOLLOW_CAM_MIN_FOCUS_OFFSET       = -10.0f;
const LLVector3 FOLLOW_CAM_DEFAULT_FOCUS_OFFSET =  LLVector3(1.0f, 0.f, 0.f);
const F32 FOLLOW_CAM_MAX_FOCUS_OFFSET       =  10.0f;

//----------------------------------------------------------------------------------------
// This affects the rate at which the camera adjusts to stay behind the subject
//----------------------------------------------------------------------------------------
const F32 FOLLOW_CAM_MIN_BEHINDNESS_LAG     = 0.0f;
const F32 FOLLOW_CAM_DEFAULT_BEHINDNESS_LAG     = 0.f;
const F32 FOLLOW_CAM_MAX_BEHINDNESS_LAG     = 3.0f;

//---------------------------------------------------------------------------------------------------------------------
// in  degrees: This is the size of the pie slice behind the subject matter within which the camera is free to move
//---------------------------------------------------------------------------------------------------------------------
const F32 FOLLOW_CAM_MIN_BEHINDNESS_ANGLE       = 0.0f;
const F32 FOLLOW_CAM_DEFAULT_BEHINDNESS_ANGLE   = 10.0f;
const F32 FOLLOW_CAM_MAX_BEHINDNESS_ANGLE       = 180.0f;
const F32 FOLLOW_CAM_BEHINDNESS_EPSILON         = 1.0f;

//------------------------------------
// Constructor
//------------------------------------
LLFollowCamParams::LLFollowCamParams()
{
    mMaxCameraDistantFromSubject        = DEFAULT_MAX_DISTANCE_FROM_SUBJECT;
    mPositionLocked                     = false;
    mFocusLocked                        = false;
    mUsePosition                        = false;
    mUseFocus                           = false;

    //------------------------------------------------------
    // setting the attributes to their defaults
    //------------------------------------------------------
    setPositionLag      ( FOLLOW_CAM_DEFAULT_POSITION_LAG           );
    setFocusLag         ( FOLLOW_CAM_DEFAULT_FOCUS_LAG              );
    setPositionThreshold( FOLLOW_CAM_DEFAULT_POSITION_THRESHOLD     );
    setFocusThreshold   ( FOLLOW_CAM_DEFAULT_FOCUS_THRESHOLD        );
    setBehindnessLag    ( FOLLOW_CAM_DEFAULT_BEHINDNESS_LAG     );
    setDistance         ( FOLLOW_CAM_DEFAULT_DISTANCE               );
    setPitch            ( FOLLOW_CAM_DEFAULT_PITCH                  );
    setFocusOffset      ( FOLLOW_CAM_DEFAULT_FOCUS_OFFSET   );
    setBehindnessAngle  ( FOLLOW_CAM_DEFAULT_BEHINDNESS_ANGLE       );
    setPositionThreshold( FOLLOW_CAM_DEFAULT_POSITION_THRESHOLD     );
    setFocusThreshold   ( FOLLOW_CAM_DEFAULT_FOCUS_THRESHOLD        );

}

LLFollowCamParams::~LLFollowCamParams() { }

//---------------------------------------------------------
// buncho set methods
//---------------------------------------------------------

//---------------------------------------------------------
void LLFollowCamParams::setPositionLag( F32 p )
{
    mPositionLag = llclamp(p, FOLLOW_CAM_MIN_POSITION_LAG, FOLLOW_CAM_MAX_POSITION_LAG);
}


//---------------------------------------------------------
void LLFollowCamParams::setFocusLag( F32 f )
{
    mFocusLag = llclamp(f, FOLLOW_CAM_MIN_FOCUS_LAG, FOLLOW_CAM_MAX_FOCUS_LAG);
}


//---------------------------------------------------------
void LLFollowCamParams::setPositionThreshold( F32 p )
{
    mPositionThreshold = llclamp(p, FOLLOW_CAM_MIN_POSITION_THRESHOLD, FOLLOW_CAM_MAX_POSITION_THRESHOLD);
}


//---------------------------------------------------------
void LLFollowCamParams::setFocusThreshold( F32 f )
{
    mFocusThreshold = llclamp(f, FOLLOW_CAM_MIN_FOCUS_THRESHOLD, FOLLOW_CAM_MAX_FOCUS_THRESHOLD);
}


//---------------------------------------------------------
void LLFollowCamParams::setPitch( F32 p )
{
    mPitch = llclamp(p, FOLLOW_CAM_MIN_PITCH, FOLLOW_CAM_MAX_PITCH);
}


//---------------------------------------------------------
void LLFollowCamParams::setBehindnessLag( F32 b )
{
    mBehindnessLag = llclamp(b, FOLLOW_CAM_MIN_BEHINDNESS_LAG, FOLLOW_CAM_MAX_BEHINDNESS_LAG);
}

//---------------------------------------------------------
void LLFollowCamParams::setBehindnessAngle( F32 b )
{
    mBehindnessMaxAngle = llclamp(b, FOLLOW_CAM_MIN_BEHINDNESS_ANGLE, FOLLOW_CAM_MAX_BEHINDNESS_ANGLE);
}

//---------------------------------------------------------
void LLFollowCamParams::setDistance( F32 d )
{
    mDistance = llclamp(d, FOLLOW_CAM_MIN_DISTANCE, mMaxCameraDistantFromSubject);
}

//---------------------------------------------------------
void LLFollowCamParams::setPositionLocked( bool l )
{
    mPositionLocked = l;
}

//---------------------------------------------------------
void LLFollowCamParams::setFocusLocked( bool l )
{
    mFocusLocked = l;

}

//---------------------------------------------------------
void LLFollowCamParams::setFocusOffset( const LLVector3& v )
{
    mFocusOffset = v;
    mFocusOffset.clamp(FOLLOW_CAM_MIN_FOCUS_OFFSET, FOLLOW_CAM_MAX_FOCUS_OFFSET);
}

//---------------------------------------------------------
void LLFollowCamParams::setPosition( const LLVector3& p )
{
    mUsePosition = true;
    mPosition = p;
}

//---------------------------------------------------------
void LLFollowCamParams::setFocus( const LLVector3& f )
{
    mUseFocus = true;
    mFocus = f;
}

//---------------------------------------------------------
// buncho get methods
//---------------------------------------------------------
F32         LLFollowCamParams::getPositionLag       () const { return mPositionLag;         }
F32         LLFollowCamParams::getFocusLag          () const { return mFocusLag;            }
F32         LLFollowCamParams::getPositionThreshold () const { return mPositionThreshold;   }
F32         LLFollowCamParams::getFocusThreshold    () const { return mFocusThreshold;      }
F32         LLFollowCamParams::getDistance          () const { return mDistance;            }
F32         LLFollowCamParams::getPitch             () const { return mPitch;               }
LLVector3   LLFollowCamParams::getFocusOffset       () const { return mFocusOffset;         }
F32         LLFollowCamParams::getBehindnessAngle   () const { return mBehindnessMaxAngle;  }
F32         LLFollowCamParams::getBehindnessLag     () const { return mBehindnessLag;       }
LLVector3   LLFollowCamParams::getPosition          () const { return mPosition;            }
LLVector3   LLFollowCamParams::getFocus             () const { return mFocus;               }
bool        LLFollowCamParams::getPositionLocked    () const { return mPositionLocked;      }
bool        LLFollowCamParams::getFocusLocked       () const { return mFocusLocked;         }

//------------------------------------
// Constructor
//------------------------------------
LLFollowCam::LLFollowCam() : LLFollowCamParams()
{
    mUpVector                           = LLVector3::z_axis;
    mSubjectPosition                    = LLVector3::zero;
    mSubjectRotation                    = LLQuaternion::DEFAULT;

    mZoomedToMinimumDistance            = false;
    mPitchCos = mPitchSin = 0.f;
    mPitchSineAndCosineNeedToBeUpdated  = true;

    mSimulatedDistance = mDistance;
}

void LLFollowCam::copyParams(LLFollowCamParams& params)
{
    setPositionLag(params.getPositionLag());
    setFocusLag(params.getFocusLag());
    setFocusThreshold( params.getFocusThreshold());
    setPositionThreshold(params.getPositionThreshold());
    setPitch(params.getPitch());
    setFocusOffset(params.getFocusOffset());
    setBehindnessAngle(params.getBehindnessAngle());
    setBehindnessLag(params.getBehindnessLag());

    setPositionLocked(params.getPositionLocked());
    setFocusLocked(params.getFocusLocked());

    setDistance(params.getDistance());
    if (params.getUsePosition())
    {
        setPosition(params.getPosition());
    }
    if (params.getUseFocus())
    {
        setFocus(params.getFocus());
    }
}

//---------------------------------------------------------------------------------------------------------
void LLFollowCam::update()
{
    //####################################################################################
    // update Focus
    //####################################################################################
    LLVector3 offsetSubjectPosition = mSubjectPosition + (mFocusOffset * mSubjectRotation);

    LLVector3 simulated_pos_agent = gAgent.getPosAgentFromGlobal(mSimulatedPositionGlobal);
    LLVector3 vectorFromCameraToSubject = offsetSubjectPosition - simulated_pos_agent;
    F32 distanceFromCameraToSubject = vectorFromCameraToSubject.magVec();

    LLVector3 whereFocusWantsToBe = mFocus;
    LLVector3 focus_pt_agent = gAgent.getPosAgentFromGlobal(mSimulatedFocusGlobal);
    if ( mFocusLocked ) // if focus is locked, only relative focus needs to be updated
    {
        mRelativeFocus = (focus_pt_agent - mSubjectPosition) * ~mSubjectRotation;
    }
    else
    {
        LLVector3 focusOffset = offsetSubjectPosition - focus_pt_agent;
        F32 focusOffsetDistance = focusOffset.magVec();

        LLVector3 focusOffsetDirection = focusOffset / focusOffsetDistance;
        whereFocusWantsToBe = focus_pt_agent +
            (focusOffsetDirection * (focusOffsetDistance - mFocusThreshold));
        if ( focusOffsetDistance > mFocusThreshold )
        {
            // this version normalizes focus threshold by distance
            // so that the effect is not changed with distance
            /*
            F32 focusThresholdNormalizedByDistance = distanceFromCameraToSubject * mFocusThreshold;
            if ( focusOffsetDistance > focusThresholdNormalizedByDistance )
            {
                LLVector3 focusOffsetDirection = focusOffset / focusOffsetDistance;
                F32 force = focusOffsetDistance - focusThresholdNormalizedByDistance;
            */

            F32 focusLagLerp = LLSmoothInterpolation::getInterpolant( mFocusLag );
            focus_pt_agent = lerp( focus_pt_agent, whereFocusWantsToBe, focusLagLerp );
            mSimulatedFocusGlobal = gAgent.getPosGlobalFromAgent(focus_pt_agent);
        }
        mRelativeFocus = lerp(mRelativeFocus, (focus_pt_agent - mSubjectPosition) * ~mSubjectRotation, LLSmoothInterpolation::getInterpolant(0.05f));
    }// if focus is not locked ---------------------------------------------


    LLVector3 whereCameraPositionWantsToBe = gAgent.getPosAgentFromGlobal(mSimulatedPositionGlobal);
    if (  mPositionLocked )
    {
        mRelativePos = (whereCameraPositionWantsToBe - mSubjectPosition) * ~mSubjectRotation;
    }
    else
    {
        //####################################################################################
        // update Position
        //####################################################################################
        //-------------------------------------------------------------------------
        // I determine the horizontal vector from the camera to the subject
        //-------------------------------------------------------------------------
        LLVector3 horizontalVectorFromCameraToSubject = vectorFromCameraToSubject;
        horizontalVectorFromCameraToSubject.mV[VZ] = 0.0f;

        //---------------------------------------------------------
        // Now I determine the horizontal distance
        //---------------------------------------------------------
        F32 horizontalDistanceFromCameraToSubject = horizontalVectorFromCameraToSubject.magVec();

        //---------------------------------------------------------
        // Then I get the (normalized) horizontal direction...
        //---------------------------------------------------------
        LLVector3 horizontalDirectionFromCameraToSubject;
        if ( horizontalDistanceFromCameraToSubject < DISTANCE_EPSILON )
        {
            // make sure we still have a normalized vector if distance is really small
            // (this case is rare and fleeting)
            horizontalDirectionFromCameraToSubject = LLVector3::z_axis;
        }
        else
        {
            // I'm not using the "normalize" method, because I can just divide by horizontalDistanceFromCameraToSubject
            horizontalDirectionFromCameraToSubject = horizontalVectorFromCameraToSubject / horizontalDistanceFromCameraToSubject;
        }

        //------------------------------------------------------------------------------------------------------------
        // Here is where I determine an offset relative to subject position in oder to set the ideal position.
        //------------------------------------------------------------------------------------------------------------
        if ( mPitchSineAndCosineNeedToBeUpdated )
        {
            calculatePitchSineAndCosine();
            mPitchSineAndCosineNeedToBeUpdated = false;
        }

        LLVector3 positionOffsetFromSubject;
        positionOffsetFromSubject.setVec
            (
                horizontalDirectionFromCameraToSubject.mV[ VX ] * mPitchCos,
                horizontalDirectionFromCameraToSubject.mV[ VY ] * mPitchCos,
                -mPitchSin
            );

        positionOffsetFromSubject *= mSimulatedDistance;

        //----------------------------------------------------------------------
        // Finally, ideal position is set by taking the subject position and
        // extending the positionOffsetFromSubject from that
        //----------------------------------------------------------------------
        LLVector3 idealCameraPosition = offsetSubjectPosition - positionOffsetFromSubject;

        //--------------------------------------------------------------------------------
        // Now I prepare to move the current camera position towards its ideal position...
        //--------------------------------------------------------------------------------
        LLVector3 vectorFromPositionToIdealPosition = idealCameraPosition - simulated_pos_agent;
        F32 distanceFromPositionToIdealPosition = vectorFromPositionToIdealPosition.magVec();

        //put this inside of the block?
        LLVector3 normalFromPositionToIdealPosition = vectorFromPositionToIdealPosition / distanceFromPositionToIdealPosition;

        whereCameraPositionWantsToBe = simulated_pos_agent +
            (normalFromPositionToIdealPosition * (distanceFromPositionToIdealPosition - mPositionThreshold));
        //-------------------------------------------------------------------------------------------------
        // The following method takes the target camera position and resets it so that it stays "behind" the subject,
        // using behindness angle and behindness force as parameters affecting the exact behavior
        //-------------------------------------------------------------------------------------------------
        if ( distanceFromPositionToIdealPosition > mPositionThreshold )
        {
            F32 positionPullLerp = LLSmoothInterpolation::getInterpolant( mPositionLag );
            simulated_pos_agent = lerp( simulated_pos_agent, whereCameraPositionWantsToBe, positionPullLerp );
        }

        //--------------------------------------------------------------------
        // don't let the camera get farther than its official max distance
        //--------------------------------------------------------------------
        if ( distanceFromCameraToSubject > mMaxCameraDistantFromSubject )
        {
            LLVector3 directionFromCameraToSubject = vectorFromCameraToSubject / distanceFromCameraToSubject;
            simulated_pos_agent = offsetSubjectPosition - directionFromCameraToSubject * mMaxCameraDistantFromSubject;
        }

        ////-------------------------------------------------------------------------------------------------
        //// The following method takes mSimulatedPositionGlobal and resets it so that it stays "behind" the subject,
        //// using behindness angle and behindness force as parameters affecting the exact behavior
        ////-------------------------------------------------------------------------------------------------
        updateBehindnessConstraint(gAgent.getPosAgentFromGlobal(mSimulatedFocusGlobal), simulated_pos_agent);
        mSimulatedPositionGlobal = gAgent.getPosGlobalFromAgent(simulated_pos_agent);

        mRelativePos = lerp(mRelativePos, (simulated_pos_agent - mSubjectPosition) * ~mSubjectRotation, LLSmoothInterpolation::getInterpolant(0.05f));
    } // if position is not locked -----------------------------------------------------------


    //####################################################################################
    // update UpVector
    //####################################################################################
    // this just points upward for now, but I anticipate future effects requiring
    // some rolling ("banking" effects for fun, swoopy vehicles, etc.)
    mUpVector = LLVector3::z_axis;
}



//-------------------------------------------------------------------------------------
bool LLFollowCam::updateBehindnessConstraint(LLVector3 focus, LLVector3& cam_position)
{
    bool constraint_active = false;
    // only apply this stuff if the behindness angle is something other than opened up all the way
    if ( mBehindnessMaxAngle < FOLLOW_CAM_MAX_BEHINDNESS_ANGLE - FOLLOW_CAM_BEHINDNESS_EPSILON )
    {
        //--------------------------------------------------------------
        // horizontalized vector from focus to camera
        //--------------------------------------------------------------
        LLVector3 horizontalVectorFromFocusToCamera;
        horizontalVectorFromFocusToCamera.setVec(cam_position - focus);
        horizontalVectorFromFocusToCamera.mV[ VZ ] = 0.0f;
        F32 cameraZ = cam_position.mV[ VZ ];

        //--------------------------------------------------------------
        // distance of horizontalized vector
        //--------------------------------------------------------------
        F32 horizontalDistance = horizontalVectorFromFocusToCamera.magVec();

        //--------------------------------------------------------------------------------------------------
        // calculate horizontalized back vector of the subject and scale by horizontalDistance
        //--------------------------------------------------------------------------------------------------
        LLVector3 horizontalSubjectBack( -1.0f, 0.0f, 0.0f );
        horizontalSubjectBack *= mSubjectRotation;
        horizontalSubjectBack.mV[ VZ ] = 0.0f;
        horizontalSubjectBack.normVec(); // because horizontalizing might make it shorter than 1
        horizontalSubjectBack *= horizontalDistance;

        //--------------------------------------------------------------------------------------------------
        // find the angle (in degrees) between these vectors
        //--------------------------------------------------------------------------------------------------
        F32 cameraOffsetAngle = 0.f;
        LLVector3 cameraOffsetRotationAxis;
        LLQuaternion camera_offset_rotation;
        camera_offset_rotation.shortestArc(horizontalSubjectBack, horizontalVectorFromFocusToCamera);
        camera_offset_rotation.getAngleAxis(&cameraOffsetAngle, cameraOffsetRotationAxis);
        cameraOffsetAngle *= RAD_TO_DEG;

        if ( cameraOffsetAngle > mBehindnessMaxAngle )
        {
            F32 fraction = ((cameraOffsetAngle - mBehindnessMaxAngle) / cameraOffsetAngle) * LLSmoothInterpolation::getInterpolant(mBehindnessLag);
            cam_position = focus + horizontalSubjectBack * (slerp(fraction, camera_offset_rotation, LLQuaternion::DEFAULT));
            cam_position.mV[VZ] = cameraZ; // clamp z value back to what it was before we started messing with it
            constraint_active = true;
        }
    }
    return constraint_active;
}


//---------------------------------------------------------
void LLFollowCam::calculatePitchSineAndCosine()
{
    F32 radian = mPitch * DEG_TO_RAD;
    mPitchCos = cos( radian );
    mPitchSin = sin( radian );
}

//---------------------------------------------------------
void LLFollowCam::setSubjectPositionAndRotation( const LLVector3 p, const LLQuaternion r )
{
    mSubjectPosition = p;
    mSubjectRotation = r;
}


//---------------------------------------------------------
void LLFollowCam::zoom( S32 z )
{
    F32 zoomAmount = z * mSimulatedDistance * FOLLOW_CAM_ZOOM_FACTOR;

    if (( zoomAmount <  FOLLOW_CAM_MIN_ZOOM_AMOUNT )
    &&  ( zoomAmount > -FOLLOW_CAM_MIN_ZOOM_AMOUNT ))
    {
        if ( zoomAmount < 0.0f )
        {
            zoomAmount = -FOLLOW_CAM_MIN_ZOOM_AMOUNT;
        }
        else
        {
            zoomAmount = FOLLOW_CAM_MIN_ZOOM_AMOUNT;
        }
    }

    mSimulatedDistance += zoomAmount;

    mZoomedToMinimumDistance = false;
    if ( mSimulatedDistance < FOLLOW_CAM_MIN_DISTANCE )
    {
        mSimulatedDistance = FOLLOW_CAM_MIN_DISTANCE;

        // if zoomAmount is negative (i.e., getting closer), then
        // this signifies having hit the minimum:
        if ( zoomAmount < 0.0f )
        {
            mZoomedToMinimumDistance = true;
        }
    }
    else if ( mSimulatedDistance > mMaxCameraDistantFromSubject )
    {
        mSimulatedDistance = mMaxCameraDistantFromSubject;
    }
}


//---------------------------------------------------------
bool LLFollowCam::isZoomedToMinimumDistance()
{
    return mZoomedToMinimumDistance;
}


//---------------------------------------------------------
void LLFollowCam::reset( const LLVector3 p, const LLVector3 f , const LLVector3 u )
{
    setPosition(p);
    setFocus(f);
    mUpVector   = u;
}

//---------------------------------------------------------
void LLFollowCam::setMaxCameraDistantFromSubject( F32 m )
{
    mMaxCameraDistantFromSubject = m;
}


void LLFollowCam::setPitch( F32 p )
{
    LLFollowCamParams::setPitch(p);
    mPitchSineAndCosineNeedToBeUpdated = true; // important
}

void LLFollowCam::setDistance( F32 d )
{
    if (d != mDistance)
    {
        LLFollowCamParams::setDistance(d);
        mSimulatedDistance = d;
        mZoomedToMinimumDistance = false;
    }
}

void LLFollowCam::setPosition( const LLVector3& p )
{
    if (p != mPosition)
    {
        LLFollowCamParams::setPosition(p);
        mSimulatedPositionGlobal = gAgent.getPosGlobalFromAgent(mPosition);
        if (mPositionLocked)
        {
            mRelativePos = (mPosition - mSubjectPosition) * ~mSubjectRotation;
        }
    }
}

void LLFollowCam::setFocus( const LLVector3& f )
{
    if (f != mFocus)
    {
        LLFollowCamParams::setFocus(f);
        mSimulatedFocusGlobal = gAgent.getPosGlobalFromAgent(f);
        if (mFocusLocked)
        {
            mRelativeFocus = (mFocus - mSubjectPosition) * ~mSubjectRotation;
        }
    }
}

void LLFollowCam::setPositionLocked( bool locked )
{
    LLFollowCamParams::setPositionLocked(locked);
    if (locked)
    {
        // propagate set position to relative position
        mRelativePos = (gAgent.getPosAgentFromGlobal(mSimulatedPositionGlobal) - mSubjectPosition) * ~mSubjectRotation;
    }
}

void LLFollowCam::setFocusLocked( bool locked )
{
    LLFollowCamParams::setFocusLocked(locked);
    if (locked)
    {
        // propagate set position to relative position
        mRelativeFocus = (gAgent.getPosAgentFromGlobal(mSimulatedFocusGlobal) - mSubjectPosition) * ~mSubjectRotation;
    }
}


LLVector3   LLFollowCam::getSimulatedPosition() const
{
    // return simulated position
    return mSubjectPosition + (mRelativePos * mSubjectRotation);
}

LLVector3   LLFollowCam::getSimulatedFocus() const
{
    // return simulated focus point
    return mSubjectPosition + (mRelativeFocus * mSubjectRotation);
}

LLVector3   LLFollowCam::getUpVector()
{
    return mUpVector;
}


//------------------------------------
// Destructor
//------------------------------------
LLFollowCam::~LLFollowCam()
{
}


//-------------------------------------------------------
// LLFollowCamMgr
//-------------------------------------------------------
LLFollowCamMgr::LLFollowCamMgr()
{
}

LLFollowCamMgr::~LLFollowCamMgr()
{
    for (param_map_t::iterator iter = mParamMap.begin(); iter != mParamMap.end(); ++iter)
    {
        LLFollowCamParams* params = iter->second;
        delete params;
    }
    mParamMap.clear();
}

void LLFollowCamMgr::setPositionLag( const LLUUID& source, F32 lag)
{
    LLFollowCamParams* paramsp = getParamsForID(source);
    if (paramsp)
    {
        paramsp->setPositionLag(lag);
    }
}

void LLFollowCamMgr::setFocusLag( const LLUUID& source, F32 lag)
{
    LLFollowCamParams* paramsp = getParamsForID(source);
    if (paramsp)
    {
        paramsp->setFocusLag(lag);
    }
}

void LLFollowCamMgr::setFocusThreshold( const LLUUID& source, F32 threshold)
{
    LLFollowCamParams* paramsp = getParamsForID(source);
    if (paramsp)
    {
        paramsp->setFocusThreshold(threshold);
    }

}

void LLFollowCamMgr::setPositionThreshold( const LLUUID& source, F32 threshold)
{
    LLFollowCamParams* paramsp = getParamsForID(source);
    if (paramsp)
    {
        paramsp->setPositionThreshold(threshold);
    }
}

void LLFollowCamMgr::setDistance( const LLUUID& source, F32 distance)
{
    LLFollowCamParams* paramsp = getParamsForID(source);
    if (paramsp)
    {
        paramsp->setDistance(distance);
    }
}

void LLFollowCamMgr::setPitch( const LLUUID& source, F32 pitch)
{
    LLFollowCamParams* paramsp = getParamsForID(source);
    if (paramsp)
    {
        paramsp->setPitch(pitch);
    }
}

void LLFollowCamMgr::setFocusOffset( const LLUUID& source, const LLVector3& offset)
{
    LLFollowCamParams* paramsp = getParamsForID(source);
    if (paramsp)
    {
        paramsp->setFocusOffset(offset);
    }
}

void LLFollowCamMgr::setBehindnessAngle( const LLUUID& source, F32 angle)
{
    LLFollowCamParams* paramsp = getParamsForID(source);
    if (paramsp)
    {
        paramsp->setBehindnessAngle(angle);
    }
}

void LLFollowCamMgr::setBehindnessLag( const LLUUID& source, F32 force)
{
    LLFollowCamParams* paramsp = getParamsForID(source);
    if (paramsp)
    {
        paramsp->setBehindnessLag(force);
    }
}

void LLFollowCamMgr::setPosition( const LLUUID& source, const LLVector3 position)
{
    LLFollowCamParams* paramsp = getParamsForID(source);
    if (paramsp)
    {
        paramsp->setPosition(position);
    }
}

void LLFollowCamMgr::setFocus( const LLUUID& source, const LLVector3 focus)
{
    LLFollowCamParams* paramsp = getParamsForID(source);
    if (paramsp)
    {
        paramsp->setFocus(focus);
    }
}

void LLFollowCamMgr::setPositionLocked( const LLUUID& source, bool locked)
{
    LLFollowCamParams* paramsp = getParamsForID(source);
    if (paramsp)
    {
        paramsp->setPositionLocked(locked);
    }
}

void LLFollowCamMgr::setFocusLocked( const LLUUID& source, bool locked )
{
    LLFollowCamParams* paramsp = getParamsForID(source);
    if (paramsp)
    {
        paramsp->setFocusLocked(locked);
    }
}

LLFollowCamParams* LLFollowCamMgr::getParamsForID(const LLUUID& source)
{
    LLFollowCamParams* params = NULL;

    param_map_t::iterator found_it = mParamMap.find(source);
    if (found_it == mParamMap.end()) // didn't find it?
    {
        params = new LLFollowCamParams();
        mParamMap[source] = params;
    }
    else
    {
        params = found_it->second;
    }

    return params;
}

LLFollowCamParams* LLFollowCamMgr::getActiveFollowCamParams()
{
    if (mParamStack.empty())
    {
        return NULL;
    }

    return mParamStack.back();
}

void LLFollowCamMgr::setCameraActive( const LLUUID& source, bool active )
{
    LLFollowCamParams* params = getParamsForID(source);
    param_stack_t::iterator found_it = std::find(mParamStack.begin(), mParamStack.end(), params);
    if (found_it != mParamStack.end())
    {
        mParamStack.erase(found_it);
    }
    // put on top of stack
    if(active)
    {
        mParamStack.push_back(params);
    }
}

void LLFollowCamMgr::removeFollowCamParams(const LLUUID& source)
{
    setCameraActive(source, false);
    LLFollowCamParams* params = getParamsForID(source);
    mParamMap.erase(source);
    delete params;
}

bool LLFollowCamMgr::isScriptedCameraSource(const LLUUID& source)
{
    param_map_t::iterator found_it = mParamMap.find(source);
    return (found_it != mParamMap.end());
}

void LLFollowCamMgr::dump()
{
    S32 param_count = 0;
    LL_INFOS() << "Scripted camera active stack" << LL_ENDL;
    for (param_stack_t::iterator param_it = mParamStack.begin();
        param_it != mParamStack.end();
        ++param_it)
    {
        LL_INFOS() << param_count++ <<
            " rot_limit: " << (*param_it)->getBehindnessAngle() <<
            " rot_lag: " << (*param_it)->getBehindnessLag() <<
            " distance: " << (*param_it)->getDistance() <<
            " focus: " << (*param_it)->getFocus() <<
            " foc_lag: " << (*param_it)->getFocusLag() <<
            " foc_lock: " << ((*param_it)->getFocusLocked() ? "Y" : "N") <<
            " foc_offset: " << (*param_it)->getFocusOffset() <<
            " foc_thresh: " << (*param_it)->getFocusThreshold() <<
            " pitch: " << (*param_it)->getPitch() <<
            " pos: " << (*param_it)->getPosition() <<
            " pos_lag: " << (*param_it)->getPositionLag() <<
            " pos_lock: " << ((*param_it)->getPositionLocked() ? "Y" : "N") <<
            " pos_thresh: " << (*param_it)->getPositionThreshold() << LL_ENDL;
    }
}