Merge branch 'development' into cmake/SPEC-7484
Signed-off-by: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com> # Conflicts: # Code/Editor/ToolBox.cpp
This commit is contained in:
+9
@@ -21,6 +21,8 @@ namespace AZ
|
||||
public:
|
||||
//! Get all material assignments that can be overridden
|
||||
virtual MaterialAssignmentMap GetOriginalMaterialAssignments() const = 0;
|
||||
//! Get material assignment id matching lod and label substring
|
||||
virtual MaterialAssignmentId FindMaterialAssignmentId(const MaterialAssignmentLodIndex lod, const AZStd::string& label) const = 0;
|
||||
//! Set material overrides
|
||||
virtual void SetMaterialOverrides(const MaterialAssignmentMap& materials) = 0;
|
||||
//! Get material overrides
|
||||
@@ -69,6 +71,13 @@ namespace AZ
|
||||
: public ComponentBus
|
||||
{
|
||||
public:
|
||||
//! Get material assignment id matching lod and label substring
|
||||
virtual MaterialAssignmentId FindMaterialAssignmentId(
|
||||
const MaterialAssignmentLodIndex lod, const AZStd::string& label) const = 0;
|
||||
|
||||
//! Returns the list of all ModelMaterialSlot's for the model, across all LODs.
|
||||
virtual RPI::ModelMaterialSlotMap GetModelMaterialSlots() const = 0;
|
||||
|
||||
virtual MaterialAssignmentMap GetMaterialAssignments() const = 0;
|
||||
virtual AZStd::unordered_set<AZ::Name> GetModelUvNames() const = 0;
|
||||
};
|
||||
|
||||
+54
-70
@@ -17,6 +17,7 @@
|
||||
#include <Atom/RPI.Public/Image/StreamingImage.h>
|
||||
#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
|
||||
#include <Atom/RPI.Reflect/Material/MaterialTypeAsset.h>
|
||||
#include <AtomLyIntegration/CommonFeatures/Mesh/MeshComponentBus.h>
|
||||
|
||||
AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT
|
||||
#include <QMenu>
|
||||
@@ -44,64 +45,15 @@ namespace AZ
|
||||
|
||||
if (classElement.GetVersion() < 3)
|
||||
{
|
||||
// The default material was changed from an asset to an EditorMaterialComponentSlot and old data must be converted
|
||||
constexpr AZ::u32 defaultMaterialAssetDataCrc = AZ_CRC("defaultMaterialAsset", 0x736fc071);
|
||||
|
||||
Data::Asset<RPI::MaterialAsset> oldDefaultMaterialData;
|
||||
if (!classElement.GetChildData(defaultMaterialAssetDataCrc, oldDefaultMaterialData))
|
||||
{
|
||||
AZ_Error("AZ::Render::EditorMaterialComponent::ConvertVersion", false, "Failed to get defaultMaterialAsset element");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!classElement.RemoveElementByName(defaultMaterialAssetDataCrc))
|
||||
{
|
||||
AZ_Error("AZ::Render::EditorMaterialComponent::ConvertVersion", false, "Failed to remove defaultMaterialAsset element");
|
||||
return false;
|
||||
}
|
||||
|
||||
EditorMaterialComponentSlot newDefaultMaterialData;
|
||||
newDefaultMaterialData.m_id = DefaultMaterialAssignmentId;
|
||||
newDefaultMaterialData.m_materialAsset = oldDefaultMaterialData;
|
||||
classElement.AddElementWithData(context, "defaultMaterialSlot", newDefaultMaterialData);
|
||||
|
||||
// Slots now support and display the default material asset when empty
|
||||
// The old placeholder assignments are irrelevant and must be cleared
|
||||
constexpr AZ::u32 materialSlotsByLodDataCrc = AZ_CRC("materialSlotsByLod", 0xb1498db6);
|
||||
|
||||
EditorMaterialComponentSlotsByLodContainer lodSlotData;
|
||||
if (!classElement.GetChildData(materialSlotsByLodDataCrc, lodSlotData))
|
||||
{
|
||||
AZ_Error("AZ::Render::EditorMaterialComponent::ConvertVersion", false, "Failed to get materialSlotsByLod element");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!classElement.RemoveElementByName(materialSlotsByLodDataCrc))
|
||||
{
|
||||
AZ_Error("AZ::Render::EditorMaterialComponent::ConvertVersion", false, "Failed to remove materialSlotsByLod element");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find and clear all slots that are assigned to the slot's default value
|
||||
for (auto& lodSlots : lodSlotData)
|
||||
{
|
||||
for (auto& slot : lodSlots)
|
||||
{
|
||||
if (slot.m_materialAsset.GetId() == slot.m_id.m_materialAssetId)
|
||||
{
|
||||
slot.m_materialAsset = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
classElement.AddElementWithData(context, "materialSlotsByLod", lodSlotData);
|
||||
AZ_Error("EditorMaterialComponent", false, "Material Component version < 3 is no longer supported");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (classElement.GetVersion() < 4)
|
||||
{
|
||||
classElement.AddElementWithData(context, "materialSlotsByLodEnabled", true);
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -238,7 +190,7 @@ namespace AZ
|
||||
for (auto& materialSlotPair : GetMaterialSlots())
|
||||
{
|
||||
EditorMaterialComponentSlot* materialSlot = materialSlotPair.second;
|
||||
if (materialSlot->m_id.IsAssetOnly())
|
||||
if (materialSlot->m_id.IsSlotIdOnly())
|
||||
{
|
||||
materialSlot->Clear();
|
||||
}
|
||||
@@ -251,7 +203,7 @@ namespace AZ
|
||||
for (auto& materialSlotPair : GetMaterialSlots())
|
||||
{
|
||||
EditorMaterialComponentSlot* materialSlot = materialSlotPair.second;
|
||||
if (materialSlot->m_id.IsLodAndAsset())
|
||||
if (materialSlot->m_id.IsLodAndSlotId())
|
||||
{
|
||||
materialSlot->Clear();
|
||||
}
|
||||
@@ -318,7 +270,7 @@ namespace AZ
|
||||
// Build the controller configuration from the editor configuration
|
||||
MaterialComponentConfig config = m_controller.GetConfiguration();
|
||||
config.m_materials.clear();
|
||||
|
||||
|
||||
for (const auto& materialSlotPair : GetMaterialSlots())
|
||||
{
|
||||
const EditorMaterialComponentSlot* materialSlot = materialSlotPair.second;
|
||||
@@ -341,7 +293,7 @@ namespace AZ
|
||||
else if (!materialSlot->m_propertyOverrides.empty() || !materialSlot->m_matModUvOverrides.empty())
|
||||
{
|
||||
MaterialAssignment& materialAssignment = config.m_materials[materialSlot->m_id];
|
||||
materialAssignment.m_materialAsset.Create(materialSlot->m_id.m_materialAssetId);
|
||||
materialAssignment.m_materialAsset = materialSlot->m_defaultMaterialAsset;
|
||||
materialAssignment.m_propertyOverrides = materialSlot->m_propertyOverrides;
|
||||
materialAssignment.m_matModUvOverrides = materialSlot->m_matModUvOverrides;
|
||||
}
|
||||
@@ -362,6 +314,9 @@ namespace AZ
|
||||
// Get the known material assignment slots from the associated model or other source
|
||||
MaterialAssignmentMap materialsFromSource;
|
||||
MaterialReceiverRequestBus::EventResult(materialsFromSource, GetEntityId(), &MaterialReceiverRequestBus::Events::GetMaterialAssignments);
|
||||
|
||||
RPI::ModelMaterialSlotMap modelMaterialSlots;
|
||||
MaterialReceiverRequestBus::EventResult(modelMaterialSlots, GetEntityId(), &MaterialReceiverRequestBus::Events::GetModelMaterialSlots);
|
||||
|
||||
// Generate the table of editable materials using the source data to define number of groups, elements, and initial values
|
||||
for (const auto& materialPair : materialsFromSource)
|
||||
@@ -385,9 +340,33 @@ namespace AZ
|
||||
OnConfigurationChanged();
|
||||
};
|
||||
|
||||
const char* UnknownSlotName = "<unknown>";
|
||||
|
||||
// If this is the default material assignment ID then it represents the default slot which is not contained in any other group
|
||||
if (slot.m_id == DefaultMaterialAssignmentId)
|
||||
{
|
||||
slot.m_label = "Default Material";
|
||||
}
|
||||
else
|
||||
{
|
||||
auto slotIter = modelMaterialSlots.find(slot.m_id.m_materialSlotStableId);
|
||||
if (slotIter != modelMaterialSlots.end())
|
||||
{
|
||||
const Name& displayName = slotIter->second.m_displayName;
|
||||
slot.m_label = !displayName.IsEmpty() ? displayName.GetStringView() : UnknownSlotName;
|
||||
|
||||
slot.m_defaultMaterialAsset = slotIter->second.m_defaultMaterialAsset;
|
||||
}
|
||||
else
|
||||
{
|
||||
slot.m_label = UnknownSlotName;
|
||||
}
|
||||
}
|
||||
|
||||
// if material is present in controller configuration, assign its data
|
||||
const MaterialAssignment& materialFromController = GetMaterialAssignmentFromMap(config.m_materials, slot.m_id);
|
||||
slot.m_materialAsset = materialFromController.m_materialAsset;
|
||||
|
||||
slot.m_propertyOverrides = materialFromController.m_propertyOverrides;
|
||||
slot.m_matModUvOverrides = materialFromController.m_matModUvOverrides;
|
||||
|
||||
@@ -400,13 +379,13 @@ namespace AZ
|
||||
continue;
|
||||
}
|
||||
|
||||
if (slot.m_id.IsAssetOnly())
|
||||
if (slot.m_id.IsSlotIdOnly())
|
||||
{
|
||||
m_materialSlots.push_back(slot);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (slot.m_id.IsLodAndAsset())
|
||||
if (slot.m_id.IsLodAndSlotId())
|
||||
{
|
||||
// Resize the containers to fit all elements
|
||||
m_materialSlotsByLod.resize(AZ::GetMax<size_t>(m_materialSlotsByLod.size(), aznumeric_cast<size_t>(slot.m_id.m_lodIndex + 1)));
|
||||
@@ -452,27 +431,26 @@ namespace AZ
|
||||
{
|
||||
AzToolsFramework::ScopedUndoBatch undoBatch("Generating materials.");
|
||||
SetDirty();
|
||||
|
||||
|
||||
// First generating a unique set of all material asset IDs that will be used for source data generation
|
||||
AZStd::unordered_set<AZ::Data::AssetId> assetIds;
|
||||
AZStd::unordered_map<AZ::Data::AssetId, AZStd::string /*slot name*/> assetIdMap;
|
||||
|
||||
auto materialSlots = GetMaterialSlots();
|
||||
for (auto& materialSlotPair : materialSlots)
|
||||
{
|
||||
EditorMaterialComponentSlot* materialSlot = materialSlotPair.second;
|
||||
if (materialSlot->m_id.m_materialAssetId.IsValid())
|
||||
Data::AssetId defaultMaterialAssetId = materialSlotPair.second->m_defaultMaterialAsset.GetId();
|
||||
if (defaultMaterialAssetId.IsValid())
|
||||
{
|
||||
assetIds.insert(materialSlot->m_id.m_materialAssetId);
|
||||
assetIdMap[defaultMaterialAssetId] = materialSlotPair.second->GetLabel();
|
||||
}
|
||||
}
|
||||
|
||||
// Convert the unique set of asset IDs into export items that can be configured in the dialog
|
||||
// The order should not matter because the table in the dialog can sort itself for a specific row
|
||||
EditorMaterialComponentExporter::ExportItemsContainer exportItems;
|
||||
for (const AZ::Data::AssetId& assetId : assetIds)
|
||||
for (auto assetIdInfo : assetIdMap)
|
||||
{
|
||||
EditorMaterialComponentExporter::ExportItem exportItem;
|
||||
exportItem.m_assetId = assetId;
|
||||
EditorMaterialComponentExporter::ExportItem exportItem{assetIdInfo.first, assetIdInfo.second};
|
||||
exportItems.push_back(exportItem);
|
||||
}
|
||||
|
||||
@@ -486,15 +464,20 @@ namespace AZ
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& assetIdOutcome = AZ::RPI::AssetUtils::MakeAssetId(exportItem.m_exportPath, 0);
|
||||
const auto& assetIdOutcome = AZ::RPI::AssetUtils::MakeAssetId(exportItem.GetExportPath(), 0);
|
||||
if (assetIdOutcome)
|
||||
{
|
||||
for (auto& materialSlotPair : materialSlots)
|
||||
{
|
||||
EditorMaterialComponentSlot* materialSlot = materialSlotPair.second;
|
||||
if (materialSlot && materialSlot->m_id.m_materialAssetId == exportItem.m_assetId)
|
||||
EditorMaterialComponentSlot* editorMaterialSlot = materialSlotPair.second;
|
||||
|
||||
if (editorMaterialSlot)
|
||||
{
|
||||
materialSlot->m_materialAsset.Create(assetIdOutcome.GetValue());
|
||||
// We need to check whether replaced material corresponds to this slot's default material.
|
||||
if (editorMaterialSlot->m_defaultMaterialAsset.GetId() == exportItem.GetOriginalAssetId())
|
||||
{
|
||||
editorMaterialSlot->m_materialAsset.Create(assetIdOutcome.GetValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -582,3 +565,4 @@ namespace AZ
|
||||
}
|
||||
} // namespace Render
|
||||
} // namespace AZ
|
||||
|
||||
|
||||
+25
-65
@@ -37,47 +37,7 @@ namespace AZ
|
||||
{
|
||||
namespace EditorMaterialComponentExporter
|
||||
{
|
||||
AZStd::string GetLabelByAssetId(const AZ::Data::AssetId& assetId)
|
||||
{
|
||||
AZStd::string label;
|
||||
if (assetId.IsValid())
|
||||
{
|
||||
// Material assets that are exported through the scene pipeline have their filenames generated by adding
|
||||
// the DCC material name as a prefix and a unique number to the end of the source file name.
|
||||
// Rather than storing the DCC material name inside of the material asset we can reproduce it by removing
|
||||
// the prefix and suffix from the product file name.
|
||||
|
||||
// We need the material product path as the initial string that will be stripped down
|
||||
const AZStd::string& productPath = AZ::RPI::AssetUtils::GetProductPathByAssetId(assetId);
|
||||
if (!productPath.empty() && AzFramework::StringFunc::Path::GetFileName(productPath.c_str(), label))
|
||||
{
|
||||
// If there is a source file, typically an FBX or other model file, we must get its filename to remove the prefix from the label
|
||||
AZStd::string prefix;
|
||||
const AZStd::string& sourcePath = AZ::RPI::AssetUtils::GetSourcePathByAssetId(assetId);
|
||||
if (!sourcePath.empty() && AZ::StringFunc::Path::GetFileName(sourcePath.c_str(), prefix))
|
||||
{
|
||||
if (!prefix.empty() && prefix.size() < label.size())
|
||||
{
|
||||
if (AZ::StringFunc::StartsWith(label, prefix, false))
|
||||
{
|
||||
// All of the product filename's tokens are separated by underscores so we must also remove the first underscore after the prefix
|
||||
label = label.substr(prefix.size() + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We can remove the numeric suffix by stripping the label of everything after the last underscore
|
||||
const auto iter = label.find_last_of("_");
|
||||
if (iter != AZStd::string::npos)
|
||||
{
|
||||
label = label.substr(0, iter);
|
||||
}
|
||||
}
|
||||
}
|
||||
return label;
|
||||
}
|
||||
|
||||
AZStd::string GetExportPathByAssetId(const AZ::Data::AssetId& assetId)
|
||||
AZStd::string GetExportPathByAssetId(const AZ::Data::AssetId& assetId, const AZStd::string& materialSlotName)
|
||||
{
|
||||
AZStd::string exportPath;
|
||||
if (assetId.IsValid())
|
||||
@@ -85,7 +45,7 @@ namespace AZ
|
||||
exportPath = AZ::RPI::AssetUtils::GetSourcePathByAssetId(assetId);
|
||||
AZ::StringFunc::Path::StripExtension(exportPath);
|
||||
exportPath += "_";
|
||||
exportPath += GetLabelByAssetId(assetId);
|
||||
exportPath += materialSlotName;
|
||||
exportPath += ".";
|
||||
exportPath += AZ::RPI::MaterialSourceData::Extension;
|
||||
AZ::StringFunc::Path::Normalize(exportPath);
|
||||
@@ -132,12 +92,12 @@ namespace AZ
|
||||
int row = 0;
|
||||
for (ExportItem& exportItem : exportItems)
|
||||
{
|
||||
QFileInfo fileInfo(GetExportPathByAssetId(exportItem.m_assetId).c_str());
|
||||
QFileInfo fileInfo(GetExportPathByAssetId(exportItem.GetOriginalAssetId(), exportItem.GetMaterialSlotName()).c_str());
|
||||
|
||||
// Configuring initial settings based on whether or not the target file already exists
|
||||
exportItem.m_exportPath = fileInfo.absoluteFilePath().toUtf8().constData();
|
||||
exportItem.m_exists = fileInfo.exists();
|
||||
exportItem.m_overwrite = false;
|
||||
exportItem.SetExportPath(fileInfo.absoluteFilePath().toUtf8().constData());
|
||||
exportItem.SetExists(fileInfo.exists());
|
||||
exportItem.SetOverwrite(false);
|
||||
|
||||
// Populate the table with data for every column
|
||||
tableWidget->setItem(row, MaterialSlotColumn, new QTableWidgetItem());
|
||||
@@ -146,23 +106,23 @@ namespace AZ
|
||||
|
||||
// Create a check box for toggling the enabled state of this item
|
||||
QCheckBox* materialSlotCheckBox = new QCheckBox(tableWidget);
|
||||
materialSlotCheckBox->setChecked(exportItem.m_enabled);
|
||||
materialSlotCheckBox->setText(GetLabelByAssetId(exportItem.m_assetId).c_str());
|
||||
materialSlotCheckBox->setChecked(exportItem.GetEnabled());
|
||||
materialSlotCheckBox->setText(exportItem.GetMaterialSlotName().c_str());
|
||||
tableWidget->setCellWidget(row, MaterialSlotColumn, materialSlotCheckBox);
|
||||
|
||||
// Create a file picker widget for selecting the save path for the exported material
|
||||
AzQtComponents::BrowseEdit* materialFileWidget = new AzQtComponents::BrowseEdit(tableWidget);
|
||||
materialFileWidget->setLineEditReadOnly(true);
|
||||
materialFileWidget->setClearButtonEnabled(false);
|
||||
materialFileWidget->setEnabled(exportItem.m_enabled);
|
||||
materialFileWidget->setEnabled(exportItem.GetEnabled());
|
||||
materialFileWidget->setText(fileInfo.fileName());
|
||||
tableWidget->setCellWidget(row, MaterialFileColumn, materialFileWidget);
|
||||
|
||||
// Create a check box for toggling the overwrite state of this item
|
||||
QWidget* overwriteCheckBoxContainer = new QWidget(tableWidget);
|
||||
QCheckBox* overwriteCheckBox = new QCheckBox(overwriteCheckBoxContainer);
|
||||
overwriteCheckBox->setChecked(exportItem.m_overwrite);
|
||||
overwriteCheckBox->setEnabled(exportItem.m_enabled && exportItem.m_exists);
|
||||
overwriteCheckBox->setChecked(exportItem.GetOverwrite());
|
||||
overwriteCheckBox->setEnabled(exportItem.GetEnabled() && exportItem.GetExists());
|
||||
|
||||
overwriteCheckBoxContainer->setLayout(new QHBoxLayout(overwriteCheckBoxContainer));
|
||||
overwriteCheckBoxContainer->layout()->addWidget(overwriteCheckBox);
|
||||
@@ -173,21 +133,21 @@ namespace AZ
|
||||
|
||||
// Whenever the selection is updated, automatically apply the change to the export item
|
||||
QObject::connect(materialSlotCheckBox, &QCheckBox::stateChanged, materialSlotCheckBox, [&exportItem, materialFileWidget, materialSlotCheckBox, overwriteCheckBox]([[maybe_unused]] int state) {
|
||||
exportItem.m_enabled = materialSlotCheckBox->isChecked();
|
||||
materialFileWidget->setEnabled(exportItem.m_enabled);
|
||||
overwriteCheckBox->setEnabled(exportItem.m_enabled && exportItem.m_exists);
|
||||
exportItem.SetEnabled(materialSlotCheckBox->isChecked());
|
||||
materialFileWidget->setEnabled(exportItem.GetEnabled());
|
||||
overwriteCheckBox->setEnabled(exportItem.GetEnabled() && exportItem.GetExists());
|
||||
});
|
||||
|
||||
// Whenever the overwrite check box is updated, automatically apply the change to the export item
|
||||
QObject::connect(overwriteCheckBox, &QCheckBox::stateChanged, overwriteCheckBox, [&exportItem, overwriteCheckBox]([[maybe_unused]] int state) {
|
||||
exportItem.m_overwrite = overwriteCheckBox->isChecked();
|
||||
exportItem.SetOverwrite(overwriteCheckBox->isChecked());
|
||||
});
|
||||
|
||||
// Whenever the browse button is clicked, open a save file dialog in the same location as the current export file setting
|
||||
QObject::connect(materialFileWidget, &AzQtComponents::BrowseEdit::attachedButtonTriggered, materialFileWidget, [&dialog, &exportItem, materialFileWidget, overwriteCheckBox]() {
|
||||
QFileInfo fileInfo = QFileDialog::getSaveFileName(&dialog,
|
||||
QString("Select Material Filename"),
|
||||
exportItem.m_exportPath.c_str(),
|
||||
exportItem.GetExportPath().c_str(),
|
||||
QString("Material (*.material)"),
|
||||
nullptr,
|
||||
QFileDialog::DontConfirmOverwrite);
|
||||
@@ -195,14 +155,14 @@ namespace AZ
|
||||
// Only update the export data if a valid path and filename was selected
|
||||
if (!fileInfo.absoluteFilePath().isEmpty())
|
||||
{
|
||||
exportItem.m_exportPath = fileInfo.absoluteFilePath().toUtf8().constData();
|
||||
exportItem.m_exists = fileInfo.exists();
|
||||
exportItem.m_overwrite = fileInfo.exists();
|
||||
exportItem.SetExportPath(fileInfo.absoluteFilePath().toUtf8().constData());
|
||||
exportItem.SetExists(fileInfo.exists());
|
||||
exportItem.SetOverwrite(fileInfo.exists());
|
||||
|
||||
// Update the controls to display the new state
|
||||
materialFileWidget->setText(fileInfo.fileName());
|
||||
overwriteCheckBox->setChecked(exportItem.m_overwrite);
|
||||
overwriteCheckBox->setEnabled(exportItem.m_enabled && exportItem.m_exists);
|
||||
overwriteCheckBox->setChecked(exportItem.GetOverwrite());
|
||||
overwriteCheckBox->setEnabled(exportItem.GetEnabled() && exportItem.GetExists());
|
||||
}
|
||||
});
|
||||
|
||||
@@ -245,24 +205,24 @@ namespace AZ
|
||||
|
||||
bool ExportMaterialSourceData(const ExportItem& exportItem)
|
||||
{
|
||||
if (!exportItem.m_enabled || exportItem.m_exportPath.empty())
|
||||
if (!exportItem.GetEnabled() || exportItem.GetExportPath().empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (exportItem.m_exists && !exportItem.m_overwrite)
|
||||
if (exportItem.GetExists() && !exportItem.GetOverwrite())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
EditorMaterialComponentUtil::MaterialEditData editData;
|
||||
if (!EditorMaterialComponentUtil::LoadMaterialEditDataFromAssetId(exportItem.m_assetId, editData))
|
||||
if (!EditorMaterialComponentUtil::LoadMaterialEditDataFromAssetId(exportItem.GetOriginalAssetId(), editData))
|
||||
{
|
||||
AZ_Warning("AZ::Render::EditorMaterialComponentExporter", false, "Failed to load material data.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!EditorMaterialComponentUtil::SaveSourceMaterialFromEditData(exportItem.m_exportPath, editData))
|
||||
if (!EditorMaterialComponentUtil::SaveSourceMaterialFromEditData(exportItem.GetExportPath(), editData))
|
||||
{
|
||||
AZ_Warning("AZ::Render::EditorMaterialComponentExporter", false, "Failed to save material data.");
|
||||
return false;
|
||||
|
||||
+30
-9
@@ -19,27 +19,48 @@ namespace AZ
|
||||
{
|
||||
namespace EditorMaterialComponentExporter
|
||||
{
|
||||
// Attemts to generate a display label for a material slot by parsing its file name
|
||||
AZStd::string GetLabelByAssetId(const AZ::Data::AssetId& assetId);
|
||||
//! Generates a destination file path for exporting material source data
|
||||
AZStd::string GetExportPathByAssetId(const AZ::Data::AssetId& assetId, const AZStd::string& materialSlotName);
|
||||
|
||||
// Generates a destination file path for exporting material source data
|
||||
AZStd::string GetExportPathByAssetId(const AZ::Data::AssetId& assetId);
|
||||
|
||||
struct ExportItem
|
||||
class ExportItem
|
||||
{
|
||||
public:
|
||||
//! @param originalAssetId AssetId of the original built-in material, which will be exported.
|
||||
//! @param materialSlotName The name of the material slot will be used as part of the exported file name.
|
||||
ExportItem(AZ::Data::AssetId originalAssetId, const AZStd::string& materialSlotName)
|
||||
: m_originalAssetId(originalAssetId)
|
||||
, m_materialSlotName(materialSlotName)
|
||||
{}
|
||||
|
||||
void SetEnabled(bool enabled) { m_enabled = enabled; }
|
||||
void SetExists(bool exists) { m_exists = exists; }
|
||||
void SetOverwrite(bool overwrite) { m_overwrite = overwrite; }
|
||||
void SetExportPath(const AZStd::string& exportPath) { m_exportPath = exportPath; }
|
||||
|
||||
bool GetEnabled() const { return m_enabled; }
|
||||
bool GetExists() const { return m_exists; }
|
||||
bool GetOverwrite() const { return m_overwrite; }
|
||||
const AZStd::string& GetExportPath() const { return m_exportPath; }
|
||||
|
||||
AZ::Data::AssetId GetOriginalAssetId() const { return m_originalAssetId; }
|
||||
const AZStd::string& GetMaterialSlotName() const { return m_materialSlotName; }
|
||||
|
||||
private:
|
||||
bool m_enabled = true;
|
||||
bool m_exists = false;
|
||||
bool m_overwrite = false;
|
||||
AZ::Data::AssetId m_assetId;
|
||||
AZStd::string m_exportPath;
|
||||
AZ::Data::AssetId m_originalAssetId; //!< AssetId of the original built-in material, which will be exported.
|
||||
AZStd::string m_materialSlotName;
|
||||
};
|
||||
|
||||
using ExportItemsContainer = AZStd::vector<ExportItem>;
|
||||
|
||||
// Generates and opens a dialog for configuring material data export paths and actions
|
||||
//! Generates and opens a dialog for configuring material data export paths and actions.
|
||||
//! Note this will not modify the m_originalAssetId field in each ExportItem.
|
||||
bool OpenExportDialog(ExportItemsContainer& exportItems);
|
||||
|
||||
// Attemts to construct and save material source data from a product asset
|
||||
//! Attemts to construct and save material source data from a product asset
|
||||
bool ExportMaterialSourceData(const ExportItem& exportItem);
|
||||
} // namespace EditorMaterialComponentExporter
|
||||
} // namespace Render
|
||||
|
||||
+13
-27
@@ -20,7 +20,7 @@
|
||||
|
||||
AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT
|
||||
#include <QMenu>
|
||||
#include <QAction>
|
||||
#include <QAction>
|
||||
#include <QCursor>
|
||||
AZ_POP_DISABLE_WARNING
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace AZ
|
||||
return false;
|
||||
}
|
||||
|
||||
const MaterialAssignmentId newId(oldId.first, oldId.second);
|
||||
const MaterialAssignmentId newId(oldId.first, oldId.second.m_subId);
|
||||
classElement.AddElementWithData(context, "id", newId);
|
||||
}
|
||||
|
||||
@@ -80,9 +80,10 @@ namespace AZ
|
||||
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
|
||||
{
|
||||
serializeContext->Class<EditorMaterialComponentSlot>()
|
||||
->Version(5, &EditorMaterialComponentSlot::ConvertVersion)
|
||||
->Version(6, &EditorMaterialComponentSlot::ConvertVersion)
|
||||
->Field("id", &EditorMaterialComponentSlot::m_id)
|
||||
->Field("materialAsset", &EditorMaterialComponentSlot::m_materialAsset)
|
||||
->Field("defaultMaterialAsset", &EditorMaterialComponentSlot::m_defaultMaterialAsset)
|
||||
;
|
||||
|
||||
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
|
||||
@@ -121,21 +122,12 @@ namespace AZ
|
||||
|
||||
AZ::Data::AssetId EditorMaterialComponentSlot::GetDefaultAssetId() const
|
||||
{
|
||||
return m_id.m_materialAssetId;
|
||||
return m_defaultMaterialAsset.GetId();
|
||||
}
|
||||
|
||||
AZStd::string EditorMaterialComponentSlot::GetLabel() const
|
||||
{
|
||||
// Generate the label for the material slot based on the assignment ID
|
||||
// If this is the default material assignment ID then it represents the default slot which is not contained in any other group
|
||||
if (m_id == DefaultMaterialAssignmentId)
|
||||
{
|
||||
return "Default Material";
|
||||
}
|
||||
|
||||
// Otherwise the label can be generated by parsing the source file name associated with the asset ID
|
||||
const AZStd::string& label = EditorMaterialComponentExporter::GetLabelByAssetId(m_id.m_materialAssetId);
|
||||
return !label.empty() ? label : "<unknown>";
|
||||
return m_label;
|
||||
}
|
||||
|
||||
bool EditorMaterialComponentSlot::HasSourceData() const
|
||||
@@ -183,27 +175,21 @@ namespace AZ
|
||||
OnMaterialChanged();
|
||||
}
|
||||
|
||||
void EditorMaterialComponentSlot::SetDefaultAsset()
|
||||
void EditorMaterialComponentSlot::ResetToDefaultAsset()
|
||||
{
|
||||
m_materialAsset = {};
|
||||
m_materialAsset = m_defaultMaterialAsset;
|
||||
m_propertyOverrides = {};
|
||||
m_matModUvOverrides = {};
|
||||
if (m_id.m_materialAssetId.IsValid())
|
||||
{
|
||||
// If no material is assigned to this slot, assign the default material from the slot id to edit its properties
|
||||
m_materialAsset.Create(m_id.m_materialAssetId);
|
||||
}
|
||||
OnMaterialChanged();
|
||||
}
|
||||
|
||||
void EditorMaterialComponentSlot::OpenMaterialExporter()
|
||||
{
|
||||
// Because we are generating a source material from this specific slot there is only one entry
|
||||
// But we still need to allow the user to reconfigure it using the dialogue
|
||||
// But we still need to allow the user to reconfigure it using the dialog
|
||||
EditorMaterialComponentExporter::ExportItemsContainer exportItems;
|
||||
{
|
||||
EditorMaterialComponentExporter::ExportItem exportItem;
|
||||
exportItem.m_assetId = m_id.m_materialAssetId;
|
||||
EditorMaterialComponentExporter::ExportItem exportItem{m_defaultMaterialAsset.GetId(), m_label};
|
||||
exportItems.push_back(exportItem);
|
||||
}
|
||||
|
||||
@@ -218,7 +204,7 @@ namespace AZ
|
||||
}
|
||||
|
||||
// Generate a new asset ID utilizing the export file path so that we can update this material slot to reference the new asset
|
||||
const auto& assetIdOutcome = AZ::RPI::AssetUtils::MakeAssetId(exportItem.m_exportPath, 0);
|
||||
const auto& assetIdOutcome = AZ::RPI::AssetUtils::MakeAssetId(exportItem.GetExportPath(), 0);
|
||||
if (assetIdOutcome)
|
||||
{
|
||||
m_materialAsset.Create(assetIdOutcome.GetValue());
|
||||
@@ -258,7 +244,7 @@ namespace AZ
|
||||
// Treated as a special property. It will be updated together with properties.
|
||||
OnPropertyChanged();
|
||||
};
|
||||
|
||||
|
||||
if (m_materialAsset.GetId().IsValid())
|
||||
{
|
||||
if (EditorMaterialComponentInspector::OpenInspectorDialog(m_materialAsset.GetId(), m_matModUvOverrides, m_modelUvNames, applyMatModUvOverrideChangedCallback))
|
||||
@@ -275,7 +261,7 @@ namespace AZ
|
||||
QAction* action = nullptr;
|
||||
|
||||
action = menu.addAction("Generate/Manage Source Material...", [this]() { OpenMaterialExporter(); });
|
||||
action->setEnabled(m_id.m_materialAssetId.IsValid());
|
||||
action->setEnabled(m_defaultMaterialAsset.GetId().IsValid());
|
||||
|
||||
menu.addSeparator();
|
||||
|
||||
|
||||
+3
-1
@@ -34,7 +34,7 @@ namespace AZ
|
||||
AZStd::string GetLabel() const;
|
||||
bool HasSourceData() const;
|
||||
void OpenMaterialEditor() const;
|
||||
void SetDefaultAsset();
|
||||
void ResetToDefaultAsset();
|
||||
void Clear();
|
||||
void ClearOverrides();
|
||||
void OpenMaterialExporter();
|
||||
@@ -42,7 +42,9 @@ namespace AZ
|
||||
void OpenUvNameMapInspector();
|
||||
|
||||
MaterialAssignmentId m_id;
|
||||
AZStd::string m_label;
|
||||
Data::Asset<RPI::MaterialAsset> m_materialAsset;
|
||||
Data::Asset<RPI::MaterialAsset> m_defaultMaterialAsset;
|
||||
MaterialPropertyOverrideMap m_propertyOverrides;
|
||||
AZStd::function<void()> m_materialChangedCallback;
|
||||
AZStd::function<void()> m_propertyChangedCallback;
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ namespace AZ
|
||||
for (const auto& oldPair : oldMaterials)
|
||||
{
|
||||
const DeprecatedMaterialAssignmentId& oldId = oldPair.first;
|
||||
const MaterialAssignmentId newId(oldId.first, oldId.second);
|
||||
const MaterialAssignmentId newId(oldId.first, oldId.second.m_subId);
|
||||
newMaterials[newId] = oldPair.second;
|
||||
|
||||
}
|
||||
|
||||
+12
-1
@@ -33,6 +33,7 @@ namespace AZ
|
||||
->Attribute(AZ::Script::Attributes::Category, "render")
|
||||
->Attribute(AZ::Script::Attributes::Module, "render")
|
||||
->Event("GetOriginalMaterialAssignments", &MaterialComponentRequestBus::Events::GetOriginalMaterialAssignments)
|
||||
->Event("FindMaterialAssignmentId", &MaterialComponentRequestBus::Events::FindMaterialAssignmentId)
|
||||
->Event("SetMaterialOverrides", &MaterialComponentRequestBus::Events::SetMaterialOverrides)
|
||||
->Event("GetMaterialOverrides", &MaterialComponentRequestBus::Events::GetMaterialOverrides)
|
||||
->Event("ClearAllMaterialOverrides", &MaterialComponentRequestBus::Events::ClearAllMaterialOverrides)
|
||||
@@ -249,10 +250,20 @@ namespace AZ
|
||||
MaterialAssignmentMap MaterialComponentController::GetOriginalMaterialAssignments() const
|
||||
{
|
||||
MaterialAssignmentMap materialAssignmentMap;
|
||||
MaterialReceiverRequestBus::EventResult(materialAssignmentMap, m_entityId, &MaterialReceiverRequestBus::Events::GetMaterialAssignments);
|
||||
MaterialReceiverRequestBus::EventResult(
|
||||
materialAssignmentMap, m_entityId, &MaterialReceiverRequestBus::Events::GetMaterialAssignments);
|
||||
return materialAssignmentMap;
|
||||
}
|
||||
|
||||
MaterialAssignmentId MaterialComponentController::FindMaterialAssignmentId(
|
||||
const MaterialAssignmentLodIndex lod, const AZStd::string& label) const
|
||||
{
|
||||
MaterialAssignmentId materialAssignmentId;
|
||||
MaterialReceiverRequestBus::EventResult(
|
||||
materialAssignmentId, m_entityId, &MaterialReceiverRequestBus::Events::FindMaterialAssignmentId, lod, label);
|
||||
return materialAssignmentId;
|
||||
}
|
||||
|
||||
void MaterialComponentController::SetMaterialOverrides(const MaterialAssignmentMap& materials)
|
||||
{
|
||||
// this function is called twice once material asset is changed, a temp variable is
|
||||
|
||||
+1
@@ -46,6 +46,7 @@ namespace AZ
|
||||
|
||||
//! MaterialComponentRequestBus overrides...
|
||||
MaterialAssignmentMap GetOriginalMaterialAssignments() const override;
|
||||
MaterialAssignmentId FindMaterialAssignmentId(const MaterialAssignmentLodIndex lod, const AZStd::string& label) const override;
|
||||
void SetMaterialOverrides(const MaterialAssignmentMap& materials) override;
|
||||
const MaterialAssignmentMap& GetMaterialOverrides() const override;
|
||||
void ClearAllMaterialOverrides() override;
|
||||
|
||||
@@ -251,6 +251,25 @@ namespace AZ
|
||||
m_meshFeatureProcessor->SetTransform(m_meshHandle, m_transformInterface->GetWorldTM(), m_cachedNonUniformScale);
|
||||
}
|
||||
}
|
||||
|
||||
RPI::ModelMaterialSlotMap MeshComponentController::GetModelMaterialSlots() const
|
||||
{
|
||||
Data::Asset<const RPI::ModelAsset> modelAsset = GetModelAsset();
|
||||
if (modelAsset.IsReady())
|
||||
{
|
||||
return modelAsset->GetMaterialSlots();
|
||||
}
|
||||
else
|
||||
{
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
MaterialAssignmentId MeshComponentController::FindMaterialAssignmentId(
|
||||
const MaterialAssignmentLodIndex lod, const AZStd::string& label) const
|
||||
{
|
||||
return FindMaterialAssignmentIdInModel(GetModel(), lod, label);
|
||||
}
|
||||
|
||||
MaterialAssignmentMap MeshComponentController::GetMaterialAssignments() const
|
||||
{
|
||||
|
||||
@@ -111,6 +111,9 @@ namespace AZ
|
||||
void OnTransformChanged(const AZ::Transform& local, const AZ::Transform& world) override;
|
||||
|
||||
// MaterialReceiverRequestBus::Handler overrides ...
|
||||
virtual MaterialAssignmentId FindMaterialAssignmentId(
|
||||
const MaterialAssignmentLodIndex lod, const AZStd::string& label) const override;
|
||||
RPI::ModelMaterialSlotMap GetModelMaterialSlots() const override;
|
||||
MaterialAssignmentMap GetMaterialAssignments() const override;
|
||||
AZStd::unordered_set<AZ::Name> GetModelUvNames() const override;
|
||||
|
||||
|
||||
@@ -96,12 +96,11 @@ namespace AZ
|
||||
skinnedSubMesh.m_vertexCount = aznumeric_cast<uint32_t>(subMeshVertexCount);
|
||||
lodVertexCount += aznumeric_cast<uint32_t>(subMeshVertexCount);
|
||||
|
||||
// The default material id used by a sub-mesh is the guid of the source scene file plus the subId which is a unique material ID from the scene API
|
||||
AZ::u32 subId = modelMesh.GetMaterialAsset().GetId().m_subId;
|
||||
AZ::Data::AssetId materialId{ actorAssetId.m_guid, subId };
|
||||
skinnedSubMesh.m_materialSlot = actor->GetMeshAsset()->FindMaterialSlot(modelMesh.GetMaterialSlotId());
|
||||
|
||||
// Queue the material asset - the ModelLod seems to handle delayed material loads
|
||||
skinnedSubMesh.m_material = Data::AssetManager::Instance().GetAsset(materialId, azrtti_typeid<RPI::MaterialAsset>(), skinnedSubMesh.m_material.GetAutoLoadBehavior());
|
||||
skinnedSubMesh.m_materialSlot.m_defaultMaterialAsset.QueueLoad();
|
||||
|
||||
subMeshes.push_back(skinnedSubMesh);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -307,6 +307,30 @@ namespace AZ
|
||||
m_meshFeatureProcessor = nullptr;
|
||||
m_skinnedMeshFeatureProcessor = nullptr;
|
||||
}
|
||||
|
||||
RPI::ModelMaterialSlotMap AtomActorInstance::GetModelMaterialSlots() const
|
||||
{
|
||||
Data::Asset<const RPI::ModelAsset> modelAsset = GetModelAsset();
|
||||
if (modelAsset.IsReady())
|
||||
{
|
||||
return modelAsset->GetMaterialSlots();
|
||||
}
|
||||
else
|
||||
{
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
MaterialAssignmentId AtomActorInstance::FindMaterialAssignmentId(
|
||||
const MaterialAssignmentLodIndex lod, const AZStd::string& label) const
|
||||
{
|
||||
if (m_skinnedMeshInstance && m_skinnedMeshInstance->m_model)
|
||||
{
|
||||
return FindMaterialAssignmentIdInModel(m_skinnedMeshInstance->m_model, lod, label);
|
||||
}
|
||||
|
||||
return MaterialAssignmentId();
|
||||
}
|
||||
|
||||
MaterialAssignmentMap AtomActorInstance::GetMaterialAssignments() const
|
||||
{
|
||||
@@ -490,16 +514,18 @@ namespace AZ
|
||||
const AZStd::vector< SkinnedSubMeshProperties>& subMeshProperties = inputLod.GetSubMeshProperties();
|
||||
for (const SkinnedSubMeshProperties& submesh : subMeshProperties)
|
||||
{
|
||||
AZ_Error("AtomActorInstance", submesh.m_material, "Actor does not have a valid default material in lod %d", lodIndex);
|
||||
if (submesh.m_material)
|
||||
Data::Asset<RPI::MaterialAsset> materialAsset = submesh.m_materialSlot.m_defaultMaterialAsset;
|
||||
AZ_Error("AtomActorInstance", materialAsset, "Actor does not have a valid default material in lod %d", lodIndex);
|
||||
|
||||
if (materialAsset)
|
||||
{
|
||||
if (!submesh.m_material->IsReady())
|
||||
if (!materialAsset->IsReady())
|
||||
{
|
||||
// Start listening for the material's OnAssetReady event.
|
||||
// AtomActorInstance::Create is called on the main thread, so there should be no need to synchronize with the OnAssetReady event handler
|
||||
// since those events will also come from the main thread
|
||||
m_waitForMaterialLoadIds.insert(submesh.m_material->GetId());
|
||||
Data::AssetBus::MultiHandler::BusConnect(submesh.m_material->GetId());
|
||||
m_waitForMaterialLoadIds.insert(materialAsset->GetId());
|
||||
Data::AssetBus::MultiHandler::BusConnect(materialAsset->GetId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,6 +120,9 @@ namespace AZ
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// MaterialReceiverRequestBus::Handler overrides...
|
||||
virtual MaterialAssignmentId FindMaterialAssignmentId(
|
||||
const MaterialAssignmentLodIndex lod, const AZStd::string& label) const override;
|
||||
RPI::ModelMaterialSlotMap GetModelMaterialSlots() const override;
|
||||
MaterialAssignmentMap GetMaterialAssignments() const override;
|
||||
AZStd::unordered_set<AZ::Name> GetModelUvNames() const override;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user