/** 
 * @file llagent.cpp
 * @brief LLAgent class implementation
 *
 * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc.
 * $License$
 */

#include "llviewerprecompiledheaders.h"

#include "stdtypes.h"
#include "stdenums.h"

#include "llagent.h" 

#include "llcoordframe.h"
#include "indra_constants.h"
#include "llmath.h"
#include "llcriticaldamp.h"
#include "llfocusmgr.h"
#include "llglheaders.h"
#include "llparcel.h"
#include "llpermissions.h"
#include "llregionhandle.h"
#include "m3math.h"
#include "m4math.h"
#include "message.h"
#include "llquaternion.h"
#include "v3math.h"
#include "v4math.h"
//#include "vmath.h"

#include "imageids.h"
#include "llbox.h"
#include "llbutton.h"
#include "llcameraview.h"
#include "llcallingcard.h"
#include "llchatbar.h"
#include "llconsole.h"
#include "lldrawable.h"
#include "llface.h"
#include "llfirstuse.h"
#include "llfloater.h"
#include "llfloateravatarinfo.h"
#include "llfloaterbuildoptions.h"
#include "llfloaterchat.h"
#include "llfloatercustomize.h"
#include "llfloaterdirectory.h"
#include "llfloatergroupinfo.h"
#include "llfloatergroups.h"
#include "llfloatermap.h"
#include "llfloatermute.h"
#include "llfloatersnapshot.h"
#include "llfloatertools.h"
#include "llfloaterworldmap.h"
#include "llgroupmgr.h"
#include "llhudeffectlookat.h"
#include "llhudmanager.h"
#include "llinventorymodel.h"
#include "llinventoryview.h"
#include "lljoystickbutton.h"
#include "llmenugl.h"
#include "llmorphview.h"
#include "llmoveview.h"
#include "llnotify.h"
#include "llquantize.h"
#include "llselectmgr.h"
#include "llsky.h"
#include "llsphere.h"
#include "llstatusbar.h"
#include "llimview.h"
#include "lltool.h"
#include "lltoolcomp.h"		// for gToolGun
#include "lltoolfocus.h"
#include "lltoolgrab.h"
#include "lltoolmgr.h"
#include "lltoolpie.h"
#include "lltoolview.h"
#include "llui.h"			// for make_ui_sound
#include "llviewercamera.h"
#include "llviewerinventory.h"
#include "llviewermenu.h"
#include "llviewernetwork.h"
#include "llviewerobjectlist.h"
#include "llviewerparcelmgr.h"
#include "llviewerparceloverlay.h"
#include "llviewerregion.h"
#include "llviewerstats.h"
#include "llviewerwindow.h"
#include "llvoavatar.h"
#include "llvoground.h"
#include "llvosky.h"
#include "llwearable.h"
#include "llwearablelist.h"
#include "llworld.h"
#include "llworldmap.h"
#include "pipeline.h"
#include "roles_constants.h"
#include "viewer.h"

// Ventrella
#include "llfollowcam.h"
// end Ventrella

extern LLMenuBarGL* gMenuBarView;
extern F32 gMinObjectDistance;
extern U8 gLastPickAlpha;
extern F32 gFrameDTClamped;

//drone wandering constants
const F32 MAX_WANDER_TIME = 20.f;						// seconds
const F32 MAX_HEADING_HALF_ERROR = 0.2f;				// radians
const F32 WANDER_MAX_SLEW_RATE = 2.f * DEG_TO_RAD;		// radians / frame
const F32 WANDER_TARGET_MIN_DISTANCE = 10.f;			// meters

// Autopilot constants
const F32 AUTOPILOT_HEADING_HALF_ERROR = 10.f * DEG_TO_RAD;	// radians
const F32 AUTOPILOT_MAX_SLEW_RATE = 1.f * DEG_TO_RAD;		// radians / frame
const F32 AUTOPILOT_STOP_DISTANCE = 2.f;					// meters
const F32 AUTOPILOT_HEIGHT_ADJUST_DISTANCE = 8.f;			// meters
const F32 AUTOPILOT_MIN_TARGET_HEIGHT_OFF_GROUND = 1.f;	// meters
const F32 AUTOPILOT_MAX_TIME_NO_PROGRESS = 1.5f;		// seconds

// face editing constants
const LLVector3d FACE_EDIT_CAMERA_OFFSET(0.4f, -0.05f, 0.07f);
const LLVector3d FACE_EDIT_TARGET_OFFSET(0.f, 0.f, 0.05f);

// Mousewheel camera zoom
const F32 MIN_ZOOM_FRACTION = 0.25f;
const F32 INITIAL_ZOOM_FRACTION = 1.f;
const F32 MAX_ZOOM_FRACTION = 8.f;
const F32 METERS_PER_WHEEL_CLICK = 1.f;

const F32 MAX_TIME_DELTA = 1.f;

const F32 CAMERA_ZOOM_HALF_LIFE = 0.07f;	// seconds
const F32 FOV_ZOOM_HALF_LIFE = 0.07f;	// seconds

const F32 CAMERA_FOCUS_HALF_LIFE = 0.f;//0.02f;
const F32 CAMERA_LAG_HALF_LIFE = 0.25f;
const F32 MIN_CAMERA_LAG = 0.5f;
const F32 MAX_CAMERA_LAG = 5.f;

const F32 CAMERA_COLLIDE_EPSILON = 0.1f;
const F32 MIN_CAMERA_DISTANCE = 0.1f;
const F32 AVATAR_ZOOM_MIN_X_FACTOR = 0.55f;
const F32 AVATAR_ZOOM_MIN_Y_FACTOR = 0.7f;
const F32 AVATAR_ZOOM_MIN_Z_FACTOR = 1.15f;

const F32 MAX_CAMERA_DISTANCE_FROM_AGENT = 50.f;

const F32 HEAD_BUFFER_SIZE = 0.3f;
const F32 CUSTOMIZE_AVATAR_CAMERA_ANIM_SLOP = 0.2f;

const F32 LAND_MIN_ZOOM = 0.15f;
const F32 AVATAR_MIN_ZOOM = 0.5f;
const F32 OBJECT_MIN_ZOOM = 0.02f;

const F32 APPEARANCE_MIN_ZOOM = 0.39f;
const F32 APPEARANCE_MAX_ZOOM = 8.f;

// fidget constants
const F32 MIN_FIDGET_TIME = 8.f; // seconds
const F32 MAX_FIDGET_TIME = 20.f; // seconds

const S32 MAX_NUM_CHAT_POSITIONS = 10;
const F32 GROUND_TO_AIR_CAMERA_TRANSITION_TIME = 0.5f;
const F32 GROUND_TO_AIR_CAMERA_TRANSITION_START_TIME = 0.5f;

const F32 MAX_VELOCITY_AUTO_LAND_SQUARED = 4.f * 4.f;

const F32 MAX_FOCUS_OFFSET = 20.f;

const F32 OBJECT_EXTENTS_PADDING = 0.5f;

const F32 MIN_RADIUS_ALPHA_SIZZLE = 0.5f;

const F64 CHAT_AGE_FAST_RATE = 3.0;

const S32 MAX_WEARABLES_PER_LAYERSET = 7;

const EWearableType WEARABLE_BAKE_TEXTURE_MAP[BAKED_TEXTURE_COUNT][MAX_WEARABLES_PER_LAYERSET] = 
{
	{ WT_SHAPE,	WT_SKIN,	WT_HAIR,	WT_INVALID,	WT_INVALID,	WT_INVALID,		WT_INVALID    },	// TEX_HEAD_BAKED
	{ WT_SHAPE, WT_SKIN,	WT_SHIRT,	WT_JACKET,	WT_GLOVES,	WT_UNDERSHIRT,	WT_INVALID	  },	// TEX_UPPER_BAKED
	{ WT_SHAPE, WT_SKIN,	WT_PANTS,	WT_SHOES,	WT_SOCKS,	WT_JACKET,		WT_UNDERPANTS },	// TEX_LOWER_BAKED
	{ WT_EYES,	WT_INVALID,	WT_INVALID,	WT_INVALID,	WT_INVALID,	WT_INVALID,		WT_INVALID    },	// TEX_EYES_BAKED
	{ WT_SKIRT,	WT_INVALID,	WT_INVALID,	WT_INVALID,	WT_INVALID,	WT_INVALID,		WT_INVALID    }		// TEX_SKIRT_BAKED
};

const LLUUID BAKED_TEXTURE_HASH[BAKED_TEXTURE_COUNT] = 
{
	LLUUID("18ded8d6-bcfc-e415-8539-944c0f5ea7a6"),
	LLUUID("338c29e3-3024-4dbb-998d-7c04cf4fa88f"),
	LLUUID("91b4a2c7-1b1a-ba16-9a16-1f8f8dcc1c3f"),
	LLUUID("b2cf28af-b840-1071-3c6a-78085d8128b5"),
	LLUUID("ea800387-ea1a-14e0-56cb-24f2022f969a")
};

//
// Statics
//
BOOL LLAgent::sDebugDisplayTarget = FALSE;

const F32 LLAgent::TYPING_TIMEOUT_SECS = 5.f;

class LLAgentFriendObserver : public LLFriendObserver
{
public:
	LLAgentFriendObserver() {}
	virtual ~LLAgentFriendObserver() {}
	virtual void changed(U32 mask);
};

void LLAgentFriendObserver::changed(U32 mask)
{
	// if there's a change we're interested in.
	if((mask & (LLFriendObserver::POWERS)) != 0)
	{
		gAgent.friendsChanged();
	}
}

// ************************************************************
// Enabled this definition to compile a 'hacked' viewer that
// locally believes the end user has godlike powers.
// #define HACKED_GODLIKE_VIEWER
// For a toggled version, see viewer.h for the
// TOGGLE_HACKED_GODLIKE_VIEWER define, instead.
// ************************************************************

// Constructors and Destructors

// JC - Please try to make this order match the order in the header
// file.  Otherwise it's hard to find variables that aren't initialized.
//-----------------------------------------------------------------------------
// LLAgent()
//-----------------------------------------------------------------------------
LLAgent::LLAgent()
:	mViewerPort(NET_USE_OS_ASSIGNED_PORT),
	mDrawDistance( DEFAULT_FAR_PLANE ),
	mAccess(SIM_ACCESS_PG),
	mGroupPowers(0),
	mGroupID(),
	//mGroupInsigniaID(),
	mMapOriginX(0),
	mMapOriginY(0),
	mMapWidth(0),
	mMapHeight(0),
	mLookAt(NULL),
	mPointAt(NULL),
	mInitialized(FALSE),
	mNumPendingQueries(0),
	mForceMouselook(FALSE),
	mTeleportState( TELEPORT_NONE ),
	mRegionp(NULL),

	mAgentOriginGlobal(),
	mPositionGlobal(),

	mDistanceTraveled(0),
	mLastPositionGlobal(LLVector3d::zero),

	mAvatarObject(NULL),

	mRenderState(0),
	mTypingTimer(),

	mCameraMode( CAMERA_MODE_THIRD_PERSON ),
	mLastCameraMode( CAMERA_MODE_THIRD_PERSON ),
	mViewsPushed(FALSE),

	mbAlwaysRun(FALSE),
	mShowAvatar(TRUE),

	mCameraAnimating( FALSE ),
	mAnimationCameraStartGlobal(),
	mAnimationFocusStartGlobal(),
	mAnimationTimer(),
	mAnimationDuration(0.33f),
	mCameraFOVZoomFactor(0.f),
	mCameraCurrentFOVZoomFactor(0.f),
	mCameraFocusOffset(),
	mCameraOffsetDefault(),
//	mCameraOffsetNorm(),
	mCameraCollidePlane(),
	mCurrentCameraDistance(2.f),		// meters, set in init()
	mTargetCameraDistance(2.f),
	mCameraZoomFraction(1.f),			// deprecated
	mThirdPersonHeadOffset(0.f, 0.f, 1.f),
	mSitCameraEnabled(FALSE),
	mFocusOnAvatar(TRUE),
	mFocusGlobal(),
	mFocusTargetGlobal(),
	mFocusObject(NULL),
	mFocusObjectOffset(),
	mFocusDotRadius( 0.1f ),			// meters
	mTrackFocusObject(TRUE),

	mFrameAgent(),

	mCrouching(FALSE),
	mIsBusy(FALSE),

	// movement keys below

	mControlFlags(0x00000000),
	mbFlagsDirty(FALSE),
	mbFlagsNeedReset(FALSE),

	mbJump(FALSE),

	mWanderTimer(),
	mWanderTargetGlobal( LLVector3d::zero ),

	mAutoPilot(FALSE),
	mAutoPilotFlyOnStop(FALSE),
	mAutoPilotTargetGlobal(),
	mAutoPilotStopDistance(1.f),
	mAutoPilotUseRotation(FALSE),
	mAutoPilotTargetFacing(LLVector3::zero),
	mAutoPilotTargetDist(0.f),
	mAutoPilotFinishedCallback(NULL),
	mAutoPilotCallbackData(NULL),
	

	mEffectColor(0.f, 1.f, 1.f, 1.f),
	mHaveHomePosition(FALSE),
	mHomeRegionHandle( 0 ),
	mNearChatRadius(10.f),
	mGodLevel( GOD_NOT ),


	mNextFidgetTime(0.f),
	mCurrentFidget(0),
	mFirstLogin(FALSE),
	mGenderChosen(FALSE),
	mAgentWearablesUpdateSerialNum(0),
	mWearablesLoaded(FALSE),
	mTextureCacheQueryID(0),
	mAppearanceSerialNum(0)
{

	U32 i;
	for (i = 0; i < TOTAL_CONTROLS; i++)
	{
		mControlsTakenCount[i] = 0;
		mControlsTakenPassedOnCount[i] = 0;
	}

	// Initialize movement keys
	mAtKey				= 0;	// Either 1, 0, or -1... indicates that movement-key is pressed
	mWalkKey			= 0;	// like AtKey, but causes less forward thrust
	mLeftKey			= 0;
	mUpKey				= 0;
	mYawKey				= 0.f;
	mPitchKey			= 0;

	mOrbitLeftKey		= 0.f;
	mOrbitRightKey		= 0.f;
	mOrbitUpKey			= 0.f;
	mOrbitDownKey		= 0.f;
	mOrbitInKey			= 0.f;
	mOrbitOutKey		= 0.f;

	mPanUpKey			= 0.f;
	mPanDownKey			= 0.f;
	mPanLeftKey			= 0.f;
	mPanRightKey		= 0.f;
	mPanInKey			= 0.f;
	mPanOutKey			= 0.f;

	mActiveCacheQueries = new S32[BAKED_TEXTURE_COUNT];
	for (i = 0; i < (U32)BAKED_TEXTURE_COUNT; i++)
	{
		mActiveCacheQueries[i] = 0;
	}

	//Ventrella
	mCameraUpVector = LLVector3::z_axis;// default is straight up 
	mFollowCam.setMaxCameraDistantFromSubject( MAX_CAMERA_DISTANCE_FROM_AGENT );
	//end ventrella
}

// Requires gSavedSettings to be initialized.
//-----------------------------------------------------------------------------
// init()
//-----------------------------------------------------------------------------
void LLAgent::init()
{
	mDrawDistance = gSavedSettings.getF32("RenderFarClip");

	gCamera = new LLViewerCamera();

	gCamera->setView(DEFAULT_FIELD_OF_VIEW);
	// Leave at 0.1 meters until we have real near clip management
	gCamera->setNear(0.1f);
	gCamera->setFar(mDrawDistance);			// if you want to change camera settings, do so in camera.h
	gCamera->setAspect( gViewerWindow->getDisplayAspectRatio() );		// default, overridden in LLViewerWindow::reshape
	gCamera->setViewHeightInPixels(768);			// default, overridden in LLViewerWindow::reshape

	setFlying( gSavedSettings.getBOOL("FlyingAtExit") );

	mCameraFocusOffsetTarget = LLVector4(gSavedSettings.getVector3("CameraOffsetBuild"));
	mCameraOffsetDefault = gSavedSettings.getVector3("CameraOffsetDefault");
//	mCameraOffsetNorm = mCameraOffsetDefault;
//	mCameraOffsetNorm.normVec();
	mCameraCollidePlane.clearVec();
	mCurrentCameraDistance = mCameraOffsetDefault.magVec();
	mTargetCameraDistance = mCurrentCameraDistance;
	mCameraZoomFraction = 1.f;
	mTrackFocusObject = gSavedSettings.getBOOL("TrackFocusObject");

//	LLDebugVarMessageBox::show("Camera Lag", &CAMERA_FOCUS_HALF_LIFE, 0.5f, 0.01f);
	gSavedSettings.getControl("RenderHideGroupTitle")->addListener(&mHideGroupTitleListener);
	gSavedSettings.getControl("EffectColor")->addListener(&mEffectColorListener);

	mEffectColor = gSavedSettings.getColor4("EffectColor");
	
	mInitialized = TRUE;
}

//-----------------------------------------------------------------------------
// cleanup()
//-----------------------------------------------------------------------------
void LLAgent::cleanup()
{
	setSitCamera(LLUUID::null);
	mAvatarObject = NULL;
	mLookAt = NULL;
	mPointAt = NULL;
	mRegionp = NULL;
	setFocusObject(NULL);
	mFadeObjects.clear();
}

//-----------------------------------------------------------------------------
// LLAgent()
//-----------------------------------------------------------------------------
LLAgent::~LLAgent()
{
	cleanup();

	delete [] mActiveCacheQueries;
	mActiveCacheQueries = NULL;

	delete gCamera;
	gCamera = NULL;
}

// Change camera back to third person, stop the autopilot,
// deselect stuff, etc.
//-----------------------------------------------------------------------------
// resetView()
//-----------------------------------------------------------------------------
void LLAgent::resetView(BOOL reset_camera)
{
	if (mAutoPilot)
	{
		stopAutoPilot(TRUE);
	}

	if (!gNoRender)
	{
		gSelectMgr->deselectAll();
		gSelectMgr->unhighlightAll();

		// By popular request, keep land selection while walking around. JC
		// gParcelMgr->deselectLand();

		// Hide all popup menus
		gPieSelf->hide(FALSE);
		gPieAvatar->hide(FALSE);
		gPieObject->hide(FALSE);
		gPieLand->hide(FALSE);
	}

	if (reset_camera && !gSavedSettings.getBOOL("FreezeTime"))
	{
		if (!gViewerWindow->getLeftMouseDown() && cameraThirdPerson())
		{
			// leaving mouse-steer mode
			LLVector3 agent_at_axis = getAtAxis();
			agent_at_axis -= projected_vec(agent_at_axis, getReferenceUpVector());
			agent_at_axis.normVec();
			gAgent.resetAxes(lerp(getAtAxis(), agent_at_axis, LLCriticalDamp::getInterpolant(0.3f)));
		}

		setFocusOnAvatar(TRUE, ANIMATE);
	}

	if (mAvatarObject.notNull())
	{
		mAvatarObject->mHUDTargetZoom = 1.f;
	}
}

void LLAgent::ageChat()
{
	if (mAvatarObject)
	{
		// get amount of time since I last chatted
		F64 elapsed_time = (F64)mAvatarObject->mChatTimer.getElapsedTimeF32();
		// add in frame time * 3 (so it ages 4x)
		mAvatarObject->mChatTimer.setAge(elapsed_time + (F64)gFrameDTClamped * (CHAT_AGE_FAST_RATE - 1.0));
	}
}

// Allow camera to be moved somewhere other than behind avatar.
//-----------------------------------------------------------------------------
// unlockView()
//-----------------------------------------------------------------------------
void LLAgent::unlockView()
{
	if (getFocusOnAvatar())
	{
		if (mAvatarObject)
		{
			setFocusGlobal( LLVector3d::zero, mAvatarObject->mID );
		}
		setFocusOnAvatar(FALSE, FALSE);	// no animation
	}
}


//-----------------------------------------------------------------------------
// moveAt()
//-----------------------------------------------------------------------------
void LLAgent::moveAt(S32 direction)
{
	// age chat timer so it fades more quickly when you are intentionally moving
	ageChat();

	setKey(direction, mAtKey);

	if (direction > 0)
	{
		setControlFlags(AGENT_CONTROL_AT_POS | AGENT_CONTROL_FAST_AT);
	}
	else if (direction < 0)
	{
		setControlFlags(AGENT_CONTROL_AT_NEG | AGENT_CONTROL_FAST_AT);
	}

	resetView();
}

//-----------------------------------------------------------------------------
// moveAtNudge()
//-----------------------------------------------------------------------------
void LLAgent::moveAtNudge(S32 direction)
{
	// age chat timer so it fades more quickly when you are intentionally moving
	ageChat();

	setKey(direction, mWalkKey);

	if (direction > 0)
	{
		setControlFlags(AGENT_CONTROL_NUDGE_AT_POS);
	}
	else if (direction < 0)
	{
		setControlFlags(AGENT_CONTROL_NUDGE_AT_NEG);
	}

	resetView();
}

//-----------------------------------------------------------------------------
// moveLeft()
//-----------------------------------------------------------------------------
void LLAgent::moveLeft(S32 direction)
{
	// age chat timer so it fades more quickly when you are intentionally moving
	ageChat();

	setKey(direction, mLeftKey);

	if (direction > 0)
	{
		setControlFlags(AGENT_CONTROL_LEFT_POS | AGENT_CONTROL_FAST_LEFT);
	}
	else if (direction < 0)
	{
		setControlFlags(AGENT_CONTROL_LEFT_NEG | AGENT_CONTROL_FAST_LEFT);
	}

	resetView();
}

//-----------------------------------------------------------------------------
// moveLeftNudge()
//-----------------------------------------------------------------------------
void LLAgent::moveLeftNudge(S32 direction)
{
	// age chat timer so it fades more quickly when you are intentionally moving
	ageChat();

	setKey(direction, mLeftKey);

	if (direction > 0)
	{
		setControlFlags(AGENT_CONTROL_NUDGE_LEFT_POS);
	}
	else if (direction < 0)
	{
		setControlFlags(AGENT_CONTROL_NUDGE_LEFT_NEG);
	}

	resetView();
}

//-----------------------------------------------------------------------------
// moveUp()
//-----------------------------------------------------------------------------
void LLAgent::moveUp(S32 direction)
{
	// age chat timer so it fades more quickly when you are intentionally moving
	ageChat();

	setKey(direction, mUpKey);

	if (direction > 0)
	{
		setControlFlags(AGENT_CONTROL_UP_POS | AGENT_CONTROL_FAST_UP);
	}
	else if (direction < 0)
	{
		setControlFlags(AGENT_CONTROL_UP_NEG | AGENT_CONTROL_FAST_UP);
	}

	resetView();
}

//-----------------------------------------------------------------------------
// moveYaw()
//-----------------------------------------------------------------------------
void LLAgent::moveYaw(F32 mag)
{
	mYawKey = mag;

	if (mag > 0)
	{
		setControlFlags(AGENT_CONTROL_YAW_POS);
	}
	else if (mag < 0)
	{
		setControlFlags(AGENT_CONTROL_YAW_NEG);
	}

	resetView();
}

//-----------------------------------------------------------------------------
// movePitch()
//-----------------------------------------------------------------------------
void LLAgent::movePitch(S32 direction)
{
	setKey(direction, mPitchKey);

	if (direction > 0)
	{
		setControlFlags(AGENT_CONTROL_PITCH_POS );
	}
	else if (direction < 0)
	{
		setControlFlags(AGENT_CONTROL_PITCH_NEG);
	}
}


// Does this parcel allow you to fly?
BOOL LLAgent::canFly()
{
	if (isGodlike()) return TRUE;

	if (!gParcelMgr) return FALSE;

	LLViewerRegion* regionp = getRegion();
	if (regionp && regionp->getBlockFly()) return FALSE;
	
	LLParcel* parcel = gParcelMgr->getAgentParcel();
	if (!parcel) return FALSE;

	// Allow owners to fly on their own land.
	if (LLViewerParcelMgr::isParcelOwnedByAgent(parcel, GP_LAND_ALLOW_FLY))
	{
		return TRUE;
	}

	return parcel->getAllowFly();
}


//-----------------------------------------------------------------------------
// setFlying()
//-----------------------------------------------------------------------------
void LLAgent::setFlying(BOOL fly)
{
	if (mAvatarObject.notNull())
	{
		if(mAvatarObject->mSignaledAnimations.find(ANIM_AGENT_STANDUP) != mAvatarObject->mSignaledAnimations.end())
		{
			return;
		}

		// don't allow taking off while sitting
		if (fly && mAvatarObject->mIsSitting)
		{
			return;
		}
	}

	if (fly)
	{
		BOOL was_flying = getFlying();
		if (gParcelMgr)
		{
			if (!canFly() && !was_flying)
			{
				// parcel doesn't let you start fly
				// gods can always fly
				// and it's OK if you're already flying
				make_ui_sound("UISndBadKeystroke");
				return;
			}
		}
		if( !was_flying )
		{
			gViewerStats->incStat(LLViewerStats::ST_FLY_COUNT);
		}
		setControlFlags(AGENT_CONTROL_FLY);
		gSavedSettings.setBOOL("FlyBtnState", TRUE);
	}
	else
	{
		clearControlFlags(AGENT_CONTROL_FLY);
		gSavedSettings.setBOOL("FlyBtnState", FALSE);
	}
	mbFlagsDirty = TRUE;
}


// UI based mechanism of setting fly state
//-----------------------------------------------------------------------------
// toggleFlying()
//-----------------------------------------------------------------------------
void LLAgent::toggleFlying()
{
	BOOL fly = !(mControlFlags & AGENT_CONTROL_FLY);

	setFlying( fly );
	resetView();
}


//-----------------------------------------------------------------------------
// setRegion()
//-----------------------------------------------------------------------------
void LLAgent::setRegion(LLViewerRegion *regionp)
{
	llassert(regionp);
	if (mRegionp != regionp)
	{
		// JC - Avoid this, causes out-of-bounds array write deep within
		// Windows.
		// char host_name[MAX_STRING];
		// regionp->getHost().getHostName(host_name, MAX_STRING);

		char ip[MAX_STRING];
		regionp->getHost().getString(ip, MAX_STRING);
		llinfos << "Moving agent into region: " << regionp->getName()
				<< " located at " << ip << llendl;
		if (mRegionp)
		{
			// We've changed regions, we're now going to change our agent coordinate frame.
			mAgentOriginGlobal = regionp->getOriginGlobal();
			LLVector3d agent_offset_global = mRegionp->getOriginGlobal();

			LLVector3 delta;
			delta.setVec(regionp->getOriginGlobal() - mRegionp->getOriginGlobal());

			setPositionAgent(getPositionAgent() - delta);
			LLVector3 camera_position_agent = gCamera->getOrigin();
			gCamera->setOrigin(camera_position_agent - delta);

			// Update all of the regions.
			gWorldPointer->updateAgentOffset(agent_offset_global);

			// Hack to keep sky in the agent's region, otherwise it may get deleted - DJS 08/02/02
			if (gSky.mVOSkyp)
			{
				gSky.mVOSkyp->setRegion(regionp);
			}
			if (gSky.mVOStarsp)
			{
				gSky.mVOStarsp->setRegion(regionp);
			}
			if (gSky.mVOGroundp)
			{
				gSky.mVOGroundp->setRegion(regionp);
			}

		}
		else
		{
			// First time initialization.
			// We've changed regions, we're now going to change our agent coordinate frame.
			mAgentOriginGlobal = regionp->getOriginGlobal();

			LLVector3 delta;
			delta.setVec(regionp->getOriginGlobal());

			setPositionAgent(getPositionAgent() - delta);
			LLVector3 camera_position_agent = gCamera->getOrigin();
			gCamera->setOrigin(camera_position_agent - delta);

			// Update all of the regions.
			gWorldPointer->updateAgentOffset(mAgentOriginGlobal);
		}
	}
	mRegionp = regionp;

	// Must shift hole-covering water object locations because local
	// coordinate frame changed.
	gWorldPointer->updateWaterObjects();

	// keep a list of regions we've been too
	// this is just an interesting stat, logged at the dataserver
	// we could trake this at the dataserver side, but that's harder
	U64 handle = regionp->getHandle();
	mRegionsVisited.insert(handle);
}


//-----------------------------------------------------------------------------
// getRegion()
//-----------------------------------------------------------------------------
LLViewerRegion *LLAgent::getRegion() const
{
	return mRegionp;
}


const LLHost& LLAgent::getRegionHost() const
{
	if (mRegionp)
	{
		return mRegionp->getHost();
	}
	else
	{
		return LLHost::invalid;
	}
}


//-----------------------------------------------------------------------------
// inPrelude()
//-----------------------------------------------------------------------------
BOOL LLAgent::inPrelude()
{
	return mRegionp && mRegionp->isPrelude();
}


//-----------------------------------------------------------------------------
// canManageEstate()
//-----------------------------------------------------------------------------

BOOL LLAgent::canManageEstate() const
{
	return mRegionp && mRegionp->canManageEstate();
}
//-----------------------------------------------------------------------------
// sendMessage()
//-----------------------------------------------------------------------------
void LLAgent::sendMessage()
{
	if (gDisconnected)
	{
		llwarns << "Trying to send message when disconnected!" << llendl;
		return;
	}
	if (!mRegionp)
	{
		llerrs << "No region for agent yet!" << llendl;
	}
	gMessageSystem->sendMessage(mRegionp->getHost());
}


//-----------------------------------------------------------------------------
// sendReliableMessage()
//-----------------------------------------------------------------------------
void LLAgent::sendReliableMessage()
{
	if (gDisconnected)
	{
		llwarns << "Trying to send message when disconnected!" << llendl;
		return;
	}
	if (!mRegionp)
	{
		llwarns << "LLAgent::sendReliableMessage No region for agent yet, not sending message!" << llendl;
		return;
	}
	gMessageSystem->sendReliable(mRegionp->getHost());
}

//-----------------------------------------------------------------------------
// getVelocity()
//-----------------------------------------------------------------------------
LLVector3 LLAgent::getVelocity() const
{
	if (mAvatarObject)
	{
		return mAvatarObject->getVelocity();
	}
	else
	{
		return LLVector3::zero;
	}
}


