rj-action-library/Runtime/Procedural/Parametric/Deformer/Deformer.cs

212 lines
4.8 KiB
C#

using Godot;
using Rokojori;
using System.Collections.Generic;
namespace Rokojori
{
[Tool]
[GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Spline.svg") ]
public partial class Deformer : Node3D
#if TOOLS
, GizmoDrawer
#endif
{
public class MappingData
{
public Vector3 localPosition;
public float parameter;
public float weight;
}
public enum WeightsMappingMethod
{
Max_Distance
}
[Export]
public Spline[] sourceSplines;
[Export]
public MeshInstance3D sourceMesh;
[Export]
public bool updateSourceMesh;
[Export]
public Spline[] deformerSplines;
[Export]
public MeshInstance3D outputMesh;
[Export( PropertyHint.Range, "0,1")]
public float targetSmoothing = 0f;
[Export]
public float splineMaxDistance = 1000f;
[Export]
public float splineMinDistance = 0f;
[Export]
public int splineMappingResolution = 40;
[Export]
public int splineMappingDepth = 3;
[Export]
public bool update = false;
[Export]
public bool updateAlways = false;
MappingData[] deformerMappings;
MeshGeometry meshGeometry;
MappingData CreateSourceMapping( Spline s, Vector3 worldPosition )
{
var curve = s.GetCurve();
var closestParameter = curve.GetClosestParameterTo( worldPosition, splineMappingResolution, splineMappingDepth );
var pose = curve.GetPoseByPointIndex( closestParameter );
var localPosition = pose.ApplyInverse( worldPosition );
var mappingData = new MappingData();
mappingData.localPosition = localPosition;
mappingData.parameter = closestParameter;
mappingData.weight = 0;
return mappingData;
}
void CreateSourceMappings()
{
meshGeometry = MeshGeometry.From( sourceMesh );
var mappingSize = meshGeometry.vertices.Count * sourceSplines.Length;
if ( deformerMappings == null || deformerMappings.Length != mappingSize )
{
deformerMappings = new MappingData[ mappingSize];
}
RJLog.Log( "Mappings:", deformerMappings.Length, meshGeometry );
for ( int i = 0; i < meshGeometry.vertices.Count; i++ )
{
var weights = 0f;
for ( int j = 0; j < sourceSplines.Length; j++ )
{
var vertex = meshGeometry.vertices[ i ];
var mapping = CreateSourceMapping( sourceSplines[ j ], vertex );
var curve = sourceSplines[ j ].GetCurve();
var distance = curve.PositionAt( mapping.parameter ) - vertex;
var inverseWeight = MathX.NormalizeClamped( distance.Length(), splineMinDistance, splineMaxDistance );
mapping.weight = 1f - inverseWeight;
weights += mapping.weight;
deformerMappings[ i * sourceSplines.Length + j ] = mapping;
}
if ( weights > 0 && weights != 1f )
{
for ( int j = 0; j < sourceSplines.Length; j++ )
{
var mapping = deformerMappings[ i * sourceSplines.Length + j ];
mapping.weight /= weights;
}
}
}
}
void CreateDeformed()
{
// RJLog.Log( "CreateDeformed" );
var cloned = meshGeometry.Clone();
for ( int i = 0; i < cloned.vertices.Count; i++ )
{
var vertexPosition = Vector3.Zero;
for ( int j = 0; j < deformerSplines.Length; j++ )
{
var mapping = deformerMappings[ i * deformerSplines.Length + j ];
var curve = deformerSplines[ j ].GetCurve();
if ( targetSmoothing > 0 )
{
var pose = curve.SmoothedPoseAt( mapping.parameter, targetSmoothing * 0.5f, 2, targetSmoothing );
vertexPosition += pose.Apply( mapping.localPosition ) * mapping.weight;
}
else
{
var pose = curve.PoseAt( mapping.parameter );
pose.ApplyTwist( curve.TwistAt( mapping.parameter ) );
vertexPosition += pose.Apply( mapping.localPosition ) * mapping.weight;
}
}
cloned.vertices[ i ] = vertexPosition;
}
// RJLog.Log( cloned.vertices.Count );
outputMesh.Mesh = cloned.GenerateMesh();
}
#if TOOLS
public void DrawGizmo( EditorNode3DGizmoPlugin gizmoPlugin, EditorNode3DGizmo gizmo )
{
gizmo.Clear();
}
public override void _Process( double delta )
{
if ( updateSourceMesh )
{
updateSourceMesh = false;
CreateSourceMappings();
}
if ( update || updateAlways )
{
update = false;
UpdateMesh();
}
UpdateGizmos();
}
void UpdateMesh()
{
if ( meshGeometry == null )
{
CreateSourceMappings();
}
if ( meshGeometry == null )
{
return;
}
CreateDeformed();
}
#endif
}
}