#nullable enable using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using T3.Core.Animation; using T3.Core.DataTypes; using T3.Core.DataTypes.Vector; using T3.Core.Logging; using T3.Core.Operator.Slots; using T3.Serialization; using T3.Core.Utils; using T3.Core.Utils.Geometry; using Int3 = T3.Core.DataTypes.Vector.Int3; namespace T3.Core.Operator; public class SymbolExtension { } public sealed class Animator : SymbolExtension { // Stores all animation curves as [childId][inputId] -> array of curves private readonly Dictionary> _curvesByChildAndInput = new(); public void CopyAnimationsTo( Animator targetAnimator, List childrenToCopyFrom, Dictionary oldToNewIdDict) { foreach (var (oldChildId, inputDict) in _curvesByChildAndInput) { if (!childrenToCopyFrom.Contains(oldChildId)) continue; if (!oldToNewIdDict.TryGetValue(oldChildId, out var newChildId)) continue; foreach (var (inputId, curves) in inputDict) { if (curves.Length == 0) continue; var cloned = new Curve[curves.Length]; for (var i = 0; i < curves.Length; i++) { cloned[i] = curves[i].TypedClone(); } targetAnimator.AddCurvesToInput(cloned.ToList(), newChildId, inputId); } } } public void RemoveAnimationsFromInstances(List instanceIds) { foreach (var childId in instanceIds) _curvesByChildAndInput.Remove(childId); } public Curve[]? AddOrRestoreCurvesToInput(IInputSlot inputSlot, Curve[]? originalCurves) { switch (inputSlot) { case Slot floatInputSlot: return AddCurvesForFloatVector(inputSlot, [floatInputSlot.Value], originalCurves); case Slot v2Slot: return AddCurvesForFloatVector(inputSlot, v2Slot.Value.ToArray(), originalCurves); case Slot v3Slot: return AddCurvesForFloatVector(inputSlot, v3Slot.Value.ToArray(), originalCurves); case Slot v4Slot: return AddCurvesForFloatVector(inputSlot, v4Slot.Value.ToArray(), originalCurves); case Slot intSlot: return AddCurvesForIntVector(inputSlot, [intSlot.Value], originalCurves); case Slot size2Slot: return AddCurvesForIntVector(inputSlot, [size2Slot.Value.Width, size2Slot.Value.Height], originalCurves); case Slot boolSlot: return AddCurvesForIntVector(inputSlot, [boolSlot.Value ? 1 : 0], originalCurves); } Log.Error("Could not create curves for this type"); return null; } private Curve[] AddCurvesForFloatVector(IInputSlot inputSlot, float[] values, Curve[]? originalCurves) { var childId = inputSlot.Parent.SymbolChildId; var inputId = inputSlot.Id; var now = Playback.Current.TimeInBars; var curves = originalCurves ?? new Curve[values.Length]; if (curves.Length != values.Length) { Array.Resize(ref curves, values.Length); } for (var i = 0; i < values.Length; i++) { // ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract curves[i] ??= new Curve(); curves[i]!.AddOrUpdateV(now, new VDefinition { Value = values[i], InInterpolation = VDefinition.KeyInterpolation.Linear, OutInterpolation = VDefinition.KeyInterpolation.Linear, BrokenTangents = true, }); } SetCurveArray(childId, inputId, curves); return curves; } private Curve[] AddCurvesForIntVector(IInputSlot inputSlot, int[] values, Curve[]? originalCurves) { var childId = inputSlot.Parent.SymbolChildId; var inputId = inputSlot.Id; var now = Playback.Current.TimeInBars; var curves = originalCurves ?? new Curve[values.Length]; if (curves.Length != values.Length) { Array.Resize(ref curves, values.Length); } for (var i = 0; i < values.Length; i++) { // ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract curves[i] ??= new Curve(); curves[i].AddOrUpdateV(now, new VDefinition { Value = values[i], InInterpolation = VDefinition.KeyInterpolation.Constant, OutInterpolation = VDefinition.KeyInterpolation.Constant, }); } SetCurveArray(childId, inputId, curves); return curves; } internal void CreateUpdateActionsForExistingCurves(IEnumerable childInstances) { //var sortedChildIds = _curvesByChildAndInput.Keys.OrderBy(id => id); foreach (var instance in childInstances) { var childId = instance.SymbolChildId; // if(!childInstances.TryGetValue(childId, out var instance)) // continue; if(!_curvesByChildAndInput.TryGetValue(childId, out var inputsDict)) continue; //var inputsDict = _curvesByChildAndInput[childId]; foreach (var inputId in inputsDict.Keys.OrderBy(id => id)) { var curves = inputsDict[inputId]; if (curves.Length == 0) continue; var inputSlot = instance.Inputs.FirstOrDefault(i => i.Id == inputId); if (inputSlot == null) continue; switch (curves.Length) { case 1: switch (inputSlot) { case Slot fSlot: fSlot.OverrideWithAnimationAction(ctx => fSlot.Value = (float)curves[0].GetSampledValue(ctx.LocalTime)); fSlot.DirtyFlag.Trigger |= DirtyFlagTrigger.Animated; break; case Slot iSlot: iSlot.OverrideWithAnimationAction(ctx => iSlot.Value = (int)curves[0].GetSampledValue(ctx.LocalTime)); iSlot.DirtyFlag.Trigger |= DirtyFlagTrigger.Animated; break; case Slot bSlot: bSlot.OverrideWithAnimationAction(ctx => bSlot.Value = curves[0].GetSampledValue(ctx.LocalTime) > 0.5f); bSlot.DirtyFlag.Trigger |= DirtyFlagTrigger.Animated; break; } break; case 2: var slot2 = inputSlot; switch (slot2) { case Slot v2Slot: v2Slot.OverrideWithAnimationAction(ctx => { v2Slot.Value.X = (float)curves[0].GetSampledValue(ctx.LocalTime); v2Slot.Value.Y = (float)curves[1].GetSampledValue(ctx.LocalTime); }); v2Slot.DirtyFlag.Trigger |= DirtyFlagTrigger.Animated; break; case Slot size2Slot: size2Slot.OverrideWithAnimationAction(ctx => { size2Slot.Value.Width = (int)curves[0].GetSampledValue(ctx.LocalTime); size2Slot.Value.Height = (int)curves[1].GetSampledValue(ctx.LocalTime); }); size2Slot.DirtyFlag.Trigger |= DirtyFlagTrigger.Animated; break; } break; case 3: var slot3 = inputSlot; switch (slot3) { case Slot v3Slot: v3Slot.OverrideWithAnimationAction(ctx => { v3Slot.Value.X = (float)curves[0].GetSampledValue(ctx.LocalTime); v3Slot.Value.Y = (float)curves[1].GetSampledValue(ctx.LocalTime); v3Slot.Value.Z = (float)curves[2].GetSampledValue(ctx.LocalTime); }); v3Slot.DirtyFlag.Trigger |= DirtyFlagTrigger.Animated; break; case Slot i3Slot: i3Slot.OverrideWithAnimationAction(ctx => { i3Slot.Value.X = (int)curves[0].GetSampledValue(ctx.LocalTime); i3Slot.Value.Y = (int)curves[1].GetSampledValue(ctx.LocalTime); i3Slot.Value.Z = (int)curves[2].GetSampledValue(ctx.LocalTime); }); i3Slot.DirtyFlag.Trigger |= DirtyFlagTrigger.Animated; break; } break; case 4: if (inputSlot is Slot v4Slot) { v4Slot.OverrideWithAnimationAction(ctx => { v4Slot.Value.X = (float)curves[0].GetSampledValue(ctx.LocalTime); v4Slot.Value.Y = (float)curves[1].GetSampledValue(ctx.LocalTime); v4Slot.Value.Z = (float)curves[2].GetSampledValue(ctx.LocalTime); v4Slot.Value.W = (float)curves[3].GetSampledValue(ctx.LocalTime); }); v4Slot.DirtyFlag.Trigger |= DirtyFlagTrigger.Animated; } break; default: Debug.Assert(false); break; } } } } public void RemoveAnimationFrom(IInputSlot inputSlot) { inputSlot.RestoreUpdateAction(); inputSlot.DirtyFlag.Trigger &= ~DirtyFlagTrigger.Animated; var childId = inputSlot.Parent.SymbolChildId; var inputId = inputSlot.Id; if (_curvesByChildAndInput.TryGetValue(childId, out var inputDict)) { inputDict.Remove(inputId); if (inputDict.Count == 0) _curvesByChildAndInput.Remove(childId); } } public bool TryGetFirstInputAnimationCurve(IInputSlot inputSlot, [NotNullWhen(true)] out Curve? curve) { curve = null; var childId = inputSlot.Parent.SymbolChildId; var inputId = inputSlot.Id; if (_curvesByChildAndInput.TryGetValue(childId, out var inputDict) && inputDict.TryGetValue(inputId, out var curves) && curves.Length > 0) { curve = curves[0]; return true; } return false; } public bool IsInputSlotAnimated(IInputSlot inputSlot) { var childId = inputSlot.Parent.SymbolChildId; var inputId = inputSlot.Id; return _curvesByChildAndInput.TryGetValue(childId, out var inputDict) && inputDict.ContainsKey(inputId); } public bool IsInputAnimated(Symbol.Child child, Symbol.Child.Input input) { return IsAnimated(child.Id, input.Id); } public bool IsAnimated(Guid childId, Guid inputId) { return _curvesByChildAndInput.TryGetValue(childId, out var inputDict) && inputDict.ContainsKey(inputId); } public bool IsInstanceAnimated(Instance instance) { return _curvesByChildAndInput.ContainsKey(instance.SymbolChildId); } public bool TryGetCurvesForChildInput(Guid childId, Guid inputId, [NotNullWhen(true)] out Curve[]? curves) { curves = null; return _curvesByChildAndInput.TryGetValue(childId, out var inputDict) && inputDict.TryGetValue(inputId, out curves) && curves.Length >0; } public bool TryGetCurvesForInputSlot(IInputSlot inputSlot, [NotNullWhen(true)] out Curve[]? curves) { return TryGetCurvesForChildInput(inputSlot.Parent.SymbolChildId, inputSlot.Id, out curves); } public IEnumerable GetTimeKeys(Guid childId, Guid inputId, double time) { if (!TryGetCurvesForChildInput(childId, inputId, out var curves)) yield break; foreach (var curve in curves) { yield return curve.GetV(time); // Note: Can in include Null! } } public void SetTimeKeys(Guid childId, Guid inputId, double time, List vDefinitions) { if (!TryGetCurvesForChildInput(childId, inputId, out var curves)) return; for (var i = 0; i < curves.Length; i++) { var vDef = vDefinitions[i]; if (vDef == null) curves[i].RemoveKeyframeAt(time); else curves[i].AddOrUpdateV(time, vDef); } } internal void Write(JsonTextWriter writer) { if (_curvesByChildAndInput.Count == 0) return; // Flatten everything into a list (childId, inputId, index, curve). var allCurves = new List<(Guid childId, Guid inputId, int index, Curve curve)>(); foreach (var (childId, inputDict) in _curvesByChildAndInput) { foreach (var (inputId, curveArr) in inputDict) { for (int i = 0; i < curveArr.Length; i++) { allCurves.Add((childId, inputId, i, curveArr[i])); } } } // Sort by index only, matching the original behavior of // .OrderBy(valuePair => valuePair.Key.Index) // so channels are grouped in a stable order (0,1,2,3, etc.) var ordered = allCurves.OrderBy(x => x.index).ToList(); writer.WritePropertyName("Animator"); writer.WriteStartArray(); foreach (var (childId, inputId, index, curve) in ordered) { writer.WriteStartObject(); writer.WriteValue("InstanceId", childId); writer.WriteValue("InputId", inputId); if (index != 0) writer.WriteValue("Index", index); curve.Write(writer); writer.WriteEndObject(); } writer.WriteEndArray(); } internal void Read(JToken inputToken, Symbol symbol) { // Build a local list so we can populate the dictionary afterwards var buffer = new List<(Guid childId, Guid inputId, int index, Curve curve)>(); foreach (var entry in inputToken) { JsonUtils.TryGetGuid(entry["InstanceId"], out var childId); JsonUtils.TryGetGuid(entry["InputId"], out var inputId); var indexToken = entry.SelectToken("Index"); var index = indexToken?.Value() ?? 0; if (!symbol.Children.ContainsKey(childId)) continue; var curve = new Curve(); curve.Read(entry); buffer.Add((childId, inputId, index, curve)); } // Sort by index so multi-channel arrays come in sorted order var sorted = buffer.OrderBy(b => b.index).ToList(); foreach (var group in sorted.GroupBy(x => (x.childId, x.inputId))) { var maxIndex = group.Max(g => g.index); var curveArray = new Curve[maxIndex + 1]; foreach (var g in group) curveArray[g.index] = g.curve; SetCurveArray(group.Key.childId, group.Key.inputId, curveArray); } } public void AddCurvesToInput(List curves, IInputSlot inputSlot) { if (inputSlot.Parent?.Parent == null) { Log.Warning("Can't add animation curves without valid structure."); return; } var childId = inputSlot.Parent.SymbolChildId; var inputId = inputSlot.Id; SetCurveArray(childId, inputId, curves.ToArray()); inputSlot.Parent.Parent.Symbol.CreateOrUpdateActionsForAnimatedChildren(); } // Overload without needing a real inputSlot (used in CopyAnimationsTo) public void AddCurvesToInput(List curves, Guid childId, Guid inputId) { SetCurveArray(childId, inputId, curves.ToArray()); } private void SetCurveArray(Guid childId, Guid inputId, Curve[] curves) { if (!_curvesByChildAndInput.TryGetValue(childId, out var inputDict)) { inputDict = new Dictionary(); _curvesByChildAndInput[childId] = inputDict; } inputDict[inputId] = curves; } public static void UpdateVector3InputValue(InputSlot inputSlot, Vector3 value) { if (inputSlot.Parent?.Parent == null) { Log.Warning("Can't add animation curves without valid structure."); return; } var animator = inputSlot.Parent.Parent.Symbol.Animator; if (animator.IsInputSlotAnimated(inputSlot)) { if (!animator.TryGetCurvesForInputSlot(inputSlot, out var curves)) return; var time = Playback.Current.TimeInBars; for (var i = 0; i < 3; i++) { var vDef = curves[i].GetV(time) ?? new VDefinition { U = time }; vDef.Value = value.GetValueUnsafe(i); curves[i].AddOrUpdateV(time, vDef); } } else { inputSlot.SetTypedInputValue(value); } } public static void UpdateFloatInputValue(InputSlot inputSlot, float value) { if (inputSlot.Parent?.Parent == null) { Log.Warning("Can't add animation curves without valid structure."); return; } if (inputSlot.Parent.Parent.Symbol .Animator .TryGetCurvesForInputSlot(inputSlot, out var curves)) { var firstCurve = curves[0]; var time = Playback.Current.TimeInBars; var vDef = firstCurve.GetV(time) ?? new VDefinition { U = time }; vDef.Value = value; firstCurve.AddOrUpdateV(time, vDef); } else { inputSlot.SetTypedInputValue(value); } } }