/** 
 * @file llselectmgr.cpp
 * @brief A manager for selected objects and faces.
 *
 * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc.
 * $License$
 */

#include "llviewerprecompiledheaders.h"

// file include
#include "llselectmgr.h"

// library includes
#include "llcachename.h"
#include "lldbstrings.h"
#include "lleconomy.h"
#include "llgl.h"
#include "llpermissions.h"
#include "llpermissionsflags.h"
#include "llptrskiplist.h"
#include "llundo.h"
#include "lluuid.h"
#include "llvolume.h"
#include "message.h"
#include "object_flags.h"
#include "llquaternion.h"

// viewer includes
#include "llagent.h"
#include "llviewerwindow.h"
#include "lldrawable.h"
#include "llfloaterproperties.h"
#include "llfloaterrate.h"
#include "llfloaterreporter.h"
#include "llfloatertools.h"
#include "llframetimer.h"
#include "llhudeffecttrail.h"
#include "llhudmanager.h"
#include "llinventorymodel.h"
#include "llmenugl.h"
#include "llstatusbar.h"
#include "llsurface.h"
#include "lltool.h"
#include "lltooldraganddrop.h"
#include "lltoolmgr.h"
#include "lltoolpie.h"
#include "llui.h"
#include "llviewercamera.h"
#include "llviewercontrol.h"
#include "llviewerimagelist.h"
#include "llviewermenu.h"
#include "llviewerobject.h"
#include "llviewerobjectlist.h"
#include "llviewerregion.h"
#include "llviewerstats.h"
#include "llvoavatar.h"
#include "llvovolume.h"
#include "pipeline.h"

#include "llglheaders.h"

//
// Consts
//

const S32 NUM_SELECTION_UNDO_ENTRIES = 200;
const F32 SILHOUETTE_UPDATE_THRESHOLD_SQUARED = 0.02f;
const S32 OWNERSHIP_COST_PER_OBJECT = 10; // Must be the same as economy_constants.price_object_claim in the database.
const S32 MAX_ACTION_QUEUE_SIZE = 20;
const S32 MAX_SILS_PER_FRAME = 50;
const S32 MAX_OBJECTS_PER_PACKET = 254;

extern LLGlobalEconomy *gGlobalEconomy;
extern LLUUID			gLastHitObjectID;
extern LLVector3d		gLastHitObjectOffset;

//
// Globals
//
LLSelectMgr* gSelectMgr = NULL;

BOOL gDebugSelectMgr = FALSE;

BOOL gHideSelectedObjects = FALSE;
BOOL gAllowSelectAvatar = FALSE;

BOOL LLSelectMgr::sRectSelectInclusive = TRUE;
BOOL LLSelectMgr::sRenderHiddenSelections = TRUE;
BOOL LLSelectMgr::sRenderLightRadius = FALSE;
F32	LLSelectMgr::sHighlightThickness = 0.f;
F32	LLSelectMgr::sHighlightUScale = 0.f;
F32	LLSelectMgr::sHighlightVScale = 0.f;
F32	LLSelectMgr::sHighlightAlpha = 0.f;
F32	LLSelectMgr::sHighlightAlphaTest = 0.f;
F32	LLSelectMgr::sHighlightUAnim = 0.f;
F32	LLSelectMgr::sHighlightVAnim = 0.f;
LLColor4 LLSelectMgr::sSilhouetteParentColor;
LLColor4 LLSelectMgr::sSilhouetteChildColor;
LLColor4 LLSelectMgr::sHighlightParentColor;
LLColor4 LLSelectMgr::sHighlightChildColor;
LLColor4 LLSelectMgr::sContextSilhouetteColor;


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// struct LLDeRezInfo
//
// Used to keep track of important derez info. 
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

struct LLDeRezInfo
{
	EDeRezDestination mDestination;
	LLUUID mDestinationID;
	LLDeRezInfo(EDeRezDestination dest, const LLUUID& dest_id) :
		mDestination(dest), mDestinationID(dest_id) {}
};

//
// Imports
//


//
// Functions
//

//-----------------------------------------------------------------------------
// LLSelectMgr()
//-----------------------------------------------------------------------------
LLSelectMgr::LLSelectMgr()
{
	mTEMode = FALSE;
	mLastCameraPos.clearVec();

	sHighlightThickness	= gSavedSettings.getF32("SelectionHighlightThickness");
	sHighlightUScale	= gSavedSettings.getF32("SelectionHighlightUScale");
	sHighlightVScale	= gSavedSettings.getF32("SelectionHighlightVScale");
	sHighlightAlpha		= gSavedSettings.getF32("SelectionHighlightAlpha");
	sHighlightAlphaTest	= gSavedSettings.getF32("SelectionHighlightAlphaTest");
	sHighlightUAnim		= gSavedSettings.getF32("SelectionHighlightUAnim");
	sHighlightVAnim		= gSavedSettings.getF32("SelectionHighlightVAnim");

	sSilhouetteParentColor = gColors.getColor("SilhouetteParentColor");
	sSilhouetteChildColor = gColors.getColor("SilhouetteChildColor");
	sHighlightParentColor = gColors.getColor("HighlightParentColor");
	sHighlightChildColor = gColors.getColor("HighlightChildColor");
	sContextSilhouetteColor = gColors.getColor("ContextSilhouetteColor")*0.5f;

	sRenderLightRadius = gSavedSettings.getBOOL("RenderLightRadius");
	
	mRenderSilhouettes = TRUE;

	mGridMode = GRID_MODE_WORLD;
	gSavedSettings.setS32("GridMode", (S32)GRID_MODE_WORLD);
	mGridValid = FALSE;

	mSelectType = SELECT_TYPE_WORLD;
}


//-----------------------------------------------------------------------------
// ~LLSelectMgr()
//-----------------------------------------------------------------------------
LLSelectMgr::~LLSelectMgr()
{
	mHoverObjects.deleteAllNodes();
	mSelectedObjects.deleteAllNodes();
	mHighlightedObjects.deleteAllNodes();
	mRectSelectedObjects.clear();
	mGridObjects.deleteAllNodes();
	mUndoQueue.clear();
	mRedoQueue.clear();
}

bool LLSelectMgr::applyToObjects(LLSelectedObjectFunctor* func)
{
	bool result = true;
	LLViewerObject* object;
	for (object = getFirstObject(); object != NULL; object = getNextObject())
	{
		result = result && func->apply(object);
	}
	return result;
}

bool LLSelectMgr::applyToRootObjects(LLSelectedObjectFunctor* func)
{
	bool result = true;
	LLViewerObject* object;
	for (object = getFirstRootObject(); 
		 object != NULL; 
		 object = getNextRootObject())
	{
		result = result && func->apply(object);
	}
	return result;
}

bool LLSelectMgr::applyToNodes(LLSelectedNodeFunctor *func)
{
	bool result = true;
	LLSelectNode* node;
	for (node = getFirstNode(); node != NULL; node = getNextNode())
	{
		result = result && func->apply(node);
	}
	return result;
}

void LLSelectMgr::updateEffects()
{
	if (mEffectsTimer.getElapsedTimeF32() > 1.f)
	{
		mSelectedObjects.updateEffects();
		mEffectsTimer.reset();
	}
}

//-----------------------------------------------------------------------------
// Select just the object, not any other group members.
//-----------------------------------------------------------------------------
void LLSelectMgr::selectObjectOnly(LLViewerObject* object, S32 face)
{
	llassert( object );

	// Don't add an object that is already in the list
	if (object->isSelected() ) {
		// make sure point at position is updated
		updatePointAt();
		gEditMenuHandler = this;
		return;
	}

	if (!canSelectObject(object))
	{
		//make_ui_sound("UISndInvalidOp");
		return;
	}

	// llinfos << "Adding object to selected object list" << llendl;

	// Place it in the list and tag it.
	// This will refresh dialogs.
	addAsIndividual(object, face);

	// Stop the object from moving (this anticipates changes on the
	// simulator in LLTask::userSelect)
	//FIXME: shouldn't zero out these either
	object->setVelocity(LLVector3::zero);
	object->setAcceleration(LLVector3::zero);
	//object->setAngularVelocity(LLVector3::zero);
	object->resetRot();

	// Always send to simulator, so you get a copy of the 
	// permissions structure back.
	gMessageSystem->newMessageFast(_PREHASH_ObjectSelect);
	gMessageSystem->nextBlockFast(_PREHASH_AgentData);
	gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() );
	gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
	gMessageSystem->nextBlockFast(_PREHASH_ObjectData);
	gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, object->getLocalID() );
	LLViewerRegion* regionp = object->getRegion();
	gMessageSystem->sendReliable( regionp->getHost());

	updatePointAt();
	updateSelectionCenter();
	saveSelectedObjectTransform(SELECT_ACTION_TYPE_PICK);

	// have selection manager handle edit menu immediately after 
	// user selects an object
	if (getObjectCount())
	{
		gEditMenuHandler = this;
	}
}

//-----------------------------------------------------------------------------
// Select the object, parents and children.
//-----------------------------------------------------------------------------
void LLSelectMgr::selectObjectAndFamily(LLViewerObject* obj, BOOL add_to_end)
{
	llassert( obj );

	// This may be incorrect if things weren't family selected before... - djs 07/08/02
	// Don't add an object that is already in the list
	if (obj->isSelected() ) 
	{
		// make sure pointat position is updated
		updatePointAt();
		gEditMenuHandler = this;
		return;
	}

	if (!canSelectObject(obj))
	{
		//make_ui_sound("UISndInvalidOp");
		return;
	}

	// Since we're selecting a family, start at the root, but
	// don't include an avatar.
	LLViewerObject* root = obj;
	
	while(!root->isAvatar() && root->getParent() && !root->isJointChild())
	{
		LLViewerObject* parent = (LLViewerObject*)root->getParent();
		if (parent->isAvatar())
		{
			break;
		}
		root = parent;
	}

	// Collect all of the objects
	LLDynamicArray<LLViewerObject*> objects;

	root->addThisAndNonJointChildren(objects);
	addAsFamily(objects, add_to_end);

	updateSelectionCenter();
	saveSelectedObjectTransform(SELECT_ACTION_TYPE_PICK);
	updatePointAt();

	dialog_refresh_all();

	// Always send to simulator, so you get a copy of the permissions
	// structure back.
	sendSelect();

	// Stop the object from moving (this anticipates changes on the
	// simulator in LLTask::userSelect)
	root->setVelocity(LLVector3::zero);
	root->setAcceleration(LLVector3::zero);
	//root->setAngularVelocity(LLVector3::zero);
	root->resetRot();

	// leave component mode
	if (!gSavedSettings.getBOOL("SelectLinkedSet"))
	{
		gSavedSettings.setBOOL("SelectLinkedSet", TRUE);
		promoteSelectionToRoot();
	}

	// have selection manager handle edit menu immediately after 
	// user selects an object
	if (getObjectCount())
	{
		gEditMenuHandler = this;
	}
}

//-----------------------------------------------------------------------------
// Select the object, parents and children.
//-----------------------------------------------------------------------------
void LLSelectMgr::selectObjectAndFamily(const LLDynamicArray<LLViewerObject*>& object_list,
										BOOL send_to_sim)
{
	// Collect all of the objects, children included
	LLDynamicArray<LLViewerObject*> objects;
	LLViewerObject *object;
	S32 i;

	if (object_list.count() < 1) return;

	// NOTE -- we add the objects in REVERSE ORDER 
	// to preserve the order in the mSelectedObjects list
	for (i = object_list.count() - 1; i >= 0; i--)
	{
		object = object_list.get(i);

		llassert( object );

		if (!canSelectObject(object)) continue;

		object->addThisAndNonJointChildren(objects);
		addAsFamily(objects);

		// Stop the object from moving (this anticipates changes on the
		// simulator in LLTask::userSelect)
		object->setVelocity(LLVector3::zero);
		object->setAcceleration(LLVector3::zero);
		//object->setAngularVelocity(LLVector3::zero);
		object->resetRot();
	}

	updateSelectionCenter();
	saveSelectedObjectTransform(SELECT_ACTION_TYPE_PICK);
	updatePointAt();
	dialog_refresh_all();

	// Almost always send to simulator, so you get a copy of the permissions
	// structure back.
	// JC: The one case where you don't want to do this is if you're selecting
	// all the objects on a sim.
	if (send_to_sim)
	{
		sendSelect();
	}

	// leave component mode
	if (!gSavedSettings.getBOOL("SelectLinkedSet"))
	{		
		gSavedSettings.setBOOL("SelectLinkedSet", TRUE);
		promoteSelectionToRoot();
	}

	// have selection manager handle edit menu immediately after 
	// user selects an object
	if (getObjectCount())
	{
		gEditMenuHandler = this;
	}
}

// Use for when the simulator kills an object.  This version also
// handles informing the current tool of the object's deletion.
//
// Caller needs to call dialog_refresh_all if necessary.
BOOL LLSelectMgr::selectionRemoveObject(const LLUUID &id)
{


	BOOL object_found = FALSE;
	LLTool *tool = NULL;
	if (!gNoRender)
	{
		tool = gToolMgr->getCurrentTool( gKeyboard->currentMask(TRUE) );

		// It's possible that the tool is editing an object that is not selected
		LLViewerObject* tool_editing_object = tool->getEditingObject();
		if( tool_editing_object && tool_editing_object->mID == id)
		{
			tool->stopEditing();
			object_found = TRUE;
		}
	}

	// Iterate through selected objects list and kill the object
	if( !object_found )
	{
		LLViewerObject* prevobjp = NULL;
		for( LLViewerObject* tobjp = getFirstObject(); tobjp != NULL; tobjp = getNextObject() )
		{
			if (tobjp == prevobjp)
			{
				// Somehow we got stuck in an infinite loop... (DaveP)
				//  this logic is kind of twisted, not sure how this is happening, so...
				llwarns << "Detected infinite loop #1 in LLSelectMgr::selectionRemoveObject:|" << llendl;
				//MikeS. adding warning and comment...
				//These infinite loops happen because the LLSelectMgr iteration routines are non-reentrant.
				//deselectObjectAndFamily uses getFirstObject and getNextObject to mess with the array,
				//resetting the arrays internal iterator state. This needs fixing BAD.
				continue;
			}
			// It's possible the item being removed has an avatar sitting on it
			// So remove the avatar that is sitting on the object.
			if (tobjp->mID == id || tobjp->isAvatar())
			{
				if (!gNoRender)
				{
					tool->stopEditing();
				}

				// lose the selection, don't tell simulator, it knows
				deselectObjectAndFamily(tobjp, FALSE);

				if (tobjp->mID == id)
				{
					if(object_found == TRUE){
						//MikeS. adding warning... This happens when removing a linked attachment while sitting on an object..
						//I think the selection manager needs to be rewritten. BAD.
						llwarns << "Detected infinite loop #2 in LLSelectMgr::selectionRemoveObject:|" << llendl;
						break;
					}
					object_found = TRUE;
				}
			}
			prevobjp = tobjp;
		}
	}

	return object_found;
}

void LLSelectMgr::deselectObjectAndFamily(LLViewerObject* object, BOOL send_to_sim)
{
	// bail if nothing selected or if object wasn't selected in the first place
	if(!object) return;
	if(!object->isSelected()) return;

	// Collect all of the objects, and remove them
	LLDynamicArray<LLViewerObject*> objects;
	object = (LLViewerObject*)object->getRoot();
	object->addThisAndAllChildren(objects);
	remove(objects);

	if (!send_to_sim) return;

	//-----------------------------------------------------------
	// Inform simulator of deselection
	//-----------------------------------------------------------
	LLViewerRegion* regionp = object->getRegion();

	BOOL start_new_message = TRUE;
	S32 select_count = 0;

	LLMessageSystem* msg = gMessageSystem;
	for (S32 i = 0; i < objects.count(); i++)
	{
		if (start_new_message)
		{
			msg->newMessageFast(_PREHASH_ObjectDeselect);
			msg->nextBlockFast(_PREHASH_AgentData);
			msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() );
			msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
			select_count++;
			start_new_message = FALSE;
		}

		msg->nextBlockFast(_PREHASH_ObjectData);
		msg->addU32Fast(_PREHASH_ObjectLocalID, (objects[i])->getLocalID());
		select_count++;

		if(msg->mCurrentSendTotal >= MTUBYTES || select_count >= MAX_OBJECTS_PER_PACKET)
		{
			msg->sendReliable(regionp->getHost() );
			select_count = 0;
			start_new_message = TRUE;
		}
	}

	if (!start_new_message)
	{
		msg->sendReliable(regionp->getHost() );
	}

	updatePointAt();
	updateSelectionCenter();
}

void LLSelectMgr::deselectObjectOnly(LLViewerObject* object, BOOL send_to_sim)
{
	// bail if nothing selected or if object wasn't selected in the first place
	if (!object) return;
	if (!object->isSelected() ) return;

	if (send_to_sim)
	{
		LLViewerRegion* region = object->getRegion();
		gMessageSystem->newMessageFast(_PREHASH_ObjectDeselect);
		gMessageSystem->nextBlockFast(_PREHASH_AgentData);
		gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() );
		gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
		gMessageSystem->nextBlockFast(_PREHASH_ObjectData);
		gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, object->getLocalID() );
		gMessageSystem->sendReliable(region->getHost());
	}

	// This will refresh dialogs.
	remove( object );

	updatePointAt();
	updateSelectionCenter();
}


//-----------------------------------------------------------------------------
// addAsFamily
//-----------------------------------------------------------------------------

void LLSelectMgr::addAsFamily(LLDynamicArray<LLViewerObject*>& objects, BOOL add_to_end)
{
	S32 count = objects.count();
	LLViewerObject *objectp = NULL;

	LLSelectNode *nodep = NULL;
	for (S32 i = 0; i < count; i++)
	{
		objectp = objects.get(i);
		
		// Can't select yourself
		if (objectp->mID == gAgentID
			&& !gAllowSelectAvatar)
		{
			continue;
		}

		if (!objectp->isSelected())
		{
			nodep = new LLSelectNode(objectp, TRUE);
			if (add_to_end)
			{
				mSelectedObjects.addNodeAtEnd(nodep);
			}
			else
			{
				mSelectedObjects.addNode(nodep);
			}
			objectp->setSelected(TRUE);

			if (objectp->getNumTEs() > 0)
			{
				nodep->selectAllTEs(TRUE);
			}
			else
			{
				// object has no faces, so don't mess with faces
			}
		}
		else
		{
			// we want this object to be selected for real
			// so clear transient flag
			LLSelectNode* select_node = findSelectNode(objectp);
			if (select_node)
			{
				select_node->setTransient(FALSE);
			}
		}
	}
	saveSelectedObjectTransform(SELECT_ACTION_TYPE_PICK);
}

//-----------------------------------------------------------------------------
// addAsIndividual() - a single object, face, etc
//-----------------------------------------------------------------------------
void LLSelectMgr::addAsIndividual(LLViewerObject *objectp, S32 face, BOOL undoable)
{
	// check to see if object is already in list
	LLSelectNode *nodep = findSelectNode(objectp);

	// if not in list, add it
	if (!nodep)
	{
		nodep = new LLSelectNode(objectp, TRUE);
		mSelectedObjects.addNode(nodep);
	}
	else
	{
		// make this a full-fledged selection
		nodep->setTransient(FALSE);
		// Move it to the front of the list
		mSelectedObjects.removeNode(nodep);
		mSelectedObjects.addNode(nodep);
	}

	// Make sure the object is tagged as selected
	objectp->setSelected( TRUE );

	// And make sure we don't consider it as part of a family
	nodep->mIndividualSelection = TRUE;

	// Handle face selection
	if (objectp->getNumTEs() <= 0)
	{
		// object has no faces, so don't do anything
	}
	else if (face == SELECT_ALL_TES)
	{
		nodep->selectAllTEs(TRUE);
	}
	else if (0 <= face && face < SELECT_MAX_TES)
	{
		nodep->selectTE(face, TRUE);
	}
	else
	{
		llerrs << "LLSelectMgr::add face " << face << " out-of-range" << llendl;
		return;
	}

	saveSelectedObjectTransform(SELECT_ACTION_TYPE_PICK);
	updateSelectionCenter();
	dialog_refresh_all();
}


void LLSelectMgr::setHoverObject(LLViewerObject *objectp)
{
	// Always blitz hover list when setting
	mHoverObjects.deleteAllNodes();

	if (!objectp)
	{
		return;
	}

	// Can't select yourself
	if (objectp->mID == gAgentID)
	{
		return;
	}

	// Can't select land
	if (objectp->getPCode() == LLViewerObject::LL_VO_SURFACE_PATCH)
	{
		return;
	}

	// Collect all of the objects
	LLDynamicArray<LLViewerObject*> objects;
	objectp = objectp->getRootEdit();
	objectp->addThisAndNonJointChildren(objects);


	S32 count = objects.count();
	LLViewerObject* cur_objectp = NULL;
	LLSelectNode* nodep = NULL;
	for(S32 i = 0; i < count; i++)
	{
		cur_objectp = objects[i];
		nodep = new LLSelectNode(cur_objectp, FALSE);
		mHoverObjects.addNodeAtEnd(nodep);
	}

	requestObjectPropertiesFamily(objectp);
}

LLSelectNode *LLSelectMgr::getHoverNode()
{
	return getHoverObjects().getFirstRootNode();
}

void LLSelectMgr::highlightObjectOnly(LLViewerObject* objectp)
{
	if (!objectp)
	{
		return;
	}

	if (objectp->getPCode() != LL_PCODE_VOLUME)
	{
		return;
	}
	
	if ((gSavedSettings.getBOOL("SelectOwnedOnly") && !objectp->permYouOwner()) ||
		(gSavedSettings.getBOOL("SelectMovableOnly") && !objectp->permMove()))
	{
		// only select my own objects
		return;
	}

	mRectSelectedObjects.insert(objectp);
}

void LLSelectMgr::highlightObjectAndFamily(LLViewerObject* objectp)
{
	if (!objectp)
	{
		return;
	}

	LLViewerObject* root_obj = (LLViewerObject*)objectp->getRoot();

	highlightObjectOnly(root_obj);

	for(U32 i = 0; i < root_obj->mChildList.size(); i++)
	{
		highlightObjectOnly(root_obj->mChildList[i]);
	}
}

// Note that this ignores the "select owned only" flag
// It's also more efficient than calling the single-object version over and
// over.
void LLSelectMgr::highlightObjectAndFamily(const LLDynamicArray<LLViewerObject*>& list)
{
	S32 i;
	S32 count = list.count();

	for (i = 0; i < count; i++)
	{
		LLViewerObject* object = list.get(i);

		if (!object) continue;
		if (object->getPCode() != LL_PCODE_VOLUME) continue;

		LLViewerObject* root = (LLViewerObject*)object->getRoot();
		mRectSelectedObjects.insert(root);

		S32 j;
		S32 child_count = root->mChildList.size();
		for (j = 0; j < child_count; j++)
		{
			LLViewerObject* child = root->mChildList[j];
			mRectSelectedObjects.insert(child);
		}
	}
}

void LLSelectMgr::unhighlightObjectOnly(LLViewerObject* objectp)
{
	if (!objectp || (objectp->getPCode() != LL_PCODE_VOLUME))
	{
		return;
	}

	mRectSelectedObjects.erase(objectp);
}

void LLSelectMgr::unhighlightObjectAndFamily(LLViewerObject* objectp)
{
	if (!objectp)
	{
		return;
	}

	LLViewerObject* root_obj = (LLViewerObject*)objectp->getRoot();

	unhighlightObjectOnly(root_obj);

	for(U32 i = 0; i < root_obj->mChildList.size(); i++)
	{
		unhighlightObjectOnly(root_obj->mChildList[i]);
	}
}


void LLSelectMgr::unhighlightAll()
{
	mRectSelectedObjects.clear();
	mHighlightedObjects.deleteAllNodes();
}

void LLSelectMgr::selectHighlightedObjects()
{
	if (!mHighlightedObjects.getNumNodes())
	{
		return;
	}

	LLSelectNode *nodep;
	for (nodep = mHighlightedObjects.getFirstNode();
		nodep;
		nodep = mHighlightedObjects.getNextNode())
	{
		LLViewerObject* objectp = nodep->getObject();

		if (!canSelectObject(objectp))
		{
			continue;
		}

		// already selected
		if (objectp->isSelected())
		{
			continue;
		}

		LLSelectNode* new_nodep = new LLSelectNode(*nodep);
		mSelectedObjects.addNode(new_nodep);

		// flag this object as selected
		objectp->setSelected(TRUE);

		mSelectType = getSelectTypeForObject(objectp);

		// request properties on root objects
		if (objectp->isRootEdit())
		{
			requestObjectPropertiesFamily(objectp);
		}
	}

	// pack up messages to let sim know these objects are selected
	sendSelect();
	unhighlightAll();
	updateSelectionCenter();
	saveSelectedObjectTransform(SELECT_ACTION_TYPE_PICK);
	updatePointAt();

	if (getObjectCount())
	{
		gEditMenuHandler = this;
	}
}

