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

#include "llviewerprecompiledheaders.h"

// file include
#define LLSELECTMGR_CPP
#include "llselectmgr.h"
#include "llmaterialmgr.h"

// library includes
#include "llcachename.h"
#include "llavatarnamecache.h"
#include "lldbstrings.h"
#include "llgl.h"
#include "llmediaentry.h"
#include "llrender.h"
#include "llnotifications.h"
#include "llpermissions.h"
#include "llpermissionsflags.h"
#include "lltrans.h"
#include "llundo.h"
#include "lluuid.h"
#include "llvolume.h"
#include "llcontrolavatar.h"
#include "message.h"
#include "object_flags.h"
#include "llquaternion.h"

// viewer includes
#include "llagent.h"
#include "llagentcamera.h"
#include "llattachmentsmgr.h"
#include "llviewerwindow.h"
#include "lldrawable.h"
#include "llfloatergltfasseteditor.h"
#include "llfloaterinspect.h"
#include "llfloaterreporter.h"
#include "llfloaterreg.h"
#include "llfloatertools.h"
#include "llframetimer.h"
#include "llfocusmgr.h"
#include "llgltfmateriallist.h"
#include "llhudeffecttrail.h"
#include "llhudmanager.h"
#include "llinventorymodel.h"
#include "llmenugl.h"
#include "llmeshrepository.h"
#include "llmutelist.h"
#include "llnotificationsutil.h"
#include "llsidepaneltaskinfo.h"
#include "llslurl.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 "llviewertexturelist.h"
#include "llviewermedia.h"
#include "llviewermediafocus.h"
#include "llviewermenu.h"
#include "llviewerobject.h"
#include "llviewerobjectlist.h"
#include "llviewerregion.h"
#include "llviewerstats.h"
#include "llvoavatarself.h"
#include "llvovolume.h"
#include "pipeline.h"
#include "llviewershadermgr.h"
#include "llpanelface.h"
#include "llglheaders.h"
#include "llinventoryobserver.h"

LLViewerObject* getSelectedParentObject(LLViewerObject *object) ;
//
// Consts
//

constexpr F32 SILHOUETTE_UPDATE_THRESHOLD_SQUARED = 0.02f;
constexpr S32 MAX_SILS_PER_FRAME = 50;
constexpr S32 MAX_OBJECTS_PER_PACKET = 254;
// For linked sets
constexpr S32 MAX_CHILDREN_PER_TASK = 255;


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;
LLUIColor LLSelectMgr::sSilhouetteParentColor;
LLUIColor LLSelectMgr::sSilhouetteChildColor;
LLUIColor LLSelectMgr::sHighlightInspectColor;
LLUIColor LLSelectMgr::sHighlightParentColor;
LLUIColor LLSelectMgr::sHighlightChildColor;
LLUIColor 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
//

//-----------------------------------------------------------------------------
// ~LLSelectionCallbackData()
//-----------------------------------------------------------------------------

LLSelectionCallbackData::LLSelectionCallbackData()
{
    LLSelectMgr *instance = LLSelectMgr::getInstance();
    LLObjectSelectionHandle selection = instance->getSelection();
    if (!selection->getNumNodes())
    {
        return;
    }
    mSelectedObjects = new LLObjectSelection();

    for (LLObjectSelection::iterator iter = selection->begin();
        iter != selection->end();)
    {
        LLObjectSelection::iterator curiter = iter++;

        LLSelectNode *nodep = *curiter;
        LLViewerObject* objectp = nodep->getObject();

        if (!objectp)
        {
            mSelectedObjects->mSelectType = SELECT_TYPE_WORLD;
        }
        else
        {
            LLSelectNode* new_nodep = new LLSelectNode(*nodep);
            mSelectedObjects->addNode(new_nodep);

            if (objectp->isHUDAttachment())
            {
                mSelectedObjects->mSelectType = SELECT_TYPE_HUD;
            }
            else if (objectp->isAttachment())
            {
                mSelectedObjects->mSelectType = SELECT_TYPE_ATTACHMENT;
            }
            else
            {
                mSelectedObjects->mSelectType = SELECT_TYPE_WORLD;
            }
        }
    }
}


//
// Functions
//

void LLSelectMgr::cleanupGlobals()
{
    LLSelectMgr::getInstance()->clearSelections();
}

//-----------------------------------------------------------------------------
// LLSelectMgr()
//-----------------------------------------------------------------------------
LLSelectMgr::LLSelectMgr()
 : mHideSelectedObjects(LLCachedControl<bool>(gSavedSettings, "HideSelectedObjects", false)),
   mRenderHighlightSelections(LLCachedControl<bool>(gSavedSettings, "RenderHighlightSelections", true)),
   mAllowSelectAvatar( LLCachedControl<bool>(gSavedSettings, "AllowSelectAvatar", false)),
   mDebugSelectMgr(LLCachedControl<bool>(gSavedSettings, "DebugSelectMgr", false))
{
    mTEMode = false;
    mTextureChannel = LLRender::DIFFUSE_MAP;
    mLastCameraPos.clearVec();

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

    sSilhouetteParentColor =LLUIColorTable::instance().getColor("SilhouetteParentColor");
    sSilhouetteChildColor = LLUIColorTable::instance().getColor("SilhouetteChildColor");
    sHighlightParentColor = LLUIColorTable::instance().getColor("HighlightParentColor");
    sHighlightChildColor = LLUIColorTable::instance().getColor("HighlightChildColor");
    sHighlightInspectColor = LLUIColorTable::instance().getColor("HighlightInspectColor");
    sContextSilhouetteColor = LLUIColorTable::instance().getColor("ContextSilhouetteColor")*0.5f;

    sRenderLightRadius = gSavedSettings.getBOOL("RenderLightRadius");

    mRenderSilhouettes = true;

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

    mSelectedObjects = new LLObjectSelection();
    mHoverObjects = new LLObjectSelection();
    mHighlightedObjects = new LLObjectSelection();

    mForceSelection = false;
    mShowSelection = false;
}


//-----------------------------------------------------------------------------
// ~LLSelectMgr()
//-----------------------------------------------------------------------------
LLSelectMgr::~LLSelectMgr()
{
    clearSelections();
}

void LLSelectMgr::clearSelections()
{
    mHoverObjects->deleteAllNodes();
    mSelectedObjects->deleteAllNodes();
    mHighlightedObjects->deleteAllNodes();
    mRectSelectedObjects.clear();
    mGridObjects.deleteAllNodes();

    LLPipeline::setRenderHighlightTextureChannel(LLRender::DIFFUSE_MAP);
}

void LLSelectMgr::update()
{
    mSelectedObjects->cleanupNodes();
}

void LLSelectMgr::updateEffects()
{
    //keep reference grid objects active
    struct f : public LLSelectedObjectFunctor
    {
        virtual bool apply(LLViewerObject* object)
        {
            LLDrawable* drawable = object->mDrawable;
            if (drawable)
            {
                gPipeline.markMoved(drawable);
            }
            return true;
        }
    } func;
    mGridObjects.applyToObjects(&func);

    if (mEffectsTimer.getElapsedTimeF32() > 1.f)
    {
        mSelectedObjects->updateEffects();
        mEffectsTimer.reset();
    }
}

void LLSelectMgr::resetObjectOverrides()
{
    resetObjectOverrides(getSelection());
}

void LLSelectMgr::resetObjectOverrides(LLObjectSelectionHandle selected_handle)
{
    struct f : public LLSelectedNodeFunctor
    {
        f(bool a, LLSelectMgr* p) : mAvatarOverridesPersist(a), mManager(p) {}
        bool mAvatarOverridesPersist;
        LLSelectMgr* mManager;
        virtual bool apply(LLSelectNode* node)
        {
            if (mAvatarOverridesPersist)
            {
                LLViewerObject* object = node->getObject();
                if (object && !object->getParent())
                {
                    LLVOAvatar* avatar = object->asAvatar();
                    if (avatar)
                    {
                        mManager->mAvatarOverridesMap.emplace(avatar->getID(), AvatarPositionOverride(node->mLastPositionLocal, node->mLastRotation, object));
                    }
                }
            }
            node->mLastPositionLocal.setVec(0, 0, 0);
            node->mLastRotation = LLQuaternion();
            node->mLastScale.setVec(0, 0, 0);
            return true;
        }
    } func(mAllowSelectAvatar, this);

    selected_handle->applyToNodes(&func);
}

void LLSelectMgr::overrideObjectUpdates()
{
    //override any position updates from simulator on objects being edited
    struct f : public LLSelectedNodeFunctor
    {
        virtual bool apply(LLSelectNode* selectNode)
        {
            LLViewerObject* object = selectNode->getObject();
            if (object && object->permMove() && !object->isPermanentEnforced())
            {
                if (!selectNode->mLastPositionLocal.isExactlyZero())
                {
                    object->setPosition(selectNode->mLastPositionLocal);
                }
                if (selectNode->mLastRotation != LLQuaternion())
                {
                    object->setRotation(selectNode->mLastRotation);
                }
                if (!selectNode->mLastScale.isExactlyZero())
                {
                    object->setScale(selectNode->mLastScale);
                }
            }
            return true;
        }
    } func;
    getSelection()->applyToNodes(&func);
}

void LLSelectMgr::resetAvatarOverrides()
{
    mAvatarOverridesMap.clear();
}

void LLSelectMgr::overrideAvatarUpdates()
{
    if (mAvatarOverridesMap.size() == 0)
    {
        return;
    }

    if (!mAllowSelectAvatar || !gFloaterTools)
    {
        resetAvatarOverrides();
        return;
    }

    if (!gFloaterTools->getVisible() && getSelection()->isEmpty())
    {
        // when user switches selection, floater is invisible and selection is empty
        LLToolset *toolset = LLToolMgr::getInstance()->getCurrentToolset();
        if (toolset->isShowFloaterTools()
            && toolset->isToolSelected(0)) // Pie tool
        {
            resetAvatarOverrides();
            return;
        }
    }

    // remove selected avatars from this list,
    // but set object overrides to make sure avatar won't snap back
    struct f : public LLSelectedNodeFunctor
    {
        f(LLSelectMgr* p) : mManager(p) {}
        LLSelectMgr* mManager;
        virtual bool apply(LLSelectNode* selectNode)
        {
            LLViewerObject* object = selectNode->getObject();
            if (object && !object->getParent())
            {
                LLVOAvatar* avatar = object->asAvatar();
                if (avatar)
                {
                    uuid_av_override_map_t::iterator iter = mManager->mAvatarOverridesMap.find(avatar->getID());
                    if (iter != mManager->mAvatarOverridesMap.end())
                    {
                        if (selectNode->mLastPositionLocal.isExactlyZero())
                        {
                            selectNode->mLastPositionLocal = iter->second.mLastPositionLocal;
                        }
                        if (selectNode->mLastRotation == LLQuaternion())
                        {
                            selectNode->mLastRotation = iter->second.mLastRotation;
                        }
                        mManager->mAvatarOverridesMap.erase(iter);
                    }
                }
            }
            return true;
        }
    } func(this);
    getSelection()->applyToNodes(&func);

    // Override avatar positions
    uuid_av_override_map_t::iterator it = mAvatarOverridesMap.begin();
    while (it != mAvatarOverridesMap.end())
    {
        if (it->second.mObject->isDead())
        {
            it = mAvatarOverridesMap.erase(it);
        }
        else
        {
            if (!it->second.mLastPositionLocal.isExactlyZero())
            {
                it->second.mObject->setPosition(it->second.mLastPositionLocal);
            }
            if (it->second.mLastRotation != LLQuaternion())
            {
                it->second.mObject->setRotation(it->second.mLastRotation);
            }
            it++;
        }
    }
}

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

    //remember primary object
    mSelectedObjects->mPrimaryObject = object;

    // Don't add an object that is already in the list
    if (object->isSelected() ) {
        // make sure point at position is updated
        updatePointAt();
        LLSelectNode* nodep = mSelectedObjects->findNode(object);
        if (nodep)
        {
            nodep->selectGLTFNode(gltf_node, gltf_primitive, true);
        }
        gEditMenuHandler = this;
        return NULL;
    }

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

    // LL_INFOS() << "Adding object to selected object list" << LL_ENDL;

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

    // Stop the object from moving (this anticipates changes on the
    // simulator in LLTask::userSelect)
    // *FIX: 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 (mSelectedObjects->getObjectCount())
    {
        gEditMenuHandler = this;
    }

    return mSelectedObjects;
}

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

    //remember primary object
    mSelectedObjects->mPrimaryObject = 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 NULL;
    }

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

    // Since we're selecting a family, start at the root, but
    // don't include an avatar.
    LLViewerObject* root = obj;

    while(!root->isAvatar() && root->getParent())
    {
        LLViewerObject* parent = (LLViewerObject*)root->getParent();
        if (parent->isAvatar())
        {
            break;
        }
        root = parent;
    }

    // Collect all of the objects
    std::vector<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("EditLinkedParts"))
    {
        gSavedSettings.setBOOL("EditLinkedParts", false);
        promoteSelectionToRoot();
    }

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

    return mSelectedObjects;
}

//-----------------------------------------------------------------------------
// Select the object, parents and children.
//-----------------------------------------------------------------------------
LLObjectSelectionHandle LLSelectMgr::selectObjectAndFamily(const std::vector<LLViewerObject*>& object_list,
                                                           bool send_to_sim)
{
    // Collect all of the objects, children included
    std::vector<LLViewerObject*> objects;

    //clear primary object (no primary object)
    mSelectedObjects->mPrimaryObject = NULL;

    if (object_list.size() < 1)
    {
        return NULL;
    }

    // NOTE -- we add the objects in REVERSE ORDER
    // to preserve the order in the mSelectedObjects list
    for (std::vector<LLViewerObject*>::const_reverse_iterator riter = object_list.rbegin();
         riter != object_list.rend(); ++riter)
    {
        LLViewerObject *object = *riter;

        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("EditLinkedParts"))
    {
        gSavedSettings.setBOOL("EditLinkedParts", false);
        promoteSelectionToRoot();
    }

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

    return mSelectedObjects;
}

// 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::removeObjectFromSelections(const LLUUID &id)
{
    bool object_found = false;
    LLTool *tool = NULL;

    tool = LLToolMgr::getInstance()->getCurrentTool();

    // 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 )
    {
        for (LLObjectSelection::iterator iter = getSelection()->begin();
             iter != getSelection()->end(); )
        {
            LLObjectSelection::iterator curiter = iter++;
            LLViewerObject* object = (*curiter)->getObject();
            if (object->mID == id)
            {
                if (tool)
                {
                    tool->stopEditing();
                }

                // lose the selection, don't tell simulator, it knows
                deselectObjectAndFamily(object, false);
                object_found = true;
                break; // must break here, may have removed multiple objects from list
            }
            else if (object->isAvatar() && object->getParent() && ((LLViewerObject*)object->getParent())->mID == id)
            {
                // It's possible the item being removed has an avatar sitting on it
                // So remove the avatar that is sitting on the object.
                deselectObjectAndFamily(object, false);
                break; // must break here, may have removed multiple objects from list
            }
        }
    }

    return object_found;
}

bool LLSelectMgr::linkObjects()
{
    if (!LLSelectMgr::getInstance()->selectGetAllRootsValid())
    {
        LLNotificationsUtil::add("UnableToLinkWhileDownloading");
        return true;
    }

    S32 object_count = LLSelectMgr::getInstance()->getSelection()->getObjectCount();
    if (object_count > MAX_CHILDREN_PER_TASK + 1)
    {
        LLSD args;
        args["COUNT"] = llformat("%d", object_count);
        int max = MAX_CHILDREN_PER_TASK+1;
        args["MAX"] = llformat("%d", max);
        LLNotificationsUtil::add("UnableToLinkObjects", args);
        return true;
    }

    if (LLSelectMgr::getInstance()->getSelection()->getRootObjectCount() < 2)
    {
        LLNotificationsUtil::add("CannotLinkIncompleteSet");
        return true;
    }

    if (!LLSelectMgr::getInstance()->selectGetRootsModify())
    {
        LLNotificationsUtil::add("CannotLinkModify");
        return true;
    }

    if (!LLSelectMgr::getInstance()->selectGetRootsNonPermanentEnforced())
    {
        LLNotificationsUtil::add("CannotLinkPermanent");
        return true;
    }

    LLUUID owner_id;
    std::string owner_name;
    if (!LLSelectMgr::getInstance()->selectGetOwner(owner_id, owner_name))
    {
        // we don't actually care if you're the owner, but novices are
        // the most likely to be stumped by this one, so offer the
        // easiest and most likely solution.
        LLNotificationsUtil::add("CannotLinkDifferentOwners");
        return true;
    }

    if (!LLSelectMgr::getInstance()->selectGetSameRegion())
    {
        LLNotificationsUtil::add("CannotLinkAcrossRegions");
        return true;
    }

    LLSelectMgr::getInstance()->sendLink();

    return true;
}

bool LLSelectMgr::unlinkObjects()
{
    S32 min_objects_for_confirm = gSavedSettings.getS32("MinObjectsForUnlinkConfirm");
    S32 unlink_object_count = mSelectedObjects->getObjectCount(); // clears out nodes with NULL objects
    if (unlink_object_count >= min_objects_for_confirm
        && unlink_object_count > mSelectedObjects->getRootObjectCount())
    {
        // total count > root count means that there are childer inside and that there are linksets that will be unlinked
        LLNotificationsUtil::add("ConfirmUnlink", LLSD(), LLSD(), boost::bind(&LLSelectMgr::confirmUnlinkObjects, this, _1, _2));
        return true;
    }

    LLSelectMgr::getInstance()->sendDelink();
    return true;
}

void LLSelectMgr::confirmUnlinkObjects(const LLSD& notification, const LLSD& response)
{
    S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
    // if Cancel pressed
    if (option == 1)
    {
        return;
    }

    LLSelectMgr::getInstance()->sendDelink();
    return;
}

// in order to link, all objects must have the same owner, and the
// agent must have the ability to modify all of the objects. However,
// we're not answering that question with this method. The question
// we're answering is: does the user have a reasonable expectation
// that a link operation should work? If so, return true, false
// otherwise. this allows the handle_link method to more finely check
// the selection and give an error message when the uer has a
// reasonable expectation for the link to work, but it will fail.
//
// For animated objects, there's additional check that if the
// selection includes at least one animated object, the total mesh
// triangle count cannot exceed the designated limit.
bool LLSelectMgr::enableLinkObjects()
{
    bool new_value = false;
    // check if there are at least 2 objects selected, and that the
    // user can modify at least one of the selected objects.

    // in component mode, can't link
    if (!gSavedSettings.getBOOL("EditLinkedParts"))
    {
        if(LLSelectMgr::getInstance()->selectGetAllRootsValid() && LLSelectMgr::getInstance()->getSelection()->getRootObjectCount() >= 2)
        {
            struct f : public LLSelectedObjectFunctor
            {
                virtual bool apply(LLViewerObject* object)
                {
                    LLViewerObject *root_object = (object == NULL) ? NULL : object->getRootEdit();
                    return object->permModify() && !object->isPermanentEnforced() &&
                        ((root_object == NULL) || !root_object->isPermanentEnforced());
                }
            } func;
            const bool firstonly = true;
            new_value = LLSelectMgr::getInstance()->getSelection()->applyToRootObjects(&func, firstonly);
        }
    }
    if (!LLSelectMgr::getInstance()->getSelection()->checkAnimatedObjectLinkable())
    {
        new_value = false;
    }
    return new_value;
}

bool LLSelectMgr::enableUnlinkObjects()
{
    LLViewerObject* first_editable_object = LLSelectMgr::getInstance()->getSelection()->getFirstEditableObject();
    LLViewerObject *root_object = (first_editable_object == NULL) ? NULL : first_editable_object->getRootEdit();

    bool new_value = LLSelectMgr::getInstance()->selectGetAllRootsValid() &&
        first_editable_object &&
        !first_editable_object->isAttachment() && !first_editable_object->isPermanentEnforced() &&
        ((root_object == NULL) || !root_object->isPermanentEnforced());

    return new_value;
}

