/**
 * @file lljoystickbutton.cpp
 * @brief LLJoystick class implementation
 *
 * $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"

#include "lljoystickbutton.h"

// Library includes
#include "llcoord.h"
#include "indra_constants.h"
#include "llrender.h"

// Project includes
#include "llui.h"
#include "llagent.h"
#include "llagentcamera.h"
#include "llviewercamera.h"
#include "llviewertexture.h"
#include "llviewertexturelist.h"
#include "llviewerwindow.h"
#include "llmoveview.h"

#include "llglheaders.h"

static LLDefaultChildRegistry::Register<LLJoystickAgentSlide> r1("joystick_slide");
static LLDefaultChildRegistry::Register<LLJoystickAgentTurn> r2("joystick_turn");
static LLDefaultChildRegistry::Register<LLJoystickCameraRotate> r3("joystick_rotate");
static LLDefaultChildRegistry::Register<LLJoystickCameraTrack> r5("joystick_track");
static LLDefaultChildRegistry::Register<LLJoystickQuaternion> r6("joystick_quat");


const F32 NUDGE_TIME = 0.25f;       // in seconds
const F32 ORBIT_NUDGE_RATE = 0.05f; // fraction of normal speed

const S32 CENTER_DOT_RADIUS = 7;

//
// Public Methods
//
void QuadrantNames::declareValues()
{
    declare("origin", JQ_ORIGIN);
    declare("up", JQ_UP);
    declare("down", JQ_DOWN);
    declare("left", JQ_LEFT);
    declare("right", JQ_RIGHT);
}


LLJoystick::LLJoystick(const LLJoystick::Params& p)
:   LLButton(p),
    mInitialOffset(0, 0),
    mLastMouse(0, 0),
    mFirstMouse(0, 0),
    mVertSlopNear(0),
    mVertSlopFar(0),
    mHorizSlopNear(0),
    mHorizSlopFar(0),
    mHeldDown(false),
    mHeldDownTimer(),
    mInitialQuadrant(p.quadrant)
{
    setHeldDownCallback(&LLJoystick::onBtnHeldDown, this);
}


void LLJoystick::updateSlop()
{
    mVertSlopNear = getRect().getHeight();
    mVertSlopFar = getRect().getHeight() * 2;

    mHorizSlopNear = getRect().getWidth();
    mHorizSlopFar = getRect().getWidth() * 2;

    // Compute initial mouse offset based on initial quadrant.
    // Place the mouse evenly between the near and far zones.
    switch (mInitialQuadrant)
    {
    case JQ_ORIGIN:
        mInitialOffset.set(0, 0);
        break;

    case JQ_UP:
        mInitialOffset.mX = 0;
        mInitialOffset.mY = (mVertSlopNear + mVertSlopFar) / 2;
        break;

    case JQ_DOWN:
        mInitialOffset.mX = 0;
        mInitialOffset.mY = - (mVertSlopNear + mVertSlopFar) / 2;
        break;

    case JQ_LEFT:
        mInitialOffset.mX = - (mHorizSlopNear + mHorizSlopFar) / 2;
        mInitialOffset.mY = 0;
        break;

    case JQ_RIGHT:
        mInitialOffset.mX = (mHorizSlopNear + mHorizSlopFar) / 2;
        mInitialOffset.mY = 0;
        break;

    default:
        LL_ERRS() << "LLJoystick::LLJoystick() - bad switch case" << LL_ENDL;
        break;
    }

    return;
}

bool LLJoystick::pointInCircle(S32 x, S32 y) const
{
    if(this->getLocalRect().getHeight() != this->getLocalRect().getWidth())
    {
        LL_WARNS() << "Joystick shape is not square"<<LL_ENDL;
        return true;
    }
    //center is x and y coordinates of center of joystick circle, and also its radius
    int center = this->getLocalRect().getHeight()/2;
    bool in_circle = (x - center) * (x - center) + (y - center) * (y - center) <= center * center;

    return in_circle;
}

bool LLJoystick::pointInCenterDot(S32 x, S32 y, S32 radius) const
{
    if (this->getLocalRect().getHeight() != this->getLocalRect().getWidth())
    {
        LL_WARNS() << "Joystick shape is not square" << LL_ENDL;
        return true;
    }

    S32 center = this->getLocalRect().getHeight() / 2;

    bool in_center_circle = (x - center) * (x - center) + (y - center) * (y - center) <= radius * radius;

    return in_center_circle;
}

bool LLJoystick::handleMouseDown(S32 x, S32 y, MASK mask)
{
    //LL_INFOS() << "joystick mouse down " << x << ", " << y << LL_ENDL;
    bool handles = false;

    if(pointInCircle(x, y))
    {
        mLastMouse.set(x, y);
        mFirstMouse.set(x, y);
        mMouseDownTimer.reset();
        handles = LLButton::handleMouseDown(x, y, mask);
    }

    return handles;
}


bool LLJoystick::handleMouseUp(S32 x, S32 y, MASK mask)
{
    // LL_INFOS() << "joystick mouse up " << x << ", " << y << LL_ENDL;

    if( hasMouseCapture() )
    {
        mLastMouse.set(x, y);
        mHeldDown = false;
        onMouseUp();
    }

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


bool LLJoystick::handleHover(S32 x, S32 y, MASK mask)
{
    if( hasMouseCapture() )
    {
        mLastMouse.set(x, y);
    }

    return LLButton::handleHover(x, y, mask);
}

F32 LLJoystick::getElapsedHeldDownTime()
{
    if( mHeldDown )
    {
        return getHeldDownTime();
    }
    else
    {
        return 0.f;
    }
}

// static
void LLJoystick::onBtnHeldDown(void *userdata)
{
    LLJoystick *self = (LLJoystick *)userdata;
    if (self)
    {
        self->mHeldDown = true;
        self->onHeldDown();
    }
}

EJoystickQuadrant LLJoystick::selectQuadrant(LLXMLNodePtr node)
{

    EJoystickQuadrant quadrant = JQ_RIGHT;

    if (node->hasAttribute("quadrant"))
    {
        std::string quadrant_name;
        node->getAttributeString("quadrant", quadrant_name);

        quadrant = quadrantFromName(quadrant_name);
    }
    return quadrant;
}


std::string LLJoystick::nameFromQuadrant(EJoystickQuadrant  quadrant)
{
    if (quadrant == JQ_ORIGIN)      return std::string("origin");
    else if (quadrant == JQ_UP)     return std::string("up");
    else if (quadrant == JQ_DOWN)   return std::string("down");
    else if (quadrant == JQ_LEFT)   return std::string("left");
    else if (quadrant == JQ_RIGHT)  return std::string("right");
    else return std::string();
}


EJoystickQuadrant LLJoystick::quadrantFromName(const std::string& sQuadrant)
{
    EJoystickQuadrant quadrant = JQ_RIGHT;

    if (sQuadrant == "origin")
    {
        quadrant = JQ_ORIGIN;
    }
    else if (sQuadrant == "up")
    {
        quadrant = JQ_UP;
    }
    else if (sQuadrant == "down")
    {
        quadrant = JQ_DOWN;
    }
    else if (sQuadrant == "left")
    {
        quadrant = JQ_LEFT;
    }
    else if (sQuadrant == "right")
    {
        quadrant = JQ_RIGHT;
    }

    return quadrant;
}


//-------------------------------------------------------------------------------
// LLJoystickAgentTurn
//-------------------------------------------------------------------------------

void LLJoystickAgentTurn::onHeldDown()
{
    F32 time = getElapsedHeldDownTime();
    updateSlop();

    //LL_INFOS() << "move forward/backward (and/or turn)" << LL_ENDL;

    S32 dx = mLastMouse.mX - mFirstMouse.mX + mInitialOffset.mX;
    S32 dy = mLastMouse.mY - mFirstMouse.mY + mInitialOffset.mY;

    float m = (float) (dx)/abs(dy);

    if (m > 1) {
        m = 1;
    }
    else if (m < -1) {
        m = -1;
    }
    gAgent.moveYaw(-LLFloaterMove::getYawRate(time)*m);


    // handle forward/back movement
    if (dy > mVertSlopFar)
    {
        // ...if mouse is forward of run region run forward
        gAgent.moveAt(1);
    }
    else if (dy > mVertSlopNear)
    {
        if( time < NUDGE_TIME )
        {
            gAgent.moveAtNudge(1);
        }
        else
        {
            // ...else if mouse is forward of walk region walk forward
            // JC 9/5/2002 - Always run / move quickly.
            gAgent.moveAt(1);
        }
    }
    else if (dy < -mVertSlopFar)
    {
        // ...else if mouse is behind run region run backward
        gAgent.moveAt(-1);
    }
    else if (dy < -mVertSlopNear)
    {
        if( time < NUDGE_TIME )
        {
            gAgent.moveAtNudge(-1);
        }
        else
        {
            // ...else if mouse is behind walk region walk backward
            // JC 9/5/2002 - Always run / move quickly.
            gAgent.moveAt(-1);
        }
    }
}

//-------------------------------------------------------------------------------
// LLJoystickAgentSlide
//-------------------------------------------------------------------------------

void LLJoystickAgentSlide::onMouseUp()
{
    F32 time = getElapsedHeldDownTime();
    if( time < NUDGE_TIME )
    {
        switch (mInitialQuadrant)
        {
        case JQ_LEFT:
            gAgent.moveLeftNudge(1);
            break;

        case JQ_RIGHT:
            gAgent.moveLeftNudge(-1);
            break;

        default:
            break;
        }
    }
}

void LLJoystickAgentSlide::onHeldDown()
{
    //LL_INFOS() << "slide left/right (and/or move forward/backward)" << LL_ENDL;

    updateSlop();

    S32 dx = mLastMouse.mX - mFirstMouse.mX + mInitialOffset.mX;
    S32 dy = mLastMouse.mY - mFirstMouse.mY + mInitialOffset.mY;

    // handle left-right sliding
    if (dx > mHorizSlopNear)
    {
        gAgent.moveLeft(-1);
    }
    else if (dx < -mHorizSlopNear)
    {
        gAgent.moveLeft(1);
    }

    // handle forward/back movement
    if (dy > mVertSlopFar)
    {
        // ...if mouse is forward of run region run forward
        gAgent.moveAt(1);
    }
    else if (dy > mVertSlopNear)
    {
        // ...else if mouse is forward of walk region walk forward
        gAgent.moveAtNudge(1);
    }
    else if (dy < -mVertSlopFar)
    {
        // ...else if mouse is behind run region run backward
        gAgent.moveAt(-1);
    }
    else if (dy < -mVertSlopNear)
    {
        // ...else if mouse is behind walk region walk backward
        gAgent.moveAtNudge(-1);
    }
}


//-------------------------------------------------------------------------------
// LLJoystickCameraRotate
//-------------------------------------------------------------------------------

LLJoystickCameraRotate::LLJoystickCameraRotate(const LLJoystickCameraRotate::Params& p)
:   LLJoystick(p),
    mInLeft( false ),
    mInTop( false ),
    mInRight( false ),
    mInBottom( false ),
    mInCenter( false )
{
    mCenterImageName = "Cam_Rotate_Center";
}


void LLJoystickCameraRotate::updateSlop()
{
    // do the initial offset calculation based on mousedown location

    // small fixed slop region
    mVertSlopNear = 16;
    mVertSlopFar = 32;

    mHorizSlopNear = 16;
    mHorizSlopFar = 32;

    return;
}


bool LLJoystickCameraRotate::handleMouseDown(S32 x, S32 y, MASK mask)
{
    gAgent.setMovementLocked(true);
    updateSlop();

    // Set initial offset based on initial click location
    S32 horiz_center = getRect().getWidth() / 2;
    S32 vert_center = getRect().getHeight() / 2;

    S32 dx = x - horiz_center;
    S32 dy = y - vert_center;

    if (pointInCenterDot(x, y, CENTER_DOT_RADIUS))
    {
        mInitialOffset.mX = 0;
        mInitialOffset.mY = 0;
        mInitialQuadrant = JQ_ORIGIN;
        mInCenter = true;

        resetJoystickCamera();
    }
    else if (dy > dx && dy > -dx)
    {
        // top
        mInitialOffset.mX = 0;
        mInitialOffset.mY = (mVertSlopNear + mVertSlopFar) / 2;
        mInitialQuadrant = JQ_UP;
    }
    else if (dy > dx && dy <= -dx)
    {
        // left
        mInitialOffset.mX = - (mHorizSlopNear + mHorizSlopFar) / 2;
        mInitialOffset.mY = 0;
        mInitialQuadrant = JQ_LEFT;
    }
    else if (dy <= dx && dy <= -dx)
    {
        // bottom
        mInitialOffset.mX = 0;
        mInitialOffset.mY = - (mVertSlopNear + mVertSlopFar) / 2;
        mInitialQuadrant = JQ_DOWN;
    }
    else
    {
        // right
        mInitialOffset.mX = (mHorizSlopNear + mHorizSlopFar) / 2;
        mInitialOffset.mY = 0;
        mInitialQuadrant = JQ_RIGHT;
    }

    return LLJoystick::handleMouseDown(x, y, mask);
}

bool LLJoystickCameraRotate::handleMouseUp(S32 x, S32 y, MASK mask)
{
    gAgent.setMovementLocked(false);
    mInCenter = false;
    return LLJoystick::handleMouseUp(x, y, mask);
}

bool LLJoystickCameraRotate::handleHover(S32 x, S32 y, MASK mask)
{
    if (!pointInCenterDot(x, y, CENTER_DOT_RADIUS))
    {
        mInCenter = false;
    }

    return LLJoystick::handleHover(x, y, mask);
}

void LLJoystickCameraRotate::onHeldDown()
{
    updateSlop();

    S32 dx = mLastMouse.mX - mFirstMouse.mX + mInitialOffset.mX;
    S32 dy = mLastMouse.mY - mFirstMouse.mY + mInitialOffset.mY;

    // left-right rotation
    if (dx > mHorizSlopNear)
    {
        gAgentCamera.unlockView();
        gAgentCamera.setOrbitLeftKey(getOrbitRate());
    }
    else if (dx < -mHorizSlopNear)
    {
        gAgentCamera.unlockView();
        gAgentCamera.setOrbitRightKey(getOrbitRate());
    }

    // over/under rotation
    if (dy > mVertSlopNear)
    {
        gAgentCamera.unlockView();
        gAgentCamera.setOrbitUpKey(getOrbitRate());
    }
    else if (dy < -mVertSlopNear)
    {
        gAgentCamera.unlockView();
        gAgentCamera.setOrbitDownKey(getOrbitRate());
    }
}

void LLJoystickCameraRotate::resetJoystickCamera()
{
    gAgentCamera.resetCameraOrbit();
}

F32 LLJoystickCameraRotate::getOrbitRate()
{
    F32 time = getElapsedHeldDownTime();
    if( time < NUDGE_TIME )
    {
        F32 rate = ORBIT_NUDGE_RATE + time * (1 - ORBIT_NUDGE_RATE)/ NUDGE_TIME;
        //LL_INFOS() << rate << LL_ENDL;
        return rate;
    }
    else
    {
        return 1;
    }
}


// Only used for drawing
void LLJoystickCameraRotate::setToggleState( bool left, bool top, bool right, bool bottom )
{
    mInLeft = left;
    mInTop = top;
    mInRight = right;
    mInBottom = bottom;
}

void LLJoystickCameraRotate::draw()
{
    LLGLSUIDefault gls_ui;

    getImageUnselected()->draw( 0, 0 );
    LLPointer<LLUIImage> image = getImageSelected();

    if (mInCenter)
    {
        drawRotatedImage(LLUI::getUIImage(mCenterImageName), 0);
    }
    else
    {
        if (mInTop)
        {
            drawRotatedImage(getImageSelected(), 0);
        }

        if (mInRight)
        {
            drawRotatedImage(getImageSelected(), 1);
        }

        if (mInBottom)
        {
            drawRotatedImage(getImageSelected(), 2);
        }

        if (mInLeft)
        {
            drawRotatedImage(getImageSelected(), 3);
        }
    }
}

// Draws image rotated by multiples of 90 degrees
void LLJoystickCameraRotate::drawRotatedImage( LLPointer<LLUIImage> image, S32 rotations )
{
    S32 width = image->getWidth();
    S32 height = image->getHeight();
    LLTexture* texture = image->getImage();

    /*
     * Scale  texture coordinate system
     * to handle the different between image size and size of texture.
     * If we will use default matrix,
     * it may break texture mapping after rotation.
     * see EXT-2023 Camera floater: arrows became shifted when pressed.
     */
    F32 uv[][2] =
    {
        { (F32)width/texture->getWidth(), (F32)height/texture->getHeight() },
        { 0.f, (F32)height/texture->getHeight() },
        { 0.f, 0.f },
        { (F32)width/texture->getWidth(), 0.f }
    };

    gGL.getTexUnit(0)->bind(texture);

    gGL.color4fv(UI_VERTEX_COLOR.mV);

    gGL.begin(LLRender::QUADS);
    {
        gGL.texCoord2fv( uv[ (rotations + 0) % 4]);
        gGL.vertex2i(width, height );

        gGL.texCoord2fv( uv[ (rotations + 1) % 4]);
        gGL.vertex2i(0, height );

        gGL.texCoord2fv( uv[ (rotations + 2) % 4]);
        gGL.vertex2i(0, 0);

        gGL.texCoord2fv( uv[ (rotations + 3) % 4]);
        gGL.vertex2i(width, 0);
    }
    gGL.end();
}



