diff --git a/Icons/RemoveVirtualCamera3D.svg b/Icons/RemoveVirtualCamera3D.svg
new file mode 100644
index 0000000..cac7800
--- /dev/null
+++ b/Icons/RemoveVirtualCamera3D.svg
@@ -0,0 +1,97 @@
+
+
diff --git a/Icons/RemoveVirtualCamera3D.svg.import b/Icons/RemoveVirtualCamera3D.svg.import
new file mode 100644
index 0000000..c2ed1a8
--- /dev/null
+++ b/Icons/RemoveVirtualCamera3D.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://beh11ebwsi3nj"
+path="res://.godot/imported/RemoveVirtualCamera3D.svg-b229129ca4dd411d22a781c703e04259.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/rokojori_action_library/Icons/RemoveVirtualCamera3D.svg"
+dest_files=["res://.godot/imported/RemoveVirtualCamera3D.svg-b229129ca4dd411d22a781c703e04259.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/Icons/SetActiveVirtualCamera3D.svg b/Icons/SetActiveVirtualCamera3D.svg
new file mode 100644
index 0000000..8f78ce1
--- /dev/null
+++ b/Icons/SetActiveVirtualCamera3D.svg
@@ -0,0 +1,101 @@
+
+
diff --git a/Icons/SetActiveVirtualCamera3D.svg.import b/Icons/SetActiveVirtualCamera3D.svg.import
new file mode 100644
index 0000000..df7cdda
--- /dev/null
+++ b/Icons/SetActiveVirtualCamera3D.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cwtmomxhbrhh4"
+path="res://.godot/imported/SetActiveVirtualCamera3D.svg-daa089c09a78ae1c001e1e1ec4c40af0.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/rokojori_action_library/Icons/SetActiveVirtualCamera3D.svg"
+dest_files=["res://.godot/imported/SetActiveVirtualCamera3D.svg-daa089c09a78ae1c001e1e1ec4c40af0.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/Icons/VirtualCamera3D.svg b/Icons/VirtualCamera3D.svg
index a254aa7..d9e08cb 100644
--- a/Icons/VirtualCamera3D.svg
+++ b/Icons/VirtualCamera3D.svg
@@ -66,8 +66,8 @@
inkscape:deskcolor="#505050"
showgrid="false"
inkscape:zoom="22.627418"
- inkscape:cx="8.1538247"
- inkscape:cy="2.320194"
+ inkscape:cx="12.308077"
+ inkscape:cy="11.203223"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
@@ -76,7 +76,7 @@
inkscape:current-layer="g2210" />
+ style="fill:#f78500;fill-opacity:1;stroke:none;stroke-width:1.72262;stroke-opacity:1" />
diff --git a/Runtime/Actions/ActionReference.cs b/Runtime/Actions/ActionReference.cs
index a60de5d..4abb6f6 100644
--- a/Runtime/Actions/ActionReference.cs
+++ b/Runtime/Actions/ActionReference.cs
@@ -4,7 +4,7 @@ using Godot;
namespace Rokojori
{
- [GlobalClass ]
+ [Tool][GlobalClass ]
public partial class ActionReference : Action
{
[Export]
diff --git a/Runtime/Actions/Conditional/Once.cs b/Runtime/Actions/Conditional/Once.cs
new file mode 100644
index 0000000..de5c2c1
--- /dev/null
+++ b/Runtime/Actions/Conditional/Once.cs
@@ -0,0 +1,30 @@
+
+using Godot;
+using System.Collections.Generic;
+
+
+namespace Rokojori
+{
+ [Tool]
+ [GlobalClass, Icon("res://addons/rokojori_action_library/Icons/ConditionalAction.svg")]
+ public partial class Once : Action
+ {
+ [Export]
+ public Action action;
+
+ [Export]
+ public bool canTrigger = true;
+
+ protected override void _OnTrigger()
+ {
+ if ( ! canTrigger )
+ {
+ return;
+ }
+
+ canTrigger = false;
+ Action.Trigger( action );
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/Runtime/Actions/Conditional/Once.cs.uid b/Runtime/Actions/Conditional/Once.cs.uid
new file mode 100644
index 0000000..a209203
--- /dev/null
+++ b/Runtime/Actions/Conditional/Once.cs.uid
@@ -0,0 +1 @@
+uid://crm7o7w0gumhn
diff --git a/Runtime/Actions/IterateActions.cs b/Runtime/Actions/IterateActions.cs
index 813a11d..1192fc3 100644
--- a/Runtime/Actions/IterateActions.cs
+++ b/Runtime/Actions/IterateActions.cs
@@ -4,7 +4,7 @@ using Godot;
namespace Rokojori
{
- [GlobalClass ]
+ [Tool][GlobalClass ]
public partial class IterateActions : Action
{
[ExportGroup( "Read Only")]
diff --git a/Runtime/Actions/LoadScene.cs b/Runtime/Actions/LoadScene.cs
index be40cc5..b34dfcd 100644
--- a/Runtime/Actions/LoadScene.cs
+++ b/Runtime/Actions/LoadScene.cs
@@ -4,7 +4,7 @@ using System.Collections.Generic;
namespace Rokojori
{
- [GlobalClass]
+ [Tool][GlobalClass]
public partial class LoadScene : SequenceAction
{
[Export]
diff --git a/Runtime/Actions/Node/SetNodeState.cs b/Runtime/Actions/Node/SetNodeState.cs
index 93e5743..8a067ce 100644
--- a/Runtime/Actions/Node/SetNodeState.cs
+++ b/Runtime/Actions/Node/SetNodeState.cs
@@ -4,7 +4,7 @@ using Godot;
namespace Rokojori
{
- [GlobalClass ]
+ [Tool][GlobalClass ]
public partial class SetNodeState : Action
{
[Export]
diff --git a/Runtime/Actions/Node3D/CopyMousePosition.cs b/Runtime/Actions/Node3D/CopyMousePosition.cs
index 903a479..d474a4b 100644
--- a/Runtime/Actions/Node3D/CopyMousePosition.cs
+++ b/Runtime/Actions/Node3D/CopyMousePosition.cs
@@ -4,7 +4,7 @@ using Godot;
namespace Rokojori
{
- [GlobalClass ]
+ [Tool][GlobalClass ]
public partial class CopyMousePosition : Action
{
[Export]
diff --git a/Runtime/Actions/Node3D/CopyPose.cs b/Runtime/Actions/Node3D/CopyPose.cs
index ef40782..0e560a4 100644
--- a/Runtime/Actions/Node3D/CopyPose.cs
+++ b/Runtime/Actions/Node3D/CopyPose.cs
@@ -4,7 +4,7 @@ using Godot;
namespace Rokojori
{
- [GlobalClass, Tool ]
+ [Tool][GlobalClass ]
public partial class CopyPose : Action
{
[Export]
diff --git a/Runtime/Actions/Node3D/LerpPosition.cs b/Runtime/Actions/Node3D/LerpPosition.cs
index 14df5b6..ace1046 100644
--- a/Runtime/Actions/Node3D/LerpPosition.cs
+++ b/Runtime/Actions/Node3D/LerpPosition.cs
@@ -3,7 +3,7 @@ using Godot;
namespace Rokojori
{
- [GlobalClass ]
+ [Tool][GlobalClass ]
public partial class LerpPosition : Action
{
[Export]
diff --git a/Runtime/Actions/Node3D/PlayParticles.cs b/Runtime/Actions/Node3D/PlayParticles.cs
index 67c93af..9f4a46b 100644
--- a/Runtime/Actions/Node3D/PlayParticles.cs
+++ b/Runtime/Actions/Node3D/PlayParticles.cs
@@ -3,7 +3,7 @@ using Godot;
namespace Rokojori
{
- [GlobalClass]
+ [Tool][GlobalClass]
public partial class PlayParticles:Action
{
[Export]
diff --git a/Runtime/Actions/Node3D/PlaySound.cs b/Runtime/Actions/Node3D/PlaySound.cs
index 212fee9..5196fd3 100644
--- a/Runtime/Actions/Node3D/PlaySound.cs
+++ b/Runtime/Actions/Node3D/PlaySound.cs
@@ -3,7 +3,7 @@ using Godot;
namespace Rokojori
{
- [GlobalClass]
+ [Tool][GlobalClass]
public partial class PlaySound:Action
{
[Export]
diff --git a/Runtime/Actions/OnPhysicsProcess.cs b/Runtime/Actions/OnPhysicsProcess.cs
index 9340f2e..715522c 100644
--- a/Runtime/Actions/OnPhysicsProcess.cs
+++ b/Runtime/Actions/OnPhysicsProcess.cs
@@ -4,7 +4,7 @@ using Godot;
namespace Rokojori
{
- [GlobalClass, Icon("res://addons/rokojori_action_library/Icons/OnEvent.svg") ]
+ [Tool][GlobalClass, Icon("res://addons/rokojori_action_library/Icons/OnEvent.svg") ]
public partial class OnPhysicsProcess : Node
{
/** Actions to execute*/
diff --git a/Runtime/Actions/OnReady.cs b/Runtime/Actions/OnReady.cs
index 28fe0c1..5d2194b 100644
--- a/Runtime/Actions/OnReady.cs
+++ b/Runtime/Actions/OnReady.cs
@@ -4,7 +4,7 @@ using Godot;
namespace Rokojori
{
- [GlobalClass, Icon("res://addons/rokojori_action_library/Icons/OnEvent.svg") ]
+ [Tool][GlobalClass, Icon("res://addons/rokojori_action_library/Icons/OnEvent.svg") ]
public partial class OnReady : Node
{
/** Actions to execute*/
diff --git a/Runtime/Actions/Sequence/Parallel.cs b/Runtime/Actions/Sequence/Parallel.cs
index 148eb94..0b49668 100644
--- a/Runtime/Actions/Sequence/Parallel.cs
+++ b/Runtime/Actions/Sequence/Parallel.cs
@@ -4,7 +4,7 @@ using System.Collections.Generic;
namespace Rokojori
{
- [GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Parallel.svg") ]
+ [Tool][GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Parallel.svg") ]
public partial class Parallel : SequenceAction
{
public enum Mode
diff --git a/Runtime/Actions/Sequence/RepeatSequence.cs b/Runtime/Actions/Sequence/RepeatSequence.cs
new file mode 100644
index 0000000..ca60e05
--- /dev/null
+++ b/Runtime/Actions/Sequence/RepeatSequence.cs
@@ -0,0 +1,87 @@
+
+using Godot;
+using System.Collections.Generic;
+
+namespace Rokojori
+{
+ [Tool][GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Parallel.svg") ]
+ public partial class RepeatSequence : SequenceAction
+ {
+ [Export]
+ public Action action;
+
+
+ [Export]
+ public int maxNumRepeats = 3;
+
+ [Export]
+ public float maxRepeatDuration = 60;
+
+ [Export]
+ public TimeLine timeLine;
+
+
+ protected override void _OnTrigger()
+ {
+
+ var id = DispatchStart();
+
+ if ( ! ( action is SequenceAction ) )
+ {
+ Trigger( action );
+ DispatchEnd( id );
+ return;
+ }
+
+ var sa = (SequenceAction) action;
+
+ var executed = 0;
+
+ System.Action callBack = null;
+
+ var tl = TimeLineManager.Ensure( timeLine );
+
+ if ( maxNumRepeats <= 0 && ( tl == null || maxRepeatDuration <= 0 ) )
+ {
+ return;
+ }
+
+ var startTime = tl.position;
+ var endTime = tl.position + maxRepeatDuration;
+
+ callBack = ( a )=>
+ {
+ if ( ! a.success )
+ {
+ DispatchCancelled( id );
+ sa.onSequenceDone.RemoveAction( callBack );
+ return;
+ }
+
+ executed ++;
+
+ var finished = ( maxNumRepeats > 0 && executed >= maxNumRepeats ) ||
+ ( tl != null && tl.position >= endTime );
+
+ if ( finished )
+ {
+ DispatchEnd( id );
+ sa.onSequenceDone.RemoveAction( callBack );
+ return;
+ }
+ else
+ {
+ Trigger( action );
+ }
+
+ };
+
+
+ sa.onSequenceDone.AddAction( callBack );
+
+
+ Trigger( action );
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/Runtime/Actions/Sequence/RepeatSequence.cs.uid b/Runtime/Actions/Sequence/RepeatSequence.cs.uid
new file mode 100644
index 0000000..a33483e
--- /dev/null
+++ b/Runtime/Actions/Sequence/RepeatSequence.cs.uid
@@ -0,0 +1 @@
+uid://j41ppn275x8i
diff --git a/Runtime/Actions/Time/Delay.cs b/Runtime/Actions/Time/Delay.cs
index b2a5cb1..d50e47b 100644
--- a/Runtime/Actions/Time/Delay.cs
+++ b/Runtime/Actions/Time/Delay.cs
@@ -4,7 +4,7 @@ using Godot;
namespace Rokojori
{
- [GlobalClass]
+ [Tool][GlobalClass]
public partial class Delay : SequenceAction
{
[Export]
diff --git a/Runtime/Actions/Time/TweenTimeLineSpeed.cs b/Runtime/Actions/Time/TweenTimeLineSpeed.cs
new file mode 100644
index 0000000..d51e1f7
--- /dev/null
+++ b/Runtime/Actions/Time/TweenTimeLineSpeed.cs
@@ -0,0 +1,77 @@
+
+using System;
+using Godot;
+
+
+namespace Rokojori
+{
+ [Tool]
+ [GlobalClass ]
+ public partial class TweenTimeLineSpeed:SequenceAction
+ {
+ [Export]
+ public TimeLine targetTimeLine;
+
+ [Export]
+ public float timeLineSpeed;
+
+ [Export]
+ public TweenType tweenType = new TweenTimeCurve();
+
+ [Export]
+ public bool cacheSpeedOnStart = true;
+
+ [Export]
+ public TimeLine tweenTimeLine;
+
+ protected override void _OnTrigger()
+ {
+ if ( targetTimeLine == null )
+ {
+ return;
+ }
+
+ var tl = tweenTimeLine == null ? TimeLineManager.Get().realtimeTimeline : tweenTimeLine;
+
+ var start = targetTimeLine.position;
+
+ var fromSpeed = targetTimeLine.runner.speed;
+ var toSpeed = timeLineSpeed;
+
+
+ var sequenceID = DispatchStart();
+
+ var tweenType = this.tweenType;
+
+ if ( tweenType == null )
+ {
+ tweenType = TweenTimeCurve.defaultCurve;
+ }
+
+ TimeLineManager.ScheduleSpanIn( tl, 0, tweenType.GetTweenDuration(),
+ ( span, type )=>
+ {
+ var timeNow = tl.position;
+ var elapsed = timeNow - start;
+
+ var state = tweenType.GetTweenPhaseForPhase( span.phase );
+
+ if ( ! cacheSpeedOnStart )
+ {
+ toSpeed = timeLineSpeed;
+ }
+
+ var lerpedSpeed = Mathf.Lerp( fromSpeed, toSpeed, state );
+
+ targetTimeLine.runner.speed = lerpedSpeed;
+
+ if ( type == TimeLineSpanUpdateType.End )
+ {
+ DispatchEnd( sequenceID );
+ }
+ }
+ );
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Runtime/Actions/Time/TweenTimeLineSpeed.cs.uid b/Runtime/Actions/Time/TweenTimeLineSpeed.cs.uid
new file mode 100644
index 0000000..76b62a4
--- /dev/null
+++ b/Runtime/Actions/Time/TweenTimeLineSpeed.cs.uid
@@ -0,0 +1 @@
+uid://b33s5utxleah2
diff --git a/Runtime/Actions/Visual/TweenParticles.cs b/Runtime/Actions/Visual/TweenParticles.cs
new file mode 100644
index 0000000..7e08917
--- /dev/null
+++ b/Runtime/Actions/Visual/TweenParticles.cs
@@ -0,0 +1,70 @@
+
+using System;
+using Godot;
+
+
+namespace Rokojori
+{
+ [Tool]
+ [GlobalClass ]
+ public partial class TweenParticles:SequenceAction
+ {
+ [Export]
+ public GpuParticles3D particles3D;
+
+ [Export]
+ public TweenParticlesData tweenParticlesData;
+
+ [Export]
+ public TweenType tweenType = new TweenTimeCurve();
+
+ [Export]
+ public TimeLine timeLine;
+
+
+ protected override void _OnTrigger()
+ {
+ if ( particles3D == null )
+ {
+ return;
+ }
+
+ var tl = TimeLineManager.Ensure( timeLine );
+
+ var start = tl.position;
+
+ var fromData = new TweenParticlesData();
+ fromData.CopyFrom( particles3D );
+ var lerpData = tweenParticlesData.Clone( true );
+
+ var sequenceID = DispatchStart();
+
+ var tweenType = this.tweenType;
+
+ if ( tweenType == null )
+ {
+ tweenType = TweenTimeCurve.defaultCurve;
+ }
+
+ TimeLineManager.ScheduleSpanIn( tl, 0, tweenType.GetTweenDuration(),
+ ( span, type )=>
+ {
+ var timeNow = tl.position;
+ var elapsed = timeNow - start;
+
+ var state = tweenType.GetTweenPhaseForPhase( span.phase );
+
+ TweenParticlesData.LerpTo( fromData, tweenParticlesData, state, lerpData );
+
+ lerpData.CopyTo( particles3D );
+
+ if ( type == TimeLineSpanUpdateType.End )
+ {
+ DispatchEnd( sequenceID );
+ }
+ }
+ );
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Runtime/Actions/Visual/TweenParticles.cs.uid b/Runtime/Actions/Visual/TweenParticles.cs.uid
new file mode 100644
index 0000000..7757eea
--- /dev/null
+++ b/Runtime/Actions/Visual/TweenParticles.cs.uid
@@ -0,0 +1 @@
+uid://seym2gxpvk7j
diff --git a/Runtime/Actions/Visual/TweenParticlesData.cs b/Runtime/Actions/Visual/TweenParticlesData.cs
new file mode 100644
index 0000000..cf6c36f
--- /dev/null
+++ b/Runtime/Actions/Visual/TweenParticlesData.cs
@@ -0,0 +1,120 @@
+using System;
+using Godot;
+
+
+namespace Rokojori
+{
+ [Tool]
+ [GlobalClass ]
+ public partial class TweenParticlesData:Resource
+ {
+
+ [Export]
+ public FloatValue amount;
+
+ [Export]
+ public FloatValue amountRatio;
+
+ [ExportGroup("Time")]
+ [Export]
+ public FloatValue lifeTime;
+
+ [Export]
+ public FloatValue speedScale;
+
+ [Export]
+ public FloatValue explosiveness;
+
+ [Export]
+ public FloatValue randomness;
+
+
+ public virtual TweenParticlesData Clone( bool deepClone )
+ {
+ var clone = new TweenParticlesData();
+
+ clone.amount = FloatValue.Clone( amount, deepClone );
+ clone.amountRatio = FloatValue.Clone( amountRatio, deepClone );
+ clone.lifeTime = FloatValue.Clone( lifeTime, deepClone );
+
+ clone.speedScale = FloatValue.Clone( speedScale, deepClone );
+ clone.explosiveness = FloatValue.Clone( explosiveness, deepClone );
+ clone.randomness = FloatValue.Clone( randomness, deepClone );
+
+ return clone;
+
+ }
+
+ public virtual void CopyFrom( GpuParticles3D particles )
+ {
+ amount = FloatValue.Create( particles.Amount );
+ amountRatio = FloatValue.Create( particles.AmountRatio );
+ lifeTime = FloatValue.Create( particles.Lifetime );
+
+ speedScale = FloatValue.Create( particles.SpeedScale );
+ explosiveness = FloatValue.Create( particles.Explosiveness );
+ randomness = FloatValue.Create( particles.Randomness );
+ }
+
+
+ public virtual void CopyFrom( TweenParticlesData tweenLightData )
+ {
+ amount = tweenLightData.amount;
+ amountRatio = tweenLightData.amountRatio;
+ lifeTime = tweenLightData.lifeTime;
+
+ speedScale = tweenLightData.speedScale;
+ explosiveness = tweenLightData.explosiveness;
+ randomness = tweenLightData.randomness;
+ }
+
+ public virtual void CopyTo( GpuParticles3D particles )
+ {
+ if ( amount != null )
+ {
+ particles.Amount = Mathf.RoundToInt( amount.value );
+
+ }
+
+ if ( amountRatio != null )
+ {
+ particles.AmountRatio = amountRatio.value;
+ particles.Emitting = amountRatio.value > 0;
+ }
+
+ if ( lifeTime != null )
+ {
+ particles.Lifetime = lifeTime.value;
+ }
+
+
+ if ( speedScale != null )
+ {
+ particles.SpeedScale = speedScale.value;
+ }
+
+ if ( explosiveness != null )
+ {
+ particles.Explosiveness = explosiveness.value;
+ }
+
+ if ( randomness != null )
+ {
+ particles.Randomness = randomness.value;
+ }
+
+ }
+
+ public static void LerpTo( TweenParticlesData a, TweenParticlesData b, float lerpAmount, TweenParticlesData output )
+ {
+ FloatValue.Lerp( a.amount, b.amount, lerpAmount, output.amount );
+ FloatValue.Lerp( a.amountRatio, b.amountRatio, lerpAmount, output.amountRatio );
+ FloatValue.Lerp( a.lifeTime, b.lifeTime, lerpAmount, output.lifeTime );
+
+ FloatValue.Lerp( a.speedScale, b.speedScale, lerpAmount, output.speedScale );
+ FloatValue.Lerp( a.explosiveness, b.explosiveness, lerpAmount, output.explosiveness );
+ FloatValue.Lerp( a.randomness, b.randomness, lerpAmount, output.randomness );
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Runtime/Actions/Visual/TweenParticlesData.cs.uid b/Runtime/Actions/Visual/TweenParticlesData.cs.uid
new file mode 100644
index 0000000..56e4c0b
--- /dev/null
+++ b/Runtime/Actions/Visual/TweenParticlesData.cs.uid
@@ -0,0 +1 @@
+uid://bvrtvo7tdhft6
diff --git a/Runtime/Actions/Visual/TweenPosition.cs b/Runtime/Actions/Visual/TweenPosition.cs
new file mode 100644
index 0000000..7926e85
--- /dev/null
+++ b/Runtime/Actions/Visual/TweenPosition.cs
@@ -0,0 +1,91 @@
+
+using System;
+using Godot;
+
+
+namespace Rokojori
+{
+ [Tool]
+ [GlobalClass ]
+ public partial class TweenPosition:SequenceAction
+ {
+ [Export]
+ public Node3D target;
+
+ [Export]
+ public Node3D endPosition;
+
+ [Export]
+ public Vector3 endOffset;
+
+ [Export]
+ public TweenType tweenType = new TweenTimeCurve();
+
+
+ [Export]
+ public bool cacheEndPositionOnStart = true;
+
+ [Export]
+ public TimeLine timeLine;
+
+
+ protected override void _OnTrigger()
+ {
+ if ( target == null )
+ {
+ return;
+ }
+
+ var tl = TimeLineManager.Ensure( timeLine );
+
+ var start = tl.position;
+
+ var fromPosition = target.GlobalPosition;
+ var toPosition = endOffset;
+
+ if ( endPosition != null )
+ {
+ toPosition += endPosition.GlobalPosition;
+ }
+
+ var sequenceID = DispatchStart();
+
+ var tweenType = this.tweenType;
+
+ if ( tweenType == null )
+ {
+ tweenType = TweenTimeCurve.defaultCurve;
+ }
+
+ TimeLineManager.ScheduleSpanIn( tl, 0, tweenType.GetTweenDuration(),
+ ( span, type )=>
+ {
+ var timeNow = tl.position;
+ var elapsed = timeNow - start;
+
+ var state = tweenType.GetTweenPhaseForPhase( span.phase );
+
+ if ( ! cacheEndPositionOnStart )
+ {
+ toPosition = endOffset;
+
+ if ( endPosition != null )
+ {
+ toPosition += endPosition.GlobalPosition;
+ }
+ }
+
+ var lerpedPosition = fromPosition.Lerp( toPosition, state );
+
+ target.GlobalPosition = lerpedPosition;
+
+ if ( type == TimeLineSpanUpdateType.End )
+ {
+ DispatchEnd( sequenceID );
+ }
+ }
+ );
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Runtime/Actions/Visual/TweenPosition.cs.uid b/Runtime/Actions/Visual/TweenPosition.cs.uid
new file mode 100644
index 0000000..41f3ae2
--- /dev/null
+++ b/Runtime/Actions/Visual/TweenPosition.cs.uid
@@ -0,0 +1 @@
+uid://n4etptbiekhq
diff --git a/Runtime/Animation/Tweens/TweenType.cs b/Runtime/Animation/Tweens/TweenType.cs
index eac6f28..b0a7d9b 100644
--- a/Runtime/Animation/Tweens/TweenType.cs
+++ b/Runtime/Animation/Tweens/TweenType.cs
@@ -8,6 +8,11 @@ namespace Rokojori
[GlobalClass ]
public partial class TweenType : Resource
{
+ public virtual void SetFromAndTo( object a, object b )
+ {
+
+ }
+
public virtual float GetTweenDuration()
{
return 0.5f;
diff --git a/Runtime/Audio/AudioGraph/Test/SineWaveTest.cs b/Runtime/Audio/AudioGraph/Test/SineWaveTest.cs
index 2e3ac9c..0f5afbe 100644
--- a/Runtime/Audio/AudioGraph/Test/SineWaveTest.cs
+++ b/Runtime/Audio/AudioGraph/Test/SineWaveTest.cs
@@ -5,7 +5,7 @@ using System.Text;
namespace Rokojori
{
- [GlobalClass]
+ [Tool][GlobalClass]
public partial class SineWaveTest: Node
{
[Export]
diff --git a/Runtime/Bits/Bytes.cs b/Runtime/Bits/Bytes.cs
index 0b1b006..6b7ee67 100644
--- a/Runtime/Bits/Bytes.cs
+++ b/Runtime/Bits/Bytes.cs
@@ -66,5 +66,20 @@ namespace Rokojori
ints.ForEach( i => list.AddRange( BitConverter.GetBytes( i ) ) );
return list;
}
+
+ public static List Convert( List v )
+ {
+ var list = new List();
+ v.ForEach(
+ f =>
+ {
+ list.AddRange( BitConverter.GetBytes( f.X ) );
+ list.AddRange( BitConverter.GetBytes( f.Y ) );
+ list.AddRange( BitConverter.GetBytes( f.Z ) );
+ list.AddRange( BitConverter.GetBytes( f.W ) );
+ }
+ );
+ return list;
+ }
}
}
\ No newline at end of file
diff --git a/Runtime/Colors/ColorX.cs b/Runtime/Colors/ColorX.cs
index 261964a..100dddf 100644
--- a/Runtime/Colors/ColorX.cs
+++ b/Runtime/Colors/ColorX.cs
@@ -1,9 +1,47 @@
using Godot;
+using System.Collections.Generic;
namespace Rokojori
{
public static class ColorX
{
+ public static Color From( List floats, float basis = 1.0f )
+ {
+ if ( floats.Count == 1 )
+ {
+ var grey = floats[ 0 ] / basis;
+ return new Color( grey, grey, grey, 1 );
+ }
+
+ if ( floats.Count == 2 )
+ {
+ var grey = floats[ 0 ] / basis;
+ var alpha = floats[ 1 ] / basis;
+ return new Color( grey, grey, grey, alpha );
+ }
+
+ if ( floats.Count == 3 )
+ {
+ var r = floats[ 0 ] / basis;
+ var g = floats[ 1 ] / basis;
+ var b = floats[ 2 ] / basis;
+
+ return new Color( r, g, b, 1 );
+ }
+
+ if ( floats.Count == 4 )
+ {
+ var r = floats[ 0 ] / basis;
+ var g = floats[ 1 ] / basis;
+ var b = floats[ 2 ] / basis;
+ var alpha = floats[ 3 ] / basis;
+
+ return new Color( r, g, b, alpha );
+ }
+
+ return new Color( 0, 0, 0, 0 );
+ }
+
public static Color UnblendBlack( this Color c, float treshold = 0 )
{
if ( c.A <= treshold )
@@ -62,6 +100,83 @@ namespace Rokojori
return color.Gamma( 2.2f );
}
+ public static Vector3 ToVector3( this Color color )
+ {
+ return new Vector3( color.R, color.G, color.B );
+ }
+
+ public static Vector4 ToVector4( this Color color )
+ {
+ return new Vector4( color.R, color.G, color.B, color.A );
+ }
+
+ public static Color ToColor( this Vector4 color )
+ {
+ return new Color( color.X, color.Y, color.Z, color.W );
+ }
+
+ public static Color ToColor( this Vector3 color )
+ {
+ return new Color( color.X, color.Y, color.Z, 1f );
+ }
+
+ public static Color ToColor( this Vector3 vec, float alpha = 1 )
+ {
+ return new Color( vec.X, vec.Y, vec.Z, alpha);
+ }
+
+ public static Color ToColor( this Vector2 vec, float b = 0, float alpha = 1 )
+ {
+ return new Color( vec.X, vec.Y, b, alpha);
+ }
+
+ public enum EdgeMode
+ {
+ Clamp,
+ Repeat,
+ TransparentBlack,
+ }
+
+
+ public static Color GetPixel( this Image image, int x, int y, EdgeMode mode )
+ {
+ if ( x < 0 || y < 0 || x >= image.GetSize().X || y >= image.GetSize().Y )
+ {
+ if ( mode == EdgeMode.TransparentBlack )
+ {
+ return new Color( 0, 0, 0, 0 );
+ }
+
+ if ( mode == EdgeMode.Repeat )
+ {
+ x = MathX.Repeat( x, image.GetSize().X );
+ y = MathX.Repeat( y, image.GetSize().Y );
+ }
+
+ if ( mode == EdgeMode.Clamp )
+ {
+ x = Mathf.Clamp( x, 0, image.GetSize().X );
+ y = Mathf.Clamp( y, 0, image.GetSize().Y );
+ }
+ }
+
+ return image.GetPixel( x, y );
+ }
+
+
+ public static Color Sample( this Image image, Vector2 uv, EdgeMode mode )
+ {
+ var pixelUV = uv * image.GetSize();
+ var lowUV = pixelUV.FloorToInt().Max( 0 );
+ var highUV = ( lowUV + Vector2I.One ).Min( image.GetSize() - Vector2I.One );
+
+ var mix = pixelUV - lowUV;
+
+ var xTop = Lerp( image.GetPixel( lowUV.X, lowUV.Y, mode ), image.GetPixel( highUV.X, lowUV.Y, mode ), mix.X );
+ var xLow = Lerp( image.GetPixel( highUV.X, lowUV.Y, mode ), image.GetPixel( highUV.X, highUV.Y, mode ), mix.X );
+
+ return Lerp( xTop, xLow, mix.Y );
+ }
public static Color Blend( Color bottom, Color top )
{
@@ -151,10 +266,10 @@ namespace Rokojori
public static Color BlendColor( Color bottom, Color top )
{
- var hslA = HSLColor.FromRGBA( bottom );
- var hslB = HSLColor.FromRGBA( top );
+ var hslBottom = HSLColor.FromRGBA( bottom );
+ var hslTop = HSLColor.FromRGBA( top );
- var combined = bottom.A == 0 ? bottom : (Color)( new HSLColor( hslB.h, hslB.s, hslA.l, top.A ) );
+ var combined = bottom.A == 0 ? bottom : (Color)( new HSLColor( hslTop.h, hslTop.s, hslBottom.l, top.A ) );
return ColorX.Blend( bottom, combined );
}
@@ -211,11 +326,6 @@ namespace Rokojori
return new Color( rgb.X, rgb.Y, rgb.Z, a );
}
- public static Vector4 ToVector4( Color c )
- {
- return new Vector4( c.R, c.G, c.B, c.A );
- }
-
public static Color FromVector4( Vector4 c )
{
return new Color( c.X, c.Y, c.Z, c.W );
diff --git a/Runtime/Colors/HSLColor.cs b/Runtime/Colors/HSLColor.cs
index 6145329..13184b6 100644
--- a/Runtime/Colors/HSLColor.cs
+++ b/Runtime/Colors/HSLColor.cs
@@ -81,8 +81,11 @@ namespace Rokojori
public static HSLColor FromRGBA( Color c )
{
- float h, s, l, a;
- a = c.A;
+ float h = 0;
+ float s = 0;
+ float l = 0;
+ float a = c.A;
+
float cmin = Mathf.Min( Mathf.Min( c.R, c.G ), c.B );
float cmax = Mathf.Max( Mathf.Max( c.R, c.G ), c.B );
diff --git a/Runtime/Godot/Cameras.cs b/Runtime/Godot/Cameras.cs
index 6db2411..22ab686 100644
--- a/Runtime/Godot/Cameras.cs
+++ b/Runtime/Godot/Cameras.cs
@@ -33,5 +33,87 @@ namespace Rokojori
return Mathf.RadToDeg( rads );
}
+ public static float ComputePixelDensityVertical( float fovDegrees, float distance, Vector2 screenSize )
+ {
+ float fovRadians = Mathf.DegToRad( fovDegrees );
+
+ float verticalViewSize = 2.0f * distance * Mathf.Tan( fovRadians / 2.0f );
+
+ float screenHeightPixels = screenSize.Y;
+
+ float pixelDensity = screenHeightPixels / verticalViewSize;
+
+ return pixelDensity;
+ }
+
+ public static float ComputeSizeOfPixelVertical( float fovDegrees, float distance, Vector2 screenSize )
+ {
+ return ComputePixelDensityVertical( fovDegrees, distance, screenSize );
+ }
+
+
+ public static float ComputePixelDistanceForSizeVertical( float fovDegrees, float size, Vector2 screenSize )
+ {
+ float fovRadians = Mathf.DegToRad(fovDegrees);
+ float screenHeightPixels = screenSize.Y;
+
+ float pixelDensity = 1.0f / size;
+
+ float distance = screenHeightPixels / (2.0f * pixelDensity * Mathf.Tan(fovRadians / 2.0f));
+
+ return distance;
+
+ }
+
+ public static float ComputePixelDensityHorizontal(float fovDegrees, float distance, Vector2 screenSize)
+ {
+ float aspectRatio = screenSize.X / screenSize.Y;
+
+ float verticalFovRad = Mathf.DegToRad( fovDegrees );
+
+ float horizontalFovRad = 2.0f * Mathf.Atan(Mathf.Tan( verticalFovRad / 2.0f ) * aspectRatio);
+
+ float horizontalViewSize = 2.0f * distance * Mathf.Tan( horizontalFovRad / 2.0f );
+
+ float pixelDensity = screenSize.X / horizontalViewSize;
+
+ return pixelDensity;
+ }
+
+ public static Vector3 GlobalToView( Transform3D transform, Vector3 globalPosition )
+ {
+ return globalPosition * transform;
+ }
+
+ public static Vector3 ViewToClip( Projection cameraProjection, Vector3 viewPosition )
+ {
+ var clip = cameraProjection * viewPosition;
+ return clip;
+ }
+
+ public static Vector2 ClipToScreen( Vector3 clipPosition )
+ {
+ var size = Vector2.One;
+ var clip = clipPosition.XY() * 0.5f + new Vector2( 0.5f, 0.5f );
+ return clip * size;
+ }
+
+ public static Vector2 ClipToScreen( Vector3 clipPosition, Vector2 screenPixelSize )
+ {
+ var clip = clipPosition.XY() * 0.5f + new Vector2( 0.5f, 0.5f );
+ return clip * screenPixelSize;
+ }
+
+
+ public static Vector2 GlobalToScreen( Vector3 globalPosition, Transform3D cameraTransform, Projection cameraProjection, Vector2 screenPixelSize )
+ {
+ var view = GlobalToView( cameraTransform, globalPosition );
+ var clip = ViewToClip( cameraProjection, view );
+ var screen = ClipToScreen( clip, screenPixelSize );
+
+ return screen;
+ }
+
+
}
}
\ No newline at end of file
diff --git a/Runtime/Godot/Generated/Classes/RJAnimatableBody3D.cs b/Runtime/Godot/Generated/Classes/RJAnimatableBody3D.cs
index 3a81a69..4d45c19 100644
--- a/Runtime/Godot/Generated/Classes/RJAnimatableBody3D.cs
+++ b/Runtime/Godot/Generated/Classes/RJAnimatableBody3D.cs
@@ -3,7 +3,7 @@ using Godot;
namespace Rokojori
{
- [GlobalClass]
+ [Tool][GlobalClass]
public partial class RJAnimatableBody3D:AnimatableBody3D
{
diff --git a/Runtime/Godot/Generated/Classes/RJCharacterBody3D.cs b/Runtime/Godot/Generated/Classes/RJCharacterBody3D.cs
index 103f2d2..2e2c140 100644
--- a/Runtime/Godot/Generated/Classes/RJCharacterBody3D.cs
+++ b/Runtime/Godot/Generated/Classes/RJCharacterBody3D.cs
@@ -3,7 +3,7 @@ using Godot;
namespace Rokojori
{
- [GlobalClass]
+ [Tool][GlobalClass]
public partial class RJCharacterBody3D:CharacterBody3D
{
diff --git a/Runtime/Godot/GodotEditorHelper.cs b/Runtime/Godot/GodotEditorHelper.cs
new file mode 100644
index 0000000..9274c04
--- /dev/null
+++ b/Runtime/Godot/GodotEditorHelper.cs
@@ -0,0 +1,45 @@
+using Godot;
+using System.Text;
+using System.Collections.Generic;
+
+namespace Rokojori
+{
+ public class GodotEditorHelper
+ {
+ public static string AbsoluteToResourcePath( string path )
+ {
+ #if TOOLS
+
+ if ( path != null )
+ {
+ return ProjectSettings.LocalizePath( path );
+ }
+
+ #endif
+
+ return path;
+ }
+
+ public static bool IsProjectPath( string path )
+ {
+ return path.StartsWith( "res://" ) || path.StartsWith( "user://" );
+ }
+
+ public static void UpdateFile( string path )
+ {
+ #if TOOLS
+
+ if ( ! IsProjectPath( path ) )
+ {
+ var pathBefore = path;
+ path = AbsoluteToResourcePath( path );
+ RJLog.Log( "Converted path", pathBefore, ">>", path );
+ }
+
+ var filesystem = EditorInterface.Singleton.GetResourceFilesystem();
+ filesystem.ReimportFiles( [ path ] );
+
+ #endif
+ }
+ }
+}
\ No newline at end of file
diff --git a/Runtime/Godot/GodotEditorHelper.cs.uid b/Runtime/Godot/GodotEditorHelper.cs.uid
new file mode 100644
index 0000000..894ba18
--- /dev/null
+++ b/Runtime/Godot/GodotEditorHelper.cs.uid
@@ -0,0 +1 @@
+uid://c8ypyibkyf0ms
diff --git a/Runtime/Godot/Nodes.cs b/Runtime/Godot/Nodes.cs
index a0900d1..67ba546 100644
--- a/Runtime/Godot/Nodes.cs
+++ b/Runtime/Godot/Nodes.cs
@@ -29,6 +29,18 @@ namespace Rokojori
}
+ public static T FindSibling( this Node node, Func evaluater = null ) where T:Node
+ {
+ if ( node == null )
+ {
+ return null;
+ }
+
+ var sibling = NodesWalker.Get().FindSibling( node, n => n as T != null );
+
+ return sibling as T;
+ }
+
public static T Find( Node root, NodePathLocatorType type = NodePathLocatorType.DirectChildren, int parentOffset = 0 ) where T:Node
{
var it = root;
@@ -245,6 +257,17 @@ namespace Rokojori
return;
}
+ public static double StartAsyncTimer( this Node node )
+ {
+ return Async.StartTimer();
+ }
+
+ public static async Task WaitForAsyncTimer( this Node node, double time )
+ {
+ time = await Async.WaitIfExceeded( time, node );
+ return time;
+ }
+
public static T CreateChild( this Node parent, string name = null ) where T:Node,new()
{
return CreateChildIn( parent, name );
diff --git a/Runtime/Godot/NodesWalker.cs b/Runtime/Godot/NodesWalker.cs
index d84a848..bac5ac6 100644
--- a/Runtime/Godot/NodesWalker.cs
+++ b/Runtime/Godot/NodesWalker.cs
@@ -40,5 +40,7 @@ namespace Rokojori
return n.GetChildCount();
}
+
+
}
}
\ No newline at end of file
diff --git a/Runtime/Godot/Scenes/SceneFileReader.cs b/Runtime/Godot/Scenes/SceneFileReader.cs
index 6e37257..36bd16b 100644
--- a/Runtime/Godot/Scenes/SceneFileReader.cs
+++ b/Runtime/Godot/Scenes/SceneFileReader.cs
@@ -12,12 +12,13 @@ namespace Rokojori
[Export]
public string path = "";
- [Export]
- public bool load
- {
- get => false;
- set { if ( value ) LoadScene(); }
- }
+ [ExportToolButton( "Read" )]
+ public Callable ReadButton => Callable.From(
+ ()=>
+ {
+ LoadScene();
+ }
+ );
[Export]
public bool exportJSON = false;
diff --git a/Runtime/Graphs/Trees/TreeWalker.cs b/Runtime/Graphs/Trees/TreeWalker.cs
index d0fd564..8a127e2 100644
--- a/Runtime/Graphs/Trees/TreeWalker.cs
+++ b/Runtime/Graphs/Trees/TreeWalker.cs
@@ -50,7 +50,7 @@ namespace Rokojori
{
var p = Parent( node );
- if ( p == null || index<0 || index >= NumChildren( p ) )
+ if ( p == null || index < 0 || index >= NumChildren( p ) )
{ return null; }
return ChildAt( p, index );
@@ -74,11 +74,97 @@ namespace Rokojori
public bool HasSiblingAt( N node, int index )
{
var p = Parent( node );
- if ( p == null || index<0 || index >= NumChildren( p ) )
- { return false; }
+ if ( p == null || index < 0 || index >= NumChildren( p ) )
+ {
+ return false;
+ }
return true;
}
-
+
+ public int NumSiblings( N node )
+ {
+ if ( node == null )
+ {
+ return 0;
+ }
+
+ var p = Parent( node );
+
+ if ( p == null )
+ {
+ return 0;
+ }
+
+ return NumChildren( p ) - 1;
+ }
+
+ public void IterateSiblings( N node, Action action )
+ {
+ if ( node == null )
+ {
+ return;
+ }
+
+ var p = Parent( node );
+
+ if ( p == null )
+ {
+ return;
+ }
+
+ var numChildren = NumChildren( p );
+
+ for ( int i = 0; i < numChildren; i++ )
+ {
+ var child = ChildAt( p, i );
+
+ if ( child == node )
+ {
+ continue;
+ }
+
+ action( child );
+ }
+
+
+
+ }
+
+ public N FindSibling( N node, Func evaluater )
+ {
+ if ( node == null )
+ {
+ return null;
+ }
+
+ var p = Parent( node );
+
+ if ( p == null )
+ {
+ return null ;
+ }
+
+ var numChildren = NumChildren( p );
+
+ for ( int i = 0; i < numChildren; i++ )
+ {
+ var child = ChildAt( p, i );
+
+ if ( child == node )
+ {
+ continue;
+ }
+
+ var result = evaluater( child );
+
+ if ( result )
+ {
+ return child;
+ }
+ }
+
+ return null;
+ }
public N FirstChild( N node )
{
diff --git a/Runtime/Interactions/Grabbable.cs b/Runtime/Interactions/Grabbable.cs
index 9b89fa2..85f1346 100644
--- a/Runtime/Interactions/Grabbable.cs
+++ b/Runtime/Interactions/Grabbable.cs
@@ -2,13 +2,21 @@ using Godot;
using System.Collections;
using System.Collections.Generic;
using Godot.Collections;
+using System.Drawing;
namespace Rokojori
{
-
+ [Tool]
[GlobalClass,Icon("res://addons/rokojori_action_library/Icons/Grabbable.svg")]
- public partial class Grabbable:Node3D
+ public partial class Grabbable:Node3D, iEnablable
{
+
+ [Export]
+ public bool enabled = true;
+ public bool IsEnabled() => enabled;
+ public void SetEnabled( bool enabled ) { this.enabled = enabled; }
+
+
[Export]
public Action onGrab;
@@ -21,14 +29,46 @@ namespace Rokojori
[Export]
public RigidBody3D rigidBody3D;
+ [Export] Pointable pointable;
+
+ [Export]
+ public bool disablePointableDuringGrab = true;
+
[ExportGroup("Read Only")]
[Export]
public Grabber grabber;
+ protected bool enablePointableOnRelease = false;
+
public void SetGrabber( Grabber grabber )
{
this.grabber = grabber;
+
+ if ( grabber != null && ( disablePointableDuringGrab || grabber.disablePointableDuringGrab ) )
+ {
+ if ( pointable == null )
+ {
+ pointable = this.FindSibling();
+ }
+
+ if ( pointable != null )
+ {
+ pointable.enabled = false;
+ enablePointableOnRelease = true;
+ }
+ }
+
+ if ( grabber == null )
+ {
+ if ( pointable != null && enablePointableOnRelease )
+ {
+ pointable.enabled = true;
+ }
+
+ enablePointableOnRelease = false;
+ }
+
if ( this.grabber != null )
{
Action.Trigger( onGrab );
diff --git a/Runtime/Interactions/Grabber.cs b/Runtime/Interactions/Grabber.cs
index a570637..a079411 100644
--- a/Runtime/Interactions/Grabber.cs
+++ b/Runtime/Interactions/Grabber.cs
@@ -5,7 +5,7 @@ using Godot.Collections;
namespace Rokojori
{
-
+ [Tool]
[GlobalClass,Icon("res://addons/rokojori_action_library/Icons/Grabber.svg")]
public partial class Grabber:Node3D, SensorInputHandler
{
@@ -27,6 +27,9 @@ namespace Rokojori
[Export]
public Node3D grabOffset;
+ [Export]
+ public bool disablePointableDuringGrab = true;
+
[Export]
public Smoothing positionSmoothing;
@@ -37,6 +40,7 @@ namespace Rokojori
[Export]
public Grabbable grabbable;
+
public override void _Ready()
diff --git a/Runtime/Interactions/Interactable.cs b/Runtime/Interactions/Interactable.cs
index 22b8500..b251dda 100644
--- a/Runtime/Interactions/Interactable.cs
+++ b/Runtime/Interactions/Interactable.cs
@@ -5,10 +5,15 @@ using Godot.Collections;
namespace Rokojori
{
-
- [GlobalClass,Icon("res://addons/rokojori_action_library/Icons/Interactable.svg")]
- public partial class Interactable:Node3D
+ [Tool]
+ [GlobalClass,Icon("res://addons/rokojori_action_library/Icons/Interactable.svg")]
+ public partial class Interactable:Node3D, iEnablable
{
+ [Export]
+ public bool enabled = true;
+ public bool IsEnabled() => enabled;
+ public void SetEnabled( bool enabled ) { this.enabled = enabled; }
+
[Export]
public Action onInteraction;
}
diff --git a/Runtime/Interactions/Interactor.cs b/Runtime/Interactions/Interactor.cs
index 89fd29d..fe3684a 100644
--- a/Runtime/Interactions/Interactor.cs
+++ b/Runtime/Interactions/Interactor.cs
@@ -5,7 +5,7 @@ using Godot.Collections;
namespace Rokojori
{
-
+ [Tool]
[GlobalClass,Icon("res://addons/rokojori_action_library/Icons/Interactor.svg")]
public partial class Interactor:Node3D, SensorInputHandler
{
diff --git a/Runtime/Interactions/Pointable.cs b/Runtime/Interactions/Pointable.cs
index 3f0d59c..afec0b7 100644
--- a/Runtime/Interactions/Pointable.cs
+++ b/Runtime/Interactions/Pointable.cs
@@ -5,10 +5,18 @@ using Godot.Collections;
namespace Rokojori
{
-
+ [Tool]
[GlobalClass,Icon("res://addons/rokojori_action_library/Icons/Pointable.svg")]
- public partial class Pointable:Node3D
+ public partial class Pointable:Node3D, iEnablable
{
+ [Export]
+ public bool enabled = true;
+
+ public bool IsEnabled() => enabled;
+ public void SetEnabled( bool enabled ) { this.enabled = enabled; }
+
+
+
[Export]
public int pointingPriority;
diff --git a/Runtime/Interactions/Selectors/InteractiveSelector.cs b/Runtime/Interactions/Selectors/InteractiveSelector.cs
new file mode 100644
index 0000000..1368d7b
--- /dev/null
+++ b/Runtime/Interactions/Selectors/InteractiveSelector.cs
@@ -0,0 +1,71 @@
+using Godot;
+using System.Collections;
+using System.Collections.Generic;
+using Godot.Collections;
+using System.Drawing;
+
+namespace Rokojori
+{
+ [Tool]
+ [GlobalClass]
+ public partial class InteractiveSelector:Selector
+ {
+ [Export]
+ public Trillean isPointable = Trillean.Any;
+
+ [Export]
+ public Trillean pointableEnabled = Trillean.Any;
+
+ [Export]
+ public Trillean isGrabbable = Trillean.Any;
+
+ [Export]
+ public Trillean grabbableEnabled = Trillean.Any;
+
+ [Export]
+ public Trillean isInteractable = Trillean.Any;
+
+ [Export]
+ public Trillean interactableEnabled = Trillean.Any;
+
+
+ public override bool Selects( Node node )
+ {
+ if ( ! Matches( node, isPointable, pointableEnabled ) )
+ {
+ return false;
+ }
+
+ if ( ! Matches( node, isGrabbable, grabbableEnabled ) )
+ {
+ return false;
+ }
+
+ if ( ! Matches( node, isInteractable, interactableEnabled ) )
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ bool Matches( Node node, Trillean isType, Trillean isEnabled ) where T:Node,iEnablable
+ {
+ var childNode = Nodes.Find( node );
+
+ if ( ! TrilleanLogic.Matches( isType, childNode != null ) )
+ {
+ return false;
+ }
+
+
+ if ( ! TrilleanLogic.Matches( isEnabled, childNode != null && childNode.IsEnabled() ) )
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Runtime/Interactions/Selectors/InteractiveSelector.cs.uid b/Runtime/Interactions/Selectors/InteractiveSelector.cs.uid
new file mode 100644
index 0000000..60855a6
--- /dev/null
+++ b/Runtime/Interactions/Selectors/InteractiveSelector.cs.uid
@@ -0,0 +1 @@
+uid://bcqxkle5dxw3c
diff --git a/Runtime/Interactions/SetPointableEnabled.cs b/Runtime/Interactions/SetPointableEnabled.cs
new file mode 100644
index 0000000..41b2e90
--- /dev/null
+++ b/Runtime/Interactions/SetPointableEnabled.cs
@@ -0,0 +1,23 @@
+using Godot;
+using System.Collections;
+using System.Collections.Generic;
+using Godot.Collections;
+
+namespace Rokojori
+{
+ [Tool]
+ [GlobalClass]
+ public partial class SetPointableEnabled:Action
+ {
+ [Export]
+ public Pointable pointable;
+
+ [Export]
+ public bool enabled = true;
+
+ protected override void _OnTrigger()
+ {
+ pointable.enabled = enabled;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Runtime/Interactions/SetPointableEnabled.cs.uid b/Runtime/Interactions/SetPointableEnabled.cs.uid
new file mode 100644
index 0000000..84365cb
--- /dev/null
+++ b/Runtime/Interactions/SetPointableEnabled.cs.uid
@@ -0,0 +1 @@
+uid://vfc8utojwc0b
diff --git a/Runtime/Interactions/iEnablable.cs b/Runtime/Interactions/iEnablable.cs
new file mode 100644
index 0000000..e348f1c
--- /dev/null
+++ b/Runtime/Interactions/iEnablable.cs
@@ -0,0 +1,14 @@
+using Godot;
+using System.Collections;
+using System.Collections.Generic;
+using Godot.Collections;
+
+namespace Rokojori
+{
+
+ public interface iEnablable
+ {
+ public bool IsEnabled();
+ public void SetEnabled( bool enabled );
+ }
+}
\ No newline at end of file
diff --git a/Runtime/Interactions/iEnablable.cs.uid b/Runtime/Interactions/iEnablable.cs.uid
new file mode 100644
index 0000000..031c686
--- /dev/null
+++ b/Runtime/Interactions/iEnablable.cs.uid
@@ -0,0 +1 @@
+uid://do5nirsv6cj67
diff --git a/Runtime/LOD/LODBuilder.cs b/Runtime/LOD/LODBuilder.cs
new file mode 100644
index 0000000..51ecac9
--- /dev/null
+++ b/Runtime/LOD/LODBuilder.cs
@@ -0,0 +1,104 @@
+using Godot;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace Rokojori
+{
+ public class LODCameraDistanceLevel
+ {
+ public Mesh mesh;
+ public Material material;
+ public float distance;
+ }
+
+ public class LODBuilder
+ {
+ public enum Mode
+ {
+ CameraDistance
+ }
+
+ Mode mode = Mode.CameraDistance;
+
+ public static LODBuilder ByCameraDistance()
+ {
+ var builder = new LODBuilder();
+
+ builder.mode = Mode.CameraDistance;
+
+ return builder;
+ }
+
+ List levels = new List();
+
+ Material material;
+
+ public LODBuilder SetMaterial( Material material )
+ {
+ this.material = material;
+
+ return this;
+ }
+
+ public LODBuilder Add( float distance, Mesh mesh, Material material = null )
+ {
+ var level = new LODCameraDistanceLevel
+ {
+ mesh = mesh,
+ distance = distance,
+ material = material
+ };
+
+ levels.Add( level );
+ return this;
+ }
+
+ public LODNode Create( Node parent, string name = "LOD" )
+ {
+ var lodNode = parent.CreateChild( name );
+
+ var arrangement = new LODArrangement();
+
+ levels.Sort( ( a, b ) => Mathf.Sign( a.distance - b.distance ) );
+
+ var maxMap = new Dictionary();
+
+ for ( int i = 0; i < levels.Count; i++ )
+ {
+ if ( ( i + 1 ) < levels.Count )
+ {
+ maxMap[ levels[ i ] ] = levels[ i + 1 ].distance;
+ }
+ else
+ {
+ maxMap[ levels[ i ] ] = 10000f;
+ }
+ }
+
+ levels[ 0 ].distance = 0;
+
+ levels.ForEach(
+ ( l )=>
+ {
+ var level = new LODLevel();
+ var distanceRule = new LODCameraDistanceRule();
+ distanceRule.minDistance = l.distance;
+ distanceRule.maxDistance = maxMap[ l ];
+
+ level.visibilityRules = [ distanceRule ];
+ level.mesh = l.mesh;
+ level.material = l.material != null ? l.material : material;
+ arrangement.levels = arrangement.levels.Add( level );
+ }
+ );
+
+ lodNode.arrangement = arrangement;
+
+
+ return lodNode;
+ }
+
+
+ }
+
+}
\ No newline at end of file
diff --git a/Runtime/LOD/LODBuilder.cs.uid b/Runtime/LOD/LODBuilder.cs.uid
new file mode 100644
index 0000000..9487b57
--- /dev/null
+++ b/Runtime/LOD/LODBuilder.cs.uid
@@ -0,0 +1 @@
+uid://c6bp7sk6mtglv
diff --git a/Runtime/LOD/LODCameraDistanceRule.cs b/Runtime/LOD/LODCameraDistanceRule.cs
index bf15ce0..5f3a822 100644
--- a/Runtime/LOD/LODCameraDistanceRule.cs
+++ b/Runtime/LOD/LODCameraDistanceRule.cs
@@ -15,7 +15,7 @@ namespace Rokojori
public override bool Evaluate( LODNode lodNode )
{
- return false;
+ return Range.Contains( minDistance, maxDistance, lodNode.distance );
}
}
diff --git a/Runtime/LOD/LODMeshCreator.cs b/Runtime/LOD/LODMeshCreator.cs
new file mode 100644
index 0000000..9662eac
--- /dev/null
+++ b/Runtime/LOD/LODMeshCreator.cs
@@ -0,0 +1,61 @@
+using Godot;
+using System.Collections;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace Rokojori
+{
+ [Tool]
+ [GlobalClass]
+ public partial class LODMeshCreator:Node
+ {
+ [Export]
+ public MeshInstance3D[] meshes;
+
+ [Export]
+ public float[] distances;
+
+ [Export]
+ public Mesh.PrimitiveType primitiveType = Mesh.PrimitiveType.Triangles;
+
+ [Export]
+ public bool generateTangents = true;
+
+ [ExportToolButton("Create")]
+ public Callable CreateButton => Callable.From( ()=>{ Create(); } );
+
+ [ExportGroup("Output")]
+ [Export]
+ public MeshInstance3D lodMesh;
+
+ public async Task Create()
+ {
+ var list = new List();
+
+ var index = 0;
+
+ foreach ( var m in meshes )
+ {
+ var mg = MeshGeometry.From( m.Mesh as ArrayMesh );
+ mg.lodEdgeLength = distances[ index ];
+ index++;
+
+ await this.RequestNextFrame();
+ }
+
+ var mesh = MeshGeometry.GenerateLODMesh( list, primitiveType, null, generateTangents );
+
+ await this.RequestNextFrame();
+
+ this.DestroyChildren();
+
+ await this.RequestNextFrame();
+
+ lodMesh = this.CreateChild( "LODMesh" );
+ lodMesh.Mesh = mesh;
+ lodMesh.Mesh.SurfaceSetMaterial( 0, MaterialSurfaceContainer.GetActiveFrom( meshes[ 0 ] ) );
+
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Runtime/LOD/LODMeshCreator.cs.uid b/Runtime/LOD/LODMeshCreator.cs.uid
new file mode 100644
index 0000000..ba975fb
--- /dev/null
+++ b/Runtime/LOD/LODMeshCreator.cs.uid
@@ -0,0 +1 @@
+uid://cmvumubfr1hvu
diff --git a/Runtime/LOD/LODNode.cs b/Runtime/LOD/LODNode.cs
index 4d670bd..a3ec591 100644
--- a/Runtime/LOD/LODNode.cs
+++ b/Runtime/LOD/LODNode.cs
@@ -1,7 +1,6 @@
using Godot;
using System.Collections;
using System.Collections.Generic;
-using Godot.Collections;
namespace Rokojori
{
@@ -9,9 +8,22 @@ namespace Rokojori
[GlobalClass]
public partial class LODNode:MeshInstance3D
{
+
[Export]
public LODArrangement arrangement;
+ [Export]
+ public int currentLevelIndex = -1;
+
+ [Export]
+ public int drawElements = 0;
+
+ [Export]
+ public float distanceScale = 1;
+
+ [Export]
+ public MeshInstance3D lod0;
+
LODLevel _currentLevel;
public override void _Process( double delta )
@@ -33,10 +45,34 @@ namespace Rokojori
return;
}
+ currentLevelIndex = arrangement.levels.IndexOf( level );
+
+ if ( lod0 != null )
+ {
+ lod0.Visible = currentLevelIndex == 0;
+ }
+
+ if ( ! faceCount.ContainsKey( level.mesh ) )
+ {
+ if ( level.mesh is ArrayMesh am )
+ {
+ faceCount[ level.mesh ] = am.SurfaceGetArrayIndexLen( 0 );
+ }
+ else
+ {
+ faceCount[ level.mesh ] = level.mesh.GetFaces().Length;
+ }
+
+
+ }
+
+ drawElements = faceCount[ level.mesh ];
Mesh = level.mesh;
MaterialOverride = level.material;
}
+ Dictionary faceCount = new Dictionary();
+
public Camera3D camera
{
@@ -73,6 +109,8 @@ namespace Rokojori
_cameraDirection = ( GlobalPosition - camera.GlobalPosition );
+ _hasCameraDirection = true;
+
return _cameraDirection;
}
}
@@ -86,12 +124,14 @@ namespace Rokojori
{
if ( _hasDistance )
{
- return _distance;
+ return _distance * distanceScale;
}
_distance = cameraDirection.Length();
- return _distance;
+ _hasDistance = true;
+
+ return _distance * distanceScale;
}
}
@@ -109,6 +149,8 @@ namespace Rokojori
_pitch = Math3D.GlobalPitch( cameraDirection );
+ _hasPitch = true;
+
return _pitch;
}
}
@@ -127,6 +169,8 @@ namespace Rokojori
_yaw = Math3D.GlobalYaw( cameraDirection );
+ _hasYaw = true;
+
return _yaw;
}
}
diff --git a/Runtime/LOD/NTree/OcTree/OcTree.cs b/Runtime/LOD/NTree/OcTree/OcTree.cs
new file mode 100644
index 0000000..a8643c9
--- /dev/null
+++ b/Runtime/LOD/NTree/OcTree/OcTree.cs
@@ -0,0 +1,262 @@
+using System.Collections;
+using System.Collections.Generic;
+using Godot;
+using System;
+using System.Linq;
+
+
+
+namespace Rokojori
+{
+ public class OcTree:OcTreeNode
+ {
+ protected Func _getPosition;
+ protected Func,List> _combinePoints;
+ protected Func,List, float,List> _smoothPoints;
+
+ protected Vector3 _min;
+ protected Vector3 _max;
+ protected float _rootCellSize;
+
+ protected int _maxDepth = 64;
+ public int maxDepth => _maxDepth;
+
+ public OcTree( Func getPosition, Vector3 min, Vector3 max, float rootCellSize, int maxDepth )
+ {
+ this._getPosition = getPosition;
+ this._min = min;
+ this._max = max;
+ this._rootCellSize = rootCellSize;
+ this._maxDepth = maxDepth;
+
+ CreateRootCells();
+
+ }
+
+ public void SetCombiner( Func,List> combinePoints )
+ {
+ this._combinePoints = combinePoints;
+ }
+
+ public void SetSmoother( Func,List,float,List> smoothPoints )
+ {
+ this._smoothPoints = smoothPoints;
+ }
+
+
+ public List CombinePoints( List values )
+ {
+ return _combinePoints( values );
+ }
+
+ public List SmoothPoints( List targetValues, List sourceSmoothing, float amount )
+ {
+ return _smoothPoints( targetValues, sourceSmoothing, amount );
+ }
+
+
+
+
+ List> _rootCells = new List>();
+ public List> rootCells => _rootCells;
+
+ public bool Insert( List data )
+ {
+ var result = true;
+
+ data.ForEach(
+ ( d )=>
+ {
+ result = result && Insert( d );
+ }
+ );
+
+ return result;
+ }
+
+ public bool Insert( T data )
+ {
+ var rootCell = GetRootCell( _getPosition( data ) );
+
+ if ( rootCell == null )
+ {
+ return false;
+ }
+
+ if ( ! rootCell.box.ContainsPoint( GetPosition( data ) ) )
+ {
+ RJLog.Log( "Box not containing point", rootCell.rootCellIndex );
+ }
+
+ return rootCell.Insert( data );
+ }
+
+ public int GetLevelForSize( float size )
+ {
+ var level = 0;
+ var it = _rootCellSize;
+
+ while ( it > size )
+ {
+ level ++;
+ it /= 2f;
+ }
+
+ return level;
+ }
+
+ public float GetSizeForLevel( int level )
+ {
+ var it = 0;
+ var size = _rootCellSize;
+
+ while ( it < level )
+ {
+ size /= 2f;
+ it ++;
+ }
+
+ return size;
+ }
+
+ public void CombineUntilSize( float size )
+ {
+ CombineUntilLevel( GetLevelForSize( size ) );
+ }
+
+
+ public void CombineUntilLevel( int minLevel, float smoothDown = 0.2f )
+ {
+ var levelMap = GetLevelMap();
+ var levels = levelMap.Keys.ToList();
+ levels.Sort();
+ var maxLevel = levels.Last();
+
+ for ( int i = maxLevel - 1; i > minLevel; i-- )
+ {
+ levelMap[ i ].ForEach(
+ ( c )=>
+ {
+ c.Combine();
+ }
+ );
+ }
+
+ if ( smoothDown > 0 )
+ {
+ for ( int i = minLevel + 1; i < maxLevel - 1; i++ )
+ {
+ levelMap[ i ].ForEach(
+ ( c )=>
+ {
+ c.SmoothDown( smoothDown );
+ }
+ );
+ }
+ }
+ }
+
+ public MapList> GetLevelMap()
+ {
+ var walker = new OcTreeWalker();
+
+ var levelMap = new MapList>();
+
+ walker.Iterate(
+ this,
+ ( n )=>
+ {
+ if ( n is OcTree tree )
+ {
+ return;
+ }
+
+ var c = n as OcTreeCell;
+
+ levelMap.Add( c.depth, c );
+ }
+ );
+
+ return levelMap;
+ }
+
+ Vector3 _startOffset = Vector3.Zero;
+ Vector3I _rootCellDimensions = Vector3I.Zero;
+
+ public Vector3I PositionToRootIndex( Vector3 position )
+ {
+ position -= _startOffset;
+
+ var index = ( position / ( Vector3.One * _rootCellSize ) ).FloorToInt();
+
+ return index;
+ }
+
+ public Vector3 GetPosition( T data )
+ {
+ return _getPosition( data );
+ }
+
+ public OcTreeCell GetRootCell( Vector3 position )
+ {
+ var rootIndex = PositionToRootIndex( position );
+ return GetRootCellByRootIndex( rootIndex );
+ }
+
+ public OcTreeCell GetRootCellByRootIndex( Vector3I rootCellIndex )
+ {
+ var rootCellFlatIndex = MathX.MultiIndexToFlatIndex( rootCellIndex, _rootCellDimensions );
+
+ if ( rootCellFlatIndex < 0 || rootCellFlatIndex >= _rootCells.Count )
+ {
+ return null;
+ }
+
+ return _rootCells[ rootCellFlatIndex ];
+ }
+
+
+ void CreateRootCells()
+ {
+ var rootCellSize3 = Vector3.One * _rootCellSize;
+ var start = _min.SnapFloored( rootCellSize3 );
+ var end = _max.SnapCeiled( rootCellSize3 );
+
+ var num = ( ( end - start ) / rootCellSize3 ).RoundToInt();
+
+ _rootCellDimensions = num;
+ _startOffset = start;
+
+ for ( int x = 0; x < num.X ; x ++ )
+ {
+ for ( int y = 0; y < num.Y; y++ )
+ {
+ for ( int z = 0; z < num.Z; z++ )
+ {
+ var rootIndex = new Vector3I( x, y, z );
+ var min = rootIndex * rootCellSize3 + start;
+ var max = min + rootCellSize3;
+
+ var cell = OcTreeCell.Create( this, min, max, _rootCells.Count );
+
+ _rootCells.Add( cell );
+
+ // var positionRootIndex = PositionToRootIndex( cell.center );
+
+ // RJLog.Log(
+ // "Creating root cell",
+ // "Roots Index:", _rootCells.Count - 1,
+ // "Cell Index:", cell.rootCellIndex,
+ // "Center:", cell.center,
+ // "RootIndex:", rootIndex,
+ // "Get Self ByRootIndex:", GetRootCellByRootIndex( rootIndex ) == cell,
+ // "RootIndex From Position: ", positionRootIndex,
+ // "Get Self ByRootIndex From Position:", GetRootCellByRootIndex( positionRootIndex ) == cell
+ // );
+ }
+ }
+ }
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Runtime/LOD/NTree/OcTree/OcTree.cs.uid b/Runtime/LOD/NTree/OcTree/OcTree.cs.uid
new file mode 100644
index 0000000..69870dd
--- /dev/null
+++ b/Runtime/LOD/NTree/OcTree/OcTree.cs.uid
@@ -0,0 +1 @@
+uid://byv0j4gqliwii
diff --git a/Runtime/LOD/NTree/OcTree/OcTreeCell.cs b/Runtime/LOD/NTree/OcTree/OcTreeCell.cs
new file mode 100644
index 0000000..7d27467
--- /dev/null
+++ b/Runtime/LOD/NTree/OcTree/OcTreeCell.cs
@@ -0,0 +1,202 @@
+using System.Collections;
+using System.Collections.Generic;
+using Godot;
+using System;
+
+
+
+namespace Rokojori
+{
+ public class OcTreeCell:OcTreeNode
+ {
+ OcTreeCell _parent;
+ public OcTreeCell parent => _parent;
+
+ OcTree _tree;
+ public OcTree tree => _tree;
+
+ List> _cells;
+ public List> cells => _cells;
+
+
+ public int numCells => _cells == null ? 0 : cells.Count;
+
+ List _values;
+ public List values => _values;
+
+ int _depth;
+ public int depth => _depth;
+
+ Vector3 _center;
+ public Vector3 center => _center;
+
+ Vector3 _size;
+ public Vector3 size => _size;
+
+ bool _isCombined = false;
+ public bool isCombined => _isCombined;
+ public bool canBeCombined => ! isLeaf && ! isCombined && numCells > 0;
+
+
+ public bool isRoot => _parent == null;
+ public bool isEmpty => _cells == null || _cells.Count == 0;
+ public bool isLeaf => _depth == tree.maxDepth;
+
+ Box3 _box;
+ public Box3 box => _box;
+
+ int _rootCellIndex = -1;
+ public int rootCellIndex => _rootCellIndex;
+
+
+ public static OcTreeCell Create( OcTree tree, Vector3 min, Vector3 max, int rootIndex )
+ {
+ var cell = new OcTreeCell();
+ cell._tree = tree;
+ cell._center = (max + min ) / 2f;
+ cell._size = ( max - min );
+ cell._depth = 0;
+ cell._box = Box3.Create( min, max );
+ cell._rootCellIndex = rootIndex;
+
+ return cell;
+ }
+
+ public static OcTreeCell Create( OcTreeCell parent, int depth, Vector3 min, Vector3 max )
+ {
+ var cell = new OcTreeCell();
+ cell._parent = parent;
+ cell._tree = parent.tree;
+ cell._center = (max + min ) / 2f;
+ cell._size = ( max - min );
+ cell._depth = depth;
+ cell._box = Box3.Create( min, max );
+ cell._rootCellIndex = parent.rootCellIndex;
+
+ return cell;
+ }
+
+ public Vector3 GetPointWithPolarUVW( Vector3 polarUVW )
+ {
+ var halfSize = _size / 2f;
+
+ return _center + halfSize * polarUVW;
+ }
+
+ public bool Insert( T data )
+ {
+ if ( isLeaf )
+ {
+ if ( _values == null )
+ {
+ _values = new List();
+ }
+
+ values.Add( data );
+ return true;
+ }
+
+ Nest();
+
+ var position = tree.GetPosition( data );
+
+ var cell = GetChildCellFor( position );
+
+ if ( cell == null )
+ {
+ RJLog.Log( "No cell found in:", this.depth, this.center, ">> for: ", position );
+
+ return false;
+ }
+
+ return cell.Insert( data );
+ }
+
+ public void SmoothDown( float amount )
+ {
+ if ( isLeaf || cells == null )
+ {
+ return;
+ }
+
+ cells.ForEach(
+ ( c )=>
+ {
+ c._values = tree.SmoothPoints( c.values, values, amount );
+ }
+ );
+
+
+
+ }
+
+ public void Combine()
+ {
+ if ( isLeaf || isCombined )
+ {
+ return;
+ }
+
+ if ( numCells == 0 )
+ {
+ _isCombined = true;
+ return;
+ }
+
+ var cellPoints = new List();
+ cells.ForEach(
+ c =>
+ {
+ if ( c.values == null )
+ {
+ return;
+ }
+
+ cellPoints.AddRange( c.values );
+ }
+ );
+
+ _values = tree.CombinePoints( cellPoints );
+ _isCombined = true;
+ }
+
+ public OcTreeCell GetChildCellFor( Vector3 position )
+ {
+ return _cells.Find( c => c.box.ContainsPoint( position ) );
+ }
+
+
+ public void Nest()
+ {
+ if ( _cells != null && _cells.Count == 8 )
+ {
+ return;
+ }
+
+ _cells = new List>();
+
+ for ( int x = -1; x < 1; x ++ )
+ {
+ var x0 = x;
+ var x1 = x + 1;
+
+ for ( int y = -1; y < 1; y ++ )
+ {
+ var y0 = y;
+ var y1 = y + 1;
+
+ for ( int z = -1; z < 1; z ++ )
+ {
+ var z0 = z;
+ var z1 = z + 1;
+
+ var min = GetPointWithPolarUVW( new Vector3( x0, y0, z0 ) );
+ var max = GetPointWithPolarUVW( new Vector3( x1, y1, z1 ) );
+
+ _cells.Add( Create( this, depth + 1, min, max ) );
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Runtime/LOD/NTree/OcTree/OcTreeCell.cs.uid b/Runtime/LOD/NTree/OcTree/OcTreeCell.cs.uid
new file mode 100644
index 0000000..7191008
--- /dev/null
+++ b/Runtime/LOD/NTree/OcTree/OcTreeCell.cs.uid
@@ -0,0 +1 @@
+uid://bkg701bcjl3nu
diff --git a/Runtime/LOD/NTree/OcTree/OcTreeNode.cs b/Runtime/LOD/NTree/OcTree/OcTreeNode.cs
new file mode 100644
index 0000000..fbd7db7
--- /dev/null
+++ b/Runtime/LOD/NTree/OcTree/OcTreeNode.cs
@@ -0,0 +1,14 @@
+using System.Collections;
+using System.Collections.Generic;
+using Godot;
+using System;
+
+
+
+namespace Rokojori
+{
+ public class OcTreeNode
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/Runtime/LOD/NTree/OcTree/OcTreeNode.cs.uid b/Runtime/LOD/NTree/OcTree/OcTreeNode.cs.uid
new file mode 100644
index 0000000..99c7887
--- /dev/null
+++ b/Runtime/LOD/NTree/OcTree/OcTreeNode.cs.uid
@@ -0,0 +1 @@
+uid://dyyollc58p3l6
diff --git a/Runtime/LOD/NTree/OcTree/OcTreeWalker.cs b/Runtime/LOD/NTree/OcTree/OcTreeWalker.cs
new file mode 100644
index 0000000..35a176e
--- /dev/null
+++ b/Runtime/LOD/NTree/OcTree/OcTreeWalker.cs
@@ -0,0 +1,53 @@
+using System.Collections;
+using System.Collections.Generic;
+using Godot;
+using System;
+
+
+
+namespace Rokojori
+{
+ public class OcTreeWalker:TreeWalker>
+ {
+ public override OcTreeNode Parent( OcTreeNode node )
+ {
+ if ( node is OcTree )
+ {
+ return null;
+ }
+
+ var cell = node as OcTreeCell;
+
+ return cell.isRoot ? cell.tree : cell.parent;
+ }
+
+ public override int NumChildren( OcTreeNode node )
+ {
+ if ( node is OcTree tree )
+ {
+ return tree.rootCells.Count;
+ }
+
+ var cell = node as OcTreeCell;
+
+ return cell.numCells;
+ }
+
+ public override OcTreeNode ChildAt( OcTreeNode node, int index )
+ {
+ if ( index < 0 || index >= NumChildren( node ) )
+ {
+ return null;
+ }
+
+ if ( node is OcTree tree )
+ {
+ return tree.rootCells[ index ];
+ }
+
+ var cell = node as OcTreeCell;
+
+ return cell.cells[ index ];
+ }
+ }
+}
\ No newline at end of file
diff --git a/Runtime/LOD/NTree/OcTree/OcTreeWalker.cs.uid b/Runtime/LOD/NTree/OcTree/OcTreeWalker.cs.uid
new file mode 100644
index 0000000..cefb67e
--- /dev/null
+++ b/Runtime/LOD/NTree/OcTree/OcTreeWalker.cs.uid
@@ -0,0 +1 @@
+uid://u5ybxjw7q03t
diff --git a/Runtime/LOD/PointClouds/Point.cs b/Runtime/LOD/PointClouds/Point.cs
new file mode 100644
index 0000000..bbf7982
--- /dev/null
+++ b/Runtime/LOD/PointClouds/Point.cs
@@ -0,0 +1,51 @@
+using System.Collections;
+using System.Collections.Generic;
+using Godot;
+using System;
+
+
+
+namespace Rokojori.PointClouds
+{
+ public class Point
+ {
+ public Vector3 position;
+ public Color color;
+ public Vector3 normal;
+ public Vector2 uv;
+
+ public Point Lerp( Point point, float amount )
+ {
+ var p = new Point();
+
+ p.position = position.Lerp( point.position, amount );
+ p.normal = normal.Lerp( point.normal, amount ).Normalized();
+ p.uv = uv.Lerp( point.uv, amount );
+ p.color = color.ToVector4().Lerp( point.color.ToVector4(), amount ).ToColor();
+
+ return p;
+ }
+
+
+ public static Point AsAverage( List points )
+ {
+ var p = new Point();
+
+ var color = new Vector4();
+
+ for ( int i = 0; i < points.Count; i++ )
+ {
+ var d = 1f / ( i + 1 );
+ p.position += d * ( points[ i ].position - p.position );
+ p.normal += d * ( points[ i ].normal - p.normal );
+ p.uv += d * ( points[ i ].uv - p.uv );
+ color += d * ( points[ i ].color.ToVector4() - color );
+ }
+
+ p.normal = p.normal.Normalized();
+ p.color = color.ToColor();
+
+ return p;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Runtime/LOD/PointClouds/Point.cs.uid b/Runtime/LOD/PointClouds/Point.cs.uid
new file mode 100644
index 0000000..2e9696d
--- /dev/null
+++ b/Runtime/LOD/PointClouds/Point.cs.uid
@@ -0,0 +1 @@
+uid://cwoel0qagkx5l
diff --git a/Runtime/LOD/PointClouds/PointCloud.cs b/Runtime/LOD/PointClouds/PointCloud.cs
new file mode 100644
index 0000000..291cfae
--- /dev/null
+++ b/Runtime/LOD/PointClouds/PointCloud.cs
@@ -0,0 +1,66 @@
+using System.Collections;
+using System.Collections.Generic;
+using Godot;
+using System;
+
+using System.Threading.Tasks;
+
+namespace Rokojori.PointClouds
+{
+ public class PointCloud
+ {
+ public List points = new List();
+
+ public Vector3 center;
+
+ public void ComputeCenter()
+ {
+ center = Math3D.ComputeAverage( points, p => p.position );
+ }
+
+ public Box3 boundingBox;
+
+ public void ComputeBoundingBox()
+ {
+ boundingBox = Box3.Create( points, p => p.position );
+ }
+
+ public void SortByDistanceToCenter()
+ {
+ ComputeCenter();
+
+ points.Sort(
+ ( a, b ) =>
+ {
+ var dA = ( a.position - center ).LengthSquared();
+ var dB = ( b.position - center ).LengthSquared();
+
+ return Mathf.Sign( dB - dA );
+ }
+ );
+ }
+
+ public ArrayMesh GenerateMesh( float normalOffset = 0f )
+ {
+ var mg = GenerateMeshGeometry( normalOffset );
+ return mg.GenerateMesh( Mesh.PrimitiveType.Points, null, false );
+ }
+
+ public MeshGeometry GenerateMeshGeometry( float normalOffset = 0f )
+ {
+ var mg = new MeshGeometry( true, false, true, false );
+ mg.customMeshAttributes.Add( new MeshAttributeVector4List( 0 ) );
+ mg.customMeshAttributes.Add( new MeshAttributeVector4List( 1 ) );
+
+ points.ForEach(
+ ( p ) =>
+ {
+ mg.AddPoint( p.position + p.normal * normalOffset, p.color.srgbToLinear(), p.normal, 0, p.uv, 1 );
+ }
+ );
+
+ return mg;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Runtime/LOD/PointClouds/PointCloud.cs.uid b/Runtime/LOD/PointClouds/PointCloud.cs.uid
new file mode 100644
index 0000000..d229241
--- /dev/null
+++ b/Runtime/LOD/PointClouds/PointCloud.cs.uid
@@ -0,0 +1 @@
+uid://box2fnynegqik
diff --git a/Runtime/LOD/PointClouds/PointCloudGenerator.cs b/Runtime/LOD/PointClouds/PointCloudGenerator.cs
new file mode 100644
index 0000000..4043210
--- /dev/null
+++ b/Runtime/LOD/PointClouds/PointCloudGenerator.cs
@@ -0,0 +1,258 @@
+using System.Collections;
+using System.Collections.Generic;
+using Godot;
+using System;
+using System.Threading.Tasks;
+
+
+namespace Rokojori.PointClouds
+{
+ [Tool]
+ [GlobalClass]
+ public partial class PointCloudGenerator:Node3D
+ {
+ [Export]
+ public MeshInstance3D mesh;
+
+ [Export]
+ public PointCloudSampler.SampleMode sampleMode = PointCloudSampler.SampleMode.Center;
+
+ [Export]
+ public float samplingPixelDensity = 0.01f;
+
+ [Export]
+ public float outputPixelDensity = 0.5f;
+
+ [Export]
+
+ public int numPoints = 0;
+
+ [Export]
+ public int squareTexture;
+
+ [Export]
+ public Material material;
+
+ [Export]
+ public bool createOutputs = false;
+
+ [Export]
+ public MeshInstance3D[] outputs;
+
+ [ExportGroup("Lerping")]
+ [Export]
+ public Curve lowerLerp;
+
+ [Export]
+ public Curve higherLerp;
+
+ [Export]
+ public int lerpSteps;
+
+ [ExportGroup("")]
+
+ [Export]
+ public LODNode lod;
+
+
+ [Export]
+ bool working = false;
+
+ [Export]
+ public string[] outputInfos = [];
+
+ [ExportToolButton( "Generate")]
+ public Callable GenerateButton => Callable.From(
+ () =>
+ {
+ GeneratePointCloud();
+ }
+ );
+
+ public async void GeneratePointCloud()
+ {
+ if ( working )
+ {
+ return;
+ }
+
+ working = true;
+ numPoints = -1;
+ this.LogInfo( "Generating Point Cloud" );
+
+
+ var sampler = new PointCloudSampler();
+
+ if ( samplingPixelDensity > 0 )
+ {
+ sampler.densityResolution = samplingPixelDensity;
+ }
+
+ sampler.materials = Materials.GetAll( mesh );
+ sampler.albedoTexture = sampler.materials.Map( m => Texture2DPropertyName.albedoTexture );
+ sampler.albedo = sampler.materials.Map( m => ColorPropertyName.albedo ); ;
+
+ var cloud = await sampler.SampleFromMesh( sampleMode, (ArrayMesh) mesh.Mesh, this );
+
+ this.LogInfo( "Sampled mesh" );
+
+ var time = Async.GetTimeMS();
+ // cloud.ComputeBoundingBox();
+
+ time = Async.PrintAndUpdateMS( time );
+
+ var longestCM = cloud.boundingBox.size.MaxDimension() * 100f;
+ var nextP2CM = MathX.NextPowerOfTwo( longestCM );
+ var rootSize = nextP2CM / 100f;
+ var ocMin = cloud.boundingBox.center - Vector3.One * rootSize;
+ var ocMax = cloud.boundingBox.center + Vector3.One * rootSize;
+ var depth = Mathf.CeilToInt( MathX.Exponent( 2, rootSize/ samplingPixelDensity ) );
+
+ var ocTree = new PointCloudOcTree( ocMin, ocMax, rootSize, depth );
+
+ var minSize = ocTree.GetSizeForLevel( depth );
+
+ this.LogInfo( "OcTree MinDepth", minSize._CM() );
+
+ this.RequestNextFrame();
+ time = Async.PrintAndUpdateMS( time, "Inserting to octree" );
+ ocTree.Insert( cloud.points );
+ time = Async.PrintAndUpdateMS( time, "Inserting to octree Done" );
+ var level = ocTree.GetLevelForSize( outputPixelDensity );
+
+ this.LogInfo( "Combining" );
+ this.RequestNextFrame();
+ time = Async.PrintAndUpdateMS( time, "Combining" );
+
+ ocTree.normalSeperation = false;
+ ocTree.CombineUntilLevel( level );
+
+ time = Async.PrintAndUpdateMS( time, "Combining Done" );
+
+ this.LogInfo( "Combining level", level, "Max Level", depth );
+
+
+
+ this.LogInfo( "Cloud Points:", cloud.points.Count );
+
+ if ( outputs != null )
+ {
+ outputs = [];
+ }
+
+
+
+ this.DestroyChildren();
+
+
+ var levelMap = ocTree.GetLevelMap();
+
+ var levelInstances = new List();
+
+ var lodBuilder = LODBuilder.ByCameraDistance();
+
+ lodBuilder.SetMaterial( material );
+
+ // lodBuilder.Add( 0, mesh.Mesh, mesh.GetSurfaceOverrideMaterial( 0 ) );
+
+ var meshGeometries = new List();
+
+ var infos = new List();
+
+ for ( int i = level + 1; i < depth; i++ )
+ {
+ var filledLevels = levelMap[ i ].Filter( l => l.values != null );
+ var points = new List();
+
+ filledLevels.ForEach(
+ l =>
+ {
+ points.AddRange( l.values );
+ }
+ );
+
+ var pc = new PointCloud();
+ pc.points = points;
+ pc.ComputeCenter();
+ pc.SortByDistanceToCenter();
+
+ var levelSize = ocTree.GetSizeForLevel( i );
+ var distance = Cameras.ComputePixelDistanceForSizeVertical( 60, levelSize, new Vector2( 1920, 1080 ) ) * 4;
+
+ // this.LogInfo(
+ // "Level", i,
+ // "Points", pc.points.Count,
+ // "Size", RegexUtility._CM( levelSize ),
+ // "Distance", RegexUtility._M( distance )
+ // );
+
+ var mg = pc.GenerateMeshGeometry( levelSize/2f );
+ meshGeometries.Add( mg );
+
+
+ infos.Add(
+ RJLog.Stringify(
+ "Level", i,
+ "Points", pc.points.Count,
+ "Size", levelSize._CM(),
+ "Vertices", mg.numVertices,
+ "Normals", mg.numNormals,
+ "UVs", mg.numUV2s,
+ "Colors", mg.numColors
+ )
+ );
+
+ var t = MathX.Map( i, level + 1, depth - 1, 1, 0 );
+ mg.lodEdgeLength = levelSize;
+ // mg.ApplyTranslation( new Vector3( 100 * t, 0, 0 ) );
+
+ var mesh = pc.GenerateMesh( levelSize / 2f );
+
+ lodBuilder.Add( levelSize, mesh );
+
+ if ( createOutputs )
+ {
+ var meshInstance = this.CreateChild();
+ meshInstance.Mesh = mesh;
+ meshInstance.MaterialOverride = material;
+
+ levelInstances.Add( meshInstance );
+ }
+ }
+
+ if ( createOutputs )
+ {
+ outputs = levelInstances.ToArray();
+ }
+
+ outputInfos = infos.ToArray();
+
+ var lodMesh = this.CreateChild( "LOD Mesh" );
+ meshGeometries.Reverse();
+ var mainGeometry = meshGeometries[ 0 ];
+ var lerpingData = new LODLerpingData();
+ lerpingData.lowerLevelWeights = lowerLerp;
+ lerpingData.higherLevelWeights = higherLerp;
+ lerpingData.lerpSteps = lerpSteps;
+ lodMesh.Mesh = MeshGeometry.GeneratePointsLODMesh( meshGeometries, lerpingData, false );
+
+ lodMesh.SetSurfaceOverrideMaterial( 0, material );
+
+ lod = lodBuilder.Create( this, "LOD" );
+ lod.lod0 = lod.CreateChild();
+ lod.lod0.Mesh = mesh.Mesh;
+ lod.lod0.MaterialOverride = mesh.GetSurfaceOverrideMaterial( 0 );
+ lod.distanceScale = 4;
+
+
+ // cloud.SortByDistanceToCenter();
+
+ // numPoints = cloud.points.Count;
+ // squareTexture = (int)Mathf.Pow( numPoints, 0.5f );
+ // output.Mesh = cloud.GenerateMesh();
+
+ working = false;
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/Runtime/LOD/PointClouds/PointCloudGenerator.cs.uid b/Runtime/LOD/PointClouds/PointCloudGenerator.cs.uid
new file mode 100644
index 0000000..74a6ad1
--- /dev/null
+++ b/Runtime/LOD/PointClouds/PointCloudGenerator.cs.uid
@@ -0,0 +1 @@
+uid://v88dkcabl6kd
diff --git a/Runtime/LOD/PointClouds/PointCloudOcTree.cs b/Runtime/LOD/PointClouds/PointCloudOcTree.cs
new file mode 100644
index 0000000..a4891bb
--- /dev/null
+++ b/Runtime/LOD/PointClouds/PointCloudOcTree.cs
@@ -0,0 +1,94 @@
+using System.Collections;
+using System.Collections.Generic;
+using Godot;
+using System;
+
+using System.Threading.Tasks;
+
+namespace Rokojori.PointClouds
+{
+ public class PointCloudOcTree:OcTree
+ {
+ List compressedNormals = new List()
+ {
+ Vector3.Up,
+ Vector3.Down,
+ Vector3.Forward,
+ Vector3.Back,
+ Vector3.Right,
+ Vector3.Left
+ };
+
+ public bool normalSeperation = false;
+
+ public PointCloudOcTree( Vector3 min, Vector3 max, float rootCellSize, int maxDepth )
+ :base( null, min, max, rootCellSize, maxDepth )
+ {
+ this._getPosition = GetPointPosition;
+ this._combinePoints = CombinePoints;
+ this._smoothPoints = SmoothPoints;
+ }
+
+ protected Vector3 GetPointPosition( Point p )
+ {
+ return p.position;
+ }
+
+ protected int GetNormalIndex( Vector3 normal )
+ {
+ var closest = -1;
+ var closestDistance = 100f;
+
+ for ( int i = 0; i < compressedNormals.Count; i++ )
+ {
+ var d = compressedNormals[ i ].DistanceSquaredTo( normal );
+
+ if ( d < closestDistance )
+ {
+ closestDistance = d;
+ closest = i;
+ }
+
+ }
+
+ return closest;
+
+ }
+
+ protected List CombinePoints( List points )
+ {
+ if ( normalSeperation )
+ {
+ var lists = compressedNormals.Map( c => new List() );
+
+ points.ForEach(
+ ( p )=>
+ {
+ var normalIndex = GetNormalIndex( p.normal );
+ lists[ normalIndex ].Add( p );
+ }
+ );
+
+ lists = lists.Filter( n => n.Count > 0 );
+
+
+ return lists.Map( l => Point.AsAverage( l ) );
+ }
+
+ return new List(){ Point.AsAverage( points ) };
+ }
+
+ protected List SmoothPoints( List targets, List points, float amount )
+ {
+ if ( targets == null || points == null )
+ {
+ return targets;
+ }
+
+ var average = Point.AsAverage( points );
+ return targets.Map( t => t.Lerp( average, amount ) );;
+ }
+
+
+ }
+}
\ No newline at end of file
diff --git a/Runtime/LOD/PointClouds/PointCloudOcTree.cs.uid b/Runtime/LOD/PointClouds/PointCloudOcTree.cs.uid
new file mode 100644
index 0000000..793e79f
--- /dev/null
+++ b/Runtime/LOD/PointClouds/PointCloudOcTree.cs.uid
@@ -0,0 +1 @@
+uid://dhn11j8cvbexf
diff --git a/Runtime/LOD/PointClouds/PointCloudSampler.cs b/Runtime/LOD/PointClouds/PointCloudSampler.cs
new file mode 100644
index 0000000..77826b1
--- /dev/null
+++ b/Runtime/LOD/PointClouds/PointCloudSampler.cs
@@ -0,0 +1,406 @@
+using System.Collections;
+using System.Collections.Generic;
+using Godot;
+using System;
+
+using System.Threading.Tasks;
+
+namespace Rokojori.PointClouds
+{
+
+ public class PointCloudSampler
+ {
+ public enum SampleMode
+ {
+ Center,
+ Vertices,
+ CenterAndVertices,
+ Density
+ }
+
+ public List materials;
+ public List albedoTexture;
+ public List albedo;
+ public float densityResolution = 0.2f;
+
+ List albedoTextureImages;
+
+ SampleMode mode;
+
+ ArrayMesh mesh;
+ Node worker;
+
+ PointCloud pointCloud;
+
+
+
+ public PointCloud SampleFromMeshSync( SampleMode sampleMode, ArrayMesh mesh, Node worker )
+ {
+ this.mode = sampleMode;
+ this.mesh = mesh;
+ this.worker = worker;
+
+ pointCloud = new PointCloud();
+
+ GrabTextureImages();
+
+ for ( int i = 0; i < mesh.GetSurfaceCount(); i++ )
+ {
+ SampleSurfaceSync( mesh, i );
+ }
+
+ return pointCloud;
+
+ }
+
+ public async Task SampleFromMesh( SampleMode sampleMode, ArrayMesh mesh, Node worker )
+ {
+
+ this.mode = sampleMode;
+ this.mesh = mesh;
+ this.worker = worker;
+
+ pointCloud = new PointCloud();
+
+ try
+ {
+
+
+ GrabTextureImages();
+
+ for ( int i = 0; i < mesh.GetSurfaceCount(); i++ )
+ {
+ await SampleSurface( mesh, i );
+ }
+
+ }
+ catch ( System.Exception e )
+ {
+ worker.LogError( e );
+ }
+
+ return pointCloud;
+
+ }
+
+
+
+ void GrabTextureImages()
+ {
+ var index = -1;
+
+ albedoTextureImages = new List();
+
+ albedoTexture.ForEach(
+ ( at )=>
+ {
+ index++;
+
+ if ( at == null )
+ {
+ albedoTextureImages.Add( null );
+ }
+
+ var material = materials[ index ];
+ var texture = at.Get( material );
+
+ if ( texture == null )
+ {
+ albedoTextureImages.Add( null );
+
+ return;
+
+ }
+
+ var image = texture.GetImage();
+
+ albedoTextureImages.Add( image );
+ }
+ );
+
+ // worker.LogInfo( "Added image:", albedoTextureImages.Count );
+ }
+
+
+
+ public void SampleSurfaceSync( ArrayMesh mesh, int surface )
+ {
+ if ( SampleMode.Density == mode )
+ {
+ SampleDensitySync( mesh, surface );
+ return;
+ }
+
+ SampleSimpleSync( mesh, surface );
+
+ return;
+ }
+
+
+
+ void SampleDensitySync( ArrayMesh mesh, int surface )
+ {
+ var mdt = new MeshDataTool();
+ mdt.CreateFromSurface( mesh, surface );
+
+ var nullTriangles = 0;
+ var numFaces = mdt.GetFaceCount();
+ var subDivs = 0;
+
+
+ for ( int i = 0; i < mdt.GetFaceCount(); i++ )
+ {
+ // time = await Async.WaitIfExceeded( time, worker );
+
+ var positions = new List();
+ var normals = new List();
+ var uvs = new List();
+
+ for ( int j = 0; j < 3; j++ )
+ {
+ var index = mdt.GetFaceVertex( i, j );
+
+ var p = mdt.GetVertex( index );
+ var uv = mdt.GetVertexUV( index );
+ var n = mdt.GetVertexNormal( index );
+
+ positions.Add( p );
+ uvs.Add( uv );
+ normals.Add( n );
+ }
+
+ var triangle = Triangle3.CreateFrom( positions );
+
+ AddPoints( positions, uvs, normals );
+
+ var minArea = densityResolution;
+
+ var points = new List();
+ triangle.GetSubdivisionPoints( points, minArea );
+
+
+ // worker.LogInfo( triangle.area, points.Count );
+
+
+ points.ForEach(
+ ( p )=>
+ {
+ var uv = (Vector2) triangle.Lerp( p, uvs[ 0 ], uvs[ 1 ], uvs[ 2 ] );
+ var n = (Vector3) triangle.Lerp( p, normals[ 0 ], normals[ 1 ], normals[ 2 ] );
+
+ AddPoint( p, uv, n );
+ subDivs ++;
+ }
+ );
+
+ // if ( triangle.area > minArea || triangle.longestEdgeLength > densityResolution )
+ // {
+ // worker.LogInfo( area );
+ // subDivs++;
+ // }
+
+ // triangle = triangle.Shrink( densityResolution );
+
+ // if ( triangle == null )
+ // {
+ // nullTriangles++;
+ // }
+
+
+ // var maxIterations = 1000;
+ // var iteration = 0;
+
+ // while ( iteration < maxIterations && triangle != null && ( triangle.area > minArea || triangle.longestEdgeLength > densityResolution ) )
+ // {
+ // var newUVs = positions.Map( p => (Vector2) triangle.Lerp( p, uvs[ 0 ], uvs[ 1 ], uvs[ 2 ] ) );
+ // var newNormals = positions.Map( p => (Vector3) triangle.Lerp( p, normals[ 0 ], normals[ 1 ], normals[ 2 ] ) );
+
+ // positions = triangle.points;
+ // uvs = newUVs;
+ // normals = newNormals;
+
+ // AddPoints( positions, uvs, normals );
+
+ // triangle = triangle.Shrink( densityResolution );
+
+ // iteration ++;
+ // }
+ }
+
+ worker.LogInfo( "Tris:", numFaces, "Nulls:", nullTriangles, "Subs:", subDivs );
+ }
+
+ void AddPoints( List p, List u, List n )
+ {
+ for ( int i = 0; i < p.Count; i++ )
+ {
+ AddPoint( p[ i ], u[ i ], n[ i ] );
+ }
+ }
+
+ void AddPoint( Vector3 p, Vector2 u, Vector3 n )
+ {
+ var point = new Point();
+ point.color = Colors.White;
+ point.position = p;
+ point.normal = n;
+ point.uv = u;
+ pointCloud.points.Add( point );
+ }
+
+ public async Task SampleSurface( ArrayMesh mesh, int surface )
+ {
+ await SampleSimple( mesh, surface );
+
+ return;
+ }
+
+ void SampleSimpleSync( ArrayMesh mesh, int surface )
+ {
+ var mdt = new MeshDataTool();
+ mdt.CreateFromSurface( mesh, surface );
+
+
+ var sampleVertices = SampleMode.Vertices == mode || SampleMode.CenterAndVertices == mode;
+ var sampleCenter = SampleMode.Center == mode || SampleMode.CenterAndVertices == mode;
+
+ for ( int i = 0; i < mdt.GetFaceCount(); i++ )
+ {
+ // time = await Async.WaitIfExceeded( time, worker );
+
+ var center = new Vector3( 0, 0, 0 );
+ var centerColor = new Vector4( 0, 0, 0, 0 );
+ var centerNormal = new Vector3( 0, 0, 0 );
+ var centerUV = new Vector2( 0, 0 );
+
+ for ( int j = 0; j < 3; j++ )
+ {
+ var index = mdt.GetFaceVertex( i, j );
+
+ var p = mdt.GetVertex( index );
+ var uv = mdt.GetVertexUV( index );
+ var n = mdt.GetVertexNormal( index );
+ var color = Colors.White;
+
+ var image = albedoTextureImages[ surface ];
+
+ if ( image != null )
+ {
+ color = image.Sample( uv, ColorX.EdgeMode.Repeat );
+ }
+
+
+
+ center += p;
+ centerColor += color.ToVector4();
+ centerNormal += n;
+ centerUV += uv;
+
+ if ( sampleVertices )
+ {
+ var point = new Point();
+ point.color = color;
+ point.position = p;
+ point.normal = n;
+ point.uv = uv;
+ pointCloud.points.Add( point );
+ }
+
+ }
+
+ if ( sampleCenter )
+ {
+ var point = new Point();
+ point.color = ( centerColor / 3.0f ).ToColor();
+ point.position = center / 3.0f;
+ point.normal = centerNormal / 3.0f;
+ point.uv = centerUV / 3.0f;
+ pointCloud.points.Add( point );
+ }
+ }
+ }
+
+ async Task SampleSimple( ArrayMesh mesh, int surface )
+ {
+ var mdt = new MeshDataTool();
+ mdt.CreateFromSurface( mesh, surface );
+
+
+ var sampleVertices = SampleMode.Vertices == mode || SampleMode.CenterAndVertices == mode;
+ var sampleCenter = SampleMode.Center == mode || SampleMode.CenterAndVertices == mode;
+
+ var time = Async.StartTimer();
+
+ var faces = mdt.GetFaceCount();
+
+ for ( int i = 0; i < faces; i++ )
+ {
+ time = await Async.WaitIfExceeded( time, worker );
+
+ if ( i % 10000 == 0 )
+ {
+ worker.LogInfo( "Tris:", i + "/" + faces, RegexUtility._FFF( ( 100f * i ) / (float)faces ) );
+ }
+
+ var center = new Vector3( 0, 0, 0 );
+ var centerColor = new Vector4( 0, 0, 0, 0 );
+ var centerNormal = new Vector3( 0, 0, 0 );
+ var centerUV = new Vector2( 0, 0 );
+
+ for ( int j = 0; j < 3; j++ )
+ {
+ var index = mdt.GetFaceVertex( i, j );
+
+ var p = mdt.GetVertex( index );
+ var uv = mdt.GetVertexUV( index );
+ var n = mdt.GetVertexNormal( index );
+ var color = Colors.White;
+
+ var image = albedoTextureImages[ surface ];
+
+ if ( image != null )
+ {
+ color = image.Sample( uv, ColorX.EdgeMode.Repeat );
+ }
+
+
+
+ center += p;
+ centerColor += color.ToVector4();
+ centerNormal += n;
+ centerUV += uv;
+
+ if ( sampleVertices )
+ {
+ var point = new Point();
+ point.color = color;
+ point.position = p;
+ point.normal = n;
+ point.uv = uv;
+ pointCloud.points.Add( point );
+ }
+
+ }
+
+ if ( sampleCenter )
+ {
+ var point = new Point();
+ point.color = ( centerColor / 3.0f ).ToColor();
+ point.position = center / 3.0f;
+ point.normal = centerNormal / 3.0f;
+ point.uv = centerUV / 3.0f;
+ pointCloud.points.Add( point );
+ }
+
+ if ( i == 0 )
+ {
+ pointCloud.boundingBox = Box3.Create( center, center );
+ }
+ else
+ {
+ pointCloud.boundingBox.IncludePoint( center );
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Runtime/LOD/PointClouds/PointCloudSampler.cs.uid b/Runtime/LOD/PointClouds/PointCloudSampler.cs.uid
new file mode 100644
index 0000000..db921ec
--- /dev/null
+++ b/Runtime/LOD/PointClouds/PointCloudSampler.cs.uid
@@ -0,0 +1 @@
+uid://dfksgo3wn58ts
diff --git a/Runtime/Math/Geometry/Box3.cs b/Runtime/Math/Geometry/Box3.cs
index 7d3ce8b..7c4e9f0 100644
--- a/Runtime/Math/Geometry/Box3.cs
+++ b/Runtime/Math/Geometry/Box3.cs
@@ -44,6 +44,21 @@ namespace Rokojori
return b;
}
+ public static Box3 Create( List data, System.Func getPosition )
+ {
+ var min = new Vector3( float.MaxValue, float.MaxValue, float.MaxValue );
+ var max = new Vector3( -float.MaxValue, -float.MaxValue, -float.MaxValue );
+
+ for ( int i = 0; i < data.Count; i++ )
+ {
+ var p = getPosition( data[ i ] );
+ min = min.Min( p );
+ max = max.Max( p );
+ }
+
+ return Create( min, max );
+ }
+
public Vector3 size => max - min;
public void IncludePoint( Vector3 p )
diff --git a/Runtime/Math/Geometry/Triangle2.cs b/Runtime/Math/Geometry/Triangle2.cs
index 188fea7..66608f2 100644
--- a/Runtime/Math/Geometry/Triangle2.cs
+++ b/Runtime/Math/Geometry/Triangle2.cs
@@ -114,7 +114,7 @@ namespace Rokojori
if ( i01 == null || ! ContainsPoint( (Vector2) i01 ) )
{
- RJLog.Log( "i01", i01, i01 != null ? ContainsPoint( (Vector2) i01 ) : false );
+ // RJLog.Log( "i01", i01, i01 != null ? ContainsPoint( (Vector2) i01 ) : false );
return null;
}
@@ -122,7 +122,7 @@ namespace Rokojori
if ( i12 == null || ! ContainsPoint( (Vector2) i12 ) )
{
- RJLog.Log( "i12", i12, i12 != null ? ContainsPoint( (Vector2) i12 ) : false );
+ // RJLog.Log( "i12", i12, i12 != null ? ContainsPoint( (Vector2) i12 ) : false );
return null;
}
@@ -130,7 +130,7 @@ namespace Rokojori
if ( i20 == null || ! ContainsPoint( (Vector2) i20 ) )
{
- RJLog.Log( "i20", i20, i20 != null ? ContainsPoint( (Vector2) i20 ) : false );
+ // RJLog.Log( "i20", i20, i20 != null ? ContainsPoint( (Vector2) i20 ) : false );
return null;
}
diff --git a/Runtime/Math/Geometry/Triangle3.cs b/Runtime/Math/Geometry/Triangle3.cs
index ea666a4..16fc06b 100644
--- a/Runtime/Math/Geometry/Triangle3.cs
+++ b/Runtime/Math/Geometry/Triangle3.cs
@@ -30,6 +30,17 @@ namespace Rokojori
_needsUpdate = true;
}
+ public static Triangle3 CreateFrom( List points, int offset = 0 )
+ {
+ var t = new Triangle3(
+ points[ 0 ],
+ points[ 1 ],
+ points[ 2 ]
+ );
+
+ return t;
+ }
+
public static Triangle3 CreateFrom( MeshGeometry mg, int a, int b, int c )
{
var t = new Triangle3(
@@ -144,6 +155,7 @@ namespace Rokojori
}
public float area => ComputeTriangleArea( a, b, c );
+
public bool Intersects( Line3 line )
{
@@ -268,6 +280,11 @@ namespace Rokojori
return LerpCurve3.FromPoints( GetPoint( 0 ), GetPoint( 1 ), GetPoint( 2 ) );
}
+ public List points => new List(){ a, b, c };
+
+
+
+
public Vector3 GetPoint( int i )
{
if ( i == 0 )
@@ -286,6 +303,22 @@ namespace Rokojori
return a;
}
+ public Line3 GetEdge( int i )
+ {
+ var p0 = GetPoint( i );
+ var p1 = GetPoint( i % 3 );
+
+ return Line3.Create( p0, p1 );
+ }
+
+ public float GetEdgeLength( int i )
+ {
+ return GetEdge( i ).length;
+ }
+
+ public float longestEdgeLength => MathX.Min( GetEdgeLength( 0 ), GetEdgeLength( 1 ), GetEdgeLength( 2 ) );
+ public float shortestEdgeLength => MathX.Max( GetEdgeLength( 0 ), GetEdgeLength( 1 ), GetEdgeLength( 2 ) );
+
void SetPointsToLine( Line3 line, int index )
{
line.Set( GetPoint( index ), GetPoint( ( index + 1 ) % 3 ) );
@@ -484,8 +517,50 @@ namespace Rokojori
return new Triangle3( Math3D.XYasXZ( t.a ), Math3D.XYasXZ( t.b ), Math3D.XYasXZ( t.c ) );
}
+ public List CenterSplit()
+ {
+ var m = center;
+
+ var subs = new List
+ {
+ new Triangle3( a, b, m ),
+ new Triangle3( b, c, m ),
+ new Triangle3( c, a, m )
+ };
+
+ return subs;
+ }
+
+ public void GetSubdivisionPoints( List output, float minArea )
+ {
+ var processingList = new List();
+
+ processingList.Add( this );
+
+ while ( processingList.Count > 0 )
+ {
+ var tri = processingList.Shift();
+ output.Add( tri.center );
+
+ var subs = tri.CenterSplit();
+
+ subs.ForEach(
+ st =>
+ {
+ if ( st.area > minArea )
+ {
+ processingList.Add( st );
+ }
+ }
+ );
+
+ }
+
+
+
+ }
+
-
public static bool InsideTriangle( Vector3 point, Vector3 a, Vector3 b, Vector3 c )
{
var bary = GetBaryCentricCoordinate( point, a, b, c );
@@ -533,7 +608,66 @@ namespace Rokojori
return new Vector3( 1f - u - v, v, u );
}
-
+ public Vector3? GetBaryCentricCoordinate( Vector3 point )
+ {
+ return GetBaryCentricCoordinate( point, a, b, c );
+ }
+
+ public Vector4? Lerp( Vector3 p, Vector4 x, Vector4 y, Vector4 z )
+ {
+ var bary = GetBaryCentricCoordinate( p );
+
+ if ( bary == null )
+ {
+ return null;
+ }
+
+ var w = ( Vector3 ) bary;
+
+ return w.Lerp( x, y, z );
+ }
+
+ public Vector3? Lerp( Vector3 p, Vector3 x, Vector3 y, Vector3 z )
+ {
+ var bary = GetBaryCentricCoordinate( p );
+
+ if ( bary == null )
+ {
+ return null;
+ }
+
+ var w = ( Vector3 ) bary;
+
+ return w.Lerp( x, y, z );
+ }
+
+ public Vector2? Lerp( Vector3 p, Vector2 x, Vector2 y, Vector2 z )
+ {
+ var bary = GetBaryCentricCoordinate( p );
+
+ if ( bary == null )
+ {
+ return null;
+ }
+
+ var w = ( Vector3 ) bary;
+
+ return w.Lerp( x, y, z );
+ }
+
+ public float? Lerp( Vector3 p, float x, float y, float z )
+ {
+ var bary = GetBaryCentricCoordinate( p );
+
+ if ( bary == null )
+ {
+ return null;
+ }
+
+ var w = ( Vector3 ) bary;
+
+ return w.Lerp( x, y, z );
+ }
}
}
\ No newline at end of file
diff --git a/Runtime/Math/Math2D.cs b/Runtime/Math/Math2D.cs
index 37392af..c2f8f88 100644
--- a/Runtime/Math/Math2D.cs
+++ b/Runtime/Math/Math2D.cs
@@ -6,8 +6,23 @@ using System;
namespace Rokojori
{
- public class Math2D
+ public static class Math2D
{
+ public static Vector2I RoundToInt( this Vector2 v )
+ {
+ return new Vector2I( Mathf.RoundToInt( v.X ), Mathf.RoundToInt( v.Y ) );
+ }
+
+ public static Vector2I FloorToInt( this Vector2 v )
+ {
+ return new Vector2I( Mathf.FloorToInt( v.X ), Mathf.FloorToInt( v.Y ) );
+ }
+
+ public static Vector2I CeilToInt( this Vector2 v )
+ {
+ return new Vector2I( Mathf.CeilToInt( v.X ), Mathf.CeilToInt( v.Y ) );
+ }
+
public static float LookingAtEachOtherAngle( Vector2 lookDirectionA, Vector2 lookDirectionB )
{
return Dot( lookDirectionA, lookDirectionB );
@@ -90,5 +105,40 @@ namespace Rokojori
{
return clockwise ? Rotate90DegreesRight( v ) : Rotate90DegreesLeft( v );
}
+
+ public static Vector2 ComputeAverage( List points )
+ {
+ if ( points == null || points.Count == 0 )
+ {
+ return Vector2.Zero;
+ }
+
+ var mean = Vector2.Zero;
+
+ for ( int i = 0; i < points.Count; i++ )
+ {
+ mean += ( points[ i ] - mean ) / ( i + 1 );
+ }
+
+ return mean;
+
+ }
+
+ public static Vector2 ComputeAverage( List containers, Func getPoint )
+ {
+ if ( containers == null || containers.Count == 0 )
+ {
+ return Vector2.Zero;
+ }
+
+ var mean = Vector2.Zero;
+
+ for ( int i = 0; i < containers.Count; i++ )
+ {
+ mean += ( getPoint( containers[ i ] ) - mean ) / ( i + 1 );
+ }
+
+ return mean;
+ }
}
}
\ No newline at end of file
diff --git a/Runtime/Math/Math3D.cs b/Runtime/Math/Math3D.cs
index e04d704..358ca58 100644
--- a/Runtime/Math/Math3D.cs
+++ b/Runtime/Math/Math3D.cs
@@ -28,6 +28,98 @@ namespace Rokojori
return LookingAtEachOther( fromDirection, to - from );
}
+ public static Vector4 Lerp( this Vector3 w, Vector4 x, Vector4 y, Vector4 z )
+ {
+ return w.X * x + w.Y * y + w.Z * z;
+ }
+
+ public static Vector3 Lerp( this Vector3 w, Vector3 x, Vector3 y, Vector3 z )
+ {
+ return w.X * x + w.Y * y + w.Z * z;
+ }
+
+
+ public static Vector2 Lerp( this Vector3 w, Vector2 x, Vector2 y, Vector2 z )
+ {
+ return w.X * x + w.Y * y + w.Z * z;
+ }
+
+ public static float Lerp( this Vector3 w, float x, float y, float z )
+ {
+ return w.X * x + w.Y * y + w.Z * z;
+ }
+
+
+ public static Vector3 ComputeAverage( List points )
+ {
+ if ( points == null || points.Count == 0 )
+ {
+ return Vector3.Zero;
+ }
+
+ var mean = Vector3.Zero;
+
+ for ( int i = 0; i < points.Count; i++ )
+ {
+ mean += ( points[ i ] - mean ) / ( i + 1 );
+ }
+
+ return mean;
+
+ }
+
+
+ public static Vector3 ComputeAverage( List containers, Func getPoint )
+ {
+ if ( containers == null || containers.Count == 0 )
+ {
+ return Vector3.Zero;
+ }
+
+ var mean = Vector3.Zero;
+
+ for ( int i = 0; i < containers.Count; i++ )
+ {
+ mean += ( getPoint( containers[ i ] ) - mean ) / ( i + 1 );
+ }
+
+ return mean;
+ }
+
+ public static Vector4 ComputeAverage( List points )
+ {
+ if ( points == null || points.Count == 0 )
+ {
+ return Vector4.Zero;
+ }
+
+ var mean = Vector4.Zero;
+
+ for ( int i = 0; i < points.Count; i++ )
+ {
+ mean += ( points[ i ] - mean ) / ( i + 1 );
+ }
+
+ return mean;
+
+ }
+
+ public static Vector4 ComputeAverage( List containers, Func getPoint )
+ {
+ if ( containers == null || containers.Count == 0 )
+ {
+ return Vector4.Zero;
+ }
+
+ var mean = Vector4.Zero;
+
+ for ( int i = 0; i < containers.Count; i++ )
+ {
+ mean += ( getPoint( containers[ i ] ) - mean ) / ( i + 1 );
+ }
+
+ return mean;
+ }
public static Transform3D TRS( Vector3 translation, Quaternion rotation, Vector3 scale )
{
@@ -267,6 +359,40 @@ namespace Rokojori
return quaternion;
}
+ public static Vector3 GetMin( List data, Func getPosition )
+ {
+ var min = new Vector3( float.MaxValue, float.MaxValue, float.MaxValue );
+
+ for ( int i = 0; i < data.Count; i++ )
+ {
+ min = min.Min( getPosition( data[ i ] ) );
+ }
+
+ return min;
+ }
+
+ public static Vector3 GetMax( List data, Func getPosition )
+ {
+ var max = new Vector3( -float.MaxValue, -float.MaxValue, -float.MaxValue );
+
+ for ( int i = 0; i < data.Count; i++ )
+ {
+ max = max.Max( getPosition( data[ i ] ) );
+ }
+
+ return max;
+ }
+
+ public static float MaxDimension( this Vector3 v )
+ {
+ return MathX.Max( v.X, v.Y, v.Z );
+ }
+
+ public static float MinDimension( this Vector3 v )
+ {
+ return MathX.Min( v.X, v.Y, v.Z );
+ }
+
public static Vector3 MinGlobalPosition( Node3D a, Node3D b )
{
return a.GlobalPosition.Min( b.GlobalPosition );
@@ -287,7 +413,7 @@ namespace Rokojori
return a.Position.Max( b.Position );
}
- public static Vector3 SnapRounded( Vector3 v, Vector3 snapping )
+ public static Vector3 SnapRounded( this Vector3 v, Vector3 snapping )
{
v.X = MathX.SnapRounded( v.X, snapping.X );
v.Y = MathX.SnapRounded( v.Y, snapping.Y );
@@ -296,7 +422,7 @@ namespace Rokojori
return v;
}
- public static Vector3 SnapRoundedXZ( Vector3 v, float snapX, float snapZ )
+ public static Vector3 SnapRoundedXZ( this Vector3 v, float snapX, float snapZ )
{
v.X = MathX.SnapRounded( v.X, snapX );
v.Z = MathX.SnapRounded( v.Z, snapZ );
@@ -304,7 +430,7 @@ namespace Rokojori
return v;
}
- public static Vector3 SnapCeiled( Vector3 v, Vector3 snapping )
+ public static Vector3 SnapCeiled( this Vector3 v, Vector3 snapping )
{
v.X = MathX.SnapCeiled( v.X, snapping.X );
v.Y = MathX.SnapCeiled( v.Y, snapping.Y );
@@ -313,7 +439,7 @@ namespace Rokojori
return v;
}
- public static Vector3 SnapFloored( Vector3 v, Vector3 snapping )
+ public static Vector3 SnapFloored( this Vector3 v, Vector3 snapping )
{
v.X = MathX.SnapFloored( v.X, snapping.X );
v.Y = MathX.SnapFloored( v.Y, snapping.Y );
@@ -322,12 +448,58 @@ namespace Rokojori
return v;
}
+ public static Vector3I RoundToInt( this Vector3 v )
+ {
+ return new Vector3I( Mathf.RoundToInt( v.X ) , Mathf.RoundToInt( v.Y ), Mathf.RoundToInt( v.Z ) );
+ }
+
+ public static Vector3I FloorToInt( this Vector3 v )
+ {
+ return new Vector3I( Mathf.FloorToInt( v.X ) , Mathf.FloorToInt( v.Y ), Mathf.FloorToInt( v.Z ) );
+ }
+
+ public static Vector3I CeilToInt( this Vector3 v )
+ {
+ return new Vector3I( Mathf.CeilToInt( v.X ) , Mathf.CeilToInt( v.Y ), Mathf.CeilToInt( v.Z ) );
+ }
+
+ public static Vector2 XY( this Vector3 v )
+ {
+ return new Vector2( v.X, v.Y );
+ }
+
+ public static Vector2 YX( this Vector3 v )
+ {
+ return new Vector2( v.Y, v.X );
+ }
+
+ public static Vector2 XZ( this Vector3 v )
+ {
+ return new Vector2( v.X, v.Z );
+ }
+
+ public static Vector2 ZX( this Vector3 v )
+ {
+ return new Vector2( v.Z, v.X );
+ }
+
+ public static Vector2 YZ( this Vector3 v )
+ {
+ return new Vector2( v.Y, v.Z );
+ }
+
+ public static Vector2 ZY( this Vector3 v )
+ {
+ return new Vector2( v.Y, v.Z );
+ }
+
public static Basis AlignUp( Basis basis, Vector3 upDirection )
{
basis.Y = upDirection;
basis.X = - basis.Z.Cross( upDirection );
return basis.Orthonormalized();
}
+
public static Quaternion AlignUp( Quaternion rotation, Vector3 upDirection )
{
diff --git a/Runtime/Math/MathX.cs b/Runtime/Math/MathX.cs
index b446df8..092e974 100644
--- a/Runtime/Math/MathX.cs
+++ b/Runtime/Math/MathX.cs
@@ -6,7 +6,7 @@ using System;
namespace Rokojori
{
- public class MathX
+ public static class MathX
{
public const float fps120Delta = 1/120f;
@@ -143,6 +143,60 @@ namespace Rokojori
return Mathf.Atan2( circle.Y, circle.X );
}
+
+
+ public static int MultiIndexToFlatIndex( List indices, List sizes )
+ {
+ var index = 0;
+ var scale = 1;
+
+ for ( int i = sizes.Count - 1; i >= 0; i-- )
+ {
+ index += indices[ i ] * scale;
+ scale *= sizes[ i ];
+ }
+
+ return index;
+ }
+
+ public static int MultiIndexToFlatIndex( Vector3I indices, Vector3I sizes )
+ {
+ var index = 0;
+
+ index += indices.Z;
+ index += indices.Y * sizes.Z;
+ index += indices.X * sizes.Z * sizes.Y;
+ return index;
+ }
+
+ public static List FlatIndexToMultiIndex( int index, List sizes, List multiIndex = null )
+ {
+ multiIndex = multiIndex == null ? new List( new int[ sizes.Count ] ) : multiIndex;
+
+ for ( int i = sizes.Count - 1; i >= 0; i-- )
+ {
+ multiIndex[ i ] = index % sizes[ i ];
+ index /= sizes[i];
+ }
+
+ return multiIndex;
+ }
+
+ public static Vector3I FlatIndexToMultiIndex( int index, Vector3I sizes )
+ {
+ var multiIndex = Vector3I.Zero;
+
+ multiIndex.Z = index % sizes.Z;
+ index /= sizes.Z;
+
+ multiIndex.Y = index % sizes.Y;
+ index /= sizes.Y;
+
+ multiIndex.X = index % sizes.X;
+
+ return multiIndex;
+ }
+
public const float DegreesToRadians = Mathf.Pi / 180f;
public const float RadiansToDegrees = 180f / Mathf.Pi;
@@ -428,6 +482,23 @@ namespace Rokojori
return max;
}
+ public static float CurveAverage( this Curve c, int numSamples = 20 )
+ {
+ if ( c == null )
+ {
+ return 0;
+ }
+
+ var value = 0f;
+
+ for ( int i = 0; i < numSamples; i++ )
+ {
+ value += c.Sample( (float) i / ( numSamples - 1 ) );
+ }
+
+ return value / numSamples;
+ }
+
public static List GetCurveWeights( Curve curve, int num, bool normalize = true )
{
var sum = 0f;
diff --git a/Runtime/Networking/Backends/LAN/LANNetworkingBackend.cs b/Runtime/Networking/Backends/LAN/LANNetworkingBackend.cs
index 92776c2..c0f2b32 100644
--- a/Runtime/Networking/Backends/LAN/LANNetworkingBackend.cs
+++ b/Runtime/Networking/Backends/LAN/LANNetworkingBackend.cs
@@ -3,7 +3,7 @@ using System.Collections.Generic;
namespace Rokojori
{
- [GlobalClass]
+ [Tool][GlobalClass]
public partial class LANNetworkBackend:NetworkBackend
{
SceneMultiplayer multiplayer;
diff --git a/Runtime/Networking/NetworkManager.cs b/Runtime/Networking/NetworkManager.cs
index 6c2e91b..df070d8 100644
--- a/Runtime/Networking/NetworkManager.cs
+++ b/Runtime/Networking/NetworkManager.cs
@@ -3,7 +3,7 @@ using Godot;
namespace Rokojori
{
- [GlobalClass,Icon("res://addons/rokojori_action_library/Icons/NetworkManager.svg")]
+ [Tool][GlobalClass,Icon("res://addons/rokojori_action_library/Icons/NetworkManager.svg")]
public partial class NetworkManager:Node
{
[Export]
diff --git a/Runtime/Networking/Nodes/AddNetworkingNodes.cs b/Runtime/Networking/Nodes/AddNetworkingNodes.cs
index 0daf5dc..efcb31d 100644
--- a/Runtime/Networking/Nodes/AddNetworkingNodes.cs
+++ b/Runtime/Networking/Nodes/AddNetworkingNodes.cs
@@ -4,7 +4,7 @@ using System.Collections.Generic;
namespace Rokojori
{
- [GlobalClass]
+ [Tool][GlobalClass]
public partial class AddNetworkNodes:Action
{
[Export]
diff --git a/Runtime/Networking/Session/JoinSession.cs b/Runtime/Networking/Session/JoinSession.cs
index 02e15d5..17384e2 100644
--- a/Runtime/Networking/Session/JoinSession.cs
+++ b/Runtime/Networking/Session/JoinSession.cs
@@ -4,7 +4,7 @@ using System.Collections.Generic;
namespace Rokojori
{
- [GlobalClass]
+ [Tool][GlobalClass]
public partial class JoinSession:Action
{
[Export]
diff --git a/Runtime/Networking/Session/NetworkSessionRequest.cs b/Runtime/Networking/Session/NetworkSessionRequest.cs
index fc28ca4..43ed557 100644
--- a/Runtime/Networking/Session/NetworkSessionRequest.cs
+++ b/Runtime/Networking/Session/NetworkSessionRequest.cs
@@ -4,7 +4,7 @@ using System.Collections.Generic;
namespace Rokojori
{
- [GlobalClass]
+ [Tool][GlobalClass]
public partial class NetworkSessionRequest:Resource
{
[Export]
diff --git a/Runtime/Networking/Session/StartSession.cs b/Runtime/Networking/Session/StartSession.cs
index 34fb0d4..d0ee747 100644
--- a/Runtime/Networking/Session/StartSession.cs
+++ b/Runtime/Networking/Session/StartSession.cs
@@ -4,7 +4,7 @@ using System.Collections.Generic;
namespace Rokojori
{
- [GlobalClass]
+ [Tool][GlobalClass]
public partial class StartSession:Action
{
[Export]
diff --git a/Runtime/Networking/Transforms/NetworkTransform3D.cs b/Runtime/Networking/Transforms/NetworkTransform3D.cs
index 66fbe4c..c34b79e 100644
--- a/Runtime/Networking/Transforms/NetworkTransform3D.cs
+++ b/Runtime/Networking/Transforms/NetworkTransform3D.cs
@@ -4,7 +4,7 @@ using System.Collections.Generic;
namespace Rokojori
{
- [GlobalClass]
+ [Tool][GlobalClass]
public partial class NetworkTransform3D:NetworkNode
{
[Export]
diff --git a/Runtime/Networking/Transforms/NetworkTransform3DType.cs b/Runtime/Networking/Transforms/NetworkTransform3DType.cs
index 4545853..cfc00a1 100644
--- a/Runtime/Networking/Transforms/NetworkTransform3DType.cs
+++ b/Runtime/Networking/Transforms/NetworkTransform3DType.cs
@@ -4,7 +4,7 @@ using System.Collections.Generic;
namespace Rokojori
{
- [GlobalClass]
+ [Tool][GlobalClass]
public partial class NetworkTransform3DType:Resource
{
[ExportCategory("Position")]
diff --git a/Runtime/Procedural/Assets/Grass/GrassPatch.cs b/Runtime/Procedural/Assets/Grass/GrassPatch.cs
index 8ab4424..89a0f1b 100644
--- a/Runtime/Procedural/Assets/Grass/GrassPatch.cs
+++ b/Runtime/Procedural/Assets/Grass/GrassPatch.cs
@@ -2,6 +2,7 @@ using System.Collections;
using System.Collections.Generic;
using Godot;
using System;
+using System.Threading.Tasks;
@@ -24,6 +25,9 @@ namespace Rokojori
[Export]
public bool updateAlways;
+ [Export]
+ public Material material;
+
[ExportGroup( "Patch")]
[Export]
public float patchSize = 2;
@@ -233,6 +237,21 @@ namespace Rokojori
[Export]
public int currentLODLevel = -1;
+ [Export]
+ public bool generateLODMesh = false;
+
+ [Export]
+ public float customLODEdgeLength = 0;
+
+ [Export]
+ public int lodLerpSteps = 2;
+
+ [Export]
+ public Curve lowCurve = MathX.Curve( 0, 1 );
+
+ [Export]
+ public Curve highCurve = MathX.Curve( 0, 1 );
+
// SerializedGodotObject _cached;
public override void _Process( double delta )
@@ -287,7 +306,7 @@ namespace Rokojori
return bladeSegments * 2 * ComputeNumBlades();
}
- public void CreatePatch()
+ public async Task CreatePatch()
{
if ( blades == 0 && bladesX == 0 && bladesZ == 0)
{
@@ -311,18 +330,12 @@ namespace Rokojori
lodLevels = new GrassPatchLODLevel[]{};
}
- if ( output == null )
- {
- this.output = this.CreateChild( "Grass Patch Mesh" );
- }
-
- var mg = new MeshGeometry();
+ this.DestroyChildren();
+ this.output = this.CreateChild( "Grass Patch Mesh" );
+
- var cellSizeX = ( patchSizeX + patchSize ) / ( bladesX + blades );
- var cellSizeZ = ( patchSizeZ + patchSize ) / ( bladesZ + blades );
-
var random = new LCG();
random.SetSeed( 1712 + seed );
@@ -330,7 +343,97 @@ namespace Rokojori
_maxWidth = MathX.CurveMaximum( bladeWidth );
- X_numBlades = 0;
+ X_numBlades = 0;
+
+ if ( ! generateLODMesh )
+ {
+ var mg = CreatePatchGeometry();
+ X_numTriangles = mg.indices.Count / 3;
+ output.Mesh = mg.GenerateMesh();
+ output.Mesh.SurfaceSetMaterial( 0, material );
+ }
+ else
+ {
+ var cachedCurrentLODLevel = currentLODLevel;
+
+ try
+ {
+ var mgs = new List();
+
+ var numTris = 0;
+
+ var time = this.StartAsyncTimer();
+ var edgeLength = ( MathX.CurveAverage( bladeHeight ) * MathX.CurveAverage( bladeScale ) ) / bladeSegments;
+
+ if ( customLODEdgeLength > 0 )
+ {
+ edgeLength = customLODEdgeLength;
+ }
+
+ this.LogInfo( "Edge Length", edgeLength );
+
+ for ( int i = 0; i < lodLevels.Length + 1; i++ )
+ {
+ time = await this.WaitForAsyncTimer( time );
+
+ var lodIndex = i - 1;
+ currentLODLevel = lodIndex;
+
+ var lodEdgeLengthScale = 1f;
+
+ if ( lodIndex >= 0 )
+ {
+ lodEdgeLengthScale = lodLevels[ lodIndex ].lodEdgeLengthScale;
+ }
+
+ var lodMG = CreatePatchGeometry();
+ lodMG.lodEdgeLength = edgeLength * lodEdgeLengthScale;
+ numTris += lodMG.vertices.Count;
+
+ lodMG.ApplyTranslation( new Vector3( 0, 0, 0 ) ) ;
+ mgs.Add( lodMG );
+ }
+
+ X_numTriangles = numTris;
+
+ LODLerpingData lerpingData = null;
+
+ if ( lodLerpSteps > 0 )
+ {
+ lerpingData = new LODLerpingData();
+ lerpingData.lowerLevelWeights = lowCurve;
+ lerpingData.higherLevelWeights = highCurve;
+ lerpingData.lerpSteps = lodLerpSteps;
+ }
+
+ output.Mesh = MeshGeometry.GenerateTrianglesLODMesh( mgs, lerpingData, false );
+ output.Mesh.SurfaceSetMaterial( 0, material );
+
+
+ }
+ catch ( System.Exception e )
+ {
+ this.LogError( e );
+ }
+
+ currentLODLevel = cachedCurrentLODLevel;
+ }
+
+
+ }
+
+ bool debugBlade = false;
+
+ MeshGeometry CreatePatchGeometry()
+ {
+ var random = new LCG();
+ random.SetSeed( 1712 + seed );
+
+ var mg = new MeshGeometry();
+
+ var cellSizeX = ( patchSizeX + patchSize ) / ( bladesX + blades );
+ var cellSizeZ = ( patchSizeZ + patchSize ) / ( bladesZ + blades );
+
var allBladesX = bladesX + blades;
var allBladesZ = bladesZ + blades;
@@ -368,6 +471,11 @@ namespace Rokojori
var bladeMG = CreateBlade( random, worldPosition );
+
+ if ( bladeMG.numNormals == 0 || bladeMG.numUVs == 0 )
+ {
+ this.LogInfo( "Invalid mg" );
+ }
if ( filterValue > filterTreshold )
{
@@ -417,6 +525,11 @@ namespace Rokojori
}
}
+ if ( mg.numNormals == 0 || mg.numUVs == 0 )
+ {
+ this.LogInfo( "Invalid mg" );
+ }
+
if ( centerPatch )
{
@@ -431,6 +544,11 @@ namespace Rokojori
var turbulenceAmount = random.Sample( vertexTurbulenceAmount );
var turbulenceScale = Vector3.One * ( vertexTurbulenceScale == null ? 1 : random.Sample( vertexTurbulenceScale ) );
+ if ( currentLODLevel != -1 )
+ {
+ turbulenceAmount *= lodLevels[ currentLODLevel ].turbulenceScale;
+ }
+
if ( vertexTurbulenceScaleX != null )
{
turbulenceScale.X *= random.Sample( vertexTurbulenceScaleX );
@@ -479,16 +597,10 @@ namespace Rokojori
mg.ScaleZForY( scaleZForY );
}
-
-
- X_numTriangles = mg.indices.Count / 3;
+ return mg;
-
- output.Mesh = mg.GenerateMesh();
}
- bool debugBlade = false;
-
MeshGeometry CreateBlade( RandomEngine random, Vector3 position )
{
// if ( debugBlade )
diff --git a/Runtime/Procedural/Assets/Grass/GrassPatchLODLevel.cs b/Runtime/Procedural/Assets/Grass/GrassPatchLODLevel.cs
index e073509..fdfb4d6 100644
--- a/Runtime/Procedural/Assets/Grass/GrassPatchLODLevel.cs
+++ b/Runtime/Procedural/Assets/Grass/GrassPatchLODLevel.cs
@@ -23,8 +23,14 @@ namespace Rokojori
[Export( PropertyHint.Range, "0,1")]
public float filter = 1;
+ [Export]
+ public float turbulenceScale = 0f;
+
[Export]
public Trillean allowTrianglesOnEnd;
+ [Export]
+ public float lodEdgeLengthScale = 1;
+
}
}
\ No newline at end of file
diff --git a/Runtime/Procedural/Assets/Grass/Windy Grass Shader.gdshader b/Runtime/Procedural/Assets/Grass/Windy Grass Shader.gdshader
index 31a7e9b..efa6746 100644
--- a/Runtime/Procedural/Assets/Grass/Windy Grass Shader.gdshader
+++ b/Runtime/Procedural/Assets/Grass/Windy Grass Shader.gdshader
@@ -5,7 +5,6 @@ render_mode blend_mix, depth_draw_opaque, cull_back, diffuse_burley, specular_sc
#include "res://addons/rokojori_action_library/Runtime/Shading/Library/Math.gdshaderinc"
#include "res://addons/rokojori_action_library/Runtime/Shading/Library/Transform.gdshaderinc"
-#include "res://addons/rokojori_action_library/Runtime/Shading/Library/Noise.gdshaderinc"
#include "res://addons/rokojori_action_library/Runtime/Shading/Library/Colors.gdshaderinc"
uniform vec4 albedo : source_color;
diff --git a/Runtime/Procedural/Baking/PixelDensityTool.cs b/Runtime/Procedural/Baking/PixelDensityTool.cs
new file mode 100644
index 0000000..2a27df9
--- /dev/null
+++ b/Runtime/Procedural/Baking/PixelDensityTool.cs
@@ -0,0 +1,61 @@
+using System.Collections;
+using System.Collections.Generic;
+using Godot;
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+
+
+namespace Rokojori
+{
+ [Tool]
+ [GlobalClass]
+ public partial class PixelDensityTool:Node
+ {
+ [Export]
+ public float fovDegrees = 60;
+
+ [Export]
+ public Vector2 screenSize = new Vector2( 1920, 1080 );
+
+ [Export]
+ public float startDistance = 1;
+
+ [Export]
+ public float endDistance = 4000;
+
+ [Export]
+ public int numEntries = 20;
+
+ [Export]
+ public float densityTablePower = 2;
+
+
+
+ [ExportToolButton( "Compute Pixel Density Tables")]
+ public Callable ExecuteButton => Callable.From( () =>
+ {
+ var list = new List();
+ for ( int i = 0; i < numEntries; i++ )
+ {
+ var t = i / ( float ) ( numEntries - 1f );
+ t = Mathf.Pow( t, densityTablePower );
+ var distance = Mathf.Lerp( startDistance, endDistance, t );
+
+ var pixelDensity = Cameras.ComputePixelDensityVertical( fovDegrees, distance, screenSize );
+ var minSize = 1f / pixelDensity;
+
+ list.Add( "[" + distance._M()+ "] density: " + pixelDensity._FF() + "px/m - size:" + ( minSize < 1 ? minSize._CM() : minSize._M() ) + " " );
+
+ }
+
+ distanceToPixelSize = list.ToArray();
+ }
+ );
+
+ [Export]
+ public string[] distanceToPixelSize;
+
+ }
+}
\ No newline at end of file
diff --git a/Runtime/Procedural/Baking/PixelDensityTool.cs.uid b/Runtime/Procedural/Baking/PixelDensityTool.cs.uid
new file mode 100644
index 0000000..b8efff4
--- /dev/null
+++ b/Runtime/Procedural/Baking/PixelDensityTool.cs.uid
@@ -0,0 +1 @@
+uid://cgavpqkqwoh8c
diff --git a/Runtime/Procedural/Baking/PointMesh/PointCloudGenerator.cs b/Runtime/Procedural/Baking/PointMesh/PointCloudGenerator.cs
new file mode 100644
index 0000000..e69de29
diff --git a/Runtime/Procedural/Baking/PointMesh/PointCloudGenerator.cs.uid b/Runtime/Procedural/Baking/PointMesh/PointCloudGenerator.cs.uid
new file mode 100644
index 0000000..fdea0af
--- /dev/null
+++ b/Runtime/Procedural/Baking/PointMesh/PointCloudGenerator.cs.uid
@@ -0,0 +1 @@
+uid://dglam3n6d2cwf
diff --git a/Runtime/Procedural/Baking/PointMesh/PointMeshBaker.cs b/Runtime/Procedural/Baking/PointMesh/PointMeshBaker.cs
new file mode 100644
index 0000000..f5e8f32
--- /dev/null
+++ b/Runtime/Procedural/Baking/PointMesh/PointMeshBaker.cs
@@ -0,0 +1,86 @@
+using System.Collections;
+using System.Collections.Generic;
+using Godot;
+using System;
+
+
+
+namespace Rokojori
+{
+ [Tool]
+ [GlobalClass]
+ public partial class PointMeshBaker:Node
+ {
+ [Export]
+ public Texture2D albedo;
+
+ [Export]
+ public Texture2D normal;
+
+ [Export]
+ public MeshInstance3D output;
+
+ [Export]
+ public int pixelsX;
+
+ [Export]
+ public int pixelsY;
+
+ [Export]
+ public Vector2 pivot;
+
+ [Export]
+ public Vector3 offset;
+
+ [Export]
+ public Vector3 scale;
+
+ [ExportToolButton( "Bake")]
+ public Callable BakeButton => Callable.From( () =>
+ {
+ BakePointMesh();
+ }
+ );
+
+ public void BakePointMesh()
+ {
+ var mg = new MeshGeometry( true, false, true, false );
+ mg.customMeshAttributes.Add( new MeshAttributeVector4List( 0 ) );
+ mg.normals = null;
+
+ var albedoImage = albedo.GetImage();
+ var normalImage = normal.GetImage();
+
+ var index = 0;
+
+ for ( int x = 0; x < pixelsX; x++ )
+ {
+ for ( int y = 0; y < pixelsY; y++ )
+ {
+ var fx = x / (float) pixelsX;
+ var fy = 1f - y / (float) pixelsY;
+
+ var position = new Vector3( fx - pivot.X, fy - pivot.Y, 0 ) * scale + offset;
+
+ var colorPosX = Mathf.FloorToInt( fx * albedoImage.GetSize().X );
+ var colorPosY = Mathf.FloorToInt( fy * albedoImage.GetSize().Y );
+ var color = albedoImage.GetPixel( colorPosX, colorPosY );
+
+ var normalPosX = Mathf.FloorToInt( fx * normalImage.GetSize().X );
+ var normalPosY = Mathf.FloorToInt( fy * normalImage.GetSize().Y );
+ var normal = normalImage.GetPixel( normalPosX, normalPosY );
+
+ mg.AddPoint( position, color.srgbToLinear(), normal.srgbToLinear().ToVector3() * 2.0f - Vector3.One, 0 );
+
+ // this.LogInfo( "Created point at:", index, position );
+
+ index ++;
+ }
+ }
+
+ output = output == null ? this.CreateChild() : output;
+
+ output.Mesh = mg.GenerateMesh( Mesh.PrimitiveType.Points, null, false );
+ }
+ }
+}
\ No newline at end of file
diff --git a/Runtime/Procedural/Baking/PointMesh/PointMeshBaker.cs.uid b/Runtime/Procedural/Baking/PointMesh/PointMeshBaker.cs.uid
new file mode 100644
index 0000000..50b0cce
--- /dev/null
+++ b/Runtime/Procedural/Baking/PointMesh/PointMeshBaker.cs.uid
@@ -0,0 +1 @@
+uid://bx52vt8y36542
diff --git a/Runtime/Procedural/Mesh/MassRenderer.cs b/Runtime/Procedural/Mesh/MassRenderer.cs
index a9fce4b..33bccb3 100644
--- a/Runtime/Procedural/Mesh/MassRenderer.cs
+++ b/Runtime/Procedural/Mesh/MassRenderer.cs
@@ -2,6 +2,7 @@ using System.Collections;
using System.Collections.Generic;
using Godot;
using System;
+using System.Linq;
@@ -43,7 +44,19 @@ namespace Rokojori
[ExportGroup("Outputs")]
[Export]
- public LODMultiMeshInstance3D multiMeshNode;
+ public LODMultiMeshInstance3D[] multiMeshNodes;
+
+ [Export]
+ public float multiMeshSplitSize = 25;
+
+ [Export]
+ public Vector3 multiMeshSplitScatterRelative = new Vector3( 2, 0, 2 );
+
+ [Export]
+ public Vector3 multiMeshSplitScatterScale = new Vector3( 10, 10, 10 );
+
+ Dictionary multiMeshMapping = new Dictionary();
+
[Export]
public Node3D meshesContainer;
[Export]
@@ -112,16 +125,25 @@ namespace Rokojori
public void Clear()
{
- multiMeshNode = Nodes.EnsureValid( multiMeshNode );
+ if ( multiMeshNodes != null )
+ {
+ for ( int i = 0 ; i < multiMeshNodes.Length; i++ )
+ {
+ multiMeshNodes[ i ] = Nodes.EnsureValid( multiMeshNodes[ i ] );
+
+ if ( multiMeshNodes[ i ] != null )
+ {
+ Nodes.RemoveAndDelete( multiMeshNodes[ i ] );
+ }
+ }
+
+ multiMeshNodes = [];
+ }
+
meshesContainer = Nodes.EnsureValid( meshesContainer );
combinedMesh = Nodes.EnsureValid( combinedMesh );
- if ( multiMeshNode != null )
- {
- multiMeshNode.Multimesh.InstanceCount = 0;
- }
-
if ( meshesContainer != null )
{
Nodes.RemoveAndDeleteChildren( meshesContainer );
@@ -135,36 +157,58 @@ namespace Rokojori
void CreateMultiMeshes()
{
- multiMeshNode = Nodes.EnsureValid( multiMeshNode );
-
- if ( multiMeshNode == null )
- {
- multiMeshNode = this.CreateChild();
- multiMeshNode.Multimesh = new MultiMesh();
- multiMeshNode.Multimesh.TransformFormat = MultiMesh.TransformFormatEnum.Transform3D;
- }
-
- var mm = multiMeshNode.Multimesh;
-
- mm.Mesh = mesh;
- mm.InstanceCount = positions.Length;
-
- if ( materialOveride != null )
- {
- multiMeshNode.MaterialOverride = materialOveride;
- }
+ var instancesSorted = new MapList();
for ( int i = 0; i < positions.Length; i++ )
{
- var trsf = Math3D.TRS( positions[ i ], rotations[ i ], scales[ i ] );
- mm.SetInstanceTransform( i, trsf );
- }
+ var floatIndex = ( positions[ i ] / multiMeshSplitSize );
+ floatIndex += Noise.Perlin3( positions[ i ] * multiMeshSplitScatterScale ) * multiMeshSplitScatterRelative;
+ var index = floatIndex.RoundToInt();
+ instancesSorted.Add( index, i );
+ }
+
+ var keys = instancesSorted.Keys.ToList();
+ var numMultiMeshes = keys.Count;
+ multiMeshNodes = new LODMultiMeshInstance3D[ numMultiMeshes ];
+
+ for ( int i = 0; i < numMultiMeshes; i++ )
+ {
+ var multiMeshNode = this.CreateChild();
+ multiMeshNode.Multimesh = new MultiMesh();
+ multiMeshNode.Multimesh.TransformFormat = MultiMesh.TransformFormatEnum.Transform3D;
+
+ multiMeshNodes[ i ] = multiMeshNode;
+
+ var mm = multiMeshNode.Multimesh;
+ var meshIndices = instancesSorted[ keys[ i ] ];
+
+ mm.Mesh = mesh;
+ mm.InstanceCount = meshIndices.Count;
+
+ if ( materialOveride != null )
+ {
+ multiMeshNode.MaterialOverride = materialOveride;
+ }
+
+ this.LogInfo( i, ">>", keys[ i ], ">>", meshIndices.Count );
+
+ for ( int j = 0; j < meshIndices.Count; j++ )
+ {
+ var index = meshIndices[ j ];
+ var trsf = Math3D.TRS( positions[ index ], rotations[ index ], scales[ index ] );
+ mm.SetInstanceTransform( j, trsf );
+ }
+
+ }
+
}
+
+
void CreateMeshes()
{
if ( meshesContainer == null )
diff --git a/Runtime/Procedural/Mesh/MaterialSurfaceContainer.cs b/Runtime/Procedural/Mesh/MaterialSurfaceContainer.cs
index 3b3af51..fd50fbe 100644
--- a/Runtime/Procedural/Mesh/MaterialSurfaceContainer.cs
+++ b/Runtime/Procedural/Mesh/MaterialSurfaceContainer.cs
@@ -34,6 +34,18 @@ namespace Rokojori
public abstract T GetMaterialInSlot( MaterialSlot slot ) where T:Material;
public abstract void SetMaterialInSlot( MaterialSlot slot, Material material );
+ public static Material GetActiveFrom( Node3D owner, int surfaceIndex = 0 )
+ {
+ var msc = From( owner, surfaceIndex );
+ return msc.GetActiveMaterial();
+ }
+
+ public static T GetActiveFrom( Node3D owner, int surfaceIndex = 0 ) where T:Material
+ {
+ var msc = From( owner, surfaceIndex );
+ return msc.GetActiveMaterial();
+ }
+
public static void SetMaterialInSlot( Node3D owner, int surfaceIndex, MaterialSlot slot, Material material )
{
var c = MaterialSurfaceContainer.From( owner, surfaceIndex );
diff --git a/Runtime/Procedural/Mesh/MeshGeometry.cs b/Runtime/Procedural/Mesh/MeshGeometry.cs
index 316f910..1f855bf 100644
--- a/Runtime/Procedural/Mesh/MeshGeometry.cs
+++ b/Runtime/Procedural/Mesh/MeshGeometry.cs
@@ -2,21 +2,280 @@ using System.Collections;
using System.Collections.Generic;
using Godot;
using System;
-
+using System.Linq;
namespace Rokojori
{
+ public class LODLerpingData
+ {
+ public Curve lowerLevelWeights = MathX.Curve( 0, 1 );
+ public Curve higherLevelWeights = MathX.Curve( 0, 1 );
+
+ public int lerpSteps = 3;
+
+ public void Sample( float weight, ListView source, List output )
+ {
+ var numSamples = Mathf.Round( weight * source.Count );
+
+ var sourceCopy = source.SubList();
+ sourceCopy.Shuffle( 7 );
+ sourceCopy.Shuffle( 3 );
+ sourceCopy.Shuffle( 5 );
+ sourceCopy.ShuffleMultiple( 5 );
+
+ for ( int i = 0; i < numSamples; i++ )
+ {
+ output.Add( sourceCopy[ i ] );
+ }
+ }
+
+ public float Sample( ListView lower, ListView higher, List output, int step, float lowerSize, float higherSize )
+ {
+ var t = lerpSteps == 1 ? 0.5f : ( ( step + 1 )/ ( ( lerpSteps + 2 ) - 1f ) );
+
+ // 4
+ // 0 => ( 0 + 1 ) / ( ( 4 + 2 ) -1 ) => 1 / ( 5 ) => 0.2
+ // 1 => ( 1 + 1 ) / ( ( 4 + 2 ) -1 ) => 2 / ( 5 ) => 0.4
+ // 2 => ( 2 + 1 ) / ( ( 4 + 2 ) -1 ) => 3 / ( 5 ) => 0.6
+ // 3 => ( 3 + 1 ) / ( ( 4 + 2 ) -1 ) => 4 / ( 5 ) => 0.8
+
+ var lowerWeight = lowerLevelWeights.Sample( 1f - t );
+ var higherWeight = higherLevelWeights.Sample( t );
+
+ Sample( lowerWeight, lower, output );
+ Sample( higherWeight, higher, output );
+
+ return Mathf.Lerp( lowerSize, higherSize, t );
+ }
+ }
+
+ public class CustomMeshAttributeList
+ {
+ protected int customIndex = 0;
+ public int index => customIndex;
+
+ protected ArrayMesh.ArrayCustomFormat customFormat;
+ public ArrayMesh.ArrayCustomFormat format => customFormat;
+
+ public CustomMeshAttributeList( int customIndex, ArrayMesh.ArrayCustomFormat customFormat )
+ {
+ this.customIndex = customIndex;
+ this.customFormat = customFormat;
+ }
+
+ public Mesh.ArrayFormat GetFormatFlag()
+ {
+ var channel = GetChannelFormat();
+ var shift = GetShift();
+
+ return channel | (Mesh.ArrayFormat)( ( (long)customFormat ) << (int)shift );
+ }
+
+ public virtual CustomMeshAttributeList Clone()
+ {
+ return null;
+ }
+
+
+ public SurfaceTool.CustomFormat GetSurfaceToolFormat()
+ {
+ if ( ArrayMesh.ArrayCustomFormat.RgbaFloat == customFormat )
+ {
+ return SurfaceTool.CustomFormat.RgbaFloat;
+ }
+
+ if ( ArrayMesh.ArrayCustomFormat.RgbFloat == customFormat )
+ {
+ return SurfaceTool.CustomFormat.RgbFloat;
+ }
+
+ if ( ArrayMesh.ArrayCustomFormat.RgFloat == customFormat )
+ {
+ return SurfaceTool.CustomFormat.RgFloat;
+ }
+
+ if ( ArrayMesh.ArrayCustomFormat.RFloat == customFormat )
+ {
+ return SurfaceTool.CustomFormat.RFloat;
+ }
+
+ return SurfaceTool.CustomFormat.Max;
+ }
+
+ public ArrayMesh.ArrayType GetTypeSlot()
+ {
+ if ( customIndex == 0 )
+ {
+ return ArrayMesh.ArrayType.Custom0;
+ }
+
+ if ( customIndex == 1 )
+ {
+ return ArrayMesh.ArrayType.Custom1;
+ }
+
+ if ( customIndex == 2 )
+ {
+ return ArrayMesh.ArrayType.Custom2;
+ }
+
+ if ( customIndex == 3 )
+ {
+ return ArrayMesh.ArrayType.Custom3;
+ }
+
+ return ArrayMesh.ArrayType.Max;
+
+ }
+
+ public ArrayMesh.ArrayFormat GetChannelFormat()
+ {
+ if ( customIndex == 0 )
+ {
+ return ArrayMesh.ArrayFormat.FormatCustom0;
+ }
+
+ if ( customIndex == 1 )
+ {
+ return ArrayMesh.ArrayFormat.FormatCustom1;
+ }
+
+ if ( customIndex == 2 )
+ {
+ return ArrayMesh.ArrayFormat.FormatCustom2;
+ }
+
+ if ( customIndex == 3 )
+ {
+ return ArrayMesh.ArrayFormat.FormatCustom3;
+ }
+
+ return 0;
+
+ }
+
+ public long GetShift()
+ {
+ if ( customIndex == 0 )
+ {
+ return (long)ArrayMesh.ArrayFormat.FormatCustom0Shift;
+ }
+
+ if ( customIndex == 1 )
+ {
+ return (long)ArrayMesh.ArrayFormat.FormatCustom1Shift;
+ }
+
+ if ( customIndex == 2 )
+ {
+ return (long)ArrayMesh.ArrayFormat.FormatCustom2Shift;
+ }
+
+ if ( customIndex == 3 )
+ {
+ return (long)ArrayMesh.ArrayFormat.FormatCustom3Shift;
+ }
+
+ return 0;
+ }
+
+ public virtual void WriteData( Godot.Collections.Array array )
+ {
+
+ }
+
+ public virtual void Write( SurfaceTool surfaceTool, int index )
+ {
+
+ }
+
+ public virtual void AddTo( CustomMeshAttributeList list, int sourceIndex )
+ {
+
+ }
+ }
+
+ public abstract class CustomMeshAttributeList : CustomMeshAttributeList
+ {
+ public List values = new List();
+ public CustomMeshAttributeList( int customIndex, ArrayMesh.ArrayCustomFormat customFormat ):base( customIndex, customFormat )
+ {}
+ }
+
+
+ public class MeshAttributeVector4List : CustomMeshAttributeList
+ {
+ public MeshAttributeVector4List( int customIndex ):base( customIndex, ArrayMesh.ArrayCustomFormat.RgbaFloat )
+ {}
+
+ public override CustomMeshAttributeList Clone()
+ {
+ var v4 = new MeshAttributeVector4List( customIndex );
+ v4.values = Lists.Clone( values );
+
+ return v4;
+ }
+
+ public override void WriteData( Godot.Collections.Array array )
+ {
+ var colors = new List();
+
+
+ values.ForEach(
+ ( v ) =>
+ {
+ colors.AddRange( new float[]{ v.X, v.Y, v.Z, v.W } );
+ }
+ );
+
+ RJLog.Log( GetTypeSlot(), customFormat, colors.Count );
+ array[ (int) GetTypeSlot() ] = colors.ToArray();
+
+
+ }
+
+
+ public override void Write( SurfaceTool tool, int index )
+ {
+ tool.SetCustom( this.index, new Color( values[ index ].X, values[ index ].Y, values[ index ].Z, values[ index ].W) );
+ }
+
+ public override void AddTo( CustomMeshAttributeList list, int sourceIndex )
+ {
+ var typeList = list as MeshAttributeVector4List;
+
+ typeList.values.Add( values[ sourceIndex ] );
+ }
+
+ }
+
+
+
public class MeshGeometry
{
public string name;
+
public List vertices = new List();
+ public int numVertices => Lists.Size( vertices );
+
public List indices = new List();
public List normals;
+ public int numNormals => Lists.Size( normals );
+
public List uvs;
+ public int numUVs => Lists.Size( uvs );
+
public List uv2s;
+ public int numUV2s => Lists.Size( uv2s );
+
public List colors;
+ public int numColors => Lists.Size( colors );
+
+ public float lodEdgeLength = 0;
+
+ public List customMeshAttributes = new List();
public int numTriangles => indices.Count / 3;
@@ -517,6 +776,19 @@ namespace Rokojori
mg.uv2s = Lists.Clone( uv2s );
mg.colors = Lists.Clone( colors );
+ mg.lodEdgeLength = lodEdgeLength;
+
+ if ( customMeshAttributes != null )
+ {
+ mg.customMeshAttributes = new List();
+ customMeshAttributes.ForEach(
+ ( m )=>
+ {
+ mg.customMeshAttributes.Add( m.Clone() );
+ }
+ );
+ }
+
return mg;
}
@@ -536,15 +808,41 @@ namespace Rokojori
}
+ public void AddPoint( Vector3 position, Color color, Vector3 normal, int normalIndex )
+ {
+ vertices.Add( position );
+ colors.Add( color );
+ ( (MeshAttributeVector4List) customMeshAttributes[ normalIndex ] ).values.Add( new Vector4( normal.X, normal.Y, normal.Z, 0.0f ) );
+
+ indices.Add( indices.Count );
+ }
+
+ public void AddPoint( Vector3 position, Color color, Vector3 normal, int normalIndex, Vector2 uv, int uvIndex )
+ {
+ vertices.Add( position );
+ colors.Add( color );
+
+ ( (MeshAttributeVector4List) customMeshAttributes[ normalIndex ] ).values.Add( new Vector4( normal.X, normal.Y, normal.Z, 0.0f ) );
+ ( (MeshAttributeVector4List) customMeshAttributes[ uvIndex ] ).values.Add( new Vector4( uv.X, uv.Y, 0.0f, 0.0f ) );
+
+ indices.Add( indices.Count );
+ }
- public void Add( MeshGeometry sourceGeometry, Transform3D? optionalTransform = null )
+ public void Add( MeshGeometry sourceGeometry, Transform3D? optionalTransform = null, List indicesTarget = null )
{
var mappedIndices = new Dictionary();
var rotation = optionalTransform == null ? Quaternion.Identity : ( (Transform3D)optionalTransform ).Basis.GetRotationQuaternion();
var transform = optionalTransform == null ? Transform3D.Identity : (Transform3D)optionalTransform;
+ var outputIndices = indices;
+
+ if ( indicesTarget != null )
+ {
+ outputIndices = indicesTarget;
+ }
+
for ( int i = 0; i < sourceGeometry.indices.Count; i++ )
{
var sourceIndex = sourceGeometry.indices[ i ];
@@ -569,10 +867,13 @@ namespace Rokojori
vertices.Add( transform * v );
}
-
-
- if ( normals != null && sourceGeometry.normals != null)
+ if ( normals != null && sourceGeometry.numNormals > 0 )
{
+ if ( sourceIndex < 0 || sourceIndex >= sourceGeometry.normals.Count )
+ {
+ RJLog.Log( "Normals index bad:", sourceIndex, sourceGeometry.normals.Count );
+ }
+
if ( optionalTransform == null )
{
normals.Add( sourceGeometry.normals[ sourceIndex ] );
@@ -584,26 +885,36 @@ namespace Rokojori
}
- if ( uvs != null && sourceGeometry.uvs != null)
+ if ( uvs != null && sourceGeometry.numUVs > 0 )
{
uvs.Add( sourceGeometry.uvs[ sourceIndex ] );
}
- if ( colors != null && sourceGeometry.colors != null)
- {
- colors.Add( sourceGeometry.colors[ sourceIndex ] );
- }
-
- if ( uv2s != null && sourceGeometry.uv2s != null)
+ if ( uv2s != null && sourceGeometry.numUV2s > 0 )
{
uv2s.Add( sourceGeometry.uv2s[ sourceIndex ] );
}
+ if ( colors != null && sourceGeometry.numColors > 0 )
+ {
+ colors.Add( sourceGeometry.colors[ sourceIndex ] );
+ }
+
+
+ if ( Lists.Size( customMeshAttributes ) == Lists.Size( sourceGeometry.customMeshAttributes ) )
+ {
+ for ( int j = 0; j < customMeshAttributes.Count; j++ )
+ {
+ var customList = customMeshAttributes[ j ];
+ var sourceCustomList = sourceGeometry.customMeshAttributes[ j ];
+ sourceCustomList.AddTo( customList, sourceIndex );
+ }
+ }
mappedIndices[ sourceIndex ] = newIndex;
}
- indices.Add( mappedIndices[ sourceIndex ] );
+ outputIndices.Add( mappedIndices[ sourceIndex ] );
}
}
@@ -1043,19 +1354,49 @@ namespace Rokojori
}
-
-
- public ArrayMesh GenerateMesh( Mesh.PrimitiveType type = Mesh.PrimitiveType.Triangles, ArrayMesh arrayMesh = null, bool generateTangents = true )
+
+ public static ArrayMesh GenerateLODMesh( List
+ mgs, Mesh.PrimitiveType type = Mesh.PrimitiveType.Triangles, ArrayMesh arrayMesh = null,
+ bool generateTangents = true, LODLerpingData lerpingData = null )
+ {
+ return mgs[ 0 ].GenerateMesh( type, arrayMesh, generateTangents, mgs.Sub( 1 ), lerpingData );
+ }
+
+ public static ArrayMesh GeneratePointsLODMesh( List mgs, LODLerpingData lerpingData = null, bool generateTangents = true )
+ {
+ return GenerateLODMesh( mgs, Mesh.PrimitiveType.Points, null, generateTangents, lerpingData );
+ }
+
+ public static ArrayMesh GenerateLinesLODMesh( List mgs, LODLerpingData lerpingData = null, bool generateTangents = true )
+ {
+ return GenerateLODMesh( mgs, Mesh.PrimitiveType.Lines, null, generateTangents, lerpingData );
+ }
+
+ public static ArrayMesh GenerateTrianglesLODMesh( List mgs, LODLerpingData lerpingData = null, bool generateTangents = true )
+ {
+ return GenerateLODMesh( mgs, Mesh.PrimitiveType.Triangles, null, generateTangents, lerpingData );
+ }
+
+ public ArrayMesh GenerateMesh(
+ Mesh.PrimitiveType type = Mesh.PrimitiveType.Triangles, ArrayMesh arrayMesh = null,
+ bool generateTangents = true, List lods = null, LODLerpingData lerpingData = null )
{
if ( arrayMesh == null )
{
arrayMesh = new ArrayMesh();
}
+ var lodDictionary = new Godot.Collections.Dictionary();
+
+
+ _GenerateLODs( type, lods, lodDictionary, lerpingData );
+
var surfaceArray = new Godot.Collections.Array();
surfaceArray.Resize( (int) Mesh.ArrayType.Max );
+ var flags = Mesh.ArrayFormat.FormatVertex | Mesh.ArrayFormat.FormatIndex;
+
if ( vertices != null && vertices.Count != 0 )
{
surfaceArray[ (int) Mesh.ArrayType.Vertex ] = vertices.ToArray();
@@ -1064,29 +1405,49 @@ namespace Rokojori
if ( normals != null && normals.Count != 0 )
{
surfaceArray[ (int) Mesh.ArrayType.Normal ] = normals.ToArray();
+ flags |= Mesh.ArrayFormat.FormatNormal;
}
if ( uvs != null && uvs.Count != 0 )
{
surfaceArray[ (int) Mesh.ArrayType.TexUV ] = uvs.ToArray();
+ flags |= Mesh.ArrayFormat.FormatTexUV;
}
if ( uv2s != null && uv2s.Count != 0 )
{
surfaceArray[ (int) Mesh.ArrayType.TexUV2 ] = uv2s.ToArray();
+ flags |= Mesh.ArrayFormat.FormatTexUV2;
}
if ( colors != null && colors.Count != 0 )
{
surfaceArray[ (int) Mesh.ArrayType.Color ] = colors.ToArray();
+ flags |= Mesh.ArrayFormat.FormatColor;
}
if ( indices != null && indices.Count != 0 )
{
surfaceArray[ (int) Mesh.ArrayType.Index ] = indices.ToArray();
+ flags |= Mesh.ArrayFormat.FormatIndex;
+ }
+
+ customMeshAttributes.ForEach(
+ c =>
+ {
+ flags |= c.GetFormatFlag();
+ c.WriteData( surfaceArray );
+ }
+ );
+
+ if ( lodDictionary != null && lodDictionary.Count > 0 )
+ {
+ var keys = Lists.From( lodDictionary.Keys );
+ RJLog.Log( "LODS:", keys );
}
- arrayMesh.AddSurfaceFromArrays( Mesh.PrimitiveType.Triangles, surfaceArray );
+ RJLog.Log( "Flags:", flags );
+ arrayMesh.AddSurfaceFromArrays( type, surfaceArray, null, lodDictionary, flags );
if ( generateTangents )
{
@@ -1094,7 +1455,135 @@ namespace Rokojori
}
return arrayMesh;
+
}
+ void _GenerateLODs( Mesh.PrimitiveType type, List lods,
+ Godot.Collections.Dictionary lodDictionary, LODLerpingData lerpingData )
+ {
+ if ( lods == null )
+ {
+ return;
+ }
+
+ RJLog.Log( "Creating mesh with LODs", vertices.Count );
+
+ var higherIndices = indices;
+ var higherIndicesLength = indices.Count;
+ var higherEdgeLength = this.lodEdgeLength;
+
+ var primitiveLength = 3;
+
+ if ( Mesh.PrimitiveType.Lines == type )
+ {
+ primitiveLength = 2;
+ }
+ else if ( Mesh.PrimitiveType.Points == type )
+ {
+ primitiveLength = 1;
+ }
+
+
+ var lodIndex = -1;
+ lods.ForEach(
+ ( lod )=>
+ {
+ lodIndex ++;
+
+
+ var lodIndices = new List();
+ Add( lod, null, lodIndices );
+
+ if ( lerpingData != null )
+ {
+ var lowOffsetIndex = lodIndices.Count / primitiveLength;
+ var lowIndices = Lists.Create( lowOffsetIndex, i => i );
+ var highIndices = Lists.Create( higherIndicesLength / primitiveLength, i => i + lowOffsetIndex );
+
+ for ( int s = 0; s < lerpingData.lerpSteps; s ++ )
+ {
+ var i = ( lerpingData.lerpSteps - 1 ) - s;
+ var lerpedGenericOutput = new List();
+
+ var lerpedEdgeLength = lerpingData.Sample( lowIndices.View(), highIndices.View(),
+ lerpedGenericOutput, i, lod.lodEdgeLength, higherEdgeLength );
+
+ var lerpedPrimitiveIndices = new List();
+
+ var evaluatedJ = 0;
+ var evaluatedK = 0;
+ var isLow = false;
+ var offset = 0;
+
+ try
+ {
+ for ( int j = 0; j < lerpedGenericOutput.Count; j++ )
+ {
+ evaluatedJ = j;
+ var primitiveIndex = lerpedGenericOutput[ j ];
+ isLow = primitiveIndex < lowOffsetIndex;
+
+ if ( primitiveIndex < lowOffsetIndex )
+ {
+ offset = primitiveIndex * primitiveLength;
+
+ for ( int k = 0; k < primitiveLength; k ++ )
+ {
+ evaluatedK = k;
+ lerpedPrimitiveIndices.Add( lodIndices[ offset + k ] );
+ }
+ }
+ else
+ {
+ offset = ( primitiveIndex - lowOffsetIndex ) * primitiveLength;
+
+ for ( int k = 0; k < primitiveLength; k ++ )
+ {
+ evaluatedK = k;
+ lerpedPrimitiveIndices.Add( higherIndices[ offset + k ] );
+ }
+ }
+
+ }
+ }
+ catch ( System.Exception e )
+ {
+ RJLog.Log(
+ "LodIndex", lodIndex,
+ "Lerped Output Size:", lerpedGenericOutput.Count,
+ "j", evaluatedJ,
+ "k", evaluatedK,
+ "isLow", isLow,
+ "offset", offset,
+ "indices", indices.Count,
+ "indices", higherIndices.Count,
+ "primitiveLength", primitiveLength,
+ "lowOffsetIndex", lowOffsetIndex,
+ "Low Size", lodIndices.Count,
+ "High Size", higherIndicesLength
+ );
+ RJLog.Error( e );
+ }
+
+ RJLog.Log( "Adding lod", lods.IndexOf( lod ), i, "size:", lerpedEdgeLength, "verts:", lerpedPrimitiveIndices.Count );
+ lodDictionary[ lerpedEdgeLength ] = lerpedPrimitiveIndices.ToArray();
+
+ }
+ }
+
+ higherIndices = lodIndices;
+ higherIndicesLength = lodIndices.Count;
+ higherEdgeLength = lod.lodEdgeLength;
+
+
+ RJLog.Log( "Adding lod", lods.IndexOf( lod ), "full", "size:", lod.lodEdgeLength, "verts:", lodIndices.Count );
+ lodDictionary[ lod.lodEdgeLength ] = lodIndices.ToArray();
+ }
+ );
+
+ RJLog.Log( "Added vertices", vertices.Count );
+ }
+
+
}
}
\ No newline at end of file
diff --git a/Runtime/Procedural/Scatter/Generators/GenerateInBox.cs b/Runtime/Procedural/Scatter/Generators/GenerateInBox.cs
index 6fe0536..5e6b0e9 100644
--- a/Runtime/Procedural/Scatter/Generators/GenerateInBox.cs
+++ b/Runtime/Procedural/Scatter/Generators/GenerateInBox.cs
@@ -34,8 +34,8 @@ namespace Rokojori
if ( snapToWorldGrid )
{
var snapping = Vector3.One / density;
- minPosition = Math3D.SnapCeiled( minPosition, snapping );
- maxPosition = Math3D.SnapFloored( maxPosition, snapping );
+ minPosition = minPosition.SnapCeiled( snapping );
+ maxPosition = maxPosition.SnapFloored( snapping );
}
var pointsX = Mathf.CeilToInt( ( maxPosition.X - minPosition.X ) * density );
diff --git a/Runtime/Procedural/Scatter/Generators/GeneratorEntry.cs b/Runtime/Procedural/Scatter/Generators/GeneratorEntry.cs
index 072e74a..6027d2d 100644
--- a/Runtime/Procedural/Scatter/Generators/GeneratorEntry.cs
+++ b/Runtime/Procedural/Scatter/Generators/GeneratorEntry.cs
@@ -26,6 +26,9 @@ namespace Rokojori
[Export]
public bool useInstancing = false;
+ [Export]
+ public float instancingSplitSize = 25;
+
protected PackedScene _cachedNode3DScene;
public PackedScene GetPackedScene()
diff --git a/Runtime/Procedural/Scatter/Generators/GeneratorScatterer.cs b/Runtime/Procedural/Scatter/Generators/GeneratorScatterer.cs
index b3a77a2..43bae04 100644
--- a/Runtime/Procedural/Scatter/Generators/GeneratorScatterer.cs
+++ b/Runtime/Procedural/Scatter/Generators/GeneratorScatterer.cs
@@ -63,6 +63,7 @@ namespace Rokojori
p.scene = gs.GetPackedScene() != null ? gs.GetPackedScene() : packedScene;
p.parent = gs.container != null ? gs.container : container;
p.instanced = gs.useInstancing;
+ p.instanceSplitSize = gs.instancingSplitSize;
}
}
diff --git a/Runtime/Procedural/Scatter/ScatterPoint.cs b/Runtime/Procedural/Scatter/ScatterPoint.cs
index 2ccbd25..796a9da 100644
--- a/Runtime/Procedural/Scatter/ScatterPoint.cs
+++ b/Runtime/Procedural/Scatter/ScatterPoint.cs
@@ -10,11 +10,13 @@ namespace Rokojori
public class ScatterPoint:PointData
{
public bool instanced = false;
+ public float instanceSplitSize = 25;
protected Scatterer _creator;
public Scatterer creator => _creator;
protected int _creatorID;
public int creatorID => _creatorID;
+
public ScatterPoint( Scatterer creator, int id )
{
this._creator = creator;
diff --git a/Runtime/Procedural/Scatter/Scatterer.cs b/Runtime/Procedural/Scatter/Scatterer.cs
index 6e5f794..b43a80e 100644
--- a/Runtime/Procedural/Scatter/Scatterer.cs
+++ b/Runtime/Procedural/Scatter/Scatterer.cs
@@ -343,6 +343,7 @@ namespace Rokojori
c.materialOveride = mwm.material;
c.mesh = mwm.mesh;
+ c.multiMeshSplitSize = sp.instanceSplitSize;
_instancedMassRenderers.Add( mwm.mesh, c );
diff --git a/Runtime/Reallusion/CCImportFile/CCCustomShader.cs b/Runtime/Reallusion/CCImportFile/CCCustomShader.cs
new file mode 100644
index 0000000..9e8b7b5
--- /dev/null
+++ b/Runtime/Reallusion/CCImportFile/CCCustomShader.cs
@@ -0,0 +1,196 @@
+
+using System.Diagnostics;
+using System.Collections;
+using System.Collections.Generic;
+using System;
+using Godot;
+
+
+namespace Rokojori.Reallusion
+{
+ public class CCCustomShader:CCImportFileBase
+ {
+ public string name;
+
+
+ public CCCustomShader( CCImportFile file, string name ):base( file )
+ {
+ this.name = name;
+ }
+
+ JSONObject _jsonObject;
+
+ public void ReadFrom( JSONObject jsonObject )
+ {
+ _jsonObject = jsonObject;
+
+ if ( name == "RLEyeOcclusion" )
+ {
+ ReadEyeOcclusion( jsonObject );
+ }
+
+ if ( name == "RLEye" )
+ {
+ ReadEye( jsonObject );
+ }
+ }
+
+ public CCJSONProperty> shadowTopColor = new CCJSONProperty>( "Shadow Color");
+ public CCJSONProperty> shadowBottomColor = new CCJSONProperty>( "Shadow2 Color");
+
+ void ReadEyeOcclusion( JSONObject jsonObject )
+ {
+ var variable = jsonObject.GetObject( "Variable" );
+
+ shadowTopColor.Read( variable );
+ shadowBottomColor.Read( variable );
+ }
+
+ public float GetFloatVariable( string name, float alternative = 0 )
+ {
+ var value = _jsonObject.ByPath( "Variable", name );
+
+ if ( value == null )
+ {
+ return alternative;
+ }
+
+ return value.floatValue;
+ }
+
+ public List GetFloatArrayVariable( string name )
+ {
+ var value = _jsonObject.ByPath( "Variable", name );
+
+ return value.AsFloatList();
+ }
+
+ public Vector3 GetVector3Variable( string name )
+ {
+ var value = GetFloatArrayVariable( name );
+
+ var v = Vector3.Zero;
+ v.X = value[ 0 ];
+ v.Y = value[ 1 ];
+ v.Z = value[ 2 ];
+
+ return v;
+ }
+
+ public Color GetColorVariable( string name, float basis, bool sRGB )
+ {
+ var value = GetFloatArrayVariable( name );
+
+ var color = ColorX.From( value, basis );
+
+ if ( sRGB )
+ {
+ color = color.linearToSRGB();
+ }
+
+ return color;
+ }
+
+ public Vector3 GetColorVariableAsVector3( string name, float basis, bool sRGB )
+ {
+ var color = GetColorVariable( name, basis, sRGB );
+ return new Vector3( color.R, color.G, color.B );
+ }
+
+ public string GetImageTexturePath( string name )
+ {
+ var value = _jsonObject.ByPath( "Image", name, "Texture Path" );
+
+ if ( value == null )
+ {
+ return null;
+ }
+
+ return value.stringValue;
+
+ }
+
+ public Texture2D GetImageTexture( string name )
+ {
+ var path = GetImageTexturePath( name );
+ return GetTextureFromRelativePath( path );
+ }
+
+ Texture2D GetTextureFromRelativePath( string relativePath )
+ {
+ var rootDirectoryPath = FilePath.Absolute( importFile.directory );
+ var relativeFilePath = rootDirectoryPath.MakeRelative( relativePath );
+
+ try
+ {
+ var texture = ResourceLoader.Load( relativeFilePath.fullPath );
+
+ return texture;
+ }
+ catch( System.Exception e )
+ {
+ RJLog.Error( "Could not load texture", relativePath, relativeFilePath.fullPath );
+ RJLog.Error( e );
+ }
+
+ return null;
+ }
+
+
+ void ReadEye( JSONObject jsonObject )
+ {
+
+ }
+
+ /* EYE */
+
+ public Texture2D GetIrisNormal()
+ {
+ return GetImageTexture( "Iris Normal" );
+ }
+
+ /* SKIN */
+
+ public Texture2D GetMicroNormal()
+ {
+ return GetImageTexture( "MicroNormal" );
+ }
+
+ public Texture2D GetMicroNormalMask()
+ {
+ return GetImageTexture( "MicroNormalMask" );
+ }
+
+ public Texture2D GetSpecularMask()
+ {
+ return GetImageTexture( "Specular Mask" );
+ }
+
+ public Texture2D GetSSSMap()
+ {
+ return GetImageTexture( "SSS Map" );
+ }
+
+ public Texture2D GetTransmisionMap()
+ {
+ return GetImageTexture( "Transmission Map" );
+ }
+
+ /* HAIR */
+
+ public Texture2D GetHairRootMap()
+ {
+ return GetImageTexture( "Hair Root Map" );
+ }
+
+ public Texture2D GetHairFlowMap()
+ {
+ return GetImageTexture( "Hair Flow Map" );
+ }
+
+ public Texture2D GetHairIDMap()
+ {
+ return GetImageTexture( "Hair ID Map" );
+ }
+ }
+}
\ No newline at end of file
diff --git a/Runtime/Reallusion/CCImportFile/CCCustomShader.cs.uid b/Runtime/Reallusion/CCImportFile/CCCustomShader.cs.uid
new file mode 100644
index 0000000..4157f30
--- /dev/null
+++ b/Runtime/Reallusion/CCImportFile/CCCustomShader.cs.uid
@@ -0,0 +1 @@
+uid://b8uk66gggbs48
diff --git a/Runtime/Reallusion/CCImportFile/CCImportFile.cs b/Runtime/Reallusion/CCImportFile/CCImportFile.cs
new file mode 100644
index 0000000..70f879e
--- /dev/null
+++ b/Runtime/Reallusion/CCImportFile/CCImportFile.cs
@@ -0,0 +1,93 @@
+
+using System.Diagnostics;
+using System.Collections;
+using System.Collections.Generic;
+using System;
+using Godot;
+
+
+namespace Rokojori.Reallusion
+{
+ public class CCImportFile:CCImportFileBase
+ {
+ public string name;
+
+ public CCObjectInfo ccObject;
+ public List messages = new List();
+ public string path;
+
+ public string directory => FilePath.Absolute( path ).CreateAbsoluteParent().fullPath;
+
+ public CCImportFile():base( null )
+ {
+ _importFile = this;
+ }
+
+ public void _Error( params object[] items )
+ {
+ Messages.Error( messages, RJLog.GetLogString( items ) );
+ }
+
+ public void _Info( params object[] items )
+ {
+ Messages.Info( messages, RJLog.GetLogString( items ) );
+ }
+
+ public void ReadFrom( JSONData data )
+ {
+ if ( data == null )
+ {
+ Error( "Root is null" );
+ return;
+ }
+
+ if ( data.dataType != JSONDataType.OBJECT )
+ {
+ Error( "Invalid root, not an object, actual type: ", data.dataType );
+ return;
+ }
+
+
+ var root = data.AsObject();
+
+ if ( root.keys.Count == 0 )
+ {
+ Error( "Root has no children" );
+ return;
+ }
+
+ if ( root.keys.Count > 1 )
+ {
+ Error( "Root has more than one child" );
+ return;
+ }
+
+ var rootKey = root.keys[ 0 ];
+
+ name = rootKey;
+
+ Info( "Found root object:", name );
+
+ var rootObject = root.GetObject( rootKey );
+
+ var objectKey = "Object";
+
+ if ( ! ValidateMember( rootObject, objectKey, "Checking cc object" ) )
+ {
+ return;
+ }
+
+ var objectData = rootObject.GetObject( objectKey );
+
+ ccObject = new CCObjectInfo( this );
+
+
+ ccObject.ReadFrom( objectData );
+
+
+ }
+
+
+
+ }
+}
\ No newline at end of file
diff --git a/Runtime/Reallusion/CCImportFile/CCImportFile.cs.uid b/Runtime/Reallusion/CCImportFile/CCImportFile.cs.uid
new file mode 100644
index 0000000..97807d9
--- /dev/null
+++ b/Runtime/Reallusion/CCImportFile/CCImportFile.cs.uid
@@ -0,0 +1 @@
+uid://bu7vm4bmr6ji1
diff --git a/Runtime/Reallusion/CCImportFile/CCImportFileBase.cs b/Runtime/Reallusion/CCImportFile/CCImportFileBase.cs
new file mode 100644
index 0000000..e935af0
--- /dev/null
+++ b/Runtime/Reallusion/CCImportFile/CCImportFileBase.cs
@@ -0,0 +1,67 @@
+
+
+
+namespace Rokojori.Reallusion
+{
+ public class CCImportFileBase
+ {
+ protected CCImportFile _importFile;
+ public CCImportFile importFile => _importFile;
+
+
+ public CCImportFileBase( CCImportFile importFile )
+ {
+ _importFile = importFile;
+ }
+
+ public virtual void Error( params object[] items )
+ {
+ importFile._Error( items );
+ }
+
+ public virtual void Info( params object[] items )
+ {
+ importFile._Info( items );
+ }
+
+
+ public bool ValidateMember( JSONData parentNode, string name, string contextInfo, JSONDataType jsonType = JSONDataType.OBJECT )
+ {
+ if ( parentNode == null )
+ {
+ Error( contextInfo, "parent node is null" );
+ return false;
+ }
+
+ if ( ! parentNode.isObject )
+ {
+ Error( contextInfo, "parent node is not an object" );
+ return false;
+ }
+
+ if ( ! parentNode.isObject )
+ {
+ Error( contextInfo, "parent node is not an object" );
+ return false;
+ }
+
+ var parentObject = parentNode.AsObject();
+
+ if ( ! parentObject.HasKey( name ) )
+ {
+ Error( contextInfo, "parent node doesn't have the key:", name );
+ return false;
+ }
+
+ var childData = parentObject.Get( name );
+
+ if ( childData.dataType != jsonType )
+ {
+ Error( contextInfo, "node doesn't have the expected type " + jsonType, "it has:", childData.dataType );
+ return false;
+ }
+
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Runtime/Reallusion/CCImportFile/CCImportFileBase.cs.uid b/Runtime/Reallusion/CCImportFile/CCImportFileBase.cs.uid
new file mode 100644
index 0000000..0a9255c
--- /dev/null
+++ b/Runtime/Reallusion/CCImportFile/CCImportFileBase.cs.uid
@@ -0,0 +1 @@
+uid://c17odjuuq0md
diff --git a/Runtime/Reallusion/CCImportFile/CCJSONProperty.cs b/Runtime/Reallusion/CCImportFile/CCJSONProperty.cs
new file mode 100644
index 0000000..967ccda
--- /dev/null
+++ b/Runtime/Reallusion/CCImportFile/CCJSONProperty.cs
@@ -0,0 +1,92 @@
+
+using System.Diagnostics;
+using System.Collections;
+using System.Collections.Generic;
+using System;
+using Godot;
+
+namespace Rokojori.Reallusion
+{
+ public class CCJSONProperty
+ {
+ protected string _name;
+ public string name => _name;
+
+ public T value;
+ public bool exists = false;
+
+ protected Func _customReader;
+
+ public CCJSONProperty( string name )
+ {
+ _name = name;
+ }
+
+ public void SetReader(Func reader = null )
+ {
+ _name = name;
+ _customReader = reader;
+ }
+
+
+ public T GetOr( T alternative )
+ {
+ if ( ! exists )
+ {
+ return alternative;
+ }
+
+ return value;
+ }
+
+
+
+
+
+
+ public void Read( JSONObject root )
+ {
+ if ( ! root.HasKey( _name ) )
+ {
+ exists = false;
+ return;
+ }
+
+ var data = root.Get( _name );
+
+ if ( _customReader != null )
+ {
+ Set( _customReader( data ) );
+ }
+ else if ( Is( typeof( int ) ) )
+ {
+ Set( data.intValue );
+ }
+ else if ( Is( typeof( float ) ) )
+ {
+ Set( data.floatValue );
+ }
+ else if ( Is( typeof( string ) ) )
+ {
+ Set( data.stringValue );
+ }
+ else if ( Is( typeof( List ) ) )
+ {
+ Set( data.AsArray().AsFloatList() );
+ }
+
+ exists = true;
+ }
+
+ void Set( object value )
+ {
+ this.value = (T) value;
+ }
+
+ bool Is( Type type )
+ {
+ return typeof( T ) == type;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Runtime/Reallusion/CCImportFile/CCJSONProperty.cs.uid b/Runtime/Reallusion/CCImportFile/CCJSONProperty.cs.uid
new file mode 100644
index 0000000..315d502
--- /dev/null
+++ b/Runtime/Reallusion/CCImportFile/CCJSONProperty.cs.uid
@@ -0,0 +1 @@
+uid://cwsu3thtluxci
diff --git a/Runtime/Reallusion/CCImportFile/CCMaterialGenerator/CCMaterialGenerator.cs b/Runtime/Reallusion/CCImportFile/CCMaterialGenerator/CCMaterialGenerator.cs
new file mode 100644
index 0000000..c9ed89b
--- /dev/null
+++ b/Runtime/Reallusion/CCImportFile/CCMaterialGenerator/CCMaterialGenerator.cs
@@ -0,0 +1,20 @@
+
+using System.Diagnostics;
+using System.Collections;
+using System.Collections.Generic;
+using System;
+using Godot;
+
+
+namespace Rokojori.Reallusion
+{
+ [Tool]
+ [GlobalClass]
+ public partial class CCMaterialGenerator:Resource
+ {
+ public virtual Material CreateMaterial( CCMaterialInfo materialInfo, CCMaterialSettings settings )
+ {
+ return materialInfo.CreateMaterial( settings );
+ }
+ }
+}
\ No newline at end of file
diff --git a/Runtime/Reallusion/CCImportFile/CCMaterialGenerator/CCMaterialGenerator.cs.uid b/Runtime/Reallusion/CCImportFile/CCMaterialGenerator/CCMaterialGenerator.cs.uid
new file mode 100644
index 0000000..8b0644f
--- /dev/null
+++ b/Runtime/Reallusion/CCImportFile/CCMaterialGenerator/CCMaterialGenerator.cs.uid
@@ -0,0 +1 @@
+uid://dri0mpp4tlwbt
diff --git a/Runtime/Reallusion/CCImportFile/CCMaterialInfo.cs b/Runtime/Reallusion/CCImportFile/CCMaterialInfo.cs
new file mode 100644
index 0000000..20b7cd4
--- /dev/null
+++ b/Runtime/Reallusion/CCImportFile/CCMaterialInfo.cs
@@ -0,0 +1,552 @@
+
+using System.Diagnostics;
+using System.Collections;
+using System.Collections.Generic;
+using System;
+using Godot;
+
+
+namespace Rokojori.Reallusion
+{
+ public class CCMaterialInfo:CCImportFileBase
+ {
+
+ public string name;
+
+
+ public CCJSONProperty materialType = new CCJSONProperty( "Material Type" );
+ public CCJSONProperty multiUVIndex = new CCJSONProperty( "MultiUV Index" );
+ public CCJSONProperty nodeType = new CCJSONProperty( "Node Type" );
+ public CCJSONProperty twoSide = new CCJSONProperty( "Two Side" );
+ public CCJSONProperty> diffuseColor = new CCJSONProperty>( "Diffuse Color" );
+ public CCJSONProperty> ambientColor = new CCJSONProperty>( "Ambient Color" );
+ public CCJSONProperty> specularColor = new CCJSONProperty>( "Specular Color" );
+ public CCJSONProperty opacity = new CCJSONProperty( "Opacity" );
+ public CCJSONProperty selfIllumination = new CCJSONProperty( "Self Illumination" );
+ public CCJSONProperty specular = new CCJSONProperty( "Specular" );
+ public CCJSONProperty glossiness = new CCJSONProperty( "Glossiness" );
+
+ public CCJSONProperty> textures = new CCJSONProperty>( "Textures" );
+
+ public CCJSONProperty customShader = new CCJSONProperty( "Custom Shader" );
+
+ public CCJSONProperty subsurfaceScatter = new CCJSONProperty( "Subsurface Scatter" );
+
+ public CCMaterialInfo( CCImportFile ccImportFile, string name ):base( ccImportFile )
+ {
+ this.name = name;
+
+ textures.SetReader(
+ ( data ) =>
+ {
+ var list = new List();
+ var obj = data.AsObject();
+
+ Info( "Reading textures:", obj.keys );
+
+ obj.keys.ForEach(
+ k =>
+ {
+ var ti = CCTextureInfo.Create( importFile, k, obj.Get( k ) );
+ Info( "Adding texture for material", name, ti.name, ti.texturePath.value );
+ list.Add( ti );
+ }
+ );
+
+
+ return list;
+ }
+ );
+
+ customShader.SetReader(
+ ( data ) =>
+ {
+ var shaderName = data.AsObject().Get( "Shader Name" ).stringValue;
+ var cs = new CCCustomShader( importFile, shaderName );
+
+ Info( "Found Shader", shaderName );
+
+ cs.ReadFrom( data.AsObject() );
+
+ return cs;
+
+ }
+ );
+
+ subsurfaceScatter.SetReader(
+ ( data ) =>
+ {
+ var ss = new CCSubsurfaceScatter( importFile );
+
+ ss.ReadFrom( data.AsObject() );
+
+ return ss;
+
+ }
+ );
+ }
+
+ public void ReadFrom( JSONObject root )
+ {
+ materialType.Read( root );
+ multiUVIndex.Read( root );
+ nodeType.Read( root );
+ twoSide.Read( root );
+ diffuseColor.Read( root );
+ ambientColor.Read( root );
+ specularColor.Read( root );
+ opacity.Read( root );
+ selfIllumination.Read( root );
+ textures.Read( root );
+ customShader.Read( root );
+ subsurfaceScatter.Read( root );
+ }
+
+ public bool IsCustomShader( string shaderName )
+ {
+ return customShader.exists && customShader.value.name == shaderName;
+ }
+
+ public Material CreateMaterial( CCMaterialSettings settings )
+ {
+ RJLog.Log( "Custom Shader", customShader.exists ? customShader.value.name : "none" );
+
+ if ( settings.materialType.shaderType == CCMaterialType.CCShaderType.RLHair )
+ {
+ return CreateHairMaterial( settings );
+ }
+
+ if ( settings.materialType.shaderType == CCMaterialType.CCShaderType.RLEyeOcclusion )
+ {
+ return CreateEyeOcclusionMaterial( settings );
+ }
+
+ if ( settings.materialType.shaderType == CCMaterialType.CCShaderType.RLEye )
+ {
+ return CreateEyeMaterial( settings );
+ }
+
+
+ if (
+ settings.materialType.shaderType == CCMaterialType.CCShaderType.RLSkin ||
+ settings.materialType.shaderType == CCMaterialType.CCShaderType.RLHead
+ )
+ {
+
+ return CreateSkinMaterial( settings );
+ }
+
+ if ( HasOpacityTexture() )
+ {
+ return CreateOpacityMaterial( settings );
+ }
+
+
+ return CreatePBRMaterial( settings );
+ }
+
+
+ public Color GetAlbedoColor( float gamma )
+ {
+ var a = opacity.GetOr( 1.0f );
+ var color = ColorX.From( diffuseColor.GetOr( new List{ 255, 255, 255, 255 } ), 255 );
+ color.A *= a;
+
+ color = color.Gamma( gamma );
+
+ return color;
+ }
+
+ public bool HasOpacityTexture()
+ {
+ if ( textures.exists )
+ {
+ var opacityTexture = textures.value.Find( t => t.name == CCTextureInfo.Opacity );
+ return opacityTexture != null;
+ }
+
+ return false;
+ }
+
+ public bool IsTransparent()
+ {
+ return HasOpacityTexture() || GetAlbedoColor( 1.0f ).A < 1.0f;
+ }
+
+
+ public CCMaterialType GetMaterialType()
+ {
+ var materialType = new CCMaterialType();
+
+ materialType.shaderType = CCMaterialType.CCShaderType.PBR;
+
+ if ( customShader.exists )
+ {
+ materialType.shaderType = ReflectionHelper.StringToEnum( customShader.value.name, CCMaterialType.CCShaderType.Unknown );
+ }
+
+ return materialType;
+ }
+
+
+ public StandardMaterial3D CreatePBRMaterial( CCMaterialSettings settings, bool skin = false )
+ {
+ var material = new StandardMaterial3D();
+
+ material.CullMode = twoSide.GetOr( false ) ? BaseMaterial3D.CullModeEnum.Disabled : BaseMaterial3D.CullModeEnum.Back;
+
+ material.Metallic = 1.0f;
+ material.AlbedoColor = GetAlbedoColor( settings.configuration.gammaForAlbedoColor );
+ material.Transparency = IsTransparent() ? BaseMaterial3D.TransparencyEnum.Alpha : BaseMaterial3D.TransparencyEnum.Disabled;
+ material.MetallicSpecular = skin ? 0.0f : 0.5f;
+
+
+ if ( skin && subsurfaceScatter.exists)
+ {
+ material.SubsurfScatterEnabled = true;
+ material.SubsurfScatterStrength = subsurfaceScatter.value.radius.value / 3.0f;
+ material.SubsurfScatterSkinMode = true;
+ }
+
+ if ( ! textures.exists )
+ {
+ return material;
+ }
+
+ textures.value.ForEach(
+ ( t ) =>
+ {
+ if ( t.isBaseColor )
+ {
+ material.AlbedoTexture = t.GetTexture();
+ var uv1Scale = t.tiling.GetOr( new List(){ 1, 1 } );
+ var uv1Offset = t.offset.GetOr( new List(){ 0, 0 } );
+ material.Uv1Scale = new Vector3( uv1Scale[ 0 ], uv1Scale[ 1 ], 1 );
+ material.Uv1Offset = new Vector3( uv1Offset[ 0 ], uv1Offset[ 1 ], 1 );
+ }
+ else if ( t.isNormal )
+ {
+ material.NormalTexture = t.GetTexture();
+ material.NormalEnabled = true;
+ }
+ else if ( t.isRoughness )
+ {
+ material.RoughnessTexture = t.GetTexture();
+ material.RoughnessTextureChannel = BaseMaterial3D.TextureChannel.Red;
+ }
+ else if ( t.isMetallic )
+ {
+ material.MetallicTexture = t.GetTexture();
+ material.MetallicTextureChannel = BaseMaterial3D.TextureChannel.Red;
+ }
+ else if ( t.isGlow )
+ {
+ material.EmissionEnabled = true;
+ material.EmissionTexture = t.GetTexture();
+ }
+ else if ( t.isAO )
+ {
+ material.AOEnabled = true;
+ material.AOTexture = t.GetTexture();
+ material.AOTextureChannel = BaseMaterial3D.TextureChannel.Red;
+ }
+ }
+ );
+
+ return material;
+ }
+
+ public ShaderMaterial CreateEyeOcclusionMaterial( CCMaterialSettings settings )
+ {
+ var eyeOcclusionMaterial = new CCEyeOcclusionMaterial();
+
+ eyeOcclusionMaterial.shadowTopColor.Set(
+ ColorX.From( customShader.value.shadowTopColor.value, 255.0f )
+ );
+
+ eyeOcclusionMaterial.shadowBottomColor.Set(
+ ColorX.From( customShader.value.shadowBottomColor.value, 255.0f )
+ );
+
+ return eyeOcclusionMaterial;
+ }
+
+ public ShaderMaterial CreateEyeMaterial( CCMaterialSettings settings )
+ {
+ var eyeMaterial = new CCEyeMaterial();
+
+ textures.value.ForEach(
+ ( t ) =>
+ {
+ if ( t.isBaseColor )
+ {
+ eyeMaterial.textureAlbedo.Set( t.GetTexture() );
+
+ }
+ }
+ );
+
+ eyeMaterial.textureNormal.Set( customShader.value.GetIrisNormal() );
+
+ return eyeMaterial;
+ }
+
+ public ShaderMaterial CreateSkinMaterial( CCMaterialSettings settings )
+ {
+ var skinMaterial = new CCSkinMaterial();
+ CustomMaterial outputMaterial = skinMaterial;
+
+ if ( settings.configuration.transmissiveSkin )
+ {
+ outputMaterial = new CCSkinTransmissiveMaterial();
+ }
+
+ if ( settings.configuration.applyAlbedoNoise )
+ {
+ var skinScale = 2.0f;
+
+ if ( name.EndsWith( "Head" ) )
+ {
+ skinScale = 1.0f;
+ }
+ else if ( name.EndsWith( "Body" ) )
+ {
+ skinScale = 5.0f;
+ }
+
+
+ skinMaterial.albedoNoiseUvScale.AssignFor( outputMaterial, skinScale );
+ skinMaterial.albedoNoiseOffset.AssignFor( outputMaterial, settings.configuration.albedoNoiseBrightness );
+ skinMaterial.albedoNoise.AssignFor( outputMaterial, settings.configuration.albedoNoiseAmount );
+ skinMaterial.textureAlbedoNoise.AssignFor( outputMaterial, settings.configuration.skinAlbedoNoise );
+ }
+
+ skinMaterial.albedo.AssignFor( outputMaterial, GetAlbedoColor( settings.configuration.gammaForAlbedoColor ) );
+ skinMaterial.specular.AssignFor( outputMaterial, 0.5f );
+
+
+ var tiling = customShader.value.GetFloatVariable( "MicroNormal Tiling", 20f );
+ skinMaterial.uv1Scale.AssignFor( outputMaterial, Vector3.One );
+ skinMaterial.uv2Scale.AssignFor( outputMaterial, Vector3.One * tiling );
+
+ skinMaterial.microNormalTexture.AssignFor( outputMaterial, customShader.value.GetMicroNormal() );
+ skinMaterial.microNormalMaskTexture.AssignFor( outputMaterial, customShader.value.GetMicroNormalMask() );
+ skinMaterial.microNormalScale.AssignFor( outputMaterial, customShader.value.GetFloatVariable( "MicroNormal Strength", 0.5f ) );
+
+ if ( outputMaterial is CCSkinTransmissiveMaterial st )
+ {
+ st.textureSubsurfaceTransmittance.Set( customShader.value.GetTransmisionMap() );
+ }
+ // skinMaterial.textureSubsurfaceTransmittance.Set( customShader.value.GetTransmisionMap() );
+ skinMaterial.textureSpecular.AssignFor( outputMaterial, customShader.value.GetSpecularMask() );
+ skinMaterial.textureSubsurfaceScattering.AssignFor( outputMaterial, customShader.value.GetSSSMap() );
+
+ textures.value.ForEach(
+ ( t ) =>
+ {
+ if ( t.isBaseColor )
+ {
+ skinMaterial.textureAlbedo.AssignFor( outputMaterial, t.GetTexture() );
+
+ var uv1Scale = t.tiling.GetOr( new List