diff --git a/Common/CVector2f.cpp b/Common/CVector2f.cpp index ba4a526b..428a86d7 100644 --- a/Common/CVector2f.cpp +++ b/Common/CVector2f.cpp @@ -28,6 +28,26 @@ void CVector2f::Write(COutputStream &Output) Output.WriteFloat(y); } +float CVector2f::Magnitude() const +{ + return sqrtf(SquaredMagnitude()); +} + +float CVector2f::SquaredMagnitude() const +{ + return Dot(*this); +} + +CVector2f CVector2f::Normalized() const +{ + return *this / Magnitude(); +} + +float CVector2f::Dot(const CVector2f& other) const +{ + return ((x * other.x) + (y * other.y)); +} + CVector2f CVector2f::operator+(const CVector2f& src) const { CVector2f out; @@ -142,11 +162,21 @@ bool CVector2f::operator!=(const CVector2f& other) const return (!(*this == other)); } +CVector2f CVector2f::operator-() const +{ + return CVector2f(-x, -y); +} + float& CVector2f::operator[](long index) { return (&x)[index]; } +const float& CVector2f::operator[](long index) const +{ + return (&x)[index]; +} + // ************ STATIC MEMBER INITIALIZATION ************ const CVector2f CVector2f::skZero = CVector2f(0, 0); const CVector2f CVector2f::skOne = CVector2f(1, 1); diff --git a/Common/CVector2f.h b/Common/CVector2f.h index dc7c931a..c201d0ec 100644 --- a/Common/CVector2f.h +++ b/Common/CVector2f.h @@ -14,6 +14,11 @@ public: CVector2f(CInputStream& Input); void Write(COutputStream& Output); + float Magnitude() const; + float SquaredMagnitude() const; + CVector2f Normalized() const; + float Dot(const CVector2f& other) const; + CVector2f operator+(const CVector2f& other) const; CVector2f operator-(const CVector2f& other) const; CVector2f operator*(const CVector2f& other) const; @@ -32,7 +37,9 @@ public: void operator/=(const float other); bool operator==(const CVector2f& other) const; bool operator!=(const CVector2f& other) const; + CVector2f operator-() const; float& operator[](long index); + const float& operator[](long index) const; // Static Members static const CVector2f skZero; diff --git a/Common/CVector3f.cpp b/Common/CVector3f.cpp index 91857477..d79e9c6e 100644 --- a/Common/CVector3f.cpp +++ b/Common/CVector3f.cpp @@ -65,7 +65,7 @@ float CVector3f::SquaredMagnitude() const CVector3f CVector3f::Normalized() const { - return *this / Magnitude();; + return *this / Magnitude(); } float CVector3f::Dot(const CVector3f& other) const diff --git a/Core/CCamera.cpp b/Core/CCamera.cpp index 321d1a37..ce25638b 100644 --- a/Core/CCamera.cpp +++ b/Core/CCamera.cpp @@ -153,7 +153,14 @@ CRay CCamera::CastRay(CVector2f DeviceCoords) void CCamera::LoadMatrices() { CGraphics::sMVPBlock.ViewMatrix = ViewMatrix(); - CGraphics::sMVPBlock.ProjectionMatrix = ProjectionMatrix(); + CGraphics::sMVPBlock.ProjectionMatrix = ProjectionMatrix(); + CGraphics::UpdateMVPBlock(); +} + +void CCamera::LoadRotationOnlyMatrices() +{ + CGraphics::sMVPBlock.ViewMatrix = RotationOnlyViewMatrix(); + CGraphics::sMVPBlock.ProjectionMatrix = ProjectionMatrix(); CGraphics::UpdateMVPBlock(); } @@ -187,6 +194,17 @@ const CMatrix4f& CCamera::ViewMatrix() return mCachedViewMatrix; } +const CMatrix4f& CCamera::RotationOnlyViewMatrix() +{ + if (mViewOutdated) + CalculateView(); + + return CMatrix4f(mCachedViewMatrix[0][0], mCachedViewMatrix[0][1], mCachedViewMatrix[0][2], 0.f, + mCachedViewMatrix[1][0], mCachedViewMatrix[1][1], mCachedViewMatrix[1][2], 0.f, + mCachedViewMatrix[2][0], mCachedViewMatrix[2][1], mCachedViewMatrix[2][2], 0.f, + mCachedViewMatrix[3][0], mCachedViewMatrix[3][1], mCachedViewMatrix[3][2], 1.f); +} + const CMatrix4f& CCamera::ProjectionMatrix() { if (mProjectionOutdated) diff --git a/Core/CCamera.h b/Core/CCamera.h index 2d2a2ead..7c6cd595 100644 --- a/Core/CCamera.h +++ b/Core/CCamera.h @@ -45,6 +45,7 @@ public: void ProcessMouseInput(EKeyInputs KeyFlags, EMouseInputs MouseFlags, float XMovement, float YMovement); CRay CastRay(CVector2f DeviceCoords); void LoadMatrices(); + void LoadRotationOnlyMatrices(); // Getters CVector3f Position() const; @@ -52,6 +53,7 @@ public: float GetYaw() const; float GetPitch() const; const CMatrix4f& ViewMatrix(); + const CMatrix4f& RotationOnlyViewMatrix(); const CMatrix4f& ProjectionMatrix(); // Setters diff --git a/Core/CRenderer.cpp b/Core/CRenderer.cpp index 9e5623dc..d39e4583 100644 --- a/Core/CRenderer.cpp +++ b/Core/CRenderer.cpp @@ -245,6 +245,9 @@ void CRenderer::RenderBloom() glBlendFunc(GL_DST_ALPHA, GL_ZERO); CDrawUtil::DrawSquare(); } + + // Clean up + glEnable(GL_DEPTH_TEST); } void CRenderer::RenderSky(CModel *pSkyboxModel, CVector3f CameraPosition) diff --git a/Scene/CSceneNode.cpp b/Scene/CSceneNode.cpp index 650a2281..9d931d3d 100644 --- a/Scene/CSceneNode.cpp +++ b/Scene/CSceneNode.cpp @@ -201,7 +201,15 @@ void CSceneNode::Translate(const CVector3f& translation, ETransformSpace transfo void CSceneNode::Rotate(const CQuaternion& rotation, ETransformSpace transformSpace) { - mRotation *= rotation; + switch (transformSpace) + { + case eWorldTransform: + mRotation = rotation * mRotation; + break; + case eLocalTransform: + mRotation *= rotation; + break; + } MarkTransformChanged(); } @@ -370,6 +378,30 @@ void CSceneNode::SetName(const std::string& Name) mName = Name; } +void CSceneNode::SetPosition(const CVector3f& position) +{ + mPosition = position; + MarkTransformChanged(); +} + +void CSceneNode::SetRotation(const CQuaternion& rotation) +{ + mRotation = rotation; + MarkTransformChanged(); +} + +void CSceneNode::SetRotation(const CVector3f& rotEuler) +{ + mRotation = CQuaternion::FromEuler(rotEuler); + MarkTransformChanged(); +} + +void CSceneNode::SetScale(const CVector3f& scale) +{ + mScale = scale; + MarkTransformChanged(); +} + void CSceneNode::SetMouseHovering(bool Hovering) { mMouseHovering = Hovering; diff --git a/Scene/CSceneNode.h b/Scene/CSceneNode.h index 933689d3..9fb38b46 100644 --- a/Scene/CSceneNode.h +++ b/Scene/CSceneNode.h @@ -98,6 +98,10 @@ public: // Setters void SetName(const std::string& Name); + void SetPosition(const CVector3f& position); + void SetRotation(const CQuaternion& rotation); + void SetRotation(const CVector3f& rotEuler); + void SetScale(const CVector3f& scale); void SetMouseHovering(bool Hovering); void SetSelected(bool Selected); void SetVisible(bool Visible); diff --git a/UI/CGizmo.cpp b/UI/CGizmo.cpp index ec21633d..f8388e73 100644 --- a/UI/CGizmo.cpp +++ b/UI/CGizmo.cpp @@ -3,6 +3,10 @@ #include #include #include +#include + +#include +#include #include CGizmo::CGizmo() @@ -11,8 +15,13 @@ CGizmo::CGizmo() SetMode(eTranslate); mSelectedAxes = eNone; + mTransformSpace = eWorldTransform; mGizmoSize = 1.f; mCameraDist = 0.f; + mIsTransforming = false; + mHasTransformed = false; + mWrapOffset = 0.f; + mEnableCursorWrap = true; mPosition = CVector3f::skZero; mRotation = CQuaternion::skIdentity; @@ -43,7 +52,8 @@ void CGizmo::AddToRenderer(CRenderer *pRenderer) CModel *pModel = pPart->pModel; // Determine whether to use the mat set for regular (0) or highlight (1) - bool isHighlighted = (mSelectedAxes & pPart->modelAxes) == pPart->modelAxes; + EGizmoAxes partAxes = pPart->modelAxes; + bool isHighlighted = (partAxes != eNone) && ((mSelectedAxes & partAxes) == pPart->modelAxes); u32 setID = (isHighlighted ? 1 : 0); // Add to renderer... @@ -58,24 +68,21 @@ void CGizmo::AddToRenderer(CRenderer *pRenderer) void CGizmo::DrawAsset(ERenderOptions options, u32 asset) { - CGraphics::sMVPBlock.ModelMatrix = mTransform.ToMatrix4f(); - CGraphics::UpdateMVPBlock(); - // Determine which SModelPart array to use if (asset >= mNumCurrentParts) return; SModelPart *pPart = mpCurrentParts; - // Draw model - bool isHighlighted = (mSelectedAxes & pPart[asset].modelAxes) == pPart[asset].modelAxes; - u32 setID = (isHighlighted ? 1 : 0); - pPart[asset].pModel->Draw((ERenderOptions) 0, setID); -} - -void CGizmo::DrawRotationOutline() -{ - CGraphics::sMVPBlock.ModelMatrix = mBillboardTransform.ToMatrix4f(); + // Set model matrix + CGraphics::sMVPBlock.ModelMatrix = (pPart[asset].isBillboard ? mBillboardTransform.ToMatrix4f() : mTransform.ToMatrix4f()); CGraphics::UpdateMVPBlock(); - smRotateClipOutline.pModel->Draw((ERenderOptions) 0, 0); + + // Choose material set + EGizmoAxes partAxes = pPart[asset].modelAxes; + bool isHighlighted = (partAxes != eNone) && ((mSelectedAxes & partAxes) == pPart[asset].modelAxes); + u32 setID = (isHighlighted ? 1 : 0); + + // Draw model + pPart[asset].pModel->Draw((ERenderOptions) 0, setID); } void CGizmo::IncrementSize() @@ -99,15 +106,17 @@ void CGizmo::DecrementSize() void CGizmo::UpdateForCamera(const CCamera &camera) { CVector3f camPos = camera.Position(); - mCameraDist = mPosition.Distance(camPos); mFlipScaleX = camPos.x < mPosition.x; mFlipScaleY = camPos.y < mPosition.y; mFlipScaleZ = camPos.z < mPosition.z; + if ((!mIsTransforming) || (mMode != eTranslate)) + mCameraDist = mPosition.Distance(camPos); + // todo: make this cleaner... CVector3f billDir = (mPosition - camPos).Normalized(); CVector3f axis = CVector3f::skForward.Cross(billDir); - float angle = acos(CVector3f::skForward.Dot(billDir)); + float angle = acosf(CVector3f::skForward.Dot(billDir)); angle = 180 + (angle * 180 / 3.14159265358979323846f); mBillboardRotation = CQuaternion::FromAxisAngle(angle, axis); } @@ -116,6 +125,7 @@ bool CGizmo::CheckSelectedAxes(const CRay &ray) { // todo: fix raycasting for rotate gizmo; currently it can hit the invisible back side of the model CRay localRay = ray.Transformed(mTransform.Inverse()); + CRay billRay = ray.Transformed(mBillboardTransform.Inverse()); // Do raycast on each model SModelPart *pPart = mpCurrentParts; @@ -135,11 +145,12 @@ bool CGizmo::CheckSelectedAxes(const CRay &ray) } CModel *pModel = pPart->pModel; + CRay& partRay = (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(localRay, AABox).first; + bool modelBoxCheck = Math::RayBoxIntersection(partRay, AABox).first; if (modelBoxCheck) { @@ -150,7 +161,7 @@ bool CGizmo::CheckSelectedAxes(const CRay &ray) { // Skip surface/box check - since we use lines the boxes might be too small SSurface *pSurf = pModel->GetSurface(iSurf); - std::pair surfCheck = pSurf->IntersectsRay(localRay, 0.05f); + std::pair surfCheck = pSurf->IntersectsRay(partRay, 0.05f); if (surfCheck.first) { @@ -186,8 +197,11 @@ bool CGizmo::CheckSelectedAxes(const CRay &ray) return (a.dist < b.dist); }); + CRay& partRay = (pPart->isBillboard ? billRay : localRay); mSelectedAxes = results.front().pPart->modelAxes; - return true; + mHitPoint = mTransform * partRay.PointOnRay(results.front().dist); + + return (mSelectedAxes != eNone); } u32 CGizmo::NumSelectedAxes() @@ -207,14 +221,43 @@ void CGizmo::ResetSelectedAxes() void CGizmo::StartTransform() { + mIsTransforming = true; + mHasTransformed = false; + mWrapOffset = CVector2f::skZero; mSetOffset = false; mTotalTranslation = CVector3f::skZero; - mTotalRotation = CQuaternion::skIdentity; + mTotalRotation = CVector3f::skZero; + mCurrentRotation = CQuaternion::skIdentity; mTotalScale = CVector3f::skOne; + + // Set rotation clockwise direction + if (mMode == eRotate) + { + CVector3f axis; + if (mSelectedAxes & eX) axis = mRotation.XAxis(); + else if (mSelectedAxes & eY) axis = mRotation.YAxis(); + else axis = mRotation.ZAxis(); + + CVector3f gizmoToHit = (mHitPoint - mPosition).Normalized(); + mClockwiseDir = axis.Cross(gizmoToHit); + } } -bool CGizmo::TransformFromInput(const CRay& ray, const CCamera& camera) +bool CGizmo::TransformFromInput(const CRay& ray, CCamera& camera) { + // Wrap cursor (this has no effect until the next time this function is called) + if (mEnableCursorWrap && (mMode != eTranslate)) + 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 == eTranslate) { // Create translate plane @@ -273,8 +316,16 @@ bool CGizmo::TransformFromInput(const CRay& ray, const CCamera& camera) else { mDeltaTranslation = mRotation.Inverse() * (newPos - mPosition + mTranslateOffset); + if (!(mSelectedAxes & eX)) mDeltaTranslation.x = 0.f; + if (!(mSelectedAxes & eY)) mDeltaTranslation.y = 0.f; + if (!(mSelectedAxes & eZ)) mDeltaTranslation.z = 0.f; + mTotalTranslation += mDeltaTranslation; - mPosition = newPos + mTranslateOffset; + mPosition += mRotation * mDeltaTranslation; + + if (!mHasTransformed && (mDeltaTranslation != CVector3f::skZero)) + mHasTransformed = true; + return true; } } @@ -286,11 +337,62 @@ bool CGizmo::TransformFromInput(const CRay& ray, const CCamera& camera) } } + // Rotate + else if (mMode == eRotate) + { + // Choose rotation axis + CVector3f axis; + if (mSelectedAxes & eX) axis = CVector3f::skUnitX; + else if (mSelectedAxes & eY) axis = CVector3f::skUnitY; + else axis = CVector3f::skUnitZ; + + // Convert hit point + clockwise direction into a line in screen space + // Clockwise direction is set in StartTransform(). Is there a cleaner way to calculate the direction? + CMatrix4f VP = camera.ViewMatrix().Transpose() * camera.ProjectionMatrix().Transpose(); + CVector2f lineOrigin = (mHitPoint * VP).xy(); + CVector2f lineDir = (((mHitPoint + mClockwiseDir) * 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(rotAmount, axis); + mDeltaRotation = mCurrentRotation * oldRot.Inverse(); + + if (mTransformSpace == eLocalTransform) + mRotation *= mDeltaRotation; + + // Add to total + if (mSelectedAxes & eX) mTotalRotation.x = rotAmount; + else if (mSelectedAxes & eY) mTotalRotation.y = rotAmount; + else mTotalRotation.z = rotAmount; + + if (!mHasTransformed && (rotAmount != 0.f)) + mHasTransformed = true; + + return true; + } + return false; } void CGizmo::EndTransform() { + mIsTransforming = false; +} + +bool CGizmo::HasTransformed() +{ + return mHasTransformed; } CGizmo::EGizmoMode CGizmo::Mode() @@ -313,6 +415,36 @@ CVector3f CGizmo::TotalTranslation() return mTotalTranslation; } +CQuaternion CGizmo::Rotation() +{ + return mRotation; +} + +CQuaternion CGizmo::DeltaRotation() +{ + return mDeltaRotation; +} + +CVector3f CGizmo::TotalRotation() +{ + return mTotalRotation; +} + +CVector3f CGizmo::Scale() +{ + return mScale; +} + +CVector3f CGizmo::DeltaScale() +{ + return mDeltaScale; +} + +CVector3f CGizmo::TotalScale() +{ + return mTotalScale; +} + void CGizmo::SetMode(EGizmoMode mode) { mMode = mode; @@ -328,7 +460,7 @@ void CGizmo::SetMode(EGizmoMode mode) case eRotate: mpCurrentParts = smRotateModels; - mNumCurrentParts = 4; + mNumCurrentParts = 5; mDeltaTranslation = CVector3f::skZero; mDeltaScale = CVector3f::skOne; break; @@ -342,47 +474,59 @@ void CGizmo::SetMode(EGizmoMode mode) } } +void CGizmo::SetTransformSpace(ETransformSpace space) +{ + mTransformSpace = space; +} + void CGizmo::SetPosition(const CVector3f& position) { mPosition = position; } -void CGizmo::SetOrientation(const CQuaternion& orientation) +void CGizmo::SetRotation(const CQuaternion& orientation) { mRotation = orientation; } +void CGizmo::EnableCursorWrap(bool wrap) +{ + mEnableCursorWrap = wrap; +} + // ************ PRIVATE STATIC ************ void CGizmo::LoadModels() { if (!smModelsLoaded) { - smTranslateModels[CGIZMO_TRANSLATE_X] = SModelPart(eX, true, (CModel*) gResCache.GetResource("../resources/editor/TranslateX.CMDL")); - smTranslateModels[CGIZMO_TRANSLATE_Y] = SModelPart(eY, true, (CModel*) gResCache.GetResource("../resources/editor/TranslateY.CMDL")); - smTranslateModels[CGIZMO_TRANSLATE_Z] = SModelPart(eZ, true, (CModel*) gResCache.GetResource("../resources/editor/TranslateZ.CMDL")); - smTranslateModels[CGIZMO_TRANSLATE_LINES_XY] = SModelPart(eXY, true, (CModel*) gResCache.GetResource("../resources/editor/TranslateLinesXY.CMDL")); - smTranslateModels[CGIZMO_TRANSLATE_LINES_XZ] = SModelPart(eXZ, true, (CModel*) gResCache.GetResource("../resources/editor/TranslateLinesXZ.CMDL")); - smTranslateModels[CGIZMO_TRANSLATE_LINES_YZ] = SModelPart(eYZ, true, (CModel*) gResCache.GetResource("../resources/editor/TranslateLinesYZ.CMDL")); - smTranslateModels[CGIZMO_TRANSLATE_POLY_XY] = SModelPart(eXY, false, (CModel*) gResCache.GetResource("../resources/editor/TranslatePolyXY.CMDL")); - smTranslateModels[CGIZMO_TRANSLATE_POLY_XZ] = SModelPart(eXZ, false, (CModel*) gResCache.GetResource("../resources/editor/TranslatePolyXZ.CMDL")); - smTranslateModels[CGIZMO_TRANSLATE_POLY_YZ] = SModelPart(eYZ, false, (CModel*) gResCache.GetResource("../resources/editor/TranslatePolyYZ.CMDL")); + Log::Write("Loading transform gizmo models"); - smRotateModels[CGIZMO_ROTATE_X] = SModelPart(eX, true, (CModel*) gResCache.GetResource("../resources/editor/RotateX.CMDL")); - smRotateModels[CGIZMO_ROTATE_Y] = SModelPart(eY, true, (CModel*) gResCache.GetResource("../resources/editor/RotateY.CMDL")); - smRotateModels[CGIZMO_ROTATE_Z] = SModelPart(eZ, true, (CModel*) gResCache.GetResource("../resources/editor/RotateZ.CMDL")); - smRotateModels[CGIZMO_ROTATE_XYZ] = SModelPart(eXYZ, false, (CModel*) gResCache.GetResource("../resources/editor/RotateXYZ.CMDL")); - smRotateClipOutline = SModelPart(eNone, false, (CModel*) gResCache.GetResource("../resources/editor/RotateClipOutline.CMDL")); + smTranslateModels[CGIZMO_TRANSLATE_X] = SModelPart(eX, true, false, (CModel*) gResCache.GetResource("../resources/editor/TranslateX.CMDL")); + smTranslateModels[CGIZMO_TRANSLATE_Y] = SModelPart(eY, true, false, (CModel*) gResCache.GetResource("../resources/editor/TranslateY.CMDL")); + smTranslateModels[CGIZMO_TRANSLATE_Z] = SModelPart(eZ, true, false, (CModel*) gResCache.GetResource("../resources/editor/TranslateZ.CMDL")); + smTranslateModels[CGIZMO_TRANSLATE_LINES_XY] = SModelPart(eXY, true, false, (CModel*) gResCache.GetResource("../resources/editor/TranslateLinesXY.CMDL")); + smTranslateModels[CGIZMO_TRANSLATE_LINES_XZ] = SModelPart(eXZ, true, false, (CModel*) gResCache.GetResource("../resources/editor/TranslateLinesXZ.CMDL")); + smTranslateModels[CGIZMO_TRANSLATE_LINES_YZ] = SModelPart(eYZ, true, false, (CModel*) gResCache.GetResource("../resources/editor/TranslateLinesYZ.CMDL")); + smTranslateModels[CGIZMO_TRANSLATE_POLY_XY] = SModelPart(eXY, false, false, (CModel*) gResCache.GetResource("../resources/editor/TranslatePolyXY.CMDL")); + smTranslateModels[CGIZMO_TRANSLATE_POLY_XZ] = SModelPart(eXZ, false, false, (CModel*) gResCache.GetResource("../resources/editor/TranslatePolyXZ.CMDL")); + smTranslateModels[CGIZMO_TRANSLATE_POLY_YZ] = SModelPart(eYZ, false, false, (CModel*) gResCache.GetResource("../resources/editor/TranslatePolyYZ.CMDL")); - smScaleModels[CGIZMO_SCALE_X] = SModelPart(eX, true, (CModel*) gResCache.GetResource("../resources/editor/ScaleX.CMDL")); - smScaleModels[CGIZMO_SCALE_Y] = SModelPart(eY, true, (CModel*) gResCache.GetResource("../resources/editor/ScaleY.CMDL")); - smScaleModels[CGIZMO_SCALE_Z] = SModelPart(eZ, true, (CModel*) gResCache.GetResource("../resources/editor/ScaleZ.CMDL")); - smScaleModels[CGIZMO_SCALE_LINES_XY] = SModelPart(eXY, true, (CModel*) gResCache.GetResource("../resources/editor/ScaleLinesXY.CMDL")); - smScaleModels[CGIZMO_SCALE_LINES_XZ] = SModelPart(eXZ, true, (CModel*) gResCache.GetResource("../resources/editor/ScaleLinesXZ.CMDL")); - smScaleModels[CGIZMO_SCALE_LINES_YZ] = SModelPart(eYZ, true, (CModel*) gResCache.GetResource("../resources/editor/ScaleLinesYZ.CMDL")); - smScaleModels[CGIZMO_SCALE_POLY_XY] = SModelPart(eXY, false, (CModel*) gResCache.GetResource("../resources/editor/ScalePolyXY.CMDL")); - smScaleModels[CGIZMO_SCALE_POLY_XZ] = SModelPart(eXZ, false, (CModel*) gResCache.GetResource("../resources/editor/ScalePolyXZ.CMDL")); - smScaleModels[CGIZMO_SCALE_POLY_YZ] = SModelPart(eYZ, false, (CModel*) gResCache.GetResource("../resources/editor/ScalePolyYZ.CMDL")); - smScaleModels[CGIZMO_SCALE_XYZ] = SModelPart(eXYZ, true, (CModel*) gResCache.GetResource("../resources/editor/ScaleXYZ.CMDL")); + smRotateModels[CGIZMO_ROTATE_OUTLINE] = SModelPart(eNone, true, true, (CModel*) gResCache.GetResource("../resources/editor/RotateClipOutline.CMDL")); + smRotateModels[CGIZMO_ROTATE_X] = SModelPart(eX, true, false, (CModel*) gResCache.GetResource("../resources/editor/RotateX.CMDL")); + smRotateModels[CGIZMO_ROTATE_Y] = SModelPart(eY, true, false, (CModel*) gResCache.GetResource("../resources/editor/RotateY.CMDL")); + smRotateModels[CGIZMO_ROTATE_Z] = SModelPart(eZ, true, false, (CModel*) gResCache.GetResource("../resources/editor/RotateZ.CMDL")); + smRotateModels[CGIZMO_ROTATE_XYZ] = SModelPart(eXYZ, false, false, (CModel*) gResCache.GetResource("../resources/editor/RotateXYZ.CMDL")); + + smScaleModels[CGIZMO_SCALE_X] = SModelPart(eX, true, false, (CModel*) gResCache.GetResource("../resources/editor/ScaleX.CMDL")); + smScaleModels[CGIZMO_SCALE_Y] = SModelPart(eY, true, false, (CModel*) gResCache.GetResource("../resources/editor/ScaleY.CMDL")); + smScaleModels[CGIZMO_SCALE_Z] = SModelPart(eZ, true, false, (CModel*) gResCache.GetResource("../resources/editor/ScaleZ.CMDL")); + smScaleModels[CGIZMO_SCALE_LINES_XY] = SModelPart(eXY, true, false, (CModel*) gResCache.GetResource("../resources/editor/ScaleLinesXY.CMDL")); + smScaleModels[CGIZMO_SCALE_LINES_XZ] = SModelPart(eXZ, true, false, (CModel*) gResCache.GetResource("../resources/editor/ScaleLinesXZ.CMDL")); + smScaleModels[CGIZMO_SCALE_LINES_YZ] = SModelPart(eYZ, true, false, (CModel*) gResCache.GetResource("../resources/editor/ScaleLinesYZ.CMDL")); + smScaleModels[CGIZMO_SCALE_POLY_XY] = SModelPart(eXY, false, false, (CModel*) gResCache.GetResource("../resources/editor/ScalePolyXY.CMDL")); + smScaleModels[CGIZMO_SCALE_POLY_XZ] = SModelPart(eXZ, false, false, (CModel*) gResCache.GetResource("../resources/editor/ScalePolyXZ.CMDL")); + smScaleModels[CGIZMO_SCALE_POLY_YZ] = SModelPart(eYZ, false, false, (CModel*) gResCache.GetResource("../resources/editor/ScalePolyYZ.CMDL")); + smScaleModels[CGIZMO_SCALE_XYZ] = SModelPart(eXYZ, true, false, (CModel*) gResCache.GetResource("../resources/editor/ScaleXYZ.CMDL")); smModelsLoaded = true; } @@ -395,10 +539,10 @@ void CGizmo::UpdateTransform() // Rotation and position values are just saved directly mScale = mGizmoSize * (mCameraDist / 10.f); - // Scale also factors in delta scale + axis flip if mode is Scale. + // Scale also factors in total scale + axis flip if mode is Scale. if (mMode == eScale) { - mScale *= mDeltaScale; + mScale *= mTotalScale; if (mFlipScaleX) mScale.x = -mScale.x; if (mFlipScaleY) mScale.y = -mScale.y; @@ -421,9 +565,40 @@ void CGizmo::UpdateTransform() } } +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[9]; -CGizmo::SModelPart CGizmo::smRotateModels[4]; +CGizmo::SModelPart CGizmo::smRotateModels[5]; CGizmo::SModelPart CGizmo::smScaleModels[10]; -CGizmo::SModelPart CGizmo::smRotateClipOutline; diff --git a/UI/CGizmo.h b/UI/CGizmo.h index fa5deadd..d32e1a54 100644 --- a/UI/CGizmo.h +++ b/UI/CGizmo.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -19,10 +20,11 @@ #define CGIZMO_TRANSLATE_POLY_XY 6 #define CGIZMO_TRANSLATE_POLY_XZ 7 #define CGIZMO_TRANSLATE_POLY_YZ 8 -#define CGIZMO_ROTATE_X 0 -#define CGIZMO_ROTATE_Y 1 -#define CGIZMO_ROTATE_Z 2 -#define CGIZMO_ROTATE_XYZ 3 +#define CGIZMO_ROTATE_OUTLINE 0 +#define CGIZMO_ROTATE_X 1 +#define CGIZMO_ROTATE_Y 2 +#define CGIZMO_ROTATE_Z 3 +#define CGIZMO_ROTATE_XYZ 4 #define CGIZMO_SCALE_X 0 #define CGIZMO_SCALE_Y 1 #define CGIZMO_SCALE_Z 2 @@ -57,10 +59,16 @@ public: private: EGizmoMode mMode; EGizmoAxes mSelectedAxes; + ETransformSpace mTransformSpace; + CVector3f mHitPoint; CTransform4f mBillboardTransform; CQuaternion mBillboardRotation; float mGizmoSize; float mCameraDist; + bool mIsTransforming; + bool mHasTransformed; + CVector2f mWrapOffset; + bool mEnableCursorWrap; CTransform4f mTransform; CVector3f mPosition; @@ -68,7 +76,8 @@ private: CVector3f mTotalTranslation; CQuaternion mRotation; CQuaternion mDeltaRotation; - CQuaternion mTotalRotation; + CQuaternion mCurrentRotation; + CVector3f mTotalRotation; // This is a CVector3f because this value displays on the UI and a quat would cause rollover CVector3f mScale; CVector3f mDeltaScale; CVector3f mTotalScale; @@ -77,21 +86,24 @@ private: bool mFlipScaleZ; CPlane mTranslatePlane; - CVector3f mLastTranslatePosition; CVector3f mTranslateOffset; + float mRotateOffset; bool mSetOffset; + CVector3f mRotateHitPoint; + CVector3f mClockwiseDir; struct SModelPart { EGizmoAxes modelAxes; bool enableRayCast; + bool isBillboard; CModel *pModel; CToken modelToken; SModelPart() {} - SModelPart(EGizmoAxes axes, bool rayCastOn, CModel *_pModel) : - modelAxes(axes), enableRayCast(rayCastOn), pModel(_pModel), modelToken(_pModel) {} + SModelPart(EGizmoAxes axes, bool rayCastOn, bool billboard, CModel *_pModel) : + modelAxes(axes), enableRayCast(rayCastOn), isBillboard(billboard), pModel(_pModel), modelToken(_pModel) {} }; SModelPart *mpCurrentParts; u32 mNumCurrentParts; @@ -99,9 +111,8 @@ private: // Static static bool smModelsLoaded; static SModelPart smTranslateModels[9]; - static SModelPart smRotateModels[4]; + static SModelPart smRotateModels[5]; static SModelPart smScaleModels[10]; - static SModelPart smRotateClipOutline; public: CGizmo(); @@ -109,7 +120,6 @@ public: void AddToRenderer(CRenderer *pRenderer); void DrawAsset(ERenderOptions options, u32 asset); - void DrawRotationOutline(); void IncrementSize(); void DecrementSize(); @@ -118,20 +128,30 @@ public: u32 NumSelectedAxes(); void ResetSelectedAxes(); void StartTransform(); - bool TransformFromInput(const CRay& ray, const CCamera& camera); + bool TransformFromInput(const CRay& ray, CCamera& camera); void EndTransform(); + bool HasTransformed(); EGizmoMode Mode(); CVector3f Position(); CVector3f DeltaTranslation(); CVector3f TotalTranslation(); + CQuaternion Rotation(); + CQuaternion DeltaRotation(); + CVector3f TotalRotation(); + CVector3f Scale(); + CVector3f DeltaScale(); + CVector3f TotalScale(); void SetMode(EGizmoMode mode); + void SetTransformSpace(ETransformSpace space); void SetPosition(const CVector3f& position); - void SetOrientation(const CQuaternion& orientation); + void SetRotation(const CQuaternion& orientation); + void EnableCursorWrap(bool wrap); // Protected protected: void UpdateTransform(); + void WrapCursor(); // Private Static private: diff --git a/UI/CWorldEditor.cpp b/UI/CWorldEditor.cpp index 7263c21d..91915fcf 100644 --- a/UI/CWorldEditor.cpp +++ b/UI/CWorldEditor.cpp @@ -35,7 +35,7 @@ CWorldEditor::CWorldEditor(QWidget *parent) : mShowGizmo = false; mGizmoHovering = false; mGizmoTransforming = false; - mUpdateUILater = false; + mGizmoUIOutdated = true; mFrameCount = 0; mFPSTimer.Start(); @@ -144,15 +144,20 @@ void CWorldEditor::SetArea(CWorld *pWorld, CGameArea *pArea) ui->InstancesTabContents->SetMaster(pMaster); } -void CWorldEditor::ViewportRayCast(CRay Ray) +void CWorldEditor::ViewportRayCast() { if (!ui->MainViewport->IsMouseInputActive()) { + // Cast ray + CVector2f mouseCoords = ui->MainViewport->MouseDeviceCoordinates(); + CCamera camera = ui->MainViewport->Camera(); + CRay ray = camera.CastRay(mouseCoords); + if (!mGizmoTransforming) { // Gizmo hover check if (mShowGizmo && !mSelectedNodes.empty()) - mGizmoHovering = mGizmo.CheckSelectedAxes(Ray); + mGizmoHovering = mGizmo.CheckSelectedAxes(ray); else { mGizmoHovering = false; @@ -160,7 +165,7 @@ void CWorldEditor::ViewportRayCast(CRay Ray) } // Scene ray check - SRayIntersection Result = mpSceneManager->SceneRayCast(Ray); + SRayIntersection Result = mpSceneManager->SceneRayCast(ray); if (Result.Hit) { @@ -170,26 +175,44 @@ void CWorldEditor::ViewportRayCast(CRay Ray) mpHoverNode = Result.pNode; mpHoverNode->SetMouseHovering(true); - mHoverPoint = Ray.PointOnRay(Result.Distance); + mHoverPoint = ray.PointOnRay(Result.Distance); } else ResetHover(); } else { - bool moved = mGizmo.TransformFromInput(Ray, ui->MainViewport->Camera()); + bool transformed = mGizmo.TransformFromInput(ray, ui->MainViewport->Camera()); - if (moved) + if (transformed) { - CVector3f delta = mGizmo.DeltaTranslation(); - - for (auto it = mSelectedNodes.begin(); it != mSelectedNodes.end(); it++) + switch (mGizmo.Mode()) { - (*it)->Translate(delta, mTransformSpace); - (*it)->BuildLightList(this->mpArea); + case CGizmo::eTranslate: + { + CVector3f delta = mGizmo.DeltaTranslation(); + + for (auto it = mSelectedNodes.begin(); it != mSelectedNodes.end(); it++) + { + (*it)->Translate(delta, mTransformSpace); + (*it)->BuildLightList(this->mpArea); + } + break; } + + case CGizmo::eRotate: + { + CQuaternion delta = mGizmo.DeltaRotation(); + + for (auto it = mSelectedNodes.begin(); it != mSelectedNodes.end(); it++) + (*it)->Rotate(delta, mTransformSpace); + + break; + } + } + RecalculateSelectionBounds(); - mUpdateUILater = true; + mGizmoUIOutdated = true; } } } @@ -293,7 +316,9 @@ void CWorldEditor::ViewportMouseRelease(QMouseEvent *pEvent) // Gizmo transform stop if (mGizmoTransforming) { + mGizmo.EndTransform(); mGizmoTransforming = false; + mGizmoUIOutdated = true; } // Object selection/deselection @@ -346,7 +371,7 @@ void CWorldEditor::ViewportPreRender() { // Perform raycast if (ui->MainViewport->underMouse()) - ViewportRayCast(ui->MainViewport->CastRay()); + ViewportRayCast(); else ResetHover(); @@ -373,13 +398,11 @@ void CWorldEditor::ViewportRender(CCamera& Camera) mpRenderer->ClearDepthBuffer(); Camera.LoadMatrices(); - if (!mGizmoTransforming) mGizmo.UpdateForCamera(Camera); + mGizmo.UpdateForCamera(Camera); mGizmo.AddToRenderer(mpRenderer); - if (mGizmo.Mode() == CGizmo::eRotate) - mGizmo.DrawRotationOutline(); - mpRenderer->RenderBuckets(Camera); + mpRenderer->ClearDepthBuffer(); } mpRenderer->EndFrame(); @@ -393,11 +416,7 @@ void CWorldEditor::ViewportPostRender() UpdateCursor(); UpdateStatusBar(); - if (mUpdateUILater) - { - UpdateSelectionUI(); - mUpdateUILater = false; - } + if (mGizmoUIOutdated) UpdateGizmoUI(); } void CWorldEditor::SetViewportSize(int Width, int Height) @@ -411,14 +430,16 @@ void CWorldEditor::SetTransformSpace(int space) { case 0: mTransformSpace = eWorldTransform; - mGizmo.SetOrientation(CQuaternion::skIdentity); + mGizmo.SetRotation(CQuaternion::skIdentity); break; case 1: mTransformSpace = eLocalTransform; if (!mSelectedNodes.empty()) - mGizmo.SetOrientation(mSelectedNodes.front()->AbsoluteRotation()); + mGizmo.SetRotation(mSelectedNodes.front()->AbsoluteRotation()); break; } + + mGizmo.SetTransformSpace(mTransformSpace); } // ************ PRIVATE ************ @@ -491,22 +512,62 @@ void CWorldEditor::UpdateSelectionUI() SelectionText = Metrics.elidedText(SelectionText, Qt::ElideRight, ui->SelectionInfoFrame->width() - 10); ui->SelectionInfoLabel->setText(SelectionText); - // Update transform - CVector3f pos = (!mSelectedNodes.empty() ? mSelectedNodes.front()->AbsolutePosition() : CVector3f::skZero); - ui->XSpinBox->setValue(pos.x); - ui->YSpinBox->setValue(pos.y); - ui->ZSpinBox->setValue(pos.z); + // Update gizmo stuff + UpdateGizmoUI(); +} + +void CWorldEditor::UpdateGizmoUI() +{ + // Update transform XYZ spin boxes + CVector3f spinBoxValue = CVector3f::skZero; + + // If the gizmo is transforming, use the total transform amount + // Otherwise, use the first selected node transform, or 0 if no selection + if (mShowGizmo) + { + switch (mGizmo.Mode()) + { + case CGizmo::eTranslate: + if (mGizmoTransforming && mGizmo.HasTransformed()) + spinBoxValue = mGizmo.TotalTranslation(); + else if (!mSelectedNodes.empty()) + spinBoxValue = mSelectedNodes.front()->AbsolutePosition(); + break; + + case CGizmo::eRotate: + if (mGizmoTransforming && mGizmo.HasTransformed()) + spinBoxValue = mGizmo.TotalRotation(); + else if (!mSelectedNodes.empty()) + spinBoxValue = mSelectedNodes.front()->AbsoluteRotation().ToEuler(); + break; + + case CGizmo::eScale: + if (mGizmoTransforming && mGizmo.HasTransformed()) + spinBoxValue = mGizmo.TotalScale(); + else if (!mSelectedNodes.empty()) + spinBoxValue = mSelectedNodes.front()->AbsoluteScale(); + break; + } + } + else if (!mSelectedNodes.empty()) spinBoxValue = mSelectedNodes.front()->AbsolutePosition(); + + ui->XSpinBox->setValue(spinBoxValue.x); + ui->YSpinBox->setValue(spinBoxValue.y); + ui->ZSpinBox->setValue(spinBoxValue.z); // Update gizmo if (!mGizmoTransforming) { - mGizmo.SetPosition(pos); + if (!mSelectedNodes.empty()) + mGizmo.SetPosition(mSelectedNodes.front()->AbsolutePosition()); if ((mTransformSpace == eLocalTransform) && !mSelectedNodes.empty()) - mGizmo.SetOrientation(mSelectedNodes.front()->AbsoluteRotation()); + mGizmo.SetRotation(mSelectedNodes.front()->AbsoluteRotation()); else - mGizmo.SetOrientation(CQuaternion::skIdentity); + mGizmo.SetRotation(CQuaternion::skIdentity); } + + mGizmoUIOutdated = false; } // ************ ACTIONS ************ @@ -660,6 +721,7 @@ void CWorldEditor::on_ActionEditLayers_triggered() void CWorldEditor::on_ActionSelectObjects_triggered() { mShowGizmo = false; + mGizmoUIOutdated = true; ui->ActionSelectObjects->setChecked(true); ui->ActionTranslate->setChecked(false); ui->ActionRotate->setChecked(false); @@ -669,6 +731,7 @@ void CWorldEditor::on_ActionSelectObjects_triggered() void CWorldEditor::on_ActionTranslate_triggered() { mShowGizmo = true; + mGizmoUIOutdated = true; mGizmo.SetMode(CGizmo::eTranslate); ui->ActionSelectObjects->setChecked(false); ui->ActionTranslate->setChecked(true); @@ -679,6 +742,7 @@ void CWorldEditor::on_ActionTranslate_triggered() void CWorldEditor::on_ActionRotate_triggered() { mShowGizmo = true; + mGizmoUIOutdated = true; mGizmo.SetMode(CGizmo::eRotate); ui->ActionSelectObjects->setChecked(false); ui->ActionTranslate->setChecked(false); @@ -689,6 +753,7 @@ void CWorldEditor::on_ActionRotate_triggered() void CWorldEditor::on_ActionScale_triggered() { mShowGizmo = true; + mGizmoUIOutdated = true; mGizmo.SetMode(CGizmo::eScale); ui->ActionSelectObjects->setChecked(false); ui->ActionTranslate->setChecked(false); diff --git a/UI/CWorldEditor.h b/UI/CWorldEditor.h index 918d8637..61beb1bb 100644 --- a/UI/CWorldEditor.h +++ b/UI/CWorldEditor.h @@ -37,7 +37,7 @@ class CWorldEditor : public QMainWindow bool mShowGizmo; bool mGizmoHovering; bool mGizmoTransforming; - bool mUpdateUILater; + bool mGizmoUIOutdated; CVector3f mHoverPoint; CSceneNode *mpHoverNode; @@ -52,7 +52,7 @@ public: ~CWorldEditor(); bool eventFilter(QObject *pObj, QEvent *pEvent); void SetArea(CWorld *pWorld, CGameArea *pArea); - void ViewportRayCast(CRay Ray); + void ViewportRayCast(); CRenderer* Renderer(); CSceneManager* Scene(); CGameArea* ActiveArea(); @@ -82,6 +82,7 @@ private: void OnSidebarResize(); void UpdateSelectionUI(); void UpdateStatusBar(); + void UpdateGizmoUI(); private slots: void OnCameraSpeedChange(double speed);