using System; using Godot; using Rokojori.Extensions; namespace Rokojori { [Tool] [GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Tween.svg")] public partial class TweenMaterial : SequenceAction, Animator { public void OnAnimatorStart(){} public void OnAnimatorEnd(){} public void OnAnimatorCancel(){} // [Export] // public Material fromMaterial; [Export] public Material toMaterial; [Export] public TweenType tweenType = new TweenTimeCurve(); [Export] public Timeline timeLine; ColorProperty[] colors = []; FloatProperty[] floats = []; public enum MaterialAssignmentMode { No_Assignment, Assignment_Once, Unique_Once, Temporary_Duplicate_For_Each_Tween } // [Export] // public MaterialAssignmentMode assignmentMode = MaterialAssignmentMode.Temporary_Duplicate_For_Each_Tween; [ExportGroup( "Target")] [Export] public Node3D target; [Export] public MaterialSlot slot = MaterialSlot.None; [Export] public int index = 0; // [ExportToolButton( "Assign From-Material")] // public Callable AssignFromMaterialButton => Callable.From( // () => // { // MaterialSurfaceContainer.SetMaterialInSlot( target, index, slot, fromMaterial ); // } // ); [ExportToolButton( "Assign To-Material")] public Callable AssignToMaterialButton => Callable.From( () => { MaterialSurfaceContainer.SetMaterialInSlot( target, index, slot, toMaterial ); } ); [ExportToolButton( "Log Material Delta")] public Callable GetMaterialDeltaButton => Callable.From( () => { var from = MaterialSurfaceContainer.GetMaterialInSlot( target, index, slot ); // this.LogInfo( "Delta", MaterialDelta.Create( from, toMaterial ) ); } ); Material _assignedMaterial = null; protected override void _OnTrigger() { if ( toMaterial == null && this._assignedMaterial == null || target == null ) { return; } var fromMaterial = MaterialSurfaceContainer.From( target, this.index ).GetMaterialInSlot( slot ); // Registers ownership of the *slot assignment itself* (as opposed to the color/float // properties on the material), so a second TweenMaterial targeting the same slot takes // over cleanly instead of both tweens fighting to set the end material. AnimationManager.StartAnimation( this, target, GetSlotKey() ); SetAssignedMaterial( fromMaterial ); var tl = TimelineManager.Ensure( timeLine ); var start = tl.position; var delta = MaterialDelta.Create( fromMaterial, toMaterial ); // this.LogInfo( "Delta:", delta ); colors = delta.colorProperties.ToArray(); floats = delta.floatProperties.ToArray(); var startColors = new Color[ colors.Length ]; var startFloats = new float[ floats.Length ]; var elementIndex = 0; foreach ( var c in colors ) { AnimationManager.StartAnimation( this, _assignedMaterial, c.propertyName ); startColors[ elementIndex ] = c.propertyName.Get( fromMaterial ); elementIndex ++; } elementIndex = 0; foreach ( var f in floats ) { AnimationManager.StartAnimation( this, _assignedMaterial, f.propertyName ); startFloats[ elementIndex ] = f.propertyName.Get( fromMaterial ); elementIndex ++; } 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 elementIndex = 0; var state = tweenType.GetTweenPhaseForPhase( span.phase ); foreach ( var c in colors ) { if ( AnimationManager.IsAnimating( this, _assignedMaterial, c.propertyName ) ) { c.ApplyLerped( _assignedMaterial, startColors[ elementIndex ], state ); } elementIndex ++; } elementIndex = 0; foreach ( var f in floats ) { if ( AnimationManager.IsAnimating( this, _assignedMaterial, f.propertyName ) ) { f.ApplyLerped( _assignedMaterial, startFloats[ elementIndex ], state ); } elementIndex ++; } if ( type == TimelineSpanUpdateType.End ) { foreach ( var c in colors ) { AnimationManager.EndAnimation( this, _assignedMaterial, c.propertyName ); } foreach ( var f in floats ) { AnimationManager.EndAnimation( this, _assignedMaterial, f.propertyName ); } // Only write the end material (and release ownership) if no later TweenMaterial // has since taken over this slot - otherwise this would stomp on it after the fact. if ( AnimationManager.IsAnimating( this, target, GetSlotKey() ) ) { if ( MaterialAssignmentMode.Temporary_Duplicate_For_Each_Tween == assignmentMode ) { _assignedMaterial = null; var msc = MaterialSurfaceContainer.From( target, index ); msc.SetMaterialInSlot( slot, toMaterial ); var prop = new ColorPropertyName(); prop.propertyName = "albedo_color"; var info = new Color(); if ( toMaterial is BaseMaterial3D bm ) { info = bm.AlbedoColor; } // this.LogInfo( "Setting:", slot, toMaterial, info, prop.Get( toMaterial ) ); } AnimationManager.EndAnimation( this, target, GetSlotKey() ); } DispatchEnd( sequenceID ); } }, this ); } string GetSlotKey() { return $"MaterialSlot:{index}:{slot}"; } void SetAssignedMaterial( Material material ) { if ( MaterialAssignmentMode.Temporary_Duplicate_For_Each_Tween == assignmentMode ) { _assignedMaterial = (Material)material.Duplicate(); var tmsc = MaterialSurfaceContainer.From( target, index ); tmsc.SetMaterialInSlot( slot, _assignedMaterial ); return; } if ( _assignedMaterial != null ) { return; } if ( MaterialAssignmentMode.No_Assignment == assignmentMode ) { _assignedMaterial = toMaterial; return; } _assignedMaterial = toMaterial; if ( MaterialAssignmentMode.Unique_Once == assignmentMode ) { _assignedMaterial = (Material)toMaterial.Duplicate(); } var msc = MaterialSurfaceContainer.From( target, index ); msc.SetMaterialInSlot( slot, _assignedMaterial ); } } }