1 // Copyright 2001-2018 Crytek GmbH / Crytek Group. All rights reserved.
3 #include "MaterialEditor.h"
5 #include <AssetSystem/Asset.h>
6 #include <AssetSystem/EditableAsset.h>
9 #include <EditorFramework/Events.h>
10 #include <EditorFramework/Inspector.h>
11 #include <LevelEditor/LevelEditorSharedState.h>
13 #include "SubMaterialView.h"
14 #include "MaterialPreview.h"
15 #include "MaterialProperties.h"
16 #include "PickMaterialTool.h"
18 #include "Dialogs/QNumericBoxDialog.h"
20 #include "IUndoObject.h"
22 #include <CryCore/CryTypeInfo.h>
24 #include <QHBoxLayout>
27 #include <QToolButton>
29 namespace Private_MaterialEditor
32 REGISTER_VIEWPANE_FACTORY(CMaterialEditor
, "Material Editor", "Tools", false);
34 class CSession
: public IAssetEditingSession
37 CSession(CMaterial
* pMaterial
) : m_pMaterial(pMaterial
) {}
39 virtual const char* GetEditorName() const override
41 return "Material Editor";
44 virtual bool OnSaveAsset(IEditableAsset
& asset
) override
46 if (!m_pMaterial
|| !m_pMaterial
->Save(false))
51 //Fill metadata and dependencies to update cryasset file
52 asset
.SetFiles({ m_pMaterial
->GetFilename() });
54 std::vector
<SAssetDependencyInfo
> filenames
;
56 //TODO : not all dependencies are found, some paths are relative to same folder which is not good ...
57 int textureCount
= m_pMaterial
->GetTextureDependencies(filenames
);
59 std::vector
<std::pair
<string
, string
>> details
;
60 details
.push_back(std::pair
<string
, string
>("subMaterialCount", ToString(m_pMaterial
->GetSubMaterialCount())));
61 details
.push_back(std::pair
<string
, string
>("textureCount", ToString(textureCount
)));
63 asset
.SetDetails(details
);
64 asset
.SetDependencies(filenames
);
68 virtual void DiscardChanges(IEditableAsset
& asset
) override
75 m_pMaterial
->Reload();
78 virtual bool OnCopyAsset(INewAsset
& asset
) override
80 CMaterial
* pMaterial
= GetIEditor()->GetMaterialManager()->DuplicateMaterial(PathUtil::Make(asset
.GetFolder(), asset
.GetName()), m_pMaterial
);
81 CSession
session(pMaterial
);
82 return session
.OnSaveAsset(asset
);
85 static CMaterial
* GetSessionMaterial(CAsset
* pAsset
)
87 IAssetEditingSession
* pSession
= pAsset
->GetEditingSession();
88 if (!pSession
|| strcmp(pSession
->GetEditorName(), "Material Editor") != 0)
92 return static_cast<CSession
*>(pSession
)->GetMaterial();
96 CMaterial
* GetMaterial() { return m_pMaterial
; }
98 _smart_ptr
<CMaterial
> m_pMaterial
;
103 CMaterialEditor::CMaterialEditor()
104 : CAssetEditor("Material")
105 , m_pMaterial(nullptr)
109 EnableDockingSystem();
111 RegisterDockableWidget("Properties", [&]()
113 CInspector
* pInspector
= new CInspector(this);
114 pInspector
->SetLockable(false);
118 RegisterDockableWidget("Material", [&]() { return new CSubMaterialView(this); }, true);
119 RegisterDockableWidget("Preview", [&]() { return new CMaterialPreviewWidget(this); });
121 GetIEditor()->GetMaterialManager()->AddListener(this);
124 void CMaterialEditor::CreateToolbar()
126 const auto pToolbar
= new QWidget
;
127 const auto pSpacer
= new QWidget();
128 pSpacer
->setSizePolicy(QSizePolicy::Expanding
, QSizePolicy::Fixed
);
129 auto toolbarLayout
= new QHBoxLayout(pToolbar
);
130 toolbarLayout
->addWidget(pSpacer
);
131 toolbarLayout
->addWidget(CreateInstantEditorToolbar());
132 toolbarLayout
->setContentsMargins(0, 0, 0, 0);
134 QVBoxLayout
* pMainLayout
= new QVBoxLayout();
135 pMainLayout
->setContentsMargins(1, 1, 1, 1);
136 pMainLayout
->addWidget(pToolbar
);
137 SetContent(pMainLayout
);
140 CMaterialEditor::~CMaterialEditor()
142 GetIEditor()->GetMaterialManager()->RemoveListener(this);
145 void CMaterialEditor::InitMenuBar()
147 AddToMenu(CEditor::MenuItems::SaveAs
);
148 AddToMenu(CEditor::MenuItems::EditMenu
);
149 AddToMenu(CEditor::MenuItems::Undo
);
150 AddToMenu(CEditor::MenuItems::Redo
);
152 CAbstractMenu
* pFileMenu
= GetMenu(CEditor::MenuItems::FileMenu
);
153 QAction
* pAction
= pFileMenu
->CreateAction(CryIcon("icons:General/Picker.ico"), tr("Pick Material From Scene"), 0, 1);
154 connect(pAction
, &QAction::triggered
, this, &CMaterialEditor::OnPickMaterial
);
156 //Add a material actions menu
157 //TODO: consider adding a toolbar for material actions
158 CAbstractMenu
* materialMenu
= GetRootMenu()->CreateMenu(tr("Material"), 0, 3);
159 materialMenu
->signalAboutToShow
.Connect(this, &CMaterialEditor::FillMaterialMenu
);
161 // Enable instant editing if possible
162 CAbstractMenu
* const pEditMenu
= GetMenu(CEditor::MenuItems::EditMenu
);
163 pEditMenu
->AddCommandAction(m_pLockAction
);
166 void CMaterialEditor::OnEditorNotifyEvent(EEditorNotifyEvent event
)
170 case eNotify_OnEndSceneOpen
:
171 case eNotify_OnEndNewScene
:
172 //HACK : Due to the questionable behavior of material manager, which clears itself when the level is loaded
173 //The materials used in the scene (and the material editor) will be recreated after the scene is loaded,
174 //making the material editor appear to be not in sync. A quick hack is to reload from filename, to get the updated material.
175 if (GetAssetBeingEdited())
177 OnOpenAsset(GetAssetBeingEdited());
182 void CMaterialEditor::OnDataBaseItemEvent(IDataBaseItem
* pItem
, EDataBaseItemEvent event
)
184 if (!pItem
|| !m_pMaterial
|| event
== EDB_ITEM_EVENT_SELECTED
)
187 CMaterial
* item
= (CMaterial
*)pItem
;
189 if (pItem
== m_pMaterial
.get())
193 case EDB_ITEM_EVENT_CHANGED
:
194 case EDB_ITEM_EVENT_UPDATE_PROPERTIES
:
195 signalMaterialPropertiesChanged(item
);
196 if (GetAssetBeingEdited())
198 GetAssetBeingEdited()->SetModified(true);
201 case EDB_ITEM_EVENT_DELETE
:
202 //Note: this happens on loading of the level. We will not handle it but things may be unexpected if the item is actually being deleted from the old material editor
203 /*CRY_ASSERT_MESSAGE(0, "Material was deleted by other means while it was being edited, this case is not well handled");
210 else if (item
->GetParent() == m_pMaterial
.get())
214 case EDB_ITEM_EVENT_CHANGED
:
215 case EDB_ITEM_EVENT_UPDATE_PROPERTIES
:
216 signalMaterialPropertiesChanged(item
);
217 if (GetAssetBeingEdited())
219 GetAssetBeingEdited()->SetModified(true);
222 case EDB_ITEM_EVENT_DELETE
: //deleted from DB, what to do ?
223 if (item
== m_pEditedMaterial
)
225 SelectMaterialForEdit(nullptr);
234 void CMaterialEditor::OnSubMaterialsChanged(CMaterial::SubMaterialChange change
)
236 if (GetAssetBeingEdited())
238 GetAssetBeingEdited()->SetModified(true);
243 case CMaterial::MaterialConverted
:
244 if (m_pMaterial
->IsMultiSubMaterial())
246 if (m_pMaterial
->GetSubMaterialCount() > 0)
248 SelectMaterialForEdit(m_pMaterial
->GetSubMaterial(0));
252 SelectMaterialForEdit(nullptr);
257 SelectMaterialForEdit(m_pMaterial
);
260 case CMaterial::SubMaterialSet
:
261 //If the material we are currently editing is no longer a child of loaded material, clear it
262 if (m_pEditedMaterial
&& m_pMaterial
->IsMultiSubMaterial() && m_pEditedMaterial
->GetParent() != m_pMaterial
)
264 SelectMaterialForEdit(nullptr);
267 case CMaterial::SlotCountChanged
:
268 if (m_pMaterial
->IsMultiSubMaterial() && m_pMaterial
->GetSubMaterialCount() == 0)
270 SelectMaterialForEdit(nullptr);
278 void CMaterialEditor::CreateDefaultLayout(CDockableContainer
* pSender
)
280 QWidget
* pCenterWidget
= pSender
->SpawnWidget("Properties");
281 pSender
->SpawnWidget("Preview", pCenterWidget
, QToolWindowAreaReference::Right
);
282 QWidget
* pMaterialWidget
= pSender
->SpawnWidget("Material", pCenterWidget
, QToolWindowAreaReference::Top
);
283 pSender
->SetSplitterSizes(pMaterialWidget
, { 1, 4 });
286 void CMaterialEditor::OnLayoutChange(const QVariantMap
& state
)
288 //Rebroadcast on layout change
289 //Do not update inspector as the properties pane is unique anyway
292 signalMaterialLoaded(m_pMaterial
);
293 if (m_pEditedMaterial
)
295 signalMaterialForEditChanged(m_pEditedMaterial
);
296 BroadcastPopulateInspector();
299 CAssetEditor::OnLayoutChange(state
);
302 bool CMaterialEditor::OnOpenAsset(CAsset
* pAsset
)
304 using namespace Private_MaterialEditor
;
306 CAbstractMenu
* materialMenu
= GetMenu(tr("Material"));
307 CRY_ASSERT(materialMenu
);
308 materialMenu
->SetEnabled(!pAsset
->IsImmutable());
310 const auto& filename
= pAsset
->GetFile(0);
311 CRY_ASSERT(filename
&& *filename
);
313 const string materialName
= GetIEditor()->GetMaterialManager()->FilenameToMaterial(filename
);
315 CMaterial
* pMaterial
= CSession::GetSessionMaterial(pAsset
);
318 pMaterial
= static_cast<CMaterial
*>(GetIEditor()->GetMaterialManager()->FindItemByName(materialName
));
323 pMaterial
= GetIEditor()->GetMaterialManager()->LoadMaterial(materialName
, false);
325 else if (!pAsset
->IsModified())
327 pMaterial
->Reload(); //Enforce loading from file every time as we cannot assume the file has not changed compared to in-memory material
335 SetMaterial(pMaterial
);
340 bool CMaterialEditor::OnSaveAsset(CEditableAsset
& editAsset
)
342 return GetAssetBeingEdited()->GetEditingSession()->OnSaveAsset(editAsset
);
345 void CMaterialEditor::OnCloseAsset()
347 SetMaterial(nullptr);
350 void CMaterialEditor::OnDiscardAssetChanges(CEditableAsset
& editAsset
)
352 CRY_ASSERT(GetAssetBeingEdited());
353 CRY_ASSERT(GetAssetBeingEdited()->GetEditingSession());
355 GetAssetBeingEdited()->GetEditingSession()->DiscardChanges(editAsset
);
358 std::unique_ptr
<IAssetEditingSession
> CMaterialEditor::CreateEditingSession()
360 using namespace Private_MaterialEditor
;
367 CSession
* pSession
= new CSession(m_pMaterial
);
368 return std::unique_ptr
<IAssetEditingSession
>(pSession
);
371 void CMaterialEditor::SelectMaterialForEdit(CMaterial
* pMaterial
)
373 if (pMaterial
!= m_pEditedMaterial
)
375 m_pEditedMaterial
= pMaterial
;
377 signalMaterialForEditChanged(pMaterial
);
379 BroadcastPopulateInspector();
383 void CMaterialEditor::BroadcastPopulateInspector()
385 if (m_pEditedMaterial
)
387 m_pMaterialSerializer
.reset(new CMaterialSerializer(m_pEditedMaterial
, !m_pEditedMaterial
->CanModify()));
388 string title
= m_pMaterial
->GetName();
389 if (m_pMaterial
->IsMultiSubMaterial())
392 title
+= m_pEditedMaterial
->GetName();
396 PopulateInspectorEvent
event([this](CInspector
& inspector
)
398 inspector
.AddPropertyTree(m_pMaterialSerializer
->CreatePropertyTree());
400 event
.Broadcast(this);
404 m_pMaterialSerializer
.reset();
405 ClearInspectorEvent().Broadcast(this);
409 void CMaterialEditor::SetMaterial(CMaterial
* pMaterial
)
411 if (pMaterial
!= m_pMaterial
)
415 m_pMaterial
->signalSubMaterialsChanged
.DisconnectObject(this);
418 SelectMaterialForEdit(nullptr);
420 m_pMaterial
= pMaterial
;
422 signalMaterialLoaded(m_pMaterial
);
426 m_pMaterial
->signalSubMaterialsChanged
.Connect(this, &CMaterialEditor::OnSubMaterialsChanged
);
428 if (m_pMaterial
->IsMultiSubMaterial())
430 if (m_pMaterial
->GetSubMaterialCount() > 0)
431 SelectMaterialForEdit(m_pMaterial
->GetSubMaterial(0));
433 SelectMaterialForEdit(nullptr);
436 SelectMaterialForEdit(m_pMaterial
);
441 void CMaterialEditor::FillMaterialMenu(CAbstractMenu
* menu
)
445 int sec
= menu
->GetNextEmptySection();
447 auto action
= menu
->CreateAction(tr("Convert To Multi-Material"), sec
);
448 action
->setEnabled(m_pMaterial
&& !m_pMaterial
->IsMultiSubMaterial());
449 connect(action
, &QAction::triggered
, this, &CMaterialEditor::OnConvertToMultiMaterial
);
451 sec
= menu
->GetNextEmptySection();
453 action
= menu
->CreateAction(tr("Convert To Single Material"), sec
);
454 action
->setEnabled(m_pMaterial
&& m_pMaterial
->IsMultiSubMaterial());
455 connect(action
, &QAction::triggered
, this, &CMaterialEditor::OnConvertToSingleMaterial
);
457 action
= menu
->CreateAction(tr("Add Sub-Material"), sec
);
458 action
->setEnabled(m_pMaterial
&& m_pMaterial
->IsMultiSubMaterial());
459 connect(action
, &QAction::triggered
, this, &CMaterialEditor::OnAddSubMaterial
);
461 action
= menu
->CreateAction(tr("Set Sub-Material Slot Count..."), sec
);
462 action
->setEnabled(m_pMaterial
&& m_pMaterial
->IsMultiSubMaterial());
463 connect(action
, &QAction::triggered
, this, &CMaterialEditor::OnSetSubMaterialSlotCount
);
466 void CMaterialEditor::OnConvertToMultiMaterial()
468 CUndo
undo("Convert To Multi-Material");
469 CRY_ASSERT(m_pMaterial
&& !m_pMaterial
->IsMultiSubMaterial());
470 m_pMaterial
->ConvertToMultiMaterial();
473 void CMaterialEditor::OnConvertToSingleMaterial()
475 CUndo
undo("Convert To Single Material");
476 CRY_ASSERT(m_pMaterial
&& m_pMaterial
->IsMultiSubMaterial());
477 m_pMaterial
->ConvertToSingleMaterial();
480 void CMaterialEditor::OnAddSubMaterial()
482 CUndo
undo("Add Sub-Material");
483 CRY_ASSERT(m_pMaterial
&& m_pMaterial
->IsMultiSubMaterial());
485 const auto count
= m_pMaterial
->GetSubMaterialCount();
486 m_pMaterial
->SetSubMaterialCount(count
+ 1);
487 m_pMaterial
->ResetSubMaterial(count
, false);
490 void CMaterialEditor::OnSetSubMaterialSlotCount()
492 CRY_ASSERT(m_pMaterial
&& m_pMaterial
->IsMultiSubMaterial());
493 QNumericBoxDialog
dialog("Set Sub-Material Slot Count", m_pMaterial
->GetSubMaterialCount(), this);
495 dialog
.RestrictToInt();
498 if (dialog
.Execute())
500 CUndo
undo("Set Sub-Material Slot Count");
501 const int newCount
= dialog
.GetValue();
502 m_pMaterial
->SetSubMaterialCount(newCount
);
504 for (int i
= 0; i
< newCount
; i
++)
506 m_pMaterial
->ResetSubMaterial(i
, true);
511 void CMaterialEditor::OnPickMaterial()
513 auto pickMaterialTool
= new CPickMaterialTool();
514 pickMaterialTool
->SetActiveEditor(this);
516 GetIEditor()->GetLevelEditorSharedState()->SetEditTool(pickMaterialTool
);
519 void CMaterialEditor::OnResetSubMaterial(int slot
)
521 CUndo
undo("Reset Sub-Material");
522 CRY_ASSERT(m_pMaterial
&& m_pMaterial
->IsMultiSubMaterial());
523 m_pMaterial
->ResetSubMaterial(slot
, false);
526 void CMaterialEditor::OnRemoveSubMaterial(int slot
)
528 CUndo
undo("Remove Sub-Material");
529 CRY_ASSERT(m_pMaterial
&& m_pMaterial
->IsMultiSubMaterial());
530 m_pMaterial
->SetSubMaterial(slot
, nullptr);