ModelExporterComponent.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  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 <AzCore/Asset/AssetCommon.h>
  10. #include <AssetBuilderSDK/AssetBuilderSDK.h>
  11. #include <AssetBuilderSDK/SerializationDependencies.h>
  12. #include <AzToolsFramework/API/EditorAssetSystemAPI.h>
  13. #include <AzToolsFramework/Debug/TraceContext.h>
  14. #include <SceneAPI/SceneCore/Containers/Scene.h>
  15. #include <SceneAPI/SceneCore/Containers/SceneManifest.h>
  16. #include <SceneAPI/SceneCore/Containers/Utilities/Filters.h>
  17. #include <SceneAPI/SceneCore/DataTypes/Groups/IMeshGroup.h>
  18. #include <SceneAPI/SceneCore/Events/ExportEventContext.h>
  19. #include <SceneAPI/SceneCore/Events/ExportProductList.h>
  20. #include <SceneAPI/SceneCore/Utilities/FileUtilities.h>
  21. #include <SceneAPI/SceneData/Rules/CoordinateSystemRule.h>
  22. #include <SceneAPI/SceneCore/Containers/Scene.h>
  23. #include <cinttypes>
  24. #include <Atom/RPI.Builders/Model/ModelExporterContexts.h>
  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. ModelAssetPostBuildContext modelAssetPostBuildContext(
  125. exportEventContext.GetScene(),
  126. exportEventContext.GetOutputDirectory(),
  127. exportEventContext.GetProductList(),
  128. meshGroup,
  129. modelAsset);
  130. combinerResult = SceneAPI::Events::Process<ModelAssetPostBuildContext>(modelAssetPostBuildContext);
  131. if (combinerResult.GetResult() != SceneAPI::Events::ProcessingResult::Success)
  132. {
  133. return combinerResult.GetResult();
  134. }
  135. //Retrieve source asset info so we can get a string with the relative path to the asset
  136. bool assetInfoResult;
  137. Data::AssetInfo info;
  138. AZStd::string watchFolder;
  139. AzToolsFramework::AssetSystemRequestBus::BroadcastResult(assetInfoResult, &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath, exportEventContext.GetScene().GetSourceFilename().c_str(), info, watchFolder);
  140. AZ_Assert(assetInfoResult, "Failed to retrieve source asset info. Can't reason about product asset paths");
  141. for (const Data::Asset<ModelLodAsset>& lodAsset : modelAsset->GetLodAssets())
  142. {
  143. AZStd::set<uint32_t> exportedSubAssets;
  144. for (const ModelLodAsset::Mesh& mesh : lodAsset->GetMeshes())
  145. {
  146. //Export all BufferAssets for this Lod
  147. //Export Index Buffer
  148. {
  149. const Data::Asset<BufferAsset>& indexBufferAsset = mesh.GetIndexBufferAssetView().GetBufferAsset();
  150. if (exportedSubAssets.find(indexBufferAsset.GetId().m_subId) == exportedSubAssets.end())
  151. {
  152. AssetExportContext bufferExportContext =
  153. {
  154. indexBufferAsset.GetHint(),
  155. BufferAsset::Extension,
  156. sourceSceneUuid,
  157. DataStream::ST_BINARY
  158. };
  159. if (!ExportAsset(indexBufferAsset, bufferExportContext, exportEventContext, "Buffer"))
  160. {
  161. return SceneAPI::Events::ProcessingResult::Failure;
  162. }
  163. exportedSubAssets.insert(indexBufferAsset.GetId().m_subId);
  164. }
  165. }
  166. //Export Stream Buffers
  167. for (const ModelLodAsset::Mesh::StreamBufferInfo& streamBufferInfo : mesh.GetStreamBufferInfoList())
  168. {
  169. const Data::Asset<BufferAsset>& bufferAsset = streamBufferInfo.m_bufferAssetView.GetBufferAsset();
  170. if (exportedSubAssets.find(bufferAsset.GetId().m_subId) == exportedSubAssets.end())
  171. {
  172. AssetExportContext bufferExportContext =
  173. {
  174. bufferAsset.GetHint(),
  175. BufferAsset::Extension,
  176. sourceSceneUuid,
  177. DataStream::ST_BINARY
  178. };
  179. if (!ExportAsset(bufferAsset, bufferExportContext, exportEventContext, "Buffer"))
  180. {
  181. return SceneAPI::Events::ProcessingResult::Failure;
  182. }
  183. exportedSubAssets.insert(bufferAsset.GetId().m_subId);
  184. }
  185. }
  186. }
  187. //Export ModelLodAsset
  188. AssetExportContext lodExportContext =
  189. {
  190. lodAsset.GetHint(),
  191. ModelLodAsset::Extension,
  192. sourceSceneUuid,
  193. DataStream::ST_BINARY
  194. };
  195. if (!ExportAsset(lodAsset, lodExportContext, exportEventContext, "Model LOD"))
  196. {
  197. return SceneAPI::Events::ProcessingResult::Failure;
  198. }
  199. } //foreach lodAsset
  200. //Export ModelAsset
  201. AssetExportContext modelExportContext =
  202. {
  203. modelAsset.GetHint(),
  204. ModelAsset::Extension,
  205. sourceSceneUuid,
  206. DataStream::ST_BINARY
  207. };
  208. if (!ExportAsset(modelAsset, modelExportContext, exportEventContext, "Model"))
  209. {
  210. return SceneAPI::Events::ProcessingResult::Failure;
  211. }
  212. // Export skin meta data
  213. if (skinMetaAsset.IsReady())
  214. {
  215. AssetExportContext skinMetaExportContext =
  216. {
  217. /*relativeFilename=*/meshGroupName,
  218. SkinMetaAsset::Extension,
  219. sourceSceneUuid,
  220. DataStream::ST_JSON
  221. };
  222. if (!ExportAsset(skinMetaAsset, skinMetaExportContext, exportEventContext, "SkinMeta"))
  223. {
  224. return SceneAPI::Events::ProcessingResult::Failure;
  225. }
  226. }
  227. // Export morph target meta data
  228. if (morphTargetMetaAsset.IsReady())
  229. {
  230. AssetExportContext morphTargetMetaExportContext =
  231. {
  232. /*relativeFilename=*/meshGroupName,
  233. MorphTargetMetaAsset::Extension,
  234. sourceSceneUuid,
  235. DataStream::ST_JSON
  236. };
  237. if (!ExportAsset(morphTargetMetaAsset, morphTargetMetaExportContext, exportEventContext, "MorphTargetMeta"))
  238. {
  239. return SceneAPI::Events::ProcessingResult::Failure;
  240. }
  241. }
  242. } //foreach meshGroup
  243. return SceneAPI::Events::ProcessingResult::Success;
  244. }
  245. template<class T>
  246. bool ModelExporterComponent::ExportAsset(
  247. const Data::Asset<T>& asset,
  248. const AssetExportContext& assetContext,
  249. SceneAPI::Events::ExportEventContext& context,
  250. [[maybe_unused]] const char* assetTypeDebugName) const
  251. {
  252. const AZStd::string assetFileName = SceneAPI::Utilities::FileUtilities::CreateOutputFileName(
  253. assetContext.m_relativeFileName, context.GetOutputDirectory(), assetContext.m_extension,
  254. context.GetScene().GetSourceExtension());
  255. if (!Utils::SaveObjectToFile(assetFileName, assetContext.m_dataStreamType, asset.Get()))
  256. {
  257. AZ_Error(s_exporterName, false, "Failed to save %s to file %s", assetTypeDebugName, assetFileName.c_str());
  258. return false;
  259. }
  260. const Uuid assetUuid = asset.GetId().m_guid;
  261. if (assetUuid != assetContext.m_sourceUuid)
  262. {
  263. AZ_Assert(false, "All product UUIDs should be the same as the scene source UUID");
  264. return false;
  265. }
  266. const uint32_t assetSubId = asset.GetId().m_subId;
  267. // Add product to output list
  268. // Otherwise the asset won't be copied from temp folders to cache
  269. SceneAPI::Events::ExportProduct& product = context.GetProductList().AddProduct(
  270. assetFileName, assetUuid, asset->GetType(), AZStd::nullopt, assetSubId);
  271. AssetBuilderSDK::JobProduct jobProduct;
  272. if (!AssetBuilderSDK::OutputObject(asset.Get(), assetFileName, asset->GetType(), assetSubId, jobProduct))
  273. {
  274. AZ_Assert(false, "Failed to output product dependencies.");
  275. return false;
  276. }
  277. for (auto& dependency : jobProduct.m_dependencies)
  278. {
  279. product.m_productDependencies.push_back(SceneAPI::Events::ExportProduct{
  280. {},
  281. dependency.m_dependencyId.m_guid,
  282. {},
  283. AZStd::nullopt,
  284. dependency.m_dependencyId.m_subId,
  285. dependency.m_flags
  286. });
  287. }
  288. return true;
  289. }
  290. void ModelExporterComponent::Reflect(ReflectContext* context)
  291. {
  292. if (auto* serialize = azrtti_cast<SerializeContext*>(context))
  293. {
  294. serialize->Class<ModelExporterComponent, SceneAPI::SceneCore::ExportingComponent>()
  295. ->Version(4);
  296. }
  297. }
  298. ModelExporterComponent::AssetExportContext::AssetExportContext(
  299. AZStd::string_view relativeFileName,
  300. AZStd::string_view extension,
  301. Uuid sourceUuid,
  302. DataStream::StreamType dataStreamType)
  303. : m_relativeFileName{relativeFileName}
  304. , m_extension{extension}
  305. , m_sourceUuid{sourceUuid}
  306. , m_dataStreamType{dataStreamType}
  307. {}
  308. } // namespace RPI
  309. } // namespace AZ