//-------------------------------------------------------------------------------
// LLJoystickCameraTrack
//-------------------------------------------------------------------------------

LLJoystickCameraTrack::Params::Params()
{
    held_down_delay.seconds(0.0);
}

LLJoystickCameraTrack::LLJoystickCameraTrack(const LLJoystickCameraTrack::Params& p)
:   LLJoystickCameraRotate(p)
{
    mCenterImageName = "Cam_Tracking_Center";
}


void LLJoystickCameraTrack::onHeldDown()
{
    updateSlop();

    S32 dx = mLastMouse.mX - mFirstMouse.mX + mInitialOffset.mX;
    S32 dy = mLastMouse.mY - mFirstMouse.mY + mInitialOffset.mY;

    if (dx > mVertSlopNear)
    {
        gAgentCamera.unlockView();
        gAgentCamera.setPanRightKey(getOrbitRate());
    }
    else if (dx < -mVertSlopNear)
    {
        gAgentCamera.unlockView();
        gAgentCamera.setPanLeftKey(getOrbitRate());
    }

    // over/under rotation
    if (dy > mVertSlopNear)
    {
        gAgentCamera.unlockView();
        gAgentCamera.setPanUpKey(getOrbitRate());
    }
    else if (dy < -mVertSlopNear)
    {
        gAgentCamera.unlockView();
        gAgentCamera.setPanDownKey(getOrbitRate());
    }
}

