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)
{
if ( contour.Count <= 3 )
{
var x = 0.0;
var y = 0.0;
for ( int i = 0; i < contour.Count; i++ )
{
x += contour[ i ].X;
y += contour[ i ].Y;
}
var w = contour.Count == 0 ? 0 : ( 1.0 / contour.Count );
return new Point( x * w, y * w);
}
var bounds = new Rectangle();
bounds.Expand(contour.Cast<Point>());
@ -204,7 +220,19 @@ namespace TriangleNet.Geometry
}
}
throw new Exception();
var exceptionInfo = "Found no point inside polygon:";
var sp = "G";
var ci = System.Globalization.CultureInfo.InvariantCulture;
for ( int i = 0; i < Math.Min( 20, length ); i++ )
{
exceptionInfo += " ";
exceptionInfo += "(" + contour[ i ].X.ToString( sp, ci ) + ", " + contour[ i ].Y.ToString( sp, ci ) + ")";
}
throw new Exception( exceptionInfo );
}
/// <summary>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 47 KiB

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()
{
_attributes = ReflectionHelper.GetFieldsOfType<SceneFileHeaderAttributeValue>( this );
_attributes = ReflectionHelper.GetFieldValuesOfType<SceneFileHeaderAttributeValue>( this );
}
protected void ReadAttributes( SceneFileHeader entry )

View File

@ -10,6 +10,15 @@ namespace Rokojori
public class RJLog
{
public static string Stringify( object obj )
{
var sb = new StringBuilder();
Stringify( obj, sb );
return sb.ToString();
}
public static void Stringify( object obj, StringBuilder output )
{
if ( obj == null )
@ -32,6 +41,34 @@ namespace Rokojori
return;
}
if ( obj is IList && obj.GetType().IsGenericType )
{
var list = (IList)obj;
output.Append( "[" );
var first = true;
foreach ( var it in list )
{
if ( first )
{
first = false;
}
else
{
output.Append( ", " );
}
Stringify( it, output );
}
output.Append( "]" );
return;
}
output.Append( obj.ToString() );
}

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 System.Collections;
using System.Collections.Generic;
using System;
namespace Rokojori
{
public class FresnetFrame
{
public Vector3 tangent;
public Vector3 normal;
public Vector3 binormal;
}
public abstract class Curve3
{
float _gradientSamplingRange = Mathf.Pow( 10f, -8f );
public abstract Vector3 SampleAt( float t );
float _tangentSamplingRange = Mathf.Pow( 10f, -8f );
public abstract Vector3 PositionAt( float t );
public virtual Quaternion RotationAt( float t )
{
return RotationFromTangent( t );
}
public Quaternion RotationFromTangent( float t )
{
// RJLog.Log( "Sampling Rotation From Tangent" );
var gradient = TangentAt( t );
return Math3D.LookRotation( gradient, Vector3.Up );
}
public virtual Pose PoseAt( float t )
{
return Pose.Create( PositionAt( t ), RotationAt( t ) );
}
public virtual void SampleMultiple( int numSamples, List<Vector3> output )
{
@ -21,7 +49,7 @@ namespace Rokojori
for ( var i = 0 ; i < numSamples; i++ )
{
var t = range.min + i * diff;
output.Add( SampleAt( t ) );
output.Add( PositionAt( t ) );
}
}
@ -38,7 +66,7 @@ namespace Rokojori
for ( var i = 0 ; i < numSamples; i++ )
{
var t = range.min + i * diff;
var p = SampleAt( t );
var p = PositionAt( t );
points.Add( new Vector2( p.X, p.Z ) );
}
@ -46,14 +74,136 @@ namespace Rokojori
return new Path2( points );
}
public virtual Vector3 GradientAt( float t )
public virtual Path2 SampleXYPath( int numSamples )
{
return GradientAt( t, _gradientSamplingRange );
return SampleXYPath( new Range( 0, 1 ), numSamples );
}
public virtual Vector3 GradientAt( float t, float samplingRange )
public virtual Path2 SampleXYPath( Range range, int numSamples )
{
return SampleAt( t + samplingRange ) - SampleAt( t - samplingRange );
var diff = range.length / ( numSamples - 1 ) ;
var points = new List<Vector2>();
for ( var i = 0 ; i < numSamples; i++ )
{
var t = range.min + i * diff;
var p = PositionAt( t );
points.Add( new Vector2( p.X, p.Y ) );
}
return new Path2( points );
}
public const int CurveLengthDivisions = 20;
List<float> _curveLengths = null;
public void ClearCurveLengths()
{
_curveLengths = null;
}
List<float> _getCurveLengths( int divisions = CurveLengthDivisions )
{
if ( _curveLengths != null && _curveLengths.Count == ( divisions + 1 ) )
{
return _curveLengths;
}
_curveLengths = new List<float>();
_curveLengths.Add( 0f );
var before = PositionAt( 0 );
var sum = 0f;
for ( int i = 1; i <= divisions; i ++ ) {
var p = PositionAt( (float) i / divisions );
sum += p.DistanceTo( before );
_curveLengths.Add( sum );
before = p;
}
return _curveLengths;
}
public float UtoT( float normalizedCurveLength, int numCurveLengths = -1 )
{
return ComputeTforNormalizedCurveLength( normalizedCurveLength, numCurveLengths );
}
public float ComputeTforNormalizedCurveLength( float normalizedCurveLength, int numCurveLengths = -1 )
{
var curveLengths = numCurveLengths < 0 ? _getCurveLengths() : _getCurveLengths( numCurveLengths );
var i = 0;
numCurveLengths = curveLengths.Count;
var targetCurveLength = normalizedCurveLength * curveLengths[ numCurveLengths - 1 ];
var low = 0;
var high = numCurveLengths - 1;
var comparison = 0f;
while ( low <= high )
{
i = Mathf.FloorToInt( low + ( high - low ) / 2 );
comparison = curveLengths[ i ] - targetCurveLength;
if ( comparison < 0 )
{
low = i + 1;
}
else if ( comparison > 0 )
{
high = i - 1;
}
else
{
high = i;
break;
}
}
i = high;
if ( curveLengths[ i ] == targetCurveLength )
{
return i / ( numCurveLengths - 1 );
}
var lengthBefore = curveLengths[ i ];
var lengthAfter = curveLengths[ i + 1 ];
var segmentLength = lengthAfter - lengthBefore;
var segmentFraction = ( targetCurveLength - lengthBefore ) / segmentLength;
var t = ( i + segmentFraction ) / ( numCurveLengths - 1 );
return t;
}
public virtual Vector3 TangentAt( float t )
{
return TangentAt( t, _tangentSamplingRange );
}
public virtual Vector3 TangentAt( float t, float samplingRange )
{
return PositionAt( t + samplingRange ) - PositionAt( t - samplingRange );
}
public virtual float ComputeLength( int numSamples )
@ -61,16 +211,131 @@ namespace Rokojori
return ComputeSubLength( Range.Of_01 , numSamples );
}
public List<FresnetFrame> ComputeFresnetFrames( int segments, bool closed )
{
var frames = _GetFresnetFramesWithTangents( segments, closed );
_ComputeInitialNormalVector( frames );
_ComputeNormalsAndBinormals( frames, closed );
return frames;
}
List<FresnetFrame> _GetFresnetFramesWithTangents( int segments, bool closed )
{
var frames = new List<FresnetFrame>();
for ( int i = 0; i <= segments; i++ )
{
var fresnetFrame = new FresnetFrame();
var u = (float) i / segments;
var t = ComputeTforNormalizedCurveLength( u );
fresnetFrame.tangent = TangentAt( t ) ;
frames.Add( fresnetFrame );
}
return frames;
}
void _ComputeInitialNormalVector( List<FresnetFrame> frames )
{
frames[ 0 ].normal = new Vector3();
frames[ 0 ].binormal = new Vector3();
var min = float.MaxValue;
var tx = Math.Abs( frames[ 0 ].tangent.X );
var ty = Math.Abs( frames[ 0 ].tangent.Y );
var tz = Math.Abs( frames[ 0 ].tangent.Z );
var normal = Vector3.Zero;
if ( tx <= min )
{
min = tx;
normal = new Vector3( 1, 0, 0 );
}
if ( ty <= min )
{
min = ty;
normal = new Vector3( 0, 1, 0 );
}
if ( tz <= min )
{
normal = new Vector3( 0, 0, 1 );
}
var vec = frames[ 0 ].tangent.Cross( normal ).Normalized();
frames[ 0 ].normal = frames[ 0 ].tangent.Cross( vec );
frames[ 0 ].binormal = frames[ 0 ].tangent.Cross( frames[ 0 ].normal );
}
void _ComputeNormalsAndBinormals( List<FresnetFrame> frames, bool closed )
{
for ( int i = 1; i < frames.Count; i++ )
{
frames[ i ].normal = frames[ i - 1 ].normal;
frames[ i ].binormal = frames[ i - 1 ].binormal;
var vec = frames[ i - 1 ].tangent.Cross( frames[ i ].tangent );
if ( vec.Length() > Mathf.Epsilon )
{
vec = vec.Normalized();
var dot = frames[ i - 1 ].tangent.Dot( frames[ i ].tangent );
var angle = Mathf.Acos( Mathf.Clamp( dot, - 1, 1 ) );
var rotationMatrix = new Transform3D().Rotated( vec, angle );
frames[ i ].normal = frames[ i ].normal * rotationMatrix;
}
frames[ i ].binormal = frames[ i ].tangent.Cross( frames[ i ].normal );
}
if ( ! closed )
{
return;
}
var d = frames[ 0 ].normal.Dot( frames[ frames.Count - 1 ].normal );
var theta = Mathf.Acos( Mathf.Clamp( d, - 1, 1 ) );
theta /= frames.Count;
if ( frames[ 0 ].tangent.Dot( frames[ 0 ].normal.Cross( frames[ frames.Count - 1 ].normal ) ) > 0 ) {
theta = -theta;
}
for ( int i = 1; i < frames.Count; i ++ )
{
var rm = new Transform3D().Rotated( frames[ i ].tangent, theta * i );
frames[ i ].normal = frames[ i ].normal * rm;
frames[ i ].binormal = frames[ i ].tangent.Cross( frames[ i ].normal );
}
}
public virtual float ComputeSubLength( Range range, int numSamples )
{
var diff = range.length / ( numSamples - 1 ) ;
var it = SampleAt( range.min );
var it = PositionAt( range.min );
var length = 0f;
for ( var i = 1 ; i < numSamples; i++ )
{
var t = range.min + i * diff;
var next = SampleAt( t );
var next = PositionAt( t );
length += ( next - it ).Length();
it = next;
}
@ -99,7 +364,7 @@ namespace Rokojori
{
var parameter = GetClosestParameterTo( position, numSegments, depth );
return SampleAt( parameter );
return PositionAt( parameter );
}
public float GetClosestParameterTo( Vector3 point, int numSegments = 20, int depth = 3 )
@ -121,7 +386,7 @@ namespace Rokojori
var closestDistance = float.MaxValue;
var minT = 0f;
var maxT = 1f;
var startPoint = SampleAt( tStart );
var startPoint = PositionAt( tStart );
var line = new Line3();
var lastT = tStart;
@ -129,7 +394,7 @@ namespace Rokojori
for ( int i = 1; i < numSegments; i++ )
{
var t = tNormalizer * i + tStart;
var nextCurvePoint = SampleAt( t );
var nextCurvePoint = PositionAt( t );
line.Set( startPoint, nextCurvePoint );
@ -162,7 +427,7 @@ namespace Rokojori
for ( int i = 0; i < numSegments; i++ )
{
var detailedT = i * tNormalizer2 + minT;
var sampledPoint = SampleAt( detailedT );
var sampledPoint = PositionAt( detailedT );
var distance = ( sampledPoint - position ).LengthSquared();
@ -175,7 +440,6 @@ namespace Rokojori
return closestParameter;
}

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++ )
{
var t = j / (float)( numPoints - 1 );
spacedPoints.Add( line.SampleAt( t ) );
spacedPoints.Add( line.PositionAt( t ) );
}
}
@ -115,7 +115,7 @@ namespace Rokojori
{
var t = i / ( (float) numSamples - 1 );
var p = curve.SampleAt( t );
var p = curve.PositionAt( t );
points.Add( p );
}
@ -232,7 +232,7 @@ namespace Rokojori
return output;
}
public override Vector3 SampleAt( float t )
public override Vector3 PositionAt( float t )
{
if ( t < 0 )
{

View File

@ -10,6 +10,11 @@ namespace Rokojori
public Vector2 end = Vector2.Zero;
public Line2( Vector2 start, Vector2 end )
{
Set( start, end );
}
public void Set( Vector2 start, Vector2 end )
{
this.start = start;
this.end = end;

View File

@ -28,7 +28,7 @@ namespace Rokojori
this.end = end;
}
public override Vector3 SampleAt( float t )
public override Vector3 PositionAt( float t )
{
return start.Lerp( end, t );
}
@ -143,7 +143,7 @@ namespace Rokojori
public Vector3 ClosestPointTo( Line3 other )
{
var parameters = ClosestParametersToLine( other );
return SampleAt( parameters.X );
return PositionAt( parameters.X );
}
public Vector3 ClosestPointTo( params Vector3[] points )

View File

@ -4,6 +4,9 @@ using Godot;
namespace Rokojori
{
public enum PointInPathResult
{
INSIDE,
@ -29,11 +32,38 @@ namespace Rokojori
this._points = points;
}
public override string ToString()
{
return "Rokojori.Path2 with " + points.Count + " points" ;
}
public Path2( Vector2[] points )
{
this._points = new List<Vector2>( points );
}
public void ApplyTransform( Transform2D transform2D )
{
for ( int i = 0; i < _points.Count; i++ )
{
_points[ i ] = transform2D * _points[ i ];
}
}
public void MirrorX( float offset = 0 )
{
for ( int i = 0; i < _points.Count; i++ )
{
var p = _points[ i ];
p.X -= offset;
p.X = - _points[ i ].X;
p.X += offset;
_points[ i ] = p;
}
}
public int numPoints => _points.Count;
public bool empty => _points.Count == 0;
@ -200,7 +230,7 @@ namespace Rokojori
for ( int i = 0; i < numPoints; i++ )
{
points.Add( Math2D.XZ( curve3.SampleAt( i * normalizer ) ) );
points.Add( Math2D.XZ( curve3.PositionAt( i * normalizer ) ) );
}
if ( closed )
@ -239,6 +269,11 @@ namespace Rokojori
return CheckPointInPathFast( point, max + new Vector2( 122.133544f, 129.45423f ) );
}
public Path2 Clone()
{
return new Path2( new List<Vector2>( _points ) );
}
public bool PointInPathReliable( Vector2 point, bool checkBoundingBox = true )
{
var min = this.leftTop;
@ -369,6 +404,40 @@ namespace Rokojori
return list;
}
public void Reverse()
{
_points.Reverse();
ClearCachedProperties();
}
public void PositionJitter( float max, Vector2 scale, Vector2 offset, RandomEngine random = null )
{
if ( random == null )
{
random = GodotRandom.Get();
}
for ( int i = 0; i < _points.Count; i++ )
{
_points[ i ] = Noise.PerlinOffset( _points[ i ], max, scale, offset, random );
}
}
public string ToSVGPath()
{
var pathCommands = new List<string>();
pathCommands.Add( "M " + SVGPathCommand.SVGCoordinate( points[ 0 ] ) );
for ( int i = 1; i < points.Count; i++ )
{
pathCommands.Add( "L " + SVGPathCommand.SVGCoordinate( points[ i ] ) );
}
return Lists.Join( pathCommands, " ");
}
public MeshGeometry CreateMeshGeometry()
{
var geometry = new MeshGeometry();

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 )
{
@ -96,5 +137,38 @@ namespace Rokojori
return p;
}
public Vector3 ApplyInverse( Vector3 p )
{
p -= position;
p = rotation.Inverse() * p;
return p;
}
public Vector3 OnRightUpCircle( float radians, float size = 1 )
{
var c = Mathf.Cos( radians ) * size;
var s = Mathf.Sin( radians ) * size;
return s * right + c * up + position;
}
public Vector3 ComputeCircleValues( float radians, Vector3 cos, Vector3 sin, float size )
{
var c = Mathf.Cos( radians ) * size;
var s = Mathf.Sin( radians ) * size;
var cValue = c * cos.X * right +
c * cos.Y * up +
c * cos.Z * forward;
var sValue = s * sin.X * right +
s * sin.Y * up +
s * sin.Z * forward;
return cValue + sValue + position;
}
}
}

