Added support for editing and resaving EGMC files + improved its preview rendering

This commit is contained in:
parax0 2016-01-16 01:13:27 -07:00
parent c0b74c9883
commit 5c3a37ca4a
20 changed files with 605 additions and 134 deletions

View File

@ -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

View File

@ -6,22 +6,74 @@ CPoiToWorld::CPoiToWorld()
CPoiToWorld::~CPoiToWorld()
{
for (auto it = mMaps.begin(); it != mMaps.end(); it++)
delete *it;
}
void CPoiToWorld::LinksForMeshID(std::list<u32>& 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<u32>& 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;
}
}
}
}

View File

@ -3,37 +3,41 @@
#include "CResource.h"
#include <list>
#include <map>
#include <vector>
class CPoiToWorld : public CResource
{
DECLARE_RESOURCE_TYPE(ePoiToWorld)
friend class CPoiToWorldLoader;
public:
struct SPoiMeshLink
struct SPoiMap
{
u32 MeshID;
u32 PoiInstanceID;
u32 PoiID;
std::list<u32> ModelIDs;
};
private:
std::vector<SPoiMeshLink> mMeshLinks;
std::vector<SPoiMap*> mMaps;
std::map<u32,SPoiMap*> mPoiLookupMap;
public:
CPoiToWorld();
~CPoiToWorld();
void LinksForMeshID(std::list<u32>& rOutInstanceIDs, u32 MeshID);
void LinksForInstanceID(std::list<u32>& 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];
}
};

View File

@ -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<SPoiMapping> 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);
}

View File

@ -0,0 +1,14 @@
#ifndef CPOITOWORLDCOOKER_H
#define CPOITOWORLDCOOKER_H
#include "Core/Resource/CPoiToWorld.h"
#include <FileIO/FileIO.h>
class CPoiToWorldCooker
{
CPoiToWorldCooker() {}
public:
static void WriteEGMC(CPoiToWorld *pPoiToWorld, IOutputStream& rOut);
};
#endif // CPOITOWORLDCOOKER_H

View File

@ -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.

View File

@ -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;

View File

@ -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()

View File

@ -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

View File

@ -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);
}
}

View File

@ -56,6 +56,7 @@ protected:
void CreateContextMenu();
signals:
void ViewportClick(CSceneNode *pNode, QMouseEvent *pEvent);
void GizmoMoved();
void CameraOrbit();

View File

@ -1,8 +1,11 @@
#include "INodeEditor.h"
#include "Editor/Undo/UndoCommands.h"
#include <QMouseEvent>
INodeEditor::INodeEditor(QWidget *pParent)
: QMainWindow(pParent)
, mPickMode(false)
, mSelectionNodeFlags(eAllNodeTypes)
, mSelectionLocked(false)
, mShowGizmo(false)
, mGizmoHovering(false)
@ -206,6 +209,28 @@ const QList<CSceneNode*>& 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()
{

View File

@ -24,6 +24,7 @@ protected:
// Node management
CScene mScene;
QList<CSceneNode*> mSelection;
FNodeFlags mSelectionNodeFlags;
CAABox mSelectionBounds;
bool mSelectionLocked;
@ -40,6 +41,12 @@ protected:
QList<QAction*> 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<CSceneNode*>& 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();

View File

@ -1,15 +1,25 @@
#include "CPoiMapEditDialog.h"
#include "ui_CPoiMapEditDialog.h"
#include "CWorldEditor.h"
#include "Editor/UICommon.h"
#include <Core/Resource/CScan.h>
#include <Core/Resource/Cooker/CPoiToWorldCooker.h>
#include <Core/ScriptExtra/CPointOfInterestExtra.h>
#include <QMouseEvent>
#include <QMessageBox>
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<CModelNode*>& rkModels = mSourceModel.GetPoiMeshList(pPOI);
// Check whether this is an important scan
bool IsImportant = false;
TResPtr<CScan> pScan = static_cast<CPointOfInterestExtra*>(pPOI->Extra())->GetScan();
if (pScan)
IsImportant = pScan->IsImportant();
const QList<CModelNode*>& 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<CModelNode*>& 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<CScan> pScan = static_cast<CPointOfInterestExtra*>(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<QPushButton*>(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<CModelNode*>(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);
}
}

View File

@ -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();
};

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>469</width>
<height>327</height>
<width>274</width>
<height>308</height>
</rect>
</property>
<property name="windowTitle">
@ -28,10 +28,55 @@
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="AddMeshButton">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../Icons.qrc">
<normaloff>:/icons/Plus.png</normaloff>:/icons/Plus.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="RemoveMeshButton">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../Icons.qrc">
<normaloff>:/icons/Minus v2.png</normaloff>:/icons/Minus v2.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="ButtonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Save</set>
</property>
</widget>
</item>
@ -88,6 +133,8 @@
</property>
</action>
</widget>
<resources/>
<resources>
<include location="../Icons.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -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<u32,CModelNode*> 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<CModelNode*> *pModelList = new QList<CModelNode*>;
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<QList<CModelNode*>*> 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<CPointOfInterestExtra*>(rkMap.pPOI->Extra())->GetScan();
CScan *pScan = static_cast<CPointOfInterestExtra*>(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<CModelNode*> *pList = new QList<CModelNode*>;
mModelMap[pPOI] = pList;
mpPoiToWorld->AddPoi(pPOI->Object()->InstanceID());
}
}
void CPoiMapModel::AddMapping(const QModelIndex& rkIndex, CModelNode *pNode)
{
CScriptNode *pPOI = PoiNodePointer(rkIndex);
AddPOI(pPOI);
QList<CModelNode*> *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<CModelNode*> *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<CModelNode*>& CPoiMapModel::GetPoiMeshList(const QModelIndex& rkInde
const QList<CModelNode*>& CPoiMapModel::GetPoiMeshList(CScriptNode *pPOI) const
{
return mPoiLookupMap[pPOI]->Models;
return *mModelMap[pPOI];
}

View File

@ -14,26 +14,24 @@ class CPoiMapModel : public QAbstractListModel
{
Q_OBJECT
public:
private:
CWorldEditor *mpEditor;
CGameArea *mpArea;
TResPtr<CPoiToWorld> mpPoiToWorld;
struct SEditorPoiMap
{
CScriptNode *pPOI;
QList<CModelNode*> Models;
};
QList<SEditorPoiMap> mMaps;
QMap<CScriptNode*,SEditorPoiMap*> mPoiLookupMap;
QMap<CScriptNode*, QList<CModelNode*>*> 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<CModelNode*>& GetPoiMeshList(const QModelIndex& rkIndex) const;
const QList<CModelNode*>& GetPoiMeshList(CScriptNode *pPOI) const;

View File

@ -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())

View File

@ -57,6 +57,8 @@ protected:
void UpdateCursor();
private slots:
void OnPickModeEnter(QCursor Cursor);
void OnPickModeExit();
void RefreshViewport();
void UpdateCameraOrbit();
void OnCameraSpeedChange(double speed);