diff --git a/Icons/Scatterer.svg b/Icons/Scatterer.svg new file mode 100644 index 0000000..eedf849 --- /dev/null +++ b/Icons/Scatterer.svg @@ -0,0 +1,83 @@ + + + + + + + + + + + + + diff --git a/Icons/Scatterer.svg.import b/Icons/Scatterer.svg.import new file mode 100644 index 0000000..0e215e1 --- /dev/null +++ b/Icons/Scatterer.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://clm1p530y0sh0" +path="res://.godot/imported/Scatterer.svg-faa0f406c786220743edbcf86085b917.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Scripts/Rokojori/Rokojori-Action-Library/Icons/Scatterer.svg" +dest_files=["res://.godot/imported/Scatterer.svg-faa0f406c786220743edbcf86085b917.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/Icons/Spline.svg b/Icons/Spline.svg new file mode 100644 index 0000000..af82aa6 --- /dev/null +++ b/Icons/Spline.svg @@ -0,0 +1,52 @@ + + + + + + + + diff --git a/Icons/Spline.svg.import b/Icons/Spline.svg.import new file mode 100644 index 0000000..6456a8b --- /dev/null +++ b/Icons/Spline.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c4t5wy2e8guge" +path="res://.godot/imported/Spline.svg-24ecac2761c76806ec867265b5304b23.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Scripts/Rokojori/Rokojori-Action-Library/Icons/Spline.svg" +dest_files=["res://.godot/imported/Spline.svg-24ecac2761c76806ec867265b5304b23.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/Runtime/Colors/ColorBlendMode.cs b/Runtime/Colors/ColorBlendMode.cs new file mode 100644 index 0000000..0cde749 --- /dev/null +++ b/Runtime/Colors/ColorBlendMode.cs @@ -0,0 +1,56 @@ +using Godot; + +namespace Rokojori +{ + public enum ColorBlendModeType + { + Alpha, + Add, + Multiply, + Replace + } + + public class ColorBlendMode + { + public static Color Blend( ColorBlendModeType type, Color bottom, Color top ) + { + switch ( type ) + { + case ColorBlendModeType.Alpha: return Alpha( bottom, top ); + case ColorBlendModeType.Multiply: return Multiply( bottom, top ); + case ColorBlendModeType.Add: return Add( bottom, top ); + case ColorBlendModeType.Replace: return Replace( bottom, top ); + } + + return new Color( 1, 1, 1, 1 ); + } + + public static Color Alpha( Color bottom, Color top ) + { + var a = top.A + bottom.A * ( 1f - top.A ); + + var colorBottom = ColorX.RGB( bottom ); + var colorTop = ColorX.RGB( top ); + + var colorRGB = ( colorTop * top.A + colorBottom * bottom.A * ( 1f - top.A ) ) / a; + + return ColorX.Create( colorRGB, a ); + } + + public static Color Multiply( Color bottom, Color top ) + { + return bottom * top; + } + + public static Color Add( Color bottom, Color top ) + { + return bottom + top; + } + + public static Color Replace( Color bottom, Color top ) + { + return top; + } + + } +} \ No newline at end of file diff --git a/Runtime/Colors/ColorX.cs b/Runtime/Colors/ColorX.cs new file mode 100644 index 0000000..643e4a2 --- /dev/null +++ b/Runtime/Colors/ColorX.cs @@ -0,0 +1,29 @@ +using Godot; + +namespace Rokojori +{ + public class ColorX + { + public static Color Lerp( Color a, Color b, float amount ) + { + return a.Lerp( b, amount ); + } + + public static Vector3 RGB( Color c ) + { + return new Vector3( c.R, c.G, c.B ); + } + + public static Vector2 RA( Color c ) + { + return new Vector2( c.R, c.A); + } + + public static Color Create( Vector3 rgb, float a ) + { + return new Color( rgb.X, rgb.Y, rgb.Z, a ); + } + + + } +} \ No newline at end of file diff --git a/Runtime/Godot/Nodes.cs b/Runtime/Godot/Nodes.cs index ac8db9e..9e1393c 100644 --- a/Runtime/Godot/Nodes.cs +++ b/Runtime/Godot/Nodes.cs @@ -75,6 +75,8 @@ namespace Rokojori ForEach( root, callback ); } + + public static List AllInScene( Func filter = null) where T:class { var list = new List(); @@ -144,6 +146,35 @@ namespace Rokojori return GetDirectChild( parent ); } + public static void RemoveAndDelete( Node node ) + { + var parent = node.GetParent(); + + if ( parent != null ) + { + parent.RemoveChild( node ); + } + + node.QueueFree(); + } + + public static void RemoveAndDeleteChildren( Node parent, bool includeInternal = false ) + { + if ( parent == null ) + { + return; + } + + var numChildren = parent.GetChildCount( includeInternal ); + + for ( int i = numChildren - 1; i >= 0; i-- ) + { + var node = parent.GetChild( i, includeInternal ); + parent.RemoveChild( node ); + node.QueueFree(); + } + } + public static T GetDirectChild( Node parent ) where T:Node { if ( parent == null ) @@ -219,6 +250,40 @@ namespace Rokojori } } + public static List MapDirectChildren( Node parent, System.Func mapper ) where T:Node + { + var list = new List(); + + ForEachDirectChild( parent, c => list.Add( mapper( c ) ) ); + + return list; + } + + public static int TypeIndex( Node parent, T child ) where T:Node + { + var counter = 0; + + var numChildren = parent.GetChildCount(); + + for ( int i = 0; i < numChildren; i++ ) + { + var node = parent.GetChild( i ); + + if ( node is T ) + { + if ( node == child ) + { + return counter; + } + + counter++; + } + + } + + return -1; + } + static NodesWalker nodesWalker = new NodesWalker(); public static T GetAnyChild( Node parent ) where T:Node diff --git a/Runtime/Math/Geometry/Box2.cs b/Runtime/Math/Geometry/Box2.cs new file mode 100644 index 0000000..6f459ee --- /dev/null +++ b/Runtime/Math/Geometry/Box2.cs @@ -0,0 +1,88 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; + +namespace Rokojori +{ + public class Box2 + { + public Vector2 min = Vector2.Zero; + public Vector2 max = Vector2.Zero; + + public Box2( Vector2 min, Vector2 max ) + { + this.min = min; + this.max = max; + } + + public Box2() + {} + + public bool ContainsPoint( Vector2 point ) + { + return ContainsPoint( min, max, point ); + } + + public Box2 Clone() + { + return new Box2( min, max ); + } + + public void UnionWith( Box2 other ) + { + min = min.Min( other.min ); + max = max.Max( other.max ); + } + + public void Grow( Vector2 abs ) + { + min -= abs; + max += abs; + } + + public void GrowByPoint( Vector2 p ) + { + min = min.Min( p ); + max = max.Max( p ); + } + + public Vector2 size => max - min; + + public void GrowRelativeToSize( float amount ) + { + Grow( size * amount ); + } + + public static bool ContainsPoint( Vector2 min, Vector2 max, Vector2 point ) + { + if ( ! Range.Contains( min.X, max.X, point.X ) ) + { + return false; + } + + if ( ! Range.Contains( min.Y, max.Y, point.Y ) ) + { + return false; + } + + return true; + } + + + public void EnsureCorrectness() + { + if ( max.X < min.X ) + { + var b = max.X; max.X = min.X; min.X = b; + } + + if ( max.Y < min.Y ) + { + var b = max.Y; max.Y = min.Y; min.Y = b; + } + + } + + } + +} \ No newline at end of file diff --git a/Runtime/Math/Geometry/Box3.cs b/Runtime/Math/Geometry/Box3.cs index eb24af5..7850900 100644 --- a/Runtime/Math/Geometry/Box3.cs +++ b/Runtime/Math/Geometry/Box3.cs @@ -26,6 +26,16 @@ namespace Rokojori return b; } + + public Box2 AsXZBox2() + { + var b = new Box2(); + b.min = Math2D.XZ( min ); + b.max = Math2D.XZ( max ); + + return b; + } + public Vector3 Constrain( Vector3 point ) { point = min.Max( point ); diff --git a/Runtime/Math/Geometry/Capsule2.cs b/Runtime/Math/Geometry/Capsule2.cs new file mode 100644 index 0000000..e3969f7 --- /dev/null +++ b/Runtime/Math/Geometry/Capsule2.cs @@ -0,0 +1,44 @@ +using Godot; +using System.Collections; +using System.Collections.Generic; + +namespace Rokojori +{ + public class Capsule2 + { + public Vector2 start = Vector2.Zero; + public Vector2 end = Vector2.Zero; + public float radius = 1; + + public Capsule2( Vector2 start, Vector2 end, float radius ) + { + this.start = start; + this.end = end; + this.radius = radius; + } + + public Capsule2 Copy() + { + return new Capsule2( start, end, radius ); + } + + public Vector2 center + { + get { return start.Lerp( end, 0.5f ); } + } + + public Line2 centerLine + { + get { return new Line2( start, end ); } + } + + public float DistanceToPoint( Vector2 p ) + { + var lineDistance = centerLine.DistanceToPoint( p ); + return Mathf.Max( 0, lineDistance - radius ); + } + + + } +} + diff --git a/Runtime/Math/Geometry/Circle.cs b/Runtime/Math/Geometry/Circle.cs new file mode 100644 index 0000000..e088cf0 --- /dev/null +++ b/Runtime/Math/Geometry/Circle.cs @@ -0,0 +1,14 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; + +namespace Rokojori +{ + public class Circle + { + public Vector2 center = Vector2.Zero; + public float radius = 1; + + + } +} \ No newline at end of file diff --git a/Runtime/Math/Geometry/Convex2.cs b/Runtime/Math/Geometry/Convex2.cs new file mode 100644 index 0000000..4d2a2fa --- /dev/null +++ b/Runtime/Math/Geometry/Convex2.cs @@ -0,0 +1,146 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; + +namespace Rokojori +{ + public class Convex2Group + { + public List items; + public Box2 boundingBox; + + public static Convex2Group From( List items ) + { + var group = new Convex2Group(); + + group.items = items; + + group.boundingBox = items[ 0 ].boundingBox.Clone(); + + items.ForEach( it => group.boundingBox.UnionWith( it.boundingBox ) ); + + // RJLog.Log( group.boundingBox ); + + return group; + + } + + public static Convex2Group FromPath( Path2 path ) + { + return From( Convex2.PathToConvexList( path ) ); + } + + public bool ContainsPoint( Vector2 point ) + { + if ( ! boundingBox.ContainsPoint( point ) ) + { + return false; + } + + for ( int i = 0; i < items.Count; i++ ) + { + if ( items[ i ].ContainsPoint( point ) ) + { + return true; + } + } + + return false; + + } + } + + public class Convex2 + { + public List points; + public List normals; + public List directions; + + public Box2 boundingBox; + public bool clockwise; + + public bool ContainsPoint( Vector2 point, bool checkBounds = true ) + { + if ( checkBounds && ! boundingBox.ContainsPoint( point ) ) + { + return false; + } + + var positive = 0; + var negative = 0; + + for ( int i = 0; i < points.Count; i++ ) + { + var p = points[ i ]; + var d = directions[ i ].Cross( point - p ); + + if ( d > 0 ) + { + positive++; + } + + if ( d < 0 ) + { + negative++; + } + + if ( positive > 0 && negative > 0 ) + { + return false; + } + } + + + return true; + } + + public static List PathToConvexList( Path2 path ) + { + var convexPolys = Geometry2D.DecomposePolygonInConvex( path.points.ToArray() ); + + var list = new List(); + + for ( int i = 0; i < convexPolys.Count; i++ ) + { + var clockwise = Geometry2D.IsPolygonClockwise( convexPolys[ i ] ); + var cnv = FromConvexHull( convexPolys[ i ], clockwise ); + + list.Add( cnv ); + } + + // RJLog.Log( "PathToConvexList:", list.Count ); + return list; + } + + public static Convex2 FromConvexHull( Vector2[] points, bool isClockwise = true ) + { + return FromConvexHull( new List( points ), isClockwise ); + } + + public static Convex2 FromConvexHull( List points, bool isClockwise = true ) + { + var cnv = new Convex2(); + cnv.points = points; + cnv.normals = new List(); + cnv.directions = new List(); + cnv.clockwise = isClockwise; + + cnv.boundingBox = new Box2( points[ 0 ], points[ 0 ] ); + + for ( int i = 0; i < points.Count; i++ ) + { + var next = ( i + 1 ) % points.Count; + + cnv.boundingBox.GrowByPoint( points[ i ] ); + + var direction = points[ next ] - points[ i ]; + var normal = Math2D.Rotate90Degrees( direction.Normalized(), ! isClockwise ); + + cnv.normals.Add( normal ); + cnv.directions.Add( direction ); + } + + return cnv; + } + } +} \ No newline at end of file diff --git a/Runtime/Math/Geometry/Curve3.cs b/Runtime/Math/Geometry/Curve3.cs index 6b2cbc6..6b5a79e 100644 --- a/Runtime/Math/Geometry/Curve3.cs +++ b/Runtime/Math/Geometry/Curve3.cs @@ -6,7 +6,7 @@ namespace Rokojori { public abstract class Curve3 { - float _gradientSamplingRange = Mathf.Pow( 10f, -13f ); + float _gradientSamplingRange = Mathf.Pow( 10f, -8f ); public abstract Vector3 SampleAt( float t ); public virtual void SampleMultiple( int numSamples, List output ) @@ -28,9 +28,13 @@ namespace Rokojori public virtual Vector3 GradientAt( float t ) { + return GradientAt( t, _gradientSamplingRange ); + } - return SampleAt( t + _gradientSamplingRange ) - SampleAt( t - _gradientSamplingRange ); - } + public virtual Vector3 GradientAt( float t, float samplingRange ) + { + return SampleAt( t + samplingRange ) - SampleAt( t - samplingRange ); + } public virtual float ComputeLength( int numSamples ) { @@ -71,7 +75,89 @@ namespace Rokojori return Mathf.Max( minSamples, numSamples ); } + public Vector3 GetClosestPositionTo( Vector3 position, int numSegments = 20, int depth = 0 ) + { + var parameter = GetClosestParameterTo( position, numSegments, depth ); + + return SampleAt( parameter ); + } + + public float GetClosestParameterTo( Vector3 point, int numSegments = 20, int depth = 3 ) + { + return GetClosestParameterTo( 0, 1, point, numSegments, depth, 0 ); + } + + public float GetDistanceTo( Vector3 point, int numSegments = 20, int depth = 3 ) + { + var p = GetClosestPositionTo( point, numSegments, depth ); + return ( p - point ).Length(); + } + + protected float GetClosestParameterTo( float tStart, float tEnd, Vector3 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 Line3(); + 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; + + + } + } } \ No newline at end of file diff --git a/Runtime/Math/Geometry/Distance3.cs b/Runtime/Math/Geometry/Distance3.cs index 53e6590..01241bc 100644 --- a/Runtime/Math/Geometry/Distance3.cs +++ b/Runtime/Math/Geometry/Distance3.cs @@ -9,9 +9,9 @@ namespace Rokojori /* - P = Point, + P = Point S = Sphere - L = Line, + L = Line C = Capsule diff --git a/Runtime/Math/Geometry/LerpCurve3.cs b/Runtime/Math/Geometry/LerpCurve3.cs index 1238f9d..a1a6715 100644 --- a/Runtime/Math/Geometry/LerpCurve3.cs +++ b/Runtime/Math/Geometry/LerpCurve3.cs @@ -30,11 +30,11 @@ namespace Rokojori public class LerpCurve3:Curve3 { - List list; + List points; public LerpCurve3( List list ) { - this.list = list; + this.points = list; if ( list.Count == 0 ) { @@ -45,12 +45,12 @@ namespace Rokojori public override float ComputeLength( int numSamples ) { var length = 0f; - var end = list.Count - 1; + var end = points.Count - 1; for ( int i = 0; i < end; i++ ) { - var p0 = list[ i ]; - var p1 = list[ i + 1 ]; + var p0 = points[ i ]; + var p1 = points[ i + 1 ]; length += p0.DistanceTo( p1 ); } @@ -58,19 +58,104 @@ namespace Rokojori return length; } + public int numSegments => points.Count -1; + public float GetSegmentDistance( int i ) + { + return ( points[ i + 1 ] - points[ i ] ).Length(); + } + + public float GetMinimumSegmentDistance() + { + var min = GetSegmentDistance( 0 ); + + for ( int i = 1; i < numSegments; i++ ) + { + min = Mathf.Min( min, GetSegmentDistance( i ) ); + } + + return min; + } + + public LerpCurve3 CreateMinimumSpaced( float minSpace ) + { + var lastPoint = points.Count - 1; + + var spacedPoints = new List(); + + var line = new Line3(); + + for ( int i = 0; i < lastPoint; i++ ) + { + line.start = points[ i ]; + line.end = points[ i + 1 ]; + + var direction = line.direction; + var length = direction.Length(); + + var numPoints = Mathf.CeilToInt( length / minSpace ); + + for ( int j = 0; j < numPoints - 1; j++ ) + { + var t = j / (float)( numPoints - 1 ); + spacedPoints.Add( line.SampleAt( t ) ); + } + } + + spacedPoints.Add( points[ points.Count - 1 ] ); + + return new LerpCurve3( spacedPoints ); + + } + + public static LerpCurve3 SampledFrom( Curve3 curve, int numSamples ) + { + var points = new List(); + + for ( int i = 0; i < numSamples; i++ ) + { + var t = i / ( (float) numSamples - 1 ); + + var p = curve.SampleAt( t ); + + points.Add( p ); + } + + return new LerpCurve3( points ); + } + + public static LerpCurve3 SampledWithResolution( Curve3 curve, float resolution, int lengthSampleResolution = 20 ) + { + var length = curve.ComputeLength( lengthSampleResolution ); + + var numSamples = Mathf.CeilToInt( length * resolution ); + + return SampledFrom( curve, numSamples ); + } + + public static LerpCurve3 SampledEqually( Curve3 curve, float minDistance, float smallestSegmentDivision = 2, int lengthSampleResolution = 20 ) + { + var lerpCurve = SampledWithResolution( curve, minDistance, lengthSampleResolution ); + + var smallestSegment = lerpCurve.GetMinimumSegmentDistance() / smallestSegmentDivision; + + var minSpace = Mathf.Min( smallestSegment, minDistance ); + + return lerpCurve.CreateMinimumSpaced( minSpace ); + } + public float UndistortParameter( float parameter ) { var completeLength = ComputeLength( 0 ); - var end = list.Count - 1; - var normalizer = 1f / ( list.Count - 1 ); + var end = points.Count - 1; + var normalizer = 1f / ( points.Count - 1 ); var length = 0f; for ( int i = 0; i < end; i++ ) { - var p0 = list[ i ]; - var p1 = list[ i + 1 ]; + var p0 = points[ i ]; + var p1 = points[ i + 1 ]; var t0 = i * normalizer; var t1 = ( i + 1 ) * normalizer; var distance = p0.DistanceTo( p1 ); @@ -93,7 +178,7 @@ namespace Rokojori { var output = new List(); - var num = list.Count - 1; + var num = points.Count - 1; if ( close ) { @@ -102,8 +187,8 @@ namespace Rokojori for ( var i = 0; i < num; i++ ) { - var start = list[ i ]; - var end = list[ i == list.Count ? 0 : i + 1 ]; + var start = points[ i ]; + var end = points[ i == points.Count ? 0 : i + 1 ]; var dir = ( end - start ); var length = dir.Length(); @@ -123,8 +208,8 @@ namespace Rokojori if ( ! close ) { - var start = list[ list.Count - 2]; - var end = list[ list.Count - 1]; + var start = points[ points.Count - 2]; + var end = points[ points.Count - 1]; var dir = ( end - start ); @@ -151,33 +236,33 @@ namespace Rokojori { if ( t < 0 ) { - return list[ 0 ]; + return points[ 0 ]; } if ( t >= 1 ) { - return list[ list.Count - 1 ]; + return points[ points.Count - 1 ]; } - if ( list.Count == 1 ) + if ( points.Count == 1 ) { - return list[ 0 ]; + return points[ 0 ]; } - var listIndex = t * ( list.Count - 1 ); + var listIndex = t * ( points.Count - 1 ); var flooredIndex = (int) Mathf.Floor( listIndex ); var lerpAmount = listIndex - flooredIndex; - if ( flooredIndex < 0 || ( flooredIndex >= list.Count - 1 ) ) + if ( flooredIndex < 0 || ( flooredIndex >= points.Count - 1 ) ) { - RJLog.Log( "Out of range index:",flooredIndex, " t:", t, " listIndex", listIndex, "Count:", list.Count ); + RJLog.Log( "Out of range index:",flooredIndex, " t:", t, " listIndex", listIndex, "Count:", points.Count ); } - var lower = list[ flooredIndex ]; - var upper = list[ flooredIndex + 1 ]; + var lower = points[ flooredIndex ]; + var upper = points[ flooredIndex + 1 ]; return Math3D.LerpUnclamped( lower, upper, lerpAmount ); @@ -218,14 +303,14 @@ namespace Rokojori line = new Line3( Vector3.Zero, Vector3.Zero ); } - var end = list.Count - 1; + var end = points.Count - 1; - var parameterNormalizer = 1f / ( list.Count - 1f ); + var parameterNormalizer = 1f / ( points.Count - 1f ); for ( int i = 0; i < end; i++ ) { - line.start = list[ i ]; - line.end = list[ i + 1 ]; + line.start = points[ i ]; + line.end = points[ i + 1 ]; var currentParameter = line.ClostestParameterToPoint( point ); var currentClosestPoint = line.GetPointAtParameter( currentParameter ); diff --git a/Runtime/Math/Geometry/Line2.cs b/Runtime/Math/Geometry/Line2.cs new file mode 100644 index 0000000..d07d8b0 --- /dev/null +++ b/Runtime/Math/Geometry/Line2.cs @@ -0,0 +1,155 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; + +namespace Rokojori +{ + public class Line2 + { + public Vector2 start = Vector2.Zero; + public Vector2 end = Vector2.Zero; + + public Line2( Vector2 start, Vector2 end ) + { + this.start = start; + this.end = end; + } + + public Line2(){} + + public float A{ get { return end.Y - start.Y; } } + public float B{ get { return start.X - end.X; } } + public float C{ get { return A * start.X + B * start.Y; } } + public Vector2 direction { get { return end - start; } } + public Vector2 reverseDirection { get { return start - end; } } + public float angle + { + get { + + var lineDirection = direction; + + return Mathf.Atan2( lineDirection.Y, lineDirection.X ); + } + } + + public float reverseAngle + { + get { + + var lineDirection = reverseDirection; + + return Mathf.Atan2( lineDirection.Y, lineDirection.X ); + } + } + + + public Vector2? InfiniteIntersectionOf( Line2 line2 ) + { + var A1 = A; + var B1 = B; + var A2 = line2.A; + var B2 = line2.B; + + var determinant = A1 * B2 - A2 * B1; + + if ( Mathf.Abs( determinant ) < Mathf.Epsilon ) + { + return null; + } + + var inverseDeterminant = 1f / determinant; + + var C1 = A1 * start.X + B1 * start.Y; + var C2 = A2 * line2.start.X + B2 * line2.start.Y; + + var x = ( B2 * C1 - B1 * C2 ) * inverseDeterminant; + var y = ( A1 * C2 - A2 * C1 ) * inverseDeterminant; + + return new Vector2( x, y ); + } + + public Vector2? IntersectionOf( Line2 other ) + { + var possibleIntersection = InfiniteIntersectionOf( other ); + + if ( possibleIntersection == null ) + { + return null; + } + + var point = (Vector2) possibleIntersection; + + if ( ! IsValueInRange( point.X, start.X, end.X ) ) + { + return null; + } + + if ( ! IsValueInRange( point.Y, start.Y, end.Y ) ) + { + return null; + } + + if ( ! IsValueInRange( point.X, other.start.X, other.end.X ) ) + { + return null; + } + + if ( ! IsValueInRange( point.Y, other.start.Y, other.end.Y ) ) + { + return null; + } + + return point; + } + + public bool IntersectsWith( Line2 other ) + { + return IntersectionOf( other ) != null; + } + + bool IsValueInRange( float value, float start, float end ) + { + var min = Mathf.Min( start, end ); + var max = Mathf.Max( start, end ); + + return min <= value && value <= max; + } + + public Vector2 GetPointAtParameter( float t ) + { + return start + direction * t; + } + + public Vector2 ClosestPointToPoint( Vector2 point ) + { + var parameter = MathX.Clamp01( ClostestParameterToPoint( point ) ); + return GetPointAtParameter( parameter ); + } + + public float ClostestParameterToPoint( Vector2 point ) + { + var startP = point - start; + var startEnd = end - start; + var startEnd2 = Math2D.Dot( startEnd, startEnd ); + var startEnd_startP = Math2D.Dot( startEnd, startP ); + + if ( startEnd2 == 0 ) + { + return 0; + } + + var t = startEnd_startP / startEnd2; + + return t; + + } + + public float DistanceToPoint( Vector2 point ) + { + return ( point - ClosestPointToPoint( point ) ).Length(); + } + + + + } +} \ No newline at end of file diff --git a/Runtime/Math/Geometry/Line3.cs b/Runtime/Math/Geometry/Line3.cs index b6f4296..b6ec42f 100644 --- a/Runtime/Math/Geometry/Line3.cs +++ b/Runtime/Math/Geometry/Line3.cs @@ -15,6 +15,19 @@ namespace Rokojori this.end = end; } + public Line3(){} + + public static Line3 Create( Vector3 start, Vector3 end ) + { + return new Line3( start, end ); + } + + public void Set( Vector3 start, Vector3 end ) + { + this.start = start; + this.end = end; + } + public override Vector3 SampleAt( float t ) { return start.Lerp( end, t ); @@ -80,6 +93,14 @@ namespace Rokojori return GetPointAtParameter( parameter ); } + public static Vector3 ClosestPointOf( Vector3 p, Vector3 lineStart, Vector3 lineEnd ) + { + var line = new Line3(); + line.Set( lineStart, lineEnd ); + + return line.ClosestPointToPoint( p ); + } + public float ClostestParameterToPoint( Vector3 point ) { var startP = point - start; @@ -119,6 +140,51 @@ namespace Rokojori return new Vector3[]{ x, y }; } + public Vector3 ClosestPointTo( Line3 other ) + { + var parameters = ClosestParametersToLine( other ); + return SampleAt( parameters.X ); + } + + public Vector3 ClosestPointTo( params Vector3[] points ) + { + var d = float.MaxValue; + var id = -1; + + for ( int i = 0; i < points.Length; i++ ) + { + var pd = DistanceToPoint( points[ i ] ); + + if ( pd < d ) + { + id = i; + d = pd; + } + } + + return points[ id ]; + } + + public Vector3 ClosestPointTo( List points ) + { + var d = float.MaxValue; + var id = -1; + + for ( int i = 0; i < points.Count; i++ ) + { + var pd = DistanceToPoint( points[ i ] ); + + if ( pd < d ) + { + id = i; + d = pd; + } + } + + return points[ id ]; + } + + public Vector2 ClosestParametersToLine( Line3 s ) { float epsilon = 0.00000001f; diff --git a/Runtime/Math/Geometry/Path2.cs b/Runtime/Math/Geometry/Path2.cs new file mode 100644 index 0000000..d07c37e --- /dev/null +++ b/Runtime/Math/Geometry/Path2.cs @@ -0,0 +1,380 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; + +namespace Rokojori +{ + public enum PointInPathResult + { + INSIDE, + OUTSIDE, + ERROR + } + + + + public class Path2 + { + float POINT_TO_CLOSE_DISTANCE = 0.0001f; + float ANGLE_TO_SIMILAR_TRESHOLD = 0.00001f / 180f; + int NUM_POINT_IN_PATH_TEST_RETRIES = 5; + + List _points = new List(); + + public List points => _points; + + + public Path2( List points ) + { + this._points = points; + } + + public int numPoints => _points.Count; + public bool empty => _points.Count == 0; + + public bool cacheBounds = true; + + Vector2? min; + Vector2? max; + + public Vector2 leftTop + { + get + { + if ( cacheBounds && min != null ) + { + return (Vector2) min; + } + + var _min = _points[ 0 ]; + + for ( int i = 1; i < _points.Count; i++ ) + { + var point = _points[ i ]; + _min.X = Mathf.Min( point.X, _min.X ); + _min.Y = Mathf.Min( point.Y, _min.Y ); + } + + min = _min; + + return (Vector2) min; + + } + } + + public Vector2 rightBottom + { + get + { + + if ( cacheBounds && max != null ) + { + return (Vector2) max; + } + + var _max = _points[ 0 ]; + + for ( int i = 1; i < _points.Count; i++ ) + { + var point = _points[ i ]; + _max.X = Mathf.Max( point.X, _max.X ); + _max.Y = Mathf.Max( point.Y, _max.Y ); + } + + max = _max; + + return (Vector2) max; + + } + } + + public void AddToNavigationPolygon( NavigationPolygon polygon ) + { + var convexPolygons = Geometry2D.DecomposePolygonInConvex( points.ToArray() ); + + for ( int i = 0; i < convexPolygons.Count; i++ ) + { + var vertices = convexPolygons[ i ]; + polygon.SetVertices( vertices ); + polygon.AddPolygon( CreateIndices( vertices.Length ) ); + } + + } + + public static int[] CreateIndices( int amount ) + { + var indices = new int[ amount ]; + + for ( int i = 0; i < amount; i++ ) + { + indices[ i ] = i; + } + + return indices; + } + + public static Path2 AsXZFrom( Curve3D curve3D, bool closed, int resolution = 20 ) + { + var points = new List(); + + var numPoints = resolution; + + var normalizer = 1f / resolution; + + for ( int i = 0; i < numPoints; i++ ) + { + points.Add( Math2D.XZ( curve3D.Samplef( i * normalizer ) ) ); + } + + if ( closed ) + { + points.Add( points[ 0 ] ); + } + + return new Path2( points ); + } + + public static Path2 AsXZFrom( Curve3 curve3, bool closed, int resolution = 20 ) + { + var points = new List(); + + var numPoints = resolution; + + var normalizer = 1f / resolution; + + for ( int i = 0; i < numPoints; i++ ) + { + points.Add( Math2D.XZ( curve3.SampleAt( i * normalizer ) ) ); + } + + if ( closed ) + { + points.Add( points[ 0 ] ); + } + + return new Path2( points ); + } + + public bool PointInPath( Vector2 p, bool fast = true, bool checkBoundingBox = true ) + { + if ( fast ) + { + return PointInPathFast( p, checkBoundingBox ); + } + else + { + return PointInPathReliable( p, checkBoundingBox ); + } + } + + public bool PointInPathFast( Vector2 point, bool checkBoundingBox = true ) + { + var min = this.leftTop; + var max = this.rightBottom; + + if ( checkBoundingBox ) + { + if ( ! Box2.ContainsPoint( min, max, point ) ) + { + return false; + } + } + + return CheckPointInPathFast( point, max + new Vector2( 122.133544f, 129.45423f ) ); + } + + public bool PointInPathReliable( Vector2 point, bool checkBoundingBox = true ) + { + var min = this.leftTop; + var max = this.rightBottom; + + if ( checkBoundingBox ) + { + if ( ! Box2.ContainsPoint( min, max, point ) ) + { + return false; + } + + } + + var endPoint = max + new Vector2( 0.1234235f, 0.4211322f ); + var result = CheckPointInPath( point, endPoint ); + + if ( PointInPathResult.ERROR != result ) + { + return PointInPathResult.INSIDE == result; + } + + var rightTop = new Vector2( max.X, min.Y ); + endPoint = rightTop + new Vector2( 0.03234235f, -0.90211322f ); + result = CheckPointInPath( point, endPoint ); + + if ( PointInPathResult.ERROR != result ) + { + return PointInPathResult.INSIDE == result; + } + + var centerTop = new Vector2( ( min.X + max.X ) * 0.5f , min.Y ); + endPoint = centerTop + new Vector2( 0.013234235f, -0.90211322f ); + result = CheckPointInPath( point, endPoint ); + + if ( PointInPathResult.ERROR != result ) + { + return PointInPathResult.INSIDE == result; + } + + var rightMiddle = new Vector2( max.X , ( min.Y + max.Y ) * 0.5f); + endPoint = rightMiddle + new Vector2( 0.013234235f, 0.00211322f ); + result = CheckPointInPath( point, endPoint ); + + if ( PointInPathResult.ERROR != result ) + { + return PointInPathResult.INSIDE == result; + } + + endPoint = leftTop + new Vector2( -0.01053535f, -0.41005465f ); + result = CheckPointInPath( point, endPoint ); + + if ( PointInPathResult.ERROR != result ) + { + return PointInPathResult.INSIDE == result; + } + + for ( int i = 0; i < NUM_POINT_IN_PATH_TEST_RETRIES; i++ ) + { + var randomX = GodotRandom.Get().Next(); + var randomY = GodotRandom.Get().Next(); + + var randomPoint = max + new Vector2( randomX, randomY ); + result = CheckPointInPath( point, endPoint ); + + if ( PointInPathResult.ERROR != result ) + { + return PointInPathResult.INSIDE == result; + } + } + + return false; + } + + public bool CheckPointInPathFast( Vector2 point, Vector2 endPoint ) + { + var pointLine = new Line2( point, endPoint ); + + var iterationLine = new Line2(); + + var intersections = 0; + + for ( int i = 0; i < _points.Count; i++ ) + { + var start = _points[ i ]; + + var nextIndex = ( i == _points.Count - 1 ) ? 0 : ( i + 1 ); + var end = _points[ nextIndex ]; + + + iterationLine.start = start; + iterationLine.end = end; + + var intersects = pointLine.IntersectsWith( iterationLine ); + + if ( intersects ) + { + intersections ++; + } + + } + + + return intersections % 2 != 0; + } + + PointInPathResult CheckPointInPath( Vector2 point, Vector2 endPoint ) + { + var pointLine = new Line2( point, endPoint ); + var pointAngle = pointLine.angle; + var pointAngle2 = pointLine.reverseAngle; + + var iterationLine = new Line2(); + + var intersections = 0; + + for ( int i = 0; i < _points.Count; i++ ) + { + var start = _points[ i ]; + + if ( pointLine.DistanceToPoint( start ) < POINT_TO_CLOSE_DISTANCE ) + { + return PointInPathResult.ERROR; + } + + var nextIndex = ( i == _points.Count - 1 ) ? 0 : ( i + 1 ); + var end = _points[ nextIndex ]; + + + iterationLine.start = _points[ i ]; + iterationLine.end = _points[ nextIndex ]; + + var iterationAngle = iterationLine.angle; + var angleDifference = AbsoluteDeltaAngleFromRadiansToDegrees( pointAngle, iterationAngle ); + + if ( angleDifference < ANGLE_TO_SIMILAR_TRESHOLD ) + { + return PointInPathResult.ERROR; + } + + angleDifference = AbsoluteDeltaAngleFromRadiansToDegrees( pointAngle2, iterationAngle ); + + if ( angleDifference < ANGLE_TO_SIMILAR_TRESHOLD ) + { + return PointInPathResult.ERROR; + } + + + var intersects = pointLine.IntersectsWith( iterationLine ); + + if ( intersects ) + { + intersections ++; + } + + } + + if ( intersections % 2 != 0 ) + { + return PointInPathResult.INSIDE; + } + + return PointInPathResult.OUTSIDE; + + } + + public static Path2 Circle( Vector2 center, float radius = 1, int resolution = 36 ) + { + var points = new List(); + + for ( int i = 0; i < resolution; i++ ) + { + var t = i / ( float ) ( resolution ); + var phase = t * Mathf.Pi * 2; + var x = Mathf.Cos( phase ) * radius; + var y = Mathf.Sin( phase ) * radius; + + points.Add( new Vector2( x, y ) + center ); + } + + return new Path2( points ); + } + + + public static float AbsoluteDeltaAngleFromRadiansToDegrees( float rA, float rB ) + { + var dA = Mathf.RadToDeg( rA ); + var dB = Mathf.RadToDeg( rB ); + + return MathX.AbsoluteDeltaAngle( dA, dB ); + } + + + } +} \ No newline at end of file diff --git a/Runtime/Math/Geometry/Plane3.cs b/Runtime/Math/Geometry/Plane3.cs new file mode 100644 index 0000000..8bcbf2d --- /dev/null +++ b/Runtime/Math/Geometry/Plane3.cs @@ -0,0 +1,122 @@ +using Godot; + +namespace Rokojori +{ + public class Plane3 + { + public Vector3 normal = Vector3.Up; + public float constant = 0; + + + public Plane3(){} + + public void Set( Vector3 normal, float constant = 0 ) + { + this.normal = normal; + this.constant = constant; + } + + public Vector3 ConstrainToPlane( Vector3 p, float distance = 10000 ) + { + var line3 = new Line3(); + line3.Set( p - normal * distance, p + normal * distance ); + + var intersection = IntersectLine( line3 ); + + if ( intersection == null ) + { + return p; + } + + return (Vector3) intersection; + } + + public Vector3 ClosestPointTo( Vector3 p ) + { + return ConstrainToPlane( p ); + } + + public float DistanceTo( Vector3 p, float normalIntersectionDistance = 5000 ) + { + var line = Line3.Create( p - normal * normalIntersectionDistance, p + normal * normalIntersectionDistance ); + var isc = IntersectLine( line ); + + if ( isc == null ) + { + return float.PositiveInfinity; + } + + var xp = (Vector3)isc; + + return ( p - xp ).Length(); + } + + public Ray3 GetIntersectionRay( Plane3 other ) + { + var lineDirection = normal.Cross( other.normal ); + + var det = lineDirection.LengthSquared(); + + if ( det == 0 ) + { + return null; + } + + var r = new Ray3(); + r.offset = ( ( lineDirection.Cross( other.normal ) * constant ) + + ( normal.Cross( lineDirection ) * other.constant ) ) / det; + + r.direction = lineDirection; + + return r; + } + + public Vector3? IntersectLine( Line3 line ) + { + var direction = line.direction; + + var denominator = Math3D.Dot( normal, direction ); + + if ( denominator == 0 ) + { + return null; + } + + var t = - ( Math3D.Dot( line.start, normal ) + constant ) / denominator; + + if ( t < 0 || t > 1 ) + { + return null; + } + + return direction * t + line.start; + } + + public static Plane3 GetZeroUp() + { + var plane = new Plane3(); + plane.Set( Vector3.Up, 0 ); + return plane; + } + + public void SetFromNormalAndCoplanarPoint( Vector3 normal, Vector3 point ) + { + this.normal = normal; + this.constant = - Math3D.Dot( point, this.normal ); + } + + public void SetFromCoplanarPoints( Vector3 a, Vector3 b, Vector3 c ) + { + var normal = Math3D.ComputeNormal( a, b, c ); + SetFromNormalAndCoplanarPoint( normal, a ); + } + + public static Plane3 FromCoplanarPoints( Vector3 a, Vector3 b, Vector3 c ) + { + var p = new Plane3(); + p.SetFromCoplanarPoints( a, b, c ); + + return p; + } + } +} \ No newline at end of file diff --git a/Runtime/Math/Geometry/Pose.cs b/Runtime/Math/Geometry/Pose.cs new file mode 100644 index 0000000..1a94f9d --- /dev/null +++ b/Runtime/Math/Geometry/Pose.cs @@ -0,0 +1,100 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; + +namespace Rokojori +{ + public class Pose + { + bool _needsUpdate = true; + Quaternion _rotation = Quaternion.Identity; + public Quaternion rotation + { + get => _rotation; + set + { + _rotation = value; + _needsUpdate = true; + } + } + + Vector3 _position = Vector3.Zero; + public Vector3 position + { + get => _position; + set + { + _position = value; + _needsUpdate = true; + } + + } + + Basis _basis; + + void Update() + { + if ( ! _needsUpdate ) + { + return; + } + + _needsUpdate = false; + + _basis = new Basis( _rotation ); + } + + public Vector3 forward + { + get + { + Update(); + + return -_basis.Z; + } + } + + public Vector3 right + { + get + { + Update(); + + return _basis.X; + } + } + + public Vector3 up + { + get + { + Update(); + + return _basis.Y; + } + } + + public Pose() + { + + } + + + public Pose ToGlobal( Node3D n ) + { + var p = new Pose(); + p.rotation = rotation * n.GetGlobalQuaternion(); + p.position = n.ToGlobal( position ); + return p; + } + + public Vector3 Apply( Vector3 p ) + { + p = rotation * p; + p += position; + + return p; + } + + } +} \ No newline at end of file diff --git a/Runtime/Math/Geometry/Ray3.cs b/Runtime/Math/Geometry/Ray3.cs new file mode 100644 index 0000000..8dcf2d9 --- /dev/null +++ b/Runtime/Math/Geometry/Ray3.cs @@ -0,0 +1,12 @@ +using Godot; + +namespace Rokojori +{ + public class Ray3 + { + public Vector3 offset; + public Vector3 direction; + + + } +} \ No newline at end of file diff --git a/Runtime/Math/Geometry/SAT2.cs b/Runtime/Math/Geometry/SAT2.cs new file mode 100644 index 0000000..c45b9a1 --- /dev/null +++ b/Runtime/Math/Geometry/SAT2.cs @@ -0,0 +1,64 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; + +namespace Rokojori +{ + + public class SAT2 + { + public static void Project( Vector2 normal, List points, Range outputProjectionRange ) + { + var min = float.MaxValue; + var max = - float.MaxValue; + + for ( var i = 0; i < points.Count; i++ ) + { + var dot = Math2D.Dot( normal, points[ i ] ); + + min = Mathf.Min( min, dot ); + max = Mathf.Max( max, dot ); + } + + outputProjectionRange.min = min; + outputProjectionRange.max = max; + } + + public static bool IsIntersecting( + List normalsA, List pointsA, + List normalsB, List pointsB ) + { + var projectionRangeA = new Range( 0, 1 ); + var projectionRangeB = new Range( 0, 1 ); + + for ( var i = 0; i < normalsA.Count; i++ ) + { + var axis = normalsA[ i ]; + + Project( axis, pointsA, projectionRangeA ); + Project( axis, pointsB, projectionRangeB ); + + if ( ! projectionRangeA.Overlaps( projectionRangeB ) ) + { + return false; + } + } + + for ( var i = 0; i < normalsB.Count; i++ ) + { + var axis = normalsB[ i ]; + Project( axis, pointsA, projectionRangeA ); + Project( axis, pointsB, projectionRangeB ); + + if ( ! projectionRangeA.Overlaps( projectionRangeB ) ) + { + return false; + } + } + + return true; + } + + } +} + diff --git a/Runtime/Math/Geometry/SAT3.cs b/Runtime/Math/Geometry/SAT3.cs new file mode 100644 index 0000000..9471622 --- /dev/null +++ b/Runtime/Math/Geometry/SAT3.cs @@ -0,0 +1,83 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; + +namespace Rokojori +{ + + public class SAT3 + { + + public static bool IsIntersecting( + List pointsA, List faceNormalsA, List edgeDirectionsA, + List pointsB, List faceNormalsB, List edgeDirectionsB + ) + { + for ( var i = 0; i < faceNormalsA.Count; i++ ) + { + var axis = faceNormalsA[ i ]; + + if ( ProjectionsHaveGap( axis, pointsA, pointsB ) ) + { + return false; + } + } + + for ( var i = 0; i < faceNormalsB.Count; i++ ) + { + var axis = faceNormalsB[ i ]; + + if ( ProjectionsHaveGap( axis, pointsA, pointsB ) ) + { + return false; + } + } + + for ( var i = 0; i < edgeDirectionsA.Count; i++ ) + { + var edgeA = edgeDirectionsA[ i ]; + + for ( var j = 0; j < edgeDirectionsB.Count; j++ ) + { + var edgeB = edgeDirectionsB[ j ]; + + var axis = edgeA.Cross( edgeB ); + + if ( ProjectionsHaveGap( axis, pointsA, pointsB ) ) + { + return false; + } + } + } + + + return true; + } + + static bool ProjectionsHaveGap( Vector3 axis, List a, List b ) + { + var projectionRangeA = Project( axis, a ); + var projectionRangeB = Project( axis, b ); + + return ! projectionRangeA.Overlaps( projectionRangeB ); + } + + public static Range Project( Vector3 axis, List points ) + { + var min = float.MaxValue; + var max = - float.MaxValue; + + for ( var i = 0; i < points.Count; i++ ) + { + var dot = Math3D.Dot( axis, points[ i ] ); + + min = Mathf.Min( min, dot ); + max = Mathf.Max( max, dot ); + } + + return new Range( min, max ); + } + + } +} + diff --git a/Runtime/Math/Geometry/Sphere.cs b/Runtime/Math/Geometry/Sphere.cs index 74aac8b..a570fde 100644 --- a/Runtime/Math/Geometry/Sphere.cs +++ b/Runtime/Math/Geometry/Sphere.cs @@ -15,6 +15,17 @@ namespace Rokojori this.radius = radius; } + public Sphere(){} + + public Sphere Create( Vector3 center, float radius ) + { + var sphere = new Sphere(); + sphere.center = center; + sphere.radius = radius; + + return sphere; + } + public Sphere Copy() { return new Sphere( center, radius ); diff --git a/Runtime/Math/Geometry/SplineCurve.cs b/Runtime/Math/Geometry/SplineCurve.cs new file mode 100644 index 0000000..4639ee8 --- /dev/null +++ b/Runtime/Math/Geometry/SplineCurve.cs @@ -0,0 +1,158 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; + +namespace Rokojori +{ + public class SplineCurveTangent + { + public Vector3 position; + public float weight = 1f; + + public SplineCurveTangent Clone() + { + var t = new SplineCurveTangent(); + t.position = position; + t.weight = weight; + + return t; + } + + } + + public class SplineCurvePoint + { + public Vector3 position; + public Quaternion rotation; + public float weight = 1f; + + public SplineCurveTangent tangentBefore = new SplineCurveTangent(); + public SplineCurveTangent tangentNext = new SplineCurveTangent(); + + public SplineCurvePoint Clone() + { + var scp = new SplineCurvePoint(); + scp.position = position; + scp.rotation = rotation; + scp.weight = weight; + scp.tangentBefore = tangentBefore.Clone(); + scp.tangentNext = tangentNext.Clone(); + + return scp; + } + + public SplineCurvePoint CloneForXZ( float y ) + { + var cloned = Clone(); + cloned.position.Y = y; + cloned.tangentBefore.position.Y = 0; + cloned.tangentNext.position.Y = 0; + + return cloned; + } + + } + + public class SplineCurve: Curve3 + { + List _points = new List(); + + public List points => _points; + + public static SplineCurve From( List points ) + { + var splineCurve = new SplineCurve(); + splineCurve._points = points; + return splineCurve; + } + + public Vector3 MinPointPosition() + { + var min = _points[ 0 ].position; + + points.ForEach( p => min = min.Min( p.position ) ); + + return min; + } + + public Vector3 MaxPointPosition() + { + var max = _points[ 0 ].position; + + points.ForEach( p => max = max.Max( p.position ) ); + + return max; + } + + + public SplineCurve Clone() + { + var splineCurve = new SplineCurve(); + splineCurve._points = Lists.Map( points, p => p.Clone() ); + return splineCurve; + } + + public SplineCurve CloneForXZ( float y ) + { + var splineCurve = new SplineCurve(); + splineCurve._points = Lists.Map( points, p => p.CloneForXZ( y ) ); + return splineCurve; + } + + + public Vector3 GetByPointIndex( float pointIndex ) + { + if ( pointIndex <= 0 ) + { + return _points[ 0 ].position; + } + + if ( pointIndex >= ( _points.Count - 1 ) ) + { + return _points[ _points.Count - 1 ].position; + } + + var lower = Mathf.FloorToInt( pointIndex ); + var higher = Mathf.CeilToInt( pointIndex ); + + var lerpAmount = pointIndex - lower; + + return RationalCubicBezier.Compute( + lerpAmount, + + _points[ lower ].position, + _points[ lower ].tangentNext.position, + _points[ higher ].tangentBefore.position, + _points[ higher ].position, + + _points[ lower ].weight, + _points[ lower ].tangentNext.weight, + _points[ higher ].tangentBefore.weight, + _points[ higher ].weight + + ); + } + + public override Vector3 SampleAt( float t ) + { + if ( _points.Count <= 0 ) + { + return Vector3.Zero; + } + + var index = NormalizedToPointIndex( t ); + + return GetByPointIndex( index ); + } + + public float NormalizedToPointIndex( float normalized ) + { + return normalized * ( _points.Count - 1 ); + } + + public float PointIndexToNormalized( float pointIndex ) + { + return pointIndex / ( _points.Count - 1 ); + } + } +} \ No newline at end of file diff --git a/Runtime/Math/Geometry/SplineCurveCreator.cs b/Runtime/Math/Geometry/SplineCurveCreator.cs new file mode 100644 index 0000000..d32dd4d --- /dev/null +++ b/Runtime/Math/Geometry/SplineCurveCreator.cs @@ -0,0 +1,137 @@ +using Godot; +using System.Collections.Generic; +using System; + +namespace Rokojori +{ + public class SplineCurveCreator + { + bool closed; + + public SplineCurve Create( List splinePoints, bool close ) + { + closed = close; + var points = new List(); + + for ( int i = 0; i < splinePoints.Count; i++ ) + { + points.Add( CreatePoint( splinePoints, i ) ); + } + + if ( closed ) + { + points.Add( CreatePoint( splinePoints, 0 ) ); + } + + return SplineCurve.From( points ); + } + + SplineCurvePoint CreatePoint( List splinePoints, int index ) + { + var splineCurvePoint = new SplineCurvePoint(); + var splinePoint = splinePoints[ index ]; + splineCurvePoint.position = splinePoint.GlobalPosition; + splineCurvePoint.weight = 1f; + + splineCurvePoint.tangentBefore.position = GetTangentPosition( splinePoints, index, true ); + splineCurvePoint.tangentNext.position = GetTangentPosition( splinePoints, index, false ); + + splineCurvePoint.tangentBefore.weight = splinePoint.tangentBeforeWeight; + splineCurvePoint.tangentNext.weight = splinePoint.tangentNextWeight; + + return splineCurvePoint; + } + + public Vector3 GetTangentPosition( List splinePoints, int index, bool before ) + { + var splinePoint = splinePoints[ index ]; + + if ( splinePoint.tangentMode == SplinePointTangentMode.Custom ) + { + return before ? splinePoint.tangentBefore.GlobalPosition : + splinePoint.tangentNext.GlobalPosition; + } + + var previousIndex = index - 1; + + if ( previousIndex == -1 ) + { + previousIndex = closed ? splinePoints.Count -1 : 0; + } + + var nextIndex = index + 1; + + if ( nextIndex == splinePoints.Count ) + { + nextIndex = closed ? 0 : splinePoints.Count - 1; + } + + var previous = splinePoints[ previousIndex ]; + var next = splinePoints[ nextIndex ]; + + var previousPosition = previous.GlobalPosition; + var nextPosition = next.GlobalPosition; + + + + var point = splinePoint.GlobalPosition; + + var overshootPrevention = splinePoint.overshootPrevention; + var tangentScale = splinePoint.tangentScale; + var symmetricTangentLength = splinePoint.symmetricTangentLength; + + if ( overshootPrevention > 0 ) + { + var previousDirection = ( previousPosition - point ) ; + var nextDirection = ( nextPosition - point ); + + var previousLength = previousDirection.Length(); + var nextLength = nextDirection.Length(); + + if ( previousLength > nextLength ) + { + previousPosition = Math3D.Lerp( + previousPosition, point + previousDirection.Normalized() * nextLength, + overshootPrevention + ); + } + else + { + nextPosition = Math3D.Lerp( + nextPosition, point + nextDirection.Normalized() * previousLength, + overshootPrevention + ); + } + } + + var direction = nextPosition - previousPosition; + var length = 0f; + + var lengthBefore = ( point - previousPosition ).Length(); + var lengthNext = ( point - nextPosition ).Length(); + + var lengthAverage = ( lengthBefore + lengthNext ) * 0.5f; + + if ( symmetricTangentLength > 0 ) + { + lengthBefore = Mathf.Lerp( lengthBefore, lengthAverage, symmetricTangentLength ); + lengthNext = Mathf.Lerp( lengthNext, lengthAverage, symmetricTangentLength ); + } + + if ( before ) + { + length = -lengthBefore; + } + else + { + length = lengthNext; + } + + return point + direction.Normalized() * length * 0.33333f * tangentScale; + } + + + + } + +} \ No newline at end of file diff --git a/Runtime/Math/Geometry/Triangle3.cs b/Runtime/Math/Geometry/Triangle3.cs index bbd565a..5e8ed33 100644 --- a/Runtime/Math/Geometry/Triangle3.cs +++ b/Runtime/Math/Geometry/Triangle3.cs @@ -5,22 +5,6 @@ using System.Collections.Generic; namespace Rokojori { - public enum TriangleSide - { - AB, - BC, - CA - } - - public class TriangleSides - { - public static readonly TriangleSide[] ALL = - { - TriangleSide.AB, - TriangleSide.BC, - TriangleSide.CA - }; - } public class Triangle3 { @@ -33,6 +17,8 @@ namespace Rokojori this.a = a; this.b = b; this.c = c; + + _needsUpdate = true; } public float perimeter @@ -47,14 +33,45 @@ namespace Rokojori } } - public float semiperimeter + public float semiperimeter => perimeter * 0.5f; + + Vector3 _center; + Sphere _boundingSphere; + Plane3 _plane; + + bool _needsUpdate = false; + + public void NeedsUpdate() { - get - { - return perimeter * 0.5f; + _needsUpdate = true; + } + + public Sphere boundingSphere + { + get + { + Update(); + return _boundingSphere; } } + void Update() + { + if ( ! _needsUpdate ) + { + return; + } + + var m = Math3D.Center( a, b, c ); + var radius = Mathf.Sqrt( MathX.Min( ( m - a ).LengthSquared(), ( m - b ).LengthSquared(), ( m -c ).LengthSquared() ) ); + _boundingSphere = new Sphere( m, radius ); + + _plane = new Plane3(); + _plane.SetFromCoplanarPoints( a, b, c ); + + _needsUpdate = false; + } + public static float ComputeTriangleArea( Vector3 a, Vector3 b, Vector3 c ) { var sideA = ( b - a ).Length(); @@ -71,26 +88,266 @@ namespace Rokojori return areaValue; } - public float area + public float area => ComputeTriangleArea( a, b, c ); + + public bool Intersects( Line3 line ) { - get + var planePoint = _plane.IntersectLine( line ); + + if ( planePoint == null ) { - return ComputeTriangleArea( a, b, c ); - } + return false; + } + + return InsideTriangle( (Vector3) planePoint, a, b, c ); + } + + public Vector3? GetIntersection( Line3 line ) + { + Update(); + + var planePoint = _plane.IntersectLine( line ); + + if ( planePoint == null ) + { + return null; + } + + if ( ! InsideTriangle( (Vector3) planePoint, a, b, c ) ) + { + return null; + } + + return planePoint; } + public bool PointInside( Vector3 p ) + { + return InsideTriangle( p, a, b, c ); + } + + public Vector3 ClosestPoint( Vector3 p ) + { + Update(); - public Line3 GetSide( TriangleSide side ) - { - switch ( side ) + var planePoint = _plane.ClosestPointTo( p ); + + if ( PointInside( planePoint ) ) { - case TriangleSide.AB: return new Line3( a, b ); - case TriangleSide.BC: return new Line3( b, c ); - case TriangleSide.CA: return new Line3( c, a ); + return planePoint; + } + + return ClosestPointToEdges( p ); + + } + + public Vector3? GetPointOnPlaneInsideTriangle( Vector3 p ) + { + Update(); + + var planePoint = _plane.ClosestPointTo( p ); + + if ( PointInside( planePoint ) ) + { + return planePoint; } return null; } + + Vector3[] closestPoints = new Vector3[3]; + + public Vector3 ClosestPointToEdges( Vector3 p ) + { + closestPoints[ 0 ] = Line3.ClosestPointOf( p, a, b ); + closestPoints[ 1 ] = Line3.ClosestPointOf( p, b, c ); + closestPoints[ 2 ] = Line3.ClosestPointOf( p, c, a ); + + return Math3D.GetClosest( p, closestPoints ); + } + + public Vector3 ClosestPoint( Line3 line ) + { + var intersection = GetIntersection( line ); + + if ( intersection != null ) + { + return (Vector3) intersection; + } + + var ab = new Line3( a, b ).ClosestPointTo( line ); + var bc = new Line3( b, c ).ClosestPointTo( line ); + var ca = new Line3( c, a ).ClosestPointTo( line ); + + return line.ClosestPointTo( ab, bc, ca ); + } + + public Vector3 GetPoint( int i ) + { + if ( i == 0 ) + { + return a; + } + else if ( i == 1 ) + { + return b; + } + else if ( i == 2 ) + { + return c; + } + + return a; + } + + void SetPointsToLine( Line3 line, int index ) + { + line.Set( GetPoint( index ), GetPoint( ( index + 1 ) % 3 ) ); + } + + public Line3 GetIntersectionLine( Triangle3 other ) + { + var ownSphere = boundingSphere; + var otherSphere = other.boundingSphere; + + if ( ! ownSphere.IntersectsSphere( otherSphere ) ) + { + return null; + } + + var line = new Line3(); + + var intersections = new List(); + + for ( int i = 0; i < 3; i++ ) + { + SetPointsToLine( line, i ); + var intersection = other.GetIntersection( line ); + + if ( intersection != null ) + { + intersections.Add( (Vector3) intersection ); + } + } + + if ( intersections.Count == 2 ) + { + line.Set( intersections[ 0 ], intersections[ 1 ] ); + return line; + } + + for ( int i = 0; i < 3; i++ ) + { + other.SetPointsToLine( line, i ); + var intersection = GetIntersection( line ); + + if ( intersection != null ) + { + intersections.Add( (Vector3) intersection ); + } + } + + if ( intersections.Count == 2 ) + { + line.Set( intersections[ 0 ], intersections[ 1 ] ); + return line; + } + + return null; + + + } + + public bool Intersects( Triangle3 other ) + { + var ownSphere = boundingSphere; + var otherSphere = other.boundingSphere; + + if ( ! ownSphere.IntersectsSphere( otherSphere ) ) + { + return false; + } + + var line = new Line3(); + + for ( int i = 0; i < 3; i++ ) + { + if ( PointInside( other.GetPoint( i ) ) ) + { + return true; + } + + if ( other.PointInside( GetPoint( i ) ) ) + { + return true; + } + + SetPointsToLine( line, i ); + + if ( other.Intersects( line ) ) + { + return true; + } + + other.SetPointsToLine( line, i ); + + if ( Intersects( line ) ) + { + return true; + } + } + + return false; + } + + + public static bool InsideTriangle( Vector3 point, Vector3 a, Vector3 b, Vector3 c ) + { + var bary = GetBaryCentricCoordinate( point, a, b, c ); + + if ( bary == null ) + { + return false; + } + + var values = (Vector3) bary; + + var insideV = Range.Contains( values.Y, 0, 1 ); + var insideU = Range.Contains( values.Z, 0, 1 ); + var insideX = Range.Contains( values.X, 0, 1 ); + + var result = insideX && insideV && insideU; + return result; + + } + + public static Vector3? GetBaryCentricCoordinate( Vector3 point, Vector3 a, Vector3 b, Vector3 c ) + { + var _v0 = c - a; + var _v1 = b - a; + var _v2 = point - a; + + var dot00 = Math3D.Dot( _v0, _v0 ); + var dot01 = Math3D.Dot( _v0, _v1 ); + var dot02 = Math3D.Dot( _v0, _v2 ); + var dot11 = Math3D.Dot( _v1, _v1 ); + var dot12 = Math3D.Dot( _v1, _v2 ); + + var denom = ( dot00 * dot11 - dot01 * dot01 ); + + if ( denom == 0 ) + { + return null; + } + + var invDenom = 1f / denom; + + var u = ( dot11 * dot02 - dot01 * dot12 ) * invDenom; + var v = ( dot00 * dot12 - dot01 * dot02 ) * invDenom; + + return new Vector3( 1f - u - v, v, u ); + } + } } \ No newline at end of file diff --git a/Runtime/Math/Math2D.cs b/Runtime/Math/Math2D.cs new file mode 100644 index 0000000..7ff9dab --- /dev/null +++ b/Runtime/Math/Math2D.cs @@ -0,0 +1,50 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; +using System.Text; +using System; + +namespace Rokojori +{ + public class Math2D + { + public static float Dot( Vector2 a, Vector2 b ) + { + return a.Dot( b ); + } + + public static Vector2 XZ( Vector3 v ) + { + return new Vector2( v.X, v.Z ); + } + + public static Vector2 Fract( Vector2 a ) + { + return new Vector2( MathX.Fract( a.X ), MathX.Fract( a.Y ) ); + } + + public static Vector2 SmoothStep( Vector2 a, Vector2 b, Vector2 t ) + { + var x = MathX.Smoothstep( a.X, b.X, t.X ); + var y = MathX.Smoothstep( a.Y, b.Y, t.Y ); + return new Vector2( x, y ); + } + + public static Vector2 Rotate90DegreesRight( Vector2 v ) + { + // 1, 0.5 => -0.5, 1 : -y, x + return new Vector2( -v.Y, v.X ); + } + + public static Vector2 Rotate90DegreesLeft( Vector2 v ) + { + // -0.5, 1 => 1, 0.5 : y, -x + return new Vector2( v.Y, -v.X ); + } + + public static Vector2 Rotate90Degrees( Vector2 v, bool clockwise ) + { + return clockwise ? Rotate90DegreesRight( v ) : Rotate90DegreesLeft( v ); + } + } +} \ No newline at end of file diff --git a/Runtime/Math/Math3D.cs b/Runtime/Math/Math3D.cs index 3d24c13..c15a7c0 100644 --- a/Runtime/Math/Math3D.cs +++ b/Runtime/Math/Math3D.cs @@ -6,8 +6,146 @@ using System; namespace Rokojori { - public class Math3D + public static class Math3D { + public static Vector3 OnCircleXZ( float radians, float size = 1 ) + { + var x = Mathf.Cos( radians ) * size; + var z = Mathf.Sin( radians ) * size; + + return new Vector3( x, 0, z ); + } + + public static Vector3 XYasXZ( Vector2 v ) + { + return new Vector3( v.X, 0, v.Y ); + } + + public static float Dot( Vector3 a, Vector3 b ) + { + return a.Dot( b ); + } + + public static Vector3 Cross( Vector3 a, Vector3 b ) + { + return a.Cross( b ); + } + + public static Vector3 Fract( Vector3 a ) + { + return new Vector3( MathX.Fract( a.X ), MathX.Fract( a.Y ), MathX.Fract( a.Z ) ); + } + + public static bool IsExactlyZero( Vector3 v ) + { + return v.X == 0 && v.Y == 0 && v.Z == 0; + } + + public static Vector3 ComputeNormalFast( Vector3 a, Vector3 b, Vector3 c ) + { + return Math3D.Cross( c - b , a - b ).Normalized(); + } + + public static Vector3 ComputeNormal( Vector3 a, Vector3 b, Vector3 c ) + { + var cross = Math3D.Cross( c - b , a - b ); + + if ( Math3D.IsExactlyZero( cross ) ) + { + cross = Math3D.Cross( b - c , a - c ); + } + + if ( Math3D.IsExactlyZero( cross ) ) + { + cross = Math3D.Cross( c - a , b - a ); + } + + return cross.Normalized(); + } + + + public static Vector3 SmoothStep( Vector3 a, Vector3 b, Vector3 t ) + { + var x = MathX.Smoothstep( a.X, b.X, t.X ); + var y = MathX.Smoothstep( a.Y, b.Y, t.Y ); + var z = MathX.Smoothstep( a.Z, b.Z, t.Z ); + + return new Vector3( x, y, z ); + } + + public static Vector3 Center( params Vector3[] points ) + { + var center = Vector3.Zero; + + if ( points.Length == 0 ) + { + return center; + } + + for ( int i = 0; i < points.Length; i++ ) + { + center += points[ i ]; + } + + return center / points.Length; + } + + public static Vector3 Center( List points ) + { + var center = Vector3.Zero; + + if ( points.Count == 0 ) + { + return center; + } + + for ( int i = 0; i < points.Count; i++ ) + { + center += points[ i ]; + } + + return center / points.Count; + } + + public static Vector3 GetClosest( Vector3 from, params Vector3[] points ) + { + var distance = float.MaxValue; + var index = -1; + + for ( int i = 0; i < points.Length; i++ ) + { + var d = from.DistanceSquaredTo( points[ i ] ); + + if ( d < distance ) + { + index = i; + distance = d; + } + } + + return points[ index ]; + } + + public static Vector3 GetClosest( Vector3 from, List points ) + { + var distance = float.MaxValue; + var index = -1; + + for ( int i = 0; i < points.Count; i++ ) + { + var d = from.DistanceSquaredTo( points[ i ] ); + + if ( d < distance ) + { + index = i; + distance = d; + } + } + + return points[ index ]; + } + + public static Vector3 LerpUnclamped( Vector3 a, Vector3 b, float amount ) { return a + amount * ( b - a ); @@ -18,12 +156,115 @@ namespace Rokojori return LerpUnclamped( a, b, MathX.Clamp01( amount ) ); } - public static Quaternion GetGlobalRotation( Node3D node ) + public static Quaternion GetGlobalQuaternion( this Node3D node ) { - return node.GlobalBasis.GetRotationQuaternion(); + return GetGlobalRotationFrom( node ); } - public static void SetGlobalRotation( Node3D node, Quaternion quaternion ) + public static Quaternion GetGlobalRotationFrom( Node3D node ) + { + var quaternion = node.GlobalBasis.GetRotationQuaternion(); + + return quaternion; + } + + public static Vector3 MinPosition( Node3D a, Node3D b ) + { + return a.GlobalPosition.Min( b.GlobalPosition ); + } + + public static Vector3 MaxPosition( Node3D a, Node3D b ) + { + return a.GlobalPosition.Max( b.GlobalPosition ); + } + + public static Vector3 SnapRounded( Vector3 v, Vector3 snapping ) + { + v.X = MathX.SnapRounded( v.X, snapping.X ); + v.Y = MathX.SnapRounded( v.Y, snapping.Y ); + v.Z = MathX.SnapRounded( v.Z, snapping.Z ); + + return v; + } + + public static Vector3 SnapCeiled( Vector3 v, Vector3 snapping ) + { + v.X = MathX.SnapCeiled( v.X, snapping.X ); + v.Y = MathX.SnapCeiled( v.Y, snapping.Y ); + v.Z = MathX.SnapCeiled( v.Z, snapping.Z ); + + return v; + } + + public static Vector3 SnapFloored( Vector3 v, Vector3 snapping ) + { + v.X = MathX.SnapFloored( v.X, snapping.X ); + v.Y = MathX.SnapFloored( v.Y, snapping.Y ); + v.Z = MathX.SnapFloored( v.Z, snapping.Z ); + + return v; + } + + public static Quaternion AlignUp( Quaternion rotation, Vector3 upDirection ) + { + var basis = new Basis( rotation ); + var aligned = AlignUp( basis, upDirection ); + return aligned.GetRotationQuaternion(); + } + + public static Basis AlignUp( Basis basis, Vector3 upDirection ) + { + basis.Y = upDirection; + basis.X = - basis.Z.Cross( upDirection ); + return basis.Orthonormalized(); + } + + public static Vector3 Lerp( Vector3 a, Vector3 b, float lerp ) + { + return a.Lerp( b, lerp ); + } + + public static Quaternion LookRotation( Vector3 direction, Vector3 up ) + { + if ( direction.Length() == 0 ) + { + return Quaternion.Identity; + } + + var t = new Transform3D(); + t.Basis = Basis.Identity; + t.Origin = Vector3.Zero; + + t = t.LookingAt( direction, up ); + + return t.Basis.GetRotationQuaternion(); + } + + public static Vector3 LerpComponents( Vector3 a, Vector3 b, Vector3 t ) + { + return new Vector3( Mathf.Lerp( a.X, b.X, t.X ), Mathf.Lerp( a.Y, b.Y, t.Y ), Mathf.Lerp( a.Z, b.Z, t.Z ) ); + } + + public static Vector4 AsVector4( this Quaternion q) + { + return new Vector4( q.X, q.Y, q.Z, q.W ); + } + + public static void SetGlobalQuaternion( this Node3D node, Quaternion quaternion ) + { + var offset = node.GlobalPosition; + + var localScale = node.Scale; + node.GlobalBasis = new Basis( quaternion ); + node.GlobalPosition = offset; + node.Scale = localScale; + + + + //SetGlobalRotationTo( node, quaternion ); + } + + public static void SetGlobalRotationTo( Node3D node, Quaternion quaternion ) { var forward = quaternion * Vector3.Forward; var up = quaternion * Vector3.Up; @@ -31,16 +272,62 @@ namespace Rokojori node.LookAt( node.GlobalPosition + forward, up ); } + public static void LookTowards( this Node3D node, Vector3 forwardDirection, Vector3 upDirection, Quaternion rotation ) + { + //forwardDirection = rotation * forwardDirection; + //upDirection = rotation * upDirection; + + node.LookTowards( forwardDirection, upDirection ); + node.SetGlobalQuaternion( node.GetGlobalQuaternion() * rotation ); + } + + public static void LookTowards( this Node3D node, Vector3 forward, Vector3 up ) + { + node.LookAt( forward + node.GlobalPosition, up ); + } + + + public static Quaternion GetDifference( this Quaternion q, Quaternion other ) + { + return GetQuaternionDifference( q, other ); + } + + public static Quaternion GetQuaternionDifference( Quaternion a, Quaternion b ) + { + return b.Inverse() * a; + } + + public static Quaternion GetQuaternionFraction( Quaternion q, float fraction ) + { + return Quaternion.Identity.Slerp( q, fraction ); + } + + + public static Vector3 GlobalForward( this Node3D node ) + { + return GetGlobalForward( node ); + } + public static Vector3 GetGlobalForward( Node3D node ) { return -node.GlobalBasis.Z; } + public static Vector3 GlobalUp( this Node3D node ) + { + return GetGlobalUp( node ); + } + public static Vector3 GetGlobalUp( Node3D node ) { return node.GlobalBasis.Y; } + public static Vector3 GlobalRight( this Node3D node ) + { + return GetGlobalRight( node ); + } + public static Vector3 GetGlobalRight( Node3D node ) { return node.GlobalBasis.X; @@ -63,6 +350,54 @@ namespace Rokojori return right.Normalized(); } + + public static void SetGlobalX( this Node3D node, float x ) + { + var gp = node.GlobalPosition; + + gp.X = x; + + node.GlobalPosition = gp; + } + + public static void SetGlobalY( this Node3D node, float y ) + { + var gp = node.GlobalPosition; + + gp.Y = y; + + node.GlobalPosition = gp; + } + + public static void SetGlobalZ( this Node3D node, float z ) + { + var gp = node.GlobalPosition; + + gp.Z = z; + + node.GlobalPosition = gp; + } + + public static Aabb? GetWorldBounds( this Node3D node ) + { + return GetWorldBoundsFrom( node ); + } + + public static Aabb? GetWorldBoundsFrom( Node3D node ) + { + Aabb? worldBounds = null; + + Nodes.ForEach( node, + ( vi )=> + { + var nBounds = vi.GetAabb(); + + worldBounds = worldBounds == null ? nBounds : ( ((Aabb)worldBounds).Merge( nBounds ) ); + } + ); + + return worldBounds; + } } } \ No newline at end of file diff --git a/Runtime/Math/MathX.cs b/Runtime/Math/MathX.cs index 941176d..61f53b5 100644 --- a/Runtime/Math/MathX.cs +++ b/Runtime/Math/MathX.cs @@ -10,6 +10,103 @@ namespace Rokojori { public const float fps120Delta = 1/120f; + public static float Min( params float[] values ) + { + var value = - float.MaxValue; + + for ( int i = 0; i < values.Length; i++ ) + { + value = Mathf.Min( values[ i ], value ); + } + + return value; + } + + public static float Max( params float[] values ) + { + var value = - float.MaxValue; + + for ( int i = 0; i < values.Length; i++ ) + { + value = Mathf.Max( values[ i ], value ); + } + + return value; + } + + public static float Sample( float tl, float tr, float bl, float br, Vector2 uv ) + { + return Mathf.Lerp( tl, tr, uv.X ) + + ( bl - tl ) * uv.Y * ( 1.0f - uv.X ) + + ( br - tr ) * uv.X * uv.Y; + } + + public static float Sample( + float ba, float bb, float bc, float bd, + float ta, float tb, float tc, float td, + Vector3 uvw + ) + { + var bottom = Sample( ba, bb, bc, bd, new Vector2( uvw.X, uvw.Y ) ); + var top = Sample( ta, tb, tc, td, new Vector2( uvw.X, uvw.Y ) ); + + return Mathf.Lerp( bottom, top, uvw.Z ); + } + + public static float Fract( float value ) + { + return value - Mathf.Floor( value ); + } + + public static float TimeLerpAmountExp( float t, float time ) + { + return Mathf.Exp( -t * time ); + } + + public static float TimeLerp( float lastValue, float nextValue, float t, float time ) + { + return Mathf.Lerp( nextValue, lastValue, TimeLerpAmountExp( t, time ) ); + } + + public static float AbsoluteDeltaAngle( float degreesA, float degreesB ) + { + return Mathf.Abs( AngleDelta( degreesA, degreesB ) ); + } + + public static float Smoothstep ( float edge0, float edge1, float x ) + { + x = MathX.Clamp01( ( x - edge0 ) / ( edge1 - edge0 ) ); + + return x * x * ( 3.0f - 2.0f * x ); + } + + public static float SnapRounded( float value, float snappingDistance ) + { + return Mathf.Round( value / snappingDistance ) * snappingDistance; + } + + public static float SnapCeiled( float value, float snappingDistance ) + { + return Mathf.Ceil( value / snappingDistance ) * snappingDistance; + } + + public static float SnapFloored( float value, float snappingDistance ) + { + return Mathf.Floor( value / snappingDistance ) * snappingDistance; + } + + public static float AngleDelta( float degreesA, float degreesB) + { + var angleDelta = degreesB - degreesA; + angleDelta = (angleDelta + 180f) % 360f - 180f; + return angleDelta; + } + + static float CustomModulo( float a, float n ) + { + return a - Mathf.Floor( a / n ) * n; + } + public static float Clamp01( float value ) { return Mathf.Clamp( value, 0, 1 ); @@ -225,10 +322,7 @@ namespace Rokojori } - public static float TimeLerp( float from, float to, float t, float timeDelta ) - { - return Mathf.Lerp( from, to, 1f - Mathf.Pow( t, timeDelta ) ); - } + public static float PolarAxis( bool negative, bool positive ) { diff --git a/Runtime/Math/_Unity_To_Godot_.txt b/Runtime/Math/_Unity_To_Godot_.txt index 44bd81c..a7ba6f7 100644 --- a/Runtime/Math/_Unity_To_Godot_.txt +++ b/Runtime/Math/_Unity_To_Godot_.txt @@ -13,7 +13,7 @@ Replace: $1.DistanceTo( Match: \.x Replace: .X -Match: \.Y +Match: \.y Replace: .Y Match: \.z diff --git a/Runtime/Navigation/NavigationMap2D.cs b/Runtime/Navigation/NavigationMap2D.cs new file mode 100644 index 0000000..5d8ce2b --- /dev/null +++ b/Runtime/Navigation/NavigationMap2D.cs @@ -0,0 +1,51 @@ +using Godot; +using System.Collections; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Rokojori +{ + public class NavigationMap2D + { + public Rid map; + public Rid region; + + public Vector2[] GetPath( Vector2 start, Vector2 end ) + { + return NavigationServer2D.MapGetPath( map, start, end, true ); + } + + public static async Task Create( Node node, List regions ) + { + var map = NavigationServer2D.MapCreate(); + NavigationServer2D.MapSetActive( map, true ); + + var cellSize = NavigationServer2D.MapGetCellSize( map ); + + + var region = NavigationServer2D.RegionCreate(); + NavigationServer2D.RegionSetTransform( region, Transform2D.Identity ); + + NavigationServer2D.RegionSetMap( region, map ); + + var polygon = new NavigationPolygon(); + // polygon.SetCe + for ( int i = 0; i < 1 && i < regions.Count; i++ ) + { + regions[ i ].AddToNavigationPolygon( polygon ); + } + + NavigationServer2D.RegionSetNavigationPolygon( region, polygon ); + + await node.ToSignal( node.GetTree(), SceneTree.SignalName.PhysicsFrame ); + + var mapData = new NavigationMap2D(); + + mapData.map = map; + mapData.region = region; + + + return mapData; + } + } +} \ No newline at end of file diff --git a/Runtime/Physics/CollisionData.cs b/Runtime/Physics/CollisionData.cs index 9978d5c..f104fb7 100644 --- a/Runtime/Physics/CollisionData.cs +++ b/Runtime/Physics/CollisionData.cs @@ -1,7 +1,8 @@ using Godot; using System.Collections; using System.Collections.Generic; - +using Godot.Collections; +using System; namespace Rokojori { @@ -14,7 +15,48 @@ namespace Rokojori public Shape3D shape; public Rid rid; - + + public static bool HasCollisionMask( Node n ) + { + return n is CsgShape3D || n is CollisionObject3D; + } + + public static uint GetCollisionMask( Node n ) + { + if ( n is CsgShape3D ) + { + return ( n as CsgShape3D ).CollisionMask; + } + + if ( n is CollisionObject3D ) + { + return ( n as CollisionObject3D ).CollisionMask; + } + + return 0; + } + + public static bool HasCollisionLayer( Node n ) + { + return n is CsgShape3D || n is CollisionObject3D; + } + + public static uint GetCollisionLayer( Node n ) + { + if ( n is CsgShape3D ) + { + return ( n as CsgShape3D ).CollisionLayer; + } + + if ( n is CollisionObject3D ) + { + return ( n as CollisionObject3D ).CollisionLayer; + } + + return 0; + } + + public void Get( PhysicsRayQueryParameters3D ray, PhysicsDirectSpaceState3D physicsState ) { var result = physicsState.IntersectRay( ray ); @@ -40,5 +82,96 @@ namespace Rokojori // RJLog.Log( "Has Collision:", HierarchyName.Of( collider ), ">> at position:", position, "with normal:", normal ); } + + public static CollisionData FindCollision( World3D world, PhysicsRayQueryParameters3D rayParameters, Func predicate ) + { + var physics = world.DirectSpaceState; + + var excludes = new Array(); + + var maxHits = 10000; + + for ( int i = 0; i < maxHits; i++ ) + { + rayParameters.Exclude = excludes; + + var collisionData = new CollisionData(); + collisionData.Get( rayParameters, physics ); + + if ( ! collisionData.hasCollision ) + { + return null; + } + + if ( predicate( collisionData ) ) + { + return collisionData; + } + + excludes.Add( collisionData.rid ); + + } + + return null; + + } + + public static int MultiRayCast( World3D world, PhysicsRayQueryParameters3D rayParameters, List collisionsOutput ) + { + var physics = world.DirectSpaceState; + + var excludes = new Array(); + + var numCollisions = 0; + + for ( int i = 0; i < collisionsOutput.Count; i++ ) + { + rayParameters.Exclude = excludes; + + var collisionData = collisionsOutput[ i ]; + collisionData.Get( rayParameters, physics ); + + if ( ! collisionData.hasCollision ) + { + return i; + } + + excludes.Add( collisionData.rid ); + + numCollisions ++; + } + + return numCollisions; + + } + + public static List MultiRayCast( World3D world, PhysicsRayQueryParameters3D rayParameters, int maxHits = 100 ) + { + var physics = world.DirectSpaceState; + + var excludes = new Array(); + + var collisions = new List(); + + for ( int i = 0; i < maxHits; i++ ) + { + rayParameters.Exclude = excludes; + + var collisionData = new CollisionData(); + collisionData.Get( rayParameters, physics ); + + if ( ! collisionData.hasCollision ) + { + return collisions; + } + + excludes.Add( collisionData.rid ); + + collisions.Add( collisionData ); + } + + return collisions; + + } } } \ No newline at end of file diff --git a/Runtime/Procedural/ConnectionCircle.cs b/Runtime/Procedural/ConnectionCircle.cs new file mode 100644 index 0000000..eafa2e5 --- /dev/null +++ b/Runtime/Procedural/ConnectionCircle.cs @@ -0,0 +1,130 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; +using System; + + + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class ConnectionCircle:Node3D + #if TOOLS + , GizmoDrawer +#endif + { + [Export] + public bool updateAlways = false; + + [Export] + public Node3D root; + + [Export] + public float radius = 1; + float _radius = -1; + + [Export( PropertyHint.Range, "2,64") ] + public int divisions = 16; + int _divisions = -1; + + [Export] + public float editorGizmoSize = 1; + + float _editorGizmoSize = 1; + + public Pose GetLocalPose( int index ) + { + var p = new Pose(); + var state = index / (float)( divisions ); + var radians = state * Mathf.Pi * 2f; + var angle = Mathf.RadToDeg( radians ); + + p.rotation = Quaternion.FromEuler( new Vector3( 0, -radians , 0 ) ); + p.position = p.rotation * Vector3.Forward * radius; + + // RJLog.Log( radians, angle, p.rotation, p.position ); + + return p; + } + + public Pose GetGlobalPose( int index ) + { + return GetLocalPose( index ).ToGlobal( this ); + } + +#if TOOLS + + + public void DrawGizmo( EditorNode3DGizmoPlugin gizmoPlugin, EditorNode3DGizmo gizmo ) + { + gizmo.Clear(); + + + var material = gizmoPlugin.GetMaterial( "main", gizmo ); + + for ( int i = 0; i < divisions; i++ ) + { + var p = GetLocalPose( i ); + + var center = p.position; + var up = p.position + p.up * editorGizmoSize; + var forward = p.position + p.forward * editorGizmoSize; + var right = p.position + p.right * radius * 4f / divisions; + var left = p.position - p.right * radius * 4f / divisions; + + /*gizmo.AddLines( new Vector3[] + { + center, forward + }, + material + );*/ + + gizmo.AddLines( new Vector3[] + { + center, up, + center, forward, + right, left, + right, forward, + left, forward, + up, forward + + }, + material + ); + + } + + + } + + public override void _Process( double delta ) + { + var changed = false; + + if ( editorGizmoSize != _editorGizmoSize || + radius != _radius || + divisions != _divisions + ) + { + _editorGizmoSize = editorGizmoSize; + _radius = radius; + _divisions = divisions; + + changed = true; + } + + if ( changed ) + { + UpdateGizmos(); + } + + + + } + + +#endif + + } +} \ No newline at end of file diff --git a/Runtime/Procedural/ConnectionPin.cs b/Runtime/Procedural/ConnectionPin.cs new file mode 100644 index 0000000..928ec39 --- /dev/null +++ b/Runtime/Procedural/ConnectionPin.cs @@ -0,0 +1,170 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; +using System; + + + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class ConnectionPin:Node3D + #if TOOLS + , GizmoDrawer +#endif + { + [Export] + public bool updateAlways = false; + + [Export] + public Node3D root; + + [Export] + public bool moveToMinX = false; + + [Export] + public bool moveToMaxX = false; + + [Export] + public bool moveToMinY = false; + + [Export] + public bool moveToMaxY = false; + + [Export] + public bool moveToMinZ = false; + + [Export] + public bool moveToMaxZ = false; + + [Export] + public float editorGizmoSize = 1; + + float _editorGizmoSize = 1; + + +#if TOOLS + + + public void DrawGizmo( EditorNode3DGizmoPlugin gizmoPlugin, EditorNode3DGizmo gizmo ) + { + gizmo.Clear(); + + var center = Vector3.Zero; + var up = Vector3.Up * editorGizmoSize; + var forward = Vector3.Forward * editorGizmoSize; + var right = Vector3.Right * editorGizmoSize; + + var material = gizmoPlugin.GetMaterial( "main", gizmo ); + + gizmo.AddLines( + new Vector3[] + { + center, up, + center, -forward, + center, right, + center, -right, + right, -forward, + -right, -forward, + up, -forward, + up, -right, + up, right + }, + material + ); + } + + public override void _Process( double delta ) + { + var changed = false; + + if ( editorGizmoSize != _editorGizmoSize ) + { + _editorGizmoSize = editorGizmoSize; + changed = true; + } + + if ( changed ) + { + UpdateGizmos(); + } + + if ( ! ( moveToMinX || moveToMaxX || moveToMinY || moveToMaxY || moveToMinZ || moveToMaxZ ) ) + { + return; + } + + if ( root == null ) + { + RJLog.Log( "No Root!" ); + ClearFlags(); + return; + } + + + var optionalBbounds = root.GetWorldBounds(); + + if ( optionalBbounds == null ) + { + RJLog.Log( "No Bounds!" ); + ClearFlags(); + return; + } + + var bounds = (Aabb) optionalBbounds; + + if ( moveToMinX || moveToMaxX ) + { + this.SetGlobalX( ( moveToMinX ? bounds.Position : bounds.End ).X ); + } + + if ( moveToMinY || moveToMaxY ) + { + this.SetGlobalY( ( moveToMinY ? bounds.Position : bounds.End ).Y ); + } + + if ( moveToMinZ || moveToMaxZ ) + { + this.SetGlobalZ( ( moveToMinZ ? bounds.Position : bounds.End ).Z ); + } + + ClearFlags(); + + + } + + void ClearFlags() + { + moveToMinX = false; + moveToMaxX = false; + + moveToMinY = false; + moveToMaxY = false; + + moveToMinZ = false; + moveToMaxZ = false; + } + +#endif + public static readonly Quaternion YawFlipRotation = Quaternion.FromEuler( new Vector3( 0, 180, 0 ) / 180 * Mathf.Pi ); + + public static void Connect( Node3D target, Node3D targetPin, Node3D sourcePin ) + { + var targetRotation = target.GetGlobalQuaternion(); + var targetPinRotation = targetPin.GetGlobalQuaternion(); + + var pinToParent = targetPinRotation.GetDifference( targetRotation ); + + var forward = sourcePin.GlobalForward(); + var up = sourcePin.GlobalUp(); + + var rotation = ConnectionPin.YawFlipRotation * pinToParent; + + target.LookTowards( forward, up, rotation ); + + var offset = ( sourcePin.GlobalPosition - targetPin.GlobalPosition ); + target.GlobalTranslate( offset ); + } + } +} \ No newline at end of file diff --git a/Runtime/Procedural/ConnectionPinTester.cs b/Runtime/Procedural/ConnectionPinTester.cs new file mode 100644 index 0000000..21d1ae4 --- /dev/null +++ b/Runtime/Procedural/ConnectionPinTester.cs @@ -0,0 +1,192 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; +using System; + + + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class ConnectionPinTester:Node3D + { + [Export] + public ConnectionPin fromPin; + + [Export] + public Node3D toParent; + [Export] + public ConnectionPin toPin; + + [Export] + public bool update = false; + + [Export] + public bool updateAlways = false; + + [Export] + public Vector3 eulerRotation; + + [Export] + public bool diff_reverse = false; + + [Export] + public bool mult_reverse = false; + + [Export] + public Vector4 targetPin; + + [Export] + public Vector4 currentPin; + + [Export] + public Vector4 needed; + + [Export] + public Vector4 oldApplying; + + [Export] + public Vector4 applyingTargetApplied; + + + [Export] + public Vector4 pinApplyied; + + [Export] + public Node3D applyingTarget; + + [Export] + public Node3D forwardTarget; + + [Export] + public Vector3 customRotationEulers; + + [Export] + public bool rotateBefore; + + public override void _Process( double delta ) + { + Connect3(); + } + + void Connect3() + { + if ( ! ( update || updateAlways ) ) + { + return; + } + + update = false; + + ConnectionPin.Connect( toParent, toPin, fromPin ); + } + + void Connect2() + { + if ( ! ( update || updateAlways ) ) + { + return; + } + + update = false; + + + var targetRotation = fromPin.GetGlobalQuaternion(); + var currentPinRotation = toPin.GetGlobalQuaternion(); + var parentRotation = applyingTarget.GetGlobalQuaternion(); + + + var pinToParent = diff_reverse ? + Math3D.GetQuaternionDifference( currentPinRotation, parentRotation ) : + Math3D.GetQuaternionDifference( parentRotation, currentPinRotation ); + + + + var forward = fromPin.GlobalForward(); + var up = fromPin.GlobalUp(); + + var customRotation = Quaternion.FromEuler( customRotationEulers / 180 * Mathf.Pi ) * pinToParent; + + forwardTarget.GlobalPosition = applyingTarget.GlobalPosition + forward; + + targetPin = targetRotation.AsVector4(); + currentPin = currentPinRotation.AsVector4(); + needed = pinToParent.AsVector4(); + oldApplying = parentRotation.AsVector4(); + applyingTargetApplied = Vector4.One * -1; + pinApplyied = Vector4.One * -1; + + applyingTarget.LookTowards( forward, up, customRotation ); + + var offset = ( fromPin.GlobalPosition - toPin.GlobalPosition ); + applyingTarget.GlobalTranslate( offset ); + + + + } + + void Connect() + { + if ( ! ( update || updateAlways ) ) + { + return; + } + + update = false; + + var eulerRads = eulerRotation / 180 * Mathf.Pi; + + var targetRotation = fromPin.GetGlobalQuaternion(); + + var currentRotation = toPin.GetGlobalQuaternion(); + + + + //toPin.SetGlobalQuaternion( targetRotation ); + + var neededRotation = diff_reverse ? + Math3D.GetQuaternionDifference( currentRotation, targetRotation ) : + Math3D.GetQuaternionDifference( targetRotation, currentRotation ); + + + var oldRotation = applyingTarget.GetGlobalQuaternion(); + + var resultRotation = mult_reverse ? neededRotation * currentRotation : + oldRotation * neededRotation; + + var pinResultRotation = mult_reverse ? neededRotation * currentRotation : + currentRotation * neededRotation; + + RJLog.Log( + "Target:", targetRotation, "\n", + "Current:", currentRotation, "\n", + "Needed:", neededRotation, "\n", + "OldRotation:", oldRotation, "\n", + "Applied:", resultRotation,"\n", + "Pin:", pinResultRotation + ); + + targetPin = targetRotation.AsVector4(); + currentPin = currentRotation.AsVector4(); + needed = neededRotation.AsVector4(); + oldApplying = oldRotation.AsVector4(); + applyingTargetApplied = resultRotation.AsVector4(); + pinApplyied = pinResultRotation.AsVector4(); + + applyingTarget.SetGlobalQuaternion( resultRotation ); + + + + //var positionOffset = fromPin.GlobalPosition - toPin.GlobalPosition; + + //toParent.GlobalPosition += positionOffset; + + //toPin.SetGlobalQuaternion( currentRotation * neededRotation ); + //Math3D.SetGlobalRotation( toPin, targetRotation ); + } + + + + } +} \ No newline at end of file diff --git a/Runtime/Procedural/Parametric/Spline/Spline.cs b/Runtime/Procedural/Parametric/Spline/Spline.cs new file mode 100644 index 0000000..0b156a4 --- /dev/null +++ b/Runtime/Procedural/Parametric/Spline/Spline.cs @@ -0,0 +1,200 @@ + +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 Spline : Node3D +#if TOOLS + , GizmoDrawer +#endif + + { + [Export] + public bool closed = false; + + [Export] + public int editorResolution = 20; + + [Export] + public bool updateAlways = false; + + [Export] + public int xzPathBakeResolution = 50; + + SplineCurve splineCurve; + Vector3 min; + Vector3 max; + + public Box3 GetBounds() + { + GetCurve(); + + return Box3.Create( min, max ); + } + + + public SplineCurve GetCurve() + { + if ( splineCurve == null ) + { + var splinePoints = Nodes.GetDirectChildren( this ); + + splineCurve = new SplineCurveCreator().Create( splinePoints, closed ); + + min = splineCurve.MinPointPosition(); + max = splineCurve.MaxPointPosition(); + } + + return splineCurve; + + } + + + + + + SplineCurve splineCurveXZ; + + public SplineCurve GetCurveXZ() + { + if ( splineCurveXZ == null ) + { + var splinePoints = Nodes.GetDirectChildren( this ); + + splineCurveXZ = new SplineCurveCreator().Create( splinePoints, closed ).CloneForXZ( 0 ); + } + + return splineCurveXZ; + + } + + Path2 xzPath; + Convex2Group convex2Group; + + public Path2 GetXZPath2() + { + if ( xzPath == null ) + { + xzPath = Path2.AsXZFrom( GetCurve(), closed, xzPathBakeResolution ); + } + + return xzPath; + } + + public Convex2Group GetConvex2Group() + { + if ( convex2Group == null ) + { + convex2Group = Convex2Group.FromPath( GetXZPath2() ); + } + + return convex2Group; + } + + public void ClearCurveCache() + { + splineCurve = null; + splineCurveXZ = null; + xzPath = null; + convex2Group = null; + } + +#if TOOLS + + public void DrawGizmo( EditorNode3DGizmoPlugin gizmoPlugin, EditorNode3DGizmo gizmo ) + { + ClearCurveCache(); + + var curve = GetCurve(); + + gizmo.Clear(); + + var linePoints = new List(); + + 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 ); + } + + List cachedPositions = new List(); + + + int renderedResolution = -100; + + public override void _Process( double delta ) + { + var changed = updateAlways; + var index = 0; + + if ( renderedResolution != editorResolution ) + { + changed = true; + } + + Nodes.ForEachDirectChild( this, + sp => + { + if ( changed ) + { + return; + } + + if ( index >= cachedPositions.Count ) + { + changed = true; + return; + } + + if ( cachedPositions[ index ] != sp.GlobalPosition ) + { + changed = true; + } + + index ++; + } + ); + + if ( ! changed ) + { + return; + } + + cachedPositions = Nodes.MapDirectChildren( this, sp => sp.GlobalPosition ); + + // RJLog.Log( "Updating gizmos" ); + UpdateGizmos(); + } +#endif + } +} \ No newline at end of file diff --git a/Runtime/Procedural/Parametric/Spline/SplinePoint.cs b/Runtime/Procedural/Parametric/Spline/SplinePoint.cs new file mode 100644 index 0000000..97a0cdc --- /dev/null +++ b/Runtime/Procedural/Parametric/Spline/SplinePoint.cs @@ -0,0 +1,114 @@ + +using Godot; +using Rokojori; +using System.Collections.Generic; + +namespace Rokojori +{ + public enum SplinePointTangentMode + { + Automatic, + Custom + } + + [System.Serializable] + public class Tangent + { + [Export] + public Node3D node3D; + [Export( PropertyHint.Range, "0,2" )] + public float weight = 0.5f; + } + + [Tool] + [GlobalClass] + public partial class SplinePoint : Node3D +#if TOOLS + , GizmoDrawerWithHandles +#endif + + { + +#if TOOLS + + public void DrawGizmo( EditorNode3DGizmoPlugin gizmoPlugin, EditorNode3DGizmo gizmo ) + { + gizmo.Clear(); + + + var mesh = new SphereMesh(); + var size = 0.1f; + mesh.Radius = size; + mesh.Height = size * 2f; + + var transform = new Transform3D(); + transform.Basis = Basis.Identity; + transform.Origin = Vector3.Zero; + + var material = gizmoPlugin.GetMaterial( "main", gizmo ); + + + gizmo.AddMesh( mesh, material, transform ); + + gizmo.AddHandles( new Vector3[]{ Vector3.Zero }, material, new int[]{} ); + + } + + public string GetHandleName( EditorNode3DGizmo gizmo, int handleId, bool secondary ) + { + return ""; + } + + Vector3 startDragPosition = Vector3.Zero; + public Variant GetHandleValue( EditorNode3DGizmo gizmo, int handleId, bool secondary ) + { + startDragPosition = GlobalPosition; + return Variant.From( GlobalPosition ); + } + + public void SetHandle( EditorNode3DGizmo gizmo, int id, bool secondary, Camera3D camera, Vector2 point ) + { + var xzPlane = new Plane( Vector3.Up, startDragPosition); + var cameraDirection = camera.ProjectLocalRayNormal( point ); + var intersection = xzPlane.IntersectsRay( camera.GlobalPosition, cameraDirection ); + + if ( intersection == null ) + { + return; + } + + GlobalPosition = (Vector3) intersection; + + } + + public void CommitHandle( EditorNode3DGizmo gizmo, int id, bool secondary, Variant restore, bool cancel ) + { + + } + +#endif + + [Export] + public SplinePointTangentMode tangentMode; + + [Export] + public Node3D tangentBefore; + [Export( PropertyHint.Range, "0,2" )] + public float tangentBeforeWeight = 1; + + [Export] + public Node3D tangentNext; + [Export( PropertyHint.Range, "0,2" )] + public float tangentNextWeight = 1; + + [Export( PropertyHint.Range, "0,1")] + public float overshootPrevention = 0.5f; + + [Export( PropertyHint.Range, "0,3")] + public float tangentScale = 1f; + + [Export( PropertyHint.Range, "0,1")] + public float symmetricTangentLength = 0f; + + } +} \ No newline at end of file diff --git a/Runtime/Procedural/Scatter/Discarder/DiscardMode.cs b/Runtime/Procedural/Scatter/Discarder/DiscardMode.cs new file mode 100644 index 0000000..3e97fa7 --- /dev/null +++ b/Runtime/Procedural/Scatter/Discarder/DiscardMode.cs @@ -0,0 +1,21 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; +using System; + + + +namespace Rokojori +{ + public enum DiscardMode + { + DiscardOutside, + DiscardInside + } + + public enum DiscardBooleanCombinator + { + Overwrite, + UseOperator + } +} \ No newline at end of file diff --git a/Runtime/Procedural/Scatter/Discarder/DiscardNoise.cs b/Runtime/Procedural/Scatter/Discarder/DiscardNoise.cs new file mode 100644 index 0000000..83082c1 --- /dev/null +++ b/Runtime/Procedural/Scatter/Discarder/DiscardNoise.cs @@ -0,0 +1,43 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; +using System; + + + +namespace Rokojori +{ + public enum DiscardNoiseOwnPositionMode + { + Ignore, + Add_Global, + Add_Local + } + + [Tool] + [GlobalClass] + public partial class DiscardNoise:Discarder + { + [Export(PropertyHint.Range, "0,1")] + public float insideTreshold = 0.5f; + + [Export] + public float noiseScale = 1; + + [Export] + public Vector3 noiseOffset = Vector3.Zero; + + [Export] + public ScattererOwnPositionMode ownPositionMode = ScattererOwnPositionMode.Ignore; + + public override bool IsInside( Vector3 position ) + { + var offset = noiseOffset + ScattererOwnPosition.ComputeOffset( ownPositionMode, this ); + + var value = Noise.Perlin( ( position - offset ) * noiseScale ); + + return value < insideTreshold; + } + + } +} \ No newline at end of file diff --git a/Runtime/Procedural/Scatter/Discarder/DiscardSphere.cs b/Runtime/Procedural/Scatter/Discarder/DiscardSphere.cs new file mode 100644 index 0000000..c763a1c --- /dev/null +++ b/Runtime/Procedural/Scatter/Discarder/DiscardSphere.cs @@ -0,0 +1,24 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; +using System; + + + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class DiscardSphere:Discarder + { + [Export] + public float radius; + + public override bool IsInside( Vector3 position ) + { + var localPoint = ToLocal( position ); + return localPoint.LengthSquared() < radius * radius; + } + + } +} \ No newline at end of file diff --git a/Runtime/Procedural/Scatter/Discarder/DiscardSpline.cs b/Runtime/Procedural/Scatter/Discarder/DiscardSpline.cs new file mode 100644 index 0000000..12d69fd --- /dev/null +++ b/Runtime/Procedural/Scatter/Discarder/DiscardSpline.cs @@ -0,0 +1,70 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; +using System; + + + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class DiscardSpline:Discarder + { + [Export] + public Spline spline; + + [Export] + public float size; + + [Export] + public bool xzOnly = false; + + [Export] + public bool asFilledXZPath = false; + + public override bool IsInside( Vector3 position ) + { + if ( asFilledXZPath ) + { + var bounds = spline.GetBounds().AsXZBox2(); + bounds.GrowRelativeToSize( 1/3f ); + + var p2 = Math2D.XZ( position ); + + if ( bounds.ContainsPoint( p2 ) ) + { + //var path = spline.GetXZPath2(); + var cg = spline.GetConvex2Group(); + var insideGroup = cg.ContainsPoint( p2 ); + // var insidePath = insideGroup || path.PointInPath( p2, fastButUnreliableFilledXZPathChecks, false ); + + if ( insideGroup ) + { + return true; + } + } + } + + if ( size <= 0 ) + { + return false; + } + + var curve = xzOnly ? spline.GetCurveXZ() : spline.GetCurve(); + + var testPosition = position; + + if ( xzOnly ) + { + testPosition.Y = 0; + } + + var distance = curve.GetDistanceTo( testPosition ); + + return distance < size; + + } + + } +} \ No newline at end of file diff --git a/Runtime/Procedural/Scatter/Discarder/Discarder.cs b/Runtime/Procedural/Scatter/Discarder/Discarder.cs new file mode 100644 index 0000000..e22a226 --- /dev/null +++ b/Runtime/Procedural/Scatter/Discarder/Discarder.cs @@ -0,0 +1,58 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; +using System; + + + +namespace Rokojori +{ + [Tool] + public partial class Discarder:Scatterer + { + [Export] + public DiscardMode mode; + + [Export] + public DiscardBooleanCombinator combinator; + + [Export] + public BooleanLogicBinaryOperator discordOperator; + + + public virtual bool IsInside( Vector3 position ) + { + return true; + } + + protected override List _Scatter( List points ) + { + points.ForEach( p => + { + var inside = IsInside( p.globalPosition ); + var outside = ! inside; + + var visible = DiscardMode.DiscardInside == mode && outside || + DiscardMode.DiscardOutside == mode && inside; + + + if ( DiscardBooleanCombinator.Overwrite == combinator ) + { + p.visible = visible; + } + else + { + var result = BooleanLogic.Binary( discordOperator, p.visible, visible ); + p.visible = result; + } + + } + + ); + + return points; + } + + + } +} \ No newline at end of file diff --git a/Runtime/Procedural/Scatter/Discarder/DiscarderList.cs b/Runtime/Procedural/Scatter/Discarder/DiscarderList.cs new file mode 100644 index 0000000..f93801f --- /dev/null +++ b/Runtime/Procedural/Scatter/Discarder/DiscarderList.cs @@ -0,0 +1,72 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; +using System; + + + +namespace Rokojori +{ + public enum DiscardListMode + { + At_Least_Inside_One, + Only_Inside_One, + Must_Be_Inside_All + } + + [Tool] + [GlobalClass] + public partial class DiscarderList:Discarder + { + [Export] + public DiscardListMode listMode; + + [Export] + public bool childrenNeedToBeVisible = true; + [Export] + public bool childrenNeedToBeProcessing = false; + + public override bool IsInside( Vector3 position ) + { + var inside = 0; + + var numNodes = 0; + + Nodes.ForEachDirectChild( this, + d => + { + if ( ! IsChildEnabled( d, childrenNeedToBeVisible, childrenNeedToBeProcessing ) ) + { + return; + } + + if ( d.IsInside( position ) ) + { + inside ++; + } + + numNodes++; + } + ); + + if ( DiscardListMode.At_Least_Inside_One == listMode ) + { + return inside > 0; + } + else if ( DiscardListMode.Only_Inside_One == listMode ) + { + return inside == 1; + } + else if ( DiscardListMode.Must_Be_Inside_All == listMode ) + { + return numNodes == inside; + } + + return false; + + + } + + + } +} \ No newline at end of file diff --git a/Runtime/Procedural/Scatter/Generators/GenerateFence.cs b/Runtime/Procedural/Scatter/Generators/GenerateFence.cs new file mode 100644 index 0000000..579e01d --- /dev/null +++ b/Runtime/Procedural/Scatter/Generators/GenerateFence.cs @@ -0,0 +1,111 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; +using System; + + + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class GenerateFence:GeneratorScatterer + { + [Export] + public Spline spline; + + [Export] + public bool xzOnly = true; + + [Export] + public float sampleDensity = 1; + + [Export] + public GeneratorEntry segment; + [Export] + public float segmentLength; + + [Export] + public GeneratorEntry pole; + [Export] + public float poleLength; + + [Export] + public GeneratorEntry startPole; + [Export] + public float startPoleLength; + + [Export] + public GeneratorEntry endPole; + [Export] + public float endPoleLength; + + SplineCurve last; + LerpCurve3 equalSpacedCurve; + + protected override List _Scatter( List points ) + { + CreateWeights(); + var curve = spline.GetCurve(); + + if ( last != curve ) + { + last = curve; + equalSpacedCurve = LerpCurve3.SampledEqually( curve, sampleDensity ); + } + + var curveLength = equalSpacedCurve.ComputeLength( 0 ); + var numPoints = Mathf.CeilToInt( curveLength * sampleDensity ); + + + var id = 0; + + for ( int i = 0; i < numPoints; i++ ) + { + var t = i / (float) ( numPoints - 1 ); + + var position = equalSpacedCurve.SampleAt( t ); + var rawDirection = equalSpacedCurve.GradientAt( t, 1f / 100f ); + var direction = rawDirection; + var length = direction.Length(); + + + if ( length != 0 ) + { + direction /= length; + } + + /*RJLog.Log( "i:", i, "t:", t, + "P:", position, + "L:", length, + "RD:", rawDirection, + "D:", direction + );*/ + + var point = CreatePoint( points, id, position.X, position.Y, position.Z ); + + point.rotation = Math3D.LookRotation( direction, Vector3.Up ); + id = point.creatorID + 1; + + } + + + return points; + } + + ScatterPoint CreatePoint( List points, int id, float x, float y, float z ) + { + var p = new ScatterPoint( this, id ); + + p.position = new Vector3( x, y, z ); + p.visible = ! setDiscarded; + p.seed = Noise.CreateSeed( p.position ); + + AssginSceneAndContainer( p ); + + points.Add( p ); + + return p; + } + } +} \ No newline at end of file diff --git a/Runtime/Procedural/Scatter/Generators/GenerateInBox.cs b/Runtime/Procedural/Scatter/Generators/GenerateInBox.cs new file mode 100644 index 0000000..3332ad7 --- /dev/null +++ b/Runtime/Procedural/Scatter/Generators/GenerateInBox.cs @@ -0,0 +1,104 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; +using System; + + + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class GenerateInBox:GeneratorScatterer + { + [Export] + public Node3D cornerA; + [Export] + public Node3D cornerB; + + [Export] + public bool xzOnly = true; + [Export] + public float density = 10; + + [Export] + public bool snapToWorldGrid = false; + + protected override List _Scatter( List points ) + { + CreateWeights(); + + var minPosition = Math3D.MinPosition( cornerA, cornerB ); + var maxPosition = Math3D.MaxPosition( cornerA, cornerB ); + + if ( snapToWorldGrid ) + { + var snapping = Vector3.One / density; + minPosition = Math3D.SnapCeiled( minPosition, snapping ); + maxPosition = Math3D.SnapFloored( maxPosition, snapping ); + } + + var pointsX = Mathf.CeilToInt( ( maxPosition.X - minPosition.X ) * density ); + var pointsY = Mathf.CeilToInt( ( maxPosition.Y - minPosition.Y ) * density ); + var pointsZ = Mathf.CeilToInt( ( maxPosition.Z - minPosition.Z ) * density ); + + var id = 0; + + if ( xzOnly ) + { + var y = ( cornerA.GlobalPosition.Y + cornerB.GlobalPosition.Y ) / 2f; + + for ( int x = 0; x < pointsX; x++ ) + { + var xPosition = MathX.Map( x, 0, pointsX - 1, minPosition.X, maxPosition.X ); + + for ( int z = 0; z < pointsZ; z++ ) + { + var zPosition = MathX.Map( z, 0, pointsZ- 1, minPosition.Z, maxPosition.Z ); + + id = CreatePoint( points, id, xPosition, y, zPosition ); + } + } + } + else + { + + for ( int x = 0; x < pointsX; x++ ) + { + var xPosition = MathX.Map( x, 0, pointsX - 1, minPosition.X, maxPosition.X ); + + for ( int y = 0; y < pointsY; y++ ) + { + var yPosition = MathX.Map( y, 0, pointsY- 1, minPosition.Y, maxPosition.Y ); + + for ( int z = 0; z < pointsZ; z++ ) + { + var zPosition = MathX.Map( z, 0, pointsZ- 1, minPosition.Z, maxPosition.Z ); + + id = CreatePoint( points, id, xPosition, yPosition, zPosition ); + } + } + } + } + + return points; + } + + int CreatePoint( List points, int id, float x, float y, float z ) + { + var p = new ScatterPoint( this, id++ ); + + p.position = new Vector3( x, y, z ); + p.visible = ! setDiscarded; + p.seed = Noise.CreateSeed( p.position ); + + AssginSceneAndContainer( p ); + + points.Add( p ); + + return id; + } + + + } +} \ No newline at end of file diff --git a/Runtime/Procedural/Scatter/Generators/GenerateOnSpline.cs b/Runtime/Procedural/Scatter/Generators/GenerateOnSpline.cs new file mode 100644 index 0000000..4571a06 --- /dev/null +++ b/Runtime/Procedural/Scatter/Generators/GenerateOnSpline.cs @@ -0,0 +1,91 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; +using System; + + + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class GenerateOnSpline:GeneratorScatterer + { + [Export] + public Spline spline; + + [Export] + public bool xzOnly = true; + + [Export] + public float density = 1; + + SplineCurve last; + LerpCurve3 equalSpacedCurve; + + protected override List _Scatter( List points ) + { + CreateWeights(); + var curve = spline.GetCurve(); + + if ( last != curve ) + { + last = curve; + equalSpacedCurve = LerpCurve3.SampledEqually( curve, density ); + } + + var curveLength = equalSpacedCurve.ComputeLength( 0 ); + var numPoints = Mathf.CeilToInt( curveLength * density ); + + + var id = 0; + + for ( int i = 0; i < numPoints; i++ ) + { + var t = i / (float) ( numPoints - 1 ); + + var position = equalSpacedCurve.SampleAt( t ); + var rawDirection = equalSpacedCurve.GradientAt( t, 1f / 100f ); + var direction = rawDirection; + var length = direction.Length(); + + + if ( length != 0 ) + { + direction /= length; + } + + /*RJLog.Log( "i:", i, "t:", t, + "P:", position, + "L:", length, + "RD:", rawDirection, + "D:", direction + );*/ + + var point = CreatePoint( points, id, position.X, position.Y, position.Z ); + + point.rotation = Math3D.LookRotation( direction, Vector3.Up ); + id = point.creatorID + 1; + + } + + + return points; + } + + ScatterPoint CreatePoint( List points, int id, float x, float y, float z ) + { + var p = new ScatterPoint( this, id ); + + p.position = new Vector3( x, y, z ); + p.visible = ! setDiscarded; + p.seed = Noise.CreateSeed( p.position ); + + AssginSceneAndContainer( p ); + + points.Add( p ); + + return p; + } + } +} \ No newline at end of file diff --git a/Runtime/Procedural/Scatter/Generators/GeneratePinBoundary.cs b/Runtime/Procedural/Scatter/Generators/GeneratePinBoundary.cs new file mode 100644 index 0000000..3d09ade --- /dev/null +++ b/Runtime/Procedural/Scatter/Generators/GeneratePinBoundary.cs @@ -0,0 +1,57 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; +using System; + + + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class GeneratePinBoundary:GeneratorScatterer + { + [Export] + public Spline spline; + + [Export] + public bool xzOnly = true; + + [Export] + public float sampleDensity; + + + SplineCurve last; + LerpCurve3 equalSpacedCurve; + + protected override List _Scatter( List points ) + { + var curve = spline.GetCurve(); + + if ( last != curve ) + { + last = curve; + equalSpacedCurve = LerpCurve3.SampledEqually( curve, sampleDensity ); + } + + return points; + } + + int CreatePoint( List points, int id, float x, float y, float z ) + { + var p = new ScatterPoint( this, id++ ); + + p.position = new Vector3( x, y, z ); + p.visible = ! setDiscarded; + p.seed = Noise.CreateSeed( p.position ); + + AssginSceneAndContainer( p ); + + points.Add( p ); + + return id; + } + + + } +} \ No newline at end of file diff --git a/Runtime/Procedural/Scatter/Generators/GeneratorEntry.cs b/Runtime/Procedural/Scatter/Generators/GeneratorEntry.cs new file mode 100644 index 0000000..146cda7 --- /dev/null +++ b/Runtime/Procedural/Scatter/Generators/GeneratorEntry.cs @@ -0,0 +1,24 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; +using System; + + + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class GeneratorEntry:Node3D + { + [Export] + public PackedScene packedScene; + + [Export] + public float probability = 1; + + [Export] + public Node3D container; + + } +} \ No newline at end of file diff --git a/Runtime/Procedural/Scatter/Generators/GeneratorScatterer.cs b/Runtime/Procedural/Scatter/Generators/GeneratorScatterer.cs new file mode 100644 index 0000000..1320925 --- /dev/null +++ b/Runtime/Procedural/Scatter/Generators/GeneratorScatterer.cs @@ -0,0 +1,88 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; +using System; + + + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class GeneratorScatterer:Scatterer + { + [Export] + public bool setDiscarded = true; + + [Export] + public PackedScene packedScene; + + [Export] + public Node3D container; + + [Export] + public bool useGeneratorEntryFromChildren = true; + + [Export] + public float childGeneratorNoiseScale = 1; + + [Export] + public Vector3 childGeneratorNoiseOffset = Vector3.Zero; + + protected List childGenerators = new List(); + protected List childGeneratorWeights = new List(); + + public void CreateWeights() + { + if ( ! useGeneratorEntryFromChildren ) + { + return; + } + + childGenerators = Nodes.GetDirectChildren( this ); + childGeneratorWeights = Lists.Map( childGenerators, c => c.probability ); + + var sum = 0f; + childGeneratorWeights.ForEach( w => sum += w ); + childGeneratorWeights = Lists.Map( childGeneratorWeights, c => c /= sum ); + } + + public void AssginSceneAndContainer( ScatterPoint p ) + { + if ( ! useGeneratorEntryFromChildren ) + { + p.scene = packedScene; + p.parent = container; + } + else + { + var value = Noise.Perlin( ( p.position + childGeneratorNoiseOffset ) * childGeneratorNoiseScale ); + var index = _FindElementIndexWithWeights( childGeneratorWeights, value ); + var gs = childGenerators[ index ]; + + p.scene = gs.packedScene != null ? gs.packedScene : packedScene; + p.parent = gs.container != null ? gs.container : container; + } + + } + + int _FindElementIndexWithWeights( List weights, float value ) + { + var limit = 0f; + + for ( int i = 0; i < weights.Count; i++ ) + { + var before = limit; + limit += weights[ i ]; + + if ( before <= value && value < limit ) + { + return i; + } + } + + return weights.Count - 1; + } + + } +} \ No newline at end of file diff --git a/Runtime/Procedural/Scatter/ScatterList.cs b/Runtime/Procedural/Scatter/ScatterList.cs new file mode 100644 index 0000000..a546254 --- /dev/null +++ b/Runtime/Procedural/Scatter/ScatterList.cs @@ -0,0 +1,72 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; +using System; + + + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class ScatterList:Scatterer + { + [Export] + public bool childrenNeedToBeVisible = true; + [Export] + public bool childrenNeedToBeProcessing = false; + + [Export] + public bool clearChildContainers = false; + + + public override void _Process( double delta ) + { + if ( clearContainers ) + { + clearContainers = false; + ClearContainers(); + } + + if ( clearChildContainers ) + { + clearChildContainers = false; + Nodes.ForEachDirectChild( this, s => s.ClearContainers() ); + } + + if ( ! ( update || updateAlways ) ) + { + return; + } + + update = false; + + ScatterAndInstantiatePoints(); + } + + protected override List _Scatter( List points ) + { + var output = points; + + Nodes.ForEachDirectChild( this, + ( s )=> + { + if ( ! IsChildEnabled( s ) ) + { + return; + } + + output = s.Scatter( output ); + } + ); + + return output; + } + + bool IsChildEnabled( Scatterer s ) + { + return IsChildEnabled( s, childrenNeedToBeVisible, childrenNeedToBeProcessing ); + } + + } +} \ No newline at end of file diff --git a/Runtime/Procedural/Scatter/ScatterPoint.cs b/Runtime/Procedural/Scatter/ScatterPoint.cs new file mode 100644 index 0000000..7f2be0e --- /dev/null +++ b/Runtime/Procedural/Scatter/ScatterPoint.cs @@ -0,0 +1,90 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; +using System; + + + +namespace Rokojori +{ + public class ScatterPoint + { + + protected Scatterer _creator; + public Scatterer creator => _creator; + protected int _creatorID; + public int creatorID => _creatorID; + + public ScatterPoint( Scatterer creator, int id ) + { + this._creator = creator; + this._creatorID = id; + } + + + public bool visible = false; + public Vector3 position = Vector3.Zero; + public bool useGlobalPosition = true; + public Quaternion rotation = Quaternion.Identity; + public bool useGlobalRotation = true; + public Vector3 scale = Vector3.One; + public PackedScene scene; + public Node3D parent; + public int seed; + + public Vector3 globalPosition => useGlobalPosition || parent == null ? + position : parent.ToGlobal( position ); + + + public bool CanBeReusedBy( ScatterPoint other ) + { + return parent == other.parent && scene == other.scene; + } + + public void UpdateInstantiated( Node3D node ) + { + ApplyTransform( node ); + } + + + public Node3D Instantiate() + { + if ( ! visible || scene == null || parent == null ) + { + return null; + } + + var node3D = scene.Instantiate(); + + parent.AddChild( node3D ); + node3D.Owner = parent.Owner; + + ApplyTransform( node3D ); + + return node3D; + } + + void ApplyTransform( Node3D node3D ) + { + if ( useGlobalPosition ) + { + node3D.GlobalPosition = position; + } + else + { + node3D.Position = position; + } + + if ( useGlobalRotation ) + { + Math3D.SetGlobalRotationTo( node3D, rotation ); + } + else + { + node3D.Quaternion = rotation; + } + + node3D.Scale = scale; + } + } +} \ No newline at end of file diff --git a/Runtime/Procedural/Scatter/Scatterer.cs b/Runtime/Procedural/Scatter/Scatterer.cs new file mode 100644 index 0000000..521018b --- /dev/null +++ b/Runtime/Procedural/Scatter/Scatterer.cs @@ -0,0 +1,260 @@ +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 Scatterer:Node3D + { + [Export] + public bool update = false; + [Export] + public bool updateAlways = false; + [Export] + public Node3D[] containersToClearNodes; + [Export] + public bool clearContainers = false; + + [Export] + public bool removeDiscarded = false; + + [Export] + public int READ_ONLY_createdPoints = 0; + [Export] + public int READ_ONLY_instantiatedPoints = 0; + [Export] + public int READ_ONLY_reusedPoints = 0; + [Export] + public int READ_ONLY_remappedPoints = 0; + + + public override void _Process( double delta ) + { + if ( clearContainers ) + { + clearContainers = false; + ClearContainers(); + } + + if ( ! ( update || updateAlways ) ) + { + return; + } + + update = false; + + ScatterAndInstantiatePoints(); + } + + public void ClearContainers() + { + Arrays.ForEach( containersToClearNodes, + c => + { + Nodes.RemoveAndDeleteChildren( c ); + } + ); + } + + class InstantiatedScatterPoint + { + public string hash; + public ScatterPoint scatterPoint; + public Node3D output; + } + + Dictionary _instantiatedPoints = new Dictionary(); + + Dictionary _scattererIDs = new Dictionary(); + + int GetScattererID( Scatterer s ) + { + if ( ! _scattererIDs.ContainsKey( s ) ) + { + _scattererIDs[ s ] = _scattererIDs.Count; + } + + return _scattererIDs[ s ]; + } + + string GetScatterPointHash( ScatterPoint p ) + { + var scatterID = GetScattererID( p.creator ) + ":" + p.creatorID; + + return scatterID; + } + + public void ScatterAndInstantiatePoints() + { + if ( _instantiatedPoints.Count == 0 ) + { + ClearContainers(); + } + + var points = Scatter( new List() ); + + READ_ONLY_createdPoints = points.Count; + READ_ONLY_instantiatedPoints = 0; + READ_ONLY_reusedPoints = 0; + READ_ONLY_remappedPoints = 0; + + var usedPoints = new HashSet(); + + points.RemoveAll( p => ! p.visible ); + + points.ForEach( p => + { + var hash = GetScatterPointHash( p ); + + if ( ! CanReuse( hash, p ) ) + { + return; + } + + var existing = _instantiatedPoints[ hash ]; + existing.scatterPoint = p; + existing.scatterPoint.UpdateInstantiated( existing.output ); + + usedPoints.Add( hash ); + + READ_ONLY_reusedPoints ++; + return; + + } + ); + + var unused = new DictionaryList(); + + foreach ( var vk in _instantiatedPoints ) + { + var ip = vk.Value; + + if ( usedPoints.Contains( ip.hash ) ) + { + continue; + } + + unused.Add( ip.scatterPoint.scene, ip ); + } + + points.ForEach( p => + { + var hash = GetScatterPointHash( p ); + + if ( usedPoints.Contains( hash ) ) + { + return; + } + + if ( unused.ContainsKey( p.scene ) ) + { + var unusedPoint = unused[ p.scene ].Find( ip => ip.scatterPoint.CanBeReusedBy( p ) ); + + if ( unusedPoint != null ) + { + unused.Remove( p.scene, unusedPoint ); + unusedPoint.scatterPoint = p; + unusedPoint.scatterPoint.UpdateInstantiated( unusedPoint.output ); + + usedPoints.Add( hash ); + + READ_ONLY_remappedPoints ++; + return; + } + } + + var instantiatedOutput = p.Instantiate(); + + if ( instantiatedOutput == null ) + { + return; + } + + var ip = new InstantiatedScatterPoint(); + ip.hash = hash; + ip.output = instantiatedOutput; + ip.scatterPoint = p; + + if ( _instantiatedPoints.ContainsKey( hash ) ) + { + Nodes.RemoveAndDelete( _instantiatedPoints[ hash ].output ); + } + + _instantiatedPoints[ hash ] = ip; + usedPoints.Add( hash ); + + READ_ONLY_instantiatedPoints++; + } + ); + + + Dictionaries.RemoveAll( + _instantiatedPoints, ( k, v ) => + { + if ( usedPoints.Contains( k ) ) + { + return false; + } + + Nodes.RemoveAndDelete( v.output ); + + return true; + } + ); + + } + + bool CanReuse( string hash, ScatterPoint sp ) + { + if ( ! _instantiatedPoints.ContainsKey( hash ) ) + { + return false; + } + + var existing = _instantiatedPoints[ hash ]; + + var canBeReused = existing.scatterPoint.CanBeReusedBy( sp ); + + return canBeReused; + } + + public List Scatter( List points ) + { + var returnedPoints = _Scatter( points ); + + if ( removeDiscarded ) + { + returnedPoints.RemoveAll( p => ! p.visible ); + } + + return returnedPoints; + } + + protected bool IsChildEnabled( Scatterer s, bool childrenNeedToBeVisible, bool childrenNeedToBeProcessing ) + { + var enabled = true; + + if ( childrenNeedToBeVisible ) + { + enabled = enabled && s.Visible; + } + + if ( childrenNeedToBeProcessing ) + { + enabled = enabled && ( s.ProcessMode != ProcessModeEnum.Disabled ); + } + + return enabled; + } + + protected virtual List _Scatter( List points ) + { + return points; + } + } +} \ No newline at end of file diff --git a/Runtime/Procedural/Scatter/ScattererOwnPositionMode.cs b/Runtime/Procedural/Scatter/ScattererOwnPositionMode.cs new file mode 100644 index 0000000..afa08f7 --- /dev/null +++ b/Runtime/Procedural/Scatter/ScattererOwnPositionMode.cs @@ -0,0 +1,36 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; +using System; + + + +namespace Rokojori +{ + public enum ScattererOwnPositionMode + { + Ignore, + Add_Global, + Add_Local + } + + public class ScattererOwnPosition + { + public static Vector3 ComputeOffset( ScattererOwnPositionMode mode, Node3D n ) + { + var offset = Vector3.Zero; + + if ( ScattererOwnPositionMode.Add_Global == mode ) + { + offset += n.GlobalPosition; + } + + if ( ScattererOwnPositionMode.Add_Local == mode ) + { + offset += n.Position; + } + + return offset; + } + } +} \ No newline at end of file diff --git a/Runtime/Procedural/Scatter/Transform/ProjectOnColliders.cs b/Runtime/Procedural/Scatter/Transform/ProjectOnColliders.cs new file mode 100644 index 0000000..c92fb60 --- /dev/null +++ b/Runtime/Procedural/Scatter/Transform/ProjectOnColliders.cs @@ -0,0 +1,121 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; +using System; + + + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class ProjectOnColliders:Scatterer + { + + + + [ExportGroup("Ray")] + [Export] + public Vector3 rayDirection = Vector3.Down; + + [Export] + public float rayLength = 10; + + [Export] + public Vector3 rayOffset = Vector3.Zero; + + [ExportGroup("Normal")] + [Export(PropertyHint.Range, "0,1")] + public float rotationAlignment = 1; + + [Export] + public bool discardSteepNormals = true; + + [Export] + public Vector3 steepNormalDirection = Vector3.Down; + + [Export(PropertyHint.Range, "0,180")] + public float steepNormalRemovalAngle = 0; + + [ExportGroup("Collisions")] + [Export( PropertyHint.Layers3DPhysics)] + public uint collisionLayer = 0; + + [Export] + public bool collideWithAreas = true; + + [Export] + public bool collideWithBodies = true; + + [Export] + public bool hitFromInside = true; + + [Export] + public bool hitBackFaces = true; + + protected override List _Scatter( List points ) + { + var world = GetWorld3D(); + var ray = new PhysicsRayQueryParameters3D(); + var direction = rayDirection.Normalized(); + var normalizedSteepNormalDirection = steepNormalDirection.Normalized(); + + var steepNormalRemovalTreshold = Mathf.Cos( Mathf.DegToRad( steepNormalRemovalAngle ) ); + + points.ForEach( + p => + { + if ( ! p.visible ) + { + return; + } + + ray.From = p.globalPosition; + ray.To = ray.From + direction * rayLength; + + var collisionData = CollisionData.FindCollision( world, ray, + ( cd ) => + { + if ( ! CollisionData.HasCollisionLayer( cd.collider ) ) + { + return false; + } + + var colliderCollisionLayer = CollisionData.GetCollisionLayer( cd.collider ); + var collides = ( colliderCollisionLayer & collisionLayer ) != 0; + // RJLog.Log( "Collision With:", "collides", collides, "colliderLayer:", colliderCollisionLayer, "own", collisionLayer, "name:", cd.collider.Name ); + return collides; + + } + ); + + p.visible = collisionData != null; + + if ( ! p.visible ) + { + return; + } + + if ( discardSteepNormals ) + { + var dot = normalizedSteepNormalDirection.Dot( collisionData.normal ); + + if ( dot >= steepNormalRemovalTreshold ) + { + p.visible = false; + return; + } + } + + p.position = collisionData.position; + var alginedRotation =Math3D.AlignUp( p.rotation, collisionData.normal ); + p.rotation = p.rotation.Slerp( alginedRotation, rotationAlignment ); + + } + + ); + + return points; + } + } +} \ No newline at end of file diff --git a/Runtime/Procedural/Scatter/Transform/RandomizeTransform.cs b/Runtime/Procedural/Scatter/Transform/RandomizeTransform.cs new file mode 100644 index 0000000..4efb476 --- /dev/null +++ b/Runtime/Procedural/Scatter/Transform/RandomizeTransform.cs @@ -0,0 +1,91 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; +using System; + + + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class RandomizeTransform:Scatterer + { + [ExportGroup( "All" )] + [Export] + public float noiseScale = 1; + [Export] + public ScattererOwnPositionMode ownPositionMode = ScattererOwnPositionMode.Ignore; + + [ExportGroup( "Position" )] + [Export] + public Vector3 positionOffset = Vector3.Zero; + [Export] + public float positionOffsetScale = 1; + [Export] + public float positionNoiseScale = 1; + [Export] + public Vector3 positionNoiseOffset = Vector3.Zero; + + [ExportGroup( "Rotation" )] + [Export] + public Vector3 rotationOffset = Vector3.Zero; + [Export] + public float rotationOffsetScale = 1; + [Export] + public float rotationNoiseScale = 1; + [Export] + public Vector3 rotationNoiseOffset = Vector3.Zero; + + [ExportGroup( "Scale" )] + + [Export] + public float uniformScaleMultiplyMin = 1; + [Export] + public float uniformScaleMultiplyMax = 1; + + + [Export] + public Vector3 componentScaleMinMultiply = Vector3.One; + [Export] + public Vector3 componentScaleMaxMultiply = Vector3.One; + [Export] + public float scaleNoiseScale = 1; + [Export] + public Vector3 scaleNoiseOffset = Vector3.Zero; + + + protected override List _Scatter( List points ) + { + var output = points; + var ownOffset = ScattererOwnPosition.ComputeOffset( ownPositionMode, this ); + + points.ForEach( + p => + { + + var point = p.position; + + var positionPerlin = ( point + positionNoiseOffset + ownOffset ) * ( noiseScale * positionNoiseScale ); + var rotationPerlin = ( point + rotationNoiseOffset + ownOffset ) * ( noiseScale * rotationNoiseScale ); + var scalePerlin = ( point + scaleNoiseOffset + ownOffset ) * ( noiseScale * scaleNoiseScale ); + + var positionRandom = Noise.PerlinPolar3( positionPerlin ); + var rotationRandom = Noise.PerlinPolar3( rotationPerlin ); + var scaleRandom = Noise.Perlin3( scalePerlin ); + var uniformScaleRandom = Noise.Perlin( scalePerlin + new Vector3( 100002, -10002, 1000 ) ); + + p.position += positionOffset * positionRandom * positionOffsetScale; + p.rotation *= Quaternion.FromEuler( rotationOffset * rotationRandom * rotationOffsetScale ); + p.scale *= Math3D.LerpComponents( componentScaleMinMultiply, componentScaleMaxMultiply, scaleRandom ); + p.scale *= Mathf.Lerp( uniformScaleMultiplyMin, uniformScaleMultiplyMax, uniformScaleRandom ); + + } + ); + + + + return output; + } + } +} \ No newline at end of file diff --git a/Runtime/Random/GodotRandom.cs b/Runtime/Random/GodotRandom.cs index e69de29..5d30641 100644 --- a/Runtime/Random/GodotRandom.cs +++ b/Runtime/Random/GodotRandom.cs @@ -0,0 +1,38 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; +using System; + + + +namespace Rokojori +{ + public class GodotRandom:RandomEngine + { + static GodotRandom _instance; + + public static GodotRandom Get() + { + if ( _instance != null ) + { + return _instance; + } + + _instance = new GodotRandom(); + return _instance; + } + + RandomNumberGenerator rng; + + public GodotRandom() + { + rng = new RandomNumberGenerator(); + rng.Randomize(); + } + + public override float Next() + { + return rng.Randf(); + } + } +} \ No newline at end of file diff --git a/Runtime/Random/Noise.cs b/Runtime/Random/Noise.cs new file mode 100644 index 0000000..66eba5e --- /dev/null +++ b/Runtime/Random/Noise.cs @@ -0,0 +1,130 @@ +using System.Collections; +using System.Collections.Generic; +using System; +using Godot; + +namespace Rokojori +{ + + public class Noise + { + public static int CreateSeed( Vector3 p ) + { + var x = p.X; + var y = p.Y; + var z = p.Z; + + x += 20422.4225f; + y += 12353.299336f; + z += 2139.4f; + + x += 3254f; + x = x % 157.3245f; + + y += 8994f; + y = y % 17.33636634f; + + z += 3.4f; + z = z % 15.99925f; + + + var seed = 35891.323903f + x * 1000 + y * 11000 + z*20935; + seed = seed % 0.12492f; + seed = Mathf.Cos( seed * 2.235f ) + Mathf.Sin( z * 2102491054 ) + Mathf.Cos( y*48924.9042f); + + var intSeed = Mathf.RoundToInt( seed * 20898977 ); + intSeed = Mathf.RoundToInt( intSeed + x * 42.3f -z *235 ); + intSeed = intSeed << 2; + seed += y % z; + seed += intSeed; + seed = seed % 0.0012f; + seed += y * 100203.3f; + seed -= z * 0122f; + + seed += x * 13.35f; + seed = seed % ( y *32535 + z + 0.1221f ); + seed -= x * 2f; + seed = seed % (x * 0.02f ); + seed += 2005235; + seed = seed % 1157.5f; + + return Mathf.RoundToInt( seed * 10000 ); + } + + + public static float Random( Vector2 v ) + { + var d = Math2D.Dot( v, new Vector2( 12.9898f, 78.233f ) ); + return MathX.Fract( Mathf.Sin ( d ) * 43758.5453123f ); + } + + public static float Random( Vector3 v ) + { + var d = Math3D.Dot( v, new Vector3( 12.9898f, 78.233f, 23.7219f ) ); + return MathX.Fract( Mathf.Sin ( d ) * 43758.5453123f ); + } + + public static float Perlin( Vector2 position ) + { + var index = position.Floor(); + var lerp = Math2D.Fract( position ); + + var uv = Math2D.SmoothStep( Vector2.Zero, Vector2.One, lerp ); + + var a = Random( index ); + var b = Random( index + new Vector2( 1.0f, 0.0f ) ); + var c = Random( index + new Vector2( 0.0f, 1.0f ) ); + var d = Random( index + new Vector2( 1.0f, 1.0f ) ); + + return MathX.Sample( a, b, c, d, uv ); + } + + public static float Perlin( Vector3 position ) + { + var index = position.Floor(); + var lerp = Math3D.Fract( position ); + + var uvw = Math3D.SmoothStep( Vector3.Zero, Vector3.One, lerp ); + + var ba = Random( index ); + var bb = Random( index + new Vector3( 1.0f, 0.0f, 0.0f ) ); + var bc = Random( index + new Vector3( 0.0f, 1.0f, 0.0f ) ); + var bd = Random( index + new Vector3( 1.0f, 1.0f, 0.0f ) ); + + var ta = Random( index + new Vector3( 0.0f, 0.0f, 1.0f ) ); + var tb = Random( index + new Vector3( 1.0f, 0.0f, 1.0f ) ); + var tc = Random( index + new Vector3( 0.0f, 1.0f, 1.0f ) ); + var td = Random( index + new Vector3( 1.0f, 1.0f, 1.0f ) ); + + return MathX.Sample( ba, bb, bc, bd, ta, tb, tc, td, uvw ); + + } + + public static float PerlinPolar( Vector3 position ) + { + return Perlin( position ) * 2 - 1; + } + + static readonly Vector3 Perlin3OffsetY = new Vector3( 31789.23f, -2101.23f, 912990.21f ); + static readonly Vector3 Perlin3OffsetZ = new Vector3( 91290.0340f, 12921, -99122.21424f ); + + public static Vector3 Perlin3( Vector3 position, Vector3? offsetY =null, Vector3? offsetZ = null ) + { + var offY = ( Vector3 ) ( offsetY == null ? Perlin3OffsetY : offsetY ); + var offZ = ( Vector3 ) ( offsetZ == null ? Perlin3OffsetZ : offsetZ ); + + var x = Perlin( position ); + var y = Perlin( position + offY ); + var z = Perlin( position + offZ ); + + return new Vector3( x, y, z ); + + } + + public static Vector3 PerlinPolar3( Vector3 position, Vector3? offsetY =null, Vector3? offsetZ = null ) + { + return Perlin3( position, offsetY, offsetZ ) * 2 - Vector3.One; + } + } + +} \ No newline at end of file diff --git a/Runtime/Random/RandomEngine.cs b/Runtime/Random/RandomEngine.cs index 8db51bd..4d6e678 100644 --- a/Runtime/Random/RandomEngine.cs +++ b/Runtime/Random/RandomEngine.cs @@ -290,6 +290,24 @@ namespace Rokojori return selection; } + public int IndexFromUnnormalizedWeights( List weights, float sumWeights = 0 ) + { + if ( sumWeights <= 0 ) + { + sumWeights = 0; + weights.ForEach( w => sumWeights += w ); + } + + return _FindElementIndexWithWeights( weights, Next() * sumWeights ); + } + + + public int IndexFromNormalizedWeights( List weights, float sumWeights = 0 ) + { + + return IndexFromUnnormalizedWeights( weights, 1 ); + } + int _FindElementIndexWithWeights( List weights, float value ) { var limit = 0f; diff --git a/Runtime/Shading/Library/Colors.gdshaderinc b/Runtime/Shading/Library/Colors.gdshaderinc index b57281e..b2757f0 100644 --- a/Runtime/Shading/Library/Colors.gdshaderinc +++ b/Runtime/Shading/Library/Colors.gdshaderinc @@ -139,3 +139,25 @@ vec4 fade( vec4 rgba, float fade ) { return vec4( rgba.rgb, rgba.a * fade ); } + +vec4 straightToPremultipliedColor( vec4 color ) +{ + return vec4( color.rgb * color.a, color.a ); +} + +vec4 premultipliedToStraightColor( vec4 color ) +{ + color.a = max( color.a, 0.000001f ); + + return vec4( color.rgb / color.a, color.a ); +} + + +vec4 blendMode_alpha( vec4 top, vec4 bottom ) +{ + float alpha = top.a + bottom.a * ( 1.0 - top.a ); + vec3 color = top.rgb * top.a + bottom.rgb * bottom.a * ( 1.0 - top.a ); + + return vec4( color, alpha ); +} + diff --git a/Runtime/Shading/Library/Noise.gdshaderinc b/Runtime/Shading/Library/Noise.gdshaderinc new file mode 100644 index 0000000..d177219 --- /dev/null +++ b/Runtime/Shading/Library/Noise.gdshaderinc @@ -0,0 +1,60 @@ +float random( vec2 uv ) +{ + return fract( sin( dot( uv.xy, vec2( 12.9898, 78.233 ) ) ) * 43758.5453123 ); +} + + +float worley( vec2 uv, float columns, float rows ) +{ + + vec2 index_uv = floor( vec2( uv.x * columns, uv.y * rows ) ); + vec2 fract_uv = fract( vec2( uv.x * columns, uv.y * rows ) ); + + float minimum_dist = 1.0; + + for ( int y= -1; y <= 1; y++ ) + { + for ( int x= -1; x <= 1; x++ ) + { + vec2 neighbor = vec2( float( x ), float( y ) ); + vec2 point = random( index_uv + neighbor ); + + vec2 diff = neighbor + point - fract_uv; + float dist = length (diff ); + + minimum_dist = min( minimum_dist, dist ); + } + } + + return minimum_dist; +} + +vec2 voronoi( vec2 uv, float columns, float rows ) +{ + vec2 index_uv = floor( vec2( uv.x * columns, uv.y * rows ) ); + vec2 fract_uv = fract( vec2( uv.x * columns, uv.y * rows ) ); + + float minimum_dist = 1.0; + vec2 minimum_point; + + for ( int y= -1; y <= 1; y++ ) + { + for ( int x= -1; x <= 1; x++ ) + { + vec2 neighbor = vec2( float( x ), float( y ) ); + vec2 point = random( index_uv + neighbor ); + + vec2 diff = neighbor + point - fract_uv; + float dist = length( diff ); + + if ( dist < minimum_dist ) + { + minimum_dist = dist; + minimum_point = point; + } + } + } + + + return minimum_point; +} \ No newline at end of file diff --git a/Runtime/Shading/Library/Transform.gdshaderinc b/Runtime/Shading/Library/Transform.gdshaderinc index e936799..a4a5eaa 100644 --- a/Runtime/Shading/Library/Transform.gdshaderinc +++ b/Runtime/Shading/Library/Transform.gdshaderinc @@ -1,10 +1,26 @@ vec3 localToWorld( vec3 _VERTEX, mat4 _MODEL_MATRIX ) { - return ( _MODEL_MATRIX * vec4( _VERTEX, 1.0 ) ).xyz; + return ( _MODEL_MATRIX * vec4( _VERTEX, 1.0 ) ).xyz; } vec3 worldToLocal( vec3 _VERTEX, mat4 _MODEL_MATRIX ) { - return ( inverse( _MODEL_MATRIX ) * vec4( _VERTEX, 1.0 ) ).xyz; + return ( inverse( _MODEL_MATRIX ) * vec4( _VERTEX, 1.0 ) ).xyz; +} + +vec2 tilingOffset( vec2 uv, vec4 tilingOffset ) +{ + uv *= tilingOffset.xy; + uv += tilingOffset.zw; + + return uv; +} + +vec2 tilingOffsetRepeat( vec2 uv, vec4 tilingOffset ) +{ + uv *= tilingOffset.xy; + uv += tilingOffset.zw; + + return mod( uv, vec2(1,1) ); } \ No newline at end of file diff --git a/Runtime/Shading/Properties/ColorPropertyName.cs b/Runtime/Shading/Properties/ColorPropertyName.cs new file mode 100644 index 0000000..4f0424f --- /dev/null +++ b/Runtime/Shading/Properties/ColorPropertyName.cs @@ -0,0 +1,54 @@ +using Godot; +using System.Reflection; +using System.Collections.Generic; + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class ColorPropertyName : Resource + { + [Export] + public string propertyName; + + public void Set( Material material, Color value ) + { + if ( material is ShaderMaterial ) + { + var shaderMaterial = ( ShaderMaterial ) material; + shaderMaterial.SetShaderParameter( propertyName, value ); + + return; + } + + var propertyInfo = material.GetType().GetProperty( propertyName ); + + if ( propertyInfo == null ) + { + return; + } + + propertyInfo.SetValue( material, value ); + + } + + public Color Get( Material material ) + { + if ( material is ShaderMaterial ) + { + var m = ( ShaderMaterial ) material; + return (Color) m.GetShaderParameter( propertyName ); + } + + var propertyInfo = material.GetType().GetProperty( propertyName ); + + if ( propertyInfo == null ) + { + return default( Color ); + } + + return (Color) propertyInfo.GetValue( material ); + + } + } +} \ No newline at end of file diff --git a/Runtime/Shading/Properties/FloatPropertyName.cs b/Runtime/Shading/Properties/FloatPropertyName.cs new file mode 100644 index 0000000..5acb5aa --- /dev/null +++ b/Runtime/Shading/Properties/FloatPropertyName.cs @@ -0,0 +1,54 @@ +using Godot; +using System.Reflection; +using System.Collections.Generic; + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class FloatPropertyName : Resource + { + [Export] + public string propertyName; + + public void Set( Material material, float value ) + { + if ( material is ShaderMaterial ) + { + var shaderMaterial = ( ShaderMaterial ) material; + shaderMaterial.SetShaderParameter( propertyName, value ); + + return; + } + + var propertyInfo = material.GetType().GetProperty( propertyName ); + + if ( propertyInfo == null ) + { + return; + } + + propertyInfo.SetValue( material, value ); + + } + + public float Get( Material material ) + { + if ( material is ShaderMaterial ) + { + var m = ( ShaderMaterial ) material; + return (float) m.GetShaderParameter( propertyName ); + } + + var propertyInfo = material.GetType().GetProperty( propertyName ); + + if ( propertyInfo == null ) + { + return default( float ); + } + + return (float) propertyInfo.GetValue( material ); + + } + } +} \ No newline at end of file diff --git a/Runtime/Shading/Properties/Vector2PropertyName.cs b/Runtime/Shading/Properties/Vector2PropertyName.cs new file mode 100644 index 0000000..ee0526b --- /dev/null +++ b/Runtime/Shading/Properties/Vector2PropertyName.cs @@ -0,0 +1,53 @@ +using Godot; +using System.Reflection; +using System.Collections.Generic; + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class Vector2PropertyName : Resource + { + [Export] + public string propertyName; + + public void Set( Material material, Vector2 value ) + { + if ( material is ShaderMaterial ) + { + var shaderMaterial = ( ShaderMaterial ) material; + shaderMaterial.SetShaderParameter( propertyName, value ); + return; + } + + var propertyInfo = material.GetType().GetProperty( propertyName ); + + if ( propertyInfo == null ) + { + return; + } + + propertyInfo.SetValue( material, value ); + + } + + public Vector2 Get( Material material ) + { + if ( material is ShaderMaterial ) + { + var m = ( ShaderMaterial ) material; + return (Vector2) m.GetShaderParameter( propertyName ); + } + + var propertyInfo = material.GetType().GetProperty( propertyName ); + + if ( propertyInfo == null ) + { + return default( Vector2 ); + } + + return (Vector2) propertyInfo.GetValue( material ); + + } + } +} \ No newline at end of file diff --git a/Runtime/Shading/Properties/Vector3PropertyName.cs b/Runtime/Shading/Properties/Vector3PropertyName.cs new file mode 100644 index 0000000..84de195 --- /dev/null +++ b/Runtime/Shading/Properties/Vector3PropertyName.cs @@ -0,0 +1,54 @@ +using Godot; +using System.Reflection; +using System.Collections.Generic; + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class Vector3PropertyName : Resource + { + [Export] + public string propertyName; + + public void Set( Material material, Vector3 value ) + { + if ( material is ShaderMaterial ) + { + var shaderMaterial = ( ShaderMaterial ) material; + shaderMaterial.SetShaderParameter( propertyName, value ); + + return; + } + + var propertyInfo = material.GetType().GetProperty( propertyName ); + + if ( propertyInfo == null ) + { + return; + } + + propertyInfo.SetValue( material, value ); + + } + + public Vector3 Get( Material material ) + { + if ( material is ShaderMaterial ) + { + var m = ( ShaderMaterial ) material; + return (Vector3) m.GetShaderParameter( propertyName ); + } + + var propertyInfo = material.GetType().GetProperty( propertyName ); + + if ( propertyInfo == null ) + { + return default( Vector3 ); + } + + return (Vector3) propertyInfo.GetValue( material ); + + } + } +} \ No newline at end of file diff --git a/Runtime/Shading/Properties/Vector4PropertyName.cs b/Runtime/Shading/Properties/Vector4PropertyName.cs new file mode 100644 index 0000000..bc1534a --- /dev/null +++ b/Runtime/Shading/Properties/Vector4PropertyName.cs @@ -0,0 +1,54 @@ +using Godot; +using System.Reflection; +using System.Collections.Generic; + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class Vector4PropertyName : Resource + { + [Export] + public string propertyName; + + public void Set( Material material, Vector4 value ) + { + if ( material is ShaderMaterial ) + { + var shaderMaterial = ( ShaderMaterial ) material; + shaderMaterial.SetShaderParameter( propertyName, value ); + + return; + } + + var propertyInfo = material.GetType().GetProperty( propertyName ); + + if ( propertyInfo == null ) + { + return; + } + + propertyInfo.SetValue( material, value ); + + } + + public Vector4 Get( Material material ) + { + if ( material is ShaderMaterial ) + { + var m = ( ShaderMaterial ) material; + return (Vector4) m.GetShaderParameter( propertyName ); + } + + var propertyInfo = material.GetType().GetProperty( propertyName ); + + if ( propertyInfo == null ) + { + return default( Vector4 ); + } + + return (Vector4) propertyInfo.GetValue( material ); + + } + } +} \ No newline at end of file diff --git a/Runtime/Structures/DictionaryList.cs b/Runtime/Structures/DictionaryList.cs new file mode 100644 index 0000000..b373ea2 --- /dev/null +++ b/Runtime/Structures/DictionaryList.cs @@ -0,0 +1,40 @@ + +using System.Diagnostics; +using System.Collections; +using System.Collections.Generic; +using System; +using Godot; + + +namespace Rokojori +{ + public class DictionaryList:Dictionary> + { + public void Add( K key, V value ) + { + if ( ! ContainsKey( key ) ) + { + this[ key ] = new List(); + } + + this[ key ].Add( value ); + } + + public void Remove( K key, V value ) + { + if ( ! ContainsKey( key ) ) + { + return; + } + + var list = this[ key ]; + + list.Remove( value ); + + if ( list.Count == 0 ) + { + Remove( key ); + } + } + } +} \ No newline at end of file diff --git a/Runtime/Time/TimeLineManager.cs b/Runtime/Time/TimeLineManager.cs index 8444692..f9962bd 100644 --- a/Runtime/Time/TimeLineManager.cs +++ b/Runtime/Time/TimeLineManager.cs @@ -8,6 +8,7 @@ using Godot; namespace Rokojori { + [Tool] [GlobalClass] public partial class TimeLineManager:RJTimeLineManager { @@ -51,6 +52,38 @@ namespace Rokojori return _idCounter; } + public static float GetPosition( RJTimeLine timeLine ) + { + var manager = Unique.Get(); + + if ( manager == null ) + { + return Time.GetTicksMsec() / 1000f; + } + + var index = Arrays.IndexOf( manager.timeLines, timeLine ); + + index = Mathf.Max( index, 0 ); + + return (float) manager.GetPosition( index ); + } + + + public static float GetPhase( RJTimeLine timeLine, float duration, float offset = 0 ) + { + var time = GetPosition( timeLine ) + offset; + + return ( time % duration ) / duration; + } + + public static float GetRangePhase( RJTimeLine timeLine, float start, float end ) + { + var time = GetPosition( timeLine ); + var normalized = MathX.Normalize( time, start, end ); + return normalized; + } + + public override void _Process( double delta ) { _runners.ForEach( r => r.UpdateTimeLine( delta, this ) ); diff --git a/Runtime/Tools/Arrays.cs b/Runtime/Tools/Arrays.cs index 45c360b..6b43272 100644 --- a/Runtime/Tools/Arrays.cs +++ b/Runtime/Tools/Arrays.cs @@ -7,6 +7,24 @@ namespace Rokojori { public class Arrays { + public static int IndexOf( T[] values, T other ) + { + return Array.IndexOf( values, other ); + } + + public static int FindIndex( T[] values, Func predicate ) + { + for ( int i = 0; i < values.Length; i++ ) + { + if ( predicate( values[ i ] ) ) + { + return i; + } + } + + return -1; + } + public static bool Contains ( T[] values, T other ) { return Array.IndexOf( values, other ) != -1; diff --git a/Runtime/Tools/BooleanLogic.cs b/Runtime/Tools/BooleanLogic.cs new file mode 100644 index 0000000..8848e64 --- /dev/null +++ b/Runtime/Tools/BooleanLogic.cs @@ -0,0 +1,35 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text; +using System; + +namespace Rokojori +{ + public enum BooleanLogicBinaryOperator + { + AND, + OR, + XOR, + NOR, + NAND, + XNOR + } + + public class BooleanLogic + { + public static bool Binary( BooleanLogicBinaryOperator op, bool l, bool r ) + { + switch ( op ) + { + case BooleanLogicBinaryOperator.AND: return l && r; + case BooleanLogicBinaryOperator.OR: return l || r; + case BooleanLogicBinaryOperator.XOR: return l || r && ! ( l && r ); + case BooleanLogicBinaryOperator.NOR: return ! ( l || r ) ; + case BooleanLogicBinaryOperator.NAND: return ! ( l && r ) ; + case BooleanLogicBinaryOperator.XNOR: return ( ! l && ! r ) || ( l && r ) ; + } + + return false; + } + } +} \ No newline at end of file diff --git a/Runtime/Tools/Dictionaries.cs b/Runtime/Tools/Dictionaries.cs new file mode 100644 index 0000000..fd22d1f --- /dev/null +++ b/Runtime/Tools/Dictionaries.cs @@ -0,0 +1,25 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text; +using System; + +namespace Rokojori +{ + public class Dictionaries + { + public static void RemoveAll( Dictionary dictionary, Func predicate ) + { + var list = new List(); + + foreach ( var kv in dictionary ) + { + if ( predicate( kv.Key, kv.Value ) ) + { + list.Add( kv.Key ); + } + } + + list.ForEach( k => dictionary.Remove( k ) ); + } + } +} \ No newline at end of file diff --git a/Runtime/Tools/ReflectionHelper.cs b/Runtime/Tools/ReflectionHelper.cs index d31b15d..6e68fa4 100644 --- a/Runtime/Tools/ReflectionHelper.cs +++ b/Runtime/Tools/ReflectionHelper.cs @@ -43,7 +43,7 @@ namespace Rokojori } - public static Type GetTypeByName( string name) + public static Type GetTypeByName( string name ) { var assemblies = new List(); assemblies.AddRange( AppDomain.CurrentDomain.GetAssemblies() ); @@ -166,7 +166,7 @@ namespace Rokojori } } - public static System.Reflection.FieldInfo GetFieldInfo( object instance, string memberName ) + public static FieldInfo GetFieldInfo( object instance, string memberName ) { var type = instance.GetType(); var fields = type.GetFields(); diff --git a/Runtime/UI/Layouts/UIFlowLayout.cs b/Runtime/UI/Layouts/UIFlowLayout.cs index 5258ecf..bb66679 100644 --- a/Runtime/UI/Layouts/UIFlowLayout.cs +++ b/Runtime/UI/Layouts/UIFlowLayout.cs @@ -30,13 +30,12 @@ namespace Rokojori { var container = (UIStylePropertyContainer) control; - var left = UIStyle.Left( container ); - var right = UIStyle.Right( container ); - var top = UIStyle.Top( container ); - var bottom = UIStyle.Bottom( container ); - var offsetX = UINumber.Compute( control, left, 0 ) - UINumber.Compute( control, right, 0 ); - var offsetY = UINumber.Compute( control, top, 0 ) - UINumber.Compute( control, bottom, 0 ); + var offsetX = UINumber.Compute( control, UIStyleNumberProperty.Left, 0 ) - + UINumber.Compute( control, UIStyleNumberProperty.Right, 0 ); + + var offsetY = UINumber.Compute( control, UIStyleNumberProperty.Top, 0 ) - + UINumber.Compute( control, UIStyleNumberProperty.Bottom, 0 ); UILayouting.SetPosition( control, parentOffset + new Vector2( offset + offsetX, yOffset + offsetY ) ); } @@ -66,33 +65,66 @@ namespace Rokojori var maxWidth = 0f; var maxHeight = 0f; + var maxVerticalPlacementOffset = 0f; + var maxLineY = lines.Count > 0 ? lines[ lines.Count - 1 ].maxY : 0; - if ( UINumber.IsNullOrNone( UIStyle.Width( region ) ) ) + region.contentSize.X = 0f; + region.contentSize.Y = maxLineY; + + lines.ForEach( l => { region.contentSize.X = Mathf.Max( region.contentSize.X, l.maxX ); } ); + + + if ( UINumber.IsNullOrNone( region, UIStyleNumberProperty.Width ) ) { - lines.ForEach( l => { maxWidth = Mathf.Max( maxWidth, l.maxX ); } ); + maxWidth = region.contentSize.X; } else { - maxWidth = UINumber.Compute( region, UIStyle.Width( region ) ); + maxWidth = UINumber.Compute( region, UIStyleNumberProperty.Width ); } - if ( lines.Count > 0 ) + if ( ! UINumber.IsNullOrNone( UIStyle.GetUINumberProperty( region, UIStyleNumberProperty.Height ) ) ) { - maxHeight = lines[ lines.Count - 1 ].maxY; + maxHeight = UINumber.Compute( region, UIStyleNumberProperty.Height ); + maxVerticalPlacementOffset = maxHeight - maxLineY; + + } + else if ( lines.Count > 0 ) + { + maxHeight = maxLineY; } - var margin = UINumber.Compute( region, UIStyle.Margin( region ), 0 ); - var marginLeft = margin + UINumber.Compute( region, UIStyle.MarginLeft( region ), 0 ); - var marginTop = margin + UINumber.Compute( region, UIStyle.MarginTop( region ), 0 ); + var margin = UINumber.Compute( region, UIStyleNumberProperty.Margin, 0 ); + var marginLeft = margin + UINumber.Compute( region, UIStyleNumberProperty.MarginLeft, 0 ); + var marginTop = margin + UINumber.Compute( region, UIStyleNumberProperty.MarginTop, 0 ); - var marginRight = margin + UINumber.Compute( region, UIStyle.MarginRight( region ), 0 ); - var marginBottom = margin + UINumber.Compute( region, UIStyle.MarginBottom( region ), 0 ); + var marginRight = margin + UINumber.Compute( region, UIStyleNumberProperty.MarginRight, 0 ); + var marginBottom = margin + UINumber.Compute( region, UIStyleNumberProperty.MarginBottom, 0 ); + + + var verticalPlacementOffset = UINumber.Compute( region, UIStyleNumberProperty.VerticalPlacement, 0, maxVerticalPlacementOffset ); + + // if ( region.Name == "Audio" ) + // { + // RJLog.Log( "Audio Placements", + // "maxHeight:", maxHeight, + // "maxLineY:", maxLineY, + // "maxVerticalPlacementOffset", maxVerticalPlacementOffset, + // "verticalPlacementOffset", verticalPlacementOffset + // ); + // } - var marginOffset = new Vector2( marginLeft, marginTop ); + var marginOffset = new Vector2( marginLeft, marginTop + verticalPlacementOffset ); + + region.contentOffset = marginOffset; PlaceControls( region, lines, marginOffset ); + var alignmentWidth = maxWidth + marginLeft + marginRight; + var horizontalAlignment = UINumber.Compute( region, UIStyleNumberProperty.HorizontalAlignment, 0 ); + region.contentOffset.X = Mathf.Lerp( 0, alignmentWidth - region.contentSize.X, horizontalAlignment / 100f ); + var verticalMargins = marginTop + marginBottom; var horizontalMargins = marginLeft + marginRight; @@ -124,12 +156,13 @@ namespace Rokojori static List CreateLines( UIRegion region ) { var x = 0f; - var width = UINumber.Compute( region, UIStyle.Width( region ), 100000000f ); + var width = UINumber.Compute( region, UIStyleNumberProperty.Width, 100000000f ); var lines = new List(); var currentLine = new Line(); - var elementSpacing = UINumber.Compute( region, UIStyle.ElementSpacing( region ), 0f ); + var elementSpacing = UINumber.Compute( region, UIStyleNumberProperty.ElementSpacing, 0f ); + var lineSpacing = UINumber.Compute( region, UIStyleNumberProperty.LineSpacing, 0f ); Nodes.ForEachDirectChild( region, c => UILayouting.UpdateChild( c ) ); @@ -137,7 +170,11 @@ namespace Rokojori Nodes.ForEachDirectChild( region, child => { - + if ( ! child.Visible ) + { + return; + } + var styleContainer = child as UIStylePropertyContainer; if ( styleContainer != null && UIStyle.Position( styleContainer ) == UIPosition.Parent_Anchor ) @@ -149,13 +186,17 @@ namespace Rokojori var cHeight = UILayouting.GetHeight( child ); var nextEndX = x + cWidth + elementSpacing; + + var lineWrap = UIStyle.LineWrap( styleContainer ); + + lineWrap = lineWrap == UILineWrap.___ ? UILineWrap.Wrap_On_Overflow : lineWrap; - if ( nextEndX > width ) + if ( UILineWrap.Wrap_Never != lineWrap && ( UILineWrap.Wrap_Always == lineWrap || nextEndX > width ) ) { lines.Add( currentLine ); currentLine = new Line(); x = 0; - currentLine.y = lines[ lines.Count - 1 ].maxY; + currentLine.y = lines[ lines.Count - 1 ].maxY + lineSpacing; nextEndX = cWidth + elementSpacing; } @@ -175,7 +216,7 @@ namespace Rokojori if ( lines.Count > 0 ) { - currentLine.y = lines[ lines.Count - 1 ].maxY; + currentLine.y = lines[ lines.Count - 1 ].maxY + lineSpacing; } lines.Add( currentLine ); @@ -188,17 +229,17 @@ namespace Rokojori static void AdjustLines( UIRegion region, List lines ) { - var verticalAlignment = UINumber.Compute( region, region.verticalAlignment, 0.5f, 1 ); + var verticalAlignment = UINumber.Compute( region, UIStyleNumberProperty.VerticalAlignment, 0.5f, 1 ); lines.ForEach( line => AdjustVerticalAlignment( region, line, verticalAlignment ) ); - if ( UINumber.IsNullOrNone( UIStyle.Width( region ) ) ) + if ( UINumber.IsNullOrNone( region, UIStyleNumberProperty.Width ) ) { return; } - var horizontalAlignment = UINumber.Compute( region, region.horizontalAlignment, 0.5f, 1 ); - var maxWidth = UINumber.Compute( region, UIStyle.Width( region ), 0 ); + var horizontalAlignment = UINumber.Compute( region, UIStyleNumberProperty.HorizontalAlignment, 0.5f, 1 ); + var maxWidth = UINumber.Compute( region, UIStyleNumberProperty.Width, 0 ); lines.ForEach( line => AdjustHorizontalAlignment( region, line, horizontalAlignment, maxWidth ) ); diff --git a/Runtime/UI/Layouts/UILayout.cs b/Runtime/UI/Layouts/UILayout.cs index ac88fbe..ad061f2 100644 --- a/Runtime/UI/Layouts/UILayout.cs +++ b/Runtime/UI/Layouts/UILayout.cs @@ -6,6 +6,7 @@ namespace Rokojori { public enum UILayout { + ___, Flow_Left_Top } } \ No newline at end of file diff --git a/Runtime/UI/Layouts/UILayouting.cs b/Runtime/UI/Layouts/UILayouting.cs index d41ac46..e5efb1f 100644 --- a/Runtime/UI/Layouts/UILayouting.cs +++ b/Runtime/UI/Layouts/UILayouting.cs @@ -6,43 +6,149 @@ namespace Rokojori { public class UILayouting { + public static Vector2 GetContentSize( Control control ) + { + if ( control is UIRegion ) + { + return ( (UIRegion) control ).contentSize; + } + + return control.Size; + } + + public static Vector2 GetContentOffset( Control control ) + { + if ( control is UIRegion ) + { + return ( (UIRegion) control ).contentOffset; + } + + return control.Size; + } + public static void UpdateChild( Control control ) { + if ( ! control.Visible ) + { + return; + } + if ( control is UIRegion ) { var childUIRegion = (UIRegion) control; childUIRegion.Layout(); } - else if ( control is UIImage ) + else if ( control is UIImage || control is UIBorderImage ) { - var uiImage = (UIImage) control; + var tw = 0; + var th = 0; - if ( uiImage.Texture == null ) + if ( control is UIImage ) { - uiImage.Size = new Vector2( 0, 0 ); - return; + var uiImage = (UIImage) control; + + if ( uiImage.Texture == null ) + { + uiImage.Size = new Vector2( 0, 0 ); + return; + } + + tw = uiImage.Texture.GetWidth(); + th = uiImage.Texture.GetHeight(); } + else if ( control is UIBorderImage ) + { + var uiBorderImage = (UIBorderImage) control; - var tw = uiImage.Texture.GetWidth(); - var th = uiImage.Texture.GetHeight(); + if ( uiBorderImage.Texture == null ) + { + uiBorderImage.Size = new Vector2( 0, 0 ); + return; + } - var w = UINumber.Compute( control, uiImage.width, tw, tw / 100f ); - var h = UINumber.Compute( control, uiImage.height, th, th / 100f ); + tw = uiBorderImage.Texture.GetWidth(); + th = uiBorderImage.Texture.GetHeight(); + } + - uiImage.Size = new Vector2( w, h ); + var container = (UIStylePropertyContainer) control; + + var w = UINumber.Compute( control, UIStyleNumberProperty.Width, tw, tw / 100f ); + var h = UINumber.Compute( control, UIStyleNumberProperty.Height, th, th / 100f ); + + control.Size = new Vector2( w, h ); + + if ( control is UIImage ) + { + var uiImage = (UIImage) control; + + if ( uiImage.Material != null ) + { + var ui = Unique.Get(); + + if ( ui == null ) + { + ui = NodesWalker.Get().GetInParents( control, n => n is UI ) as UI; + } + + if ( ui == null ) + { + RJLog.Log( "No UI Found" ); + return; + } + + //RJLog.Log( "Setting Size", ui.settings.sizePropertyName.propertyName, HierarchyName.Of( uiImage ) ); + ui.settings.sizePropertyName.Set( uiImage.Material, uiImage.Size ); + + UIShaderProperties.UpdateProperties( uiImage, uiImage.Material ); + return; + } + + } } + else if ( control is UIText ) + { + var text = (UIText) control; + + var container = (UIStylePropertyContainer) control; + + text.uiTextLabelSettings.FontSize = + UINumber.ComputeInt( control, UIStyleNumberProperty.FontSize, UINumber.em(), UINumber.em() / 100f ); + text.uiTextLabelSettings.FontColor = + UIColor.Compute( control, UIStyleColorProperty.FontColor, Colors.White ); + + text.uiTextLabelSettings.OutlineSize = + UINumber.ComputeInt( control, UIStyleNumberProperty.FontOutlineSize, 0 ); + + text.uiTextLabelSettings.OutlineColor = + UIColor.Compute( control, UIStyleColorProperty.FontOutlineColor, Colors.Transparent ); + + text.uiTextLabelSettings.ShadowSize = + UINumber.ComputeInt( control, UIStyleNumberProperty.FontShadowSize, 0 ); + + text.uiTextLabelSettings.ShadowColor = + UIColor.Compute( control, UIStyleColorProperty.FontShadowColor, Colors.Black ); + + text.uiTextLabelSettings.ShadowOffset = new Vector2( + UINumber.Compute( control, UIStyleNumberProperty.FontShadowOffsetX, 0 ), + UINumber.Compute( control, UIStyleNumberProperty.FontShadowOffsetY, 0 ) + ); + + control.UpdateMinimumSize(); + control.Size = control.GetMinimumSize(); + } else { - control.UpdateMinimumSize(); - control.Size = control.GetMinimumSize(); + // control.UpdateMinimumSize(); + // control.Size = control.GetMinimumSize(); } UILayouting.UpdatePivot( control ); } - public static void SetPositionInParentAnchor( UIStylePropertyContainer region ) + public static void SetPositionInParentAnchor( UIStylePropertyContainer container ) { - var control = (Control) region; + var control = (Control) container; var p = NodesWalker.Get().Parent( control ) as Control; var pWidth = p == null ? UI.GetWindowWidth( control ) : UILayouting.GetWidth( p ); @@ -52,31 +158,37 @@ namespace Rokojori var y = p.Position.Y; - if ( ! UINumber.IsNullOrNone( UIStyle.Left( region ) )) + if ( ! UINumber.IsNullOrNone( container, UIStyleNumberProperty.Left ) ) { - var left = UINumber.Compute( control, UIStyle.Left( region ), 0 ); + var left = UINumber.Compute( control, UIStyleNumberProperty.Left, 0 ); x = left; } - else if ( ! UINumber.IsNullOrNone( UIStyle.Right( region ) ) ) + else if ( ! UINumber.IsNullOrNone( container, UIStyleNumberProperty.Right ) ) { - var right = UINumber.Compute( control, UIStyle.Right( region ), 0 ); + var right = UINumber.Compute( control, UIStyleNumberProperty.Right, 0 ); x = ( pWidth - UILayouting.GetWidth( control ) ) - right; } - if ( ! UINumber.IsNullOrNone( UIStyle.Top( region ) )) + if ( ! UINumber.IsNullOrNone( container, UIStyleNumberProperty.Top )) { - var top = UINumber.Compute( control, UIStyle.Top( region ), 0 ); + var top = UINumber.Compute( control, UIStyleNumberProperty.Top, 0 ); y = top; } - else if ( ! UINumber.IsNullOrNone( UIStyle.Bottom( region ) ) ) + else if ( ! UINumber.IsNullOrNone( container, UIStyleNumberProperty.Bottom ) ) { - var bottom = UINumber.Compute( control, UIStyle.Bottom( region ), 0 ); + var bottom = UINumber.Compute( control, UIStyleNumberProperty.Bottom, 0 ); y = ( pHeight - UILayouting.GetHeight( control ) ) - bottom; } - UILayouting.SetPosition( control, new Vector2( x, y ) ); - + // var margin = UINumber.Compute( control, UIStyle.Margin( container ), 0 ); + // var marginLeft = margin + UINumber.Compute( control, UIStyle.MarginLeft( container ), 0 ); + // var marginTop = margin + UINumber.Compute( control, UIStyle.MarginRight( container ), 0 ); + // var marginRight = margin + UINumber.Compute( control, UIStyle.MarginRight( container ), 0 ); + // var marginBottom = margin + UINumber.Compute( control, UIStyle.MarginBottom( container ), 0 ); + // UILayouting.SetPosition( control, new Vector2( x - ( marginLeft + marginRight ), y - ( marginTop + marginBottom ) ) ); + + UILayouting.SetPosition( control, new Vector2( x, y ) ); } public static void UpdatePivot( Control c ) @@ -88,18 +200,18 @@ namespace Rokojori var container = c as UIStylePropertyContainer; - var pivotX = UINumber.Compute( c, UIStyle.PivotX( container ), 0.5f * c.Size.X, c.Size.X ); - var pivotY = UINumber.Compute( c, UIStyle.PivotY( container ), 0.5f * c.Size.Y, c.Size.Y ); + var pivotX = UINumber.Compute( c, UIStyleNumberProperty.PivotX, 0.5f * c.Size.X, c.Size.X ); + var pivotY = UINumber.Compute( c, UIStyleNumberProperty.PivotY, 0.5f * c.Size.Y, c.Size.Y ); c.PivotOffset = new Vector2( pivotX, pivotY ); - c.Rotation = UINumber.Compute( c, UIStyle.Rotation( container ), 0 ); + c.Rotation = UINumber.Compute( c, UIStyleNumberProperty.Rotation, 0 ); - var scale = UINumber.Compute( c, UIStyle.Scale( container ), 1, 1 ); + var scale = UINumber.Compute( c, UIStyleNumberProperty.Scale, 1, 1 ); c.Scale = new Vector2( - UINumber.Compute( c, UIStyle.ScaleX( container ), 1, 1 ) , - UINumber.Compute( c, UIStyle.ScaleY( container ), 1, 1 ) + UINumber.Compute( c, UIStyleNumberProperty.ScaleX, 1, 1 ) , + UINumber.Compute( c, UIStyleNumberProperty.ScaleY, 1, 1 ) ) * scale; } @@ -110,9 +222,9 @@ namespace Rokojori { var container = c as UIStylePropertyContainer; - var margin = UINumber.Compute( c, UIStyle.Margin( container ), 0 ); - var marginLeft = margin + UINumber.Compute( c, UIStyle.MarginLeft( container ), 0 ); - var marginTop = margin + UINumber.Compute( c, UIStyle.MarginTop( container ), 0 ); + var margin = UINumber.Compute( c, UIStyleNumberProperty.Margin, 0 ); + var marginLeft = margin + UINumber.Compute( c, UIStyleNumberProperty.MarginLeft, 0 ); + var marginTop = margin + UINumber.Compute( c, UIStyleNumberProperty.MarginTop, 0 ); position.X += marginLeft; position.Y += marginTop; @@ -128,9 +240,9 @@ namespace Rokojori { var container = c as UIStylePropertyContainer; - var margin = UINumber.Compute( c, UIStyle.Margin( container ), 0 ); - var marginLeft = margin + UINumber.Compute( c, UIStyle.MarginLeft( container ), 0 ); - var marginRight = margin + UINumber.Compute( c, UIStyle.MarginRight( container ), 0 ); + var margin = UINumber.Compute( c, UIStyleNumberProperty.Margin, 0 ); + var marginLeft = margin + UINumber.Compute( c, UIStyleNumberProperty.MarginLeft, 0 ); + var marginRight = margin + UINumber.Compute( c, UIStyleNumberProperty.MarginRight, 0 ); return c.Size.X + marginLeft + marginRight; @@ -145,9 +257,9 @@ namespace Rokojori { var container = c as UIStylePropertyContainer; - var margin = UINumber.Compute( c, UIStyle.Margin( container ), 0 ); - var marginTop = margin + UINumber.Compute( c, UIStyle.MarginTop( container ), 0 ); - var marginBottom = margin + UINumber.Compute( c, UIStyle.MarginBottom( container ), 0 ); + var margin = UINumber.Compute( c, UIStyleNumberProperty.Margin, 0 ); + var marginTop = margin + UINumber.Compute( c, UIStyleNumberProperty.MarginTop, 0 ); + var marginBottom = margin + UINumber.Compute( c, UIStyleNumberProperty.MarginBottom, 0 ); return c.Size.Y + marginTop + marginBottom; diff --git a/Runtime/UI/Nodes/UIBorderImage.cs b/Runtime/UI/Nodes/UIBorderImage.cs new file mode 100644 index 0000000..47f48c9 --- /dev/null +++ b/Runtime/UI/Nodes/UIBorderImage.cs @@ -0,0 +1,178 @@ + +using Godot; +using Rokojori; +using System.Collections.Generic; + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class UIBorderImage:NinePatchRect, UIStylePropertyContainer + { + [Export] + public UIStyle parentStyle; + + [ExportGroup("Size & Margins")] + [Export] + public UINumber width; + + [Export] + public UINumber height; + + + [Export] + public UINumber margin; + + [Export] + public UINumber marginLeft; + [Export] + public UINumber marginTop; + [Export] + public UINumber marginRight; + [Export] + public UINumber marginBottom; + + + [ExportGroup( "Position" )] + [Export] + public UIPosition position; + [Export] + public UILineWrap lineWrap; + [Export] + public UINumber left; + [Export] + public UINumber top; + [Export] + public UINumber right; + [Export] + public UINumber bottom; + + + [ExportGroup("Rotation & Scale")] + [Export] + public UINumber pivotX; + [Export] + public UINumber pivotY; + + [Export] + public UINumber rotation; + + [Export] + public UINumber scale; + + [Export] + public UINumber scaleX; + [Export] + public UINumber scaleY; + + public List> GetActiveShaderUIColorTransitions() + { + return null; + } + + public List> GetActiveShaderUINumberTransitions() + { + return null; + } + + [ExportGroup("Transitions")] + [Export] + public TransitionSettingsAll transitionSettings; + public TransitionSettingsAll GetTransitionSettingsAll() + { + return transitionSettings; + } + + [Export] + public UINumberTransition[] numberTransitions; + public UINumberTransition[] GetNumberTransitions() + { + return numberTransitions; + } + public List> activeNumberTransitions = new List>(); + public List> GetActiveUINumberTransitions() + { + return activeNumberTransitions; + } + + [Export] + public UIColorTransition[] colorTransitions; + public UIColorTransition[] GetColorTransitions() + { + return colorTransitions; + } + + public List> activeColorTransitions = new List>(); + public List> GetActiveUIColorTransitions() + { + return activeColorTransitions; + } + + public UIStyle GetUIStyleParent() + { + return parentStyle; + } + + public UIPosition GetUIPosition() + { + return position; + } + + public UILineWrap GetUILineWrap() + { + return lineWrap; + } + + public UILayout GetUILayout() + { + return UILayout.___; + } + + public ShaderUIColor[] GetShaderUIColors() + { + return null; + } + + public ShaderUINumber[] GetShaderUINumbers() + { + return null; + } + + public UIColor GetUIStyleColorProperty( UIStyleColorProperty property ) + { + return null; + } + + public UINumber GetUIStyleNumberProperty( UIStyleNumberProperty property ) + { + switch ( property ) + { + case UIStyleNumberProperty.Width: return width; + case UIStyleNumberProperty.Height: return height; + + case UIStyleNumberProperty.Margin: return margin; + + case UIStyleNumberProperty.MarginLeft: return marginLeft; + case UIStyleNumberProperty.MarginRight: return marginRight; + case UIStyleNumberProperty.MarginTop: return marginTop; + case UIStyleNumberProperty.MarginBottom: return marginBottom; + + + case UIStyleNumberProperty.Left: return left; + case UIStyleNumberProperty.Right: return right; + case UIStyleNumberProperty.Top: return top; + case UIStyleNumberProperty.Bottom: return bottom; + + case UIStyleNumberProperty.PivotX: return pivotX; + case UIStyleNumberProperty.PivotY: return pivotY; + case UIStyleNumberProperty.Rotation: return rotation; + + case UIStyleNumberProperty.Scale: return scale; + case UIStyleNumberProperty.ScaleX: return scaleX; + case UIStyleNumberProperty.ScaleY: return scaleY; + } + + return null; + } + } +} \ No newline at end of file diff --git a/Runtime/UI/Nodes/UIImage.cs b/Runtime/UI/Nodes/UIImage.cs index d078661..e5f3e32 100644 --- a/Runtime/UI/Nodes/UIImage.cs +++ b/Runtime/UI/Nodes/UIImage.cs @@ -1,6 +1,7 @@ using Godot; using Rokojori; +using System.Collections.Generic; namespace Rokojori { @@ -11,7 +12,7 @@ namespace Rokojori [Export] public UIStyle parentStyle; - [ExportCategory("Size & Margins")] + [ExportGroup("Size & Margins")] [Export] public UINumber width; @@ -32,10 +33,12 @@ namespace Rokojori public UINumber marginBottom; - [ExportCategory("Position")] + [ExportGroup( "Position" )] [Export] public UIPosition position; [Export] + public UILineWrap lineWrap; + [Export] public UINumber left; [Export] public UINumber top; @@ -45,7 +48,7 @@ namespace Rokojori public UINumber bottom; - [ExportCategory("Rotation & Scale")] + [ExportGroup( "Rotation & Scale" )] [Export] public UINumber pivotX; [Export] @@ -62,6 +65,58 @@ namespace Rokojori [Export] public UINumber scaleY; + [ExportGroup( "Shader Properties" )] + [Export] + public ShaderUIColor[] colorProperties; + public List> activeShaderColorTransitions = new List>(); + public List> GetActiveShaderUIColorTransitions() + { + return activeShaderColorTransitions; + } + + [Export] + public ShaderUINumber[] numberProperties; + public List> activeShaderNumberTransitions = new List>(); + public List> GetActiveShaderUINumberTransitions() + { + return activeShaderNumberTransitions; + } + + [ExportGroup("Transitions")] + [Export] + public TransitionSettingsAll transitionSettings; + public TransitionSettingsAll GetTransitionSettingsAll() + { + return transitionSettings; + } + + [Export] + public UINumberTransition[] numberTransitions; + public UINumberTransition[] GetNumberTransitions() + { + return numberTransitions; + } + + public List> activeNumberTransitions = new List>(); + public List> GetActiveUINumberTransitions() + { + return activeNumberTransitions; + } + + [Export] + public UIColorTransition[] colorTransitions; + public UIColorTransition[] GetColorTransitions() + { + return colorTransitions; + } + + public List> activeColorTransitions = new List>(); + public List> GetActiveUIColorTransitions() + { + return activeColorTransitions; + } + + public UIStyle GetUIStyleParent() { @@ -73,6 +128,27 @@ namespace Rokojori return position; } + public UILineWrap GetUILineWrap() + { + return lineWrap; + } + + public UILayout GetUILayout() + { + return UILayout.___; + } + + + public ShaderUIColor[] GetShaderUIColors() + { + return colorProperties; + } + + public ShaderUINumber[] GetShaderUINumbers() + { + return numberProperties; + } + public UINumber GetUIStyleNumberProperty( UIStyleNumberProperty property ) { switch ( property ) @@ -104,5 +180,10 @@ namespace Rokojori return null; } + + public UIColor GetUIStyleColorProperty( UIStyleColorProperty property ) + { + return null; + } } } \ No newline at end of file diff --git a/Runtime/UI/Nodes/UIRegion.cs b/Runtime/UI/Nodes/UIRegion.cs index 6b66557..47934d4 100644 --- a/Runtime/UI/Nodes/UIRegion.cs +++ b/Runtime/UI/Nodes/UIRegion.cs @@ -1,5 +1,6 @@ using Godot; +using System.Collections.Generic; namespace Rokojori { @@ -10,7 +11,7 @@ namespace Rokojori [Export] public UIStyle parentStyle; - [ExportCategory( "Layout" )] + [ExportGroup( "Layout" )] [Export] public UILayout layout; @@ -18,6 +19,8 @@ namespace Rokojori public UINumber horizontalAlignment; [Export] public UINumber verticalAlignment; + [Export] + public UINumber verticalPlacement; [Export] public UINumber elementSpacing; @@ -25,7 +28,7 @@ namespace Rokojori public UINumber lineSpacing; - [ExportCategory( "Size & Margins" )] + [ExportGroup( "Size & Margins" )] [Export] public UINumber width; [Export] @@ -44,7 +47,22 @@ namespace Rokojori [Export] public UINumber marginBottom; - [ExportCategory( "Font" )] + + [ExportGroup( "Position" )] + [Export] + public UIPosition position; + [Export] + public UILineWrap lineWrap; + [Export] + public UINumber left; + [Export] + public UINumber top; + [Export] + public UINumber right; + [Export] + public UINumber bottom; + + [ExportGroup( "Font" )] [Export] public Font font; [Export] @@ -62,20 +80,55 @@ namespace Rokojori [Export] public UIColor shadowColor; + [Export] + public UINumber shadowOffsetX; + [Export] + public UINumber shadowOffsetY; + public List> GetActiveShaderUIColorTransitions() + { + return null; + } + + public List> GetActiveShaderUINumberTransitions() + { + return null; + } - [ExportCategory( "Position" )] + + [ExportGroup("Transitions")] [Export] - public UIPosition position; - [Export] - public UINumber left; - [Export] - public UINumber top; - [Export] - public UINumber right; - [Export] - public UINumber bottom; + public TransitionSettingsAll transitionSettings; + public TransitionSettingsAll GetTransitionSettingsAll() + { + return transitionSettings; + } + [Export] + public UINumberTransition[] numberTransitions; + public UINumberTransition[] GetNumberTransitions() + { + return numberTransitions; + } + + public List> activeNumberTransitions = new List>(); + public List> GetActiveUINumberTransitions() + { + return activeNumberTransitions; + } + + [Export] + public UIColorTransition[] colorTransitions; + public UIColorTransition[] GetColorTransitions() + { + return colorTransitions; + } + + public List> activeColorTransitions = new List>(); + public List> GetActiveUIColorTransitions() + { + return activeColorTransitions; + } public UIStyle GetUIStyleParent() { @@ -87,6 +140,26 @@ namespace Rokojori return position; } + public UILineWrap GetUILineWrap() + { + return lineWrap; + } + + public UILayout GetUILayout() + { + return layout; + } + + public ShaderUIColor[] GetShaderUIColors() + { + return null; + } + + public ShaderUINumber[] GetShaderUINumbers() + { + return null; + } + public UINumber GetUIStyleNumberProperty( UIStyleNumberProperty property ) { switch ( property ) @@ -98,8 +171,10 @@ namespace Rokojori case UIStyleNumberProperty.Width: return width; case UIStyleNumberProperty.Height: return height; - + case UIStyleNumberProperty.HorizontalAlignment: return horizontalAlignment; + case UIStyleNumberProperty.VerticalAlignment: return verticalAlignment; + case UIStyleNumberProperty.VerticalPlacement: return verticalPlacement; case UIStyleNumberProperty.ElementSpacing: return elementSpacing; case UIStyleNumberProperty.LineSpacing: return lineSpacing; @@ -109,20 +184,48 @@ namespace Rokojori case UIStyleNumberProperty.MarginRight: return marginRight; case UIStyleNumberProperty.MarginTop: return marginTop; case UIStyleNumberProperty.MarginBottom: return marginBottom; + + case UIStyleNumberProperty.FontSize: return fontSize; + case UIStyleNumberProperty.FontOutlineSize: return outlineSize; + case UIStyleNumberProperty.FontShadowSize: return shadowSize; + case UIStyleNumberProperty.FontShadowOffsetX: return shadowOffsetX; + case UIStyleNumberProperty.FontShadowOffsetY: return shadowOffsetY; + } return null; } + public UIColor GetUIStyleColorProperty( UIStyleColorProperty property ) + { + switch ( property ) + { + case UIStyleColorProperty.FontColor: return fontColor; + case UIStyleColorProperty.FontOutlineColor: return outlineColor; + case UIStyleColorProperty.FontShadowColor: return shadowColor; + } + + return null; + } public void Layout() { + var layout = UIStyle.Layout( this ); + switch ( layout ) { - case UILayout.Flow_Left_Top: UIFlowLayout.Apply( this ); break; + case UILayout.___: + case UILayout.Flow_Left_Top: + { + UIFlowLayout.Apply( this ); + } + break; } } + public Vector2 contentSize = Vector2.Zero; + public Vector2 contentOffset = Vector2.Zero; + } } \ No newline at end of file diff --git a/Runtime/UI/Nodes/UIText.cs b/Runtime/UI/Nodes/UIText.cs index cb55277..1c35f97 100644 --- a/Runtime/UI/Nodes/UIText.cs +++ b/Runtime/UI/Nodes/UIText.cs @@ -1,6 +1,7 @@ using Godot; using Rokojori; +using System.Collections.Generic; namespace Rokojori { @@ -11,10 +12,30 @@ namespace Rokojori [Export] public UIStyle parentStyle; + [ExportGroup( "Font" )] + [Export] + public Font font; [Export] public UINumber fontSize; + [Export] + public UIColor fontColor; - [ExportCategory("Size & Margins")] + [Export] + public UINumber outlineSize; + [Export] + public UIColor outlineColor; + + [Export] + public UINumber shadowSize; + [Export] + public UIColor shadowColor; + + [Export] + public UINumber shadowOffsetX; + [Export] + public UINumber shadowOffsetY; + + [ExportGroup("Size & Margins")] [Export] public UINumber width; @@ -35,10 +56,12 @@ namespace Rokojori public UINumber marginBottom; - [ExportCategory("Position")] + [ExportGroup( "Position" )] [Export] public UIPosition position; [Export] + public UILineWrap lineWrap; + [Export] public UINumber left; [Export] public UINumber top; @@ -48,7 +71,7 @@ namespace Rokojori public UINumber bottom; - [ExportCategory("Rotation & Scale")] + [ExportGroup("Rotation & Scale")] [Export] public UINumber pivotX; [Export] @@ -65,6 +88,72 @@ namespace Rokojori [Export] public UINumber scaleY; + public List> GetActiveShaderUIColorTransitions() + { + return null; + } + + public List> GetActiveShaderUINumberTransitions() + { + return null; + } + + [ExportGroup("Transitions")] + [Export] + public TransitionSettingsAll transitionSettings; + public TransitionSettingsAll GetTransitionSettingsAll() + { + return transitionSettings; + } + + [Export] + public UINumberTransition[] numberTransitions; + public UINumberTransition[] GetNumberTransitions() + { + return numberTransitions; + } + + public List> activeNumberTransitions = new List>(); + public List> GetActiveUINumberTransitions() + { + return activeNumberTransitions; + } + + [Export] + public UIColorTransition[] colorTransitions; + public UIColorTransition[] GetColorTransitions() + { + return colorTransitions; + } + + public List> activeColorTransitions = new List>(); + public List> GetActiveUIColorTransitions() + { + return activeColorTransitions; + } + + LabelSettings _labelSettings; + + public LabelSettings uiTextLabelSettings + { + get + { + if ( _labelSettings == null ) + { + _labelSettings = new LabelSettings(); + } + + LabelSettings = _labelSettings; + return _labelSettings; + } + + set + { + _labelSettings = value.Duplicate() as LabelSettings; + LabelSettings = _labelSettings; + } + } + public UIStyle GetUIStyleParent() { return parentStyle; @@ -75,6 +164,28 @@ namespace Rokojori return position; } + public UILineWrap GetUILineWrap() + { + return lineWrap; + } + + public UILayout GetUILayout() + { + return UILayout.___; + } + + public ShaderUIColor[] GetShaderUIColors() + { + return null; + } + + public ShaderUINumber[] GetShaderUINumbers() + { + return null; + } + + + public UINumber GetUIStyleNumberProperty( UIStyleNumberProperty property ) { switch ( property ) @@ -102,9 +213,27 @@ namespace Rokojori case UIStyleNumberProperty.Scale: return scale; case UIStyleNumberProperty.ScaleX: return scaleX; case UIStyleNumberProperty.ScaleY: return scaleY; + + case UIStyleNumberProperty.FontSize: return fontSize; + case UIStyleNumberProperty.FontOutlineSize: return outlineSize; + case UIStyleNumberProperty.FontShadowSize: return shadowSize; + case UIStyleNumberProperty.FontShadowOffsetX: return shadowOffsetX; + case UIStyleNumberProperty.FontShadowOffsetY: return shadowOffsetY; } return null; } + + public UIColor GetUIStyleColorProperty( UIStyleColorProperty property ) + { + switch ( property ) + { + case UIStyleColorProperty.FontColor: return fontColor; + case UIStyleColorProperty.FontOutlineColor: return outlineColor; + case UIStyleColorProperty.FontShadowColor: return shadowColor; + } + + return null; + } } } \ No newline at end of file diff --git a/Runtime/UI/OnSliderValueChange.cs b/Runtime/UI/OnSliderValueChange.cs new file mode 100644 index 0000000..16e6c64 --- /dev/null +++ b/Runtime/UI/OnSliderValueChange.cs @@ -0,0 +1,45 @@ + +using Godot; +using System; + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class OnSliderValueChange : Node + { + [Export] + public HSlider slider; + HSlider _connectedSlider = null; + + [Export] + public RJAction onChange; + + public void Update() + { + if ( _connectedSlider == slider ) + { + return; + } + + if ( _connectedSlider != null ) + { + _connectedSlider.Changed -= OnChanged; + } + + _connectedSlider = slider; + + + if ( _connectedSlider != null ) + { + _connectedSlider.Changed += OnChanged; + } + + } + + void OnChanged() + { + Actions.Trigger( onChange ); + } + } +} \ No newline at end of file diff --git a/Runtime/UI/ShaderProperties/Float/borderRadius.tres b/Runtime/UI/ShaderProperties/Float/borderRadius.tres new file mode 100644 index 0000000..c9b78c6 --- /dev/null +++ b/Runtime/UI/ShaderProperties/Float/borderRadius.tres @@ -0,0 +1,7 @@ +[gd_resource type="Resource" script_class="FloatPropertyName" load_steps=2 format=3 uid="uid://v423srfwpna8"] + +[ext_resource type="Script" path="res://Scripts/Rokojori/Rokojori-Action-Library/Runtime/Shading/Properties/FloatPropertyName.cs" id="1_4guhd"] + +[resource] +script = ExtResource("1_4guhd") +propertyName = "borderRadius" diff --git a/Runtime/UI/ShaderProperties/Float/offset.tres b/Runtime/UI/ShaderProperties/Float/offset.tres new file mode 100644 index 0000000..9975188 --- /dev/null +++ b/Runtime/UI/ShaderProperties/Float/offset.tres @@ -0,0 +1,7 @@ +[gd_resource type="Resource" script_class="FloatPropertyName" load_steps=2 format=3 uid="uid://dbg2rgj5s7uqn"] + +[ext_resource type="Script" path="res://Scripts/Rokojori/Rokojori-Action-Library/Runtime/Shading/Properties/FloatPropertyName.cs" id="1_2hnh6"] + +[resource] +script = ExtResource("1_2hnh6") +propertyName = "offset" diff --git a/Runtime/UI/ShaderProperties/Float/strokeSize.tres b/Runtime/UI/ShaderProperties/Float/strokeSize.tres new file mode 100644 index 0000000..a20b9b1 --- /dev/null +++ b/Runtime/UI/ShaderProperties/Float/strokeSize.tres @@ -0,0 +1,7 @@ +[gd_resource type="Resource" script_class="FloatPropertyName" load_steps=2 format=3 uid="uid://dngbeoiix72sf"] + +[ext_resource type="Script" path="res://Scripts/Rokojori/Rokojori-Action-Library/Runtime/Shading/Properties/FloatPropertyName.cs" id="1_0sf2s"] + +[resource] +script = ExtResource("1_0sf2s") +propertyName = "strokeSize" diff --git a/Runtime/UI/ShaderProperties/ShaderUIColor.cs b/Runtime/UI/ShaderProperties/ShaderUIColor.cs new file mode 100644 index 0000000..6e0c2ac --- /dev/null +++ b/Runtime/UI/ShaderProperties/ShaderUIColor.cs @@ -0,0 +1,24 @@ + +using Godot; +using Rokojori; + +namespace Rokojori +{ + + [Tool] + [GlobalClass] + public partial class ShaderUIColor : Resource + { + [Export] + public ColorPropertyName colorPropertyName; + + [Export] + public UIColor color; + + public void UpdateMaterial( UIStylePropertyContainer container, Material material ) + { + var colorValue = UIColor.Compute( container as Control, color, Colors.White ); + colorPropertyName.Set( material, colorValue ); + } + } +} \ No newline at end of file diff --git a/Runtime/UI/ShaderProperties/ShaderUINumber.cs b/Runtime/UI/ShaderProperties/ShaderUINumber.cs new file mode 100644 index 0000000..0f59d9e --- /dev/null +++ b/Runtime/UI/ShaderProperties/ShaderUINumber.cs @@ -0,0 +1,46 @@ + +using Godot; +using Rokojori; + +namespace Rokojori +{ + + [Tool] + [GlobalClass] + public partial class ShaderUINumber : Resource + { + [Export] + public FloatPropertyName floatPropertyName; + + [Export] + public UINumber number; + + [Export] + public TransitionSettings transitionSettings; + + public void UpdateMaterial( UIStylePropertyContainer container, Material material ) + { + var control = container as Control; + var numberValue = UINumber.Compute( container as Control, number, 0f ); + + var allSettings = UIStyle.GetTransitionSettingsAll( container ); + var usesTransition = transitionSettings != null || allSettings != null && allSettings.transitionAllProperties; + + if ( ! usesTransition ) + { + floatPropertyName.Set( material, numberValue ); + return; + } + + /* + var transitionValue = ActiveStyleTransition.ProcessTransition( + container, numberValue, container.GetActiveShaderUINumberTransitions(), + number, floatPropertyName, + ()=>{ return UIStyle.GetTransitionSettings( container ); } + + ); + */ + + } + } +} \ No newline at end of file diff --git a/Runtime/UI/ShaderProperties/UIShaderProperties.cs b/Runtime/UI/ShaderProperties/UIShaderProperties.cs new file mode 100644 index 0000000..56ea623 --- /dev/null +++ b/Runtime/UI/ShaderProperties/UIShaderProperties.cs @@ -0,0 +1,42 @@ + +using Godot; +using Rokojori; + +namespace Rokojori +{ + public partial class UIShaderProperties + { + public static void UpdateProperties( UIStylePropertyContainer container, Material material ) + { + var numbers = container.GetShaderUINumbers(); + + if ( numbers != null ) + { + foreach ( var n in numbers ) + { + if ( n == null ) + { + continue; + } + + n.UpdateMaterial( container, material ); + } + } + + var colors = container.GetShaderUIColors(); + + if ( colors != null ) + { + foreach ( var c in colors ) + { + if ( c == null ) + { + continue; + } + + c.UpdateMaterial( container, material ); + } + } + } + } +} \ No newline at end of file diff --git a/Runtime/UI/ShaderProperties/Vector2/Size.tres b/Runtime/UI/ShaderProperties/Vector2/Size.tres new file mode 100644 index 0000000..5760d96 --- /dev/null +++ b/Runtime/UI/ShaderProperties/Vector2/Size.tres @@ -0,0 +1,7 @@ +[gd_resource type="Resource" script_class="Vector2PropertyName" load_steps=2 format=3 uid="uid://bhy8b3gopkq4m"] + +[ext_resource type="Script" path="res://Scripts/Rokojori/Rokojori-Action-Library/Runtime/Shading/Properties/Vector2PropertyName.cs" id="1_t5csl"] + +[resource] +script = ExtResource("1_t5csl") +propertyName = "size" diff --git a/Runtime/UI/Styling/UIColor.cs b/Runtime/UI/Styling/UIColor.cs index f714304..eda3cf2 100644 --- a/Runtime/UI/Styling/UIColor.cs +++ b/Runtime/UI/Styling/UIColor.cs @@ -4,12 +4,129 @@ using Rokojori; namespace Rokojori { + public enum UIColorAnimationBlend + { + Multiply, + Add, + Replace + } [Tool] [GlobalClass] public partial class UIColor : Resource { [Export] - public Color color; + public Color color = Colors.White; + + [Export] + public bool isAnimated = false; + + [Export] + public Gradient animationGradient; + + [Export] + public ColorBlendModeType blendMode = ColorBlendModeType.Multiply; + + [Export] + public float animationDuration = 1; + + [Export] + public float animationOffset = 0; + + [Export] + public RJTimeLine timeLine; + + public static Color Compute( Control control, UIStyleColorProperty property, Color defaultColor ) + { + var container = control as UIStylePropertyContainer; + var transition = UIStyle.GetTransition( container, property ); + + var uiColor = UIStyle.GetUIColorProperty( control as UIStylePropertyContainer, property ); + var computedColor = Compute( control, uiColor, defaultColor ); + + var allSettings = UIStyle.GetTransitionSettingsAll( container ); + + var usesTransition = transition != null || allSettings != null && allSettings.transitionAllProperties; + + if ( ! usesTransition ) + { + return computedColor; + } + + var activeNumberTransitions = container.GetActiveUIColorTransitions(); + + var propertyTransition = activeNumberTransitions.Find( t => t != null && t.propertyType == property ); + + if ( propertyTransition == null ) + { + propertyTransition = new ActiveStyleTransition(); + propertyTransition.propertyType = property; + propertyTransition.value = uiColor; + propertyTransition.transitioning = false; + + activeNumberTransitions.Add( propertyTransition ); + + return computedColor; + } + else + { + + if ( propertyTransition.value != uiColor && ! propertyTransition.transitioning && UIStyle.GetTransitionSettings( container, property ) != null) + { + var transitionSettings = UIStyle.GetTransitionSettings( container, property ); + propertyTransition.timeLine = transitionSettings.timeLine; + propertyTransition.start = TimeLineManager.GetPosition( transitionSettings.timeLine ); + propertyTransition.end = propertyTransition.start + transitionSettings.duration; + propertyTransition.transitioning = true; + propertyTransition.curve = transitionSettings.curve; + } + } + + if ( propertyTransition.value == uiColor ) + { + propertyTransition.transitioning = false; + return computedColor; + } + + var computedTransitionValue = Compute( control, propertyTransition.value, defaultColor ); + + var transitionPhase = TimeLineManager.GetRangePhase( propertyTransition.timeLine, propertyTransition.start, propertyTransition.end ); + + if ( transitionPhase >= 1 ) + { + activeNumberTransitions.Remove( propertyTransition ); + } + + var amount = MathX.Clamp01( transitionPhase ); + var curveAmount = amount; + + if ( propertyTransition.curve != null ) + { + curveAmount = propertyTransition.curve.Sample( curveAmount ); + } + + return ColorX.Lerp( computedTransitionValue, computedColor, curveAmount ); + } + + public static Color Compute( Control control, UIColor color, Color defaultColor ) + { + if ( color == null ) + { + return defaultColor; + } + + if ( ! color.isAnimated || color.animationGradient == null ) + { + return color.color; + } + + var phase = TimeLineManager.GetPhase( color.timeLine, color.animationDuration, color.animationOffset ); + + var gradientColor = color.animationGradient.Sample( phase ); + + return ColorBlendMode.Blend( color.blendMode, color.color, gradientColor ); + + } + } } \ No newline at end of file diff --git a/Runtime/UI/Styling/UILineWrap.cs b/Runtime/UI/Styling/UILineWrap.cs new file mode 100644 index 0000000..f0f0b8e --- /dev/null +++ b/Runtime/UI/Styling/UILineWrap.cs @@ -0,0 +1,15 @@ + +using Godot; +using Rokojori; + +namespace Rokojori +{ + public enum UILineWrap + { + ___, + Wrap_On_Overflow, + Wrap_Always, + Wrap_Never + } +} + diff --git a/Runtime/UI/Styling/UINumber.cs b/Runtime/UI/Styling/UINumber.cs index 38429d1..b3bf534 100644 --- a/Runtime/UI/Styling/UINumber.cs +++ b/Runtime/UI/Styling/UINumber.cs @@ -14,16 +14,37 @@ namespace Rokojori [Export] public string unit = ""; + + [ExportGroup("Animation")] + + [Export] + public bool isAnimated; + + [Export] + public Curve animationCurve; + [Export] + public float animationDuration; + + [Export] + public float animationOffset; + + [Export] + public RJTimeLine timeLine; public bool IsNone => unit == "none"; - public float Compute( Control control, float alternative ) + public float ComputeRaw( Control control, float alternative ) { return UINumber.Compute( control, this, alternative ); } + public static bool IsNullOrNone( UIStylePropertyContainer container, UIStyleNumberProperty property ) + { + return IsNullOrNone( UIStyle.GetUINumberProperty( container, property ) ); + } + public static bool IsNullOrNone( UINumber number ) { if ( number == null ) @@ -34,7 +55,155 @@ namespace Rokojori return number.IsNone; } - + public static int ComputeInt( Control control, UINumber number, float alternative = 0, float relative = 100 ) + { + return Mathf.RoundToInt( Compute( control, number, alternative, relative ) ); + } + + public static int ComputeInt( Control control, UIStyleNumberProperty property, float alternative = 0, float relative = 100 ) + { + return Mathf.RoundToInt( Compute( control, property, alternative, relative ) ); + } + + public bool Equals( UINumber other ) + { + if ( other == this ) + { + return true; + } + + if ( other == null ) + { + return false; + } + + if ( other.value != value ) + { + return false; + } + + if ( other.unit != unit ) + { + return false; + } + + if ( other.isAnimated != isAnimated ) + { + return false; + } + + if ( other.animationCurve != animationCurve ) + { + return false; + } + + if ( other.animationDuration != animationDuration ) + { + return false; + } + + if ( other.animationOffset != animationOffset ) + { + return false; + } + + if ( other.timeLine != timeLine ) + { + return false; + } + + return true; + + } + + public void CopyForm( UINumber other ) + { + if ( other == this ) + { + return; + } + + value = other.value; + unit = other.unit; + isAnimated = other.isAnimated; + animationCurve = other.animationCurve; + animationDuration = other.animationDuration; + animationOffset = other.animationOffset; + timeLine = other.timeLine; + } + + public static float Compute( Control control, UIStyleNumberProperty property, float alternative = 0, float relative = 100 ) + { + var container = control as UIStylePropertyContainer; + var transition = UIStyle.GetTransition( container, property ); + + var number = UIStyle.GetUINumberProperty( control as UIStylePropertyContainer, property ); + var computedValue = Compute( control, number, alternative, relative ); + + var allSettings = UIStyle.GetTransitionSettingsAll( container ); + + var usesTransition = transition != null || allSettings != null && allSettings.transitionAllProperties; + + if ( ! usesTransition ) + { + return computedValue; + } + + var activeNumberTransitions = container.GetActiveUINumberTransitions(); + + var propertyTransition = activeNumberTransitions.Find( t => t != null && t.propertyType == property ); + + if ( propertyTransition == null ) + { + propertyTransition = new ActiveStyleTransition(); + propertyTransition.propertyType = property; + propertyTransition.value = number; + propertyTransition.transitioning = false; + + activeNumberTransitions.Add( propertyTransition ); + + return computedValue; + } + else + { + if ( propertyTransition.value != number && ! propertyTransition.transitioning && UIStyle.GetTransitionSettings( container, property ) != null ) + { + var transitionSettings = UIStyle.GetTransitionSettings( container, property ); + propertyTransition.timeLine = transitionSettings.timeLine; + propertyTransition.start = TimeLineManager.GetPosition( transitionSettings.timeLine ); + propertyTransition.end = propertyTransition.start + transitionSettings.duration; + propertyTransition.transitioning = true; + propertyTransition.curve = transitionSettings.curve; + } + } + + if ( propertyTransition.value == number ) + { + propertyTransition.transitioning = false; + return computedValue; + } + + var computedTransitionValue = Compute( control, propertyTransition.value, alternative, relative ); + + var transitionPhase = TimeLineManager.GetRangePhase( propertyTransition.timeLine, propertyTransition.start, propertyTransition.end ); + + if ( transitionPhase >= 1 ) + { + activeNumberTransitions.Remove( propertyTransition ); + } + + var amount = MathX.Clamp01( transitionPhase ); + var curveAmount = amount; + + if ( propertyTransition.curve != null ) + { + curveAmount = propertyTransition.curve.Sample( curveAmount ); + } + + return Mathf.Lerp( computedTransitionValue, computedValue, curveAmount ); + } + + public static float Compute( Control control, UINumber number, float alternative = 0, float relative = 100 ) { if ( number == null || control == null || number.IsNone ) @@ -52,12 +221,27 @@ namespace Rokojori static UI _ui; - static float em() + public static float em() { return _ui == null ? 12 : _ui.X_computedFontSizePixels; } public static float Compute( Control control, UINumber number, float width, float height, float relative ) + { + var value = ComputeWithoutAnimation( control, number, width, height, relative ); + + if ( ! number.isAnimated || number.animationCurve == null ) + { + return value; + } + + var phase = TimeLineManager.GetPhase( number.timeLine, number.animationDuration, number.animationOffset ); + + return number.animationCurve.Sample( phase ) * value; + + } + + static float ComputeWithoutAnimation( Control control, UINumber number, float width, float height, float relative ) { if ( number == null ) { @@ -92,13 +276,37 @@ namespace Rokojori return number.value / 100f * ( parent == null ? width : parent.Size.X ); } + case "cw": + { + var parent = control.GetParent(); + return number.value / 100f * ( parent == null ? width : UILayouting.GetContentSize( parent ).X ); + } + + case "ch": + { + var parent = control.GetParent(); + return number.value / 100f * ( parent == null ? height : UILayouting.GetContentSize( parent ).Y ); + } + + case "cx": + { + var parent = control.GetParent(); + return number.value / 100f * ( parent == null ? 0 : UILayouting.GetContentOffset( parent ).X ); + } + + case "cy": + { + var parent = control.GetParent(); + return number.value / 100f * ( parent == null ? 0 : UILayouting.GetContentOffset( parent ).Y ); + } + case "ph": { var parent = control.GetParent(); return number.value / 100f * ( parent == null ? height : parent.Size.Y ); } - case "px": case "": + case "": { return number.value; } @@ -111,13 +319,22 @@ namespace Rokojori var expression = new Expression(); - var expressionText = number.unit == null ? "" : RegexUtility.Replace( number.unit, "%", " * relative " ); var parseResult = expression.Parse( expressionText, new string[] { - "em","vw", "vh", "pw", "ph", "px", "relative", "value" } - ); + "em", + + "vw", "vh", + "pw", "ph", + + "cw","ch", + "cx","cy", + + "relative", + "value" + } + ); if ( Error.Ok != parseResult ) { @@ -128,14 +345,26 @@ namespace Rokojori var inputs = new Godot.Collections.Array(); inputs.Add( em() ); - inputs.Add( width / 100f ); - inputs.Add( height / 100f ); + + // vw, vh + inputs.Add( width / 100f ); inputs.Add( height / 100f ); + + // pw, ph inputs.Add( ( parentControl == null ? width : parentControl.Size.X ) / 100f ); inputs.Add( ( parentControl == null ? height : parentControl.Size.Y ) / 100f ); - inputs.Add( 1 ); + + // cw, ch + inputs.Add( ( parentControl == null ? width : UILayouting.GetContentSize( parentControl ).X ) / 100f ); + inputs.Add( ( parentControl == null ? width : UILayouting.GetContentSize( parentControl ).Y ) / 100f ); + + // cx, cy + inputs.Add( ( parentControl == null ? 0 : UILayouting.GetContentOffset( parentControl ).X ) / 100f ); + inputs.Add( ( parentControl == null ? 0 : UILayouting.GetContentOffset( parentControl ).Y ) / 100f ); + + // "relative" inputs.Add( relative / 100f ); - + // value if ( number.unit.IndexOf( "value" ) == -1 ) { inputs.Add( 0 ); diff --git a/Runtime/UI/Styling/UIStyle.cs b/Runtime/UI/Styling/UIStyle.cs index 6e21252..2a78fad 100644 --- a/Runtime/UI/Styling/UIStyle.cs +++ b/Runtime/UI/Styling/UIStyle.cs @@ -1,7 +1,8 @@ using Godot; using Rokojori; - +using System.Collections.Generic; + namespace Rokojori { @@ -12,7 +13,7 @@ namespace Rokojori [Export] public UIStyle parentStyle; - [ExportCategory( "Layout" )] + [ExportGroup( "Layout" )] [Export] public UILayout layout; @@ -20,6 +21,8 @@ namespace Rokojori public UINumber horizontalAlignment; [Export] public UINumber verticalAlignment; + [Export] + public UINumber verticalPlacement; [Export] public UINumber elementSpacing; @@ -27,7 +30,7 @@ namespace Rokojori public UINumber lineSpacing; - [ExportCategory( "Size & Margins" )] + [ExportGroup( "Size & Margins" )] [Export] public UINumber width; [Export] @@ -46,7 +49,7 @@ namespace Rokojori [Export] public UINumber marginBottom; - [ExportCategory( "Font" )] + [ExportGroup( "Font" )] [Export] public Font font; [Export] @@ -64,10 +67,17 @@ namespace Rokojori [Export] public UIColor shadowColor; - [ExportCategory( "Position" )] + [Export] + public UINumber shadowOffsetX; + [Export] + public UINumber shadowOffsetY; + + [ExportGroup( "Position" )] [Export] public UIPosition position; [Export] + public UILineWrap lineWrap; + [Export] public UINumber left; [Export] public UINumber top; @@ -75,7 +85,74 @@ namespace Rokojori public UINumber right; [Export] public UINumber bottom; - + + [ExportGroup("Rotation & Scale")] + [Export] + public UINumber pivotX; + [Export] + public UINumber pivotY; + + [Export] + public UINumber rotation; + + [Export] + public UINumber scale; + + [Export] + public UINumber scaleX; + [Export] + public UINumber scaleY; + + [ExportGroup( "Shader Properties" )] + [Export] + public ShaderUIColor[] colorProperties; + [Export] + public ShaderUINumber[] numberProperties; + + public List> GetActiveShaderUIColorTransitions() + { + return null; + } + + public List> GetActiveShaderUINumberTransitions() + { + return null; + } + + [ExportGroup("Transitions")] + [Export] + public TransitionSettingsAll transitionSettings; + public TransitionSettingsAll GetTransitionSettingsAll() + { + return transitionSettings; + } + + [Export] + public UINumberTransition[] numberTransitions; + public UINumberTransition[] GetNumberTransitions() + { + return numberTransitions; + } + + public List> GetActiveUINumberTransitions() + { + return null; + } + + + [Export] + public UIColorTransition[] colorTransitions; + public UIColorTransition[] GetColorTransitions() + { + return colorTransitions; + } + + public List> GetActiveUIColorTransitions() + { + return null;; + } + + public UIStyle GetUIStyleParent() { return parentStyle; @@ -86,6 +163,26 @@ namespace Rokojori return position; } + public UILineWrap GetUILineWrap() + { + return lineWrap; + } + + public UILayout GetUILayout() + { + return layout; + } + + public ShaderUIColor[] GetShaderUIColors() + { + return colorProperties; + } + + public ShaderUINumber[] GetShaderUINumbers() + { + return numberProperties; + } + public UINumber GetUIStyleNumberProperty( UIStyleNumberProperty property ) { switch ( property ) @@ -95,6 +192,9 @@ namespace Rokojori case UIStyleNumberProperty.Top: return top; case UIStyleNumberProperty.Bottom: return bottom; + case UIStyleNumberProperty.HorizontalAlignment: return horizontalAlignment; + case UIStyleNumberProperty.VerticalAlignment: return verticalAlignment; + case UIStyleNumberProperty.VerticalPlacement: return verticalPlacement; case UIStyleNumberProperty.ElementSpacing: return elementSpacing; case UIStyleNumberProperty.LineSpacing: return lineSpacing; @@ -102,17 +202,45 @@ namespace Rokojori case UIStyleNumberProperty.Width: return width; case UIStyleNumberProperty.Height: return height; + + case UIStyleNumberProperty.Margin: return margin; case UIStyleNumberProperty.MarginLeft: return marginLeft; case UIStyleNumberProperty.MarginRight: return marginRight; case UIStyleNumberProperty.MarginTop: return marginTop; case UIStyleNumberProperty.MarginBottom: return marginBottom; + + case UIStyleNumberProperty.FontSize: return fontSize; + case UIStyleNumberProperty.FontOutlineSize: return outlineSize; + case UIStyleNumberProperty.FontShadowSize: return shadowSize; + case UIStyleNumberProperty.FontShadowOffsetX: return shadowOffsetX; + case UIStyleNumberProperty.FontShadowOffsetY: return shadowOffsetY; + + case UIStyleNumberProperty.PivotX: return pivotX; + case UIStyleNumberProperty.PivotY: return pivotY; + case UIStyleNumberProperty.Rotation: return rotation; + + case UIStyleNumberProperty.Scale: return scale; + case UIStyleNumberProperty.ScaleX: return scaleX; + case UIStyleNumberProperty.ScaleY: return scaleY; } return null; } + public UIColor GetUIStyleColorProperty( UIStyleColorProperty property ) + { + switch ( property ) + { + case UIStyleColorProperty.FontColor: return fontColor; + case UIStyleColorProperty.FontOutlineColor: return outlineColor; + case UIStyleColorProperty.FontShadowColor: return shadowColor; + } + + return null; + } + public static UINumber GetReferenceableNumberProperty( UIStylePropertyContainer container, UIStyleNumberProperty property ) { if ( container == null ) @@ -132,8 +260,367 @@ namespace Rokojori return GetReferenceableNumberProperty( style, property ); } + public static UIColor GetReferenceableColorProperty( UIStylePropertyContainer container, UIStyleColorProperty property ) + { + if ( container == null ) + { + return null; + } + + var ownProperty = container.GetUIStyleColorProperty( property ); + + if ( ownProperty != null ) + { + return ownProperty; + } + + var style = container.GetUIStyleParent(); + + return GetReferenceableColorProperty( style, property ); + } + + public static UINumberTransition GetTransition( UIStylePropertyContainer container, UIStyleNumberProperty property ) + { + switch ( property ) + { + case UIStyleNumberProperty.FontSize: + case UIStyleNumberProperty.FontOutlineSize: + case UIStyleNumberProperty.FontShadowSize: + case UIStyleNumberProperty.FontShadowOffsetX: + case UIStyleNumberProperty.FontShadowOffsetY: + { + return _GetTransition( true, container, property ); + } + } + + return _GetTransition( false, container, property ); + + } + + static UINumberTransition _GetTransition( bool inheritable, UIStylePropertyContainer container, UIStyleNumberProperty property ) + { + var transitions = container.GetNumberTransitions(); + + var index = transitions == null ? -1 : Arrays.FindIndex( transitions, t => t != null && t.property == property ); + + if ( index != -1 ) + { + return transitions[ index ]; + } + + var styleParent = container.GetUIStyleParent(); + + if ( styleParent == null ) + { + return null; + } + + var styleParentTransition = _GetTransition( false, styleParent, property ); + + if ( styleParentTransition != null ) + { + return styleParentTransition; + } + + if ( ! ( container is Control ) || ! inheritable ) + { + return null; + } + + var control = container as Control; + UINumberTransition parentTransition = null; + + NodesWalker.Get().GetInParents( control, + it => + { + if ( ! ( it is UIStylePropertyContainer ) ) + { + return false; + } + + var container = (UIStylePropertyContainer) it; + parentTransition = _GetTransition( true, container, property ); + + return parentTransition != null; + } + ); + + return parentTransition; + + } + + public static TransitionSettingsAll GetTransitionSettingsAll( UIStylePropertyContainer container ) + { + var settings = container.GetTransitionSettingsAll(); + + if ( settings != null ) + { + return settings; + } + + var styleParent = container.GetUIStyleParent(); + + if ( styleParent == null ) + { + return null; + } + + return GetTransitionSettingsAll( styleParent ); + } + + + /*public static TransitionSettings GetTransitionSettings( UIStylePropertyContainer container, FloatPropertyName floatProperty ) + { + var transition = GetTransition( container, floatProperty ); + + if ( transition != null && transition.settings != null ) + { + return transition.settings; + } + + var containerSettings = container.GetTransitionSettingsAll(); + + if ( containerSettings != null ) + { + return containerSettings; + } + + var styleParent = container.GetUIStyleParent(); + + if ( styleParent == null ) + { + return null; + } + + return GetTransitionSettings( styleParent, floatProperty ); + } + */ + public static TransitionSettings GetTransitionSettings( UIStylePropertyContainer container, UIStyleColorProperty colorProperty ) + { + var transition = GetTransition( container, colorProperty ); + + if ( transition != null && transition.settings != null ) + { + return transition.settings; + } + + var containerSettings = container.GetTransitionSettingsAll(); + + if ( containerSettings != null ) + { + return containerSettings; + } + + var styleParent = container.GetUIStyleParent(); + + if ( styleParent == null ) + { + return null; + } + + return GetTransitionSettings( styleParent, colorProperty ); + } + + public static TransitionSettings GetTransitionSettings( UIStylePropertyContainer container, UIStyleNumberProperty numberProperty ) + { + var transition = GetTransition( container, numberProperty ); + + if ( transition != null && transition.settings != null ) + { + return transition.settings; + } + + var containerSettings = container.GetTransitionSettingsAll(); + + if ( containerSettings != null ) + { + return containerSettings; + } + + var styleParent = container.GetUIStyleParent(); + + if ( styleParent == null ) + { + return null; + } + + return GetTransitionSettings( styleParent, numberProperty ); + } + + public static UIColorTransition GetTransition( UIStylePropertyContainer container, UIStyleColorProperty property ) + { + switch ( property ) + { + case UIStyleColorProperty.FontColor: + case UIStyleColorProperty.FontOutlineColor: + case UIStyleColorProperty.FontShadowColor: + { + return _GetTransition( true, container, property ); + } + } + + return _GetTransition( false, container, property ); + + } + + static UIColorTransition _GetTransition( bool inheritable, UIStylePropertyContainer container, UIStyleColorProperty property ) + { + var transitions = container.GetColorTransitions(); + + var index = transitions == null ? -1 : Arrays.FindIndex( transitions, t => t != null && t.property == property ); + + if ( index != -1 ) + { + return transitions[ index ]; + } + + var styleParent = container.GetUIStyleParent(); + + if ( styleParent == null ) + { + return null; + } + + var styleParentTransition = _GetTransition( false, styleParent, property ); + + if ( styleParentTransition != null ) + { + return styleParentTransition; + } + + if ( ! ( container is Control ) || ! inheritable ) + { + return null; + } + + var control = container as Control; + UIColorTransition parentTransition = null; + + NodesWalker.Get().GetInParents( control, + it => + { + if ( ! ( it is UIStylePropertyContainer ) ) + { + return false; + } + + var container = (UIStylePropertyContainer) it; + parentTransition = _GetTransition( true, container, property ); + + return parentTransition != null; + } + ); + + return parentTransition; + + } + + + public static UINumber GetInheritableNumberProperty( UIStylePropertyContainer container, UIStyleNumberProperty property ) + { + if ( container == null ) + { + return null; + } + + var ownProperty = container.GetUIStyleNumberProperty( property ); + + if ( ownProperty != null ) + { + return ownProperty; + } + + var parentStyle = container.GetUIStyleParent(); + var parentStyleProperty = GetReferenceableNumberProperty( parentStyle, property ); + + if ( parentStyleProperty != null ) + { + return parentStyleProperty; + } + + if ( ! ( container is Control ) ) + { + return null; + } + + var control = container as Control; + UINumber parentNumber = null; + + NodesWalker.Get().GetInParents( control, + it => + { + if ( ! ( it is UIStylePropertyContainer ) ) + { + return false; + } + + var container = (UIStylePropertyContainer) it; + parentNumber = GetReferenceableNumberProperty( container, property ); + + return parentNumber != null; + } + ); + + return parentNumber; + + } + + public static UIColor GetInheritableColorProperty( UIStylePropertyContainer container, UIStyleColorProperty property ) + { + if ( container == null ) + { + return null; + } + + var ownProperty = container.GetUIStyleColorProperty( property ); + + if ( ownProperty != null ) + { + return ownProperty; + } + + var parentStyle = container.GetUIStyleParent(); + var parentStyleProperty = GetReferenceableColorProperty( parentStyle, property ); + + if ( parentStyleProperty != null ) + { + return parentStyleProperty; + } + + if ( ! ( container is Control ) ) + { + return null; + } + + var control = container as Control; + UIColor parentColor = null; + + NodesWalker.Get().GetInParents( control, + it => + { + if ( ! ( it is UIStylePropertyContainer ) ) + { + return false; + } + + var container = (UIStylePropertyContainer) it; + parentColor = GetReferenceableColorProperty( container, property ); + + return parentColor != null; + } + ); + + return parentColor; + + } + public static UIPosition Position( UIStylePropertyContainer container ) { + if ( container == null ) + { + return UIPosition.___; + } + var ownProperty = container.GetUIPosition(); if ( ownProperty != UIPosition.___ ) @@ -141,102 +628,94 @@ namespace Rokojori return ownProperty; } - return container.GetUIPosition(); + var parent = container.GetUIStyleParent(); + + if ( parent == null ) + { + return UIPosition.___; + } + + return parent.GetUIPosition(); } - public static UINumber Width( UIStylePropertyContainer container ) + public static UILayout Layout( UIStylePropertyContainer container ) { - return GetReferenceableNumberProperty( container, UIStyleNumberProperty.Width ); + if ( container == null ) + { + return UILayout.___; + } + + var ownProperty = container.GetUILayout(); + + if ( ownProperty != UILayout.___ ) + { + return ownProperty; + } + + var parent = container.GetUIStyleParent(); + + if ( parent == null ) + { + return UILayout.___; + } + + return parent.GetUILayout(); } - public static UINumber Height( UIStylePropertyContainer container ) + public static UILineWrap LineWrap( UIStylePropertyContainer container ) { - return GetReferenceableNumberProperty( container, UIStyleNumberProperty.Height ); + if ( container == null ) + { + return UILineWrap.___; + } + + var ownProperty = container.GetUILineWrap(); + + if ( ownProperty != UILineWrap.___ ) + { + return ownProperty; + } + + var parent = container.GetUIStyleParent(); + + if ( parent == null ) + { + return UILineWrap.___; + } + + return parent.GetUILineWrap(); } - public static UINumber Left( UIStylePropertyContainer container ) + public static UINumber GetUINumberProperty( UIStylePropertyContainer container, UIStyleNumberProperty property ) { - return GetReferenceableNumberProperty( container, UIStyleNumberProperty.Left ); + switch ( property ) + { + case UIStyleNumberProperty.FontSize: + case UIStyleNumberProperty.FontOutlineSize: + case UIStyleNumberProperty.FontShadowSize: + case UIStyleNumberProperty.FontShadowOffsetX: + case UIStyleNumberProperty.FontShadowOffsetY: + { + return GetInheritableNumberProperty( container, property ); + } + } + + return GetReferenceableNumberProperty( container, property ); } - public static UINumber Right( UIStylePropertyContainer container ) + public static UIColor GetUIColorProperty( UIStylePropertyContainer container, UIStyleColorProperty property ) { - return GetReferenceableNumberProperty( container, UIStyleNumberProperty.Right ); - } + switch ( property ) + { + case UIStyleColorProperty.FontColor: + case UIStyleColorProperty.FontOutlineColor: + case UIStyleColorProperty.FontShadowColor: + { + return GetInheritableColorProperty( container, property ); + } + } - public static UINumber Top( UIStylePropertyContainer container ) - { - return GetReferenceableNumberProperty( container, UIStyleNumberProperty.Top ); - } - - public static UINumber Bottom( UIStylePropertyContainer container ) - { - return GetReferenceableNumberProperty( container, UIStyleNumberProperty.Bottom ); - } - - public static UINumber Margin( UIStylePropertyContainer container ) - { - return GetReferenceableNumberProperty( container, UIStyleNumberProperty.Margin ); - } - - public static UINumber MarginLeft( UIStylePropertyContainer container ) - { - return GetReferenceableNumberProperty( container, UIStyleNumberProperty.MarginLeft ); - } - - public static UINumber MarginRight( UIStylePropertyContainer container ) - { - return GetReferenceableNumberProperty( container, UIStyleNumberProperty.MarginRight ); - } - - public static UINumber MarginTop( UIStylePropertyContainer container ) - { - return GetReferenceableNumberProperty( container, UIStyleNumberProperty.MarginTop ); - } - - public static UINumber MarginBottom( UIStylePropertyContainer container ) - { - return GetReferenceableNumberProperty( container, UIStyleNumberProperty.MarginBottom ); - } - - public static UINumber PivotX( UIStylePropertyContainer container ) - { - return GetReferenceableNumberProperty( container, UIStyleNumberProperty.PivotX ); - } - - public static UINumber PivotY( UIStylePropertyContainer container ) - { - return GetReferenceableNumberProperty( container, UIStyleNumberProperty.PivotY ); - } - - public static UINumber Rotation( UIStylePropertyContainer container ) - { - return GetReferenceableNumberProperty( container, UIStyleNumberProperty.Rotation ); - } - - public static UINumber Scale( UIStylePropertyContainer container ) - { - return GetReferenceableNumberProperty( container, UIStyleNumberProperty.Scale ); - } - - public static UINumber ScaleX( UIStylePropertyContainer container ) - { - return GetReferenceableNumberProperty( container, UIStyleNumberProperty.ScaleX ); - } - - public static UINumber ScaleY( UIStylePropertyContainer container ) - { - return GetReferenceableNumberProperty( container, UIStyleNumberProperty.ScaleY ); - } - - public static UINumber ElementSpacing( UIStylePropertyContainer container ) - { - return GetReferenceableNumberProperty( container, UIStyleNumberProperty.ElementSpacing ); - } - - public static UINumber LineSpacing( UIStylePropertyContainer container ) - { - return GetReferenceableNumberProperty( container, UIStyleNumberProperty.LineSpacing ); + return GetReferenceableColorProperty( container, property ); } } diff --git a/Runtime/UI/Styling/UIStyleProperty.cs b/Runtime/UI/Styling/UIStyleProperty.cs index ade5289..edceb7a 100644 --- a/Runtime/UI/Styling/UIStyleProperty.cs +++ b/Runtime/UI/Styling/UIStyleProperty.cs @@ -16,6 +16,9 @@ namespace Rokojori ElementSpacing, LineSpacing, + HorizontalAlignment, + VerticalAlignment, + VerticalPlacement, Margin, MarginLeft, @@ -34,7 +37,9 @@ namespace Rokojori FontSize, FontOutlineSize, - FontShadowSize + FontShadowSize, + FontShadowOffsetX, + FontShadowOffsetY } public enum UIStyleColorProperty diff --git a/Runtime/UI/Styling/UIStylePropertyContainer.cs b/Runtime/UI/Styling/UIStylePropertyContainer.cs index c17e19c..e77f7fb 100644 --- a/Runtime/UI/Styling/UIStylePropertyContainer.cs +++ b/Runtime/UI/Styling/UIStylePropertyContainer.cs @@ -1,6 +1,7 @@ using Godot; using Rokojori; +using System.Collections.Generic; namespace Rokojori { @@ -9,6 +10,23 @@ namespace Rokojori UIStyle GetUIStyleParent(); UIPosition GetUIPosition(); + UILayout GetUILayout(); + UILineWrap GetUILineWrap(); + UINumber GetUIStyleNumberProperty( UIStyleNumberProperty property ); + UIColor GetUIStyleColorProperty( UIStyleColorProperty property ); + + ShaderUIColor[] GetShaderUIColors(); + List> GetActiveShaderUIColorTransitions(); + + ShaderUINumber[] GetShaderUINumbers(); + List> GetActiveShaderUINumberTransitions(); + + TransitionSettingsAll GetTransitionSettingsAll(); + UINumberTransition[] GetNumberTransitions(); + List> GetActiveUINumberTransitions(); + + UIColorTransition[] GetColorTransitions(); + List> GetActiveUIColorTransitions(); } } \ No newline at end of file diff --git a/Runtime/UI/Styling/UIStyling.cs b/Runtime/UI/Styling/UIStyling.cs index f6d8e20..70c7c0e 100644 --- a/Runtime/UI/Styling/UIStyling.cs +++ b/Runtime/UI/Styling/UIStyling.cs @@ -8,7 +8,7 @@ namespace Rokojori { public static bool CanPosition( Control control ) { - if ( control is UIRegion || control is UIImage || control is UIText ) + if ( control is UIRegion || control is UIImage || control is UIBorderImage || control is UIText ) { return true; } diff --git a/Runtime/UI/Transitions/ActiveStyleTransition.cs b/Runtime/UI/Transitions/ActiveStyleTransition.cs new file mode 100644 index 0000000..02ae665 --- /dev/null +++ b/Runtime/UI/Transitions/ActiveStyleTransition.cs @@ -0,0 +1,82 @@ + +using Godot; +using Rokojori; +using System.Collections.Generic; +using System; + +namespace Rokojori +{ + + [Tool] + public class ActiveStyleTransition + { + public V value; + public P propertyType; + public RJTimeLine timeLine; + public float start; + public float end; + public Curve curve; + public bool transitioning; + + public static O ProcessTransition( + UIStylePropertyContainer container, O computedValue, + List> activeTransitions, V activeValue, P property, + Func getTransitionSettings, + Func computeValue, + Func lerpValue + ) + { + var propertyTransition = activeTransitions.Find( t => t != null && EqualityComparer