View File

@ -7,9 +7,19 @@ using TriangleNet.Meshing.Algorithm;
namespace Rokojori
{
using ClipperPath = List<ClipperLib.IntPoint>;
using ClipperShape = List<List<ClipperLib.IntPoint>>;
public enum ShapeFillRule
{
NonZero,
EvenOdd
}
public class Shape2
{
public List<Path2> paths = new List<Path2>();
public ShapeFillRule fillRule = ShapeFillRule.NonZero;
public Shape2( params Path2[] paths )
{
@ -21,20 +31,66 @@ namespace Rokojori
this.paths.AddRange( paths );
}
Polygon _CreateTNetPolygon()
public Shape2 CleanUp( ShapeFillRule fillRule = ShapeFillRule.NonZero )
{
var clipperPath = ToClipperPaths( this );
var clipper = new ClipperLib.Clipper();
var fillType = _ConvertFillRule( fillRule );
clipperPath = ClipperLib.Clipper.SimplifyPolygons( clipperPath, fillType );
return FromClipperPaths( clipperPath );
}
Polygon _CreateTNetPolygon( bool simplify = true )
{
// RJLog.Log( "Creating polygon", paths.Count );
if ( paths.Count == 0 )
{
RJLog.Log( "No paths" );
return null;
}
var polyPaths = paths;
if ( simplify && false )
{
var resultPaths = Lists.Map( polyPaths, p => Path2.ToClipperPath( p ) );
resultPaths = ClipperLib.Clipper.SimplifyPolygons( resultPaths, ClipperLib.PolyFillType.pftEvenOdd );
polyPaths = new List<Path2>();
resultPaths.ForEach(
( r ) =>
{
polyPaths.Add( Path2.FromClipperPath( r ) );
}
);
}
// RJLog.Log( "Using paths", polyPaths.Count );
var polygon = new Polygon();
var index = 0;
paths.ForEach(
var firstIsClockWise = polyPaths.Count > 0 && polyPaths[ 0 ].isClockwise;
polyPaths.ForEach(
( path )=>
{
var vertices = Lists.Map( path.points, p => new Vertex( p.X, p.Y ) );
var isHole = path.isClockwise;
var center = path.center;
var isClockwise = path.isClockwise;
var isHole = firstIsClockWise != isClockwise;
// RJLog.Log( "Adding contour", vertices.Count, isHole );
// RJLog.Log( "Adding contour", center, vertices.Count, isHole ? "Hole" : "Fill" );
polygon.Add( new Contour( vertices, index ), isHole );
@ -65,6 +121,54 @@ namespace Rokojori
return shape;
}
static ClipperLib.PolyFillType _ConvertFillRule( ShapeFillRule fillRule )
{
return ShapeFillRule.EvenOdd == fillRule ? ClipperLib.PolyFillType.pftEvenOdd : ClipperLib.PolyFillType.pftNonZero;
}
public string ToSVGPath()
{
var svgPaths = Lists.Map( paths, p => p.ToSVGPath() );
return Lists.Join( svgPaths, " " );
}
public static Shape2 UnionAll( List<Path2> paths )
{
paths.ForEach( p =>
{
if ( ! p.isClockwise )
{
p.Reverse();
}
// RJLog.Log( pathIndex++, p.isClockwise );
}
);
var shape = new Shape2();
shape.fillRule = ShapeFillRule.EvenOdd;
// shape.paths = paths;
// shape.CleanUp();
shape.paths.Add( paths[ 0 ] );
for ( int i = 1; i < paths.Count; i++ )
{
var otherShape = new Shape2();
otherShape.fillRule = ShapeFillRule.EvenOdd;
otherShape.paths.Add( paths[ i ] );
var pathsBefore = shape.paths.Count;
shape = Shape2.Boolean( shape, otherShape, Geometry2D.PolyBooleanOperation.Union );
// RJLog.Log( "Union:", pathsBefore, shape.paths.Count );
}
return shape;
}
public static Shape2 Boolean( Shape2 a, Shape2 b, Geometry2D.PolyBooleanOperation booleanOperation, bool simplify = true )
{
// RJLog.Log( "Using Clipper Library" );
@ -89,11 +193,14 @@ namespace Rokojori
type = ClipperLib.ClipType.ctXor;
}
RJLog.Log( "ShapeBool", "type: " + type, "boolOp: " + booleanOperation, "A|B >>",clipperPathsA.Count, clipperPathsB.Count );
var subjectPolyType = _ConvertFillRule( a.fillRule );
var clipPolyType = _ConvertFillRule( b.fillRule );
// RJLog.Log( "ShapeBool", "type: " + type, "boolOp: " + booleanOperation, "A|B >>",clipperPathsA.Count, clipperPathsB.Count );
var clipper = new ClipperLib.Clipper();
clipper.AddPaths( clipperPathsA, ClipperLib.PolyType.ptSubject, true);
clipper.AddPaths( clipperPathsB, ClipperLib.PolyType.ptClip, true);
clipper.Execute( type, resultPaths );
clipper.Execute( type, resultPaths, subjectPolyType, clipPolyType );
if ( simplify )
{
@ -113,16 +220,130 @@ namespace Rokojori
return s;
}
public MeshGeometry CreateMeshGeometry()
public static Shape2 FromSVGPath( string pathData, float resolution )
{
if ( paths.Count == 1 )
var shape = new Shape2();
var commands = SVGPathParser.Parse( pathData );
if ( commands != null )
{
/*
RJLog.Log( "Commands:", commands.Count );
var commandsInfo = new List<string>();
var cindex = 0;
commands.ForEach(
( c )=>
{
commandsInfo.Add( "[" + cindex + "] " + c.type + ":\n" + RJLog.Stringify( c.paramaters ) );
cindex ++;
}
);
RJLog.Log( Lists.Join( commandsInfo, "\n" ) );
*/
var extractor = new SVGPathExtractor();
List<Vector2> pathPoints = null;
var index = 0;
extractor.onInstruction.AddAction(
( si )=>
{
var newPathIsStarting = SVGPathInstructionType.MoveTo == si.type ||
(
SVGPathInstructionType.MoveTo != si.type && pathPoints == null
);
if ( newPathIsStarting )
{
if ( pathPoints != null )
{
shape.paths.Add( new Path2( pathPoints ) );
}
pathPoints = new List<Vector2>();
}
var debugPoints = new List<Vector2>();
si.AddPoints( debugPoints, resolution );
var cm = si.sourceCommand;
var parameterLength = SVGPathCommand.GetParameterLengthForCommand( cm.type );
var cmInfo = "[" + cm.pathIndex + "]" + si.sourceCommand.type;
if ( parameterLength > 0 )
{
cmInfo += " ";
var offset = parameterLength * si.sourceCommandIndex;
for ( int i = 0; i < parameterLength; i++ )
{
if ( i != 0 )
{
cmInfo += ", ";
}
cmInfo += RegexUtility.NumberToString( cm.paramaters[ offset + i ] );
}
}
// RJLog.Log( index, newPathIsStarting ? "+" : " ", si.type, debugPoints, ">>", cmInfo );
index++;
si.AddPoints( pathPoints, resolution );
}
);
extractor.Process( commands );
if ( pathPoints != null )
{
shape.paths.Add( new Path2( pathPoints ) );
}
}
else
{
RJLog.Log( "No commands found for:", pathData );
var messages = SVGPathParser.GetMessages( pathData );
RJLog.Log( messages );
}
// RJLog.Log( "Shape has", shape.paths.Count, "paths >> ", Lists.Map( shape.paths, p => p.points.Count + "" ) );
return shape;
}
public MeshGeometry CreateFillMeshGeometry()
{
/*if ( paths.Count == 1 )
{
// RJLog.Log( "Only 1 path" );
return paths[ 0 ].CreateMeshGeometry();
}
}*/
var polygon = _CreateTNetPolygon();
if ( polygon == null )
{
RJLog.Log( "Could not create polygon" );
return null;
}
var options = new ConstraintOptions();
var quality = new QualityOptions();
var triangulator = new Dwyer();

View File

@ -1,3 +1,4 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Godot;
@ -24,6 +25,8 @@ namespace Rokojori
{
public Vector3 position;
public Quaternion rotation;
public Vector3 scale;
public float twist = 0f;
public float weight = 1f;
public SplineCurveTangent tangentBefore = new SplineCurveTangent();
@ -34,14 +37,16 @@ namespace Rokojori
var scp = new SplineCurvePoint();
scp.position = position;
scp.rotation = rotation;
scp.scale = scale;
scp.weight = weight;
scp.twist = twist;
scp.tangentBefore = tangentBefore.Clone();
scp.tangentNext = tangentNext.Clone();
return scp;
}
public SplineCurvePoint CloneForXZ( float y )
public SplineCurvePoint CloneForXZ( float y = 0 )
{
var cloned = Clone();
cloned.position.Y = y;
@ -51,6 +56,29 @@ namespace Rokojori
return cloned;
}
public SplineCurvePoint CloneForXY( float z = 0 )
{
var cloned = Clone();
cloned.position.Z = z;
cloned.tangentBefore.position.Z = 0;
cloned.tangentNext.position.Z = 0;
return cloned;
}
public SplineCurvePoint ApplyPose( Pose p )
{
var cloned = Clone();
cloned.position = p.Apply( cloned.position );
cloned.rotation = cloned.rotation * p.rotation;
cloned.tangentBefore.position = p.Apply( cloned.tangentBefore.position );
cloned.tangentNext.position = p.Apply( cloned.tangentNext.position );
return cloned;
}
}
public class SplineCurve: Curve3
@ -92,24 +120,37 @@ namespace Rokojori
return splineCurve;
}
public SplineCurve CloneForXZ( float y )
public SplineCurve ApplyPose( Pose pose )
{
var splineCurve = new SplineCurve();
splineCurve._points = Lists.Map( points, p => p.ApplyPose( pose ) );
return splineCurve;
}
public SplineCurve CloneForXZ( float y = 0 )
{
var splineCurve = new SplineCurve();
splineCurve._points = Lists.Map( points, p => p.CloneForXZ( y ) );
return splineCurve;
}
public Vector3 GetByPointIndex( float pointIndex )
public SplineCurve CloneForXY( float z = 0 )
{
if ( pointIndex <= 0 )
{
return _points[ 0 ].position;
var splineCurve = new SplineCurve();
splineCurve._points = Lists.Map( points, p => p.CloneForXY( z ) );
return splineCurve;
}
if ( pointIndex >= ( _points.Count - 1 ) )
public Vector3 GetPositionByPointIndex( float pointIndex )
{
return _points[ _points.Count - 1 ].position;
if ( pointIndex <= 0 || pointIndex >= ( _points.Count - 1 ) )
{
var index = pointIndex <= 0 ? 0 : ( _points.Count - 1 );
return _points[ index ].position;
}
var lower = Mathf.FloorToInt( pointIndex );
@ -133,7 +174,147 @@ namespace Rokojori
);
}
public override Vector3 SampleAt( float t )
public Pose GetPoseByPointIndex( float pointIndex )
{
if ( pointIndex <= 0 || pointIndex >= ( _points.Count - 1 ) )
{
var index = pointIndex <= 0 ? 0 : ( _points.Count - 1 );
return Pose.Create( _points[ index ].position, _points[ index ].rotation );
}
var lower = Mathf.FloorToInt( pointIndex );
var higher = Mathf.CeilToInt( pointIndex );
var lerpAmount = pointIndex - lower;
var pLower = _points[ lower ];
var pHigher = _points[ higher ];
var position = RationalCubicBezier.Compute(
lerpAmount,
pLower.position,
pLower.tangentNext.position,
pHigher.tangentBefore.position,
pHigher.position,
pLower.weight,
pLower.tangentNext.weight,
pHigher.tangentBefore.weight,
pHigher.weight
);
var rotation = pLower.rotation.Slerp( pHigher.rotation, lerpAmount );
return Pose.Create( position, rotation );
}
public Quaternion GetRotationByPointIndex( float pointIndex )
{
if ( pointIndex <= 0 || pointIndex >= ( _points.Count - 1 ) )
{
var index = pointIndex <= 0 ? 0 : ( _points.Count - 1 );
return _points[ index ].rotation;
}
var lower = Mathf.FloorToInt( pointIndex );
var higher = Mathf.CeilToInt( pointIndex );
var lerpAmount = pointIndex - lower;
return _points[ lower ].rotation.Slerp( _points[ higher ].rotation, lerpAmount );
}
public Vector3 GetScaleByPointIndex( float pointIndex )
{
if ( pointIndex <= 0 || pointIndex >= ( _points.Count - 1 ) )
{
var index = pointIndex <= 0 ? 0 : ( _points.Count - 1 );
return _points[ index ].scale;
}
var lower = Mathf.FloorToInt( pointIndex );
var higher = Mathf.CeilToInt( pointIndex );
var lerpAmount = pointIndex - lower;
return _points[ lower ].scale.Lerp( _points[ higher ].scale, lerpAmount );
}
public float GetTwistByPointIndex( float pointIndex )
{
if ( pointIndex <= 0 || pointIndex >= ( _points.Count - 1 ) )
{
var index = pointIndex <= 0 ? 0 : ( _points.Count - 1 );
return _points[ index ].twist;
}
var lower = Mathf.FloorToInt( pointIndex );
var higher = Mathf.CeilToInt( pointIndex );
var lerpAmount = pointIndex - lower;
return Mathf.Lerp( _points[ lower ].twist, _points[ higher ].twist, lerpAmount );
}
public T LerpByPointIndex<T>( float pointIndex, Func<int,T> get, Func<T,T,float,T> lerp )
{
if ( pointIndex <= 0 || pointIndex >= ( _points.Count - 1 ) )
{
var index = pointIndex <= 0 ? 0 : ( _points.Count - 1 );
return get( index );
}
var lower = Mathf.FloorToInt( pointIndex );
var higher = Mathf.CeilToInt( pointIndex );
var lerpAmount = pointIndex - lower;
return lerp( get( lower ), get( higher ), lerpAmount );
}
public T SmoothStepByPointIndex<T>( float pointIndex, Func<int,T> get, Func<T,T,float,T> lerp )
{
if ( pointIndex <= 0 || pointIndex >= ( _points.Count - 1 ) )
{
var index = pointIndex <= 0 ? 0 : ( _points.Count - 1 );
return get( index );
}
var lower = Mathf.FloorToInt( pointIndex );
var higher = Mathf.CeilToInt( pointIndex );
var lerpAmount = pointIndex - lower;
var smoothStepAmount = Mathf.SmoothStep( 0, 1, lerpAmount );
return lerp( get( lower ), get( higher ), smoothStepAmount );
}
public Vector3 SmoothStepScaleByPointIndex( float pointIndex )
{
return SmoothStepByPointIndex<Vector3>( pointIndex,
( id ) => _points[ id ].scale,
( a, b, t ) => a.Lerp( b, t )
);
}
public float SmoothStepTwistByPointIndex( float pointIndex )
{
return SmoothStepByPointIndex<float>( pointIndex,
( id ) => _points[ id ].twist,
( a, b, t ) => Mathf.Lerp( a, b, t )
);
}
public override Vector3 PositionAt( float t )
{
if ( _points.Count <= 0 )
{
@ -142,7 +323,20 @@ namespace Rokojori
var index = NormalizedToPointIndex( t );
return GetByPointIndex( index );
return GetPositionByPointIndex( index );
}
public override Quaternion RotationAt( float t )
{
if ( _points.Count <= 0 )
{
return Quaternion.Identity;
}
var index = NormalizedToPointIndex( t );
return GetRotationByPointIndex( index );
}
public float NormalizedToPointIndex( float normalized )

View File

@ -31,10 +31,13 @@ namespace Rokojori
var splineCurvePoint = new SplineCurvePoint();
var splinePoint = splinePoints[ index ];
splineCurvePoint.position = splinePoint.GlobalPosition;
splineCurvePoint.rotation = splinePoint.GetGlobalQuaternion();
splineCurvePoint.scale = splinePoint.Scale;
splineCurvePoint.twist = splinePoint.twist;
splineCurvePoint.weight = 1f;
splineCurvePoint.tangentBefore.position = GetTangentPosition( splinePoints, index, true );
splineCurvePoint.tangentNext.position = GetTangentPosition( splinePoints, index, false );
splineCurvePoint.tangentBefore.position = GetTangentPosition( splinePoints, index, true, closed );
splineCurvePoint.tangentNext.position = GetTangentPosition( splinePoints, index, false, closed );
splineCurvePoint.tangentBefore.weight = splinePoint.tangentBeforeWeight;
splineCurvePoint.tangentNext.weight = splinePoint.tangentNextWeight;
@ -42,7 +45,54 @@ namespace Rokojori
return splineCurvePoint;
}
public Vector3 GetTangentPosition( List<SplinePoint> splinePoints, int index, bool before )
public static Vector3 GetTangentDirectionSmoothed( float smoothing, List<SplinePoint> splinePoints, int index, bool before, bool closed )
{
var previousIndex = MathX.SafeIndex( index - 1, splinePoints.Count, closed );
var currentIndex = index;
var nextIndex = MathX.SafeIndex( index + 1, splinePoints.Count, closed );
var previousDirection = GetTangentDirection( splinePoints, previousIndex, before, closed );
var currentDirection = GetTangentDirection( splinePoints, currentIndex, before, closed );
var nextDirection = GetTangentDirection( splinePoints, nextIndex, before, closed );
var smoothed = ( previousDirection + currentDirection + nextDirection ) / 3.0f;
return currentDirection + smoothing * ( smoothed - currentDirection );
}
public static Vector3 GetTangentDirectionSmoothed( int numSamples, float smoothing, List<SplinePoint> splinePoints, int index, bool before, bool closed )
{
var smoothedTangent = Vector3.Zero;
var unsmoothedTangent = Vector3.Zero;
for ( int i = -numSamples; i <= numSamples; i++ )
{
var sampleIndex = MathX.SafeIndex( index + i, splinePoints.Count, closed );
var direction = GetTangentDirection( splinePoints, sampleIndex, before, closed );
smoothedTangent += direction;
if ( i == 0 )
{
unsmoothedTangent = direction;
}
}
smoothedTangent /= ( numSamples * 2 + 1 );
return unsmoothedTangent + smoothing * ( smoothedTangent - unsmoothedTangent );
}
public static Vector3 GetTangentDirection( List<SplinePoint> splinePoints, int index, bool before, bool closed )
{
var splinePoint = splinePoints[ index ];
return GetTangentPosition( splinePoints, index, before, closed ) - splinePoint.GlobalPosition;
}
public static Vector3 GetTangentPosition( List<SplinePoint> splinePoints, int index, bool before, bool closed )
{
var splinePoint = splinePoints[ index ];

View File

@ -182,6 +182,32 @@ namespace Rokojori
return line.ClosestPointTo( ab, bc, ca );
}
public float GetHeightOfPoint( int i )
{
var p = GetPoint( i );
var b = GetPoint( ( i + 1 ) % 3 );
var a = GetPoint( ( i + 2 ) % 3 );
var ab = Line3.ClosestPointOf( p, a, b );
return ( p - ab ).Length();
}
public float GetArea( int i = 0 )
{
var p = GetPoint( i );
var b = GetPoint( ( i + 1 ) % 3 );
var a = GetPoint( ( i + 2 ) % 3 );
var ab = Line3.ClosestPointOf( p, a, b );
var h = ( p - ab ).Length();
return ( ( a - b ).Length() * h ) / 2f;
}
public Vector3 GetPoint( int i )
{
if ( i == 0 )

View File

@ -18,6 +18,30 @@ namespace Rokojori
return new Vector2( v.X, v.Z );
}
public static Vector2 Map( Vector2 value, Vector2 inMin, Vector2 inMax, Vector2 outMin, Vector2 outMax )
{
return new Vector2(
MathX.Map( value.X, inMin.X, inMax.X, outMin.X, outMax.X ),
MathX.Map( value.Y, inMin.Y, inMax.Y, outMin.Y, outMax.Y )
);
}
public static Vector2 Clamp01( Vector2 v )
{
return new Vector2(
MathX.Clamp01( v.X ),
MathX.Clamp01( v.Y )
);
}
public static Vector2 Circle( float radians, float size = 1 )
{
var x = Mathf.Cos( radians ) * size;
var y = Mathf.Sin( radians ) * size;
return new Vector2( x, y );
}
public static Vector2 Fract( Vector2 a )
{
return new Vector2( MathX.Fract( a.X ), MathX.Fract( a.Y ) );

View File

@ -8,6 +8,15 @@ namespace Rokojori
{
public static class Math3D
{
public static Vector3 Clamp01( Vector3 v )
{
return new Vector3(
MathX.Clamp01( v.X ),
MathX.Clamp01( v.Y ),
MathX.Clamp01( v.Z )
);
}
public static Vector3 OnCircleXZ( float radians, float size = 1 )
{
var x = Mathf.Cos( radians ) * size;
@ -16,6 +25,14 @@ namespace Rokojori
return new Vector3( x, 0, z );
}
public static Vector3 OnCircleXY( float radians, float size = 1 )
{
var x = Mathf.Cos( radians ) * size;
var y = Mathf.Sin( radians ) * size;
return new Vector3( x, y, 0 );
}
public static Vector3 XYasXZ( Vector2 v )
{
return new Vector3( v.X, 0, v.Y );
@ -212,6 +229,24 @@ namespace Rokojori
return aligned.GetRotationQuaternion();
}
public static float AngleXY( Vector3 direction )
{
return Mathf.Atan2( direction.Y, direction.X );
}
public static float AngleXZ( Vector3 direction )
{
return Mathf.Atan2( direction.Z, direction.X );
}
public static Vector3 NormalAngle( float angle )
{
var y = Mathf.Sin( angle );
var z = Mathf.Cos( angle );
return new Vector3( 0, y, z );
}
public static Basis AlignUp( Basis basis, Vector3 upDirection )
{
basis.Y = upDirection;
@ -250,6 +285,27 @@ namespace Rokojori
return new Vector4( q.X, q.Y, q.Z, q.W );
}
public static Quaternion RotateX( float radians )
{
if ( radians == 0 ) { return Quaternion.Identity; }
return Quaternion.FromEuler( new Vector3( radians, 0, 0 ) );
}
public static Quaternion RotateY( float radians )
{
if ( radians == 0 ) { return Quaternion.Identity; }
return Quaternion.FromEuler( new Vector3( 0, radians, 0 ) );
}
public static Quaternion RotateZ( float radians )
{
if ( radians == 0 ) { return Quaternion.Identity; }
return Quaternion.FromEuler( new Vector3( 0, 0, radians ) );
}
public static void SetGlobalQuaternion( this Node3D node, Quaternion quaternion )
{
var offset = node.GlobalPosition;

View File

@ -10,6 +10,29 @@ namespace Rokojori
{
public const float fps120Delta = 1/120f;
static Dictionary<int,int> factorialLookUp = new Dictionary<int, int>();
public static int Factorial( int i )
{
if ( factorialLookUp.ContainsKey( i ) )
{
return factorialLookUp[ i ];
}
var result = 1;
if ( i > 1 )
{
var before = Factorial( i - 1 );
result = before * i;
}
factorialLookUp[ i ] = result;
return factorialLookUp[ i ];
}
public static float Min( params float[] values )
{
var value = - float.MaxValue;
@ -134,6 +157,7 @@ namespace Rokojori
return Map( value, 0, inputMax, 0, outputMax );
}
public static float MapClamped( float value, float inputMin, float inputMax,
float outputMin, float outputMax )
{
@ -242,6 +266,18 @@ namespace Rokojori
return ( phase - phaseStart ) / ( phaseStart - phaseEnd );
}
public static int SafeIndex( int index, int maxElements, bool wrap = false )
{
if ( wrap )
{
return MathX.Repeat( index, maxElements );
}
else
{
return Mathf.Clamp( index, 0, maxElements - 1 );
}
}
public static int Repeat( int value, int range )
{
@ -273,6 +309,70 @@ namespace Rokojori
return value;
}
public static Curve Curve( float y0, float y1 )
{
var curve = new Curve();
curve.AddPoint( new Vector2( 0, y0 ) );
curve.AddPoint( new Vector2( 1, y1 ) );
curve.SetPointRightMode( 0, Godot.Curve.TangentMode.Linear );
curve.SetPointLeftMode( 1, Godot.Curve.TangentMode.Linear );
return curve;
}
public static Curve Curve( float y )
{
return Curve( y, y );
}
public static float CurveAngle( Curve c, float t, float samplingRange = 0.01f )
{
var x0 = Mathf.Max( t - samplingRange, 0 );
var x1 = Mathf.Min( t + samplingRange, 1 );
var y0 = c.Sample( x0 );
var y1 = c.Sample( x1 );
return Mathf.Atan2( y1 - y0, x1 - x0 );
}
public static float CurveMaximum( Curve c, int numSamples = 20 )
{
var max = 0f;
for ( int i = 0; i < numSamples; i++ )
{
max = Mathf.Max( max, c.Sample( (float) i / ( numSamples - 1 ) ) );
}
return max;
}
public static List<float> GetCurveWeights( Curve curve, int num, bool normalize = true )
{
var sum = 0f;
var weights = new List<float>();
for ( int i = 0; i < num; i++ )
{
var t = (float)i / ( num - 1 );
var w = curve.Sample( t );
sum += w;
weights.Add( w );
}
if ( normalize )
{
for ( int i = 0; i < num; i++ )
{
weights[ i ] = weights[ i ] / sum;
}
}
return weights;
}
public static float Exponent( float base_, float power )
{

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 ); }
}
public static Range Of_Zero
{
get { return new Range( 0, 0 ); }
}
public static Range Of_One
{
get { return new Range( 1, 1 ); }
}
public static bool Contains( float min, float max, float value )
{
return min <= value && value <= max;

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 meshGeometry = shape.CreateMeshGeometry();
var meshGeometry = shape.CreateFillMeshGeometry();
meshInstance3D.Mesh = meshGeometry.GenerateMesh();
@ -165,7 +165,7 @@ namespace Rokojori
var shape = Shape2.Boolean( circleAreaShape, islands, Geometry2D.PolyBooleanOperation.Difference );
var meshGeometry = shape.CreateMeshGeometry();
var meshGeometry = shape.CreateFillMeshGeometry();
meshInstance3D.Mesh = meshGeometry.GenerateMesh();

View File

@ -9,8 +9,8 @@ namespace Rokojori
{
public class MeshGeometry
{
public List<Vector3> vertices;
public List<int> indices;
public List<Vector3> vertices = new List<Vector3>();
public List<int> indices = new List<int>();
public List<Vector3> normals;
public List<Vector2> uvs;
@ -20,6 +20,23 @@ namespace Rokojori
public int numTriangles => indices.Count / 3;
public void ApplyTransform( Transform3D trsf )
{
for ( int i = 0; i < vertices.Count; i++ )
{
vertices[ i ] = trsf * vertices[ i ];
normals[ i ] = trsf.Basis.GetRotationQuaternion() * normals[ i ];
}
}
public void Offset( Vector3 offset )
{
for ( int i = 0; i < vertices.Count; i++ )
{
vertices[ i ] = vertices[ i ] + offset;
}
}
public void ForEachTriangle( Action<int,int,int,int> callback )
{
var index = 0;
@ -36,6 +53,62 @@ namespace Rokojori
}
}
static int GetUVIndex( int u, int v, int segments )
{
return u * ( segments + 1 ) + v;
}
public static MeshGeometry CreateFromUVFunction( Func<Vector2,Pose> uv, int uSegments, int vSegments )
{
var mg = new MeshGeometry();
for ( int i = 0; i <= uSegments; i++ )
{
var uI = (float)i / uSegments;
for ( int j = 0; j <= vSegments; j++ )
{
var vJ = (float)j / vSegments;
var uvIJ = new Vector2( uI, vJ );
var pose = uv( uvIJ );
mg.vertices.Add( pose.position );
mg.normals.Add( pose.up );
mg.uvs.Add( uvIJ );
}
}
for ( int i = 0; i < uSegments; i++ )
{
var u0 = i;
var u1 = i + 1;
for ( int j = 0; j < vSegments; j++ )
{
var v0 = j;
var v1 = j + 1;
var a = GetUVIndex( u0, v0, vSegments );
var b = GetUVIndex( u1, v0, vSegments );
var c = GetUVIndex( u1, v1, vSegments );
var d = GetUVIndex( u0, v1, vSegments );
mg.AddTriangle( a, b, d );
mg.AddTriangle( b, c, d );
// mg.AddTriangle( d, b, a );
// mg.AddTriangle( d, c, b );
}
}
return mg;
}
public MeshGeometry UniqueTriangles()
{
var mg = new MeshGeometry();
@ -128,17 +201,103 @@ namespace Rokojori
colors[ c ] = color;
}
public 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>();
this.indices = new List<int>();
Initialize( normals, uvs, colors, uvs2 );
}
public void Initialize( bool normals = true, bool uvs = true, bool colors = false, bool uvs2 = false )
{
this.normals = normals ? new List<Vector3>() : null;
this.uvs = uvs ? new List<Vector2>() : null;
this.uv2s = uvs2 ? new List<Vector2>() : null;
this.colors = colors ? new List<Color>() : null;
}
public MeshGeometry Clone()
{
var mg = new MeshGeometry();
mg.vertices = Lists.Clone( vertices );
mg.indices = Lists.Clone( indices );
mg.normals = Lists.Clone( normals );
mg.uvs = Lists.Clone( uvs );
mg.uv2s = Lists.Clone( uv2s );
mg.colors = Lists.Clone( colors );
return mg;
}
public void FlipNormalDirection()
{
for ( int i = 0; i < normals.Count; i++ )
{
normals[ i ] = -normals[ i ];
}
for ( int i =0 ; i < indices.Count; i += 3 )
{
var b = indices[ i ];
indices[ i ] = indices[ i + 2 ];
indices[ i + 2 ] = b;
}
}
public void Add( MeshGeometry mg )
{
var mappedIndices = new Dictionary<int,int>();
for ( int i = 0; i < mg.indices.Count; i++ )
{
var mgIndex = mg.indices[ i ];
if ( ! mappedIndices.ContainsKey( mgIndex ) )
{
var newIndex = vertices.Count;
if ( mgIndex >= mg.vertices.Count || mgIndex < 0 )
{
RJLog.Log( "Out of range:", i, ">>", mgIndex, mg.vertices.Count, mg.indices );
}
var v = mg.vertices[ mgIndex ];
vertices.Add( v );
if ( normals != null && mg.normals != null)
{
normals.Add( mg.normals[ mgIndex ] );
}
if ( uvs != null && mg.uvs != null)
{
uvs.Add( mg.uvs[ mgIndex ] );
}
if ( colors != null && mg.colors != null)
{
colors.Add( mg.colors[ mgIndex ] );
}
if ( uv2s != null && mg.uv2s != null)
{
uv2s.Add( mg.uv2s[ mgIndex ] );
}
mappedIndices[ mgIndex ] = newIndex;
}
indices.Add( mappedIndices[ mgIndex ] );
}
}
public void AddTriangle( int a, int b, int c )
{
Lists.Add( indices, a, b, c );
}
public void AddTriangle(
Vector3 va, Vector3 vb, Vector3 vc,
@ -187,6 +346,11 @@ namespace Rokojori
AddTriangle( vc, vd, va, nc, nd, na, uvc, uvd, uva );
}
public void AddQuad( int lt, int rt, int lb, int rb )
{
Lists.Add( indices, lb, rt, lt, rt, lb, rb );
}
public void AddQuad(
Vector3 va, Vector3 vb, Vector3 vc, Vector3 vd,

View File

@ -5,6 +5,12 @@ using System.Collections.Generic;
namespace Rokojori
{
public enum SplineAutoOrientationMode
{
Tangent,
Next_Neighbor
}
[Tool]
[GlobalClass, Icon("res://Scripts/Rokojori/Rokojori-Action-Library/Icons/Spline.svg") ]
public partial class Spline : Node3D
@ -19,6 +25,19 @@ namespace Rokojori
[Export]
public int editorResolution = 20;
[Export]
public bool autoOrienation = false;
[Export]
public SplineAutoOrientationMode autoOrientationMode = SplineAutoOrientationMode.Tangent;
[Export( PropertyHint.Range, "-1,1")]
public float autoOrientationTangentAdjustment = 0f;
[Export]
public Vector3 autoOrientationUp = Vector3.Up;
[Export]
public bool updateAlways = false;
@ -119,12 +138,12 @@ namespace Rokojori
renderedResolution = editorResolution;
var lastPoint = ToLocal( curve.SampleAt( 0 ) );
var lastPoint = ToLocal( curve.PositionAt( 0 ) );
for ( int i = 1; i < resolution; i++ )
{
var t = i / (float) (resolution - 1 );
var point = ToLocal( curve.SampleAt( t ) );
var point = ToLocal( curve.PositionAt( t ) );
linePoints.Add( lastPoint );
linePoints.Add( point );
@ -152,6 +171,52 @@ namespace Rokojori
int renderedResolution = -100;
void AutoOrientate()
{
var list = Nodes.GetDirectChildren<SplinePoint>( this );
if ( list.Count <= 1 )
{
return;
}
if ( SplineAutoOrientationMode.Next_Neighbor == autoOrientationMode )
{
for ( int i = 0; i < list.Count; i++ )
{
var sp = list[ i ];
if ( i == ( list.Count - 1 ) )
{
if ( closed )
{
var first = list[ 0 ];
sp.LookAt( first.GlobalPosition, autoOrientationUp );
}
continue;
}
var next = list[ i + 1 ];
sp.LookAt( next.GlobalPosition, autoOrientationUp );
}
}
else if ( SplineAutoOrientationMode.Tangent == autoOrientationMode )
{
for ( int i = 0; i < list.Count; i++ )
{
var sp = list[ i ];
var tangentNext = SplineCurveCreator.GetTangentDirectionSmoothed( 1, autoOrientationTangentAdjustment, list, i, false, closed );
sp.LookAt( sp.GlobalPosition + tangentNext, autoOrientationUp );
}
}
}
public override void _Process( double delta )
{
var changed = updateAlways;
@ -162,6 +227,11 @@ namespace Rokojori
changed = true;
}
if ( autoOrienation )
{
AutoOrientate();
}
Nodes.ForEachDirectChild<SplinePoint>( this,
sp =>
{

View File

@ -88,6 +88,10 @@ namespace Rokojori
#endif
[Export]
public float twist = 0;
[Export]
public SplinePointTangentMode tangentMode;
@ -110,5 +114,8 @@ namespace Rokojori
[Export( PropertyHint.Range, "0,1")]
public float symmetricTangentLength = 0f;
}
}

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 position = equalSpacedCurve.SampleAt( t );
var rawDirection = equalSpacedCurve.GradientAt( t, 1f / 100f );
var position = equalSpacedCurve.PositionAt( t );
var rawDirection = equalSpacedCurve.TangentAt( t, 1f / 100f );
var direction = rawDirection;
var length = direction.Length();

View File

@ -44,8 +44,8 @@ namespace Rokojori
{
var t = i / (float) ( numPoints - 1 );
var position = equalSpacedCurve.SampleAt( t );
var rawDirection = equalSpacedCurve.GradientAt( t, 1f / 100f );
var position = equalSpacedCurve.PositionAt( t );
var rawDirection = equalSpacedCurve.TangentAt( t, 1f / 100f );
var direction = rawDirection;
var length = direction.Length();

View File

@ -64,6 +64,23 @@ namespace Rokojori
return MathX.Fract( Mathf.Sin ( d ) * 43758.5453123f );
}
public static float PerlinXZ( Vector3 position )
{
return Perlin( Math2D.XZ( position ) );
}
public static Vector2 PerlinOffset( Vector2 position, float max, Vector2 scale, Vector2 offset, RandomEngine random )
{
var transformedPoint = position * scale + offset;
var value = Perlin( transformedPoint ) * max;
var angle = random.AngleRadians();
var movement = Math2D.Circle( angle, value );
return position + movement;
}
public static float Perlin( Vector2 position )
{
var index = position.Floor();

View File

@ -21,6 +21,11 @@ namespace Rokojori
return this.Next()*( b - a ) + a;
}
public float Sample( Curve curve )
{
return curve.Sample( Next() );
}
public float Polar()
{
return this.Next() * 2f - 1f;
@ -79,6 +84,11 @@ namespace Rokojori
return new Vector2( x, y );
}
public float AngleRadians()
{
return Range( 0, Mathf.Pi * 2f );
}
public Vector3 InCube()
{
return new Vector3( Polar(), Polar(), Polar() );
@ -144,7 +154,6 @@ namespace Rokojori
return IntegerInclusive( 0, max );
}
public int IntegerExclusive( int min, int max )
{
var nextValue = this.Next();
@ -311,6 +320,23 @@ namespace Rokojori
return _FindElementIndexWithWeights( weights, Next() * sumWeights );
}
public int IndexFromCurveWeights( Curve curve, int numIndices )
{
var weights = new List<float>();
var sumWeights = 0f;
for ( int i = 0; i < numIndices; i++ )
{
var t = (float)i / ( numIndices - 1 );
var w = curve.Sample( t );
weights.Add( w );
sumWeights += w;
}
return IndexFromUnnormalizedWeights( weights, sumWeights );
}
public int IndexFromNormalizedWeights( List<float> weights, float sumWeights = 0 )
{

View File

@ -62,6 +62,12 @@ namespace Rokojori
data.Sort( ( a, b ) => Compare( a, b ) );
}
public static void SortList( List<T> data, Func<T,float> getValue )
{
var vc = Create( getValue );
vc.Sort( data );
}
public void Sort( int index, int count, List<T> data )
{
ClearCache();

View File

@ -13,6 +13,7 @@ namespace Rokojori
public bool hasError => _hasError;
void AddMatcher( LexerMatcher matcher )
{
var list = _modes.ContainsKey( matcher.mode ) ? _modes[ matcher.mode ] : null;
@ -77,7 +78,7 @@ namespace Rokojori
var lexerEvent = new LexerEvent( "", 0, -2 );
var numTries = 0;
var maxTries = 1000;
var maxTries = 1000000;
while ( offset < source.Length && numTries < maxTries)
{

View File

@ -2,11 +2,135 @@ using System.Collections;
using System.Collections.Generic;
using System.Text;
using System;
using Godot;
namespace Rokojori
{
public class Lists
{
public static void Sort<T>( List<T> data, Func<T,float> getValue )
{
ValueSorter<T>.SortList( data, getValue );
}
public class IndexLerpResult
{
public int closestIndex = -1;
public int secondIndex = -1;
public float lerpAmount = 0.5f;
}
public static List<T> Clone<T>( List<T> data )
{
if ( data == null )
{
return null;
}
var cloned = new List<T>();
cloned.AddRange( data );
return cloned;
}
public static IndexLerpResult LerpIndex<T>( List<T> data, float value, Func<T,float> getValue, bool sort = false )
{
if ( sort )
{
Sort( data, getValue );
}
var result = new IndexLerpResult();
result.closestIndex = ClosestIndex<T>( data, value, getValue );
result.secondIndex = SecondClosestIndex<T>( data, result.closestIndex, value, getValue );
if ( result.closestIndex == result.secondIndex )
{
return result;
}
var closestValue = getValue( data[ result.closestIndex ] );
var secondValue = getValue( data[ result.secondIndex ] );
var min = closestValue;
var max = secondValue;
var flip = false;
if ( closestValue > secondValue )
{
flip = true;
min = secondValue;
max = closestValue;
}
result.lerpAmount = MathX.Normalize( value, min, max );
if ( flip )
{
result.lerpAmount = 1f - result.lerpAmount;
}
return result;
}
public static int SecondClosestIndex<T>( List<T> data, int closest, float compareValue, Func<T,float> getValue, bool sort = false )
{
if ( sort )
{
Sort( data, getValue );
}
if ( data.Count == 1 )
{
return 0;
}
if ( closest == 0 )
{
return closest + 1;
}
if ( closest == data.Count - 1 )
{
return data.Count - 2;
}
var before = data[ closest - 1 ];
var after = data[ closest + 1 ];
var bD = Mathf.Abs( getValue( before ) - compareValue );
var aD = Mathf.Abs( getValue( after ) - compareValue );
if ( bD < aD )
{
return closest - 1 ;
}
return closest + 1;
}
public static int ClosestIndex<T>( List<T> data, float compareValue, Func<T,float> getValue )
{
var index = -1;
var distance = float.MaxValue;
for ( int i = 0 ; i < data.Count; i++ )
{
var d = Mathf.Abs( getValue( data[ i ] ) - compareValue );
if ( d < distance )
{
index = i;
distance = d;
}
}
return index;
}
public static List<int> CollectIndices<T>( List<T> list, Func<T,bool> evaluator )
{
var output = new List<int>();

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 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.Generic;
using System.Text.RegularExpressions;
using Godot;
namespace Rokojori
{
public class SVGPathCommand
{
public int pathIndex = -1;
public string type;
public List<float> paramaters = new List<float>();
public static string SVGCoordinate( float x, float y )
{
return RegexUtility.NumberToString( x ) + " " + RegexUtility.NumberToString( y );
}
public static string SVGCoordinate( Vector2 v )
{
return SVGCoordinate( v.X, v.Y );
}
public override string ToString()
{
if ( type == "z" || type == "Z" )

View File

@ -4,73 +4,6 @@ using System.Collections.Generic;
namespace Rokojori
{
public enum SVGPathInstructionType
{
MoveTo,
LineTo,
QuadraticBezierTo,
CubicBezierTo,
ArcTo,
Close
}
public class SVGPathInstruction
{
public SVGPathInstructionType type = SVGPathInstructionType.MoveTo;
public Vector2 startPoint = Vector2.Zero;
public Vector2 controlPoint1 = Vector2.Zero;
public Vector2 controlPoint2 = Vector2.Zero;
public Vector2 endPoint = Vector2.Zero;
public Vector2 radius = Vector2.Zero;
public float angle = 0;
public bool largeArcFlag = false;
public bool sweepFlag = false;
public override string ToString()
{
return GetInfo();
}
public string GetInfo()
{
var infos = new List<object>();
infos.Add( type );
if ( SVGPathInstructionType.MoveTo == type )
{
infos.Add( endPoint );
}
else if ( SVGPathInstructionType.LineTo == type )
{
infos.Add( startPoint );
infos.Add( endPoint );
}
else if ( SVGPathInstructionType.QuadraticBezierTo == type )
{
infos.Add( startPoint );
infos.Add( controlPoint1 );
infos.Add( endPoint );
}
else if ( SVGPathInstructionType.CubicBezierTo == type )
{
infos.Add( startPoint );
infos.Add( controlPoint1 );
infos.Add( controlPoint2 );
infos.Add( endPoint );
}
else if ( SVGPathInstructionType.ArcTo == type )
{
infos.Add( startPoint );
infos.Add( radius );
infos.Add( angle );
infos.Add( largeArcFlag );
infos.Add( sweepFlag );
infos.Add( endPoint );
}
return RJLog.GetLogString( infos.ToArray() );
}
}
public class SVGPathExtractor
{
@ -114,7 +47,7 @@ namespace Rokojori
case "h": case "H":
{
ProcessHorizonatlTo( command );
ProcessHorizontalTo( command );
}
break;
@ -138,7 +71,7 @@ namespace Rokojori
case "z": case "Z":
{
ProcessClose();
ProcessClose( command );
}
break;
@ -146,11 +79,18 @@ namespace Rokojori
}
}
void ProcessClose()
void ProcessClose( SVGPathCommand command )
{
instruction.sourceCommand = command;
instruction.sourceCommandIndex = 0;
instruction.type = SVGPathInstructionType.Close;
instruction.startPoint = currentPoint;
instruction.startPoint = startPoint;
instruction.endPoint = startPoint;
currentPoint = startPoint;
DispatchInstruction();
}
void ProcessMoveTo( SVGPathCommand command )
@ -158,14 +98,22 @@ namespace Rokojori
var relative = command.type == "m";
var parameters = command.paramaters;
var sourceCommandIndex = 0;
instruction.sourceCommand = command;
for ( int i = 0; i < parameters.Count; i+= 2 )
{
ProcessToEndpoint( relative, SVGPathInstructionType.MoveTo, parameters[ i ], parameters[ i + 1 ] );
instruction.sourceCommandIndex = sourceCommandIndex ++;
if ( i == 0 )
{
startPoint = currentPoint;
ProcessMoveToStartPoint( relative, SVGPathInstructionType.MoveTo, parameters[ i ], parameters[ i + 1 ] );
}
else
{
ProcessToEndpoint( relative, SVGPathInstructionType.LineTo, parameters[ i ], parameters[ i + 1 ] );
}
}
}
@ -174,9 +122,15 @@ namespace Rokojori
var relative = command.type == "l";
var parameters = command.paramaters;
var sourceCommandIndex = 0;
instruction.sourceCommand = command;
for ( int i = 0; i < parameters.Count; i+= 2 )
{
instruction.sourceCommandIndex = sourceCommandIndex ++;
ProcessToEndpoint( relative, SVGPathInstructionType.LineTo, parameters[ i ], parameters[ i + 1 ] );
}
}
@ -185,22 +139,34 @@ namespace Rokojori
var relative = command.type == "v";
var parameters = command.paramaters;
var sourceCommandIndex = 0;
instruction.sourceCommand = command;
for ( int i = 0; i < parameters.Count; i++ )
{
instruction.sourceCommandIndex = sourceCommandIndex ++;
var x = relative ? 0 : currentPoint.X;
ProcessToEndpoint( relative, SVGPathInstructionType.LineTo, x, parameters[ i ] );
var y = parameters[ i ];
ProcessToEndpoint( relative, SVGPathInstructionType.LineTo, x, y );
}
}
void ProcessHorizonatlTo( SVGPathCommand command )
void ProcessHorizontalTo( SVGPathCommand command )
{
var relative = command.type == "v";
var relative = command.type == "h";
var parameters = command.paramaters;
var sourceCommandIndex = 0;
instruction.sourceCommand = command;
for ( int i = 0; i < parameters.Count; i++ )
{
instruction.sourceCommandIndex = sourceCommandIndex ++;
var x = parameters[ i ];
var y = relative ? 0 : currentPoint.Y;
ProcessToEndpoint( relative, SVGPathInstructionType.LineTo, parameters[ i ], y );
ProcessToEndpoint( relative, SVGPathInstructionType.LineTo, x, y );
}
}
@ -209,13 +175,19 @@ namespace Rokojori
var relative = command.type == "a";
var parameters = command.paramaters;
var sourceCommandIndex = 0;
instruction.sourceCommand = command;
for ( int i = 0; i < parameters.Count; i+= 7 )
{
instruction.sourceCommandIndex = sourceCommandIndex ++;
ProcessArc( relative,
parameters[ i ], parameters[ i + 1 ],
parameters[ i + 2 ], parameters[ i + 3 ],
parameters[ i + 4 ], parameters[ i + 5 ],
parameters[ i + 7 ]
parameters[ i + 6 ]
);
}
}
@ -253,8 +225,13 @@ namespace Rokojori
var relative = command.type == "c";
var parameters = command.paramaters;
var sourceCommandIndex = 0;
instruction.sourceCommand = command;
for ( int i = 0; i < parameters.Count; i+= 6 )
{
instruction.sourceCommandIndex = sourceCommandIndex ++;
ProcessCubic( relative,
parameters[ i ], parameters[ i + 1 ],
parameters[ i + 2 ], parameters[ i + 3 ],
@ -268,8 +245,13 @@ namespace Rokojori
var relative = command.type == "s";
var parameters = command.paramaters;
var sourceCommandIndex = 0;
instruction.sourceCommand = command;
for ( int i = 0; i < parameters.Count; i+= 4 )
{
instruction.sourceCommandIndex = sourceCommandIndex ++;
var cpDiff = currentPoint - lastControlPoint;
var cpNext = relative ? cpDiff : ( currentPoint + cpDiff );
@ -315,9 +297,13 @@ namespace Rokojori
var relative = command.type == "q";
var parameters = command.paramaters;
var sourceCommandIndex = 0;
instruction.sourceCommand = command;
for ( int i = 0; i < parameters.Count; i+= 4 )
{
instruction.sourceCommandIndex = sourceCommandIndex ++;
ProcessQuadratic( relative,
parameters[ i ], parameters[ i + 1 ],
parameters[ i + 2 ], parameters[ i + 3 ]
@ -330,8 +316,12 @@ namespace Rokojori
var relative = command.type == "t";
var parameters = command.paramaters;
var sourceCommandIndex = 0;
instruction.sourceCommand = command;
for ( int i = 0; i < parameters.Count; i+= 2 )
{
instruction.sourceCommandIndex = sourceCommandIndex ++;
var cpDiff = currentPoint - lastControlPoint;
var cpNext = relative ? cpDiff : ( currentPoint + cpDiff );
@ -368,6 +358,27 @@ namespace Rokojori
}
void ProcessMoveToStartPoint( bool relative, SVGPathInstructionType type, float x, float y )
{
instruction.type = type;
instruction.endPoint.X = x;
instruction.endPoint.Y = y;
if ( relative )
{
instruction.endPoint += currentPoint;
}
currentPoint = instruction.endPoint;
instruction.startPoint = currentPoint;
startPoint = currentPoint;
DispatchInstruction();
}
void ProcessToEndpoint( bool relative, SVGPathInstructionType type, float x, float y )
{
instruction.type = type;

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 commandIndex = SVGPathCommands.IndexOf( lowerCase );
if ( commandIndex == -1 )
@ -53,6 +55,8 @@ namespace Rokojori
var commandEnd = ProcessCommand( d, i );
// RJLog.Log( "Processing", d[ i ], ">>", lowerCase, " ends ", commandEnd );
if ( commandEnd == -1 || Messages.HasError( messages ) )
{
Messages.Error( messages, "Command couldn't be processed at '" + i + "'. Processed " + d[ i ] );
@ -65,12 +69,13 @@ namespace Rokojori
return commands;
}
int ProcessCommand( string d, int offset)
int ProcessCommand( string d, int offset )
{
var end = FindEnd( d, offset + 1 );
var pc = new SVGPathCommand();
pc.type = d[ offset ] + "";
pc.pathIndex = commands.Count;
ReadParameters( pc, d, offset + 1, end );
@ -95,12 +100,33 @@ namespace Rokojori
}
void ReadParameters( SVGPathCommand command, string d, int start, int end )
{
var parameters = d.Substring( start, ( end + 1 ) - start );
parameters = parameters.Trim();
// RJLog.Log( "Parsing parameters:", parameters );
var regex = new Regex( " |," );
var splitted = RegexUtility.Split( parameters, " |," );
splitted.ForEach(
( s )=>
{
var number = RegexUtility.ParseFloat( s.Trim() );
command.paramaters.Add( number );
// RJLog.Log( s, ">>", number );
}
);
}
void ReadParametersOld( SVGPathCommand command, string d, int start, int end )
{
var matcher = LexerMatcherLibrary.NumberMatcher;
var offset = start;
var isNegative = false;
while ( offset <= end )
while ( offset < end )
{
if ( d[ offset ] == ' ' || d[ offset ] == ',' )
{

View File

@ -6,6 +6,8 @@ namespace Rokojori
{
public class XMLAttributeName:XMLElementSelector
{
public static readonly XMLAttributeName id = XMLAttributeName.Create( "id" );
string _attributeName;
string _nameSpace;
@ -35,6 +37,12 @@ namespace Rokojori
public static XMLAttributeName Create( string type, string nameSpace = null )
{
if ( nameSpace == null && type.Contains( ":" ) )
{
nameSpace = type.Substring( 0, type.IndexOf( ":" ) );
type = type.Substring( type.IndexOf( ":" ) + 1 );
}
var elementNodeType = new XMLAttributeName();
elementNodeType._attributeName = type;
elementNodeType._nameSpace = nameSpace;

View File

@ -7,7 +7,11 @@
{
public class XMLReader
{
XMLLexer lexer = new XMLLexer();
XMLLexer _lexer = new XMLLexer();
public XMLLexer lexer => _lexer;
TextLinesMapper linesMapper = new TextLinesMapper();
List<LexerEvent> events;
XMLDocument document;
@ -18,9 +22,9 @@
public XMLDocument Read( string text )
{
this.text = text;
events = lexer.LexToList( text );
events = _lexer.LexToList( text );
if ( lexer.hasError )
if ( _lexer.hasError )
{
linesMapper.Map( text );
@ -45,7 +49,7 @@
CreateDocument();
RJLog.Log( document.Serialize() );
// RJLog.Log( document.Serialize() );
return document;
}
@ -58,9 +62,7 @@
node = document;
lexer.GrabMatches( events, text );
_lexer.GrabMatches( events, text );
events.ForEach(
( e )=>
@ -121,7 +123,7 @@
Add( element );
var parentName = node == document ? "document" : ((XMLElementNode)node).fullNodeName;
RJLog.Log( "Adding Element", element.fullNodeName, parentName );
// RJLog.Log( "Adding Element", element.fullNodeName, parentName );
node = element;
}
@ -141,7 +143,7 @@
var element = (XMLElementNode) node;
var infos = Lists.Join( Lists.Map( insideTagElements, i => i.match ), "" );
RJLog.Log( "Set Attributes", element.fullNodeName, insideTagElements.Count, infos );
// RJLog.Log( "Set Attributes", element.fullNodeName, insideTagElements.Count, infos );
var attributeNameIndices = Lists.CollectIndices( insideTagElements, le => XMLLexer.XMLAttributeName.Matches( le ) );

View File

@ -15,32 +15,108 @@ namespace Rokojori
[Export]
public bool loadFile;
[Export]
public MeshInstance3D meshInstance3D;
[Export]
public bool selectRandom = false;
[Export]
public string selectorAttribute;
[Export]
public string selectorValue;
[Export]
public float resolution;
[Export]
public bool autoReload = false;
[Export]
public int reloadFrames = 10;
int reloadCounter = 0;
public override void _Process( double delta )
{
if ( ! loadFile )
{
if ( autoReload )
{
reloadCounter ++;
if ( reloadCounter > reloadFrames )
{
reloadCounter = 0;
loadFile = true;
}
}
return;
}
Load();
}
string loadedPath = null;
XMLDocument doc;
void Load()
{
loadFile = false;
if ( path != loadedPath )
{
var text = FilesSync.LoadUTF8( path );
var reader = new XMLReader();
var doc = reader.Read( text );
doc = reader.Read( text );
}
var paths = doc.querySelectorAll( SVGElementName.path );
RJLog.Log( "paths:", paths.Count );
XMLElementNode randomPath = null;
if ( selectRandom )
{
randomPath = GodotRandom.Get().From( paths );
}
XMLWalker.instance.Iterate( doc,
n =>
{
if ( ! ( n is XMLElementNode ) )
{
return;
}
var e = (XMLElementNode) n;
}
);
// RJLog.Log( "Paths in SVG:", paths.Count );
var attributeSelector = XMLAttributeName.Create( selectorAttribute );
paths.ForEach(
( p ) =>
{
if ( XMLAttributeName.id.Selects( p ) )
{
// RJLog.Log( "Processing:", XMLAttributeName.id.Get( p ), attributeSelector.Get( p ), attributeSelector.Get( p ) == selectorValue );
}
var pathData = SVGAttributeName.d.Get( p );
if ( pathData == null )
@ -49,7 +125,25 @@ namespace Rokojori
return;
}
RJLog.Log( "pathData:", pathData );
if ( randomPath == p || ( randomPath == null && attributeSelector.Selects( p ) && attributeSelector.Get( p ) == selectorValue ) )
{
var shape2 = Shape2.FromSVGPath( pathData, resolution );
if ( meshInstance3D != null )
{
var meshGeometry = shape2.CreateFillMeshGeometry();
if ( meshGeometry != null )
{
meshInstance3D.Mesh = meshGeometry.GenerateMesh();
}
}
}
/*RJLog.Log( "pathData:", pathData );
var commands = SVGPathParser.Parse( pathData );
@ -75,6 +169,7 @@ namespace Rokojori
RJLog.Log( "Commands failed parsing:", Lists.Join( messages, "\n" ) );
}
*/
}