1 // Copyright 2001-2019 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)
108 GetIEditor()->GetMaterialManager()->AddListener(this);
111 CMaterialEditor::~CMaterialEditor()
113 GetIEditor()->GetMaterialManager()->RemoveListener(this);
116 void CMaterialEditor::RegisterActions()
118 RegisterAction("general.undo", &CMaterialEditor::OnUndo
);
119 RegisterAction("general.redo", &CMaterialEditor::OnRedo
);
122 void CMaterialEditor::InitMenuBar()
124 AddToMenu({ CEditor::MenuItems::FileMenu
, CEditor::MenuItems::SaveAs
, CEditor::MenuItems::EditMenu
, CEditor::MenuItems::Undo
, CEditor::MenuItems::Redo
});
126 CAbstractMenu
* pFileMenu
= GetMenu(CEditor::MenuItems::FileMenu
);
127 QAction
* pAction
= pFileMenu
->CreateAction(CryIcon("icons:General/Picker.ico"), tr("Pick Material From Scene"), 0, 1);
128 connect(pAction
, &QAction::triggered
, this, &CMaterialEditor::OnPickMaterial
);
130 //Add a material actions menu
131 //TODO: consider adding a toolbar for material actions
132 CAbstractMenu
* materialMenu
= GetRootMenu()->CreateMenu(tr("Material"), 0, 3);
133 materialMenu
->signalAboutToShow
.Connect(this, &CMaterialEditor::FillMaterialMenu
);
136 void CMaterialEditor::OnEditorNotifyEvent(EEditorNotifyEvent event
)
140 case eNotify_OnEndSceneOpen
:
141 case eNotify_OnEndNewScene
:
142 //HACK : Due to the questionable behavior of material manager, which clears itself when the level is loaded
143 //The materials used in the scene (and the material editor) will be recreated after the scene is loaded,
144 //making the material editor appear to be not in sync. A quick hack is to reload from filename, to get the updated material.
145 if (GetAssetBeingEdited())
147 OnOpenAsset(GetAssetBeingEdited());
152 void CMaterialEditor::OnDataBaseItemEvent(IDataBaseItem
* pItem
, EDataBaseItemEvent event
)
154 if (!pItem
|| !m_pMaterial
|| event
== EDB_ITEM_EVENT_SELECTED
)
157 CMaterial
* item
= (CMaterial
*)pItem
;
159 if (pItem
== m_pMaterial
.get())
163 case EDB_ITEM_EVENT_CHANGED
:
164 case EDB_ITEM_EVENT_UPDATE_PROPERTIES
:
165 signalMaterialPropertiesChanged(item
);
166 if (GetAssetBeingEdited())
168 GetAssetBeingEdited()->SetModified(true);
171 case EDB_ITEM_EVENT_DELETE
:
172 //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
173 /*CRY_ASSERT_MESSAGE(0, "Material was deleted by other means while it was being edited, this case is not well handled");
180 else if (item
->GetParent() == m_pMaterial
.get())
184 case EDB_ITEM_EVENT_CHANGED
:
185 case EDB_ITEM_EVENT_UPDATE_PROPERTIES
:
186 signalMaterialPropertiesChanged(item
);
187 if (GetAssetBeingEdited())
189 GetAssetBeingEdited()->SetModified(true);
192 case EDB_ITEM_EVENT_DELETE
: //deleted from DB, what to do ?
193 if (item
== m_pEditedMaterial
)
195 SelectMaterialForEdit(nullptr);
204 void CMaterialEditor::OnSubMaterialsChanged(CMaterial::SubMaterialChange change
)
206 if (GetAssetBeingEdited())
208 GetAssetBeingEdited()->SetModified(true);
213 case CMaterial::MaterialConverted
:
214 if (m_pMaterial
->IsMultiSubMaterial())
216 if (m_pMaterial
->GetSubMaterialCount() > 0)
218 SelectMaterialForEdit(m_pMaterial
->GetSubMaterial(0));
222 SelectMaterialForEdit(nullptr);
227 SelectMaterialForEdit(m_pMaterial
);
230 case CMaterial::SubMaterialSet
:
231 //If the material we are currently editing is no longer a child of loaded material, clear it
232 if (m_pEditedMaterial
&& m_pMaterial
->IsMultiSubMaterial() && m_pEditedMaterial
->GetParent() != m_pMaterial
)
234 SelectMaterialForEdit(nullptr);
237 case CMaterial::SlotCountChanged
:
238 if (m_pMaterial
->IsMultiSubMaterial() && m_pMaterial
->GetSubMaterialCount() == 0)
240 SelectMaterialForEdit(nullptr);
248 void CMaterialEditor::OnInitialize()
250 RegisterDockableWidget("Properties", [&]()
252 CInspector
* pInspector
= new CInspector(this);
253 pInspector
->SetLockable(false);
255 if (m_pEditedMaterial
)
257 BroadcastPopulateInspector();
263 RegisterDockableWidget("Material", [&]() { return new CSubMaterialView(this); }, true);
264 RegisterDockableWidget("Preview", [&]() { return new CMaterialPreviewWidget(this); });
269 void CMaterialEditor::OnCreateDefaultLayout(CDockableContainer
* pSender
, QWidget
* pAssetBrowser
)
271 QWidget
* pCenterWidget
= pSender
->SpawnWidget("Properties", pAssetBrowser
, QToolWindowAreaReference::VSplitRight
);
272 pSender
->SpawnWidget("Preview", pCenterWidget
, QToolWindowAreaReference::Right
);
273 QWidget
* pMaterialWidget
= pSender
->SpawnWidget("Material", pCenterWidget
, QToolWindowAreaReference::Top
);
274 pSender
->SetSplitterSizes(pMaterialWidget
, { 1, 4 });
277 void CMaterialEditor::OnLayoutChange(const QVariantMap
& state
)
279 //Rebroadcast on layout change
280 //Do not update inspector as the properties pane is unique anyway
283 signalMaterialLoaded(m_pMaterial
);
284 if (m_pEditedMaterial
)
286 signalMaterialForEditChanged(m_pEditedMaterial
);
289 CAssetEditor::OnLayoutChange(state
);
292 bool CMaterialEditor::OnOpenAsset(CAsset
* pAsset
)
294 using namespace Private_MaterialEditor
;
296 CAbstractMenu
* materialMenu
= GetMenu(tr("Material"));
297 CRY_ASSERT(materialMenu
);
298 materialMenu
->SetEnabled(!pAsset
->IsImmutable());
300 const auto& filename
= pAsset
->GetFile(0);
301 CRY_ASSERT(filename
&& *filename
);
303 const string materialName
= GetIEditor()->GetMaterialManager()->FilenameToMaterial(filename
);
305 CMaterial
* pMaterial
= CSession::GetSessionMaterial(pAsset
);
308 pMaterial
= static_cast<CMaterial
*>(GetIEditor()->GetMaterialManager()->FindItemByName(materialName
));
313 pMaterial
= GetIEditor()->GetMaterialManager()->LoadMaterial(materialName
, false);
315 else if (!pAsset
->IsModified())
317 pMaterial
->Reload(); //Enforce loading from file every time as we cannot assume the file has not changed compared to in-memory material
325 SetMaterial(pMaterial
);
330 void CMaterialEditor::OnCloseAsset()
332 SetMaterial(nullptr);
335 void CMaterialEditor::OnDiscardAssetChanges(CEditableAsset
& editAsset
)
337 CRY_ASSERT(GetAssetBeingEdited());
338 CRY_ASSERT(GetAssetBeingEdited()->GetEditingSession());
340 GetAssetBeingEdited()->GetEditingSession()->DiscardChanges(editAsset
);
343 std::unique_ptr
<IAssetEditingSession
> CMaterialEditor::CreateEditingSession()
345 using namespace Private_MaterialEditor
;
352 CSession
* pSession
= new CSession(m_pMaterial
);
353 return std::unique_ptr
<IAssetEditingSession
>(pSession
);
356 void CMaterialEditor::SelectMaterialForEdit(CMaterial
* pMaterial
)
358 if (pMaterial
!= m_pEditedMaterial
)
360 m_pEditedMaterial
= pMaterial
;
362 signalMaterialForEditChanged(pMaterial
);
364 BroadcastPopulateInspector();
368 void CMaterialEditor::BroadcastPopulateInspector()
370 if (m_pEditedMaterial
)
372 m_pMaterialSerializer
.reset(new CMaterialSerializer(m_pEditedMaterial
, !m_pEditedMaterial
->CanModify()));
373 string title
= m_pMaterial
->GetName();
374 if (m_pMaterial
->IsMultiSubMaterial())
377 title
+= m_pEditedMaterial
->GetName();
381 PopulateInspectorEvent
event([this](CInspector
& inspector
)
383 inspector
.AddPropertyTree(m_pMaterialSerializer
->CreatePropertyTree());
385 event
.Broadcast(this);
389 m_pMaterialSerializer
.reset();
390 ClearInspectorEvent().Broadcast(this);
394 void CMaterialEditor::SetMaterial(CMaterial
* pMaterial
)
396 if (pMaterial
!= m_pMaterial
)
400 m_pMaterial
->signalSubMaterialsChanged
.DisconnectObject(this);
403 SelectMaterialForEdit(nullptr);
405 m_pMaterial
= pMaterial
;
407 signalMaterialLoaded(m_pMaterial
);
411 m_pMaterial
->signalSubMaterialsChanged
.Connect(this, &CMaterialEditor::OnSubMaterialsChanged
);
413 if (m_pMaterial
->IsMultiSubMaterial())
415 if (m_pMaterial
->GetSubMaterialCount() > 0)
416 SelectMaterialForEdit(m_pMaterial
->GetSubMaterial(0));
418 SelectMaterialForEdit(nullptr);
421 SelectMaterialForEdit(m_pMaterial
);
426 void CMaterialEditor::FillMaterialMenu(CAbstractMenu
* menu
)
430 int sec
= menu
->GetNextEmptySection();
432 auto action
= menu
->CreateAction(tr("Convert To Multi-Material"), sec
);
433 action
->setEnabled(m_pMaterial
&& !m_pMaterial
->IsMultiSubMaterial());
434 connect(action
, &QAction::triggered
, this, &CMaterialEditor::OnConvertToMultiMaterial
);
436 sec
= menu
->GetNextEmptySection();
438 action
= menu
->CreateAction(tr("Convert To Single Material"), sec
);
439 action
->setEnabled(m_pMaterial
&& m_pMaterial
->IsMultiSubMaterial());
440 connect(action
, &QAction::triggered
, this, &CMaterialEditor::OnConvertToSingleMaterial
);
442 action
= menu
->CreateAction(tr("Add Sub-Material"), sec
);
443 action
->setEnabled(m_pMaterial
&& m_pMaterial
->IsMultiSubMaterial());
444 connect(action
, &QAction::triggered
, this, &CMaterialEditor::OnAddSubMaterial
);
446 action
= menu
->CreateAction(tr("Set Sub-Material Slot Count..."), sec
);
447 action
->setEnabled(m_pMaterial
&& m_pMaterial
->IsMultiSubMaterial());
448 connect(action
, &QAction::triggered
, this, &CMaterialEditor::OnSetSubMaterialSlotCount
);
451 void CMaterialEditor::OnConvertToMultiMaterial()
453 CUndo
undo("Convert To Multi-Material");
454 CRY_ASSERT(m_pMaterial
&& !m_pMaterial
->IsMultiSubMaterial());
455 m_pMaterial
->ConvertToMultiMaterial();
458 void CMaterialEditor::OnConvertToSingleMaterial()
460 CUndo
undo("Convert To Single Material");
461 CRY_ASSERT(m_pMaterial
&& m_pMaterial
->IsMultiSubMaterial());
462 m_pMaterial
->ConvertToSingleMaterial();
465 void CMaterialEditor::OnAddSubMaterial()
467 CUndo
undo("Add Sub-Material");
468 CRY_ASSERT(m_pMaterial
&& m_pMaterial
->IsMultiSubMaterial());
470 const auto count
= m_pMaterial
->GetSubMaterialCount();
471 m_pMaterial
->SetSubMaterialCount(count
+ 1);
472 m_pMaterial
->ResetSubMaterial(count
, false);
475 void CMaterialEditor::OnSetSubMaterialSlotCount()
477 CRY_ASSERT(m_pMaterial
&& m_pMaterial
->IsMultiSubMaterial());
478 QNumericBoxDialog
dialog("Set Sub-Material Slot Count", m_pMaterial
->GetSubMaterialCount(), this);
480 dialog
.RestrictToInt();
483 if (dialog
.Execute())
485 CUndo
undo("Set Sub-Material Slot Count");
486 const int newCount
= dialog
.GetValue();
487 m_pMaterial
->SetSubMaterialCount(newCount
);
489 for (int i
= 0; i
< newCount
; i
++)
491 m_pMaterial
->ResetSubMaterial(i
, true);
496 void CMaterialEditor::OnPickMaterial()
498 auto pickMaterialTool
= new CPickMaterialTool();
499 pickMaterialTool
->SetActiveEditor(this);
501 GetIEditor()->GetLevelEditorSharedState()->SetEditTool(pickMaterialTool
);
504 void CMaterialEditor::OnResetSubMaterial(int slot
)
506 CUndo
undo("Reset Sub-Material");
507 CRY_ASSERT(m_pMaterial
&& m_pMaterial
->IsMultiSubMaterial());
508 m_pMaterial
->ResetSubMaterial(slot
, false);
511 void CMaterialEditor::OnRemoveSubMaterial(int slot
)
513 CUndo
undo("Remove Sub-Material");
514 CRY_ASSERT(m_pMaterial
&& m_pMaterial
->IsMultiSubMaterial());
515 m_pMaterial
->SetSubMaterial(slot
, nullptr);