#include "CGizmo.h"
#include <Common/Math/MathUtil.h>
#include <Core/GameProject/CResourceStore.h>
#include <Core/Render/CDrawUtil.h>
#include <Core/Render/CRenderer.h>
#include <Common/Log.h>

#include <iostream>
#include <QApplication>
#include <QDesktopWidget>
#include <QScreen>

CGizmo::CGizmo()
    : mSelectedAxes(EAxis::None)
    , mTransformSpace(ETransformSpace::World)
    , mGizmoSize(1.f)
    , mCameraDist(0.f)
    , mIsTransforming(false)
    , mHasTransformed(false)
    , mWrapOffset(0.f)
    , mEnableCursorWrap(true)
    , mPosition(CVector3f::skZero)
    , mRotation(CQuaternion::skIdentity)
    , mLocalRotation(CQuaternion::skIdentity)
    , mScale(CVector3f::skOne)
    , mFlipScaleX(false)
    , mFlipScaleY(false)
    , mFlipScaleZ(false)
    , mDeltaTranslation(CVector3f::skZero)
    , mDeltaRotation(CQuaternion::skIdentity)
    , mDeltaScale(CVector3f::skOne)
    , mTotalScale(CVector3f::skOne)
    , mSetOffset(false)
{
    LoadModels();
    SetMode(EGizmoMode::Translate);
}

CGizmo::~CGizmo()
{
}

void CGizmo::AddToRenderer(CRenderer *pRenderer, const SViewInfo&)
{
    // Transform is updated every frame even if the user doesn't modify the gizmo
    // in order to account for scale changes based on camera distance
    UpdateTransform();
    SModelPart *pPart = mpCurrentParts;

    // Add all parts to renderer
    for (uint32 iPart = 0; iPart < mNumCurrentParts; iPart++)
    {
        CModel *pModel = pPart->pModel;

        // Determine whether to use the mat set for regular (0) or highlight (1)
        FAxes PartAxes = pPart->ModelAxes;
        bool IsHighlighted = (PartAxes != EAxis::None) && ((mSelectedAxes & PartAxes) == pPart->ModelAxes);
        uint32 SetID = (IsHighlighted ? 1 : 0);

        // Add to renderer...
        pRenderer->AddMesh(this, iPart, pModel->AABox().Transformed(mTransform), pModel->HasTransparency(SetID), ERenderCommand::DrawMesh, EDepthGroup::Foreground);
        pPart++;
    }
}

void CGizmo::Draw(FRenderOptions /*Options*/, int ComponentIndex, ERenderCommand /*Command*/, const SViewInfo& /*rkViewInfo*/)
{
    // Determine which SModelPart array to use
    if (ComponentIndex >= (int) mNumCurrentParts) return;
    SModelPart *pPart = mpCurrentParts;

    // Set model matrix
    if (pPart[ComponentIndex].IsBillboard)
        CGraphics::sMVPBlock.ModelMatrix = mBillboardTransform;
    else if ((mMode == EGizmoMode::Scale) && ((mSelectedAxes & pPart[ComponentIndex].ModelAxes) != 0))
        CGraphics::sMVPBlock.ModelMatrix = mScaledTransform;
    else
        CGraphics::sMVPBlock.ModelMatrix = mTransform;

    CGraphics::UpdateMVPBlock();

    // Clear tint color
    CGraphics::sPixelBlock.TintColor = CColor::skWhite;
    CGraphics::UpdatePixelBlock();

    // Choose material set
    FAxes PartAxes = pPart[ComponentIndex].ModelAxes;
    bool IsHighlighted = (PartAxes != EAxis::None) && ((mSelectedAxes & PartAxes) == pPart[ComponentIndex].ModelAxes);
    uint32 SetID = (IsHighlighted ? 1 : 0);

    // Draw model
    pPart[ComponentIndex].pModel->Draw((FRenderOptions) 0, SetID);
}

void CGizmo::IncrementSize()
{
    static const float skIncAmount = 1.3f;
    static const float skMaxSize = powf(skIncAmount, 4);

    mGizmoSize *= skIncAmount;
    if (mGizmoSize > skMaxSize) mGizmoSize = skMaxSize;
}

