227 lines
10 KiB
C#
227 lines
10 KiB
C#
using T3.Core.Animation;
|
|
using T3.Core.DataTypes;
|
|
using Xunit;
|
|
|
|
namespace Core.Tests;
|
|
|
|
public class TensionInterpolationTests
|
|
{
|
|
[Fact]
|
|
public void DefaultTension_BehavesLikeOriginal()
|
|
{
|
|
// Tension 1.0 (default) should produce the same result as the original interpolation
|
|
var curve = new Curve();
|
|
curve.AddOrUpdateV(0.0, new VDefinition
|
|
{
|
|
Value = 0.0,
|
|
InInterpolation = VDefinition.KeyInterpolation.Smooth,
|
|
OutInterpolation = VDefinition.KeyInterpolation.Smooth,
|
|
TensionIn = 1.0f,
|
|
TensionOut = 1.0f,
|
|
});
|
|
curve.AddOrUpdateV(1.0, new VDefinition
|
|
{
|
|
Value = 1.0,
|
|
InInterpolation = VDefinition.KeyInterpolation.Smooth,
|
|
OutInterpolation = VDefinition.KeyInterpolation.Smooth,
|
|
TensionIn = 1.0f,
|
|
TensionOut = 1.0f,
|
|
});
|
|
|
|
// Should match a curve without explicit tension settings
|
|
var curveNoTension = new Curve();
|
|
curveNoTension.AddOrUpdateV(0.0, new VDefinition
|
|
{
|
|
Value = 0.0,
|
|
InInterpolation = VDefinition.KeyInterpolation.Smooth,
|
|
OutInterpolation = VDefinition.KeyInterpolation.Smooth,
|
|
});
|
|
curveNoTension.AddOrUpdateV(1.0, new VDefinition
|
|
{
|
|
Value = 1.0,
|
|
InInterpolation = VDefinition.KeyInterpolation.Smooth,
|
|
OutInterpolation = VDefinition.KeyInterpolation.Smooth,
|
|
});
|
|
|
|
for (double u = 0.0; u <= 1.0; u += 0.1)
|
|
{
|
|
Assert.Equal(curveNoTension.GetSampledValue(u), curve.GetSampledValue(u), 10);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void LowTension_ProducesSnapperCurve()
|
|
{
|
|
// Low tension (0.3) should produce a snappier transition — closer to linear
|
|
var curveSnap = new Curve();
|
|
curveSnap.AddOrUpdateV(0.0, new VDefinition
|
|
{
|
|
Value = 0.0,
|
|
InInterpolation = VDefinition.KeyInterpolation.Tangent,
|
|
OutInterpolation = VDefinition.KeyInterpolation.Tangent,
|
|
OutTangentAngle = Math.Atan(1.0), // 45 degrees, slope=1
|
|
TensionOut = 0.3f,
|
|
});
|
|
curveSnap.AddOrUpdateV(1.0, new VDefinition
|
|
{
|
|
Value = 1.0,
|
|
InInterpolation = VDefinition.KeyInterpolation.Tangent,
|
|
OutInterpolation = VDefinition.KeyInterpolation.Tangent,
|
|
InTangentAngle = Math.Atan(1.0),
|
|
TensionIn = 0.3f,
|
|
});
|
|
|
|
var curveNormal = new Curve();
|
|
curveNormal.AddOrUpdateV(0.0, new VDefinition
|
|
{
|
|
Value = 0.0,
|
|
InInterpolation = VDefinition.KeyInterpolation.Tangent,
|
|
OutInterpolation = VDefinition.KeyInterpolation.Tangent,
|
|
OutTangentAngle = Math.Atan(1.0),
|
|
TensionOut = 1.0f,
|
|
});
|
|
curveNormal.AddOrUpdateV(1.0, new VDefinition
|
|
{
|
|
Value = 1.0,
|
|
InInterpolation = VDefinition.KeyInterpolation.Tangent,
|
|
OutInterpolation = VDefinition.KeyInterpolation.Tangent,
|
|
InTangentAngle = Math.Atan(1.0),
|
|
TensionIn = 1.0f,
|
|
});
|
|
|
|
// At midpoint, low tension should be closer to linear (0.5) than high tension
|
|
var snapMid = curveSnap.GetSampledValue(0.5);
|
|
var normalMid = curveNormal.GetSampledValue(0.5);
|
|
|
|
// Low tension should produce a DIFFERENT curve than normal tension
|
|
var snapQuarter = curveSnap.GetSampledValue(0.25);
|
|
var normalQuarter = curveNormal.GetSampledValue(0.25);
|
|
|
|
Assert.True(Math.Abs(snapQuarter - normalQuarter) > 0.01,
|
|
$"Different tensions should produce different curves: snap={snapQuarter:F4}, normal={normalQuarter:F4}");
|
|
}
|
|
|
|
[Fact]
|
|
public void HighTension_ProducesMoreOvershoot()
|
|
{
|
|
// High tension (2.0) with tangents pointing up should produce overshoot
|
|
var curve = new Curve();
|
|
curve.AddOrUpdateV(0.0, new VDefinition
|
|
{
|
|
Value = 0.0,
|
|
InInterpolation = VDefinition.KeyInterpolation.Tangent,
|
|
OutInterpolation = VDefinition.KeyInterpolation.Tangent,
|
|
OutTangentAngle = 0.7854, // 45 degrees up
|
|
TensionOut = 2.0f,
|
|
});
|
|
curve.AddOrUpdateV(1.0, new VDefinition
|
|
{
|
|
Value = 0.0,
|
|
InInterpolation = VDefinition.KeyInterpolation.Tangent,
|
|
OutInterpolation = VDefinition.KeyInterpolation.Tangent,
|
|
InTangentAngle = Math.PI - 0.7854, // coming from above
|
|
TensionIn = 2.0f,
|
|
});
|
|
|
|
// With high tension and upward tangents, midpoint should overshoot significantly above 0
|
|
var mid = curve.GetSampledValue(0.5);
|
|
Assert.True(mid > 0.5, $"High tension should produce significant overshoot, got {mid}");
|
|
}
|
|
|
|
[Fact]
|
|
public void ZeroTension_ApproachesLinear()
|
|
{
|
|
// Near-zero tension should produce nearly linear interpolation regardless of angle
|
|
var curve = new Curve();
|
|
curve.AddOrUpdateV(0.0, new VDefinition
|
|
{
|
|
Value = 0.0,
|
|
InInterpolation = VDefinition.KeyInterpolation.Tangent,
|
|
OutInterpolation = VDefinition.KeyInterpolation.Tangent,
|
|
OutTangentAngle = 1.2, // steep angle
|
|
TensionOut = 0.05f,
|
|
});
|
|
curve.AddOrUpdateV(1.0, new VDefinition
|
|
{
|
|
Value = 1.0,
|
|
InInterpolation = VDefinition.KeyInterpolation.Tangent,
|
|
OutInterpolation = VDefinition.KeyInterpolation.Tangent,
|
|
InTangentAngle = 1.2,
|
|
TensionIn = 0.05f,
|
|
});
|
|
|
|
// With near-zero tension, tangent slopes contribute almost nothing →
|
|
// the Hermite basis is dominated by the position terms → approaches a smooth S-curve
|
|
// between the endpoints, NOT necessarily linear. Verify the curve stays within bounds.
|
|
for (double u = 0.0; u <= 1.0; u += 0.1)
|
|
{
|
|
var sampled = curve.GetSampledValue(u);
|
|
Assert.True(sampled >= -0.1 && sampled <= 1.1,
|
|
$"Near-zero tension should stay close to [0,1] range, got {sampled} at u={u}");
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void TensionSerializationRoundTrip()
|
|
{
|
|
var original = new VDefinition
|
|
{
|
|
Value = 1.0,
|
|
InInterpolation = VDefinition.KeyInterpolation.Tangent,
|
|
OutInterpolation = VDefinition.KeyInterpolation.Tangent,
|
|
Weighted = true,
|
|
TensionIn = 0.5f,
|
|
TensionOut = 2.0f,
|
|
};
|
|
|
|
var sb = new System.Text.StringBuilder();
|
|
using var sw = new System.IO.StringWriter(sb);
|
|
using var writer = new Newtonsoft.Json.JsonTextWriter(sw);
|
|
writer.WriteStartObject();
|
|
original.Write(writer);
|
|
writer.WriteEndObject();
|
|
writer.Flush();
|
|
|
|
var json = Newtonsoft.Json.Linq.JObject.Parse(sb.ToString());
|
|
var restored = new VDefinition();
|
|
restored.Read(json);
|
|
|
|
Assert.Equal(0.5f, restored.TensionIn);
|
|
Assert.Equal(2.0f, restored.TensionOut);
|
|
}
|
|
|
|
[Fact]
|
|
public void DefaultTension_OmittedFromJson()
|
|
{
|
|
var vDef = new VDefinition
|
|
{
|
|
Value = 1.0,
|
|
TensionIn = 1.0f,
|
|
TensionOut = 1.0f,
|
|
};
|
|
|
|
var sb = new System.Text.StringBuilder();
|
|
using var sw = new System.IO.StringWriter(sb);
|
|
using var writer = new Newtonsoft.Json.JsonTextWriter(sw);
|
|
writer.WriteStartObject();
|
|
vDef.Write(writer);
|
|
writer.WriteEndObject();
|
|
writer.Flush();
|
|
|
|
var json = Newtonsoft.Json.Linq.JObject.Parse(sb.ToString());
|
|
|
|
Assert.Null(json["TensionIn"]);
|
|
Assert.Null(json["TensionOut"]);
|
|
}
|
|
|
|
[Fact]
|
|
public void CloneCopiesTension()
|
|
{
|
|
var original = new VDefinition { TensionIn = 0.3f, TensionOut = 2.5f };
|
|
var clone = original.Clone();
|
|
|
|
Assert.Equal(0.3f, clone.TensionIn);
|
|
Assert.Equal(2.5f, clone.TensionOut);
|
|
}
|
|
}
|