From 1bcbcaaf9997f167dd9f0b0ed2f9cf24d1fdd3bf Mon Sep 17 00:00:00 2001 From: Josef Date: Fri, 17 Jan 2025 18:44:27 +0100 Subject: [PATCH] Pose & Geometry Updates --- Runtime/Godot/ResourceHelper.cs | 12 ++ Runtime/Math/Geometry/Pose.cs | 14 ++ Runtime/Math/Geometry/SplineCurve.cs | 50 ++++++ Runtime/Math/Geometry/SplineCurveCreator.cs | 149 +++++++++++++++- Runtime/Procedural/Assets/Grass/GrassPatch.cs | 159 ++++++++++++++++-- .../Assets/Grass/Windy Grass Shader.gdshader | 80 +++++++++ Runtime/Procedural/Mesh/MeshGeometry.cs | 32 +++- .../Mesh/Modifiers/MeshGeometryModifier.cs | 15 ++ .../SplinesDeformerModifier.cs | 129 ++++++++++++++ .../SplinesDeformerSettings.cs | 30 ++++ .../Parametric/Deformer/Deformer.cs | 10 +- Runtime/Shading/Materials/Materials.cs | 4 +- .../Text/JSON/Serializers/JSONDeserializer.cs | 2 +- Runtime/Text/JSON/Serializers/Reference.cs | 2 +- Runtime/Tools/ReflectionHelper.cs | 104 ++++++++---- 15 files changed, 728 insertions(+), 64 deletions(-) create mode 100644 Runtime/Godot/ResourceHelper.cs create mode 100644 Runtime/Procedural/Assets/Grass/Windy Grass Shader.gdshader create mode 100644 Runtime/Procedural/Mesh/Modifiers/MeshGeometryModifier.cs create mode 100644 Runtime/Procedural/Mesh/Modifiers/SplinesDeformer/SplinesDeformerModifier.cs create mode 100644 Runtime/Procedural/Mesh/Modifiers/SplinesDeformer/SplinesDeformerSettings.cs diff --git a/Runtime/Godot/ResourceHelper.cs b/Runtime/Godot/ResourceHelper.cs new file mode 100644 index 0000000..b2cb10f --- /dev/null +++ b/Runtime/Godot/ResourceHelper.cs @@ -0,0 +1,12 @@ +using Godot; +using System.Collections.Generic; + +namespace Rokojori +{ + public class ResourceHelper + { + public static void Overwrite( List overwriteProperties, T sourceResource, T targetResource ) where T:Resource + { + } + } +} \ No newline at end of file diff --git a/Runtime/Math/Geometry/Pose.cs b/Runtime/Math/Geometry/Pose.cs index 93c6b30..77486c8 100644 --- a/Runtime/Math/Geometry/Pose.cs +++ b/Runtime/Math/Geometry/Pose.cs @@ -202,6 +202,20 @@ namespace Rokojori target.SetGlobalQuaternion( source.GetGlobalQuaternion() ); } + public void Rotate( Quaternion rotation ) + { + position = rotation * position; + this.rotation *= rotation; + this.rotation = this.rotation.Normalized(); + } + + public void RotateAround( Quaternion rotation, Vector3 pivot ) + { + position -= pivot; + Rotate( rotation ); + position += pivot; + } + public static Pose Merge( List poses, List weights = null ) { diff --git a/Runtime/Math/Geometry/SplineCurve.cs b/Runtime/Math/Geometry/SplineCurve.cs index 1022dc5..c7afacb 100644 --- a/Runtime/Math/Geometry/SplineCurve.cs +++ b/Runtime/Math/Geometry/SplineCurve.cs @@ -32,6 +32,23 @@ namespace Rokojori public SplineCurveTangent tangentBefore = new SplineCurveTangent(); public SplineCurveTangent tangentNext = new SplineCurveTangent(); + public Pose pose + { + get => Pose.Create( position, rotation ); + set + { + position = value.position; + rotation = value.rotation; + } + } + + public void RotateAround( Quaternion rotation, Vector3 pivot ) + { + var p = pose; + p.RotateAround( rotation, pivot ); + pose = p; + } + public SplineCurvePoint Clone() { var scp = new SplineCurvePoint(); @@ -77,6 +94,11 @@ namespace Rokojori return cloned; } + public void LookTowards( Vector3 direction, Vector3 up ) + { + rotation = Math3D.LookRotation( direction, up ); + } + } @@ -94,6 +116,34 @@ namespace Rokojori return splineCurve; } + public void AutoOrientateByTangents( Vector3 up, bool closed = false, float autoOrientationTangentAdjustment = 0f ) + { + var list = _points; + + for ( int i = 0; i < list.Count; i++ ) + { + var point = list[ i ]; + + var tangentForward = Vector3.Zero; + + if ( i == ( list.Count - 1 ) && ! closed ) + { + tangentForward = - SplineCurveCreator.GetTangentDirectionSmoothed( 1, autoOrientationTangentAdjustment, list, i, true, false ); + } + else + { + tangentForward = SplineCurveCreator.GetTangentDirectionSmoothed( 1, autoOrientationTangentAdjustment, list, i, false, closed ); + } + + if ( tangentForward.Length() == 0 ) + { + continue; + } + + point.LookTowards( tangentForward, up ); + } + } + public Vector3 MinPointPosition() { if ( _points.Count == 0 ) diff --git a/Runtime/Math/Geometry/SplineCurveCreator.cs b/Runtime/Math/Geometry/SplineCurveCreator.cs index f94246a..ca0cd2b 100644 --- a/Runtime/Math/Geometry/SplineCurveCreator.cs +++ b/Runtime/Math/Geometry/SplineCurveCreator.cs @@ -8,7 +8,25 @@ namespace Rokojori { bool closed; - public SplineCurve Create( List splinePoints, bool close ) + public SplineCurve Create( List splinePoints, bool close = false ) + { + closed = close; + var points = new List(); + + for ( int i = 0; i < splinePoints.Count; i++ ) + { + points.Add( CreatePoint( splinePoints, i ) ); + } + + if ( closed ) + { + points.Add( CreatePoint( splinePoints, 0 ) ); + } + + return SplineCurve.From( points ); + } + + public SplineCurve Create( List splinePoints, bool close = false ) { closed = close; var points = new List(); @@ -45,6 +63,26 @@ namespace Rokojori return splineCurvePoint; } + + SplineCurvePoint CreatePoint( List splinePoints, int index ) + { + var splineCurvePoint = new SplineCurvePoint(); + var splinePoint = splinePoints[ index ]; + splineCurvePoint.position = splinePoint; + splineCurvePoint.rotation = Quaternion.Identity; + splineCurvePoint.scale = Vector3.One; + splineCurvePoint.twist = 0; + splineCurvePoint.weight = 1f; + + splineCurvePoint.tangentBefore.position = GetTangentPosition( splinePoints, index, true, closed ); + splineCurvePoint.tangentNext.position = GetTangentPosition( splinePoints, index, false, closed ); + + splineCurvePoint.tangentBefore.weight = 1; + splineCurvePoint.tangentNext.weight = 1; + + return splineCurvePoint; + } + public static Vector3 GetTangentDirectionSmoothed( float smoothing, List splinePoints, int index, bool before, bool closed ) { var previousIndex = MathX.SafeIndex( index - 1, splinePoints.Count, closed ); @@ -84,12 +122,106 @@ namespace Rokojori return unsmoothedTangent + smoothing * ( smoothedTangent - unsmoothedTangent ); } + public static Vector3 GetTangentDirectionSmoothed( int numSamples, float smoothing, List splinePoints, int index, bool before, bool closed ) + { + var smoothedTangent = Vector3.Zero; + var unsmoothedTangent = Vector3.Zero; + + for ( int i = -numSamples; i <= numSamples; i++ ) + { + var sampleIndex = MathX.SafeIndex( index + i, splinePoints.Count, closed ); + var direction = GetTangentDirection( splinePoints, sampleIndex, before, closed ); + smoothedTangent += direction; + + if ( i == 0 ) + { + unsmoothedTangent = direction; + } + } + + smoothedTangent /= ( numSamples * 2 + 1 ); + + return unsmoothedTangent + smoothing * ( smoothedTangent - unsmoothedTangent ); + } + public static Vector3 GetTangentDirection( List splinePoints, int index, bool before, bool closed ) { var splinePoint = splinePoints[ index ]; return GetTangentPosition( splinePoints, index, before, closed ) - splinePoint.GlobalPosition; + } + + public static Vector3 GetTangentDirection( List splinePoints, int index, bool before, bool closed, + float overshootPrevention = 0, float tangentScale = 1, float symmetricTangentLength = 0 ) + { + var splinePoint = splinePoints[ index ]; + + return GetTangentPosition( splinePoints, index, before, closed, overshootPrevention, tangentScale, symmetricTangentLength ) - splinePoint.position; + } + + + + public static Vector3 GetTangentPosition( List splinePoints, int index, bool before, bool closed, + float overshootPrevention = 0, float tangentScale = 1, float symmetricTangentLength = 0 ) + { + var splinePoint = splinePoints[ index ]; + + var previousIndex = index - 1; + + if ( previousIndex == -1 ) + { + previousIndex = closed ? splinePoints.Count -1 : 0; + } + + var nextIndex = index + 1; + + if ( nextIndex == splinePoints.Count ) + { + nextIndex = closed ? 0 : splinePoints.Count - 1; + } + + var previous = splinePoints[ previousIndex ]; + var next = splinePoints[ nextIndex ]; + + var previousPosition = previous.position; + var nextPosition = next.position; + + var position = splinePoint.position; + + return GetTangentPosition( position, previousPosition, nextPosition, before, overshootPrevention, tangentScale, symmetricTangentLength ); + + } + + public static Vector3 GetTangentPosition( List splinePoints, int index, bool before, bool closed, + float overshootPrevention = 0, float tangentScale = 1, float symmetricTangentLength = 0 ) + { + var splinePoint = splinePoints[ index ]; + + var previousIndex = index - 1; + + if ( previousIndex == -1 ) + { + previousIndex = closed ? splinePoints.Count -1 : 0; + } + + var nextIndex = index + 1; + + if ( nextIndex == splinePoints.Count ) + { + nextIndex = closed ? 0 : splinePoints.Count - 1; + } + + var previous = splinePoints[ previousIndex ]; + var next = splinePoints[ nextIndex ]; + + var previousPosition = previous; + var nextPosition = next; + + var position = splinePoint; + + return GetTangentPosition( position, previousPosition, nextPosition, before, overshootPrevention, tangentScale, symmetricTangentLength ); + } public static Vector3 GetTangentPosition( List splinePoints, int index, bool before, bool closed ) @@ -124,12 +256,22 @@ namespace Rokojori - var point = splinePoint.GlobalPosition; + var position = splinePoint.GlobalPosition; var overshootPrevention = splinePoint.overshootPrevention; var tangentScale = splinePoint.tangentScale; var symmetricTangentLength = splinePoint.symmetricTangentLength; + return GetTangentPosition( position, previousPosition, nextPosition, before, overshootPrevention, tangentScale, symmetricTangentLength ); + + } + + + public static Vector3 GetTangentPosition( Vector3 point, Vector3 previousPosition, Vector3 nextPosition, bool before, + float overshootPrevention = 0, float tangentScale = 1, float symmetricTangentLength = 0 ) + { + + if ( overshootPrevention > 0 ) { var previousDirection = ( previousPosition - point ) ; @@ -179,9 +321,6 @@ namespace Rokojori return point + direction.Normalized() * length * 0.33333f * tangentScale; } - - - } } \ No newline at end of file diff --git a/Runtime/Procedural/Assets/Grass/GrassPatch.cs b/Runtime/Procedural/Assets/Grass/GrassPatch.cs index 8eee333..f67d27b 100644 --- a/Runtime/Procedural/Assets/Grass/GrassPatch.cs +++ b/Runtime/Procedural/Assets/Grass/GrassPatch.cs @@ -53,7 +53,7 @@ namespace Rokojori [ExportGroup( "Blade Triangles")] - [Export( PropertyHint.Range, "1,20")] + [Export( PropertyHint.Range, "1,256")] public int bladeSegments = 3; [Export] @@ -82,6 +82,13 @@ namespace Rokojori [Export] public Curve uvSegmentWeightsFar = null; + [Export( PropertyHint.Range, "0,1" )] + public float uvSegmentDistortion = 0.5f; + + [Export( PropertyHint.Range, "0,0.5" )] + public float uvSegmentMaxRange = 0.3f; + + [ExportGroup( "Blade Shape")] [Export] public Curve bladeScale = MathX.Curve( 1f ); @@ -104,6 +111,23 @@ namespace Rokojori [Export] public Curve bladeBending2 = null; + [Export] + public Curve bladeArching = MathX.Curve( 0f ); + + [Export] + public Curve bladeArching2 = null; + + + [Export] + public Curve bladeTwisting = null; + + [Export] + public Curve bladeTwisting2 = null; + + [Export] + public Curve rolling = MathX.Curve( 0f ); + + [ExportGroup( "Blade Offset & Scale")] [Export] @@ -134,6 +158,9 @@ namespace Rokojori [Export] public Curve normalBlending = MathX.Curve( 0.5f ); + [Export] + public Curve normalBlendingAmountOverY = MathX.Curve( 1f ); + [Export] public Vector3 normalBlendingDirection = Vector3.Up; @@ -239,6 +266,8 @@ namespace Rokojori var allBladesX = bladesX + blades; var allBladesZ = bladesZ + blades; + var numDebugs = 0; + for ( int i = 0; i < allBladesX; i++ ) { var x = ( i + 0.5f ) * cellSizeX; @@ -268,8 +297,11 @@ namespace Rokojori worldPosition ); - var bladeMG = CreateBlade( random, worldPosition ); + debugBlade = X_numBlades < 10; + + var bladeMG = CreateBlade( random, worldPosition ); + if ( filterValue > filterTreshold ) { continue; @@ -296,15 +328,19 @@ namespace Rokojori clonedBladeMG = bladeMG.Clone(); } - bladeMG.BlendNormals( normalBlendingDirection, normalBlendingAmount ); - + var yRange = bladeMG.GetRangeY(); + + // bladeMG.BlendNormals( normalBlendingDirection, normalBlendingAmount ); + bladeMG.BlendNormalsOverY( normalBlendingDirection, normalBlendingAmount, yRange.min, yRange.max, normalBlendingAmountOverY ); + mg.Add( bladeMG ); if ( createBackFaces ) { clonedBladeMG.FlipNormalDirection(); - clonedBladeMG.BlendNormals( normalBlendingDirection, normalBlendingAmount ); + // clonedBladeMG.BlendNormals( normalBlendingDirection, normalBlendingAmount ); + clonedBladeMG.BlendNormalsOverY( normalBlendingDirection, normalBlendingAmount, yRange.min, yRange.max, normalBlendingAmountOverY ); mg.Add( clonedBladeMG ); } @@ -318,9 +354,15 @@ namespace Rokojori output.Mesh = mg.GenerateMesh(); } + bool debugBlade = false; MeshGeometry CreateBlade( RandomEngine random, Vector3 position ) { + // if ( debugBlade ) + // { + // this.LogInfo( "Blade:", X_numBlades ); + // } + var bladeSegments = this.bladeSegments; var currentLodLevel = Mathf.Clamp( currentLODLevel, -1, lodLevels.Length - 1 ); @@ -405,7 +447,6 @@ namespace Rokojori endingTriangles = TrilleanLogic.ToBool( useTris, endingTriangles ); } - for ( int i = 0; i <= bladeSegments; i++ ) @@ -421,10 +462,14 @@ namespace Rokojori var width = Mathf.Lerp( bladeWidth.Sample( v ), bw2.Sample( v ), bladeWidthLerp ); width *= scaling * bladeWidthMultiplier; - var bending = Mathf.Lerp( bladeBending.Sample( v ), bb2.Sample( v ), bladeBendLerp ) * Vector3.Forward.Z * scaling; - var bendingNormalAngle = Mathf.LerpAngle( MathX.CurveAngle( bladeBending, v ), MathX.CurveAngle( bb2, v ), bladeBendLerp ); - var bendingNormal = Math3D.NormalAngle( bendingNormalAngle ); - + + + // var bending = Mathf.Lerp( bladeBending.Sample( v ), bb2.Sample( v ), bladeBendLerp ) * Vector3.Forward.Z * scaling; + // var bendingNormalAngle = Mathf.LerpAngle( MathX.CurveAngle( bladeBending, v ), MathX.CurveAngle( bb2, v ), bladeBendLerp ); + // var bendingNormal = Math3D.NormalAngle( bendingNormalAngle ); + + var bending = 0; + var bendingNormal = Vector3.Back; if ( endingTriangles && width < 0.005f && ( i == 0 || ( i == bladeSegments - 1 )) ) { @@ -443,11 +488,27 @@ namespace Rokojori bmg.normals.Add( bendingNormal ); bmg.normals.Add( bendingNormal ); - bmg.uvs.Add( MapUV( new Vector2( -width / _maxWidth, v ), uvMin, uvMax ) ); - bmg.uvs.Add( MapUV( new Vector2( width / _maxWidth, v ), uvMin, uvMax ) ); + var uvCoord = width / _maxWidth; + + uvCoord = Mathf.Lerp( uvCoord, 1f, uvSegmentDistortion ); + + uvCoord *= uvSegmentMaxRange; + var uvL = MapUV( new Vector2( -uvCoord + 0.5f, v ), uvMin, uvMax ); + var uvR = MapUV( new Vector2( uvCoord + 0.5f, v ), uvMin, uvMax ); + + bmg.uvs.Add( uvL ); + bmg.uvs.Add( uvR ); + + // if ( debugBlade ) + // { + // this.LogInfo( i, ">>", uvL, uvR ); + // } + + } } + for ( int i = 0; i < bladeSegments; i++ ) { var index = i * 2; @@ -471,9 +532,83 @@ namespace Rokojori } } + var yStart = size * 0f - inGround; + var yEnd = size * 1f - inGround; + + var startPosition = new Vector3( 0, yStart, 0 ); + var endPosition = new Vector3( 0, yEnd, 0 ); + + var inputList = new List(){ startPosition, endPosition }; + var inputSpline = new SplineCurveCreator().Create( inputList ); + inputSpline.AutoOrientateByTangents( Vector3.Back ); + + var outputList = new List(); + + var numPoints = Mathf.Max( 2, bladeSegments * 2 ); + + for ( int i = 0; i < numPoints ; i++ ) + { + var t = (float)i / ( numPoints - 1f ); + var y = size * t - inGround; + var bb2 = bladeBending2 == null ? bladeBending : bladeBending2; + var v = 1f - t; + var bending = Mathf.Lerp( bladeBending.Sample( v ), bb2.Sample( v ), bladeBendLerp ) * Vector3.Forward.Z * scaling; + + var p = new Vector3( 0, y, bending ); + + outputList.Add( p ); + } + + var outputSpline = new SplineCurveCreator().Create( outputList ); + + + var outputPoints = outputSpline.points; + + if ( bladeTwisting != null ) + { + for ( int i = 0; i < outputPoints.Count; i++ ) + { + var t = (float)i / ( numPoints - 1f ); + var v = 1f - t; + + var bt2 = bladeTwisting2 == null ? bladeTwisting : bladeTwisting2; + var tw = Mathf.Lerp( bladeTwisting.Sample( v ), bt2.Sample( v ), bladeBendLerp ); + var p = outputPoints[ i ]; + p.twist = tw; + } + + } + + outputSpline.AutoOrientateByTangents( Vector3.Back ); + + for ( int i = 0; i < outputPoints.Count - 1; i++ ) + { + var t = i / (float)( outputPoints.Count - 2 ); + var rotation = rolling.Sample( t ); + RollSpline( outputPoints, i, rotation ); + } + + var modifier = new SplinesDeformModifier(); + modifier.sourceSplines = new SplineCurve[]{ inputSpline }; + modifier.targetSplines = new SplineCurve[]{ outputSpline }; + modifier.settings = new SplinesDeformerSettings(); + + bmg = modifier.Modify( bmg ); + + return bmg; } + void RollSpline( List points, int start, float amount ) + { + var pivot = points[ start ].position; + var rotation = Math3D.RotateX( MathX.DegreesToRadians * amount / (float) points.Count ).Normalized(); + + for ( int i = start + 1; i < points.Count; i++ ) + { + points[ i ].RotateAround( rotation, pivot ); + } + } Vector2 MapUV( Vector2 uv, Vector2 min, Vector2 max ) { diff --git a/Runtime/Procedural/Assets/Grass/Windy Grass Shader.gdshader b/Runtime/Procedural/Assets/Grass/Windy Grass Shader.gdshader new file mode 100644 index 0000000..50a6062 --- /dev/null +++ b/Runtime/Procedural/Assets/Grass/Windy Grass Shader.gdshader @@ -0,0 +1,80 @@ +// NOTE: Shader automatically converted from Godot Engine 4.3.stable.mono's StandardMaterial3D. + +shader_type spatial; +render_mode blend_mix, depth_draw_opaque, cull_back, diffuse_burley, specular_schlick_ggx; + +#include "res://addons/rokojori_action_library/Runtime/Shading/Library/Math.gdshaderinc" +#include "res://addons/rokojori_action_library/Runtime/Shading/Library/Transform.gdshaderinc" +#include "res://addons/rokojori_action_library/Runtime/Shading/Library/Noise.gdshaderinc" + + +uniform vec4 albedo : source_color; +uniform sampler2D texture_albedo : source_color, filter_linear_mipmap, repeat_enable; + +uniform float roughness : hint_range(0.0, 1.0); +uniform sampler2D texture_metallic : hint_default_white, filter_linear_mipmap, repeat_enable; +uniform vec4 metallic_texture_channel; +uniform sampler2D texture_roughness : hint_roughness_r, filter_linear_mipmap, repeat_enable; + +uniform float specular : hint_range(0.0, 1.0, 0.01); +uniform float metallic : hint_range(0.0, 1.0, 0.01); + +uniform sampler2D texture_normal : hint_roughness_normal, filter_linear_mipmap, repeat_enable; +uniform float normal_scale : hint_range(-16.0, 16.0); + +uniform sampler2D texture_ambient_occlusion : hint_default_white, filter_linear_mipmap, repeat_enable; +uniform vec4 ao_texture_channel; +uniform float ao_light_affect : hint_range(0.0, 1.0, 0.01); + +uniform vec3 uv1_scale; +uniform vec3 uv1_offset; + +uniform float windStrength = 0; +uniform vec2 windSpeed = vec2(1,1); +uniform float windScale = 1; +uniform sampler2D windNoise; +uniform vec2 windNoiseAngleOffset; +uniform vec2 windNoiseStrengthOffset; +uniform float windStart = 0; +uniform float windEnd = 1; +uniform float windWeightCurve:hint_range(0,1) = 0.5f; +uniform float windHeightCompensation :hint_range(0,1) = 0.5f; + +void vertex() +{ + UV = UV * uv1_scale.xy + uv1_offset.xy; + + float windAmount = normalizeToRange01( VERTEX.y, windStart, windEnd ); + float rawWindAmount = windAmount; + windAmount = mix( windAmount, windAmount * windAmount, windWeightCurve ); + vec3 worldVertex = localToWorld( VERTEX, MODEL_MATRIX ).xyz; + vec2 windUV = TIME * windSpeed + worldVertex.xz * windScale; + float angle = texture( windNoise, windUV + windNoiseAngleOffset).r * PI * 2.0; + float strength = texture( windNoise, windUV + windNoiseStrengthOffset ).r * windStrength; + vec2 circle = onCircle( angle ) * strength; + VERTEX = worldToLocal( worldVertex + vec3( circle.x, 0, circle.y ) * windAmount, MODEL_MATRIX ); + VERTEX.y = mix( VERTEX.y, max( 0, VERTEX.y - strength * windAmount), windHeightCompensation * 2.0f ); + +} + +void fragment() +{ + vec2 base_uv = UV; + + vec4 albedo_tex = texture(texture_albedo, base_uv); + ALBEDO = albedo.rgb * albedo_tex.rgb; + + float metallic_tex = dot(texture(texture_metallic, base_uv), metallic_texture_channel); + METALLIC = metallic_tex * metallic; + SPECULAR = specular; + + vec4 roughness_texture_channel = vec4(1.0, 0.0, 0.0, 0.0); + float roughness_tex = dot(texture(texture_roughness, base_uv), roughness_texture_channel); + ROUGHNESS = roughness_tex * roughness; + + NORMAL_MAP = texture(texture_normal, base_uv).rgb; + NORMAL_MAP_DEPTH = normal_scale; + + AO = dot(texture(texture_ambient_occlusion, base_uv), ao_texture_channel); + AO_LIGHT_AFFECT = ao_light_affect; +} diff --git a/Runtime/Procedural/Mesh/MeshGeometry.cs b/Runtime/Procedural/Mesh/MeshGeometry.cs index 54c2533..6a765a5 100644 --- a/Runtime/Procedural/Mesh/MeshGeometry.cs +++ b/Runtime/Procedural/Mesh/MeshGeometry.cs @@ -167,7 +167,7 @@ namespace Rokojori if ( amount >= 1 ) { - for ( int i = 0; i < normals.Count; i++ ) + for ( int i = 0; i < normals.Count; i++ ) { normals[ i ] = direction; } @@ -181,6 +181,36 @@ namespace Rokojori } } + public Range GetRangeY() + { + var minY = float.MaxValue; + var maxY = -float.MaxValue; + + for ( int i = 0; i < vertices.Count; i++ ) + { + minY = Mathf.Min( minY, vertices[ i ].Y ); + maxY = Mathf.Max( maxY, vertices[ i ].Y ); + } + + return new Range( minY, maxY ); + } + + public void BlendNormalsOverY( Vector3 direction, float amount, float startY, float endY, Curve curve ) + { + if ( amount <= 0 ) + { + return; + } + + for ( int i = 0; i < normals.Count; i++ ) + { + var yPositionAmount = MathX.RemapClamped( vertices[ i ].Y, startY, endY, 0, 1 ); + var blendAmount = curve.Sample( yPositionAmount ) * amount; + blendAmount = MathX.Clamp01( blendAmount ); + normals[ i ] = Math3D.BlendNormals( normals[ i ], direction, blendAmount ); + } + } + public void Offset( Vector3 offset ) { for ( int i = 0; i < vertices.Count; i++ ) diff --git a/Runtime/Procedural/Mesh/Modifiers/MeshGeometryModifier.cs b/Runtime/Procedural/Mesh/Modifiers/MeshGeometryModifier.cs new file mode 100644 index 0000000..e898f7a --- /dev/null +++ b/Runtime/Procedural/Mesh/Modifiers/MeshGeometryModifier.cs @@ -0,0 +1,15 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; +using System; + + + +namespace Rokojori +{ + public class MeshGeometryModifier + { + public MeshGeometry Modify( MeshGeometry mg ){ return mg; } + + } +} \ No newline at end of file diff --git a/Runtime/Procedural/Mesh/Modifiers/SplinesDeformer/SplinesDeformerModifier.cs b/Runtime/Procedural/Mesh/Modifiers/SplinesDeformer/SplinesDeformerModifier.cs new file mode 100644 index 0000000..0327973 --- /dev/null +++ b/Runtime/Procedural/Mesh/Modifiers/SplinesDeformer/SplinesDeformerModifier.cs @@ -0,0 +1,129 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; +using System; + + + +namespace Rokojori +{ + public class SplinesDeformerMappingData + { + public Vector3 localPosition; + public Vector3 localNormal; + public float normalizedSplineParameter; + public float weight; + } + + public class SplinesDeformModifier + { + public SplineCurve[] sourceSplines; + public SplineCurve[] targetSplines; + + public SplinesDeformerSettings settings; + + + + public MeshGeometry Modify( MeshGeometry mg ) + { + var mappings = CreateSourceMappings( mg ); + return CreateDeformed( mg, mappings ); + } + + SplinesDeformerMappingData CreateSourceMapping( SplineCurve curve, Vector3 worldPosition, Vector3 worldNormal ) + { + var closestParameter = curve.GetClosestParameterTo( worldPosition, settings.splineMappingResolution, settings.splineMappingDepth ); + var pointIndex = curve.NormalizedToPointIndex( closestParameter ); + var pose = curve.GetPoseByPointIndex( pointIndex ); + + var localPosition = pose.ApplyInverse( worldPosition ); + var localNormal = pose.rotation.Inverse() * worldNormal; + + var mappingData = new SplinesDeformerMappingData(); + + mappingData.localPosition = localPosition; + mappingData.localNormal = localNormal; + mappingData.normalizedSplineParameter = closestParameter; + mappingData.weight = 0; + + return mappingData; + } + + SplinesDeformerMappingData[] CreateSourceMappings( MeshGeometry meshGeometry) + { + var mappingSize = meshGeometry.vertices.Count * sourceSplines.Length; + + var deformerMappings = new SplinesDeformerMappingData[ mappingSize]; + + 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 normal = meshGeometry.normals[ i ]; + var curve = sourceSplines[ j ]; + var mapping = CreateSourceMapping( curve, vertex, normal ); + + var distance = curve.PositionAt( mapping.normalizedSplineParameter ) - vertex; + var inverseWeight = MathX.NormalizeClamped( distance.Length(), settings.splineMinDistance, settings.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; + } + } + } + + return deformerMappings; + } + + MeshGeometry CreateDeformed( MeshGeometry meshGeometry, SplinesDeformerMappingData[] mappingData ) + { + var cloned = meshGeometry.Clone(); + + for ( int i = 0; i < cloned.vertices.Count; i++ ) + { + var vertex = Vector3.Zero; + var normal = Vector3.Zero; + + for ( int j = 0; j < targetSplines.Length; j++ ) + { + var mapping = mappingData[ i * targetSplines.Length + j ]; + var curve = targetSplines[ j ]; + + if ( settings.targetSmoothing > 0 ) + { + var pose = curve.SmoothedPoseAt( mapping.normalizedSplineParameter, settings.targetSmoothing * 0.5f, 2, settings.targetSmoothing ); + pose.ApplyTwist( curve.TwistAt( mapping.normalizedSplineParameter ) ); + 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 ) ); + vertex += pose.Apply( mapping.localPosition ) * mapping.weight; + normal += pose.rotation * mapping.localNormal * mapping.weight; + } + + } + + cloned.vertices[ i ] = vertex; + cloned.normals[ i ] = normal.Normalized(); + } + + return cloned; + } + + } +} \ No newline at end of file diff --git a/Runtime/Procedural/Mesh/Modifiers/SplinesDeformer/SplinesDeformerSettings.cs b/Runtime/Procedural/Mesh/Modifiers/SplinesDeformer/SplinesDeformerSettings.cs new file mode 100644 index 0000000..2978a32 --- /dev/null +++ b/Runtime/Procedural/Mesh/Modifiers/SplinesDeformer/SplinesDeformerSettings.cs @@ -0,0 +1,30 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; +using System; + + + +namespace Rokojori +{ + [GlobalClass,Tool] + public partial class SplinesDeformerSettings:Resource + { + [Export( PropertyHint.Range, "0,1")] + public float targetSmoothing = 0f; + + [ExportGroup( "Spline Settings")] + + [Export] + public float splineMaxDistance = 1000f; + + [Export] + public float splineMinDistance = 0f; + + [Export] + public int splineMappingResolution = 40; + + [Export] + public int splineMappingDepth = 3; + } +} \ No newline at end of file diff --git a/Runtime/Procedural/Parametric/Deformer/Deformer.cs b/Runtime/Procedural/Parametric/Deformer/Deformer.cs index 00c194f..84496ce 100644 --- a/Runtime/Procedural/Parametric/Deformer/Deformer.cs +++ b/Runtime/Procedural/Parametric/Deformer/Deformer.cs @@ -14,7 +14,7 @@ namespace Rokojori #endif { - public class MappingData + public class DeformerMappingData { public Vector3 localPosition; public Vector3 localNormal; @@ -72,10 +72,10 @@ namespace Rokojori - MappingData[] deformerMappings; + DeformerMappingData[] deformerMappings; MeshGeometry meshGeometry; - MappingData CreateSourceMapping( Spline s, Vector3 worldPosition, Vector3 worldNormal ) + DeformerMappingData CreateSourceMapping( Spline s, Vector3 worldPosition, Vector3 worldNormal ) { var curve = s.GetCurve(); var closestParameter = curve.GetClosestParameterTo( worldPosition, splineMappingResolution, splineMappingDepth ); @@ -85,7 +85,7 @@ namespace Rokojori var localPosition = pose.ApplyInverse( worldPosition ); var localNormal = pose.rotation.Inverse() * worldNormal; - var mappingData = new MappingData(); + var mappingData = new DeformerMappingData(); mappingData.localPosition = localPosition; mappingData.localNormal = localNormal; @@ -103,7 +103,7 @@ namespace Rokojori if ( deformerMappings == null || deformerMappings.Length != mappingSize ) { - deformerMappings = new MappingData[ mappingSize]; + deformerMappings = new DeformerMappingData[ mappingSize]; } this.LogInfo( "Mappings:", deformerMappings.Length, meshGeometry ); diff --git a/Runtime/Shading/Materials/Materials.cs b/Runtime/Shading/Materials/Materials.cs index d0b3719..b37f845 100644 --- a/Runtime/Shading/Materials/Materials.cs +++ b/Runtime/Shading/Materials/Materials.cs @@ -46,7 +46,7 @@ namespace Rokojori } else if ( node is CsgPrimitive3D ) { - return ReflectionHelper.GetMemberValue( node, "material" ) as M; + return ReflectionHelper.GetDataMemberValue( node, "material" ); } else if ( node is GpuParticles3D gp) @@ -99,7 +99,7 @@ namespace Rokojori if ( node is CsgPrimitive3D csg ) { - ReflectionHelper.SetMemberValue( node, "material", material ); + ReflectionHelper.SetDataMemberValue( node, "material", material ); if ( csg.MaterialOverride != null ) { diff --git a/Runtime/Text/JSON/Serializers/JSONDeserializer.cs b/Runtime/Text/JSON/Serializers/JSONDeserializer.cs index e65ef9f..5f807d0 100644 --- a/Runtime/Text/JSON/Serializers/JSONDeserializer.cs +++ b/Runtime/Text/JSON/Serializers/JSONDeserializer.cs @@ -260,7 +260,7 @@ namespace Rokojori void SetMemberValue( object instance, string name, object value ) { - ReflectionHelper.SetMemberValue( instance, name, value ); + ReflectionHelper.SetDataMemberValue( instance, name, value ); } void SetListValue( IList list, int index, object value ) diff --git a/Runtime/Text/JSON/Serializers/Reference.cs b/Runtime/Text/JSON/Serializers/Reference.cs index 7c884b4..ef10861 100644 --- a/Runtime/Text/JSON/Serializers/Reference.cs +++ b/Runtime/Text/JSON/Serializers/Reference.cs @@ -105,7 +105,7 @@ namespace Rokojori } else { - ReflectionHelper.SetMemberValue( target, name, value ); + ReflectionHelper.SetDataMemberValue( target, name, value ); } } diff --git a/Runtime/Tools/ReflectionHelper.cs b/Runtime/Tools/ReflectionHelper.cs index 01ec8b4..6d3ed02 100644 --- a/Runtime/Tools/ReflectionHelper.cs +++ b/Runtime/Tools/ReflectionHelper.cs @@ -187,6 +187,7 @@ namespace Rokojori } } + public static FieldInfo GetFieldInfo( object instance, string memberName ) { var type = instance.GetType(); @@ -205,58 +206,87 @@ namespace Rokojori return index == -1 ? null : fields[ index ]; } - public static bool HasMember( object instance, string memberName ) + /*public static bool HasMember( object instance, string memberName ) { return GetFieldInfo( instance, memberName ) != null; - } + }*/ - public static object GetMemberValue( object instance, string memberName ) + public static MemberInfo GetDataMemberInfo( object instance, string memberName, BindingFlags flags = BindingFlags.Public | BindingFlags.Instance ) { - try + var type = instance.GetType(); + var fieldInfo = type.GetField( memberName, flags ); + + if ( fieldInfo != null ) { - var field = ReflectionHelper.GetFieldInfo( instance, memberName ); - - if ( field == null ) - { - RJLog.Log(instance, "GetValue: member not found with name ", "'" + memberName + "'" ); - - return null; - } - - return field.GetValue( instance ); - + return fieldInfo; } - catch( System.Exception e ) - { - RJLog.Log(e ); - } - - return null; + + return type.GetProperty( memberName, flags ); } - public static void SetMemberValue( object instance, string memberName, object value ) + public const BindingFlags defaultBindings = BindingFlags.Public | + BindingFlags.NonPublic | + BindingFlags.Instance | + BindingFlags.Static; + + public static bool HasDataMember( object instance, string memberName, BindingFlags flags = ReflectionHelper.defaultBindings ) { - try + return GetDataMemberInfo( instance, memberName, flags ) != null; + } + + public static T GetDataMemberValue( object instance, string memberName, BindingFlags flags = ReflectionHelper.defaultBindings ) + { + var info = GetDataMemberInfo( instance, memberName, flags ); + + if ( info == null ) { - var field = ReflectionHelper.GetFieldInfo( instance, memberName ); - - if ( field == null ) - { - RJLog.Log(instance, "SetValue: member not found with name ", "'" + memberName + "'" ); - - return; - } - - field.SetValue( instance, value ); - + return default(T); } - catch( System.Exception e ) + + if ( info is FieldInfo fieldInfo ) { - RJLog.Log(e ); + return (T) fieldInfo.GetValue( instance ); } - + + if ( info is PropertyInfo propertyInfo ) + { + return (T) propertyInfo.GetValue( instance ); + } + + return default(T); + } + public static void SetDataMemberValue( object instance, string memberName, object value, BindingFlags flags = ReflectionHelper.defaultBindings ) + { + var info = GetDataMemberInfo( instance, memberName, flags ); + + if ( info == null ) + { + return; + } + + if ( info is FieldInfo fieldInfo ) + { + fieldInfo.SetValue( instance, value ); + } + + if ( info is PropertyInfo propertyInfo ) + { + propertyInfo.SetValue( instance, value ); + } + } + + public static void CopyDataMembersFromTo( object source, object target, List dataMembers ) + { + dataMembers.ForEach( + dm => + { + var value = GetDataMemberValue( source, dm ); + SetDataMemberValue( target, dm, value ); + } + ); + } } } \ No newline at end of file