void LLSelectMgr::deselectObjectAndFamily(LLViewerObject* object, bool send_to_sim, bool include_entire_object)
{
    // 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
    std::vector<LLViewerObject*> objects;

    if (include_entire_object)
    {
        // Since we're selecting a family, start at the root, but
        // don't include an avatar.
        LLViewerObject* root = object;

        while(!root->isAvatar() && root->getParent())
        {
            LLViewerObject* parent = (LLViewerObject*)root->getParent();
            if (parent->isAvatar())
            {
                break;
            }
            root = parent;
        }

        object = root;
    }
    else
    {
        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 (U32 i = 0; i < objects.size(); 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++;

        // Zap the angular velocity, as the sim will set it to zero
        objects[i]->setAngularVelocity( 0,0,0 );
        objects[i]->setVelocity( 0,0,0 );

        if(msg->isSendFull(NULL) || 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;

    // Zap the angular velocity, as the sim will set it to zero
    object->setAngularVelocity( 0,0,0 );
    object->setVelocity( 0,0,0 );

    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(std::vector<LLViewerObject*>& objects, bool add_to_end)
{
    for (std::vector<LLViewerObject*>::iterator iter = objects.begin();
         iter != objects.end(); ++iter)
    {
        LLViewerObject* objectp = *iter;

        // Can't select yourself
        if (objectp->mID == gAgentID
            && !mAllowSelectAvatar)
        {
            continue;
        }

        if (!objectp->isSelected())
        {
            LLSelectNode *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);
                objectp->setAllTESelected(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 = mSelectedObjects->findNode(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, S32 gltf_node, S32 gltf_primitive)
{
    // check to see if object is already in list
    LLSelectNode *nodep = mSelectedObjects->findNode(objectp);

    // if not in list, add it
    if (!nodep)
    {
        nodep = new LLSelectNode(objectp, true);
        mSelectedObjects->addNode(nodep);
        llassert_always(nodep->getObject());
    }
    else
    {
        // make this a full-fledged selection
        nodep->setTransient(false);
        // Move it to the front of the list
        mSelectedObjects->moveNodeToFront(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);
        objectp->setAllTESelected(true);
    }
    else if (0 <= face && face < SELECT_MAX_TES)
    {
        nodep->selectTE(face, true);
        objectp->setTESelected(face, true);
    }
    else
    {
        LL_ERRS() << "LLSelectMgr::add face " << face << " out-of-range" << LL_ENDL;
        return;
    }

    // Handle glTF node selection
    if (gltf_node >= 0)
    {
        nodep->selectGLTFNode(gltf_node, gltf_primitive, true);

    }

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


LLObjectSelectionHandle LLSelectMgr::setHoverObject(LLViewerObject *objectp, S32 face)
{
    if (!objectp)
    {
        mHoverObjects->deleteAllNodes();
        return NULL;
    }

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

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

    mHoverObjects->mPrimaryObject = objectp;

    objectp = objectp->getRootEdit();

    // is the requested object the same as the existing hover object root?
    // NOTE: there is only ever one linked set in mHoverObjects
    if (mHoverObjects->getFirstRootObject() != objectp)
    {

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

        mHoverObjects->deleteAllNodes();
        for (std::vector<LLViewerObject*>::iterator iter = objects.begin();
             iter != objects.end(); ++iter)
        {
            LLViewerObject* cur_objectp = *iter;
            if(!cur_objectp || cur_objectp->isDead())
            {
                continue;
            }
            LLSelectNode* nodep = new LLSelectNode(cur_objectp, false);
            nodep->selectTE(face, true);
            mHoverObjects->addNodeAtEnd(nodep);
        }

        requestObjectPropertiesFamily(objectp);
    }

    return mHoverObjects;
}

LLSelectNode *LLSelectMgr::getHoverNode()
{
    return mHoverObjects->getFirstRootNode();
}

LLSelectNode *LLSelectMgr::getPrimaryHoverNode()
{
    return mHoverObjects->mSelectNodeMap[mHoverObjects->mPrimaryObject];
}

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() || objectp->isPermanentEnforced())))
    {
        // 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);

    LLViewerObject::const_child_list_t& child_list = root_obj->getChildren();
    for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin();
         iter != child_list.end(); iter++)
    {
        LLViewerObject* child = *iter;
        highlightObjectOnly(child);
    }
}

// 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 std::vector<LLViewerObject*>& objects)
{
    for (std::vector<LLViewerObject*>::const_iterator iter1 = objects.begin();
         iter1 != objects.end(); ++iter1)
    {
        LLViewerObject* object = *iter1;

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

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

        LLViewerObject::const_child_list_t& child_list = root->getChildren();
        for (LLViewerObject::child_list_t::const_iterator iter2 = child_list.begin();
             iter2 != child_list.end(); iter2++)
        {
            LLViewerObject* child = *iter2;
            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);

    LLViewerObject::const_child_list_t& child_list = root_obj->getChildren();
    for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin();
         iter != child_list.end(); iter++)
    {
        LLViewerObject* child = *iter;
        unhighlightObjectOnly(child);
    }
}


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

LLObjectSelectionHandle LLSelectMgr::selectHighlightedObjects()
{
    if (!mHighlightedObjects->getNumNodes())
    {
        return NULL;
    }

    //clear primary object
    mSelectedObjects->mPrimaryObject = NULL;

    for (LLObjectSelection::iterator iter = getHighlightedObjects()->begin();
         iter != getHighlightedObjects()->end(); )
    {
        LLObjectSelection::iterator curiter = iter++;

        LLSelectNode *nodep = *curiter;
        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);
        objectp->setAllTESelected(true);

        mSelectedObjects->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 (mSelectedObjects->getObjectCount())
    {
        gEditMenuHandler = this;
    }

    return mSelectedObjects;
}

void LLSelectMgr::deselectHighlightedObjects()
{
    bool select_linked_set = !gSavedSettings.getBOOL("EditLinkedParts");
    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);

    LLViewerObject::const_child_list_t& child_list = objectp->getChildren();
    for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin();
         iter != child_list.end(); iter++)
    {
        LLViewerObject* child = *iter;
        nodep = new LLSelectNode(child, false);
        mGridObjects.addNodeAtEnd(nodep);
    }
}

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

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

void LLSelectMgr::getGrid(LLVector3& origin, LLQuaternion &rotation, LLVector3 &scale, bool for_snap_guides)
{
    mGridObjects.cleanupNodes();

    LLViewerObject* first_grid_object = mGridObjects.getFirstObject();

    if (mGridMode == GRID_MODE_LOCAL && mSelectedObjects->getObjectCount())
    {
        //LLViewerObject* root = getSelectedParentObject(mSelectedObjects->getFirstObject());
        mGridOrigin = mSavedSelectionBBox.getCenterAgent();
        mGridScale = mSavedSelectionBBox.getExtentLocal() * 0.5f;

        // DEV-12570 Just taking the saved selection box rotation prevents
        // wild rotations of linked sets while in local grid mode
        //if(mSelectedObjects->getObjectCount() < 2 || !root || root->mDrawable.isNull())
        {
            mGridRotation = mSavedSelectionBBox.getRotation();
        }
        /*else //set to the root object
        {
            mGridRotation = root->getRenderRotation();
        }*/
    }
    else if (mGridMode == GRID_MODE_REF_OBJECT && first_grid_object && first_grid_object->mDrawable.notNull())
    {
        LLSelectNode *node = mSelectedObjects->findNode(first_grid_object);
        if (!for_snap_guides && node)
        {
            mGridRotation = node->mSavedRotation;
        }
        else
        {
            mGridRotation = first_grid_object->getRenderRotation();
        }

        LLVector4a min_extents(F32_MAX);
        LLVector4a max_extents(-F32_MAX);
        bool grid_changed = false;
        for (LLObjectSelection::iterator iter = mGridObjects.begin();
             iter != mGridObjects.end(); ++iter)
        {
            LLViewerObject* object = (*iter)->getObject();
            LLDrawable* drawable = object->mDrawable;
            if (drawable)
            {
                const LLVector4a* ext = drawable->getSpatialExtents();
                update_min_max(min_extents, max_extents, ext[0]);
                update_min_max(min_extents, max_extents, ext[1]);
                grid_changed = true;
            }
        }
        if (grid_changed)
        {
            LLVector4a center, size;
            center.setAdd(min_extents, max_extents);
            center.mul(0.5f);
            size.setSub(max_extents, min_extents);
            size.mul(0.5f);

            mGridOrigin.set(center.getF32ptr());
            LLDrawable* drawable = first_grid_object->mDrawable;
            if (drawable && drawable->isActive())
            {
                mGridOrigin = mGridOrigin * first_grid_object->getRenderMatrix();
            }
            mGridScale.set(size.getF32ptr());
        }
    }
    else // GRID_MODE_WORLD or just plain default
    {
        const bool non_root_ok = true;
        LLViewerObject* first_object = mSelectedObjects->getFirstRootObject(non_root_ok);

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

        mSelectedObjects->mSelectType = getSelectTypeForObject( first_object );

        switch (mSelectedObjects->mSelectType)
        {
        case SELECT_TYPE_ATTACHMENT:
            if (first_object && first_object->getRootEdit()->mDrawable.notNull())
            {
                // 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:
            mGridScale = LLVector3(1.f, 1.f, 1.f) * llmin(gSavedSettings.getF32("GridResolution"), 0.5f);
            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;
}

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

void LLSelectMgr::remove(std::vector<LLViewerObject*>& objects)
{
    for (std::vector<LLViewerObject*>::iterator iter = objects.begin();
         iter != objects.end(); ++iter)
    {
        LLViewerObject* objectp = *iter;
        LLSelectNode* nodep = mSelectedObjects->findNode(objectp);
        if (nodep)
        {
            objectp->setSelected(false);
            mSelectedObjects->removeNode(nodep);
            nodep = NULL;
        }
    }
    updateSelectionCenter();
    dialog_refresh_all();
}


//-----------------------------------------------------------------------------
// remove() - a single object
//-----------------------------------------------------------------------------
void LLSelectMgr::remove(LLViewerObject *objectp, S32 te, bool undoable)
{
    // get object node (and verify it is in the selected list)
    LLSelectNode *nodep = mSelectedObjects->findNode(objectp);
    if (!nodep)
    {
        return;
    }

    // if face = all, remove object from list
    if ((objectp->getNumTEs() <= 0) || (te == SELECT_ALL_TES))
    {
        // Remove all faces (or the object doesn't have faces) so remove the node
        mSelectedObjects->removeNode(nodep);
        nodep = NULL;
        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);
            objectp->setTESelected(te, false);
        }
        else
        {
            LL_ERRS() << "LLSelectMgr::remove - tried to remove TE " << te << " that wasn't selected" << LL_ENDL;
            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);
            nodep = NULL;
            objectp->setSelected( false );
            // *FIXME: Doesn't update simulator that object is no longer selected
        }
    }
    else
    {
        // ...out of range face
        LL_ERRS() << "LLSelectMgr::remove - TE " << te << " out of range" << LL_ENDL;
    }

    updateSelectionCenter();
    dialog_refresh_all();
}


//-----------------------------------------------------------------------------
// removeAll()
//-----------------------------------------------------------------------------
void LLSelectMgr::removeAll()
{
    for (LLObjectSelection::iterator iter = mSelectedObjects->begin();
         iter != mSelectedObjects->end(); iter++ )
    {
        LLViewerObject *objectp = (*iter)->getObject();
        objectp->setSelected( false );
    }

    mSelectedObjects->deleteAllNodes();

    updateSelectionCenter();
    dialog_refresh_all();
}

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

    bool selection_changed = false;

    for (LLObjectSelection::iterator iter = getSelection()->begin();
         iter != getSelection()->end(); )
    {
        LLObjectSelection::iterator curiter = iter++;
        LLSelectNode* nodep = *curiter;
        LLViewerObject* object = nodep->getObject();

        if (nodep->mIndividualSelection)
        {
            selection_changed = true;
        }

        LLViewerObject* parentp = object;
        while(parentp->getParent() && !(parentp->isRootEdit()))
        {
            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()
{
    std::vector<LLViewerObject*> objects;

    for (LLObjectSelection::root_iterator iter = getSelection()->root_begin();
         iter != getSelection()->root_end(); iter++)
    {
        LLViewerObject* object = (*iter)->getObject();
        object->addThisAndNonJointChildren(objects);
    }

    if (!objects.empty())
    {
        deselectAll();
        for (std::vector<LLViewerObject*>::iterator iter = objects.begin();
             iter != objects.end(); ++iter)
        {
            LLViewerObject* objectp = *iter;
            selectObjectOnly(objectp);
        }
    }
}

//-----------------------------------------------------------------------------
// dump()
//-----------------------------------------------------------------------------
void LLSelectMgr::dump()
{
    LL_INFOS() << "Selection Manager: " << mSelectedObjects->getNumNodes() << " items" << LL_ENDL;

    LL_INFOS() << "TE mode " << mTEMode << LL_ENDL;

    S32 count = 0;
    for (LLObjectSelection::iterator iter = getSelection()->begin();
         iter != getSelection()->end(); iter++ )
    {
        LLViewerObject* objectp = (*iter)->getObject();
        LL_INFOS() << "Object " << count << " type " << LLPrimitive::pCodeToString(objectp->getPCode()) << LL_ENDL;
        LL_INFOS() << "  hasLSL " << objectp->flagScripted() << LL_ENDL;
        LL_INFOS() << "  hasTouch " << objectp->flagHandleTouch() << LL_ENDL;
        LL_INFOS() << "  hasMoney " << objectp->flagTakesMoney() << LL_ENDL;
        LL_INFOS() << "  getposition " << objectp->getPosition() << LL_ENDL;
        LL_INFOS() << "  getpositionAgent " << objectp->getPositionAgent() << LL_ENDL;
        LL_INFOS() << "  getpositionRegion " << objectp->getPositionRegion() << LL_ENDL;
        LL_INFOS() << "  getpositionGlobal " << objectp->getPositionGlobal() << LL_ENDL;
        LLDrawable* drawablep = objectp->mDrawable;
        LL_INFOS() << "  " << (drawablep&& drawablep->isVisible() ? "visible" : "invisible") << LL_ENDL;
        LL_INFOS() << "  " << (drawablep&& drawablep->isState(LLDrawable::FORCE_INVISIBLE) ? "force_invisible" : "") << LL_ENDL;
        count++;
    }

    // Face iterator
    for (LLObjectSelection::iterator iter = getSelection()->begin();
         iter != getSelection()->end(); iter++ )
    {
        LLSelectNode* node = *iter;
        LLViewerObject* objectp = node->getObject();
        if (!objectp)
            continue;
        for (S32 te = 0; te < objectp->getNumTEs(); ++te )
        {
            if (node->isTESelected(te))
            {
                LL_INFOS() << "Object " << objectp << " te " << te << LL_ENDL;
            }
        }
    }

    LL_INFOS() << mHighlightedObjects->getNumNodes() << " objects currently highlighted." << LL_ENDL;

    LL_INFOS() << "Center global " << mSelectionCenterGlobal << LL_ENDL;
}

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


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

struct LLSelectMgrSendFunctor : public LLSelectedObjectFunctor
{
    virtual bool apply(LLViewerObject* object)
    {
        if (object->permModify())
        {
            object->sendTEUpdate();
        }
        return true;
    }
};

void LLObjectSelection::applyNoCopyTextureToTEs(LLViewerInventoryItem* item)
{
    if (!item)
    {
        return;
    }
    LLViewerTexture* image = LLViewerTextureManager::getFetchedTexture(item->getAssetUUID());

    for (iterator iter = begin(); iter != end(); ++iter)
    {
        LLSelectNode* node = *iter;
        LLViewerObject* object = (*iter)->getObject();
        if (!object)
        {
            continue;
        }

        S32 num_tes = llmin((S32)object->getNumTEs(), (S32)object->getNumFaces());
        bool texture_copied = false;
        bool updated = false;
        for (S32 te = 0; te < num_tes; ++te)
        {
            if (node->isTESelected(te))
            {
                //(no-copy) textures must be moved to the object's inventory only once
                // without making any copies
                if (!texture_copied)
                {
                    LLToolDragAndDrop::handleDropMaterialProtections(object, item, LLToolDragAndDrop::SOURCE_AGENT, LLUUID::null);
                    texture_copied = true;
                }

                // apply texture for the selected faces
                add(LLStatViewer::EDIT_TEXTURE, 1);
                object->setTEImage(te, image);
                updated = true;
            }
        }

        if (updated) // not nessesary? sendTEUpdate update supposed to be done by sendfunc
        {
            dialog_refresh_all();

            // send the update to the simulator
            object->sendTEUpdate();
        }
    }
}

bool LLObjectSelection::applyRestrictedPbrMaterialToTEs(LLViewerInventoryItem* item)
{
    if (!item)
    {
        return false;
    }

    LLUUID asset_id = item->getAssetUUID();
    if (asset_id.isNull())
    {
        asset_id = BLANK_MATERIAL_ASSET_ID;
    }

    bool material_copied_all_faces = true;

    for (iterator iter = begin(); iter != end(); ++iter)
    {
        LLSelectNode* node = *iter;
        LLViewerObject* object = (*iter)->getObject();
        if (!object)
        {
            continue;
        }

        S32 num_tes = llmin((S32)object->getNumTEs(), (S32)object->getNumFaces());
        bool material_copied = false;
        for (S32 te = 0; te < num_tes; ++te)
        {
            if (node->isTESelected(te))
            {
                //(no-copy), (no-modify), and (no-transfer) materials must be moved to the object's inventory only once
                // without making any copies
                if (!material_copied && asset_id.notNull())
                {
                    material_copied = (bool)LLToolDragAndDrop::handleDropMaterialProtections(object, item, LLToolDragAndDrop::SOURCE_AGENT, LLUUID::null);
                }
                if (!material_copied)
                {
                    // Applying the material is not possible for this object given the current inventory
                    material_copied_all_faces = false;
                    break;
                }

                // apply texture for the selected faces
                // blank out most override data on the server
                //add(LLStatViewer::EDIT_TEXTURE, 1);
                object->setRenderMaterialID(te, asset_id);
            }
        }
    }

    LLGLTFMaterialList::flushUpdates();

    return material_copied_all_faces;
}


//-----------------------------------------------------------------------------
// selectionSetImage()
//-----------------------------------------------------------------------------
// *TODO: re-arch texture applying out of lltooldraganddrop
bool 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) )
    {
         LL_DEBUGS() << "Attempted to apply no-copy texture " << imageid
             << " to multiple objects" << LL_ENDL;

        LLNotificationsUtil::add("FailedToApplyTextureNoCopyToMultiple");
        return false;
    }

    struct f : public LLSelectedTEFunctor
    {
        LLViewerInventoryItem* mItem;
        LLUUID mImageID;
        f(LLViewerInventoryItem* item, const LLUUID& id) : mItem(item), mImageID(id) {}
        bool apply(LLViewerObject* objectp, S32 te)
        {
            if(!objectp || !objectp->permModify())
            {
                return false;
            }

            // Might be better to run willObjectAcceptInventory
            if (mItem && objectp->isAttachment())
            {
                const LLPermissions& perm = mItem->getPermissions();
                bool unrestricted = (perm.getMaskBase() & PERM_ITEM_UNRESTRICTED) == PERM_ITEM_UNRESTRICTED;
                if (!unrestricted)
                {
                    // Attachments are in world and in inventory simultaneously,
                    // at the moment server doesn't support such a situation.
                    return false;
                }
            }

            if (mItem)
            {
                LLToolDragAndDrop::dropTextureOneFace(objectp,
                                                      te,
                                                      mItem,
                                                      LLToolDragAndDrop::SOURCE_AGENT,
                                                      LLUUID::null,
                                                      false);
            }
            else // not an inventory item
            {
                // 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, LLViewerTextureManager::getFetchedTexture(mImageID, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE));
            }

            return true;
        }
    };

    if (item && !item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID()))
    {
        getSelection()->applyNoCopyTextureToTEs(item);
    }
    else
    {
        f setfunc(item, imageid);
        getSelection()->applyToTEs(&setfunc);
    }


    struct g : public LLSelectedObjectFunctor
    {
        LLViewerInventoryItem* mItem;
        g(LLViewerInventoryItem* item) : mItem(item) {}
        virtual bool apply(LLViewerObject* object)
        {
            if (!mItem)
            {
                object->sendTEUpdate();
                // 1 particle effect per object
                LLHUDEffectSpiral *effectp = (LLHUDEffectSpiral *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_BEAM, true);
                effectp->setSourceObject(gAgentAvatarp);
                effectp->setTargetObject(object);
                effectp->setDuration(LL_HUD_DUR_SHORT);
                effectp->setColor(LLColor4U(gAgent.getEffectColor()));
            }
            return true;
        }
    } sendfunc(item);
    getSelection()->applyToObjects(&sendfunc);

    return true;
}

//-----------------------------------------------------------------------------
// selectionSetGLTFMaterial()
//-----------------------------------------------------------------------------
bool LLSelectMgr::selectionSetGLTFMaterial(const LLUUID& mat_id)
{
    // First for (no copy) textures and multiple object selection
    LLViewerInventoryItem* item = gInventory.getItem(mat_id);
    if (item
        && !item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID())
        && (mSelectedObjects->getNumNodes() > 1))
    {
        LL_DEBUGS() << "Attempted to apply no-copy material " << mat_id
            << "to multiple objects" << LL_ENDL;

        LLNotificationsUtil::add("FailedToApplyGLTFNoCopyToMultiple");
        return false;
    }

    struct f : public LLSelectedTEFunctor
    {
        LLViewerInventoryItem* mItem;
        LLUUID mMatId;
        bool material_copied_any_face = false;
        bool material_copied_all_faces = true;
        f(LLViewerInventoryItem* item, const LLUUID& id) : mItem(item), mMatId(id) {}
        bool apply(LLViewerObject* objectp, S32 te)
        {
            if (!objectp || !objectp->permModify())
            {
                return false;
            }
            LLUUID asset_id = mMatId;
            if (mItem)
            {
                const LLPermissions& perm = mItem->getPermissions();
                bool from_library = perm.getOwner() == ALEXANDRIA_LINDEN_ID;
                if (objectp->isAttachment())
                {
                    bool unrestricted = (perm.getMaskBase() & PERM_ITEM_UNRESTRICTED) == PERM_ITEM_UNRESTRICTED;

                    if (!unrestricted && !from_library)
                    {
                        // Attachments are in world and in inventory simultaneously,
                        // at the moment server doesn't support such a situation.
                        return false;
                    }
                }

                if (!from_library
                    // Check if item may be copied into the object's inventory
                    && !LLToolDragAndDrop::handleDropMaterialProtections(objectp, mItem, LLToolDragAndDrop::SOURCE_AGENT, LLUUID::null))
                {
                    return false;
                }

                asset_id = mItem->getAssetUUID();
                if (asset_id.isNull())
                {
                    asset_id = BLANK_MATERIAL_ASSET_ID;
                }
            }

            // Blank out most override data on the object and send to server
            objectp->setRenderMaterialID(te, asset_id);

            return true;
        }
    };

    bool success = true;
    if (item
        &&  (!item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID()) ||
             !item->getPermissions().allowOperationBy(PERM_TRANSFER, gAgent.getID()) ||
             !item->getPermissions().allowOperationBy(PERM_MODIFY, gAgent.getID())
            )
        && item->getPermissions().getOwner() != ALEXANDRIA_LINDEN_ID
        )
    {
        success = success && getSelection()->applyRestrictedPbrMaterialToTEs(item);
    }
    else
    {
        f setfunc(item, mat_id);
        success = success && getSelection()->applyToTEs(&setfunc);
    }

    struct g : public LLSelectedObjectFunctor
    {
        LLViewerInventoryItem* mItem;
        g(LLViewerInventoryItem* item) : mItem(item) {}
        virtual bool apply(LLViewerObject* object)
        {
            if (object && !object->permModify())
            {
                return false;
            }

            if (!mItem)
            {
                // 1 particle effect per object
                LLHUDEffectSpiral *effectp = (LLHUDEffectSpiral *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_BEAM, true);
                effectp->setSourceObject(gAgentAvatarp);
                effectp->setTargetObject(object);
                effectp->setDuration(LL_HUD_DUR_SHORT);
                effectp->setColor(LLColor4U(gAgent.getEffectColor()));
            }

            dialog_refresh_all();
            object->sendTEUpdate();
            return true;
        }
    } sendfunc(item);
    success = success && getSelection()->applyToObjects(&sendfunc);

    LLGLTFMaterialList::flushUpdates();

    return success;
}

//-----------------------------------------------------------------------------
// selectionSetColor()
//-----------------------------------------------------------------------------
void LLSelectMgr::selectionSetColor(const LLColor4 &color)
{
    struct f : public LLSelectedTEFunctor
    {
        LLColor4 mColor;
        f(const LLColor4& c) : mColor(c) {}
        bool apply(LLViewerObject* object, S32 te)
        {
            if (object->permModify())
            {
                object->setTEColor(te, mColor);
            }
            return true;
        }
    } setfunc(color);
    getSelection()->applyToTEs(&setfunc);

    LLSelectMgrSendFunctor sendfunc;
    getSelection()->applyToObjects(&sendfunc);
}

//-----------------------------------------------------------------------------
// selectionSetColorOnly()
//-----------------------------------------------------------------------------
void LLSelectMgr::selectionSetColorOnly(const LLColor4 &color)
{
    struct f : public LLSelectedTEFunctor
    {
        LLColor4 mColor;
        f(const LLColor4& c) : mColor(c) {}
        bool apply(LLViewerObject* object, S32 te)
        {
            if (object->permModify())
            {
                LLColor4 prev_color = object->getTE(te)->getColor();
                mColor.mV[VALPHA] = prev_color.mV[VALPHA];
                // update viewer side color in anticipation of update from simulator
                object->setTEColor(te, mColor);
            }
            return true;
        }
    } setfunc(color);
    getSelection()->applyToTEs(&setfunc);

    LLSelectMgrSendFunctor sendfunc;
    getSelection()->applyToObjects(&sendfunc);
}

//-----------------------------------------------------------------------------
// selectionSetAlphaOnly()
//-----------------------------------------------------------------------------
void LLSelectMgr::selectionSetAlphaOnly(const F32 alpha)
{
    struct f : public LLSelectedTEFunctor
    {
        F32 mAlpha;
        f(const F32& a) : mAlpha(a) {}
        bool apply(LLViewerObject* object, S32 te)
        {
            if (object->permModify())
            {
                LLColor4 prev_color = object->getTE(te)->getColor();
                prev_color.mV[VALPHA] = mAlpha;
                // update viewer side color in anticipation of update from simulator
                object->setTEColor(te, prev_color);
            }
            return true;
        }
    } setfunc(alpha);
    getSelection()->applyToTEs(&setfunc);

    LLSelectMgrSendFunctor sendfunc;
    getSelection()->applyToObjects(&sendfunc);
}

void LLSelectMgr::selectionRevertColors()
{
    struct f : public LLSelectedTEFunctor
    {
        LLObjectSelectionHandle mSelectedObjects;
        f(LLObjectSelectionHandle sel) : mSelectedObjects(sel) {}
        bool apply(LLViewerObject* object, S32 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);
                }
            }
            return true;
        }
    } setfunc(mSelectedObjects);
    getSelection()->applyToTEs(&setfunc);

    LLSelectMgrSendFunctor sendfunc;
    getSelection()->applyToObjects(&sendfunc);
}