//-----------------------------------------------------------------------------
// setPositionAgent()
//-----------------------------------------------------------------------------
void LLAgent::setPositionAgent(const LLVector3 &pos_agent)
{
	if (!pos_agent.isFinite())
	{
		llerrs << "setPositionAgent is not a number" << llendl;
	}

	if (!mAvatarObject.isNull() && mAvatarObject->getParent())
	{
		LLVector3 pos_agent_sitting;
		LLVector3d pos_agent_d;
		LLViewerObject *parent = (LLViewerObject*)mAvatarObject->getParent();

		pos_agent_sitting = mAvatarObject->getPosition() * parent->getRotation() + parent->getPositionAgent();
		pos_agent_d.setVec(pos_agent_sitting);

		mFrameAgent.setOrigin(pos_agent_sitting);
		mPositionGlobal = pos_agent_d + mAgentOriginGlobal;
	}
	else
	{
		mFrameAgent.setOrigin(pos_agent);

		LLVector3d pos_agent_d;
		pos_agent_d.setVec(pos_agent);
		mPositionGlobal = pos_agent_d + mAgentOriginGlobal;
	}
}

//-----------------------------------------------------------------------------
// slamLookAt()
//-----------------------------------------------------------------------------
void LLAgent::slamLookAt(const LLVector3 &look_at)
{
	LLVector3 look_at_norm = look_at;
	look_at_norm.mV[VZ] = 0.f;
	look_at_norm.normVec();
	resetAxes(look_at_norm);
}

//-----------------------------------------------------------------------------
// getPositionGlobal()
//-----------------------------------------------------------------------------
const LLVector3d &LLAgent::getPositionGlobal() 
{
	if (!mAvatarObject.isNull() && !mAvatarObject->mDrawable.isNull())
	{
		mPositionGlobal = getPosGlobalFromAgent(mAvatarObject->getRenderPosition());
	}
	else
	{
		mPositionGlobal = getPosGlobalFromAgent(mFrameAgent.getOrigin());
	}

	return mPositionGlobal;
}

//-----------------------------------------------------------------------------
// getPositionAgent()
//-----------------------------------------------------------------------------
const LLVector3 &LLAgent::getPositionAgent()
{
	if(!mAvatarObject.isNull() && !mAvatarObject->mDrawable.isNull())
	{
		mFrameAgent.setOrigin(mAvatarObject->getRenderPosition());	
	}

	return mFrameAgent.getOrigin();
}

//-----------------------------------------------------------------------------
// getRegionsVisited()
//-----------------------------------------------------------------------------
const S32 LLAgent::getRegionsVisited() const
{
	return mRegionsVisited.size();
}

//-----------------------------------------------------------------------------
// getDistanceTraveled()
//-----------------------------------------------------------------------------
const F64 LLAgent::getDistanceTraveled() const
{
	return mDistanceTraveled;
}


//-----------------------------------------------------------------------------
// getPosAgentFromGlobal()
//-----------------------------------------------------------------------------
LLVector3 LLAgent::getPosAgentFromGlobal(const LLVector3d &pos_global) const
{
	LLVector3 pos_agent;
	pos_agent.setVec(pos_global - mAgentOriginGlobal);
	return pos_agent;
}


//-----------------------------------------------------------------------------
// getPosGlobalFromAgent()
//-----------------------------------------------------------------------------
LLVector3d LLAgent::getPosGlobalFromAgent(const LLVector3 &pos_agent) const
{
	LLVector3d pos_agent_d;
	pos_agent_d.setVec(pos_agent);
	return pos_agent_d + mAgentOriginGlobal;
}


//-----------------------------------------------------------------------------
// resetAxes()
//-----------------------------------------------------------------------------
void LLAgent::resetAxes()
{
	mFrameAgent.resetAxes();
}


// Copied from LLCamera::setOriginAndLookAt
// Look_at must be unit vector
//-----------------------------------------------------------------------------
// resetAxes()
//-----------------------------------------------------------------------------
void LLAgent::resetAxes(const LLVector3 &look_at)
{
	LLVector3	skyward = getReferenceUpVector();

	// if look_at has zero length, fail
	// if look_at and skyward are parallel, fail
	//
	// Test both of these conditions with a cross product.
	LLVector3 cross(look_at % skyward);
	if (cross.isNull())
	{
		llinfos << "LLAgent::resetAxes cross-product is zero" << llendl;
		return;
	}

	// Make sure look_at and skyward are not parallel
	// and neither are zero length
	LLVector3 left(skyward % look_at);
	LLVector3 up(look_at % left);

	mFrameAgent.setAxes(look_at, left, up);
}


//-----------------------------------------------------------------------------
// rotate()
//-----------------------------------------------------------------------------
void LLAgent::rotate(F32 angle, const LLVector3 &axis) 
{ 
	mFrameAgent.rotate(angle, axis); 
}


//-----------------------------------------------------------------------------
// rotate()
//-----------------------------------------------------------------------------
void LLAgent::rotate(F32 angle, F32 x, F32 y, F32 z) 
{ 
	mFrameAgent.rotate(angle, x, y, z); 
}


//-----------------------------------------------------------------------------
// rotate()
//-----------------------------------------------------------------------------
void LLAgent::rotate(const LLMatrix3 &matrix) 
{ 
	mFrameAgent.rotate(matrix); 
}


//-----------------------------------------------------------------------------
// rotate()
//-----------------------------------------------------------------------------
void LLAgent::rotate(const LLQuaternion &quaternion) 
{ 
	mFrameAgent.rotate(quaternion); 
}


//-----------------------------------------------------------------------------
// getReferenceUpVector()
//-----------------------------------------------------------------------------
LLVector3 LLAgent::getReferenceUpVector()
{
	// this vector is in the coordinate frame of the avatar's parent object, or the world if none
	LLVector3 up_vector = LLVector3::z_axis;
	if (mAvatarObject.notNull() && 
		mAvatarObject->getParent() &&
		mAvatarObject->mDrawable.notNull())
	{
		U32 camera_mode = mCameraAnimating ? mLastCameraMode : mCameraMode;
		// and in third person...
		if (camera_mode == CAMERA_MODE_THIRD_PERSON)
		{
			// make the up vector point to the absolute +z axis
			up_vector = up_vector * ~((LLViewerObject*)mAvatarObject->getParent())->getRenderRotation();
		}
		else if (camera_mode == CAMERA_MODE_MOUSELOOK)
		{
			// make the up vector point to the avatar's +z axis
			up_vector = up_vector * mAvatarObject->mDrawable->getRotation();
		}
	}

	return up_vector;
}


// Radians, positive is forward into ground
//-----------------------------------------------------------------------------
// pitch()
//-----------------------------------------------------------------------------
void LLAgent::pitch(F32 angle)
{
	// don't let user pitch if pointed almost all the way down or up
	
	// A dot B = mag(A) * mag(B) * cos(angle between A and B)
	// so... cos(angle between A and B) = A dot B / mag(A) / mag(B)
	//                                  = A dot B for unit vectors

	LLVector3 skyward = getReferenceUpVector();

	F32			look_down_limit;
	F32			look_up_limit = 10.f * DEG_TO_RAD;

	F32 angle_from_skyward = acos( mFrameAgent.getAtAxis() * skyward );

	if (mAvatarObject.notNull() && mAvatarObject->mIsSitting)
	{
		look_down_limit = 130.f * DEG_TO_RAD;
	}
	else
	{
		look_down_limit = 170.f * DEG_TO_RAD;
	}

	// clamp pitch to limits
	if ((angle >= 0.f) && (angle_from_skyward + angle > look_down_limit))
	{
		angle = look_down_limit - angle_from_skyward;
	}
	else if ((angle < 0.f) && (angle_from_skyward + angle < look_up_limit))
	{
		angle = look_up_limit - angle_from_skyward;
	}

	mFrameAgent.pitch(angle);
}


//-----------------------------------------------------------------------------
// roll()
//-----------------------------------------------------------------------------
void LLAgent::roll(F32 angle)
{
	mFrameAgent.roll(angle);
}


//-----------------------------------------------------------------------------
// yaw()
//-----------------------------------------------------------------------------
void LLAgent::yaw(F32 angle)
{
	if (!rotateGrabbed())
	{
		mFrameAgent.rotate(angle, getReferenceUpVector());
	}
}


// Returns a quat that represents the rotation of the agent in the absolute frame
//-----------------------------------------------------------------------------
// getQuat()
//-----------------------------------------------------------------------------
LLQuaternion LLAgent::getQuat() const
{
	return mFrameAgent.getQuaternion();
}


//-----------------------------------------------------------------------------
// calcFocusOffset()
//-----------------------------------------------------------------------------
LLVector3d LLAgent::calcFocusOffset(LLViewerObject *object, S32 x, S32 y)
{
	// calculate offset based on view direction
	BOOL is_avatar = object->isAvatar();
	LLMatrix4 obj_matrix = is_avatar  ? ((LLVOAvatar*)object)->mPelvisp->getWorldMatrix() : object->getRenderMatrix();
	LLQuaternion obj_rot = is_avatar  ? ((LLVOAvatar*)object)->mPelvisp->getWorldRotation() : object->getRenderRotation();
	LLVector3 obj_pos = is_avatar ? ((LLVOAvatar*)object)->mPelvisp->getWorldPosition() : object->getRenderPosition();
	LLQuaternion inv_obj_rot = ~obj_rot;

	LLVector3 obj_dir_abs = obj_pos - gCamera->getOrigin();
	obj_dir_abs.rotVec(inv_obj_rot);
	obj_dir_abs.normVec();
	obj_dir_abs.abs();

	LLVector3 object_extents = object->getScale();
	// make sure they object extents are non-zero
	object_extents.clamp(0.001f, F32_MAX);
	LLVector3 object_half_extents = object_extents * 0.5f;

	obj_dir_abs.mV[VX] = obj_dir_abs.mV[VX] / object_extents.mV[VX];
	obj_dir_abs.mV[VY] = obj_dir_abs.mV[VY] / object_extents.mV[VY];
	obj_dir_abs.mV[VZ] = obj_dir_abs.mV[VZ] / object_extents.mV[VZ];

	LLVector3 normal;
	if (obj_dir_abs.mV[VX] > obj_dir_abs.mV[VY] && obj_dir_abs.mV[VX] > obj_dir_abs.mV[VZ])
	{
		normal.setVec(obj_matrix.getFwdRow4());
	}
	else if (obj_dir_abs.mV[VY] > obj_dir_abs.mV[VZ])
	{
		normal.setVec(obj_matrix.getLeftRow4());
	}
	else
	{
		normal.setVec(obj_matrix.getUpRow4());
	}
	normal.normVec();

	LLVector3d focus_pt_global;
	// RN: should we check return value for valid pick?
	gViewerWindow->mousePointOnPlaneGlobal(focus_pt_global, x, y, gAgent.getPosGlobalFromAgent(obj_pos), normal);
	LLVector3 focus_pt = gAgent.getPosAgentFromGlobal(focus_pt_global);
	// find vector from camera to focus point in object coordinates
	LLVector3 camera_focus_vec = focus_pt - gCamera->getOrigin();
	// convert to object-local space
	camera_focus_vec.rotVec(inv_obj_rot);

	// find vector from object origin to focus point in object coordinates
	LLVector3 focus_delta = focus_pt - obj_pos;
	// convert to object-local space
	focus_delta.rotVec(inv_obj_rot);

	// calculate clip percentage needed to get focus offset back in bounds along the camera_focus axis
	LLVector3 clip_fraction;

	for (U32 axis = VX; axis <= VZ; axis++)
	{
		F32 clip_amt;
		if (focus_delta.mV[axis] > 0.f)
		{
			clip_amt = llmax(0.f, focus_delta.mV[axis] - object_half_extents.mV[axis]);
		}
		else
		{
			clip_amt = llmin(0.f, focus_delta.mV[axis] + object_half_extents.mV[axis]);
		}

		// don't divide by very small nunber
		if (llabs(camera_focus_vec.mV[axis]) < 0.0001f)
		{
			clip_fraction.mV[axis] = 0.f;
		}
		else
		{
			clip_fraction.mV[axis] = clip_amt / camera_focus_vec.mV[axis];
		}
	}

	LLVector3 abs_clip_fraction = clip_fraction;
	abs_clip_fraction.abs();

	// find greatest shrinkage factor and
	// rescale focus offset to inside object extents
	if (abs_clip_fraction.mV[VX] > abs_clip_fraction.mV[VY] &&
		abs_clip_fraction.mV[VX] > abs_clip_fraction.mV[VZ])
	{
		focus_delta -= clip_fraction.mV[VX] * camera_focus_vec;
	}
	else if (abs_clip_fraction.mV[VY] > abs_clip_fraction.mV[VZ])
	{
		focus_delta -= clip_fraction.mV[VY] * camera_focus_vec;
	}
	else
	{
		focus_delta -= clip_fraction.mV[VZ] * camera_focus_vec;
	}

	// convert back to world space
	focus_delta.rotVec(obj_rot);
	
	if (!is_avatar) 
	{
		//unproject relative clicked coordinate from window coordinate using GL
		glPushMatrix();
		glMatrixMode(GL_PROJECTION);
		glLoadMatrixf((const GLfloat*) gCamera->getProjection().mMatrix);
		glMatrixMode(GL_MODELVIEW);
		glLoadMatrixf((const GLfloat*) gCamera->getModelview().mMatrix);
		glMultMatrixf((const GLfloat*) obj_matrix.mMatrix);

		GLint viewport[4];
		GLdouble modelview[16];
		GLdouble projection[16];
		GLfloat winX, winY, winZ;
		GLdouble posX, posY, posZ;

		glGetDoublev( GL_MODELVIEW_MATRIX, modelview );
		glGetDoublev( GL_PROJECTION_MATRIX, projection );
		glGetIntegerv( GL_VIEWPORT, viewport );

		winX = ((F32)x) * gViewerWindow->getDisplayScale().mV[VX];
		winY = ((F32)y) * gViewerWindow->getDisplayScale().mV[VY];
		glReadPixels( llfloor(winX), llfloor(winY), 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &winZ );

		gluUnProject( winX, winY, winZ, modelview, projection, viewport, &posX, &posY, &posZ);

		glPopMatrix();

		LLVector3 obj_rel = LLVector3((F32)posX, (F32)posY, (F32)posZ);
		LLVector3 obj_center = LLVector3(0, 0, 0);
		obj_rel = obj_rel * object->getRenderMatrix(); //mDrawable->getWorldMatrix();
		obj_center = obj_center * object->getRenderMatrix();//mDrawable->getWorldMatrix();
		obj_rel -= object->getRenderPosition();//mDrawable->getWorldPosition();
		
		//now that we have the object relative position, we should bias toward the center of the object 
		//based on the distance of the camera to the focus point vs. the distance of the camera to the focus

		F32 relDist = llabs(obj_rel * gCamera->getAtAxis());
		F32 viewDist = dist_vec(obj_center + obj_rel, gCamera->getOrigin());


		LLBBox obj_bbox = object->getBoundingBoxAgent();
		F32 bias = 0.f;

		LLVector3 virtual_camera_pos = gAgent.getPosAgentFromGlobal(mFocusTargetGlobal + (getCameraPositionGlobal() - mFocusTargetGlobal) / (1.f + mCameraFOVZoomFactor));

		if(obj_bbox.containsPointAgent(virtual_camera_pos))
		{
			// if the camera is inside the object (large, hollow objects, for example)
			// force focus point all the way to destination depth, away from object center
			bias = 1.f;
		}
		else
		{
			// perform magic number biasing of focus point towards surface vs. planar center
			bias = clamp_rescale(relDist/viewDist, 0.1f, 0.7f, 0.0f, 1.0f);
		}
		
		obj_rel = lerp(focus_delta, obj_rel, bias);
		
		return LLVector3d(obj_rel);
	}

	return LLVector3d(focus_delta.mV[VX], focus_delta.mV[VY], focus_delta.mV[VZ]);
}

//-----------------------------------------------------------------------------
// calcCameraMinDistance()
//-----------------------------------------------------------------------------
BOOL LLAgent::calcCameraMinDistance(F32 &obj_min_distance)
{
	BOOL soft_limit = FALSE; // is the bounding box to be treated literally (volumes) or as an approximation (avatars)

	if (!mFocusObject || mFocusObject->isDead())
	{
		obj_min_distance = 0.f;
		return TRUE;
	}

	if (mFocusObject->mDrawable.isNull())
	{
#ifdef LL_RELEASE_FOR_DOWNLOAD
		llwarns << "Focus object with no drawable!" << llendl;
#else
		mFocusObject->dump();
		llerrs << "Focus object with no drawable!" << llendl;
#endif
		obj_min_distance = 0.f;
		return TRUE;
	}
	
	LLQuaternion inv_object_rot = ~mFocusObject->getRenderRotation();
	LLVector3 target_offset_origin = mFocusObjectOffset;
	LLVector3 camera_offset_target(getCameraPositionAgent() - getPosAgentFromGlobal(mFocusTargetGlobal));

	// convert offsets into object local space
	camera_offset_target.rotVec(inv_object_rot);
	target_offset_origin.rotVec(inv_object_rot);

	// push around object extents based on target offset
	LLVector3 object_extents = mFocusObject->getScale();
	if (mFocusObject->isAvatar())
	{
		// fudge factors that lets you zoom in on avatars a bit more (which don't do FOV zoom)
		object_extents.mV[VX] *= AVATAR_ZOOM_MIN_X_FACTOR;
		object_extents.mV[VY] *= AVATAR_ZOOM_MIN_Y_FACTOR;
		object_extents.mV[VZ] *= AVATAR_ZOOM_MIN_Z_FACTOR;
		soft_limit = TRUE;
	}
	LLVector3 abs_target_offset = target_offset_origin;
	abs_target_offset.abs();

	LLVector3 target_offset_dir = target_offset_origin;
	F32 object_radius = mFocusObject->getVObjRadius();

	BOOL target_outside_object_extents = FALSE;

	for (U32 i = VX; i <= VZ; i++)
	{
		if (abs_target_offset.mV[i] * 2.f > object_extents.mV[i] + OBJECT_EXTENTS_PADDING)
		{
			target_outside_object_extents = TRUE;
		}
		if (camera_offset_target.mV[i] > 0.f)
		{
			object_extents.mV[i] -= target_offset_origin.mV[i] * 2.f;
		}
		else
		{
			object_extents.mV[i] += target_offset_origin.mV[i] * 2.f;
		}
	}

	// don't shrink the object extents so far that the object inverts
	object_extents.clamp(0.001f, F32_MAX);

	// move into first octant
	LLVector3 camera_offset_target_abs_norm = camera_offset_target;
	camera_offset_target_abs_norm.abs();
	// make sure offset is non-zero
	camera_offset_target_abs_norm.clamp(0.001f, F32_MAX);
	camera_offset_target_abs_norm.normVec();

	// find camera position relative to normalized object extents
	LLVector3 camera_offset_target_scaled = camera_offset_target_abs_norm;
	camera_offset_target_scaled.mV[VX] /= object_extents.mV[VX];
	camera_offset_target_scaled.mV[VY] /= object_extents.mV[VY];
	camera_offset_target_scaled.mV[VZ] /= object_extents.mV[VZ];

	if (camera_offset_target_scaled.mV[VX] > camera_offset_target_scaled.mV[VY] && 
		camera_offset_target_scaled.mV[VX] > camera_offset_target_scaled.mV[VZ])
	{
		if (camera_offset_target_abs_norm.mV[VX] < 0.001f)
		{
			obj_min_distance = object_extents.mV[VX] * 0.5f;
		}
		else
		{
			obj_min_distance = object_extents.mV[VX] * 0.5f / camera_offset_target_abs_norm.mV[VX];
		}
	}
	else if (camera_offset_target_scaled.mV[VY] > camera_offset_target_scaled.mV[VZ])
	{
		if (camera_offset_target_abs_norm.mV[VY] < 0.001f)
		{
			obj_min_distance = object_extents.mV[VY] * 0.5f;
		}
		else
		{
			obj_min_distance = object_extents.mV[VY] * 0.5f / camera_offset_target_abs_norm.mV[VY];
		}
	}
	else
	{
		if (camera_offset_target_abs_norm.mV[VZ] < 0.001f)
		{
			obj_min_distance = object_extents.mV[VZ] * 0.5f;
		}
		else
		{
			obj_min_distance = object_extents.mV[VZ] * 0.5f / camera_offset_target_abs_norm.mV[VZ];
		}
	}

	LLVector3 object_split_axis;
	LLVector3 target_offset_scaled = target_offset_origin;
	target_offset_scaled.abs();
	target_offset_scaled.normVec();
	target_offset_scaled.mV[VX] /= object_extents.mV[VX];
	target_offset_scaled.mV[VY] /= object_extents.mV[VY];
	target_offset_scaled.mV[VZ] /= object_extents.mV[VZ];

	if (target_offset_scaled.mV[VX] > target_offset_scaled.mV[VY] && 
		target_offset_scaled.mV[VX] > target_offset_scaled.mV[VZ])
	{
		object_split_axis = LLVector3::x_axis;
	}
	else if (target_offset_scaled.mV[VY] > target_offset_scaled.mV[VZ])
	{
		object_split_axis = LLVector3::y_axis;
	}
	else
	{
		object_split_axis = LLVector3::z_axis;
	}

	LLVector3 camera_offset_object(getCameraPositionAgent() - mFocusObject->getPositionAgent());

	// length projected orthogonal to target offset
	F32 camera_offset_dist = (camera_offset_object - target_offset_dir * (camera_offset_object * target_offset_dir)).magVec();

	// calculate whether the target point would be "visible" if it were outside the bounding box
	// on the opposite of the splitting plane defined by object_split_axis;
	BOOL exterior_target_visible = FALSE;
	if (camera_offset_dist > object_radius)
	{
		// target is visible from camera, so turn off fov zoom
		exterior_target_visible = TRUE;
	}

	F32 camera_offset_clip = camera_offset_object * object_split_axis;
	F32 target_offset_clip = target_offset_dir * object_split_axis;

	// target has moved outside of object extents
	// check to see if camera and target are on same side 
	if (target_outside_object_extents)
	{
		if (camera_offset_clip > 0.f && target_offset_clip > 0.f)
		{
			return FALSE;
		}
		else if (camera_offset_clip < 0.f && target_offset_clip < 0.f)
		{
			return FALSE;
		}
	}

	// clamp obj distance to diagonal of 10 by 10 cube
	obj_min_distance = llmin(obj_min_distance, 10.f * F_SQRT3);

	obj_min_distance += gCamera->getNear() + (soft_limit ? 0.1f : 0.2f);
	
	return TRUE;
}

F32 LLAgent::getCameraZoomFraction()
{
	// 0.f -> camera zoomed all the way out
	// 1.f -> camera zoomed all the way in
	if (gSelectMgr->getObjectCount() && gSelectMgr->getSelectType() == SELECT_TYPE_HUD)
	{
		// already [0,1]
		return mAvatarObject->mHUDTargetZoom;
	}
	else if (mFocusOnAvatar && cameraThirdPerson())
	{
		return clamp_rescale(mCameraZoomFraction, MIN_ZOOM_FRACTION, MAX_ZOOM_FRACTION, 1.f, 0.f);
	}
	else if (cameraCustomizeAvatar())
	{
		F32 distance = (F32)mCameraFocusOffsetTarget.magVec();
		return clamp_rescale(distance, APPEARANCE_MIN_ZOOM, APPEARANCE_MAX_ZOOM, 1.f, 0.f );
	}
	else
	{
		F32 min_zoom;
		const F32 DIST_FUDGE = 16.f; // meters
		F32 max_zoom = gWorldPointer ? llmin(mDrawDistance - DIST_FUDGE, 
								gWorldPointer->getRegionWidthInMeters() - DIST_FUDGE,
								MAX_CAMERA_DISTANCE_FROM_AGENT) : MAX_CAMERA_DISTANCE_FROM_AGENT;

		F32 distance = (F32)mCameraFocusOffsetTarget.magVec();
		if (mFocusObject.notNull())
		{
			if (mFocusObject->isAvatar())
			{
				min_zoom = AVATAR_MIN_ZOOM;
			}
			else
			{
				min_zoom = OBJECT_MIN_ZOOM;
			}
		}
		else
		{
			min_zoom = LAND_MIN_ZOOM;
		}

		return clamp_rescale(distance, min_zoom, max_zoom, 1.f, 0.f);
	}
}

void LLAgent::setCameraZoomFraction(F32 fraction)
{
	// 0.f -> camera zoomed all the way out
	// 1.f -> camera zoomed all the way in
	if (gSelectMgr->getObjectCount() && gSelectMgr->getSelectType() == SELECT_TYPE_HUD)
	{
		mAvatarObject->mHUDTargetZoom = fraction;
	}
	else if (mFocusOnAvatar && cameraThirdPerson())
	{
		mCameraZoomFraction = rescale(fraction, 0.f, 1.f, MAX_ZOOM_FRACTION, MIN_ZOOM_FRACTION);
	}
	else if (cameraCustomizeAvatar())
	{
		LLVector3d camera_offset_dir = mCameraFocusOffsetTarget;
		camera_offset_dir.normVec();
		mCameraFocusOffsetTarget = camera_offset_dir * rescale(fraction, 0.f, 1.f, APPEARANCE_MAX_ZOOM, APPEARANCE_MIN_ZOOM);
	}
	else
	{
		F32 min_zoom = LAND_MIN_ZOOM;
		const F32 DIST_FUDGE = 16.f; // meters
		F32 max_zoom = llmin(mDrawDistance - DIST_FUDGE, 
								gWorldPointer->getRegionWidthInMeters() - DIST_FUDGE,
								MAX_CAMERA_DISTANCE_FROM_AGENT);

		if (mFocusObject.notNull())
		{
			if (mFocusObject.notNull())
			{
				if (mFocusObject->isAvatar())
				{
					min_zoom = AVATAR_MIN_ZOOM;
				}
				else
				{
					min_zoom = OBJECT_MIN_ZOOM;
				}
			}
		}

		LLVector3d camera_offset_dir = mCameraFocusOffsetTarget;
		camera_offset_dir.normVec();
		mCameraFocusOffsetTarget = camera_offset_dir * rescale(fraction, 0.f, 1.f, max_zoom, min_zoom);
	}
	startCameraAnimation();
}


//-----------------------------------------------------------------------------
// cameraOrbitAround()
//-----------------------------------------------------------------------------
void LLAgent::cameraOrbitAround(const F32 radians)
{
	if (gSelectMgr->getObjectCount() && gSelectMgr->getSelectType() == SELECT_TYPE_HUD)
	{
		// do nothing for hud selection
	}
	else if (mFocusOnAvatar && (mCameraMode == CAMERA_MODE_THIRD_PERSON || mCameraMode == CAMERA_MODE_FOLLOW))
	{
		mFrameAgent.rotate(radians, getReferenceUpVector());
	}
	else
	{
		mCameraFocusOffsetTarget.rotVec(radians, 0.f, 0.f, 1.f);
		
		cameraZoomIn(1.f);
	}
}


//-----------------------------------------------------------------------------
// cameraOrbitOver()
//-----------------------------------------------------------------------------
void LLAgent::cameraOrbitOver(const F32 angle)
{
	if (gSelectMgr->getObjectCount() && gSelectMgr->getSelectType() == SELECT_TYPE_HUD)
	{
		// do nothing for hud selection
	}
	else if (mFocusOnAvatar && mCameraMode == CAMERA_MODE_THIRD_PERSON)
	{
		pitch(angle);
	}
	else
	{
		LLVector3 camera_offset_unit(mCameraFocusOffsetTarget);
		camera_offset_unit.normVec();

		F32 angle_from_up = acos( camera_offset_unit * getReferenceUpVector() );

		LLVector3d left_axis;
		left_axis.setVec(gCamera->getLeftAxis());
		F32 new_angle = llclamp(angle_from_up - angle, 1.f * DEG_TO_RAD, 179.f * DEG_TO_RAD);
		mCameraFocusOffsetTarget.rotVec(angle_from_up - new_angle, left_axis);

		cameraZoomIn(1.f);
	}
}

//-----------------------------------------------------------------------------
// cameraZoomIn()
//-----------------------------------------------------------------------------
void LLAgent::cameraZoomIn(const F32 fraction)
{
	if (gDisconnected)
	{
		return;
	}

	if (gSelectMgr->getObjectCount() && gSelectMgr->getSelectType() == SELECT_TYPE_HUD)
	{
		// just update hud zoom level
		mAvatarObject->mHUDTargetZoom /= fraction;
		return;
	}


	LLVector3d	camera_offset(mCameraFocusOffsetTarget);
	LLVector3d	camera_offset_unit(mCameraFocusOffsetTarget);
	F32 min_zoom = LAND_MIN_ZOOM;
	F32 current_distance = (F32)camera_offset_unit.normVec();
	F32 new_distance = current_distance * fraction;

	// Don't move through focus point
	if (mFocusObject)
	{
		LLVector3 camera_offset_dir((F32)camera_offset_unit.mdV[VX], (F32)camera_offset_unit.mdV[VY], (F32)camera_offset_unit.mdV[VZ]);

		if (mFocusObject->isAvatar())
		{
			calcCameraMinDistance(min_zoom);
		}
		else
		{
			min_zoom = OBJECT_MIN_ZOOM;
		}
	}

	new_distance = llmax(new_distance, min_zoom); 

	// Don't zoom too far back
	const F32 DIST_FUDGE = 16.f; // meters
	F32 max_distance = llmin(mDrawDistance - DIST_FUDGE, 
							 gWorldPointer->getRegionWidthInMeters() - DIST_FUDGE );

	if (new_distance > max_distance)
	{
		new_distance = max_distance;

		/*
		// Unless camera is unlocked
		if (!LLViewerCamera::sDisableCameraConstraints)
		{
			return;
		}
		*/
	}

	if( cameraCustomizeAvatar() )
	{
		new_distance = llclamp( new_distance, APPEARANCE_MIN_ZOOM, APPEARANCE_MAX_ZOOM );
	}

	mCameraFocusOffsetTarget = new_distance * camera_offset_unit;
}

