ModelAsset.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  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 <Atom/RPI.Reflect/Model/ModelAsset.h>
  9. #include <Atom/RPI.Reflect/Model/ModelAssetHelpers.h>
  10. #include <Atom/RPI.Reflect/Model/ModelKdTree.h>
  11. #include <AzCore/Asset/AssetSerializer.h>
  12. #include <AzCore/Jobs/JobFunction.h>
  13. #include <AzCore/Math/IntersectSegment.h>
  14. #include <AzCore/std/limits.h>
  15. #include <AzCore/RTTI/ReflectContext.h>
  16. #include <AzCore/Serialization/SerializeContext.h>
  17. #include <AzCore/Serialization/EditContext.h>
  18. #include <AzFramework/Asset/AssetSystemBus.h>
  19. namespace AZ
  20. {
  21. namespace RPI
  22. {
  23. const char* ModelAsset::DisplayName = "ModelAsset";
  24. const char* ModelAsset::Group = "Model";
  25. const char* ModelAsset::Extension = "azmodel";
  26. void ModelAsset::Reflect(ReflectContext* context)
  27. {
  28. if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
  29. {
  30. serializeContext->Class<ModelAsset, Data::AssetData>()
  31. ->Version(1)
  32. ->Field("Name", &ModelAsset::m_name)
  33. ->Field("Aabb", &ModelAsset::m_aabb)
  34. ->Field("MaterialSlots", &ModelAsset::m_materialSlots)
  35. ->Field("LodAssets", &ModelAsset::m_lodAssets)
  36. ->Field("Tags", &ModelAsset::m_tags)
  37. ;
  38. // Note: This class needs to have edit context reflection so PropertyAssetCtrl::OnEditButtonClicked
  39. // can open the asset with the preferred asset editor (Scene Settings).
  40. if (auto* editContext = serializeContext->GetEditContext())
  41. {
  42. editContext->Class<ModelAsset>("Model Asset", "")
  43. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  44. ;
  45. }
  46. }
  47. }
  48. ModelAsset::ModelAsset()
  49. {
  50. // c-tor and d-tor have to be defined in .cpp in order to have AZStd::unique_ptr<ModelKdTree> without having to include the header of KDTree
  51. }
  52. ModelAsset::~ModelAsset()
  53. {
  54. // c-tor and d-tor have to be defined in .cpp in order to have AZStd::unique_ptr<ModelKdTree> without having to include the header of KDTree
  55. }
  56. const Name& ModelAsset::GetName() const
  57. {
  58. return m_name;
  59. }
  60. const Aabb& ModelAsset::GetAabb() const
  61. {
  62. return m_aabb;
  63. }
  64. const ModelMaterialSlotMap& ModelAsset::GetMaterialSlots() const
  65. {
  66. return m_materialSlots;
  67. }
  68. const ModelMaterialSlot& ModelAsset::FindMaterialSlot(uint32_t stableId) const
  69. {
  70. auto iter = m_materialSlots.find(stableId);
  71. if (iter == m_materialSlots.end())
  72. {
  73. return m_fallbackSlot;
  74. }
  75. else
  76. {
  77. return iter->second;
  78. }
  79. }
  80. size_t ModelAsset::GetLodCount() const
  81. {
  82. return m_lodAssets.size();
  83. }
  84. AZStd::span<const Data::Asset<ModelLodAsset>> ModelAsset::GetLodAssets() const
  85. {
  86. return AZStd::span<const Data::Asset<ModelLodAsset>>(m_lodAssets);
  87. }
  88. void ModelAsset::LoadBufferAssets()
  89. {
  90. for (auto& lodAsset : m_lodAssets)
  91. {
  92. lodAsset->LoadBufferAssets();
  93. }
  94. }
  95. void ModelAsset::ReleaseBufferAssets()
  96. {
  97. for (auto& lodAsset : m_lodAssets)
  98. {
  99. lodAsset->ReleaseBufferAssets();
  100. }
  101. }
  102. void ModelAsset::AddRefBufferAssets()
  103. {
  104. if (m_bufferAssetsRef == 0)
  105. {
  106. LoadBufferAssets();
  107. }
  108. m_bufferAssetsRef++;
  109. }
  110. void ModelAsset::ReleaseRefBufferAssets()
  111. {
  112. if (m_bufferAssetsRef > 0)
  113. {
  114. m_bufferAssetsRef--;
  115. if (m_bufferAssetsRef == 0)
  116. {
  117. ReleaseBufferAssets();
  118. }
  119. }
  120. }
  121. bool ModelAsset::SupportLocalRayIntersection() const
  122. {
  123. return m_bufferAssetsRef > 0;
  124. }
  125. void ModelAsset::SetReady()
  126. {
  127. m_status = Data::AssetData::AssetStatus::Ready;
  128. }
  129. bool ModelAsset::LocalRayIntersectionAgainstModel(
  130. const AZ::Vector3& rayStart, const AZ::Vector3& rayDir, bool allowBruteForce,
  131. float& distanceNormalized, AZ::Vector3& normal) const
  132. {
  133. if (!m_modelTriangleCount)
  134. {
  135. // [GFX TODO][ATOM-4343 Bake mesh spatial information during AP processing]
  136. m_modelTriangleCount = CalculateTriangleCount();
  137. }
  138. // check the total vertex count for this model and skip kd-tree if the model is simple enough
  139. if (*m_modelTriangleCount > s_minimumModelTriangleCountToOptimize)
  140. {
  141. if (!m_kdTree)
  142. {
  143. BuildKdTree();
  144. AZ_WarningOnce("Model", false, "ray intersection against a model that is still creating spatial information");
  145. return allowBruteForce ? BruteForceRayIntersect(rayStart, rayDir, distanceNormalized, normal) : false;
  146. }
  147. else
  148. {
  149. return m_kdTree->RayIntersection(rayStart, rayDir, distanceNormalized, normal);
  150. }
  151. }
  152. return BruteForceRayIntersect(rayStart, rayDir, distanceNormalized, normal);
  153. }
  154. const AZStd::vector<AZ::Name>& ModelAsset::GetTags() const
  155. {
  156. return m_tags;
  157. }
  158. void ModelAsset::BuildKdTree() const
  159. {
  160. AZStd::lock_guard<AZStd::mutex> lock(m_kdTreeLock);
  161. if ((m_isKdTreeCalculationRunning == false) && !m_kdTree)
  162. {
  163. m_isKdTreeCalculationRunning = true;
  164. // ModelAsset can go away while the job is queued up or is in progress, keep it alive until the job is done
  165. const_cast<ModelAsset*>(this)->Acquire();
  166. // [GFX TODO][ATOM-4343 Bake mesh spatial information during AP processing]
  167. // This is a temporary workaround to enable interactive Editor experience.
  168. // For runtime approach is to do this during asset processing and serialized spatial information alongside with mesh model assets
  169. const auto jobLambda = [&]() -> void
  170. {
  171. AZ_PROFILE_FUNCTION(RPI);
  172. AZStd::unique_ptr<ModelKdTree> tree = AZStd::make_unique<ModelKdTree>();
  173. tree->Build(this);
  174. AZStd::lock_guard<AZStd::mutex> jobLock(m_kdTreeLock);
  175. m_isKdTreeCalculationRunning = false;
  176. m_kdTree = AZStd::move(tree);
  177. const_cast<ModelAsset*>(this)->Release();
  178. };
  179. Job* executeGroupJob = aznew JobFunction<decltype(jobLambda)>(jobLambda, true, nullptr); // Auto-deletes
  180. executeGroupJob->Start();
  181. }
  182. }
  183. bool ModelAsset::BruteForceRayIntersect(
  184. const AZ::Vector3& rayStart, const AZ::Vector3& rayDir, float& distanceNormalized, AZ::Vector3& normal) const
  185. {
  186. // brute force - check every triangle
  187. if (GetLodAssets().empty() == false)
  188. {
  189. // intersect against the highest level of detail
  190. if (ModelLodAsset* loadAssetPtr = GetLodAssets()[0].Get())
  191. {
  192. bool anyHit = false;
  193. AZ::Vector3 intersectionNormal;
  194. float shortestDistanceNormalized = AZStd::numeric_limits<float>::max();
  195. for (const ModelLodAsset::Mesh& mesh : loadAssetPtr->GetMeshes())
  196. {
  197. float currentDistanceNormalized;
  198. if (LocalRayIntersectionAgainstMesh(mesh, rayStart, rayDir, currentDistanceNormalized, intersectionNormal))
  199. {
  200. anyHit = true;
  201. if (currentDistanceNormalized < shortestDistanceNormalized)
  202. {
  203. normal = intersectionNormal;
  204. shortestDistanceNormalized = currentDistanceNormalized;
  205. }
  206. }
  207. }
  208. if (anyHit)
  209. {
  210. distanceNormalized = shortestDistanceNormalized;
  211. }
  212. return anyHit;
  213. }
  214. }
  215. return false;
  216. }
  217. bool ModelAsset::LocalRayIntersectionAgainstMesh(
  218. const ModelLodAsset::Mesh& mesh,
  219. const AZ::Vector3& rayStart,
  220. const AZ::Vector3& rayDir,
  221. float& distanceNormalized,
  222. AZ::Vector3& normal) const
  223. {
  224. const BufferAssetView& indexBufferView = mesh.GetIndexBufferAssetView();
  225. const BufferAssetView* positionBufferView = mesh.GetSemanticBufferAssetView(m_positionName);
  226. if (positionBufferView && positionBufferView->GetBufferAsset().Get())
  227. {
  228. BufferAsset* bufferAssetViewPtr = positionBufferView->GetBufferAsset().Get();
  229. BufferAsset* indexAssetViewPtr = indexBufferView.GetBufferAsset().Get();
  230. if (!bufferAssetViewPtr || !indexAssetViewPtr)
  231. {
  232. return false;
  233. }
  234. RHI::BufferViewDescriptor positionBufferViewDesc = positionBufferView->GetBufferViewDescriptor();
  235. AZStd::span<const uint8_t> positionRawBuffer = bufferAssetViewPtr->GetBuffer();
  236. const uint32_t positionElementSize = positionBufferViewDesc.m_elementSize;
  237. const uint32_t positionElementCount = positionBufferViewDesc.m_elementCount;
  238. // Position is 3 floats
  239. if (positionElementSize != sizeof(float) * 3)
  240. {
  241. AZ_Warning(
  242. "ModelAsset", false, "unsupported mesh posiiton format, only full 3 floats per vertex are supported at the moment");
  243. return false;
  244. }
  245. RHI::BufferViewDescriptor indexBufferViewDesc = indexBufferView.GetBufferViewDescriptor();
  246. AZStd::span<const uint8_t> indexRawBuffer = indexAssetViewPtr->GetBuffer();
  247. const AZ::Vector3 rayEnd = rayStart + rayDir;
  248. AZ::Vector3 a, b, c;
  249. AZ::Vector3 intersectionNormal;
  250. bool anyHit = false;
  251. float shortestDistanceNormalized = AZStd::numeric_limits<float>::max();
  252. const AZ::u32* indexPtr = reinterpret_cast<const AZ::u32*>(
  253. indexRawBuffer.data() + (indexBufferViewDesc.m_elementOffset * indexBufferViewDesc.m_elementSize));
  254. const float* positionPtr = reinterpret_cast<const float*>(
  255. positionRawBuffer.data() + (positionBufferViewDesc.m_elementOffset * positionBufferViewDesc.m_elementSize));
  256. Intersect::SegmentTriangleHitTester hitTester(rayStart, rayEnd);
  257. constexpr int StepSize = 3; // number of values per vertex (x, y, z)
  258. for (uint32_t indexIter = 0; indexIter < indexBufferViewDesc.m_elementCount; indexIter += StepSize, indexPtr += StepSize)
  259. {
  260. AZ::u32 index0 = indexPtr[0];
  261. AZ::u32 index1 = indexPtr[1];
  262. AZ::u32 index2 = indexPtr[2];
  263. if (index0 >= positionElementCount || index1 >= positionElementCount || index2 >= positionElementCount)
  264. {
  265. AZ_Warning("ModelAsset", false, "mesh has a bad vertex index");
  266. return false;
  267. }
  268. // faster than AZ::Vector3 c-tor
  269. const float* aRef = &positionPtr[index0 * StepSize];
  270. a.Set(aRef);
  271. const float* bRef = &positionPtr[index1 * StepSize];
  272. b.Set(bRef);
  273. const float* cRef = &positionPtr[index2 * StepSize];
  274. c.Set(cRef);
  275. float currentDistanceNormalized;
  276. if (hitTester.IntersectSegmentTriangleCCW(a, b, c, intersectionNormal, currentDistanceNormalized))
  277. {
  278. anyHit = true;
  279. if (currentDistanceNormalized < shortestDistanceNormalized)
  280. {
  281. normal = intersectionNormal;
  282. shortestDistanceNormalized = currentDistanceNormalized;
  283. }
  284. }
  285. }
  286. if (anyHit)
  287. {
  288. distanceNormalized = shortestDistanceNormalized;
  289. }
  290. return anyHit;
  291. }
  292. return false;
  293. }
  294. AZStd::size_t ModelAsset::CalculateTriangleCount() const
  295. {
  296. AZStd::size_t modelTriangleCount = 0;
  297. if (GetLodAssets().empty() == false)
  298. {
  299. if (ModelLodAsset* loadAssetPtr = GetLodAssets()[0].Get())
  300. {
  301. for (const ModelLodAsset::Mesh& mesh : loadAssetPtr->GetMeshes())
  302. {
  303. const AZStd::span<const ModelLodAsset::Mesh::StreamBufferInfo>& streamBufferList = mesh.GetStreamBufferInfoList();
  304. // find position semantic
  305. const ModelLodAsset::Mesh::StreamBufferInfo* positionBuffer = nullptr;
  306. for (const ModelLodAsset::Mesh::StreamBufferInfo& bufferInfo : streamBufferList)
  307. {
  308. if (bufferInfo.m_semantic.m_name == m_positionName)
  309. {
  310. positionBuffer = &bufferInfo;
  311. break;
  312. }
  313. }
  314. if (positionBuffer)
  315. {
  316. const RHI::BufferViewDescriptor& desc = positionBuffer->m_bufferAssetView.GetBufferViewDescriptor();
  317. modelTriangleCount += desc.m_elementCount / 3;
  318. }
  319. }
  320. }
  321. }
  322. AZ_Warning("Model", modelTriangleCount < ((2<<23) / 3), "Model has too many vertices for the spatial optimization. Currently only up to 16,777,216 is supported");
  323. return modelTriangleCount;
  324. }
  325. void ModelAsset::InitData(
  326. AZ::Name name,
  327. AZStd::span<Data::Asset<ModelLodAsset>> lodAssets,
  328. const ModelMaterialSlotMap& materialSlots,
  329. const ModelMaterialSlot& fallbackSlot,
  330. AZStd::span<AZ::Name> tags)
  331. {
  332. AZ_Assert(!m_isKdTreeCalculationRunning, "Overwriting a ModelAsset while it is calculating its kd tree.");
  333. // Clear out the current AABB, we'll reset it with the data from the LOD assets.
  334. m_aabb = AZ::Aabb::CreateNull();
  335. // Copy the trivially-copyable data.
  336. m_name = name;
  337. m_materialSlots = materialSlots;
  338. m_fallbackSlot = fallbackSlot;
  339. // Clear out the runtime-calculated data.
  340. m_kdTree = {};
  341. m_isKdTreeCalculationRunning = false;
  342. m_modelTriangleCount = {};
  343. // Clear out tags and LOD Assets, we'll set those individually.
  344. m_lodAssets.clear();
  345. m_tags = {};
  346. for (const auto& lodAsset : lodAssets)
  347. {
  348. m_lodAssets.push_back(lodAsset);
  349. if (lodAsset.IsReady())
  350. {
  351. m_aabb.AddAabb(lodAsset->GetAabb());
  352. }
  353. }
  354. for (const auto& tag : tags)
  355. {
  356. m_tags.push_back(tag);
  357. }
  358. }
  359. // Create a stable ID for our default fallback model.
  360. const AZ::Data::AssetId ModelAssetHandler::s_defaultModelAssetId{ "{D676DD3C-0560-4F39-99E0-B6DCBC7CEDAA}", 0 };
  361. Data::AssetHandler::LoadResult ModelAssetHandler::LoadAssetData(
  362. const AZ::Data::Asset<AZ::Data::AssetData>& asset,
  363. AZStd::shared_ptr<AZ::Data::AssetDataStream> stream,
  364. const AZ::Data::AssetFilterCB& assetLoadFilterCB)
  365. {
  366. // If there's a 0-length stream, this must be trying to load our default fallback model.
  367. // Fill in the asset data with a generated unit X-shaped model.
  368. // We need to generate the data instead of load a fallback asset because model assets have dependencies
  369. // on buffer and material assets, and fallback assets need to not have any dependencies to be able to load
  370. // correctly when used as fallbacks. (The AssetManager doesn't currently support handling dependency pre-loading
  371. // for fallback assets)
  372. if (stream->GetLength() == 0)
  373. {
  374. auto assetData = asset.GetAs<AZ::RPI::ModelAsset>();
  375. if (assetData)
  376. {
  377. ModelAssetHelpers::CreateUnitX(assetData);
  378. }
  379. return Data::AssetHandler::LoadResult::LoadComplete;
  380. }
  381. return AssetHandler::LoadAssetData(asset, stream, assetLoadFilterCB);
  382. }
  383. Data::AssetId ModelAssetHandler::AssetMissingInCatalog(const Data::Asset<Data::AssetData>& asset)
  384. {
  385. AZ_Info(
  386. "Model",
  387. "Model id " AZ_STRING_FORMAT " not found in asset catalog, using fallback model.\n",
  388. AZ_STRING_ARG(asset.GetId().ToFixedString()));
  389. // Find out if the asset is missing completely or just still processing
  390. // and escalate the asset to the top of the list if it's queued.
  391. AzFramework::AssetSystem::AssetStatus missingAssetStatus = AzFramework::AssetSystem::AssetStatus::AssetStatus_Unknown;
  392. AzFramework::AssetSystemRequestBus::BroadcastResult(
  393. missingAssetStatus, &AzFramework::AssetSystem::AssetSystemRequests::GetAssetStatusById, asset.GetId().m_guid);
  394. if (missingAssetStatus == AzFramework::AssetSystem::AssetStatus::AssetStatus_Queued)
  395. {
  396. bool sendSucceeded = false;
  397. AzFramework::AssetSystemRequestBus::BroadcastResult(
  398. sendSucceeded, &AzFramework::AssetSystem::AssetSystemRequests::EscalateAssetByUuid, asset.GetId().m_guid);
  399. }
  400. // Make sure the default model asset has an entry in the asset catalog so that the asset system will try to load it.
  401. // Note that we specifically give it a 0-byte size and a non-empty path so that the load will just trivially succeed
  402. // with a 0-byte asset stream. This will enable us to detect it in LoadAssetData and fill in the data with a generated
  403. // unit cube. We can't use an on-disk model asset because the AssetMissing system currently doesn't correctly handle
  404. // assets with dependent assets (like azmodel), so we need to just load an "empty" asset and then fill it in with data
  405. // in LoadAssetData.
  406. {
  407. Data::AssetInfo assetInfo;
  408. assetInfo.m_assetId = s_defaultModelAssetId;
  409. assetInfo.m_assetType = azrtti_typeid<AZ::RPI::ModelAsset>();
  410. assetInfo.m_relativePath = "default_fallback_model";
  411. assetInfo.m_sizeBytes = 0;
  412. AZ::Data::AssetCatalogRequestBus::Broadcast(
  413. &AZ::Data::AssetCatalogRequestBus::Events::RegisterAsset, assetInfo.m_assetId, assetInfo);
  414. }
  415. return s_defaultModelAssetId;
  416. }
  417. } // namespace RPI
  418. } // namespace AZ