void CGizmo::DecrementSize()
{
    static const float skDecAmount = (1.f / 1.3f);
    static const float skMinSize = powf(skDecAmount, 4);

    mGizmoSize *= skDecAmount;
    if (mGizmoSize < skMinSize) mGizmoSize = skMinSize;
}

void CGizmo::UpdateForCamera(const CCamera& rkCamera)
{
    CVector3f CamPos = rkCamera.Position();
    CVector3f CameraToGizmo = (mPosition - CamPos).Normalized();
    mFlipScaleX = (mRotation.XAxis().Dot(CameraToGizmo) >= 0.f);
    mFlipScaleY = (mRotation.YAxis().Dot(CameraToGizmo) >= 0.f);
    mFlipScaleZ = (mRotation.ZAxis().Dot(CameraToGizmo) >= 0.f);

    if ((!mIsTransforming) || (mMode != EGizmoMode::Translate))
        mCameraDist = mPosition.Distance(CamPos);

    // todo: make this cleaner...
    CVector3f BillDir = (CamPos - mPosition).Normalized();
    CVector3f Axis = CVector3f::skForward.Cross(BillDir);
    float Angle = acosf(CVector3f::skForward.Dot(BillDir));
    mBillboardRotation = CQuaternion::FromAxisAngle(Angle, Axis);
}

bool CGizmo::CheckSelectedAxes(const CRay& rkRay)
{
    CRay LocalRay = rkRay.Transformed(mTransform.Inverse());
    CRay BillRay = rkRay.Transformed(mBillboardTransform.Inverse());

    // Do raycast on each model
    SModelPart *pPart = mpCurrentParts;

    struct SResult {
        SModelPart *pPart;
        float Dist;
    };
    std::list<SResult> Results;

    for (uint32 iPart = 0; iPart < mNumCurrentParts; iPart++)
    {
        if (!pPart->EnableRayCast)
        {
            pPart++;
            continue;
        }

        CModel *pModel = pPart->pModel;
        CRay& rPartRay = (pPart->IsBillboard ? BillRay : LocalRay);

        // Ray/Model AABox test - allow buffer room because lines are small
        CAABox AABox = pModel->AABox();
        AABox.ExpandBy(CVector3f::skOne);
        bool ModelBoxCheck = Math::RayBoxIntersection(rPartRay, AABox).first;

        if (ModelBoxCheck)
        {
            bool Hit = false;
            float Dist;

            for (uint32 iSurf = 0; iSurf < pModel->GetSurfaceCount(); iSurf++)
            {
                // Skip surface/box check - since we use lines the boxes might be too small
                SSurface *pSurf = pModel->GetSurface(iSurf);
                std::pair<bool,float> SurfCheck = pSurf->IntersectsRay(rPartRay, false, 0.05f);

                if (SurfCheck.first)
                {
                    if ((!Hit) || (SurfCheck.second < Dist))
                        Dist = SurfCheck.second;

                    Hit = true;
                }
            }

            if (Hit)
            {
                SResult Result;
                Result.pPart = pPart;
                Result.Dist = Dist;
                Results.push_back(Result);
            }
        }

        pPart++;
    }

    // Results list empty = no hits
    if (Results.empty())
    {
        mSelectedAxes = EAxis::None;
        return false;
    }

    // Otherwise, we have at least one hit - sort results and set selected axes
    Results.sort([](const SResult& rkLeft, SResult& rkRight) -> bool
            {
                return (rkLeft.Dist < rkRight.Dist);
            });

    CRay& rPartRay = (pPart->IsBillboard ? BillRay : LocalRay);
    mSelectedAxes = Results.front().pPart->ModelAxes;
    mHitPoint = mTransform * rPartRay.PointOnRay(Results.front().Dist);

    return (mSelectedAxes != EAxis::None);
}

uint32 CGizmo::NumSelectedAxes()
{
    uint32 Out = 0;

    for (uint32 iAxis = 1; iAxis < 8; iAxis <<= 1)
        if (mSelectedAxes & FAxes(iAxis)) Out++;

    return Out;
}

