SVG, Procedural Updates
This commit is contained in:
		
							parent
							
								
									521d609dd1
								
							
						
					
					
						commit
						9131afc04d
					
				|  | @ -130,6 +130,22 @@ namespace TriangleNet.Geometry | |||
| 
 | ||||
|         private static Point FindPointInPolygon(List<Vertex> contour, int limit, double eps) | ||||
|         { | ||||
|             if ( contour.Count <= 3 ) | ||||
|             { | ||||
|               var x = 0.0; | ||||
|               var y = 0.0; | ||||
| 
 | ||||
|               for ( int i = 0; i < contour.Count; i++ ) | ||||
|               { | ||||
|                 x += contour[ i ].X; | ||||
|                 y += contour[ i ].Y; | ||||
|               } | ||||
| 
 | ||||
|               var w = contour.Count == 0 ? 0 : ( 1.0 / contour.Count ); | ||||
| 
 | ||||
|               return new Point( x * w, y * w); | ||||
|             } | ||||
| 
 | ||||
|             var bounds = new Rectangle(); | ||||
|             bounds.Expand(contour.Cast<Point>()); | ||||
| 
 | ||||
|  | @ -204,7 +220,19 @@ namespace TriangleNet.Geometry | |||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             throw new Exception(); | ||||
|              | ||||
|             var exceptionInfo = "Found no point inside polygon:"; | ||||
| 
 | ||||
|             var sp = "G"; | ||||
|             var ci = System.Globalization.CultureInfo.InvariantCulture; | ||||
| 
 | ||||
|             for ( int i = 0; i < Math.Min( 20, length ); i++ ) | ||||
|             { | ||||
|               exceptionInfo += " "; | ||||
|               exceptionInfo += "(" + contour[ i ].X.ToString( sp, ci ) + ", " + contour[ i ].Y.ToString( sp, ci ) + ")"; | ||||
|             } | ||||
| 
 | ||||
|             throw new Exception( exceptionInfo ); | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|  |  | |||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 47 KiB | 
|  | @ -0,0 +1,23 @@ | |||
| using Godot; | ||||
| using System.Text; | ||||
| using System.Collections.Generic; | ||||
| 
 | ||||
| namespace Rokojori | ||||
| {   | ||||
|   public class MakeResourcesUnique | ||||
|   { | ||||
|     public static void On( Node node ) | ||||
|     { | ||||
|       var resourceFields = ReflectionHelper.GetFieldInfosOfType<Resource>( node ); | ||||
| 
 | ||||
|       for ( int i = 0; i < resourceFields.Count; i++ ) | ||||
|       { | ||||
|         var rf = resourceFields[ i ]; | ||||
|         var resource = (Resource) rf.GetValue( node ); | ||||
|         var value = resource.Duplicate( true ); | ||||
| 
 | ||||
|         rf.SetValue( node, value ); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | @ -27,7 +27,7 @@ namespace Rokojori | |||
| 
 | ||||
|     protected virtual void SetAttributes() | ||||
|     { | ||||
|       _attributes = ReflectionHelper.GetFieldsOfType<SceneFileHeaderAttributeValue>( this ); | ||||
|       _attributes = ReflectionHelper.GetFieldValuesOfType<SceneFileHeaderAttributeValue>( this ); | ||||
|     } | ||||
| 
 | ||||
|     protected void ReadAttributes( SceneFileHeader entry ) | ||||
|  |  | |||
|  | @ -10,6 +10,15 @@ namespace Rokojori | |||
|   public class RJLog | ||||
|   { | ||||
| 
 | ||||
|     public static string Stringify( object obj ) | ||||
|     { | ||||
|       var sb = new StringBuilder();  | ||||
| 
 | ||||
|       Stringify( obj, sb ); | ||||
|        | ||||
|       return sb.ToString(); | ||||
|     } | ||||
| 
 | ||||
|     public static void Stringify( object obj, StringBuilder output ) | ||||
|     { | ||||
|       if ( obj == null ) | ||||
|  | @ -32,6 +41,34 @@ namespace Rokojori | |||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       if ( obj is IList && obj.GetType().IsGenericType ) | ||||
|       { | ||||
|         var list = (IList)obj; | ||||
| 
 | ||||
|         output.Append( "[" ); | ||||
| 
 | ||||
|         var first = true; | ||||
| 
 | ||||
|         foreach ( var it in list ) | ||||
|         { | ||||
|           if ( first ) | ||||
|           { | ||||
|             first = false; | ||||
|           } | ||||
|           else | ||||
|           { | ||||
|             output.Append( ", " ); | ||||
|           } | ||||
| 
 | ||||
| 
 | ||||
|           Stringify( it, output ); | ||||
|         } | ||||
| 
 | ||||
|         output.Append( "]" ); | ||||
| 
 | ||||
|         return; | ||||
|       } | ||||
|        | ||||
|       output.Append( obj.ToString() ); | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -0,0 +1,29 @@ | |||
| using System.Collections; | ||||
| using System.Collections.Generic; | ||||
| using Godot; | ||||
| 
 | ||||
| namespace Rokojori | ||||
| { | ||||
| 
 | ||||
|   public class BernsteinPolynom | ||||
|   { | ||||
|     public static float Compute( int degree, int i, float t ) | ||||
|     { | ||||
|       var result = 0f; | ||||
| 
 | ||||
|       var n = degree; | ||||
|          | ||||
|       var weight = BinomoalCoefficent.Compute( n, i ); | ||||
|        | ||||
|       var inverse = Mathf.Pow( 1 - t, n - i ); | ||||
|       var forward = Mathf.Pow( t, i ); | ||||
|        | ||||
|       result += inverse * forward * weight;       | ||||
| 
 | ||||
|       return result; | ||||
|     } | ||||
|      | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,54 @@ | |||
| using System.Collections; | ||||
| using System.Collections.Generic; | ||||
| using Godot; | ||||
| 
 | ||||
| namespace Rokojori | ||||
| { | ||||
|   public class Bezier | ||||
|   { | ||||
|     public static float Compute( float t, List<float> values ) | ||||
|     { | ||||
|       var result = 0f; | ||||
| 
 | ||||
|       var degree = values.Count; | ||||
| 
 | ||||
|       for ( int i = 0; i < values.Count; i++ ) | ||||
|       { | ||||
|         var bp = BernsteinPolynom.Compute( degree, i, t ); | ||||
|         result += values[ i ] * bp; | ||||
|       } | ||||
| 
 | ||||
|       return result; | ||||
|     } | ||||
| 
 | ||||
|     public static Vector2 Compute( float t, List<Vector2> values ) | ||||
|     { | ||||
|       var result = Vector2.Zero; | ||||
| 
 | ||||
|       var degree = values.Count; | ||||
| 
 | ||||
|       for ( int i = 0; i < values.Count; i++ ) | ||||
|       { | ||||
|         var bp = BernsteinPolynom.Compute( degree, i, t ); | ||||
|         result += values[ i ] * bp; | ||||
|       } | ||||
| 
 | ||||
|       return result; | ||||
|     } | ||||
| 
 | ||||
|     public static Vector3 Compute( float t, List<Vector3> values ) | ||||
|     { | ||||
|       var result = Vector3.Zero; | ||||
| 
 | ||||
|       var degree = values.Count; | ||||
| 
 | ||||
|       for ( int i = 0; i < values.Count; i++ ) | ||||
|       { | ||||
|         var bp = BernsteinPolynom.Compute( degree, i, t ); | ||||
|         result += values[ i ] * bp; | ||||
|       } | ||||
| 
 | ||||
|       return result; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,18 @@ | |||
| using System.Collections; | ||||
| using System.Collections.Generic; | ||||
| using Godot; | ||||
| 
 | ||||
| namespace Rokojori | ||||
| { | ||||
| 
 | ||||
|   public class BinomoalCoefficent | ||||
|   { | ||||
|     public static float Compute( int n, int k ) | ||||
|     { | ||||
|       return MathX.Factorial( n ) / ( MathX.Factorial( k ) * MathX.Factorial( n - k ) ); | ||||
|     } | ||||
|      | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,54 @@ | |||
| using System.Collections; | ||||
| using System.Collections.Generic; | ||||
| using Godot; | ||||
| 
 | ||||
| namespace Rokojori | ||||
| { | ||||
| 
 | ||||
|   public class CubicBezier | ||||
|   { | ||||
|     public static float Compute( | ||||
|       float t,  | ||||
|       float p0, float p1, float p2, float p3 | ||||
|     ) | ||||
|     { | ||||
|       var bp0 = ( 1 - t ) * ( 1 - t ) * ( 1 - t ); | ||||
|       var bp1 = 3 * ( 1 - t ) * ( 1 - t ) * t; | ||||
|       var bp2 = 3 * ( 1 - t ) * t * t; | ||||
|       var bp3 = t * t * t; | ||||
| 
 | ||||
|       return p0 * bp0 + p1 * bp1 + p2 * bp2 + p3 * bp3; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public static Vector2 Compute( | ||||
|       float t,  | ||||
|       Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3 | ||||
|     ) | ||||
|     { | ||||
|       var bp0 = ( 1 - t ) * ( 1 - t ) * ( 1 - t ); | ||||
|       var bp1 = 3 * ( 1 - t ) * ( 1 - t ) * t; | ||||
|       var bp2 = 3 * ( 1 - t ) * t * t; | ||||
|       var bp3 = t * t * t; | ||||
| 
 | ||||
|       return p0 * bp0 + p1 * bp1 + p2 * bp2 + p3 * bp3; | ||||
|     } | ||||
| 
 | ||||
|     public static Vector3 Compute( | ||||
|       float t,  | ||||
|       Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3 | ||||
|     ) | ||||
|     { | ||||
|       var bp0 = ( 1 - t ) * ( 1 - t ) * ( 1 - t ); | ||||
|       var bp1 = 3 * ( 1 - t ) * ( 1 - t ) * t; | ||||
|       var bp2 = 3 * ( 1 - t ) * t * t; | ||||
|       var bp3 = t * t * t; | ||||
| 
 | ||||
|       return p0 * bp0 + p1 * bp1 + p2 * bp2 + p3 * bp3; | ||||
|     }    | ||||
| 
 | ||||
|      | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,199 @@ | |||
| using Godot; | ||||
| using System.Collections; | ||||
| using System.Collections.Generic; | ||||
| using System;  | ||||
| 
 | ||||
| namespace Rokojori | ||||
| { | ||||
| 	public abstract class Curve2 | ||||
| 	{ | ||||
|     float _gradientSamplingRange = Mathf.Pow( 10f, -8f ); | ||||
|     public abstract Vector2 SampleAt( float t ); | ||||
|      | ||||
|     public virtual void SampleMultiple( int numSamples, List<Vector2> output ) | ||||
|     { | ||||
|       SampleMultiple( new Range( 0, 1 ), numSamples, output ); | ||||
|     } | ||||
|      | ||||
|     public virtual void SampleMultiple( Range range, int numSamples, List<Vector2> output ) | ||||
|     { | ||||
|       var diff = range.length / ( numSamples - 1 ) ; | ||||
|        | ||||
|       for ( var i = 0 ; i < numSamples; i++ ) | ||||
|       { | ||||
|         var t = range.min + i * diff; | ||||
|         output.Add( SampleAt( t ) ); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     public virtual Path2 SamplePath( int numSamples ) | ||||
|     { | ||||
|       return SamplePath( new Range( 0, 1 ), numSamples ); | ||||
|     } | ||||
|      | ||||
|     public virtual Path2 SamplePath( Range range, int numSamples ) | ||||
|     { | ||||
|       var diff = range.length / ( numSamples - 1 ) ; | ||||
|       var points = new List<Vector2>(); | ||||
|        | ||||
|       for ( var i = 0 ; i < numSamples; i++ ) | ||||
|       { | ||||
|         var t = range.min + i * diff; | ||||
|         var p = SampleAt( t ); | ||||
|         points.Add( new Vector2( p.X, p.Y ) ); | ||||
|          | ||||
|       } | ||||
| 
 | ||||
|       return new Path2( points ); | ||||
|     } | ||||
| 
 | ||||
|     public virtual Vector2 GradientAt( float t ) | ||||
|     { | ||||
|       return GradientAt( t, _gradientSamplingRange ); | ||||
|     }  | ||||
| 
 | ||||
|     public virtual Vector2 GradientAt( float t, float samplingRange ) | ||||
|     { | ||||
|       return SampleAt( t + samplingRange ) - SampleAt( t - samplingRange ); | ||||
|     }  | ||||
| 
 | ||||
|     public virtual float ComputeLength( int numSamples ) | ||||
|     { | ||||
|       return ComputeSubLength( Range.Of_01 , numSamples ); | ||||
|     }     | ||||
| 
 | ||||
|     public virtual float ComputeSubLength( Range range, int numSamples ) | ||||
|     { | ||||
|       var diff = range.length / ( numSamples - 1 ) ; | ||||
|       var it = SampleAt( range.min ); | ||||
|       var length = 0f; | ||||
| 
 | ||||
|       for ( var i = 1 ; i < numSamples; i++ ) | ||||
|       { | ||||
|         var t = range.min + i * diff; | ||||
|         var next = SampleAt( t ); | ||||
|         length += ( next - it ).Length(); | ||||
|         it = next; | ||||
|       } | ||||
| 
 | ||||
|       return length; | ||||
|     } | ||||
|      | ||||
|     public virtual int ComputeNumSamplesFor( Range range, | ||||
|     float minimumDistance, int minSamples = 2,  | ||||
|     int numSamplesForLengthComputation = -1 ) | ||||
|     { | ||||
|       if ( minSamples < 2 ) | ||||
|       { minSamples = 2; } | ||||
|        | ||||
|       if ( numSamplesForLengthComputation <= 0  ) | ||||
|       { numSamplesForLengthComputation = minSamples * 4; } | ||||
| 
 | ||||
|       float distance = ComputeSubLength( range, numSamplesForLengthComputation ); | ||||
|       float numFloatingCuts = distance / minimumDistance; | ||||
|       int numSamples = Mathf.CeilToInt( numFloatingCuts ); | ||||
| 
 | ||||
|       return Mathf.Max( minSamples, numSamples ); | ||||
|     } | ||||
| 
 | ||||
|     public Vector2 GetClosestPositionTo( Vector2 position, int numSegments = 20, int depth = 0 ) | ||||
|     { | ||||
|       var parameter = GetClosestParameterTo( position, numSegments, depth ); | ||||
| 
 | ||||
|       return SampleAt( parameter ); | ||||
|     } | ||||
| 
 | ||||
|     public float GetClosestParameterTo( Vector2 point, int numSegments = 20, int depth = 3 ) | ||||
|     { | ||||
|       return GetClosestParameterTo( 0, 1, point, numSegments, depth, 0 ); | ||||
|     } | ||||
| 
 | ||||
|     public float GetDistanceTo( Vector2 point, int numSegments = 20, int depth = 3 ) | ||||
|     { | ||||
|       var p = GetClosestPositionTo( point, numSegments, depth ); | ||||
|       return ( p - point ).Length(); | ||||
|     } | ||||
| 
 | ||||
|     protected float GetClosestParameterTo( float tStart, float tEnd, Vector2 position, int numSegments, int maxDepth, int currentDepth = 0 ) | ||||
|     { | ||||
|       var tNormalizer = ( tEnd - tStart ) / (float) ( numSegments - 1 ); | ||||
| 
 | ||||
|       var closestIndex = -1;       | ||||
|       var closestDistance = float.MaxValue; | ||||
|       var minT = 0f; | ||||
|       var maxT = 1f; | ||||
|       var startPoint = SampleAt( tStart ); | ||||
| 
 | ||||
|       var line = new Line2(); | ||||
|       var lastT = tStart; | ||||
| 
 | ||||
|       for ( int i = 1; i < numSegments; i++ ) | ||||
|       { | ||||
|         var t = tNormalizer * i + tStart; | ||||
|         var nextCurvePoint = SampleAt( t ); | ||||
| 
 | ||||
|         line.Set( startPoint, nextCurvePoint ); | ||||
|          | ||||
|         var closestPoint = line.ClosestPointToPoint( position ); | ||||
| 
 | ||||
|         var distance = ( position - closestPoint ).LengthSquared(); | ||||
| 
 | ||||
|         if ( distance < closestDistance ) | ||||
|         { | ||||
|           closestDistance = distance; | ||||
|           closestIndex = i - 1; | ||||
|           minT = lastT; | ||||
|           maxT = t; | ||||
|         } | ||||
| 
 | ||||
|         startPoint = nextCurvePoint; | ||||
|         lastT = t; | ||||
|       } | ||||
| 
 | ||||
|       if ( maxDepth != currentDepth ) | ||||
|       {  | ||||
|         return GetClosestParameterTo( minT, maxT, position, numSegments, maxDepth, currentDepth + 1 ); | ||||
|       } | ||||
| 
 | ||||
|       var tNormalizer2 = ( maxT - minT ) / (float)(numSegments - 1 ); | ||||
|        | ||||
|       closestDistance = float.MaxValue; | ||||
|       var closestParameter = 0.5f; | ||||
| 
 | ||||
|       for ( int i = 0; i < numSegments; i++ ) | ||||
|       { | ||||
|         var detailedT = i * tNormalizer2 + minT; | ||||
|         var sampledPoint = SampleAt( detailedT ); | ||||
| 
 | ||||
|         var distance = ( sampledPoint - position ).LengthSquared(); | ||||
| 
 | ||||
|         if ( distance < closestDistance ) | ||||
|         { | ||||
|           closestParameter = detailedT; | ||||
|           closestDistance = distance; | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       return closestParameter; | ||||
|        | ||||
|        | ||||
|     } | ||||
| 
 | ||||
|          | ||||
|   } | ||||
| 
 | ||||
|   public class CustomCurve2:Curve2 | ||||
|   { | ||||
|     Func<float,Vector2> _customFunction; | ||||
| 
 | ||||
|     public CustomCurve2( Func<float,Vector2> customFunction ) | ||||
|     { | ||||
|       _customFunction = customFunction;       | ||||
|     } | ||||
| 
 | ||||
|     public override Vector2 SampleAt( float t ) | ||||
|     { | ||||
|       return _customFunction( t ); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | @ -1,13 +1,41 @@ | |||
| using Godot; | ||||
| using System.Collections; | ||||
| using System.Collections.Generic; | ||||
| using System; | ||||
| 
 | ||||
| namespace Rokojori | ||||
| { | ||||
|   public class FresnetFrame | ||||
|   { | ||||
|     public Vector3 tangent; | ||||
|     public Vector3 normal; | ||||
|     public Vector3 binormal; | ||||
|   } | ||||
| 
 | ||||
| 	public abstract class Curve3 | ||||
| 	{ | ||||
|     float _gradientSamplingRange = Mathf.Pow( 10f, -8f ); | ||||
|     public abstract Vector3 SampleAt( float t ); | ||||
|     float _tangentSamplingRange = Mathf.Pow( 10f, -8f ); | ||||
|     public abstract Vector3 PositionAt( float t ); | ||||
| 
 | ||||
|     public virtual Quaternion RotationAt( float t ) | ||||
|     { | ||||
|       return RotationFromTangent( t ); | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     public Quaternion RotationFromTangent( float t ) | ||||
|     { | ||||
|       // RJLog.Log( "Sampling Rotation From Tangent" ); | ||||
|       var gradient = TangentAt( t ); | ||||
|       return Math3D.LookRotation( gradient, Vector3.Up ); | ||||
|     } | ||||
| 
 | ||||
|     public virtual Pose PoseAt( float t ) | ||||
|     { | ||||
|       return Pose.Create( PositionAt( t ), RotationAt( t ) ); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|      | ||||
|     public virtual void SampleMultiple( int numSamples, List<Vector3> output ) | ||||
|     { | ||||
|  | @ -21,7 +49,7 @@ namespace Rokojori | |||
|       for ( var i = 0 ; i < numSamples; i++ ) | ||||
|       { | ||||
|         var t = range.min + i * diff; | ||||
|         output.Add( SampleAt( t ) ); | ||||
|         output.Add( PositionAt( t ) ); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|  | @ -38,7 +66,7 @@ namespace Rokojori | |||
|       for ( var i = 0 ; i < numSamples; i++ ) | ||||
|       { | ||||
|         var t = range.min + i * diff; | ||||
|         var p = SampleAt( t ); | ||||
|         var p = PositionAt( t ); | ||||
|         points.Add( new Vector2( p.X, p.Z ) ); | ||||
|          | ||||
|       } | ||||
|  | @ -46,14 +74,136 @@ namespace Rokojori | |||
|       return new Path2( points ); | ||||
|     } | ||||
| 
 | ||||
|     public virtual Vector3 GradientAt( float t ) | ||||
|     public virtual Path2 SampleXYPath( int numSamples ) | ||||
|     { | ||||
|       return GradientAt( t, _gradientSamplingRange ); | ||||
|       return SampleXYPath( new Range( 0, 1 ), numSamples ); | ||||
|     } | ||||
|      | ||||
|     public virtual Vector3 GradientAt( float t, float samplingRange ) | ||||
|     public virtual Path2 SampleXYPath( Range range, int numSamples ) | ||||
|     { | ||||
|       return SampleAt( t + samplingRange ) - SampleAt( t - samplingRange ); | ||||
|       var diff = range.length / ( numSamples - 1 ) ; | ||||
|       var points = new List<Vector2>(); | ||||
|        | ||||
|       for ( var i = 0 ; i < numSamples; i++ ) | ||||
|       { | ||||
|         var t = range.min + i * diff; | ||||
|         var p = PositionAt( t ); | ||||
|         points.Add( new Vector2( p.X, p.Y ) ); | ||||
|          | ||||
|       } | ||||
| 
 | ||||
|       return new Path2( points ); | ||||
|     } | ||||
| 
 | ||||
|     public const int CurveLengthDivisions = 20; | ||||
| 
 | ||||
|     List<float> _curveLengths = null; | ||||
|      | ||||
|     public void ClearCurveLengths() | ||||
|     { | ||||
|       _curveLengths = null; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     List<float> _getCurveLengths( int divisions = CurveLengthDivisions )  | ||||
|     { | ||||
| 
 | ||||
|       if ( _curveLengths != null && _curveLengths.Count == ( divisions + 1 ) )  | ||||
|       { | ||||
|         return _curveLengths; | ||||
|       } | ||||
| 
 | ||||
| 
 | ||||
|       _curveLengths = new List<float>(); | ||||
|       _curveLengths.Add( 0f ); | ||||
| 
 | ||||
|       var before = PositionAt( 0 ); | ||||
|       var sum = 0f; | ||||
| 
 | ||||
|       for ( int i = 1; i <= divisions; i ++ ) { | ||||
| 
 | ||||
|         var p = PositionAt( (float) i / divisions ); | ||||
|         sum += p.DistanceTo( before ); | ||||
| 
 | ||||
|         _curveLengths.Add( sum ); | ||||
|         before = p; | ||||
| 
 | ||||
|       } | ||||
| 
 | ||||
|       return _curveLengths; | ||||
|     } | ||||
| 
 | ||||
|     public float UtoT( float normalizedCurveLength, int numCurveLengths = -1 )  | ||||
|     {  | ||||
|       return ComputeTforNormalizedCurveLength( normalizedCurveLength, numCurveLengths ); | ||||
|     } | ||||
| 
 | ||||
|     public float ComputeTforNormalizedCurveLength( float normalizedCurveLength, int numCurveLengths = -1 )  | ||||
|     { | ||||
| 
 | ||||
|       var curveLengths = numCurveLengths < 0 ? _getCurveLengths() : _getCurveLengths( numCurveLengths ); | ||||
| 
 | ||||
|       var i = 0; | ||||
|       numCurveLengths = curveLengths.Count; | ||||
| 
 | ||||
|       var targetCurveLength = normalizedCurveLength * curveLengths[ numCurveLengths - 1 ]; | ||||
| 
 | ||||
|       var low = 0; | ||||
|       var high = numCurveLengths - 1; | ||||
|       var comparison = 0f; | ||||
| 
 | ||||
|       while ( low <= high )  | ||||
|       { | ||||
|         i = Mathf.FloorToInt( low + ( high - low ) / 2 );  | ||||
| 
 | ||||
|         comparison = curveLengths[ i ] - targetCurveLength; | ||||
| 
 | ||||
|         if ( comparison < 0 )  | ||||
|         { | ||||
|           low = i + 1; | ||||
|         }  | ||||
|         else if ( comparison > 0 )  | ||||
|         { | ||||
|           high = i - 1; | ||||
|         }  | ||||
|         else  | ||||
|         { | ||||
|           high = i; | ||||
|           break; | ||||
|         } | ||||
| 
 | ||||
|       } | ||||
| 
 | ||||
|       i = high; | ||||
| 
 | ||||
|       if ( curveLengths[ i ] == targetCurveLength )  | ||||
|       { | ||||
|         return i / ( numCurveLengths - 1 ); | ||||
|       } | ||||
| 
 | ||||
| 
 | ||||
|       var lengthBefore = curveLengths[ i ]; | ||||
|       var lengthAfter  = curveLengths[ i + 1 ]; | ||||
| 
 | ||||
|       var segmentLength = lengthAfter - lengthBefore; | ||||
| 
 | ||||
|       var segmentFraction = ( targetCurveLength - lengthBefore ) / segmentLength; | ||||
| 
 | ||||
| 
 | ||||
|       var t = ( i + segmentFraction ) / ( numCurveLengths - 1 ); | ||||
| 
 | ||||
|       return t; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public virtual Vector3 TangentAt( float t ) | ||||
|     { | ||||
|       return TangentAt( t, _tangentSamplingRange ); | ||||
|     }  | ||||
| 
 | ||||
|     public virtual Vector3 TangentAt( float t, float samplingRange ) | ||||
|     { | ||||
|       return PositionAt( t + samplingRange ) - PositionAt( t - samplingRange ); | ||||
|     }  | ||||
| 
 | ||||
|     public virtual float ComputeLength( int numSamples ) | ||||
|  | @ -61,16 +211,131 @@ namespace Rokojori | |||
|       return ComputeSubLength( Range.Of_01 , numSamples ); | ||||
|     }     | ||||
| 
 | ||||
|     public List<FresnetFrame> ComputeFresnetFrames( int segments, bool closed ) | ||||
|     { | ||||
|       var frames = _GetFresnetFramesWithTangents( segments, closed ); | ||||
| 
 | ||||
|       _ComputeInitialNormalVector( frames ); | ||||
|       _ComputeNormalsAndBinormals( frames, closed ); | ||||
| 
 | ||||
|       return frames; | ||||
|     } | ||||
| 
 | ||||
|     List<FresnetFrame> _GetFresnetFramesWithTangents( int segments, bool closed ) | ||||
|     { | ||||
|       var frames = new List<FresnetFrame>(); | ||||
| 
 | ||||
|       for ( int i = 0; i <= segments; i++ )  | ||||
|       { | ||||
|         var fresnetFrame = new FresnetFrame(); | ||||
|         var u = (float) i / segments; | ||||
|         var t = ComputeTforNormalizedCurveLength( u ); | ||||
|         fresnetFrame.tangent = TangentAt( t ) ; | ||||
|         frames.Add( fresnetFrame ); | ||||
|       } | ||||
| 
 | ||||
|       return frames; | ||||
|     } | ||||
| 
 | ||||
|     void _ComputeInitialNormalVector( List<FresnetFrame> frames ) | ||||
|     { | ||||
|       frames[ 0 ].normal = new Vector3(); | ||||
|       frames[ 0 ].binormal = new Vector3(); | ||||
| 
 | ||||
|       var min = float.MaxValue; | ||||
| 
 | ||||
|       var tx = Math.Abs( frames[ 0 ].tangent.X ); | ||||
|       var ty = Math.Abs( frames[ 0 ].tangent.Y ); | ||||
|       var tz = Math.Abs( frames[ 0 ].tangent.Z ); | ||||
| 
 | ||||
|       var normal = Vector3.Zero; | ||||
|        | ||||
|       if ( tx <= min )  | ||||
|       { | ||||
|         min = tx; | ||||
|         normal = new Vector3( 1, 0, 0 ); | ||||
| 
 | ||||
|       } | ||||
| 
 | ||||
|       if ( ty <= min )  | ||||
|       { | ||||
|         min = ty; | ||||
|         normal = new Vector3( 0, 1, 0 ); | ||||
| 
 | ||||
|       } | ||||
| 
 | ||||
|       if ( tz <= min )  | ||||
|       { | ||||
|         normal = new Vector3( 0, 0, 1 ); | ||||
|       } | ||||
| 
 | ||||
|       var vec = frames[ 0 ].tangent.Cross( normal ).Normalized(); | ||||
| 
 | ||||
|       frames[ 0 ].normal   = frames[ 0 ].tangent.Cross( vec ); | ||||
|       frames[ 0 ].binormal = frames[ 0 ].tangent.Cross( frames[ 0 ].normal ); | ||||
|     } | ||||
| 
 | ||||
|     void _ComputeNormalsAndBinormals( List<FresnetFrame> frames, bool closed ) | ||||
|     { | ||||
|       for ( int i = 1; i < frames.Count; i++ )  | ||||
|       { | ||||
|         frames[ i ].normal   = frames[ i - 1 ].normal; | ||||
|         frames[ i ].binormal = frames[ i - 1 ].binormal; | ||||
| 
 | ||||
|         var vec = frames[ i - 1 ].tangent.Cross( frames[ i ].tangent ); | ||||
| 
 | ||||
|         if ( vec.Length() > Mathf.Epsilon )  | ||||
|         { | ||||
|           vec = vec.Normalized(); | ||||
| 
 | ||||
|           var dot = frames[ i - 1 ].tangent.Dot( frames[ i ].tangent ); | ||||
|           var angle = Mathf.Acos( Mathf.Clamp( dot, - 1, 1 ) );  | ||||
| 
 | ||||
|           var rotationMatrix = new Transform3D().Rotated( vec, angle ); | ||||
|           frames[ i ].normal = frames[ i ].normal * rotationMatrix; | ||||
|         } | ||||
| 
 | ||||
|         frames[ i ].binormal = frames[ i ].tangent.Cross( frames[ i ].normal );  | ||||
| 
 | ||||
| 	  	} | ||||
| 
 | ||||
|       if ( ! closed  )  | ||||
|       { | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
| 
 | ||||
|       var d = frames[ 0 ].normal.Dot( frames[ frames.Count - 1 ].normal ); | ||||
| 
 | ||||
| 			var theta = Mathf.Acos( Mathf.Clamp( d, - 1, 1 ) ); | ||||
| 			theta /= frames.Count; | ||||
| 
 | ||||
| 			if ( frames[ 0 ].tangent.Dot( frames[ 0 ].normal.Cross( frames[ frames.Count - 1 ].normal ) ) > 0 ) { | ||||
| 
 | ||||
| 				theta = -theta; | ||||
| 			} | ||||
| 
 | ||||
| 			for ( int i = 1; i < frames.Count; i ++ )  | ||||
|       { | ||||
|         var rm = new Transform3D().Rotated( frames[ i ].tangent, theta * i ); | ||||
|         frames[ i ].normal = frames[ i ].normal * rm; | ||||
| 				frames[ i ].binormal = frames[ i ].tangent.Cross( frames[ i ].normal ); | ||||
| 			} | ||||
|        | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     public virtual float ComputeSubLength( Range range, int numSamples ) | ||||
|     { | ||||
|       var diff = range.length / ( numSamples - 1 ) ; | ||||
|       var it = SampleAt( range.min ); | ||||
|       var it = PositionAt( range.min ); | ||||
|       var length = 0f; | ||||
| 
 | ||||
|       for ( var i = 1 ; i < numSamples; i++ ) | ||||
|       { | ||||
|         var t = range.min + i * diff; | ||||
|         var next = SampleAt( t ); | ||||
|         var next = PositionAt( t ); | ||||
|         length += ( next - it ).Length(); | ||||
|         it = next; | ||||
|       } | ||||
|  | @ -99,7 +364,7 @@ namespace Rokojori | |||
|     { | ||||
|       var parameter = GetClosestParameterTo( position, numSegments, depth ); | ||||
| 
 | ||||
|       return SampleAt( parameter ); | ||||
|       return PositionAt( parameter ); | ||||
|     } | ||||
| 
 | ||||
|     public float GetClosestParameterTo( Vector3 point, int numSegments = 20, int depth = 3 ) | ||||
|  | @ -121,7 +386,7 @@ namespace Rokojori | |||
|       var closestDistance = float.MaxValue; | ||||
|       var minT = 0f; | ||||
|       var maxT = 1f; | ||||
|       var startPoint = SampleAt( tStart ); | ||||
|       var startPoint = PositionAt( tStart ); | ||||
| 
 | ||||
|       var line = new Line3(); | ||||
|       var lastT = tStart; | ||||
|  | @ -129,7 +394,7 @@ namespace Rokojori | |||
|       for ( int i = 1; i < numSegments; i++ ) | ||||
|       { | ||||
|         var t = tNormalizer * i + tStart; | ||||
|         var nextCurvePoint = SampleAt( t ); | ||||
|         var nextCurvePoint = PositionAt( t ); | ||||
| 
 | ||||
|         line.Set( startPoint, nextCurvePoint ); | ||||
|          | ||||
|  | @ -162,7 +427,7 @@ namespace Rokojori | |||
|       for ( int i = 0; i < numSegments; i++ ) | ||||
|       { | ||||
|         var detailedT = i * tNormalizer2 + minT; | ||||
|         var sampledPoint = SampleAt( detailedT ); | ||||
|         var sampledPoint = PositionAt( detailedT ); | ||||
| 
 | ||||
|         var distance = ( sampledPoint - position ).LengthSquared(); | ||||
| 
 | ||||
|  | @ -175,7 +440,6 @@ namespace Rokojori | |||
| 
 | ||||
|       return closestParameter;       | ||||
|        | ||||
|        | ||||
|     } | ||||
| 
 | ||||
|          | ||||
|  |  | |||
|  | @ -0,0 +1,22 @@ | |||
| using Godot; | ||||
| using System.Collections; | ||||
| using System.Collections.Generic; | ||||
| using System; | ||||
| 
 | ||||
| namespace Rokojori | ||||
| { | ||||
|   public class CustomCurve3:Curve3 | ||||
|   { | ||||
|     Func<float,Vector3> _customFunction; | ||||
| 
 | ||||
|     public CustomCurve3( Func<float,Vector3> customFunction ) | ||||
|     { | ||||
|       _customFunction = customFunction;       | ||||
|     } | ||||
| 
 | ||||
|     public override Vector3 PositionAt( float t ) | ||||
|     { | ||||
|       return _customFunction( t ); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | @ -97,7 +97,7 @@ namespace Rokojori | |||
|         for ( int j = 0; j < numPoints - 1; j++ ) | ||||
|         { | ||||
|           var t = j / (float)( numPoints - 1 ); | ||||
|           spacedPoints.Add( line.SampleAt( t ) ); | ||||
|           spacedPoints.Add( line.PositionAt( t ) ); | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|  | @ -115,7 +115,7 @@ namespace Rokojori | |||
|       { | ||||
|         var t = i / ( (float) numSamples - 1 ); | ||||
| 
 | ||||
|         var p = curve.SampleAt( t ); | ||||
|         var p = curve.PositionAt( t ); | ||||
| 
 | ||||
|         points.Add( p ); | ||||
|       } | ||||
|  | @ -232,7 +232,7 @@ namespace Rokojori | |||
|       return output; | ||||
|     } | ||||
| 
 | ||||
|     public override Vector3 SampleAt( float t ) | ||||
|     public override Vector3 PositionAt( float t ) | ||||
|     { | ||||
|       if ( t < 0 ) | ||||
|       { | ||||
|  |  | |||
|  | @ -10,6 +10,11 @@ namespace Rokojori | |||
|     public Vector2 end = Vector2.Zero; | ||||
| 
 | ||||
|     public Line2( Vector2 start, Vector2 end )  | ||||
|     { | ||||
|       Set( start, end ); | ||||
|     }     | ||||
| 
 | ||||
|     public void Set( Vector2 start, Vector2 end )  | ||||
|     { | ||||
|       this.start = start; | ||||
|       this.end = end; | ||||
|  |  | |||
|  | @ -28,7 +28,7 @@ namespace Rokojori | |||
|       this.end = end; | ||||
|     }  | ||||
| 
 | ||||
|     public override Vector3 SampleAt( float t ) | ||||
|     public override Vector3 PositionAt( float t ) | ||||
|     { | ||||
|       return start.Lerp( end, t ); | ||||
|     } | ||||
|  | @ -143,7 +143,7 @@ namespace Rokojori | |||
|     public Vector3 ClosestPointTo( Line3 other ) | ||||
|     { | ||||
|       var parameters = ClosestParametersToLine( other ); | ||||
|       return SampleAt( parameters.X ); | ||||
|       return PositionAt( parameters.X ); | ||||
|     } | ||||
|      | ||||
|     public Vector3 ClosestPointTo( params Vector3[] points ) | ||||
|  |  | |||
|  | @ -4,6 +4,9 @@ using Godot; | |||
| 
 | ||||
| namespace Rokojori | ||||
| { | ||||
| 
 | ||||
|    | ||||
| 
 | ||||
|   public enum PointInPathResult | ||||
|   { | ||||
|     INSIDE, | ||||
|  | @ -29,11 +32,38 @@ namespace Rokojori | |||
|       this._points = points; | ||||
|     } | ||||
| 
 | ||||
|     public override string ToString() | ||||
|     {     | ||||
|       return "Rokojori.Path2 with " + points.Count + " points" ;  | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     public Path2( Vector2[] points ) | ||||
|     { | ||||
|       this._points = new List<Vector2>( points ); | ||||
|     } | ||||
| 
 | ||||
|     public void ApplyTransform( Transform2D transform2D ) | ||||
|     { | ||||
|       for ( int i = 0; i < _points.Count; i++ ) | ||||
|       { | ||||
|         _points[ i ] = transform2D * _points[ i ];  | ||||
|       }       | ||||
|     } | ||||
| 
 | ||||
|     public void MirrorX( float offset = 0 ) | ||||
|     { | ||||
|       for ( int i = 0; i < _points.Count; i++ ) | ||||
|       { | ||||
|         var p = _points[ i ]; | ||||
|         p.X -= offset; | ||||
|         p.X = - _points[ i ].X; | ||||
|         p.X += offset; | ||||
| 
 | ||||
|         _points[ i ] = p; | ||||
|       }  | ||||
|     } | ||||
| 
 | ||||
|     public int numPoints => _points.Count; | ||||
|     public bool empty => _points.Count == 0; | ||||
| 
 | ||||
|  | @ -200,7 +230,7 @@ namespace Rokojori | |||
| 
 | ||||
|       for ( int i = 0; i < numPoints; i++ ) | ||||
|       { | ||||
|         points.Add( Math2D.XZ( curve3.SampleAt( i * normalizer ) ) ); | ||||
|         points.Add( Math2D.XZ( curve3.PositionAt( i * normalizer ) ) ); | ||||
|       } | ||||
| 
 | ||||
|       if ( closed ) | ||||
|  | @ -239,6 +269,11 @@ namespace Rokojori | |||
|       return CheckPointInPathFast( point, max + new Vector2( 122.133544f, 129.45423f ) ); | ||||
|     } | ||||
| 
 | ||||
|     public Path2 Clone() | ||||
|     {      | ||||
|       return new Path2( new List<Vector2>( _points ) ); | ||||
|     } | ||||
| 
 | ||||
|     public bool PointInPathReliable( Vector2 point, bool checkBoundingBox = true ) | ||||
|     { | ||||
|       var min = this.leftTop; | ||||
|  | @ -369,6 +404,40 @@ namespace Rokojori | |||
|       return list; | ||||
|     } | ||||
|      | ||||
|     public void Reverse() | ||||
|     { | ||||
|       _points.Reverse(); | ||||
|       ClearCachedProperties(); | ||||
|     } | ||||
| 
 | ||||
|     public void PositionJitter( float max, Vector2 scale, Vector2 offset, RandomEngine random = null ) | ||||
|     { | ||||
|       if ( random == null ) | ||||
|       { | ||||
|         random = GodotRandom.Get(); | ||||
|       } | ||||
| 
 | ||||
|       for ( int i = 0; i < _points.Count; i++ ) | ||||
|       { | ||||
|         _points[ i ] = Noise.PerlinOffset( _points[ i ], max, scale, offset, random ); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     public string ToSVGPath() | ||||
|     { | ||||
|       var pathCommands = new List<string>(); | ||||
| 
 | ||||
|       pathCommands.Add( "M " + SVGPathCommand.SVGCoordinate( points[ 0 ] ) ); | ||||
| 
 | ||||
|       for ( int i = 1; i < points.Count; i++ ) | ||||
|       { | ||||
|         pathCommands.Add( "L " + SVGPathCommand.SVGCoordinate( points[ i ] ) ); | ||||
|       } | ||||
| 
 | ||||
|       return Lists.Join( pathCommands, " "); | ||||
|     } | ||||
| 
 | ||||
|     public MeshGeometry CreateMeshGeometry() | ||||
|     { | ||||
|       var geometry = new MeshGeometry(); | ||||
|  |  | |||
|  | @ -79,6 +79,47 @@ namespace Rokojori | |||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public static Pose Create( Vector3 position, Quaternion rotation ) | ||||
|     { | ||||
|       var p = new Pose(); | ||||
|       p.position = position; | ||||
|       p.rotation = rotation; | ||||
|       return p; | ||||
|     } | ||||
| 
 | ||||
|     public static Pose From( Pose a ) | ||||
|     { | ||||
|        var p = new Pose(); | ||||
|       p.position = a.position; | ||||
|       p.rotation = a.rotation; | ||||
|       return p; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public static Pose Lerp( Pose a, Pose b, float weight = 0.5f ) | ||||
|     { | ||||
|       var p = From( a ); | ||||
|       p.position = p.position.Lerp( b.position, weight ); | ||||
|       p.rotation = p.rotation.Slerp( b.rotation, weight ); | ||||
|       return p; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public static Pose From( Node3D node3D ) | ||||
|     { | ||||
|       var p = new Pose(); | ||||
|       p.position = node3D.GlobalPosition; | ||||
|       p.rotation = node3D.GetGlobalQuaternion(); | ||||
|       return p; | ||||
|     } | ||||
| 
 | ||||
|     public static Pose InverseFrom( Node3D node3D ) | ||||
|     { | ||||
|       var p = new Pose(); | ||||
|       p.position = - node3D.GlobalPosition; | ||||
|       p.rotation = node3D.GetGlobalQuaternion().Inverse(); | ||||
|       return p; | ||||
|     } | ||||
| 
 | ||||
|     public Pose ToGlobal( Node3D n ) | ||||
|     { | ||||
|  | @ -96,5 +137,38 @@ namespace Rokojori | |||
|       return p; | ||||
|     } | ||||
| 
 | ||||
|     public Vector3 ApplyInverse( Vector3 p ) | ||||
|     { | ||||
|       p -= position; | ||||
|       p = rotation.Inverse() * p;      | ||||
| 
 | ||||
|       return p; | ||||
|     } | ||||
| 
 | ||||
|     public Vector3 OnRightUpCircle( float radians, float size = 1 ) | ||||
|     { | ||||
|       var c = Mathf.Cos( radians ) * size; | ||||
|       var s = Mathf.Sin( radians ) * size; | ||||
| 
 | ||||
|       return s * right + c * up + position; | ||||
|     } | ||||
| 
 | ||||
|     public Vector3 ComputeCircleValues( float radians, Vector3 cos, Vector3 sin, float size ) | ||||
|     { | ||||
|       var c = Mathf.Cos( radians ) * size; | ||||
|       var s = Mathf.Sin( radians ) * size; | ||||
| 
 | ||||
|       var cValue = c * cos.X * right + | ||||
|                    c * cos.Y * up + | ||||
|                    c * cos.Z * forward; | ||||
| 
 | ||||
|       var sValue = s * sin.X * right + | ||||
|                    s * sin.Y * up + | ||||
|                    s * sin.Z * forward; | ||||
| 
 | ||||
|       return cValue + sValue + position; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|   } | ||||
| } | ||||
|  | @ -7,9 +7,19 @@ using TriangleNet.Meshing.Algorithm; | |||
| 
 | ||||
| namespace Rokojori | ||||
| { | ||||
|   using ClipperPath = List<ClipperLib.IntPoint>; | ||||
|   using ClipperShape = List<List<ClipperLib.IntPoint>>; | ||||
| 
 | ||||
|   public enum ShapeFillRule | ||||
|   { | ||||
|     NonZero, | ||||
|     EvenOdd     | ||||
|   } | ||||
| 
 | ||||
| 	public class Shape2 | ||||
| 	{ | ||||
|     public List<Path2> paths = new List<Path2>(); | ||||
|     public ShapeFillRule fillRule = ShapeFillRule.NonZero; | ||||
| 
 | ||||
|     public Shape2( params Path2[] paths ) | ||||
|     { | ||||
|  | @ -21,20 +31,66 @@ namespace Rokojori | |||
|       this.paths.AddRange( paths ); | ||||
|     } | ||||
| 
 | ||||
|     Polygon _CreateTNetPolygon() | ||||
|     public Shape2 CleanUp( ShapeFillRule fillRule = ShapeFillRule.NonZero ) | ||||
|     { | ||||
|       var clipperPath = ToClipperPaths( this ); | ||||
| 
 | ||||
|       var clipper = new ClipperLib.Clipper(); | ||||
| 
 | ||||
|       var fillType = _ConvertFillRule( fillRule ); | ||||
| 
 | ||||
|       clipperPath = ClipperLib.Clipper.SimplifyPolygons( clipperPath, fillType ); | ||||
| 
 | ||||
|       return FromClipperPaths( clipperPath ); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     Polygon _CreateTNetPolygon( bool simplify = true ) | ||||
|     { | ||||
|       // RJLog.Log( "Creating polygon", paths.Count ); | ||||
| 
 | ||||
|       if ( paths.Count == 0 ) | ||||
|       { | ||||
|         RJLog.Log( "No paths" ); | ||||
|         return null; | ||||
|       } | ||||
| 
 | ||||
|       var polyPaths = paths; | ||||
| 
 | ||||
|       if ( simplify && false ) | ||||
|       { | ||||
|         var resultPaths = Lists.Map( polyPaths, p => Path2.ToClipperPath( p ) ); | ||||
|          | ||||
|         resultPaths = ClipperLib.Clipper.SimplifyPolygons( resultPaths, ClipperLib.PolyFillType.pftEvenOdd ); | ||||
| 
 | ||||
|         polyPaths = new List<Path2>(); | ||||
| 
 | ||||
|         resultPaths.ForEach( | ||||
|         ( r ) => | ||||
|           { | ||||
|             polyPaths.Add( Path2.FromClipperPath( r ) ); | ||||
|           } | ||||
|         ); | ||||
| 
 | ||||
|       } | ||||
| 
 | ||||
|       // RJLog.Log( "Using paths", polyPaths.Count ); | ||||
| 
 | ||||
|       var polygon = new Polygon(); | ||||
|        | ||||
|    | ||||
|       var index = 0; | ||||
|       paths.ForEach( | ||||
|       var firstIsClockWise = polyPaths.Count > 0 && polyPaths[ 0 ].isClockwise; | ||||
| 
 | ||||
|       polyPaths.ForEach( | ||||
|         ( path )=> | ||||
|         { | ||||
|           var vertices = Lists.Map( path.points, p => new Vertex( p.X, p.Y ) ); | ||||
|           var isHole = path.isClockwise; | ||||
|           var center = path.center; | ||||
|           var isClockwise = path.isClockwise; | ||||
|           var isHole = firstIsClockWise != isClockwise; | ||||
| 
 | ||||
|           // RJLog.Log( "Adding contour", vertices.Count, isHole ); | ||||
|           //  RJLog.Log( "Adding contour", center, vertices.Count, isHole ? "Hole" : "Fill" ); | ||||
| 
 | ||||
| 
 | ||||
|           polygon.Add( new Contour( vertices, index ), isHole ); | ||||
|  | @ -65,6 +121,54 @@ namespace Rokojori | |||
|       return shape; | ||||
|     } | ||||
| 
 | ||||
|     static ClipperLib.PolyFillType _ConvertFillRule( ShapeFillRule fillRule ) | ||||
|     { | ||||
|       return ShapeFillRule.EvenOdd == fillRule ? ClipperLib.PolyFillType.pftEvenOdd : ClipperLib.PolyFillType.pftNonZero; | ||||
|     } | ||||
| 
 | ||||
|     public string ToSVGPath() | ||||
|     { | ||||
|       var svgPaths = Lists.Map( paths, p => p.ToSVGPath() ); | ||||
|       return Lists.Join( svgPaths, " " ); | ||||
|     } | ||||
|      | ||||
|     public static Shape2 UnionAll( List<Path2> paths ) | ||||
|     {  | ||||
|       paths.ForEach( p => | ||||
|        { | ||||
|          if ( ! p.isClockwise ) | ||||
|          { | ||||
|            p.Reverse(); | ||||
|          } | ||||
| 
 | ||||
|         //  RJLog.Log( pathIndex++, p.isClockwise );  | ||||
|        }  | ||||
|       ); | ||||
| 
 | ||||
|       var shape = new Shape2(); | ||||
|       shape.fillRule = ShapeFillRule.EvenOdd; | ||||
|       // shape.paths = paths; | ||||
|       // shape.CleanUp(); | ||||
| 
 | ||||
|        | ||||
|       shape.paths.Add( paths[ 0 ] ); | ||||
| 
 | ||||
|       for ( int i = 1; i < paths.Count; i++ ) | ||||
|       { | ||||
|         var otherShape = new Shape2(); | ||||
|         otherShape.fillRule = ShapeFillRule.EvenOdd; | ||||
|         otherShape.paths.Add( paths[ i ] ); | ||||
|         var pathsBefore = shape.paths.Count; | ||||
|         shape = Shape2.Boolean( shape, otherShape, Geometry2D.PolyBooleanOperation.Union ); | ||||
| 
 | ||||
|         // RJLog.Log( "Union:", pathsBefore, shape.paths.Count ); | ||||
| 
 | ||||
|          | ||||
|       } | ||||
| 
 | ||||
|       return shape; | ||||
|     } | ||||
| 
 | ||||
|     public static Shape2 Boolean( Shape2 a, Shape2 b, Geometry2D.PolyBooleanOperation booleanOperation, bool simplify = true ) | ||||
|     { | ||||
|       // RJLog.Log( "Using Clipper Library" ); | ||||
|  | @ -89,11 +193,14 @@ namespace Rokojori | |||
|         type = ClipperLib.ClipType.ctXor; | ||||
|       } | ||||
| 
 | ||||
|       RJLog.Log( "ShapeBool", "type: " + type, "boolOp: " + booleanOperation, "A|B >>",clipperPathsA.Count, clipperPathsB.Count ); | ||||
|       var subjectPolyType = _ConvertFillRule( a.fillRule ); | ||||
|       var clipPolyType    = _ConvertFillRule( b.fillRule ); | ||||
| 
 | ||||
|       // RJLog.Log( "ShapeBool", "type: " + type, "boolOp: " + booleanOperation, "A|B >>",clipperPathsA.Count, clipperPathsB.Count ); | ||||
| 			var clipper = new ClipperLib.Clipper(); | ||||
| 			clipper.AddPaths( clipperPathsA, ClipperLib.PolyType.ptSubject, true);  | ||||
| 			clipper.AddPaths( clipperPathsB, ClipperLib.PolyType.ptClip, true);  | ||||
| 			clipper.Execute( type, resultPaths ); | ||||
| 			clipper.Execute( type, resultPaths, subjectPolyType, clipPolyType ); | ||||
| 
 | ||||
|       if ( simplify ) | ||||
|       { | ||||
|  | @ -113,16 +220,130 @@ namespace Rokojori | |||
|       return s; | ||||
|     } | ||||
| 
 | ||||
|     public MeshGeometry CreateMeshGeometry() | ||||
|     public static Shape2 FromSVGPath( string pathData, float resolution ) | ||||
|     { | ||||
|       if ( paths.Count == 1 ) | ||||
|       var shape = new Shape2(); | ||||
|       var commands = SVGPathParser.Parse( pathData ); | ||||
| 
 | ||||
|       if ( commands != null ) | ||||
|       {  | ||||
|         /*           | ||||
|         RJLog.Log( "Commands:", commands.Count ); | ||||
| 
 | ||||
|         var commandsInfo = new List<string>(); | ||||
| 
 | ||||
|         var cindex = 0; | ||||
|         commands.ForEach( | ||||
|           ( c )=> | ||||
|           { | ||||
|             commandsInfo.Add( "[" + cindex + "] " + c.type + ":\n" + RJLog.Stringify( c.paramaters ) ); | ||||
|             cindex ++; | ||||
|           } | ||||
|         ); | ||||
| 
 | ||||
|         RJLog.Log( Lists.Join( commandsInfo, "\n" ) ); | ||||
|          | ||||
|         */ | ||||
|          | ||||
|         var extractor = new SVGPathExtractor(); | ||||
|          | ||||
|         List<Vector2> pathPoints = null; | ||||
| 
 | ||||
|         var index = 0; | ||||
|         extractor.onInstruction.AddAction( | ||||
|           ( si )=> | ||||
|           { | ||||
|             var newPathIsStarting = SVGPathInstructionType.MoveTo == si.type ||  | ||||
|             ( | ||||
|               SVGPathInstructionType.MoveTo != si.type && pathPoints == null | ||||
|             ); | ||||
| 
 | ||||
|             | ||||
|             if ( newPathIsStarting ) | ||||
|             { | ||||
|               if ( pathPoints != null ) | ||||
|               { | ||||
|                 shape.paths.Add( new Path2( pathPoints ) ); | ||||
|               } | ||||
| 
 | ||||
|               pathPoints = new List<Vector2>(); | ||||
|              | ||||
|             } | ||||
| 
 | ||||
|             var debugPoints = new List<Vector2>(); | ||||
| 
 | ||||
|             si.AddPoints( debugPoints, resolution ); | ||||
| 
 | ||||
|             var cm = si.sourceCommand; | ||||
|             var parameterLength = SVGPathCommand.GetParameterLengthForCommand( cm.type ); | ||||
| 
 | ||||
|             var cmInfo = "[" + cm.pathIndex + "]" + si.sourceCommand.type; | ||||
| 
 | ||||
|             if ( parameterLength > 0 ) | ||||
|             { | ||||
|               cmInfo += " "; | ||||
| 
 | ||||
|               var offset = parameterLength * si.sourceCommandIndex; | ||||
|                | ||||
|               for ( int i = 0; i < parameterLength; i++ ) | ||||
|               { | ||||
|                 if ( i != 0 ) | ||||
|                 { | ||||
|                   cmInfo += ", "; | ||||
|                 } | ||||
| 
 | ||||
|                 cmInfo += RegexUtility.NumberToString( cm.paramaters[ offset + i ] ); | ||||
|               } | ||||
|             } | ||||
| 
 | ||||
|             // RJLog.Log( index, newPathIsStarting ? "+" : " ",  si.type, debugPoints, ">>", cmInfo ); | ||||
| 
 | ||||
|             index++; | ||||
| 
 | ||||
|             si.AddPoints( pathPoints, resolution ); | ||||
|              | ||||
|           } | ||||
|         ); | ||||
| 
 | ||||
|         extractor.Process( commands ); | ||||
| 
 | ||||
|         if ( pathPoints != null ) | ||||
|         { | ||||
|           shape.paths.Add( new Path2( pathPoints ) ); | ||||
|         } | ||||
| 
 | ||||
|          | ||||
|       } | ||||
|       else | ||||
|       { | ||||
|         RJLog.Log( "No commands found for:", pathData ); | ||||
|         var messages = SVGPathParser.GetMessages( pathData ); | ||||
|         RJLog.Log( messages ); | ||||
| 
 | ||||
|       } | ||||
| 
 | ||||
|       // RJLog.Log( "Shape has", shape.paths.Count, "paths >> ", Lists.Map( shape.paths, p => p.points.Count + "" ) ); | ||||
| 
 | ||||
| 
 | ||||
|       return shape; | ||||
|     } | ||||
| 
 | ||||
|     public MeshGeometry CreateFillMeshGeometry() | ||||
|     { | ||||
|       /*if ( paths.Count == 1 ) | ||||
|       { | ||||
|         // RJLog.Log( "Only 1 path" ); | ||||
|         return paths[ 0 ].CreateMeshGeometry(); | ||||
|       } | ||||
|       }*/ | ||||
| 
 | ||||
|       var polygon = _CreateTNetPolygon(); | ||||
| 
 | ||||
|       if ( polygon == null ) | ||||
|       { | ||||
|         RJLog.Log( "Could not create polygon" ); | ||||
|         return null; | ||||
|       } | ||||
| 
 | ||||
|       var options = new ConstraintOptions(); | ||||
|     	var quality = new QualityOptions(); | ||||
|       var triangulator = new Dwyer();  | ||||
|  |  | |||
|  | @ -1,3 +1,4 @@ | |||
| using System; | ||||
| using System.Collections; | ||||
| using System.Collections.Generic; | ||||
| using Godot; | ||||
|  | @ -24,6 +25,8 @@ namespace Rokojori | |||
|   { | ||||
|     public Vector3 position; | ||||
|     public Quaternion rotation; | ||||
|     public Vector3 scale; | ||||
|     public float twist = 0f; | ||||
|     public float weight = 1f;  | ||||
| 
 | ||||
|     public SplineCurveTangent tangentBefore = new SplineCurveTangent(); | ||||
|  | @ -34,14 +37,16 @@ namespace Rokojori | |||
|       var scp = new SplineCurvePoint(); | ||||
|       scp.position = position; | ||||
|       scp.rotation = rotation; | ||||
|       scp.scale = scale; | ||||
|       scp.weight = weight; | ||||
|       scp.twist = twist; | ||||
|       scp.tangentBefore = tangentBefore.Clone(); | ||||
|       scp.tangentNext = tangentNext.Clone(); | ||||
| 
 | ||||
|       return scp; | ||||
|     } | ||||
| 
 | ||||
|     public SplineCurvePoint CloneForXZ( float y ) | ||||
|     public SplineCurvePoint CloneForXZ( float y = 0 ) | ||||
|     { | ||||
|       var cloned = Clone(); | ||||
|       cloned.position.Y = y; | ||||
|  | @ -51,6 +56,29 @@ namespace Rokojori | |||
|       return cloned; | ||||
|     } | ||||
| 
 | ||||
|     public SplineCurvePoint CloneForXY( float z = 0 ) | ||||
|     { | ||||
|       var cloned = Clone(); | ||||
|       cloned.position.Z = z; | ||||
|       cloned.tangentBefore.position.Z = 0; | ||||
|       cloned.tangentNext.position.Z = 0; | ||||
| 
 | ||||
|       return cloned; | ||||
|     } | ||||
| 
 | ||||
|     public SplineCurvePoint ApplyPose( Pose p ) | ||||
|     { | ||||
|       var cloned = Clone(); | ||||
|       cloned.position = p.Apply( cloned.position ); | ||||
|       cloned.rotation = cloned.rotation * p.rotation; | ||||
|       cloned.tangentBefore.position = p.Apply( cloned.tangentBefore.position ); | ||||
|       cloned.tangentNext.position = p.Apply( cloned.tangentNext.position ); | ||||
| 
 | ||||
|       return cloned; | ||||
|     } | ||||
| 
 | ||||
|      | ||||
| 
 | ||||
|   } | ||||
| 
 | ||||
|   public class SplineCurve: Curve3 | ||||
|  | @ -92,24 +120,37 @@ namespace Rokojori | |||
|       return splineCurve; | ||||
|     } | ||||
| 
 | ||||
|     public SplineCurve CloneForXZ( float y ) | ||||
|     public SplineCurve ApplyPose( Pose pose ) | ||||
|     { | ||||
|       var splineCurve = new SplineCurve(); | ||||
|       splineCurve._points = Lists.Map( points, p => p.ApplyPose( pose ) ); | ||||
|       return splineCurve; | ||||
|     } | ||||
| 
 | ||||
|     public SplineCurve CloneForXZ( float y = 0 ) | ||||
|     { | ||||
|       var splineCurve = new SplineCurve(); | ||||
|       splineCurve._points = Lists.Map( points, p => p.CloneForXZ( y ) ); | ||||
|       return splineCurve; | ||||
|     } | ||||
| 
 | ||||
|      | ||||
|     public Vector3 GetByPointIndex( float pointIndex ) | ||||
|     public SplineCurve CloneForXY( float z  = 0 ) | ||||
|     { | ||||
|       if ( pointIndex <= 0 ) | ||||
|       { | ||||
|         return _points[ 0 ].position; | ||||
|       var splineCurve = new SplineCurve(); | ||||
|       splineCurve._points = Lists.Map( points, p => p.CloneForXY( z ) ); | ||||
|       return splineCurve; | ||||
|     } | ||||
| 
 | ||||
|       if ( pointIndex >= ( _points.Count - 1 ) ) | ||||
|      | ||||
| 
 | ||||
|      | ||||
|     public Vector3 GetPositionByPointIndex( float pointIndex ) | ||||
|     { | ||||
|         return _points[ _points.Count - 1 ].position; | ||||
|       if ( pointIndex <= 0 ||  pointIndex >= ( _points.Count - 1 ) ) | ||||
|       { | ||||
|         var index = pointIndex <= 0 ? 0 : ( _points.Count - 1 ); | ||||
| 
 | ||||
|         return _points[ index ].position; | ||||
|       } | ||||
| 
 | ||||
|       var lower  = Mathf.FloorToInt( pointIndex ); | ||||
|  | @ -133,7 +174,147 @@ namespace Rokojori | |||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     public override Vector3 SampleAt( float t ) | ||||
|     public Pose GetPoseByPointIndex( float pointIndex ) | ||||
|     { | ||||
|       if ( pointIndex <= 0 ||  pointIndex >= ( _points.Count - 1 ) ) | ||||
|       { | ||||
|         var index = pointIndex <= 0 ? 0 : ( _points.Count - 1 ); | ||||
| 
 | ||||
|         return Pose.Create( _points[ index ].position, _points[ index ].rotation ); | ||||
|       } | ||||
| 
 | ||||
|       var lower  = Mathf.FloorToInt( pointIndex ); | ||||
|       var higher = Mathf.CeilToInt( pointIndex ); | ||||
| 
 | ||||
|       var lerpAmount = pointIndex - lower; | ||||
| 
 | ||||
|       var pLower  = _points[ lower ]; | ||||
|       var pHigher = _points[ higher ]; | ||||
| 
 | ||||
|       var position = RationalCubicBezier.Compute(  | ||||
|         lerpAmount, | ||||
| 
 | ||||
|         pLower.position,  | ||||
|         pLower.tangentNext.position, | ||||
|         pHigher.tangentBefore.position, | ||||
|         pHigher.position, | ||||
| 
 | ||||
|         pLower.weight,  | ||||
|         pLower.tangentNext.weight, | ||||
|         pHigher.tangentBefore.weight, | ||||
|         pHigher.weight | ||||
|          | ||||
|       ); | ||||
| 
 | ||||
|       var rotation =  pLower.rotation.Slerp( pHigher.rotation, lerpAmount ); | ||||
| 
 | ||||
|       return Pose.Create( position, rotation ); | ||||
|     } | ||||
| 
 | ||||
|     public Quaternion GetRotationByPointIndex( float pointIndex ) | ||||
|     { | ||||
|       if ( pointIndex <= 0 ||  pointIndex >= ( _points.Count - 1 ) ) | ||||
|       { | ||||
|         var index = pointIndex <= 0 ? 0 : ( _points.Count - 1 ); | ||||
| 
 | ||||
|         return _points[ index ].rotation; | ||||
|       } | ||||
| 
 | ||||
|       var lower  = Mathf.FloorToInt( pointIndex ); | ||||
|       var higher = Mathf.CeilToInt( pointIndex ); | ||||
| 
 | ||||
|       var lerpAmount = pointIndex - lower; | ||||
| 
 | ||||
|       return _points[ lower ].rotation.Slerp( _points[ higher ].rotation, lerpAmount ); | ||||
|     } | ||||
| 
 | ||||
|     public Vector3 GetScaleByPointIndex( float pointIndex ) | ||||
|     { | ||||
|       if ( pointIndex <= 0 ||  pointIndex >= ( _points.Count - 1 ) ) | ||||
|       { | ||||
|         var index = pointIndex <= 0 ? 0 : ( _points.Count - 1 ); | ||||
| 
 | ||||
|         return _points[ index ].scale; | ||||
|       } | ||||
| 
 | ||||
|       var lower  = Mathf.FloorToInt( pointIndex ); | ||||
|       var higher = Mathf.CeilToInt( pointIndex ); | ||||
| 
 | ||||
|       var lerpAmount = pointIndex - lower; | ||||
| 
 | ||||
|       return _points[ lower ].scale.Lerp( _points[ higher ].scale, lerpAmount ); | ||||
|     } | ||||
| 
 | ||||
|     public float GetTwistByPointIndex( float pointIndex ) | ||||
|     { | ||||
|       if ( pointIndex <= 0 ||  pointIndex >= ( _points.Count - 1 ) ) | ||||
|       { | ||||
|         var index = pointIndex <= 0 ? 0 : ( _points.Count - 1 ); | ||||
| 
 | ||||
|         return _points[ index ].twist; | ||||
|       } | ||||
| 
 | ||||
|       var lower  = Mathf.FloorToInt( pointIndex ); | ||||
|       var higher = Mathf.CeilToInt( pointIndex ); | ||||
| 
 | ||||
|       var lerpAmount = pointIndex - lower; | ||||
| 
 | ||||
|       return Mathf.Lerp( _points[ lower ].twist, _points[ higher ].twist, lerpAmount ); | ||||
|     } | ||||
| 
 | ||||
|     public T LerpByPointIndex<T>( float pointIndex, Func<int,T> get, Func<T,T,float,T> lerp ) | ||||
|     { | ||||
|       if ( pointIndex <= 0 ||  pointIndex >= ( _points.Count - 1 ) ) | ||||
|       { | ||||
|         var index = pointIndex <= 0 ? 0 : ( _points.Count - 1 ); | ||||
| 
 | ||||
|         return get( index ); | ||||
|       } | ||||
| 
 | ||||
|       var lower  = Mathf.FloorToInt( pointIndex ); | ||||
|       var higher = Mathf.CeilToInt( pointIndex ); | ||||
| 
 | ||||
|       var lerpAmount = pointIndex - lower; | ||||
| 
 | ||||
|       return lerp( get( lower ), get( higher ), lerpAmount ); | ||||
|     } | ||||
| 
 | ||||
|     public T SmoothStepByPointIndex<T>( float pointIndex, Func<int,T> get, Func<T,T,float,T> lerp ) | ||||
|     { | ||||
|       if ( pointIndex <= 0 ||  pointIndex >= ( _points.Count - 1 ) ) | ||||
|       { | ||||
|         var index = pointIndex <= 0 ? 0 : ( _points.Count - 1 ); | ||||
| 
 | ||||
|         return get( index ); | ||||
|       } | ||||
| 
 | ||||
|       var lower  = Mathf.FloorToInt( pointIndex ); | ||||
|       var higher = Mathf.CeilToInt( pointIndex ); | ||||
| 
 | ||||
|       var lerpAmount = pointIndex - lower; | ||||
| 
 | ||||
|       var smoothStepAmount = Mathf.SmoothStep( 0, 1, lerpAmount ); | ||||
|        | ||||
|       return lerp( get( lower ), get( higher ), smoothStepAmount ); | ||||
|     } | ||||
| 
 | ||||
|     public Vector3 SmoothStepScaleByPointIndex( float pointIndex ) | ||||
|     { | ||||
|       return SmoothStepByPointIndex<Vector3>( pointIndex,  | ||||
|              ( id ) => _points[ id ].scale,  | ||||
|              ( a, b, t ) => a.Lerp( b, t ) | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     public float SmoothStepTwistByPointIndex( float pointIndex ) | ||||
|     { | ||||
|       return SmoothStepByPointIndex<float>( pointIndex,  | ||||
|              ( id ) => _points[ id ].twist,  | ||||
|              ( a, b, t ) => Mathf.Lerp( a, b, t ) | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     public override Vector3 PositionAt( float t ) | ||||
|     { | ||||
|       if ( _points.Count <= 0 ) | ||||
|       { | ||||
|  | @ -142,7 +323,20 @@ namespace Rokojori | |||
| 
 | ||||
|       var index = NormalizedToPointIndex( t ); | ||||
| 
 | ||||
|       return GetByPointIndex( index ); | ||||
|       return GetPositionByPointIndex( index ); | ||||
|     } | ||||
| 
 | ||||
|     public override Quaternion RotationAt( float t ) | ||||
|     { | ||||
|        | ||||
|       if ( _points.Count <= 0 ) | ||||
|       { | ||||
|         return Quaternion.Identity; | ||||
|       } | ||||
| 
 | ||||
|       var index = NormalizedToPointIndex( t ); | ||||
| 
 | ||||
|       return GetRotationByPointIndex( index ); | ||||
|     } | ||||
| 
 | ||||
|     public float NormalizedToPointIndex( float normalized ) | ||||
|  |  | |||
|  | @ -31,10 +31,13 @@ namespace Rokojori | |||
|       var splineCurvePoint = new SplineCurvePoint(); | ||||
|       var splinePoint = splinePoints[ index ]; | ||||
|       splineCurvePoint.position = splinePoint.GlobalPosition; | ||||
|       splineCurvePoint.rotation = splinePoint.GetGlobalQuaternion(); | ||||
|       splineCurvePoint.scale  = splinePoint.Scale; | ||||
|       splineCurvePoint.twist = splinePoint.twist; | ||||
|       splineCurvePoint.weight = 1f; | ||||
|        | ||||
|       splineCurvePoint.tangentBefore.position = GetTangentPosition( splinePoints, index, true ); | ||||
|       splineCurvePoint.tangentNext.position   = GetTangentPosition( splinePoints, index, false ); | ||||
|       splineCurvePoint.tangentBefore.position = GetTangentPosition( splinePoints, index, true, closed ); | ||||
|       splineCurvePoint.tangentNext.position   = GetTangentPosition( splinePoints, index, false, closed ); | ||||
| 
 | ||||
|       splineCurvePoint.tangentBefore.weight = splinePoint.tangentBeforeWeight; | ||||
|       splineCurvePoint.tangentNext.weight = splinePoint.tangentNextWeight; | ||||
|  | @ -42,7 +45,54 @@ namespace Rokojori | |||
|       return splineCurvePoint; | ||||
|     } | ||||
| 
 | ||||
|     public Vector3 GetTangentPosition( List<SplinePoint> splinePoints, int index, bool before ) | ||||
|     public static Vector3 GetTangentDirectionSmoothed( float smoothing, List<SplinePoint> splinePoints, int index, bool before, bool closed ) | ||||
|     { | ||||
|       var previousIndex = MathX.SafeIndex( index - 1, splinePoints.Count, closed ); | ||||
|       var currentIndex  = index; | ||||
|       var nextIndex     = MathX.SafeIndex( index + 1, splinePoints.Count, closed ); | ||||
| 
 | ||||
|       var previousDirection = GetTangentDirection( splinePoints, previousIndex, before, closed ); | ||||
|       var currentDirection  = GetTangentDirection( splinePoints, currentIndex, before, closed ); | ||||
|       var nextDirection     = GetTangentDirection( splinePoints, nextIndex, before, closed ); | ||||
| 
 | ||||
|       var smoothed = ( previousDirection + currentDirection + nextDirection ) / 3.0f;    | ||||
| 
 | ||||
|       return currentDirection + smoothing * ( smoothed - currentDirection ); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     public static Vector3 GetTangentDirectionSmoothed( int numSamples, float smoothing, List<SplinePoint> 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<SplinePoint> splinePoints, int index, bool before, bool closed ) | ||||
|     { | ||||
|       var splinePoint = splinePoints[ index ]; | ||||
| 
 | ||||
|       return GetTangentPosition( splinePoints, index, before, closed ) - splinePoint.GlobalPosition; | ||||
|     } | ||||
| 
 | ||||
|     public static Vector3 GetTangentPosition( List<SplinePoint> splinePoints, int index, bool before, bool closed ) | ||||
|     { | ||||
|       var splinePoint = splinePoints[ index ]; | ||||
| 
 | ||||
|  |  | |||
|  | @ -182,6 +182,32 @@ namespace Rokojori | |||
|       return line.ClosestPointTo( ab, bc, ca ); | ||||
|     } | ||||
| 
 | ||||
|     public float GetHeightOfPoint( int i ) | ||||
|     { | ||||
|       var p = GetPoint( i ); | ||||
| 
 | ||||
|       var b = GetPoint( ( i + 1 ) % 3 ); | ||||
|       var a = GetPoint( ( i + 2 ) % 3 ); | ||||
| 
 | ||||
|       var ab = Line3.ClosestPointOf( p, a, b ); | ||||
| 
 | ||||
|       return ( p - ab ).Length(); | ||||
|     } | ||||
| 
 | ||||
|     public float GetArea( int i = 0 ) | ||||
|     { | ||||
|       var p = GetPoint( i ); | ||||
| 
 | ||||
|       var b = GetPoint( ( i + 1 ) % 3 ); | ||||
|       var a = GetPoint( ( i + 2 ) % 3 ); | ||||
| 
 | ||||
|       var ab = Line3.ClosestPointOf( p, a, b ); | ||||
| 
 | ||||
|       var h =  ( p - ab ).Length(); | ||||
| 
 | ||||
|       return ( ( a - b ).Length() * h ) / 2f; | ||||
|     } | ||||
| 
 | ||||
|     public Vector3 GetPoint( int i ) | ||||
|     { | ||||
|       if ( i == 0 ) | ||||
|  |  | |||
|  | @ -18,6 +18,30 @@ namespace Rokojori | |||
|       return new Vector2( v.X, v.Z ); | ||||
|     } | ||||
| 
 | ||||
|     public static Vector2 Map( Vector2 value, Vector2 inMin, Vector2 inMax, Vector2 outMin, Vector2 outMax ) | ||||
|     { | ||||
|       return new Vector2(  | ||||
|         MathX.Map( value.X, inMin.X, inMax.X, outMin.X, outMax.X ),  | ||||
|         MathX.Map( value.Y, inMin.Y, inMax.Y, outMin.Y, outMax.Y )  | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     public static Vector2 Clamp01( Vector2 v ) | ||||
|     { | ||||
|       return new Vector2( | ||||
|         MathX.Clamp01( v.X ), | ||||
|         MathX.Clamp01( v.Y ) | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     public static Vector2 Circle( float radians, float size = 1 ) | ||||
|     { | ||||
|       var x = Mathf.Cos( radians ) * size; | ||||
|       var y = Mathf.Sin( radians ) * size; | ||||
| 
 | ||||
|       return new Vector2( x, y );     | ||||
|     } | ||||
| 
 | ||||
|     public static Vector2 Fract( Vector2 a ) | ||||
|     { | ||||
|       return new Vector2( MathX.Fract( a.X ), MathX.Fract( a.Y ) ); | ||||
|  |  | |||
|  | @ -8,6 +8,15 @@ namespace Rokojori | |||
| { | ||||
|   public static class Math3D | ||||
|   { | ||||
|     public static Vector3 Clamp01( Vector3 v ) | ||||
|     { | ||||
|       return new Vector3( | ||||
|         MathX.Clamp01( v.X ), | ||||
|         MathX.Clamp01( v.Y ), | ||||
|         MathX.Clamp01( v.Z ) | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     public static Vector3 OnCircleXZ( float radians, float size = 1 ) | ||||
|     { | ||||
|       var x = Mathf.Cos( radians ) * size; | ||||
|  | @ -16,6 +25,14 @@ namespace Rokojori | |||
|       return new Vector3( x, 0, z ); | ||||
|     } | ||||
| 
 | ||||
|     public static Vector3 OnCircleXY( float radians, float size = 1 ) | ||||
|     { | ||||
|       var x = Mathf.Cos( radians ) * size; | ||||
|       var y = Mathf.Sin( radians ) * size; | ||||
| 
 | ||||
|       return new Vector3( x, y, 0 ); | ||||
|     } | ||||
| 
 | ||||
|     public static Vector3 XYasXZ( Vector2 v ) | ||||
|     { | ||||
|       return new Vector3( v.X, 0, v.Y ); | ||||
|  | @ -212,6 +229,24 @@ namespace Rokojori | |||
|       return aligned.GetRotationQuaternion(); | ||||
|     } | ||||
| 
 | ||||
|     public static float AngleXY( Vector3 direction ) | ||||
|     { | ||||
|       return Mathf.Atan2( direction.Y, direction.X ); | ||||
|     } | ||||
| 
 | ||||
|     public static float AngleXZ( Vector3 direction ) | ||||
|     { | ||||
|       return Mathf.Atan2( direction.Z, direction.X ); | ||||
|     } | ||||
| 
 | ||||
|     public static Vector3 NormalAngle( float angle ) | ||||
|     { | ||||
|       var y = Mathf.Sin( angle ); | ||||
|       var z = Mathf.Cos( angle ); | ||||
| 
 | ||||
|       return new Vector3( 0, y, z ); | ||||
|     } | ||||
| 
 | ||||
|     public static Basis AlignUp( Basis basis, Vector3 upDirection ) | ||||
|     { | ||||
|       basis.Y = upDirection; | ||||
|  | @ -250,6 +285,27 @@ namespace Rokojori | |||
|       return new Vector4( q.X, q.Y, q.Z, q.W ); | ||||
|     } | ||||
| 
 | ||||
|     public static Quaternion RotateX( float radians ) | ||||
|     { | ||||
|       if ( radians == 0 ) { return Quaternion.Identity; } | ||||
| 
 | ||||
|       return Quaternion.FromEuler( new Vector3( radians, 0, 0 ) ); | ||||
|     } | ||||
| 
 | ||||
|     public static Quaternion RotateY( float radians ) | ||||
|     { | ||||
|       if ( radians == 0 ) { return Quaternion.Identity; } | ||||
| 
 | ||||
|       return Quaternion.FromEuler( new Vector3( 0, radians, 0 ) ); | ||||
|     } | ||||
| 
 | ||||
|     public static Quaternion RotateZ( float radians ) | ||||
|     { | ||||
|       if ( radians == 0 ) { return Quaternion.Identity; } | ||||
|        | ||||
|       return Quaternion.FromEuler( new Vector3( 0, 0, radians ) ); | ||||
|     } | ||||
| 
 | ||||
|     public static void SetGlobalQuaternion( this Node3D node, Quaternion quaternion ) | ||||
|     { | ||||
|       var offset = node.GlobalPosition; | ||||
|  |  | |||
|  | @ -10,6 +10,29 @@ namespace Rokojori | |||
|   { | ||||
|     public const float fps120Delta = 1/120f; | ||||
| 
 | ||||
|     static Dictionary<int,int> factorialLookUp = new Dictionary<int, int>(); | ||||
| 
 | ||||
|     public static int Factorial( int i ) | ||||
|     {  | ||||
|       if ( factorialLookUp.ContainsKey( i ) ) | ||||
|       { | ||||
|         return factorialLookUp[ i ]; | ||||
|       } | ||||
| 
 | ||||
|       var result = 1; | ||||
| 
 | ||||
|       if ( i > 1 ) | ||||
|       { | ||||
|         var before = Factorial( i - 1 ); | ||||
|         result = before * i; | ||||
|       } | ||||
| 
 | ||||
|       factorialLookUp[ i ] = result; | ||||
| 
 | ||||
|       return factorialLookUp[ i ]; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public static float Min( params float[] values ) | ||||
|     { | ||||
|       var value = - float.MaxValue; | ||||
|  | @ -134,6 +157,7 @@ namespace Rokojori | |||
|       return Map( value, 0, inputMax, 0, outputMax ); | ||||
|     } | ||||
| 
 | ||||
|      | ||||
|     public static float MapClamped( float value, float inputMin, float inputMax,  | ||||
|     float outputMin, float outputMax ) | ||||
|     { | ||||
|  | @ -242,6 +266,18 @@ namespace Rokojori | |||
|       return ( phase - phaseStart ) / ( phaseStart - phaseEnd );  | ||||
|     } | ||||
| 
 | ||||
|     public static int SafeIndex( int index, int maxElements, bool wrap = false ) | ||||
|     { | ||||
|       if ( wrap ) | ||||
|       { | ||||
|         return MathX.Repeat( index, maxElements ); | ||||
|       } | ||||
|       else | ||||
|       { | ||||
|         return Mathf.Clamp( index, 0, maxElements - 1 ); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     public static int Repeat( int value, int range ) | ||||
|     { | ||||
|  | @ -273,6 +309,70 @@ namespace Rokojori | |||
|       return value; | ||||
|     } | ||||
| 
 | ||||
|     public static Curve Curve( float y0, float y1 ) | ||||
|     { | ||||
|       var curve = new Curve(); | ||||
|       curve.AddPoint( new Vector2( 0, y0 ) ); | ||||
|       curve.AddPoint( new Vector2( 1, y1 ) ); | ||||
| 
 | ||||
|       curve.SetPointRightMode( 0, Godot.Curve.TangentMode.Linear ); | ||||
|       curve.SetPointLeftMode( 1, Godot.Curve.TangentMode.Linear ); | ||||
| 
 | ||||
|       return curve; | ||||
|     } | ||||
| 
 | ||||
|     public static Curve Curve( float y ) | ||||
|     { | ||||
|       return Curve( y, y ); | ||||
|     } | ||||
| 
 | ||||
|     public static float CurveAngle( Curve c, float t, float samplingRange = 0.01f ) | ||||
|     { | ||||
|       var x0 = Mathf.Max( t - samplingRange, 0 ); | ||||
|       var x1 = Mathf.Min( t + samplingRange, 1 ); | ||||
| 
 | ||||
|       var y0 = c.Sample( x0 ); | ||||
|       var y1 = c.Sample( x1 ); | ||||
| 
 | ||||
|       return Mathf.Atan2( y1 - y0, x1 - x0 ); | ||||
|     } | ||||
| 
 | ||||
|     public static float CurveMaximum( Curve c, int numSamples = 20 ) | ||||
|     { | ||||
|       var max = 0f; | ||||
| 
 | ||||
|       for ( int i = 0; i < numSamples; i++ ) | ||||
|       { | ||||
|         max = Mathf.Max( max, c.Sample( (float) i / ( numSamples - 1 ) ) );  | ||||
|       } | ||||
| 
 | ||||
|       return max; | ||||
|     } | ||||
| 
 | ||||
|     public static List<float> GetCurveWeights( Curve curve, int num, bool normalize = true ) | ||||
|     { | ||||
|       var sum = 0f; | ||||
|        | ||||
|       var weights = new List<float>(); | ||||
| 
 | ||||
|       for ( int i = 0; i < num; i++ ) | ||||
|       {  | ||||
|         var t = (float)i / ( num - 1 ); | ||||
|         var w = curve.Sample( t ); | ||||
|         sum += w; | ||||
|         weights.Add( w ); | ||||
|       } | ||||
| 
 | ||||
|       if ( normalize ) | ||||
|       { | ||||
|         for ( int i = 0; i < num; i++ ) | ||||
|         { | ||||
|           weights[ i ] = weights[ i ] / sum; | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       return weights; | ||||
|     } | ||||
| 
 | ||||
|     public static float Exponent( float base_, float power  ) | ||||
|     { | ||||
|  |  | |||
|  | @ -0,0 +1,50 @@ | |||
| using System.Collections; | ||||
| using System.Collections.Generic; | ||||
| using Godot; | ||||
| 
 | ||||
| namespace Rokojori | ||||
| { | ||||
| 
 | ||||
|   public class QuadraticBezier | ||||
|   { | ||||
|     public static float Compute( | ||||
|       float t,  | ||||
|       float p0, float p1, float p2 | ||||
|     ) | ||||
|     { | ||||
|       var bp0 = ( 1 - t ) * ( 1 - t ); | ||||
|       var bp1 = 2 * ( 1 - t ) * t; | ||||
|       var bp2 = t * t; | ||||
| 
 | ||||
|       return p0 * bp0 + p1 * bp1 + p2 * bp2; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public static Vector2 Compute( | ||||
|       float t,  | ||||
|       Vector2 p0, Vector2 p1, Vector2 p2 | ||||
|     ) | ||||
|     { | ||||
|       var bp0 = ( 1 - t ) * ( 1 - t ); | ||||
|       var bp1 = 2 * ( 1 - t ) * t; | ||||
|       var bp2 = t * t; | ||||
| 
 | ||||
|       return p0 * bp0 + p1 * bp1 + p2 * bp2; | ||||
|     } | ||||
| 
 | ||||
|     public static Vector3 Compute( | ||||
|       float t,  | ||||
|       Vector3 p0, Vector3 p1, Vector3 p2 | ||||
|     ) | ||||
|     { | ||||
|       var bp0 = ( 1 - t ) * ( 1 - t ); | ||||
|       var bp1 = 2 * ( 1 - t ) * t; | ||||
|       var bp2 = t * t; | ||||
| 
 | ||||
|       return p0 * bp0 + p1 * bp1 + p2 * bp2; | ||||
|     }    | ||||
|        | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  | @ -80,17 +80,6 @@ namespace Rokojori | |||
|       get { return new Range( 0, 1 ); } | ||||
|     } | ||||
| 
 | ||||
|     public static Range Of_Zero | ||||
|     { | ||||
|       get { return new Range( 0, 0 ); } | ||||
|     } | ||||
| 
 | ||||
|     public static Range Of_One | ||||
|     { | ||||
|       get { return new Range( 1, 1 ); } | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     public static bool Contains( float min, float max, float value ) | ||||
|     { | ||||
|       return min <= value && value <= max; | ||||
|  |  | |||
|  | @ -0,0 +1,347 @@ | |||
| using System.Collections; | ||||
| using System.Collections.Generic; | ||||
| using Godot; | ||||
| using System; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| namespace Rokojori | ||||
| { | ||||
|   [Tool] | ||||
|   [GlobalClass, Icon("res://Scripts/Rokojori/Rokojori-Action-Library/Icons/Scatterer.svg") ] | ||||
|   public partial class GrassPatch:Node3D | ||||
|   { | ||||
|     [Export] | ||||
|     public MeshInstance3D output; | ||||
|      | ||||
|     [Export] | ||||
|     public int seed = 1984; | ||||
| 
 | ||||
|      | ||||
|     [Export] | ||||
|     public bool update; | ||||
| 
 | ||||
|     [Export] | ||||
|     public bool updateAlways; | ||||
| 
 | ||||
|     [Export( PropertyHint.Range, "0,100")] | ||||
|     public int blades = 20; | ||||
| 
 | ||||
|     [Export( PropertyHint.Range, "0,100")] | ||||
|     public int bladesX = 0; | ||||
| 
 | ||||
|     [Export( PropertyHint.Range, "0,100")] | ||||
|     public int bladesZ = 0; | ||||
| 
 | ||||
|     [Export] | ||||
|     public int X_numBlades; | ||||
| 
 | ||||
|     [Export] | ||||
|     public float patchSize = 2; | ||||
| 
 | ||||
|     [Export] | ||||
|     public float patchSizeX = 0; | ||||
| 
 | ||||
|     [Export] | ||||
|     public float patchSizeZ = 0; | ||||
| 
 | ||||
|     [Export] | ||||
|     public Vector2 patchOffsetPosition = new Vector2( 0.5f, 0.5f ); | ||||
| 
 | ||||
|     [Export( PropertyHint.Range, "1,20")] | ||||
|     public int bladeSegments = 3; | ||||
| 
 | ||||
|     [Export] | ||||
|     public bool createBackFaces = true; | ||||
| 
 | ||||
|     [Export] | ||||
|     public int X_numTriangles; | ||||
| 
 | ||||
| 
 | ||||
|     [Export] | ||||
|     public Curve bladeSegmentMapping = MathX.Curve( 0, 1 ); | ||||
| 
 | ||||
|     [Export] | ||||
|     public int uvSegmentColumns = 1; | ||||
| 
 | ||||
|     [Export] | ||||
|     public int uvSegmentRows = 1; | ||||
| 
 | ||||
|     [Export] | ||||
|     public Curve uvSegmentWeightsClose = MathX.Curve( 1f ); | ||||
| 
 | ||||
|     [Export] | ||||
|     public Curve uvSegmentWeightsFar = null; | ||||
| 
 | ||||
|     [Export] | ||||
|     public Curve bladeScale = MathX.Curve( 1f ); | ||||
| 
 | ||||
|     [Export] | ||||
|     public Curve bladeWidth = MathX.Curve( 0.05f ); | ||||
| 
 | ||||
|     [Export] | ||||
|     public Curve bladeWidth2 = null; | ||||
| 
 | ||||
|     [Export] | ||||
|     public Curve bladeBending = MathX.Curve( 0f ); | ||||
| 
 | ||||
|     [Export] | ||||
|     public Curve bladeBending2 = null; | ||||
| 
 | ||||
|     [Export] | ||||
|     public Curve bladeHeight = MathX.Curve( 0.4f ); | ||||
| 
 | ||||
|     [Export] | ||||
|     public Curve bladeInGround = MathX.Curve( 0.05f ); | ||||
| 
 | ||||
|     [Export] | ||||
|     public Curve positionJitter = MathX.Curve( 0.05f ); | ||||
| 
 | ||||
|     [Export] | ||||
|     public Curve scaleByDistanceX = MathX.Curve( 1f, 1f ); | ||||
| 
 | ||||
|     [Export] | ||||
|     public Curve scaleByDistanceZ = MathX.Curve( 1f, 1f ); | ||||
| 
 | ||||
|     [Export] | ||||
|     public Curve yawRotation = MathX.Curve( 0f, 1f ); | ||||
| 
 | ||||
|     [Export] | ||||
|     public Curve randomRotation = MathX.Curve( 0f, 20f ); | ||||
| 
 | ||||
|     [Export (PropertyHint.Range, "0,1")] | ||||
|     public float filterTreshold = 1; | ||||
| 
 | ||||
|     [Export] | ||||
|     public Vector2 filterScale = new Vector2( 1, 1 ); | ||||
| 
 | ||||
|     [Export] | ||||
|     public Vector2 filterOffset = new Vector2( 0, 0 ); | ||||
| 
 | ||||
|     [Export] | ||||
|     public Vector2 positionToFilter = new Vector2( 0, 0 ); | ||||
| 
 | ||||
|     public override void _Process( double delta ) | ||||
|     { | ||||
|       if ( ! ( update || updateAlways ) ) | ||||
|       { | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       update = false;  | ||||
| 
 | ||||
|       CreatePatch(); | ||||
|     } | ||||
| 
 | ||||
|     float _maxWidth = 0; | ||||
| 
 | ||||
|     Vector3 _patchOffset = Vector3.Zero; | ||||
| 
 | ||||
|     public void CreatePatch() | ||||
|     { | ||||
|       var mg = new MeshGeometry(); | ||||
| 
 | ||||
|       var cellSizeX = ( patchSizeX + patchSize ) / ( bladesX + blades ); | ||||
|       var cellSizeZ = ( patchSizeZ + patchSize ) / ( bladesZ + blades ); | ||||
| 
 | ||||
|       var random = new LCG(); | ||||
|       random.SetSeed( 1712 + seed ); | ||||
| 
 | ||||
|       _patchOffset = new Vector3( - ( patchSizeX + patchSize ) * patchOffsetPosition.X, 0, - ( patchSizeZ + patchSize ) * patchOffsetPosition.Y ); | ||||
| 
 | ||||
|       _maxWidth = MathX.CurveMaximum( bladeWidth ); | ||||
| 
 | ||||
|       X_numBlades = 0; | ||||
| 
 | ||||
|       var allBladesX = bladesX + blades; | ||||
|       var allBladesZ = bladesZ + blades; | ||||
| 
 | ||||
|       for ( int i = 0; i < allBladesX; i++ ) | ||||
|       { | ||||
|         var x = ( i + 0.5f ) * cellSizeX; | ||||
| 
 | ||||
|         for ( int j = 0; j < allBladesZ; j++ ) | ||||
|         { | ||||
|           var z = ( j + 0.5f ) * cellSizeZ; | ||||
| 
 | ||||
|           random.SetSeed( i * 11223 + j *12895 + seed ); | ||||
|           var position = new Vector3( x, 0, z ); | ||||
|           var yaw = random.Sample( yawRotation ); | ||||
|           var rotationY = yaw * Mathf.Pi * 2f; | ||||
|           var maxRotation = random.Sample( randomRotation ); | ||||
|           var rotationOther = random.Next() * Mathf.DegToRad( maxRotation ); | ||||
|           var rotationX = random.Next() * rotationOther; | ||||
|           var rotationZ = rotationOther - rotationX; | ||||
|           var positionOffset = Math3D.OnCircleXZ( random.Next() * Mathf.Pi * 2f ) * random.Sample( positionJitter ); | ||||
|           var worldPosition = position + _patchOffset + positionOffset; | ||||
| 
 | ||||
|           var filterValue = Noise.PerlinXZ(  Math3D.XYasXZ( filterScale ) * worldPosition + Math3D.XYasXZ( filterOffset ) + -GlobalPosition * Math3D.XYasXZ( positionToFilter )); | ||||
| 
 | ||||
|           var trsf = new Transform3D(  | ||||
|               new Basis( Math3D.RotateY( rotationY ) * Math3D.RotateX( rotationX ) * Math3D.RotateZ( rotationZ )),  | ||||
|               worldPosition | ||||
|             ); | ||||
| 
 | ||||
|           var bladeMG = CreateBlade( random, worldPosition ); | ||||
| 
 | ||||
|           if ( filterValue > filterTreshold ) | ||||
|           { | ||||
|             continue; | ||||
|           }               | ||||
|           | ||||
|           bladeMG.ApplyTransform( trsf ); | ||||
|            | ||||
|           mg.Add( bladeMG );   | ||||
| 
 | ||||
|           if ( createBackFaces ) | ||||
|           { | ||||
|             var blade2MG = bladeMG.Clone(); | ||||
|             blade2MG.FlipNormalDirection();    | ||||
|             mg.Add( blade2MG );        | ||||
|           }           | ||||
| 
 | ||||
| 
 | ||||
|           X_numBlades ++; | ||||
|         } | ||||
|       }       | ||||
| 
 | ||||
|       X_numTriangles = mg.indices.Count / 3; | ||||
|       output.Mesh = mg.GenerateMesh(); | ||||
|     } | ||||
| 
 | ||||
|     MeshGeometry CreateBlade( RandomEngine random, Vector3 position ) | ||||
|     { | ||||
|       var bmg = new MeshGeometry(); | ||||
| 
 | ||||
|        | ||||
|       var inGround = random.Sample( bladeInGround ); | ||||
|       var height   = random.Sample( bladeHeight ); | ||||
| 
 | ||||
|       var size = height + inGround; | ||||
| 
 | ||||
|       var distancesToCenter = new Vector2( position.X / ( patchSizeX + patchSize ), position.Z / ( patchSizeZ  + patchSize ) ); | ||||
|       distancesToCenter = distancesToCenter.Abs(); | ||||
| 
 | ||||
|       var minDistance = distancesToCenter.Length(); | ||||
|       var scaling = scaleByDistanceX.Sample( distancesToCenter.X ) * scaleByDistanceZ.Sample( distancesToCenter.Y ); | ||||
|       scaling *= random.Sample( bladeScale ); | ||||
|       size *= scaling; | ||||
|       var firstIsTriangle = false;   | ||||
|       var lastIsTriangle  = false; | ||||
| 
 | ||||
|       var bladeWidthLerp = random.Next(); | ||||
|       var bladeBendLerp  = random.Next(); | ||||
| 
 | ||||
|       var uvMin = new Vector2( 0, 0 ); | ||||
|       var uvMax = new Vector2( 1, 1 ); | ||||
| 
 | ||||
|       var uvSegments = uvSegmentRows * uvSegmentColumns; | ||||
| 
 | ||||
|       if ( uvSegments > 1 ) | ||||
|       { | ||||
|         var index = random.IntegerExclusive( uvSegments ); | ||||
| 
 | ||||
|         if ( uvSegmentWeightsClose != null ) | ||||
|         { | ||||
|          | ||||
|           var weightsClose = MathX.GetCurveWeights( uvSegmentWeightsClose, uvSegments ); | ||||
| 
 | ||||
|           if ( uvSegmentWeightsFar != null ) | ||||
|           { | ||||
|             var weightsFar   = MathX.GetCurveWeights( uvSegmentWeightsFar, uvSegments );  | ||||
|             var distanceFade = minDistance; | ||||
| 
 | ||||
|             for ( int i = 0; i < uvSegments; i++ ) | ||||
|             { | ||||
|               weightsClose[ i ] += distanceFade * ( weightsFar[ i ] - weightsClose[ i ] ); | ||||
|             } | ||||
|           } | ||||
| 
 | ||||
|           index = random.IndexFromUnnormalizedWeights( weightsClose );     | ||||
|         }    | ||||
|          | ||||
|         var x = index % uvSegmentColumns; | ||||
|         var y = index / uvSegmentRows; | ||||
| 
 | ||||
|         var xSize = 1f / uvSegmentRows; | ||||
|         var ySize = 1f / uvSegmentColumns; | ||||
| 
 | ||||
|         uvMin.X = x * xSize; | ||||
|         uvMin.Y = y * ySize; | ||||
| 
 | ||||
|         uvMax = uvMin + new Vector2( xSize, ySize ); | ||||
|       } | ||||
| 
 | ||||
|       for ( int i = 0; i <= bladeSegments; i++ ) | ||||
|       { | ||||
|         var t = (float)i / bladeSegments; | ||||
|         t = bladeSegmentMapping.Sample( t ); | ||||
| 
 | ||||
|         var v = 1f - t; | ||||
| 
 | ||||
|         var y = size * t - inGround; | ||||
|         var bw2 = bladeWidth2 == null ? bladeWidth : bladeWidth2; | ||||
|         var bb2 = bladeBending2 == null ? bladeBending : bladeBending2;  | ||||
| 
 | ||||
|         var width = Mathf.Lerp( bladeWidth.Sample( v ), bw2.Sample( v ), bladeWidthLerp ); | ||||
|         width *= scaling; | ||||
|         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 ); | ||||
| 
 | ||||
|         if ( width < 0.005f && ( i == 0 || ( i == bladeSegments - 1 )) ) | ||||
|         { | ||||
|           bmg.vertices.Add( new Vector3( 0, y, bending ) ); | ||||
|           bmg.normals.Add( bendingNormal ); | ||||
|           bmg.uvs.Add( MapUV( new Vector2( 0.5f, v ), uvMin, uvMax ) ); | ||||
| 
 | ||||
|           if ( i == 0 ){ firstIsTriangle = true; } | ||||
|           if ( i == bladeSegments - 1 ){ lastIsTriangle = true; } | ||||
|         }  | ||||
|         else | ||||
|         { | ||||
|           bmg.vertices.Add( new Vector3( -width, y, bending ) ); | ||||
|           bmg.vertices.Add( new Vector3(  width, y, bending ) ); | ||||
| 
 | ||||
|           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 ) ); | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       for ( int i = 0; i < bladeSegments; i++ ) | ||||
|       { | ||||
|         var index = i * 2; | ||||
| 
 | ||||
|         if ( firstIsTriangle && index != 0 ) | ||||
|         { | ||||
|           index --; | ||||
|         } | ||||
|          | ||||
|         if ( i == 0 && firstIsTriangle ) | ||||
|         { | ||||
|           bmg.AddTriangle( index + 0, index + 1, index + 2 ); | ||||
|         } | ||||
|         else if ( i == ( bladeSegments - 1 ) && lastIsTriangle ) | ||||
|         { | ||||
|           bmg.AddTriangle( index + 1, index + 0, index + 2 ); | ||||
|         } | ||||
|         else  | ||||
|         { | ||||
|           bmg.AddQuad( index + 0, index + 1, index + 2, index + 3 ); | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       return bmg; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     Vector2 MapUV( Vector2 uv, Vector2 min, Vector2 max ) | ||||
|     { | ||||
|       uv = Math2D.Clamp01( uv ); | ||||
|       return Math2D.Map( uv, Vector2.Zero, Vector2.One, min, max ); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,192 @@ | |||
| using System.Collections; | ||||
| using System.Collections.Generic; | ||||
| using Godot; | ||||
| using System; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| namespace Rokojori | ||||
| { | ||||
|   [Tool] | ||||
|   [GlobalClass, Icon("res://Scripts/Rokojori/Rokojori-Action-Library/Icons/Spline.svg") ] | ||||
|   public partial class LeafMesh:Node3D | ||||
|   { | ||||
|     [Export] | ||||
|     public MeshInstance3D output; | ||||
| 
 | ||||
|     [Export] | ||||
|     public int seed; | ||||
|      | ||||
|     [Export] | ||||
|     public bool update; | ||||
| 
 | ||||
|     [Export] | ||||
|     public bool updateAlways; | ||||
| 
 | ||||
|     [Export] | ||||
|     public float height; | ||||
| 
 | ||||
|     [Export] | ||||
|     public Curve centerStrandShape; | ||||
| 
 | ||||
|     [Export] | ||||
|     public float centerStrandShapeTopOffset; | ||||
| 
 | ||||
|     [Export] | ||||
|     public float centerStrandShapeBottomOffset; | ||||
| 
 | ||||
|     [Export] | ||||
|     public int numStrands; | ||||
| 
 | ||||
|     [Export] | ||||
|     public Curve strandHeightPositions; | ||||
| 
 | ||||
|     [Export] | ||||
|     public Curve strandShape; | ||||
| 
 | ||||
|     [Export] | ||||
|     public Curve strandSize; | ||||
| 
 | ||||
|     [Export] | ||||
|     public Curve strandWidth; | ||||
| 
 | ||||
|     [Export] | ||||
|     public Curve strandLength; | ||||
| 
 | ||||
|     [Export] | ||||
|     public Curve strandAngles; | ||||
| 
 | ||||
|     [Export] | ||||
|     public int strandResolution = 20; | ||||
| 
 | ||||
|     [Export] | ||||
|     public float positionJitterMax; | ||||
| 
 | ||||
|     [Export] | ||||
|     public Vector2 positionJitterScale; | ||||
| 
 | ||||
|     [Export] | ||||
|     public Vector2 positionJitterOffset; | ||||
|      | ||||
| 
 | ||||
| 
 | ||||
|     public override void _Process( double delta ) | ||||
|     { | ||||
|       if ( ! ( update || updateAlways ) ) | ||||
|       { | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       update = false; | ||||
| 
 | ||||
|       try | ||||
|       { | ||||
|         Create(); | ||||
|       } | ||||
|       catch ( System.Exception e ) | ||||
|       { | ||||
|         RJLog.Error( e ); | ||||
|         updateAlways = false; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     void Create() | ||||
|     { | ||||
|       var paths = new List<Path2>(); | ||||
| 
 | ||||
|       for ( int i = 0; i < numStrands; i++ ) | ||||
|       { | ||||
|         var part = CreateStrandPart( i ); | ||||
| 
 | ||||
|         var t = i / (float) ( numStrands - 1 ); | ||||
|         var ht = strandHeightPositions.Sample( t ); | ||||
|         var y = ht * height; | ||||
|          | ||||
|         var turns = strandAngles.Sample( t ); | ||||
|         var rotation = Mathf.DegToRad( turns * 360f ); | ||||
|          | ||||
|         var trsf = new Transform2D( rotation, new Vector2( 0, y ) ); | ||||
| 
 | ||||
|         part.ApplyTransform( trsf ); | ||||
| 
 | ||||
|         paths.Add( part ); | ||||
| 
 | ||||
|         var mirrored = part.Clone(); | ||||
|         mirrored.MirrorX(); | ||||
| 
 | ||||
|         paths.Add( mirrored ); | ||||
|       } | ||||
| 
 | ||||
|       var centerPathPoints = new List<Vector2>(); | ||||
| 
 | ||||
|       for ( int i = 0; i < strandResolution; i++ ) | ||||
|       {  | ||||
|         var t = i / (float)( strandResolution - 1 ); | ||||
|         var y = strandHeightPositions.Sample( t ); | ||||
|         var x = centerStrandShape.Sample( t ); | ||||
| 
 | ||||
|         var size = ( height + centerStrandShapeTopOffset + centerStrandShapeBottomOffset ); | ||||
|         var p = new Vector2( x, y * size - centerStrandShapeBottomOffset ); | ||||
| 
 | ||||
|         centerPathPoints.Add( p ); | ||||
|       } | ||||
| 
 | ||||
|       for ( int i = 0; i < strandResolution; i++ ) | ||||
|       {  | ||||
|         var index = ( strandResolution - 1 ) - i; | ||||
| 
 | ||||
|         var p = centerPathPoints[ index ]; | ||||
|         p.X = -p.X; | ||||
|         centerPathPoints.Add( p ); | ||||
|       } | ||||
| 
 | ||||
| 
 | ||||
|       var centerPath = new Path2( centerPathPoints ); | ||||
|       paths.Add( centerPath ); | ||||
| 
 | ||||
|       var random = new LCG(); | ||||
|       random.SetSeed( seed ); | ||||
|       paths.ForEach( p => p.PositionJitter( positionJitterMax, positionJitterScale, positionJitterOffset, random ) ); | ||||
| 
 | ||||
|       var shape = Shape2.UnionAll( paths ); | ||||
| 
 | ||||
|       // RJLog.Log( shape.ToSVGPath() ); | ||||
| 
 | ||||
| 
 | ||||
|       var fillGeometry = shape.CreateFillMeshGeometry(); | ||||
|       var mesh = fillGeometry.GenerateMesh(); | ||||
|       // RJLog.Log( "First Path:", shape.paths[ 0 ].points, shape.paths, "mesh", fillGeometry.indices.Count ); | ||||
|       output.Mesh = mesh; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     Path2 CreateStrandPart( int index ) | ||||
|     { | ||||
|       var points = new List<Vector2>(); | ||||
|       var u = index / (float)( numStrands - 1 ); | ||||
|       var s = strandSize.Sample( u ); | ||||
|       var l = strandLength.Sample( u ) * s; | ||||
|       var w = strandWidth.Sample( u ) * s; | ||||
| 
 | ||||
|       for ( int i = 0; i < strandResolution; i++ ) | ||||
|       { | ||||
|         var t = i / (float)( strandResolution - 1 );         | ||||
|         var p = new Vector2( t * l, strandShape.Sample( t ) * w ); | ||||
| 
 | ||||
|         points.Add( p ); | ||||
|       } | ||||
| 
 | ||||
|       for ( int i = 0; i < strandResolution; i++ ) | ||||
|       { | ||||
|         var reverseIndex = ( strandResolution - 1 ) - i; | ||||
|         var lowerPoint = points[ reverseIndex ]; | ||||
|         lowerPoint.Y = -lowerPoint.Y; | ||||
|         points.Add( lowerPoint ); | ||||
|       } | ||||
| 
 | ||||
|       var path = new Path2( points ); | ||||
|       return path; | ||||
|     } | ||||
| 
 | ||||
|   } | ||||
| } | ||||
|  | @ -128,7 +128,7 @@ namespace Rokojori | |||
| 
 | ||||
|       var shape = Path2.Boolean( pathA, pathB, booleanOperation ); | ||||
| 
 | ||||
|       var meshGeometry = shape.CreateMeshGeometry(); | ||||
|       var meshGeometry = shape.CreateFillMeshGeometry(); | ||||
| 
 | ||||
|       meshInstance3D.Mesh = meshGeometry.GenerateMesh(); | ||||
| 
 | ||||
|  | @ -165,7 +165,7 @@ namespace Rokojori | |||
| 
 | ||||
|       var shape = Shape2.Boolean( circleAreaShape, islands, Geometry2D.PolyBooleanOperation.Difference ); | ||||
| 
 | ||||
|       var meshGeometry = shape.CreateMeshGeometry(); | ||||
|       var meshGeometry = shape.CreateFillMeshGeometry(); | ||||
| 
 | ||||
|       meshInstance3D.Mesh = meshGeometry.GenerateMesh(); | ||||
| 
 | ||||
|  |  | |||
|  | @ -9,8 +9,8 @@ namespace Rokojori | |||
| { | ||||
|   public class MeshGeometry | ||||
|   { | ||||
|     public List<Vector3> vertices; | ||||
|     public List<int> indices; | ||||
|     public List<Vector3> vertices = new List<Vector3>(); | ||||
|     public List<int> indices = new List<int>(); | ||||
| 
 | ||||
|     public List<Vector3> normals; | ||||
|     public List<Vector2> uvs; | ||||
|  | @ -20,6 +20,23 @@ namespace Rokojori | |||
|     public int numTriangles => indices.Count / 3; | ||||
|      | ||||
| 
 | ||||
|     public void ApplyTransform( Transform3D trsf ) | ||||
|     { | ||||
|       for ( int i = 0; i < vertices.Count; i++ ) | ||||
|       { | ||||
|         vertices[ i ] = trsf * vertices[ i ]; | ||||
|         normals[ i ] = trsf.Basis.GetRotationQuaternion() * normals[ i ]; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     public void Offset( Vector3 offset ) | ||||
|     { | ||||
|       for ( int i = 0; i < vertices.Count; i++ ) | ||||
|       { | ||||
|         vertices[ i ] = vertices[ i ] + offset; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     public void ForEachTriangle( Action<int,int,int,int> callback ) | ||||
|     { | ||||
|       var index = 0; | ||||
|  | @ -36,6 +53,62 @@ namespace Rokojori | |||
|       } | ||||
|     } | ||||
| 
 | ||||
|     static int GetUVIndex( int u, int v, int segments ) | ||||
|     { | ||||
|       return u * ( segments + 1 ) + v; | ||||
|     } | ||||
| 
 | ||||
|     public static MeshGeometry CreateFromUVFunction( Func<Vector2,Pose> uv, int uSegments, int vSegments ) | ||||
|     { | ||||
|       var mg = new MeshGeometry(); | ||||
|        | ||||
|       for ( int i = 0; i <= uSegments; i++ ) | ||||
|       { | ||||
|         var uI = (float)i / uSegments; | ||||
| 
 | ||||
|         for ( int j = 0; j <= vSegments; j++ ) | ||||
|         { | ||||
|           var vJ = (float)j / vSegments; | ||||
| 
 | ||||
|           var uvIJ = new Vector2( uI, vJ ); | ||||
| 
 | ||||
|           var pose = uv( uvIJ ); | ||||
| 
 | ||||
|           mg.vertices.Add( pose.position ); | ||||
|           mg.normals.Add( pose.up ); | ||||
|           mg.uvs.Add( uvIJ ); | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
| 
 | ||||
|       for ( int i = 0; i < uSegments; i++ ) | ||||
|       { | ||||
|         var u0 = i; | ||||
|         var u1 = i + 1; | ||||
| 
 | ||||
|         for ( int j = 0; j < vSegments; j++ ) | ||||
|         { | ||||
|           var v0 = j; | ||||
|           var v1 = j + 1; | ||||
| 
 | ||||
|           var a = GetUVIndex( u0, v0, vSegments ); | ||||
|           var b = GetUVIndex( u1, v0, vSegments ); | ||||
|           var c = GetUVIndex( u1, v1, vSegments ); | ||||
|           var d = GetUVIndex( u0, v1, vSegments ); | ||||
| 
 | ||||
|           mg.AddTriangle( a, b, d ); | ||||
|           mg.AddTriangle( b, c, d ); | ||||
| 
 | ||||
|           // mg.AddTriangle( d, b, a ); | ||||
|           // mg.AddTriangle( d, c, b ); | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       return mg; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     public MeshGeometry UniqueTriangles() | ||||
|     { | ||||
|       var mg = new MeshGeometry(); | ||||
|  | @ -128,17 +201,103 @@ namespace Rokojori | |||
|       colors[ c ] = color; | ||||
|     } | ||||
| 
 | ||||
|     public MeshGeometry( bool normals = true, bool uvs = true, bool colors = false, bool uvs2 = false ) | ||||
|     { | ||||
|       Initialize( normals, uvs, colors, uvs2 ); | ||||
|     } | ||||
| 
 | ||||
|     public void Initialize( bool normals = true, bool uvs = true, bool colors = false, bool uvs2 = false ) | ||||
|     {       | ||||
|       this.vertices = new List<Vector3>(); | ||||
|       this.indices = new List<int>(); | ||||
|        | ||||
|       this.normals = normals ? new List<Vector3>() : null; | ||||
|       this.uvs = uvs ? new List<Vector2>() : null; | ||||
|       this.uv2s = uvs2 ? new List<Vector2>() : null; | ||||
|       this.colors = colors ? new List<Color>() : null;       | ||||
|     } | ||||
| 
 | ||||
|     public MeshGeometry Clone() | ||||
|     { | ||||
|       var mg = new MeshGeometry(); | ||||
|       mg.vertices = Lists.Clone( vertices ); | ||||
|       mg.indices = Lists.Clone( indices ); | ||||
|       mg.normals = Lists.Clone( normals ); | ||||
| 
 | ||||
|       mg.uvs = Lists.Clone( uvs ); | ||||
|       mg.uv2s = Lists.Clone( uv2s ); | ||||
|       mg.colors = Lists.Clone( colors ); | ||||
| 
 | ||||
|       return mg; | ||||
|     } | ||||
| 
 | ||||
|     public void FlipNormalDirection() | ||||
|     { | ||||
|       for ( int i = 0; i < normals.Count; i++ ) | ||||
|       { | ||||
|         normals[ i ] = -normals[ i ]; | ||||
|       } | ||||
| 
 | ||||
|       for ( int i =0 ; i < indices.Count; i += 3 ) | ||||
|       { | ||||
|         var b = indices[ i ]; | ||||
|         indices[ i ] = indices[ i + 2 ]; | ||||
|         indices[ i + 2 ] = b; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     public void Add( MeshGeometry mg ) | ||||
|     { | ||||
|       var mappedIndices = new Dictionary<int,int>(); | ||||
| 
 | ||||
|       for ( int i = 0; i < mg.indices.Count; i++ ) | ||||
|       { | ||||
|         var mgIndex = mg.indices[ i ]; | ||||
| 
 | ||||
|         if ( ! mappedIndices.ContainsKey( mgIndex ) ) | ||||
|         { | ||||
|           var newIndex = vertices.Count; | ||||
| 
 | ||||
|           if ( mgIndex >= mg.vertices.Count || mgIndex < 0 ) | ||||
|           { | ||||
|             RJLog.Log( "Out of range:", i, ">>", mgIndex, mg.vertices.Count, mg.indices ); | ||||
|           } | ||||
| 
 | ||||
|           var v = mg.vertices[ mgIndex ];  | ||||
| 
 | ||||
|           vertices.Add( v ); | ||||
| 
 | ||||
|           if ( normals != null && mg.normals != null) | ||||
|           { | ||||
|             normals.Add( mg.normals[ mgIndex ] ); | ||||
|           } | ||||
| 
 | ||||
|           if ( uvs != null && mg.uvs != null) | ||||
|           { | ||||
|             uvs.Add( mg.uvs[ mgIndex ] ); | ||||
|           } | ||||
| 
 | ||||
|           if ( colors != null && mg.colors != null) | ||||
|           { | ||||
|             colors.Add( mg.colors[ mgIndex ] ); | ||||
|           } | ||||
| 
 | ||||
|           if ( uv2s != null && mg.uv2s != null) | ||||
|           { | ||||
|             uv2s.Add( mg.uv2s[ mgIndex ] ); | ||||
|           } | ||||
| 
 | ||||
| 
 | ||||
|           mappedIndices[ mgIndex ] = newIndex; | ||||
|         } | ||||
| 
 | ||||
|         indices.Add( mappedIndices[ mgIndex ] ); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     public void AddTriangle( int a, int b, int c )     | ||||
|     { | ||||
|       Lists.Add( indices, a, b, c ); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     public void AddTriangle(  | ||||
|       Vector3 va, Vector3 vb, Vector3 vc,  | ||||
|  | @ -187,6 +346,11 @@ namespace Rokojori | |||
|       AddTriangle( vc, vd, va,  nc, nd, na,  uvc, uvd, uva ); | ||||
|     } | ||||
| 
 | ||||
|     public void AddQuad( int lt, int rt, int lb, int rb )     | ||||
|     { | ||||
|       Lists.Add( indices, lb, rt, lt, rt, lb, rb ); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     public void AddQuad( | ||||
|       Vector3 va, Vector3 vb, Vector3 vc, Vector3 vd, | ||||
|  |  | |||
|  | @ -5,6 +5,12 @@ using System.Collections.Generic; | |||
| 
 | ||||
| namespace Rokojori | ||||
| {   | ||||
|   public enum SplineAutoOrientationMode | ||||
|   { | ||||
|     Tangent, | ||||
|     Next_Neighbor | ||||
|   } | ||||
| 
 | ||||
|   [Tool] | ||||
|   [GlobalClass, Icon("res://Scripts/Rokojori/Rokojori-Action-Library/Icons/Spline.svg") ] | ||||
|   public partial class Spline : Node3D | ||||
|  | @ -19,6 +25,19 @@ namespace Rokojori | |||
|     [Export] | ||||
|     public int editorResolution = 20; | ||||
| 
 | ||||
|     [Export] | ||||
|     public bool autoOrienation = false; | ||||
| 
 | ||||
|     [Export] | ||||
|     public SplineAutoOrientationMode autoOrientationMode = SplineAutoOrientationMode.Tangent; | ||||
| 
 | ||||
|     [Export( PropertyHint.Range, "-1,1")] | ||||
|     public float autoOrientationTangentAdjustment = 0f; | ||||
| 
 | ||||
|     [Export]   | ||||
|     public Vector3 autoOrientationUp = Vector3.Up; | ||||
| 
 | ||||
| 
 | ||||
|     [Export] | ||||
|     public bool updateAlways = false; | ||||
| 
 | ||||
|  | @ -119,12 +138,12 @@ namespace Rokojori | |||
| 
 | ||||
|       renderedResolution = editorResolution; | ||||
| 
 | ||||
|       var lastPoint = ToLocal( curve.SampleAt( 0 ) ); | ||||
|       var lastPoint = ToLocal( curve.PositionAt( 0 ) ); | ||||
| 
 | ||||
|       for ( int i = 1; i < resolution; i++ ) | ||||
|       { | ||||
|         var t = i / (float) (resolution - 1 ); | ||||
|         var point = ToLocal( curve.SampleAt( t ) );  | ||||
|         var point = ToLocal( curve.PositionAt( t ) );  | ||||
| 
 | ||||
|         linePoints.Add( lastPoint ); | ||||
|         linePoints.Add( point ); | ||||
|  | @ -152,6 +171,52 @@ namespace Rokojori | |||
|      | ||||
|     int renderedResolution = -100; | ||||
| 
 | ||||
|     void AutoOrientate() | ||||
|     { | ||||
|       var list = Nodes.GetDirectChildren<SplinePoint>( this ); | ||||
| 
 | ||||
|       if ( list.Count <= 1 ) | ||||
|       { | ||||
|         return; | ||||
|       } | ||||
|          | ||||
|       if ( SplineAutoOrientationMode.Next_Neighbor == autoOrientationMode ) | ||||
|       { | ||||
|         for ( int i = 0; i < list.Count; i++ ) | ||||
|         { | ||||
|           var sp = list[ i ]; | ||||
| 
 | ||||
|           if ( i == ( list.Count - 1 ) ) | ||||
|           { | ||||
|             if ( closed ) | ||||
|             { | ||||
|               var first = list[ 0 ]; | ||||
|               sp.LookAt( first.GlobalPosition, autoOrientationUp ); | ||||
|             } | ||||
| 
 | ||||
|             continue; | ||||
|           }           | ||||
| 
 | ||||
|           var next = list[ i + 1 ]; | ||||
|           sp.LookAt( next.GlobalPosition, autoOrientationUp ); | ||||
|         } | ||||
|       } | ||||
|       else if ( SplineAutoOrientationMode.Tangent == autoOrientationMode ) | ||||
|       { | ||||
| 
 | ||||
|         for ( int i = 0; i < list.Count; i++ ) | ||||
|         { | ||||
|           var sp = list[ i ];    | ||||
| 
 | ||||
|           var tangentNext = SplineCurveCreator.GetTangentDirectionSmoothed( 1, autoOrientationTangentAdjustment, list, i, false, closed ); | ||||
|            | ||||
|           sp.LookAt( sp.GlobalPosition + tangentNext, autoOrientationUp ); | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|        | ||||
|     } | ||||
| 
 | ||||
|     public override void _Process( double delta ) | ||||
|     { | ||||
|       var changed = updateAlways; | ||||
|  | @ -162,6 +227,11 @@ namespace Rokojori | |||
|         changed = true; | ||||
|       } | ||||
| 
 | ||||
|       if ( autoOrienation ) | ||||
|       { | ||||
|         AutoOrientate();         | ||||
|       } | ||||
| 
 | ||||
|       Nodes.ForEachDirectChild<SplinePoint>( this,  | ||||
|         sp =>  | ||||
|         { | ||||
|  |  | |||
|  | @ -88,6 +88,10 @@ namespace Rokojori | |||
| 
 | ||||
| #endif | ||||
| 
 | ||||
|      | ||||
|     [Export] | ||||
|     public float twist = 0; | ||||
|      | ||||
|     [Export] | ||||
|     public SplinePointTangentMode tangentMode; | ||||
| 
 | ||||
|  | @ -110,5 +114,8 @@ namespace Rokojori | |||
|     [Export( PropertyHint.Range, "0,1")] | ||||
|     public float symmetricTangentLength = 0f; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,262 @@ | |||
| 
 | ||||
| using Godot; | ||||
| using Rokojori; | ||||
| using System.Collections.Generic; | ||||
| 
 | ||||
| namespace Rokojori | ||||
| {   | ||||
|   public enum TubeSegmentMode | ||||
|   { | ||||
|     Fixed_Division, | ||||
|     Depending_On_Length, | ||||
|     Maximum_Of_Both, | ||||
|     Minimum_Of_Both | ||||
|   } | ||||
| 
 | ||||
|   public enum ShapeOrientationMode | ||||
|   { | ||||
|     Auto, | ||||
|     Original, | ||||
|     Reverse | ||||
|   } | ||||
| 
 | ||||
|   [Tool] | ||||
|   [GlobalClass, Icon("res://Scripts/Rokojori/Rokojori-Action-Library/Icons/Spline.svg") ] | ||||
|   public partial class Tube : Node3D | ||||
|   { | ||||
|     [Export] | ||||
|     public MeshInstance3D output; | ||||
| 
 | ||||
|     [Export] | ||||
|     public Spline spline; | ||||
| 
 | ||||
|     [Export] | ||||
|     public TubeSegmentMode segmentMode = TubeSegmentMode.Fixed_Division; | ||||
| 
 | ||||
|     [Export] | ||||
|     public int fixedSplineSegmentDivisions = 20; | ||||
| 
 | ||||
|     [Export] | ||||
|     public float splineSegmentLength = 2; | ||||
| 
 | ||||
| 
 | ||||
|     [Export] | ||||
|     public bool undistortSplineSegments = true; | ||||
| 
 | ||||
|     [Export] | ||||
|     public Curve twistCurve; | ||||
| 
 | ||||
|     [Export] | ||||
|     public float radius; | ||||
| 
 | ||||
|     [Export] | ||||
|     public int radialSegments = 8; | ||||
| 
 | ||||
|     [Export] | ||||
|     public Curve radiusSizeCurve; | ||||
| 
 | ||||
|     [Export] | ||||
|     public Curve radiusWidthCurve; | ||||
| 
 | ||||
|     [Export] | ||||
|     public Curve radiusHeightCurve; | ||||
| 
 | ||||
|     [Export] | ||||
|     public bool scaleRadiusByPathTransforms = true; | ||||
| 
 | ||||
|     [Export] | ||||
|     public TubeShape[] shapes; | ||||
| 
 | ||||
|     [Export] | ||||
|     public bool update; | ||||
| 
 | ||||
|     [Export] | ||||
|     public bool updateAlways; | ||||
| 
 | ||||
| 
 | ||||
| #if TOOLS | ||||
|      | ||||
|     public void DrawGizmo( EditorNode3DGizmoPlugin gizmoPlugin, EditorNode3DGizmo gizmo ) | ||||
|     { | ||||
|       /* | ||||
|       ClearCurveCache(); | ||||
| 
 | ||||
|       var curve = GetCurve(); | ||||
| 
 | ||||
|       gizmo.Clear(); | ||||
| 
 | ||||
|       var linePoints = new List<Vector3>(); | ||||
| 
 | ||||
|       int resolution = editorResolution <= 0 ? 20 : editorResolution; | ||||
| 
 | ||||
|       renderedResolution = editorResolution; | ||||
| 
 | ||||
|       var lastPoint = ToLocal( curve.SampleAt( 0 ) ); | ||||
| 
 | ||||
|       for ( int i = 1; i < resolution; i++ ) | ||||
|       { | ||||
|         var t = i / (float) (resolution - 1 ); | ||||
|         var point = ToLocal( curve.SampleAt( t ) );  | ||||
| 
 | ||||
|         linePoints.Add( lastPoint ); | ||||
|         linePoints.Add( point ); | ||||
| 
 | ||||
|         lastPoint = point; | ||||
|       } | ||||
| 
 | ||||
|       for ( int i = 0; i < curve.points.Count; i++ ) | ||||
|       { | ||||
|         var p = curve.points[ i ]; | ||||
|         linePoints.Add( ToLocal( p.position ) ); | ||||
|         linePoints.Add( ToLocal( p.tangentBefore.position ) ); | ||||
| 
 | ||||
|         linePoints.Add( ToLocal( p.position ) ); | ||||
|         linePoints.Add( ToLocal( p.tangentNext.position ) ); | ||||
|       } | ||||
| 
 | ||||
|       var material = gizmoPlugin.GetMaterial( "main", gizmo ); | ||||
| 
 | ||||
|       gizmo.AddLines( linePoints.ToArray(), material, false ); | ||||
| 
 | ||||
|       */ | ||||
|     } | ||||
| 
 | ||||
|     public override void _Process( double delta ) | ||||
|     { | ||||
|       if ( ! ( updateAlways || update ) ) | ||||
|       { | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       if ( spline == null ) | ||||
|       { | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       update = false; | ||||
| 
 | ||||
|        | ||||
|       var mg = CreateMesh(); | ||||
|       output.Mesh = mg.GenerateMesh(); | ||||
| 
 | ||||
|       UpdateGizmos(); | ||||
| 
 | ||||
|     } | ||||
|      | ||||
| #endif | ||||
| 
 | ||||
|     MeshGeometry CreateMesh() | ||||
|     { | ||||
|       var curve = spline.GetCurve(); | ||||
|       int splineSegments = fixedSplineSegmentDivisions; | ||||
| 
 | ||||
|       if ( TubeSegmentMode.Fixed_Division != segmentMode ) | ||||
|       { | ||||
|         splineSegments = Mathf.CeilToInt( curve.ComputeLength( curve.points.Count * 3 ) / splineSegmentLength ); | ||||
| 
 | ||||
|         if ( TubeSegmentMode.Maximum_Of_Both == segmentMode ) | ||||
|         { | ||||
|           splineSegments = Mathf.Max( splineSegments, fixedSplineSegmentDivisions ); | ||||
|         }  | ||||
|         if ( TubeSegmentMode.Minimum_Of_Both == segmentMode ) | ||||
|         { | ||||
|           splineSegments = Mathf.Min( splineSegments, fixedSplineSegmentDivisions ); | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       var shapesList = new List<TubeShape>(); | ||||
| 
 | ||||
|       if ( shapes != null && shapes.Length > 0 ) | ||||
|       { | ||||
|         shapesList.AddRange( shapes ); | ||||
|         Lists.Sort( shapesList, s => s.tubePosition ); | ||||
| 
 | ||||
|         shapesList.ForEach( s => s.ClearCache() ); | ||||
|          | ||||
|       } | ||||
| 
 | ||||
|        | ||||
|       var mg = MeshGeometry.CreateFromUVFunction( | ||||
|         ( Vector2 uv ) => | ||||
|         { | ||||
|           var t = undistortSplineSegments ? curve.ComputeTforNormalizedCurveLength( uv.Y, splineSegments ) : uv.Y; | ||||
|           var index = curve.NormalizedToPointIndex( t ); | ||||
|           var pose = curve.PoseAt( t );         | ||||
|           var radiusSize = radius * ( radiusSizeCurve == null ? 1 : radiusSizeCurve.Sample( t ) ); | ||||
|           var sizeX = radiusSize; | ||||
|           var sizeY = radiusSize; | ||||
|           var twistOffset = 0f; | ||||
| 
 | ||||
|           if ( radiusWidthCurve != null ) | ||||
|           { | ||||
|             sizeX *= radiusWidthCurve.Sample( t ); | ||||
|           } | ||||
| 
 | ||||
|           if ( radiusHeightCurve != null ) | ||||
|           { | ||||
|             sizeY *= radiusHeightCurve.Sample( t ); | ||||
|           } | ||||
| 
 | ||||
|           if ( scaleRadiusByPathTransforms ) | ||||
|           { | ||||
|              | ||||
|             var splineScale = curve.SmoothStepScaleByPointIndex( index ); | ||||
| 
 | ||||
|             if ( scaleRadiusByPathTransforms ) | ||||
|             { | ||||
|               sizeX *= splineScale.X; | ||||
|               sizeY *= splineScale.Y; | ||||
|             } | ||||
|           } | ||||
| 
 | ||||
|           if ( twistCurve != null ) | ||||
|           { | ||||
|             twistOffset = Mathf.DegToRad( twistCurve.Sample( t ) ); | ||||
|           } | ||||
| 
 | ||||
|           twistOffset += Mathf.DegToRad( curve.SmoothStepTwistByPointIndex( index ) * 360 ); | ||||
| 
 | ||||
|           var twistRotation = Math3D.RotateZ( twistOffset ).Normalized(); | ||||
| 
 | ||||
|           var scale = new Vector3( sizeX, sizeY, 0 ); | ||||
| 
 | ||||
|           if ( shapesList.Count > 0 ) | ||||
|           {  | ||||
|             if ( shapesList.Count == 1 ) | ||||
|             { | ||||
|               return shapesList[ 0 ].GetPose( undistortSplineSegments, splineSegments, radialSegments, pose, uv, scale, twistRotation ); | ||||
|             } | ||||
| 
 | ||||
|             var lerpResult = Lists.LerpIndex( shapesList, uv.Y, s => s.tubePosition ); | ||||
| 
 | ||||
| 
 | ||||
|             var closestShape = shapesList[ lerpResult.closestIndex ]; | ||||
|             var secondShape  = shapesList[ lerpResult.secondIndex ]; | ||||
| 
 | ||||
|             var closestPose = closestShape.GetPose( undistortSplineSegments, splineSegments, radialSegments, pose, uv, scale, twistRotation ); | ||||
|             var secondPose  = secondShape.GetPose( undistortSplineSegments, splineSegments, radialSegments, pose, uv, scale, twistRotation ); | ||||
| 
 | ||||
|             var smoothLerp = Mathf.SmoothStep( 0f, 1f, lerpResult.lerpAmount ); | ||||
|             return Pose.Lerp( closestPose, secondPose, smoothLerp ); | ||||
|              | ||||
|           }           | ||||
|           else  | ||||
|           { | ||||
|             var angle = uv.X * 2f * Mathf.Pi * ( sizeX / sizeY ); | ||||
|             var circlePose = Pose.Create( Vector3.Zero, pose.rotation * Math3D.RotateZ( angle ) ); | ||||
|             var circleStart = Vector3.Up * radiusSize * scale; | ||||
| 
 | ||||
|             var positionOnCircle = circlePose.Apply( circleStart ); | ||||
| 
 | ||||
|             return Pose.Create( positionOnCircle + pose.position, circlePose.rotation ); | ||||
|           } | ||||
| 
 | ||||
|         },  | ||||
|         radialSegments, splineSegments  | ||||
|       ); | ||||
| 
 | ||||
|       return mg; | ||||
|     } | ||||
| 
 | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,105 @@ | |||
| 
 | ||||
| using Godot; | ||||
| using Rokojori; | ||||
| using System.Collections.Generic; | ||||
| 
 | ||||
| namespace Rokojori | ||||
| {   | ||||
|   [Tool] | ||||
|   [GlobalClass, Icon("res://Scripts/Rokojori/Rokojori-Action-Library/Icons/Spline.svg") ] | ||||
|   public partial class TubeShape:Node | ||||
|   {  | ||||
|     [Export] | ||||
|     public Spline spline; | ||||
| 
 | ||||
|     [Export] | ||||
|     public Node3D pivotPose; | ||||
| 
 | ||||
|     [Export] | ||||
|     public float tubePosition; | ||||
| 
 | ||||
|     [Export] | ||||
|     public float scale = 1f; | ||||
|      | ||||
|     [Export] | ||||
|     public float widthScale = 1f; | ||||
| 
 | ||||
|     [Export] | ||||
|     public float heightScale = 1f; | ||||
| 
 | ||||
|     [Export] | ||||
|     public ShapeOrientationMode shapeOrientationMode = ShapeOrientationMode.Auto; | ||||
| 
 | ||||
|     bool cached = false; | ||||
| 
 | ||||
|     bool shapeForward = true; | ||||
|     SplineCurve radialShape; | ||||
|      | ||||
| 
 | ||||
|     public void ClearCache() | ||||
|     { | ||||
|       cached = false; | ||||
|     } | ||||
| 
 | ||||
|     public Pose GetPose( bool undistort, int splineSegments, int radialSegments, Pose pose, Vector2 uv, Vector3 scale, Quaternion twistRotation ) | ||||
|     { | ||||
|       if ( ! cached ) | ||||
|       { | ||||
|         radialShape = spline.GetCurve(); | ||||
|         shapeForward = true; | ||||
|          | ||||
|         if ( pivotPose != null ) | ||||
|         { | ||||
|           radialShape = radialShape.ApplyPose( Pose.InverseFrom( pivotPose ) ); | ||||
|         } | ||||
| 
 | ||||
|         radialShape = radialShape.CloneForXY();         | ||||
| 
 | ||||
|         if  ( ShapeOrientationMode.Original != shapeOrientationMode ) | ||||
|         { | ||||
|           if ( ShapeOrientationMode.Auto == shapeOrientationMode ) | ||||
|           { | ||||
|             var path = radialShape.SampleXYPath( radialSegments ); | ||||
|             shapeForward = path.isClockwise; | ||||
|           } | ||||
| 
 | ||||
|           if ( ShapeOrientationMode.Reverse == shapeOrientationMode ) | ||||
|           { | ||||
|             shapeForward = false; | ||||
|           } | ||||
|            | ||||
|         } | ||||
| 
 | ||||
|         cached = true; | ||||
|       } | ||||
|        | ||||
| 
 | ||||
|       scale.X *= widthScale * this.scale; | ||||
|       scale.Y *= heightScale * this.scale; | ||||
| 
 | ||||
|       var radialT = ! shapeForward ? uv.X : ( 1f - uv.X ); | ||||
|       radialT = undistort ?  | ||||
|                   radialShape.UtoT( radialT, splineSegments ) :  | ||||
|                   radialT;   | ||||
| 
 | ||||
|       var shapePosition = radialShape.PositionAt( radialT ) * scale; | ||||
|       var shapeTangent  = radialShape.TangentAt( radialT, 1f / radialSegments ) * scale;  | ||||
| 
 | ||||
| 
 | ||||
|       shapePosition = twistRotation * shapePosition; | ||||
|       shapeTangent  = twistRotation * shapeTangent; | ||||
| 
 | ||||
|       var angle = Math3D.AngleXY( shapeTangent ) + Mathf.Pi; | ||||
|       var rotation = Math3D.RotateZ( angle ); | ||||
|          | ||||
|       var combinedRotation = pose.rotation * rotation; | ||||
| 
 | ||||
|       var rotatedShapePosition = Vector3.Zero; | ||||
| 
 | ||||
|       rotatedShapePosition = pose.rotation * shapePosition;             | ||||
| 
 | ||||
|       return Pose.Create( rotatedShapePosition + pose.position, combinedRotation ); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| } | ||||
|  | @ -64,8 +64,8 @@ namespace Rokojori | |||
|       { | ||||
|         var t = i / (float) ( numPoints - 1 ); | ||||
| 
 | ||||
|         var position = equalSpacedCurve.SampleAt( t ); | ||||
|         var rawDirection = equalSpacedCurve.GradientAt( t, 1f / 100f ); | ||||
|         var position = equalSpacedCurve.PositionAt( t ); | ||||
|         var rawDirection = equalSpacedCurve.TangentAt( t, 1f / 100f ); | ||||
|         var direction = rawDirection; | ||||
|         var length = direction.Length(); | ||||
|          | ||||
|  |  | |||
|  | @ -44,8 +44,8 @@ namespace Rokojori | |||
|       { | ||||
|         var t = i / (float) ( numPoints - 1 ); | ||||
| 
 | ||||
|         var position = equalSpacedCurve.SampleAt( t ); | ||||
|         var rawDirection = equalSpacedCurve.GradientAt( t, 1f / 100f ); | ||||
|         var position = equalSpacedCurve.PositionAt( t ); | ||||
|         var rawDirection = equalSpacedCurve.TangentAt( t, 1f / 100f ); | ||||
|         var direction = rawDirection; | ||||
|         var length = direction.Length(); | ||||
|          | ||||
|  |  | |||
|  | @ -64,6 +64,23 @@ namespace Rokojori | |||
|       return MathX.Fract( Mathf.Sin ( d ) * 43758.5453123f ); | ||||
|     } | ||||
| 
 | ||||
|     public static float PerlinXZ( Vector3 position ) | ||||
|     { | ||||
|       return Perlin( Math2D.XZ( position ) ); | ||||
|     }  | ||||
| 
 | ||||
|     public static Vector2 PerlinOffset( Vector2 position, float max, Vector2 scale, Vector2 offset, RandomEngine random ) | ||||
|     { | ||||
|       var transformedPoint = position * scale + offset; | ||||
|        | ||||
|       var value = Perlin( transformedPoint ) * max; | ||||
| 
 | ||||
|       var angle = random.AngleRadians();  | ||||
|       var movement = Math2D.Circle( angle, value ); | ||||
| 
 | ||||
|       return position + movement; | ||||
|     } | ||||
| 
 | ||||
|     public static float Perlin( Vector2 position ) | ||||
|     { | ||||
|       var index = position.Floor(); | ||||
|  |  | |||
|  | @ -21,6 +21,11 @@ namespace Rokojori | |||
|       return this.Next()*( b - a ) + a; | ||||
|     } | ||||
| 
 | ||||
|     public float Sample( Curve curve ) | ||||
|     { | ||||
|       return curve.Sample( Next() ); | ||||
|     } | ||||
| 
 | ||||
|     public float Polar() | ||||
|     { | ||||
|       return this.Next() * 2f - 1f; | ||||
|  | @ -79,6 +84,11 @@ namespace Rokojori | |||
|       return new Vector2( x, y ); | ||||
|     } | ||||
| 
 | ||||
|     public float AngleRadians() | ||||
|     { | ||||
|       return Range( 0, Mathf.Pi * 2f ); | ||||
|     } | ||||
| 
 | ||||
|     public Vector3 InCube() | ||||
|     { | ||||
|       return new Vector3( Polar(), Polar(), Polar() ); | ||||
|  | @ -144,7 +154,6 @@ namespace Rokojori | |||
|       return IntegerInclusive( 0, max ); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     public int IntegerExclusive( int min, int max )  | ||||
|     { | ||||
|       var nextValue = this.Next(); | ||||
|  | @ -311,6 +320,23 @@ namespace Rokojori | |||
|       return _FindElementIndexWithWeights( weights, Next() * sumWeights ); | ||||
|     } | ||||
| 
 | ||||
|     public int IndexFromCurveWeights( Curve curve, int numIndices ) | ||||
|     { | ||||
|       var weights = new List<float>(); | ||||
|       var sumWeights = 0f; | ||||
| 
 | ||||
|       for ( int i = 0; i < numIndices; i++ ) | ||||
|       { | ||||
|         var t = (float)i / ( numIndices - 1 ); | ||||
|         var w = curve.Sample( t ); | ||||
| 
 | ||||
|         weights.Add( w );  | ||||
|         sumWeights += w; | ||||
|       } | ||||
| 
 | ||||
|       return IndexFromUnnormalizedWeights( weights, sumWeights ); | ||||
|     } | ||||
| 
 | ||||
|      | ||||
|     public int IndexFromNormalizedWeights( List<float> weights, float sumWeights = 0 ) | ||||
|     { | ||||
|  |  | |||
|  | @ -62,6 +62,12 @@ namespace Rokojori | |||
|       data.Sort( ( a, b ) => Compare( a, b ) ); | ||||
|     } | ||||
| 
 | ||||
|     public static void SortList( List<T> data, Func<T,float> getValue ) | ||||
|     { | ||||
|       var vc = Create( getValue ); | ||||
|       vc.Sort( data ); | ||||
|     } | ||||
| 
 | ||||
|     public void Sort( int index, int count, List<T> data ) | ||||
|     { | ||||
|       ClearCache(); | ||||
|  |  | |||
|  | @ -13,6 +13,7 @@ namespace Rokojori | |||
| 
 | ||||
|     public bool hasError => _hasError; | ||||
|      | ||||
| 
 | ||||
|     void AddMatcher( LexerMatcher matcher ) | ||||
|     { | ||||
|       var list = _modes.ContainsKey( matcher.mode ) ? _modes[ matcher.mode ] : null; | ||||
|  | @ -77,7 +78,7 @@ namespace Rokojori | |||
|       var lexerEvent = new LexerEvent( "", 0, -2 ); | ||||
| 
 | ||||
|       var numTries = 0; | ||||
|       var maxTries = 1000; | ||||
|       var maxTries = 1000000; | ||||
| 
 | ||||
|       while ( offset < source.Length && numTries < maxTries) | ||||
|       { | ||||
|  |  | |||
|  | @ -2,11 +2,135 @@ using System.Collections; | |||
| using System.Collections.Generic; | ||||
| using System.Text; | ||||
| using System; | ||||
| using Godot; | ||||
| 
 | ||||
| namespace Rokojori | ||||
| { | ||||
|    | ||||
| 
 | ||||
|   public class Lists | ||||
|   { | ||||
|     public static void Sort<T>( List<T> data, Func<T,float> getValue ) | ||||
|     { | ||||
|       ValueSorter<T>.SortList( data, getValue ); | ||||
|     } | ||||
| 
 | ||||
|     public class IndexLerpResult | ||||
|     { | ||||
|       public int closestIndex = -1; | ||||
|       public int secondIndex = -1; | ||||
|       public float lerpAmount = 0.5f; | ||||
|     } | ||||
| 
 | ||||
|     public static List<T> Clone<T>( List<T> data ) | ||||
|     { | ||||
|       if ( data == null ) | ||||
|       { | ||||
|         return null; | ||||
|       } | ||||
|        | ||||
|       var cloned = new List<T>(); | ||||
|       cloned.AddRange( data ); | ||||
|       return cloned; | ||||
|     } | ||||
| 
 | ||||
|     public static IndexLerpResult LerpIndex<T>( List<T> data, float value, Func<T,float> getValue, bool sort = false ) | ||||
|     { | ||||
|       if ( sort ) | ||||
|       { | ||||
|         Sort( data, getValue ); | ||||
|       } | ||||
| 
 | ||||
|       var result = new IndexLerpResult(); | ||||
|       result.closestIndex = ClosestIndex<T>( data, value, getValue ); | ||||
|       result.secondIndex  = SecondClosestIndex<T>( data, result.closestIndex, value, getValue ); | ||||
| 
 | ||||
|       if ( result.closestIndex  == result.secondIndex ) | ||||
|       { | ||||
|         return result; | ||||
|       } | ||||
| 
 | ||||
|       var closestValue = getValue( data[ result.closestIndex ] ); | ||||
|       var secondValue  = getValue( data[ result.secondIndex ] ); | ||||
| 
 | ||||
|        | ||||
|       var min = closestValue; | ||||
|       var max = secondValue; | ||||
|       var flip = false; | ||||
| 
 | ||||
|       if ( closestValue > secondValue ) | ||||
|       { | ||||
|         flip = true; | ||||
|         min = secondValue; | ||||
|         max = closestValue; | ||||
|       } | ||||
| 
 | ||||
|       result.lerpAmount = MathX.Normalize( value, min, max ); | ||||
| 
 | ||||
|       if ( flip ) | ||||
|       { | ||||
|         result.lerpAmount = 1f - result.lerpAmount; | ||||
|       } | ||||
| 
 | ||||
|       return result;       | ||||
| 
 | ||||
|     }  | ||||
| 
 | ||||
|     public static int SecondClosestIndex<T>( List<T> data, int closest, float compareValue, Func<T,float> getValue, bool sort = false ) | ||||
|     { | ||||
|       if ( sort ) | ||||
|       { | ||||
|         Sort( data, getValue ); | ||||
|       } | ||||
| 
 | ||||
|       if ( data.Count == 1 ) | ||||
|       { | ||||
|         return 0; | ||||
|       } | ||||
| 
 | ||||
|       if ( closest == 0 ) | ||||
|       { | ||||
|         return closest + 1; | ||||
|       } | ||||
| 
 | ||||
|       if ( closest == data.Count - 1 ) | ||||
|       { | ||||
|         return data.Count - 2; | ||||
|       } | ||||
| 
 | ||||
|       var before = data[ closest - 1 ]; | ||||
|       var after  = data[ closest + 1 ]; | ||||
| 
 | ||||
|       var bD = Mathf.Abs( getValue( before ) - compareValue ); | ||||
|       var aD = Mathf.Abs( getValue( after ) - compareValue ); | ||||
| 
 | ||||
|       if ( bD < aD ) | ||||
|       { | ||||
|         return closest - 1 ; | ||||
|       } | ||||
| 
 | ||||
|       return closest + 1; | ||||
|     } | ||||
| 
 | ||||
|     public static int ClosestIndex<T>( List<T> data, float compareValue, Func<T,float> getValue ) | ||||
|     { | ||||
|       var index = -1; | ||||
|       var distance = float.MaxValue; | ||||
| 
 | ||||
|       for ( int i = 0 ; i < data.Count; i++ ) | ||||
|       { | ||||
|         var d = Mathf.Abs( getValue( data[ i ] ) - compareValue ); | ||||
| 
 | ||||
|         if ( d < distance ) | ||||
|         { | ||||
|           index = i; | ||||
|           distance = d; | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       return index; | ||||
|     } | ||||
| 
 | ||||
|     public static List<int> CollectIndices<T>( List<T> list, Func<T,bool> evaluator ) | ||||
|     { | ||||
|       var output = new List<int>(); | ||||
|  |  | |||
|  | @ -128,7 +128,28 @@ namespace Rokojori | |||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public static List<T> GetFieldsOfType<T>( object instance ) where T:class  | ||||
|     public static List<FieldInfo> GetFieldInfosOfType<T>( object instance ) where T:class  | ||||
|     { | ||||
|       var type = instance.GetType(); | ||||
|       var fields = type.GetFields(); | ||||
|       var list = new List<FieldInfo>(); | ||||
| 
 | ||||
|       for ( int i = 0; i < fields.Length; i++ ) | ||||
|       { | ||||
|         var value = fields[ i ].GetValue( instance ); | ||||
|         var tValue = value as T; | ||||
| 
 | ||||
|         if ( tValue != null ) | ||||
|         { | ||||
|           list.Add( fields[ i ] ); | ||||
|         }  | ||||
|       } | ||||
| 
 | ||||
|       return list; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     public static List<T> GetFieldValuesOfType<T>( object instance ) where T:class  | ||||
|     { | ||||
|       var type = instance.GetType(); | ||||
|       var fields = type.GetFields(); | ||||
|  |  | |||
|  | @ -0,0 +1,107 @@ | |||
| 
 | ||||
| using Godot; | ||||
| 
 | ||||
| namespace Rokojori | ||||
| {  | ||||
| 
 | ||||
|   public class SVGArc | ||||
|   { | ||||
|     public static Vector2 GetPointOnCenterArc( Vector2 r, float theta, float delta ) | ||||
|     { | ||||
|       return new Vector2( | ||||
|        r.X * Mathf.Cos( theta ) * Mathf.Cos( delta ) - r.Y * Mathf.Sin( theta ) * Mathf.Sin( delta ), | ||||
|        r.X * Mathf.Sin( theta ) * Mathf.Cos( delta ) + r.Y * Mathf.Cos( theta ) * Mathf.Sin( delta ) | ||||
|       ); | ||||
| 
 | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public static void EndpointToCenterArc(  | ||||
|       Vector2 start, Vector2 end, Vector2 radius, float xAngle, bool flagA, bool flagS,  | ||||
|       out Vector2 centerPosition,  | ||||
|       out Vector2 centerRadius,  | ||||
|       out Vector2 centerAngles  | ||||
|       ) | ||||
|     { | ||||
|       var rX = Mathf.Abs( radius.X ); | ||||
|       var rY = Mathf.Abs( radius.Y ); | ||||
| 
 | ||||
|       var dx2 = ( start.X - end.X ) / 2f; | ||||
|       var dy2 = ( start.Y - end.Y ) / 2f; | ||||
| 
 | ||||
|       var x1p =  Mathf.Cos( xAngle ) * dx2 + Mathf.Sin( xAngle ) * dy2; | ||||
|       var y1p = -Mathf.Sin( xAngle ) * dx2 + Mathf.Cos( xAngle ) * dy2; | ||||
| 
 | ||||
|       var rxs = rX * rX; | ||||
|       var rys = rY * rY; | ||||
|       var x1ps = x1p * x1p; | ||||
|       var y1ps = y1p * y1p; | ||||
| 
 | ||||
|       var cr = x1ps / rxs + y1ps / rys; | ||||
| 
 | ||||
|       if ( cr > 1 )  | ||||
|       { | ||||
|         var s = Mathf.Sqrt( cr ); | ||||
| 
 | ||||
|         rX = s * rX; | ||||
|         rY = s * rY; | ||||
| 
 | ||||
|         rxs = rX * rX; | ||||
|         rys = rY * rY; | ||||
|       } | ||||
| 
 | ||||
|       var dq = ( rxs * y1ps + rys * x1ps ); | ||||
|       var pq = ( rxs * rys - dq ) / dq; | ||||
|       var q = Mathf.Sqrt( Mathf.Max( 0, pq ) ); | ||||
| 
 | ||||
|       if ( flagA == flagS ) | ||||
|       { | ||||
|         q = -q; | ||||
|       } | ||||
| 
 | ||||
|       var cxp = q * rX * y1p / rY; | ||||
|       var cyp = - q * rY * x1p / rX; | ||||
| 
 | ||||
|       var cx = Mathf.Cos( xAngle ) * cxp - Mathf.Sin( xAngle ) * cyp + ( start.X + end.X ) / 2; | ||||
|       var cy = Mathf.Sin( xAngle ) * cxp + Mathf.Cos( xAngle ) * cyp + ( start.Y + end.Y ) / 2; | ||||
| 
 | ||||
|       var theta = Angle( 1f, 0f, ( x1p-cxp ) / rX, ( y1p - cyp ) / rY ); | ||||
|        | ||||
|       var delta = Angle( | ||||
|         ( x1p - cxp ) / rX,  | ||||
|         ( y1p - cyp ) / rY, | ||||
|         ( -x1p - cxp ) / rX,  | ||||
|         ( -y1p-cyp ) / rY ); | ||||
| 
 | ||||
|       delta = delta % Mathf.Pi * 2; | ||||
| 
 | ||||
|       if ( ! flagS ) | ||||
|       { | ||||
|         delta -= 2 * Mathf.Pi; | ||||
|       } | ||||
| 
 | ||||
|       centerRadius = new Vector2( rX, rY ); | ||||
|       centerPosition =  new Vector2(cx, cy ); | ||||
| 
 | ||||
|       centerAngles =  new Vector2( theta, delta ); | ||||
|     } | ||||
| 
 | ||||
|     static float Angle( float ux, float uy, float vx, float vy ) | ||||
|     { | ||||
|       var u =  new Vector2( ux, uy ); | ||||
|       var v =  new Vector2(  vx, vy ); | ||||
|        | ||||
|       var dot = Math2D.Dot( u, v ); | ||||
|       var len = u.Length( ) * v.Length( ); | ||||
| 
 | ||||
|       var ang = Mathf.Acos( Mathf.Clamp( dot / len,-1,1 ) ); | ||||
|      | ||||
|       if ( ( u.X * v.Y - u.Y * v.X ) < 0 ) | ||||
|       { | ||||
|         ang = -ang;  | ||||
|       } | ||||
| 
 | ||||
|       return ang; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | @ -1,14 +1,26 @@ | |||
| using System.Collections; | ||||
| using System.Collections.Generic; | ||||
| using System.Text.RegularExpressions; | ||||
| using Godot; | ||||
| 
 | ||||
| namespace Rokojori | ||||
| {   | ||||
|   public class SVGPathCommand | ||||
|   { | ||||
|     public int pathIndex = -1; | ||||
|     public string type; | ||||
|     public List<float> paramaters = new List<float>(); | ||||
| 
 | ||||
|     public static string SVGCoordinate( float x, float y ) | ||||
|     { | ||||
|       return RegexUtility.NumberToString( x ) + " " + RegexUtility.NumberToString( y ); | ||||
|     } | ||||
| 
 | ||||
|      public static string SVGCoordinate( Vector2 v ) | ||||
|     { | ||||
|       return SVGCoordinate( v.X, v.Y ); | ||||
|     } | ||||
| 
 | ||||
|     public override string ToString() | ||||
|     { | ||||
|       if ( type == "z" || type == "Z" ) | ||||
|  |  | |||
|  | @ -4,73 +4,6 @@ using System.Collections.Generic; | |||
| 
 | ||||
| namespace Rokojori | ||||
| {   | ||||
|   public enum SVGPathInstructionType | ||||
|   { | ||||
|     MoveTo, | ||||
|     LineTo, | ||||
|     QuadraticBezierTo, | ||||
|     CubicBezierTo, | ||||
|     ArcTo, | ||||
|     Close     | ||||
|   } | ||||
| 
 | ||||
|   public class SVGPathInstruction | ||||
|   { | ||||
|     public SVGPathInstructionType type = SVGPathInstructionType.MoveTo; | ||||
|     public Vector2 startPoint = Vector2.Zero; | ||||
|     public Vector2 controlPoint1 = Vector2.Zero; | ||||
|     public Vector2 controlPoint2 = Vector2.Zero; | ||||
|     public Vector2 endPoint = Vector2.Zero; | ||||
|     public Vector2 radius = Vector2.Zero; | ||||
|     public float angle = 0; | ||||
|     public bool largeArcFlag = false; | ||||
|     public bool sweepFlag = false; | ||||
| 
 | ||||
|     public override string ToString() | ||||
|     { | ||||
|       return GetInfo(); | ||||
|     } | ||||
| 
 | ||||
|     public string GetInfo() | ||||
|     { | ||||
|       var infos = new List<object>(); | ||||
|       infos.Add( type ); | ||||
| 
 | ||||
|       if ( SVGPathInstructionType.MoveTo == type ) | ||||
|       { | ||||
|         infos.Add( endPoint ); | ||||
|       } | ||||
|       else if ( SVGPathInstructionType.LineTo == type ) | ||||
|       { | ||||
|         infos.Add( startPoint ); | ||||
|         infos.Add( endPoint ); | ||||
|       } | ||||
|       else if ( SVGPathInstructionType.QuadraticBezierTo == type ) | ||||
|       { | ||||
|         infos.Add( startPoint ); | ||||
|         infos.Add( controlPoint1 ); | ||||
|         infos.Add( endPoint ); | ||||
|       } | ||||
|       else if ( SVGPathInstructionType.CubicBezierTo == type ) | ||||
|       { | ||||
|         infos.Add( startPoint ); | ||||
|         infos.Add( controlPoint1 ); | ||||
|         infos.Add( controlPoint2 ); | ||||
|         infos.Add( endPoint ); | ||||
|       } | ||||
|       else if ( SVGPathInstructionType.ArcTo == type ) | ||||
|       { | ||||
|         infos.Add( startPoint ); | ||||
|         infos.Add( radius ); | ||||
|         infos.Add( angle ); | ||||
|         infos.Add( largeArcFlag ); | ||||
|         infos.Add( sweepFlag ); | ||||
|         infos.Add( endPoint ); | ||||
|       } | ||||
| 
 | ||||
|       return RJLog.GetLogString( infos.ToArray() ); | ||||
|     } | ||||
|   } | ||||
|    | ||||
|   public class SVGPathExtractor | ||||
|   { | ||||
|  | @ -114,7 +47,7 @@ namespace Rokojori | |||
| 
 | ||||
|         case "h": case "H":  | ||||
|         {  | ||||
|           ProcessHorizonatlTo( command );  | ||||
|           ProcessHorizontalTo( command );  | ||||
|         }  | ||||
|         break; | ||||
| 
 | ||||
|  | @ -138,7 +71,7 @@ namespace Rokojori | |||
| 
 | ||||
|         case "z": case "Z": | ||||
|         { | ||||
|           ProcessClose(); | ||||
|           ProcessClose( command ); | ||||
|         } | ||||
|         break; | ||||
| 
 | ||||
|  | @ -146,11 +79,18 @@ namespace Rokojori | |||
|       } | ||||
|     }    | ||||
| 
 | ||||
|     void ProcessClose() | ||||
|     void ProcessClose( SVGPathCommand command )  | ||||
|     { | ||||
|       instruction.sourceCommand = command; | ||||
|       instruction.sourceCommandIndex = 0; | ||||
| 
 | ||||
|       instruction.type = SVGPathInstructionType.Close; | ||||
|       instruction.startPoint = currentPoint; | ||||
|       instruction.startPoint = startPoint; | ||||
|       instruction.endPoint   = startPoint; | ||||
| 
 | ||||
|       currentPoint = startPoint; | ||||
| 
 | ||||
|       DispatchInstruction(); | ||||
|     } | ||||
| 
 | ||||
|     void ProcessMoveTo( SVGPathCommand command ) | ||||
|  | @ -158,14 +98,22 @@ namespace Rokojori | |||
|       var relative = command.type == "m"; | ||||
|       var parameters = command.paramaters;      | ||||
| 
 | ||||
|       var sourceCommandIndex = 0; | ||||
|       instruction.sourceCommand = command; | ||||
| 
 | ||||
|       for ( int i = 0; i < parameters.Count; i+= 2 ) | ||||
|       { | ||||
|         ProcessToEndpoint( relative, SVGPathInstructionType.MoveTo, parameters[ i ], parameters[ i + 1 ] ); | ||||
|         instruction.sourceCommandIndex = sourceCommandIndex ++; | ||||
| 
 | ||||
|         if ( i == 0 ) | ||||
|         { | ||||
|           startPoint = currentPoint; | ||||
|           ProcessMoveToStartPoint( relative, SVGPathInstructionType.MoveTo, parameters[ i ], parameters[ i + 1 ] ); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|           ProcessToEndpoint( relative, SVGPathInstructionType.LineTo, parameters[ i ], parameters[ i + 1 ] ); | ||||
|         } | ||||
|          | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|  | @ -174,9 +122,15 @@ namespace Rokojori | |||
|       var relative = command.type == "l"; | ||||
|       var parameters = command.paramaters; | ||||
| 
 | ||||
|       var sourceCommandIndex = 0; | ||||
|       instruction.sourceCommand = command; | ||||
| 
 | ||||
|       for ( int i = 0; i < parameters.Count; i+= 2 ) | ||||
|       { | ||||
|         instruction.sourceCommandIndex = sourceCommandIndex ++; | ||||
| 
 | ||||
|         ProcessToEndpoint( relative, SVGPathInstructionType.LineTo, parameters[ i ], parameters[ i + 1 ] ); | ||||
|          | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|  | @ -185,22 +139,34 @@ namespace Rokojori | |||
|       var relative = command.type == "v"; | ||||
|       var parameters = command.paramaters; | ||||
| 
 | ||||
|       var sourceCommandIndex = 0; | ||||
|       instruction.sourceCommand = command; | ||||
| 
 | ||||
|       for ( int i = 0; i < parameters.Count; i++ ) | ||||
|       { | ||||
|         instruction.sourceCommandIndex = sourceCommandIndex ++; | ||||
| 
 | ||||
|         var x = relative ? 0 : currentPoint.X; | ||||
|         ProcessToEndpoint( relative, SVGPathInstructionType.LineTo, x, parameters[ i ] ); | ||||
|         var y = parameters[ i ]; | ||||
|         ProcessToEndpoint( relative, SVGPathInstructionType.LineTo, x, y ); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     void ProcessHorizonatlTo( SVGPathCommand command ) | ||||
|     void ProcessHorizontalTo( SVGPathCommand command ) | ||||
|     { | ||||
|       var relative = command.type == "v"; | ||||
|       var relative = command.type == "h"; | ||||
|       var parameters = command.paramaters; | ||||
| 
 | ||||
|       var sourceCommandIndex = 0; | ||||
|       instruction.sourceCommand = command; | ||||
| 
 | ||||
|       for ( int i = 0; i < parameters.Count; i++ ) | ||||
|       { | ||||
|         instruction.sourceCommandIndex = sourceCommandIndex ++; | ||||
| 
 | ||||
|         var x = parameters[ i ]; | ||||
|         var y = relative ? 0 : currentPoint.Y; | ||||
|         ProcessToEndpoint( relative, SVGPathInstructionType.LineTo, parameters[ i ], y ); | ||||
|         ProcessToEndpoint( relative, SVGPathInstructionType.LineTo, x, y ); | ||||
|       } | ||||
|     }  | ||||
| 
 | ||||
|  | @ -209,13 +175,19 @@ namespace Rokojori | |||
|       var relative = command.type == "a"; | ||||
|       var parameters = command.paramaters; | ||||
| 
 | ||||
|       var sourceCommandIndex = 0; | ||||
|       instruction.sourceCommand = command; | ||||
| 
 | ||||
| 
 | ||||
|       for ( int i = 0; i < parameters.Count; i+= 7 ) | ||||
|       { | ||||
|         instruction.sourceCommandIndex = sourceCommandIndex ++; | ||||
| 
 | ||||
|         ProcessArc( relative,  | ||||
|           parameters[ i ], parameters[ i + 1 ],  | ||||
|           parameters[ i + 2 ], parameters[ i + 3 ],  | ||||
|           parameters[ i + 4 ], parameters[ i + 5 ], | ||||
|           parameters[ i + 7 ] | ||||
|           parameters[ i + 6 ] | ||||
|         ); | ||||
|       } | ||||
|     } | ||||
|  | @ -253,8 +225,13 @@ namespace Rokojori | |||
|       var relative = command.type == "c"; | ||||
|       var parameters = command.paramaters; | ||||
| 
 | ||||
|       var sourceCommandIndex = 0; | ||||
|       instruction.sourceCommand = command; | ||||
| 
 | ||||
|       for ( int i = 0; i < parameters.Count; i+= 6 ) | ||||
|       { | ||||
|         instruction.sourceCommandIndex = sourceCommandIndex ++; | ||||
| 
 | ||||
|         ProcessCubic( relative,  | ||||
|           parameters[ i ], parameters[ i + 1 ],  | ||||
|           parameters[ i + 2 ], parameters[ i + 3 ],  | ||||
|  | @ -268,8 +245,13 @@ namespace Rokojori | |||
|       var relative = command.type == "s"; | ||||
|       var parameters = command.paramaters; | ||||
| 
 | ||||
|       var sourceCommandIndex = 0; | ||||
|       instruction.sourceCommand = command; | ||||
| 
 | ||||
|       for ( int i = 0; i < parameters.Count; i+= 4 ) | ||||
|       { | ||||
|         instruction.sourceCommandIndex = sourceCommandIndex ++; | ||||
| 
 | ||||
|         var cpDiff = currentPoint - lastControlPoint; | ||||
|         var cpNext = relative ? cpDiff : ( currentPoint + cpDiff ); | ||||
| 
 | ||||
|  | @ -315,9 +297,13 @@ namespace Rokojori | |||
|       var relative = command.type == "q"; | ||||
|       var parameters = command.paramaters; | ||||
| 
 | ||||
|       var sourceCommandIndex = 0; | ||||
|       instruction.sourceCommand = command; | ||||
| 
 | ||||
| 
 | ||||
|       for ( int i = 0; i < parameters.Count; i+= 4 ) | ||||
|       { | ||||
|          | ||||
|         instruction.sourceCommandIndex = sourceCommandIndex ++; | ||||
|         ProcessQuadratic( relative,  | ||||
|           parameters[ i ], parameters[ i + 1 ],  | ||||
|           parameters[ i + 2 ], parameters[ i + 3 ] | ||||
|  | @ -330,8 +316,12 @@ namespace Rokojori | |||
|       var relative = command.type == "t"; | ||||
|       var parameters = command.paramaters; | ||||
| 
 | ||||
|       var sourceCommandIndex = 0; | ||||
|       instruction.sourceCommand = command; | ||||
| 
 | ||||
|       for ( int i = 0; i < parameters.Count; i+= 2 ) | ||||
|       { | ||||
|         instruction.sourceCommandIndex = sourceCommandIndex ++; | ||||
|         var cpDiff = currentPoint - lastControlPoint; | ||||
|         var cpNext = relative ? cpDiff : ( currentPoint + cpDiff ); | ||||
| 
 | ||||
|  | @ -368,6 +358,27 @@ namespace Rokojori | |||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     void ProcessMoveToStartPoint( bool relative, SVGPathInstructionType type, float x, float y ) | ||||
|     { | ||||
|       instruction.type = type; | ||||
|        | ||||
|       instruction.endPoint.X = x; | ||||
|       instruction.endPoint.Y = y; | ||||
| 
 | ||||
|       if ( relative ) | ||||
|       { | ||||
|         instruction.endPoint += currentPoint; | ||||
|       } | ||||
| 
 | ||||
|       currentPoint = instruction.endPoint; | ||||
|       instruction.startPoint = currentPoint; | ||||
| 
 | ||||
|       startPoint = currentPoint; | ||||
| 
 | ||||
|       DispatchInstruction(); | ||||
|        | ||||
|     } | ||||
| 
 | ||||
|     void ProcessToEndpoint( bool relative, SVGPathInstructionType type, float x, float y ) | ||||
|     { | ||||
|       instruction.type = type; | ||||
|  |  | |||
|  | @ -0,0 +1,146 @@ | |||
| using Godot; | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| 
 | ||||
| namespace Rokojori | ||||
| {   | ||||
| 
 | ||||
|   public enum SVGPathInstructionType | ||||
|   { | ||||
|     MoveTo, | ||||
|     LineTo, | ||||
|     QuadraticBezierTo, | ||||
|     CubicBezierTo, | ||||
|     ArcTo, | ||||
|     Close     | ||||
|   } | ||||
| 
 | ||||
|   public class SVGPathInstruction | ||||
|   { | ||||
|     public SVGPathCommand sourceCommand; | ||||
|     public int sourceCommandIndex; | ||||
|     public SVGPathInstructionType type = SVGPathInstructionType.MoveTo; | ||||
|     public Vector2 startPoint = Vector2.Zero; | ||||
|     public Vector2 controlPoint1 = Vector2.Zero; | ||||
|     public Vector2 controlPoint2 = Vector2.Zero; | ||||
|     public Vector2 endPoint = Vector2.Zero; | ||||
|     public Vector2 radius = Vector2.Zero; | ||||
|     public float angle = 0; | ||||
|     public bool largeArcFlag = false; | ||||
|     public bool sweepFlag = false; | ||||
| 
 | ||||
|     public override string ToString() | ||||
|     { | ||||
|       return GetInfo(); | ||||
|     } | ||||
| 
 | ||||
|     public void AddPoints( List<Vector2> pathPoints, float resolution = 1, int maxPoints = 1000 ) | ||||
|     { | ||||
|       if ( SVGPathInstructionType.MoveTo == type ) | ||||
|       { | ||||
|         pathPoints.Add( startPoint ); | ||||
|       } | ||||
|       else if (  | ||||
|         SVGPathInstructionType.LineTo == type || | ||||
|         SVGPathInstructionType.Close == type | ||||
|       ) | ||||
|       { | ||||
|         pathPoints.Add( endPoint ); | ||||
|       } | ||||
|       else if ( SVGPathInstructionType.ArcTo == type ) | ||||
|       { | ||||
|         var centerPosition = new Vector2(); | ||||
|         var centerRadius   = new Vector2(); | ||||
|         var centerAngles   = new Vector2(); | ||||
|          | ||||
| 
 | ||||
|         SVGArc.EndpointToCenterArc( | ||||
|           startPoint, endPoint, radius, angle, largeArcFlag, sweepFlag, | ||||
|           out centerPosition, out centerRadius, out centerAngles );  | ||||
| 
 | ||||
| 
 | ||||
|         var arcResolution = Math.Max( centerRadius.X, centerRadius.Y ) / resolution; | ||||
| 
 | ||||
|         arcResolution = Mathf.Clamp( arcResolution, 1, maxPoints );  | ||||
|          | ||||
|         RJLog.Log( "Arc Resolution", arcResolution ); | ||||
| 
 | ||||
| 
 | ||||
|         for ( int i = 1; i < arcResolution - 1; i++ ) | ||||
|         { | ||||
|           var t = centerAngles.Y * ( (float)i / ( arcResolution - 1 ) ); | ||||
|           var arcPoint = SVGArc.GetPointOnCenterArc( centerRadius, centerAngles.X, t ) + centerPosition; | ||||
| 
 | ||||
|           pathPoints.Add( arcPoint ); | ||||
|         } | ||||
| 
 | ||||
|         pathPoints.Add( endPoint ); | ||||
| 
 | ||||
|       } | ||||
|       else if (  | ||||
|         SVGPathInstructionType.QuadraticBezierTo == type ||  | ||||
|         SVGPathInstructionType.CubicBezierTo == type | ||||
|        ) | ||||
|       { | ||||
|         var isQuadratic = SVGPathInstructionType.QuadraticBezierTo == type; | ||||
| 
 | ||||
|         var curve = isQuadratic ? | ||||
|           new CustomCurve2( t => QuadraticBezier.Compute( t, startPoint, controlPoint1, endPoint ) ) : | ||||
|           new CustomCurve2( t => CubicBezier.Compute( t, startPoint, controlPoint1, controlPoint2, endPoint ) ) | ||||
|         ; | ||||
| 
 | ||||
|         var length = curve.ComputeLength( isQuadratic ? 4 : 6 ); | ||||
| 
 | ||||
|         var numPoints = Mathf.Clamp( Mathf.RoundToInt( length / resolution ), 1, maxPoints );  | ||||
| 
 | ||||
|         for ( int i = 1; i < numPoints - 1; i++ ) | ||||
|         { | ||||
|           var t = (float) i / ( numPoints - 1 ); | ||||
|           pathPoints.Add( curve.SampleAt( t ) ); | ||||
|         } | ||||
| 
 | ||||
|         pathPoints.Add( curve.SampleAt( 1 ) ); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     public string GetInfo() | ||||
|     { | ||||
|       var infos = new List<object>(); | ||||
|       infos.Add( type ); | ||||
| 
 | ||||
|       if ( SVGPathInstructionType.MoveTo == type ) | ||||
|       { | ||||
|         infos.Add( endPoint ); | ||||
|       } | ||||
|       else if ( SVGPathInstructionType.LineTo == type ) | ||||
|       { | ||||
|         infos.Add( startPoint ); | ||||
|         infos.Add( endPoint ); | ||||
|       } | ||||
|       else if ( SVGPathInstructionType.QuadraticBezierTo == type ) | ||||
|       { | ||||
|         infos.Add( startPoint ); | ||||
|         infos.Add( controlPoint1 ); | ||||
|         infos.Add( endPoint ); | ||||
|       } | ||||
|       else if ( SVGPathInstructionType.CubicBezierTo == type ) | ||||
|       { | ||||
|         infos.Add( startPoint ); | ||||
|         infos.Add( controlPoint1 ); | ||||
|         infos.Add( controlPoint2 ); | ||||
|         infos.Add( endPoint ); | ||||
|       } | ||||
|       else if ( SVGPathInstructionType.ArcTo == type ) | ||||
|       { | ||||
|         infos.Add( startPoint ); | ||||
|         infos.Add( radius ); | ||||
|         infos.Add( angle ); | ||||
|         infos.Add( largeArcFlag ); | ||||
|         infos.Add( sweepFlag ); | ||||
|         infos.Add( endPoint ); | ||||
|       } | ||||
| 
 | ||||
|       return RJLog.GetLogString( infos.ToArray() ); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | @ -43,6 +43,8 @@ namespace Rokojori | |||
| 
 | ||||
|         var lowerCase = ( d[ i ] + "" ).ToLower(); | ||||
| 
 | ||||
|          | ||||
| 
 | ||||
|         var commandIndex = SVGPathCommands.IndexOf( lowerCase ); | ||||
|          | ||||
|         if ( commandIndex == -1 ) | ||||
|  | @ -53,6 +55,8 @@ namespace Rokojori | |||
| 
 | ||||
|         var commandEnd = ProcessCommand( d, i ); | ||||
| 
 | ||||
|         // RJLog.Log( "Processing", d[ i ], ">>", lowerCase, " ends ", commandEnd ); | ||||
| 
 | ||||
|         if ( commandEnd == -1 || Messages.HasError( messages ) ) | ||||
|         { | ||||
|           Messages.Error( messages, "Command couldn't be processed at '" + i + "'. Processed " + d[ i ] ); | ||||
|  | @ -71,6 +75,7 @@ namespace Rokojori | |||
| 
 | ||||
|       var pc = new SVGPathCommand(); | ||||
|       pc.type = d[ offset ] + ""; | ||||
|       pc.pathIndex = commands.Count; | ||||
| 
 | ||||
|       ReadParameters( pc, d, offset + 1, end );  | ||||
| 
 | ||||
|  | @ -95,12 +100,33 @@ namespace Rokojori | |||
|     }   | ||||
| 
 | ||||
|     void ReadParameters( SVGPathCommand command, string d, int start, int end ) | ||||
|     { | ||||
|        | ||||
|       var parameters = d.Substring( start, ( end + 1 ) - start ); | ||||
|       parameters = parameters.Trim(); | ||||
| 
 | ||||
|      // RJLog.Log( "Parsing parameters:", parameters ); | ||||
| 
 | ||||
|       var regex = new Regex( " |," ); | ||||
|       var splitted = RegexUtility.Split( parameters, " |," ); | ||||
| 
 | ||||
|       splitted.ForEach( | ||||
|         ( s )=> | ||||
|         { | ||||
|           var number = RegexUtility.ParseFloat( s.Trim() ); | ||||
|           command.paramaters.Add( number ); | ||||
|           // RJLog.Log( s, ">>", number ); | ||||
|         } | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     void ReadParametersOld( SVGPathCommand command, string d, int start, int end ) | ||||
|     { | ||||
|       var matcher = LexerMatcherLibrary.NumberMatcher; | ||||
|       var offset = start; | ||||
|       var isNegative = false; | ||||
| 
 | ||||
|       while ( offset <= end ) | ||||
|       while ( offset < end ) | ||||
|       { | ||||
|         if ( d[ offset ] == ' ' || d[ offset ] == ','  ) | ||||
|         { | ||||
|  |  | |||
|  | @ -6,6 +6,8 @@ namespace Rokojori | |||
| {   | ||||
|   public class XMLAttributeName:XMLElementSelector | ||||
|   { | ||||
|     public static readonly XMLAttributeName id = XMLAttributeName.Create( "id" ); | ||||
| 
 | ||||
|     string _attributeName; | ||||
|     string _nameSpace; | ||||
| 
 | ||||
|  | @ -35,6 +37,12 @@ namespace Rokojori | |||
| 
 | ||||
|     public static XMLAttributeName Create( string type, string nameSpace = null ) | ||||
|     { | ||||
|       if ( nameSpace == null && type.Contains( ":" ) ) | ||||
|       { | ||||
|         nameSpace = type.Substring( 0, type.IndexOf( ":" ) ); | ||||
|         type = type.Substring( type.IndexOf( ":" ) + 1 ); | ||||
|       } | ||||
| 
 | ||||
|       var elementNodeType = new XMLAttributeName(); | ||||
|       elementNodeType._attributeName = type; | ||||
|       elementNodeType._nameSpace = nameSpace; | ||||
|  |  | |||
|  | @ -7,7 +7,11 @@ | |||
|   {   | ||||
|     public class XMLReader | ||||
|     { | ||||
|       XMLLexer lexer = new XMLLexer(); | ||||
|       XMLLexer _lexer = new XMLLexer(); | ||||
|       public XMLLexer lexer => _lexer;  | ||||
|        | ||||
| 
 | ||||
|        | ||||
|       TextLinesMapper linesMapper = new TextLinesMapper(); | ||||
|       List<LexerEvent> events; | ||||
|       XMLDocument document; | ||||
|  | @ -18,9 +22,9 @@ | |||
|       public XMLDocument Read( string text ) | ||||
|       { | ||||
|         this.text = text; | ||||
|         events = lexer.LexToList( text ); | ||||
|         events = _lexer.LexToList( text ); | ||||
|          | ||||
|         if ( lexer.hasError ) | ||||
|         if ( _lexer.hasError ) | ||||
|         { | ||||
|           linesMapper.Map( text ); | ||||
| 
 | ||||
|  | @ -45,7 +49,7 @@ | |||
| 
 | ||||
|         CreateDocument(); | ||||
| 
 | ||||
|         RJLog.Log( document.Serialize() ); | ||||
|         // RJLog.Log( document.Serialize() ); | ||||
| 
 | ||||
|         return document; | ||||
|       } | ||||
|  | @ -58,9 +62,7 @@ | |||
| 
 | ||||
|         node = document; | ||||
|        | ||||
|         lexer.GrabMatches( events, text ); | ||||
| 
 | ||||
| 
 | ||||
|         _lexer.GrabMatches( events, text ); | ||||
| 
 | ||||
|         events.ForEach( | ||||
|           ( e )=> | ||||
|  | @ -121,7 +123,7 @@ | |||
|               Add( element ); | ||||
| 
 | ||||
|               var parentName = node == document ? "document" : ((XMLElementNode)node).fullNodeName; | ||||
|               RJLog.Log( "Adding Element", element.fullNodeName, parentName ); | ||||
|               // RJLog.Log( "Adding Element", element.fullNodeName, parentName ); | ||||
|               node = element; | ||||
|                | ||||
|             }             | ||||
|  | @ -141,7 +143,7 @@ | |||
|         var element = (XMLElementNode) node; | ||||
| 
 | ||||
|         var infos = Lists.Join( Lists.Map( insideTagElements, i => i.match ), "" ); | ||||
|         RJLog.Log( "Set Attributes", element.fullNodeName, insideTagElements.Count, infos );         | ||||
|         // RJLog.Log( "Set Attributes", element.fullNodeName, insideTagElements.Count, infos );         | ||||
| 
 | ||||
|         var attributeNameIndices = Lists.CollectIndices( insideTagElements, le => XMLLexer.XMLAttributeName.Matches( le ) ); | ||||
| 
 | ||||
|  |  | |||
|  | @ -15,32 +15,108 @@ namespace Rokojori | |||
|     [Export] | ||||
|     public bool loadFile; | ||||
| 
 | ||||
|     [Export] | ||||
|     public MeshInstance3D meshInstance3D; | ||||
| 
 | ||||
|     [Export] | ||||
|     public bool selectRandom = false; | ||||
| 
 | ||||
|     [Export] | ||||
|     public string selectorAttribute; | ||||
| 
 | ||||
|     [Export] | ||||
|     public string selectorValue; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     [Export] | ||||
|     public float resolution; | ||||
| 
 | ||||
| 
 | ||||
|     [Export] | ||||
|     public bool autoReload = false; | ||||
| 
 | ||||
|     [Export] | ||||
|     public int reloadFrames = 10; | ||||
| 
 | ||||
|     int reloadCounter = 0; | ||||
| 
 | ||||
|     public override void _Process( double delta ) | ||||
|     { | ||||
| 
 | ||||
|       if ( ! loadFile ) | ||||
|       { | ||||
|         if ( autoReload ) | ||||
|         { | ||||
|           reloadCounter ++; | ||||
| 
 | ||||
|           if ( reloadCounter > reloadFrames ) | ||||
|           { | ||||
|             reloadCounter = 0; | ||||
|             loadFile = true; | ||||
|           } | ||||
|         } | ||||
|          | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       Load(); | ||||
|     } | ||||
| 
 | ||||
|      | ||||
|     string loadedPath = null; | ||||
|     XMLDocument doc; | ||||
| 
 | ||||
|     void Load() | ||||
|     { | ||||
|       loadFile = false; | ||||
| 
 | ||||
|       if ( path != loadedPath ) | ||||
|       {    | ||||
|         var text = FilesSync.LoadUTF8( path ); | ||||
|         var reader = new XMLReader(); | ||||
| 
 | ||||
|       var doc = reader.Read( text ); | ||||
|         doc = reader.Read( text ); | ||||
|       } | ||||
|       | ||||
|       var paths = doc.querySelectorAll( SVGElementName.path ); | ||||
| 
 | ||||
|       RJLog.Log( "paths:", paths.Count ); | ||||
|       XMLElementNode randomPath = null; | ||||
| 
 | ||||
|       if ( selectRandom ) | ||||
|       { | ||||
|         randomPath = GodotRandom.Get().From( paths ); | ||||
| 
 | ||||
|       } | ||||
| 
 | ||||
| 
 | ||||
|       XMLWalker.instance.Iterate( doc,  | ||||
|         n =>  | ||||
|         {  | ||||
|           if ( ! ( n is XMLElementNode ) ) | ||||
|           { | ||||
|             return; | ||||
|           } | ||||
| 
 | ||||
|           var e = (XMLElementNode) n; | ||||
|         } | ||||
|       ); | ||||
|        | ||||
| 
 | ||||
|       // RJLog.Log( "Paths in SVG:", paths.Count ); | ||||
| 
 | ||||
|       | ||||
|        | ||||
|       var attributeSelector = XMLAttributeName.Create( selectorAttribute ); | ||||
| 
 | ||||
|       paths.ForEach( | ||||
|         ( p ) => | ||||
|         { | ||||
|           if ( XMLAttributeName.id.Selects( p ) ) | ||||
|           { | ||||
|             // RJLog.Log( "Processing:", XMLAttributeName.id.Get( p ), attributeSelector.Get( p ), attributeSelector.Get( p ) == selectorValue ); | ||||
|           }           | ||||
| 
 | ||||
|           var pathData = SVGAttributeName.d.Get( p ); | ||||
| 
 | ||||
|           if ( pathData == null ) | ||||
|  | @ -49,7 +125,25 @@ namespace Rokojori | |||
|             return; | ||||
|           }  | ||||
| 
 | ||||
|           RJLog.Log( "pathData:", pathData ); | ||||
|            | ||||
| 
 | ||||
|           if ( randomPath == p || ( randomPath == null && attributeSelector.Selects( p ) && attributeSelector.Get( p ) == selectorValue ) ) | ||||
|           { | ||||
|             var shape2 = Shape2.FromSVGPath( pathData, resolution ); | ||||
|              | ||||
|             if ( meshInstance3D != null ) | ||||
|             { | ||||
|               var meshGeometry = shape2.CreateFillMeshGeometry(); | ||||
| 
 | ||||
|               if ( meshGeometry != null ) | ||||
|               { | ||||
|                 meshInstance3D.Mesh = meshGeometry.GenerateMesh(); | ||||
|               }               | ||||
|             } | ||||
| 
 | ||||
|           } | ||||
| 
 | ||||
|           /*RJLog.Log( "pathData:", pathData ); | ||||
|           var commands = SVGPathParser.Parse( pathData ); | ||||
| 
 | ||||
| 
 | ||||
|  | @ -75,6 +169,7 @@ namespace Rokojori | |||
|             RJLog.Log( "Commands failed parsing:", Lists.Join( messages, "\n" ) ); | ||||
|           } | ||||
| 
 | ||||
|           */ | ||||
| 
 | ||||
|            | ||||
|         } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Josef
						Josef