Code/HumanoidRetargeter/Core/Target/AnimGraph2Sources.cs

Builds the KV3 source text for AnimGraph2 documents: a `.skel` skeleton header pointing at a source model file, and an `.anim` clip header bound to a skeleton, both with scale values. The strings are handed to the engine's animation compiler, which rejects JSON.

#nullable enable annotations

namespace HumanoidRetargeter.Core.Target;

/// <summary>
/// The source files an AnimGraph2 graph plays from, as KV3 text (their compiler rejects JSON): a skeleton
/// (<c>.skel</c>) listing the bones of a model's source art, and a clip (<c>.anim</c>) per animation, compiled
/// against that skeleton.
/// </summary>
public static class AnimGraph2Sources
{
    /// <summary>The KV3 header both source kinds are written with.</summary>
    public const string GenericHeader =
        "<!-- kv3 encoding:text:version{e21c7f3c-8a33-41c5-9977-a76d3a32aa0d} format:generic:version{7412167c-06e9-4698-aff2-e63eb59037e7} -->";

    /// <summary>A <c>.skel</c> with the bones of <paramref name="sourceFile"/> (an FBX, DMX or SMD), scaled by
    /// <paramref name="globalScale"/>.</summary>
    public static string Skeleton(string sourceFile, double globalScale = 1.0)
        => Kv3.Serialize(new Kv3Document(GenericHeader, new KvObject
        {
            ["m_sourceFilename"] = new KvString(sourceFile),
            ["m_flGlobalScale"] = new KvDouble(globalScale),
            ["m_additionalSourceFilenames"] = new KvArray(),
            ["m_rootBoneName"] = new KvString(""),
        }));

    /// <summary>An <c>.anim</c> playing <paramref name="sourceFile"/>'s first take on <paramref name="skeleton"/>
    /// (a <c>.skel</c>), scaled by <paramref name="importScale"/>.</summary>
    public static string Clip(string sourceFile, string skeleton, double importScale = 1.0)
        => Kv3.Serialize(new Kv3Document(GenericHeader, new KvObject
        {
            ["_class"] = new KvString("CClipDocument"),
            ["m_sourceFilename"] = new KvString(sourceFile),
            ["m_animationSkeletonName"] = new KvString(skeleton),
            ["m_nTake"] = new KvLong(0),
            ["m_flImportScale"] = new KvDouble(importScale),
        }));
}