void CGizmo::ResetSelectedAxes()
{
    mSelectedAxes = EAxis::None;
}

void CGizmo::StartTransform()
{
    mIsTransforming = true;
    mHasTransformed = false;
    mWrapOffset = CVector2f::skZero;
    mSetOffset = false;
    mTotalTranslation = CVector3f::skZero;
    mTotalRotation = CVector3f::skZero;
    mCurrentRotation = CQuaternion::skIdentity;
    mTotalScale = CVector3f::skOne;

    // Set rotation direction
    if (mMode == EGizmoMode::Rotate)
    {
        CVector3f Axis;
        if (mSelectedAxes & EAxis::X) Axis = mRotation.XAxis();
        else if (mSelectedAxes & EAxis::Y) Axis = mRotation.YAxis();
        else Axis = mRotation.ZAxis();

        CVector3f GizmoToHit = (mHitPoint - mPosition).Normalized();
        mMoveDir = Axis.Cross(GizmoToHit);
    }

    // Set scale direction
    else if (mMode == EGizmoMode::Scale)
    {
        // Only need to set scale direction if < 3 axes selected
        if (NumSelectedAxes() != 3)
        {
            // One axis; direction = selected axis
            if (NumSelectedAxes() == 1)
            {
                if (mSelectedAxes & EAxis::X)      mMoveDir = mRotation.XAxis();
                else if (mSelectedAxes & EAxis::Y) mMoveDir = mRotation.YAxis();
                else                               mMoveDir = mRotation.ZAxis();
            }

            // Two axes; interpolate between the two selected axes
            else if (NumSelectedAxes() == 2)
            {
                CVector3f AxisA = (mSelectedAxes & EAxis::X ? mRotation.XAxis() : mRotation.YAxis());
                CVector3f AxisB = (mSelectedAxes & EAxis::Z ? mRotation.ZAxis() : mRotation.YAxis());
                mMoveDir = (AxisA + AxisB) / 2.f;
            }
        }
    }
}

