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

#if LL_MSVC
// disable warning about boost::lexical_cast returning uninitialized data
// when it fails to parse the string
#pragma warning (disable:4701)
#endif

#include "llviewerprecompiledheaders.h"

#include "llvoavatarself.h"
#include "llvoavatar.h"

#include "pipeline.h"

#include "llagent.h" //  Get state values from here
#include "llattachmentsmgr.h"
#include "llagentcamera.h"
#include "llagentwearables.h"
#include "llhudeffecttrail.h"
#include "llhudmanager.h"
#include "llinventoryfunctions.h"
#include "lllocaltextureobject.h"
#include "llnotificationsutil.h"
#include "llselectmgr.h"
#include "lltoolgrab.h"	// for needsRenderBeam
#include "lltoolmgr.h" // for needsRenderBeam
#include "lltoolmorph.h"
#include "lltrans.h"
#include "llviewercamera.h"
#include "llviewercontrol.h"
#include "llviewermenu.h"
#include "llviewerobjectlist.h"
#include "llviewerstats.h"
#include "llviewerregion.h"
#include "llviewertexlayer.h"
#include "llviewerwearable.h"
#include "llappearancemgr.h"
#include "llmeshrepository.h"
#include "llvovolume.h"
#include "llsdutil.h"
#include "llstartup.h"
#include "llsdserialize.h"
#include "llcallstack.h"
#include "llcorehttputil.h"

// Firestorm includes
#include "fslslbridge.h"
#include "lggbeammaps.h"
#include "piemenu.h"
#include "pieslice.h"
#include "pieseparator.h"
// [RLVa:KB] - Checked: RLVa-2.0.2
#include "rlvhandler.h"
#include "rlvhelper.h"
#include "rlvlocks.h"
// [/RLVa:KB]


// <FS:Ansariel> [Legacy Bake]
#include "llviewernetwork.h"
// </FS:Ansariel> [Legacy Bake]

#if LL_MSVC
// disable boost::lexical_cast warning
#pragma warning (disable:4702)
#endif

#include <boost/lexical_cast.hpp>

LLPointer<LLVOAvatarSelf> gAgentAvatarp = NULL;

BOOL isAgentAvatarValid()
{
	return (gAgentAvatarp.notNull() && gAgentAvatarp->isValid());
}

void selfStartPhase(const std::string& phase_name)
{
	if (isAgentAvatarValid())
	{
		gAgentAvatarp->startPhase(phase_name);
	}
}

void selfStopPhase(const std::string& phase_name, bool err_check)
{
	if (isAgentAvatarValid())
	{
		gAgentAvatarp->stopPhase(phase_name, err_check);
	}
}

void selfClearPhases()
{
	if (isAgentAvatarValid())
	{
		gAgentAvatarp->clearPhases();
	}
}

using namespace LLAvatarAppearanceDefines;


LLSD summarize_by_buckets(std::vector<LLSD> in_records, std::vector<std::string> by_fields, std::string val_field);

/*********************************************************************************
 **                                                                             **
 ** Begin private LLVOAvatarSelf Support classes
 **
 **/

struct LocalTextureData
{
	LocalTextureData() : 
		mIsBakedReady(false), 
		mDiscard(MAX_DISCARD_LEVEL+1), 
		mImage(NULL), 
		mWearableID(IMG_DEFAULT_AVATAR),
		mTexEntry(NULL)
	{}
	LLPointer<LLViewerFetchedTexture> mImage;
	bool mIsBakedReady;
	S32 mDiscard;
	LLUUID mWearableID;	// UUID of the wearable that this texture belongs to, not of the image itself
	LLTextureEntry *mTexEntry;
};

//-----------------------------------------------------------------------------
// Callback data
//-----------------------------------------------------------------------------


/**
 **
 ** End LLVOAvatarSelf Support classes
 **                                                                             **
 *********************************************************************************/


//-----------------------------------------------------------------------------
// Static Data
//-----------------------------------------------------------------------------
S32Bytes LLVOAvatarSelf::sScratchTexBytes(0);
std::map< LLGLenum, LLGLuint*> LLVOAvatarSelf::sScratchTexNames;


/*********************************************************************************
 **                                                                             **
 ** Begin LLVOAvatarSelf Constructor routines
 **
 **/

LLVOAvatarSelf::LLVOAvatarSelf(const LLUUID& id,
							   const LLPCode pcode,
							   LLViewerRegion* regionp) :
	LLVOAvatar(id, pcode, regionp),
// [RLVa:KB] - Checked: 2012-07-28 (RLVa-1.4.7)
	mAttachmentSignal(NULL),
// [/RLVa:KB]
	mScreenp(NULL),
	mLastRegionHandle(0),
	mRegionCrossingCount(0),
	mIsCrossingRegion(false), // <FS:Ansariel> FIRE-12004: Attachments getting lost on TP
	// Value outside legal range, so will always be a mismatch the
	// first time through.
	mLastHoverOffsetSent(LLVector3(0.0f, 0.0f, -999.0f)),
    mInitialMetric(true),
    mMetricSequence(0)
{
// <FS:Ansariel> [Legacy Bake]
#ifdef OPENSIM
	if (!LLGridManager::getInstance()->isInSecondLife())
	{
		gAgentWearables.setAvatarObject(this);
	}
#endif
// </FS:Ansariel> [Legacy Bake]

	mMotionController.mIsSelf = TRUE;

	LL_DEBUGS() << "Marking avatar as self " << id << LL_ENDL;
}

// Called periodically for diagnostics, return true when done.
bool output_self_av_texture_diagnostics()
{
	if (!isAgentAvatarValid())
		return true; // done checking

	gAgentAvatarp->outputRezDiagnostics();

	return false;
}

bool update_avatar_rez_metrics()
{
	if (!isAgentAvatarValid())
		return true;
	
	gAgentAvatarp->updateAvatarRezMetrics(false);

	return false;
}

// <FS:Ansariel> [Legacy Bake]
bool check_for_unsupported_baked_appearance()
{
	if (!isAgentAvatarValid())
		return true;

	gAgentAvatarp->checkForUnsupportedServerBakeAppearance();
	return false;
}
// </FS:Ansariel> [Legacy Bake]

void LLVOAvatarSelf::initInstance()
{
	BOOL status = TRUE;
	// creates hud joint(mScreen) among other things
	status &= loadAvatarSelf();

	// adds attachment points to mScreen among other things
	LLVOAvatar::initInstance();

	LL_INFOS() << "Self avatar object created. Starting timer." << LL_ENDL;
	mDebugSelfLoadTimer.reset();
	// clear all times to -1 for debugging
	for (U32 i =0; i < LLAvatarAppearanceDefines::TEX_NUM_INDICES; ++i)
	{
		for (U32 j = 0; j <= MAX_DISCARD_LEVEL; ++j)
		{
			mDebugTextureLoadTimes[i][j] = -1.0f;
		}
	}

	for (U32 i =0; i < LLAvatarAppearanceDefines::BAKED_NUM_INDICES; ++i)
	{
		mDebugBakedTextureTimes[i][0] = -1.0f;
		mDebugBakedTextureTimes[i][1] = -1.0f;
	}

// [RLVa:KB] - Checked: 2010-12-12 (RLVa-1.2.2c) | Added: RLVa-1.2.2c
	RlvAttachPtLookup::initLookupTable();
// [/RLVa:KB]

	status &= buildMenus();
	if (!status)
	{
		LL_ERRS() << "Unable to load user's avatar" << LL_ENDL;
		return;
	}

	setHoverIfRegionEnabled();

	//doPeriodically(output_self_av_texture_diagnostics, 30.0);
	doPeriodically(update_avatar_rez_metrics, 5.0);
	// <FS:Ansariel> [Legacy Bake]
	doPeriodically(check_for_unsupported_baked_appearance, 120.0);
	doPeriodically(boost::bind(&LLVOAvatarSelf::checkStuckAppearance, this), 30.0);

    mInitFlags |= 1<<2;
}

void LLVOAvatarSelf::setHoverIfRegionEnabled()
{
	if (getRegion() && getRegion()->simulatorFeaturesReceived())
	{
		if (getRegion()->avatarHoverHeightEnabled())
		{
			F32 hover_z = gSavedPerAccountSettings.getF32("AvatarHoverOffsetZ");
			setHoverOffset(LLVector3(0.0, 0.0, llclamp(hover_z,MIN_HOVER_Z,MAX_HOVER_Z)));
			LL_INFOS("Avatar") << avString() << " set hover height from debug setting " << hover_z << LL_ENDL;
		}
		// <FS:Ansariel> [Legacy bake]
		else if (!isUsingServerBakes())
		{
			computeBodySize();
		}
		// </FS:Ansariel> [Legacy bake]
		else 
		{
			setHoverOffset(LLVector3(0.0, 0.0, 0.0));
			LL_INFOS("Avatar") << avString() << " zeroing hover height, region does not support" << LL_ENDL;
		}
	}
	else
	{
		LL_INFOS("Avatar") << avString() << " region or simulator features not known, no change on hover" << LL_ENDL;
		if (getRegion())
		{
			getRegion()->setSimulatorFeaturesReceivedCallback(boost::bind(&LLVOAvatarSelf::onSimulatorFeaturesReceived,this,_1));
		}

	}
}

bool LLVOAvatarSelf::checkStuckAppearance()
{
	const F32 CONDITIONAL_UNSTICK_INTERVAL = 300.0;
	const F32 UNCONDITIONAL_UNSTICK_INTERVAL = 600.0;
	
	if (gAgentWearables.isCOFChangeInProgress())
	{
		LL_DEBUGS("Avatar") << "checking for stuck appearance" << LL_ENDL;
		F32 change_time = gAgentWearables.getCOFChangeTime();
		LL_DEBUGS("Avatar") << "change in progress for " << change_time << " seconds" << LL_ENDL;
		S32 active_hp = LLAppearanceMgr::instance().countActiveHoldingPatterns();
		LL_DEBUGS("Avatar") << "active holding patterns " << active_hp << " seconds" << LL_ENDL;
		S32 active_copies = LLAppearanceMgr::instance().getActiveCopyOperations();
		LL_DEBUGS("Avatar") << "active copy operations " << active_copies << LL_ENDL;

		if ((change_time > CONDITIONAL_UNSTICK_INTERVAL && active_copies == 0) ||
			(change_time > UNCONDITIONAL_UNSTICK_INTERVAL))
		{
			gAgentWearables.notifyLoadingFinished();
		}
	}

	// Return false to continue running check periodically.
	return LLApp::isExiting();
}

// virtual
void LLVOAvatarSelf::markDead()
{
	mBeam = NULL;
	LLVOAvatar::markDead();
}

/*virtual*/ BOOL LLVOAvatarSelf::loadAvatar()
{
	BOOL success = LLVOAvatar::loadAvatar();

	// set all parameters stored directly in the avatar to have
	// the isSelfParam to be TRUE - this is used to prevent
	// them from being animated or trigger accidental rebakes
	// when we copy params from the wearable to the base avatar.
	for (LLViewerVisualParam* param = (LLViewerVisualParam*) getFirstVisualParam(); 
		 param;
		 param = (LLViewerVisualParam*) getNextVisualParam())
	{
		if (param->getWearableType() != LLWearableType::WT_INVALID)
		{
			param->setIsDummy(TRUE);
		}
	}

	return success;
}


BOOL LLVOAvatarSelf::loadAvatarSelf()
{
	BOOL success = TRUE;
	// avatar_skeleton.xml
	if (!buildSkeletonSelf(sAvatarSkeletonInfo))
	{
		LL_WARNS() << "avatar file: buildSkeleton() failed" << LL_ENDL;
		return FALSE;
	}

	return success;
}

BOOL LLVOAvatarSelf::buildSkeletonSelf(const LLAvatarSkeletonInfo *info)
{
	// add special-purpose "screen" joint
	mScreenp = new LLViewerJoint("mScreen", NULL);
	// for now, put screen at origin, as it is only used during special
	// HUD rendering mode
	F32 aspect = LLViewerCamera::getInstance()->getAspect();
	LLVector3 scale(1.f, aspect, 1.f);
	mScreenp->setScale(scale);
	// SL-315
	mScreenp->setWorldPosition(LLVector3::zero);
	// need to update screen agressively when sidebar opens/closes, for example
	mScreenp->mUpdateXform = TRUE;
	return TRUE;
}