void LLJoystickCameraTrack::resetJoystickCamera()
{
    gAgentCamera.resetCameraPan();
}

//-------------------------------------------------------------------------------
// LLJoystickQuaternion
//-------------------------------------------------------------------------------

LLJoystickQuaternion::Params::Params()
{
}

LLJoystickQuaternion::LLJoystickQuaternion(const LLJoystickQuaternion::Params &p):
    LLJoystick(p),
    mInLeft(false),
    mInTop(false),
    mInRight(false),
    mInBottom(false),
    mVectorZero(0.0f, 0.0f, 1.0f),
    mRotation(),
    mUpDnAxis(1.0f, 0.0f, 0.0f),
    mLfRtAxis(0.0f, 0.0f, 1.0f),
    mXAxisIndex(2), // left & right across the control
    mYAxisIndex(0), // up & down across the  control
    mZAxisIndex(1)  // tested for above and below
{
    for (int i = 0; i < 3; ++i)
    {
        mLfRtAxis.mV[i] = (mXAxisIndex == i) ? 1.0f : 0.0f;
        mUpDnAxis.mV[i] = (mYAxisIndex == i) ? 1.0f : 0.0f;
    }
}

void LLJoystickQuaternion::setToggleState(bool left, bool top, bool right, bool bottom)
{
    mInLeft = left;
    mInTop = top;
    mInRight = right;
    mInBottom = bottom;
}