bool CGizmo::TransformFromInput(const CRay& rkRay, CCamera& rCamera)
{
    // Wrap cursor (this has no effect until the next time this function is called)
    if (mEnableCursorWrap && (mMode != EGizmoMode::Translate))
        WrapCursor();

    // Calculate normalized cursor position
    QPoint CursorPos = QCursor::pos();
    QRect Geom = QApplication::desktop()->screenGeometry();
    CVector2f MouseCoords(
                (((2.f * CursorPos.x()) / Geom.width()) - 1.f),
                (1.f - ((2.f * CursorPos.y()) / Geom.height()))
            );

    // Translate
    if (mMode == EGizmoMode::Translate)
    {
        // Create translate plane
        CVector3f AxisA, AxisB;
        uint32 NumAxes = NumSelectedAxes();

        if (NumAxes == 1)
        {
            if (mSelectedAxes & EAxis::X) AxisB = mRotation.XAxis();
            else if (mSelectedAxes & EAxis::Y) AxisB = mRotation.YAxis();
            else AxisB = mRotation.ZAxis();

            CVector3f GizmoToCamera = (mPosition - rCamera.Position()).Normalized();
            AxisA = AxisB.Cross(GizmoToCamera);
        }

        else if (NumAxes == 2)
        {
            AxisA = (mSelectedAxes & EAxis::X ? mRotation.XAxis() : mRotation.YAxis());
            AxisB = (mSelectedAxes & EAxis::Z ? mRotation.ZAxis() : mRotation.YAxis());
        }

        CVector3f PlaneNormal = AxisA.Cross(AxisB);
        mTranslatePlane.Redefine(PlaneNormal, mPosition);

        // Do translate
        std::pair<bool,float> Result = Math::RayPlaneIntersection(rkRay, mTranslatePlane);

        if (Result.first)
        {
            CVector3f Hit = rkRay.PointOnRay(Result.second);
            CVector3f LocalDelta = mRotation.Inverse() * (Hit - mPosition);

            // Calculate new position
            CVector3f NewPos = mPosition;
            if (mSelectedAxes & EAxis::X) NewPos += mRotation.XAxis() * LocalDelta.X;
            if (mSelectedAxes & EAxis::Y) NewPos += mRotation.YAxis() * LocalDelta.Y;
            if (mSelectedAxes & EAxis::Z) NewPos += mRotation.ZAxis() * LocalDelta.Z;

            // Check relativity of new pos to camera to reduce issue where the gizmo might
            // go flying off into the distance if newPosToCamera is parallel to the plane
            CVector3f NewPosToCamera = (NewPos - rCamera.Position()).Normalized();
            float Dot = Math::Abs(PlaneNormal.Dot(NewPosToCamera));
            if (Dot < 0.02f) return false;

            // Set offset
            if (!mSetOffset)
            {
                mTranslateOffset = mPosition - NewPos;
                mDeltaTranslation = CVector3f::skZero;
                mSetOffset = true;
                return false;
            }

            // Apply translation
            else
            {
                mDeltaTranslation = mRotation.Inverse() * (NewPos - mPosition + mTranslateOffset);
                if (!(mSelectedAxes & EAxis::X)) mDeltaTranslation.X = 0.f;
                if (!(mSelectedAxes & EAxis::Y)) mDeltaTranslation.Y = 0.f;
                if (!(mSelectedAxes & EAxis::Z)) mDeltaTranslation.Z = 0.f;

                mTotalTranslation += mDeltaTranslation;
                mPosition += mRotation * mDeltaTranslation;

                if (!mHasTransformed && (mDeltaTranslation != CVector3f::skZero))
                    mHasTransformed = true;

                return mHasTransformed;
            }
        }

        else
        {
            mDeltaTranslation = CVector3f::skZero;
            return false;
        }
    }

    // Rotate
    else if (mMode == EGizmoMode::Rotate)
    {
        // Choose rotation axis
        CVector3f Axis;
        if (mSelectedAxes & EAxis::X) Axis = CVector3f::skUnitX;
        else if (mSelectedAxes & EAxis::Y) Axis = CVector3f::skUnitY;
        else Axis = CVector3f::skUnitZ;

        // Convert hit point + move direction into a line in screen space
        // Clockwise direction is set in StartTransform(). Is there a cleaner way to calculate the direction?
        CMatrix4f VP = rCamera.ViewMatrix().Transpose() * rCamera.ProjectionMatrix().Transpose();
        CVector2f LineOrigin = (mHitPoint * VP).XY();
        CVector2f LineDir = (((mHitPoint + mMoveDir) * VP).XY() - LineOrigin).Normalized();
        float RotAmount = LineDir.Dot(MouseCoords + mWrapOffset - LineOrigin) * 180.f;

        // Set offset
        if (!mSetOffset)
        {
            mRotateOffset = -RotAmount;
            mDeltaRotation = CQuaternion::skIdentity;
            mSetOffset = true;
            return false;
        }

        // Apply rotation
        RotAmount += mRotateOffset;
        CQuaternion OldRot = mCurrentRotation;
        mCurrentRotation = CQuaternion::FromAxisAngle(Math::DegreesToRadians(RotAmount), Axis);
        mDeltaRotation = mCurrentRotation * OldRot.Inverse();

        if (mTransformSpace == ETransformSpace::Local)
            mRotation *= mDeltaRotation;

        // Add to total
        if (mSelectedAxes & EAxis::X)      mTotalRotation.X = RotAmount;
        else if (mSelectedAxes & EAxis::Y) mTotalRotation.Y = RotAmount;
        else                               mTotalRotation.Z = RotAmount;

        if (!mHasTransformed && (RotAmount != 0.f))
            mHasTransformed = true;

        return mHasTransformed;
    }

    // Scale
    else if (mMode == EGizmoMode::Scale)
    {
        // Create a line in screen space. First step: line origin
        CMatrix4f VP = rCamera.ViewMatrix().Transpose() * rCamera.ProjectionMatrix().Transpose();
        CVector2f LineOrigin = (mPosition * VP).XY();

        // Next step: determine the appropriate world space direction using the selected axes and then convert to screen space
        // Since the axes can be flipped while the gizmo is transforming, this has to be done every frame rather than
        // pre-saving the world space direction like the rotate gizmo does.
        CVector3f DirX = (mFlipScaleX ? -mRotation.XAxis() : mRotation.XAxis());
        CVector3f DirY = (mFlipScaleY ? -mRotation.YAxis() : mRotation.YAxis());
        CVector3f DirZ = (mFlipScaleZ ? -mRotation.ZAxis() : mRotation.ZAxis());
        CVector2f LineDir;

        // One axis - world space direction is just the selected axis
        if (NumSelectedAxes() == 1)
        {
            CVector3f WorldDir;
            if (mSelectedAxes & EAxis::X)      WorldDir = DirX;
            else if (mSelectedAxes & EAxis::Y) WorldDir = DirY;
            else                               WorldDir = DirZ;
            LineDir = (((mPosition + WorldDir) * VP).XY() - LineOrigin).Normalized();
        }
        // Two axes - take the two selected axes and convert them to world space, then average them for the line direction
        else if (NumSelectedAxes() == 2)
        {
            CVector3f AxisA = (mSelectedAxes & EAxis::X ? DirX : DirY);
            CVector3f AxisB = (mSelectedAxes & EAxis::Z ? DirZ : DirY);
            CVector2f ScreenA = (((mPosition + AxisA) * VP).XY() - LineOrigin).Normalized();
            CVector2f ScreenB = (((mPosition + AxisB) * VP).XY() - LineOrigin).Normalized();
            LineDir = ((ScreenA + ScreenB) / 2.f).Normalized();
        }
        // Three axes - use straight up
        else LineDir = CVector2f::skUp;

        float ScaleAmount = LineDir.Dot(MouseCoords + mWrapOffset - LineOrigin) * 5.f;

        // Set offset
        if (!mSetOffset)
        {
            mScaleOffset = -ScaleAmount;
            mDeltaScale = CVector3f::skOne;
            mSetOffset = true;
            return false;
        }

        // Apply scale
        ScaleAmount = ScaleAmount + mScaleOffset + 1.f;

        // A multiplier is applied to the scale amount of it's less than 1 to prevent it from going negative
        if (ScaleAmount < 1.f)
            ScaleAmount = 1.f / (-(ScaleAmount - 1.f) + 1.f);

        CVector3f OldScale = mTotalScale;

        mTotalScale = CVector3f::skOne;
        if (mSelectedAxes & EAxis::X) mTotalScale.X = ScaleAmount;
        if (mSelectedAxes & EAxis::Y) mTotalScale.Y = ScaleAmount;
        if (mSelectedAxes & EAxis::Z) mTotalScale.Z = ScaleAmount;

        mDeltaScale = mTotalScale / OldScale;

        if (!mHasTransformed && (ScaleAmount != 1.f))
            mHasTransformed = true;

        return mHasTransformed;
    }

    return false;
}