//-----------------------------------------------------------------------------
// cameraOrbitIn()
//-----------------------------------------------------------------------------
void LLAgent::cameraOrbitIn(const F32 meters)
{
	if (mFocusOnAvatar && mCameraMode == CAMERA_MODE_THIRD_PERSON)
	{
		F32 camera_offset_dist = llmax(0.001f, mCameraOffsetDefault.magVec());
		
		mCameraZoomFraction = (mTargetCameraDistance - meters) / camera_offset_dist;

		if (!gSavedSettings.getBOOL("FreezeTime") && mCameraZoomFraction < MIN_ZOOM_FRACTION && meters > 0.f)
		{
			// No need to animate, camera is already there.
			changeCameraToMouselook(FALSE);
		}

		mCameraZoomFraction = llclamp(mCameraZoomFraction, MIN_ZOOM_FRACTION, MAX_ZOOM_FRACTION);
	}
	else
	{
		LLVector3d	camera_offset(mCameraFocusOffsetTarget);
		LLVector3d	camera_offset_unit(mCameraFocusOffsetTarget);
		F32 current_distance = (F32)camera_offset_unit.normVec();
		F32 new_distance = current_distance - meters;
		F32 min_zoom = LAND_MIN_ZOOM;
		
		// Don't move through focus point
		if (mFocusObject.notNull())
		{
			if (mFocusObject->isAvatar())
			{
				min_zoom = AVATAR_MIN_ZOOM;
			}
			else
			{
				min_zoom = OBJECT_MIN_ZOOM;
			}
		}

		new_distance = llmax(new_distance, min_zoom);

		// Don't zoom too far back
		const F32 DIST_FUDGE = 16.f; // meters
		F32 max_distance = llmin(mDrawDistance - DIST_FUDGE, 
								 gWorldPointer->getRegionWidthInMeters() - DIST_FUDGE );

		if (new_distance > max_distance)
		{
			// Unless camera is unlocked
			if (!LLViewerCamera::sDisableCameraConstraints)
			{
				return;
			}
		}

		if( CAMERA_MODE_CUSTOMIZE_AVATAR == getCameraMode() )
		{
			llclamp( new_distance, APPEARANCE_MIN_ZOOM, APPEARANCE_MAX_ZOOM );
		}

		// Compute new camera offset
		mCameraFocusOffsetTarget = new_distance * camera_offset_unit;
		cameraZoomIn(1.f);
	}
}


//-----------------------------------------------------------------------------
// cameraPanIn()
//-----------------------------------------------------------------------------
void LLAgent::cameraPanIn(F32 meters)
{
	LLVector3d at_axis;
	at_axis.setVec(gCamera->getAtAxis());

	mFocusTargetGlobal += meters * at_axis;
	mFocusGlobal = mFocusTargetGlobal;
	// don't enforce zoom constraints as this is the only way for users to get past them easily
	updateFocusOffset();
}

//-----------------------------------------------------------------------------
// cameraPanLeft()
//-----------------------------------------------------------------------------
void LLAgent::cameraPanLeft(F32 meters)
{
	LLVector3d left_axis;
	left_axis.setVec(gCamera->getLeftAxis());

	mFocusTargetGlobal += meters * left_axis;
	mFocusGlobal = mFocusTargetGlobal;
	cameraZoomIn(1.f);
	updateFocusOffset();
}

//-----------------------------------------------------------------------------
// cameraPanUp()
//-----------------------------------------------------------------------------
void LLAgent::cameraPanUp(F32 meters)
{
	LLVector3d up_axis;
	up_axis.setVec(gCamera->getUpAxis());

	mFocusTargetGlobal += meters * up_axis;
	mFocusGlobal = mFocusTargetGlobal;
	cameraZoomIn(1.f);
	updateFocusOffset();
}

//-----------------------------------------------------------------------------
// setKey()
//-----------------------------------------------------------------------------
void LLAgent::setKey(const S32 direction, S32 &key)
{
	if (direction > 0)
	{
		key = 1;
	}
	else if (direction < 0)
	{
		key = -1;
	}
	else
	{
		key = 0;
	}
}


//-----------------------------------------------------------------------------
// getControlFlags()
//-----------------------------------------------------------------------------
U32 LLAgent::getControlFlags()
{
/*
	// HACK -- avoids maintenance of control flags when camera mode is turned on or off,
	// only worries about it when the flags are measured
	if (mCameraMode == CAMERA_MODE_MOUSELOOK) 
	{
		if ( !(mControlFlags & AGENT_CONTROL_MOUSELOOK) )
		{
			mControlFlags |= AGENT_CONTROL_MOUSELOOK;
		}
	}
*/
	return mControlFlags;
}

//-----------------------------------------------------------------------------
// setControlFlags()
//-----------------------------------------------------------------------------
void LLAgent::setControlFlags(U32 mask)
{
	mControlFlags |= mask;
	mbFlagsDirty = TRUE;
}


//-----------------------------------------------------------------------------
// clearControlFlags()
//-----------------------------------------------------------------------------
void LLAgent::clearControlFlags(U32 mask)
{
	U32 old_flags = mControlFlags;
	mControlFlags &= ~mask;
	if (old_flags != mControlFlags)
	{
		mbFlagsDirty = TRUE;
	}
}

//-----------------------------------------------------------------------------
// controlFlagsDirty()
//-----------------------------------------------------------------------------
BOOL LLAgent::controlFlagsDirty() const
{
	return mbFlagsDirty;
}

//-----------------------------------------------------------------------------
// enableControlFlagReset()
//-----------------------------------------------------------------------------
void LLAgent::enableControlFlagReset()
{
	mbFlagsNeedReset = TRUE;
}

//-----------------------------------------------------------------------------
// resetControlFlags()
//-----------------------------------------------------------------------------
void LLAgent::resetControlFlags()
{
	if (mbFlagsNeedReset)
	{
		mbFlagsNeedReset = FALSE;
		mbFlagsDirty = FALSE;
		// reset all of the ephemeral flags
		// some flags are managed elsewhere
		mControlFlags &= AGENT_CONTROL_AWAY | AGENT_CONTROL_FLY | AGENT_CONTROL_MOUSELOOK;
	}
}

//-----------------------------------------------------------------------------
// setAFK()
//-----------------------------------------------------------------------------
void LLAgent::setAFK()
{
	// Drones can't go AFK
	if (gNoRender)
	{
		return;
	}

	if (!gAllowAFK)
	{
		return;
	}

	if (!gAgent.getRegion())
	{
		// Don't set AFK if we're not talking to a region yet.
		return;
	}

	if (!(mControlFlags & AGENT_CONTROL_AWAY))
	{
		sendAnimationRequest(ANIM_AGENT_AWAY, ANIM_REQUEST_START);
		setControlFlags(AGENT_CONTROL_AWAY | AGENT_CONTROL_STOP);
		gAwayTimer.start();
		if (gAFKMenu)
		{
			gAFKMenu->setLabel("Set Not Away");
		}
	}
}

//-----------------------------------------------------------------------------
// clearAFK()
//-----------------------------------------------------------------------------
void LLAgent::clearAFK()
{
	gAwayTriggerTimer.reset();

	// Gods can sometimes get into away state (via gestures)
	// without setting the appropriate control flag. JC
	LLVOAvatar* av = mAvatarObject;
	if (mControlFlags & AGENT_CONTROL_AWAY
		|| (av
			&& (av->mSignaledAnimations.find(ANIM_AGENT_AWAY) != av->mSignaledAnimations.end())))
	{
		sendAnimationRequest(ANIM_AGENT_AWAY, ANIM_REQUEST_STOP);
		clearControlFlags(AGENT_CONTROL_AWAY);
		if (gAFKMenu)
		{
			gAFKMenu->setLabel("Set Away");
		}
	}
}

//-----------------------------------------------------------------------------
// getAFK()
//-----------------------------------------------------------------------------
BOOL LLAgent::getAFK() const
{
	return (mControlFlags & AGENT_CONTROL_AWAY) != 0;
}

//-----------------------------------------------------------------------------
// setBusy()
//-----------------------------------------------------------------------------
void LLAgent::setBusy()
{
	sendAnimationRequest(ANIM_AGENT_BUSY, ANIM_REQUEST_START);
	mIsBusy = TRUE;
	if (gBusyMenu)
	{
		gBusyMenu->setLabel("Set Not Busy");
	}
	if (gFloaterMute)
	{
		gFloaterMute->updateButtons();
	}
}

//-----------------------------------------------------------------------------
// clearBusy()
//-----------------------------------------------------------------------------
void LLAgent::clearBusy()
{
	mIsBusy = FALSE;
	sendAnimationRequest(ANIM_AGENT_BUSY, ANIM_REQUEST_STOP);
	if (gBusyMenu)
	{
		gBusyMenu->setLabel("Set Busy");
	}
	if (gFloaterMute)
	{
		gFloaterMute->updateButtons();
	}
}

//-----------------------------------------------------------------------------
// getBusy()
//-----------------------------------------------------------------------------
BOOL LLAgent::getBusy() const
{
	return mIsBusy;
}


//-----------------------------------------------------------------------------
// updateWanderTarget()
//-----------------------------------------------------------------------------
void LLAgent::updateWanderTarget()
{
	S32 num_regions;
	LLViewerRegion*	rand_region;
	F32 rand_x;
	F32 rand_y;

	if (mWanderTimer.checkExpirationAndReset(frand(MAX_WANDER_TIME)))
	{
		// Pick a random spot to wander towards
		num_regions = gWorldPointer->mActiveRegionList.getLength();
		S32 region_num = llround(frand(1.f) * num_regions);
		rand_region = gWorldPointer->mActiveRegionList.getFirstData();
		S32 i = 0;
		while (i < region_num)
		{
			rand_region = gWorldPointer->mActiveRegionList.getNextData();
			i++;
		}
		rand_x = frand(rand_region->getWidth());
		rand_y = frand(rand_region->getWidth());
		
		stopAutoPilot();
		startAutoPilotGlobal(rand_region->getPosGlobalFromRegion(LLVector3(rand_x, rand_y, 0.f)));
	}
}

//-----------------------------------------------------------------------------
// startAutoPilotGlobal()
//-----------------------------------------------------------------------------
void LLAgent::startAutoPilotGlobal(const LLVector3d &target_global, const std::string& behavior_name, const LLQuaternion *target_rotation, void (*finish_callback)(BOOL, void *),  void *callback_data, F32 stop_distance, F32 rot_threshold)
{
	if (!gAgent.getAvatarObject() || !gWorldPointer)
	{
		return;
	}
	
	mAutoPilotFinishedCallback = finish_callback;
	mAutoPilotCallbackData = callback_data;
	mAutoPilotRotationThreshold = rot_threshold;
	mAutoPilotBehaviorName = behavior_name;

	LLVector3d delta_pos( target_global );
	delta_pos -= getPositionGlobal();
	F64 distance = delta_pos.magVec();
	LLVector3d trace_target = target_global;

	trace_target.mdV[VZ] -= 10.f;

	LLVector3d intersection;
	LLVector3 normal;
	LLViewerObject *hit_obj;
	F32 heightDelta = gWorldPointer->resolveStepHeightGlobal(NULL, target_global, trace_target, intersection, normal, &hit_obj);

	if (stop_distance > 0.f)
	{
		mAutoPilotStopDistance = stop_distance;
	}
	else
	{
		// Guess at a reasonable stop distance.
		mAutoPilotStopDistance = fsqrtf( distance );
		if (mAutoPilotStopDistance < 0.5f) 
		{
			mAutoPilotStopDistance = 0.5f;
		}
	}

	mAutoPilotFlyOnStop = getFlying();

	if (distance > 30.0)
	{
		setFlying(TRUE);
	}

	if ( distance > 1.f && heightDelta > (sqrtf(mAutoPilotStopDistance) + 1.f))
	{
		setFlying(TRUE);
		mAutoPilotFlyOnStop = TRUE;
	}

	mAutoPilot = TRUE;
	mAutoPilotTargetGlobal = target_global;

	// trace ray down to find height of destination from ground
	LLVector3d traceEndPt = target_global;
	traceEndPt.mdV[VZ] -= 20.f;

	LLVector3d targetOnGround;
	LLVector3 groundNorm;
	LLViewerObject *obj;

	gWorldPointer->resolveStepHeightGlobal(NULL, target_global, traceEndPt, targetOnGround, groundNorm, &obj);
	F64 target_height = llmax((F64)gAgent.getAvatarObject()->getPelvisToFoot(), target_global.mdV[VZ] - targetOnGround.mdV[VZ]);

	// clamp z value of target to minimum height above ground
	mAutoPilotTargetGlobal.mdV[VZ] = targetOnGround.mdV[VZ] + target_height;
	mAutoPilotTargetDist = (F32)dist_vec(gAgent.getPositionGlobal(), mAutoPilotTargetGlobal);
	if (target_rotation)
	{
		mAutoPilotUseRotation = TRUE;
		mAutoPilotTargetFacing = LLVector3::x_axis * *target_rotation;
		mAutoPilotTargetFacing.mV[VZ] = 0.f;
		mAutoPilotTargetFacing.normVec();
	}
	else
	{
		mAutoPilotUseRotation = FALSE;
	}

	mAutoPilotNoProgressFrameCount = 0;
}


//-----------------------------------------------------------------------------
// startFollowPilot()
//-----------------------------------------------------------------------------
void LLAgent::startFollowPilot(const LLUUID &leader_id)
{
	if (!mAutoPilot) return;

	mLeaderID = leader_id;
	if ( mLeaderID.isNull() ) return;

	LLViewerObject* object = gObjectList.findObject(mLeaderID);
	if (!object) 
	{
		mLeaderID = LLUUID::null;
		return;
	}

	startAutoPilotGlobal(object->getPositionGlobal());
}


//-----------------------------------------------------------------------------
// stopAutoPilot()
//-----------------------------------------------------------------------------
void LLAgent::stopAutoPilot(BOOL user_cancel)
{
	if (mAutoPilot)
	{
		mAutoPilot = FALSE;
		if (mAutoPilotUseRotation && !user_cancel)
		{
			resetAxes(mAutoPilotTargetFacing);
		}
		//NB: auto pilot can terminate for a reason other than reaching the destination
		//TODO: enforce rotation constraint here as well
		if (mAutoPilotFinishedCallback && 
			((mAutoPilotTargetDist < mAutoPilotStopDistance) || (mAutoPilotNoProgressFrameCount > AUTOPILOT_MAX_TIME_NO_PROGRESS * gFPSClamped)))
		{
			mAutoPilotFinishedCallback(!user_cancel && dist_vec(gAgent.getPositionGlobal(), mAutoPilotTargetGlobal) < mAutoPilotTargetDist, mAutoPilotCallbackData);
		}
		mLeaderID = LLUUID::null;

		// If the user cancelled, don't change the fly state
		if (!user_cancel)
		{
			setFlying(mAutoPilotFlyOnStop);
		}
		setControlFlags(AGENT_CONTROL_STOP);

		if (user_cancel && !mAutoPilotBehaviorName.empty())
		{
			if (mAutoPilotBehaviorName == "Sit")
				LLNotifyBox::showXml("CancelledSit");
			else if (mAutoPilotBehaviorName == "Attach")
				LLNotifyBox::showXml("CancelledAttach");
			else
				LLNotifyBox::showXml("Cancelled");
		}
	}
}


// Returns necessary agent pitch and yaw changes, radians.
//-----------------------------------------------------------------------------
// autoPilot()
//-----------------------------------------------------------------------------
void LLAgent::autoPilot(F32 *delta_yaw)
{
	if (mAutoPilot)
	{
		if (!mLeaderID.isNull())
		{
			LLViewerObject* object = gObjectList.findObject(mLeaderID);
			if (!object) 
			{
				stopAutoPilot();
				return;
			}
			mAutoPilotTargetGlobal = object->getPositionGlobal();
		}
		
		if (!mAvatarObject)
		{
			return;
		}

		if (mAvatarObject->mInAir)
		{
			setFlying(TRUE);
		}
	
		LLVector3 at;
		at.setVec(mFrameAgent.getAtAxis());
		LLVector3 target_agent = getPosAgentFromGlobal(mAutoPilotTargetGlobal);
		LLVector3 direction = target_agent - getPositionAgent();

		F32 target_dist = direction.magVec();

		if (target_dist >= mAutoPilotTargetDist)
		{
			mAutoPilotNoProgressFrameCount++;
			if (mAutoPilotNoProgressFrameCount > AUTOPILOT_MAX_TIME_NO_PROGRESS * gFPSClamped)
			{
				stopAutoPilot();
				return;
			}
		}

		mAutoPilotTargetDist = target_dist;

		// Make this a two-dimensional solution
		at.mV[VZ] = 0.f;
		direction.mV[VZ] = 0.f;

		at.normVec();
		F32 xy_distance = direction.normVec();

		F32 yaw = 0.f;
		if (mAutoPilotTargetDist > mAutoPilotStopDistance)
		{
			yaw = angle_between(mFrameAgent.getAtAxis(), direction);
		}
		else if (mAutoPilotUseRotation)
		{
			// we're close now just aim at target facing
			yaw = angle_between(at, mAutoPilotTargetFacing);
			direction = mAutoPilotTargetFacing;
		}

		yaw = 4.f * yaw / gFPSClamped;

		// figure out which direction to turn
		LLVector3 scratch(at % direction);

		if (scratch.mV[VZ] > 0.f)
		{
			setControlFlags(AGENT_CONTROL_YAW_POS);
		}
		else
		{
			yaw = -yaw;
			setControlFlags(AGENT_CONTROL_YAW_NEG);
		}

		*delta_yaw = yaw;

		// Compute when to start slowing down and when to stop
		F32 stop_distance = mAutoPilotStopDistance;
		F32 slow_distance;
		if (getFlying())
		{
			slow_distance = llmax(6.f, mAutoPilotStopDistance + 5.f);
			stop_distance = llmax(2.f, mAutoPilotStopDistance);
		}
		else
		{
			slow_distance = llmax(3.f, mAutoPilotStopDistance + 2.f);
		}

		// If we're flying, handle autopilot points above or below you.
		if (getFlying() && xy_distance < AUTOPILOT_HEIGHT_ADJUST_DISTANCE)
		{
			if (mAvatarObject)
			{
				F64 current_height = mAvatarObject->getPositionGlobal().mdV[VZ];
				F32 delta_z = (F32)(mAutoPilotTargetGlobal.mdV[VZ] - current_height);
				F32 slope = delta_z / xy_distance;
				if (slope > 0.45f && delta_z > 6.f)
				{
					setControlFlags(AGENT_CONTROL_FAST_UP | AGENT_CONTROL_UP_POS);
				}
				else if (slope > 0.002f && delta_z > 0.5f)
				{
					setControlFlags(AGENT_CONTROL_UP_POS);
				}
				else if (slope < -0.45f && delta_z < -6.f && current_height > AUTOPILOT_MIN_TARGET_HEIGHT_OFF_GROUND)
				{
					setControlFlags(AGENT_CONTROL_FAST_UP | AGENT_CONTROL_UP_NEG);
				}
				else if (slope < -0.002f && delta_z < -0.5f && current_height > AUTOPILOT_MIN_TARGET_HEIGHT_OFF_GROUND)
				{
					setControlFlags(AGENT_CONTROL_UP_NEG);
				}
			}
		}

		//  calculate delta rotation to target heading
		F32 delta_target_heading = angle_between(mFrameAgent.getAtAxis(), mAutoPilotTargetFacing);

		if (xy_distance > slow_distance && yaw < (F_PI / 10.f))
		{
			// walking/flying fast
			setControlFlags(AGENT_CONTROL_FAST_AT | AGENT_CONTROL_AT_POS);
		}
		else if (mAutoPilotTargetDist > mAutoPilotStopDistance)
		{
			// walking/flying slow
			if (at * direction > 0.9f)
			{
				setControlFlags(AGENT_CONTROL_AT_POS);
			}
			else if (at * direction < -0.9f)
			{
				setControlFlags(AGENT_CONTROL_AT_NEG);
			}
		}

		// check to see if we need to keep rotating to target orientation
		if (mAutoPilotTargetDist < mAutoPilotStopDistance)
		{
			setControlFlags(AGENT_CONTROL_STOP);
			if(!mAutoPilotUseRotation || (delta_target_heading < mAutoPilotRotationThreshold))
			{
				stopAutoPilot();
			}
		}
	}
}


//-----------------------------------------------------------------------------
// propagate()
//-----------------------------------------------------------------------------
void LLAgent::propagate(const F32 dt)
{
	// Update UI based on agent motion
	LLFloaterMove *floater_move = LLFloaterMove::getInstance();
	if (floater_move)
	{
		floater_move->mForwardButton   ->setToggleState( mAtKey > 0 || mWalkKey > 0 );
		floater_move->mBackwardButton  ->setToggleState( mAtKey < 0 || mWalkKey < 0 );
		floater_move->mSlideLeftButton ->setToggleState( mLeftKey > 0 );
		floater_move->mSlideRightButton->setToggleState( mLeftKey < 0 );
		floater_move->mTurnLeftButton  ->setToggleState( mYawKey > 0.f );
		floater_move->mTurnRightButton ->setToggleState( mYawKey < 0.f );
		floater_move->mMoveUpButton    ->setToggleState( mUpKey > 0 );
		floater_move->mMoveDownButton  ->setToggleState( mUpKey < 0 );
	}

	// handle rotation based on keyboard levels
	const F32 YAW_RATE = 90.f * DEG_TO_RAD;				// radians per second
	yaw( YAW_RATE * mYawKey * dt );

	const F32 PITCH_RATE = 90.f * DEG_TO_RAD;			// radians per second
	pitch(PITCH_RATE * (F32) mPitchKey * dt);
	
	// handle auto-land behavior
	if (mAvatarObject)
	{
		BOOL in_air = mAvatarObject->mInAir;
		LLVector3 land_vel = getVelocity();
		land_vel.mV[VZ] = 0.f;

		if (!in_air 
			&& mUpKey < 0 
			&& land_vel.magVecSquared() < MAX_VELOCITY_AUTO_LAND_SQUARED
			&& gSavedSettings.getBOOL("AutomaticFly"))
		{
			// land automatically
			setFlying(FALSE);
		}
	}

	// clear keys
	mAtKey = 0;
	mWalkKey = 0;
	mLeftKey = 0;
	mUpKey = 0;
	mYawKey = 0.f;
	mPitchKey = 0;
}

//-----------------------------------------------------------------------------
// updateAgentPosition()
//-----------------------------------------------------------------------------
void LLAgent::updateAgentPosition(const F32 dt, const F32 yaw_radians, const S32 mouse_x, const S32 mouse_y)
{
	propagate(dt);

	// static S32 cameraUpdateCount = 0;

	rotate(yaw_radians, 0, 0, 1);
	
	//
	// Check for water and land collision, set underwater flag
	//

	updateLookAt(mouse_x, mouse_y);
}

//-----------------------------------------------------------------------------
// updateLookAt()
//-----------------------------------------------------------------------------
void LLAgent::updateLookAt(const S32 mouse_x, const S32 mouse_y)
{
	static LLVector3 last_at_axis;


	if ( mAvatarObject.isNull() )
	{
		return;
	}

	LLQuaternion av_inv_rot = ~mAvatarObject->mRoot.getWorldRotation();
	LLVector3 root_at = LLVector3::x_axis * mAvatarObject->mRoot.getWorldRotation();

	if 	((gViewerWindow->getMouseVelocityStat()->getCurrent() < 0.01f) &&
		(root_at * last_at_axis > 0.95f ))
	{
		LLVector3 vel = mAvatarObject->getVelocity();
		if (vel.magVecSquared() > 4.f)
		{
			setLookAt(LOOKAT_TARGET_IDLE, mAvatarObject, vel * av_inv_rot);
		}
		else
		{
			//FIXME: rotate mframeagent by sit object's rotation?
			LLQuaternion look_rotation = mAvatarObject->mIsSitting ? mAvatarObject->getRenderRotation() : mFrameAgent.getQuaternion(); // use camera's current rotation
			LLVector3 look_offset = LLVector3(2.f, 0.f, 0.f) * look_rotation * av_inv_rot;
			setLookAt(LOOKAT_TARGET_IDLE, mAvatarObject, look_offset);
		}
		last_at_axis = root_at;
		return;
	}

	last_at_axis = root_at;
	
	if (CAMERA_MODE_CUSTOMIZE_AVATAR == getCameraMode())
	{
		setLookAt(LOOKAT_TARGET_NONE, mAvatarObject, LLVector3(-2.f, 0.f, 0.f));	
	}
	else
	{
		// Move head based on cursor position
		ELookAtType lookAtType = LOOKAT_TARGET_NONE;
		LLVector3 headLookAxis;
		LLCoordFrame frameCamera = *((LLCoordFrame*)gCamera);

		F32 x_from_center = mouse_x_from_center( mouse_x );	// range from -0.5 to 0.5
		F32 y_from_center = mouse_y_from_center( mouse_y );	// range from -0.5 to 0.5

		if (cameraMouselook())
		{
			lookAtType = LOOKAT_TARGET_MOUSELOOK;
		}
		else if (cameraThirdPerson())
		{
			frameCamera.yaw( - x_from_center * gSavedSettings.getF32("YawFromMousePosition") * DEG_TO_RAD);
			frameCamera.pitch( - y_from_center * gSavedSettings.getF32("PitchFromMousePosition") * DEG_TO_RAD);
			lookAtType = LOOKAT_TARGET_FREELOOK;
		}

		headLookAxis = frameCamera.getAtAxis();
		// RN: we use world-space offset for mouselook and freelook
		//headLookAxis = headLookAxis * av_inv_rot;
		setLookAt(lookAtType, mAvatarObject, headLookAxis);
	}
}

// friends and operators

std::ostream& operator<<(std::ostream &s, const LLAgent &agent)
{
	// This is unfinished, but might never be used. 
	// We'll just leave it for now; we can always delete it.
	s << " { "
	  << "  Frame = " << agent.mFrameAgent << "\n"
	  << " }";
	return s;
}


// ------------------- Beginning of legacy LLCamera hack ----------------------
// This section is included for legacy LLCamera support until
// it is no longer needed.  Some legacy code must exist in 
// non-legacy functions, and is labeled with "// legacy" comments.

//-----------------------------------------------------------------------------
// setAvatarObject()
//-----------------------------------------------------------------------------
void LLAgent::setAvatarObject(LLVOAvatar *avatar)			
{ 
	mAvatarObject = avatar;

	if (!avatar)
	{
		llinfos << "Setting LLAgent::mAvatarObject to NULL" << llendl;
		return;
	}

	if (!mLookAt)
	{
		mLookAt = (LLHUDEffectLookAt *)gHUDManager->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_LOOKAT);
	}
	if (!mPointAt)
	{
		mPointAt = (LLHUDEffectPointAt *)gHUDManager->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_POINTAT);
	}
	
	if (!mLookAt.isNull())
	{
		mLookAt->setSourceObject(avatar);
	}
	if (!mPointAt.isNull())
	{
		mPointAt->setSourceObject(avatar);
	}

	sendAgentWearablesRequest();
}

// TRUE if your own avatar needs to be rendered.  Usually only
// in third person and build.
//-----------------------------------------------------------------------------
// needsRenderAvatar()
//-----------------------------------------------------------------------------
BOOL LLAgent::needsRenderAvatar()
{
	if (cameraMouselook() && !LLVOAvatar::sVisibleInFirstPerson)
	{
		return FALSE;
	}

	return mShowAvatar && mGenderChosen;
}

// TRUE if we need to render your own avatar's head.
BOOL LLAgent::needsRenderHead()
{
	return mShowAvatar && !cameraMouselook();
}

//-----------------------------------------------------------------------------
// startTyping()
//-----------------------------------------------------------------------------
void LLAgent::startTyping()
{
	mTypingTimer.reset();

	if (getRenderState() & AGENT_STATE_TYPING)
	{
		// already typing, don't trigger a different animation
		return;
	}
	setRenderState(AGENT_STATE_TYPING);

	if (mChatTimer.getElapsedTimeF32() < 2.f)
	{
		LLViewerObject* chatter = gObjectList.findObject(mLastChatterID);
		if (chatter && chatter->isAvatar())
		{
			gAgent.setLookAt(LOOKAT_TARGET_RESPOND, chatter, LLVector3::zero);
		}
	}

	if (gSavedSettings.getBOOL("PlayTypingAnim"))
	{
		sendAnimationRequest(ANIM_AGENT_TYPE, ANIM_REQUEST_START);
	}
	gChatBar->sendChatFromViewer("", CHAT_TYPE_START, FALSE);
}

//-----------------------------------------------------------------------------
// stopTyping()
//-----------------------------------------------------------------------------
void LLAgent::stopTyping()
{
	if (mRenderState & AGENT_STATE_TYPING)
	{
		clearRenderState(AGENT_STATE_TYPING);
		sendAnimationRequest(ANIM_AGENT_TYPE, ANIM_REQUEST_STOP);
		gChatBar->sendChatFromViewer("", CHAT_TYPE_STOP, FALSE);
	}
}

//-----------------------------------------------------------------------------
// setRenderState()
//-----------------------------------------------------------------------------
void LLAgent::setRenderState(U8 newstate)
{
	mRenderState |= newstate;
}

//-----------------------------------------------------------------------------
// clearRenderState()
//-----------------------------------------------------------------------------
void LLAgent::clearRenderState(U8 clearstate)
{
	mRenderState &= ~clearstate;
}


