3
0

ModelExporterComponent.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  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 <Model/ModelExporterComponent.h>
  9. #include <Model/ModelExporterContexts.h>
  10. #include <AzCore/Asset/AssetCommon.h>
  11. #include <AssetBuilderSDK/AssetBuilderSDK.h>
  12. #include <AssetBuilderSDK/SerializationDependencies.h>
  13. #include <AzToolsFramework/API/EditorAssetSystemAPI.h>
  14. #include <AzToolsFramework/Debug/TraceContext.h>
  15. #include <SceneAPI/SceneCore/Containers/Scene.h>
  16. #include <SceneAPI/SceneCore/Containers/SceneManifest.h>
  17. #include <SceneAPI/SceneCore/Containers/Utilities/Filters.h>
  18. #include <SceneAPI/SceneCore/DataTypes/Groups/IMeshGroup.h>
  19. #include <SceneAPI/SceneCore/Events/ExportEventContext.h>
  20. #include <SceneAPI/SceneCore/Events/ExportProductList.h>
  21. #include <SceneAPI/SceneCore/Utilities/FileUtilities.h>
  22. #include <SceneAPI/SceneData/Rules/CoordinateSystemRule.h>
  23. #include <SceneAPI/SceneCore/Containers/Scene.h>
  24. #include <cinttypes>
  25. #include <Atom/RPI.Reflect/Material/MaterialAsset.h>
  26. #include <AzCore/Settings/SettingsRegistry.h>
  27. namespace AZ
  28. {
  29. namespace RPI
  30. {
  31. [[maybe_unused]] static const char* s_exporterName = "Atom Model Builder";
  32. ModelExporterComponent::ModelExporterComponent()
  33. {
  34. // This setting disables model output (for automated testing purposes) to allow an FBX file to be processed without including
  35. // all the dependencies required to process a model.
  36. auto settingsRegistry = AZ::SettingsRegistry::Get();
  37. bool skipAtomOutput = false;
  38. if (settingsRegistry && settingsRegistry->Get(skipAtomOutput, "/O3DE/SceneAPI/AssetImporter/SkipAtomOutput") && skipAtomOutput)
  39. {
  40. return;
  41. }
  42. BindToCall(&ModelExporterComponent::ExportModel);
  43. }
  44. SceneAPI::Events::ProcessingResult
  45. ModelExporterComponent::ExportModel(SceneAPI::Events::ExportEventContext& exportEventContext) const
  46. {
  47. const SceneAPI::Containers::SceneManifest& manifest =
  48. exportEventContext.GetScene().GetManifest();
  49. const Uuid sourceSceneUuid = exportEventContext.GetScene().GetSourceGuid();
  50. auto valueStorage = manifest.GetValueStorage();
  51. auto view = SceneAPI::Containers::MakeDerivedFilterView<
  52. SceneAPI::DataTypes::IMeshGroup>(valueStorage);
  53. SceneAPI::Events::ProcessingResultCombiner combinerResult;
  54. MaterialAssetsByUid materialsByUid;
  55. MaterialAssetBuilderContext materialContext(exportEventContext.GetScene(), materialsByUid);
  56. combinerResult = SceneAPI::Events::Process<MaterialAssetBuilderContext>(materialContext);
  57. if (combinerResult.GetResult() == SceneAPI::Events::ProcessingResult::Failure)
  58. {
  59. return SceneAPI::Events::ProcessingResult::Failure;
  60. }
  61. //Export MaterialAssets
  62. for (auto& materialPair : materialsByUid)
  63. {
  64. const Data::Asset<MaterialAsset>& asset = materialPair.second.m_asset;
  65. // MaterialAssetBuilderContext could attach an independent material asset rather than
  66. // generate one using the scene data, so we must skip the export step in that case.
  67. if (asset.GetId().m_guid != exportEventContext.GetScene().GetSourceGuid())
  68. {
  69. continue;
  70. }
  71. uint64_t materialUid = materialPair.first;
  72. const AZStd::string& sceneName = exportEventContext.GetScene().GetName();
  73. // escape the material name acceptable for a filename
  74. AZStd::string materialName = materialPair.second.m_name;
  75. for (char& item : materialName)
  76. {
  77. if (!isalpha(item) && !isdigit(item))
  78. {
  79. item = '_';
  80. }
  81. }
  82. const AZStd::string relativeMaterialFileName = AZStd::string::format("%s_%s_%" PRIu64, sceneName.c_str(), materialName.data(), materialUid);
  83. AssetExportContext materialExportContext =
  84. {
  85. relativeMaterialFileName,
  86. MaterialAsset::Extension,
  87. sourceSceneUuid,
  88. DataStream::ST_BINARY
  89. };
  90. if (!ExportAsset(asset, materialExportContext, exportEventContext, "Material"))
  91. {
  92. return SceneAPI::Events::ProcessingResult::Failure;
  93. }
  94. }
  95. AZStd::set<AZStd::string> groupNames;
  96. for (const SceneAPI::DataTypes::IMeshGroup& meshGroup : view)
  97. {
  98. const AZStd::string& meshGroupName = meshGroup.GetName();
  99. //Check for duplicate group names
  100. if(groupNames.find(meshGroupName) != groupNames.end())
  101. {
  102. AZ_Warning(s_exporterName, false, "Multiple mesh groups with duplicate name: \"%s\". Skipping export...", meshGroupName.c_str());
  103. continue;
  104. }
  105. groupNames.insert(meshGroupName);
  106. AZ_TraceContext("Mesh group", meshGroupName.c_str());
  107. // Get the coordinate system conversion rule.
  108. AZ::SceneAPI::CoordinateSystemConverter coordSysConverter;
  109. AZStd::shared_ptr<AZ::SceneAPI::SceneData::CoordinateSystemRule> coordinateSystemRule = meshGroup.GetRuleContainerConst().FindFirstByType<AZ::SceneAPI::SceneData::CoordinateSystemRule>();
  110. if (coordinateSystemRule)
  111. {
  112. coordinateSystemRule->UpdateCoordinateSystemConverter();
  113. coordSysConverter = coordinateSystemRule->GetCoordinateSystemConverter();
  114. }
  115. Data::Asset<ModelAsset> modelAsset;
  116. Data::Asset<SkinMetaAsset> skinMetaAsset;
  117. Data::Asset<MorphTargetMetaAsset> morphTargetMetaAsset;
  118. ModelAssetBuilderContext modelContext(exportEventContext.GetScene(), meshGroup, coordSysConverter, materialsByUid, modelAsset, skinMetaAsset, morphTargetMetaAsset);
  119. combinerResult = SceneAPI::Events::Process<ModelAssetBuilderContext>(modelContext);
  120. if (combinerResult.GetResult() != SceneAPI::Events::ProcessingResult::Success)
  121. {
  122. return combinerResult.GetResult();
  123. }
  124. //Retrieve source asset info so we can get a string with the relative path to the asset
  125. bool assetInfoResult;
  126. Data::AssetInfo info;
  127. AZStd::string watchFolder;
  128. AzToolsFramework::AssetSystemRequestBus::BroadcastResult(assetInfoResult, &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath, exportEventContext.GetScene().GetSourceFilename().c_str(), info, watchFolder);
  129. AZ_Assert(assetInfoResult, "Failed to retrieve source asset info. Can't reason about product asset paths");
  130. for (const Data::Asset<ModelLodAsset>& lodAsset : modelAsset->GetLodAssets())
  131. {
  132. AZStd::set<uint32_t> exportedSubAssets;
  133. for (const ModelLodAsset::Mesh& mesh : lodAsset->GetMeshes())
  134. {
  135. //Export all BufferAssets for this Lod
  136. //Export Index Buffer
  137. {
  138. const Data::Asset<BufferAsset>& indexBufferAsset = mesh.GetIndexBufferAssetView().GetBufferAsset();
  139. if (exportedSubAssets.find(indexBufferAsset.GetId().m_subId) == exportedSubAssets.end())
  140. {
  141. AssetExportContext bufferExportContext =
  142. {
  143. indexBufferAsset.GetHint(),
  144. BufferAsset::Extension,
  145. sourceSceneUuid,
  146. DataStream::ST_BINARY
  147. };
  148. if (!ExportAsset(indexBufferAsset, bufferExportContext, exportEventContext, "Buffer"))
  149. {
  150. return SceneAPI::Events::ProcessingResult::Failure;
  151. }
  152. exportedSubAssets.insert(indexBufferAsset.GetId().m_subId);
  153. }
  154. }
  155. //Export Stream Buffers
  156. for (const ModelLodAsset::Mesh::StreamBufferInfo& streamBufferInfo : mesh.GetStreamBufferInfoList())
  157. {
  158. const Data::Asset<BufferAsset>& bufferAsset = streamBufferInfo.m_bufferAssetView.GetBufferAsset();
  159. if (exportedSubAssets.find(bufferAsset.GetId().m_subId) == exportedSubAssets.end())
  160. {
  161. AssetExportContext bufferExportContext =
  162. {
  163. bufferAsset.GetHint(),
  164. BufferAsset::Extension,
  165. sourceSceneUuid,
  166. DataStream::ST_BINARY
  167. };
  168. if (!ExportAsset(bufferAsset, bufferExportContext, exportEventContext, "Buffer"))
  169. {
  170. return SceneAPI::Events::ProcessingResult::Failure;
  171. }
  172. exportedSubAssets.insert(bufferAsset.GetId().m_subId);
  173. }
  174. }
  175. }
  176. //Export ModelLodAsset
  177. AssetExportContext lodExportContext =
  178. {
  179. lodAsset.GetHint(),
  180. ModelLodAsset::Extension,
  181. sourceSceneUuid,
  182. DataStream::ST_BINARY
  183. };
  184. if (!ExportAsset(lodAsset, lodExportContext, exportEventContext, "Model LOD"))
  185. {
  186. return SceneAPI::Events::ProcessingResult::Failure;
  187. }
  188. } //foreach lodAsset
  189. //Export ModelAsset
  190. AssetExportContext modelExportContext =
  191. {
  192. modelAsset.GetHint(),
  193. ModelAsset::Extension,
  194. sourceSceneUuid,
  195. DataStream::ST_BINARY
  196. };
  197. if (!ExportAsset(modelAsset, modelExportContext, exportEventContext, "Model"))
  198. {
  199. return SceneAPI::Events::ProcessingResult::Failure;
  200. }
  201. // Export skin meta data
  202. if (skinMetaAsset.IsReady())
  203. {
  204. AssetExportContext skinMetaExportContext =
  205. {
  206. /*relativeFilename=*/meshGroupName,
  207. SkinMetaAsset::Extension,
  208. sourceSceneUuid,
  209. DataStream::ST_JSON
  210. };
  211. if (!ExportAsset(skinMetaAsset, skinMetaExportContext, exportEventContext, "SkinMeta"))
  212. {
  213. return SceneAPI::Events::ProcessingResult::Failure;
  214. }
  215. }
  216. // Export morph target meta data
  217. if (morphTargetMetaAsset.IsReady())
  218. {
  219. AssetExportContext morphTargetMetaExportContext =
  220. {
  221. /*relativeFilename=*/meshGroupName,
  222. MorphTargetMetaAsset::Extension,
  223. sourceSceneUuid,
  224. DataStream::ST_JSON
  225. };
  226. if (!ExportAsset(morphTargetMetaAsset, morphTargetMetaExportContext, exportEventContext, "MorphTargetMeta"))
  227. {
  228. return SceneAPI::Events::ProcessingResult::Failure;
  229. }
  230. }
  231. } //foreach meshGroup
  232. return SceneAPI::Events::ProcessingResult::Success;
  233. }
  234. template<class T>
  235. bool ModelExporterComponent::ExportAsset(
  236. const Data::Asset<T>& asset,
  237. const AssetExportContext& assetContext,
  238. SceneAPI::Events::ExportEventContext& context,
  239. [[maybe_unused]] const char* assetTypeDebugName) const
  240. {
  241. const AZStd::string assetFileName = SceneAPI::Utilities::FileUtilities::CreateOutputFileName(
  242. assetContext.m_relativeFileName, context.GetOutputDirectory(), assetContext.m_extension,
  243. context.GetScene().GetSourceExtension());
  244. if (!Utils::SaveObjectToFile(assetFileName, assetContext.m_dataStreamType, asset.Get()))
  245. {
  246. AZ_Error(s_exporterName, false, "Failed to save %s to file %s", assetTypeDebugName, assetFileName.c_str());
  247. return false;
  248. }
  249. const Uuid assetUuid = asset.GetId().m_guid;
  250. if (assetUuid != assetContext.m_sourceUuid)
  251. {
  252. AZ_Assert(false, "All product UUIDs should be the same as the scene source UUID");
  253. return false;
  254. }
  255. const uint32_t assetSubId = asset.GetId().m_subId;
  256. // Add product to output list
  257. // Otherwise the asset won't be copied from temp folders to cache
  258. SceneAPI::Events::ExportProduct& product = context.GetProductList().AddProduct(
  259. assetFileName, assetUuid, asset->GetType(), AZStd::nullopt, assetSubId);
  260. AssetBuilderSDK::JobProduct jobProduct;
  261. if (!AssetBuilderSDK::OutputObject(asset.Get(), assetFileName, asset->GetType(), assetSubId, jobProduct))
  262. {
  263. AZ_Assert(false, "Failed to output product dependencies.");
  264. return false;
  265. }
  266. for (auto& dependency : jobProduct.m_dependencies)
  267. {
  268. product.m_productDependencies.push_back(SceneAPI::Events::ExportProduct{
  269. {},
  270. dependency.m_dependencyId.m_guid,
  271. {},
  272. AZStd::nullopt,
  273. dependency.m_dependencyId.m_subId,
  274. dependency.m_flags
  275. });
  276. }
  277. return true;
  278. }
  279. void ModelExporterComponent::Reflect(ReflectContext* context)
  280. {
  281. if (auto* serialize = azrtti_cast<SerializeContext*>(context))
  282. {
  283. serialize->Class<ModelExporterComponent, SceneAPI::SceneCore::ExportingComponent>()
  284. ->Version(4);
  285. }
  286. }
  287. ModelExporterComponent::AssetExportContext::AssetExportContext(
  288. AZStd::string_view relativeFileName,
  289. AZStd::string_view extension,
  290. Uuid sourceUuid,
  291. DataStream::StreamType dataStreamType)
  292. : m_relativeFileName{relativeFileName}
  293. , m_extension{extension}
  294. , m_sourceUuid{sourceUuid}
  295. , m_dataStreamType{dataStreamType}
  296. {}
  297. } // namespace RPI
  298. } // namespace AZ