void CGizmo::EndTransform()
{
    mTotalScale = CVector3f::skOne;
    mIsTransforming = false;
}

void CGizmo::SetMode(EGizmoMode Mode)
{
    mMode = Mode;

    switch (Mode)
    {
    case EGizmoMode::Translate:
        mpCurrentParts = smTranslateModels;
        mNumCurrentParts = CGIZMO_TRANSLATE_NUM;
        mDeltaRotation = CQuaternion::skIdentity;
        mDeltaScale = CVector3f::skOne;
        break;

    case EGizmoMode::Rotate:
        mpCurrentParts = smRotateModels;
        mNumCurrentParts = CGIZMO_ROTATE_NUM;
        mDeltaTranslation = CVector3f::skZero;
        mDeltaScale = CVector3f::skOne;
        break;

    case EGizmoMode::Scale:
        mpCurrentParts = smScaleModels;
        mNumCurrentParts = CGIZMO_SCALE_NUM;
        mDeltaTranslation = CVector3f::skZero;
        mDeltaRotation = CQuaternion::skIdentity;
        break;
    default: break;
    }
}

void CGizmo::SetTransformSpace(ETransformSpace Space)
{
    mTransformSpace = Space;

    if (Space == ETransformSpace::World)
        mRotation = CQuaternion::skIdentity;
    else
        mRotation = mLocalRotation;
}

