From 5c3a37ca4ae0009bb523a543a7ce3c4b6c0f7c3d Mon Sep 17 00:00:00 2001 From: parax0 Date: Sat, 16 Jan 2016 01:13:27 -0700 Subject: [PATCH] Added support for editing and resaving EGMC files + improved its preview rendering --- src/Core/Core.pro | 6 +- src/Core/Resource/CPoiToWorld.cpp | 68 ++++++- src/Core/Resource/CPoiToWorld.h | 26 +-- .../Resource/Cooker/CPoiToWorldCooker.cpp | 36 ++++ src/Core/Resource/Cooker/CPoiToWorldCooker.h | 14 ++ src/Core/Resource/Factory/CModelLoader.cpp | 7 +- .../Resource/Factory/CPoiToWorldLoader.cpp | 11 +- src/Core/Scene/CModelNode.cpp | 13 ++ src/Core/Scene/CModelNode.h | 22 ++- src/Editor/CSceneViewport.cpp | 36 +--- src/Editor/CSceneViewport.h | 1 + src/Editor/INodeEditor.cpp | 79 +++++++++ src/Editor/INodeEditor.h | 17 ++ src/Editor/WorldEditor/CPoiMapEditDialog.cpp | 166 ++++++++++++++++-- src/Editor/WorldEditor/CPoiMapEditDialog.h | 22 ++- src/Editor/WorldEditor/CPoiMapEditDialog.ui | 55 +++++- src/Editor/WorldEditor/CPoiMapModel.cpp | 119 ++++++++++--- src/Editor/WorldEditor/CPoiMapModel.h | 18 +- src/Editor/WorldEditor/CWorldEditor.cpp | 21 ++- src/Editor/WorldEditor/CWorldEditor.h | 2 + 20 files changed, 605 insertions(+), 134 deletions(-) create mode 100644 src/Core/Resource/Cooker/CPoiToWorldCooker.cpp create mode 100644 src/Core/Resource/Cooker/CPoiToWorldCooker.h diff --git a/src/Core/Core.pro b/src/Core/Core.pro index 57c03b4e..1806147a 100644 --- a/src/Core/Core.pro +++ b/src/Core/Core.pro @@ -184,7 +184,8 @@ HEADERS += \ Scene/CSceneIterator.h \ Resource/CResourceInfo.h \ Resource/CPoiToWorld.h \ - Resource/Factory/CPoiToWorldLoader.h + Resource/Factory/CPoiToWorldLoader.h \ + Resource/Cooker/CPoiToWorldCooker.h # Source Files SOURCES += \ @@ -272,4 +273,5 @@ SOURCES += \ Scene/CScene.cpp \ Scene/CSceneIterator.cpp \ Resource/CPoiToWorld.cpp \ - Resource/Factory/CPoiToWorldLoader.cpp + Resource/Factory/CPoiToWorldLoader.cpp \ + Resource/Cooker/CPoiToWorldCooker.cpp diff --git a/src/Core/Resource/CPoiToWorld.cpp b/src/Core/Resource/CPoiToWorld.cpp index 3ba9d389..5ec63606 100644 --- a/src/Core/Resource/CPoiToWorld.cpp +++ b/src/Core/Resource/CPoiToWorld.cpp @@ -6,22 +6,74 @@ CPoiToWorld::CPoiToWorld() CPoiToWorld::~CPoiToWorld() { + for (auto it = mMaps.begin(); it != mMaps.end(); it++) + delete *it; } -void CPoiToWorld::LinksForMeshID(std::list& rOutInstanceIDs, u32 MeshID) +void CPoiToWorld::AddPoi(u32 PoiID) { - for (u32 iLink = 0; iLink < mMeshLinks.size(); iLink++) + // Check if this POI already exists + auto it = mPoiLookupMap.find(PoiID); + + if (it == mPoiLookupMap.end()) { - if (mMeshLinks[iLink].MeshID == MeshID) - rOutInstanceIDs.push_back(mMeshLinks[iLink].PoiInstanceID); + SPoiMap *pMap = new SPoiMap(); + pMap->PoiID = PoiID; + + mMaps.push_back(pMap); + mPoiLookupMap[PoiID] = pMap; } } -void CPoiToWorld::LinksForInstanceID(std::list& rOutMeshIDs, u32 InstanceID) +void CPoiToWorld::AddPoiMeshMap(u32 PoiID, u32 ModelID) { - for (u32 iLink = 0; iLink < mMeshLinks.size(); iLink++) + // Make sure the POI exists; the add function won't do anything if it does + AddPoi(PoiID); + SPoiMap *pMap = mPoiLookupMap[PoiID]; + + // Check whether this model ID is already mapped to this POI + for (auto it = pMap->ModelIDs.begin(); it != pMap->ModelIDs.end(); it++) { - if (mMeshLinks[iLink].PoiInstanceID == InstanceID) - rOutMeshIDs.push_back(mMeshLinks[iLink].MeshID); + if (*it == ModelID) + return; + } + + // We didn't return, so this is a new mapping + pMap->ModelIDs.push_back(ModelID); +} + +void CPoiToWorld::RemovePoi(u32 PoiID) +{ + for (auto it = mMaps.begin(); it != mMaps.end(); it++) + { + if ((*it)->PoiID == PoiID) + { + mMaps.erase(it); + mPoiLookupMap.erase(PoiID); + return; + } + } +} + +void CPoiToWorld::RemovePoiMeshMap(u32 PoiID, u32 ModelID) +{ + auto MapIt = mPoiLookupMap.find(PoiID); + + if (MapIt != mPoiLookupMap.end()) + { + SPoiMap *pMap = MapIt->second; + + for (auto ListIt = pMap->ModelIDs.begin(); ListIt != pMap->ModelIDs.end(); ListIt++) + { + if (*ListIt == ModelID) + { + pMap->ModelIDs.erase(ListIt); + + if (pMap->ModelIDs.empty()) + RemovePoi(PoiID); + + break; + } + } } } diff --git a/src/Core/Resource/CPoiToWorld.h b/src/Core/Resource/CPoiToWorld.h index e2c00954..f426cad6 100644 --- a/src/Core/Resource/CPoiToWorld.h +++ b/src/Core/Resource/CPoiToWorld.h @@ -3,37 +3,41 @@ #include "CResource.h" #include +#include +#include class CPoiToWorld : public CResource { DECLARE_RESOURCE_TYPE(ePoiToWorld) - friend class CPoiToWorldLoader; public: - struct SPoiMeshLink + struct SPoiMap { - u32 MeshID; - u32 PoiInstanceID; + u32 PoiID; + std::list ModelIDs; }; private: - std::vector mMeshLinks; + std::vector mMaps; + std::map mPoiLookupMap; public: CPoiToWorld(); ~CPoiToWorld(); - void LinksForMeshID(std::list& rOutInstanceIDs, u32 MeshID); - void LinksForInstanceID(std::list& rOutMeshIDs, u32 InstanceID); + void AddPoi(u32 PoiID); + void AddPoiMeshMap(u32 PoiID, u32 ModelID); + void RemovePoi(u32 PoiID); + void RemovePoiMeshMap(u32 PoiID, u32 ModelID); - inline u32 NumMeshLinks() + inline u32 NumMappedPOIs() const { - return mMeshLinks.size(); + return mMaps.size(); } - inline const SPoiMeshLink& MeshLinkByIndex(u32 Index) + inline const SPoiMap* MapByIndex(u32 Index) const { - return mMeshLinks[Index]; + return mMaps[Index]; } }; diff --git a/src/Core/Resource/Cooker/CPoiToWorldCooker.cpp b/src/Core/Resource/Cooker/CPoiToWorldCooker.cpp new file mode 100644 index 00000000..407d8acf --- /dev/null +++ b/src/Core/Resource/Cooker/CPoiToWorldCooker.cpp @@ -0,0 +1,36 @@ +#include "CPoiToWorldCooker.h" + +void CPoiToWorldCooker::WriteEGMC(CPoiToWorld *pPoiToWorld, IOutputStream& rOut) +{ + // Create mappings list + struct SPoiMapping + { + u32 MeshID; + u32 PoiID; + }; + std::vector Mappings; + + for (u32 iPoi = 0; iPoi < pPoiToWorld->NumMappedPOIs(); iPoi++) + { + const CPoiToWorld::SPoiMap *kpMap = pPoiToWorld->MapByIndex(iPoi); + + for (auto it = kpMap->ModelIDs.begin(); it != kpMap->ModelIDs.end(); it++) + { + SPoiMapping Mapping; + Mapping.MeshID = *it; + Mapping.PoiID = kpMap->PoiID; + Mappings.push_back(Mapping); + } + } + + // Write EGMC + rOut.WriteLong(Mappings.size()); + + for (u32 iMap = 0; iMap < Mappings.size(); iMap++) + { + rOut.WriteLong(Mappings[iMap].MeshID); + rOut.WriteLong(Mappings[iMap].PoiID); + } + + rOut.WriteToBoundary(32, -1); +} diff --git a/src/Core/Resource/Cooker/CPoiToWorldCooker.h b/src/Core/Resource/Cooker/CPoiToWorldCooker.h new file mode 100644 index 00000000..b74ea4cf --- /dev/null +++ b/src/Core/Resource/Cooker/CPoiToWorldCooker.h @@ -0,0 +1,14 @@ +#ifndef CPOITOWORLDCOOKER_H +#define CPOITOWORLDCOOKER_H + +#include "Core/Resource/CPoiToWorld.h" +#include + +class CPoiToWorldCooker +{ + CPoiToWorldCooker() {} +public: + static void WriteEGMC(CPoiToWorld *pPoiToWorld, IOutputStream& rOut); +}; + +#endif // CPOITOWORLDCOOKER_H diff --git a/src/Core/Resource/Factory/CModelLoader.cpp b/src/Core/Resource/Factory/CModelLoader.cpp index 9f4969e0..6f6c557e 100644 --- a/src/Core/Resource/Factory/CModelLoader.cpp +++ b/src/Core/Resource/Factory/CModelLoader.cpp @@ -239,12 +239,7 @@ void CModelLoader::LoadSurfaceHeaderPrime(IInputStream& Model, SSurface *pSurf) pSurf->ReflectionDirection = CVector3f(Model); if (mVersion >= eEchoesDemo) - { - Model.Seek(0x2, SEEK_CUR); // Skipping unknown value - pSurf->MeshID = Model.ReadShort(); - } - else - pSurf->MeshID = -1; + Model.Seek(0x4, SEEK_CUR); // Skipping unknown values bool HasAABox = (ExtraSize >= 0x18); // MREAs have a set of bounding box coordinates here. diff --git a/src/Core/Resource/Factory/CPoiToWorldLoader.cpp b/src/Core/Resource/Factory/CPoiToWorldLoader.cpp index e7e46d62..693343dc 100644 --- a/src/Core/Resource/Factory/CPoiToWorldLoader.cpp +++ b/src/Core/Resource/Factory/CPoiToWorldLoader.cpp @@ -3,14 +3,13 @@ CPoiToWorld* CPoiToWorldLoader::LoadEGMC(IInputStream& rEGMC) { CPoiToWorld *pOut = new CPoiToWorld(); - u32 NumLinks = rEGMC.ReadLong(); + u32 NumMappings = rEGMC.ReadLong(); - for (u32 iLink = 0; iLink < NumLinks; iLink++) + for (u32 iMap = 0; iMap < NumMappings; iMap++) { - CPoiToWorld::SPoiMeshLink Link; - Link.MeshID = rEGMC.ReadLong(); - Link.PoiInstanceID = rEGMC.ReadLong(); - pOut->mMeshLinks.push_back(Link); + u32 MeshID = rEGMC.ReadLong(); + u32 InstanceID = rEGMC.ReadLong(); + pOut->AddPoiMeshMap(InstanceID, MeshID); } return pOut; diff --git a/src/Core/Scene/CModelNode.cpp b/src/Core/Scene/CModelNode.cpp index 58235d59..d7e6c579 100644 --- a/src/Core/Scene/CModelNode.cpp +++ b/src/Core/Scene/CModelNode.cpp @@ -10,6 +10,7 @@ CModelNode::CModelNode(CScene *pScene, CSceneNode *pParent, CModel *pModel) : CS mScale = CVector3f(1.f); mLightingEnabled = true; mForceAlphaOn = false; + mEnableScanOverlay = false; mTintColor = CColor::skWhite; } @@ -59,6 +60,18 @@ void CModelNode::Draw(FRenderOptions Options, int ComponentIndex, const SViewInf mpModel->Draw(Options, mActiveMatSet); else mpModel->DrawSurface(Options, ComponentIndex, mActiveMatSet); + + if (mEnableScanOverlay) + { + CDrawUtil::UseColorShader(mScanOverlayColor); + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ZERO); + Options |= eNoMaterialSetup; + + if (ComponentIndex < 0) + mpModel->Draw(Options, 0); + else + mpModel->DrawSurface(Options, ComponentIndex, mActiveMatSet); + } } void CModelNode::DrawSelection() diff --git a/src/Core/Scene/CModelNode.h b/src/Core/Scene/CModelNode.h index b3d34ed2..152e753b 100644 --- a/src/Core/Scene/CModelNode.h +++ b/src/Core/Scene/CModelNode.h @@ -11,6 +11,8 @@ class CModelNode : public CSceneNode bool mLightingEnabled; bool mForceAlphaOn; CColor mTintColor; + bool mEnableScanOverlay; + CColor mScanOverlayColor; public: explicit CModelNode(CScene *pScene, CSceneNode *pParent = 0, CModel *pModel = 0); @@ -26,15 +28,17 @@ public: // Setters void SetModel(CModel *pModel); - inline void SetMatSet(u32 MatSet) { mActiveMatSet = MatSet; } - inline void SetDynamicLighting(bool Enable) { mLightingEnabled = Enable; } - inline void ForceAlphaEnabled(bool Enable) { mForceAlphaOn = Enable; } - inline void SetTintColor(const CColor& rkTintColor) { mTintColor = rkTintColor; } - inline void ClearTintColor() { mTintColor = CColor::skWhite; } - inline CModel* Model() const { return mpModel; } - inline u32 MatSet() const { return mActiveMatSet; } - inline bool IsDynamicLightingEnabled() const { return mLightingEnabled; } - inline u32 FindMeshID() const { return mpModel->GetSurface(0)->MeshID; } + inline void SetMatSet(u32 MatSet) { mActiveMatSet = MatSet; } + inline void SetDynamicLighting(bool Enable) { mLightingEnabled = Enable; } + inline void ForceAlphaEnabled(bool Enable) { mForceAlphaOn = Enable; } + inline void SetTintColor(const CColor& rkTintColor) { mTintColor = rkTintColor; } + inline void ClearTintColor() { mTintColor = CColor::skWhite; } + inline void SetScanOverlayEnabled(bool Enable) { mEnableScanOverlay = Enable; } + inline void SetScanOverlayColor(const CColor& rkColor) { mScanOverlayColor = rkColor; } + inline CModel* Model() const { return mpModel; } + inline u32 MatSet() const { return mActiveMatSet; } + inline bool IsDynamicLightingEnabled() const { return mLightingEnabled; } + inline u32 FindMeshID() const { return mpModel->GetSurface(0)->MeshID; } }; #endif // CMODELNODE_H diff --git a/src/Editor/CSceneViewport.cpp b/src/Editor/CSceneViewport.cpp index 9187fe13..0d6a5196 100644 --- a/src/Editor/CSceneViewport.cpp +++ b/src/Editor/CSceneViewport.cpp @@ -346,41 +346,7 @@ void CSceneViewport::OnMouseRelease(QMouseEvent *pEvent) // Object selection/deselection else - { - bool validNode = (mpHoverNode && (mpHoverNode->NodeType() != eStaticNode) && (mpHoverNode->NodeType() != eModelNode)); - bool altPressed = ((pEvent->modifiers() & Qt::AltModifier) != 0); - bool ctrlPressed = ((pEvent->modifiers() & Qt::ControlModifier) != 0); - - // Alt: Deselect - if (altPressed) - { - if (!validNode) - return; - - mpEditor->DeselectNode(mpHoverNode); - } - - // Ctrl: Add to selection - else if (ctrlPressed) - { - if (validNode) - mpEditor->SelectNode(mpHoverNode); - } - - // Neither: clear selection + select - else - { - if (!mGizmoHovering) - { - if (validNode) - mpEditor->ClearAndSelectNode(mpHoverNode); - else - mpEditor->ClearSelection(); - } - } - - mpEditor->UpdateSelectionUI(); - } + emit ViewportClick(mpHoverNode, pEvent); } } diff --git a/src/Editor/CSceneViewport.h b/src/Editor/CSceneViewport.h index 10b5fa92..17c7aea0 100644 --- a/src/Editor/CSceneViewport.h +++ b/src/Editor/CSceneViewport.h @@ -56,6 +56,7 @@ protected: void CreateContextMenu(); signals: + void ViewportClick(CSceneNode *pNode, QMouseEvent *pEvent); void GizmoMoved(); void CameraOrbit(); diff --git a/src/Editor/INodeEditor.cpp b/src/Editor/INodeEditor.cpp index 828b097f..8c8f6139 100644 --- a/src/Editor/INodeEditor.cpp +++ b/src/Editor/INodeEditor.cpp @@ -1,8 +1,11 @@ #include "INodeEditor.h" #include "Editor/Undo/UndoCommands.h" +#include INodeEditor::INodeEditor(QWidget *pParent) : QMainWindow(pParent) + , mPickMode(false) + , mSelectionNodeFlags(eAllNodeTypes) , mSelectionLocked(false) , mShowGizmo(false) , mGizmoHovering(false) @@ -206,6 +209,28 @@ const QList& INodeEditor::GetSelection() const return mSelection; } +void INodeEditor::EnterPickMode(FNodeFlags AllowedNodes, bool ExitOnInvalidPick, bool EmitOnInvalidPick, QCursor Cursor /*= Qt::CrossCursor*/) +{ + // If we're already in pick mode, exit first so the previous caller has a chance to disconnect + if (mPickMode) + ExitPickMode(); + + mPickMode = true; + mAllowedPickNodes = AllowedNodes; + mExitOnInvalidPick = ExitOnInvalidPick; + mEmitOnInvalidPick = EmitOnInvalidPick; + emit PickModeEntered(Cursor); +} + +void INodeEditor::ExitPickMode() +{ + if (mPickMode) + { + mPickMode = false; + emit PickModeExited(); + } +} + // ************ PUBLIC SLOTS ************ void INodeEditor::OnGizmoMoved() { @@ -237,6 +262,60 @@ void INodeEditor::OnGizmoMoved() UpdateGizmoUI(); } +// ************ PROTECTED SLOTS ************ +void INodeEditor::OnViewportClick(CSceneNode *pHoverNode, QMouseEvent *pEvent) +{ + // Not in pick mode: process node selection/deselection + if (!mPickMode) + { + bool ValidNode = (pHoverNode && (pHoverNode->NodeType() & mSelectionNodeFlags)); + bool AltPressed = ((pEvent->modifiers() & Qt::AltModifier) != 0); + bool CtrlPressed = ((pEvent->modifiers() & Qt::ControlModifier) != 0); + + // Alt: Deselect + if (AltPressed) + { + if (!ValidNode) + return; + + DeselectNode(pHoverNode); + } + + // Ctrl: Add to selection + else if (CtrlPressed) + { + if (ValidNode) + SelectNode(pHoverNode); + } + + // Neither: clear selection + select + else + { + if (!mGizmoHovering) + { + if (ValidNode) + ClearAndSelectNode(pHoverNode); + else + ClearSelection(); + } + } + + UpdateSelectionUI(); + } + + // In pick mode: process node pick + else + { + bool ValidNode = (pHoverNode && (pHoverNode->NodeType() & mAllowedPickNodes)); + + if (ValidNode || mEmitOnInvalidPick) + emit PickModeClick(pHoverNode, pEvent); + + if (!ValidNode && mExitOnInvalidPick) + ExitPickMode(); + } +} + // ************ PRIVATE ************ void INodeEditor::UpdateTransformActionsEnabled() { diff --git a/src/Editor/INodeEditor.h b/src/Editor/INodeEditor.h index 92a10f4c..e6093b4a 100644 --- a/src/Editor/INodeEditor.h +++ b/src/Editor/INodeEditor.h @@ -24,6 +24,7 @@ protected: // Node management CScene mScene; QList mSelection; + FNodeFlags mSelectionNodeFlags; CAABox mSelectionBounds; bool mSelectionLocked; @@ -40,6 +41,12 @@ protected: QList mGizmoActions; QComboBox *mpTransformCombo; + // Pick mode + bool mPickMode; + bool mExitOnInvalidPick; + bool mEmitOnInvalidPick; + FNodeFlags mAllowedPickNodes; + public: explicit INodeEditor(QWidget *pParent = 0); virtual ~INodeEditor(); @@ -63,10 +70,17 @@ public: bool HasSelection() const; const QList& GetSelection() const; + void EnterPickMode(FNodeFlags AllowedNodes, bool ExitOnInvalidPick, bool EmitOnInvalidPick, QCursor Cursor = Qt::CrossCursor); + void ExitPickMode(); + signals: void SelectionModified(); void SelectionTransformed(); + void PickModeEntered(QCursor Cursor); + void PickModeExited(); + void PickModeClick(CSceneNode *pNode, QMouseEvent *pEvent); + public slots: void OnGizmoMoved(); virtual void UpdateGizmoUI() = 0; @@ -75,6 +89,9 @@ public slots: protected: virtual void GizmoModeChanged(CGizmo::EGizmoMode /*mode*/) {} +protected slots: + void OnViewportClick(CSceneNode *pHoverNode, QMouseEvent *pEvent); + private: void UpdateTransformActionsEnabled(); diff --git a/src/Editor/WorldEditor/CPoiMapEditDialog.cpp b/src/Editor/WorldEditor/CPoiMapEditDialog.cpp index 8804bb52..52b65676 100644 --- a/src/Editor/WorldEditor/CPoiMapEditDialog.cpp +++ b/src/Editor/WorldEditor/CPoiMapEditDialog.cpp @@ -1,15 +1,25 @@ #include "CPoiMapEditDialog.h" #include "ui_CPoiMapEditDialog.h" #include "CWorldEditor.h" +#include "Editor/UICommon.h" + #include +#include #include +#include +#include + +const CColor CPoiMapEditDialog::skNormalColor(0.137255f, 0.184314f, 0.776471f, 0.5f); +const CColor CPoiMapEditDialog::skImportantColor(0.721569f, 0.066667f, 0.066667f, 0.5f); + CPoiMapEditDialog::CPoiMapEditDialog(CWorldEditor *pEditor, QWidget *parent) : QMainWindow(parent) , ui(new Ui::CPoiMapEditDialog) , mpEditor(pEditor) , mSourceModel(pEditor, this) , mHighlightMode(eHighlightSelected) + , mPickType(eNotPicking) { mModel.setSourceModel(&mSourceModel); mModel.sort(0); @@ -29,8 +39,11 @@ CPoiMapEditDialog::CPoiMapEditDialog(CWorldEditor *pEditor, QWidget *parent) connect(ui->ListView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(OnSelectionChanged(QItemSelection,QItemSelection))); connect(ui->ListView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(OnItemDoubleClick(QModelIndex))); - connect(ui->ButtonBox, SIGNAL(accepted()), this, SLOT(close())); - connect(ui->ButtonBox, SIGNAL(rejected()), this, SLOT(close())); + connect(ui->AddMeshButton, SIGNAL(clicked()), this, SLOT(PickButtonClicked())); + connect(ui->RemoveMeshButton, SIGNAL(clicked()), this, SLOT(PickButtonClicked())); + connect(ui->ButtonBox->button(QDialogButtonBox::Ok), SIGNAL(clicked()), this, SLOT(close())); + connect(ui->ButtonBox->button(QDialogButtonBox::Cancel), SIGNAL(clicked()), this, SLOT(close())); + connect(ui->ButtonBox->button(QDialogButtonBox::Save), SIGNAL(clicked()), this, SLOT(Save())); } CPoiMapEditDialog::~CPoiMapEditDialog() @@ -40,25 +53,33 @@ CPoiMapEditDialog::~CPoiMapEditDialog() // Clear model tints if (mHighlightMode != eHighlightNone) SetHighlightNone(); + + // Stop picking + if (mPickType != eNotPicking) + StopPicking(); +} + +void CPoiMapEditDialog::closeEvent(QCloseEvent* /*pEvent*/) +{ + if (mPickType != eNotPicking) + mpEditor->ExitPickMode(); + + emit Closed(); } void CPoiMapEditDialog::HighlightPoiModels(const QModelIndex& rkIndex) { // Get POI and models QModelIndex SourceIndex = mModel.mapToSource(rkIndex); - CScriptNode *pPOI = mSourceModel.PoiNodePointer(SourceIndex); - const QList& rkModels = mSourceModel.GetPoiMeshList(pPOI); - - // Check whether this is an important scan - bool IsImportant = false; - TResPtr pScan = static_cast(pPOI->Extra())->GetScan(); - - if (pScan) - IsImportant = pScan->IsImportant(); + const QList& rkModels = mSourceModel.GetPoiMeshList(SourceIndex); + bool Important = IsImportant(SourceIndex); // Highlight the meshes for (int iMdl = 0; iMdl < rkModels.size(); iMdl++) - rkModels[iMdl]->SetTintColor(IsImportant ? CColor::skRed : CColor::skBlue); + { + rkModels[iMdl]->SetScanOverlayEnabled(true); + rkModels[iMdl]->SetScanOverlayColor(Important ? skImportantColor : skNormalColor); + } } void CPoiMapEditDialog::UnhighlightPoiModels(const QModelIndex& rkIndex) @@ -67,7 +88,48 @@ void CPoiMapEditDialog::UnhighlightPoiModels(const QModelIndex& rkIndex) const QList& rkModels = mSourceModel.GetPoiMeshList(SourceIndex); for (int iMdl = 0; iMdl < rkModels.size(); iMdl++) - rkModels[iMdl]->ClearTintColor(); + rkModels[iMdl]->SetScanOverlayEnabled(false); +} + +void CPoiMapEditDialog::HighlightModel(const QModelIndex& rkIndex, CModelNode *pNode) +{ + bool Important = IsImportant(rkIndex); + pNode->SetScanOverlayEnabled(true); + pNode->SetScanOverlayColor(Important ? skImportantColor : skNormalColor); +} + +void CPoiMapEditDialog::UnhighlightModel(CModelNode *pNode) +{ + pNode->SetScanOverlayEnabled(false); +} + +bool CPoiMapEditDialog::IsImportant(const QModelIndex& rkIndex) +{ + CScriptNode *pPOI = mSourceModel.PoiNodePointer(rkIndex); + + bool Important = false; + TResPtr pScan = static_cast(pPOI->Extra())->GetScan(); + + if (pScan) + Important = pScan->IsImportant(); + + return Important; +} + +void CPoiMapEditDialog::Save() +{ + CPoiToWorld *pPoiToWorld = mpEditor->ActiveArea()->GetPoiToWorldMap(); + + TString FileName = pPoiToWorld->FullSource(); + CFileOutStream Out(FileName.ToStdString(), IOUtil::eBigEndian); + + if (Out.IsValid()) + { + CPoiToWorldCooker::WriteEGMC(pPoiToWorld, Out); + QMessageBox::information(this, "Saved", QString("Saved to %1!").arg(TO_QSTRING(pPoiToWorld->Source()))); + } + else + QMessageBox::warning(this, "Error", "Couldn't save EGMC; unable to open output file"); } void CPoiMapEditDialog::SetHighlightSelected() @@ -127,3 +189,81 @@ void CPoiMapEditDialog::OnItemDoubleClick(QModelIndex Index) CScriptNode *pPOI = mSourceModel.PoiNodePointer(SourceIndex); mpEditor->ClearAndSelectNode(pPOI); } + +void CPoiMapEditDialog::PickButtonClicked() +{ + QPushButton *pButton = qobject_cast(sender()); + + if (!pButton->isChecked()) + mpEditor->ExitPickMode(); + + else + { + mpEditor->EnterPickMode(eModelNode, false, false); + connect(mpEditor, SIGNAL(PickModeExited()), this, SLOT(StopPicking())); + connect(mpEditor, SIGNAL(PickModeClick(CSceneNode*,QMouseEvent*)), this, SLOT(OnNodePicked(CSceneNode*,QMouseEvent*))); + pButton->setChecked(true); + + if (pButton == ui->AddMeshButton) + { + mPickType = eAddMeshes; + ui->RemoveMeshButton->setChecked(false); + } + + else if (pButton == ui->RemoveMeshButton) + { + mPickType = eRemoveMeshes; + ui->AddMeshButton->setChecked(false); + } + } +} + +void CPoiMapEditDialog::StopPicking() +{ + ui->AddMeshButton->setChecked(false); + ui->RemoveMeshButton->setChecked(false); + mPickType = eNotPicking; + + disconnect(mpEditor, 0, this, 0); +} + +void CPoiMapEditDialog::OnNodePicked(CSceneNode *pNode, QMouseEvent* pEvent) +{ + // Check for valid selection + QModelIndexList Indices = ui->ListView->selectionModel()->selectedRows(); + if (Indices.isEmpty()) return; + + // Map selection to source model + QModelIndexList SourceIndices; + for (auto it = Indices.begin(); it != Indices.end(); it++) + SourceIndices << mModel.mapToSource(*it); + + // If alt is pressed, invert the pick mode + CModelNode *pModel = static_cast(pNode); + bool AltPressed = (pEvent->modifiers() & Qt::AltModifier) != 0; + + EPickType PickType; + if (!AltPressed) PickType = mPickType; + else if (mPickType == eAddMeshes) PickType = eRemoveMeshes; + else PickType = eAddMeshes; + + // Add meshes + if (PickType == eAddMeshes) + { + for (auto it = SourceIndices.begin(); it != SourceIndices.end(); it++) + mSourceModel.AddMapping(*it, pModel); + + if (mHighlightMode != eHighlightNone) + HighlightModel(SourceIndices.front(), pModel); + } + + // Remove meshes + else if (PickType == eRemoveMeshes) + { + for (auto it = SourceIndices.begin(); it != SourceIndices.end(); it++) + mSourceModel.RemoveMapping(*it, pModel); + + if (mHighlightMode != eHighlightNone) + UnhighlightModel(pModel); + } +} diff --git a/src/Editor/WorldEditor/CPoiMapEditDialog.h b/src/Editor/WorldEditor/CPoiMapEditDialog.h index 99fe3543..d804f61e 100644 --- a/src/Editor/WorldEditor/CPoiMapEditDialog.h +++ b/src/Editor/WorldEditor/CPoiMapEditDialog.h @@ -27,20 +27,40 @@ class CPoiMapEditDialog : public QMainWindow QSortFilterProxyModel mModel; EHighlightMode mHighlightMode; + enum EPickType + { + eNotPicking, + eAddMeshes, + eRemoveMeshes, + eAddPOIs + } mPickType; + + static const CColor skNormalColor; + static const CColor skImportantColor; + public: explicit CPoiMapEditDialog(CWorldEditor *pEditor, QWidget *parent = 0); ~CPoiMapEditDialog(); - void closeEvent(QCloseEvent *) { emit Closed(); } + void closeEvent(QCloseEvent *pEvent); void HighlightPoiModels(const QModelIndex& rkIndex); void UnhighlightPoiModels(const QModelIndex& rkIndex); + void HighlightModel(const QModelIndex& rkIndex, CModelNode *pNode); + void UnhighlightModel(CModelNode *pNode); + void RefreshHighlights(); + bool IsImportant(const QModelIndex& rkIndex); public slots: + void Save(); void SetHighlightSelected(); void SetHighlightAll(); void SetHighlightNone(); void OnSelectionChanged(const QItemSelection& rkSelected, const QItemSelection& rkDeselected); void OnItemDoubleClick(QModelIndex Index); + void PickButtonClicked(); + void StopPicking(); + void OnNodePicked(CSceneNode *pNode, QMouseEvent *pEvent); + signals: void Closed(); }; diff --git a/src/Editor/WorldEditor/CPoiMapEditDialog.ui b/src/Editor/WorldEditor/CPoiMapEditDialog.ui index 6312ce74..303b0ca9 100644 --- a/src/Editor/WorldEditor/CPoiMapEditDialog.ui +++ b/src/Editor/WorldEditor/CPoiMapEditDialog.ui @@ -6,8 +6,8 @@ 0 0 - 469 - 327 + 274 + 308 @@ -28,10 +28,55 @@ + + + + + + + + + + :/icons/Plus.png:/icons/Plus.png + + + true + + + + + + + + + + + :/icons/Minus v2.png:/icons/Minus v2.png + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Save @@ -88,6 +133,8 @@ - + + + diff --git a/src/Editor/WorldEditor/CPoiMapModel.cpp b/src/Editor/WorldEditor/CPoiMapModel.cpp index 615c6d20..3d3098b3 100644 --- a/src/Editor/WorldEditor/CPoiMapModel.cpp +++ b/src/Editor/WorldEditor/CPoiMapModel.cpp @@ -7,13 +7,12 @@ CPoiMapModel::CPoiMapModel(CWorldEditor *pEditor, QObject *pParent /*= 0*/) : QAbstractListModel(pParent) , mpEditor(pEditor) + , mpArea(pEditor->ActiveArea()) + , mpPoiToWorld(mpArea->GetPoiToWorldMap()) { - mpEditor = pEditor; - mpPoiToWorld = mpEditor->ActiveArea()->GetPoiToWorldMap(); - if (mpPoiToWorld) { - // Create map of model nodes + // Create an ID -> Model Node lookup map QMap NodeMap; for (CSceneIterator It(mpEditor->Scene(), eModelNode, true); !It.DoneIterating(); ++It) @@ -22,26 +21,36 @@ CPoiMapModel::CPoiMapModel(CWorldEditor *pEditor, QObject *pParent /*= 0*/) NodeMap[pNode->FindMeshID()] = pNode; } - // Create list of mappings - for (u32 iMap = 0; iMap < mpPoiToWorld->NumMeshLinks(); iMap++) + // Create internal model map + for (u32 iPoi = 0; iPoi < mpPoiToWorld->NumMappedPOIs(); iPoi++) { - const CPoiToWorld::SPoiMeshLink& rkLink = mpPoiToWorld->MeshLinkByIndex(iMap); - CScriptNode *pPOI = mpEditor->Scene()->ScriptNodeByID(rkLink.PoiInstanceID); + const CPoiToWorld::SPoiMap *pkMap = mpPoiToWorld->MapByIndex(iPoi); + CScriptNode *pPoiNode = mpEditor->Scene()->ScriptNodeByID(pkMap->PoiID); - if (!mPoiLookupMap.contains(pPOI)) + if (pPoiNode) { - SEditorPoiMap Map; - Map.pPOI = pPOI; - mMaps << Map; - mPoiLookupMap[pPOI] = &mMaps.last(); - } + QList *pModelList = new QList; - if (NodeMap.contains(rkLink.MeshID)) - mPoiLookupMap[pPOI]->Models << NodeMap[rkLink.MeshID]; + for (auto it = pkMap->ModelIDs.begin(); it != pkMap->ModelIDs.end(); it++) + { + if (NodeMap.contains(*it)) + *pModelList << NodeMap[*it]; + } + + mModelMap[pPoiNode] = pModelList; + } } } } +CPoiMapModel::~CPoiMapModel() +{ + QList*> Lists = mModelMap.values(); + + for (auto it = Lists.begin(); it != Lists.end(); it++) + delete *it; +} + QVariant CPoiMapModel::headerData(int Section, Qt::Orientation Orientation, int Role) const { if ( (Section == 0) && (Orientation == Qt::Horizontal) && (Role == Qt::DisplayRole) ) @@ -52,31 +61,33 @@ QVariant CPoiMapModel::headerData(int Section, Qt::Orientation Orientation, int int CPoiMapModel::rowCount(const QModelIndex& /*rkParent*/) const { - return mMaps.size(); + return mpPoiToWorld->NumMappedPOIs(); } QVariant CPoiMapModel::data(const QModelIndex& rkIndex, int Role) const { - if (rkIndex.row() < mMaps.size()) + if (rkIndex.row() < rowCount(QModelIndex())) { - const SEditorPoiMap& rkMap = mMaps[rkIndex.row()]; + const CPoiToWorld::SPoiMap *pkMap = mpPoiToWorld->MapByIndex(rkIndex.row()); + CScriptObject *pPOI = mpArea->GetInstanceByID(pkMap->PoiID); if (Role == Qt::DisplayRole) { - if (rkMap.pPOI) - return TO_QSTRING(rkMap.pPOI->Object()->InstanceName()); + if (pPOI) + return TO_QSTRING(pPOI->InstanceName()); else return "[INVALID POI]"; } else if (Role == Qt::DecorationRole) { + CScriptNode *pNode = mpEditor->Scene()->NodeForObject(pPOI); bool IsImportant = false; - if (rkMap.pPOI) + if (pNode) { // Get scan - CScan *pScan = static_cast(rkMap.pPOI->Extra())->GetScan(); + CScan *pScan = static_cast(pNode->Extra())->GetScan(); if (pScan) IsImportant = pScan->IsImportant(); @@ -92,10 +103,66 @@ QVariant CPoiMapModel::data(const QModelIndex& rkIndex, int Role) const return QVariant::Invalid; } +void CPoiMapModel::AddPOI(CScriptNode *pPOI) +{ + if (!mModelMap.contains(pPOI)) + { + QList *pList = new QList; + mModelMap[pPOI] = pList; + mpPoiToWorld->AddPoi(pPOI->Object()->InstanceID()); + } +} + +void CPoiMapModel::AddMapping(const QModelIndex& rkIndex, CModelNode *pNode) +{ + CScriptNode *pPOI = PoiNodePointer(rkIndex); + AddPOI(pPOI); + + QList *pList = mModelMap[pPOI]; + if (!pList->contains(pNode)) + pList->append(pNode); + + mpPoiToWorld->AddPoiMeshMap(pPOI->Object()->InstanceID(), pNode->FindMeshID()); +} + +void CPoiMapModel::RemovePOI(const QModelIndex& rkIndex) +{ + CScriptNode *pPOI = PoiNodePointer(rkIndex); + + if (mModelMap.contains(pPOI)) + { + delete mModelMap[pPOI]; + mModelMap.remove(pPOI); + } + + mpPoiToWorld->RemovePoi(pPOI->Object()->InstanceID()); +} + +void CPoiMapModel::RemoveMapping(const QModelIndex& rkIndex, CModelNode *pNode) +{ + CScriptNode *pPOI = PoiNodePointer(rkIndex); + + if (mModelMap.contains(pPOI)) + { + QList *pList = mModelMap[pPOI]; + pList->removeOne(pNode); + + if (pList->isEmpty()) + RemovePOI(rkIndex); + else + mpPoiToWorld->RemovePoiMeshMap(pPOI->Object()->InstanceID(), pNode->FindMeshID()); + } + else + mpPoiToWorld->RemovePoiMeshMap(pPOI->Object()->InstanceID(), pNode->FindMeshID()); +} + CScriptNode* CPoiMapModel::PoiNodePointer(const QModelIndex& rkIndex) const { - if (rkIndex.row() < mMaps.size()) - return mMaps[rkIndex.row()].pPOI; + if ((u32) rkIndex.row() < mpPoiToWorld->NumMappedPOIs()) + { + const CPoiToWorld::SPoiMap *pkMap = mpPoiToWorld->MapByIndex(rkIndex.row()); + return mpEditor->Scene()->ScriptNodeByID(pkMap->PoiID); + } return nullptr; } @@ -108,5 +175,5 @@ const QList& CPoiMapModel::GetPoiMeshList(const QModelIndex& rkInde const QList& CPoiMapModel::GetPoiMeshList(CScriptNode *pPOI) const { - return mPoiLookupMap[pPOI]->Models; + return *mModelMap[pPOI]; } diff --git a/src/Editor/WorldEditor/CPoiMapModel.h b/src/Editor/WorldEditor/CPoiMapModel.h index a336c18d..445f9dbd 100644 --- a/src/Editor/WorldEditor/CPoiMapModel.h +++ b/src/Editor/WorldEditor/CPoiMapModel.h @@ -14,26 +14,24 @@ class CPoiMapModel : public QAbstractListModel { Q_OBJECT -public: - -private: CWorldEditor *mpEditor; + CGameArea *mpArea; TResPtr mpPoiToWorld; - struct SEditorPoiMap - { - CScriptNode *pPOI; - QList Models; - }; - QList mMaps; - QMap mPoiLookupMap; + QMap*> mModelMap; public: explicit CPoiMapModel(CWorldEditor *pEditor, QObject *pParent = 0); + ~CPoiMapModel(); QVariant headerData(int Section, Qt::Orientation Orientation, int Role) const; int rowCount(const QModelIndex& rkParent) const; QVariant data(const QModelIndex& rkIndex, int Role) const; + void AddPOI(CScriptNode *pPOI); + void AddMapping(const QModelIndex& rkIndex, CModelNode *pNode); + void RemovePOI(const QModelIndex& rkIndex); + void RemoveMapping(const QModelIndex& rkIndex, CModelNode *pNode); + CScriptNode* PoiNodePointer(const QModelIndex& rkIndex) const; const QList& GetPoiMeshList(const QModelIndex& rkIndex) const; const QList& GetPoiMeshList(CScriptNode *pPOI) const; diff --git a/src/Editor/WorldEditor/CWorldEditor.cpp b/src/Editor/WorldEditor/CWorldEditor.cpp index 06f6f626..34cc090a 100644 --- a/src/Editor/WorldEditor/CWorldEditor.cpp +++ b/src/Editor/WorldEditor/CWorldEditor.cpp @@ -31,6 +31,7 @@ CWorldEditor::CWorldEditor(QWidget *parent) : mpPoiDialog = nullptr; mGizmoHovering = false; mGizmoTransforming = false; + mSelectionNodeFlags = eScriptNode | eLightNode; // Start refresh timer connect(&mRefreshTimer, SIGNAL(timeout()), this, SLOT(RefreshViewport())); @@ -67,10 +68,13 @@ CWorldEditor::CWorldEditor(QWidget *parent) : addAction(ui->ActionDecrementGizmo); // Connect signals and slots + connect(ui->MainViewport, SIGNAL(ViewportClick(CSceneNode*,QMouseEvent*)), this, SLOT(OnViewportClick(CSceneNode*,QMouseEvent*))); connect(ui->MainViewport, SIGNAL(GizmoMoved()), this, SLOT(OnGizmoMoved())); connect(ui->MainViewport, SIGNAL(CameraOrbit()), this, SLOT(UpdateCameraOrbit())); connect(this, SIGNAL(SelectionModified()), this, SLOT(UpdateCameraOrbit())); connect(this, SIGNAL(SelectionTransformed()), this, SLOT(UpdateCameraOrbit())); + connect(this, SIGNAL(PickModeEntered(QCursor)), this, SLOT(OnPickModeEnter(QCursor))); + connect(this, SIGNAL(PickModeExited()), this, SLOT(OnPickModeExit())); 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))); @@ -102,6 +106,7 @@ bool CWorldEditor::eventFilter(QObject *pObj, QEvent *pEvent) void CWorldEditor::SetArea(CWorld *pWorld, CGameArea *pArea) { + ExitPickMode(); ui->MainViewport->ResetHover(); ClearSelection(); ui->ModifyTabContents->ClearUI(); @@ -173,7 +178,7 @@ void CWorldEditor::UpdateStatusBar() { CSceneNode *pHoverNode = ui->MainViewport->HoverNode(); - if (pHoverNode && (pHoverNode->NodeType() != eStaticNode) && (pHoverNode->NodeType() != eModelNode)) + if (pHoverNode && (pHoverNode->NodeType() & mSelectionNodeFlags)) StatusText = TO_QSTRING(pHoverNode->Name()); } } @@ -266,13 +271,13 @@ void CWorldEditor::GizmoModeChanged(CGizmo::EGizmoMode mode) void CWorldEditor::UpdateCursor() { - if (ui->MainViewport->IsCursorVisible()) + if (ui->MainViewport->IsCursorVisible() && !mPickMode) { CSceneNode *pHoverNode = ui->MainViewport->HoverNode(); if (ui->MainViewport->IsHoveringGizmo()) ui->MainViewport->SetCursorState(Qt::SizeAllCursor); - else if ((pHoverNode) && (pHoverNode->NodeType() != eStaticNode) && (pHoverNode->NodeType() != eModelNode)) + else if ((pHoverNode) && (pHoverNode->NodeType() & mSelectionNodeFlags)) ui->MainViewport->SetCursorState(Qt::PointingHandCursor); else ui->MainViewport->SetCursorState(Qt::ArrowCursor); @@ -280,6 +285,16 @@ void CWorldEditor::UpdateCursor() } // ************ PRIVATE SLOTS ************ +void CWorldEditor::OnPickModeEnter(QCursor Cursor) +{ + ui->MainViewport->SetCursorState(Cursor); +} + +void CWorldEditor::OnPickModeExit() +{ + UpdateCursor(); +} + void CWorldEditor::RefreshViewport() { if (!mGizmo.IsTransforming()) diff --git a/src/Editor/WorldEditor/CWorldEditor.h b/src/Editor/WorldEditor/CWorldEditor.h index fa81f1e7..4304d619 100644 --- a/src/Editor/WorldEditor/CWorldEditor.h +++ b/src/Editor/WorldEditor/CWorldEditor.h @@ -57,6 +57,8 @@ protected: void UpdateCursor(); private slots: + void OnPickModeEnter(QCursor Cursor); + void OnPickModeExit(); void RefreshViewport(); void UpdateCameraOrbit(); void OnCameraSpeedChange(double speed);