Add cloth painting tools to Editor

This commit is contained in:
Wojtek Figat
2023-07-13 23:30:37 +02:00
parent 3b90e75307
commit 24c03c0e4b
15 changed files with 873 additions and 43 deletions

View File

@@ -2,6 +2,7 @@
#include "Cloth.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Math/Ray.h"
#include "Engine/Graphics/Models/MeshBase.h"
#include "Engine/Graphics/Models/MeshDeformation.h"
#include "Engine/Physics/PhysicsBackend.h"
@@ -109,6 +110,160 @@ void Cloth::ClearInteria()
#endif
}
Array<Float3> Cloth::GetParticles() const
{
Array<Float3> result;
#if WITH_CLOTH
if (_cloth)
{
PROFILE_CPU();
PhysicsBackend::LockClothParticles(_cloth);
const Span<const Float4> particles = PhysicsBackend::GetClothParticles(_cloth);
result.Resize(particles.Length());
const Float4* src = particles.Get();
Float3* dst = result.Get();
for (int32 i = 0; i < particles.Length(); i++)
dst[i] = Float3(src[i]);
PhysicsBackend::UnlockClothParticles(_cloth);
}
#endif
return result;
}
void Cloth::SetParticles(Span<const Float3> value)
{
PROFILE_CPU();
#if !BUILD_RELEASE
{
// Sanity check
const Float3* src = value.Get();
bool allValid = true;
for (int32 i = 0; i < value.Length(); i++)
allValid &= !src[i].IsNanOrInfinity();
ASSERT(allValid);
}
#endif
#if WITH_CLOTH
if (_cloth)
{
// Update cloth particles
PhysicsBackend::LockClothParticles(_cloth);
PhysicsBackend::SetClothParticles(_cloth, Span<const Float4>(), value, Span<const float>());
PhysicsBackend::UnlockClothParticles(_cloth);
}
#endif
}
Span<float> Cloth::GetPaint() const
{
return ToSpan(_paint);
}
void Cloth::SetPaint(Span<const float> value)
{
PROFILE_CPU();
if (value.IsInvalid())
{
// Remove paint when set to empty
_paint.SetCapacity(0);
#if WITH_CLOTH
if (_cloth)
{
PhysicsBackend::SetClothPaint(_cloth, value);
}
#endif
return;
}
#if !BUILD_RELEASE
{
// Sanity check
const float* src = value.Get();
bool allValid = true;
for (int32 i = 0; i < value.Length(); i++)
allValid &= !isnan(src[i]) && !isinf(src[i]);
ASSERT(allValid);
}
#endif
_paint.Set(value.Get(), value.Length());
#if WITH_CLOTH
if (_cloth)
{
// Update cloth particles
Array<float> invMasses;
CalculateInvMasses(invMasses);
PhysicsBackend::LockClothParticles(_cloth);
PhysicsBackend::SetClothParticles(_cloth, Span<const Float4>(), Span<const Float3>(), ToSpan<float, const float>(invMasses));
PhysicsBackend::UnlockClothParticles(_cloth);
PhysicsBackend::SetClothPaint(_cloth, value);
}
#endif
}
bool Cloth::IntersectsItself(const Ray& ray, Real& distance, Vector3& normal)
{
#if USE_PRECISE_MESH_INTERSECTS
if (!Actor::IntersectsItself(ray, distance, normal))
return false;
#if WITH_CLOTH
if (_cloth)
{
// Precise per-triangle intersection
const ModelInstanceActor::MeshReference mesh = GetMesh();
if (mesh.Actor == nullptr)
return false;
BytesContainer indicesData;
int32 indicesCount;
if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Index, indicesData, indicesCount))
return false;
PhysicsBackend::LockClothParticles(_cloth);
const Span<const Float4> particles = PhysicsBackend::GetClothParticles(_cloth);
const Transform transform = GetTransform();
const bool indices16bit = indicesData.Length() / indicesCount == sizeof(uint16);
const int32 trianglesCount = indicesCount / 3;
bool result = false;
distance = MAX_Real;
for (int32 triangleIndex = 0; triangleIndex < trianglesCount; triangleIndex++)
{
const int32 index = triangleIndex * 3;
int32 i0, i1, i2;
if (indices16bit)
{
i0 = indicesData.Get<uint16>()[index];
i1 = indicesData.Get<uint16>()[index + 1];
i2 = indicesData.Get<uint16>()[index + 2];
}
else
{
i0 = indicesData.Get<uint32>()[index];
i1 = indicesData.Get<uint32>()[index + 1];
i2 = indicesData.Get<uint32>()[index + 2];
}
const Vector3 v0 = transform.LocalToWorld(Vector3(particles[i0]));
const Vector3 v1 = transform.LocalToWorld(Vector3(particles[i1]));
const Vector3 v2 = transform.LocalToWorld(Vector3(particles[i2]));
Real d;
if (CollisionsHelper::RayIntersectsTriangle(ray, v0, v1, v2, d) && d < distance)
{
result = true;
normal = Vector3::Normalize((v1 - v0) ^ (v2 - v0));
distance = d;
// Flip normal if needed as cloth is two-sided
const Vector3 hitPos = ray.GetPoint(d);
if (Vector3::DistanceSquared(hitPos + normal, ray.Position) > Math::Square(d))
normal = -normal;
}
}
PhysicsBackend::UnlockClothParticles(_cloth);
return result;
}
#endif
return false;
#else
return Actor::IntersectsItself(ray, distance, normal);
#endif
}
void Cloth::Serialize(SerializeStream& stream, const void* otherObj)
{
Actor::Serialize(stream, otherObj);
@@ -120,6 +275,12 @@ void Cloth::Serialize(SerializeStream& stream, const void* otherObj)
SERIALIZE_MEMBER(Collision, _collisionSettings);
SERIALIZE_MEMBER(Simulation, _simulationSettings);
SERIALIZE_MEMBER(Fabric, _fabricSettings);
if (Serialization::ShouldSerialize(_paint, other ? &other->_paint : nullptr))
{
// Serialize as Base64
stream.JKEY("Paint");
stream.Blob(_paint.Get(), _paint.Count() * sizeof(float));
}
}
void Cloth::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
@@ -132,24 +293,29 @@ void Cloth::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
DESERIALIZE_MEMBER(Collision, _collisionSettings);
DESERIALIZE_MEMBER(Simulation, _simulationSettings);
DESERIALIZE_MEMBER(Fabric, _fabricSettings);
DESERIALIZE_MEMBER(Paint, _paint);
// Refresh cloth when settings were changed
if (IsDuringPlay())
Rebuild();
}
#if USE_EDITOR
void Cloth::DrawPhysicsDebug(RenderView& view)
{
#if WITH_CLOTH
#if WITH_CLOTH && COMPILE_WITH_DEBUG_DRAW
if (_cloth)
{
const ModelInstanceActor::MeshReference mesh = GetMesh();
if (mesh.Actor == nullptr)
return;
BytesContainer indicesData;
int32 indicesCount = 0;
int32 indicesCount;
if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Index, indicesData, indicesCount))
return;
PhysicsBackend::LockClothParticles(_cloth);
const Span<const Float4> particles = PhysicsBackend::GetClothCurrentParticles(_cloth);
const Span<const Float4> particles = PhysicsBackend::GetClothParticles(_cloth);
const Transform transform = GetTransform();
const bool indices16bit = indicesData.Length() / indicesCount == sizeof(uint16);
const int32 trianglesCount = indicesCount / 3;
@@ -172,7 +338,6 @@ void Cloth::DrawPhysicsDebug(RenderView& view)
const Vector3 v0 = transform.LocalToWorld(Vector3(particles[i0]));
const Vector3 v1 = transform.LocalToWorld(Vector3(particles[i1]));
const Vector3 v2 = transform.LocalToWorld(Vector3(particles[i2]));
// TODO: highlight immovable cloth particles with a different color
DEBUG_DRAW_TRIANGLE(v0, v1, v2, Color::Pink, 0, true);
}
PhysicsBackend::UnlockClothParticles(_cloth);
@@ -182,7 +347,7 @@ void Cloth::DrawPhysicsDebug(RenderView& view)
void Cloth::OnDebugDrawSelected()
{
#if WITH_CLOTH
#if WITH_CLOTH && COMPILE_WITH_DEBUG_DRAW
if (_cloth)
{
DEBUG_DRAW_WIRE_BOX(_box, Color::Violet.RGBMultiplied(0.8f), 0, true);
@@ -190,11 +355,11 @@ void Cloth::OnDebugDrawSelected()
if (mesh.Actor == nullptr)
return;
BytesContainer indicesData;
int32 indicesCount = 0;
int32 indicesCount;
if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Index, indicesData, indicesCount))
return;
PhysicsBackend::LockClothParticles(_cloth);
const Span<const Float4> particles = PhysicsBackend::GetClothCurrentParticles(_cloth);
const Span<const Float4> particles = PhysicsBackend::GetClothParticles(_cloth);
const Transform transform = GetTransform();
const bool indices16bit = indicesData.Length() / indicesCount == sizeof(uint16);
const int32 trianglesCount = indicesCount / 3;
@@ -217,10 +382,16 @@ void Cloth::OnDebugDrawSelected()
const Vector3 v0 = transform.LocalToWorld(Vector3(particles[i0]));
const Vector3 v1 = transform.LocalToWorld(Vector3(particles[i1]));
const Vector3 v2 = transform.LocalToWorld(Vector3(particles[i2]));
// TODO: highlight immovable cloth particles with a different color
DEBUG_DRAW_LINE(v0, v1, Color::White, 0, false);
DEBUG_DRAW_LINE(v1, v2, Color::White, 0, false);
DEBUG_DRAW_LINE(v2, v0, Color::White, 0, false);
Color c0 = Color::White, c1 = Color::White, c2 = Color::White;
if (_paint.Count() == particles.Length())
{
c0 = Color::Lerp(Color::Red, Color::White, _paint[i0]);
c1 = Color::Lerp(Color::Red, Color::White, _paint[i1]);
c2 = Color::Lerp(Color::Red, Color::White, _paint[i2]);
}
DebugDraw::DrawLine(v0, v1, c0, c1, 0, false);
DebugDraw::DrawLine(v1, v2, c1, c2, 0, false);
DebugDraw::DrawLine(v2, v0, c2, c0, 0, false);
}
PhysicsBackend::UnlockClothParticles(_cloth);
}
@@ -233,10 +404,11 @@ void Cloth::OnDebugDrawSelected()
void Cloth::BeginPlay(SceneBeginData* data)
{
#if WITH_CLOTH
if (CreateCloth())
{
LOG(Error, "Failed to create cloth '{0}'", GetNamePath());
}
#endif
Actor::BeginPlay(data);
}
@@ -245,10 +417,10 @@ void Cloth::EndPlay()
{
Actor::EndPlay();
#if WITH_CLOTH
if (_cloth)
{
DestroyCloth();
}
#endif
}
void Cloth::OnEnable()
@@ -258,9 +430,7 @@ void Cloth::OnEnable()
#endif
#if WITH_CLOTH
if (_cloth)
{
PhysicsBackend::AddCloth(GetPhysicsScene()->GetPhysicsScene(), _cloth);
}
#endif
Actor::OnEnable();
@@ -272,9 +442,7 @@ void Cloth::OnDisable()
#if WITH_CLOTH
if (_cloth)
{
PhysicsBackend::RemoveCloth(GetPhysicsScene()->GetPhysicsScene(), _cloth);
}
#endif
#if USE_EDITOR
GetSceneRendering()->RemovePhysicsDebug<Cloth, &Cloth::DrawPhysicsDebug>(this);
@@ -347,6 +515,12 @@ bool Cloth::CreateCloth()
desc.IndicesData = data.Get();
desc.IndicesCount = count;
desc.IndicesStride = data.Length() / count;
Array<float> invMasses;
CalculateInvMasses(invMasses);
desc.InvMassesData = invMasses.Count() == desc.VerticesCount ? invMasses.Get() : nullptr;
desc.InvMassesStride = sizeof(float);
desc.MaxDistancesData = _paint.Count() == desc.VerticesCount ? _paint.Get() : nullptr;
desc.MaxDistancesStride = sizeof(float);
// Create cloth
ASSERT(_cloth == nullptr);
@@ -389,6 +563,95 @@ void Cloth::DestroyCloth()
#endif
}
void Cloth::CalculateInvMasses(Array<float>& invMasses)
{
// Use per-particle max distance to evaluate which particles are immovable
#if WITH_CLOTH
if (_paint.IsEmpty())
return;
// Get mesh data
const ModelInstanceActor::MeshReference mesh = GetMesh();
if (mesh.Actor == nullptr)
return;
BytesContainer verticesData;
int32 verticesCount;
if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Vertex0, verticesData, verticesCount))
return;
BytesContainer indicesData;
int32 indicesCount;
if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Index, indicesData, indicesCount))
return;
const int32 verticesStride = verticesData.Length() / verticesCount;
const bool indices16bit = indicesData.Length() / indicesCount == sizeof(uint16);
const int32 trianglesCount = indicesCount / 3;
// Sum triangle area for each influenced particle
invMasses.Resize(verticesCount);
for (int32 triangleIndex = 0; triangleIndex < trianglesCount; triangleIndex++)
{
const int32 index = triangleIndex * 3;
int32 i0, i1, i2;
if (indices16bit)
{
i0 = indicesData.Get<uint16>()[index];
i1 = indicesData.Get<uint16>()[index + 1];
i2 = indicesData.Get<uint16>()[index + 2];
}
else
{
i0 = indicesData.Get<uint32>()[index];
i1 = indicesData.Get<uint32>()[index + 1];
i2 = indicesData.Get<uint32>()[index + 2];
}
#define GET_POS(i) *(Float3*)((byte*)verticesData.Get() + i * verticesStride)
const Float3 v0(GET_POS(i0));
const Float3 v1(GET_POS(i1));
const Float3 v2(GET_POS(i2));
#undef GET_POS
const float area = Float3::TriangleArea(v0, v1, v2);
invMasses.Get()[i0] += area;
invMasses.Get()[i1] += area;
invMasses.Get()[i2] += area;
}
// Count fixed vertices which max movement distance is zero
int32 fixedCount = 0;
float massSum = 0;
for (int32 i = 0; i < verticesCount; i++)
{
float& mass = invMasses[i];
const float maxDistance = _paint[i];
if (maxDistance < 0.01f)
{
// Fixed
fixedCount++;
mass = 0.0f;
}
else
{
// Kinetic so include it's mass contribution
massSum += mass;
}
}
if (massSum > ZeroTolerance)
{
// Normalize and inverse particles mass
const float massScale = (float)(verticesCount - fixedCount) / massSum;
for (int32 i = 0; i < verticesCount; i++)
{
float& mass = invMasses[i];
if (mass > 0.0f)
{
mass *= massScale;
mass = 1.0f / mass;
}
}
}
#endif
}
void Cloth::OnUpdated()
{
if (_meshDeformation)
@@ -410,7 +673,7 @@ void Cloth::RunClothDeformer(const MeshBase* mesh, MeshDeformationData& deformat
#if WITH_CLOTH
PROFILE_CPU_NAMED("Cloth");
PhysicsBackend::LockClothParticles(_cloth);
const Span<const Float4> particles = PhysicsBackend::GetClothCurrentParticles(_cloth);
const Span<const Float4> particles = PhysicsBackend::GetClothParticles(_cloth);
// Update mesh vertices based on the cloth particles positions
auto vbData = deformation.VertexBuffer.Data.Get();
@@ -478,7 +741,7 @@ void Cloth::RunClothDeformer(const MeshBase* mesh, MeshDeformationData& deformat
{
for (uint32 i = 0; i < vbCount; i++)
{
*((Float3*)vbData) = *(Float3*)&particles.Get()[i];
*(Float3*)vbData = *(Float3*)&particles.Get()[i];
vbData += vbStride;
}
}

View File

@@ -129,6 +129,11 @@ API_CLASS(Attributes="ActorContextMenu(\"New/Physics/Cloth\"), ActorToolbox(\"Ph
/// </summary>
API_FIELD() float SolverFrequency = 300.0f;
/// <summary>
/// The maximum distance cloth particles can move from the original location (within local-space of the actor). Scaled by painted per-particle value (0-1) to restrict movement of certain particles.
/// </summary>
API_FIELD() float MaxDistance = 1000.0f;
/// <summary>
/// Wind velocity vector (direction and magnitude) in world coordinates. A greater magnitude applies a stronger wind force. Ensure that Air Drag and Air Lift coefficients are non-zero in order to apply wind force.
/// </summary>
@@ -202,6 +207,7 @@ private:
Vector3 _cachedPosition = Vector3::Zero;
ModelInstanceActor::MeshReference _mesh;
MeshDeformation* _meshDeformation = nullptr;
Array<float> _paint;
public:
/// <summary>
@@ -283,8 +289,29 @@ public:
/// </summary>
API_FUNCTION() void ClearInteria();
/// <summary>
/// Gets the cloth particles data with per-particle XYZ position (in local cloth-space).
/// </summary>
API_FUNCTION() Array<Float3> GetParticles() const;
/// <summary>
/// Sets the cloth particles data with per-particle XYZ position (in local cloth-space). The size of the input data had to match the cloth size.
/// </summary>
API_FUNCTION() void SetParticles(Span<const Float3> value);
/// <summary>
/// Gets the cloth particles paint data with per-particle max distance (normalized 0-1, 0 makes particle immovable). Returned value is empty if cloth was not initialized or doesn't use paint feature.
/// </summary>
API_FUNCTION() Span<float> GetPaint() const;
/// <summary>
/// Sets the cloth particles paint data with per-particle max distance (normalized 0-1, 0 makes particle immovable). The size of the input data had to match the cloth size. Set to empty to remove paint.
/// </summary>
API_FUNCTION() void SetPaint(Span<const float> value);
public:
// [Actor]
bool IntersectsItself(const Ray& ray, Real& distance, Vector3& normal) override;
void Serialize(SerializeStream& stream, const void* otherObj) override;
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
@@ -307,6 +334,7 @@ private:
#endif
bool CreateCloth();
void DestroyCloth();
void CalculateInvMasses(Array<float>& invMasses);
void OnUpdated();
void RunClothDeformer(const MeshBase* mesh, struct MeshDeformationData& deformation);
};

View File

@@ -129,7 +129,7 @@ public:
}
};
class ProfilerPhysX : public physx::PxProfilerCallback
class ProfilerPhysX : public PxProfilerCallback
{
public:
void* zoneStart(const char* eventName, bool detached, uint64_t contextId) override
@@ -162,6 +162,7 @@ struct ClothSettings
const PxVec3& clothBoundsSize = clothPhysX->getBoundingBoxScale();
BoundingBox localBounds;
BoundingBox::FromPoints(P2C(clothBoundsPos - clothBoundsSize), P2C(clothBoundsPos + clothBoundsSize), localBounds);
CHECK(!localBounds.Minimum.IsNanOrInfinity() && !localBounds.Maximum.IsNanOrInfinity());
// Transform local-space bounds into world-space
const PxTransform clothPose(clothPhysX->getTranslation(), clothPhysX->getRotation());
@@ -3340,12 +3341,14 @@ void* PhysicsBackend::CreateCloth(const PhysicsClothDesc& desc)
meshDesc.points.data = desc.VerticesData;
meshDesc.points.stride = desc.VerticesStride;
meshDesc.points.count = desc.VerticesCount;
meshDesc.invMasses.data = desc.InvMassesData;
meshDesc.invMasses.stride = desc.InvMassesStride;
meshDesc.invMasses.count = desc.InvMassesData ? desc.VerticesCount : 0;
meshDesc.triangles.data = desc.IndicesData;
meshDesc.triangles.stride = desc.IndicesStride * 3;
meshDesc.triangles.count = desc.IndicesCount / 3;
if (desc.IndicesStride == sizeof(uint16))
meshDesc.flags |= nv::cloth::MeshFlag::e16_BIT_INDICES;
// TODO: provide invMasses data
const Float3 gravity(PhysicsSettings::Get()->DefaultGravity);
nv::cloth::Vector<int32_t>::Type phaseTypeInfo;
// TODO: automatically reuse fabric from existing cloths (simply check for input data used for computations to improve perf when duplicating cloths or with prefab)
@@ -3359,10 +3362,17 @@ void* PhysicsBackend::CreateCloth(const PhysicsClothDesc& desc)
// Create cloth object
static_assert(sizeof(Float4) == sizeof(PxVec4), "Size mismatch");
Array<Float4> initialState;
// TODO: provide initial state for cloth from the owner (eg. current skinned mesh position)
initialState.Resize((int32)desc.VerticesCount);
for (uint32 i = 0; i < desc.VerticesCount; i++)
initialState.Get()[i] = Float4(*(Float3*)((byte*)desc.VerticesData + i * desc.VerticesStride), 1.0f); // TODO: set .w to invMass of that vertex
if (desc.InvMassesData)
{
for (uint32 i = 0; i < desc.VerticesCount; i++)
initialState.Get()[i] = Float4(*(Float3*)((byte*)desc.VerticesData + i * desc.VerticesStride), *(float*)((byte*)desc.InvMassesData + i * desc.InvMassesStride));
}
else
{
for (uint32 i = 0; i < desc.VerticesCount; i++)
initialState.Get()[i] = Float4(*(Float3*)((byte*)desc.VerticesData + i * desc.VerticesStride), 1.0f);
}
const nv::cloth::Range<PxVec4> initialParticlesRange((PxVec4*)initialState.Get(), (PxVec4*)initialState.Get() + initialState.Count());
nv::cloth::Cloth* clothPhysX = ClothFactory->createCloth(initialParticlesRange, *fabric);
fabric->decRefCount();
@@ -3371,6 +3381,14 @@ void* PhysicsBackend::CreateCloth(const PhysicsClothDesc& desc)
LOG(Error, "createCloth failed");
return nullptr;
}
if (desc.MaxDistancesData)
{
nv::cloth::Range<PxVec4> motionConstraints = clothPhysX->getMotionConstraints();
ASSERT(motionConstraints.size() == desc.VerticesCount);
for (uint32 i = 0; i < desc.VerticesCount; i++)
motionConstraints.begin()[i] = PxVec4(*(PxVec3*)((byte*)desc.VerticesData + i * desc.VerticesStride), *(float*)((byte*)desc.MaxDistancesData + i * desc.MaxDistancesStride));
}
clothPhysX->setUserData(desc.Actor);
// Setup settings
FabricSettings fabricSettings;
@@ -3438,6 +3456,7 @@ void PhysicsBackend::SetClothSimulationSettings(void* cloth, const void* setting
auto clothPhysX = (nv::cloth::Cloth*)cloth;
const auto& settings = *(const Cloth::SimulationSettings*)settingsPtr;
clothPhysX->setSolverFrequency(settings.SolverFrequency);
clothPhysX->setMotionConstraintScaleBias(settings.MaxDistance, 0.0f);
clothPhysX->setWindVelocity(C2P(settings.WindVelocity));
}
@@ -3510,13 +3529,65 @@ void PhysicsBackend::UnlockClothParticles(void* cloth)
clothPhysX->unlockParticles();
}
Span<const Float4> PhysicsBackend::GetClothCurrentParticles(void* cloth)
Span<const Float4> PhysicsBackend::GetClothParticles(void* cloth)
{
auto clothPhysX = (const nv::cloth::Cloth*)cloth;
const nv::cloth::MappedRange<const PxVec4> range = clothPhysX->getCurrentParticles();
return Span<const Float4>((const Float4*)range.begin(), (int32)range.size());
}
void PhysicsBackend::SetClothParticles(void* cloth, Span<const Float4> value, Span<const Float3> positions, Span<const float> invMasses)
{
auto clothPhysX = (nv::cloth::Cloth*)cloth;
nv::cloth::MappedRange<PxVec4> range = clothPhysX->getCurrentParticles();
const uint32_t size = range.size();
PxVec4* dst = range.begin();
if (value.IsValid())
{
// Set XYZW
CHECK((uint32_t)value.Length() >= size);
Platform::MemoryCopy(dst, value.Get(), size * sizeof(Float4));
}
if (positions.IsValid())
{
// Set XYZ
CHECK((uint32_t)positions.Length() >= size);
const Float3* src = positions.Get();
for (uint32 i = 0; i < size; i++)
dst[i] = PxVec4(C2P(src[i]), dst[i].w);
}
if (invMasses.IsValid())
{
// Set W
CHECK((uint32_t)invMasses.Length() >= size);
const float* src = invMasses.Get();
for (uint32 i = 0; i < size; i++)
dst[i].w = src[i];
// Apply previous particles too
nv::cloth::MappedRange<PxVec4> range2 = clothPhysX->getPreviousParticles();
for (uint32 i = 0; i < size; i++)
range2.begin()[i].w = src[i];
}
}
void PhysicsBackend::SetClothPaint(void* cloth, Span<const float> value)
{
auto clothPhysX = (nv::cloth::Cloth*)cloth;
if (value.IsValid())
{
const nv::cloth::MappedRange<const PxVec4> range = ((const nv::cloth::Cloth*)clothPhysX)->getCurrentParticles();
nv::cloth::Range<PxVec4> motionConstraints = clothPhysX->getMotionConstraints();
ASSERT(motionConstraints.size() <= (uint32)value.Length());
for (int32 i = 0; i < value.Length(); i++)
motionConstraints.begin()[i] = PxVec4(range[i].getXYZ(), value[i]);
}
else
{
clothPhysX->clearMotionConstraints();
}
}
void PhysicsBackend::AddCloth(void* scene, void* cloth)
{
auto scenePhysX = (ScenePhysX*)scene;

View File

@@ -40,10 +40,14 @@ struct PhysicsClothDesc
class Cloth* Actor;
void* VerticesData;
void* IndicesData;
float* InvMassesData;
float* MaxDistancesData;
uint32 VerticesCount;
uint32 VerticesStride;
uint32 IndicesCount;
uint32 IndicesStride;
uint32 InvMassesStride;
uint32 MaxDistancesStride;
};
/// <summary>
@@ -281,7 +285,9 @@ public:
static void ClearClothInertia(void* cloth);
static void LockClothParticles(void* cloth);
static void UnlockClothParticles(void* cloth);
static Span<const Float4> GetClothCurrentParticles(void* cloth);
static Span<const Float4> GetClothParticles(void* cloth);
static void SetClothParticles(void* cloth, Span<const Float4> value, Span<const Float3> positions, Span<const float> invMasses);
static void SetClothPaint(void* cloth, Span<const float> value);
static void AddCloth(void* scene, void* cloth);
static void RemoveCloth(void* scene, void* cloth);
#endif