#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;
}