Changed the .material file format to use a flat list for material property values, instead of a tree structure.

This is needed to support deeply nested material property groups, it just makes the serialization code a lot simpler than trying to support nested groups in the .material file. It also makes the file more readable and easier to search all files for particular properties.
I also updated MaterialSourceData to hide the property values behind a clean API.

Signed-off-by: santorac <55155825+santorac@users.noreply.github.com>
monroegm-disable-blank-issue-2
santorac 4 years ago
parent 4bb93e4c94
commit 1daa9fbbed

@ -82,9 +82,8 @@ namespace AZ
// The source data for generating material asset // The source data for generating material asset
sourceData.m_materialType = GetMaterialTypePath(); sourceData.m_materialType = GetMaterialTypePath();
auto handleTexture = [&materialData, &sourceData]( auto handleTexture = [&materialData, &sourceData](const char* propertyTextureGroup, SceneAPI::DataTypes::IMaterialData::TextureMapType textureType)
const char* propertyTextureGroup, SceneAPI::DataTypes::IMaterialData::TextureMapType textureType) { {
MaterialSourceData::PropertyMap& properties = sourceData.m_properties[propertyTextureGroup];
const AZStd::string& texturePath = materialData.GetTexture(textureType); const AZStd::string& texturePath = materialData.GetTexture(textureType);
// Check to see if the image asset exists. If not, skip this texture map and just disable it. // Check to see if the image asset exists. If not, skip this texture map and just disable it.
@ -101,7 +100,7 @@ namespace AZ
if (assetFound) if (assetFound)
{ {
properties["textureMap"] = texturePath; sourceData.SetPropertyValue(MaterialPropertyId{propertyTextureGroup, "textureMap"}, texturePath);
} }
else if (!texturePath.empty()) else if (!texturePath.empty())
{ {
@ -120,7 +119,7 @@ namespace AZ
{ {
anyPBRInUse = true; anyPBRInUse = true;
handleTexture("baseColor", SceneAPI::DataTypes::IMaterialData::TextureMapType::BaseColor); handleTexture("baseColor", SceneAPI::DataTypes::IMaterialData::TextureMapType::BaseColor);
sourceData.m_properties["baseColor"]["textureBlendMode"] = AZStd::string("Lerp"); sourceData.SetPropertyValue(Name{"baseColor.textureBlendMode"}, AZStd::string("Lerp"));
} }
else else
{ {
@ -135,10 +134,10 @@ namespace AZ
if (baseColor.has_value()) if (baseColor.has_value())
{ {
anyPBRInUse = true; anyPBRInUse = true;
sourceData.m_properties["baseColor"]["color"] = toColor(baseColor.value()); sourceData.SetPropertyValue(Name{"baseColor.color"}, toColor(baseColor.value()));
} }
sourceData.m_properties["opacity"]["factor"] = materialData.GetOpacity(); sourceData.SetPropertyValue(Name{"opacity.factor"}, materialData.GetOpacity());
auto applyOptionalPropertiesFunc = [&sourceData, &anyPBRInUse](const auto& propertyGroup, const auto& propertyName, const auto& propertyOptional) auto applyOptionalPropertiesFunc = [&sourceData, &anyPBRInUse](const auto& propertyGroup, const auto& propertyName, const auto& propertyOptional)
{ {
@ -147,7 +146,7 @@ namespace AZ
if (propertyOptional.has_value()) if (propertyOptional.has_value())
{ {
anyPBRInUse = true; anyPBRInUse = true;
sourceData.m_properties[propertyGroup][propertyName] = propertyOptional.value(); sourceData.SetPropertyValue(MaterialPropertyId{propertyGroup, propertyName}, propertyOptional.value());
} }
}; };
@ -160,7 +159,7 @@ namespace AZ
applyOptionalPropertiesFunc("roughness", "useTexture", materialData.GetUseRoughnessMap()); applyOptionalPropertiesFunc("roughness", "useTexture", materialData.GetUseRoughnessMap());
handleTexture("emissive", SceneAPI::DataTypes::IMaterialData::TextureMapType::Emissive); handleTexture("emissive", SceneAPI::DataTypes::IMaterialData::TextureMapType::Emissive);
sourceData.m_properties["emissive"]["color"] = toColor(materialData.GetEmissiveColor()); sourceData.SetPropertyValue(Name{"emissive.color"}, toColor(materialData.GetEmissiveColor()));
applyOptionalPropertiesFunc("emissive", "intensity", materialData.GetEmissiveIntensity()); applyOptionalPropertiesFunc("emissive", "intensity", materialData.GetEmissiveIntensity());
applyOptionalPropertiesFunc("emissive", "useTexture", materialData.GetUseEmissiveMap()); applyOptionalPropertiesFunc("emissive", "useTexture", materialData.GetUseEmissiveMap());
@ -171,7 +170,7 @@ namespace AZ
{ {
// If it doesn't have the useColorMap property, then it's a non-PBR material and the baseColor // If it doesn't have the useColorMap property, then it's a non-PBR material and the baseColor
// texture needs to be set to the diffuse color. // texture needs to be set to the diffuse color.
sourceData.m_properties["baseColor"]["color"] = toColor(materialData.GetDiffuseColor()); sourceData.SetPropertyValue(Name{"baseColor.color"}, toColor(materialData.GetDiffuseColor()));
} }
return true; return true;
} }

@ -59,17 +59,25 @@ namespace AZ
uint32_t m_materialTypeVersion = 0; //!< The version of the material type that was used to configure this material uint32_t m_materialTypeVersion = 0; //!< The version of the material type that was used to configure this material
using PropertyMap = AZStd::map<AZStd::string, MaterialPropertyValue>;
using PropertyGroupMap = AZStd::map<AZStd::string, PropertyMap>;
PropertyGroupMap m_properties;
enum class ApplyVersionUpdatesResult enum class ApplyVersionUpdatesResult
{ {
Failed, Failed,
NoUpdates, NoUpdates,
UpdatesApplied UpdatesApplied
}; };
//! If the data was loaded from an old format file (i.e. where "properties" was a tree with property values nested under groups),
//! this converts to the new format where properties are stored in a flat list.
void ConvertToNewDataFormat();
// Note that even though we use an unordered map, the JSON serialization system is nice enough to sort the data when saving to JSON.
using PropertyValueMap = AZStd::unordered_map<Name, MaterialPropertyValue>;
void SetPropertyValue(const Name& propertyId, const MaterialPropertyValue& value);
const MaterialPropertyValue& GetPropertyValue(const Name& propertyId) const;
PropertyValueMap GetPropertyValues() const;
bool HasPropertyValue(const Name& propertyId) const;
void RemovePropertyValue(const Name& propertyId);
//! Creates a MaterialAsset from the MaterialSourceData content. //! Creates a MaterialAsset from the MaterialSourceData content.
//! @param assetId ID for the MaterialAsset //! @param assetId ID for the MaterialAsset
@ -96,8 +104,16 @@ namespace AZ
AZStd::unordered_set<AZStd::string>* sourceDependencies = nullptr) const; AZStd::unordered_set<AZStd::string>* sourceDependencies = nullptr) const;
private: private:
void ApplyPropertiesToAssetCreator( void ApplyPropertiesToAssetCreator(
AZ::RPI::MaterialAssetCreator& materialAssetCreator, const AZStd::string_view& materialSourceFilePath) const; AZ::RPI::MaterialAssetCreator& materialAssetCreator, const AZStd::string_view& materialSourceFilePath) const;
// @deprecated: Don't use "properties" in JSON, use "propertyValues" instead.
using PropertyGroupMap = AZStd::unordered_map<Name, PropertyValueMap>;
PropertyGroupMap m_propertiesOld;
PropertyValueMap m_propertyValues;
MaterialPropertyValue m_invalidValue;
}; };
} // namespace RPI } // namespace RPI
} // namespace AZ } // namespace AZ

