/**
 * @file llmaniptranslate.cpp
 * @brief LLManipTranslate class implementation
 *
 * $LicenseInfo:firstyear=2002&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$
 */

/**
 * Positioning tool
 */

#include "llviewerprecompiledheaders.h"

#include "llmaniptranslate.h"

#include "llgl.h"
#include "llrender.h"

#include "llagent.h"
#include "llagentcamera.h"
#include "llbbox.h"
#include "llbox.h"
#include "llviewercontrol.h"
#include "llcriticaldamp.h"
#include "llcylinder.h"
#include "lldrawable.h"
#include "llfloatertools.h"
#include "llfontgl.h"
#include "llglheaders.h"
#include "llhudrender.h"
#include "llresmgr.h"
#include "llselectmgr.h"
#include "llrendersphere.h"
#include "llstatusbar.h"
#include "lltoolmgr.h"
#include "llviewercamera.h"
#include "llviewerjoint.h"
#include "llviewerobject.h"
#include "llviewerwindow.h"
#include "llvoavatarself.h"
#include "llworld.h"
#include "llui.h"
#include "pipeline.h"
#include "llviewershadermgr.h"
#include "lltrans.h"

const S32 NUM_AXES = 3;
const S32 MOUSE_DRAG_SLOP = 2;       // pixels
const F32 SELECTED_ARROW_SCALE = 1.3f;
const F32 MANIPULATOR_HOTSPOT_START = 0.2f;
const F32 MANIPULATOR_HOTSPOT_END = 1.2f;
const F32 SNAP_GUIDE_SCREEN_SIZE = 0.7f;
const F32 MIN_PLANE_MANIP_DOT_PRODUCT = 0.25f;
const F32 PLANE_TICK_SIZE = 0.4f;
const F32 MANIPULATOR_SCALE_HALF_LIFE = 0.07f;
const F32 SNAP_ARROW_SCALE = 0.7f;

static LLPointer<LLViewerTexture> sGridTex = NULL ;

const LLManip::EManipPart MANIPULATOR_IDS[9] =
{
    LLManip::LL_X_ARROW,
    LLManip::LL_Y_ARROW,
    LLManip::LL_Z_ARROW,
    LLManip::LL_X_ARROW,
    LLManip::LL_Y_ARROW,
    LLManip::LL_Z_ARROW,
    LLManip::LL_YZ_PLANE,
    LLManip::LL_XZ_PLANE,
    LLManip::LL_XY_PLANE
};

const U32 ARROW_TO_AXIS[4] =
{
    VX,
    VX,
    VY,
    VZ
};

// Sort manipulator handles by their screen-space projection
struct ClosestToCamera
{
    bool operator()(const LLManipTranslate::ManipulatorHandle& a,
                    const LLManipTranslate::ManipulatorHandle& b) const
    {
        return a.mEndPosition.mV[VZ] < b.mEndPosition.mV[VZ];
    }
};

LLManipTranslate::LLManipTranslate( LLToolComposite* composite )
:   LLManip( std::string("Move"), composite ),
    mLastHoverMouseX(-1),
    mLastHoverMouseY(-1),
    mMouseOutsideSlop(false),
    mCopyMadeThisDrag(false),
    mMouseDownX(-1),
    mMouseDownY(-1),
    mAxisArrowLength(50),
    mConeSize(0),
    mArrowLengthMeters(0.f),
    mGridSizeMeters(1.f),
    mPlaneManipOffsetMeters(0.f),
    mUpdateTimer(),
    mSnapOffsetMeters(0.f),
    mSubdivisions(10.f),
    mInSnapRegime(false),
    mArrowScales(1.f, 1.f, 1.f),
    mPlaneScales(1.f, 1.f, 1.f),
    mPlaneManipPositions(1.f, 1.f, 1.f, 1.f)
{
    if (sGridTex.isNull())
    {
        restoreGL();
    }
}

//static
U32 LLManipTranslate::getGridTexName()
{
    if(sGridTex.isNull())
    {
        restoreGL() ;
    }

    return sGridTex.isNull() ? 0 : sGridTex->getTexName() ;
}

//static
void LLManipTranslate::destroyGL()
{
    if (sGridTex)
    {
        sGridTex = NULL ;
    }
}

//static
void LLManipTranslate::restoreGL()
{
    //generate grid texture
    U32 rez = 512;
    U32 mip = 0;

    destroyGL() ;
    sGridTex = LLViewerTextureManager::getLocalTexture() ;
    if(!sGridTex->createGLTexture())
    {
        sGridTex = NULL ;
        return ;
    }

    GLuint* d = new GLuint[rez*rez];

    gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_TEXTURE, sGridTex->getTexName(), true);
    gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_TRILINEAR);

    while (rez >= 1)
    {
        for (U32 i = 0; i < rez*rez; i++)
        {
            d[i] = 0x00FFFFFF;
        }

        U32 subcol = 0xFFFFFFFF;
        if (rez >= 4)
        {   //large grain grid
            for (U32 i = 0; i < rez; i++)
            {
                if (rez <= 16)
                {
                    if (rez == 16)
                    {
                        subcol = 0xA0FFFFFF;
                    }
                    else if (rez == 8)
                    {
                        subcol = 0x80FFFFFF;
                    }
                    else
                    {
                        subcol = 0x40FFFFFF;
                    }
                }
                else
                {
                    subcol = 0xFFFFFFFF;
                }
                d[i         *rez+ 0      ] = subcol;
                d[0         *rez+ i      ] = subcol;
                if (rez >= 32)
                {
                    d[i         *rez+ (rez-1)] = subcol;
                    d[(rez-1)   *rez+ i      ] = subcol;
                }

                if (rez >= 64)
                {
                    subcol = 0xFFFFFFFF;

                    if (i > 0 && i < (rez-1))
                    {
                        d[i         *rez+ 1      ] = subcol;
                        d[i         *rez+ (rez-2)] = subcol;
                        d[1         *rez+ i      ] = subcol;
                        d[(rez-2)   *rez+ i      ] = subcol;
                    }
                }
            }
        }

        subcol = 0x50A0A0A0;
        if (rez >= 128)
        { //small grain grid
            for (U32 i = 8; i < rez; i+=8)
            {
                for (U32 j = 2; j < rez-2; j++)
                {
                    d[i *rez+ j] = subcol;
                    d[j *rez+ i] = subcol;
                }
            }
        }
        if (rez >= 64)
        { //medium grain grid
            if (rez == 64)
            {
                subcol = 0x50A0A0A0;
            }
            else
            {
                subcol = 0xA0D0D0D0;
            }

            for (U32 i = 32; i < rez; i+=32)
            {
                U32 pi = i-1;
                for (U32 j = 2; j < rez-2; j++)
                {
                    d[i     *rez+ j] = subcol;
                    d[j     *rez+ i] = subcol;

                    if (rez > 128)
                    {
                        d[pi    *rez+ j] = subcol;
                        d[j     *rez+ pi] = subcol;
                    }
                }
            }
        }
        LLImageGL::setManualImage(GL_TEXTURE_2D, mip, GL_RGBA, rez, rez, GL_RGBA, GL_UNSIGNED_BYTE, d);
        rez = rez >> 1;
        mip++;
    }
    delete [] d;
}


LLManipTranslate::~LLManipTranslate()
{
}


void LLManipTranslate::handleSelect()
{
    LLSelectMgr::getInstance()->saveSelectedObjectTransform(SELECT_ACTION_TYPE_PICK);
    if (gFloaterTools)
    {
        gFloaterTools->setStatusText("move");
    }
    LLManip::handleSelect();
}

bool LLManipTranslate::handleMouseDown(S32 x, S32 y, MASK mask)
{
    bool    handled = false;

    // didn't click in any UI object, so must have clicked in the world
    if( (mHighlightedPart == LL_X_ARROW ||
         mHighlightedPart == LL_Y_ARROW ||
         mHighlightedPart == LL_Z_ARROW ||
         mHighlightedPart == LL_YZ_PLANE ||
         mHighlightedPart == LL_XZ_PLANE ||
         mHighlightedPart == LL_XY_PLANE ) )
    {
        handled = handleMouseDownOnPart( x, y, mask );
    }

    return handled;
}

// Assumes that one of the arrows on an object was hit.
bool LLManipTranslate::handleMouseDownOnPart( S32 x, S32 y, MASK mask )
{
    bool can_move = canAffectSelection();
    if (!can_move)
    {
        return false;
    }

    highlightManipulators(x, y);
    S32 hit_part = mHighlightedPart;

    if( (hit_part != LL_X_ARROW) &&
        (hit_part != LL_Y_ARROW) &&
        (hit_part != LL_Z_ARROW) &&
        (hit_part != LL_YZ_PLANE) &&
        (hit_part != LL_XZ_PLANE) &&
        (hit_part != LL_XY_PLANE) )
    {
        return true;
    }

    mHelpTextTimer.reset();
    sNumTimesHelpTextShown++;

    LLSelectMgr::getInstance()->getGrid(mGridOrigin, mGridRotation, mGridScale);

    LLSelectMgr::getInstance()->enableSilhouette(false);

    // we just started a drag, so save initial object positions
    LLSelectMgr::getInstance()->saveSelectedObjectTransform(SELECT_ACTION_TYPE_MOVE);

    mManipPart = (EManipPart)hit_part;
    mMouseDownX = x;
    mMouseDownY = y;
    mMouseOutsideSlop = false;

    LLVector3       axis;

    LLSelectNode *selectNode = mObjectSelection->getFirstMoveableNode(true);

    if (!selectNode)
    {
        // didn't find the object in our selection...oh well
        LL_WARNS() << "Trying to translate an unselected object" << LL_ENDL;
        return true;
    }

    LLViewerObject *selected_object = selectNode->getObject();
    if (!selected_object)
    {
        // somehow we lost the object!
        LL_WARNS() << "Translate manip lost the object, no selected object" << LL_ENDL;
        gViewerWindow->setCursor(UI_CURSOR_TOOLTRANSLATE);
        return true;
    }

    // Compute unit vectors for arrow hit and a plane through that vector
    bool axis_exists = getManipAxis(selected_object, mManipPart, axis);
    getManipNormal(selected_object, mManipPart, mManipNormal);

    //LLVector3 select_center_agent = gAgent.getPosAgentFromGlobal(LLSelectMgr::getInstance()->getSelectionCenterGlobal());
    // TomY: The above should (?) be identical to the below
    LLVector3 select_center_agent = getPivotPoint();
    mSubdivisions = getSubdivisionLevel(select_center_agent, axis_exists ? axis : LLVector3::z_axis, getMinGridScale());

    // if we clicked on a planar manipulator, recenter mouse cursor
    if (mManipPart >= LL_YZ_PLANE && mManipPart <= LL_XY_PLANE)
    {
        LLCoordGL mouse_pos;
        if (!LLViewerCamera::getInstance()->projectPosAgentToScreen(select_center_agent, mouse_pos))
        {
            // mouse_pos may be nonsense
            LL_WARNS() << "Failed to project object center to screen" << LL_ENDL;
        }
        else if (gSavedSettings.getBOOL("SnapToMouseCursor"))
        {
            LLUI::getInstance()->setMousePositionScreen(mouse_pos.mX, mouse_pos.mY);
            x = mouse_pos.mX;
            y = mouse_pos.mY;
        }
    }

    LLSelectMgr::getInstance()->updateSelectionCenter();
    LLVector3d object_start_global = gAgent.getPosGlobalFromAgent(getPivotPoint());
    getMousePointOnPlaneGlobal(mDragCursorStartGlobal, x, y, object_start_global, mManipNormal);
    mDragSelectionStartGlobal = object_start_global;
    mCopyMadeThisDrag = false;

    // Route future Mouse messages here preemptively.  (Release on mouse up.)
    setMouseCapture( true );

    return true;
}

