HumanoidRetargeter/Skeleton/SourceScene.cs

Container type representing an imported source animation file. Stores a Skeleton in centimeters, a list of resampled Clips, import unit scale, axis metadata from the source GlobalSettings, optional authored role mapping, and import notes.

File Access
using System;
using System.Collections.Generic;
using HumanoidRetargeter.Mapping;

namespace HumanoidRetargeter.Skeleton;

/// <summary>
/// The result of ingesting a source animation file: a skeleton with its rest pose plus the
/// file's clips resampled on a fixed fps grid.
/// </summary>
/// <remarks>
/// Unit policy: all translations are converted to <b>centimeters</b> at import time
/// (<see cref="UnitScaleCm"/> records the applied source-unit→cm factor for diagnostics).
/// Axis policy: the file's native axes are <b>preserved</b> — no axis conversion is performed.
/// The original FBX <c>GlobalSettings</c> axes are recorded so later pipeline stages can
/// interpret directions (axis indices: 0 = X, 1 = Y, 2 = Z).
/// </remarks>
public sealed class SourceScene
{
    /// <summary>Source skeleton with rest pose, in centimeters, native axes.</summary>
    public Skeleton Skeleton { get; }

    /// <summary>Clips resampled at a fixed fps; locals indexed in skeleton bone order.</summary>
    public IReadOnlyList<Clip> Clips { get; }

    /// <summary>Source-unit → centimeter factor that was applied to all translations at import.</summary>
    public float UnitScaleCm { get; }

    /// <summary>Up axis index from GlobalSettings (0 = X, 1 = Y, 2 = Z; FBX default 1).</summary>
    public int UpAxis { get; }

    /// <summary>Sign of the up axis (+1 or -1).</summary>
    public int UpAxisSign { get; }

    /// <summary>Front axis index from GlobalSettings (FBX default 2 = Z).</summary>
    public int FrontAxis { get; }

    /// <summary>Sign of the front axis (+1 or -1).</summary>
    public int FrontAxisSign { get; }

    /// <summary>Coordinate (right) axis index from GlobalSettings (FBX default 0 = X).</summary>
    public int CoordAxis { get; }

    /// <summary>Sign of the coordinate axis (+1 or -1).</summary>
    public int CoordAxisSign { get; }

    /// <summary>OriginalUpAxis from GlobalSettings (-1 when the exporter did not record one).</summary>
    public int OriginalUpAxis { get; }

    /// <summary>
    /// Human-readable import diagnostics (e.g. cross-stack static-translation disagreements).
    /// Empty when the import was unambiguous.
    /// </summary>
    public IReadOnlyList<string> Notes { get; }

    /// <summary>
    /// A role mapping AUTHORED inside the source file itself, when the format carries one —
    /// a VRM's <c>humanoid.humanBones</c> block (set by the glTF importer,
    /// <see cref="MappingSource.Authored"/>, confidence 1.0). Null for formats/files without
    /// authored role data. <see cref="Retargeter.ResolveMapping"/> consults this before user
    /// presets and shipped-profile detection — it is ground truth from the file.
    /// </summary>
    public MappingResult? AuthoredMapping { get; set; }

    /// <summary>Creates a source scene container.</summary>
    public SourceScene(
        Skeleton skeleton,
        IReadOnlyList<Clip> clips,
        float unitScaleCm,
        int upAxis = 1, int upAxisSign = 1,
        int frontAxis = 2, int frontAxisSign = 1,
        int coordAxis = 0, int coordAxisSign = 1,
        int originalUpAxis = -1,
        IReadOnlyList<string>? notes = null)
    {
        Skeleton = skeleton ?? throw new ArgumentNullException(nameof(skeleton));
        Clips = clips ?? throw new ArgumentNullException(nameof(clips));
        UnitScaleCm = unitScaleCm;
        UpAxis = upAxis;
        UpAxisSign = upAxisSign;
        FrontAxis = frontAxis;
        FrontAxisSign = frontAxisSign;
        CoordAxis = coordAxis;
        CoordAxisSign = coordAxisSign;
        OriginalUpAxis = originalUpAxis;
        Notes = notes ?? Array.Empty<string>();
    }
}