Tied up a few loose ends to support deeply nested property groups.

Simplified the call back for MaterialTypeSourceData::EnumeratePropertyGroups while also providing more data.
Made the Material Inspector join nested property group display names to be like "Layer 1 | Base Color", since the leaf property groups are shown as a flat list in the inspector.
Fixed CreateMaterialAssetFromSourceData to include the imported json files in the list of sourceDependencies. This triggers the Material Editor to hot-reload when one of these json files changes.
Updated a few places that were still assuming only one level of property group.
Updated EditorMaterialComponentInspector to apply the per-property-group material functors, before it was still only applying the top-level onces.
Moved some accessor function implementations to the cpp files, per feedback on another already-merged PR.

Testing:
Made changes to MinimalMultilayerPbr (in AtomSampleViewer) to use nested property groups, and saw the correct behavior in the Material Editor's property inspector.
Used MaterialComponent's property inspector to edit a StandardPbr material instance. Confrimed that functors were correctly controlling property visibility by enabling and disabling things like emissive and clear coat.
Used MaterialComponent's property inspector to edit a MinimalMultilayerPbr material instance. Saw all the expected groups and properties show up. Confirmed that per-group functors were correctly controlling property visibility.
Used MaterialComponent's property inspector to export a material instance and confirmed the .material file included the expected properties.