void LLSelectMgr::deselectHighlightedObjects()
{
	BOOL select_linked_set = gSavedSettings.getBOOL("SelectLinkedSet");
	for (std::set<LLPointer<LLViewerObject> >::iterator iter = mRectSelectedObjects.begin();
		 iter != mRectSelectedObjects.end(); iter++)
	{
		LLViewerObject *objectp = *iter;
		if (!select_linked_set)
		{
			deselectObjectOnly(objectp);
		}
		else
		{
			LLViewerObject* root_object = (LLViewerObject*)objectp->getRoot();
			if (root_object->isSelected())
			{
				deselectObjectAndFamily(root_object);
			}
		}
	}

	unhighlightAll();
}

void LLSelectMgr::addGridObject(LLViewerObject* objectp)
{
	LLSelectNode* nodep = new LLSelectNode(objectp, FALSE);
	mGridObjects.addNodeAtEnd(nodep);
	for (U32 i = 0; i < objectp->mChildList.size(); i++)
	{
		nodep = new LLSelectNode(objectp->mChildList[i], FALSE);
		mGridObjects.addNodeAtEnd(nodep);
	}
}

void LLSelectMgr::clearGridObjects()
{
	mGridObjects.deleteAllNodes();
}

void LLSelectMgr::setGridMode(EGridMode mode)
{
	mGridMode = mode;
	gSavedSettings.setS32("GridMode", mode);
	updateSelectionCenter();
	mGridValid = FALSE;
}

void LLSelectMgr::getGrid(LLVector3& origin, LLQuaternion &rotation, LLVector3 &scale)
{
	LLSelectNode* grid_node = mGridObjects.getFirstNode();
	LLViewerObject* grid_object = mGridObjects.getFirstObject();
	//FIXME: get to work with multiple grid objects
	if (grid_node && grid_node->getObject()->isDead())
	{
		mGridObjects.removeNode(grid_node);
		grid_object = NULL;
	}

	if (mGridMode == GRID_MODE_LOCAL && gSelectMgr->getObjectCount())
	{
		LLBBox bbox = mSavedSelectionBBox;
		mGridOrigin = mSavedSelectionBBox.getCenterAgent();
		mGridRotation = mSavedSelectionBBox.getRotation();
		mGridScale = mSavedSelectionBBox.getExtentLocal() * 0.5f;
	}
	else if (mGridMode == GRID_MODE_REF_OBJECT && grid_object && grid_object->mDrawable.notNull())
	{
		mGridRotation = grid_object->getRenderRotation();
		LLVector3 first_grid_obj_pos = grid_object->getRenderPosition();

		LLVector3 min_extents(F32_MAX, F32_MAX, F32_MAX);
		LLVector3 max_extents(F32_MIN, F32_MIN, F32_MIN);
		BOOL grid_changed = FALSE;
		LLSelectNode* grid_nodep;
		for (grid_nodep = mGridObjects.getFirstNode();
			grid_nodep;
			grid_nodep = mGridObjects.getNextNode())
			{
				grid_object = grid_nodep->getObject();

				LLVector3 local_min_extents(F32_MAX, F32_MAX, F32_MAX);
				LLVector3 local_max_extents(F32_MIN, F32_MIN, F32_MIN);

				//FIXME: silhouette flag is insufficient as it gets cleared by view update
				if (!mGridValid || 
					grid_object->isChanged(LLXform::SILHOUETTE)
					|| (grid_object->getParent() && grid_object->getParent()->isChanged(LLXform::SILHOUETTE)))
				{
					getSilhouetteExtents(grid_nodep, mGridRotation, local_min_extents, local_max_extents);
					grid_changed = TRUE;
					LLVector3 object_offset = (grid_object->getRenderPosition() - first_grid_obj_pos) * ~mGridRotation;
					local_min_extents += object_offset;
					local_max_extents += object_offset;
				}
				min_extents.mV[VX] = llmin(min_extents.mV[VX], local_min_extents.mV[VX]);
				min_extents.mV[VY] = llmin(min_extents.mV[VY], local_min_extents.mV[VY]);
				min_extents.mV[VZ] = llmin(min_extents.mV[VZ], local_min_extents.mV[VZ]);
				max_extents.mV[VX] = llmax(max_extents.mV[VX], local_max_extents.mV[VX]);
				max_extents.mV[VY] = llmax(max_extents.mV[VY], local_max_extents.mV[VY]);
				max_extents.mV[VZ] = llmax(max_extents.mV[VZ], local_max_extents.mV[VZ]);
			}
		if (grid_changed)
		{
			mGridOrigin = lerp(min_extents, max_extents, 0.5f);
			mGridOrigin = mGridOrigin * ~mGridRotation;
			mGridOrigin += first_grid_obj_pos;
			mGridScale = (max_extents - min_extents) * 0.5f;
		}
	}
	else // GRID_MODE_WORLD or just plain default
	{
		LLViewerObject* first_object = gSelectMgr->getFirstRootObject();
		if (!first_object)
		{
			first_object = gSelectMgr->getFirstObject();
		}

		mGridOrigin.clearVec();
		mGridRotation.loadIdentity();

		mSelectType = getSelectTypeForObject( first_object );

		switch (mSelectType)
		{
		case SELECT_TYPE_ATTACHMENT:
			if (first_object)
			{
				// this means this object *has* to be an attachment
				LLXform* attachment_point_xform = first_object->getRootEdit()->mDrawable->mXform.getParent();
				mGridOrigin = attachment_point_xform->getWorldPosition();
				mGridRotation = attachment_point_xform->getWorldRotation();
				mGridScale = LLVector3(1.f, 1.f, 1.f) * gSavedSettings.getF32("GridResolution");
			}
			break;
		case SELECT_TYPE_HUD:
			// use HUD-scaled grid
			mGridScale = LLVector3(0.25f, 0.25f, 0.25f);
			break;
		case SELECT_TYPE_WORLD:
			mGridScale = LLVector3(1.f, 1.f, 1.f) * gSavedSettings.getF32("GridResolution");
			break;
		}
	}
	llassert(mGridOrigin.isFinite());

	origin = mGridOrigin;
	rotation = mGridRotation;
	scale = mGridScale;
	mGridValid = TRUE;
}



LLSelectNode* LLSelectMgr::findSelectNode(LLViewerObject *object)
{
	return mSelectedObjects.findNode(object);
}

//-----------------------------------------------------------------------------
// contains()
//-----------------------------------------------------------------------------
BOOL LLSelectMgr::contains(LLViewerObject* object)
{
	return mSelectedObjects.findNode(object) != NULL;
}


//-----------------------------------------------------------------------------
// contains()
//-----------------------------------------------------------------------------
BOOL LLSelectMgr::contains(LLViewerObject* object, S32 te)
{
	LLSelectNode *nodep;
	if (te == SELECT_ALL_TES)
	{
		// ...all faces
		for (nodep = mSelectedObjects.getFirstNode(); nodep; nodep = mSelectedObjects.getNextNode() )
		{
			if (nodep->getObject() == object)
			{
				BOOL all_selected = TRUE;
				for (S32 i = 0; i < SELECT_MAX_TES; i++)
				{
					all_selected = all_selected && nodep->isTESelected(i);
				}
				return all_selected;
			}
		}
		return FALSE;
	}
	else
	{
		// ...one face
		for (nodep = mSelectedObjects.getFirstNode(); nodep; nodep = mSelectedObjects.getNextNode() )
		{
			if (nodep->getObject() == object && nodep->isTESelected(te))
			{
				return TRUE;
			}
		}
		return FALSE;
	}
}

//-----------------------------------------------------------------------------
// remove() - an array of objects
//-----------------------------------------------------------------------------

void LLSelectMgr::remove(LLDynamicArray<LLViewerObject*>& objects)
{
	S32 count = objects.count();
	LLViewerObject *objectp = NULL;
	LLSelectNode *nodep = NULL;
	for(S32 i = 0; i < count; i++)
	{
		objectp = objects.get(i);
		for(nodep = mSelectedObjects.getFirstNode();
			nodep != NULL;
			nodep = mSelectedObjects.getNextNode())
		{
			if(nodep->getObject() == objectp)
			{
				objectp->setSelected(FALSE);
				mSelectedObjects.removeNode(nodep);
				break;
			}
		}
			}
	updateSelectionCenter();
	dialog_refresh_all();
}


//-----------------------------------------------------------------------------
// remove() - a single object
//-----------------------------------------------------------------------------
void LLSelectMgr::remove(LLViewerObject *objectp, S32 te, BOOL undoable)
{
	// check if object already in list
	// FIXME: can we just check isSelected()?
	LLSelectNode *nodep = findSelectNode(objectp);

	if (!nodep)
	{
		return;
	}


	// if face = all, remove object from list
	if (objectp->getNumTEs() <= 0)
	{
		// object doesn't have faces, so blow it away
		mSelectedObjects.removeNode(nodep);
		objectp->setSelected( FALSE );
	}
	else if (te == SELECT_ALL_TES)
	{
		mSelectedObjects.removeNode(nodep);
		objectp->setSelected( FALSE );
	}
	else if (0 <= te && te < SELECT_MAX_TES)
	{
		// ...valid face, check to see if it was on
		if (nodep->isTESelected(te))
		{
			nodep->selectTE(te, FALSE);
		}
		else
		{
			llerrs << "LLSelectMgr::remove - tried to remove TE " << te << " that wasn't selected" << llendl;
			return;
		}

		// ...check to see if this operation turned off all faces
		BOOL found = FALSE;
		for (S32 i = 0; i < nodep->getObject()->getNumTEs(); i++)
		{
			found = found || nodep->isTESelected(i);
		}

		// ...all faces now turned off, so remove
		if (!found)
		{
			mSelectedObjects.removeNode(nodep);
			objectp->setSelected( FALSE );

			// BUG: Doesn't update simulator that object is gone.
		}
	}
	else
	{
		// ...out of range face
		llerrs << "LLSelectMgr::remove - TE " << te << " out of range" << llendl;
	}

	updateSelectionCenter();
	dialog_refresh_all();
}


//-----------------------------------------------------------------------------
// removeAll()
//-----------------------------------------------------------------------------
void LLSelectMgr::removeAll()
{
	LLViewerObject *objectp;
	for (objectp = mSelectedObjects.getFirstObject(); objectp; objectp = mSelectedObjects.getNextObject() )
	{
		objectp->setSelected( FALSE );
	}

	mSelectedObjects.deleteAllNodes();

	updateSelectionCenter();
	dialog_refresh_all();
}

//-----------------------------------------------------------------------------
// promoteSelectionToRoot()
//-----------------------------------------------------------------------------
void LLSelectMgr::promoteSelectionToRoot()
{
	std::set<LLViewerObject*> selection_set;

	BOOL selection_changed = FALSE;

	LLSelectNode* nodep;
	LLViewerObject *objectp;
	for (nodep = mSelectedObjects.getFirstNode(); 
		 nodep; 
		 nodep = mSelectedObjects.getNextNode() )
	{
		if (nodep->mIndividualSelection)
		{
			selection_changed = TRUE;
		}

		objectp = nodep->getObject();
		LLViewerObject* parentp = objectp;
		while(parentp->getParent() && !(parentp->isRootEdit() || parentp->isJointChild()))
		{
			parentp = (LLViewerObject*)parentp->getParent();
		}
	
		selection_set.insert(parentp);
	}

	if (selection_changed)
	{
		deselectAll();

		std::set<LLViewerObject*>::iterator set_iter;
		for (set_iter = selection_set.begin(); set_iter != selection_set.end(); ++set_iter)
		{
			selectObjectAndFamily(*set_iter);
		}
	}
}

//-----------------------------------------------------------------------------
// demoteSelectionToIndividuals()
//-----------------------------------------------------------------------------
void LLSelectMgr::demoteSelectionToIndividuals()
{
	LLDynamicArray<LLViewerObject*> objects;

	for (LLViewerObject* root_objectp = mSelectedObjects.getFirstRootObject();
		root_objectp;
		root_objectp = mSelectedObjects.getNextRootObject())
	{
		root_objectp->addThisAndNonJointChildren(objects);
	}

	if (objects.getLength())
	{
		deselectAll();
		for(S32 i = 0; i < objects.count(); i++)
		{
			selectObjectOnly(objects[i]);
		}
	}
}

//-----------------------------------------------------------------------------
// getObjectCount()
//-----------------------------------------------------------------------------
S32 LLSelectMgr::getObjectCount()
{
	return mSelectedObjects.getNumNodes();
}


//-----------------------------------------------------------------------------
// getTECount()
//-----------------------------------------------------------------------------
S32 LLSelectMgr::getTECount()
{
	S32 count = 0;

	LLSelectNode* nodep;
	for (nodep = mSelectedObjects.getFirstNode(); nodep; nodep = mSelectedObjects.getNextNode() )
	{
		if (nodep->getObject())
		{
			S32 num_tes = nodep->getObject()->getNumTEs();
			for (S32 te = 0; te < num_tes; te++)
			{
				if (nodep->isTESelected(te))
				{
					count++;
				}
			}
		}
	}

	return count;
}

//-----------------------------------------------------------------------------
// getRootObjectCount()
//-----------------------------------------------------------------------------
S32 LLSelectMgr::getRootObjectCount()
{
	LLSelectNode *nodep;

	S32 count = 0;
	for(nodep = mSelectedObjects.getFirstRootNode(); nodep; nodep = mSelectedObjects.getNextRootNode())
	{
		++count;
	}
	return count;
}


//-----------------------------------------------------------------------------
// dump()
//-----------------------------------------------------------------------------
void LLSelectMgr::dump()
{
	llinfos << "Selection Manager: " << mSelectedObjects.getNumNodes() << " items" << llendl;

	llinfos << "TE mode " << mTEMode << llendl;

	S32 i = 0;

	LLViewerObject *objectp;
	for (objectp = mSelectedObjects.getFirstObject();
		 objectp;
		 objectp = mSelectedObjects.getNextObject())
	{
		llinfos << "Object " << i << " type " << LLPrimitive::pCodeToString(objectp->getPCode()) << llendl;
		llinfos << "  hasLSL " << objectp->flagScripted() << llendl;
		llinfos << "  hasTouch " << objectp->flagHandleTouch() << llendl;
		llinfos << "  hasMoney " << objectp->flagTakesMoney() << llendl;
		llinfos << "  getposition " << objectp->getPosition() << llendl;
		llinfos << "  getpositionAgent " << objectp->getPositionAgent() << llendl;
		llinfos << "  getpositionRegion " << objectp->getPositionRegion() << llendl;
		llinfos << "  getpositionGlobal " << objectp->getPositionGlobal() << llendl;
		LLDrawable* drawablep = objectp->mDrawable;
		llinfos << "  " << (drawablep&& drawablep->isVisible() ? "visible" : "invisible") << llendl;
		llinfos << "  " << (drawablep&& drawablep->isState(LLDrawable::FORCE_INVISIBLE) ? "force_invisible" : "") << llendl;
		i++;
	}

	// Face iterator
	S32 te;
	for (mSelectedObjects.getFirstTE(&objectp, &te);
		 objectp;
		 mSelectedObjects.getNextTE(&objectp, &te))
	{
		llinfos << "Object " << objectp << " te " << te << llendl;
	}

	llinfos << mHighlightedObjects.getNumNodes() << " objects currently highlighted." << llendl;

	llinfos << "Center global " << mSelectionCenterGlobal << llendl;
}

//-----------------------------------------------------------------------------
// cleanup()
//-----------------------------------------------------------------------------
void LLSelectMgr::cleanup()
{
	mSilhouetteImagep = NULL;
}


//---------------------------------------------------------------------------
// Manipulate properties of selected objects
//---------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// selectionSetImage()
//-----------------------------------------------------------------------------
//FIXME: re-arch texture applying out of lltooldraganddrop
void LLSelectMgr::selectionSetImage(const LLUUID& imageid)
{
	// First for (no copy) textures and multiple object selection
	LLViewerInventoryItem* item = gInventory.getItem(imageid);

	if(item 
		&& !item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID())
		&& (mSelectedObjects.getNumNodes() > 1) )
	{
		llwarns << "Attempted to apply no-copy texture to multiple objects"
				<< llendl;
		return;
	}

	LLViewerObject* objectp;
	S32 te;

	// Apply the texture to each side
	for (mSelectedObjects.getFirstTE(&objectp, &te); objectp; mSelectedObjects.getNextTE(&objectp, &te))
	{

		if (item)
		{
			LLToolDragAndDrop::dropTextureOneFace(objectp,te,item,LLToolDragAndDrop::SOURCE_AGENT,LLUUID::null);
			
			// HACK! HACK! ARG!
			// FIXME: Replace mSelectedObjects with a REAL container class!
			LLViewerObject* tmp_object;
			S32 tmp_te;
			mSelectedObjects.getCurrentTE(&tmp_object,&tmp_te);
			if ((tmp_object != objectp) || (tmp_te != te) )
			{
				//AAARG someone has moved our list around!
				mSelectedObjects.getFirstTE(&tmp_object, &tmp_te);
				while ((tmp_object != objectp) || (tmp_te != te))
				{
					mSelectedObjects.getNextTE(&tmp_object, &tmp_te);
				}
			}
		}
		else
		{
			// Texture picker defaults aren't inventory items
			// * Don't need to worry about permissions for them
			// * Can just apply the texture and be done with it.
			objectp->setTEImage(te, gImageList.getImage(imageid));
			objectp->sendTEUpdate();
		}
	}

	// 1 particle effect per object
	if (mSelectType != SELECT_TYPE_HUD)
	{
		for (objectp = mSelectedObjects.getFirstObject(); objectp; objectp = mSelectedObjects.getNextObject())
		{
			LLHUDEffectSpiral *effectp = (LLHUDEffectSpiral *)gHUDManager->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_BEAM, TRUE);
			effectp->setSourceObject(gAgent.getAvatarObject());     
			effectp->setTargetObject(objectp);                      
			effectp->setDuration(LL_HUD_DUR_SHORT);                 
			effectp->setColor(LLColor4U(gAgent.getEffectColor()));  
		}
	}
}

//-----------------------------------------------------------------------------
// selectionSetColor()
//-----------------------------------------------------------------------------
void LLSelectMgr::selectionSetColor(const LLColor4 &color)
{
	LLViewerObject* object;
	S32 te;
	for (mSelectedObjects.getFirstTE(&object, &te); object; mSelectedObjects.getNextTE(&object, &te) )
	{
		if (object->permModify())
		{
			// update viewer side color in anticipation of update from simulator
			object->setTEColor(te, color);
		}
	}

	for ( object = mSelectedObjects.getFirstObject(); object; object = mSelectedObjects.getNextObject() )
	{
		if (object->permModify())
		{
			object->sendTEUpdate();
		}
	}
}

//-----------------------------------------------------------------------------
// selectionSetColorOnly()
//-----------------------------------------------------------------------------
void LLSelectMgr::selectionSetColorOnly(const LLColor4 &color)
{
	LLViewerObject* object;
	LLColor4 new_color = color;
	S32 te;
	for (mSelectedObjects.getFirstTE(&object, &te); object; mSelectedObjects.getNextTE(&object, &te) )
	{
		if (object->permModify())
		{
			LLColor4 prev_color = object->getTE(te)->getColor();
			new_color.mV[VALPHA] = prev_color.mV[VALPHA];
			// update viewer side color in anticipation of update from simulator
			object->setTEColor(te, new_color);
		}
	}

	for ( object = mSelectedObjects.getFirstObject(); object; object = mSelectedObjects.getNextObject() )
	{
		if (object->permModify())
		{
			object->sendTEUpdate();
		}
	}
}

//-----------------------------------------------------------------------------
// selectionSetAlphaOnly()
//-----------------------------------------------------------------------------
void LLSelectMgr::selectionSetAlphaOnly(const F32 alpha)
{
	LLViewerObject* object;
	S32 te;
	for (mSelectedObjects.getFirstTE(&object, &te); object; mSelectedObjects.getNextTE(&object, &te) )
	{
		if (object->permModify())
		{
			LLColor4 prev_color = object->getTE(te)->getColor();
			prev_color.mV[VALPHA] = alpha;
			// update viewer side color in anticipation of update from simulator
			object->setTEColor(te, prev_color);
		}
	}

	for ( object = mSelectedObjects.getFirstObject(); object; object = mSelectedObjects.getNextObject() )
	{
		if (object->permModify())
		{
			object->sendTEUpdate();
		}
	}
}

void LLSelectMgr::selectionRevertColors()
{
	LLViewerObject* object;
	S32 te;

	for (mSelectedObjects.getFirstTE(&object, &te); object; mSelectedObjects.getNextTE(&object, &te) )
	{
		if (object->permModify())
		{
			LLSelectNode* nodep = mSelectedObjects.findNode(object);
			if (nodep && te < (S32)nodep->mSavedColors.size())
			{
				LLColor4 color = nodep->mSavedColors[te];
				// update viewer side color in anticipation of update from simulator
				object->setTEColor(te, color);
			}
		}
	}

	for ( object = mSelectedObjects.getFirstObject(); object; object = mSelectedObjects.getNextObject() )
	{
		if (object->permModify())
		{
			object->sendTEUpdate();
		}
	}
}

BOOL LLSelectMgr::selectionRevertTextures()
{
	LLViewerObject* object;
	S32 te;

	BOOL revert_successful = TRUE;
	for (mSelectedObjects.getFirstTE(&object, &te); object; mSelectedObjects.getNextTE(&object, &te) )
	{
		if (object->permModify())
		{
			LLSelectNode* nodep = mSelectedObjects.findNode(object);
			if (nodep && te < (S32)nodep->mSavedTextures.size())
			{
				LLUUID id = nodep->mSavedTextures[te];
				// update textures on viewer side
				if (id.isNull())
				{
					// this was probably a no-copy texture, leave image as-is
					revert_successful = FALSE;
				}
				else
				{
					object->setTEImage(te, gImageList.getImage(id));
				}
			}
		}
	}

	// propagate texture changes to server
	for ( object = mSelectedObjects.getFirstObject(); object; object = mSelectedObjects.getNextObject() )
	{
		if (object->permModify())
		{
			object->sendTEUpdate();
		}
	}

	return revert_successful;
}

void LLSelectMgr::selectionSetBumpmap(U8 bumpmap)
{
	LLViewerObject* object;
	S32 te;
	for (mSelectedObjects.getFirstTE(&object, &te); object; mSelectedObjects.getNextTE(&object, &te) )
	{
		if (object->permModify())
		{
			// update viewer side color in anticipation of update from simulator
			object->setTEBumpmap(te, bumpmap);
		}
	}

	for ( object = mSelectedObjects.getFirstObject(); object; object = mSelectedObjects.getNextObject() )
	{
		if (object->permModify())
		{
			object->sendTEUpdate();
		}
	}
}

void LLSelectMgr::selectionSetTexGen(U8 texgen)
{
	LLViewerObject* object;
	S32 te;
	for (mSelectedObjects.getFirstTE(&object, &te); object; mSelectedObjects.getNextTE(&object, &te) )
	{
		if (object->permModify())
		{
			// update viewer side color in anticipation of update from simulator
			object->setTETexGen(te, texgen);
		}
	}

	for ( object = mSelectedObjects.getFirstObject(); object; object = mSelectedObjects.getNextObject() )
	{
		if (object->permModify())
		{
			object->sendTEUpdate();
		}
	}
}


void LLSelectMgr::selectionSetShiny(U8 shiny)
{
	LLViewerObject* object;
	S32 te;
	for (mSelectedObjects.getFirstTE(&object, &te); object; mSelectedObjects.getNextTE(&object, &te) )
	{
		if (object->permModify())
		{
			// update viewer side color in anticipation of update from simulator
			object->setTEShiny(te, shiny);
		}
	}

	for ( object = mSelectedObjects.getFirstObject(); object; object = mSelectedObjects.getNextObject() )
	{
		if (object->permModify())
		{
			object->sendTEUpdate();
		}
	}
}

void LLSelectMgr::selectionSetFullbright(U8 fullbright)
{
	LLViewerObject* object;
	S32 te;
	for (mSelectedObjects.getFirstTE(&object, &te); object; mSelectedObjects.getNextTE(&object, &te) )
	{
		if (object->permModify())
		{
			// update viewer side color in anticipation of update from simulator
			object->setTEFullbright(te, fullbright);
		}
	}

	for ( object = mSelectedObjects.getFirstObject(); object; object = mSelectedObjects.getNextObject() )
	{
		if (object->permModify())
		{
			object->sendTEUpdate();
			if (fullbright)
			{
				U8 material = object->getMaterial();
				U8 mcode = material & LL_MCODE_MASK;
				if (mcode == LL_MCODE_LIGHT)
				{
					mcode = LL_MCODE_GLASS;
					material = (material & ~LL_MCODE_MASK) | mcode;
					object->setMaterial(material);
					object->sendMaterialUpdate();
				}
			}
		}
	}
}

