TimeLines, VirtualCameras

This commit is contained in:
Josef 2024-05-19 17:59:41 +02:00
parent a383c5da49
commit 0e4f09e19c
24 changed files with 1503 additions and 44 deletions

View File

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="16"
viewBox="0 0 16 16"
width="16"
version="1.1"
id="svg4"
sodipodi:docname="VirtualCamera3DSlot.svg"
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
xml:space="preserve"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs8"><linearGradient
inkscape:collect="never"
id="linearGradient3074"><stop
style="stop-color:#e26708;stop-opacity:1;"
offset="0"
id="stop3070" /><stop
style="stop-color:#bb3c00;stop-opacity:1;"
offset="1"
id="stop3072" /></linearGradient><radialGradient
xlink:href="#linearGradient45008"
id="radialGradient3076"
cx="30.688875"
cy="30.069115"
fx="30.688875"
fy="30.069115"
r="14.05412"
gradientUnits="userSpaceOnUse" /><linearGradient
xlink:href="#linearGradient45008"
id="linearGradient45010"
x1="-31.87768"
y1="22.065159"
x2="-31.87768"
y2="48.78738"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(101.16951,-6.5921995)" /><linearGradient
id="linearGradient45008"><stop
style="stop-color:#e14500;stop-opacity:1;"
offset="0"
id="stop45004" /><stop
style="stop-color:#e17900;stop-opacity:1;"
offset="0.59811592"
id="stop45012" /><stop
style="stop-color:#e19c00;stop-opacity:1;"
offset="1"
id="stop45006" /></linearGradient><linearGradient
xlink:href="#linearGradient45008"
id="linearGradient46715"
x1="31.917692"
y1="47.524929"
x2="31.917692"
y2="22.632998"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(1.7923447e-6)" /></defs><sodipodi:namedview
id="namedview6"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
showgrid="false"
inkscape:zoom="22.627418"
inkscape:cx="17.390407"
inkscape:cy="0"
inkscape:window-width="3840"
inkscape:window-height="2066"
inkscape:window-x="-11"
inkscape:window-y="-11"
inkscape:window-maximized="1"
inkscape:current-layer="g2210" /><g
id="g2210"
transform="matrix(0.54328517,0,0,0.54328517,-9.4489315,-11.300948)"><path
d="m 38.807674,25.692051 a 4.270706,4.270706 0 0 0 -4.270706,3.953249 4.270706,4.270706 0 1 0 -4.270706,7.184752 v 3.097685 a 1.4235688,1.4235688 0 0 0 1.42357,1.423568 h 8.541413 a 1.4235688,1.4235688 0 0 0 1.423568,-1.423568 v -1.42357 l 4.270708,2.847138 v -8.541412 l -4.270708,2.847137 v -2.519719 a 4.270706,4.270706 0 0 0 -2.847139,-7.44526 z"
fill="#fc7f7f"
id="path66330"
style="fill:#f7b200;fill-opacity:1;stroke:none;stroke-width:1.42356;stroke-opacity:1" /><path
sodipodi:type="star"
style="fill:#f7b200;fill-opacity:1;stroke:none;stroke-width:0.975547;stroke-linecap:round;stroke-dasharray:1.95109, 1.95109"
id="path80365"
inkscape:flatsided="true"
sodipodi:sides="3"
sodipodi:cx="-6.2499995"
sodipodi:cy="6.7656245"
sodipodi:r1="2.1930733"
sodipodi:r2="1.0965366"
sodipodi:arg1="0"
sodipodi:arg2="1.0471976"
inkscape:rounded="0"
inkscape:randomized="0"
d="m -4.0569263,6.7656245 -3.2896099,1.8992572 0,-3.7985143 z"
transform="matrix(2.4507872,0,0,2.4507872,35.79769,18.74394)"
inkscape:transform-center-x="-0.73000592" /></g></svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -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

View File

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

View File

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

View File

@ -1,5 +1,6 @@
using Godot;
using System.Collections.Generic;
namespace Rokojori
{
@ -44,6 +45,37 @@ namespace Rokojori
return null;
}
public static List<T> GetDirectChildren<T>( Node parent ) where T:Node
{
if ( parent == null )
{
return null;
}
var list = new List<T>();
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<T>( Node parent, System.Action<T> action ) where T:Node
{
if ( parent == null || action == null )

View File

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

40
Runtime/Math/Math3D.cs Normal file
View File

@ -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;
}
}
}