bool LLManipTranslate::handleHover(S32 x, S32 y, MASK mask)
{
    // Translation tool only works if mouse button is down.
    // Bail out if mouse not down.
    if( !hasMouseCapture() )
    {
        LL_DEBUGS("UserInput") << "hover handled by LLManipTranslate (inactive)" << LL_ENDL;
        // Always show cursor
        // gViewerWindow->setCursor(UI_CURSOR_ARROW);
        gViewerWindow->setCursor(UI_CURSOR_TOOLTRANSLATE);

        highlightManipulators(x, y);
        return true;
    }

    // Handle auto-rotation if necessary.
    LLRect world_rect = gViewerWindow->getWorldViewRectScaled();
    const F32 ROTATE_ANGLE_PER_SECOND = 30.f * DEG_TO_RAD;
    const S32 ROTATE_H_MARGIN = world_rect.getWidth() / 20;
    const F32 rotate_angle = ROTATE_ANGLE_PER_SECOND / gFPSClamped;
    bool rotated = false;

    // ...build mode moves camera about focus point
    if (mObjectSelection->getSelectType() != SELECT_TYPE_HUD)
    {
        if (x < ROTATE_H_MARGIN)
        {
            gAgentCamera.cameraOrbitAround(rotate_angle);
            rotated = true;
        }
        else if (x > world_rect.getWidth() - ROTATE_H_MARGIN)
        {
            gAgentCamera.cameraOrbitAround(-rotate_angle);
            rotated = true;
        }
    }

    // Suppress processing if mouse hasn't actually moved.
    // This may cause problems if the camera moves outside of the
    // rotation above.
    if( x == mLastHoverMouseX && y == mLastHoverMouseY && !rotated)
    {
        LL_DEBUGS("UserInput") << "hover handled by LLManipTranslate (mouse unmoved)" << LL_ENDL;
        gViewerWindow->setCursor(UI_CURSOR_TOOLTRANSLATE);
        return true;
    }
    mLastHoverMouseX = x;
    mLastHoverMouseY = y;

    // Suppress if mouse hasn't moved past the initial slop region
    // Reset once we start moving
    if( !mMouseOutsideSlop )
    {
        if (abs(mMouseDownX - x) < MOUSE_DRAG_SLOP && abs(mMouseDownY - y) < MOUSE_DRAG_SLOP )
        {
            LL_DEBUGS("UserInput") << "hover handled by LLManipTranslate (mouse inside slop)" << LL_ENDL;
            gViewerWindow->setCursor(UI_CURSOR_TOOLTRANSLATE);
            return true;
        }
        else
        {
            // ...just went outside the slop region
            mMouseOutsideSlop = true;
            // If holding down shift, leave behind a copy.
            if (mask == MASK_COPY)
            {
                // ...we're trying to make a copy
                LLSelectMgr::getInstance()->selectDuplicate(LLVector3::zero, false);
                mCopyMadeThisDrag = true;

                // When we make the copy, we don't want to do any other processing.
                // If so, the object will also be moved, and the copy will be offset.
                LL_DEBUGS("UserInput") << "hover handled by LLManipTranslate (made copy)" << LL_ENDL;
                gViewerWindow->setCursor(UI_CURSOR_TOOLTRANSLATE);
            }
        }
    }

    // Throttle updates to 10 per second.

    LLVector3       axis_f;
    LLVector3d      axis_d;

    // pick the first object to constrain to grid w/ common origin
    // this is so we don't screw up groups
    LLSelectNode* selectNode = mObjectSelection->getFirstMoveableNode(true);
    if (!selectNode)
    {
        // somehow we lost the object!
        LL_WARNS() << "Translate manip lost the object, no selectNode" << LL_ENDL;
        gViewerWindow->setCursor(UI_CURSOR_TOOLTRANSLATE);
        return true;
    }

    LLViewerObject* object = selectNode->getObject();
    if (!object)
    {
        // somehow we lost the object!
        LL_WARNS() << "Translate manip lost the object, no object in selectNode" << LL_ENDL;
        gViewerWindow->setCursor(UI_CURSOR_TOOLTRANSLATE);
        return true;
    }

    // Compute unit vectors for arrow hit and a plane through that vector
    bool axis_exists = getManipAxis(object, mManipPart, axis_f);        // TODO: move this

    axis_d.setVec(axis_f);

    LLSelectMgr::getInstance()->updateSelectionCenter();
    LLVector3d current_pos_global = gAgent.getPosGlobalFromAgent(getPivotPoint());

    mSubdivisions = getSubdivisionLevel(getPivotPoint(), axis_f, getMinGridScale());

    // Project the cursor onto that plane
    LLVector3d relative_move;
    getMousePointOnPlaneGlobal(relative_move, x, y, current_pos_global, mManipNormal);\
    relative_move -= mDragCursorStartGlobal;

    // You can't move more than some distance from your original mousedown point.
    if (gSavedSettings.getBOOL("LimitDragDistance"))
    {
        F32 max_drag_distance = gSavedSettings.getF32("MaxDragDistance");

        if (relative_move.magVecSquared() > max_drag_distance * max_drag_distance)
        {
            LL_DEBUGS("UserInput") << "hover handled by LLManipTranslate (too far)" << LL_ENDL;
            gViewerWindow->setCursor(UI_CURSOR_NOLOCKED);
            return true;
        }
    }

    F64 axis_magnitude = relative_move * axis_d;                    // dot product
    LLVector3d cursor_point_snap_line;

    F64 off_axis_magnitude;

    getMousePointOnPlaneGlobal(cursor_point_snap_line, x, y, current_pos_global, mSnapOffsetAxis % axis_f);
    off_axis_magnitude = axis_exists ? llabs((cursor_point_snap_line - current_pos_global) * LLVector3d(mSnapOffsetAxis)) : 0.f;

    if (gSavedSettings.getBOOL("SnapEnabled"))
    {
        if (off_axis_magnitude > mSnapOffsetMeters)
        {
            mInSnapRegime = true;
            LLVector3 cursor_snap_agent = gAgent.getPosAgentFromGlobal(cursor_point_snap_line);

            F32 cursor_grid_dist = (cursor_snap_agent - mGridOrigin) * axis_f;

            F32 snap_dist = getMinGridScale() / (2.f * mSubdivisions);
            F32 relative_snap_dist = fmodf(llabs(cursor_grid_dist) + snap_dist, getMinGridScale() / mSubdivisions);
            if (relative_snap_dist < snap_dist * 2.f)
            {
                if (cursor_grid_dist > 0.f)
                {
                    cursor_grid_dist -= relative_snap_dist - snap_dist;
                }
                else
                {
                    cursor_grid_dist += relative_snap_dist - snap_dist;
                }
            }

            F32 object_start_on_axis = (gAgent.getPosAgentFromGlobal(mDragSelectionStartGlobal) - mGridOrigin) * axis_f;
            axis_magnitude = cursor_grid_dist - object_start_on_axis;
        }
        else if (mManipPart >= LL_YZ_PLANE && mManipPart <= LL_XY_PLANE)
        {
            // subtract offset from object center
            LLVector3d cursor_point_global;
            getMousePointOnPlaneGlobal( cursor_point_global, x, y, current_pos_global, mManipNormal );
            cursor_point_global -= (mDragCursorStartGlobal - mDragSelectionStartGlobal);

            // snap to planar grid
            LLVector3 cursor_point_agent = gAgent.getPosAgentFromGlobal(cursor_point_global);
            LLVector3 camera_plane_projection = LLViewerCamera::getInstance()->getAtAxis();
            camera_plane_projection -= projected_vec(camera_plane_projection, mManipNormal);
            camera_plane_projection.normVec();
            LLVector3 camera_projected_dir = camera_plane_projection;
            camera_plane_projection.rotVec(~mGridRotation);
            camera_plane_projection.scaleVec(mGridScale);
            camera_plane_projection.abs();
            F32 max_grid_scale;
            if (camera_plane_projection.mV[VX] > camera_plane_projection.mV[VY] &&
                camera_plane_projection.mV[VX] > camera_plane_projection.mV[VZ])
            {
                max_grid_scale = mGridScale.mV[VX];
            }
            else if (camera_plane_projection.mV[VY] > camera_plane_projection.mV[VZ])
            {
                max_grid_scale = mGridScale.mV[VY];
            }
            else
            {
                max_grid_scale = mGridScale.mV[VZ];
            }

            F32 num_subdivisions = getSubdivisionLevel(getPivotPoint(), camera_projected_dir, max_grid_scale);

            F32 grid_scale_a;
            F32 grid_scale_b;
            LLVector3 cursor_point_grid = (cursor_point_agent - mGridOrigin) * ~mGridRotation;

            switch (mManipPart)
            {
            case LL_YZ_PLANE:
                grid_scale_a = mGridScale.mV[VY] / num_subdivisions;
                grid_scale_b = mGridScale.mV[VZ] / num_subdivisions;
                cursor_point_grid.mV[VY] -= fmod(cursor_point_grid.mV[VY] + grid_scale_a * 0.5f, grid_scale_a) - grid_scale_a * 0.5f;
                cursor_point_grid.mV[VZ] -= fmod(cursor_point_grid.mV[VZ] + grid_scale_b * 0.5f, grid_scale_b) - grid_scale_b * 0.5f;
                break;
            case LL_XZ_PLANE:
                grid_scale_a = mGridScale.mV[VX] / num_subdivisions;
                grid_scale_b = mGridScale.mV[VZ] / num_subdivisions;
                cursor_point_grid.mV[VX] -= fmod(cursor_point_grid.mV[VX] + grid_scale_a * 0.5f, grid_scale_a) - grid_scale_a * 0.5f;
                cursor_point_grid.mV[VZ] -= fmod(cursor_point_grid.mV[VZ] + grid_scale_b * 0.5f, grid_scale_b) - grid_scale_b * 0.5f;
                break;
            case LL_XY_PLANE:
                grid_scale_a = mGridScale.mV[VX] / num_subdivisions;
                grid_scale_b = mGridScale.mV[VY] / num_subdivisions;
                cursor_point_grid.mV[VX] -= fmod(cursor_point_grid.mV[VX] + grid_scale_a * 0.5f, grid_scale_a) - grid_scale_a * 0.5f;
                cursor_point_grid.mV[VY] -= fmod(cursor_point_grid.mV[VY] + grid_scale_b * 0.5f, grid_scale_b) - grid_scale_b * 0.5f;
                break;
            default:
                break;
            }
            cursor_point_agent = (cursor_point_grid * mGridRotation) + mGridOrigin;
            relative_move.setVec(cursor_point_agent - gAgent.getPosAgentFromGlobal(mDragSelectionStartGlobal));
            mInSnapRegime = true;
        }
        else
        {
            mInSnapRegime = false;
        }
    }
    else
    {
        mInSnapRegime = false;
    }

    // Clamp to arrow direction
    // *FIX: does this apply anymore?
    if (!axis_exists)
    {
        axis_magnitude = relative_move.normVec();
        axis_d.setVec(relative_move);
        axis_d.normVec();
        axis_f.setVec(axis_d);
    }

    LLVector3d clamped_relative_move = axis_magnitude * axis_d; // scalar multiply
    LLVector3 clamped_relative_move_f = (F32)axis_magnitude * axis_f; // scalar multiply

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

        if (selectNode->mSelectedGLTFNode != -1)
        {
            // manipulating a GLTF node
            clamped_relative_move_f -= selectNode->mLastMoveLocal;
            object->moveGLTFNode(selectNode->mSelectedGLTFNode, clamped_relative_move_f);
            selectNode->mLastMoveLocal += clamped_relative_move_f;
        }
        else
        {
            // Only apply motion to root objects and objects selected
            // as "individual".
            if (!object->isRootEdit() && !selectNode->mIndividualSelection)
            {
                continue;
            }

            if (!object->isRootEdit())
            {
                // child objects should not update if parent is selected
                LLViewerObject* editable_root = (LLViewerObject*)object->getParent();
                if (editable_root->isSelected())
                {
                    // we will be moved properly by our parent, so skip
                    continue;
                }
            }

            LLViewerObject* root_object = (object == NULL) ? NULL : object->getRootEdit();
            if (object->permMove() && !object->isPermanentEnforced() &&
                ((root_object == NULL) || !root_object->isPermanentEnforced()))
            {
                // handle attachments in local space
                if (object->isAttachment() && object->mDrawable.notNull())
                {
                    // calculate local version of relative move
                    LLQuaternion objWorldRotation = object->mDrawable->mXform.getParent()->getWorldRotation();
                    objWorldRotation.transQuat();

                    LLVector3 old_position_local = object->getPosition();
                    LLVector3 new_position_local = selectNode->mSavedPositionLocal + (clamped_relative_move_f * objWorldRotation);

                    //RN: I forget, but we need to do this because of snapping which doesn't often result
                    // in position changes even when the mouse moves
                    object->setPosition(new_position_local);
                    rebuild(object);
                    gAgentAvatarp->clampAttachmentPositions();
                    new_position_local = object->getPosition();

                    if (selectNode->mIndividualSelection)
                    {
                        // counter-translate child objects if we are moving the root as an individual
                        object->resetChildrenPosition(old_position_local - new_position_local, true);
                    }
                }
                else
                {
                    // compute new position to send to simulators, but don't set it yet.
                    // We need the old position to know which simulator to send the move message to.
                    LLVector3d new_position_global = selectNode->mSavedPositionGlobal + clamped_relative_move;

                    // Don't let object centers go too far underground
                    F64 min_height = LLWorld::getInstance()->getMinAllowedZ(object, object->getPositionGlobal());
                    if (new_position_global.mdV[VZ] < min_height)
                    {
                        new_position_global.mdV[VZ] = min_height;
                    }

                    // For safety, cap heights where objects can be dragged
                    if (new_position_global.mdV[VZ] > MAX_OBJECT_Z)
                    {
                        new_position_global.mdV[VZ] = MAX_OBJECT_Z;
                    }

                    // Grass is always drawn on the ground, so clamp its position to the ground
                    if (object->getPCode() == LL_PCODE_LEGACY_GRASS)
                    {
                        new_position_global.mdV[VZ] = LLWorld::getInstance()->resolveLandHeightGlobal(new_position_global) + 1.f;
                    }

                    if (object->isRootEdit())
                    {
                        new_position_global = LLWorld::getInstance()->clipToVisibleRegions(object->getPositionGlobal(), new_position_global);
                    }

                    // PR: Only update if changed
                    LLVector3 old_position_agent = object->getPositionAgent();
                    LLVector3 new_position_agent = gAgent.getPosAgentFromGlobal(new_position_global);
                    if (object->isRootEdit())
                    {
                        // finally, move parent object after children have calculated new offsets
                        object->setPositionAgent(new_position_agent);
                        rebuild(object);
                    }
                    else
                    {
                        LLViewerObject* root_object = object->getRootEdit();
                        new_position_agent -= root_object->getPositionAgent();
                        new_position_agent = new_position_agent * ~root_object->getRotation();
                        object->setPositionParent(new_position_agent, false);
                        rebuild(object);
                    }

                    if (selectNode->mIndividualSelection)
                    {
                        // counter-translate child objects if we are moving the root as an individual
                        object->resetChildrenPosition(old_position_agent - new_position_agent, true);
                    }
                }
                selectNode->mLastPositionLocal = object->getPosition();
            }
        }
    }


    LLSelectMgr::getInstance()->updateSelectionCenter();
    gAgentCamera.clearFocusObject();
    dialog_refresh_all();       // ??? is this necessary?

    LL_DEBUGS("UserInput") << "hover handled by LLManipTranslate (active)" << LL_ENDL;
    gViewerWindow->setCursor(UI_CURSOR_TOOLTRANSLATE);
    return true;
}