void LLSelectMgr::selectionSetMediaTypeAndURL(U8 media_type, const std::string& media_url)
{
	LLViewerObject* object;
	S32 te;
	U8 media_flags = LLTextureEntry::MF_NONE;
	if (media_type == LLViewerObject::MEDIA_TYPE_WEB_PAGE)
	{
		media_flags = LLTextureEntry::MF_WEB_PAGE;
	}

	for (mSelectedObjects.getFirstTE(&object, &te); object; mSelectedObjects.getNextTE(&object, &te) )
	{
		if (object->permModify())
		{
			// update viewer side color in anticipation of update from simulator
			object->setTEMediaFlags(te, media_flags);
		}
	}

	for ( object = mSelectedObjects.getFirstObject(); object; object = mSelectedObjects.getNextObject() )
	{
		if (object->permModify())
		{
			// JAMESDEBUG TODO set object media type
			object->setMediaType(media_type);
			object->setMediaURL(media_url);

			object->sendTEUpdate();
		}
	}
}



//-----------------------------------------------------------------------------
// findObjectPermissions()
//-----------------------------------------------------------------------------
LLPermissions* LLSelectMgr::findObjectPermissions(const LLViewerObject* object)
{
	LLSelectNode* nodep;

	for (nodep = mSelectedObjects.getFirstNode(); nodep; nodep = mSelectedObjects.getNextNode() )
	{
		if((nodep->getObject() == object) && nodep->mValid)
		{
			return nodep->mPermissions;
		}
	}

	return NULL;
}


//-----------------------------------------------------------------------------
// selectionGetTexUUID()
//-----------------------------------------------------------------------------
BOOL LLSelectMgr::selectionGetTexUUID(LLUUID& id)
{
	LLViewerObject* first_objectp;
	S32 first_te;
	mSelectedObjects.getPrimaryTE(&first_objectp, &first_te);

	// nothing selected
	if (!first_objectp)
	{
		return FALSE;
	}
	
	LLViewerImage* first_imagep = first_objectp->getTEImage(first_te);
	
	if (!first_imagep)
	{
		return FALSE;
	}

	BOOL identical = TRUE;
	LLViewerObject *objectp;
	S32 te;
	for (mSelectedObjects.getFirstTE(&objectp, &te); objectp; mSelectedObjects.getNextTE(&objectp, &te) )
	{
		if (objectp->getTEImage(te) != first_imagep)
		{
			identical = FALSE;
			break;
		}
	}

	id = first_imagep->getID();
	return identical;
}

//-----------------------------------------------------------------------------
// selectionGetColor()
//-----------------------------------------------------------------------------
BOOL LLSelectMgr::selectionGetColor(LLColor4 &color)
{
	LLViewerObject* first_object;
	S32 first_te;
	mSelectedObjects.getPrimaryTE(&first_object, &first_te);

	// nothing selected
	if (!first_object)
	{
		return FALSE;
	}

	LLColor4 first_color;
	if (!first_object->getTE(first_te))
	{
		return FALSE;
	}
	else
	{
		first_color = first_object->getTE(first_te)->getColor();
	}

	BOOL identical = TRUE;
	LLViewerObject* object;
	S32 te;
	for (mSelectedObjects.getFirstTE(&object, &te); object; mSelectedObjects.getNextTE(&object, &te))
	{
		if (!object->getTE(te) || (object->getTE(te)->getColor() != first_color))
		{
			identical = FALSE;
			break;
		}
	}

	color = first_color;
	return identical;
}


//-----------------------------------------------------------------------------
// selectionGetBumpmap()
//-----------------------------------------------------------------------------
BOOL LLSelectMgr::selectionGetBumpmap(U8 *bumpmap)
{
	LLViewerObject* first_object;
	S32 first_te;
	mSelectedObjects.getPrimaryTE(&first_object, &first_te);

	// nothing selected
	if (!first_object)
	{
		return FALSE;
	}

	U8 first_value;
	if (!first_object->getTE(first_te))
	{
		return FALSE;
	}
	else
	{
		first_value = first_object->getTE(first_te)->getBumpmap();
	}

	BOOL identical = TRUE;
	LLViewerObject* object;
	S32 te;
	for (mSelectedObjects.getFirstTE(&object, &te); object; mSelectedObjects.getNextTE(&object, &te))
	{
		if (!object->getTE(te) || (object->getTE(te)->getBumpmap() != first_value))
		{
			identical = FALSE;
			break;
		}
	}

	*bumpmap = first_value;
	return identical;
}

//-----------------------------------------------------------------------------
// selectionGetShiny()
//-----------------------------------------------------------------------------
BOOL LLSelectMgr::selectionGetShiny(U8 *shiny)
{
	LLViewerObject* first_object;
	S32 first_te;
	mSelectedObjects.getPrimaryTE(&first_object, &first_te);

	// nothing selected
	if (!first_object)
	{
		return FALSE;
	}

	U8 first_value;
	if (!first_object->getTE(first_te))
	{
		return FALSE;
	}
	else
	{
		first_value = first_object->getTE(first_te)->getShiny();
	}

	BOOL identical = TRUE;
	LLViewerObject* object;
	S32 te;
	for (mSelectedObjects.getFirstTE(&object, &te); object; mSelectedObjects.getNextTE(&object, &te))
	{
		if (!object->getTE(te) || (object->getTE(te)->getShiny() != first_value))
		{
			identical = FALSE;
			break;
		}
	}

	*shiny = first_value;
	return identical;
}

//-----------------------------------------------------------------------------
// selectionGetFullbright()
//-----------------------------------------------------------------------------
BOOL LLSelectMgr::selectionGetFullbright(U8 *fullbright)
{
	LLViewerObject* first_object;
	S32 first_te;
	mSelectedObjects.getPrimaryTE(&first_object, &first_te);

	// nothing selected
	if (!first_object)
	{
		return FALSE;
	}

	U8 first_value;
	if (!first_object->getTE(first_te))
	{
		return FALSE;
	}
	else
	{
		first_value = first_object->getTE(first_te)->getFullbright();
	}

	BOOL identical = TRUE;
	LLViewerObject* object;
	S32 te;
	for (mSelectedObjects.getFirstTE(&object, &te); object; mSelectedObjects.getNextTE(&object, &te))
	{
		if (!object->getTE(te) || (object->getTE(te)->getFullbright() != first_value))
		{
			identical = FALSE;
			break;
		}
	}

	*fullbright = first_value;
	return identical;
}

// JAMESDEBUG TODO make this return mediatype off viewer object
bool LLSelectMgr::selectionGetMediaType(U8 *media_type)
{
	LLViewerObject* first_object;
	S32 first_te;
	mSelectedObjects.getPrimaryTE(&first_object, &first_te);

	// nothing selected
	if (!first_object)
	{
		return false;
	}

	U8 first_value;
	if (!first_object->getTE(first_te))
	{
		return false;
	}
	else
	{
		first_value = first_object->getTE(first_te)->getMediaFlags();
	}

	bool identical = true;
	LLViewerObject* object;
	S32 te;
	for (mSelectedObjects.getFirstTE(&object, &te); object; mSelectedObjects.getNextTE(&object, &te))
	{
		if (!object->getTE(te) || (object->getTE(te)->getMediaFlags() != first_value))
		{
			identical = false;
			break;
		}
	}

	*media_type = first_value;
	return identical;
}



//-----------------------------------------------------------------------------
// selectionSetMaterial()
//-----------------------------------------------------------------------------
void LLSelectMgr::selectionSetMaterial(U8 material)
{
	LLViewerObject* object;
	for (object = mSelectedObjects.getFirstObject(); object != NULL; object = mSelectedObjects.getNextObject() )
	{
		if (object->permModify())
		{
			U8 cur_material = object->getMaterial();
			material |= (cur_material & ~LL_MCODE_MASK);
			object->setMaterial(material);
			object->sendMaterialUpdate();
		}
	}
}

// True if all selected objects have this PCode
BOOL LLSelectMgr::selectionAllPCode(LLPCode code)
{
	LLViewerObject *object;
	for ( object = mSelectedObjects.getFirstObject(); object; object = mSelectedObjects.getNextObject() )
	{
		if (object->getPCode() != code)
		{
			return FALSE;
		}
	}
	return TRUE;
}

//-----------------------------------------------------------------------------
// selectionGetMaterial()
//-----------------------------------------------------------------------------
BOOL LLSelectMgr::selectionGetMaterial(U8 *out_material)
{
	LLViewerObject *object = mSelectedObjects.getFirstObject();
	if (!object) return FALSE;

	U8 material = object->getMaterial();

	BOOL identical = TRUE;
	for ( object = mSelectedObjects.getFirstObject(); object; object = mSelectedObjects.getNextObject() )
	{
		if ( material != object->getMaterial())
		{
			identical = FALSE;
			break;
		}
	}

	*out_material = material;
	return identical;
}

BOOL LLSelectMgr::selectionGetClickAction(U8 *out_action)
{
	LLViewerObject *object = mSelectedObjects.getFirstObject();
	if (!object) return FALSE;

	U8 action = object->getClickAction();

	BOOL identical = TRUE;
	for ( object = mSelectedObjects.getFirstObject(); object; object = mSelectedObjects.getNextObject() )
	{
		if ( action != object->getClickAction())
		{
			identical = FALSE;
			break;
		}
	}

	*out_action = action;
	return identical;
}

void LLSelectMgr::selectionSetClickAction(U8 action)
{
	LLViewerObject* object = NULL;
	for ( object = mSelectedObjects.getFirstObject(); object; object = mSelectedObjects.getNextObject() )
	{
		object->setClickAction(action);
	}
	sendListToRegions(
		"ObjectClickAction",
		packAgentAndSessionID,
		packObjectClickAction, 
		&action,
		SEND_INDIVIDUALS);
}


//-----------------------------------------------------------------------------
// godlike requests
//-----------------------------------------------------------------------------

typedef std::pair<const LLString, const LLString> godlike_request_t;
void LLSelectMgr::sendGodlikeRequest(const LLString& request, const LLString& param)
{
	// If the agent is neither godlike nor an estate owner, the server
	// will reject the request.
	LLString message_type;
	if (gAgent.isGodlike())
	{
		message_type = "GodlikeMessage";
	}
	else
	{
		message_type = "EstateOwnerMessage";
	}

	godlike_request_t data(request, param);
	if(!getRootObjectCount())
	{
		LLMessageSystem* msg = gMessageSystem;
		msg->newMessage(message_type.c_str());
		LLSelectMgr::packGodlikeHead(&data);
		gAgent.sendReliableMessage();
	}
	else
	{
		sendListToRegions(message_type, packGodlikeHead, packObjectIDAsParam, &data, SEND_ONLY_ROOTS);
	}
}

void LLSelectMgr::packGodlikeHead(void* user_data)
{
	LLMessageSystem* msg = gMessageSystem;
	msg->nextBlockFast(_PREHASH_AgentData);
	msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
	msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
	godlike_request_t* data = (godlike_request_t*)user_data;
	msg->nextBlock("MethodData");
	msg->addString("Method", data->first.c_str());
	msg->addUUID("Invoice", LLUUID::null);

	// The parameters used to be restricted to either string or
	// integer. This mimics that behavior under the new 'string-only'
	// parameter list by not packing a string if there wasn't one
	// specified. The object ids will be packed in the
	// packObjectIDAsParam() method.
	if(data->second.size() > 0)
	{
		msg->nextBlock("ParamList");
		msg->addString("Parameter", data->second);
	}
}

// static
void LLSelectMgr::packObjectIDAsParam(LLSelectNode* node, void *)
{
	char buf [MAX_STRING];
	sprintf(buf, "%u", node->getObject()->getLocalID());
	gMessageSystem->nextBlock("ParamList");
	gMessageSystem->addString("Parameter", buf);
}

//-----------------------------------------------------------------------------
// Rotation options
//-----------------------------------------------------------------------------
void LLSelectMgr::selectionResetRotation()
{
	LLQuaternion identity(0.f, 0.f, 0.f, 1.f);

	LLViewerObject* object;
	for (object = mSelectedObjects.getFirstRootObject(); object; object = mSelectedObjects.getNextRootObject() )
	{
		object->setRotation(identity);
		if (object->mDrawable.notNull())
		{
			gPipeline.markMoved(object->mDrawable, TRUE);
		}
		object->sendRotationUpdate();
	}
}

void LLSelectMgr::selectionRotateAroundZ(F32 degrees)
{
	LLQuaternion rot( degrees * DEG_TO_RAD, LLVector3(0,0,1) );

	LLViewerObject* object;
	for (object = mSelectedObjects.getFirstRootObject(); object; object = mSelectedObjects.getNextRootObject() )
	{
		object->setRotation( object->getRotationEdit() * rot );
		if (object->mDrawable.notNull())
		{
			gPipeline.markMoved(object->mDrawable, TRUE);
		}
		object->sendRotationUpdate();
	}
}


//-----------------------------------------------------------------------------
// selectionTexScaleAutofit()
//-----------------------------------------------------------------------------
void LLSelectMgr::selectionTexScaleAutofit(F32 repeats_per_meter)
{
	LLViewerObject* object;
	S32 te;
	for (mSelectedObjects.getFirstTE(&object, &te); object; mSelectedObjects.getNextTE(&object, &te))
	{
		if (!object->permModify())
		{
			continue;
		}

		if (object->getNumTEs() == 0)
		{
			continue;
		}

		// Compute S,T to axis mapping
		U32 s_axis, t_axis;
		if (!getTESTAxes(object, te, &s_axis, &t_axis))
		{
			continue;
		}

		F32 new_s = object->getScale().mV[s_axis] * repeats_per_meter;
		F32 new_t = object->getScale().mV[t_axis] * repeats_per_meter;

		object->setTEScale(te, new_s, new_t);
	}

	for (object = mSelectedObjects.getFirstObject(); object; object = mSelectedObjects.getNextObject())
	{
		if (object->permModify())
		{
			object->sendTEUpdate();
		}
	}
}


// BUG: Only works for boxes.
// Face numbering for flex boxes as of 1.14.2002
//-----------------------------------------------------------------------------
// getFaceSTAxes()
//-----------------------------------------------------------------------------
BOOL LLSelectMgr::getTESTAxes(const LLViewerObject* object, const U8 face, U32* s_axis, U32* t_axis)
{
	if (face == 0)
	{
		*s_axis = VX; *t_axis = VY;
		return TRUE;
	}
	else if (face == 1)
	{
		*s_axis = VX; *t_axis = VZ;
		return TRUE;
	}
	else if (face == 2)
	{
		*s_axis = VY; *t_axis = VZ;
		return TRUE;
	}
	else if (face == 3)
	{
		*s_axis = VX; *t_axis = VZ;
		return TRUE;
	}
	else if (face == 4)
	{
		*s_axis = VY; *t_axis = VZ;
		return TRUE;
	}
	else if (face == 5)
	{
		*s_axis = VX; *t_axis = VY;
		return TRUE;
	}
	else
	{
		// unknown face
		return FALSE;
	}
}

// Called at the end of a scale operation, this adjusts the textures to attempt to
// maintain a constant repeats per meter.
// BUG: Only works for flex boxes.
//-----------------------------------------------------------------------------
// adjustTexturesByScale()
//-----------------------------------------------------------------------------
void LLSelectMgr::adjustTexturesByScale(BOOL send_to_sim, BOOL stretch)
{
	LLViewerObject* object;
	LLSelectNode* selectNode;

	BOOL send = FALSE;
	
	for (selectNode = mSelectedObjects.getFirstNode(); selectNode; selectNode = mSelectedObjects.getNextNode())
	{
		object = selectNode->getObject();
		if (!object->permModify())
		{
			continue;
		}

		if (object->getNumTEs() == 0)
		{
			continue;
		}

		for (U8 te_num = 0; te_num < object->getNumTEs(); te_num++)
		{
			const LLTextureEntry* tep = object->getTE(te_num);

			BOOL planar = tep->getTexGen() == LLTextureEntry::TEX_GEN_PLANAR;
			if (planar == stretch)
			{
				// Figure out how S,T changed with scale operation
				U32 s_axis, t_axis;
				if (!getTESTAxes(object, te_num, &s_axis, &t_axis)) continue;

				LLVector3 scale_ratio = selectNode->mTextureScaleRatios[te_num]; 
				LLVector3 object_scale = object->getScale();
				
				// Apply new scale to face
				if (planar)
				{
					object->setTEScale(te_num, 1.f/object_scale.mV[s_axis]*scale_ratio.mV[s_axis],
										1.f/object_scale.mV[t_axis]*scale_ratio.mV[t_axis]);
				}
				else
				{
					object->setTEScale(te_num, scale_ratio.mV[s_axis]*object_scale.mV[s_axis],
											scale_ratio.mV[t_axis]*object_scale.mV[t_axis]);
				}
				send = send_to_sim;
			}
		}

		if (send)
		{
			object->sendTEUpdate();
		}
	}
}


//-----------------------------------------------------------------------------
// selectionResetTexInfo()
//-----------------------------------------------------------------------------
void LLSelectMgr::selectionResetTexInfo(S32 selected_face)
{
	S32 start_face, end_face;

	LLViewerObject* object;
	for (object = mSelectedObjects.getFirstObject(); object; object = mSelectedObjects.getNextObject())
	{
		if (!object->permModify())
		{
			continue;
		}
		if (object->getNumTEs() == 0)
		{
			continue;
		}

		if (selected_face == -1)
		{
			start_face = 0;
			end_face = object->getNumTEs() - 1;
		}
		else
		{
			start_face = selected_face;
			end_face = selected_face;
		}

		for (S32 face = start_face; face <= end_face; face++)
		{
			// Actually, each object should reset to its appropriate value.
			object->setTEScale(face, 1.f, 1.f);
			object->setTEOffset(face, 0.f, 0.f);
			object->setTERotation(face, 0.f);
		}

		object->sendTEUpdate();
	}
}

//-----------------------------------------------------------------------------
// getFirstEditableObject()
//-----------------------------------------------------------------------------
LLViewerObject* LLSelectMgr::getFirstEditableObject(BOOL get_root)
{
	LLViewerObject* object = NULL;
	for(LLViewerObject* cur = mSelectedObjects.getFirstObject(); cur; cur = mSelectedObjects.getNextObject())
	{
		if( cur->permModify() )
		{
			object = cur;
			break;
		}
	}	

	if (get_root && object)
	{
		LLViewerObject *parent;
		while ((parent = (LLViewerObject*)object->getParent()))
		{
			if (parent->isSelected())
			{
				object = parent;
			}
			else
			{
				break;
			}
		}
	}

	return object;
}

//-----------------------------------------------------------------------------
// getFirstMoveableObject()
//-----------------------------------------------------------------------------
LLViewerObject* LLSelectMgr::getFirstMoveableObject(BOOL get_root)
{
	LLViewerObject* object = NULL;
	for(LLViewerObject* cur = mSelectedObjects.getFirstObject(); cur; cur = mSelectedObjects.getNextObject())
	{
		if( cur->permMove() )
		{
			object = cur;
			break;
		}
	}	

	if (get_root && object && !object->isJointChild())
	{
		LLViewerObject *parent;
		while ((parent = (LLViewerObject*)object->getParent()))
		{
			if (parent->isSelected())
			{
				object = parent;
			}
			else
			{
				break;
			}
		}
	}

	return object;
}

//-----------------------------------------------------------------------------
// getFirstEditableNode()
//-----------------------------------------------------------------------------
LLSelectNode* LLSelectMgr::getFirstEditableNode(BOOL get_root)
{
	LLSelectNode* selectNode = NULL;

	if (get_root)
	{
		for(selectNode = mSelectedObjects.getFirstRootNode(); selectNode; selectNode = mSelectedObjects.getNextRootNode())
		{
			if( selectNode->getObject()->permModify() )
			{
				return selectNode;
				break;
			}
		}
	}
	for(selectNode = mSelectedObjects.getFirstNode(); selectNode; selectNode = mSelectedObjects.getNextNode())
	{
		if( selectNode->getObject()->permModify() )
		{
			return selectNode;
			break;
		}
	}
	
	return NULL;
}

//-----------------------------------------------------------------------------
// getFirstMoveableNode()
//-----------------------------------------------------------------------------
LLSelectNode* LLSelectMgr::getFirstMoveableNode(BOOL get_root)
{
	LLSelectNode* selectNode = NULL;

	if (get_root)
	{
		for(selectNode = mSelectedObjects.getFirstRootNode(); selectNode; selectNode = mSelectedObjects.getNextRootNode())
		{
			if( selectNode->getObject()->permMove() )
			{
				return selectNode;
				break;
			}
		}
	}
	for(selectNode = mSelectedObjects.getFirstNode(); selectNode; selectNode = mSelectedObjects.getNextNode())
	{
		if( selectNode->getObject()->permMove() )
		{
			return selectNode;
			break;
		}
	}
	
	return NULL;
}

//-----------------------------------------------------------------------------
// getFirstDeleteableObject()
//-----------------------------------------------------------------------------
LLViewerObject* LLSelectMgr::getFirstDeleteableObject(BOOL get_root)
{
	//RN: don't currently support deletion of child objects, as that requires separating them first
	// then derezzing to trash
	get_root = TRUE;

	LLViewerObject* object = NULL;
	if (get_root)
	{
		for(LLViewerObject* current = getFirstRootObject();
			current != NULL;
			current = getNextRootObject())
		{
			// you can delete an object if permissions allow it, you are
			// the owner, you are an officer in the group that owns the
			// object, or you are not the owner but it is on land you own
			// or land owned by your group. (whew!)
			if(   (current->permModify()) 
			|| (current->permYouOwner())
			|| (!current->permAnyOwner())			// public
			|| (current->isOverAgentOwnedLand())
			|| (current->isOverGroupOwnedLand())
			)
			{

				if( !current->isAttachment() )
				{
					object = current;
					break;
				}
			}
		}	
	}
	else
	{
		for(LLViewerObject* current = getFirstObject();
			current != NULL;
			current = getNextObject())
		{
			// you can delete an object if permissions allow it, you are
			// the owner, you are an officer in the group that owns the
			// object, or you are not the owner but it is on land you own
			// or land owned by your group. (whew!)
			if(   (current->permModify()) 
			|| (current->permYouOwner())
			|| (!current->permAnyOwner())			// public
			|| (current->isOverAgentOwnedLand())
			|| (current->isOverGroupOwnedLand())
			)
			{
				if( !current->isAttachment() )
				{
					object = current;
					break;
				}
			}
		}	
	}

	return object;
}

//-----------------------------------------------------------------------------
// getFirstCopyableObject()
//-----------------------------------------------------------------------------
LLViewerObject* LLSelectMgr::getFirstCopyableObject(BOOL get_root)
{
	LLViewerObject* object = NULL;
	for(LLViewerObject* cur = mSelectedObjects.getFirstObject(); cur; cur = mSelectedObjects.getNextObject())
	{
		if( cur->permCopy() && !cur->isAttachment())
		{
			object = cur;
			break;
		}
	}	

	if (get_root && object)
	{
		LLViewerObject *parent;
		while ((parent = (LLViewerObject*)object->getParent()))
		{
			if (parent->isSelected())
			{
				object = parent;
			}
			else
			{
				break;
			}
		}
	}

	return object;
}

//-----------------------------------------------------------------------------
// areMultpleEditableObjectsSelected()
//-----------------------------------------------------------------------------
BOOL LLSelectMgr::areMultpleEditableObjectsSelected()
{
	S32 count = 0;
	for( LLViewerObject* cur = mSelectedObjects.getFirstObject(); cur; cur = mSelectedObjects.getNextObject() )
	{
		if( cur->permModify() )
		{
			count++;
			if( count > 1 )
			{
				return TRUE;
			}
		}
	}
	return FALSE;
}

//-----------------------------------------------------------------------------
// selectGetAllRootsValid()
// Returns true if the viewer has information on all selected objects
//-----------------------------------------------------------------------------
BOOL LLSelectMgr::selectGetAllRootsValid()
{
	for( LLSelectNode* node = getFirstRootNode(); node; node = getNextRootNode() )
	{
	
		if( !node->mValid )
		{
			return FALSE;
		}

		if( !node->getObject() )
		{
			return FALSE;
		}
	}
	return TRUE;
}


//-----------------------------------------------------------------------------
// selectGetAllValid()
// Returns true if the viewer has information on all selected objects
//-----------------------------------------------------------------------------
BOOL LLSelectMgr::selectGetAllValid()
{
	for( LLSelectNode* node = getFirstNode(); node; node = getNextNode() )
	{
	
		if( !node->mValid )
		{
			return FALSE;
		}

		if( !node->getObject() )
		{
			return FALSE;
		}
	}
	return TRUE;
}


//-----------------------------------------------------------------------------
// selectGetModify() - return true if current agent can modify all
// selected objects.
//-----------------------------------------------------------------------------
BOOL LLSelectMgr::selectGetModify()
{
	for( LLSelectNode* node = getFirstNode(); node; node = getNextNode() )
	{
		if( !node->mValid )
		{
			return FALSE;
		}
		LLViewerObject* object = node->getObject();
		if( !object || !object->permModify() )
		{
			return FALSE;
		}
	}

	return TRUE;
}