@ -216,11 +216,11 @@ namespace AZ
//! This field is unused, and has been replaced by MaterialTypeSourceData::m_version below. It is kept for legacy file compatibility to suppress warnings and errors. //! This field is unused, and has been replaced by MaterialTypeSourceData::m_version below. It is kept for legacy file compatibility to suppress warnings and errors.
uint32_t m_versionOld = 0; uint32_t m_versionOld = 0;
//! [Deprecated] Use m_propertyGroups instead //! @deprecated: Use m_propertyGroups instead
//! List of groups that will contain the available properties //! List of groups that will contain the available properties
AZStd::vector<GroupDefinition> m_groupsOld; AZStd::vector<GroupDefinition> m_groupsOld;
//! [Deprecated] Use m_propertyGroups instead //! @deprecated: Use m_propertyGroups instead
AZStd::map<AZStd::string /*group name*/, AZStd::vector<PropertyDefinition>> m_propertiesOld; AZStd::map<AZStd::string /*group name*/, AZStd::vector<PropertyDefinition>> m_propertiesOld;
//! Collection of all available user-facing properties //! Collection of all available user-facing properties

@ -24,6 +24,7 @@ namespace AZ
namespace RPI namespace RPI
{ {
class MaterialSourceData;
class MaterialTypeSourceData; class MaterialTypeSourceData;
namespace MaterialUtils namespace MaterialUtils
@ -48,11 +49,14 @@ namespace AZ
//! @return if resolving is successful. An error will be reported if it fails. //! @return if resolving is successful. An error will be reported if it fails.
bool ResolveMaterialPropertyEnumValue(const MaterialPropertyDescriptor* propertyDescriptor, const AZ::Name& enumName, MaterialPropertyValue& outResolvedValue); bool ResolveMaterialPropertyEnumValue(const MaterialPropertyDescriptor* propertyDescriptor, const AZ::Name& enumName, MaterialPropertyValue& outResolvedValue);
//! Load material type from a json file. If the file path is relative, the loaded json document must be provided. //! Load a material type from a json file. If the file path is relative, the loaded json document must be provided.
//! Otherwise, it will use the passed in document first if not null, or load the json document from the path. //! Otherwise, it will use the passed in document first if not null, or load the json document from the path.
//! @param filePath a relative path if document is provided, an absolute path if document is not provided. //! @param filePath a relative path if document is provided, an absolute path if document is not provided.
//! @param document the loaded json document. //! @param document the loaded json document.
AZ::Outcome<MaterialTypeSourceData> LoadMaterialTypeSourceData(const AZStd::string& filePath, const rapidjson::Value* document = nullptr); AZ::Outcome<MaterialTypeSourceData> LoadMaterialTypeSourceData(const AZStd::string& filePath, const rapidjson::Value* document = nullptr);
//! Load a material from a json file.
AZ::Outcome<MaterialSourceData> LoadMaterialSourceData(const AZStd::string& filePath, const rapidjson::Value* document = nullptr, bool warningsAsErrors = false);
//! Utility function for custom JSON serializers to report results as "Skipped" when encountering keys that aren't recognized //! Utility function for custom JSON serializers to report results as "Skipped" when encountering keys that aren't recognized
//! as part of the custom format. //! as part of the custom format.

@ -52,7 +52,7 @@ namespace AZ
{ {
AssetBuilderSDK::AssetBuilderDesc materialBuilderDescriptor; AssetBuilderSDK::AssetBuilderDesc materialBuilderDescriptor;
materialBuilderDescriptor.m_name = JobKey; materialBuilderDescriptor.m_name = JobKey;
materialBuilderDescriptor.m_version = 117; // new material type file format materialBuilderDescriptor.m_version = 119; // new material file format
materialBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.material", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); materialBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.material", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
materialBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.materialtype", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); materialBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.materialtype", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
materialBuilderDescriptor.m_busId = azrtti_typeid<MaterialBuilder>(); materialBuilderDescriptor.m_busId = azrtti_typeid<MaterialBuilder>();
@ -129,38 +129,6 @@ namespace AZ
} }
} }
template<typename MaterialSourceDataT>
AZ::Outcome<MaterialSourceDataT> LoadSourceData(const rapidjson::Value& value, const AZStd::string& filePath)
{
MaterialSourceDataT material;
JsonDeserializerSettings settings;
JsonReportingHelper reportingHelper;
reportingHelper.Attach(settings);
// This is required by some custom material serializers to support relative path references.
JsonFileLoadContext fileLoadContext;
fileLoadContext.PushFilePath(filePath);
settings.m_metadata.Add(fileLoadContext);
JsonSerialization::Load(material, value, settings);
if (reportingHelper.ErrorsReported())
{
return AZ::Failure();
}
else if (reportingHelper.WarningsReported())
{
AZ_Error(MaterialBuilderName, false, "Warnings reported while loading '%s'", filePath.c_str());
return AZ::Failure();
}
else
{
return AZ::Success(AZStd::move(material));
}
}
void MaterialBuilder::CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response) const void MaterialBuilder::CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response) const
{ {
if (m_isShuttingDown) if (m_isShuttingDown)
@ -295,7 +263,7 @@ namespace AZ
AZ::Data::Asset<MaterialAsset> MaterialBuilder::CreateMaterialAsset(AZStd::string_view materialSourceFilePath, const rapidjson::Value& json) const AZ::Data::Asset<MaterialAsset> MaterialBuilder::CreateMaterialAsset(AZStd::string_view materialSourceFilePath, const rapidjson::Value& json) const
{ {
auto material = LoadSourceData<MaterialSourceData>(json, materialSourceFilePath); auto material = MaterialUtils::LoadMaterialSourceData(materialSourceFilePath, &json, true);
if (!material.IsSuccess()) if (!material.IsSuccess())
{ {

@ -54,10 +54,11 @@ namespace AZ
->Field("materialType", &MaterialSourceData::m_materialType) ->Field("materialType", &MaterialSourceData::m_materialType)
->Field("materialTypeVersion", &MaterialSourceData::m_materialTypeVersion) ->Field("materialTypeVersion", &MaterialSourceData::m_materialTypeVersion)
->Field("parentMaterial", &MaterialSourceData::m_parentMaterial) ->Field("parentMaterial", &MaterialSourceData::m_parentMaterial)
->Field("properties", &MaterialSourceData::m_properties) ->Field("properties", &MaterialSourceData::m_propertiesOld)
->Field("propertyValues", &MaterialSourceData::m_propertyValues)
; ;
serializeContext->RegisterGenericType<PropertyMap>(); serializeContext->RegisterGenericType<PropertyValueMap>();
serializeContext->RegisterGenericType<PropertyGroupMap>(); serializeContext->RegisterGenericType<PropertyGroupMap>();
} }
} }
@ -73,6 +74,55 @@ namespace AZ
} }
} }
void MaterialSourceData::SetPropertyValue(const Name& propertyId, const MaterialPropertyValue& value)
{
if (!propertyId.IsEmpty())
{
m_propertyValues[propertyId] = value;
}
}
const MaterialPropertyValue& MaterialSourceData::GetPropertyValue(const Name& propertyId) const
{
auto iter = m_propertyValues.find(propertyId);
if (iter == m_propertyValues.end())
{
return m_invalidValue;
}
else
{
return iter->second;
}
}
void MaterialSourceData::RemovePropertyValue(const Name& propertyId)
{
m_propertyValues.erase(propertyId);
}
MaterialSourceData::PropertyValueMap MaterialSourceData::GetPropertyValues() const
{
return m_propertyValues;
}
bool MaterialSourceData::HasPropertyValue(const Name& propertyId) const
{
return m_propertyValues.find(propertyId) != m_propertyValues.end();
}
void MaterialSourceData::ConvertToNewDataFormat()
{
for (auto& [groupName, propertyList] : m_propertiesOld)
{
for (auto& [propertyName, propertyValue] : propertyList)
{
SetPropertyValue(MaterialPropertyId{groupName, propertyName}, propertyValue);
}
}
m_propertiesOld.clear();
}
Outcome<Data::Asset<MaterialAsset>> MaterialSourceData::CreateMaterialAsset( Outcome<Data::Asset<MaterialAsset>> MaterialSourceData::CreateMaterialAsset(
Data::AssetId assetId, AZStd::string_view materialSourceFilePath, MaterialAssetProcessingMode processingMode, bool elevateWarnings) const Data::AssetId assetId, AZStd::string_view materialSourceFilePath, MaterialAssetProcessingMode processingMode, bool elevateWarnings) const
{ {
@ -250,12 +300,14 @@ namespace AZ
return Failure(); return Failure();
} }
MaterialSourceData parentSourceData; auto loadParentResult = MaterialUtils::LoadMaterialSourceData(parentSourceAbsPath);
if (!AZ::RPI::JsonUtils::LoadObjectFromFile(parentSourceAbsPath, parentSourceData)) if (!loadParentResult)
{ {
AZ_Error("MaterialSourceData", false, "Failed to load MaterialSourceData for parent material: '%s'.", parentSourceAbsPath.c_str()); AZ_Error("MaterialSourceData", false, "Failed to load MaterialSourceData for parent material: '%s'.", parentSourceAbsPath.c_str());
return Failure(); return Failure();
} }
MaterialSourceData parentSourceData = loadParentResult.TakeValue();
// Make sure that all materials in the hierarchy share the same material type // Make sure that all materials in the hierarchy share the same material type
const auto parentTypeAssetId = AssetUtils::MakeAssetId(parentSourceAbsPath, parentSourceData.m_materialType, 0); const auto parentTypeAssetId = AssetUtils::MakeAssetId(parentSourceAbsPath, parentSourceData.m_materialType, 0);
@ -314,30 +366,29 @@ namespace AZ
void MaterialSourceData::ApplyPropertiesToAssetCreator( void MaterialSourceData::ApplyPropertiesToAssetCreator(
AZ::RPI::MaterialAssetCreator& materialAssetCreator, const AZStd::string_view& materialSourceFilePath) const AZ::RPI::MaterialAssetCreator& materialAssetCreator, const AZStd::string_view& materialSourceFilePath) const
{ {
for (auto& group : m_properties) for (auto& [propertyId, propertyValue] : m_propertyValues)
{ {
for (auto& property : group.second) if (!propertyValue.IsValid())
{
materialAssetCreator.ReportWarning("Source data for material property value is invalid.");
}
else
{ {
MaterialPropertyId propertyId{ group.first, property.first };
if (!property.second.IsValid())
{
materialAssetCreator.ReportWarning("Source data for material property value is invalid.");
}
// If the source value type is a string, there are two possible property types: Image and Enum. If there is a "." in // If the source value type is a string, there are two possible property types: Image and Enum. If there is a "." in
// the string (for the extension) we assume it's an Image and look up the referenced Asset. Otherwise, we can assume // the string (for the extension) we assume it's an Image and look up the referenced Asset. Otherwise, we can assume
// it's an Enum value and just preserve the original string. // it's an Enum value and just preserve the original string.
else if (property.second.Is<AZStd::string>() && AzFramework::StringFunc::Contains(property.second.GetValue<AZStd::string>(), ".")) if (propertyValue.Is<AZStd::string>() && AzFramework::StringFunc::Contains(propertyValue.GetValue<AZStd::string>(), "."))
{ {
Data::Asset<ImageAsset> imageAsset; Data::Asset<ImageAsset> imageAsset;
MaterialUtils::GetImageAssetResult result = MaterialUtils::GetImageAssetReference( MaterialUtils::GetImageAssetResult result = MaterialUtils::GetImageAssetReference(
imageAsset, materialSourceFilePath, property.second.GetValue<AZStd::string>()); imageAsset, materialSourceFilePath, propertyValue.GetValue<AZStd::string>());
if (result == MaterialUtils::GetImageAssetResult::Missing) if (result == MaterialUtils::GetImageAssetResult::Missing)
{ {
materialAssetCreator.ReportWarning( materialAssetCreator.ReportWarning(
"Material property '%s': Could not find the image '%s'", propertyId.GetCStr(), "Material property '%s': Could not find the image '%s'", propertyId.GetCStr(),
property.second.GetValue<AZStd::string>().data()); propertyValue.GetValue<AZStd::string>().data());
} }
imageAsset.SetAutoLoadBehavior(Data::AssetLoadBehavior::PreLoad); imageAsset.SetAutoLoadBehavior(Data::AssetLoadBehavior::PreLoad);
@ -345,7 +396,7 @@ namespace AZ
} }
else else
{ {
materialAssetCreator.SetPropertyValue(propertyId, property.second); materialAssetCreator.SetPropertyValue(propertyId, propertyValue);
} }
} }
} }

@ -100,9 +100,9 @@ namespace AZ
serializeContext->Class<PropertyLayout>() serializeContext->Class<PropertyLayout>()
->Version(3) // Added propertyGroups ->Version(3) // Added propertyGroups
->Field("version", &PropertyLayout::m_versionOld) //< Deprecated, preserved for backward compatibility, replaced by MaterialTypeSourceData::version ->Field("version", &PropertyLayout::m_versionOld) //< @deprecated: preserved for backward compatibility, replaced by MaterialTypeSourceData::version
->Field("groups", &PropertyLayout::m_groupsOld) //< Deprecated, preserved for backward compatibility, replaced by propertyGroups ->Field("groups", &PropertyLayout::m_groupsOld) //< @deprecated: preserved for backward compatibility, replaced by propertyGroups
->Field("properties", &PropertyLayout::m_propertiesOld) //< Deprecated, preserved for backward compatibility, replaced by propertyGroups ->Field("properties", &PropertyLayout::m_propertiesOld) //< @deprecated: preserved for backward compatibility, replaced by propertyGroups
->Field("propertyGroups", &PropertyLayout::m_propertyGroups) ->Field("propertyGroups", &PropertyLayout::m_propertyGroups)
; ;

@ -12,6 +12,7 @@
#include <Atom/RPI.Reflect/Image/StreamingImageAsset.h> #include <Atom/RPI.Reflect/Image/StreamingImageAsset.h>
#include <Atom/RPI.Reflect/Material/MaterialAsset.h> #include <Atom/RPI.Reflect/Material/MaterialAsset.h>
#include <Atom/RPI.Reflect/Material/MaterialTypeAsset.h> #include <Atom/RPI.Reflect/Material/MaterialTypeAsset.h>
#include <Atom/RPI.Edit/Material/MaterialSourceData.h>
#include <Atom/RPI.Edit/Material/MaterialTypeSourceData.h> #include <Atom/RPI.Edit/Material/MaterialTypeSourceData.h>
#include <Atom/RPI.Edit/Common/JsonReportingHelper.h> #include <Atom/RPI.Edit/Common/JsonReportingHelper.h>
#include <Atom/RPI.Edit/Common/JsonFileLoadContext.h> #include <Atom/RPI.Edit/Common/JsonFileLoadContext.h>
@ -82,7 +83,7 @@ namespace AZ
loadOutcome = AZ::JsonSerializationUtils::ReadJsonFile(filePath, AZ::RPI::JsonUtils::DefaultMaxFileSize); loadOutcome = AZ::JsonSerializationUtils::ReadJsonFile(filePath, AZ::RPI::JsonUtils::DefaultMaxFileSize);
if (!loadOutcome.IsSuccess()) if (!loadOutcome.IsSuccess())
{ {
AZ_Error("AZ::RPI::JsonUtils", false, "%s", loadOutcome.GetError().c_str()); AZ_Error("MaterialUtils", false, "%s", loadOutcome.GetError().c_str());
return AZ::Failure(); return AZ::Failure();
} }
@ -114,6 +115,46 @@ namespace AZ
return AZ::Success(AZStd::move(materialType)); return AZ::Success(AZStd::move(materialType));
} }
} }
AZ::Outcome<MaterialSourceData> LoadMaterialSourceData(const AZStd::string& filePath, const rapidjson::Value* document, bool warningsAsErrors)
{
AZ::Outcome<rapidjson::Document, AZStd::string> loadOutcome;
if (document == nullptr)
{
loadOutcome = AZ::JsonSerializationUtils::ReadJsonFile(filePath, AZ::RPI::JsonUtils::DefaultMaxFileSize);
if (!loadOutcome.IsSuccess())
{
AZ_Error("MaterialUtils", false, "%s", loadOutcome.GetError().c_str());
return AZ::Failure();
}
document = &loadOutcome.GetValue();
}
MaterialSourceData material;
JsonDeserializerSettings settings;
JsonReportingHelper reportingHelper;
reportingHelper.Attach(settings);
JsonSerialization::Load(material, *document, settings);
material.ConvertToNewDataFormat();
if (reportingHelper.ErrorsReported())
{
return AZ::Failure();
}
else if (warningsAsErrors && reportingHelper.WarningsReported())
{
AZ_Error("MaterialUtils", false, "Warnings reported while loading '%s'", filePath.c_str());
return AZ::Failure();
}
else
{
return AZ::Success(AZStd::move(material));
}
}
void CheckForUnrecognizedJsonFields(const AZStd::string_view* acceptedFieldNames, uint32_t acceptedFieldNameCount, const rapidjson::Value& object, JsonDeserializerContext& context, JsonSerializationResult::ResultCode &result) void CheckForUnrecognizedJsonFields(const AZStd::string_view* acceptedFieldNames, uint32_t acceptedFieldNameCount, const rapidjson::Value& object, JsonDeserializerContext& context, JsonSerializationResult::ResultCode &result)
{ {

@ -86,7 +86,7 @@ namespace UnitTest
RPITestFixture::TearDown(); RPITestFixture::TearDown();
} }
Data::Asset<MaterialTypeAsset> CreateTestMaterialTypeAsset(Data::AssetId assetId) Data::Asset<MaterialTypeAsset> CreateTestMaterialTypeAsset(Data::AssetId assetId)
{ {
const char* materialTypeJson = R"( const char* materialTypeJson = R"(
@ -146,21 +146,21 @@ namespace UnitTest
} }
)"; )";
MaterialTypeSourceData materialTypeSourceData; MaterialTypeSourceData materialTypeSourceData;
LoadTestDataFromJson(materialTypeSourceData, materialTypeJson); LoadTestDataFromJson(materialTypeSourceData, materialTypeJson);
return materialTypeSourceData.CreateMaterialTypeAsset(assetId).TakeValue(); return materialTypeSourceData.CreateMaterialTypeAsset(assetId).TakeValue();
} }
}; };
void AddPropertyGroup(MaterialSourceData& material, AZStd::string_view groupName) void AddPropertyGroup(MaterialSourceData&, AZStd::string_view)
{ {
material.m_properties.insert(groupName); // Old function, left blank intentionally
} }
void AddProperty(MaterialSourceData& material, AZStd::string_view groupName, AZStd::string_view propertyName, const MaterialPropertyValue& anyValue) void AddProperty(MaterialSourceData& material, AZStd::string_view groupName, AZStd::string_view propertyName, const MaterialPropertyValue& value)
{ {
material.m_properties[groupName][propertyName] = anyValue; MaterialPropertyId id{groupName, propertyName};
material.SetPropertyValue(id, value);
} }
TEST_F(MaterialSourceDataTests, CreateMaterialAsset_BasicProperties) TEST_F(MaterialSourceDataTests, CreateMaterialAsset_BasicProperties)
@ -359,80 +359,61 @@ namespace UnitTest
void CheckEqual(MaterialSourceData& a, MaterialSourceData& b) void CheckEqual(MaterialSourceData& a, MaterialSourceData& b)
{ {
EXPECT_STREQ(a.m_materialType.data(), b.m_materialType.data()); EXPECT_STREQ(a.m_materialType.c_str(), b.m_materialType.c_str());
EXPECT_STREQ(a.m_description.data(), b.m_description.data()); EXPECT_STREQ(a.m_description.c_str(), b.m_description.c_str());
EXPECT_STREQ(a.m_parentMaterial.data(), b.m_parentMaterial.data()); EXPECT_STREQ(a.m_parentMaterial.c_str(), b.m_parentMaterial.c_str());
EXPECT_EQ(a.m_materialTypeVersion, b.m_materialTypeVersion); EXPECT_EQ(a.m_materialTypeVersion, b.m_materialTypeVersion);
EXPECT_EQ(a.m_properties.size(), b.m_properties.size()); EXPECT_EQ(a.GetPropertyValues().size(), b.GetPropertyValues().size());
for (auto& groupA : a.m_properties)
{
AZStd::string groupName = groupA.first;
auto groupIterB = b.m_properties.find(groupName); for (auto& [propertyId, propertyValue] : a.GetPropertyValues())
if (groupIterB == b.m_properties.end()) {
if (!b.HasPropertyValue(propertyId))
{ {
EXPECT_TRUE(false) << "groupB[" << groupName.c_str() << "] not found"; EXPECT_TRUE(false) << "Property '" << propertyId.GetCStr() << "' not found in material B";
continue; continue;
} }
auto& groupB = *groupIterB; auto& propertyA = propertyValue;
auto& propertyB = b.GetPropertyValue(propertyId);
EXPECT_EQ(groupA.second.size(), groupB.second.size()) << " for group[" << groupName.c_str() << "]";
for (auto& propertyIterA : groupA.second)
{
AZStd::string propertyName = propertyIterA.first;
auto propertyIterB = groupB.second.find(propertyName); AZStd::string propertyReference = AZStd::string::format(" for property '%s'", propertyId.GetCStr());
if (propertyIterB == groupB.second.end())
{
EXPECT_TRUE(false) << "groupB[" << groupName.c_str() << "][" << propertyName.c_str() << "] not found";
continue;
}
auto& propertyA = propertyIterA.second;
auto& propertyB = propertyIterB->second;
AZStd::string propertyReference = AZStd::string::format(" for property '%s.%s'", groupName.c_str(), propertyName.c_str());
// We allow some types like Vector4 and Color or Int and UInt to be interchangeable since they serialize the same and can be converted when the MaterialAsset is finalized. // We allow some types like Vector4 and Color or Int and UInt to be interchangeable since they serialize the same and can be converted when the MaterialAsset is finalized.
if (AreTypesCompatible<bool>(propertyA, propertyB)) if (AreTypesCompatible<bool>(propertyA, propertyB))
{ {
EXPECT_EQ(propertyA.GetValue<bool>(), propertyB.GetValue<bool>()) << propertyReference.c_str(); EXPECT_EQ(propertyA.GetValue<bool>(), propertyB.GetValue<bool>()) << propertyReference.c_str();
} }
else if (AreTypesCompatible<int32_t>(propertyA, propertyB)) else if (AreTypesCompatible<int32_t>(propertyA, propertyB))
{ {
EXPECT_EQ(GetAsInt(propertyA), GetAsInt(propertyB)) << propertyReference.c_str(); EXPECT_EQ(GetAsInt(propertyA), GetAsInt(propertyB)) << propertyReference.c_str();
} }
else if (AreTypesCompatible<float>(propertyA, propertyB)) else if (AreTypesCompatible<float>(propertyA, propertyB))
{ {
EXPECT_NEAR(propertyA.GetValue<float>(), propertyB.GetValue<float>(), 0.01) << propertyReference.c_str(); EXPECT_NEAR(propertyA.GetValue<float>(), propertyB.GetValue<float>(), 0.01) << propertyReference.c_str();
} }
else if (AreTypesCompatible<Vector2>(propertyA, propertyB)) else if (AreTypesCompatible<Vector2>(propertyA, propertyB))
{ {
EXPECT_TRUE(propertyA.GetValue<Vector2>().IsClose(propertyB.GetValue<Vector2>())) << propertyReference.c_str(); EXPECT_TRUE(propertyA.GetValue<Vector2>().IsClose(propertyB.GetValue<Vector2>())) << propertyReference.c_str();
} }
else if (AreTypesCompatible<Vector3>(propertyA, propertyB)) else if (AreTypesCompatible<Vector3>(propertyA, propertyB))
{ {
EXPECT_TRUE(propertyA.GetValue<Vector3>().IsClose(propertyB.GetValue<Vector3>())) << propertyReference.c_str(); EXPECT_TRUE(propertyA.GetValue<Vector3>().IsClose(propertyB.GetValue<Vector3>())) << propertyReference.c_str();
} }
else if (AreTypesCompatible<Vector4>(propertyA, propertyB)) else if (AreTypesCompatible<Vector4>(propertyA, propertyB))
{ {
EXPECT_TRUE(GetAsVector4(propertyA).IsClose(GetAsVector4(propertyB))) << propertyReference.c_str(); EXPECT_TRUE(GetAsVector4(propertyA).IsClose(GetAsVector4(propertyB))) << propertyReference.c_str();
} }
else if (AreTypesCompatible<AZStd::string>(propertyA, propertyB)) else if (AreTypesCompatible<AZStd::string>(propertyA, propertyB))
{ {
EXPECT_STREQ(propertyA.GetValue<AZStd::string>().c_str(), propertyB.GetValue<AZStd::string>().c_str()) << propertyReference.c_str(); EXPECT_STREQ(propertyA.GetValue<AZStd::string>().c_str(), propertyB.GetValue<AZStd::string>().c_str()) << propertyReference.c_str();
} }
else else
{ {
ADD_FAILURE(); ADD_FAILURE();
}
} }
} }
} }
TEST_F(MaterialSourceDataTests, TestJsonRoundTrip) TEST_F(MaterialSourceDataTests, TestJsonRoundTrip)
@ -465,6 +446,88 @@ namespace UnitTest
CheckEqual(sourceDataOriginal, sourceDataCopy); CheckEqual(sourceDataOriginal, sourceDataCopy);
} }
TEST_F(MaterialSourceDataTests, TestLoadLegacyFormat)
{
const AZStd::string inputJson = R"(
{
"materialType": "test.materialtype", // Doesn't matter, this isn't loaded
"properties": {
"groupA": {
"myBool": true,
"myInt": 5,
"myFloat": 0.5
},
"groupB": {
"myFloat2": [0.1, 0.2],
"myFloat3": [0.3, 0.4, 0.5],
"myFloat4": [0.6, 0.7, 0.8, 0.9],
"myString": "Hello"
}
}
}
)";
MaterialSourceData material;
LoadTestDataFromJson(material, inputJson);
material.ConvertToNewDataFormat();
MaterialSourceData expectedMaterial;
expectedMaterial.m_materialType = "test.materialtype";
expectedMaterial.SetPropertyValue(Name{"groupA.myBool"}, true);
expectedMaterial.SetPropertyValue(Name{"groupA.myInt"}, 5);
expectedMaterial.SetPropertyValue(Name{"groupA.myFloat"}, 0.5f);
expectedMaterial.SetPropertyValue(Name{"groupB.myFloat2"}, Vector2(0.1f, 0.2f));
expectedMaterial.SetPropertyValue(Name{"groupB.myFloat3"}, Vector3(0.3f, 0.4f, 0.5f));
expectedMaterial.SetPropertyValue(Name{"groupB.myFloat4"}, Vector4(0.6f, 0.7f, 0.8f, 0.9f));
expectedMaterial.SetPropertyValue(Name{"groupB.myString"}, AZStd::string{"Hello"});
CheckEqual(expectedMaterial, material);
}
TEST_F(MaterialSourceDataTests, TestPropertyValues)
{
MaterialSourceData material;
Name foo{"foo"};
Name bar{"bar"};
Name baz{"baz"};
EXPECT_EQ(0, material.GetPropertyValues().size());
EXPECT_FALSE(material.HasPropertyValue(foo));
EXPECT_FALSE(material.HasPropertyValue(bar));
EXPECT_FALSE(material.HasPropertyValue(baz));
EXPECT_FALSE(material.GetPropertyValue(foo).IsValid());
EXPECT_FALSE(material.GetPropertyValue(bar).IsValid());
EXPECT_FALSE(material.GetPropertyValue(baz).IsValid());
material.SetPropertyValue(Name{"foo"}, 2);
material.SetPropertyValue(Name{"bar"}, true);
material.SetPropertyValue(Name{"baz"}, 0.5f);
EXPECT_EQ(3, material.GetPropertyValues().size());
EXPECT_TRUE(material.HasPropertyValue(foo));
EXPECT_TRUE(material.HasPropertyValue(bar));
EXPECT_TRUE(material.HasPropertyValue(baz));
EXPECT_TRUE(material.GetPropertyValue(foo).IsValid());
EXPECT_TRUE(material.GetPropertyValue(bar).IsValid());
EXPECT_TRUE(material.GetPropertyValue(baz).IsValid());
EXPECT_EQ(material.GetPropertyValue(foo).GetValue<int32_t>(), 2);
EXPECT_EQ(material.GetPropertyValue(bar).GetValue<bool>(), true);
EXPECT_EQ(material.GetPropertyValue(baz).GetValue<float>(), 0.5f);
material.RemovePropertyValue(bar);
EXPECT_EQ(2, material.GetPropertyValues().size());
EXPECT_TRUE(material.HasPropertyValue(foo));
EXPECT_FALSE(material.HasPropertyValue(bar));
EXPECT_TRUE(material.HasPropertyValue(baz));
EXPECT_TRUE(material.GetPropertyValue(foo).IsValid());
EXPECT_FALSE(material.GetPropertyValue(bar).IsValid());
EXPECT_TRUE(material.GetPropertyValue(baz).IsValid());
EXPECT_EQ(material.GetPropertyValue(foo).GetValue<int32_t>(), 2);
EXPECT_EQ(material.GetPropertyValue(baz).GetValue<float>(), 0.5f);
}
TEST_F(MaterialSourceDataTests, Load_MaterialTypeAfterPropertyList) TEST_F(MaterialSourceDataTests, Load_MaterialTypeAfterPropertyList)
{ {
@ -487,21 +550,14 @@ namespace UnitTest
} }
)"; )";
const char* materialTypeFilePath = "@exefolder@/Temp/simpleMaterialType.materialtype"; AZ::Utils::WriteFile(simpleMaterialTypeJson, "@exefolder@/Temp/simpleMaterialType.materialtype");
AZ::IO::FileIOStream file;
EXPECT_TRUE(file.Open(materialTypeFilePath, AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath));
file.Write(simpleMaterialTypeJson.size(), simpleMaterialTypeJson.data());
file.Close();
// It shouldn't matter whether the materialType field appears before the property value list. This allows for the possibility // It shouldn't matter whether the materialType field appears before the property value list. This allows for the possibility
// that customer scripts generate material data and happen to use an unexpected order. // that customer scripts generate material data and happen to use an unexpected order.
const AZStd::string inputJson = R"( const AZStd::string inputJson = R"(
{ {
"properties": { "propertyValues": {
"general": { "general.testValue": 1.2
"testValue": 1.2
}
}, },
"materialType": "@exefolder@/Temp/simpleMaterialType.materialtype" "materialType": "@exefolder@/Temp/simpleMaterialType.materialtype"
} }
@ -513,7 +569,7 @@ namespace UnitTest
EXPECT_EQ(AZ::JsonSerializationResult::Tasks::ReadField, loadResult.m_jsonResultCode.GetTask()); EXPECT_EQ(AZ::JsonSerializationResult::Tasks::ReadField, loadResult.m_jsonResultCode.GetTask());
EXPECT_EQ(AZ::JsonSerializationResult::Processing::Completed, loadResult.m_jsonResultCode.GetProcessing()); EXPECT_EQ(AZ::JsonSerializationResult::Processing::Completed, loadResult.m_jsonResultCode.GetProcessing());
float testValue = material.m_properties["general"]["testValue"].GetValue<float>(); float testValue = material.GetPropertyValue(Name{"general.testValue"}).GetValue<float>();
EXPECT_FLOAT_EQ(1.2f, testValue); EXPECT_FLOAT_EQ(1.2f, testValue);
} }
@ -522,10 +578,8 @@ namespace UnitTest
const AZStd::string inputJson = R"( const AZStd::string inputJson = R"(
{ {
"materialTypeVersion": 1, "materialTypeVersion": 1,
"properties": { "propertyValues": {
"baseColor": { "baseColor.color": [1.0,1.0,1.0]
"color": [1.0,1.0,1.0]
}
} }
} }
)"; )";
@ -561,10 +615,8 @@ namespace UnitTest
{ {
"materialType": "DoesNotExist.materialtype", "materialType": "DoesNotExist.materialtype",
"materialTypeVersion": 1, "materialTypeVersion": 1,
"properties": { "propertyValues": {
"baseColor": { "baseColor.color": [1.0,1.0,1.0]
"color": [1.0,1.0,1.0]
}
} }
} }
)"; )";
@ -900,10 +952,8 @@ namespace UnitTest
const AZStd::string inputJson = AZStd::string::format(R"( const AZStd::string inputJson = AZStd::string::format(R"(
{ {
"materialType": "@exefolder@/Temp/test.materialtype", "materialType": "@exefolder@/Temp/test.materialtype",
"properties": { "propertyValues": {
"%s": { "%s.%s": %s
"%s": %s
}
} }
} }
)", groupName, propertyName, jsonValue); )", groupName, propertyName, jsonValue);
@ -965,7 +1015,239 @@ namespace UnitTest
CheckEndToEndDataTypeResolution("MyFloat4", "{\"y\":0.2, \"x\":0.1, \"Z\":0.3}", Vector4{0.1f, 0.2f, 0.3f, 0.0f}); CheckEndToEndDataTypeResolution("MyFloat4", "{\"y\":0.2, \"x\":0.1, \"Z\":0.3}", Vector4{0.1f, 0.2f, 0.3f, 0.0f});
CheckEndToEndDataTypeResolution("MyFloat4", "{\"y\":0.2, \"W\":0.4, \"x\":0.1, \"Z\":0.3}", Vector4{0.1f, 0.2f, 0.3f, 0.4f}); CheckEndToEndDataTypeResolution("MyFloat4", "{\"y\":0.2, \"W\":0.4, \"x\":0.1, \"Z\":0.3}", Vector4{0.1f, 0.2f, 0.3f, 0.4f});
} }
TEST_F(MaterialSourceDataTests, CreateMaterialAssetFromSourceData_MultiLevelDataInheritance)
{
// Note the data being tested here is based on CreateMaterialAsset_MultiLevelDataInheritance()
const AZStd::string simpleMaterialTypeJson = R"(
{
"propertyLayout": {
"propertyGroups":
[
{
"name": "general",
"properties": [
{
"name": "MyFloat",
"type": "Float"
},
{
"name": "MyFloat2",
"type": "Vector2"
},
{
"name": "MyColor",
"type": "Color"
}
]
}
]
}
}
)";
AZ::Utils::WriteFile(simpleMaterialTypeJson, "@exefolder@/Temp/test.materialtype");
const AZStd::string material1Json = R"(
{
"materialType": "@exefolder@/Temp/test.materialtype",
"propertyValues": {
"general.MyFloat": 1.5,
"general.MyColor": [0.1, 0.2, 0.3, 0.4]
}
}
)";
AZ::Utils::WriteFile(material1Json, "@exefolder@/Temp/m1.material");
const AZStd::string material2Json = R"(
{
"materialType": "@exefolder@/Temp/test.materialtype",
"parentMaterial": "@exefolder@/Temp/m1.material",
"propertyValues": {
"general.MyFloat2": [4.1, 4.2],
"general.MyColor": [0.15, 0.25, 0.35, 0.45]
}
}
)";
AZ::Utils::WriteFile(material2Json, "@exefolder@/Temp/m2.material");
const AZStd::string material3Json = R"(
{
"materialType": "@exefolder@/Temp/test.materialtype",
"parentMaterial": "@exefolder@/Temp/m2.material",
"propertyValues": {
"general.MyFloat": 3.5
}
}
)";
AZ::Utils::WriteFile(material3Json, "@exefolder@/Temp/m3.material");
MaterialSourceData sourceDataLevel1 = MaterialUtils::LoadMaterialSourceData("@exefolder@/Temp/m1.material").TakeValue();
MaterialSourceData sourceDataLevel2 = MaterialUtils::LoadMaterialSourceData("@exefolder@/Temp/m2.material").TakeValue();
MaterialSourceData sourceDataLevel3 = MaterialUtils::LoadMaterialSourceData("@exefolder@/Temp/m3.material").TakeValue();
auto materialAssetLevel1 = sourceDataLevel1.CreateMaterialAssetFromSourceData(Uuid::CreateRandom());
EXPECT_TRUE(materialAssetLevel1.IsSuccess());
EXPECT_TRUE(materialAssetLevel1.GetValue()->WasPreFinalized());
auto materialAssetLevel2 = sourceDataLevel2.CreateMaterialAssetFromSourceData(Uuid::CreateRandom());
EXPECT_TRUE(materialAssetLevel2.IsSuccess());
EXPECT_TRUE(materialAssetLevel2.GetValue()->WasPreFinalized());
auto materialAssetLevel3 = sourceDataLevel3.CreateMaterialAssetFromSourceData(Uuid::CreateRandom());
EXPECT_TRUE(materialAssetLevel3.IsSuccess());
EXPECT_TRUE(materialAssetLevel3.GetValue()->WasPreFinalized());
auto layout = materialAssetLevel1.GetValue()->GetMaterialPropertiesLayout();
MaterialPropertyIndex myFloat = layout->FindPropertyIndex(Name("general.MyFloat"));
MaterialPropertyIndex myFloat2 = layout->FindPropertyIndex(Name("general.MyFloat2"));
MaterialPropertyIndex myColor = layout->FindPropertyIndex(Name("general.MyColor"));
AZStd::span<const MaterialPropertyValue> properties;
// Check level 1 properties
properties = materialAssetLevel1.GetValue()->GetPropertyValues();
EXPECT_EQ(properties[myFloat.GetIndex()].GetValue<float>(), 1.5f);
EXPECT_EQ(properties[myFloat2.GetIndex()].GetValue<Vector2>(), Vector2(0.0f, 0.0f));
EXPECT_EQ(properties[myColor.GetIndex()].GetValue<Color>(), Color(0.1f, 0.2f, 0.3f, 0.4f));
// Check level 2 properties
properties = materialAssetLevel2.GetValue()->GetPropertyValues();
EXPECT_EQ(properties[myFloat.GetIndex()].GetValue<float>(), 1.5f);
EXPECT_EQ(properties[myFloat2.GetIndex()].GetValue<Vector2>(), Vector2(4.1f, 4.2f));
EXPECT_EQ(properties[myColor.GetIndex()].GetValue<Color>(), Color(0.15f, 0.25f, 0.35f, 0.45f));
// Check level 3 properties
properties = materialAssetLevel3.GetValue()->GetPropertyValues();
EXPECT_EQ(properties[myFloat.GetIndex()].GetValue<float>(), 3.5f);
EXPECT_EQ(properties[myFloat2.GetIndex()].GetValue<Vector2>(), Vector2(4.1f, 4.2f));
EXPECT_EQ(properties[myColor.GetIndex()].GetValue<Color>(), Color(0.15f, 0.25f, 0.35f, 0.45f));
}
TEST_F(MaterialSourceDataTests, CreateMaterialAssetFromSourceData_MultiLevelDataInheritance_OldFormat)
{
// This test is the same as CreateMaterialAssetFromSourceData_MultiLevelDataInheritance except it uses the old format
// where material property values in the .material file were nested, with properties listed under a group object,
// rather than using a flat list of property values.
// Basically, we are making sure that MaterialSourceData::ConvertToNewDataFormat() is getting called.
const AZStd::string simpleMaterialTypeJson = R"(
{
"propertyLayout": {
"propertyGroups":
[
{
"name": "general",
"properties": [
{
"name": "MyFloat",
"type": "Float"
},
{
"name": "MyFloat2",
"type": "Vector2"
},
{
"name": "MyColor",
"type": "Color"
}
]
}
]
}
}
)";
AZ::Utils::WriteFile(simpleMaterialTypeJson, "@exefolder@/Temp/test.materialtype");
const AZStd::string material1Json = R"(
{
"materialType": "@exefolder@/Temp/test.materialtype",
"properties": {
"general": {
"MyFloat": 1.5,
"MyColor": [0.1, 0.2, 0.3, 0.4]
}
}
}
)";
AZ::Utils::WriteFile(material1Json, "@exefolder@/Temp/m1.material");
const AZStd::string material2Json = R"(
{
"materialType": "@exefolder@/Temp/test.materialtype",
"parentMaterial": "@exefolder@/Temp/m1.material",
"properties": {
"general": {
"MyFloat2": [4.1, 4.2],
"MyColor": [0.15, 0.25, 0.35, 0.45]
}
}
}
)";
AZ::Utils::WriteFile(material2Json, "@exefolder@/Temp/m2.material");
const AZStd::string material3Json = R"(
{
"materialType": "@exefolder@/Temp/test.materialtype",
"parentMaterial": "@exefolder@/Temp/m2.material",
"properties": {
"general": {
"MyFloat": 3.5
}
}
}
)";
AZ::Utils::WriteFile(material3Json, "@exefolder@/Temp/m3.material");
MaterialSourceData sourceDataLevel1 = MaterialUtils::LoadMaterialSourceData("@exefolder@/Temp/m1.material").TakeValue();
MaterialSourceData sourceDataLevel2 = MaterialUtils::LoadMaterialSourceData("@exefolder@/Temp/m2.material").TakeValue();
MaterialSourceData sourceDataLevel3 = MaterialUtils::LoadMaterialSourceData("@exefolder@/Temp/m3.material").TakeValue();
auto materialAssetLevel1 = sourceDataLevel1.CreateMaterialAssetFromSourceData(Uuid::CreateRandom());
EXPECT_TRUE(materialAssetLevel1.IsSuccess());
EXPECT_TRUE(materialAssetLevel1.GetValue()->WasPreFinalized());
auto materialAssetLevel2 = sourceDataLevel2.CreateMaterialAssetFromSourceData(Uuid::CreateRandom());
EXPECT_TRUE(materialAssetLevel2.IsSuccess());
EXPECT_TRUE(materialAssetLevel2.GetValue()->WasPreFinalized());
auto materialAssetLevel3 = sourceDataLevel3.CreateMaterialAssetFromSourceData(Uuid::CreateRandom());
EXPECT_TRUE(materialAssetLevel3.IsSuccess());
EXPECT_TRUE(materialAssetLevel3.GetValue()->WasPreFinalized());
auto layout = materialAssetLevel1.GetValue()->GetMaterialPropertiesLayout();
MaterialPropertyIndex myFloat = layout->FindPropertyIndex(Name("general.MyFloat"));
MaterialPropertyIndex myFloat2 = layout->FindPropertyIndex(Name("general.MyFloat2"));
MaterialPropertyIndex myColor = layout->FindPropertyIndex(Name("general.MyColor"));
AZStd::span<const MaterialPropertyValue> properties;
// Check level 1 properties
properties = materialAssetLevel1.GetValue()->GetPropertyValues();
EXPECT_EQ(properties[myFloat.GetIndex()].GetValue<float>(), 1.5f);
EXPECT_EQ(properties[myFloat2.GetIndex()].GetValue<Vector2>(), Vector2(0.0f, 0.0f));
EXPECT_EQ(properties[myColor.GetIndex()].GetValue<Color>(), Color(0.1f, 0.2f, 0.3f, 0.4f));
// Check level 2 properties
properties = materialAssetLevel2.GetValue()->GetPropertyValues();
EXPECT_EQ(properties[myFloat.GetIndex()].GetValue<float>(), 1.5f);
EXPECT_EQ(properties[myFloat2.GetIndex()].GetValue<Vector2>(), Vector2(4.1f, 4.2f));
EXPECT_EQ(properties[myColor.GetIndex()].GetValue<Color>(), Color(0.15f, 0.25f, 0.35f, 0.45f));
// Check level 3 properties
properties = materialAssetLevel3.GetValue()->GetPropertyValues();
EXPECT_EQ(properties[myFloat.GetIndex()].GetValue<float>(), 3.5f);
EXPECT_EQ(properties[myFloat2.GetIndex()].GetValue<Vector2>(), Vector2(4.1f, 4.2f));
EXPECT_EQ(properties[myColor.GetIndex()].GetValue<Color>(), Color(0.15f, 0.25f, 0.35f, 0.45f));
}
} }

