A data container representing one source animation file conversion request for the retargeter pipeline. It holds source bytes, filename, optional skeleton companion bytes, import options, mapping overrides, solver options and various output flags used by the conversion pipeline.
#nullable enable annotations
using System;
using System.Collections.Generic;
using HumanoidRetargeter.Core.Cleanup;
using HumanoidRetargeter.Core.Formats;
using HumanoidRetargeter.Core.Mapping;
using HumanoidRetargeter.Core.Solve;
using HumanoidRetargeter.Core.Target;
namespace HumanoidRetargeter.Core;
/// <summary>
/// One source animation file to retarget (engine-agnostic: bytes in, no file IO). Every
/// request runs its OWN profile detection, so a single batch may mix Mixamo + ActorCore +
/// BVH sources — unless <see cref="MappingOverride"/> supplies a mapping explicitly.
/// </summary>
[Alias( "HumanoidRetargeter.RetargetRequest" )]
public sealed class RetargetRequest
{
/// <summary>Solver choice for this request's clips. <see cref="SolverKind.DeepLearning"/>
/// requires the batch's <see cref="RetargetTargetSpec.DlWeights"/> to be set; the
/// conversion fails per-clip with a clear error otherwise.</summary>
public SolverKind Solver { get; init; } = SolverKind.Geometric;
/// <summary>Raw bytes of the source file (.fbx, .bvh, .glb, .gltf, .vrm, .anm, .an5 or .cba).</summary>
public required byte[] SourceData { get; init; }
/// <summary>
/// Source file name (used for the report and DMX provenance). The extension drives the
/// format choice (<c>.fbx</c> / <c>.bvh</c> / <c>.glb</c> / <c>.gltf</c> / <c>.vrm</c> —
/// a VRM is a glTF container whose authored humanoid bone map becomes the mapping — /
/// <c>.anm</c> / <c>.an5</c> RenderWare animations and <c>.cba</c> EA ANT packages,
/// which additionally need <see cref="SkeletonData"/>); when the extension is unknown the content is sniffed
/// (FBX binary magic / "FBXHeaderExtension" / BVH "HIERARCHY" / GLB 'glTF' magic /
/// glTF JSON / RenderWare 0x1B animation chunk / ANTSTM3b).
/// </summary>
public required string SourceFileName { get; init; }
/// <summary>
/// Raw bytes of a companion SKELETON file for formats whose animation files carry no
/// skeleton of their own: RenderWare <c>.anm</c>/<c>.an5</c> sources require the
/// character model's <c>.dff</c>; EA ANT <c>.cba</c> sources require an ordered joint-table
/// JSON (either the root array or an extracted <c>{ "joints": [...] }</c> wrapper).
/// Callers resolve the companion file; the facade does no file IO. Ignored by
/// self-contained formats. A request without its required companion fails clearly.
/// </summary>
public byte[]? SkeletonData { get; init; }
/// <summary>
/// Optional external-buffer reader for plain <c>.gltf</c> input. IO-owning callers
/// resolve the URI relative to the document; GLB and data-URI glTF leave this null.
/// </summary>
public Func<string, byte[]>? ExternalBufferResolver { get; init; }
/// <summary>
/// Caller-supplied identity of this request, echoed verbatim on every produced
/// <see cref="ClipResult.SourceId"/> so callers can join results back to their own
/// entries unambiguously (e.g. the editor window passes the FULL file path here, since
/// two files in different folders may share the same <see cref="SourceFileName"/>).
/// Null = <see cref="SourceFileName"/>.
/// </summary>
public string? SourceId { get; init; }
/// <summary>
/// Import sample rate the source clips are resampled to (BVH native frames / FBX curves
/// are evaluated on this grid). Null = the importer default (30 fps).
/// </summary>
public float? SampleFps { get; init; }
/// <summary>
/// Restricts the conversion to ONE take of the source file (0-based index into the
/// imported scene's clips). Null = convert all takes. Out of range fails the request's
/// clip result with a clear error (the batch continues). UI listings that expand a
/// multi-take file into one entry per take submit one request per selected take.
/// When <see cref="ClipDefinitions"/> is set this index addresses the DEFINITIONS
/// instead (each definition is what a UI row represents then).
/// </summary>
public int? TakeIndex { get; init; }
/// <summary>
/// Optional external clip definitions, parsed from a Unity <c><file>.fbx.meta</c>
/// sidecar (<see cref="UnityMeta.ParseClipAnimations"/>): Unity animation packs ship FBX
/// files whose clips are sub-ranges of ONE source timeline. When set (non-empty), the
/// conversion produces one output clip per definition instead of one per take: the
/// definition's take (matched by <see cref="ExternalClipDef.TakeName"/>, falling back to
/// the file's first take) is sliced to the definition's native-frame range
/// (<see cref="UnityMeta.Slice"/>), named <see cref="ExternalClipDef.Name"/> (sanitized
/// like take names, collision-suffixed across the batch) and looped per
/// <see cref="ExternalClipDef.Loop"/> unless <see cref="LoopingOverride"/> is set.
/// <see cref="TakeIndex"/> then indexes INTO this list. Null = no definitions.
/// </summary>
public IReadOnlyList<ExternalClipDef>? ClipDefinitions { get; init; }
/// <summary>
/// UI-supplied mapping (manual mapping table or a user preset loaded Editor-side).
/// Null = auto-detect per request: preset profiles via <see cref="ProfileDetector"/>,
/// then the <see cref="AutoMapper"/> as best-effort fallback.
/// </summary>
public MappingResult? MappingOverride { get; init; }
/// <summary>Solver tunables (hip scales, finger transfer). ClipIndex/ClipName are managed
/// by the pipeline per take and ignored here. Null = defaults.</summary>
public SolveOptions? Solve { get; init; }
/// <summary>
/// Root-motion handling. <see cref="RootMotionMode.Extract"/> on a target without a
/// dedicated animated root bone (the s&box rig: pelvis is parentless, root_IK is
/// IkBaked) leaves the frames untouched and instead sets the ExtractMotion flag on the
/// clip's vmdl AnimFile entry — Source 2's compile-time extraction replaces the missing
/// bone-level extraction. <see cref="RootMotionMode.InPlace"/> always operates on the
/// hips directly.
/// </summary>
public RootMotionMode RootMotion { get; init; } = RootMotionMode.Off;
/// <summary>Run the Kovar foot-plant cleanup pass on the solved frames (default on).</summary>
public bool FootPlantCleanup { get; init; } = true;
/// <summary>
/// Copy the source clip's per-frame LOCAL translations onto same-named target bones
/// (hips and its ancestors excluded — trajectory stays solver-owned). For SAME-RIG
/// conversions of authored takes (a target FBX's own embedded animations): the solver
/// pins every non-hips bone to its rest translation, silently dropping a Biped take's
/// animated spine/thigh translations (~19cm of authored body sway on a death fall).
/// Meaningless across different rigs — leave off (default) for real retargets.
/// </summary>
public bool PreserveSourceTranslations { get; init; }
/// <summary>
/// Optional arm end-effector IK pass pulling the wrists onto limb-length-normalized
/// source hand positions. Default OFF: the geometric solver already matches anatomical
/// directions, so arm IK only helps reach-critical work (props, contact poses) and can
/// otherwise disturb elbow styling.
/// </summary>
public bool ArmEffectorIk { get; init; }
/// <summary>
/// Generate <c>AE_FOOTSTEP</c> AnimEvent nodes on each produced clip's vmdl AnimFile
/// entry (default OFF). After solving and cleanup, settled contacts following foot
/// lifts are detected on the SOLVED target clip, including in-place locomotion. Each
/// touchdown becomes one footstep event, in the exact node
/// shape the shipped citizen data uses (see <see cref="Target.FootstepEvents"/>).
/// Skipped (with a report note) when the target rig lacks complete leg chains.
/// </summary>
public bool GenerateFootstepEvents { get; init; }
/// <summary>
/// Additionally produce a mirrored twin of every converted clip (default OFF), named
/// <c><clip>_M</c> (collision-suffixed across the batch as usual). Mirroring runs
/// in TARGET space on the solved clip (<see cref="Solve.ClipMirror"/>): left/right role
/// bone channels swap and everything is reflected across the target character's sagittal
/// plane; IK-baked helper bones are re-baked from the mirrored body afterwards.
/// </summary>
public bool CreateMirroredVariant { get; init; }
/// <summary>
/// Additionally register an additive (delta) twin of every converted clip in the
/// generated/augmented vmdl (default OFF), named <c><clip>_delta</c> (the shipped
/// citizen naming; collision-suffixed across the batch as usual). The twin is a second
/// AnimFile entry REUSING the clip's DMX with an <c>AnimSubtract</c> child
/// (<c>anim_name</c> = the base sequence, <c>frame</c> = <see cref="AdditiveReferenceFrame"/>) — the shipped
/// <c>IdleLayer_01</c>/<c>IdleLayer_01_delta</c> pattern, where resourcecompiler
/// subtracts the reference frame at compile time (no frame math happens here). The
/// resulting <c>_delta</c> sequence is what s&box layered animation additively
/// blends on top of a base pose.
/// </summary>
public bool CreateAdditiveVariant { get; init; }
/// <summary>Reference pose in the sampled output clip (zero-based). Defaults to its
/// first frame; choose a neutral pose for the intended additive layer.</summary>
public int AdditiveReferenceFrame { get; init; }
/// <summary>Output clip name override; with multiple takes an index suffix is appended.
/// Null = the source take name.</summary>
public string? ClipNameOverride { get; init; }
/// <summary>Force the looping flag on the output sequence(s); null = the source clip's flag.</summary>
public bool? LoopingOverride { get; init; }
}