OverviewFilesCommitsIssuesDocsReleasesPeers

ifsrenderer-linux / IFSEngine / Animation / Dopesheet.cs

62 lines · csharp ref:
#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,
    };

}