diff --git a/Common/CPlane.cpp b/Common/CPlane.cpp new file mode 100644 index 00000000..0ec3e607 --- /dev/null +++ b/Common/CPlane.cpp @@ -0,0 +1,44 @@ +#include "CPlane.h" + +CPlane::CPlane() +{ + mNormal = CVector3f::skUp; + mDist = 0.f; +} + +CPlane::CPlane(const CVector3f& normal, float dist) +{ + mNormal = normal; + mDist = dist; +} + +CPlane::CPlane(const CVector3f& normal, const CVector3f& origin) +{ + Redefine(normal, origin); +} + +void CPlane::Redefine(const CVector3f& normal, const CVector3f& origin) +{ + mNormal = normal; + mDist = -normal.Dot(origin); +} + +CVector3f CPlane::Normal() const +{ + return mNormal; +} + +float CPlane::Dist() const +{ + return mDist; +} + +void CPlane::SetNormal(const CVector3f& normal) +{ + mNormal = normal; +} + +void CPlane::SetDist(float dist) +{ + mDist = dist; +} diff --git a/Common/CPlane.h b/Common/CPlane.h new file mode 100644 index 00000000..7a06dd20 --- /dev/null +++ b/Common/CPlane.h @@ -0,0 +1,23 @@ +#ifndef CPLANE_H +#define CPLANE_H + +#include "CVector3f.h" + +class CPlane +{ + CVector3f mNormal; + float mDist; + +public: + CPlane(); + CPlane(const CVector3f& normal, float dist); + CPlane(const CVector3f& normal, const CVector3f& origin); + + void Redefine(const CVector3f& normal, const CVector3f& origin); + CVector3f Normal() const; + float Dist() const; + void SetNormal(const CVector3f& normal); + void SetDist(float dist); +}; + +#endif // CPLANE_H diff --git a/Common/CQuaternion.cpp b/Common/CQuaternion.cpp index 3208743e..4fbdd369 100644 --- a/Common/CQuaternion.cpp +++ b/Common/CQuaternion.cpp @@ -18,6 +18,47 @@ CQuaternion::CQuaternion(float _x, float _y, float _z, float _w) w = _w; } +CVector3f CQuaternion::XAxis() +{ + return (*this * CVector3f::skUnitX); +} + +CVector3f CQuaternion::YAxis() +{ + return (*this * CVector3f::skUnitY); +} + +CVector3f CQuaternion::ZAxis() +{ + return (*this * CVector3f::skUnitZ); +} + +CQuaternion CQuaternion::Inverse() +{ + float fNorm = (w * w) + (x * x) + (y * y) + (z * z); + + if (fNorm > 0.f) + { + float fInvNorm = 1.f / fNorm; + return CQuaternion(-x * fInvNorm, -y * fInvNorm, -z * fInvNorm, w * fInvNorm); + } + else + return CQuaternion::skZero; +} + +// ************ OPERATORS ************ +CVector3f CQuaternion::operator*(const CVector3f& vec) const +{ + CVector3f uv, uuv; + CVector3f qvec(x, y, z); + uv = qvec.Cross(vec); + uuv = qvec.Cross(uv); + uv *= (2.0f * w); + uuv *= 2.0f; + + return vec + uv + uuv; +} + CQuaternion CQuaternion::operator*(const CQuaternion& other) const { CQuaternion out; @@ -84,3 +125,4 @@ CQuaternion CQuaternion::FromAxisAngle(float angle, CVector3f axis) } CQuaternion CQuaternion::skIdentity = CQuaternion(0.f, 0.f, 0.f, 1.f); +CQuaternion CQuaternion::skZero = CQuaternion(0.f, 0.f, 0.f, 0.f); diff --git a/Common/CQuaternion.h b/Common/CQuaternion.h index a22528c1..c6da1cc5 100644 --- a/Common/CQuaternion.h +++ b/Common/CQuaternion.h @@ -11,7 +11,13 @@ public: CQuaternion(); CQuaternion(float _x, float _y, float _z, float _w); + CVector3f XAxis(); + CVector3f YAxis(); + CVector3f ZAxis(); + CQuaternion Inverse(); + // Operators + CVector3f operator*(const CVector3f& vec) const; CQuaternion operator*(const CQuaternion& other) const; void operator *= (const CQuaternion& other); @@ -20,6 +26,7 @@ public: static CQuaternion FromAxisAngle(float angle, CVector3f axis); static CQuaternion skIdentity; + static CQuaternion skZero; }; #endif // CQUATERNION_H diff --git a/Common/CVector3f.cpp b/Common/CVector3f.cpp index 2a9c6aaf..91857477 100644 --- a/Common/CVector3f.cpp +++ b/Common/CVector3f.cpp @@ -279,12 +279,15 @@ const float& CVector3f::operator[](long index) const const CVector3f CVector3f::skZero = CVector3f(0.f); const CVector3f CVector3f::skOne = CVector3f(1.f); const CVector3f CVector3f::skInfinite = CVector3f(FLT_MAX); -const CVector3f CVector3f::skForward = CVector3f(0.f, 1.f, 0.f); -const CVector3f CVector3f::skBack = CVector3f(0.f, -1.f, 0.f); -const CVector3f CVector3f::skRight = CVector3f( 1.f, 0.f, 0.f); -const CVector3f CVector3f::skLeft = CVector3f(-1.f, 0.f, 0.f); -const CVector3f CVector3f::skUp = CVector3f(0.f, 0.f, 1.f); -const CVector3f CVector3f::skDown = CVector3f(0.f, 0.f, -1.f); +const CVector3f CVector3f::skUnitX = CVector3f(1.f, 0.f, 0.f); +const CVector3f CVector3f::skUnitY = CVector3f(0.f, 1.f, 0.f); +const CVector3f CVector3f::skUnitZ = CVector3f(0.f, 0.f, 1.f); +const CVector3f CVector3f::skRight = CVector3f::skUnitX; +const CVector3f CVector3f::skLeft = -CVector3f::skUnitX; +const CVector3f CVector3f::skForward = CVector3f::skUnitY; +const CVector3f CVector3f::skBack = -CVector3f::skUnitY; +const CVector3f CVector3f::skUp = CVector3f::skUnitZ; +const CVector3f CVector3f::skDown = -CVector3f::skUnitZ; // ************ OTHER ************ std::ostream& operator<<(std::ostream& o, const CVector3f& Vector) diff --git a/Common/CVector3f.h b/Common/CVector3f.h index 565fb07b..b8ebe05b 100644 --- a/Common/CVector3f.h +++ b/Common/CVector3f.h @@ -75,10 +75,13 @@ public: static const CVector3f skZero; static const CVector3f skOne; static const CVector3f skInfinite; - static const CVector3f skForward; - static const CVector3f skBack; + static const CVector3f skUnitX; + static const CVector3f skUnitY; + static const CVector3f skUnitZ; static const CVector3f skRight; static const CVector3f skLeft; + static const CVector3f skForward; + static const CVector3f skBack; static const CVector3f skUp; static const CVector3f skDown; diff --git a/Common/ETransformSpace.h b/Common/ETransformSpace.h new file mode 100644 index 00000000..f39f6de7 --- /dev/null +++ b/Common/ETransformSpace.h @@ -0,0 +1,11 @@ +#ifndef ETRANSFORMSPACE +#define ETRANSFORMSPACE + +enum ETransformSpace +{ + eWorldTransform, + eLocalTransform +}; + +#endif // ETRANSFORMSPACE + diff --git a/Common/Math.cpp b/Common/Math.cpp index 563cc434..3684d9f5 100644 --- a/Common/Math.cpp +++ b/Common/Math.cpp @@ -3,6 +3,11 @@ namespace Math { +float Abs(float v) +{ + return fabs(v); +} + float Pow(float Base, float Exponent) { return pow(Base, Exponent); @@ -15,6 +20,23 @@ float Distance(const CVector3f& A, const CVector3f& B) Pow(B.z - A.z, 2.f) ); } +std::pair RayPlaneIntersecton(const CRay& ray, const CPlane& plane) +{ + // Code based on ray/plane intersect code from Ogre + // https://bitbucket.org/sinbad/ogre/src/197116fd2ac62c57cdeed1666f9866c3dddd4289/OgreMain/src/OgreMath.cpp?at=default#OgreMath.cpp-350 + + // Are ray and plane parallel? + float denom = plane.Normal().Dot(ray.Direction()); + + if (Abs(denom) < FLT_EPSILON) + return std::pair(false, 0.f); + + // Not parallel + float nom = plane.Normal().Dot(ray.Origin()) + plane.Dist(); + float t = -(nom / denom); + return std::pair(t >= 0.f, t); +} + std::pair RayBoxIntersection(const CRay& Ray, const CAABox& Box) { // Code slightly modified from Ogre diff --git a/Common/Math.h b/Common/Math.h index 2f83ca53..f5d5b955 100644 --- a/Common/Math.h +++ b/Common/Math.h @@ -3,6 +3,7 @@ #include "CAABox.h" #include "CRay.h" +#include "CPlane.h" #include "CVector3f.h" #include "SRayIntersection.h" #include @@ -10,10 +11,14 @@ namespace Math { +float Abs(float v); + float Pow(float Base, float Exponent); float Distance(const CVector3f& A, const CVector3f& B); +std::pair RayPlaneIntersecton(const CRay& ray, const CPlane& plane); + std::pair RayBoxIntersection(const CRay& Ray, const CAABox& Box); std::pair RayLineIntersection(const CRay& ray, const CVector3f& pointA, diff --git a/Core/CRenderer.cpp b/Core/CRenderer.cpp index be0e8c49..9e5623dc 100644 --- a/Core/CRenderer.cpp +++ b/Core/CRenderer.cpp @@ -140,10 +140,6 @@ void CRenderer::RenderBuckets(CCamera& Camera) mTransparentBucket.Sort(Camera); mTransparentBucket.Draw(mOptions); mTransparentBucket.Clear(); - - // Clear depth buffer to enable more rendering passes - glDepthMask(GL_TRUE); - glClear(GL_DEPTH_BUFFER_BIT); } void CRenderer::RenderBloom() @@ -331,6 +327,12 @@ void CRenderer::EndFrame() gDrawCount = 0; } +void CRenderer::ClearDepthBuffer() +{ + glDepthMask(GL_TRUE); + glClear(GL_DEPTH_BUFFER_BIT); +} + // ************ PRIVATE ************ void CRenderer::InitFramebuffer() { diff --git a/Core/CRenderer.h b/Core/CRenderer.h index 62dba437..a90b7989 100644 --- a/Core/CRenderer.h +++ b/Core/CRenderer.h @@ -72,6 +72,7 @@ public: void AddTransparentMesh(IRenderable *pRenderable, u32 AssetID, CAABox& AABox, ERenderCommand Command); void BeginFrame(); void EndFrame(); + void ClearDepthBuffer(); // Private private: diff --git a/Core/CSceneManager.cpp b/Core/CSceneManager.cpp index 70e7bdb0..8b6571d4 100644 --- a/Core/CSceneManager.cpp +++ b/Core/CSceneManager.cpp @@ -157,7 +157,6 @@ void CSceneManager::SetActiveArea(CGameArea* _area) { CScriptObject *pObj = pGenLayer->ObjectByIndex(o); CScriptNode *Node = AddScriptObject(pObj); - Node->BuildLightList(mpArea); // Add to map mScriptNodeMap[pObj->InstanceID()] = Node; @@ -165,9 +164,12 @@ void CSceneManager::SetActiveArea(CGameArea* _area) } PickEnvironmentObjects(); - // Ensure script nodes have valid positions + // Ensure script nodes have valid positions + build light lists for (auto it = mScriptNodeMap.begin(); it != mScriptNodeMap.end(); it++) + { it->second->GeneratePosition(); + it->second->BuildLightList(mpArea); + } u32 NumLightLayers = mpArea->GetLightLayerCount(); CGraphics::sAreaAmbientColor = CColor::skBlack; diff --git a/PrimeWorldEditor.pro b/PrimeWorldEditor.pro index 8b26ddff..942262f5 100644 --- a/PrimeWorldEditor.pro +++ b/PrimeWorldEditor.pro @@ -134,7 +134,8 @@ SOURCES += \ UI/WScanPreviewPanel.cpp \ UI/WIntegralSpinBox.cpp \ UI/CAboutDialog.cpp \ - UI/CGizmo.cpp + UI/CGizmo.cpp \ + Common/CPlane.cpp HEADERS += \ Common/AnimUtil.h \ @@ -283,7 +284,9 @@ HEADERS += \ UI/CAboutDialog.h \ UI/CGizmo.h \ Core/IRenderable.h \ - Core/SRenderablePtr.h + Core/SRenderablePtr.h \ + Common/ETransformSpace.h \ + Common/CPlane.h FORMS += \ UI/CWorldEditorWindow.ui \ diff --git a/Scene/CSceneNode.cpp b/Scene/CSceneNode.cpp index 86ba0ec5..650a2281 100644 --- a/Scene/CSceneNode.cpp +++ b/Scene/CSceneNode.cpp @@ -185,15 +185,29 @@ void CSceneNode::DrawBoundingBox() } // ************ TRANSFORM ************ -void CSceneNode::Translate(const CVector3f& Translation) +void CSceneNode::Translate(const CVector3f& translation, ETransformSpace transformSpace) { - mPosition += Translation; + switch (transformSpace) + { + case eWorldTransform: + mPosition += translation; + break; + case eLocalTransform: + mPosition += mRotation * translation; + break; + } MarkTransformChanged(); } -void CSceneNode::Scale(const CVector3f& Scale) +void CSceneNode::Rotate(const CQuaternion& rotation, ETransformSpace transformSpace) { - mScale *= Scale; + mRotation *= rotation; + MarkTransformChanged(); +} + +void CSceneNode::Scale(const CVector3f& scale, ETransformSpace transformSpace) +{ + mScale *= scale; MarkTransformChanged(); } @@ -209,9 +223,9 @@ void CSceneNode::UpdateTransform() void CSceneNode::ForceRecalculateTransform() { _mCachedTransform = CTransform4f::skIdentity; - _mCachedTransform.Scale(GetAbsoluteScale()); - _mCachedTransform.Rotate(GetAbsoluteRotation()); - _mCachedTransform.Translate(GetAbsolutePosition()); + _mCachedTransform.Scale(AbsoluteScale()); + _mCachedTransform.Rotate(AbsoluteRotation()); + _mCachedTransform.Translate(AbsolutePosition()); _mCachedAABox = mLocalAABox.Transformed(_mCachedTransform); // Sync with children - only needed if caller hasn't marked transform changed already @@ -259,47 +273,47 @@ CSceneManager* CSceneNode::Scene() return mpScene; } -CVector3f CSceneNode::GetPosition() const +CVector3f CSceneNode::LocalPosition() const { return mPosition; } -CVector3f CSceneNode::GetAbsolutePosition() const +CVector3f CSceneNode::AbsolutePosition() const { CVector3f ret = mPosition; if ((mpParent) && (InheritsPosition())) - ret += mpParent->GetAbsolutePosition(); + ret += mpParent->AbsolutePosition(); return ret; } -CQuaternion CSceneNode::GetRotation() const +CQuaternion CSceneNode::LocalRotation() const { return mRotation; } -CQuaternion CSceneNode::GetAbsoluteRotation() const +CQuaternion CSceneNode::AbsoluteRotation() const { CQuaternion ret = mRotation; if ((mpParent) && (InheritsRotation())) - ret *= mpParent->GetAbsoluteRotation(); + ret *= mpParent->AbsoluteRotation(); return ret; } -CVector3f CSceneNode::GetScale() const +CVector3f CSceneNode::LocalScale() const { return mScale; } -CVector3f CSceneNode::GetAbsoluteScale() const +CVector3f CSceneNode::AbsoluteScale() const { CVector3f ret = mScale; if ((mpParent) && (InheritsScale())) - ret *= mpParent->GetAbsoluteScale(); + ret *= mpParent->AbsoluteScale(); return ret; } diff --git a/Scene/CSceneNode.h b/Scene/CSceneNode.h index d6b82261..933689d3 100644 --- a/Scene/CSceneNode.h +++ b/Scene/CSceneNode.h @@ -3,13 +3,14 @@ #include #include "ENodeType.h" -#include -#include #include +#include #include #include -#include #include +#include +#include +#include #include #include #include @@ -68,8 +69,9 @@ public: void DrawBoundingBox(); // Transform - void Translate(const CVector3f& Translation); - void Scale(const CVector3f& Scale); + void Translate(const CVector3f& translation, ETransformSpace transformSpace); + void Rotate(const CQuaternion& rotation, ETransformSpace transformSpace); + void Scale(const CVector3f& scale, ETransformSpace transformSpace); void UpdateTransform(); void ForceRecalculateTransform(); void MarkTransformChanged(); @@ -79,12 +81,12 @@ public: std::string Name() const; CSceneNode* Parent() const; CSceneManager* Scene(); - CVector3f GetPosition() const; - CVector3f GetAbsolutePosition() const; - CQuaternion GetRotation() const; - CQuaternion GetAbsoluteRotation() const; - CVector3f GetScale() const; - CVector3f GetAbsoluteScale() const; + CVector3f LocalPosition() const; + CVector3f AbsolutePosition() const; + CQuaternion LocalRotation() const; + CQuaternion AbsoluteRotation() const; + CVector3f LocalScale() const; + CVector3f AbsoluteScale() const; CAABox AABox(); CVector3f CenterPoint(); bool MarkedVisible() const; diff --git a/Scene/CScriptNode.cpp b/Scene/CScriptNode.cpp index c075d6ec..291386da 100644 --- a/Scene/CScriptNode.cpp +++ b/Scene/CScriptNode.cpp @@ -45,7 +45,7 @@ CScriptNode::CScriptNode(CSceneManager *pScene, CSceneNode *pParent, CScriptObje { mpVolumePreviewNode = new CModelNode(pScene, this, pVolumeModel); mpVolumePreviewNode->SetInheritance(true, (VolumeShape == 1), false); - mpVolumePreviewNode->Scale(mpInstance->GetVolume()); + mpVolumePreviewNode->Scale(mpInstance->GetVolume(), eWorldTransform); mpVolumePreviewNode->ForceAlphaEnabled(true); } } @@ -266,7 +266,7 @@ void CScriptNode::GeneratePosition() const SLink& link = (mpInstance->NumInLinks() > 0 ? mpInstance->InLink(0) : mpInstance->OutLink(0)); CScriptNode *pNode = mpScene->ScriptNodeByID(link.ObjectID); pNode->GeneratePosition(); - mPosition = pNode->GetAbsolutePosition(); + mPosition = pNode->AbsolutePosition(); mPosition.z += (pNode->AABox().Size().z / 2.f); mPosition.z += (AABox().Size().z / 2.f); mPosition.z += 2.f; diff --git a/UI/CEditorGLWidget.cpp b/UI/CEditorGLWidget.cpp index 58853861..1bbcad81 100644 --- a/UI/CEditorGLWidget.cpp +++ b/UI/CEditorGLWidget.cpp @@ -105,24 +105,31 @@ void CEditorGLWidget::mousePressEvent(QMouseEvent *pEvent) // Left click only activates if mouse input is inactive to prevent the user from // clicking on things and creating selection rectangles while the cursor is hidden - else if (pEvent->button() == Qt::LeftButton) - mButtonsPressed |= eLeftButton; + else + { + if (pEvent->button() == Qt::LeftButton) + mButtonsPressed |= eLeftButton; + + emit MouseClick(pEvent); + } mLastMousePos = pEvent->globalPos(); } void CEditorGLWidget::mouseReleaseEvent(QMouseEvent *pEvent) { + bool fromMouseInput = IsMouseInputActive(); if (pEvent->button() == Qt::LeftButton) mButtonsPressed &= ~eLeftButton; if (pEvent->button() == Qt::MidButton) mButtonsPressed &= ~eMiddleButton; if (pEvent->button() == Qt::RightButton) mButtonsPressed &= ~eRightButton; - // Make cursor visible and emit mouse click event if middle/right mouse buttons are both released + // Make cursor visible if needed if (!IsMouseInputActive()) - { SetCursorVisible(true); - emit MouseClick(pEvent); - } + + // Emit mouse release event if we didn't just exit mouse input (or regardless on left click) + if (!fromMouseInput || (pEvent->button() == Qt::LeftButton)) + emit MouseRelease(pEvent); } void CEditorGLWidget::keyPressEvent(QKeyEvent *pEvent) diff --git a/UI/CEditorGLWidget.h b/UI/CEditorGLWidget.h index 12b840d1..646b490f 100644 --- a/UI/CEditorGLWidget.h +++ b/UI/CEditorGLWidget.h @@ -54,6 +54,7 @@ signals: void Render(CCamera& Camera); void PostRender(); void MouseClick(QMouseEvent *pEvent); + void MouseRelease(QMouseEvent *pEvent); void MouseDrag(QMouseEvent *pEvent); private: diff --git a/UI/CGizmo.cpp b/UI/CGizmo.cpp index c94bc07d..ec21633d 100644 --- a/UI/CGizmo.cpp +++ b/UI/CGizmo.cpp @@ -2,6 +2,8 @@ #include #include #include +#include +#include CGizmo::CGizmo() { @@ -15,9 +17,10 @@ CGizmo::CGizmo() mPosition = CVector3f::skZero; mRotation = CQuaternion::skIdentity; mScale = CVector3f::skOne; - mDeltaPosition = CVector3f::skZero; + mDeltaTranslation = CVector3f::skZero; mDeltaRotation = CQuaternion::skIdentity; mDeltaScale = CVector3f::skOne; + mSetOffset = false; mFlipScaleX = false; mFlipScaleY = false; mFlipScaleZ = false; @@ -109,11 +112,10 @@ void CGizmo::UpdateForCamera(const CCamera &camera) mBillboardRotation = CQuaternion::FromAxisAngle(angle, axis); } -bool CGizmo::IntersectsRay(const CRay &ray) +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()); - float threshold = 0.02f * mGizmoSize * mCameraDist; // Do raycast on each model SModelPart *pPart = mpCurrentParts; @@ -126,7 +128,12 @@ bool CGizmo::IntersectsRay(const CRay &ray) for (u32 iPart = 0; iPart < mNumCurrentParts; iPart++) { - if (!pPart->enableRayCast) continue; + if (!pPart->enableRayCast) + { + pPart++; + continue; + } + CModel *pModel = pPart->pModel; // Ray/Model AABox test - allow buffer room because lines are small @@ -141,7 +148,7 @@ bool CGizmo::IntersectsRay(const CRay &ray) for (u32 iSurf = 0; iSurf < pModel->GetSurfaceCount(); iSurf++) { - // Skip surface/box check + // 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); @@ -183,11 +190,129 @@ bool CGizmo::IntersectsRay(const CRay &ray) return true; } +u32 CGizmo::NumSelectedAxes() +{ + u32 out = 0; + + for (u32 iAxis = 1; iAxis < 8; iAxis <<= 1) + if (mSelectedAxes & (EGizmoAxes) iAxis) out++; + + return out; +} + +void CGizmo::ResetSelectedAxes() +{ + mSelectedAxes = eNone; +} + +void CGizmo::StartTransform() +{ + mSetOffset = false; + mTotalTranslation = CVector3f::skZero; + mTotalRotation = CQuaternion::skIdentity; + mTotalScale = CVector3f::skOne; +} + +bool CGizmo::TransformFromInput(const CRay& ray, const CCamera& camera) +{ + if (mMode == eTranslate) + { + // Create translate plane + CVector3f axisA, axisB; + u32 numAxes = NumSelectedAxes(); + + if (numAxes == 1) + { + if (mSelectedAxes & eX) axisB = mRotation.XAxis(); + else if (mSelectedAxes & eY) axisB = mRotation.YAxis(); + else axisB = mRotation.ZAxis(); + + CVector3f gizmoToCamera = (mPosition - camera.Position()).Normalized(); + axisA = axisB.Cross(gizmoToCamera); + } + + else if (numAxes == 2) + { + axisA = (mSelectedAxes & eX ? mRotation.XAxis() : mRotation.YAxis()); + axisB = (mSelectedAxes & eZ ? mRotation.ZAxis() : mRotation.YAxis()); + } + + CVector3f planeNormal = axisA.Cross(axisB); + mTranslatePlane.Redefine(planeNormal, mPosition); + + // Do translate + std::pair result = Math::RayPlaneIntersecton(ray, mTranslatePlane); + + if (result.first) + { + CVector3f hit = ray.PointOnRay(result.second); + CVector3f localDelta = mRotation.Inverse() * (hit - mPosition); + + // Calculate new position + CVector3f newPos = mPosition; + if (mSelectedAxes & eX) newPos += mRotation.XAxis() * localDelta.x; + if (mSelectedAxes & eY) newPos += mRotation.YAxis() * localDelta.y; + if (mSelectedAxes & eZ) 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 - camera.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); + mTotalTranslation += mDeltaTranslation; + mPosition = newPos + mTranslateOffset; + return true; + } + } + + else + { + mDeltaTranslation = CVector3f::skZero; + return false; + } + } + + return false; +} + +void CGizmo::EndTransform() +{ +} + CGizmo::EGizmoMode CGizmo::Mode() { return mMode; } +CVector3f CGizmo::Position() +{ + return mPosition; +} + +CVector3f CGizmo::DeltaTranslation() +{ + return mDeltaTranslation; +} + +CVector3f CGizmo::TotalTranslation() +{ + return mTotalTranslation; +} + void CGizmo::SetMode(EGizmoMode mode) { mMode = mode; @@ -197,16 +322,22 @@ void CGizmo::SetMode(EGizmoMode mode) case eTranslate: mpCurrentParts = smTranslateModels; mNumCurrentParts = 9; + mDeltaRotation = CQuaternion::skIdentity; + mDeltaScale = CVector3f::skOne; break; case eRotate: mpCurrentParts = smRotateModels; mNumCurrentParts = 4; + mDeltaTranslation = CVector3f::skZero; + mDeltaScale = CVector3f::skOne; break; case eScale: mpCurrentParts = smScaleModels; mNumCurrentParts = 10; + mDeltaTranslation = CVector3f::skZero; + mDeltaRotation = CQuaternion::skIdentity; break; } } @@ -216,9 +347,9 @@ void CGizmo::SetPosition(const CVector3f& position) mPosition = position; } -void CGizmo::ResetSelectedAxes() +void CGizmo::SetOrientation(const CQuaternion& orientation) { - mSelectedAxes = eNone; + mRotation = orientation; } // ************ PRIVATE STATIC ************ diff --git a/UI/CGizmo.h b/UI/CGizmo.h index 9d7348fe..fa5deadd 100644 --- a/UI/CGizmo.h +++ b/UI/CGizmo.h @@ -1,8 +1,9 @@ #ifndef CGIZMO_H #define CGIZMO_H -#include +#include #include +#include #include #include #include @@ -63,15 +64,24 @@ private: CTransform4f mTransform; CVector3f mPosition; + CVector3f mDeltaTranslation; + CVector3f mTotalTranslation; CQuaternion mRotation; - CVector3f mScale; - CVector3f mDeltaPosition; CQuaternion mDeltaRotation; + CQuaternion mTotalRotation; + CVector3f mScale; CVector3f mDeltaScale; + CVector3f mTotalScale; bool mFlipScaleX; bool mFlipScaleY; bool mFlipScaleZ; + CPlane mTranslatePlane; + CVector3f mLastTranslatePosition; + CVector3f mTranslateOffset; + bool mSetOffset; + + struct SModelPart { EGizmoAxes modelAxes; @@ -104,12 +114,20 @@ public: void IncrementSize(); void DecrementSize(); void UpdateForCamera(const CCamera& camera); - bool IntersectsRay(const CRay& ray); + bool CheckSelectedAxes(const CRay& ray); + u32 NumSelectedAxes(); + void ResetSelectedAxes(); + void StartTransform(); + bool TransformFromInput(const CRay& ray, const CCamera& camera); + void EndTransform(); EGizmoMode Mode(); + CVector3f Position(); + CVector3f DeltaTranslation(); + CVector3f TotalTranslation(); void SetMode(EGizmoMode mode); void SetPosition(const CVector3f& position); - void ResetSelectedAxes(); + void SetOrientation(const CQuaternion& orientation); // Protected protected: diff --git a/UI/CWorldEditor.cpp b/UI/CWorldEditor.cpp index f8fe2188..7263c21d 100644 --- a/UI/CWorldEditor.cpp +++ b/UI/CWorldEditor.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include "WDraggableSpinBox.h" @@ -32,6 +33,9 @@ CWorldEditor::CWorldEditor(QWidget *parent) : mpHoverNode = nullptr; mDrawSky = true; mShowGizmo = false; + mGizmoHovering = false; + mGizmoTransforming = false; + mUpdateUILater = false; mFrameCount = 0; mFPSTimer.Start(); @@ -47,7 +51,6 @@ CWorldEditor::CWorldEditor(QWidget *parent) : delete pOldTitleBar; - // Initialize UI stuff ui->ModifyTabContents->SetEditor(this); ui->InstancesTabContents->SetEditor(this, mpSceneManager); @@ -55,17 +58,27 @@ CWorldEditor::CWorldEditor(QWidget *parent) : ui->CamSpeedSpinBox->SetDefaultValue(1.0); ResetHover(); + mTransformSpace = eWorldTransform; + + QComboBox *pTransformCombo = new QComboBox(this); + pTransformCombo->setMinimumWidth(75); + pTransformCombo->addItem("World"); + pTransformCombo->addItem("Local"); + ui->MainToolBar->insertWidget(0, pTransformCombo); + // Initialize offscreen actions addAction(ui->ActionIncrementGizmo); addAction(ui->ActionDecrementGizmo); // Connect signals and slots + connect(pTransformCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(SetTransformSpace(int))); connect(ui->CamSpeedSpinBox, SIGNAL(valueChanged(double)), this, SLOT(OnCameraSpeedChange(double))); connect(ui->MainViewport, SIGNAL(PreRender()), this, SLOT(ViewportPreRender())); connect(ui->MainViewport, SIGNAL(Render(CCamera&)), this, SLOT(ViewportRender(CCamera&))); connect(ui->MainViewport, SIGNAL(ViewportResized(int,int)), this, SLOT(SetViewportSize(int,int))); connect(ui->MainViewport, SIGNAL(frameSwapped()), this, SLOT(ViewportPostRender())); connect(ui->MainViewport, SIGNAL(MouseClick(QMouseEvent*)), this, SLOT(ViewportMouseClick(QMouseEvent*))); + connect(ui->MainViewport, SIGNAL(MouseRelease(QMouseEvent*)), this, SLOT(ViewportMouseRelease(QMouseEvent*))); } CWorldEditor::~CWorldEditor() @@ -135,28 +148,58 @@ void CWorldEditor::ViewportRayCast(CRay Ray) { if (!ui->MainViewport->IsMouseInputActive()) { - // Gizmo ray check - mGizmoHovering = mGizmo.IntersectsRay(Ray); - - // Scene ray check - SRayIntersection Result = mpSceneManager->SceneRayCast(Ray); - - if (Result.Hit) + if (!mGizmoTransforming) { - if (mpHoverNode) - mpHoverNode->SetMouseHovering(false); + // Gizmo hover check + if (mShowGizmo && !mSelectedNodes.empty()) + mGizmoHovering = mGizmo.CheckSelectedAxes(Ray); + else + { + mGizmoHovering = false; + mGizmo.ResetSelectedAxes(); + } - mpHoverNode = Result.pNode; - mpHoverNode->SetMouseHovering(true); + // Scene ray check + SRayIntersection Result = mpSceneManager->SceneRayCast(Ray); - mHoverPoint = Ray.PointOnRay(Result.Distance); + if (Result.Hit) + { + if (mpHoverNode) + mpHoverNode->SetMouseHovering(false); + + mpHoverNode = Result.pNode; + mpHoverNode->SetMouseHovering(true); + + mHoverPoint = Ray.PointOnRay(Result.Distance); + } + else + ResetHover(); } else - ResetHover(); + { + bool moved = mGizmo.TransformFromInput(Ray, ui->MainViewport->Camera()); + + if (moved) + { + CVector3f delta = mGizmo.DeltaTranslation(); + + for (auto it = mSelectedNodes.begin(); it != mSelectedNodes.end(); it++) + { + (*it)->Translate(delta, mTransformSpace); + (*it)->BuildLightList(this->mpArea); + } + RecalculateSelectionBounds(); + mUpdateUILater = true; + } + } } else { - mGizmo.ResetSelectedAxes(); + if (!mGizmoTransforming) + { + mGizmoHovering = false; + mGizmo.ResetSelectedAxes(); + } ResetHover(); } } @@ -229,44 +272,73 @@ void CWorldEditor::ClearSelection() // ************ SLOTS ************ void CWorldEditor::ViewportMouseDrag(QMouseEvent *pEvent) { - // todo: gizmo translate/rotate/scale implementation } void CWorldEditor::ViewportMouseClick(QMouseEvent *pEvent) { - // Process left click (button press) + bool AltPressed = ((pEvent->modifiers() & Qt::AltModifier) != 0); + bool CtrlPressed = ((pEvent->modifiers() & Qt::ControlModifier) != 0); + + if (mGizmoHovering && !AltPressed && !CtrlPressed) + { + mGizmoTransforming = true; + mGizmo.StartTransform(); + } +} + +void CWorldEditor::ViewportMouseRelease(QMouseEvent *pEvent) +{ if (pEvent->button() == Qt::LeftButton) { - bool ValidNode = ((mpHoverNode) && (mpHoverNode->NodeType() != eStaticNode)); - bool AltPressed = ((pEvent->modifiers() & Qt::AltModifier) != 0); - bool CtrlPressed = ((pEvent->modifiers() & Qt::ControlModifier) != 0); - - // Alt pressed - deselect object - if (AltPressed) + // Gizmo transform stop + if (mGizmoTransforming) { - // No valid node selected - do nothing - if (!ValidNode) - return; - - DeselectNode(mpHoverNode); + mGizmoTransforming = false; } - // Other - select object - else + // Object selection/deselection + else if (!ui->MainViewport->IsMouseInputActive()) { - // Control not pressed - clear existing selection - if (!CtrlPressed) - ClearSelection(); + bool ValidNode = ((mpHoverNode) && (mpHoverNode->NodeType() != eStaticNode)); + bool AltPressed = ((pEvent->modifiers() & Qt::AltModifier) != 0); + bool CtrlPressed = ((pEvent->modifiers() & Qt::ControlModifier) != 0); - // Add hover node to selection - if (ValidNode) - SelectNode(mpHoverNode); + // Alt pressed - deselect object + if (AltPressed) + { + // No valid node selected - do nothing + if (!ValidNode) + return; + + DeselectNode(mpHoverNode); + } + + // Ctrl pressed - add object to selection + else if (CtrlPressed) + { + // Add hover node to selection + if (ValidNode) + SelectNode(mpHoverNode); + } + + // Neither pressed + else + { + // If the gizmo isn't under the mouse, clear existing selection + select object (if applicable) + if (!mGizmoHovering) + { + ClearSelection(); + + if (ValidNode) + SelectNode(mpHoverNode); + } + } + + UpdateSelectionUI(); } - - UpdateSelectionUI(); } - // Later, possibly expand to context menu creation for right-click + // todo: context menu creation on right-click goes here } // ************ SLOTS ************ @@ -298,8 +370,10 @@ void CWorldEditor::ViewportRender(CCamera& Camera) if (mShowGizmo && (mSelectedNodes.size() > 0)) { + mpRenderer->ClearDepthBuffer(); + Camera.LoadMatrices(); - mGizmo.UpdateForCamera(Camera); + if (!mGizmoTransforming) mGizmo.UpdateForCamera(Camera); mGizmo.AddToRenderer(mpRenderer); if (mGizmo.Mode() == CGizmo::eRotate) @@ -318,6 +392,12 @@ void CWorldEditor::ViewportPostRender() // Update UI with raycast results UpdateCursor(); UpdateStatusBar(); + + if (mUpdateUILater) + { + UpdateSelectionUI(); + mUpdateUILater = false; + } } void CWorldEditor::SetViewportSize(int Width, int Height) @@ -325,6 +405,22 @@ void CWorldEditor::SetViewportSize(int Width, int Height) mpRenderer->SetViewportSize(Width, Height); } +void CWorldEditor::SetTransformSpace(int space) +{ + switch (space) + { + case 0: + mTransformSpace = eWorldTransform; + mGizmo.SetOrientation(CQuaternion::skIdentity); + break; + case 1: + mTransformSpace = eLocalTransform; + if (!mSelectedNodes.empty()) + mGizmo.SetOrientation(mSelectedNodes.front()->AbsoluteRotation()); + break; + } +} + // ************ PRIVATE ************ void CWorldEditor::RecalculateSelectionBounds() { @@ -396,13 +492,21 @@ void CWorldEditor::UpdateSelectionUI() ui->SelectionInfoLabel->setText(SelectionText); // Update transform - CVector3f pos = (!mSelectedNodes.empty() ? mSelectedNodes.front()->GetAbsolutePosition() : CVector3f::skZero); + 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 - mGizmo.SetPosition(pos); + if (!mGizmoTransforming) + { + mGizmo.SetPosition(pos); + + if ((mTransformSpace == eLocalTransform) && !mSelectedNodes.empty()) + mGizmo.SetOrientation(mSelectedNodes.front()->AbsoluteRotation()); + else + mGizmo.SetOrientation(CQuaternion::skIdentity); + } } // ************ ACTIONS ************ diff --git a/UI/CWorldEditor.h b/UI/CWorldEditor.h index 858107c3..918d8637 100644 --- a/UI/CWorldEditor.h +++ b/UI/CWorldEditor.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -25,6 +26,7 @@ class CWorldEditor : public QMainWindow CRenderer *mpRenderer; CSceneManager *mpSceneManager; CGizmo mGizmo; + ETransformSpace mTransformSpace; CCamera mCamera; CGameArea *mpArea; CWorld *mpWorld; @@ -34,6 +36,8 @@ class CWorldEditor : public QMainWindow bool mDrawSky; bool mShowGizmo; bool mGizmoHovering; + bool mGizmoTransforming; + bool mUpdateUILater; CVector3f mHoverPoint; CSceneNode *mpHoverNode; @@ -64,7 +68,9 @@ public slots: void ViewportPostRender(); void ViewportMouseDrag(QMouseEvent *pEvent); void ViewportMouseClick(QMouseEvent *pEvent); + void ViewportMouseRelease(QMouseEvent *pEvent); void SetViewportSize(int Width, int Height); + void SetTransformSpace(int space); private: Ui::CWorldEditor *ui; diff --git a/UI/CWorldEditor.ui b/UI/CWorldEditor.ui index ac3ecab7..40935ee2 100644 --- a/UI/CWorldEditor.ui +++ b/UI/CWorldEditor.ui @@ -440,13 +440,16 @@ - + Qt::NoContextMenu toolBar_2 + + false + TopToolBarArea @@ -593,13 +596,19 @@ - + Qt::NoContextMenu toolBar + + + 32 + 32 + + TopToolBarArea