mirror of
https://github.com/AxioDL/PrimeWorldEditor.git
synced 2025-12-21 18:59:12 +00:00
685 lines
24 KiB
C++
685 lines
24 KiB
C++
#include "Editor/CGizmo.h"
|
|
|
|
#include <Common/Math/MathUtil.h>
|
|
#include <Core/GameProject/CResourceStore.h>
|
|
#include <Core/Render/CDrawUtil.h>
|
|
#include <Core/Render/CGraphics.h>
|
|
#include <Core/Render/CRenderer.h>
|
|
#include <Core/Resource/Model/CModel.h>
|
|
#include <Core/Resource/Model/SSurface.h>
|
|
|
|
#include <QApplication>
|
|
#include <QScreen>
|
|
|
|
struct SGizmoModelPart
|
|
{
|
|
FAxes ModelAxes;
|
|
bool EnableRayCast = false;
|
|
bool IsBillboard = false;
|
|
TResPtr<CModel> pModel;
|
|
|
|
SGizmoModelPart() : ModelAxes(EAxis::None) {};
|
|
SGizmoModelPart(FAxes Axes, bool RayCastOn, bool Billboard, TResPtr<CModel> _pModel)
|
|
: ModelAxes(Axes)
|
|
, EnableRayCast(RayCastOn)
|
|
, IsBillboard(Billboard)
|
|
, pModel(std::move(_pModel))
|
|
{
|
|
}
|
|
};
|
|
|
|
namespace
|
|
{
|
|
constinit bool smModelsLoaded = false;
|
|
std::array<SGizmoModelPart, CGIZMO_TRANSLATE_NUM> smTranslateModels;
|
|
std::array<SGizmoModelPart, CGIZMO_ROTATE_NUM> smRotateModels;
|
|
std::array<SGizmoModelPart, CGIZMO_SCALE_NUM> smScaleModels;
|
|
}
|
|
|
|
static void LoadModels()
|
|
{
|
|
if (!smModelsLoaded)
|
|
{
|
|
debugf("Loading transform gizmo models");
|
|
|
|
smTranslateModels[CGIZMO_TRANSLATE_X] = SGizmoModelPart(EAxis::X, true, false, gpEditorStore->LoadResource("editor/TranslateX.CMDL"));
|
|
smTranslateModels[CGIZMO_TRANSLATE_Y] = SGizmoModelPart(EAxis::Y, true, false, gpEditorStore->LoadResource("editor/TranslateY.CMDL"));
|
|
smTranslateModels[CGIZMO_TRANSLATE_Z] = SGizmoModelPart(EAxis::Z, true, false, gpEditorStore->LoadResource("editor/TranslateZ.CMDL"));
|
|
smTranslateModels[CGIZMO_TRANSLATE_LINES_XY] = SGizmoModelPart(EAxis::XY, true, false, gpEditorStore->LoadResource("editor/TranslateLinesXY.CMDL"));
|
|
smTranslateModels[CGIZMO_TRANSLATE_LINES_XZ] = SGizmoModelPart(EAxis::XZ, true, false, gpEditorStore->LoadResource("editor/TranslateLinesXZ.CMDL"));
|
|
smTranslateModels[CGIZMO_TRANSLATE_LINES_YZ] = SGizmoModelPart(EAxis::YZ, true, false, gpEditorStore->LoadResource("editor/TranslateLinesYZ.CMDL"));
|
|
smTranslateModels[CGIZMO_TRANSLATE_POLY_XY] = SGizmoModelPart(EAxis::XY, false, false, gpEditorStore->LoadResource("editor/TranslatePolyXY.CMDL"));
|
|
smTranslateModels[CGIZMO_TRANSLATE_POLY_XZ] = SGizmoModelPart(EAxis::XZ, false, false, gpEditorStore->LoadResource("editor/TranslatePolyXZ.CMDL"));
|
|
smTranslateModels[CGIZMO_TRANSLATE_POLY_YZ] = SGizmoModelPart(EAxis::YZ, false, false, gpEditorStore->LoadResource("editor/TranslatePolyYZ.CMDL"));
|
|
|
|
smRotateModels[CGIZMO_ROTATE_OUTLINE] = SGizmoModelPart(EAxis::None, true, true, gpEditorStore->LoadResource("editor/RotateClipOutline.CMDL"));
|
|
smRotateModels[CGIZMO_ROTATE_X] = SGizmoModelPart(EAxis::X, true, false, gpEditorStore->LoadResource("editor/RotateX.CMDL"));
|
|
smRotateModels[CGIZMO_ROTATE_Y] = SGizmoModelPart(EAxis::Y, true, false, gpEditorStore->LoadResource("editor/RotateY.CMDL"));
|
|
smRotateModels[CGIZMO_ROTATE_Z] = SGizmoModelPart(EAxis::Z, true, false, gpEditorStore->LoadResource("editor/RotateZ.CMDL"));
|
|
smRotateModels[CGIZMO_ROTATE_XYZ] = SGizmoModelPart(EAxis::XYZ, false, false, gpEditorStore->LoadResource("editor/RotateXYZ.CMDL"));
|
|
|
|
smScaleModels[CGIZMO_SCALE_X] = SGizmoModelPart(EAxis::X, true, false, gpEditorStore->LoadResource("editor/ScaleX.CMDL"));
|
|
smScaleModels[CGIZMO_SCALE_Y] = SGizmoModelPart(EAxis::Y, true, false, gpEditorStore->LoadResource("editor/ScaleY.CMDL"));
|
|
smScaleModels[CGIZMO_SCALE_Z] = SGizmoModelPart(EAxis::Z, true, false, gpEditorStore->LoadResource("editor/ScaleZ.CMDL"));
|
|
smScaleModels[CGIZMO_SCALE_LINES_XY] = SGizmoModelPart(EAxis::XY, true, false, gpEditorStore->LoadResource("editor/ScaleLinesXY.CMDL"));
|
|
smScaleModels[CGIZMO_SCALE_LINES_XZ] = SGizmoModelPart(EAxis::XZ, true, false, gpEditorStore->LoadResource("editor/ScaleLinesXZ.CMDL"));
|
|
smScaleModels[CGIZMO_SCALE_LINES_YZ] = SGizmoModelPart(EAxis::YZ, true, false, gpEditorStore->LoadResource("editor/ScaleLinesYZ.CMDL"));
|
|
smScaleModels[CGIZMO_SCALE_POLY_XY] = SGizmoModelPart(EAxis::XY, true, false, gpEditorStore->LoadResource("editor/ScalePolyXY.CMDL"));
|
|
smScaleModels[CGIZMO_SCALE_POLY_XZ] = SGizmoModelPart(EAxis::XZ, true, false, gpEditorStore->LoadResource("editor/ScalePolyXZ.CMDL"));
|
|
smScaleModels[CGIZMO_SCALE_POLY_YZ] = SGizmoModelPart(EAxis::YZ, true, false, gpEditorStore->LoadResource("editor/ScalePolyYZ.CMDL"));
|
|
smScaleModels[CGIZMO_SCALE_XYZ] = SGizmoModelPart(EAxis::XYZ, true, false, gpEditorStore->LoadResource("editor/ScaleXYZ.CMDL"));
|
|
|
|
smModelsLoaded = true;
|
|
}
|
|
}
|
|
|
|
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();
|
|
const auto* pPart = mpCurrentParts;
|
|
|
|
// Add all parts to renderer
|
|
for (uint32 iPart = 0; iPart < mNumCurrentParts; iPart++)
|
|
{
|
|
const 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 SGizmoModelPart array to use
|
|
if (ComponentIndex >= (int) mNumCurrentParts)
|
|
return;
|
|
|
|
auto* 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)
|
|
{
|
|
const CRay LocalRay = rkRay.Transformed(mTransform.Inverse());
|
|
const CRay BillRay = rkRay.Transformed(mBillboardTransform.Inverse());
|
|
|
|
// Do raycast on each model
|
|
const auto* pPart = mpCurrentParts;
|
|
|
|
struct SResult {
|
|
const SGizmoModelPart* pPart;
|
|
float Dist;
|
|
|
|
static bool DistanceLessThan(const SResult& left, const SResult& right)
|
|
{
|
|
return left.Dist < right.Dist;
|
|
}
|
|
};
|
|
std::list<SResult> Results;
|
|
|
|
for (uint32 iPart = 0; iPart < mNumCurrentParts; iPart++)
|
|
{
|
|
if (!pPart->EnableRayCast)
|
|
{
|
|
pPart++;
|
|
continue;
|
|
}
|
|
|
|
const CModel* pModel = pPart->pModel;
|
|
const 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)
|
|
{
|
|
Results.push_back({
|
|
.pPart = pPart,
|
|
.Dist = Dist,
|
|
});
|
|
}
|
|
}
|
|
|
|
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(SResult::DistanceLessThan);
|
|
|
|
const 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;
|
|
}
|
|
|
|
// ************ 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;
|
|
}
|
|
}
|