#nullable enable using System; using System.Threading; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using T3.Core.DataTypes; using T3.Serialization; namespace T3.Core.Animation; public sealed class VDefinition { public enum KeyInterpolation { Constant = 0, Linear, Smooth, Cubic, Horizontal, Tangent, } public int UniqueId { get; } = Interlocked.Increment(ref _nextId); /// /// Set by when this key is added to its table. /// Cleared when removed. Property setters use this to auto-invalidate the curve's cache. /// internal Curve? ParentCurve; private double _u; public double U { get => _u; set => _u = Math.Round(value, Curve.TimePrecision); } public double Value { get => _value; set { _value = value; ParentCurve?.NotifyChanged(); } } public KeyInterpolation InInterpolation { get => _inInterpolation; set { _inInterpolation = value; ParentCurve?.NotifyChanged(); } } public KeyInterpolation OutInterpolation { get => _outInterpolation; set { _outInterpolation = value; ParentCurve?.NotifyChanged(); } } public double InTangentAngle { get => _inTangentAngle; set { _inTangentAngle = value; ParentCurve?.NotifyChanged(); } } public double OutTangentAngle { get => _outTangentAngle; set { _outTangentAngle = value; ParentCurve?.NotifyChanged(); } } public bool Weighted { get => _weighted; set { _weighted = value; ParentCurve?.NotifyChanged(); } } public bool BrokenTangents { get => _brokenTangents; set { _brokenTangents = value; ParentCurve?.NotifyChanged(); } } /// Influence multiplier for the incoming tangent. Default 1.0 = full segment width. public float TensionIn { get => _tensionIn; set { _tensionIn = value; ParentCurve?.NotifyChanged(); } } /// Influence multiplier for the outgoing tangent. Default 1.0 = full segment width. public float TensionOut { get => _tensionOut; set { _tensionOut = value; ParentCurve?.NotifyChanged(); } } public VDefinition Clone() { return new VDefinition() { _value = _value, _u = _u, _inInterpolation = _inInterpolation, _outInterpolation = _outInterpolation, _inTangentAngle = _inTangentAngle, _outTangentAngle = _outTangentAngle, _weighted = _weighted, _brokenTangents = _brokenTangents, _tensionIn = _tensionIn, _tensionOut = _tensionOut, // ParentCurve intentionally NOT copied — clone is independent }; } public void CopyValuesFrom(VDefinition def) { _value = def._value; _u = def._u; _inInterpolation = def._inInterpolation; _outInterpolation = def._outInterpolation; _inTangentAngle = def._inTangentAngle; _outTangentAngle = def._outTangentAngle; _weighted = def._weighted; _brokenTangents = def._brokenTangents; _tensionIn = def._tensionIn; _tensionOut = def._tensionOut; ParentCurve?.NotifyChanged(); } internal void Read(JToken jsonV) { _value = jsonV.Value(nameof(Value)); _inTangentAngle = jsonV.ReadValueSafe(nameof(InTangentAngle), 0.0); _outTangentAngle = jsonV.ReadValueSafe(nameof(OutTangentAngle), 0.0); _weighted = jsonV.ReadValueSafe(nameof(Weighted), false); _brokenTangents = jsonV.ReadValueSafe(nameof(BrokenTangents), false); _tensionIn = (float)jsonV.ReadValueSafe(nameof(TensionIn), 1.0); _tensionOut = (float)jsonV.ReadValueSafe(nameof(TensionOut), 1.0); // New format: unified InInterpolation / OutInterpolation if (jsonV[nameof(InInterpolation)] != null) { _inInterpolation = jsonV[nameof(InInterpolation)].GetEnumValue(KeyInterpolation.Linear); _outInterpolation = jsonV[nameof(OutInterpolation)].GetEnumValue(KeyInterpolation.Linear); } else { // Legacy format: convert InType+InEditMode / OutType+OutEditMode ReadLegacyInterpolation(jsonV); } } private void ReadLegacyInterpolation(JToken jsonV) { var inTypeStr = jsonV["InType"]?.Value() ?? "Linear"; var outTypeStr = jsonV["OutType"]?.Value() ?? "Linear"; var inEditStr = jsonV["InEditMode"]?.Value() ?? "Linear"; var outEditStr = jsonV["OutEditMode"]?.Value() ?? "Linear"; _inInterpolation = ConvertLegacy(inTypeStr, inEditStr); _outInterpolation = ConvertLegacy(outTypeStr, outEditStr); } private static KeyInterpolation ConvertLegacy(string interpolationType, string editMode) { return interpolationType switch { "Constant" => KeyInterpolation.Constant, "Linear" => KeyInterpolation.Linear, "Spline" => editMode switch { "Smooth" => KeyInterpolation.Smooth, "Cubic" => KeyInterpolation.Cubic, "Horizontal" => KeyInterpolation.Horizontal, "Tangent" => KeyInterpolation.Tangent, "Linear" => KeyInterpolation.Linear, _ => KeyInterpolation.Smooth }, _ => KeyInterpolation.Linear }; } internal void Write(JsonTextWriter writer) { writer.WriteValue(nameof(Value), _value); writer.WriteObject(nameof(InInterpolation), _inInterpolation); writer.WriteObject(nameof(OutInterpolation), _outInterpolation); if (_inTangentAngle != 0.0) writer.WriteValue(nameof(InTangentAngle), _inTangentAngle); if (_outTangentAngle != 0.0) writer.WriteValue(nameof(OutTangentAngle), _outTangentAngle); if (_weighted) writer.WriteValue(nameof(Weighted), true); if (_brokenTangents) writer.WriteValue(nameof(BrokenTangents), true); // ReSharper disable CompareOfFloatsByEqualityOperator if (_tensionIn != 1.0f) writer.WriteValue(nameof(TensionIn), _tensionIn); if (_tensionOut != 1.0f) writer.WriteValue(nameof(TensionOut), _tensionOut); // ReSharper restore CompareOfFloatsByEqualityOperator } private double _value; private KeyInterpolation _inInterpolation = KeyInterpolation.Linear; private KeyInterpolation _outInterpolation = KeyInterpolation.Linear; private double _inTangentAngle; private double _outTangentAngle; private bool _weighted; private bool _brokenTangents; private float _tensionIn = 1.0f; private float _tensionOut = 1.0f; private static int _nextId; }