AssImpBlendShapeImporter.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  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/Serialization/SerializeContext.h>
  9. #include <AzCore/std/containers/bitset.h>
  10. #include <AzCore/std/smart_ptr/make_shared.h>
  11. #include <AzCore/std/string/conversions.h>
  12. #include <AzToolsFramework/Debug/TraceContext.h>
  13. #include <SceneAPI/SceneBuilder/Importers/AssImpBlendShapeImporter.h>
  14. #include <SceneAPI/SceneBuilder/Importers/Utilities/AssImpMeshImporterUtilities.h>
  15. #include <SceneAPI/SceneBuilder/Importers/Utilities/RenamedNodesMap.h>
  16. #include <SceneAPI/SceneBuilder/Importers/ImporterUtilities.h>
  17. #include <SceneAPI/SceneBuilder/SceneSystem.h>
  18. #include <SceneAPI/SDKWrapper/AssImpNodeWrapper.h>
  19. #include <SceneAPI/SDKWrapper/AssImpSceneWrapper.h>
  20. #include <SceneAPI/SDKWrapper/AssImpTypeConverter.h>
  21. #include <SceneAPI/SceneCore/Utilities/Reporting.h>
  22. #include <SceneAPI/SceneData/GraphData/MeshData.h>
  23. #include <SceneAPI/SceneData/GraphData/SkinMeshData.h>
  24. #include <SceneAPI/SceneData/GraphData/BlendShapeData.h>
  25. #include <assimp/scene.h>
  26. namespace AZ
  27. {
  28. namespace SceneAPI
  29. {
  30. namespace SceneBuilder
  31. {
  32. AssImpBlendShapeImporter::AssImpBlendShapeImporter()
  33. {
  34. BindToCall(&AssImpBlendShapeImporter::ImportBlendShapes);
  35. }
  36. void AssImpBlendShapeImporter::Reflect(ReflectContext* context)
  37. {
  38. SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context);
  39. if (serializeContext)
  40. {
  41. // Revision 3: Fixed an issue where jack.fbx was failing to process
  42. // Revision 4: Handle duplicate blend shape animations
  43. serializeContext->Class<AssImpBlendShapeImporter, SceneCore::LoadingComponent>()->Version(4);
  44. }
  45. }
  46. Events::ProcessingResult AssImpBlendShapeImporter::ImportBlendShapes(AssImpSceneNodeAppendedContext& context)
  47. {
  48. AZ_TraceContext("Importer", "Blend Shapes");
  49. int numMesh = context.m_sourceNode.GetAssImpNode()->mNumMeshes;
  50. bool animMeshExists = false;
  51. for (int idx = 0; idx < numMesh; idx++)
  52. {
  53. int meshId = context.m_sourceNode.GetAssImpNode()->mMeshes[idx];
  54. aiMesh* aiMesh = context.m_sourceScene.GetAssImpScene()->mMeshes[meshId];
  55. if (aiMesh->mNumAnimMeshes)
  56. {
  57. animMeshExists = true;
  58. break;
  59. }
  60. }
  61. if (!animMeshExists)
  62. {
  63. return Events::ProcessingResult::Ignored;
  64. }
  65. GetMeshDataFromParentResult meshDataResult(GetMeshDataFromParent(context));
  66. if (!meshDataResult.IsSuccess())
  67. {
  68. return meshDataResult.GetError();
  69. }
  70. Events::ProcessingResultCombiner combinedBlendShapeResult;
  71. // 1. Loop through meshes & anims
  72. // Create storage: Anim to meshes
  73. // 2. Loop through anims & meshes
  74. // Create an anim mesh for each anim, with meshes re-combined.
  75. // AssImp separates meshes that have multiple materials.
  76. // This code re-combines them to match previous FBX SDK behavior,
  77. // so they can be separated by engine code instead.
  78. // Can't de-dupe nodes in the first loop because we can't generate names until we create nodes later.
  79. // Because meshes are split on material at this point and need to be recombined, we can be in a position where
  80. // There is a legit duped anim mesh that needs to be combined based on the outer non-anim mesh,
  81. // or this is a duplicately named anim mesh that needs to be de-duped. There is also the case where both are true,
  82. // it's a duplicate name and the non-anim mesh has to be deduped.
  83. // Helper struct to track an anim mesh and its associated mesh.
  84. struct AnimMeshAndSceneMeshIndex
  85. {
  86. AnimMeshAndSceneMeshIndex(const aiAnimMesh* aiAnimMesh, const aiMesh* aiMesh)
  87. : m_aiAnimMesh(aiAnimMesh)
  88. , m_aiMesh(aiMesh)
  89. {
  90. }
  91. const aiAnimMesh* m_aiAnimMesh = nullptr;
  92. const aiMesh* m_aiMesh = nullptr;
  93. };
  94. // Helper struct to track all anim meshes at an index for all scene meshes.
  95. struct AnimMeshAndSceneMeshes
  96. {
  97. AZStd::vector<AnimMeshAndSceneMeshIndex> m_animMeshAndSceneMeshIndex;
  98. };
  99. // Map the animation index to the list of anim meshes at that index, and the mesh associated with those anim meshes.
  100. AZStd::map<int, AnimMeshAndSceneMeshes> animMeshIndexToSceneMeshes;
  101. for (int nodeMeshIdx = 0; nodeMeshIdx < numMesh; nodeMeshIdx++)
  102. {
  103. int sceneMeshIdx = context.m_sourceNode.GetAssImpNode()->mMeshes[nodeMeshIdx];
  104. const aiMesh* aiMesh = context.m_sourceScene.GetAssImpScene()->mMeshes[sceneMeshIdx];
  105. for (unsigned int animIdx = 0; animIdx < aiMesh->mNumAnimMeshes; animIdx++)
  106. {
  107. aiAnimMesh* aiAnimMesh = aiMesh->mAnimMeshes[animIdx];
  108. // This code executes if:
  109. // A mesh in the FBX file had multiple materials and blend shapes.
  110. // This means that AssImp splits that mesh to one material per mesh.
  111. // AssImp creates a set of anim meshes for each mesh based on that split.
  112. // This verifies that those anim mesh arrays are in the same order across all split meshes, if it fails
  113. // it means this logic needs to be updated, but it also catches that here earlier in an obvious way,
  114. // instead of failing later in a harder to track way.
  115. if (animMeshIndexToSceneMeshes.contains(animIdx))
  116. {
  117. const AnimMeshAndSceneMeshIndex& firstExistingAnim(
  118. animMeshIndexToSceneMeshes[animIdx].m_animMeshAndSceneMeshIndex[0]);
  119. if (strcmp(
  120. firstExistingAnim.m_aiAnimMesh->mName.C_Str(),
  121. aiAnimMesh->mName.C_Str()) != 0)
  122. {
  123. AZ_Error(
  124. Utilities::ErrorWindow, false,
  125. "Meshes %s and %s on node %s have mismatched animations %s and %s at index %d. This can be resolved by "
  126. "either manually separating meshes by material in the source scene file, or by updating this logic to "
  127. "handle out of order animation indices.",
  128. firstExistingAnim.m_aiMesh->mName.C_Str(),
  129. aiMesh->mName.C_Str(),
  130. context.m_sourceNode.GetName(),
  131. firstExistingAnim.m_aiAnimMesh->mName.C_Str(),
  132. aiAnimMesh->mName.C_Str(), animIdx);
  133. return Events::ProcessingResult::Failure;
  134. }
  135. }
  136. animMeshIndexToSceneMeshes[animIdx].m_animMeshAndSceneMeshIndex.emplace_back(
  137. AnimMeshAndSceneMeshIndex(aiAnimMesh, aiMesh));
  138. }
  139. }
  140. for (const auto& animMeshToSceneMeshes : animMeshIndexToSceneMeshes)
  141. {
  142. AZStd::shared_ptr<SceneData::GraphData::BlendShapeData> blendShapeData =
  143. AZStd::make_shared<SceneData::GraphData::BlendShapeData>();
  144. if (animMeshToSceneMeshes.second.m_animMeshAndSceneMeshIndex.size() == 0)
  145. {
  146. AZ_Error(Utilities::ErrorWindow, false, "Blend shape animations were expected but missing on node %s.",
  147. context.m_sourceNode.GetName());
  148. return Events::ProcessingResult::Failure;
  149. }
  150. // Some DCC tools, like Maya, include a full path separated by '.' in the node names.
  151. // For example, "cone_skin_blendShapeNode.cone_squash"
  152. // Downstream processing doesn't want anything but the last part of that node name,
  153. // so find the last '.' and remove anything before it.
  154. AZStd::string nodeName(animMeshToSceneMeshes.second.m_animMeshAndSceneMeshIndex[0].m_aiAnimMesh->mName.C_Str());
  155. size_t dotIndex = nodeName.rfind('.');
  156. if (dotIndex != AZStd::string::npos)
  157. {
  158. nodeName.erase(0, dotIndex + 1);
  159. }
  160. int vertexOffset = 0;
  161. RenamedNodesMap::SanitizeNodeName(nodeName, context.m_scene.GetGraph(), context.m_currentGraphPosition, "BlendShape");
  162. for (const auto& animMeshAndSceneIndex : animMeshToSceneMeshes.second.m_animMeshAndSceneMeshIndex)
  163. {
  164. const aiAnimMesh* aiAnimMesh = animMeshAndSceneIndex.m_aiAnimMesh;
  165. const aiMesh* aiMesh = animMeshAndSceneIndex.m_aiMesh;
  166. AZStd::bitset<SceneData::GraphData::BlendShapeData::MaxNumUVSets> uvSetUsedFlags;
  167. for (AZ::u8 uvSetIndex = 0; uvSetIndex < SceneData::GraphData::BlendShapeData::MaxNumUVSets; ++uvSetIndex)
  168. {
  169. uvSetUsedFlags.set(uvSetIndex, aiAnimMesh->HasTextureCoords(uvSetIndex));
  170. }
  171. AZStd::bitset<SceneData::GraphData::BlendShapeData::MaxNumColorSets> colorSetUsedFlags;
  172. for (AZ::u8 colorSetIndex = 0; colorSetIndex < SceneData::GraphData::BlendShapeData::MaxNumColorSets;
  173. ++colorSetIndex)
  174. {
  175. colorSetUsedFlags.set(colorSetIndex, aiAnimMesh->HasVertexColors(colorSetIndex));
  176. }
  177. blendShapeData->ReserveData(
  178. aiAnimMesh->mNumVertices, aiAnimMesh->HasTangentsAndBitangents(), uvSetUsedFlags, colorSetUsedFlags);
  179. for (unsigned int vertIdx = 0; vertIdx < aiAnimMesh->mNumVertices; ++vertIdx)
  180. {
  181. AZ::Vector3 vertex(AssImpSDKWrapper::AssImpTypeConverter::ToVector3(aiAnimMesh->mVertices[vertIdx]));
  182. context.m_sourceSceneSystem.SwapVec3ForUpAxis(vertex);
  183. context.m_sourceSceneSystem.ConvertUnit(vertex);
  184. blendShapeData->AddPosition(vertex);
  185. blendShapeData->SetVertexIndexToControlPointIndexMap(vertIdx + vertexOffset, vertIdx + vertexOffset);
  186. // Add normals
  187. if (aiAnimMesh->HasNormals())
  188. {
  189. AZ::Vector3 normal(AssImpSDKWrapper::AssImpTypeConverter::ToVector3(aiAnimMesh->mNormals[vertIdx]));
  190. context.m_sourceSceneSystem.SwapVec3ForUpAxis(normal);
  191. normal.NormalizeSafe();
  192. blendShapeData->AddNormal(normal);
  193. }
  194. // Add tangents and bitangents
  195. if (aiAnimMesh->HasTangentsAndBitangents())
  196. {
  197. // Vector4's constructor that takes in a vector3 sets w to 1.0f automatically.
  198. const AZ::Vector4 tangent(AssImpSDKWrapper::AssImpTypeConverter::ToVector3(aiAnimMesh->mTangents[vertIdx]));
  199. const AZ::Vector3 bitangent = AssImpSDKWrapper::AssImpTypeConverter::ToVector3(aiAnimMesh->mBitangents[vertIdx]);
  200. blendShapeData->AddTangentAndBitangent(tangent, bitangent);
  201. }
  202. // Add UVs
  203. for (AZ::u8 uvSetIdx = 0; uvSetIdx < SceneData::GraphData::BlendShapeData::MaxNumUVSets; ++uvSetIdx)
  204. {
  205. if (aiAnimMesh->HasTextureCoords(uvSetIdx))
  206. {
  207. const AZ::Vector2 vertexUV(
  208. aiAnimMesh->mTextureCoords[uvSetIdx][vertIdx].x,
  209. // The engine's V coordinate is reverse of how it's stored in assImp.
  210. 1.0f - aiAnimMesh->mTextureCoords[uvSetIdx][vertIdx].y);
  211. blendShapeData->AddUV(vertexUV, uvSetIdx);
  212. }
  213. }
  214. // Add colors
  215. for (AZ::u8 colorSetIdx = 0; colorSetIdx < SceneData::GraphData::BlendShapeData::MaxNumColorSets; ++colorSetIdx)
  216. {
  217. if (aiAnimMesh->HasVertexColors(colorSetIdx))
  218. {
  219. SceneAPI::DataTypes::Color color =
  220. AssImpSDKWrapper::AssImpTypeConverter::ToColor(aiAnimMesh->mColors[colorSetIdx][vertIdx]);
  221. blendShapeData->AddColor(color, colorSetIdx);
  222. }
  223. }
  224. }
  225. // aiAnimMesh just has a list of positions for vertices. The face indices are on the original mesh.
  226. for (unsigned int faceIdx = 0; faceIdx < aiMesh->mNumFaces; ++faceIdx)
  227. {
  228. aiFace face = aiMesh->mFaces[faceIdx];
  229. DataTypes::IBlendShapeData::Face blendFace;
  230. if (face.mNumIndices != 3)
  231. {
  232. // AssImp should have triangulated everything, so if this happens then someone has
  233. // probably changed AssImp's import settings. The engine only supports triangles.
  234. AZ_Error(
  235. Utilities::ErrorWindow, false,
  236. "Mesh for node %s has a face with %d vertices, only 3 vertices are supported per face.",
  237. nodeName.c_str(),
  238. face.mNumIndices);
  239. continue;
  240. }
  241. for (unsigned int idx = 0; idx < face.mNumIndices; ++idx)
  242. {
  243. blendFace.vertexIndex[idx] = face.mIndices[idx] + vertexOffset;
  244. }
  245. blendShapeData->AddFace(blendFace);
  246. }
  247. vertexOffset += aiMesh->mNumVertices;
  248. }
  249. // Report problem if no vertex or face converted to MeshData
  250. if (blendShapeData->GetVertexCount() <= 0 || blendShapeData->GetFaceCount() <= 0)
  251. {
  252. AZ_Error(Utilities::ErrorWindow, false, "Missing geometry data in blendshape node %s.", nodeName.c_str());
  253. return Events::ProcessingResult::Failure;
  254. }
  255. Containers::SceneGraph::NodeIndex newIndex =
  256. context.m_scene.GetGraph().AddChild(context.m_currentGraphPosition, nodeName.c_str());
  257. Events::ProcessingResult blendShapeResult;
  258. AssImpSceneAttributeDataPopulatedContext dataPopulated(context, blendShapeData, newIndex, nodeName);
  259. blendShapeResult = Events::Process(dataPopulated);
  260. if (blendShapeResult != Events::ProcessingResult::Failure)
  261. {
  262. blendShapeResult = AddAttributeDataNodeWithContexts(dataPopulated);
  263. }
  264. combinedBlendShapeResult += blendShapeResult;
  265. }
  266. return combinedBlendShapeResult.GetResult();
  267. }
  268. } // namespace SceneBuilder
  269. } // namespace SceneAPI
  270. } // namespace AZ