View File

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

113
Runtime/Math/Range.cs Normal file
View File

@ -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;
}
}
}

117
Runtime/Math/RangeDouble.cs Normal file
View File

@ -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;
}
}
}

51
Runtime/Math/Smoother.cs Normal file
View File

@ -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;
}
}
}

View File

@ -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;

View File

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

View File

@ -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;
}
}

View File

@ -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<TimeLineRunner> _runners = new List<TimeLineRunner>();
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;
}
}
}

View File

@ -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<TimeLineEvent> events = new List<TimeLineEvent>();
List<TimeLineSpan> spans = new List<TimeLineSpan>();
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<int> eventRemovals = null;
List<int> 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<int>(); }
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<int>(); }
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 );
}
}
}

View File

@ -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<int> action, bool persistent = false )
{
var scheduler = Unique<TimeLineScheduler>.Get();
return scheduler._ScheduleEventIn( timeLine, offset, action, persistent );
}
public static int ScheduleEventAt( RJTimeLine timeLine, double position, Action<int> action, bool persistent = false )
{
var scheduler = Unique<TimeLineScheduler>.Get();
return scheduler._ScheduleEventAt( timeLine, position, action, persistent );
}
int _ScheduleEventAt( RJTimeLine timeLine, double position, Action<int> action, bool persistent = false )
{
AttachListeners();
var tm = Unique<TimeLineManager>.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<int> action, bool persistent = false )
{
var tm = Unique<TimeLineManager>.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<int,int> action, bool persistent = false )
{
var scheduler = Unique<TimeLineScheduler>.Get();
return scheduler._ScheduleSpan( timeLine, start, end, action, persistent );
}
public static int ScheduleSpanIn( RJTimeLine timeLine, double offset, double duration, Action<int,int> action, bool persistent = false )
{
var scheduler = Unique<TimeLineScheduler>.Get();
var tm = Unique<TimeLineManager>.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<int,int> action, bool persistent = false )
{
AttachListeners();
var tm = Unique<TimeLineManager>.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<int> _persistentEventsAndSpans = new HashSet<int>();
Dictionary<int,Action<int>> _eventActions = new Dictionary<int, Action<int>>();
Dictionary<int,Action<int,int>> _spanActions = new Dictionary<int, Action<int,int>>();
void AttachListeners()
{
if ( _listenersAttached )
{
return;
}
_listenersAttached = true;
var tm = Unique<TimeLineManager>.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 );
}
}
}

View File

@ -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;
}
}

View File

@ -33,6 +33,14 @@ namespace Rokojori
return list.Count == 0 ? default(T) : list[ list.Count - 1 ];
}
public static void RemoveIncreasingIndices<T>( List<T> list, List<int> removals )
{
for ( var i = removals.Count - 1; i >= 0; i-- )
{
list.RemoveAt( i );
}
}
public static int CountItems<T>( List<T> list, Predicate<T> test )
{
@ -117,6 +125,13 @@ namespace Rokojori
return copy;
}
public static List<T> From<[Godot.MustBeVariant] T>( Godot.Collections.Array<T> list )
{
var copy = new List<T>();
copy.AddRange( list );
return copy;
}
public static void Filter<T>( List<T> inputList, List<T> list, Func<T,int,bool> filter )
{
for ( int i = 0; i < inputList.Count; i++ )
@ -232,6 +247,21 @@ namespace Rokojori
return list;
}
public static List<U> Map<[Godot.MustBeVariant]T,U>( Godot.Collections.Array<T> inputList, Func<T,U> mapper, List<U> list = null )
{
if ( list == null )
{
list = new List<U>();
}
for ( int i = 0; i < inputList.Count; i++ )
{
list.Add( mapper( inputList[ i ] ) );
}
return list;
}
public static List<U> Map<T,U>( T[] inputList, Func<T,U> mapper, List<U> list = null )
{
if ( list == null )
@ -246,5 +276,7 @@ namespace Rokojori
return list;
}
}
}

View File

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

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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<VirtualCamera3DSlot> _cameraSlots = new List<VirtualCamera3DSlot>();
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<VirtualCamera3DSlot>( 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;
}
}
}

View File

@ -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<VirtualCamera3DManager>();
if ( vm == null )
{
return;
}
vm.SetActiveSlot( this );
}
}
}