bool LLJoystickQuaternion::handleMouseDown(S32 x, S32 y, MASK mask)
{
    updateSlop();

    // Set initial offset based on initial click location
    S32 horiz_center = getRect().getWidth() / 2;
    S32 vert_center = getRect().getHeight() / 2;

    S32 dx = x - horiz_center;
    S32 dy = y - vert_center;

    if (dy > dx && dy > -dx)
    {
        // top
        mInitialOffset.mX = 0;
        mInitialOffset.mY = (mVertSlopNear + mVertSlopFar) / 2;
        mInitialQuadrant = JQ_UP;
    }
    else if (dy > dx && dy <= -dx)
    {
        // left
        mInitialOffset.mX = -(mHorizSlopNear + mHorizSlopFar) / 2;
        mInitialOffset.mY = 0;
        mInitialQuadrant = JQ_LEFT;
    }
    else if (dy <= dx && dy <= -dx)
    {
        // bottom
        mInitialOffset.mX = 0;
        mInitialOffset.mY = -(mVertSlopNear + mVertSlopFar) / 2;
        mInitialQuadrant = JQ_DOWN;
    }
    else
    {
        // right
        mInitialOffset.mX = (mHorizSlopNear + mHorizSlopFar) / 2;
        mInitialOffset.mY = 0;
        mInitialQuadrant = JQ_RIGHT;
    }

    return LLJoystick::handleMouseDown(x, y, mask);
}

