3
0

ModelAsset.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  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::SetReady()
  89. {
  90. m_status = Data::AssetData::AssetStatus::Ready;
  91. }
  92. bool ModelAsset::LocalRayIntersectionAgainstModel(
  93. const AZ::Vector3& rayStart, const AZ::Vector3& rayDir, bool allowBruteForce,
  94. float& distanceNormalized, AZ::Vector3& normal) const
  95. {
  96. if (!m_modelTriangleCount)
  97. {
  98. // [GFX TODO][ATOM-4343 Bake mesh spatial information during AP processing]
  99. m_modelTriangleCount = CalculateTriangleCount();
  100. }
  101. // check the total vertex count for this model and skip kd-tree if the model is simple enough
  102. if (*m_modelTriangleCount > s_minimumModelTriangleCountToOptimize)
  103. {
  104. if (!m_kdTree)
  105. {
  106. BuildKdTree();
  107. AZ_WarningOnce("Model", false, "ray intersection against a model that is still creating spatial information");
  108. return allowBruteForce ? BruteForceRayIntersect(rayStart, rayDir, distanceNormalized, normal) : false;
  109. }
  110. else
  111. {
  112. return m_kdTree->RayIntersection(rayStart, rayDir, distanceNormalized, normal);
  113. }
  114. }
  115. return BruteForceRayIntersect(rayStart, rayDir, distanceNormalized, normal);
  116. }
  117. const AZStd::vector<AZ::Name>& ModelAsset::GetTags() const
  118. {
  119. return m_tags;
  120. }
  121. void ModelAsset::BuildKdTree() const
  122. {
  123. AZStd::lock_guard<AZStd::mutex> lock(m_kdTreeLock);
  124. if ((m_isKdTreeCalculationRunning == false) && !m_kdTree)
  125. {
  126. m_isKdTreeCalculationRunning = true;
  127. // ModelAsset can go away while the job is queued up or is in progress, keep it alive until the job is done
  128. const_cast<ModelAsset*>(this)->Acquire();
  129. // [GFX TODO][ATOM-4343 Bake mesh spatial information during AP processing]
  130. // This is a temporary workaround to enable interactive Editor experience.
  131. // For runtime approach is to do this during asset processing and serialized spatial information alongside with mesh model assets
  132. const auto jobLambda = [&]() -> void
  133. {
  134. AZ_PROFILE_FUNCTION(RPI);
  135. AZStd::unique_ptr<ModelKdTree> tree = AZStd::make_unique<ModelKdTree>();
  136. tree->Build(this);
  137. AZStd::lock_guard<AZStd::mutex> jobLock(m_kdTreeLock);
  138. m_isKdTreeCalculationRunning = false;
  139. m_kdTree = AZStd::move(tree);
  140. const_cast<ModelAsset*>(this)->Release();
  141. };
  142. Job* executeGroupJob = aznew JobFunction<decltype(jobLambda)>(jobLambda, true, nullptr); // Auto-deletes
  143. executeGroupJob->Start();
  144. }
  145. }
  146. bool ModelAsset::BruteForceRayIntersect(
  147. const AZ::Vector3& rayStart, const AZ::Vector3& rayDir, float& distanceNormalized, AZ::Vector3& normal) const
  148. {
  149. // brute force - check every triangle
  150. if (GetLodAssets().empty() == false)
  151. {
  152. // intersect against the highest level of detail
  153. if (ModelLodAsset* loadAssetPtr = GetLodAssets()[0].Get())
  154. {
  155. bool anyHit = false;
  156. AZ::Vector3 intersectionNormal;
  157. float shortestDistanceNormalized = AZStd::numeric_limits<float>::max();
  158. for (const ModelLodAsset::Mesh& mesh : loadAssetPtr->GetMeshes())
  159. {
  160. float currentDistanceNormalized;
  161. if (LocalRayIntersectionAgainstMesh(mesh, rayStart, rayDir, currentDistanceNormalized, intersectionNormal))
  162. {
  163. anyHit = true;
  164. if (currentDistanceNormalized < shortestDistanceNormalized)
  165. {
  166. normal = intersectionNormal;
  167. shortestDistanceNormalized = currentDistanceNormalized;
  168. }
  169. }
  170. }
  171. if (anyHit)
  172. {
  173. distanceNormalized = shortestDistanceNormalized;
  174. }
  175. return anyHit;
  176. }
  177. }
  178. return false;
  179. }
  180. bool ModelAsset::LocalRayIntersectionAgainstMesh(
  181. const ModelLodAsset::Mesh& mesh,
  182. const AZ::Vector3& rayStart,
  183. const AZ::Vector3& rayDir,
  184. float& distanceNormalized,
  185. AZ::Vector3& normal) const
  186. {
  187. const BufferAssetView& indexBufferView = mesh.GetIndexBufferAssetView();
  188. const BufferAssetView* positionBufferView = mesh.GetSemanticBufferAssetView(m_positionName);
  189. if (positionBufferView && positionBufferView->GetBufferAsset().Get())
  190. {
  191. BufferAsset* bufferAssetViewPtr = positionBufferView->GetBufferAsset().Get();
  192. BufferAsset* indexAssetViewPtr = indexBufferView.GetBufferAsset().Get();
  193. if (!bufferAssetViewPtr || !indexAssetViewPtr)
  194. {
  195. return false;
  196. }
  197. RHI::BufferViewDescriptor positionBufferViewDesc = positionBufferView->GetBufferViewDescriptor();
  198. AZStd::span<const uint8_t> positionRawBuffer = bufferAssetViewPtr->GetBuffer();
  199. const uint32_t positionElementSize = positionBufferViewDesc.m_elementSize;
  200. const uint32_t positionElementCount = positionBufferViewDesc.m_elementCount;
  201. // Position is 3 floats
  202. if (positionElementSize != sizeof(float) * 3)
  203. {
  204. AZ_Warning(
  205. "ModelAsset", false, "unsupported mesh posiiton format, only full 3 floats per vertex are supported at the moment");
  206. return false;
  207. }
  208. RHI::BufferViewDescriptor indexBufferViewDesc = indexBufferView.GetBufferViewDescriptor();
  209. AZStd::span<const uint8_t> indexRawBuffer = indexAssetViewPtr->GetBuffer();
  210. const AZ::Vector3 rayEnd = rayStart + rayDir;
  211. AZ::Vector3 a, b, c;
  212. AZ::Vector3 intersectionNormal;
  213. bool anyHit = false;
  214. float shortestDistanceNormalized = AZStd::numeric_limits<float>::max();
  215. const AZ::u32* indexPtr = reinterpret_cast<const AZ::u32*>(
  216. indexRawBuffer.data() + (indexBufferViewDesc.m_elementOffset * indexBufferViewDesc.m_elementSize));
  217. const float* positionPtr = reinterpret_cast<const float*>(
  218. positionRawBuffer.data() + (positionBufferViewDesc.m_elementOffset * positionBufferViewDesc.m_elementSize));
  219. Intersect::SegmentTriangleHitTester hitTester(rayStart, rayEnd);
  220. constexpr int StepSize = 3; // number of values per vertex (x, y, z)
  221. for (uint32_t indexIter = 0; indexIter < indexBufferViewDesc.m_elementCount; indexIter += StepSize, indexPtr += StepSize)
  222. {
  223. AZ::u32 index0 = indexPtr[0];
  224. AZ::u32 index1 = indexPtr[1];
  225. AZ::u32 index2 = indexPtr[2];
  226. if (index0 >= positionElementCount || index1 >= positionElementCount || index2 >= positionElementCount)
  227. {
  228. AZ_Warning("ModelAsset", false, "mesh has a bad vertex index");
  229. return false;
  230. }
  231. // faster than AZ::Vector3 c-tor
  232. const float* aRef = &positionPtr[index0 * StepSize];
  233. a.Set(aRef);
  234. const float* bRef = &positionPtr[index1 * StepSize];
  235. b.Set(bRef);
  236. const float* cRef = &positionPtr[index2 * StepSize];
  237. c.Set(cRef);
  238. float currentDistanceNormalized;
  239. if (hitTester.IntersectSegmentTriangleCCW(a, b, c, intersectionNormal, currentDistanceNormalized))
  240. {
  241. anyHit = true;
  242. if (currentDistanceNormalized < shortestDistanceNormalized)
  243. {
  244. normal = intersectionNormal;
  245. shortestDistanceNormalized = currentDistanceNormalized;
  246. }
  247. }
  248. }
  249. if (anyHit)
  250. {
  251. distanceNormalized = shortestDistanceNormalized;
  252. }
  253. return anyHit;
  254. }
  255. return false;
  256. }
  257. AZStd::size_t ModelAsset::CalculateTriangleCount() const
  258. {
  259. AZStd::size_t modelTriangleCount = 0;
  260. if (GetLodAssets().empty() == false)
  261. {
  262. if (ModelLodAsset* loadAssetPtr = GetLodAssets()[0].Get())
  263. {
  264. for (const ModelLodAsset::Mesh& mesh : loadAssetPtr->GetMeshes())
  265. {
  266. const AZStd::span<const ModelLodAsset::Mesh::StreamBufferInfo>& streamBufferList = mesh.GetStreamBufferInfoList();
  267. // find position semantic
  268. const ModelLodAsset::Mesh::StreamBufferInfo* positionBuffer = nullptr;
  269. for (const ModelLodAsset::Mesh::StreamBufferInfo& bufferInfo : streamBufferList)
  270. {
  271. if (bufferInfo.m_semantic.m_name == m_positionName)
  272. {
  273. positionBuffer = &bufferInfo;
  274. break;
  275. }
  276. }
  277. if (positionBuffer)
  278. {
  279. const RHI::BufferViewDescriptor& desc = positionBuffer->m_bufferAssetView.GetBufferViewDescriptor();
  280. modelTriangleCount += desc.m_elementCount / 3;
  281. }
  282. }
  283. }
  284. }
  285. 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");
  286. return modelTriangleCount;
  287. }
  288. void ModelAsset::InitData(
  289. AZ::Name name,
  290. AZStd::span<Data::Asset<ModelLodAsset>> lodAssets,
  291. const ModelMaterialSlotMap& materialSlots,
  292. const ModelMaterialSlot& fallbackSlot,
  293. AZStd::span<AZ::Name> tags)
  294. {
  295. AZ_Assert(!m_isKdTreeCalculationRunning, "Overwriting a ModelAsset while it is calculating its kd tree.");
  296. // Clear out the current AABB, we'll reset it with the data from the LOD assets.
  297. m_aabb = AZ::Aabb::CreateNull();
  298. // Copy the trivially-copyable data.
  299. m_name = name;
  300. m_materialSlots = materialSlots;
  301. m_fallbackSlot = fallbackSlot;
  302. // Clear out the runtime-calculated data.
  303. m_kdTree = {};
  304. m_isKdTreeCalculationRunning = false;
  305. m_modelTriangleCount = {};
  306. // Clear out tags and LOD Assets, we'll set those individually.
  307. m_lodAssets.clear();
  308. m_tags = {};
  309. for (const auto& lodAsset : lodAssets)
  310. {
  311. m_lodAssets.push_back(lodAsset);
  312. if (lodAsset.IsReady())
  313. {
  314. m_aabb.AddAabb(lodAsset->GetAabb());
  315. }
  316. }
  317. for (const auto& tag : tags)
  318. {
  319. m_tags.push_back(tag);
  320. }
  321. }
  322. // Create a stable ID for our default fallback model.
  323. const AZ::Data::AssetId ModelAssetHandler::s_defaultModelAssetId{ "{D676DD3C-0560-4F39-99E0-B6DCBC7CEDAA}", 0 };
  324. Data::AssetHandler::LoadResult ModelAssetHandler::LoadAssetData(
  325. const AZ::Data::Asset<AZ::Data::AssetData>& asset,
  326. AZStd::shared_ptr<AZ::Data::AssetDataStream> stream,
  327. const AZ::Data::AssetFilterCB& assetLoadFilterCB)
  328. {
  329. // If there's a 0-length stream, this must be trying to load our default fallback model.
  330. // Fill in the asset data with a generated unit X-shaped model.
  331. // We need to generate the data instead of load a fallback asset because model assets have dependencies
  332. // on buffer and material assets, and fallback assets need to not have any dependencies to be able to load
  333. // correctly when used as fallbacks. (The AssetManager doesn't currently support handling dependency pre-loading
  334. // for fallback assets)
  335. if (stream->GetLength() == 0)
  336. {
  337. auto assetData = asset.GetAs<AZ::RPI::ModelAsset>();
  338. if (assetData)
  339. {
  340. ModelAssetHelpers::CreateUnitX(assetData);
  341. }
  342. return Data::AssetHandler::LoadResult::LoadComplete;
  343. }
  344. return AssetHandler::LoadAssetData(asset, stream, assetLoadFilterCB);
  345. }
  346. Data::AssetId ModelAssetHandler::AssetMissingInCatalog(const Data::Asset<Data::AssetData>& asset)
  347. {
  348. AZ_Info(
  349. "Model",
  350. "Model id " AZ_STRING_FORMAT " not found in asset catalog, using fallback model.\n",
  351. AZ_STRING_ARG(asset.GetId().ToFixedString()));
  352. // Find out if the asset is missing completely or just still processing
  353. // and escalate the asset to the top of the list if it's queued.
  354. AzFramework::AssetSystem::AssetStatus missingAssetStatus = AzFramework::AssetSystem::AssetStatus::AssetStatus_Unknown;
  355. AzFramework::AssetSystemRequestBus::BroadcastResult(
  356. missingAssetStatus, &AzFramework::AssetSystem::AssetSystemRequests::GetAssetStatusById, asset.GetId().m_guid);
  357. if (missingAssetStatus == AzFramework::AssetSystem::AssetStatus::AssetStatus_Queued)
  358. {
  359. bool sendSucceeded = false;
  360. AzFramework::AssetSystemRequestBus::BroadcastResult(
  361. sendSucceeded, &AzFramework::AssetSystem::AssetSystemRequests::EscalateAssetByUuid, asset.GetId().m_guid);
  362. }
  363. // Make sure the default model asset has an entry in the asset catalog so that the asset system will try to load it.
  364. // Note that we specifically give it a 0-byte size and a non-empty path so that the load will just trivially succeed
  365. // with a 0-byte asset stream. This will enable us to detect it in LoadAssetData and fill in the data with a generated
  366. // unit cube. We can't use an on-disk model asset because the AssetMissing system currently doesn't correctly handle
  367. // assets with dependent assets (like azmodel), so we need to just load an "empty" asset and then fill it in with data
  368. // in LoadAssetData.
  369. {
  370. Data::AssetInfo assetInfo;
  371. assetInfo.m_assetId = s_defaultModelAssetId;
  372. assetInfo.m_assetType = azrtti_typeid<AZ::RPI::ModelAsset>();
  373. assetInfo.m_relativePath = "default_fallback_model";
  374. assetInfo.m_sizeBytes = 0;
  375. AZ::Data::AssetCatalogRequestBus::Broadcast(
  376. &AZ::Data::AssetCatalogRequestBus::Events::RegisterAsset, assetInfo.m_assetId, assetInfo);
  377. }
  378. return s_defaultModelAssetId;
  379. }
  380. } // namespace RPI
  381. } // namespace AZ