//-----------------------------------------------------------------------------
// selectGetRootsModify() - return true if current agent can modify all
// selected root objects.
//-----------------------------------------------------------------------------
BOOL LLSelectMgr::selectGetRootsModify()
{
	for( LLSelectNode* node = getFirstRootNode(); node; node = getNextRootNode() )
	{
		if( !node->mValid )
		{
			return FALSE;
		}
		LLViewerObject* object = node->getObject();
		if( !object || !object->permModify() )
		{
			return FALSE;
		}
	}

	return TRUE;
}


//-----------------------------------------------------------------------------
// selectGetRootsTransfer() - return true if current agent can transfer all
// selected root objects.
//-----------------------------------------------------------------------------
BOOL LLSelectMgr::selectGetRootsTransfer()
{
	for(LLSelectNode* node = getFirstRootNode(); node; node = getNextRootNode())
	{
		if(!node->mValid)
		{
			return FALSE;
		}
		LLViewerObject* object = node->getObject();
		if(!object || !object->permTransfer())
		{
			return FALSE;
		}
	}
	return TRUE;
}

//-----------------------------------------------------------------------------
// selectGetRootsCopy() - return true if current agent can copy all
// selected root objects.
//-----------------------------------------------------------------------------
BOOL LLSelectMgr::selectGetRootsCopy()
{
	for(LLSelectNode* node = getFirstRootNode(); node; node = getNextRootNode())
	{
		if(!node->mValid)
		{
			return FALSE;
		}
		LLViewerObject* object = node->getObject();
		if(!object || !object->permCopy())
		{
			return FALSE;
		}
	}
	return TRUE;
}

//-----------------------------------------------------------------------------
// selectGetCreator()
// Creator information only applies to root objects.
//-----------------------------------------------------------------------------
BOOL LLSelectMgr::selectGetCreator(LLUUID& id, LLString& name)
{
	LLSelectNode* node = getFirstRootNode();
	if(!node) node = getFirstNode();
	if(!node) return FALSE;
	if(!node->mValid) return FALSE;
	LLViewerObject* obj = node->getObject();
	if(!obj) return FALSE;
	if(!(obj->isRoot() || obj->isJointChild())) return FALSE;

	id = node->mPermissions->getCreator();

	BOOL identical = TRUE;
	for ( node = getNextRootNode(); node; node = getNextRootNode() )
	{
		if (!node->mValid)
		{
			identical = FALSE;
			break;
		}

		if ( !(id == node->mPermissions->getCreator() ) )
		{
			identical = FALSE;
			break;
		}
	}

	if (identical)
	{
		char firstname[DB_FIRST_NAME_BUF_SIZE];
		char lastname[DB_LAST_NAME_BUF_SIZE];
		gCacheName->getName(id, firstname, lastname);
		name.assign( firstname );
		name.append( " " );
		name.append( lastname );
	}
	else
	{
		name.assign( "(multiple)" );
	}

	return identical;
}


//-----------------------------------------------------------------------------
// selectGetOwner()
// Owner information only applies to roots.
//-----------------------------------------------------------------------------
BOOL LLSelectMgr::selectGetOwner(LLUUID& id, LLString& name)
{
	LLSelectNode* node = getFirstRootNode();
	if(!node) node = getFirstNode();
	if(!node) return FALSE;
	if(!node->mValid) return FALSE;
	LLViewerObject* obj = node->getObject();
	if(!obj) return FALSE;
	if(!(obj->isRoot() || obj->isJointChild())) return FALSE;

	BOOL group_owner = FALSE;
	id.setNull();
	node->mPermissions->getOwnership(id, group_owner);

	BOOL identical = TRUE;
	for ( node = getNextRootNode(); node; node = getNextRootNode() )
	{
		if (!node->mValid)
		{
			identical = FALSE;
			break;
		}

		LLUUID owner_id;
		BOOL is_group_owned = FALSE;
		if (!(node->mPermissions->getOwnership(owner_id, is_group_owned))
			|| owner_id != id )
		{
			identical = FALSE;
			break;
		}
	}

	BOOL public_owner = (id.isNull() && !group_owner);

	if (identical)
	{
		if (group_owner)
		{
			name.assign( "(Group Owned)");
		}
		else if(!public_owner)
		{
			char firstname[DB_FIRST_NAME_BUF_SIZE];
			char lastname[DB_LAST_NAME_BUF_SIZE];
			gCacheName->getName(id, firstname, lastname);
			name.assign( firstname );
			name.append( " " );
			name.append( lastname );
		}
		else
		{
			name.assign("Public");
		}
	}
	else
	{
		name.assign( "(multiple)" );
	}

	return identical;
}


//-----------------------------------------------------------------------------
// selectGetLastOwner()
// Owner information only applies to roots.
//-----------------------------------------------------------------------------
BOOL LLSelectMgr::selectGetLastOwner(LLUUID& id, LLString& name)
{
	LLSelectNode* node = getFirstRootNode();
	if(!node) node = getFirstNode();
	if(!node) return FALSE;
	if(!node->mValid) return FALSE;
	LLViewerObject* obj = node->getObject();
	if(!obj) return FALSE;
	if(!(obj->isRoot() || obj->isJointChild())) return FALSE;

	id = node->mPermissions->getLastOwner();

	BOOL identical = TRUE;
	for ( node = getNextRootNode(); node; node = getNextRootNode() )
	{
		if (!node->mValid)
		{
			identical = FALSE;
			break;
		}

		if ( !(id == node->mPermissions->getLastOwner() ) )
		{
			identical = FALSE;
			break;
		}
	}

	BOOL public_owner = (id.isNull());

	if (identical)
	{
		if(!public_owner)
		{
			char firstname[DB_FIRST_NAME_BUF_SIZE];
			char lastname[DB_LAST_NAME_BUF_SIZE];
			gCacheName->getName(id, firstname, lastname);
			name.assign( firstname );
			name.append( " " );
			name.append( lastname );
		}
		else
		{
			name.assign("Public or Group");
		}
	}
	else
	{
		name.assign( "" );
	}

	return identical;
}


//-----------------------------------------------------------------------------
// selectGetGroup()
// Group information only applies to roots.
//-----------------------------------------------------------------------------
BOOL LLSelectMgr::selectGetGroup(LLUUID& id)
{
	LLSelectNode* node = getFirstRootNode();
	if(!node) node = getFirstNode();
	if(!node) return FALSE;
	if(!node->mValid) return FALSE;
	LLViewerObject* obj = node->getObject();
	if(!obj) return FALSE;
	if(!(obj->isRoot() || obj->isJointChild())) return FALSE;

	id = node->mPermissions->getGroup();

	BOOL identical = TRUE;
	for ( node = getNextRootNode(); node; node = getNextRootNode() )
	{
		if (!node->mValid)
		{
			identical = FALSE;
			break;
		}

		if ( !(id == node->mPermissions->getGroup() ) )
		{
			identical = FALSE;
			break;
		}
	}

	return identical;
}

//-----------------------------------------------------------------------------
// selectIsGroupOwned()
// Only operates on root nodes.  
// Returns TRUE if all have valid data and they are all group owned.
//-----------------------------------------------------------------------------
BOOL LLSelectMgr::selectIsGroupOwned()
{
	LLSelectNode* node = getFirstRootNode();
	if(!node) node = getFirstNode();
	if(!node) return FALSE;
	if(!node->mValid) return FALSE;
	LLViewerObject* obj = node->getObject();
	if(!obj) return FALSE;
	if(!(obj->isRoot() || obj->isJointChild())) return FALSE;

	BOOL is_group_owned = node->mPermissions->isGroupOwned();

	if(is_group_owned)
	{
		for ( node = getNextRootNode(); node; node = getNextRootNode() )
		{
			if (!node->mValid)
			{
				is_group_owned = FALSE;
				break;
			}

			if ( !( node->mPermissions->isGroupOwned() ) )
			{
				is_group_owned = FALSE;
				break;
			}
		}
	}
	return is_group_owned;
}

//-----------------------------------------------------------------------------
// selectGetPerm()
// Only operates on root nodes.
// Returns TRUE if all have valid data.
// mask_on has bits set to true where all permissions are true
// mask_off has bits set to true where all permissions are false
// if a bit is off both in mask_on and mask_off, the values differ within
// the selection.
//-----------------------------------------------------------------------------
BOOL LLSelectMgr::selectGetPerm(U8 which_perm, U32* mask_on, U32* mask_off)
{
	LLSelectNode* node = getFirstRootNode();
	if (!node) return FALSE;
	if (!node->mValid)	return FALSE;

	U32 mask;
	U32 mask_and	= 0xffffffff;
	U32 mask_or		= 0x00000000;
	BOOL all_valid	= TRUE;

	for ( node = getFirstRootNode(); node; node = getNextRootNode() )
	{
		if (!node->mValid)
		{
			all_valid = FALSE;
			break;
		}

		switch( which_perm )
		{
		case PERM_BASE:
			mask = node->mPermissions->getMaskBase();
			break;
		case PERM_OWNER:
			mask = node->mPermissions->getMaskOwner();
			break;
		case PERM_GROUP:
			mask = node->mPermissions->getMaskGroup();
			break;
		case PERM_EVERYONE:
			mask = node->mPermissions->getMaskEveryone();
			break;
		case PERM_NEXT_OWNER:
			mask = node->mPermissions->getMaskNextOwner();
			break;
		default:
			mask = 0x0;
			break;
		}
		mask_and &= mask;
		mask_or	 |= mask;
	}

	if (all_valid)
	{
		// ...true through all ANDs means all true
		*mask_on  = mask_and;

		// ...false through all ORs means all false
		*mask_off = ~mask_or;
		return TRUE;
	}
	else
	{
		*mask_on  = 0;
		*mask_off = 0;
		return FALSE;
	}
}



BOOL LLSelectMgr::selectGetOwnershipCost(S32* out_cost)
{
	return mSelectedObjects.getOwnershipCost(*out_cost);
}

BOOL LLSelectMgr::selectGetPermissions(LLPermissions& perm)
{
	LLSelectNode* node = getFirstRootNode();
	if (!node) return FALSE;
	if (!node->mValid)	return FALSE;
	BOOL valid = TRUE;
	perm = *(node->mPermissions);
	for(node = getNextRootNode(); node != NULL; node = getNextRootNode())
	{
		if(!node->mValid)
		{
			valid = FALSE;
			break;
		}
		perm.accumulate(*(node->mPermissions));
	}
	return valid;
}


void LLSelectMgr::selectDelete()
{
	S32 deleteable_count = 0;

	BOOL locked_but_deleteable_object = FALSE;
	BOOL no_copy_but_deleteable_object = FALSE;
	BOOL all_owned_by_you = TRUE;
	for(LLViewerObject* obj = getFirstObject();
		obj != NULL;
		obj = getNextObject())
	{
		if( obj->isAttachment() )
		{
			continue;
		}

		deleteable_count++;

		// Check to see if you can delete objects which are locked.
		if(!obj->permMove())
		{
			locked_but_deleteable_object = TRUE;
		}
		if(!obj->permCopy())
		{
			no_copy_but_deleteable_object = TRUE;
		}
		if(!obj->permYouOwner())
		{
			all_owned_by_you = FALSE;
		}
	}

	if( 0 == deleteable_count )
	{
		make_ui_sound("UISndInvalidOp");
		return;
	}

	if(locked_but_deleteable_object ||
	   no_copy_but_deleteable_object ||
	   !all_owned_by_you)
	{
		// convert any transient pie-menu selections to full selection so this operation
		// has some context
		// NOTE: if user cancels delete operation, this will potentially leave objects selected outside of build mode
		// but this is ok, if not ideal
		convertTransient();

		//This is messy, but needed to get all english our of the UI.
		if(locked_but_deleteable_object && !no_copy_but_deleteable_object && all_owned_by_you)
		{
			//Locked only
			gViewerWindow->alertXml(  "ConfirmObjectDeleteLock",
								  &LLSelectMgr::confirmDelete,
								  this);
		}
		else if(!locked_but_deleteable_object && no_copy_but_deleteable_object && all_owned_by_you)
		{
			//No Copy only
			gViewerWindow->alertXml(  "ConfirmObjectDeleteNoCopy",
								  &LLSelectMgr::confirmDelete,
								  this);
		}
		else if(!locked_but_deleteable_object && !no_copy_but_deleteable_object && !all_owned_by_you)
		{
			//not owned only
			gViewerWindow->alertXml(  "ConfirmObjectDeleteNoOwn",
								  &LLSelectMgr::confirmDelete,
								  this);
		}
		else if(locked_but_deleteable_object && no_copy_but_deleteable_object && all_owned_by_you)
		{
			//locked and no copy
			gViewerWindow->alertXml(  "ConfirmObjectDeleteLockNoCopy",
								  &LLSelectMgr::confirmDelete,
								  this);
		}
		else if(locked_but_deleteable_object && !no_copy_but_deleteable_object && !all_owned_by_you)
		{
			//locked and not owned
			gViewerWindow->alertXml(  "ConfirmObjectDeleteLockNoOwn",
								  &LLSelectMgr::confirmDelete,
								  this);
		}
		else if(!locked_but_deleteable_object && no_copy_but_deleteable_object && !all_owned_by_you)
		{
			//no copy and not owned
			gViewerWindow->alertXml(  "ConfirmObjectDeleteNoCopyNoOwn",
								  &LLSelectMgr::confirmDelete,
								  this);
		}
		else
		{
			//locked, no copy and not owned
			gViewerWindow->alertXml(  "ConfirmObjectDeleteLockNoCopyNoOwn",
								  &LLSelectMgr::confirmDelete,
								  this);
		}
		
		
		
	}
	else
	{
		confirmDelete(0, (void*)this);
	}
}

// static
void LLSelectMgr::confirmDelete(S32 option, void* data)
{
	LLSelectMgr* self = (LLSelectMgr*)data;
	if(!self) return;
	switch(option)
	{
	case 0:
		{
			// TODO: Make sure you have delete permissions on all of them.
			LLUUID trash_id = gInventory.findCategoryUUIDForType(LLAssetType::AT_TRASH);
			// attempt to derez into the trash.
			LLDeRezInfo* info = new LLDeRezInfo(DRD_TRASH, trash_id);
			self->sendListToRegions("DeRezObject",
									packDeRezHeader,
									packObjectLocalID,
									(void*)info,
									SEND_ONLY_ROOTS);
			// VEFFECT: Delete Object - one effect for all deletes
			if (self->mSelectType != SELECT_TYPE_HUD)
			{
				LLHUDEffectSpiral *effectp = (LLHUDEffectSpiral *)gHUDManager->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_POINT, TRUE);
				effectp->setPositionGlobal( self->getSelectionCenterGlobal() );
				effectp->setColor(LLColor4U(gAgent.getEffectColor()));
				F32 duration = 0.5f;
				duration += self->getObjectCount() / 64.f;
				effectp->setDuration(duration);
			}

			gAgent.setLookAt(LOOKAT_TARGET_CLEAR);

			// Keep track of how many objects have been deleted.
			F64 obj_delete_count = gViewerStats->getStat(LLViewerStats::ST_OBJECT_DELETE_COUNT);
			obj_delete_count += self->getObjectCount();
			gViewerStats->setStat(LLViewerStats::ST_OBJECT_DELETE_COUNT, obj_delete_count );
		}
		break;
	case 1:
	default:
		break;
	}
}


void LLSelectMgr::selectForceDelete()
{
	sendListToRegions(
		"ObjectDelete",
		packDeleteHeader,
		packObjectLocalID,
		(void*)TRUE,
		SEND_ONLY_ROOTS);
}


// returns TRUE if anything is for sale. calculates the total price
// and stores that value in price.
BOOL LLSelectMgr::selectIsForSale(S32& price)
{
	BOOL any_for_sale = FALSE;
	price = 0;

	LLSelectNode *node;
	for (node = getFirstRootNode(); node; node = getNextRootNode() )
	{
		if (node->mSaleInfo.isForSale())
		{
			price += node->mSaleInfo.getSalePrice();
			any_for_sale = TRUE;
		}
	}

	return any_for_sale;

}

// returns TRUE if all nodes are valid. method also stores an
// accumulated sale info.
BOOL LLSelectMgr::selectGetSaleInfo(LLSaleInfo& sale_info)
{
	LLSelectNode* node = getFirstRootNode();
	if (!node) return FALSE;
	if (!node->mValid)	return FALSE;
	BOOL valid = TRUE;
	sale_info = node->mSaleInfo;
	for(node = getNextRootNode(); node != NULL; node = getNextRootNode())
	{
		if(!node->mValid)
		{
			valid = FALSE;
			break;
		}
		sale_info.accumulate(node->mSaleInfo);
	}
	return valid;
}

BOOL LLSelectMgr::selectGetAggregatePermissions(LLAggregatePermissions& ag_perm)
{
	LLSelectNode* node = getFirstNode();
	if (!node) return FALSE;
	if (!node->mValid)	return FALSE;
	BOOL valid = TRUE;
	ag_perm = node->mAggregatePerm;
	for(node = getNextNode(); node != NULL; node = getNextNode())
	{
		if(!node->mValid)
		{
			valid = FALSE;
			break;
		}
		ag_perm.aggregate(node->mAggregatePerm);
	}
	return valid;
}

BOOL LLSelectMgr::selectGetAggregateTexturePermissions(LLAggregatePermissions& ag_perm)
{
	LLSelectNode* node = getFirstNode();
	if (!node) return FALSE;
	if (!node->mValid)	return FALSE;
	BOOL valid = TRUE;
	ag_perm = node->getObject()->permYouOwner() ? node->mAggregateTexturePermOwner : node->mAggregateTexturePerm;
	for(node = getNextNode(); node != NULL; node = getNextNode())
	{
		if(!node->mValid)
		{
			valid = FALSE;
			break;
		}
		ag_perm.aggregate(node->getObject()->permYouOwner() ? node->mAggregateTexturePermOwner : node->mAggregateTexturePerm);
	}
	return valid;
}


// returns TRUE is any node is currenly worn as an attachment
BOOL LLSelectMgr::selectionIsAttachment()
{
	return (mSelectType == SELECT_TYPE_ATTACHMENT || mSelectType == SELECT_TYPE_HUD);
}

//--------------------------------------------------------------------
// Duplicate objects
//--------------------------------------------------------------------

// JC - If this doesn't work right, duplicate the selection list
// before doing anything, do a deselect, then send the duplicate
// messages.
struct LLDuplicateData
{
	LLVector3	offset;
	U32			flags;
};

void LLSelectMgr::selectDuplicate(const LLVector3& offset, BOOL select_copy)
{
	if (selectionIsAttachment())
	{
		//RN: do not duplicate attachments
		make_ui_sound("UISndInvalidOp");
		return;
	}
	LLDuplicateData	data;

	data.offset = offset;
	data.flags = (select_copy ? FLAGS_CREATE_SELECTED : 0x0);

	sendListToRegions("ObjectDuplicate", packDuplicateHeader, packDuplicate, &data, SEND_ONLY_ROOTS);

	if (select_copy)
	{
		// the new copy will be coming in selected
		deselectAll();
	}
	else
	{
		for (LLSelectNode* node = getFirstRootNode(); node; node = getNextRootNode())
		{
			node->mDuplicated = TRUE;
			node->mDuplicatePos = node->getObject()->getPositionGlobal();
			node->mDuplicateRot = node->getObject()->getRotation();
		}
	}
}

void LLSelectMgr::repeatDuplicate()
{
	if (selectionIsAttachment())
	{
		//RN: do not duplicate attachments
		make_ui_sound("UISndInvalidOp");
		return;
	}

	LLSelectNode* node;
	LLDynamicArray<LLViewerObject*> non_duplicated_objects;

	for (node = getFirstRootNode(); node; node = getNextRootNode())
	{
		if (!node->mDuplicated)
		{
			non_duplicated_objects.put(node->getObject());
		}
	}

	// make sure only previously duplicated objects are selected
	for (S32 i = 0; i < non_duplicated_objects.count(); i++)
	{
		deselectObjectAndFamily(non_duplicated_objects[i]);
	}
	
	// duplicate objects in place
	LLDuplicateData	data;

	data.offset = LLVector3::zero;
	data.flags = 0x0;

	sendListToRegions("ObjectDuplicate", packDuplicateHeader, packDuplicate, &data, SEND_ONLY_ROOTS);

	// move current selection based on delta from duplication position and update duplication position
	for (node = getFirstRootNode(); node; node = getNextRootNode())
	{
		if (node->mDuplicated)
		{
			LLQuaternion cur_rot = node->getObject()->getRotation();
			LLQuaternion rot_delta = (~node->mDuplicateRot * cur_rot);
			LLQuaternion new_rot = cur_rot * rot_delta;
			LLVector3d cur_pos = node->getObject()->getPositionGlobal();
			LLVector3d new_pos = cur_pos + ((cur_pos - node->mDuplicatePos) * rot_delta);

			node->mDuplicatePos = node->getObject()->getPositionGlobal();
			node->mDuplicateRot = node->getObject()->getRotation();
			node->getObject()->setPositionGlobal(new_pos);
			node->getObject()->setRotation(new_rot);
		}
	}

	sendMultipleUpdate(UPD_ROTATION | UPD_POSITION);
}

// static 
void LLSelectMgr::packDuplicate( LLSelectNode* node, void *duplicate_data )
{
	gMessageSystem->nextBlockFast(_PREHASH_ObjectData);
	gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, node->getObject()->getLocalID());
}


//--------------------------------------------------------------------
// Duplicate On Ray
//--------------------------------------------------------------------

// Duplicates the selected objects, but places the copy along a cast
// ray.
struct LLDuplicateOnRayData
{
	LLVector3	mRayStartRegion;
	LLVector3	mRayEndRegion;
	BOOL		mBypassRaycast;
	BOOL		mRayEndIsIntersection;
	LLUUID		mRayTargetID;
	BOOL		mCopyCenters;
	BOOL		mCopyRotates;
	U32			mFlags;
};

void LLSelectMgr::selectDuplicateOnRay(const LLVector3 &ray_start_region,
									   const LLVector3 &ray_end_region,
									   BOOL bypass_raycast,
									   BOOL ray_end_is_intersection,
									   const LLUUID &ray_target_id,
									   BOOL copy_centers,
									   BOOL copy_rotates,
									   BOOL select_copy)
{
	if (selectionIsAttachment())
	{
		// do not duplicate attachments
		make_ui_sound("UISndInvalidOp");
		return;
	}
	
	LLDuplicateOnRayData	data;

	data.mRayStartRegion	= ray_start_region;
	data.mRayEndRegion		= ray_end_region;
	data.mBypassRaycast		= bypass_raycast;
	data.mRayEndIsIntersection = ray_end_is_intersection;
	data.mRayTargetID		= ray_target_id;
	data.mCopyCenters		= copy_centers;
	data.mCopyRotates		= copy_rotates;
	data.mFlags				= (select_copy ? FLAGS_CREATE_SELECTED : 0x0);

	sendListToRegions("ObjectDuplicateOnRay", 
		packDuplicateOnRayHead, packObjectLocalID, &data, SEND_ONLY_ROOTS);

	if (select_copy)
	{
		// the new copy will be coming in selected
		deselectAll();
	}
}

// static
void LLSelectMgr::packDuplicateOnRayHead(void *user_data)
{
	LLMessageSystem *msg = gMessageSystem;
	LLDuplicateOnRayData *data = (LLDuplicateOnRayData *)user_data;

	msg->nextBlockFast(_PREHASH_AgentData);
	msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() );
	msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID() );
	msg->addUUIDFast(_PREHASH_GroupID, gAgent.getGroupID() );
	msg->addVector3Fast(_PREHASH_RayStart, data->mRayStartRegion );
	msg->addVector3Fast(_PREHASH_RayEnd, data->mRayEndRegion );
	msg->addBOOLFast(_PREHASH_BypassRaycast, data->mBypassRaycast );
	msg->addBOOLFast(_PREHASH_RayEndIsIntersection, data->mRayEndIsIntersection );
	msg->addBOOLFast(_PREHASH_CopyCenters, data->mCopyCenters );
	msg->addBOOLFast(_PREHASH_CopyRotates, data->mCopyRotates );
	msg->addUUIDFast(_PREHASH_RayTargetID, data->mRayTargetID );
	msg->addU32Fast(_PREHASH_DuplicateFlags, data->mFlags );
}



//------------------------------------------------------------------------
// Object position, scale, rotation update, all-in-one
//------------------------------------------------------------------------

void LLSelectMgr::sendMultipleUpdate(U32 type)
{
	if (type == UPD_NONE) return;
	// send individual updates when selecting textures or individual objects
	ESendType send_type = (gSavedSettings.getBOOL("SelectLinkedSet") && !getTEMode()) ? SEND_ONLY_ROOTS : SEND_ROOTS_FIRST;
	if (send_type == SEND_ONLY_ROOTS)
	{
		// tell simulator to apply to whole linked sets
		type |= UPD_LINKED_SETS;
	}

	sendListToRegions(
		"MultipleObjectUpdate",
		packAgentAndSessionID,
		packMultipleUpdate,
		&type,
		send_type);
}