void LLManipTranslate::highlightManipulators(S32 x, S32 y)
{
    mHighlightedPart = LL_NO_PART;

    if (!mObjectSelection->getObjectCount())
    {
        return;
    }

    //LLBBox bbox = LLSelectMgr::getInstance()->getBBoxOfSelection();
    LLMatrix4 projMatrix = LLViewerCamera::getInstance()->getProjection();
    LLMatrix4 modelView = LLViewerCamera::getInstance()->getModelview();

    LLVector3 object_position = getPivotPoint();

    LLVector3 grid_origin;
    LLVector3 grid_scale;
    LLQuaternion grid_rotation;

    LLSelectMgr::getInstance()->getGrid(grid_origin, grid_rotation, grid_scale);

    LLVector3 relative_camera_dir;

    LLMatrix4 transform;

    if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD)
    {
        relative_camera_dir = LLVector3(1.f, 0.f, 0.f) * ~grid_rotation;
        LLVector4 translation(object_position);
        transform.initRotTrans(grid_rotation, translation);
        LLMatrix4 cfr(OGL_TO_CFR_ROTATION);
        transform *= cfr;
        LLMatrix4 window_scale;
        F32 zoom_level = 2.f * gAgentCamera.mHUDCurZoom;
        window_scale.initAll(LLVector3(zoom_level / LLViewerCamera::getInstance()->getAspect(), zoom_level, 0.f),
            LLQuaternion::DEFAULT,
            LLVector3::zero);
        transform *= window_scale;
    }
    else
    {
        relative_camera_dir = (object_position - LLViewerCamera::getInstance()->getOrigin()) * ~grid_rotation;
        relative_camera_dir.normVec();

        transform.initRotTrans(grid_rotation, LLVector4(object_position));
        transform *= modelView;
        transform *= projMatrix;
    }

    S32 numManips = 0;

    // edges
    mManipulatorVertices[numManips++] = LLVector4(mArrowLengthMeters * MANIPULATOR_HOTSPOT_START, 0.f, 0.f, 1.f);
    mManipulatorVertices[numManips++] = LLVector4(mArrowLengthMeters * MANIPULATOR_HOTSPOT_END, 0.f, 0.f, 1.f);

    mManipulatorVertices[numManips++] = LLVector4(0.f, mArrowLengthMeters * MANIPULATOR_HOTSPOT_START, 0.f, 1.f);
    mManipulatorVertices[numManips++] = LLVector4(0.f, mArrowLengthMeters * MANIPULATOR_HOTSPOT_END, 0.f, 1.f);

    mManipulatorVertices[numManips++] = LLVector4(0.f, 0.f, mArrowLengthMeters * MANIPULATOR_HOTSPOT_START, 1.f);
    mManipulatorVertices[numManips++] = LLVector4(0.f, 0.f, mArrowLengthMeters * MANIPULATOR_HOTSPOT_END, 1.f);

    mManipulatorVertices[numManips++] = LLVector4(mArrowLengthMeters * -MANIPULATOR_HOTSPOT_START, 0.f, 0.f, 1.f);
    mManipulatorVertices[numManips++] = LLVector4(mArrowLengthMeters * -MANIPULATOR_HOTSPOT_END, 0.f, 0.f, 1.f);

    mManipulatorVertices[numManips++] = LLVector4(0.f, mArrowLengthMeters * -MANIPULATOR_HOTSPOT_START, 0.f, 1.f);
    mManipulatorVertices[numManips++] = LLVector4(0.f, mArrowLengthMeters * -MANIPULATOR_HOTSPOT_END, 0.f, 1.f);

    mManipulatorVertices[numManips++] = LLVector4(0.f, 0.f, mArrowLengthMeters * -MANIPULATOR_HOTSPOT_START, 1.f);
    mManipulatorVertices[numManips++] = LLVector4(0.f, 0.f, mArrowLengthMeters * -MANIPULATOR_HOTSPOT_END, 1.f);

    S32 num_arrow_manips = numManips;

    // planar manipulators
    bool planar_manip_yz_visible = false;
    bool planar_manip_xz_visible = false;
    bool planar_manip_xy_visible = false;

    mManipulatorVertices[numManips] = LLVector4(0.f, mPlaneManipOffsetMeters * (1.f - PLANE_TICK_SIZE * 0.5f), mPlaneManipOffsetMeters * (1.f - PLANE_TICK_SIZE * 0.5f), 1.f);
    mManipulatorVertices[numManips++].scaleVec(mPlaneManipPositions);
    mManipulatorVertices[numManips] = LLVector4(0.f, mPlaneManipOffsetMeters * (1.f + PLANE_TICK_SIZE * 0.5f), mPlaneManipOffsetMeters * (1.f + PLANE_TICK_SIZE * 0.5f), 1.f);
    mManipulatorVertices[numManips++].scaleVec(mPlaneManipPositions);
    if (llabs(relative_camera_dir.mV[VX]) > MIN_PLANE_MANIP_DOT_PRODUCT)
    {
        planar_manip_yz_visible = true;
    }

    mManipulatorVertices[numManips] = LLVector4(mPlaneManipOffsetMeters * (1.f - PLANE_TICK_SIZE * 0.5f), 0.f, mPlaneManipOffsetMeters * (1.f - PLANE_TICK_SIZE * 0.5f), 1.f);
    mManipulatorVertices[numManips++].scaleVec(mPlaneManipPositions);
    mManipulatorVertices[numManips] = LLVector4(mPlaneManipOffsetMeters * (1.f + PLANE_TICK_SIZE * 0.5f), 0.f, mPlaneManipOffsetMeters * (1.f + PLANE_TICK_SIZE * 0.5f), 1.f);
    mManipulatorVertices[numManips++].scaleVec(mPlaneManipPositions);
    if (llabs(relative_camera_dir.mV[VY]) > MIN_PLANE_MANIP_DOT_PRODUCT)
    {
        planar_manip_xz_visible = true;
    }

    mManipulatorVertices[numManips] = LLVector4(mPlaneManipOffsetMeters * (1.f - PLANE_TICK_SIZE * 0.5f), mPlaneManipOffsetMeters * (1.f - PLANE_TICK_SIZE * 0.5f), 0.f, 1.f);
    mManipulatorVertices[numManips++].scaleVec(mPlaneManipPositions);
    mManipulatorVertices[numManips] = LLVector4(mPlaneManipOffsetMeters * (1.f + PLANE_TICK_SIZE * 0.5f), mPlaneManipOffsetMeters * (1.f + PLANE_TICK_SIZE * 0.5f), 0.f, 1.f);
    mManipulatorVertices[numManips++].scaleVec(mPlaneManipPositions);
    if (llabs(relative_camera_dir.mV[VZ]) > MIN_PLANE_MANIP_DOT_PRODUCT)
    {
        planar_manip_xy_visible = true;
    }

    // Project up to 9 manipulators to screen space 2*X, 2*Y, 2*Z, 3*planes
    std::vector<ManipulatorHandle> projected_manipulators;
    projected_manipulators.reserve(9);

    for (S32 i = 0; i < num_arrow_manips; i+= 2)
    {
        LLVector4 projected_start = mManipulatorVertices[i] * transform;
        projected_start = projected_start * (1.f / projected_start.mV[VW]);

        LLVector4 projected_end = mManipulatorVertices[i + 1] * transform;
        projected_end = projected_end * (1.f / projected_end.mV[VW]);

        ManipulatorHandle projected_manip(
                LLVector3(projected_start.mV[VX], projected_start.mV[VY], projected_start.mV[VZ]),
                LLVector3(projected_end.mV[VX], projected_end.mV[VY], projected_end.mV[VZ]),
                MANIPULATOR_IDS[i / 2],
                10.f); // 10 pixel hotspot for arrows
        projected_manipulators.push_back(projected_manip);
    }

    if (planar_manip_yz_visible)
    {
        S32 i = num_arrow_manips;
        LLVector4 projected_start = mManipulatorVertices[i] * transform;
        projected_start = projected_start * (1.f / projected_start.mV[VW]);

        LLVector4 projected_end = mManipulatorVertices[i + 1] * transform;
        projected_end = projected_end * (1.f / projected_end.mV[VW]);

        ManipulatorHandle projected_manip(
                LLVector3(projected_start.mV[VX], projected_start.mV[VY], projected_start.mV[VZ]),
                LLVector3(projected_end.mV[VX], projected_end.mV[VY], projected_end.mV[VZ]),
                MANIPULATOR_IDS[i / 2],
                20.f); // 20 pixels for planar manipulators
        projected_manipulators.push_back(projected_manip);
    }

    if (planar_manip_xz_visible)
    {
        S32 i = num_arrow_manips + 2;
        LLVector4 projected_start = mManipulatorVertices[i] * transform;
        projected_start = projected_start * (1.f / projected_start.mV[VW]);

        LLVector4 projected_end = mManipulatorVertices[i + 1] * transform;
        projected_end = projected_end * (1.f / projected_end.mV[VW]);

        ManipulatorHandle projected_manip(
                LLVector3(projected_start.mV[VX], projected_start.mV[VY], projected_start.mV[VZ]),
                LLVector3(projected_end.mV[VX], projected_end.mV[VY], projected_end.mV[VZ]),
                MANIPULATOR_IDS[i / 2],
                20.f); // 20 pixels for planar manipulators
        projected_manipulators.push_back(projected_manip);
    }

    if (planar_manip_xy_visible)
    {
        S32 i = num_arrow_manips + 4;
        LLVector4 projected_start = mManipulatorVertices[i] * transform;
        projected_start = projected_start * (1.f / projected_start.mV[VW]);

        LLVector4 projected_end = mManipulatorVertices[i + 1] * transform;
        projected_end = projected_end * (1.f / projected_end.mV[VW]);

        ManipulatorHandle projected_manip(
                LLVector3(projected_start.mV[VX], projected_start.mV[VY], projected_start.mV[VZ]),
                LLVector3(projected_end.mV[VX], projected_end.mV[VY], projected_end.mV[VZ]),
                MANIPULATOR_IDS[i / 2],
                20.f); // 20 pixels for planar manipulators
        projected_manipulators.push_back(projected_manip);
    }

    LLVector2 manip_start_2d;
    LLVector2 manip_end_2d;
    LLVector2 manip_dir;
    LLRect world_view_rect = gViewerWindow->getWorldViewRectScaled();
    F32 half_width = (F32)world_view_rect.getWidth() / 2.f;
    F32 half_height = (F32)world_view_rect.getHeight() / 2.f;
    LLVector2 mousePos((F32)x - half_width, (F32)y - half_height);
    LLVector2 mouse_delta;

    // Keep order consistent with insertion via stable_sort
    std::stable_sort( projected_manipulators.begin(),
        projected_manipulators.end(),
        ClosestToCamera() );

    std::vector<ManipulatorHandle>::iterator it = projected_manipulators.begin();
    for ( ; it != projected_manipulators.end(); ++it)
    {
        ManipulatorHandle& manipulator = *it;
        {
            manip_start_2d.setVec(manipulator.mStartPosition.mV[VX] * half_width, manipulator.mStartPosition.mV[VY] * half_height);
            manip_end_2d.setVec(manipulator.mEndPosition.mV[VX] * half_width, manipulator.mEndPosition.mV[VY] * half_height);
            manip_dir = manip_end_2d - manip_start_2d;

            mouse_delta = mousePos - manip_start_2d;

            F32 manip_length = manip_dir.normVec();

            F32 mouse_pos_manip = mouse_delta * manip_dir;
            F32 mouse_dist_manip_squared = mouse_delta.magVecSquared() - (mouse_pos_manip * mouse_pos_manip);

            if (mouse_pos_manip > 0.f &&
                mouse_pos_manip < manip_length &&
                mouse_dist_manip_squared < manipulator.mHotSpotRadius * manipulator.mHotSpotRadius)
            {
                mHighlightedPart = manipulator.mManipID;
                break;
            }
        }
    }
}