void CGizmo::SetLocalRotation(const CQuaternion& rkOrientation)
{
    mLocalRotation = rkOrientation;

    if (mTransformSpace == ETransformSpace::Local)
        mRotation = rkOrientation;
}

// ************ PRIVATE STATIC ************
void CGizmo::LoadModels()
{
    if (!smModelsLoaded)
    {
        debugf("Loading transform gizmo models");

        smTranslateModels[CGIZMO_TRANSLATE_X]        = SModelPart(EAxis::X,  true,  false, gpEditorStore->LoadResource("editor/TranslateX.CMDL"));
        smTranslateModels[CGIZMO_TRANSLATE_Y]        = SModelPart(EAxis::Y,  true,  false, gpEditorStore->LoadResource("editor/TranslateY.CMDL"));
        smTranslateModels[CGIZMO_TRANSLATE_Z]        = SModelPart(EAxis::Z,  true,  false, gpEditorStore->LoadResource("editor/TranslateZ.CMDL"));
        smTranslateModels[CGIZMO_TRANSLATE_LINES_XY] = SModelPart(EAxis::XY, true,  false, gpEditorStore->LoadResource("editor/TranslateLinesXY.CMDL"));
        smTranslateModels[CGIZMO_TRANSLATE_LINES_XZ] = SModelPart(EAxis::XZ, true,  false, gpEditorStore->LoadResource("editor/TranslateLinesXZ.CMDL"));
        smTranslateModels[CGIZMO_TRANSLATE_LINES_YZ] = SModelPart(EAxis::YZ, true,  false, gpEditorStore->LoadResource("editor/TranslateLinesYZ.CMDL"));
        smTranslateModels[CGIZMO_TRANSLATE_POLY_XY]  = SModelPart(EAxis::XY, false, false, gpEditorStore->LoadResource("editor/TranslatePolyXY.CMDL"));
        smTranslateModels[CGIZMO_TRANSLATE_POLY_XZ]  = SModelPart(EAxis::XZ, false, false, gpEditorStore->LoadResource("editor/TranslatePolyXZ.CMDL"));
        smTranslateModels[CGIZMO_TRANSLATE_POLY_YZ]  = SModelPart(EAxis::YZ, false, false, gpEditorStore->LoadResource("editor/TranslatePolyYZ.CMDL"));

        smRotateModels[CGIZMO_ROTATE_OUTLINE] = SModelPart(EAxis::None, true,  true,  gpEditorStore->LoadResource("editor/RotateClipOutline.CMDL"));
        smRotateModels[CGIZMO_ROTATE_X]       = SModelPart(EAxis::X,    true,  false, gpEditorStore->LoadResource("editor/RotateX.CMDL"));
        smRotateModels[CGIZMO_ROTATE_Y]       = SModelPart(EAxis::Y,    true,  false, gpEditorStore->LoadResource("editor/RotateY.CMDL"));
        smRotateModels[CGIZMO_ROTATE_Z]       = SModelPart(EAxis::Z,    true,  false, gpEditorStore->LoadResource("editor/RotateZ.CMDL"));
        smRotateModels[CGIZMO_ROTATE_XYZ]     = SModelPart(EAxis::XYZ,  false, false, gpEditorStore->LoadResource("editor/RotateXYZ.CMDL"));

        smScaleModels[CGIZMO_SCALE_X]         = SModelPart(EAxis::X,   true,  false, gpEditorStore->LoadResource("editor/ScaleX.CMDL"));
        smScaleModels[CGIZMO_SCALE_Y]         = SModelPart(EAxis::Y,   true,  false, gpEditorStore->LoadResource("editor/ScaleY.CMDL"));
        smScaleModels[CGIZMO_SCALE_Z]         = SModelPart(EAxis::Z,   true,  false, gpEditorStore->LoadResource("editor/ScaleZ.CMDL"));
        smScaleModels[CGIZMO_SCALE_LINES_XY]  = SModelPart(EAxis::XY,  true,  false, gpEditorStore->LoadResource("editor/ScaleLinesXY.CMDL"));
        smScaleModels[CGIZMO_SCALE_LINES_XZ]  = SModelPart(EAxis::XZ,  true,  false, gpEditorStore->LoadResource("editor/ScaleLinesXZ.CMDL"));
        smScaleModels[CGIZMO_SCALE_LINES_YZ]  = SModelPart(EAxis::YZ,  true,  false, gpEditorStore->LoadResource("editor/ScaleLinesYZ.CMDL"));
        smScaleModels[CGIZMO_SCALE_POLY_XY]   = SModelPart(EAxis::XY,  true,  false, gpEditorStore->LoadResource("editor/ScalePolyXY.CMDL"));
        smScaleModels[CGIZMO_SCALE_POLY_XZ]   = SModelPart(EAxis::XZ,  true,  false, gpEditorStore->LoadResource("editor/ScalePolyXZ.CMDL"));
        smScaleModels[CGIZMO_SCALE_POLY_YZ]   = SModelPart(EAxis::YZ,  true,  false, gpEditorStore->LoadResource("editor/ScalePolyYZ.CMDL"));
        smScaleModels[CGIZMO_SCALE_XYZ]       = SModelPart(EAxis::XYZ, true,  false, gpEditorStore->LoadResource("editor/ScaleXYZ.CMDL"));

        smModelsLoaded = true;
    }
}

