PrimeWorldEditor/UI/CWorldEditor.cpp

473 lines
14 KiB
C++
Raw Normal View History

#include "CWorldEditor.h"
#include "ui_CWorldEditor.h"
#include "CBasicViewport.h"
#include <gtc/matrix_transform.hpp>
#include <Core/CDrawUtil.h>
#include <iostream>
#include <QOpenGLContext>
#include <QFontMetrics>
#include <QComboBox>
#include <Core/Log.h>
#include "WDraggableSpinBox.h"
#include "WVectorEditor.h"
#include "undo/UndoCommands.h"
#include "WorldEditor/CLayerEditor.h"
#include "WorldEditor/WModifyTab.h"
#include "WorldEditor/WInstancesTab.h"
CWorldEditor::CWorldEditor(QWidget *parent) :
INodeEditor(parent),
ui(new Ui::CWorldEditor)
{
Log::Write("Creating World Editor");
ui->setupUi(this);
mpArea = nullptr;
mpWorld = nullptr;
mGizmoHovering = false;
mGizmoTransforming = false;
// Start refresh timer
connect(&mRefreshTimer, SIGNAL(timeout()), this, SLOT(RefreshViewport()));
mRefreshTimer.start(0);
// 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->MainViewport->SetScene(this, &mScene);
ui->ModifyTabContents->SetEditor(this);
ui->InstancesTabContents->SetEditor(this, &mScene);
ui->MainDock->installEventFilter(this);
ui->TransformSpinBox->SetOrientation(Qt::Horizontal);
ui->TransformSpinBox->layout()->setContentsMargins(0,0,0,0);
ui->CamSpeedSpinBox->SetDefaultValue(1.0);
mpTransformCombo->setMinimumWidth(75);
ui->MainToolBar->addActions(mGizmoActions);
ui->MainToolBar->addWidget(mpTransformCombo);
ui->menuEdit->addActions(mUndoActions);
// Initialize offscreen actions
addAction(ui->ActionIncrementGizmo);
addAction(ui->ActionDecrementGizmo);
// Connect signals and slots
connect(ui->MainViewport, SIGNAL(GizmoMoved()), this, SLOT(OnGizmoMoved()));
connect(ui->TransformSpinBox, SIGNAL(ValueChanged(CVector3f)), this, SLOT(OnTransformSpinBoxModified(CVector3f)));
connect(ui->TransformSpinBox, SIGNAL(EditingDone(CVector3f)), this, SLOT(OnTransformSpinBoxEdited(CVector3f)));
connect(ui->CamSpeedSpinBox, SIGNAL(valueChanged(double)), this, SLOT(OnCameraSpeedChange(double)));
}
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)
{
ui->MainViewport->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);
mScene.SetActiveWorld(pWorld);
mScene.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);
}
CGameArea* CWorldEditor::ActiveArea()
{
return mpArea;
}
// ************ SLOTS ************
void CWorldEditor::UpdateCursor()
{
if (ui->MainViewport->IsCursorVisible())
{
CSceneNode *pHoverNode = ui->MainViewport->HoverNode();
if (ui->MainViewport->IsHoveringGizmo())
2015-08-17 18:10:42 +00:00
ui->MainViewport->SetCursorState(Qt::SizeAllCursor);
else if ((pHoverNode) && (pHoverNode->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)
{
if (ui->MainViewport->underMouse())
{
CSceneNode *pHoverNode = ui->MainViewport->HoverNode();
if (pHoverNode && (pHoverNode->NodeType() != eStaticNode))
StatusText = QString::fromStdString(pHoverNode->Name());
}
}
if (ui->statusbar->currentMessage() != StatusText)
ui->statusbar->showMessage(StatusText);
}
void CWorldEditor::UpdateSelectionUI()
{
// Update sidebar
ui->ModifyTabContents->GenerateUI(mSelection);
// Update selection info text
QString SelectionText;
if (mSelection.size() == 1)
SelectionText = QString::fromStdString(mSelection.front()->Name());
else if (mSelection.size() > 1)
SelectionText = QString("%1 objects selected").arg(mSelection.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
if (!ui->TransformSpinBox->IsBeingDragged())
{
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 (!mSelection.empty())
spinBoxValue = mSelection.front()->AbsolutePosition();
break;
case CGizmo::eRotate:
if (mGizmoTransforming && mGizmo.HasTransformed())
spinBoxValue = mGizmo.TotalRotation();
else if (!mSelection.empty())
spinBoxValue = mSelection.front()->AbsoluteRotation().ToEuler();
break;
case CGizmo::eScale:
if (mGizmoTransforming && mGizmo.HasTransformed())
spinBoxValue = mGizmo.TotalScale();
else if (!mSelection.empty())
spinBoxValue = mSelection.front()->AbsoluteScale();
break;
}
}
else if (!mSelection.empty()) spinBoxValue = mSelection.front()->AbsolutePosition();
ui->TransformSpinBox->blockSignals(true);
ui->TransformSpinBox->SetValue(spinBoxValue);
ui->TransformSpinBox->blockSignals(false);
}
// Update gizmo
if (!mGizmoTransforming)
{
// Set gizmo transform
if (!mSelection.empty())
{
mGizmo.SetPosition(mSelection.front()->AbsolutePosition());
mGizmo.SetLocalRotation(mSelection.front()->AbsoluteRotation());
}
}
}
void CWorldEditor::GizmoModeChanged(CGizmo::EGizmoMode mode)
{
ui->TransformSpinBox->SetSingleStep( (mode == CGizmo::eRotate ? 1.0 : 0.1) );
ui->TransformSpinBox->SetDefaultValue( (mode == CGizmo::eScale ? 1.0 : 0.0) );
}
// ************ ACTIONS ************
void CWorldEditor::RefreshViewport()
{
if (!mGizmo.IsTransforming())
mGizmo.ResetSelectedAxes();
// Process input + update UI
ui->MainViewport->ProcessInput();
UpdateCursor();
UpdateStatusBar();
UpdateGizmoUI();
// Render
ui->MainViewport->Render();
}
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);
}
void CWorldEditor::OnTransformSpinBoxModified(CVector3f value)
{
if (mSelection.empty()) return;
switch (mGizmo.Mode())
{
case CGizmo::eTranslate:
{
CVector3f delta = value - mSelection.front()->AbsolutePosition();
mUndoStack.push(new CTranslateNodeCommand(this, mSelection, delta, mTranslateSpace));
break;
}
case CGizmo::eRotate:
{
CQuaternion delta = CQuaternion::FromEuler(value) * mSelection.front()->AbsoluteRotation().Inverse();
mUndoStack.push(new CRotateNodeCommand(this, mSelection, CVector3f::skZero, delta, mRotateSpace));
break;
}
case CGizmo::eScale:
{
CVector3f delta = value / mSelection.front()->AbsoluteScale();
mUndoStack.push(new CScaleNodeCommand(this, mSelection, CVector3f::skZero, delta));
break;
}
}
RecalculateSelectionBounds();
UpdateGizmoUI();
}
void CWorldEditor::OnTransformSpinBoxEdited(CVector3f)
{
// bit of a hack - the vector editor emits a second "editing done" signal when it loses focus
ui->TransformSpinBox->blockSignals(true);
ui->MainViewport->setFocus();
ui->TransformSpinBox->blockSignals(false);
if (mSelection.empty()) return;
if (mGizmo.Mode() == CGizmo::eTranslate) mUndoStack.push(CTranslateNodeCommand::End());
else if (mGizmo.Mode() == CGizmo::eRotate) mUndoStack.push(CRotateNodeCommand::End());
else if (mGizmo.Mode() == CGizmo::eScale) mUndoStack.push(CScaleNodeCommand::End());
UpdateGizmoUI();
}
// These functions are from "Go to slot" in the designer
void CWorldEditor::on_ActionDrawWorld_triggered()
{
mScene.SetWorld(ui->ActionDrawWorld->isChecked());
}
void CWorldEditor::on_ActionDrawCollision_triggered()
{
mScene.SetCollision(ui->ActionDrawCollision->isChecked());
}
void CWorldEditor::on_ActionDrawObjects_triggered()
{
mScene.SetObjects(ui->ActionDrawObjects->isChecked());
}
void CWorldEditor::on_ActionDrawLights_triggered()
{
mScene.SetLights(ui->ActionDrawLights->isChecked());
}
void CWorldEditor::on_ActionDrawSky_triggered()
{
ui->MainViewport->SetSkyEnabled(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()
{
ui->MainViewport->Renderer()->SetBloom(CRenderer::eNoBloom);
ui->ActionNoBloom->setChecked(true);
ui->ActionBloomMaps->setChecked(false);
ui->ActionFakeBloom->setChecked(false);
ui->ActionBloom->setChecked(false);
}
void CWorldEditor::on_ActionBloomMaps_triggered()
{
ui->MainViewport->Renderer()->SetBloom(CRenderer::eBloomMaps);
ui->ActionNoBloom->setChecked(false);
ui->ActionBloomMaps->setChecked(true);
ui->ActionFakeBloom->setChecked(false);
ui->ActionBloom->setChecked(false);
}
void CWorldEditor::on_ActionFakeBloom_triggered()
{
ui->MainViewport->Renderer()->SetBloom(CRenderer::eFakeBloom);
ui->ActionNoBloom->setChecked(false);
ui->ActionBloomMaps->setChecked(false);
ui->ActionFakeBloom->setChecked(true);
ui->ActionBloom->setChecked(false);
}
void CWorldEditor::on_ActionBloom_triggered()
{
ui->MainViewport->Renderer()->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.Direction();
CVector3f NewPos;
// Zoom on selection
if (mSelection.size() != 0)
{
CVector3f Min = mSelectionBounds.Min();
CVector3f Max = mSelectionBounds.Max();
float Dist = ((Max.x - Min.x) + (Max.y - Min.y) + (Max.z - Min.z)) / 3.f;
NewPos = mSelectionBounds.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;
NewPos = AreaBox.Center() + (CamDir * -(Dist * skAreaDistScale));
}
Camera.SetPosition(NewPos);
}
void CWorldEditor::on_ActionDisableBackfaceCull_triggered()
{
ui->MainViewport->Renderer()->ToggleBackfaceCull(!ui->ActionDisableBackfaceCull->isChecked());
}
void CWorldEditor::on_ActionDisableAlpha_triggered()
{
ui->MainViewport->Renderer()->ToggleAlphaDisabled(ui->ActionDisableAlpha->isChecked());
}
void CWorldEditor::on_ActionEditLayers_triggered()
{
// Launch layer editor
CLayerEditor Editor(this);
Editor.SetArea(mpArea);
Editor.exec();
}
void CWorldEditor::on_ActionIncrementGizmo_triggered()
{
mGizmo.IncrementSize();
}
void CWorldEditor::on_ActionDecrementGizmo_triggered()
{
mGizmo.DecrementSize();
}