//-----------------------------------------------------------------------------
// getRenderState()
//-----------------------------------------------------------------------------
U8 LLAgent::getRenderState()
{
	if (gNoRender || gToolMgr == NULL || gSelectMgr == NULL || gKeyboard == NULL)
	{
		return 0;
	}

	// FIXME: don't do stuff in a getter!  This is infinite loop city!
	if ((mTypingTimer.getElapsedTimeF32() > TYPING_TIMEOUT_SECS) 
		&& (mRenderState & AGENT_STATE_TYPING))
	{
		stopTyping();
	}
	
	if ((!gSelectMgr->isEmpty() && gSelectMgr->shouldShowSelection())
		|| gToolMgr->getCurrentTool( gKeyboard->currentMask(TRUE) )->isEditing() )
	{
		setRenderState(AGENT_STATE_EDITING);
	}
	else
	{
		clearRenderState(AGENT_STATE_EDITING);
	}

	return mRenderState;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------

static const LLFloaterView::skip_list_t& get_skip_list()
{
	static LLFloaterView::skip_list_t skip_list;
	skip_list.insert(gFloaterMap);
	return skip_list;
}

//-----------------------------------------------------------------------------
// endAnimationUpdateUI()
//-----------------------------------------------------------------------------
void LLAgent::endAnimationUpdateUI()
{
	if (mCameraMode == mLastCameraMode)
	{
		// We're already done endAnimationUpdateUI for this transition.
		return;
	}

	// clean up UI from mode we're leaving
	if ( mLastCameraMode == CAMERA_MODE_MOUSELOOK )
	{
		// show mouse cursor
		gViewerWindow->showCursor();
		// show menus
		gMenuBarView->setVisible(TRUE);
		gStatusBar->setVisibleForMouselook(true);

		gCurrentToolset = gBasicToolset;
		gToolMgr->useSelectedTool( gCurrentToolset );

		// Only pop if we have pushed...
		if (TRUE == mViewsPushed)
		{
			mViewsPushed = FALSE;
			gFloaterView->popVisibleAll(get_skip_list());
		}

		gAgent.setLookAt(LOOKAT_TARGET_CLEAR);
		if( gMorphView )
		{
			gMorphView->setVisible( FALSE );
		}

		// Disable mouselook-specific animations
		if (mAvatarObject)
		{
			if( mAvatarObject->isAnyAnimationSignaled(AGENT_GUN_AIM_ANIMS, NUM_AGENT_GUN_AIM_ANIMS) )
			{
				if (mAvatarObject->mSignaledAnimations.find(ANIM_AGENT_AIM_RIFLE_R) != mAvatarObject->mSignaledAnimations.end())
				{
					sendAnimationRequest(ANIM_AGENT_AIM_RIFLE_R, ANIM_REQUEST_STOP);
					sendAnimationRequest(ANIM_AGENT_HOLD_RIFLE_R, ANIM_REQUEST_START);
				}
				if (mAvatarObject->mSignaledAnimations.find(ANIM_AGENT_AIM_HANDGUN_R) != mAvatarObject->mSignaledAnimations.end())
				{
					sendAnimationRequest(ANIM_AGENT_AIM_HANDGUN_R, ANIM_REQUEST_STOP);
					sendAnimationRequest(ANIM_AGENT_HOLD_HANDGUN_R, ANIM_REQUEST_START);
				}
				if (mAvatarObject->mSignaledAnimations.find(ANIM_AGENT_AIM_BAZOOKA_R) != mAvatarObject->mSignaledAnimations.end())
				{
					sendAnimationRequest(ANIM_AGENT_AIM_BAZOOKA_R, ANIM_REQUEST_STOP);
					sendAnimationRequest(ANIM_AGENT_HOLD_BAZOOKA_R, ANIM_REQUEST_START);
				}
				if (mAvatarObject->mSignaledAnimations.find(ANIM_AGENT_AIM_BOW_L) != mAvatarObject->mSignaledAnimations.end())
				{
					sendAnimationRequest(ANIM_AGENT_AIM_BOW_L, ANIM_REQUEST_STOP);
					sendAnimationRequest(ANIM_AGENT_HOLD_BOW_L, ANIM_REQUEST_START);
				}
			}
		}
	}
	else
	if(	mLastCameraMode == CAMERA_MODE_CUSTOMIZE_AVATAR )
	{
		// make sure we ask to save changes

		gCurrentToolset = gBasicToolset;
		gToolMgr->useSelectedTool( gCurrentToolset );

		// HACK: If we're quitting, and we were in customize avatar, don't
		// let the mini-map go visible again. JC
		if (!gQuitRequested)
		{
			gFloaterMap->popVisible();
		}

		if( gMorphView )
		{
			gMorphView->setVisible( FALSE );
		}

		if (mAvatarObject)
		{
			sendAnimationRequest(ANIM_AGENT_CUSTOMIZE, ANIM_REQUEST_STOP);
			sendAnimationRequest(ANIM_AGENT_CUSTOMIZE_DONE, ANIM_REQUEST_START);
		}
		setLookAt(LOOKAT_TARGET_CLEAR);
	}

	//---------------------------------------------------------------------
	// Set up UI for mode we're entering
	//---------------------------------------------------------------------
	if (mCameraMode == CAMERA_MODE_MOUSELOOK)
	{
		// hide menus
		gMenuBarView->setVisible(FALSE);
		gStatusBar->setVisibleForMouselook(false);

		// clear out camera lag effect
		mCameraLag.clearVec();

		// JC - Added for always chat in third person option
		gFocusMgr.setKeyboardFocus(NULL, NULL);

		gCurrentToolset = gMouselookToolset;
		gToolMgr->useSelectedTool( gMouselookToolset );

		mViewsPushed = TRUE;

		gFloaterView->pushVisibleAll(FALSE, get_skip_list());

		if( gMorphView )
		{
			gMorphView->setVisible(FALSE);
		}

		gIMView->setFloaterOpen( FALSE );
		gConsole->setVisible( TRUE );

		if (mAvatarObject)
		{
			// Trigger mouselook-specific animations
			if( mAvatarObject->isAnyAnimationSignaled(AGENT_GUN_HOLD_ANIMS, NUM_AGENT_GUN_HOLD_ANIMS) )
			{
				if (mAvatarObject->mSignaledAnimations.find(ANIM_AGENT_HOLD_RIFLE_R) != mAvatarObject->mSignaledAnimations.end())
				{
					sendAnimationRequest(ANIM_AGENT_HOLD_RIFLE_R, ANIM_REQUEST_STOP);
					sendAnimationRequest(ANIM_AGENT_AIM_RIFLE_R, ANIM_REQUEST_START);
				}
				if (mAvatarObject->mSignaledAnimations.find(ANIM_AGENT_HOLD_HANDGUN_R) != mAvatarObject->mSignaledAnimations.end())
				{
					sendAnimationRequest(ANIM_AGENT_HOLD_HANDGUN_R, ANIM_REQUEST_STOP);
					sendAnimationRequest(ANIM_AGENT_AIM_HANDGUN_R, ANIM_REQUEST_START);
				}
				if (mAvatarObject->mSignaledAnimations.find(ANIM_AGENT_HOLD_BAZOOKA_R) != mAvatarObject->mSignaledAnimations.end())
				{
					sendAnimationRequest(ANIM_AGENT_HOLD_BAZOOKA_R, ANIM_REQUEST_STOP);
					sendAnimationRequest(ANIM_AGENT_AIM_BAZOOKA_R, ANIM_REQUEST_START);
				}
				if (mAvatarObject->mSignaledAnimations.find(ANIM_AGENT_HOLD_BOW_L) != mAvatarObject->mSignaledAnimations.end())
				{
					sendAnimationRequest(ANIM_AGENT_HOLD_BOW_L, ANIM_REQUEST_STOP);
					sendAnimationRequest(ANIM_AGENT_AIM_BOW_L, ANIM_REQUEST_START);
				}
			}
			if (mAvatarObject->getParent())
			{
				LLVector3 at_axis = gCamera->getAtAxis();
				LLViewerObject* root_object = (LLViewerObject*)mAvatarObject->getRoot();
				if (root_object->flagCameraDecoupled())
				{
					resetAxes(at_axis);
				}
				else
				{
					resetAxes(at_axis * ~((LLViewerObject*)mAvatarObject->getParent())->getRenderRotation());
				}
			}
		}

	}
	else if (mCameraMode == CAMERA_MODE_CUSTOMIZE_AVATAR)
	{
		gCurrentToolset = gFaceEditToolset;
		gToolMgr->useSelectedTool( gFaceEditToolset );

		gFloaterMap->pushVisible(FALSE);
		/*
		LLView *view;
		for (view = gFloaterView->getFirstChild(); view; view = gFloaterView->getNextChild())
		{
			view->pushVisible(FALSE);
		}
		*/

		if( gMorphView )
		{
			gMorphView->setVisible( TRUE );
		}

		// freeze avatar
		if (mAvatarObject)
		{
			mPauseRequest = mAvatarObject->requestPause();
		}
	}

	if (getAvatarObject())
	{
		getAvatarObject()->updateAttachmentVisibility(mCameraMode);
	}

	gFloaterTools->dirty();

	// Don't let this be called more than once if the camera
	// mode hasn't changed.  --JC
	mLastCameraMode = mCameraMode;
}


//-----------------------------------------------------------------------------
// updateCamera()
//-----------------------------------------------------------------------------
void LLAgent::updateCamera()
{
	//Ventrella - changed camera_skyward to the new global "mCameraUpVector"
	mCameraUpVector = LLVector3::z_axis;
	//LLVector3	camera_skyward(0.f, 0.f, 1.f);
	//end Ventrella

	U32 camera_mode = mCameraAnimating ? mLastCameraMode : mCameraMode;

	validateFocusObject();

	if (!mAvatarObject.isNull() && 
		mAvatarObject->mIsSitting &&
		camera_mode == CAMERA_MODE_MOUSELOOK)
	{
		//Ventrella
		//changed camera_skyward to the new global "mCameraUpVector"
		mCameraUpVector = mCameraUpVector * mAvatarObject->getRenderRotation();
		//end Ventrella
	}

	if (cameraThirdPerson() && mFocusOnAvatar && LLFollowCamMgr::getActiveFollowCamParams())
	{
		changeCameraToFollow();
	}

	//Ventrella
	//NOTE - this needs to be integrated into a general upVector system here within llAgent. 
	if ( camera_mode == CAMERA_MODE_FOLLOW && mFocusOnAvatar )
	{
		mCameraUpVector = mFollowCam.getUpVector();
	}
	//end Ventrella

	if (mSitCameraEnabled)
	{
		if (mSitCameraReferenceObject->isDead())
		{
			setSitCamera(LLUUID::null);
		}
	}

	// Update UI with our camera inputs
	if (gFloaterCamera)
	{
		gFloaterCamera->mRotate->setToggleState(
			mOrbitRightKey > 0.f,	// left
			mOrbitUpKey > 0.f,		// top
			mOrbitLeftKey > 0.f,	// right
			mOrbitDownKey > 0.f);	// bottom

		gFloaterCamera->mZoom->setToggleState( 
			mOrbitInKey > 0.f,		// top
			mOrbitOutKey > 0.f);	// bottom

		gFloaterCamera->mTrack->setToggleState(
			mPanLeftKey > 0.f,		// left
			mPanUpKey > 0.f,		// top
			mPanRightKey > 0.f,		// right
			mPanDownKey > 0.f);		// bottom
	}

	// Handle camera movement based on keyboard.
	const F32 ORBIT_OVER_RATE = 90.f * DEG_TO_RAD;			// radians per second
	const F32 ORBIT_AROUND_RATE = 90.f * DEG_TO_RAD;		// radians per second
	const F32 PAN_RATE = 5.f;								// meters per second

	if( mOrbitUpKey || mOrbitDownKey )
	{
		F32 input_rate = mOrbitUpKey - mOrbitDownKey;
		cameraOrbitOver( input_rate * ORBIT_OVER_RATE / gFPSClamped );
	}

	if( mOrbitLeftKey || mOrbitRightKey)
	{
		F32 input_rate = mOrbitLeftKey - mOrbitRightKey;
		cameraOrbitAround( input_rate * ORBIT_AROUND_RATE / gFPSClamped );
	}

	if( mOrbitInKey || mOrbitOutKey )
	{
		F32 input_rate = mOrbitInKey - mOrbitOutKey;
		
		LLVector3d to_focus = gAgent.getPosGlobalFromAgent(gCamera->getOrigin()) - calcFocusPositionTargetGlobal();
		F32 distance_to_focus = (F32)to_focus.magVec();
		// Move at distance (in meters) meters per second
		cameraOrbitIn( input_rate * distance_to_focus / gFPSClamped );
	}

	if( mPanInKey || mPanOutKey )
	{
		F32 input_rate = mPanInKey - mPanOutKey;
		cameraPanIn( input_rate * PAN_RATE / gFPSClamped );
	}

	if( mPanRightKey || mPanLeftKey )
	{
		F32 input_rate = mPanRightKey - mPanLeftKey;
		cameraPanLeft( input_rate * -PAN_RATE / gFPSClamped );
	}

	if( mPanUpKey || mPanDownKey )
	{
		F32 input_rate = mPanUpKey - mPanDownKey;
		cameraPanUp( input_rate * PAN_RATE / gFPSClamped );
	}

	// Clear camera keyboard keys.
	mOrbitLeftKey		= 0.f;
	mOrbitRightKey		= 0.f;
	mOrbitUpKey			= 0.f;
	mOrbitDownKey		= 0.f;
	mOrbitInKey			= 0.f;
	mOrbitOutKey		= 0.f;

	mPanRightKey		= 0.f;
	mPanLeftKey			= 0.f;
	mPanUpKey			= 0.f;
	mPanDownKey			= 0.f;
	mPanInKey			= 0.f;
	mPanOutKey			= 0.f;

	// lerp camera focus offset
	mCameraFocusOffset = lerp(mCameraFocusOffset, mCameraFocusOffsetTarget, LLCriticalDamp::getInterpolant(CAMERA_FOCUS_HALF_LIFE));

	//Ventrella
	if ( mCameraMode == CAMERA_MODE_FOLLOW )
	{
		if ( !mAvatarObject.isNull() )
		{
			//--------------------------------------------------------------------------------
			// this is where the avatar's position and rotation are given to followCam, and 
			// where it is updated. All three of its attributes are updated: (1) position, 
			// (2) focus, and (3) upvector. They can then be queried elsewhere in llAgent.
			//--------------------------------------------------------------------------------
			//FIXME: use combined rotation of frameagent and sit object
			LLQuaternion avatarRotationForFollowCam = mAvatarObject->mIsSitting ? mAvatarObject->getRenderRotation() : mFrameAgent.getQuaternion();

			LLFollowCamParams* current_cam = LLFollowCamMgr::getActiveFollowCamParams();
			if (current_cam)
			{
				mFollowCam.copyParams(*current_cam);
				mFollowCam.setSubjectPositionAndRotation( mAvatarObject->getRenderPosition(), avatarRotationForFollowCam );
				mFollowCam.update();
			}
			else
			{
				changeCameraToThirdPerson(TRUE);
			}
		}
	}
	// end Ventrella

	BOOL hit_limit;
	LLVector3d camera_pos_global;
	LLVector3d camera_target_global = calcCameraPositionTargetGlobal(&hit_limit);
	mCameraVirtualPositionAgent = getPosAgentFromGlobal(camera_target_global);
	LLVector3d focus_target_global = calcFocusPositionTargetGlobal();

	// perform field of view correction
	mCameraFOVZoomFactor = calcCameraFOVZoomFactor();
	camera_target_global = focus_target_global + (camera_target_global - focus_target_global) * (1.f + mCameraFOVZoomFactor);

	// do alpha fade on focus object
	F32 fade_increment = mFocusObjectFadeTimer.getElapsedTimeAndResetF32();

	if (mFocusObject.notNull() && !mFocusObject->isAttachment() && mFocusObject->mDrawable.notNull())
	{
		F32 increment = fade_increment;
		if (mFocusObjectDist < -0.2f)
		{
			increment *= -1.f;
		}

		if (mFocusObject->getVObjRadius() > MIN_RADIUS_ALPHA_SIZZLE)
		{
			S32 num_faces = mFocusObject->mDrawable->getNumFaces();
			for (S32 i = 0; i < num_faces; i++)
			{
				LLFace* facep = mFocusObject->mDrawable->getFace(i);
				F32 fade = facep->mAlphaFade;
				fade = llclamp(fade + increment, 0.f, 1.f);
				facep->mAlphaFade = fade;
			}
		}
	}

	// do alpha fade in on fade objects
	std::set< LLPointer<LLViewerObject> >::iterator fade_object_it;
	for (fade_object_it = mFadeObjects.begin(); fade_object_it != mFadeObjects.end(); )
	{
		LLViewerObject* fade_object = *fade_object_it;
		if (fade_object->isDead())
		{
			// remove from list
			mFadeObjects.erase(fade_object_it++);
		}
		else
		{
			LLDrawable* drawablep = fade_object->mDrawable;
			if (drawablep && fade_object->getVObjRadius() > MIN_RADIUS_ALPHA_SIZZLE)
			{
				S32 num_faces = drawablep->getNumFaces();
				BOOL fade_done = TRUE;
				for (S32 i = 0; i < num_faces; i++)
				{
					LLFace* facep = drawablep->getFace(i);
					F32 fade = facep->mAlphaFade;
					fade = llclamp(fade - fade_increment, 0.f, 1.f);
					facep->mAlphaFade = fade;
					if (fade > 0.f)
					{
						fade_done = FALSE;
					}
				}
				if (fade_done)
				{
					mFadeObjects.erase(fade_object_it++);
				}
				else
				{
					fade_object_it++;
				}
			}
			else
			{
				fade_object_it++;
			}
		}
	}

	mShowAvatar = TRUE; // can see avatar by default

	// Adjust position for animation
	if (mCameraAnimating)
	{
		F32 time = mAnimationTimer.getElapsedTimeF32();

		// yet another instance of critically damped motion, hooray!
		// F32 fraction_of_animation = 1.f - pow(2.f, -time / CAMERA_ZOOM_HALF_LIFE);

		// linear interpolation
		F32 fraction_of_animation = time / mAnimationDuration;

		BOOL isfirstPerson = mCameraMode == CAMERA_MODE_MOUSELOOK;
		BOOL wasfirstPerson = mLastCameraMode == CAMERA_MODE_MOUSELOOK;
		F32 fraction_animation_to_skip;

		if (mAnimationCameraStartGlobal == camera_target_global)
		{
			fraction_animation_to_skip = 0.f;
		}
		else
		{
			LLVector3d cam_delta = mAnimationCameraStartGlobal - camera_target_global;
			fraction_animation_to_skip = HEAD_BUFFER_SIZE / (F32)cam_delta.magVec();
		}
		F32 animation_start_fraction = (wasfirstPerson) ? fraction_animation_to_skip : 0.f;
		F32 animation_finish_fraction =  (isfirstPerson) ? (1.f - fraction_animation_to_skip) : 1.f;
	
		if (fraction_of_animation < animation_finish_fraction)
		{
			if (fraction_of_animation < animation_start_fraction || fraction_of_animation > animation_finish_fraction )
			{
				mShowAvatar = FALSE;
			}

			// ...adjust position for animation
			camera_pos_global = lerp(mAnimationCameraStartGlobal, camera_target_global, fraction_of_animation);
			mFocusGlobal = lerp(mAnimationFocusStartGlobal, focus_target_global, fraction_of_animation);
		}
		else
		{
			// ...animation complete
			mCameraAnimating = FALSE;

			camera_pos_global = camera_target_global;
			mFocusGlobal = focus_target_global;

			endAnimationUpdateUI();
			mShowAvatar = TRUE;
		}

		if (getAvatarObject() && mCameraMode != CAMERA_MODE_MOUSELOOK)
		{
			getAvatarObject()->updateAttachmentVisibility(mCameraMode);
		}
	}
	else
	{
		camera_pos_global = camera_target_global;
		mFocusGlobal = focus_target_global;
		mShowAvatar = TRUE;
	}

	mCameraCurrentFOVZoomFactor = lerp(mCameraCurrentFOVZoomFactor, mCameraFOVZoomFactor, LLCriticalDamp::getInterpolant(FOV_ZOOM_HALF_LIFE));

//	llinfos << "Current FOV Zoom: " << mCameraCurrentFOVZoomFactor << " Target FOV Zoom: " << mCameraFOVZoomFactor << " Object penetration: " << mFocusObjectDist << llendl;

	F32 ui_offset = 0.f;
	if( CAMERA_MODE_CUSTOMIZE_AVATAR == mCameraMode ) 
	{
		ui_offset = calcCustomizeAvatarUIOffset( camera_pos_global );
	}


	LLVector3 focus_agent = getPosAgentFromGlobal(mFocusGlobal);
	
	mCameraPositionAgent	= getPosAgentFromGlobal(camera_pos_global);

	// Move the camera

	//Ventrella
	gCamera->updateCameraLocation(mCameraPositionAgent, mCameraUpVector, focus_agent);
	//gCamera->updateCameraLocation(mCameraPositionAgent, camera_skyward, focus_agent);
	//end Ventrella

	//RN: translate UI offset after camera is oriented properly
	gCamera->translate(gCamera->getLeftAxis() * ui_offset);
	
	// Change FOV
	gCamera->setView(gCamera->getDefaultFOV() / (1.f + mCameraCurrentFOVZoomFactor));

	// follow camera when in customize mode
	if (cameraCustomizeAvatar())	
	{
		setLookAt(LOOKAT_TARGET_FOCUS, NULL, mCameraPositionAgent);
	}

	// update the travel distance stat
	// this isn't directly related to the camera
	// but this seemed like the best place to do this
	LLVector3d global_pos = getPositionGlobal(); 
	if (! mLastPositionGlobal.isExactlyZero())
	{
		LLVector3d delta = global_pos - mLastPositionGlobal;
		mDistanceTraveled += delta.magVec();
	}
	mLastPositionGlobal = global_pos;

	if (LLVOAvatar::sVisibleInFirstPerson && mAvatarObject.notNull() && !mAvatarObject->mIsSitting && cameraMouselook())
	{
		LLVector3 head_pos = mAvatarObject->mHeadp->getWorldPosition() + 
			LLVector3(0.08f, 0.f, 0.05f) * mAvatarObject->mHeadp->getWorldRotation() + 
			LLVector3(0.1f, 0.f, 0.f) * mAvatarObject->mPelvisp->getWorldRotation();
		LLVector3 diff = mCameraPositionAgent - head_pos;
		diff = diff * ~mAvatarObject->mRoot.getWorldRotation();

		LLJoint* torso_joint = mAvatarObject->mTorsop;
		LLJoint* chest_joint = mAvatarObject->mChestp;
		LLVector3 torso_scale = torso_joint->getScale();
		LLVector3 chest_scale = chest_joint->getScale();

		// shorten avatar skeleton to avoid foot interpenetration
		if (!mAvatarObject->mInAir)
		{
			LLVector3 chest_offset = LLVector3(0.f, 0.f, chest_joint->getPosition().mV[VZ]) * torso_joint->getWorldRotation();
			F32 z_compensate = llclamp(-diff.mV[VZ], -0.2f, 1.f);
			F32 scale_factor = llclamp(1.f - ((z_compensate * 0.5f) / chest_offset.mV[VZ]), 0.5f, 1.2f);
			torso_joint->setScale(LLVector3(1.f, 1.f, scale_factor));

			LLJoint* neck_joint = mAvatarObject->mNeckp;
			LLVector3 neck_offset = LLVector3(0.f, 0.f, neck_joint->getPosition().mV[VZ]) * chest_joint->getWorldRotation();
			scale_factor = llclamp(1.f - ((z_compensate * 0.5f) / neck_offset.mV[VZ]), 0.5f, 1.2f);
			chest_joint->setScale(LLVector3(1.f, 1.f, scale_factor));
			diff.mV[VZ] = 0.f;
		}

		mAvatarObject->mPelvisp->setPosition(mAvatarObject->mPelvisp->getPosition() + diff);

		mAvatarObject->mRoot.updateWorldMatrixChildren();

		for(LLViewerJointAttachment *attachment = mAvatarObject->mAttachmentPoints.getFirstData();
			attachment;
			attachment = mAvatarObject->mAttachmentPoints.getNextData())
		{
			LLViewerObject *attached_object = attachment->getObject(0);
			if (attached_object && !attached_object->isDead() && attached_object->mDrawable.notNull())
			{
				// clear any existing "early" movements of attachment
				attached_object->mDrawable->clearState(LLDrawable::EARLY_MOVE);
				gPipeline.updateMoveNormalAsync(attached_object->mDrawable);
				attached_object->updateText();
			}
		}

		torso_joint->setScale(torso_scale);
		chest_joint->setScale(chest_scale);
	}
}

void LLAgent::updateFocusOffset()
{
	validateFocusObject();
	if (mFocusObject.notNull())
	{
		LLVector3d obj_pos = getPosGlobalFromAgent(mFocusObject->getRenderPosition());
		mFocusObjectOffset.setVec(mFocusTargetGlobal - obj_pos);
	}
}

void LLAgent::validateFocusObject()
{
	if (mFocusObject.notNull() && 
		(mFocusObject->isDead()))
	{
		mFocusObjectOffset.clearVec();
		clearFocusObject();
		mCameraFOVZoomFactor = 0.f;
	}
}

//-----------------------------------------------------------------------------
// calcCustomizeAvatarUIOffset()
//-----------------------------------------------------------------------------
F32 LLAgent::calcCustomizeAvatarUIOffset( const LLVector3d& camera_pos_global )
{
	F32 ui_offset = 0.f;

	if( gFloaterCustomize )
	{
		const LLRect& rect = gFloaterCustomize->getRect();

		// Move the camera so that the avatar isn't covered up by this floater.
		F32 fraction_of_fov = 0.5f - (0.5f * (1.f - llmin(1.f, ((F32)rect.getWidth() / (F32)gViewerWindow->getWindowWidth()))));
		F32 apparent_angle = fraction_of_fov * gCamera->getView() * gCamera->getAspect();  // radians
		F32 offset = tan(apparent_angle);

		if( rect.mLeft < (gViewerWindow->getWindowWidth() - rect.mRight) )
		{
			// Move the avatar to the right (camera to the left)
			ui_offset = offset;
		}
		else
		{
			// Move the avatar to the left (camera to the right)
			ui_offset = -offset;
		}
	}
	F32 range = (F32)dist_vec(camera_pos_global, gAgent.getFocusGlobal());
	mUIOffset = lerp(mUIOffset, ui_offset, LLCriticalDamp::getInterpolant(0.05f));
	return mUIOffset * range;
}

//-----------------------------------------------------------------------------
// calcFocusPositionTargetGlobal()
//-----------------------------------------------------------------------------
LLVector3d LLAgent::calcFocusPositionTargetGlobal()
{
	if (mFocusObject.notNull() && mFocusObject->isDead())
	{
		clearFocusObject();
	}

	// Ventrella
	if ( mCameraMode == CAMERA_MODE_FOLLOW && mFocusOnAvatar )
	{
		mFocusTargetGlobal = gAgent.getPosGlobalFromAgent(mFollowCam.getSimulatedFocus());
		return mFocusTargetGlobal;
	}// End Ventrella 
	else if (mCameraMode == CAMERA_MODE_MOUSELOOK)
	{
		LLVector3d at_axis(1.0, 0.0, 0.0);
		LLQuaternion agent_rot = mFrameAgent.getQuaternion();
		if (!mAvatarObject.isNull() && mAvatarObject->getParent())
		{
			LLViewerObject* root_object = (LLViewerObject*)mAvatarObject->getRoot();
			if (!root_object->flagCameraDecoupled())
			{
				agent_rot *= ((LLViewerObject*)(mAvatarObject->getParent()))->getRenderRotation();
			}
		}
		at_axis = at_axis * agent_rot;
		mFocusTargetGlobal = calcCameraPositionTargetGlobal() + at_axis;
		return mFocusTargetGlobal;
	}
	else if (mCameraMode == CAMERA_MODE_CUSTOMIZE_AVATAR)
	{
		return mFocusTargetGlobal;
	}
	else if (!mFocusOnAvatar)
	{
		if (mFocusObject.notNull() && !mFocusObject->isDead() && mFocusObject->mDrawable.notNull())
		{
			LLDrawable* drawablep = mFocusObject->mDrawable;
			
			if (mTrackFocusObject && drawablep && drawablep->isActive())
			{
				if (mFocusObject->isSelected())
				{
					gPipeline.updateMoveNormalAsync(drawablep);
				}
				else
				{
					if (drawablep->isState(LLDrawable::MOVE_UNDAMPED))
					{
						gPipeline.updateMoveNormalAsync(drawablep);
					}
					else
					{
						gPipeline.updateMoveDampedAsync(drawablep);
					}
				}
			}
			// if not tracking object, update offset based on new object position
			else
			{
				updateFocusOffset();
			}
			LLVector3 focus_agent = mFocusObject->getRenderPosition() + mFocusObjectOffset;
			mFocusTargetGlobal.setVec(getPosGlobalFromAgent(focus_agent));
			//FIXME: get camera pointat behavior working
			//if (mTrackFocusObject)
			//{
			//	mCameraFocusOffset = gAgent.getPosGlobalFromAgent(gCamera->getOrigin()) - mFocusTargetGlobal;
			//}
		}
		return mFocusTargetGlobal;
	}
	else if (mSitCameraEnabled && mAvatarObject.notNull() && mAvatarObject->mIsSitting && mSitCameraReferenceObject.notNull())
	{
		// sit camera
		LLVector3 object_pos = mSitCameraReferenceObject->getRenderPosition();
		LLQuaternion object_rot = mSitCameraReferenceObject->getRenderRotation();

		LLVector3 target_pos = object_pos + (mSitCameraFocus * object_rot);
		return getPosGlobalFromAgent(target_pos);
	}
	else
	{
		// ...offset from avatar
		LLVector3d focus_offset;
		focus_offset.setVec(gSavedSettings.getVector3("FocusOffsetDefault"));

		LLQuaternion agent_rot = mFrameAgent.getQuaternion();
		if (!mAvatarObject.isNull() && mAvatarObject->getParent())
		{
			agent_rot *= ((LLViewerObject*)(mAvatarObject->getParent()))->getRenderRotation();
		}

		focus_offset = focus_offset * agent_rot;

		return getPositionGlobal() + focus_offset;
	}
}

void LLAgent::setupSitCamera()
{
	// agent frame entering this function is in world coordinates
	if (mAvatarObject.notNull() && mAvatarObject->getParent())
	{
		LLQuaternion parent_rot = ((LLViewerObject*)mAvatarObject->getParent())->getRenderRotation();
		// slam agent coordinate frame to proper parent local version
		LLVector3 at_axis = mFrameAgent.getAtAxis();
		at_axis.mV[VZ] = 0.f;
		at_axis.normVec();
		resetAxes(at_axis * ~parent_rot);
	}
}

//-----------------------------------------------------------------------------
// getCameraPositionAgent()
//-----------------------------------------------------------------------------
const LLVector3 &LLAgent::getCameraPositionAgent() const
{
	return gCamera->getOrigin();
}

//-----------------------------------------------------------------------------
// getCameraPositionGlobal()
//-----------------------------------------------------------------------------
LLVector3d LLAgent::getCameraPositionGlobal() const
{
	if (gCamera)
	{
		return getPosGlobalFromAgent(gCamera->getOrigin());
	}
	else
	{
		return (LLVector3d::zero);
	}
}

//-----------------------------------------------------------------------------
// calcCameraFOVZoomFactor()
//-----------------------------------------------------------------------------
F32	LLAgent::calcCameraFOVZoomFactor()
{
	LLVector3 camera_offset_dir;
	camera_offset_dir.setVec(mCameraFocusOffset);

	if (mCameraMode == CAMERA_MODE_MOUSELOOK)
	{
		return 0.f;
	}
	else if (mFocusObject.notNull() && !mFocusObject->isAvatar())
	{
		// don't FOV zoom on mostly transparent objects
		LLVector3 focus_offset = mFocusObjectOffset;
		F32 obj_min_dist = 0.f;
		calcCameraMinDistance(obj_min_dist);
		F32 current_distance = llmax(0.001f, camera_offset_dir.magVec());

		mFocusObjectDist = obj_min_dist - current_distance;

		F32 new_fov_zoom = llclamp(mFocusObjectDist / current_distance, 0.f, 1000.f);
		return new_fov_zoom;
	}
	else // focusing on land or avatar
	{
		// keep old field of view until user changes focus explicitly
		return mCameraFOVZoomFactor;
		//return 0.f;
	}
}

//-----------------------------------------------------------------------------
// calcCameraPositionTargetGlobal()
//-----------------------------------------------------------------------------
LLVector3d LLAgent::calcCameraPositionTargetGlobal(BOOL *hit_limit)
{
	// Compute base camera position and look-at points.
	F32			camera_land_height;
	LLVector3d	frame_center_global = mAvatarObject.isNull() ? getPositionGlobal() 
															 : getPosGlobalFromAgent(mAvatarObject->mRoot.getWorldPosition());
		
	LLVector3   upAxis = getUpAxis();
	BOOL		isConstrained = FALSE;
	LLVector3d	head_offset;
	head_offset.setVec(mThirdPersonHeadOffset);

	LLVector3d camera_position_global;

	// Ventrella 
	if ( mCameraMode == CAMERA_MODE_FOLLOW && mFocusOnAvatar )
	{
		camera_position_global = gAgent.getPosGlobalFromAgent(mFollowCam.getSimulatedPosition());
	}// End Ventrella
	else if (mCameraMode == CAMERA_MODE_MOUSELOOK)
	{
		if (mAvatarObject.isNull() || mAvatarObject->mDrawable.isNull())
		{
			llwarns << "Null avatar drawable!" << llendl;
			return LLVector3d::zero;
		}
		head_offset.clearVec();
		if (mAvatarObject->mIsSitting && mAvatarObject->getParent())
		{
			mAvatarObject->updateHeadOffset();
			head_offset.mdV[VX] = mAvatarObject->mHeadOffset.mV[VX];
			head_offset.mdV[VY] = mAvatarObject->mHeadOffset.mV[VY];
			head_offset.mdV[VZ] = mAvatarObject->mHeadOffset.mV[VZ] + 0.1f;
			const LLMatrix4& mat = ((LLViewerObject*) mAvatarObject->getParent())->getRenderMatrix();
			camera_position_global = getPosGlobalFromAgent
								((mAvatarObject->getPosition()+
								 LLVector3(head_offset)*mAvatarObject->getRotation()) * mat);
		}
		else
		{
			head_offset.mdV[VZ] = mAvatarObject->mHeadOffset.mV[VZ];
			if (mAvatarObject->mIsSitting)
			{
				head_offset.mdV[VZ] += 0.1;
			}
			camera_position_global = getPosGlobalFromAgent(mAvatarObject->getRenderPosition());//frame_center_global;
			head_offset = head_offset * mAvatarObject->getRenderRotation();
			camera_position_global = camera_position_global + head_offset;
		}
	}
	else if (mCameraMode == CAMERA_MODE_THIRD_PERSON && mFocusOnAvatar)
	{
		LLVector3 local_camera_offset;
		F32 camera_distance = 0.f;

		if (mSitCameraEnabled 
			&& mAvatarObject.notNull() 
			&& mAvatarObject->mIsSitting 
			&& mSitCameraReferenceObject.notNull())
		{
			// sit camera
			LLVector3 object_pos = mSitCameraReferenceObject->getRenderPosition();
			LLQuaternion object_rot = mSitCameraReferenceObject->getRenderRotation();

			LLVector3 target_pos = object_pos + (mSitCameraPos * object_rot);

			camera_position_global = getPosGlobalFromAgent(target_pos);
		}
		else
		{
			local_camera_offset = mCameraZoomFraction * mCameraOffsetDefault;
			
			// are we sitting down?
			if (mAvatarObject.notNull() && mAvatarObject->getParent())
			{
				LLQuaternion parent_rot = ((LLViewerObject*)mAvatarObject->getParent())->getRenderRotation();
				// slam agent coordinate frame to proper parent local version
				LLVector3 at_axis = mFrameAgent.getAtAxis() * parent_rot;
				at_axis.mV[VZ] = 0.f;
				at_axis.normVec();
				resetAxes(at_axis * ~parent_rot);

				local_camera_offset = local_camera_offset * mFrameAgent.getQuaternion() * parent_rot;
			}
			else
			{
				local_camera_offset = mFrameAgent.rotateToAbsolute( local_camera_offset );
			}

			if (!mCameraCollidePlane.isExactlyZero() && (mAvatarObject.isNull() || !mAvatarObject->mIsSitting))
			{
				LLVector3 plane_normal;
				plane_normal.setVec(mCameraCollidePlane.mV);

				F32 offset_dot_norm = local_camera_offset * plane_normal;
				if (llabs(offset_dot_norm) < 0.001f)
				{
					offset_dot_norm = 0.001f;
				}
				
				camera_distance = local_camera_offset.normVec();

				F32 pos_dot_norm = getPosAgentFromGlobal(frame_center_global + head_offset) * plane_normal;
				
				// if agent is outside the colliding half-plane
				if (pos_dot_norm > mCameraCollidePlane.mV[VW])
				{
					// check to see if camera is on the opposite side (inside) the half-plane
					if (offset_dot_norm + pos_dot_norm < mCameraCollidePlane.mV[VW])
					{
						// diminish offset by factor to push it back outside the half-plane
						camera_distance *= (pos_dot_norm - mCameraCollidePlane.mV[VW] - CAMERA_COLLIDE_EPSILON) / -offset_dot_norm;
					}
				}
				else
				{
					if (offset_dot_norm + pos_dot_norm > mCameraCollidePlane.mV[VW])
					{
						camera_distance *= (mCameraCollidePlane.mV[VW] - pos_dot_norm - CAMERA_COLLIDE_EPSILON) / offset_dot_norm;
					}
				}
			}
			else
			{
				camera_distance = local_camera_offset.normVec();
			}

			mTargetCameraDistance = llmax(camera_distance, MIN_CAMERA_DISTANCE);

			if (mTargetCameraDistance != mCurrentCameraDistance)
			{
				F32 camera_lerp_amt = LLCriticalDamp::getInterpolant(CAMERA_ZOOM_HALF_LIFE);

				mCurrentCameraDistance = lerp(mCurrentCameraDistance, mTargetCameraDistance, camera_lerp_amt);
			}

			// Make the camera distance current
			local_camera_offset *= mCurrentCameraDistance;

			// set the global camera position
			LLVector3d camera_offset;
			
			LLVector3 av_pos = mAvatarObject.isNull() ? LLVector3::zero : mAvatarObject->getRenderPosition();
			camera_offset.setVec( local_camera_offset );
			camera_position_global = frame_center_global + head_offset + camera_offset;

			if (!mAvatarObject.isNull())
			{
				LLVector3d camera_lag_d;
				F32 lag_interp = LLCriticalDamp::getInterpolant(CAMERA_LAG_HALF_LIFE);
				LLVector3 target_lag;
				LLVector3 vel = getVelocity();

				// lag by appropriate amount for flying
				F32 time_in_air = mAvatarObject->mTimeInAir.getElapsedTimeF32();
				if(!mCameraAnimating && mAvatarObject->mInAir && time_in_air > GROUND_TO_AIR_CAMERA_TRANSITION_START_TIME)
				{
					LLVector3 frame_at_axis = mFrameAgent.getAtAxis();
					frame_at_axis -= projected_vec(frame_at_axis, getReferenceUpVector());
					frame_at_axis.normVec();

					//transition smoothly in air mode, to avoid camera pop
					F32 u = (time_in_air - GROUND_TO_AIR_CAMERA_TRANSITION_START_TIME) / GROUND_TO_AIR_CAMERA_TRANSITION_TIME;
					u = llclamp(u, 0.f, 1.f);

					lag_interp *= u;

					if (gViewerWindow->getLeftMouseDown() && gLastHitObjectID == mAvatarObject->getID())
					{
						// disable camera lag when using mouse-directed steering
						target_lag.clearVec();
					}
					else
					{
						target_lag = vel * gSavedSettings.getF32("DynamicCameraStrength") / 30.f;
					}

					mCameraLag = lerp(mCameraLag, target_lag, lag_interp);

					F32 lag_dist = mCameraLag.magVec();
					if (lag_dist > MAX_CAMERA_LAG)
					{
						mCameraLag = mCameraLag * MAX_CAMERA_LAG / lag_dist;
					}

					// clamp camera lag so that avatar is always in front
					F32 dot = (mCameraLag - (frame_at_axis * (MIN_CAMERA_LAG * u))) * frame_at_axis;
					if (dot < -(MIN_CAMERA_LAG * u))
					{
						mCameraLag -= (dot + (MIN_CAMERA_LAG * u)) * frame_at_axis;
					}
				}
				else
				{
					mCameraLag = lerp(mCameraLag, LLVector3::zero, LLCriticalDamp::getInterpolant(0.15f));
				}

				camera_lag_d.setVec(mCameraLag);
				camera_position_global = camera_position_global - camera_lag_d;
			}
		}
	}
	else
	{
		LLVector3d focusPosGlobal = calcFocusPositionTargetGlobal();
		// camera gets pushed out later wrt mCameraFOVZoomFactor...this is "raw" value
		camera_position_global = focusPosGlobal + mCameraFocusOffset;
	}

	if (!LLViewerCamera::sDisableCameraConstraints && !gAgent.isGodlike())
	{
		LLViewerRegion* regionp = gWorldPointer->getRegionFromPosGlobal(
			camera_position_global);
		bool constrain = true;
		if(regionp && regionp->canManageEstate())
		{
			constrain = false;
		}
		if(constrain)
		{
			F32 max_dist = ( CAMERA_MODE_CUSTOMIZE_AVATAR == mCameraMode ) ?
				APPEARANCE_MAX_ZOOM : MAX_CAMERA_DISTANCE_FROM_AGENT;

			LLVector3d camera_offset = camera_position_global
				- gAgent.getPositionGlobal();
			F32 camera_distance = (F32)camera_offset.magVec();

			if(camera_distance > max_dist)
			{
				camera_position_global = gAgent.getPositionGlobal() + 
					(max_dist / camera_distance) * camera_offset;
				isConstrained = TRUE;
			}
		}

// JC - Could constrain camera based on parcel stuff here.
//			LLViewerRegion *regionp = gWorldPointer->getRegionFromPosGlobal(camera_position_global);
//			
//			if (regionp && !regionp->mParcelOverlay->isBuildCameraAllowed(regionp->getPosRegionFromGlobal(camera_position_global)))
//			{
//				camera_position_global = last_position_global;
//
//				isConstrained = TRUE;
//			}
	}

	// Don't let camera go underground
	F32 camera_min_off_ground = getCameraMinOffGround();

	if (gWorldPointer)
	{
		camera_land_height = gWorldPointer->resolveLandHeightGlobal(camera_position_global);
	}
	else
	{
		camera_land_height = 0.f;
	}

	if (camera_position_global.mdV[VZ] < camera_land_height + camera_min_off_ground)
	{
		camera_position_global.mdV[VZ] = camera_land_height + camera_min_off_ground;

		gMinObjectDistance = MIN_NEAR_PLANE;
		isConstrained = TRUE;
	}


	if (hit_limit)
	{
		*hit_limit = isConstrained;
	}

	return camera_position_global;
}


//-----------------------------------------------------------------------------
// handleScrollWheel()
//-----------------------------------------------------------------------------
void LLAgent::handleScrollWheel(S32 clicks)
{
	if ( mCameraMode == CAMERA_MODE_FOLLOW && gAgent.getFocusOnAvatar())
	{
		if ( ! mFollowCam.getPositionLocked() ) // not if the followCam position is locked in place
		{
			mFollowCam.zoom( clicks ); 
			if ( mFollowCam.isZoomedToMinimumDistance() )
			{
				changeCameraToMouselook(FALSE);
			}
		}
	}
	else
	{
		const F32 ROOT_ROOT_TWO = sqrt(F_SQRT2);

		// Block if camera is animating
		if (mCameraAnimating)
		{
			return;
		}

		if (gSelectMgr->getObjectCount() && gSelectMgr->getSelectType() == SELECT_TYPE_HUD)
		{
			F32 zoom_factor = (F32)pow(0.8, -clicks);
			cameraZoomIn(zoom_factor);
		}
		else if (mFocusOnAvatar && mCameraMode == CAMERA_MODE_THIRD_PERSON)
		{
			F32 current_zoom_fraction = mTargetCameraDistance / mCameraOffsetDefault.magVec();
			current_zoom_fraction *= 1.f - pow(ROOT_ROOT_TWO, clicks);
			
			cameraOrbitIn(current_zoom_fraction * mCameraOffsetDefault.magVec());
		}
		else
		{
			F32 current_zoom_fraction = (F32)mCameraFocusOffsetTarget.magVec();
			cameraOrbitIn(current_zoom_fraction * (1.f - pow(ROOT_ROOT_TWO, clicks)));
		}
	}
}


//-----------------------------------------------------------------------------
// getCameraMinOffGround()
//-----------------------------------------------------------------------------
F32 LLAgent::getCameraMinOffGround()
{
	if (mCameraMode == CAMERA_MODE_MOUSELOOK)
	{
		return 0.f;
	}
	else
	{
		if (LLViewerCamera::sDisableCameraConstraints)
		{
			return -1000.f;
		}
		else
		{
			return 0.5f;
		}
	}
}


//-----------------------------------------------------------------------------
// resetCamera()
//-----------------------------------------------------------------------------
void LLAgent::resetCamera()
{
	// Remove any pitch from the avatar
	LLVector3 at = mFrameAgent.getAtAxis();
	at.mV[VZ] = 0.f;
	at.normVec();
	gAgent.resetAxes(at);
	// have to explicitly clear field of view zoom now
	mCameraFOVZoomFactor = 0.f;

	updateCamera();
}

//-----------------------------------------------------------------------------
// changeCameraToMouselook()
//-----------------------------------------------------------------------------
void LLAgent::changeCameraToMouselook(BOOL animate)
{
	// visibility changes at end of animation
	gViewerWindow->getWindow()->resetBusyCount();

	// unpause avatar animation
	mPauseRequest = NULL;

	gCurrentToolset = gMouselookToolset;
	gCurrentToolset->selectFirstTool();
	gToolMgr->useSelectedTool( gCurrentToolset );

	gSavedSettings.setBOOL("FirstPersonBtnState",	FALSE);
	gSavedSettings.setBOOL("MouselookBtnState",		TRUE);
	gSavedSettings.setBOOL("ThirdPersonBtnState",	FALSE);
	gSavedSettings.setBOOL("BuildBtnState",			FALSE);

	if (mAvatarObject)
	{
		mAvatarObject->stopMotion( ANIM_AGENT_BODY_NOISE );
		mAvatarObject->stopMotion( ANIM_AGENT_BREATHE_ROT );
	}

	//gViewerWindow->stopGrab();
	gSelectMgr->deselectAll();
	gViewerWindow->hideCursor();
	gViewerWindow->moveCursorToCenter();

	if( mCameraMode != CAMERA_MODE_MOUSELOOK )
	{
		gViewerWindow->setKeyboardFocus( NULL, NULL );
		
		mLastCameraMode = mCameraMode;
		mCameraMode = CAMERA_MODE_MOUSELOOK;
		U32 old_flags = mControlFlags;
		setControlFlags(AGENT_CONTROL_MOUSELOOK);
		if (old_flags != mControlFlags)
		{
			mbFlagsDirty = TRUE;
		}

		if (animate)
		{
			startCameraAnimation();
		}
		else
		{
			mCameraAnimating = FALSE;
			endAnimationUpdateUI();
		}
	}
}


//-----------------------------------------------------------------------------
// changeCameraToDefault()
//-----------------------------------------------------------------------------
void LLAgent::changeCameraToDefault()
{
	if (LLFollowCamMgr::getActiveFollowCamParams())
	{
		changeCameraToFollow();
	}
	else
	{
		changeCameraToThirdPerson();
	}
}


// Ventrella
//-----------------------------------------------------------------------------
// changeCameraToFollow()
//-----------------------------------------------------------------------------
void LLAgent::changeCameraToFollow(BOOL animate)
{
	if( mCameraMode != CAMERA_MODE_FOLLOW )
	{
		if (mCameraMode == CAMERA_MODE_MOUSELOOK)
		{
			animate = FALSE;
		}
		startCameraAnimation();

		mLastCameraMode = mCameraMode;
		mCameraMode = CAMERA_MODE_FOLLOW;

		// bang-in the current focus, position, and up vector of the follow cam
		mFollowCam.reset( mCameraPositionAgent, gCamera->getPointOfInterest(), LLVector3::z_axis );
		
		if (gBasicToolset)
		{
			gCurrentToolset = gBasicToolset;
			gCurrentToolset->selectFirstTool();
			gToolMgr->useSelectedTool( gCurrentToolset );
		}

		if (mAvatarObject)
		{
			mAvatarObject->mPelvisp->setPosition(LLVector3::zero);
			mAvatarObject->startMotion( ANIM_AGENT_BODY_NOISE );
			mAvatarObject->startMotion( ANIM_AGENT_BREATHE_ROT );
		}

		gSavedSettings.setBOOL("FirstPersonBtnState",	FALSE);
		gSavedSettings.setBOOL("MouselookBtnState",		FALSE);
		gSavedSettings.setBOOL("ThirdPersonBtnState",	TRUE);
		gSavedSettings.setBOOL("BuildBtnState",			FALSE);

		// unpause avatar animation
		mPauseRequest = NULL;

		U32 old_flags = mControlFlags;
		clearControlFlags(AGENT_CONTROL_MOUSELOOK);
		if (old_flags != mControlFlags)
		{
			mbFlagsDirty = TRUE;
		}

		//RN: this doesn't seem to be necessary and destroys the UE for script-driven cameras
		//gViewerWindow->setKeyboardFocus( NULL, NULL );
		//gViewerWindow->setMouseCapture( NULL, NULL );

		if (animate)
		{
			startCameraAnimation();
		}
		else
		{
			mCameraAnimating = FALSE;
			endAnimationUpdateUI();
		}
	}
}

//-----------------------------------------------------------------------------
// changeCameraToThirdPerson()
//-----------------------------------------------------------------------------
void LLAgent::changeCameraToThirdPerson(BOOL animate)
{
//printf( "changeCameraToThirdPerson\n" );

	gViewerWindow->getWindow()->resetBusyCount();

	mCameraZoomFraction = INITIAL_ZOOM_FRACTION;

	if (mAvatarObject)
	{
		mAvatarObject->mPelvisp->setPosition(LLVector3::zero);
		mAvatarObject->startMotion( ANIM_AGENT_BODY_NOISE );
		mAvatarObject->startMotion( ANIM_AGENT_BREATHE_ROT );
	}

	gSavedSettings.setBOOL("FirstPersonBtnState",	FALSE);
	gSavedSettings.setBOOL("MouselookBtnState",		FALSE);
	gSavedSettings.setBOOL("ThirdPersonBtnState",	TRUE);
	gSavedSettings.setBOOL("BuildBtnState",			FALSE);

	LLVector3 at_axis;

	// unpause avatar animation
	mPauseRequest = NULL;

	if( mCameraMode != CAMERA_MODE_THIRD_PERSON )
	{
		if (gBasicToolset)
		{
			gCurrentToolset = gBasicToolset;
			gCurrentToolset->selectFirstTool();
			gToolMgr->useSelectedTool( gCurrentToolset );
		}

		mCameraLag.clearVec();
		if (mCameraMode == CAMERA_MODE_MOUSELOOK)
		{
			mCurrentCameraDistance = MIN_CAMERA_DISTANCE;
			mTargetCameraDistance = MIN_CAMERA_DISTANCE;
			animate = FALSE;
		}
		mLastCameraMode = mCameraMode;
		mCameraMode = CAMERA_MODE_THIRD_PERSON;
		U32 old_flags = mControlFlags;
		clearControlFlags(AGENT_CONTROL_MOUSELOOK);
		if (old_flags != mControlFlags)
		{
			mbFlagsDirty = TRUE;
		}

		//RN: this doesn't seem to be necessary and destroys the UE for script-driven cameras
		//gViewerWindow->setKeyboardFocus( NULL, NULL );
		//gViewerWindow->setMouseCapture( NULL, NULL );
	}

	// Remove any pitch from the avatar
	if (!mAvatarObject.isNull() && mAvatarObject->getParent())
	{
		LLQuaternion obj_rot = ((LLViewerObject*)mAvatarObject->getParent())->getRenderRotation();
		at_axis = gCamera->getAtAxis();
		at_axis.mV[VZ] = 0.f;
		at_axis.normVec();
		resetAxes(at_axis * ~obj_rot);
	}
	else
	{
		at_axis = mFrameAgent.getAtAxis();
		at_axis.mV[VZ] = 0.f;
		at_axis.normVec();
		resetAxes(at_axis);
	}


	if (animate)
	{
		startCameraAnimation();
	}
	else
	{
		mCameraAnimating = FALSE;
		endAnimationUpdateUI();
	}
}

//-----------------------------------------------------------------------------
// changeCameraToCustomizeAvatar()
//-----------------------------------------------------------------------------
void LLAgent::changeCameraToCustomizeAvatar(BOOL animate)
{
	setControlFlags(AGENT_CONTROL_STAND_UP); // force stand up
	gViewerWindow->getWindow()->resetBusyCount();

	if (gFaceEditToolset)
	{
		gCurrentToolset = gFaceEditToolset;
		gCurrentToolset->selectFirstTool();
		gToolMgr->useSelectedTool( gCurrentToolset );
	}

	gSavedSettings.setBOOL("FirstPersonBtnState", FALSE);
	gSavedSettings.setBOOL("MouselookBtnState", FALSE);
	gSavedSettings.setBOOL("ThirdPersonBtnState", FALSE);
	gSavedSettings.setBOOL("BuildBtnState", FALSE);

	if (animate)
	{
		startCameraAnimation();
	}

	// Remove any pitch from the avatar
	LLVector3 at = mFrameAgent.getAtAxis();
	at.mV[VZ] = 0.f;
	at.normVec();
	gAgent.resetAxes(at);

	if( mCameraMode != CAMERA_MODE_CUSTOMIZE_AVATAR )
	{
		mLastCameraMode = mCameraMode;
		mCameraMode = CAMERA_MODE_CUSTOMIZE_AVATAR;
		U32 old_flags = mControlFlags;
		clearControlFlags(AGENT_CONTROL_MOUSELOOK);
		if (old_flags != mControlFlags)
		{
			mbFlagsDirty = TRUE;
		}

		gViewerWindow->setKeyboardFocus( NULL, NULL );
		gViewerWindow->setMouseCapture( NULL, NULL );

		LLVOAvatar::onCustomizeStart();
	}

	if (animate && !mAvatarObject.isNull())
	{
		sendAnimationRequest(ANIM_AGENT_CUSTOMIZE, ANIM_REQUEST_START);
		LLMotion* turn_motion = mAvatarObject->findMotion(ANIM_AGENT_CUSTOMIZE);
		if (turn_motion)
		{
			mAnimationDuration = turn_motion->getDuration() + CUSTOMIZE_AVATAR_CAMERA_ANIM_SLOP;
		}
		else
		{
			mAnimationDuration = gSavedSettings.getF32("ZoomTime");
		}
		gAgent.setFocusGlobal(LLVector3d::zero);
	}
	else
	{
		mCameraAnimating = FALSE;
		endAnimationUpdateUI();
	}

}


//
// Focus point management
//

//-----------------------------------------------------------------------------
// startCameraAnimation()
//-----------------------------------------------------------------------------
void LLAgent::startCameraAnimation()
{
	mAnimationCameraStartGlobal = getCameraPositionGlobal();
	mAnimationFocusStartGlobal = mFocusGlobal;
	mAnimationTimer.reset();
	mCameraAnimating = TRUE;
	mAnimationDuration = gSavedSettings.getF32("ZoomTime");
}

//-----------------------------------------------------------------------------
// stopCameraAnimation()
//-----------------------------------------------------------------------------
void LLAgent::stopCameraAnimation()
{
	mCameraAnimating = FALSE;
}

void LLAgent::clearFocusObject()
{
	if (mFocusObject.notNull())
	{
		startCameraAnimation();

		setFocusObject(NULL);
		mFocusObjectOffset.clearVec();
	}
}

void LLAgent::setFocusObject(LLViewerObject* object)
{
	if (mFocusObject.notNull() && 
		mFocusObject->mDrawable.notNull() && 
		mFocusObject->getPCode() == LL_PCODE_VOLUME &&
		mFocusObject != object)
	{
		LLPointer<LLViewerObject> fade_object_ptr(mFocusObject);

		if (fade_object_ptr.notNull() && mFadeObjects.find(fade_object_ptr) == mFadeObjects.end())
		{
			mFadeObjects.insert(fade_object_ptr);
		}
	}

	mFocusObject = object;
}

// Focus on a point, but try to keep camera position stable.
//-----------------------------------------------------------------------------
// setFocusGlobal()
//-----------------------------------------------------------------------------
void LLAgent::setFocusGlobal(const LLVector3d& focus, const LLUUID &object_id)
{
	setFocusObject(gObjectList.findObject(object_id));
	LLVector3d old_focus = mFocusTargetGlobal;
	LLViewerObject *focus_obj = mFocusObject;

	// if focus has changed
	if (old_focus != focus)
	{
		if (focus.isExactlyZero())
		{
			if (!mAvatarObject.isNull())
			{
				mFocusTargetGlobal = getPosGlobalFromAgent(mAvatarObject->mHeadp->getWorldPosition());
			}
			else
			{
				mFocusTargetGlobal = getPositionGlobal();
			}
			mCameraFocusOffsetTarget = getCameraPositionGlobal() - mFocusTargetGlobal;
			mCameraFocusOffset = mCameraFocusOffsetTarget;
			setLookAt(LOOKAT_TARGET_CLEAR);
		}
		else
		{
			mFocusTargetGlobal = focus;
			if (!focus_obj)
			{
				mCameraFOVZoomFactor = 0.f;
			}

			mCameraFocusOffsetTarget = gAgent.getPosGlobalFromAgent(mCameraVirtualPositionAgent) - mFocusTargetGlobal;

			startCameraAnimation();

			if (focus_obj)
			{
				if (focus_obj->isAvatar())
				{
					setLookAt(LOOKAT_TARGET_FOCUS, focus_obj);
				}
				else
				{
					setLookAt(LOOKAT_TARGET_FOCUS, focus_obj, (getPosAgentFromGlobal(focus) - focus_obj->getRenderPosition()) * ~focus_obj->getRenderRotation());
				}
			}
			else
			{
				setLookAt(LOOKAT_TARGET_FOCUS, NULL, getPosAgentFromGlobal(mFocusTargetGlobal));
			}
		}
	}
	else // focus == mFocusTargetGlobal
	{
		if (focus.isExactlyZero())
		{
			if (!mAvatarObject.isNull())
			{
				mFocusTargetGlobal = getPosGlobalFromAgent(mAvatarObject->mHeadp->getWorldPosition());
			}
			else
			{
				mFocusTargetGlobal = getPositionGlobal();
			}
		}
		mCameraFocusOffsetTarget = (getCameraPositionGlobal() - mFocusTargetGlobal) / (1.f + mCameraFOVZoomFactor);;
		mCameraFocusOffset = mCameraFocusOffsetTarget;
	}

	if (mFocusObject.notNull())
	{
		// for attachments, make offset relative to avatar, not the attachment
		if (mFocusObject->isAttachment())
		{
			while (!mFocusObject->isAvatar())
			{
				mFocusObject = (LLViewerObject*) mFocusObject->getParent();
			}
			setFocusObject((LLViewerObject*)mFocusObject);
		}
		updateFocusOffset();
	}
}

// Used for avatar customization
//-----------------------------------------------------------------------------
// setCameraPosAndFocusGlobal()
//-----------------------------------------------------------------------------
void LLAgent::setCameraPosAndFocusGlobal(const LLVector3d& camera_pos, const LLVector3d& focus, const LLUUID &object_id)
{
	LLVector3d old_focus = mFocusTargetGlobal;

	F64 focus_delta_squared = (old_focus - focus).magVecSquared();
	const F64 ANIM_EPSILON_SQUARED = 0.0001;
	if( focus_delta_squared > ANIM_EPSILON_SQUARED )
	{
		startCameraAnimation();

		if( CAMERA_MODE_CUSTOMIZE_AVATAR == mCameraMode ) 
		{
			// Compensate for the fact that the camera has already been offset to make room for LLFloaterCustomize.
			mAnimationCameraStartGlobal -= LLVector3d(gCamera->getLeftAxis() * calcCustomizeAvatarUIOffset( mAnimationCameraStartGlobal ));
		}
	}
	
	//gCamera->setOrigin( gAgent.getPosAgentFromGlobal( camera_pos ) );
	setFocusObject(gObjectList.findObject(object_id));
	mFocusTargetGlobal = focus;
	mCameraFocusOffsetTarget = camera_pos - focus;
	mCameraFocusOffset = mCameraFocusOffsetTarget;

	if (mFocusObject)
	{
		if (mFocusObject->isAvatar())
		{
			setLookAt(LOOKAT_TARGET_FOCUS, mFocusObject);
		}
		else
		{
			setLookAt(LOOKAT_TARGET_FOCUS, mFocusObject, (getPosAgentFromGlobal(focus) - mFocusObject->getRenderPosition()) * ~mFocusObject->getRenderRotation());
		}
	}
	else
	{
		setLookAt(LOOKAT_TARGET_FOCUS, NULL, getPosAgentFromGlobal(mFocusTargetGlobal));
	}

	if( mCameraAnimating )
	{
		const F64 ANIM_METERS_PER_SECOND = 10.0;
		const F64 MIN_ANIM_SECONDS = 0.5;
		F64 anim_duration = llmax( MIN_ANIM_SECONDS, sqrt(focus_delta_squared) / ANIM_METERS_PER_SECOND );
		setAnimationDuration( (F32)anim_duration );
	}

	updateFocusOffset();
}

//-----------------------------------------------------------------------------
// setSitCamera()
//-----------------------------------------------------------------------------
void LLAgent::setSitCamera(const LLUUID &object_id, const LLVector3 &camera_pos, const LLVector3 &camera_focus)
{
	BOOL camera_enabled = !object_id.isNull();

	if (camera_enabled)
	{
		LLViewerObject *reference_object = gObjectList.findObject(object_id);
		if (reference_object)
		{
			//convert to root object relative?
			mSitCameraPos = camera_pos;
			mSitCameraFocus = camera_focus;
			mSitCameraReferenceObject = reference_object;
			mSitCameraEnabled = TRUE;
		}
	}
	else
	{
		mSitCameraPos.clearVec();
		mSitCameraFocus.clearVec();
		mSitCameraReferenceObject = NULL;
		mSitCameraEnabled = FALSE;
	}
}

//-----------------------------------------------------------------------------
// setFocusOnAvatar()
//-----------------------------------------------------------------------------
void LLAgent::setFocusOnAvatar(BOOL focus_on_avatar, BOOL animate)
{
	if (focus_on_avatar != mFocusOnAvatar)
	{
		if (animate)
		{
			startCameraAnimation();
		}
		else
		{
			stopCameraAnimation();
		}
	}
	
	//RN: when focused on the avatar, we're not "looking" at it
	// looking implies intent while focusing on avatar means
	// you're just walking around with a camera on you...eesh.
	if (focus_on_avatar && !mFocusOnAvatar)
	{
		setFocusGlobal(LLVector3d::zero);
		mCameraFOVZoomFactor = 0.f;
		if (mCameraMode == CAMERA_MODE_THIRD_PERSON)
		{
			LLVector3 at_axis;
			if (!mAvatarObject.isNull() && mAvatarObject->getParent())
			{
				LLQuaternion obj_rot = ((LLViewerObject*)mAvatarObject->getParent())->getRenderRotation();
				at_axis = gCamera->getAtAxis();
				at_axis.mV[VZ] = 0.f;
				at_axis.normVec();
				resetAxes(at_axis * ~obj_rot);
			}
			else
			{
				at_axis = gCamera->getAtAxis();
				at_axis.mV[VZ] = 0.f;
				at_axis.normVec();
				resetAxes(at_axis);
			}
		}
	}
	
	mFocusOnAvatar = focus_on_avatar;
}

//-----------------------------------------------------------------------------
// heardChat()
//-----------------------------------------------------------------------------
void LLAgent::heardChat(const LLChat& chat)
{
	if (chat.mChatType == CHAT_TYPE_START 
		|| chat.mChatType == CHAT_TYPE_STOP)
	{
		return;
	}

	mLastChatterID = chat.mFromID;
	mChatTimer.reset();
	
	mNearChatRadius = CHAT_NORMAL_RADIUS / 2.f;
}

//-----------------------------------------------------------------------------
// lookAtLastChat()
//-----------------------------------------------------------------------------
void LLAgent::lookAtLastChat()
{
	// Block if camera is animating or not in normal third person camera mode
	if (mCameraAnimating || !cameraThirdPerson())
	{
		return;
	}

	LLViewerObject *chatter = gObjectList.findObject(mLastChatterID);
	if (chatter)
	{
		LLVector3 delta_pos;
		if (chatter->isAvatar())
		{
			LLVOAvatar *chatter_av = (LLVOAvatar*)chatter;
			if (!mAvatarObject.isNull() && chatter_av->mHeadp)
			{
				delta_pos = chatter_av->mHeadp->getWorldPosition() - mAvatarObject->mHeadp->getWorldPosition();
			}
			else
			{
				delta_pos = chatter->getPositionAgent() - getPositionAgent();
			}
			delta_pos.normVec();

			setControlFlags(AGENT_CONTROL_STOP);

			changeCameraToThirdPerson();

			LLVector3 new_camera_pos = mAvatarObject->mHeadp->getWorldPosition();
			LLVector3 left = delta_pos % LLVector3::z_axis;
			left.normVec();
			LLVector3 up = left % delta_pos;
			up.normVec();
			new_camera_pos -= delta_pos * 0.4f;
			new_camera_pos += left * 0.3f;
			new_camera_pos += up * 0.2f;
			if (chatter_av->mHeadp)
			{
				setFocusGlobal(getPosGlobalFromAgent(chatter_av->mHeadp->getWorldPosition()), mLastChatterID);
				mCameraFocusOffsetTarget = getPosGlobalFromAgent(new_camera_pos) - gAgent.getPosGlobalFromAgent(chatter_av->mHeadp->getWorldPosition());
			}
			else
			{
				setFocusGlobal(chatter->getPositionGlobal(), mLastChatterID);
				mCameraFocusOffsetTarget = getPosGlobalFromAgent(new_camera_pos) - chatter->getPositionGlobal();
			}
			setFocusOnAvatar(FALSE, TRUE);
		}
		else
		{
			delta_pos = chatter->getRenderPosition() - getPositionAgent();
			delta_pos.normVec();

			setControlFlags(AGENT_CONTROL_STOP);

			changeCameraToThirdPerson();

			LLVector3 new_camera_pos = mAvatarObject->mHeadp->getWorldPosition();
			LLVector3 left = delta_pos % LLVector3::z_axis;
			left.normVec();
			LLVector3 up = left % delta_pos;
			up.normVec();
			new_camera_pos -= delta_pos * 0.4f;
			new_camera_pos += left * 0.3f;
			new_camera_pos += up * 0.2f;

			setFocusGlobal(chatter->getPositionGlobal(), mLastChatterID);
			mCameraFocusOffsetTarget = getPosGlobalFromAgent(new_camera_pos) - chatter->getPositionGlobal();
			setFocusOnAvatar(FALSE, TRUE);
		}
	}
}

const F32 SIT_POINT_EXTENTS = 0.2f;

// Grabs current position
void LLAgent::setStartPosition(U32 location_id)
{
	LLViewerObject		*object;

	if ( !(gAgentID == LLUUID::null) )
	{
		// we've got an ID for an agent viewerobject
		object = gObjectList.findObject(gAgentID);
		if (object)
		{
			// we've got the viewer object
			// Sometimes the agent can be velocity interpolated off of
			// this simulator.  Clamp it to the region the agent is
			// in, a little bit in on each side.
			const F32 INSET = 0.5f;	//meters
			const F32 REGION_WIDTH = gWorldPointer->getRegionWidthInMeters();

			LLVector3 agent_pos = getPositionAgent();

			if (mAvatarObject)
			{
				// the z height is at the agent's feet
				agent_pos.mV[VZ] -= 0.5f * mAvatarObject->mBodySize.mV[VZ];
			}

			agent_pos.mV[VX] = llclamp( agent_pos.mV[VX], INSET, REGION_WIDTH - INSET );
			agent_pos.mV[VY] = llclamp( agent_pos.mV[VY], INSET, REGION_WIDTH - INSET );

			// Don't let them go below ground, or too high.
			agent_pos.mV[VZ] = llclamp( agent_pos.mV[VZ], 
				mRegionp->getLandHeightRegion( agent_pos ), 
				gWorldPointer->getRegionMaxHeight() );

			LLMessageSystem* msg = gMessageSystem;
			msg->newMessageFast(_PREHASH_SetStartLocationRequest);
			msg->nextBlockFast( _PREHASH_AgentData);
			msg->addUUIDFast(_PREHASH_AgentID, getID());
			msg->addUUIDFast(_PREHASH_SessionID, getSessionID());
			msg->nextBlockFast( _PREHASH_StartLocationData);
			// corrected by sim
			msg->addStringFast(_PREHASH_SimName, "");
			msg->addU32Fast(_PREHASH_LocationID, location_id);
			msg->addVector3Fast(_PREHASH_LocationPos, agent_pos);
			msg->addVector3Fast(_PREHASH_LocationLookAt,mFrameAgent.getAtAxis());

			// Reliable only helps when setting home location.  Last
			// location is sent on quit, and we don't have time to ack
			// the packets.
			msg->sendReliable(mRegionp->getHost());

			const U32 HOME_INDEX = 1;
			if( HOME_INDEX == location_id )
			{
				setHomePosRegion( mRegionp->getHandle(), getPositionAgent() );
			}
		}
		else
		{
			llinfos << "setStartPosition - Can't find agent viewerobject id " << gAgentID << llendl;
		}
	}
}

void LLAgent::requestStopMotion( LLMotion* motion )
{
	// Notify all avatars that a motion has stopped.
	// This is needed to clear the animation state bits
	LLUUID anim_state = motion->getID();

	// if motion is not looping, it could have stopped by running out of time
	// so we need to tell the server this
//	llinfos << "Sending stop for motion " << motion->getName() << llendl;
	sendAnimationRequest( anim_state, ANIM_REQUEST_STOP );

	// handle automatic state transitions (based on completion of animation playback)
	if (anim_state == ANIM_AGENT_STANDUP)
	{
		// send stand up command
		setControlFlags(AGENT_CONTROL_FINISH_ANIM);

		// now trigger dusting self off animation
		if (mAvatarObject.notNull() && !mAvatarObject->mBelowWater && rand() % 3 == 0)
			sendAnimationRequest( ANIM_AGENT_BRUSH, ANIM_REQUEST_START );
	}
	else if (anim_state == ANIM_AGENT_PRE_JUMP || anim_state == ANIM_AGENT_LAND || anim_state == ANIM_AGENT_MEDIUM_LAND)
	{
		setControlFlags(AGENT_CONTROL_FINISH_ANIM);
	}
}

BOOL LLAgent::isGodlike() const
{
#ifdef HACKED_GODLIKE_VIEWER
	return TRUE;
#else
	if(mAdminOverride) return TRUE;
	return mGodLevel > GOD_NOT;
#endif
}

U8 LLAgent::getGodLevel() const
{
#ifdef HACKED_GODLIKE_VIEWER
	return GOD_MAINTENANCE;
#else
	if(mAdminOverride) return GOD_FULL;
	return mGodLevel;
#endif
}


void LLAgent::buildFullname(std::string& name) const
{
	if (mAvatarObject)
	{
		name = mAvatarObject->getFullname();
	}
}

void LLAgent::buildFullnameAndTitle(std::string& name) const
{
	if (isGroupMember())
	{
		name = mGroupTitle;
		name += ' ';
	}
	else
	{
		name.erase(0, name.length());
	}

	if (mAvatarObject)
	{
		name += mAvatarObject->getFullname();
	}
}

BOOL LLAgent::isInGroup(const LLUUID& group_id) const
{
	S32 count = mGroups.count();
	for(S32 i = 0; i < count; ++i)
	{
		if(mGroups.get(i).mID == group_id)
		{
			return TRUE;
		}
	}
	return FALSE;
}

// This implementation should mirror LLAgentInfo::hasPowerInGroup
BOOL LLAgent::hasPowerInGroup(const LLUUID& group_id, U64 power) const
{
	// GP_NO_POWERS can also mean no power is enough to grant an ability.
	if (GP_NO_POWERS == power) return FALSE;

	S32 count = mGroups.count();
	for(S32 i = 0; i < count; ++i)
	{
		if(mGroups.get(i).mID == group_id)
		{
			return (BOOL)((mGroups.get(i).mPowers & power) > 0);
		}
	}
	return FALSE;
}

BOOL LLAgent::hasPowerInActiveGroup(U64 power) const
{
	return (mGroupID.notNull() && (hasPowerInGroup(mGroupID, power)));
}

U64 LLAgent::getPowerInGroup(const LLUUID& group_id) const
{
	S32 count = mGroups.count();
	for(S32 i = 0; i < count; ++i)
	{
		if(mGroups.get(i).mID == group_id)
		{
			return (mGroups.get(i).mPowers);
		}
	}

	return GP_NO_POWERS;
}

BOOL LLAgent::getGroupData(const LLUUID& group_id, LLGroupData& data) const
{
	S32 count = mGroups.count();
	for(S32 i = 0; i < count; ++i)
	{
		if(mGroups.get(i).mID == group_id)
		{
			data = mGroups.get(i);
			return TRUE;
		}
	}
	return FALSE;
}

S32 LLAgent::getGroupContribution(const LLUUID& group_id) const
{
	S32 count = mGroups.count();
	for(S32 i = 0; i < count; ++i)
	{
		if(mGroups.get(i).mID == group_id)
		{
			S32 contribution = mGroups.get(i).mContribution;
			return contribution;
		}
	}
	return 0;
}

BOOL LLAgent::setGroupContribution(const LLUUID& group_id, S32 contribution)
{
	S32 count = mGroups.count();
	for(S32 i = 0; i < count; ++i)
	{
		if(mGroups.get(i).mID == group_id)
		{
			mGroups.get(i).mContribution = contribution;
			LLMessageSystem* msg = gMessageSystem;
			msg->newMessage("SetGroupContribution");
			msg->nextBlock("AgentData");
			msg->addUUID("AgentID", gAgentID);
			msg->addUUID("SessionID", gAgentSessionID);
			msg->nextBlock("Data");
			msg->addUUID("GroupID", group_id);
			msg->addS32("Contribution", contribution);
			sendReliableMessage();
			return TRUE;
		}
	}
	return FALSE;
}

BOOL LLAgent::setGroupAcceptNotices(const LLUUID& group_id, BOOL accept_notices)
{
	S32 count = mGroups.count();
	for(S32 i = 0; i < count; ++i)
	{
		if(mGroups.get(i).mID == group_id)
		{
			mGroups.get(i).mAcceptNotices = accept_notices;
			LLMessageSystem* msg = gMessageSystem;
			msg->newMessage("SetGroupAcceptNotices");
			msg->nextBlock("AgentData");
			msg->addUUID("AgentID", gAgentID);
			msg->addUUID("SessionID", gAgentSessionID);
			msg->nextBlock("Data");
			msg->addUUID("GroupID", group_id);
			msg->addBOOL("AcceptNotices", accept_notices);
			sendReliableMessage();
			return TRUE;
		}
	}
	return FALSE;
}

// utility to build a location string
void LLAgent::buildLocationString(std::string& str)
{
	const LLVector3& agent_pos_region = getPositionAgent();
	S32 pos_x = S32(agent_pos_region.mV[VX]);
	S32 pos_y = S32(agent_pos_region.mV[VY]);
	S32 pos_z = S32(agent_pos_region.mV[VZ]);

	// Round the numbers based on the velocity
	LLVector3 agent_velocity = getVelocity();
	F32 velocity_mag_sq = agent_velocity.magVecSquared();

	const F32 FLY_CUTOFF = 6.f;		// meters/sec
	const F32 FLY_CUTOFF_SQ = FLY_CUTOFF * FLY_CUTOFF;
	const F32 WALK_CUTOFF = 1.5f;	// meters/sec
	const F32 WALK_CUTOFF_SQ = WALK_CUTOFF * WALK_CUTOFF;

	if (velocity_mag_sq > FLY_CUTOFF_SQ)
	{
		pos_x -= pos_x % 4;
		pos_y -= pos_y % 4;
	}
	else if (velocity_mag_sq > WALK_CUTOFF_SQ)
	{
		pos_x -= pos_x % 2;
		pos_y -= pos_y % 2;
	}

	// create a defult name and description for the landmark
	std::string buffer;
	if( !strcmp("", gParcelMgr->getAgentParcelName()) )
	{
		// the parcel doesn't have a name
		buffer = llformat("%.32s (%d, %d, %d)",
						  getRegion()->getName().c_str(),
						  pos_x, pos_y, pos_z);
	}
	else
	{
		// the parcel has a name, so include it in the landmark name
		buffer = llformat("%.32s, %.32s (%d, %d, %d)",
						  gParcelMgr->getAgentParcelName(),
						  getRegion()->getName().c_str(),
						  pos_x, pos_y, pos_z);
	}
	str = buffer;
}

LLQuaternion LLAgent::getHeadRotation()
{
	if (mAvatarObject.isNull() || !mAvatarObject->mPelvisp || !mAvatarObject->mHeadp)
	{
		return LLQuaternion::DEFAULT;
	}

	if (!gAgent.cameraMouselook())
	{
		return mAvatarObject->getRotation();
	}

	// We must be in mouselook
	LLVector3 look_dir( gCamera->getAtAxis() );
	LLVector3 up = look_dir % mFrameAgent.getLeftAxis();
	LLVector3 left = up % look_dir;

	LLQuaternion rot(look_dir, left, up);
	if (mAvatarObject->getParent())
	{
		rot = rot * ~mAvatarObject->getParent()->getRotation();
	}

	return rot;
}

void LLAgent::sendAnimationRequests(LLDynamicArray<LLUUID> &anim_ids, EAnimRequest request)
{
	if (gAgentID.isNull())
	{
		return;
	}

	S32 num_valid_anims = 0;

	LLMessageSystem* msg = gMessageSystem;
	msg->newMessageFast(_PREHASH_AgentAnimation);
	msg->nextBlockFast(_PREHASH_AgentData);
	msg->addUUIDFast(_PREHASH_AgentID, getID());
	msg->addUUIDFast(_PREHASH_SessionID, getSessionID());

	for (S32 i = 0; i < anim_ids.count(); i++)
	{
		if (anim_ids[i].isNull())
		{
			continue;
		}
		msg->nextBlockFast(_PREHASH_AnimationList);
		msg->addUUIDFast(_PREHASH_AnimID, (anim_ids[i]) );
		msg->addBOOLFast(_PREHASH_StartAnim, (request == ANIM_REQUEST_START) ? TRUE : FALSE);
		num_valid_anims++;
	}

	if (num_valid_anims)
	{
		sendReliableMessage();
	}
}

void LLAgent::sendAnimationRequest(const LLUUID &anim_id, EAnimRequest request)
{
	if (gAgentID.isNull() || anim_id.isNull() || !mRegionp)
	{
		return;
	}

	LLMessageSystem* msg = gMessageSystem;
	msg->newMessageFast(_PREHASH_AgentAnimation);
	msg->nextBlockFast(_PREHASH_AgentData);
	msg->addUUIDFast(_PREHASH_AgentID, getID());
	msg->addUUIDFast(_PREHASH_SessionID, getSessionID());

	msg->nextBlockFast(_PREHASH_AnimationList);
	msg->addUUIDFast(_PREHASH_AnimID, (anim_id) );
	msg->addBOOLFast(_PREHASH_StartAnim, (request == ANIM_REQUEST_START) ? TRUE : FALSE);

	sendReliableMessage();
}

void LLAgent::friendsChanged()
{
	LLCollectProxyBuddies collector;
	LLAvatarTracker::instance().applyFunctor(collector);
	mProxyForAgents = collector.mProxy;
}

BOOL LLAgent::isGrantedProxy(const LLPermissions& perm)
{
	return (mProxyForAgents.count(perm.getOwner()) > 0);
}

BOOL LLAgent::allowOperation(PermissionBit op,
							 const LLPermissions& perm,
							 U64 group_proxy_power,
							 U8 god_minimum)
{
	// Check god level.
	if (getGodLevel() >= god_minimum) return TRUE;

	if (!perm.isOwned()) return FALSE;

	// A group member with group_proxy_power can act as owner.
	BOOL is_group_owned;
	LLUUID owner_id;
	perm.getOwnership(owner_id, is_group_owned);
	LLUUID group_id(perm.getGroup());
	LLUUID agent_proxy(getID());

	if (is_group_owned)
	{
		if (hasPowerInGroup(group_id, group_proxy_power))
		{
			// Let the member assume the group's id for permission requests.
			agent_proxy = owner_id;
		}
	}
	else
	{
		// Check for granted mod permissions.
		if ((PERM_OWNER != op) && isGrantedProxy(perm))
		{
			agent_proxy = owner_id;
		}
	}

	// This is the group id to use for permission requests.
	// Only group members may use this field.
	LLUUID group_proxy = LLUUID::null;
	if (group_id.notNull() && isInGroup(group_id))
	{
		group_proxy = group_id;
	}

	// We now have max ownership information.
	if (PERM_OWNER == op)
	{
		// This this was just a check for ownership, we can now return the answer.
		return (agent_proxy == owner_id);
	}

	return perm.allowOperationBy(op, agent_proxy, group_proxy);
}


void LLAgent::getName(LLString& name)
{
	// Note: assumes that name points to a buffer of at least DB_FULL_NAME_BUF_SIZE bytes.
	name.clear();

	if (mAvatarObject)
	{
		LLNameValue *first_nv = mAvatarObject->getNVPair("FirstName");
		LLNameValue *last_nv = mAvatarObject->getNVPair("LastName");
		if (first_nv && last_nv)
		{
			name = first_nv->printData() + " " + last_nv->printData();
		}
		else
		{
			llwarns << "Agent is missing FirstName and/or LastName nv pair." << llendl;
		}
	}
	else
	{
		name = gSavedSettings.getString("FirstName") + " " + gSavedSettings.getString("LastName");
	}
}

const LLColor4 &LLAgent::getEffectColor()
{
	return mEffectColor;
}

void LLAgent::setEffectColor(const LLColor4 &color)
{
	mEffectColor = color;
}

void LLAgent::initOriginGlobal(const LLVector3d &origin_global)
{
	mAgentOriginGlobal = origin_global;
}

void update_group_floaters(const LLUUID& group_id)
{
	// *HACK: added to do a live update of the groups floater if it is
	// open.
	LLFloaterGroups* fg = LLFloaterGroups::getInstance(gAgent.getID());
	if(fg)
	{
		fg->reset();
	}

	LLFloaterGroupInfo::refreshGroup(group_id);

	// update avatar info
	LLFloaterAvatarInfo* fa = LLFloaterAvatarInfo::getInstance(gAgent.getID());
	if(fa)
	{
		fa->resetGroupList();
	}

	if (gIMView)
	{
		// update the talk view
		gIMView->refresh();
	}
}

// static
void LLAgent::processAgentDropGroup(LLMessageSystem *msg, void **)
{
	LLUUID	agent_id;
	msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id );

	if (agent_id != gAgentID)
	{
		llwarns << "processAgentDropGroup for agent other than me" << llendl;
		return;
	}

	LLUUID	group_id;
	msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_GroupID, group_id );

	// Remove the group if it already exists remove it and add the new data to pick up changes.
	LLGroupData gd;
	gd.mID = group_id;
	S32 index = gAgent.mGroups.find(gd);
	if (index != -1)
	{
		gAgent.mGroups.remove(index);
		if (gAgent.getGroupID() == group_id)
		{
			gAgent.mGroupID.setNull();
			gAgent.mGroupPowers = 0;
			gAgent.mGroupName[0] = '\0';
			gAgent.mGroupTitle[0] = '\0';
		}
		
		// refresh all group information
		gAgent.sendAgentDataUpdateRequest();

		gGroupMgr->clearGroupData(group_id);
		// close the floater for this group, if any.
		LLFloaterGroupInfo::closeGroup(group_id);
		// refresh the group panel of the search window, if necessary.
		LLFloaterDirectory::refreshGroup(group_id);
	}
	else
	{
		llwarns << "processAgentDropGroup, agent is not part of group " << group_id << llendl;
	}
}