// static
void LLSelectMgr::packMultipleUpdate(LLSelectNode* node, void *user_data)
{
	LLViewerObject* object = node->getObject();
	U32	*type32 = (U32 *)user_data;
	U8 type = (U8)*type32;
	U8	data[256];

	gMessageSystem->nextBlockFast(_PREHASH_ObjectData);
	gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID,	object->getLocalID() );
	gMessageSystem->addU8Fast(_PREHASH_Type, type );

	S32 offset = 0;

	// JC: You MUST pack the data in this order.  The receiving
	// routine process_multiple_update_message on simulator will
	// extract them in this order.

	if (type & UPD_POSITION)
	{
		htonmemcpy(&data[offset], &(object->getPosition().mV), MVT_LLVector3, 12); 
		offset += 12;
	}
	if (type & UPD_ROTATION)
	{
		LLQuaternion quat = object->getRotation();
		LLVector3 vec = quat.packToVector3();
		htonmemcpy(&data[offset], &(vec.mV), MVT_LLQuaternion, 12); 
		offset += 12;
	}
	if (type & UPD_SCALE)
	{
		//llinfos << "Sending object scale " << object->getScale() << llendl;
		htonmemcpy(&data[offset], &(object->getScale().mV), MVT_LLVector3, 12); 
		offset += 12;
	}
	gMessageSystem->addBinaryDataFast(_PREHASH_Data, data, offset);
}

//------------------------------------------------------------------------
// Ownership
//------------------------------------------------------------------------
struct LLOwnerData
{
	LLUUID	owner_id;
	LLUUID	group_id;
	BOOL	override;
};

void LLSelectMgr::sendOwner(const LLUUID& owner_id,
							const LLUUID& group_id,
							BOOL override)
{
	LLOwnerData data;

	data.owner_id = owner_id;
	data.group_id = group_id;
	data.override = override;

	sendListToRegions("ObjectOwner", packOwnerHead, packObjectLocalID, &data, SEND_ONLY_ROOTS);
}

// static
void LLSelectMgr::packOwnerHead(void *user_data)
{
	LLOwnerData *data = (LLOwnerData *)user_data;

	gMessageSystem->nextBlockFast(_PREHASH_AgentData);
	gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() );
	gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID() );
	gMessageSystem->nextBlockFast(_PREHASH_HeaderData);
	gMessageSystem->addBOOLFast(_PREHASH_Override, data->override);
	gMessageSystem->addUUIDFast(_PREHASH_OwnerID, data->owner_id);
	gMessageSystem->addUUIDFast(_PREHASH_GroupID, data->group_id);
}

//------------------------------------------------------------------------
// Group
//------------------------------------------------------------------------

void LLSelectMgr::sendGroup(const LLUUID& group_id)
{
	LLUUID local_group_id(group_id);
	sendListToRegions("ObjectGroup", packAgentAndSessionAndGroupID, packObjectLocalID, &local_group_id, SEND_ONLY_ROOTS);
}


//------------------------------------------------------------------------
// Buy
//------------------------------------------------------------------------

struct LLBuyData
{
	LLDynamicArray<LLViewerObject*> mObjectsSent;
	LLUUID mCategoryID;
	LLSaleInfo mSaleInfo;
};

// FIXME: doesn't work for multiple object buy, which UI does not currently support
// sale info is used for verification only, if it doesn't match region info then sale is canceled
// Need to get sale info -as displayed in the UI- for every item.
void LLSelectMgr::sendBuy(const LLUUID& buyer_id, const LLUUID& category_id, const LLSaleInfo sale_info)
{
	LLBuyData buy;
	buy.mCategoryID = category_id;
	buy.mSaleInfo = sale_info;
	sendListToRegions("ObjectBuy", packAgentGroupAndCatID, packBuyObjectIDs, &buy, SEND_ONLY_ROOTS);
}

// static
void LLSelectMgr::packBuyObjectIDs(LLSelectNode* node, void* data)
{
	LLBuyData* buy = (LLBuyData*)data;

	LLViewerObject* object = node->getObject();
	if(buy->mObjectsSent.find(object) == LLDynamicArray<LLViewerObject*>::FAIL)
	{
		buy->mObjectsSent.put(object);
		gMessageSystem->nextBlockFast(_PREHASH_ObjectData);
		gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, node->getObject()->getLocalID() );
		gMessageSystem->addU8Fast(_PREHASH_SaleType, buy->mSaleInfo.getSaleType());
		gMessageSystem->addS32Fast(_PREHASH_SalePrice, buy->mSaleInfo.getSalePrice());
	}
}

//------------------------------------------------------------------------
// Permissions
//------------------------------------------------------------------------

struct LLPermData
{
	U8 mField;
	BOOL mSet;
	U32 mMask;
	BOOL mOverride;
};

// TODO: Make this able to fail elegantly.
void LLSelectMgr::setObjectPermissions(U8 field,
									   BOOL set,
									   U32 mask,
									   BOOL override)
{
	LLPermData data;

	data.mField = field;
	data.mSet = set;
	data.mMask = mask;
	data.mOverride = override;

	sendListToRegions("ObjectPermissions", packPermissionsHead, packPermissions, &data, SEND_ONLY_ROOTS);
}

void LLSelectMgr::packPermissionsHead(void* user_data)
{
	LLPermData* data = (LLPermData*)user_data;
	gMessageSystem->nextBlockFast(_PREHASH_AgentData);
	gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
	gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
	gMessageSystem->nextBlockFast(_PREHASH_HeaderData);
	gMessageSystem->addBOOLFast(_PREHASH_Override, data->mOverride);
}	


// Now that you've added a bunch of objects, send a select message
// on the entire list for efficiency.
/*
void LLSelectMgr::sendSelect()
{
	llerrs << "Not implemented" << llendl;
}
*/

void LLSelectMgr::deselectAll()
{
	if (!mSelectedObjects.getNumNodes())
	{
		return;
	}

	sendListToRegions(
		"ObjectDeselect",
		packAgentAndSessionID,
		packObjectLocalID,
		NULL,
		SEND_INDIVIDUALS);

	removeAll();
	
	mLastSentSelectionCenterGlobal.clearVec();

	updatePointAt();
	gHUDManager->clearJoints();
	updateSelectionCenter();
}

void LLSelectMgr::deselectTransient()
{
	std::set<LLViewerObject*> objects_to_deselect;
	LLSelectNode *nodep;
	for (nodep = mSelectedObjects.getFirstNode(); nodep; nodep = mSelectedObjects.getNextNode())
	{
		if (nodep->isTransient())
		{
			objects_to_deselect.insert(nodep->getObject());
		}
	}

	std::set<LLViewerObject*>::iterator iter;
	for (iter = objects_to_deselect.begin();
		iter != objects_to_deselect.end();
		++iter)
	{
		deselectObjectOnly(*iter);
	}

	gHUDManager->clearJoints();
	updateSelectionCenter();
}

void LLSelectMgr::convertTransient()
{
	LLSelectNode *nodep;
	for (nodep = mSelectedObjects.getFirstNode(); nodep; nodep = mSelectedObjects.getNextNode())
	{
		nodep->setTransient(FALSE);
	}
}

void LLSelectMgr::deselectAllIfTooFar()
{
	if (isEmpty() || mSelectType == SELECT_TYPE_HUD)
	{
		return;
	}

	// HACK: Don't deselect when we're navigating to rate an object's
	// owner or creator.  JC
	if (gPieObject->getVisible() || gPieRate->getVisible() )
	{
		return;
	}

	LLVector3d selectionCenter = getSelectionCenterGlobal();
	if (gSavedSettings.getBOOL("LimitSelectDistance") 
		&& !selectionCenter.isExactlyZero())
	{
		F32 deselect_dist = gSavedSettings.getF32("MaxSelectDistance");
		F32 deselect_dist_sq = deselect_dist * deselect_dist;

		LLVector3d select_delta = gAgent.getPositionGlobal() - selectionCenter;
		F32 select_dist_sq = (F32) select_delta.magVecSquared();

		if (select_dist_sq > deselect_dist_sq)
		{
			if (gDebugSelectMgr)
			{
				llinfos << "Selection manager: auto-deselecting, select_dist = " << fsqrtf(select_dist_sq) << llendl;
				llinfos << "agent pos global = " << gAgent.getPositionGlobal() << llendl;
				llinfos << "selection pos global = " << selectionCenter << llendl;
			}

			deselectAll();
		}
	}
}


void LLSelectMgr::setObjectName(const LLString& name)
{
	// we only work correctly if 1 object is selected.
	if(getRootObjectCount() == 1)
	{
		sendListToRegions("ObjectName",
						  packAgentAndSessionID,
						  packObjectName,
						  (void*)name.c_str(),
						  SEND_ONLY_ROOTS);
	}
	else if(getObjectCount() == 1)
	{
		sendListToRegions("ObjectName",
						  packAgentAndSessionID,
						  packObjectName,
						  (void*)name.c_str(),
						  SEND_INDIVIDUALS);
	}
}

void LLSelectMgr::setObjectDescription(const LLString& desc)
{
	// we only work correctly if 1 object is selected.
	if(getRootObjectCount() == 1)
	{
		sendListToRegions("ObjectDescription",
						  packAgentAndSessionID,
						  packObjectDescription,
						  (void*)desc.c_str(),
						  SEND_ONLY_ROOTS);
	}
	else if(getObjectCount() == 1)
	{
		sendListToRegions("ObjectDescription",
						  packAgentAndSessionID,
						  packObjectDescription,
						  (void*)desc.c_str(),
						  SEND_INDIVIDUALS);
	}
}

void LLSelectMgr::setObjectCategory(const LLCategory& category)
{
	// for now, we only want to be able to set one root category at
	// a time.
	if(getRootObjectCount() != 1) return;
	sendListToRegions("ObjectCategory",
					  packAgentAndSessionID,
					  packObjectCategory,
					  (void*)(&category),
					  SEND_ONLY_ROOTS);
}

void LLSelectMgr::setObjectSaleInfo(const LLSaleInfo& sale_info)
{
	// Only one sale info at a time for now
	if(getRootObjectCount() != 1) return;
	sendListToRegions("ObjectSaleInfo",
					  packAgentAndSessionID,
					  packObjectSaleInfo,
					  (void*)(&sale_info),
					  SEND_ONLY_ROOTS);
}

//----------------------------------------------------------------------
// Attachments
//----------------------------------------------------------------------

void LLSelectMgr::sendAttach(U8 attachment_point)
{
	LLViewerObject* attach_object = mSelectedObjects.getFirstRootObject();

	if (!attach_object || !gAgent.getAvatarObject() || mSelectType != SELECT_TYPE_WORLD)
	{
		return;
	}

	// Special case: Attach to default location for this object.
	if (0 == attachment_point)
	{
		sendListToRegions(
			"ObjectAttach",
			packAgentIDAndSessionAndAttachment, 
			packObjectIDAndRotation, 
			&attachment_point, 
			SEND_ONLY_ROOTS );
	}
	else
	{
		LLViewerJointAttachment* attachment = gAgent.getAvatarObject()->mAttachmentPoints.getIfThere(attachment_point);
		if (attachment)
		{
			LLQuaternion object_world_rot = attach_object->getRenderRotation();
			LLQuaternion attachment_pt__world_rot = attachment->getWorldRotation();
			LLQuaternion local_rot = object_world_rot * ~attachment_pt__world_rot;

			F32 x,y,z;
			local_rot.getEulerAngles(&x, &y, &z);
			// snap to nearest 90 degree rotation
			// make sure all euler angles are positive
			if (x < F_PI_BY_TWO) x += F_TWO_PI;
			if (y < F_PI_BY_TWO) y += F_TWO_PI;
			if (z < F_PI_BY_TWO) z += F_TWO_PI;

			// add 45 degrees so that rounding down becomes rounding off
			x += F_PI_BY_TWO / 2.f;
			y += F_PI_BY_TWO / 2.f;
			z += F_PI_BY_TWO / 2.f;
			// round down to nearest multiple of 90 degrees
			x -= fmodf(x, F_PI_BY_TWO);
			y -= fmodf(y, F_PI_BY_TWO);
			z -= fmodf(z, F_PI_BY_TWO);

			// pass the requested rotation on to the simulator
			local_rot.setQuat(x, y, z);
			attach_object->setRotation(local_rot);

			sendListToRegions(
				"ObjectAttach",
				packAgentIDAndSessionAndAttachment,
				packObjectIDAndRotation,
				&attachment_point,
				SEND_ONLY_ROOTS );
		}
	}
}

void LLSelectMgr::sendDetach()
{
	if (!mSelectedObjects.getNumNodes() || mSelectType == SELECT_TYPE_WORLD)
	{
		return;
	}

	sendListToRegions(
		"ObjectDetach",
		packAgentAndSessionID,
		packObjectLocalID,
		NULL,
		SEND_ONLY_ROOTS );
}


void LLSelectMgr::sendDropAttachment()
{
	if (!mSelectedObjects.getNumNodes() || mSelectType == SELECT_TYPE_WORLD)
	{
		return;
	}

	sendListToRegions(
		"ObjectDrop",
		packAgentAndSessionID,
		packObjectLocalID,
		NULL,
		SEND_ONLY_ROOTS);
}

//----------------------------------------------------------------------
// Links
//----------------------------------------------------------------------

void LLSelectMgr::sendLink()
{
	if (!mSelectedObjects.getNumNodes())
	{
		return;
	}

	sendListToRegions(
		"ObjectLink",
		packAgentAndSessionID,
		packObjectLocalID,
		NULL,
		SEND_ONLY_ROOTS);
}

void LLSelectMgr::sendDelink()
{
	if (!mSelectedObjects.getNumNodes())
	{
		return;
	}

	// Delink needs to send individuals so you can unlink a single object from
	// a linked set.
	sendListToRegions(
		"ObjectDelink",
		packAgentAndSessionID,
		packObjectLocalID,
		NULL,
		SEND_INDIVIDUALS);
}


//----------------------------------------------------------------------
// Hinges
//----------------------------------------------------------------------

void LLSelectMgr::sendHinge(U8 type)
{
	if (!mSelectedObjects.getNumNodes())
	{
		return;
	}

	sendListToRegions(
		"ObjectHinge",
		packHingeHead,
		packObjectLocalID,
		&type,
		SEND_ONLY_ROOTS);
}


void LLSelectMgr::sendDehinge()
{
	if (!mSelectedObjects.getNumNodes())
	{
		return;
	}

	sendListToRegions(
		"ObjectDehinge",
		packAgentAndSessionID,
		packObjectLocalID,
		NULL,
		SEND_ONLY_ROOTS);
}

void LLSelectMgr::sendSelect()
{
	if (!mSelectedObjects.getNumNodes())
	{
		return;
	}

	sendListToRegions(
		"ObjectSelect",
		packAgentAndSessionID,
		packObjectLocalID,
		NULL,
		SEND_INDIVIDUALS);
}

// static
void LLSelectMgr::packHingeHead(void *user_data)
{
	U8	*type = (U8 *)user_data;

	gMessageSystem->nextBlockFast(_PREHASH_AgentData);
	gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() );
	gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID() );
	gMessageSystem->nextBlockFast(_PREHASH_JointType);
	gMessageSystem->addU8Fast(_PREHASH_Type, *type );
}


void LLSelectMgr::selectionDump()
{
	LLViewerObject *object;

	for (object = getFirstObject(); object; object = getNextObject() )
	{
		object->dump();
	}
}

void LLSelectMgr::saveSelectedObjectColors()
{
	LLSelectNode* selectNode;
	for (selectNode = getFirstNode(); selectNode; selectNode = getNextNode() )
	{
		selectNode->saveColors();
	}
}

void LLSelectMgr::saveSelectedObjectTextures()
{
	LLSelectNode*		selectNode;

	// invalidate current selection so we update saved textures
	for (selectNode = getFirstNode(); selectNode; selectNode = getNextNode() )
	{
		selectNode->mValid = FALSE;
	}

	// request object properties message to get updated permissions data
	sendSelect();
}


// This routine should be called whenever a drag is initiated.
// also need to know to which simulator to send update message
void LLSelectMgr::saveSelectedObjectTransform(EActionType action_type)
{
	LLSelectNode*		selectNode;

	if (isEmpty())
	{
		// nothing selected, so nothing to save
		return;
	}

	for (selectNode = getFirstNode(); selectNode; selectNode = getNextNode() )
	{
		LLViewerObject*		object;
		object = selectNode->getObject();
		selectNode->mSavedPositionLocal = object->getPosition();
		if (object->isAttachment())
		{
			if (object->isRootEdit())
			{
				LLXform* parent_xform = object->mDrawable->getXform()->getParent();
				selectNode->mSavedPositionGlobal = gAgent.getPosGlobalFromAgent((object->getPosition() * parent_xform->getWorldRotation()) + parent_xform->getWorldPosition());
			}
			else
			{
				LLViewerObject* attachment_root = (LLViewerObject*)object->getParent();
				LLXform* parent_xform = attachment_root->mDrawable->getXform()->getParent();
				LLVector3 root_pos = (attachment_root->getPosition() * parent_xform->getWorldRotation()) + parent_xform->getWorldPosition();
				LLQuaternion root_rot = (attachment_root->getRotation() * parent_xform->getWorldRotation());
				selectNode->mSavedPositionGlobal = gAgent.getPosGlobalFromAgent((object->getPosition() * root_rot) + root_pos);
			}
			selectNode->mSavedRotation = object->getRenderRotation();
		}
		else
		{
			selectNode->mSavedPositionGlobal = object->getPositionGlobal();
			selectNode->mSavedRotation = object->getRotationRegion();
		}
		
		selectNode->mSavedScale = object->getScale();
		selectNode->saveTextureScaleRatios();

		if (object->isAttachment() && 
			action_type != SELECT_ACTION_TYPE_PICK)
		{
			LLSelectAction*		selectAction = new LLSelectAction();
			selectAction->mActionType = action_type;
			selectAction->mPosition = object->getPosition();
			selectAction->mRotation = object->getRotation();
			selectAction->mScale = object->getScale();
			selectAction->mObjectID = object->getID();
			selectAction->mIndividualSelection = selectNode->mIndividualSelection;

			mUndoQueue.push_back(selectAction);

			while ((mUndoQueue.size() > (U32)MAX_ACTION_QUEUE_SIZE))
			{
				LLSelectAction* action = mUndoQueue.front();
				mUndoQueue.pop_front();
				delete action;
			}
	
			// remove this object from the redo queue
			std::deque<LLSelectAction*>::iterator it;
			for (it = mRedoQueue.begin(); it != mRedoQueue.end();)
			{
				if ((*it)->mObjectID == object->getID())
				{
					LLSelectAction* actionp = *it;
					it = mRedoQueue.erase(it);
					delete actionp;
				}
				else
				{
					++it;
				}
			}
		}
	}
	mSavedSelectionBBox = getBBoxOfSelection();
}

void LLSelectMgr::selectionUpdatePhysics(BOOL physics)
{
	LLViewerObject *object;

	for (object = getFirstObject(); object; object = getNextObject() )
	{
		if (   !object->permModify()  		// preemptive permissions check
			|| !(object->isRoot()			// don't send for child objects
			|| object->isJointChild()))
		{
			continue;
		}
		object->setFlags( FLAGS_USE_PHYSICS, physics);
	}
}

void LLSelectMgr::selectionUpdateTemporary(BOOL is_temporary)
{
	LLViewerObject *object;

	for (object = getFirstObject(); object; object = getNextObject() )
	{
		if (   !object->permModify()  		// preemptive permissions check
			|| !(object->isRoot()			// don't send for child objects
			|| object->isJointChild()))
		{
			continue;
		}
		object->setFlags( FLAGS_TEMPORARY_ON_REZ, is_temporary);
	}
}

void LLSelectMgr::selectionUpdatePhantom(BOOL is_phantom)
{
	LLViewerObject *object;

	for (object = getFirstObject(); object; object = getNextObject() )
	{
		if (   !object->permModify()  		// preemptive permissions check
			|| !(object->isRoot()			// don't send for child objects
			|| object->isJointChild()))
		{
			continue;
		}
		object->setFlags( FLAGS_PHANTOM, is_phantom);
	}
}

void LLSelectMgr::selectionUpdateCastShadows(BOOL cast_shadows)
{
	LLViewerObject *object;

	for (object = getFirstObject(); object; object = getNextObject() )
	{
		if (   !object->permModify()  		// preemptive permissions check
			|| object->isJointChild())
		{
			continue;
		}
		object->setFlags( FLAGS_CAST_SHADOWS, cast_shadows);
	}
}


//----------------------------------------------------------------------
// Helpful packing functions for sendObjectMessage()
//----------------------------------------------------------------------

// static 
void LLSelectMgr::packAgentIDAndSessionAndAttachment( void *user_data)
{
	U8 *attachment_point = (U8*)user_data;
	gMessageSystem->nextBlockFast(_PREHASH_AgentData);
	gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() );
	gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
	gMessageSystem->addU8Fast(_PREHASH_AttachmentPoint, *attachment_point);
}

// static
void LLSelectMgr::packAgentID(	void *user_data)
{
	gMessageSystem->nextBlockFast(_PREHASH_AgentData);
	gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() );
}

// static
void LLSelectMgr::packAgentAndSessionID(void* user_data)
{
	gMessageSystem->nextBlockFast(_PREHASH_AgentData);
	gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
	gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
}

// static
void LLSelectMgr::packAgentAndGroupID(void* user_data)
{
	LLOwnerData *data = (LLOwnerData *)user_data;

	gMessageSystem->nextBlockFast(_PREHASH_AgentData);
	gMessageSystem->addUUIDFast(_PREHASH_AgentID, data->owner_id );
	gMessageSystem->addUUIDFast(_PREHASH_GroupID, data->group_id );
}

// static
void LLSelectMgr::packAgentAndSessionAndGroupID(void* user_data)
{
	LLUUID* group_idp = (LLUUID*) user_data;
	gMessageSystem->nextBlockFast(_PREHASH_AgentData);
	gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
	gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
	gMessageSystem->addUUIDFast(_PREHASH_GroupID, *group_idp);
}

// static
void LLSelectMgr::packDuplicateHeader(void* data)
{
	LLUUID group_id(gAgent.getGroupID());
	packAgentAndSessionAndGroupID(&group_id);

	LLDuplicateData* dup_data = (LLDuplicateData*) data;

	gMessageSystem->nextBlockFast(_PREHASH_SharedData);
	gMessageSystem->addVector3Fast(_PREHASH_Offset, dup_data->offset);
	gMessageSystem->addU32Fast(_PREHASH_DuplicateFlags, dup_data->flags);
}

// static
void LLSelectMgr::packDeleteHeader(void* userdata)
{
	BOOL force = (BOOL)(intptr_t)userdata;

	gMessageSystem->nextBlockFast(_PREHASH_AgentData);
	gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() );
	gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
	gMessageSystem->addBOOLFast(_PREHASH_Force, force);
}

// static
void LLSelectMgr::packAgentGroupAndCatID(void* user_data)
{
	LLBuyData* buy = (LLBuyData*)user_data;
	gMessageSystem->nextBlockFast(_PREHASH_AgentData);
	gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
	gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
	gMessageSystem->addUUIDFast(_PREHASH_GroupID, gAgent.getGroupID());
	gMessageSystem->addUUIDFast(_PREHASH_CategoryID, buy->mCategoryID);
}

//static
void LLSelectMgr::packDeRezHeader(void* user_data)
{
	LLDeRezInfo* info = (LLDeRezInfo*)user_data;
	gMessageSystem->nextBlockFast(_PREHASH_AgentData);
	gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
	gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
	gMessageSystem->nextBlockFast(_PREHASH_AgentBlock);
	gMessageSystem->addUUIDFast(_PREHASH_GroupID, gAgent.getGroupID());
	gMessageSystem->addU8Fast(_PREHASH_Destination, (U8)info->mDestination);
	gMessageSystem->addUUIDFast(_PREHASH_DestinationID, info->mDestinationID);
	LLUUID tid;
	tid.generate();
	gMessageSystem->addUUIDFast(_PREHASH_TransactionID, tid);
	const U8 PACKET = 1;
	gMessageSystem->addU8Fast(_PREHASH_PacketCount, PACKET);
	gMessageSystem->addU8Fast(_PREHASH_PacketNumber, PACKET);
}

// static 
void LLSelectMgr::packObjectID(LLSelectNode* node, void *user_data)
{
	gMessageSystem->nextBlockFast(_PREHASH_ObjectData);
	gMessageSystem->addUUIDFast(_PREHASH_ObjectID, node->getObject()->mID );
}

void LLSelectMgr::packObjectIDAndRotation(LLSelectNode* node, void *user_data)
{
	gMessageSystem->nextBlockFast(_PREHASH_ObjectData);
	gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, node->getObject()->getLocalID() );
	gMessageSystem->addQuatFast(_PREHASH_Rotation, node->getObject()->getRotation());
}

void LLSelectMgr::packObjectClickAction(LLSelectNode* node, void *user_data)
{
	gMessageSystem->nextBlockFast(_PREHASH_ObjectData);
	gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, node->getObject()->getLocalID() );
	gMessageSystem->addU8("ClickAction", node->getObject()->getClickAction());
}