F32 LLManipTranslate::getMinGridScale()
{
    F32 scale;
    switch (mManipPart)
    {
    case LL_NO_PART:
    default:
        scale = 1.f;
        break;
    case LL_X_ARROW:
        scale = mGridScale.mV[VX];
        break;
    case LL_Y_ARROW:
        scale = mGridScale.mV[VY];
        break;
    case LL_Z_ARROW:
        scale = mGridScale.mV[VZ];
        break;
    case LL_YZ_PLANE:
        scale = llmin(mGridScale.mV[VY], mGridScale.mV[VZ]);
        break;
    case LL_XZ_PLANE:
        scale = llmin(mGridScale.mV[VX], mGridScale.mV[VZ]);
        break;
    case LL_XY_PLANE:
        scale = llmin(mGridScale.mV[VX], mGridScale.mV[VY]);
        break;
    }

    return scale;
}


bool LLManipTranslate::handleMouseUp(S32 x, S32 y, MASK mask)
{
    // first, perform normal processing in case this was a quick-click
    handleHover(x, y, mask);

    if(hasMouseCapture())
    {
        // make sure arrow colors go back to normal
        mManipPart = LL_NO_PART;
        LLSelectMgr::getInstance()->enableSilhouette(true);

        // Might have missed last update due to UPDATE_DELAY timing.
        LLSelectMgr::getInstance()->sendMultipleUpdate( UPD_POSITION );

        mInSnapRegime = false;
        LLSelectMgr::getInstance()->saveSelectedObjectTransform(SELECT_ACTION_TYPE_PICK);
        //gAgent.setObjectTracking(gSavedSettings.getBOOL("TrackFocusObject"));
    }

    return LLManip::handleMouseUp(x, y, mask);
}


void LLManipTranslate::render()
{
    gGL.matrixMode(LLRender::MM_MODELVIEW);
    gGL.pushMatrix();
    if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD)
    {
        F32 zoom = gAgentCamera.mHUDCurZoom;
        gGL.scalef(zoom, zoom, zoom);
    }
    {
        LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE);
        renderGuidelines();
    }
    {
        //LLGLDisable gls_stencil(GL_STENCIL_TEST);
        renderTranslationHandles();
        renderSnapGuides();
    }
    gGL.popMatrix();

    renderText();
}

