diff --git a/Source/Engine/Physics/Actors/Cloth.cpp b/Source/Engine/Physics/Actors/Cloth.cpp index 23f7633e1..40c058455 100644 --- a/Source/Engine/Physics/Actors/Cloth.cpp +++ b/Source/Engine/Physics/Actors/Cloth.cpp @@ -193,8 +193,8 @@ void Cloth::SetPaint(Span value) CalculateInvMasses(invMasses); PhysicsBackend::LockClothParticles(_cloth); PhysicsBackend::SetClothParticles(_cloth, Span(), Span(), ToSpan(invMasses)); - PhysicsBackend::UnlockClothParticles(_cloth); PhysicsBackend::SetClothPaint(_cloth, value); + PhysicsBackend::UnlockClothParticles(_cloth); } #endif } @@ -346,6 +346,11 @@ void Cloth::DrawPhysicsDebug(RenderView& view) i1 = indicesData.Get()[index + 1]; i2 = indicesData.Get()[index + 2]; } + if (_paint.Count() == particles.Length()) + { + if (Math::Max(_paint[i0], _paint[i1], _paint[i2]) < ZeroTolerance) + continue; + } const Vector3 v0 = transform.LocalToWorld(Vector3(particles[i0])); const Vector3 v1 = transform.LocalToWorld(Vector3(particles[i1])); const Vector3 v2 = transform.LocalToWorld(Vector3(particles[i2])); @@ -609,7 +614,7 @@ void Cloth::CalculateInvMasses(Array& invMasses) // Sum triangle area for each influenced particle invMasses.Resize(verticesCount); - invMasses.SetAll(0.0f); + Platform::MemoryClear(invMasses.Get(), verticesCount * sizeof(float)); for (int32 triangleIndex = 0; triangleIndex < trianglesCount; triangleIndex++) { const int32 index = triangleIndex * 3; @@ -689,7 +694,92 @@ void Cloth::CalculateInvMasses(Array& invMasses) #endif } -void Cloth::OnUpdated() +void Cloth::OnPreUpdate() +{ + // Get current skinned mesh pose for the simulation of the non-kinematic vertices + if (auto* animatedModel = Cast(GetParent())) + { + if (animatedModel->GraphInstance.NodesPose.IsEmpty() || _paint.IsEmpty()) + return; + const ModelInstanceActor::MeshReference mesh = GetMesh(); + if (mesh.Actor == nullptr) + return; + BytesContainer verticesData; + int32 verticesCount; + if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Vertex0, verticesData, verticesCount)) + return; + auto vbStride = (uint32)verticesData.Length() / verticesCount; + ASSERT(vbStride == sizeof(VB0SkinnedElementType)); + PhysicsBackend::LockClothParticles(_cloth); + const Span particles = PhysicsBackend::GetClothParticles(_cloth); + // TODO: optimize memory allocs (eg. write directly to nvCloth mapped range or use shared allocator) + Array particlesSkinned; + particlesSkinned.Set(particles.Get(), particles.Length()); + + // TODO: optimize memory allocs (eg. get pose as Span for readonly) + Array pose; + animatedModel->GetCurrentPose(pose); + const SkeletonData& skeleton = animatedModel->SkinnedModel->Skeleton; + + // Animated model uses skinning thus requires to set vertex position inverse to skeleton bones + const float* paint = _paint.Get(); + bool anyFixed = false; + for (int32 i = 0; i < verticesCount; i++) + { + if (paint[i] > ZeroTolerance) + continue; + VB0SkinnedElementType& vb0 = verticesData.Get()[i]; + + // Calculate skinned vertex matrix from bones blending + const Float4 blendWeights = vb0.BlendWeights.ToFloat4(); + // TODO: optimize this or use _skinningData from AnimatedModel to access current mesh bones data directly + Matrix matrix; + const SkeletonBone& bone0 = skeleton.Bones[vb0.BlendIndices.R]; + Matrix::Multiply(bone0.OffsetMatrix, pose[bone0.NodeIndex], matrix); + Matrix boneMatrix = matrix * blendWeights.X; + if (blendWeights.Y > 0.0f) + { + const SkeletonBone& bone1 = skeleton.Bones[vb0.BlendIndices.G]; + Matrix::Multiply(bone1.OffsetMatrix, pose[bone1.NodeIndex], matrix); + boneMatrix += matrix * blendWeights.Y; + } + if (blendWeights.Z > 0.0f) + { + const SkeletonBone& bone2 = skeleton.Bones[vb0.BlendIndices.B]; + Matrix::Multiply(bone2.OffsetMatrix, pose[bone2.NodeIndex], matrix); + boneMatrix += matrix * blendWeights.Z; + } + if (blendWeights.W > 0.0f) + { + const SkeletonBone& bone3 = skeleton.Bones[vb0.BlendIndices.A]; + Matrix::Multiply(bone3.OffsetMatrix, pose[bone3.NodeIndex], matrix); + boneMatrix += matrix * blendWeights.W; + } + + // Skin vertex position (similar to GPU vertex shader) + Float3 pos = Float3::Transform(vb0.Position, boneMatrix); + + // Transform back to the cloth space + // TODO: skip when using identity? + pos = _localTransform.WorldToLocal(pos); + + // Override fixed particle position + particlesSkinned[i] = Float4(pos, 0.0f); + anyFixed = true; + } + + if (anyFixed) + { + // Update particles + PhysicsBackend::SetClothParticles(_cloth, ToSpan(particlesSkinned), Span(), Span()); + PhysicsBackend::SetClothPaint(_cloth, ToSpan(_paint)); + } + + PhysicsBackend::UnlockClothParticles(_cloth); + } +} + +void Cloth::OnPostUpdate() { if (_meshDeformation) { @@ -722,7 +812,7 @@ void Cloth::RunClothDeformer(const MeshBase* mesh, MeshDeformationData& deformat { if (animatedModel->GraphInstance.NodesPose.IsEmpty()) { - // Delay unit skinning data is ready + // Delay until skinning data is ready PhysicsBackend::UnlockClothParticles(_cloth); _meshDeformation->Dirty(_mesh.LODIndex, _mesh.MeshIndex, MeshBufferType::Vertex0); return; @@ -735,9 +825,15 @@ void Cloth::RunClothDeformer(const MeshBase* mesh, MeshDeformationData& deformat // Animated model uses skinning thus requires to set vertex position inverse to skeleton bones ASSERT(vbStride == sizeof(VB0SkinnedElementType)); + const float* paint = _paint.Count() >= particles.Length() ? _paint.Get() : nullptr; for (uint32 i = 0; i < vbCount; i++) { VB0SkinnedElementType& vb0 = *(VB0SkinnedElementType*)vbData; + vbData += vbStride; + + // Skip fixed vertices + if (paint && paint[i] < ZeroTolerance) + continue; // Calculate skinned vertex matrix from bones blending const Float4 blendWeights = vb0.BlendWeights.ToFloat4(); @@ -770,14 +866,13 @@ void Cloth::RunClothDeformer(const MeshBase* mesh, MeshDeformationData& deformat Matrix::Invert(boneMatrix, boneMatrixInv); Float3 pos = *(Float3*)&particles.Get()[i]; vb0.Position = Float3::Transform(pos, boneMatrixInv); - - vbData += vbStride; } } else { for (uint32 i = 0; i < vbCount; i++) { + // Copy particle positions to the mesh data *(Float3*)vbData = *(Float3*)&particles.Get()[i]; vbData += vbStride; } diff --git a/Source/Engine/Physics/Actors/Cloth.h b/Source/Engine/Physics/Actors/Cloth.h index b31c12984..fc5eaea63 100644 --- a/Source/Engine/Physics/Actors/Cloth.h +++ b/Source/Engine/Physics/Actors/Cloth.h @@ -338,6 +338,7 @@ private: bool CreateCloth(); void DestroyCloth(); void CalculateInvMasses(Array& invMasses); - void OnUpdated(); + void OnPreUpdate(); + void OnPostUpdate(); void RunClothDeformer(const MeshBase* mesh, struct MeshDeformationData& deformation); }; diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index 88a123be9..a93ab0292 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -1436,6 +1436,7 @@ void PhysicsBackend::EndSimulateScene(void* scene) { auto clothPhysX = (nv::cloth::Cloth*)cloths[i]; const auto& clothSettings = Cloths[clothPhysX]; + clothSettings.Actor->OnPreUpdate(); // Setup automatic scene collisions with colliders around the cloth if (clothSettings.SceneCollisions) @@ -1609,7 +1610,7 @@ void PhysicsBackend::EndSimulateScene(void* scene) auto clothPhysX = (nv::cloth::Cloth*)cloths[i]; const auto& clothSettings = Cloths[clothPhysX]; clothSettings.UpdateBounds(clothPhysX); - clothSettings.Actor->OnUpdated(); + clothSettings.Actor->OnPostUpdate(); } } } @@ -3572,7 +3573,16 @@ void PhysicsBackend::SetClothParticles(void* cloth, Span value, Sp { // Set XYZW CHECK((uint32_t)value.Length() >= size); - Platform::MemoryCopy(dst, value.Get(), size * sizeof(Float4)); + const Float4* src = value.Get(); + Platform::MemoryCopy(dst, src, size * sizeof(Float4)); + + // Apply previous particles too for immovable particles + nv::cloth::MappedRange range2 = clothPhysX->getPreviousParticles(); + for (uint32 i = 0; i < size; i++) + { + if (src[i].W <= ZeroTolerance) + range2.begin()[i] = (PxVec4&)src[i]; + } } if (positions.IsValid()) {