Overhauled the .materialtype file format to group all related properties data together. This prepares the way for a number of possible improvements, especially unlocking the ability to factor out material type configuration to be shared by multiple material types.
Here we formalize the concept of a Property Set, which replaces property "groups", containing the group name and description, properties, and functors all in one place. The Property Set structure will allow arbitrarily deep nesting, whereas before you only had one level of grouping. This nesting is not fully supported yet throughout the system, particularly in the Material Editor. It was easier to go ahead and put in some of the nesting mechanims, parituclar in the implementation of MaterialTypeSourceData. This change is backward compatible, which is proved with unit tests, and by the fact that only MinimalPBR.materialtype has been updated to the new format. StandardPBR, EnhancedPBR, and others are still using the old format. (In a subsequent commit I'll update these as well, to prove that the new format works correctly). Other changes and improvements... - A new constructor for MaterialPropertyId - Improved API for MaterialTypeSourceData that hides a good deal more of it's data as private, with clear and convenient APIs. Especially AddProperty, AddPropertySet, FindProperty, FindPropertySet, EnumerateProperties, EnumeratePropertySets. - Added lots of new unit tests - Updated MinimalPBR.materialtype to the new format. Testing: - Updated unit tests. - Reprocessed Atom material assets. - Ran AtomSampleViewer material screenshot test. - Opened, edited, saved material in the Material Editor. - Opened a level, edited material property overrides, saved and reloaded. Signed-off-by: santorac <55155825+santorac@users.noreply.github.com>
This commit is contained in:
@@ -35,6 +35,7 @@ namespace AZ
|
||||
MaterialPropertyId(AZStd::string_view groupName, AZStd::string_view propertyName);
|
||||
MaterialPropertyId(const Name& groupName, const Name& propertyName);
|
||||
MaterialPropertyId(const AZStd::array_view<AZStd::string> names);
|
||||
MaterialPropertyId(const AZStd::array_view<AZStd::string> groupNames, AZStd::string_view propertyName);
|
||||
|
||||
AZ_DEFAULT_COPY_MOVE(MaterialPropertyId);
|
||||
|
||||
@@ -44,6 +45,9 @@ namespace AZ
|
||||
//! This is included for convenience so it can be used for error messages in the same way an AZ::Name is used.
|
||||
const char* GetCStr() const;
|
||||
|
||||
//! Wraps Name::GetStringView() for convenience.
|
||||
AZStd::string_view GetStringView() const;
|
||||
|
||||
//! Returns a hash of the full name. This is needed for compatibility with NameIdReflectionMap.
|
||||
Name::Hash GetHash() const;
|
||||
|
||||
|
||||
@@ -68,8 +68,9 @@ namespace AZ
|
||||
|
||||
struct PropertyDefinition
|
||||
{
|
||||
AZ_CLASS_ALLOCATOR(PropertyDefinition, SystemAllocator, 0);
|
||||
AZ_TYPE_INFO(AZ::RPI::MaterialTypeSourceData::PropertyDefinition, "{E0DB3C0D-75DB-4ADB-9E79-30DA63FA18B7}");
|
||||
|
||||
|
||||
static const float DefaultMin;
|
||||
static const float DefaultMax;
|
||||
static const float DefaultStep;
|
||||
@@ -117,68 +118,159 @@ namespace AZ
|
||||
AZStd::unordered_map<Name/*shaderOption*/, Name/*value*/> m_shaderOptionValues;
|
||||
};
|
||||
|
||||
using PropertyList = AZStd::vector<PropertyDefinition>;
|
||||
using PropertyList = AZStd::vector<AZStd::unique_ptr<PropertyDefinition>>;
|
||||
|
||||
struct PropertySet
|
||||
{
|
||||
friend class MaterialTypeSourceData;
|
||||
|
||||
AZ_CLASS_ALLOCATOR(PropertySet, SystemAllocator, 0);
|
||||
AZ_TYPE_INFO(AZ::RPI::MaterialTypeSourceData::PropertySet, "{BA3AA0E4-C74D-4FD0-ADB2-00B060F06314}");
|
||||
|
||||
public:
|
||||
|
||||
PropertySet() = default;
|
||||
AZ_DISABLE_COPY(PropertySet)
|
||||
|
||||
const AZStd::string& GetName() const { return m_name; }
|
||||
const AZStd::string& GetDisplayName() const { return m_displayName; }
|
||||
const AZStd::string& GetDescription() const { return m_description; }
|
||||
const PropertyList& GetProperties() const { return m_properties; }
|
||||
const AZStd::vector<AZStd::unique_ptr<PropertySet>>& GetPropertySets() const { return m_propertySets; }
|
||||
const AZStd::vector<Ptr<MaterialFunctorSourceDataHolder>>& GetFunctors() const { return m_materialFunctorSourceData; }
|
||||
|
||||
void SetDisplayName(AZStd::string_view displayName) { m_displayName = displayName; }
|
||||
void SetDescription(AZStd::string_view description) { m_description = description; }
|
||||
|
||||
PropertyDefinition* AddProperty(AZStd::string_view name);
|
||||
PropertySet* AddPropertySet(AZStd::string_view name);
|
||||
|
||||
private:
|
||||
|
||||
static PropertySet* AddPropertySet(AZStd::string_view name, AZStd::vector<AZStd::unique_ptr<PropertySet>>& toPropertySetList);
|
||||
|
||||
AZStd::string m_name;
|
||||
AZStd::string m_displayName;
|
||||
AZStd::string m_description;
|
||||
PropertyList m_properties;
|
||||
AZStd::vector<AZStd::unique_ptr<PropertySet>> m_propertySets;
|
||||
AZStd::vector<Ptr<MaterialFunctorSourceDataHolder>> m_materialFunctorSourceData;
|
||||
};
|
||||
|
||||
|
||||
struct PropertyLayout
|
||||
{
|
||||
AZ_TYPE_INFO(AZ::RPI::MaterialTypeSourceData::PropertyLayout, "{AE53CF3F-5C3B-44F5-B2FB-306F0EB06393}");
|
||||
|
||||
PropertyLayout() = default;
|
||||
AZ_DISABLE_COPY(PropertyLayout)
|
||||
|
||||
//! Indicates the version of the set of available properties. Can be used to detect materials that might need to be updated.
|
||||
uint32_t m_version = 0;
|
||||
|
||||
//! [Deprecated] Use m_propertySets instead
|
||||
//! List of groups that will contain the available properties
|
||||
AZStd::vector<GroupDefinition> m_groups;
|
||||
|
||||
//! [Deprecated] Use m_propertySets instead
|
||||
//! Collection of all available user-facing properties
|
||||
AZStd::map<AZStd::string /*group name*/, PropertyList> m_properties;
|
||||
AZStd::map<AZStd::string /*group name*/, AZStd::vector<PropertyDefinition>> m_properties;
|
||||
|
||||
AZStd::vector<AZStd::unique_ptr<PropertySet>> m_propertySets;
|
||||
};
|
||||
|
||||
PropertySet* AddPropertySet(AZStd::string_view propertySetId);
|
||||
//PropertySet* AddPropertySet(AZStd::string_view parentPropertySetId, AZStd::string_view name);
|
||||
PropertyDefinition* AddProperty(AZStd::string_view propertyId);
|
||||
//PropertyDefinition* AddProperty(AZStd::string_view parentPropertySetId, AZStd::string_view name);
|
||||
|
||||
AZStd::string m_description;
|
||||
const PropertyLayout& GetPropertyLayout() const { return m_propertyLayout; }
|
||||
|
||||
PropertyLayout m_propertyLayout;
|
||||
AZStd::string m_description; //< TODO: Make this private
|
||||
|
||||
//! A list of shader variants that are always used at runtime; they cannot be turned off
|
||||
AZStd::vector<ShaderVariantReferenceData> m_shaderCollection;
|
||||
AZStd::vector<ShaderVariantReferenceData> m_shaderCollection; //< TODO: Make this private
|
||||
|
||||
//! Material functors provide custom logic and calculations to configure shaders, render states, and more. See MaterialFunctor.h for details.
|
||||
AZStd::vector<Ptr<MaterialFunctorSourceDataHolder>> m_materialFunctorSourceData;
|
||||
AZStd::vector<Ptr<MaterialFunctorSourceDataHolder>> m_materialFunctorSourceData; //< TODO: Make this private
|
||||
|
||||
//! Override names for UV input in the shaders of this material type.
|
||||
//! Using ordered map to sort names on loading.
|
||||
using UvNameMap = AZStd::map<AZStd::string, AZStd::string>;
|
||||
UvNameMap m_uvNameMap;
|
||||
UvNameMap m_uvNameMap; //< TODO: Make this private
|
||||
|
||||
//! Copy over UV custom names to the properties enum values.
|
||||
void ResolveUvEnums();
|
||||
|
||||
const PropertySet* FindPropertySet(AZStd::string_view propertySetId) const;
|
||||
|
||||
const GroupDefinition* FindGroup(AZStd::string_view groupName) const;
|
||||
const PropertyDefinition* FindProperty(AZStd::string_view propertyId) const;
|
||||
|
||||
const PropertyDefinition* FindProperty(AZStd::string_view groupName, AZStd::string_view propertyName) const;
|
||||
//! Tokenizes an ID string like "itemA.itemB.itemC" into a vector like ["itemA", "itemB", "itemC"]
|
||||
static AZStd::vector<AZStd::string_view> TokenizeId(AZStd::string_view id);
|
||||
|
||||
//! Splits an ID string like "itemA.itemB.itemC" into a vector like ["itemA.itemB", "itemC"]
|
||||
static AZStd::vector<AZStd::string_view> SplitId(AZStd::string_view id);
|
||||
|
||||
//! Construct a complete list of group definitions, including implicit groups, arranged in the same order as the source data
|
||||
//! Groups with the same name will be consolidated into a single entry
|
||||
AZStd::vector<GroupDefinition> GetGroupDefinitionsInDisplayOrder() const;
|
||||
//! Call back function type used with the enumeration functions
|
||||
using EnumeratePropertySetsCallback = AZStd::function<bool(
|
||||
const AZStd::string&, // The property ID context (i.e. "levelA.levelB."
|
||||
const PropertySet* // the next property set in the tree
|
||||
)>;
|
||||
//! Recursively traverses all of the property sets contained in the material type, executing a callback function for each.
|
||||
//! @return false if the enumeration was terminated early by the callback returning false.
|
||||
bool EnumeratePropertySets(const EnumeratePropertySetsCallback& callback) const;
|
||||
|
||||
//! Call back function type used with the numeration functions
|
||||
using EnumeratePropertiesCallback = AZStd::function<bool(
|
||||
const AZStd::string&, // The name of the group containing the property
|
||||
const AZStd::string&, // The name of the property
|
||||
const PropertyDefinition& // the property definition object that corresponds to the group and property names
|
||||
const AZStd::string&, // The property ID context (i.e. "levelA.levelB."
|
||||
const PropertyDefinition* // the property definition object
|
||||
)>;
|
||||
|
||||
//! Traverse all of the properties contained in the source data executing a callback function
|
||||
//! Traversal will occur in group alphabetical order and stop once all properties have been enumerated or the callback function returns false
|
||||
void EnumerateProperties(const EnumeratePropertiesCallback& callback) const;
|
||||
|
||||
//! Traverse all of the properties in the source data in display/storage order executing a callback function
|
||||
//! Traversal will stop once all properties have been enumerated or the callback function returns false
|
||||
void EnumeratePropertiesInDisplayOrder(const EnumeratePropertiesCallback& callback) const;
|
||||
|
||||
//! Recursively traverses all of the properties contained in the material type, executing a callback function for each.
|
||||
//! @return false if the enumeration was terminated early by the callback returning false.
|
||||
bool EnumerateProperties(const EnumeratePropertiesCallback& callback) const;
|
||||
|
||||
//! Convert the property value into the format that will be stored in the source data
|
||||
//! This is primarily needed to support conversions of special types like enums and images
|
||||
bool ConvertPropertyValueToSourceDataFormat(const PropertyDefinition& propertyDefinition, MaterialPropertyValue& propertyValue) const;
|
||||
|
||||
Outcome<Data::Asset<MaterialTypeAsset>> CreateMaterialTypeAsset(Data::AssetId assetId, AZStd::string_view materialTypeSourceFilePath = "", bool elevateWarnings = true) const;
|
||||
|
||||
bool ConvertToNewDataFormat();
|
||||
|
||||
private:
|
||||
|
||||
//PropertySet* FindPropertySet(AZStd::array_view<AZStd::string_view> parsedPropertySetId, AZStd::array_view<AZStd::unique_ptr<PropertySet>> inPropertySetList);
|
||||
const PropertySet* FindPropertySet(AZStd::array_view<AZStd::string_view> parsedPropertySetId, AZStd::array_view<AZStd::unique_ptr<PropertySet>> inPropertySetList) const;
|
||||
|
||||
//PropertyDefinition* FindProperty(AZStd::array_view<AZStd::string_view> parsedPropertyId, AZStd::array_view<AZStd::unique_ptr<PropertySet>> inPropertySetList);
|
||||
const PropertyDefinition* FindProperty(AZStd::array_view<AZStd::string_view> parsedPropertyId, AZStd::array_view<AZStd::unique_ptr<PropertySet>> inPropertySetList) const;
|
||||
|
||||
//PropertyDefinition* FindProperty(AZStd::array_view<AZStd::string_view> parsedPropertyId, PropertySet& inPropertySet);
|
||||
//const PropertyDefinition* FindProperty(AZStd::array_view<AZStd::string_view> parsedPropertyId, const PropertySet& inPropertySet) const;
|
||||
|
||||
// Function overloads for recursion, returns false to indicate that recursion should end.
|
||||
bool EnumeratePropertySets(const EnumeratePropertySetsCallback& callback, AZStd::string propertyIdContext, const AZStd::vector<AZStd::unique_ptr<PropertySet>>& inPropertySetList) const;
|
||||
bool EnumerateProperties(const EnumeratePropertiesCallback& callback, AZStd::string propertyIdContext, const AZStd::vector<AZStd::unique_ptr<PropertySet>>& inPropertySetList) const;
|
||||
|
||||
//! Recursively populates a material asset with properties from the tree of material property sets.
|
||||
//! @param materialTypeSourceFilePath path to the material type file that is being processed, used to look up relative paths
|
||||
//! @param propertyNameContext the accumulated prefix that should be applied to any property names encountered in the current @propertySet
|
||||
//! @param propertySet the current PropertySet that is being processed
|
||||
//! @return false if errors are detected and processing should abort
|
||||
bool BuildPropertyList(
|
||||
const AZStd::string& materialTypeSourceFilePath,
|
||||
MaterialTypeAssetCreator& materialTypeAssetCreator,
|
||||
AZStd::vector<AZStd::string>& propertyNameContext,
|
||||
const MaterialTypeSourceData::PropertySet* propertySet) const;
|
||||
|
||||
//! Construct a complete list of group definitions, including implicit groups, arranged in the same order as the source data.
|
||||
//! Groups with the same name will be consolidated into a single entry.
|
||||
//! Operates on the old format PropertyLayout::m_groups, used for conversion to the new format.
|
||||
AZStd::vector<GroupDefinition> GetOldFormatGroupDefinitionsInDisplayOrder() const;
|
||||
|
||||
PropertyLayout m_propertyLayout;
|
||||
};
|
||||
|
||||
//! The wrapper class for derived material functors.
|
||||
@@ -207,7 +299,7 @@ namespace AZ
|
||||
return m_actualSourceData ? m_actualSourceData->CreateFunctor(editorContext) : Failure();
|
||||
}
|
||||
|
||||
const Ptr<MaterialFunctorSourceData> GetActualSourceData() const { return m_actualSourceData; }
|
||||
Ptr<MaterialFunctorSourceData> GetActualSourceData() const { return m_actualSourceData; }
|
||||
private:
|
||||
Ptr<MaterialFunctorSourceData> m_actualSourceData = nullptr; // The derived material functor instance.
|
||||
};
|
||||
|
||||
@@ -103,6 +103,36 @@ namespace AZ
|
||||
AzFramework::StringFunc::Join(fullName, names.begin(), names.end(), ".");
|
||||
m_fullName = fullName;
|
||||
}
|
||||
|
||||
MaterialPropertyId::MaterialPropertyId(const AZStd::array_view<AZStd::string> groupNames, AZStd::string_view propertyName)
|
||||
{
|
||||
for (const auto& name : groupNames)
|
||||
{
|
||||
if (!IsValidName(name))
|
||||
{
|
||||
AZ_Error("MaterialPropertyId", false, "'%s' is not a valid identifier.", name.c_str());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!IsValidName(propertyName))
|
||||
{
|
||||
AZ_Error("MaterialPropertyId", false, "'%.*s' is not a valid identifier.", AZ_STRING_ARG(propertyName));
|
||||
return;
|
||||
}
|
||||
|
||||
if (groupNames.empty())
|
||||
{
|
||||
m_fullName = propertyName;
|
||||
}
|
||||
else
|
||||
{
|
||||
AZStd::string fullName;
|
||||
AzFramework::StringFunc::Join(fullName, groupNames.begin(), groupNames.end(), ".");
|
||||
fullName = AZStd::string::format("%s.%.*s", fullName.c_str(), AZ_STRING_ARG(propertyName));
|
||||
m_fullName = fullName;
|
||||
}
|
||||
}
|
||||
|
||||
MaterialPropertyId::operator const Name&() const
|
||||
{
|
||||
@@ -113,6 +143,11 @@ namespace AZ
|
||||
{
|
||||
return m_fullName.GetCStr();
|
||||
}
|
||||
|
||||
AZStd::string_view MaterialPropertyId::GetStringView() const
|
||||
{
|
||||
return m_fullName.GetStringView();
|
||||
}
|
||||
|
||||
Name::Hash MaterialPropertyId::GetHash() const
|
||||
{
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <Atom/RPI.Edit/Material/MaterialTypeSourceData.h>
|
||||
#include <Atom/RPI.Edit/Material/MaterialSourceDataSerializer.h>
|
||||
#include <Atom/RPI.Edit/Material/MaterialPropertySerializer.h>
|
||||
#include <Atom/RPI.Edit/Material/MaterialPropertyId.h>
|
||||
|
||||
#include <AzCore/Math/Color.h>
|
||||
#include <AzCore/Math/Vector2.h>
|
||||
@@ -63,6 +64,7 @@ namespace AZ
|
||||
}
|
||||
|
||||
// Construct the full property name (groupName.propertyName) by parsing it from the JSON path string.
|
||||
// Note we don't yet support full nested property sets, but eventually this should not be limited to just one group with one list of properties...
|
||||
size_t startPropertyName = context.GetPath().Get().rfind('/');
|
||||
size_t startGroupName = context.GetPath().Get().rfind('/', startPropertyName-1);
|
||||
AZStd::string_view groupName = context.GetPath().Get().substr(startGroupName + 1, startPropertyName - startGroupName - 1);
|
||||
@@ -70,7 +72,7 @@ namespace AZ
|
||||
|
||||
JSR::ResultCode result(JSR::Tasks::ReadField);
|
||||
|
||||
auto propertyDefinition = materialType->FindProperty(groupName, propertyName);
|
||||
auto propertyDefinition = materialType->FindProperty(MaterialPropertyId{groupName, propertyName}.GetStringView());
|
||||
if (!propertyDefinition)
|
||||
{
|
||||
AZStd::string message = AZStd::string::format("Property '%.*s.%.*s' not found in material type.", AZ_STRING_ARG(groupName), AZ_STRING_ARG(propertyName));
|
||||
|
||||
@@ -103,6 +103,7 @@ namespace AZ
|
||||
settings.m_clearContainers = context.ShouldClearContainers();
|
||||
|
||||
JsonSerializationResult::ResultCode materialTypeLoadResult = JsonSerialization::Load(materialTypeData, materialTypeJson.GetValue(), settings);
|
||||
materialTypeData.ConvertToNewDataFormat();
|
||||
materialTypeData.ResolveUvEnums();
|
||||
|
||||
// Restore prior configuration
|
||||
|
||||
@@ -56,7 +56,11 @@ namespace AZ
|
||||
serializeContext->Class<PropertyConnection>()->Version(3);
|
||||
serializeContext->Class<GroupDefinition>()->Version(4);
|
||||
serializeContext->Class<PropertyDefinition>()->Version(1);
|
||||
|
||||
|
||||
serializeContext->RegisterGenericType<AZStd::unique_ptr<PropertySet>>();
|
||||
serializeContext->RegisterGenericType<AZStd::unique_ptr<PropertyDefinition>>();
|
||||
serializeContext->RegisterGenericType<AZStd::vector<AZStd::unique_ptr<PropertySet>>>();
|
||||
serializeContext->RegisterGenericType<AZStd::vector<AZStd::unique_ptr<PropertyDefinition>>>();
|
||||
serializeContext->RegisterGenericType<PropertyConnectionList>();
|
||||
|
||||
serializeContext->Class<ShaderVariantReferenceData>()
|
||||
@@ -66,11 +70,22 @@ namespace AZ
|
||||
->Field("options", &ShaderVariantReferenceData::m_shaderOptionValues)
|
||||
;
|
||||
|
||||
serializeContext->Class<PropertySet>()
|
||||
->Version(1)
|
||||
->Field("name", &PropertySet::m_name)
|
||||
->Field("displayName", &PropertySet::m_displayName)
|
||||
->Field("description", &PropertySet::m_description)
|
||||
->Field("properties", &PropertySet::m_properties)
|
||||
->Field("propertySets", &PropertySet::m_propertySets)
|
||||
->Field("functors", &PropertySet::m_materialFunctorSourceData)
|
||||
;
|
||||
|
||||
serializeContext->Class<PropertyLayout>()
|
||||
->Version(1)
|
||||
->Field("version", &PropertyLayout::m_version)
|
||||
->Field("groups", &PropertyLayout::m_groups)
|
||||
->Field("properties", &PropertyLayout::m_properties)
|
||||
->Field("groups", &PropertyLayout::m_groups) //< Old, preserved for backward compatibility, replaced by propertySets
|
||||
->Field("properties", &PropertyLayout::m_properties) //< Old, preserved for backward compatibility, replaced by propertySets
|
||||
->Field("propertySets", &PropertyLayout::m_propertySets)
|
||||
;
|
||||
|
||||
serializeContext->RegisterGenericType<UvNameMap>();
|
||||
@@ -92,43 +107,397 @@ namespace AZ
|
||||
, m_shaderIndex(shaderIndex)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
const float MaterialTypeSourceData::PropertyDefinition::DefaultMin = std::numeric_limits<float>::lowest();
|
||||
const float MaterialTypeSourceData::PropertyDefinition::DefaultMax = std::numeric_limits<float>::max();
|
||||
const float MaterialTypeSourceData::PropertyDefinition::DefaultStep = 0.1f;
|
||||
|
||||
const MaterialTypeSourceData::GroupDefinition* MaterialTypeSourceData::FindGroup(AZStd::string_view groupName) const
|
||||
|
||||
/*static*/ MaterialTypeSourceData::PropertySet* MaterialTypeSourceData::PropertySet::AddPropertySet(AZStd::string_view name, AZStd::vector<AZStd::unique_ptr<PropertySet>>& toPropertySetList)
|
||||
{
|
||||
for (const GroupDefinition& group : m_propertyLayout.m_groups)
|
||||
{
|
||||
if (group.m_name == groupName)
|
||||
auto iter = AZStd::find_if(toPropertySetList.begin(), toPropertySetList.end(), [name](const AZStd::unique_ptr<PropertySet>& existingPropertySet)
|
||||
{
|
||||
return &group;
|
||||
return existingPropertySet->m_name == name;
|
||||
});
|
||||
|
||||
if (iter != toPropertySetList.end())
|
||||
{
|
||||
AZ_Error("Material source data", false, "PropertySet named '%.*s' already exists", AZ_STRING_ARG(name));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!MaterialPropertyId::IsValidName(name))
|
||||
{
|
||||
AZ_Error("Material source data", false, "'%.*s' is not a valid identifier", AZ_STRING_ARG(name));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
toPropertySetList.push_back(AZStd::make_unique<PropertySet>());
|
||||
toPropertySetList.back()->m_name = name;
|
||||
return toPropertySetList.back().get();
|
||||
}
|
||||
|
||||
MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::PropertySet::AddProperty(AZStd::string_view name)
|
||||
{
|
||||
auto propertyIter = AZStd::find_if(m_properties.begin(), m_properties.end(), [name](const AZStd::unique_ptr<PropertyDefinition>& existingProperty)
|
||||
{
|
||||
return existingProperty->m_name == name;
|
||||
});
|
||||
|
||||
if (propertyIter != m_properties.end())
|
||||
{
|
||||
AZ_Error("Material source data", false, "PropertySet '%s' already contains a property named '%.*s'", m_name.c_str(), AZ_STRING_ARG(name));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto propertySetIter = AZStd::find_if(m_propertySets.begin(), m_propertySets.end(), [name](const AZStd::unique_ptr<PropertySet>& existingPropertySet)
|
||||
{
|
||||
return existingPropertySet->m_name == name;
|
||||
});
|
||||
|
||||
if (propertySetIter != m_propertySets.end())
|
||||
{
|
||||
AZ_Error("Material source data", false, "Property name '%.*s' collides with a PropertySet of the same name", AZ_STRING_ARG(name));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!MaterialPropertyId::IsValidName(name))
|
||||
{
|
||||
AZ_Error("Material source data", false, "'%.*s' is not a valid identifier", AZ_STRING_ARG(name));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
m_properties.emplace_back(AZStd::make_unique<PropertyDefinition>());
|
||||
m_properties.back()->m_name = name;
|
||||
return m_properties.back().get();
|
||||
}
|
||||
|
||||
MaterialTypeSourceData::PropertySet* MaterialTypeSourceData::PropertySet::AddPropertySet(AZStd::string_view name)
|
||||
{
|
||||
auto iter = AZStd::find_if(m_properties.begin(), m_properties.end(), [name](const AZStd::unique_ptr<PropertyDefinition>& existingProperty)
|
||||
{
|
||||
return existingProperty->m_name == name;
|
||||
});
|
||||
|
||||
if (iter != m_properties.end())
|
||||
{
|
||||
AZ_Error("Material source data", false, "PropertySet name '%.*s' collides with a Property of the same name", AZ_STRING_ARG(name));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return AddPropertySet(name, m_propertySets);
|
||||
}
|
||||
|
||||
MaterialTypeSourceData::PropertySet* MaterialTypeSourceData::AddPropertySet(AZStd::string_view propertySetId)
|
||||
{
|
||||
AZStd::vector<AZStd::string_view> splitPropertySetId = SplitId(propertySetId);
|
||||
|
||||
if (splitPropertySetId.size() == 1)
|
||||
{
|
||||
return PropertySet::AddPropertySet(propertySetId, m_propertyLayout.m_propertySets);
|
||||
}
|
||||
|
||||
// TODO: Delete
|
||||
//return AddPropertySet(splitPropertySetId[0], splitPropertySetId[1]);
|
||||
|
||||
PropertySet* parentPropertySet = const_cast<PropertySet*>(const_cast<MaterialTypeSourceData*>(this)->FindPropertySet(splitPropertySetId[0]));
|
||||
|
||||
if (!parentPropertySet)
|
||||
{
|
||||
AZ_Error("Material source data", false, "PropertySet '%.*s' does not exists", AZ_STRING_ARG(splitPropertySetId[0]));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return parentPropertySet->AddPropertySet(splitPropertySetId[1]);
|
||||
}
|
||||
|
||||
// TODO: Delete
|
||||
//MaterialTypeSourceData::PropertySet* MaterialTypeSourceData::AddPropertySet(AZStd::string_view parentPropertySetId, AZStd::string_view name)
|
||||
//{
|
||||
// PropertySet* parentPropertySet = const_cast<PropertySet*>(const_cast<MaterialTypeSourceData*>(this)->FindPropertySet(parentPropertySetId));
|
||||
//
|
||||
// if (!parentPropertySet)
|
||||
// {
|
||||
// AZ_Error("Material source data", false, "PropertySet '%.*s' does not exists", AZ_STRING_ARG(parentPropertySetId));
|
||||
// return nullptr;
|
||||
// }
|
||||
|
||||
// return parentPropertySet->AddPropertySet(name);
|
||||
//}
|
||||
|
||||
MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::AddProperty(AZStd::string_view propertyId)
|
||||
{
|
||||
AZStd::vector<AZStd::string_view> splitPropertyId = SplitId(propertyId);
|
||||
//if (splitPropertyId.empty())
|
||||
//{
|
||||
// return nullptr;
|
||||
//}
|
||||
|
||||
if (splitPropertyId.size() == 1)
|
||||
{
|
||||
AZ_Error("Material source data", false, "Property id '%.*s' is invalid. Properties must be added to a PropertySet (i.e. \"general.%.*s\").", AZ_STRING_ARG(propertyId), AZ_STRING_ARG(propertyId));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// TODO: Delete
|
||||
//return AddProperty(splitPropertyId[0], splitPropertyId[1]);
|
||||
|
||||
PropertySet* parentPropertySet = const_cast<PropertySet*>(const_cast<MaterialTypeSourceData*>(this)->FindPropertySet(splitPropertyId[0]));
|
||||
|
||||
if (!parentPropertySet)
|
||||
{
|
||||
AZ_Error("Material source data", false, "PropertySet '%.*s' does not exists", AZ_STRING_ARG(splitPropertyId[0]));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return parentPropertySet->AddProperty(splitPropertyId[1]);
|
||||
}
|
||||
|
||||
// TODO: Delete
|
||||
//MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::AddProperty(AZStd::string_view parentPropertySetId, AZStd::string_view name)
|
||||
//{
|
||||
// PropertySet* parentPropertySet = const_cast<PropertySet*>(const_cast<MaterialTypeSourceData*>(this)->FindPropertySet(parentPropertySetId));
|
||||
//
|
||||
// if (!parentPropertySet)
|
||||
// {
|
||||
// AZ_Error("Material source data", false, "PropertySet '%.*s' does not exists", AZ_STRING_ARG(parentPropertySetId));
|
||||
// return nullptr;
|
||||
// }
|
||||
|
||||
// return parentPropertySet->AddProperty(name);
|
||||
//}
|
||||
|
||||
const MaterialTypeSourceData::PropertySet* MaterialTypeSourceData::FindPropertySet(AZStd::array_view<AZStd::string_view> parsedPropertySetId, AZStd::array_view<AZStd::unique_ptr<PropertySet>> inPropertySetList) const
|
||||
{
|
||||
for (const auto& propertySet : inPropertySetList)
|
||||
{
|
||||
if (propertySet->m_name != parsedPropertySetId[0])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (parsedPropertySetId.size() == 1)
|
||||
{
|
||||
return propertySet.get();
|
||||
}
|
||||
else
|
||||
{
|
||||
AZStd::array_view<AZStd::string_view> subPath{parsedPropertySetId.begin() + 1, parsedPropertySetId.end()};
|
||||
|
||||
if (!subPath.empty())
|
||||
{
|
||||
const MaterialTypeSourceData::PropertySet* propertySubset = FindPropertySet(subPath, propertySet->m_propertySets);
|
||||
if (propertySubset)
|
||||
{
|
||||
return propertySubset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::FindProperty(AZStd::string_view groupName, AZStd::string_view propertyName) const
|
||||
{
|
||||
auto groupIter = m_propertyLayout.m_properties.find(groupName);
|
||||
if (groupIter == m_propertyLayout.m_properties.end())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
//MaterialTypeSourceData::PropertySet* MaterialTypeSourceData::FindPropertySet(AZStd::array_view<AZStd::string_view> parsedPropertySetId, AZStd::array_view<AZStd::unique_ptr<PropertySet>> inPropertySetList)
|
||||
//{
|
||||
// return const_cast<PropertySet*>(const_cast<const MaterialTypeSourceData*>(this)->FindPropertySet(parsedPropertySetId, inPropertySetList));
|
||||
//}
|
||||
|
||||
for (const PropertyDefinition& property : groupIter->second)
|
||||
const MaterialTypeSourceData::PropertySet* MaterialTypeSourceData::FindPropertySet(AZStd::string_view propertySetId) const
|
||||
{
|
||||
AZStd::vector<AZStd::string_view> tokens = TokenizeId(propertySetId);
|
||||
return FindPropertySet(tokens, m_propertyLayout.m_propertySets);
|
||||
}
|
||||
|
||||
//MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::FindProperty(AZStd::array_view<AZStd::string_view> parsedPropertyId, PropertySet& inPropertySet)
|
||||
//{
|
||||
// if (parsedPropertyId.size() == 1)
|
||||
// {
|
||||
// for (AZStd::unique_ptr<PropertyDefinition>& property : inPropertySet.m_properties)
|
||||
// {
|
||||
// if (property->m_name == parsedPropertyId[0])
|
||||
// {
|
||||
// return property.get();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// return FindProperty(parsedPropertyId, inPropertySet.m_propertySets);
|
||||
//}
|
||||
|
||||
//const MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::FindProperty(AZStd::array_view<AZStd::string_view> parsedPropertyId, const PropertySet& inPropertySet) const
|
||||
//{
|
||||
// MaterialTypeSourceData* nonConstThis = const_cast<MaterialTypeSourceData*>(this);
|
||||
// PropertySet& nonConstPropertySet = *const_cast<PropertySet*>(&inPropertySet);
|
||||
// return const_cast<PropertyDefinition*>(nonConstThis->FindProperty(parsedPropertyId, nonConstPropertySet));
|
||||
//}
|
||||
|
||||
const MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::FindProperty(
|
||||
AZStd::array_view<AZStd::string_view> parsedPropertyId,
|
||||
AZStd::array_view<AZStd::unique_ptr<PropertySet>> inPropertySetList) const
|
||||
{
|
||||
for (const auto& propertySet : inPropertySetList)
|
||||
{
|
||||
if (property.m_name == propertyName)
|
||||
if (propertySet->m_name == parsedPropertyId[0])
|
||||
{
|
||||
return &property;
|
||||
AZStd::array_view<AZStd::string_view> subPath {parsedPropertyId.begin() + 1, parsedPropertyId.end()};
|
||||
|
||||
if (subPath.size() == 1)
|
||||
{
|
||||
for (AZStd::unique_ptr<PropertyDefinition>& property : propertySet->m_properties)
|
||||
{
|
||||
if (property->m_name == subPath[0])
|
||||
{
|
||||
return property.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(subPath.size() > 1)
|
||||
{
|
||||
const MaterialTypeSourceData::PropertyDefinition* property = FindProperty(subPath, propertySet->m_propertySets);
|
||||
if (property)
|
||||
{
|
||||
return property;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
//MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::FindProperty(AZStd::array_view<AZStd::string_view> parsedPropertyId, AZStd::array_view<AZStd::unique_ptr<PropertySet>> inPropertySetList)
|
||||
//{
|
||||
// return const_cast<PropertyDefinition*>(const_cast<const MaterialTypeSourceData*>(this)->FindProperty(parsedPropertyId, inPropertySetList));
|
||||
//}
|
||||
|
||||
const MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::FindProperty(AZStd::string_view propertyId) const
|
||||
{
|
||||
AZStd::vector<AZStd::string_view> tokens = TokenizeId(propertyId);
|
||||
return FindProperty(tokens, m_propertyLayout.m_propertySets);
|
||||
}
|
||||
|
||||
AZStd::vector<AZStd::string_view> MaterialTypeSourceData::TokenizeId(AZStd::string_view id)
|
||||
{
|
||||
AZStd::vector<AZStd::string_view> tokens;
|
||||
AzFramework::StringFunc::Tokenize(id, tokens, "./", true, true);
|
||||
return tokens;
|
||||
}
|
||||
|
||||
AZStd::vector<AZStd::string_view> MaterialTypeSourceData::SplitId(AZStd::string_view id)
|
||||
{
|
||||
AZStd::vector<AZStd::string_view> parts;
|
||||
parts.reserve(2);
|
||||
size_t lastDelim = id.rfind('.', id.size()-1);
|
||||
if (lastDelim == AZStd::string::npos)
|
||||
{
|
||||
parts.push_back(id);
|
||||
}
|
||||
else
|
||||
{
|
||||
parts.push_back(AZStd::string_view{id.begin(), id.begin()+lastDelim});
|
||||
parts.push_back(AZStd::string_view{id.begin()+lastDelim+1, id.end()});
|
||||
}
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
bool MaterialTypeSourceData::EnumeratePropertySets(const EnumeratePropertySetsCallback& callback, AZStd::string propertyNameContext, const AZStd::vector<AZStd::unique_ptr<PropertySet>>& inPropertySetList) const
|
||||
{
|
||||
for (auto& propertySet : inPropertySetList)
|
||||
{
|
||||
if (!callback(propertyNameContext, propertySet.get()))
|
||||
{
|
||||
return false; // Stop processing
|
||||
}
|
||||
|
||||
const AZStd::string propertyNameContext2 = propertyNameContext + propertySet->m_name + ".";
|
||||
|
||||
if (!EnumeratePropertySets(callback, propertyNameContext2, propertySet->m_propertySets))
|
||||
{
|
||||
return false; // Stop processing
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MaterialTypeSourceData::EnumeratePropertySets(const EnumeratePropertySetsCallback& callback) const
|
||||
{
|
||||
if (!callback)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return EnumeratePropertySets(callback, {}, m_propertyLayout.m_propertySets);
|
||||
}
|
||||
|
||||
bool MaterialTypeSourceData::EnumerateProperties(const EnumeratePropertiesCallback& callback, AZStd::string propertyNameContext, const AZStd::vector<AZStd::unique_ptr<PropertySet>>& inPropertySetList) const
|
||||
{
|
||||
|
||||
for (auto& propertySet : inPropertySetList)
|
||||
{
|
||||
const AZStd::string propertyNameContext2 = propertyNameContext + propertySet->m_name + ".";
|
||||
|
||||
for (auto& property : propertySet->m_properties)
|
||||
{
|
||||
if (!callback(propertyNameContext2, property.get()))
|
||||
{
|
||||
return false; // Stop processing
|
||||
}
|
||||
}
|
||||
|
||||
if (!EnumerateProperties(callback, propertyNameContext2, propertySet->m_propertySets))
|
||||
{
|
||||
return false; // Stop processing
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MaterialTypeSourceData::EnumerateProperties(const EnumeratePropertiesCallback& callback) const
|
||||
{
|
||||
if (!callback)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return EnumerateProperties(callback, {}, m_propertyLayout.m_propertySets);
|
||||
}
|
||||
|
||||
bool MaterialTypeSourceData::ConvertToNewDataFormat()
|
||||
{
|
||||
for (const auto& group : GetOldFormatGroupDefinitionsInDisplayOrder())
|
||||
{
|
||||
auto propertyListItr = m_propertyLayout.m_properties.find(group.m_name);
|
||||
if (propertyListItr != m_propertyLayout.m_properties.end())
|
||||
{
|
||||
const auto& propertyList = propertyListItr->second;
|
||||
for (auto& propertyDefinition : propertyList)
|
||||
{
|
||||
PropertySet* propertySet = const_cast<PropertySet*>(const_cast<MaterialTypeSourceData*>(this)->FindPropertySet(group.m_name));
|
||||
|
||||
if (!propertySet)
|
||||
{
|
||||
m_propertyLayout.m_propertySets.emplace_back(AZStd::make_unique<PropertySet>());
|
||||
m_propertyLayout.m_propertySets.back()->m_name = group.m_name;
|
||||
m_propertyLayout.m_propertySets.back()->m_displayName = group.m_displayName;
|
||||
m_propertyLayout.m_propertySets.back()->m_description = group.m_description;
|
||||
propertySet = m_propertyLayout.m_propertySets.back().get();
|
||||
}
|
||||
|
||||
PropertyDefinition* newProperty = propertySet->AddProperty(propertyDefinition.m_name);
|
||||
|
||||
*newProperty = propertyDefinition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_propertyLayout.m_groups.clear();
|
||||
m_propertyLayout.m_properties.clear();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MaterialTypeSourceData::ResolveUvEnums()
|
||||
{
|
||||
AZStd::vector<AZStd::string> enumValues;
|
||||
@@ -137,20 +506,20 @@ namespace AZ
|
||||
{
|
||||
enumValues.push_back(uvNamePair.second);
|
||||
}
|
||||
|
||||
for (auto& group : m_propertyLayout.m_properties)
|
||||
{
|
||||
for (PropertyDefinition& property : group.second)
|
||||
|
||||
EnumerateProperties([&enumValues](const AZStd::string&, const MaterialTypeSourceData::PropertyDefinition* property)
|
||||
{
|
||||
if (property.m_dataType == AZ::RPI::MaterialPropertyDataType::Enum && property.m_enumIsUv)
|
||||
if (property->m_dataType == AZ::RPI::MaterialPropertyDataType::Enum && property->m_enumIsUv)
|
||||
{
|
||||
property.m_enumValues = enumValues;
|
||||
// const_cast is safe because this is internal to the MaterialTypeSourceData. It isn't worth complicating things
|
||||
// by adding another version of EnumerateProperties.
|
||||
const_cast<MaterialTypeSourceData::PropertyDefinition*>(property)->m_enumValues = enumValues;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
AZStd::vector<MaterialTypeSourceData::GroupDefinition> MaterialTypeSourceData::GetGroupDefinitionsInDisplayOrder() const
|
||||
AZStd::vector<MaterialTypeSourceData::GroupDefinition> MaterialTypeSourceData::GetOldFormatGroupDefinitionsInDisplayOrder() const
|
||||
{
|
||||
AZStd::vector<MaterialTypeSourceData::GroupDefinition> groupDefinitions;
|
||||
groupDefinitions.reserve(m_propertyLayout.m_properties.size());
|
||||
@@ -184,54 +553,7 @@ namespace AZ
|
||||
return groupDefinitions;
|
||||
}
|
||||
|
||||
void MaterialTypeSourceData::EnumerateProperties(const EnumeratePropertiesCallback& callback) const
|
||||
{
|
||||
if (!callback)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& propertyListPair : m_propertyLayout.m_properties)
|
||||
{
|
||||
const AZStd::string& groupName = propertyListPair.first;
|
||||
const auto& propertyList = propertyListPair.second;
|
||||
for (const auto& propertyDefinition : propertyList)
|
||||
{
|
||||
const AZStd::string& propertyName = propertyDefinition.m_name;
|
||||
if (!callback(groupName, propertyName, propertyDefinition))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MaterialTypeSourceData::EnumeratePropertiesInDisplayOrder(const EnumeratePropertiesCallback& callback) const
|
||||
{
|
||||
if (!callback)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& groupDefinition : GetGroupDefinitionsInDisplayOrder())
|
||||
{
|
||||
const AZStd::string& groupName = groupDefinition.m_name;
|
||||
const auto propertyListItr = m_propertyLayout.m_properties.find(groupName);
|
||||
if (propertyListItr != m_propertyLayout.m_properties.end())
|
||||
{
|
||||
const auto& propertyList = propertyListItr->second;
|
||||
for (const auto& propertyDefinition : propertyList)
|
||||
{
|
||||
const AZStd::string& propertyName = propertyDefinition.m_name;
|
||||
if (!callback(groupName, propertyName, propertyDefinition))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: It looks like this function doesn't operate on MaterialTypeSourceData data, it belongs in MaterialUtils
|
||||
bool MaterialTypeSourceData::ConvertPropertyValueToSourceDataFormat(const PropertyDefinition& propertyDefinition, MaterialPropertyValue& propertyValue) const
|
||||
{
|
||||
if (propertyDefinition.m_dataType == AZ::RPI::MaterialPropertyDataType::Enum && propertyValue.Is<uint32_t>())
|
||||
@@ -274,6 +596,178 @@ namespace AZ
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MaterialTypeSourceData::BuildPropertyList(
|
||||
const AZStd::string& materialTypeSourceFilePath,
|
||||
MaterialTypeAssetCreator& materialTypeAssetCreator,
|
||||
AZStd::vector<AZStd::string>& propertyNameContext,
|
||||
const MaterialTypeSourceData::PropertySet* propertySet) const
|
||||
{
|
||||
for (const AZStd::unique_ptr<PropertyDefinition>& property : propertySet->m_properties)
|
||||
{
|
||||
// Register the property...
|
||||
|
||||
MaterialPropertyId propertyId{propertyNameContext, property->m_name};
|
||||
|
||||
if (!propertyId.IsValid())
|
||||
{
|
||||
// MaterialPropertyId reports an error message
|
||||
return false;
|
||||
}
|
||||
|
||||
auto propertySetIter = AZStd::find_if(propertySet->GetPropertySets().begin(), propertySet->GetPropertySets().end(),
|
||||
[&property](const AZStd::unique_ptr<PropertySet>& existingPropertySet)
|
||||
{
|
||||
return existingPropertySet->GetName() == property->m_name;
|
||||
});
|
||||
|
||||
if (propertySetIter != propertySet->GetPropertySets().end())
|
||||
{
|
||||
AZ_Error("Material source data", false, "Material property '%s' collides with a PropertySet with the same ID.", propertyId.GetCStr());
|
||||
return false;
|
||||
}
|
||||
|
||||
materialTypeAssetCreator.BeginMaterialProperty(propertyId, property->m_dataType);
|
||||
|
||||
if (property->m_dataType == MaterialPropertyDataType::Enum)
|
||||
{
|
||||
materialTypeAssetCreator.SetMaterialPropertyEnumNames(property->m_enumValues);
|
||||
}
|
||||
|
||||
for (auto& output : property->m_outputConnections)
|
||||
{
|
||||
switch (output.m_type)
|
||||
{
|
||||
case MaterialPropertyOutputType::ShaderInput:
|
||||
{
|
||||
materialTypeAssetCreator.ConnectMaterialPropertyToShaderInput(Name{output.m_fieldName});
|
||||
break;
|
||||
}
|
||||
case MaterialPropertyOutputType::ShaderOption:
|
||||
{
|
||||
if (output.m_shaderIndex >= 0)
|
||||
{
|
||||
materialTypeAssetCreator.ConnectMaterialPropertyToShaderOption(Name{output.m_fieldName}, output.m_shaderIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
materialTypeAssetCreator.ConnectMaterialPropertyToShaderOptions(Name{output.m_fieldName});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MaterialPropertyOutputType::Invalid:
|
||||
// Don't add any output mappings, this is the case when material functors are expected to process the property
|
||||
break;
|
||||
default:
|
||||
AZ_Assert(false, "Unsupported MaterialPropertyOutputType");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
materialTypeAssetCreator.EndMaterialProperty();
|
||||
|
||||
// Parse and set the property's value...
|
||||
if (!property->m_value.IsValid())
|
||||
{
|
||||
AZ_Warning("Material source data", false, "Source data for material property value is invalid.");
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (property->m_dataType)
|
||||
{
|
||||
case MaterialPropertyDataType::Image:
|
||||
{
|
||||
Outcome<Data::Asset<ImageAsset>> imageAssetResult = MaterialUtils::GetImageAssetReference(materialTypeSourceFilePath, property->m_value.GetValue<AZStd::string>());
|
||||
|
||||
if (imageAssetResult.IsSuccess())
|
||||
{
|
||||
materialTypeAssetCreator.SetPropertyValue(propertyId, imageAssetResult.GetValue());
|
||||
}
|
||||
else
|
||||
{
|
||||
materialTypeAssetCreator.ReportError("Material property '%s': Could not find the image '%s'", propertyId.GetCStr(), property->m_value.GetValue<AZStd::string>().data());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MaterialPropertyDataType::Enum:
|
||||
{
|
||||
MaterialPropertyIndex propertyIndex = materialTypeAssetCreator.GetMaterialPropertiesLayout()->FindPropertyIndex(propertyId);
|
||||
const MaterialPropertyDescriptor* propertyDescriptor = materialTypeAssetCreator.GetMaterialPropertiesLayout()->GetPropertyDescriptor(propertyIndex);
|
||||
|
||||
AZ::Name enumName = AZ::Name(property->m_value.GetValue<AZStd::string>());
|
||||
uint32_t enumValue = propertyDescriptor->GetEnumValue(enumName);
|
||||
if (enumValue == MaterialPropertyDescriptor::InvalidEnumValue)
|
||||
{
|
||||
materialTypeAssetCreator.ReportError("Enum value '%s' couldn't be found in the 'enumValues' list", enumName.GetCStr());
|
||||
}
|
||||
else
|
||||
{
|
||||
materialTypeAssetCreator.SetPropertyValue(propertyId, enumValue);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
materialTypeAssetCreator.SetPropertyValue(propertyId, property->m_value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const AZStd::unique_ptr<PropertySet>& propertySubset : propertySet->m_propertySets)
|
||||
{
|
||||
propertyNameContext.push_back(propertySubset->m_name);
|
||||
|
||||
bool success = BuildPropertyList(
|
||||
materialTypeSourceFilePath,
|
||||
materialTypeAssetCreator,
|
||||
propertyNameContext,
|
||||
propertySubset.get());
|
||||
|
||||
propertyNameContext.pop_back();
|
||||
|
||||
if (!success)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// We cannot create the MaterialFunctor until after all the properties are added because
|
||||
// CreateFunctor() may need to look up properties in the MaterialPropertiesLayout
|
||||
for (auto& functorData : propertySet->m_materialFunctorSourceData)
|
||||
{
|
||||
MaterialFunctorSourceData::FunctorResult result = functorData->CreateFunctor(
|
||||
MaterialFunctorSourceData::RuntimeContext(
|
||||
materialTypeSourceFilePath,
|
||||
materialTypeAssetCreator.GetMaterialPropertiesLayout(),
|
||||
materialTypeAssetCreator.GetMaterialShaderResourceGroupLayout(),
|
||||
materialTypeAssetCreator.GetShaderCollection()
|
||||
)
|
||||
);
|
||||
|
||||
if (result.IsSuccess())
|
||||
{
|
||||
Ptr<MaterialFunctor>& functor = result.GetValue();
|
||||
if (functor != nullptr)
|
||||
{
|
||||
materialTypeAssetCreator.AddMaterialFunctor(functor);
|
||||
|
||||
for (const AZ::Name& optionName : functorData->GetActualSourceData()->GetShaderOptionDependencies())
|
||||
{
|
||||
materialTypeAssetCreator.ClaimShaderOptionOwnership(Name{optionName.GetCStr()});
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
materialTypeAssetCreator.ReportError("Failed to create MaterialFunctor");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Outcome<Data::Asset<MaterialTypeAsset>> MaterialTypeSourceData::CreateMaterialTypeAsset(Data::AssetId assetId, AZStd::string_view materialTypeSourceFilePath, bool elevateWarnings) const
|
||||
{
|
||||
MaterialTypeAssetCreator materialTypeAssetCreator;
|
||||
@@ -327,103 +821,16 @@ namespace AZ
|
||||
return Failure();
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& groupIter : m_propertyLayout.m_properties)
|
||||
|
||||
for (const AZStd::unique_ptr<PropertySet>& propertySet : m_propertyLayout.m_propertySets)
|
||||
{
|
||||
const AZStd::string& groupName = groupIter.first;
|
||||
AZStd::vector<AZStd::string> propertyNameContext;
|
||||
propertyNameContext.push_back(propertySet->m_name);
|
||||
bool success = BuildPropertyList(materialTypeSourceFilePath, materialTypeAssetCreator, propertyNameContext, propertySet.get());
|
||||
|
||||
for (const PropertyDefinition& property : groupIter.second)
|
||||
if (!success)
|
||||
{
|
||||
// Register the property...
|
||||
|
||||
MaterialPropertyId propertyId{ groupName, property.m_name };
|
||||
|
||||
if (!propertyId.IsValid())
|
||||
{
|
||||
materialTypeAssetCreator.ReportWarning("Cannot create material property with invalid ID '%s'.", propertyId.GetCStr());
|
||||
continue;
|
||||
}
|
||||
|
||||
materialTypeAssetCreator.BeginMaterialProperty(propertyId, property.m_dataType);
|
||||
|
||||
if (property.m_dataType == MaterialPropertyDataType::Enum)
|
||||
{
|
||||
materialTypeAssetCreator.SetMaterialPropertyEnumNames(property.m_enumValues);
|
||||
}
|
||||
|
||||
for (auto& output : property.m_outputConnections)
|
||||
{
|
||||
switch (output.m_type)
|
||||
{
|
||||
case MaterialPropertyOutputType::ShaderInput:
|
||||
materialTypeAssetCreator.ConnectMaterialPropertyToShaderInput(Name{ output.m_fieldName.data() });
|
||||
break;
|
||||
case MaterialPropertyOutputType::ShaderOption:
|
||||
if (output.m_shaderIndex >= 0)
|
||||
{
|
||||
materialTypeAssetCreator.ConnectMaterialPropertyToShaderOption(Name{ output.m_fieldName.data() }, output.m_shaderIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
materialTypeAssetCreator.ConnectMaterialPropertyToShaderOptions(Name{ output.m_fieldName.data() });
|
||||
}
|
||||
break;
|
||||
case MaterialPropertyOutputType::Invalid:
|
||||
// Don't add any output mappings, this is the case when material functors are expected to process the property
|
||||
break;
|
||||
default:
|
||||
AZ_Assert(false, "Unsupported MaterialPropertyOutputType");
|
||||
return Failure();
|
||||
}
|
||||
}
|
||||
|
||||
materialTypeAssetCreator.EndMaterialProperty();
|
||||
|
||||
// Parse and set the property's value...
|
||||
if (!property.m_value.IsValid())
|
||||
{
|
||||
AZ_Warning("Material source data", false, "Source data for material property value is invalid.");
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (property.m_dataType)
|
||||
{
|
||||
case MaterialPropertyDataType::Image:
|
||||
{
|
||||
Outcome<Data::Asset<ImageAsset>> imageAssetResult = MaterialUtils::GetImageAssetReference(materialTypeSourceFilePath, property.m_value.GetValue<AZStd::string>());
|
||||
|
||||
if (imageAssetResult.IsSuccess())
|
||||
{
|
||||
materialTypeAssetCreator.SetPropertyValue(propertyId, imageAssetResult.GetValue());
|
||||
}
|
||||
else
|
||||
{
|
||||
materialTypeAssetCreator.ReportError("Material property '%s': Could not find the image '%s'", propertyId.GetCStr(), property.m_value.GetValue<AZStd::string>().data());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MaterialPropertyDataType::Enum:
|
||||
{
|
||||
MaterialPropertyIndex propertyIndex = materialTypeAssetCreator.GetMaterialPropertiesLayout()->FindPropertyIndex(propertyId);
|
||||
const MaterialPropertyDescriptor* propertyDescriptor = materialTypeAssetCreator.GetMaterialPropertiesLayout()->GetPropertyDescriptor(propertyIndex);
|
||||
|
||||
AZ::Name enumName = AZ::Name(property.m_value.GetValue<AZStd::string>());
|
||||
uint32_t enumValue = propertyDescriptor ? propertyDescriptor->GetEnumValue(enumName) : MaterialPropertyDescriptor::InvalidEnumValue;
|
||||
if (enumValue == MaterialPropertyDescriptor::InvalidEnumValue)
|
||||
{
|
||||
materialTypeAssetCreator.ReportError("Enum value '%s' couldn't be found in the 'enumValues' list", enumName.GetCStr());
|
||||
}
|
||||
else
|
||||
{
|
||||
materialTypeAssetCreator.SetPropertyValue(propertyId, enumValue);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
materialTypeAssetCreator.SetPropertyValue(propertyId, property.m_value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return Failure();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -90,6 +90,7 @@ namespace AZ
|
||||
settings.m_metadata.Add(fileLoadContext);
|
||||
|
||||
JsonSerialization::Load(materialType, *document, settings);
|
||||
materialType.ConvertToNewDataFormat();
|
||||
materialType.ResolveUvEnums();
|
||||
|
||||
if (reportingHelper.ErrorsReported())
|
||||
|
||||
@@ -84,7 +84,7 @@ namespace UnitTest
|
||||
}
|
||||
}
|
||||
|
||||
m_checked = true;
|
||||
m_checked = true;
|
||||
}
|
||||
|
||||
void ErrorMessageFinder::ReportFailure(const AZStd::string& failureMessage)
|
||||
|
||||
@@ -94,6 +94,40 @@ namespace UnitTest
|
||||
errorMessageFinder.CheckExpectedErrorsFound();
|
||||
}
|
||||
|
||||
TEST_F(MaterialPropertyIdTests, TestConstructWithMultipleParentNamesSeparateFromPropertyName)
|
||||
{
|
||||
AZStd::vector<AZStd::string> names{"layer1", "clearCoat", "normal"};
|
||||
MaterialPropertyId id{names, "factor"};
|
||||
EXPECT_TRUE(id.IsValid());
|
||||
EXPECT_STREQ(id.GetCStr(), "layer1.clearCoat.normal.factor");
|
||||
AZ::Name idCastedToName = id;
|
||||
EXPECT_EQ(idCastedToName, AZ::Name{"layer1.clearCoat.normal.factor"});
|
||||
}
|
||||
|
||||
TEST_F(MaterialPropertyIdTests, TestConstructWithMultipleParentNamesSeparateFromPropertyName_BadParentName)
|
||||
{
|
||||
ErrorMessageFinder errorMessageFinder;
|
||||
errorMessageFinder.AddExpectedErrorMessage("not a valid identifier");
|
||||
|
||||
AZStd::vector<AZStd::string> names{"layer1", "clear-coat", "normal"};
|
||||
MaterialPropertyId id{names, "factor"};
|
||||
EXPECT_FALSE(id.IsValid());
|
||||
|
||||
errorMessageFinder.CheckExpectedErrorsFound();
|
||||
}
|
||||
|
||||
TEST_F(MaterialPropertyIdTests, TestConstructWithMultipleParentNamesSeparateFromPropertyName_BadPropertyName)
|
||||
{
|
||||
ErrorMessageFinder errorMessageFinder;
|
||||
errorMessageFinder.AddExpectedErrorMessage("not a valid identifier");
|
||||
|
||||
AZStd::vector<AZStd::string> names{"layer1", "clearCoat", "normal"};
|
||||
MaterialPropertyId id{names, "#factor"};
|
||||
EXPECT_FALSE(id.IsValid());
|
||||
|
||||
errorMessageFinder.CheckExpectedErrorsFound();
|
||||
}
|
||||
|
||||
TEST_F(MaterialPropertyIdTests, TestParse)
|
||||
{
|
||||
MaterialPropertyId id = MaterialPropertyId::Parse("layer1.clearCoat.normal.factor");
|
||||
|
||||
@@ -201,33 +201,38 @@ namespace UnitTest
|
||||
TEST_F(MaterialSourceDataTests, TestJsonRoundTrip)
|
||||
{
|
||||
const char* materialTypeJson =
|
||||
"{ \n"
|
||||
" \"propertyLayout\": { \n"
|
||||
" \"version\": 1, \n"
|
||||
" \"groups\": [ \n"
|
||||
" { \"name\": \"groupA\" }, \n"
|
||||
" { \"name\": \"groupB\" }, \n"
|
||||
" { \"name\": \"groupC\" } \n"
|
||||
" ], \n"
|
||||
" \"properties\": { \n"
|
||||
" \"groupA\": [ \n"
|
||||
" {\"name\": \"MyBool\", \"type\": \"bool\"}, \n"
|
||||
" {\"name\": \"MyInt\", \"type\": \"int\"}, \n"
|
||||
" {\"name\": \"MyUInt\", \"type\": \"uint\"} \n"
|
||||
" ], \n"
|
||||
" \"groupB\": [ \n"
|
||||
" {\"name\": \"MyFloat\", \"type\": \"float\"}, \n"
|
||||
" {\"name\": \"MyFloat2\", \"type\": \"vector2\"}, \n"
|
||||
" {\"name\": \"MyFloat3\", \"type\": \"vector3\"} \n"
|
||||
" ], \n"
|
||||
" \"groupC\": [ \n"
|
||||
" {\"name\": \"MyFloat4\", \"type\": \"vector4\"}, \n"
|
||||
" {\"name\": \"MyColor\", \"type\": \"color\"}, \n"
|
||||
" {\"name\": \"MyImage\", \"type\": \"image\"} \n"
|
||||
" ] \n"
|
||||
" } \n"
|
||||
" } \n"
|
||||
"} \n";
|
||||
R"(
|
||||
{
|
||||
"propertyLayout": {
|
||||
"propertySets": [
|
||||
{
|
||||
"name": "groupA",
|
||||
"properties": [
|
||||
{"name": "MyBool", "type": "bool"},
|
||||
{"name": "MyInt", "type": "int"},
|
||||
{"name": "MyUInt", "type": "uint"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "groupB",
|
||||
"properties": [
|
||||
{"name": "MyFloat", "type": "float"},
|
||||
{"name": "MyFloat2", "type": "vector2"},
|
||||
{"name": "MyFloat3", "type": "vector3"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "groupC",
|
||||
"properties": [
|
||||
{"name": "MyFloat4", "type": "vector4"},
|
||||
{"name": "MyColor", "type": "color"},
|
||||
{"name": "MyImage", "type": "image"}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
const char* materialTypeFilePath = "@exefolder@/Gems/Atom/RPI/Code/Tests/Material/Temp/roundTripTest.materialtype";
|
||||
|
||||
@@ -259,25 +264,29 @@ namespace UnitTest
|
||||
|
||||
MaterialSourceData sourceDataCopy;
|
||||
JsonTestResult loadResult = LoadTestDataFromJson(sourceDataCopy, sourceDataSerialized);
|
||||
|
||||
|
||||
CheckEqual(sourceDataOriginal, sourceDataCopy);
|
||||
}
|
||||
|
||||
TEST_F(MaterialSourceDataTests, Load_MaterialTypeAfterPropertyList)
|
||||
{
|
||||
const AZStd::string simpleMaterialTypeJson = R"(
|
||||
{
|
||||
"propertyLayout": {
|
||||
"properties": {
|
||||
"general": [
|
||||
{
|
||||
"propertyLayout": {
|
||||
"propertySets":
|
||||
[
|
||||
{
|
||||
"name": "testColor",
|
||||
"type": "color"
|
||||
"name": "general",
|
||||
"properties": [
|
||||
{
|
||||
"name": "testColor",
|
||||
"type": "color"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
const char* materialTypeFilePath = "@exefolder@/Gems/Atom/RPI/Code/Tests/Material/Temp/simpleMaterialType.materialtype";
|
||||
@@ -376,18 +385,22 @@ namespace UnitTest
|
||||
TEST_F(MaterialSourceDataTests, Load_MaterialTypeMessagesAreReported)
|
||||
{
|
||||
const AZStd::string simpleMaterialTypeJson = R"(
|
||||
{
|
||||
"propertyLayout": {
|
||||
"properties": {
|
||||
"general": [
|
||||
{
|
||||
"propertyLayout": {
|
||||
"propertySets":
|
||||
[
|
||||
{
|
||||
"name": "testColor",
|
||||
"type": "color"
|
||||
"name": "general",
|
||||
"properties": [
|
||||
{
|
||||
"name": "testColor",
|
||||
"type": "color"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
const char* materialTypeFilePath = "@exefolder@/Gems/Atom/RPI/Code/Tests/Material/Temp/simpleMaterialType.materialtype";
|
||||
@@ -416,24 +429,28 @@ namespace UnitTest
|
||||
EXPECT_EQ(AZ::JsonSerializationResult::Processing::Completed, loadResult.m_jsonResultCode.GetProcessing());
|
||||
|
||||
// propertyLayout is a field in the material type, not the material
|
||||
EXPECT_TRUE(loadResult.ContainsMessage("[simpleMaterialType.materialtype]/propertyLayout/properties", "Successfully read"));
|
||||
EXPECT_TRUE(loadResult.ContainsMessage("[simpleMaterialType.materialtype]/propertyLayout/propertySets", "Successfully read"));
|
||||
}
|
||||
|
||||
TEST_F(MaterialSourceDataTests, Load_Error_PropertyNotFound)
|
||||
{
|
||||
const AZStd::string simpleMaterialTypeJson = R"(
|
||||
{
|
||||
"propertyLayout": {
|
||||
"properties": {
|
||||
"general": [
|
||||
{
|
||||
"propertyLayout": {
|
||||
"propertySets":
|
||||
[
|
||||
{
|
||||
"name": "testColor",
|
||||
"type": "color"
|
||||
"name": "general",
|
||||
"properties": [
|
||||
{
|
||||
"name": "testColor",
|
||||
"type": "color"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
const char* materialTypeFilePath = "@exefolder@/Gems/Atom/RPI/Code/Tests/Material/Temp/simpleMaterialType.materialtype";
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,50 +2,48 @@
|
||||
"description": "Base Material with properties used to define Standard PBR, a metallic-roughness Physically-Based Rendering (PBR) material shading model.",
|
||||
"propertyLayout": {
|
||||
"version": 3,
|
||||
"groups": [
|
||||
"propertySets": [
|
||||
{
|
||||
"name": "settings",
|
||||
"displayName": "Settings"
|
||||
"displayName": "Settings",
|
||||
"properties": [
|
||||
{
|
||||
"name": "color",
|
||||
"displayName": "Color",
|
||||
"type": "Color",
|
||||
"defaultValue": [ 1.0, 1.0, 1.0 ],
|
||||
"connection": {
|
||||
"type": "ShaderInput",
|
||||
"name": "m_baseColor"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "metallic",
|
||||
"displayName": "Metallic",
|
||||
"type": "Float",
|
||||
"defaultValue": 0.0,
|
||||
"min": 0.0,
|
||||
"max": 1.0,
|
||||
"connection": {
|
||||
"type": "ShaderInput",
|
||||
"name": "m_metallic"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "roughness",
|
||||
"displayName": "Roughness",
|
||||
"type": "Float",
|
||||
"defaultValue": 1.0,
|
||||
"min": 0.0,
|
||||
"max": 1.0,
|
||||
"connection": {
|
||||
"type": "ShaderInput",
|
||||
"name": "m_roughness"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"settings": [
|
||||
{
|
||||
"name": "color",
|
||||
"displayName": "Color",
|
||||
"type": "Color",
|
||||
"defaultValue": [ 1.0, 1.0, 1.0 ],
|
||||
"connection": {
|
||||
"type": "ShaderInput",
|
||||
"name": "m_baseColor"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "metallic",
|
||||
"displayName": "Metallic",
|
||||
"type": "Float",
|
||||
"defaultValue": 0.0,
|
||||
"min": 0.0,
|
||||
"max": 1.0,
|
||||
"connection": {
|
||||
"type": "ShaderInput",
|
||||
"name": "m_metallic"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "roughness",
|
||||
"displayName": "Roughness",
|
||||
"type": "Float",
|
||||
"defaultValue": 1.0,
|
||||
"min": 0.0,
|
||||
"max": 1.0,
|
||||
"connection": {
|
||||
"type": "ShaderInput",
|
||||
"name": "m_roughness"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"shaders": [
|
||||
{
|
||||
|
||||
@@ -230,7 +230,7 @@ namespace MaterialEditor
|
||||
|
||||
// create source data from properties
|
||||
MaterialSourceData sourceData;
|
||||
sourceData.m_propertyLayoutVersion = m_materialTypeSourceData.m_propertyLayout.m_version;
|
||||
sourceData.m_propertyLayoutVersion = m_materialTypeSourceData.GetPropertyLayout().m_version;
|
||||
sourceData.m_materialType = m_materialSourceData.m_materialType;
|
||||
sourceData.m_parentMaterial = m_materialSourceData.m_parentMaterial;
|
||||
|
||||
@@ -302,7 +302,7 @@ namespace MaterialEditor
|
||||
|
||||
// create source data from properties
|
||||
MaterialSourceData sourceData;
|
||||
sourceData.m_propertyLayoutVersion = m_materialTypeSourceData.m_propertyLayout.m_version;
|
||||
sourceData.m_propertyLayoutVersion = m_materialTypeSourceData.GetPropertyLayout().m_version;
|
||||
sourceData.m_materialType = m_materialSourceData.m_materialType;
|
||||
sourceData.m_parentMaterial = m_materialSourceData.m_parentMaterial;
|
||||
|
||||
@@ -373,7 +373,7 @@ namespace MaterialEditor
|
||||
|
||||
// create source data from properties
|
||||
MaterialSourceData sourceData;
|
||||
sourceData.m_propertyLayoutVersion = m_materialTypeSourceData.m_propertyLayout.m_version;
|
||||
sourceData.m_propertyLayoutVersion = m_materialTypeSourceData.GetPropertyLayout().m_version;
|
||||
sourceData.m_materialType = m_materialSourceData.m_materialType;
|
||||
|
||||
// Only assign a parent path if the source was a .material
|
||||
@@ -592,24 +592,26 @@ namespace MaterialEditor
|
||||
bool result = true;
|
||||
|
||||
// populate sourceData with properties that meet the filter
|
||||
m_materialTypeSourceData.EnumerateProperties([this, &sourceData, &propertyFilter, &result](const AZStd::string& groupName, const AZStd::string& propertyName, const auto& propertyDefinition) {
|
||||
m_materialTypeSourceData.EnumerateProperties([this, &sourceData, &propertyFilter, &result](const AZStd::string& propertyIdContext, const auto& propertyDefinition) {
|
||||
|
||||
const MaterialPropertyId propertyId(groupName, propertyName);
|
||||
const AZStd::string propertyId = propertyIdContext + propertyDefinition->m_name;
|
||||
|
||||
const auto it = m_properties.find(propertyId);
|
||||
const auto it = m_properties.find(Name{propertyId});
|
||||
if (it != m_properties.end() && propertyFilter(it->second))
|
||||
{
|
||||
MaterialPropertyValue propertyValue = AtomToolsFramework::ConvertToRuntimeType(it->second.GetValue());
|
||||
if (propertyValue.IsValid())
|
||||
{
|
||||
if (!m_materialTypeSourceData.ConvertPropertyValueToSourceDataFormat(propertyDefinition, propertyValue))
|
||||
if (!m_materialTypeSourceData.ConvertPropertyValueToSourceDataFormat(*propertyDefinition, propertyValue))
|
||||
{
|
||||
AZ_Error("MaterialDocument", false, "Material document property could not be converted: '%s' in '%s'.", propertyId.GetCStr(), m_absolutePath.c_str());
|
||||
AZ_Error("MaterialDocument", false, "Material document property could not be converted: '%s' in '%s'.", propertyId.c_str(), m_absolutePath.c_str());
|
||||
result = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
sourceData.m_properties[groupName][propertyName].m_value = propertyValue;
|
||||
|
||||
// TODO: Support populating the Material Editor with nested property sets, not just the top level.
|
||||
const AZStd::string groupName = propertyId.substr(0, propertyId.size() - propertyDefinition->m_name.size() - 1);
|
||||
sourceData.m_properties[groupName][propertyDefinition->m_name].m_value = propertyValue;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -678,7 +680,7 @@ namespace MaterialEditor
|
||||
AZ_Error("MaterialDocument", false, "Material type source data could not be loaded: '%s'.", materialTypeSourceFilePath.c_str());
|
||||
return false;
|
||||
}
|
||||
m_materialTypeSourceData = materialTypeOutcome.GetValue();
|
||||
m_materialTypeSourceData = materialTypeOutcome.TakeValue();
|
||||
}
|
||||
else if (AzFramework::StringFunc::Path::IsExtension(m_absolutePath.c_str(), MaterialTypeSourceData::Extension))
|
||||
{
|
||||
@@ -691,7 +693,7 @@ namespace MaterialEditor
|
||||
AZ_Error("MaterialDocument", false, "Material type source data could not be loaded: '%s'.", m_absolutePath.c_str());
|
||||
return false;
|
||||
}
|
||||
m_materialTypeSourceData = materialTypeOutcome.GetValue();
|
||||
m_materialTypeSourceData = materialTypeOutcome.TakeValue();
|
||||
|
||||
// The document represents a material, not a material type.
|
||||
// If the input data is a material type file we have to generate the material source data by referencing it.
|
||||
@@ -770,33 +772,41 @@ namespace MaterialEditor
|
||||
// Populate the property map from a combination of source data and assets
|
||||
// Assets must still be used for now because they contain the final accumulated value after all other materials
|
||||
// in the hierarchy are applied
|
||||
m_materialTypeSourceData.EnumerateProperties([this, &parentPropertyValues](const AZStd::string& groupName, const AZStd::string& propertyName, const auto& propertyDefinition) {
|
||||
AtomToolsFramework::DynamicPropertyConfig propertyConfig;
|
||||
|
||||
// Assign id before conversion so it can be used in dynamic description
|
||||
propertyConfig.m_id = MaterialPropertyId(groupName, propertyName);
|
||||
|
||||
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)
|
||||
m_materialTypeSourceData.EnumeratePropertySets([this, &parentPropertyValues](const AZStd::string& propertyIdContext, const MaterialTypeSourceData::PropertySet* propertySet)
|
||||
{
|
||||
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()]);
|
||||
auto groupDefinition = m_materialTypeSourceData.FindGroup(groupName);
|
||||
propertyConfig.m_groupName = groupDefinition ? groupDefinition->m_displayName : groupName;
|
||||
m_properties[propertyConfig.m_id] = AtomToolsFramework::DynamicProperty(propertyConfig);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
AtomToolsFramework::DynamicPropertyConfig propertyConfig;
|
||||
|
||||
for (const auto& propertyDefinition : propertySet->GetProperties())
|
||||
{
|
||||
// Assign id before conversion so it can be used in dynamic description
|
||||
propertyConfig.m_id = propertyIdContext + propertySet->GetName() + "." + propertyDefinition->m_name;
|
||||
|
||||
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 sets, not just the top level.
|
||||
// (Does DynamicPropertyConfig really even need m_groupName?)
|
||||
propertyConfig.m_groupName = propertySet->GetDisplayName();
|
||||
m_properties[propertyConfig.m_id] = AtomToolsFramework::DynamicProperty(propertyConfig);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
// Populate the property group visibility map
|
||||
for (MaterialTypeSourceData::GroupDefinition& group : m_materialTypeSourceData.GetGroupDefinitionsInDisplayOrder())
|
||||
// TODO: Support populating the Material Editor with nested property sets, not just the top level.
|
||||
for (const AZStd::unique_ptr<MaterialTypeSourceData::PropertySet>& propertySet : m_materialTypeSourceData.GetPropertyLayout().m_propertySets)
|
||||
{
|
||||
m_propertyGroupVisibility[AZ::Name{group.m_name}] = true;
|
||||
m_propertyGroupVisibility[AZ::Name{propertySet->GetName()}] = true;
|
||||
}
|
||||
|
||||
// Adding properties for material type and parent as part of making dynamic
|
||||
@@ -877,6 +887,39 @@ namespace MaterialEditor
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool enumerateResult = m_materialTypeSourceData.EnumeratePropertySets(
|
||||
[this, &materialTypeSourceFilePath](const AZStd::string&, const MaterialTypeSourceData::PropertySet* propertySet)
|
||||
{
|
||||
const MaterialFunctorSourceData::EditorContext editorContext = MaterialFunctorSourceData::EditorContext(
|
||||
materialTypeSourceFilePath, m_materialAsset->GetMaterialPropertiesLayout());
|
||||
|
||||
for (Ptr<MaterialFunctorSourceDataHolder> functorData : propertySet->GetFunctors())
|
||||
{
|
||||
MaterialFunctorSourceData::FunctorResult result = functorData->CreateFunctor(editorContext);
|
||||
|
||||
if (result.IsSuccess())
|
||||
{
|
||||
Ptr<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;
|
||||
});
|
||||
|
||||
if (!enumerateResult)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
AZ::RPI::MaterialPropertyFlags dirtyFlags;
|
||||
dirtyFlags.set(); // Mark all properties as dirty since we just loaded the material and need to initialize property visibility
|
||||
|
||||
+13
-18
@@ -163,28 +163,23 @@ namespace MaterialEditor
|
||||
const AZ::RPI::MaterialTypeSourceData* materialTypeSourceData = nullptr;
|
||||
MaterialDocumentRequestBus::EventResult(
|
||||
materialTypeSourceData, m_documentId, &MaterialDocumentRequestBus::Events::GetMaterialTypeSourceData);
|
||||
|
||||
for (const auto& groupDefinition : materialTypeSourceData->GetGroupDefinitionsInDisplayOrder())
|
||||
|
||||
// TODO: Support populating the Material Editor with nested property sets, not just the top level.
|
||||
for (const AZStd::unique_ptr<AZ::RPI::MaterialTypeSourceData::PropertySet>& propertySet : materialTypeSourceData->GetPropertyLayout().m_propertySets)
|
||||
{
|
||||
const AZStd::string& groupName = groupDefinition.m_name;
|
||||
const AZStd::string& groupDisplayName = !groupDefinition.m_displayName.empty() ? groupDefinition.m_displayName : groupName;
|
||||
const AZStd::string& groupDescription =
|
||||
!groupDefinition.m_description.empty() ? groupDefinition.m_description : groupDisplayName;
|
||||
const AZStd::string& groupName = propertySet->GetName();
|
||||
const AZStd::string& groupDisplayName = !propertySet->GetDisplayName().empty() ? propertySet->GetDisplayName() : groupName;
|
||||
const AZStd::string& groupDescription = !propertySet->GetDescription().empty() ? propertySet->GetDescription() : groupDisplayName;
|
||||
auto& group = m_groups[groupName];
|
||||
|
||||
const auto& propertyLayout = materialTypeSourceData->m_propertyLayout;
|
||||
const auto& propertyListItr = propertyLayout.m_properties.find(groupName);
|
||||
if (propertyListItr != propertyLayout.m_properties.end())
|
||||
group.m_properties.reserve(propertySet->GetProperties().size());
|
||||
for (const auto& propertyDefinition : propertySet->GetProperties())
|
||||
{
|
||||
group.m_properties.reserve(propertyListItr->second.size());
|
||||
for (const auto& propertyDefinition : propertyListItr->second)
|
||||
{
|
||||
AtomToolsFramework::DynamicProperty property;
|
||||
AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(
|
||||
property, m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::GetProperty,
|
||||
AZ::RPI::MaterialPropertyId(groupName, propertyDefinition.m_name));
|
||||
group.m_properties.push_back(property);
|
||||
}
|
||||
AtomToolsFramework::DynamicProperty property;
|
||||
AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(
|
||||
property, m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::GetProperty,
|
||||
AZ::RPI::MaterialPropertyId(groupName, propertyDefinition->m_name));
|
||||
group.m_properties.push_back(property);
|
||||
}
|
||||
|
||||
// Passing in same group as main and comparison instance to enable custom value comparison for highlighting modified properties
|
||||
|
||||
+19
-23
@@ -283,35 +283,31 @@ namespace AZ
|
||||
AddUvNamesGroup();
|
||||
|
||||
// Copy all of the properties from the material asset to the source data that will be exported
|
||||
for (const auto& groupDefinition : m_editData.m_materialTypeSourceData.GetGroupDefinitionsInDisplayOrder())
|
||||
// TODO: Support populating the Material Editor with nested property sets, not just the top level.
|
||||
for (const AZStd::unique_ptr<AZ::RPI::MaterialTypeSourceData::PropertySet>& propertySet : m_editData.m_materialTypeSourceData.GetPropertyLayout().m_propertySets)
|
||||
{
|
||||
const AZStd::string& groupName = groupDefinition.m_name;
|
||||
const AZStd::string& groupDisplayName = !groupDefinition.m_displayName.empty() ? groupDefinition.m_displayName : groupName;
|
||||
const AZStd::string& groupDescription = !groupDefinition.m_description.empty() ? groupDefinition.m_description : groupDisplayName;
|
||||
const AZStd::string& groupName = propertySet->GetName();
|
||||
const AZStd::string& groupDisplayName = !propertySet->GetDisplayName().empty() ? propertySet->GetDisplayName() : groupName;
|
||||
const AZStd::string& groupDescription = !propertySet->GetDescription().empty() ? propertySet->GetDescription() : groupDisplayName;
|
||||
auto& group = m_groups[groupName];
|
||||
|
||||
const auto& propertyLayout = m_editData.m_materialTypeSourceData.m_propertyLayout;
|
||||
const auto& propertyListItr = propertyLayout.m_properties.find(groupName);
|
||||
if (propertyListItr != propertyLayout.m_properties.end())
|
||||
|
||||
group.m_properties.reserve(propertySet->GetProperties().size());
|
||||
for (const auto& propertyDefinition : propertySet->GetProperties())
|
||||
{
|
||||
group.m_properties.reserve(propertyListItr->second.size());
|
||||
for (const auto& propertyDefinition : propertyListItr->second)
|
||||
{
|
||||
AtomToolsFramework::DynamicPropertyConfig propertyConfig;
|
||||
AtomToolsFramework::DynamicPropertyConfig propertyConfig;
|
||||
|
||||
// Assign id before conversion so it can be used in dynamic description
|
||||
propertyConfig.m_id = AZ::RPI::MaterialPropertyId(groupName, propertyDefinition.m_name);
|
||||
// Assign id before conversion so it can be used in dynamic description
|
||||
propertyConfig.m_id = AZ::RPI::MaterialPropertyId(groupName, propertyDefinition->m_name);
|
||||
|
||||
AtomToolsFramework::ConvertToPropertyConfig(propertyConfig, propertyDefinition);
|
||||
AtomToolsFramework::ConvertToPropertyConfig(propertyConfig, *propertyDefinition.get());
|
||||
|
||||
propertyConfig.m_groupName = groupDisplayName;
|
||||
const auto& propertyIndex = m_editData.m_materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyConfig.m_id);
|
||||
propertyConfig.m_showThumbnail = true;
|
||||
propertyConfig.m_defaultValue = AtomToolsFramework::ConvertToEditableType(m_editData.m_materialTypeAsset->GetDefaultPropertyValues()[propertyIndex.GetIndex()]);
|
||||
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);
|
||||
}
|
||||
propertyConfig.m_groupName = groupDisplayName;
|
||||
const auto& propertyIndex = m_editData.m_materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyConfig.m_id);
|
||||
propertyConfig.m_showThumbnail = true;
|
||||
propertyConfig.m_defaultValue = AtomToolsFramework::ConvertToEditableType(m_editData.m_materialTypeAsset->GetDefaultPropertyValues()[propertyIndex.GetIndex()]);
|
||||
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
|
||||
|
||||
+37
-33
@@ -91,7 +91,7 @@ namespace AZ
|
||||
AZ_Error("AZ::Render::EditorMaterialComponentUtil", false, "Failed to load material type source data: %s", editData.m_materialTypeSourcePath.c_str());
|
||||
return false;
|
||||
}
|
||||
editData.m_materialTypeSourceData = materialTypeOutcome.GetValue();
|
||||
editData.m_materialTypeSourceData = materialTypeOutcome.TakeValue();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ namespace AZ
|
||||
{
|
||||
// Construct the material source data object that will be exported
|
||||
AZ::RPI::MaterialSourceData exportData;
|
||||
exportData.m_propertyLayoutVersion = editData.m_materialTypeSourceData.m_propertyLayout.m_version;
|
||||
exportData.m_propertyLayoutVersion = editData.m_materialTypeSourceData.GetPropertyLayout().m_version;
|
||||
|
||||
// Converting absolute material paths to relative paths
|
||||
bool result = false;
|
||||
@@ -137,42 +137,46 @@ namespace AZ
|
||||
|
||||
// Copy all of the properties from the material asset to the source data that will be exported
|
||||
result = true;
|
||||
editData.m_materialTypeSourceData.EnumerateProperties([&](const AZStd::string& groupName, const AZStd::string& propertyName, const auto& propertyDefinition) {
|
||||
const AZ::RPI::MaterialPropertyId propertyId(groupName, propertyName);
|
||||
const AZ::RPI::MaterialPropertyIndex propertyIndex =
|
||||
editData.m_materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyId);
|
||||
|
||||
AZ::RPI::MaterialPropertyValue propertyValue = editData.m_materialAsset->GetPropertyValues()[propertyIndex.GetIndex()];
|
||||
|
||||
AZ::RPI::MaterialPropertyValue propertyValueDefault = propertyDefinition.m_value;
|
||||
if (editData.m_materialParentAsset.IsReady())
|
||||
editData.m_materialTypeSourceData.EnumerateProperties([&](const AZStd::string& propertyIdContext, const AZ::RPI::MaterialTypeSourceData::PropertyDefinition* propertyDefinition)
|
||||
{
|
||||
propertyValueDefault = editData.m_materialParentAsset->GetPropertyValues()[propertyIndex.GetIndex()];
|
||||
}
|
||||
AZ::Name propertyId(propertyIdContext + propertyDefinition->m_name);
|
||||
|
||||
// Check for and apply any property overrides before saving property values
|
||||
auto propertyOverrideItr = editData.m_materialPropertyOverrideMap.find(propertyId);
|
||||
if(propertyOverrideItr != editData.m_materialPropertyOverrideMap.end())
|
||||
{
|
||||
propertyValue = AZ::RPI::MaterialPropertyValue::FromAny(propertyOverrideItr->second);
|
||||
}
|
||||
const AZ::RPI::MaterialPropertyIndex propertyIndex =
|
||||
editData.m_materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyId);
|
||||
|
||||
if (!editData.m_materialTypeSourceData.ConvertPropertyValueToSourceDataFormat(propertyDefinition, propertyValue))
|
||||
{
|
||||
AZ_Error("AZ::Render::EditorMaterialComponentUtil", false, "Failed to export: %s", path.c_str());
|
||||
result = false;
|
||||
return false;
|
||||
}
|
||||
AZ::RPI::MaterialPropertyValue propertyValue = editData.m_materialAsset->GetPropertyValues()[propertyIndex.GetIndex()];
|
||||
|
||||
// Don't export values if they are the same as the material type or parent
|
||||
if (propertyValueDefault == propertyValue)
|
||||
{
|
||||
AZ::RPI::MaterialPropertyValue propertyValueDefault = propertyDefinition->m_value;
|
||||
if (editData.m_materialParentAsset.IsReady())
|
||||
{
|
||||
propertyValueDefault = editData.m_materialParentAsset->GetPropertyValues()[propertyIndex.GetIndex()];
|
||||
}
|
||||
|
||||
// Check for and apply any property overrides before saving property values
|
||||
auto propertyOverrideItr = editData.m_materialPropertyOverrideMap.find(propertyId);
|
||||
if(propertyOverrideItr != editData.m_materialPropertyOverrideMap.end())
|
||||
{
|
||||
propertyValue = AZ::RPI::MaterialPropertyValue::FromAny(propertyOverrideItr->second);
|
||||
}
|
||||
|
||||
if (!editData.m_materialTypeSourceData.ConvertPropertyValueToSourceDataFormat(*propertyDefinition, propertyValue))
|
||||
{
|
||||
AZ_Error("AZ::Render::EditorMaterialComponentUtil", false, "Failed to export: %s", path.c_str());
|
||||
result = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't export values if they are the same as the material type or parent
|
||||
if (propertyValueDefault == propertyValue)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: Support populating the Material Editor with nested property sets, not just the top level.
|
||||
const AZStd::string groupName = propertyId.GetStringView().substr(0, propertyId.GetStringView().size() - propertyDefinition->m_name.size() - 1);
|
||||
exportData.m_properties[groupName][propertyDefinition->m_name].m_value = propertyValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
exportData.m_properties[groupName][propertyDefinition.m_name].m_value = propertyValue;
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
return result && AZ::RPI::JsonUtils::SaveObjectToFile(path, exportData);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user