// static
void LLSelectMgr::packObjectLocalID(LLSelectNode* node, void *)
{
	gMessageSystem->nextBlockFast(_PREHASH_ObjectData);
	gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, node->getObject()->getLocalID());
}

// static
void LLSelectMgr::packObjectName(LLSelectNode* node, void* user_data)
{
	char* name = (char*)user_data;
	if(!name) return;
	gMessageSystem->nextBlockFast(_PREHASH_ObjectData);
	gMessageSystem->addU32Fast(_PREHASH_LocalID, node->getObject()->getLocalID());
	gMessageSystem->addStringFast(_PREHASH_Name, name);
}

// static
void LLSelectMgr::packObjectDescription(LLSelectNode* node,
										void* user_data)
{
	char* desc = (char*)user_data;
	if(!desc) return;
	gMessageSystem->nextBlockFast(_PREHASH_ObjectData);
	gMessageSystem->addU32Fast(_PREHASH_LocalID, node->getObject()->getLocalID());
	gMessageSystem->addStringFast(_PREHASH_Description, desc);
}

// static
void LLSelectMgr::packObjectCategory(LLSelectNode* node, void* user_data)
{
	LLCategory* category = (LLCategory*)user_data;
	if(!category) return;
	gMessageSystem->nextBlockFast(_PREHASH_ObjectData);
	gMessageSystem->addU32Fast(_PREHASH_LocalID, node->getObject()->getLocalID());
	category->packMessage(gMessageSystem);
}

// static
void LLSelectMgr::packObjectSaleInfo(LLSelectNode* node, void* user_data)
{
	LLSaleInfo* sale_info = (LLSaleInfo*)user_data;
	if(!sale_info) return;
	gMessageSystem->nextBlockFast(_PREHASH_ObjectData);
	gMessageSystem->addU32Fast(_PREHASH_LocalID, node->getObject()->getLocalID());
	sale_info->packMessage(gMessageSystem);
}

// static
void LLSelectMgr::packPhysics(LLSelectNode* node, void *user_data)
{
}

// static
void LLSelectMgr::packShape(LLSelectNode* node, void *user_data)
{
}

// static 
void LLSelectMgr::packPermissions(LLSelectNode* node, void *user_data)
{
	LLPermData *data = (LLPermData *)user_data;

	gMessageSystem->nextBlockFast(_PREHASH_ObjectData);
	gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, node->getObject()->getLocalID());

	gMessageSystem->addU8Fast(_PREHASH_Field,	data->mField);
	gMessageSystem->addBOOLFast(_PREHASH_Set,		data->mSet);
	gMessageSystem->addU32Fast(_PREHASH_Mask,		data->mMask);
}

// Utility function to send some information to every region containing
// an object on the selection list.  We want to do this to reduce the total
// number of packets sent by the viewer.
void LLSelectMgr::sendListToRegions(const LLString& message_name,
									void (*pack_header)(void *user_data), 
									void (*pack_body)(LLSelectNode* node, void *user_data), 
									void *user_data,
									ESendType send_type)
{
	LLSelectNode* node;
	LLViewerRegion*	last_region;
	LLViewerRegion*	current_region;

	S32 objects_sent = 0;
	S32 packets_sent = 0;
	S32 objects_in_this_packet = 0;

	std::queue<LLSelectNode*> nodes_to_send;
	
	switch(send_type)
	{
	case SEND_ONLY_ROOTS:
		node = mSelectedObjects.getFirstRootNode();
		while(node)
		{
			nodes_to_send.push(node);
			node = mSelectedObjects.getNextRootNode();
		}
		break;
	case SEND_INDIVIDUALS:
		node = mSelectedObjects.getFirstNode();
		while(node)
		{
			nodes_to_send.push(node);
			node = mSelectedObjects.getNextNode();
		}
		break;
	case SEND_ROOTS_FIRST:
		// first roots...
		node = mSelectedObjects.getFirstNode();
		while(node)
		{
			if (node->getObject()->isRootEdit())
			{
				nodes_to_send.push(node);
			}
			node = mSelectedObjects.getNextNode();
		}

		// then children...
		node = mSelectedObjects.getFirstNode();
		while(node)
		{
			if (!node->getObject()->isRootEdit())
			{
				nodes_to_send.push(node);
			}
			node = mSelectedObjects.getNextNode();
		}
		break;
	case SEND_CHILDREN_FIRST:
		// first children...
		node = mSelectedObjects.getFirstNode();
		while(node)
		{
			if (!node->getObject()->isRootEdit())
			{
				nodes_to_send.push(node);
			}
			node = mSelectedObjects.getNextNode();
		}

		// ...then roots
		node = mSelectedObjects.getFirstNode();
		while(node)
		{
			if (node->getObject()->isRootEdit())
			{
				nodes_to_send.push(node);
			}
			node = mSelectedObjects.getNextNode();
		}
		break;

	default:
		llerrs << "Bad send type " << send_type << " passed to SendListToRegions()" << llendl;
	}

	// bail if nothing selected
	if (nodes_to_send.empty()) return;

	node = nodes_to_send.front();
	nodes_to_send.pop();

	// cache last region information
	current_region = node->getObject()->getRegion();

	// Start duplicate message
	// CRO: this isn't 
	gMessageSystem->newMessage(message_name.c_str());
	(*pack_header)(user_data);

	// For each object
	while (node != NULL)
	{
		// remember the last region, look up the current one
		last_region = current_region;
		current_region = node->getObject()->getRegion();

		// if to same simulator and message not too big
		if ((current_region == last_region)
			&& (gMessageSystem->mCurrentSendTotal < MTUBYTES)
			&& (objects_in_this_packet < MAX_OBJECTS_PER_PACKET))
		{
			// add another instance of the body of the data
			(*pack_body)(node, user_data);
			++objects_sent;
			++objects_in_this_packet;

			// and on to the next object
			if(nodes_to_send.empty())
			{
				node = NULL;
			}
			else
			{
				node =  nodes_to_send.front();
				nodes_to_send.pop();
			}
		}
		else
		{
			// otherwise send current message and start new one
			gMessageSystem->sendReliable( last_region->getHost());
			packets_sent++;
			objects_in_this_packet = 0;

			gMessageSystem->newMessage(message_name.c_str());
			(*pack_header)(user_data);

			// don't move to the next object, we still need to add the
			// body data. 
		}
	}

	// flush messages
	if (gMessageSystem->mCurrentSendTotal > 0)
	{
		gMessageSystem->sendReliable( current_region->getHost());
		packets_sent++;
	}
	else
	{
		gMessageSystem->clearMessage();
	}

	// llinfos << "sendListToRegions " << message_name << " obj " << objects_sent << " pkt " << packets_sent << llendl;
}


//
// Network communications
//

void LLSelectMgr::requestObjectPropertiesFamily(LLViewerObject* object)
{
	LLMessageSystem* msg = gMessageSystem;

	msg->newMessageFast(_PREHASH_RequestObjectPropertiesFamily);
	msg->nextBlockFast(_PREHASH_AgentData);
	msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
	msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
	msg->nextBlockFast(_PREHASH_ObjectData);
	msg->addU32Fast(_PREHASH_RequestFlags, 0x0 );
	msg->addUUIDFast(_PREHASH_ObjectID, object->mID );

	LLViewerRegion* regionp = object->getRegion();
	msg->sendReliable( regionp->getHost() );
}


// static
void LLSelectMgr::processObjectProperties(LLMessageSystem* msg, void** user_data)
{
	S32 i;
	S32 count = msg->getNumberOfBlocksFast(_PREHASH_ObjectData);
	for (i = 0; i < count; i++)
	{
		LLUUID id;
		msg->getUUIDFast(_PREHASH_ObjectData, _PREHASH_ObjectID, id, i);

		LLUUID creator_id;
		LLUUID owner_id;
		LLUUID group_id;
		LLUUID last_owner_id;
		LLUUID extra_id;
		U32 base_mask, owner_mask, group_mask, everyone_mask, next_owner_mask;
		LLSaleInfo sale_info;
		LLCategory category;
		LLAggregatePermissions ag_perms;
		LLAggregatePermissions ag_texture_perms;
		LLAggregatePermissions ag_texture_perms_owner;
		
		msg->getUUIDFast(_PREHASH_ObjectData, _PREHASH_CreatorID, creator_id, i);
		msg->getUUIDFast(_PREHASH_ObjectData, _PREHASH_OwnerID, owner_id, i);
		msg->getUUIDFast(_PREHASH_ObjectData, _PREHASH_GroupID, group_id, i);
		msg->getU32Fast(_PREHASH_ObjectData, _PREHASH_BaseMask, base_mask, i);
		msg->getU32Fast(_PREHASH_ObjectData, _PREHASH_OwnerMask, owner_mask, i);
		msg->getU32Fast(_PREHASH_ObjectData, _PREHASH_GroupMask, group_mask, i);
		msg->getU32Fast(_PREHASH_ObjectData, _PREHASH_EveryoneMask, everyone_mask, i);
		msg->getU32Fast(_PREHASH_ObjectData, _PREHASH_NextOwnerMask, next_owner_mask, i);
		sale_info.unpackMultiMessage(msg, _PREHASH_ObjectData, i);

		ag_perms.unpackMessage(msg, _PREHASH_ObjectData, _PREHASH_AggregatePerms, i);
		ag_texture_perms.unpackMessage(msg, _PREHASH_ObjectData, _PREHASH_AggregatePermTextures, i);
		ag_texture_perms_owner.unpackMessage(msg, _PREHASH_ObjectData, _PREHASH_AggregatePermTexturesOwner, i);
		category.unpackMultiMessage(msg, _PREHASH_ObjectData, i);

		S16 inv_serial = 0;
		msg->getS16Fast(_PREHASH_ObjectData, _PREHASH_InventorySerial, inv_serial, i);

		LLUUID item_id;
		msg->getUUIDFast(_PREHASH_ObjectData, _PREHASH_ItemID, item_id, i);
		LLUUID folder_id;
		msg->getUUIDFast(_PREHASH_ObjectData, _PREHASH_FolderID, folder_id, i);
		LLUUID from_task_id;
		msg->getUUIDFast(_PREHASH_ObjectData, _PREHASH_FromTaskID, from_task_id, i);

		msg->getUUIDFast(_PREHASH_ObjectData, _PREHASH_LastOwnerID, last_owner_id, i);

		char name[DB_INV_ITEM_NAME_BUF_SIZE];
		msg->getStringFast(_PREHASH_ObjectData, _PREHASH_Name, DB_INV_ITEM_NAME_BUF_SIZE, name, i);
		char desc[DB_INV_ITEM_DESC_BUF_SIZE];
		msg->getStringFast(_PREHASH_ObjectData, _PREHASH_Description, DB_INV_ITEM_DESC_BUF_SIZE, desc, i);

		char touch_name[DB_INV_ITEM_NAME_BUF_SIZE];
		msg->getStringFast(_PREHASH_ObjectData, _PREHASH_TouchName, DB_INV_ITEM_NAME_BUF_SIZE, touch_name, i);
		char sit_name[DB_INV_ITEM_DESC_BUF_SIZE];
		msg->getStringFast(_PREHASH_ObjectData, _PREHASH_SitName, DB_INV_ITEM_DESC_BUF_SIZE, sit_name, i);

		//unpack TE IDs
		std::vector<LLUUID> texture_ids;
		S32 size = msg->getSizeFast(_PREHASH_ObjectData, i, _PREHASH_TextureID);
		if (size > 0)
		{
			S8 packed_buffer[SELECT_MAX_TES * UUID_BYTES];
			msg->getBinaryDataFast(_PREHASH_ObjectData, _PREHASH_TextureID, packed_buffer, 0, i, SELECT_MAX_TES * UUID_BYTES);

			for (S32 buf_offset = 0; buf_offset < size; buf_offset += UUID_BYTES)
			{
				LLUUID id;
				memcpy(id.mData, packed_buffer + buf_offset, UUID_BYTES);
				texture_ids.push_back(id);
			}
		}


		// Iterate through nodes at end, since it can be on both the regular AND hover list
		BOOL found = FALSE;
		LLSelectNode* node;
		for (node = gSelectMgr->mSelectedObjects.getFirstNode();
			 node;
			 node = gSelectMgr->mSelectedObjects.getNextNode())
		{
			if (node->getObject()->mID == id)
			{
				found = TRUE;
				break;
			}
		}


		if (node)
		{
			if (node->mInventorySerial != inv_serial)
			{
				node->getObject()->dirtyInventory();
			}

			// save texture data as soon as we get texture perms first time
			if (!node->mValid)
			{
				BOOL can_copy = FALSE;
				BOOL can_transfer = FALSE;

				LLAggregatePermissions::EValue value = LLAggregatePermissions::AP_NONE;
				if(node->getObject()->permYouOwner())
				{
					value = ag_texture_perms_owner.getValue(PERM_COPY);
					if (value == LLAggregatePermissions::AP_EMPTY || value == LLAggregatePermissions::AP_ALL)
					{
						can_copy = TRUE;
					}
					value = ag_texture_perms_owner.getValue(PERM_TRANSFER);
					if (value == LLAggregatePermissions::AP_EMPTY || value == LLAggregatePermissions::AP_ALL)
					{
						can_transfer = TRUE;
					}
				}
				else
				{
					value = ag_texture_perms.getValue(PERM_COPY);
					if (value == LLAggregatePermissions::AP_EMPTY || value == LLAggregatePermissions::AP_ALL)
					{
						can_copy = TRUE;
					}
					value = ag_texture_perms.getValue(PERM_TRANSFER);
					if (value == LLAggregatePermissions::AP_EMPTY || value == LLAggregatePermissions::AP_ALL)
					{
						can_transfer = TRUE;
					}
				}

				if (can_copy && can_transfer)
				{
					// this should be the only place that saved textures is called
					node->saveTextures(texture_ids);
				}
			}

			node->mValid = TRUE;
			node->mPermissions->init(creator_id, owner_id,
									 last_owner_id, group_id);
			node->mPermissions->initMasks(base_mask, owner_mask, everyone_mask, group_mask, next_owner_mask);
			node->mItemID = item_id;
			node->mFolderID = folder_id;
			node->mFromTaskID = from_task_id;
			node->mName.assign(name);
			node->mDescription.assign(desc);
			node->mSaleInfo = sale_info;
			node->mAggregatePerm = ag_perms;
			node->mAggregateTexturePerm = ag_texture_perms;
			node->mAggregateTexturePermOwner = ag_texture_perms_owner;
			node->mCategory = category;
			node->mInventorySerial = inv_serial;
			node->mSitName.assign(sit_name);
			node->mTouchName.assign(touch_name);
		}
	}

	dialog_refresh_all();

	// silly hack to allow 'save into inventory' 
	if(gPopupMenuView->getVisible())
	{
		gPopupMenuView->setItemEnabled(SAVE_INTO_INVENTORY,
									   enable_save_into_inventory(NULL));
	}

	// hack for left-click buy object
	LLToolPie::selectionPropertiesReceived();
}

// static
void LLSelectMgr::processObjectPropertiesFamily(LLMessageSystem* msg, void** user_data)
{
	LLUUID id;

	U32 request_flags;
	LLUUID creator_id;
	LLUUID owner_id;
	LLUUID group_id;
	LLUUID extra_id;
	U32 base_mask, owner_mask, group_mask, everyone_mask, next_owner_mask;
	LLSaleInfo sale_info;
	LLCategory category;
	
	msg->getU32Fast(_PREHASH_ObjectData, _PREHASH_RequestFlags,	request_flags );
	msg->getUUIDFast(_PREHASH_ObjectData, _PREHASH_ObjectID,		id );
	msg->getUUIDFast(_PREHASH_ObjectData, _PREHASH_OwnerID,		owner_id );
	msg->getUUIDFast(_PREHASH_ObjectData, _PREHASH_GroupID,		group_id );
	msg->getU32Fast(_PREHASH_ObjectData, _PREHASH_BaseMask,		base_mask );
	msg->getU32Fast(_PREHASH_ObjectData, _PREHASH_OwnerMask,		owner_mask );
	msg->getU32Fast(_PREHASH_ObjectData,_PREHASH_GroupMask,		group_mask );
	msg->getU32Fast(_PREHASH_ObjectData, _PREHASH_EveryoneMask,	everyone_mask );
	msg->getU32Fast(_PREHASH_ObjectData, _PREHASH_NextOwnerMask, next_owner_mask);
	sale_info.unpackMessage(msg, _PREHASH_ObjectData);
	category.unpackMessage(msg, _PREHASH_ObjectData);

	LLUUID last_owner_id;
	msg->getUUIDFast(_PREHASH_ObjectData, _PREHASH_LastOwnerID, last_owner_id );

	// unpack name & desc
	char name[DB_INV_ITEM_NAME_BUF_SIZE];
	msg->getStringFast(_PREHASH_ObjectData, _PREHASH_Name, DB_INV_ITEM_NAME_BUF_SIZE, name);

	char desc[DB_INV_ITEM_DESC_BUF_SIZE];
	msg->getStringFast(_PREHASH_ObjectData, _PREHASH_Description, DB_INV_ITEM_DESC_BUF_SIZE, desc);

	// the reporter widget askes the server for info about picked objects
	if (request_flags & (COMPLAINT_REPORT_REQUEST | BUG_REPORT_REQUEST))
	{
		EReportType report_type = (COMPLAINT_REPORT_REQUEST & request_flags) ? COMPLAINT_REPORT : BUG_REPORT;
		LLFloaterReporter *reporterp = LLFloaterReporter::getReporter(report_type);
		if (reporterp)
		{
			char first_name[DB_FIRST_NAME_BUF_SIZE];
			char last_name[DB_LAST_NAME_BUF_SIZE];
			gCacheName->getName(owner_id, first_name, last_name);
			LLString fullname(first_name);
			fullname.append(" ");
			fullname.append(last_name);
			reporterp->setPickedObjectProperties(name, fullname.c_str());
		}
	}

	// Now look through all of the hovered nodes
	BOOL found = FALSE;
	LLSelectNode* node;
	for (node = gSelectMgr->mHoverObjects.getFirstNode();
		 node;
		 node = gSelectMgr->mHoverObjects.getNextNode())
	{
		if (node->getObject()->mID == id)
		{
			found = TRUE;
			break;
		}
	}

	if (node)
	{
		node->mValid = TRUE;
		node->mPermissions->init(LLUUID::null, owner_id,
								 last_owner_id, group_id);
		node->mPermissions->initMasks(base_mask, owner_mask, everyone_mask, group_mask, next_owner_mask);
		node->mSaleInfo = sale_info;
		node->mCategory = category;
		node->mName.assign(name);
		node->mDescription.assign(desc);
	}

	dialog_refresh_all();
}


// static
void LLSelectMgr::processForceObjectSelect(LLMessageSystem* msg, void**)
{
	BOOL reset_list;
	msg->getBOOL("Header", "ResetList", reset_list);

	if (reset_list)
	{
		gSelectMgr->deselectAll();
	}

	LLUUID full_id;
	S32 local_id;
	LLViewerObject* object;
	LLDynamicArray<LLViewerObject*> objects;
	S32 i;
	S32 block_count = msg->getNumberOfBlocks("Data");

	for (i = 0; i < block_count; i++)
	{
		msg->getS32("Data", "LocalID", local_id, i);

		gObjectList.getUUIDFromLocal(full_id, 
									 local_id, 
									 msg->getSenderIP(),
									 msg->getSenderPort());
		object = gObjectList.findObject(full_id);
		if (object)
		{
			objects.put(object);
		}
	}

	// Don't select, just highlight
	gSelectMgr->highlightObjectAndFamily(objects);
}


extern LLGLdouble	gGLModelView[16];

void LLSelectMgr::updateSilhouettes()
{
	LLSelectNode *node;
	S32 num_sils_genned = 0;

	LLVector3d	cameraPos = gAgent.getCameraPositionGlobal();
	F32 currentCameraZoom = gAgent.getCurrentCameraBuildOffset();

	if (!mSilhouetteImagep)
	{
		LLUUID id;
		id.set( gViewerArt.getString("silhouette.tga") );
		mSilhouetteImagep = gImageList.getImage(id, TRUE, TRUE);
	}


	if((cameraPos - mLastCameraPos).magVecSquared() > SILHOUETTE_UPDATE_THRESHOLD_SQUARED * currentCameraZoom * currentCameraZoom)
	{
		for (node = mSelectedObjects.getFirstNode(); node; node = mSelectedObjects.getNextNode() )
		{
			if (node->getObject())
			{
				node->getObject()->setChanged(LLXform::SILHOUETTE);
			}
		}
		
		mLastCameraPos = gAgent.getCameraPositionGlobal();
	}
	
	LLDynamicArray<LLViewerObject*> changed_objects;

	if (mSelectedObjects.getNumNodes())
	{
		//gGLSPipelineSelection.set();

		//mSilhouetteImagep->bindTexture();
		//glAlphaFunc(GL_GREATER, sHighlightAlphaTest);

		for (S32 pass = 0; pass < 2; pass++)
		{
			for (node = mSelectedObjects.getFirstNode(); node; node = mSelectedObjects.getNextNode() )
			{
				LLViewerObject* objectp = node->getObject();

				// do roots first, then children so that root flags are cleared ASAP
				BOOL roots_only = (pass == 0);
				BOOL is_root = (objectp->isRootEdit());
				if (roots_only != is_root || objectp->mDrawable.isNull())
				{
					continue;
				}

				if (!node->mSilhouetteExists 
					|| objectp->isChanged(LLXform::SILHOUETTE)
					|| (objectp->getParent() && objectp->getParent()->isChanged(LLXform::SILHOUETTE)))
				{
					if (num_sils_genned++ < MAX_SILS_PER_FRAME && objectp->mDrawable->isVisible())
					{
						generateSilhouette(node, gCamera->getOrigin());
						changed_objects.put(objectp);
					}
					else if (objectp->isAttachment())
					{
						//RN: hack for orthogonal projection of HUD attachments
						LLViewerJointAttachment* attachment_pt = (LLViewerJointAttachment*)objectp->getRootEdit()->mDrawable->getParent();
						if (attachment_pt && attachment_pt->getIsHUDAttachment())
						{
							LLVector3 camera_pos = LLVector3(-10000.f, 0.f, 0.f);
							generateSilhouette(node, camera_pos);
						}
					}
				}

				//if (!gFloaterTools || !gFloaterTools->getVisible())
				//{
				//	node->renderOneSilhouette(sContextSilhouetteColor);
				//}
				//else if (objectp->isRootEdit())
				//{
				//	node->renderOneSilhouette(sSilhouetteParentColor);
				//}
				//else
				//{
				//	node->renderOneSilhouette(sSilhouetteChildColor);
				//}
			}
		}
		//mSilhouetteImagep->unbindTexture(0, GL_TEXTURE_2D);
	}

	if (mRectSelectedObjects.size() > 0)
	{
		//gGLSPipelineSelection.set();

		//mSilhouetteImagep->bindTexture();
		//glAlphaFunc(GL_GREATER, sHighlightAlphaTest);

		std::set<LLViewerObject*> roots;

		// sync mHighlightedObjects with mRectSelectedObjects since the latter is rebuilt every frame and former
		// persists from frame to frame to avoid regenerating object silhouettes
		// mHighlightedObjects includes all siblings of rect selected objects

		BOOL select_linked_set = gSavedSettings.getBOOL("SelectLinkedSet");

		// generate list of roots from current object selection
		for (std::set<LLPointer<LLViewerObject> >::iterator iter = mRectSelectedObjects.begin();
			 iter != mRectSelectedObjects.end(); iter++)
		{
			LLViewerObject *objectp = *iter;
			if (select_linked_set)
			{
				LLViewerObject *rootp = (LLViewerObject*)objectp->getRoot();
				roots.insert(rootp);
			}
			else
			{
				roots.insert(objectp);
			}
		}

		// remove highlight nodes not in roots list
		LLDynamicArray<LLSelectNode*> remove_these_nodes;
		LLDynamicArray<LLViewerObject*> remove_these_roots;
		for (LLSelectNode* nodep = mHighlightedObjects.getFirstNode(); nodep; nodep = mHighlightedObjects.getNextNode())
		{
			LLViewerObject* objectp = nodep->getObject();
			if (objectp->isRoot() || !select_linked_set)
			{
				if (roots.count(objectp) == 0)
				{
					remove_these_nodes.put(nodep);
				}
				else
				{
					remove_these_roots.put(objectp);
				}
			}
			else
			{
				LLViewerObject* rootp = (LLViewerObject*)objectp->getRoot();

				if (roots.count(rootp) == 0)
				{
					remove_these_nodes.put(nodep);
				}
			}
		}

		// remove all highlight nodes no longer in rectangle selection
		S32 i;
		for (i = 0; i < remove_these_nodes.count(); i++)
		{
			mHighlightedObjects.removeNode(remove_these_nodes[i]);
		}

		// remove all root objects already being highlighted
		for (i = 0; i < remove_these_roots.count(); i++)
		{
			roots.erase(remove_these_roots[i]);
		}

		// add all new objects in rectangle selection
		for (std::set<LLViewerObject*>::iterator iter = roots.begin();
			 iter != roots.end(); iter++)
		{
			LLViewerObject* objectp = *iter;
			LLSelectNode* rect_select_node = new LLSelectNode(objectp, TRUE);
			rect_select_node->selectAllTEs(TRUE);

			if (!canSelectObject(objectp))
			{
				continue;
			}

			mHighlightedObjects.addNode(rect_select_node);

			if (!select_linked_set)
			{
				rect_select_node->mIndividualSelection = TRUE;
			}
			else
			{
				for (U32 i = 0; i < objectp->mChildList.size(); i++)
				{
					LLViewerObject* child_objectp = objectp->mChildList[i];

					if (!canSelectObject(child_objectp))
					{
						continue;
					}

					rect_select_node = new LLSelectNode(objectp->mChildList[i], TRUE);
					rect_select_node->selectAllTEs(TRUE);
					mHighlightedObjects.addNode(rect_select_node);
				}
			}
		}

		num_sils_genned	= 0;

		// render silhouettes for highlighted objects
		//BOOL subtracting_from_selection = (gKeyboard->currentMask(TRUE) == MASK_CONTROL);
		for (S32 pass = 0; pass < 2; pass++)
		{
			for (node = mHighlightedObjects.getFirstNode(); node; node = mHighlightedObjects.getNextNode() )
			{
				LLViewerObject* objectp = node->getObject();

				// do roots first, then children so that root flags are cleared ASAP
				BOOL roots_only = (pass == 0);
				BOOL is_root = objectp->isRootEdit();
				if (roots_only != is_root)
				{
					continue;
				}

				if (!node->mSilhouetteExists 
					|| objectp->isChanged(LLXform::SILHOUETTE)
					|| (objectp->getParent() && objectp->getParent()->isChanged(LLXform::SILHOUETTE)))
				{
					if (num_sils_genned++ < MAX_SILS_PER_FRAME)
					{
						generateSilhouette(node, gCamera->getOrigin());
						changed_objects.put(objectp);			
					}
					else if (objectp->isAttachment() && objectp->getRootEdit()->mDrawable.notNull())
					{
						//RN: hack for orthogonal projection of HUD attachments
						LLViewerJointAttachment* attachment_pt = (LLViewerJointAttachment*)objectp->getRootEdit()->mDrawable->getParent();
						if (attachment_pt && attachment_pt->getIsHUDAttachment())
						{
							LLVector3 camera_pos = LLVector3(-10000.f, 0.f, 0.f);
							generateSilhouette(node, camera_pos);
						}
					}
				}
				//LLColor4 highlight_color;
				//
				//if (subtracting_from_selection)
				//{
				//	node->renderOneSilhouette(LLColor4::red);
				//}
				//else if (!objectp->isSelected())
				//{
				//	highlight_color = objectp->isRoot() ? sHighlightParentColor : sHighlightChildColor;
				//	node->renderOneSilhouette(highlight_color);
				//}
			}
		}
		//mSilhouetteImagep->unbindTexture(0, GL_TEXTURE_2D);
	}
	else
	{
		mHighlightedObjects.deleteAllNodes();
	}

	for (S32 i = 0; i < changed_objects.count(); i++)
	{
		// clear flags after traversing node list (as child objects need to refer to parent flags, etc)
		changed_objects[i]->clearChanged(LLXform::MOVED | LLXform::SILHOUETTE);
	}
	
	//glAlphaFunc(GL_GREATER, 0.01f);
}

