MorphTargetExporter.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  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 <Model/MorphTargetExporter.h>
  9. #include <Atom/RPI.Reflect/Model/MorphTargetDelta.h>
  10. #include <SceneAPI/SceneCore/Containers/Utilities/Filters.h>
  11. #include <SceneAPI/SceneCore/Containers/Utilities/SceneGraphUtilities.h>
  12. #include <SceneAPI/SceneCore/Utilities/SceneGraphSelector.h>
  13. #include <SceneAPI/SceneCore/Containers/Views/FilterIterator.h>
  14. #include <SceneAPI/SceneCore/Containers/Views/PairIterator.h>
  15. #include <SceneAPI/SceneCore/Containers/Views/SceneGraphDownwardsIterator.h>
  16. #include <SceneAPI/SceneCore/Containers/Views/SceneGraphChildIterator.h>
  17. #include <AzToolsFramework/API/EditorAssetSystemAPI.h>
  18. #include <AzCore/Asset/AssetManagerBus.h>
  19. namespace AZ::RPI
  20. {
  21. using namespace AZ::SceneAPI;
  22. AZStd::unordered_map<AZStd::string, MorphTargetExporter::SourceBlendShapeInfo> MorphTargetExporter::GetBlendShapeInfos(
  23. const Containers::Scene& scene,
  24. const MeshData* meshData) const
  25. {
  26. const Containers::SceneGraph& sceneGraph = scene.GetGraph();
  27. const auto foundBaseMeshIter = AZStd::find_if(sceneGraph.GetContentStorage().cbegin(), sceneGraph.GetContentStorage().cend(), [meshData](const auto& nodeData)
  28. {
  29. return nodeData.get() == meshData;
  30. });
  31. if (foundBaseMeshIter == sceneGraph.GetContentStorage().cend())
  32. {
  33. return {};
  34. }
  35. const auto baseMeshNodeIndex = sceneGraph.ConvertToNodeIndex(foundBaseMeshIter);
  36. const auto childBlendShapeDatas = Containers::MakeDerivedFilterView<DataTypes::IBlendShapeData>(
  37. Containers::Views::MakeSceneGraphChildView(sceneGraph, baseMeshNodeIndex, sceneGraph.GetContentStorage().cbegin(), true)
  38. );
  39. AZStd::unordered_map<AZStd::string, SourceBlendShapeInfo> result;
  40. for (auto it = childBlendShapeDatas.cbegin(); it != childBlendShapeDatas.cend(); ++it)
  41. {
  42. const Containers::SceneGraph::NodeIndex blendShapeNodeIndex = sceneGraph.ConvertToNodeIndex(it.GetBaseIterator().GetBaseIterator().GetHierarchyIterator());
  43. Events::GraphMetaInfo::VirtualTypesSet types;
  44. Events::GraphMetaInfoBus::Broadcast(&Events::GraphMetaInfo::GetVirtualTypes, types, scene, blendShapeNodeIndex);
  45. if (!types.contains(Events::GraphMetaInfo::GetIgnoreVirtualType()))
  46. {
  47. const AZStd::string blendShapeName{sceneGraph.GetNodeName(blendShapeNodeIndex).GetName(), sceneGraph.GetNodeName(blendShapeNodeIndex).GetNameLength()};
  48. result[blendShapeName].m_sceneNodeIndices.emplace_back(blendShapeNodeIndex);
  49. }
  50. }
  51. return result;
  52. }
  53. void MorphTargetExporter::ProduceMorphTargets(
  54. uint32_t productMeshIndex,
  55. uint32_t startVertex,
  56. const AZStd::map<uint32_t, uint32_t>& oldToNewIndicesMap,
  57. const Containers::Scene& scene,
  58. const ModelAssetBuilderComponent::SourceMeshContent& sourceMesh,
  59. ModelAssetBuilderComponent::ProductMeshContent& productMesh,
  60. MorphTargetMetaAssetCreator& metaAssetCreator,
  61. const AZ::SceneAPI::CoordinateSystemConverter& coordSysConverter)
  62. {
  63. const Containers::SceneGraph& sceneGraph = scene.GetGraph();
  64. const auto baseMeshIt = AZStd::find(sceneGraph.GetContentStorage().cbegin(), sceneGraph.GetContentStorage().cend(), sourceMesh.m_meshData);
  65. const Containers::SceneGraph::NodeIndex baseMeshIndex = sceneGraph.ConvertToNodeIndex(baseMeshIt);
  66. const AZStd::string_view baseMeshName{sceneGraph.GetNodeName(baseMeshIndex).GetName(), sceneGraph.GetNodeName(baseMeshIndex).GetNameLength()};
  67. // Get the blend shapes for the given mesh
  68. AZStd::unordered_map<AZStd::string, SourceBlendShapeInfo> blendShapeInfos = GetBlendShapeInfos(scene, sourceMesh.m_meshData.get());
  69. for (const auto& iter : blendShapeInfos)
  70. {
  71. const AZStd::string& blendShapeName = iter.first;
  72. for (const SceneAPI::Containers::SceneGraph::NodeIndex& sceneNodeIndex : iter.second.m_sceneNodeIndices)
  73. {
  74. const AZStd::shared_ptr<const DataTypes::IBlendShapeData>& blendShapeData =
  75. azrtti_cast<const DataTypes::IBlendShapeData*>(sceneGraph.GetNodeContent(sceneNodeIndex));
  76. AZ_Assert(blendShapeData, "Node is expected to be a blend shape.");
  77. if (blendShapeData)
  78. {
  79. const Containers::SceneGraph::NodeIndex morphMeshParentIndex = sceneGraph.GetNodeParent(sceneNodeIndex);
  80. const AZStd::string_view sourceMeshName{sceneGraph.GetNodeName(morphMeshParentIndex).GetName(), sceneGraph.GetNodeName(morphMeshParentIndex).GetNameLength()};
  81. AZ_Assert(AZ::StringFunc::Equal(baseMeshName, sourceMeshName, /*bCaseSensitive=*/true),
  82. "Scene graph mesh node (%.*s) has a different name than the product mesh (%.*s).",
  83. AZ_STRING_ARG(sourceMeshName), AZ_STRING_ARG(baseMeshName));
  84. const DataTypes::MatrixType globalTransform = Utilities::BuildWorldTransform(sceneGraph, sceneNodeIndex);
  85. BuildMorphTargetMesh(
  86. productMeshIndex, startVertex, oldToNewIndicesMap, sourceMesh, productMesh, metaAssetCreator, blendShapeName, blendShapeData, globalTransform,
  87. coordSysConverter, scene.GetSourceFilename());
  88. }
  89. }
  90. }
  91. }
  92. float MorphTargetExporter::CalcPositionDeltaTolerance(const ModelAssetBuilderComponent::SourceMeshContent& mesh) const
  93. {
  94. AZ::Aabb meshAabb = AZ::Aabb::CreateNull();
  95. const unsigned int numVertices = static_cast<unsigned int>(mesh.m_meshData->GetVertexCount());
  96. for (unsigned int i = 0; i < numVertices; ++i)
  97. {
  98. meshAabb.AddPoint(mesh.m_meshData->GetPosition(i));
  99. }
  100. const float radius = (meshAabb.GetMax() - meshAabb.GetMin()).GetLength() * 0.5f;
  101. float tolerance = radius * s_positionDeltaTolerance; // TODO: Value needs further consideration but is proven to work for EMotion FX.
  102. if (tolerance < AZ::Constants::FloatEpsilon)
  103. {
  104. tolerance = AZ::Constants::FloatEpsilon;
  105. }
  106. return tolerance;
  107. }
  108. template <class StorageType>
  109. StorageType MorphTargetExporter::Compress(float value, float minValue, float maxValue)
  110. {
  111. // Number of steps within the specified range
  112. constexpr uint32_t CONVERT_VALUE = (1 << (sizeof(StorageType) << 3)) - 1;
  113. const float f = static_cast<float>(CONVERT_VALUE) / (maxValue - minValue);
  114. return static_cast<StorageType>((value - minValue) * f);
  115. };
  116. void MorphTargetExporter::BuildMorphTargetMesh(
  117. uint32_t productMeshIndex,
  118. uint32_t startVertex,
  119. const AZStd::map<uint32_t, uint32_t>& oldToNewIndicesMap,
  120. const ModelAssetBuilderComponent::SourceMeshContent& sourceMesh,
  121. ModelAssetBuilderComponent::ProductMeshContent& productMesh,
  122. MorphTargetMetaAssetCreator& metaAssetCreator,
  123. const AZStd::string& blendShapeName,
  124. const AZStd::shared_ptr<const DataTypes::IBlendShapeData>& blendShapeData,
  125. const DataTypes::MatrixType& globalTransform,
  126. const AZ::SceneAPI::CoordinateSystemConverter& coordSysConverter,
  127. const AZStd::string& sourceSceneFilename)
  128. {
  129. const float tolerance = CalcPositionDeltaTolerance(sourceMesh);
  130. AZ::Aabb deltaPositionAabb = AZ::Aabb::CreateNull();
  131. AZStd::vector<PackedCompressedMorphTargetDelta>& packedCompressedMorphTargetVertexData = productMesh.m_morphTargetVertexData;
  132. MorphTargetMetaAsset::MorphTarget metaData;
  133. metaData.m_meshNodeName = sourceMesh.m_name.GetStringView();
  134. metaData.m_morphTargetName = blendShapeName;
  135. metaData.m_meshIndex = productMeshIndex;
  136. // The start index is after any previously added deltas from other morph targets
  137. // that are part of the same product mesh
  138. metaData.m_startIndex = aznumeric_cast<uint32_t>(packedCompressedMorphTargetVertexData.size());
  139. // Determine the vertex index range for the morph target.
  140. // Ignore any vertices that are associated with a later product mesh
  141. uint32_t numVertices = aznumeric_cast<uint32_t>(productMesh.m_positions.size() / 3);
  142. uint32_t endVertex = startVertex + numVertices;
  143. if (blendShapeData->GetVertexCount() != sourceMesh.m_meshData->GetVertexCount()
  144. || blendShapeData->GetVertexCount() < endVertex)
  145. {
  146. AZ_Error(ModelAssetBuilderComponent::s_builderName, false,
  147. "Skipping blend shape (%s) as it contains more/less vertices (%d) than the neutral mesh (%d). "
  148. "The blend shape is most likely influencing multiple meshes, which is currently not supported.",
  149. blendShapeName.c_str(), numVertices, sourceMesh.m_meshData->GetVertexCount());
  150. return;
  151. }
  152. // Multiply normal by inverse transpose to avoid incorrect values produced by non-uniformly scaled transforms.
  153. DataTypes::MatrixType globalTransformN = globalTransform.GetInverseFull().GetTranspose();
  154. globalTransformN.SetTranslation(AZ::Vector3::CreateZero());
  155. AZStd::vector<AZ::Vector3> uncompressedPositionDeltas;
  156. uncompressedPositionDeltas.reserve(numVertices);
  157. AZStd::vector<CompressedMorphTargetDelta> compressedDeltas;
  158. compressedDeltas.reserve(numVertices);
  159. uint32_t numMorphedVertices = 0;
  160. for (uint32_t vertexIndex = startVertex; vertexIndex < endVertex; ++vertexIndex)
  161. {
  162. AZ::Vector3 targetPosition = blendShapeData->GetPosition(vertexIndex);
  163. targetPosition = globalTransform * targetPosition;
  164. targetPosition = coordSysConverter.ConvertVector3(targetPosition);
  165. AZ::Vector3 neutralPosition = sourceMesh.m_meshData->GetPosition(vertexIndex);
  166. neutralPosition = globalTransform * neutralPosition;
  167. neutralPosition = coordSysConverter.ConvertVector3(neutralPosition);
  168. // Check if the vertex positions are different.
  169. if (!targetPosition.IsClose(neutralPosition, tolerance))
  170. {
  171. // Add a new delta
  172. numMorphedVertices++;
  173. compressedDeltas.emplace_back(RPI::CompressedMorphTargetDelta{});
  174. RPI::CompressedMorphTargetDelta& currentDelta = compressedDeltas.back();
  175. // Set the morphed index
  176. const auto& iter = oldToNewIndicesMap.find(vertexIndex);
  177. if (iter != oldToNewIndicesMap.end())
  178. {
  179. currentDelta.m_morphedVertexIndex = iter->second;
  180. }
  181. else
  182. {
  183. AZ_Assert(false, "%s: Attempting to add a morph target that influences a vertex that is not part of the current mesh.", ModelAssetBuilderComponent::s_builderName);
  184. }
  185. const AZ::Vector3 deltaPosition = targetPosition - neutralPosition;
  186. deltaPositionAabb.AddPoint(deltaPosition);
  187. // We can't compress the positions until they've all been gathered so we know the min/max,
  188. // so keep track of the uncompressed positions for now
  189. uncompressedPositionDeltas.emplace_back(deltaPosition);
  190. // Normal
  191. {
  192. AZ::Vector3 neutralNormal = sourceMesh.m_meshData->GetNormal(vertexIndex);
  193. neutralNormal = globalTransformN * neutralNormal;
  194. neutralNormal = coordSysConverter.ConvertVector3(neutralNormal);
  195. AZ::Vector3 targetNormal = blendShapeData->GetNormal(vertexIndex);
  196. targetNormal = globalTransformN * targetNormal;
  197. targetNormal = coordSysConverter.ConvertVector3(targetNormal);
  198. targetNormal.NormalizeSafe();
  199. const AZ::Vector3 deltaNormal = targetNormal - neutralNormal;
  200. currentDelta.m_normalX = Compress<uint8_t>(deltaNormal.GetX(), MorphTargetDeltaConstants::s_tangentSpaceDeltaMin, MorphTargetDeltaConstants::s_tangentSpaceDeltaMax);
  201. currentDelta.m_normalY = Compress<uint8_t>(deltaNormal.GetY(), MorphTargetDeltaConstants::s_tangentSpaceDeltaMin, MorphTargetDeltaConstants::s_tangentSpaceDeltaMax);
  202. currentDelta.m_normalZ = Compress<uint8_t>(deltaNormal.GetZ(), MorphTargetDeltaConstants::s_tangentSpaceDeltaMin, MorphTargetDeltaConstants::s_tangentSpaceDeltaMax);
  203. }
  204. // Tangent
  205. {
  206. // Insert zero-delta until morphed tangents are supported in SceneAPI
  207. currentDelta.m_tangentX = Compress<uint8_t>(0.0f, MorphTargetDeltaConstants::s_tangentSpaceDeltaMin, MorphTargetDeltaConstants::s_tangentSpaceDeltaMax);
  208. currentDelta.m_tangentY = Compress<uint8_t>(0.0f, MorphTargetDeltaConstants::s_tangentSpaceDeltaMin, MorphTargetDeltaConstants::s_tangentSpaceDeltaMax);
  209. currentDelta.m_tangentZ = Compress<uint8_t>(0.0f, MorphTargetDeltaConstants::s_tangentSpaceDeltaMin, MorphTargetDeltaConstants::s_tangentSpaceDeltaMax);
  210. }
  211. // Bitangent
  212. {
  213. // Insert zero-delta until morphed bitangents are supported in SceneAPI
  214. currentDelta.m_bitangentX = Compress<uint8_t>(0.0f, MorphTargetDeltaConstants::s_tangentSpaceDeltaMin, MorphTargetDeltaConstants::s_tangentSpaceDeltaMax);
  215. currentDelta.m_bitangentY = Compress<uint8_t>(0.0f, MorphTargetDeltaConstants::s_tangentSpaceDeltaMin, MorphTargetDeltaConstants::s_tangentSpaceDeltaMax);
  216. currentDelta.m_bitangentZ = Compress<uint8_t>(0.0f, MorphTargetDeltaConstants::s_tangentSpaceDeltaMin, MorphTargetDeltaConstants::s_tangentSpaceDeltaMax);
  217. }
  218. }
  219. }
  220. metaData.m_numVertices = numMorphedVertices;
  221. const float morphedVerticesRatio = numMorphedVertices / static_cast<float>(numVertices);
  222. if (numMorphedVertices > 0)
  223. {
  224. AZ_Printf(
  225. ModelAssetBuilderComponent::s_builderName,
  226. "Blend shape named '%s' morphs %.1f%% (%" PRIu32 " / % " PRIu32 ") of the vertices of mesh %s ",
  227. blendShapeName.c_str(),
  228. morphedVerticesRatio * 100.0f,
  229. numMorphedVertices,
  230. numVertices,
  231. productMesh.m_name.GetCStr());
  232. // Calculate the minimum and maximum position compression values.
  233. {
  234. const AZ::Vector3& boxMin = deltaPositionAabb.GetMin();
  235. const AZ::Vector3& boxMax = deltaPositionAabb.GetMax();
  236. auto [minValue, maxValue] =
  237. AZStd::minmax({ boxMin.GetX(), boxMin.GetY(), boxMin.GetZ(), boxMax.GetX(), boxMax.GetY(), boxMax.GetZ() });
  238. // Make sure the diff between min and max isn't too small.
  239. if (maxValue - minValue < 1.0f)
  240. {
  241. minValue -= 0.5f; // TODO: Value needs further consideration but is proven to work for EMotion FX.
  242. maxValue += 0.5f;
  243. }
  244. metaData.m_minPositionDelta = minValue;
  245. metaData.m_maxPositionDelta = maxValue;
  246. }
  247. metaData.m_wrinkleMask = GetWrinkleMask(sourceSceneFilename, blendShapeName);
  248. metaAssetCreator.AddMorphTarget(metaData);
  249. AZ_Assert(uncompressedPositionDeltas.size() == compressedDeltas.size(), "Number of uncompressed (%d) and compressed position delta components (%d) do not match.",
  250. uncompressedPositionDeltas.size(), compressedDeltas.size());
  251. // Compress the position deltas. (Only the newly added ones from this morph target)
  252. for (size_t i = 0; i < uncompressedPositionDeltas.size(); i++)
  253. {
  254. compressedDeltas[i].m_positionX = Compress<uint16_t>(uncompressedPositionDeltas[i].GetX(), metaData.m_minPositionDelta, metaData.m_maxPositionDelta);
  255. compressedDeltas[i].m_positionY = Compress<uint16_t>(uncompressedPositionDeltas[i].GetY(), metaData.m_minPositionDelta, metaData.m_maxPositionDelta);
  256. compressedDeltas[i].m_positionZ = Compress<uint16_t>(uncompressedPositionDeltas[i].GetZ(), metaData.m_minPositionDelta, metaData.m_maxPositionDelta);
  257. }
  258. // Now that we have all our compressed deltas, they need to be packed
  259. // the way the shader expects to read them and added to the product mesh
  260. packedCompressedMorphTargetVertexData.reserve(packedCompressedMorphTargetVertexData.size() + compressedDeltas.size());
  261. for (size_t i = 0; i < compressedDeltas.size(); ++i)
  262. {
  263. packedCompressedMorphTargetVertexData.emplace_back(RPI::PackMorphTargetDelta(compressedDeltas[i]));
  264. }
  265. AZ_Assert((packedCompressedMorphTargetVertexData.size() - metaData.m_startIndex) == numMorphedVertices, "Vertex index range (%d) in morph target meta data does not match number of morphed vertices (%d).",
  266. packedCompressedMorphTargetVertexData.size() - metaData.m_startIndex, numMorphedVertices);
  267. }
  268. else
  269. {
  270. AZ_Info(
  271. ModelAssetBuilderComponent::s_builderName,
  272. "Blend shape named '%s' does not affect mesh %s",
  273. blendShapeName.c_str(),
  274. productMesh.m_name.GetCStr());
  275. }
  276. }
  277. Data::Asset<RPI::StreamingImageAsset> MorphTargetExporter::GetWrinkleMask(const AZStd::string& sourceSceneFullFilePath, const AZStd::string& blendShapeName) const
  278. {
  279. AZ::Data::Asset<AZ::RPI::StreamingImageAsset> imageAsset;
  280. // See if there is a wrinkle map mask for this mesh
  281. AZStd::string sceneRelativeFilePath;
  282. bool relativePathFound = true;
  283. AzToolsFramework::AssetSystemRequestBus::BroadcastResult(relativePathFound, &AzToolsFramework::AssetSystemRequestBus::Events::GetRelativeProductPathFromFullSourceOrProductPath, sourceSceneFullFilePath, sceneRelativeFilePath);
  284. if (relativePathFound)
  285. {
  286. AZ::StringFunc::Path::StripFullName(sceneRelativeFilePath);
  287. // Get the folder the masks are supposed to be in
  288. AZStd::string folderName;
  289. AZ::StringFunc::Path::GetFileName(sourceSceneFullFilePath.c_str(), folderName);
  290. folderName += "_wrinklemasks";
  291. // Note: for now, we're assuming the mask is always authored as a .tif
  292. AZStd::string blendMaskFileName = blendShapeName + "_wrinklemask.tif.streamingimage";
  293. AZStd::string maskFolderAndFile;
  294. AZ::StringFunc::Path::Join(folderName.c_str(), blendMaskFileName.c_str(), maskFolderAndFile);
  295. AZStd::string maskRelativePath;
  296. AZ::StringFunc::Path::Join(sceneRelativeFilePath.c_str(), maskFolderAndFile.c_str(), maskRelativePath);
  297. AZ::StringFunc::Path::Normalize(maskRelativePath);
  298. // Now see if the file exists
  299. AZ::Data::AssetId maskAssetId;
  300. Data::AssetCatalogRequestBus::BroadcastResult(maskAssetId, &Data::AssetCatalogRequests::GetAssetIdByPath, maskRelativePath.c_str(), AZ::Data::s_invalidAssetType, false);
  301. if (maskAssetId.IsValid())
  302. {
  303. // Flush asset manager events to ensure no asset references are held by closures queued on Ebuses.
  304. AZ::Data::AssetManager::Instance().DispatchEvents();
  305. imageAsset.Create(maskAssetId, AZ::Data::AssetLoadBehavior::PreLoad, false);
  306. }
  307. }
  308. return imageAsset;
  309. }
  310. } // namespace AZ::RPI