@ -53,112 +53,93 @@ namespace MaterialEditor
return & m_materialTypeSourceData ;
return & m_materialTypeSourceData ;
}
}
const AZStd : : any & MaterialDocument : : G etPropertyValue( const AZ : : Name & propertyId ) const
void MaterialDocument : : S etPropertyValue( const AZ : : Name & propertyId , const AZStd : : any & value )
{
{
if ( ! IsOpen ( ) )
if ( ! IsOpen ( ) )
{
{
AZ_Error ( " MaterialDocument " , false , " Document is not open. " ) ;
AZ_Error ( " MaterialDocument " , false , " Document is not open. " ) ;
return m_invalidValue ;
return ;
}
}
const auto it = m_properties . find ( propertyId ) ;
AtomToolsFramework : : DynamicProperty * foundProperty = { } ;
if ( it = = m_properties . end ( ) )
TraverseGroups ( m_groups , [ & , this ] ( auto & group ) {
{
for ( auto & property : group - > m_properties )
AZ_Error ( " MaterialDocument " , false , " Document property could not be found: '%s'. " , propertyId . GetCStr ( ) ) ;
{
return m_invalidValue ;
if ( property . GetId ( ) = = propertyId )
}
{
foundProperty = & property ;
const AtomToolsFramework : : DynamicProperty & property = it - > second ;
// This first converts to an acceptable runtime type in case the value came from script
return property . GetValue ( ) ;
const AZ : : RPI : : MaterialPropertyValue propertyValue = AtomToolsFramework : : ConvertToRuntimeType ( value ) ;
}
const AtomToolsFramework : : DynamicProperty & MaterialDocument : : GetProperty ( const AZ : : Name & propertyId ) const
property . SetValue ( AtomToolsFramework : : ConvertToEditableType ( propertyValue ) ) ;
{
if ( ! IsOpen ( ) )
const auto propertyIndex = m_materialInstance - > FindPropertyIndex ( propertyId ) ;
{
if ( ! propertyIndex . IsNull ( ) )
AZ_Error ( " MaterialDocument " , false , " Document is not open. " ) ;
{
return m_invalidProperty ;
if ( m_materialInstance - > SetPropertyValue ( propertyIndex , propertyValue ) )
}
{
AZ : : RPI : : MaterialPropertyFlags dirtyFlags = m_materialInstance - > GetPropertyDirtyFlags ( ) ;
Recompile ( ) ;
RunEditorMaterialFunctors ( dirtyFlags ) ;
}
}
AtomToolsFramework : : AtomToolsDocumentNotificationBus : : Broadcast (
& AtomToolsFramework : : AtomToolsDocumentNotificationBus : : Events : : OnDocumentObjectInfoChanged , m_id ,
GetObjectInfoFromDynamicPropertyGroup ( group . get ( ) ) , false ) ;
AtomToolsFramework : : AtomToolsDocumentNotificationBus : : Broadcast (
& AtomToolsFramework : : AtomToolsDocumentNotificationBus : : Events : : OnDocumentModified , m_id ) ;
return false ;
}
}
return true ;
} ) ;
const auto it = m_properties . find ( propertyId ) ;
if ( ! foundProperty )
if ( it = = m_properties . end ( ) )
{
{
AZ_Error ( " MaterialDocument " , false , " Document property could not be found: '%s'. " , propertyId . GetCStr ( ) ) ;
AZ_Error ( " MaterialDocument " , false , " Document property could not be found: '%s'. " , propertyId . GetCStr ( ) ) ;
return m_invalidProperty ;
}
}
const AtomToolsFramework : : DynamicProperty & property = it - > second ;
return property ;
}
}
bool MaterialDocument : : IsPropertyGroupVisible ( const AZ : : Name & propertyGroupFullName ) const
const AZStd : : any & MaterialDocument : : GetPropertyValue ( const AZ : : Name & propertyId ) const
{
{
if ( ! IsOpen ( ) )
if ( ! IsOpen ( ) )
{
{
AZ_Error ( " MaterialDocument " , false , " Document is not open. " ) ;
AZ_Error ( " MaterialDocument " , false , " Document is not open. " ) ;
return false ;
return m_invalidValu e;
}
}
const auto it = m_propertyGroupVisibility . find ( propertyGroupFullName ) ;
auto property = FindProperty ( propertyId ) ;
if ( it = = m_propertyGroupVisibility . end ( ) )
if ( ! property )
{
{
AZ_Error ( " MaterialDocument " , false , " Document property group could not be found: '%s'." , property GroupFullName . GetCStr ( ) ) ;
AZ_Error ( " MaterialDocument " , false , " Document property could not be found: '%s'." , property Id . GetCStr ( ) ) ;
return fals e;
return m_invalidValu e;
}
}
return it- > second ;
return property- > GetValue ( ) ;
}
}
void MaterialDocument : : SetPropertyValue ( const AZ : : Name & propertyId , const AZStd : : any & value )
AZStd : : vector < AtomToolsFramework : : DocumentObjectInfo > MaterialDocument : : GetObjectInfo ( ) const
{
{
if ( ! IsOpen ( ) )
if ( ! IsOpen ( ) )
{
{
AZ_Error ( " MaterialDocument " , false , " Document is not open. " ) ;
AZ_Error ( " MaterialDocument " , false , " Document is not open. " ) ;
return ;
return { } ;
}
}
const auto it = m_properties . find ( propertyId ) ;
AZStd : : vector < AtomToolsFramework : : DocumentObjectInfo > objects ;
if ( it = = m_properties . end ( ) )
objects . reserve ( m_groups . size ( ) ) ;
{
AZ_Error ( " MaterialDocument " , false , " Document property could not be found: '%s'. " , propertyId . GetCStr ( ) ) ;
return ;
}
// This first converts to an acceptable runtime type in case the value came from script
const AZ : : RPI : : MaterialPropertyValue propertyValue = AtomToolsFramework : : ConvertToRuntimeType ( value ) ;
AtomToolsFramework : : DynamicProperty & property = it - > second ;
property . SetValue ( AtomToolsFramework : : ConvertToEditableType ( propertyValue ) ) ;
const auto propertyIndex = m_materialInstance - > FindPropertyIndex ( propertyId ) ;
AtomToolsFramework : : DocumentObjectInfo objectInfo ;
if ( ! propertyIndex . IsNull ( ) )
for ( const auto & group : m_groups )
{
{
if ( m_materialInstance - > SetPropertyValue ( propertyIndex , propertyValue ) )
objects . push_back ( GetObjectInfoFromDynamicPropertyGroup ( group . get ( ) ) ) ;
{
AZ : : RPI : : MaterialPropertyFlags dirtyFlags = m_materialInstance - > GetPropertyDirtyFlags ( ) ;
Recompile ( ) ;
EditorMaterialFunctorResult result = RunEditorMaterialFunctors ( dirtyFlags ) ;
for ( const AZ : : Name & changedPropertyGroupName : result . m_updatedPropertyGroups )
{
AtomToolsFramework : : AtomToolsDocumentNotificationBus : : Broadcast (
& AtomToolsFramework : : AtomToolsDocumentNotificationBus : : Events : : OnDocumentPropertyGroupVisibilityChanged , m_id ,
changedPropertyGroupName , IsPropertyGroupVisible ( changedPropertyGroupName ) ) ;
}
for ( const AZ : : Name & changedPropertyName : result . m_updatedProperties )
{
AtomToolsFramework : : AtomToolsDocumentNotificationBus : : Broadcast (
& AtomToolsFramework : : AtomToolsDocumentNotificationBus : : Events : : OnDocumentPropertyConfigModified , m_id ,
GetProperty ( changedPropertyName ) ) ;
}
}
}
}
AtomToolsFramework : : AtomToolsDocumentNotificationBus : : Broadcast (
return objects ;
& AtomToolsFramework : : AtomToolsDocumentNotificationBus : : Events : : OnDocumentPropertyValueModified , m_id , property ) ;
AtomToolsFramework : : AtomToolsDocumentNotificationBus : : Broadcast (
& AtomToolsFramework : : AtomToolsDocumentNotificationBus : : Events : : OnDocumentModified , m_id ) ;
}
}
bool MaterialDocument : : Save ( )
bool MaterialDocument : : Save ( )
@ -185,14 +166,15 @@ namespace MaterialEditor
}
}
// after saving, reset to a clean state
// after saving, reset to a clean state
for ( auto & propertyPair : m_properties )
TraverseGroups ( m_groups , [ & ] ( auto & group ) {
{
for ( auto & property : group - > m_properties )
AtomToolsFramework : : DynamicProperty & property = propertyPair . second ;
{
auto propertyConfig = property . GetConfig ( ) ;
auto propertyConfig = property . GetConfig ( ) ;
propertyConfig . m_originalValue = property . GetValue ( ) ;
propertyConfig . m_originalValue = property . GetValue ( ) ;
property . SetConfig ( propertyConfig ) ;
property . SetConfig ( propertyConfig ) ;
}
}
return true ;
} ) ;
return SaveSucceeded ( ) ;
return SaveSucceeded ( ) ;
}
}
@ -273,12 +255,19 @@ namespace MaterialEditor
bool MaterialDocument : : IsModified ( ) const
bool MaterialDocument : : IsModified ( ) const
{
{
return AZStd : : any_of ( m_properties . begin ( ) , m_properties . end ( ) ,
bool result = false ;
[ ] ( const auto & propertyPair )
TraverseGroups ( m_groups , [ & ] ( auto & group ) {
{
for ( auto & property : group - > m_properties )
const AtomToolsFramework : : DynamicProperty & property = propertyPair . second ;
{
return ! AtomToolsFramework : : ArePropertyValuesEqual ( property . GetValue ( ) , property . GetConfig ( ) . m_originalValue ) ;
if ( ! AtomToolsFramework : : ArePropertyValuesEqual ( property . GetValue ( ) , property . GetConfig ( ) . m_originalValue ) )
{
result = true ;
return false ;
}
}
return true ;
} ) ;
} ) ;
return result ;
}
}
bool MaterialDocument : : IsSavable ( ) const
bool MaterialDocument : : IsSavable ( ) const
@ -290,11 +279,13 @@ namespace MaterialEditor
{
{
// Save the current properties as a momento for undo before any changes are applied
// Save the current properties as a momento for undo before any changes are applied
m_propertyValuesBeforeEdit . clear ( ) ;
m_propertyValuesBeforeEdit . clear ( ) ;
for ( const auto & propertyPair : m_properties )
TraverseGroups ( m_groups , [ this ] ( auto & group ) {
{
for ( auto & property : group - > m_properties )
const AtomToolsFramework : : DynamicProperty & property = propertyPair . second ;
{
m_propertyValuesBeforeEdit [ property . GetId ( ) ] = property . GetValue ( ) ;
m_propertyValuesBeforeEdit [ property . GetId ( ) ] = property . GetValue ( ) ;
}
}
return true ;
} ) ;
return true ;
return true ;
}
}
@ -348,10 +339,10 @@ namespace MaterialEditor
AZ : : Name propertyId { propertyIdContext + propertyDefinition - > GetName ( ) } ;
AZ : : Name propertyId { propertyIdContext + propertyDefinition - > GetName ( ) } ;
const auto it = m_properties . find ( propertyId ) ;
const auto property = FindProperty ( propertyId ) ;
if ( it ! = m_properties . end ( ) & & propertyFilter ( it - > second ) )
if ( property & & propertyFilter ( * property ) )
{
{
AZ : : RPI : : MaterialPropertyValue propertyValue = AtomToolsFramework : : ConvertToRuntimeType ( it- > second . GetValue ( ) ) ;
AZ : : RPI : : MaterialPropertyValue propertyValue = AtomToolsFramework : : ConvertToRuntimeType ( property- > GetValue ( ) ) ;
if ( propertyValue . IsValid ( ) )
if ( propertyValue . IsValid ( ) )
{
{
if ( ! AtomToolsFramework : : ConvertToExportFormat ( m_savePathNormalized , propertyId , * propertyDefinition , propertyValue ) )
if ( ! AtomToolsFramework : : ConvertToExportFormat ( m_savePathNormalized , propertyId , * propertyDefinition , propertyValue ) )
@ -394,52 +385,17 @@ namespace MaterialEditor
// The material document and inspector are constructed from source data
// The material document and inspector are constructed from source data
if ( AzFramework : : StringFunc : : Path : : IsExtension ( m_absolutePath . c_str ( ) , AZ : : RPI : : MaterialSourceData : : Extension ) )
if ( AzFramework : : StringFunc : : Path : : IsExtension ( m_absolutePath . c_str ( ) , AZ : : RPI : : MaterialSourceData : : Extension ) )
{
{
// Load the material source data so that we can check properties and create a material asset from it
if ( ! LoadMaterialSourceData ( ) )
if ( ! AZ : : RPI : : JsonUtils : : LoadObjectFromFile ( m_absolutePath , m_materialSourceData ) )
{
AZ_Error ( " MaterialDocument " , false , " Material source data could not be loaded: '%s'. " , m_absolutePath . c_str ( ) ) ;
return OpenFailed ( ) ;
}
// We always need the absolute path for the material type and parent material to load source data and resolving
// relative paths when saving. This will convert and store them as absolute paths for use within the document.
if ( ! m_materialSourceData . m_parentMaterial . empty ( ) )
{
m_materialSourceData . m_parentMaterial =
AZ : : RPI : : AssetUtils : : ResolvePathReference ( m_absolutePath , m_materialSourceData . m_parentMaterial ) ;
}
if ( ! m_materialSourceData . m_materialType . empty ( ) )
{
m_materialSourceData . m_materialType =
AZ : : RPI : : AssetUtils : : ResolvePathReference ( m_absolutePath , m_materialSourceData . m_materialType ) ;
}
// Load the material type source data which provides the layout and default values of all of the properties
auto materialTypeOutcome = AZ : : RPI : : MaterialUtils : : LoadMaterialTypeSourceData ( m_materialSourceData . m_materialType ) ;
if ( ! materialTypeOutcome . IsSuccess ( ) )
{
{
AZ_Error ( " MaterialDocument " , false , " Material type source data could not be loaded: '%s'. " , m_materialSourceData . m_materialType . c_str ( ) ) ;
return OpenFailed ( ) ;
return OpenFailed ( ) ;
}
}
m_materialTypeSourceData = materialTypeOutcome . TakeValue ( ) ;
}
}
else if ( AzFramework : : StringFunc : : Path : : IsExtension ( m_absolutePath . c_str ( ) , AZ : : RPI : : MaterialTypeSourceData : : Extension ) )
else if ( AzFramework : : StringFunc : : Path : : IsExtension ( m_absolutePath . c_str ( ) , AZ : : RPI : : MaterialTypeSourceData : : Extension ) )
{
{
// A material document can be created or loaded from material or material type source data. If we are attempting to load
if ( ! LoadMaterialTypeSourceData ( ) )
// material type source data then the material source data object can be created just by referencing the document path as the
// material type path.
auto materialTypeOutcome = AZ : : RPI : : MaterialUtils : : LoadMaterialTypeSourceData ( m_absolutePath ) ;
if ( ! materialTypeOutcome . IsSuccess ( ) )
{
{
AZ_Error ( " MaterialDocument " , false , " Material type source data could not be loaded: '%s'. " , m_absolutePath . c_str ( ) ) ;
return OpenFailed ( ) ;
return OpenFailed ( ) ;
}
}
m_materialTypeSourceData = materialTypeOutcome . TakeValue ( ) ;
// We are storing absolute paths in the loaded version of the source data so that the files can be resolved at all times.
m_materialSourceData . m_materialType = m_absolutePath ;
m_materialSourceData . m_parentMaterial . clear ( ) ;
}
}
else
else
{
{
@ -519,58 +475,21 @@ namespace MaterialEditor
// where such changes are supported at runtime.
// where such changes are supported at runtime.
m_materialInstance - > SetPsoHandlingOverride ( AZ : : RPI : : MaterialPropertyPsoHandling : : Allowed ) ;
m_materialInstance - > SetPsoHandlingOverride ( AZ : : RPI : : MaterialPropertyPsoHandling : : Allowed ) ;
// Populate the property map from a combination of source data and assets
// Adding properties for material type and parent as part of making dynamic properties and the inspector more general purpose. This
// Assets must still be used for now because they contain the final accumulated value after all other materials
// allows the read only properties to appear in the inspector like any other property. This may change or be removed once support
// in the hierarchy are applied
// for changing the material parent is implemented.
m_materialTypeSourceData . EnumeratePropertyGroups ( [ this , & parentPropertyValues ] ( const AZStd : : string & propertyIdContext , const AZ : : RPI : : MaterialTypeSourceData : : PropertyGroup * propertyGroup )
m_groups . emplace_back ( aznew AtomToolsFramework : : DynamicPropertyGroup ) ;
{
m_groups . back ( ) - > m_name = " overview " ;
AtomToolsFramework : : DynamicPropertyConfig propertyConfig ;
m_groups . back ( ) - > m_displayName = " Overview " ;
m_groups . back ( ) - > m_description = m_materialSourceData . m_description ;
for ( const auto & propertyDefinition : propertyGroup - > GetProperties ( ) )
{
// Assign id before conversion so it can be used in dynamic description
propertyConfig . m_id = propertyIdContext + propertyGroup - > GetName ( ) + " . " + propertyDefinition - > GetName ( ) ;
const auto & propertyIndex = m_materialAsset - > GetMaterialPropertiesLayout ( ) - > FindPropertyIndex ( propertyConfig . m_id ) ;
const bool propertyIndexInBounds = propertyIndex . IsValid ( ) & & propertyIndex . GetIndex ( ) < m_materialAsset - > GetPropertyValues ( ) . size ( ) ;
AZ_Warning ( " MaterialDocument " , propertyIndexInBounds , " Failed to add material property '%s' to document '%s'. " , propertyConfig . m_id . GetCStr ( ) , m_absolutePath . c_str ( ) ) ;
if ( propertyIndexInBounds )
{
AtomToolsFramework : : ConvertToPropertyConfig ( propertyConfig , * propertyDefinition ) ;
propertyConfig . m_showThumbnail = true ;
propertyConfig . m_originalValue = AtomToolsFramework : : ConvertToEditableType ( m_materialAsset - > GetPropertyValues ( ) [ propertyIndex . GetIndex ( ) ] ) ;
propertyConfig . m_parentValue = AtomToolsFramework : : ConvertToEditableType ( parentPropertyValues [ propertyIndex . GetIndex ( ) ] ) ;
// TODO: Support populating the Material Editor with nested property groups, not just the top level.
// (Does DynamicPropertyConfig really even need m_groupName?)
propertyConfig . m_groupName = propertyGroup - > GetDisplayName ( ) ;
m_properties [ propertyConfig . m_id ] = AtomToolsFramework : : DynamicProperty ( propertyConfig ) ;
}
}
return true ;
} ) ;
// Populate the property group visibility map
// 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_materialTypeSourceData . GetPropertyLayout ( ) . m_propertyGroups )
{
m_propertyGroupVisibility [ AZ : : Name { propertyGroup - > GetName ( ) } ] = true ;
}
// Adding properties for material type and parent as part of making dynamic
// properties and the inspector more general purpose.
// This allows the read only properties to appear in the inspector like any
// other property.
// This may change or be removed once support for changing the material parent
// is implemented.
AtomToolsFramework : : DynamicPropertyConfig propertyConfig ;
AtomToolsFramework : : DynamicPropertyConfig propertyConfig ;
propertyConfig . m_dataType = AtomToolsFramework : : DynamicPropertyType : : Asset ;
propertyConfig . m_dataType = AtomToolsFramework : : DynamicPropertyType : : Asset ;
propertyConfig . m_id = " overview.materialType " ;
propertyConfig . m_id = " overview.materialType " ;
propertyConfig . m_name = " materialType " ;
propertyConfig . m_name = " materialType " ;
propertyConfig . m_displayName = " Material Type " ;
propertyConfig . m_displayName = " Material Type " ;
propertyConfig . m_groupName = " Overview " ;
propertyConfig . m_groupName = " overview " ;
propertyConfig . m_groupDisplayName = " Overview " ;
propertyConfig . m_description = " The material type defines the layout, properties, default values, shader connections, and other "
propertyConfig . m_description = " The material type defines the layout, properties, default values, shader connections, and other "
" data needed to create and edit a derived material. " ;
" data needed to create and edit a derived material. " ;
propertyConfig . m_defaultValue = AZStd : : any ( materialTypeAsset ) ;
propertyConfig . m_defaultValue = AZStd : : any ( materialTypeAsset ) ;
@ -578,14 +497,15 @@ namespace MaterialEditor
propertyConfig . m_parentValue = propertyConfig . m_defaultValue ;
propertyConfig . m_parentValue = propertyConfig . m_defaultValue ;
propertyConfig . m_readOnly = true ;
propertyConfig . m_readOnly = true ;
m_ properties[ propertyConfig . m_id ] = AtomToolsFramework : : DynamicProperty ( propertyConfig ) ;
m_ groups. back ( ) - > m_properties . push_back ( AtomToolsFramework : : DynamicProperty ( propertyConfig ) ) ;
propertyConfig = { } ;
propertyConfig = { } ;
propertyConfig . m_dataType = AtomToolsFramework : : DynamicPropertyType : : Asset ;
propertyConfig . m_dataType = AtomToolsFramework : : DynamicPropertyType : : Asset ;
propertyConfig . m_id = " overview.parentMaterial " ;
propertyConfig . m_id = " overview.parentMaterial " ;
propertyConfig . m_name = " parentMaterial " ;
propertyConfig . m_name = " parentMaterial " ;
propertyConfig . m_displayName = " Parent Material " ;
propertyConfig . m_displayName = " Parent Material " ;
propertyConfig . m_groupName = " Overview " ;
propertyConfig . m_groupName = " overview " ;
propertyConfig . m_groupDisplayName = " Overview " ;
propertyConfig . m_description =
propertyConfig . m_description =
" The parent material provides an initial configuration whose properties are inherited and overriden by a derived material. " ;
" The parent material provides an initial configuration whose properties are inherited and overriden by a derived material. " ;
propertyConfig . m_defaultValue = AZStd : : any ( parentMaterialAsset ) ;
propertyConfig . m_defaultValue = AZStd : : any ( parentMaterialAsset ) ;
@ -594,9 +514,14 @@ namespace MaterialEditor
propertyConfig . m_readOnly = true ;
propertyConfig . m_readOnly = true ;
propertyConfig . m_showThumbnail = true ;
propertyConfig . m_showThumbnail = true ;
m_properties [ propertyConfig . m_id ] = AtomToolsFramework : : DynamicProperty ( propertyConfig ) ;
m_groups . back ( ) - > m_properties . push_back ( AtomToolsFramework : : DynamicProperty ( propertyConfig ) ) ;
m_groups . emplace_back ( aznew AtomToolsFramework : : DynamicPropertyGroup ) ;
m_groups . back ( ) - > m_name = UvGroupName ;
m_groups . back ( ) - > m_displayName = " UV Sets " ;
m_groups . back ( ) - > m_description = " UV set names in this material, which can be renamed to match those in the model. " ;
//Add UV name customization properties
// Add UV name customization properties
const AZ : : RPI : : MaterialUvNameMap & uvNameMap = materialTypeAsset - > GetUvNameMap ( ) ;
const AZ : : RPI : : MaterialUvNameMap & uvNameMap = materialTypeAsset - > GetUvNameMap ( ) ;
for ( const AZ : : RPI : : UvNamePair & uvNamePair : uvNameMap )
for ( const AZ : : RPI : : UvNamePair & uvNamePair : uvNameMap )
{
{
@ -608,61 +533,69 @@ namespace MaterialEditor
propertyConfig . m_id = AZ : : RPI : : MaterialPropertyId ( UvGroupName , shaderInput ) ;
propertyConfig . m_id = AZ : : RPI : : MaterialPropertyId ( UvGroupName , shaderInput ) ;
propertyConfig . m_name = shaderInput ;
propertyConfig . m_name = shaderInput ;
propertyConfig . m_displayName = shaderInput ;
propertyConfig . m_displayName = shaderInput ;
propertyConfig . m_groupName = " UV Sets " ;
propertyConfig . m_groupName = UvGroupName ;
propertyConfig . m_groupDisplayName = " UV Sets " ;
propertyConfig . m_description = shaderInput ;
propertyConfig . m_description = shaderInput ;
propertyConfig . m_defaultValue = uvName ;
propertyConfig . m_defaultValue = uvName ;
propertyConfig . m_originalValue = uvName ;
propertyConfig . m_originalValue = uvName ;
propertyConfig . m_parentValue = uvName ;
propertyConfig . m_parentValue = uvName ;
propertyConfig . m_readOnly = true ;
propertyConfig . m_readOnly = true ;
m_ properties[ propertyConfig . m_id ] = AtomToolsFramework : : DynamicProperty ( propertyConfig ) ;
m_ groups. back ( ) - > m_properties . push_back ( AtomToolsFramework : : DynamicProperty ( propertyConfig ) ) ;
}
}
// Add material functors that are in the top-level functors list.
// Populate the property map from a combination of source data and assets
const AZ : : RPI : : MaterialFunctorSourceData : : EditorContext editorContext =
// Assets must still be used for now because they contain the final accumulated value after all other materials
AZ : : RPI : : MaterialFunctorSourceData : : EditorContext ( m_materialSourceData . m_materialType , m_materialAsset - > GetMaterialPropertiesLayout ( ) ) ;
// in the hierarchy are applied
for ( AZ : : RPI : : Ptr < AZ : : RPI : : MaterialFunctorSourceDataHolder > functorData : m_materialTypeSourceData . m_materialFunctorSourceData )
bool enumerateResult = m_materialTypeSourceData . EnumeratePropertyGroups (
{
[ this , & parentPropertyValues ] (
AZ : : RPI : : MaterialFunctorSourceData : : FunctorResult result2 = functorData - > CreateFunctor ( editorContext ) ;
const AZStd : : string & propertyIdContext , const AZ : : RPI : : MaterialTypeSourceData : : PropertyGroup * propertyGroup )
if ( result2 . IsSuccess ( ) )
{
{
AZ : : RPI : : Ptr < AZ : : RPI : : MaterialFunctor > & functor = result2 . GetValue ( ) ;
// Add any material functors that are located inside each property group.
if ( functor ! = nullptr )
if ( ! AddEditorMaterialFunctors ( propertyGroup - > GetFunctors ( ) ) )
{
{
m_editorFunctors . push_back ( functor ) ;
return false ;
}
}
}
else
{
AZ_Error ( " MaterialDocument " , false , " Material functors were not created: '%s'. " , m_absolutePath . c_str ( ) ) ;
return OpenFailed ( ) ;
}
}
// Add any material functors that are located inside each property group.
m_groups . emplace_back ( aznew AtomToolsFramework : : DynamicPropertyGroup ) ;
bool enumerateResult = m_materialTypeSourceData . EnumeratePropertyGroups (
m_groups . back ( ) - > m_name = propertyIdContext + propertyGroup - > GetName ( ) ;
[ this ] ( const AZStd : : string & , const AZ : : RPI : : MaterialTypeSourceData : : PropertyGroup * propertyGroup )
m_groups . back ( ) - > m_displayName = propertyGroup - > GetDisplayName ( ) ;
{
m_groups . back ( ) - > m_description = propertyGroup - > GetDescription ( ) ;
const AZ : : RPI : : MaterialFunctorSourceData : : EditorContext editorContext = AZ : : RPI : : MaterialFunctorSourceData : : EditorContext (
m_materialSourceData . m_materialType , m_materialAsset - > GetMaterialPropertiesLayout ( ) ) ;
for ( AZ : : RPI : : Ptr < AZ : : RPI : : MaterialFunctorSourceDataHolder > functorData : propertyGroup - > GetFunctor s( ) )
for ( const auto & propertyDefinition : propertyGroup - > GetProperties ( ) )
{
{
AZ : : RPI : : MaterialFunctorSourceData : : FunctorResult result = functorData - > CreateFunctor ( editorContext ) ;
// Assign id before conversion so it can be used in dynamic description
AtomToolsFramework : : DynamicPropertyConfig propertyConfig ;
propertyConfig . m_id = m_groups . back ( ) - > m_name + " . " + propertyDefinition - > GetName ( ) ;
const auto & propertyIndex = m_materialAsset - > GetMaterialPropertiesLayout ( ) - > FindPropertyIndex ( propertyConfig . m_id ) ;
const bool propertyIndexInBounds =
propertyIndex . IsValid ( ) & & propertyIndex . GetIndex ( ) < m_materialAsset - > GetPropertyValues ( ) . size ( ) ;
AZ_Warning (
" MaterialDocument " , propertyIndexInBounds , " Failed to add material property '%s' to document '%s'. " ,
propertyConfig . m_id . GetCStr ( ) , m_absolutePath . c_str ( ) ) ;
if ( result . IsSuccess ( ) )
if ( propertyIndexInBounds )
{
{
AZ : : RPI : : Ptr < AZ : : RPI : : MaterialFunctor > & functor = result . GetValue ( ) ;
AtomToolsFramework : : ConvertToPropertyConfig ( propertyConfig , * propertyDefinition ) ;
if ( functor ! = nullptr )
// TODO: Support populating the Material Editor with nested property groups, not just the top level.
// (Does DynamicPropertyConfig really even need m_groupDisplayName?)
propertyConfig . m_groupName = m_groups . back ( ) - > m_name ;
propertyConfig . m_groupDisplayName = m_groups . back ( ) - > m_displayName ;
propertyConfig . m_showThumbnail = true ;
propertyConfig . m_originalValue =
AtomToolsFramework : : ConvertToEditableType ( m_materialAsset - > GetPropertyValues ( ) [ propertyIndex . GetIndex ( ) ] ) ;
propertyConfig . m_parentValue =
AtomToolsFramework : : ConvertToEditableType ( parentPropertyValues [ propertyIndex . GetIndex ( ) ] ) ;
propertyConfig . m_dataChangeCallback = [ documentId = m_id , propertyId = propertyConfig . m_id ] ( const AZStd : : any & value )
{
{
m_editorFunctors . push_back ( functor ) ;
MaterialDocumentRequestBus : : Event (
}
documentId , & MaterialDocumentRequestBus : : Events : : SetPropertyValue , propertyId , value ) ;
}
return AZ : : Edit : : PropertyRefreshLevels : : AttributesAndValues ;
else
} ;
{
AZ_Error ( " MaterialDocument " , false , " Material functors were not created: '%s'. " , m_absolutePath . c_str ( ) ) ;
m_groups . back ( ) - > m_properties . push_back ( AtomToolsFramework : : DynamicProperty ( propertyConfig ) ) ;
return false ;
}
}
}
}
@ -674,6 +607,12 @@ namespace MaterialEditor
return OpenFailed ( ) ;
return OpenFailed ( ) ;
}
}
// Add material functors that are in the top-level functors list.
if ( ! AddEditorMaterialFunctors ( m_materialTypeSourceData . m_materialFunctorSourceData ) )
{
return OpenFailed ( ) ;
}
AZ : : RPI : : MaterialPropertyFlags dirtyFlags ;
AZ : : RPI : : MaterialPropertyFlags dirtyFlags ;
dirtyFlags . set ( ) ; // Mark all properties as dirty since we just loaded the material and need to initialize property visibility
dirtyFlags . set ( ) ; // Mark all properties as dirty since we just loaded the material and need to initialize property visibility
RunEditorMaterialFunctors ( dirtyFlags ) ;
RunEditorMaterialFunctors ( dirtyFlags ) ;
@ -681,17 +620,35 @@ namespace MaterialEditor
return OpenSucceeded ( ) ;
return OpenSucceeded ( ) ;
}
}
void MaterialDocument : : Clear ( )
{
AtomToolsFramework : : AtomToolsDocument : : Clear ( ) ;
AZ : : TickBus : : Handler : : BusDisconnect ( ) ;
m_materialAsset = { } ;
m_materialInstance = { } ;
m_compilePending = { } ;
m_groups . clear ( ) ;
m_editorFunctors . clear ( ) ;
m_materialTypeSourceData = AZ : : RPI : : MaterialTypeSourceData ( ) ;
m_materialSourceData = AZ : : RPI : : MaterialSourceData ( ) ;
m_propertyValuesBeforeEdit . clear ( ) ;
}
bool MaterialDocument : : ReopenRecordState ( )
bool MaterialDocument : : ReopenRecordState ( )
{
{
m_propertyValuesBeforeReopen . clear ( ) ;
m_propertyValuesBeforeReopen . clear ( ) ;
for ( const auto & propertyPair : m_properties )
TraverseGroups ( m_groups , [ this ] ( auto & group ) {
{
for ( auto & property : group - > m_properties )
const AtomToolsFramework : : DynamicProperty & property = propertyPair . second ;
if ( ! AtomToolsFramework : : ArePropertyValuesEqual ( property . GetValue ( ) , property . GetConfig ( ) . m_parentValue ) )
{
{
m_propertyValuesBeforeReopen [ property . GetId ( ) ] = property . GetValue ( ) ;
if ( ! AtomToolsFramework : : ArePropertyValuesEqual ( property . GetValue ( ) , property . GetConfig ( ) . m_parentValue ) )
{
m_propertyValuesBeforeReopen [ property . GetId ( ) ] = property . GetValue ( ) ;
}
}
}
}
return true ;
} ) ;
return AtomToolsDocument : : ReopenRecordState ( ) ;
return AtomToolsDocument : : ReopenRecordState ( ) ;
}
}
@ -711,20 +668,59 @@ namespace MaterialEditor
}
}
}
}
void MaterialDocument : : Clear ( )
bool MaterialDocument : : LoadMaterialSourceData ( )
{
{
AtomToolsFramework : : AtomToolsDocument : : Clear ( ) ;
// Load the material source data so that we can check properties and create a material asset from it
if ( ! AZ : : RPI : : JsonUtils : : LoadObjectFromFile ( m_absolutePath , m_materialSourceData ) )
{
AZ_Error ( " MaterialDocument " , false , " Material source data could not be loaded: '%s'. " , m_absolutePath . c_str ( ) ) ;
return false ;
}
AZ : : TickBus : : Handler : : BusDisconnect ( ) ;
// We always need the absolute path for the material type and parent material to load source data and resolving
// relative paths when saving. This will convert and store them as absolute paths for use within the document.
if ( ! m_materialSourceData . m_parentMaterial . empty ( ) )
{
m_materialSourceData . m_parentMaterial =
AZ : : RPI : : AssetUtils : : ResolvePathReference ( m_absolutePath , m_materialSourceData . m_parentMaterial ) ;
}
m_materialAsset = { } ;
if ( ! m_materialSourceData . m_materialType . empty ( ) )
m_materialInstance = { } ;
{
m_compilePending = { } ;
m_materialSourceData . m_materialType =
m_properties . clear ( ) ;
AZ : : RPI : : AssetUtils : : ResolvePathReference ( m_absolutePath , m_materialSourceData . m_materialType ) ;
m_editorFunctors . clear ( ) ;
}
m_materialTypeSourceData = AZ : : RPI : : MaterialTypeSourceData ( ) ;
m_materialSourceData = AZ : : RPI : : MaterialSourceData ( ) ;
// Load the material type source data which provides the layout and default values of all of the properties
m_propertyValuesBeforeEdit . clear ( ) ;
auto materialTypeOutcome = AZ : : RPI : : MaterialUtils : : LoadMaterialTypeSourceData ( m_materialSourceData . m_materialType ) ;
if ( ! materialTypeOutcome . IsSuccess ( ) )
{
AZ_Error ( " MaterialDocument " , false , " Material type source data could not be loaded: '%s'. " , m_materialSourceData . m_materialType . c_str ( ) ) ;
return false ;
}
m_materialTypeSourceData = materialTypeOutcome . TakeValue ( ) ;
return true ;
}
bool MaterialDocument : : LoadMaterialTypeSourceData ( )
{
// A material document can be created or loaded from material or material type source data. If we are attempting to load
// material type source data then the material source data object can be created just by referencing the document path as the
// material type path.
auto materialTypeOutcome = AZ : : RPI : : MaterialUtils : : LoadMaterialTypeSourceData ( m_absolutePath ) ;
if ( ! materialTypeOutcome . IsSuccess ( ) )
{
AZ_Error ( " MaterialDocument " , false , " Material type source data could not be loaded: '%s'. " , m_absolutePath . c_str ( ) ) ;
return false ;
}
m_materialTypeSourceData = materialTypeOutcome . TakeValue ( ) ;
// We are storing absolute paths in the loaded version of the source data so that the files can be resolved at all times.
m_materialSourceData . m_materialType = m_absolutePath ;
m_materialSourceData . m_parentMaterial . clear ( ) ;
return true ;
}
}
void MaterialDocument : : RestorePropertyValues ( const PropertyValueMap & propertyValues )
void MaterialDocument : : RestorePropertyValues ( const PropertyValueMap & propertyValues )
@ -737,60 +733,186 @@ namespace MaterialEditor
}
}
}
}
MaterialDocument : : EditorMaterialFunctorResult MaterialDocument : : RunEditorMaterialFunctors ( AZ : : RPI : : MaterialPropertyFlags dirtyFlags )
bool MaterialDocument : : AddEditorMaterialFunctors (
const AZStd : : vector < AZ : : RPI : : Ptr < AZ : : RPI : : MaterialFunctorSourceDataHolder > > & functorSourceDataHolders )
{
{
EditorMaterialFunctorResult result ;
const AZ : : RPI : : MaterialFunctorSourceData : : EditorContext editorContext = AZ : : RPI : : MaterialFunctorSourceData : : EditorContext (
m_materialSourceData . m_materialType , m_materialAsset - > GetMaterialPropertiesLayout ( ) ) ;
AZStd : : unordered_map < AZ : : Name , AZ : : RPI : : MaterialPropertyDynamicMetadata > propertyDynamicMetadata ;
for ( AZ : : RPI : : Ptr < AZ : : RPI : : MaterialFunctorSourceDataHolder > functorData : functorSourceDataHolders )
AZStd : : unordered_map < AZ : : Name , AZ : : RPI : : MaterialPropertyGroupDynamicMetadata > propertyGroupDynamicMetadata ;
for ( auto & propertyPair : m_properties )
{
AtomToolsFramework : : DynamicProperty & property = propertyPair . second ;
AtomToolsFramework : : ConvertToPropertyMetaData ( propertyDynamicMetadata [ property . GetId ( ) ] , property . GetConfig ( ) ) ;
}
for ( auto & groupPair : m_propertyGroupVisibility )
{
{
AZ : : RPI : : Material PropertyGroupDynamicMetadata& metadata = propertyGroupDynamicMetadata [ AZ : : Name { groupPair . first } ] ;
AZ : : RPI : : MaterialFunctorSourceData : : FunctorResult result = functorData - > CreateFunctor ( editorContext ) ;
bool visible = groupPair . second ;
if ( result . IsSuccess ( ) )
metadata . m_visibility = visible ?
{
AZ : : RPI : : MaterialPropertyGroupVisibility : : Enabled : AZ : : RPI : : MaterialPropertyGroupVisibility : : Hidden ;
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_absolutePath . c_str ( ) ) ;
return false ;
}
}
}
return true ;
}
void MaterialDocument : : RunEditorMaterialFunctors ( AZ : : RPI : : MaterialPropertyFlags dirtyFlags )
{
AZStd : : unordered_map < AZ : : Name , AZ : : RPI : : MaterialPropertyDynamicMetadata > propertyDynamicMetadata ;
AZStd : : unordered_map < AZ : : Name , AZ : : RPI : : MaterialPropertyGroupDynamicMetadata > propertyGroupDynamicMetadata ;
TraverseGroups ( m_groups , [ & ] ( auto & group ) {
AZ : : RPI : : MaterialPropertyGroupDynamicMetadata & metadata = propertyGroupDynamicMetadata [ AZ : : Name { group - > m_name } ] ;
metadata . m_visibility = group - > m_visible ? AZ : : RPI : : MaterialPropertyGroupVisibility : : Enabled : AZ : : RPI : : MaterialPropertyGroupVisibility : : Hidden ;
for ( auto & property : group - > m_properties )
{
AtomToolsFramework : : ConvertToPropertyMetaData ( propertyDynamicMetadata [ property . GetId ( ) ] , property . GetConfig ( ) ) ;
}
return true ;
} ) ;
AZStd : : unordered_set < AZ : : Name > updatedProperties ;
AZStd : : unordered_set < AZ : : Name > updatedPropertyGroups ;
for ( AZ : : RPI : : Ptr < AZ : : RPI : : MaterialFunctor > & functor : m_editorFunctors )
for ( AZ : : RPI : : Ptr < AZ : : RPI : : MaterialFunctor > & functor : m_editorFunctors )
{
{
const AZ : : RPI : : MaterialPropertyFlags & materialPropertyDependencies = functor - > GetMaterialPropertyDependencies ( ) ;
const AZ : : RPI : : MaterialPropertyFlags & materialPropertyDependencies = functor - > GetMaterialPropertyDependencies ( ) ;
// None also covers case that the client code doesn't register material properties to dependencies,
// None also covers case that the client code doesn't register material properties to dependencies,
// which will later get caught in Process() when trying to access a property.
// which will later get caught in Process() when trying to access a property.
if ( materialPropertyDependencies . none ( ) | | functor - > NeedsProcess ( dirtyFlags ) )
if ( materialPropertyDependencies . none ( ) | | functor - > NeedsProcess ( dirtyFlags ) )
{
{
AZ : : RPI : : MaterialFunctor : : EditorContext context = AZ : : RPI : : MaterialFunctor : : EditorContext (
AZ : : RPI : : MaterialFunctor : : EditorContext context = AZ : : RPI : : MaterialFunctor : : EditorContext (
m_materialInstance - > GetPropertyValues ( ) ,
m_materialInstance - > GetPropertyValues ( ) , m_materialInstance - > GetMaterialPropertiesLayout ( ) , propertyDynamicMetadata ,
m_materialInstance - > GetMaterialPropertiesLayout ( ) ,
propertyGroupDynamicMetadata , updatedProperties , updatedPropertyGroups ,
propertyDynamicMetadata ,
& materialPropertyDependencies ) ;
propertyGroupDynamicMetadata ,
result . m_updatedProperties ,
result . m_updatedPropertyGroups ,
& materialPropertyDependencies
) ;
functor - > Process ( context ) ;
functor - > Process ( context ) ;
}
}
}
}
for ( auto & propertyPair : m_properties )
TraverseGroups ( m_groups , [ & ] ( auto & group ) {
bool groupChange = false ;
bool groupRebuilt = false ;
if ( updatedPropertyGroups . find ( AZ : : Name ( group - > m_name ) ) ! = updatedPropertyGroups . end ( ) )
{
AZ : : RPI : : MaterialPropertyGroupDynamicMetadata & metadata = propertyGroupDynamicMetadata [ AZ : : Name { group - > m_name } ] ;
group - > m_visible = metadata . m_visibility ! = AZ : : RPI : : MaterialPropertyGroupVisibility : : Hidden ;
groupChange = true ;
}
for ( auto & property : group - > m_properties )
{
if ( updatedProperties . find ( AZ : : Name ( property . GetId ( ) ) ) ! = updatedProperties . end ( ) )
{
const bool visibleBefore = property . GetConfig ( ) . m_visible ;
AtomToolsFramework : : DynamicPropertyConfig propertyConfig = property . GetConfig ( ) ;
AtomToolsFramework : : ConvertToPropertyConfig ( propertyConfig , propertyDynamicMetadata [ property . GetId ( ) ] ) ;
property . SetConfig ( propertyConfig ) ;
groupChange = true ;
groupRebuilt | = visibleBefore ! = property . GetConfig ( ) . m_visible ;
}
}
if ( groupChange | | groupRebuilt )
{
AtomToolsFramework : : AtomToolsDocumentNotificationBus : : Broadcast (
& AtomToolsFramework : : AtomToolsDocumentNotificationBus : : Events : : OnDocumentObjectInfoChanged , m_id ,
GetObjectInfoFromDynamicPropertyGroup ( group . get ( ) ) , groupRebuilt ) ;
}
return true ;
} ) ;
}
AtomToolsFramework : : DocumentObjectInfo MaterialDocument : : GetObjectInfoFromDynamicPropertyGroup (
const AtomToolsFramework : : DynamicPropertyGroup * group ) const
{
AtomToolsFramework : : DocumentObjectInfo objectInfo ;
objectInfo . m_visible = group - > m_visible ;
objectInfo . m_name = group - > m_name ;
objectInfo . m_displayName = group - > m_displayName ;
objectInfo . m_description = group - > m_description ;
objectInfo . m_objectType = azrtti_typeid < AtomToolsFramework : : DynamicPropertyGroup > ( ) ;
objectInfo . m_objectPtr = const_cast < AtomToolsFramework : : DynamicPropertyGroup * > ( group ) ;
return objectInfo ;
}
bool MaterialDocument : : TraverseGroups (
AZStd : : vector < AZStd : : shared_ptr < AtomToolsFramework : : DynamicPropertyGroup > > & groups ,
AZStd : : function < bool ( AZStd : : shared_ptr < AtomToolsFramework : : DynamicPropertyGroup > & ) > callback )
{
if ( ! callback )
{
{
AtomToolsFramework : : DynamicProperty & property = propertyPair . second ;
return false ;
AtomToolsFramework : : DynamicPropertyConfig propertyConfig = property . GetConfig ( ) ;
AtomToolsFramework : : ConvertToPropertyConfig ( propertyConfig , propertyDynamicMetadata [ property . GetId ( ) ] ) ;
property . SetConfig ( propertyConfig ) ;
}
}
for ( auto & updatedPropertyGroup : result . m_updatedPropertyGroups )
for ( auto & group : groups )
{
{
bool visible = propertyGroupDynamicMetadata [ updatedPropertyGroup ] . m_visibility = = AZ : : RPI : : MaterialPropertyGroupVisibility : : Enabled ;
if ( ! callback ( group ) | | ! TraverseGroups ( group - > m_groups , callback ) )
m_propertyGroupVisibility [ updatedPropertyGroup ] = visible ;
{
return false ;
}
}
return true ;
}
bool MaterialDocument : : TraverseGroups (
const AZStd : : vector < AZStd : : shared_ptr < AtomToolsFramework : : DynamicPropertyGroup > > & groups ,
AZStd : : function < bool ( const AZStd : : shared_ptr < AtomToolsFramework : : DynamicPropertyGroup > & ) > callback ) const
{
if ( ! callback )
{
return false ;
}
}
for ( auto & group : groups )
{
if ( ! callback ( group ) | | ! TraverseGroups ( group - > m_groups , callback ) )
{
return false ;
}
}
return true ;
}
AtomToolsFramework : : DynamicProperty * MaterialDocument : : FindProperty ( const AZ : : Name & propertyId )
{
AtomToolsFramework : : DynamicProperty * result = nullptr ;
TraverseGroups ( m_groups , [ & ] ( auto & group ) {
for ( auto & property : group - > m_properties )
{
if ( property . GetId ( ) = = propertyId )
{
result = & property ;
return false ;
}
}
return true ;
} ) ;
return result ;
}
const AtomToolsFramework : : DynamicProperty * MaterialDocument : : FindProperty ( const AZ : : Name & propertyId ) const
{
AtomToolsFramework : : DynamicProperty * result = nullptr ;
TraverseGroups ( m_groups , [ & ] ( auto & group ) {
for ( auto & property : group - > m_properties )
{
if ( property . GetId ( ) = = propertyId )
{
result = & property ;
return false ;
}
}
return true ;
} ) ;
return result ;
return result ;
}
}
} // namespace MaterialEditor
} // namespace MaterialEditor