bool LLJoystickQuaternion::handleMouseUp(S32 x, S32 y, MASK mask)
{
    return LLJoystick::handleMouseUp(x, y, mask);
}

void LLJoystickQuaternion::onHeldDown()
{
    LLVector3 axis;
    updateSlop();

    S32 dx = mLastMouse.mX - mFirstMouse.mX + mInitialOffset.mX;
    S32 dy = mLastMouse.mY - mFirstMouse.mY + mInitialOffset.mY;

    // left-right rotation
    if (dx > mHorizSlopNear)
    {
        axis += mUpDnAxis;
    }
    else if (dx < -mHorizSlopNear)
    {
        axis -= mUpDnAxis;
    }

    // over/under rotation
    if (dy > mVertSlopNear)
    {
        axis += mLfRtAxis;
    }
    else if (dy < -mVertSlopNear)
    {
        axis -= mLfRtAxis;
    }

    if (axis.isNull())
        return;

    axis.normalize();

    LLQuaternion delta;
    delta.setAngleAxis(0.0523599f, axis);   // about 3deg

    mRotation *= delta;
    setValue(mRotation.getValue());
    onCommit();
}

void LLJoystickQuaternion::draw()
{
    LLGLSUIDefault gls_ui;

    getImageUnselected()->draw(0, 0);
    LLPointer<LLUIImage> image = getImageSelected();

    if (mInTop)
    {
        drawRotatedImage(getImageSelected(), 0);
    }

    if (mInRight)
    {
        drawRotatedImage(getImageSelected(), 1);
    }

    if (mInBottom)
    {
        drawRotatedImage(getImageSelected(), 2);
    }

    if (mInLeft)
    {
        drawRotatedImage(getImageSelected(), 3);
    }

    LLVector3 draw_point = mVectorZero * mRotation;
    S32 halfwidth = getRect().getWidth() / 2;
    S32 halfheight = getRect().getHeight() / 2;
    draw_point.mV[mXAxisIndex] = (draw_point.mV[mXAxisIndex] + 1.0f) * halfwidth;
    draw_point.mV[mYAxisIndex] = (draw_point.mV[mYAxisIndex] + 1.0f) * halfheight;

    gl_circle_2d(draw_point.mV[mXAxisIndex], draw_point.mV[mYAxisIndex], 4, 8,
        draw_point.mV[mZAxisIndex] >= 0.f);

}