.Default.Equals( t.propertyType, property ) ); + + if ( propertyTransition == null ) + { + propertyTransition = new ActiveStyleTransition(); + propertyTransition.propertyType = property; + propertyTransition.value = activeValue; + propertyTransition.transitioning = false; + + activeTransitions.Add( propertyTransition ); + + return computedValue; + } + else + { + if ( ! EqualityComparer.Default.Equals( propertyTransition.value, activeValue ) && + ! propertyTransition.transitioning && getTransitionSettings() != null ) + { + var transitionSettings = getTransitionSettings(); + propertyTransition.timeLine = transitionSettings.timeLine; + propertyTransition.start = TimeLineManager.GetPosition( transitionSettings.timeLine ); + propertyTransition.end = propertyTransition.start + transitionSettings.duration; + propertyTransition.transitioning = true; + propertyTransition.curve = transitionSettings.curve; + } + } + + if ( EqualityComparer.Default.Equals( propertyTransition.value, activeValue ) ) + { + propertyTransition.transitioning = false; + return computedValue; + } + + var computedTransitionValue = computeValue( propertyTransition.value ); + + var transitionPhase = TimeLineManager.GetRangePhase( propertyTransition.timeLine, propertyTransition.start, propertyTransition.end ); + + if ( transitionPhase >= 1 ) + { + activeTransitions.Remove( propertyTransition ); + } + + var amount = MathX.Clamp01( transitionPhase ); + var curveAmount = amount; + + if ( propertyTransition.curve != null ) + { + curveAmount = propertyTransition.curve.Sample( curveAmount ); + } + + return lerpValue( computedTransitionValue, computedValue, curveAmount ); + } + } +} \ No newline at end of file diff --git a/Runtime/UI/Transitions/TransitionSettings.cs b/Runtime/UI/Transitions/TransitionSettings.cs new file mode 100644 index 0000000..b7a0504 --- /dev/null +++ b/Runtime/UI/Transitions/TransitionSettings.cs @@ -0,0 +1,23 @@ + +using Godot; +using Rokojori; + +namespace Rokojori +{ + + [Tool] + [GlobalClass] + public partial class TransitionSettings:Resource + { + [Export] + public float duration; + + [Export] + public Curve curve; + + [Export] + public RJTimeLine timeLine; + + + } +} \ No newline at end of file diff --git a/Runtime/UI/Transitions/TransitionSettingsAll.cs b/Runtime/UI/Transitions/TransitionSettingsAll.cs new file mode 100644 index 0000000..cd07cc3 --- /dev/null +++ b/Runtime/UI/Transitions/TransitionSettingsAll.cs @@ -0,0 +1,15 @@ + +using Godot; +using Rokojori; + +namespace Rokojori +{ + + [Tool] + [GlobalClass] + public partial class TransitionSettingsAll:TransitionSettings + { + [Export] + public bool transitionAllProperties; + } +} \ No newline at end of file diff --git a/Runtime/UI/Transitions/UIColorTransition.cs b/Runtime/UI/Transitions/UIColorTransition.cs new file mode 100644 index 0000000..bc8f8b6 --- /dev/null +++ b/Runtime/UI/Transitions/UIColorTransition.cs @@ -0,0 +1,19 @@ + +using Godot; +using Rokojori; + +namespace Rokojori +{ + + [Tool] + [GlobalClass] + public partial class UIColorTransition : Resource + { + [Export] + public UIStyleColorProperty property; + + [Export] + public TransitionSettings settings; + + } +} \ No newline at end of file diff --git a/Runtime/UI/Transitions/UINumberTransition.cs b/Runtime/UI/Transitions/UINumberTransition.cs new file mode 100644 index 0000000..0c25f21 --- /dev/null +++ b/Runtime/UI/Transitions/UINumberTransition.cs @@ -0,0 +1,18 @@ + +using Godot; +using Rokojori; + +namespace Rokojori +{ + + [Tool] + [GlobalClass] + public partial class UINumberTransition : Resource + { + [Export] + public UIStyleNumberProperty property; + + [Export] + public TransitionSettings settings; + } +} \ No newline at end of file diff --git a/Runtime/UI/UI-Settings-Default.tres b/Runtime/UI/UI-Settings-Default.tres new file mode 100644 index 0000000..8f58e7a --- /dev/null +++ b/Runtime/UI/UI-Settings-Default.tres @@ -0,0 +1,8 @@ +[gd_resource type="Resource" script_class="UISettings" load_steps=3 format=3 uid="uid://dp57o0ykhkqfj"] + +[ext_resource type="Script" path="res://Scripts/Rokojori/Rokojori-Action-Library/Runtime/UI/UISettings.cs" id="1_5a283"] +[ext_resource type="Resource" uid="uid://bhy8b3gopkq4m" path="res://Scripts/Rokojori/Rokojori-Action-Library/Runtime/UI/ShaderProperties/Vector2/Size.tres" id="2_cdd3u"] + +[resource] +script = ExtResource("1_5a283") +sizePropertyName = ExtResource("2_cdd3u") diff --git a/Runtime/UI/UI.cs b/Runtime/UI/UI.cs index f90a0b8..98e23aa 100644 --- a/Runtime/UI/UI.cs +++ b/Runtime/UI/UI.cs @@ -8,6 +8,9 @@ namespace Rokojori [GlobalClass] public partial class UI : Control { + [Export] + public UISettings settings; + [Export] public UINumber fontSize; diff --git a/Runtime/UI/UISettings.cs b/Runtime/UI/UISettings.cs new file mode 100644 index 0000000..523a258 --- /dev/null +++ b/Runtime/UI/UISettings.cs @@ -0,0 +1,13 @@ + +using Godot; + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class UISettings : Resource + { + [Export] + public Vector2PropertyName sizePropertyName; + } +} \ No newline at end of file diff --git a/Runtime/VirtualCameras/FollowCamera3D.cs b/Runtime/VirtualCameras/FollowCamera3D.cs index 12cdbac..b289012 100644 --- a/Runtime/VirtualCameras/FollowCamera3D.cs +++ b/Runtime/VirtualCameras/FollowCamera3D.cs @@ -49,13 +49,13 @@ namespace Rokojori void Rotate( float delta ) { - var currentRotation = Math3D.GetGlobalRotation( this ); + var currentRotation = Math3D.GetGlobalQuaternion( this ); LookAt( target.GlobalPosition, Vector3.Up, true ); - var nextRotation = Math3D.GetGlobalRotation( this ); + var nextRotation = Math3D.GetGlobalQuaternion( this ); var smoothedRotation = smoother.SmoothWithCoefficient( currentRotation, nextRotation, rotationSmoothingCoefficient, delta ); - Math3D.SetGlobalRotation( this, smoothedRotation ); + Math3D.SetGlobalRotationTo( this, smoothedRotation ); } } } \ No newline at end of file diff --git a/Tools/GizmoDrawer.cs b/Tools/GizmoDrawer.cs new file mode 100644 index 0000000..3b24f95 --- /dev/null +++ b/Tools/GizmoDrawer.cs @@ -0,0 +1,46 @@ + +using Godot; +using Rokojori; +using System.Collections.Generic; + +namespace Rokojori +{ + public interface GizmoDrawer + { + void DrawGizmo( EditorNode3DGizmoPlugin gizmoPlugin, EditorNode3DGizmo gizmo ); + + } + + public interface GizmoDrawerWithHandles:GizmoDrawer + { + string GetHandleName( EditorNode3DGizmo gizmo, int handleId, bool secondary ); + Variant GetHandleValue( EditorNode3DGizmo gizmo, int handleId, bool secondary ); + + void SetHandle( EditorNode3DGizmo gizmo, int id, bool secondary, Camera3D camera, Vector2 point ); + void CommitHandle( EditorNode3DGizmo gizmo, int id, bool secondary, Variant restore, bool cancel ); + + /* + + public string GetHandleName( EditorNode3DGizmo gizmo, int handleId, bool secondary ) + { + return ""; + } + + public Variant GetHandleValue( EditorNode3DGizmo gizmo, int handleId, bool secondary ) + { + return Variant.From( 0 ); + } + + public void SetHandle( EditorNode3DGizmo gizmo, int id, bool secondary, Camera3D camera, Vector2 point ) + { + + } + + public void CommitHandle( EditorNode3DGizmo gizmo, int id, bool secondary, Variant restore, bool cancel ) + { + + } + + */ + } +} \ No newline at end of file diff --git a/Tools/GizmoDrawerPlugin.cs b/Tools/GizmoDrawerPlugin.cs new file mode 100644 index 0000000..681e127 --- /dev/null +++ b/Tools/GizmoDrawerPlugin.cs @@ -0,0 +1,88 @@ + +using Godot; +using Rokojori; +using System.Collections.Generic; + +namespace Rokojori +{ + [Tool] + public partial class GizmoDrawerPlugin : EditorNode3DGizmoPlugin + { + public GizmoDrawerPlugin() + { + CreateMaterial( "main", new Color( 1, 1, 1 , 1 ) ); + } + + public override string _GetGizmoName() + { + return "Gizmo Drawer"; + } + + public override bool _HasGizmo( Node3D node ) + { + return node is GizmoDrawer; + } + + public override void _Redraw( EditorNode3DGizmo gizmo ) + { + var gizmoDrawer = gizmo.GetNode3D() as GizmoDrawer; + + if ( gizmoDrawer == null ) + { + return; + } + + gizmoDrawer.DrawGizmo( this, gizmo ); + + } + + public override string _GetHandleName( EditorNode3DGizmo gizmo, int handleId, bool secondary ) + { + var gizmoDrawerWithHandles = gizmo.GetNode3D() as GizmoDrawerWithHandles; + + if ( gizmoDrawerWithHandles == null ) + { + return null; + } + + return gizmoDrawerWithHandles.GetHandleName( gizmo, handleId, secondary ); + } + + public override Variant _GetHandleValue( EditorNode3DGizmo gizmo, int handleId, bool secondary ) + { + var gizmoDrawerWithHandles = gizmo.GetNode3D() as GizmoDrawerWithHandles; + + if ( gizmoDrawerWithHandles == null ) + { + return Variant.CreateFrom( (string)null ); + } + + return gizmoDrawerWithHandles.GetHandleValue( gizmo, handleId, secondary ); + } + + + public override void _SetHandle( EditorNode3DGizmo gizmo, int handle_id, bool secondary, Camera3D camera, Vector2 screen_pos ) + { + var gizmoDrawerWithHandles = gizmo.GetNode3D() as GizmoDrawerWithHandles; + + if ( gizmoDrawerWithHandles == null ) + { + return; + } + + gizmoDrawerWithHandles.SetHandle( gizmo, handle_id, secondary, camera, screen_pos ); + } + + public override void _CommitHandle( EditorNode3DGizmo gizmo, int handle_id, bool secondary, Variant restore, bool cancel ) + { + var gizmoDrawerWithHandles = gizmo.GetNode3D() as GizmoDrawerWithHandles; + + if ( gizmoDrawerWithHandles == null ) + { + return; + } + + gizmoDrawerWithHandles.CommitHandle( gizmo, handle_id, secondary, restore, cancel ); + } + } +} \ No newline at end of file diff --git a/Tools/Gizmos.cs b/Tools/Gizmos.cs new file mode 100644 index 0000000..16ccec8 --- /dev/null +++ b/Tools/Gizmos.cs @@ -0,0 +1,17 @@ + +using Godot; +using Rokojori; +using System.Collections.Generic; + +namespace Rokojori +{ + [Tool] + public class Gizmos + { +#if TOOLS + + + +#endif + } +} \ No newline at end of file