Signed-off-by: santorac <55155825+santorac@users.noreply.github.com>
This commit is contained in:
santorac
2022-02-09 13:13:40 -08:00
parent 2a3c5b38e5
commit 8fbd2aaaf5
9 changed files with 241 additions and 112 deletions
@@ -102,29 +102,10 @@ namespace AZ
UnloadMaterial();
return false;
}
// Add material functors that are in the top-level functors list. Other functors are also added per-property-group elsewhere.
AddEditorMaterialFunctors(m_editData.m_materialTypeSourceData.m_materialFunctorSourceData, AZ::RPI::MaterialNameContext{});
// Get a list of all the editor functors to be used for property editor states
auto propertyLayout = m_editData.m_materialAsset->GetMaterialPropertiesLayout();
AZ::RPI::MaterialNameContext materialNameContext; // There is no name context for top-level functors, only functors inside PropertyGroups
const AZ::RPI::MaterialFunctorSourceData::EditorContext editorContext =
AZ::RPI::MaterialFunctorSourceData::EditorContext(m_editData.m_materialTypeSourcePath, propertyLayout, &materialNameContext);
for (AZ::RPI::Ptr<AZ::RPI::MaterialFunctorSourceDataHolder> functorData : m_editData.m_materialTypeSourceData.m_materialFunctorSourceData)
{
AZ::RPI::MaterialFunctorSourceData::FunctorResult createResult = functorData->CreateFunctor(editorContext);
if (createResult.IsSuccess())
{
AZ::RPI::Ptr<AZ::RPI::MaterialFunctor>& functor = createResult.GetValue();
if (functor != nullptr)
{
m_editorFunctors.push_back(functor);
}
}
else
{
AZ_Error("AZ::Render::EditorMaterialComponentInspector", false, "Material functors were not created: '%s'.", m_editData.m_materialTypeSourcePath.c_str());
}
}
Populate();
LoadOverridesFromEntity();
@@ -295,47 +276,73 @@ namespace AZ
void MaterialPropertyInspector::AddPropertiesGroup()
{
// Copy all of the properties from the material asset to the source data that will be exported
// TODO: Support populating the Material Editor with nested property groups, not just the top level.
for (const AZStd::unique_ptr<AZ::RPI::MaterialTypeSourceData::PropertyGroup>& propertyGroup : m_editData.m_materialTypeSourceData.GetPropertyLayout().m_propertyGroups)
{
const AZStd::string& groupName = propertyGroup->GetName();
const AZStd::string& groupDisplayName = !propertyGroup->GetDisplayName().empty() ? propertyGroup->GetDisplayName() : groupName;
const AZStd::string& groupDescription = !propertyGroup->GetDescription().empty() ? propertyGroup->GetDescription() : groupDisplayName;
auto& group = m_groups[groupName];
group.m_properties.reserve(propertyGroup->GetProperties().size());
for (const auto& propertyDefinition : propertyGroup->GetProperties())
m_editData.m_materialTypeSourceData.EnumeratePropertyGroups(
[this](const AZ::RPI::MaterialTypeSourceData::PropertyGroupStack& propertyGroupStack)
{
AtomToolsFramework::DynamicPropertyConfig propertyConfig;
using namespace AZ::RPI;
// Assign id before conversion so it can be used in dynamic description
propertyConfig.m_id = AZ::RPI::MaterialPropertyId(groupName, propertyDefinition->GetName());
AtomToolsFramework::ConvertToPropertyConfig(propertyConfig, *propertyDefinition.get());
const auto& propertyIndex =
m_editData.m_materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyConfig.m_id);
const MaterialTypeSourceData::PropertyGroup* propertyGroupDefinition = propertyGroupStack.back();
MaterialNameContext groupNameContext = MaterialTypeSourceData::MakeMaterialNameContext(propertyGroupStack);
propertyConfig.m_groupName = groupDisplayName;
propertyConfig.m_showThumbnail = true;
propertyConfig.m_defaultValue = AtomToolsFramework::ConvertToEditableType(
m_editData.m_materialTypeAsset->GetDefaultPropertyValues()[propertyIndex.GetIndex()]);
// There is no explicit parent material here. Material instance property overrides replace the values from the
// assigned material asset. Its values should be treated as parent, for comparison, in this case.
propertyConfig.m_parentValue = AtomToolsFramework::ConvertToEditableType(
m_editData.m_materialTypeAsset->GetDefaultPropertyValues()[propertyIndex.GetIndex()]);
propertyConfig.m_originalValue = AtomToolsFramework::ConvertToEditableType(
m_editData.m_materialAsset->GetPropertyValues()[propertyIndex.GetIndex()]);
group.m_properties.emplace_back(propertyConfig);
}
AddEditorMaterialFunctors(propertyGroupDefinition->GetFunctors(), groupNameContext);
// Passing in same group as main and comparison instance to enable custom value comparison for highlighting modified properties
auto propertyGroupWidget = new AtomToolsFramework::InspectorPropertyGroupWidget(
&group, &group, group.TYPEINFO_Uuid(), this, this, GetGroupSaveStateKey(groupName), {},
[this](const auto node) { return GetInstanceNodePropertyIndicator(node); }, 0);
AddGroup(groupName, groupDisplayName, groupDescription, propertyGroupWidget);
}
AZStd::vector<AZStd::string> groupNameVector;
AZStd::vector<AZStd::string> groupDisplayNameVector;
for (auto& group : propertyGroupStack)
{
groupNameVector.push_back(group->GetName());
groupDisplayNameVector.push_back(!group->GetDisplayName().empty() ? group->GetDisplayName() : group->GetName());
}
AZStd::string groupId;
AzFramework::StringFunc::Join(groupId, groupNameVector.begin(), groupNameVector.end(), ".");
auto& group = m_groups[groupId];
group.m_name = groupId;
AzFramework::StringFunc::Join(group.m_displayName, groupDisplayNameVector.begin(), groupDisplayNameVector.end(), " | ");
group.m_description = !propertyGroupDefinition->GetDescription().empty() ? propertyGroupDefinition->GetDescription() : group.m_displayName;
group.m_properties.reserve(propertyGroupDefinition->GetProperties().size());
for (const auto& propertyDefinition : propertyGroupDefinition->GetProperties())
{
AtomToolsFramework::DynamicPropertyConfig propertyConfig;
// Assign id before conversion so it can be used in dynamic description
propertyConfig.m_id = propertyDefinition->GetName();
groupNameContext.ContextualizeProperty(propertyConfig.m_id);
AtomToolsFramework::ConvertToPropertyConfig(propertyConfig, *propertyDefinition);
const auto& propertyIndex =
m_editData.m_materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyConfig.m_id);
// (Does DynamicPropertyConfig really even need m_groupName? It doesn't seem to be used anywhere)
propertyConfig.m_groupName = group.m_name;
propertyConfig.m_groupDisplayName = group.m_displayName;
propertyConfig.m_showThumbnail = true;
propertyConfig.m_defaultValue = AtomToolsFramework::ConvertToEditableType(
m_editData.m_materialTypeAsset->GetDefaultPropertyValues()[propertyIndex.GetIndex()]);
// There is no explicit parent material here. Material instance property overrides replace the values from the
// assigned material asset. Its values should be treated as parent, for comparison, in this case.
propertyConfig.m_parentValue = AtomToolsFramework::ConvertToEditableType(
m_editData.m_materialTypeAsset->GetDefaultPropertyValues()[propertyIndex.GetIndex()]);
propertyConfig.m_originalValue = AtomToolsFramework::ConvertToEditableType(
m_editData.m_materialAsset->GetPropertyValues()[propertyIndex.GetIndex()]);
group.m_properties.emplace_back(propertyConfig);
}
// Passing in same group as main and comparison instance to enable custom value comparison for highlighting modified properties
auto propertyGroupWidget = new AtomToolsFramework::InspectorPropertyGroupWidget(
&group, &group, group.TYPEINFO_Uuid(), this, this, GetGroupSaveStateKey(group.m_name), {},
[this](const auto node) { return GetInstanceNodePropertyIndicator(node); }, 0);
AddGroup(group.m_name, group.m_displayName, group.m_description, propertyGroupWidget);
return true;
});
}
void MaterialPropertyInspector::Populate()
@@ -435,6 +442,37 @@ namespace AZ
// m_updatePreview should be set to true here for continuous preview updates as slider/color properties change but needs
// throttling
}
bool MaterialPropertyInspector::AddEditorMaterialFunctors(
const AZStd::vector<AZ::RPI::Ptr<AZ::RPI::MaterialFunctorSourceDataHolder>>& functorSourceDataHolders,
const AZ::RPI::MaterialNameContext& nameContext)
{
// Copied from MaterialDocument::AddEditorMaterialFunctors, should be refactored at some point
const AZ::RPI::MaterialFunctorSourceData::EditorContext editorContext = AZ::RPI::MaterialFunctorSourceData::EditorContext(
m_editData.m_materialTypeSourcePath, m_editData.m_materialAsset->GetMaterialPropertiesLayout(), &nameContext);
for (AZ::RPI::Ptr<AZ::RPI::MaterialFunctorSourceDataHolder> functorData : functorSourceDataHolders)
{
AZ::RPI::MaterialFunctorSourceData::FunctorResult result = functorData->CreateFunctor(editorContext);
if (result.IsSuccess())
{
AZ::RPI::Ptr<AZ::RPI::MaterialFunctor>& functor = result.GetValue();
if (functor != nullptr)
{
m_editorFunctors.push_back(functor);
}
}
else
{
AZ_Error("MaterialDocument", false, "Material functors were not created: '%s'.", m_editData.m_materialTypeSourcePath.c_str());
return false;
}
}
return true;
}
void MaterialPropertyInspector::RunEditorMaterialFunctors()
{