void LLSelectMgr::renderSilhouettes(BOOL for_hud)
{
	if (!mRenderSilhouettes)
	{
		return;
	}

	LLSelectNode *node;
	LLViewerImage::bindTexture(gSelectMgr->mSilhouetteImagep);
	LLGLSPipelineSelection gls_select;
	glAlphaFunc(GL_GREATER, 0.0f);
	LLGLEnable blend(GL_BLEND);
	LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE);

	LLVOAvatar* avatar = gAgent.getAvatarObject();
	if (for_hud && avatar)
	{
		LLBBox hud_bbox = avatar->getHUDBBox();

		F32 cur_zoom = avatar->mHUDCurZoom;

		// set up transform to encompass bounding box of HUD
		glMatrixMode(GL_PROJECTION);
		glPushMatrix();
		glLoadIdentity();
		F32 depth = llmax(1.f, hud_bbox.getExtentLocal().mV[VX] * 1.1f);
		glOrtho(-0.5f * gCamera->getAspect(), 0.5f * gCamera->getAspect(), -0.5f, 0.5f, 0.f, depth);

		glMatrixMode(GL_MODELVIEW);
		glPushMatrix();
		glLoadIdentity();
		glLoadMatrixf(OGL_TO_CFR_ROTATION);		// Load Cory's favorite reference frame
		glTranslatef(-hud_bbox.getCenterLocal().mV[VX] + (depth *0.5f), 0.f, 0.f);
		glScalef(cur_zoom, cur_zoom, cur_zoom);
	}
	if (mSelectedObjects.getNumNodes())
	{
		glPushAttrib(GL_FOG_BIT);
		for (S32 pass = 0; pass < 2; pass++)
		{
			for (node = mSelectedObjects.getFirstNode(); node; node = mSelectedObjects.getNextNode() )
			{
				LLViewerObject* objectp = node->getObject();
				if (objectp->isHUDAttachment() != for_hud)
				{
					continue;
				}
				if (node->isTransient())
				{
					BOOL oldHidden = LLSelectMgr::sRenderHiddenSelections;
					LLSelectMgr::sRenderHiddenSelections = FALSE;
					node->renderOneSilhouette(sContextSilhouetteColor);
					LLSelectMgr::sRenderHiddenSelections = oldHidden;
				}
				else if (objectp->isRootEdit())
				{
					node->renderOneSilhouette(sSilhouetteParentColor);
				}
				else
				{
					node->renderOneSilhouette(sSilhouetteChildColor);
				}
			}
		}
		glPopAttrib();
	}

	if (mHighlightedObjects.getNumNodes())
	{
		// render silhouettes for highlighted objects
		BOOL subtracting_from_selection = (gKeyboard->currentMask(TRUE) == MASK_CONTROL);
		for (S32 pass = 0; pass < 2; pass++)
		{
			for (node = mHighlightedObjects.getFirstNode(); node; node = mHighlightedObjects.getNextNode() )
			{
				LLViewerObject* objectp = node->getObject();
				if (objectp->isHUDAttachment() != for_hud)
				{
					continue;
				}

				if (subtracting_from_selection)
				{
					node->renderOneSilhouette(LLColor4::red);
				}
				else if (!objectp->isSelected())
				{
					LLColor4 highlight_color = objectp->isRoot() ? sHighlightParentColor : sHighlightChildColor;
					node->renderOneSilhouette(highlight_color);
				}
			}
		}
	}

	if (for_hud && avatar)
	{
		glMatrixMode(GL_PROJECTION);
		glPopMatrix();

		glMatrixMode(GL_MODELVIEW);
		glPopMatrix();
		stop_glerror();
	}

	gSelectMgr->mSilhouetteImagep->unbindTexture(0, GL_TEXTURE_2D);
	glAlphaFunc(GL_GREATER, 0.01f);
}

void LLSelectMgr::generateSilhouette(LLSelectNode* nodep, const LLVector3& view_point)
{
	LLViewerObject* objectp = nodep->getObject();

	if (objectp && objectp->getPCode() == LL_PCODE_VOLUME)
	{
		((LLVOVolume*)objectp)->generateSilhouette(nodep, view_point);
	}
}

void LLSelectMgr::getSilhouetteExtents(LLSelectNode* nodep, const LLQuaternion& orientation, LLVector3& min_extents, LLVector3& max_extents)
{
	LLViewerObject* objectp = nodep->getObject();

	if (objectp->mDrawable.isNull())
	{
		return;
	}
	
	LLQuaternion test_rot = orientation * ~objectp->getRenderRotation();
	LLVector3 x_axis_rot = LLVector3::x_axis * test_rot;
	LLVector3 y_axis_rot = LLVector3::y_axis * test_rot;
	LLVector3 z_axis_rot = LLVector3::z_axis * test_rot;

	x_axis_rot.scaleVec(objectp->mDrawable->getScale());
	y_axis_rot.scaleVec(objectp->mDrawable->getScale());
	z_axis_rot.scaleVec(objectp->mDrawable->getScale());

	generateSilhouette(nodep, objectp->mDrawable->getPositionAgent() + x_axis_rot * 100.f);

	S32 num_vertices = nodep->mSilhouetteVertices.size();
	if (num_vertices)
	{
		min_extents.mV[VY] = llmin(min_extents.mV[VY], nodep->mSilhouetteVertices[0] * y_axis_rot);
		max_extents.mV[VY] = llmax(max_extents.mV[VY], nodep->mSilhouetteVertices[0] * y_axis_rot);

		min_extents.mV[VZ] = llmin(min_extents.mV[VZ], nodep->mSilhouetteVertices[0] * z_axis_rot);
		max_extents.mV[VZ] = llmax(min_extents.mV[VZ], nodep->mSilhouetteVertices[0] * z_axis_rot);

		for (S32 vert = 1; vert < num_vertices; vert++)
		{
			F32 y_pos = nodep->mSilhouetteVertices[vert] * y_axis_rot;
			F32 z_pos = nodep->mSilhouetteVertices[vert] * z_axis_rot;
			min_extents.mV[VY] = llmin(y_pos, min_extents.mV[VY]);
			max_extents.mV[VY] = llmax(y_pos, max_extents.mV[VY]);
			min_extents.mV[VZ] = llmin(z_pos, min_extents.mV[VZ]);
			max_extents.mV[VZ] = llmax(z_pos, max_extents.mV[VZ]);
		}
	}

	generateSilhouette(nodep, objectp->mDrawable->getPositionAgent() + y_axis_rot * 100.f);

	num_vertices = nodep->mSilhouetteVertices.size();
	if (num_vertices)
	{
		min_extents.mV[VX] = llmin(min_extents.mV[VX], nodep->mSilhouetteVertices[0] * x_axis_rot);
		max_extents.mV[VX] = llmax(max_extents.mV[VX], nodep->mSilhouetteVertices[0] * x_axis_rot);

		for (S32 vert = 1; vert < num_vertices; vert++)
		{
			F32 x_pos = nodep->mSilhouetteVertices[vert] * x_axis_rot;
			min_extents.mV[VX] = llmin(x_pos, min_extents.mV[VX]);
			max_extents.mV[VX] = llmax(x_pos, max_extents.mV[VX]);
		}
	}

	generateSilhouette(nodep, gCamera->getOrigin());
}


//
// Utility classes
//
LLSelectNode::LLSelectNode(LLViewerObject* object, BOOL glow)
{
	mObject = object;
	selectAllTEs(FALSE);
	mIndividualSelection	= FALSE;
	mTransient		= FALSE;
	mValid			= FALSE;
	mPermissions	= new LLPermissions();
	mInventorySerial = 0;
	mName = LLString::null;
	mDescription = LLString::null;
	mTouchName = LLString::null;
	mSitName = LLString::null;
	mSilhouetteExists = FALSE;
	mDuplicated = FALSE;

	saveColors();
}

LLSelectNode::LLSelectNode(const LLSelectNode& nodep)
{
	S32 i;
	for (i = 0; i < SELECT_MAX_TES; i++)
	{
		mTESelected[i] = nodep.mTESelected[i];
	}
	mLastTESelected = nodep.mLastTESelected;

	mIndividualSelection = nodep.mIndividualSelection;

	mValid = nodep.mValid;
	mTransient		= nodep.mTransient;
	mPermissions = new LLPermissions(*nodep.mPermissions);
	mSaleInfo = nodep.mSaleInfo;;
	mAggregatePerm = nodep.mAggregatePerm;
	mAggregateTexturePerm = nodep.mAggregateTexturePerm;
	mAggregateTexturePermOwner = nodep.mAggregateTexturePermOwner;
	mName = nodep.mName;
	mDescription = nodep.mDescription;
	mCategory = nodep.mCategory;
	mSavedPositionLocal = nodep.mSavedPositionLocal;
	mSavedPositionGlobal = nodep.mSavedPositionGlobal;
	mSavedScale = nodep.mSavedScale;
	mSavedRotation = nodep.mSavedRotation;
	mDuplicated = nodep.mDuplicated;
	mDuplicatePos = nodep.mDuplicatePos;
	mDuplicateRot = nodep.mDuplicateRot;
	mItemID = nodep.mItemID;
	mFolderID = nodep.mFolderID;
	mFromTaskID = nodep.mFromTaskID;
	mTouchName = nodep.mTouchName;
	mSitName = nodep.mSitName;

	mSilhouetteVertices = nodep.mSilhouetteVertices;
	mSilhouetteNormals = nodep.mSilhouetteNormals;
	mSilhouetteSegments = nodep.mSilhouetteSegments;
	mSilhouetteExists = nodep.mSilhouetteExists;
	mObject = nodep.mObject;

	std::vector<LLColor4>::const_iterator color_iter;
	mSavedColors.clear();
	for (color_iter = nodep.mSavedColors.begin(); color_iter != nodep.mSavedColors.end(); ++color_iter)
	{
		mSavedColors.push_back(*color_iter);
	}
	
	saveTextures(nodep.mSavedTextures);
}

LLSelectNode::~LLSelectNode()
{
	delete mPermissions;
	mPermissions = NULL;
}

void LLSelectNode::selectAllTEs(BOOL b)
{
	for (S32 i = 0; i < SELECT_MAX_TES; i++)
	{
		mTESelected[i] = b;
	}
	mLastTESelected = 0;
}

void LLSelectNode::selectTE(S32 te_index, BOOL selected)
{
	if (te_index < 0 || te_index >= SELECT_MAX_TES)
	{
		return;
	}
	mTESelected[te_index] = selected;
	mLastTESelected = te_index;
}

BOOL LLSelectNode::isTESelected(S32 te_index)
{
	if (te_index < 0 || te_index >= mObject->getNumTEs())
	{
		return FALSE;
	}
	return mTESelected[te_index];
}

S32 LLSelectNode::getLastSelectedTE()
{
	if (!isTESelected(mLastTESelected))
	{
		return -1;
	}
	return mLastTESelected;
}

LLViewerObject *LLSelectNode::getObject()
{
	if (!mObject)
	{
		return NULL;
	}
	else if (mObject->isDead())
	{
		mObject = NULL;
	}
	return mObject;
}

void LLSelectNode::saveColors()
{
	if (mObject.notNull())
	{
		mSavedColors.clear();
		for (S32 i = 0; i < mObject->getNumTEs(); i++)
		{
			const LLTextureEntry* tep = mObject->getTE(i);
			mSavedColors.push_back(tep->getColor());
		}
	}
}

void LLSelectNode::saveTextures(const std::vector<LLUUID>& textures)
{
	if (mObject.notNull())
	{
		mSavedTextures.clear();

		std::vector<LLUUID>::const_iterator texture_it;
		for (texture_it = textures.begin(); texture_it != textures.end(); ++texture_it)
		{
			mSavedTextures.push_back(*texture_it);
		}
	}
}

void LLSelectNode::saveTextureScaleRatios()
{
	mTextureScaleRatios.clear();
	if (mObject.notNull())
	{
		for (U8 i = 0; i < mObject->getNumTEs(); i++)
		{
			F32 s,t;
			const LLTextureEntry* tep = mObject->getTE(i);
			tep->getScale(&s,&t);
			U32 s_axis, t_axis;

			gSelectMgr->getTESTAxes(mObject, i, &s_axis, &t_axis);

			LLVector3 v;
			LLVector3 scale = mObject->getScale();

			if (tep->getTexGen() == LLTextureEntry::TEX_GEN_PLANAR)
			{
				v.mV[s_axis] = s*scale.mV[s_axis];
				v.mV[t_axis] = t*scale.mV[t_axis];
			}
			else
			{
				v.mV[s_axis] = s/scale.mV[s_axis];
				v.mV[t_axis] = t/scale.mV[t_axis];
			}

			mTextureScaleRatios.push_back(v);
		}
	}
}


// This implementation should be similar to LLTask::allowOperationOnTask
BOOL LLSelectNode::allowOperationOnNode(PermissionBit op, U64 group_proxy_power) const
{
	// Extract ownership.
	BOOL object_is_group_owned = FALSE;
	LLUUID object_owner_id;
	mPermissions->getOwnership(object_owner_id, object_is_group_owned);

	// Operations on invalid or public objects is not allowed.
	if (!mObject || (mObject->isDead()) || !mPermissions->isOwned())
	{
		return FALSE;
	}

	// The transfer permissions can never be given through proxy.
	if (PERM_TRANSFER == op)
	{
		// The owner of an agent-owned object can transfer to themselves.
		if ( !object_is_group_owned 
			&& (gAgent.getID() == object_owner_id) )
		{
			return TRUE;
		}
		else
		{
			// Otherwise check aggregate permissions.
			return mObject->permTransfer();
		}
	}

	if (PERM_MOVE == op
		|| PERM_MODIFY == op)
	{
		// only owners can move or modify their attachments
		// no proxy allowed.
		if (mObject->isAttachment() && object_owner_id != gAgent.getID())
		{
			return FALSE;
		}
	}

	// Calculate proxy_agent_id and group_id to use for permissions checks.
	// proxy_agent_id may be set to the object owner through group powers.
	// group_id can only be set to the object's group, if the agent is in that group.
	LLUUID group_id = LLUUID::null;
	LLUUID proxy_agent_id = gAgent.getID();

	// Gods can always operate.
	if (gAgent.isGodlike())
	{
		return TRUE;
	}

	// Check if the agent is in the same group as the object.
	LLUUID object_group_id = mPermissions->getGroup();
	if (object_group_id.notNull() &&
		gAgent.isInGroup(object_group_id))
	{
		// Assume the object's group during this operation.
		group_id = object_group_id;
	}

	// Only allow proxy powers for PERM_COPY if the actual agent can
	// receive the item (ie has PERM_TRANSFER permissions).
	// NOTE: op == PERM_TRANSFER has already been handled, but if
	// that ever changes we need to BLOCK proxy powers for PERM_TRANSFER.  DK 03/28/06
	if (PERM_COPY != op || mPermissions->allowTransferTo(gAgent.getID()))
	{
		// Check if the agent can assume ownership through group proxy or agent-granted proxy.
		if (   ( object_is_group_owned 
				&& gAgent.hasPowerInGroup(object_owner_id, group_proxy_power))
				// Only allow proxy for move, modify, and copy.
				|| ( (PERM_MOVE == op || PERM_MODIFY == op || PERM_COPY == op)
					&& (!object_is_group_owned
						&& gAgent.isGrantedProxy(*mPermissions))))
		{
			// This agent is able to assume the ownership role for this operation.
			proxy_agent_id = object_owner_id;
		}
	}
	
	// We now have max ownership information.
	if (PERM_OWNER == op)
	{
		// This this was just a check for ownership, we can now return the answer.
		return (proxy_agent_id == object_owner_id ? TRUE : FALSE);
	}

	// check permissions to see if the agent can operate
	return (mPermissions->allowOperationBy(op, proxy_agent_id, group_id));
}

//-----------------------------------------------------------------------------
// renderOneSilhouette()
//-----------------------------------------------------------------------------
void LLSelectNode::renderOneSilhouette(const LLColor4 &color)
{
	LLViewerObject* objectp = getObject();
	if (!objectp)
	{
		return;
	}

	LLDrawable* drawable = objectp->mDrawable;
	if(!drawable)
	{
		return;
	}

	if (!mSilhouetteExists)
	{
		return;
	}

	BOOL is_hud_object = objectp->isHUDAttachment();

	if (!drawable->isVisible() && !is_hud_object)
	{
		return;
	}

	if (mSilhouetteVertices.size() == 0 || mSilhouetteNormals.size() != mSilhouetteVertices.size())
	{
		return;
	}

	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	if (!is_hud_object)
	{
		glLoadIdentity();
		glMultMatrixd(gGLModelView);
	}
	
	
	if (drawable->isActive())
	{
		glMultMatrixf((F32*) objectp->getRenderMatrix().mMatrix);
	}

	LLVolume *volume = objectp->getVolume();
	if (volume)
	{
		F32 silhouette_thickness;
		if (is_hud_object && gAgent.getAvatarObject())
		{
			silhouette_thickness = LLSelectMgr::sHighlightThickness / gAgent.getAvatarObject()->mHUDCurZoom;
		}
		else
		{
			LLVector3 view_vector = gCamera->getOrigin() - objectp->getRenderPosition();
			silhouette_thickness = drawable->mDistanceWRTCamera * LLSelectMgr::sHighlightThickness * (gCamera->getView() / gCamera->getDefaultFOV());
		}		
		F32 animationTime = (F32)LLFrameTimer::getElapsedSeconds();

		F32 u_coord = fmod(animationTime * LLSelectMgr::sHighlightUAnim, 1.f);
		F32 v_coord = 1.f - fmod(animationTime * LLSelectMgr::sHighlightVAnim, 1.f);
		F32 u_divisor = 1.f / ((F32)(mSilhouetteVertices.size() - 1));

		if (LLSelectMgr::sRenderHiddenSelections) // && gFloaterTools && gFloaterTools->getVisible())
		{
			glBlendFunc(GL_SRC_COLOR, GL_ONE);
			LLGLEnable fog(GL_FOG);
			glFogi(GL_FOG_MODE, GL_LINEAR);
			float d = (gCamera->getPointOfInterest()-gCamera->getOrigin()).magVec();
			LLColor4 fogCol = color * (F32)llclamp((gSelectMgr->getSelectionCenterGlobal()-gAgent.getCameraPositionGlobal()).magVec()/(gSelectMgr->getBBoxOfSelection().getExtentLocal().magVec()*4), 0.0, 1.0);
			glFogf(GL_FOG_START, d);
			glFogf(GL_FOG_END, d*(1 + (gCamera->getView() / gCamera->getDefaultFOV())));
			glFogfv(GL_FOG_COLOR, fogCol.mV);

			LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE, GL_GEQUAL);
			glAlphaFunc(GL_GREATER, 0.01f);
			glBegin(GL_LINES);
			{
				S32 i = 0;
				for (S32 seg_num = 0; seg_num < (S32)mSilhouetteSegments.size(); seg_num++)
				{
// 					S32 first_i = i;
					for(; i < mSilhouetteSegments[seg_num]; i++)
					{
						u_coord += u_divisor * LLSelectMgr::sHighlightUScale;

						glColor4f(color.mV[VRED], color.mV[VGREEN], color.mV[VBLUE], 0.4f);
						glTexCoord2f( u_coord, v_coord );
						glVertex3fv( mSilhouetteVertices[i].mV );
					}

					/*u_coord += u_divisor * LLSelectMgr::sHighlightUScale;
					glColor4f(color.mV[VRED], color.mV[VGREEN], color.mV[VBLUE], 0.4f);
					glTexCoord2f( u_coord, v_coord );
					glVertex3fv( mSilhouetteVertices[first_i].mV );*/
				}
			}
            glEnd();
			u_coord = fmod(animationTime * LLSelectMgr::sHighlightUAnim, 1.f);
		}

		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		//glAlphaFunc(GL_GREATER, LLSelectMgr::sHighlightAlphaTest);
		glBegin(GL_TRIANGLES);
		{
			S32 i = 0;
			for (S32 seg_num = 0; seg_num < (S32)mSilhouetteSegments.size(); seg_num++)
			{
				S32 first_i = i;
				LLVector3 v;
				LLVector2 t;

				for(; i < mSilhouetteSegments[seg_num]; i++)
				{

					if (i == first_i) {
					    LLVector3 vert = (mSilhouetteNormals[i]) * silhouette_thickness;
						vert += mSilhouetteVertices[i];

						glColor4f(color.mV[VRED], color.mV[VGREEN], color.mV[VBLUE], 0.0f); //LLSelectMgr::sHighlightAlpha);
						glTexCoord2f( u_coord, v_coord + LLSelectMgr::sHighlightVScale );
						glVertex3fv( vert.mV ); 
						
						u_coord += u_divisor * LLSelectMgr::sHighlightUScale;

						glColor4f(color.mV[VRED]*2, color.mV[VGREEN]*2, color.mV[VBLUE]*2, LLSelectMgr::sHighlightAlpha*2);
						glTexCoord2f( u_coord, v_coord );
						glVertex3fv( mSilhouetteVertices[i].mV );

						v = mSilhouetteVertices[i];
						t = LLVector2(u_coord, v_coord);
					}
					else {
                        LLVector3 vert = (mSilhouetteNormals[i]) * silhouette_thickness;
						vert += mSilhouetteVertices[i];

						glColor4f(color.mV[VRED], color.mV[VGREEN], color.mV[VBLUE], 0.0f); //LLSelectMgr::sHighlightAlpha);
						glTexCoord2f( u_coord, v_coord + LLSelectMgr::sHighlightVScale );
						glVertex3fv( vert.mV ); 
						glVertex3fv( vert.mV ); 
						
						glTexCoord2fv(t.mV);
						u_coord += u_divisor * LLSelectMgr::sHighlightUScale;
						glColor4f(color.mV[VRED]*2, color.mV[VGREEN]*2, color.mV[VBLUE]*2, LLSelectMgr::sHighlightAlpha*2);
						glVertex3fv(v.mV);
						glTexCoord2f( u_coord, v_coord );
						glVertex3fv( mSilhouetteVertices[i].mV );

					}
				}
			}
		}
		glEnd();

	}
	glPopMatrix();
}