BOOL LLVOAvatarSelf::buildMenus()
{
	//-------------------------------------------------------------------------
	// build the attach and detach menus
	//-------------------------------------------------------------------------
	gAttachBodyPartPieMenus[0] = NULL;

	LLContextMenu::Params params;
	params.label(LLTrans::getString("BodyPartsRightArm"));
	params.name(params.label);
	params.visible(false);
	gAttachBodyPartPieMenus[1] = LLUICtrlFactory::create<LLContextMenu> (params);

	params.label(LLTrans::getString("BodyPartsHead"));
	params.name(params.label);
	gAttachBodyPartPieMenus[2] = LLUICtrlFactory::create<LLContextMenu> (params);

	params.label(LLTrans::getString("BodyPartsLeftArm"));
	params.name(params.label);
	gAttachBodyPartPieMenus[3] = LLUICtrlFactory::create<LLContextMenu> (params);

	gAttachBodyPartPieMenus[4] = NULL;

	params.label(LLTrans::getString("BodyPartsLeftLeg"));
	params.name(params.label);
	gAttachBodyPartPieMenus[5] = LLUICtrlFactory::create<LLContextMenu> (params);

	params.label(LLTrans::getString("BodyPartsTorso"));
	params.name(params.label);
	gAttachBodyPartPieMenus[6] = LLUICtrlFactory::create<LLContextMenu> (params);

	params.label(LLTrans::getString("BodyPartsRightLeg"));
	params.name(params.label);
	gAttachBodyPartPieMenus[7] = LLUICtrlFactory::create<LLContextMenu> (params);

	params.label(LLTrans::getString("BodyPartsEnhancedSkeleton"));
	params.name(params.label);
	gAttachBodyPartPieMenus[8] = LLUICtrlFactory::create<LLContextMenu>(params);

	gDetachBodyPartPieMenus[0] = NULL;

	params.label(LLTrans::getString("BodyPartsRightArm"));
	params.name(params.label);
	gDetachBodyPartPieMenus[1] = LLUICtrlFactory::create<LLContextMenu> (params);

	params.label(LLTrans::getString("BodyPartsHead"));
	params.name(params.label);
	gDetachBodyPartPieMenus[2] = LLUICtrlFactory::create<LLContextMenu> (params);

	params.label(LLTrans::getString("BodyPartsLeftArm"));
	params.name(params.label);
	gDetachBodyPartPieMenus[3] = LLUICtrlFactory::create<LLContextMenu> (params);

	gDetachBodyPartPieMenus[4] = NULL;

	params.label(LLTrans::getString("BodyPartsLeftLeg"));
	params.name(params.label);
	gDetachBodyPartPieMenus[5] = LLUICtrlFactory::create<LLContextMenu> (params);

	params.label(LLTrans::getString("BodyPartsTorso"));
	params.name(params.label);
	gDetachBodyPartPieMenus[6] = LLUICtrlFactory::create<LLContextMenu> (params);

	params.label(LLTrans::getString("BodyPartsRightLeg"));
	params.name(params.label);
	gDetachBodyPartPieMenus[7] = LLUICtrlFactory::create<LLContextMenu> (params);

	params.label(LLTrans::getString("BodyPartsEnhancedSkeleton"));
	params.name(params.label);
	gDetachBodyPartPieMenus[8] = LLUICtrlFactory::create<LLContextMenu>(params);

// <FS:Zi> Pie menu
	//-------------------------------------------------------------------------
	// build the attach and detach pie menus
	//-------------------------------------------------------------------------
	PieMenu::Params pieParams;
	pieParams.visible(false);

	gPieAttachBodyPartMenus[0] = NULL;

	pieParams.label(LLTrans::getString("BodyPartsRightArm"));
	pieParams.name(pieParams.label);
	gPieAttachBodyPartMenus[1] = LLUICtrlFactory::create<PieMenu> (pieParams);

	pieParams.label(LLTrans::getString("BodyPartsHead"));
	pieParams.name(pieParams.label);
	gPieAttachBodyPartMenus[2] = LLUICtrlFactory::create<PieMenu> (pieParams);

	pieParams.label(LLTrans::getString("BodyPartsLeftArm"));
	pieParams.name(pieParams.label);
	gPieAttachBodyPartMenus[3] = LLUICtrlFactory::create<PieMenu> (pieParams);

	gPieAttachBodyPartMenus[4] = NULL;

	pieParams.label(LLTrans::getString("BodyPartsLeftLeg"));
	pieParams.name(pieParams.label);
	gPieAttachBodyPartMenus[5] = LLUICtrlFactory::create<PieMenu> (pieParams);

	pieParams.label(LLTrans::getString("BodyPartsTorso"));
	pieParams.name(pieParams.label);
	gPieAttachBodyPartMenus[6] = LLUICtrlFactory::create<PieMenu> (pieParams);

	pieParams.label(LLTrans::getString("BodyPartsRightLeg"));
	pieParams.name(pieParams.label);
	gPieAttachBodyPartMenus[7] = LLUICtrlFactory::create<PieMenu> (pieParams);

	gPieDetachBodyPartMenus[0] = NULL;

	pieParams.label(LLTrans::getString("BodyPartsRightArm"));
	pieParams.name(pieParams.label);
	gPieDetachBodyPartMenus[1] = LLUICtrlFactory::create<PieMenu> (pieParams);

	pieParams.label(LLTrans::getString("BodyPartsHead"));
	pieParams.name(pieParams.label);
	gPieDetachBodyPartMenus[2] = LLUICtrlFactory::create<PieMenu> (pieParams);

	pieParams.label(LLTrans::getString("BodyPartsLeftArm"));
	pieParams.name(pieParams.label);
	gPieDetachBodyPartMenus[3] = LLUICtrlFactory::create<PieMenu> (pieParams);

	gPieDetachBodyPartMenus[4] = NULL;

	pieParams.label(LLTrans::getString("BodyPartsLeftLeg"));
	pieParams.name(pieParams.label);
	gPieDetachBodyPartMenus[5] = LLUICtrlFactory::create<PieMenu> (pieParams);

	pieParams.label(LLTrans::getString("BodyPartsTorso"));
	pieParams.name(pieParams.label);
	gPieDetachBodyPartMenus[6] = LLUICtrlFactory::create<PieMenu> (pieParams);

	pieParams.label(LLTrans::getString("BodyPartsRightLeg"));
	pieParams.name(pieParams.label);
	gPieDetachBodyPartMenus[7] = LLUICtrlFactory::create<PieMenu> (pieParams);
// </FS:Zi> Pie menu

	for (S32 i = 0; i < 9; i++)
	{
		if (gAttachBodyPartPieMenus[i])
		{
			gAttachPieMenu->appendContextSubMenu( gAttachBodyPartPieMenus[i] );
		}
		else
		{
			for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); 
				 iter != mAttachmentPoints.end();
				 ++iter)
			{
				LLViewerJointAttachment* attachment = iter->second;
				if (attachment && attachment->getGroup() == i)
				{
					LLMenuItemCallGL::Params item_params;

					std::string sub_piemenu_name = attachment->getName();
					if (LLTrans::getString(sub_piemenu_name) != "")
					{
						item_params.label = LLTrans::getString(sub_piemenu_name);
					}
					else
					{
						item_params.label = sub_piemenu_name;
					}
					item_params.name =(item_params.label );
					item_params.on_click.function_name = "Object.AttachToAvatar";
					item_params.on_click.parameter = iter->first;
					// [RLVa:KB] - No changes, but we do need the parameter to always be idxAttachPt for object_selected_and_point_valid()
					item_params.on_enable.function_name = "Object.EnableWear";
					item_params.on_enable.parameter = iter->first;
					LLMenuItemCallGL* item = LLUICtrlFactory::create<LLMenuItemCallGL>(item_params);

					gAttachPieMenu->addChild(item);

					break;

				}
			}
		}

		if (gDetachBodyPartPieMenus[i])
		{
			gDetachPieMenu->appendContextSubMenu( gDetachBodyPartPieMenus[i] );
			gDetachAttSelfMenu->appendContextSubMenu(gDetachBodyPartPieMenus[i]);
			gDetachAvatarMenu->appendContextSubMenu(gDetachBodyPartPieMenus[i]);
			// <FS:Ansariel> FIRE-7893: "Detach" sub-menu on inspect menu without function
			gInspectSelfDetachMenu->appendContextSubMenu( gDetachBodyPartPieMenus[i] );
		}
		else
		{
			for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); 
				 iter != mAttachmentPoints.end();
				 ++iter)
			{
				LLViewerJointAttachment* attachment = iter->second;
				if (attachment && attachment->getGroup() == i)
				{
					LLMenuItemCallGL::Params item_params;
					std::string sub_piemenu_name = attachment->getName();
					if (LLTrans::getString(sub_piemenu_name) != "")
					{
						item_params.label = LLTrans::getString(sub_piemenu_name);
					}
					else
					{
						item_params.label = sub_piemenu_name;
					}
					item_params.name =(item_params.label );
					item_params.on_click.function_name = "Attachment.DetachFromPoint";
					item_params.on_click.parameter = iter->first;
					item_params.on_enable.function_name = "Attachment.PointFilled";
					item_params.on_enable.parameter = iter->first;
					LLMenuItemCallGL* item = LLUICtrlFactory::create<LLMenuItemCallGL>(item_params);

					gDetachPieMenu->addChild(item);
					gDetachAttSelfMenu->addChild(LLUICtrlFactory::create<LLMenuItemCallGL>(item_params));
					gDetachAvatarMenu->addChild(LLUICtrlFactory::create<LLMenuItemCallGL>(item_params));
					// <FS:Ansariel> FIRE-7893: "Detach" sub-menu on inspect menu without function
					gInspectSelfDetachMenu->addChild(item);

					break;
				}
			}
		}
	}
	

	// <FS:Zi> Pie menu
	for (S32 i = 0; i < PIE_MAX_SLICES; i++)
	{
		// Skip former slices of left and right hand that have been moved into the
		// corresponding Left/Right arm groups;
		// 0 is Bento enhanced skeleton now, manually added in menu_pie_avatar_self.xml
		// and menu_pie_object.xml
		if (i == 0)
		{
			continue;
		}
		else if (i == 4)
		{
			PieSeparator::Params pie_sep_params;
			pieParams.name("AttachSlot4Separator");
			gPieAttachMenu->addChild(LLUICtrlFactory::create<PieSeparator>(pie_sep_params));
			pieParams.name("DetachSlot4Separator");
			gPieDetachMenu->addChild(LLUICtrlFactory::create<PieSeparator>(pie_sep_params));
			continue;
		}

		if (gPieAttachBodyPartMenus[i])
		{
			gPieAttachMenu->appendContextSubMenu(gPieAttachBodyPartMenus[i]);
		}
		else
		{
			for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); 
				 iter != mAttachmentPoints.end();
				 ++iter)
			{
				LLViewerJointAttachment* attachment = iter->second;
				if (attachment && attachment->getGroup() == i)
				{
					PieSlice::Params slice_params;

					std::string sub_piemenu_name = attachment->getName();
					if (LLTrans::getString(sub_piemenu_name) != "")
					{
						slice_params.label = LLTrans::getString(sub_piemenu_name);
					}
					else
					{
						slice_params.label = sub_piemenu_name;
					}

					slice_params.name = (slice_params.label);
					slice_params.on_click.function_name = "Object.AttachToAvatar";
					slice_params.on_click.parameter = iter->first;
					slice_params.on_enable.function_name = "Object.EnableWear";
					slice_params.on_enable.parameter = iter->first;
					PieSlice* slice = LLUICtrlFactory::create<PieSlice>(slice_params);

					gPieAttachMenu->addChild(slice);

					break;
				}
			}
		}

		if (gPieDetachBodyPartMenus[i])
		{
			gPieDetachMenu->appendContextSubMenu(gPieDetachBodyPartMenus[i]);
		}
		else
		{
			for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); 
				 iter != mAttachmentPoints.end();
				 ++iter)
			{
				LLViewerJointAttachment* attachment = iter->second;
				if (attachment && attachment->getGroup() == i)
				{
					PieSlice::Params slice_params;
					std::string sub_piemenu_name = attachment->getName();
					if (LLTrans::getString(sub_piemenu_name) != "")
					{
						slice_params.label = LLTrans::getString(sub_piemenu_name);
					}
					else
					{
						slice_params.label = sub_piemenu_name;
					}

					slice_params.name = (slice_params.label);
					slice_params.on_click.function_name = "Attachment.DetachFromPoint";
					slice_params.on_click.parameter = iter->first;
					slice_params.on_enable.function_name = "Attachment.PointFilled";
					slice_params.on_enable.parameter = iter->first;
					PieSlice* slice = LLUICtrlFactory::create<PieSlice>(slice_params);

					gPieDetachMenu->addChild(slice);

					break;
				}
			}
		}
	}
	// </FS:Zi> Pie menu

	// add screen attachments
	for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); 
		 iter != mAttachmentPoints.end();
		 ++iter)
	{
		LLViewerJointAttachment* attachment = iter->second;
		if (attachment && attachment->getGroup() == 9)
		{
			LLMenuItemCallGL::Params item_params;
			PieSlice::Params slice_params;	// <FS:Zi> Pie menu
			std::string sub_piemenu_name = attachment->getName();
			if (LLTrans::getString(sub_piemenu_name) != "")
			{
				item_params.label = LLTrans::getString(sub_piemenu_name);
				slice_params.label = LLTrans::getString(sub_piemenu_name);	// <FS:Zi> Pie menu
			}
			else
			{
				item_params.label = sub_piemenu_name;
				slice_params.label = sub_piemenu_name;	// <FS:Zi> Pie menu
			}
			item_params.name =(item_params.label );
			item_params.on_click.function_name = "Object.AttachToAvatar";
			item_params.on_click.parameter = iter->first;
			// [RLVa:KB] - No changes, but we do need the parameter to always be idxAttachPt for object_selected_and_point_valid()
			item_params.on_enable.function_name = "Object.EnableWear";
			item_params.on_enable.parameter = iter->first;
			LLMenuItemCallGL* item = LLUICtrlFactory::create<LLMenuItemCallGL>(item_params);
			gAttachScreenPieMenu->addChild(item);

			item_params.on_click.function_name = "Attachment.DetachFromPoint";
			item_params.on_click.parameter = iter->first;
			item_params.on_enable.function_name = "Attachment.PointFilled";
			item_params.on_enable.parameter = iter->first;
			item = LLUICtrlFactory::create<LLMenuItemCallGL>(item_params);
			gDetachScreenPieMenu->addChild(item);
			gDetachHUDAttSelfMenu->addChild(LLUICtrlFactory::create<LLMenuItemCallGL>(item_params));
			gDetachHUDAvatarMenu->addChild(LLUICtrlFactory::create<LLMenuItemCallGL>(item_params));
			// <FS:Ansariel> FIRE-7893: "Detach" sub-menu on inspect menu without function
			gInspectSelfDetachScreenMenu->addChild(LLUICtrlFactory::create<LLMenuItemCallGL>(item_params));

			// <FS:Zi> Pie menu
			slice_params.name =(slice_params.label );
			slice_params.on_click.function_name = "Object.AttachToAvatar";
			slice_params.on_click.parameter = iter->first;
			slice_params.on_enable.function_name = "Object.EnableWear";
			slice_params.on_enable.parameter = iter->first;
			PieSlice* slice = LLUICtrlFactory::create<PieSlice>(slice_params);
			gPieAttachScreenMenu->addChild(slice);

			slice_params.on_click.function_name = "Attachment.DetachFromPoint";
			slice_params.on_click.parameter = iter->first;
			slice_params.on_enable.function_name = "Attachment.PointFilled";
			slice_params.on_enable.parameter = iter->first;
			slice = LLUICtrlFactory::create<PieSlice>(slice_params);
			gPieDetachScreenMenu->addChild(slice);
			// </FS:Zi> Pie menu
		}
	}

	for (S32 pass = 0; pass < 2; pass++)
	{
		// *TODO: Skinning - gAttachSubMenu is an awful, awful hack
		if (!gAttachSubMenu)
		{
			break;
		}
		for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); 
			 iter != mAttachmentPoints.end();
			 ++iter)
		{
			LLViewerJointAttachment* attachment = iter->second;
			if (attachment->getIsHUDAttachment() != (pass == 1))
			{
				continue;
			}
			LLMenuItemCallGL::Params item_params;
			std::string sub_piemenu_name = attachment->getName();
			if (LLTrans::getString(sub_piemenu_name) != "")
			{
				item_params.label = LLTrans::getString(sub_piemenu_name);
			}
			else
			{
				item_params.label = sub_piemenu_name;
			}
			item_params.name =(item_params.label );
			item_params.on_click.function_name = "Object.AttachToAvatar";
			item_params.on_click.parameter = iter->first;
			// [RLVa:KB] - No changes, but we do need the parameter to always be idxAttachPt for object_selected_and_point_valid()
			item_params.on_enable.function_name = "Object.EnableWear";
			item_params.on_enable.parameter = iter->first;
			//* TODO: Skinning:
			//LLSD params;
			//params["index"] = iter->first;
			//params["label"] = attachment->getName();
			//item->addEventHandler("on_enable", LLMenuItemCallGL::MenuCallback().function_name("Attachment.Label").parameter(params));
				
			LLMenuItemCallGL* item = LLUICtrlFactory::create<LLMenuItemCallGL>(item_params);
			gAttachSubMenu->addChild(item);

			item_params.on_click.function_name = "Attachment.DetachFromPoint";
			item_params.on_click.parameter = iter->first;
			item_params.on_enable.function_name = "Attachment.PointFilled";
			item_params.on_enable.parameter = iter->first;
			//* TODO: Skinning: item->addEventHandler("on_enable", LLMenuItemCallGL::MenuCallback().function_name("Attachment.Label").parameter(params));

			item = LLUICtrlFactory::create<LLMenuItemCallGL>(item_params);
			gDetachSubMenu->addChild(item);
		}
		if (pass == 0)
		{
			// put separator between non-hud and hud attachments
			gAttachSubMenu->addSeparator();
			gDetachSubMenu->addSeparator();
		}
	}

	for (S32 group = 0; group < 9; group++)
	{
		// skip over groups that don't have sub menus
		if (!gAttachBodyPartPieMenus[group] || !gDetachBodyPartPieMenus[group])
		{
			continue;
		}

		std::multimap<S32, S32> attachment_pie_menu_map;

		// gather up all attachment points assigned to this group, and throw into map sorted by pie slice number
		for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); 
			 iter != mAttachmentPoints.end();
			 ++iter)
		{
			LLViewerJointAttachment* attachment = iter->second;
			if(attachment && attachment->getGroup() == group)
			{
				// use multimap to provide a partial order off of the pie slice key
				S32 pie_index = attachment->getPieSlice();
				attachment_pie_menu_map.insert(std::make_pair(pie_index, iter->first));
			}
		}

		// add in requested order to pie menu, inserting separators as necessary
		for (std::multimap<S32, S32>::iterator attach_it = attachment_pie_menu_map.begin();
			 attach_it != attachment_pie_menu_map.end(); ++attach_it)
		{
			S32 attach_index = attach_it->second;

			// <FS:Ansariel> Skip bridge attachment spot
			if (attach_index == 127)
			{
				continue;
			}
			// </FS:Ansariel>

			LLViewerJointAttachment* attachment = get_if_there(mAttachmentPoints, attach_index, (LLViewerJointAttachment*)NULL);
			if (attachment)
			{
				LLMenuItemCallGL::Params item_params;
				item_params.name = attachment->getName();
				item_params.label = LLTrans::getString(attachment->getName());
				item_params.on_click.function_name = "Object.AttachToAvatar";
				item_params.on_click.parameter = attach_index;
				// [RLVa:KB] - No changes, but we do need the parameter to always be idxAttachPt for object_selected_and_point_valid()
				item_params.on_enable.function_name = "Object.EnableWear";
				item_params.on_enable.parameter = attach_index;

				LLMenuItemCallGL* item = LLUICtrlFactory::create<LLMenuItemCallGL>(item_params);
				gAttachBodyPartPieMenus[group]->addChild(item);
					
				item_params.on_click.function_name = "Attachment.DetachFromPoint";
				item_params.on_click.parameter = attach_index;
				item_params.on_enable.function_name = "Attachment.PointFilled";
				item_params.on_enable.parameter = attach_index;
				item = LLUICtrlFactory::create<LLMenuItemCallGL>(item_params);
				gDetachBodyPartPieMenus[group]->addChild(item);
			}
		}
	}

	// <FS:Zi> Pie menu
	for (S32 group = 0; group < PIE_MAX_SLICES; group++)
	{
		// skip over groups that don't have sub menus
		if (!gPieAttachBodyPartMenus[group] || !gPieDetachBodyPartMenus[group])
		{
			continue;
		}

		std::multimap<S32, S32> attachment_pie_menu_map;

		// gather up all attachment points assigned to this group, and throw into map sorted by pie slice number
		for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); 
			 iter != mAttachmentPoints.end();
			 ++iter)
		{
			LLViewerJointAttachment* attachment = iter->second;
			if(attachment && attachment->getGroup() == group)
			{
				// use multimap to provide a partial order off of the pie slice key
				S32 pie_index = attachment->getPieSlice();
				attachment_pie_menu_map.insert(std::make_pair(pie_index, iter->first));
			}
		}

		// add in requested order to pie menu, inserting separators as necessary
		for (std::multimap<S32, S32>::iterator attach_it = attachment_pie_menu_map.begin();
			 attach_it != attachment_pie_menu_map.end(); ++attach_it)
		{
			S32 attach_index = attach_it->second;

			// <FS:Ansariel> Skip bridge attachment spot
			if (attach_index == 127)
			{
				continue;
			}
			// </FS:Ansariel>

			LLViewerJointAttachment* attachment = get_if_there(mAttachmentPoints, attach_index, (LLViewerJointAttachment*)NULL);
			if (attachment)
			{
				PieSlice::Params slice_params;
				slice_params.name = attachment->getName();
				slice_params.label = LLTrans::getString(attachment->getName());
				slice_params.on_click.function_name = "Object.AttachToAvatar";
				slice_params.on_click.parameter = attach_index;
				slice_params.on_enable.function_name = "Object.EnableWear";
				slice_params.on_enable.parameter = attach_index;

				PieSlice* slice = LLUICtrlFactory::create<PieSlice>(slice_params);
				gPieAttachBodyPartMenus[group]->addChild(slice);

				slice_params.on_click.function_name = "Attachment.DetachFromPoint";
				slice_params.on_click.parameter = attach_index;
				slice_params.on_enable.function_name = "Attachment.PointFilled";
				slice_params.on_enable.parameter = attach_index;
				slice = LLUICtrlFactory::create<PieSlice>(slice_params);
				gPieDetachBodyPartMenus[group]->addChild(slice);
			}
		}
	}
	// </FS:Zi> Pie menu

	return TRUE;
}

void LLVOAvatarSelf::cleanup()
{
	markDead();
 	delete mScreenp;
 	mScreenp = NULL;
	mRegionp = NULL;
}

