Deformer Normals Update + Smoothing

This commit is contained in:
Josef 2025-01-13 19:11:12 +01:00
parent acfed9eda6
commit 3ca59f8dab
12 changed files with 458 additions and 12 deletions

View File

@ -19,7 +19,6 @@ namespace Rokojori
_OnTrigger();
}
protected override void _OnTrigger()
{
if ( source == null || target == null )

View File

@ -0,0 +1,67 @@
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Text;
using Godot;
namespace Rokojori
{
[Tool]
[GlobalClass]
public partial class Follow:Node
{
[Export]
public Node3D source;
[Export]
public Node3D target;
[Export]
public Smoothing positionSmoothing;
[Export]
public TimeLine timeline;
float _lastCoefficient = 0;
int _lastFrames = -1;
public override void _Process( double delta )
{
if ( positionSmoothing != null && positionSmoothing is ExpSmoothing exp )
{
if ( _lastCoefficient != exp.coefficient )
{
_lastCoefficient = exp.coefficient;
var duration = exp.ComputeDuration();
var framesOn60hz = duration / ( 1f / 60f );
this.LogInfo( "Duration:", duration._FFF(), "seconds or", framesOn60hz._FF(), "frames", "For coefficient:", _lastCoefficient );
}
}
if ( positionSmoothing != null && positionSmoothing is FrameSmoothing fra )
{
if ( _lastFrames != fra.frames )
{
_lastFrames = fra.frames;
var coefficient = fra.GetCoefficientForFrames( fra.frames );
var framesOn60hz = fra.frames * ( 1f / 60f );
this.LogInfo( "Frames:", fra.frames, "Seconds", framesOn60hz._FF(), "Coefficient:", coefficient );
}
}
if ( source == null || target == null || Engine.IsEditorHint() )
{
return;
}
var tl = TimeLine.IfNull_ReplaceByGameTime( timeline );
target.GlobalPosition = Smoothing.Apply( positionSmoothing, source.GlobalPosition, tl.delta );
}
}
}

View File

@ -0,0 +1,26 @@
using System.Collections;
using System.Collections.Generic;
using Godot;
namespace Rokojori
{
[Tool]
[GlobalClass]
public partial class ExpSmoothing: Smoothing
{
[Export( PropertyHint.Range, "0,200")]
public float coefficient = 1;
protected override float _ComputeInterpolationAmount( float delta )
{
return 1f - Mathf.Exp( -coefficient * delta );
}
public static float GetDurationForCoefficient( float coefficient )
{
var exp = new ExpSmoothing();
exp.coefficient = coefficient;
return exp.ComputeDuration();
}
}
}

View File

@ -0,0 +1,66 @@
using System.Collections;
using System.Collections.Generic;
using Godot;
namespace Rokojori
{
[Tool]
[GlobalClass]
public partial class FrameSmoothing: Smoothing
{
[Export( PropertyHint.Range, "0,600")]
public int frames = 10;
static Dictionary<int,float> _framesToCoefficient = new Dictionary<int, float>();
protected override float _ComputeInterpolationAmount( float delta )
{
if ( frames <= 0 )
{
return 1;
}
var coefficient = GetCoefficientForFrames( frames );
return 1f - Mathf.Exp( -coefficient * delta );
}
public float GetCoefficientForFrames( int frames )
{
if ( ! _framesToCoefficient.ContainsKey( frames ) )
{
_framesToCoefficient[ frames ] = ComputeCoefficientForFrames( frames );
}
return _framesToCoefficient[ frames ];
}
static float ComputeCoefficientForFrames( int numFrames, float treshold = 1f/60f * 0.1f )
{
var framesDuration = numFrames * 1/60f;
var minMaxSearch = new MinMaxSearch<float>(
( a, b, t ) => Mathf.Lerp( a, b, t ),
( c ) => ExpSmoothing.GetDurationForCoefficient( c )
);
var lowDurationCoefficient = 100000f;
var highDurationCoefficient = 0.1f;
RJLog.Log( "Finding coefficient for frames", numFrames, ">>", framesDuration, "s" );
var coefficient = minMaxSearch.Find( framesDuration, lowDurationCoefficient, highDurationCoefficient, treshold );
RJLog.Log( "Found coefficient:", coefficient, ">> duration", ExpSmoothing.GetDurationForCoefficient( coefficient )._FF(), "s" );
return coefficient;
}
}
}

View File

@ -0,0 +1,129 @@
using System.Collections;
using System.Collections.Generic;
using Godot;
namespace Rokojori
{
[Tool]
[GlobalClass]
public partial class Smoothing: Resource
{
float _currentFloat = 0;
Vector2 _currentVector2 = Vector2.Zero;
Vector3 _currentVector3 = Vector3.Zero;
Vector4 _currentVector4 = Vector4.Zero;
Quaternion _currentQuaternion = Quaternion.Identity;
Color _currentColor = Colors.Black;
public float Smooth( float nextValue, float delta )
{
_currentFloat = Mathf.Lerp( _currentFloat, nextValue, _ComputeInterpolationAmount( delta ) );
return _currentFloat;
}
public static float Apply( Smoothing sm, float value, float delta )
{
if ( sm == null ){ return value; }
return sm.Smooth( value, delta );
}
public Vector2 Smooth( Vector2 nextValue, float delta )
{
_currentVector2 = _currentVector2.Lerp( nextValue, _ComputeInterpolationAmount( delta ) );
return _currentVector2;
}
public static Vector2 Apply( Smoothing sm, Vector2 value, float delta )
{
if ( sm == null ){ return value; }
return sm.Smooth( value, delta );
}
public Vector3 Smooth( Vector3 nextValue, float delta )
{
_currentVector3 = _currentVector3.Lerp( nextValue, _ComputeInterpolationAmount( delta ) );
return _currentVector3;
}
public static Vector3 Apply( Smoothing sm, Vector3 value, float delta )
{
if ( sm == null ){ return value; }
return sm.Smooth( value, delta );
}
public Vector4 Smooth( Vector4 nextValue, float delta )
{
_currentVector4 = _currentVector4.Lerp( nextValue, _ComputeInterpolationAmount( delta ) );
return _currentVector4;
}
public static Vector4 Apply( Smoothing sm, Vector4 value, float delta )
{
if ( sm == null ){ return value; }
return sm.Smooth( value, delta );
}
public Quaternion Smooth( Quaternion nextValue, float delta )
{
_currentQuaternion = _currentQuaternion.Slerp( nextValue, _ComputeInterpolationAmount( delta ) );
return _currentQuaternion;
}
public static Quaternion Apply( Smoothing sm, Quaternion value, float delta )
{
if ( sm == null ){ return value; }
return sm.Smooth( value, delta );
}
public Color Smooth( Color nextValue, float delta )
{
_currentColor = _currentColor.Lerp( nextValue, _ComputeInterpolationAmount( delta ) );
return _currentColor;
}
public static Color Apply( Smoothing sm, Color value, float delta )
{
if ( sm == null ){ return value; }
return sm.Smooth( value, delta );
}
protected virtual float _ComputeInterpolationAmount( float delta )
{
return 0;
}
public virtual float ComputeDuration( float delta = 1f/240f, float tresholdValue = 0.05f )
{
var cached = _currentFloat;
var value = 1f;
_currentFloat = 1f;
var duration = 0f;
var lastValue = value;
var maxDuration = 30;
while ( value > tresholdValue && duration < maxDuration )
{
lastValue = value;
value = Smooth( 0, delta );
duration += delta;
}
_currentFloat = cached;
return MathX.RemapClamped( tresholdValue, lastValue, value, duration - delta, duration );
}
}
}

View File

@ -17,6 +17,7 @@ namespace Rokojori
public class MappingData
{
public Vector3 localPosition;
public Vector3 localNormal;
public float normalizedSplineParameter;
public float weight;
}
@ -74,7 +75,7 @@ namespace Rokojori
MappingData[] deformerMappings;
MeshGeometry meshGeometry;
MappingData CreateSourceMapping( Spline s, Vector3 worldPosition )
MappingData CreateSourceMapping( Spline s, Vector3 worldPosition, Vector3 worldNormal )
{
var curve = s.GetCurve();
var closestParameter = curve.GetClosestParameterTo( worldPosition, splineMappingResolution, splineMappingDepth );
@ -82,10 +83,12 @@ namespace Rokojori
var pose = curve.GetPoseByPointIndex( pointIndex );
var localPosition = pose.ApplyInverse( worldPosition );
var localNormal = pose.rotation.Inverse() * worldNormal;
var mappingData = new MappingData();
mappingData.localPosition = localPosition;
mappingData.localNormal = localNormal;
mappingData.normalizedSplineParameter = closestParameter;
mappingData.weight = 0;
@ -112,7 +115,8 @@ namespace Rokojori
for ( int j = 0; j < sourceSplines.Length; j++ )
{
var vertex = meshGeometry.vertices[ i ];
var mapping = CreateSourceMapping( sourceSplines[ j ], vertex );
var normal = meshGeometry.normals[ i ];
var mapping = CreateSourceMapping( sourceSplines[ j ], vertex, normal );
var curve = sourceSplines[ j ].GetCurve();
var distance = curve.PositionAt( mapping.normalizedSplineParameter ) - vertex;
var inverseWeight = MathX.NormalizeClamped( distance.Length(), splineMinDistance, splineMaxDistance );
@ -140,7 +144,8 @@ namespace Rokojori
for ( int i = 0; i < cloned.vertices.Count; i++ )
{
var vertexPosition = Vector3.Zero;
var vertex = Vector3.Zero;
var normal = Vector3.Zero;
for ( int j = 0; j < deformerSplines.Length; j++ )
{
@ -151,19 +156,22 @@ namespace Rokojori
{
var pose = curve.SmoothedPoseAt( mapping.normalizedSplineParameter, targetSmoothing * 0.5f, 2, targetSmoothing );
pose.ApplyTwist( curve.TwistAt( mapping.normalizedSplineParameter ) );
vertexPosition += pose.Apply( mapping.localPosition ) * mapping.weight;
vertex += pose.Apply( mapping.localPosition ) * mapping.weight;
normal += pose.rotation * mapping.localNormal * mapping.weight;
}
else
{
var pose = curve.PoseAt( mapping.normalizedSplineParameter );
pose.ApplyTwist( curve.TwistAt( mapping.normalizedSplineParameter ) );
vertexPosition += pose.Apply( mapping.localPosition ) * mapping.weight;
vertex += pose.Apply( mapping.localPosition ) * mapping.weight;
normal += pose.rotation * mapping.localNormal * mapping.weight;
}
}
cloned.vertices[ i ] = vertexPosition;
cloned.vertices[ i ] = vertex;
cloned.normals[ i ] = normal.Normalized();
}
// RJLog.Log( cloned.vertices.Count );

View File

@ -0,0 +1,13 @@
using Godot;
using System.Reflection;
using System.Collections.Generic;
namespace Rokojori
{
[Tool]
[GlobalClass]
public partial class SphericalParticleMesh : Mesh
{
}
}

View File

@ -0,0 +1,93 @@
using System.Collections;
using System.Collections.Generic;
using System;
using System.Reflection;
using System.Text.RegularExpressions;
using Godot;
namespace Rokojori
{
public class MinMaxSearch<T>
{
Func<T,T,float,T> lerp;
Func<T,float> getValue;
Func<T,T,bool> validateLimits;
public bool cacheValues = true;
public float interpolationAmount = 1f;
Dictionary<T,float> _cachedValues = new Dictionary<T, float>();
public MinMaxSearch( Func<T,T,float,T> lerp, Func<T,float> getValue, Func<T,T,bool> validateLimits = null)
{
this.lerp = lerp;
this.getValue = getValue;
this.validateLimits = validateLimits;
}
public T Find( float searchValue, T low, T high, float treshold )
{
if ( validateLimits != null && ! validateLimits( low, high ) )
{
throw new Exception( "Limit validation failed" );
}
var tweened = GetTweened( searchValue, low, high );
var tweenedValue = GetValue( tweened );
var tweenedDifference = searchValue - tweenedValue;
Safe.While ( () => Mathf.Abs( tweenedDifference ) > treshold ,
()=>
{
if ( searchValue > tweenedValue )
{
low = tweened;
}
else
{
high = tweened;
}
if ( validateLimits != null && ! validateLimits( low, high ) )
{
throw new Exception( "Limit validation failed" );
}
tweened = GetTweened( searchValue, low, high );
tweenedValue = GetValue( tweened );
tweenedDifference = searchValue - tweenedValue;
}
);
return tweened;
}
public T GetTweened( float value, T low, T high )
{
var lowValue = getValue( low );
var highValue = getValue( high );
var position = MathX.Normalize( value, lowValue, highValue );
position = Mathf.Lerp( 0.5f, position, interpolationAmount );
return lerp( low, high, position );
}
float GetValue( T t )
{
if ( ! cacheValues )
{
return getValue( t );
}
if ( _cachedValues.ContainsKey( t ) )
{
return _cachedValues[ t ];
}
_cachedValues[ t ] = getValue( t );
return _cachedValues[ t ];
}
}
}

View File

@ -9,7 +9,7 @@ using Godot;
namespace Rokojori
{
public class RegexUtility
public static class RegexUtility
{
public static Regex MakeSticky( Regex regex )
{
@ -61,6 +61,23 @@ namespace Rokojori
return "0";
}
public static string _F( this float value )
{
return value.ToString( "0.0", CultureInfo.InvariantCulture );
}
public static string _FF( this float value )
{
return value.ToString( "0.00", CultureInfo.InvariantCulture );
}
public static string _FFF( this float value )
{
return value.ToString( "0.000", CultureInfo.InvariantCulture );
}
public static double ParseDouble( string source, double alternative = 0 )
{
try

View File

@ -37,5 +37,28 @@ namespace Rokojori
}
}
public static TimeLine IfNull_ReplaceByGameTime( TimeLine other )
{
if ( other != null )
{
return other;
}
var tm = Unique<TimeLineManager>.Get();
return tm.gameTimeTimeLine;
}
public static TimeLine IfNull_ReplaceByRealTime( TimeLine other )
{
if ( other != null )
{
return other;
}
var tm = Unique<TimeLineManager>.Get();
return tm.realTimeTimeLine;
}
}
}

View File

@ -16,7 +16,7 @@ namespace Rokojori
public TimeLine[] timeLines;
[Export]
public TimeLine engineTimeScale;
public TimeLine gameTimeTimeLine;
[Export]
public TimeLine realTimeTimeLine;
@ -52,9 +52,9 @@ namespace Rokojori
{
UpdateRealTime( delta );
if ( engineTimeScale != null )
if ( gameTimeTimeLine != null )
{
Engine.TimeScale = GetModulatedSpeed( engineTimeScale );
Engine.TimeScale = GetModulatedSpeed( gameTimeTimeLine );
}
_runners.ForEach( r => r.UpdateTimeLine( unscaledTimeDelta, this ) );

View File

@ -12,11 +12,16 @@ namespace Rokojori
public static void While( Func<bool> condition, System.Action action, System.Action onTooManyIterations = null )
{
if ( condition == null || action == null )
{
return;
}
var it = 0;
while ( it < Safe.maxWhileIterations && condition() )
{
action();
action();
it++;
}