/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ #include #include #include #include // NvCloth library includes #include #include namespace NvCloth { namespace Internal { FabricId ComputeFabricId( const AZStd::vector& particles, const AZStd::vector& indices, const AZ::Vector3& fabricGravity, bool useGeodesicTether) { AZ::Crc32 upperCrc32(particles.data(), sizeof(SimParticleFormat)*particles.size()); upperCrc32.Add(&fabricGravity, sizeof(fabricGravity)); AZ::Crc32 lowerCrc32(indices.data(), sizeof(SimIndexType)*indices.size()); lowerCrc32.Add(&useGeodesicTether, sizeof(useGeodesicTether)); const AZ::u32 upper = static_cast(upperCrc32); const AZ::u32 lower = static_cast(lowerCrc32); const AZ::u64 id = static_cast(lower) | (static_cast(upper) << 32); return FabricId(id); } nv::cloth::BoundedData ToNvBoundedData(const void* data, size_t stride, size_t count) { nv::cloth::BoundedData boundedData; boundedData.data = data; boundedData.stride = static_cast(stride); boundedData.count = static_cast(count); return boundedData; } template void CopyNvRange(const nv::cloth::Range& nvRange, AZStd::vector& azVector) { azVector.resize(nvRange.size()); AZStd::copy(nvRange.begin(), nvRange.end(), azVector.begin()); } void CopyCookedData(FabricCookedData::InternalCookedData& azCookedData, const nv::cloth::CookedData& nvCookedData) { azCookedData.m_numParticles = nvCookedData.mNumParticles; // All these are fast copies CopyNvRange(nvCookedData.mPhaseIndices, azCookedData.m_phaseIndices); CopyNvRange(nvCookedData.mPhaseTypes, azCookedData.m_phaseTypes); CopyNvRange(nvCookedData.mSets, azCookedData.m_sets); CopyNvRange(nvCookedData.mRestvalues, azCookedData.m_restValues); CopyNvRange(nvCookedData.mStiffnessValues, azCookedData.m_stiffnessValues); CopyNvRange(nvCookedData.mIndices, azCookedData.m_indices); CopyNvRange(nvCookedData.mAnchors, azCookedData.m_anchors); CopyNvRange(nvCookedData.mTetherLengths, azCookedData.m_tetherLengths); CopyNvRange(nvCookedData.mTriangles, azCookedData.m_triangles); } AZStd::optional Cook( const AZStd::vector& particles, const AZStd::vector& indices, const AZ::Vector3& fabricGravity, bool useGeodesicTether) { // Check if all the particles are static (inverse masses are all 0) const bool fullyStaticFabric = AZStd::all_of(particles.cbegin(), particles.cend(), [](const SimParticleFormat& particle) { return particle.GetW() == 0.0f; }); const int numIndicesPerTriangle = 3; const AZStd::vector defaultInvMasses(particles.size(), 1.0f); nv::cloth::ClothMeshDesc meshDesc; meshDesc.setToDefault(); meshDesc.points = ToNvBoundedData(particles.data(), sizeof(SimParticleFormat), particles.size()); if (!fullyStaticFabric) { const int offsetToW = 3; meshDesc.invMasses = ToNvBoundedData(reinterpret_cast(particles.data()) + offsetToW, sizeof(SimParticleFormat), particles.size()); } else { // NvCloth doesn't support cooking a fabric where all its simulation particles are static (inverse masses are all 0.0). // In this situation we will cook the fabric with the default inverse masses (all 1.0). At runtime, inverse masses are // provided to the cloth when created, and they will override the fabric ones. NvCloth does support the cloth instance // to be fully static, but not the fabric. meshDesc.invMasses = ToNvBoundedData(defaultInvMasses.data(), sizeof(float), defaultInvMasses.size()); } meshDesc.triangles = ToNvBoundedData(indices.data(), sizeof(SimIndexType) * numIndicesPerTriangle, indices.size() / numIndicesPerTriangle); meshDesc.flags = (sizeof(SimIndexType) == 2) ? nv::cloth::MeshFlag::e16_BIT_INDICES : 0; AZStd::unique_ptr cooker(NvClothCreateFabricCooker()); if (!cooker || !cooker->cook(meshDesc, *reinterpret_cast(&fabricGravity), useGeodesicTether)) { return AZStd::nullopt; } FabricId fabricId = ComputeFabricId(particles, indices, fabricGravity, useGeodesicTether); if (!fabricId.IsValid()) { return AZStd::nullopt; } FabricCookedData fabricData; fabricData.m_id = fabricId; fabricData.m_particles = particles; fabricData.m_indices = indices; fabricData.m_gravity = fabricGravity; fabricData.m_useGeodesicTether = useGeodesicTether; CopyCookedData(fabricData.m_internalData, cooker->getCookedData()); return AZStd::optional(AZStd::move(fabricData)); } void WeldVertices( const AZStd::vector& particles, const AZStd::vector& indices, AZStd::vector& weldedParticles, AZStd::vector& weldedIndices, AZStd::vector& remappedVertices, float weldingDistance = AZ::g_fltEps) { // Comparison functor for simulation particles based on the position. // Inverse mass is not involved in the comparison. struct ParticlesCompareLess { bool operator()(const SimParticleFormat& lhs, const SimParticleFormat& rhs) const { if (!AZ::IsClose(lhs.GetX(), rhs.GetX(), m_weldingDistance)) { return lhs.GetX() < rhs.GetX(); } else if (!AZ::IsClose(lhs.GetY(), rhs.GetY(), m_weldingDistance)) { return lhs.GetY() < rhs.GetY(); } else if (!AZ::IsClose(lhs.GetZ(), rhs.GetZ(), m_weldingDistance)) { return lhs.GetZ() < rhs.GetZ(); } return false; } float m_weldingDistance = AZ::g_fltEps; }; using ParticleToIndicesMap = AZStd::map, ParticlesCompareLess>; ParticleToIndicesMap particleToIndicesMap({ weldingDistance }); for (size_t originalIndex = 0; originalIndex < particles.size(); ++originalIndex) { // To weld vertices with the same position we use a map where the key is the particle itself. // When inserting the particle to the map it will pick up the particle with the same position. auto insertedIt = particleToIndicesMap.insert({ particles[originalIndex], {} }).first; insertedIt->second.push_back(originalIndex); // Keep the minimum inverse mass value when welding particles. // It's OK to modify the W of the key element from the map because it's not involved in the comparison functor. insertedIt->first.SetW( AZStd::min( insertedIt->first.GetW(), particles[originalIndex].GetW())); } // Compose welded particles and remapped vertices. int remappedIndex = 0; const int invalidIndex = -1; weldedParticles.resize_no_construct(particleToIndicesMap.size()); remappedVertices.resize(particles.size(), invalidIndex); for (const auto& particleToIndicesPair : particleToIndicesMap) { weldedParticles[remappedIndex] = particleToIndicesPair.first; for (const size_t& originalIndex : particleToIndicesPair.second) { remappedVertices[originalIndex] = remappedIndex; } ++remappedIndex; } // Compose welded indices. weldedIndices.resize_no_construct(indices.size()); for (size_t i = 0; i < indices.size(); ++i) { const int remappedVertexIndex = remappedVertices[indices[i]]; AZ_Assert(remappedVertexIndex >= 0, "Vertex Index %u has an invalid remapping", indices[i]); weldedIndices[i] = static_cast(remappedVertexIndex); } } void RemoveStaticTriangles( const AZStd::vector& particles, const AZStd::vector& indices, AZStd::vector& simplifiedParticles, AZStd::vector& simplifiedIndices, AZStd::vector& remappedVertices) { using ParticleIndexSet = AZStd::set; using TriangleIndices = AZStd::array; ParticleIndexSet particleIndexSet; const size_t numTriangles = indices.size() / 3; size_t simplifiedNumTriangles = 0; auto isTriangleStatic = [&particles](const TriangleIndices& triangleIndices) { return (particles[triangleIndices[0]].GetW() == 0.0f) && (particles[triangleIndices[1]].GetW() == 0.0f) && (particles[triangleIndices[2]].GetW() == 0.0f); }; // Collect all the vertices that belongs to non-static triangles for (size_t triangleIndex = 0; triangleIndex < numTriangles; ++triangleIndex) { const TriangleIndices triangleIndices = {{ indices[triangleIndex * 3 + 0], indices[triangleIndex * 3 + 1], indices[triangleIndex * 3 + 2] }}; if (isTriangleStatic(triangleIndices)) { continue; } for (const auto& vertexIndex : triangleIndices) { particleIndexSet.insert({ vertexIndex }); } ++simplifiedNumTriangles; } // Compose simplified particles and remapped vertices. int remappedIndex = 0; const int invalidIndex = -1; simplifiedParticles.resize_no_construct(particleIndexSet.size()); remappedVertices.resize(particles.size(), invalidIndex); for (const auto& particleIndex : particleIndexSet) { simplifiedParticles[remappedIndex] = particles[particleIndex]; remappedVertices[particleIndex] = remappedIndex; ++remappedIndex; } // Compose simplified indices. size_t simplifiedIndex = 0; simplifiedIndices.resize_no_construct(simplifiedNumTriangles * 3); for (size_t triangleIndex = 0; triangleIndex < numTriangles; ++triangleIndex) { const TriangleIndices triangleIndices = {{ indices[triangleIndex * 3 + 0], indices[triangleIndex * 3 + 1], indices[triangleIndex * 3 + 2] }}; if (isTriangleStatic(triangleIndices)) { continue; } for (const auto& vertexIndex : triangleIndices) { const int remappedVertexIndex = remappedVertices[vertexIndex]; AZ_Assert(remappedVertexIndex >= 0, "Vertex Index %u has an invalid remapping", vertexIndex); simplifiedIndices[simplifiedIndex++] = static_cast(remappedVertexIndex); } } AZ_Assert(simplifiedIndex == simplifiedIndices.size(), "Number of indices after removing static particles is %zu, but it's expected %zu.", simplifiedIndex, simplifiedIndices.size()); } } AZStd::optional FabricCooker::CookFabric( const AZStd::vector& particles, const AZStd::vector& indices, const AZ::Vector3& fabricGravity, bool useGeodesicTether) { AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Cloth); return Internal::Cook(particles, indices, fabricGravity, useGeodesicTether); } void FabricCooker::SimplifyMesh( const AZStd::vector& particles, const AZStd::vector& indices, AZStd::vector& simplifiedParticles, AZStd::vector& simplifiedIndices, AZStd::vector& remappedVertices, bool removeStaticTriangles) { AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Cloth); // Weld vertices together AZStd::vector weldedParticles; AZStd::vector weldedIndices; AZStd::vector weldedRemappedVertices; Internal::WeldVertices( particles, indices, weldedParticles, weldedIndices, weldedRemappedVertices); if (!removeStaticTriangles) { simplifiedParticles = AZStd::move(weldedParticles); simplifiedIndices = AZStd::move(weldedIndices); remappedVertices = AZStd::move(weldedRemappedVertices); return; } // Remove static particles AZStd::vector simplifiedRemappedVertices; Internal::RemoveStaticTriangles( weldedParticles, weldedIndices, simplifiedParticles, simplifiedIndices, simplifiedRemappedVertices); // Compose final remapped vertices remappedVertices.resize_no_construct(particles.size()); for (size_t i = 0; i < particles.size(); ++i) { const int weldedRemappedIndex = weldedRemappedVertices[i]; remappedVertices[i] = simplifiedRemappedVertices[weldedRemappedIndex]; } } } // namespace NvCloth