MotionSetAsset.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  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/Asset/AssetManager.h>
  9. #include <AzCore/Component/TickBus.h>
  10. #include <AzCore/StringFunc/StringFunc.h>
  11. #include <AzCore/Utils/Utils.h>
  12. #include <Integration/Assets/MotionSetAsset.h>
  13. #include <EMotionFX/Source/MotionSet.h>
  14. #include <EMotionFX/Source/EMotionFXManager.h>
  15. #include <EMotionFX/Source/Importer/Importer.h>
  16. namespace EMotionFX
  17. {
  18. namespace Integration
  19. {
  20. AZ_CLASS_ALLOCATOR_IMPL(MotionSetAsset, EMotionFXAllocator)
  21. AZ_CLASS_ALLOCATOR_IMPL(MotionSetAssetHandler, EMotionFXAllocator)
  22. /**
  23. * Custom callback registered with EMotion FX for the purpose of intercepting
  24. * motion load requests. We want to pipe all requested loads through our
  25. * asset system.
  26. */
  27. class CustomMotionSetCallback
  28. : public EMotionFX::MotionSetCallback
  29. {
  30. public:
  31. AZ_CLASS_ALLOCATOR(CustomMotionSetCallback, EMotionFXAllocator);
  32. CustomMotionSetCallback(const AZ::Data::Asset<MotionSetAsset>& asset)
  33. : MotionSetCallback(asset.Get()->m_emfxMotionSet.get())
  34. , m_assetData(asset.Get())
  35. {
  36. }
  37. EMotionFX::Motion* LoadMotion(EMotionFX::MotionSet::MotionEntry* entry) override
  38. {
  39. // When EMotionFX requests a motion to be loaded, retrieve it from the asset database.
  40. // It should already be loaded through a motion set.
  41. const char* motionFile = entry->GetFilename();
  42. AZ::Data::AssetId motionAssetId;
  43. AZ::Data::AssetCatalogRequestBus::BroadcastResult(
  44. motionAssetId,
  45. &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath,
  46. motionFile,
  47. azrtti_typeid<MotionAsset>(),
  48. false);
  49. // if it failed to find it, it might be still compiling - try forcing an immediate compile:
  50. if (!motionAssetId.IsValid())
  51. {
  52. AZ_TracePrintf("EMotionFX", "Motion \"%s\" is missing, requesting the asset system to compile it now.\n", motionFile);
  53. AzFramework::AssetSystemRequestBus::Broadcast(&AzFramework::AssetSystem::AssetSystemRequests::CompileAssetSync, motionFile);
  54. // and then try again:
  55. AZ::Data::AssetCatalogRequestBus::BroadcastResult(motionAssetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, motionFile, azrtti_typeid<MotionAsset>(), false);
  56. if (motionAssetId.IsValid())
  57. {
  58. AZ_TracePrintf("EMotionFX", "Motion \"%s\" successfully compiled.\n", motionFile);
  59. }
  60. }
  61. if (motionAssetId.IsValid())
  62. {
  63. for (const auto& motionAsset : m_assetData->m_motionAssets)
  64. {
  65. if (motionAsset.GetId() == motionAssetId)
  66. {
  67. AZ_Assert(motionAsset, "Motion \"%s\" was found in the asset database, but is not initialized.", entry->GetFilename());
  68. AZ_Error("EMotionFX", motionAsset.Get()->m_emfxMotion.get(), "Motion \"%s\" was found in the asset database, but is not valid.", entry->GetFilename());
  69. return motionAsset.Get()->m_emfxMotion.get();
  70. }
  71. }
  72. }
  73. AZ_Error("EMotionFX", false, "Failed to locate motion \"%s\" in the asset database.", entry->GetFilename());
  74. return nullptr;
  75. }
  76. MotionSetAsset* m_assetData;
  77. };
  78. MotionSetAsset::MotionSetAsset(AZ::Data::AssetId id)
  79. : EMotionFXAsset(id)
  80. {}
  81. MotionSetAsset::~MotionSetAsset()
  82. {
  83. AZ::Data::AssetBus::MultiHandler::BusDisconnect();
  84. }
  85. void MotionSetAsset::SetData(EMotionFX::MotionSet* motionSet)
  86. {
  87. m_emfxMotionSet.reset(motionSet);
  88. m_status = AZ::Data::AssetData::AssetStatus::Ready;
  89. }
  90. //////////////////////////////////////////////////////////////////////////
  91. void MotionSetAsset::OnAssetReloaded(AZ::Data::Asset<AZ::Data::AssetData> asset)
  92. {
  93. for (AZ::Data::Asset<MotionAsset>& motionAsset : m_motionAssets)
  94. {
  95. if (motionAsset.GetId() == asset.GetId())
  96. {
  97. motionAsset = asset;
  98. NotifyMotionSetModified(AZ::Data::Asset<MotionSetAsset>(this, AZ::Data::AssetLoadBehavior::Default));
  99. break;
  100. }
  101. }
  102. }
  103. //////////////////////////////////////////////////////////////////////////
  104. void MotionSetAsset::NotifyMotionSetModified(const AZ::Data::Asset<MotionSetAsset>& asset)
  105. {
  106. // When a dependent motion reloads, consider the motion set reloaded as well.
  107. // This allows characters using this motion set to refresh state and reference the new motions.
  108. if (!asset.Get()->m_isReloadPending)
  109. {
  110. AZStd::function<void()> notifyReload = [asset]()
  111. {
  112. using namespace AZ::Data;
  113. AssetBus::Event(asset.GetId(), &AssetBus::Events::OnAssetReloaded, asset);
  114. asset.Get()->m_isReloadPending = false;
  115. };
  116. AZ::TickBus::QueueFunction(notifyReload);
  117. }
  118. }
  119. //////////////////////////////////////////////////////////////////////////
  120. bool MotionSetAssetHandler::OnInitAsset(const AZ::Data::Asset<AZ::Data::AssetData>& asset)
  121. {
  122. MotionSetAsset* assetData = asset.GetAs<MotionSetAsset>();
  123. EMotionFX::Importer::MotionSetSettings motionSettings;
  124. motionSettings.m_isOwnedByRuntime = true;
  125. assetData->m_emfxMotionSet.reset(EMotionFX::GetImporter().LoadMotionSet(
  126. assetData->m_emfxNativeData.data(),
  127. assetData->m_emfxNativeData.size(),
  128. &motionSettings));
  129. if (!assetData->m_emfxMotionSet)
  130. {
  131. AZ_Error("EMotionFX", false, "Failed to initialize motion set asset %s", asset.GetHint().c_str());
  132. return false;
  133. }
  134. // The following code is required to be set so the FileManager detects changes to the files loaded
  135. // through this method. Once EMotionFX is integrated to the asset system this can go away.
  136. AZStd::string assetFilename;
  137. AZ::Data::AssetCatalogRequestBus::BroadcastResult(
  138. assetFilename, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetPathById, asset.GetId());
  139. AZ::IO::FixedMaxPath projectPath = AZ::Utils::GetProjectPath();
  140. if (!projectPath.empty())
  141. {
  142. AZ::IO::FixedMaxPathString filename{ (projectPath / assetFilename).LexicallyNormal().FixedMaxPathStringAsPosix() };
  143. assetData->m_emfxMotionSet->SetFilename(filename.c_str());
  144. }
  145. else
  146. {
  147. if (GetEMotionFX().GetIsInEditorMode())
  148. {
  149. AZ_Warning("EMotionFX", false, "Failed to retrieve project root path . Cannot set absolute filename for '%s'", assetFilename.c_str());
  150. }
  151. assetData->m_emfxMotionSet->SetFilename(assetFilename.c_str());
  152. }
  153. // now load them in:
  154. const EMotionFX::MotionSet::MotionEntries& motionEntries = assetData->m_emfxMotionSet->GetMotionEntries();
  155. // Get the motions in the motion set. Escalate them to the top of the build queue first so that they can be done in parallel.
  156. // This call is fire-and-forget and is very lightweight.
  157. for (const auto& item : motionEntries)
  158. {
  159. const EMotionFX::MotionSet::MotionEntry* motionEntry = item.second;
  160. const char* motionFilename = motionEntry->GetFilename();
  161. AzFramework::AssetSystemRequestBus::Broadcast(&AzFramework::AssetSystem::AssetSystemRequests::EscalateAssetBySearchTerm, motionFilename);
  162. }
  163. // now that they're all escalated, the asset processor will be processing them across all threads, and we can request them one by one:
  164. for (const auto& item : motionEntries)
  165. {
  166. const EMotionFX::MotionSet::MotionEntry* motionEntry = item.second;
  167. const char* motionFilename = motionEntry->GetFilename();
  168. // Find motion file in catalog and grab the asset.
  169. // Jump on the AssetBus for the asset, and queue load.
  170. AZ::Data::AssetId motionAssetId;
  171. AZ::Data::AssetCatalogRequestBus::BroadcastResult(
  172. motionAssetId,
  173. &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath,
  174. motionFilename,
  175. AZ::Data::s_invalidAssetType,
  176. false);
  177. // if it failed to find it, it might be still compiling - try forcing an immediate compile. CompileAssetSync
  178. // will block until the compilation completes AND the catalog is up to date.
  179. if (!motionAssetId.IsValid())
  180. {
  181. AZ_TracePrintf("EMotionFX", "Motion \"%s\" is missing, requesting the asset system to compile it now.\n", motionFilename);
  182. AzFramework::AssetSystemRequestBus::Broadcast(&AzFramework::AssetSystem::AssetSystemRequests::CompileAssetSync, motionFilename);
  183. // and then try again:
  184. AZ::Data::AssetCatalogRequestBus::BroadcastResult(motionAssetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, motionFilename, azrtti_typeid<MotionAsset>(), false);
  185. if (motionAssetId.IsValid())
  186. {
  187. AZ_TracePrintf("EMotionFX", "Motion \"%s\" successfully compiled.\n", motionFilename);
  188. }
  189. }
  190. if (motionAssetId.IsValid())
  191. {
  192. AZ::Data::Asset<MotionAsset> motionAsset = AZ::Data::AssetManager::Instance().GetAsset<MotionAsset>(motionAssetId, AZ::Data::AssetLoadBehavior::Default);
  193. if (motionAsset)
  194. {
  195. motionAsset.BlockUntilLoadComplete();
  196. assetData->BusConnect(motionAssetId);
  197. assetData->m_motionAssets.push_back(motionAsset);
  198. }
  199. else
  200. {
  201. AZ_Warning("EMotionFX", false, "Motion \"%s\" in motion set \"%s\" could not be loaded.", motionFilename, assetFilename.c_str());
  202. }
  203. }
  204. else
  205. {
  206. AZ_Warning("EMotionFX", false, "Motion \"%s\" in motion set \"%s\" could not be found in the asset catalog.", motionFilename, assetFilename.c_str());
  207. }
  208. }
  209. // Set motion set's motion load callback, so if EMotion FX queries back for a motion,
  210. // we can pull the one managed through an AZ::Asset.
  211. assetData->m_emfxMotionSet->SetCallback(aznew CustomMotionSetCallback(asset));
  212. assetData->ReleaseEMotionFXData();
  213. return true;
  214. }
  215. //////////////////////////////////////////////////////////////////////////
  216. AZ::Data::AssetType MotionSetAssetHandler::GetAssetType() const
  217. {
  218. return azrtti_typeid<MotionSetAsset>();
  219. }
  220. //////////////////////////////////////////////////////////////////////////
  221. void MotionSetAssetHandler::GetAssetTypeExtensions(AZStd::vector<AZStd::string>& extensions)
  222. {
  223. extensions.push_back("motionset");
  224. }
  225. //////////////////////////////////////////////////////////////////////////
  226. const char* MotionSetAssetHandler::GetAssetTypeDisplayName() const
  227. {
  228. return "EMotion FX Motion Set";
  229. }
  230. //////////////////////////////////////////////////////////////////////////
  231. const char* MotionSetAssetHandler::GetBrowserIcon() const
  232. {
  233. return "Editor/Images/AssetBrowser/MotionSet_80.svg";
  234. }
  235. //////////////////////////////////////////////////////////////////////////
  236. void MotionSetAssetBuilderHandler::InitAsset(const AZ::Data::Asset<AZ::Data::AssetData>& asset, bool loadStageSucceeded, bool isReload)
  237. {
  238. // Don't need to load the referenced motionset and motion assets since we only care about the product ID ot relative path of the product dependency
  239. AZ_UNUSED(asset);
  240. AZ_UNUSED(loadStageSucceeded);
  241. AZ_UNUSED(isReload);
  242. }
  243. AZ::Data::AssetHandler::LoadResult MotionSetAssetBuilderHandler::LoadAssetData(
  244. const AZ::Data::Asset<AZ::Data::AssetData>& asset,
  245. AZStd::shared_ptr<AZ::Data::AssetDataStream> stream,
  246. const AZ::Data::AssetFilterCB& assetLoadFilterCB)
  247. {
  248. AZ_UNUSED(asset);
  249. AZ_UNUSED(stream);
  250. AZ_UNUSED(assetLoadFilterCB);
  251. return AZ::Data::AssetHandler::LoadResult::LoadComplete;
  252. }
  253. } // namespace Integration
  254. } // namespace EMotionFX