#include "CSceneViewport.h" #include "CSelectionIterator.h" #include "UICommon.h" #include "Editor/Undo/UndoCommands.h" #include #include #include #include #include #include CSceneViewport::CSceneViewport(QWidget *pParent) : CBasicViewport(pParent) , mpEditor(nullptr) , mpScene(nullptr) , mRenderingMergedWorld(true) , mGizmoTransforming(false) , mpHoverNode(nullptr) , mHoverPoint(CVector3f::skZero) , mpContextMenu(nullptr) , mpMenuNode(nullptr) { mGrid.SetColor(CColor(0.f, 0.f, 0.6f, 0.f), CColor(0.f, 0.f, 1.f, 0.f)); mLinkLine.SetColor(CColor::skYellow); mpRenderer = new CRenderer(); mpRenderer->SetClearColor(CColor::skBlack); mpRenderer->SetViewportSize(width(), height()); mViewInfo.pScene = mpScene; mViewInfo.pRenderer = mpRenderer; mViewInfo.ShowFlags = EShowFlag::MergedWorld | EShowFlag::ObjectGeometry | EShowFlag::Lights | EShowFlag::Sky; CreateContextMenu(); } CSceneViewport::~CSceneViewport() { delete mpRenderer; } void CSceneViewport::SetScene(INodeEditor *pEditor, CScene *pScene) { mpEditor = pEditor; mpScene = pScene; } void CSceneViewport::SetShowWorld(bool Visible) { if (mRenderingMergedWorld) SetShowFlag(EShowFlag::MergedWorld, Visible); else SetShowFlag(EShowFlag::SplitWorld, Visible); } void CSceneViewport::SetRenderMergedWorld(bool RenderMerged) { mRenderingMergedWorld = RenderMerged; if (mViewInfo.ShowFlags & (EShowFlag::SplitWorld | EShowFlag::MergedWorld)) { SetShowFlag(EShowFlag::SplitWorld, !RenderMerged); SetShowFlag(EShowFlag::MergedWorld, RenderMerged); } } FShowFlags CSceneViewport::ShowFlags() const { return mViewInfo.ShowFlags; } CRenderer* CSceneViewport::Renderer() { return mpRenderer; } CSceneNode* CSceneViewport::HoverNode() { return mpHoverNode; } CVector3f CSceneViewport::HoverPoint() { return mHoverPoint; } void CSceneViewport::CheckGizmoInput(const CRay& rkRay) { CGizmo *pGizmo = mpEditor->Gizmo(); // Gizmo not transforming: Check for gizmo hover if (!pGizmo->IsTransforming()) { if (mpEditor->IsGizmoVisible()) mGizmoHovering = pGizmo->CheckSelectedAxes(rkRay); else mGizmoHovering = false; } // Gizmo transforming: Run gizmo input with ray/mouse coords else if (mGizmoTransforming) { bool transformed = pGizmo->TransformFromInput(rkRay, mCamera); if (transformed) emit GizmoMoved(); } else mGizmoHovering = false; } SRayIntersection CSceneViewport::SceneRayCast(const CRay& rkRay) { if (mpEditor->Gizmo()->IsTransforming()) { ResetHover(); return SRayIntersection(); } SRayIntersection Intersect = mpScene->SceneRayCast(rkRay, mViewInfo); if (Intersect.Hit) { if (mpHoverNode) mpHoverNode->SetMouseHovering(false); mpHoverNode = Intersect.pNode; mpHoverNode->SetMouseHovering(true); mHoverPoint = rkRay.PointOnRay(Intersect.Distance); } else { mHoverPoint = rkRay.PointOnRay(10.f); ResetHover(); } return Intersect; } void CSceneViewport::ResetHover() { if (mpHoverNode) mpHoverNode->SetMouseHovering(false); mpHoverNode = nullptr; } bool CSceneViewport::IsHoveringGizmo() { return mGizmoHovering; } void CSceneViewport::keyPressEvent(QKeyEvent *pEvent) { CBasicViewport::keyPressEvent(pEvent); if (!pEvent->modifiers() && pEvent->key() == Qt::Key_Z && !pEvent->isAutoRepeat()) { mCamera.SetMoveMode(ECameraMoveMode::Orbit); emit CameraOrbit(); } } void CSceneViewport::keyReleaseEvent(QKeyEvent* pEvent) { CBasicViewport::keyReleaseEvent(pEvent); if (pEvent->key() == Qt::Key_Z && !pEvent->isAutoRepeat()) { mCamera.SetMoveMode(ECameraMoveMode::Free); } } // ************ PROTECTED ************ void CSceneViewport::CreateContextMenu() { mpContextMenu = new QMenu(this); // Main context menu mpToggleSelectAction = new QAction("ToggleSelect", this); connect(mpToggleSelectAction, SIGNAL(triggered()), this, SLOT(OnToggleSelect())); mpHideSelectionSeparator = new QAction(this); mpHideSelectionSeparator->setSeparator(true); mpHideSelectionAction = new QAction("Hide selection", this); connect(mpHideSelectionAction, SIGNAL(triggered()), this, SLOT(OnHideSelection())); mpHideUnselectedAction = new QAction("Hide unselected", this); connect(mpHideUnselectedAction, SIGNAL(triggered()), this, SLOT(OnHideUnselected())); mpHideHoverSeparator = new QAction(this); mpHideHoverSeparator->setSeparator(this); mpHideHoverNodeAction = new QAction("HideNode", this); connect(mpHideHoverNodeAction, SIGNAL(triggered()), this, SLOT(OnHideNode())); mpHideHoverTypeAction = new QAction("HideType", this); connect(mpHideHoverTypeAction, SIGNAL(triggered()), this, SLOT(OnHideType())); mpHideHoverLayerAction = new QAction("HideLayer", this); connect(mpHideHoverLayerAction, SIGNAL(triggered()), this, SLOT(OnHideLayer())); mpUnhideSeparator = new QAction(this); mpUnhideSeparator->setSeparator(true); mpUnhideAllAction = new QAction("Unhide all", this); connect(mpUnhideAllAction, SIGNAL(triggered()), this, SLOT(OnUnhideAll())); QList Actions; Actions << mpToggleSelectAction << mpHideSelectionSeparator << mpHideSelectionAction << mpHideUnselectedAction << mpHideHoverSeparator << mpHideHoverNodeAction << mpHideHoverTypeAction << mpHideHoverLayerAction << mpUnhideSeparator << mpUnhideAllAction; mpContextMenu->addActions(Actions); // Select Connected menu mpSelectConnectedMenu = new QMenu("Select connected...", this); mpSelectConnectedOutgoingAction = new QAction("...via outgoing links", this); connect(mpSelectConnectedOutgoingAction, SIGNAL(triggered()), this, SLOT(OnSelectConnected())); mpSelectConnectedIncomingAction = new QAction("...via incoming links", this); connect(mpSelectConnectedIncomingAction, SIGNAL(triggered()), this, SLOT(OnSelectConnected())); mpSelectConnectedAllAction = new QAction("...via all links", this); connect(mpSelectConnectedAllAction, SIGNAL(triggered()), this, SLOT(OnSelectConnected())); QList SelectConnectedActions; SelectConnectedActions << mpSelectConnectedOutgoingAction << mpSelectConnectedIncomingAction << mpSelectConnectedAllAction; mpSelectConnectedMenu->addActions(SelectConnectedActions); mpContextMenu->insertMenu(mpHideSelectionSeparator, mpSelectConnectedMenu); } QMouseEvent CSceneViewport::CreateMouseEvent() { return QMouseEvent(QEvent::MouseMove, mapFromGlobal(QCursor::pos()), Qt::NoButton, qApp->mouseButtons(), qApp->keyboardModifiers()); } void CSceneViewport::FindConnectedObjects(uint32 InstanceID, bool SearchOutgoing, bool SearchIncoming, QList& rIDList) { CScriptNode *pScript = mpScene->NodeForInstanceID(InstanceID); if (!pScript) return; CScriptObject *pInst = pScript->Instance(); rIDList << InstanceID; if (SearchOutgoing) { for (uint32 iLink = 0; iLink < pInst->NumLinks(ELinkType::Outgoing); iLink++) { CLink *pLink = pInst->Link(ELinkType::Outgoing, iLink); if (!rIDList.contains(pLink->ReceiverID())) FindConnectedObjects(pLink->ReceiverID(), SearchOutgoing, SearchIncoming, rIDList); } } if (SearchIncoming) { for (uint32 iLink = 0; iLink < pInst->NumLinks(ELinkType::Incoming); iLink++) { CLink *pLink = pInst->Link(ELinkType::Incoming, iLink); if (!rIDList.contains(pLink->SenderID())) FindConnectedObjects(pLink->SenderID(), SearchOutgoing, SearchIncoming, rIDList); } } } // ************ PROTECTED SLOTS ************ void CSceneViewport::CheckUserInput() { bool MouseActive = (underMouse() && !IsMouseInputActive()); if (!MouseActive || mViewInfo.GameMode) { ResetHover(); mGizmoHovering = false; } if (MouseActive) { CRay Ray = CastRay(); if (!mViewInfo.GameMode) CheckGizmoInput(Ray); if (!mpEditor->Gizmo()->IsTransforming()) mRayIntersection = SceneRayCast(Ray); } else mRayIntersection = SRayIntersection(); QMouseEvent Event = CreateMouseEvent(); emit InputProcessed(mRayIntersection, &Event); } void CSceneViewport::Paint() { if (!mpScene) return; mpRenderer->SetClearColor(CColor::skBlack); mpRenderer->BeginFrame(); // todo: The sky should really just be a regular node in the background depth group instead of having special rendering code here if ((mViewInfo.ShowFlags & EShowFlag::Sky) || mViewInfo.GameMode) { CModel *pSky = mpScene->ActiveSkybox(); if (pSky) mpRenderer->RenderSky(pSky, mViewInfo); } mCamera.LoadMatrices(); mpScene->AddSceneToRenderer(mpRenderer, mViewInfo); // Add gizmo to renderer if (mpEditor->IsGizmoVisible() && !mViewInfo.GameMode) { CGizmo *pGizmo = mpEditor->Gizmo(); pGizmo->UpdateForCamera(mCamera); pGizmo->AddToRenderer(mpRenderer, mViewInfo); } // Draw grid if the scene is empty if (!mViewInfo.GameMode && mpScene->ActiveArea() == nullptr) mGrid.AddToRenderer(mpRenderer, mViewInfo); // Draw the line for the link the user is editing. if (mLinkLineEnabled) mLinkLine.AddToRenderer(mpRenderer, mViewInfo); mpRenderer->RenderBuckets(mViewInfo); mpRenderer->EndFrame(); } void CSceneViewport::ContextMenu(QContextMenuEvent *pEvent) { // mpHoverNode is cleared during mouse input, so this call is necessary. todo: better way? mRayIntersection = SceneRayCast(CastRay()); // Set up actions TString NodeName; bool HasHoverNode = (mpHoverNode && (mpHoverNode->NodeType() != ENodeType::Static) && (mpHoverNode->NodeType() != ENodeType::Model)); bool HasSelection = mpEditor->HasSelection(); bool IsScriptNode = (mpHoverNode && mpHoverNode->NodeType() == ENodeType::Script); mpToggleSelectAction->setVisible(HasHoverNode); mpSelectConnectedMenu->menuAction()->setVisible(IsScriptNode); mpHideSelectionSeparator->setVisible(HasHoverNode); mpHideSelectionAction->setVisible(HasSelection); mpHideUnselectedAction->setVisible(HasSelection); mpHideHoverSeparator->setVisible(HasSelection); mpHideHoverNodeAction->setVisible(HasHoverNode); mpHideHoverTypeAction->setVisible(IsScriptNode); mpHideHoverLayerAction->setVisible(IsScriptNode); mpUnhideSeparator->setVisible(HasHoverNode); if (HasHoverNode) { TString Name = IsScriptNode ? static_cast(mpHoverNode)->Instance()->InstanceName() : mpHoverNode->Name(); if (mpHoverNode->IsSelected()) mpToggleSelectAction->setText(QString("Deselect %1").arg(TO_QSTRING(Name))); else mpToggleSelectAction->setText(QString("Select %1").arg(TO_QSTRING(Name))); } if (IsScriptNode) { CScriptNode *pScript = static_cast(mpHoverNode); NodeName = pScript->Instance()->InstanceName(); mpHideHoverTypeAction->setText( QString("Hide all %1 objects").arg(TO_QSTRING(pScript->Template()->Name())) ); mpHideHoverLayerAction->setText( QString("Hide layer %1").arg(TO_QSTRING(pScript->Instance()->Layer()->Name())) ); } else if (HasHoverNode) NodeName = mpHoverNode->Name(); mpHideHoverNodeAction->setText(QString("Hide %1").arg(TO_QSTRING(NodeName))); // Show menu mpMenuNode = mpHoverNode; mpContextMenu->exec(pEvent->pos()); } void CSceneViewport::OnResize() { mpRenderer->SetViewportSize(width(), height()); } void CSceneViewport::OnMouseClick(QMouseEvent *pEvent) { bool AltPressed = ((pEvent->modifiers() & Qt::AltModifier) != 0); bool CtrlPressed = ((pEvent->modifiers() & Qt::ControlModifier) != 0); if (mGizmoHovering && !AltPressed && !CtrlPressed) { mGizmoTransforming = true; mpEditor->Gizmo()->StartTransform(); mpEditor->BeginGizmoTransform(); } } void CSceneViewport::OnMouseRelease(QMouseEvent *pEvent) { if (pEvent->button() == Qt::LeftButton) { // Stop gizmo transform if (mGizmoTransforming) { CGizmo *pGizmo = mpEditor->Gizmo(); pGizmo->EndTransform(); mpEditor->EndGizmoTransform(); mGizmoTransforming = false; } // Object selection/deselection else emit ViewportClick(mRayIntersection, pEvent); } } // ************ MENU ACTIONS ************ void CSceneViewport::OnToggleSelect() { if (mpMenuNode->IsSelected()) mpEditor->DeselectNode(mpMenuNode); else mpEditor->SelectNode(mpMenuNode); } void CSceneViewport::OnSelectConnected() { QList InstanceIDs; bool SearchOutgoing = (sender() == mpSelectConnectedOutgoingAction || sender() == mpSelectConnectedAllAction); bool SearchIncoming = (sender() == mpSelectConnectedIncomingAction || sender() == mpSelectConnectedAllAction); FindConnectedObjects(static_cast(mpMenuNode)->Instance()->InstanceID(), SearchOutgoing, SearchIncoming, InstanceIDs); QList Nodes; foreach (uint32 ID, InstanceIDs) Nodes << mpScene->NodeForInstanceID(ID); bool ShouldClear = ((qApp->keyboardModifiers() & Qt::ControlModifier) == 0); mpEditor->BatchSelectNodes(Nodes, ShouldClear, "Select Connected"); } void CSceneViewport::OnHideSelection() { for (CSelectionIterator It(mpEditor->Selection()); It; ++It) It->SetVisible(false); } void CSceneViewport::OnHideUnselected() { for (CSceneIterator It(mpScene, ENodeType::Script | ENodeType::Light); !It.DoneIterating(); ++It) if (!It->IsSelected()) It->SetVisible(false); } void CSceneViewport::OnHideNode() { mpMenuNode->SetVisible(false); } void CSceneViewport::OnHideType() { static_cast(mpMenuNode)->Template()->SetVisible(false); } void CSceneViewport::OnHideLayer() { static_cast(mpMenuNode)->Instance()->Layer()->SetVisible(false); } void CSceneViewport::OnUnhideAll() { CSceneIterator it(mpScene, ENodeType::Script | ENodeType::Light, true); while (!it.DoneIterating()) { if (!it->IsVisible()) { if (it->NodeType() == ENodeType::Light) it->SetVisible(true); else { CScriptNode *pScript = static_cast(*it); if (!pScript->MarkedVisible()) pScript->SetVisible(true); else { pScript->Template()->SetVisible(true); pScript->Instance()->Layer()->SetVisible(true); } } } ++it; } } void CSceneViewport::OnContextMenuClose() { mpContextMenu = nullptr; mpMenuNode = nullptr; }