LLVOAvatarSelf::~LLVOAvatarSelf()
{
	cleanup();

// [RLVa:KB] - Checked: 2012-07-28 (RLVa-1.4.7)
	delete mAttachmentSignal;
// [/RLVa:KB]
}

/**
 **
 ** End LLVOAvatarSelf Constructor routines
 **                                                                             **
 *********************************************************************************/

// virtual
bool LLVOAvatarSelf::updateCharacter(LLAgent &agent)
{
	// update screen joint size
	if (mScreenp)
	{
		F32 aspect = LLViewerCamera::getInstance()->getAspect();
		LLVector3 scale(1.f, aspect, 1.f);
		mScreenp->setScale(scale);
		mScreenp->updateWorldMatrixChildren();
		resetHUDAttachments();
	}
	
	return LLVOAvatar::updateCharacter(agent);
}

// virtual
BOOL LLVOAvatarSelf::isValid() const
{
	// <FS:Ansariel> Skip unregistering attachments during shutdown
	//return ((getRegion() != NULL) && !isDead());
	return ((getRegion() != NULL) && !isDead() && !LLApp::isExiting());
}

// virtual
void LLVOAvatarSelf::idleUpdate(LLAgent &agent, const F64 &time)
{
	if (isValid())
	{
		LLVOAvatar::idleUpdate(agent, time);
		idleUpdateTractorBeam();

		// <FS:Ansariel> Make sure to definitely stop typing
		if ((gAgent.getTypingTime() > LLAgent::TYPING_TIMEOUT_SECS) && (gAgent.getRenderState() & AGENT_STATE_TYPING))
		{
			gAgent.stopTyping();
		}
		// </FS:Ansariel>
	}
}

// virtual
//<FS:ND> Query by JointKey rather than just a string, the key can be a U32 index for faster lookup
//LLJoint *LLVOAvatarSelf::getJoint( const std::string &name )
LLJoint *LLVOAvatarSelf::getJoint( const JointKey &name )
// </FS:ND>
{
    LLJoint *jointp = NULL;
    jointp = LLVOAvatar::getJoint(name);
	if (!jointp && mScreenp)
	{
		//<FS:ND> Query by JointKey rather than just a string, the key can be a U32 index for faster lookup
		//jointp = mScreenp->findJoint(name);
		jointp = mScreenp->findJoint(name.mName);
		// </FS:ND>
        if (jointp)
        {
            //<FS:ND> Query by JointKey rather than just a string, the key can be a U32 index for faster lookup
            //mJointMap[name] = jointp;
            mJointMap[name.mKey] = jointp;
            // </FS:ND>
        }
	}
    if (jointp && jointp != mScreenp && jointp != mRoot)
    {
        llassert(LLVOAvatar::getJoint((S32)jointp->getJointNum())==jointp);
    }
    return jointp;
}

// virtual
// <FS:Ansariel> [Legacy Bake]
//BOOL LLVOAvatarSelf::setVisualParamWeight(const LLVisualParam *which_param, F32 weight)
BOOL LLVOAvatarSelf::setVisualParamWeight(const LLVisualParam *which_param, F32 weight, BOOL upload_bake)
{
	if (!which_param)
	{
		return FALSE;
	}
	LLViewerVisualParam *param = (LLViewerVisualParam*) LLCharacter::getVisualParam(which_param->getID());
	// <FS:Ansariel> [Legacy Bake]
	//return setParamWeight(param,weight);
	return setParamWeight(param,weight,upload_bake);
}

// virtual
// <FS:Ansariel> [Legacy Bake]
//BOOL LLVOAvatarSelf::setVisualParamWeight(const char* param_name, F32 weight)
BOOL LLVOAvatarSelf::setVisualParamWeight(const char* param_name, F32 weight, BOOL upload_bake)
{
	if (!param_name)
	{
		return FALSE;
	}
	LLViewerVisualParam *param = (LLViewerVisualParam*) LLCharacter::getVisualParam(param_name);
	// <FS:Ansariel> [Legacy Bake]
	//return setParamWeight(param,weight);
	return setParamWeight(param,weight,upload_bake);
}

// virtual
// <FS:Ansariel> [Legacy Bake]
//BOOL LLVOAvatarSelf::setVisualParamWeight(const LLViewerVisualParam *param, F32 weight)
BOOL LLVOAvatarSelf::setVisualParamWeight(S32 index, F32 weight, BOOL upload_bake)
{
	LLViewerVisualParam *param = (LLViewerVisualParam*) LLCharacter::getVisualParam(index);
	// <FS:Ansariel> [Legacy Bake]
	//return setParamWeight(param,weight);
	return setParamWeight(param,weight,upload_bake);
}

// <FS:Ansariel> [Legacy Bake]
//BOOL LLVOAvatarSelf::setParamWeight(const LLViewerVisualParam *param, F32 weight)
BOOL LLVOAvatarSelf::setParamWeight(const LLViewerVisualParam *param, F32 weight, BOOL upload_bake)
{
	if (!param)
	{
		return FALSE;
	}

	if (param->getCrossWearable())
	{
		LLWearableType::EType type = (LLWearableType::EType)param->getWearableType();
		U32 size = gAgentWearables.getWearableCount(type);
		for (U32 count = 0; count < size; ++count)
		{
			LLViewerWearable *wearable = gAgentWearables.getViewerWearable(type,count);
			if (wearable)
			{
				// <FS:Ansariel> [Legacy Bake]
				//wearable->setVisualParamWeight(param->getID(), weight);
				wearable->setVisualParamWeight(param->getID(), weight, upload_bake);
			}
		}
	}

	// <FS:Ansariel> [Legacy Bake]
	//return LLCharacter::setVisualParamWeight(param,weight);
	return LLCharacter::setVisualParamWeight(param,weight,upload_bake);
}

/*virtual*/ 
void LLVOAvatarSelf::updateVisualParams()
{
	LLVOAvatar::updateVisualParams();
}

void LLVOAvatarSelf::writeWearablesToAvatar()
{
	for (U32 type = 0; type < LLWearableType::WT_COUNT; type++)
	{
		LLWearable *wearable = gAgentWearables.getTopWearable((LLWearableType::EType)type);
		if (wearable)
		{
			wearable->writeToAvatar(this);
		}
	}

}

/*virtual*/
void LLVOAvatarSelf::idleUpdateAppearanceAnimation()
{
	// Animate all top-level wearable visual parameters
	// <FS:Ansariel> [Legacy Bake]
	//gAgentWearables.animateAllWearableParams(calcMorphAmount());
	gAgentWearables.animateAllWearableParams(calcMorphAmount(), FALSE);

	// Apply wearable visual params to avatar
	writeWearablesToAvatar();

	//allow avatar to process updates
	LLVOAvatar::idleUpdateAppearanceAnimation();

}

// virtual
void LLVOAvatarSelf::requestStopMotion(LLMotion* motion)
{
	// Only agent avatars should handle the stop motion notifications.

	// Notify agent that motion has stopped
	gAgent.requestStopMotion(motion);
}

// virtual
bool LLVOAvatarSelf::hasMotionFromSource(const LLUUID& source_id)
{
	AnimSourceIterator motion_it = mAnimationSources.find(source_id);
	return motion_it != mAnimationSources.end();
}

// virtual
void LLVOAvatarSelf::stopMotionFromSource(const LLUUID& source_id)
{
	for (AnimSourceIterator motion_it = mAnimationSources.find(source_id); motion_it != mAnimationSources.end(); )
	{
		gAgent.sendAnimationRequest(motion_it->second, ANIM_REQUEST_STOP);
		mAnimationSources.erase(motion_it++);
	}


	LLViewerObject* object = gObjectList.findObject(source_id);
	if (object)
	{
		object->setFlagsWithoutUpdate(FLAGS_ANIM_SOURCE, FALSE);
	}
}

void LLVOAvatarSelf::setLocalTextureTE(U8 te, LLViewerTexture* image, U32 index)
{
	if (te >= TEX_NUM_INDICES)
	{
		llassert(0);
		return;
	}

	if (getTEImage(te)->getID() == image->getID())
	{
		return;
	}

	if (isIndexBakedTexture((ETextureIndex)te))
	{
		llassert(0);
		return;
	}

	setTEImage(te, image);
}

//virtual
void LLVOAvatarSelf::removeMissingBakedTextures()
{	
	BOOL removed = FALSE;
	for (U32 i = 0; i < mBakedTextureDatas.size(); i++)
	{
		const S32 te = mBakedTextureDatas[i].mTextureIndex;
		const LLViewerTexture* tex = getTEImage(te);

		// Replace with default if we can't find the asset, assuming the
		// default is actually valid (which it should be unless something
		// is seriously wrong).
		if (!tex || tex->isMissingAsset())
		{
			LLViewerTexture *imagep = LLViewerTextureManager::getFetchedTexture(IMG_DEFAULT_AVATAR);
			if (imagep && imagep != tex)
			{
				setTEImage(te, imagep);
				removed = TRUE;
			}
		}
	}

	if (removed)
	{
		for (U32 i = 0; i < mBakedTextureDatas.size(); i++)
		{
			LLViewerTexLayerSet *layerset = getTexLayerSet(i);
			layerset->setUpdatesEnabled(TRUE);
			// <FS:Ansariel> [Legacy Bake]
			//invalidateComposite(layerset);
			invalidateComposite(layerset, FALSE);
		}
		updateMeshTextures();
		// <FS:Ansariel> [Legacy Bake]
		if (getRegion() && !getRegion()->getCentralBakeVersion())
		{
			requestLayerSetUploads();
		}
		// </FS:Ansariel> [Legacy Bake]
	}
}
// <FS:Beq> Check whether the BOM capability is different to last time we changed region (even across login)
void LLVOAvatarSelf::checkBOMRebakeRequired()
{
	if(getRegion())
	{
		auto bom_can_be_used_here = getRegion()->bakesOnMeshEnabled();
		static const LLCachedControl<bool> using_bom(gSavedSettings, "CurrentlyUsingBakesOnMesh", true);
		if( using_bom != bom_can_be_used_here)
		{
			// force a rebake when the last grid we were on (including previous login) had different BOM support
			// This replicates forceAppearanceUpdate rather than pulling in the whole of llavatarself.
			if(!LLGridManager::instance().isInSecondLife())
			{
				doAfterInterval([](){ if (isAgentAvatarValid()) { gAgentAvatarp->forceBakeAllTextures(true); }}, 5.0);
			}
			// update the setting even if we are in SL so that switch SL to OS and back 
			gSavedSettings.setBOOL("CurrentlyUsingBakesOnMesh", bom_can_be_used_here);
		}
	}
}
// </FS:Beq>

void LLVOAvatarSelf::onSimulatorFeaturesReceived(const LLUUID& region_id)
{
	LL_INFOS("Avatar") << "simulator features received, setting hover based on region props" << LL_ENDL;
	setHoverIfRegionEnabled();
	checkBOMRebakeRequired();// <FS:Beq/> BOM we may have stale cache, rebake may be needed
}

//virtual
void LLVOAvatarSelf::updateRegion(LLViewerRegion *regionp)
{
	// Save the global position
	LLVector3d global_pos_from_old_region = getPositionGlobal();

	// Change the region
	setRegion(regionp);

	if (regionp)
	{	// Set correct region-relative position from global coordinates
		setPositionGlobal(global_pos_from_old_region);

		// Diagnostic info
		//LLVector3d pos_from_new_region = getPositionGlobal();
		//LL_INFOS() << "pos_from_old_region is " << global_pos_from_old_region
		//	<< " while pos_from_new_region is " << pos_from_new_region
		//	<< LL_ENDL;

		// Update hover height, or schedule callback, based on whether
		// it's supported in this region.
		if (regionp->simulatorFeaturesReceived())
		{
			setHoverIfRegionEnabled();
			checkBOMRebakeRequired();// <FS:Beq/> BOM we may have stale cache, rebake may be needed
		}
		else
		{
			regionp->setSimulatorFeaturesReceivedCallback(boost::bind(&LLVOAvatarSelf::onSimulatorFeaturesReceived,this,_1));
		}
	}

	if (!regionp || (regionp->getHandle() != mLastRegionHandle))
	{
		if (mLastRegionHandle != 0)
		{
			++mRegionCrossingCount;
			F64Seconds delta(mRegionCrossingTimer.getElapsedTimeF32());
			record(LLStatViewer::REGION_CROSSING_TIME, delta);

			// Diagnostics
			LL_INFOS() << "Region crossing took " << (F32)(delta * 1000.0).value() << " ms " << LL_ENDL;
		}
		if (regionp)
		{
			mLastRegionHandle = regionp->getHandle();
		}
	}
	mRegionCrossingTimer.reset();
	LLViewerObject::updateRegion(regionp);
	setIsCrossingRegion(false); // <FS:Ansariel> FIRE-12004: Attachments getting lost on TP
}

//--------------------------------------------------------------------
// draw tractor beam when editing objects
//--------------------------------------------------------------------
//virtual
void LLVOAvatarSelf::idleUpdateTractorBeam()
{
	LLColor4U rgb = gLggBeamMaps.getCurrentColor(LLColor4U(gAgent.getEffectColor()));

	// <FS:Ansariel> Private point at
	static LLCachedControl<bool> private_pointat(gSavedSettings, "PrivatePointAtTarget", false);

	// This is only done for yourself (maybe it should be in the agent?)
	// <FS:Ansariel> Private point at
	//if (!needsRenderBeam() || !isBuilt())
	if (!needsRenderBeam() || !isBuilt() || private_pointat)
	// </FS:Ansariel>
	{
		mBeam = NULL;
		gLggBeamMaps.stopBeamChat();
	}
	else if (!mBeam || mBeam->isDead())
	{
		// VEFFECT: Tractor Beam
		mBeam = (LLHUDEffectSpiral *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_BEAM);
		mBeam->setColor(rgb);
		mBeam->setSourceObject(this);
		mBeamTimer.reset();
	}

	if (!mBeam.isNull())
	{
		LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection();

		if (gAgentCamera.mPointAt.notNull())
		{
			// get point from pointat effect
			mBeam->setPositionGlobal(gAgentCamera.mPointAt->getPointAtPosGlobal());
			gLggBeamMaps.updateBeamChat(gAgentCamera.mPointAt->getPointAtPosGlobal());
			mBeam->triggerLocal();
		}
		else if (selection->getFirstRootObject() && 
				selection->getSelectType() != SELECT_TYPE_HUD)
		{
			LLViewerObject* objectp = selection->getFirstRootObject();
			mBeam->setTargetObject(objectp);
		}
		else
		{
			mBeam->setTargetObject(NULL);
			LLTool *tool = LLToolMgr::getInstance()->getCurrentTool();
			if (tool->isEditing())
			{
				if (tool->getEditingObject())
				{
					mBeam->setTargetObject(tool->getEditingObject());
				}
				else
				{
					mBeam->setPositionGlobal(tool->getEditingPointGlobal());
				}
			}
			else
			{
				const LLPickInfo& pick = gViewerWindow->getLastPick();
				mBeam->setPositionGlobal(pick.mPosGlobal);
			}
		}
		if (mBeamTimer.getElapsedTimeF32() > gLggBeamMaps.setUpAndGetDuration())
		{
			mBeam->setColor(rgb);
			mBeam->setNeedsSendToSim(TRUE);
			mBeamTimer.reset();
			gLggBeamMaps.fireCurrentBeams(mBeam, rgb);
		}
	}
}

//-----------------------------------------------------------------------------
// restoreMeshData()
//-----------------------------------------------------------------------------
// virtual
void LLVOAvatarSelf::restoreMeshData()
{
	//LL_INFOS() << "Restoring" << LL_ENDL;
	mMeshValid = TRUE;
	updateJointLODs();
	updateAttachmentVisibility(gAgentCamera.getCameraMode());

	// force mesh update as LOD might not have changed to trigger this
	gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_GEOMETRY, TRUE);
}



//-----------------------------------------------------------------------------
// updateAttachmentVisibility()
//-----------------------------------------------------------------------------
void LLVOAvatarSelf::updateAttachmentVisibility(U32 camera_mode)
{
	for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); 
		 iter != mAttachmentPoints.end();
		 ++iter)
	{
		LLViewerJointAttachment* attachment = iter->second;
		// <FS:Ansariel> Possible crash fix
		if (!attachment)
		{
			continue;
		}
		// </FS:Ansariel>
		if (attachment->getIsHUDAttachment())
		{
			attachment->setAttachmentVisibility(TRUE);
		}
		else
		{
// [RLVa:KB] - Checked: RLVa-2.0.2
			bool fRlvCanShowAttachment = true;
			if (rlv_handler_t::isEnabled())
			{
				fRlvCanShowAttachment =
					(!gRlvHandler.hasBehaviour(RLV_BHVR_SHOWSELF)) &&
					( (!gRlvHandler.hasBehaviour(RLV_BHVR_SHOWSELFHEAD)) || (RLV_ATTACHGROUP_HEAD != rlvAttachGroupFromIndex(attachment->getGroup())) );
			}
// [/RLVa:KB]

			switch (camera_mode)
			{
				case CAMERA_MODE_MOUSELOOK:
					if (LLVOAvatar::sVisibleInFirstPerson && attachment->getVisibleInFirstPerson())
					{
// [RLVa:KB] - Checked: RLVa-2.0.2
						attachment->setAttachmentVisibility(fRlvCanShowAttachment);
// [/RLVa:KB]
//						attachment->setAttachmentVisibility(TRUE);
					}
					else
					{
						attachment->setAttachmentVisibility(FALSE);
					}
					break;
				default:
// [RLVa:KB] - Checked: RLVa-2.0.2
					attachment->setAttachmentVisibility(fRlvCanShowAttachment);
// [/RLVa:KB]
//					attachment->setAttachmentVisibility(TRUE);
					break;
			}
		}
	}
}