// ************ PROTECTED ************
void CGizmo::UpdateTransform()
{
    // Scale is recalculated every frame because it changes frequently, based on camera distance
    // Rotation and position values are just saved directly
    mScale = mGizmoSize * (mCameraDist / 10.f);

    // Scale also factors in axis flip if mode is Scale.
    if (mMode == EGizmoMode::Scale)
    {
        if (mFlipScaleX) mScale.X = -mScale.X;
        if (mFlipScaleY) mScale.Y = -mScale.Y;
        if (mFlipScaleZ) mScale.Z = -mScale.Z;
    }

    // Create transform
    mTransform.SetIdentity();
    mTransform.Scale(mScale);
    mTransform.Rotate(mRotation);
    mTransform.Translate(mPosition);

    // Create billboard transform for rotation gizmo
    if (mMode == EGizmoMode::Rotate)
    {
        mBillboardTransform.SetIdentity();
        mBillboardTransform.Scale(mScale);
        mBillboardTransform.Rotate(mBillboardRotation);
        mBillboardTransform.Translate(mPosition);
    }

    // Create scaled transform for scale gizmo
    else if (mMode == EGizmoMode::Scale)
    {
        mScaledTransform.SetIdentity();
        mScaledTransform.Scale(mScale * mTotalScale);
        mScaledTransform.Rotate(mRotation);
        mScaledTransform.Translate(mPosition);
    }
}

void CGizmo::WrapCursor()
{
    QRect Geom = QApplication::desktop()->screenGeometry();
    QPoint CursorPos = QCursor::pos();

    // Horizontal
    if (CursorPos.x() == Geom.width() - 1)
    {
        QCursor::setPos(1, CursorPos.y());
        mWrapOffset.X += 2.f;
    }
    else if (CursorPos.x() == 0)
    {
        QCursor::setPos(Geom.width() - 2, CursorPos.y());
        mWrapOffset.X -= 2.f;
    }

    // Vertical
    CursorPos = QCursor::pos(); // Grab again to account for changes on horizontal wrap

    if (CursorPos.y() == Geom.height() - 1)
    {
        QCursor::setPos(CursorPos.x(), 1);
        mWrapOffset.Y -= 2.f;
    }
    else if (CursorPos.y() == 0)
    {
        QCursor::setPos(CursorPos.x(), Geom.height() - 2);
        mWrapOffset.Y += 2.f;
    }
}

// ************ STATIC MEMBER INITIALIZATION ************
bool CGizmo::smModelsLoaded = false;
CGizmo::SModelPart CGizmo::smTranslateModels[CGIZMO_TRANSLATE_NUM];
CGizmo::SModelPart CGizmo::smRotateModels[CGIZMO_ROTATE_NUM];
CGizmo::SModelPart CGizmo::smScaleModels[CGIZMO_SCALE_NUM];