void LLSelectMgr::selectionRevertShinyColors()
{
    struct f : public LLSelectedTEFunctor
    {
        LLObjectSelectionHandle mSelectedObjects;
        f(LLObjectSelectionHandle sel) : mSelectedObjects(sel) {}
        bool apply(LLViewerObject* object, S32 te)
        {
            if (object->permModify())
            {
                LLSelectNode* nodep = mSelectedObjects->findNode(object);
                if (nodep && te < (S32)nodep->mSavedShinyColors.size())
                {
                    LLColor4 color = nodep->mSavedShinyColors[te];
                    // update viewer side color in anticipation of update from simulator
                    LLMaterialPtr old_mat = object->getTE(te)->getMaterialParams();
                    if (!old_mat.isNull())
                    {
                        LLMaterialPtr new_mat = gFloaterTools->getPanelFace()->createDefaultMaterial(old_mat);
                        new_mat->setSpecularLightColor(color);
                        object->getTE(te)->setMaterialParams(new_mat);
                        LLMaterialMgr::getInstance()->put(object->getID(), te, *new_mat);
                    }
                }
            }
            return true;
        }
    } setfunc(mSelectedObjects);
    getSelection()->applyToTEs(&setfunc);

    LLSelectMgrSendFunctor sendfunc;
    getSelection()->applyToObjects(&sendfunc);
}

bool LLSelectMgr::selectionRevertTextures()
{
    struct f : public LLSelectedTEFunctor
    {
        LLObjectSelectionHandle mSelectedObjects;
        f(LLObjectSelectionHandle sel) : mSelectedObjects(sel) {}
        bool apply(LLViewerObject* object, S32 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
                        return false;
                    }
                    else
                    {
                        object->setTEImage(te, LLViewerTextureManager::getFetchedTexture(id, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE));

                    }
                }
            }
            return true;
        }
    } setfunc(mSelectedObjects);
    bool revert_successful = getSelection()->applyToTEs(&setfunc);

    LLSelectMgrSendFunctor sendfunc;
    getSelection()->applyToObjects(&sendfunc);

    return revert_successful;
}

void LLSelectMgr::selectionRevertGLTFMaterials()
{
    struct f : public LLSelectedTEFunctor
    {
        LLObjectSelectionHandle mSelectedObjects;
        f(LLObjectSelectionHandle sel) : mSelectedObjects(sel) {}
        bool apply(LLViewerObject* objectp, S32 te)
        {
            if (objectp && !objectp->permModify())
            {
                return false;
            }

            LLSelectNode* nodep = mSelectedObjects->findNode(objectp);
            if (nodep && te < (S32)nodep->mSavedGLTFMaterialIds.size())
            {
                // Restore base material
                LLUUID asset_id = nodep->mSavedGLTFMaterialIds[te];

                // Update material locally
                objectp->setRenderMaterialID(te, asset_id, false /*wait for LLGLTFMaterialList update*/);
                objectp->setTEGLTFMaterialOverride(te, nodep->mSavedGLTFOverrideMaterials[te]);

                // Enqueue update to server
                if (asset_id.notNull())
                {
                    // Restore overrides and base material
                    LLGLTFMaterialList::queueApply(objectp, te, asset_id, nodep->mSavedGLTFOverrideMaterials[te]);
                }
                else
                {
                    //blank override out
                    LLGLTFMaterialList::queueApply(objectp, te, asset_id);
                }

            }
            return true;
        }
    } setfunc(mSelectedObjects);
    getSelection()->applyToTEs(&setfunc);
}

void LLSelectMgr::selectionSetBumpmap(U8 bumpmap, const LLUUID &image_id)
{
    struct f : public LLSelectedTEFunctor
    {
        U8 mBump;
        f(const U8& b) : mBump(b) {}
        bool apply(LLViewerObject* object, S32 te)
        {
            if (object->permModify())
            {
                // update viewer side color in anticipation of update from simulator
                object->setTEBumpmap(te, mBump);
            }
            return true;
        }
    } setfunc(bumpmap);

    LLViewerInventoryItem* item = gInventory.getItem(image_id);
    if(item
        && !item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID())
        && (mSelectedObjects->getNumNodes() > 1) )
    {
        LL_WARNS() << "Attempted to apply no-copy texture to multiple objects" << LL_ENDL;
        return;
    }

    if (item && !item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID()))
    {
        LLViewerObject *object = mSelectedObjects->getFirstRootObject();
        if (!object)
        {
            return;
        }
        const LLPermissions& perm = item->getPermissions();
        bool unrestricted = (perm.getMaskBase() & PERM_ITEM_UNRESTRICTED) == PERM_ITEM_UNRESTRICTED;
        bool attached = object->isAttachment();
        if (attached && !unrestricted)
        {
            // Attachments are in world and in inventory simultaneously,
            // at the moment server doesn't support such a situation.
            return;
        }
        LLToolDragAndDrop::handleDropMaterialProtections(object, item, LLToolDragAndDrop::SOURCE_AGENT, LLUUID::null);
    }
    getSelection()->applyToTEs(&setfunc);

    LLSelectMgrSendFunctor sendfunc;
    getSelection()->applyToObjects(&sendfunc);
}

void LLSelectMgr::selectionSetTexGen(U8 texgen)
{
    struct f : public LLSelectedTEFunctor
    {
        U8 mTexgen;
        f(const U8& t) : mTexgen(t) {}
        bool apply(LLViewerObject* object, S32 te)
        {
            if (object->permModify())
            {
                // update viewer side color in anticipation of update from simulator
                object->setTETexGen(te, mTexgen);
            }
            return true;
        }
    } setfunc(texgen);
    getSelection()->applyToTEs(&setfunc);

    LLSelectMgrSendFunctor sendfunc;
    getSelection()->applyToObjects(&sendfunc);
}


void LLSelectMgr::selectionSetShiny(U8 shiny, const LLUUID &image_id)
{
    struct f : public LLSelectedTEFunctor
    {
        U8 mShiny;
        f(const U8& t) : mShiny(t) {}
        bool apply(LLViewerObject* object, S32 te)
        {
            if (object->permModify())
            {
                // update viewer side color in anticipation of update from simulator
                object->setTEShiny(te, mShiny);
            }
            return true;
        }
    } setfunc(shiny);

    LLViewerInventoryItem* item = gInventory.getItem(image_id);
    if(item
        && !item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID())
        && (mSelectedObjects->getNumNodes() > 1) )
    {
        LL_WARNS() << "Attempted to apply no-copy texture to multiple objects" << LL_ENDL;
        return;
    }

    if (item && !item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID()))
    {
        LLViewerObject *object = mSelectedObjects->getFirstRootObject();
        if (!object)
        {
            return;
        }
        const LLPermissions& perm = item->getPermissions();
        bool unrestricted = (perm.getMaskBase() & PERM_ITEM_UNRESTRICTED) == PERM_ITEM_UNRESTRICTED;
        bool attached = object->isAttachment();
        if (attached && !unrestricted)
        {
            // Attachments are in world and in inventory simultaneously,
            // at the moment server doesn't support such a situation.
            return;
        }
        LLToolDragAndDrop::handleDropMaterialProtections(object, item, LLToolDragAndDrop::SOURCE_AGENT, LLUUID::null);
    }
    getSelection()->applyToTEs(&setfunc);

    LLSelectMgrSendFunctor sendfunc;
    getSelection()->applyToObjects(&sendfunc);
}

void LLSelectMgr::selectionSetFullbright(U8 fullbright)
{
    struct f : public LLSelectedTEFunctor
    {
        U8 mFullbright;
        f(const U8& t) : mFullbright(t) {}
        bool apply(LLViewerObject* object, S32 te)
        {
            if (object->permModify())
            {
                // update viewer side color in anticipation of update from simulator
                object->setTEFullbright(te, mFullbright);
            }
            return true;
        }
    } setfunc(fullbright);
    getSelection()->applyToTEs(&setfunc);

    struct g : public LLSelectedObjectFunctor
    {
        U8 mFullbright;
        g(const U8& t) : mFullbright(t) {}
        virtual bool apply(LLViewerObject* object)
        {
            if (object->permModify())
            {
                object->sendTEUpdate();
                if (mFullbright)
                {
                    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();
                    }
                }
            }
            return true;
        }
    } sendfunc(fullbright);
    getSelection()->applyToObjects(&sendfunc);
}

// This function expects media_data to be a map containing relevant
// media data name/value pairs (e.g. home_url, etc.)
void LLSelectMgr::selectionSetMedia(U8 media_type, const LLSD &media_data)
{
    struct f : public LLSelectedTEFunctor
    {
        U8 mMediaFlags;
        const LLSD &mMediaData;
        f(const U8& t, const LLSD& d) : mMediaFlags(t), mMediaData(d) {}
        bool apply(LLViewerObject* object, S32 te)
        {
            if (object->permModify())
            {
                // If we are adding media, then check the current state of the
                // media data on this face.
                //  - If it does not have media, AND we are NOT setting the HOME URL, then do NOT add media to this
                // face.
                //  - If it does not have media, and we ARE setting the HOME URL, add media to this face.
                //  - If it does already have media, add/update media to/on this face
                // If we are removing media, just do it (ignore the passed-in LLSD).
                if (mMediaFlags & LLTextureEntry::MF_HAS_MEDIA)
                {
                    llassert(mMediaData.isMap());
                    const LLTextureEntry *texture_entry = object->getTE(te);
                    if (!mMediaData.isMap() ||
                        ((NULL != texture_entry) && !texture_entry->hasMedia() && !mMediaData.has(LLMediaEntry::HOME_URL_KEY)))
                    {
                        // skip adding/updating media
                    }
                    else {
                        // Add/update media
                        object->setTEMediaFlags(te, mMediaFlags);
                        LLVOVolume *vo = dynamic_cast<LLVOVolume*>(object);
                        llassert(NULL != vo);
                        if (NULL != vo)
                        {
                            vo->syncMediaData(te, mMediaData, true/*merge*/, true/*ignore_agent*/);
                        }
                    }
                }
                else
                {
                    // delete media (or just set the flags)
                    object->setTEMediaFlags(te, mMediaFlags);
                }
            }
            return true;
        }
    } setfunc(media_type, media_data);
    getSelection()->applyToTEs(&setfunc);

    struct f2 : public LLSelectedObjectFunctor
    {
        virtual bool apply(LLViewerObject* object)
        {
            if (object->permModify())
            {
                object->sendTEUpdate();
                LLVOVolume *vo = dynamic_cast<LLVOVolume*>(object);
                llassert(NULL != vo);
                // It's okay to skip this object if hasMedia() is false...
                // the sendTEUpdate() above would remove all media data if it were
                // there.
                if (NULL != vo && vo->hasMedia())
                {
                    // Send updated media data FOR THE ENTIRE OBJECT
                    vo->sendMediaDataUpdate();
                }
            }
            return true;
        }
    } func2;
    mSelectedObjects->applyToObjects( &func2 );
}

void LLSelectMgr::selectionSetGlow(F32 glow)
{
    struct f1 : public LLSelectedTEFunctor
    {
        F32 mGlow;
        f1(F32 glow) : mGlow(glow) {};
        bool apply(LLViewerObject* object, S32 face)
        {
            if (object->permModify())
            {
                // update viewer side color in anticipation of update from simulator
                object->setTEGlow(face, mGlow);
            }
            return true;
        }
    } func1(glow);
    mSelectedObjects->applyToTEs( &func1 );

    struct f2 : public LLSelectedObjectFunctor
    {
        virtual bool apply(LLViewerObject* object)
        {
            if (object->permModify())
            {
                object->sendTEUpdate();
            }
            return true;
        }
    } func2;
    mSelectedObjects->applyToObjects( &func2 );
}

void LLSelectMgr::selectionSetMaterialParams(LLSelectedTEMaterialFunctor* material_func, int te)
{
    struct f1 : public LLSelectedTEFunctor
    {
        LLMaterialPtr mMaterial;
        f1(LLSelectedTEMaterialFunctor* material_func, int te) : _material_func(material_func), _specific_te(te) {}

        bool apply(LLViewerObject* object, S32 te)
        {
            if (_specific_te == -1 || (te == _specific_te))
            {
                if (object && object->permModify() && _material_func)
                {
                    LLTextureEntry* tep = object->getTE(te);
                    if (tep)
                    {
                        LLMaterialPtr current_material = tep->getMaterialParams();
                        _material_func->apply(object, te, tep, current_material);
                    }
                }
            }
            return true;
        }

        LLSelectedTEMaterialFunctor* _material_func;
        int _specific_te;
    } func1(material_func, te);
    mSelectedObjects->applyToTEs( &func1 );

    struct f2 : public LLSelectedObjectFunctor
    {
        virtual bool apply(LLViewerObject* object)
        {
            if (object->permModify())
            {
                object->sendTEUpdate();
            }
            return true;
        }
    } func2;
    mSelectedObjects->applyToObjects( &func2 );
}

void LLSelectMgr::selectionRemoveMaterial()
{
    struct f1 : public LLSelectedTEFunctor
    {
        bool apply(LLViewerObject* object, S32 face)
        {
            if (object->permModify())
            {
                    LL_DEBUGS("Materials") << "Removing material from object " << object->getID() << " face " << face << LL_ENDL;
                LLMaterialMgr::getInstance()->remove(object->getID(),face);
                object->setTEMaterialParams(face, NULL);
            }
            return true;
        }
    } func1;
    mSelectedObjects->applyToTEs( &func1 );

    struct f2 : public LLSelectedObjectFunctor
    {
        virtual bool apply(LLViewerObject* object)
        {
            if (object->permModify())
            {
                object->sendTEUpdate();
            }
            return true;
        }
    } func2;
    mSelectedObjects->applyToObjects( &func2 );
}


//-----------------------------------------------------------------------------
// findObjectPermissions()
//-----------------------------------------------------------------------------
LLPermissions* LLSelectMgr::findObjectPermissions(const LLViewerObject* object)
{
    for (LLObjectSelection::valid_iterator iter = getSelection()->valid_begin();
         iter != getSelection()->valid_end(); iter++ )
    {
        LLSelectNode* nodep = *iter;
        if (nodep->getObject() == object)
        {
            return nodep->mPermissions;
        }
    }

    return NULL;
}


//-----------------------------------------------------------------------------
// selectionGetGlow()
//-----------------------------------------------------------------------------
bool LLSelectMgr::selectionGetGlow(F32 *glow)
{
    bool identical;
    F32 lglow = 0.f;
    struct f1 : public LLSelectedTEGetFunctor<F32>
    {
        F32 get(LLViewerObject* object, S32 face)
        {
            return object->getTE(face)->getGlow();
        }
    } func;
    identical = mSelectedObjects->getSelectedTEValue( &func, lglow );

    *glow = lglow;
    return identical;
}


void LLSelectMgr::selectionSetPhysicsType(U8 type)
{
    struct f : public LLSelectedObjectFunctor
    {
        U8 mType;
        f(const U8& t) : mType(t) {}
        virtual bool apply(LLViewerObject* object)
        {
            if (object->permModify())
            {
                object->setPhysicsShapeType(mType);
                object->updateFlags(true);
            }
            return true;
        }
    } sendfunc(type);
    getSelection()->applyToObjects(&sendfunc);
}

void LLSelectMgr::selectionSetFriction(F32 friction)
{
    struct f : public LLSelectedObjectFunctor
    {
        F32 mFriction;
        f(const F32& friction) : mFriction(friction) {}
        virtual bool apply(LLViewerObject* object)
        {
            if (object->permModify())
            {
                object->setPhysicsFriction(mFriction);
                object->updateFlags(true);
            }
            return true;
        }
    } sendfunc(friction);
    getSelection()->applyToObjects(&sendfunc);
}

void LLSelectMgr::selectionSetGravity(F32 gravity )
{
    struct f : public LLSelectedObjectFunctor
    {
        F32 mGravity;
        f(const F32& gravity) : mGravity(gravity) {}
        virtual bool apply(LLViewerObject* object)
        {
            if (object->permModify())
            {
                object->setPhysicsGravity(mGravity);
                object->updateFlags(true);
            }
            return true;
        }
    } sendfunc(gravity);
    getSelection()->applyToObjects(&sendfunc);
}

void LLSelectMgr::selectionSetDensity(F32 density )
{
    struct f : public LLSelectedObjectFunctor
    {
        F32 mDensity;
        f(const F32& density ) : mDensity(density) {}
        virtual bool apply(LLViewerObject* object)
        {
            if (object->permModify())
            {
                object->setPhysicsDensity(mDensity);
                object->updateFlags(true);
            }
            return true;
        }
    } sendfunc(density);
    getSelection()->applyToObjects(&sendfunc);
}

void LLSelectMgr::selectionSetRestitution(F32 restitution)
{
    struct f : public LLSelectedObjectFunctor
    {
        F32 mRestitution;
        f(const F32& restitution ) : mRestitution(restitution) {}
        virtual bool apply(LLViewerObject* object)
        {
            if (object->permModify())
            {
                object->setPhysicsRestitution(mRestitution);
                object->updateFlags(true);
            }
            return true;
        }
    } sendfunc(restitution);
    getSelection()->applyToObjects(&sendfunc);
}


//-----------------------------------------------------------------------------
// selectionSetMaterial()
//-----------------------------------------------------------------------------
void LLSelectMgr::selectionSetMaterial(U8 material)
{
    struct f : public LLSelectedObjectFunctor
    {
        U8 mMaterial;
        f(const U8& t) : mMaterial(t) {}
        virtual bool apply(LLViewerObject* object)
        {
            if (object->permModify())
            {
                U8 cur_material = object->getMaterial();
                U8 material = mMaterial | (cur_material & ~LL_MCODE_MASK);
                object->setMaterial(material);
                object->sendMaterialUpdate();
            }
            return true;
        }
    } sendfunc(material);
    getSelection()->applyToObjects(&sendfunc);
}

// true if all selected objects have this PCode
bool LLSelectMgr::selectionAllPCode(LLPCode code)
{
    struct f : public LLSelectedObjectFunctor
    {
        LLPCode mCode;
        f(const LLPCode& t) : mCode(t) {}
        virtual bool apply(LLViewerObject* object)
        {
            if (object->getPCode() != mCode)
            {
                return false;
            }
            return true;
        }
    } func(code);
    bool res = getSelection()->applyToObjects(&func);
    return res;
}

bool LLSelectMgr::selectionGetIncludeInSearch(bool* include_in_search_out)
{
    LLViewerObject *object = mSelectedObjects->getFirstRootObject();
    if (!object) return false;

    bool include_in_search = object->getIncludeInSearch();

    bool identical = true;

    for (LLObjectSelection::root_iterator iter = getSelection()->root_begin();
         iter != getSelection()->root_end(); iter++)
    {
        LLViewerObject* object = (*iter)->getObject();

        if ( include_in_search != object->getIncludeInSearch())
        {
            identical = false;
            break;
        }
    }

    *include_in_search_out = include_in_search;
    return identical;
}

void LLSelectMgr::selectionSetIncludeInSearch(bool include_in_search)
{
    LLViewerObject* object = NULL;
    for (LLObjectSelection::root_iterator iter = getSelection()->root_begin();
         iter != getSelection()->root_end(); iter++)
    {
        object = (*iter)->getObject();
        object->setIncludeInSearch(include_in_search);
    }
    sendListToRegions(
        "ObjectIncludeInSearch",
        packAgentAndSessionID,
        packObjectIncludeInSearch,
        logNoOp,
        &include_in_search,
        SEND_ONLY_ROOTS);
}

bool LLSelectMgr::selectionGetClickAction(U8 *out_action)
{
    LLViewerObject *object = mSelectedObjects->getFirstObject();
    if (!object)
    {
        return false;
    }

    U8 action = object->getClickAction();
    *out_action = action;

    struct f : public LLSelectedObjectFunctor
    {
        U8 mAction;
        f(const U8& t) : mAction(t) {}
        virtual bool apply(LLViewerObject* object)
        {
            if ( mAction != object->getClickAction())
            {
                return false;
            }
            return true;
        }
    } func(action);
    bool res = getSelection()->applyToObjects(&func);
    return res;
}

void LLSelectMgr::selectionSetClickAction(U8 action)
{
    struct f : public LLSelectedObjectFunctor
    {
        U8 mAction;
        f(const U8& t) : mAction(t) {}
        virtual bool apply(LLViewerObject* object)
        {
            object->setClickAction(mAction);
            return true;
        }
    } func(action);
    getSelection()->applyToObjects(&func);

    sendListToRegions("ObjectClickAction",
                      packAgentAndSessionID,
                      packObjectClickAction,
                      logNoOp,
                      &action,
                      SEND_INDIVIDUALS);
}


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

