Files
PrimeWorldEditor/src/Editor/CGizmo.cpp
Lioncache b2bd0cd326 General: Update to Qt6
Migrate over to Qt 6 so that we can keep the UI toolkit pegged at the
current major version.

Unfortunately this also means we have to gut a small feature in the
progress dialogs, since the extras module doesn't exist in Qt6 anymore.

Few things of note:

QVector<> is internally an alias of QList now, so any changeover is due
to that to make the semantics a little clearer.

QtConcurrent requires arguments to be swapped on some invocations, and
discarding instances need to use the global thread pool instead.

fromStdList(), etc can be replaced with range constructors.

--no-angle and other commands are removed from newer versions of
windeployqt

QVariant::Invalid (and other type IDs) are deprecated and also break
existing functionality. Instead we can return default constructed
QVariants where applicable, which restores functionality that would be
broken if left as is (e.g. many list would straight up not populate or
have wonky size hinting).

The reason for this is that the QVariant(QVariant::Type) constructor
models a unique kind of internal QVariant state where it's considered
to be in an invalid state, but accessing the (supposedly) invalid state
will instead return a default constructed value of the internal type.

This kinda sucks because this means genuinely invalid states that would
warrant an assertion or other type of error would be silently ignored
and execution would continue on as normal, so this also enforces
correctness a little bit (on top of, well, fixing all the broken UI
controls).
2025-11-29 19:54:35 -05:00

653 lines
23 KiB
C++

#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 <QApplication>
#include <QScreen>
CGizmo::CGizmo()
{
LoadModels();
SetMode(EGizmoMode::Translate);
}
CGizmo::~CGizmo() = default;
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)
const FAxes PartAxes = pPart->ModelAxes;
const bool IsHighlighted = (PartAxes != EAxis::None) && ((mSelectedAxes & PartAxes) == pPart->ModelAxes);
const size_t 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::White();
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::Forward().Cross(BillDir);
float Angle = acosf(CVector3f::Forward().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::One());
const bool ModelBoxCheck = Math::RayBoxIntersection(rPartRay, AABox).first;
if (ModelBoxCheck)
{
bool Hit = false;
float Dist = 0.0f;
for (size_t iSurf = 0; iSurf < pModel->GetSurfaceCount(); iSurf++)
{
// Skip surface/box check - since we use lines the boxes might be too small
const SSurface* pSurf = pModel->GetSurface(iSurf);
const auto [intersects, distance] = pSurf->IntersectsRay(rPartRay, false, 0.05f);
if (intersects)
{
if (!Hit || distance < Dist)
Dist = distance;
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) {
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() const
{
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::Zero();
mSetOffset = false;
mTotalTranslation = CVector3f::Zero();
mTotalRotation = CVector3f::Zero();
mCurrentRotation = CQuaternion::Identity();
mTotalScale = CVector3f::One();
// 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();
const 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)
{
const CVector3f AxisA = (mSelectedAxes & EAxis::X ? mRotation.XAxis() : mRotation.YAxis());
const 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::primaryScreen()->geometry();
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::Zero();
mSetOffset = true;
return false;
}
else // Apply translation
{
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::Zero()))
mHasTransformed = true;
return mHasTransformed;
}
}
else
{
mDeltaTranslation = CVector3f::Zero();
return false;
}
}
// Rotate
if (mMode == EGizmoMode::Rotate)
{
// Choose rotation axis
CVector3f Axis;
if (mSelectedAxes & EAxis::X)
Axis = CVector3f::UnitX();
else if (mSelectedAxes & EAxis::Y)
Axis = CVector3f::UnitY();
else
Axis = CVector3f::UnitZ();
// 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::Identity();
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
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();
}
else // Three axes - use straight up
{
LineDir = CVector2f::Up();
}
float ScaleAmount = LineDir.Dot(MouseCoords + mWrapOffset - LineOrigin) * 5.f;
// Set offset
if (!mSetOffset)
{
mScaleOffset = -ScaleAmount;
mDeltaScale = CVector3f::One();
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::One();
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::One();
mIsTransforming = false;
}
void CGizmo::SetMode(EGizmoMode Mode)
{
mMode = Mode;
switch (Mode)
{
case EGizmoMode::Translate:
mpCurrentParts = smTranslateModels.data();
mNumCurrentParts = smTranslateModels.size();
mDeltaRotation = CQuaternion::Identity();
mDeltaScale = CVector3f::One();
break;
case EGizmoMode::Rotate:
mpCurrentParts = smRotateModels.data();
mNumCurrentParts = smRotateModels.size();
mDeltaTranslation = CVector3f::Zero();
mDeltaScale = CVector3f::One();
break;
case EGizmoMode::Scale:
mpCurrentParts = smScaleModels.data();
mNumCurrentParts = smScaleModels.size();
mDeltaTranslation = CVector3f::Zero();
mDeltaRotation = CQuaternion::Identity();
break;
default: break;
}
}
void CGizmo::SetTransformSpace(ETransformSpace Space)
{
mTransformSpace = Space;
if (Space == ETransformSpace::World)
mRotation = CQuaternion::Identity();
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::primaryScreen()->geometry();
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;
}
}