//-----------------------------------------------------------------------------
// updatedWearable( LLWearableType::EType type )
// forces an update to any baked textures relevant to type.
// will force an upload of the resulting bake if the second parameter is TRUE
//-----------------------------------------------------------------------------
// <FS:Ansariel> [Legacy Bake]
//void LLVOAvatarSelf::wearableUpdated(LLWearableType::EType type)
void LLVOAvatarSelf::wearableUpdated(LLWearableType::EType type, BOOL upload_result)
{
	for (LLAvatarAppearanceDictionary::BakedTextures::const_iterator baked_iter = sAvatarDictionary->getBakedTextures().begin();
		 baked_iter != sAvatarDictionary->getBakedTextures().end();
		 ++baked_iter)
	{
		const LLAvatarAppearanceDictionary::BakedEntry *baked_dict = baked_iter->second;
		const LLAvatarAppearanceDefines::EBakedTextureIndex index = baked_iter->first;

		if (baked_dict)
		{
			for (LLAvatarAppearanceDefines::wearables_vec_t::const_iterator type_iter = baked_dict->mWearables.begin();
				type_iter != baked_dict->mWearables.end();
				 ++type_iter)
			{
				const LLWearableType::EType comp_type = *type_iter;
				if (comp_type == type)
				{
					LLViewerTexLayerSet *layerset = getLayerSet(index);
					if (layerset)
					{
						layerset->setUpdatesEnabled(true);
						// <FS:Ansariel> [Legacy Bake]
						//invalidateComposite(layerset);
						invalidateComposite(layerset, upload_result);
					}
					break;
				}
			}
		}
	}

	// </FS:Ansariel> [Legacy Bake]
	// Physics type has no associated baked textures, but change of params needs to be sent to
	// other avatars.
	if (type == LLWearableType::WT_PHYSICS)
	{
		gAgent.sendAgentSetAppearance();
	}
	// <FS:Ansariel> [Legacy Bake]
}

//-----------------------------------------------------------------------------
// isWearingAttachment()
//-----------------------------------------------------------------------------
BOOL LLVOAvatarSelf::isWearingAttachment(const LLUUID& inv_item_id) const
{
	const LLUUID& base_inv_item_id = gInventory.getLinkedItemID(inv_item_id);
	for (attachment_map_t::const_iterator iter = mAttachmentPoints.begin(); 
		 iter != mAttachmentPoints.end();
		 ++iter)
	{
		const LLViewerJointAttachment* attachment = iter->second;
		if (attachment->getAttachedObject(base_inv_item_id))
		{
			return TRUE;
		}
	}
	return FALSE;
}

//-----------------------------------------------------------------------------
// getWornAttachment()
//-----------------------------------------------------------------------------
LLViewerObject* LLVOAvatarSelf::getWornAttachment(const LLUUID& inv_item_id)
{
	const LLUUID& base_inv_item_id = gInventory.getLinkedItemID(inv_item_id);
	for (attachment_map_t::const_iterator iter = mAttachmentPoints.begin(); 
		 iter != mAttachmentPoints.end();
		 ++iter)
	{
		LLViewerJointAttachment* attachment = iter->second;
 		if (LLViewerObject *attached_object = attachment->getAttachedObject(base_inv_item_id))
		{
			return attached_object;
		}
	}
	return NULL;
}

// [RLVa:KB] - Checked: 2012-07-28 (RLVa-1.4.7)
boost::signals2::connection LLVOAvatarSelf::setAttachmentCallback(const attachment_signal_t::slot_type& cb)
{
	if (!mAttachmentSignal)
		mAttachmentSignal = new attachment_signal_t();
	return mAttachmentSignal->connect(cb);
}
// [/RLVa:KB]
// [RLVa:KB] - Checked: 2010-03-14 (RLVa-1.2.0a) | Modified: RLVa-1.2.0a
LLViewerJointAttachment* LLVOAvatarSelf::getWornAttachmentPoint(const LLUUID& idItem) const
{
	const LLUUID& idItemBase = gInventory.getLinkedItemID(idItem);
	for (attachment_map_t::const_iterator itAttachPt = mAttachmentPoints.begin(); itAttachPt != mAttachmentPoints.end(); ++itAttachPt)
	{
		LLViewerJointAttachment* pAttachPt = itAttachPt->second;
 		if (pAttachPt->getAttachedObject(idItemBase))
			return pAttachPt;
	}
	return NULL;
}
// [/RLVa:KB]

bool LLVOAvatarSelf::getAttachedPointName(const LLUUID& inv_item_id, std::string& name) const
{
	if (!gInventory.getItem(inv_item_id))
	{
		name = "ATTACHMENT_MISSING_ITEM";
		return false;
	}
	const LLUUID& base_inv_item_id = gInventory.getLinkedItemID(inv_item_id);
	if (!gInventory.getItem(base_inv_item_id))
	{
		name = "ATTACHMENT_MISSING_BASE_ITEM";
		return false;
	}
	for (attachment_map_t::const_iterator iter = mAttachmentPoints.begin(); 
		 iter != mAttachmentPoints.end(); 
		 ++iter)
	{
		const LLViewerJointAttachment* attachment = iter->second;
		if (attachment->getAttachedObject(base_inv_item_id))
		{
			name = attachment->getName();
			return true;
		}
	}

	name = "ATTACHMENT_NOT_ATTACHED";
	return false;
}

//virtual
const LLViewerJointAttachment *LLVOAvatarSelf::attachObject(LLViewerObject *viewer_object)
{
	const LLViewerJointAttachment *attachment = LLVOAvatar::attachObject(viewer_object);
	if (!attachment)
	{
		return 0;
	}

	updateAttachmentVisibility(gAgentCamera.getCameraMode());
	
	// Then make sure the inventory is in sync with the avatar.

	// Should just be the last object added
	if (attachment->isObjectAttached(viewer_object))
	{
		const LLUUID& attachment_id = viewer_object->getAttachmentItemID();
		LLAppearanceMgr::instance().registerAttachment(attachment_id);

		// <FS:TT> ReplaceWornItemsOnly
		gInventory.wearAttachmentsOnAvatarCheckRemove(viewer_object, attachment);
		// </FS:TT>
		// <FS:TT> Client LSL Bridge
		if (attachment->getName() == FS_BRIDGE_ATTACHMENT_POINT_NAME && gSavedSettings.getBOOL("UseLSLBridge"))
		{
			LLViewerInventoryItem* inv_object = gInventory.getItem(viewer_object->getAttachmentItemID());
			if (inv_object && inv_object->getName() == FSLSLBridge::instance().currentFullName())
			{
				FSLSLBridge::instance().processAttach(viewer_object, attachment);
			}
		}
		// </FS:TT>
		updateLODRiggedAttachments();		

// [RLVa:KB] - Checked: 2010-08-22 (RLVa-1.2.1a) | Modified: RLVa-1.2.1a
		// NOTE: RLVa event handlers should be invoked *after* LLVOAvatar::attachObject() calls LLViewerJointAttachment::addObject()
		if (mAttachmentSignal)
		{
			(*mAttachmentSignal)(viewer_object, attachment, ACTION_ATTACH);
		}
		if (rlv_handler_t::isEnabled())
		{
			RlvAttachmentLockWatchdog::instance().onAttach(viewer_object, attachment);
			gRlvHandler.onAttach(viewer_object, attachment);

			if ( (attachment->getIsHUDAttachment()) && (!gRlvAttachmentLocks.hasLockedHUD()) )
				gRlvAttachmentLocks.updateLockedHUD();
		}
// [/RLVa:KB]
	}

	return attachment;
}

//virtual
BOOL LLVOAvatarSelf::detachObject(LLViewerObject *viewer_object)
{
	const LLUUID attachment_id = viewer_object->getAttachmentItemID();

// [RLVa:KB] - Checked: 2010-03-05 (RLVa-1.2.0a) | Added: RLVa-1.2.0a
	// NOTE: RLVa event handlers should be invoked *before* LLVOAvatar::detachObject() calls LLViewerJointAttachment::removeObject()
	//if (rlv_handler_t::isEnabled())
	//{
	//	for (attachment_map_t::const_iterator itAttachPt = mAttachmentPoints.begin(); itAttachPt != mAttachmentPoints.end(); ++itAttachPt)
	//	{
	//		const LLViewerJointAttachment* pAttachPt = itAttachPt->second;
	//		if (pAttachPt->isObjectAttached(viewer_object))
	//		{
	// <FS:TT> Client LSL Bridge - moving the rlv check to get the pAttachPt for the bridge
	const LLViewerJointAttachment* pAttachPt = NULL;
	for (attachment_map_t::const_iterator itAttachPt = mAttachmentPoints.begin(); itAttachPt != mAttachmentPoints.end(); ++itAttachPt)
	{
		pAttachPt = itAttachPt->second;
		if (pAttachPt->isObjectAttached(viewer_object))
		{
			if (rlv_handler_t::isEnabled())
			{
	// </FS:TT>
				RlvAttachmentLockWatchdog::instance().onDetach(viewer_object, pAttachPt);
				gRlvHandler.onDetach(viewer_object, pAttachPt);
			}
			if (mAttachmentSignal)
			{
				(*mAttachmentSignal)(viewer_object, pAttachPt, ACTION_DETACH);
			}

			// <FS:TT> Client LSL Bridge
			if (pAttachPt->getName() == FS_BRIDGE_ATTACHMENT_POINT_NAME)
			{
				LLViewerInventoryItem* inv_object = gInventory.getItem(viewer_object->getAttachmentItemID());
				if (inv_object && inv_object->getName() == FSLSLBridge::instance().currentFullName())
				{
					FSLSLBridge::instance().processDetach(viewer_object, pAttachPt);
				}
			}
			// </FS:TT>
			break;
		}
	}
// [/RLVa:KB]

	if ( LLVOAvatar::detachObject(viewer_object) )
	{
		// the simulator should automatically handle permission revocation
		
		stopMotionFromSource(attachment_id);
		LLFollowCamMgr::getInstance()->setCameraActive(viewer_object->getID(), FALSE);
		
		LLViewerObject::const_child_list_t& child_list = viewer_object->getChildren();
		for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin();
			 iter != child_list.end(); 
			 ++iter)
		{
			LLViewerObject* child_objectp = *iter;
			// the simulator should automatically handle
			// permissions revocation
			
			stopMotionFromSource(child_objectp->getID());
			LLFollowCamMgr::getInstance()->setCameraActive(child_objectp->getID(), FALSE);
		}
		
		// Make sure the inventory is in sync with the avatar.

		// Update COF contents, don't trigger appearance update.
		if (!isValid())
		{
			LL_INFOS() << "removeItemLinks skipped, avatar is under destruction" << LL_ENDL;
		}
		else
		{
			LLAppearanceMgr::instance().unregisterAttachment(attachment_id);
		}
		
// [RLVa:KB] - Checked: 2010-08-22 (RLVa-1.2.1a) | Added: RLVa-1.2.1a
		if ( (rlv_handler_t::isEnabled()) && (viewer_object->isHUDAttachment()) && (gRlvAttachmentLocks.hasLockedHUD()) )
			gRlvAttachmentLocks.updateLockedHUD();
// [/RLVa:KB]

		return TRUE;
	}
	return FALSE;
}

// static
BOOL LLVOAvatarSelf::detachAttachmentIntoInventory(const LLUUID &item_id)
{
	LLInventoryItem* item = gInventory.getItem(item_id);
//	if (item)
// [RLVa:KB] - Checked: 2010-09-04 (RLVa-1.2.1c) | Added: RLVa-1.2.1c
	if ( (item) && (((!rlv_handler_t::isEnabled()) || (gRlvAttachmentLocks.canDetach(item))) ||
		FSLSLBridge::instance().canDetach(item->getUUID())) )
// [/RLVa:KB]
	{
		gMessageSystem->newMessageFast(_PREHASH_DetachAttachmentIntoInv);
		gMessageSystem->nextBlockFast(_PREHASH_ObjectData);
		gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
		gMessageSystem->addUUIDFast(_PREHASH_ItemID, item_id);
		gMessageSystem->sendReliable(gAgent.getRegionHost());
		
		// This object might have been selected, so let the selection manager know it's gone now
		LLViewerObject *found_obj = gObjectList.findObject(item_id);
		if (found_obj)
		{
			LLSelectMgr::getInstance()->remove(found_obj);
		}

		// Error checking in case this object was attached to an invalid point
		// In that case, just remove the item from COF preemptively since detach 
		// will fail.
		if (isAgentAvatarValid())
		{
			const LLViewerObject *attached_obj = gAgentAvatarp->getWornAttachment(item_id);
			if (!attached_obj)
			{
				LLAppearanceMgr::instance().removeCOFItemLinks(item_id);
			}
		}
		return TRUE;
	}
	return FALSE;
}

U32 LLVOAvatarSelf::getNumWearables(LLAvatarAppearanceDefines::ETextureIndex i) const
{
	LLWearableType::EType type = sAvatarDictionary->getTEWearableType(i);
	return gAgentWearables.getWearableCount(type);
}

// virtual
void LLVOAvatarSelf::localTextureLoaded(BOOL success, LLViewerFetchedTexture *src_vi, LLImageRaw* src_raw, LLImageRaw* aux_src, S32 discard_level, BOOL final, void* userdata)
{	

	const LLUUID& src_id = src_vi->getID();
	LLAvatarTexData *data = (LLAvatarTexData *)userdata;
	ETextureIndex index = data->mIndex;
	if (!isIndexLocalTexture(index)) return;

	LLLocalTextureObject *local_tex_obj = getLocalTextureObject(index, 0);

	// fix for EXT-268. Preventing using of NULL pointer
	if(NULL == local_tex_obj)
	{
		LL_WARNS("TAG") << "There is no Local Texture Object with index: " << index 
			<< ", final: " << final
			<< LL_ENDL;
		return;
	}
	if (success)
	{
		if (!local_tex_obj->getBakedReady() &&
			local_tex_obj->getImage() != NULL &&
			(local_tex_obj->getID() == src_id) &&
			discard_level < local_tex_obj->getDiscard())
		{
			local_tex_obj->setDiscard(discard_level);
			requestLayerSetUpdate(index);
			if (isEditingAppearance())
			{
				LLVisualParamHint::requestHintUpdates();
			}
			updateMeshTextures();
		}
	}
	else if (final)
	{
		// Failed: asset is missing
		if (!local_tex_obj->getBakedReady() &&
			local_tex_obj->getImage() != NULL &&
			local_tex_obj->getImage()->getID() == src_id)
		{
			local_tex_obj->setDiscard(0);
			requestLayerSetUpdate(index);
			updateMeshTextures();
		}
	}
}

// virtual
BOOL LLVOAvatarSelf::getLocalTextureGL(ETextureIndex type, LLViewerTexture** tex_pp, U32 index) const
{
	*tex_pp = NULL;

	if (!isIndexLocalTexture(type)) return FALSE;
	if (getLocalTextureID(type, index) == IMG_DEFAULT_AVATAR) return TRUE;

	const LLLocalTextureObject *local_tex_obj = getLocalTextureObject(type, index);
	if (!local_tex_obj)
	{
		return FALSE;
	}
	*tex_pp = dynamic_cast<LLViewerTexture*> (local_tex_obj->getImage());
	return TRUE;
}

LLViewerFetchedTexture* LLVOAvatarSelf::getLocalTextureGL(LLAvatarAppearanceDefines::ETextureIndex type, U32 index) const
{
	if (!isIndexLocalTexture(type))
	{
		return NULL;
	}

	const LLLocalTextureObject *local_tex_obj = getLocalTextureObject(type, index);
	if (!local_tex_obj)
	{
		return NULL;
	}
	if (local_tex_obj->getID() == IMG_DEFAULT_AVATAR)
	{
		return LLViewerTextureManager::getFetchedTexture(IMG_DEFAULT_AVATAR);
	}
	return dynamic_cast<LLViewerFetchedTexture*> (local_tex_obj->getImage());
}

const LLUUID& LLVOAvatarSelf::getLocalTextureID(ETextureIndex type, U32 index) const
{
	if (!isIndexLocalTexture(type)) return IMG_DEFAULT_AVATAR;

	const LLLocalTextureObject *local_tex_obj = getLocalTextureObject(type, index);
	if (local_tex_obj && local_tex_obj->getImage() != NULL)
	{
		return local_tex_obj->getImage()->getID();
	}
	return IMG_DEFAULT_AVATAR;
} 


//-----------------------------------------------------------------------------
// isLocalTextureDataAvailable()
// Returns true if at least the lowest quality discard level exists for every texture
// in the layerset.
//-----------------------------------------------------------------------------
BOOL LLVOAvatarSelf::isLocalTextureDataAvailable(const LLViewerTexLayerSet* layerset) const
{
	/* if (layerset == mBakedTextureDatas[BAKED_HEAD].mTexLayerSet)
	   return getLocalDiscardLevel(TEX_HEAD_BODYPAINT) >= 0; */
	for (LLAvatarAppearanceDictionary::BakedTextures::const_iterator baked_iter = sAvatarDictionary->getBakedTextures().begin();
		 baked_iter != sAvatarDictionary->getBakedTextures().end();
		 ++baked_iter)
	{
		const EBakedTextureIndex baked_index = baked_iter->first;
		if (layerset == mBakedTextureDatas[baked_index].mTexLayerSet)
		{
			BOOL ret = true;
			const LLAvatarAppearanceDictionary::BakedEntry *baked_dict = baked_iter->second;
			for (texture_vec_t::const_iterator local_tex_iter = baked_dict->mLocalTextures.begin();
				 local_tex_iter != baked_dict->mLocalTextures.end();
				 ++local_tex_iter)
			{
				const ETextureIndex tex_index = *local_tex_iter;
				const LLWearableType::EType wearable_type = sAvatarDictionary->getTEWearableType(tex_index);
				const U32 wearable_count = gAgentWearables.getWearableCount(wearable_type);
				for (U32 wearable_index = 0; wearable_index < wearable_count; wearable_index++)
				{
					BOOL tex_avail = (getLocalDiscardLevel(tex_index, wearable_index) >= 0);
					ret &= tex_avail;
				}
			}
			return ret;
		}
	}
	llassert(0);
	return FALSE;
}