typedef std::pair<const std::string, const std::string> godlike_request_t;

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

    godlike_request_t data(request, param);
    if(!mSelectedObjects->getRootObjectCount())
    {
        LLMessageSystem* msg = gMessageSystem;
        msg->newMessage(message_type.c_str());
        LLSelectMgr::packGodlikeHead(&data);
        gAgent.sendReliableMessage();
    }
    else
    {
        sendListToRegions(message_type, packGodlikeHead, packObjectIDAsParam, logNoOp, &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());
    msg->addUUID("TransactionID", LLUUID::null);
    godlike_request_t* data = (godlike_request_t*)user_data;
    msg->nextBlock("MethodData");
    msg->addString("Method", data->first);
    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::logNoOp(LLSelectNode* node, void *)
{
}

// static
void LLSelectMgr::logAttachmentRequest(LLSelectNode* node, void *)
{
    LLAttachmentsMgr::instance().onAttachmentRequested(node->mItemID);
}

// static
void LLSelectMgr::logDetachRequest(LLSelectNode* node, void *)
{
    LLAttachmentsMgr::instance().onDetachRequested(node->mItemID);
}

// static
void LLSelectMgr::packObjectIDAsParam(LLSelectNode* node, void *)
{
    std::string buf = llformat("%u", node->getObject()->getLocalID());
    gMessageSystem->nextBlock("ParamList");
    gMessageSystem->addString("Parameter", buf);
}

//-----------------------------------------------------------------------------
// selectionTexScaleAutofit()
//-----------------------------------------------------------------------------
void LLSelectMgr::selectionTexScaleAutofit(F32 repeats_per_meter)
{
    struct f : public LLSelectedTEFunctor
    {
        F32 mRepeatsPerMeter;
        f(const F32& t) : mRepeatsPerMeter(t) {}
        bool apply(LLViewerObject* object, S32 te)
        {

            if (object->permModify())
            {
                // Compute S,T to axis mapping
                U32 s_axis, t_axis;
                if (!LLPrimitive::getTESTAxes(te, &s_axis, &t_axis))
                {
                    return true;
                }

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

                object->setTEScale(te, new_s, new_t);
            }
            return true;
        }
    } setfunc(repeats_per_meter);
    getSelection()->applyToTEs(&setfunc);

    LLSelectMgrSendFunctor sendfunc;
    getSelection()->applyToObjects(&sendfunc);
}



// 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)
{
    for (LLObjectSelection::iterator iter = getSelection()->begin();
         iter != getSelection()->end(); iter++)
    {
        LLSelectNode* selectNode = *iter;
        LLViewerObject* object = selectNode->getObject();

        if (!object)
        {
            continue;
        }

        if (!object->permModify())
        {
            continue;
        }

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

        bool send = false;

        for (U8 te_num = 0; te_num < object->getNumTEs(); te_num++)
        {
            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 (!LLPrimitive::getTESTAxes(te_num, &s_axis, &t_axis))
                {
                    continue;
                }

                LLVector3 object_scale = object->getScale();
                LLVector3 diffuse_scale_ratio  = selectNode->mTextureScaleRatios[te_num];

                // We like these to track together. NORSPEC-96
                //
                LLVector3 normal_scale_ratio   = diffuse_scale_ratio;
                LLVector3 specular_scale_ratio = diffuse_scale_ratio;

                // Apply new scale to face
                if (planar)
                {
                    F32 diffuse_scale_s = diffuse_scale_ratio.mV[s_axis]/object_scale.mV[s_axis];
                    F32 diffuse_scale_t = diffuse_scale_ratio.mV[t_axis]/object_scale.mV[t_axis];

                    F32 normal_scale_s = normal_scale_ratio.mV[s_axis]/object_scale.mV[s_axis];
                    F32 normal_scale_t = normal_scale_ratio.mV[t_axis]/object_scale.mV[t_axis];

                    F32 specular_scale_s = specular_scale_ratio.mV[s_axis]/object_scale.mV[s_axis];
                    F32 specular_scale_t = specular_scale_ratio.mV[t_axis]/object_scale.mV[t_axis];

                    object->setTEScale(te_num, diffuse_scale_s, diffuse_scale_t);

                    if (tep && !tep->getMaterialParams().isNull())
                    {
                        LLMaterialPtr orig = tep->getMaterialParams();
                        LLMaterialPtr p = gFloaterTools->getPanelFace()->createDefaultMaterial(orig);
                        p->setNormalRepeat(normal_scale_s, normal_scale_t);
                        p->setSpecularRepeat(specular_scale_s, specular_scale_t);

                        LLMaterialMgr::getInstance()->put(object->getID(), te_num, *p);
                    }
                }
                else
                {

                    F32 diffuse_scale_s = diffuse_scale_ratio.mV[s_axis]*object_scale.mV[s_axis];
                    F32 diffuse_scale_t = diffuse_scale_ratio.mV[t_axis]*object_scale.mV[t_axis];

                    F32 normal_scale_s = normal_scale_ratio.mV[s_axis]*object_scale.mV[s_axis];
                    F32 normal_scale_t = normal_scale_ratio.mV[t_axis]*object_scale.mV[t_axis];

                    F32 specular_scale_s = specular_scale_ratio.mV[s_axis]*object_scale.mV[s_axis];
                    F32 specular_scale_t = specular_scale_ratio.mV[t_axis]*object_scale.mV[t_axis];

                    object->setTEScale(te_num, diffuse_scale_s,diffuse_scale_t);

                    LLTextureEntry* tep = object->getTE(te_num);

                    if (tep && !tep->getMaterialParams().isNull())
                    {
                        LLMaterialPtr orig = tep->getMaterialParams();
                        LLMaterialPtr p = gFloaterTools->getPanelFace()->createDefaultMaterial(orig);

                        p->setNormalRepeat(normal_scale_s, normal_scale_t);
                        p->setSpecularRepeat(specular_scale_s, specular_scale_t);

                        LLMaterialMgr::getInstance()->put(object->getID(), te_num, *p);
                    }
                }

                if (tep->getGLTFMaterial())
                {
                    LLPointer<LLGLTFMaterial> material = tep->getGLTFMaterialOverride();
                    if (!material)
                    {
                        material = new LLGLTFMaterial();
                        tep->setGLTFMaterialOverride(material);
                    }

                    F32 scale_x = 1;
                    F32 scale_y = 1;

                    for (U32 i = 0; i < LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT; ++i)
                    {
                        LLVector3 scale_ratio = selectNode->mGLTFScaleRatios[te_num][i];

                        if (planar)
                        {
                            scale_x = scale_ratio.mV[s_axis] / object_scale.mV[s_axis];
                            scale_y = scale_ratio.mV[t_axis] / object_scale.mV[t_axis];
                        }
                        else
                        {
                            scale_x = scale_ratio.mV[s_axis] * object_scale.mV[s_axis];
                            scale_y = scale_ratio.mV[t_axis] * object_scale.mV[t_axis];
                        }
                        material->mTextureTransform[i].mScale.set(scale_x, scale_y);
                    }

                    LLFetchedGLTFMaterial* render_mat = (LLFetchedGLTFMaterial*)tep->getGLTFRenderMaterial();
                    if (render_mat)
                    {
                        render_mat->applyOverride(*material);
                    }

                    if (send_to_sim)
                    {
                        LLGLTFMaterialList::queueModify(object, te_num, material);
                    }
                }
                send = send_to_sim;
            }
        }

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

//-----------------------------------------------------------------------------
// selectGetAllRootsValid()
// Returns true if the viewer has information on all selected objects
//-----------------------------------------------------------------------------
bool LLSelectMgr::selectGetAllRootsValid()
{
    for (LLObjectSelection::root_iterator iter = getSelection()->root_begin();
         iter != getSelection()->root_end(); ++iter )
    {
        LLSelectNode* node = *iter;
        if( !node->mValid )
        {
            return false;
        }
    }
    return true;
}


//-----------------------------------------------------------------------------
// selectGetAllValid()
// Returns true if the viewer has information on all selected objects
//-----------------------------------------------------------------------------
bool LLSelectMgr::selectGetAllValid()
{
    for (LLObjectSelection::iterator iter = getSelection()->begin();
         iter != getSelection()->end(); ++iter )
    {
        LLSelectNode* node = *iter;
        if( !node->mValid )
        {
            return false;
        }
    }
    return true;
}

//-----------------------------------------------------------------------------
// selectGetAllValidAndObjectsFound() - return true if selections are
// valid and objects are found.
//
// For EXT-3114 - same as selectGetModify() without the modify check.
//-----------------------------------------------------------------------------
bool LLSelectMgr::selectGetAllValidAndObjectsFound()
{
    for (LLObjectSelection::iterator iter = getSelection()->begin();
         iter != getSelection()->end(); iter++ )
    {
        LLSelectNode* node = *iter;
        LLViewerObject* object = node->getObject();
        if( !object || !node->mValid )
        {
            return false;
        }
    }
    return true;
}

//-----------------------------------------------------------------------------
// selectGetModify() - return true if current agent can modify all
// selected objects.
//-----------------------------------------------------------------------------
bool LLSelectMgr::selectGetModify()
{
    for (LLObjectSelection::iterator iter = getSelection()->begin();
         iter != getSelection()->end(); iter++ )
    {
        LLSelectNode* node = *iter;
        LLViewerObject* object = node->getObject();
        if( !object || !node->mValid )
        {
            return false;
        }
        if( !object->permModify() )
        {
            return false;
        }
    }
    return true;
}

//-----------------------------------------------------------------------------
// selectGetRootsModify() - return true if current agent can modify all
// selected root objects.
//-----------------------------------------------------------------------------
bool LLSelectMgr::selectGetRootsModify()
{
    for (LLObjectSelection::root_iterator iter = getSelection()->root_begin();
         iter != getSelection()->root_end(); iter++ )
    {
        LLSelectNode* node = *iter;
        LLViewerObject* object = node->getObject();
        if( !node->mValid )
        {
            return false;
        }
        if( !object->permModify() )
        {
            return false;
        }
    }

    return true;
}

//-----------------------------------------------------------------------------
// selectGetSameRegion() - return true if all objects are in same region
//-----------------------------------------------------------------------------
bool LLSelectMgr::selectGetSameRegion()
{
    if (getSelection()->isEmpty())
    {
        return true;
    }
    LLViewerObject* object = getSelection()->getFirstObject();
    if (!object)
    {
        return false;
    }
    LLViewerRegion* current_region = object->getRegion();

    for (LLObjectSelection::root_iterator iter = getSelection()->root_begin();
        iter != getSelection()->root_end(); iter++)
    {
        LLSelectNode* node = *iter;
        object = node->getObject();
        if (!node->mValid || !object || current_region != object->getRegion())
        {
            return false;
        }
    }

    return true;
}

//-----------------------------------------------------------------------------
// selectGetNonPermanentEnforced() - return true if all objects are not
// permanent enforced
//-----------------------------------------------------------------------------
bool LLSelectMgr::selectGetNonPermanentEnforced()
{
    for (LLObjectSelection::iterator iter = getSelection()->begin();
         iter != getSelection()->end(); iter++ )
    {
        LLSelectNode* node = *iter;
        LLViewerObject* object = node->getObject();
        if( !object || !node->mValid )
        {
            return false;
        }
        if( object->isPermanentEnforced())
        {
            return false;
        }
    }
    return true;
}

//-----------------------------------------------------------------------------
// selectGetRootsNonPermanentEnforced() - return true if all root objects are
// not permanent enforced
//-----------------------------------------------------------------------------
bool LLSelectMgr::selectGetRootsNonPermanentEnforced()
{
    for (LLObjectSelection::root_iterator iter = getSelection()->root_begin();
         iter != getSelection()->root_end(); iter++ )
    {
        LLSelectNode* node = *iter;
        LLViewerObject* object = node->getObject();
        if( !node->mValid )
        {
            return false;
        }
        if( object->isPermanentEnforced())
        {
            return false;
        }
    }

    return true;
}

//-----------------------------------------------------------------------------
// selectGetPermanent() - return true if all objects are permanent
//-----------------------------------------------------------------------------
bool LLSelectMgr::selectGetPermanent()
{
    for (LLObjectSelection::iterator iter = getSelection()->begin();
         iter != getSelection()->end(); iter++ )
    {
        LLSelectNode* node = *iter;
        LLViewerObject* object = node->getObject();
        if( !object || !node->mValid )
        {
            return false;
        }
        if( !object->flagObjectPermanent())
        {
            return false;
        }
    }
    return true;
}

//-----------------------------------------------------------------------------
// selectGetRootsPermanent() - return true if all root objects are
// permanent
//-----------------------------------------------------------------------------
bool LLSelectMgr::selectGetRootsPermanent()
{
    for (LLObjectSelection::root_iterator iter = getSelection()->root_begin();
         iter != getSelection()->root_end(); iter++ )
    {
        LLSelectNode* node = *iter;
        LLViewerObject* object = node->getObject();
        if( !node->mValid )
        {
            return false;
        }
        if( !object->flagObjectPermanent())
        {
            return false;
        }
    }

    return true;
}

//-----------------------------------------------------------------------------
// selectGetCharacter() - return true if all objects are character
//-----------------------------------------------------------------------------
bool LLSelectMgr::selectGetCharacter()
{
    for (LLObjectSelection::iterator iter = getSelection()->begin();
         iter != getSelection()->end(); iter++ )
    {
        LLSelectNode* node = *iter;
        LLViewerObject* object = node->getObject();
        if( !object || !node->mValid )
        {
            return false;
        }
        if( !object->flagCharacter())
        {
            return false;
        }
    }
    return true;
}

//-----------------------------------------------------------------------------
// selectGetRootsCharacter() - return true if all root objects are
// character
//-----------------------------------------------------------------------------
bool LLSelectMgr::selectGetRootsCharacter()
{
    for (LLObjectSelection::root_iterator iter = getSelection()->root_begin();
         iter != getSelection()->root_end(); iter++ )
    {
        LLSelectNode* node = *iter;
        LLViewerObject* object = node->getObject();
        if( !node->mValid )
        {
            return false;
        }
        if( !object->flagCharacter())
        {
            return false;
        }
    }

    return true;
}

//-----------------------------------------------------------------------------
// selectGetNonPathfinding() - return true if all objects are not pathfinding
//-----------------------------------------------------------------------------
bool LLSelectMgr::selectGetNonPathfinding()
{
    for (LLObjectSelection::iterator iter = getSelection()->begin();
         iter != getSelection()->end(); iter++ )
    {
        LLSelectNode* node = *iter;
        LLViewerObject* object = node->getObject();
        if( !object || !node->mValid )
        {
            return false;
        }
        if( object->flagObjectPermanent() || object->flagCharacter())
        {
            return false;
        }
    }
    return true;
}

//-----------------------------------------------------------------------------
// selectGetRootsNonPathfinding() - return true if all root objects are not
// pathfinding
//-----------------------------------------------------------------------------
bool LLSelectMgr::selectGetRootsNonPathfinding()
{
    for (LLObjectSelection::root_iterator iter = getSelection()->root_begin();
         iter != getSelection()->root_end(); iter++ )
    {
        LLSelectNode* node = *iter;
        LLViewerObject* object = node->getObject();
        if( !node->mValid )
        {
            return false;
        }
        if( object->flagObjectPermanent() || object->flagCharacter())
        {
            return false;
        }
    }

    return true;
}

//-----------------------------------------------------------------------------
// selectGetNonPermanent() - return true if all objects are not permanent
//-----------------------------------------------------------------------------
bool LLSelectMgr::selectGetNonPermanent()
{
    for (LLObjectSelection::iterator iter = getSelection()->begin();
         iter != getSelection()->end(); iter++ )
    {
        LLSelectNode* node = *iter;
        LLViewerObject* object = node->getObject();
        if( !object || !node->mValid )
        {
            return false;
        }
        if( object->flagObjectPermanent())
        {
            return false;
        }
    }
    return true;
}

//-----------------------------------------------------------------------------
// selectGetRootsNonPermanent() - return true if all root objects are not
// permanent
//-----------------------------------------------------------------------------
bool LLSelectMgr::selectGetRootsNonPermanent()
{
    for (LLObjectSelection::root_iterator iter = getSelection()->root_begin();
         iter != getSelection()->root_end(); iter++ )
    {
        LLSelectNode* node = *iter;
        LLViewerObject* object = node->getObject();
        if( !node->mValid )
        {
            return false;
        }
        if( object->flagObjectPermanent())
        {
            return false;
        }
    }

    return true;
}

//-----------------------------------------------------------------------------
// selectGetNonCharacter() - return true if all objects are not character
//-----------------------------------------------------------------------------
bool LLSelectMgr::selectGetNonCharacter()
{
    for (LLObjectSelection::iterator iter = getSelection()->begin();
         iter != getSelection()->end(); iter++ )
    {
        LLSelectNode* node = *iter;
        LLViewerObject* object = node->getObject();
        if( !object || !node->mValid )
        {
            return false;
        }
        if( object->flagCharacter())
        {
            return false;
        }
    }
    return true;
}

//-----------------------------------------------------------------------------
// selectGetRootsNonCharacter() - return true if all root objects are not
// character
//-----------------------------------------------------------------------------
bool LLSelectMgr::selectGetRootsNonCharacter()
{
    for (LLObjectSelection::root_iterator iter = getSelection()->root_begin();
         iter != getSelection()->root_end(); iter++ )
    {
        LLSelectNode* node = *iter;
        LLViewerObject* object = node->getObject();
        if( !node->mValid )
        {
            return false;
        }
        if( object->flagCharacter())
        {
            return false;
        }
    }

    return true;
}


//-----------------------------------------------------------------------------
// selectGetEditableLinksets() - return true if all objects are editable
//                               pathfinding linksets
//-----------------------------------------------------------------------------
bool LLSelectMgr::selectGetEditableLinksets()
{
    for (LLObjectSelection::iterator iter = getSelection()->begin();
         iter != getSelection()->end(); iter++ )
    {
        LLSelectNode* node = *iter;
        LLViewerObject* object = node->getObject();
        if( !object || !node->mValid )
        {
            return false;
        }
        if (object->flagUsePhysics() ||
            object->flagTemporaryOnRez() ||
            object->flagCharacter() ||
            object->flagVolumeDetect() ||
            object->flagAnimSource() ||
            (object->getRegion() != gAgent.getRegion()) ||
            (!gAgent.isGodlike() &&
            !gAgent.canManageEstate() &&
            !object->permYouOwner() &&
            !object->permMove()))
        {
            return false;
        }
    }
    return true;
}

//-----------------------------------------------------------------------------
// selectGetViewableCharacters() - return true if all objects are characters
//                        viewable within the pathfinding characters floater
//-----------------------------------------------------------------------------
bool LLSelectMgr::selectGetViewableCharacters()
{
    for (LLObjectSelection::iterator iter = getSelection()->begin();
         iter != getSelection()->end(); iter++ )
    {
        LLSelectNode* node = *iter;
        LLViewerObject* object = node->getObject();
        if( !object || !node->mValid )
        {
            return false;
        }
        if( !object->flagCharacter() ||
            (object->getRegion() != gAgent.getRegion()))
        {
            return false;
        }
    }
    return true;
}

//-----------------------------------------------------------------------------
// selectGetRootsTransfer() - return true if current agent can transfer all
// selected root objects.
//-----------------------------------------------------------------------------
bool LLSelectMgr::selectGetRootsTransfer()
{
    for (LLObjectSelection::root_iterator iter = getSelection()->root_begin();
         iter != getSelection()->root_end(); iter++ )
    {
        LLSelectNode* node = *iter;
        LLViewerObject* object = node->getObject();
        if( !node->mValid )
        {
            return false;
        }
        if(!object->permTransfer())
        {
            return false;
        }
    }
    return true;
}

//-----------------------------------------------------------------------------
// selectGetRootsCopy() - return true if current agent can copy all
// selected root objects.
//-----------------------------------------------------------------------------
bool LLSelectMgr::selectGetRootsCopy()
{
    for (LLObjectSelection::root_iterator iter = getSelection()->root_begin();
         iter != getSelection()->root_end(); iter++ )
    {
        LLSelectNode* node = *iter;
        LLViewerObject* object = node->getObject();
        if( !node->mValid )
        {
            return false;
        }
        if(!object->permCopy())
        {
            return false;
        }
    }
    return true;
}

struct LLSelectGetFirstTest
{
    LLSelectGetFirstTest() : mIdentical(true), mFirst(true) { }
    virtual ~LLSelectGetFirstTest() { }

    // returns false to break out of the iteration.
    bool checkMatchingNode(LLSelectNode* node)
    {
        if (!node || !node->mValid)
        {
            return false;
        }

        if (mFirst)
        {
            mFirstValue = getValueFromNode(node);
            mFirst = false;
        }
        else
        {
            if ( mFirstValue != getValueFromNode(node) )
            {
                mIdentical = false;
                // stop testing once we know not all selected are identical.
                return false;
            }
        }
        // continue testing.
        return true;
    }

    bool mIdentical;
    LLUUID mFirstValue;

protected:
    virtual const LLUUID& getValueFromNode(LLSelectNode* node) = 0;

private:
    bool mFirst;
};

void LLSelectMgr::getFirst(LLSelectGetFirstTest* test)
{
    if (gSavedSettings.getBOOL("EditLinkedParts"))
    {
        for (LLObjectSelection::valid_iterator iter = getSelection()->valid_begin();
            iter != getSelection()->valid_end(); ++iter )
        {
            if (!test->checkMatchingNode(*iter))
            {
                break;
            }
        }
    }
    else
    {
        for (LLObjectSelection::root_object_iterator iter = getSelection()->root_object_begin();
            iter != getSelection()->root_object_end(); ++iter )
        {
            if (!test->checkMatchingNode(*iter))
            {
                break;
            }
        }
    }
}

//-----------------------------------------------------------------------------
// selectGetCreator()
// Creator information only applies to roots unless editing linked parts.
//-----------------------------------------------------------------------------
struct LLSelectGetFirstCreator : public LLSelectGetFirstTest
{
protected:
    virtual const LLUUID& getValueFromNode(LLSelectNode* node)
    {
        return node->mPermissions->getCreator();
    }
};

bool LLSelectMgr::selectGetCreator(LLUUID& result_id, std::string& name)
{
    LLSelectGetFirstCreator test;
    getFirst(&test);

    if (test.mFirstValue.isNull())
    {
        name = LLTrans::getString("AvatarNameNobody");
        return false;
    }

    result_id = test.mFirstValue;

    if (test.mIdentical)
    {
        name = LLSLURL("agent", test.mFirstValue, "inspect").getSLURLString();
    }
    else
    {
        name = LLTrans::getString("AvatarNameMultiple");
    }

    return test.mIdentical;
}

//-----------------------------------------------------------------------------
// selectGetOwner()
// Owner information only applies to roots unless editing linked parts.
//-----------------------------------------------------------------------------
struct LLSelectGetFirstOwner : public LLSelectGetFirstTest
{
protected:
    virtual const LLUUID& getValueFromNode(LLSelectNode* node)
    {
        // Don't use 'getOwnership' since we return a reference, not a copy.
        // Will return LLUUID::null if unowned (which is not allowed and should never happen.)
        return node->mPermissions->isGroupOwned() ? node->mPermissions->getGroup() : node->mPermissions->getOwner();
    }
};

bool LLSelectMgr::selectGetOwner(LLUUID& result_id, std::string& name)
{
    LLSelectGetFirstOwner test;
    getFirst(&test);

    if (test.mFirstValue.isNull())
    {
        return false;
    }

    result_id = test.mFirstValue;

    if (test.mIdentical)
    {
        bool group_owned = selectIsGroupOwned();
        if (group_owned)
        {
            name = LLSLURL("group", test.mFirstValue, "inspect").getSLURLString();
        }
        else
        {
            name = LLSLURL("agent", test.mFirstValue, "inspect").getSLURLString();
        }
    }
    else
    {
        name = LLTrans::getString("AvatarNameMultiple");
    }

    return test.mIdentical;
}

//-----------------------------------------------------------------------------
// selectGetLastOwner()
// Owner information only applies to roots unless editing linked parts.
//-----------------------------------------------------------------------------
struct LLSelectGetFirstLastOwner : public LLSelectGetFirstTest
{
protected:
    virtual const LLUUID& getValueFromNode(LLSelectNode* node)
    {
        return node->mPermissions->getLastOwner();
    }
};

bool LLSelectMgr::selectGetLastOwner(LLUUID& result_id, std::string& name)
{
    LLSelectGetFirstLastOwner test;
    getFirst(&test);

    if (test.mFirstValue.isNull())
    {
        return false;
    }

    result_id = test.mFirstValue;

    if (test.mIdentical)
    {
        name = LLSLURL("agent", test.mFirstValue, "inspect").getSLURLString();
    }
    else
    {
        name.assign( "" );
    }

    return test.mIdentical;
}

//-----------------------------------------------------------------------------
// selectGetGroup()
// Group information only applies to roots unless editing linked parts.
//-----------------------------------------------------------------------------
struct LLSelectGetFirstGroup : public LLSelectGetFirstTest
{
protected:
    virtual const LLUUID& getValueFromNode(LLSelectNode* node)
    {
        return node->mPermissions->getGroup();
    }
};

bool LLSelectMgr::selectGetGroup(LLUUID& result_id)
{
    LLSelectGetFirstGroup test;
    getFirst(&test);

    result_id = test.mFirstValue;
    return test.mIdentical;
}

//-----------------------------------------------------------------------------
// selectIsGroupOwned()
// Only operates on root nodes unless editing linked parts.
// Returns true if the first selected is group owned.
//-----------------------------------------------------------------------------
struct LLSelectGetFirstGroupOwner : public LLSelectGetFirstTest
{
protected:
    virtual const LLUUID& getValueFromNode(LLSelectNode* node)
    {
        if (node->mPermissions->isGroupOwned())
        {
            return node->mPermissions->getGroup();
        }
        return LLUUID::null;
    }
};

bool LLSelectMgr::selectIsGroupOwned()
{
    LLSelectGetFirstGroupOwner test;
    getFirst(&test);

    return test.mFirstValue.notNull();
}

//-----------------------------------------------------------------------------
// 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)
{
    U32 mask;
    U32 mask_and    = 0xffffffff;
    U32 mask_or     = 0x00000000;
    bool all_valid  = false;

    for (LLObjectSelection::root_iterator iter = getSelection()->root_begin();
         iter != getSelection()->root_end(); iter++)
    {
        LLSelectNode* node = *iter;

        if (!node->mValid)
        {
            all_valid = false;
            break;
        }

        all_valid = true;

        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::selectGetPermissions(LLPermissions& result_perm)
{
    bool first = true;
    LLPermissions perm;
    for (LLObjectSelection::root_iterator iter = getSelection()->root_begin();
         iter != getSelection()->root_end(); iter++ )
    {
        LLSelectNode* node = *iter;
        if (!node->mValid)
        {
            return false;
        }

        if (first)
        {
            perm = *(node->mPermissions);
            first = false;
        }
        else
        {
            perm.accumulate(*(node->mPermissions));
        }
    }

    result_perm = perm;

    return true;
}


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 (LLObjectSelection::iterator iter = getSelection()->begin();
         iter != getSelection()->end(); iter++)
    {
        LLViewerObject* obj = (*iter)->getObject();

        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;
    }

    LLNotification::Params params("ConfirmObjectDeleteLock");
    params.functor.function(boost::bind(&LLSelectMgr::confirmDelete, _1, _2, getSelection()));

    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
            params.name("ConfirmObjectDeleteLock");
        }
        else if(!locked_but_deleteable_object && no_copy_but_deleteable_object && all_owned_by_you)
        {
            //No Copy only
            params.name("ConfirmObjectDeleteNoCopy");
        }
        else if(!locked_but_deleteable_object && !no_copy_but_deleteable_object && !all_owned_by_you)
        {
            //not owned only
            params.name("ConfirmObjectDeleteNoOwn");
        }
        else if(locked_but_deleteable_object && no_copy_but_deleteable_object && all_owned_by_you)
        {
            //locked and no copy
            params.name("ConfirmObjectDeleteLockNoCopy");
        }
        else if(locked_but_deleteable_object && !no_copy_but_deleteable_object && !all_owned_by_you)
        {
            //locked and not owned
            params.name("ConfirmObjectDeleteLockNoOwn");
        }
        else if(!locked_but_deleteable_object && no_copy_but_deleteable_object && !all_owned_by_you)
        {
            //no copy and not owned
            params.name("ConfirmObjectDeleteNoCopyNoOwn");
        }
        else
        {
            //locked, no copy and not owned
            params.name("ConfirmObjectDeleteLockNoCopyNoOwn");
        }

        LLNotifications::instance().add(params);
    }
    else
    {
        LLNotifications::instance().forceResponse(params, 0);
    }
}

// static
bool LLSelectMgr::confirmDelete(const LLSD& notification, const LLSD& response, LLObjectSelectionHandle handle)
{
    S32 option = LLNotification::getSelectedOption(notification, response);
    if (!handle->getObjectCount())
    {
        LL_WARNS() << "Nothing to delete!" << LL_ENDL;
        return false;
    }

    switch(option)
    {
    case 0:
        {
            // TODO: Make sure you have delete permissions on all of them.
            const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH);
            // attempt to derez into the trash.
            LLDeRezInfo info(DRD_TRASH, trash_id);
            LLSelectMgr::getInstance()->sendListToRegions("DeRezObject",
                                                          packDeRezHeader,
                                                          packObjectLocalID,
                                                          logNoOp,
                                                          (void*) &info,
                                                          SEND_ONLY_ROOTS);
            // VEFFECT: Delete Object - one effect for all deletes
            if (LLSelectMgr::getInstance()->mSelectedObjects->mSelectType != SELECT_TYPE_HUD)
            {
                LLHUDEffectSpiral *effectp = (LLHUDEffectSpiral *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_POINT, true);
                effectp->setPositionGlobal( LLSelectMgr::getInstance()->getSelectionCenterGlobal() );
                effectp->setColor(LLColor4U(gAgent.getEffectColor()));
                F32 duration = 0.5f;
                duration += LLSelectMgr::getInstance()->mSelectedObjects->getObjectCount() / 64.f;
                effectp->setDuration(duration);
            }

            gAgentCamera.setLookAt(LOOKAT_TARGET_CLEAR);

            // Keep track of how many objects have been deleted.
            add(LLStatViewer::DELETE_OBJECT, LLSelectMgr::getInstance()->mSelectedObjects->getObjectCount());
        }
        break;
    case 1:
    default:
        break;
    }
    return false;
}


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

