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