//-----------------------------------------------------------------------------
// virtual
// isLocalTextureDataFinal()
// Returns true if the highest quality discard level exists for every texture
// in the layerset.
//-----------------------------------------------------------------------------
BOOL LLVOAvatarSelf::isLocalTextureDataFinal(const LLViewerTexLayerSet* layerset) const
{
	// <FS:Ansariel> Replace frequently called gSavedSettings
	//const U32 desired_tex_discard_level = gSavedSettings.getU32("TextureDiscardLevel"); 
	static LLCachedControl<U32> sTextureDiscardLevel(gSavedSettings, "TextureDiscardLevel");
	const U32 desired_tex_discard_level = sTextureDiscardLevel();
	// </FS:Ansariel>
	// const U32 desired_tex_discard_level = 0; // hack to not bake textures on lower discard levels.

	for (U32 i = 0; i < mBakedTextureDatas.size(); i++)
	{
		if (layerset == mBakedTextureDatas[i].mTexLayerSet)
		{
			const LLAvatarAppearanceDictionary::BakedEntry *baked_dict = sAvatarDictionary->getBakedTexture((EBakedTextureIndex)i);
			for (texture_vec_t::const_iterator local_tex_iter = baked_dict->mLocalTextures.begin();
				 local_tex_iter != baked_dict->mLocalTextures.end();
				 ++local_tex_iter)
			{
				const ETextureIndex tex_index = *local_tex_iter;
				const LLWearableType::EType wearable_type = sAvatarDictionary->getTEWearableType(tex_index);
				const U32 wearable_count = gAgentWearables.getWearableCount(wearable_type);
				for (U32 wearable_index = 0; wearable_index < wearable_count; wearable_index++)
				{
					S32 local_discard_level = getLocalDiscardLevel(*local_tex_iter, wearable_index);
					if ((local_discard_level > (S32)(desired_tex_discard_level)) ||
						(local_discard_level < 0 ))
					{
						return FALSE;
					}
				}
			}
			return TRUE;
		}
	}
	llassert(0);
	return FALSE;
}


BOOL LLVOAvatarSelf::isAllLocalTextureDataFinal() const
{
	// <FS:Ansariel> Replace frequently called gSavedSettings
	//const U32 desired_tex_discard_level = gSavedSettings.getU32("TextureDiscardLevel"); 
	static LLCachedControl<U32> sTextureDiscardLevel(gSavedSettings, "TextureDiscardLevel");
	const U32 desired_tex_discard_level = sTextureDiscardLevel();
	// </FS:Ansariel>
	// const U32 desired_tex_discard_level = 0; // hack to not bake textures on lower discard levels

	for (U32 i = 0; i < mBakedTextureDatas.size(); i++)
	{
		const LLAvatarAppearanceDictionary::BakedEntry *baked_dict = sAvatarDictionary->getBakedTexture((EBakedTextureIndex)i);
		for (texture_vec_t::const_iterator local_tex_iter = baked_dict->mLocalTextures.begin();
			 local_tex_iter != baked_dict->mLocalTextures.end();
			 ++local_tex_iter)
		{
			const ETextureIndex tex_index = *local_tex_iter;
			const LLWearableType::EType wearable_type = sAvatarDictionary->getTEWearableType(tex_index);
			const U32 wearable_count = gAgentWearables.getWearableCount(wearable_type);
			for (U32 wearable_index = 0; wearable_index < wearable_count; wearable_index++)
			{
				S32 local_discard_level = getLocalDiscardLevel(*local_tex_iter, wearable_index);
				if ((local_discard_level > (S32)(desired_tex_discard_level)) ||
					(local_discard_level < 0 ))
				{
					return FALSE;
				}
			}
		}
	}
	return TRUE;
}

BOOL LLVOAvatarSelf::isTextureDefined(LLAvatarAppearanceDefines::ETextureIndex type, U32 index) const
{
	LLUUID id;
	BOOL isDefined = TRUE;
	if (isIndexLocalTexture(type))
	{
		const LLWearableType::EType wearable_type = sAvatarDictionary->getTEWearableType(type);
		const U32 wearable_count = gAgentWearables.getWearableCount(wearable_type);
		if (index >= wearable_count)
		{
			// invalid index passed in. check all textures of a given type
			for (U32 wearable_index = 0; wearable_index < wearable_count; wearable_index++)
			{
				id = getLocalTextureID(type, wearable_index);
				isDefined &= (id != IMG_DEFAULT_AVATAR && id != IMG_DEFAULT);
			}
		}
		else
		{
			id = getLocalTextureID(type, index);
			isDefined &= (id != IMG_DEFAULT_AVATAR && id != IMG_DEFAULT);
		}
	}
	else
	{
		id = getTEImage(type)->getID();
		isDefined &= (id != IMG_DEFAULT_AVATAR && id != IMG_DEFAULT);
	}
	
	return isDefined;
}

//virtual
BOOL LLVOAvatarSelf::isTextureVisible(LLAvatarAppearanceDefines::ETextureIndex type, U32 index) const
{
	if (isIndexBakedTexture(type))
	{
		return LLVOAvatar::isTextureVisible(type, (U32)0);
	}

	LLUUID tex_id = getLocalTextureID(type,index);
	return (tex_id != IMG_INVISIBLE) 
			|| (LLDrawPoolAlpha::sShowDebugAlpha);
}

//virtual
BOOL LLVOAvatarSelf::isTextureVisible(LLAvatarAppearanceDefines::ETextureIndex type, LLViewerWearable *wearable) const
{
	if (isIndexBakedTexture(type))
	{
		return LLVOAvatar::isTextureVisible(type);
	}

	U32 index;
	if (gAgentWearables.getWearableIndex(wearable,index))
	{
		return isTextureVisible(type,index);
	}
	else
	{
		LL_WARNS() << "Wearable not found" << LL_ENDL;
		return FALSE;
	}
}

bool LLVOAvatarSelf::areTexturesCurrent() const
{
	// <FS:Ansariel> [Legacy Bake]
	//return gAgentWearables.areWearablesLoaded();
	return !hasPendingBakedUploads() && gAgentWearables.areWearablesLoaded();
}

// <FS:Ansariel> [Legacy Bake]
//void LLVOAvatarSelf::invalidateComposite( LLTexLayerSet* layerset)
void LLVOAvatarSelf::invalidateComposite( LLTexLayerSet* layerset, BOOL upload_result)
{
	LLViewerTexLayerSet *layer_set = dynamic_cast<LLViewerTexLayerSet*>(layerset);
	if( !layer_set || !layer_set->getUpdatesEnabled() )
	{
		return;
	}
	// LL_INFOS() << "LLVOAvatar::invalidComposite() " << layerset->getBodyRegionName() << LL_ENDL;

	layer_set->requestUpdate();
	layer_set->invalidateMorphMasks();

	// <FS:Ansariel> [Legacy Bake]
	if( upload_result  && (getRegion() && !getRegion()->getCentralBakeVersion()))
	{
		llassert(isSelf());

		ETextureIndex baked_te = getBakedTE( layer_set );
		setTEImage( baked_te, LLViewerTextureManager::getFetchedTexture(IMG_DEFAULT_AVATAR) );
		layer_set->requestUpload();
		updateMeshTextures();
	}
	// </FS:Ansariel> [Legacy Bake]
}

void LLVOAvatarSelf::invalidateAll()
{
	for (U32 i = 0; i < mBakedTextureDatas.size(); i++)
	{
		LLViewerTexLayerSet *layerset = getTexLayerSet(i);
		// <FS:Ansariel> [Legacy Bake]
		//invalidateComposite(layerset);
		invalidateComposite(layerset, TRUE);
	}
	//mDebugSelfLoadTimer.reset();
}

//-----------------------------------------------------------------------------
// setCompositeUpdatesEnabled()
//-----------------------------------------------------------------------------
void LLVOAvatarSelf::setCompositeUpdatesEnabled( bool b )
{
	for (U32 i = 0; i < mBakedTextureDatas.size(); i++)
	{
		setCompositeUpdatesEnabled(i, b);
	}
}

void LLVOAvatarSelf::setCompositeUpdatesEnabled(U32 index, bool b)
{
	LLViewerTexLayerSet *layerset = getTexLayerSet(index);
	if (layerset )
	{
		layerset->setUpdatesEnabled( b );
	}
}

bool LLVOAvatarSelf::isCompositeUpdateEnabled(U32 index)
{
	LLViewerTexLayerSet *layerset = getTexLayerSet(index);
	if (layerset)
	{
		return layerset->getUpdatesEnabled();
	}
	return false;
}

void LLVOAvatarSelf::setupComposites()
{
	for (U32 i = 0; i < mBakedTextureDatas.size(); i++)
	{
		ETextureIndex tex_index = mBakedTextureDatas[i].mTextureIndex;
		BOOL layer_baked = isTextureDefined(tex_index, gAgentWearables.getWearableCount(tex_index));
		LLViewerTexLayerSet *layerset = getTexLayerSet(i);
		if (layerset)
		{
			layerset->setUpdatesEnabled(!layer_baked);
		}
	}
}

void LLVOAvatarSelf::updateComposites()
{
	for (U32 i = 0; i < mBakedTextureDatas.size(); i++)
	{
		LLViewerTexLayerSet *layerset = getTexLayerSet(i);
		if (layerset 
			&& ((i != BAKED_SKIRT) || isWearingWearableType(LLWearableType::WT_SKIRT)))
		{
			layerset->updateComposite();
		}
	}
}

// virtual
S32 LLVOAvatarSelf::getLocalDiscardLevel(ETextureIndex type, U32 wearable_index) const
{
	if (!isIndexLocalTexture(type)) return FALSE;

	const LLLocalTextureObject *local_tex_obj = getLocalTextureObject(type, wearable_index);
	if (local_tex_obj)
	{
		const LLViewerFetchedTexture* image = dynamic_cast<LLViewerFetchedTexture*>( local_tex_obj->getImage() );
		if (type >= 0
			&& local_tex_obj->getID() != IMG_DEFAULT_AVATAR
			&& !image->isMissingAsset())
		{
			return image->getDiscardLevel();
		}
		else
		{
			// We don't care about this (no image associated with the layer) treat as fully loaded.
			return 0;
		}
	}
	return 0;
}

// virtual
// Counts the memory footprint of local textures.
void LLVOAvatarSelf::getLocalTextureByteCount(S32* gl_bytes) const
{
	*gl_bytes = 0;
	for (S32 type = 0; type < TEX_NUM_INDICES; type++)
	{
		if (!isIndexLocalTexture((ETextureIndex)type)) continue;
		U32 max_tex = getNumWearables((ETextureIndex) type);
		for (U32 num = 0; num < max_tex; num++)
		{
			const LLLocalTextureObject *local_tex_obj = getLocalTextureObject((ETextureIndex) type, num);
			if (local_tex_obj)
			{
				const LLViewerFetchedTexture* image_gl = dynamic_cast<LLViewerFetchedTexture*>( local_tex_obj->getImage() );
				if (image_gl)
				{
					S32 bytes = (S32)image_gl->getWidth() * image_gl->getHeight() * image_gl->getComponents();
					
					if (image_gl->hasGLTexture())
					{
						*gl_bytes += bytes;
					}
				}
			}
		}
	}
}

// virtual 
void LLVOAvatarSelf::setLocalTexture(ETextureIndex type, LLViewerTexture* src_tex, BOOL baked_version_ready, U32 index)
{
	if (!isIndexLocalTexture(type)) return;

	LLViewerFetchedTexture* tex = LLViewerTextureManager::staticCastToFetchedTexture(src_tex, TRUE) ;
	if(!tex)
	{
		return ;
	}

	S32 desired_discard = isSelf() ? 0 : 2;
	LLLocalTextureObject *local_tex_obj = getLocalTextureObject(type,index);
	if (!local_tex_obj)
	{
		if (type >= TEX_NUM_INDICES)
		{
			LL_ERRS() << "Tried to set local texture with invalid type: (" << (U32) type << ", " << index << ")" << LL_ENDL;
			return;
		}
		LLWearableType::EType wearable_type = sAvatarDictionary->getTEWearableType(type);
		if (!gAgentWearables.getViewerWearable(wearable_type,index))
		{
			// no wearable is loaded, cannot set the texture.
			return;
		}
		gAgentWearables.addLocalTextureObject(wearable_type,type,index);
		local_tex_obj = getLocalTextureObject(type,index);
		if (!local_tex_obj)
		{
			LL_ERRS() << "Unable to create LocalTextureObject for wearable type & index: (" << (U32) wearable_type << ", " << index << ")" << LL_ENDL;
			return;
		}
		
		LLViewerTexLayerSet *layer_set = getLayerSet(type);
		if (layer_set)
		{
			layer_set->cloneTemplates(local_tex_obj, type, gAgentWearables.getViewerWearable(wearable_type,index));
		}

	}
	if (!baked_version_ready)
	{
		if (tex != local_tex_obj->getImage() || local_tex_obj->getBakedReady())
		{
			local_tex_obj->setDiscard(MAX_DISCARD_LEVEL+1);
		}
		if (tex->getID() != IMG_DEFAULT_AVATAR)
		{
			if (local_tex_obj->getDiscard() > desired_discard)
			{
				S32 tex_discard = tex->getDiscardLevel();
				if (tex_discard >= 0 && tex_discard <= desired_discard)
				{
					local_tex_obj->setDiscard(tex_discard);
					if (isSelf())
					{
						requestLayerSetUpdate(type);
						if (isEditingAppearance())
						{
							LLVisualParamHint::requestHintUpdates();
						}
					}
				}
				else
				{					
					tex->setLoadedCallback(onLocalTextureLoaded, desired_discard, TRUE, FALSE, new LLAvatarTexData(getID(), type), NULL);
				}
			}
			tex->setMinDiscardLevel(desired_discard);
		}
	}
	local_tex_obj->setImage(tex);
	local_tex_obj->setID(tex->getID());
	setBakedReady(type,baked_version_ready,index);
}

//virtual
void LLVOAvatarSelf::setBakedReady(LLAvatarAppearanceDefines::ETextureIndex type, BOOL baked_version_exists, U32 index)
{
	if (!isIndexLocalTexture(type)) return;
	LLLocalTextureObject *local_tex_obj = getLocalTextureObject(type,index);
	if (local_tex_obj)
	{
		local_tex_obj->setBakedReady( baked_version_exists );
	}
}


// virtual
void LLVOAvatarSelf::dumpLocalTextures() const
{
	LL_INFOS() << "Local Textures:" << LL_ENDL;

	/* ETextureIndex baked_equiv[] = {
	   TEX_UPPER_BAKED,
	   if (isTextureDefined(baked_equiv[i])) */
	for (LLAvatarAppearanceDictionary::Textures::const_iterator iter = sAvatarDictionary->getTextures().begin();
		 iter != sAvatarDictionary->getTextures().end();
		 ++iter)
	{
		const LLAvatarAppearanceDictionary::TextureEntry *texture_dict = iter->second;
		if (!texture_dict->mIsLocalTexture || !texture_dict->mIsUsedByBakedTexture)
			continue;

		const EBakedTextureIndex baked_index = texture_dict->mBakedTextureIndex;
		const ETextureIndex baked_equiv = sAvatarDictionary->getBakedTexture(baked_index)->mTextureIndex;

		const std::string &name = texture_dict->mName;
		const LLLocalTextureObject *local_tex_obj = getLocalTextureObject(iter->first, 0);
		// index is baked texture - index is not relevant. putting in 0 as placeholder
		if (isTextureDefined(baked_equiv, 0))
		{
#if LL_RELEASE_FOR_DOWNLOAD
			// End users don't get to trivially see avatar texture IDs, makes textures
			// easier to steal. JC
			LL_INFOS() << "LocTex " << name << ": Baked " << LL_ENDL;
#else
			LL_INFOS() << "LocTex " << name << ": Baked " << getTEImage(baked_equiv)->getID() << LL_ENDL;
#endif
		}
		else if (local_tex_obj && local_tex_obj->getImage() != NULL)
		{
			if (local_tex_obj->getImage()->getID() == IMG_DEFAULT_AVATAR)
			{
				LL_INFOS() << "LocTex " << name << ": None" << LL_ENDL;
			}
			else
			{
				const LLViewerFetchedTexture* image = dynamic_cast<LLViewerFetchedTexture*>( local_tex_obj->getImage() );

				LL_INFOS() << "LocTex " << name << ": "
						<< "Discard " << image->getDiscardLevel() << ", "
						<< "(" << image->getWidth() << ", " << image->getHeight() << ") " 
#if !LL_RELEASE_FOR_DOWNLOAD
					// End users don't get to trivially see avatar texture IDs,
					// makes textures easier to steal
						<< image->getID() << " "
#endif
						<< "Priority: " << image->getDecodePriority()
						<< LL_ENDL;
			}
		}
		else
		{
			LL_INFOS() << "LocTex " << name << ": No LLViewerTexture" << LL_ENDL;
		}
	}
}

//-----------------------------------------------------------------------------
// static 
// onLocalTextureLoaded()
//-----------------------------------------------------------------------------