void LLManipTranslate::renderSnapGuides()
{
    if (!gSavedSettings.getBOOL("SnapEnabled"))
    {
        return;
    }

    F32 max_subdivisions = sGridMaxSubdivisionLevel;//(F32)gSavedSettings.getS32("GridSubdivision");
    F32 line_alpha = gSavedSettings.getF32("GridOpacity");

    gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
    LLGLDepthTest gls_depth(GL_TRUE);
    LLGLDisable gls_cull(GL_CULL_FACE);
    LLVector3 translate_axis;

    if (mManipPart == LL_NO_PART)
    {
        return;
    }

    LLSelectNode *first_node = mObjectSelection->getFirstMoveableNode(true);
    if (!first_node)
    {
        return;
    }

    updateGridSettings();

    F32 smallest_grid_unit_scale = getMinGridScale() / max_subdivisions;
    LLVector3 grid_origin;
    LLVector3 grid_scale;
    LLQuaternion grid_rotation;

    LLSelectMgr::getInstance()->getGrid(grid_origin, grid_rotation, grid_scale);
    LLVector3 saved_selection_center = getSavedPivotPoint(); //LLSelectMgr::getInstance()->getSavedBBoxOfSelection().getCenterAgent();
    LLVector3 selection_center = getPivotPoint();

    LLViewerObject *first_object = first_node->getObject();

    //pick appropriate projection plane for snap rulers according to relative camera position
    if (mManipPart >= LL_X_ARROW && mManipPart <= LL_Z_ARROW)
    {
        LLVector3 normal;
        LLColor4 inner_color;
        LLManip::EManipPart temp_manip = mManipPart;
        switch (mManipPart)
        {
        case LL_X_ARROW:
            normal.setVec(1,0,0);
            inner_color.setVec(0,1,1,line_alpha);
            mManipPart = LL_YZ_PLANE;
            break;
        case LL_Y_ARROW:
            normal.setVec(0,1,0);
            inner_color.setVec(1,0,1,line_alpha);
            mManipPart = LL_XZ_PLANE;
            break;
        case LL_Z_ARROW:
            normal.setVec(0,0,1);
            inner_color.setVec(1,1,0,line_alpha);
            mManipPart = LL_XY_PLANE;
            break;
        default:
            break;
        }

        highlightIntersection(normal, selection_center, grid_rotation, inner_color);
        mManipPart = temp_manip;
        getManipAxis(first_object, mManipPart, translate_axis);

        LLVector3 at_axis_abs;
        if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD)
        {
            at_axis_abs = LLVector3::x_axis * ~grid_rotation;
        }
        else
        {
            at_axis_abs = saved_selection_center - LLViewerCamera::getInstance()->getOrigin();
            at_axis_abs.normVec();

            at_axis_abs = at_axis_abs * ~grid_rotation;
        }
        at_axis_abs.abs();

        if (at_axis_abs.mV[VX] > at_axis_abs.mV[VY] && at_axis_abs.mV[VX] > at_axis_abs.mV[VZ])
        {
            if (mManipPart == LL_Y_ARROW)
            {
                mSnapOffsetAxis = LLVector3::z_axis;
            }
            else if (mManipPart == LL_Z_ARROW)
            {
                mSnapOffsetAxis = LLVector3::y_axis;
            }
            else if (at_axis_abs.mV[VY] > at_axis_abs.mV[VZ])
            {
                mSnapOffsetAxis = LLVector3::z_axis;
            }
            else
            {
                mSnapOffsetAxis = LLVector3::y_axis;
            }
        }
        else if (at_axis_abs.mV[VY] > at_axis_abs.mV[VZ])
        {
            if (mManipPart == LL_X_ARROW)
            {
                mSnapOffsetAxis = LLVector3::z_axis;
            }
            else if (mManipPart == LL_Z_ARROW)
            {
                mSnapOffsetAxis = LLVector3::x_axis;
            }
            else if (at_axis_abs.mV[VX] > at_axis_abs.mV[VZ])
            {
                mSnapOffsetAxis = LLVector3::z_axis;
            }
            else
            {
                mSnapOffsetAxis = LLVector3::x_axis;
            }
        }
        else
        {
            if (mManipPart == LL_X_ARROW)
            {
                mSnapOffsetAxis = LLVector3::y_axis;
            }
            else if (mManipPart == LL_Y_ARROW)
            {
                mSnapOffsetAxis = LLVector3::x_axis;
            }
            else if (at_axis_abs.mV[VX] > at_axis_abs.mV[VY])
            {
                mSnapOffsetAxis = LLVector3::y_axis;
            }
            else
            {
                mSnapOffsetAxis = LLVector3::x_axis;
            }
        }

        mSnapOffsetAxis = mSnapOffsetAxis * grid_rotation;

        F32 guide_size_meters;

        if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD)
        {
            guide_size_meters = 1.f / gAgentCamera.mHUDCurZoom;
            mSnapOffsetMeters = mArrowLengthMeters * 1.5f;
        }
        else
        {
            LLVector3 cam_to_selection = getPivotPoint() - LLViewerCamera::getInstance()->getOrigin();
            F32 current_range = cam_to_selection.normVec();
            guide_size_meters = SNAP_GUIDE_SCREEN_SIZE * gViewerWindow->getWorldViewHeightRaw() * current_range / LLViewerCamera::getInstance()->getPixelMeterRatio();

            F32 fraction_of_fov = mAxisArrowLength / (F32) LLViewerCamera::getInstance()->getViewHeightInPixels();
            F32 apparent_angle = fraction_of_fov * LLViewerCamera::getInstance()->getView();  // radians
            F32 offset_at_camera = tan(apparent_angle) * 1.5f;
            F32 range = dist_vec(gAgent.getPosAgentFromGlobal(first_node->mSavedPositionGlobal), LLViewerCamera::getInstance()->getOrigin());
            mSnapOffsetMeters = range * offset_at_camera;
        }

        LLVector3 tick_start;
        LLVector3 tick_end;

        // how far away from grid origin is the selection along the axis of translation?
        F32 dist_grid_axis = (selection_center - mGridOrigin) * translate_axis;
        // find distance to nearest smallest grid unit
        F32 offset_nearest_grid_unit = fmodf(dist_grid_axis, smallest_grid_unit_scale);
        // how many smallest grid units are we away from largest grid scale?
        S32 sub_div_offset = ll_round(fmodf(dist_grid_axis - offset_nearest_grid_unit, getMinGridScale() / sGridMinSubdivisionLevel) / smallest_grid_unit_scale);
        S32 num_ticks_per_side = llmax(1, llfloor(0.5f * guide_size_meters / smallest_grid_unit_scale));

        LLGLDepthTest gls_depth(GL_FALSE);

        for (S32 pass = 0; pass < 3; pass++)
        {
            LLColor4 line_color = setupSnapGuideRenderPass(pass);
            LLGLDepthTest gls_depth(pass != 1);

            gGL.begin(LLRender::LINES);
            {
                LLVector3 line_start = selection_center + (mSnapOffsetMeters * mSnapOffsetAxis) + (translate_axis * (guide_size_meters * 0.5f + offset_nearest_grid_unit));
                LLVector3 line_end = selection_center + (mSnapOffsetMeters * mSnapOffsetAxis) - (translate_axis * (guide_size_meters * 0.5f + offset_nearest_grid_unit));
                LLVector3 line_mid = (line_start + line_end) * 0.5f;

                gGL.color4f(line_color.mV[VRED], line_color.mV[VGREEN], line_color.mV[VBLUE], line_color.mV[VALPHA] * 0.2f);
                gGL.vertex3fv(line_start.mV);
                gGL.color4f(line_color.mV[VRED], line_color.mV[VGREEN], line_color.mV[VBLUE], line_color.mV[VALPHA]);
                gGL.vertex3fv(line_mid.mV);
                gGL.vertex3fv(line_mid.mV);
                gGL.color4f(line_color.mV[VRED], line_color.mV[VGREEN], line_color.mV[VBLUE], line_color.mV[VALPHA] * 0.2f);
                gGL.vertex3fv(line_end.mV);

                line_start.setVec(selection_center + (mSnapOffsetAxis * -mSnapOffsetMeters) + (translate_axis * guide_size_meters * 0.5f));
                line_end.setVec(selection_center + (mSnapOffsetAxis * -mSnapOffsetMeters) - (translate_axis * guide_size_meters * 0.5f));
                line_mid = (line_start + line_end) * 0.5f;

                gGL.color4f(line_color.mV[VRED], line_color.mV[VGREEN], line_color.mV[VBLUE], line_color.mV[VALPHA] * 0.2f);
                gGL.vertex3fv(line_start.mV);
                gGL.color4f(line_color.mV[VRED], line_color.mV[VGREEN], line_color.mV[VBLUE], line_color.mV[VALPHA]);
                gGL.vertex3fv(line_mid.mV);
                gGL.vertex3fv(line_mid.mV);
                gGL.color4f(line_color.mV[VRED], line_color.mV[VGREEN], line_color.mV[VBLUE], line_color.mV[VALPHA] * 0.2f);
                gGL.vertex3fv(line_end.mV);

                for (S32 i = -num_ticks_per_side; i <= num_ticks_per_side; i++)
                {
                    tick_start = selection_center + (translate_axis * (smallest_grid_unit_scale * (F32)i - offset_nearest_grid_unit));

                    //No need check this condition to prevent tick position scaling (FIX MAINT-5207/5208)
                    //F32 cur_subdivisions = getSubdivisionLevel(tick_start, translate_axis, getMinGridScale());
                    /*if (fmodf((F32)(i + sub_div_offset), (max_subdivisions / cur_subdivisions)) != 0.f)
                    {
                        continue;
                    }*/

                    // add in off-axis offset
                    tick_start += (mSnapOffsetAxis * mSnapOffsetMeters);

                    F32 tick_scale = 1.f;
                    for (F32 division_level = max_subdivisions; division_level >= sGridMinSubdivisionLevel; division_level /= 2.f)
                    {
                        if (fmodf((F32)(i + sub_div_offset), division_level) == 0.f)
                        {
                            break;
                        }
                        tick_scale *= 0.7f;
                    }

//                  S32 num_ticks_to_fade = is_sub_tick ? num_ticks_per_side / 2 : num_ticks_per_side;
//                  F32 alpha = line_alpha * (1.f - (0.8f *  ((F32)llabs(i) / (F32)num_ticks_to_fade)));

                    tick_end = tick_start + (mSnapOffsetAxis * mSnapOffsetMeters * tick_scale);

                    gGL.color4f(line_color.mV[VRED], line_color.mV[VGREEN], line_color.mV[VBLUE], line_color.mV[VALPHA]);
                    gGL.vertex3fv(tick_start.mV);
                    gGL.vertex3fv(tick_end.mV);

                    tick_start = selection_center + (mSnapOffsetAxis * -mSnapOffsetMeters) +
                        (translate_axis * (getMinGridScale() / (F32)(max_subdivisions) * (F32)i - offset_nearest_grid_unit));
                    tick_end = tick_start - (mSnapOffsetAxis * mSnapOffsetMeters * tick_scale);

                    gGL.vertex3fv(tick_start.mV);
                    gGL.vertex3fv(tick_end.mV);
                }
            }
            gGL.end();

            if (mInSnapRegime)
            {
                LLVector3 line_start = selection_center - mSnapOffsetAxis * mSnapOffsetMeters;
                LLVector3 line_end = selection_center + mSnapOffsetAxis * mSnapOffsetMeters;

                gGL.begin(LLRender::LINES);
                {
                    gGL.color4f(line_color.mV[VRED], line_color.mV[VGREEN], line_color.mV[VBLUE], line_color.mV[VALPHA]);

                    gGL.vertex3fv(line_start.mV);
                    gGL.vertex3fv(line_end.mV);
                }
                gGL.end();

                // draw snap guide arrow
                gGL.begin(LLRender::TRIANGLES);
                {
                    gGL.color4f(line_color.mV[VRED], line_color.mV[VGREEN], line_color.mV[VBLUE], line_color.mV[VALPHA]);

                    LLVector3 arrow_dir;
                    LLVector3 arrow_span = translate_axis;

                    arrow_dir = -mSnapOffsetAxis;
                    gGL.vertex3fv((line_start + arrow_dir * mConeSize * SNAP_ARROW_SCALE).mV);
                    gGL.vertex3fv((line_start + arrow_span * mConeSize * SNAP_ARROW_SCALE).mV);
                    gGL.vertex3fv((line_start - arrow_span * mConeSize * SNAP_ARROW_SCALE).mV);

                    arrow_dir = mSnapOffsetAxis;
                    gGL.vertex3fv((line_end + arrow_dir * mConeSize * SNAP_ARROW_SCALE).mV);
                    gGL.vertex3fv((line_end + arrow_span * mConeSize * SNAP_ARROW_SCALE).mV);
                    gGL.vertex3fv((line_end - arrow_span * mConeSize * SNAP_ARROW_SCALE).mV);
                }
                gGL.end();
            }
        }

        sub_div_offset = ll_round(fmod(dist_grid_axis - offset_nearest_grid_unit, getMinGridScale() * 32.f) / smallest_grid_unit_scale);

        LLVector2 screen_translate_axis(llabs(translate_axis * LLViewerCamera::getInstance()->getLeftAxis()), llabs(translate_axis * LLViewerCamera::getInstance()->getUpAxis()));
        screen_translate_axis.normVec();

        S32 tick_label_spacing = ll_round(screen_translate_axis * sTickLabelSpacing);

        // render tickmark values
        for (S32 i = -num_ticks_per_side; i <= num_ticks_per_side; i++)
        {
            LLVector3 tick_pos = selection_center + (translate_axis * ((smallest_grid_unit_scale * (F32)i) - offset_nearest_grid_unit));
            F32 alpha = line_alpha * (1.f - (0.5f *  ((F32)llabs(i) / (F32)num_ticks_per_side)));

            F32 tick_scale = 1.f;
            for (F32 division_level = max_subdivisions; division_level >= sGridMinSubdivisionLevel; division_level /= 2.f)
            {
                if (fmodf((F32)(i + sub_div_offset), division_level) == 0.f)
                {
                    break;
                }
                tick_scale *= 0.7f;
            }

            if (fmodf((F32)(i + sub_div_offset), (max_subdivisions / getSubdivisionLevel(tick_pos, translate_axis, getMinGridScale(), tick_label_spacing))) == 0.f)
            {
                F32 snap_offset_meters;

                if (mSnapOffsetAxis * LLViewerCamera::getInstance()->getUpAxis() > 0.f)
                {
                    snap_offset_meters = mSnapOffsetMeters;
                }
                else
                {
                    snap_offset_meters = -mSnapOffsetMeters;
                }
                LLVector3 text_origin = selection_center +
                        (translate_axis * ((smallest_grid_unit_scale * (F32)i) - offset_nearest_grid_unit)) +
                            (mSnapOffsetAxis * snap_offset_meters * (1.f + tick_scale));

                LLVector3 tick_offset = (tick_pos - mGridOrigin) * ~mGridRotation;
                F32 offset_val = 0.5f * tick_offset.mV[ARROW_TO_AXIS[mManipPart]] / getMinGridScale();
                EGridMode grid_mode = LLSelectMgr::getInstance()->getGridMode();
                F32 text_highlight = 0.8f;
                if(i - ll_round(offset_nearest_grid_unit / smallest_grid_unit_scale) == 0 && mInSnapRegime)
                {
                    text_highlight = 1.f;
                }

                if (grid_mode == GRID_MODE_WORLD)
                {
                    // rescale units to meters from multiple of grid scale
                    offset_val *= 2.f * grid_scale[ARROW_TO_AXIS[mManipPart]];
                    renderTickValue(text_origin, offset_val, std::string("m"), LLColor4(text_highlight, text_highlight, text_highlight, alpha));
                }
                else
                {
                    renderTickValue(text_origin, offset_val, std::string("x"), LLColor4(text_highlight, text_highlight, text_highlight, alpha));
                }
            }
        }
        if (mObjectSelection->getSelectType() != SELECT_TYPE_HUD)
        {
            // render helpful text
            if (mHelpTextTimer.getElapsedTimeF32() < sHelpTextVisibleTime + sHelpTextFadeTime && sNumTimesHelpTextShown < sMaxTimesShowHelpText)
            {
                F32 snap_offset_meters_up;
                if (mSnapOffsetAxis * LLViewerCamera::getInstance()->getUpAxis() > 0.f)
                {
                    snap_offset_meters_up = mSnapOffsetMeters;
                }
                else
                {
                    snap_offset_meters_up = -mSnapOffsetMeters;
                }

                LLVector3 selection_center_start = getSavedPivotPoint();//LLSelectMgr::getInstance()->getSavedBBoxOfSelection().getCenterAgent();

                LLVector3 help_text_pos = selection_center_start + (snap_offset_meters_up * 3.f * mSnapOffsetAxis);
                const LLFontGL* big_fontp = LLFontGL::getFontSansSerif();

                std::string help_text = LLTrans::getString("manip_hint1");
                LLColor4 help_text_color = LLColor4::white;
                help_text_color.mV[VALPHA] = clamp_rescale(mHelpTextTimer.getElapsedTimeF32(), sHelpTextVisibleTime, sHelpTextVisibleTime + sHelpTextFadeTime, line_alpha, 0.f);
                hud_render_utf8text(help_text, help_text_pos, nullptr, *big_fontp, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, -0.5f * big_fontp->getWidthF32(help_text), 3.f, help_text_color, false);
                help_text = LLTrans::getString("manip_hint2");
                help_text_pos -= LLViewerCamera::getInstance()->getUpAxis() * mSnapOffsetMeters * 0.2f;
                hud_render_utf8text(help_text, help_text_pos, nullptr, *big_fontp, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, -0.5f * big_fontp->getWidthF32(help_text), 3.f, help_text_color, false);
            }
        }
    }
    else
    {
        // render gridlines for planar snapping

        F32 u = 0, v = 0;
        LLColor4 inner_color;
        LLVector3 normal;
        LLVector3 grid_center = selection_center - grid_origin;
        F32 usc = 1;
        F32 vsc = 1;

        grid_center *= ~grid_rotation;

        switch (mManipPart)
        {
        case LL_YZ_PLANE:
            u = grid_center.mV[VY];
            v = grid_center.mV[VZ];
            usc = grid_scale.mV[VY];
            vsc = grid_scale.mV[VZ];
            inner_color.setVec(0,1,1,line_alpha);
            normal.setVec(1,0,0);
            break;
        case LL_XZ_PLANE:
            u = grid_center.mV[VX];
            v = grid_center.mV[VZ];
            usc = grid_scale.mV[VX];
            vsc = grid_scale.mV[VZ];
            inner_color.setVec(1,0,1,line_alpha);
            normal.setVec(0,1,0);
            break;
        case LL_XY_PLANE:
            u = grid_center.mV[VX];
            v = grid_center.mV[VY];
            usc = grid_scale.mV[VX];
            vsc = grid_scale.mV[VY];
            inner_color.setVec(1,1,0,line_alpha);
            normal.setVec(0,0,1);
            break;
        default:
            break;
        }

        gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
        highlightIntersection(normal, selection_center, grid_rotation, inner_color);

        gGL.pushMatrix();

        F32 x,y,z,angle_radians;
        grid_rotation.getAngleAxis(&angle_radians, &x, &y, &z);
        gGL.translatef(selection_center.mV[VX], selection_center.mV[VY], selection_center.mV[VZ]);
        gGL.rotatef(angle_radians * RAD_TO_DEG, x, y, z);

        F32 sz = mGridSizeMeters;
        F32 tiles = sz;

        gGL.matrixMode(LLRender::MM_TEXTURE);
        gGL.pushMatrix();
        usc = 1.0f/usc;
        vsc = 1.0f/vsc;

        while (usc > vsc*4.0f)
        {
            usc *= 0.5f;
        }
        while (vsc > usc * 4.0f)
        {
            vsc *= 0.5f;
        }

        gGL.scalef(usc, vsc, 1.0f);
        gGL.translatef(u, v, 0);

        float a = line_alpha;

        {
            //draw grid behind objects
            LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE);

            {
                //LLGLDisable stencil(GL_STENCIL_TEST);
                {
                    LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE, GL_GREATER);
                    gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_TEXTURE, getGridTexName());
                    gGL.flush();
                    gGL.blendFunc(LLRender::BF_ZERO, LLRender::BF_ONE_MINUS_SOURCE_ALPHA);
                    renderGrid(u,v,tiles,0.9f, 0.9f, 0.9f,a*0.15f);
                    gGL.flush();
                    gGL.setSceneBlendType(LLRender::BT_ALPHA);
                }

                {
                    //draw black overlay
                    gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
                    renderGrid(u,v,tiles,0.0f, 0.0f, 0.0f,a*0.16f);

                    //draw grid top
                    gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_TEXTURE, getGridTexName());
                    renderGrid(u,v,tiles,1,1,1,a);

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

                {
                    LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE);
                    renderGuidelines();
                }

                {
                    LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE, GL_GREATER);
                    gGL.flush();

                    switch (mManipPart)
                    {
                      case LL_YZ_PLANE:
                        renderGuidelines(false, true, true);
                        break;
                      case LL_XZ_PLANE:
                        renderGuidelines(true, false, true);
                        break;
                      case LL_XY_PLANE:
                        renderGuidelines(true, true, false);
                        break;
                      default:
                        break;
                    }
                    gGL.flush();
                }
            }
        }
    }
}