bool LLSelectMgr::selectGetEditMoveLinksetPermissions(bool &move, bool &modify)
{
    move = true;
    modify = true;
    bool selecting_linked_set = !gSavedSettings.getBOOL("EditLinkedParts");

    for (LLObjectSelection::iterator iter = getSelection()->begin();
        iter != getSelection()->end(); iter++)
    {
        LLSelectNode* nodep = *iter;
        LLViewerObject* object = nodep->getObject();
        if (!object || !nodep->mValid)
        {
            move = false;
            modify = false;
            return false;
        }

        LLViewerObject *root_object = object->getRootEdit();
        bool this_object_movable = false;
        if (object->permMove() && !object->isPermanentEnforced() &&
            ((root_object == NULL) || !root_object->isPermanentEnforced()) &&
            (object->permModify() || selecting_linked_set))
        {
            this_object_movable = true;
        }
        move = move && this_object_movable;
        modify = modify && object->permModify();
    }

    return true;
}

void LLSelectMgr::selectGetAggregateSaleInfo(U32 &num_for_sale,
                                             bool &is_for_sale_mixed,
                                             bool &is_sale_price_mixed,
                                             S32 &total_sale_price,
                                             S32 &individual_sale_price)
{
    num_for_sale = 0;
    is_for_sale_mixed = false;
    is_sale_price_mixed = false;
    total_sale_price = 0;
    individual_sale_price = 0;


    // Empty set.
    if (getSelection()->root_begin() == getSelection()->root_end())
        return;

    LLSelectNode *node = *(getSelection()->root_begin());
    const bool first_node_for_sale = node->mSaleInfo.isForSale();
    const S32 first_node_sale_price = node->mSaleInfo.getSalePrice();

    for (LLObjectSelection::root_iterator iter = getSelection()->root_begin();
         iter != getSelection()->root_end(); iter++)
    {
        LLSelectNode* node = *iter;
        const bool node_for_sale = node->mSaleInfo.isForSale();
        const S32 node_sale_price = node->mSaleInfo.getSalePrice();

        // Set mixed if the fields don't match the first node's fields.
        if (node_for_sale != first_node_for_sale)
            is_for_sale_mixed = true;
        if (node_sale_price != first_node_sale_price)
            is_sale_price_mixed = true;

        if (node_for_sale)
        {
            total_sale_price += node_sale_price;
            num_for_sale ++;
        }
    }

    individual_sale_price = first_node_sale_price;
    if (is_for_sale_mixed)
    {
        is_sale_price_mixed = true;
        individual_sale_price = 0;
    }
}

// returns true if all nodes are valid. method also stores an
// accumulated sale info.
bool LLSelectMgr::selectGetSaleInfo(LLSaleInfo& result_sale_info)
{
    bool first = true;
    LLSaleInfo sale_info;
    for (LLObjectSelection::root_iterator iter = getSelection()->root_begin();
         iter != getSelection()->root_end(); iter++ )
    {
        LLSelectNode* node = *iter;
        if (!node->mValid)
        {
            return false;
        }

        if (first)
        {
            sale_info = node->mSaleInfo;
            first = false;
        }
        else
        {
            sale_info.accumulate(node->mSaleInfo);
        }
    }

    result_sale_info = sale_info;

    return true;
}

bool LLSelectMgr::selectGetAggregatePermissions(LLAggregatePermissions& result_perm)
{
    bool first = true;
    LLAggregatePermissions perm;
    for (LLObjectSelection::root_iterator iter = getSelection()->root_begin();
         iter != getSelection()->root_end(); iter++ )
    {
        LLSelectNode* node = *iter;
        if (!node->mValid)
        {
            return false;
        }

        if (first)
        {
            perm = node->mAggregatePerm;
            first = false;
        }
        else
        {
            perm.aggregate(node->mAggregatePerm);
        }
    }

    result_perm = perm;

    return true;
}

bool LLSelectMgr::selectGetAggregateTexturePermissions(LLAggregatePermissions& result_perm)
{
    bool first = true;
    LLAggregatePermissions perm;
    for (LLObjectSelection::root_iterator iter = getSelection()->root_begin();
         iter != getSelection()->root_end(); iter++ )
    {
        LLSelectNode* node = *iter;
        if (!node->mValid)
        {
            return false;
        }

        LLAggregatePermissions t_perm = node->getObject()->permYouOwner() ? node->mAggregateTexturePermOwner : node->mAggregateTexturePerm;
        if (first)
        {
            perm = t_perm;
            first = false;
        }
        else
        {
            perm.aggregate(t_perm);
        }
    }

    result_perm = perm;

    return true;
}

bool LLSelectMgr::isMovableAvatarSelected()
{
    if (mAllowSelectAvatar && getSelection()->getObjectCount() == 1)
    {
        // nothing but avatar should be selected, so check that
        // there is only one selected object and it is a root
        LLViewerObject* obj = getSelection()->getFirstRootObject();
        return obj && obj->isAvatar() && getSelection()->getFirstMoveableNode(true);
    }
    return false;
}

//--------------------------------------------------------------------
// 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 (mSelectedObjects->isAttachment())
    {
        //RN: do not duplicate attachments
        make_ui_sound("UISndInvalidOp");
        return;
    }
    if (!canDuplicate())
    {
        LLSelectNode* node = getSelection()->getFirstRootNode(NULL, true);
        if (node)
        {
            LLSD args;
            args["OBJ_NAME"] = node->mName;
            LLNotificationsUtil::add("NoCopyPermsNoObject", args);
            return;
        }
    }
    LLDuplicateData data;

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

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

    if (select_copy)
    {
        // the new copy will be coming in selected
        deselectAll();
    }
    else
    {
        for (LLObjectSelection::root_iterator iter = getSelection()->root_begin();
             iter != getSelection()->root_end(); iter++ )
        {
            LLSelectNode* node = *iter;
            node->mDuplicated = true;
            node->mDuplicatePos = node->getObject()->getPositionGlobal();
            node->mDuplicateRot = node->getObject()->getRotation();
        }
    }
}

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

    std::vector<LLViewerObject*> non_duplicated_objects;

    for (LLObjectSelection::root_iterator iter = getSelection()->root_begin();
         iter != getSelection()->root_end(); iter++ )
    {
        LLSelectNode* node = *iter;
        if (!node->mDuplicated)
        {
            non_duplicated_objects.push_back(node->getObject());
        }
    }

    // make sure only previously duplicated objects are selected
    for (std::vector<LLViewerObject*>::iterator iter = non_duplicated_objects.begin();
         iter != non_duplicated_objects.end(); ++iter)
    {
        LLViewerObject* objectp = *iter;
        deselectObjectAndFamily(objectp);
    }

    // duplicate objects in place
    LLDuplicateData data;

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

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

    // move current selection based on delta from duplication position and update duplication position
    for (LLObjectSelection::root_iterator iter = getSelection()->root_begin();
         iter != getSelection()->root_end(); iter++ )
    {
        LLSelectNode* node = *iter;
        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 (mSelectedObjects->isAttachment())
    {
        // 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, logNoOp, &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("EditLinkedParts") && !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,
        logNoOp,
        &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)
    {
        htolememcpy(&data[offset], &(object->getPosition().mV), MVT_LLVector3, 12);
        offset += 12;
    }
    if (type & UPD_ROTATION)
    {
        LLQuaternion quat = object->getRotation();
        LLVector3 vec = quat.packToVector3();
        htolememcpy(&data[offset], &(vec.mV), MVT_LLQuaternion, 12);
        offset += 12;
    }
    if (type & UPD_SCALE)
    {
        //LL_INFOS() << "Sending object scale " << object->getScale() << LL_ENDL;
        htolememcpy(&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, logNoOp, &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, logNoOp, &local_group_id, SEND_ONLY_ROOTS);
}


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

struct LLBuyData
{
    std::vector<LLViewerObject*> mObjectsSent;
    LLUUID mCategoryID;
    LLSaleInfo mSaleInfo;
};

// *NOTE: does not 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, logNoOp, &buy, SEND_ONLY_ROOTS);
}

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

    LLViewerObject* object = node->getObject();
    if (std::find(buy->mObjectsSent.begin(), buy->mObjectsSent.end(), object) == buy->mObjectsSent.end())
    {
        buy->mObjectsSent.push_back(object);
        gMessageSystem->nextBlockFast(_PREHASH_ObjectData);
        gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, object->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::selectionSetObjectPermissions(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, logNoOp, &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()
{
    LL_ERRS() << "Not implemented" << LL_ENDL;
}
*/

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

    // Zap the angular velocity, as the sim will set it to zero
    for (LLObjectSelection::iterator iter = mSelectedObjects->begin();
         iter != mSelectedObjects->end(); iter++ )
    {
        LLViewerObject *objectp = (*iter)->getObject();
        objectp->setAngularVelocity( 0,0,0 );
        objectp->setVelocity( 0,0,0 );
    }

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

    removeAll();

    mLastSentSelectionCenterGlobal.clearVec();

    updatePointAt();
}

void LLSelectMgr::deselectAllForStandingUp()
{
    /*
    This function is similar deselectAll() except for the first if statement
    which was removed. This is needed as a workaround for DEV-2854
    */

    // Zap the angular velocity, as the sim will set it to zero
    for (LLObjectSelection::iterator iter = mSelectedObjects->begin();
         iter != mSelectedObjects->end(); iter++ )
    {
        LLViewerObject *objectp = (*iter)->getObject();
        objectp->setAngularVelocity( 0,0,0 );
        objectp->setVelocity( 0,0,0 );
    }

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

    removeAll();

    mLastSentSelectionCenterGlobal.clearVec();

    updatePointAt();
}

void LLSelectMgr::deselectUnused()
{
    // no more outstanding references to this selection
    if (mSelectedObjects->getNumRefs() == 1)
    {
        deselectAll();
    }
}

void LLSelectMgr::convertTransient()
{
    LLObjectSelection::iterator node_it;
    for (node_it = mSelectedObjects->begin(); node_it != mSelectedObjects->end(); ++node_it)
    {
        LLSelectNode *nodep = *node_it;
        nodep->setTransient(false);
    }
}

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

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

    LLVector3d selectionCenter = getSelectionCenterGlobal();
    if (gSavedSettings.getBOOL("LimitSelectDistance")
        && (!mSelectedObjects->getPrimaryObject() || !mSelectedObjects->getPrimaryObject()->isAvatar())
        && (mSelectedObjects->getPrimaryObject() != LLViewerMediaFocus::getInstance()->getFocusedObject())
        && !mSelectedObjects->isAttachment()
        && !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 (mDebugSelectMgr)
            {
                LL_INFOS() << "Selection manager: auto-deselecting, select_dist = " << (F32) sqrt(select_dist_sq) << LL_ENDL;
                LL_INFOS() << "agent pos global = " << gAgent.getPositionGlobal() << LL_ENDL;
                LL_INFOS() << "selection pos global = " << selectionCenter << LL_ENDL;
            }

            deselectAll();
        }
    }
}


void LLSelectMgr::selectionSetObjectName(const std::string& name)
{
    std::string name_copy(name);

    // we only work correctly if 1 object is selected.
    if(mSelectedObjects->getRootObjectCount() == 1)
    {
        sendListToRegions("ObjectName",
                          packAgentAndSessionID,
                          packObjectName,
                          logNoOp,
                          (void*)(&name_copy),
                          SEND_ONLY_ROOTS);
    }
    else if(mSelectedObjects->getObjectCount() == 1)
    {
        sendListToRegions("ObjectName",
                          packAgentAndSessionID,
                          packObjectName,
                          logNoOp,
                          (void*)(&name_copy),
                          SEND_INDIVIDUALS);
    }
}

void LLSelectMgr::selectionSetObjectDescription(const std::string& desc)
{
    std::string desc_copy(desc);

    // we only work correctly if 1 object is selected.
    if(mSelectedObjects->getRootObjectCount() == 1)
    {
        sendListToRegions("ObjectDescription",
                          packAgentAndSessionID,
                          packObjectDescription,
                          logNoOp,
                          (void*)(&desc_copy),
                          SEND_ONLY_ROOTS);
    }
    else if(mSelectedObjects->getObjectCount() == 1)
    {
        sendListToRegions("ObjectDescription",
                          packAgentAndSessionID,
                          packObjectDescription,
                          logNoOp,
                          (void*)(&desc_copy),
                          SEND_INDIVIDUALS);
    }
}

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

void LLSelectMgr::selectionSetObjectSaleInfo(const LLSaleInfo& sale_info)
{
    sendListToRegions("ObjectSaleInfo",
                      packAgentAndSessionID,
                      packObjectSaleInfo,
                      logNoOp,
                      (void*)(&sale_info),
                      SEND_ONLY_ROOTS);
}

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

void LLSelectMgr::sendAttach(U8 attachment_point, bool replace)
{
    sendAttach(mSelectedObjects, attachment_point, replace);
}

void LLSelectMgr::sendAttach(LLObjectSelectionHandle selection_handle, U8 attachment_point, bool replace)
{
    if (selection_handle.isNull())
    {
        return;
    }

    LLViewerObject* attach_object = selection_handle->getFirstRootObject();

    if (!attach_object || !isAgentAvatarValid() || selection_handle->mSelectType != SELECT_TYPE_WORLD)
    {
        return;
    }

    bool build_mode = LLToolMgr::getInstance()->inEdit();
    // Special case: Attach to default location for this object.
    if (0 == attachment_point ||
        get_if_there(gAgentAvatarp->mAttachmentPoints, (S32)attachment_point, (LLViewerJointAttachment*)NULL))
    {
        if (!replace || attachment_point != 0)
        {
            // If we know the attachment point then we got here by clicking an
            // "Attach to..." context menu item, so we should add, not replace.
            attachment_point |= ATTACHMENT_ADD;
        }

        sendListToRegions(
            selection_handle,
            "ObjectAttach",
            packAgentIDAndSessionAndAttachment,
            packObjectIDAndRotation,
            logAttachmentRequest,
            &attachment_point,
            SEND_ONLY_ROOTS );
        if (!build_mode)
        {
            // After "ObjectAttach" server will unsubscribe us from properties updates
            // so either deselect objects or resend selection after attach packet reaches server
            // In case of build_mode LLPanelObjectInventory::refresh() will deal with selection
            // Still unsubscribe even in case selection_handle is not current selection
            deselectAll();
        }
    }
}

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

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


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

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

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

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

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

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

    struct f : public LLSelectedObjectFunctor
    { //on delink, any modifyable object should
        f() {}

        virtual bool apply(LLViewerObject* object)
        {
            if (object->permModify())
            {
                if (object->getPhysicsShapeType() == LLViewerObject::PHYSICS_SHAPE_NONE)
                {
                    object->setPhysicsShapeType(LLViewerObject::PHYSICS_SHAPE_CONVEX_HULL);
                    object->updateFlags();
                }
            }
            return true;
        }
    } sendfunc;
    getSelection()->applyToObjects(&sendfunc);


    // Delink needs to send individuals so you can unlink a single object from
    // a linked set.
    sendListToRegions(
        "ObjectDelink",
        packAgentAndSessionID,
        packObjectLocalID,
        logNoOp,
        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,
        logNoOp,
        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()
{
    struct f : public LLSelectedObjectFunctor
    {
        virtual bool apply(LLViewerObject* object)
        {
            object->dump();
            return true;
        }
    } func;
    getSelection()->applyToObjects(&func);
}

void LLSelectMgr::saveSelectedObjectColors()
{
    struct f : public LLSelectedNodeFunctor
    {
        virtual bool apply(LLSelectNode* node)
        {
            node->saveColors();
            return true;
        }
    } func;
    getSelection()->applyToNodes(&func);
}

void LLSelectMgr::saveSelectedShinyColors()
{
    struct f : public LLSelectedNodeFunctor
    {
        virtual bool apply(LLSelectNode* node)
        {
            node->saveShinyColors();
            return true;
        }
    } func;
    getSelection()->applyToNodes(&func);
}

void LLSelectMgr::saveSelectedObjectTextures()
{
    // invalidate current selection so we update saved textures
    struct f : public LLSelectedNodeFunctor
    {
        virtual bool apply(LLSelectNode* node)
        {
            node->mValid = false;
            return true;
        }
    } func;
    getSelection()->applyToNodes(&func);

    // 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)
{
    if (mSelectedObjects->isEmpty())
    {
        // nothing selected, so nothing to save
        return;
    }

    struct f : public LLSelectedNodeFunctor
    {
        EActionType mActionType;
        LLSelectMgr* mManager;
        f(EActionType a, LLSelectMgr* p) : mActionType(a), mManager(p) {}
        virtual bool apply(LLSelectNode* selectNode)
        {
            LLViewerObject* object = selectNode->getObject();
            if (!object)
            {
                return true; // skip
            }

            if (selectNode->mSelectedGLTFNode != -1)
            {
                // save GLTF node state
                object->getGLTFNodeTransformAgent(selectNode->mSelectedGLTFNode, &selectNode->mSavedPositionLocal, &selectNode->mSavedRotation, &selectNode->mSavedScale);
                selectNode->mSavedPositionGlobal = gAgent.getPosGlobalFromAgent(selectNode->mSavedPositionLocal);
                selectNode->mLastMoveLocal.setZero();
            }
            else
            {
                selectNode->mSavedPositionLocal = object->getPosition();
                if (object->isAttachment())
                {
                    if (object->isRootEdit())
                    {
                        LLXform* parent_xform = object->mDrawable->getXform()->getParent();
                        if (parent_xform)
                        {
                            selectNode->mSavedPositionGlobal = gAgent.getPosGlobalFromAgent((object->getPosition() * parent_xform->getWorldRotation()) + parent_xform->getWorldPosition());
                        }
                        else
                        {
                            selectNode->mSavedPositionGlobal = object->getPositionGlobal();
                        }
                    }
                    else
                    {
                        LLViewerObject* attachment_root = (LLViewerObject*)object->getParent();
                        LLXform* parent_xform = attachment_root ? attachment_root->mDrawable->getXform()->getParent() : NULL;
                        if (parent_xform)
                        {
                            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);
                        }
                        else
                        {
                            selectNode->mSavedPositionGlobal = object->getPositionGlobal();
                        }
                    }
                    selectNode->mSavedRotation = object->getRenderRotation();
                }
                else
                {
                    selectNode->mSavedPositionGlobal = object->getPositionGlobal();
                    selectNode->mSavedRotation = object->getRotationRegion();
                }

                selectNode->mSavedScale = object->getScale();
                selectNode->saveTextureScaleRatios(mManager->mTextureChannel);
            }
            return true;
        }
    } func(action_type, this);
    getSelection()->applyToNodes(&func);

    mSavedSelectionBBox = getBBoxOfSelection();
}

struct LLSelectMgrApplyFlags : public LLSelectedObjectFunctor
{
    LLSelectMgrApplyFlags(U32 flags, bool state) : mFlags(flags), mState(state) {}
    U32 mFlags;
    bool mState;
    virtual bool apply(LLViewerObject* object)
    {
        if ( object->permModify())
        {
            if (object->isRoot())       // don't send for child objects
            {
                object->setFlags( mFlags, mState);
            }
            else if (FLAGS_WORLD & mFlags && ((LLViewerObject*)object->getRoot())->isSelected())
            {
                // FLAGS_WORLD are shared by all items in linkset
                object->setFlagsWithoutUpdate(FLAGS_WORLD & mFlags, mState);
            }
        };
        return true;
    }
};

void LLSelectMgr::selectionUpdatePhysics(bool physics)
{
    LLSelectMgrApplyFlags func( FLAGS_USE_PHYSICS, physics);
    getSelection()->applyToObjects(&func);
}

void LLSelectMgr::selectionUpdateTemporary(bool is_temporary)
{
    LLSelectMgrApplyFlags func( FLAGS_TEMPORARY_ON_REZ, is_temporary);
    getSelection()->applyToObjects(&func);
}

void LLSelectMgr::selectionUpdatePhantom(bool is_phantom)
{
    LLSelectMgrApplyFlags func( FLAGS_PHANTOM, is_phantom);
    getSelection()->applyToObjects(&func);
}

//----------------------------------------------------------------------
// 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());
}

void LLSelectMgr::packObjectIncludeInSearch(LLSelectNode* node, void *user_data)
{
    gMessageSystem->nextBlockFast(_PREHASH_ObjectData);
    gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, node->getObject()->getLocalID() );
    gMessageSystem->addBOOL("IncludeInSearch", node->getObject()->getIncludeInSearch());
}