// static
void LLAgent::processAgentGroupDataUpdate(LLMessageSystem *msg, void **)
{
	LLUUID	agent_id;

	msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id );

	if (agent_id != gAgentID)
	{
		llwarns << "processAgentGroupDataUpdate for agent other than me" << llendl;
		return;
	}	
	
	S32 count = msg->getNumberOfBlocksFast(_PREHASH_GroupData);
	LLGroupData group;
	S32 index = -1;
	bool need_floater_update = false;
	char group_name[DB_GROUP_NAME_BUF_SIZE];
	for(S32 i = 0; i < count; ++i)
	{
		msg->getUUIDFast(_PREHASH_GroupData, _PREHASH_GroupID, group.mID, i);
		msg->getUUIDFast(_PREHASH_GroupData, _PREHASH_GroupInsigniaID, group.mInsigniaID, i);
		msg->getU64(_PREHASH_GroupData, "GroupPowers", group.mPowers, i);
		msg->getBOOL(_PREHASH_GroupData, "AcceptNotices", group.mAcceptNotices, i);
		msg->getS32(_PREHASH_GroupData, "Contribution", group.mContribution, i);
		msg->getStringFast(_PREHASH_GroupData, _PREHASH_GroupName, DB_GROUP_NAME_BUF_SIZE, group_name, i);
		group.mName.assign(group_name);
		
		if(group.mID.notNull())
		{
			need_floater_update = true;
			// Remove the group if it already exists remove it and add the new data to pick up changes.
			index = gAgent.mGroups.find(group);
			if (index != -1)
			{
				gAgent.mGroups.remove(index);
			}
			gAgent.mGroups.put(group);
		}
		if (need_floater_update)
		{
			update_group_floaters(group.mID);
		}
	}

}

