#include "CWorldEditor.h" #include "ui_CWorldEditor.h" #include "CEditorGLWidget.h" #include #include #include #include #include #include #include #include "WDraggableSpinBox.h" #include "WorldEditor/CLayerEditor.h" #include "WorldEditor/WModifyTab.h" #include "WorldEditor/WInstancesTab.h" CWorldEditor::CWorldEditor(QWidget *parent) : QMainWindow(parent), ui(new Ui::CWorldEditor) { Log::Write("Creating World Editor"); ui->setupUi(this); mpRenderer = new CRenderer(); mpRenderer->SetClearColor(CColor::skBlack); QSize ViewSize = ui->MainViewport->size(); mpRenderer->SetViewportSize(ViewSize.width(), ViewSize.height()); mpSceneManager = new CSceneManager(); mpArea = nullptr; mpWorld = nullptr; mpHoverNode = nullptr; mDrawSky = true; mShowGizmo = false; mGizmoHovering = false; mGizmoTransforming = false; mGizmoUIOutdated = true; mFrameCount = 0; mFPSTimer.Start(); // Create blank title bar with some space to allow for dragging the dock QWidget *pOldTitleBar = ui->MainDock->titleBarWidget(); QWidget *pNewTitleBar = new QWidget(ui->MainDock); QVBoxLayout *pTitleLayout = new QVBoxLayout(pNewTitleBar); pTitleLayout->setSpacing(10); pNewTitleBar->setLayout(pTitleLayout); ui->MainDock->setTitleBarWidget(pNewTitleBar); delete pOldTitleBar; // Initialize UI stuff ui->ModifyTabContents->SetEditor(this); ui->InstancesTabContents->SetEditor(this, mpSceneManager); ui->MainDock->installEventFilter(this); 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() { delete ui; } bool CWorldEditor::eventFilter(QObject *pObj, QEvent *pEvent) { if (pObj == ui->MainDock) { if (pEvent->type() == QEvent::Resize) { UpdateSelectionUI(); } } return false; } void CWorldEditor::SetArea(CWorld *pWorld, CGameArea *pArea) { ResetHover(); ClearSelection(); ui->ModifyTabContents->ClearUI(); ui->ModifyTabContents->ClearCachedEditors(); ui->InstancesTabContents->SetMaster(nullptr); ui->InstancesTabContents->SetArea(pArea); // Clear old area - hack until better world/area loader is implemented if ((mpArea) && (pArea != mpArea)) mpArea->ClearScriptLayers(); // Load new area mpArea = pArea; mpWorld = pWorld; mAreaToken = CToken(pArea); mWorldToken = CToken(pWorld); mpSceneManager->SetActiveWorld(pWorld); mpSceneManager->SetActiveArea(pArea); // Snap camera to location of area CTransform4f AreaTransform = pArea->GetTransform(); CVector3f AreaPosition(AreaTransform[0][3], AreaTransform[1][3], AreaTransform[2][3]); ui->MainViewport->Camera().Snap(AreaPosition); // Default bloom to Fake Bloom for Metroid Prime 3; disable for other games if (mpWorld->Version() == eCorruption) { ui->menuBloom->setEnabled(true); on_ActionFakeBloom_triggered(); } else { ui->menuBloom->setEnabled(false); on_ActionNoBloom_triggered(); } // Set up sidebar tabs CMasterTemplate *pMaster = CMasterTemplate::GetMasterForGame(mpWorld->Version()); ui->InstancesTabContents->SetMaster(pMaster); } 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); else { mGizmoHovering = false; mGizmo.ResetSelectedAxes(); } // Scene ray check SRayIntersection Result = mpSceneManager->SceneRayCast(ray); if (Result.Hit) { if (mpHoverNode) mpHoverNode->SetMouseHovering(false); mpHoverNode = Result.pNode; mpHoverNode->SetMouseHovering(true); mHoverPoint = ray.PointOnRay(Result.Distance); } else ResetHover(); } else { bool transformed = mGizmo.TransformFromInput(ray, ui->MainViewport->Camera()); if (transformed) { switch (mGizmo.Mode()) { 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(); mGizmoUIOutdated = true; } } } else { if (!mGizmoTransforming) { mGizmoHovering = false; mGizmo.ResetSelectedAxes(); } ResetHover(); } } CRenderer* CWorldEditor::Renderer() { return mpRenderer; } CSceneManager* CWorldEditor::Scene() { return mpSceneManager; } CGameArea* CWorldEditor::ActiveArea() { return mpArea; } // ************ SELECTION ************ void CWorldEditor::SelectNode(CSceneNode *pNode) { if (!pNode->IsSelected()) { pNode->SetSelected(true); mSelectedNodes.push_back(pNode); mSelectionAABox.ExpandBounds(pNode->AABox()); if (pNode->NodeType() == eScriptNode) { CScriptNode *pScript = static_cast(pNode); if (pScript->HasPreviewVolume()) mSelectionAABox.ExpandBounds(pScript->PreviewVolumeAABox()); } } UpdateSelectionUI(); } void CWorldEditor::DeselectNode(CSceneNode *pNode) { if (pNode->IsSelected()) { pNode->SetSelected(false); for (auto it = mSelectedNodes.begin(); it != mSelectedNodes.end(); it++) { if (*it == pNode) { mSelectedNodes.erase(it); break; } } } RecalculateSelectionBounds(); UpdateSelectionUI(); } void CWorldEditor::ClearSelection() { for (auto it = mSelectedNodes.begin(); it != mSelectedNodes.end(); it++) (*it)->SetSelected(false); mSelectedNodes.clear(); mSelectionAABox = CAABox::skInfinite; UpdateSelectionUI(); } // ************ SLOTS ************ void CWorldEditor::ViewportMouseDrag(QMouseEvent *pEvent) { } void CWorldEditor::ViewportMouseClick(QMouseEvent *pEvent) { 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) { // Gizmo transform stop if (mGizmoTransforming) { mGizmo.EndTransform(); mGizmoTransforming = false; mGizmoUIOutdated = true; } // Object selection/deselection else if (!ui->MainViewport->IsMouseInputActive()) { 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) { // 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(); } } // todo: context menu creation on right-click goes here } // ************ SLOTS ************ void CWorldEditor::ViewportPreRender() { // Perform raycast if (ui->MainViewport->underMouse()) ViewportRayCast(); else ResetHover(); // Start frame mFrameTimer.Start(); mpRenderer->BeginFrame(); } void CWorldEditor::ViewportRender(CCamera& Camera) { mpSceneManager->AddSceneToRenderer(mpRenderer); if (mDrawSky) { CModel *pSky = mpSceneManager->GetActiveSkybox(); if (pSky) mpRenderer->RenderSky(pSky, Camera); } Camera.LoadMatrices(); mpRenderer->RenderBuckets(Camera); mpRenderer->RenderBloom(); if (mShowGizmo && (mSelectedNodes.size() > 0)) { mpRenderer->ClearDepthBuffer(); Camera.LoadMatrices(); mGizmo.UpdateForCamera(Camera); mGizmo.AddToRenderer(mpRenderer); mpRenderer->RenderBuckets(Camera); mpRenderer->ClearDepthBuffer(); } mpRenderer->EndFrame(); mFrameTimer.Stop(); mFrameCount++; } void CWorldEditor::ViewportPostRender() { // Update UI with raycast results UpdateCursor(); UpdateStatusBar(); if (mGizmoUIOutdated) UpdateGizmoUI(); } void CWorldEditor::SetViewportSize(int Width, int Height) { mpRenderer->SetViewportSize(Width, Height); } void CWorldEditor::SetTransformSpace(int space) { switch (space) { case 0: mTransformSpace = eWorldTransform; mGizmo.SetRotation(CQuaternion::skIdentity); break; case 1: mTransformSpace = eLocalTransform; if (!mSelectedNodes.empty()) mGizmo.SetRotation(mSelectedNodes.front()->AbsoluteRotation()); break; } mGizmo.SetTransformSpace(mTransformSpace); } // ************ PRIVATE ************ void CWorldEditor::RecalculateSelectionBounds() { mSelectionAABox = CAABox::skInfinite; for (auto it = mSelectedNodes.begin(); it != mSelectedNodes.end(); it++) { mSelectionAABox.ExpandBounds( (*it)->AABox() ); if ((*it)->NodeType() == eScriptNode) { CScriptNode *pScript = static_cast(*it); if (pScript->HasPreviewVolume()) mSelectionAABox.ExpandBounds(pScript->PreviewVolumeAABox()); } } } void CWorldEditor::ResetHover() { if (mpHoverNode) mpHoverNode->SetMouseHovering(false); mpHoverNode = nullptr; mHoverPoint = CVector3f::skZero; } void CWorldEditor::UpdateCursor() { if (ui->MainViewport->IsCursorVisible()) { if (mGizmoHovering) ui->MainViewport->SetCursorState(Qt::SizeAllCursor); else if ((mpHoverNode) && (mpHoverNode->NodeType() != eStaticNode)) ui->MainViewport->SetCursorState(Qt::PointingHandCursor); else ui->MainViewport->SetCursorState(Qt::ArrowCursor); } } void CWorldEditor::UpdateStatusBar() { // Would be cool to do more frequent status bar updates with more info. Unfortunately, this causes lag. QString StatusText = ""; if (!mGizmoHovering && mpHoverNode) { if (mpHoverNode->NodeType() != eStaticNode) StatusText = QString::fromStdString(mpHoverNode->Name()); } if (ui->statusbar->currentMessage() != StatusText) ui->statusbar->showMessage(StatusText); } void CWorldEditor::UpdateSelectionUI() { // Update sidebar ui->ModifyTabContents->GenerateUI(mSelectedNodes); // Update selection info text QString SelectionText; if (mSelectedNodes.size() == 1) SelectionText = QString::fromStdString(mSelectedNodes.front()->Name()); else if (mSelectedNodes.size() > 1) SelectionText = QString("%1 objects selected").arg(mSelectedNodes.size()); QFontMetrics Metrics(ui->SelectionInfoLabel->font()); SelectionText = Metrics.elidedText(SelectionText, Qt::ElideRight, ui->SelectionInfoFrame->width() - 10); ui->SelectionInfoLabel->setText(SelectionText); // 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) { if (!mSelectedNodes.empty()) mGizmo.SetPosition(mSelectedNodes.front()->AbsolutePosition()); if ((mTransformSpace == eLocalTransform) && !mSelectedNodes.empty()) mGizmo.SetRotation(mSelectedNodes.front()->AbsoluteRotation()); else mGizmo.SetRotation(CQuaternion::skIdentity); } mGizmoUIOutdated = false; } // ************ ACTIONS ************ void CWorldEditor::OnCameraSpeedChange(double speed) { static const double skDefaultSpeed = 1.0; ui->MainViewport->Camera().SetMoveSpeed(skDefaultSpeed * speed); ui->CamSpeedSpinBox->blockSignals(true); ui->CamSpeedSpinBox->setValue(speed); ui->CamSpeedSpinBox->blockSignals(false); } // These functions are from "Go to slot" in the designer void CWorldEditor::on_ActionDrawWorld_triggered() { mpSceneManager->SetWorld(ui->ActionDrawWorld->isChecked()); } void CWorldEditor::on_ActionDrawCollision_triggered() { mpSceneManager->SetCollision(ui->ActionDrawCollision->isChecked()); } void CWorldEditor::on_ActionDrawObjects_triggered() { mpSceneManager->SetObjects(ui->ActionDrawObjects->isChecked()); } void CWorldEditor::on_ActionDrawLights_triggered() { mpSceneManager->SetLights(ui->ActionDrawLights->isChecked()); } void CWorldEditor::on_ActionDrawSky_triggered() { mDrawSky = ui->ActionDrawSky->isChecked(); } void CWorldEditor::on_ActionNoLighting_triggered() { CGraphics::sLightMode = CGraphics::NoLighting; ui->ActionNoLighting->setChecked(true); ui->ActionBasicLighting->setChecked(false); ui->ActionWorldLighting->setChecked(false); } void CWorldEditor::on_ActionBasicLighting_triggered() { CGraphics::sLightMode = CGraphics::BasicLighting; ui->ActionNoLighting->setChecked(false); ui->ActionBasicLighting->setChecked(true); ui->ActionWorldLighting->setChecked(false); } void CWorldEditor::on_ActionWorldLighting_triggered() { CGraphics::sLightMode = CGraphics::WorldLighting; ui->ActionNoLighting->setChecked(false); ui->ActionBasicLighting->setChecked(false); ui->ActionWorldLighting->setChecked(true); } void CWorldEditor::on_ActionNoBloom_triggered() { mpRenderer->SetBloom(CRenderer::eNoBloom); ui->ActionNoBloom->setChecked(true); ui->ActionBloomMaps->setChecked(false); ui->ActionFakeBloom->setChecked(false); ui->ActionBloom->setChecked(false); } void CWorldEditor::on_ActionBloomMaps_triggered() { mpRenderer->SetBloom(CRenderer::eBloomMaps); ui->ActionNoBloom->setChecked(false); ui->ActionBloomMaps->setChecked(true); ui->ActionFakeBloom->setChecked(false); ui->ActionBloom->setChecked(false); } void CWorldEditor::on_ActionFakeBloom_triggered() { mpRenderer->SetBloom(CRenderer::eFakeBloom); ui->ActionNoBloom->setChecked(false); ui->ActionBloomMaps->setChecked(false); ui->ActionFakeBloom->setChecked(true); ui->ActionBloom->setChecked(false); } void CWorldEditor::on_ActionBloom_triggered() { mpRenderer->SetBloom(CRenderer::eBloom); ui->ActionNoBloom->setChecked(false); ui->ActionBloomMaps->setChecked(false); ui->ActionFakeBloom->setChecked(false); ui->ActionBloom->setChecked(true); } void CWorldEditor::on_ActionZoomOnSelection_triggered() { static const float skDistScale = 2.5f; static const float skAreaDistScale = 0.8f; CCamera& Camera = ui->MainViewport->Camera(); CVector3f CamDir = Camera.GetDirection(); CVector3f NewPos; // Zoom on selection if (mSelectedNodes.size() != 0) { CVector3f Min = mSelectionAABox.Min(); CVector3f Max = mSelectionAABox.Max(); float Dist = ((Max.x - Min.x) + (Max.y - Min.y) + (Max.z - Min.z)) / 3.f; //float Dist = mSelectionAABox.Min().Distance(mSelectionAABox.Max()); NewPos = mSelectionAABox.Center() + (CamDir * -(Dist * skDistScale)); } // Zoom on area else { CAABox AreaBox = mpArea->AABox(); CVector3f Min = AreaBox.Min(); CVector3f Max = AreaBox.Max(); float Dist = ((Max.x - Min.x) + (Max.y - Min.y) + (Max.z - Min.z)) / 3.f; //float Dist = AreaBox.Min().Distance(AreaBox.Max()); NewPos = AreaBox.Center() + (CamDir * -(Dist * skAreaDistScale)); } Camera.SetPosition(NewPos); } void CWorldEditor::on_ActionDisableBackfaceCull_triggered() { mpRenderer->ToggleBackfaceCull(!ui->ActionDisableBackfaceCull->isChecked()); } void CWorldEditor::on_ActionDisableAlpha_triggered() { mpRenderer->ToggleAlphaDisabled(ui->ActionDisableAlpha->isChecked()); } void CWorldEditor::on_ActionEditLayers_triggered() { // Launch layer editor CLayerEditor Editor(this); Editor.SetArea(mpArea); Editor.exec(); } void CWorldEditor::on_ActionSelectObjects_triggered() { mShowGizmo = false; mGizmoUIOutdated = true; ui->ActionSelectObjects->setChecked(true); ui->ActionTranslate->setChecked(false); ui->ActionRotate->setChecked(false); ui->ActionScale->setChecked(false); } void CWorldEditor::on_ActionTranslate_triggered() { mShowGizmo = true; mGizmoUIOutdated = true; mGizmo.SetMode(CGizmo::eTranslate); ui->ActionSelectObjects->setChecked(false); ui->ActionTranslate->setChecked(true); ui->ActionRotate->setChecked(false); ui->ActionScale->setChecked(false); } void CWorldEditor::on_ActionRotate_triggered() { mShowGizmo = true; mGizmoUIOutdated = true; mGizmo.SetMode(CGizmo::eRotate); ui->ActionSelectObjects->setChecked(false); ui->ActionTranslate->setChecked(false); ui->ActionRotate->setChecked(true); ui->ActionScale->setChecked(false); } void CWorldEditor::on_ActionScale_triggered() { mShowGizmo = true; mGizmoUIOutdated = true; mGizmo.SetMode(CGizmo::eScale); ui->ActionSelectObjects->setChecked(false); ui->ActionTranslate->setChecked(false); ui->ActionRotate->setChecked(false); ui->ActionScale->setChecked(true); } void CWorldEditor::on_ActionIncrementGizmo_triggered() { mGizmo.IncrementSize(); } void CWorldEditor::on_ActionDecrementGizmo_triggered() { mGizmo.DecrementSize(); }