EditorMaterialComponentUtil.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <Material/EditorMaterialComponentUtil.h>
  9. #include <Atom/RPI.Edit/Common/AssetUtils.h>
  10. #include <Atom/RPI.Edit/Common/JsonUtils.h>
  11. #include <Atom/RPI.Edit/Material/MaterialPropertyId.h>
  12. #include <Atom/RPI.Edit/Material/MaterialSourceData.h>
  13. #include <Atom/RPI.Edit/Material/MaterialTypeSourceData.h>
  14. #include <Atom/RPI.Edit/Material/MaterialUtils.h>
  15. #include <Atom/RPI.Reflect/Material/MaterialAsset.h>
  16. #include <Atom/RPI.Reflect/Material/MaterialNameContext.h>
  17. #include <Atom/RPI.Reflect/Material/MaterialPropertiesLayout.h>
  18. #include <Atom/RPI.Reflect/Material/MaterialTypeAsset.h>
  19. #include <AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h>
  20. #include <AtomToolsFramework/Util/MaterialPropertyUtil.h>
  21. #include <AtomToolsFramework/Util/Util.h>
  22. #include <AzCore/std/ranges/elements_view.h>
  23. #include <AzFramework/API/ApplicationAPI.h>
  24. #include <AzToolsFramework/API/EditorAssetSystemAPI.h>
  25. #include <AzToolsFramework/API/EntityPropertyEditorRequestsBus.h>
  26. #include <AzToolsFramework/API/ToolsApplicationAPI.h>
  27. namespace AZ
  28. {
  29. namespace Render
  30. {
  31. namespace EditorMaterialComponentUtil
  32. {
  33. bool LoadMaterialEditDataFromAssetId(const AZ::Data::AssetId& assetId, MaterialEditData& editData)
  34. {
  35. editData = MaterialEditData();
  36. if (!assetId.IsValid())
  37. {
  38. AZ_Warning("AZ::Render::EditorMaterialComponentUtil", false, "Attempted to load material data for invalid asset id.");
  39. return false;
  40. }
  41. editData.m_materialAssetId = assetId;
  42. // Load the originating product asset from which the new source has set will be generated
  43. auto materialAssetOutcome = AZ::RPI::AssetUtils::LoadAsset<AZ::RPI::MaterialAsset>(editData.m_materialAssetId);
  44. if (!materialAssetOutcome)
  45. {
  46. AZ_Error("AZ::Render::EditorMaterialComponentUtil", false, "Failed to load material asset: %s", editData.m_materialAssetId.ToString<AZStd::string>().c_str());
  47. return false;
  48. }
  49. editData.m_materialAsset = materialAssetOutcome.GetValue();
  50. editData.m_materialTypeAsset = editData.m_materialAsset->GetMaterialTypeAsset();
  51. editData.m_materialParentAsset = {};
  52. editData.m_materialSourcePath = AZ::RPI::AssetUtils::GetSourcePathByAssetId(editData.m_materialAsset.GetId());
  53. if (AzFramework::StringFunc::Path::IsExtension(
  54. editData.m_materialSourcePath.c_str(), AZ::RPI::MaterialSourceData::Extension))
  55. {
  56. if (!AZ::RPI::JsonUtils::LoadObjectFromFile(editData.m_materialSourcePath, editData.m_materialSourceData))
  57. {
  58. AZ_Error("AZ::Render::EditorMaterialComponentUtil", false, "Material source data could not be loaded: '%s'.", editData.m_materialSourcePath.c_str());
  59. return false;
  60. }
  61. }
  62. if (!editData.m_materialSourceData.m_parentMaterial.empty())
  63. {
  64. // There is a parent for this material
  65. auto parentMaterialResult = AZ::RPI::AssetUtils::LoadAsset<AZ::RPI::MaterialAsset>(editData.m_materialSourcePath, editData.m_materialSourceData.m_parentMaterial);
  66. if (!parentMaterialResult)
  67. {
  68. AZ_Error("AZ::Render::EditorMaterialComponentUtil", false, "Parent material asset could not be loaded: '%s'.", editData.m_materialSourceData.m_parentMaterial.c_str());
  69. return false;
  70. }
  71. editData.m_materialParentAsset = parentMaterialResult.GetValue();
  72. editData.m_materialParentSourcePath = AZ::RPI::AssetUtils::GetSourcePathByAssetId(editData.m_materialParentAsset.GetId());
  73. }
  74. // We need a valid path to the material type source data to get the property layout and assign to the new material
  75. editData.m_materialTypeSourcePath = AZ::RPI::AssetUtils::GetSourcePathByAssetId(editData.m_materialTypeAsset.GetId());
  76. if (editData.m_materialTypeSourcePath.empty())
  77. {
  78. AZ_Error("AZ::Render::EditorMaterialComponentUtil", false, "Failed to locate source material type asset: %s", editData.m_materialAssetId.ToString<AZStd::string>().c_str());
  79. return false;
  80. }
  81. // With the introduction of the material pipeline, abstract material types, and intermediate assets, the material could
  82. // be referencing a generated material type in the intermediate asset folder. We need to map back to the original
  83. // material type.
  84. editData.m_originalMaterialTypeSourcePath =
  85. AZ::RPI::MaterialUtils::PredictOriginalMaterialTypeSourcePath(editData.m_materialTypeSourcePath);
  86. if (editData.m_originalMaterialTypeSourcePath.empty())
  87. {
  88. AZ_Error("AZ::Render::EditorMaterialComponentUtil", false, "Build to locate originating source material type asset: %s", editData.m_materialAssetId.ToString<AZStd::string>().c_str());
  89. return false;
  90. }
  91. // Load the material type source data
  92. auto materialTypeOutcome = AZ::RPI::MaterialUtils::LoadMaterialTypeSourceData(editData.m_materialTypeSourcePath);
  93. if (!materialTypeOutcome.IsSuccess())
  94. {
  95. AZ_Error("AZ::Render::EditorMaterialComponentUtil", false, "Failed to load material type source data: %s", editData.m_materialTypeSourcePath.c_str());
  96. return false;
  97. }
  98. editData.m_materialTypeSourceData = materialTypeOutcome.TakeValue();
  99. return true;
  100. }
  101. bool SaveSourceMaterialFromEditData(const AZStd::string& path, const MaterialEditData& editData)
  102. {
  103. if (path.empty() || !editData.m_materialAsset.IsReady() || !editData.m_materialTypeAsset.IsReady() ||
  104. editData.m_materialTypeSourcePath.empty() || editData.m_originalMaterialTypeSourcePath.empty())
  105. {
  106. AZ_Error("AZ::Render::EditorMaterialComponentUtil", false, "Can not export: %s", path.c_str());
  107. return false;
  108. }
  109. // Construct the material source data object that will be exported
  110. AZ::RPI::MaterialSourceData exportData;
  111. exportData.m_materialTypeVersion = editData.m_materialTypeAsset->GetVersion();
  112. // Source material files that should reference the originating source material type instead of the potential intermediate
  113. // material type asset.
  114. exportData.m_materialType = AtomToolsFramework::GetPathToExteralReference(path, editData.m_originalMaterialTypeSourcePath);
  115. exportData.m_parentMaterial = AtomToolsFramework::GetPathToExteralReference(path, editData.m_materialParentSourcePath);
  116. // Copy all of the properties from the material asset to the source data that will be exported
  117. bool result = true;
  118. editData.m_materialTypeSourceData.EnumerateProperties([&](const AZ::RPI::MaterialPropertySourceData* propertyDefinition, const AZ::RPI::MaterialNameContext& nameContext)
  119. {
  120. AZ::Name propertyId{propertyDefinition->GetName()};
  121. nameContext.ContextualizeProperty(propertyId);
  122. const AZ::RPI::MaterialPropertyIndex propertyIndex =
  123. editData.m_materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyId);
  124. AZ::RPI::MaterialPropertyValue propertyValue =
  125. editData.m_materialAsset->GetPropertyValues()[propertyIndex.GetIndex()];
  126. AZ::RPI::MaterialPropertyValue propertyValueDefault = propertyDefinition->m_value;
  127. if (editData.m_materialParentAsset.IsReady())
  128. {
  129. propertyValueDefault = editData.m_materialParentAsset->GetPropertyValues()[propertyIndex.GetIndex()];
  130. }
  131. // Check for and apply any property overrides before saving property values
  132. auto propertyOverrideItr = editData.m_materialPropertyOverrideMap.find(propertyId);
  133. if (propertyOverrideItr != editData.m_materialPropertyOverrideMap.end())
  134. {
  135. propertyValue = AZ::RPI::MaterialPropertyValue::FromAny(propertyOverrideItr->second);
  136. }
  137. if (!AtomToolsFramework::ConvertToExportFormat(path, propertyId, *propertyDefinition, propertyValue))
  138. {
  139. AZ_Error("AZ::Render::EditorMaterialComponentUtil", false, "Failed to export: %s", path.c_str());
  140. result = false;
  141. return false;
  142. }
  143. // Don't export values if they are the same as the material type or parent
  144. if (propertyValueDefault == propertyValue)
  145. {
  146. return true;
  147. }
  148. exportData.SetPropertyValue(propertyId, propertyValue);
  149. return true;
  150. });
  151. return result && AZ::RPI::JsonUtils::SaveObjectToFile(path, exportData);
  152. }
  153. AZ::Data::AssetId GetMaterialTypeAssetIdFromMaterialAssetId(const AZ::Data::AssetId& materialAssetId)
  154. {
  155. if (materialAssetId.IsValid())
  156. {
  157. AZ::Outcome<AZStd::vector<AZ::Data::ProductDependency>, AZStd::string> dependencyResult = AZ::Failure(AZStd::string());
  158. AZ::Data::AssetCatalogRequestBus::BroadcastResult(
  159. dependencyResult, &AZ::Data::AssetCatalogRequestBus::Events::GetAllProductDependencies, materialAssetId);
  160. if (dependencyResult)
  161. {
  162. for (const auto& dependency : dependencyResult.GetValue())
  163. {
  164. AZ::Data::AssetInfo info;
  165. AZ::Data::AssetCatalogRequestBus::BroadcastResult(
  166. info, &AZ::Data::AssetCatalogRequests::GetAssetInfoById, dependency.m_assetId);
  167. if (info.m_assetType == azrtti_typeid<AZ::RPI::MaterialTypeAsset>())
  168. {
  169. // Immediately return the first material type that's encountered because the material system currently
  170. // supports only one material type for any hierarchy of materials.
  171. return info.m_assetId;
  172. }
  173. }
  174. }
  175. }
  176. return {};
  177. }
  178. bool DoEntitiesHaveMatchingMaterialTypes(
  179. const AZ::EntityId& primaryEntityId,
  180. const AzToolsFramework::EntityIdSet& secondaryEntityIds,
  181. const MaterialAssignmentId& materialAssignmentId)
  182. {
  183. AZ::Data::AssetId primaryMaterialAssetId = {};
  184. MaterialComponentRequestBus::EventResult(
  185. primaryMaterialAssetId, primaryEntityId, &MaterialComponentRequestBus::Events::GetMaterialAssetId,
  186. materialAssignmentId);
  187. AZ::Data::AssetId primaryMaterialTypeAssetId = GetMaterialTypeAssetIdFromMaterialAssetId(primaryMaterialAssetId);
  188. return primaryMaterialTypeAssetId.IsValid() && AZStd::all_of(
  189. secondaryEntityIds.begin(), secondaryEntityIds.end(),
  190. [&](const AZ::EntityId& secondaryEntityId)
  191. {
  192. AZ::Data::AssetId secondaryMaterialAssetId = {};
  193. MaterialComponentRequestBus::EventResult(
  194. secondaryMaterialAssetId, secondaryEntityId, &MaterialComponentRequestBus::Events::GetMaterialAssetId,
  195. materialAssignmentId);
  196. AZ::Data::AssetId secondaryMaterialTypeAssetId = GetMaterialTypeAssetIdFromMaterialAssetId(secondaryMaterialAssetId);
  197. return primaryMaterialTypeAssetId == secondaryMaterialTypeAssetId;
  198. });
  199. }
  200. bool DoEntitiesHaveMatchingMaterials(
  201. const AZ::EntityId& primaryEntityId,
  202. const AzToolsFramework::EntityIdSet& secondaryEntityIds,
  203. const MaterialAssignmentId& materialAssignmentId)
  204. {
  205. AZ::Data::AssetId primaryMaterialAssetId = {};
  206. MaterialComponentRequestBus::EventResult(
  207. primaryMaterialAssetId, primaryEntityId, &MaterialComponentRequestBus::Events::GetMaterialAssetId,
  208. materialAssignmentId);
  209. return primaryMaterialAssetId.IsValid() && AZStd::all_of(
  210. secondaryEntityIds.begin(), secondaryEntityIds.end(),
  211. [&](const AZ::EntityId& secondaryEntityId)
  212. {
  213. AZ::Data::AssetId secondaryMaterialAssetId = {};
  214. MaterialComponentRequestBus::EventResult(
  215. secondaryMaterialAssetId, secondaryEntityId, &MaterialComponentRequestBus::Events::GetMaterialAssetId,
  216. materialAssignmentId);
  217. return primaryMaterialAssetId == secondaryMaterialAssetId;
  218. });
  219. }
  220. bool DoEntitiesHaveMatchingMaterialSlots(
  221. const AZ::EntityId& primaryEntityId, const AzToolsFramework::EntityIdSet& secondaryEntityIds)
  222. {
  223. MaterialAssignmentMap primaryMaterialSlots;
  224. MaterialComponentRequestBus::EventResult(
  225. primaryMaterialSlots, primaryEntityId, &MaterialComponentRequestBus::Events::GetDefaultMaterialMap);
  226. return AZStd::all_of(
  227. secondaryEntityIds.begin(), secondaryEntityIds.end(),
  228. [&](const AZ::EntityId& secondaryEntityId)
  229. {
  230. MaterialAssignmentMap secondaryMaterialSlots;
  231. MaterialComponentRequestBus::EventResult(
  232. secondaryMaterialSlots, secondaryEntityId,
  233. &MaterialComponentRequestBus::Events::GetDefaultMaterialMap);
  234. if (primaryMaterialSlots.size() != secondaryMaterialSlots.size())
  235. {
  236. return false;
  237. }
  238. for (const auto& slotPair : primaryMaterialSlots)
  239. {
  240. const auto& slotItr = secondaryMaterialSlots.find(slotPair.first);
  241. if (slotItr == secondaryMaterialSlots.end())
  242. {
  243. return false;
  244. }
  245. }
  246. return true;
  247. });
  248. }
  249. AzToolsFramework::EntityIdSet GetSelectedEntitiesFromActiveInspector()
  250. {
  251. AzToolsFramework::EntityIdList entityIds;
  252. AzToolsFramework::EntityPropertyEditorRequestBus::Broadcast(
  253. &AzToolsFramework::EntityPropertyEditorRequestBus::Events::GetSelectedAndPinnedEntities, entityIds);
  254. return AzToolsFramework::EntityIdSet(entityIds.begin(), entityIds.end());
  255. }
  256. AzToolsFramework::EntityIdSet GetEntitiesMatchingMaterialSlots(
  257. const AZ::EntityId& primaryEntityId, const AzToolsFramework::EntityIdSet& secondaryEntityIds)
  258. {
  259. AzToolsFramework::EntityIdSet entityIds = secondaryEntityIds;
  260. AZStd::erase_if(
  261. entityIds,
  262. [&](const AZ::EntityId& secondaryEntityId)
  263. {
  264. return !DoEntitiesHaveMatchingMaterialSlots(primaryEntityId, { secondaryEntityId });
  265. });
  266. return entityIds;
  267. }
  268. } // namespace EditorMaterialComponentUtil
  269. } // namespace Render
  270. } // namespace AZ