diff --git a/Source/Editor/CustomEditors/Dedicated/ClothEditor.cs b/Source/Editor/CustomEditors/Dedicated/ClothEditor.cs
new file mode 100644
index 000000000..999631991
--- /dev/null
+++ b/Source/Editor/CustomEditors/Dedicated/ClothEditor.cs
@@ -0,0 +1,77 @@
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+
+using FlaxEditor.Gizmo;
+using FlaxEditor.Scripting;
+using FlaxEngine;
+using FlaxEngine.GUI;
+using FlaxEngine.Tools;
+
+namespace FlaxEditor.CustomEditors.Dedicated
+{
+ ///
+ /// Custom editor for .
+ ///
+ ///
+ [CustomEditor(typeof(Cloth)), DefaultEditor]
+ class ClothEditor : ActorEditor
+ {
+ private ClothPaintingGizmoMode _gizmoMode;
+ private Viewport.Modes.EditorGizmoMode _prevMode;
+
+ ///
+ public override void Initialize(LayoutElementsContainer layout)
+ {
+ base.Initialize(layout);
+
+ if (Values.Count != 1)
+ return;
+
+ // Add gizmo painting mode to the viewport
+ var owner = Presenter.Owner;
+ if (owner == null)
+ return;
+ var gizmoOwner = owner as IGizmoOwner ?? owner.PresenterViewport as IGizmoOwner;
+ if (gizmoOwner == null)
+ return;
+ var gizmos = gizmoOwner.Gizmos;
+ _gizmoMode = new ClothPaintingGizmoMode();
+ gizmos.AddMode(_gizmoMode);
+ _prevMode = gizmos.ActiveMode;
+ gizmos.ActiveMode = _gizmoMode;
+ _gizmoMode.Gizmo.SetPaintCloth((Cloth)Values[0]);
+
+ // Insert gizmo mode options to properties editing
+ var paintGroup = layout.Group("Cloth Painting");
+ var paintValue = new ReadOnlyValueContainer(new ScriptType(typeof(ClothPaintingGizmoMode)), _gizmoMode);
+ paintGroup.Object(paintValue);
+ {
+ var grid = paintGroup.CustomContainer();
+ var gridControl = grid.CustomControl;
+ gridControl.ClipChildren = false;
+ gridControl.Height = Button.DefaultHeight;
+ gridControl.SlotsHorizontally = 2;
+ gridControl.SlotsVertically = 1;
+ grid.Button("Fill", "Fills the cloth particles with given paint value.").Button.Clicked += _gizmoMode.Gizmo.Fill;
+ grid.Button("Reset", "Clears the cloth particles paint.").Button.Clicked += _gizmoMode.Gizmo.Reset;
+ }
+ }
+
+ ///
+ protected override void Deinitialize()
+ {
+ // Cleanup gizmos
+ if (_gizmoMode != null)
+ {
+ var gizmos = _gizmoMode.Owner.Gizmos;
+ if (gizmos.ActiveMode == _gizmoMode)
+ gizmos.ActiveMode = _prevMode;
+ gizmos.RemoveMode(_gizmoMode);
+ _gizmoMode.Dispose();
+ _gizmoMode = null;
+ }
+ _prevMode = null;
+
+ base.Deinitialize();
+ }
+ }
+}
diff --git a/Source/Editor/Gizmo/GizmosCollection.cs b/Source/Editor/Gizmo/GizmosCollection.cs
index beb653826..0df516d32 100644
--- a/Source/Editor/Gizmo/GizmosCollection.cs
+++ b/Source/Editor/Gizmo/GizmosCollection.cs
@@ -75,6 +75,10 @@ namespace FlaxEditor.Gizmo
///
public event Action ActiveModeChanged;
+ ///
+ /// Init.
+ ///
+ /// The gizmos owner interface.
public GizmosCollection(IGizmoOwner owner)
{
_owner = owner;
diff --git a/Source/Editor/Gizmo/IGizmoOwner.cs b/Source/Editor/Gizmo/IGizmoOwner.cs
index 893e9a44a..a28810179 100644
--- a/Source/Editor/Gizmo/IGizmoOwner.cs
+++ b/Source/Editor/Gizmo/IGizmoOwner.cs
@@ -1,5 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+using System.Collections.Generic;
using FlaxEngine;
namespace FlaxEditor.Gizmo
@@ -94,5 +95,11 @@ namespace FlaxEditor.Gizmo
/// Gets the root tree node for the scene graph.
///
SceneGraph.RootNode SceneGraphRoot { get; }
+
+ ///
+ /// Selects the scene objects.
+ ///
+ /// The nodes to select
+ void Select(List nodes);
}
}
diff --git a/Source/Editor/Tools/ClothPainting.cs b/Source/Editor/Tools/ClothPainting.cs
new file mode 100644
index 000000000..2ad3b94a1
--- /dev/null
+++ b/Source/Editor/Tools/ClothPainting.cs
@@ -0,0 +1,359 @@
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using FlaxEditor;
+using FlaxEditor.Gizmo;
+using FlaxEditor.SceneGraph;
+using FlaxEditor.Viewport.Modes;
+
+namespace FlaxEngine.Tools
+{
+ sealed class ClothPaintingGizmoMode : EditorGizmoMode
+ {
+ [HideInEditor]
+ public ClothPaintingGizmo Gizmo;
+
+#pragma warning disable CS0649
+ ///
+ /// Brush radius (world-space).
+ ///
+ public float BrushSize = 50.0f;
+
+ ///
+ /// Brush paint intensity.
+ ///
+ public float BrushStrength = 2.0f;
+
+ ///
+ /// Brush paint falloff. Hardens or softens painting.
+ ///
+ public float BrushFalloff = 1.5f;
+
+ ///
+ /// Value to paint with. Hold Ctrl hey to paint with inverse value (1 - value).
+ ///
+ public float PaintValue = 0.0f;
+
+ ///
+ /// Enables continuous painting, otherwise single paint on click.
+ ///
+ public bool ContinuousPaint;
+#pragma warning restore CS0649
+
+ public override void Init(IGizmoOwner owner)
+ {
+ base.Init(owner);
+
+ Gizmo = new ClothPaintingGizmo(owner, this);
+ }
+
+ public override void OnActivated()
+ {
+ base.OnActivated();
+
+ Owner.Gizmos.Active = Gizmo;
+ }
+ }
+
+ sealed class ClothPaintingGizmo : GizmoBase
+ {
+ private Model _brushModel;
+ private MaterialInstance _brushMaterial;
+ private ClothPaintingGizmoMode _gizmoMode;
+ private bool _isPainting;
+ private int _paintUpdateCount;
+ private bool _hasHit;
+ private Vector3 _hitLocation;
+ private Vector3 _hitNormal;
+ private Cloth _cloth;
+ private Float3[] _clothParticles;
+ private float[] _clothPaint;
+ private EditClothPaintAction _undoAction;
+
+ public bool IsPainting => _isPainting;
+
+ public ClothPaintingGizmo(IGizmoOwner owner, ClothPaintingGizmoMode mode)
+ : base(owner)
+ {
+ _gizmoMode = mode;
+ }
+
+ public void SetPaintCloth(Cloth cloth)
+ {
+ if (_cloth == cloth)
+ return;
+ PaintEnd();
+ _cloth = cloth;
+ _clothParticles = cloth?.GetParticles();
+ _clothPaint = null;
+ _hasHit = false;
+ }
+
+ public void Fill()
+ {
+ PaintEnd();
+ PaintStart();
+ var clothPaint = _clothPaint;
+ var paintValue = Mathf.Saturate(_gizmoMode.PaintValue);
+ for (int i = 0; i < clothPaint.Length; i++)
+ clothPaint[i] = paintValue;
+ _cloth.SetPaint(clothPaint);
+ PaintEnd();
+ }
+
+ public void Reset()
+ {
+ PaintEnd();
+ PaintStart();
+ var clothPaint = _clothPaint;
+ for (int i = 0; i < clothPaint.Length; i++)
+ clothPaint[i] = 1.0f;
+ _cloth.SetPaint(clothPaint);
+ PaintEnd();
+ }
+
+ private void PaintStart()
+ {
+ if (IsPainting)
+ return;
+
+ if (Editor.Instance.Undo.Enabled)
+ _undoAction = new EditClothPaintAction(_cloth);
+ _isPainting = true;
+ _paintUpdateCount = 0;
+
+ // Get initial cloth paint state
+ var clothParticles = _clothParticles;
+ var clothPaint = _cloth.GetPaint();
+ if (clothPaint == null || clothPaint.Length != clothParticles.Length)
+ {
+ _clothPaint = clothPaint = new float[clothParticles.Length];
+ for (int i = 0; i < clothPaint.Length; i++)
+ clothPaint[i] = 1.0f;
+ }
+ _clothPaint = clothPaint;
+ }
+
+ private void PaintUpdate()
+ {
+ if (!_gizmoMode.ContinuousPaint && _paintUpdateCount > 0)
+ return;
+ Profiler.BeginEvent("Cloth Paint");
+
+ // Edit the cloth paint
+ var clothParticles = _clothParticles;
+ var clothPaint = _clothPaint;
+ if (clothParticles == null || clothPaint == null)
+ throw new Exception();
+ var instanceTransform = _cloth.Transform;
+ var brushSphere = new BoundingSphere(_hitLocation, _gizmoMode.BrushSize);
+ var paintValue = Mathf.Saturate(_gizmoMode.PaintValue);
+ if (Owner.IsControlDown)
+ paintValue = 1.0f - paintValue;
+ var modified = false;
+ for (int i = 0; i < clothParticles.Length; i++)
+ {
+ var pos = instanceTransform.LocalToWorld(clothParticles[i]);
+ var dst = Vector3.Distance(ref pos, ref brushSphere.Center);
+ if (dst > brushSphere.Radius)
+ continue;
+ float strength = _gizmoMode.BrushStrength * Mathf.Lerp(1.0f, 1.0f - (float)dst / (float)brushSphere.Radius, _gizmoMode.BrushFalloff);
+ if (strength > Mathf.Epsilon)
+ {
+ // Paint the particle
+ ref var paint = ref clothPaint[i];
+ paint = Mathf.Saturate(Mathf.Lerp(paint, paintValue, Mathf.Saturate(strength)));
+ modified = true;
+ }
+ }
+ _paintUpdateCount++;
+ if (modified)
+ {
+ // Update cloth particles state
+ _cloth.SetPaint(clothPaint);
+ }
+
+ Profiler.EndEvent();
+ }
+
+ private void PaintEnd()
+ {
+ if (!IsPainting)
+ return;
+
+ if (_undoAction != null)
+ {
+ _undoAction.RecordEnd();
+ Editor.Instance.Undo.AddAction(_undoAction);
+ _undoAction = null;
+ }
+ _isPainting = false;
+ _paintUpdateCount = 0;
+ _clothPaint = null;
+ }
+
+ public override bool IsControllingMouse => IsPainting;
+
+ public override BoundingSphere FocusBounds => _cloth?.Sphere ?? base.FocusBounds;
+
+ public override void Update(float dt)
+ {
+ _hasHit = false;
+ if (!IsActive)
+ {
+ SetPaintCloth(null);
+ return;
+ }
+ var cloth = _cloth;
+ if (cloth == null)
+ return;
+
+ // Perform detailed tracing to find cursor location for the brush
+ var ray = Owner.MouseRay;
+ if (cloth.IntersectsItself(ray, out var closest, out var hitNormal))
+ {
+ // Cursor hit cloth
+ _hasHit = true;
+ _hitLocation = ray.GetPoint(closest);
+ _hitNormal = hitNormal;
+ }
+ else
+ {
+ // Cursor hit other object or nothing
+ PaintEnd();
+ return;
+ }
+
+ // Handle painting
+ if (Owner.IsLeftMouseButtonDown)
+ PaintStart();
+ else
+ PaintEnd();
+ if (IsPainting)
+ PaintUpdate();
+ }
+
+ public override void Pick()
+ {
+ var ray = Owner.MouseRay;
+ var view = new Ray(Owner.ViewPosition, Owner.ViewDirection);
+ var rayCastFlags = SceneGraphNode.RayCastData.FlagTypes.SkipColliders | SceneGraphNode.RayCastData.FlagTypes.SkipEditorPrimitives;
+ var hit = Owner.SceneGraphRoot.RayCast(ref ray, ref view, out _, rayCastFlags);
+ if (hit != null && hit is ActorNode)
+ Owner.Select(new List { hit });
+ }
+
+ public override void Draw(ref RenderContext renderContext)
+ {
+ if (!IsActive || !_cloth)
+ return;
+
+ base.Draw(ref renderContext);
+
+ // TODO: impl this
+ if (_hasHit)
+ {
+ var viewOrigin = renderContext.View.Origin;
+
+ // Draw paint brush
+ if (!_brushModel)
+ _brushModel = Content.LoadAsyncInternal("Editor/Primitives/Sphere");
+ if (!_brushMaterial)
+ _brushMaterial = Content.LoadAsyncInternal(EditorAssets.FoliageBrushMaterial)?.CreateVirtualInstance();
+ if (_brushModel && _brushMaterial)
+ {
+ _brushMaterial.SetParameterValue("Color", new Color(1.0f, 0.85f, 0.0f)); // TODO: expose to editor options
+ _brushMaterial.SetParameterValue("DepthBuffer", Owner.RenderTask.Buffers.DepthBuffer);
+ Quaternion rotation = RootNode.RaycastNormalRotation(ref _hitNormal);
+ Matrix transform = Matrix.Scaling(_gizmoMode.BrushSize * 0.01f) * Matrix.RotationQuaternion(rotation) * Matrix.Translation(_hitLocation - viewOrigin);
+ _brushModel.Draw(ref renderContext, _brushMaterial, ref transform);
+ }
+ }
+ }
+
+ public override void OnActivated()
+ {
+ base.OnActivated();
+
+ _hasHit = false;
+ }
+
+ public override void OnDeactivated()
+ {
+ base.OnDeactivated();
+
+ PaintEnd();
+ SetPaintCloth(null);
+ Object.Destroy(ref _brushMaterial);
+ _brushModel = null;
+ }
+ }
+
+ sealed class EditClothPaintAction : IUndoAction
+ {
+ private Guid _actorId;
+ private string _before, _after;
+
+ public EditClothPaintAction(Cloth cloth)
+ {
+ _actorId = cloth.ID;
+ _before = GetState(cloth);
+ }
+
+ public static bool IsValidState(string state)
+ {
+ return state != null && state.Contains("\"Paint\":");
+ }
+
+ public static string GetState(Cloth cloth)
+ {
+ var json = cloth.ToJson();
+ var start = json.IndexOf("\"Paint\":");
+ if (start == -1)
+ return null;
+ var end = json.IndexOf('\"', json.IndexOf('\"', start + 8) + 1);
+ json = "{" + json.Substring(start, end - start) + "\"}";
+ return json;
+ }
+
+ public static void SetState(Cloth cloth, string state)
+ {
+ if (state == null)
+ cloth.SetPaint(null);
+ else
+ Editor.Internal_DeserializeSceneObject(Object.GetUnmanagedPtr(cloth), state);
+ }
+
+ public void RecordEnd()
+ {
+ var cloth = Object.Find(ref _actorId);
+ _after = GetState(cloth);
+ Editor.Instance.Scene.MarkSceneEdited(cloth.Scene);
+ }
+
+ private void Set(string state)
+ {
+ var cloth = Object.Find(ref _actorId);
+ SetState(cloth, state);
+ Editor.Instance.Scene.MarkSceneEdited(cloth.Scene);
+ }
+
+ public string ActionString => "Edit Cloth Paint";
+
+ public void Do()
+ {
+ Set(_after);
+ }
+
+ public void Undo()
+ {
+ Set(_before);
+ }
+
+ public void Dispose()
+ {
+ _before = _after = null;
+ }
+ }
+}
diff --git a/Source/Editor/Tools/VertexPainting.cs b/Source/Editor/Tools/VertexPainting.cs
index bb6b2c716..30bb0c84f 100644
--- a/Source/Editor/Tools/VertexPainting.cs
+++ b/Source/Editor/Tools/VertexPainting.cs
@@ -1,6 +1,7 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
+using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using FlaxEditor.CustomEditors;
@@ -596,18 +597,12 @@ namespace FlaxEditor.Tools
///
public override void Pick()
{
- // Get mouse ray and try to hit any object
var ray = Owner.MouseRay;
var view = new Ray(Owner.ViewPosition, Owner.ViewDirection);
var rayCastFlags = SceneGraphNode.RayCastData.FlagTypes.SkipColliders | SceneGraphNode.RayCastData.FlagTypes.SkipEditorPrimitives;
- var hit = Editor.Instance.Scene.Root.RayCast(ref ray, ref view, out _, rayCastFlags);
-
- // Update selection
- var sceneEditing = Editor.Instance.SceneEditing;
+ var hit = Owner.SceneGraphRoot.RayCast(ref ray, ref view, out _, rayCastFlags);
if (hit != null && hit is ActorNode actorNode && actorNode.Actor is StaticModel model)
- {
- sceneEditing.Select(hit);
- }
+ Owner.Select(new List { hit });
}
///
diff --git a/Source/Editor/Viewport/EditorGizmoViewport.cs b/Source/Editor/Viewport/EditorGizmoViewport.cs
index 2a99080ed..9a4fd34a2 100644
--- a/Source/Editor/Viewport/EditorGizmoViewport.cs
+++ b/Source/Editor/Viewport/EditorGizmoViewport.cs
@@ -1,6 +1,8 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+using System.Collections.Generic;
using FlaxEditor.Gizmo;
+using FlaxEditor.SceneGraph;
using FlaxEditor.Viewport.Cameras;
using FlaxEngine;
using FlaxEngine.GUI;
@@ -12,7 +14,7 @@ namespace FlaxEditor.Viewport
///
///
///
- public class EditorGizmoViewport : EditorViewport, IGizmoOwner
+ public abstract class EditorGizmoViewport : EditorViewport, IGizmoOwner
{
private UpdateDelegate _update;
@@ -79,6 +81,9 @@ namespace FlaxEditor.Viewport
///
public SceneGraph.RootNode SceneGraphRoot { get; }
+ ///
+ public abstract void Select(List nodes);
+
///
protected override bool IsControllingMouse => Gizmos.Active?.IsControllingMouse ?? false;
diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs
index 77032ff80..9591915ab 100644
--- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs
+++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs
@@ -1150,6 +1150,12 @@ namespace FlaxEditor.Viewport
return result;
}
+ ///
+ public override void Select(List nodes)
+ {
+ _editor.SceneEditing.Select(nodes);
+ }
+
///
public override void OnDestroy()
{
diff --git a/Source/Editor/Viewport/PrefabWindowViewport.cs b/Source/Editor/Viewport/PrefabWindowViewport.cs
index e8289d214..99d7cb8e6 100644
--- a/Source/Editor/Viewport/PrefabWindowViewport.cs
+++ b/Source/Editor/Viewport/PrefabWindowViewport.cs
@@ -301,7 +301,7 @@ namespace FlaxEditor.Viewport
///
public void ShowSelectedActors()
{
- var orient = Viewport.ViewOrientation;
+ var orient = ViewOrientation;
((FPSCamera)ViewportCamera).ShowActors(TransformGizmo.SelectedParents, ref orient);
}
@@ -345,7 +345,10 @@ namespace FlaxEditor.Viewport
public RootNode SceneGraphRoot => _window.Graph.Root;
///
- public EditorViewport Viewport => this;
+ public void Select(List nodes)
+ {
+ _window.Select(nodes);
+ }
///
protected override bool IsControllingMouse => Gizmos.Active?.IsControllingMouse ?? false;
diff --git a/Source/Engine/Content/Assets/VisualScript.cpp b/Source/Engine/Content/Assets/VisualScript.cpp
index e292a0133..0f1c73e2c 100644
--- a/Source/Engine/Content/Assets/VisualScript.cpp
+++ b/Source/Engine/Content/Assets/VisualScript.cpp
@@ -2209,14 +2209,14 @@ void VisualScript::GetMethodSignature(int32 index, String& name, byte& flags, St
Span VisualScript::GetMetaData(int32 typeID)
{
auto meta = Graph.Meta.GetEntry(typeID);
- return meta ? ToSpan(meta->Data.Get(), meta->Data.Count()) : Span(nullptr, 0);
+ return meta ? ToSpan(meta->Data) : Span(nullptr, 0);
}
Span VisualScript::GetMethodMetaData(int32 index, int32 typeID)
{
auto& method = _methods[index];
auto meta = method.Node->Meta.GetEntry(typeID);
- return meta ? ToSpan(meta->Data.Get(), meta->Data.Count()) : Span(nullptr, 0);
+ return meta ? ToSpan(meta->Data) : Span(nullptr, 0);
}
#endif
diff --git a/Source/Engine/Core/Math/Vector3.cpp b/Source/Engine/Core/Math/Vector3.cpp
index d73cbd6d5..1bf03c2ef 100644
--- a/Source/Engine/Core/Math/Vector3.cpp
+++ b/Source/Engine/Core/Math/Vector3.cpp
@@ -312,7 +312,7 @@ void Float3::FindBestAxisVectors(Float3& firstAxis, Float3& secondAxis) const
template<>
float Float3::TriangleArea(const Float3& v0, const Float3& v1, const Float3& v2)
{
- return (v2 - v0 ^ v1 - v0).Length() * 0.5f;
+ return ((v2 - v0) ^ (v1 - v0)).Length() * 0.5f;
}
template<>
@@ -626,7 +626,7 @@ void Double3::FindBestAxisVectors(Double3& firstAxis, Double3& secondAxis) const
template<>
double Double3::TriangleArea(const Double3& v0, const Double3& v1, const Double3& v2)
{
- return (v2 - v0 ^ v1 - v0).Length() * 0.5;
+ return ((v2 - v0) ^ (v1 - v0)).Length() * 0.5;
}
template<>
diff --git a/Source/Engine/Core/Types/Span.h b/Source/Engine/Core/Types/Span.h
index e7bea5fbd..e0518ec77 100644
--- a/Source/Engine/Core/Types/Span.h
+++ b/Source/Engine/Core/Types/Span.h
@@ -115,6 +115,12 @@ inline Span ToSpan(const T* ptr, int32 length)
return Span(ptr, length);
}
+template
+inline Span ToSpan(const Array& data)
+{
+ return Span((U*)data.Get(), data.Count());
+}
+
template
inline bool SpanContains(const Span span, const T& value)
{
diff --git a/Source/Engine/Physics/Actors/Cloth.cpp b/Source/Engine/Physics/Actors/Cloth.cpp
index a16f5b8af..5bf8d264e 100644
--- a/Source/Engine/Physics/Actors/Cloth.cpp
+++ b/Source/Engine/Physics/Actors/Cloth.cpp
@@ -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 Cloth::GetParticles() const
+{
+ Array result;
+#if WITH_CLOTH
+ if (_cloth)
+ {
+ PROFILE_CPU();
+ PhysicsBackend::LockClothParticles(_cloth);
+ const Span 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 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(), value, Span());
+ PhysicsBackend::UnlockClothParticles(_cloth);
+ }
+#endif
+}
+
+Span Cloth::GetPaint() const
+{
+ return ToSpan(_paint);
+}
+
+void Cloth::SetPaint(Span 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 invMasses;
+ CalculateInvMasses(invMasses);
+ PhysicsBackend::LockClothParticles(_cloth);
+ PhysicsBackend::SetClothParticles(_cloth, Span(), Span(), ToSpan(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 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()[index];
+ i1 = indicesData.Get()[index + 1];
+ i2 = indicesData.Get()[index + 2];
+ }
+ else
+ {
+ i0 = indicesData.Get()[index];
+ i1 = indicesData.Get()[index + 1];
+ i2 = indicesData.Get()[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 particles = PhysicsBackend::GetClothCurrentParticles(_cloth);
+ const Span 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 particles = PhysicsBackend::GetClothCurrentParticles(_cloth);
+ const Span 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(this);
@@ -347,6 +515,12 @@ bool Cloth::CreateCloth()
desc.IndicesData = data.Get();
desc.IndicesCount = count;
desc.IndicesStride = data.Length() / count;
+ Array 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& 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()[index];
+ i1 = indicesData.Get()[index + 1];
+ i2 = indicesData.Get()[index + 2];
+ }
+ else
+ {
+ i0 = indicesData.Get()[index];
+ i1 = indicesData.Get()[index + 1];
+ i2 = indicesData.Get()[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 particles = PhysicsBackend::GetClothCurrentParticles(_cloth);
+ const Span 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;
}
}
diff --git a/Source/Engine/Physics/Actors/Cloth.h b/Source/Engine/Physics/Actors/Cloth.h
index dcc6544a8..ac87bf5ee 100644
--- a/Source/Engine/Physics/Actors/Cloth.h
+++ b/Source/Engine/Physics/Actors/Cloth.h
@@ -129,6 +129,11 @@ API_CLASS(Attributes="ActorContextMenu(\"New/Physics/Cloth\"), ActorToolbox(\"Ph
///
API_FIELD() float SolverFrequency = 300.0f;
+ ///
+ /// 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.
+ ///
+ API_FIELD() float MaxDistance = 1000.0f;
+
///
/// 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.
///
@@ -202,6 +207,7 @@ private:
Vector3 _cachedPosition = Vector3::Zero;
ModelInstanceActor::MeshReference _mesh;
MeshDeformation* _meshDeformation = nullptr;
+ Array _paint;
public:
///
@@ -283,8 +289,29 @@ public:
///
API_FUNCTION() void ClearInteria();
+ ///
+ /// Gets the cloth particles data with per-particle XYZ position (in local cloth-space).
+ ///
+ API_FUNCTION() Array GetParticles() const;
+
+ ///
+ /// 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.
+ ///
+ API_FUNCTION() void SetParticles(Span value);
+
+ ///
+ /// 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.
+ ///
+ API_FUNCTION() Span GetPaint() const;
+
+ ///
+ /// 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.
+ ///
+ API_FUNCTION() void SetPaint(Span 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& invMasses);
void OnUpdated();
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 57ef4ce17..da393f529 100644
--- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp
+++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp
@@ -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::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 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 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 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 PhysicsBackend::GetClothCurrentParticles(void* cloth)
+Span PhysicsBackend::GetClothParticles(void* cloth)
{
auto clothPhysX = (const nv::cloth::Cloth*)cloth;
const nv::cloth::MappedRange range = clothPhysX->getCurrentParticles();
return Span((const Float4*)range.begin(), (int32)range.size());
}
+void PhysicsBackend::SetClothParticles(void* cloth, Span value, Span positions, Span invMasses)
+{
+ auto clothPhysX = (nv::cloth::Cloth*)cloth;
+ nv::cloth::MappedRange 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 range2 = clothPhysX->getPreviousParticles();
+ for (uint32 i = 0; i < size; i++)
+ range2.begin()[i].w = src[i];
+ }
+}
+
+void PhysicsBackend::SetClothPaint(void* cloth, Span value)
+{
+ auto clothPhysX = (nv::cloth::Cloth*)cloth;
+ if (value.IsValid())
+ {
+ const nv::cloth::MappedRange range = ((const nv::cloth::Cloth*)clothPhysX)->getCurrentParticles();
+ nv::cloth::Range 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;
diff --git a/Source/Engine/Physics/PhysicsBackend.h b/Source/Engine/Physics/PhysicsBackend.h
index 5e8743d69..2de719183 100644
--- a/Source/Engine/Physics/PhysicsBackend.h
+++ b/Source/Engine/Physics/PhysicsBackend.h
@@ -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;
};
///
@@ -281,7 +285,9 @@ public:
static void ClearClothInertia(void* cloth);
static void LockClothParticles(void* cloth);
static void UnlockClothParticles(void* cloth);
- static Span GetClothCurrentParticles(void* cloth);
+ static Span GetClothParticles(void* cloth);
+ static void SetClothParticles(void* cloth, Span value, Span positions, Span invMasses);
+ static void SetClothPaint(void* cloth, Span value);
static void AddCloth(void* scene, void* cloth);
static void RemoveCloth(void* scene, void* cloth);
#endif