ProceduralSkinnedMesh.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  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 <ProceduralSkinnedMesh.h>
  9. #include <AzCore/Math/MathUtils.h>
  10. #include <Atom/RPI.Reflect/Model/ModelAssetHelpers.h>
  11. const uint32_t maxInfluencesPerVertex = 4;
  12. using namespace AZ;
  13. namespace AtomSampleViewer
  14. {
  15. void ProceduralSkinnedMesh::Resize(SkinnedMeshConfig& skinnedMeshConfig)
  16. {
  17. m_verticesPerSegment = skinnedMeshConfig.m_verticesPerSegment;
  18. m_segmentCount = static_cast<uint32_t>(skinnedMeshConfig.m_segmentCount);
  19. m_vertexCount = m_segmentCount * m_verticesPerSegment;
  20. m_alignedVertCountForRGBStream = aznumeric_cast<uint32_t>(RPI::ModelAssetHelpers::GetAlignedCount<float>(m_vertexCount, RHI::Format::R32G32B32_FLOAT, RPI::SkinnedMeshBufferAlignment));
  21. m_alignedVertCountForRGBAStream = aznumeric_cast<uint32_t>(RPI::ModelAssetHelpers::GetAlignedCount<float>(m_vertexCount, RHI::Format::R32G32B32A32_FLOAT, RPI::SkinnedMeshBufferAlignment));
  22. m_boneCount = AZ::GetMax(1u, static_cast<uint32_t>(skinnedMeshConfig.m_boneCount));
  23. m_influencesPerVertex = AZ::GetMax(0u, AZ::GetMin(static_cast<uint32_t>(skinnedMeshConfig.m_influencesPerVertex), AZ::GetMin(m_boneCount, maxInfluencesPerVertex)));
  24. m_subMeshCount = skinnedMeshConfig.m_subMeshCount;
  25. // For now, use a conservative AABB. A better AABB will be added with ATOM-3624
  26. m_aabb.AddPoint(AZ::Vector3(-m_height - m_radius, -m_height - m_radius, -m_height - m_radius));
  27. m_aabb.AddPoint(AZ::Vector3(m_height + m_radius, m_height + m_radius, m_height + m_radius));
  28. CalculateBones();
  29. CalculateSegments();
  30. CalculateVertexBuffers();
  31. }
  32. void ProceduralSkinnedMesh::UpdateAnimation(float time, bool useOutOfSyncBoneAnimation)
  33. {
  34. // Use the remainder of time/1000 to avoid floating point issues below that occur because time can be a large number
  35. time = fmodf(time, 1000.0f);
  36. for (uint32_t boneIndex = 0; boneIndex < m_boneCount; ++boneIndex)
  37. {
  38. // The lower bones move a little in advance of the upper bones,
  39. // otherwise, the entire cylinder would rotate around the origin at once
  40. float boneTime = 0.0f;
  41. if (useOutOfSyncBoneAnimation)
  42. {
  43. // Speed up the lower bones' animation relative to the upper bones
  44. // This leads to more drastic contorting of the bones, which better illustrates the impact of additional bone influences
  45. float boneOffset = 1.0f + static_cast<float>(m_boneCount - 1 - boneIndex) / static_cast<float>(m_boneCount - 1);
  46. boneTime = time * boneOffset;
  47. }
  48. else
  49. {
  50. // Offset the lower bones' animation by a fixed but constant amount
  51. float boneOffset = static_cast<float>(m_boneCount - 1 - boneIndex) / static_cast<float>(m_boneCount - 1);
  52. boneTime = time + boneOffset;
  53. }
  54. // Oscillate from 0 to Pi and back again,
  55. // 0 corresponds to a line lying along the positive x access, rotating counter-clockwise around the y-axis
  56. float cosTime = AZ::Cos(boneTime);
  57. float angle = (AZ::Constants::Pi + cosTime * AZ::Constants::Pi) / 2.0f;
  58. // Arc towards the ground by adjusting the height and rotation of the bone transform
  59. AZ::Matrix3x4 boneTransform = AZ::Matrix3x4::CreateIdentity();
  60. // Always arc around the y axis
  61. float xPos = cosf(angle) * m_boneHeights[boneIndex];
  62. float zPos = sinf(angle) * m_boneHeights[boneIndex];
  63. boneTransform.SetTranslation(xPos, 0.0f, zPos);
  64. // For the root bone and for the out of sync bone animation, just orient the bones away from the origin
  65. float boneRotationAngle = angle;
  66. if (boneIndex > 0 && !useOutOfSyncBoneAnimation)
  67. {
  68. // For bones besides the first one, point away from the previous
  69. AZ::Vector3 direction = boneTransform.GetTranslation() - m_boneMatrices[boneIndex - 1].GetTranslation();
  70. direction.Normalize();
  71. boneRotationAngle = atan2f(direction.GetZ(), direction.GetX());
  72. }
  73. // AZ::Quaternion::CreateRotationY assumes that an angle of 0 is pointing straight up the z-axis
  74. // and the line rotates clockwise around the y-axis, so adjust boneRotationAngle to compensate
  75. boneRotationAngle = -boneRotationAngle + AZ::Constants::HalfPi;
  76. boneTransform.SetRotationPartFromQuaternion(AZ::Quaternion::CreateRotationY(boneRotationAngle));
  77. m_boneMatrices[boneIndex] = boneTransform;
  78. }
  79. }
  80. uint32_t ProceduralSkinnedMesh::GetInfluencesPerVertex() const
  81. {
  82. return m_influencesPerVertex;
  83. }
  84. uint32_t ProceduralSkinnedMesh::GetSubMeshCount() const
  85. {
  86. return m_subMeshCount;
  87. }
  88. float ProceduralSkinnedMesh::GetSubMeshYOffset() const
  89. {
  90. constexpr float spaceBetweenSubmeshes = .01f;
  91. return m_radius * 2.0f + spaceBetweenSubmeshes;
  92. }
  93. void ProceduralSkinnedMesh::CalculateVertexBuffers()
  94. {
  95. // There are 6 indices per-side, and one fewer side than vertices per side since the first/last vertex in the segment have the same position (but different uvs)
  96. // Exclude one segment in the count because you need a second segment in order to make triangles between them
  97. m_indices.resize(6 * (m_verticesPerSegment - 1) * (m_segmentCount - 1));
  98. uint32_t currentIndex = 0;
  99. // For each segment (except for the last one), create triangles with the segment above it
  100. for (uint32_t segmentIndex = 0; segmentIndex < m_segmentCount - 1; ++segmentIndex)
  101. {
  102. for (uint32_t sideIndex = 0; sideIndex < m_verticesPerSegment - 1; ++sideIndex)
  103. {
  104. // Each side has four vertices
  105. uint32_t bottomLeft = segmentIndex * m_verticesPerSegment + sideIndex;
  106. uint32_t bottomRight = segmentIndex * m_verticesPerSegment + sideIndex + 1;
  107. uint32_t topLeft = (segmentIndex + 1) * m_verticesPerSegment + sideIndex;
  108. uint32_t topRight = (segmentIndex + 1) * m_verticesPerSegment + sideIndex + 1;
  109. // Each side has two triangles, using a right handed coordinate system
  110. m_indices[currentIndex++] = bottomLeft;
  111. m_indices[currentIndex++] = topRight;
  112. m_indices[currentIndex++] = topLeft;
  113. m_indices[currentIndex++] = bottomLeft;
  114. m_indices[currentIndex++] = bottomRight;
  115. m_indices[currentIndex++] = topRight;
  116. }
  117. }
  118. m_positions.resize(m_alignedVertCountForRGBStream);
  119. m_normals.resize(m_alignedVertCountForRGBStream);
  120. m_bitangents.resize(m_alignedVertCountForRGBStream);
  121. size_t alignedTangentVertCount = RPI::ModelAssetHelpers::GetAlignedCount<float>(m_vertexCount, RPI::TangentFormat, RPI::SkinnedMeshBufferAlignment);
  122. m_tangents.resize(alignedTangentVertCount);
  123. // We pack 16 bit joint id's into 32 bit uints.
  124. uint32_t numVertInfluences = m_vertexCount * m_influencesPerVertex;
  125. size_t alignedIndicesVertCount = RPI::ModelAssetHelpers::GetAlignedCount<uint32_t>(numVertInfluences, RPI::SkinIndicesFormat, RPI::SkinnedMeshBufferAlignment);
  126. m_blendIndices.resize(alignedIndicesVertCount);
  127. size_t alignedWeightsVertCount = RPI::ModelAssetHelpers::GetAlignedCount<float>(numVertInfluences, RPI::SkinWeightFormat, RPI::SkinnedMeshBufferAlignment);
  128. m_blendWeights.resize(alignedWeightsVertCount);
  129. m_uvs.resize(m_vertexCount);
  130. for (uint32_t vertexIndex = 0; vertexIndex < m_vertexCount; ++vertexIndex)
  131. {
  132. // Vertices circle around the origin counter-clockwise, then move up one segment and do it again.
  133. uint32_t indexWithinTheCurrentSegment = vertexIndex % m_verticesPerSegment;
  134. uint32_t segmentIndex = vertexIndex / m_verticesPerSegment;
  135. // Get the x and y positions from a unit circle
  136. float vertexAngle = (AZ::Constants::TwoPi / static_cast<float>(m_verticesPerSegment - 1)) * static_cast<float>(indexWithinTheCurrentSegment);
  137. m_positions[(vertexIndex * RPI::PositionFloatsPerVert) + 0] = cosf(vertexAngle) * m_radius;
  138. m_positions[(vertexIndex * RPI::PositionFloatsPerVert) + 1] = sinf(vertexAngle) * m_radius;
  139. m_positions[(vertexIndex * RPI::PositionFloatsPerVert) + 2] = m_segmentHeightOffsets[segmentIndex];
  140. // Normals are flat on the z-plane and point away from the origin in the direction of the vertex position
  141. m_normals[(vertexIndex * RPI::PositionFloatsPerVert) + 0] = m_positions[(vertexIndex * RPI::PositionFloatsPerVert) + 0];
  142. m_normals[(vertexIndex * RPI::PositionFloatsPerVert) + 1] = m_positions[(vertexIndex * RPI::PositionFloatsPerVert) + 1];
  143. m_normals[(vertexIndex * RPI::PositionFloatsPerVert) + 2] = 0.0f;
  144. // Bitangent is straight down
  145. m_bitangents[(vertexIndex * RPI::PositionFloatsPerVert)+0] = 0.0f;
  146. m_bitangents[(vertexIndex * RPI::PositionFloatsPerVert)+1] = 0.0f;
  147. m_bitangents[(vertexIndex * RPI::PositionFloatsPerVert)+2] = -1.0f;
  148. for (size_t i = 0; i < m_influencesPerVertex; ++i)
  149. {
  150. // m_blendIndices has two id's packed into a single uint32
  151. size_t packedIndex = vertexIndex * m_influencesPerVertex / 2 + i / 2;
  152. // m_blendWeights has an individual weight per influence
  153. size_t unpackedIndex = vertexIndex * m_influencesPerVertex + i;
  154. // Blend indices/weights are the same for each vertex in the segment,
  155. // so copy the source data from the segment. Both id's and weights are unpacked
  156. size_t sourceIndex = segmentIndex * m_influencesPerVertex + i;
  157. // Pack the segment blend indices, two per 32-bit uint
  158. if (i % 2 == 0)
  159. {
  160. // Put the first/even ids in the most significant bits
  161. m_blendIndices[packedIndex] = m_segmentBlendIndices[sourceIndex] << 16;
  162. }
  163. else
  164. {
  165. // Put the next/odd ids in the least significant bits
  166. m_blendIndices[packedIndex] |= m_segmentBlendIndices[sourceIndex];
  167. }
  168. // Copy the weights
  169. m_blendWeights[unpackedIndex] = m_segmentBlendWeights[sourceIndex];
  170. }
  171. // The uvs wrap around the cylinder exactly once
  172. m_uvs[vertexIndex][0] = static_cast<float>(indexWithinTheCurrentSegment) / static_cast<float>(m_verticesPerSegment - 1);
  173. // The uvs stretch from bottom to top
  174. m_uvs[vertexIndex][1] = m_segmentHeights[segmentIndex] / m_height;
  175. }
  176. // Do a separate pass on the tangents, since the positions need to be known first
  177. for (uint32_t vertexIndex = 0; vertexIndex < m_vertexCount; ++vertexIndex)
  178. {
  179. // Tangent for each side points horizontally from the left vertex to the right vertex of each side
  180. uint32_t leftVertex = vertexIndex;
  181. // The last vertex of the segment will have the first vertex of the segment as its neighbor, not just the next vertex (which would be in the next segment)
  182. uint32_t rightVertex = (leftVertex + 1) % m_verticesPerSegment;
  183. m_tangents[(vertexIndex * RPI::TangentFloatsPerVert)+0] = m_positions[(leftVertex * RPI::PositionFloatsPerVert) + 0] - m_positions[(rightVertex * RPI::PositionFloatsPerVert)+0];
  184. m_tangents[(vertexIndex * RPI::TangentFloatsPerVert)+1] = m_positions[(leftVertex * RPI::PositionFloatsPerVert) + 1] - m_positions[(rightVertex * RPI::PositionFloatsPerVert)+1];
  185. m_tangents[(vertexIndex * RPI::TangentFloatsPerVert)+2] = 0.0f;
  186. m_tangents[(vertexIndex * RPI::TangentFloatsPerVert)+3] = 1.0f;
  187. }
  188. }
  189. void ProceduralSkinnedMesh::CalculateBones()
  190. {
  191. m_boneHeights.resize(m_boneCount);
  192. m_boneMatrices.resize(m_boneCount);
  193. for (uint32_t boneIndex = 0; boneIndex < m_boneCount; ++boneIndex)
  194. {
  195. // Evenly distribute the bones up the center of the cylinder, but not all the way to the top
  196. m_boneHeights[boneIndex] = static_cast<float>(boneIndex) / static_cast<float>(m_boneCount);
  197. m_boneMatrices[boneIndex] = AZ::Matrix3x4::CreateTranslation(AZ::Vector3(0.0f, 0.0f, m_boneHeights[boneIndex]));
  198. }
  199. }
  200. void ProceduralSkinnedMesh::CalculateSegments()
  201. {
  202. m_segmentHeights.resize(m_segmentCount);
  203. m_segmentHeightOffsets.resize(m_segmentCount);
  204. // All vertices in a given segment will share the same skin influences
  205. m_segmentBlendIndices.resize(m_segmentCount * m_influencesPerVertex);
  206. m_segmentBlendWeights.resize(m_segmentCount * m_influencesPerVertex);
  207. for (uint32_t segmentIndex = 0; segmentIndex < m_segmentCount; ++segmentIndex)
  208. {
  209. float currentSegmentHeight = m_height * (static_cast<float>(segmentIndex) / static_cast<float>(m_segmentCount - 1));
  210. m_segmentHeights[segmentIndex] = currentSegmentHeight;
  211. // Find the closest bone that is still below or equal to the current segment in height
  212. int boneIndexBelow = 0;
  213. for (; boneIndexBelow < static_cast<int>(m_boneCount - 1); ++boneIndexBelow)
  214. {
  215. // If the next bone is higher than the current height, we've found the below/above indices we're looking for
  216. if (m_boneHeights[boneIndexBelow + 1] > currentSegmentHeight)
  217. {
  218. break;
  219. }
  220. }
  221. // If boneIndexAbove >= m_boneCount, we'll know we've reached the top
  222. int boneIndexAbove = boneIndexBelow + 1;
  223. AZStd::array<uint32_t, 4> currentBlendIndices = { 0, 0, 0, 0 };
  224. AZStd::array<float, 4> currentBlendDistances = { 0.0f, 0.0f, 0.0f, 0.0f };
  225. AZStd::array<float, 4> currentBlendWeights = { 0.0f, 0.0f, 0.0f, 0.0f };
  226. float totalDistanceToAllBoneInfluences = 0.0f;
  227. // As long as we're still surrounded by two valid bones, we're going to alternate taking the bone that's closest/farthest from the segment.
  228. // If there are no more in one direction, we'll just keep taking them from the other direction
  229. enum class BoneSelectionMethod { TakeClosest, TakeFarthest, TakeOnly };
  230. // If there is only one valid direction to start with, use TakeOnly. Otherwise, use TakeClosest
  231. BoneSelectionMethod currentBoneSelectionMethod = boneIndexBelow < 0 || boneIndexAbove == static_cast<int>(m_boneCount) ? BoneSelectionMethod::TakeOnly : BoneSelectionMethod::TakeClosest;
  232. // Assume that we won't get into a state where there are no bones above and no bones below before we've finished
  233. AZ_Assert(m_influencesPerVertex <= m_boneCount && m_influencesPerVertex <= maxInfluencesPerVertex, "SkinnedMeshExampleComponent - m_influencesPerVertex was incorrectly clamped.");
  234. for (uint32_t i = 0; i < m_influencesPerVertex; ++i)
  235. {
  236. float distanceToBoneBelow = boneIndexBelow >= 0 ? AZ::Abs(currentSegmentHeight - m_boneHeights[boneIndexBelow]) : AZ::Constants::FloatMax;
  237. float distanceToBoneAbove = boneIndexAbove < static_cast<int>(m_boneCount) ? AZ::Abs(currentSegmentHeight - m_boneHeights[boneIndexAbove]) : AZ::Constants::FloatMax;
  238. switch (currentBoneSelectionMethod)
  239. {
  240. case BoneSelectionMethod::TakeClosest:
  241. // Get the closest out of the two surrounding bones
  242. currentBlendIndices[i] = distanceToBoneBelow < distanceToBoneAbove ? boneIndexBelow : boneIndexAbove;
  243. currentBoneSelectionMethod = BoneSelectionMethod::TakeFarthest;
  244. break;
  245. case BoneSelectionMethod::TakeFarthest:
  246. // Get the furthest out of the two surrounding bones
  247. currentBlendIndices[i] = distanceToBoneBelow < distanceToBoneAbove ? boneIndexAbove : boneIndexBelow;
  248. // Move on to the next two surrounding bones
  249. boneIndexBelow--;
  250. boneIndexAbove++;
  251. currentBoneSelectionMethod = BoneSelectionMethod::TakeClosest;
  252. break;
  253. case BoneSelectionMethod::TakeOnly:
  254. if (boneIndexBelow >= 0)
  255. {
  256. currentBlendIndices[i] = boneIndexBelow;
  257. boneIndexBelow--;
  258. }
  259. else
  260. {
  261. currentBlendIndices[i] = boneIndexAbove;
  262. boneIndexAbove++;
  263. }
  264. break;
  265. default:
  266. AZ_Assert(false, "Invalid BoneSelectionMethod")
  267. break;
  268. }
  269. // If we run out of valid bones in one direction or the other, start taking the only valid boneIndex
  270. if (boneIndexBelow < 0 || boneIndexAbove >= static_cast<int>(m_boneCount))
  271. {
  272. currentBoneSelectionMethod = BoneSelectionMethod::TakeOnly;
  273. }
  274. // Get the distance from the center of the segment to the bone.
  275. currentBlendDistances[i] = AZ::Abs(currentSegmentHeight - m_boneHeights[currentBlendIndices[i]]);
  276. totalDistanceToAllBoneInfluences += currentBlendDistances[i];
  277. }
  278. float heightOffset = 0.0f;
  279. // Now that we know what bones to use, pick some bone weights based on the distance
  280. if (m_influencesPerVertex == 1 || segmentIndex == m_segmentCount - 1)
  281. {
  282. // Hard-code the 1 influence case to avoid a possible divide-by-zero below
  283. // Also, the last segment should just follow the last bone
  284. currentBlendWeights[0] = 1.0f;
  285. heightOffset = m_boneHeights[currentBlendIndices[0]];
  286. }
  287. else
  288. {
  289. for (uint32_t i = 0; i < m_influencesPerVertex; ++i)
  290. {
  291. // Use the absolute value the distance to each bone to determine the weights
  292. currentBlendWeights[i] = ((totalDistanceToAllBoneInfluences - currentBlendDistances[i]) / totalDistanceToAllBoneInfluences) / (static_cast<float>(m_influencesPerVertex - 1));
  293. // Use the weighted heights of the bones to determine how much we'll need to offset each vertex from the bones
  294. // so that the height relative to the bones is equal to the target segment height
  295. heightOffset += m_boneHeights[currentBlendIndices[i]] * currentBlendWeights[i];
  296. }
  297. }
  298. // Now copy the resulting influences into the larger buffer
  299. for (size_t i = 0; i < m_influencesPerVertex; ++i)
  300. {
  301. size_t destinationIndex = segmentIndex * m_influencesPerVertex + i;
  302. m_segmentBlendIndices[destinationIndex] = currentBlendIndices[i];
  303. m_segmentBlendWeights[destinationIndex] = currentBlendWeights[i];
  304. }
  305. m_segmentHeightOffsets[segmentIndex] = currentSegmentHeight - heightOffset;
  306. }
  307. }
  308. uint32_t ProceduralSkinnedMesh::GetVertexCount() const
  309. {
  310. return m_vertexCount;
  311. }
  312. uint32_t ProceduralSkinnedMesh::GetAlignedVertCountForRGBStream() const
  313. {
  314. return m_alignedVertCountForRGBStream;
  315. }
  316. uint32_t ProceduralSkinnedMesh::GetAlignedVertCountForRGBAStream() const
  317. {
  318. return m_alignedVertCountForRGBAStream;
  319. }
  320. }