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