528 lines
21 KiB
C#
528 lines
21 KiB
C#
#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<Guid, Dictionary<Guid, Curve[]>> _curvesByChildAndInput = new();
|
|
|
|
public void CopyAnimationsTo(
|
|
Animator targetAnimator,
|
|
List<Guid> childrenToCopyFrom,
|
|
Dictionary<Guid, Guid> 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<Guid> instanceIds)
|
|
{
|
|
foreach (var childId in instanceIds)
|
|
_curvesByChildAndInput.Remove(childId);
|
|
}
|
|
|
|
public Curve[]? AddOrRestoreCurvesToInput(IInputSlot inputSlot, Curve[]? originalCurves)
|
|
{
|
|
switch (inputSlot)
|
|
{
|
|
case Slot<float> floatInputSlot:
|
|
return AddCurvesForFloatVector(inputSlot, [floatInputSlot.Value], originalCurves);
|
|
|
|
case Slot<Vector2> v2Slot:
|
|
return AddCurvesForFloatVector(inputSlot, v2Slot.Value.ToArray(), originalCurves);
|
|
|
|
case Slot<Vector3> v3Slot:
|
|
return AddCurvesForFloatVector(inputSlot, v3Slot.Value.ToArray(), originalCurves);
|
|
|
|
case Slot<Vector4> v4Slot:
|
|
return AddCurvesForFloatVector(inputSlot, v4Slot.Value.ToArray(), originalCurves);
|
|
|
|
case Slot<int> intSlot:
|
|
return AddCurvesForIntVector(inputSlot, [intSlot.Value], originalCurves);
|
|
|
|
case Slot<Int2> size2Slot:
|
|
return AddCurvesForIntVector(inputSlot,
|
|
[size2Slot.Value.Width, size2Slot.Value.Height],
|
|
originalCurves);
|
|
|
|
case Slot<bool> 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<Instance> 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<float> fSlot:
|
|
fSlot.OverrideWithAnimationAction(ctx =>
|
|
fSlot.Value = (float)curves[0].GetSampledValue(ctx.LocalTime));
|
|
fSlot.DirtyFlag.Trigger |= DirtyFlagTrigger.Animated;
|
|
break;
|
|
|
|
case Slot<int> iSlot:
|
|
iSlot.OverrideWithAnimationAction(ctx =>
|
|
iSlot.Value = (int)curves[0].GetSampledValue(ctx.LocalTime));
|
|
iSlot.DirtyFlag.Trigger |= DirtyFlagTrigger.Animated;
|
|
break;
|
|
|
|
case Slot<bool> 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<System.Numerics.Vector2> 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<Int2> 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<System.Numerics.Vector3> 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<Int3> 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<System.Numerics.Vector4> 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<VDefinition?> 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<VDefinition?> 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<int>() ?? 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<Curve> 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<Curve> 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<Guid, Curve[]>();
|
|
_curvesByChildAndInput[childId] = inputDict;
|
|
}
|
|
inputDict[inputId] = curves;
|
|
}
|
|
|
|
public static void UpdateVector3InputValue(InputSlot<System.Numerics.Vector3> 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<float> 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);
|
|
}
|
|
}
|
|
} |