You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Code/Sandbox/Editor/Material/MaterialManager.cpp

2096 lines
67 KiB
C++

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
#include "EditorDefs.h"
#include "MaterialManager.h"
// Qt
#include <QMessageBox>
// AzCore
#include <AzCore/IO/Path/Path.h>
// AzFramework
#include <AzFramework/Render/RenderSystemBus.h>
// AzToolsFramework
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <AzToolsFramework/AssetBrowser/EBusFindAssetTypeByName.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserEntry.h>
// Editor
#include "MainWindow.h"
#include "MaterialLibrary.h"
#include "MaterialSender.h"
#include "MaterialUtils.h"
#include "ModelViewport.h"
#include "ISourceControl.h"
#include "UsedResources.h"
#include "Viewport.h"
#include "Commands/CommandManager.h"
#include "Include/IObjectManager.h"
#include "Objects/BaseObject.h"
#include "Objects/SelectionGroup.h"
static const char* MATERIALS_LIBS_PATH = "Materials/";
static unsigned int s_highlightUpdateCounter = 0;
// Convert a material name into a material identifier (no extension, no gamename, etc) so that it can be compared
// in the hash.
static QString UnifyMaterialName(const QString& source)
{
char tempBuffer[AZ_MAX_PATH_LEN];
azstrncpy(tempBuffer, AZ_ARRAY_SIZE(tempBuffer), source.toUtf8().data(), AZ_ARRAY_SIZE(tempBuffer) - 1);
MaterialUtils::UnifyMaterialName(tempBuffer);
return QString(tempBuffer);
}
struct SHighlightMode
{
float m_colorHue;
float m_period;
bool m_continuous;
};
static SHighlightMode g_highlightModes[] = {
{ 0.70f, 0.8f, true }, // purple
{ 0.25f, 0.75f, false }, // green
{ 0.0, 0.75f, true } // red
};
class CMaterialHighlighter
{
public:
void Start(CMaterial* pMaterial, int modeFlag);
void Stop(CMaterial* pMaterial, int modeFlag);
void GetHighlightColor(ColorF* color, float* intensity, int flags);
void ClearMaterials() { m_materials.clear(); };
void RestoreMaterials();
void Update();
private:
struct SHighlightOptions
{
int m_modeFlags;
};
typedef std::map<CMaterial*, SHighlightOptions> Materials;
Materials m_materials;
};
AZStd::string DccMaterialToSourcePath(const AZStd::string& relativeDccMaterialPath)
{
AZStd::string fullSourcePath;
bool sourcePathFound = false;
// Get source path using relative .dccmtl path
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(sourcePathFound, &AzToolsFramework::AssetSystemRequestBus::Events::GetFullSourcePathFromRelativeProductPath, relativeDccMaterialPath, fullSourcePath);
if (sourcePathFound)
{
// Set source path extension to ".mtl"
AzFramework::StringFunc::Path::ReplaceExtension(fullSourcePath, MATERIAL_FILE_EXT);
}
return fullSourcePath;
}
void CMaterialHighlighter::Start(CMaterial* pMaterial, int modeFlag)
{
Materials::iterator it = m_materials.find(pMaterial);
if (it == m_materials.end())
{
SHighlightOptions& options = m_materials[pMaterial];
options.m_modeFlags = modeFlag;
}
else
{
SHighlightOptions& options = it->second;
options.m_modeFlags |= modeFlag;
}
}
void CMaterialHighlighter::Stop(CMaterial* pMaterial, int modeFlag)
{
if (pMaterial)
{
pMaterial->SetHighlightFlags(0);
}
Materials::iterator it = m_materials.find(pMaterial);
if (it == m_materials.end())
{
return;
}
SHighlightOptions& options = it->second;
MAKE_SURE((options.m_modeFlags & modeFlag) != 0, return );
options.m_modeFlags &= ~modeFlag;
if (options.m_modeFlags == 0)
{
m_materials.erase(it);
}
}
void CMaterialHighlighter::RestoreMaterials()
{
for (Materials::iterator it = m_materials.begin(); it != m_materials.end(); ++it)
{
if (it->first)
{
it->first->SetHighlightFlags(0);
}
}
}
void CMaterialHighlighter::Update()
{
unsigned int counter = s_highlightUpdateCounter;
Materials::iterator it;
for (it = m_materials.begin(); it != m_materials.end(); ++it)
{
// Only update each material every 4 frames
if (counter++ % 4 == 0)
{
it->first->SetHighlightFlags(it->second.m_modeFlags);
}
}
s_highlightUpdateCounter = (s_highlightUpdateCounter + 1) % 4;
}
void CMaterialHighlighter::GetHighlightColor(ColorF* color, float* intensity, int flags)
{
MAKE_SURE(color != 0, return );
MAKE_SURE(intensity != 0, return );
*intensity = 0.0f;
if (flags == 0)
{
return;
}
int flagIndex = 0;
while (flags)
{
if ((flags & 1) != 0)
{
break;
}
flags = flags >> 1;
++flagIndex;
}
MAKE_SURE(flagIndex < sizeof(g_highlightModes) / sizeof(g_highlightModes[0]), return );
const SHighlightMode& mode = g_highlightModes[flagIndex];
float t = GetTickCount() / 1000.0f;
float h = mode.m_colorHue;
float s = 1.0f;
float v = 1.0f;
color->fromHSV(h + sinf(t * g_PI2 * 5.0f) * 0.025f, s, v);
color->a = 1.0f;
if (mode.m_continuous)
{
*intensity = fabsf(sinf(t * g_PI2 / mode.m_period));
}
else
{
*intensity = max(0.0f, sinf(t * g_PI2 / mode.m_period));
}
}
//////////////////////////////////////////////////////////////////////////
// CMaterialManager implementation.
//////////////////////////////////////////////////////////////////////////
CMaterialManager::CMaterialManager(CRegistrationContext& regCtx)
: m_pHighlighter(new CMaterialHighlighter)
, m_highlightMask(eHighlight_All)
, m_currentFolder("")
, m_joinThreads(false)
{
m_bUniqGuidMap = false;
m_bUniqNameMap = true;
m_bEditorUiReady = false;
m_bSourceControlErrorReported = false;
m_sourceControlFunctionQueued = false;
m_pLevelLibrary = (CBaseLibrary*)AddLibrary("Level", true);
m_MatSender = new CMaterialSender(true);
EBusFindAssetTypeByName materialResult("Material"); //from MaterialAssetTypeInfo.cpp, case insensitive
AZ::AssetTypeInfoBus::BroadcastResult(materialResult, &AZ::AssetTypeInfo::GetAssetType);
m_materialAssetType = materialResult.GetAssetType();
EBusFindAssetTypeByName dccMaterialResult("DccMaterial"); //from MaterialAssetTypeInfo.cpp, case insensitive
AZ::AssetTypeInfoBus::BroadcastResult(dccMaterialResult, &AZ::AssetTypeInfo::GetAssetType);
m_dccMaterialAssetType = dccMaterialResult.GetAssetType();
RegisterCommands(regCtx);
AzToolsFramework::AssetBrowser::AssetBrowserInteractionNotificationBus::Handler::BusConnect();
AzToolsFramework::AssetBrowser::AssetBrowserModelNotificationBus::Handler::BusConnect();
AzFramework::AssetCatalogEventBus::Handler::BusConnect();
AzToolsFramework::EditorEvents::Bus::Handler::BusConnect();
}
//////////////////////////////////////////////////////////////////////////
CMaterialManager::~CMaterialManager()
{
AzToolsFramework::AssetBrowser::AssetBrowserModelNotificationBus::Handler::BusDisconnect();
AzFramework::AssetCatalogEventBus::Handler::BusDisconnect();
AzToolsFramework::EditorEvents::Bus::Handler::BusDisconnect();
delete m_pHighlighter;
m_pHighlighter = 0;
if (gEnv->p3DEngine)
{
gEnv->p3DEngine->GetMaterialManager()->SetListener(NULL);
}
if (m_MatSender)
{
delete m_MatSender;
m_MatSender = 0;
}
// Terminate thread that saves dcc materials.
m_joinThreads = true;
if (m_bEditorUiReady)
{
m_dccMaterialSaveSemaphore.release();
m_dccMaterialSaveThread.join();
}
}
//////////////////////////////////////////////////////////////////////////
void CMaterialManager::Set3DEngine()
{
if (gEnv->p3DEngine)
{
gEnv->p3DEngine->GetMaterialManager()->SetListener(this);
}
}
//////////////////////////////////////////////////////////////////////////
void CMaterialManager::ClearAll()
{
SetCurrentMaterial(NULL);
CBaseLibraryManager::ClearAll();
m_pLevelLibrary = (CBaseLibrary*)AddLibrary("Level", true);
}
//////////////////////////////////////////////////////////////////////////
CMaterial* CMaterialManager::CreateMaterial(const QString& sMaterialName,const XmlNodeRef& node, int nMtlFlags, [[maybe_unused]] unsigned long nLoadingFlags)
{
CMaterial* pMaterial = new CMaterial(sMaterialName, nMtlFlags);
if (node)
{
CBaseLibraryItem::SerializeContext serCtx(node, true);
serCtx.bUniqName = true;
pMaterial->Serialize(serCtx);
}
if (!pMaterial->IsPureChild() && !(pMaterial->GetFlags() & MTL_FLAG_UIMATERIAL))
{
RegisterItem(pMaterial);
}
return pMaterial;
}
//////////////////////////////////////////////////////////////////////////
CMaterial* CMaterialManager::CreateMaterial(const char* sMaterialName,const XmlNodeRef& node, int nMtlFlags, unsigned long nLoadingFlags)
{
return CreateMaterial(QString(sMaterialName), node, nMtlFlags, nLoadingFlags);
}
//////////////////////////////////////////////////////////////////////////
void CMaterialManager::Export(XmlNodeRef& node)
{
XmlNodeRef libs = node->newChild("MaterialsLibrary");
for (int i = 0; i < GetLibraryCount(); i++)
{
IDataBaseLibrary* pLib = GetLibrary(i);
// Level libraries are saved in in level.
XmlNodeRef libNode = libs->newChild("Library");
// Export library.
libNode->setAttr("Name", pLib->GetName().toUtf8().data());
}
}
//////////////////////////////////////////////////////////////////////////
int CMaterialManager::ExportLib(CMaterialLibrary* pLib, XmlNodeRef& libNode)
{
int num = 0;
// Export library.
libNode->setAttr("Name", pLib->GetName().toUtf8().data());
libNode->setAttr("File", pLib->GetFilename().toUtf8().data());
char version[50];
GetIEditor()->GetFileVersion().ToString(version, AZ_ARRAY_SIZE(version));
libNode->setAttr("SandboxVersion", version);
// Serialize prototypes.
for (int j = 0; j < pLib->GetItemCount(); j++)
{
CMaterial* pMtl = (CMaterial*)pLib->GetItem(j);
// Only export real used materials.
if (pMtl->IsDummy() || !pMtl->IsUsed() || pMtl->IsPureChild())
{
continue;
}
XmlNodeRef itemNode = libNode->newChild("Material");
itemNode->setAttr("Name", pMtl->GetName().toUtf8().data());
num++;
}
return num;
}
//////////////////////////////////////////////////////////////////////////
void CMaterialManager::SetSelectedItem(IDataBaseItem* pItem)
{
m_pSelectedItem = (CBaseLibraryItem*)pItem;
SetCurrentMaterial((CMaterial*)pItem);
}
//////////////////////////////////////////////////////////////////////////
void CMaterialManager::SetCurrentMaterial(CMaterial* pMtl)
{
if (m_pCurrentMaterial)
{
// Changing current material. save old one.
if (m_pCurrentMaterial->IsModified())
{
m_pCurrentMaterial->Save();
}
}
m_pCurrentMaterial = pMtl;
if (m_pCurrentMaterial)
{
m_pCurrentMaterial->OnMakeCurrent();
m_pCurrentEngineMaterial = m_pCurrentMaterial->GetMatInfo();
}
else
{
m_pCurrentEngineMaterial = 0;
}
m_pSelectedItem = pMtl;
m_pSelectedParent = pMtl ? pMtl->GetParent() : NULL;
NotifyItemEvent(m_pCurrentMaterial, EDB_ITEM_EVENT_SELECTED);
}
//////////////////////////////////////////////////////////////////////////
void CMaterialManager::SetCurrentFolder(const QString& folder)
{
m_currentFolder = folder;
}
//////////////////////////////////////////////////////////////////////////
void CMaterialManager::SetMarkedMaterials(const std::vector<_smart_ptr<CMaterial> >& markedMaterials)
{
m_markedMaterials = markedMaterials;
}
void CMaterialManager::OnLoadShader(CMaterial* pMaterial)
{
RemoveFromHighlighting(pMaterial, eHighlight_All);
AddForHighlighting(pMaterial);
}
//////////////////////////////////////////////////////////////////////////
CMaterial* CMaterialManager::GetCurrentMaterial() const
{
return m_pCurrentMaterial;
}
//////////////////////////////////////////////////////////////////////////
CBaseLibraryItem* CMaterialManager::MakeNewItem()
{
CMaterial* pMaterial = new CMaterial("", 0);
return pMaterial;
}
//////////////////////////////////////////////////////////////////////////
CBaseLibrary* CMaterialManager::MakeNewLibrary()
{
return new CMaterialLibrary(this);
}
//////////////////////////////////////////////////////////////////////////
QString CMaterialManager::GetRootNodeName()
{
return "MaterialsLibs";
}
//////////////////////////////////////////////////////////////////////////
QString CMaterialManager::GetLibsPath()
{
if (m_libsPath.isEmpty())
{
m_libsPath = MATERIALS_LIBS_PATH;
}
return m_libsPath;
}
//////////////////////////////////////////////////////////////////////////
void CMaterialManager::ReportDuplicateItem(CBaseLibraryItem* pItem, CBaseLibraryItem* pOldItem)
{
QString sLibName;
if (pOldItem->GetLibrary())
{
sLibName = pOldItem->GetLibrary()->GetName();
}
CErrorRecord err;
err.pItem = (CMaterial*)pOldItem;
err.error = QObject::tr("Material %1 with the duplicate name to the loaded material %2 ignored").arg(pItem->GetName(), pOldItem->GetName());
GetIEditor()->GetErrorReport()->ReportError(err);
}
//////////////////////////////////////////////////////////////////////////
void CMaterialManager::Serialize([[maybe_unused]] XmlNodeRef& node, bool bLoading)
{
//CBaseLibraryManager::Serialize( node,bLoading );
if (bLoading)
{
}
else
{
}
}
//////////////////////////////////////////////////////////////////////////
void CMaterialManager::OnEditorNotifyEvent(EEditorNotifyEvent event)
{
CBaseLibraryManager::OnEditorNotifyEvent(event);
switch (event)
{
case eNotify_OnInit:
InitMatSender();
break;
case eNotify_OnIdleUpdate:
m_pHighlighter->Update();
break;
case eNotify_OnBeginGameMode:
m_pHighlighter->RestoreMaterials();
break;
case eNotify_OnEndGameMode:
ReloadDirtyMaterials();
break;
case eNotify_OnBeginNewScene:
SetCurrentMaterial(0);
break;
case eNotify_OnBeginSceneOpen:
SetCurrentMaterial(0);
break;
case eNotify_OnMissionChange:
SetCurrentMaterial(0);
break;
case eNotify_OnCloseScene:
SetCurrentMaterial(0);
m_pHighlighter->ClearMaterials();
break;
case eNotify_OnQuit:
SetCurrentMaterial(0);
if (gEnv->p3DEngine)
{
gEnv->p3DEngine->GetMaterialManager()->SetListener(NULL);
}
break;
}
}
//////////////////////////////////////////////////////////////////////////
void CMaterialManager::ReloadDirtyMaterials()
{
if (!GetIEditor()->Get3DEngine())
{
return;
}
IMaterialManager* runtimeMaterialManager = GetIEditor()->Get3DEngine()->GetMaterialManager();
uint32 mtlCount = 0;
runtimeMaterialManager->GetLoadedMaterials(NULL, mtlCount);
if (mtlCount > 0)
{
AZStd::vector<_smart_ptr<IMaterial>> allMaterials;
allMaterials.reserve(mtlCount);
[[maybe_unused]] uint32 mtlCountPrev = mtlCount;
runtimeMaterialManager->GetLoadedMaterials(&allMaterials, mtlCount);
AZ_Assert(mtlCountPrev == mtlCount && mtlCount == allMaterials.size(), "It appears GetLoadedMaterials was not used correctly.");
for (size_t i = 0; i < mtlCount; ++i)
{
_smart_ptr<IMaterial> pMtl = allMaterials[i];
if (pMtl && pMtl->IsDirty())
{
runtimeMaterialManager->ReloadMaterial(pMtl);
}
}
}
}
//////////////////////////////////////////////////////////////////////////
CMaterial* CMaterialManager::LoadMaterial(const QString& sMaterialName, bool bMakeIfNotFound)
{
LOADING_TIME_PROFILE_SECTION(GetISystem());
QString sMaterialNameClear = UnifyMaterialName(sMaterialName);
QString fullSourcePath = MaterialToFilename(sMaterialNameClear);
QString relativePath = PathUtil::ReplaceExtension(sMaterialNameClear.toUtf8().data(), MATERIAL_FILE_EXT).c_str();
return LoadMaterialInternal(sMaterialNameClear, fullSourcePath, relativePath, bMakeIfNotFound);
}
//////////////////////////////////////////////////////////////////////////
XmlNodeRef CMaterialManager::LoadXmlNode(const QString &fullSourcePath, const QString &relativeFilePath)
{
XmlNodeRef materialNode = GetISystem()->LoadXmlFromFile(fullSourcePath.toUtf8().data());
if (!materialNode)
{
// try again with the product file in case its present
materialNode = GetISystem()->LoadXmlFromFile(relativeFilePath.toUtf8().data());
}
return materialNode;
}
//////////////////////////////////////////////////////////////////////////
CMaterial* CMaterialManager::LoadMaterialWithFullSourcePath(const QString& relativeFilePath, const QString& fullSourcePath, bool makeIfNotFound /*= true*/)
{
QString materialNameClear = UnifyMaterialName(relativeFilePath);
return LoadMaterialInternal(materialNameClear, fullSourcePath, relativeFilePath, makeIfNotFound);
}
//////////////////////////////////////////////////////////////////////////
CMaterial* CMaterialManager::LoadMaterialInternal(const QString &materialNameClear, const QString &fullSourcePath, const QString &relativeFilePath, bool makeIfNotFound)
{
// Note: We are loading from source files here, not from compiled assets, so there is no need to query the asset system for compilation status, etc.
// Load material with this name if not yet loaded.
CMaterial* pMaterial = (CMaterial*)FindItemByName(materialNameClear);
if (pMaterial)
{
// If this is a dummy material that was created before for not found mtl file
// try reload the mtl file again to get valid material data.
if (pMaterial->IsDummy())
{
XmlNodeRef mtlNode = GetISystem()->LoadXmlFromFile(fullSourcePath.toUtf8().data());
if (mtlNode)
{
DeleteMaterial(pMaterial);
pMaterial = CreateMaterial(materialNameClear, mtlNode);
}
}
return pMaterial;
}
XmlNodeRef mtlNode = LoadXmlNode(fullSourcePath, relativeFilePath);
if (mtlNode)
{
pMaterial = CreateMaterial(materialNameClear, mtlNode);
}
else
{
if (makeIfNotFound)
{
pMaterial = new CMaterial(materialNameClear);
pMaterial->SetDummy(true);
RegisterItem(pMaterial);
CErrorRecord err;
err.error = QObject::tr("Material %1 not found").arg(materialNameClear);
GetIEditor()->GetErrorReport()->ReportError(err);
}
}
return pMaterial;
}
//////////////////////////////////////////////////////////////////////////
CMaterial* CMaterialManager::LoadMaterial(const char* sMaterialName, bool bMakeIfNotFound)
{
return LoadMaterial(QString(sMaterialName), bMakeIfNotFound);
}
//////////////////////////////////////////////////////////////////////////
void CMaterialManager::AddSourceFileOpeners(const char* fullSourceFileName, [[maybe_unused]] const AZ::Uuid& sourceUUID, AzToolsFramework::AssetBrowser::SourceFileOpenerList& openers)
{
using namespace AzToolsFramework;
using namespace AzToolsFramework::AssetBrowser;
// disable if other project is active
if (AzFramework::Render::RenderSystemRequestBus::HasHandlers())
{
return;
}
if (AZStd::wildcard_match("*.mtl", fullSourceFileName))
{
// we can handle these!
auto materialCallback = [this](const char* fullSourceFileNameInCall, const AZ::Uuid& sourceUUIDInCall)
{
const SourceAssetBrowserEntry* fullDetails = SourceAssetBrowserEntry::GetSourceByUuid(sourceUUIDInCall);
if (fullDetails)
{
CMaterial* materialFile = LoadMaterialWithFullSourcePath(QString::fromUtf8(fullDetails->GetRelativePath().c_str()), QString::fromUtf8(fullSourceFileNameInCall), false);
if (materialFile)
{
OpenViewPane("Material Editor");
SetCurrentMaterial(materialFile); // the material browser pane should be able to deal with this.
}
}
};
openers.push_back({ "O3DE_MaterialEditor", "Open In Material Editor...", QIcon(), materialCallback });
}
}
//////////////////////////////////////////////////////////////////////////
static bool MaterialRequiresSurfaceType(CMaterial* pMaterial)
{
// Do not enforce Surface Type...
// ...over editor UI materials
if ((pMaterial->GetFlags() & MTL_FLAG_UIMATERIAL) != 0)
{
return false;
}
// ...over SKY
if (pMaterial->GetShaderName() == "DistanceCloud" ||
pMaterial->GetShaderName() == "Sky" ||
pMaterial->GetShaderName() == "SkyHDR")
{
return false;
}
// ...over terrain materials
if (pMaterial->GetShaderName() == "Terrain.Layer")
{
return false;
}
// ...over vegetation
if (pMaterial->GetShaderName() == "Vegetation")
{
return false;
}
// ...over decals
bool requiresSurfaceType = true;
CVarBlock* pShaderGenParams = pMaterial->GetShaderGenParamsVars();
if (pShaderGenParams)
{
if (IVariable* pVar = pShaderGenParams->FindVariable("Decal"))
{
int value = 0;
pVar->Get(value);
if (value)
{
requiresSurfaceType = false;
}
}
// The function GetShaderGenParamsVars allocates a new CVarBlock object, so let's clean it up here
delete pShaderGenParams;
}
return requiresSurfaceType;
}
//////////////////////////////////////////////////////////////////////////
int CMaterialManager::GetHighlightFlags(CMaterial* pMaterial) const
{
if (pMaterial == NULL)
{
return 0;
}
if ((pMaterial->GetFlags() & MTL_FLAG_NODRAW) != 0)
{
return 0;
}
int result = 0;
if (pMaterial == m_pHighlightMaterial)
{
result |= eHighlight_Pick;
}
const QString& surfaceTypeName = pMaterial->GetSurfaceTypeName();
if (surfaceTypeName.isEmpty() && MaterialRequiresSurfaceType(pMaterial))
{
result |= eHighlight_NoSurfaceType;
}
if (GetIEditor()->Get3DEngine())
{
if (ISurfaceTypeManager* pSurfaceManager = GetIEditor()->Get3DEngine()->GetMaterialManager()->GetSurfaceTypeManager())
{
const ISurfaceType* pSurfaceType = pSurfaceManager->GetSurfaceTypeByName(surfaceTypeName.toUtf8().data());
if (pSurfaceType && pSurfaceType->GetBreakability() != 0)
{
result |= eHighlight_Breakable;
}
}
}
return result;
}
//////////////////////////////////////////////////////////////////////////
void CMaterialManager::AddForHighlighting(CMaterial* pMaterial)
{
if (pMaterial == NULL)
{
return;
}
int highlightFlags = (GetHighlightFlags(pMaterial) & m_highlightMask);
if (highlightFlags != 0)
{
m_pHighlighter->Start(pMaterial, highlightFlags);
}
int count = pMaterial->GetSubMaterialCount();
for (int i = 0; i < count; ++i)
{
CMaterial* pChild = pMaterial->GetSubMaterial(i);
if (!pChild)
{
continue;
}
AddForHighlighting(pChild);
}
}
//////////////////////////////////////////////////////////////////////////
void CMaterialManager::RemoveFromHighlighting(CMaterial* pMaterial, int mask)
{
if (pMaterial == NULL)
{
return;
}
m_pHighlighter->Stop(pMaterial, mask);
int count = pMaterial->GetSubMaterialCount();
for (int i = 0; i < count; ++i)
{
CMaterial* pChild = pMaterial->GetSubMaterial(i);
if (!pChild)
{
continue;
}
RemoveFromHighlighting(pChild, mask);
}
}
//////////////////////////////////////////////////////////////////////////
void CMaterialManager::UpdateHighlightedMaterials()
{
IDataBaseItemEnumerator* pEnum = CBaseLibraryManager::GetItemEnumerator();
if (!pEnum)
{
return;
}
CMaterial* pMaterial = (CMaterial*)pEnum->GetFirst();
while (pMaterial)
{
RemoveFromHighlighting(pMaterial, eHighlight_All);
AddForHighlighting(pMaterial);
pMaterial = (CMaterial*)pEnum->GetNext();
}
pEnum->Release();
}
//////////////////////////////////////////////////////////////////////////
void CMaterialManager::OnRequestMaterial(_smart_ptr<IMaterial> pMatInfo)
{
const char* pcName = pMatInfo->GetName();
CMaterial* pMaterial = (CMaterial*) pMatInfo->GetUserData();
if (!pMaterial && pcName && *pcName)
{
pMaterial = LoadMaterial(pcName, false);
}
if (pMaterial)
{
_smart_ptr<IMaterial> pNewMatInfo = pMaterial->GetMatInfo(true);
assert(pNewMatInfo == pMatInfo);
//Only register if the material is not registered
if (!pMaterial->IsRegistered())
{
RegisterItem(pMaterial);
}
}
}
//////////////////////////////////////////////////////////////////////////
void CMaterialManager::OnCreateMaterial(_smart_ptr<IMaterial> pMatInfo)
{
CMaterial* existingMaterial = static_cast<CMaterial*>(FindItemByName(UnifyMaterialName(pMatInfo->GetName())));
bool materialAlreadyExists = existingMaterial != nullptr;
// If its not a sub-material or a UI material
if (!(pMatInfo->GetFlags() & MTL_FLAG_PURE_CHILD) && !(pMatInfo->GetFlags() & MTL_FLAG_UIMATERIAL))
{
// Create a new editor material if it doesn't exist
if (!materialAlreadyExists)
{
CMaterial* pMaterial = new CMaterial(pMatInfo->GetName());
pMaterial->SetFromMatInfo(pMatInfo);
RegisterItem(pMaterial);
AddForHighlighting(pMaterial);
}
else
{
// If the material already exists, re-set its values from the engine material that was just re-loaded
existingMaterial->SetFromMatInfo(pMatInfo);
}
}
}
//////////////////////////////////////////////////////////////////////////
void CMaterialManager::OnDeleteMaterial(_smart_ptr<IMaterial> pMaterial)
{
CMaterial* pMtl = (CMaterial*)pMaterial->GetUserData();
if (pMtl)
{
RemoveFromHighlighting(pMtl, eHighlight_All);
DeleteMaterial(pMtl);
}
}
//////////////////////////////////////////////////////////////////////////
bool CMaterialManager::IsCurrentMaterial(_smart_ptr<IMaterial> pMaterial) const
{
if (!pMaterial)
{
return false;
}
CMaterial* pMtl = static_cast<CMaterial*>(pMaterial->GetUserData());
bool currentMaterial = (pMtl == m_pCurrentMaterial);
if (pMtl->GetParent())
{
currentMaterial |= (pMtl->GetParent() == m_pCurrentMaterial);
}
for (size_t subMatIdx = 0; subMatIdx < pMtl->GetMatInfo()->GetSubMtlCount(); ++subMatIdx)
{
if (static_cast<CMaterial*>(pMtl->GetMatInfo()->GetSubMtl(subMatIdx)->GetUserData()) == m_pCurrentMaterial)
{
currentMaterial = true;
break;
}
}
return currentMaterial;
}
//////////////////////////////////////////////////////////////////////////
CMaterial* CMaterialManager::FromIMaterial(_smart_ptr<IMaterial> engineMaterial)
{
if (!engineMaterial)
{
return nullptr;
}
CMaterial* editorMaterial = (CMaterial*)engineMaterial->GetUserData();
if (!editorMaterial)
{
// If the user data isn't set, check for an existing material with the same name
editorMaterial = static_cast<CMaterial*>(FindItemByName(UnifyMaterialName(engineMaterial->GetName())));
}
return editorMaterial;
}
//////////////////////////////////////////////////////////////////////////
void CMaterialManager::SaveAllLibs()
{
}
//////////////////////////////////////////////////////////////////////////
QString CMaterialManager::FilenameToMaterial(const QString& filename)
{
// Convert a full or relative path to a normalized name that can be used in a hash (so lowercase, relative path, correct slashes, remove extension)
// note that it may already be an asset path, if so, don't add the overhead of calling into the AP and convert it.
// if it starts with an alias (@) or if its an absolute file path, we need to convert it. Otherwise we really don't...
QString name = filename;
if (name.startsWith(QChar('@')) || AZ::IO::PathView(name.toUtf8().data()).IsAbsolute())
{
name = Path::FullPathToGamePath(filename); // convert any full path to a relative path instead.
}
QByteArray n = name.toUtf8();
MaterialUtils::UnifyMaterialName(n.data()); // Utility function used by all other parts of the code to unify slashes, lowercase, and remove extension
return QString::fromUtf8(n);
}
//////////////////////////////////////////////////////////////////////////
QString CMaterialManager::MaterialToFilename(const QString& sMaterialName)
{
QString materialWithExtension = Path::ReplaceExtension(sMaterialName, MATERIAL_FILE_EXT);
QString fileName = Path::GamePathToFullPath(materialWithExtension);
const int mtlExtensionLength = strlen(MATERIAL_FILE_EXT);
if (fileName.right(mtlExtensionLength).toLower() != MATERIAL_FILE_EXT)
{
// we got something back which is not a mtl, fall back heuristic:
AZStd::string pathName(fileName.toUtf8().data());
AZStd::string fileNameOfMaterial;
AzFramework::StringFunc::Path::StripFullName(pathName); // remove the filename of the path to the FBX file so now it just contains the folder of the fbx file.
AzFramework::StringFunc::Path::GetFullFileName(materialWithExtension.toUtf8().data(), fileNameOfMaterial); // remove the path part of the material so it only contains the file name
AZStd::string finalName;
AzFramework::StringFunc::Path::Join(pathName.c_str(), fileNameOfMaterial.c_str(), finalName);
fileName = finalName.c_str();
}
return fileName;
}
//////////////////////////////////////////////////////////////////////////
const AZ::Data::AssetType& CMaterialManager::GetMaterialAssetType()
{
return m_materialAssetType;
}
//////////////////////////////////////////////////////////////////////////
void CMaterialManager::DeleteMaterial(CMaterial* pMtl)
{
assert(pMtl);
_smart_ptr<CMaterial> _ref(pMtl);
if (pMtl == GetCurrentMaterial())
{
SetCurrentMaterial(NULL);
}
DeleteItem(pMtl);
// Delete it from all sub materials.
for (int i = 0; i < m_pLevelLibrary->GetItemCount(); i++)
{
CMaterial* pMultiMtl = (CMaterial*)m_pLevelLibrary->GetItem(i);
if (pMultiMtl->IsMultiSubMaterial())
{
for (int slot = 0; slot < pMultiMtl->GetSubMaterialCount(); slot++)
{
if (pMultiMtl->GetSubMaterial(slot) == pMultiMtl)
{
// Clear this sub material slot.
pMultiMtl->SetSubMaterial(slot, 0);
}
}
}
}
}
//////////////////////////////////////////////////////////////////////////
void CMaterialManager::RemoveMaterialFromDisk(const char * fileName)
{
using namespace AzToolsFramework;
if (fileName)
{
SourceControlCommandBus::Broadcast(&SourceControlCommandBus::Events::RequestDelete, fileName,
[](bool success, const SourceControlFileInfo& info)
{
//If the file is not managed by source control, delete it locally
if (!success && !info.IsManaged())
{
QFile::remove(info.m_filePath.c_str());
}
}
);
}
}
//////////////////////////////////////////////////////////////////////////
void CMaterialManager::RegisterCommands(CRegistrationContext& regCtx)
{
CommandManagerHelper::RegisterCommand(regCtx.pCommandManager, "material", "duplicate", "", "", AZStd::bind(&CMaterialManager::Command_Duplicate, this));
CommandManagerHelper::RegisterCommand(regCtx.pCommandManager, "material", "merge", "", "", AZStd::bind(&CMaterialManager::Command_Merge, this));
CommandManagerHelper::RegisterCommand(regCtx.pCommandManager, "material", "delete", "", "", AZStd::bind(&CMaterialManager::Command_Delete, this));
CommandManagerHelper::RegisterCommand(regCtx.pCommandManager, "material", "assign_to_selection", "", "", AZStd::bind(&CMaterialManager::Command_AssignToSelection, this));
CommandManagerHelper::RegisterCommand(regCtx.pCommandManager, "material", "select_assigned_objects", "", "", AZStd::bind(&CMaterialManager::Command_SelectAssignedObjects, this));
CommandManagerHelper::RegisterCommand(regCtx.pCommandManager, "material", "select_from_object", "", "", AZStd::bind(&CMaterialManager::Command_SelectFromObject, this));
}
//////////////////////////////////////////////////////////////////////////
bool CMaterialManager::SelectSaveMaterial(QString& itemName, QString& fullSourcePath, const char* defaultStartPath)
{
QString startPath;
if (defaultStartPath && defaultStartPath[0] != '\0')
{
startPath = defaultStartPath;
}
else
{
startPath = GetIEditor()->GetSearchPath(EDITOR_PATH_MATERIALS);
}
if (!CFileUtil::SelectSaveFile("Material Files (*.mtl)", "mtl", startPath, fullSourcePath))
{
return false;
}
itemName = FilenameToMaterial(fullSourcePath);
if (itemName.isEmpty())
{
return false;
}
return true;
}
//////////////////////////////////////////////////////////////////////////
CMaterial* CMaterialManager::SelectNewMaterial(int nMtlFlags, [[maybe_unused]] const char* sStartPath)
{
QString path = m_pCurrentMaterial ? Path::GetPath(m_pCurrentMaterial->GetFilename()) : m_currentFolder;
QString itemName;
QString fullPath;
if (!SelectSaveMaterial(itemName, fullPath, path.toUtf8().data()))
{
return 0;
}
if (FindItemByName(itemName))
{
Warning("Material with name %s already exist", itemName.toUtf8().data());
return 0;
}
_smart_ptr<CMaterial> mtl = CreateMaterial(itemName, XmlNodeRef(), nMtlFlags);
mtl->Update();
bool skipReadOnly = true;
mtl->Save(skipReadOnly, fullPath);
SetCurrentMaterial(mtl);
return mtl;
}
//////////////////////////////////////////////////////////////////////////
void CMaterialManager::Command_Create()
{
SelectNewMaterial(0);
}
//////////////////////////////////////////////////////////////////////////
void CMaterialManager::Command_CreateMulti()
{
SelectNewMaterial(MTL_FLAG_MULTI_SUBMTL);
}
//////////////////////////////////////////////////////////////////////////
void CMaterialManager::Command_ConvertToMulti()
{
CMaterial* pMaterial = GetCurrentMaterial();
if (pMaterial && pMaterial->GetSubMaterialCount() == 0)
{
CMaterial* pSubMat = new CMaterial(*pMaterial);
pSubMat->SetName(pSubMat->GetShortName());
pSubMat->SetFlags(pSubMat->GetFlags() | MTL_FLAG_PURE_CHILD);
pMaterial->SetFlags(MTL_FLAG_MULTI_SUBMTL);
pMaterial->SetSubMaterialCount(1);
pMaterial->SetSubMaterial(0, pSubMat);
pMaterial->Save();
pMaterial->Reload();
SetSelectedItem(pSubMat);
}
else
{
Warning(pMaterial ? "azlmbr.legacy.material.convert_to_multi called on invalid material setup" : "azlmbr.legacy.material.convert_to_multi called while no material selected");
}
}
//////////////////////////////////////////////////////////////////////////
void CMaterialManager::Command_Duplicate()
{
CMaterial* pSrcMtl = GetCurrentMaterial();
if (!pSrcMtl)
{
CErrorRecord err;
err.error = "azlmbr.legacy.material.duplicate called while no materials selected";
GetIEditor()->GetErrorReport()->ReportError(err);
return;
}
if (GetIEditor()->IsSourceControlAvailable())
{
uint32 attrib = pSrcMtl->GetFileAttributes();
if ((attrib & SCC_FILE_ATTRIBUTE_INPAK) && (attrib & SCC_FILE_ATTRIBUTE_MANAGED) && !(attrib & SCC_FILE_ATTRIBUTE_NORMAL))
{
// Get latest for making folders with right case
CFileUtil::GetLatestFromSourceControl(pSrcMtl->GetFilename().toUtf8().data());
}
}
if (pSrcMtl != 0 && !pSrcMtl->IsPureChild())
{
QString newUniqueRelativePath = MakeUniqueItemName(pSrcMtl->GetName());
// Create a new material.
_smart_ptr<CMaterial> pMtl = DuplicateMaterial(newUniqueRelativePath.toUtf8().data(), pSrcMtl);
if (pMtl)
{
// Get the new filename from the relative path
AZStd::string newFileName;
AzFramework::StringFunc::Path::GetFileName(newUniqueRelativePath.toUtf8().data(), newFileName);
// Get the full path to the original material, so we know which folder to put the new material in
AZStd::string newFullFilePath = pSrcMtl->GetFilename().toUtf8().data();
// Replace the original material filename with the filename from the new relative path + the material file extension to get the new full file path
AzFramework::StringFunc::Path::ReplaceFullName(newFullFilePath, newFileName.c_str(), MATERIAL_FILE_EXT);
AzFramework::StringFunc::Path::Normalize(newFullFilePath);
const bool skipReadOnly = true;
pMtl->Save(skipReadOnly, newFullFilePath.c_str());
SetSelectedItem(pMtl);
}
}
}
//////////////////////////////////////////////////////////////////////////
CMaterial* CMaterialManager::DuplicateMaterial(const char* newName, CMaterial* pOriginal)
{
if (!newName)
{
assert(0 && "NULL newName passed into CMaterialManager::DuplicateMaterial");
return 0;
}
if (!pOriginal)
{
assert(0 && "NULL pOriginal passed into CMaterialManager::DuplicateMaterial");
return 0;
}
XmlNodeRef node = GetISystem()->CreateXmlNode("Material");
CBaseLibraryItem::SerializeContext ctx(node, false);
ctx.bCopyPaste = true;
pOriginal->Serialize(ctx);
return CreateMaterial(newName, node, pOriginal->GetFlags());
}
//////////////////////////////////////////////////////////////////////////
void CMaterialManager::GenerateUniqueSubmaterialName(const CMaterial* pSourceMaterial, const CMaterial* pTargetMaterial, QString& uniqueSubmaterialName) const
{
QString sourceMaterialName = pSourceMaterial->GetName();
// We don't need the whole path to the material, just the base name
QFileInfo filename(sourceMaterialName);
sourceMaterialName = filename.baseName();
uniqueSubmaterialName = sourceMaterialName;
size_t nameIndex = 0;
bool nameUpdated = true;
while (nameUpdated)
{
nameUpdated = false;
for (size_t k = 0; k < pTargetMaterial->GetSubMaterialCount(); ++k)
{
CMaterial* pSubMaterial = pTargetMaterial->GetSubMaterial(k);
if (pSubMaterial && pSubMaterial->GetName() == uniqueSubmaterialName)
{
++nameIndex;
uniqueSubmaterialName = QStringLiteral("%1%2").arg(sourceMaterialName).arg(nameIndex, 2, 10, QLatin1Char('0'));
nameUpdated = true;
break;
}
}
}
}
//////////////////////////////////////////////////////////////////////////
bool CMaterialManager::DuplicateAsSubMaterialAtIndex(CMaterial* pSourceMaterial, CMaterial* pTargetMaterial, int subMaterialIndex)
{
if (pSourceMaterial && pTargetMaterial && pTargetMaterial->GetSubMaterialCount() > subMaterialIndex)
{
// Resolve name collisions between the source material and the submaterials in the target material
QString newSubMaterialName;
GenerateUniqueSubmaterialName(pSourceMaterial, pTargetMaterial, newSubMaterialName);
// Mark the material to be duplicated as a PURE_CHILD since it is being duplicated as a submaterial
int sourceMaterialFlags = pSourceMaterial->GetFlags();
pSourceMaterial->SetFlags(sourceMaterialFlags | MTL_FLAG_PURE_CHILD);
CMaterial* pNewSubMaterial = DuplicateMaterial(newSubMaterialName.toUtf8().data(), pSourceMaterial);
pTargetMaterial->SetSubMaterial(subMaterialIndex, pNewSubMaterial);
// Reset the flags of the source material to their original values
pSourceMaterial->SetFlags(sourceMaterialFlags);
return true;
}
return false;
}
//////////////////////////////////////////////////////////////////////////
void CMaterialManager::Command_Merge()
{
QString itemName;
QString fullPath;
QString defaultMaterialPath;
if (m_pCurrentMaterial)
{
defaultMaterialPath = Path::GetPath(m_pCurrentMaterial->GetFilename());
}
if (!SelectSaveMaterial(itemName, fullPath, defaultMaterialPath.toUtf8().data()))
{
return;
}
_smart_ptr<CMaterial> pNewMaterial = CreateMaterial(itemName, XmlNodeRef(), MTL_FLAG_MULTI_SUBMTL);
size_t totalSubMaterialCount = 0;
for (_smart_ptr<CMaterial> pMaterial : m_markedMaterials)
{
if (pMaterial->IsMultiSubMaterial())
{
totalSubMaterialCount += pMaterial->GetSubMaterialCount();
}
else
{
totalSubMaterialCount++;
}
}
pNewMaterial->SetSubMaterialCount(totalSubMaterialCount);
size_t subMaterialIndex = 0;
for (_smart_ptr<CMaterial> pMaterial : m_markedMaterials)
{
if (pMaterial->IsMultiSubMaterial())
{
// Loop through each submaterial and duplicate it as a submaterial in the new material
for (size_t j = 0; j < pMaterial->GetSubMaterialCount(); ++j)
{
CMaterial* pSubMaterial = pMaterial->GetSubMaterial(j);
if (DuplicateAsSubMaterialAtIndex(pSubMaterial, pNewMaterial, subMaterialIndex))
{
++subMaterialIndex;
}
}
}
else
{
// Duplicate the material as a submaterial in the new material
if (DuplicateAsSubMaterialAtIndex(pMaterial, pNewMaterial, subMaterialIndex))
{
++subMaterialIndex;
}
}
}
pNewMaterial->Update();
const bool skipReadOnly = true;
pNewMaterial->Save(skipReadOnly, fullPath);
SetCurrentMaterial(pNewMaterial);
}
//////////////////////////////////////////////////////////////////////////
void CMaterialManager::Command_Delete()
{
CMaterial* pMtl = GetCurrentMaterial();
if (pMtl)
{
CUndo undo("Delete Material");
QString str = QObject::tr("Delete Material %1?\r\nNote: Material file %2 will also be deleted.")
.arg(pMtl->GetName(), pMtl->GetFilename());
if (QMessageBox::question(QApplication::activeWindow(), QObject::tr("Delete Confirmation"), str) == QMessageBox::Yes)
{
AZStd::string matName = pMtl->GetFilename().toUtf8().data();
DeleteMaterial(pMtl);
RemoveMaterialFromDisk(matName.c_str());
SetCurrentMaterial(0);
}
}
}
//////////////////////////////////////////////////////////////////////////
void CMaterialManager::Command_AssignToSelection()
{
CMaterial* pMtl = GetCurrentMaterial();
if (pMtl)
{
CUndo undo("Assign Material");
CSelectionGroup* pSel = GetIEditor()->GetSelection();
if (pMtl->IsPureChild())
{
const QString title = QObject::tr("Assign Submaterial");
const QString message = QObject::tr("You can assign submaterials to objects only for preview purpose. This assignment will not be saved with the level and will not be exported to the game.");
if (QMessageBox::information(QApplication::activeWindow(), title, message, QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel)
{
return;
}
}
if (!pSel->IsEmpty())
{
for (int i = 0; i < pSel->GetCount(); i++)
{
pSel->GetObject(i)->SetMaterial(pMtl);
}
}
}
CViewport* pViewport = GetIEditor()->GetActiveView();
if (pViewport)
{
pViewport->Drop(QPoint(-1, -1), pMtl);
}
}
//////////////////////////////////////////////////////////////////////////
void CMaterialManager::Command_ResetSelection()
{
CSelectionGroup* pSel = GetIEditor()->GetSelection();
if (!pSel->IsEmpty())
{
CUndo undo("Reset Material");
for (int i = 0; i < pSel->GetCount(); i++)
{
pSel->GetObject(i)->SetMaterial(0);
}
}
CViewport* pViewport = GetIEditor()->GetActiveView();
if (pViewport)
{
pViewport->Drop(QPoint(-1, -1), 0);
}
}
//////////////////////////////////////////////////////////////////////////
void CMaterialManager::Command_SelectAssignedObjects()
{
CMaterial* pMtl = GetCurrentMaterial();
if (pMtl)
{
CUndo undo("Select Object(s)");
CBaseObjectsArray objects;
GetIEditor()->GetObjectManager()->GetObjects(objects);
for (int i = 0; i < objects.size(); i++)
{
CBaseObject* pObject = objects[i];
if (pObject->GetMaterial() == pMtl || pObject->GetRenderMaterial() == pMtl)
{
if (pObject->IsHidden() || pObject->IsFrozen())
{
continue;
}
GetIEditor()->GetObjectManager()->SelectObject(pObject);
}
}
}
}
//////////////////////////////////////////////////////////////////////////
void CMaterialManager::Command_SelectFromObject()
{
if (GetIEditor()->IsInPreviewMode())
{
CViewport* pViewport = GetIEditor()->GetActiveView();
if (CModelViewport* p = viewport_cast<CModelViewport*>(pViewport))
{
CMaterial* pMtl = p->GetMaterial();
SetCurrentMaterial(pMtl);
}
return;
}
CSelectionGroup* pSel = GetIEditor()->GetSelection();
if (pSel->IsEmpty())
{
return;
}
for (int i = 0; i < pSel->GetCount(); i++)
{
CMaterial* pMtl = pSel->GetObject(i)->GetRenderMaterial();
if (pMtl)
{
SetCurrentMaterial(pMtl);
return;
}
}
}
//////////////////////////////////////////////////////////////////////////
void CMaterialManager::PickPreviewMaterial()
{
XmlNodeRef data = XmlHelpers::CreateXmlNode("ExportMaterial");
CMaterial* pMtl = GetCurrentMaterial();
if (!pMtl)
{
return;
}
if (pMtl->IsPureChild() && pMtl->GetParent())
{
pMtl = pMtl->GetParent();
}
if (pMtl->GetFlags() & MTL_FLAG_WIRE)
{
data->setAttr("Flag_Wire", 1);
}
if (pMtl->GetFlags() & MTL_FLAG_2SIDED)
{
data->setAttr("Flag_2Sided", 1);
}
data->setAttr("Name", pMtl->GetName().toUtf8().data());
data->setAttr("FileName", pMtl->GetFilename().toUtf8().data());
XmlNodeRef node = data->newChild("Material");
CBaseLibraryItem::SerializeContext serCtx(node, false);
pMtl->Serialize(serCtx);
if (!pMtl->IsMultiSubMaterial())
{
XmlNodeRef texturesNode = node->findChild("Textures");
if (texturesNode)
{
for (int i = 0; i < texturesNode->getChildCount(); i++)
{
XmlNodeRef texNode = texturesNode->getChild(i);
QString file;
if (texNode->getAttr("File", file))
{
texNode->setAttr("File", Path::GamePathToFullPath(file).toUtf8().data());
}
}
}
}
else
{
XmlNodeRef childsNode = node->findChild("SubMaterials");
if (childsNode)
{
int nSubMtls = childsNode->getChildCount();
for (int i = 0; i < nSubMtls; i++)
{
XmlNodeRef node2 = childsNode->getChild(i);
XmlNodeRef texturesNode = node2->findChild("Textures");
if (texturesNode)
{
for (int ii = 0; ii < texturesNode->getChildCount(); ii++)
{
XmlNodeRef texNode = texturesNode->getChild(ii);
QString file;
if (texNode->getAttr("File", file))
{
texNode->setAttr("File", Path::GamePathToFullPath(file).toUtf8().data());
}
}
}
}
}
}
m_MatSender->SendMessage(eMSM_GetSelectedMaterial, data);
}
//////////////////////////////////////////////////////////////////////////
void CMaterialManager::SyncMaterialEditor()
{
#if defined(AZ_PLATFORM_WINDOWS)
if (!m_MatSender)
{
return;
}
if (!m_MatSender->GetMessage())
{
return;
}
if (m_MatSender->m_h.msg == eMSM_Create)
{
XmlNodeRef node = m_MatSender->m_node->findChild("Material");
if (!node)
{
return;
}
QString sMtlName;
QString sMaxFile;
XmlNodeRef root = m_MatSender->m_node;
root->getAttr("Name", sMtlName);
root->getAttr("MaxFile", sMaxFile);
int IsMulti = 0;
root->getAttr("IsMulti", IsMulti);
int nMtlFlags = 0;
if (IsMulti)
{
nMtlFlags |= MTL_FLAG_MULTI_SUBMTL;
}
if (root->haveAttr("Flag_Wire"))
{
nMtlFlags |= MTL_FLAG_WIRE;
}
if (root->haveAttr("Flag_2Sided"))
{
nMtlFlags |= MTL_FLAG_2SIDED;
}
_smart_ptr<CMaterial> pMtl = SelectNewMaterial(nMtlFlags, Path::GetPath(sMaxFile).toUtf8().data());
if (!pMtl)
{
return;
}
if (!IsMulti)
{
node->delAttr("Shader"); // Remove shader attribute.
XmlNodeRef texturesNode = node->findChild("Textures");
if (texturesNode)
{
for (int i = 0; i < texturesNode->getChildCount(); i++)
{
XmlNodeRef texNode = texturesNode->getChild(i);
QString file;
if (texNode->getAttr("File", file))
{
//make path relative to the project specific game folder
QString newfile = Path::MakeGamePath(file);
if (!newfile.isEmpty())
{
file = newfile;
}
texNode->setAttr("File", file.toUtf8().data());
}
}
}
}
else
{
XmlNodeRef childsNode = node->findChild("SubMaterials");
if (childsNode)
{
int nSubMtls = childsNode->getChildCount();
for (int i = 0; i < nSubMtls; i++)
{
XmlNodeRef node2 = childsNode->getChild(i);
node2->delAttr("Shader"); // Remove shader attribute.
XmlNodeRef texturesNode = node2->findChild("Textures");
if (texturesNode)
{
for (int ii = 0; ii < texturesNode->getChildCount(); ii++)
{
XmlNodeRef texNode = texturesNode->getChild(ii);
QString file;
if (texNode->getAttr("File", file))
{
//make path relative to the project specific game folder
QString newfile = Path::MakeGamePath(file);
if (!newfile.isEmpty())
{
file = newfile;
}
texNode->setAttr("File", file.toUtf8().data());
}
}
}
}
}
}
CBaseLibraryItem::SerializeContext ctx(node, true);
ctx.bUndo = true;
pMtl->Serialize(ctx);
pMtl->Update();
SetCurrentMaterial(0);
SetCurrentMaterial(pMtl);
}
if (m_MatSender->m_h.msg == eMSM_GetSelectedMaterial)
{
PickPreviewMaterial();
}
#endif
}
//////////////////////////////////////////////////////////////////////////
void CMaterialManager::InitMatSender()
{
//MatSend(true);
m_MatSender->Create();
QWidget* mainWindow = MainWindow::instance();
m_MatSender->SetupWindows(mainWindow, mainWindow);
XmlNodeRef node = XmlHelpers::CreateXmlNode("Temp");
m_MatSender->SendMessage(eMSM_Init, node);
}
//////////////////////////////////////////////////////////////////////////
void CMaterialManager::GotoMaterial([[maybe_unused]] CMaterial* pMaterial)
{
}
//////////////////////////////////////////////////////////////////////////
void CMaterialManager::GotoMaterial([[maybe_unused]] _smart_ptr<IMaterial> pMtl)
{
}
//////////////////////////////////////////////////////////////////////////
void CMaterialManager::SetHighlightedMaterial(CMaterial* pMtl)
{
if (m_pHighlightMaterial)
{
RemoveFromHighlighting(m_pHighlightMaterial, eHighlight_Pick);
}
m_pHighlightMaterial = pMtl;
if (m_pHighlightMaterial)
{
AddForHighlighting(m_pHighlightMaterial);
}
}
//////////////////////////////////////////////////////////////////////////
void CMaterialManager::HighlightedMaterialChanged(CMaterial* pMtl)
{
if (!pMtl)
{
return;
}
RemoveFromHighlighting(pMtl, eHighlight_All);
AddForHighlighting(pMtl);
}
//////////////////////////////////////////////////////////////////////////
void CMaterialManager::SetHighlightMask(int highlightMask)
{
if (m_highlightMask != highlightMask)
{
m_highlightMask = highlightMask;
UpdateHighlightedMaterials();
}
}
//////////////////////////////////////////////////////////////////////////
void CMaterialManager::GatherResources(_smart_ptr<IMaterial> pMaterial, CUsedResources& resources)
{
if (!pMaterial)
{
return;
}
int nSubMtlCount = pMaterial->GetSubMtlCount();
if (nSubMtlCount > 0)
{
for (int i = 0; i < nSubMtlCount; i++)
{
GatherResources(pMaterial->GetSubMtl(i), resources);
}
}
else
{
SShaderItem& shItem = pMaterial->GetShaderItem();
if (shItem.m_pShaderResources)
{
SInputShaderResources res;
shItem.m_pShaderResources->ConvertToInputResource(&res);
for (auto& iter : res.m_TexturesResourcesMap )
{
SEfResTexture* pTexture = &(iter.second);
if (!pTexture->m_Name.empty())
{
resources.Add(pTexture->m_Name.c_str());
}
}
gEnv->pRenderer->EF_ReleaseInputShaderResource(&res);
}
}
}
///////////////////////////////////////////////////////////////////////////
void CMaterialManager::GetHighlightColor(ColorF* color, float* intensity, int flags)
{
MAKE_SURE(m_pHighlighter, return );
m_pHighlighter->GetHighlightColor(color, intensity, flags);
}
///////////////////////////////////////////////////////////////////////////
// This will be called when the editor welcome screen is displayed.
// At this point the editor is ready for UI events, which means we can
// process .dccmtl paths and display error to the user if necessary
bool CMaterialManager::SkipEditorStartupUI()
{
// Editor started
m_bEditorUiReady = true;
// If we have any file paths buffered
if (m_sourceControlBuffer.size() > 0)
{
// Start queuing
QueueSourceControlTick();
}
// Launch thread responsible for saving cached
// .dccmtl files as source .mtl files
StartDccMaterialSaveThread();
// Never want to skip Startup UI
return false;
}
///////////////////////////////////////////////////////////////////////////
// Queues the function TickSourceControl() to be exectued next frame
void CMaterialManager::QueueSourceControlTick()
{
// If TickSourceControl is not currently queued
if (!m_sourceControlFunctionQueued)
{
// Queue it
AZStd::function<void()> tickFunction = [this]()
{
TickSourceControl();
};
AZ::SystemTickBus::QueueFunction(tickFunction);
// Stop further queues as TickSourceControl will queue itself
// until there are no more paths in the buffer to process
m_sourceControlFunctionQueued = true;
}
}
///////////////////////////////////////////////////////////////////////////
// Takes a single path from m_sourceControlBuffer and passes it to
// DccMaterialSourceControlCheck(). Then if there are more paths
// remaining in the buffer, it will queue itself for execution next
// frame. The reason for doing only one material every tick is to avoid
// flooding source control with too many requests and stalling the editor
void CMaterialManager::TickSourceControl()
{
m_sourceControlFunctionQueued = false;
AZStd::string filePath;
bool moreRemaining = false;
{
AZStd::lock_guard<AZStd::mutex> lock(m_sourceControlBufferMutex);
if (m_sourceControlBuffer.size() < 1)
{
return;
}
filePath = m_sourceControlBuffer.back();
m_sourceControlBuffer.pop_back();
moreRemaining = !m_sourceControlBuffer.empty();
}
// Process it
DccMaterialSourceControlCheck(filePath);
// If there are more paths to check
if (moreRemaining)
{
// Queue again
QueueSourceControlTick();
}
}
///////////////////////////////////////////////////////////////////////////
// Launches new thread running the DccMaterialSaveThreadFunc() function
void CMaterialManager::StartDccMaterialSaveThread()
{
AZStd::thread_desc threadDesc;
threadDesc.m_name = "Dcc Material Save Thread";
m_dccMaterialSaveThread = AZStd::thread(
[this]()
{
DccMaterialSaveThreadFunc();
},
&threadDesc);
}
///////////////////////////////////////////////////////////////////////////
// Will save all the .dccmtl file paths in the buffer to source .mtl
// Runs on a separate thread so as not to stall the main thread
void CMaterialManager::DccMaterialSaveThreadFunc()
{
while (true)
{
m_dccMaterialSaveSemaphore.acquire();
// Exit condition, set to true in destructor
if (m_joinThreads)
{
return;
}
AZStd::vector<AZStd::string> dccMaterialPaths;
// Lock the buffer and copy file paths locally
{
AZStd::lock_guard<AZStd::mutex> lock(m_dccMaterialSaveMutex);
dccMaterialPaths.reserve(m_dccMaterialSaveBuffer.size());
for (AZStd::string& fileName : m_dccMaterialSaveBuffer)
{
dccMaterialPaths.push_back(fileName);
}
m_dccMaterialSaveBuffer.clear();
}
// Save all the buffered .dccmtl files
for (AZStd::string& fileName : dccMaterialPaths)
{
SaveDccMaterial(fileName);
}
// Clear local strings
dccMaterialPaths.clear();
}
}
///////////////////////////////////////////////////////////////////////////
// Async source control request. If successful, the callback will add the
// file name to the buffer for processing by the Dcc Material Save Thread
void CMaterialManager::DccMaterialSourceControlCheck(const AZStd::string& relativeDccMaterialPath)
{
AZStd::string fullSourcePath = DccMaterialToSourcePath(relativeDccMaterialPath);
if (!DccMaterialRequiresSave(relativeDccMaterialPath, fullSourcePath))
{
// Source .mtl update not required, early out
return;
}
// Create callback for source control operation (see SCCommandBus::Broadcast below)
AzToolsFramework::SourceControlResponseCallback callback =
[this, relativeDccMaterialPath, fullSourcePath](bool success, const AzToolsFramework::SourceControlFileInfo& info)
{
if (success || !info.IsReadOnly())
{
// File needs saving, add it to the buffer for processing by the dcc material thread
// Lock access to the buffer
AZStd::lock_guard<AZStd::mutex> lock(m_dccMaterialSaveMutex);
// Add file path
m_dccMaterialSaveBuffer.push_back(relativeDccMaterialPath);
// Notify thread there's work to do
m_dccMaterialSaveSemaphore.release();
}
else
{
QString errorMessage = QObject::tr("Could not check out read-only file %1 in source control. Either check your source control configuration or disable source control.").arg(QString::fromUtf8(fullSourcePath.c_str()));
// Alter error message slightly if source control is disabled
bool isSourceControlActive = false;
AzToolsFramework::SourceControlConnectionRequestBus::BroadcastResult(isSourceControlActive, &AzToolsFramework::SourceControlConnectionRequestBus::Events::IsActive);
if (!isSourceControlActive)
{
errorMessage = QObject::tr("Could not check out read-only file %1 because source control is disabled. Either enable source control or check out the file manually to make it writable.").arg(QString::fromUtf8(fullSourcePath.c_str()));
}
// Pop open an error message box if this is the first error we encounter
if (!m_bSourceControlErrorReported)
{
// Report warning in message box
QString errorTitle = QStringLiteral("Dcc Material Error");
QMessageBox::warning(QApplication::activeWindow(), errorTitle, errorMessage, QMessageBox::Cancel);
// Only report source control error box to the user once,
// no need to spam them for every material
m_bSourceControlErrorReported = true;
}
AZ_Error("Rendering", false, errorMessage.toUtf8().data());
}
};
// Request edit from source control (happens asynchronously)
using SCCommandBus = AzToolsFramework::SourceControlCommandBus;
SCCommandBus::Broadcast(&SCCommandBus::Events::RequestEdit, fullSourcePath.c_str(), true, callback);
}
///////////////////////////////////////////////////////////////////////////
// Handles when .dccmtl is created
void CMaterialManager::EntryAdded(const AzToolsFramework::AssetBrowser::AssetBrowserEntry* assetEntry)
{
if (assetEntry->GetEntryType() != AzToolsFramework::AssetBrowser::AssetBrowserEntry::AssetEntryType::Product)
{
// Ignore non-product entries
return;
}
const AzToolsFramework::AssetBrowser::ProductAssetBrowserEntry* productAssetEntry = azrtti_cast<const AzToolsFramework::AssetBrowser::ProductAssetBrowserEntry*>(assetEntry);
if (productAssetEntry && productAssetEntry->GetAssetType() != m_dccMaterialAssetType)
{
// Ignore types that aren't .dccmtl
return;
}
AddDccMaterialPath(productAssetEntry->GetRelativePath());
}
///////////////////////////////////////////////////////////////////////////
// Handles when .dccmtl is edited
void CMaterialManager::OnCatalogAssetChanged(const AZ::Data::AssetId& assetId)
{
AZ::Data::AssetInfo assetInfo;
EBUS_EVENT_RESULT(assetInfo, AZ::Data::AssetCatalogRequestBus, GetAssetInfoById, assetId);
if (assetInfo.m_assetType != m_dccMaterialAssetType)
{
// Ignore types that aren't .dccmtl
return;
}
AddDccMaterialPath(assetInfo.m_relativePath);
}
///////////////////////////////////////////////////////////////////////////
void CMaterialManager::AddDccMaterialPath(const AZStd::string relativeDccMaterialPath)
{
if (relativeDccMaterialPath.empty())
{
return;
}
// Lock access to the buffer
AZStd::lock_guard<AZStd::mutex> lock(m_sourceControlBufferMutex);
// Add file path
m_sourceControlBuffer.push_back(relativeDccMaterialPath);
if (m_bEditorUiReady)
{
QueueSourceControlTick();
}
}
///////////////////////////////////////////////////////////////////////////
// Given the path of a .dccmtl in cache, save it as a source .mtl
void CMaterialManager::SaveDccMaterial(const AZStd::string& relativeDccMaterialPath)
{
// __________________________________________________
// Load .dccmtl
XmlNodeRef dccNode = GetISystem()->LoadXmlFromFile(relativeDccMaterialPath.c_str());
if (!dccNode)
{
AZ_Error("MaterialManager", false, "CMaterialManager::SaveDccMaterial: Failed to load XML node from .dccmtl file: %s", relativeDccMaterialPath.c_str());
return;
}
// __________________________________________________
// Save as source .mtl file
AZStd::string fullSourcePath = DccMaterialToSourcePath(relativeDccMaterialPath);
bool saveSuccessful = dccNode->saveToFile(fullSourcePath.c_str());
if (!saveSuccessful)
{
AZ_Error("MaterialManager", false, "CMaterialManager::SaveDccMaterial: Failed to save source .mtl from .dccmtl file: %s", relativeDccMaterialPath.c_str());
}
}
///////////////////////////////////////////////////////////////////////////
// Compares the hash values from .dccmtl and source .mtl to determine if
// .dccmtl has changed and needs to be saved.
bool CMaterialManager::DccMaterialRequiresSave(const AZStd::string& relativeDccMaterialPath, const AZStd::string& fullSourcePath)
{
// __________________________________________________
// Get Source Hash
AZ::u32 sourceHash = 0;
// Check if material is already loaded
QString unifiedName = UnifyMaterialName(QString(relativeDccMaterialPath.c_str()));
CMaterial* sourceMaterial = (CMaterial*)FindItemByName(unifiedName);
if (sourceMaterial && !sourceMaterial->IsDummy())
{
sourceHash = sourceMaterial->GetDccMaterialHash();
}
else
{
XmlNodeRef sourceNode = GetISystem()->LoadXmlFromFile(fullSourcePath.c_str());
if (sourceNode)
{
sourceNode->getAttr("DccMaterialHash", sourceHash);
}
else
{
// Couldn't find source node or material, so we need to save the dcc material as a source material
// No need to check the dcc material hash, just return true
return true;
}
}
// __________________________________________________
// Get DCC material Hash
AZ::u32 dccHash = 0;
XmlNodeRef dccNode = GetISystem()->LoadXmlFromFile(relativeDccMaterialPath.c_str());
if (!dccNode)
{
AZ_Error("MaterialManager", false, "CMaterialManager::DccMaterialRequiresSave: Failed to load XML node from .dccmtl file: %s", relativeDccMaterialPath.c_str());
return false;
}
dccNode->getAttr("DccMaterialHash", dccHash);
// __________________________________________________
// Compare hash values
// Only update if .dccmtl hash is different from the source hash
return (dccHash != sourceHash);
}