/* * 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 #include // NvCloth library includes #include #include #include #include #include namespace NvCloth { namespace Internal { // Returns AZ::Vector3 as physx::PxVec3 using the same memory. // It's safe to reinterpret AZ::Vector3 as physx::PxVec3 because they have the same memory layout // and AZ::Vector3 has more memory alignment restrictions than physx::PxVec3. // The opposite operation would NOT be safe. physx::PxVec3& AsPxVec3(AZ::Vector3& azVec) { return *reinterpret_cast(&azVec); } const physx::PxVec3& AsPxVec3(const AZ::Vector3& azVec) { return *reinterpret_cast(&azVec); } // Returns AZ::Quaternion as physx::PxQuat using the same memory. // It's safe to reinterpret AZ::Quaternion as physx::PxQuat because they have the same memory layout // and AZ::Quaternion has more memory alignment restrictions than physx::PxQuat. // The opposite operation would NOT be safe. physx::PxQuat& AsPxQuat(AZ::Quaternion& azQuat) { return *reinterpret_cast(&azQuat); } const physx::PxQuat& AsPxQuat(const AZ::Quaternion& azQuat) { return *reinterpret_cast(&azQuat); } // Copies an AZ vector of AZ::Vector4 elements as a NvCloth Range of physx::PxVec4 elements. // // It's safe to reinterpret AZ::Vector4 as physx::PxVec4 because they have the same memory layout. // Each one has its own memory with their appropriate alignments. void FastCopy(const AZStd::vector& azVector, nv::cloth::Range& nvRange) { AZ_Assert(azVector.size() == nvRange.size(), "Mismatch in number of elements. AZ vector: %zu Nv Range: %u", azVector.size(), nvRange.size()); static_assert(sizeof(physx::PxVec4) == sizeof(AZ::Vector4), "physx::PxVec4 and AZ::Vector4 types have different sizes"); // Reinterpret cast to floats so it does a fast copy. AZStd::copy( reinterpret_cast(azVector.data()), reinterpret_cast(azVector.data() + azVector.size()), reinterpret_cast(nvRange.begin())); } // Copies a NvCloth Range of physx::PxVec4 elements as an AZ vector of AZ::Vector4 elements. // // It's safe to reinterpret AZ::Vector4 as physx::PxVec4 because they have the same memory layout. // Each one has its own memory with their appropriate alignments. void FastCopy(const nv::cloth::Range& nvRange, AZStd::vector& azVector) { AZ_Assert(azVector.size() == nvRange.size(), "Mismatch in number of elements. AZ vector: %zu Nv Range: %u", azVector.size(), nvRange.size()); static_assert(sizeof(physx::PxVec4) == sizeof(AZ::Vector4), "physx::PxVec4 and AZ::Vector4 types have different sizes"); // Reinterpret cast to floats so it does a fast copy. AZStd::copy( reinterpret_cast(nvRange.begin()), reinterpret_cast(nvRange.end()), reinterpret_cast(azVector.begin())); } // Moves an AZ vector of AZ::Vector4 elements as a NvCloth Range of physx::PxVec4 elements. // // It's safe to reinterpret AZ::Vector4 as physx::PxVec4 because they have the same memory layout. // Each one has its own memory with their appropriate alignments. void FastMove(AZStd::vector&& azVector, nv::cloth::Range& nvRange) { AZ_Assert(azVector.size() == nvRange.size(), "Mismatch in number of elements. AZ vector: %zu Nv Range: %u", azVector.size(), nvRange.size()); static_assert(sizeof(physx::PxVec4) == sizeof(AZ::Vector4), "physx::PxVec4 and AZ::Vector4 types have different sizes"); // Reinterpret cast to floats so it does a fast move. AZStd::move( reinterpret_cast(azVector.data()), reinterpret_cast(azVector.data() + azVector.size()), reinterpret_cast(nvRange.begin())); } // Moves a NvCloth Range of physx::PxVec4 elements as an AZ vector of AZ::Vector4 elements. // // It's safe to reinterpret AZ::Vector4 as physx::PxVec4 because they have the same memory layout. // Each one has its own memory with their appropriate alignments. void FastMove(nv::cloth::Range&& nvRange, AZStd::vector& azVector) { AZ_Assert(azVector.size() == nvRange.size(), "Mismatch in number of elements. AZ vector: %zu Nv Range: %u", azVector.size(), nvRange.size()); static_assert(sizeof(physx::PxVec4) == sizeof(AZ::Vector4), "physx::PxVec4 and AZ::Vector4 types have different sizes"); // Reinterpret cast to floats so it does a fast copy. AZStd::move( reinterpret_cast(nvRange.begin()), reinterpret_cast(nvRange.end()), reinterpret_cast(azVector.begin())); } } Cloth::Cloth( ClothId id, const AZStd::vector& initialParticles, Fabric* fabric, NvClothUniquePtr nvCloth) : m_id(id) , m_nvCloth(AZStd::move(nvCloth)) , m_fabric(fabric) , m_initialParticles(initialParticles) , m_initialParticlesWithMassApplied(initialParticles) { m_simParticles = initialParticles; // Construct the default list of phase configurations const size_t numPhaseTypes = m_fabric->GetPhaseTypes().size(); m_nvPhaseConfigs.reserve(numPhaseTypes); for (size_t phaseIndex = 0; phaseIndex < numPhaseTypes; phaseIndex++) { m_nvPhaseConfigs.emplace_back(static_cast(phaseIndex)); } ApplyPhaseConfigs(); // Set default gravity const AZ::Vector3 gravity(0.0f, 0.0f, -9.81f); SetGravity(gravity); // One more cloth instance using the fabric m_fabric->m_numClothsUsingFabric++; } Cloth::~Cloth() { // If cloth is still part of a solver, remove it if (m_solver) { m_solver->RemoveCloth(this); } // One less cloth instance using the fabric m_fabric->m_numClothsUsingFabric--; } void Cloth::Update() { AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Cloth); ResolveStaticParticles(); if (!RetrieveSimulationResults()) { RestoreSimulation(); } } ClothId Cloth::GetId() const { return m_id; } const AZStd::vector& Cloth::GetInitialParticles() const { return m_initialParticles; } const AZStd::vector& Cloth::GetInitialIndices() const { return m_fabric->m_cookedData.m_indices; } const AZStd::vector& Cloth::GetParticles() const { return m_simParticles; } void Cloth::SetParticles(const AZStd::vector& particles) { if (m_simParticles.size() != particles.size()) { AZ_Warning("Cloth", false, "Unable to set cloth particles as it doesn't match the number of elements. Number of particles passed %zu, expected %zu.", particles.size(), m_simParticles.size()); return; } m_simParticles = particles; CopySimParticlesToNvCloth(); } void Cloth::SetParticles(AZStd::vector&& particles) { if (m_simParticles.size() != particles.size()) { AZ_Warning("Cloth", false, "Unable to set cloth particles as it doesn't match the number of elements. Number of particles passed %zu, expected %zu.", particles.size(), m_simParticles.size()); return; } m_simParticles = AZStd::move(particles); CopySimParticlesToNvCloth(); } void Cloth::DiscardParticleDelta() { const nv::cloth::MappedRange currentParticles = nv::cloth::readCurrentParticles(*m_nvCloth); nv::cloth::MappedRange previousParticles = m_nvCloth->getPreviousParticles(); // Reinterpret cast to floats so it does a fast copy. AZStd::copy( reinterpret_cast(currentParticles.begin()), reinterpret_cast(currentParticles.end()), reinterpret_cast(previousParticles.begin())); } const FabricCookedData& Cloth::GetFabricCookedData() const { return m_fabric->m_cookedData; } IClothConfigurator* Cloth::GetClothConfigurator() { return this; } void Cloth::SetTransform(const AZ::Transform& transformWorld) { m_nvCloth->setTranslation(Internal::AsPxVec3(transformWorld.GetPosition())); m_nvCloth->setRotation(Internal::AsPxQuat(AZ::Quaternion::CreateFromTransform(transformWorld))); } void Cloth::ClearInertia() { m_nvCloth->clearInertia(); } void Cloth::SetMass(float mass) { if (AZ::IsClose(m_mass, mass, std::numeric_limits::epsilon())) { return; } m_mass = mass; const float inverseMass = (m_mass > 0.0f) ? (1.0f / m_mass) : 0.0f; for (size_t i = 0; i < m_simParticles.size(); ++i) { const float particleInvMass = m_initialParticles[i].GetW() * inverseMass; m_simParticles[i].SetW(particleInvMass); m_initialParticlesWithMassApplied[i].SetW(particleInvMass); } CopySimInverseMassesToNvCloth(); } void Cloth::SetGravity(const AZ::Vector3& gravity) { m_nvCloth->setGravity(Internal::AsPxVec3(gravity)); } void Cloth::SetStiffnessFrequency(float frequency) { m_nvCloth->setStiffnessFrequency(frequency); } void Cloth::SetDamping(const AZ::Vector3& damping) { m_nvCloth->setDamping(Internal::AsPxVec3(damping)); } void Cloth::SetDampingLinearDrag(const AZ::Vector3& linearDrag) { m_nvCloth->setLinearDrag(Internal::AsPxVec3(linearDrag)); } void Cloth::SetDampingAngularDrag(const AZ::Vector3& angularDrag) { m_nvCloth->setAngularDrag(Internal::AsPxVec3(angularDrag)); } void Cloth::SetLinearInertia(const AZ::Vector3& linearInertia) { m_nvCloth->setLinearInertia(Internal::AsPxVec3(linearInertia)); } void Cloth::SetAngularInertia(const AZ::Vector3& angularInertia) { m_nvCloth->setAngularInertia(Internal::AsPxVec3(angularInertia)); } void Cloth::SetCentrifugalInertia(const AZ::Vector3& centrifugalInertia) { m_nvCloth->setCentrifugalInertia(Internal::AsPxVec3(centrifugalInertia)); } void Cloth::SetWindVelocity(const AZ::Vector3& velocity) { m_nvCloth->setWindVelocity(Internal::AsPxVec3(velocity)); } void Cloth::SetWindDragCoefficient(float drag) { const float airDragPerc = 0.97f; // To improve cloth stability m_nvCloth->setDragCoefficient(airDragPerc * drag); } void Cloth::SetWindLiftCoefficient(float lift) { const float airLiftPerc = 0.8f; // To improve cloth stability m_nvCloth->setLiftCoefficient(airLiftPerc * lift); } void Cloth::SetWindFluidDensity(float density) { m_nvCloth->setFluidDensity(density); } void Cloth::SetCollisionFriction(float friction) { m_nvCloth->setFriction(friction); } void Cloth::SetCollisionMassScale(float scale) { m_nvCloth->setCollisionMassScale(scale); } void Cloth::EnableContinuousCollision(bool value) { m_nvCloth->enableContinuousCollision(value); } void Cloth::SetCollisionAffectsStaticParticles(bool value) { m_collisionAffectsStaticParticles = value; } void Cloth::SetSelfCollisionDistance(float distance) { m_nvCloth->setSelfCollisionDistance(distance); } void Cloth::SetSelfCollisionStiffness(float stiffness) { m_nvCloth->setSelfCollisionStiffness(stiffness); } void Cloth::SetVerticalPhaseConfig( float stiffness, float stiffnessMultiplier, float compressionLimit, float stretchLimit) { SetPhaseConfig( nv::cloth::ClothFabricPhaseType::eVERTICAL, stiffness, stiffnessMultiplier, compressionLimit, stretchLimit); } void Cloth::SetHorizontalPhaseConfig( float stiffness, float stiffnessMultiplier, float compressionLimit, float stretchLimit) { SetPhaseConfig( nv::cloth::ClothFabricPhaseType::eHORIZONTAL, stiffness, stiffnessMultiplier, compressionLimit, stretchLimit); } void Cloth::SetBendingPhaseConfig( float stiffness, float stiffnessMultiplier, float compressionLimit, float stretchLimit) { SetPhaseConfig( nv::cloth::ClothFabricPhaseType::eBENDING, stiffness, stiffnessMultiplier, compressionLimit, stretchLimit); } void Cloth::SetShearingPhaseConfig( float stiffness, float stiffnessMultiplier, float compressionLimit, float stretchLimit) { SetPhaseConfig( nv::cloth::ClothFabricPhaseType::eSHEARING, stiffness, stiffnessMultiplier, compressionLimit, stretchLimit); } void Cloth::SetTetherConstraintStiffness(float stiffness) { m_nvCloth->setTetherConstraintStiffness(stiffness); } void Cloth::SetTetherConstraintScale(float scale) { m_nvCloth->setTetherConstraintScale(scale); } void Cloth::SetSolverFrequency(float frequency) { m_nvCloth->setSolverFrequency(frequency); } void Cloth::SetAcceleationFilterWidth(AZ::u32 width) { m_nvCloth->setAcceleationFilterWidth(width); } void Cloth::SetSphereColliders(const AZStd::vector& spheres) { m_nvCloth->setSpheres( ToPxVec4NvRange(spheres), 0, m_nvCloth->getNumSpheres()); } void Cloth::SetSphereColliders(AZStd::vector&& spheres) { SetSphereColliders(spheres); // NvCloth does not offer a move overload for setSpheres, calling the const reference one. } void Cloth::SetCapsuleColliders(const AZStd::vector& capsuleIndices) { m_nvCloth->setCapsules( ToNvRange(capsuleIndices), 0, m_nvCloth->getNumCapsules()); } void Cloth::SetCapsuleColliders(AZStd::vector&& capsuleIndices) { SetCapsuleColliders(capsuleIndices); // NvCloth does not offer a move overload for setCapsules, calling the const reference one. } void Cloth::SetMotionConstraints(const AZStd::vector& constraints) { if (m_simParticles.size() != constraints.size()) { AZ_Warning("Cloth", false, "Unable to set motions constraints as it doesn't match the number of particles. Numbers of constraints passed %zu, expected %zu.", constraints.size(), m_simParticles.size()); return; } m_motionConstraints = constraints; nv::cloth::Range motionConstraints = m_nvCloth->getMotionConstraints(); Internal::FastCopy(m_motionConstraints, motionConstraints); } void Cloth::SetMotionConstraints(AZStd::vector&& constraints) { if (m_simParticles.size() != constraints.size()) { AZ_Warning("Cloth", false, "Unable to set motions constraints as it doesn't match the number of particles. Numbers of constraints passed %zu, expected %zu.", constraints.size(), m_simParticles.size()); return; } m_motionConstraints = AZStd::move(constraints); nv::cloth::Range motionConstraints = m_nvCloth->getMotionConstraints(); Internal::FastCopy(m_motionConstraints, motionConstraints); } void Cloth::ClearMotionConstraints() { m_motionConstraints.clear(); m_nvCloth->clearMotionConstraints(); } void Cloth::SetMotionConstraintsScale(float scale) { m_nvCloth->setMotionConstraintScaleBias(scale, m_nvCloth->getMotionConstraintBias()); } void Cloth::SetMotionConstraintsBias(float bias) { m_nvCloth->setMotionConstraintScaleBias(m_nvCloth->getMotionConstraintScale(), bias); } void Cloth::SetMotionConstraintsStiffness(float stiffness) { m_nvCloth->setMotionConstraintStiffness(stiffness); } void Cloth::SetSeparationConstraints(const AZStd::vector& constraints) { if (m_simParticles.size() != constraints.size()) { AZ_Warning("Cloth", false, "Unable to set separation constraints as it doesn't match the number of particles. Numbers of constraints passed %zu, expected %zu.", constraints.size(), m_simParticles.size()); return; } nv::cloth::Range separationConstraints = m_nvCloth->getSeparationConstraints(); Internal::FastCopy(constraints, separationConstraints); } void Cloth::SetSeparationConstraints(AZStd::vector&& constraints) { if (m_simParticles.size() != constraints.size()) { AZ_Warning("Cloth", false, "Unable to set separation constraints as it doesn't match the number of particles. Numbers of constraints passed %zu, expected %zu.", constraints.size(), m_simParticles.size()); return; } nv::cloth::Range separationConstraints = m_nvCloth->getSeparationConstraints(); Internal::FastMove(AZStd::move(constraints), separationConstraints); } void Cloth::ClearSeparationConstraints() { m_nvCloth->clearSeparationConstraints(); } void Cloth::ResolveStaticParticles() { if (m_collisionAffectsStaticParticles) { // Nothing to do as in NvCloth colliders affect static particles. return; } // During simulation static particle are always affected by colliders and motion constraints. // To remove the effect of colliders on Static Particles we will restore their positions, // either with the motion constraints (if existent) or the last simulated particles. nv::cloth::MappedRange particles = m_nvCloth->getCurrentParticles(); const AZStd::vector& positions = m_motionConstraints.empty() ? m_simParticles : m_motionConstraints; for (AZ::u32 i = 0; i < particles.size(); ++i) { // Checking NvCloth current particles is important because their W component will // have the result left by the simulation applying both inverse masses and motion constraints. if (particles[i].w == 0.0f) { auto& particle = particles[i]; const auto& position = positions[i]; particle.x = position.GetX(); particle.y = position.GetY(); particle.z = position.GetZ(); } } } bool Cloth::RetrieveSimulationResults() { const nv::cloth::MappedRange particles = nv::cloth::readCurrentParticles(*m_nvCloth); bool validCloth = AZStd::all_of(particles.begin(), particles.end(), [](const physx::PxVec4& particle) { return particle.isFinite(); }) && // On some platforms when cloth simulation gets corrupted it puts all particles' position to (0,0,0) AZStd::any_of(particles.begin(), particles.end(), [](const physx::PxVec4& particle) { return particle.x != 0.0f || particle.y != 0.0f || particle.z != 0.0f; }); if (validCloth) { for (AZ::u32 i = 0; i < particles.size(); ++i) { m_simParticles[i].SetX(particles[i].x); m_simParticles[i].SetY(particles[i].y); m_simParticles[i].SetZ(particles[i].z); // Not copying inverse masses on purpose since they could be different after running the simulation. // This solves a problem when using a value of zero in the motion constraints distance // or scale. All inverse masses would go to zero and since we were copying them back, // the original data got lost and it was not able to return to a normal state after // changing the values back to values other than zero. } m_numInvalidSimulations = 0; // Reset counter as the results were valid } return validCloth; } void Cloth::RestoreSimulation() { nv::cloth::MappedRange previousParticles = m_nvCloth->getPreviousParticles(); nv::cloth::MappedRange currentParticles = m_nvCloth->getCurrentParticles(); const AZ::u32 maxAttemptsToRestoreCloth = 15; if (m_numInvalidSimulations <= maxAttemptsToRestoreCloth) { // Leave the NvCloth simulation particles in their last known good position. Internal::FastCopy(m_simParticles, previousParticles); Internal::FastCopy(m_simParticles, currentParticles); } else { // Reset NvCloth simulation particles to their initial position if after a number of // attempts cloth has not been restored to a stable state. Internal::FastCopy(m_initialParticlesWithMassApplied, previousParticles); Internal::FastCopy(m_initialParticlesWithMassApplied, currentParticles); } m_nvCloth->clearInertia(); m_nvCloth->clearInterpolation(); m_numInvalidSimulations++; } void Cloth::CopySimParticlesToNvCloth() { // The positions must be copied into the current particles inside NvCloth. // Note: Inverse masses are copied as well to do a fast copy, // but inverse masses copied to current particles have no effect. nv::cloth::MappedRange currentParticles = m_nvCloth->getCurrentParticles(); Internal::FastCopy(m_simParticles, currentParticles); CopySimInverseMassesToNvCloth(); } void Cloth::CopySimInverseMassesToNvCloth() { // The inverse masses must be copied into the previous particles inside NvCloth // to take effect for the next simulation update. nv::cloth::MappedRange previousParticles = m_nvCloth->getPreviousParticles(); for (AZ::u32 i = 0; i < previousParticles.size(); ++i) { previousParticles[i].w = m_simParticles[i].GetW(); } } void Cloth::SetPhaseConfig( int32_t phaseType, float stiffness, float stiffnessMultiplier, float compressionLimit, float stretchLimit) { const auto& phaseTypes = m_fabric->GetPhaseTypes(); for (size_t i = 0; i < phaseTypes.size(); ++i) { if (phaseTypes[i] == phaseType) { m_nvPhaseConfigs[i].mStiffness = stiffness; m_nvPhaseConfigs[i].mStiffnessMultiplier = 1.0f - AZ::GetClamp(stiffnessMultiplier, 0.0f, 1.0f); // Internally a value of 1 means no scale inside nvcloth. m_nvPhaseConfigs[i].mCompressionLimit = 1.0f + compressionLimit; // A value of 1.0f is no compression inside nvcloth. From [0.0, INF] to [1.0, INF]. m_nvPhaseConfigs[i].mStretchLimit = 1.0f + stretchLimit; // A value of 1.0f is no stretch inside nvcloth. From [0.0, INF] to [1.0, INF]. } } ApplyPhaseConfigs(); } void Cloth::ApplyPhaseConfigs() { m_nvCloth->setPhaseConfig( ToNvRange(m_nvPhaseConfigs)); } } // namespace NvCloth