void LLManipTranslate::renderGrid(F32 x, F32 y, F32 size, F32 r, F32 g, F32 b, F32 a)
{
    F32 d = size*0.5f;

    for (F32 xx = -size-d; xx < size+d; xx += d)
    {
        gGL.begin(LLRender::TRIANGLE_STRIP);
        for (F32 yy = -size-d; yy < size+d; yy += d)
        {
            float dx, dy, da;

            dx = xx; dy = yy;
            da = sqrtf(llmax(0.0f, 1.0f-sqrtf(dx*dx+dy*dy)/size))*a;
            gGL.texCoord2f(dx, dy);
            renderGridVert(dx,dy,r,g,b,da);

            dx = xx+d; dy = yy;
            da = sqrtf(llmax(0.0f, 1.0f-sqrtf(dx*dx+dy*dy)/size))*a;
            gGL.texCoord2f(dx, dy);
            renderGridVert(dx,dy,r,g,b,da);

            dx = xx; dy = yy+d;
            da = sqrtf(llmax(0.0f, 1.0f-sqrtf(dx*dx+dy*dy)/size))*a;
            gGL.texCoord2f(dx, dy);
            renderGridVert(dx,dy,r,g,b,da);

            dx = xx+d; dy = yy+d;
            da = sqrtf(llmax(0.0f, 1.0f-sqrtf(dx*dx+dy*dy)/size))*a;
            gGL.texCoord2f(dx, dy);
            renderGridVert(dx,dy,r,g,b,da);
        }
        gGL.end();
    }


}

void LLManipTranslate::highlightIntersection(LLVector3 normal,
                                             LLVector3 selection_center,
                                             LLQuaternion grid_rotation,
                                             LLColor4 inner_color)
{
#if 0 // DEPRECATED
    if (!gSavedSettings.getBOOL("GridCrossSections"))
    {
        return;
    }


    LLGLSLShader* shader = LLGLSLShader::sCurBoundShaderPtr;


    static const U32 types[] = { LLRenderPass::PASS_SIMPLE, LLRenderPass::PASS_ALPHA, LLRenderPass::PASS_FULLBRIGHT, LLRenderPass::PASS_SHINY };
    static const U32 num_types = LL_ARRAY_SIZE(types);

    GLuint stencil_mask = 0xFFFFFFFF;
    //stencil in volumes

    gGL.flush();

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

    {
        //glStencilMask(stencil_mask); //deprecated
        //glClearStencil(1);
        //glClear(GL_STENCIL_BUFFER_BIT);
        LLGLEnable cull_face(GL_CULL_FACE);
        //LLGLEnable stencil(GL_STENCIL_TEST);
        LLGLDepthTest depth (GL_TRUE, GL_FALSE, GL_ALWAYS);
        //glStencilFunc(GL_ALWAYS, 0, stencil_mask);
        gGL.setColorMask(false, false);
        gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);

        gGL.diffuseColor4f(1,1,1,1);

        //setup clip plane
        normal = normal * grid_rotation;
        if (normal * (LLViewerCamera::getInstance()->getOrigin()-selection_center) < 0)
        {
            normal = -normal;
        }
        F32 d = -(selection_center * normal);
        glm::vec4 plane(normal.mV[0], normal.mV[1], normal.mV[2], d );

        plane = glm::inverse(gGL.getModelviewMatrix()) * plane;

        static LLStaticHashedString sClipPlane("clip_plane");
        gClipProgram.uniform4fv(sClipPlane, 1, plane.v);

        bool particles = gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_PARTICLES);
        bool clouds = gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_CLOUDS);

        if (particles)
        {
            LLPipeline::toggleRenderType(LLPipeline::RENDER_TYPE_PARTICLES);
        }
        if (clouds)
        {
            LLPipeline::toggleRenderType(LLPipeline::RENDER_TYPE_CLOUDS);
        }

        //stencil in volumes
        //glStencilOp(GL_INCR, GL_INCR, GL_INCR);
        glCullFace(GL_FRONT);
        for (U32 i = 0; i < num_types; i++)
        {
            gPipeline.renderObjects(types[i], LLVertexBuffer::MAP_VERTEX, false);
        }

        //glStencilOp(GL_DECR, GL_DECR, GL_DECR);
        glCullFace(GL_BACK);
        for (U32 i = 0; i < num_types; i++)
        {
            gPipeline.renderObjects(types[i], LLVertexBuffer::MAP_VERTEX, false);
        }

        if (particles)
        {
            LLPipeline::toggleRenderType(LLPipeline::RENDER_TYPE_PARTICLES);
        }
        if (clouds)
        {
            LLPipeline::toggleRenderType(LLPipeline::RENDER_TYPE_CLOUDS);
        }

        gGL.setColorMask(true, false);
    }
    gGL.color4f(1,1,1,1);

    gGL.pushMatrix();

    F32 x,y,z,angle_radians;
    grid_rotation.getAngleAxis(&angle_radians, &x, &y, &z);
    gGL.translatef(selection_center.mV[VX], selection_center.mV[VY], selection_center.mV[VZ]);
    gGL.rotatef(angle_radians * RAD_TO_DEG, x, y, z);

    F32 sz = mGridSizeMeters;
    F32 tiles = sz;

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

    //draw volume/plane intersections
    {
        gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
        LLGLDepthTest depth(GL_FALSE);
        //LLGLEnable stencil(GL_STENCIL_TEST);
        glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
        glStencilFunc(GL_EQUAL, 0, stencil_mask);
        renderGrid(0,0,tiles,inner_color.mV[0], inner_color.mV[1], inner_color.mV[2], 0.25f);
    }

    glStencilFunc(GL_ALWAYS, 255, 0xFFFFFFFF);
    glStencilMask(0xFFFFFFFF);
    glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);

    gGL.popMatrix();