// 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)
{
    const std::string* name = (const std::string*)user_data;
    if(!name->empty())
    {
        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)
{
    const std::string* desc = (const std::string*)user_data;
    if(desc)
    {   // Empty (non-null, but zero length) descriptions are OK
        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 std::string& message_name,
                                    void (*pack_header)(void *user_data),
                                    void (*pack_body)(LLSelectNode* node, void *user_data),
                                    void (*log_func)(LLSelectNode* node, void *user_data),
                                    void *user_data,
                                    ESendType send_type)
{
    sendListToRegions(mSelectedObjects, message_name, pack_header, pack_body, log_func, user_data, send_type);
}
void LLSelectMgr::sendListToRegions(LLObjectSelectionHandle selected_handle,
                                    const std::string& message_name,
                                    void (*pack_header)(void *user_data),
                                    void (*pack_body)(LLSelectNode* node, void *user_data),
                                    void (*log_func)(LLSelectNode* node, void *user_data),
                                    void *user_data,
                                    ESendType send_type)
{
    LLSelectNode* node;
    LLSelectNode* linkset_root = NULL;
    LLViewerRegion* last_region;
    LLViewerRegion* current_region;
    S32 objects_in_this_packet = 0;

    bool link_operation = message_name == "ObjectLink";

    if (mAllowSelectAvatar)
    {
        if (selected_handle->getObjectCount() == 1
            && selected_handle->getFirstObject() != NULL
            && selected_handle->getFirstObject()->isAvatar())
        {
            // Server doesn't move avatars at the moment, it is a local debug feature,
            // but server does update position regularly, so do not drop mLastPositionLocal
            // Position override for avatar gets reset in LLAgentCamera::resetView().
        }
        else
        {
            // drop mLastPositionLocal (allow next update through)
            resetObjectOverrides(selected_handle);
        }
    }
    else
    {
        //clear update override data (allow next update through)
        resetObjectOverrides(selected_handle);
    }

    std::queue<LLSelectNode*> nodes_to_send;

    struct push_all : public LLSelectedNodeFunctor
    {
        std::queue<LLSelectNode*>& nodes_to_send;
        push_all(std::queue<LLSelectNode*>& n) : nodes_to_send(n) {}
        virtual bool apply(LLSelectNode* node)
        {
            if (node->getObject())
            {
                nodes_to_send.push(node);
            }
            return true;
        }
    };
    struct push_some : public LLSelectedNodeFunctor
    {
        std::queue<LLSelectNode*>& nodes_to_send;
        bool mRoots;
        push_some(std::queue<LLSelectNode*>& n, bool roots) : nodes_to_send(n), mRoots(roots) {}
        virtual bool apply(LLSelectNode* node)
        {
            if (node->getObject())
            {
                bool is_root = node->getObject()->isRootEdit();
                if ((mRoots && is_root) || (!mRoots && !is_root))
                {
                    nodes_to_send.push(node);
                }
            }
            return true;
        }
    };
    struct push_all  pushall(nodes_to_send);
    struct push_some pushroots(nodes_to_send, true);
    struct push_some pushnonroots(nodes_to_send, false);

    switch(send_type)
    {
      case SEND_ONLY_ROOTS:
          if(message_name == "ObjectBuy")
            selected_handle->applyToRootNodes(&pushroots);
          else
            selected_handle->applyToRootNodes(&pushall);

        break;
      case SEND_INDIVIDUALS:
        selected_handle->applyToNodes(&pushall);
        break;
      case SEND_ROOTS_FIRST:
        // first roots...
        selected_handle->applyToNodes(&pushroots);
        // then children...
        selected_handle->applyToNodes(&pushnonroots);
        break;
      case SEND_CHILDREN_FIRST:
        // first children...
        selected_handle->applyToNodes(&pushnonroots);
        // then roots...
        selected_handle->applyToNodes(&pushroots);
        break;

    default:
        LL_ERRS() << "Bad send type " << send_type << " passed to SendListToRegions()" << LL_ENDL;
    }

    // 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->isSendFull(NULL))
            && (objects_in_this_packet < MAX_OBJECTS_PER_PACKET))
        {
            if (link_operation && linkset_root == NULL)
            {
                // linksets over 254 will be split into multiple messages,
                // but we need to provide same root for all messages or we will get separate linksets
                linkset_root = node;
            }
            // add another instance of the body of the data
            (*pack_body)(node, user_data);
            // do any related logging
            (*log_func)(node, user_data);
            ++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());
            objects_in_this_packet = 0;

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

            if (linkset_root != NULL)
            {
                if (current_region != last_region)
                {
                    // root should be in one region with the child, reset it
                    linkset_root = NULL;
                }
                else
                {
                    // add root instance into new message
                    (*pack_body)(linkset_root, user_data);
                    ++objects_in_this_packet;
                }
            }

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

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

    // LL_INFOS() << "sendListToRegions " << message_name << " obj " << objects_sent << " pkt " << packets_sent << LL_ENDL;
}


//
// 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;
        U64 creation_date;
        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->getU64Fast(_PREHASH_ObjectData, _PREHASH_CreationDate, creation_date, 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);

        std::string name;
        msg->getStringFast(_PREHASH_ObjectData, _PREHASH_Name, name, i);
        std::string desc;
        msg->getStringFast(_PREHASH_ObjectData, _PREHASH_Description, desc, i);

        std::string touch_name;
        msg->getStringFast(_PREHASH_ObjectData, _PREHASH_TouchName, touch_name, i);
        std::string sit_name;
        msg->getStringFast(_PREHASH_ObjectData, _PREHASH_SitName, sit_name, i);

        //unpack TE IDs
        uuid_vec_t 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 tid;
                memcpy(tid.mData, packed_buffer + buf_offset, UUID_BYTES);      /* Flawfinder: ignore */
                texture_ids.push_back(tid);
            }
        }


        // Iterate through nodes at end, since it can be on both the regular AND hover list
        struct f : public LLSelectedNodeFunctor
        {
            LLUUID mID;
            f(const LLUUID& id) : mID(id) {}
            virtual bool apply(LLSelectNode* node)
            {
                return (node->getObject() && node->getObject()->mID == mID);
            }
        } func(id);
        LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstNode(&func);

        if (!node)
        {
            LL_WARNS() << "Couldn't find object " << id << " selected." << LL_ENDL;
        }
        else
        {
            // save texture data as soon as we get texture perms first time
            bool save_textures = !node->mValid;
            if (node->mInventorySerial != inv_serial && node->getObject())
            {
                node->getObject()->dirtyInventory();

                // Even if this isn't object's first udpate, inventory changed
                // and some of the applied textures might have been in inventory
                // so update texture list.
                save_textures = true;
            }

            if (save_textures)
            {
                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)
                {
                    node->saveTextures(texture_ids);
                }

                if (can_copy && can_transfer && node->getObject()->getVolume())
                {
                    uuid_vec_t material_ids;
                    gltf_materials_vec_t override_materials;
                    LLVOVolume* vobjp = (LLVOVolume*)node->getObject();
                    for (int i = 0; i < vobjp->getNumTEs(); ++i)
                    {
                        material_ids.push_back(vobjp->getRenderMaterialID(i));

                        // Make a copy to ensure we won't affect live material
                        // with any potential changes nor live changes will be
                        // reflected in a saved copy.
                        // Like changes from local material (reuses pointer) or
                        // from live editor (revert mechanics might modify this)
                        LLGLTFMaterial* old_override = node->getObject()->getTE(i)->getGLTFMaterialOverride();
                        if (old_override)
                        {
                            LLPointer<LLGLTFMaterial> mat = new LLGLTFMaterial(*old_override);
                            override_materials.push_back(mat);
                        }
                        else
                        {
                            override_materials.push_back(nullptr);
                        }
                    }
                    // processObjectProperties does not include overrides so this
                    // might need to be moved to LLGLTFMaterialOverrideDispatchHandler
                    node->saveGLTFMaterials(material_ids, override_materials);
                }
            }

            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->mCreationDate = creation_date;
            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();

    // 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
    std::string name;
    msg->getStringFast(_PREHASH_ObjectData, _PREHASH_Name, name);

    std::string desc;
    msg->getStringFast(_PREHASH_ObjectData, _PREHASH_Description, desc);

    // the reporter widget askes the server for info about picked objects
    if (request_flags & COMPLAINT_REPORT_REQUEST )
    {
        LLFloaterReporter *reporterp = LLFloaterReg::findTypedInstance<LLFloaterReporter>("reporter");
        if (reporterp)
        {
            LLAvatarName av_name;
            LLAvatarNameCache::get(owner_id, &av_name);
            reporterp->setPickedObjectProperties(name, av_name.getUserName(), owner_id);
        }
    }
    else if (request_flags & OBJECT_PAY_REQUEST)
    {
        // check if the owner of the paid object is muted
        LLMuteList::getInstance()->autoRemove(owner_id, LLMuteList::AR_MONEY);
    }

    // Now look through all of the hovered nodes
    struct f : public LLSelectedNodeFunctor
    {
        LLUUID mID;
        f(const LLUUID& id) : mID(id) {}
        virtual bool apply(LLSelectNode* node)
        {
            return (node->getObject() && node->getObject()->mID == mID);
        }
    } func(id);
    LLSelectNode* node = LLSelectMgr::getInstance()->mHoverObjects->getFirstNode(&func);

    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)
    {
        LLSelectMgr::getInstance()->deselectAll();
    }

    LLUUID full_id;
    S32 local_id;
    LLViewerObject* object;
    std::vector<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.push_back(object);
        }
    }

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

extern F32  gGLModelView[16];

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

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

    if (!mSilhouetteImagep)
    {
        mSilhouetteImagep = LLViewerTextureManager::getFetchedTextureFromFile("silhouette.j2c", FTT_LOCAL_FILE, true, LLGLTexture::BOOST_UI);
    }

    mHighlightedObjects->cleanupNodes();

    if((cameraPos - mLastCameraPos).magVecSquared() > SILHOUETTE_UPDATE_THRESHOLD_SQUARED * currentCameraZoom * currentCameraZoom)
    {
        struct f : public LLSelectedObjectFunctor
        {
            virtual bool apply(LLViewerObject* object)
            {
                object->setChanged(LLXform::SILHOUETTE);
                return true;
            }
        } func;
        getSelection()->applyToObjects(&func);

        mLastCameraPos = gAgentCamera.getCameraPositionGlobal();
    }

    std::vector<LLViewerObject*> changed_objects;

    updateSelectionSilhouette(mSelectedObjects, num_sils_genned, changed_objects);
    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("EditLinkedParts");

        // 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
        std::vector<LLSelectNode*> remove_these_nodes;
        std::vector<LLViewerObject*> remove_these_roots;

        for (LLObjectSelection::iterator iter = mHighlightedObjects->begin();
             iter != mHighlightedObjects->end(); iter++)
        {
            LLSelectNode* node = *iter;
            LLViewerObject* objectp = node->getObject();
            if (!objectp)
                continue;
            if (objectp->isRoot() || !select_linked_set)
            {
                if (roots.count(objectp) == 0)
                {
                    remove_these_nodes.push_back(node);
                }
                else
                {
                    remove_these_roots.push_back(objectp);
                }
            }
            else
            {
                LLViewerObject* rootp = (LLViewerObject*)objectp->getRoot();

                if (roots.count(rootp) == 0)
                {
                    remove_these_nodes.push_back(node);
                }
            }
        }

        // remove all highlight nodes no longer in rectangle selection
        for (std::vector<LLSelectNode*>::iterator iter = remove_these_nodes.begin();
             iter != remove_these_nodes.end(); ++iter)
        {
            LLSelectNode* nodep = *iter;
            mHighlightedObjects->removeNode(nodep);
        }

        // remove all root objects already being highlighted
        for (std::vector<LLViewerObject*>::iterator iter = remove_these_roots.begin();
             iter != remove_these_roots.end(); ++iter)
        {
            LLViewerObject* objectp = *iter;
            roots.erase(objectp);
        }

        // add all new objects in rectangle selection
        for (std::set<LLViewerObject*>::iterator iter = roots.begin();
             iter != roots.end(); iter++)
        {
            LLViewerObject* objectp = *iter;
            if (!canSelectObject(objectp))
            {
                continue;
            }

            LLSelectNode* rect_select_root_node = new LLSelectNode(objectp, true);
            rect_select_root_node->selectAllTEs(true);

            if (!select_linked_set)
            {
                rect_select_root_node->mIndividualSelection = true;
            }
            else
            {
                LLViewerObject::const_child_list_t& child_list = objectp->getChildren();
                for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin();
                     iter != child_list.end(); iter++)
                {
                    LLViewerObject* child_objectp = *iter;

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

                    LLSelectNode* rect_select_node = new LLSelectNode(child_objectp, true);
                    rect_select_node->selectAllTEs(true);
                    mHighlightedObjects->addNodeAtEnd(rect_select_node);
                }
            }

            // Add the root last, to preserve order for link operations.
            mHighlightedObjects->addNodeAtEnd(rect_select_root_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 (LLObjectSelection::iterator iter = mHighlightedObjects->begin();
                 iter != mHighlightedObjects->end(); iter++)
            {
                LLSelectNode* node = *iter;
                LLViewerObject* objectp = node->getObject();
                if (!objectp)
                    continue;

                // 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, LLViewerCamera::getInstance()->getOrigin());
                        changed_objects.push_back(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 (std::vector<LLViewerObject*>::iterator iter = changed_objects.begin();
         iter != changed_objects.end(); ++iter)
    {
        // clear flags after traversing node list (as child objects need to refer to parent flags, etc)
        LLViewerObject* objectp = *iter;
        objectp->clearChanged(LLXform::MOVED | LLXform::SILHOUETTE);
    }
}

void LLSelectMgr::updateSelectionSilhouette(LLObjectSelectionHandle object_handle, S32& num_sils_genned, std::vector<LLViewerObject*>& changed_objects)
{
    if (object_handle->getNumNodes())
    {
        //gGLSPipelineSelection.set();

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

        for (S32 pass = 0; pass < 2; pass++)
        {
            for (LLObjectSelection::iterator iter = object_handle->begin();
                iter != object_handle->end(); iter++)
            {
                LLSelectNode* node = *iter;
                LLViewerObject* objectp = node->getObject();
                if (!objectp)
                    continue;
                // 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, LLViewerCamera::getInstance()->getOrigin());
                        changed_objects.push_back(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);
                        }
                    }
                }
            }
        }
    }
}
void LLSelectMgr::renderSilhouettes(bool for_hud)
{
    if (!mRenderSilhouettes || !mRenderHighlightSelections)
    {
        return;
    }

    gGL.getTexUnit(0)->bind(mSilhouetteImagep);
    LLGLSPipelineSelection gls_select;
    LLGLEnable blend(GL_BLEND);
    LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE);

    if (isAgentAvatarValid() && for_hud)
    {
        LLBBox hud_bbox = gAgentAvatarp->getHUDBBox();

        F32 cur_zoom = gAgentCamera.mHUDCurZoom;

        // set up transform to encompass bounding box of HUD
        gGL.matrixMode(LLRender::MM_PROJECTION);
        gGL.pushMatrix();
        gGL.loadIdentity();
        F32 depth = llmax(1.f, hud_bbox.getExtentLocal().mV[VX] * 1.1f);
        gGL.ortho(-0.5f * LLViewerCamera::getInstance()->getAspect(), 0.5f * LLViewerCamera::getInstance()->getAspect(), -0.5f, 0.5f, 0.f, depth);

        gGL.matrixMode(LLRender::MM_MODELVIEW);
        gGL.pushMatrix();
        gGL.pushUIMatrix();
        gGL.loadUIIdentity();
        gGL.loadIdentity();
        gGL.loadMatrix(OGL_TO_CFR_ROTATION);        // Load Cory's favorite reference frame
        gGL.translatef(-hud_bbox.getCenterLocal().mV[VX] + (depth *0.5f), 0.f, 0.f);
        gGL.scalef(cur_zoom, cur_zoom, cur_zoom);
    }

    bool wireframe_selection = (gFloaterTools && gFloaterTools->getVisible()) || LLSelectMgr::sRenderHiddenSelections;
    F32 fogCfx = (F32)llclamp((LLSelectMgr::getInstance()->getSelectionCenterGlobal() - gAgentCamera.getCameraPositionGlobal()).magVec() / (LLSelectMgr::getInstance()->getBBoxOfSelection().getExtentLocal().magVec() * 4), 0.0, 1.0);

    LLColor4 sParentColor = sSilhouetteParentColor;
    sParentColor.mV[VALPHA] = LLSelectMgr::sHighlightAlpha;
    LLColor4 sChildColor = sSilhouetteChildColor;
    sChildColor.mV[VALPHA] = LLSelectMgr::sHighlightAlpha;

    auto renderMeshSelection_f = [fogCfx, wireframe_selection](LLSelectNode* node, LLViewerObject* objectp, LLColor4 hlColor)
    {
        LLGLSLShader* shader = LLGLSLShader::sCurBoundShaderPtr;

        if (shader)
        {
            gDebugProgram.bind();
        }

        gGL.matrixMode(LLRender::MM_MODELVIEW);
        gGL.pushMatrix();

        bool is_hud_object = objectp->isHUDAttachment();

        if (!is_hud_object)
        {
            gGL.loadIdentity();
            gGL.multMatrix(gGLModelView);
        }

        if (objectp->mDrawable->isActive())
        {
            gGL.multMatrix((F32*)objectp->getRenderMatrix().mMatrix);
        }
        else if (!is_hud_object)
        {
            LLVector3 trans = objectp->getRegion()->getOriginAgent();
            gGL.translatef(trans.mV[0], trans.mV[1], trans.mV[2]);
        }

        bool bRenderHidenSelection = node->isTransient() ? false : LLSelectMgr::sRenderHiddenSelections;


        LLVOVolume* vobj = objectp->mDrawable->getVOVolume();
        if (vobj)
        {
            LLVertexBuffer::unbind();
            gGL.pushMatrix();
            gGL.multMatrix((F32*)vobj->getRelativeXform().mMatrix);

            if (objectp->mDrawable->isState(LLDrawable::RIGGED))
            {
                vobj->updateRiggedVolume(true);
            }
        }

        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

        S32 num_tes = llmin((S32)objectp->getNumTEs(), (S32)objectp->getNumFaces()); // avatars have TEs but no faces
        for (S32 te = 0; te < num_tes; ++te)
        {
            if (node->isTESelected(te))
            {
                objectp->mDrawable->getFace(te)->renderOneWireframe(hlColor, fogCfx, wireframe_selection, bRenderHidenSelection, nullptr != shader);
            }
        }

        gGL.popMatrix();
        gGL.popMatrix();

        glLineWidth(1.f);
        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

        if (shader)
        {
            shader->bind();
        }
    };

    if (mSelectedObjects->getNumNodes())
    {
        LLUUID inspect_item_id= LLUUID::null;
        LLFloaterInspect* inspect_instance = LLFloaterReg::getTypedInstance<LLFloaterInspect>("inspect");
        if(inspect_instance && inspect_instance->getVisible())
        {
            inspect_item_id = inspect_instance->getSelectedUUID();
        }
        else
        {
            LLSidepanelTaskInfo *panel_task_info = LLSidepanelTaskInfo::getActivePanel();
            if (panel_task_info)
            {
                inspect_item_id = panel_task_info->getSelectedUUID();
            }
        }

        LLUUID focus_item_id = LLViewerMediaFocus::getInstance()->getFocusedObjectID();
        for (S32 pass = 0; pass < 2; pass++)
        {
            for (LLObjectSelection::iterator iter = mSelectedObjects->begin();
                 iter != mSelectedObjects->end(); iter++)
            {
                LLSelectNode* node = *iter;

                if (getTEMode() && !node->hasSelectedTE())
                    continue;

                LLViewerObject* objectp = node->getObject();
                if (!objectp)
                    continue;

                if (objectp->mDrawable
                    && objectp->mDrawable->getVOVolume()
                    && objectp->mDrawable->getVOVolume()->isMesh())
                {
                    LLColor4 hlColor = objectp->isRootEdit() ? sParentColor : sChildColor;
                    if (objectp->getID() == inspect_item_id)
                    {
                        hlColor = sHighlightInspectColor;
                    }
                    else if (node->isTransient())
                    {
                        hlColor = sContextSilhouetteColor;
                    }
                    renderMeshSelection_f(node, objectp, hlColor);
                }
                else
                {
                    if (objectp->isHUDAttachment() != for_hud)
                    {
                        continue;
                    }
                    if (objectp->getID() == focus_item_id)
                    {
                        node->renderOneSilhouette(gFocusMgr.getFocusColor());
                    }
                    else if (objectp->getID() == inspect_item_id)
                    {
                        node->renderOneSilhouette(sHighlightInspectColor);
                    }
                    else 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);
                    }
                }
            } //for all selected node's
        } //for pass
    }

    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 (LLObjectSelection::iterator iter = mHighlightedObjects->begin();
                 iter != mHighlightedObjects->end(); iter++)
            {
                LLSelectNode* node = *iter;
                LLViewerObject* objectp = node->getObject();
                if (!objectp)
                    continue;
                if (objectp->isHUDAttachment() != for_hud)
                {
                    continue;
                }

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

    if (isAgentAvatarValid() && for_hud)
    {
        gGL.matrixMode(LLRender::MM_PROJECTION);
        gGL.popMatrix();

        gGL.matrixMode(LLRender::MM_MODELVIEW);
        gGL.popMatrix();
        gGL.popUIMatrix();
        stop_glerror();
    }

    gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
}

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);
    }
}

//
// Utility classes
//
LLSelectNode::LLSelectNode(LLViewerObject* object, bool glow)
:   mObject(object),
    mIndividualSelection(false),
    mTransient(false),
    mValid(false),
    mPermissions(new LLPermissions()),
    mInventorySerial(0),
    mSilhouetteExists(false),
    mDuplicated(false),
    mTESelectMask(0),
    mLastTESelected(0),
    mName(LLStringUtil::null),
    mDescription(LLStringUtil::null),
    mTouchName(LLStringUtil::null),
    mSitName(LLStringUtil::null),
    mCreationDate(0)
{
    saveColors();
    saveShinyColors();
}

LLSelectNode::LLSelectNode(const LLSelectNode& nodep)
{
    mTESelectMask = nodep.mTESelectMask;
    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;
    mInventorySerial = 0;
    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;
    mCreationDate = nodep.mCreationDate;

    mSilhouetteVertices = nodep.mSilhouetteVertices;
    mSilhouetteNormals = nodep.mSilhouetteNormals;
    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);
    }
    mSavedShinyColors.clear();
    for (color_iter = nodep.mSavedShinyColors.begin(); color_iter != nodep.mSavedShinyColors.end(); ++color_iter)
    {
        mSavedShinyColors.push_back(*color_iter);
    }

    saveTextures(nodep.mSavedTextures);
    saveGLTFMaterials(nodep.mSavedGLTFMaterialIds, nodep.mSavedGLTFOverrideMaterials);
}

LLSelectNode::~LLSelectNode()
{
    LLSelectMgr *manager = LLSelectMgr::getInstance();
    if (manager->mAllowSelectAvatar
        && (!mLastPositionLocal.isExactlyZero()
            || mLastRotation != LLQuaternion()))
    {
        LLViewerObject* object = getObject(); //isDead() check
        if (object && !object->getParent())
        {
            LLVOAvatar* avatar = object->asAvatar();
            if (avatar)
            {
                // Avatar was moved and needs to stay that way
                manager->mAvatarOverridesMap.emplace(avatar->getID(), LLSelectMgr::AvatarPositionOverride(mLastPositionLocal, mLastRotation, object));
            }
        }
    }

    delete mPermissions;
    mPermissions = NULL;
}

void LLSelectNode::selectAllTEs(bool b)
{
    mTESelectMask = b ? TE_SELECT_MASK_ALL : 0x0;
    mLastTESelected = 0;
}

void LLSelectNode::selectTE(S32 te_index, bool selected)
{
    if (te_index < 0 || te_index >= SELECT_MAX_TES)
    {
        return;
    }
    S32 mask = 0x1 << te_index;
    if(selected)
    {
        mTESelectMask |= mask;
    }
    else
    {
        mTESelectMask &= ~mask;
    }
    mLastTESelected = te_index;
}

void LLSelectNode::selectGLTFNode(S32 node_index, S32 primitive_index, bool selected)
{
    if (node_index < 0)
    {
        return;
    }

    mSelectedGLTFNode = node_index;
    mSelectedGLTFPrimitive = primitive_index;
}

bool LLSelectNode::isTESelected(S32 te_index) const
{
    if (te_index < 0 || te_index >= mObject->getNumTEs())
    {
        return false;
    }
    return (mTESelectMask & (0x1 << te_index)) != 0;
}

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

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

