FabricCooker.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  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/Debug/Profiler.h>
  9. #include <AzCore/std/smart_ptr/unique_ptr.h>
  10. #include <AzCore/std/containers/map.h>
  11. #include <AzCore/std/containers/set.h>
  12. #include <System/FabricCooker.h>
  13. // NvCloth library includes
  14. #include <NvCloth/Range.h>
  15. #include <NvClothExt/ClothFabricCooker.h>
  16. namespace NvCloth
  17. {
  18. namespace Internal
  19. {
  20. FabricId ComputeFabricId(
  21. const AZStd::vector<SimParticleFormat>& particles,
  22. const AZStd::vector<SimIndexType>& indices,
  23. const AZ::Vector3& fabricGravity,
  24. bool useGeodesicTether)
  25. {
  26. AZ::Crc32 upperCrc32(particles.data(), sizeof(SimParticleFormat)*particles.size());
  27. upperCrc32.Add(&fabricGravity, sizeof(fabricGravity));
  28. AZ::Crc32 lowerCrc32(indices.data(), sizeof(SimIndexType)*indices.size());
  29. lowerCrc32.Add(&useGeodesicTether, sizeof(useGeodesicTether));
  30. const AZ::u32 upper = static_cast<AZ::u32>(upperCrc32);
  31. const AZ::u32 lower = static_cast<AZ::u32>(lowerCrc32);
  32. const AZ::u64 id =
  33. static_cast<AZ::u64>(lower) |
  34. (static_cast<AZ::u64>(upper) << 32);
  35. return FabricId(id);
  36. }
  37. nv::cloth::BoundedData ToNvBoundedData(const void* data, size_t stride, size_t count)
  38. {
  39. nv::cloth::BoundedData boundedData;
  40. boundedData.data = data;
  41. boundedData.stride = static_cast<physx::PxU32>(stride);
  42. boundedData.count = static_cast<physx::PxU32>(count);
  43. return boundedData;
  44. }
  45. template <typename T>
  46. static void CopyNvRange(const nv::cloth::Range<const T>& nvRange, AZStd::vector<T>& azVector)
  47. {
  48. azVector.resize(nvRange.size());
  49. AZStd::copy(nvRange.begin(), nvRange.end(), azVector.begin());
  50. }
  51. void CopyCookedData(FabricCookedData::InternalCookedData& azCookedData, const nv::cloth::CookedData& nvCookedData)
  52. {
  53. azCookedData.m_numParticles = nvCookedData.mNumParticles;
  54. // All these are fast copies
  55. CopyNvRange(nvCookedData.mPhaseIndices, azCookedData.m_phaseIndices);
  56. CopyNvRange(nvCookedData.mPhaseTypes, azCookedData.m_phaseTypes);
  57. CopyNvRange(nvCookedData.mSets, azCookedData.m_sets);
  58. CopyNvRange(nvCookedData.mRestvalues, azCookedData.m_restValues);
  59. CopyNvRange(nvCookedData.mStiffnessValues, azCookedData.m_stiffnessValues);
  60. CopyNvRange(nvCookedData.mIndices, azCookedData.m_indices);
  61. CopyNvRange(nvCookedData.mAnchors, azCookedData.m_anchors);
  62. CopyNvRange(nvCookedData.mTetherLengths, azCookedData.m_tetherLengths);
  63. CopyNvRange(nvCookedData.mTriangles, azCookedData.m_triangles);
  64. }
  65. AZStd::optional<FabricCookedData> Cook(
  66. const AZStd::vector<SimParticleFormat>& particles,
  67. const AZStd::vector<SimIndexType>& indices,
  68. const AZ::Vector3& fabricGravity,
  69. bool useGeodesicTether)
  70. {
  71. // Check if all the particles are static (inverse masses are all 0)
  72. const bool fullyStaticFabric = AZStd::all_of(particles.cbegin(), particles.cend(),
  73. [](const SimParticleFormat& particle)
  74. {
  75. return particle.GetW() == 0.0f;
  76. });
  77. const int numIndicesPerTriangle = 3;
  78. const AZStd::vector<float> defaultInvMasses(particles.size(), 1.0f);
  79. nv::cloth::ClothMeshDesc meshDesc;
  80. meshDesc.setToDefault();
  81. meshDesc.points = ToNvBoundedData(particles.data(), sizeof(SimParticleFormat), particles.size());
  82. if (!fullyStaticFabric)
  83. {
  84. const int offsetToW = 3;
  85. meshDesc.invMasses = ToNvBoundedData(reinterpret_cast<const float*>(particles.data()) + offsetToW, sizeof(SimParticleFormat), particles.size());
  86. }
  87. else
  88. {
  89. // NvCloth doesn't support cooking a fabric where all its simulation particles are static (inverse masses are all 0.0).
  90. // In this situation we will cook the fabric with the default inverse masses (all 1.0). At runtime, inverse masses are
  91. // provided to the cloth when created, and they will override the fabric ones. NvCloth does support the cloth instance
  92. // to be fully static, but not the fabric.
  93. meshDesc.invMasses = ToNvBoundedData(defaultInvMasses.data(), sizeof(float), defaultInvMasses.size());
  94. }
  95. meshDesc.triangles = ToNvBoundedData(indices.data(), sizeof(SimIndexType) * numIndicesPerTriangle, indices.size() / numIndicesPerTriangle);
  96. meshDesc.flags = (sizeof(SimIndexType) == 2) ? nv::cloth::MeshFlag::e16_BIT_INDICES : 0;
  97. AZStd::unique_ptr<nv::cloth::ClothFabricCooker> cooker(NvClothCreateFabricCooker());
  98. if (!cooker ||
  99. !cooker->cook(meshDesc, *reinterpret_cast<const physx::PxVec3*>(&fabricGravity), useGeodesicTether))
  100. {
  101. return AZStd::nullopt;
  102. }
  103. FabricId fabricId = ComputeFabricId(particles, indices, fabricGravity, useGeodesicTether);
  104. if (!fabricId.IsValid())
  105. {
  106. return AZStd::nullopt;
  107. }
  108. FabricCookedData fabricData;
  109. fabricData.m_id = fabricId;
  110. fabricData.m_particles = particles;
  111. fabricData.m_indices = indices;
  112. fabricData.m_gravity = fabricGravity;
  113. fabricData.m_useGeodesicTether = useGeodesicTether;
  114. CopyCookedData(fabricData.m_internalData, cooker->getCookedData());
  115. return AZStd::optional<FabricCookedData>(AZStd::move(fabricData));
  116. }
  117. void WeldVertices(
  118. const AZStd::vector<SimParticleFormat>& particles,
  119. const AZStd::vector<SimIndexType>& indices,
  120. AZStd::vector<SimParticleFormat>& weldedParticles,
  121. AZStd::vector<SimIndexType>& weldedIndices,
  122. AZStd::vector<int>& remappedVertices,
  123. float weldingDistance = AZ::Constants::FloatEpsilon)
  124. {
  125. // Comparison functor for simulation particles based on the position.
  126. // Inverse mass is not involved in the comparison.
  127. struct ParticlesCompareLess
  128. {
  129. bool operator()(const SimParticleFormat& lhs, const SimParticleFormat& rhs) const
  130. {
  131. if (!AZ::IsClose(lhs.GetX(), rhs.GetX(), m_weldingDistance))
  132. {
  133. return lhs.GetX() < rhs.GetX();
  134. }
  135. else if (!AZ::IsClose(lhs.GetY(), rhs.GetY(), m_weldingDistance))
  136. {
  137. return lhs.GetY() < rhs.GetY();
  138. }
  139. else if (!AZ::IsClose(lhs.GetZ(), rhs.GetZ(), m_weldingDistance))
  140. {
  141. return lhs.GetZ() < rhs.GetZ();
  142. }
  143. return false;
  144. }
  145. float m_weldingDistance = AZ::Constants::FloatEpsilon;
  146. };
  147. using ParticleToIndicesMap = AZStd::map<SimParticleFormat, AZStd::vector<size_t>, ParticlesCompareLess>;
  148. ParticleToIndicesMap particleToIndicesMap({ weldingDistance });
  149. for (size_t originalIndex = 0; originalIndex < particles.size(); ++originalIndex)
  150. {
  151. // To weld vertices with the same position we use a map where the key is the particle itself.
  152. // When inserting the particle to the map it will pick up the particle with the same position.
  153. auto insertedIt = particleToIndicesMap.insert({ particles[originalIndex], {} }).first;
  154. insertedIt->second.push_back(originalIndex);
  155. // Keep the minimum inverse mass value when welding particles.
  156. // It's OK to modify the W of the key element from the map because it's not involved in the comparison functor.
  157. insertedIt->first.SetW(
  158. AZStd::min(
  159. insertedIt->first.GetW(),
  160. particles[originalIndex].GetW()));
  161. }
  162. // Compose welded particles and remapped vertices.
  163. int remappedIndex = 0;
  164. const int invalidIndex = -1;
  165. weldedParticles.resize_no_construct(particleToIndicesMap.size());
  166. remappedVertices.resize(particles.size(), invalidIndex);
  167. for (const auto& particleToIndicesPair : particleToIndicesMap)
  168. {
  169. weldedParticles[remappedIndex] = particleToIndicesPair.first;
  170. for (const size_t& originalIndex : particleToIndicesPair.second)
  171. {
  172. remappedVertices[originalIndex] = remappedIndex;
  173. }
  174. ++remappedIndex;
  175. }
  176. // Compose welded indices.
  177. weldedIndices.resize_no_construct(indices.size());
  178. for (size_t i = 0; i < indices.size(); ++i)
  179. {
  180. const int remappedVertexIndex = remappedVertices[indices[i]];
  181. AZ_Assert(remappedVertexIndex >= 0, "Vertex Index %u has an invalid remapping", indices[i]);
  182. weldedIndices[i] = static_cast<SimIndexType>(remappedVertexIndex);
  183. }
  184. }
  185. void RemoveStaticTriangles(
  186. const AZStd::vector<SimParticleFormat>& particles,
  187. const AZStd::vector<SimIndexType>& indices,
  188. AZStd::vector<SimParticleFormat>& simplifiedParticles,
  189. AZStd::vector<SimIndexType>& simplifiedIndices,
  190. AZStd::vector<int>& remappedVertices)
  191. {
  192. using ParticleIndexSet = AZStd::set<size_t>;
  193. using TriangleIndices = AZStd::array<SimIndexType, 3>;
  194. ParticleIndexSet particleIndexSet;
  195. const size_t numTriangles = indices.size() / 3;
  196. size_t simplifiedNumTriangles = 0;
  197. auto isTriangleStatic = [&particles](const TriangleIndices& triangleIndices)
  198. {
  199. return (particles[triangleIndices[0]].GetW() == 0.0f)
  200. && (particles[triangleIndices[1]].GetW() == 0.0f)
  201. && (particles[triangleIndices[2]].GetW() == 0.0f);
  202. };
  203. // Collect all the vertices that belongs to non-static triangles
  204. for (size_t triangleIndex = 0; triangleIndex < numTriangles; ++triangleIndex)
  205. {
  206. const TriangleIndices triangleIndices =
  207. {{
  208. indices[triangleIndex * 3 + 0],
  209. indices[triangleIndex * 3 + 1],
  210. indices[triangleIndex * 3 + 2]
  211. }};
  212. if (isTriangleStatic(triangleIndices))
  213. {
  214. continue;
  215. }
  216. for (const auto& vertexIndex : triangleIndices)
  217. {
  218. particleIndexSet.insert({ vertexIndex });
  219. }
  220. ++simplifiedNumTriangles;
  221. }
  222. // Compose simplified particles and remapped vertices.
  223. int remappedIndex = 0;
  224. const int invalidIndex = -1;
  225. simplifiedParticles.resize_no_construct(particleIndexSet.size());
  226. remappedVertices.resize(particles.size(), invalidIndex);
  227. for (const auto& particleIndex : particleIndexSet)
  228. {
  229. simplifiedParticles[remappedIndex] = particles[particleIndex];
  230. remappedVertices[particleIndex] = remappedIndex;
  231. ++remappedIndex;
  232. }
  233. // Compose simplified indices.
  234. size_t simplifiedIndex = 0;
  235. simplifiedIndices.resize_no_construct(simplifiedNumTriangles * 3);
  236. for (size_t triangleIndex = 0; triangleIndex < numTriangles; ++triangleIndex)
  237. {
  238. const TriangleIndices triangleIndices =
  239. {{
  240. indices[triangleIndex * 3 + 0],
  241. indices[triangleIndex * 3 + 1],
  242. indices[triangleIndex * 3 + 2]
  243. }};
  244. if (isTriangleStatic(triangleIndices))
  245. {
  246. continue;
  247. }
  248. for (const auto& vertexIndex : triangleIndices)
  249. {
  250. const int remappedVertexIndex = remappedVertices[vertexIndex];
  251. AZ_Assert(remappedVertexIndex >= 0, "Vertex Index %u has an invalid remapping", vertexIndex);
  252. simplifiedIndices[simplifiedIndex++] = static_cast<SimIndexType>(remappedVertexIndex);
  253. }
  254. }
  255. AZ_Assert(simplifiedIndex == simplifiedIndices.size(),
  256. "Number of indices after removing static particles is %zu, but it's expected %zu.", simplifiedIndex, simplifiedIndices.size());
  257. }
  258. }
  259. AZStd::optional<FabricCookedData> FabricCooker::CookFabric(
  260. const AZStd::vector<SimParticleFormat>& particles,
  261. const AZStd::vector<SimIndexType>& indices,
  262. const AZ::Vector3& fabricGravity,
  263. bool useGeodesicTether)
  264. {
  265. AZ_PROFILE_FUNCTION(Cloth);
  266. return Internal::Cook(particles, indices, fabricGravity, useGeodesicTether);
  267. }
  268. void FabricCooker::SimplifyMesh(
  269. const AZStd::vector<SimParticleFormat>& particles,
  270. const AZStd::vector<SimIndexType>& indices,
  271. AZStd::vector<SimParticleFormat>& simplifiedParticles,
  272. AZStd::vector<SimIndexType>& simplifiedIndices,
  273. AZStd::vector<int>& remappedVertices,
  274. bool removeStaticTriangles)
  275. {
  276. AZ_PROFILE_FUNCTION(Cloth);
  277. // Weld vertices together
  278. AZStd::vector<SimParticleFormat> weldedParticles;
  279. AZStd::vector<SimIndexType> weldedIndices;
  280. AZStd::vector<int> weldedRemappedVertices;
  281. Internal::WeldVertices(
  282. particles, indices,
  283. weldedParticles, weldedIndices,
  284. weldedRemappedVertices);
  285. if (!removeStaticTriangles)
  286. {
  287. simplifiedParticles = AZStd::move(weldedParticles);
  288. simplifiedIndices = AZStd::move(weldedIndices);
  289. remappedVertices = AZStd::move(weldedRemappedVertices);
  290. return;
  291. }
  292. // Remove static particles
  293. AZStd::vector<int> simplifiedRemappedVertices;
  294. Internal::RemoveStaticTriangles(
  295. weldedParticles, weldedIndices,
  296. simplifiedParticles, simplifiedIndices,
  297. simplifiedRemappedVertices);
  298. // Compose final remapped vertices
  299. remappedVertices.resize_no_construct(particles.size());
  300. for (size_t i = 0; i < particles.size(); ++i)
  301. {
  302. const int weldedRemappedIndex = weldedRemappedVertices[i];
  303. remappedVertices[i] = simplifiedRemappedVertices[weldedRemappedIndex];
  304. }
  305. }
  306. } // namespace NvCloth