SVG, Procedural Updates

This commit is contained in:
Josef 2024-11-12 09:03:36 +01:00
parent 521d609dd1
commit 9131afc04d
50 changed files with 3551 additions and 186 deletions

View File

@ -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

View File

@ -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 );
}
}
}
}

View File

@ -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 )

View File

@ -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 )
@ -31,6 +40,34 @@ namespace Rokojori
output.Append( RegexUtility.NumberToString( (double)obj ) ); output.Append( RegexUtility.NumberToString( (double)obj ) );
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() );
} }

View File

@ -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;
}
}
}

54
Runtime/Math/Bezier.cs Normal file
View File

@ -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;
}
}
}

View File

@ -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 ) );
}
}
}

View File

@ -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;
}
}
}

View File

@ -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 );
}
}
}

View File

@ -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 Path2 SampleXYPath( 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 = 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 GradientAt( float t, float samplingRange ) public virtual Vector3 TangentAt( float t, float samplingRange )
{ {
return SampleAt( t + samplingRange ) - SampleAt( t - 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();
@ -173,8 +438,7 @@ namespace Rokojori
} }
} }
return closestParameter; return closestParameter;
} }

View File

@ -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 );
}
}
}

View File

@ -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 )
{ {

View File

@ -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;

View File

@ -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 )

View File

@ -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;
@ -368,6 +403,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()
{ {

View File

@ -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;
}
} }
} }

View File

@ -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 );
@ -56,7 +112,7 @@ namespace Rokojori
return paths; return paths;
} }
public static Shape2 FromClipperPaths( List<List<ClipperLib.IntPoint>> paths ) public static Shape2 FromClipperPaths( List<List<ClipperLib.IntPoint>> paths )
{ {
var shape = new Shape2(); var shape = new Shape2();
@ -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();

View File

@ -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 )
{
if ( pointIndex <= 0 || pointIndex >= ( _points.Count - 1 ) )
{ {
return _points[ _points.Count - 1 ].position; 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 )

View File

@ -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 ];

View File

@ -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 )

View File

@ -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 ) );

View File

@ -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;

View File

@ -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 )
{ {

View File

@ -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;
}
}
}

View File

@ -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;

View File

@ -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 );
}
}
}

View File

@ -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;
}
}
}

View File

@ -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();

View File

@ -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,
@ -177,7 +336,7 @@ namespace Rokojori
AddTriangle( va, vb, vc, n, n, n, uv, uv, uv ); AddTriangle( va, vb, vc, n, n, n, uv, uv, uv );
} }
public void AddQuad( public void AddQuad(
Vector3 va, Vector3 vb, Vector3 vc, Vector3 vd, Vector3 va, Vector3 vb, Vector3 vc, Vector3 vd,
Vector3 na, Vector3 nb, Vector3 nc, Vector3 nd, Vector3 na, Vector3 nb, Vector3 nc, Vector3 nd,
Vector2 uva, Vector2 uvb, Vector2 uvc, Vector2 uvd Vector2 uva, Vector2 uvb, Vector2 uvc, Vector2 uvd
@ -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,

View File

@ -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 =>
{ {

View File

@ -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;
} }
} }

View File

@ -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;
}
}
}

View File

@ -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 );
}
}
}

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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 )
{ {

View File

@ -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();

View File

@ -12,6 +12,7 @@ namespace Rokojori
bool _hasError =false; bool _hasError =false;
public bool hasError => _hasError; public bool hasError => _hasError;
void AddMatcher( LexerMatcher matcher ) void AddMatcher( LexerMatcher matcher )
{ {
@ -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)
{ {

View File

@ -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>();

View File

@ -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();

107
Runtime/XML/SVG/SVGArc.cs Normal file
View File

@ -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;
}
}
}

View File

@ -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" )

View File

@ -4,74 +4,7 @@ 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
{ {
Vector2 currentPoint = new Vector2(); Vector2 currentPoint = new Vector2();
@ -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,26 +79,41 @@ 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 )
{ {
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;

View File

@ -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() );
}
}
}

View File

@ -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 ] == ',' )
{ {

View File

@ -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;

View File

@ -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 ) );

View File

@ -15,41 +15,135 @@ 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;
var text = FilesSync.LoadUTF8( path ); if ( path != loadedPath )
var reader = new XMLReader(); {
var text = FilesSync.LoadUTF8( path );
var doc = reader.Read( text ); var reader = new XMLReader();
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 )
{ {
RJLog.Log( "No path data found:", p ); RJLog.Log( "No path data found:", p );
return; return;
}
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 ); /*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" ) );
} }
*/
} }