@ -362,8 +362,8 @@ namespace MaterialEditor
} }
// TODO: Support populating the Material Editor with nested property groups, not just the top level. // TODO: Support populating the Material Editor with nested property groups, not just the top level.
const AZStd::string groupName = propertyId.GetStringView().substr(0, propertyId.GetStringView().size() - propertyDefinition->GetName().size() - 1); AZStd::string_view groupName = propertyId.GetStringView().substr(0, propertyId.GetStringView().size() - propertyDefinition->GetName().size() - 1);
sourceData.m_properties[groupName][propertyDefinition->GetName()] = propertyValue; sourceData.SetPropertyValue(AZ::RPI::MaterialPropertyId{groupName, propertyDefinition->GetName()}, propertyValue);
} }
} }
return true; return true;
@ -395,12 +395,15 @@ namespace MaterialEditor
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 // 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)) auto loadResult = AZ::RPI::MaterialUtils::LoadMaterialSourceData(m_absolutePath);
if (!loadResult)
{ {
AZ_Error("MaterialDocument", false, "Material source data could not be loaded: '%s'.", m_absolutePath.c_str()); AZ_Error("MaterialDocument", false, "Material source data could not be loaded: '%s'.", m_absolutePath.c_str());
return OpenFailed(); return OpenFailed();
} }
m_materialSourceData = loadResult.TakeValue();
// We always need the absolute path for the material type and parent material to load source data and resolving // 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. // relative paths when saving. This will convert and store them as absolute paths for use within the document.
if (!m_materialSourceData.m_parentMaterial.empty()) if (!m_materialSourceData.m_parentMaterial.empty())
@ -482,12 +485,15 @@ namespace MaterialEditor
if (!m_materialSourceData.m_parentMaterial.empty()) if (!m_materialSourceData.m_parentMaterial.empty())
{ {
AZ::RPI::MaterialSourceData parentMaterialSourceData; AZ::RPI::MaterialSourceData parentMaterialSourceData;
if (!AZ::RPI::JsonUtils::LoadObjectFromFile(m_materialSourceData.m_parentMaterial, parentMaterialSourceData)) auto loadResult = AZ::RPI::MaterialUtils::LoadMaterialSourceData(m_materialSourceData.m_parentMaterial);
if (!loadResult)
{ {
AZ_Error("MaterialDocument", false, "Material parent source data could not be loaded for: '%s'.", m_materialSourceData.m_parentMaterial.c_str()); AZ_Error("MaterialDocument", false, "Material parent source data could not be loaded for: '%s'.", m_materialSourceData.m_parentMaterial.c_str());
return OpenFailed(); return OpenFailed();
} }
parentMaterialSourceData = loadResult.TakeValue();
const auto parentMaterialAssetIdResult = AZ::RPI::AssetUtils::MakeAssetId(m_materialSourceData.m_parentMaterial, 0); const auto parentMaterialAssetIdResult = AZ::RPI::AssetUtils::MakeAssetId(m_materialSourceData.m_parentMaterial, 0);
if (!parentMaterialAssetIdResult) if (!parentMaterialAssetIdResult)
{ {

@ -149,8 +149,8 @@ namespace AZ
} }
// TODO: Support populating the Material Editor with nested property groups, not just the top level. // TODO: Support populating the Material Editor with nested property groups, not just the top level.
const AZStd::string groupName = propertyId.GetStringView().substr(0, propertyId.GetStringView().size() - propertyDefinition->GetName().size() - 1); AZStd::string_view groupName = propertyId.GetStringView().substr(0, propertyId.GetStringView().size() - propertyDefinition->GetName().size() - 1);
exportData.m_properties[groupName][propertyDefinition->GetName()] = propertyValue; exportData.SetPropertyValue(RPI::MaterialPropertyId{groupName, propertyDefinition->GetName()}, propertyValue);
return true; return true;
}); });

Loading…
Cancel
Save