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) |         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(); |             var bounds = new Rectangle(); | ||||||
|             bounds.Expand(contour.Cast<Point>()); |             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> |         /// <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() |     protected virtual void SetAttributes() | ||||||
|     { |     { | ||||||
|       _attributes = ReflectionHelper.GetFieldsOfType<SceneFileHeaderAttributeValue>( this ); |       _attributes = ReflectionHelper.GetFieldValuesOfType<SceneFileHeaderAttributeValue>( this ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     protected void ReadAttributes( SceneFileHeader entry ) |     protected void ReadAttributes( SceneFileHeader entry ) | ||||||
|  |  | ||||||
|  | @ -10,6 +10,15 @@ namespace Rokojori | ||||||
|   public class RJLog |   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 ) |     public static void Stringify( object obj, StringBuilder output ) | ||||||
|     { |     { | ||||||
|       if ( obj == null ) |       if ( obj == null ) | ||||||
|  | @ -32,6 +41,34 @@ namespace Rokojori | ||||||
|         return; |         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() ); |       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 Godot; | ||||||
| using System.Collections; | using System.Collections; | ||||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||||
|  | using System; | ||||||
| 
 | 
 | ||||||
| namespace Rokojori | namespace Rokojori | ||||||
| { | { | ||||||
|  |   public class FresnetFrame | ||||||
|  |   { | ||||||
|  |     public Vector3 tangent; | ||||||
|  |     public Vector3 normal; | ||||||
|  |     public Vector3 binormal; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
| 	public abstract class Curve3 | 	public abstract class Curve3 | ||||||
| 	{ | 	{ | ||||||
|     float _gradientSamplingRange = Mathf.Pow( 10f, -8f ); |     float _tangentSamplingRange = Mathf.Pow( 10f, -8f ); | ||||||
|     public abstract Vector3 SampleAt( float t ); |     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 ) |     public virtual void SampleMultiple( int numSamples, List<Vector3> output ) | ||||||
|     { |     { | ||||||
|  | @ -21,7 +49,7 @@ namespace Rokojori | ||||||
|       for ( var i = 0 ; i < numSamples; i++ ) |       for ( var i = 0 ; i < numSamples; i++ ) | ||||||
|       { |       { | ||||||
|         var t = range.min + i * diff; |         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++ ) |       for ( var i = 0 ; i < numSamples; i++ ) | ||||||
|       { |       { | ||||||
|         var t = range.min + i * diff; |         var t = range.min + i * diff; | ||||||
|         var p = SampleAt( t ); |         var p = PositionAt( t ); | ||||||
|         points.Add( new Vector2( p.X, p.Z ) ); |         points.Add( new Vector2( p.X, p.Z ) ); | ||||||
|          |          | ||||||
|       } |       } | ||||||
|  | @ -46,14 +74,136 @@ namespace Rokojori | ||||||
|       return new Path2( points ); |       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 ) |     public virtual float ComputeLength( int numSamples ) | ||||||
|  | @ -61,16 +211,131 @@ namespace Rokojori | ||||||
|       return ComputeSubLength( Range.Of_01 , numSamples ); |       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 ) |     public virtual float ComputeSubLength( Range range, int numSamples ) | ||||||
|     { |     { | ||||||
|       var diff = range.length / ( numSamples - 1 ) ; |       var diff = range.length / ( numSamples - 1 ) ; | ||||||
|       var it = SampleAt( range.min ); |       var it = PositionAt( range.min ); | ||||||
|       var length = 0f; |       var length = 0f; | ||||||
| 
 | 
 | ||||||
|       for ( var i = 1 ; i < numSamples; i++ ) |       for ( var i = 1 ; i < numSamples; i++ ) | ||||||
|       { |       { | ||||||
|         var t = range.min + i * diff; |         var t = range.min + i * diff; | ||||||
|         var next = SampleAt( t ); |         var next = PositionAt( t ); | ||||||
|         length += ( next - it ).Length(); |         length += ( next - it ).Length(); | ||||||
|         it = next; |         it = next; | ||||||
|       } |       } | ||||||
|  | @ -99,7 +364,7 @@ namespace Rokojori | ||||||
|     { |     { | ||||||
|       var parameter = GetClosestParameterTo( position, numSegments, depth ); |       var parameter = GetClosestParameterTo( position, numSegments, depth ); | ||||||
| 
 | 
 | ||||||
|       return SampleAt( parameter ); |       return PositionAt( parameter ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public float GetClosestParameterTo( Vector3 point, int numSegments = 20, int depth = 3 ) |     public float GetClosestParameterTo( Vector3 point, int numSegments = 20, int depth = 3 ) | ||||||
|  | @ -121,7 +386,7 @@ namespace Rokojori | ||||||
|       var closestDistance = float.MaxValue; |       var closestDistance = float.MaxValue; | ||||||
|       var minT = 0f; |       var minT = 0f; | ||||||
|       var maxT = 1f; |       var maxT = 1f; | ||||||
|       var startPoint = SampleAt( tStart ); |       var startPoint = PositionAt( tStart ); | ||||||
| 
 | 
 | ||||||
|       var line = new Line3(); |       var line = new Line3(); | ||||||
|       var lastT = tStart; |       var lastT = tStart; | ||||||
|  | @ -129,7 +394,7 @@ namespace Rokojori | ||||||
|       for ( int i = 1; i < numSegments; i++ ) |       for ( int i = 1; i < numSegments; i++ ) | ||||||
|       { |       { | ||||||
|         var t = tNormalizer * i + tStart; |         var t = tNormalizer * i + tStart; | ||||||
|         var nextCurvePoint = SampleAt( t ); |         var nextCurvePoint = PositionAt( t ); | ||||||
| 
 | 
 | ||||||
|         line.Set( startPoint, nextCurvePoint ); |         line.Set( startPoint, nextCurvePoint ); | ||||||
|          |          | ||||||
|  | @ -162,7 +427,7 @@ namespace Rokojori | ||||||
|       for ( int i = 0; i < numSegments; i++ ) |       for ( int i = 0; i < numSegments; i++ ) | ||||||
|       { |       { | ||||||
|         var detailedT = i * tNormalizer2 + minT; |         var detailedT = i * tNormalizer2 + minT; | ||||||
|         var sampledPoint = SampleAt( detailedT ); |         var sampledPoint = PositionAt( detailedT ); | ||||||
| 
 | 
 | ||||||
|         var distance = ( sampledPoint - position ).LengthSquared(); |         var distance = ( sampledPoint - position ).LengthSquared(); | ||||||
| 
 | 
 | ||||||
|  | @ -175,7 +440,6 @@ namespace Rokojori | ||||||
| 
 | 
 | ||||||
|       return closestParameter;       |       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++ ) |         for ( int j = 0; j < numPoints - 1; j++ ) | ||||||
|         { |         { | ||||||
|           var t = j / (float)( numPoints - 1 ); |           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 t = i / ( (float) numSamples - 1 ); | ||||||
| 
 | 
 | ||||||
|         var p = curve.SampleAt( t ); |         var p = curve.PositionAt( t ); | ||||||
| 
 | 
 | ||||||
|         points.Add( p ); |         points.Add( p ); | ||||||
|       } |       } | ||||||
|  | @ -232,7 +232,7 @@ namespace Rokojori | ||||||
|       return output; |       return output; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public override Vector3 SampleAt( float t ) |     public override Vector3 PositionAt( float t ) | ||||||
|     { |     { | ||||||
|       if ( t < 0 ) |       if ( t < 0 ) | ||||||
|       { |       { | ||||||
|  |  | ||||||
|  | @ -10,6 +10,11 @@ namespace Rokojori | ||||||
|     public Vector2 end = Vector2.Zero; |     public Vector2 end = Vector2.Zero; | ||||||
| 
 | 
 | ||||||
|     public Line2( Vector2 start, Vector2 end )  |     public Line2( Vector2 start, Vector2 end )  | ||||||
|  |     { | ||||||
|  |       Set( start, end ); | ||||||
|  |     }     | ||||||
|  | 
 | ||||||
|  |     public void Set( Vector2 start, Vector2 end )  | ||||||
|     { |     { | ||||||
|       this.start = start; |       this.start = start; | ||||||
|       this.end = end; |       this.end = end; | ||||||
|  |  | ||||||
|  | @ -28,7 +28,7 @@ namespace Rokojori | ||||||
|       this.end = end; |       this.end = end; | ||||||
|     }  |     }  | ||||||
| 
 | 
 | ||||||
|     public override Vector3 SampleAt( float t ) |     public override Vector3 PositionAt( float t ) | ||||||
|     { |     { | ||||||
|       return start.Lerp( end, t ); |       return start.Lerp( end, t ); | ||||||
|     } |     } | ||||||
|  | @ -143,7 +143,7 @@ namespace Rokojori | ||||||
|     public Vector3 ClosestPointTo( Line3 other ) |     public Vector3 ClosestPointTo( Line3 other ) | ||||||
|     { |     { | ||||||
|       var parameters = ClosestParametersToLine( other ); |       var parameters = ClosestParametersToLine( other ); | ||||||
|       return SampleAt( parameters.X ); |       return PositionAt( parameters.X ); | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     public Vector3 ClosestPointTo( params Vector3[] points ) |     public Vector3 ClosestPointTo( params Vector3[] points ) | ||||||
|  |  | ||||||
|  | @ -4,6 +4,9 @@ using Godot; | ||||||
| 
 | 
 | ||||||
| namespace Rokojori | namespace Rokojori | ||||||
| { | { | ||||||
|  | 
 | ||||||
|  |    | ||||||
|  | 
 | ||||||
|   public enum PointInPathResult |   public enum PointInPathResult | ||||||
|   { |   { | ||||||
|     INSIDE, |     INSIDE, | ||||||
|  | @ -29,11 +32,38 @@ namespace Rokojori | ||||||
|       this._points = points; |       this._points = points; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public override string ToString() | ||||||
|  |     {     | ||||||
|  |       return "Rokojori.Path2 with " + points.Count + " points" ;  | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|     public Path2( Vector2[] points ) |     public Path2( Vector2[] points ) | ||||||
|     { |     { | ||||||
|       this._points = new List<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 int numPoints => _points.Count; | ||||||
|     public bool empty => _points.Count == 0; |     public bool empty => _points.Count == 0; | ||||||
| 
 | 
 | ||||||
|  | @ -200,7 +230,7 @@ namespace Rokojori | ||||||
| 
 | 
 | ||||||
|       for ( int i = 0; i < numPoints; i++ ) |       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 ) |       if ( closed ) | ||||||
|  | @ -239,6 +269,11 @@ namespace Rokojori | ||||||
|       return CheckPointInPathFast( point, max + new Vector2( 122.133544f, 129.45423f ) ); |       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 ) |     public bool PointInPathReliable( Vector2 point, bool checkBoundingBox = true ) | ||||||
|     { |     { | ||||||
|       var min = this.leftTop; |       var min = this.leftTop; | ||||||
|  | @ -369,6 +404,40 @@ namespace Rokojori | ||||||
|       return list; |       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() |     public MeshGeometry CreateMeshGeometry() | ||||||
|     { |     { | ||||||
|       var geometry = new MeshGeometry(); |       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 ) |     public Pose ToGlobal( Node3D n ) | ||||||
|     { |     { | ||||||
|  | @ -96,5 +137,38 @@ namespace Rokojori | ||||||
|       return p; |       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 | namespace Rokojori | ||||||
| { | { | ||||||
|  |   using ClipperPath = List<ClipperLib.IntPoint>; | ||||||
|  |   using ClipperShape = List<List<ClipperLib.IntPoint>>; | ||||||
|  | 
 | ||||||
|  |   public enum ShapeFillRule | ||||||
|  |   { | ||||||
|  |     NonZero, | ||||||
|  |     EvenOdd     | ||||||
|  |   } | ||||||
|  | 
 | ||||||
| 	public class Shape2 | 	public class Shape2 | ||||||
| 	{ | 	{ | ||||||
|     public List<Path2> paths = new List<Path2>(); |     public List<Path2> paths = new List<Path2>(); | ||||||
|  |     public ShapeFillRule fillRule = ShapeFillRule.NonZero; | ||||||
| 
 | 
 | ||||||
|     public Shape2( params Path2[] paths ) |     public Shape2( params Path2[] paths ) | ||||||
|     { |     { | ||||||
|  | @ -21,20 +31,66 @@ namespace Rokojori | ||||||
|       this.paths.AddRange( paths ); |       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 ); |       // 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 polygon = new Polygon(); | ||||||
|        |        | ||||||
|  |    | ||||||
|       var index = 0; |       var index = 0; | ||||||
|       paths.ForEach( |       var firstIsClockWise = polyPaths.Count > 0 && polyPaths[ 0 ].isClockwise; | ||||||
|  | 
 | ||||||
|  |       polyPaths.ForEach( | ||||||
|         ( path )=> |         ( path )=> | ||||||
|         { |         { | ||||||
|           var vertices = Lists.Map( path.points, p => new Vertex( p.X, p.Y ) ); |           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 ); |           polygon.Add( new Contour( vertices, index ), isHole ); | ||||||
|  | @ -65,6 +121,54 @@ namespace Rokojori | ||||||
|       return shape; |       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 ) |     public static Shape2 Boolean( Shape2 a, Shape2 b, Geometry2D.PolyBooleanOperation booleanOperation, bool simplify = true ) | ||||||
|     { |     { | ||||||
|       // RJLog.Log( "Using Clipper Library" ); |       // RJLog.Log( "Using Clipper Library" ); | ||||||
|  | @ -89,11 +193,14 @@ namespace Rokojori | ||||||
|         type = ClipperLib.ClipType.ctXor; |         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(); | 			var clipper = new ClipperLib.Clipper(); | ||||||
| 			clipper.AddPaths( clipperPathsA, ClipperLib.PolyType.ptSubject, true);  | 			clipper.AddPaths( clipperPathsA, ClipperLib.PolyType.ptSubject, true);  | ||||||
| 			clipper.AddPaths( clipperPathsB, ClipperLib.PolyType.ptClip, true);  | 			clipper.AddPaths( clipperPathsB, ClipperLib.PolyType.ptClip, true);  | ||||||
| 			clipper.Execute( type, resultPaths ); | 			clipper.Execute( type, resultPaths, subjectPolyType, clipPolyType ); | ||||||
| 
 | 
 | ||||||
|       if ( simplify ) |       if ( simplify ) | ||||||
|       { |       { | ||||||
|  | @ -113,16 +220,130 @@ namespace Rokojori | ||||||
|       return s; |       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" ); |         // RJLog.Log( "Only 1 path" ); | ||||||
|         return paths[ 0 ].CreateMeshGeometry(); |         return paths[ 0 ].CreateMeshGeometry(); | ||||||
|       } |       }*/ | ||||||
| 
 | 
 | ||||||
|       var polygon = _CreateTNetPolygon(); |       var polygon = _CreateTNetPolygon(); | ||||||
| 
 | 
 | ||||||
|  |       if ( polygon == null ) | ||||||
|  |       { | ||||||
|  |         RJLog.Log( "Could not create polygon" ); | ||||||
|  |         return null; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|       var options = new ConstraintOptions(); |       var options = new ConstraintOptions(); | ||||||
|     	var quality = new QualityOptions(); |     	var quality = new QualityOptions(); | ||||||
|       var triangulator = new Dwyer();  |       var triangulator = new Dwyer();  | ||||||
|  |  | ||||||
|  | @ -1,3 +1,4 @@ | ||||||
|  | using System; | ||||||
| using System.Collections; | using System.Collections; | ||||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||||
| using Godot; | using Godot; | ||||||
|  | @ -24,6 +25,8 @@ namespace Rokojori | ||||||
|   { |   { | ||||||
|     public Vector3 position; |     public Vector3 position; | ||||||
|     public Quaternion rotation; |     public Quaternion rotation; | ||||||
|  |     public Vector3 scale; | ||||||
|  |     public float twist = 0f; | ||||||
|     public float weight = 1f;  |     public float weight = 1f;  | ||||||
| 
 | 
 | ||||||
|     public SplineCurveTangent tangentBefore = new SplineCurveTangent(); |     public SplineCurveTangent tangentBefore = new SplineCurveTangent(); | ||||||
|  | @ -34,14 +37,16 @@ namespace Rokojori | ||||||
|       var scp = new SplineCurvePoint(); |       var scp = new SplineCurvePoint(); | ||||||
|       scp.position = position; |       scp.position = position; | ||||||
|       scp.rotation = rotation; |       scp.rotation = rotation; | ||||||
|  |       scp.scale = scale; | ||||||
|       scp.weight = weight; |       scp.weight = weight; | ||||||
|  |       scp.twist = twist; | ||||||
|       scp.tangentBefore = tangentBefore.Clone(); |       scp.tangentBefore = tangentBefore.Clone(); | ||||||
|       scp.tangentNext = tangentNext.Clone(); |       scp.tangentNext = tangentNext.Clone(); | ||||||
| 
 | 
 | ||||||
|       return scp; |       return scp; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public SplineCurvePoint CloneForXZ( float y ) |     public SplineCurvePoint CloneForXZ( float y = 0 ) | ||||||
|     { |     { | ||||||
|       var cloned = Clone(); |       var cloned = Clone(); | ||||||
|       cloned.position.Y = y; |       cloned.position.Y = y; | ||||||
|  | @ -51,6 +56,29 @@ namespace Rokojori | ||||||
|       return cloned; |       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 |   public class SplineCurve: Curve3 | ||||||
|  | @ -92,24 +120,37 @@ namespace Rokojori | ||||||
|       return splineCurve; |       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(); |       var splineCurve = new SplineCurve(); | ||||||
|       splineCurve._points = Lists.Map( points, p => p.CloneForXZ( y ) ); |       splineCurve._points = Lists.Map( points, p => p.CloneForXZ( y ) ); | ||||||
|       return splineCurve; |       return splineCurve; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|      |     public SplineCurve CloneForXY( float z  = 0 ) | ||||||
|     public Vector3 GetByPointIndex( float pointIndex ) |  | ||||||
|     { |     { | ||||||
|       if ( pointIndex <= 0 ) |       var splineCurve = new SplineCurve(); | ||||||
|       { |       splineCurve._points = Lists.Map( points, p => p.CloneForXY( z ) ); | ||||||
|         return _points[ 0 ].position; |       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 ); |       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 ) |       if ( _points.Count <= 0 ) | ||||||
|       { |       { | ||||||
|  | @ -142,7 +323,20 @@ namespace Rokojori | ||||||
| 
 | 
 | ||||||
|       var index = NormalizedToPointIndex( t ); |       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 ) |     public float NormalizedToPointIndex( float normalized ) | ||||||
|  |  | ||||||
|  | @ -31,10 +31,13 @@ namespace Rokojori | ||||||
|       var splineCurvePoint = new SplineCurvePoint(); |       var splineCurvePoint = new SplineCurvePoint(); | ||||||
|       var splinePoint = splinePoints[ index ]; |       var splinePoint = splinePoints[ index ]; | ||||||
|       splineCurvePoint.position = splinePoint.GlobalPosition; |       splineCurvePoint.position = splinePoint.GlobalPosition; | ||||||
|  |       splineCurvePoint.rotation = splinePoint.GetGlobalQuaternion(); | ||||||
|  |       splineCurvePoint.scale  = splinePoint.Scale; | ||||||
|  |       splineCurvePoint.twist = splinePoint.twist; | ||||||
|       splineCurvePoint.weight = 1f; |       splineCurvePoint.weight = 1f; | ||||||
|        |        | ||||||
|       splineCurvePoint.tangentBefore.position = GetTangentPosition( splinePoints, index, true ); |       splineCurvePoint.tangentBefore.position = GetTangentPosition( splinePoints, index, true, closed ); | ||||||
|       splineCurvePoint.tangentNext.position   = GetTangentPosition( splinePoints, index, false ); |       splineCurvePoint.tangentNext.position   = GetTangentPosition( splinePoints, index, false, closed ); | ||||||
| 
 | 
 | ||||||
|       splineCurvePoint.tangentBefore.weight = splinePoint.tangentBeforeWeight; |       splineCurvePoint.tangentBefore.weight = splinePoint.tangentBeforeWeight; | ||||||
|       splineCurvePoint.tangentNext.weight = splinePoint.tangentNextWeight; |       splineCurvePoint.tangentNext.weight = splinePoint.tangentNextWeight; | ||||||
|  | @ -42,7 +45,54 @@ namespace Rokojori | ||||||
|       return splineCurvePoint; |       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 ]; |       var splinePoint = splinePoints[ index ]; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -182,6 +182,32 @@ namespace Rokojori | ||||||
|       return line.ClosestPointTo( ab, bc, ca ); |       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 ) |     public Vector3 GetPoint( int i ) | ||||||
|     { |     { | ||||||
|       if ( i == 0 ) |       if ( i == 0 ) | ||||||
|  |  | ||||||
|  | @ -18,6 +18,30 @@ namespace Rokojori | ||||||
|       return new Vector2( v.X, v.Z ); |       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 ) |     public static Vector2 Fract( Vector2 a ) | ||||||
|     { |     { | ||||||
|       return new Vector2( MathX.Fract( a.X ), MathX.Fract( a.Y ) ); |       return new Vector2( MathX.Fract( a.X ), MathX.Fract( a.Y ) ); | ||||||
|  |  | ||||||
|  | @ -8,6 +8,15 @@ namespace Rokojori | ||||||
| { | { | ||||||
|   public static class Math3D |   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 ) |     public static Vector3 OnCircleXZ( float radians, float size = 1 ) | ||||||
|     { |     { | ||||||
|       var x = Mathf.Cos( radians ) * size; |       var x = Mathf.Cos( radians ) * size; | ||||||
|  | @ -16,6 +25,14 @@ namespace Rokojori | ||||||
|       return new Vector3( x, 0, z ); |       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 ) |     public static Vector3 XYasXZ( Vector2 v ) | ||||||
|     { |     { | ||||||
|       return new Vector3( v.X, 0, v.Y ); |       return new Vector3( v.X, 0, v.Y ); | ||||||
|  | @ -212,6 +229,24 @@ namespace Rokojori | ||||||
|       return aligned.GetRotationQuaternion(); |       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 ) |     public static Basis AlignUp( Basis basis, Vector3 upDirection ) | ||||||
|     { |     { | ||||||
|       basis.Y = upDirection; |       basis.Y = upDirection; | ||||||
|  | @ -250,6 +285,27 @@ namespace Rokojori | ||||||
|       return new Vector4( q.X, q.Y, q.Z, q.W ); |       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 ) |     public static void SetGlobalQuaternion( this Node3D node, Quaternion quaternion ) | ||||||
|     { |     { | ||||||
|       var offset = node.GlobalPosition; |       var offset = node.GlobalPosition; | ||||||
|  |  | ||||||
|  | @ -10,6 +10,29 @@ namespace Rokojori | ||||||
|   { |   { | ||||||
|     public const float fps120Delta = 1/120f; |     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 ) |     public static float Min( params float[] values ) | ||||||
|     { |     { | ||||||
|       var value = - float.MaxValue; |       var value = - float.MaxValue; | ||||||
|  | @ -134,6 +157,7 @@ namespace Rokojori | ||||||
|       return Map( value, 0, inputMax, 0, outputMax ); |       return Map( value, 0, inputMax, 0, outputMax ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |      | ||||||
|     public static float MapClamped( float value, float inputMin, float inputMax,  |     public static float MapClamped( float value, float inputMin, float inputMax,  | ||||||
|     float outputMin, float outputMax ) |     float outputMin, float outputMax ) | ||||||
|     { |     { | ||||||
|  | @ -242,6 +266,18 @@ namespace Rokojori | ||||||
|       return ( phase - phaseStart ) / ( phaseStart - phaseEnd );  |       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 ) |     public static int Repeat( int value, int range ) | ||||||
|     { |     { | ||||||
|  | @ -273,6 +309,70 @@ namespace Rokojori | ||||||
|       return value; |       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  ) |     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 ); } |       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 ) |     public static bool Contains( float min, float max, float value ) | ||||||
|     { |     { | ||||||
|       return min <= value && value <= max; |       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 shape = Path2.Boolean( pathA, pathB, booleanOperation ); | ||||||
| 
 | 
 | ||||||
|       var meshGeometry = shape.CreateMeshGeometry(); |       var meshGeometry = shape.CreateFillMeshGeometry(); | ||||||
| 
 | 
 | ||||||
|       meshInstance3D.Mesh = meshGeometry.GenerateMesh(); |       meshInstance3D.Mesh = meshGeometry.GenerateMesh(); | ||||||
| 
 | 
 | ||||||
|  | @ -165,7 +165,7 @@ namespace Rokojori | ||||||
| 
 | 
 | ||||||
|       var shape = Shape2.Boolean( circleAreaShape, islands, Geometry2D.PolyBooleanOperation.Difference ); |       var shape = Shape2.Boolean( circleAreaShape, islands, Geometry2D.PolyBooleanOperation.Difference ); | ||||||
| 
 | 
 | ||||||
|       var meshGeometry = shape.CreateMeshGeometry(); |       var meshGeometry = shape.CreateFillMeshGeometry(); | ||||||
| 
 | 
 | ||||||
|       meshInstance3D.Mesh = meshGeometry.GenerateMesh(); |       meshInstance3D.Mesh = meshGeometry.GenerateMesh(); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -9,8 +9,8 @@ namespace Rokojori | ||||||
| { | { | ||||||
|   public class MeshGeometry |   public class MeshGeometry | ||||||
|   { |   { | ||||||
|     public List<Vector3> vertices; |     public List<Vector3> vertices = new List<Vector3>(); | ||||||
|     public List<int> indices; |     public List<int> indices = new List<int>(); | ||||||
| 
 | 
 | ||||||
|     public List<Vector3> normals; |     public List<Vector3> normals; | ||||||
|     public List<Vector2> uvs; |     public List<Vector2> uvs; | ||||||
|  | @ -20,6 +20,23 @@ namespace Rokojori | ||||||
|     public int numTriangles => indices.Count / 3; |     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 ) |     public void ForEachTriangle( Action<int,int,int,int> callback ) | ||||||
|     { |     { | ||||||
|       var index = 0; |       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() |     public MeshGeometry UniqueTriangles() | ||||||
|     { |     { | ||||||
|       var mg = new MeshGeometry(); |       var mg = new MeshGeometry(); | ||||||
|  | @ -128,17 +201,103 @@ namespace Rokojori | ||||||
|       colors[ c ] = color; |       colors[ c ] = color; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public void Initialize( bool normals = true, bool uvs = true, bool colors = false, bool uvs2 = false) |     public MeshGeometry( bool normals = true, bool uvs = true, bool colors = false, bool uvs2 = false ) | ||||||
|     { |     { | ||||||
|       this.vertices = new List<Vector3>(); |       Initialize( normals, uvs, colors, uvs2 ); | ||||||
|       this.indices = new List<int>(); |     } | ||||||
| 
 | 
 | ||||||
|  |     public void Initialize( bool normals = true, bool uvs = true, bool colors = false, bool uvs2 = false ) | ||||||
|  |     {       | ||||||
|       this.normals = normals ? new List<Vector3>() : null; |       this.normals = normals ? new List<Vector3>() : null; | ||||||
|       this.uvs = uvs ? new List<Vector2>() : null; |       this.uvs = uvs ? new List<Vector2>() : null; | ||||||
|       this.uv2s = uvs2 ? new List<Vector2>() : null; |       this.uv2s = uvs2 ? new List<Vector2>() : null; | ||||||
|       this.colors = colors ? new List<Color>() : 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(  |     public void AddTriangle(  | ||||||
|       Vector3 va, Vector3 vb, Vector3 vc,  |       Vector3 va, Vector3 vb, Vector3 vc,  | ||||||
|  | @ -187,6 +346,11 @@ namespace Rokojori | ||||||
|       AddTriangle( vc, vd, va,  nc, nd, na,  uvc, uvd, uva ); |       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( |     public void AddQuad( | ||||||
|       Vector3 va, Vector3 vb, Vector3 vc, Vector3 vd, |       Vector3 va, Vector3 vb, Vector3 vc, Vector3 vd, | ||||||
|  |  | ||||||
|  | @ -5,6 +5,12 @@ using System.Collections.Generic; | ||||||
| 
 | 
 | ||||||
| namespace Rokojori | namespace Rokojori | ||||||
| {   | {   | ||||||
|  |   public enum SplineAutoOrientationMode | ||||||
|  |   { | ||||||
|  |     Tangent, | ||||||
|  |     Next_Neighbor | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   [Tool] |   [Tool] | ||||||
|   [GlobalClass, Icon("res://Scripts/Rokojori/Rokojori-Action-Library/Icons/Spline.svg") ] |   [GlobalClass, Icon("res://Scripts/Rokojori/Rokojori-Action-Library/Icons/Spline.svg") ] | ||||||
|   public partial class Spline : Node3D |   public partial class Spline : Node3D | ||||||
|  | @ -19,6 +25,19 @@ namespace Rokojori | ||||||
|     [Export] |     [Export] | ||||||
|     public int editorResolution = 20; |     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] |     [Export] | ||||||
|     public bool updateAlways = false; |     public bool updateAlways = false; | ||||||
| 
 | 
 | ||||||
|  | @ -119,12 +138,12 @@ namespace Rokojori | ||||||
| 
 | 
 | ||||||
|       renderedResolution = editorResolution; |       renderedResolution = editorResolution; | ||||||
| 
 | 
 | ||||||
|       var lastPoint = ToLocal( curve.SampleAt( 0 ) ); |       var lastPoint = ToLocal( curve.PositionAt( 0 ) ); | ||||||
| 
 | 
 | ||||||
|       for ( int i = 1; i < resolution; i++ ) |       for ( int i = 1; i < resolution; i++ ) | ||||||
|       { |       { | ||||||
|         var t = i / (float) (resolution - 1 ); |         var t = i / (float) (resolution - 1 ); | ||||||
|         var point = ToLocal( curve.SampleAt( t ) );  |         var point = ToLocal( curve.PositionAt( t ) );  | ||||||
| 
 | 
 | ||||||
|         linePoints.Add( lastPoint ); |         linePoints.Add( lastPoint ); | ||||||
|         linePoints.Add( point ); |         linePoints.Add( point ); | ||||||
|  | @ -152,6 +171,52 @@ namespace Rokojori | ||||||
|      |      | ||||||
|     int renderedResolution = -100; |     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 ) |     public override void _Process( double delta ) | ||||||
|     { |     { | ||||||
|       var changed = updateAlways; |       var changed = updateAlways; | ||||||
|  | @ -162,6 +227,11 @@ namespace Rokojori | ||||||
|         changed = true; |         changed = true; | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|  |       if ( autoOrienation ) | ||||||
|  |       { | ||||||
|  |         AutoOrientate();         | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|       Nodes.ForEachDirectChild<SplinePoint>( this,  |       Nodes.ForEachDirectChild<SplinePoint>( this,  | ||||||
|         sp =>  |         sp =>  | ||||||
|         { |         { | ||||||
|  |  | ||||||
|  | @ -88,6 +88,10 @@ namespace Rokojori | ||||||
| 
 | 
 | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
|  |      | ||||||
|  |     [Export] | ||||||
|  |     public float twist = 0; | ||||||
|  |      | ||||||
|     [Export] |     [Export] | ||||||
|     public SplinePointTangentMode tangentMode; |     public SplinePointTangentMode tangentMode; | ||||||
| 
 | 
 | ||||||
|  | @ -110,5 +114,8 @@ namespace Rokojori | ||||||
|     [Export( PropertyHint.Range, "0,1")] |     [Export( PropertyHint.Range, "0,1")] | ||||||
|     public float symmetricTangentLength = 0f; |     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 t = i / (float) ( numPoints - 1 ); | ||||||
| 
 | 
 | ||||||
|         var position = equalSpacedCurve.SampleAt( t ); |         var position = equalSpacedCurve.PositionAt( t ); | ||||||
|         var rawDirection = equalSpacedCurve.GradientAt( t, 1f / 100f ); |         var rawDirection = equalSpacedCurve.TangentAt( t, 1f / 100f ); | ||||||
|         var direction = rawDirection; |         var direction = rawDirection; | ||||||
|         var length = direction.Length(); |         var length = direction.Length(); | ||||||
|          |          | ||||||
|  |  | ||||||
|  | @ -44,8 +44,8 @@ namespace Rokojori | ||||||
|       { |       { | ||||||
|         var t = i / (float) ( numPoints - 1 ); |         var t = i / (float) ( numPoints - 1 ); | ||||||
| 
 | 
 | ||||||
|         var position = equalSpacedCurve.SampleAt( t ); |         var position = equalSpacedCurve.PositionAt( t ); | ||||||
|         var rawDirection = equalSpacedCurve.GradientAt( t, 1f / 100f ); |         var rawDirection = equalSpacedCurve.TangentAt( t, 1f / 100f ); | ||||||
|         var direction = rawDirection; |         var direction = rawDirection; | ||||||
|         var length = direction.Length(); |         var length = direction.Length(); | ||||||
|          |          | ||||||
|  |  | ||||||
|  | @ -64,6 +64,23 @@ namespace Rokojori | ||||||
|       return MathX.Fract( Mathf.Sin ( d ) * 43758.5453123f ); |       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 ) |     public static float Perlin( Vector2 position ) | ||||||
|     { |     { | ||||||
|       var index = position.Floor(); |       var index = position.Floor(); | ||||||
|  |  | ||||||
|  | @ -21,6 +21,11 @@ namespace Rokojori | ||||||
|       return this.Next()*( b - a ) + a; |       return this.Next()*( b - a ) + a; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public float Sample( Curve curve ) | ||||||
|  |     { | ||||||
|  |       return curve.Sample( Next() ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public float Polar() |     public float Polar() | ||||||
|     { |     { | ||||||
|       return this.Next() * 2f - 1f; |       return this.Next() * 2f - 1f; | ||||||
|  | @ -79,6 +84,11 @@ namespace Rokojori | ||||||
|       return new Vector2( x, y ); |       return new Vector2( x, y ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public float AngleRadians() | ||||||
|  |     { | ||||||
|  |       return Range( 0, Mathf.Pi * 2f ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public Vector3 InCube() |     public Vector3 InCube() | ||||||
|     { |     { | ||||||
|       return new Vector3( Polar(), Polar(), Polar() ); |       return new Vector3( Polar(), Polar(), Polar() ); | ||||||
|  | @ -144,7 +154,6 @@ namespace Rokojori | ||||||
|       return IntegerInclusive( 0, max ); |       return IntegerInclusive( 0, max ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|     public int IntegerExclusive( int min, int max )  |     public int IntegerExclusive( int min, int max )  | ||||||
|     { |     { | ||||||
|       var nextValue = this.Next(); |       var nextValue = this.Next(); | ||||||
|  | @ -311,6 +320,23 @@ namespace Rokojori | ||||||
|       return _FindElementIndexWithWeights( weights, Next() * sumWeights ); |       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 ) |     public int IndexFromNormalizedWeights( List<float> weights, float sumWeights = 0 ) | ||||||
|     { |     { | ||||||
|  |  | ||||||
|  | @ -62,6 +62,12 @@ namespace Rokojori | ||||||
|       data.Sort( ( a, b ) => Compare( a, b ) ); |       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 ) |     public void Sort( int index, int count, List<T> data ) | ||||||
|     { |     { | ||||||
|       ClearCache(); |       ClearCache(); | ||||||
|  |  | ||||||
|  | @ -13,6 +13,7 @@ namespace Rokojori | ||||||
| 
 | 
 | ||||||
|     public bool hasError => _hasError; |     public bool hasError => _hasError; | ||||||
|      |      | ||||||
|  | 
 | ||||||
|     void AddMatcher( LexerMatcher matcher ) |     void AddMatcher( LexerMatcher matcher ) | ||||||
|     { |     { | ||||||
|       var list = _modes.ContainsKey( matcher.mode ) ? _modes[ matcher.mode ] : null; |       var list = _modes.ContainsKey( matcher.mode ) ? _modes[ matcher.mode ] : null; | ||||||
|  | @ -77,7 +78,7 @@ namespace Rokojori | ||||||
|       var lexerEvent = new LexerEvent( "", 0, -2 ); |       var lexerEvent = new LexerEvent( "", 0, -2 ); | ||||||
| 
 | 
 | ||||||
|       var numTries = 0; |       var numTries = 0; | ||||||
|       var maxTries = 1000; |       var maxTries = 1000000; | ||||||
| 
 | 
 | ||||||
|       while ( offset < source.Length && numTries < maxTries) |       while ( offset < source.Length && numTries < maxTries) | ||||||
|       { |       { | ||||||
|  |  | ||||||
|  | @ -2,11 +2,135 @@ using System.Collections; | ||||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||||
| using System.Text; | using System.Text; | ||||||
| using System; | using System; | ||||||
|  | using Godot; | ||||||
| 
 | 
 | ||||||
| namespace Rokojori | namespace Rokojori | ||||||
| { | { | ||||||
|  |    | ||||||
|  | 
 | ||||||
|   public class Lists |   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 ) |     public static List<int> CollectIndices<T>( List<T> list, Func<T,bool> evaluator ) | ||||||
|     { |     { | ||||||
|       var output = new List<int>(); |       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 type = instance.GetType(); | ||||||
|       var fields = type.GetFields(); |       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; | ||||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||||
| using System.Text.RegularExpressions; | using System.Text.RegularExpressions; | ||||||
|  | using Godot; | ||||||
| 
 | 
 | ||||||
| namespace Rokojori | namespace Rokojori | ||||||
| {   | {   | ||||||
|   public class SVGPathCommand |   public class SVGPathCommand | ||||||
|   { |   { | ||||||
|  |     public int pathIndex = -1; | ||||||
|     public string type; |     public string type; | ||||||
|     public List<float> paramaters = new List<float>(); |     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() |     public override string ToString() | ||||||
|     { |     { | ||||||
|       if ( type == "z" || type == "Z" ) |       if ( type == "z" || type == "Z" ) | ||||||
|  |  | ||||||
|  | @ -4,73 +4,6 @@ using System.Collections.Generic; | ||||||
| 
 | 
 | ||||||
| namespace Rokojori | 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 |   public class SVGPathExtractor | ||||||
|   { |   { | ||||||
|  | @ -114,7 +47,7 @@ namespace Rokojori | ||||||
| 
 | 
 | ||||||
|         case "h": case "H":  |         case "h": case "H":  | ||||||
|         {  |         {  | ||||||
|           ProcessHorizonatlTo( command );  |           ProcessHorizontalTo( command );  | ||||||
|         }  |         }  | ||||||
|         break; |         break; | ||||||
| 
 | 
 | ||||||
|  | @ -138,7 +71,7 @@ namespace Rokojori | ||||||
| 
 | 
 | ||||||
|         case "z": case "Z": |         case "z": case "Z": | ||||||
|         { |         { | ||||||
|           ProcessClose(); |           ProcessClose( command ); | ||||||
|         } |         } | ||||||
|         break; |         break; | ||||||
| 
 | 
 | ||||||
|  | @ -146,11 +79,18 @@ namespace Rokojori | ||||||
|       } |       } | ||||||
|     }    |     }    | ||||||
| 
 | 
 | ||||||
|     void ProcessClose() |     void ProcessClose( SVGPathCommand command )  | ||||||
|     { |     { | ||||||
|  |       instruction.sourceCommand = command; | ||||||
|  |       instruction.sourceCommandIndex = 0; | ||||||
|  | 
 | ||||||
|       instruction.type = SVGPathInstructionType.Close; |       instruction.type = SVGPathInstructionType.Close; | ||||||
|       instruction.startPoint = currentPoint; |       instruction.startPoint = startPoint; | ||||||
|       instruction.endPoint   = startPoint; |       instruction.endPoint   = startPoint; | ||||||
|  | 
 | ||||||
|  |       currentPoint = startPoint; | ||||||
|  | 
 | ||||||
|  |       DispatchInstruction(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     void ProcessMoveTo( SVGPathCommand command ) |     void ProcessMoveTo( SVGPathCommand command ) | ||||||
|  | @ -158,14 +98,22 @@ namespace Rokojori | ||||||
|       var relative = command.type == "m"; |       var relative = command.type == "m"; | ||||||
|       var parameters = command.paramaters;      |       var parameters = command.paramaters;      | ||||||
| 
 | 
 | ||||||
|  |       var sourceCommandIndex = 0; | ||||||
|  |       instruction.sourceCommand = command; | ||||||
|  | 
 | ||||||
|       for ( int i = 0; i < parameters.Count; i+= 2 ) |       for ( int i = 0; i < parameters.Count; i+= 2 ) | ||||||
|       { |       { | ||||||
|         ProcessToEndpoint( relative, SVGPathInstructionType.MoveTo, parameters[ i ], parameters[ i + 1 ] ); |         instruction.sourceCommandIndex = sourceCommandIndex ++; | ||||||
| 
 | 
 | ||||||
|         if ( i == 0 ) |         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 relative = command.type == "l"; | ||||||
|       var parameters = command.paramaters; |       var parameters = command.paramaters; | ||||||
| 
 | 
 | ||||||
|  |       var sourceCommandIndex = 0; | ||||||
|  |       instruction.sourceCommand = command; | ||||||
|  | 
 | ||||||
|       for ( int i = 0; i < parameters.Count; i+= 2 ) |       for ( int i = 0; i < parameters.Count; i+= 2 ) | ||||||
|       { |       { | ||||||
|  |         instruction.sourceCommandIndex = sourceCommandIndex ++; | ||||||
|  | 
 | ||||||
|         ProcessToEndpoint( relative, SVGPathInstructionType.LineTo, parameters[ i ], parameters[ i + 1 ] ); |         ProcessToEndpoint( relative, SVGPathInstructionType.LineTo, parameters[ i ], parameters[ i + 1 ] ); | ||||||
|  |          | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -185,22 +139,34 @@ namespace Rokojori | ||||||
|       var relative = command.type == "v"; |       var relative = command.type == "v"; | ||||||
|       var parameters = command.paramaters; |       var parameters = command.paramaters; | ||||||
| 
 | 
 | ||||||
|  |       var sourceCommandIndex = 0; | ||||||
|  |       instruction.sourceCommand = command; | ||||||
|  | 
 | ||||||
|       for ( int i = 0; i < parameters.Count; i++ ) |       for ( int i = 0; i < parameters.Count; i++ ) | ||||||
|       { |       { | ||||||
|  |         instruction.sourceCommandIndex = sourceCommandIndex ++; | ||||||
|  | 
 | ||||||
|         var x = relative ? 0 : currentPoint.X; |         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 parameters = command.paramaters; | ||||||
| 
 | 
 | ||||||
|  |       var sourceCommandIndex = 0; | ||||||
|  |       instruction.sourceCommand = command; | ||||||
|  | 
 | ||||||
|       for ( int i = 0; i < parameters.Count; i++ ) |       for ( int i = 0; i < parameters.Count; i++ ) | ||||||
|       { |       { | ||||||
|  |         instruction.sourceCommandIndex = sourceCommandIndex ++; | ||||||
|  | 
 | ||||||
|  |         var x = parameters[ i ]; | ||||||
|         var y = relative ? 0 : currentPoint.Y; |         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 relative = command.type == "a"; | ||||||
|       var parameters = command.paramaters; |       var parameters = command.paramaters; | ||||||
| 
 | 
 | ||||||
|  |       var sourceCommandIndex = 0; | ||||||
|  |       instruction.sourceCommand = command; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|       for ( int i = 0; i < parameters.Count; i+= 7 ) |       for ( int i = 0; i < parameters.Count; i+= 7 ) | ||||||
|       { |       { | ||||||
|  |         instruction.sourceCommandIndex = sourceCommandIndex ++; | ||||||
|  | 
 | ||||||
|         ProcessArc( relative,  |         ProcessArc( relative,  | ||||||
|           parameters[ i ], parameters[ i + 1 ],  |           parameters[ i ], parameters[ i + 1 ],  | ||||||
|           parameters[ i + 2 ], parameters[ i + 3 ],  |           parameters[ i + 2 ], parameters[ i + 3 ],  | ||||||
|           parameters[ i + 4 ], parameters[ i + 5 ], |           parameters[ i + 4 ], parameters[ i + 5 ], | ||||||
|           parameters[ i + 7 ] |           parameters[ i + 6 ] | ||||||
|         ); |         ); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  | @ -253,8 +225,13 @@ namespace Rokojori | ||||||
|       var relative = command.type == "c"; |       var relative = command.type == "c"; | ||||||
|       var parameters = command.paramaters; |       var parameters = command.paramaters; | ||||||
| 
 | 
 | ||||||
|  |       var sourceCommandIndex = 0; | ||||||
|  |       instruction.sourceCommand = command; | ||||||
|  | 
 | ||||||
|       for ( int i = 0; i < parameters.Count; i+= 6 ) |       for ( int i = 0; i < parameters.Count; i+= 6 ) | ||||||
|       { |       { | ||||||
|  |         instruction.sourceCommandIndex = sourceCommandIndex ++; | ||||||
|  | 
 | ||||||
|         ProcessCubic( relative,  |         ProcessCubic( relative,  | ||||||
|           parameters[ i ], parameters[ i + 1 ],  |           parameters[ i ], parameters[ i + 1 ],  | ||||||
|           parameters[ i + 2 ], parameters[ i + 3 ],  |           parameters[ i + 2 ], parameters[ i + 3 ],  | ||||||
|  | @ -268,8 +245,13 @@ namespace Rokojori | ||||||
|       var relative = command.type == "s"; |       var relative = command.type == "s"; | ||||||
|       var parameters = command.paramaters; |       var parameters = command.paramaters; | ||||||
| 
 | 
 | ||||||
|  |       var sourceCommandIndex = 0; | ||||||
|  |       instruction.sourceCommand = command; | ||||||
|  | 
 | ||||||
|       for ( int i = 0; i < parameters.Count; i+= 4 ) |       for ( int i = 0; i < parameters.Count; i+= 4 ) | ||||||
|       { |       { | ||||||
|  |         instruction.sourceCommandIndex = sourceCommandIndex ++; | ||||||
|  | 
 | ||||||
|         var cpDiff = currentPoint - lastControlPoint; |         var cpDiff = currentPoint - lastControlPoint; | ||||||
|         var cpNext = relative ? cpDiff : ( currentPoint + cpDiff ); |         var cpNext = relative ? cpDiff : ( currentPoint + cpDiff ); | ||||||
| 
 | 
 | ||||||
|  | @ -315,9 +297,13 @@ namespace Rokojori | ||||||
|       var relative = command.type == "q"; |       var relative = command.type == "q"; | ||||||
|       var parameters = command.paramaters; |       var parameters = command.paramaters; | ||||||
| 
 | 
 | ||||||
|  |       var sourceCommandIndex = 0; | ||||||
|  |       instruction.sourceCommand = command; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|       for ( int i = 0; i < parameters.Count; i+= 4 ) |       for ( int i = 0; i < parameters.Count; i+= 4 ) | ||||||
|       { |       { | ||||||
|          |         instruction.sourceCommandIndex = sourceCommandIndex ++; | ||||||
|         ProcessQuadratic( relative,  |         ProcessQuadratic( relative,  | ||||||
|           parameters[ i ], parameters[ i + 1 ],  |           parameters[ i ], parameters[ i + 1 ],  | ||||||
|           parameters[ i + 2 ], parameters[ i + 3 ] |           parameters[ i + 2 ], parameters[ i + 3 ] | ||||||
|  | @ -330,8 +316,12 @@ namespace Rokojori | ||||||
|       var relative = command.type == "t"; |       var relative = command.type == "t"; | ||||||
|       var parameters = command.paramaters; |       var parameters = command.paramaters; | ||||||
| 
 | 
 | ||||||
|  |       var sourceCommandIndex = 0; | ||||||
|  |       instruction.sourceCommand = command; | ||||||
|  | 
 | ||||||
|       for ( int i = 0; i < parameters.Count; i+= 2 ) |       for ( int i = 0; i < parameters.Count; i+= 2 ) | ||||||
|       { |       { | ||||||
|  |         instruction.sourceCommandIndex = sourceCommandIndex ++; | ||||||
|         var cpDiff = currentPoint - lastControlPoint; |         var cpDiff = currentPoint - lastControlPoint; | ||||||
|         var cpNext = relative ? cpDiff : ( currentPoint + cpDiff ); |         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 ) |     void ProcessToEndpoint( bool relative, SVGPathInstructionType type, float x, float y ) | ||||||
|     { |     { | ||||||
|       instruction.type = type; |       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 lowerCase = ( d[ i ] + "" ).ToLower(); | ||||||
| 
 | 
 | ||||||
|  |          | ||||||
|  | 
 | ||||||
|         var commandIndex = SVGPathCommands.IndexOf( lowerCase ); |         var commandIndex = SVGPathCommands.IndexOf( lowerCase ); | ||||||
|          |          | ||||||
|         if ( commandIndex == -1 ) |         if ( commandIndex == -1 ) | ||||||
|  | @ -53,6 +55,8 @@ namespace Rokojori | ||||||
| 
 | 
 | ||||||
|         var commandEnd = ProcessCommand( d, i ); |         var commandEnd = ProcessCommand( d, i ); | ||||||
| 
 | 
 | ||||||
|  |         // RJLog.Log( "Processing", d[ i ], ">>", lowerCase, " ends ", commandEnd ); | ||||||
|  | 
 | ||||||
|         if ( commandEnd == -1 || Messages.HasError( messages ) ) |         if ( commandEnd == -1 || Messages.HasError( messages ) ) | ||||||
|         { |         { | ||||||
|           Messages.Error( messages, "Command couldn't be processed at '" + i + "'. Processed " + d[ i ] ); |           Messages.Error( messages, "Command couldn't be processed at '" + i + "'. Processed " + d[ i ] ); | ||||||
|  | @ -65,12 +69,13 @@ namespace Rokojori | ||||||
|       return commands; |       return commands; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     int ProcessCommand( string d, int offset) |     int ProcessCommand( string d, int offset ) | ||||||
|     { |     { | ||||||
|       var end = FindEnd( d, offset + 1 ); |       var end = FindEnd( d, offset + 1 ); | ||||||
| 
 | 
 | ||||||
|       var pc = new SVGPathCommand(); |       var pc = new SVGPathCommand(); | ||||||
|       pc.type = d[ offset ] + ""; |       pc.type = d[ offset ] + ""; | ||||||
|  |       pc.pathIndex = commands.Count; | ||||||
| 
 | 
 | ||||||
|       ReadParameters( pc, d, offset + 1, end );  |       ReadParameters( pc, d, offset + 1, end );  | ||||||
| 
 | 
 | ||||||
|  | @ -95,12 +100,33 @@ namespace Rokojori | ||||||
|     }   |     }   | ||||||
| 
 | 
 | ||||||
|     void ReadParameters( SVGPathCommand command, string d, int start, int end ) |     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 matcher = LexerMatcherLibrary.NumberMatcher; | ||||||
|       var offset = start; |       var offset = start; | ||||||
|       var isNegative = false; |       var isNegative = false; | ||||||
| 
 | 
 | ||||||
|       while ( offset <= end ) |       while ( offset < end ) | ||||||
|       { |       { | ||||||
|         if ( d[ offset ] == ' ' || d[ offset ] == ','  ) |         if ( d[ offset ] == ' ' || d[ offset ] == ','  ) | ||||||
|         { |         { | ||||||
|  |  | ||||||
|  | @ -6,6 +6,8 @@ namespace Rokojori | ||||||
| {   | {   | ||||||
|   public class XMLAttributeName:XMLElementSelector |   public class XMLAttributeName:XMLElementSelector | ||||||
|   { |   { | ||||||
|  |     public static readonly XMLAttributeName id = XMLAttributeName.Create( "id" ); | ||||||
|  | 
 | ||||||
|     string _attributeName; |     string _attributeName; | ||||||
|     string _nameSpace; |     string _nameSpace; | ||||||
| 
 | 
 | ||||||
|  | @ -35,6 +37,12 @@ namespace Rokojori | ||||||
| 
 | 
 | ||||||
|     public static XMLAttributeName Create( string type, string nameSpace = null ) |     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(); |       var elementNodeType = new XMLAttributeName(); | ||||||
|       elementNodeType._attributeName = type; |       elementNodeType._attributeName = type; | ||||||
|       elementNodeType._nameSpace = nameSpace; |       elementNodeType._nameSpace = nameSpace; | ||||||
|  |  | ||||||
|  | @ -7,7 +7,11 @@ | ||||||
|   {   |   {   | ||||||
|     public class XMLReader |     public class XMLReader | ||||||
|     { |     { | ||||||
|       XMLLexer lexer = new XMLLexer(); |       XMLLexer _lexer = new XMLLexer(); | ||||||
|  |       public XMLLexer lexer => _lexer;  | ||||||
|  |        | ||||||
|  | 
 | ||||||
|  |        | ||||||
|       TextLinesMapper linesMapper = new TextLinesMapper(); |       TextLinesMapper linesMapper = new TextLinesMapper(); | ||||||
|       List<LexerEvent> events; |       List<LexerEvent> events; | ||||||
|       XMLDocument document; |       XMLDocument document; | ||||||
|  | @ -18,9 +22,9 @@ | ||||||
|       public XMLDocument Read( string text ) |       public XMLDocument Read( string text ) | ||||||
|       { |       { | ||||||
|         this.text = text; |         this.text = text; | ||||||
|         events = lexer.LexToList( text ); |         events = _lexer.LexToList( text ); | ||||||
|          |          | ||||||
|         if ( lexer.hasError ) |         if ( _lexer.hasError ) | ||||||
|         { |         { | ||||||
|           linesMapper.Map( text ); |           linesMapper.Map( text ); | ||||||
| 
 | 
 | ||||||
|  | @ -45,7 +49,7 @@ | ||||||
| 
 | 
 | ||||||
|         CreateDocument(); |         CreateDocument(); | ||||||
| 
 | 
 | ||||||
|         RJLog.Log( document.Serialize() ); |         // RJLog.Log( document.Serialize() ); | ||||||
| 
 | 
 | ||||||
|         return document; |         return document; | ||||||
|       } |       } | ||||||
|  | @ -58,9 +62,7 @@ | ||||||
| 
 | 
 | ||||||
|         node = document; |         node = document; | ||||||
|        |        | ||||||
|         lexer.GrabMatches( events, text ); |         _lexer.GrabMatches( events, text ); | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
|         events.ForEach( |         events.ForEach( | ||||||
|           ( e )=> |           ( e )=> | ||||||
|  | @ -121,7 +123,7 @@ | ||||||
|               Add( element ); |               Add( element ); | ||||||
| 
 | 
 | ||||||
|               var parentName = node == document ? "document" : ((XMLElementNode)node).fullNodeName; |               var parentName = node == document ? "document" : ((XMLElementNode)node).fullNodeName; | ||||||
|               RJLog.Log( "Adding Element", element.fullNodeName, parentName ); |               // RJLog.Log( "Adding Element", element.fullNodeName, parentName ); | ||||||
|               node = element; |               node = element; | ||||||
|                |                | ||||||
|             }             |             }             | ||||||
|  | @ -141,7 +143,7 @@ | ||||||
|         var element = (XMLElementNode) node; |         var element = (XMLElementNode) node; | ||||||
| 
 | 
 | ||||||
|         var infos = Lists.Join( Lists.Map( insideTagElements, i => i.match ), "" ); |         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 ) ); |         var attributeNameIndices = Lists.CollectIndices( insideTagElements, le => XMLLexer.XMLAttributeName.Matches( le ) ); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -15,32 +15,108 @@ namespace Rokojori | ||||||
|     [Export] |     [Export] | ||||||
|     public bool loadFile; |     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 ) |     public override void _Process( double delta ) | ||||||
|     { |     { | ||||||
|  | 
 | ||||||
|       if ( ! loadFile ) |       if ( ! loadFile ) | ||||||
|       { |       { | ||||||
|  |         if ( autoReload ) | ||||||
|  |         { | ||||||
|  |           reloadCounter ++; | ||||||
|  | 
 | ||||||
|  |           if ( reloadCounter > reloadFrames ) | ||||||
|  |           { | ||||||
|  |             reloadCounter = 0; | ||||||
|  |             loadFile = true; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |          | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       Load(); |       Load(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |      | ||||||
|  |     string loadedPath = null; | ||||||
|  |     XMLDocument doc; | ||||||
|  | 
 | ||||||
|     void Load() |     void Load() | ||||||
|     { |     { | ||||||
|       loadFile = false; |       loadFile = false; | ||||||
| 
 | 
 | ||||||
|  |       if ( path != loadedPath ) | ||||||
|  |       {    | ||||||
|         var text = FilesSync.LoadUTF8( path ); |         var text = FilesSync.LoadUTF8( path ); | ||||||
|         var reader = new XMLReader(); |         var reader = new XMLReader(); | ||||||
| 
 | 
 | ||||||
|       var doc = reader.Read( text ); |         doc = reader.Read( text ); | ||||||
|  |       } | ||||||
|       |       | ||||||
|       var paths = doc.querySelectorAll( SVGElementName.path ); |       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( |       paths.ForEach( | ||||||
|         ( p ) => |         ( 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 ); |           var pathData = SVGAttributeName.d.Get( p ); | ||||||
| 
 | 
 | ||||||
|           if ( pathData == null ) |           if ( pathData == null ) | ||||||
|  | @ -49,7 +125,25 @@ namespace Rokojori | ||||||
|             return; |             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 ); |           var commands = SVGPathParser.Parse( pathData ); | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -75,6 +169,7 @@ namespace Rokojori | ||||||
|             RJLog.Log( "Commands failed parsing:", Lists.Join( messages, "\n" ) ); |             RJLog.Log( "Commands failed parsing:", Lists.Join( messages, "\n" ) ); | ||||||
|           } |           } | ||||||
| 
 | 
 | ||||||
|  |           */ | ||||||
| 
 | 
 | ||||||
|            |            | ||||||
|         } |         } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	 Josef
						Josef