void LLSelectNode::setObject(LLViewerObject* object)
{
    mObject = object;
}

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::saveShinyColors()
{
    if (mObject.notNull())
    {
        mSavedShinyColors.clear();
        for (S32 i = 0; i < mObject->getNumTEs(); i++)
        {
            const LLMaterialPtr mat = mObject->getTE(i)->getMaterialParams();
            if (!mat.isNull())
            {
                mSavedShinyColors.push_back(mat->getSpecularLightColor());
            }
            else
            {
                mSavedShinyColors.push_back(LLColor4::white);
            }
        }
    }
}

void LLSelectNode::saveTextures(const uuid_vec_t& textures)
{
    if (mObject.notNull())
    {
        mSavedTextures.clear();

        for (uuid_vec_t::const_iterator texture_it = textures.begin();
             texture_it != textures.end(); ++texture_it)
        {
            mSavedTextures.push_back(*texture_it);
        }
    }
}

void LLSelectNode::saveGLTFMaterials(const uuid_vec_t& materials, const gltf_materials_vec_t& override_materials)
{
    if (mObject.notNull())
    {
        mSavedGLTFMaterialIds.clear();
        mSavedGLTFOverrideMaterials.clear();

        for (uuid_vec_t::const_iterator materials_it = materials.begin();
            materials_it != materials.end(); ++materials_it)
        {
            mSavedGLTFMaterialIds.push_back(*materials_it);
        }

        for (gltf_materials_vec_t::const_iterator mat_it = override_materials.begin();
            mat_it != override_materials.end(); ++mat_it)
        {
            mSavedGLTFOverrideMaterials.push_back(*mat_it);
        }
    }
}

void LLSelectNode::saveTextureScaleRatios(LLRender::eTexIndex index_to_query)
{
    mTextureScaleRatios.clear();
    mGLTFScaleRatios.clear();

    if (mObject.notNull())
    {

        LLVector3 scale = mObject->getScale();

        for (U8 i = 0; i < mObject->getNumTEs(); i++)
        {
            F32 diffuse_s = 1.0f;
            F32 diffuse_t = 1.0f;

            LLVector3 v;
            const LLTextureEntry* tep = mObject->getTE(i);
            if (!tep)
                continue;

            U32 s_axis = VX;
            U32 t_axis = VY;
            LLPrimitive::getTESTAxes(i, &s_axis, &t_axis);

            tep->getScale(&diffuse_s,&diffuse_t);

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

            LLGLTFMaterial* material = tep->getGLTFMaterialOverride();
            LLVector3 material_v;
            F32 scale_x = 1;
            F32 scale_y = 1;
            std::vector<LLVector3> material_v_vec;
            for (U32 i = 0; i < LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT; ++i)
            {
                if (material)
                {
                    LLGLTFMaterial::TextureTransform& transform = material->mTextureTransform[i];
                    scale_x = transform.mScale[VX];
                    scale_y = transform.mScale[VY];
                }
                else
                {
                    // Not having an override doesn't mean that there is no material
                    scale_x = 1;
                    scale_y = 1;
                }

                if (tep->getTexGen() == LLTextureEntry::TEX_GEN_PLANAR)
                {
                    material_v.mV[s_axis] = scale_x * scale.mV[s_axis];
                    material_v.mV[t_axis] = scale_y * scale.mV[t_axis];
                }
                else
                {
                    material_v.mV[s_axis] = scale_x / scale.mV[s_axis];
                    material_v.mV[t_axis] = scale_y / scale.mV[t_axis];
                }
                material_v_vec.push_back(material_v);
            }
            mGLTFScaleRatios.push_back(material_v_vec);
        }
    }
}


// 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;
    }

    // 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;
    }

    LLVOVolume* vobj = drawable->getVOVolume();
    if (vobj && vobj->isMesh())
    {
        //This check (if(...)) with assert here just for ensure that this situation will not happens, and can be removed later. For example on the next release.
        llassert(!"renderOneWireframe() was removed SL-10194");
        return;
    }

    if (!mSilhouetteExists)
    {
        return;
    }

    bool is_hud_object = objectp->isHUDAttachment();

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


    LLGLSLShader* shader = LLGLSLShader::sCurBoundShaderPtr;

    if (shader)
    { //use UI program for selection highlights (texture color modulated by vertex color)
        gUIProgram.bind();
    }

    gGL.matrixMode(LLRender::MM_MODELVIEW);
    gGL.pushMatrix();
    gGL.pushUIMatrix();
    gGL.loadUIIdentity();

    if (!is_hud_object)
    {
        gGL.loadIdentity();
        gGL.multMatrix(gGLModelView);
    }


    if (drawable->isActive())
    {
        gGL.multMatrix((F32*) objectp->getRenderMatrix().mMatrix);
    }

    LLVolume *volume = objectp->getVolume();
    if (volume)
    {
        F32 silhouette_thickness;
        if (isAgentAvatarValid() && is_hud_object)
        {
            silhouette_thickness = LLSelectMgr::sHighlightThickness / gAgentCamera.mHUDCurZoom;
        }
        else
        {
            LLVector3 view_vector = LLViewerCamera::getInstance()->getOrigin() - objectp->getRenderPosition();
            silhouette_thickness = view_vector.magVec() * LLSelectMgr::sHighlightThickness * (LLViewerCamera::getInstance()->getView() / LLViewerCamera::getInstance()->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())
        {
            gGL.flush();
            gGL.blendFunc(LLRender::BF_SOURCE_COLOR, LLRender::BF_ONE);

            LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE, GL_GEQUAL);
            gGL.flush();
            gGL.begin(LLRender::LINES);
            {
                gGL.color4f(color.mV[VRED], color.mV[VGREEN], color.mV[VBLUE], 0.4f);

                for(S32 i = 0; i < mSilhouetteVertices.size(); i += 2)
                {
                    u_coord += u_divisor * LLSelectMgr::sHighlightUScale;
                    gGL.texCoord2f( u_coord, v_coord );
                    gGL.vertex3fv( mSilhouetteVertices[i].mV);
                    u_coord += u_divisor * LLSelectMgr::sHighlightUScale;
                    gGL.texCoord2f( u_coord, v_coord );
                    gGL.vertex3fv(mSilhouetteVertices[i+1].mV);
                }
            }
            gGL.end();
            u_coord = fmod(animationTime * LLSelectMgr::sHighlightUAnim, 1.f);
        }

        gGL.flush();
        gGL.setSceneBlendType(LLRender::BT_ALPHA);
        gGL.begin(LLRender::TRIANGLES);
        {
            for(S32 i = 0; i < mSilhouetteVertices.size(); i+=2)
            {
                if (!mSilhouetteNormals[i].isFinite() ||
                    !mSilhouetteNormals[i+1].isFinite())
                { //skip skewed segments
                    continue;
                }

                LLVector3 v[4];
                LLVector2 tc[4];
                v[0] = mSilhouetteVertices[i] + (mSilhouetteNormals[i] * silhouette_thickness);
                tc[0].set(u_coord, v_coord + LLSelectMgr::sHighlightVScale);

                v[1] = mSilhouetteVertices[i];
                tc[1].set(u_coord, v_coord);

                u_coord += u_divisor * LLSelectMgr::sHighlightUScale;

                v[2] = mSilhouetteVertices[i+1] + (mSilhouetteNormals[i+1] * silhouette_thickness);
                tc[2].set(u_coord, v_coord + LLSelectMgr::sHighlightVScale);

                v[3] = mSilhouetteVertices[i+1];
                tc[3].set(u_coord,v_coord);

                gGL.color4f(color.mV[VRED], color.mV[VGREEN], color.mV[VBLUE], 0.0f); //LLSelectMgr::sHighlightAlpha);
                gGL.texCoord2fv(tc[0].mV);
                gGL.vertex3fv( v[0].mV );

                gGL.color4f(color.mV[VRED]*2, color.mV[VGREEN]*2, color.mV[VBLUE]*2, LLSelectMgr::sHighlightAlpha);
                gGL.texCoord2fv( tc[1].mV );
                gGL.vertex3fv( v[1].mV );

                gGL.color4f(color.mV[VRED], color.mV[VGREEN], color.mV[VBLUE], 0.0f); //LLSelectMgr::sHighlightAlpha);
                gGL.texCoord2fv( tc[2].mV );
                gGL.vertex3fv( v[2].mV );

                gGL.vertex3fv( v[2].mV );

                gGL.color4f(color.mV[VRED]*2, color.mV[VGREEN]*2, color.mV[VBLUE]*2, LLSelectMgr::sHighlightAlpha);
                gGL.texCoord2fv( tc[1].mV );
                gGL.vertex3fv( v[1].mV );

                gGL.texCoord2fv( tc[3].mV );
                gGL.vertex3fv( v[3].mV );
            }
        }
        gGL.end();
        gGL.flush();
    }
    gGL.popMatrix();
    gGL.popUIMatrix();

    if (shader)
    {
        shader->bind();
    }
}

//
// Utility Functions
//

// *DEPRECATED: See header comment.
void dialog_refresh_all()
{
    // This is the easiest place to fire the update signal, as it will
    // make cleaning up the functions below easier.  Also, sometimes entities
    // outside the selection manager change properties of selected objects
    // and call into this function.  Yuck.
    LLSelectMgr::getInstance()->mUpdateSignal();

    // *TODO: Eliminate all calls into outside classes below, make those
    // objects register with the update signal.

    gFloaterTools->dirty();

    gMenuObject->needsArrange();

    if( gMenuAttachmentSelf->getVisible() )
    {
        gMenuAttachmentSelf->arrange();
    }
    if( gMenuAttachmentOther->getVisible() )
    {
        gMenuAttachmentOther->arrange();
    }

    LLFloaterInspect* inspect_instance = LLFloaterReg::getTypedInstance<LLFloaterInspect>("inspect");
    if(inspect_instance)
    {
        inspect_instance->dirty();
    }

    LLSidepanelTaskInfo *panel_task_info = LLSidepanelTaskInfo::getActivePanel();
    if (panel_task_info)
    {
        panel_task_info->dirty();
    }

    LLFloaterGLTFAssetEditor * gltf_editor = LLFloaterReg::findTypedInstance<LLFloaterGLTFAssetEditor>("gltf_asset_editor");
    if (gltf_editor)
    {
        gltf_editor->dirty();
    }
}

S32 get_family_count(LLViewerObject *parent)
{
    if (!parent)
    {
        LL_WARNS() << "Trying to get_family_count on null parent!" << LL_ENDL;
    }
    S32 count = 1;  // for this object
    LLViewerObject::const_child_list_t& child_list = parent->getChildren();
    for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin();
         iter != child_list.end(); iter++)
    {
        LLViewerObject* child = *iter;

        if (!child)
        {
            LL_WARNS() << "Family object has NULL child!  Show Doug." << LL_ENDL;
        }
        else if (child->isDead())
        {
            LL_WARNS() << "Family object has dead child object.  Show Doug." << LL_ENDL;
        }
        else
        {
            if (LLSelectMgr::getInstance()->canSelectObject(child))
            {
                count += get_family_count( child );
            }
        }
    }
    return count;
}