void LLVOAvatarSelf::onLocalTextureLoaded(BOOL success, LLViewerFetchedTexture *src_vi, LLImageRaw* src_raw, LLImageRaw* aux_src, S32 discard_level, BOOL final, void* userdata)
{
	LLAvatarTexData *data = (LLAvatarTexData *)userdata;
	LLVOAvatarSelf *self = (LLVOAvatarSelf *)gObjectList.findObject(data->mAvatarID);
	if (self)
	{
		// We should only be handling local textures for ourself
		self->localTextureLoaded(success, src_vi, src_raw, aux_src, discard_level, final, userdata);
	}
	// ensure data is cleaned up
	if (final || !success)
	{
		delete data;
	}
}

/*virtual*/	void LLVOAvatarSelf::setImage(const U8 te, LLViewerTexture *imagep, const U32 index)
{
	if (isIndexLocalTexture((ETextureIndex)te))
	{
		setLocalTexture((ETextureIndex)te, imagep, FALSE ,index);
	}
	else 
	{
		setTEImage(te,imagep);
	}
}

/*virtual*/ LLViewerTexture* LLVOAvatarSelf::getImage(const U8 te, const U32 index) const
{
	if (isIndexLocalTexture((ETextureIndex)te))
	{
		return getLocalTextureGL((ETextureIndex)te,index);
	}
	else 
	{
		return getTEImage(te);
	}
}


// static
void LLVOAvatarSelf::dumpTotalLocalTextureByteCount()
{
	S32 gl_bytes = 0;
	gAgentAvatarp->getLocalTextureByteCount(&gl_bytes);
	LL_INFOS() << "Total Avatar LocTex GL:" << (gl_bytes/1024) << "KB" << LL_ENDL;
}

bool LLVOAvatarSelf::getIsCloud() const
{
	// Let people know why they're clouded without spamming them into oblivion.
	bool do_warn = false;
	static LLTimer time_since_notice;
	F32 update_freq = 30.0;
	if (time_since_notice.getElapsedTimeF32() > update_freq)
	{
		time_since_notice.reset();
		do_warn = true;
	}
	
	// do we have our body parts?
	S32 shape_count = gAgentWearables.getWearableCount(LLWearableType::WT_SHAPE);
	S32 hair_count = gAgentWearables.getWearableCount(LLWearableType::WT_HAIR);
	S32 eye_count = gAgentWearables.getWearableCount(LLWearableType::WT_EYES);
	S32 skin_count = gAgentWearables.getWearableCount(LLWearableType::WT_SKIN);
	if (!shape_count || !hair_count || !eye_count || !skin_count)
	{
		if (do_warn)
		{
			LL_INFOS() << "Self is clouded due to missing one or more required body parts: "
					<< (shape_count ? "" : "SHAPE ")
					<< (hair_count ? "" : "HAIR ")
					<< (eye_count ? "" : "EYES ")
					<< (skin_count ? "" : "SKIN ")
					<< LL_ENDL;
		}
		return true;
	}

	if (!isTextureDefined(TEX_HAIR, 0))
	{
		if (do_warn)
		{
			LL_INFOS() << "Self is clouded because of no hair texture" << LL_ENDL;
		}
		return true;
	}

	if (!mPreviousFullyLoaded)
	{
		if (!isLocalTextureDataAvailable(getLayerSet(BAKED_LOWER)) &&
			(!isTextureDefined(TEX_LOWER_BAKED, 0)))
		{
			if (do_warn)
			{
				LL_INFOS() << "Self is clouded because lower textures not baked" << LL_ENDL;
			}
			return true;
		}

		if (!isLocalTextureDataAvailable(getLayerSet(BAKED_UPPER)) &&
			(!isTextureDefined(TEX_UPPER_BAKED, 0)))
		{
			if (do_warn)
			{
				LL_INFOS() << "Self is clouded because upper textures not baked" << LL_ENDL;
			}
			return true;
		}

		for (U32 i = 0; i < mBakedTextureDatas.size(); i++)
		{
			if (i == BAKED_SKIRT && !isWearingWearableType(LLWearableType::WT_SKIRT))
				continue;

			const BakedTextureData& texture_data = mBakedTextureDatas[i];
			if (!isTextureDefined(texture_data.mTextureIndex, 0))
				continue;

			// Check for the case that texture is defined but not sufficiently loaded to display anything.
			const LLViewerTexture* baked_img = getImage( texture_data.mTextureIndex, 0 );
			if (!baked_img || !baked_img->hasGLTexture())
			{
				if (do_warn)
				{
					LL_INFOS() << "Self is clouded because texture at index " << i
							<< " (texture index is " << texture_data.mTextureIndex << ") is not loaded" << LL_ENDL;
				}
				return true;
			}
		}

		LL_DEBUGS() << "Avatar de-clouded" << LL_ENDL;
	}
	return false;
}

/*static*/
void LLVOAvatarSelf::debugOnTimingLocalTexLoaded(BOOL success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, BOOL final, void* userdata)
{
	if (gAgentAvatarp.notNull())
	{
		gAgentAvatarp->debugTimingLocalTexLoaded(success, src_vi, src, aux_src, discard_level, final, userdata);
	}
}

void LLVOAvatarSelf::debugTimingLocalTexLoaded(BOOL success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, BOOL final, void* userdata)
{
	LLAvatarTexData *data = (LLAvatarTexData *)userdata;
	if (!data)
	{
		return;
	}

	ETextureIndex index = data->mIndex;
	
	if (index < 0 || index >= TEX_NUM_INDICES)
	{
		return;
	}

	if (discard_level >=0 && discard_level <= MAX_DISCARD_LEVEL) // ignore discard level -1, as it means we have no data.
	{
		mDebugTextureLoadTimes[(U32)index][(U32)discard_level] = mDebugSelfLoadTimer.getElapsedTimeF32();
	}
	if (final)
	{
		delete data;
	}
}

void LLVOAvatarSelf::debugBakedTextureUpload(EBakedTextureIndex index, BOOL finished)
{
	U32 done = 0;
	if (finished)
	{
		done = 1;
	}
	mDebugBakedTextureTimes[index][done] = mDebugSelfLoadTimer.getElapsedTimeF32();
}

const std::string LLVOAvatarSelf::verboseDebugDumpLocalTextureDataInfo(const LLViewerTexLayerSet* layerset) const
{
	std::ostringstream outbuf;
    LLWearableType *wr_inst = LLWearableType::getInstance();
	for (LLAvatarAppearanceDictionary::BakedTextures::const_iterator baked_iter =
			 sAvatarDictionary->getBakedTextures().begin();
		 baked_iter != sAvatarDictionary->getBakedTextures().end();
		 ++baked_iter)
	{
		const EBakedTextureIndex baked_index = baked_iter->first;
		if (layerset == mBakedTextureDatas[baked_index].mTexLayerSet)
		{
			outbuf << "baked_index: " << baked_index << "\n";
			const LLAvatarAppearanceDictionary::BakedEntry *baked_dict = baked_iter->second;
			for (texture_vec_t::const_iterator local_tex_iter = baked_dict->mLocalTextures.begin();
				 local_tex_iter != baked_dict->mLocalTextures.end();
				 ++local_tex_iter)
			{
				const ETextureIndex tex_index = *local_tex_iter;
				const std::string tex_name = sAvatarDictionary->getTexture(tex_index)->mName;
				outbuf << "  tex_index " << (S32) tex_index << " name " << tex_name << "\n";
				const LLWearableType::EType wearable_type = sAvatarDictionary->getTEWearableType(tex_index);
				const U32 wearable_count = gAgentWearables.getWearableCount(wearable_type);
				if (wearable_count > 0)
				{
					for (U32 wearable_index = 0; wearable_index < wearable_count; wearable_index++)
					{
						outbuf << "    " << wr_inst->getTypeName(wearable_type) << " " << wearable_index << ":";
						const LLLocalTextureObject *local_tex_obj = getLocalTextureObject(tex_index, wearable_index);
						if (local_tex_obj)
						{
							LLViewerFetchedTexture* image = dynamic_cast<LLViewerFetchedTexture*>( local_tex_obj->getImage() );
							if (tex_index >= 0
								&& local_tex_obj->getID() != IMG_DEFAULT_AVATAR
								&& !image->isMissingAsset())
							{
								outbuf << " id: " << image->getID()
									   << " refs: " << image->getNumRefs()
									   << " glocdisc: " << getLocalDiscardLevel(tex_index, wearable_index)
									   << " discard: " << image->getDiscardLevel()
									   << " desired: " << image->getDesiredDiscardLevel()
									   << " decode: " << image->getDecodePriority()
									   << " addl: " << image->getAdditionalDecodePriority()
									   << " ts: " << image->getTextureState()
									   << " bl: " << image->getBoostLevel()
									   << " fl: " << image->isFullyLoaded() // this is not an accessor for mFullyLoaded - see comment there.
									   << " cl: " << (image->isFullyLoaded() && image->getDiscardLevel()==0) // "completely loaded"
									   << " mvs: " << image->getMaxVirtualSize()
									   << " mvsc: " << image->getMaxVirtualSizeResetCounter()
									   << " mem: " << image->getTextureMemory();
							}
						}
						outbuf << "\n";
					}
				}
			}
			break;
		}
	}
	return outbuf.str();
}

void LLVOAvatarSelf::dumpAllTextures() const
{
	std::string vd_text = "Local textures per baked index and wearable:\n";
	for (LLAvatarAppearanceDefines::LLAvatarAppearanceDictionary::BakedTextures::const_iterator baked_iter = sAvatarDictionary->getBakedTextures().begin();
		 baked_iter != sAvatarDictionary->getBakedTextures().end();
		 ++baked_iter)
	{
		const LLAvatarAppearanceDefines::EBakedTextureIndex baked_index = baked_iter->first;
		const LLViewerTexLayerSet *layerset = debugGetLayerSet(baked_index);
		if (!layerset) continue;
		const LLViewerTexLayerSetBuffer *layerset_buffer = layerset->getViewerComposite();
		if (!layerset_buffer) continue;
		vd_text += verboseDebugDumpLocalTextureDataInfo(layerset);
	}
	LL_DEBUGS("Avatar") << vd_text << LL_ENDL;
}

const std::string LLVOAvatarSelf::debugDumpLocalTextureDataInfo(const LLViewerTexLayerSet* layerset) const
{
	std::string text="";
    LLWearableType *wr_inst = LLWearableType::getInstance();

	text = llformat("[Final:%d Avail:%d] ",isLocalTextureDataFinal(layerset), isLocalTextureDataAvailable(layerset));

	/* if (layerset == mBakedTextureDatas[BAKED_HEAD].mTexLayerSet)
	   return getLocalDiscardLevel(TEX_HEAD_BODYPAINT) >= 0; */
	for (LLAvatarAppearanceDictionary::BakedTextures::const_iterator baked_iter = sAvatarDictionary->getBakedTextures().begin();
		 baked_iter != sAvatarDictionary->getBakedTextures().end();
		 ++baked_iter)
	{
		const EBakedTextureIndex baked_index = baked_iter->first;
		if (layerset == mBakedTextureDatas[baked_index].mTexLayerSet)
		{
			const LLAvatarAppearanceDictionary::BakedEntry *baked_dict = baked_iter->second;
			text += llformat("%d-%s ( ",baked_index, baked_dict->mName.c_str());
			for (texture_vec_t::const_iterator local_tex_iter = baked_dict->mLocalTextures.begin();
				 local_tex_iter != baked_dict->mLocalTextures.end();
				 ++local_tex_iter)
			{
				const ETextureIndex tex_index = *local_tex_iter;
				const LLWearableType::EType wearable_type = sAvatarDictionary->getTEWearableType(tex_index);
				const U32 wearable_count = gAgentWearables.getWearableCount(wearable_type);
				if (wearable_count > 0)
				{
					text += wr_inst->getTypeName(wearable_type) + ":";
					for (U32 wearable_index = 0; wearable_index < wearable_count; wearable_index++)
					{
						const U32 discard_level = getLocalDiscardLevel(tex_index, wearable_index);
						std::string discard_str = llformat("%d ",discard_level);
						text += llformat("%d ",discard_level);
					}
				}
			}
			text += ")";
			break;
		}
	}
	return text;
}

const std::string LLVOAvatarSelf::debugDumpAllLocalTextureDataInfo() const
{
	std::string text;
	const U32 override_tex_discard_level = gSavedSettings.getU32("TextureDiscardLevel");

	for (U32 i = 0; i < mBakedTextureDatas.size(); i++)
	{
		const LLAvatarAppearanceDictionary::BakedEntry *baked_dict = sAvatarDictionary->getBakedTexture((EBakedTextureIndex)i);
		BOOL is_texture_final = TRUE;
		for (texture_vec_t::const_iterator local_tex_iter = baked_dict->mLocalTextures.begin();
			 local_tex_iter != baked_dict->mLocalTextures.end();
			 ++local_tex_iter)
		{
			const ETextureIndex tex_index = *local_tex_iter;
			const LLWearableType::EType wearable_type = sAvatarDictionary->getTEWearableType(tex_index);
			const U32 wearable_count = gAgentWearables.getWearableCount(wearable_type);
			for (U32 wearable_index = 0; wearable_index < wearable_count; wearable_index++)
			{
				is_texture_final &= (getLocalDiscardLevel(*local_tex_iter, wearable_index) <= (S32)(override_tex_discard_level));
			}
		}
		text += llformat("%s:%d ",baked_dict->mName.c_str(),is_texture_final);
	}
	return text;
}

void LLVOAvatarSelf::appearanceChangeMetricsCoro(std::string url)
{
    LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
    LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
        httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("appearanceChangeMetrics", httpPolicy));
    LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);
    LLCore::HttpOptions::ptr_t httpOpts = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions);

    S32 currentSequence = mMetricSequence;
    if (S32_MAX == ++mMetricSequence)
        mMetricSequence = 0;

    LLSD msg;
    msg["message"] = "ViewerAppearanceChangeMetrics";
    msg["session_id"] = gAgentSessionID;
    msg["agent_id"] = gAgentID;
    msg["sequence"] = currentSequence;
    msg["initial"] = mInitialMetric;
    msg["break"] = false;
    msg["duration"] = mTimeSinceLastRezMessage.getElapsedTimeF32();

    // Status of our own rezzing.
    msg["rez_status"] = LLVOAvatar::rezStatusToString(getRezzedStatus());

    // Status of all nearby avs including ourself.
    msg["nearby"] = LLSD::emptyArray();
    std::vector<S32> rez_counts;
    LLVOAvatar::getNearbyRezzedStats(rez_counts);
    for (S32 rez_stat = 0; rez_stat < rez_counts.size(); ++rez_stat)
    {
        std::string rez_status_name = LLVOAvatar::rezStatusToString(rez_stat);
        msg["nearby"][rez_status_name] = rez_counts[rez_stat];
    }

    //	std::vector<std::string> bucket_fields("timer_name","is_self","grid_x","grid_y","is_using_server_bake");
    std::vector<std::string> by_fields;
    by_fields.push_back("timer_name");
    by_fields.push_back("completed");
    by_fields.push_back("grid_x");
    by_fields.push_back("grid_y");
    by_fields.push_back("is_using_server_bakes");
    by_fields.push_back("is_self");
    by_fields.push_back("central_bake_version");
    LLSD summary = summarize_by_buckets(mPendingTimerRecords, by_fields, std::string("elapsed"));
    msg["timers"] = summary;

    mPendingTimerRecords.clear();

    LL_DEBUGS("Avatar") << avString() << "message: " << ll_pretty_print_sd(msg) << LL_ENDL;

    gPendingMetricsUploads++;

    LLSD result = httpAdapter->postAndSuspend(httpRequest, url, msg);

    LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
    LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);

    gPendingMetricsUploads--;

    if (!status)
    {
        LL_WARNS("Avatar") << "Unable to upload statistics" << LL_ENDL;
        return;
    }
    else
    {
        LL_INFOS("Avatar") << "Statistics upload OK" << LL_ENDL;
        mInitialMetric = false;
    }
}

bool LLVOAvatarSelf::updateAvatarRezMetrics(bool force_send)
{
	const F32 AV_METRICS_INTERVAL_QA = 30.0;
	F32 send_period = 300.0;

	static LLCachedControl<bool> qa_mode_metrics(gSavedSettings,"QAModeMetrics");
	if (qa_mode_metrics)
	{
		send_period = AV_METRICS_INTERVAL_QA;
	}

	if (force_send || mTimeSinceLastRezMessage.getElapsedTimeF32() > send_period)
	{
		// Stats for completed phases have been getting logged as they
		// complete.  This will give us stats for any timers that
		// haven't finished as of the metric's being sent.
		
		if (force_send)
		{
			LLVOAvatar::logPendingPhasesAllAvatars();
		}
		sendViewerAppearanceChangeMetrics();
	}

	return false;
}

void LLVOAvatarSelf::addMetricsTimerRecord(const LLSD& record)
{
	mPendingTimerRecords.push_back(record);
}

bool operator<(const LLSD& a, const LLSD& b)
{
	std::ostringstream aout, bout;
	aout << LLSDNotationStreamer(a);
	bout << LLSDNotationStreamer(b);
	std::string astring = aout.str();
	std::string bstring = bout.str();

	return astring < bstring;

}

