SceneBuilderWorker.cpp 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  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 <AzCore/Component/ComponentApplicationBus.h>
  9. #include <AzCore/IO/FileIO.h>
  10. #include <AzCore/IO/SystemFile.h>
  11. #include <AzCore/Serialization/Utils.h>
  12. #include <SceneAPI/SceneCore/DataTypes/IGraphObject.h>
  13. #include <AzCore/Serialization/SerializeContext.h>
  14. #include <AzCore/std/containers/set.h>
  15. #include <AzCore/Utils/Utils.h>
  16. #include <AzFramework/Application/Application.h>
  17. #include <AzFramework/StringFunc/StringFunc.h>
  18. #include <AzToolsFramework/Debug/TraceContext.h>
  19. #include <SceneAPI/SceneCore/Components/ExportingComponent.h>
  20. #include <SceneAPI/SceneCore/Components/GenerationComponent.h>
  21. #include <SceneAPI/SceneCore/Components/LoadingComponent.h>
  22. #include <SceneAPI/SceneCore/Components/Utilities/EntityConstructor.h>
  23. #include <SceneAPI/SceneCore/Containers/Scene.h>
  24. #include <SceneAPI/SceneCore/Containers/SceneManifest.h>
  25. #include <SceneAPI/SceneCore/Events/AssetImportRequest.h>
  26. #include <SceneAPI/SceneCore/Events/GenerateEventContext.h>
  27. #include <SceneAPI/SceneCore/Events/ExportProductList.h>
  28. #include <SceneAPI/SceneCore/Events/ExportEventContext.h>
  29. #include <SceneAPI/SceneCore/Events/SceneSerializationBus.h>
  30. #include <SceneAPI/SceneCore/Utilities/Reporting.h>
  31. #include <SceneAPI/SceneCore/SceneBuilderDependencyBus.h>
  32. #include <SceneAPI/SceneCore/DataTypes/GraphData/IMeshData.h>
  33. #include <SceneAPI/SceneCore/DataTypes/GraphData/IAnimationData.h>
  34. #include <AssetBuilderSDK/AssetBuilderSDK.h>
  35. #include <AzCore/Serialization/Json/JsonUtils.h>
  36. #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
  37. #include <rapidjson/pointer.h>
  38. #include <SceneBuilder/SceneBuilderWorker.h>
  39. #include <SceneBuilder/TraceMessageHook.h>
  40. namespace SceneBuilder
  41. {
  42. void SceneBuilderWorker::ShutDown()
  43. {
  44. m_isShuttingDown = true;
  45. }
  46. const char* SceneBuilderWorker::GetFingerprint() const
  47. {
  48. if (m_cachedFingerprint.empty())
  49. {
  50. // put them in an ORDERED set so that changing the reflection
  51. // or the gems loaded does not invalidate scene files due to order of reflection changing.
  52. AZStd::set<AZStd::string> fragments;
  53. AZ::SerializeContext* context = nullptr;
  54. AZ::ComponentApplicationBus::BroadcastResult(context, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
  55. if (context)
  56. {
  57. auto callback = [&fragments](const AZ::SerializeContext::ClassData* data, const AZ::Uuid& typeId)
  58. {
  59. AZ_UNUSED(typeId);
  60. fragments.insert(AZStd::string::format("[%s:v%i]", data->m_name, data->m_version));
  61. return true;
  62. };
  63. context->EnumerateDerived(callback, azrtti_typeid<AZ::SceneAPI::SceneCore::ExportingComponent>(), azrtti_typeid<AZ::SceneAPI::SceneCore::ExportingComponent>());
  64. context->EnumerateDerived(callback, azrtti_typeid<AZ::SceneAPI::SceneCore::GenerationComponent>(), azrtti_typeid<AZ::SceneAPI::SceneCore::GenerationComponent>());
  65. context->EnumerateDerived(callback, azrtti_typeid<AZ::SceneAPI::SceneCore::LoadingComponent>(), azrtti_typeid<AZ::SceneAPI::SceneCore::LoadingComponent>());
  66. }
  67. AZ::SceneAPI::SceneBuilderDependencyBus::Broadcast(&AZ::SceneAPI::SceneBuilderDependencyRequests::AddFingerprintInfo, fragments);
  68. for (const AZStd::string& element : fragments)
  69. {
  70. m_cachedFingerprint.append(element);
  71. }
  72. // A general catch all version fingerprint. Update this to force all FBX files to recompile.
  73. m_cachedFingerprint.append("Version 1");
  74. }
  75. return m_cachedFingerprint.c_str();
  76. }
  77. void SceneBuilderWorker::PopulateSourceDependencies(const AZStd::string& manifestJson, AZStd::vector<AssetBuilderSDK::SourceFileDependency>& sourceFileDependencies)
  78. {
  79. auto readJsonOutcome = AZ::JsonSerializationUtils::ReadJsonString(manifestJson);
  80. AZStd::string errorMsg;
  81. if (!readJsonOutcome.IsSuccess())
  82. {
  83. // This may be an old format xml file. We don't have any dependencies in the old format so there's no point trying to parse an xml
  84. return;
  85. }
  86. rapidjson::Document document = readJsonOutcome.TakeValue();
  87. auto manifestObject = document.GetObject();
  88. auto valuesIterator = manifestObject.FindMember("values");
  89. auto valuesArray = valuesIterator->value.GetArray();
  90. AZStd::vector<AZStd::string> paths;
  91. AZ::SceneAPI::Events::AssetImportRequestBus::Broadcast(
  92. &AZ::SceneAPI::Events::AssetImportRequestBus::Events::GetManifestDependencyPaths, paths);
  93. for (const auto& value : valuesArray)
  94. {
  95. auto object = value.GetObject();
  96. for (const auto& path : paths)
  97. {
  98. rapidjson::Pointer pointer(path.c_str());
  99. auto dependencyValue = pointer.Get(object);
  100. if (dependencyValue && dependencyValue->IsString())
  101. {
  102. AZStd::string dependency = dependencyValue->GetString();
  103. sourceFileDependencies.emplace_back(AssetBuilderSDK::SourceFileDependency(
  104. dependency, AZ::Uuid::CreateNull(), AssetBuilderSDK::SourceFileDependency::SourceFileDependencyType::Absolute));
  105. }
  106. }
  107. }
  108. }
  109. bool SceneBuilderWorker::ManifestDependencyCheck(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response)
  110. {
  111. AZStd::string manifestExtension;
  112. AZStd::string generatedManifestExtension;
  113. AZ::SceneAPI::Events::AssetImportRequestBus::Broadcast(
  114. &AZ::SceneAPI::Events::AssetImportRequestBus::Events::GetManifestExtension, manifestExtension);
  115. AZ::SceneAPI::Events::AssetImportRequestBus::Broadcast(
  116. &AZ::SceneAPI::Events::AssetImportRequestBus::Events::GetGeneratedManifestExtension, generatedManifestExtension);
  117. if (manifestExtension.empty() || generatedManifestExtension.empty())
  118. {
  119. AZ_Error("SceneBuilderWorker", false, "Failed to get scene manifest extension");
  120. return false;
  121. }
  122. AZ::SettingsRegistryInterface::FixedValueString assetCacheRoot;
  123. AZ::SettingsRegistry::Get()->Get(assetCacheRoot, AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder);
  124. auto manifestPath = (AZ::IO::Path(request.m_watchFolder) / (request.m_sourceFile + manifestExtension));
  125. auto generatedManifestPath = (AZ::IO::Path(assetCacheRoot) / (request.m_sourceFile + generatedManifestExtension));
  126. auto populateDependenciesFunc = [&response](const AZStd::string& path)
  127. {
  128. auto readFileOutcome = AZ::Utils::ReadFile(path, AZ::SceneAPI::Containers::SceneManifest::MaxSceneManifestFileSizeInBytes);
  129. if (!readFileOutcome.IsSuccess())
  130. {
  131. AZ_Error("SceneBuilderWorker", false, "%s", readFileOutcome.GetError().c_str());
  132. return;
  133. }
  134. PopulateSourceDependencies(readFileOutcome.TakeValue(), response.m_sourceFileDependencyList);
  135. };
  136. if (AZ::IO::FileIOBase::GetInstance()->Exists(manifestPath.Native().c_str()))
  137. {
  138. populateDependenciesFunc(manifestPath.Native());
  139. }
  140. else if (AZ::IO::FileIOBase::GetInstance()->Exists(generatedManifestPath.Native().c_str()))
  141. {
  142. populateDependenciesFunc(generatedManifestPath.Native());
  143. }
  144. return true;
  145. }
  146. void SceneBuilderWorker::CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response)
  147. {
  148. // Check for shutdown
  149. if (m_isShuttingDown)
  150. {
  151. response.m_result = AssetBuilderSDK::CreateJobsResultCode::ShuttingDown;
  152. return;
  153. }
  154. for (auto& enabledPlatform : request.m_enabledPlatforms)
  155. {
  156. AssetBuilderSDK::JobDescriptor descriptor;
  157. descriptor.m_jobKey = "Scene compilation";
  158. descriptor.SetPlatformIdentifier(enabledPlatform.m_identifier.c_str());
  159. descriptor.m_failOnError = true;
  160. descriptor.m_priority = 11; // these may control logic (actors and motions specifically)
  161. descriptor.m_additionalFingerprintInfo = GetFingerprint();
  162. AZ::SceneAPI::SceneBuilderDependencyBus::Broadcast(&AZ::SceneAPI::SceneBuilderDependencyRequests::ReportJobDependencies,
  163. descriptor.m_jobDependencyList, enabledPlatform.m_identifier.c_str());
  164. response.m_createJobOutputs.push_back(descriptor);
  165. }
  166. // Adding corresponding _wrinklemask folder as a source file dependency
  167. // This enables morph target assets to get references to the wrinkle masks
  168. // in the MorphTargetExporter, so they can be automatically applied at runtime
  169. AssetBuilderSDK::SourceFileDependency sourceFileDependencyInfo;
  170. AZStd::string relPath = request.m_sourceFile.c_str();
  171. AzFramework::StringFunc::Path::StripExtension(relPath);
  172. relPath += "_wrinklemasks";
  173. AzFramework::StringFunc::Path::AppendSeparator(relPath);
  174. relPath += "*_wrinklemask.*";
  175. sourceFileDependencyInfo.m_sourceFileDependencyPath = relPath;
  176. sourceFileDependencyInfo.m_sourceDependencyType = AssetBuilderSDK::SourceFileDependency::SourceFileDependencyType::Wildcards;
  177. response.m_sourceFileDependencyList.push_back(sourceFileDependencyInfo);
  178. if (!ManifestDependencyCheck(request, response))
  179. {
  180. return;
  181. }
  182. response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
  183. }
  184. void SceneBuilderWorker::ProcessJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response)
  185. {
  186. using namespace AZ::SceneAPI::Containers;
  187. // Only used during processing to redirect trace printfs with an warning or error window to the appropriate reporting function.
  188. TraceMessageHook messageHook;
  189. // Load Scene graph and manifest from the provided path and then initialize them.
  190. if (m_isShuttingDown)
  191. {
  192. AZ_TracePrintf(AZ::SceneAPI::Utilities::LogWindow, "Loading scene was cancelled.\n");
  193. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled;
  194. return;
  195. }
  196. AZStd::shared_ptr<Scene> scene;
  197. if (!LoadScene(scene, request, response))
  198. {
  199. return;
  200. }
  201. // Run scene generation step to allow for runtime generation of SceneGraph objects
  202. if (m_isShuttingDown)
  203. {
  204. AZ_TracePrintf(AZ::SceneAPI::Utilities::LogWindow, "Generation of dynamic scene objects was cancelled.\n");
  205. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled;
  206. return;
  207. }
  208. if (!GenerateScene(scene.get(), request, response))
  209. {
  210. return;
  211. }
  212. // Process the scene.
  213. if (m_isShuttingDown)
  214. {
  215. AZ_TracePrintf(AZ::SceneAPI::Utilities::LogWindow, "Processing scene was cancelled.\n");
  216. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled;
  217. return;
  218. }
  219. if (!ExportScene(scene, request, response))
  220. {
  221. return;
  222. }
  223. AZ_TracePrintf(AZ::SceneAPI::Utilities::LogWindow, "Finalizing scene processing.\n");
  224. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  225. }
  226. AZ::Uuid SceneBuilderWorker::GetUUID()
  227. {
  228. return AZ::Uuid::CreateString("{BD8BF658-9485-4FE3-830E-8EC3A23C35F3}");
  229. }
  230. void SceneBuilderWorker::PopulateProductDependencies(const AZ::SceneAPI::Events::ExportProduct& exportProduct, const char* watchFolder, AssetBuilderSDK::JobProduct& jobProduct) const
  231. {
  232. // Register the product dependencies and path dependencies from the export product to the job product.
  233. for (const AZ::SceneAPI::Events::ExportProduct& dependency : exportProduct.m_productDependencies)
  234. {
  235. jobProduct.m_dependencies.emplace_back(
  236. AZ::Data::AssetId(dependency.m_id, dependency.m_subId.value_or(0)),
  237. dependency.m_dependencyFlags);
  238. }
  239. for (const AZStd::string& pathDependency : exportProduct.m_legacyPathDependencies)
  240. {
  241. // SceneCore doesn't have access to AssetBuilderSDK, so it doesn't have access to the
  242. // ProductPathDependency type or the ProductPathDependencyType enum. Exporters registered with the
  243. // Scene Builder should report path dependencies on source files as absolute paths, while dependencies
  244. // on product files should be reported as relative paths.
  245. if (AzFramework::StringFunc::Path::IsRelative(pathDependency.c_str()))
  246. {
  247. // Make sure the path is relative to the watch folder. Paths passed in might be using asset database separators.
  248. // Convert to system separators for path manipulation.
  249. AZStd::string normalizedPathDependency = pathDependency;
  250. AZStd::string normalizedWatchFolder(watchFolder);
  251. AZStd::string assetRootRelativePath;
  252. AzFramework::StringFunc::Path::Normalize(normalizedWatchFolder);
  253. AzFramework::StringFunc::Path::Normalize(normalizedPathDependency);
  254. AzFramework::StringFunc::Path::Join(normalizedWatchFolder.c_str(), normalizedPathDependency .c_str(), assetRootRelativePath);
  255. AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::Bus::Events::MakePathRelative, assetRootRelativePath, watchFolder);
  256. jobProduct.m_pathDependencies.emplace(assetRootRelativePath, AssetBuilderSDK::ProductPathDependencyType::ProductFile);
  257. }
  258. else
  259. {
  260. jobProduct.m_pathDependencies.emplace(pathDependency, AssetBuilderSDK::ProductPathDependencyType::SourceFile);
  261. }
  262. }
  263. jobProduct.m_dependenciesHandled = true; // We've populated the dependencies immediately above so it's OK to tell the AP we've handled dependencies
  264. }
  265. bool SceneBuilderWorker::LoadScene(AZStd::shared_ptr<AZ::SceneAPI::Containers::Scene>& result,
  266. const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response)
  267. {
  268. using namespace AZ::SceneAPI;
  269. using namespace AZ::SceneAPI::Containers;
  270. using namespace AZ::SceneAPI::Events;
  271. AZ_TracePrintf(Utilities::LogWindow, "Loading scene.\n");
  272. SceneSerializationBus::BroadcastResult(result, &SceneSerializationBus::Events::LoadScene, request.m_fullPath, request.m_sourceFileUUID);
  273. if (!result)
  274. {
  275. AZ_TracePrintf(Utilities::ErrorWindow, "Failed to load scene file.\n");
  276. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
  277. return false;
  278. }
  279. AZ_TraceContext("Manifest", result->GetManifestFilename());
  280. if (result->GetManifest().IsEmpty())
  281. {
  282. AZ_TracePrintf(Utilities::WarningWindow, "No manifest loaded and not enough information to create a default manifest.\n");
  283. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  284. return false; // Still return false as there's no work so should exit.
  285. }
  286. return true;
  287. }
  288. bool SceneBuilderWorker::GenerateScene(AZ::SceneAPI::Containers::Scene* scene,
  289. const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response)
  290. {
  291. using namespace AZ::SceneAPI;
  292. using namespace AZ::SceneAPI::Events;
  293. using namespace AZ::SceneAPI::SceneCore;
  294. const char* platformIdentifier = request.m_jobDescription.GetPlatformIdentifier().c_str();
  295. AZ_TracePrintf(Utilities::LogWindow, "Creating generate entities.\n");
  296. EntityConstructor::EntityPointer exporter = EntityConstructor::BuildEntity("Scene Generation", azrtti_typeid<GenerationComponent>());
  297. ProcessingResultCombiner result;
  298. AZ_TracePrintf(Utilities::LogWindow, "Preparing for generation.\n");
  299. result += Process<PreGenerateEventContext>(*scene, platformIdentifier);
  300. AZ_TracePrintf(Utilities::LogWindow, "Generating...\n");
  301. result += Process<GenerateEventContext>(*scene, platformIdentifier);
  302. AZ_TracePrintf(Utilities::LogWindow, "Generating LODs...\n");
  303. result += Process<GenerateLODEventContext>(*scene, platformIdentifier);
  304. AZ_TracePrintf(Utilities::LogWindow, "Generating additions...\n");
  305. result += Process<GenerateAdditionEventContext>(*scene, platformIdentifier);
  306. AZ_TracePrintf(Utilities::LogWindow, "Simplifing scene...\n");
  307. result += Process<GenerateSimplificationEventContext>(*scene, platformIdentifier);
  308. AZ_TracePrintf(Utilities::LogWindow, "Finalizing generation process.\n");
  309. result += Process<PostGenerateEventContext>(*scene, platformIdentifier);
  310. if (result.GetResult() == ProcessingResult::Failure)
  311. {
  312. AZ_TracePrintf(Utilities::ErrorWindow, "Failure during scene generation.\n");
  313. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
  314. return false;
  315. }
  316. return true;
  317. }
  318. bool SceneBuilderWorker::ExportScene(const AZStd::shared_ptr<AZ::SceneAPI::Containers::Scene>& scene,
  319. const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response)
  320. {
  321. using namespace AZ::SceneAPI;
  322. using namespace AZ::SceneAPI::Events;
  323. using namespace AZ::SceneAPI::SceneCore;
  324. AZ_Assert(scene, "Invalid scene passed for exporting.");
  325. const AZStd::string& outputFolder = request.m_tempDirPath;
  326. const char* platformIdentifier = request.m_jobDescription.GetPlatformIdentifier().c_str();
  327. AZ_TraceContext("Output folder", outputFolder.c_str());
  328. AZ_TraceContext("Platform", platformIdentifier);
  329. AZ_TracePrintf(Utilities::LogWindow, "Processing scene.\n");
  330. AZ_TracePrintf(Utilities::LogWindow, "Creating export entities.\n");
  331. EntityConstructor::EntityPointer exporter = EntityConstructor::BuildEntity("Scene Exporters", ExportingComponent::TYPEINFO_Uuid());
  332. ExportProductList productList;
  333. ProcessingResultCombiner result;
  334. AZ_TracePrintf(Utilities::LogWindow, "Preparing for export.\n");
  335. result += Process<PreExportEventContext>(productList, outputFolder, *scene, platformIdentifier);
  336. AZ_TracePrintf(Utilities::LogWindow, "Exporting...\n");
  337. result += Process<ExportEventContext>(productList, outputFolder, *scene, platformIdentifier);
  338. AZ_TracePrintf(Utilities::LogWindow, "Finalizing export process.\n");
  339. result += Process<PostExportEventContext>(productList, outputFolder, platformIdentifier);
  340. auto itr = request.m_jobDescription.m_jobParameters.find(AZ_CRC_CE("DebugFlag"));
  341. if (itr != request.m_jobDescription.m_jobParameters.end() && itr->second == "true")
  342. {
  343. AZStd::string productName;
  344. AzFramework::StringFunc::Path::GetFullFileName(scene->GetSourceFilename().c_str(), productName);
  345. AzFramework::StringFunc::Path::ReplaceExtension(productName, "dbgsg");
  346. AZ::SceneAPI::Utilities::DebugOutput::BuildDebugSceneGraph(outputFolder.c_str(), productList, scene, productName);
  347. }
  348. AZ_TracePrintf(Utilities::LogWindow, "Collecting and registering products.\n");
  349. for (const ExportProduct& product : productList.GetProducts())
  350. {
  351. const AZ::u32 subId = product.m_subId.has_value() ? product.m_subId.value() : BuildSubId(product);
  352. AZ_TracePrintf(Utilities::LogWindow, "Listed product: %s+0x%08x - %s (type %s)\n", product.m_id.ToString<AZStd::string>().c_str(),
  353. subId, product.m_filename.c_str(), product.m_assetType.ToString<AZStd::string>().c_str());
  354. AssetBuilderSDK::JobProduct jobProduct(product.m_filename, product.m_assetType, subId);
  355. PopulateProductDependencies(product, request.m_watchFolder.c_str(), jobProduct);
  356. response.m_outputProducts.emplace_back(jobProduct);
  357. // Unlike the version in ResourceCompilerScene/SceneCompiler.cpp, this version doesn't need to deal with sub ids that were
  358. // created before explicit sub ids were added to the SceneAPI.
  359. }
  360. switch (result.GetResult())
  361. {
  362. case ProcessingResult::Success:
  363. return true;
  364. case ProcessingResult::Ignored:
  365. // While ResourceCompilerScene is still around there's situations where either this builder or RCScene does work but the other not.
  366. // That used to be a cause for a warning and will be again once RCScene has been removed. It's not possible to detect if either
  367. // did any work so the warning is disabled for now.
  368. // AZ_TracePrintf(Utilities::WarningWindow, "Nothing found to convert and export.\n");
  369. return true;
  370. case ProcessingResult::Failure:
  371. AZ_TracePrintf(Utilities::ErrorWindow, "Failure during conversion and exporting.\n");
  372. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
  373. return false;
  374. default:
  375. AZ_TracePrintf(Utilities::ErrorWindow,
  376. "Unexpected result from conversion and exporting (%i).\n", result.GetResult());
  377. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
  378. return false;
  379. }
  380. return true;
  381. }
  382. // BuildSubId has an equivalent counterpart in ResourceCompilerScene. Both need to remain the same to avoid problems with sub ids.
  383. AZ::u32 SceneBuilderWorker::BuildSubId(const AZ::SceneAPI::Events::ExportProduct& product) const
  384. {
  385. // Instead of the just the lower 16-bits, use the full 32-bits that are available. There are production examples of
  386. // uber-scene files that contain hundreds of meshes that need to be split into individual mesh objects as an example.
  387. AZ::u32 id = static_cast<AZ::u32>(product.m_id.GetHash());
  388. if (product.m_lod.has_value())
  389. {
  390. AZ::u8 lod = product.m_lod.value();
  391. if (lod > 0xF)
  392. {
  393. AZ_TracePrintf(AZ::SceneAPI::Utilities::WarningWindow, "%i is too large to fit in the allotted bits for LOD.\n", static_cast<AZ::u32>(lod));
  394. lod = 0xF;
  395. }
  396. // The product uses lods so mask out the lod bits and set them appropriately.
  397. id &= ~AssetBuilderSDK::SUBID_MASK_LOD_LEVEL;
  398. id |= lod << AssetBuilderSDK::SUBID_LOD_LEVEL_SHIFT;
  399. }
  400. return id;
  401. }
  402. } // namespace SceneBuilder