// static
void LLAgent::processAgentDataUpdate(LLMessageSystem *msg, void **)
{
	LLUUID	agent_id;

	msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id );

	if (agent_id != gAgentID)
	{
		llwarns << "processAgentDataUpdate for agent other than me" << llendl;
		return;
	}

	msg->getStringFast(_PREHASH_AgentData, _PREHASH_GroupTitle, DB_GROUP_TITLE_BUF_SIZE, gAgent.mGroupTitle);
	LLUUID active_id;
	msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_ActiveGroupID, active_id);


	if(active_id.notNull())
	{
		gAgent.mGroupID = active_id;
		msg->getU64(_PREHASH_AgentData, "GroupPowers", gAgent.mGroupPowers);
		msg->getString(_PREHASH_AgentData, _PREHASH_GroupName, DB_GROUP_NAME_BUF_SIZE, gAgent.mGroupName);
	}
	else
	{
		gAgent.mGroupID.setNull();
		gAgent.mGroupPowers = 0;
		gAgent.mGroupName[0] = '\0';
	}		

	update_group_floaters(active_id);

	gAgent.fireEvent(new LLEvent(&gAgent, "new group"), "");
}

// static
void LLAgent::processScriptControlChange(LLMessageSystem *msg, void **)
{
	S32 block_count = msg->getNumberOfBlocks("Data");
	for (S32 block_index = 0; block_index < block_count; block_index++)
	{
		BOOL take_controls;
		U32	controls;
		BOOL passon;
		U32 i;
		msg->getBOOL("Data", "TakeControls", take_controls, block_index);
		if (take_controls)
		{
			// take controls
			msg->getU32("Data", "Controls", controls, block_index );
			msg->getBOOL("Data", "PassToAgent", passon, block_index );
			U32 total_count = 0;
			for (i = 0; i < TOTAL_CONTROLS; i++)
			{
				if (controls & ( 1 << i))
				{
					if (passon)
					{
						gAgent.mControlsTakenPassedOnCount[i]++;
					}
					else
					{
						gAgent.mControlsTakenCount[i]++;
					}
					total_count++;
				}
			}
		
			// Any control taken?  If so, might be first time.
			if (total_count > 0)
			{
				LLFirstUse::useOverrideKeys();
			}
		}
		else
		{
			// release controls
			msg->getU32("Data", "Controls", controls, block_index );
			msg->getBOOL("Data", "PassToAgent", passon, block_index );
			for (i = 0; i < TOTAL_CONTROLS; i++)
			{
				if (controls & ( 1 << i))
				{
					if (passon)
					{
						gAgent.mControlsTakenPassedOnCount[i]--;
						if (gAgent.mControlsTakenPassedOnCount[i] < 0)
						{
							gAgent.mControlsTakenPassedOnCount[i] = 0;
						}
					}
					else
					{
						gAgent.mControlsTakenCount[i]--;
						if (gAgent.mControlsTakenCount[i] < 0)
						{
							gAgent.mControlsTakenCount[i] = 0;
						}
					}
				}
			}
		}
	}
}

/*
// static
void LLAgent::processControlTake(LLMessageSystem *msg, void **)
{
	U32	controls;
	msg->getU32("Data", "Controls", controls );
	U32 passon;
	msg->getBOOL("Data", "PassToAgent", passon );

	S32 i;
	S32 total_count = 0;
	for (i = 0; i < TOTAL_CONTROLS; i++)
	{
		if (controls & ( 1 << i))
		{
			if (passon)
			{
				gAgent.mControlsTakenPassedOnCount[i]++;
			}
			else
			{
				gAgent.mControlsTakenCount[i]++;
			}
			total_count++;
		}
	}

	// Any control taken?  If so, might be first time.
	if (total_count > 0)
	{
		LLFirstUse::useOverrideKeys();
	}
}

// static
void LLAgent::processControlRelease(LLMessageSystem *msg, void **)
{
	U32	controls;
	msg->getU32("Data", "Controls", controls );
	U32 passon;
	msg->getBOOL("Data", "PassToAgent", passon );

	S32 i;
	for (i = 0; i < TOTAL_CONTROLS; i++)
	{
		if (controls & ( 1 << i))
		{
			if (passon)
			{
				gAgent.mControlsTakenPassedOnCount[i]--;
				if (gAgent.mControlsTakenPassedOnCount[i] < 0)
				{
					gAgent.mControlsTakenPassedOnCount[i] = 0;
				}
			}
			else
			{
				gAgent.mControlsTakenCount[i]--;
				if (gAgent.mControlsTakenCount[i] < 0)
				{
					gAgent.mControlsTakenCount[i] = 0;
				}
			}
		}
	}
}
*/

