Files
tooll3--t3/Core.Tests/SmoothTangentTests.cs
2026-07-13 13:13:17 +08:00

161 lines
5.6 KiB
C#

using T3.Core.Animation;
using T3.Core.DataTypes;
using Xunit;
using Xunit.Abstractions;
namespace Core.Tests;
/// <summary>
/// Tests that expose issues with smooth tangent computation,
/// specifically where straight segments get bent by overshoot clamping.
/// </summary>
public class SmoothTangentTests
{
private readonly ITestOutputHelper _output;
public SmoothTangentTests(ITestOutputHelper output)
{
_output = output;
}
[Fact]
public void ThreeSmoothCollinearKeys_ShouldBeStraight()
{
// Three smooth keys on y=x should produce a perfectly straight line.
var curve = new Curve();
curve.AddOrUpdateV(0.0, MakeSmooth(0.0, 0.0));
curve.AddOrUpdateV(0.5, MakeSmooth(0.5, 0.5));
curve.AddOrUpdateV(1.0, MakeSmooth(1.0, 1.0));
DumpTangents(curve, "ThreeSmoothCollinear y=x");
for (double u = 0.0; u <= 1.0; u += 0.05)
{
var sampled = curve.GetSampledValue(u);
_output.WriteLine($" u={u:F2} expected={u:F4} actual={sampled:F4} error={Math.Abs(sampled - u):E2}");
Assert.Equal(u, sampled, 3); // tolerance 0.001
}
}
[Fact]
public void ThreeSmoothCollinearKeys_Steep_ShouldBeStraight()
{
// y = 10*x: steeper slope, still collinear
var curve = new Curve();
curve.AddOrUpdateV(0.0, MakeSmooth(0.0, 0.0));
curve.AddOrUpdateV(0.5, MakeSmooth(0.5, 5.0));
curve.AddOrUpdateV(1.0, MakeSmooth(1.0, 10.0));
DumpTangents(curve, "ThreeSmoothCollinear y=10x");
for (double u = 0.0; u <= 1.0; u += 0.05)
{
var expected = u * 10.0;
var sampled = curve.GetSampledValue(u);
_output.WriteLine($" u={u:F2} expected={expected:F4} actual={sampled:F4} error={Math.Abs(sampled - expected):E2}");
Assert.Equal(expected, sampled, 2); // tolerance 0.01
}
}
[Fact]
public void FourSmoothCollinearKeys_ShouldBeStraight()
{
// Four evenly spaced smooth keys on y=x
var curve = new Curve();
curve.AddOrUpdateV(0.0, MakeSmooth(0.0, 0.0));
curve.AddOrUpdateV(0.333, MakeSmooth(0.333, 0.333));
curve.AddOrUpdateV(0.667, MakeSmooth(0.667, 0.667));
curve.AddOrUpdateV(1.0, MakeSmooth(1.0, 1.0));
DumpTangents(curve, "FourSmoothCollinear y=x");
for (double u = 0.0; u <= 1.0; u += 0.05)
{
var sampled = curve.GetSampledValue(u);
_output.WriteLine($" u={u:F2} expected={u:F4} actual={sampled:F4} error={Math.Abs(sampled - u):E2}");
Assert.Equal(u, sampled, 3);
}
}
[Fact]
public void ThreeSmoothCollinearKeys_UnevenSpacing_ShouldBeStraight()
{
// Collinear points with uneven spacing: [0,0], [0.1, 0.1], [1.0, 1.0]
// This is a common failure case for tangent clamping heuristics.
var curve = new Curve();
curve.AddOrUpdateV(0.0, MakeSmooth(0.0, 0.0));
curve.AddOrUpdateV(0.1, MakeSmooth(0.1, 0.1));
curve.AddOrUpdateV(1.0, MakeSmooth(1.0, 1.0));
DumpTangents(curve, "ThreeSmoothCollinear uneven [0,0]-[0.1,0.1]-[1,1]");
for (double u = 0.0; u <= 1.0; u += 0.05)
{
var sampled = curve.GetSampledValue(u);
_output.WriteLine($" u={u:F2} expected={u:F4} actual={sampled:F4} error={Math.Abs(sampled - u):E2}");
Assert.Equal(u, sampled, 2); // tolerance 0.01
}
}
[Fact]
public void ThreeSmoothCollinearKeys_Descending_ShouldBeStraight()
{
// Descending collinear: y = 1 - x
var curve = new Curve();
curve.AddOrUpdateV(0.0, MakeSmooth(0.0, 1.0));
curve.AddOrUpdateV(0.5, MakeSmooth(0.5, 0.5));
curve.AddOrUpdateV(1.0, MakeSmooth(1.0, 0.0));
DumpTangents(curve, "ThreeSmoothCollinear descending y=1-x");
for (double u = 0.0; u <= 1.0; u += 0.05)
{
var expected = 1.0 - u;
var sampled = curve.GetSampledValue(u);
_output.WriteLine($" u={u:F2} expected={expected:F4} actual={sampled:F4} error={Math.Abs(sampled - expected):E2}");
Assert.Equal(expected, sampled, 3);
}
}
[Fact]
public void ThreeSmoothKeys_ConstantValue_ShouldBeFlat()
{
// All keys at same value — must remain flat
var curve = new Curve();
curve.AddOrUpdateV(0.0, MakeSmooth(0.0, 5.0));
curve.AddOrUpdateV(0.5, MakeSmooth(0.5, 5.0));
curve.AddOrUpdateV(1.0, MakeSmooth(1.0, 5.0));
DumpTangents(curve, "ThreeSmooth constant y=5");
for (double u = 0.0; u <= 1.0; u += 0.05)
{
var sampled = curve.GetSampledValue(u);
Assert.Equal(5.0, sampled, 4);
}
}
private static VDefinition MakeSmooth(double time, double value)
{
return new VDefinition
{
U = time,
Value = value,
InInterpolation = VDefinition.KeyInterpolation.Smooth,
OutInterpolation = VDefinition.KeyInterpolation.Smooth,
};
}
private void DumpTangents(Curve curve, string label)
{
_output.WriteLine($"--- {label} ---");
foreach (var kvp in curve.Table)
{
var v = kvp.Value;
_output.WriteLine($" U={v.U:F4} Value={v.Value:F4} " +
$"InAngle={v.InTangentAngle:F4} ({Math.Atan(Math.Tan(v.InTangentAngle)):F4} slope={Math.Tan(v.InTangentAngle):F4}) " +
$"OutAngle={v.OutTangentAngle:F4} ({Math.Atan(Math.Tan(v.OutTangentAngle)):F4} slope={Math.Tan(v.OutTangentAngle):F4})");
}
}
}