//-----------------------------------------------------------------------------
// updateSelectionCenter
//
// FIXME this is a grab bag of functionality only some of which has to do
// with the selection center
// -----------------------------------------------------------------------------
void LLSelectMgr::updateSelectionCenter()
{
    const F32 MOVE_SELECTION_THRESHOLD = 1.f;       //  Movement threshold in meters for updating selection
                                                    //  center (tractor beam)

    // override any avatar updates received
    // Works only if avatar was repositioned
    // and edit floater is visible
    overrideAvatarUpdates();
    //override any object updates received
    //for selected objects
    overrideObjectUpdates();

    LLViewerObject* object = mSelectedObjects->getFirstObject();
    if (!object)
    {
        // nothing selected, probably grabbing
        // Ignore by setting to avatar origin.
        mSelectionCenterGlobal.clearVec();
        mShowSelection = false;
        mSelectionBBox = LLBBox();
        resetAgentHUDZoom();
    }
    else
    {
        mSelectedObjects->mSelectType = getSelectTypeForObject(object);

        if (mSelectedObjects->mSelectType != SELECT_TYPE_HUD && isAgentAvatarValid())
        {
            // reset hud ZOOM
            resetAgentHUDZoom();
        }

        mShowSelection = false;
        LLBBox bbox;

        // have stuff selected
        LLVector3d select_center;
        // keep a list of jointed objects for showing the joint HUDEffects

        // Initialize the bounding box to the root prim, so the BBox orientation
        // matches the root prim's (affecting the orientation of the manipulators).
        bbox.addBBoxAgent( (mSelectedObjects->getFirstRootObject(true))->getBoundingBoxAgent() );

        for (LLObjectSelection::iterator iter = mSelectedObjects->begin();
             iter != mSelectedObjects->end(); iter++)
        {
            LLSelectNode* node = *iter;
            LLViewerObject* object = node->getObject();
            if (!object)
                continue;

            LLViewerObject *root = object->getRootEdit();
            if (mSelectedObjects->mSelectType == SELECT_TYPE_WORLD && // not an attachment
                !root->isChild(gAgentAvatarp) && // not the object you're sitting on
                !object->isAvatar()) // not another avatar
            {
                mShowSelection = true;
            }

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

        LLVector3 bbox_center_agent = bbox.getCenterAgent();
        mSelectionCenterGlobal = gAgent.getPosGlobalFromAgent(bbox_center_agent);
        mSelectionBBox = bbox;

    }

    if ( !(gAgentID == LLUUID::null))
    {
        LLTool      *tool = LLToolMgr::getInstance()->getCurrentTool();
        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 && mSelectedObjects->getObjectCount() == 0)
    {
        gEditMenuHandler = NULL;
    }

    pauseAssociatedAvatars();
}

//-----------------------------------------------------------------------------
// pauseAssociatedAvatars
//
// If the selection includes an attachment or an animated object, the
// associated avatars should pause their animations until they are no
// longer selected.
//-----------------------------------------------------------------------------
void LLSelectMgr::pauseAssociatedAvatars()
{
    mPauseRequests.clear();

    for (LLObjectSelection::iterator iter = mSelectedObjects->begin();
         iter != mSelectedObjects->end(); iter++)
    {
        LLSelectNode* node = *iter;
        LLViewerObject* object = node->getObject();
        if (!object)
            continue;

        mSelectedObjects->mSelectType = getSelectTypeForObject(object);

        LLVOAvatar* parent_av = NULL;
        if (mSelectedObjects->mSelectType == SELECT_TYPE_ATTACHMENT)
        {
            // Selection can be obsolete, confirm that this is an attachment
            // and find parent avatar
            parent_av = object->getAvatarAncestor();
        }

        // Can be both an attachment and animated object
        if (parent_av)
        {
            // It's an attachment. Pause the avatar it's attached to.
            mPauseRequests.push_back(parent_av->requestPause());
        }

        if (object->isAnimatedObject() && object->getControlAvatar())
        {
            // It's an animated object. Pause the control avatar.
            mPauseRequests.push_back(object->getControlAvatar()->requestPause());
        }
    }
}

void LLSelectMgr::updatePointAt()
{
    if (mShowSelection)
    {
        if (mSelectedObjects->getObjectCount())
        {
            LLVector3 select_offset;
            const LLPickInfo& pick = gViewerWindow->getLastPick();
            LLViewerObject *click_object = pick.getObject();
            if (click_object && click_object->isSelected())
            {
                // clicked on another object in our selection group, use that as target
                select_offset.setVec(pick.mObjectOffset);
                select_offset.rotVec(~click_object->getRenderRotation());

                gAgentCamera.setPointAt(POINTAT_TARGET_SELECT, click_object, select_offset);
                gAgentCamera.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
                gAgentCamera.setPointAt(POINTAT_TARGET_SELECT, mSelectedObjects->getFirstObject());
                gAgentCamera.setLookAt(LOOKAT_TARGET_SELECT, mSelectedObjects->getFirstObject());
            }
        }
        else
        {
            gAgentCamera.setPointAt(POINTAT_TARGET_CLEAR);
            gAgentCamera.setLookAt(LOOKAT_TARGET_CLEAR);
        }
    }
    else
    {
        gAgentCamera.setPointAt(POINTAT_TARGET_CLEAR);
        gAgentCamera.setLookAt(LOOKAT_TARGET_CLEAR);
    }
}

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


//-----------------------------------------------------------------------------
// canUndo()
//-----------------------------------------------------------------------------
bool LLSelectMgr::canUndo() const
{
    // Can edit or can move
    return const_cast<LLSelectMgr*>(this)->mSelectedObjects->getFirstUndoEnabledObject() != NULL; // HACK: casting away constness - MG;
}

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

//-----------------------------------------------------------------------------
// canRedo()
//-----------------------------------------------------------------------------
bool LLSelectMgr::canRedo() const
{
    return const_cast<LLSelectMgr*>(this)->mSelectedObjects->getFirstEditableObject() != NULL; // HACK: casting away constness - MG
}

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

//-----------------------------------------------------------------------------
// canDoDelete()
//-----------------------------------------------------------------------------
bool LLSelectMgr::canDoDelete() const
{
    bool can_delete = false;
    // This function is "logically const" - it does not change state in
    // a way visible outside the selection manager.
    LLSelectMgr* self = const_cast<LLSelectMgr*>(this);
    LLViewerObject* obj = self->mSelectedObjects->getFirstDeleteableObject();
    // Note: Can only delete root objects (see getFirstDeleteableObject() for more info)
    if (obj!= NULL)
    {
        // all the faces needs to be selected
        if(self->mSelectedObjects->contains(obj,SELECT_ALL_TES ))
        {
            can_delete = true;
        }
    }

    return can_delete;
}

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

//-----------------------------------------------------------------------------
// canDeselect()
//-----------------------------------------------------------------------------
bool LLSelectMgr::canDeselect() const
{
    return !mSelectedObjects->isEmpty();
}

//-----------------------------------------------------------------------------
// deselect()
//-----------------------------------------------------------------------------
void LLSelectMgr::deselect()
{
    deselectAll();
}
//-----------------------------------------------------------------------------
// canDuplicate()
//-----------------------------------------------------------------------------
bool LLSelectMgr::canDuplicate() const
{
    return const_cast<LLSelectMgr*>(this)->mSelectedObjects->getFirstCopyableObject() != NULL; // HACK: casting away constness - MG
}
//-----------------------------------------------------------------------------
// duplicate()
//-----------------------------------------------------------------------------
void LLSelectMgr::duplicate()
{
    LLVector3 offset(0.5f, 0.5f, 0.f);
    selectDuplicate(offset, true);
}

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()
{
    struct f : public LLSelectedObjectFunctor
    {
        virtual bool apply(LLViewerObject* object)
        {
            if (!LLSelectMgr::getInstance()->canSelectObject(object))
            {
                LLSelectMgr::getInstance()->deselectObjectOnly(object);
            }
            return true;
        }
    } func;
    getSelection()->applyToObjects(&func);
}

bool LLSelectMgr::canSelectObject(LLViewerObject* object, bool ignore_select_owned)
{
    // Never select dead objects
    if (!object || object->isDead())
    {
        return false;
    }

    if (mForceSelection)
    {
        return true;
    }

    if(!ignore_select_owned)
    {
        if ((gSavedSettings.getBOOL("SelectOwnedOnly") && !object->permYouOwner()) ||
                (gSavedSettings.getBOOL("SelectMovableOnly") && (!object->permMove() ||  object->isPermanentEnforced())))
        {
            // only select my own objects
            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 (mSelectedObjects->getObjectCount() > 0 && mSelectedObjects->mSelectType != selection_type) return false;

    return true;
}

bool LLSelectMgr::setForceSelection(bool force)
{
    std::swap(mForceSelection,force);
    return force;
}

void LLSelectMgr::resetAgentHUDZoom()
{
    if (gAgentCamera.mHUDTargetZoom != 1)
    {
        gAgentCamera.mHUDTargetZoom = 1.f;
        gAgentCamera.mHUDCurZoom = 1.f;
    }
}

void LLSelectMgr::getAgentHUDZoom(F32 &target_zoom, F32 &current_zoom) const
{
    target_zoom = gAgentCamera.mHUDTargetZoom;
    current_zoom = gAgentCamera.mHUDCurZoom;
}

void LLSelectMgr::setAgentHUDZoom(F32 target_zoom, F32 current_zoom)
{
    gAgentCamera.mHUDTargetZoom = target_zoom;
    gAgentCamera.mHUDCurZoom = current_zoom;
}

/////////////////////////////////////////////////////////////////////////////
// Object selection iterator helpers
/////////////////////////////////////////////////////////////////////////////
bool LLObjectSelection::is_root::operator()(LLSelectNode *node)
{
    LLViewerObject* object = node->getObject();
    return (object != NULL) && !node->mIndividualSelection && (object->isRootEdit());
}

bool LLObjectSelection::is_valid_root::operator()(LLSelectNode *node)
{
    LLViewerObject* object = node->getObject();
    return (object != NULL) && node->mValid && !node->mIndividualSelection && (object->isRootEdit());
}

bool LLObjectSelection::is_root_object::operator()(LLSelectNode *node)
{
    LLViewerObject* object = node->getObject();
    return (object != NULL) && (object->isRootEdit());
}

LLObjectSelection::LLObjectSelection() :
    LLRefCount(),
    mSelectType(SELECT_TYPE_WORLD)
{
}

LLObjectSelection::~LLObjectSelection()
{
    deleteAllNodes();
}

void LLObjectSelection::cleanupNodes()
{
    for (list_t::iterator iter = mList.begin(); iter != mList.end(); )
    {
        list_t::iterator curiter = iter++;
        LLSelectNode* node = *curiter;
        if (node->getObject() == NULL || node->getObject()->isDead())
        {
            mList.erase(curiter);
            delete node;
        }
    }
}

void LLObjectSelection::updateEffects()
{
}

S32 LLObjectSelection::getNumNodes()
{
    return static_cast<S32>(mList.size());
}

void LLObjectSelection::addNode(LLSelectNode *nodep)
{
    llassert_always(nodep->getObject() && !nodep->getObject()->isDead());
    mList.push_front(nodep);
    mSelectNodeMap[nodep->getObject()] = nodep;
}

void LLObjectSelection::addNodeAtEnd(LLSelectNode *nodep)
{
    llassert_always(nodep->getObject() && !nodep->getObject()->isDead());
    mList.push_back(nodep);
    mSelectNodeMap[nodep->getObject()] = nodep;
}

void LLObjectSelection::moveNodeToFront(LLSelectNode *nodep)
{
    mList.remove(nodep);
    mList.push_front(nodep);
}

void LLObjectSelection::removeNode(LLSelectNode *nodep)
{
    mSelectNodeMap.erase(nodep->getObject());
    if (nodep->getObject() == mPrimaryObject)
    {
        mPrimaryObject = NULL;
    }
    nodep->setObject(NULL); // Will get erased in cleanupNodes()
    mList.remove(nodep);
}

void LLObjectSelection::deleteAllNodes()
{
    std::for_each(mList.begin(), mList.end(), DeletePointer());
    mList.clear();
    mSelectNodeMap.clear();
    mPrimaryObject = NULL;
}

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

//-----------------------------------------------------------------------------
// isEmpty()
//-----------------------------------------------------------------------------
bool LLObjectSelection::isEmpty() const
{
    return (mList.size() == 0);
}


//-----------------------------------------------------------------------------
// getObjectCount() - returns number of non null objects
//-----------------------------------------------------------------------------
S32 LLObjectSelection::getObjectCount()
{
    cleanupNodes();
    S32 count = static_cast<S32>(mList.size());

    return count;
}

F32 LLObjectSelection::getSelectedObjectCost()
{
    cleanupNodes();
    F32 cost = 0.f;

    for (list_t::iterator iter = mList.begin(); iter != mList.end(); ++iter)
    {
        LLSelectNode* node = *iter;
        LLViewerObject* object = node->getObject();

        if (object)
        {
            cost += object->getObjectCost();
        }
    }

    return cost;
}

F32 LLObjectSelection::getSelectedLinksetCost()
{
    cleanupNodes();
    F32 cost = 0.f;

    std::set<LLViewerObject*> me_roots;

    for (list_t::iterator iter = mList.begin(); iter != mList.end(); ++iter)
    {
        LLSelectNode* node = *iter;
        LLViewerObject* object = node->getObject();

        if (object && !object->isAttachment())
        {
            LLViewerObject* root = static_cast<LLViewerObject*>(object->getRoot());
            if (root)
            {
                if (me_roots.find(root) == me_roots.end())
                {
                    me_roots.insert(root);
                    cost += root->getLinksetCost();
                }
            }
        }
    }

    return cost;
}

F32 LLObjectSelection::getSelectedPhysicsCost()
{
    cleanupNodes();
    F32 cost = 0.f;

    for (list_t::iterator iter = mList.begin(); iter != mList.end(); ++iter)
    {
        LLSelectNode* node = *iter;
        LLViewerObject* object = node->getObject();

        if (object)
        {
            cost += object->getPhysicsCost();
        }
    }

    return cost;
}

F32 LLObjectSelection::getSelectedLinksetPhysicsCost()
{
    cleanupNodes();
    F32 cost = 0.f;

    std::set<LLViewerObject*> me_roots;

    for (list_t::iterator iter = mList.begin(); iter != mList.end(); ++iter)
    {
        LLSelectNode* node = *iter;
        LLViewerObject* object = node->getObject();

        if (object)
        {
            LLViewerObject* root = static_cast<LLViewerObject*>(object->getRoot());
            if (root)
            {
                if (me_roots.find(root) == me_roots.end())
                {
                    me_roots.insert(root);
                    cost += root->getLinksetPhysicsCost();
                }
            }
        }
    }

    return cost;
}

F32 LLObjectSelection::getSelectedObjectStreamingCost(S32* total_bytes, S32* visible_bytes)
{
    F32 cost = 0.f;
    for (list_t::iterator iter = mList.begin(); iter != mList.end(); ++iter)
    {
        LLSelectNode* node = *iter;
        LLViewerObject* object = node->getObject();

        if (object)
        {
            cost += object->getStreamingCost();

            S32 bytes = 0;
            S32 visible = 0;
            LLMeshCostData costs;
            if (object->getCostData(costs))
            {
                bytes = costs.getSizeTotal();
                visible = costs.getSizeByLOD(object->getLOD());
            }
            if (total_bytes)
            {
                *total_bytes += bytes;
            }

            if (visible_bytes)
            {
                *visible_bytes += visible;
            }
        }
    }

    return cost;
}

U32 LLObjectSelection::getSelectedObjectTriangleCount(S32* vcount)
{
    U32 count = 0;
    for (list_t::iterator iter = mList.begin(); iter != mList.end(); ++iter)
    {
        LLSelectNode* node = *iter;
        LLViewerObject* object = node->getObject();

        if (object)
        {
            S32 vt = 0;
            count += object->getTriangleCount(&vt);
            *vcount += vt;
        }
    }

    return count;
}

S32 LLObjectSelection::getSelectedObjectRenderCost()
{
       S32 cost = 0;
       LLVOVolume::texture_cost_t textures;
       typedef std::set<LLUUID> uuid_list_t;
       uuid_list_t computed_objects;

       typedef std::list<LLPointer<LLViewerObject> > child_list_t;
       typedef const child_list_t const_child_list_t;

       // add render cost of complete linksets first, to get accurate texture counts
       for (list_t::iterator iter = mList.begin(); iter != mList.end(); ++iter)
       {
               LLSelectNode* node = *iter;

               LLVOVolume* object = (LLVOVolume*)node->getObject();

               if (object && object->isRootEdit())
               {
                   cost += object->getRenderCost(textures);
                   computed_objects.insert(object->getID());

                   const const_child_list_t& children = object->getChildren();
                   for (LLViewerObject* child_obj : children)
                   {
                       LLVOVolume *child = dynamic_cast<LLVOVolume*>( child_obj );
                       if (child)
                       {
                           cost += child->getRenderCost(textures);
                           computed_objects.insert(child->getID());
                       }
                   }

                   for (LLVOVolume::texture_cost_t::iterator iter = textures.begin(); iter != textures.end(); ++iter)
                   {
                       // add the cost of each individual texture in the linkset
                       cost += LLVOVolume::getTextureCost(*iter);
                   }

                   textures.clear();
               }
       }

       // add any partial linkset objects, texture cost may be slightly misleading
        for (list_t::iterator iter = mList.begin(); iter != mList.end(); ++iter)
        {
            LLSelectNode* node = *iter;
            LLVOVolume* object = (LLVOVolume*)node->getObject();

            if (object && computed_objects.find(object->getID()) == computed_objects.end()  )
            {
                    cost += object->getRenderCost(textures);
                    computed_objects.insert(object->getID());
            }

            for (LLVOVolume::texture_cost_t::iterator iter = textures.begin(); iter != textures.end(); ++iter)
            {
                // add the cost of each individual texture in the linkset
                cost += LLVOVolume::getTextureCost(*iter);
            }

            textures.clear();
        }

       return cost;
}

//-----------------------------------------------------------------------------
// getTECount()
//-----------------------------------------------------------------------------
S32 LLObjectSelection::getTECount()
{
    S32 count = 0;
    for (LLObjectSelection::iterator iter = begin(); iter != end(); iter++)
    {
        LLSelectNode* node = *iter;
        LLViewerObject* object = node->getObject();
        if (!object)
            continue;
        S32 num_tes = object->getNumTEs();
        for (S32 te = 0; te < num_tes; te++)
        {
            if (node->isTESelected(te))
            {
                ++count;
            }
        }
    }
    return count;
}

//-----------------------------------------------------------------------------
// getRootObjectCount()
//-----------------------------------------------------------------------------
S32 LLObjectSelection::getRootObjectCount()
{
    S32 count = 0;
    for (LLObjectSelection::root_iterator iter = root_begin(); iter != root_end(); iter++)
    {
        ++count;
    }
    return count;
}

bool LLObjectSelection::applyToObjects(LLSelectedObjectFunctor* func)
{
    bool result = true;
    for (iterator iter = begin(); iter != end(); )
    {
        iterator nextiter = iter++;
        LLViewerObject* object = (*nextiter)->getObject();
        if (!object)
            continue;
        bool r = func->apply(object);
        result = result && r;
    }
    return result;
}

bool LLObjectSelection::checkAnimatedObjectEstTris()
{
    F32 est_tris = 0;
    F32 max_tris = 0;
    S32 anim_count = 0;
    for (root_iterator iter = root_begin(); iter != root_end(); ++iter)
    {
        LLViewerObject* object = (*iter)->getObject();
        if (!object)
            continue;
        if (object->isAnimatedObject())
        {
            anim_count++;
        }
        est_tris += object->recursiveGetEstTrianglesMax();
        max_tris = llmax((F32)max_tris,(F32)object->getAnimatedObjectMaxTris());
    }
    return anim_count==0 || est_tris <= max_tris;
}

bool LLObjectSelection::checkAnimatedObjectLinkable()
{
    return checkAnimatedObjectEstTris();
}

bool LLObjectSelection::applyToRootObjects(LLSelectedObjectFunctor* func, bool firstonly)
{
    bool result = !firstonly;
    for (root_iterator iter = root_begin(); iter != root_end(); )
    {
        root_iterator nextiter = iter++;
        LLViewerObject* object = (*nextiter)->getObject();
        if (!object)
            continue;
        bool r = func->apply(object);
        if (firstonly && r)
            return true;
        else
            result = result && r;
    }
    return result;
}

bool LLObjectSelection::applyToTEs(LLSelectedTEFunctor* func, bool firstonly)
{
    bool result = !firstonly;
    for (iterator iter = begin(); iter != end(); )
    {
        iterator nextiter = iter++;
        LLSelectNode* node = *nextiter;
        LLViewerObject* object = (*nextiter)->getObject();
        if (!object)
            continue;
        S32 num_tes = llmin((S32)object->getNumTEs(), (S32)object->getNumFaces()); // avatars have TEs but no faces
        for (S32 te = 0; te < num_tes; ++te)
        {
            if (node->isTESelected(te))
            {
                bool r = func->apply(object, te);
                if (firstonly && r)
                    return true;
                else
                    result = result && r;
            }
        }
    }
    return result;
}

bool LLObjectSelection::applyToNodes(LLSelectedNodeFunctor *func, bool firstonly)
{
    bool result = !firstonly;
    for (iterator iter = begin(); iter != end(); )
    {
        iterator nextiter = iter++;
        LLSelectNode* node = *nextiter;
        bool r = func->apply(node);
        if (firstonly && r)
            return true;
        else
            result = result && r;
    }
    return result;
}

bool LLObjectSelection::applyToRootNodes(LLSelectedNodeFunctor *func, bool firstonly)
{
    bool result = !firstonly;
    for (root_iterator iter = root_begin(); iter != root_end(); )
    {
        root_iterator nextiter = iter++;
        LLSelectNode* node = *nextiter;
        bool r = func->apply(node);
        if (firstonly && r)
            return true;
        else
            result = result && r;
    }
    return result;
}

bool LLObjectSelection::isMultipleTESelected()
{
    bool te_selected = false;
    // ...all faces
    for (LLObjectSelection::iterator iter = begin();
         iter != end(); iter++)
    {
        LLSelectNode* nodep = *iter;
        for (S32 i = 0; i < SELECT_MAX_TES; i++)
        {
            if(nodep->isTESelected(i))
            {
                if(te_selected)
                {
                    return true;
                }
                te_selected = true;
            }
        }
    }
    return false;
}

//-----------------------------------------------------------------------------
// contains()
//-----------------------------------------------------------------------------
bool LLObjectSelection::contains(LLViewerObject* object)
{
    return findNode(object) != NULL;
}


//-----------------------------------------------------------------------------
// contains()
//-----------------------------------------------------------------------------
bool LLObjectSelection::contains(LLViewerObject* object, S32 te)
{
    if (te == SELECT_ALL_TES)
    {
        // ...all faces
        for (LLObjectSelection::iterator iter = begin();
             iter != end(); iter++)
        {
            LLSelectNode* nodep = *iter;
            if (nodep->getObject() == object)
            {
                // Optimization
                if (nodep->getTESelectMask() == TE_SELECT_MASK_ALL)
                {
                    return true;
                }

                bool all_selected = true;
                for (S32 i = 0; i < object->getNumTEs(); i++)
                {
                    all_selected = all_selected && nodep->isTESelected(i);
                }
                return all_selected;
            }
        }
        return false;
    }
    else
    {
        // ...one face
        for (LLObjectSelection::iterator iter = begin(); iter != end(); iter++)
        {
            LLSelectNode* nodep = *iter;
            if (nodep->getObject() == object && nodep->isTESelected(te))
            {
                return true;
            }
        }
        return false;
    }
}

// returns true is any node is currenly worn as an attachment
bool LLObjectSelection::isAttachment()
{
    return (mSelectType == SELECT_TYPE_ATTACHMENT || mSelectType == SELECT_TYPE_HUD);
}

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

//-----------------------------------------------------------------------------
// getFirstNode
//-----------------------------------------------------------------------------
LLSelectNode* LLObjectSelection::getFirstNode(LLSelectedNodeFunctor* func)
{
    for (iterator iter = begin(); iter != end(); ++iter)
    {
        LLSelectNode* node = *iter;
        if (func == NULL || func->apply(node))
        {
            return node;
        }
    }
    return NULL;
}

LLSelectNode* LLObjectSelection::getFirstRootNode(LLSelectedNodeFunctor* func, bool non_root_ok)
{
    for (root_iterator iter = root_begin(); iter != root_end(); ++iter)
    {
        LLSelectNode* node = *iter;
        if (func == NULL || func->apply(node))
        {
            return node;
        }
    }
    if (non_root_ok)
    {
        // Get non root
        return getFirstNode(func);
    }
    return NULL;
}


//-----------------------------------------------------------------------------
// getFirstSelectedObject
//-----------------------------------------------------------------------------
LLViewerObject* LLObjectSelection::getFirstSelectedObject(LLSelectedNodeFunctor* func, bool get_parent)
{
    LLSelectNode* res = getFirstNode(func);
    if (res && get_parent)
    {
        return getSelectedParentObject(res->getObject());
    }
    else if (res)
    {
        return res->getObject();
    }
    return NULL;
}

//-----------------------------------------------------------------------------
// getFirstObject()
//-----------------------------------------------------------------------------
LLViewerObject* LLObjectSelection::getFirstObject()
{
    LLSelectNode* res = getFirstNode(NULL);
    return res ? res->getObject() : NULL;
}

//-----------------------------------------------------------------------------
// getFirstRootObject()
//-----------------------------------------------------------------------------
LLViewerObject* LLObjectSelection::getFirstRootObject(bool non_root_ok)
{
    LLSelectNode* res = getFirstRootNode(NULL, non_root_ok);
    return res ? res->getObject() : NULL;
}

//-----------------------------------------------------------------------------
// getFirstMoveableNode()
//-----------------------------------------------------------------------------
LLSelectNode* LLObjectSelection::getFirstMoveableNode(bool get_root_first)
{
    struct f : public LLSelectedNodeFunctor
    {
        bool apply(LLSelectNode* node)
        {
            LLViewerObject* obj = node->getObject();
            return obj && obj->permMove() && !obj->isPermanentEnforced();
        }
    } func;
    LLSelectNode* res = get_root_first ? getFirstRootNode(&func, true) : getFirstNode(&func);
    return res;
}

//-----------------------------------------------------------------------------
// getFirstCopyableObject()
//-----------------------------------------------------------------------------
LLViewerObject* LLObjectSelection::getFirstCopyableObject(bool get_parent)
{
    struct f : public LLSelectedNodeFunctor
    {
        bool apply(LLSelectNode* node)
        {
            LLViewerObject* obj = node->getObject();
            return obj && obj->permCopy() && !obj->isAttachment();
        }
    } func;
    return getFirstSelectedObject(&func, get_parent);
}

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

    struct f : public LLSelectedNodeFunctor
    {
        bool apply(LLSelectNode* node)
        {
            LLViewerObject* obj = node->getObject();
            // you can delete an object if you are the owner
            // or you have permission to modify it.
            if( obj && !obj->isPermanentEnforced() &&
                ( (obj->permModify()) ||
                (obj->permYouOwner()) ||
                (!obj->permAnyOwner())  ))      // public
            {
                if( !obj->isAttachment() )
                {
                    return true;
                }
            }
            return false;
        }
    } func;
    LLSelectNode* node = getFirstNode(&func);
    return node ? node->getObject() : NULL;
}

//-----------------------------------------------------------------------------
// getFirstEditableObject()
//-----------------------------------------------------------------------------
LLViewerObject* LLObjectSelection::getFirstEditableObject(bool get_parent)
{
    struct f : public LLSelectedNodeFunctor
    {
        bool apply(LLSelectNode* node)
        {
            LLViewerObject* obj = node->getObject();
            return obj && obj->permModify();
        }
    } func;
    return getFirstSelectedObject(&func, get_parent);
}

//-----------------------------------------------------------------------------
// getFirstMoveableObject()
//-----------------------------------------------------------------------------
LLViewerObject* LLObjectSelection::getFirstMoveableObject(bool get_parent)
{
    struct f : public LLSelectedNodeFunctor
    {
        bool apply(LLSelectNode* node)
        {
            LLViewerObject* obj = node->getObject();
            return obj && obj->permMove() && !obj->isPermanentEnforced();
        }
    } func;
    return getFirstSelectedObject(&func, get_parent);
}

//-----------------------------------------------------------------------------
// getFirstUndoEnabledObject()
//-----------------------------------------------------------------------------
LLViewerObject* LLObjectSelection::getFirstUndoEnabledObject(bool get_parent)
{
    struct f : public LLSelectedNodeFunctor
    {
        bool apply(LLSelectNode* node)
        {
            LLViewerObject* obj = node->getObject();
            return obj && (obj->permModify() || (obj->permMove() && !obj->isPermanentEnforced()));
        }
    } func;
    return getFirstSelectedObject(&func, get_parent);
}

//-----------------------------------------------------------------------------
// Position + Rotation update methods called from LLViewerJoystick
//-----------------------------------------------------------------------------
bool LLSelectMgr::selectionMove(const LLVector3& displ,
                                  F32 roll, F32 pitch, F32 yaw, U32 update_type)
{
    if (update_type == UPD_NONE)
    {
        return false;
    }

    LLVector3 displ_global;
    bool update_success = true;
    bool update_position = update_type & UPD_POSITION;
    bool update_rotation = update_type & UPD_ROTATION;
    const bool noedit_linked_parts = !gSavedSettings.getBOOL("EditLinkedParts");

    if (update_position)
    {
        // calculate the distance of the object closest to the camera origin
        F32 min_dist_squared = F32_MAX; // value will be overridden in the loop

        LLVector3 obj_pos;
        for (LLObjectSelection::root_iterator it = getSelection()->root_begin();
             it != getSelection()->root_end(); ++it)
        {
            obj_pos = (*it)->getObject()->getPositionEdit();

            F32 obj_dist_squared = dist_vec_squared(obj_pos, LLViewerCamera::getInstance()->getOrigin());
            if (obj_dist_squared < min_dist_squared)
            {
                min_dist_squared = obj_dist_squared;
            }
        }

        // factor the distance into the displacement vector. This will get us
        // equally visible movements for both close and far away selections.
        F32 min_dist = sqrt((F32) sqrtf(min_dist_squared)) / 2;
        displ_global.setVec(displ.mV[0] * min_dist,
                            displ.mV[1] * min_dist,
                            displ.mV[2] * min_dist);

        // equates to: Displ_global = Displ * M_cam_axes_in_global_frame
        displ_global = LLViewerCamera::getInstance()->rotateToAbsolute(displ_global);
    }

    LLQuaternion new_rot;
    if (update_rotation)
    {
        // let's calculate the rotation around each camera axes
        LLQuaternion qx(roll, LLViewerCamera::getInstance()->getAtAxis());
        LLQuaternion qy(pitch, LLViewerCamera::getInstance()->getLeftAxis());
        LLQuaternion qz(yaw, LLViewerCamera::getInstance()->getUpAxis());
        new_rot.setQuat(qx * qy * qz);
    }

    LLViewerObject *obj;
    S32 obj_count = getSelection()->getObjectCount();
    for (LLObjectSelection::root_iterator it = getSelection()->root_begin();
         it != getSelection()->root_end(); ++it )
    {
        obj = (*it)->getObject();
        bool enable_pos = false, enable_rot = false;
        bool perm_move = obj->permMove() && !obj->isPermanentEnforced();
        bool perm_mod = obj->permModify();

        LLVector3d sel_center(getSelectionCenterGlobal());

        if (update_rotation)
        {
            enable_rot = perm_move
                && ((perm_mod && !obj->isAttachment()) || noedit_linked_parts);

            if (enable_rot)
            {
                auto children_count = obj->getChildren().size();
                if (obj_count > 1 && children_count > 0)
                {
                    // for linked sets, rotate around the group center
                    const LLVector3 t(obj->getPositionGlobal() - sel_center);

                    // Ra = T x R x T^-1
                    LLMatrix4 mt;   mt.setTranslation(t);
                    const LLMatrix4 mnew_rot(new_rot);
                    LLMatrix4 mt_1; mt_1.setTranslation(-t);
                    mt *= mnew_rot;
                    mt *= mt_1;

                    // Rfin = Rcur * Ra
                    obj->setRotation(obj->getRotationEdit() * mt.quaternion());
                    displ_global += mt.getTranslation();
                }
                else
                {
                    obj->setRotation(obj->getRotationEdit() * new_rot);
                }
            }
            else
            {
                update_success = false;
            }
        }

        if (update_position)
        {
            // establish if object can be moved or not
            enable_pos = perm_move && !obj->isAttachment()
            && (perm_mod || noedit_linked_parts);

            if (enable_pos)
            {
                obj->setPosition(obj->getPositionEdit() + displ_global);
            }
            else
            {
                update_success = false;
            }
        }

        if (enable_pos && enable_rot && obj->mDrawable.notNull())
        {
            gPipeline.markMoved(obj->mDrawable, true);
        }
    }

    if (update_position && update_success && obj_count > 1)
    {
        updateSelectionCenter();
    }

    return update_success;
}

void LLSelectMgr::sendSelectionMove()
{
    LLSelectNode *node = mSelectedObjects->getFirstRootNode();
    if (node == NULL)
    {
        return;
    }

    //saveSelectedObjectTransform(SELECT_ACTION_TYPE_PICK);

    U32 update_type = UPD_POSITION | UPD_ROTATION;
    LLViewerRegion *last_region, *curr_region = node->getObject()->getRegion();
    S32 objects_in_this_packet = 0;

    // apply to linked objects if unable to select their individual parts
    if (!gSavedSettings.getBOOL("EditLinkedParts") && !getTEMode())
    {
        // tell simulator to apply to whole linked sets
        update_type |= UPD_LINKED_SETS;
    }

    // prepare first bulk message
    gMessageSystem->newMessage("MultipleObjectUpdate");
    packAgentAndSessionID(&update_type);

    LLViewerObject *obj = NULL;
    for (LLObjectSelection::root_iterator it = getSelection()->root_begin();
         it != getSelection()->root_end(); ++it)
    {
        obj = (*it)->getObject();

        // note: following code adapted from sendListToRegions() (@3924)
        last_region = curr_region;
        curr_region = obj->getRegion();

        // if not simulator or message too big
        if (curr_region != last_region
            || gMessageSystem->isSendFull(NULL)
            || objects_in_this_packet >= MAX_OBJECTS_PER_PACKET)
        {
            // send sim the current message and start new one
            gMessageSystem->sendReliable(last_region->getHost());
            objects_in_this_packet = 0;
            gMessageSystem->newMessage("MultipleObjectUpdate");
            packAgentAndSessionID(&update_type);
        }

        // add another instance of the body of data
        packMultipleUpdate(*it, &update_type);
        ++objects_in_this_packet;
    }

    // flush remaining messages
    if (gMessageSystem->getCurrentSendTotal() > 0)
    {
        gMessageSystem->sendReliable(curr_region->getHost());
    }
    else
    {
        gMessageSystem->clearMessage();
    }

    //saveSelectedObjectTransform(SELECT_ACTION_TYPE_PICK);
}

template<>
bool LLCheckIdenticalFunctor<F32>::same(const F32& a, const F32& b, const F32& tolerance)
{
    F32 delta = (a - b);
    F32 abs_delta = fabs(delta);
    return abs_delta <= tolerance;
}

#define DEF_DUMMY_CHECK_FUNCTOR(T)                                                  \
template<>                                                                          \
bool LLCheckIdenticalFunctor<T>::same(const T& a, const T& b, const T& tolerance)   \
{                                                                                   \
    (void)tolerance;                                                                \
    return a == b;                                                                  \
}

DEF_DUMMY_CHECK_FUNCTOR(LLUUID)
DEF_DUMMY_CHECK_FUNCTOR(LLGLenum)
DEF_DUMMY_CHECK_FUNCTOR(LLTextureEntry)
DEF_DUMMY_CHECK_FUNCTOR(LLTextureEntry::e_texgen)
DEF_DUMMY_CHECK_FUNCTOR(bool)
DEF_DUMMY_CHECK_FUNCTOR(U8)
DEF_DUMMY_CHECK_FUNCTOR(int)
DEF_DUMMY_CHECK_FUNCTOR(LLColor4)
DEF_DUMMY_CHECK_FUNCTOR(LLMediaEntry)
DEF_DUMMY_CHECK_FUNCTOR(LLPointer<LLMaterial>)
DEF_DUMMY_CHECK_FUNCTOR(LLPointer<LLGLTFMaterial>)
DEF_DUMMY_CHECK_FUNCTOR(std::string)
DEF_DUMMY_CHECK_FUNCTOR(std::vector<std::string>)

template<>
bool LLCheckIdenticalFunctor<class LLFace *>::same(class LLFace* const & a, class LLFace* const & b, class LLFace* const & tolerance)   \
{                                                                                   \
    (void)tolerance;                                                                \
    return a == b;                                                                  \
}