//static
void LLAgent::processAgentCachedTextureResponse(LLMessageSystem *mesgsys, void **user_data)
{
	gAgent.mNumPendingQueries--;

	LLVOAvatar* avatarp = gAgent.getAvatarObject();
	if (!avatarp || avatarp->isDead())
	{
		llwarns << "No avatar for user in cached texture update!" << llendl;
		return;
	}

	if (gAgent.cameraCustomizeAvatar())
	{
		// ignore baked textures when in customize mode
		return;
	}

	S32 query_id;
	mesgsys->getS32Fast(_PREHASH_AgentData, _PREHASH_SerialNum, query_id);

	S32 num_texture_blocks = mesgsys->getNumberOfBlocksFast(_PREHASH_WearableData);


	S32 num_results = 0;
	for (S32 texture_block = 0; texture_block < num_texture_blocks; texture_block++)
	{
		LLUUID texture_id;
		U8 texture_index;

		mesgsys->getUUIDFast(_PREHASH_WearableData, _PREHASH_TextureID, texture_id, texture_block);
		mesgsys->getU8Fast(_PREHASH_WearableData, _PREHASH_TextureIndex, texture_index, texture_block);

		if (texture_id.notNull() 
			&& (S32)texture_index < BAKED_TEXTURE_COUNT 
			&& gAgent.mActiveCacheQueries[ texture_index ] == query_id)
		{
			//llinfos << "Received cached texture " << (U32)texture_index << ": " << texture_id << llendl;
			avatarp->setCachedBakedTexture((LLVOAvatar::ETextureIndex)LLVOAvatar::sBakedTextureIndices[texture_index], texture_id);
			//avatarp->setTETexture( LLVOAvatar::sBakedTextureIndices[texture_index], texture_id );
			gAgent.mActiveCacheQueries[ texture_index ] = 0;
			num_results++;
		}
	}

	llinfos << "Received cached texture response for " << num_results << " textures." << llendl;

	avatarp->updateMeshTextures();

	if (gAgent.mNumPendingQueries == 0)
	{
		// RN: not sure why composites are disabled at this point
		avatarp->setCompositeUpdatesEnabled(TRUE);
		gAgent.sendAgentSetAppearance();
	}
}

BOOL LLAgent::anyControlGrabbed() const
{
	U32 i;
	for (i = 0; i < TOTAL_CONTROLS; i++)
	{
		if (gAgent.mControlsTakenCount[i] > 0)
			return TRUE;
		if (gAgent.mControlsTakenPassedOnCount[i] > 0)
			return TRUE;
	}
	return FALSE;
}

BOOL LLAgent::isControlGrabbed(S32 control_index) const
{
	return mControlsTakenCount[control_index] > 0;
}

void LLAgent::forceReleaseControls()
{
	gMessageSystem->newMessage("ForceScriptControlRelease");
	gMessageSystem->nextBlock("AgentData");
	gMessageSystem->addUUID("AgentID", getID());
	gMessageSystem->addUUID("SessionID", getSessionID());
	sendReliableMessage();
}

void LLAgent::setHomePosRegion( const U64& region_handle, const LLVector3& pos_region)
{
	mHaveHomePosition = TRUE;
	mHomeRegionHandle = region_handle;
	mHomePosRegion = pos_region;
}

BOOL LLAgent::getHomePosGlobal( LLVector3d* pos_global )
{
	if(!mHaveHomePosition)
	{
		return FALSE;
	}
	F32 x = 0;
	F32 y = 0;
	from_region_handle( mHomeRegionHandle, &x, &y);
	pos_global->setVec( x + mHomePosRegion.mV[VX], y + mHomePosRegion.mV[VY], mHomePosRegion.mV[VZ] );
	return TRUE;
}

void LLAgent::clearVisualParams(void *data)
{
	LLVOAvatar* avatarp = gAgent.getAvatarObject();
	if (avatarp)
	{
		avatarp->clearVisualParamWeights();
		avatarp->updateVisualParams();
	}
}

//---------------------------------------------------------------------------
// Teleport
//---------------------------------------------------------------------------

// teleportCore() - stuff to do on any teleport
// protected
bool LLAgent::teleportCore(bool is_local)
{
	if(TELEPORT_NONE != mTeleportState)
	{
		llwarns << "Attempt to teleport when already teleporting." << llendl;
		return false;
	}

	// Don't call LLFirstUse::useTeleport because we don't know
	// yet if the teleport will succeed.  Look in 
	// process_teleport_location_reply

	// close the map and find panels so we can see our destination
	LLFloaterWorldMap::hide(NULL);
	LLFloaterDirectory::hide(NULL);

	// Close all pie menus, deselect land, etc.
	// Don't change the camera until we know teleport succeeded. JC
	resetView(FALSE);

	// local logic
	gViewerStats->incStat(LLViewerStats::ST_TELEPORT_COUNT);
	if (!is_local)
	{
		gTeleportDisplay = TRUE;
		gAgent.setTeleportState( LLAgent::TELEPORT_START );
	}
	make_ui_sound("UISndTeleportOut");
	return true;
}

void LLAgent::teleportRequest(
	const U64& region_handle,
	const LLVector3& pos_local)
{
	LLViewerRegion* regionp = getRegion();
	if(regionp && teleportCore())
	{
		llinfos << "TeleportRequest: '" << region_handle << "':" << pos_local
				<< llendl;
		LLMessageSystem* msg = gMessageSystem;
		msg->newMessage("TeleportLocationRequest");
		msg->nextBlockFast(_PREHASH_AgentData);
		msg->addUUIDFast(_PREHASH_AgentID, getID());
		msg->addUUIDFast(_PREHASH_SessionID, getSessionID());
		msg->nextBlockFast(_PREHASH_Info);
		msg->addU64("RegionHandle", region_handle);
		msg->addVector3("Position", pos_local);
		LLVector3 look_at(0,1,0);
		msg->addVector3("LookAt", look_at);
		sendReliableMessage();
	}
}

// Landmark ID = LLUUID::null means teleport home
void LLAgent::teleportViaLandmark(const LLUUID& landmark_id)
{
	LLViewerRegion *regionp = getRegion();
	if(regionp && teleportCore())
	{
		LLMessageSystem* msg = gMessageSystem;
		msg->newMessageFast(_PREHASH_TeleportLandmarkRequest);
		msg->nextBlockFast(_PREHASH_Info);
		msg->addUUIDFast(_PREHASH_AgentID, getID());
		msg->addUUIDFast(_PREHASH_SessionID, getSessionID());
		msg->addUUIDFast(_PREHASH_LandmarkID, landmark_id);
		sendReliableMessage();
	}
}

void LLAgent::teleportViaLure(const LLUUID& lure_id, BOOL godlike)
{
	LLViewerRegion* regionp = getRegion();
	if(regionp && teleportCore())
	{
		U32 teleport_flags = 0x0;
		if (godlike)
		{
			teleport_flags |= TELEPORT_FLAGS_VIA_GODLIKE_LURE;
			teleport_flags |= TELEPORT_FLAGS_DISABLE_CANCEL;
		}
		else
		{
			teleport_flags |= TELEPORT_FLAGS_VIA_LURE;
		}

		// send the message
		LLMessageSystem* msg = gMessageSystem;
		msg->newMessageFast(_PREHASH_TeleportLureRequest);
		msg->nextBlockFast(_PREHASH_Info);
		msg->addUUIDFast(_PREHASH_AgentID, getID());
		msg->addUUIDFast(_PREHASH_SessionID, getSessionID());
		msg->addUUIDFast(_PREHASH_LureID, lure_id);
		msg->addU32("TeleportFlags", teleport_flags);
		sendReliableMessage();
	}	
}


// James Cook, July 28, 2005
void LLAgent::teleportCancel()
{
	LLViewerRegion* regionp = getRegion();
	if(regionp)
	{
		// send the message
		LLMessageSystem* msg = gMessageSystem;
		msg->newMessage("TeleportCancel");
		msg->nextBlockFast(_PREHASH_Info);
		msg->addUUIDFast(_PREHASH_AgentID, getID());
		msg->addUUIDFast(_PREHASH_SessionID, getSessionID());
		sendReliableMessage();
	}	
	gTeleportDisplay = FALSE;
	gAgent.setTeleportState( LLAgent::TELEPORT_CANCELLING );
}


void LLAgent::teleportViaLocation(const LLVector3d& pos_global)
{
	LLViewerRegion* regionp = getRegion();
	LLSimInfo* info = gWorldMap->simInfoFromPosGlobal(pos_global);
	if(regionp && info)
	{
		U32 x_pos;
		U32 y_pos;
		from_region_handle(info->mHandle, &x_pos, &y_pos);
		LLVector3 pos_local(
			(F32)(pos_global.mdV[VX] - x_pos),
			(F32)(pos_global.mdV[VY] - y_pos),
			(F32)(pos_global.mdV[VZ]));
		teleportRequest(info->mHandle, pos_local);
	}
	else if(regionp && 
		teleportCore(regionp->getHandle() == to_region_handle_global((F32)pos_global.mdV[VX], (F32)pos_global.mdV[VY])))
	{
		llwarns << "Using deprecated teleportlocationrequest." << llendl; 
		// send the message
		LLMessageSystem* msg = gMessageSystem;
		msg->newMessageFast(_PREHASH_TeleportLocationRequest);
		msg->nextBlockFast(_PREHASH_AgentData);
		msg->addUUIDFast(_PREHASH_AgentID, getID());
		msg->addUUIDFast(_PREHASH_SessionID, getSessionID());

		msg->nextBlockFast(_PREHASH_Info);
		F32 width = regionp->getWidth();
		LLVector3 pos(fmod((F32)pos_global.mdV[VX], width),
					  fmod((F32)pos_global.mdV[VY], width),
					  (F32)pos_global.mdV[VZ]);
		F32 region_x = (F32)(pos_global.mdV[VX]);
		F32 region_y = (F32)(pos_global.mdV[VY]);
		U64 region_handle = to_region_handle_global(region_x, region_y);
		msg->addU64Fast(_PREHASH_RegionHandle, region_handle);
		msg->addVector3Fast(_PREHASH_Position, pos);
		pos.mV[VX] += 1;
		msg->addVector3Fast(_PREHASH_LookAt, pos);
		sendReliableMessage();
	}
}

void LLAgent::setTeleportState(ETeleportState state)
{
	mTeleportState = state;
	if (mTeleportState > TELEPORT_NONE && gSavedSettings.getBOOL("FreezeTime"))
	{
		LLFloaterSnapshot::hide(0);
	}
}

void LLAgent::fidget()
{
	if (!getAFK())
	{
		F32 curTime = mFidgetTimer.getElapsedTimeF32();
		if (curTime > mNextFidgetTime)
		{
			// pick a random fidget anim here
			S32 oldFidget = mCurrentFidget;

			mCurrentFidget = gLindenLabRandomNumber.llrand(NUM_AGENT_STAND_ANIMS);

			if (mCurrentFidget != oldFidget)
			{
				LLAgent::stopFidget();

				
				switch(mCurrentFidget)
				{
				case 0:
					mCurrentFidget = 0;
					break;
				case 1:
					sendAnimationRequest(ANIM_AGENT_STAND_1, ANIM_REQUEST_START);
					mCurrentFidget = 1;
					break;
				case 2:
					sendAnimationRequest(ANIM_AGENT_STAND_2, ANIM_REQUEST_START);
					mCurrentFidget = 2;
					break;
				case 3:
					sendAnimationRequest(ANIM_AGENT_STAND_3, ANIM_REQUEST_START);
					mCurrentFidget = 3;
					break;
				case 4:
					sendAnimationRequest(ANIM_AGENT_STAND_4, ANIM_REQUEST_START);
					mCurrentFidget = 4;
					break;
				}
			}

			// calculate next fidget time
			mNextFidgetTime = curTime + gLindenLabRandomNumber.llfrand(MAX_FIDGET_TIME - MIN_FIDGET_TIME) + MIN_FIDGET_TIME;
		}
	}
}

void LLAgent::stopFidget()
{
	LLDynamicArray<LLUUID> anims;
	anims.put(ANIM_AGENT_STAND_1);
	anims.put(ANIM_AGENT_STAND_2);
	anims.put(ANIM_AGENT_STAND_3);
	anims.put(ANIM_AGENT_STAND_4);

	gAgent.sendAnimationRequests(anims, ANIM_REQUEST_STOP);
}


void LLAgent::requestEnterGodMode()
{
	LLMessageSystem* msg = gMessageSystem;
	msg->newMessageFast(_PREHASH_RequestGodlikePowers);
	msg->nextBlockFast(_PREHASH_AgentData);
	msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
	msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
	msg->nextBlockFast(_PREHASH_RequestBlock);
	msg->addBOOLFast(_PREHASH_Godlike, TRUE);
	msg->addUUIDFast(_PREHASH_Token, LLUUID::null);

	// simulator and userserver need to know about your request
	sendReliableMessage();
	msg->sendReliable(gUserServer);
}

void LLAgent::requestLeaveGodMode()
{
	LLMessageSystem* msg = gMessageSystem;
	msg->newMessageFast(_PREHASH_RequestGodlikePowers);
	msg->nextBlockFast(_PREHASH_AgentData);
	msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
	msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
	msg->nextBlockFast(_PREHASH_RequestBlock);
	msg->addBOOLFast(_PREHASH_Godlike, FALSE);
	msg->addUUIDFast(_PREHASH_Token, LLUUID::null);

	// simulator and userserver need to know about your request
	sendReliableMessage();
	msg->sendReliable(gUserServer);
}

// wearables
LLAgent::createStandardWearablesAllDoneCallback::~createStandardWearablesAllDoneCallback()
{
	gAgent.createStandardWearablesAllDone();
}

LLAgent::sendAgentWearablesUpdateCallback::~sendAgentWearablesUpdateCallback()
{
	gAgent.sendAgentWearablesUpdate();
}

LLAgent::addWearableToAgentInventoryCallback::addWearableToAgentInventoryCallback(
	LLPointer<LLRefCount> cb, S32 index, LLWearable* wearable, U32 todo) :
	mIndex(index),
	mWearable(wearable),
	mTodo(todo),
	mCB(cb)
{
}

void LLAgent::addWearableToAgentInventoryCallback::fire(const LLUUID& inv_item)
{
	if (inv_item.isNull())
		return;

	gAgent.addWearabletoAgentInventoryDone(mIndex, inv_item, mWearable);

	if (mTodo & CALL_UPDATE)
	{
		gAgent.sendAgentWearablesUpdate();
	}
	if (mTodo & CALL_RECOVERDONE)
	{
		gAgent.recoverMissingWearableDone();
	}
	/*
	 * Do this for every one in the loop
	 */
	if (mTodo & CALL_CREATESTANDARDDONE)
	{
		gAgent.createStandardWearablesDone(mIndex);
	}
	if (mTodo & CALL_MAKENEWOUTFITDONE)
	{
		gAgent.makeNewOutfitDone(mIndex);
	}
}

void LLAgent::addWearabletoAgentInventoryDone(
	S32 index,
	const LLUUID& item_id,
	LLWearable* wearable)
{
	if (item_id.isNull())
		return;

	LLUUID old_item_id = mWearableEntry[index].mItemID;
	mWearableEntry[index].mItemID = item_id;
	mWearableEntry[index].mWearable = wearable;
	if (old_item_id.notNull())
		gInventory.addChangedMask(LLInventoryObserver::LABEL, old_item_id);
	gInventory.addChangedMask(LLInventoryObserver::LABEL, item_id);
	LLViewerInventoryItem* item = gInventory.getItem(item_id);
	if(item && wearable)
	{
		// We're changing the asset id, so we both need to set it
		// locally via setAssetUUID() and via setTransactionID() which
		// will be decoded on the server. JC
		item->setAssetUUID(wearable->getID());
		item->setTransactionID(wearable->getTransactionID());
		gInventory.addChangedMask(LLInventoryObserver::INTERNAL, item_id);
		item->updateServer(FALSE);
	}
	gInventory.notifyObservers();
}

void LLAgent::sendAgentWearablesUpdate()
{
	// First make sure that we have inventory items for each wearable
	S32 i;
	for(i=0; i < WT_COUNT; ++i)
	{
		LLWearable* wearable = mWearableEntry[ i ].mWearable;
		if (wearable)
		{
			if( mWearableEntry[ i ].mItemID.isNull() )
			{
				LLPointer<LLInventoryCallback> cb =
					new addWearableToAgentInventoryCallback(
						LLPointer<LLRefCount>(NULL),
						i,
						wearable,
						addWearableToAgentInventoryCallback::CALL_NONE);
				addWearableToAgentInventory(cb, wearable);
			}
			else
			{
				gInventory.addChangedMask( LLInventoryObserver::LABEL,
						mWearableEntry[i].mItemID );
			}
		}
	}

	// Then make sure the inventory is in sync with the avatar.
	gInventory.notifyObservers();

	// Send the AgentIsNowWearing 
	gMessageSystem->newMessageFast(_PREHASH_AgentIsNowWearing);

	gMessageSystem->nextBlockFast(_PREHASH_AgentData);
	gMessageSystem->addUUIDFast(_PREHASH_AgentID, getID());
	gMessageSystem->addUUIDFast(_PREHASH_SessionID, getSessionID());

	lldebugs << "sendAgentWearablesUpdate()" << llendl;
	for(i=0; i < WT_COUNT; ++i)
	{
		gMessageSystem->nextBlockFast(_PREHASH_WearableData);

		U8 type_u8 = (U8)i;
		gMessageSystem->addU8Fast(_PREHASH_WearableType, type_u8 );

		LLWearable* wearable = mWearableEntry[ i ].mWearable;
		if( wearable )
		{
			//llinfos << "Sending wearable " << wearable->getName() << llendl;
			gMessageSystem->addUUIDFast(_PREHASH_ItemID, mWearableEntry[ i ].mItemID );
		}
		else
		{
			//llinfos << "Not wearing wearable type " << LLWearable::typeToTypeName((EWearableType)i) << llendl;
			gMessageSystem->addUUIDFast(_PREHASH_ItemID, LLUUID::null );
		}

		lldebugs << "       " << LLWearable::typeToTypeLabel((EWearableType)i) << ": " << (wearable ? wearable->getID() : LLUUID::null) << llendl;
	}
	gAgent.sendReliableMessage();
}

void LLAgent::saveWearable( EWearableType type, BOOL send_update )
{
	LLWearable* old_wearable = mWearableEntry[(S32)type].mWearable;
	if( old_wearable && (old_wearable->isDirty() || old_wearable->isOldVersion()) )
	{
		LLWearable* new_wearable = gWearableList.createCopyFromAvatar( old_wearable );
		mWearableEntry[(S32)type].mWearable = new_wearable;

		LLInventoryItem* item = gInventory.getItem(mWearableEntry[(S32)type].mItemID);
		if( item )
		{
			// Update existing inventory item
			LLPointer<LLViewerInventoryItem> template_item =
				new LLViewerInventoryItem(item->getUUID(),
										  item->getParentUUID(),
										  item->getPermissions(),
										  new_wearable->getID(),
										  new_wearable->getAssetType(),
										  item->getInventoryType(),
										  item->getName(),
										  item->getDescription(),
										  item->getSaleInfo(),
										  item->getFlags(),
										  item->getCreationDate());
			template_item->setTransactionID(new_wearable->getTransactionID());
			template_item->updateServer(FALSE);
			gInventory.updateItem(template_item);
		}
		else
		{
			// Add a new inventory item (shouldn't ever happen here)
			U32 todo = addWearableToAgentInventoryCallback::CALL_NONE;
			if (send_update)
			{
				todo |= addWearableToAgentInventoryCallback::CALL_UPDATE;
			}
			LLPointer<LLInventoryCallback> cb =
				new addWearableToAgentInventoryCallback(
					LLPointer<LLRefCount>(NULL),
					(S32)type,
					new_wearable,
					todo);
			addWearableToAgentInventory(cb, new_wearable);
			return;
		}

		if( send_update )
		{
			sendAgentWearablesUpdate();
		}
	}
}

void LLAgent::saveWearableAs(
	EWearableType type,
	const std::string& new_name,
	BOOL save_in_lost_and_found)
{
	if(!isWearableCopyable(type))
	{
		llwarns << "LLAgent::saveWearableAs() not copyable." << llendl;
		return;
	}
	LLWearable* old_wearable = getWearable(type);
	if(!old_wearable)
	{
		llwarns << "LLAgent::saveWearableAs() no old wearable." << llendl;
		return;
	}
	LLInventoryItem* item = gInventory.getItem(mWearableEntry[type].mItemID);
	if(!item)
	{
		llwarns << "LLAgent::saveWearableAs() no inventory item." << llendl;
		return;
	}
	std::string trunc_name(new_name);
	LLString::truncate(trunc_name, DB_INV_ITEM_NAME_STR_LEN);
	LLWearable* new_wearable = gWearableList.createCopyFromAvatar(
		old_wearable,
		trunc_name);
	LLPointer<LLInventoryCallback> cb =
		new addWearableToAgentInventoryCallback(
			LLPointer<LLRefCount>(NULL),
			type,
			new_wearable,
			addWearableToAgentInventoryCallback::CALL_UPDATE);
	LLUUID category_id;
	if (save_in_lost_and_found)
	{
		category_id = gInventory.findCategoryUUIDForType(
			LLAssetType::AT_LOST_AND_FOUND);
	}
	else
	{
		// put in same folder as original
		category_id = item->getParentUUID();
	}

	copy_inventory_item(
		gAgent.getID(),
		item->getPermissions().getOwner(),
		item->getUUID(),
		category_id,
		new_name,
		cb);

/*
	LLWearable* old_wearable = getWearable( type );
	if( old_wearable )
	{
		LLString old_name = old_wearable->getName();
		old_wearable->setName( new_name );
		LLWearable* new_wearable = gWearableList.createCopyFromAvatar( old_wearable );
		old_wearable->setName( old_name );
			
		LLUUID category_id;
		LLInventoryItem* item = gInventory.getItem( mWearableEntry[ type ].mItemID );
		if( item )
		{
			new_wearable->setPermissions(item->getPermissions());
			if (save_in_lost_and_found)
			{
				category_id = gInventory.findCategoryUUIDForType(LLAssetType::AT_LOST_AND_FOUND);
			}
			else
			{
				// put in same folder as original
				category_id = item->getParentUUID();
			}
			LLInventoryView* view = LLInventoryView::getActiveInventory();
			if(view)
			{
				view->getPanel()->setSelection(item->getUUID(), TAKE_FOCUS_NO);
			}
		}

		mWearableEntry[ type ].mWearable = new_wearable;
		LLPointer<LLInventoryCallback> cb =
			new addWearableToAgentInventoryCallback(
				LLPointer<LLRefCount>(NULL),
				type,
				addWearableToAgentInventoryCallback::CALL_UPDATE);
		addWearableToAgentInventory(cb, new_wearable, category_id);
	}
*/
}

void LLAgent::revertWearable( EWearableType type )
{
	LLWearable* wearable = mWearableEntry[(S32)type].mWearable;
	if( wearable )
	{
		wearable->writeToAvatar( TRUE );
	}
	sendAgentSetAppearance();
}

void LLAgent::revertAllWearables()
{
	for( S32 i=0; i < WT_COUNT; i++ )
	{
		revertWearable( (EWearableType)i );
	}
}

void LLAgent::saveAllWearables()
{
	//if(!gInventory.isLoaded())
	//{
	//	return;
	//}

	for( S32 i=0; i < WT_COUNT; i++ )
	{
		saveWearable( (EWearableType)i, FALSE );
	}
	sendAgentWearablesUpdate();
}

// Called when the user changes the name of a wearable inventory item that is currenlty being worn.
void LLAgent::setWearableName( const LLUUID& item_id, const std::string& new_name )
{
	for( S32 i=0; i < WT_COUNT; i++ )
	{
		if( mWearableEntry[i].mItemID == item_id )
		{
			LLWearable* old_wearable = mWearableEntry[i].mWearable;
			llassert( old_wearable );

			LLString old_name = old_wearable->getName();
			old_wearable->setName( new_name );
			LLWearable* new_wearable = gWearableList.createCopy( old_wearable );
			LLInventoryItem* item = gInventory.getItem(item_id);
			if(item)
			{
				new_wearable->setPermissions(item->getPermissions());
			}
			old_wearable->setName( old_name );

			mWearableEntry[i].mWearable = new_wearable;
			sendAgentWearablesUpdate();
			break;
		}
	}
}


BOOL LLAgent::isWearableModifiable(EWearableType type)
{
	LLUUID item_id = getWearableItem(type);
	if(!item_id.isNull())
	{
		LLInventoryItem* item = gInventory.getItem(item_id);
		if(item && item->getPermissions().allowModifyBy(gAgent.getID(),
														gAgent.getGroupID()))
		{
			return TRUE;
		}
	}
	return FALSE;
}

BOOL LLAgent::isWearableCopyable(EWearableType type)
{
	LLUUID item_id = getWearableItem(type);
	if(!item_id.isNull())
	{
		LLInventoryItem* item = gInventory.getItem(item_id);
		if(item && item->getPermissions().allowCopyBy(gAgent.getID(),
													  gAgent.getGroupID()))
		{
			return TRUE;
		}
	}
	return FALSE;
}

U32 LLAgent::getWearablePermMask(EWearableType type)
{
	LLUUID item_id = getWearableItem(type);
	if(!item_id.isNull())
	{
		LLInventoryItem* item = gInventory.getItem(item_id);
		if(item)
		{
			return item->getPermissions().getMaskOwner();
		}
	}
	return PERM_NONE;
}

LLInventoryItem* LLAgent::getWearableInventoryItem(EWearableType type)
{
	LLUUID item_id = getWearableItem(type);
	LLInventoryItem* item = NULL;
	if(item_id.notNull())
	{
		 item = gInventory.getItem(item_id);
	}
	return item;
}

LLWearable* LLAgent::getWearableFromWearableItem( const LLUUID& item_id )
{
	for( S32 i=0; i < WT_COUNT; i++ )
	{
		if( mWearableEntry[i].mItemID == item_id )
		{
			return mWearableEntry[i].mWearable;
		}
	}
	return NULL;
}


void LLAgent::sendAgentWearablesRequest()
{
	gMessageSystem->newMessageFast(_PREHASH_AgentWearablesRequest);
	gMessageSystem->nextBlockFast(_PREHASH_AgentData);
	gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() );
	gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID() );
	sendReliableMessage();
}

// Used to enable/disable menu items.
// static
BOOL LLAgent::selfHasWearable( void* userdata )
{
	EWearableType type = (EWearableType)(intptr_t)userdata;
	return gAgent.getWearable( type ) != NULL;
}

BOOL LLAgent::isWearingItem( const LLUUID& item_id )
{
	return (getWearableFromWearableItem( item_id ) != NULL);
}


// static
void LLAgent::processAgentInitialWearablesUpdate( LLMessageSystem* mesgsys, void** user_data )
{
	// We should only receive this message a single time.  Ignore subsequent AgentWearablesUpdates
	// that may result from AgentWearablesRequest having been sent more than once. 
	static BOOL first = TRUE;
	if( first )
	{
		first = FALSE;
	}
	else
	{
		return;
	}
	
	if (gNoRender)
	{
		return;
	}

	LLUUID agent_id;
	gMessageSystem->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id );

	LLVOAvatar* avatar = gAgent.getAvatarObject();
	if( avatar && (agent_id == avatar->getID()) )
	{
		gMessageSystem->getU32Fast(_PREHASH_AgentData, _PREHASH_SerialNum, gAgent.mAgentWearablesUpdateSerialNum );
		
		S32 num_wearables = gMessageSystem->getNumberOfBlocksFast(_PREHASH_WearableData);
		if( num_wearables < 4 )
		{
			// Transitional state.  Avatars should always have at least their body parts (hair, eyes, shape and skin).
			// The fact that they don't have any here (only a dummy is sent) implies that this account existed
			// before we had wearables, or that the database has gotten messed up.
			// Deal with this by creating new body parts.
			//avatar->createStandardWearables();

			// no, deal with it by noting that we need to choose a
			// gender.
			gAgent.setGenderChosen(FALSE);
			return;
		}

		//lldebugs << "processAgentInitialWearablesUpdate()" << llendl;
		// Add wearables
		LLUUID asset_id_array[ WT_COUNT ];
		S32 i;
		for( i=0; i < num_wearables; i++ )
		{
			U8 type_u8 = 0;
			gMessageSystem->getU8Fast(_PREHASH_WearableData, _PREHASH_WearableType, type_u8, i );
			if( type_u8 >= WT_COUNT )
			{
				continue;
			}
			EWearableType type = (EWearableType) type_u8;

			LLUUID item_id;
			gMessageSystem->getUUIDFast(_PREHASH_WearableData, _PREHASH_ItemID, item_id, i );

			LLUUID asset_id;
			gMessageSystem->getUUIDFast(_PREHASH_WearableData, _PREHASH_AssetID, asset_id, i );
			if( asset_id.isNull() )
			{
				LLWearable::removeFromAvatar( type, FALSE );
			}
			else
			{
				LLAssetType::EType asset_type = LLWearable::typeToAssetType( type );
				if( asset_type == LLAssetType::AT_NONE )
				{
					continue;
				}

				gAgent.mWearableEntry[type].mItemID = item_id;
				asset_id_array[type] = asset_id;
			}

			lldebugs << "       " << LLWearable::typeToTypeLabel(type) << llendl;
		}

		// now that we have the asset ids...request the wearable assets
		for( i = 0; i < WT_COUNT; i++ )
		{
			if( !gAgent.mWearableEntry[i].mItemID.isNull() )
			{
				gWearableList.getAsset( 
					asset_id_array[i],
					LLString::null,
					LLWearable::typeToAssetType( (EWearableType) i ), 
					LLAgent::onInitialWearableAssetArrived, (void*)(intptr_t)i );
			}
		}
	}
}

// A single wearable that the avatar was wearing on start-up has arrived from the database.
// static
void LLAgent::onInitialWearableAssetArrived( LLWearable* wearable, void* userdata )
{
	EWearableType type = (EWearableType)(intptr_t)userdata;

	LLVOAvatar* avatar = gAgent.getAvatarObject();
	if( !avatar )
	{
		return;
	}

	if( wearable )
	{
		llassert( type == wearable->getType() );
		gAgent.mWearableEntry[ type ].mWearable = wearable;

		// disable composites if initial textures are baked
		avatar->setupComposites();
		gAgent.queryWearableCache();

		wearable->writeToAvatar( FALSE );
		avatar->setCompositeUpdatesEnabled(TRUE);
		gInventory.addChangedMask( LLInventoryObserver::LABEL, gAgent.mWearableEntry[type].mItemID );
	}
	else
	{
		// Somehow the asset doesn't exist in the database.
		gAgent.recoverMissingWearable( type );
	}

	gInventory.notifyObservers();

	// Have all the wearables that the avatar was wearing at log-in arrived?
	if( !gAgent.mWearablesLoaded )
	{
		gAgent.mWearablesLoaded = TRUE;
		for( S32 i = 0; i < WT_COUNT; i++ )
		{
			if( !gAgent.mWearableEntry[i].mItemID.isNull() && !gAgent.mWearableEntry[i].mWearable )
			{
				gAgent.mWearablesLoaded = FALSE;
				break;
			}
		}
	}

	if( gAgent.mWearablesLoaded )
	{
		// Make sure that the server's idea of the avatar's wearables actually match the wearables.
		gAgent.sendAgentSetAppearance();

		// Check to see if there are any baked textures that we hadn't uploaded before we logged off last time.
		// If there are any, schedule them to be uploaded as soon as the layer textures they depend on arrive.
		if( !gAgent.cameraCustomizeAvatar() )
		{
			avatar->requestLayerSetUploads();
		}
	}
}