#endif
}

void LLManipTranslate::renderText()
{
    if (mObjectSelection->getRootObjectCount() && !mObjectSelection->isAttachment())
    {
        LLVector3 pos = getPivotPoint();
        renderXYZ(pos);
    }
    else
    {
        const bool children_ok = true;
        LLViewerObject* objectp = mObjectSelection->getFirstRootObject(children_ok);
        if (objectp)
        {
            renderXYZ(objectp->getPositionEdit());
        }
    }
}

void LLManipTranslate::renderTranslationHandles()
{
    LLVector3 grid_origin;
    LLVector3 grid_scale;
    LLQuaternion grid_rotation;
    LLGLDepthTest gls_depth(GL_FALSE);

    LLSelectMgr::getInstance()->getGrid(grid_origin, grid_rotation, grid_scale);
    LLVector3 at_axis;
    if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD)
    {
        at_axis = LLVector3::x_axis * ~grid_rotation;
    }
    else
    {
        at_axis = LLViewerCamera::getInstance()->getAtAxis() * ~grid_rotation;
    }

    if (at_axis.mV[VX] > 0.f)
    {
        mPlaneManipPositions.mV[VX] = 1.f;
    }
    else
    {
        mPlaneManipPositions.mV[VX] = -1.f;
    }

    if (at_axis.mV[VY] > 0.f)
    {
        mPlaneManipPositions.mV[VY] = 1.f;
    }
    else
    {
        mPlaneManipPositions.mV[VY] = -1.f;
    }

    if (at_axis.mV[VZ] > 0.f)
    {
        mPlaneManipPositions.mV[VZ] = 1.f;
    }
    else
    {
        mPlaneManipPositions.mV[VZ] = -1.f;
    }

    LLViewerObject *first_object = mObjectSelection->getFirstMoveableObject(true);
    if (!first_object) return;

    LLVector3 selection_center = getPivotPoint();

    // Drag handles
    if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD)
    {
        mArrowLengthMeters = mAxisArrowLength / gViewerWindow->getWorldViewHeightRaw();
        mArrowLengthMeters /= gAgentCamera.mHUDCurZoom;
    }
    else
    {
        LLVector3 camera_pos_agent = gAgentCamera.getCameraPositionAgent();
        F32 range = dist_vec(camera_pos_agent, selection_center);
        F32 range_from_agent = dist_vec(gAgent.getPositionAgent(), selection_center);

        // Don't draw handles if you're too far away
        if (gSavedSettings.getBOOL("LimitSelectDistance"))
        {
            if (range_from_agent > gSavedSettings.getF32("MaxSelectDistance"))
            {
                return;
            }
        }

        if (range > 0.001f)
        {
            // range != zero
            F32 fraction_of_fov = mAxisArrowLength / (F32) LLViewerCamera::getInstance()->getViewHeightInPixels();
            F32 apparent_angle = fraction_of_fov * LLViewerCamera::getInstance()->getView();  // radians
            mArrowLengthMeters = range * tan(apparent_angle);
        }
        else
        {
            // range == zero
            mArrowLengthMeters = 1.0f;
        }
    }
    //Assume that UI scale factor is equivalent for X and Y axis
    F32 ui_scale_factor = LLUI::getScaleFactor().mV[VX];
    mArrowLengthMeters *= ui_scale_factor;

    mPlaneManipOffsetMeters = mArrowLengthMeters * 1.8f;
    mGridSizeMeters = gSavedSettings.getF32("GridDrawSize");
    mConeSize = mArrowLengthMeters / 4.f;

    gGL.matrixMode(LLRender::MM_MODELVIEW);
    gGL.pushMatrix();
    {
        gGL.translatef(selection_center.mV[VX], selection_center.mV[VY], selection_center.mV[VZ]);

        F32 angle_radians, x, y, z;
        grid_rotation.getAngleAxis(&angle_radians, &x, &y, &z);

        gGL.rotatef(angle_radians * RAD_TO_DEG, x, y, z);

        LLQuaternion invRotation = grid_rotation;
        invRotation.conjQuat();

        LLVector3 relative_camera_dir;

        if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD)
        {
            relative_camera_dir = LLVector3::x_axis * invRotation;
        }
        else
        {
            relative_camera_dir = (selection_center - LLViewerCamera::getInstance()->getOrigin()) * invRotation;
        }
        relative_camera_dir.normVec();

        {
            gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
            LLGLDisable cull_face(GL_CULL_FACE);

            LLColor4 color1;
            LLColor4 color2;

            // update manipulator sizes
            for (S32 index = 0; index < 3; index++)
            {
                if (index == mManipPart - LL_X_ARROW || index == mHighlightedPart - LL_X_ARROW)
                {
                    mArrowScales.mV[index] = lerp(mArrowScales.mV[index], SELECTED_ARROW_SCALE, LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE ));
                    mPlaneScales.mV[index] = lerp(mPlaneScales.mV[index], 1.f, LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE ));
                }
                else if (index == mManipPart - LL_YZ_PLANE || index == mHighlightedPart - LL_YZ_PLANE)
                {
                    mArrowScales.mV[index] = lerp(mArrowScales.mV[index], 1.f, LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE ));
                    mPlaneScales.mV[index] = lerp(mPlaneScales.mV[index], SELECTED_ARROW_SCALE, LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE ));
                }
                else
                {
                    mArrowScales.mV[index] = lerp(mArrowScales.mV[index], 1.f, LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE ));
                    mPlaneScales.mV[index] = lerp(mPlaneScales.mV[index], 1.f, LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE ));
                }
            }

            if ((mManipPart == LL_NO_PART || mManipPart == LL_YZ_PLANE) && llabs(relative_camera_dir.mV[VX]) > MIN_PLANE_MANIP_DOT_PRODUCT)
            {
                // render YZ plane manipulator
                gGL.pushMatrix();
                gGL.scalef(mPlaneManipPositions.mV[VX], mPlaneManipPositions.mV[VY], mPlaneManipPositions.mV[VZ]);
                gGL.translatef(0.f, mPlaneManipOffsetMeters, mPlaneManipOffsetMeters);
                gGL.scalef(mPlaneScales.mV[VX], mPlaneScales.mV[VX], mPlaneScales.mV[VX]);
                if (mHighlightedPart == LL_YZ_PLANE)
                {
                    color1.setVec(0.f, 1.f, 0.f, 1.f);
                    color2.setVec(0.f, 0.f, 1.f, 1.f);
                }
                else
                {
                    color1.setVec(0.f, 1.f, 0.f, 0.6f);
                    color2.setVec(0.f, 0.f, 1.f, 0.6f);
                }
                gGL.begin(LLRender::TRIANGLES);
                {
                    gGL.color4fv(color1.mV);
                    gGL.vertex3f(0.f, mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f), mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f));
                    gGL.vertex3f(0.f, mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f), mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.75f));
                    gGL.vertex3f(0.f, mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f), mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f));

                    gGL.color4fv(color2.mV);
                    gGL.vertex3f(0.f, mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f), mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f));
                    gGL.vertex3f(0.f, mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.75f), mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f));
                    gGL.vertex3f(0.f, mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f), mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f));
                }
                gGL.end();

                LLUI::setLineWidth(3.0f);
                gGL.begin(LLRender::LINES);
                {
                    gGL.color4f(0.f, 0.f, 0.f, 0.3f);
                    gGL.vertex3f(0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f,  mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f);
                    gGL.vertex3f(0.f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE  * 0.25f,  mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f);
                    gGL.vertex3f(0.f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE  * 0.25f,  mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f);
                    gGL.vertex3f(0.f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE  * 0.1f,   mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.1f);
                    gGL.vertex3f(0.f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE  * 0.25f,  mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f);
                    gGL.vertex3f(0.f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE  * 0.1f,   mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.4f);

                    gGL.vertex3f(0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f);
                    gGL.vertex3f(0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.25f);
                    gGL.vertex3f(0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.25f);
                    gGL.vertex3f(0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.1f,  mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.1f);
                    gGL.vertex3f(0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.25f);
                    gGL.vertex3f(0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.4f,  mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.1f);
                }
                gGL.end();
                LLUI::setLineWidth(1.0f);
                gGL.popMatrix();
            }

            if ((mManipPart == LL_NO_PART || mManipPart == LL_XZ_PLANE) && llabs(relative_camera_dir.mV[VY]) > MIN_PLANE_MANIP_DOT_PRODUCT)
            {
                // render XZ plane manipulator
                gGL.pushMatrix();
                gGL.scalef(mPlaneManipPositions.mV[VX], mPlaneManipPositions.mV[VY], mPlaneManipPositions.mV[VZ]);
                gGL.translatef(mPlaneManipOffsetMeters, 0.f, mPlaneManipOffsetMeters);
                gGL.scalef(mPlaneScales.mV[VY], mPlaneScales.mV[VY], mPlaneScales.mV[VY]);
                if (mHighlightedPart == LL_XZ_PLANE)
                {
                    color1.setVec(0.f, 0.f, 1.f, 1.f);
                    color2.setVec(1.f, 0.f, 0.f, 1.f);
                }
                else
                {
                    color1.setVec(0.f, 0.f, 1.f, 0.6f);
                    color2.setVec(1.f, 0.f, 0.f, 0.6f);
                }

                gGL.begin(LLRender::TRIANGLES);
                {
                    gGL.color4fv(color1.mV);
                    gGL.vertex3f(mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f), 0.f, mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f));
                    gGL.vertex3f(mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.75f), 0.f, mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f));
                    gGL.vertex3f(mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f), 0.f, mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f));

                    gGL.color4fv(color2.mV);
                    gGL.vertex3f(mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f), 0.f, mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f));
                    gGL.vertex3f(mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f),   0.f, mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.75f));
                    gGL.vertex3f(mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f),   0.f, mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f));
                }
                gGL.end();

                LLUI::setLineWidth(3.0f);
                gGL.begin(LLRender::LINES);
                {
                    gGL.color4f(0.f, 0.f, 0.f, 0.3f);
                    gGL.vertex3f(mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f,  0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f);
                    gGL.vertex3f(mPlaneManipOffsetMeters * PLANE_TICK_SIZE  * 0.25f,  0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f);
                    gGL.vertex3f(mPlaneManipOffsetMeters * PLANE_TICK_SIZE  * 0.25f,  0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f);
                    gGL.vertex3f(mPlaneManipOffsetMeters * PLANE_TICK_SIZE  * 0.1f,   0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.1f);
                    gGL.vertex3f(mPlaneManipOffsetMeters * PLANE_TICK_SIZE  * 0.25f,  0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f);
                    gGL.vertex3f(mPlaneManipOffsetMeters * PLANE_TICK_SIZE  * 0.1f,   0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.4f);

                    gGL.vertex3f(mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f,  0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f);
                    gGL.vertex3f(mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f,  0.f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.25f);
                    gGL.vertex3f(mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f,  0.f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.25f);
                    gGL.vertex3f(mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.1f,   0.f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.1f);
                    gGL.vertex3f(mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f,  0.f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.25f);
                    gGL.vertex3f(mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.4f,   0.f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.1f);
                }
                gGL.end();
                LLUI::setLineWidth(1.0f);

                gGL.popMatrix();
            }

            if ((mManipPart == LL_NO_PART || mManipPart == LL_XY_PLANE) && llabs(relative_camera_dir.mV[VZ]) > MIN_PLANE_MANIP_DOT_PRODUCT)
            {
                // render XY plane manipulator
                gGL.pushMatrix();
                gGL.scalef(mPlaneManipPositions.mV[VX], mPlaneManipPositions.mV[VY], mPlaneManipPositions.mV[VZ]);

/*                            Y
                              ^
                              v1
                              |  \
                              |<- v0
                              |  /| \
                              v2__v__v3 > X
*/
                    LLVector3 v0,v1,v2,v3;
#if 0
                    // This should theoretically work but looks off; could be tuned later -SJB
                    gGL.translatef(-mPlaneManipOffsetMeters, -mPlaneManipOffsetMeters, 0.f);
                    v0 = LLVector3(mPlaneManipOffsetMeters * ( PLANE_TICK_SIZE * 0.25f), mPlaneManipOffsetMeters * ( PLANE_TICK_SIZE * 0.25f), 0.f);
                    v1 = LLVector3(mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f), mPlaneManipOffsetMeters * ( PLANE_TICK_SIZE * 0.75f), 0.f);
                    v2 = LLVector3(mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f), mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f), 0.f);
                    v3 = LLVector3(mPlaneManipOffsetMeters * ( PLANE_TICK_SIZE * 0.75f), mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f), 0.f);