F32 LLJoystickQuaternion::getOrbitRate()
{
    return 1;
}

void LLJoystickQuaternion::updateSlop()
{
    // small fixed slop region
    mVertSlopNear = 16;
    mVertSlopFar = 32;

    mHorizSlopNear = 16;
    mHorizSlopFar = 32;
}

void LLJoystickQuaternion::drawRotatedImage(LLPointer<LLUIImage> image, S32 rotations)
{
    S32 width = image->getWidth();
    S32 height = image->getHeight();
    LLTexture* texture = image->getImage();

    /*
    * Scale  texture coordinate system
    * to handle the different between image size and size of texture.
    */
    F32 uv[][2] =
    {
        { (F32)width / texture->getWidth(), (F32)height / texture->getHeight() },
        { 0.f, (F32)height / texture->getHeight() },
        { 0.f, 0.f },
        { (F32)width / texture->getWidth(), 0.f }
    };

    gGL.getTexUnit(0)->bind(texture);

    gGL.color4fv(UI_VERTEX_COLOR.mV);

    gGL.begin(LLRender::QUADS);
    {
        gGL.texCoord2fv(uv[(rotations + 0) % 4]);
        gGL.vertex2i(width, height);

        gGL.texCoord2fv(uv[(rotations + 1) % 4]);
        gGL.vertex2i(0, height);

        gGL.texCoord2fv(uv[(rotations + 2) % 4]);
        gGL.vertex2i(0, 0);

        gGL.texCoord2fv(uv[(rotations + 3) % 4]);
        gGL.vertex2i(width, 0);
    }
    gGL.end();
}

void LLJoystickQuaternion::setRotation(const LLQuaternion &value)
{
    if (value != mRotation)
    {
        mRotation = value;
        mRotation.normalize();
        LLJoystick::setValue(mRotation.getValue());
    }
}

LLQuaternion LLJoystickQuaternion::getRotation() const
{
    return mRotation;
}