//
// Utility Functions
//

// Update everyone who cares about the selection list
void dialog_refresh_all()
{
	if (gNoRender)
	{
		return;
	}

	//could refresh selected object info in toolbar here

	gFloaterTools->dirty();

	if( gPieObject->getVisible() )
	{
		gPieObject->arrange();
	}

	LLFloaterProperties::dirtyAll();
}

S32 get_family_count(LLViewerObject *parent)
{
	if (!parent)
	{
		llwarns << "Trying to get_family_count on null parent!" << llendl;
	}
	S32 count = 1;	// for this object
	for (U32 i = 0; i < parent->mChildList.size(); i++)
	{
		LLViewerObject* child = parent->mChildList[i];

		if (!child)
		{
			llwarns << "Family object has NULL child!  Show Doug." << llendl;
		}
		else if (child->isDead())
		{
			llwarns << "Family object has dead child object.  Show Doug." << llendl;
		}
		else
		{
			if (gSelectMgr->canSelectObject(child))
			{
				count += get_family_count( child );
			}
		}
	}
	return count;
}

//-----------------------------------------------------------------------------
// updateSelectionCenter
//-----------------------------------------------------------------------------
void LLSelectMgr::updateSelectionCenter()
{
	const F32 MOVE_SELECTION_THRESHOLD = 1.f;		//  Movement threshold in meters for updating selection
													//  center (tractor beam)

	LLViewerObject* object = mSelectedObjects.getFirstObject();
	if (!object)
	{
		// nothing selected, probably grabbing
		// Ignore by setting to avatar origin.
		mSelectionCenterGlobal.clearVec();
		mShowSelection = FALSE;
		mSelectionBBox = LLBBox(); 
		mPauseRequest = NULL;
		if (gAgent.getAvatarObject())
		{
			gAgent.getAvatarObject()->mHUDTargetZoom = 1.f;
			gAgent.getAvatarObject()->mHUDCurZoom = 1.f;
		}
	}
	else
	{
		mSelectType = getSelectTypeForObject(object);

		if (mSelectType == SELECT_TYPE_ATTACHMENT && gAgent.getAvatarObject())
		{
			mPauseRequest = gAgent.getAvatarObject()->requestPause();
		}
		else
		{
			mPauseRequest = NULL;
		}

		if (mSelectType != SELECT_TYPE_HUD && gAgent.getAvatarObject())
		{
			// reset hud ZOOM
			gAgent.getAvatarObject()->mHUDTargetZoom = 1.f;
			gAgent.getAvatarObject()->mHUDCurZoom = 1.f;
		}

		mShowSelection = FALSE;
		LLBBox bbox;

		// have stuff selected
		LLVector3d select_center;
		// keep a list of jointed objects for showing the joint HUDEffects
		gHUDManager->clearJoints();
		LLDynamicArray < LLViewerObject *> jointed_objects;

		for (object = mSelectedObjects.getFirstObject(); object; object = mSelectedObjects.getNextObject() )
		{
			LLViewerObject *myAvatar = gAgent.getAvatarObject();
			LLViewerObject *root = object->getRootEdit();
			if (mSelectType == SELECT_TYPE_WORLD && // not an attachment
				!root->isChild(myAvatar) && // not the object you're sitting on
				!object->isAvatar()) // not another avatar
			{
				mShowSelection = TRUE;
			}

			bbox.addBBoxAgent( object->getBoundingBoxAgent() );

			if (object->isJointChild())
			{
				jointed_objects.put(object);
			}
		} // end for
		
		LLVector3 bbox_center_agent = bbox.getCenterAgent();
		mSelectionCenterGlobal = gAgent.getPosGlobalFromAgent(bbox_center_agent);
		mSelectionBBox = bbox;

		if (jointed_objects.count())
		{
			gHUDManager->showJoints(&jointed_objects);
		}
	}
	
	if ( !(gAgentID == LLUUID::null) ) 
	{
		LLTool		*tool = gToolMgr->getCurrentTool( gKeyboard->currentMask(TRUE) );
		if (mShowSelection)
		{
			LLVector3d select_center_global;

			if( tool->isEditing() )
			{
				select_center_global = tool->getEditingPointGlobal();
			}
			else
			{
				select_center_global = mSelectionCenterGlobal;
			}

			// Send selection center if moved beyond threshold (used to animate tractor beam)	
			LLVector3d diff;
			diff = select_center_global - mLastSentSelectionCenterGlobal;

			if ( diff.magVecSquared() > MOVE_SELECTION_THRESHOLD*MOVE_SELECTION_THRESHOLD )
			{
				//  Transmit updated selection center 
				mLastSentSelectionCenterGlobal = select_center_global;
			}
		}
	}

	// give up edit menu if no objects selected
	if (gEditMenuHandler == this && getObjectCount() == 0)
	{
		gEditMenuHandler = NULL;
	}
}

void LLSelectMgr::updatePointAt()
{
	if (mShowSelection)
	{
		if (getObjectCount())
		{					
			LLVector3 select_offset;
			LLViewerObject *click_object = gObjectList.findObject(gLastHitObjectID);
			if (click_object && click_object->isSelected())
			{
				// clicked on another object in our selection group, use that as target
				select_offset.setVec(gLastHitObjectOffset);
				select_offset.rotVec(~click_object->getRenderRotation());
		
				gAgent.setPointAt(POINTAT_TARGET_SELECT, click_object, select_offset);
				gAgent.setLookAt(LOOKAT_TARGET_SELECT, click_object, select_offset);
			}
			else
			{
				// didn't click on an object this time, revert to pointing at center of first object
				gAgent.setPointAt(POINTAT_TARGET_SELECT, getFirstObject());
				gAgent.setLookAt(LOOKAT_TARGET_SELECT, getFirstObject());
			}
		}
		else
		{
			gAgent.setPointAt(POINTAT_TARGET_CLEAR);
			gAgent.setLookAt(LOOKAT_TARGET_CLEAR);
		}
	}
	else
	{
		gAgent.setPointAt(POINTAT_TARGET_CLEAR);
		gAgent.setLookAt(LOOKAT_TARGET_CLEAR);
	}
}

//-----------------------------------------------------------------------------
// getBBoxOfSelection()
//-----------------------------------------------------------------------------
LLBBox LLSelectMgr::getBBoxOfSelection() const
{
	return mSelectionBBox;
}


//-----------------------------------------------------------------------------
// canUndo()
//-----------------------------------------------------------------------------
BOOL LLSelectMgr::canUndo()
{
	return getFirstEditableObject() != NULL;
}

//-----------------------------------------------------------------------------
// undo()
//-----------------------------------------------------------------------------
void LLSelectMgr::undo()
{
	BOOL select_linked_set = gSavedSettings.getBOOL("SelectLinkedSet");
	LLUUID group_id(gAgent.getGroupID());
	sendListToRegions("Undo", packAgentAndSessionAndGroupID, packObjectID, &group_id, select_linked_set ? SEND_ONLY_ROOTS : SEND_CHILDREN_FIRST);
}

//-----------------------------------------------------------------------------
// canRedo()
//-----------------------------------------------------------------------------
BOOL LLSelectMgr::canRedo()
{
	return getFirstEditableObject() != NULL;
}

//-----------------------------------------------------------------------------
// redo()
//-----------------------------------------------------------------------------
void LLSelectMgr::redo()
{
	BOOL select_linked_set = gSavedSettings.getBOOL("SelectLinkedSet");
	LLUUID group_id(gAgent.getGroupID());
	sendListToRegions("Redo", packAgentAndSessionAndGroupID, packObjectID, &group_id, select_linked_set ? SEND_ONLY_ROOTS : SEND_CHILDREN_FIRST);
}

//-----------------------------------------------------------------------------
// canDoDelete()
//-----------------------------------------------------------------------------
BOOL LLSelectMgr::canDoDelete()
{
	return getFirstDeleteableObject() != NULL;
}

//-----------------------------------------------------------------------------
// doDelete()
//-----------------------------------------------------------------------------
void LLSelectMgr::doDelete()
{
	selectDelete();
}

//-----------------------------------------------------------------------------
// canDeselect()
//-----------------------------------------------------------------------------
BOOL LLSelectMgr::canDeselect()
{
	return !isEmpty();
}

//-----------------------------------------------------------------------------
// deselect()
//-----------------------------------------------------------------------------
void LLSelectMgr::deselect()
{
	deselectAll();
}
//-----------------------------------------------------------------------------
// canDuplicate()
//-----------------------------------------------------------------------------
BOOL LLSelectMgr::canDuplicate()
{
	return getFirstCopyableObject() != NULL;
}
//-----------------------------------------------------------------------------
// duplicate()
//-----------------------------------------------------------------------------
void LLSelectMgr::duplicate()
{
	LLVector3 offset(0.5f, 0.5f, 0.f);
	selectDuplicate(offset, TRUE);
}
//-----------------------------------------------------------------------------
// undoRedo()
//-----------------------------------------------------------------------------
U32 LLSelectMgr::undoRedo(std::deque<LLSelectAction*> &queue_src, std::deque<LLSelectAction*> &queue_dst, const LLUUID &object_id)
{
	if (queue_src.size() == 0)
	{
		return 0;
	}

	U32 update_type = 0;
	std::deque<LLSelectAction*> temp_queue;
	LLSelectAction* src_actionp = queue_src.back();

	while (src_actionp->mObjectID != object_id)
	{
		temp_queue.push_back(src_actionp);
		queue_src.pop_back();
		if (!queue_src.size())
		{
			// put everything back
			LLSelectAction* actionp;
			while (temp_queue.size())
			{
				actionp = temp_queue.back();
				temp_queue.pop_back();
				queue_src.push_back(actionp);
			}
			return 0;
		}
		src_actionp = queue_src.back();
	}

	if(src_actionp)
	{
		LLSelectAction* dst_actionp = new LLSelectAction();
		dst_actionp->mActionType = src_actionp->mActionType;
		dst_actionp->mObjectID = src_actionp->mObjectID;
		dst_actionp->mIndividualSelection = src_actionp->mIndividualSelection;

		LLViewerObject* object = gObjectList.findObject(src_actionp->mObjectID);
		if (object && object->mDrawable.notNull())
		{
			LLVector3 old_position_local = object->getPosition();

			switch(src_actionp->mActionType)
			{
			case SELECT_ACTION_TYPE_MOVE:
				dst_actionp->mPosition = object->mDrawable->getPosition();
				object->setPosition(src_actionp->mPosition, TRUE);
				if (object->isRootEdit() && src_actionp->mIndividualSelection)
				{
					// counter-translate children
					LLVector3 parent_offset = (src_actionp->mPosition - old_position_local) * ~object->getRotation();

					// counter-translate child objects if we are moving the root as an individual
					for (U32 child_num = 0; child_num < object->mChildList.size(); child_num++)
					{
						LLViewerObject* childp = object->mChildList[child_num];
						childp->setPosition(childp->getPosition() - parent_offset);
					}
				}
				update_type |= UPD_POSITION;
				break;
			case SELECT_ACTION_TYPE_ROTATE:
				dst_actionp->mPosition = object->mDrawable->getPosition();
				dst_actionp->mRotation = object->mDrawable->getRotation();
				object->setRotation(src_actionp->mRotation, TRUE);
				object->setPosition(src_actionp->mPosition, TRUE);
				if (object->isRootEdit() && src_actionp->mIndividualSelection)
				{
					// counter-translate and rotate children
					LLVector3 parent_offset = (src_actionp->mPosition - old_position_local) * ~object->getRotation();

					for (U32 child_num = 0; child_num < object->mChildList.size(); child_num++)
					{
						LLViewerObject* childp = object->mChildList[child_num];
						LLQuaternion delta_rot_inv = dst_actionp->mRotation * ~src_actionp->mRotation;
						childp->setPosition((childp->getPosition() * delta_rot_inv) - parent_offset);
						childp->setRotation(childp->getRotation() * delta_rot_inv );
					}
				}
				update_type |= UPD_ROTATION | UPD_POSITION;
				break;
			case SELECT_ACTION_TYPE_SCALE:
				dst_actionp->mPosition = object->mDrawable->getPosition();
				dst_actionp->mScale = object->getScale();
				object->setScale(src_actionp->mScale, TRUE);
				object->setPosition(src_actionp->mPosition, TRUE);
				update_type |= UPD_SCALE | UPD_POSITION;
				break;
			default:
				// do nothing
				break;
			}
		}
		queue_src.pop_back();
		delete src_actionp;
		queue_dst.push_back(dst_actionp);
		while (queue_dst.size() > (U32)MAX_ACTION_QUEUE_SIZE)
		{
			LLSelectAction* action = queue_dst.front();
			queue_dst.pop_front();
			delete action;
		}

	}

	// put everything back
	LLSelectAction* actionp;
	while (temp_queue.size())
	{
		actionp = temp_queue.back();
		temp_queue.pop_back();
		queue_src.push_back(actionp);
	}

	return update_type;
}

ESelectType LLSelectMgr::getSelectTypeForObject(LLViewerObject* object)
{
	if (!object)
	{
		return SELECT_TYPE_WORLD;
	}
	if (object->isHUDAttachment())
	{
		return SELECT_TYPE_HUD;
	}
	else if (object->isAttachment())
	{
		return SELECT_TYPE_ATTACHMENT;
	}
	else
	{
		return SELECT_TYPE_WORLD;
	}
}

void LLSelectMgr::validateSelection()
{
	LLViewerObject* objectp;
	for (objectp = getFirstObject(); objectp; objectp = getNextObject())
	{
		if (!canSelectObject(objectp))
		{
			deselectObjectOnly(objectp);
		}
	}
}

BOOL LLSelectMgr::canSelectObject(LLViewerObject* object)
{
	if (mForceSelection)
	{
		return TRUE;
	}

	if ((gSavedSettings.getBOOL("SelectOwnedOnly") && !object->permYouOwner()) ||
		(gSavedSettings.getBOOL("SelectMovableOnly") && !object->permMove()))
	{
		// only select my own objects
		return FALSE;
	}

	// Can't select dead objects
	if (object->isDead()) return FALSE;

	// Can't select orphans
	if (object->isOrphaned()) return FALSE;
	
	// Can't select avatars
	if (object->isAvatar()) return FALSE;

	// Can't select land
	if (object->getPCode() == LLViewerObject::LL_VO_SURFACE_PATCH) return FALSE;

	ESelectType selection_type = getSelectTypeForObject(object);
	if (getObjectCount() > 0 && mSelectType != selection_type) return FALSE;

	return TRUE;
}

LLSelectNodeList::LLSelectNodeList() : std::list<LLSelectNode*>()
{
	mCurrentTE = -1;
	mCurrentNode = end();
}

LLSelectNodeList::~LLSelectNodeList()
{
	std::for_each(begin(), end(), DeletePointer());
}

void LLSelectNodeList::updateEffects()
{
}

S32 LLSelectNodeList::getNumNodes()
{
	return size();
}

void LLSelectNodeList::addNode(LLSelectNode *nodep)
{
	push_front(nodep);
	mSelectNodeMap[nodep->getObject()] = nodep;
}

void LLSelectNodeList::addNodeAtEnd(LLSelectNode *nodep)
{
	push_back(nodep);
	mSelectNodeMap[nodep->getObject()] = nodep;
}

void LLSelectNodeList::removeNode(LLSelectNode *nodep)
{
	std::list<LLSelectNode*>::iterator iter;
	for (iter = begin(); iter != end(); ++iter)
	{
		if ((*iter) == nodep)
		{
			mSelectNodeMap.erase(nodep->getObject());
			erase(iter++);
		}
	}
}

void LLSelectNodeList::deleteAllNodes()
{
	std::for_each(begin(), end(), DeletePointer());
	clear();
	mSelectNodeMap.clear();
}

LLSelectNode* LLSelectNodeList::findNode(LLViewerObject* objectp)
{
	std::map<LLViewerObject*, LLSelectNode*>::iterator found_it = mSelectNodeMap.find(objectp);
	if (found_it != mSelectNodeMap.end())
	{
		return found_it->second;
	}
	return NULL;
}

//-----------------------------------------------------------------------------
// getFirstNode()
//-----------------------------------------------------------------------------
LLSelectNode *LLSelectNodeList::getFirstNode()
{
	mCurrentNode = begin();//getFirstData();

	while (mCurrentNode != end() && !(*mCurrentNode)->getObject())
	{
		// The object on this was killed at some point, delete it.
		erase(mCurrentNode++);
	}

	if (mCurrentNode != end())
	{
		return *mCurrentNode;
	}
	
	return NULL;
}

//-----------------------------------------------------------------------------
// getCurrentNode()
//-----------------------------------------------------------------------------
LLSelectNode *LLSelectNodeList::getCurrentNode()
{
	while (mCurrentNode != end() && !(*mCurrentNode)->getObject())
	{
		// The object on this was killed at some point, delete it.
		erase(mCurrentNode++);
	}

	if (mCurrentNode != end())
	{
		return *mCurrentNode;
	}
	return NULL;
}

//-----------------------------------------------------------------------------
// getNextNode()
//-----------------------------------------------------------------------------
LLSelectNode *LLSelectNodeList::getNextNode()
{
	++mCurrentNode;

	while (mCurrentNode != end() && !(*mCurrentNode)->getObject())
	{
		// The object on this was killed at some point, delete it.
		erase(mCurrentNode++);
	}

	if (mCurrentNode != end())
	{
		return *mCurrentNode;
	}
	return NULL;
}



//-----------------------------------------------------------------------------
// getFirstObject()
//-----------------------------------------------------------------------------
LLViewerObject* LLSelectNodeList::getFirstObject()
{
	mCurrentNode = begin();

	while (mCurrentNode != end() && !(*mCurrentNode)->getObject())
	{
		// The object on this was killed at some point, delete it.
		erase(mCurrentNode++);
	}

	if (mCurrentNode != end())
	{
		return (*mCurrentNode)->getObject();
	}

	return NULL;
}


//-----------------------------------------------------------------------------
// getNextObject()
//-----------------------------------------------------------------------------
LLViewerObject* LLSelectNodeList::getNextObject()
{
	++mCurrentNode;// = getNextData();

	while (mCurrentNode != end() && !(*mCurrentNode)->getObject())
	{
		// The object on this was killed at some point, delete it.
		erase(mCurrentNode++);
	}

	if (mCurrentNode != end())
	{
		return (*mCurrentNode)->getObject();
	}

	return NULL;
}



//-----------------------------------------------------------------------------
// getPrimaryTE()
//-----------------------------------------------------------------------------
void LLSelectNodeList::getPrimaryTE(LLViewerObject* *object, S32 *te)
{
	// initialize object and te
	*te = 0;
	*object = NULL;

	BOOL searching_roots = TRUE;

	// try for root node first, then first child node
	LLSelectNode *primary_node = getFirstNode(); //getFirstRootNode();
	if (!primary_node)
	{
		primary_node = getFirstNode();
		searching_roots = FALSE;
	}

	while (primary_node)
	{
		S32 last_selected_te = primary_node->getLastSelectedTE();
		if (last_selected_te >= 0)
		{
			*object = primary_node->getObject();
			*te = last_selected_te;
			return;
		}
		for(S32 cur_te = 0; cur_te < primary_node->getObject()->getNumTEs(); cur_te++)
		{
			// if face selected
			if (primary_node->isTESelected(cur_te))
			{
				// return this object and face
				*object = primary_node->getObject();
				*te = cur_te;
				return;
			}
		}
		if (searching_roots)
		{
			primary_node = getNextRootNode();
			if (!primary_node)
			{
				primary_node = getFirstNode();
				searching_roots = FALSE;
			}
		}
		else
		{
			primary_node = getNextNode();
		}
	}
}

//-----------------------------------------------------------------------------
// getFirstTE()
//-----------------------------------------------------------------------------
void LLSelectNodeList::getFirstTE(LLViewerObject* *object, S32 *te)
{
	// start with first face
	mCurrentTE = 0;

	LLSelectNode *cur_node = getFirstNode();

	// repeat over all selection nodes
	while (cur_node)
	{
		// skip objects with no faces
		if (cur_node->getObject()->getNumTEs() == 0)
		{
			mCurrentTE = 0;
			cur_node = getNextNode();
			continue;
		}

		// repeat over all faces for this object
		while (mCurrentTE < cur_node->getObject()->getNumTEs())
		{
			// if face selected
			if (cur_node->isTESelected(mCurrentTE))
			{
				// return this object and face
				*object = cur_node->getObject();
				*te = mCurrentTE;
				return;
			}

			mCurrentTE++;
		}

		// Couldn't find a selected face.
		// This can happen if an object's volume parameters are changed in such a way
		// that texture entries are eliminated.
		//
		// TODO: Consider selecting all faces in this case?  Subscribe the selection
		// list to the volume changing code?

		mCurrentTE = 0;
		cur_node = getNextNode();
	}

	// The list doesn't contain any nodes.  Return NULL.
	*object = NULL;
	*te = -1;
	return;
}


//-----------------------------------------------------------------------------
// getNextFace()
//-----------------------------------------------------------------------------
void LLSelectNodeList::getNextTE(LLViewerObject* *object, S32 *te)
{
	// try next face
	mCurrentTE++;

	LLSelectNode *cur_node = getCurrentNode();
	// repeat over remaining selection nodes
	while ( cur_node )
	{
		// skip objects with no faces
		if (cur_node->getObject()->getNumTEs() == 0)
		{
			mCurrentTE = 0;
			cur_node = getNextNode();
			continue;
		}

		// repeat over all faces for this object
		// CRO: getNumTEs() no longer equals mFaces.count(), so use mFaces.count() instead
		while ( mCurrentTE < cur_node->getObject()->getNumTEs() )
		{
			// if face selected
			if (cur_node->isTESelected(mCurrentTE))
			{
				// return this object and face
				*object = cur_node->getObject();
				*te = mCurrentTE;
				return;
			}

			mCurrentTE++;
		}

		mCurrentTE = 0;
		cur_node = getNextNode();
	}

	// The list doesn't contain any nodes.  Return NULL.
	*object = NULL;
	*te = -1;
	return;
}

void LLSelectNodeList::getCurrentTE(LLViewerObject* *object, S32 *te)
{
	if (mCurrentNode != end())
	{
		*object = (*mCurrentNode)->getObject();
		*te = mCurrentTE;
	}
	else
	{
		*object = NULL;
		*te = -1;
	}
}
//-----------------------------------------------------------------------------
// getFirstRootNode()
//-----------------------------------------------------------------------------
LLSelectNode *LLSelectNodeList::getFirstRootNode()
{
	LLSelectNode *cur_node = getFirstNode();

	// scan through child objects and roots set to ignore
	while (cur_node && 
				(!(cur_node->getObject()->isRootEdit() || cur_node->getObject()->isJointChild()) ||
					cur_node->mIndividualSelection))
	{
		cur_node = getNextNode();
	}

	return cur_node;
}


//-----------------------------------------------------------------------------
// getNextRootNode()
//-----------------------------------------------------------------------------
LLSelectNode *LLSelectNodeList::getNextRootNode()
{
	LLSelectNode *cur_node = getNextNode();

	while (cur_node && 
				(!(cur_node->getObject()->isRootEdit() || cur_node->getObject()->isJointChild()) ||
					cur_node->mIndividualSelection))
	{
		cur_node = getNextNode();
	}

	return cur_node;
}


//-----------------------------------------------------------------------------
// getFirstRootObject()
//-----------------------------------------------------------------------------
LLViewerObject *LLSelectNodeList::getFirstRootObject()
{
	LLSelectNode *node = getFirstRootNode();

	if (node)
	{
		return node->getObject();
	}
	else
	{
		return NULL;
	}
}


//-----------------------------------------------------------------------------
// getNextRootObject()
//-----------------------------------------------------------------------------
LLViewerObject *LLSelectNodeList::getNextRootObject()
{
	LLSelectNode *node = getNextRootNode();

	if (node)
	{
		return node->getObject();
	}
	else
	{
		return NULL;
	}
}

//-----------------------------------------------------------------------------
// isEmpty()
//-----------------------------------------------------------------------------
BOOL LLSelectNodeList::isEmpty()
{
	return (size() == 0);
}

//-----------------------------------------------------------------------------
// getOwnershipCost()
//-----------------------------------------------------------------------------
BOOL LLSelectNodeList::getOwnershipCost(S32 &cost)
{
	S32 count = 0;
	for( LLSelectNode* nodep = getFirstNode(); nodep; nodep = getNextNode() )
	{
		count++;
	}

	cost = count * OWNERSHIP_COST_PER_OBJECT;

	return (count > 0);
}