#else
                    gGL.translatef(mPlaneManipOffsetMeters, mPlaneManipOffsetMeters, 0.f);
                    v0 = LLVector3(mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f), mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f), 0.f);
                    v1 = LLVector3(mPlaneManipOffsetMeters * ( PLANE_TICK_SIZE * 0.25f), mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.75f), 0.f);
                    v2 = LLVector3(mPlaneManipOffsetMeters * ( PLANE_TICK_SIZE * 0.25f), mPlaneManipOffsetMeters * ( PLANE_TICK_SIZE * 0.25f), 0.f);
                    v3 = LLVector3(mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.75f), mPlaneManipOffsetMeters * ( PLANE_TICK_SIZE * 0.25f), 0.f);
#endif
                    gGL.scalef(mPlaneScales.mV[VZ], mPlaneScales.mV[VZ], mPlaneScales.mV[VZ]);
                    if (mHighlightedPart == LL_XY_PLANE)
                    {
                        color1.setVec(1.f, 0.f, 0.f, 1.f);
                        color2.setVec(0.f, 1.f, 0.f, 1.f);
                    }
                    else
                    {
                        color1.setVec(0.8f, 0.f, 0.f, 0.6f);
                        color2.setVec(0.f, 0.8f, 0.f, 0.6f);
                    }

                    gGL.begin(LLRender::TRIANGLES);
                    {
                        gGL.color4fv(color1.mV);
                        gGL.vertex3fv(v0.mV);
                        gGL.vertex3fv(v1.mV);
                        gGL.vertex3fv(v2.mV);

                        gGL.color4fv(color2.mV);
                        gGL.vertex3fv(v2.mV);
                        gGL.vertex3fv(v3.mV);
                        gGL.vertex3fv(v0.mV);
                    }
                    gGL.end();

                    LLUI::setLineWidth(3.0f);
                    gGL.begin(LLRender::LINES);
                    {
                        gGL.color4f(0.f, 0.f, 0.f, 0.3f);
                        LLVector3 v12 = (v1 + v2) * .5f;
                        gGL.vertex3fv(v0.mV);
                        gGL.vertex3fv(v12.mV);
                        gGL.vertex3fv(v12.mV);
                        gGL.vertex3fv((v12 + (v0-v12)*.3f + (v2-v12)*.3f).mV);
                        gGL.vertex3fv(v12.mV);
                        gGL.vertex3fv((v12 + (v0-v12)*.3f + (v1-v12)*.3f).mV);

                        LLVector3 v23 = (v2 + v3) * .5f;
                        gGL.vertex3fv(v0.mV);
                        gGL.vertex3fv(v23.mV);
                        gGL.vertex3fv(v23.mV);
                        gGL.vertex3fv((v23 + (v0-v23)*.3f + (v3-v23)*.3f).mV);
                        gGL.vertex3fv(v23.mV);
                        gGL.vertex3fv((v23 + (v0-v23)*.3f + (v2-v23)*.3f).mV);
                    }
                    gGL.end();
                    LLUI::setLineWidth(1.0f);

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

            // Since we draw handles with depth testing off, we need to draw them in the
            // proper depth order.

            // Copied from LLDrawable::updateGeometry
            LLVector3 pos_agent     = first_object->getPositionAgent();
            LLVector3 camera_agent  = gAgentCamera.getCameraPositionAgent();
            LLVector3 headPos       = pos_agent - camera_agent;

            LLVector3 orientWRTHead    = headPos * invRotation;

            // Find nearest vertex
            U32 nearest = (orientWRTHead.mV[0] < 0.0f ? 1 : 0) +
                (orientWRTHead.mV[1] < 0.0f ? 2 : 0) +
                (orientWRTHead.mV[2] < 0.0f ? 4 : 0);

            // opposite faces on Linden cubes:
            // 0 & 5
            // 1 & 3
            // 2 & 4

            // Table of order to draw faces, based on nearest vertex
            static U32 face_list[8][NUM_AXES*2] = {
                { 2,0,1, 4,5,3 }, // v6  F201 F453
                { 2,0,3, 4,5,1 }, // v7  F203 F451
                { 4,0,1, 2,5,3 }, // v5  F401 F253
                { 4,0,3, 2,5,1 }, // v4  F403 F251
                { 2,5,1, 4,0,3 }, // v2  F251 F403
                { 2,5,3, 4,0,1 }, // v3  F253 F401
                { 4,5,1, 2,0,3 }, // v1  F451 F203
                { 4,5,3, 2,0,1 }, // v0  F453 F201
            };
            static const EManipPart which_arrow[6] = {
                LL_Z_ARROW,
                LL_X_ARROW,
                LL_Y_ARROW,
                LL_X_ARROW,
                LL_Y_ARROW,
                LL_Z_ARROW};

            // draw arrows for deeper faces first, closer faces last
            LLVector3 camera_axis;
            if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD)
            {
                camera_axis = LLVector3::x_axis;
            }
            else
            {
                camera_axis.setVec(gAgentCamera.getCameraPositionAgent() - first_object->getPositionAgent());
            }

            for (U32 i = 0; i < NUM_AXES*2; i++)
            {
                U32 face = face_list[nearest][i];

                LLVector3 arrow_axis;
                getManipAxis(first_object, which_arrow[face], arrow_axis);

                renderArrow(which_arrow[face],
                            mManipPart,
                            (face >= 3) ? -mConeSize : mConeSize,
                            (face >= 3) ? -mArrowLengthMeters : mArrowLengthMeters,
                            mConeSize,
                            false);
            }
        }
    }
    gGL.popMatrix();
}


void LLManipTranslate::renderArrow(S32 which_arrow, S32 selected_arrow, F32 box_size, F32 arrow_size, F32 handle_size, bool reverse_direction)
{
    gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
    LLGLEnable gls_blend(GL_BLEND);

    for (S32 pass = 1; pass <= 2; pass++)
    {
        LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE, pass == 1 ? GL_LEQUAL : GL_GREATER);
        gGL.pushMatrix();

        S32 index = 0;

        index = ARROW_TO_AXIS[which_arrow];

        // assign a color for this arrow
        LLColor4 color;  // black
        if (which_arrow == selected_arrow || which_arrow == mHighlightedPart)
        {
            color.mV[index] = (pass == 1) ? 1.f : 0.5f;
        }
        else if (selected_arrow != LL_NO_PART)
        {
            color.mV[VALPHA] = 0.f;
        }
        else
        {
            color.mV[index] = pass == 1 ? .8f : .35f ;          // red, green, or blue
            color.mV[VALPHA] = 0.6f;
        }
        gGL.color4fv( color.mV );

        LLVector3 vec;

        {
            LLUI::setLineWidth(2.0f);
            gGL.begin(LLRender::LINES);
                vec.mV[index] = box_size;
                gGL.vertex3f(vec.mV[0], vec.mV[1], vec.mV[2]);

                vec.mV[index] = arrow_size;
                gGL.vertex3f(vec.mV[0], vec.mV[1], vec.mV[2]);
            gGL.end();
            LLUI::setLineWidth(1.0f);
        }

        gGL.translatef(vec.mV[0], vec.mV[1], vec.mV[2]);
        gGL.scalef(handle_size, handle_size, handle_size);

        F32 rot = 0.0f;
        LLVector3 axis;

        switch(which_arrow)
        {
        case LL_X_ARROW:
            rot = reverse_direction ? -90.0f : 90.0f;
            axis.mV[1] = 1.0f;
            break;
        case LL_Y_ARROW:
            rot = reverse_direction ? 90.0f : -90.0f;
            axis.mV[0] = 1.0f;
            break;
        case LL_Z_ARROW:
            rot = reverse_direction ? 180.0f : 0.0f;
            axis.mV[0] = 1.0f;
            break;
        default:
            LL_ERRS() << "renderArrow called with bad arrow " << which_arrow << LL_ENDL;
            break;
        }

        gGL.diffuseColor4fv(color.mV);
        gGL.rotatef(rot, axis.mV[0], axis.mV[1], axis.mV[2]);
        gGL.scalef(mArrowScales.mV[index], mArrowScales.mV[index], mArrowScales.mV[index] * 1.5f);

        gCone.render();

        gGL.popMatrix();
    }
}

void LLManipTranslate::renderGridVert(F32 x_trans, F32 y_trans, F32 r, F32 g, F32 b, F32 alpha)
{
    gGL.color4f(r, g, b, alpha);
    switch (mManipPart)
    {
    case LL_YZ_PLANE:
        gGL.vertex3f(0, x_trans, y_trans);
        break;
    case LL_XZ_PLANE:
        gGL.vertex3f(x_trans, 0, y_trans);
        break;
    case LL_XY_PLANE:
        gGL.vertex3f(x_trans, y_trans, 0);
        break;
    default:
        gGL.vertex3f(0,0,0);
        break;
    }

}

// virtual
bool LLManipTranslate::canAffectSelection()
{
    bool can_move = mObjectSelection->getObjectCount() != 0;
    if (can_move)
    {
        struct f : public LLSelectedObjectFunctor
        {
            virtual bool apply(LLViewerObject* objectp)
            {
                LLViewerObject *root_object = (objectp == NULL) ? NULL : objectp->getRootEdit();
                return objectp->permMove() && !objectp->isPermanentEnforced() &&
                    ((root_object == NULL) || !root_object->isPermanentEnforced()) &&
                    (objectp->permModify() || !gSavedSettings.getBOOL("EditLinkedParts"));
            }
        } func;
        can_move = mObjectSelection->applyToObjects(&func);
    }
    return can_move;
}