#nullable enable
using System;
using System.Collections.Generic;
namespace IFSEngine.Animation;
public class Dopesheet
{
public Dictionary<string, Channel> Channels { get; init; } = [];
public TimeSpan Length { get; set; } = TimeSpan.FromSeconds(10);
public int Fps { get; set; } = 30;
private double keyframeEpsilon => 0.99 / Fps;
public void AddOrUpdateChannel(string name, string path, TimeOnly time, double value)
{
var t = time.ToTimeSpan().TotalSeconds;
if (Channels.TryGetValue(path, out var channel))
{
if (channel.Keyframes.Find(k => Math.Abs(k.t - t) < keyframeEpsilon) is var kf and not null)
{
kf.Value = value;
}
else
{
channel.Keyframes.Add(NewKeyframe(t, value));
}
}
else
{
Channels[path] = new Channel(name, NewKeyframe(t, value));
}
}
/// <summary>
/// A keyframe as the ◇ diamond makes one.
/// </summary>
/// <remarks>
/// <para>
/// Bezier with auto-clamped handles, which is Blender's default and NOT the engine's:
/// <see cref="Keyframe"/> still defaults to <see cref="InterpolationMode.CatmullRom"/>, so
/// that keyframes loaded from existing files keep evaluating exactly as they did.
/// </para>
/// <para>
/// The old default is what made a hold impossible. Catmull-Rom's tangent is
/// <c>(p2 - p0) / 2</c>, from the neighbours' VALUES alone, so it never flattens: two keys
/// set to the same value still got a slope, and the segment between them had to leave that
/// value and come back - 4.8% off a held 1.0 in the reported case. An auto-clamped handle
/// is zero at a local extremum, which is what two equal keys in a row are.
/// </para>
/// </remarks>
private static Keyframe NewKeyframe(double t, double value) => new()
{
t = t,
Value = value,
Type = KeyframeType.Bezier,
LeftHandleType = HandleType.AutoClamped,
RightHandleType = HandleType.AutoClamped,
};
}