Add cloth painting tools to Editor
This commit is contained in:
77
Source/Editor/CustomEditors/Dedicated/ClothEditor.cs
Normal file
77
Source/Editor/CustomEditors/Dedicated/ClothEditor.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom editor for <see cref="Cloth"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="ActorEditor" />
|
||||
[CustomEditor(typeof(Cloth)), DefaultEditor]
|
||||
class ClothEditor : ActorEditor
|
||||
{
|
||||
private ClothPaintingGizmoMode _gizmoMode;
|
||||
private Viewport.Modes.EditorGizmoMode _prevMode;
|
||||
|
||||
/// <inheritdoc />
|
||||
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<UniformGridPanel>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,6 +75,10 @@ namespace FlaxEditor.Gizmo
|
||||
/// </summary>
|
||||
public event Action<EditorGizmoMode> ActiveModeChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Init.
|
||||
/// </summary>
|
||||
/// <param name="owner">The gizmos owner interface.</param>
|
||||
public GizmosCollection(IGizmoOwner owner)
|
||||
{
|
||||
_owner = owner;
|
||||
|
||||
@@ -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.
|
||||
/// </summary>
|
||||
SceneGraph.RootNode SceneGraphRoot { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Selects the scene objects.
|
||||
/// </summary>
|
||||
/// <param name="nodes">The nodes to select</param>
|
||||
void Select(List<SceneGraph.SceneGraphNode> nodes);
|
||||
}
|
||||
}
|
||||
|
||||
359
Source/Editor/Tools/ClothPainting.cs
Normal file
359
Source/Editor/Tools/ClothPainting.cs
Normal file
@@ -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
|
||||
/// <summary>
|
||||
/// Brush radius (world-space).
|
||||
/// </summary>
|
||||
public float BrushSize = 50.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Brush paint intensity.
|
||||
/// </summary>
|
||||
public float BrushStrength = 2.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Brush paint falloff. Hardens or softens painting.
|
||||
/// </summary>
|
||||
public float BrushFalloff = 1.5f;
|
||||
|
||||
/// <summary>
|
||||
/// Value to paint with. Hold Ctrl hey to paint with inverse value (1 - value).
|
||||
/// </summary>
|
||||
public float PaintValue = 0.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Enables continuous painting, otherwise single paint on click.
|
||||
/// </summary>
|
||||
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<SceneGraphNode> { 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<Model>("Editor/Primitives/Sphere");
|
||||
if (!_brushMaterial)
|
||||
_brushMaterial = Content.LoadAsyncInternal<Material>(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<Cloth>(ref _actorId);
|
||||
_after = GetState(cloth);
|
||||
Editor.Instance.Scene.MarkSceneEdited(cloth.Scene);
|
||||
}
|
||||
|
||||
private void Set(string state)
|
||||
{
|
||||
var cloth = Object.Find<Cloth>(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
/// <inheritdoc />
|
||||
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<SceneGraphNode> { hit });
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -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
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.Viewport.EditorViewport" />
|
||||
/// <seealso cref="IGizmoOwner" />
|
||||
public class EditorGizmoViewport : EditorViewport, IGizmoOwner
|
||||
public abstract class EditorGizmoViewport : EditorViewport, IGizmoOwner
|
||||
{
|
||||
private UpdateDelegate _update;
|
||||
|
||||
@@ -79,6 +81,9 @@ namespace FlaxEditor.Viewport
|
||||
/// <inheritdoc />
|
||||
public SceneGraph.RootNode SceneGraphRoot { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract void Select(List<SceneGraphNode> nodes);
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool IsControllingMouse => Gizmos.Active?.IsControllingMouse ?? false;
|
||||
|
||||
|
||||
@@ -1150,6 +1150,12 @@ namespace FlaxEditor.Viewport
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Select(List<SceneGraphNode> nodes)
|
||||
{
|
||||
_editor.SceneEditing.Select(nodes);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
|
||||
@@ -301,7 +301,7 @@ namespace FlaxEditor.Viewport
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <inheritdoc />
|
||||
public EditorViewport Viewport => this;
|
||||
public void Select(List<SceneGraphNode> nodes)
|
||||
{
|
||||
_window.Select(nodes);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool IsControllingMouse => Gizmos.Active?.IsControllingMouse ?? false;
|
||||
|
||||
@@ -2209,14 +2209,14 @@ void VisualScript::GetMethodSignature(int32 index, String& name, byte& flags, St
|
||||
Span<byte> VisualScript::GetMetaData(int32 typeID)
|
||||
{
|
||||
auto meta = Graph.Meta.GetEntry(typeID);
|
||||
return meta ? ToSpan(meta->Data.Get(), meta->Data.Count()) : Span<byte>(nullptr, 0);
|
||||
return meta ? ToSpan(meta->Data) : Span<byte>(nullptr, 0);
|
||||
}
|
||||
|
||||
Span<byte> 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<byte>(nullptr, 0);
|
||||
return meta ? ToSpan(meta->Data) : Span<byte>(nullptr, 0);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -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<>
|
||||
|
||||
@@ -115,6 +115,12 @@ inline Span<T> ToSpan(const T* ptr, int32 length)
|
||||
return Span<T>(ptr, length);
|
||||
}
|
||||
|
||||
template<typename T, typename U = T, typename AllocationType = HeapAllocation>
|
||||
inline Span<U> ToSpan(const Array<T, AllocationType>& data)
|
||||
{
|
||||
return Span<U>((U*)data.Get(), data.Count());
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline bool SpanContains(const Span<T> span, const T& value)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user