// Given a vector of LLSD records, return an LLSD array of bucketed stats for val_field.
LLSD summarize_by_buckets(std::vector<LLSD> in_records,
						  std::vector<std::string> by_fields,
						  std::string val_field)
{
	LLSD result = LLSD::emptyArray();
	std::map<LLSD,LLViewerStats::StatsAccumulator> accum;
	for (std::vector<LLSD>::iterator in_record_iter = in_records.begin();
		 in_record_iter != in_records.end(); ++in_record_iter)
	{
		LLSD& record = *in_record_iter;
		LLSD key;
		for (std::vector<std::string>::iterator field_iter = by_fields.begin();
			 field_iter != by_fields.end(); ++field_iter)
		{
			const std::string& field = *field_iter;
			key[field] = record[field];
		}
		LLViewerStats::StatsAccumulator& stats = accum[key];
		F32 value = record[val_field].asReal();
		stats.push(value);
	}
	for (std::map<LLSD,LLViewerStats::StatsAccumulator>::iterator accum_it = accum.begin();
		 accum_it != accum.end(); ++accum_it)
	{
		LLSD out_record = accum_it->first;
		out_record["stats"] = accum_it->second.asLLSD();
		result.append(out_record);
	}
	return result;
}

void LLVOAvatarSelf::sendViewerAppearanceChangeMetrics()
{
    std::string	caps_url;
	if (getRegion())
	{
		// runway - change here to activate.
		caps_url = getRegion()->getCapability("ViewerMetrics");
	}
	if (!caps_url.empty())
	{

        LLCoros::instance().launch("LLVOAvatarSelf::appearanceChangeMetricsCoro",
            boost::bind(&LLVOAvatarSelf::appearanceChangeMetricsCoro, this, caps_url));
		mTimeSinceLastRezMessage.reset();
	}
}

const LLUUID& LLVOAvatarSelf::grabBakedTexture(EBakedTextureIndex baked_index) const
{
	if (canGrabBakedTexture(baked_index))
	{
		ETextureIndex tex_index = sAvatarDictionary->bakedToLocalTextureIndex(baked_index);
		if (tex_index == TEX_NUM_INDICES)
		{
			return LLUUID::null;
		}
		return getTEImage( tex_index )->getID();
	}
	return LLUUID::null;
}

BOOL LLVOAvatarSelf::canGrabBakedTexture(EBakedTextureIndex baked_index) const
{
	ETextureIndex tex_index = sAvatarDictionary->bakedToLocalTextureIndex(baked_index);
	if (tex_index == TEX_NUM_INDICES)
	{
		return FALSE;
	}
	// Check if the texture hasn't been baked yet.
	if (!isTextureDefined(tex_index, 0))
	{
		LL_DEBUGS() << "getTEImage( " << (U32) tex_index << " )->getID() == IMG_DEFAULT_AVATAR" << LL_ENDL;
		return FALSE;
	}

	if (gAgent.isGodlikeWithoutAdminMenuFakery())
		return TRUE;

	// Check permissions of textures that show up in the
	// baked texture.  We don't want people copying people's
	// work via baked textures.

	const LLAvatarAppearanceDictionary::BakedEntry *baked_dict = sAvatarDictionary->getBakedTexture(baked_index);
	for (texture_vec_t::const_iterator iter = baked_dict->mLocalTextures.begin();
		 iter != baked_dict->mLocalTextures.end();
		 ++iter)
	{
		const ETextureIndex t_index = (*iter);
		LLWearableType::EType wearable_type = sAvatarDictionary->getTEWearableType(t_index);
		U32 count = gAgentWearables.getWearableCount(wearable_type);
		LL_DEBUGS() << "Checking index " << (U32) t_index << " count: " << count << LL_ENDL;
		
		for (U32 wearable_index = 0; wearable_index < count; ++wearable_index)
		{
			LLViewerWearable *wearable = gAgentWearables.getViewerWearable(wearable_type, wearable_index);
			if (wearable)
			{
				const LLLocalTextureObject *texture = wearable->getLocalTextureObject((S32)t_index);
				const LLUUID& texture_id = texture->getID();
				if (texture_id != IMG_DEFAULT_AVATAR)
				{
					// Search inventory for this texture.
					LLViewerInventoryCategory::cat_array_t cats;
					LLViewerInventoryItem::item_array_t items;
					LLAssetIDMatches asset_id_matches(texture_id);
					gInventory.collectDescendentsIf(LLUUID::null,
													cats,
													items,
													LLInventoryModel::INCLUDE_TRASH,
													asset_id_matches);

					BOOL can_grab = FALSE;
					LL_DEBUGS() << "item count for asset " << texture_id << ": " << items.size() << LL_ENDL;
					if (items.size())
					{
						// search for full permissions version
						for (S32 i = 0; i < items.size(); i++)
						{
							LLViewerInventoryItem* itemp = items[i];
												if (itemp->getIsFullPerm())
							{
								can_grab = TRUE;
								break;
							}
						}
					}
					if (!can_grab) return FALSE;
				}
			}
		}
	}

	return TRUE;
}

void LLVOAvatarSelf::addLocalTextureStats( ETextureIndex type, LLViewerFetchedTexture* imagep,
										   F32 texel_area_ratio, BOOL render_avatar, BOOL covered_by_baked)
{
	if (!isIndexLocalTexture(type)) return;

	// Sunshine - ignoring covered_by_baked will force local textures
	// to always load.  Fix for SH-4001 and many related issues.  Do
	// not restore this without some more targetted fix for the local
	// textures failing to load issue.
	//if (!covered_by_baked)
	{
		if (imagep->getID() != IMG_DEFAULT_AVATAR)
		{
			imagep->setNoDelete();
			if (imagep->getDiscardLevel() != 0)
			{
				F32 desired_pixels;
				desired_pixels = llmin(mPixelArea, (F32)getTexImageArea());
				
				imagep->setBoostLevel(getAvatarBoostLevel());
				imagep->setAdditionalDecodePriority(SELF_ADDITIONAL_PRI) ;
				imagep->resetTextureStats();
				imagep->setMaxVirtualSizeResetInterval(MAX_TEXTURE_VIRTUAL_SIZE_RESET_INTERVAL);
				imagep->addTextureStats( desired_pixels / texel_area_ratio );
				imagep->forceUpdateBindStats() ;
				if (imagep->getDiscardLevel() < 0)
				{
					mHasGrey = TRUE; // for statistics gathering
				}
			}
		}
		else
		{
			// texture asset is missing
			mHasGrey = TRUE; // for statistics gathering
		}
	}
}

LLLocalTextureObject* LLVOAvatarSelf::getLocalTextureObject(LLAvatarAppearanceDefines::ETextureIndex i, U32 wearable_index) const
{
	LLWearableType::EType type = sAvatarDictionary->getTEWearableType(i);
	LLViewerWearable* wearable = gAgentWearables.getViewerWearable(type, wearable_index);
	if (wearable)
	{
		return wearable->getLocalTextureObject(i);
	}

	return NULL;
}

//-----------------------------------------------------------------------------
// getBakedTE()
// Used by the LayerSet.  (Layer sets don't in general know what textures depend on them.)
//-----------------------------------------------------------------------------
ETextureIndex LLVOAvatarSelf::getBakedTE( const LLViewerTexLayerSet* layerset ) const
{
	for (U32 i = 0; i < mBakedTextureDatas.size(); i++)
	{
		if (layerset == mBakedTextureDatas[i].mTexLayerSet )
		{
			return mBakedTextureDatas[i].mTextureIndex;
		}
	}
	llassert(0);
	return TEX_HEAD_BAKED;
}

// FIXME: This is not called consistently. Something may be broken.
void LLVOAvatarSelf::outputRezDiagnostics() const
{
	if(!gSavedSettings.getBOOL("DebugAvatarLocalTexLoadedTime"))
	{
		return ;
	}

	const F32 final_time = mDebugSelfLoadTimer.getElapsedTimeF32();
	LL_DEBUGS("Avatar") << "REZTIME: Myself rez stats:" << LL_ENDL;
	LL_DEBUGS("Avatar") << "\t Time from avatar creation to load wearables: " << (S32)mDebugTimeWearablesLoaded << LL_ENDL;
	LL_DEBUGS("Avatar") << "\t Time from avatar creation to de-cloud: " << (S32)mDebugTimeAvatarVisible << LL_ENDL;
	LL_DEBUGS("Avatar") << "\t Time from avatar creation to de-cloud for others: " << (S32)final_time << LL_ENDL;
	LL_DEBUGS("Avatar") << "\t Load time for each texture: " << LL_ENDL;
	for (U32 i = 0; i < LLAvatarAppearanceDefines::TEX_NUM_INDICES; ++i)
	{
		std::stringstream out;
		out << "\t\t (" << i << ") ";
		U32 j=0;
		for (j=0; j <= MAX_DISCARD_LEVEL; j++)
		{
			out << "\t";
			S32 load_time = (S32)mDebugTextureLoadTimes[i][j];
			if (load_time == -1)
			{
				out << "*";
				if (j == 0)
					break;
			}
			else
			{
				out << load_time;
			}
		}

		// Don't print out non-existent textures.
		if (j != 0)
		{
			LL_DEBUGS("Avatar") << out.str() << LL_ENDL;
		}
	}
	LL_DEBUGS("Avatar") << "\t Time points for each upload (start / finish)" << LL_ENDL;
	// <FS:Beq> Missed update for OpenSim BOM
	// for (U32 i = 0; i < LLAvatarAppearanceDefines::BAKED_NUM_INDICES; ++i)
	for (U32 i = 0; i < getNumBakes(); ++i)
	// </FS:Beq>
	{
		LL_DEBUGS("Avatar") << "\t\t (" << i << ") \t" << (S32)mDebugBakedTextureTimes[i][0] << " / " << (S32)mDebugBakedTextureTimes[i][1] << LL_ENDL;
	}

	for (LLAvatarAppearanceDefines::LLAvatarAppearanceDictionary::BakedTextures::const_iterator baked_iter = sAvatarDictionary->getBakedTextures().begin();
		 baked_iter != sAvatarDictionary->getBakedTextures().end();
		 ++baked_iter)
	{
		const LLAvatarAppearanceDefines::EBakedTextureIndex baked_index = baked_iter->first;
		const LLViewerTexLayerSet *layerset = debugGetLayerSet(baked_index);
		if (!layerset) continue;
		const LLViewerTexLayerSetBuffer *layerset_buffer = layerset->getViewerComposite();
		if (!layerset_buffer) continue;
		LL_DEBUGS("Avatar") << layerset_buffer->dumpTextureInfo() << LL_ENDL;
	}

	dumpAllTextures();
}

void LLVOAvatarSelf::outputRezTiming(const std::string& msg) const
{
	LL_DEBUGS("Avatar")
		<< avString()
		<< llformat("%s. Time from avatar creation: %.2f", msg.c_str(), mDebugSelfLoadTimer.getElapsedTimeF32())
		<< LL_ENDL;
}

void LLVOAvatarSelf::reportAvatarRezTime() const
{
	// TODO: report mDebugSelfLoadTimer.getElapsedTimeF32() somehow.
}

//-- SUNSHINE CLEANUP - not clear we need any of this, may be sufficient to request server appearance in llviewermenu.cpp:handle_rebake_textures()
void LLVOAvatarSelf::forceBakeAllTextures(bool slam_for_debug)
{
	if(!isAgentAvatarValid()){ return; } // <FS:Beq/> Avoid force bak e on invalid avatar pointer (based on similar change by Rye)
	LL_INFOS() << "TAT: forced full rebake. " << LL_ENDL;

	for (U32 i = 0; i < mBakedTextureDatas.size(); i++)
	{
		ETextureIndex baked_index = mBakedTextureDatas[i].mTextureIndex;
		LLViewerTexLayerSet* layer_set = getLayerSet(baked_index);
		if (layer_set)
		{
			if (slam_for_debug)
			{
				layer_set->setUpdatesEnabled(TRUE);
				// <FS:Ansariel> [Legacy Bake]
				layer_set->cancelUpload();
			}

			// <FS:Ansariel> [Legacy Bake]
			//invalidateComposite(layer_set);
			invalidateComposite(layer_set, TRUE);
			add(LLStatViewer::TEX_REBAKES, 1);
		}
		else
		{
			LL_WARNS() << "TAT: NO LAYER SET FOR " << (S32)baked_index << LL_ENDL;
		}
	}

	// Don't know if this is needed
	updateMeshTextures();
}

//-----------------------------------------------------------------------------
// requestLayerSetUpdate()
//-----------------------------------------------------------------------------
void LLVOAvatarSelf::requestLayerSetUpdate(ETextureIndex index )
{
	/* switch(index)
		case LOCTEX_UPPER_BODYPAINT:  
		case LOCTEX_UPPER_SHIRT:
			if( mUpperBodyLayerSet )
				mUpperBodyLayerSet->requestUpdate(); */
	const LLAvatarAppearanceDictionary::TextureEntry *texture_dict = sAvatarDictionary->getTexture(index);
	if (!texture_dict)
		return;
	if (!texture_dict->mIsLocalTexture || !texture_dict->mIsUsedByBakedTexture)
		return;
	const EBakedTextureIndex baked_index = texture_dict->mBakedTextureIndex;
	if (mBakedTextureDatas[baked_index].mTexLayerSet)
	{
		mBakedTextureDatas[baked_index].mTexLayerSet->requestUpdate();
	}
}

LLViewerTexLayerSet* LLVOAvatarSelf::getLayerSet(ETextureIndex index) const
{
       /* switch(index)
               case TEX_HEAD_BAKED:
               case TEX_HEAD_BODYPAINT:
                       return mHeadLayerSet; */
       const LLAvatarAppearanceDictionary::TextureEntry *texture_dict = sAvatarDictionary->getTexture(index);
       if (texture_dict && texture_dict->mIsUsedByBakedTexture)
       {
               const EBakedTextureIndex baked_index = texture_dict->mBakedTextureIndex;
               return getLayerSet(baked_index);
       }
       return NULL;
}

LLViewerTexLayerSet* LLVOAvatarSelf::getLayerSet(EBakedTextureIndex baked_index) const
{
       /* switch(index)
               case TEX_HEAD_BAKED:
               case TEX_HEAD_BODYPAINT:
                       return mHeadLayerSet; */
	// <FS:Beq> BOM fallback support for OpenSim legacy
    //    if (baked_index >= 0 && baked_index < BAKED_NUM_INDICES)
       if (baked_index >= 0 && baked_index < getNumBakes())
	//</FS:Beq>
       {
		   return  getTexLayerSet(baked_index);
       }
       return NULL;
}




// static
void LLVOAvatarSelf::onCustomizeStart(bool disable_camera_switch)
{
	if (isAgentAvatarValid())
	{
		if (!gAgentAvatarp->mEndCustomizeCallback.get())
		{
			gAgentAvatarp->mEndCustomizeCallback = new LLUpdateAppearanceOnDestroy;
		}
		
		gAgentAvatarp->mIsEditingAppearance = true;
		gAgentAvatarp->mUseLocalAppearance = true;
		if (gSavedSettings.getBOOL("AppearanceCameraMovement") && !disable_camera_switch)
		{
			gAgentCamera.changeCameraToCustomizeAvatar();
		}
		
		gAgentAvatarp->invalidateAll(); // mark all bakes as dirty, request updates
		gAgentAvatarp->updateMeshTextures(); // make sure correct textures are applied to the avatar mesh.
		gAgentAvatarp->updateTextures(); // call updateTextureStats
	}
}

// static
void LLVOAvatarSelf::onCustomizeEnd(bool disable_camera_switch)
{

	if (isAgentAvatarValid())
	{
		gAgentAvatarp->mIsEditingAppearance = false;
		// <FS:Ansariel> [Legacy Bake]
		if (gAgentAvatarp->getRegion() && !gAgentAvatarp->getRegion()->getCentralBakeVersion())
		{
			// FIXME DRANO - move to sendAgentSetAppearance, make conditional on upload complete.
			gAgentAvatarp->mUseLocalAppearance = false;
		}
		// </FS:Ansariel> [Legacy Bake]
		gAgentAvatarp->invalidateAll();

		// <FS:Ansariel> Cinder's fix for STORM-2096 / FIRE-5740
		//if (gSavedSettings.getBOOL("AppearanceCameraMovement") && !disable_camera_switch)
		if (gAgentCamera.cameraCustomizeAvatar() && !disable_camera_switch)
		// </FS:Ansariel>
		{
			gAgentCamera.changeCameraToDefault();
			gAgentCamera.resetView();
		}

		// Dereferencing the previous callback will cause
		// updateAppearanceFromCOF to be called, whenever all refs
		// have resolved.
		gAgentAvatarp->mEndCustomizeCallback = NULL;
	}
}

// virtual
bool LLVOAvatarSelf::shouldRenderRigged() const
{
    return gAgent.needsRenderAvatar(); 
}