// Normally, all wearables referred to "AgentWearablesUpdate" will correspond to actual assets in the
// database.  If for some reason, we can't load one of those assets, we can try to reconstruct it so that
// the user isn't left without a shape, for example.  (We can do that only after the inventory has loaded.)
void LLAgent::recoverMissingWearable( EWearableType type )
{
	// Try to recover by replacing missing wearable with a new one.
	LLNotifyBox::showXml("ReplacedMissingWearable");
	lldebugs << "Wearable " << LLWearable::typeToTypeLabel( type ) << " could not be downloaded.  Replaced inventory item with default wearable." << llendl;
	LLWearable* new_wearable = gWearableList.createNewWearable(type);

	S32 type_s32 = (S32) type;
	mWearableEntry[type_s32].mWearable = new_wearable;
	new_wearable->writeToAvatar( TRUE );

	// Add a new one in the lost and found folder.
	// (We used to overwrite the "not found" one, but that could potentially
	// destory content.) JC
	LLUUID lost_and_found_id = 
		gInventory.findCategoryUUIDForType(LLAssetType::AT_LOST_AND_FOUND);
	LLPointer<LLInventoryCallback> cb =
		new addWearableToAgentInventoryCallback(
			LLPointer<LLRefCount>(NULL),
			type_s32,
			new_wearable,
			addWearableToAgentInventoryCallback::CALL_RECOVERDONE);
	addWearableToAgentInventory( cb, new_wearable, lost_and_found_id, TRUE);
}

void LLAgent::recoverMissingWearableDone()
{
	// Have all the wearables that the avatar was wearing at log-in arrived or been fabricated?
	mWearablesLoaded = TRUE;
	for( S32 i = 0; i < WT_COUNT; i++ )
	{
		if( !mWearableEntry[i].mItemID.isNull() && !mWearableEntry[i].mWearable )
		{
			mWearablesLoaded = FALSE;
			break;
		}
	}

	if( mWearablesLoaded )
	{
		// Make sure that the server's idea of the avatar's wearables actually match the wearables.
		sendAgentSetAppearance();
	}
	else
	{
		gInventory.addChangedMask( LLInventoryObserver::LABEL, LLUUID::null );
		gInventory.notifyObservers();
	}
}

void LLAgent::createStandardWearables(BOOL female)
{
	llwarns << "Creating Standard " << (female ? "female" : "male" )
			<< " Wearables" << llendl;

	if (mAvatarObject.isNull())
	{
		return;
	}

	if(female) mAvatarObject->setSex(SEX_FEMALE);
	else mAvatarObject->setSex(SEX_MALE);

	BOOL create[WT_COUNT] = 
	{
		TRUE,  //WT_SHAPE
		TRUE,  //WT_SKIN
		TRUE,  //WT_HAIR
		TRUE,  //WT_EYES
		TRUE,  //WT_SHIRT
		TRUE,  //WT_PANTS
		TRUE,  //WT_SHOES
		TRUE,  //WT_SOCKS
		FALSE, //WT_JACKET
		FALSE, //WT_GLOVES
		TRUE,  //WT_UNDERSHIRT
		TRUE,  //WT_UNDERPANTS
		FALSE  //WT_SKIRT
	};

	for( S32 i=0; i < WT_COUNT; i++ )
	{
		bool once = false;
		LLPointer<LLRefCount> donecb = NULL;
		if( create[i] )
		{
			if (!once)
			{
				once = true;
				donecb = new createStandardWearablesAllDoneCallback;
			}
			llassert( mWearableEntry[i].mWearable == NULL );
			LLWearable* wearable = gWearableList.createNewWearable((EWearableType)i);
			mWearableEntry[i].mWearable = wearable;
			// no need to update here...
			LLPointer<LLInventoryCallback> cb =
				new addWearableToAgentInventoryCallback(
					donecb,
					i,
					wearable,
					addWearableToAgentInventoryCallback::CALL_CREATESTANDARDDONE);
			addWearableToAgentInventory(cb, wearable, LLUUID::null, FALSE);
		}
	}
}
void LLAgent::createStandardWearablesDone(S32 index)
{
	LLWearable* wearable = mWearableEntry[index].mWearable;

	if (wearable)
	{
		wearable->writeToAvatar(TRUE);
	}
}

void LLAgent::createStandardWearablesAllDone()
{
	// ... because sendAgentWearablesUpdate will notify inventory
	// observers.
	mWearablesLoaded = TRUE; 
	sendAgentWearablesUpdate();
	sendAgentSetAppearance();

	// Treat this as the first texture entry message, if none received yet
	mAvatarObject->onFirstTEMessageReceived();
}

void LLAgent::makeNewOutfit( 
	const std::string& new_folder_name,
	const LLDynamicArray<S32>& wearables_to_include,
	const LLDynamicArray<S32>& attachments_to_include,
	BOOL rename_clothing)
{
	if (mAvatarObject.isNull())
	{
		return;
	}

	// First, make a folder in the Clothes directory.
	LLUUID folder_id = gInventory.createNewCategory(
		gInventory.findCategoryUUIDForType(LLAssetType::AT_CLOTHING),
		LLAssetType::AT_NONE,
		new_folder_name);

	bool found_first_item = false;

	///////////////////
	// Wearables

	if( wearables_to_include.count() )
	{
		// Then, iterate though each of the wearables and save copies of them in the folder.
		S32 i;
		S32 count = wearables_to_include.count();
		LLDynamicArray<LLUUID> delete_items;
		LLPointer<LLRefCount> cbdone = NULL;
		for( i = 0; i < count; ++i )
		{
			S32 index = wearables_to_include[i];
			LLWearable* old_wearable = mWearableEntry[ index ].mWearable;
			if( old_wearable )
			{
				std::string new_name;
				LLWearable* new_wearable;
				new_wearable = gWearableList.createCopy(old_wearable);
				if (rename_clothing)
				{
					new_name = new_folder_name;
					new_name.append(" ");
					new_name.append(old_wearable->getTypeLabel());
					LLString::truncate(new_name, DB_INV_ITEM_NAME_STR_LEN);
					new_wearable->setName(new_name);
				}

				LLViewerInventoryItem* item = gInventory.getItem(mWearableEntry[index].mItemID);
				S32 todo = addWearableToAgentInventoryCallback::CALL_NONE;
				if (!found_first_item)
				{
					found_first_item = true;
					/* set the focus to the first item */
					todo |= addWearableToAgentInventoryCallback::CALL_MAKENEWOUTFITDONE;
					/* send the agent wearables update when done */
					cbdone = new sendAgentWearablesUpdateCallback;
				}
				LLPointer<LLInventoryCallback> cb =
					new addWearableToAgentInventoryCallback(
						cbdone,
						index,
						new_wearable,
						todo);
				if (isWearableCopyable((EWearableType)index))
				{
					copy_inventory_item(
						gAgent.getID(),
						item->getPermissions().getOwner(),
						item->getUUID(),
						folder_id,
						new_name,
						cb);
				}
				else
				{
					move_inventory_item(
						gAgent.getID(),
						gAgent.getSessionID(),
						item->getUUID(),
						folder_id,
						new_name,
						cb);
				}
			}
		}
		gInventory.notifyObservers();
	}


	///////////////////
	// Attachments

	if( attachments_to_include.count() )
	{
		BOOL msg_started = FALSE;
		LLMessageSystem* msg = gMessageSystem;
		S32 i;
		for( i = 0; i < attachments_to_include.count(); i++ )
		{
			S32 attachment_pt = attachments_to_include[i];
			LLViewerJointAttachment* attachment = mAvatarObject->mAttachmentPoints.getIfThere( attachment_pt );
			if(!attachment) continue;
			LLViewerObject* attached_object = attachment->getObject(0);
			if(!attached_object) continue;
			const LLUUID& item_id = attachment->getItemID();
			if(item_id.isNull()) continue;
			LLInventoryItem* item = gInventory.getItem(item_id);
			if(!item) continue;
			if(!msg_started)
			{
				msg_started = TRUE;
				msg->newMessage("CreateNewOutfitAttachments");
				msg->nextBlock("AgentData");
				msg->addUUID("AgentID", getID());
				msg->addUUID("SessionID", getSessionID());
				msg->nextBlock("HeaderData");
				msg->addUUID("NewFolderID", folder_id);
			}
			msg->nextBlock("ObjectData");
			msg->addUUID("OldItemID", item_id);
			msg->addUUID("OldFolderID", item->getParentUUID());
		}

		if( msg_started )
		{
			sendReliableMessage();
		}

	} 
}

void LLAgent::makeNewOutfitDone(S32 index)
{
	LLUUID first_item_id = mWearableEntry[index].mItemID;
	// Open the inventory and select the first item we added.
	if( first_item_id.notNull() )
	{
		LLInventoryView* view = LLInventoryView::getActiveInventory();
		if(view)
		{
			view->getPanel()->setSelection(first_item_id, TAKE_FOCUS_NO);
		}
	}
}


void LLAgent::addWearableToAgentInventory(
	LLPointer<LLInventoryCallback> cb,
	LLWearable* wearable,
	const LLUUID& category_id,
	BOOL notify)
{
	create_inventory_item(
		gAgent.getID(),
		gAgent.getSessionID(),
		category_id,
		wearable->getTransactionID(),
		wearable->getName(),
		wearable->getDescription(),
		wearable->getAssetType(),
		LLInventoryType::IT_WEARABLE,
		wearable->getType(),
		wearable->getPermissions().getMaskNextOwner(),
		cb);
}

//-----------------------------------------------------------------------------
// sendAgentSetAppearance()
//-----------------------------------------------------------------------------
void LLAgent::sendAgentSetAppearance()
{
	if (mAvatarObject.isNull()) return;

	if (mNumPendingQueries > 0 && !gAgent.cameraCustomizeAvatar()) 
	{
		return;
	}

	llinfos << "TAT: Sent AgentSetAppearance: " <<
		(( mAvatarObject->getTEImage( LLVOAvatar::TEX_HEAD_BAKED )->getID() != IMG_DEFAULT_AVATAR )  ? "HEAD " : "head " ) <<
		(( mAvatarObject->getTEImage( LLVOAvatar::TEX_UPPER_BAKED )->getID() != IMG_DEFAULT_AVATAR ) ? "UPPER " : "upper " ) <<
		(( mAvatarObject->getTEImage( LLVOAvatar::TEX_LOWER_BAKED )->getID() != IMG_DEFAULT_AVATAR ) ? "LOWER " : "lower " ) <<
		(( mAvatarObject->getTEImage( LLVOAvatar::TEX_EYES_BAKED )->getID() != IMG_DEFAULT_AVATAR )  ? "EYES" : "eyes" ) << llendl;
	//dumpAvatarTEs( "sendAgentSetAppearance()" );

	LLMessageSystem* msg = gMessageSystem;
	msg->newMessageFast(_PREHASH_AgentSetAppearance);
	msg->nextBlockFast(_PREHASH_AgentData);
	msg->addUUIDFast(_PREHASH_AgentID, getID());
	msg->addUUIDFast(_PREHASH_SessionID, getSessionID());

	// correct for the collisiton tolerance (to make it look like the 
	// agent is actually walking on the ground/object)
	// NOTE -- when we start correcting all of the other Havok geometry 
	// to compensate for the COLLISION_TOLERANCE ugliness we will have 
	// to tweak this number again
	LLVector3 body_size = mAvatarObject->mBodySize;
	msg->addVector3Fast(_PREHASH_Size, body_size);	

	// To guard against out of order packets
	// Note: always start by sending 1.  This resets the server's count. 0 on the server means "uninitialized"
	mAppearanceSerialNum++;
	msg->addU32Fast(_PREHASH_SerialNum, mAppearanceSerialNum );

	// is texture data current relative to wearables?
	// KLW - TAT this will probably need to check the local queue.
	BOOL textures_current = !mAvatarObject->hasPendingBakedUploads() && mWearablesLoaded;

	S32 baked_texture_index;
	for( baked_texture_index = 0; baked_texture_index < BAKED_TEXTURE_COUNT; baked_texture_index++ )
	{
		S32 tex_index = LLVOAvatar::sBakedTextureIndices[baked_texture_index];

		// if we're not wearing a skirt, we don't need the texture to be baked
		if (tex_index == LLVOAvatar::TEX_SKIRT_BAKED && !mAvatarObject->isWearingWearableType(WT_SKIRT))
		{
			continue;
		}

		// IMG_DEFAULT_AVATAR means not baked
		if (mAvatarObject->getTEImage( tex_index)->getID() == IMG_DEFAULT_AVATAR)
		{
			textures_current = FALSE;
			break;
		}
	}

	// only update cache entries if we have all our baked textures
	if (textures_current)
	{
		llinfos << "TAT: Sending cached texture data" << llendl;
		for (baked_texture_index = 0; baked_texture_index < BAKED_TEXTURE_COUNT; baked_texture_index++)
		{
			LLUUID hash;

			for( S32 wearable_num = 0; wearable_num < MAX_WEARABLES_PER_LAYERSET; wearable_num++ )
			{
				EWearableType wearable_type = WEARABLE_BAKE_TEXTURE_MAP[baked_texture_index][wearable_num];

				LLWearable* wearable = getWearable( wearable_type );
				if (wearable)
				{
					hash ^= wearable->getID();
				}
			}

			if (hash.notNull())
			{
				hash ^= BAKED_TEXTURE_HASH[baked_texture_index];
			}

			S32 tex_index = LLVOAvatar::sBakedTextureIndices[baked_texture_index];

			msg->nextBlockFast(_PREHASH_WearableData);
			msg->addUUIDFast(_PREHASH_CacheID, hash);
			msg->addU8Fast(_PREHASH_TextureIndex, (U8)tex_index);
		}
	}

	msg->nextBlockFast(_PREHASH_ObjectData);
	mAvatarObject->packTEMessage( gMessageSystem );

	S32 transmitted_params = 0;
	for (LLViewerVisualParam* param = (LLViewerVisualParam*)mAvatarObject->getFirstVisualParam();
		 param;
		 param = (LLViewerVisualParam*)mAvatarObject->getNextVisualParam())
	{
		F32 param_value = param->getWeight();
	
		if (param->getGroup() == VISUAL_PARAM_GROUP_TWEAKABLE)
		{
			msg->nextBlockFast(_PREHASH_VisualParam );
			
			// We don't send the param ids.  Instead, we assume that the receiver has the same params in the same sequence.
			U8 new_weight = F32_to_U8(param_value, param->getMinWeight(), param->getMaxWeight());
			msg->addU8Fast(_PREHASH_ParamValue, new_weight );
			transmitted_params++;
		}
	}

//	llinfos << "Avatar XML num VisualParams transmitted = " << transmitted_params << llendl;
	sendReliableMessage();
}

void LLAgent::sendAgentDataUpdateRequest()
{
	gMessageSystem->newMessageFast(_PREHASH_AgentDataUpdateRequest);
	gMessageSystem->nextBlockFast(_PREHASH_AgentData);
	gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
	gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
	sendReliableMessage();
}

void LLAgent::removeWearable( EWearableType type )
{
	LLWearable* old_wearable = mWearableEntry[ type ].mWearable;

	if ( (gAgent.mAccess < SIM_ACCESS_MATURE)
		 && (type == WT_UNDERSHIRT || type == WT_UNDERPANTS))
	{
		// Can't take off underclothing in simple UI mode or on PG accounts
		return;
	}

	if( old_wearable )
	{
		if( old_wearable->isDirty() )
		{
			// Bring up view-modal dialog: Save changes? Yes, No, Cancel
			gViewerWindow->alertXml("RemoveWearableSave", LLAgent::onRemoveWearableDialog, (void*)(S32)type );
			return;
		}
		else
		{
			removeWearableFinal( type );
		}
	}
}

// static 
void LLAgent::onRemoveWearableDialog( S32 option, void* userdata )
{
	EWearableType type = (EWearableType)(intptr_t)userdata;
	switch( option )
	{
	case 0:  // "Save"
		gAgent.saveWearable( type );
		gAgent.removeWearableFinal( type );
		break;

	case 1:  // "Don't Save"
		gAgent.removeWearableFinal( type );
		break;

	case 2: // "Cancel"
		break;

	default:
		llassert(0);
		break;
	}
}

// Called by removeWearable() and onRemoveWearableDialog() to actually do the removal.
void LLAgent::removeWearableFinal( EWearableType type )
{
	LLWearable* old_wearable = mWearableEntry[ type ].mWearable;

	gInventory.addChangedMask( LLInventoryObserver::LABEL, mWearableEntry[type].mItemID );

	mWearableEntry[ type ].mWearable = NULL;
	mWearableEntry[ type ].mItemID.setNull();

	queryWearableCache();

	if( old_wearable )
	{
		old_wearable->removeFromAvatar( TRUE );
	}

	// Update the server
	sendAgentWearablesUpdate(); 
	sendAgentSetAppearance();
	gInventory.notifyObservers();
}

void LLAgent::copyWearableToInventory( EWearableType type )
{
	LLWearable* wearable = mWearableEntry[ type ].mWearable;
	if( wearable )
	{
		// Save the old wearable if it has changed.
		if( wearable->isDirty() )
		{
			wearable = gWearableList.createCopyFromAvatar( wearable );
			mWearableEntry[ type ].mWearable = wearable;
		}

		// Make a new entry in the inventory.  (Put it in the same folder as the original item if possible.)
		LLUUID category_id;
		LLInventoryItem* item = gInventory.getItem( mWearableEntry[ type ].mItemID );
		if( item )
		{
			category_id = item->getParentUUID();
			wearable->setPermissions(item->getPermissions());
		}
		LLPointer<LLInventoryCallback> cb =
			new addWearableToAgentInventoryCallback(
				LLPointer<LLRefCount>(NULL),
				type,
				wearable);
		addWearableToAgentInventory(cb, wearable, category_id);
	}
}


// A little struct to let setWearable() communicate more than one value with onSetWearableDialog().
struct LLSetWearableData
{
	LLSetWearableData( const LLUUID& new_item_id, LLWearable* new_wearable ) :
		mNewItemID( new_item_id ), mNewWearable( new_wearable ) {}
	LLUUID				mNewItemID;
	LLWearable*			mNewWearable;
};

BOOL LLAgent::needsReplacement(EWearableType  wearableType, S32 remove)
{
	return TRUE;
	/*if (remove) return TRUE;
	
	return getWearable(wearableType) ? TRUE : FALSE;*/
}

// Assumes existing wearables are not dirty.
void LLAgent::setWearableOutfit( 
	const LLInventoryItem::item_array_t& items,
	const LLDynamicArray< LLWearable* >& wearables,
	BOOL remove )
{
	lldebugs << "setWearableOutfit() start" << llendl;

	BOOL wearables_to_remove[WT_COUNT];
	wearables_to_remove[WT_SHAPE]		= FALSE;
	wearables_to_remove[WT_SKIN]		= FALSE;
	wearables_to_remove[WT_HAIR]		= FALSE;
	wearables_to_remove[WT_EYES]		= FALSE;
	wearables_to_remove[WT_SHIRT]		= remove;
	wearables_to_remove[WT_PANTS]		= remove;
	wearables_to_remove[WT_SHOES]		= remove;
	wearables_to_remove[WT_SOCKS]		= remove;
	wearables_to_remove[WT_JACKET]		= remove;
	wearables_to_remove[WT_GLOVES]		= remove;
	wearables_to_remove[WT_UNDERSHIRT]	= (gAgent.mAccess >= SIM_ACCESS_MATURE) & remove;
	wearables_to_remove[WT_UNDERPANTS]	= (gAgent.mAccess >= SIM_ACCESS_MATURE) & remove;
	wearables_to_remove[WT_SKIRT]		= remove;

	S32 count = wearables.count();
	llassert( items.count() == count );

	S32 i;
	for( i = 0; i < count; i++ )
	{
		LLWearable* new_wearable = wearables[i];
		LLPointer<LLInventoryItem> new_item = items[i];

		EWearableType type = new_wearable->getType();
		wearables_to_remove[type] = FALSE;

		LLWearable* old_wearable = mWearableEntry[ type ].mWearable;
		if( old_wearable )
		{
			const LLUUID& old_item_id = mWearableEntry[ type ].mItemID;
			if( (old_wearable->getID() == new_wearable->getID()) &&
				(old_item_id == new_item->getUUID()) )
			{
				lldebugs << "No change to wearable asset and item: " << LLWearable::typeToTypeName( type ) << llendl;
				continue;
			}

			gInventory.addChangedMask(LLInventoryObserver::LABEL, old_item_id);

			// Assumes existing wearables are not dirty.
			if( old_wearable->isDirty() )
			{
				llassert(0);
				continue;
			}
		}

		mWearableEntry[ type ].mItemID = new_item->getUUID();
		mWearableEntry[ type ].mWearable = new_wearable;
	}

	std::vector<LLWearable*> wearables_being_removed;

	for( i = 0; i < WT_COUNT; i++ )
	{
		if( wearables_to_remove[i] )
		{
			wearables_being_removed.push_back(mWearableEntry[ i ].mWearable);
			mWearableEntry[ i ].mWearable = NULL;
			
			gInventory.addChangedMask(LLInventoryObserver::LABEL, mWearableEntry[ i ].mItemID);
			mWearableEntry[ i ].mItemID.setNull();
		}
	}

	gInventory.notifyObservers();

	queryWearableCache();

	std::vector<LLWearable*>::iterator wearable_iter;

	for( wearable_iter = wearables_being_removed.begin(); 
		wearable_iter != wearables_being_removed.end();
		++wearable_iter)
	{
		LLWearable* wearablep = *wearable_iter;
		if (wearablep)
		{
			wearablep->removeFromAvatar( TRUE );
		}
	}

	for( i = 0; i < count; i++ )
	{
		wearables[i]->writeToAvatar( TRUE );
	}

	LLFloaterCustomize::setCurrentWearableType( WT_SHAPE );

	// Start rendering & update the server
	mWearablesLoaded = TRUE; 
	sendAgentWearablesUpdate();
	sendAgentSetAppearance();

	lldebugs << "setWearableOutfit() end" << llendl;
}


// User has picked "wear on avatar" from a menu.
void LLAgent::setWearable( LLInventoryItem* new_item, LLWearable* new_wearable )
{
	EWearableType type = new_wearable->getType();

	LLWearable* old_wearable = mWearableEntry[ type ].mWearable;
	if( old_wearable )
	{
		const LLUUID& old_item_id = mWearableEntry[ type ].mItemID;
		if( (old_wearable->getID() == new_wearable->getID()) &&
			(old_item_id == new_item->getUUID()) )
		{
			lldebugs << "No change to wearable asset and item: " << LLWearable::typeToTypeName( type ) << llendl;
			return;
		}

		if( old_wearable->isDirty() )
		{
			// Bring up modal dialog: Save changes? Yes, No, Cancel
			gViewerWindow->alertXml( "SetWearableSave", LLAgent::onSetWearableDialog,
				new LLSetWearableData( new_item->getUUID(), new_wearable ));
			return;
		}
	}

	setWearableFinal( new_item, new_wearable );
}

// static 
void LLAgent::onSetWearableDialog( S32 option, void* userdata )
{
	LLSetWearableData* data = (LLSetWearableData*)userdata;
	LLInventoryItem* new_item = gInventory.getItem( data->mNewItemID );
	if( !new_item )
	{
		delete data;
		return;
	}

	switch( option )
	{
	case 0:  // "Save"
		gAgent.saveWearable( data->mNewWearable->getType() );
		gAgent.setWearableFinal( new_item, data->mNewWearable );
		break;

	case 1:  // "Don't Save"
		gAgent.setWearableFinal( new_item, data->mNewWearable );
		break;

	case 2: // "Cancel"
		break;

	default:
		llassert(0);
		break;
	}

	delete data;
}

// Called from setWearable() and onSetWearableDialog() to actually set the wearable.
void LLAgent::setWearableFinal( LLInventoryItem* new_item, LLWearable* new_wearable )
{
	EWearableType type = new_wearable->getType();

	// Replace the old wearable with a new one.
	llassert( new_item->getAssetUUID() == new_wearable->getID() );
	LLUUID old_item_id = mWearableEntry[ type ].mItemID;
	mWearableEntry[ type ].mItemID = new_item->getUUID();
	mWearableEntry[ type ].mWearable = new_wearable;

	if (old_item_id.notNull())
	{
		gInventory.addChangedMask( LLInventoryObserver::LABEL, old_item_id );
		gInventory.notifyObservers();
	}

	//llinfos << "LLVOAvatar::setWearable()" << llendl;
	queryWearableCache();
	new_wearable->writeToAvatar( TRUE );

	// Update the server
	sendAgentWearablesUpdate();
	sendAgentSetAppearance();
}

void LLAgent::queryWearableCache()
{
	if (!mWearablesLoaded)
	{
		return;
	}

	// Look up affected baked textures.
	// If they exist:
	//		disallow updates for affected layersets (until dataserver responds with cache request.)
	//		If cache miss…turn updates back on and invalidate composite.
	//		If cache hit, modify baked texture entries.
	//
	// Cache requests contain list of hashes for each baked texture entry.
	// Response is list of valid baked texture assets. (same message)

	gMessageSystem->newMessageFast(_PREHASH_AgentCachedTexture);
	gMessageSystem->nextBlockFast(_PREHASH_AgentData);
	gMessageSystem->addUUIDFast(_PREHASH_AgentID, getID());
	gMessageSystem->addUUIDFast(_PREHASH_SessionID, getSessionID());
	gMessageSystem->addS32Fast(_PREHASH_SerialNum, mTextureCacheQueryID);

	S32 num_queries = 0;
	for (S32 baked_texture_index = 0; baked_texture_index < BAKED_TEXTURE_COUNT; baked_texture_index++)
	{
		LLUUID hash;
		for (S32 wearable_num = 0; wearable_num < MAX_WEARABLES_PER_LAYERSET; wearable_num++)
		{
			EWearableType wearable_type = WEARABLE_BAKE_TEXTURE_MAP[baked_texture_index][wearable_num];
				
			LLWearable* wearable = getWearable( wearable_type );
			if (wearable)
			{
				hash ^= wearable->getID();
			}
		}
		if (hash.notNull())
		{
			hash ^= BAKED_TEXTURE_HASH[baked_texture_index];
			num_queries++;
			//FIXME: make sure at least one request gets packed

			//llinfos << "Requesting texture for hash " << hash << " in baked texture slot " << baked_texture_index << llendl;
			gMessageSystem->nextBlockFast(_PREHASH_WearableData);
			gMessageSystem->addUUIDFast(_PREHASH_ID, hash);
			gMessageSystem->addU8Fast(_PREHASH_TextureIndex, (U8)baked_texture_index);
		}

		mActiveCacheQueries[ baked_texture_index ] = mTextureCacheQueryID;
	}

	llinfos << "Requesting texture cache entry for " << num_queries << " baked textures" << llendl;
	gMessageSystem->sendReliable(getRegion()->getHost());
	mNumPendingQueries++;
	mTextureCacheQueryID++;
}

// User has picked "remove from avatar" from a menu.
// static
void LLAgent::userRemoveWearable( void* userdata )
{
	EWearableType type = (EWearableType)(intptr_t)userdata;
	
	if( !(type==WT_SHAPE || type==WT_SKIN || type==WT_HAIR ) ) //&&
		//!((gAgent.mAccess >= SIM_ACCESS_MATURE) && ( type==WT_UNDERPANTS || type==WT_UNDERSHIRT )) )
	{
		gAgent.removeWearable( type );
	}
}

void LLAgent::userRemoveAllClothes( void* userdata )
{
	// We have to do this up front to avoid having to deal with the case of multiple wearables being dirty.
	if( gFloaterCustomize )
	{
		gFloaterCustomize->askToSaveAllIfDirty( LLAgent::userRemoveAllClothesStep2, NULL );
	}
	else
	{
		LLAgent::userRemoveAllClothesStep2( TRUE, NULL );
	}
}

void LLAgent::userRemoveAllClothesStep2( BOOL proceed, void* userdata )
{
	if( proceed )
	{
		gAgent.removeWearable( WT_SHIRT );
		gAgent.removeWearable( WT_PANTS );
		gAgent.removeWearable( WT_SHOES );
		gAgent.removeWearable( WT_SOCKS );
		gAgent.removeWearable( WT_JACKET );
		gAgent.removeWearable( WT_GLOVES );
		gAgent.removeWearable( WT_UNDERSHIRT );
		gAgent.removeWearable( WT_UNDERPANTS );
		gAgent.removeWearable( WT_SKIRT );
	}
}

void LLAgent::userRemoveAllAttachments( void* userdata )
{
	LLVOAvatar* avatarp = gAgent.getAvatarObject();
	if(!avatarp)
	{
		llwarns << "No avatar found." << llendl;
		return;
	}

	gMessageSystem->newMessage("ObjectDetach");
	gMessageSystem->nextBlockFast(_PREHASH_AgentData);
	gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() );
	gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());

	LLViewerJointAttachment* attachment;
	for (attachment = avatarp->mAttachmentPoints.getFirstData();
		 attachment;
		 attachment = avatarp->mAttachmentPoints.getNextData())
	{
		LLViewerObject* objectp = attachment->getObject(0);
		if (objectp)
		{
			gMessageSystem->nextBlockFast(_PREHASH_ObjectData);
			gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, objectp->getLocalID());
		}
	}
	gMessageSystem->sendReliable( gAgent.getRegionHost() );
}

bool LLAgent::LLHideGroupTitleListener::handleEvent(LLPointer<LLEvent> event, const LLSD &userdata)
{
	gAgent.setHideGroupTitle(event->getValue());
	return true;

}

bool LLAgent::LLEffectColorListener::handleEvent(LLPointer<LLEvent> event, const LLSD &userdata)
{
	gAgent.setEffectColor(LLColor4(event->getValue()));
	return true;
}

void LLAgent::observeFriends()
{
	if(!mFriendObserver)
	{
		mFriendObserver = new LLAgentFriendObserver;
		LLAvatarTracker::instance().addObserver(mFriendObserver);
		friendsChanged();
	}
}

// EOF
