diff --git a/Icons/VirtualCamera3DSlot.svg b/Icons/VirtualCamera3DSlot.svg new file mode 100644 index 0000000..f0b4924 --- /dev/null +++ b/Icons/VirtualCamera3DSlot.svg @@ -0,0 +1,98 @@ + + diff --git a/Icons/VirtualCamera3DSlot.svg.import b/Icons/VirtualCamera3DSlot.svg.import new file mode 100644 index 0000000..76014f3 --- /dev/null +++ b/Icons/VirtualCamera3DSlot.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bw4lwbtmuf06s" +path="res://.godot/imported/VirtualCamera3DSlot.svg-c72b6fb2631e86a90ef74119fcd12075.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Scripts/Rokojori/Rokojori-Action-Library/Icons/VirtualCamera3DSlot.svg" +dest_files=["res://.godot/imported/VirtualCamera3DSlot.svg-c72b6fb2631e86a90ef74119fcd12075.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/Actions/ActionReference.cs b/Runtime/Actions/ActionReference.cs new file mode 100644 index 0000000..1ecccaa --- /dev/null +++ b/Runtime/Actions/ActionReference.cs @@ -0,0 +1,19 @@ + +using Godot; + + +namespace Rokojori +{ + [GlobalClass ] + public partial class ActionReference : RJAction + { + [Export] + public RJAction referencedAction; + + public override void _OnTrigger() + { + Actions.Trigger( referencedAction ); + } + } + +} \ No newline at end of file diff --git a/Runtime/Actions/Delay.cs b/Runtime/Actions/Delay.cs index ec9f163..778af6e 100644 --- a/Runtime/Actions/Delay.cs +++ b/Runtime/Actions/Delay.cs @@ -13,45 +13,27 @@ namespace Rokojori [Export] public string message; - float elapsed = 0; - bool running = false; - bool finished = false; - - int runID = 0; - - public override void _Ready() - { - /// OnSequenceDone += ( id, success ) => { RJLog.Log( "OnSequenceDone", Name ); } ; - } + [Export] + public RJTimeLine timeLine; + public override void _OnTrigger() { - //RJLog.Log( "Starting", Name ); - runID = DispatchStart(); - - elapsed = 0; - running = true; - finished = false; + var sequenceID = DispatchStart(); + var eventID = TimeLineScheduler.ScheduleEventIn( timeLine, duration, + ( eventID ) => + { + RJLog.Log( ">> delay ended", Time.GetTicksMsec(), sequenceID, eventID ); + DispatchEnd( sequenceID ); + + } + ); + + RJLog.Log( ">> delay started", Time.GetTicksMsec(), sequenceID, eventID ); + } - public override void _Process( double delta ) - { - if ( ! running ) - { - return; - } - - elapsed += (float) delta; - - if ( elapsed > duration ) - { - running = false; - finished = true; - - RJLog.Log( Name, " >> '" + message + "'" ); - DispatchEnd( runID ); - } - } + } } \ No newline at end of file diff --git a/Runtime/Godot/Nodes.cs b/Runtime/Godot/Nodes.cs index 2a894d8..baa3273 100644 --- a/Runtime/Godot/Nodes.cs +++ b/Runtime/Godot/Nodes.cs @@ -1,5 +1,6 @@ using Godot; +using System.Collections.Generic; namespace Rokojori { @@ -44,6 +45,37 @@ namespace Rokojori return null; } + public static List GetDirectChildren( Node parent ) where T:Node + { + if ( parent == null ) + { + return null; + } + + var list = new List(); + + var numChildren = parent.GetChildCount(); + + for ( int i = 0; i < numChildren; i++ ) + { + var node = parent.GetChild( i ); + var script = node.GetScript(); + + RJLog.Log( "Node is", typeof(T), node.Name, node.GetType(), script.GetType(), ">>", ( node is T ) ); + + + if ( ! ( node is T ) ) + { + continue; + } + + list.Add( node as T ); + + } + + return list; + } + public static void ForEachDirectChild( Node parent, System.Action action ) where T:Node { if ( parent == null || action == null ) diff --git a/Runtime/Logging/RJLog.cs b/Runtime/Logging/RJLog.cs index 6d446e4..b158235 100644 --- a/Runtime/Logging/RJLog.cs +++ b/Runtime/Logging/RJLog.cs @@ -18,20 +18,26 @@ namespace Rokojori return; } + if ( obj is float || obj is double ) + { + output.Append( RegexUtility.WriteDouble( (double)obj ) ); + return; + } + output.Append( obj.ToString() ); } static void LogMessage( string message ) { var trace = GetTrace(); - GD.PrintRich("[b]" + message + "[/b]"); + GD.PrintRich("\n[b]" + message + "[/b]"); GD.PrintRich( trace ); } static void LogErrorMessage( string message ) { var trace = GetTrace(); - GD.PrintErr( message ); + GD.PrintErr( "\n"+ message ); GD.PrintRich( trace ); } diff --git a/Runtime/Math/Math3D.cs b/Runtime/Math/Math3D.cs new file mode 100644 index 0000000..5c91340 --- /dev/null +++ b/Runtime/Math/Math3D.cs @@ -0,0 +1,40 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; +using System.Text; +using System; + +namespace Rokojori +{ + public class Math3D + { + public static Quaternion GetGlobalRotation( Node3D node ) + { + return node.GlobalBasis.GetRotationQuaternion(); + } + + public static void SetGlobalRotation( Node3D node, Quaternion quaternion ) + { + var forward = quaternion * Vector3.Forward; + var up = quaternion * Vector3.Up; + + node.LookAt( node.GlobalPosition + forward, up ); + } + + public static Vector3 GetGlobalForward( Node3D node ) + { + return node.GlobalBasis.Z; + } + + public static Vector3 GetGlobalUp( Node3D node ) + { + return node.GlobalBasis.Y; + } + + public static Vector3 GetGlobalRight( Node3D node ) + { + return node.GlobalBasis.X; + } + } + +} \ No newline at end of file diff --git a/Runtime/Math/MathX.cs b/Runtime/Math/MathX.cs index e811c15..2f54639 100644 --- a/Runtime/Math/MathX.cs +++ b/Runtime/Math/MathX.cs @@ -8,6 +8,7 @@ namespace Rokojori { public class MathX { + public const float fps120Delta = 1/120f; public static float Clamp01( float value ) { @@ -186,17 +187,17 @@ namespace Rokojori return Mathf.Pow( power, 1f / exponent ); } - public static float SmoothingCoefficient( float ms, float reachingTarget = 0.1f, float frameDurationMS = 16.66666666f ) + public static float SmoothingCoefficient( float ms, float reachingTarget = 0.1f, float frameDurationMS = MathX.fps120Delta ) { return 1f - Base( ms / frameDurationMS, reachingTarget ); } - public static float SmoothValue( float oldValue, float newValue, float ms, float reachingTarget = 0.1f, float frameDurationMS = 16.66666666f ) + public static float SmoothValue( float oldValue, float newValue, float ms, float reachingTarget = 0.1f, float frameDurationMS = MathX.fps120Delta ) { return oldValue + SmoothingCoefficient( ms, reachingTarget, frameDurationMS ) * ( newValue - oldValue ); } - public static float SmoothDegrees( float oldValue, float newValue, float ms, float reachingTarget = 0.1f, float frameDurationMS = 16.66666666f ) + public static float SmoothDegrees( float oldValue, float newValue, float ms, float reachingTarget = 0.1f, float frameDurationMS = MathX.fps120Delta ) { oldValue = Mathf.Wrap( oldValue, 0, 360 ); newValue = Mathf.Wrap( newValue, 0, 360 ); @@ -218,7 +219,7 @@ namespace Rokojori return oldValue + SmoothingCoefficient( ms, reachingTarget, frameDurationMS ) * ( newValue - oldValue ); } - public static Vector3 SmoothVector3( Vector3 oldValue, Vector3 newValue, float ms, float reachingTarget = 0.1f, float frameDurationMS = 16.66666666f ) + public static Vector3 SmoothVector3( Vector3 oldValue, Vector3 newValue, float ms, float reachingTarget = 0.1f, float frameDurationMS = 8.33333333333f ) { return oldValue + SmoothingCoefficient( ms, reachingTarget, frameDurationMS ) * ( newValue - oldValue ); } diff --git a/Runtime/Math/Range.cs b/Runtime/Math/Range.cs new file mode 100644 index 0000000..58b9da4 --- /dev/null +++ b/Runtime/Math/Range.cs @@ -0,0 +1,113 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; + +namespace Rokojori +{ + [System.Serializable] + public class Range + { + public float min; + public float max; + + public Range( float min, float max ) + { + this.min = min; + this.max = max; + } + + public void EnsureCorrectness() + { + if ( max < min ) + { + var b = max; max = min; min = b; + } + } + + public bool Contains( float value ) + { + return min <= value && value <= max; + } + + public bool Overlaps( Range other ) + { + if ( other.max < min ) { return false; } + if ( other.min > max ) { return false; } + + if ( other.Contains( min ) || other.Contains( max ) ) + { + return true; + } + + if ( Contains( min ) || Contains( max ) ) + { + return true; + } + + return false; + } + + public float center { get { return 0.5f * ( max + min ); } } + public float range { get { return max - min; } } + public float length { get { return max - min; } } + + public float DistanceTo( Range other ) + { + var center = this.center; + var otherCenter = other.center; + + var range = this.range; + var otherRange = other.range; + + var distance = Mathf.Abs( center - otherCenter ); + + return Mathf.Max( 0, distance - 0.5f * ( range + otherRange ) ); + + } + + public Range Copy() + { + return new Range( min, max ); + } + + public float SampleAt( float normalized ) + { + return min + ( max - min ) * normalized; + } + + public static Range Of_01 + { + get { return new Range( 0, 1 ); } + } + + public static Range Of_Zero + { + get { return new Range( 0, 0 ); } + } + + public static Range Of_One + { + get { return new Range( 1, 1 ); } + } + + + public static bool Contains( float min, float max, float value ) + { + return min <= value && value <= max; + } + + public static bool ContainsExclusive( float min, float max, float value ) + { + return min < value && value < max; + } + + public static bool ContainsExclusiveMax( float min, float max, float value ) + { + return min <= value && value < max; + } + + + } + +} + diff --git a/Runtime/Math/RangeDouble.cs b/Runtime/Math/RangeDouble.cs new file mode 100644 index 0000000..2fbc947 --- /dev/null +++ b/Runtime/Math/RangeDouble.cs @@ -0,0 +1,117 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; + +namespace Rokojori +{ + [System.Serializable] + public class RangeDouble + { + public double min; + public double max; + + public RangeDouble( double min, double max ) + { + this.min = min; + this.max = max; + } + + public void EnsureCorrectness() + { + if ( max < min ) + { + var b = max; max = min; min = b; + } + } + + public bool Contains( double value ) + { + return min <= value && value <= max; + } + + public bool Overlaps( RangeDouble other ) + { + if ( other.max < min ) { return false; } + if ( other.min > max ) { return false; } + + if ( other.Contains( min ) || other.Contains( max ) ) + { + return true; + } + + if ( Contains( min ) || Contains( max ) ) + { + return true; + } + + return false; + } + + public double center { get { return 0.5f * ( max + min ); } } + public double range { get { return max - min; } } + public double length { get { return max - min; } } + + public double DistanceTo( RangeDouble other ) + { + var center = this.center; + var otherCenter = other.center; + + var RangeDouble = this.range; + var otherRangeDouble = other.range; + + var distance = Mathf.Abs( center - otherCenter ); + + return Mathf.Max( 0, distance - 0.5f * ( RangeDouble + otherRangeDouble ) ); + + } + + public RangeDouble Copy() + { + return new RangeDouble( min, max ); + } + + public double SampleAt( double normalized ) + { + return min + ( max - min ) * normalized; + } + + public static RangeDouble Of_01 + { + get { return new RangeDouble( 0, 1 ); } + } + + public static RangeDouble Of_Zero + { + get { return new RangeDouble( 0, 0 ); } + } + + public static RangeDouble Of_One + { + get { return new RangeDouble( 1, 1 ); } + } + + + public static bool Contains( double min, double max, double value ) + { + return min <= value && value <= max; + } + + public static bool ContainsExclusive( double min, double max, double value ) + { + return min < value && value < max; + } + + public static bool ContainsExclusiveMax( double min, double max, double value ) + { + return min <= value && value < max; + } + + public static bool ContainsExclusiveMin( double min, double max, double value ) + { + return min < value && value <= max; + } + + } + +} + diff --git a/Runtime/Math/Smoother.cs b/Runtime/Math/Smoother.cs new file mode 100644 index 0000000..c3f03f9 --- /dev/null +++ b/Runtime/Math/Smoother.cs @@ -0,0 +1,51 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; + +namespace Rokojori +{ + public class Smoother + { + float overflowDelta = 0; + + public float SmoothForDuration( float value, float nextValue, float duration, float delta, float processDelta = MathX.fps120Delta ) + { + var coefficient = MathX.SmoothingCoefficient( duration * 1000f ); + return SmoothWithCoefficient( value, nextValue, coefficient, delta ); + } + + + public float SmoothWithCoefficient( float value, float nextValue, float coefficient, float delta, float processDelta = MathX.fps120Delta ) + { + overflowDelta += delta; + + while ( overflowDelta > processDelta ) + { + value += coefficient * ( nextValue - value ); + overflowDelta -= processDelta; + } + + return value; + } + + + public Quaternion SmoothForDuration( Quaternion value, Quaternion nextValue, float duration, float delta, float processDelta = MathX.fps120Delta ) + { + var coefficient = MathX.SmoothingCoefficient( duration * 1000f ); + return SmoothWithCoefficient( value, nextValue, coefficient, delta ); + } + + public Quaternion SmoothWithCoefficient( Quaternion value, Quaternion nextValue, float coefficient, float delta, float processDelta = MathX.fps120Delta ) + { + overflowDelta += delta; + + while ( overflowDelta > processDelta ) + { + value = value.Slerp( nextValue, coefficient ); + overflowDelta -= processDelta; + } + + return value; + } + } +} \ No newline at end of file diff --git a/Runtime/Random/LCG.cs b/Runtime/Random/LCG.cs index 495eccb..3f010f5 100644 --- a/Runtime/Random/LCG.cs +++ b/Runtime/Random/LCG.cs @@ -29,7 +29,7 @@ namespace Rokojori var ms = Time.GetUnixTimeFromSystem() % 1; var seed = (int) ( ms * 10000 ); var lcg = new LCG(); - RJLog.Log( "Seed", seed, Time.GetUnixTimeFromSystem() ); + // RJLog.Log( "Seed", seed, Time.GetUnixTimeFromSystem() ); lcg.SetSeed( seed ); lcg.Next(); return lcg; diff --git a/Runtime/Sensors/InputSensor.cs b/Runtime/Sensors/InputSensor.cs index 6035434..5ff9d6f 100644 --- a/Runtime/Sensors/InputSensor.cs +++ b/Runtime/Sensors/InputSensor.cs @@ -10,6 +10,18 @@ namespace Rokojori [Export] public string inputActionName = ""; + [Export] + public bool pollAsButton = false; + + [Export] + public bool pollAsReleasedOnly = false; + + [Export] + public float buttonSmoothingFilterCoefficient = 1; + + [Export] + public float axisActivationTreshold = 0.75f; + float _value = 0; bool _wasActive = false; bool _isActive = false; @@ -34,13 +46,30 @@ namespace Rokojori _value = value; _wasActive = _isActive; - _isActive = _value > 0; + _isActive = _value > axisActivationTreshold; } public override void _Process( double delta ) { - var inputValue = Input.GetActionRawStrength( inputActionName ); - UpdateValue( inputValue ); + if ( pollAsButton ) + { + if ( pollAsReleasedOnly ) + { + bool state = Input.IsActionJustReleased( inputActionName ); + UpdateValue( state ? 1 : 0 ); + } + else + { + bool state = Input.IsActionPressed( inputActionName ); + UpdateValue( state ? 1 : 0 ); + } + + } + else + { + float value = Input.GetActionRawStrength( inputActionName ); + UpdateValue( value ); + } } } diff --git a/Runtime/Time/TimeLineEvent.cs b/Runtime/Time/TimeLineEvent.cs new file mode 100644 index 0000000..e9684d0 --- /dev/null +++ b/Runtime/Time/TimeLineEvent.cs @@ -0,0 +1,17 @@ + +using System.Diagnostics; +using System.Collections; +using System.Collections.Generic; +using System; +using Godot; + + +namespace Rokojori +{ + public class TimeLineEvent + { + public long id; + public bool persistent; + public double position; + } +} \ No newline at end of file diff --git a/Runtime/Time/TimeLineManager.cs b/Runtime/Time/TimeLineManager.cs new file mode 100644 index 0000000..8444692 --- /dev/null +++ b/Runtime/Time/TimeLineManager.cs @@ -0,0 +1,115 @@ + +using System.Diagnostics; +using System.Collections; +using System.Collections.Generic; +using System; +using Godot; + + +namespace Rokojori +{ + [GlobalClass] + public partial class TimeLineManager:RJTimeLineManager + { + [Export] + public RJTimeLine[] timeLines; + + List _runners = new List(); + + int _idCounter = 0; + bool _initialized = false; + + public override void _Ready() + { + Initialize(); + } + + void Initialize() + { + if ( _initialized ) + { + return; + } + + _initialized = true; + _runners = Lists.Map( timeLines, tl => new TimeLineRunner( tl ) ); + } + + TimeLineRunner GetRunner( int index ) + { + if ( index < 0 || index >= _runners.Count ) + { + return null ; + } + + return _runners[ index ]; + } + + public override int CreateID() + { + _idCounter ++; + return _idCounter; + } + + public override void _Process( double delta ) + { + _runners.ForEach( r => r.UpdateTimeLine( delta, this ) ); + } + + public override void ScheduleEvent( int timeLineIndex, double position, int callbackID, bool isPersistent ) + { + var runner = _runners[ timeLineIndex ]; + runner.ScheduleEvent( position, callbackID, isPersistent ); + } + + public override void ScheduleSpan( int timeLineIndex, double start, double end, int callbackID, bool isPersistent ) + { + var runner = _runners[ timeLineIndex ]; + runner.ScheduleSpan( start, end, callbackID, isPersistent ); + } + + public override double GetLastPosition( int timeLineIndex ) + { + var runner = GetRunner( timeLineIndex ); if ( runner == null ){ return 0; } + + return runner.lastPosition; + } + + public override double GetPosition( int timeLineIndex ) + { + var runner = GetRunner( timeLineIndex ); if ( runner == null ){ return 0; } + return runner.position; + } + + public override void SetPosition( int timeLineIndex, double position ) + { + var runner = GetRunner( timeLineIndex ); if ( runner == null ){ return; } + runner.position = position; + } + + public override double GetSpeed( int timeLineIndex ) + { + var runner = GetRunner( timeLineIndex ); if ( runner == null ){ return 0; } + return runner.speed; + } + + public override void SetSpeed( int timeLineIndex, double speed ) + { + var runner = GetRunner( timeLineIndex ); if ( runner == null ){ return; } + runner.speed = speed; + } + + + public override bool GetPlayState( int timeLineIndex ) + { + var runner = GetRunner( timeLineIndex ); if ( runner == null ){ return false; } + return runner.playing; + } + + public override void SetPlayState( int timeLineIndex, bool isPlaying ) + { + var runner = GetRunner( timeLineIndex ); if ( runner == null ){ return; } + runner.playing = isPlaying; + } + } +} \ No newline at end of file diff --git a/Runtime/Time/TimeLineRunner.cs b/Runtime/Time/TimeLineRunner.cs new file mode 100644 index 0000000..ccdaa6e --- /dev/null +++ b/Runtime/Time/TimeLineRunner.cs @@ -0,0 +1,162 @@ + +using System.Diagnostics; +using System.Collections; +using System.Collections.Generic; +using System; +using Godot; + + +namespace Rokojori +{ + public class TimeLineRunner + { + public RJTimeLine timeLine; + + public double lastPosition = 0; + public double position = 0; + public bool playing = false; + public double deltaScale = 1; + public double speed = 1; + + List events = new List(); + List spans = new List(); + + public TimeLineRunner( RJTimeLine timeLine ) + { + this.timeLine = timeLine; + + playing = timeLine.AutoStart; + } + + public void UpdateTimeLine( double delta, TimeLineManager manager ) + { + if ( ! playing ) + { + return; + } + + lastPosition = position; + position += delta * deltaScale * speed; + + var isForward = speed >= 0; + + if ( isForward ) + { + ProcessForward( manager ); + } + + } + + void ProcessForward( TimeLineManager manager ) + { + List eventRemovals = null; + List spanRemovals = null; + + + for ( int i = 0; i < events.Count; i++ ) + { + var eventPosition = events[ i ].position; + + if ( ! RangeDouble.ContainsExclusiveMax( lastPosition, position, eventPosition ) ) + { + continue; + } + + RJLog.Log( "Emitting:", + "last:", lastPosition, + "now:", position, + "event:", eventPosition + ); + + manager.EmitSignal( TimeLineManager.SignalName.OnEvent, events[ i ].id ); + + if ( events[ i ].persistent ) + { + continue; + } + + if ( eventRemovals == null ){ eventRemovals = new List(); } + + eventRemovals.Add( i ); + + } + + if ( eventRemovals != null ) + { + Lists.RemoveIncreasingIndices( events, eventRemovals ); + } + + if ( spans.Count == 0 ) + { + return; + } + + var timelineSpan = new RangeDouble( lastPosition, position ); + var span = new RangeDouble( 0, 1 ); + + for ( int i = 0; i < spans.Count; i++ ) + { + span.min = spans[ i ].start; + span.max = spans[ i ].end; + + if ( ! timelineSpan.Overlaps( span ) ) + { + continue; + } + + var isStart = timelineSpan.Contains( spans[ i ].start ); + var isEnd = timelineSpan.Contains( spans[ i ].end ); + + + var spanType = TimeLineSpan.InSpan; + + if ( isStart && isEnd ) + { + spanType = TimeLineSpan.CompletelyInside; + } + else if ( isStart ) + { + spanType = TimeLineSpan.Start; + } + else if ( isEnd ) + { + spanType = TimeLineSpan.End; + } + + manager.EmitSignal( TimeLineManager.SignalName.OnSpan, spans[ i ].id, spanType ); + + if ( spans[ i ].persistent ) + { + continue; + } + + if ( spanRemovals == null ){ spanRemovals = new List(); } + + spanRemovals.Add( i ); + + } + } + + public void ScheduleEvent( double position, int callbackID, bool isPersistent ) + { + var tle = new TimeLineEvent(); + tle.position = position; + tle.id = callbackID; + tle.persistent = isPersistent; + + events.Add( tle ); + } + + public void ScheduleSpan( double start, double end, int callbackID, bool isPersistent ) + { + var tse = new TimeLineSpan(); + tse.start = start; + tse.end = end; + tse.id = callbackID; + tse.persistent = isPersistent; + tse.wasInside = false; + + spans.Add( tse ); + } + } +} \ No newline at end of file diff --git a/Runtime/Time/TimeLineScheduler.cs b/Runtime/Time/TimeLineScheduler.cs new file mode 100644 index 0000000..945c38e --- /dev/null +++ b/Runtime/Time/TimeLineScheduler.cs @@ -0,0 +1,138 @@ + +using System.Diagnostics; +using System.Collections; +using System.Collections.Generic; +using System; +using Godot; + + +namespace Rokojori +{ + [GlobalClass] + public partial class TimeLineScheduler:Node + { + public static int ScheduleEventIn( RJTimeLine timeLine, double offset, Action action, bool persistent = false ) + { + var scheduler = Unique.Get(); + return scheduler._ScheduleEventIn( timeLine, offset, action, persistent ); + } + + public static int ScheduleEventAt( RJTimeLine timeLine, double position, Action action, bool persistent = false ) + { + var scheduler = Unique.Get(); + return scheduler._ScheduleEventAt( timeLine, position, action, persistent ); + } + + + + int _ScheduleEventAt( RJTimeLine timeLine, double position, Action action, bool persistent = false ) + { + AttachListeners(); + var tm = Unique.Get(); + var tIndex = tm.GetTimeLineIndex( timeLine ); + var id = tm.CreateID(); + _eventActions[ id ] = action; + tm.ScheduleEvent( tIndex, position, id, persistent ); + return id; + } + + int _ScheduleEventIn( RJTimeLine timeLine, double offset, Action action, bool persistent = false ) + { + var tm = Unique.Get(); + var tIndex = tm.GetTimeLineIndex( timeLine ); + var position = tm.GetPosition( tIndex ) + offset; + return _ScheduleEventAt( timeLine, position, action, persistent ); + } + + + public static int ScheduleSpanAt( RJTimeLine timeLine, double start, double end, Action action, bool persistent = false ) + { + var scheduler = Unique.Get(); + return scheduler._ScheduleSpan( timeLine, start, end, action, persistent ); + } + + public static int ScheduleSpanIn( RJTimeLine timeLine, double offset, double duration, Action action, bool persistent = false ) + { + var scheduler = Unique.Get(); + var tm = Unique.Get(); + var tIndex = tm.GetTimeLineIndex( timeLine ); + var position = tm.GetPosition( tIndex ); + var start = position + offset; + var end = start + duration; + return scheduler._ScheduleSpan( timeLine, start, end, action, persistent ); + } + + int _ScheduleSpan( RJTimeLine timeLine, double start, double end, Action action, bool persistent = false ) + { + AttachListeners(); + var tm = Unique.Get(); + var tIndex = tm.GetTimeLineIndex( timeLine ); + var id = tm.CreateID(); + _spanActions[ id ] = action; + tm.ScheduleSpan( tIndex, start, end, id, persistent ); + return id; + } + + bool _listenersAttached = false; + + + HashSet _persistentEventsAndSpans = new HashSet(); + Dictionary> _eventActions = new Dictionary>(); + Dictionary> _spanActions = new Dictionary>(); + + void AttachListeners() + { + if ( _listenersAttached ) + { + return; + } + + _listenersAttached = true; + + var tm = Unique.Get(); + + tm.OnEvent += ( id ) => OnEvent( id ); + tm.OnSpan += ( id, type ) => onSpan( id, type ); + } + + void OnEvent( long s_id ) + { + var id = (int) s_id; + + if ( ! _eventActions.ContainsKey( id ) ) + { + return; + } + + _eventActions[ id ]( id ); + + if ( _persistentEventsAndSpans.Contains( id ) ) + { + return; + } + + _eventActions.Remove( id ); + + } + + void onSpan( long s_id, long s_type ) + { + var id = (int) s_id; + var type = (int) s_type; + + if ( ! _spanActions.ContainsKey( id ) ) + { + return; + } + + _spanActions[ id ]( id, type ); + + if ( _persistentEventsAndSpans.Contains( id ) ) + { + return; + } + + _spanActions.Remove( id ); + } + } +} \ No newline at end of file diff --git a/Runtime/Time/TimeLineSpan.cs b/Runtime/Time/TimeLineSpan.cs new file mode 100644 index 0000000..9157629 --- /dev/null +++ b/Runtime/Time/TimeLineSpan.cs @@ -0,0 +1,24 @@ + +using System.Diagnostics; +using System.Collections; +using System.Collections.Generic; +using System; +using Godot; + + +namespace Rokojori +{ + public class TimeLineSpan + { + public static int Start = 0; + public static int InSpan = 1; + public static int End = 2; + public static int CompletelyInside = 3; + + public long id; + public bool persistent; + public double start; + public double end; + public bool wasInside; + } +} \ No newline at end of file diff --git a/Runtime/Tools/Lists.cs b/Runtime/Tools/Lists.cs index bb1117c..f38f7ed 100644 --- a/Runtime/Tools/Lists.cs +++ b/Runtime/Tools/Lists.cs @@ -33,6 +33,14 @@ namespace Rokojori return list.Count == 0 ? default(T) : list[ list.Count - 1 ]; } + public static void RemoveIncreasingIndices( List list, List removals ) + { + for ( var i = removals.Count - 1; i >= 0; i-- ) + { + list.RemoveAt( i ); + } + } + public static int CountItems( List list, Predicate test ) { @@ -117,6 +125,13 @@ namespace Rokojori return copy; } + public static List From<[Godot.MustBeVariant] T>( Godot.Collections.Array list ) + { + var copy = new List(); + copy.AddRange( list ); + return copy; + } + public static void Filter( List inputList, List list, Func filter ) { for ( int i = 0; i < inputList.Count; i++ ) @@ -232,6 +247,21 @@ namespace Rokojori return list; } + public static List Map<[Godot.MustBeVariant]T,U>( Godot.Collections.Array inputList, Func mapper, List list = null ) + { + if ( list == null ) + { + list = new List(); + } + + for ( int i = 0; i < inputList.Count; i++ ) + { + list.Add( mapper( inputList[ i ] ) ); + } + + return list; + } + public static List Map( T[] inputList, Func mapper, List list = null ) { if ( list == null ) @@ -246,5 +276,7 @@ namespace Rokojori return list; } + + } } \ No newline at end of file diff --git a/Runtime/VirtualCameras/FollowCamera3D.cs b/Runtime/VirtualCameras/FollowCamera3D.cs new file mode 100644 index 0000000..12cdbac --- /dev/null +++ b/Runtime/VirtualCameras/FollowCamera3D.cs @@ -0,0 +1,61 @@ + +using System.Diagnostics; +using System.Collections; +using System.Collections.Generic; +using System; +using Godot; + + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class FollowCamera3D:VirtualCamera3D + { + [Export] + public Node3D target; + [Export] + public float speed = 5; + [Export] + public float distance = 5; + [Export] + public float rotationSmoothingCoefficient = 0.1f; + + Smoother smoother = new Smoother(); + + public override void _Process( double delta ) + { + if ( target == null ) + { + return; + } + + var d = (float) delta; + + Move( d ); + Rotate( d ); + } + + void Move( float delta ) + { + var direction = target.GlobalPosition - GlobalPosition; + + if ( direction.Length() > distance ) + { + var step = direction.Normalized() * speed * delta; + GlobalPosition += step; + } + } + + void Rotate( float delta ) + { + var currentRotation = Math3D.GetGlobalRotation( this ); + LookAt( target.GlobalPosition, Vector3.Up, true ); + var nextRotation = Math3D.GetGlobalRotation( this ); + + var smoothedRotation = smoother.SmoothWithCoefficient( currentRotation, nextRotation, rotationSmoothingCoefficient, delta ); + + Math3D.SetGlobalRotation( this, smoothedRotation ); + } + } +} \ No newline at end of file diff --git a/Runtime/VirtualCameras/MouseEditorCamera.cs b/Runtime/VirtualCameras/MouseEditorCamera.cs new file mode 100644 index 0000000..016701a --- /dev/null +++ b/Runtime/VirtualCameras/MouseEditorCamera.cs @@ -0,0 +1,173 @@ + +using System.Diagnostics; +using System.Collections; +using System.Collections.Generic; +using System; +using Godot; + + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class MouseEditorCamera:VirtualCamera3D + { + [Export] + public Vector3 target; + [Export] + public float yaw = 0; + + [Export] + public float pitch = 0; + + [Export] + public float distance = 10; + float smoothDistance = 10; + + [ExportCategory("Orbit")] + + [Export] + public float yawSpeed = 1; + + [Export] + public float pitchSpeed = 1; + + [Export] + public float minPitch = -89; + + [Export] + public float maxPitch = 89; + + [Export] + public RJSensor orbitButton; + + [Export] + public RJSensor[] orbitModifierButtons; + + [ExportCategory("Pan")] + + [Export] + public float panSpeedX = 1f; + + [Export] + public float panSpeedY = 1f; + + [Export] + public RJSensor panButton; + + [Export] + public RJSensor[] panModifierButtons; + + [ExportCategory("Zoom")] + + [Export] + public float zoomStepInPercentage = 10; + + [Export] + public float minDistance = 0.001f; + + [Export] + public float maxDistance = 200f; + + + [Export] + public RJSensor zoomInButton; + + [Export] + public RJSensor[] zoomInModifierButtons; + + [Export] + public RJSensor zoomOutButton; + + [Export] + public RJSensor[] zoomOutModifierButtons; + + [Export] + public float zoomSmoothingCoefficient = 0.1f; + Smoother smoother = new Smoother(); + + public override void _Process( double delta ) + { + Orbit(); + Pan(); + Zoom(); + + Apply( (float) delta ); + + if ( ! hasMotionDelta ) + { + motionDelta.X = 0; + motionDelta.Y = 0; + } + + hasMotionDelta = false; + } + + bool hasMotionDelta = false; + Vector2 motionDelta = Vector2.Zero; + + public override void _Input( InputEvent inputEvent ) + { + if ( inputEvent is InputEventMouseMotion ) + { + var eventMouseMotion = inputEvent as InputEventMouseMotion; + motionDelta = eventMouseMotion.ScreenRelative; + hasMotionDelta = true; + } + + } + + void Orbit() + { + if ( ! orbitButton.IsActive() ) + { + return; + } + + yaw += motionDelta.X * yawSpeed; + pitch += motionDelta.Y * pitchSpeed; + + pitch = Mathf.Clamp( pitch, minPitch, maxPitch ); + + } + + void Pan() + { + if ( ! panButton.IsActive() ) + { + return; + } + + var xAmount = motionDelta.X * smoothDistance * GlobalBasis.X * panSpeedX; + var yAmount = motionDelta.Y * smoothDistance * GlobalBasis.Y * panSpeedY; + + target += xAmount + yAmount; + + } + + void Zoom() + { + if ( zoomInButton.IsActive() ) + { + distance *= Mathf.Pow( 1 + zoomStepInPercentage / 100f, 1 ); + } + + if ( zoomOutButton.IsActive() ) + { + distance *= Mathf.Pow( 1 + zoomStepInPercentage / 100f, -1 ); + } + + distance = Mathf.Clamp( distance, minDistance, maxDistance ); + } + + + void Apply( float delta ) + { + smoothDistance = smoother.SmoothWithCoefficient( smoothDistance, distance, zoomSmoothingCoefficient, delta ); + GlobalRotation = new Vector3( Mathf.DegToRad( pitch ), Mathf.DegToRad( yaw ), 0 ); + + var forward = Math3D.GetGlobalForward( this ) * smoothDistance; + GlobalPosition = target - forward; + } + } +} \ No newline at end of file diff --git a/Runtime/VirtualCameras/VirtualCamera3D.cs b/Runtime/VirtualCameras/VirtualCamera3D.cs new file mode 100644 index 0000000..c6e7022 --- /dev/null +++ b/Runtime/VirtualCameras/VirtualCamera3D.cs @@ -0,0 +1,34 @@ + +using System.Diagnostics; +using System.Collections; +using System.Collections.Generic; +using System; +using Godot; + + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class VirtualCamera3D:RJVirtualCamera3D + { + [Export] + public float fov = 60; + + public override Vector3 GetCameraPosition() + { + return GlobalPosition; + } + + public override Quaternion GetCameraRotation() + { + return GlobalBasis.GetRotationQuaternion(); + } + + public override float GetCameraFOV() + { + return fov; + } + + } +} \ No newline at end of file diff --git a/Runtime/VirtualCameras/VirtualCamera3DManager.cs b/Runtime/VirtualCameras/VirtualCamera3DManager.cs new file mode 100644 index 0000000..32b693c --- /dev/null +++ b/Runtime/VirtualCameras/VirtualCamera3DManager.cs @@ -0,0 +1,128 @@ + +using System.Diagnostics; +using System.Collections; +using System.Collections.Generic; +using System; +using Godot; + + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class VirtualCamera3DManager:RJVirtualCamera3DManager + { + [Export] + public Camera3D camera; + + [Export] + public bool refreshSlots = false; + + public override void _Process( double delta ) + { + LerpCameras( delta ); + } + + public float smoothStepDelta => 1f / CameraPrioritySmoothingStepFPS; + public float safeSmoothing => Mathf.Max( 0, CameraPrioritySmoothingCoefficient ); + + List _cameraSlots = new List(); + + public void SetActiveSlot( VirtualCamera3DSlot slot ) + { + _cameraSlots.ForEach( c => c.priority = c == slot ? 1 : 0 ); + } + + void LerpCameras( double delta ) + { + if ( refreshSlots || _cameraSlots == null || _cameraSlots.Count == 0 ) + { + refreshSlots = false; + _cameraSlots = Nodes.GetDirectChildren( this ); + + //RJLog.Log( "GRABBED SLOTs" , _cameraSlots.Count ); + } + + var sumPriority = 0f; + + _cameraSlots.ForEach( + c => + { + c.Update( delta, this ); + sumPriority += MathF.Max( 0, c.smoothedPriority ); + } + ); + + if ( sumPriority == 0 ) + { + return; + } + + var position = new Vector3(); + var up = new Vector3(); + var forward = new Vector3(); + + _cameraSlots.ForEach( + c => + { + var priority = MathF.Max( 0, c.smoothedPriority ); + var rotation = c.camera.GetCameraRotation(); + var vUp = rotation * Vector3.Up; + var vForward = rotation * Vector3.Forward; + + position += priority * c.camera.GetCameraPosition(); + up += priority * vUp; + forward += priority * vForward; + } + ); + + position /= sumPriority; + + if ( forward.LengthSquared() == 0 ) + { + forward = camera.Basis.Z; + } + else + { + forward = forward.Normalized(); + } + + if ( up.LengthSquared() == 0 ) + { + up = camera.Basis.Y; + } + else + { + up = up.Normalized(); + } + + + camera.GlobalPosition = position; + camera.LookAt( position - forward, up ); + + } + + public override RJVirtualCamera3D GetCamera( int index ) + { + return _cameraSlots[ index ].camera; + } + + public override int GetCameraIndex( RJVirtualCamera3D camera3D ) + { + return _cameraSlots.FindIndex( c => c.camera == camera3D ); + } + + public override float GetCameraPriority( int index ) + { + return _cameraSlots[ index ].priority; + } + + public override void SetCameraPriority( int index, float priority ) + { + _cameraSlots[ index ].priority = priority; + } + + + + } +} \ No newline at end of file diff --git a/Runtime/VirtualCameras/VirtualCamera3DSlot.cs b/Runtime/VirtualCameras/VirtualCamera3DSlot.cs new file mode 100644 index 0000000..9e12d02 --- /dev/null +++ b/Runtime/VirtualCameras/VirtualCamera3DSlot.cs @@ -0,0 +1,50 @@ + +using System.Diagnostics; +using System.Collections; +using System.Collections.Generic; +using System; +using Godot; + + +namespace Rokojori +{ + [Tool] + [Icon("res://Scripts/Rokojori/Rokojori-Action-Library/Icons/VirtualCamera3DSlot.svg") ] + [GlobalClass] + public partial class VirtualCamera3DSlot:RJAction + { + [Export] + public RJVirtualCamera3D camera; + + [Export] + public float priority; + + float _smoothedPriority; + public float smoothedPriority => _smoothedPriority; + + Smoother smoother = new Smoother(); + + public void Update( double delta, VirtualCamera3DManager manager ) + { + + _smoothedPriority = smoother.SmoothWithCoefficient( _smoothedPriority, priority, + manager.safeSmoothing, + (float) delta, manager.smoothStepDelta ); + + } + + public override void _OnTrigger() + { + var vm = GetParent(); + + if ( vm == null ) + { + return; + } + + vm.SetActiveSlot( this ); + } + + + } +}