// HACK: this will null out the avatar's local texture IDs before the TE message is sent
//       to ensure local texture IDs are not sent to other clients in the area.
//       this is a short-term solution. The long term solution will be to not set the texture
//       IDs in the avatar object, and keep them only in the wearable.
//       This will involve further refactoring that is too risky for the initial release of 2.0.
bool LLVOAvatarSelf::sendAppearanceMessage(LLMessageSystem *mesgsys) const
{
	LLUUID texture_id[TEX_NUM_INDICES];
	// pack away current TEs to make sure we don't send them out
	for (LLAvatarAppearanceDictionary::Textures::const_iterator iter = sAvatarDictionary->getTextures().begin();
		 iter != sAvatarDictionary->getTextures().end();
		 ++iter)
	{
		const ETextureIndex index = iter->first;
		const LLAvatarAppearanceDictionary::TextureEntry *texture_dict = iter->second;
		// <FS:Beq> hide the surplus bakes and universals from non-BOM
		// if (!texture_dict->mIsBakedTexture)
		if( (index == TEX_SKIRT || index == TEX_SKIRT_TATTOO) && !gAgentAvatarp->isWearingWearableType(LLWearableType::WT_SKIRT) )
		{
			// TODO(BEQ): combine this with clause below once proven it works.
			LL_DEBUGS("Avatar") << "Ignoring skirt related texture at index=" << index << LL_ENDL;
			LLTextureEntry &entry = getTEref((U8) index);
			texture_id[index] = entry.getID();
			entry.setID(IMG_DEFAULT_AVATAR);
		}
		if (!texture_dict->mIsBakedTexture || index >= getRegion()->getRegionMaxTEs())
		// </FS:Beq>
		{
			LLTextureEntry &entry = getTEref((U8) index);
			texture_id[index] = entry.getID();
			entry.setID(IMG_DEFAULT_AVATAR);
		}
	}
	
	
	// <ClientTag> hack -- Zwagoth
	LL_INFOS() << "Setting clientTag" << LL_ENDL;
	LLTextureEntry* entry = getTE(0);
	//You edit this to change the tag in your client. Yes.
	const char* tag_client = "Firestorm";
	LLUUID client_name;
	strncpy((char*)&client_name.mData[0], tag_client, UUID_BYTES);
	static LLCachedControl<LLColor4> tag_color(gSavedPerAccountSettings, "FirestormTagColor", LLColor4(1,0,1,1));
	entry->setColor(tag_color);
	//This glow is used to tell if the tag color is set or not.
	entry->setGlow(0.1f);
	entry->setID(client_name);

    // Need to reset these if you turn off the tag system without relogging, they persist otherwise.
	//
	//LL_INFOS() << "Clearing clientTag" << LL_ENDL;
	//LLTextureEntry* entry = getTE(0);
	//if(entry->getGlow()>0.f)
	//{
	//	entry->setGlow(0.0f);
	//	entry->setColor(LLColor4::white);
	//}
	// </ClientTag>


	bool success = packTEMessage(mesgsys);

	// unpack TEs to make sure we don't re-trigger a bake
	for (LLAvatarAppearanceDictionary::Textures::const_iterator iter = sAvatarDictionary->getTextures().begin();
		 iter != sAvatarDictionary->getTextures().end();
		 ++iter)
	{
		const ETextureIndex index = iter->first;
		const LLAvatarAppearanceDictionary::TextureEntry *texture_dict = iter->second;
		// <FS:Beq> hide the surplus bakes and universals from non-BOM
		// if (!texture_dict->mIsBakedTexture)
		if (!texture_dict->mIsBakedTexture || index >= getRegion()->getRegionMaxTEs())
		// </FS:Beq>
		{
			LLTextureEntry &entry = getTEref((U8) index);
			entry.setID(texture_id[index]);
		}
	}

	return success;
}

//------------------------------------------------------------------------
// sendHoverHeight()
//------------------------------------------------------------------------
void LLVOAvatarSelf::sendHoverHeight() const
{
	std::string url = gAgent.getRegionCapability("AgentPreferences");

	if (!url.empty())
	{
		LLSD update = LLSD::emptyMap();
		const LLVector3& hover_offset = getHoverOffset();
		update["hover_height"] = hover_offset[2];

		LL_DEBUGS("Avatar") << avString() << "sending hover height value " << hover_offset[2] << LL_ENDL;

        // *TODO: - this class doesn't really do anything, could just use a base
        // class responder if nothing else gets added. 
        // (comment from removed Responder)
        LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, update, 
            "Hover height sent to sim", "Hover height not sent to sim");
		mLastHoverOffsetSent = hover_offset;
	}
}

void LLVOAvatarSelf::setHoverOffset(const LLVector3& hover_offset, bool send_update)
{
	if (getHoverOffset() != hover_offset)
	{
		LL_INFOS("Avatar") << avString() << " setting hover due to change " << hover_offset[2] << LL_ENDL;
		LLVOAvatar::setHoverOffset(hover_offset, send_update);
	}
	if (send_update && (hover_offset != mLastHoverOffsetSent))
	{
		LL_INFOS("Avatar") << avString() << " sending hover due to change " << hover_offset[2] << LL_ENDL;
		sendHoverHeight();
	}
}

//------------------------------------------------------------------------
// needsRenderBeam()
//------------------------------------------------------------------------
BOOL LLVOAvatarSelf::needsRenderBeam()
{
	LLTool *tool = LLToolMgr::getInstance()->getCurrentTool();

	BOOL is_touching_or_grabbing = (tool == LLToolGrab::getInstance() && LLToolGrab::getInstance()->isEditing());
	if (LLToolGrab::getInstance()->getEditingObject() && 
		LLToolGrab::getInstance()->getEditingObject()->isAttachment())
	{
		// don't render selection beam on hud objects
		is_touching_or_grabbing = FALSE;
	}
	return is_touching_or_grabbing || (getAttachmentState() & AGENT_STATE_EDITING && LLSelectMgr::getInstance()->shouldShowSelection());
}

// static
void LLVOAvatarSelf::deleteScratchTextures()
{
	for(std::map< LLGLenum, LLGLuint*>::iterator it = sScratchTexNames.begin(), end_it = sScratchTexNames.end();
		it != end_it;
		++it)
	{
		LLImageGL::deleteTextures(1, (U32 *)it->second );
		stop_glerror();
	}

	if( sScratchTexBytes.value() )
	{
		LL_DEBUGS() << "Clearing Scratch Textures " << (S32Kilobytes)sScratchTexBytes << LL_ENDL;

		delete_and_clear(sScratchTexNames);
		LLImageGL::sGlobalTextureMemory -= sScratchTexBytes;
		sScratchTexBytes = S32Bytes(0);
	}
}

// static 
void LLVOAvatarSelf::dumpScratchTextureByteCount()
{
	LL_INFOS() << "Scratch Texture GL: " << (sScratchTexBytes/1024) << "KB" << LL_ENDL;
}

void LLVOAvatarSelf::dumpWearableInfo(LLAPRFile& outfile)
{
	// <FS:ND> Remove LLVolatileAPRPool/apr_file_t and use FILE* instead
	// apr_file_t* file = outfile.getFileHandle();
	LLAPRFile::tFiletype* file = outfile.getFileHandle();
	// </FS:ND>

	if (!file)
	{
		return;
	}

	
	apr_file_printf( file, "\n<wearable_info>\n" );

	LLWearableData *wd = getWearableData();
    LLWearableType *wr_inst = LLWearableType::getInstance();
	for (S32 type = 0; type < LLWearableType::WT_COUNT; type++)
	{
		const std::string& type_name = wr_inst->getTypeName((LLWearableType::EType)type);
		for (U32 j=0; j< wd->getWearableCount((LLWearableType::EType)type); j++)
		{
			LLViewerWearable *wearable = gAgentWearables.getViewerWearable((LLWearableType::EType)type,j);
			apr_file_printf( file, "\n\t    <wearable type=\"%s\" name=\"%s\"/>\n",
							 type_name.c_str(), wearable->getName().c_str() );
			LLWearable::visual_param_vec_t v_params;
			wearable->getVisualParams(v_params);
			for (LLWearable::visual_param_vec_t::iterator it = v_params.begin();
				 it != v_params.end(); ++it)
			{
				LLVisualParam *param = *it;
				dump_visual_param(file, param, param->getWeight());
			}
		}
	}
	apr_file_printf( file, "\n</wearable_info>\n" );
}

// [RLVa:KB] - Checked: 2013-03-03 (RLVa-1.4.8)
F32 LLVOAvatarSelf::getAvatarOffset() /*const*/
{
#ifdef OPENSIM
	if (!LLGridManager::getInstance()->isInSecondLife())
	{
		return (isUsingServerBakes()) ? LLAvatarAppearance::getAvatarOffset() : gSavedPerAccountSettings.getF32("AvatarHoverOffsetZ");
	}
#endif
	return LLAvatarAppearance::getAvatarOffset();
}
// [/RLVa:KB]

// <FS:Ansariel> [Legacy Bake]
//-----------------------------------------------------------------------------
// Legacy baking
//-----------------------------------------------------------------------------
//virtual
U32  LLVOAvatarSelf::processUpdateMessage(LLMessageSystem *mesgsys,
													 void **user_data,
													 U32 block_num,
													 const EObjectUpdateType update_type,
													 LLDataPacker *dp)
{
	U32 retval = LLVOAvatar::processUpdateMessage(mesgsys,user_data,block_num,update_type,dp);

	return retval;
}

BOOL LLVOAvatarSelf::isBakedTextureFinal(const LLAvatarAppearanceDefines::EBakedTextureIndex index) const
{
	const LLViewerTexLayerSet *layerset = getLayerSet(index);
	if (!layerset) return FALSE;
	const LLViewerTexLayerSetBuffer *layerset_buffer = layerset->getViewerComposite();
	if (!layerset_buffer) return FALSE;
	return !layerset_buffer->uploadNeeded();
}

//-----------------------------------------------------------------------------
// requestLayerSetUploads()
//-----------------------------------------------------------------------------
void LLVOAvatarSelf::requestLayerSetUploads()
{
	for (U32 i = 0; i < mBakedTextureDatas.size(); i++)
	{
		requestLayerSetUpload((EBakedTextureIndex)i);
	}
}

void LLVOAvatarSelf::requestLayerSetUpload(LLAvatarAppearanceDefines::EBakedTextureIndex i)
{
	ETextureIndex tex_index = mBakedTextureDatas[i].mTextureIndex;
	const BOOL layer_baked = isTextureDefined(tex_index, gAgentWearables.getWearableCount(tex_index));
	LLViewerTexLayerSet *layerset = getLayerSet(i);
	if (!layer_baked && layerset)
	{
		layerset->requestUpload();
	}
}

// virtual
bool LLVOAvatarSelf::hasPendingBakedUploads() const
{
	// <FS:Beq> BOMOS constrain uploads for non-BOM.
	// for (U32 i = 0; i < mBakedTextureDatas.size(); i++)
	for (U32 i = 0; i < getNumBakes(); i++)
	{
		LLViewerTexLayerSet* layerset = getTexLayerSet(i);
		if (layerset && layerset->getViewerComposite() && layerset->getViewerComposite()->uploadPending())
		{
			return true;
		}
	}
	return false;
}

void CheckAgentAppearanceService_httpSuccess( LLSD const &aData )
{
		LL_DEBUGS("Avatar") << "OK" << LL_ENDL;
}

void forceAppearanceUpdate()
{
	// Trying to rebake immediately after crossing region boundary
	// seems to be failure prone; adding a delay factor. Yes, this
	// fix is ad-hoc and not guaranteed to work in all cases.
	// <FS:Beq> Never trust the avatarp lifetime.
	// doAfterInterval(boost::bind(&LLVOAvatarSelf::forceBakeAllTextures,	gAgentAvatarp.get(), true), 5.0);
	doAfterInterval([](){ if (isAgentAvatarValid()) { gAgentAvatarp->forceBakeAllTextures(true); }}, 5.0);
	// </FS:Beq>

}

void CheckAgentAppearanceService_httpFailure( LLSD const &aData )
{
	if (isAgentAvatarValid())
	{
		LL_DEBUGS("Avatar") << "failed, will rebake " << aData << LL_ENDL;
		forceAppearanceUpdate();
	}	
}

void LLVOAvatarSelf::checkForUnsupportedServerBakeAppearance()
{
	// Need to check only if we have a server baked appearance and are
	// in a non-baking region.
	if (!gAgentAvatarp->isUsingServerBakes())
		return;
	if (!gAgent.getRegion() || gAgent.getRegion()->getCentralBakeVersion()!=0)
		return;

	// if baked image service is unknown, need to refresh.
	if (LLAppearanceMgr::instance().getAppearanceServiceURL().empty())
	{
		forceAppearanceUpdate();
	}
	// query baked image service to check status.
	std::string image_url = gAgentAvatarp->getImageURL(TEX_HEAD_BAKED,
													   getTE(TEX_HEAD_BAKED)->getID());

	LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpGet( image_url, CheckAgentAppearanceService_httpSuccess, CheckAgentAppearanceService_httpFailure );
}

void LLVOAvatarSelf::setNewBakedTexture(LLAvatarAppearanceDefines::EBakedTextureIndex i, const LLUUID &uuid)
{
	ETextureIndex index = LLAvatarAppearance::getDictionary()->bakedToLocalTextureIndex(i);
	setNewBakedTexture(index, uuid);
}


//-----------------------------------------------------------------------------
// setNewBakedTexture()
// A new baked texture has been successfully uploaded and we can start using it now.
//-----------------------------------------------------------------------------
void LLVOAvatarSelf::setNewBakedTexture( ETextureIndex te, const LLUUID& uuid )
{
	// Baked textures live on other sims.
	LLHost target_host = getObjectHost();	
	setTEImage( te, LLViewerTextureManager::getFetchedTextureFromHost( uuid, FTT_HOST_BAKE, target_host ) );
	updateMeshTextures();
	dirtyMesh();

	LLVOAvatar::cullAvatarsByPixelArea();

	/* switch(te)
		case TEX_HEAD_BAKED:
			LL_INFOS() << "New baked texture: HEAD" << LL_ENDL; */
	const LLAvatarAppearanceDictionary::TextureEntry *texture_dict = LLAvatarAppearance::getDictionary()->getTexture(te);
	if (texture_dict->mIsBakedTexture)
	{
		debugBakedTextureUpload(texture_dict->mBakedTextureIndex, TRUE); // FALSE for start of upload, TRUE for finish.
		LL_INFOS() << "New baked texture: " << texture_dict->mName << " UUID: " << uuid <<LL_ENDL;
	}
	else
	{
		LL_WARNS() << "New baked texture: unknown te " << te << LL_ENDL;
	}
	
	//	dumpAvatarTEs( "setNewBakedTexture() send" );
	// RN: throttle uploads
	if (!hasPendingBakedUploads())
	{
		gAgent.sendAgentSetAppearance();

		if (gSavedSettings.getBOOL("DebugAvatarRezTime"))
		{
			LLSD args;
			args["EXISTENCE"] = llformat("%d",(U32)mDebugExistenceTimer.getElapsedTimeF32());
			args["TIME"] = llformat("%d",(U32)mDebugSelfLoadTimer.getElapsedTimeF32());
			if (isAllLocalTextureDataFinal())
			{
				LLNotificationsUtil::add("AvatarRezSelfBakedDoneNotification",args);
				LL_DEBUGS("Avatar") << "REZTIME: [ " << (U32)mDebugExistenceTimer.getElapsedTimeF32()
						<< "sec ]"
						<< avString() 
						<< "RuthTimer " << (U32)mRuthDebugTimer.getElapsedTimeF32()
						<< " SelfLoadTimer " << (U32)mDebugSelfLoadTimer.getElapsedTimeF32()
						<< " Notification " << "AvatarRezSelfBakedDoneNotification"
						<< LL_ENDL;
			}
			else
			{
				args["STATUS"] = debugDumpAllLocalTextureDataInfo();
				LLNotificationsUtil::add("AvatarRezSelfBakedUpdateNotification",args);
				LL_DEBUGS("Avatar") << "REZTIME: [ " << (U32)mDebugExistenceTimer.getElapsedTimeF32()
						<< "sec ]"
						<< avString() 
						<< "RuthTimer " << (U32)mRuthDebugTimer.getElapsedTimeF32()
						<< " SelfLoadTimer " << (U32)mDebugSelfLoadTimer.getElapsedTimeF32()
						<< " Notification " << "AvatarRezSelfBakedUpdateNotification"
						<< LL_ENDL;
			}
		}

		outputRezDiagnostics();
	}
}

//-----------------------------------------------------------------------------
// setCachedBakedTexture()
// A baked texture id was received from a cache query, make it active
//-----------------------------------------------------------------------------
void LLVOAvatarSelf::setCachedBakedTexture( ETextureIndex te, const LLUUID& uuid )
{
	setTETexture( te, uuid );

	/* switch(te)
		case TEX_HEAD_BAKED:
			if( mHeadLayerSet )
				mHeadLayerSet->cancelUpload(); */
	for (U32 i = 0; i < mBakedTextureDatas.size(); i++)
	{
		LLViewerTexLayerSet *layerset = getTexLayerSet(i);
		if ( mBakedTextureDatas[i].mTextureIndex == te && layerset)
		{
			layerset->cancelUpload();
		}
	}
}

// static
void LLVOAvatarSelf::processRebakeAvatarTextures(LLMessageSystem* msg, void**)
{
	LLUUID texture_id;
	msg->getUUID("TextureData", "TextureID", texture_id);
	if (!isAgentAvatarValid()) return;

	// If this is a texture corresponding to one of our baked entries, 
	// just rebake that layer set.
	BOOL found = FALSE;

	/* ETextureIndex baked_texture_indices[BAKED_NUM_INDICES] =
			TEX_HEAD_BAKED,
			TEX_UPPER_BAKED, */
	for (LLAvatarAppearanceDictionary::Textures::const_iterator iter = LLAvatarAppearance::getDictionary()->getTextures().begin();
		 iter != LLAvatarAppearance::getDictionary()->getTextures().end();
		 ++iter)
	{
		const ETextureIndex index = iter->first;
		const LLAvatarAppearanceDictionary::TextureEntry *texture_dict = iter->second;
		if (texture_dict->mIsBakedTexture)
		{
			if (texture_id == gAgentAvatarp->getTEImage(index)->getID())
			{
				LLViewerTexLayerSet* layer_set = gAgentAvatarp->getLayerSet(index);
				if (layer_set)
				{
					LL_INFOS() << "TAT: rebake - matched entry " << (S32)index << LL_ENDL;
					gAgentAvatarp->invalidateComposite(layer_set, TRUE);
					found = TRUE;
					add(LLStatViewer::TEX_REBAKES, 1);
				}
			}
		}
	}

	// If texture not found, rebake all entries.
	if (!found)
	{
		gAgentAvatarp->forceBakeAllTextures();
	}
	else
	{
		// Not sure if this is necessary, but forceBakeAllTextures() does it.
		gAgentAvatarp->updateMeshTextures();
	}
}
// </FS:Ansariel> [Legacy Bake]

