s&box Package Code Search

Search C# source code, UI razor templates, shaders, and configs across s&box packages.

Showing code results for query: * (331 total matches found)
notpointless.chomnr_humanoid_mocap / HumanoidMocap/Cleanup/TwistBoneFollow.cs
Game library
#nullable enable annotations

using System;
using System.Collections.Generic;
using System.Numerics;
using HumanoidMocap.Mapping;
using HumanoidMocap.Maths;
using HumanoidMocap.Target;

namespace HumanoidMocap.Cleanup;

using Vector3 = System.Numerics.Vector3; // s&box compat: shadow engine's global-namespace Vector3 (see Code/HumanoidMocap/Assembly.cs)

/// <summary>
/// Drives unmapped limb deform bones from the joints whose motion they distribute.
/// Auto-rigged exports (Auto-Rig Pro <c>forearm_twist.l</c>, AdvancedSkeleton
/// <c>ElbowPart1_L</c>, Biped <c>Bip01 L ForeTwist</c>) spread limb roll across helper
/// bones the game constrains at runtime; a baked retarget that leaves them at rest
/// candy-wraps the skin — the reported wrist "spike fans" when the hand pronates.
/// </summary>
/// <remarks>
/// Detection is geometric, name-free: an UNMAPPED bone whose parent is a mapped limb
/// bone (upper/lower arm or leg) and whose rest position lies ON the segment from that
/// parent to the parent's mapped chain child (within 15° of the axis, fraction
/// 0.05..1.1 along it). Each detected twist follows the chain child's per-frame local
/// ROLL — the twist component of its rotation delta about the limb axis — scaled by the
/// twist's fractional position (a bone at 60% of the forearm takes 60% of the hand's
/// roll; ARP's proximal <c>arm_twist</c> at fraction ~0 correctly takes ~none). Pure
/// swing carries no twist component, so elbows/knees bending never move these bones.
/// Serial deform bones between two mapped limb joints are handled separately: their
/// world-space motion delta is interpolated between the endpoints while both mapped
/// endpoint transforms remain unchanged. This covers rigs that split each bend/twist
/// section into two weighted bones without relying on exporter-specific names. An
/// unmapped sibling at the mapped joint's same pivot follows its complete rotation;
/// this covers dual control/deform rigs where a mechanism forearm/femur drives the next
/// joint while coincident anatomical bones carry the skin.
/// </remarks>
public static class TwistBoneFollow
{
    private static readonly (BoneRole Parent, BoneRole Child)[] Segments =
    {
        (BoneRole.ClavicleL, BoneRole.UpperArmL),
        (BoneRole.UpperArmL, BoneRole.LowerArmL), (BoneRole.LowerArmL, BoneRole.HandL),
        (BoneRole.ClavicleR, BoneRole.UpperArmR),
        (BoneRole.UpperArmR, BoneRole.LowerArmR), (BoneRole.LowerArmR, BoneRole.HandR),
        (BoneRole.Hips, BoneRole.UpperLegL),
        (BoneRole.UpperLegL, BoneRole.LowerLegL), (BoneRole.LowerLegL, BoneRole.FootL),
        (BoneRole.Hips, BoneRole.UpperLegR),
        (BoneRole.UpperLegR, BoneRole.LowerLegR), (BoneRole.LowerLegR, BoneRole.FootR),
    };

    private readonly record struct InlineBone(int Bone, int Parent, int Child, float Fraction);

    private readonly record struct FullFollower(int Bone, int Driver);

    /// <summary>Applies the pass in place; returns how many limb helpers were driven.</summary>
    public static int Apply(
        IReadOnlyList<XForm[]> frames, TargetRig rig, IReadOnlySet<int>? excluded)
    {
        ArgumentNullException.ThrowIfNull(frames);
        ArgumentNullException.ThrowIfNull(rig);
        var skeleton = rig.Skeleton;

        var twists = new List<(int Bone, int Driver, Vector3 Axis, float Fraction)>();
        var fullFollowers = new List<FullFollower>();
        var fullFollowerBones = new HashSet<int>();
        foreach (var (parentRole, childRole) in Segments)
        {
            if (rig.BoneForRole(parentRole) is not { } parent
                || rig.BoneForRole(childRole) is not { } child
                || skeleton[child].ParentIndex != parent)
                continue;

            // Limb axis and length in the PARENT's local space (the chain child's rest
            // local translation).
            var axis = skeleton[child].RestLocal.Pos;
            var length = axis.Length();
            if (length < 1e-3f)
                continue;
            axis /= length;

            for (var i = 0; i < skeleton.Count; i++)
            {
                if (i == child || skeleton[i].ParentIndex != parent
                    || rig.RoleOf(i) is not null || excluded?.Contains(i) == true)
                    continue;
                var pos = skeleton[i].RestLocal.Pos;
                // Blender control/deform exports commonly put a mechanism joint and one
                // or more skinned anatomical joints at the same pivot (MCH_forearm beside
                // radius/ulna, MCH_femur beside femur). The mapped mechanism drives the
                // next joint, but its deform siblings need the complete bend and roll;
                // treating them as ordinary twist bones copies roll only and leaves the
                // mesh behind while the hand/leg moves away.
                if ((pos - skeleton[child].RestLocal.Pos).Length()
                    <= MathF.Max(0.01f, length * 0.01f))
                {
                    if (fullFollowerBones.Add(i))
                        fullFollowers.Add(new FullFollower(i, child));
                    continue;
                }
                var along = Vector3.Dot(pos, axis);
                var fraction = along / length;
                if (fraction is < 0.05f or > 1.1f)
                    continue;
                var offAxis = (pos - axis * along).Length();
                if (offAxis > MathF.Tan(15f * MathF.PI / 180f) * MathF.Max(along, 1e-3f))
                    continue;
                twists.Add((i, child, axis, Math.Clamp(fraction, 0f, 1f)));
            }
        }
        var inline = FindInlineBones(rig, excluded);
        if (twists.Count == 0 && inline.Count == 0 && fullFollowers.Count == 0)
            return 0;

        foreach (var frame in frames)
        {
            foreach (var follower in fullFollowers)
            {
                // Both bones share a parent, so the driver's local-space rotation delta
                // can be applied directly while retaining the deform bone's bind offset.
                var delta = MathQ.Normalize(frame[follower.Driver].Rot
                    * Quaternion.Conjugate(skeleton[follower.Driver].RestLocal.Rot));
                frame[follower.Bone] = new XForm(
                    frame[follower.Bone].Pos,
                    MathQ.Normalize(delta * skeleton[follower.Bone].RestLocal.Rot));
            }
            foreach (var (bone, driver, axis, fraction) in twists)
            {
                // The driver's rotation delta from rest, in the shared parent's space,
                // forced to the SHORTEST arc (W >= 0) so the twist angle below is
                // continuous in (-180°, 180°) and never flips representation.
                var delta = MathQ.Normalize(
                    frame[driver].Rot * Quaternion.Conjugate(skeleton[driver].RestLocal.Rot));
                if (delta.W < 0f)
                    delta = new Quaternion(-delta.X, -delta.Y, -delta.Z, -delta.W);
                // Twist component about the limb axis (swing-twist decomposition).
                var proj = Vector3.Dot(new Vector3(delta.X, delta.Y, delta.Z), axis);
                // Ill-conditioned when the delta approaches a pure 180° SWING (both the
                // axis projection and W collapse toward 0): the decomposition then
                // amplifies noise into huge fake rolls — measured on a throw clip, the
                // kicking foot injected ±99° into the calf twist bone and the calf skin
                // flipped upward ("the leg is up"). Keep rest instead.
                var conditioning = MathF.Sqrt(proj * proj + delta.W * delta.W);
                if (conditioning < 0.2f)
                    continue;
                var angle = 2f * MathF.Atan2(proj, delta.W);
                var scaled = Quaternion.CreateFromAxisAngle(axis, angle * fraction);
                frame[bone] = new XForm(
                    frame[bone].Pos, MathQ.Normalize(scaled * skeleton[bone].RestLocal.Rot));
            }

            if (inline.Count > 0)
                FollowInlineBones(frame, skeleton, inline);
        }
        return twists.Count + inline.Count + fullFollowers.Count;
    }

    private static List<InlineBone> FindInlineBones(
        TargetRig rig, IReadOnlySet<int>? excluded)
    {
        var skeleton = rig.Skeleton;
        var result = new List<InlineBone>();
        var seen = new HashSet<int>();
        foreach (var (parentRole, childRole) in Segments)
        {
            if (rig.BoneForRole(parentRole) is not { } parent
                || rig.BoneForRole(childRole) is not { } child)
                continue;

            var path = new List<int>();
            for (var bone = skeleton[child].ParentIndex;
                 bone >= 0 && bone != parent;
                 bone = skeleton[bone].ParentIndex)
                path.Add(bone);
            if (path.Count == 0
                || skeleton[path[^1]].ParentIndex != parent
                || path.Any(bone => rig.RoleOf(bone) is not null
                    || excluded?.Contains(bone) == true))
                continue;
            path.Reverse();

            var length = 0f;
            var previous = parent;
            foreach (var bone in path.Append(child))
            {
                length += (skeleton.RestWorld[bone].Pos
                    - skeleton.RestWorld[previous].Pos).Length();
                previous = bone;
            }
            if (length < 1e-3f)
                continue;

            var along = 0f;
            previous = parent;
            foreach (var bone in path)
            {
                along += (skeleton.RestWorld[bone].Pos
                    - skeleton.RestWorld[previous].Pos).Length();
                if (seen.Add(bone))
                    result.Add(new InlineBone(bone, parent, child, along / length));
                previous = bone;
            }
        }
        return result;
    }

    private static void FollowInlineBones(
        XForm[] frame, Skeleton.Skeleton skeleton, IReadOnlyList<InlineBone> inline)
    {
        var world = new Skeleton.Pose(frame).ToWorld(skeleton);
        var desired = world.ToArray();
        var pathBones = new HashSet<int>();

        foreach (var group in inline.GroupBy(entry => (entry.Parent, entry.Child)))
        {
            var parent = group.Key.Parent;
            var child = group.Key.Child;
            var parentDelta = MathQ.Normalize(world[parent].Rot
                * Quaternion.Conjugate(skeleton.RestWorld[parent].Rot));
            var childDelta = MathQ.Normalize(world[child].Rot
                * Quaternion.Conjugate(skeleton.RestWorld[child].Rot));
            if (Quaternion.Dot(parentDelta, childDelta) < 0f)
                childDelta = new Quaternion(
                    -childDelta.X, -childDelta.Y, -childDelta.Z, -childDelta.W);

            foreach (var entry in group)
            {
                var delta = MathQ.Normalize(Quaternion.Slerp(
                    parentDelta, childDelta, entry.Fraction));
                desired[entry.Bone] = new XForm(
                    world[entry.Bone].Pos,
                    MathQ.Normalize(delta * skeleton.RestWorld[entry.Bone].Rot));
                pathBones.Add(entry.Bone);
            }
            // Compensate the mapped endpoint locally so its already-solved world transform
            // remains exact after its intermediary parent starts following the motion.
            pathBones.Add(child);
        }

        foreach (var bone in pathBones.OrderBy(index => index))
        {
            var parent = skeleton[bone].ParentIndex;
            frame[bone] = parent < 0
                ? desired[bone]
                : XForm.ToLocal(desired[parent], desired[bone]);
        }
    }
}
notpointless.chomnr_humanoid_mocap / HumanoidMocap/Formats/Gltf/GltfModelDmxWriter.cs
Game library
#nullable enable annotations

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Numerics;
using System.Text;
using System.Text.Json;
using HumanoidMocap.Formats.Dmx;
using HumanoidMocap.Formats.Fbx;
using HumanoidMocap.Maths;
using HumanoidMocap.Skeleton;
using SkeletonModel = HumanoidMocap.Skeleton.Skeleton;

namespace HumanoidMocap.Formats.Gltf;

using Matrix4x4 = System.Numerics.Matrix4x4;
using Quaternion = System.Numerics.Quaternion;
using Vector2 = System.Numerics.Vector2;
using Vector3 = System.Numerics.Vector3;

/// <summary>
/// Converts the skinned meshes in a glTF/GLB model to Source 2 model-DMX. ModelDoc does
/// not accept glTF as a RenderMeshFile, while DMX preserves the same skeleton, vertices,
/// materials and four-weight skinning without an external converter.
/// </summary>
public static class GltfModelDmxWriter
{
    private const float MetersToCentimeters = 100f;

    /// <summary>Writes a Y-up, centimeter model-DMX for the already imported target rig.</summary>
    public static string Write(
        byte[] data, SkeletonModel skeleton, string name,
        Func<string, byte[]>? externalBufferResolver = null)
    {
        ArgumentNullException.ThrowIfNull(data);
        ArgumentNullException.ThrowIfNull(skeleton);
        ArgumentNullException.ThrowIfNull(name);

        var document = GltfDocument.Parse(data, externalBufferResolver);
        var parts = ReadMeshParts(document, skeleton);
        if (parts.Count == 0)
            throw new FormatException("glTF contains no supported mesh primitives.");
        return Emit(skeleton, name, parts);
    }

    private sealed class MeshPart
    {
        public required string Name;
        public required string Material;
        public required Vector3[] Positions;
        public required Vector3[] Normals;
        public required Vector2[] TexCoords;
        public required int[] Triangles;
        public required float[] Weights;
        public required int[] Joints;
    }

    private static List<MeshPart> ReadMeshParts(GltfDocument document, SkeletonModel skeleton)
    {
        var root = document.Root;
        if (!root.TryGetProperty("nodes", out var nodeArray)
            || !root.TryGetProperty("meshes", out var meshArray))
            return new List<MeshPart>();

        root.TryGetProperty("skins", out var skinArray);
        root.TryGetProperty("materials", out var materialArray);
        var worlds = NodeWorlds(document);
        var bonesByName = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
        for (var i = 0; i < skeleton.Count; i++)
            bonesByName[skeleton[i].Name] = i;

        var parts = new List<MeshPart>();
        var usedNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
        for (var nodeIndex = 0; nodeIndex < nodeArray.GetArrayLength(); nodeIndex++)
        {
            var node = nodeArray[nodeIndex];
            if (!node.TryGetProperty("mesh", out var meshProperty))
                continue;
            var meshIndex = meshProperty.GetInt32();
            if (meshIndex < 0 || meshIndex >= meshArray.GetArrayLength())
                throw new FormatException($"glTF node {nodeIndex} references invalid mesh {meshIndex}.");
            var mesh = meshArray[meshIndex];
            if (!mesh.TryGetProperty("primitives", out var primitives))
                continue;

            var skinIndex = node.TryGetProperty("skin", out var skinProperty)
                ? skinProperty.GetInt32() : -1;
            var skinJoints = MapSkinJoints(
                document, skeleton, bonesByName, skinArray, skinIndex);
            var skinTransforms = SkinTransforms(document, skinArray, skinIndex, worlds, skinJoints, skeleton);
            var normalMatrix = NormalMatrix(worlds[nodeIndex]);
            var primitiveIndex = 0;
            foreach (var primitive in primitives.EnumerateArray())
            {
                if (!primitive.TryGetProperty("attributes", out var attributes)
                    || !attributes.TryGetProperty("POSITION", out var positionProperty))
                {
                    primitiveIndex++;
                    continue;
                }

                var positions = new Accessor(document, positionProperty.GetInt32(), 3);
                var vertexCount = positions.Count;
                if (vertexCount == 0)
                {
                    primitiveIndex++;
                    continue;
                }

                var transformedPositions = new Vector3[vertexCount];
                ReadSkinning(document, attributes, vertexCount, skinJoints,
                    out var weights, out var joints);
                var vertexTransforms = new Matrix4x4[vertexCount];
                for (var i = 0; i < vertexCount; i++)
                {
                    var transform = worlds[nodeIndex];
                    if (skinTransforms is not null)
                    {
                        transform = default;
                        for (var influence = 0; influence < 4; influence++)
                        {
                            var at = i * 4 + influence;
                            if (weights[at] > 0f)
                                transform += skinTransforms[joints[at]] * weights[at];
                        }
                    }
                    vertexTransforms[i] = transform;
                    var value = new Vector3(
                        positions.Float(i, 0), positions.Float(i, 1), positions.Float(i, 2));
                    transformedPositions[i] = Vector3.Transform(value, transform)
                        * MetersToCentimeters;
                }

                var rawIndices = ReadIndices(document, primitive, vertexCount);
                var mode = primitive.TryGetProperty("mode", out var modeProperty)
                    ? modeProperty.GetInt32() : 4;
                var triangles = Triangulate(rawIndices, mode);

                var normals = new Vector3[vertexCount];
                if (attributes.TryGetProperty("NORMAL", out var normalProperty))
                {
                    var source = new Accessor(document, normalProperty.GetInt32(), 3);
                    RequireCount(source, vertexCount, "NORMAL");
                    for (var i = 0; i < vertexCount; i++)
                    {
                        var value = new Vector3(
                            source.Float(i, 0), source.Float(i, 1), source.Float(i, 2));
                        var transform = skinTransforms is null ? normalMatrix : NormalMatrix(vertexTransforms[i]);
                        normals[i] = NormalizeOr(Vector3.TransformNormal(value, transform), Vector3.UnitY);
                    }
                }
                else
                {
                    GenerateNormals(transformedPositions, triangles, normals);
                }

                var texCoords = new Vector2[vertexCount];
                if (attributes.TryGetProperty("TEXCOORD_0", out var texCoordProperty))
                {
                    var source = new Accessor(document, texCoordProperty.GetInt32(), 2);
                    RequireCount(source, vertexCount, "TEXCOORD_0");
                    for (var i = 0; i < vertexCount; i++)
                        texCoords[i] = new Vector2(source.Float(i, 0), source.Float(i, 1));
                }

                var baseName = node.TryGetProperty("name", out var nodeName)
                    ? nodeName.GetString()
                    : mesh.TryGetProperty("name", out var meshName) ? meshName.GetString() : null;
                var partName = UniqueName(
                    Sanitize(baseName ?? $"mesh_{meshIndex}") + $"_{primitiveIndex}", usedNames);
                parts.Add(new MeshPart
                {
                    Name = partName,
                    Material = MaterialName(materialArray, primitive),
                    Positions = transformedPositions,
                    Normals = normals,
                    TexCoords = texCoords,
                    Triangles = triangles,
                    Weights = weights,
                    Joints = joints,
                });
                primitiveIndex++;
            }
        }
        return parts;
    }

    private static Matrix4x4[] NodeWorlds(GltfDocument document)
    {
        var result = new Matrix4x4[document.Nodes.Count];
        var state = new byte[document.Nodes.Count];

        Matrix4x4 Visit(int index)
        {
            if (state[index] == 2)
                return result[index];
            if (state[index] == 1)
                throw new FormatException("glTF node graph contains a cycle.");
            state[index] = 1;
            var node = document.Nodes[index];
            var local = Matrix4x4.CreateScale(node.Scale)
                * Matrix4x4.CreateFromQuaternion(node.Rotation)
                * Matrix4x4.CreateTranslation(node.Translation);
            result[index] = node.Parent < 0 ? local : local * Visit(node.Parent);
            state[index] = 2;
            return result[index];
        }

        for (var i = 0; i < result.Length; i++)
            Visit(i);
        return result;
    }

    private static Matrix4x4 NormalMatrix(Matrix4x4 world)
    {
        if (!Matrix4x4.Invert(world, out var inverse))
            return Matrix4x4.Identity;
        return Matrix4x4.Transpose(inverse);
    }

    // Bake the authored skin into the node rest pose before DMX generates new inverse
    // binds. glTF skinned vertices use inverseBind * jointWorld, NOT meshNodeWorld.
    // Keeping the full matrices here also bakes inherited scale into the rigid DMX rig.
    private static Dictionary<int, Matrix4x4>? SkinTransforms(
        GltfDocument document, JsonElement skins, int skinIndex,
        Matrix4x4[] worlds, int[] mappedJoints, SkeletonModel skeleton)
    {
        if (mappedJoints.Length == 0)
            return null;
        var skin = skins[skinIndex];
        var nodes = skin.GetProperty("joints");
        var inverseBinds = skin.TryGetProperty("inverseBindMatrices", out var property)
            ? new Accessor(document, property.GetInt32(), 16) : null;
        if (inverseBinds is not null)
            RequireCount(inverseBinds, mappedJoints.Length, "inverseBindMatrices");
        var result = new Dictionary<int, Matrix4x4>();
        for (var i = 0; i < mappedJoints.Length; i++)
        {
            var inverse = Matrix4x4.Identity;
            if (inverseBinds is not null)
                inverse = ReadMatrix(inverseBinds, i);
            // The target can use the authored skin bind instead of the posed scene TRS.
            // Rebind the mesh to that exact skeleton; retain scale omitted by XForm.
            Matrix4x4.Decompose(worlds[nodes[i].GetInt32()], out var scale, out _, out _);
            var rest = skeleton.RestWorld[mappedJoints[i]];
            var jointWorld = Matrix4x4.CreateScale(scale)
                * Matrix4x4.CreateFromQuaternion(rest.Rot)
                * Matrix4x4.CreateTranslation(rest.Pos / MetersToCentimeters);
            result[mappedJoints[i]] = inverse * jointWorld;
        }
        return result;
    }

    internal static SkeletonModel WithSkinBindPose(GltfDocument document, SkeletonModel skeleton)
    {
        if (!document.Root.TryGetProperty("skins", out var skins))
            return skeleton;
        var byName = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
        for (var i = 0; i < skeleton.Count; i++)
            byName[Sanitize(skeleton[i].Name)] = i;
        var binds = new Dictionary<int, XForm>();
        var nodeWorlds = NodeWorlds(document);
        for (var s = 0; s < skins.GetArrayLength(); s++)
        {
            if (!skins[s].TryGetProperty("inverseBindMatrices", out var property))
                continue;
            var joints = MapSkinJoints(document, skeleton, byName, skins, s);
            var accessor = new Accessor(document, property.GetInt32(), 16);
            RequireCount(accessor, joints.Length, "inverseBindMatrices");
            for (var j = 0; j < joints.Length; j++)
            {
                if (!Matrix4x4.Invert(ReadMatrix(accessor, j), out var matrix))
                    throw new FormatException("glTF skin has a singular inverse bind matrix.");
                // Some exporters fold a bind-shape scale into these matrices. Those
                // are valid for skinning but cannot replace the scene's rigid rest.
                // Zero-offset scaffold joints do not describe the character's scale.
                if (skeleton[joints[j]].RestLocal.Pos.LengthSquared() > 1e-6f)
                {
                    Matrix4x4.Decompose(matrix, out var bindScale, out _, out _);
                    var node = skins[s].GetProperty("joints")[j].GetInt32();
                    Matrix4x4.Decompose(nodeWorlds[node], out var sceneScale, out _, out _);
                    if (Vector3.Distance(bindScale, sceneScale) > .001f * sceneScale.Length())
                        return skeleton;
                }
                var bind = FbxTransform.ToRigid(matrix);
                bind.Pos *= MetersToCentimeters;
                if (binds.TryGetValue(joints[j], out var previous)
                    && (Vector3.Distance(previous.Pos, bind.Pos) > .01f
                        || MathQ.AngleBetween(previous.Rot, bind.Rot) > .001f))
                    return skeleton; // Different per-mesh bind spaces cannot define one rig rest.
                binds[joints[j]] = bind;
            }
        }
        if (binds.Count == 0)
            return skeleton;
        // Bind matrices may use an origin below the displayed scene. Preserve the
        // scene's floor placement (glTF is Y-up), rather than burying the new rest.
        var sceneFloor = float.PositiveInfinity;
        var bindFloor = float.PositiveInfinity;
        foreach (var pair in binds)
        {
            sceneFloor = MathF.Min(sceneFloor, skeleton.RestWorld[pair.Key].Pos.Y);
            bindFloor = MathF.Min(bindFloor, pair.Value.Pos.Y);
        }
        var placement = new Vector3(0f, sceneFloor - bindFloor, 0f);
        var world = new XForm[skeleton.Count];
        var definitions = new List<BoneDefinition>();
        for (var i = 0; i < skeleton.Count; i++)
        {
            var bone = skeleton[i];
            var parent = bone.ParentIndex;
            world[i] = binds.TryGetValue(i, out var bind) ? bind
                : parent < 0 ? bone.RestLocal : XForm.Compose(world[parent], bone.RestLocal);
            if (binds.ContainsKey(i))
                world[i].Pos += placement;
            var local = parent < 0 ? world[i] : XForm.ToLocal(world[parent], world[i]);
            definitions.Add(new BoneDefinition(bone.Name, parent < 0 ? null : skeleton[parent].Name, local));
        }
        return SkeletonModel.Create(definitions);
    }

    private static Matrix4x4 ReadMatrix(Accessor source, int i) => new(
        source.Float(i, 0), source.Float(i, 1), source.Float(i, 2), source.Float(i, 3),
        source.Float(i, 4), source.Float(i, 5), source.Float(i, 6), source.Float(i, 7),
        source.Float(i, 8), source.Float(i, 9), source.Float(i, 10), source.Float(i, 11),
        source.Float(i, 12), source.Float(i, 13), source.Float(i, 14), source.Float(i, 15));

    private static int[] MapSkinJoints(
        GltfDocument document, SkeletonModel skeleton, Dictionary<string, int> bonesByName,
        JsonElement skinArray, int skinIndex)
    {
        if (skinIndex < 0)
            return Array.Empty<int>();
        if (skinArray.ValueKind != JsonValueKind.Array || skinIndex >= skinArray.GetArrayLength())
            throw new FormatException($"glTF mesh references invalid skin {skinIndex}.");
        var skin = skinArray[skinIndex];
        if (!skin.TryGetProperty("joints", out var joints))
            return Array.Empty<int>();

        var result = new int[joints.GetArrayLength()];
        for (var i = 0; i < result.Length; i++)
        {
            var nodeIndex = joints[i].GetInt32();
            if (nodeIndex < 0 || nodeIndex >= document.Nodes.Count)
                throw new FormatException($"glTF skin references invalid joint node {nodeIndex}.");
            var raw = document.Nodes[nodeIndex].Name ?? $"node_{nodeIndex}";
            var safe = Sanitize(raw);
            if (!bonesByName.TryGetValue(safe, out var bone)
                && !bonesByName.TryGetValue(Sanitize(raw + "#" + nodeIndex), out bone))
            {
                throw new FormatException(
                    $"glTF skin joint '{raw}' is absent from the imported target skeleton.");
            }
            if (bone < 0 || bone >= skeleton.Count)
                throw new FormatException($"glTF skin joint '{raw}' mapped outside the target skeleton.");
            result[i] = bone;
        }
        return result;
    }

    private static void ReadSkinning(
        GltfDocument document, JsonElement attributes, int vertexCount, int[] skinJoints,
        out float[] weights, out int[] joints)
    {
        weights = new float[checked(vertexCount * 4)];
        joints = new int[checked(vertexCount * 4)];
        if (skinJoints.Length == 0
            || !attributes.TryGetProperty("JOINTS_0", out var jointProperty)
            || !attributes.TryGetProperty("WEIGHTS_0", out var weightProperty))
        {
            for (var i = 0; i < vertexCount; i++)
            {
                weights[i * 4] = 1f;
                joints[i * 4] = skinJoints.Length > 0 ? skinJoints[0] : 0;
            }
            return;
        }

        var jointSource = new Accessor(document, jointProperty.GetInt32(), 4);
        var weightSource = new Accessor(document, weightProperty.GetInt32(), 4);
        RequireCount(jointSource, vertexCount, "JOINTS_0");
        RequireCount(weightSource, vertexCount, "WEIGHTS_0");
        for (var vertex = 0; vertex < vertexCount; vertex++)
        {
            var total = 0f;
            for (var influence = 0; influence < 4; influence++)
            {
                var skinJoint = jointSource.Unsigned(vertex, influence);
                if (skinJoint < 0 || skinJoint >= skinJoints.Length)
                    throw new FormatException($"glTF JOINTS_0 references invalid skin joint {skinJoint}.");
                var at = vertex * 4 + influence;
                joints[at] = skinJoints[skinJoint];
                var weight = weightSource.Float(vertex, influence);
                weights[at] = float.IsFinite(weight) && weight > 0f ? weight : 0f;
                total += weights[at];
            }
            if (total <= 1e-8f)
            {
                weights[vertex * 4] = 1f;
                joints[vertex * 4] = skinJoints[0];
                continue;
            }
            for (var influence = 0; influence < 4; influence++)
                weights[vertex * 4 + influence] /= total;
        }
    }

    private static int[] ReadIndices(
        GltfDocument document, JsonElement primitive, int vertexCount)
    {
        if (!primitive.TryGetProperty("indices", out var indexProperty))
        {
            var sequential = new int[vertexCount];
            for (var i = 0; i < sequential.Length; i++)
                sequential[i] = i;
            return sequential;
        }

        var source = new Accessor(document, indexProperty.GetInt32(), 1);
        var result = new int[source.Count];
        for (var i = 0; i < result.Length; i++)
        {
            result[i] = source.Unsigned(i, 0);
            if (result[i] < 0 || result[i] >= vertexCount)
                throw new FormatException($"glTF index {result[i]} exceeds vertex count {vertexCount}.");
        }
        return result;
    }

    private static int[] Triangulate(int[] indices, int mode)
    {
        var triangles = new List<int>();
        if (mode == 4) // TRIANGLES
        {
            if (indices.Length % 3 != 0)
                throw new FormatException("glTF triangle index count is not divisible by three.");
            triangles.AddRange(indices);
        }
        else if (mode == 5) // TRIANGLE_STRIP
        {
            for (var i = 2; i < indices.Length; i++)
            {
                var a = indices[i - 2];
                var b = indices[i - 1];
                var c = indices[i];
                if ((i & 1) != 0)
                    (a, b) = (b, a);
                if (a != b && b != c && a != c)
                {
                    triangles.Add(a);
                    triangles.Add(b);
                    triangles.Add(c);
                }
            }
        }
        else if (mode == 6) // TRIANGLE_FAN
        {
            for (var i = 2; i < indices.Length; i++)
            {
                if (indices[0] == indices[i - 1] || indices[i - 1] == indices[i]
                    || indices[0] == indices[i])
                    continue;
                triangles.Add(indices[0]);
                triangles.Add(indices[i - 1]);
                triangles.Add(indices[i]);
            }
        }
        else
        {
            throw new FormatException($"glTF primitive mode {mode} is not a triangle mesh.");
        }
        return triangles.ToArray();
    }

    private static void GenerateNormals(Vector3[] positions, int[] triangles, Vector3[] normals)
    {
        for (var i = 0; i + 2 < triangles.Length; i += 3)
        {
            var a = triangles[i];
            var b = triangles[i + 1];
            var c = triangles[i + 2];
            var normal = Vector3.Cross(positions[b] - positions[a], positions[c] - positions[a]);
            normals[a] += normal;
            normals[b] += normal;
            normals[c] += normal;
        }
        for (var i = 0; i < normals.Length; i++)
            normals[i] = NormalizeOr(normals[i], Vector3.UnitY);
    }

    private static Vector3 NormalizeOr(Vector3 value, Vector3 fallback)
        => value.LengthSquared() > 1e-12f ? Vector3.Normalize(value) : fallback;

    private static void RequireCount(Accessor accessor, int expected, string semantic)
    {
        if (accessor.Count != expected)
            throw new FormatException(
                $"glTF {semantic} has {accessor.Count} entries; expected {expected}.");
    }

    private static string MaterialName(JsonElement materials, JsonElement primitive)
    {
        if (!primitive.TryGetProperty("material", out var materialProperty))
            return "default";
        var index = materialProperty.GetInt32();
        if (materials.ValueKind != JsonValueKind.Array || index < 0 || index >= materials.GetArrayLength())
            throw new FormatException($"glTF primitive references invalid material {index}.");
        var material = materials[index];
        return material.TryGetProperty("name", out var name) && !string.IsNullOrEmpty(name.GetString())
            ? name.GetString()!
            : $"material_{index}";
    }

    private static string Sanitize(string value)
    {
        var result = new StringBuilder(value.Length);
        foreach (var c in value)
            result.Append(char.IsLetterOrDigit(c) || c == '_' ? c : '_');
        return result.Length > 0 ? result.ToString() : "unnamed";
    }

    private static string UniqueName(string value, HashSet<string> used)
    {
        var candidate = value;
        var suffix = 2;
        while (!used.Add(candidate))
            candidate = value + "_" + suffix++;
        return candidate;
    }

    private sealed class Accessor
    {
        private readonly byte[] _buffer;
        private readonly int _start;
        private readonly int _stride;
        private readonly int _componentSize;
        private readonly int _componentType;
        private readonly bool _normalized;

        public int Count { get; }
        public int Components { get; }

        public Accessor(GltfDocument document, int index, int expectedComponents)
        {
            var root = document.Root;
            if (!root.TryGetProperty("accessors", out var accessors)
                || index < 0 || index >= accessors.GetArrayLength())
                throw new FormatException($"glTF accessor {index} does not exist.");
            var accessor = accessors[index];
            if (accessor.TryGetProperty("sparse", out _))
                throw new FormatException("Sparse glTF mesh accessors are not supported.");

            Components = accessor.GetProperty("type").GetString() switch
            {
                "SCALAR" => 1,
                "VEC2" => 2,
                "VEC3" => 3,
                "VEC4" => 4,
                "MAT4" => 16,
                var type => throw new FormatException($"Unsupported glTF accessor type '{type}'."),
            };
            if (Components != expectedComponents)
                throw new FormatException(
                    $"glTF accessor {index} has {Components} components; expected {expectedComponents}.");

            Count = accessor.GetProperty("count").GetInt32();
            if (Count < 0)
                throw new FormatException($"glTF accessor {index} has a negative count.");
            _componentType = accessor.GetProperty("componentType").GetInt32();
            _componentSize = _componentType switch
            {
                5120 or 5121 => 1,
                5122 or 5123 => 2,
                5125 or 5126 => 4,
                _ => throw new FormatException(
                    $"Unsupported glTF accessor component type {_componentType}."),
            };
            _normalized = accessor.TryGetProperty("normalized", out var normalized)
                && normalized.GetBoolean();

            if (!accessor.TryGetProperty("bufferView", out var viewProperty))
            {
                _buffer = Array.Empty<byte>();
                _start = 0;
                _stride = checked(Components * _componentSize);
                return;
            }

            var views = root.GetProperty("bufferViews");
            var viewIndex = viewProperty.GetInt32();
            if (viewIndex < 0 || viewIndex >= views.GetArrayLength())
                throw new FormatException($"glTF bufferView {viewIndex} does not exist.");
            var view = views[viewIndex];
            var bufferIndex = view.GetProperty("buffer").GetInt32();
            if (bufferIndex < 0 || bufferIndex >= document.Buffers.Count)
                throw new FormatException($"glTF buffer {bufferIndex} does not exist.");
            _buffer = document.Buffers[bufferIndex];
            var viewOffset = view.TryGetProperty("byteOffset", out var vo) ? vo.GetInt32() : 0;
            var accessorOffset = accessor.TryGetProperty("byteOffset", out var ao) ? ao.GetInt32() : 0;
            _start = checked(viewOffset + accessorOffset);
            var elementSize = checked(Components * _componentSize);
            _stride = view.TryGetProperty("byteStride", out var stride)
                ? stride.GetInt32() : elementSize;
            if (_stride < elementSize)
                throw new FormatException("glTF accessor stride is smaller than its element.");
            var end = Count == 0 ? _start : (long)_start + (long)(Count - 1) * _stride + elementSize;
            if (_start < 0 || end > _buffer.Length)
                throw new FormatException($"glTF accessor {index} reads beyond its buffer.");
        }

        public float Float(int element, int component)
        {
            if (_buffer.Length == 0)
                return 0f;
            var offset = Offset(element, component);
            return _componentType switch
            {
                5120 => _normalized
                    ? MathF.Max(unchecked((sbyte)_buffer[offset]) / 127f, -1f)
                    : unchecked((sbyte)_buffer[offset]),
                5121 => _normalized ? _buffer[offset] / 255f : _buffer[offset],
                5122 => _normalized
                    ? MathF.Max(BitConverter.ToInt16(_buffer, offset) / 32767f, -1f)
                    : BitConverter.ToInt16(_buffer, offset),
                5123 => _normalized
                    ? BitConverter.ToUInt16(_buffer, offset) / 65535f
                    : BitConverter.ToUInt16(_buffer, offset),
                5125 => BitConverter.ToUInt32(_buffer, offset),
                _ => BitConverter.ToSingle(_buffer, offset),
            };
        }

        public int Unsigned(int element, int component)
        {
            if (_buffer.Length == 0)
                return 0;
            var offset = Offset(element, component);
            return _componentType switch
            {
                5121 => _buffer[offset],
                5123 => BitConverter.ToUInt16(_buffer, offset),
                5125 => checked((int)BitConverter.ToUInt32(_buffer, offset)),
                _ => throw new FormatException(
                    $"glTF indices require an unsigned integer accessor, got {_componentType}."),
            };
        }

        private int Offset(int element, int component)
        {
            if (element < 0 || element >= Count || component < 0 || component >= Components)
                throw new FormatException("glTF accessor index is out of range.");
            return checked(_start + element * _stride + component * _componentSize);
        }
    }

    private static string Emit(SkeletonModel skeleton, string name, IReadOnlyList<MeshPart> parts)
    {
        var writer = new Kv2Writer();
        var modelId = Id(name, "model");
        var jointIds = new string[skeleton.Count];
        for (var i = 0; i < skeleton.Count; i++)
            jointIds[i] = Id(name, "joint:" + skeleton[i].Name);
        var dagIds = new string[parts.Count];
        var meshIds = new string[parts.Count];
        var vertexIds = new string[parts.Count];
        for (var i = 0; i < parts.Count; i++)
        {
            dagIds[i] = Id(name, $"dag:{i}:{parts[i].Name}");
            meshIds[i] = Id(name, $"mesh:{i}:{parts[i].Name}");
            vertexIds[i] = Id(name, $"vertices:{i}:{parts[i].Name}");
        }

        writer.Raw("<!-- dmx encoding keyvalues2_noids 4 format model 22 -->");
        writer.BeginTop("DmElement");
        writer.Attr("name", "string", "root");
        writer.Attr("model", "element", modelId);
        writer.Attr("skeleton", "element", modelId);
        writer.EndTop();

        writer.BeginTop("DmeModel");
        writer.Attr("id", "elementid", modelId);
        writer.Attr("name", "string", name);
        WriteTransform(writer, "transform", Vector3.Zero, Quaternion.Identity);
        writer.Attr("visible", "bool", "1");
        var children = new List<string>();
        for (var i = 0; i < skeleton.Count; i++)
            if (skeleton[i].ParentIndex < 0)
                children.Add(jointIds[i]);
        children.AddRange(dagIds);
        WriteRefs(writer, "children", children);
        WriteRefs(writer, "jointList", jointIds);
        writer.Attr("upAxis", "string", "Y");
        writer.BeginInline("axisSystem", "DmeAxisSystem");
        writer.Attr("upAxis", "int", "2");
        writer.Attr("forwardParity", "int", "2");
        writer.Attr("coordSys", "int", "0");
        writer.EndInline();
        writer.EndTop();

        for (var i = 0; i < skeleton.Count; i++)
        {
            var bone = skeleton[i];
            writer.BeginTop("DmeJoint");
            writer.Attr("id", "elementid", jointIds[i]);
            writer.Attr("name", "string", bone.Name);
            WriteTransform(writer, "transform", bone.RestLocal.Pos, bone.RestLocal.Rot);
            writer.Attr("visible", "bool", "1");
            var boneChildren = new List<string>();
            for (var child = 0; child < skeleton.Count; child++)
                if (skeleton[child].ParentIndex == i)
                    boneChildren.Add(jointIds[child]);
            if (boneChildren.Count > 0)
                WriteRefs(writer, "children", boneChildren);
            writer.EndTop();
        }

        for (var i = 0; i < parts.Count; i++)
        {
            var part = parts[i];
            writer.BeginTop("DmeDag");
            writer.Attr("id", "elementid", dagIds[i]);
            writer.Attr("name", "string", part.Name);
            WriteTransform(writer, "transform", Vector3.Zero, Quaternion.Identity);
            writer.Attr("shape", "element", meshIds[i]);
            writer.Attr("visible", "bool", "1");
            writer.EndTop();

            writer.BeginTop("DmeMesh");
            writer.Attr("id", "elementid", meshIds[i]);
            writer.Attr("name", "string", part.Name);
            writer.Attr("visible", "bool", "1");
            writer.Attr("currentState", "element", vertexIds[i]);
            WriteRefs(writer, "baseStates", new[] { vertexIds[i] });
            writer.BeginArray("faceSets");
            writer.BeginArrayElement("DmeFaceSet");
            writer.Attr("name", "string", part.Material);
            writer.BeginArray("faces", "int_array");
            for (var index = 0; index < part.Triangles.Length; index++)
            {
                writer.Value(part.Triangles[index].ToString(CultureInfo.InvariantCulture), false);
                if (index % 3 == 2)
                    writer.Value("-1", index == part.Triangles.Length - 1);
            }
            writer.EndArray();
            writer.BeginInline("material", "DmeMaterial");
            writer.Attr("name", "string", part.Material);
            writer.Attr("mtlName", "string", part.Material);
            writer.EndInline();
            writer.EndArrayElement(true);
            writer.EndArray();
            writer.EndTop();

            writer.BeginTop("DmeVertexData");
            writer.Attr("id", "elementid", vertexIds[i]);
            writer.Attr("name", "string", "bind");
            writer.BeginArray("vertexFormat", "string_array");
            var formats = new[]
                { "position$0", "normal$0", "texcoord$0", "blendweights$0", "blendindices$0" };
            for (var format = 0; format < formats.Length; format++)
                writer.Value(formats[format], format == formats.Length - 1);
            writer.EndArray();
            writer.Attr("jointCount", "int", "4");
            writer.Attr("flipVCoordinates", "bool", "0");
            WriteVectors(writer, "position$0", "vector3_array", part.Positions,
                value => Vec(value));
            WriteIdentityIndices(writer, "position$0Indices", part.Positions.Length);
            WriteVectors(writer, "normal$0", "vector3_array", part.Normals,
                value => Vec(value));
            WriteIdentityIndices(writer, "normal$0Indices", part.Normals.Length);
            WriteVectors(writer, "texcoord$0", "vector2_array", part.TexCoords,
                value => $"{F(value.X)} {F(value.Y)}");
            WriteIdentityIndices(writer, "texcoord$0Indices", part.TexCoords.Length);
            WriteScalars(writer, "blendweights$0", "float_array", part.Weights,
                value => F(value));
            WriteScalars(writer, "blendindices$0", "int_array", part.Joints,
                value => value.ToString(CultureInfo.InvariantCulture));
            writer.EndTop();
        }
        return writer.ToString();
    }

    private static void WriteTransform(
        Kv2Writer writer, string name, Vector3 position, Quaternion orientation)
    {
        writer.BeginInline(name, "DmeTransform");
        writer.Attr("name", "string", name);
        writer.Attr("position", "vector3", Vec(position));
        writer.Attr("orientation", "quaternion",
            $"{F(orientation.X)} {F(orientation.Y)} {F(orientation.Z)} {F(orientation.W)}");
        writer.Attr("scale", "float", "1");
        writer.EndInline();
    }

    private static void WriteRefs(Kv2Writer writer, string name, IReadOnlyList<string> ids)
    {
        writer.BeginArray(name);
        for (var i = 0; i < ids.Count; i++)
            writer.ElementRef(ids[i], i == ids.Count - 1);
        writer.EndArray();
    }

    private static void WriteVectors<T>(
        Kv2Writer writer, string name, string type, T[] values, Func<T, string> format)
    {
        writer.BeginArray(name, type);
        for (var i = 0; i < values.Length; i++)
            writer.Value(format(values[i]), i == values.Length - 1);
        writer.EndArray();
    }

    private static void WriteScalars<T>(
        Kv2Writer writer, string name, string type, T[] values, Func<T, string> format)
        => WriteVectors(writer, name, type, values, format);

    private static void WriteIdentityIndices(Kv2Writer writer, string name, int count)
    {
        writer.BeginArray(name, "int_array");
        for (var i = 0; i < count; i++)
            writer.Value(i.ToString(CultureInfo.InvariantCulture), i == count - 1);
        writer.EndArray();
    }

    private static string Id(string name, string path)
        => DmxWriter.ElementGuid(name, "gltf-model:" + path)
            .ToString("D", CultureInfo.InvariantCulture);

    private static string F(float value)
        => value == 0f ? "0" : ((double)value).ToString("0.##########", CultureInfo.InvariantCulture);

    private static string Vec(Vector3 value) => $"{F(value.X)} {F(value.Y)} {F(value.Z)}";

    private sealed class Kv2Writer
    {
        private readonly StringBuilder _text = new();
        private int _indent;

        public void Raw(string value) => _text.Append(value).Append("\r\n");

        private void Line(string value)
            => _text.Append('\t', _indent).Append(value).Append("\r\n");

        public void Attr(string name, string type, string value)
            => Line($"\"{Escape(name)}\" \"{type}\" \"{Escape(value)}\"");

        public void BeginTop(string type)
        {
            Line($"\"{type}\"");
            Line("{");
            _indent++;
        }

        public void EndTop()
        {
            _indent--;
            Line("}");
            _text.Append("\r\n");
        }

        public void BeginInline(string name, string type)
        {
            Line($"\"{Escape(name)}\" \"{type}\"");
            Line("{");
            _indent++;
        }

        public void EndInline()
        {
            _indent--;
            Line("}");
        }

        public void BeginArray(string name, string type = "element_array")
        {
            Line($"\"{Escape(name)}\" \"{type}\"");
            Line("[");
            _indent++;
        }

        public void EndArray()
        {
            _indent--;
            Line("]");
        }

        public void BeginArrayElement(string type)
        {
            Line($"\"{type}\"");
            Line("{");
            _indent++;
        }

        public void EndArrayElement(bool last)
        {
            _indent--;
            Line(last ? "}" : "},");
        }

        public void ElementRef(string id, bool last)
            => Line($"\"element\" \"{id}\"" + (last ? "" : ","));

        public void Value(string value, bool last)
            => Line($"\"{Escape(value)}\"" + (last ? "" : ","));

        private static string Escape(string value)
            => value.Replace("\\", "\\\\").Replace("\"", "\\\"")
                .Replace("\r", " ").Replace("\n", " ");

        public override string ToString() => _text.ToString();
    }
}
notpointless.chomnr_humanoid_mocap / HumanoidMocap/Formats/Renderware/RwDffSkeleton.cs
Game library
#nullable enable annotations

using System;
using System.Collections.Generic;
using System.Numerics;
using System.Text;
using HumanoidMocap.Maths;

namespace HumanoidMocap.Formats.Renderware;

using Vector3 = System.Numerics.Vector3; // s&box compat: shadow engine's global-namespace Vector3 (see Code/HumanoidMocap/Assembly.cs)

/// <summary>One HAnim node of a parsed .dff skeleton, in HAnim node order — the order
/// RwAnimAnimation keyframes address nodes in.</summary>
public sealed class RwDffNode
{
    /// <summary>HAnim node id (stable across FSB2 characters, e.g. 1000 = "Bip01").</summary>
    public required int NodeId { get; init; }

    /// <summary>
    /// Bone name: the frame's authored name when the .dff carries one (FSB2 stores 3ds Max
    /// Biped names like <c>Bip01 L Thigh</c> in the RpUserData extension), else the
    /// synthesized stable fallback <c>rw_node_&lt;id&gt;</c>. Unique within the skeleton.
    /// </summary>
    public required string Name { get; init; }

    /// <summary>Parent NODE index (into the node list), or -1 for the root node.</summary>
    public required int ParentIndex { get; init; }

    /// <summary>Rest (bind) transform relative to the parent NODE (intermediate non-HAnim
    /// frames composed in), native .dff units/axes.</summary>
    public required XForm RestLocal { get; init; }

    /// <summary>HAnim PUSH/POP hierarchy flags from the node table (diagnostic).</summary>
    public required uint Flags { get; init; }
}

/// <summary>Result of parsing a .dff model's skeleton.</summary>
public sealed class RwDffSkeletonData
{
    /// <summary>HAnim nodes in node-index order (== animation keyframe node order).</summary>
    public required IReadOnlyList<RwDffNode> Nodes { get; init; }

    /// <summary>Total FrameList frame count (HAnim and non-HAnim frames alike).</summary>
    public required int FrameCount { get; init; }
}

/// <summary>
/// RenderWare .dff model skeleton parser — reads ONLY the Clump's FrameList and the RpHAnim
/// plugin data (geometry is skipped entirely).
/// </summary>
/// <remarks>
/// <para><b>Layout</b> (verified against FSB2 <c>character.pak</c> models): Clump (0x10) →
/// struct (0x1) → FrameList (0xE) → struct (0x1) = {u32 numFrames, numFrames × 56-byte
/// frames {f32 rot[9] row-major 3x3, f32 pos[3], i32 parentIndex, u32 flags}}, then one
/// Extension (0x3) chunk PER frame containing optional sub-chunks: 0x11E = HAnimPLG
/// {u32 version, u32 nodeId, u32 numNodes, [u32 flags, u32 keyFrameSize,
/// numNodes × {u32 nodeId, u32 nodeIndex, u32 nodeFlags}]} (exactly one frame owns the full
/// node table), 0x11F = RpUserData (FSB2 stores the real 3ds Max bone name under the
/// <c>name</c> attribute — the classic frame-name chunk 0x253F2FE is present but empty).</para>
/// <para><b>Rotation matrices</b> are row-major with rows = the frame's basis vectors
/// (RenderWare right/up/at), i.e. row-vector convention <c>v_parent = v_child · M</c> —
/// the same convention as <see cref="Matrix4x4"/>, so
/// <see cref="Quaternion.CreateFromRotationMatrix"/> converts directly (verified: FK over
/// the FSB2 rig lands the feet at ground level and the head at ~184 cm).</para>
/// <para><b>Node order and parents</b>: the HAnim node table order IS the animation
/// keyframe node order. Each frame's own HAnimPLG carries its nodeId; a node's parent is
/// the nearest ancestor FRAME that is itself an HAnim node (intermediate plain frames —
/// FSB2 has one root dummy — are composed into the node's rest transform).</para>
/// </remarks>
public static class RwDffSkeleton
{
    /// <summary>Parses the skeleton (FrameList + HAnim) out of .dff bytes.</summary>
    /// <exception cref="FormatException">Malformed/truncated stream, or no HAnim data.</exception>
    public static RwDffSkeletonData Parse(byte[] data)
    {
        ArgumentNullException.ThrowIfNull(data);

        var (rootType, clumpStart, clumpSize) = RwStream.ReadChunk(data, 0, data.Length);
        if (rootType != RwStream.ChunkClump)
            throw new FormatException(
                $"Not a RenderWare model (.dff): expected a Clump chunk (0x10), found 0x{rootType:X}.");
        var clumpEnd = clumpStart + clumpSize;

        // Find the FrameList inside the clump (the clump struct precedes it; geometry
        // lists follow and are never visited — we stop at the first FrameList).
        var offset = clumpStart;
        while (offset < clumpEnd)
        {
            var (type, payloadStart, payloadSize) = RwStream.ReadChunk(data, offset, clumpEnd);
            if (type == RwStream.ChunkFrameList)
                return ParseFrameList(data, payloadStart, payloadStart + payloadSize);
            offset = payloadStart + payloadSize;
        }
        throw new FormatException("RenderWare .dff has no FrameList chunk — cannot read a skeleton.");
    }

    /// <summary>
    /// Cheap probe used by skeleton resolution: the HAnim node count of .dff bytes, or null
    /// when the bytes are not a parseable .dff with HAnim data. Never throws.
    /// </summary>
    public static int? PeekNodeCount(byte[] data)
    {
        try
        {
            return Parse(data).Nodes.Count;
        }
        catch (FormatException)
        {
            return null;
        }
    }

    // ================================================================ frame list

    private sealed class Frame
    {
        public required XForm Local;
        public required int Parent;
        public int NodeId = -1;         // HAnim node id, -1 when the frame has no HAnimPLG
        public string Name = "";
    }

    private static RwDffSkeletonData ParseFrameList(byte[] data, int start, int end)
    {
        var (structType, structStart, structSize) = RwStream.ReadChunk(data, start, end);
        if (structType != RwStream.ChunkStruct)
            throw new FormatException("RenderWare FrameList: expected leading struct chunk.");

        var frameCount = RwStream.I32(data, structStart);
        if (frameCount <= 0 || structSize < 4 + frameCount * 56)
            throw new FormatException($"RenderWare FrameList declares invalid frame count {frameCount}.");

        var frames = new List<Frame>(frameCount);
        for (var i = 0; i < frameCount; i++)
        {
            var p = structStart + 4 + i * 56;
            var local = ReadFrameTransform(data, p);
            var parent = RwStream.I32(data, p + 48);
            if (parent >= i || parent < -1)
                throw new FormatException(
                    $"RenderWare FrameList: frame {i} has invalid parent index {parent}.");
            frames.Add(new Frame { Local = local, Parent = parent });
        }

        // One Extension chunk per frame, in frame order.
        (int NodeId, int NodeIndex, uint Flags)[]? nodeTable = null;
        var offset = structStart + structSize;
        for (var i = 0; i < frameCount; i++)
        {
            var (extType, extStart, extSize) = RwStream.ReadChunk(data, offset, end);
            if (extType != RwStream.ChunkExtension)
                throw new FormatException(
                    $"RenderWare FrameList: expected Extension chunk for frame {i}, found 0x{extType:X}.");
            ParseFrameExtension(data, extStart, extStart + extSize, frames[i], ref nodeTable);
            offset = extStart + extSize;
        }

        if (nodeTable is null)
            throw new FormatException(
                "RenderWare .dff has no HAnim node table — the model carries no animatable skeleton.");

        return BuildNodes(frames, nodeTable, frameCount);
    }

    private static XForm ReadFrameTransform(byte[] data, int p)
    {
        // Row-major 3x3, rows = basis vectors (row-vector convention, see class remarks).
        var m = new Matrix4x4(
            RwStream.F32(data, p + 0), RwStream.F32(data, p + 4), RwStream.F32(data, p + 8), 0f,
            RwStream.F32(data, p + 12), RwStream.F32(data, p + 16), RwStream.F32(data, p + 20), 0f,
            RwStream.F32(data, p + 24), RwStream.F32(data, p + 28), RwStream.F32(data, p + 32), 0f,
            0f, 0f, 0f, 1f);
        var pos = new Vector3(
            RwStream.F32(data, p + 36), RwStream.F32(data, p + 40), RwStream.F32(data, p + 44));
        if (!float.IsFinite(pos.X) || !float.IsFinite(pos.Y) || !float.IsFinite(pos.Z))
            throw new FormatException("RenderWare FrameList: non-finite frame translation.");
        var rot = MathQ.Normalize(Quaternion.CreateFromRotationMatrix(m));
        if (!float.IsFinite(rot.X) || !float.IsFinite(rot.Y) || !float.IsFinite(rot.Z) || !float.IsFinite(rot.W))
            throw new FormatException("RenderWare FrameList: non-finite frame rotation.");
        return new XForm(pos, rot);
    }

    // ================================================================ extensions

    private static void ParseFrameExtension(
        byte[] data, int start, int end, Frame frame,
        ref (int NodeId, int NodeIndex, uint Flags)[]? nodeTable)
    {
        var offset = start;
        while (offset < end)
        {
            var (type, payloadStart, payloadSize) = RwStream.ReadChunk(data, offset, end);
            switch (type)
            {
                case RwStream.ChunkHAnimPlg:
                    ParseHAnim(data, payloadStart, payloadSize, frame, ref nodeTable);
                    break;
                case RwStream.ChunkUserDataPlg:
                    var userName = ReadUserDataName(data, payloadStart, payloadStart + payloadSize);
                    if (!string.IsNullOrEmpty(userName))
                        frame.Name = userName;
                    break;
                case RwStream.ChunkFrameName:
                    // Classic frame-name string chunk. FSB2 leaves these empty (the real
                    // names live in RpUserData) but honor them when present, without
                    // overriding an already-found user-data name.
                    if (frame.Name.Length == 0 && payloadSize > 0)
                        frame.Name = ReadCString(data, payloadStart, payloadSize);
                    break;
            }
            offset = payloadStart + payloadSize;
        }
    }

    private static void ParseHAnim(
        byte[] data, int start, int size, Frame frame,
        ref (int NodeId, int NodeIndex, uint Flags)[]? nodeTable)
    {
        if (size < 12)
            throw new FormatException("RenderWare HAnimPLG chunk is too small.");
        frame.NodeId = RwStream.I32(data, start + 4);
        var numNodes = RwStream.I32(data, start + 8);
        if (numNodes <= 0)
            return;
        if (size < 20 + numNodes * 12)
            throw new FormatException(
                $"RenderWare HAnimPLG node table truncated (numNodes={numNodes}, size={size}).");
        if (nodeTable is not null)
            throw new FormatException("RenderWare .dff carries more than one HAnim node table.");

        nodeTable = new (int, int, uint)[numNodes];
        for (var i = 0; i < numNodes; i++)
        {
            var p = start + 20 + i * 12;
            nodeTable[i] = (RwStream.I32(data, p), RwStream.I32(data, p + 4), RwStream.U32(data, p + 8));
        }
    }

    /// <summary>RpUserData: {u32 numAttrs, per attr {u32 nameLen, name, u32 format,
    /// u32 count, elements}}; format 3 = string elements {u32 len, chars}. Returns the
    /// first string value of the attribute named <c>name</c>, or "".</summary>
    private static string ReadUserDataName(byte[] data, int start, int end)
    {
        if (end - start < 4)
            return "";
        var attrCount = RwStream.I32(data, start);
        var offset = start + 4;
        for (var a = 0; a < attrCount; a++)
        {
            if (offset + 4 > end)
                return "";
            var nameLen = RwStream.I32(data, offset);
            offset += 4;
            if (nameLen < 0 || offset + nameLen > end)
                return "";
            var attrName = ReadCString(data, offset, nameLen);
            offset += nameLen;
            if (offset + 8 > end)
                return "";
            var format = RwStream.I32(data, offset);
            var elementCount = RwStream.I32(data, offset + 4);
            offset += 8;
            for (var e = 0; e < elementCount; e++)
            {
                switch (format)
                {
                    case 1: // int
                    case 2: // float
                        offset += 4;
                        break;
                    case 3: // string
                        if (offset + 4 > end)
                            return "";
                        var len = RwStream.I32(data, offset);
                        offset += 4;
                        if (len < 0 || offset + len > end)
                            return "";
                        if (attrName == "name")
                            return ReadCString(data, offset, len);
                        offset += len;
                        break;
                    default:
                        return ""; // unknown element format — cannot skip safely
                }
            }
        }
        return "";
    }

    private static string ReadCString(byte[] data, int start, int maxLen)
    {
        var len = 0;
        while (len < maxLen && data[start + len] != 0)
            len++;
        return Encoding.ASCII.GetString(data, start, len);
    }

    // ================================================================ node building

    private static RwDffSkeletonData BuildNodes(
        List<Frame> frames, (int NodeId, int NodeIndex, uint Flags)[] nodeTable, int frameCount)
    {
        // frame index by node id (each HAnim frame carries its own node id).
        var frameByNodeId = new Dictionary<int, int>(frames.Count);
        for (var i = 0; i < frames.Count; i++)
        {
            if (frames[i].NodeId >= 0 && !frameByNodeId.TryAdd(frames[i].NodeId, i))
                throw new FormatException(
                    $"RenderWare .dff: duplicate HAnim node id {frames[i].NodeId}.");
        }

        // The table's nodeIndex is the animation keyframe order — order by it.
        var ordered = new (int NodeId, uint Flags)[nodeTable.Length];
        var seen = new bool[nodeTable.Length];
        foreach (var (nodeId, nodeIndex, flags) in nodeTable)
        {
            if (nodeIndex < 0 || nodeIndex >= nodeTable.Length || seen[nodeIndex])
                throw new FormatException(
                    $"RenderWare HAnim node table has invalid/duplicate node index {nodeIndex}.");
            seen[nodeIndex] = true;
            ordered[nodeIndex] = (nodeId, flags);
        }

        var nodeIndexByFrame = new Dictionary<int, int>(nodeTable.Length);
        for (var n = 0; n < ordered.Length; n++)
        {
            if (!frameByNodeId.TryGetValue(ordered[n].NodeId, out var frameIndex))
                throw new FormatException(
                    $"RenderWare HAnim node id {ordered[n].NodeId} has no matching frame.");
            nodeIndexByFrame[frameIndex] = n;
        }

        var usedNames = new HashSet<string>(StringComparer.Ordinal);
        var nodes = new RwDffNode[ordered.Length];
        for (var n = 0; n < ordered.Length; n++)
        {
            var frameIndex = frameByNodeId[ordered[n].NodeId];

            // Parent = nearest ancestor frame that is itself an HAnim node; plain frames
            // in between are composed into the rest transform (world = parent ∘ local).
            var local = frames[frameIndex].Local;
            var parentFrame = frames[frameIndex].Parent;
            var parentNode = -1;
            while (parentFrame >= 0)
            {
                if (nodeIndexByFrame.TryGetValue(parentFrame, out var pn))
                {
                    parentNode = pn;
                    break;
                }
                local = XForm.Compose(frames[parentFrame].Local, local);
                parentFrame = frames[parentFrame].Parent;
            }
            if (parentNode >= n && parentNode != -1)
                throw new FormatException(
                    $"RenderWare HAnim node order is not parent-first (node {n} has parent node {parentNode}).");

            var name = frames[frameIndex].Name;
            if (string.IsNullOrEmpty(name))
                name = $"rw_node_{ordered[n].NodeId}";
            name = UniqueName(name, usedNames);

            nodes[n] = new RwDffNode
            {
                NodeId = ordered[n].NodeId,
                Name = name,
                ParentIndex = parentNode,
                RestLocal = local,
                Flags = ordered[n].Flags,
            };
        }

        return new RwDffSkeletonData { Nodes = nodes, FrameCount = frameCount };
    }

    private static string UniqueName(string name, HashSet<string> usedNames)
    {
        if (usedNames.Add(name))
            return name;
        for (var i = 2; ; i++)
        {
            var candidate = $"{name}#{i}";
            if (usedNames.Add(candidate))
                return candidate;
        }
    }
}
notpointless.chomnr_humanoid_mocap / HumanoidMocap/Inference/PalmDetectionFilter.cs
Game library
// Weighted suppression adapted from MediaPipe's NonMaxSuppressionCalculator.
// Copyright 2019 The MediaPipe Authors. Licensed under Apache-2.0.
// See Editor/HumanoidMocap/Inference/MediaPipe.LICENSE and THIRD_PARTY_NOTICES.md.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;

namespace HumanoidMocap.Inference;
using Vector2 = System.Numerics.Vector2;

public sealed record PalmDetection(float Score,float X,float Y,float Width,float Height,Vector2[] Points);

public static class PalmDetectionFilter
{
    /// <summary>Merge boxes and keypoints by detection score. Each cluster is
    /// compared with its highest-scoring original box, not its moving average.
    /// The seed score is retained; averaging does not create a confidence score.</summary>
    public static List<PalmDetection> Merge(IEnumerable<PalmDetection> candidates,int limit=2,float threshold=.3f)
    {
        var remaining=candidates.Where(p=>float.IsFinite(p.Score+p.X+p.Y+p.Width+p.Height)
            &&p.Score>0&&p.Width>0&&p.Height>0&&p.Points.All(v=>float.IsFinite(v.X+v.Y)))
            .OrderByDescending(p=>p.Score).ToList();
        var result=new List<PalmDetection>();
        while(remaining.Count>0&&result.Count<limit)
        {
            var seed=remaining[0];var rest=new List<PalmDetection>();
            var points=new Vector2[seed.Points.Length];float weight=0,xmin=0,ymin=0,xmax=0,ymax=0;
            foreach(var candidate in remaining)
            {
                if(Iou(seed,candidate)<=threshold){rest.Add(candidate);continue;}
                if(candidate.Points.Length!=points.Length)throw new ArgumentException("Palm keypoint counts differ.");
                var score=candidate.Score;weight+=score;
                xmin+=(candidate.X-candidate.Width/2)*score;ymin+=(candidate.Y-candidate.Height/2)*score;
                xmax+=(candidate.X+candidate.Width/2)*score;ymax+=(candidate.Y+candidate.Height/2)*score;
                for(var i=0;i<points.Length;i++)points[i]+=candidate.Points[i]*score;
            }
            if(weight<=0)break;
            xmin/=weight;ymin/=weight;xmax/=weight;ymax/=weight;
            for(var i=0;i<points.Length;i++)points[i]/=weight;
            result.Add(new(seed.Score,(xmin+xmax)/2,(ymin+ymax)/2,xmax-xmin,ymax-ymin,points));
            remaining=rest;
        }
        return result;
    }

    static float Iou(PalmDetection a,PalmDetection b)
    {
        var width=Math.Max(0,Math.Min(a.X+a.Width/2,b.X+b.Width/2)-Math.Max(a.X-a.Width/2,b.X-b.Width/2));
        var height=Math.Max(0,Math.Min(a.Y+a.Height/2,b.Y+b.Height/2)-Math.Max(a.Y-a.Height/2,b.Y-b.Height/2));
        var intersection=width*height;var union=a.Width*a.Height+b.Width*b.Height-intersection;
        return union>0?intersection/union:0;
    }
}
notpointless.chomnr_humanoid_mocap / HumanoidMocap/Motion/CaptureStanceProportion.cs
Game library
using System;
using System.Collections.Generic;
using System.Numerics;
using HumanoidMocap.Cleanup;
using HumanoidMocap.Mapping;
using HumanoidMocap.Maths;
using HumanoidMocap.Skeleton;
using HumanoidMocap.Target;

namespace HumanoidMocap.Motion;
using Vector3 = System.Numerics.Vector3;

/// <summary>Keeps a captured body's stance width when the target's hips are a different width.
/// Leg rotations copied onto a rig whose hip joints sit wider (relative to its legs) than the
/// performer's carry both feet outward by the extra half-width: on a reconstructed performer
/// with hip joints 0.15 leg-lengths apart and Human's 0.25, a 0.62 leg-length stance became 0.73
/// and read as splayed legs. Each ankle is moved back along the pelvis' own lateral axis by that
/// difference and the leg is re-solved, preserving bone lengths and the foot's world orientation.
/// Applied before ground alignment and foot anchoring. A proportion correction, not new capture.</summary>
public static class CaptureStanceProportion
{
    public sealed record Result(float HalfWidthCorrection,int Samples);
    public static Result Apply(List<XForm[]> frames,SourceScene source,MappingResult mapping,TargetRig target,FootChain left,FootChain right)
    {
        if(frames.Count==0)return new(0,0);
        var rig=target.Skeleton;var sourceRest=source.Skeleton.RestWorld;
        if(!mapping.RoleToBone.TryGetValue(BoneRole.UpperLegL,out var hipL)||!mapping.RoleToBone.TryGetValue(BoneRole.UpperLegR,out var hipR)||
            !mapping.RoleToBone.TryGetValue(BoneRole.LowerLegL,out var kneeL)||!mapping.RoleToBone.TryGetValue(BoneRole.FootL,out var footL))return new(0,0);
        var sourceLeg=Vector3.Distance(sourceRest[hipL].Pos,sourceRest[kneeL].Pos)+Vector3.Distance(sourceRest[kneeL].Pos,sourceRest[footL].Pos);
        var targetRest=rig.RestWorld;
        var targetLeg=Vector3.Distance(targetRest[left.Hip].Pos,targetRest[left.Knee].Pos)+Vector3.Distance(targetRest[left.Knee].Pos,targetRest[left.Ankle].Pos);
        if(!(sourceLeg>1e-4f)||!(targetLeg>1e-4f))return new(0,0);
        // Half the hip-joint spacing each skeleton has per unit of its own leg, in target units.
        var correction=(Vector3.Distance(targetRest[left.Hip].Pos,targetRest[right.Hip].Pos)/targetLeg
            -Vector3.Distance(sourceRest[hipL].Pos,sourceRest[hipR].Pos)/sourceLeg)*targetLeg*.5f;
        if(!float.IsFinite(correction)||MathF.Abs(correction)<targetLeg*.005f)return new(0,0);
        var world=new XForm[rig.Count];var samples=0;
        foreach(var frame in frames)
        {
            foreach(var (leg,other) in new[]{(left,right),(right,left)})
            {
                FkUtil.ToWorld(frame,rig,world);
                var lateral=world[leg.Hip].Pos-world[other.Hip].Pos;if(lateral.LengthSquared()<1e-8f)continue;
                lateral=Vector3.Normalize(lateral);
                var hip=world[leg.Hip];var knee=world[leg.Knee];var ankle=world[leg.Ankle];var footRotation=ankle.Rot;
                var goal=ankle.Pos-lateral*correction;
                // Never ask for more than the leg can reach; the foot keeps its direction from the hip.
                var reach=(Vector3.Distance(hip.Pos,knee.Pos)+Vector3.Distance(knee.Pos,ankle.Pos))*.9995f;
                var fromHip=goal-hip.Pos;if(fromHip.Length()>reach)goal=hip.Pos+Vector3.Normalize(fromHip)*reach;
                var bend=Vector3.Cross(knee.Pos-hip.Pos,ankle.Pos-knee.Pos);
                if(bend.LengthSquared()<1e-8f)bend=Vector3.Transform(Vector3.UnitX,hip.Rot);
                var ik=TwoBoneIk.Solve(hip.Pos,knee.Pos,ankle.Pos,goal,soften:0,stableBendAxis:bend);
                EffectorIk.ApplyWorldDeltas(frame,rig,leg.Hip,leg.Knee,leg.Ankle,ik.UpperWorldDelta,ik.LowerWorldDelta,world);
                FkUtil.ToWorld(frame,rig,world);
                var parent=rig[leg.Ankle].ParentIndex;
                frame[leg.Ankle].Rot=Quaternion.Normalize((parent<0?Quaternion.Identity:Quaternion.Inverse(world[parent].Rot))*footRotation);
                samples++;
            }
        }
        return new(correction,samples);
    }
}
notpointless.chomnr_humanoid_mocap / HumanoidMocap/Motion/TargetCorrections.cs
Game library
using System;
using System.Collections.Generic;
using System.Numerics;
using HumanoidMocap.Cleanup;
using HumanoidMocap.Mapping;
using HumanoidMocap.Maths;
using HumanoidMocap.Target;

namespace HumanoidMocap.Motion;
using Vector3 = System.Numerics.Vector3;

public sealed class TargetCorrectionSettings
{
    public bool FirstPerson { get; set; }
    /// <summary>Identifies the selected target geometry for explicitly authored finger contacts.</summary>
    public string ContactTargetKey { get; set; } = "";
    public Vector3 LeftShoulder { get; set; } = new(.18f,1.45f,0);
    public Vector3 RightShoulder { get; set; } = new(-.18f,1.45f,0);
    public Vector3 LeftElbow { get; set; } = new(.45f,1.1f,.15f);
    public Vector3 RightElbow { get; set; } = new(-.45f,1.1f,.15f);
    public float Reach { get; set; } = .995f;
    public float GroundOffset { get; set; }
    public float FacingDegrees { get; set; }
    public bool StabilizeFeet { get; set; } = true;
    /// <summary>Manual camera-space wrist position edits, applied before confirmed prop contacts and target IK.</summary>
    public List<WristPositionOffset> WristOffsets { get; set; } = new();
    /// <summary>Editable placement of a camera-relative hand capture in the target's
    /// Y-up metre frame. This is a user assumption, not recovered camera tracking.</summary>
    public Vector3 CaptureCameraPosition { get; set; } = new(0,1.65f,0);
    public float CaptureCameraYawDegrees { get; set; } = 180;
    public float CaptureCameraPitchDegrees { get; set; }
    /// <summary>The capture camera faced the performer instead of being worn by them. The placement
    /// above then describes a camera in front of the character, and a first-person preview should
    /// look from the character's own head rather than from that camera.</summary>
    public bool CaptureFacesSubject { get; set; }
    /// <summary>Shrink a hand capture toward the camera, by at most a quarter, when the
    /// target's arms are too short to reach it. Points keep their viewing rays, so the
    /// first-person picture is unchanged. Skipped while prop contacts share the capture space.</summary>
    public bool FitCaptureToArmReach { get; set; } = true;

    public static TargetCorrectionSettings ForRig(TargetRig rig,TargetUpAxis axis)
    {
        var scale=axis==TargetUpAxis.ZUpEngine?39.3700787f:100f;
        var rotation=axis==TargetUpAxis.YUpCm?Quaternion.Identity:Quaternion.CreateFromAxisAngle(Vector3.UnitX,-MathF.PI/2);
        Vector3 Position(BoneRole role,Vector3 fallback)=>rig.BoneForRole(role) is int b
            ?Vector3.Transform(rig.Skeleton.RestWorld[b].Pos/scale,rotation):fallback;
        var result=new TargetCorrectionSettings();
        result.LeftShoulder=Position(BoneRole.UpperArmL,result.LeftShoulder);
        result.RightShoulder=Position(BoneRole.UpperArmR,result.RightShoulder);
        var height=Math.Max(.2f,(result.LeftShoulder.Y+result.RightShoulder.Y)/2)/1.45f;
        result.LeftElbow=result.LeftShoulder+new Vector3(.27f,-.35f,.15f)*height;
        result.RightElbow=result.RightShoulder+new Vector3(-.27f,-.35f,.15f)*height;
        result.CaptureCameraPosition=Position(BoneRole.Head,new(0,1.55f,0))+Vector3.UnitY*.1f*height;
        // FPS viewmodels without a body use their authored origin as the assumed
        // camera position. A full-body eye-height fallback would lift these wrists
        // above the rig and exhaust arm reach before any captured movement.
        if(rig.BoneForRole(BoneRole.Head) is null&&rig.BoneForRole(BoneRole.Hips) is null)
        {
            result.CaptureCameraPosition=Vector3.Zero;
            // A viewmodel can face a different horizontal axis than a body rig.
            // Its left/right shoulder line supplies lateral direction; detached
            // hands can use their authored wrist spacing. This remains editable.
            var leftRole=rig.BoneForRole(BoneRole.UpperArmL) is not null?BoneRole.UpperArmL:BoneRole.HandL;
            var rightRole=rig.BoneForRole(BoneRole.UpperArmR) is not null?BoneRole.UpperArmR:BoneRole.HandR;
            if(rig.BoneForRole(leftRole) is not null&&rig.BoneForRole(rightRole) is not null)
            {
                var lateral=Position(leftRole,Vector3.Zero)-Position(rightRole,Vector3.Zero);lateral.Y=0;
                if(lateral.LengthSquared()>1e-8f)
                {
                    lateral=Vector3.Normalize(lateral);var forward=Vector3.Cross(lateral,Vector3.UnitY);
                    result.CaptureCameraYawDegrees=MathF.Atan2(-forward.X,-forward.Z)*180/MathF.PI;
                    float ArmLength(BoneRole upper,BoneRole lower,BoneRole hand)=>
                        rig.BoneForRole(upper) is not null&&rig.BoneForRole(lower) is not null&&rig.BoneForRole(hand) is not null
                        ?Vector3.Distance(Position(upper,Vector3.Zero),Position(lower,Vector3.Zero))+
                            Vector3.Distance(Position(lower,Vector3.Zero),Position(hand,Vector3.Zero)):0;
                    var armLength=Math.Max(ArmLength(BoneRole.UpperArmL,BoneRole.LowerArmL,BoneRole.HandL),
                        ArmLength(BoneRole.UpperArmR,BoneRole.LowerArmR,BoneRole.HandR));
                    var proportion=armLength>1e-4f?armLength/.6f:1f;
                    result.LeftElbow=result.LeftShoulder+(lateral*.27f-Vector3.UnitY*.35f+forward*.15f)*proportion;
                    result.RightElbow=result.RightShoulder+(-lateral*.27f-Vector3.UnitY*.35f+forward*.15f)*proportion;
                }
            }
        }
        return result;
    }
}

public static class TargetCorrections
{
    public static void Apply(List<XForm[]> frames,TargetRig rig,TargetUpAxis axis,TargetCorrectionSettings settings,
        IReadOnlyList<Dictionary<BoneRole,XForm>>? wristTargets=null)
    {
        var skeleton=rig.Skeleton;var world=new XForm[skeleton.Count];
        var left=new ArmConstraintSolver();var right=new ArmConstraintSolver();
        var scale=axis==TargetUpAxis.ZUpEngine?39.3700787f:100f;
        var conversion=axis==TargetUpAxis.YUpCm?Quaternion.Identity:Quaternion.CreateFromAxisAngle(Vector3.UnitX,MathF.PI/2);
        Vector3 Convert(Vector3 v)=>Vector3.Transform(v*scale,conversion);
        var up=axis==TargetUpAxis.YUpCm?Vector3.UnitY:Vector3.UnitZ;
        var yaw=Quaternion.CreateFromAxisAngle(up,settings.FacingDegrees*MathF.PI/180);
        for(var frameIndex=0;frameIndex<frames.Count;frameIndex++)
        {
            var frame=frames[frameIndex];
            if(!settings.FirstPerson&&wristTargets is null)
            {
                for(var i=0;i<frame.Length;i++)if(skeleton[i].ParentIndex<0)
                    frame[i]=new XForm(Vector3.Transform(frame[i].Pos,yaw)+up*settings.GroundOffset*scale,Quaternion.Normalize(yaw*frame[i].Rot));
                continue;
            }
            Solve(BoneRole.UpperArmL,BoneRole.LowerArmL,BoneRole.HandL,settings.LeftShoulder,settings.LeftElbow,left);
            Solve(BoneRole.UpperArmR,BoneRole.LowerArmR,BoneRole.HandR,settings.RightShoulder,settings.RightElbow,right);
            void Solve(BoneRole upperRole,BoneRole lowerRole,BoneRole handRole,Vector3 shoulder,Vector3 pole,ArmConstraintSolver solver)
            {
                if(rig.BoneForRole(handRole) is not int h)return;
                FkUtil.ToWorld(frame,skeleton,world);
                var originalWrist=world[h];
                if(wristTargets is not null&&!wristTargets[frameIndex].TryGetValue(handRole,out originalWrist))return;
                if(rig.BoneForRole(upperRole) is not int u || rig.BoneForRole(lowerRole) is not int l)
                {
                    if(wristTargets is not null)SetWorld(h,originalWrist);
                    return;
                }
                var upperLength=Vector3.Distance(world[u].Pos,world[l].Pos);var lowerLength=Vector3.Distance(world[l].Pos,world[h].Pos);
                var result=solver.Solve(originalWrist.Pos,originalWrist.Rot,new ArmSettings { Shoulder=Convert(shoulder),ElbowTarget=Convert(pole),UpperLength=upperLength,ForearmLength=lowerLength,MaximumReach=settings.Reach });
                var upperRotation=Quaternion.Normalize(MathQ.FromTo(world[l].Pos-world[u].Pos,result.Elbow-result.Shoulder)*world[u].Rot);
                SetWorld(u,new XForm(result.Shoulder,upperRotation));
                FkUtil.ToWorld(frame,skeleton,world);
                var lowerRotation=Quaternion.Normalize(MathQ.FromTo(world[h].Pos-world[l].Pos,result.Wrist-result.Elbow)*world[l].Rot);
                SetWorld(l,new XForm(world[l].Pos,lowerRotation));
                FkUtil.ToWorld(frame,skeleton,world);
                SetWorld(h,new XForm(world[h].Pos,originalWrist.Rot)); // preserve captured wrist attitude and finger locals
            }
            void SetWorld(int index,XForm value)
            {
                var parent=skeleton[index].ParentIndex;
                frame[index]=parent<0?value:XForm.Compose(world[parent].Inverse(),value);
            }
        }
    }
}
notpointless.chomnr_humanoid_mocap / HumanoidMocap/Skeleton/Clip.cs
Game library
#nullable enable annotations

using System;
using System.Collections.Generic;
using HumanoidMocap.Maths;

namespace HumanoidMocap.Skeleton;

/// <summary>
/// A sampled animation clip: a fixed-rate sequence of frames, each holding one
/// parent-relative local transform per bone (skeleton bone order). Clips are always
/// resampled at ingest — no key data is preserved.
/// </summary>
public sealed class Clip
{
    /// <summary>Clip (sequence) name.</summary>
    public string Name { get; }

    /// <summary>Sample rate in frames per second.</summary>
    public float Fps { get; }

    /// <summary>
    /// Native frame rate of the take in the SOURCE file (FBX GlobalSettings
    /// TimeMode/CustomFrameRate, BVH 1/FrameTime). External frame ranges — Unity
    /// <c>.fbx.meta</c> <c>clipAnimations</c> definitions — are expressed in THIS rate, so
    /// they must be rescaled by <c>Fps / NativeFps</c> to index the resampled
    /// <see cref="Frames"/>. Equals <see cref="Fps"/> when the importer records no native rate.
    /// </summary>
    public float NativeFps { get; }

    /// <summary>Whether the clip is authored to loop.</summary>
    public bool Looping { get; }

    /// <summary>Frames in playback order; each entry is one local transform per bone.</summary>
    public List<XForm[]> Frames { get; }

    /// <summary>Number of frames currently in the clip.</summary>
    public int FrameCount => Frames.Count;

    /// <summary>
    /// Clip duration in seconds at <see cref="Fps"/>: the time span between the first and the
    /// last sample, <c>(FrameCount - 1) / Fps</c> (frames are fence posts, intervals are the
    /// spans between them — matching the DMX timeFrame this clip serializes to). Zero for
    /// empty and single-frame clips.
    /// </summary>
    public float Duration => FrameCount <= 1 ? 0f : (FrameCount - 1) / Fps;

    /// <summary>Creates an empty clip.</summary>
    /// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="fps"/> is not positive.</exception>
    public Clip(string name, float fps, bool looping)
        : this(name, fps, looping, new List<XForm[]>())
    {
    }

    /// <summary>Creates a clip wrapping an existing frame list (not copied).</summary>
    /// <param name="nativeFps">Source-file native frame rate (<see cref="NativeFps"/>);
    /// null = same as <paramref name="fps"/>.</param>
    /// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="fps"/> (or a
    /// provided <paramref name="nativeFps"/>) is not positive.</exception>
    public Clip(string name, float fps, bool looping, List<XForm[]> frames, float? nativeFps = null)
    {
        ArgumentNullException.ThrowIfNull(name);
        ArgumentNullException.ThrowIfNull(frames);
        if (!(fps > 0f) || !float.IsFinite(fps))
            throw new ArgumentOutOfRangeException(nameof(fps), fps, "Fps must be a positive finite number.");
        if (nativeFps is { } native && (!(native > 0f) || !float.IsFinite(native)))
            throw new ArgumentOutOfRangeException(nameof(nativeFps), native, "NativeFps must be a positive finite number.");

        Name = name;
        Fps = fps;
        NativeFps = nativeFps ?? fps;
        Looping = looping;
        Frames = frames;
    }
}
notpointless.chomnr_humanoid_mocap / HumanoidMocap/Solve/RestNormalizer.cs
Game library
#nullable enable annotations

using System;
using System.Collections.Generic;
using System.Numerics;
using HumanoidMocap.Mapping;
using HumanoidMocap.Maths;
using SkeletonModel = HumanoidMocap.Skeleton.Skeleton;

namespace HumanoidMocap.Solve;

using Vector3 = System.Numerics.Vector3; // s&box compat: shadow engine's global-namespace Vector3 (see Code/HumanoidMocap/Assembly.cs)

/// <summary>
/// A skeleton's rest pose as explicit world transforms (indexed like the skeleton's bones).
/// Produced by <see cref="RestNormalizer"/>; feed it to
/// <see cref="CanonicalFrames.Build(SkeletonModel, MappingResult, IReadOnlyList{XForm})"/>.
/// </summary>
public sealed class RestPose
{
    /// <summary>Rest world transforms per bone (positions in cm).</summary>
    public XForm[] WorldRest { get; init; } = Array.Empty<XForm>();
}

/// <summary>
/// Rest-pose detection (T-pose / A-pose / I-pose) and normalization to a canonical T-pose.
/// Runs on <b>both</b> source and target rests before canonical frames are built, so deltas
/// measured against the normalized source rest apply cleanly to the normalized target rest
/// (the s&amp;box human rig itself rests in a strong A-pose, ~52° below horizontal).
/// </summary>
/// <remarks>
/// <para><b>Non-anatomical binds:</b> some exports (NVIDIA SOMA uniform-skeleton BVH) carry
/// a bind that is bone-length encoding, not a pose — every OFFSET runs along ±X, so identity
/// rest rotations collapse the figure into a stick (measured on the SOMA repro: character up
/// = world +X, thigh·up = +1.00/−1.00, thighs 180° apart — a thigh pointing at the
/// shoulders). Every anatomical bind in the corpus measures thigh·up ≤ −0.75 and ≤ 46°
/// between thighs (the posed Defenses.fbx stance included), so
/// <see cref="IsAnatomicalRest"/> separates the two with a wide margin. When the bind fails
/// the check, <see cref="Normalize(SkeletonModel, MappingResult, IReadOnlyList{XForm})"/>
/// rebuilds the rest from the supplied reference pose (the clip's first frame — a real pose
/// whose rotations carry the missing rest orientation); without a reference pose
/// normalization throws <see cref="ArgumentException"/> rather than build canonical frames
/// on a non-pose (callers that probe rest geometry already treat that as "skip").</para>
/// <para><b>Detection:</b> the angle of each (LowerArm.head − UpperArm.head) rest segment
/// against the character's horizontal lateral direction (per side, then averaged):
/// 0–15° → <see cref="DetectedPose.TPose"/>; 15–60° with the arm <i>below</i> horizontal →
/// <see cref="DetectedPose.APose"/>; 60–95° with the arms hanging predominantly <i>down</i>
/// (arm·up ≤ −0.5) → <see cref="DetectedPose.IPose"/> (relaxed N-pose rests — first-frame
/// rebuilt rests measure 64–88° below horizontal at arm·up −0.90…−1.00 across the corpus);
/// anything else → <see cref="DetectedPose.Other"/>. Legs are checked analogously against
/// vertical for wide stances.</para>
/// <para><b>Normalization (swing-only, hierarchical, per limb chain — never the spine):</b>
/// each arm segment is swung about its joint so the chain matches the canonical T-pose:
/// upper arm → ±lateral (exactly horizontal), forearm → ±lateral (straight arm), hand →
/// ±lateral; every swing rotates all descendant world rests about the joint (positions orbit
/// the joint, orientations are premultiplied), so segment lengths never change. Hand roll is
/// then resolved to the palm-down convention by rotating about the limb axis until the hand's
/// geometric dorsal normal (<see cref="HandGeometry.Dorsal"/>) aligns with character up.
/// Legs are only normalized (thigh and calf swung to exactly −up) when a wide stance
/// (&gt; 15° off vertical) is detected — normal rigs keep their slight natural leg splay.</para>
/// </remarks>
public static class RestNormalizer
{
    /// <summary>Rest-pose family detected from the arm rest angle.</summary>
    public enum DetectedPose
    {
        /// <summary>Arms within 15° of horizontal.</summary>
        TPose,

        /// <summary>Arms 15–60° below horizontal.</summary>
        APose,

        /// <summary>Arms hanging 60–95° below horizontal, predominantly downward (relaxed
        /// N-pose; typical for rests rebuilt from a clip's first frame).</summary>
        IPose,

        /// <summary>Anything else (arms raised, missing, or extreme poses).</summary>
        Other,
    }

    /// <summary>What detection and normalization found and did; surfaced in the mapping report.</summary>
    public sealed class RestReport
    {
        /// <summary>Detected rest-pose family.</summary>
        public DetectedPose Detected { get; set; } = DetectedPose.Other;

        /// <summary>Average upper-arm rest angle against the horizontal lateral direction,
        /// degrees (0 = perfect T-pose).</summary>
        public float UpperArmAngleDeg { get; set; } = float.NaN;

        /// <summary>True when the bind rest failed <see cref="IsAnatomicalRest"/> and the
        /// normalized rest was rebuilt from the caller's reference pose instead.</summary>
        public bool RebuiltFromReferencePose { get; set; }

        /// <summary>Human-readable notes: corrections applied, skipped steps, oddities.</summary>
        public List<string> Notes { get; } = new();
    }

    private const float TPoseMaxDeg = 15f;
    private const float APoseMaxDeg = 60f;
    private const float IPoseMaxDeg = 95f;
    private const float IPoseMaxUpDot = -0.5f;
    private const float WideStanceMinDeg = 15f;

    /// <summary>Plausibility cap on thigh·characterUp: a rest thigh pointing less than ~78°
    /// away from the shoulder direction is anatomically impossible. Corpus anatomical binds
    /// measure ≤ −0.75; the SOMA stick bind +1.00.</summary>
    private const float ThighMaxUpDot = 0.2f;

    /// <summary>Plausibility floor on thighL·thighR (cos 120°): rest thighs more than 120°
    /// apart are anatomically impossible. Corpus anatomical binds measure ≤ 46° apart
    /// (cos ≥ 0.69); the SOMA stick bind 180° (−1.00).</summary>
    private const float ThighPairMinDot = -0.5f;

    /// <summary>
    /// Detects the rest pose of <paramref name="skeleton"/> and returns a T-pose-normalized
    /// copy of its rest world transforms plus a report. The skeleton itself is not modified.
    /// </summary>
    /// <exception cref="ArgumentException">Thrown when the mapping lacks the bones the
    /// character frame needs (see <see cref="CharacterFrame.Compute"/>), or when the bind
    /// rest is not an anatomical pose (see <see cref="IsAnatomicalRest"/>) — without a
    /// reference pose there is nothing valid to normalize.</exception>
    public static (RestPose Normalized, RestReport Report) Normalize(SkeletonModel skeleton, MappingResult map)
        => Normalize(skeleton, map, referencePoseLocals: null);

    /// <summary>
    /// Like <see cref="Normalize(SkeletonModel, MappingResult)"/>, but when the bind rest is
    /// not an anatomical pose (see <see cref="IsAnatomicalRest"/> and the class remarks) the
    /// rest is rebuilt from <paramref name="referencePoseLocals"/> (parent-relative locals,
    /// indexed like the skeleton — pass the clip's first frame) before normalization.
    /// </summary>
    /// <exception cref="ArgumentException">As the two-argument overload; a non-anatomical
    /// bind only throws when <paramref name="referencePoseLocals"/> is null.</exception>
    public static (RestPose Normalized, RestReport Report) Normalize(
        SkeletonModel skeleton, MappingResult map, IReadOnlyList<XForm>? referencePoseLocals,
        Vector3? worldUp = null)
    {
        ArgumentNullException.ThrowIfNull(skeleton);
        ArgumentNullException.ThrowIfNull(map);

        var world = new XForm[skeleton.Count];
        for (var i = 0; i < skeleton.Count; i++)
            world[i] = skeleton.RestWorld[i];

        var report = new RestReport();
        if (!IsAnatomicalRest(skeleton, map, world))
        {
            if (referencePoseLocals is null)
            {
                throw new ArgumentException(
                    "Bind rest is not an anatomical humanoid pose (a rest thigh points toward "
                    + "the shoulders or the thighs are anti-parallel — e.g. a bone-length "
                    + "'stick' bind with identity rotations) and no reference pose is "
                    + "available to rebuild it.");
            }
            if (referencePoseLocals.Count != skeleton.Count)
            {
                throw new ArgumentException(
                    $"referencePoseLocals has {referencePoseLocals.Count} entries for a "
                    + $"{skeleton.Count}-bone skeleton.", nameof(referencePoseLocals));
            }

            for (var i = 0; i < skeleton.Count; i++)
            {
                var parent = skeleton[i].ParentIndex;
                world[i] = parent < 0
                    ? referencePoseLocals[i]
                    : XForm.Compose(world[parent], referencePoseLocals[i]);
            }
            report.RebuiltFromReferencePose = true;
            report.Notes.Add(
                "Bind rest is not an anatomical pose (bone-length stick bind); rest rebuilt "
                + "from the reference pose (clip first frame).");
        }

        // Arm/leg normalization never moves the hip or shoulder joints, so the character
        // frame computed on the input rest stays valid throughout.
        var cf = CharacterFrame.Compute(skeleton, map, world, worldUp);

        DetectArms(map, world, cf, report);
        NormalizeArms(skeleton, map, world, cf, report);
        NormalizeLegsIfWide(skeleton, map, world, cf, report);

        return (new RestPose { WorldRest = world }, report);
    }

    // ---------------------------------------------------------------- plausibility

    /// <summary>
    /// True when <paramref name="worldRest"/> is plausible as an anatomical humanoid pose:
    /// both rest thighs must point away from the shoulder line (thigh·up ≤
    /// <see cref="ThighMaxUpDot"/>) and be no more than 120° apart. Rigs without both
    /// complete thighs (or a shoulder anchor) are unjudgeable and pass. Measured margins:
    /// every anatomical corpus bind (T-pose, A-pose and the posed Defenses.fbx stance)
    /// scores thigh·up ≤ −0.75 / thighs ≤ 46° apart; the SOMA uniform-skeleton stick bind
    /// scores thigh·up +1.00 / 180° apart.
    /// </summary>
    public static bool IsAnatomicalRest(
        SkeletonModel skeleton, MappingResult map, IReadOnlyList<XForm> worldRest)
    {
        ArgumentNullException.ThrowIfNull(skeleton);
        ArgumentNullException.ThrowIfNull(map);
        ArgumentNullException.ThrowIfNull(worldRest);

        Vector3? Pos(BoneRole role)
            => map.RoleToBone.TryGetValue(role, out var i) && i < worldRest.Count
                ? worldRest[i].Pos
                : null;

        Vector3? Dir(BoneRole from, BoneRole to)
        {
            var a = Pos(from);
            var b = Pos(to);
            if (a is null || b is null)
                return null;
            var d = b.Value - a.Value;
            return d.LengthSquared() < 1e-8f ? null : Vector3.Normalize(d);
        }

        var hipL = Pos(BoneRole.UpperLegL);
        var hipR = Pos(BoneRole.UpperLegR);
        var thighL = Dir(BoneRole.UpperLegL, BoneRole.LowerLegL);
        var thighR = Dir(BoneRole.UpperLegR, BoneRole.LowerLegR);
        if (hipL is null || hipR is null || thighL is null || thighR is null)
            return true; // legs unmapped/degenerate: cannot judge, preserve behavior

        var midHips = (hipL.Value + hipR.Value) * 0.5f;
        var midShoulders = Midpoint(Pos(BoneRole.UpperArmL), Pos(BoneRole.UpperArmR))
            ?? Midpoint(Pos(BoneRole.ClavicleL), Pos(BoneRole.ClavicleR))
            ?? Pos(BoneRole.Neck);
        if (midShoulders is null)
            return true; // no shoulder anchor: cannot judge

        var upRaw = midShoulders.Value - midHips;
        if (upRaw.LengthSquared() < 1e-8f)
            return true;
        var up = Vector3.Normalize(upRaw);

        return Vector3.Dot(thighL.Value, up) <= ThighMaxUpDot
            && Vector3.Dot(thighR.Value, up) <= ThighMaxUpDot
            && Vector3.Dot(thighL.Value, thighR.Value) >= ThighPairMinDot;
    }

    private static Vector3? Midpoint(Vector3? a, Vector3? b)
        => a is not null && b is not null ? (a.Value + b.Value) * 0.5f : null;

    // ---------------------------------------------------------------- detection

    private static void DetectArms(MappingResult map, XForm[] world, CharacterFrame cf, RestReport report)
    {
        var angleSum = 0f;
        var upDotSum = 0f;
        var count = 0;
        var allBelowOrLevel = true;

        foreach (var (upper, lower, sign) in new[]
        {
            (BoneRole.UpperArmL, BoneRole.LowerArmL, 1f),
            (BoneRole.UpperArmR, BoneRole.LowerArmR, -1f),
        })
        {
            if (!map.RoleToBone.TryGetValue(upper, out var u) || !map.RoleToBone.TryGetValue(lower, out var l))
                continue;
            var dir = world[l].Pos - world[u].Pos;
            angleSum += Deg(MathQ.AngleBetween(dir, cf.Lateral * sign));
            count++;
            var upDot = Vector3.Dot(Vector3.Normalize(dir), cf.Up);
            upDotSum += upDot;
            // "Below horizontal" with a small tolerance so a T-pose arm 1° above still counts.
            allBelowOrLevel &= upDot < 0.05f;
        }

        if (count == 0)
        {
            report.Detected = DetectedPose.Other;
            report.Notes.Add("Upper/lower arms unmapped; rest pose undetectable, no arm normalization.");
            return;
        }

        var angle = angleSum / count;
        report.UpperArmAngleDeg = angle;
        report.Detected = angle <= TPoseMaxDeg
            ? DetectedPose.TPose
            : angle <= APoseMaxDeg && allBelowOrLevel
                ? DetectedPose.APose
                // Hanging arms read ~90° from lateral whether they point down OR forward;
                // the up-dot cap keeps forward-reaching binds out of the I-pose class.
                : angle <= IPoseMaxDeg && allBelowOrLevel && upDotSum / count <= IPoseMaxUpDot
                    ? DetectedPose.IPose
                    : DetectedPose.Other;
        report.Notes.Add(
            $"Arm rest angle {angle:F1} deg from horizontal -> {report.Detected}.");
    }

    // ---------------------------------------------------------------- arms

    private static void NormalizeArms(
        SkeletonModel skeleton, MappingResult map, XForm[] world, CharacterFrame cf, RestReport report)
    {
        foreach (var (side, left, sign) in new[] { ("L", true, 1f), ("R", false, -1f) })
        {
            if (!TryBone(map, "UpperArm" + side, out var upper) || !TryBone(map, "LowerArm" + side, out var lower))
                continue;
            var lateral = cf.Lateral * sign;

            // 1. Swing the whole arm so (elbow - shoulder) hits exactly ±lateral.
            SwingSegment(skeleton, world, upper, world[lower].Pos - world[upper].Pos, lateral);

            // 2. Re-measure and swing the forearm so (hand - elbow) is also ±lateral (straight
            //    arm; elbow flexion is not introduced or removed, only swing).
            var hasHand = TryBone(map, "Hand" + side, out var hand);
            if (hasHand)
                SwingSegment(skeleton, world, lower, world[hand].Pos - world[lower].Pos, lateral);

            // 3. Swing the hand along the limb axis using its anatomical chain-child point
            //    (midpoint of the mapped finger proximals).
            if (hasHand)
            {
                var knuckles = HandGeometry.FingerProximalMidpoint(map, world, left);
                if (knuckles is not null)
                    SwingSegment(skeleton, world, hand, knuckles.Value - world[hand].Pos, lateral);

                // 4. Roll: rotate about the (now lateral) limb axis until the geometric dorsal
                //    normal points up -> the canonical palm-down T-pose convention.
                var dorsal = HandGeometry.Dorsal(map, world, left);
                if (dorsal is not null)
                {
                    var rollDeg = RollAboutAxis(skeleton, world, hand, lateral, dorsal.Value, cf.Up);
                    report.Notes.Add($"Hand {side}: palm-down roll correction {rollDeg:F1} deg.");
                }
                else
                {
                    report.Notes.Add($"Hand {side}: fingers unmapped/degenerate, palm roll left as-is.");
                }
            }
        }
    }

    // ---------------------------------------------------------------- legs

    private static void NormalizeLegsIfWide(
        SkeletonModel skeleton, MappingResult map, XForm[] world, CharacterFrame cf, RestReport report)
    {
        var down = -cf.Up;
        foreach (var side in new[] { "L", "R" })
        {
            if (!TryBone(map, "UpperLeg" + side, out var upper) || !TryBone(map, "LowerLeg" + side, out var lower))
                continue;

            var angle = Deg(MathQ.AngleBetween(world[lower].Pos - world[upper].Pos, down));
            if (angle <= WideStanceMinDeg)
                continue; // normal stance: leave the natural leg splay untouched

            report.Notes.Add($"Leg {side}: wide stance ({angle:F1} deg off vertical), normalized to vertical.");
            SwingSegment(skeleton, world, upper, world[lower].Pos - world[upper].Pos, down);
            if (TryBone(map, "Foot" + side, out var foot))
                SwingSegment(skeleton, world, lower, world[foot].Pos - world[lower].Pos, down);
        }
    }

    // ---------------------------------------------------------------- mechanics

    private static bool TryBone(MappingResult map, string roleName, out int bone)
        => map.RoleToBone.TryGetValue(Enum.Parse<BoneRole>(roleName), out bone);

    /// <summary>
    /// Swings the subtree rooted at <paramref name="joint"/> by the shortest-arc rotation
    /// taking <paramref name="currentDir"/> onto <paramref name="targetDir"/>, pivoting at the
    /// joint's head: descendant positions orbit the joint, orientations are premultiplied.
    /// </summary>
    private static void SwingSegment(
        SkeletonModel skeleton, XForm[] world, int joint, Vector3 currentDir, Vector3 targetDir)
        => RotateSubtree(skeleton, world, joint, MathQ.FromTo(currentDir, targetDir), world[joint].Pos);

    /// <summary>
    /// Rotates the subtree at <paramref name="joint"/> about <paramref name="axis"/> (through
    /// the joint) by the signed angle that brings <paramref name="currentRef"/>, projected ⊥
    /// axis, onto <paramref name="targetRef"/> projected ⊥ axis. Returns the applied angle in
    /// degrees.
    /// </summary>
    private static float RollAboutAxis(
        SkeletonModel skeleton, XForm[] world, int joint, Vector3 axis, Vector3 currentRef, Vector3 targetRef)
    {
        var a = currentRef - axis * Vector3.Dot(currentRef, axis);
        var b = targetRef - axis * Vector3.Dot(targetRef, axis);
        if (a.LengthSquared() < 1e-8f || b.LengthSquared() < 1e-8f)
            return 0f;

        var angle = MathF.Atan2(Vector3.Dot(Vector3.Cross(a, b), axis), Vector3.Dot(a, b));
        RotateSubtree(skeleton, world, joint, Quaternion.CreateFromAxisAngle(axis, angle), world[joint].Pos);
        return Deg(angle);
    }

    private static void RotateSubtree(
        SkeletonModel skeleton, XForm[] world, int root, Quaternion rotation, Vector3 pivot)
    {
        // Bones are topologically sorted, so a single forward pass finds the whole subtree.
        Span<bool> inSubtree = skeleton.Count <= 512 ? stackalloc bool[skeleton.Count] : new bool[skeleton.Count];
        inSubtree[root] = true;
        for (var i = root; i < skeleton.Count; i++)
        {
            var parent = skeleton[i].ParentIndex;
            if (i != root && (parent < 0 || !inSubtree[parent]))
                continue;
            if (i != root)
                inSubtree[i] = true;
            world[i] = new XForm(
                pivot + Vector3.Transform(world[i].Pos - pivot, rotation),
                MathQ.Normalize(rotation * world[i].Rot));
        }
    }

    private static float Deg(float radians) => radians * (180f / MathF.PI);
}
notpointless.chomnr_humanoid_mocap / HumanoidMocap/Solve/SolveOptions.cs
Game library
#nullable enable annotations

using System.Collections.Generic;
using HumanoidMocap.Mapping;

namespace HumanoidMocap.Solve;

/// <summary>How a mapped role's rotation is transferred by the <see cref="GeometricSolver"/>.</summary>
public enum RoleTransferMode
{
    /// <summary>
    /// Absolute canonical-orientation matching: the target's animated chain direction is
    /// driven to <b>equal</b> the source's (in character-frame coordinates). Right for limbs
    /// and the spine — the pose IS the direction — but it also imposes the source rig's rest
    /// proportions/posture on roles whose rest directions legitimately differ between rigs.
    /// </summary>
    AbsoluteDirection,

    /// <summary>
    /// Rest-relative delta: the source's canonical-space rotation <i>delta from its own
    /// normalized rest</i> is replayed onto the <b>target's</b> normalized rest
    /// (<c>W_t(f) = C_t·ΔC(f)·C_t⁻¹·R_tgtNormRest</c> with
    /// <c>ΔC(f) = C_s⁻¹·ΔR(f)·C_s</c>). The target keeps its own rest carriage (shoulder
    /// line height, neck-base angle) and moves with the source. Identical to
    /// <see cref="AbsoluteDirection"/> when source and target rigs coincide.
    /// Clavicles use the delta relative to a shared mapped chest ancestor, then
    /// inherit the solved target chest motion, so body turns do not become shrugs.
    /// </summary>
    DeltaFromRest,

    /// <summary>
    /// Character-space delta: the source's world-rotation delta from its normalized rest is
    /// re-expressed in character coordinates and applied to the <b>target's</b> normalized
    /// rest (<c>W_t(f) = M·ΔR(f)·M⁻¹·R_tgtNormRest</c> with <c>M = Q_tgt·Q_src⁻¹</c>, the
    /// same character basis change <see cref="AbsoluteDirection"/> premultiplies). Like
    /// <see cref="DeltaFromRest"/> the target keeps its own rest carriage, but the delta
    /// keeps its <i>world</i> rotation axes instead of being remapped through the per-role
    /// canonical frames — the faithful replay when the rigs' rest chain directions diverge
    /// so far that canonical-axis remapping would tilt every rotation axis by that
    /// divergence (measured 23–44° on feet: CMU/ARP ankle anatomy vs the s&amp;box rig's
    /// steep ankle, where canonical remapping mis-pitched planted feet by up to 47°).
    /// Identical to the other modes when source and target rigs coincide.
    /// </summary>
    CharacterDeltaFromRest,
}

/// <summary>Options controlling a single retarget solve (one clip → one output clip).</summary>
public sealed class SolveOptions
{
    // The grounding pipeline uses world vertical for legs as well as pelvis travel.
    // Keep the standalone solver's character-relative direction contract unchanged.
    internal bool GroundedLegDirections { get; init; }

    /// <summary>
    /// Default per-role transfer modes: shoulder girdle and neck carriage are
    /// <see cref="RoleTransferMode.DeltaFromRest"/> (each rig's clavicle line / neck-base
    /// direction is rig anatomy, not pose — absolute matching was measured to drag the
    /// s&amp;box shoulders 6–28° toward the source's flatter/lower clavicle line and is the
    /// "low shoulders, hunched neck" artifact), and feet are
    /// <see cref="RoleTransferMode.CharacterDeltaFromRest"/> (a rest foot→toe direction is
    /// ankle anatomy too — rigs diverge 11–44° from the s&amp;box rig's steep ankle, so
    /// absolute matching pitched planted feet up to 25° off flat, the "feet bent
    /// upward/inward" artifact; the character-space delta keeps the rotation's world axes,
    /// which canonical-frame remapping would tilt by that same divergence). The head is
    /// <see cref="RoleTransferMode.CharacterDeltaFromRest"/> for the same reason: the rest
    /// neck→head direction is head-joint-placement anatomy (measured 0–27° forward lean
    /// across neutral-rest rigs vs the s&amp;box rig's 25.5°), so the target keeps its own
    /// neutral skull attitude and replays the source's attitude <i>changes</i> — for the
    /// head this computes exactly what the previous virtual-frame absolute matching did.
    /// Two solver fallbacks adjust these defaults per rig pair: on a toe-less source the
    /// foot entries become <see cref="RoleTransferMode.DeltaFromRest"/> (virtual-foot
    /// fallback), and a source whose normalized rest head attitude is implausible as a
    /// neutral carriage (a posed bind — e.g. a chin-down/tilted fighting-stance rest,
    /// measured 40.7° forward / 16.9° lateral on such a rig where the delta replay read
    /// ~12° "looking up at an angle") switches the head to
    /// <see cref="RoleTransferMode.AbsoluteDirection"/> so the gaze follows the source
    /// absolutely instead of replaying deltas from a posed reference (see the
    /// <see cref="GeometricSolver"/> remarks for both). Everything else (limbs, spine,
    /// toes, fingers) stays absolute: there the worldspace direction IS the pose.
    /// Full-body motion captures override the clavicle default with observed absolute
    /// directions: a body model's zero pose is not necessarily neutral shoulder carriage.
    /// </summary>
    public static IReadOnlyDictionary<BoneRole, RoleTransferMode> DefaultTransferModes { get; } =
        new Dictionary<BoneRole, RoleTransferMode>
        {
            [BoneRole.ClavicleL] = RoleTransferMode.DeltaFromRest,
            [BoneRole.ClavicleR] = RoleTransferMode.DeltaFromRest,
            [BoneRole.Neck] = RoleTransferMode.DeltaFromRest,
            [BoneRole.Head] = RoleTransferMode.CharacterDeltaFromRest,
            [BoneRole.FootL] = RoleTransferMode.CharacterDeltaFromRest,
            [BoneRole.FootR] = RoleTransferMode.CharacterDeltaFromRest,
        };

    /// <summary>
    /// Per-role transfer modes. Null (default) = <see cref="DefaultTransferModes"/> plus the
    /// solver's fallback heuristics (a toe-less source's virtual foot direction overrides
    /// the foot default to <see cref="RoleTransferMode.DeltaFromRest"/>, and a posed-rest
    /// source head overrides the head default to
    /// <see cref="RoleTransferMode.AbsoluteDirection"/> — see the
    /// <see cref="GeometricSolver"/> remarks). A non-null map REPLACES the defaults entirely
    /// and disables every fallback heuristic: each role uses exactly the mode in the map, and
    /// roles absent from it are <see cref="RoleTransferMode.AbsoluteDirection"/>. Pass an
    /// empty dictionary for fully absolute (legacy) behavior — API callers supplying a map
    /// opt out of all heuristics.
    /// </summary>
    public IReadOnlyDictionary<BoneRole, RoleTransferMode>? TransferModes { get; init; }

    // The motion-document path supplies reconstructed collarbone directions. Keep
    // other per-role default heuristics active, and honor explicit transfer modes.
    internal bool CaptureClavicleDirections { get; init; }

    /// <summary>
    /// Scale applied to the pelvis translation components perpendicular to the character up
    /// direction. Null (default) = automatic: target hip height / source hip height, both
    /// measured on the normalized rests.
    /// </summary>
    public float? HipScaleHorizontal { get; init; }

    /// <summary>
    /// Scale applied to the pelvis translation component along the character up direction.
    /// Null (default) = the same automatic hip-height ratio as <see cref="HipScaleHorizontal"/>.
    /// </summary>
    public float? HipScaleVertical { get; init; }

    /// <summary>Whether finger roles are transferred; when false, target finger bones keep
    /// their rest locals.</summary>
    public bool TransferFingers { get; init; } = true;

    /// <summary>Output clip name; null = the source clip's name.</summary>
    public string? ClipName { get; init; }

    /// <summary>Index of the source clip to retarget (<c>SourceScene.Clips</c>).</summary>
    public int ClipIndex { get; init; }
}
notpointless.chomnr_humanoid_mocap / InferenceWorker/CameraRotationTrack.cs
Game library
using System.Numerics;
using System.Runtime.InteropServices;
using HumanoidMocap.Inference;
using OpenCvSharp;

namespace HumanoidMocap.Worker;

/// <summary>Frame-to-frame rotation of a moving recording camera, in the role of GVHMR's
/// SimpleVO: background features outside the followed person are tracked between sampled
/// frames, a rotation is solved for each pair with the job's pinhole assumption, and the
/// chain is interpolated to every frame. Only rotation is estimated. Camera translation and
/// scene scale are not, so this is not camera tracking or world reconstruction.</summary>
public sealed class CameraRotationTrack : IDisposable
{
    public const string Version="background-rotation-v2";
    public const string FollowedPrefix="Moving recording camera followed:";
    const int WorkingWidth=640,Step=6,MinimumInliers=40;
    public sealed record Result(float[] AngularVelocity6d,int Pairs,int UsablePairs,float TotalDegrees,float LargestPairDegrees,float MeanInlierRatio=0)
    {
        /// <summary>Background that fits one rotation homography this well shows little parallax, so the
        /// camera turned about a nearly fixed point, as a standing operator's does. A camera that also
        /// travels leaves parallax, and its position is then unknown.</summary>
        public bool RotationOnly=>Usable&&MeanInlierRatio>=.8f;
        /// <summary>Usable only when nearly every sampled pair could be solved.</summary>
        public bool Usable=>Pairs>0&&UsablePairs>=Pairs*.8f;
        public string Diagnostic=>Usable
            ?FormattableString.Invariant($"{FollowedPrefix} camera rotation solved from background features for {UsablePairs}/{Pairs} sampled frame pairs, {TotalDegrees:F1} degrees in total and at most {LargestPairDegrees:F1} degrees per pair, and supplied to GVHMR in place of a still-camera assumption. {MeanInlierRatio*100:F0}% of background features fit a pure rotation, so the camera is treated as {(RotationOnly?"turning in place":"also travelling")}. Rotation only, from an assumed lens; camera translation and scale are not recovered.")
            :FormattableString.Invariant($"Moving recording camera could not be followed: only {UsablePairs}/{Pairs} sampled frame pairs had enough background features. The capture stays camera-relative.");
    }
    readonly float focal;Mat? previous;Rect2f previousBody;int frames;
    readonly List<(int Frame,Quaternion WorldToCamera)> samples=new();int pairs,usable;float largest,inlierRatios;
    /// <param name="focalLength">The job's pinhole focal length in source pixels.</param>
    public CameraRotationTrack(float focalLength){if(!(focalLength>0))throw new ArgumentOutOfRangeException(nameof(focalLength));focal=focalLength;}
    public void Add(DecodedVideoFrame frame,GvhmrDecoder.Box person,bool last)
    {
        var index=frames++;if(index%Step!=0&&!last)return;
        var scale=WorkingWidth/(float)frame.Width;
        using var rgba=new Mat(frame.Height,frame.Width,MatType.CV_8UC4);Marshal.Copy(frame.Rgba,0,rgba.Data,frame.Rgba.Length);
        using var full=new Mat();Cv2.CvtColor(rgba,full,ColorConversionCodes.RGBA2GRAY);
        var gray=new Mat();Cv2.Resize(full,gray,new Size(WorkingWidth,Math.Max(1,(int)MathF.Round(frame.Height*scale))),0,0,InterpolationFlags.Area);
        var body=new Rect2f((person.CenterX-person.Size*.3f)*scale,(person.CenterY-person.Size*.55f)*scale,person.Size*.6f*scale,person.Size*1.1f*scale);
        if(previous is null){previous=gray;previousBody=body;samples.Add((index,Quaternion.Identity));return;}
        pairs++;var rotation=Solve(previous,gray,previousBody,body,focal*scale,out var inlierRatio);
        if(rotation is { } solved)
        {
            usable++;largest=Math.Max(largest,Degrees(solved));inlierRatios+=inlierRatio;
            samples.Add((index,Quaternion.Normalize(solved*samples[^1].WorldToCamera)));
        }
        else samples.Add((index,samples[^1].WorldToCamera)); // unsolved pair: no rotation claimed
        previous.Dispose();previous=gray;previousBody=body;
    }
    static float Degrees(Quaternion q)=>2*MathF.Acos(Math.Clamp(MathF.Abs(q.W),0,1))*180/MathF.PI;
    static Quaternion? Solve(Mat a,Mat b,Rect2f bodyA,Rect2f bodyB,float f,out float inlierRatio)
    {
        inlierRatio=0;
        using var mask=new Mat(a.Size(),MatType.CV_8UC1,Scalar.White);
        Cv2.Rectangle(mask,new Rect((int)bodyA.X,(int)bodyA.Y,(int)bodyA.Width,(int)bodyA.Height),Scalar.Black,-1);
        var points=Cv2.GoodFeaturesToTrack(a,600,.01,7,mask,3,false,.04);
        if(points.Length<MinimumInliers)return null;
        var tracked=new Point2f[points.Length];Cv2.CalcOpticalFlowPyrLK(a,b,points,ref tracked,out var status,out _,new Size(21,21),4);
        var returned=new Point2f[points.Length];Cv2.CalcOpticalFlowPyrLK(b,a,tracked,ref returned,out var back,out _,new Size(21,21),4);
        var from=new List<Point2d>();var to=new List<Point2d>();
        for(var i=0;i<points.Length;i++)
        {
            if(status[i]==0||back[i]==0||bodyB.Contains(tracked[i])||points[i].DistanceTo(returned[i])>1)continue;
            from.Add(new(points[i].X,points[i].Y));to.Add(new(tracked[i].X,tracked[i].Y));
        }
        if(from.Count<MinimumInliers)return null;
        // Distant background under camera rotation moves by the homography K R K^-1.
        using var inliers=new Mat();
        using var homography=Cv2.FindHomography(from,to,HomographyMethods.Ransac,1.5,inliers);
        if(homography.Empty()||Cv2.CountNonZero(inliers)<Math.Max(MinimumInliers,from.Count*.5))return null;
        inlierRatio=Cv2.CountNonZero(inliers)/(float)from.Count;
        double cx=(a.Width-1)*.5,cy=(a.Height-1)*.5;
        var h=new double[3,3];for(var r=0;r<3;r++)for(var c=0;c<3;c++)h[r,c]=homography.At<double>(r,c);
        double[,] k={{f,0,cx},{0,f,cy},{0,0,1}},inverse={{1/f,0,-cx/f},{0,1/f,-cy/f},{0,0,1}};
        var m=Multiply(inverse,Multiply(h,k));
        // Nearest rotation: orthonormalise with SVD and fix the sign.
        using var matrix=new Mat(3,3,MatType.CV_64FC1);for(var r=0;r<3;r++)for(var c=0;c<3;c++)matrix.Set(r,c,m[r,c]);
        using var w=new Mat();using var u=new Mat();using var vt=new Mat();Cv2.SVDecomp(matrix,w,u,vt);
        using var product=(u*vt).ToMat();var rotation=new double[3,3];for(var r=0;r<3;r++)for(var c=0;c<3;c++)rotation[r,c]=product.At<double>(r,c);
        var determinant=rotation[0,0]*(rotation[1,1]*rotation[2,2]-rotation[1,2]*rotation[2,1])-rotation[0,1]*(rotation[1,0]*rotation[2,2]-rotation[1,2]*rotation[2,0])+rotation[0,2]*(rotation[1,0]*rotation[2,1]-rotation[1,1]*rotation[2,0]);
        if(determinant<0)for(var r=0;r<3;r++)for(var c=0;c<3;c++)rotation[r,c]=-rotation[r,c];
        // System.Numerics uses row vectors: its matrix is the transpose of this column-vector rotation.
        var q=Quaternion.Normalize(Quaternion.CreateFromRotationMatrix(new Matrix4x4(
            (float)rotation[0,0],(float)rotation[1,0],(float)rotation[2,0],0,(float)rotation[0,1],(float)rotation[1,1],(float)rotation[2,1],0,
            (float)rotation[0,2],(float)rotation[1,2],(float)rotation[2,2],0,0,0,0,1)));
        // A sampled pair a fifth of a second apart cannot plausibly turn this far; treat it as a failed solve.
        return float.IsFinite(q.W)&&Degrees(q)<=25?q:null;
    }
    static double[,] Multiply(double[,] a,double[,] b)
    {var result=new double[3,3];for(var r=0;r<3;r++)for(var c=0;c<3;c++)for(var i=0;i<3;i++)result[r,c]+=a[r,i]*b[i,c];return result;}
    public Result Finish()
    {
        var count=frames;var result=new float[count*6];if(count==0)return new(result,0,0,0,0);
        var orientation=new Quaternion[count];var next=0;
        for(var t=0;t<count;t++)
        {
            while(next<samples.Count-1&&samples[next+1].Frame<=t)next++;
            var a=samples[next];var b=samples[Math.Min(next+1,samples.Count-1)];
            orientation[t]=b.Frame==a.Frame?a.WorldToCamera:Quaternion.Slerp(a.WorldToCamera,b.WorldToCamera,Math.Clamp((t-a.Frame)/(float)(b.Frame-a.Frame),0,1));
        }
        for(var t=0;t<count;t++)
        {
            // GVHMR compute_cam_angvel: R[t+1] R[t]^T, with the final value repeated.
            var s=Math.Min(t,count-2);var relative=count<2?Quaternion.Identity:Quaternion.Normalize(orientation[s+1]*Quaternion.Conjugate(orientation[s]));
            var m=Matrix4x4.CreateFromQuaternion(relative);
            // First two rows of the column-vector rotation matrix (PyTorch3D 6D layout).
            result[t*6]=m.M11;result[t*6+1]=m.M21;result[t*6+2]=m.M31;result[t*6+3]=m.M12;result[t*6+4]=m.M22;result[t*6+5]=m.M32;
        }
        return new(result,pairs,usable,Degrees(orientation[^1]),largest,usable==0?0:inlierRatios/usable);
    }
    public void Dispose(){previous?.Dispose();previous=null;}
}
notpointless.chomnr_humanoid_mocap / InferenceWorker/HandModelDownloads.cs
Game library
using HumanoidMocap.Inference;
using System.Security.Cryptography;
using System.Text.Json;

namespace HumanoidMocap.Worker;

/// <summary>Only the explicitly selected backend and its crop detector are downloaded.</summary>
public static class HandModelDownloads
{
    public sealed record Asset(string Path,string Url,long Bytes,string Sha256);
    static readonly Asset Detector=new("hand_landmarker.task",
        "https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task",
        7819105,"fbc2a30080c3c557093b5ddfc334698132eb341044ccee322ccf8bcf3607cde1");
    static readonly Asset WildHands=new("wildhands/wildhands.ckpt",
        "https://drive.usercontent.google.com/download?id=1FJWBrMmTKjKAo6j5DQS1KYpqqFbAbJ9Q&export=download&confirm=t",
        855094722,"cac3f9a9334da852f3993e95b4ec088dcc6c69f0337db63dacd83e4642880a7b");
    static readonly Asset Wilor=new("wilor/wilor_final.ckpt",
        "https://huggingface.co/spaces/rolpotamias/WiLoR/resolve/99fe3d7acff8104ecca1055df7467709506c2fa6/pretrained_models/wilor_final.ckpt",
        2564989533,"3e97aafc7dd08d883a4cc5a027df61fdb6fda6136dbd1319405413862ada6bb2");
    static readonly Asset MobileHand=new("mobilehand/hmr_model_freihand_auc.pth",
        "https://raw.githubusercontent.com/gmntu/mobilehand/51c112364013b803c38955b55a1572b0d402894c/model/hmr_model_freihand_auc.pth",
        15152098,MobileHandModel.CheckpointSha256);

    public static async Task Ensure(string folder,string backend,CancellationToken token)
    {
        var assets=backend switch{"mediapipe"=>new[]{Detector},"mobilehand"=>new[]{Detector,MobileHand},"wildhands"=>new[]{Detector,WildHands},"wilor"=>new[]{Detector,Wilor},_=>throw new NotSupportedException("Select MediaPipe, MobileHand, WildHands or WiLoR. ACE is not downloaded or loaded by this worker.")};
        using var http=new HttpClient{Timeout=TimeSpan.FromHours(1)};
        foreach(var asset in assets)
        {
            var path=Path.Combine(folder,asset.Path);Directory.CreateDirectory(Path.GetDirectoryName(path)!);
            if(!File.Exists(path))await ModelDownload.Fetch(http,asset.Url,path,asset.Bytes,asset.Sha256,Console.WriteLine,token);
            else await Verify(path,asset,token);
            Console.WriteLine("Verified "+Path.GetFileName(path));
        }
        File.WriteAllText(Path.Combine(folder,backend+"-models.json"),JsonSerializer.Serialize(new{backend,assets,verifiedUtc=DateTime.UtcNow},new JsonSerializerOptions{WriteIndented=true}));
    }
    static async Task Verify(string path,Asset asset,CancellationToken token)
    {
        if(new FileInfo(path).Length!=asset.Bytes)throw new InvalidDataException("Unexpected model size; original preserved: "+path);
        var hash=await Task.Run(()=>FileChecksum.Sha256(path),token);
        if(!hash.Equals(asset.Sha256,StringComparison.OrdinalIgnoreCase))throw new InvalidDataException("Model checksum mismatch; original preserved: "+path);
    }
}
notpointless.chomnr_humanoid_mocap / Editor/HumanoidMocap/MappingEditor.cs
Editor library
#nullable enable annotations

using System;
using System.Collections.Generic;
using System.Linq;
using Editor;
using HumanoidMocap.Mapping;
using SkeletonModel = HumanoidMocap.Skeleton.Skeleton;

namespace HumanoidMocap.Editor;

/// <summary>
/// Manual bone-mapping editor: one row per canonical <see cref="BoneRole"/>, grouped
/// anatomically (Body / Arms / Legs / Fingers L / Fingers R), each with a combo of the
/// source skeleton's bone names (plus <c>&lt;none&gt;</c>), pre-filled from the entry's
/// current mapping. Apply produces a <see cref="MappingSource.Manual"/>
/// <see cref="MappingResult"/> that the window installs as the file's mapping override.
/// </summary>
public sealed class MappingEditor : Dialog
{
	static readonly (string Group, BoneRole[] Roles)[] Groups =
	{
		("Body", new[]
		{
			BoneRole.Hips, BoneRole.Spine0, BoneRole.Spine1, BoneRole.Spine2,
			BoneRole.Spine3, BoneRole.Spine4, BoneRole.Neck, BoneRole.Head,
		}),
		("Arms", new[]
		{
			BoneRole.ClavicleL, BoneRole.UpperArmL, BoneRole.LowerArmL, BoneRole.HandL,
			BoneRole.ClavicleR, BoneRole.UpperArmR, BoneRole.LowerArmR, BoneRole.HandR,
		}),
		("Legs", new[]
		{
			BoneRole.UpperLegL, BoneRole.LowerLegL, BoneRole.FootL, BoneRole.ToeL,
			BoneRole.UpperLegR, BoneRole.LowerLegR, BoneRole.FootR, BoneRole.ToeR,
		}),
		("Fingers (left)", FingerRoles( "L" )),
		("Fingers (right)", FingerRoles( "R" )),
	};

	static BoneRole[] FingerRoles( string side )
		=> Enum.GetValues<BoneRole>()
			.Where( r => r.ToString().EndsWith( side, StringComparison.Ordinal )
				&& (r.ToString().StartsWith( "Thumb" ) || r.ToString().StartsWith( "Index" )
					|| r.ToString().StartsWith( "Middle" ) || r.ToString().StartsWith( "Ring" )
					|| r.ToString().StartsWith( "Pinky" )) )
			.ToArray();

	readonly SkeletonModel _skeleton;
	readonly Dictionary<BoneRole, int> _selection;

	/// <summary>Invoked with the manual mapping when the user applies.</summary>
	public Action<MappingResult> Applied { get; set; }

	/// <summary>Creates the editor pre-filled from <paramref name="current"/>.</summary>
	public MappingEditor( Widget parent, string fileName, SkeletonModel skeleton, MappingResult current )
		: base( parent )
	{
		_skeleton = skeleton;
		_selection = new Dictionary<BoneRole, int>( current?.RoleToBone ?? new Dictionary<BoneRole, int>() );

		Window.WindowTitle = $"Bone Mapping - {fileName}";
		Window.SetWindowIcon( "device_hub" );
		Window.SetModal( true, true );
		Window.MinimumWidth = 460;
		Window.MinimumHeight = 600;

		Layout = Layout.Column();
		Layout.Margin = 12;
		Layout.Spacing = 8;

		Layout.Add( new Label( this )
		{
			Text = "Assign bones to their roles; leave absent bones at <none>. For hand capture, "
				+ "map each wrist and its finger chains. Arms are optional; a torso and legs are not required.",
			WordWrap = true,
		} );

		var scroll = Layout.Add( new ScrollArea( this ), 1 );
		scroll.Canvas = new Widget( scroll );
		scroll.Canvas.Layout = Layout.Column();
		scroll.Canvas.Layout.Margin = new Sandbox.UI.Margin( 4, 4, 16, 4 );
		scroll.Canvas.Layout.Spacing = 4;
		var canvas = scroll.Canvas.Layout;

		foreach ( var (group, roles) in Groups )
		{
			var header = canvas.Add( new Label( this ) { Text = group } );
			header.SetStyles( $"font-weight: 600; color: {Theme.Blue.Hex}; margin-top: 8px;" );

			foreach ( var role in roles )
				canvas.Add( BuildRoleRow( role ) );
		}

		canvas.AddStretchCell();

		var buttons = Layout.AddRow();
		buttons.Spacing = 8;
		buttons.AddStretchCell();
		buttons.Add( new Button( "Cancel" ) { Clicked = Close } );
		var apply = buttons.Add( new Button.Primary( "Apply Mapping" ) { Icon = "check" } );
		apply.Clicked = Apply;

		Window.Size = new Vector2( 520, 720 );
	}

	Widget BuildRoleRow( BoneRole role )
	{
		var row = new Widget( this );
		row.Layout = Layout.Row();
		row.Layout.Spacing = 8;

		row.Layout.Add( new Label( this ) { Text = role.ToString(), FixedWidth = 130 } );

		var combo = row.Layout.Add( new ComboBox( this ), 1 );
		combo.AddItem( "<none>", "block",
			() => _selection.Remove( role ),
			selected: !_selection.ContainsKey( role ) );

		for ( var i = 0; i < _skeleton.Count; i++ )
		{
			var boneIndex = i;
			combo.AddItem( _skeleton[i].Name, null,
				() => _selection[role] = boneIndex,
				selected: _selection.TryGetValue( role, out var sel ) && sel == boneIndex );
		}

		return row;
	}

	void Apply()
	{
		// Reject duplicate assignments up front (the target rig builder would throw later).
		var duplicates = _selection.GroupBy( kv => kv.Value ).Where( g => g.Count() > 1 ).ToList();
		if ( duplicates.Count > 0 )
		{
			var first = duplicates[0];
			var roles = string.Join( ", ", first.Select( kv => kv.Key ) );
			new PopupWindow( "Duplicate assignment",
				$"Bone \"{_skeleton[first.Key].Name}\" is assigned to multiple roles: {roles}." )
				.Show();
			return;
		}

		var result = new MappingResult( "manual", MappingSource.Manual ) { Confidence = 1f };
		foreach ( var kv in _selection )
			result.RoleToBone[kv.Key] = kv.Value;
		result.Notes.Add( "Mapping assigned by hand in the mapping editor." );

		Applied?.Invoke( result );
		Close();
	}
}
notpointless.chomnr_humanoid_mocap / Editor/HumanoidMocap/MocapContactEditor.cs
Editor library
using System;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using Editor;
using Sandbox;
using HumanoidMocap.Mapping;
using HumanoidMocap.Motion;

namespace HumanoidMocap.Editor;

public sealed partial class RetargetWindow
{
    internal ContactEditorDialog OpenContactEditor(ContactInterval contact=null)
    {
        if(_editedMotion is null||_processing is not null)return null;
        var dialog=new ContactEditorDialog(this,contact);dialog.Show();return dialog;
    }

    internal async Task SaveContactAsync(MotionDocument expected,int index,ContactInterval replacement)
    {
        if(expected!=_editedMotion||_processing is not null)throw new InvalidOperationException("The capture changed. Reopen contact editing.");
        var candidate=ContactAuthoring.Replace(expected,index,replacement);
        if(new PropContactMotion(candidate).UnsupportedReason(replacement) is {} reason)throw new ArgumentException(reason);
        var source=_rawMotion.Copy();source.Contacts=candidate.Contacts;
        _editedMotion=_appliedCleanup is null?source:MotionCleanup.Apply(source,_appliedCleanup);
        RefreshContacts();await RefreshMocapPreviewAsync();
    }

    internal sealed class ContactEditorDialog : Dialog
    {
        internal readonly LineEdit Start,End,Target;
        internal readonly Label Status;
        internal readonly Button Save;
        internal readonly Button Place;
        internal readonly Checkbox HoldOrientation;
        internal readonly Checkbox Sliding;
        internal readonly MocapContactKeys Keys;
        internal FingerContactDialog FingerDialog;
        internal readonly Button Fingers;
        readonly MotionDocument _motion;
        readonly ContactInterval _draft;

        public ContactEditorDialog(RetargetWindow owner,ContactInterval contact):base(owner)
        {
            _motion=owner._editedMotion;
            var index=contact is null?-1:_motion.Contacts.IndexOf(contact);
            _draft=index<0?new(){Start=_motion.Frames[0].Time,End=_motion.Frames[^1].Time,Review=ContactReview.Suggested}:
                _motion.Copy().Contacts[index];
            Window.WindowTitle=index<0?"Add wrist contact":"Edit wrist contact";Window.SetWindowIcon("touch_app");
            Window.Size=new Vector2(560,430);Window.MinimumSize=new Vector2(520,420);
            SetStyles($"background-color: {Theme.WidgetBackground.Hex}; color: {Theme.Text.Hex};");
            Layout=Layout.Column();Layout.Margin=16;Layout.Spacing=10;
            Layout.Add(new Label("Place an object-local wrist anchor and review it against the video. Saved edits return to Suggested; confirm them in Contact review.",this){WordWrap=true});
            var hands=Layout.Add(new ComboBox(this));
            foreach(var bone in _motion.Bones.Where(b=>b.Role is BoneRole.HandL or BoneRole.HandR))
            {var name=bone.Name;if(string.IsNullOrEmpty(_draft.Bone))_draft.Bone=name;hands.AddItem(name,onSelected:()=>{if(_draft.Bone!=name)_draft.LocalRotation=null;_draft.Bone=name;},selected:name==_draft.Bone);}
            var props=Layout.Add(new ComboBox(this));
            foreach(var prop in _motion.Objects)foreach(var bone in prop.Bones)
            {
                var id=prop.Id;var name=bone.Name;if(string.IsNullOrEmpty(_draft.Object)){_draft.Object=id;_draft.ObjectBone=name;}
                props.AddItem(id+" / "+name,onSelected:()=>{if(_draft.Object!=id||_draft.ObjectBone!=name)_draft.LocalRotation=null;_draft.Object=id;_draft.ObjectBone=name;},
                    selected:id==_draft.Object&&(name==_draft.ObjectBone||string.IsNullOrEmpty(_draft.ObjectBone)&&bone.Parent<0));
            }
            LineEdit Input(string title,string value){var row=Layout.AddRow();row.Spacing=8;row.Add(new Label(title,this){FixedWidth=160});return row.Add(new LineEdit(this){Text=value},1);}
            Start=Input("Start at video (s)",_draft.Start.ToString("R",CultureInfo.InvariantCulture));
            End=Input("End at video (s)",_draft.End.ToString("R",CultureInfo.InvariantCulture));
            Target=Input("Local X, Y, Z (m)",string.Join(",",_draft.LocalTarget.Select(v=>v.ToString("R",CultureInfo.InvariantCulture))));
            HoldOrientation=Layout.Add(new Checkbox("Hold wrist orientation relative to prop"){Value=_draft.LocalRotation is not null});
            HoldOrientation.ToolTip="Optional for a rigid grip. Place an anchor below, then review and confirm. Captured finger articulation remains unchanged.";
            var place=Place=Layout.Add(new Button("Use wrist at interval midpoint","my_location"));
            place.ToolTip="Use the captured wrist at the nearest midpoint sample. This places a manual anchor; it does not detect a grip. Sliding position keys are preserved.";
            Status=new Label("",this){WordWrap=true};
            Status.SetStyles($"color: {Theme.Yellow.Hex};");
            Sliding=Layout.Add(new Checkbox("Sliding contact · animate the local wrist target"){Value=_draft.Sliding});
            Sliding.ToolTip="Uses at least two authored position keys. Turning this off keeps the keys for later and uses the fixed anchor above.";
            void RefreshMode()
            {
                _draft.Sliding=Sliding.Value;Keys.Visible=Sliding.Value;Target.ReadOnly=Sliding.Value;
                props.Enabled=hands.Enabled=_draft.TargetKeys.Count==0&&_draft.FingerTargets.Count==0;
                props.ToolTip=hands.ToolTip=!props.Enabled?"Remove the draft's position keys and finger points before changing their hand or object coordinate frame.":"";
                Window.Size=new Vector2(560,Sliding.Value?700:500);
            }
            Keys=Layout.Add(new MocapContactKeys(this,_motion,_draft,Read,()=>owner.PlaybackTime,message=>Status.Text=message,RefreshMode));
            Sliding.Clicked=RefreshMode;RefreshMode();
            var fingerActions=Layout.AddRow();fingerActions.Spacing=8;
            Fingers=fingerActions.Add(new Button("Finger contact points…","touch_app"),1);
            fingerActions.Add(new Button("Clear points","clear"){ToolTip="Remove all target-specific finger points from this draft. Cancel restores the saved contact.",Clicked=()=>{
                _draft.FingerTargets.Clear();RefreshMode();Status.Text="Finger points removed from this draft. Save to keep the change, or cancel to restore them.";
            }});
            Fingers.Clicked=()=>{try{Read();FingerDialog=new(owner,this,_draft,()=>{RefreshMode();Status.Text=$"{_draft.FingerTargets.Count} target-specific finger points. Save and review before confirming.";});FingerDialog.Show();}catch(Exception error){Status.Text=error.Message;}};
            Layout.Add(Status);
            place.Clicked=()=>{try{Read();var time=ContactAuthoring.PlaceAtWrist(_motion,_draft,HoldOrientation.Value);Target.Text=string.Join(",",_draft.LocalTarget.Select(v=>v.ToString("R",CultureInfo.InvariantCulture)));Status.Text=$"Manual anchor placed from the wrist at {time:F3} s.";}catch(Exception e){Status.Text=e.Message;}};
            var buttons=Layout.AddRow();buttons.AddStretchCell();buttons.Add(new Button("Cancel"){Clicked=Close});Save=buttons.Add(new Button.Primary("Save suggestion"));
            Save.Clicked=async ()=>{
                try{Read();if(Sliding.Value&&Keys.HasUnappliedChanges)throw new ArgumentException("Add or update the edited sliding key before saving.");
                    if(HoldOrientation.Value&&_draft.LocalRotation is null)throw new ArgumentException("Use wrist at interval midpoint to place the orientation anchor.");
                    if(!HoldOrientation.Value)_draft.LocalRotation=null;
                    _draft.Review=ContactReview.Suggested;_draft.Reason="Manually placed/edited wrist contact; requires review against the video.";
                    Save.Enabled=false;await owner.SaveContactAsync(_motion,index,_draft);await EditorPipeline.SwitchToMainThread();if(this.IsValid())Close();}
                catch(Exception e){await EditorPipeline.SwitchToMainThread();if(this.IsValid()){Status.Text=e.Message;Save.Enabled=true;}}
            };
        }
        void Read()
        {
            _draft.Start=double.Parse(Start.Text,CultureInfo.InvariantCulture);_draft.End=double.Parse(End.Text,CultureInfo.InvariantCulture);
            _draft.LocalTarget=MotionDocument.A(Vector(Target));
        }
    }
}
notpointless.chomnr_humanoid_mocap / Editor/HumanoidMocap/MocapContactTimeline.cs
Editor library
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Editor;
using Sandbox;
using HumanoidMocap.Mapping;
using HumanoidMocap.Motion;

namespace HumanoidMocap.Editor;

// Uses the exposed Widget/Paint/Menu APIs and Theme colors used by sbox-public's
// scrub bars. No MovieMaker session or engine-internal timeline dependency.
sealed class MocapContactTimeline : Widget
{
    MotionDocument motion;
    IReadOnlyList<WristPositionOffset> wristOffsets=Array.Empty<WristPositionOffset>();
    double playhead;
    public Action<float> Seek { get; set; }
    public Action<MotionDocument,int> Edit { get; set; }
    public Action<MotionDocument,int,ContactReview> Review { get; set; }
    public Action<MotionDocument,int,double,double> ChangeRange { get; set; }
    public Action<MotionDocument,WristPositionOffset> EditWrist { get; set; }

    public MocapContactTimeline(Widget parent):base(parent)
    {
        FixedHeight=36;MouseTracking=true;Visible=false;
        ToolTip="Contact intervals: yellow needs review, green confirmed, gray disabled. Click to seek; double-click to edit; right-click for review and timing.";
    }
    public void SetMotion(MotionDocument value,IReadOnlyList<WristPositionOffset> edits=null)
    {motion=value;wristOffsets=edits?.ToArray()??Array.Empty<WristPositionOffset>();Visible=value is not null&&(value.Contacts.Count>0||wristOffsets.Count>0);Update();}
    public void SetPlayhead(double time)
    {if(playhead==time)return;playhead=time;Update();}
    float X(double time)
    {
        if(motion is null)return 6;
        var start=motion.Frames[0].Time;var duration=motion.Frames[^1].Time-start;
        return 6+(float)(duration>0?Math.Clamp((time-start)/duration,0,1):0)*Math.Max(1,Width-12);
    }
    float Fraction(float x)=>Math.Clamp((x-6)/Math.Max(1,Width-12),0,1);
    int Lane(ContactInterval contact)=>motion.Bones.FirstOrDefault(b=>b.Name==contact.Bone)?.Role==BoneRole.HandR?1:0;
    string Side(ContactInterval contact)=>motion.Bones.FirstOrDefault(b=>b.Name==contact.Bone)?.Role switch
        {BoneRole.HandL=>"L",BoneRole.HandR=>"R",_=>contact.Bone};
    internal Rect ContactRect(int index)
    {
        var c=motion.Contacts[index];var start=X(c.Start);var end=X(c.End);
        return new(start,2+Lane(c)*18,Math.Max(2,end-start),14);
    }
    int[] Hits(Vector2 position)=>motion is null?Array.Empty<int>():Enumerable.Range(0,motion.Contacts.Count)
        .Where(i=>ContactRect(i).Grow(2).IsInside(position)).ToArray();
    internal Rect WristRect(int index)
    {
        var edit=wristOffsets[index];var start=X(edit.Start);var end=X(edit.End);
        return new(start,15+(edit.Hand==BoneRole.HandR?18:0),Math.Max(2,end-start),3);
    }
    int[] WristHits(Vector2 position)=>Enumerable.Range(0,wristOffsets.Count).Where(i=>WristRect(i).Grow(2).IsInside(position)).ToArray();
    protected override void OnPaint()
    {
        Paint.ClearPen();Paint.SetBrush(Theme.WindowBackground);Paint.DrawRect(LocalRect,3);
        if(motion is null)return;
        for(var i=0;i<motion.Contacts.Count;i++)
        {
            var c=motion.Contacts[i];var rect=ContactRect(i);
            var color=c.Review==ContactReview.Suggested?Theme.Yellow:c.Review==ContactReview.Confirmed?Theme.Green:Theme.TextLight;
            Paint.SetPen(color,1);Paint.SetBrush(color.WithAlpha(c.Review==ContactReview.Disabled?.1f:.25f));Paint.DrawRect(rect,2);
            var state=c.Review==ContactReview.Suggested?"?":c.Review==ContactReview.Confirmed?"✓":"×";
            var prefix=Side(c)+" "+state;
            var text=prefix+" · "+c.Object;
            if(Paint.MeasureText(text).x>rect.Width-6)text=prefix;
            if(Paint.MeasureText(text).x<=rect.Width-6)Paint.DrawText(rect.Shrink(3,0),text,TextFlag.LeftCenter);
        }
        for(var i=0;i<wristOffsets.Count;i++)
        {
            Paint.ClearPen();Paint.SetBrush(wristOffsets[i].Enabled?Theme.Blue:Theme.TextLight.WithAlpha(.35f));Paint.DrawRect(WristRect(i),1);
        }
        Paint.SetPen(Theme.Text.WithAlpha(.8f),1);var x=X(playhead);Paint.DrawLine(new Vector2(x,0),new Vector2(x,Height));
    }
    protected override void OnMouseMove(MouseEvent e)
    {
        var hits=Hits(e.LocalPosition);var wrists=WristHits(e.LocalPosition);Cursor=CursorShape.Finger;
        ToolTip=hits.Length+wrists.Length==0?"Click to seek. Yellow contacts need review; green are confirmed; blue marks manual wrist correction; gray is disabled.":
            string.Join("\n",hits.Select(i=>{var c=motion.Contacts[i];return $"{c.Bone} → {c.Object} · {c.Start:F3}–{c.End:F3} s · {c.Review}";})
                .Concat(wrists.Select(i=>{var c=wristOffsets[i];return $"{(c.Hand==BoneRole.HandL?"Left":"Right")} wrist · {c.Start:F3}–{c.End:F3} s · {(c.Enabled?"Manual position correction":"Disabled correction")}";})))+
            "\nClick to seek; double-click to edit; right-click to review or change timing.";
    }
    protected override void OnMousePress(MouseEvent e)
    {
        var hits=Hits(e.LocalPosition);var wrists=WristHits(e.LocalPosition);var expected=motion;
        if(expected is null)return;
        if(e.LeftMouseButton)
        {
            Seek?.Invoke(Fraction(e.LocalPosition.x));
            if(e.IsDoubleClick&&hits.Length+wrists.Length==1)
            {if(wrists.Length==1)EditWrist?.Invoke(expected,wristOffsets[wrists[0]]);else Edit?.Invoke(expected,hits[0]);}
            e.Accepted=true;
        }
        else if(e.RightMouseButton&&hits.Length+wrists.Length>0)
        {
            var menu=new Menu();var time=Math.Clamp(playhead,expected.Frames[0].Time,expected.Frames[^1].Time);
            Seek?.Invoke(Fraction(X(time)));
            foreach(var index in hits)
            {
                var c=expected.Contacts[index];var supported=new PropContactMotion(expected).UnsupportedReason(c) is null;
                menu.AddHeading($"{c.Bone} → {c.Object} · {c.Review}");
                menu.AddOption("Edit contact…","edit",()=>Edit?.Invoke(expected,index)).Enabled=supported;
                menu.AddOption("Confirm","check",()=>Review?.Invoke(expected,index,ContactReview.Confirmed)).Enabled=supported;
                menu.AddOption("Disable","block",()=>Review?.Invoke(expected,index,ContactReview.Disabled));
                menu.AddOption($"Start at playhead ({time:F3} s)","first_page",()=>ChangeRange?.Invoke(expected,index,time,c.End)).Enabled=supported&&time<c.End;
                menu.AddOption($"End at playhead ({time:F3} s)","last_page",()=>ChangeRange?.Invoke(expected,index,c.Start,time)).Enabled=supported&&time>c.Start;
            }
            foreach(var index in wrists)
            {
                var edit=wristOffsets[index];menu.AddHeading($"{(edit.Hand==BoneRole.HandL?"Left":"Right")} wrist · manual correction");
                menu.AddOption("Edit wrist correction…","edit_location",()=>EditWrist?.Invoke(expected,edit));
            }
            menu.OpenAtCursor();e.Accepted=true;
        }
    }
}

public sealed partial class RetargetWindow
{
    MocapContactTimeline _contactTimeline;
    async Task ChangeContactRangeAsync(MotionDocument expected,int index,double start,double end)
    {
        try
        {
            if(expected!=_editedMotion||_processing is not null)return;
            var contact=expected.Copy().Contacts[index];contact.Start=start;contact.End=end;
            contact.Review=ContactReview.Suggested;contact.Reason="Interval edited on the timeline; review against the video.";
            await SaveContactAsync(expected,index,contact);
        }
        catch(Exception error){await EditorPipeline.SwitchToMainThread();if(this.IsValid())_captureStatus.Text=error.Message;}
    }
}
notpointless.chomnr_humanoid_mocap / Code/HumanoidMocap/Inference/HandMotionBuilder.cs
Game library
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using HumanoidMocap.Mapping;
using HumanoidMocap.Maths;
using HumanoidMocap.Motion;
using HumanoidMocap.Solve;
using HumanoidMocap.Target;

namespace HumanoidMocap.Inference;
using Vector3=System.Numerics.Vector3;

/// <summary>Fits observed landmarks to a canonical hand skeleton. The source contains
/// hands only; shoulders and arm IK belong to target retargeting.</summary>
public sealed class HandMotionBuilder
{
    readonly TargetRig rig;
    readonly XForm[] rest;
    readonly int[] sourceBones;
    readonly Dictionary<int,int> documentIndices;
    CameraObservation? previewCamera;
    public MotionDocument Document { get; }
    public bool SwapHands { get; set; }
    public float WristPlaneWidth { get; set; }=.9f;
    public float WristPlaneDepth { get; set; }=.4f;

    public HandMotionBuilder(TargetRig template,string name,string video,string hash,double fps)
    {
        rig=template;rest=rig.Skeleton.RestWorld.Select(t=>new XForm(t.Pos/100,t.Rot)).ToArray();
        sourceBones=rig.Skeleton.Bones.Where(b=>rig.RoleOf(b.Index) is BoneRole.HandL or BoneRole.HandR||
            rig.RoleOf(b.Index) is { } role&&FingerSolver.IsFingerRole(role)).Select(b=>b.Index).ToArray();
        documentIndices=sourceBones.Select((source,index)=>(source,index)).ToDictionary(x=>x.source,x=>x.index);
        Document=new MotionDocument{Name=name,SourceVideo=video,SourceSha256=hash,SourceFps=fps,
            Backend="MediaPipe hands / experimental managed C#",ModelVersion="hand_landmarker/float16/1; hand-forest-v3-authored-metacarpals; camera-framing-v1",
            Space=MotionSpace.CameraRelative,MetricScaleCalibrated=false};
        foreach(var source in sourceBones)
        {
            var role=rig.RoleOf(source);var hand=role is BoneRole.HandL or BoneRole.HandR;
            var parent=hand?-1:rig.Skeleton[source].ParentIndex;
            while(parent>=0&&!documentIndices.ContainsKey(parent))parent=rig.Skeleton[parent].ParentIndex;
            if(!hand&&parent<0)throw new ArgumentException("Finger mapping must descend from a mapped hand.");
            var local=parent<0?new XForm(Vector3.Zero,rest[source].Rot):XForm.Compose(rest[parent].Inverse(),rest[source]);
            Document.Bones.Add(new(){Name=rig.Skeleton[source].Name,Role=role,Parent=parent<0?-1:documentIndices[parent],Group=hand?"arms":"fingers",
                RestPosition=MotionDocument.A(local.Pos),RestRotation=MotionDocument.A(local.Rot)});
        }
        Document.Diagnostics.AddRange(new[]{"Experimental landmark reconstruction. Model-port parity has not been established.",
            "Hand-relative 3D landmarks are reconstructed; rotations are fitted to a fixed canonical hand skeleton.",
            "Metacarpal rest transforms are authored template anatomy, not observed motion. They are labeled Authored while their hand is observed.",
            "Finger segment directions follow the landmarks. Axial twist is unmeasured and estimated by minimal swing relative to the parent segment; it is not captured finger torsion.",
            "Camera-relative wrist translation uses an assumed image plane, not measured depth or camera motion.",
            "Separated hand tracks can retain left/right identity through weak classifier disagreement. Saved handedness probabilities below 0.5 record that disagreement; no missing hand is generated.",
            "The source contains no shoulders or elbows. Target arm IK is estimated after reconstruction.",
            "Unobserved hands hold their last pose and remain labeled unobserved. No per-joint confidence is supplied."});
    }
    public void Add(double time,int width,int height,IReadOnlyList<HandObservation> observations)
    {
        if(width<=0||height<=0||!float.IsFinite(WristPlaneWidth)||!float.IsFinite(WristPlaneDepth)||WristPlaneWidth<=0||WristPlaneDepth<=0)
            throw new ArgumentException("Invalid image dimensions or assumed wrist plane.");
        var focal=width*WristPlaneDepth/WristPlaneWidth;
        if(previewCamera is null)
        {
            previewCamera=new(){Id="video",Source="Authored preview camera matching the assumed wrist plane; not recovered video calibration",
                ImageWidth=width,ImageHeight=height,Calibrated=false,Synchronized=true,
                Intrinsics=new[]{focal,0,width/2f,0,focal,height/2f,0,0,1}};
            Document.Cameras.Add(previewCamera);
        }
        else if(previewCamera.Intrinsics is {} intrinsics&&(previewCamera.ImageWidth!=width||previewCamera.ImageHeight!=height||intrinsics[0]!=focal))
        {
            // One static camera cannot describe changing image/plane geometry.
            previewCamera.ImageWidth=previewCamera.ImageHeight=null;previewCamera.Intrinsics=null;
            previewCamera.Source="Assumed wrist-plane geometry changes within this clip; preview camera is unspecified";
        }
        var previous=Document.Frames.LastOrDefault();
        var frame=new MotionFrame{Time=time,
            Positions=(previous?.Positions??Document.Bones.Select(b=>b.RestPosition).ToArray()).Select(p=>p.ToArray()).ToArray(),
            Rotations=(previous?.Rotations??Document.Bones.Select(b=>b.RestRotation).ToArray()).Select(q=>q.ToArray()).ToArray(),
            Evidence=Enumerable.Repeat(JointEvidence.Unobserved,sourceBones.Length).ToArray(),Confidence=null};
        var desired=new Dictionary<int,Quaternion>();
        foreach(var observed in observations.GroupBy(h=>h.Side).Select(g=>g.OrderByDescending(h=>h.Presence).First()))
        {
            if(observed.Side is not ("L" or "R")||observed.ImageLandmarks.Length!=21||observed.RelativeWorldLandmarks.Length!=21)continue;
            var side=SwapHands?(observed.Side=="L"?"R":"L"):observed.Side;
            BoneRole Role(string name)=>Enum.Parse<BoneRole>(name+side);
            if(rig.BoneForRole(Role("Hand")) is not int hand||rig.BoneForRole(Role("IndexProx")) is not int index||
                rig.BoneForRole(Role("PinkyProx")) is not int pinky||rig.BoneForRole(Role("MiddleProx")) is not int middle)continue;
            // MediaPipe x-right/y-down/z-away -> document x-right/y-up/z-toward viewer.
            var points=observed.RelativeWorldLandmarks.Select(v=>new Vector3(v.X,-v.Y,-v.Z)).ToArray();
            var across=points[5]-points[17];var restAcross=rest[index].Pos-rest[pinky].Pos;
            if(!TryBasis(rest[middle].Pos-rest[hand].Pos,restAcross,out var reference)||
                !TryBasis(points[9]-points[0],across,out var orientation))continue;
            var wrist=observed.ImageLandmarks[0];
            if(!Finite(wrist))continue;
            var handIndex=documentIndices[hand];
            frame.Positions[handIndex]=MotionDocument.A(new Vector3((wrist.X/width-.5f)*WristPlaneWidth,
                (.5f-wrist.Y/height)*WristPlaneWidth*height/width,-WristPlaneDepth));
            desired[handIndex]=Quaternion.Normalize(orientation*Quaternion.Inverse(reference)*rest[hand].Rot);
            frame.Evidence[handIndex]=JointEvidence.Reconstructed;
            foreach(var finger in new[]{"Thumb","Index","Middle","Ring","Pinky"})
                if(rig.BoneForRole(Role(finger+"Meta")) is int meta&&documentIndices.TryGetValue(meta,out var metaIndex))
                {
                    frame.Positions[metaIndex]=Document.Bones[metaIndex].RestPosition.ToArray();
                    frame.Rotations[metaIndex]=Document.Bones[metaIndex].RestRotation.ToArray();
                    frame.Evidence[metaIndex]=JointEvidence.Authored;
                }
            foreach(var (finger,start) in new[]{("Thumb",1),("Index",5),("Middle",9),("Ring",13),("Pinky",17)})
            {
                var parentDelta=Quaternion.Normalize(desired[handIndex]*Quaternion.Inverse(rest[hand].Rot));
                var segments=new[]{"Prox","Mid","Dist"};
                for(var k=0;k<3;k++)
                {
                    if(rig.BoneForRole(Role(finger+segments[k])) is not int bone)continue;
                    Vector3 direction;
                    if(k<2&&rig.BoneForRole(Role(finger+segments[k+1])) is int next)direction=rest[next].Pos-rest[bone].Pos;
                    else if(k>0&&rig.BoneForRole(Role(finger+segments[k-1])) is int parent)direction=rest[bone].Pos-rest[parent].Pos;
                    else continue;
                    var capturedDirection=points[start+k+1]-points[start+k];
                    if(!Finite(direction)||!Finite(capturedDirection)||direction.LengthSquared()<1e-10f||capturedDirection.LengthSquared()<1e-10f)break;
                    // A segment direction does not measure roll. Carry its parent's
                    // frame and apply only the swing needed to match the observation.
                    // Independent palm-axis bases become singular when a finger
                    // points across the palm and can add a spurious 180-degree twist.
                    var predictedDirection=Vector3.Transform(direction,parentDelta);
                    var delta=Quaternion.Normalize(MathQ.FromTo(predictedDirection,capturedDirection)*parentDelta);
                    var joint=documentIndices[bone];
                    desired[joint]=Quaternion.Normalize(delta*rest[bone].Rot);
                    frame.Evidence[joint]=JointEvidence.Reconstructed;
                    parentDelta=delta;
                }
            }
        }
        var world=new Quaternion[sourceBones.Length];
        for(var i=0;i<world.Length;i++)
        {
            var parent=Document.Bones[i].Parent;var parentRotation=parent<0?Quaternion.Identity:world[parent];
            if(desired.TryGetValue(i,out var rotation))frame.Rotations[i]=MotionDocument.A(Quaternion.Normalize(Quaternion.Inverse(parentRotation)*rotation));
            world[i]=Quaternion.Normalize(parentRotation*MotionDocument.Q(frame.Rotations[i]));
        }
        Document.Frames.Add(frame);
    }
    static bool Finite(Vector3 v)=>float.IsFinite(v.X)&&float.IsFinite(v.Y)&&float.IsFinite(v.Z);
    static bool TryBasis(Vector3 direction,Vector3 across,out Quaternion rotation)
    {
        rotation=Quaternion.Identity;
        if(!Finite(direction)||!Finite(across)||direction.LengthSquared()<1e-10f)return false;
        var x=Vector3.Normalize(direction);var y=across-x*Vector3.Dot(across,x);
        if(y.LengthSquared()<1e-10f)return false;
        y=Vector3.Normalize(y);var z=Vector3.Normalize(Vector3.Cross(x,y));
        rotation=Quaternion.Normalize(Quaternion.CreateFromRotationMatrix(new Matrix4x4(x.X,x.Y,x.Z,0,y.X,y.Y,y.Z,0,z.X,z.Y,z.Z,0,0,0,0,1)));
        return true;
    }
}
notpointless.chomnr_humanoid_mocap / Code/HumanoidMocap/Motion/CaptureGround.cs
Game library
using System;
using System.Collections.Generic;
using System.Linq;
using HumanoidMocap.Cleanup;
using HumanoidMocap.Mapping;
using HumanoidMocap.Maths;
using HumanoidMocap.Target;

namespace HumanoidMocap.Motion;
using Vector3 = System.Numerics.Vector3;

/// <summary>Keeps a body capture on the floor all the way through. The foot lock corrects floor drift only
/// from detected foot contacts, and only for world-relative captures; a camera-relative capture from a camera
/// that moved was grounded once, and dance with few flat-footed moments had nothing to anchor on. On a
/// step-dance clip the feet rose from the floor to 45 cm over 17 seconds.
///
/// Within any stretch of about a second and a half somebody standing, walking or dancing puts a foot down,
/// so the lower envelope of the lowest foot's height is the floor: a rolling minimum followed by a rolling
/// maximum over that window (a morphological opening), which follows a slow rise exactly and drops anything
/// narrower than the window, such as jumps. Only the floor's change over the clip is removed, relative to its
/// lowest level, never by more than would push a foot below the floor: a capture that sits at one height the
/// whole time (a stage, a placed world capture) keeps it.</summary>
public static class CaptureGround
{
    /// <summary>Seconds within which a foot is expected to touch the floor.</summary>
    public const double WindowSeconds = 1.5;
    /// <summary>Corrections smaller than this, in centimetres, leave the clip untouched.</summary>
    public const float MinimumCorrectionCm = 1;

    /// <returns>The largest correction applied, in centimetres.</returns>
    public static float Apply( List<XForm[]> frames, TargetRig target, TargetUpAxis axis, float fps )
    {
        if ( frames.Count < 3 || !(fps > 0) ) return 0;
        var rig = target.Skeleton;
        var up = axis == TargetUpAxis.YUpCm ? Vector3.UnitY : Vector3.UnitZ;
        var toCm = axis == TargetUpAxis.ZUpEngine ? 2.54f : 1f;
        var joints = new[] { BoneRole.FootL, BoneRole.FootR, BoneRole.ToeL, BoneRole.ToeR }
            .Select( target.BoneForRole ).Where( b => b is not null ).Select( b => b.Value ).ToArray();
        if ( joints.Length == 0 ) return 0;
        // Height of the lowest foot joint above its own rest height, per frame.
        var lowest = new float[frames.Count]; var world = new XForm[rig.Count];
        for ( var f = 0; f < frames.Count; f++ )
        {
            FkUtil.ToWorld( frames[f], rig, world );
            var h = float.PositiveInfinity;
            foreach ( var j in joints ) h = Math.Min( h, Vector3.Dot( world[j].Pos, up ) - Vector3.Dot( rig.RestWorld[j].Pos, up ) );
            lowest[f] = h;
        }
        var radius = Math.Max( 1, (int)Math.Round( WindowSeconds * fps / 2 ) );
        var eroded = new float[frames.Count]; var floor = new double[frames.Count];
        for ( var f = 0; f < frames.Count; f++ )
        {
            var m = float.PositiveInfinity;
            for ( var k = Math.Max( 0, f - radius ); k <= Math.Min( frames.Count - 1, f + radius ); k++ ) m = Math.Min( m, lowest[k] );
            eroded[f] = m;
        }
        for ( var f = 0; f < frames.Count; f++ )
        {
            var m = float.NegativeInfinity;
            for ( var k = Math.Max( 0, f - radius ); k <= Math.Min( frames.Count - 1, f + radius ); k++ ) m = Math.Max( m, eroded[k] );
            floor[f] = m;
        }
        // The rolling minimum steps as the window slides; a slow zero-phase filter leaves only the drift.
        if ( frames.Count >= 8 && fps > 2 )
        {
            var (b, a) = MocapSmooth.ButterLowpass( 2, Math.Min( .5, fps * .2 ), fps );
            floor = MocapSmooth.FiltFilt( b, a, floor );
        }
        var reference = floor.Min();
        var largest = 0f;
        var shifts = new float[frames.Count];
        for ( var f = 0; f < frames.Count; f++ )
        {
            // Only the change in floor height is removed, and never below the floor.
            shifts[f] = (float)Math.Min( floor[f] - reference, Math.Max( 0, lowest[f] ) );
            largest = Math.Max( largest, Math.Abs( shifts[f] ) * toCm );
        }
        if ( largest < MinimumCorrectionCm ) return 0;
        for ( var f = 0; f < frames.Count; f++ )
            for ( var bone = 0; bone < rig.Count; bone++ )
                if ( rig[bone].ParentIndex < 0 ) frames[f][bone].Pos -= up * shifts[f];
        return largest;
    }
}
notpointless.chomnr_humanoid_mocap / Code/HumanoidMocap/Motion/CapturePlacement.cs
Game library
using System;
using System.Numerics;
using HumanoidMocap.Maths;
using HumanoidMocap.Target;

namespace HumanoidMocap.Motion;
using Vector3 = System.Numerics.Vector3;

/// <summary>The same explicit camera placement for captured hands and authoritative props.</summary>
public readonly record struct CapturePlacement(float Units,Quaternion Rotation,Vector3 Position)
{
    public static CapturePlacement ForTarget(TargetUpAxis axis,TargetCorrectionSettings settings)
    {
        var units=axis==TargetUpAxis.ZUpEngine?39.3700787f:100f;
        var axisRotation=axis==TargetUpAxis.YUpCm?Quaternion.Identity:Quaternion.CreateFromAxisAngle(Vector3.UnitX,MathF.PI/2);
        var camera=Quaternion.CreateFromYawPitchRoll(settings.CaptureCameraYawDegrees*MathF.PI/180,
            settings.CaptureCameraPitchDegrees*MathF.PI/180,0);
        return new(units,Quaternion.Normalize(axisRotation*camera),Vector3.Transform(settings.CaptureCameraPosition*units,axisRotation));
    }
    public XForm Transform(XForm capture)=>new(Vector3.Transform(capture.Pos*Units,Rotation)+Position,
        Quaternion.Normalize(Rotation*capture.Rot));
}
notpointless.chomnr_humanoid_mocap / Code/HumanoidMocap/Target/SboxBoneClassifier.cs
Game library
#nullable enable annotations

using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using HumanoidMocap.Mapping;

namespace HumanoidMocap.Target;

/// <summary>
/// Name-based bone classification and role assignment rules for the s&amp;box humanoid rig
/// (design doc §3). Used by <see cref="TargetRigGenerator"/> to produce the committed
/// target-rig definition; consumers should read classes/roles from <see cref="TargetRig"/>
/// rather than re-deriving them.
/// </summary>
public static partial class SboxBoneClassifier
{
    // Plain cached Regex instead of [GeneratedRegex]: the s&box in-engine compiler
    // does not run the regex source generator, so partial GeneratedRegex methods
    // fail to compile there ("must have an implementation part").
    private static readonly Regex TwistSuffixRegex = new(@"_twist\d+$");
    private static Regex TwistSuffix() => TwistSuffixRegex;

    // arm_elbow/leg_knee on the human rig; leg_glute on the legacy citizen rig.
    private static readonly Regex ConstraintHelperRegex = new(@"^(arm_elbow|leg_knee|leg_glute)_helper(_|$)");
    private static Regex ConstraintHelper() => ConstraintHelperRegex;

    private static readonly Regex IkSuffixRegex = new(@"(_IK_target|_IK_attach|_ikrule)$");
    private static Regex IkSuffix() => IkSuffixRegex;

    private static readonly Regex AimMatrixPrefixRegex = new(@"^aim_matrix_");
    private static Regex AimMatrixPrefix() => AimMatrixPrefixRegex;

    // Face bones on the classic citizen rig (eye_L/R, ear_L/R, face_lid_*): no canonical
    // role exists for them, the solver never retargets them, and the engine's procedural
    // systems (eye look-at, blinking) pose them in game.
    private static readonly Regex FacePrefixRegex = new(@"^(eye|ear|face)_");
    private static Regex FacePrefix() => FacePrefixRegex;

    // Case-insensitive twin of FacePrefixRegex for IsFaceBone: custom rigs classified by
    // BoneClassRules match face names case-insensitively, and the channel decision must
    // agree with that classification.
    private static readonly Regex FacePrefixAnyCaseRegex = new(@"^(eye|ear|face)_", RegexOptions.IgnoreCase);

    /// <summary>
    /// True for face bones (<c>eye_*</c>, <c>ear_*</c>, <c>face_*</c>). Unlike the
    /// twist/helper <see cref="BoneClass.ConstraintDriven"/> bones — which the model's
    /// AnimConstraintList re-drives on every evaluated frame — NOTHING drives face bones in
    /// a compiled sequence: the constraint list never references them and the engine's eye
    /// look-at / blinking only runs in game. A face joint left channel-less in the DMX is
    /// baked statically by resourcecompiler, so the eyes detach from the moving head in
    /// ModelDoc ("eyes out of their sockets"). Retargeted clips must therefore carry
    /// rest-local channels for them — exactly what the shipped fbx2dmx clips do
    /// (reference: <c>dev/m0/ref_idlepose.dmx</c> carries <c>eye_L_p/_o</c>,
    /// <c>face_lid_*_p/_o</c> channels).
    /// </summary>
    public static bool IsFaceBone(string name)
    {
        ArgumentNullException.ThrowIfNull(name);
        return FacePrefixAnyCaseRegex.IsMatch(name);
    }

    /// <summary>Classifies an s&amp;box rig bone by name.</summary>
    public static BoneClass Classify(string name)
    {
        ArgumentNullException.ThrowIfNull(name);

        if (TwistSuffix().IsMatch(name) || ConstraintHelper().IsMatch(name) || name == "neck_clothing"
            || FacePrefix().IsMatch(name))
            return BoneClass.ConstraintDriven;

        if (name == "root_IK" || name == "hold_L" || name == "hold_R"
            || IkSuffix().IsMatch(name) || AimMatrixPrefix().IsMatch(name))
            return BoneClass.IkBaked;

        return BoneClass.Animated;
    }

    /// <summary>
    /// Returns the canonical role of an s&amp;box rig bone, or null when the bone carries no
    /// role (every non-<see cref="BoneClass.Animated"/> bone, by construction).
    /// </summary>
    public static BoneRole? RoleFor(string name)
    {
        ArgumentNullException.ThrowIfNull(name);

        if (Classify(name) != BoneClass.Animated)
            return null;

        return RoleByName.TryGetValue(name, out var role) ? role : null;
    }

    /// <summary>Role table for the s&amp;box bone names (built once, ordinal-keyed).</summary>
    private static readonly IReadOnlyDictionary<string, BoneRole> RoleByName = BuildRoleTable();

    private static Dictionary<string, BoneRole> BuildRoleTable()
    {
        var table = new Dictionary<string, BoneRole>(StringComparer.Ordinal)
        {
            ["pelvis"] = BoneRole.Hips,
            ["spine_0"] = BoneRole.Spine0,
            ["spine_1"] = BoneRole.Spine1,
            ["spine_2"] = BoneRole.Spine2,
            ["neck_0"] = BoneRole.Neck,
            ["head"] = BoneRole.Head,
        };

        foreach (var side in new[] { "L", "R" })
        {
            table[$"clavicle_{side}"] = ParseRole($"Clavicle{side}");
            table[$"arm_upper_{side}"] = ParseRole($"UpperArm{side}");
            table[$"arm_lower_{side}"] = ParseRole($"LowerArm{side}");
            table[$"hand_{side}"] = ParseRole($"Hand{side}");
            table[$"leg_upper_{side}"] = ParseRole($"UpperLeg{side}");
            table[$"leg_lower_{side}"] = ParseRole($"LowerLeg{side}");
            table[$"ankle_{side}"] = ParseRole($"Foot{side}");
            table[$"ball_{side}"] = ParseRole($"Toe{side}");

            foreach (var (finger, rolePrefix) in new[]
            {
                ("thumb", "Thumb"), ("index", "Index"), ("middle", "Middle"),
                ("ring", "Ring"), ("pinky", "Pinky"),
            })
            {
                // Segment naming on the rig: meta = metacarpal, 0/1/2 = proximal/middle/distal.
                // The s&box thumb has no metacarpal bone, but the rule is kept uniform so a
                // hypothetical finger_thumb_meta_* would still map (the enum defines ThumbMeta*).
                table[$"finger_{finger}_meta_{side}"] = ParseRole($"{rolePrefix}Meta{side}");
                table[$"finger_{finger}_0_{side}"] = ParseRole($"{rolePrefix}Prox{side}");
                table[$"finger_{finger}_1_{side}"] = ParseRole($"{rolePrefix}Mid{side}");
                table[$"finger_{finger}_2_{side}"] = ParseRole($"{rolePrefix}Dist{side}");
            }
        }

        return table;
    }

    private static BoneRole ParseRole(string name) => Enum.Parse<BoneRole>(name);
}
notpointless.chomnr_humanoid_mocap / Editor/HumanoidMocap/Inference/GvhmrStatistics.cs
Editor library
namespace HumanoidMocap.Inference;

// GVHMR ee960bb6: MM_V1_AMASS_LOCAL_BEDLAM_CAM, stats_compose.py.
// Values copied verbatim; terms in Gvhmr.LICENSE.
internal static class GvhmrStatistics
{
    internal static readonly float[] Mean={9.6969e-01f,-5.9719e-02f,-3.7700e-02f,5.8256e-02f,9.0800e-01f,1.0972e-01f,9.7636e-01f,4.3401e-02f,4.3110e-03f,-4.3032e-02f,9.0261e-01f,1.4478e-01f,9.9288e-01f,3.5673e-03f,1.6264e-02f,-2.2260e-03f,9.3470e-01f,-2.3495e-01f,9.7147e-01f,5.2553e-02f,-9.3666e-02f,-5.4550e-02f,8.3321e-01f,-2.4246e-01f,9.7971e-01f,-3.8429e-02f,5.3575e-03f,1.5537e-02f,8.1449e-01f,-3.0926e-01f,9.9532e-01f,-9.4398e-03f,-3.8328e-02f,8.5141e-03f,9.8880e-01f,1.9976e-04f,9.5602e-01f,-3.9528e-02f,2.0017e-01f,1.0363e-02f,9.5965e-01f,1.3770e-01f,9.6223e-01f,-4.6278e-02f,-1.5177e-01f,6.6705e-02f,9.5545e-01f,1.2519e-01f,9.9767e-01f,-1.2616e-02f,-2.5442e-04f,1.1661e-02f,9.9376e-01f,-3.6222e-02f,9.9511e-01f,-1.0583e-02f,1.2130e-02f,7.6461e-03f,9.9137e-01f,2.0029e-02f,9.9295e-01f,7.2917e-03f,4.9454e-03f,-8.0286e-03f,9.9137e-01f,2.3707e-03f,9.7698e-01f,1.9943e-02f,1.3808e-03f,-2.2006e-02f,9.7375e-01f,-6.7936e-02f,9.2804e-01f,2.5005e-01f,-5.7167e-02f,-2.4047e-01f,9.4246e-01f,2.5863e-02f,9.2957e-01f,-2.1329e-01f,1.1112e-01f,2.0741e-01f,9.4876e-01f,2.9901e-02f,9.7683e-01f,-4.1210e-02f,2.3248e-03f,4.0967e-02f,9.7365e-01f,5.7309e-03f,6.4513e-01f,6.1999e-01f,-2.5469e-01f,-6.2342e-01f,6.8177e-01f,3.5524e-02f,6.6192e-01f,-5.9341e-01f,2.7136e-01f,5.9269e-01f,6.8966e-01f,3.1309e-02f,6.8946e-01f,-1.1676e-01f,-4.9859e-01f,4.0969e-02f,9.3656e-01f,-1.4875e-01f,6.2787e-01f,1.3793e-01f,5.4289e-01f,-9.1946e-02f,9.2868e-01f,-1.1927e-01f,9.3012e-01f,-8.3810e-02f,-1.1951e-01f,9.7211e-02f,8.9118e-01f,5.9887e-02f,9.3033e-01f,7.1047e-02f,7.5264e-02f,-8.0679e-02f,8.8562e-01f,4.8960e-02f,0.2310f,0.1750f,0.2931f,-0.1859f,-1.1163f,-1.1028f,-0.2573f,0.3555f,0.3732f,0.2852f,-4.9862e-03f,-8.7136e-04f,-1.4187e-03f,1.4825e-02f,-9.4419e-01f,-5.1653e-02f,3.6018e-04f,-2.2327e-04f,2.2316e-03f,-4.4879e-02f,-9.7435e-01f,1.0021e-01f,-0.0002f,-0.0006f,0.0069f};
    internal static readonly float[] StandardDeviation={0.0612f,0.1390f,0.1779f,0.1415f,0.1826f,0.3268f,0.0440f,0.1382f,0.1542f,0.1348f,0.1930f,0.3272f,0.0132f,0.0801f,0.0855f,0.0729f,0.1255f,0.2238f,0.0554f,0.1088f,0.1727f,0.0939f,0.3294f,0.3559f,0.0532f,0.1082f,0.1554f,0.0768f,0.3446f,0.3407f,0.0120f,0.0650f,0.0584f,0.0632f,0.0198f,0.1335f,0.0631f,0.1250f,0.1574f,0.1047f,0.0730f,0.2091f,0.0759f,0.1241f,0.1667f,0.1112f,0.0831f,0.2185f,0.0060f,0.0441f,0.0502f,0.0441f,0.0102f,0.0946f,0.0237f,0.0722f,0.0610f,0.0738f,0.0479f,0.0949f,0.0369f,0.0943f,0.0610f,0.0966f,0.0498f,0.0729f,0.0425f,0.1001f,0.1824f,0.0972f,0.0408f,0.1887f,0.0594f,0.1842f,0.1884f,0.2020f,0.0457f,0.1018f,0.0640f,0.1990f,0.1854f,0.2133f,0.0467f,0.0910f,0.0392f,0.1049f,0.1776f,0.1037f,0.0413f,0.1945f,0.1733f,0.2612f,0.1905f,0.2963f,0.1512f,0.1861f,0.1710f,0.2663f,0.1896f,0.3135f,0.1568f,0.2219f,0.3976f,0.1594f,0.2810f,0.1855f,0.0845f,0.2398f,0.4398f,0.1629f,0.2685f,0.1990f,0.0998f,0.2556f,0.1137f,0.2837f,0.1419f,0.2761f,0.1678f,0.2973f,0.1172f,0.3010f,0.1394f,0.2910f,0.1724f,0.3039f,0.8831f,0.7965f,1.0899f,1.1788f,1.2128f,1.1081f,0.9780f,1.1434f,0.8498f,1.1462f,0.7048f,0.1713f,0.6884f,0.1548f,0.1546f,0.2403f,0.6070f,0.5355f,0.5873f,0.6285f,0.2336f,0.7675f,0.0064f,0.0070f,0.0138f};
}
notpointless.chomnr_humanoid_mocap / Code/HumanoidMocap/Formats/Fbx/FbxBinaryWriter.cs
Game library
#nullable enable annotations

using System;
using System.Buffers.Binary;
using System.IO;
using System.Text;

namespace HumanoidMocap.Formats.Fbx;

/// <summary>
/// Serializes an <see cref="FbxNode"/> tree back to binary FBX (version 7400 layout —
/// u32 header fields, universally readable). The inverse of
/// <see cref="FbxTokenizer.Parse"/>: a tree parsed from a 7.x binary file and written
/// here re-parses to an identical tree (arrays are written uncompressed; zlib-encoded
/// inputs therefore round-trip by VALUE, not byte-for-byte).
/// </summary>
/// <remarks>
/// Used by <see cref="FbxBindPoseFixer"/> to persist repaired node transforms. The footer
/// is written the way Blender's exporter does: a fixed 16-byte watermark (importers treat
/// it as opaque), zero padding to a 16-byte boundary, the version echo, 120 zero bytes and
/// the closing magic. The FBX SDK computes a content hash here, but every consumer we
/// target (s&amp;box, Blender, assimp) ignores it.
/// </remarks>
public static class FbxBinaryWriter
{
    private const uint Version = 7400;

    private static readonly byte[] HeaderMagic =
        "Kaydara FBX Binary  \0\x1a\0"u8.ToArray();

    // Blender's fbx_binary.py FOOT_ID + closing magic bytes.
    private static readonly byte[] FooterWatermark =
    {
        0xfa, 0xbc, 0xab, 0x09, 0xd0, 0xc8, 0xd4, 0x66, 0xb1, 0x76, 0xfb, 0x83, 0x1c, 0xf7, 0x26, 0x7e,
    };

    private static readonly byte[] FooterMagic =
    {
        0xf8, 0x5a, 0x8c, 0x6a, 0xde, 0xf5, 0xd9, 0x7e, 0xec, 0xe9, 0x0c, 0x6e, 0x0c, 0xc0, 0x00, 0x00,
    };

    /// <summary>
    /// Serializes <paramref name="root"/> (a virtual root whose children are the top-level
    /// document nodes, as produced by <see cref="FbxTokenizer.Parse"/>).
    /// </summary>
    public static byte[] Write(FbxNode root)
    {
        ArgumentNullException.ThrowIfNull(root);

        using var ms = new MemoryStream();
        ms.Write(HeaderMagic);
        WriteU32(ms, Version);

        foreach (var child in root.Children)
            WriteNode(ms, child);
        WriteNullRecord(ms);

        WriteFooter(ms);
        return ms.ToArray();
    }

    // ------------------------------------------------------------------ nodes

    private static void WriteNode(MemoryStream ms, FbxNode node)
    {
        long headerAt = ms.Position;
        // Placeholder header: endOffset, numProps, propListLen (patched after the body).
        WriteU32(ms, 0);
        WriteU32(ms, (uint)node.Properties.Count);
        WriteU32(ms, 0);
        var nameBytes = Encoding.ASCII.GetBytes(node.Name);
        if (nameBytes.Length > byte.MaxValue)
            throw new FormatException($"FBX write: node name too long ({node.Name.Length} chars).");
        ms.WriteByte((byte)nameBytes.Length);
        ms.Write(nameBytes);

        long propsAt = ms.Position;
        foreach (var p in node.Properties)
            WriteProperty(ms, p, node.Name);
        long propsLen = ms.Position - propsAt;

        if (node.Children.Count > 0)
        {
            foreach (var child in node.Children)
                WriteNode(ms, child);
            WriteNullRecord(ms);
        }

        long endAt = ms.Position;
        ms.Position = headerAt;
        WriteU32(ms, checked((uint)endAt));
        WriteU32(ms, (uint)node.Properties.Count);
        WriteU32(ms, checked((uint)propsLen));
        ms.Position = endAt;
    }

    private static void WriteNullRecord(MemoryStream ms)
    {
        Span<byte> zeros = stackalloc byte[13];
        zeros.Clear();
        ms.Write(zeros);
    }

    // ------------------------------------------------------------------ properties

    private static void WriteProperty(MemoryStream ms, object value, string owner)
    {
        switch (value)
        {
            case short y:
                ms.WriteByte((byte)'Y');
                WriteI16(ms, y);
                break;
            case bool c:
                ms.WriteByte((byte)'C');
                ms.WriteByte(c ? (byte)1 : (byte)0);
                break;
            case int i:
                ms.WriteByte((byte)'I');
                WriteI32(ms, i);
                break;
            case float f:
                ms.WriteByte((byte)'F');
                WriteF32(ms, f);
                break;
            case double d:
                ms.WriteByte((byte)'D');
                WriteF64(ms, d);
                break;
            case long l:
                ms.WriteByte((byte)'L');
                WriteI64(ms, l);
                break;

            case float[] fa:
                WriteArrayHeader(ms, 'f', fa.Length, 4);
                foreach (var x in fa)
                    WriteF32(ms, x);
                break;
            case double[] da:
                WriteArrayHeader(ms, 'd', da.Length, 8);
                foreach (var x in da)
                    WriteF64(ms, x);
                break;
            case long[] la:
                WriteArrayHeader(ms, 'l', la.Length, 8);
                foreach (var x in la)
                    WriteI64(ms, x);
                break;
            case int[] ia:
                WriteArrayHeader(ms, 'i', ia.Length, 4);
                foreach (var x in ia)
                    WriteI32(ms, x);
                break;
            case bool[] ba:
                WriteArrayHeader(ms, 'b', ba.Length, 1);
                foreach (var x in ba)
                    ms.WriteByte(x ? (byte)1 : (byte)0);
                break;

            case string s:
            {
                ms.WriteByte((byte)'S');
                var bytes = Encoding.UTF8.GetBytes(s);
                WriteU32(ms, (uint)bytes.Length);
                ms.Write(bytes);
                break;
            }
            case byte[] r:
                ms.WriteByte((byte)'R');
                WriteU32(ms, (uint)r.Length);
                ms.Write(r);
                break;

            default:
                throw new FormatException(
                    $"FBX write: node '{owner}': unsupported property CLR type {value?.GetType().Name ?? "null"}.");
        }
    }

    private static void WriteArrayHeader(MemoryStream ms, char code, int count, int elemSize)
    {
        ms.WriteByte((byte)code);
        WriteU32(ms, (uint)count);
        WriteU32(ms, 0); // encoding 0 = uncompressed
        WriteU32(ms, checked((uint)(count * elemSize)));
    }

    // ------------------------------------------------------------------ footer

    private static void WriteFooter(MemoryStream ms)
    {
        ms.Write(FooterWatermark);

        // Zero-pad so the version echo starts 16-aligned (Blender pads at least 1 byte).
        int pad = (int)(16 - ms.Position % 16);
        for (int i = 0; i < pad; i++)
            ms.WriteByte(0);

        WriteU32(ms, Version);
        Span<byte> zeros = stackalloc byte[120];
        zeros.Clear();
        ms.Write(zeros);
        ms.Write(FooterMagic);
    }

    // ------------------------------------------------------------------ primitives

    private static void WriteU32(MemoryStream ms, uint v)
    {
        Span<byte> b = stackalloc byte[4];
        BinaryPrimitives.WriteUInt32LittleEndian(b, v);
        ms.Write(b);
    }

    private static void WriteI16(MemoryStream ms, short v)
    {
        Span<byte> b = stackalloc byte[2];
        BinaryPrimitives.WriteInt16LittleEndian(b, v);
        ms.Write(b);
    }

    private static void WriteI32(MemoryStream ms, int v)
    {
        Span<byte> b = stackalloc byte[4];
        BinaryPrimitives.WriteInt32LittleEndian(b, v);
        ms.Write(b);
    }

    private static void WriteI64(MemoryStream ms, long v)
    {
        Span<byte> b = stackalloc byte[8];
        BinaryPrimitives.WriteInt64LittleEndian(b, v);
        ms.Write(b);
    }

    private static void WriteF32(MemoryStream ms, float v)
    {
        Span<byte> b = stackalloc byte[4];
        BinaryPrimitives.WriteSingleLittleEndian(b, v);
        ms.Write(b);
    }

    private static void WriteF64(MemoryStream ms, double v)
    {
        Span<byte> b = stackalloc byte[8];
        BinaryPrimitives.WriteDoubleLittleEndian(b, v);
        ms.Write(b);
    }
}
Debug: View Raw JSON Response
{
    "TotalCount": 331,
    "Files": [
        {
            "Ident": "notpointless.chomnr_humanoid_mocap",
            "Path": "HumanoidMocap/Cleanup/TwistBoneFollow.cs",
            "FileName": "TwistBoneFollow.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 395388,
            "IsPrivate": false,
            "Code": "#nullable enable annotations\r\n\r\nusing System;\r\nusing System.Collections.Generic;\r\nusing System.Numerics;\r\nusing HumanoidMocap.Mapping;\r\nusing HumanoidMocap.Maths;\r\nusing HumanoidMocap.Target;\r\n\r\nnamespace HumanoidMocap.Cleanup;\r\n\r\nusing Vector3 = System.Numerics.Vector3; // s&box compat: shadow engine's global-namespace Vector3 (see Code/HumanoidMocap/Assembly.cs)\r\n\r\n/// <summary>\r\n/// Drives unmapped limb deform bones from the joints whose motion they distribute.\r\n/// Auto-rigged exports (Auto-Rig Pro <c>forearm_twist.l</c>, AdvancedSkeleton\r\n/// <c>ElbowPart1_L</c>, Biped <c>Bip01 L ForeTwist</c>) spread limb roll across helper\r\n/// bones the game constrains at runtime; a baked retarget that leaves them at rest\r\n/// candy-wraps the skin \u2014 the reported wrist \"spike fans\" when the hand pronates.\r\n/// </summary>\r\n/// <remarks>\r\n/// Detection is geometric, name-free: an UNMAPPED bone whose parent is a mapped limb\r\n/// bone (upper/lower arm or leg) and whose rest position lies ON the segment from that\r\n/// parent to the parent's mapped chain child (within 15\u00b0 of the axis, fraction\r\n/// 0.05..1.1 along it). Each detected twist follows the chain child's per-frame local\r\n/// ROLL \u2014 the twist component of its rotation delta about the limb axis \u2014 scaled by the\r\n/// twist's fractional position (a bone at 60% of the forearm takes 60% of the hand's\r\n/// roll; ARP's proximal <c>arm_twist</c> at fraction ~0 correctly takes ~none). Pure\r\n/// swing carries no twist component, so elbows/knees bending never move these bones.\r\n/// Serial deform bones between two mapped limb joints are handled separately: their\r\n/// world-space motion delta is interpolated between the endpoints while both mapped\r\n/// endpoint transforms remain unchanged. This covers rigs that split each bend/twist\r\n/// section into two weighted bones without relying on exporter-specific names. An\r\n/// unmapped sibling at the mapped joint's same pivot follows its complete rotation;\r\n/// this covers dual control/deform rigs where a mechanism forearm/femur drives the next\r\n/// joint while coincident anatomical bones carry the skin.\r\n/// </remarks>\r\npublic static class TwistBoneFollow\r\n{\r\n    private static readonly (BoneRole Parent, BoneRole Child)[] Segments =\r\n    {\r\n        (BoneRole.ClavicleL, BoneRole.UpperArmL),\r\n        (BoneRole.UpperArmL, BoneRole.LowerArmL), (BoneRole.LowerArmL, BoneRole.HandL),\r\n        (BoneRole.ClavicleR, BoneRole.UpperArmR),\r\n        (BoneRole.UpperArmR, BoneRole.LowerArmR), (BoneRole.LowerArmR, BoneRole.HandR),\r\n        (BoneRole.Hips, BoneRole.UpperLegL),\r\n        (BoneRole.UpperLegL, BoneRole.LowerLegL), (BoneRole.LowerLegL, BoneRole.FootL),\r\n        (BoneRole.Hips, BoneRole.UpperLegR),\r\n        (BoneRole.UpperLegR, BoneRole.LowerLegR), (BoneRole.LowerLegR, BoneRole.FootR),\r\n    };\r\n\r\n    private readonly record struct InlineBone(int Bone, int Parent, int Child, float Fraction);\r\n\r\n    private readonly record struct FullFollower(int Bone, int Driver);\r\n\r\n    /// <summary>Applies the pass in place; returns how many limb helpers were driven.</summary>\r\n    public static int Apply(\r\n        IReadOnlyList<XForm[]> frames, TargetRig rig, IReadOnlySet<int>? excluded)\r\n    {\r\n        ArgumentNullException.ThrowIfNull(frames);\r\n        ArgumentNullException.ThrowIfNull(rig);\r\n        var skeleton = rig.Skeleton;\r\n\r\n        var twists = new List<(int Bone, int Driver, Vector3 Axis, float Fraction)>();\r\n        var fullFollowers = new List<FullFollower>();\r\n        var fullFollowerBones = new HashSet<int>();\r\n        foreach (var (parentRole, childRole) in Segments)\r\n        {\r\n            if (rig.BoneForRole(parentRole) is not { } parent\r\n                || rig.BoneForRole(childRole) is not { } child\r\n                || skeleton[child].ParentIndex != parent)\r\n                continue;\r\n\r\n            // Limb axis and length in the PARENT's local space (the chain child's rest\r\n            // local translation).\r\n            var axis = skeleton[child].RestLocal.Pos;\r\n            var length = axis.Length();\r\n            if (length < 1e-3f)\r\n                continue;\r\n            axis /= length;\r\n\r\n            for (var i = 0; i < skeleton.Count; i++)\r\n            {\r\n                if (i == child || skeleton[i].ParentIndex != parent\r\n                    || rig.RoleOf(i) is not null || excluded?.Contains(i) == true)\r\n                    continue;\r\n                var pos = skeleton[i].RestLocal.Pos;\r\n                // Blender control/deform exports commonly put a mechanism joint and one\r\n                // or more skinned anatomical joints at the same pivot (MCH_forearm beside\r\n                // radius/ulna, MCH_femur beside femur). The mapped mechanism drives the\r\n                // next joint, but its deform siblings need the complete bend and roll;\r\n                // treating them as ordinary twist bones copies roll only and leaves the\r\n                // mesh behind while the hand/leg moves away.\r\n                if ((pos - skeleton[child].RestLocal.Pos).Length()\r\n                    <= MathF.Max(0.01f, length * 0.01f))\r\n                {\r\n                    if (fullFollowerBones.Add(i))\r\n                        fullFollowers.Add(new FullFollower(i, child));\r\n                    continue;\r\n                }\r\n                var along = Vector3.Dot(pos, axis);\r\n                var fraction = along / length;\r\n                if (fraction is < 0.05f or > 1.1f)\r\n                    continue;\r\n                var offAxis = (pos - axis * along).Length();\r\n                if (offAxis > MathF.Tan(15f * MathF.PI / 180f) * MathF.Max(along, 1e-3f))\r\n                    continue;\r\n                twists.Add((i, child, axis, Math.Clamp(fraction, 0f, 1f)));\r\n            }\r\n        }\r\n        var inline = FindInlineBones(rig, excluded);\r\n        if (twists.Count == 0 && inline.Count == 0 && fullFollowers.Count == 0)\r\n            return 0;\r\n\r\n        foreach (var frame in frames)\r\n        {\r\n            foreach (var follower in fullFollowers)\r\n            {\r\n                // Both bones share a parent, so the driver's local-space rotation delta\r\n                // can be applied directly while retaining the deform bone's bind offset.\r\n                var delta = MathQ.Normalize(frame[follower.Driver].Rot\r\n                    * Quaternion.Conjugate(skeleton[follower.Driver].RestLocal.Rot));\r\n                frame[follower.Bone] = new XForm(\r\n                    frame[follower.Bone].Pos,\r\n                    MathQ.Normalize(delta * skeleton[follower.Bone].RestLocal.Rot));\r\n            }\r\n            foreach (var (bone, driver, axis, fraction) in twists)\r\n            {\r\n                // The driver's rotation delta from rest, in the shared parent's space,\r\n                // forced to the SHORTEST arc (W >= 0) so the twist angle below is\r\n                // continuous in (-180\u00b0, 180\u00b0) and never flips representation.\r\n                var delta = MathQ.Normalize(\r\n                    frame[driver].Rot * Quaternion.Conjugate(skeleton[driver].RestLocal.Rot));\r\n                if (delta.W < 0f)\r\n                    delta = new Quaternion(-delta.X, -delta.Y, -delta.Z, -delta.W);\r\n                // Twist component about the limb axis (swing-twist decomposition).\r\n                var proj = Vector3.Dot(new Vector3(delta.X, delta.Y, delta.Z), axis);\r\n                // Ill-conditioned when the delta approaches a pure 180\u00b0 SWING (both the\r\n                // axis projection and W collapse toward 0): the decomposition then\r\n                // amplifies noise into huge fake rolls \u2014 measured on a throw clip, the\r\n                // kicking foot injected \u00b199\u00b0 into the calf twist bone and the calf skin\r\n                // flipped upward (\"the leg is up\"). Keep rest instead.\r\n                var conditioning = MathF.Sqrt(proj * proj + delta.W * delta.W);\r\n                if (conditioning < 0.2f)\r\n                    continue;\r\n                var angle = 2f * MathF.Atan2(proj, delta.W);\r\n                var scaled = Quaternion.CreateFromAxisAngle(axis, angle * fraction);\r\n                frame[bone] = new XForm(\r\n                    frame[bone].Pos, MathQ.Normalize(scaled * skeleton[bone].RestLocal.Rot));\r\n            }\r\n\r\n            if (inline.Count > 0)\r\n                FollowInlineBones(frame, skeleton, inline);\r\n        }\r\n        return twists.Count + inline.Count + fullFollowers.Count;\r\n    }\r\n\r\n    private static List<InlineBone> FindInlineBones(\r\n        TargetRig rig, IReadOnlySet<int>? excluded)\r\n    {\r\n        var skeleton = rig.Skeleton;\r\n        var result = new List<InlineBone>();\r\n        var seen = new HashSet<int>();\r\n        foreach (var (parentRole, childRole) in Segments)\r\n        {\r\n            if (rig.BoneForRole(parentRole) is not { } parent\r\n                || rig.BoneForRole(childRole) is not { } child)\r\n                continue;\r\n\r\n            var path = new List<int>();\r\n            for (var bone = skeleton[child].ParentIndex;\r\n                 bone >= 0 && bone != parent;\r\n                 bone = skeleton[bone].ParentIndex)\r\n                path.Add(bone);\r\n            if (path.Count == 0\r\n                || skeleton[path[^1]].ParentIndex != parent\r\n                || path.Any(bone => rig.RoleOf(bone) is not null\r\n                    || excluded?.Contains(bone) == true))\r\n                continue;\r\n            path.Reverse();\r\n\r\n            var length = 0f;\r\n            var previous = parent;\r\n            foreach (var bone in path.Append(child))\r\n            {\r\n                length += (skeleton.RestWorld[bone].Pos\r\n                    - skeleton.RestWorld[previous].Pos).Length();\r\n                previous = bone;\r\n            }\r\n            if (length < 1e-3f)\r\n                continue;\r\n\r\n            var along = 0f;\r\n            previous = parent;\r\n            foreach (var bone in path)\r\n            {\r\n                along += (skeleton.RestWorld[bone].Pos\r\n                    - skeleton.RestWorld[previous].Pos).Length();\r\n                if (seen.Add(bone))\r\n                    result.Add(new InlineBone(bone, parent, child, along / length));\r\n                previous = bone;\r\n            }\r\n        }\r\n        return result;\r\n    }\r\n\r\n    private static void FollowInlineBones(\r\n        XForm[] frame, Skeleton.Skeleton skeleton, IReadOnlyList<InlineBone> inline)\r\n    {\r\n        var world = new Skeleton.Pose(frame).ToWorld(skeleton);\r\n        var desired = world.ToArray();\r\n        var pathBones = new HashSet<int>();\r\n\r\n        foreach (var group in inline.GroupBy(entry => (entry.Parent, entry.Child)))\r\n        {\r\n            var parent = group.Key.Parent;\r\n            var child = group.Key.Child;\r\n            var parentDelta = MathQ.Normalize(world[parent].Rot\r\n                * Quaternion.Conjugate(skeleton.RestWorld[parent].Rot));\r\n            var childDelta = MathQ.Normalize(world[child].Rot\r\n                * Quaternion.Conjugate(skeleton.RestWorld[child].Rot));\r\n            if (Quaternion.Dot(parentDelta, childDelta) < 0f)\r\n                childDelta = new Quaternion(\r\n                    -childDelta.X, -childDelta.Y, -childDelta.Z, -childDelta.W);\r\n\r\n            foreach (var entry in group)\r\n            {\r\n                var delta = MathQ.Normalize(Quaternion.Slerp(\r\n                    parentDelta, childDelta, entry.Fraction));\r\n                desired[entry.Bone] = new XForm(\r\n                    world[entry.Bone].Pos,\r\n                    MathQ.Normalize(delta * skeleton.RestWorld[entry.Bone].Rot));\r\n                pathBones.Add(entry.Bone);\r\n            }\r\n            // Compensate the mapped endpoint locally so its already-solved world transform\r\n            // remains exact after its intermediary parent starts following the motion.\r\n            pathBones.Add(child);\r\n        }\r\n\r\n        foreach (var bone in pathBones.OrderBy(index => index))\r\n        {\r\n            var parent = skeleton[bone].ParentIndex;\r\n            frame[bone] = parent < 0\r\n                ? desired[bone]\r\n                : XForm.ToLocal(desired[parent], desired[bone]);\r\n        }\r\n    }\r\n}\r\n"
        },
        {
            "Ident": "notpointless.chomnr_humanoid_mocap",
            "Path": "HumanoidMocap/Formats/Gltf/GltfModelDmxWriter.cs",
            "FileName": "GltfModelDmxWriter.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 395388,
            "IsPrivate": false,
            "Code": "#nullable enable annotations\r\n\r\nusing System;\r\nusing System.Collections.Generic;\r\nusing System.Globalization;\r\nusing System.Numerics;\r\nusing System.Text;\r\nusing System.Text.Json;\r\nusing HumanoidMocap.Formats.Dmx;\r\nusing HumanoidMocap.Formats.Fbx;\r\nusing HumanoidMocap.Maths;\r\nusing HumanoidMocap.Skeleton;\r\nusing SkeletonModel = HumanoidMocap.Skeleton.Skeleton;\r\n\r\nnamespace HumanoidMocap.Formats.Gltf;\r\n\r\nusing Matrix4x4 = System.Numerics.Matrix4x4;\r\nusing Quaternion = System.Numerics.Quaternion;\r\nusing Vector2 = System.Numerics.Vector2;\r\nusing Vector3 = System.Numerics.Vector3;\r\n\r\n/// <summary>\r\n/// Converts the skinned meshes in a glTF/GLB model to Source 2 model-DMX. ModelDoc does\r\n/// not accept glTF as a RenderMeshFile, while DMX preserves the same skeleton, vertices,\r\n/// materials and four-weight skinning without an external converter.\r\n/// </summary>\r\npublic static class GltfModelDmxWriter\r\n{\r\n    private const float MetersToCentimeters = 100f;\r\n\r\n    /// <summary>Writes a Y-up, centimeter model-DMX for the already imported target rig.</summary>\r\n    public static string Write(\r\n        byte[] data, SkeletonModel skeleton, string name,\r\n        Func<string, byte[]>? externalBufferResolver = null)\r\n    {\r\n        ArgumentNullException.ThrowIfNull(data);\r\n        ArgumentNullException.ThrowIfNull(skeleton);\r\n        ArgumentNullException.ThrowIfNull(name);\r\n\r\n        var document = GltfDocument.Parse(data, externalBufferResolver);\r\n        var parts = ReadMeshParts(document, skeleton);\r\n        if (parts.Count == 0)\r\n            throw new FormatException(\"glTF contains no supported mesh primitives.\");\r\n        return Emit(skeleton, name, parts);\r\n    }\r\n\r\n    private sealed class MeshPart\r\n    {\r\n        public required string Name;\r\n        public required string Material;\r\n        public required Vector3[] Positions;\r\n        public required Vector3[] Normals;\r\n        public required Vector2[] TexCoords;\r\n        public required int[] Triangles;\r\n        public required float[] Weights;\r\n        public required int[] Joints;\r\n    }\r\n\r\n    private static List<MeshPart> ReadMeshParts(GltfDocument document, SkeletonModel skeleton)\r\n    {\r\n        var root = document.Root;\r\n        if (!root.TryGetProperty(\"nodes\", out var nodeArray)\r\n            || !root.TryGetProperty(\"meshes\", out var meshArray))\r\n            return new List<MeshPart>();\r\n\r\n        root.TryGetProperty(\"skins\", out var skinArray);\r\n        root.TryGetProperty(\"materials\", out var materialArray);\r\n        var worlds = NodeWorlds(document);\r\n        var bonesByName = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);\r\n        for (var i = 0; i < skeleton.Count; i++)\r\n            bonesByName[skeleton[i].Name] = i;\r\n\r\n        var parts = new List<MeshPart>();\r\n        var usedNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);\r\n        for (var nodeIndex = 0; nodeIndex < nodeArray.GetArrayLength(); nodeIndex++)\r\n        {\r\n            var node = nodeArray[nodeIndex];\r\n            if (!node.TryGetProperty(\"mesh\", out var meshProperty))\r\n                continue;\r\n            var meshIndex = meshProperty.GetInt32();\r\n            if (meshIndex < 0 || meshIndex >= meshArray.GetArrayLength())\r\n                throw new FormatException($\"glTF node {nodeIndex} references invalid mesh {meshIndex}.\");\r\n            var mesh = meshArray[meshIndex];\r\n            if (!mesh.TryGetProperty(\"primitives\", out var primitives))\r\n                continue;\r\n\r\n            var skinIndex = node.TryGetProperty(\"skin\", out var skinProperty)\r\n                ? skinProperty.GetInt32() : -1;\r\n            var skinJoints = MapSkinJoints(\r\n                document, skeleton, bonesByName, skinArray, skinIndex);\r\n            var skinTransforms = SkinTransforms(document, skinArray, skinIndex, worlds, skinJoints, skeleton);\r\n            var normalMatrix = NormalMatrix(worlds[nodeIndex]);\r\n            var primitiveIndex = 0;\r\n            foreach (var primitive in primitives.EnumerateArray())\r\n            {\r\n                if (!primitive.TryGetProperty(\"attributes\", out var attributes)\r\n                    || !attributes.TryGetProperty(\"POSITION\", out var positionProperty))\r\n                {\r\n                    primitiveIndex++;\r\n                    continue;\r\n                }\r\n\r\n                var positions = new Accessor(document, positionProperty.GetInt32(), 3);\r\n                var vertexCount = positions.Count;\r\n                if (vertexCount == 0)\r\n                {\r\n                    primitiveIndex++;\r\n                    continue;\r\n                }\r\n\r\n                var transformedPositions = new Vector3[vertexCount];\r\n                ReadSkinning(document, attributes, vertexCount, skinJoints,\r\n                    out var weights, out var joints);\r\n                var vertexTransforms = new Matrix4x4[vertexCount];\r\n                for (var i = 0; i < vertexCount; i++)\r\n                {\r\n                    var transform = worlds[nodeIndex];\r\n                    if (skinTransforms is not null)\r\n                    {\r\n                        transform = default;\r\n                        for (var influence = 0; influence < 4; influence++)\r\n                        {\r\n                            var at = i * 4 + influence;\r\n                            if (weights[at] > 0f)\r\n                                transform += skinTransforms[joints[at]] * weights[at];\r\n                        }\r\n                    }\r\n                    vertexTransforms[i] = transform;\r\n                    var value = new Vector3(\r\n                        positions.Float(i, 0), positions.Float(i, 1), positions.Float(i, 2));\r\n                    transformedPositions[i] = Vector3.Transform(value, transform)\r\n                        * MetersToCentimeters;\r\n                }\r\n\r\n                var rawIndices = ReadIndices(document, primitive, vertexCount);\r\n                var mode = primitive.TryGetProperty(\"mode\", out var modeProperty)\r\n                    ? modeProperty.GetInt32() : 4;\r\n                var triangles = Triangulate(rawIndices, mode);\r\n\r\n                var normals = new Vector3[vertexCount];\r\n                if (attributes.TryGetProperty(\"NORMAL\", out var normalProperty))\r\n                {\r\n                    var source = new Accessor(document, normalProperty.GetInt32(), 3);\r\n                    RequireCount(source, vertexCount, \"NORMAL\");\r\n                    for (var i = 0; i < vertexCount; i++)\r\n                    {\r\n                        var value = new Vector3(\r\n                            source.Float(i, 0), source.Float(i, 1), source.Float(i, 2));\r\n                        var transform = skinTransforms is null ? normalMatrix : NormalMatrix(vertexTransforms[i]);\r\n                        normals[i] = NormalizeOr(Vector3.TransformNormal(value, transform), Vector3.UnitY);\r\n                    }\r\n                }\r\n                else\r\n                {\r\n                    GenerateNormals(transformedPositions, triangles, normals);\r\n                }\r\n\r\n                var texCoords = new Vector2[vertexCount];\r\n                if (attributes.TryGetProperty(\"TEXCOORD_0\", out var texCoordProperty))\r\n                {\r\n                    var source = new Accessor(document, texCoordProperty.GetInt32(), 2);\r\n                    RequireCount(source, vertexCount, \"TEXCOORD_0\");\r\n                    for (var i = 0; i < vertexCount; i++)\r\n                        texCoords[i] = new Vector2(source.Float(i, 0), source.Float(i, 1));\r\n                }\r\n\r\n                var baseName = node.TryGetProperty(\"name\", out var nodeName)\r\n                    ? nodeName.GetString()\r\n                    : mesh.TryGetProperty(\"name\", out var meshName) ? meshName.GetString() : null;\r\n                var partName = UniqueName(\r\n                    Sanitize(baseName ?? $\"mesh_{meshIndex}\") + $\"_{primitiveIndex}\", usedNames);\r\n                parts.Add(new MeshPart\r\n                {\r\n                    Name = partName,\r\n                    Material = MaterialName(materialArray, primitive),\r\n                    Positions = transformedPositions,\r\n                    Normals = normals,\r\n                    TexCoords = texCoords,\r\n                    Triangles = triangles,\r\n                    Weights = weights,\r\n                    Joints = joints,\r\n                });\r\n                primitiveIndex++;\r\n            }\r\n        }\r\n        return parts;\r\n    }\r\n\r\n    private static Matrix4x4[] NodeWorlds(GltfDocument document)\r\n    {\r\n        var result = new Matrix4x4[document.Nodes.Count];\r\n        var state = new byte[document.Nodes.Count];\r\n\r\n        Matrix4x4 Visit(int index)\r\n        {\r\n            if (state[index] == 2)\r\n                return result[index];\r\n            if (state[index] == 1)\r\n                throw new FormatException(\"glTF node graph contains a cycle.\");\r\n            state[index] = 1;\r\n            var node = document.Nodes[index];\r\n            var local = Matrix4x4.CreateScale(node.Scale)\r\n                * Matrix4x4.CreateFromQuaternion(node.Rotation)\r\n                * Matrix4x4.CreateTranslation(node.Translation);\r\n            result[index] = node.Parent < 0 ? local : local * Visit(node.Parent);\r\n            state[index] = 2;\r\n            return result[index];\r\n        }\r\n\r\n        for (var i = 0; i < result.Length; i++)\r\n            Visit(i);\r\n        return result;\r\n    }\r\n\r\n    private static Matrix4x4 NormalMatrix(Matrix4x4 world)\r\n    {\r\n        if (!Matrix4x4.Invert(world, out var inverse))\r\n            return Matrix4x4.Identity;\r\n        return Matrix4x4.Transpose(inverse);\r\n    }\r\n\r\n    // Bake the authored skin into the node rest pose before DMX generates new inverse\r\n    // binds. glTF skinned vertices use inverseBind * jointWorld, NOT meshNodeWorld.\r\n    // Keeping the full matrices here also bakes inherited scale into the rigid DMX rig.\r\n    private static Dictionary<int, Matrix4x4>? SkinTransforms(\r\n        GltfDocument document, JsonElement skins, int skinIndex,\r\n        Matrix4x4[] worlds, int[] mappedJoints, SkeletonModel skeleton)\r\n    {\r\n        if (mappedJoints.Length == 0)\r\n            return null;\r\n        var skin = skins[skinIndex];\r\n        var nodes = skin.GetProperty(\"joints\");\r\n        var inverseBinds = skin.TryGetProperty(\"inverseBindMatrices\", out var property)\r\n            ? new Accessor(document, property.GetInt32(), 16) : null;\r\n        if (inverseBinds is not null)\r\n            RequireCount(inverseBinds, mappedJoints.Length, \"inverseBindMatrices\");\r\n        var result = new Dictionary<int, Matrix4x4>();\r\n        for (var i = 0; i < mappedJoints.Length; i++)\r\n        {\r\n            var inverse = Matrix4x4.Identity;\r\n            if (inverseBinds is not null)\r\n                inverse = ReadMatrix(inverseBinds, i);\r\n            // The target can use the authored skin bind instead of the posed scene TRS.\r\n            // Rebind the mesh to that exact skeleton; retain scale omitted by XForm.\r\n            Matrix4x4.Decompose(worlds[nodes[i].GetInt32()], out var scale, out _, out _);\r\n            var rest = skeleton.RestWorld[mappedJoints[i]];\r\n            var jointWorld = Matrix4x4.CreateScale(scale)\r\n                * Matrix4x4.CreateFromQuaternion(rest.Rot)\r\n                * Matrix4x4.CreateTranslation(rest.Pos / MetersToCentimeters);\r\n            result[mappedJoints[i]] = inverse * jointWorld;\r\n        }\r\n        return result;\r\n    }\r\n\r\n    internal static SkeletonModel WithSkinBindPose(GltfDocument document, SkeletonModel skeleton)\r\n    {\r\n        if (!document.Root.TryGetProperty(\"skins\", out var skins))\r\n            return skeleton;\r\n        var byName = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);\r\n        for (var i = 0; i < skeleton.Count; i++)\r\n            byName[Sanitize(skeleton[i].Name)] = i;\r\n        var binds = new Dictionary<int, XForm>();\r\n        var nodeWorlds = NodeWorlds(document);\r\n        for (var s = 0; s < skins.GetArrayLength(); s++)\r\n        {\r\n            if (!skins[s].TryGetProperty(\"inverseBindMatrices\", out var property))\r\n                continue;\r\n            var joints = MapSkinJoints(document, skeleton, byName, skins, s);\r\n            var accessor = new Accessor(document, property.GetInt32(), 16);\r\n            RequireCount(accessor, joints.Length, \"inverseBindMatrices\");\r\n            for (var j = 0; j < joints.Length; j++)\r\n            {\r\n                if (!Matrix4x4.Invert(ReadMatrix(accessor, j), out var matrix))\r\n                    throw new FormatException(\"glTF skin has a singular inverse bind matrix.\");\r\n                // Some exporters fold a bind-shape scale into these matrices. Those\r\n                // are valid for skinning but cannot replace the scene's rigid rest.\r\n                // Zero-offset scaffold joints do not describe the character's scale.\r\n                if (skeleton[joints[j]].RestLocal.Pos.LengthSquared() > 1e-6f)\r\n                {\r\n                    Matrix4x4.Decompose(matrix, out var bindScale, out _, out _);\r\n                    var node = skins[s].GetProperty(\"joints\")[j].GetInt32();\r\n                    Matrix4x4.Decompose(nodeWorlds[node], out var sceneScale, out _, out _);\r\n                    if (Vector3.Distance(bindScale, sceneScale) > .001f * sceneScale.Length())\r\n                        return skeleton;\r\n                }\r\n                var bind = FbxTransform.ToRigid(matrix);\r\n                bind.Pos *= MetersToCentimeters;\r\n                if (binds.TryGetValue(joints[j], out var previous)\r\n                    && (Vector3.Distance(previous.Pos, bind.Pos) > .01f\r\n                        || MathQ.AngleBetween(previous.Rot, bind.Rot) > .001f))\r\n                    return skeleton; // Different per-mesh bind spaces cannot define one rig rest.\r\n                binds[joints[j]] = bind;\r\n            }\r\n        }\r\n        if (binds.Count == 0)\r\n            return skeleton;\r\n        // Bind matrices may use an origin below the displayed scene. Preserve the\r\n        // scene's floor placement (glTF is Y-up), rather than burying the new rest.\r\n        var sceneFloor = float.PositiveInfinity;\r\n        var bindFloor = float.PositiveInfinity;\r\n        foreach (var pair in binds)\r\n        {\r\n            sceneFloor = MathF.Min(sceneFloor, skeleton.RestWorld[pair.Key].Pos.Y);\r\n            bindFloor = MathF.Min(bindFloor, pair.Value.Pos.Y);\r\n        }\r\n        var placement = new Vector3(0f, sceneFloor - bindFloor, 0f);\r\n        var world = new XForm[skeleton.Count];\r\n        var definitions = new List<BoneDefinition>();\r\n        for (var i = 0; i < skeleton.Count; i++)\r\n        {\r\n            var bone = skeleton[i];\r\n            var parent = bone.ParentIndex;\r\n            world[i] = binds.TryGetValue(i, out var bind) ? bind\r\n                : parent < 0 ? bone.RestLocal : XForm.Compose(world[parent], bone.RestLocal);\r\n            if (binds.ContainsKey(i))\r\n                world[i].Pos += placement;\r\n            var local = parent < 0 ? world[i] : XForm.ToLocal(world[parent], world[i]);\r\n            definitions.Add(new BoneDefinition(bone.Name, parent < 0 ? null : skeleton[parent].Name, local));\r\n        }\r\n        return SkeletonModel.Create(definitions);\r\n    }\r\n\r\n    private static Matrix4x4 ReadMatrix(Accessor source, int i) => new(\r\n        source.Float(i, 0), source.Float(i, 1), source.Float(i, 2), source.Float(i, 3),\r\n        source.Float(i, 4), source.Float(i, 5), source.Float(i, 6), source.Float(i, 7),\r\n        source.Float(i, 8), source.Float(i, 9), source.Float(i, 10), source.Float(i, 11),\r\n        source.Float(i, 12), source.Float(i, 13), source.Float(i, 14), source.Float(i, 15));\r\n\r\n    private static int[] MapSkinJoints(\r\n        GltfDocument document, SkeletonModel skeleton, Dictionary<string, int> bonesByName,\r\n        JsonElement skinArray, int skinIndex)\r\n    {\r\n        if (skinIndex < 0)\r\n            return Array.Empty<int>();\r\n        if (skinArray.ValueKind != JsonValueKind.Array || skinIndex >= skinArray.GetArrayLength())\r\n            throw new FormatException($\"glTF mesh references invalid skin {skinIndex}.\");\r\n        var skin = skinArray[skinIndex];\r\n        if (!skin.TryGetProperty(\"joints\", out var joints))\r\n            return Array.Empty<int>();\r\n\r\n        var result = new int[joints.GetArrayLength()];\r\n        for (var i = 0; i < result.Length; i++)\r\n        {\r\n            var nodeIndex = joints[i].GetInt32();\r\n            if (nodeIndex < 0 || nodeIndex >= document.Nodes.Count)\r\n                throw new FormatException($\"glTF skin references invalid joint node {nodeIndex}.\");\r\n            var raw = document.Nodes[nodeIndex].Name ?? $\"node_{nodeIndex}\";\r\n            var safe = Sanitize(raw);\r\n            if (!bonesByName.TryGetValue(safe, out var bone)\r\n                && !bonesByName.TryGetValue(Sanitize(raw + \"#\" + nodeIndex), out bone))\r\n            {\r\n                throw new FormatException(\r\n                    $\"glTF skin joint '{raw}' is absent from the imported target skeleton.\");\r\n            }\r\n            if (bone < 0 || bone >= skeleton.Count)\r\n                throw new FormatException($\"glTF skin joint '{raw}' mapped outside the target skeleton.\");\r\n            result[i] = bone;\r\n        }\r\n        return result;\r\n    }\r\n\r\n    private static void ReadSkinning(\r\n        GltfDocument document, JsonElement attributes, int vertexCount, int[] skinJoints,\r\n        out float[] weights, out int[] joints)\r\n    {\r\n        weights = new float[checked(vertexCount * 4)];\r\n        joints = new int[checked(vertexCount * 4)];\r\n        if (skinJoints.Length == 0\r\n            || !attributes.TryGetProperty(\"JOINTS_0\", out var jointProperty)\r\n            || !attributes.TryGetProperty(\"WEIGHTS_0\", out var weightProperty))\r\n        {\r\n            for (var i = 0; i < vertexCount; i++)\r\n            {\r\n                weights[i * 4] = 1f;\r\n                joints[i * 4] = skinJoints.Length > 0 ? skinJoints[0] : 0;\r\n            }\r\n            return;\r\n        }\r\n\r\n        var jointSource = new Accessor(document, jointProperty.GetInt32(), 4);\r\n        var weightSource = new Accessor(document, weightProperty.GetInt32(), 4);\r\n        RequireCount(jointSource, vertexCount, \"JOINTS_0\");\r\n        RequireCount(weightSource, vertexCount, \"WEIGHTS_0\");\r\n        for (var vertex = 0; vertex < vertexCount; vertex++)\r\n        {\r\n            var total = 0f;\r\n            for (var influence = 0; influence < 4; influence++)\r\n            {\r\n                var skinJoint = jointSource.Unsigned(vertex, influence);\r\n                if (skinJoint < 0 || skinJoint >= skinJoints.Length)\r\n                    throw new FormatException($\"glTF JOINTS_0 references invalid skin joint {skinJoint}.\");\r\n                var at = vertex * 4 + influence;\r\n                joints[at] = skinJoints[skinJoint];\r\n                var weight = weightSource.Float(vertex, influence);\r\n                weights[at] = float.IsFinite(weight) && weight > 0f ? weight : 0f;\r\n                total += weights[at];\r\n            }\r\n            if (total <= 1e-8f)\r\n            {\r\n                weights[vertex * 4] = 1f;\r\n                joints[vertex * 4] = skinJoints[0];\r\n                continue;\r\n            }\r\n            for (var influence = 0; influence < 4; influence++)\r\n                weights[vertex * 4 + influence] /= total;\r\n        }\r\n    }\r\n\r\n    private static int[] ReadIndices(\r\n        GltfDocument document, JsonElement primitive, int vertexCount)\r\n    {\r\n        if (!primitive.TryGetProperty(\"indices\", out var indexProperty))\r\n        {\r\n            var sequential = new int[vertexCount];\r\n            for (var i = 0; i < sequential.Length; i++)\r\n                sequential[i] = i;\r\n            return sequential;\r\n        }\r\n\r\n        var source = new Accessor(document, indexProperty.GetInt32(), 1);\r\n        var result = new int[source.Count];\r\n        for (var i = 0; i < result.Length; i++)\r\n        {\r\n            result[i] = source.Unsigned(i, 0);\r\n            if (result[i] < 0 || result[i] >= vertexCount)\r\n                throw new FormatException($\"glTF index {result[i]} exceeds vertex count {vertexCount}.\");\r\n        }\r\n        return result;\r\n    }\r\n\r\n    private static int[] Triangulate(int[] indices, int mode)\r\n    {\r\n        var triangles = new List<int>();\r\n        if (mode == 4) // TRIANGLES\r\n        {\r\n            if (indices.Length % 3 != 0)\r\n                throw new FormatException(\"glTF triangle index count is not divisible by three.\");\r\n            triangles.AddRange(indices);\r\n        }\r\n        else if (mode == 5) // TRIANGLE_STRIP\r\n        {\r\n            for (var i = 2; i < indices.Length; i++)\r\n            {\r\n                var a = indices[i - 2];\r\n                var b = indices[i - 1];\r\n                var c = indices[i];\r\n                if ((i & 1) != 0)\r\n                    (a, b) = (b, a);\r\n                if (a != b && b != c && a != c)\r\n                {\r\n                    triangles.Add(a);\r\n                    triangles.Add(b);\r\n                    triangles.Add(c);\r\n                }\r\n            }\r\n        }\r\n        else if (mode == 6) // TRIANGLE_FAN\r\n        {\r\n            for (var i = 2; i < indices.Length; i++)\r\n            {\r\n                if (indices[0] == indices[i - 1] || indices[i - 1] == indices[i]\r\n                    || indices[0] == indices[i])\r\n                    continue;\r\n                triangles.Add(indices[0]);\r\n                triangles.Add(indices[i - 1]);\r\n                triangles.Add(indices[i]);\r\n            }\r\n        }\r\n        else\r\n        {\r\n            throw new FormatException($\"glTF primitive mode {mode} is not a triangle mesh.\");\r\n        }\r\n        return triangles.ToArray();\r\n    }\r\n\r\n    private static void GenerateNormals(Vector3[] positions, int[] triangles, Vector3[] normals)\r\n    {\r\n        for (var i = 0; i + 2 < triangles.Length; i += 3)\r\n        {\r\n            var a = triangles[i];\r\n            var b = triangles[i + 1];\r\n            var c = triangles[i + 2];\r\n            var normal = Vector3.Cross(positions[b] - positions[a], positions[c] - positions[a]);\r\n            normals[a] += normal;\r\n            normals[b] += normal;\r\n            normals[c] += normal;\r\n        }\r\n        for (var i = 0; i < normals.Length; i++)\r\n            normals[i] = NormalizeOr(normals[i], Vector3.UnitY);\r\n    }\r\n\r\n    private static Vector3 NormalizeOr(Vector3 value, Vector3 fallback)\r\n        => value.LengthSquared() > 1e-12f ? Vector3.Normalize(value) : fallback;\r\n\r\n    private static void RequireCount(Accessor accessor, int expected, string semantic)\r\n    {\r\n        if (accessor.Count != expected)\r\n            throw new FormatException(\r\n                $\"glTF {semantic} has {accessor.Count} entries; expected {expected}.\");\r\n    }\r\n\r\n    private static string MaterialName(JsonElement materials, JsonElement primitive)\r\n    {\r\n        if (!primitive.TryGetProperty(\"material\", out var materialProperty))\r\n            return \"default\";\r\n        var index = materialProperty.GetInt32();\r\n        if (materials.ValueKind != JsonValueKind.Array || index < 0 || index >= materials.GetArrayLength())\r\n            throw new FormatException($\"glTF primitive references invalid material {index}.\");\r\n        var material = materials[index];\r\n        return material.TryGetProperty(\"name\", out var name) && !string.IsNullOrEmpty(name.GetString())\r\n            ? name.GetString()!\r\n            : $\"material_{index}\";\r\n    }\r\n\r\n    private static string Sanitize(string value)\r\n    {\r\n        var result = new StringBuilder(value.Length);\r\n        foreach (var c in value)\r\n            result.Append(char.IsLetterOrDigit(c) || c == '_' ? c : '_');\r\n        return result.Length > 0 ? result.ToString() : \"unnamed\";\r\n    }\r\n\r\n    private static string UniqueName(string value, HashSet<string> used)\r\n    {\r\n        var candidate = value;\r\n        var suffix = 2;\r\n        while (!used.Add(candidate))\r\n            candidate = value + \"_\" + suffix++;\r\n        return candidate;\r\n    }\r\n\r\n    private sealed class Accessor\r\n    {\r\n        private readonly byte[] _buffer;\r\n        private readonly int _start;\r\n        private readonly int _stride;\r\n        private readonly int _componentSize;\r\n        private readonly int _componentType;\r\n        private readonly bool _normalized;\r\n\r\n        public int Count { get; }\r\n        public int Components { get; }\r\n\r\n        public Accessor(GltfDocument document, int index, int expectedComponents)\r\n        {\r\n            var root = document.Root;\r\n            if (!root.TryGetProperty(\"accessors\", out var accessors)\r\n                || index < 0 || index >= accessors.GetArrayLength())\r\n                throw new FormatException($\"glTF accessor {index} does not exist.\");\r\n            var accessor = accessors[index];\r\n            if (accessor.TryGetProperty(\"sparse\", out _))\r\n                throw new FormatException(\"Sparse glTF mesh accessors are not supported.\");\r\n\r\n            Components = accessor.GetProperty(\"type\").GetString() switch\r\n            {\r\n                \"SCALAR\" => 1,\r\n                \"VEC2\" => 2,\r\n                \"VEC3\" => 3,\r\n                \"VEC4\" => 4,\r\n                \"MAT4\" => 16,\r\n                var type => throw new FormatException($\"Unsupported glTF accessor type '{type}'.\"),\r\n            };\r\n            if (Components != expectedComponents)\r\n                throw new FormatException(\r\n                    $\"glTF accessor {index} has {Components} components; expected {expectedComponents}.\");\r\n\r\n            Count = accessor.GetProperty(\"count\").GetInt32();\r\n            if (Count < 0)\r\n                throw new FormatException($\"glTF accessor {index} has a negative count.\");\r\n            _componentType = accessor.GetProperty(\"componentType\").GetInt32();\r\n            _componentSize = _componentType switch\r\n            {\r\n                5120 or 5121 => 1,\r\n                5122 or 5123 => 2,\r\n                5125 or 5126 => 4,\r\n                _ => throw new FormatException(\r\n                    $\"Unsupported glTF accessor component type {_componentType}.\"),\r\n            };\r\n            _normalized = accessor.TryGetProperty(\"normalized\", out var normalized)\r\n                && normalized.GetBoolean();\r\n\r\n            if (!accessor.TryGetProperty(\"bufferView\", out var viewProperty))\r\n            {\r\n                _buffer = Array.Empty<byte>();\r\n                _start = 0;\r\n                _stride = checked(Components * _componentSize);\r\n                return;\r\n            }\r\n\r\n            var views = root.GetProperty(\"bufferViews\");\r\n            var viewIndex = viewProperty.GetInt32();\r\n            if (viewIndex < 0 || viewIndex >= views.GetArrayLength())\r\n                throw new FormatException($\"glTF bufferView {viewIndex} does not exist.\");\r\n            var view = views[viewIndex];\r\n            var bufferIndex = view.GetProperty(\"buffer\").GetInt32();\r\n            if (bufferIndex < 0 || bufferIndex >= document.Buffers.Count)\r\n                throw new FormatException($\"glTF buffer {bufferIndex} does not exist.\");\r\n            _buffer = document.Buffers[bufferIndex];\r\n            var viewOffset = view.TryGetProperty(\"byteOffset\", out var vo) ? vo.GetInt32() : 0;\r\n            var accessorOffset = accessor.TryGetProperty(\"byteOffset\", out var ao) ? ao.GetInt32() : 0;\r\n            _start = checked(viewOffset + accessorOffset);\r\n            var elementSize = checked(Components * _componentSize);\r\n            _stride = view.TryGetProperty(\"byteStride\", out var stride)\r\n                ? stride.GetInt32() : elementSize;\r\n            if (_stride < elementSize)\r\n                throw new FormatException(\"glTF accessor stride is smaller than its element.\");\r\n            var end = Count == 0 ? _start : (long)_start + (long)(Count - 1) * _stride + elementSize;\r\n            if (_start < 0 || end > _buffer.Length)\r\n                throw new FormatException($\"glTF accessor {index} reads beyond its buffer.\");\r\n        }\r\n\r\n        public float Float(int element, int component)\r\n        {\r\n            if (_buffer.Length == 0)\r\n                return 0f;\r\n            var offset = Offset(element, component);\r\n            return _componentType switch\r\n            {\r\n                5120 => _normalized\r\n                    ? MathF.Max(unchecked((sbyte)_buffer[offset]) / 127f, -1f)\r\n                    : unchecked((sbyte)_buffer[offset]),\r\n                5121 => _normalized ? _buffer[offset] / 255f : _buffer[offset],\r\n                5122 => _normalized\r\n                    ? MathF.Max(BitConverter.ToInt16(_buffer, offset) / 32767f, -1f)\r\n                    : BitConverter.ToInt16(_buffer, offset),\r\n                5123 => _normalized\r\n                    ? BitConverter.ToUInt16(_buffer, offset) / 65535f\r\n                    : BitConverter.ToUInt16(_buffer, offset),\r\n                5125 => BitConverter.ToUInt32(_buffer, offset),\r\n                _ => BitConverter.ToSingle(_buffer, offset),\r\n            };\r\n        }\r\n\r\n        public int Unsigned(int element, int component)\r\n        {\r\n            if (_buffer.Length == 0)\r\n                return 0;\r\n            var offset = Offset(element, component);\r\n            return _componentType switch\r\n            {\r\n                5121 => _buffer[offset],\r\n                5123 => BitConverter.ToUInt16(_buffer, offset),\r\n                5125 => checked((int)BitConverter.ToUInt32(_buffer, offset)),\r\n                _ => throw new FormatException(\r\n                    $\"glTF indices require an unsigned integer accessor, got {_componentType}.\"),\r\n            };\r\n        }\r\n\r\n        private int Offset(int element, int component)\r\n        {\r\n            if (element < 0 || element >= Count || component < 0 || component >= Components)\r\n                throw new FormatException(\"glTF accessor index is out of range.\");\r\n            return checked(_start + element * _stride + component * _componentSize);\r\n        }\r\n    }\r\n\r\n    private static string Emit(SkeletonModel skeleton, string name, IReadOnlyList<MeshPart> parts)\r\n    {\r\n        var writer = new Kv2Writer();\r\n        var modelId = Id(name, \"model\");\r\n        var jointIds = new string[skeleton.Count];\r\n        for (var i = 0; i < skeleton.Count; i++)\r\n            jointIds[i] = Id(name, \"joint:\" + skeleton[i].Name);\r\n        var dagIds = new string[parts.Count];\r\n        var meshIds = new string[parts.Count];\r\n        var vertexIds = new string[parts.Count];\r\n        for (var i = 0; i < parts.Count; i++)\r\n        {\r\n            dagIds[i] = Id(name, $\"dag:{i}:{parts[i].Name}\");\r\n            meshIds[i] = Id(name, $\"mesh:{i}:{parts[i].Name}\");\r\n            vertexIds[i] = Id(name, $\"vertices:{i}:{parts[i].Name}\");\r\n        }\r\n\r\n        writer.Raw(\"<!-- dmx encoding keyvalues2_noids 4 format model 22 -->\");\r\n        writer.BeginTop(\"DmElement\");\r\n        writer.Attr(\"name\", \"string\", \"root\");\r\n        writer.Attr(\"model\", \"element\", modelId);\r\n        writer.Attr(\"skeleton\", \"element\", modelId);\r\n        writer.EndTop();\r\n\r\n        writer.BeginTop(\"DmeModel\");\r\n        writer.Attr(\"id\", \"elementid\", modelId);\r\n        writer.Attr(\"name\", \"string\", name);\r\n        WriteTransform(writer, \"transform\", Vector3.Zero, Quaternion.Identity);\r\n        writer.Attr(\"visible\", \"bool\", \"1\");\r\n        var children = new List<string>();\r\n        for (var i = 0; i < skeleton.Count; i++)\r\n            if (skeleton[i].ParentIndex < 0)\r\n                children.Add(jointIds[i]);\r\n        children.AddRange(dagIds);\r\n        WriteRefs(writer, \"children\", children);\r\n        WriteRefs(writer, \"jointList\", jointIds);\r\n        writer.Attr(\"upAxis\", \"string\", \"Y\");\r\n        writer.BeginInline(\"axisSystem\", \"DmeAxisSystem\");\r\n        writer.Attr(\"upAxis\", \"int\", \"2\");\r\n        writer.Attr(\"forwardParity\", \"int\", \"2\");\r\n        writer.Attr(\"coordSys\", \"int\", \"0\");\r\n        writer.EndInline();\r\n        writer.EndTop();\r\n\r\n        for (var i = 0; i < skeleton.Count; i++)\r\n        {\r\n            var bone = skeleton[i];\r\n            writer.BeginTop(\"DmeJoint\");\r\n            writer.Attr(\"id\", \"elementid\", jointIds[i]);\r\n            writer.Attr(\"name\", \"string\", bone.Name);\r\n            WriteTransform(writer, \"transform\", bone.RestLocal.Pos, bone.RestLocal.Rot);\r\n            writer.Attr(\"visible\", \"bool\", \"1\");\r\n            var boneChildren = new List<string>();\r\n            for (var child = 0; child < skeleton.Count; child++)\r\n                if (skeleton[child].ParentIndex == i)\r\n                    boneChildren.Add(jointIds[child]);\r\n            if (boneChildren.Count > 0)\r\n                WriteRefs(writer, \"children\", boneChildren);\r\n            writer.EndTop();\r\n        }\r\n\r\n        for (var i = 0; i < parts.Count; i++)\r\n        {\r\n            var part = parts[i];\r\n            writer.BeginTop(\"DmeDag\");\r\n            writer.Attr(\"id\", \"elementid\", dagIds[i]);\r\n            writer.Attr(\"name\", \"string\", part.Name);\r\n            WriteTransform(writer, \"transform\", Vector3.Zero, Quaternion.Identity);\r\n            writer.Attr(\"shape\", \"element\", meshIds[i]);\r\n            writer.Attr(\"visible\", \"bool\", \"1\");\r\n            writer.EndTop();\r\n\r\n            writer.BeginTop(\"DmeMesh\");\r\n            writer.Attr(\"id\", \"elementid\", meshIds[i]);\r\n            writer.Attr(\"name\", \"string\", part.Name);\r\n            writer.Attr(\"visible\", \"bool\", \"1\");\r\n            writer.Attr(\"currentState\", \"element\", vertexIds[i]);\r\n            WriteRefs(writer, \"baseStates\", new[] { vertexIds[i] });\r\n            writer.BeginArray(\"faceSets\");\r\n            writer.BeginArrayElement(\"DmeFaceSet\");\r\n            writer.Attr(\"name\", \"string\", part.Material);\r\n            writer.BeginArray(\"faces\", \"int_array\");\r\n            for (var index = 0; index < part.Triangles.Length; index++)\r\n            {\r\n                writer.Value(part.Triangles[index].ToString(CultureInfo.InvariantCulture), false);\r\n                if (index % 3 == 2)\r\n                    writer.Value(\"-1\", index == part.Triangles.Length - 1);\r\n            }\r\n            writer.EndArray();\r\n            writer.BeginInline(\"material\", \"DmeMaterial\");\r\n            writer.Attr(\"name\", \"string\", part.Material);\r\n            writer.Attr(\"mtlName\", \"string\", part.Material);\r\n            writer.EndInline();\r\n            writer.EndArrayElement(true);\r\n            writer.EndArray();\r\n            writer.EndTop();\r\n\r\n            writer.BeginTop(\"DmeVertexData\");\r\n            writer.Attr(\"id\", \"elementid\", vertexIds[i]);\r\n            writer.Attr(\"name\", \"string\", \"bind\");\r\n            writer.BeginArray(\"vertexFormat\", \"string_array\");\r\n            var formats = new[]\r\n                { \"position$0\", \"normal$0\", \"texcoord$0\", \"blendweights$0\", \"blendindices$0\" };\r\n            for (var format = 0; format < formats.Length; format++)\r\n                writer.Value(formats[format], format == formats.Length - 1);\r\n            writer.EndArray();\r\n            writer.Attr(\"jointCount\", \"int\", \"4\");\r\n            writer.Attr(\"flipVCoordinates\", \"bool\", \"0\");\r\n            WriteVectors(writer, \"position$0\", \"vector3_array\", part.Positions,\r\n                value => Vec(value));\r\n            WriteIdentityIndices(writer, \"position$0Indices\", part.Positions.Length);\r\n            WriteVectors(writer, \"normal$0\", \"vector3_array\", part.Normals,\r\n                value => Vec(value));\r\n            WriteIdentityIndices(writer, \"normal$0Indices\", part.Normals.Length);\r\n            WriteVectors(writer, \"texcoord$0\", \"vector2_array\", part.TexCoords,\r\n                value => $\"{F(value.X)} {F(value.Y)}\");\r\n            WriteIdentityIndices(writer, \"texcoord$0Indices\", part.TexCoords.Length);\r\n            WriteScalars(writer, \"blendweights$0\", \"float_array\", part.Weights,\r\n                value => F(value));\r\n            WriteScalars(writer, \"blendindices$0\", \"int_array\", part.Joints,\r\n                value => value.ToString(CultureInfo.InvariantCulture));\r\n            writer.EndTop();\r\n        }\r\n        return writer.ToString();\r\n    }\r\n\r\n    private static void WriteTransform(\r\n        Kv2Writer writer, string name, Vector3 position, Quaternion orientation)\r\n    {\r\n        writer.BeginInline(name, \"DmeTransform\");\r\n        writer.Attr(\"name\", \"string\", name);\r\n        writer.Attr(\"position\", \"vector3\", Vec(position));\r\n        writer.Attr(\"orientation\", \"quaternion\",\r\n            $\"{F(orientation.X)} {F(orientation.Y)} {F(orientation.Z)} {F(orientation.W)}\");\r\n        writer.Attr(\"scale\", \"float\", \"1\");\r\n        writer.EndInline();\r\n    }\r\n\r\n    private static void WriteRefs(Kv2Writer writer, string name, IReadOnlyList<string> ids)\r\n    {\r\n        writer.BeginArray(name);\r\n        for (var i = 0; i < ids.Count; i++)\r\n            writer.ElementRef(ids[i], i == ids.Count - 1);\r\n        writer.EndArray();\r\n    }\r\n\r\n    private static void WriteVectors<T>(\r\n        Kv2Writer writer, string name, string type, T[] values, Func<T, string> format)\r\n    {\r\n        writer.BeginArray(name, type);\r\n        for (var i = 0; i < values.Length; i++)\r\n            writer.Value(format(values[i]), i == values.Length - 1);\r\n        writer.EndArray();\r\n    }\r\n\r\n    private static void WriteScalars<T>(\r\n        Kv2Writer writer, string name, string type, T[] values, Func<T, string> format)\r\n        => WriteVectors(writer, name, type, values, format);\r\n\r\n    private static void WriteIdentityIndices(Kv2Writer writer, string name, int count)\r\n    {\r\n        writer.BeginArray(name, \"int_array\");\r\n        for (var i = 0; i < count; i++)\r\n            writer.Value(i.ToString(CultureInfo.InvariantCulture), i == count - 1);\r\n        writer.EndArray();\r\n    }\r\n\r\n    private static string Id(string name, string path)\r\n        => DmxWriter.ElementGuid(name, \"gltf-model:\" + path)\r\n            .ToString(\"D\", CultureInfo.InvariantCulture);\r\n\r\n    private static string F(float value)\r\n        => value == 0f ? \"0\" : ((double)value).ToString(\"0.##########\", CultureInfo.InvariantCulture);\r\n\r\n    private static string Vec(Vector3 value) => $\"{F(value.X)} {F(value.Y)} {F(value.Z)}\";\r\n\r\n    private sealed class Kv2Writer\r\n    {\r\n        private readonly StringBuilder _text = new();\r\n        private int _indent;\r\n\r\n        public void Raw(string value) => _text.Append(value).Append(\"\\r\\n\");\r\n\r\n        private void Line(string value)\r\n            => _text.Append('\\t', _indent).Append(value).Append(\"\\r\\n\");\r\n\r\n        public void Attr(string name, string type, string value)\r\n            => Line($\"\\\"{Escape(name)}\\\" \\\"{type}\\\" \\\"{Escape(value)}\\\"\");\r\n\r\n        public void BeginTop(string type)\r\n        {\r\n            Line($\"\\\"{type}\\\"\");\r\n            Line(\"{\");\r\n            _indent++;\r\n        }\r\n\r\n        public void EndTop()\r\n        {\r\n            _indent--;\r\n            Line(\"}\");\r\n            _text.Append(\"\\r\\n\");\r\n        }\r\n\r\n        public void BeginInline(string name, string type)\r\n        {\r\n            Line($\"\\\"{Escape(name)}\\\" \\\"{type}\\\"\");\r\n            Line(\"{\");\r\n            _indent++;\r\n        }\r\n\r\n        public void EndInline()\r\n        {\r\n            _indent--;\r\n            Line(\"}\");\r\n        }\r\n\r\n        public void BeginArray(string name, string type = \"element_array\")\r\n        {\r\n            Line($\"\\\"{Escape(name)}\\\" \\\"{type}\\\"\");\r\n            Line(\"[\");\r\n            _indent++;\r\n        }\r\n\r\n        public void EndArray()\r\n        {\r\n            _indent--;\r\n            Line(\"]\");\r\n        }\r\n\r\n        public void BeginArrayElement(string type)\r\n        {\r\n            Line($\"\\\"{type}\\\"\");\r\n            Line(\"{\");\r\n            _indent++;\r\n        }\r\n\r\n        public void EndArrayElement(bool last)\r\n        {\r\n            _indent--;\r\n            Line(last ? \"}\" : \"},\");\r\n        }\r\n\r\n        public void ElementRef(string id, bool last)\r\n            => Line($\"\\\"element\\\" \\\"{id}\\\"\" + (last ? \"\" : \",\"));\r\n\r\n        public void Value(string value, bool last)\r\n            => Line($\"\\\"{Escape(value)}\\\"\" + (last ? \"\" : \",\"));\r\n\r\n        private static string Escape(string value)\r\n            => value.Replace(\"\\\\\", \"\\\\\\\\\").Replace(\"\\\"\", \"\\\\\\\"\")\r\n                .Replace(\"\\r\", \" \").Replace(\"\\n\", \" \");\r\n\r\n        public override string ToString() => _text.ToString();\r\n    }\r\n}\r\n"
        },
        {
            "Ident": "notpointless.chomnr_humanoid_mocap",
            "Path": "HumanoidMocap/Formats/Renderware/RwDffSkeleton.cs",
            "FileName": "RwDffSkeleton.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 395388,
            "IsPrivate": false,
            "Code": "#nullable enable annotations\r\n\r\nusing System;\r\nusing System.Collections.Generic;\r\nusing System.Numerics;\r\nusing System.Text;\r\nusing HumanoidMocap.Maths;\r\n\r\nnamespace HumanoidMocap.Formats.Renderware;\r\n\r\nusing Vector3 = System.Numerics.Vector3; // s&box compat: shadow engine's global-namespace Vector3 (see Code/HumanoidMocap/Assembly.cs)\r\n\r\n/// <summary>One HAnim node of a parsed .dff skeleton, in HAnim node order \u2014 the order\r\n/// RwAnimAnimation keyframes address nodes in.</summary>\r\npublic sealed class RwDffNode\r\n{\r\n    /// <summary>HAnim node id (stable across FSB2 characters, e.g. 1000 = \"Bip01\").</summary>\r\n    public required int NodeId { get; init; }\r\n\r\n    /// <summary>\r\n    /// Bone name: the frame's authored name when the .dff carries one (FSB2 stores 3ds Max\r\n    /// Biped names like <c>Bip01 L Thigh</c> in the RpUserData extension), else the\r\n    /// synthesized stable fallback <c>rw_node_&lt;id&gt;</c>. Unique within the skeleton.\r\n    /// </summary>\r\n    public required string Name { get; init; }\r\n\r\n    /// <summary>Parent NODE index (into the node list), or -1 for the root node.</summary>\r\n    public required int ParentIndex { get; init; }\r\n\r\n    /// <summary>Rest (bind) transform relative to the parent NODE (intermediate non-HAnim\r\n    /// frames composed in), native .dff units/axes.</summary>\r\n    public required XForm RestLocal { get; init; }\r\n\r\n    /// <summary>HAnim PUSH/POP hierarchy flags from the node table (diagnostic).</summary>\r\n    public required uint Flags { get; init; }\r\n}\r\n\r\n/// <summary>Result of parsing a .dff model's skeleton.</summary>\r\npublic sealed class RwDffSkeletonData\r\n{\r\n    /// <summary>HAnim nodes in node-index order (== animation keyframe node order).</summary>\r\n    public required IReadOnlyList<RwDffNode> Nodes { get; init; }\r\n\r\n    /// <summary>Total FrameList frame count (HAnim and non-HAnim frames alike).</summary>\r\n    public required int FrameCount { get; init; }\r\n}\r\n\r\n/// <summary>\r\n/// RenderWare .dff model skeleton parser \u2014 reads ONLY the Clump's FrameList and the RpHAnim\r\n/// plugin data (geometry is skipped entirely).\r\n/// </summary>\r\n/// <remarks>\r\n/// <para><b>Layout</b> (verified against FSB2 <c>character.pak</c> models): Clump (0x10) \u2192\r\n/// struct (0x1) \u2192 FrameList (0xE) \u2192 struct (0x1) = {u32 numFrames, numFrames \u00d7 56-byte\r\n/// frames {f32 rot[9] row-major 3x3, f32 pos[3], i32 parentIndex, u32 flags}}, then one\r\n/// Extension (0x3) chunk PER frame containing optional sub-chunks: 0x11E = HAnimPLG\r\n/// {u32 version, u32 nodeId, u32 numNodes, [u32 flags, u32 keyFrameSize,\r\n/// numNodes \u00d7 {u32 nodeId, u32 nodeIndex, u32 nodeFlags}]} (exactly one frame owns the full\r\n/// node table), 0x11F = RpUserData (FSB2 stores the real 3ds Max bone name under the\r\n/// <c>name</c> attribute \u2014 the classic frame-name chunk 0x253F2FE is present but empty).</para>\r\n/// <para><b>Rotation matrices</b> are row-major with rows = the frame's basis vectors\r\n/// (RenderWare right/up/at), i.e. row-vector convention <c>v_parent = v_child \u00b7 M</c> \u2014\r\n/// the same convention as <see cref=\"Matrix4x4\"/>, so\r\n/// <see cref=\"Quaternion.CreateFromRotationMatrix\"/> converts directly (verified: FK over\r\n/// the FSB2 rig lands the feet at ground level and the head at ~184 cm).</para>\r\n/// <para><b>Node order and parents</b>: the HAnim node table order IS the animation\r\n/// keyframe node order. Each frame's own HAnimPLG carries its nodeId; a node's parent is\r\n/// the nearest ancestor FRAME that is itself an HAnim node (intermediate plain frames \u2014\r\n/// FSB2 has one root dummy \u2014 are composed into the node's rest transform).</para>\r\n/// </remarks>\r\npublic static class RwDffSkeleton\r\n{\r\n    /// <summary>Parses the skeleton (FrameList + HAnim) out of .dff bytes.</summary>\r\n    /// <exception cref=\"FormatException\">Malformed/truncated stream, or no HAnim data.</exception>\r\n    public static RwDffSkeletonData Parse(byte[] data)\r\n    {\r\n        ArgumentNullException.ThrowIfNull(data);\r\n\r\n        var (rootType, clumpStart, clumpSize) = RwStream.ReadChunk(data, 0, data.Length);\r\n        if (rootType != RwStream.ChunkClump)\r\n            throw new FormatException(\r\n                $\"Not a RenderWare model (.dff): expected a Clump chunk (0x10), found 0x{rootType:X}.\");\r\n        var clumpEnd = clumpStart + clumpSize;\r\n\r\n        // Find the FrameList inside the clump (the clump struct precedes it; geometry\r\n        // lists follow and are never visited \u2014 we stop at the first FrameList).\r\n        var offset = clumpStart;\r\n        while (offset < clumpEnd)\r\n        {\r\n            var (type, payloadStart, payloadSize) = RwStream.ReadChunk(data, offset, clumpEnd);\r\n            if (type == RwStream.ChunkFrameList)\r\n                return ParseFrameList(data, payloadStart, payloadStart + payloadSize);\r\n            offset = payloadStart + payloadSize;\r\n        }\r\n        throw new FormatException(\"RenderWare .dff has no FrameList chunk \u2014 cannot read a skeleton.\");\r\n    }\r\n\r\n    /// <summary>\r\n    /// Cheap probe used by skeleton resolution: the HAnim node count of .dff bytes, or null\r\n    /// when the bytes are not a parseable .dff with HAnim data. Never throws.\r\n    /// </summary>\r\n    public static int? PeekNodeCount(byte[] data)\r\n    {\r\n        try\r\n        {\r\n            return Parse(data).Nodes.Count;\r\n        }\r\n        catch (FormatException)\r\n        {\r\n            return null;\r\n        }\r\n    }\r\n\r\n    // ================================================================ frame list\r\n\r\n    private sealed class Frame\r\n    {\r\n        public required XForm Local;\r\n        public required int Parent;\r\n        public int NodeId = -1;         // HAnim node id, -1 when the frame has no HAnimPLG\r\n        public string Name = \"\";\r\n    }\r\n\r\n    private static RwDffSkeletonData ParseFrameList(byte[] data, int start, int end)\r\n    {\r\n        var (structType, structStart, structSize) = RwStream.ReadChunk(data, start, end);\r\n        if (structType != RwStream.ChunkStruct)\r\n            throw new FormatException(\"RenderWare FrameList: expected leading struct chunk.\");\r\n\r\n        var frameCount = RwStream.I32(data, structStart);\r\n        if (frameCount <= 0 || structSize < 4 + frameCount * 56)\r\n            throw new FormatException($\"RenderWare FrameList declares invalid frame count {frameCount}.\");\r\n\r\n        var frames = new List<Frame>(frameCount);\r\n        for (var i = 0; i < frameCount; i++)\r\n        {\r\n            var p = structStart + 4 + i * 56;\r\n            var local = ReadFrameTransform(data, p);\r\n            var parent = RwStream.I32(data, p + 48);\r\n            if (parent >= i || parent < -1)\r\n                throw new FormatException(\r\n                    $\"RenderWare FrameList: frame {i} has invalid parent index {parent}.\");\r\n            frames.Add(new Frame { Local = local, Parent = parent });\r\n        }\r\n\r\n        // One Extension chunk per frame, in frame order.\r\n        (int NodeId, int NodeIndex, uint Flags)[]? nodeTable = null;\r\n        var offset = structStart + structSize;\r\n        for (var i = 0; i < frameCount; i++)\r\n        {\r\n            var (extType, extStart, extSize) = RwStream.ReadChunk(data, offset, end);\r\n            if (extType != RwStream.ChunkExtension)\r\n                throw new FormatException(\r\n                    $\"RenderWare FrameList: expected Extension chunk for frame {i}, found 0x{extType:X}.\");\r\n            ParseFrameExtension(data, extStart, extStart + extSize, frames[i], ref nodeTable);\r\n            offset = extStart + extSize;\r\n        }\r\n\r\n        if (nodeTable is null)\r\n            throw new FormatException(\r\n                \"RenderWare .dff has no HAnim node table \u2014 the model carries no animatable skeleton.\");\r\n\r\n        return BuildNodes(frames, nodeTable, frameCount);\r\n    }\r\n\r\n    private static XForm ReadFrameTransform(byte[] data, int p)\r\n    {\r\n        // Row-major 3x3, rows = basis vectors (row-vector convention, see class remarks).\r\n        var m = new Matrix4x4(\r\n            RwStream.F32(data, p + 0), RwStream.F32(data, p + 4), RwStream.F32(data, p + 8), 0f,\r\n            RwStream.F32(data, p + 12), RwStream.F32(data, p + 16), RwStream.F32(data, p + 20), 0f,\r\n            RwStream.F32(data, p + 24), RwStream.F32(data, p + 28), RwStream.F32(data, p + 32), 0f,\r\n            0f, 0f, 0f, 1f);\r\n        var pos = new Vector3(\r\n            RwStream.F32(data, p + 36), RwStream.F32(data, p + 40), RwStream.F32(data, p + 44));\r\n        if (!float.IsFinite(pos.X) || !float.IsFinite(pos.Y) || !float.IsFinite(pos.Z))\r\n            throw new FormatException(\"RenderWare FrameList: non-finite frame translation.\");\r\n        var rot = MathQ.Normalize(Quaternion.CreateFromRotationMatrix(m));\r\n        if (!float.IsFinite(rot.X) || !float.IsFinite(rot.Y) || !float.IsFinite(rot.Z) || !float.IsFinite(rot.W))\r\n            throw new FormatException(\"RenderWare FrameList: non-finite frame rotation.\");\r\n        return new XForm(pos, rot);\r\n    }\r\n\r\n    // ================================================================ extensions\r\n\r\n    private static void ParseFrameExtension(\r\n        byte[] data, int start, int end, Frame frame,\r\n        ref (int NodeId, int NodeIndex, uint Flags)[]? nodeTable)\r\n    {\r\n        var offset = start;\r\n        while (offset < end)\r\n        {\r\n            var (type, payloadStart, payloadSize) = RwStream.ReadChunk(data, offset, end);\r\n            switch (type)\r\n            {\r\n                case RwStream.ChunkHAnimPlg:\r\n                    ParseHAnim(data, payloadStart, payloadSize, frame, ref nodeTable);\r\n                    break;\r\n                case RwStream.ChunkUserDataPlg:\r\n                    var userName = ReadUserDataName(data, payloadStart, payloadStart + payloadSize);\r\n                    if (!string.IsNullOrEmpty(userName))\r\n                        frame.Name = userName;\r\n                    break;\r\n                case RwStream.ChunkFrameName:\r\n                    // Classic frame-name string chunk. FSB2 leaves these empty (the real\r\n                    // names live in RpUserData) but honor them when present, without\r\n                    // overriding an already-found user-data name.\r\n                    if (frame.Name.Length == 0 && payloadSize > 0)\r\n                        frame.Name = ReadCString(data, payloadStart, payloadSize);\r\n                    break;\r\n            }\r\n            offset = payloadStart + payloadSize;\r\n        }\r\n    }\r\n\r\n    private static void ParseHAnim(\r\n        byte[] data, int start, int size, Frame frame,\r\n        ref (int NodeId, int NodeIndex, uint Flags)[]? nodeTable)\r\n    {\r\n        if (size < 12)\r\n            throw new FormatException(\"RenderWare HAnimPLG chunk is too small.\");\r\n        frame.NodeId = RwStream.I32(data, start + 4);\r\n        var numNodes = RwStream.I32(data, start + 8);\r\n        if (numNodes <= 0)\r\n            return;\r\n        if (size < 20 + numNodes * 12)\r\n            throw new FormatException(\r\n                $\"RenderWare HAnimPLG node table truncated (numNodes={numNodes}, size={size}).\");\r\n        if (nodeTable is not null)\r\n            throw new FormatException(\"RenderWare .dff carries more than one HAnim node table.\");\r\n\r\n        nodeTable = new (int, int, uint)[numNodes];\r\n        for (var i = 0; i < numNodes; i++)\r\n        {\r\n            var p = start + 20 + i * 12;\r\n            nodeTable[i] = (RwStream.I32(data, p), RwStream.I32(data, p + 4), RwStream.U32(data, p + 8));\r\n        }\r\n    }\r\n\r\n    /// <summary>RpUserData: {u32 numAttrs, per attr {u32 nameLen, name, u32 format,\r\n    /// u32 count, elements}}; format 3 = string elements {u32 len, chars}. Returns the\r\n    /// first string value of the attribute named <c>name</c>, or \"\".</summary>\r\n    private static string ReadUserDataName(byte[] data, int start, int end)\r\n    {\r\n        if (end - start < 4)\r\n            return \"\";\r\n        var attrCount = RwStream.I32(data, start);\r\n        var offset = start + 4;\r\n        for (var a = 0; a < attrCount; a++)\r\n        {\r\n            if (offset + 4 > end)\r\n                return \"\";\r\n            var nameLen = RwStream.I32(data, offset);\r\n            offset += 4;\r\n            if (nameLen < 0 || offset + nameLen > end)\r\n                return \"\";\r\n            var attrName = ReadCString(data, offset, nameLen);\r\n            offset += nameLen;\r\n            if (offset + 8 > end)\r\n                return \"\";\r\n            var format = RwStream.I32(data, offset);\r\n            var elementCount = RwStream.I32(data, offset + 4);\r\n            offset += 8;\r\n            for (var e = 0; e < elementCount; e++)\r\n            {\r\n                switch (format)\r\n                {\r\n                    case 1: // int\r\n                    case 2: // float\r\n                        offset += 4;\r\n                        break;\r\n                    case 3: // string\r\n                        if (offset + 4 > end)\r\n                            return \"\";\r\n                        var len = RwStream.I32(data, offset);\r\n                        offset += 4;\r\n                        if (len < 0 || offset + len > end)\r\n                            return \"\";\r\n                        if (attrName == \"name\")\r\n                            return ReadCString(data, offset, len);\r\n                        offset += len;\r\n                        break;\r\n                    default:\r\n                        return \"\"; // unknown element format \u2014 cannot skip safely\r\n                }\r\n            }\r\n        }\r\n        return \"\";\r\n    }\r\n\r\n    private static string ReadCString(byte[] data, int start, int maxLen)\r\n    {\r\n        var len = 0;\r\n        while (len < maxLen && data[start + len] != 0)\r\n            len++;\r\n        return Encoding.ASCII.GetString(data, start, len);\r\n    }\r\n\r\n    // ================================================================ node building\r\n\r\n    private static RwDffSkeletonData BuildNodes(\r\n        List<Frame> frames, (int NodeId, int NodeIndex, uint Flags)[] nodeTable, int frameCount)\r\n    {\r\n        // frame index by node id (each HAnim frame carries its own node id).\r\n        var frameByNodeId = new Dictionary<int, int>(frames.Count);\r\n        for (var i = 0; i < frames.Count; i++)\r\n        {\r\n            if (frames[i].NodeId >= 0 && !frameByNodeId.TryAdd(frames[i].NodeId, i))\r\n                throw new FormatException(\r\n                    $\"RenderWare .dff: duplicate HAnim node id {frames[i].NodeId}.\");\r\n        }\r\n\r\n        // The table's nodeIndex is the animation keyframe order \u2014 order by it.\r\n        var ordered = new (int NodeId, uint Flags)[nodeTable.Length];\r\n        var seen = new bool[nodeTable.Length];\r\n        foreach (var (nodeId, nodeIndex, flags) in nodeTable)\r\n        {\r\n            if (nodeIndex < 0 || nodeIndex >= nodeTable.Length || seen[nodeIndex])\r\n                throw new FormatException(\r\n                    $\"RenderWare HAnim node table has invalid/duplicate node index {nodeIndex}.\");\r\n            seen[nodeIndex] = true;\r\n            ordered[nodeIndex] = (nodeId, flags);\r\n        }\r\n\r\n        var nodeIndexByFrame = new Dictionary<int, int>(nodeTable.Length);\r\n        for (var n = 0; n < ordered.Length; n++)\r\n        {\r\n            if (!frameByNodeId.TryGetValue(ordered[n].NodeId, out var frameIndex))\r\n                throw new FormatException(\r\n                    $\"RenderWare HAnim node id {ordered[n].NodeId} has no matching frame.\");\r\n            nodeIndexByFrame[frameIndex] = n;\r\n        }\r\n\r\n        var usedNames = new HashSet<string>(StringComparer.Ordinal);\r\n        var nodes = new RwDffNode[ordered.Length];\r\n        for (var n = 0; n < ordered.Length; n++)\r\n        {\r\n            var frameIndex = frameByNodeId[ordered[n].NodeId];\r\n\r\n            // Parent = nearest ancestor frame that is itself an HAnim node; plain frames\r\n            // in between are composed into the rest transform (world = parent \u2218 local).\r\n            var local = frames[frameIndex].Local;\r\n            var parentFrame = frames[frameIndex].Parent;\r\n            var parentNode = -1;\r\n            while (parentFrame >= 0)\r\n            {\r\n                if (nodeIndexByFrame.TryGetValue(parentFrame, out var pn))\r\n                {\r\n                    parentNode = pn;\r\n                    break;\r\n                }\r\n                local = XForm.Compose(frames[parentFrame].Local, local);\r\n                parentFrame = frames[parentFrame].Parent;\r\n            }\r\n            if (parentNode >= n && parentNode != -1)\r\n                throw new FormatException(\r\n                    $\"RenderWare HAnim node order is not parent-first (node {n} has parent node {parentNode}).\");\r\n\r\n            var name = frames[frameIndex].Name;\r\n            if (string.IsNullOrEmpty(name))\r\n                name = $\"rw_node_{ordered[n].NodeId}\";\r\n            name = UniqueName(name, usedNames);\r\n\r\n            nodes[n] = new RwDffNode\r\n            {\r\n                NodeId = ordered[n].NodeId,\r\n                Name = name,\r\n                ParentIndex = parentNode,\r\n                RestLocal = local,\r\n                Flags = ordered[n].Flags,\r\n            };\r\n        }\r\n\r\n        return new RwDffSkeletonData { Nodes = nodes, FrameCount = frameCount };\r\n    }\r\n\r\n    private static string UniqueName(string name, HashSet<string> usedNames)\r\n    {\r\n        if (usedNames.Add(name))\r\n            return name;\r\n        for (var i = 2; ; i++)\r\n        {\r\n            var candidate = $\"{name}#{i}\";\r\n            if (usedNames.Add(candidate))\r\n                return candidate;\r\n        }\r\n    }\r\n}\r\n"
        },
        {
            "Ident": "notpointless.chomnr_humanoid_mocap",
            "Path": "HumanoidMocap/Inference/PalmDetectionFilter.cs",
            "FileName": "PalmDetectionFilter.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 395388,
            "IsPrivate": false,
            "Code": "// Weighted suppression adapted from MediaPipe's NonMaxSuppressionCalculator.\r\n// Copyright 2019 The MediaPipe Authors. Licensed under Apache-2.0.\r\n// See Editor/HumanoidMocap/Inference/MediaPipe.LICENSE and THIRD_PARTY_NOTICES.md.\r\nusing System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing System.Numerics;\r\n\r\nnamespace HumanoidMocap.Inference;\r\nusing Vector2 = System.Numerics.Vector2;\r\n\r\npublic sealed record PalmDetection(float Score,float X,float Y,float Width,float Height,Vector2[] Points);\r\n\r\npublic static class PalmDetectionFilter\r\n{\r\n    /// <summary>Merge boxes and keypoints by detection score. Each cluster is\r\n    /// compared with its highest-scoring original box, not its moving average.\r\n    /// The seed score is retained; averaging does not create a confidence score.</summary>\r\n    public static List<PalmDetection> Merge(IEnumerable<PalmDetection> candidates,int limit=2,float threshold=.3f)\r\n    {\r\n        var remaining=candidates.Where(p=>float.IsFinite(p.Score+p.X+p.Y+p.Width+p.Height)\r\n            &&p.Score>0&&p.Width>0&&p.Height>0&&p.Points.All(v=>float.IsFinite(v.X+v.Y)))\r\n            .OrderByDescending(p=>p.Score).ToList();\r\n        var result=new List<PalmDetection>();\r\n        while(remaining.Count>0&&result.Count<limit)\r\n        {\r\n            var seed=remaining[0];var rest=new List<PalmDetection>();\r\n            var points=new Vector2[seed.Points.Length];float weight=0,xmin=0,ymin=0,xmax=0,ymax=0;\r\n            foreach(var candidate in remaining)\r\n            {\r\n                if(Iou(seed,candidate)<=threshold){rest.Add(candidate);continue;}\r\n                if(candidate.Points.Length!=points.Length)throw new ArgumentException(\"Palm keypoint counts differ.\");\r\n                var score=candidate.Score;weight+=score;\r\n                xmin+=(candidate.X-candidate.Width/2)*score;ymin+=(candidate.Y-candidate.Height/2)*score;\r\n                xmax+=(candidate.X+candidate.Width/2)*score;ymax+=(candidate.Y+candidate.Height/2)*score;\r\n                for(var i=0;i<points.Length;i++)points[i]+=candidate.Points[i]*score;\r\n            }\r\n            if(weight<=0)break;\r\n            xmin/=weight;ymin/=weight;xmax/=weight;ymax/=weight;\r\n            for(var i=0;i<points.Length;i++)points[i]/=weight;\r\n            result.Add(new(seed.Score,(xmin+xmax)/2,(ymin+ymax)/2,xmax-xmin,ymax-ymin,points));\r\n            remaining=rest;\r\n        }\r\n        return result;\r\n    }\r\n\r\n    static float Iou(PalmDetection a,PalmDetection b)\r\n    {\r\n        var width=Math.Max(0,Math.Min(a.X+a.Width/2,b.X+b.Width/2)-Math.Max(a.X-a.Width/2,b.X-b.Width/2));\r\n        var height=Math.Max(0,Math.Min(a.Y+a.Height/2,b.Y+b.Height/2)-Math.Max(a.Y-a.Height/2,b.Y-b.Height/2));\r\n        var intersection=width*height;var union=a.Width*a.Height+b.Width*b.Height-intersection;\r\n        return union>0?intersection/union:0;\r\n    }\r\n}\r\n"
        },
        {
            "Ident": "notpointless.chomnr_humanoid_mocap",
            "Path": "HumanoidMocap/Motion/CaptureStanceProportion.cs",
            "FileName": "CaptureStanceProportion.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 395388,
            "IsPrivate": false,
            "Code": "using System;\r\nusing System.Collections.Generic;\r\nusing System.Numerics;\r\nusing HumanoidMocap.Cleanup;\r\nusing HumanoidMocap.Mapping;\r\nusing HumanoidMocap.Maths;\r\nusing HumanoidMocap.Skeleton;\r\nusing HumanoidMocap.Target;\r\n\r\nnamespace HumanoidMocap.Motion;\r\nusing Vector3 = System.Numerics.Vector3;\r\n\r\n/// <summary>Keeps a captured body's stance width when the target's hips are a different width.\r\n/// Leg rotations copied onto a rig whose hip joints sit wider (relative to its legs) than the\r\n/// performer's carry both feet outward by the extra half-width: on a reconstructed performer\r\n/// with hip joints 0.15 leg-lengths apart and Human's 0.25, a 0.62 leg-length stance became 0.73\r\n/// and read as splayed legs. Each ankle is moved back along the pelvis' own lateral axis by that\r\n/// difference and the leg is re-solved, preserving bone lengths and the foot's world orientation.\r\n/// Applied before ground alignment and foot anchoring. A proportion correction, not new capture.</summary>\r\npublic static class CaptureStanceProportion\r\n{\r\n    public sealed record Result(float HalfWidthCorrection,int Samples);\r\n    public static Result Apply(List<XForm[]> frames,SourceScene source,MappingResult mapping,TargetRig target,FootChain left,FootChain right)\r\n    {\r\n        if(frames.Count==0)return new(0,0);\r\n        var rig=target.Skeleton;var sourceRest=source.Skeleton.RestWorld;\r\n        if(!mapping.RoleToBone.TryGetValue(BoneRole.UpperLegL,out var hipL)||!mapping.RoleToBone.TryGetValue(BoneRole.UpperLegR,out var hipR)||\r\n            !mapping.RoleToBone.TryGetValue(BoneRole.LowerLegL,out var kneeL)||!mapping.RoleToBone.TryGetValue(BoneRole.FootL,out var footL))return new(0,0);\r\n        var sourceLeg=Vector3.Distance(sourceRest[hipL].Pos,sourceRest[kneeL].Pos)+Vector3.Distance(sourceRest[kneeL].Pos,sourceRest[footL].Pos);\r\n        var targetRest=rig.RestWorld;\r\n        var targetLeg=Vector3.Distance(targetRest[left.Hip].Pos,targetRest[left.Knee].Pos)+Vector3.Distance(targetRest[left.Knee].Pos,targetRest[left.Ankle].Pos);\r\n        if(!(sourceLeg>1e-4f)||!(targetLeg>1e-4f))return new(0,0);\r\n        // Half the hip-joint spacing each skeleton has per unit of its own leg, in target units.\r\n        var correction=(Vector3.Distance(targetRest[left.Hip].Pos,targetRest[right.Hip].Pos)/targetLeg\r\n            -Vector3.Distance(sourceRest[hipL].Pos,sourceRest[hipR].Pos)/sourceLeg)*targetLeg*.5f;\r\n        if(!float.IsFinite(correction)||MathF.Abs(correction)<targetLeg*.005f)return new(0,0);\r\n        var world=new XForm[rig.Count];var samples=0;\r\n        foreach(var frame in frames)\r\n        {\r\n            foreach(var (leg,other) in new[]{(left,right),(right,left)})\r\n            {\r\n                FkUtil.ToWorld(frame,rig,world);\r\n                var lateral=world[leg.Hip].Pos-world[other.Hip].Pos;if(lateral.LengthSquared()<1e-8f)continue;\r\n                lateral=Vector3.Normalize(lateral);\r\n                var hip=world[leg.Hip];var knee=world[leg.Knee];var ankle=world[leg.Ankle];var footRotation=ankle.Rot;\r\n                var goal=ankle.Pos-lateral*correction;\r\n                // Never ask for more than the leg can reach; the foot keeps its direction from the hip.\r\n                var reach=(Vector3.Distance(hip.Pos,knee.Pos)+Vector3.Distance(knee.Pos,ankle.Pos))*.9995f;\r\n                var fromHip=goal-hip.Pos;if(fromHip.Length()>reach)goal=hip.Pos+Vector3.Normalize(fromHip)*reach;\r\n                var bend=Vector3.Cross(knee.Pos-hip.Pos,ankle.Pos-knee.Pos);\r\n                if(bend.LengthSquared()<1e-8f)bend=Vector3.Transform(Vector3.UnitX,hip.Rot);\r\n                var ik=TwoBoneIk.Solve(hip.Pos,knee.Pos,ankle.Pos,goal,soften:0,stableBendAxis:bend);\r\n                EffectorIk.ApplyWorldDeltas(frame,rig,leg.Hip,leg.Knee,leg.Ankle,ik.UpperWorldDelta,ik.LowerWorldDelta,world);\r\n                FkUtil.ToWorld(frame,rig,world);\r\n                var parent=rig[leg.Ankle].ParentIndex;\r\n                frame[leg.Ankle].Rot=Quaternion.Normalize((parent<0?Quaternion.Identity:Quaternion.Inverse(world[parent].Rot))*footRotation);\r\n                samples++;\r\n            }\r\n        }\r\n        return new(correction,samples);\r\n    }\r\n}\r\n"
        },
        {
            "Ident": "notpointless.chomnr_humanoid_mocap",
            "Path": "HumanoidMocap/Motion/TargetCorrections.cs",
            "FileName": "TargetCorrections.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 395388,
            "IsPrivate": false,
            "Code": "using System;\r\nusing System.Collections.Generic;\r\nusing System.Numerics;\r\nusing HumanoidMocap.Cleanup;\r\nusing HumanoidMocap.Mapping;\r\nusing HumanoidMocap.Maths;\r\nusing HumanoidMocap.Target;\r\n\r\nnamespace HumanoidMocap.Motion;\r\nusing Vector3 = System.Numerics.Vector3;\r\n\r\npublic sealed class TargetCorrectionSettings\r\n{\r\n    public bool FirstPerson { get; set; }\r\n    /// <summary>Identifies the selected target geometry for explicitly authored finger contacts.</summary>\r\n    public string ContactTargetKey { get; set; } = \"\";\r\n    public Vector3 LeftShoulder { get; set; } = new(.18f,1.45f,0);\r\n    public Vector3 RightShoulder { get; set; } = new(-.18f,1.45f,0);\r\n    public Vector3 LeftElbow { get; set; } = new(.45f,1.1f,.15f);\r\n    public Vector3 RightElbow { get; set; } = new(-.45f,1.1f,.15f);\r\n    public float Reach { get; set; } = .995f;\r\n    public float GroundOffset { get; set; }\r\n    public float FacingDegrees { get; set; }\r\n    public bool StabilizeFeet { get; set; } = true;\r\n    /// <summary>Manual camera-space wrist position edits, applied before confirmed prop contacts and target IK.</summary>\r\n    public List<WristPositionOffset> WristOffsets { get; set; } = new();\r\n    /// <summary>Editable placement of a camera-relative hand capture in the target's\r\n    /// Y-up metre frame. This is a user assumption, not recovered camera tracking.</summary>\r\n    public Vector3 CaptureCameraPosition { get; set; } = new(0,1.65f,0);\r\n    public float CaptureCameraYawDegrees { get; set; } = 180;\r\n    public float CaptureCameraPitchDegrees { get; set; }\r\n    /// <summary>The capture camera faced the performer instead of being worn by them. The placement\r\n    /// above then describes a camera in front of the character, and a first-person preview should\r\n    /// look from the character's own head rather than from that camera.</summary>\r\n    public bool CaptureFacesSubject { get; set; }\r\n    /// <summary>Shrink a hand capture toward the camera, by at most a quarter, when the\r\n    /// target's arms are too short to reach it. Points keep their viewing rays, so the\r\n    /// first-person picture is unchanged. Skipped while prop contacts share the capture space.</summary>\r\n    public bool FitCaptureToArmReach { get; set; } = true;\r\n\r\n    public static TargetCorrectionSettings ForRig(TargetRig rig,TargetUpAxis axis)\r\n    {\r\n        var scale=axis==TargetUpAxis.ZUpEngine?39.3700787f:100f;\r\n        var rotation=axis==TargetUpAxis.YUpCm?Quaternion.Identity:Quaternion.CreateFromAxisAngle(Vector3.UnitX,-MathF.PI/2);\r\n        Vector3 Position(BoneRole role,Vector3 fallback)=>rig.BoneForRole(role) is int b\r\n            ?Vector3.Transform(rig.Skeleton.RestWorld[b].Pos/scale,rotation):fallback;\r\n        var result=new TargetCorrectionSettings();\r\n        result.LeftShoulder=Position(BoneRole.UpperArmL,result.LeftShoulder);\r\n        result.RightShoulder=Position(BoneRole.UpperArmR,result.RightShoulder);\r\n        var height=Math.Max(.2f,(result.LeftShoulder.Y+result.RightShoulder.Y)/2)/1.45f;\r\n        result.LeftElbow=result.LeftShoulder+new Vector3(.27f,-.35f,.15f)*height;\r\n        result.RightElbow=result.RightShoulder+new Vector3(-.27f,-.35f,.15f)*height;\r\n        result.CaptureCameraPosition=Position(BoneRole.Head,new(0,1.55f,0))+Vector3.UnitY*.1f*height;\r\n        // FPS viewmodels without a body use their authored origin as the assumed\r\n        // camera position. A full-body eye-height fallback would lift these wrists\r\n        // above the rig and exhaust arm reach before any captured movement.\r\n        if(rig.BoneForRole(BoneRole.Head) is null&&rig.BoneForRole(BoneRole.Hips) is null)\r\n        {\r\n            result.CaptureCameraPosition=Vector3.Zero;\r\n            // A viewmodel can face a different horizontal axis than a body rig.\r\n            // Its left/right shoulder line supplies lateral direction; detached\r\n            // hands can use their authored wrist spacing. This remains editable.\r\n            var leftRole=rig.BoneForRole(BoneRole.UpperArmL) is not null?BoneRole.UpperArmL:BoneRole.HandL;\r\n            var rightRole=rig.BoneForRole(BoneRole.UpperArmR) is not null?BoneRole.UpperArmR:BoneRole.HandR;\r\n            if(rig.BoneForRole(leftRole) is not null&&rig.BoneForRole(rightRole) is not null)\r\n            {\r\n                var lateral=Position(leftRole,Vector3.Zero)-Position(rightRole,Vector3.Zero);lateral.Y=0;\r\n                if(lateral.LengthSquared()>1e-8f)\r\n                {\r\n                    lateral=Vector3.Normalize(lateral);var forward=Vector3.Cross(lateral,Vector3.UnitY);\r\n                    result.CaptureCameraYawDegrees=MathF.Atan2(-forward.X,-forward.Z)*180/MathF.PI;\r\n                    float ArmLength(BoneRole upper,BoneRole lower,BoneRole hand)=>\r\n                        rig.BoneForRole(upper) is not null&&rig.BoneForRole(lower) is not null&&rig.BoneForRole(hand) is not null\r\n                        ?Vector3.Distance(Position(upper,Vector3.Zero),Position(lower,Vector3.Zero))+\r\n                            Vector3.Distance(Position(lower,Vector3.Zero),Position(hand,Vector3.Zero)):0;\r\n                    var armLength=Math.Max(ArmLength(BoneRole.UpperArmL,BoneRole.LowerArmL,BoneRole.HandL),\r\n                        ArmLength(BoneRole.UpperArmR,BoneRole.LowerArmR,BoneRole.HandR));\r\n                    var proportion=armLength>1e-4f?armLength/.6f:1f;\r\n                    result.LeftElbow=result.LeftShoulder+(lateral*.27f-Vector3.UnitY*.35f+forward*.15f)*proportion;\r\n                    result.RightElbow=result.RightShoulder+(-lateral*.27f-Vector3.UnitY*.35f+forward*.15f)*proportion;\r\n                }\r\n            }\r\n        }\r\n        return result;\r\n    }\r\n}\r\n\r\npublic static class TargetCorrections\r\n{\r\n    public static void Apply(List<XForm[]> frames,TargetRig rig,TargetUpAxis axis,TargetCorrectionSettings settings,\r\n        IReadOnlyList<Dictionary<BoneRole,XForm>>? wristTargets=null)\r\n    {\r\n        var skeleton=rig.Skeleton;var world=new XForm[skeleton.Count];\r\n        var left=new ArmConstraintSolver();var right=new ArmConstraintSolver();\r\n        var scale=axis==TargetUpAxis.ZUpEngine?39.3700787f:100f;\r\n        var conversion=axis==TargetUpAxis.YUpCm?Quaternion.Identity:Quaternion.CreateFromAxisAngle(Vector3.UnitX,MathF.PI/2);\r\n        Vector3 Convert(Vector3 v)=>Vector3.Transform(v*scale,conversion);\r\n        var up=axis==TargetUpAxis.YUpCm?Vector3.UnitY:Vector3.UnitZ;\r\n        var yaw=Quaternion.CreateFromAxisAngle(up,settings.FacingDegrees*MathF.PI/180);\r\n        for(var frameIndex=0;frameIndex<frames.Count;frameIndex++)\r\n        {\r\n            var frame=frames[frameIndex];\r\n            if(!settings.FirstPerson&&wristTargets is null)\r\n            {\r\n                for(var i=0;i<frame.Length;i++)if(skeleton[i].ParentIndex<0)\r\n                    frame[i]=new XForm(Vector3.Transform(frame[i].Pos,yaw)+up*settings.GroundOffset*scale,Quaternion.Normalize(yaw*frame[i].Rot));\r\n                continue;\r\n            }\r\n            Solve(BoneRole.UpperArmL,BoneRole.LowerArmL,BoneRole.HandL,settings.LeftShoulder,settings.LeftElbow,left);\r\n            Solve(BoneRole.UpperArmR,BoneRole.LowerArmR,BoneRole.HandR,settings.RightShoulder,settings.RightElbow,right);\r\n            void Solve(BoneRole upperRole,BoneRole lowerRole,BoneRole handRole,Vector3 shoulder,Vector3 pole,ArmConstraintSolver solver)\r\n            {\r\n                if(rig.BoneForRole(handRole) is not int h)return;\r\n                FkUtil.ToWorld(frame,skeleton,world);\r\n                var originalWrist=world[h];\r\n                if(wristTargets is not null&&!wristTargets[frameIndex].TryGetValue(handRole,out originalWrist))return;\r\n                if(rig.BoneForRole(upperRole) is not int u || rig.BoneForRole(lowerRole) is not int l)\r\n                {\r\n                    if(wristTargets is not null)SetWorld(h,originalWrist);\r\n                    return;\r\n                }\r\n                var upperLength=Vector3.Distance(world[u].Pos,world[l].Pos);var lowerLength=Vector3.Distance(world[l].Pos,world[h].Pos);\r\n                var result=solver.Solve(originalWrist.Pos,originalWrist.Rot,new ArmSettings { Shoulder=Convert(shoulder),ElbowTarget=Convert(pole),UpperLength=upperLength,ForearmLength=lowerLength,MaximumReach=settings.Reach });\r\n                var upperRotation=Quaternion.Normalize(MathQ.FromTo(world[l].Pos-world[u].Pos,result.Elbow-result.Shoulder)*world[u].Rot);\r\n                SetWorld(u,new XForm(result.Shoulder,upperRotation));\r\n                FkUtil.ToWorld(frame,skeleton,world);\r\n                var lowerRotation=Quaternion.Normalize(MathQ.FromTo(world[h].Pos-world[l].Pos,result.Wrist-result.Elbow)*world[l].Rot);\r\n                SetWorld(l,new XForm(world[l].Pos,lowerRotation));\r\n                FkUtil.ToWorld(frame,skeleton,world);\r\n                SetWorld(h,new XForm(world[h].Pos,originalWrist.Rot)); // preserve captured wrist attitude and finger locals\r\n            }\r\n            void SetWorld(int index,XForm value)\r\n            {\r\n                var parent=skeleton[index].ParentIndex;\r\n                frame[index]=parent<0?value:XForm.Compose(world[parent].Inverse(),value);\r\n            }\r\n        }\r\n    }\r\n}\r\n"
        },
        {
            "Ident": "notpointless.chomnr_humanoid_mocap",
            "Path": "HumanoidMocap/Skeleton/Clip.cs",
            "FileName": "Clip.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 395388,
            "IsPrivate": false,
            "Code": "#nullable enable annotations\r\n\r\nusing System;\r\nusing System.Collections.Generic;\r\nusing HumanoidMocap.Maths;\r\n\r\nnamespace HumanoidMocap.Skeleton;\r\n\r\n/// <summary>\r\n/// A sampled animation clip: a fixed-rate sequence of frames, each holding one\r\n/// parent-relative local transform per bone (skeleton bone order). Clips are always\r\n/// resampled at ingest \u2014 no key data is preserved.\r\n/// </summary>\r\npublic sealed class Clip\r\n{\r\n    /// <summary>Clip (sequence) name.</summary>\r\n    public string Name { get; }\r\n\r\n    /// <summary>Sample rate in frames per second.</summary>\r\n    public float Fps { get; }\r\n\r\n    /// <summary>\r\n    /// Native frame rate of the take in the SOURCE file (FBX GlobalSettings\r\n    /// TimeMode/CustomFrameRate, BVH 1/FrameTime). External frame ranges \u2014 Unity\r\n    /// <c>.fbx.meta</c> <c>clipAnimations</c> definitions \u2014 are expressed in THIS rate, so\r\n    /// they must be rescaled by <c>Fps / NativeFps</c> to index the resampled\r\n    /// <see cref=\"Frames\"/>. Equals <see cref=\"Fps\"/> when the importer records no native rate.\r\n    /// </summary>\r\n    public float NativeFps { get; }\r\n\r\n    /// <summary>Whether the clip is authored to loop.</summary>\r\n    public bool Looping { get; }\r\n\r\n    /// <summary>Frames in playback order; each entry is one local transform per bone.</summary>\r\n    public List<XForm[]> Frames { get; }\r\n\r\n    /// <summary>Number of frames currently in the clip.</summary>\r\n    public int FrameCount => Frames.Count;\r\n\r\n    /// <summary>\r\n    /// Clip duration in seconds at <see cref=\"Fps\"/>: the time span between the first and the\r\n    /// last sample, <c>(FrameCount - 1) / Fps</c> (frames are fence posts, intervals are the\r\n    /// spans between them \u2014 matching the DMX timeFrame this clip serializes to). Zero for\r\n    /// empty and single-frame clips.\r\n    /// </summary>\r\n    public float Duration => FrameCount <= 1 ? 0f : (FrameCount - 1) / Fps;\r\n\r\n    /// <summary>Creates an empty clip.</summary>\r\n    /// <exception cref=\"ArgumentOutOfRangeException\">Thrown when <paramref name=\"fps\"/> is not positive.</exception>\r\n    public Clip(string name, float fps, bool looping)\r\n        : this(name, fps, looping, new List<XForm[]>())\r\n    {\r\n    }\r\n\r\n    /// <summary>Creates a clip wrapping an existing frame list (not copied).</summary>\r\n    /// <param name=\"nativeFps\">Source-file native frame rate (<see cref=\"NativeFps\"/>);\r\n    /// null = same as <paramref name=\"fps\"/>.</param>\r\n    /// <exception cref=\"ArgumentOutOfRangeException\">Thrown when <paramref name=\"fps\"/> (or a\r\n    /// provided <paramref name=\"nativeFps\"/>) is not positive.</exception>\r\n    public Clip(string name, float fps, bool looping, List<XForm[]> frames, float? nativeFps = null)\r\n    {\r\n        ArgumentNullException.ThrowIfNull(name);\r\n        ArgumentNullException.ThrowIfNull(frames);\r\n        if (!(fps > 0f) || !float.IsFinite(fps))\r\n            throw new ArgumentOutOfRangeException(nameof(fps), fps, \"Fps must be a positive finite number.\");\r\n        if (nativeFps is { } native && (!(native > 0f) || !float.IsFinite(native)))\r\n            throw new ArgumentOutOfRangeException(nameof(nativeFps), native, \"NativeFps must be a positive finite number.\");\r\n\r\n        Name = name;\r\n        Fps = fps;\r\n        NativeFps = nativeFps ?? fps;\r\n        Looping = looping;\r\n        Frames = frames;\r\n    }\r\n}\r\n"
        },
        {
            "Ident": "notpointless.chomnr_humanoid_mocap",
            "Path": "HumanoidMocap/Solve/RestNormalizer.cs",
            "FileName": "RestNormalizer.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 395388,
            "IsPrivate": false,
            "Code": "#nullable enable annotations\r\n\r\nusing System;\r\nusing System.Collections.Generic;\r\nusing System.Numerics;\r\nusing HumanoidMocap.Mapping;\r\nusing HumanoidMocap.Maths;\r\nusing SkeletonModel = HumanoidMocap.Skeleton.Skeleton;\r\n\r\nnamespace HumanoidMocap.Solve;\r\n\r\nusing Vector3 = System.Numerics.Vector3; // s&box compat: shadow engine's global-namespace Vector3 (see Code/HumanoidMocap/Assembly.cs)\r\n\r\n/// <summary>\r\n/// A skeleton's rest pose as explicit world transforms (indexed like the skeleton's bones).\r\n/// Produced by <see cref=\"RestNormalizer\"/>; feed it to\r\n/// <see cref=\"CanonicalFrames.Build(SkeletonModel, MappingResult, IReadOnlyList{XForm})\"/>.\r\n/// </summary>\r\npublic sealed class RestPose\r\n{\r\n    /// <summary>Rest world transforms per bone (positions in cm).</summary>\r\n    public XForm[] WorldRest { get; init; } = Array.Empty<XForm>();\r\n}\r\n\r\n/// <summary>\r\n/// Rest-pose detection (T-pose / A-pose / I-pose) and normalization to a canonical T-pose.\r\n/// Runs on <b>both</b> source and target rests before canonical frames are built, so deltas\r\n/// measured against the normalized source rest apply cleanly to the normalized target rest\r\n/// (the s&amp;box human rig itself rests in a strong A-pose, ~52\u00b0 below horizontal).\r\n/// </summary>\r\n/// <remarks>\r\n/// <para><b>Non-anatomical binds:</b> some exports (NVIDIA SOMA uniform-skeleton BVH) carry\r\n/// a bind that is bone-length encoding, not a pose \u2014 every OFFSET runs along \u00b1X, so identity\r\n/// rest rotations collapse the figure into a stick (measured on the SOMA repro: character up\r\n/// = world +X, thigh\u00b7up = +1.00/\u22121.00, thighs 180\u00b0 apart \u2014 a thigh pointing at the\r\n/// shoulders). Every anatomical bind in the corpus measures thigh\u00b7up \u2264 \u22120.75 and \u2264 46\u00b0\r\n/// between thighs (the posed Defenses.fbx stance included), so\r\n/// <see cref=\"IsAnatomicalRest\"/> separates the two with a wide margin. When the bind fails\r\n/// the check, <see cref=\"Normalize(SkeletonModel, MappingResult, IReadOnlyList{XForm})\"/>\r\n/// rebuilds the rest from the supplied reference pose (the clip's first frame \u2014 a real pose\r\n/// whose rotations carry the missing rest orientation); without a reference pose\r\n/// normalization throws <see cref=\"ArgumentException\"/> rather than build canonical frames\r\n/// on a non-pose (callers that probe rest geometry already treat that as \"skip\").</para>\r\n/// <para><b>Detection:</b> the angle of each (LowerArm.head \u2212 UpperArm.head) rest segment\r\n/// against the character's horizontal lateral direction (per side, then averaged):\r\n/// 0\u201315\u00b0 \u2192 <see cref=\"DetectedPose.TPose\"/>; 15\u201360\u00b0 with the arm <i>below</i> horizontal \u2192\r\n/// <see cref=\"DetectedPose.APose\"/>; 60\u201395\u00b0 with the arms hanging predominantly <i>down</i>\r\n/// (arm\u00b7up \u2264 \u22120.5) \u2192 <see cref=\"DetectedPose.IPose\"/> (relaxed N-pose rests \u2014 first-frame\r\n/// rebuilt rests measure 64\u201388\u00b0 below horizontal at arm\u00b7up \u22120.90\u2026\u22121.00 across the corpus);\r\n/// anything else \u2192 <see cref=\"DetectedPose.Other\"/>. Legs are checked analogously against\r\n/// vertical for wide stances.</para>\r\n/// <para><b>Normalization (swing-only, hierarchical, per limb chain \u2014 never the spine):</b>\r\n/// each arm segment is swung about its joint so the chain matches the canonical T-pose:\r\n/// upper arm \u2192 \u00b1lateral (exactly horizontal), forearm \u2192 \u00b1lateral (straight arm), hand \u2192\r\n/// \u00b1lateral; every swing rotates all descendant world rests about the joint (positions orbit\r\n/// the joint, orientations are premultiplied), so segment lengths never change. Hand roll is\r\n/// then resolved to the palm-down convention by rotating about the limb axis until the hand's\r\n/// geometric dorsal normal (<see cref=\"HandGeometry.Dorsal\"/>) aligns with character up.\r\n/// Legs are only normalized (thigh and calf swung to exactly \u2212up) when a wide stance\r\n/// (&gt; 15\u00b0 off vertical) is detected \u2014 normal rigs keep their slight natural leg splay.</para>\r\n/// </remarks>\r\npublic static class RestNormalizer\r\n{\r\n    /// <summary>Rest-pose family detected from the arm rest angle.</summary>\r\n    public enum DetectedPose\r\n    {\r\n        /// <summary>Arms within 15\u00b0 of horizontal.</summary>\r\n        TPose,\r\n\r\n        /// <summary>Arms 15\u201360\u00b0 below horizontal.</summary>\r\n        APose,\r\n\r\n        /// <summary>Arms hanging 60\u201395\u00b0 below horizontal, predominantly downward (relaxed\r\n        /// N-pose; typical for rests rebuilt from a clip's first frame).</summary>\r\n        IPose,\r\n\r\n        /// <summary>Anything else (arms raised, missing, or extreme poses).</summary>\r\n        Other,\r\n    }\r\n\r\n    /// <summary>What detection and normalization found and did; surfaced in the mapping report.</summary>\r\n    public sealed class RestReport\r\n    {\r\n        /// <summary>Detected rest-pose family.</summary>\r\n        public DetectedPose Detected { get; set; } = DetectedPose.Other;\r\n\r\n        /// <summary>Average upper-arm rest angle against the horizontal lateral direction,\r\n        /// degrees (0 = perfect T-pose).</summary>\r\n        public float UpperArmAngleDeg { get; set; } = float.NaN;\r\n\r\n        /// <summary>True when the bind rest failed <see cref=\"IsAnatomicalRest\"/> and the\r\n        /// normalized rest was rebuilt from the caller's reference pose instead.</summary>\r\n        public bool RebuiltFromReferencePose { get; set; }\r\n\r\n        /// <summary>Human-readable notes: corrections applied, skipped steps, oddities.</summary>\r\n        public List<string> Notes { get; } = new();\r\n    }\r\n\r\n    private const float TPoseMaxDeg = 15f;\r\n    private const float APoseMaxDeg = 60f;\r\n    private const float IPoseMaxDeg = 95f;\r\n    private const float IPoseMaxUpDot = -0.5f;\r\n    private const float WideStanceMinDeg = 15f;\r\n\r\n    /// <summary>Plausibility cap on thigh\u00b7characterUp: a rest thigh pointing less than ~78\u00b0\r\n    /// away from the shoulder direction is anatomically impossible. Corpus anatomical binds\r\n    /// measure \u2264 \u22120.75; the SOMA stick bind +1.00.</summary>\r\n    private const float ThighMaxUpDot = 0.2f;\r\n\r\n    /// <summary>Plausibility floor on thighL\u00b7thighR (cos 120\u00b0): rest thighs more than 120\u00b0\r\n    /// apart are anatomically impossible. Corpus anatomical binds measure \u2264 46\u00b0 apart\r\n    /// (cos \u2265 0.69); the SOMA stick bind 180\u00b0 (\u22121.00).</summary>\r\n    private const float ThighPairMinDot = -0.5f;\r\n\r\n    /// <summary>\r\n    /// Detects the rest pose of <paramref name=\"skeleton\"/> and returns a T-pose-normalized\r\n    /// copy of its rest world transforms plus a report. The skeleton itself is not modified.\r\n    /// </summary>\r\n    /// <exception cref=\"ArgumentException\">Thrown when the mapping lacks the bones the\r\n    /// character frame needs (see <see cref=\"CharacterFrame.Compute\"/>), or when the bind\r\n    /// rest is not an anatomical pose (see <see cref=\"IsAnatomicalRest\"/>) \u2014 without a\r\n    /// reference pose there is nothing valid to normalize.</exception>\r\n    public static (RestPose Normalized, RestReport Report) Normalize(SkeletonModel skeleton, MappingResult map)\r\n        => Normalize(skeleton, map, referencePoseLocals: null);\r\n\r\n    /// <summary>\r\n    /// Like <see cref=\"Normalize(SkeletonModel, MappingResult)\"/>, but when the bind rest is\r\n    /// not an anatomical pose (see <see cref=\"IsAnatomicalRest\"/> and the class remarks) the\r\n    /// rest is rebuilt from <paramref name=\"referencePoseLocals\"/> (parent-relative locals,\r\n    /// indexed like the skeleton \u2014 pass the clip's first frame) before normalization.\r\n    /// </summary>\r\n    /// <exception cref=\"ArgumentException\">As the two-argument overload; a non-anatomical\r\n    /// bind only throws when <paramref name=\"referencePoseLocals\"/> is null.</exception>\r\n    public static (RestPose Normalized, RestReport Report) Normalize(\r\n        SkeletonModel skeleton, MappingResult map, IReadOnlyList<XForm>? referencePoseLocals,\r\n        Vector3? worldUp = null)\r\n    {\r\n        ArgumentNullException.ThrowIfNull(skeleton);\r\n        ArgumentNullException.ThrowIfNull(map);\r\n\r\n        var world = new XForm[skeleton.Count];\r\n        for (var i = 0; i < skeleton.Count; i++)\r\n            world[i] = skeleton.RestWorld[i];\r\n\r\n        var report = new RestReport();\r\n        if (!IsAnatomicalRest(skeleton, map, world))\r\n        {\r\n            if (referencePoseLocals is null)\r\n            {\r\n                throw new ArgumentException(\r\n                    \"Bind rest is not an anatomical humanoid pose (a rest thigh points toward \"\r\n                    + \"the shoulders or the thighs are anti-parallel \u2014 e.g. a bone-length \"\r\n                    + \"'stick' bind with identity rotations) and no reference pose is \"\r\n                    + \"available to rebuild it.\");\r\n            }\r\n            if (referencePoseLocals.Count != skeleton.Count)\r\n            {\r\n                throw new ArgumentException(\r\n                    $\"referencePoseLocals has {referencePoseLocals.Count} entries for a \"\r\n                    + $\"{skeleton.Count}-bone skeleton.\", nameof(referencePoseLocals));\r\n            }\r\n\r\n            for (var i = 0; i < skeleton.Count; i++)\r\n            {\r\n                var parent = skeleton[i].ParentIndex;\r\n                world[i] = parent < 0\r\n                    ? referencePoseLocals[i]\r\n                    : XForm.Compose(world[parent], referencePoseLocals[i]);\r\n            }\r\n            report.RebuiltFromReferencePose = true;\r\n            report.Notes.Add(\r\n                \"Bind rest is not an anatomical pose (bone-length stick bind); rest rebuilt \"\r\n                + \"from the reference pose (clip first frame).\");\r\n        }\r\n\r\n        // Arm/leg normalization never moves the hip or shoulder joints, so the character\r\n        // frame computed on the input rest stays valid throughout.\r\n        var cf = CharacterFrame.Compute(skeleton, map, world, worldUp);\r\n\r\n        DetectArms(map, world, cf, report);\r\n        NormalizeArms(skeleton, map, world, cf, report);\r\n        NormalizeLegsIfWide(skeleton, map, world, cf, report);\r\n\r\n        return (new RestPose { WorldRest = world }, report);\r\n    }\r\n\r\n    // ---------------------------------------------------------------- plausibility\r\n\r\n    /// <summary>\r\n    /// True when <paramref name=\"worldRest\"/> is plausible as an anatomical humanoid pose:\r\n    /// both rest thighs must point away from the shoulder line (thigh\u00b7up \u2264\r\n    /// <see cref=\"ThighMaxUpDot\"/>) and be no more than 120\u00b0 apart. Rigs without both\r\n    /// complete thighs (or a shoulder anchor) are unjudgeable and pass. Measured margins:\r\n    /// every anatomical corpus bind (T-pose, A-pose and the posed Defenses.fbx stance)\r\n    /// scores thigh\u00b7up \u2264 \u22120.75 / thighs \u2264 46\u00b0 apart; the SOMA uniform-skeleton stick bind\r\n    /// scores thigh\u00b7up +1.00 / 180\u00b0 apart.\r\n    /// </summary>\r\n    public static bool IsAnatomicalRest(\r\n        SkeletonModel skeleton, MappingResult map, IReadOnlyList<XForm> worldRest)\r\n    {\r\n        ArgumentNullException.ThrowIfNull(skeleton);\r\n        ArgumentNullException.ThrowIfNull(map);\r\n        ArgumentNullException.ThrowIfNull(worldRest);\r\n\r\n        Vector3? Pos(BoneRole role)\r\n            => map.RoleToBone.TryGetValue(role, out var i) && i < worldRest.Count\r\n                ? worldRest[i].Pos\r\n                : null;\r\n\r\n        Vector3? Dir(BoneRole from, BoneRole to)\r\n        {\r\n            var a = Pos(from);\r\n            var b = Pos(to);\r\n            if (a is null || b is null)\r\n                return null;\r\n            var d = b.Value - a.Value;\r\n            return d.LengthSquared() < 1e-8f ? null : Vector3.Normalize(d);\r\n        }\r\n\r\n        var hipL = Pos(BoneRole.UpperLegL);\r\n        var hipR = Pos(BoneRole.UpperLegR);\r\n        var thighL = Dir(BoneRole.UpperLegL, BoneRole.LowerLegL);\r\n        var thighR = Dir(BoneRole.UpperLegR, BoneRole.LowerLegR);\r\n        if (hipL is null || hipR is null || thighL is null || thighR is null)\r\n            return true; // legs unmapped/degenerate: cannot judge, preserve behavior\r\n\r\n        var midHips = (hipL.Value + hipR.Value) * 0.5f;\r\n        var midShoulders = Midpoint(Pos(BoneRole.UpperArmL), Pos(BoneRole.UpperArmR))\r\n            ?? Midpoint(Pos(BoneRole.ClavicleL), Pos(BoneRole.ClavicleR))\r\n            ?? Pos(BoneRole.Neck);\r\n        if (midShoulders is null)\r\n            return true; // no shoulder anchor: cannot judge\r\n\r\n        var upRaw = midShoulders.Value - midHips;\r\n        if (upRaw.LengthSquared() < 1e-8f)\r\n            return true;\r\n        var up = Vector3.Normalize(upRaw);\r\n\r\n        return Vector3.Dot(thighL.Value, up) <= ThighMaxUpDot\r\n            && Vector3.Dot(thighR.Value, up) <= ThighMaxUpDot\r\n            && Vector3.Dot(thighL.Value, thighR.Value) >= ThighPairMinDot;\r\n    }\r\n\r\n    private static Vector3? Midpoint(Vector3? a, Vector3? b)\r\n        => a is not null && b is not null ? (a.Value + b.Value) * 0.5f : null;\r\n\r\n    // ---------------------------------------------------------------- detection\r\n\r\n    private static void DetectArms(MappingResult map, XForm[] world, CharacterFrame cf, RestReport report)\r\n    {\r\n        var angleSum = 0f;\r\n        var upDotSum = 0f;\r\n        var count = 0;\r\n        var allBelowOrLevel = true;\r\n\r\n        foreach (var (upper, lower, sign) in new[]\r\n        {\r\n            (BoneRole.UpperArmL, BoneRole.LowerArmL, 1f),\r\n            (BoneRole.UpperArmR, BoneRole.LowerArmR, -1f),\r\n        })\r\n        {\r\n            if (!map.RoleToBone.TryGetValue(upper, out var u) || !map.RoleToBone.TryGetValue(lower, out var l))\r\n                continue;\r\n            var dir = world[l].Pos - world[u].Pos;\r\n            angleSum += Deg(MathQ.AngleBetween(dir, cf.Lateral * sign));\r\n            count++;\r\n            var upDot = Vector3.Dot(Vector3.Normalize(dir), cf.Up);\r\n            upDotSum += upDot;\r\n            // \"Below horizontal\" with a small tolerance so a T-pose arm 1\u00b0 above still counts.\r\n            allBelowOrLevel &= upDot < 0.05f;\r\n        }\r\n\r\n        if (count == 0)\r\n        {\r\n            report.Detected = DetectedPose.Other;\r\n            report.Notes.Add(\"Upper/lower arms unmapped; rest pose undetectable, no arm normalization.\");\r\n            return;\r\n        }\r\n\r\n        var angle = angleSum / count;\r\n        report.UpperArmAngleDeg = angle;\r\n        report.Detected = angle <= TPoseMaxDeg\r\n            ? DetectedPose.TPose\r\n            : angle <= APoseMaxDeg && allBelowOrLevel\r\n                ? DetectedPose.APose\r\n                // Hanging arms read ~90\u00b0 from lateral whether they point down OR forward;\r\n                // the up-dot cap keeps forward-reaching binds out of the I-pose class.\r\n                : angle <= IPoseMaxDeg && allBelowOrLevel && upDotSum / count <= IPoseMaxUpDot\r\n                    ? DetectedPose.IPose\r\n                    : DetectedPose.Other;\r\n        report.Notes.Add(\r\n            $\"Arm rest angle {angle:F1} deg from horizontal -> {report.Detected}.\");\r\n    }\r\n\r\n    // ---------------------------------------------------------------- arms\r\n\r\n    private static void NormalizeArms(\r\n        SkeletonModel skeleton, MappingResult map, XForm[] world, CharacterFrame cf, RestReport report)\r\n    {\r\n        foreach (var (side, left, sign) in new[] { (\"L\", true, 1f), (\"R\", false, -1f) })\r\n        {\r\n            if (!TryBone(map, \"UpperArm\" + side, out var upper) || !TryBone(map, \"LowerArm\" + side, out var lower))\r\n                continue;\r\n            var lateral = cf.Lateral * sign;\r\n\r\n            // 1. Swing the whole arm so (elbow - shoulder) hits exactly \u00b1lateral.\r\n            SwingSegment(skeleton, world, upper, world[lower].Pos - world[upper].Pos, lateral);\r\n\r\n            // 2. Re-measure and swing the forearm so (hand - elbow) is also \u00b1lateral (straight\r\n            //    arm; elbow flexion is not introduced or removed, only swing).\r\n            var hasHand = TryBone(map, \"Hand\" + side, out var hand);\r\n            if (hasHand)\r\n                SwingSegment(skeleton, world, lower, world[hand].Pos - world[lower].Pos, lateral);\r\n\r\n            // 3. Swing the hand along the limb axis using its anatomical chain-child point\r\n            //    (midpoint of the mapped finger proximals).\r\n            if (hasHand)\r\n            {\r\n                var knuckles = HandGeometry.FingerProximalMidpoint(map, world, left);\r\n                if (knuckles is not null)\r\n                    SwingSegment(skeleton, world, hand, knuckles.Value - world[hand].Pos, lateral);\r\n\r\n                // 4. Roll: rotate about the (now lateral) limb axis until the geometric dorsal\r\n                //    normal points up -> the canonical palm-down T-pose convention.\r\n                var dorsal = HandGeometry.Dorsal(map, world, left);\r\n                if (dorsal is not null)\r\n                {\r\n                    var rollDeg = RollAboutAxis(skeleton, world, hand, lateral, dorsal.Value, cf.Up);\r\n                    report.Notes.Add($\"Hand {side}: palm-down roll correction {rollDeg:F1} deg.\");\r\n                }\r\n                else\r\n                {\r\n                    report.Notes.Add($\"Hand {side}: fingers unmapped/degenerate, palm roll left as-is.\");\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    // ---------------------------------------------------------------- legs\r\n\r\n    private static void NormalizeLegsIfWide(\r\n        SkeletonModel skeleton, MappingResult map, XForm[] world, CharacterFrame cf, RestReport report)\r\n    {\r\n        var down = -cf.Up;\r\n        foreach (var side in new[] { \"L\", \"R\" })\r\n        {\r\n            if (!TryBone(map, \"UpperLeg\" + side, out var upper) || !TryBone(map, \"LowerLeg\" + side, out var lower))\r\n                continue;\r\n\r\n            var angle = Deg(MathQ.AngleBetween(world[lower].Pos - world[upper].Pos, down));\r\n            if (angle <= WideStanceMinDeg)\r\n                continue; // normal stance: leave the natural leg splay untouched\r\n\r\n            report.Notes.Add($\"Leg {side}: wide stance ({angle:F1} deg off vertical), normalized to vertical.\");\r\n            SwingSegment(skeleton, world, upper, world[lower].Pos - world[upper].Pos, down);\r\n            if (TryBone(map, \"Foot\" + side, out var foot))\r\n                SwingSegment(skeleton, world, lower, world[foot].Pos - world[lower].Pos, down);\r\n        }\r\n    }\r\n\r\n    // ---------------------------------------------------------------- mechanics\r\n\r\n    private static bool TryBone(MappingResult map, string roleName, out int bone)\r\n        => map.RoleToBone.TryGetValue(Enum.Parse<BoneRole>(roleName), out bone);\r\n\r\n    /// <summary>\r\n    /// Swings the subtree rooted at <paramref name=\"joint\"/> by the shortest-arc rotation\r\n    /// taking <paramref name=\"currentDir\"/> onto <paramref name=\"targetDir\"/>, pivoting at the\r\n    /// joint's head: descendant positions orbit the joint, orientations are premultiplied.\r\n    /// </summary>\r\n    private static void SwingSegment(\r\n        SkeletonModel skeleton, XForm[] world, int joint, Vector3 currentDir, Vector3 targetDir)\r\n        => RotateSubtree(skeleton, world, joint, MathQ.FromTo(currentDir, targetDir), world[joint].Pos);\r\n\r\n    /// <summary>\r\n    /// Rotates the subtree at <paramref name=\"joint\"/> about <paramref name=\"axis\"/> (through\r\n    /// the joint) by the signed angle that brings <paramref name=\"currentRef\"/>, projected \u22a5\r\n    /// axis, onto <paramref name=\"targetRef\"/> projected \u22a5 axis. Returns the applied angle in\r\n    /// degrees.\r\n    /// </summary>\r\n    private static float RollAboutAxis(\r\n        SkeletonModel skeleton, XForm[] world, int joint, Vector3 axis, Vector3 currentRef, Vector3 targetRef)\r\n    {\r\n        var a = currentRef - axis * Vector3.Dot(currentRef, axis);\r\n        var b = targetRef - axis * Vector3.Dot(targetRef, axis);\r\n        if (a.LengthSquared() < 1e-8f || b.LengthSquared() < 1e-8f)\r\n            return 0f;\r\n\r\n        var angle = MathF.Atan2(Vector3.Dot(Vector3.Cross(a, b), axis), Vector3.Dot(a, b));\r\n        RotateSubtree(skeleton, world, joint, Quaternion.CreateFromAxisAngle(axis, angle), world[joint].Pos);\r\n        return Deg(angle);\r\n    }\r\n\r\n    private static void RotateSubtree(\r\n        SkeletonModel skeleton, XForm[] world, int root, Quaternion rotation, Vector3 pivot)\r\n    {\r\n        // Bones are topologically sorted, so a single forward pass finds the whole subtree.\r\n        Span<bool> inSubtree = skeleton.Count <= 512 ? stackalloc bool[skeleton.Count] : new bool[skeleton.Count];\r\n        inSubtree[root] = true;\r\n        for (var i = root; i < skeleton.Count; i++)\r\n        {\r\n            var parent = skeleton[i].ParentIndex;\r\n            if (i != root && (parent < 0 || !inSubtree[parent]))\r\n                continue;\r\n            if (i != root)\r\n                inSubtree[i] = true;\r\n            world[i] = new XForm(\r\n                pivot + Vector3.Transform(world[i].Pos - pivot, rotation),\r\n                MathQ.Normalize(rotation * world[i].Rot));\r\n        }\r\n    }\r\n\r\n    private static float Deg(float radians) => radians * (180f / MathF.PI);\r\n}\r\n"
        },
        {
            "Ident": "notpointless.chomnr_humanoid_mocap",
            "Path": "HumanoidMocap/Solve/SolveOptions.cs",
            "FileName": "SolveOptions.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 395388,
            "IsPrivate": false,
            "Code": "#nullable enable annotations\r\n\r\nusing System.Collections.Generic;\r\nusing HumanoidMocap.Mapping;\r\n\r\nnamespace HumanoidMocap.Solve;\r\n\r\n/// <summary>How a mapped role's rotation is transferred by the <see cref=\"GeometricSolver\"/>.</summary>\r\npublic enum RoleTransferMode\r\n{\r\n    /// <summary>\r\n    /// Absolute canonical-orientation matching: the target's animated chain direction is\r\n    /// driven to <b>equal</b> the source's (in character-frame coordinates). Right for limbs\r\n    /// and the spine \u2014 the pose IS the direction \u2014 but it also imposes the source rig's rest\r\n    /// proportions/posture on roles whose rest directions legitimately differ between rigs.\r\n    /// </summary>\r\n    AbsoluteDirection,\r\n\r\n    /// <summary>\r\n    /// Rest-relative delta: the source's canonical-space rotation <i>delta from its own\r\n    /// normalized rest</i> is replayed onto the <b>target's</b> normalized rest\r\n    /// (<c>W_t(f) = C_t\u00b7\u0394C(f)\u00b7C_t\u207b\u00b9\u00b7R_tgtNormRest</c> with\r\n    /// <c>\u0394C(f) = C_s\u207b\u00b9\u00b7\u0394R(f)\u00b7C_s</c>). The target keeps its own rest carriage (shoulder\r\n    /// line height, neck-base angle) and moves with the source. Identical to\r\n    /// <see cref=\"AbsoluteDirection\"/> when source and target rigs coincide.\r\n    /// Clavicles use the delta relative to a shared mapped chest ancestor, then\r\n    /// inherit the solved target chest motion, so body turns do not become shrugs.\r\n    /// </summary>\r\n    DeltaFromRest,\r\n\r\n    /// <summary>\r\n    /// Character-space delta: the source's world-rotation delta from its normalized rest is\r\n    /// re-expressed in character coordinates and applied to the <b>target's</b> normalized\r\n    /// rest (<c>W_t(f) = M\u00b7\u0394R(f)\u00b7M\u207b\u00b9\u00b7R_tgtNormRest</c> with <c>M = Q_tgt\u00b7Q_src\u207b\u00b9</c>, the\r\n    /// same character basis change <see cref=\"AbsoluteDirection\"/> premultiplies). Like\r\n    /// <see cref=\"DeltaFromRest\"/> the target keeps its own rest carriage, but the delta\r\n    /// keeps its <i>world</i> rotation axes instead of being remapped through the per-role\r\n    /// canonical frames \u2014 the faithful replay when the rigs' rest chain directions diverge\r\n    /// so far that canonical-axis remapping would tilt every rotation axis by that\r\n    /// divergence (measured 23\u201344\u00b0 on feet: CMU/ARP ankle anatomy vs the s&amp;box rig's\r\n    /// steep ankle, where canonical remapping mis-pitched planted feet by up to 47\u00b0).\r\n    /// Identical to the other modes when source and target rigs coincide.\r\n    /// </summary>\r\n    CharacterDeltaFromRest,\r\n}\r\n\r\n/// <summary>Options controlling a single retarget solve (one clip \u2192 one output clip).</summary>\r\npublic sealed class SolveOptions\r\n{\r\n    // The grounding pipeline uses world vertical for legs as well as pelvis travel.\r\n    // Keep the standalone solver's character-relative direction contract unchanged.\r\n    internal bool GroundedLegDirections { get; init; }\r\n\r\n    /// <summary>\r\n    /// Default per-role transfer modes: shoulder girdle and neck carriage are\r\n    /// <see cref=\"RoleTransferMode.DeltaFromRest\"/> (each rig's clavicle line / neck-base\r\n    /// direction is rig anatomy, not pose \u2014 absolute matching was measured to drag the\r\n    /// s&amp;box shoulders 6\u201328\u00b0 toward the source's flatter/lower clavicle line and is the\r\n    /// \"low shoulders, hunched neck\" artifact), and feet are\r\n    /// <see cref=\"RoleTransferMode.CharacterDeltaFromRest\"/> (a rest foot\u2192toe direction is\r\n    /// ankle anatomy too \u2014 rigs diverge 11\u201344\u00b0 from the s&amp;box rig's steep ankle, so\r\n    /// absolute matching pitched planted feet up to 25\u00b0 off flat, the \"feet bent\r\n    /// upward/inward\" artifact; the character-space delta keeps the rotation's world axes,\r\n    /// which canonical-frame remapping would tilt by that same divergence). The head is\r\n    /// <see cref=\"RoleTransferMode.CharacterDeltaFromRest\"/> for the same reason: the rest\r\n    /// neck\u2192head direction is head-joint-placement anatomy (measured 0\u201327\u00b0 forward lean\r\n    /// across neutral-rest rigs vs the s&amp;box rig's 25.5\u00b0), so the target keeps its own\r\n    /// neutral skull attitude and replays the source's attitude <i>changes</i> \u2014 for the\r\n    /// head this computes exactly what the previous virtual-frame absolute matching did.\r\n    /// Two solver fallbacks adjust these defaults per rig pair: on a toe-less source the\r\n    /// foot entries become <see cref=\"RoleTransferMode.DeltaFromRest\"/> (virtual-foot\r\n    /// fallback), and a source whose normalized rest head attitude is implausible as a\r\n    /// neutral carriage (a posed bind \u2014 e.g. a chin-down/tilted fighting-stance rest,\r\n    /// measured 40.7\u00b0 forward / 16.9\u00b0 lateral on such a rig where the delta replay read\r\n    /// ~12\u00b0 \"looking up at an angle\") switches the head to\r\n    /// <see cref=\"RoleTransferMode.AbsoluteDirection\"/> so the gaze follows the source\r\n    /// absolutely instead of replaying deltas from a posed reference (see the\r\n    /// <see cref=\"GeometricSolver\"/> remarks for both). Everything else (limbs, spine,\r\n    /// toes, fingers) stays absolute: there the worldspace direction IS the pose.\r\n    /// Full-body motion captures override the clavicle default with observed absolute\r\n    /// directions: a body model's zero pose is not necessarily neutral shoulder carriage.\r\n    /// </summary>\r\n    public static IReadOnlyDictionary<BoneRole, RoleTransferMode> DefaultTransferModes { get; } =\r\n        new Dictionary<BoneRole, RoleTransferMode>\r\n        {\r\n            [BoneRole.ClavicleL] = RoleTransferMode.DeltaFromRest,\r\n            [BoneRole.ClavicleR] = RoleTransferMode.DeltaFromRest,\r\n            [BoneRole.Neck] = RoleTransferMode.DeltaFromRest,\r\n            [BoneRole.Head] = RoleTransferMode.CharacterDeltaFromRest,\r\n            [BoneRole.FootL] = RoleTransferMode.CharacterDeltaFromRest,\r\n            [BoneRole.FootR] = RoleTransferMode.CharacterDeltaFromRest,\r\n        };\r\n\r\n    /// <summary>\r\n    /// Per-role transfer modes. Null (default) = <see cref=\"DefaultTransferModes\"/> plus the\r\n    /// solver's fallback heuristics (a toe-less source's virtual foot direction overrides\r\n    /// the foot default to <see cref=\"RoleTransferMode.DeltaFromRest\"/>, and a posed-rest\r\n    /// source head overrides the head default to\r\n    /// <see cref=\"RoleTransferMode.AbsoluteDirection\"/> \u2014 see the\r\n    /// <see cref=\"GeometricSolver\"/> remarks). A non-null map REPLACES the defaults entirely\r\n    /// and disables every fallback heuristic: each role uses exactly the mode in the map, and\r\n    /// roles absent from it are <see cref=\"RoleTransferMode.AbsoluteDirection\"/>. Pass an\r\n    /// empty dictionary for fully absolute (legacy) behavior \u2014 API callers supplying a map\r\n    /// opt out of all heuristics.\r\n    /// </summary>\r\n    public IReadOnlyDictionary<BoneRole, RoleTransferMode>? TransferModes { get; init; }\r\n\r\n    // The motion-document path supplies reconstructed collarbone directions. Keep\r\n    // other per-role default heuristics active, and honor explicit transfer modes.\r\n    internal bool CaptureClavicleDirections { get; init; }\r\n\r\n    /// <summary>\r\n    /// Scale applied to the pelvis translation components perpendicular to the character up\r\n    /// direction. Null (default) = automatic: target hip height / source hip height, both\r\n    /// measured on the normalized rests.\r\n    /// </summary>\r\n    public float? HipScaleHorizontal { get; init; }\r\n\r\n    /// <summary>\r\n    /// Scale applied to the pelvis translation component along the character up direction.\r\n    /// Null (default) = the same automatic hip-height ratio as <see cref=\"HipScaleHorizontal\"/>.\r\n    /// </summary>\r\n    public float? HipScaleVertical { get; init; }\r\n\r\n    /// <summary>Whether finger roles are transferred; when false, target finger bones keep\r\n    /// their rest locals.</summary>\r\n    public bool TransferFingers { get; init; } = true;\r\n\r\n    /// <summary>Output clip name; null = the source clip's name.</summary>\r\n    public string? ClipName { get; init; }\r\n\r\n    /// <summary>Index of the source clip to retarget (<c>SourceScene.Clips</c>).</summary>\r\n    public int ClipIndex { get; init; }\r\n}\r\n"
        },
        {
            "Ident": "notpointless.chomnr_humanoid_mocap",
            "Path": "InferenceWorker/CameraRotationTrack.cs",
            "FileName": "CameraRotationTrack.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 395388,
            "IsPrivate": false,
            "Code": "using System.Numerics;\r\nusing System.Runtime.InteropServices;\r\nusing HumanoidMocap.Inference;\r\nusing OpenCvSharp;\r\n\r\nnamespace HumanoidMocap.Worker;\r\n\r\n/// <summary>Frame-to-frame rotation of a moving recording camera, in the role of GVHMR's\r\n/// SimpleVO: background features outside the followed person are tracked between sampled\r\n/// frames, a rotation is solved for each pair with the job's pinhole assumption, and the\r\n/// chain is interpolated to every frame. Only rotation is estimated. Camera translation and\r\n/// scene scale are not, so this is not camera tracking or world reconstruction.</summary>\r\npublic sealed class CameraRotationTrack : IDisposable\r\n{\r\n    public const string Version=\"background-rotation-v2\";\r\n    public const string FollowedPrefix=\"Moving recording camera followed:\";\r\n    const int WorkingWidth=640,Step=6,MinimumInliers=40;\r\n    public sealed record Result(float[] AngularVelocity6d,int Pairs,int UsablePairs,float TotalDegrees,float LargestPairDegrees,float MeanInlierRatio=0)\r\n    {\r\n        /// <summary>Background that fits one rotation homography this well shows little parallax, so the\r\n        /// camera turned about a nearly fixed point, as a standing operator's does. A camera that also\r\n        /// travels leaves parallax, and its position is then unknown.</summary>\r\n        public bool RotationOnly=>Usable&&MeanInlierRatio>=.8f;\r\n        /// <summary>Usable only when nearly every sampled pair could be solved.</summary>\r\n        public bool Usable=>Pairs>0&&UsablePairs>=Pairs*.8f;\r\n        public string Diagnostic=>Usable\r\n            ?FormattableString.Invariant($\"{FollowedPrefix} camera rotation solved from background features for {UsablePairs}/{Pairs} sampled frame pairs, {TotalDegrees:F1} degrees in total and at most {LargestPairDegrees:F1} degrees per pair, and supplied to GVHMR in place of a still-camera assumption. {MeanInlierRatio*100:F0}% of background features fit a pure rotation, so the camera is treated as {(RotationOnly?\"turning in place\":\"also travelling\")}. Rotation only, from an assumed lens; camera translation and scale are not recovered.\")\r\n            :FormattableString.Invariant($\"Moving recording camera could not be followed: only {UsablePairs}/{Pairs} sampled frame pairs had enough background features. The capture stays camera-relative.\");\r\n    }\r\n    readonly float focal;Mat? previous;Rect2f previousBody;int frames;\r\n    readonly List<(int Frame,Quaternion WorldToCamera)> samples=new();int pairs,usable;float largest,inlierRatios;\r\n    /// <param name=\"focalLength\">The job's pinhole focal length in source pixels.</param>\r\n    public CameraRotationTrack(float focalLength){if(!(focalLength>0))throw new ArgumentOutOfRangeException(nameof(focalLength));focal=focalLength;}\r\n    public void Add(DecodedVideoFrame frame,GvhmrDecoder.Box person,bool last)\r\n    {\r\n        var index=frames++;if(index%Step!=0&&!last)return;\r\n        var scale=WorkingWidth/(float)frame.Width;\r\n        using var rgba=new Mat(frame.Height,frame.Width,MatType.CV_8UC4);Marshal.Copy(frame.Rgba,0,rgba.Data,frame.Rgba.Length);\r\n        using var full=new Mat();Cv2.CvtColor(rgba,full,ColorConversionCodes.RGBA2GRAY);\r\n        var gray=new Mat();Cv2.Resize(full,gray,new Size(WorkingWidth,Math.Max(1,(int)MathF.Round(frame.Height*scale))),0,0,InterpolationFlags.Area);\r\n        var body=new Rect2f((person.CenterX-person.Size*.3f)*scale,(person.CenterY-person.Size*.55f)*scale,person.Size*.6f*scale,person.Size*1.1f*scale);\r\n        if(previous is null){previous=gray;previousBody=body;samples.Add((index,Quaternion.Identity));return;}\r\n        pairs++;var rotation=Solve(previous,gray,previousBody,body,focal*scale,out var inlierRatio);\r\n        if(rotation is { } solved)\r\n        {\r\n            usable++;largest=Math.Max(largest,Degrees(solved));inlierRatios+=inlierRatio;\r\n            samples.Add((index,Quaternion.Normalize(solved*samples[^1].WorldToCamera)));\r\n        }\r\n        else samples.Add((index,samples[^1].WorldToCamera)); // unsolved pair: no rotation claimed\r\n        previous.Dispose();previous=gray;previousBody=body;\r\n    }\r\n    static float Degrees(Quaternion q)=>2*MathF.Acos(Math.Clamp(MathF.Abs(q.W),0,1))*180/MathF.PI;\r\n    static Quaternion? Solve(Mat a,Mat b,Rect2f bodyA,Rect2f bodyB,float f,out float inlierRatio)\r\n    {\r\n        inlierRatio=0;\r\n        using var mask=new Mat(a.Size(),MatType.CV_8UC1,Scalar.White);\r\n        Cv2.Rectangle(mask,new Rect((int)bodyA.X,(int)bodyA.Y,(int)bodyA.Width,(int)bodyA.Height),Scalar.Black,-1);\r\n        var points=Cv2.GoodFeaturesToTrack(a,600,.01,7,mask,3,false,.04);\r\n        if(points.Length<MinimumInliers)return null;\r\n        var tracked=new Point2f[points.Length];Cv2.CalcOpticalFlowPyrLK(a,b,points,ref tracked,out var status,out _,new Size(21,21),4);\r\n        var returned=new Point2f[points.Length];Cv2.CalcOpticalFlowPyrLK(b,a,tracked,ref returned,out var back,out _,new Size(21,21),4);\r\n        var from=new List<Point2d>();var to=new List<Point2d>();\r\n        for(var i=0;i<points.Length;i++)\r\n        {\r\n            if(status[i]==0||back[i]==0||bodyB.Contains(tracked[i])||points[i].DistanceTo(returned[i])>1)continue;\r\n            from.Add(new(points[i].X,points[i].Y));to.Add(new(tracked[i].X,tracked[i].Y));\r\n        }\r\n        if(from.Count<MinimumInliers)return null;\r\n        // Distant background under camera rotation moves by the homography K R K^-1.\r\n        using var inliers=new Mat();\r\n        using var homography=Cv2.FindHomography(from,to,HomographyMethods.Ransac,1.5,inliers);\r\n        if(homography.Empty()||Cv2.CountNonZero(inliers)<Math.Max(MinimumInliers,from.Count*.5))return null;\r\n        inlierRatio=Cv2.CountNonZero(inliers)/(float)from.Count;\r\n        double cx=(a.Width-1)*.5,cy=(a.Height-1)*.5;\r\n        var h=new double[3,3];for(var r=0;r<3;r++)for(var c=0;c<3;c++)h[r,c]=homography.At<double>(r,c);\r\n        double[,] k={{f,0,cx},{0,f,cy},{0,0,1}},inverse={{1/f,0,-cx/f},{0,1/f,-cy/f},{0,0,1}};\r\n        var m=Multiply(inverse,Multiply(h,k));\r\n        // Nearest rotation: orthonormalise with SVD and fix the sign.\r\n        using var matrix=new Mat(3,3,MatType.CV_64FC1);for(var r=0;r<3;r++)for(var c=0;c<3;c++)matrix.Set(r,c,m[r,c]);\r\n        using var w=new Mat();using var u=new Mat();using var vt=new Mat();Cv2.SVDecomp(matrix,w,u,vt);\r\n        using var product=(u*vt).ToMat();var rotation=new double[3,3];for(var r=0;r<3;r++)for(var c=0;c<3;c++)rotation[r,c]=product.At<double>(r,c);\r\n        var determinant=rotation[0,0]*(rotation[1,1]*rotation[2,2]-rotation[1,2]*rotation[2,1])-rotation[0,1]*(rotation[1,0]*rotation[2,2]-rotation[1,2]*rotation[2,0])+rotation[0,2]*(rotation[1,0]*rotation[2,1]-rotation[1,1]*rotation[2,0]);\r\n        if(determinant<0)for(var r=0;r<3;r++)for(var c=0;c<3;c++)rotation[r,c]=-rotation[r,c];\r\n        // System.Numerics uses row vectors: its matrix is the transpose of this column-vector rotation.\r\n        var q=Quaternion.Normalize(Quaternion.CreateFromRotationMatrix(new Matrix4x4(\r\n            (float)rotation[0,0],(float)rotation[1,0],(float)rotation[2,0],0,(float)rotation[0,1],(float)rotation[1,1],(float)rotation[2,1],0,\r\n            (float)rotation[0,2],(float)rotation[1,2],(float)rotation[2,2],0,0,0,0,1)));\r\n        // A sampled pair a fifth of a second apart cannot plausibly turn this far; treat it as a failed solve.\r\n        return float.IsFinite(q.W)&&Degrees(q)<=25?q:null;\r\n    }\r\n    static double[,] Multiply(double[,] a,double[,] b)\r\n    {var result=new double[3,3];for(var r=0;r<3;r++)for(var c=0;c<3;c++)for(var i=0;i<3;i++)result[r,c]+=a[r,i]*b[i,c];return result;}\r\n    public Result Finish()\r\n    {\r\n        var count=frames;var result=new float[count*6];if(count==0)return new(result,0,0,0,0);\r\n        var orientation=new Quaternion[count];var next=0;\r\n        for(var t=0;t<count;t++)\r\n        {\r\n            while(next<samples.Count-1&&samples[next+1].Frame<=t)next++;\r\n            var a=samples[next];var b=samples[Math.Min(next+1,samples.Count-1)];\r\n            orientation[t]=b.Frame==a.Frame?a.WorldToCamera:Quaternion.Slerp(a.WorldToCamera,b.WorldToCamera,Math.Clamp((t-a.Frame)/(float)(b.Frame-a.Frame),0,1));\r\n        }\r\n        for(var t=0;t<count;t++)\r\n        {\r\n            // GVHMR compute_cam_angvel: R[t+1] R[t]^T, with the final value repeated.\r\n            var s=Math.Min(t,count-2);var relative=count<2?Quaternion.Identity:Quaternion.Normalize(orientation[s+1]*Quaternion.Conjugate(orientation[s]));\r\n            var m=Matrix4x4.CreateFromQuaternion(relative);\r\n            // First two rows of the column-vector rotation matrix (PyTorch3D 6D layout).\r\n            result[t*6]=m.M11;result[t*6+1]=m.M21;result[t*6+2]=m.M31;result[t*6+3]=m.M12;result[t*6+4]=m.M22;result[t*6+5]=m.M32;\r\n        }\r\n        return new(result,pairs,usable,Degrees(orientation[^1]),largest,usable==0?0:inlierRatios/usable);\r\n    }\r\n    public void Dispose(){previous?.Dispose();previous=null;}\r\n}\r\n"
        },
        {
            "Ident": "notpointless.chomnr_humanoid_mocap",
            "Path": "InferenceWorker/HandModelDownloads.cs",
            "FileName": "HandModelDownloads.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 395388,
            "IsPrivate": false,
            "Code": "using HumanoidMocap.Inference;\r\nusing System.Security.Cryptography;\r\nusing System.Text.Json;\r\n\r\nnamespace HumanoidMocap.Worker;\r\n\r\n/// <summary>Only the explicitly selected backend and its crop detector are downloaded.</summary>\r\npublic static class HandModelDownloads\r\n{\r\n    public sealed record Asset(string Path,string Url,long Bytes,string Sha256);\r\n    static readonly Asset Detector=new(\"hand_landmarker.task\",\r\n        \"https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task\",\r\n        7819105,\"fbc2a30080c3c557093b5ddfc334698132eb341044ccee322ccf8bcf3607cde1\");\r\n    static readonly Asset WildHands=new(\"wildhands/wildhands.ckpt\",\r\n        \"https://drive.usercontent.google.com/download?id=1FJWBrMmTKjKAo6j5DQS1KYpqqFbAbJ9Q&export=download&confirm=t\",\r\n        855094722,\"cac3f9a9334da852f3993e95b4ec088dcc6c69f0337db63dacd83e4642880a7b\");\r\n    static readonly Asset Wilor=new(\"wilor/wilor_final.ckpt\",\r\n        \"https://huggingface.co/spaces/rolpotamias/WiLoR/resolve/99fe3d7acff8104ecca1055df7467709506c2fa6/pretrained_models/wilor_final.ckpt\",\r\n        2564989533,\"3e97aafc7dd08d883a4cc5a027df61fdb6fda6136dbd1319405413862ada6bb2\");\r\n    static readonly Asset MobileHand=new(\"mobilehand/hmr_model_freihand_auc.pth\",\r\n        \"https://raw.githubusercontent.com/gmntu/mobilehand/51c112364013b803c38955b55a1572b0d402894c/model/hmr_model_freihand_auc.pth\",\r\n        15152098,MobileHandModel.CheckpointSha256);\r\n\r\n    public static async Task Ensure(string folder,string backend,CancellationToken token)\r\n    {\r\n        var assets=backend switch{\"mediapipe\"=>new[]{Detector},\"mobilehand\"=>new[]{Detector,MobileHand},\"wildhands\"=>new[]{Detector,WildHands},\"wilor\"=>new[]{Detector,Wilor},_=>throw new NotSupportedException(\"Select MediaPipe, MobileHand, WildHands or WiLoR. ACE is not downloaded or loaded by this worker.\")};\r\n        using var http=new HttpClient{Timeout=TimeSpan.FromHours(1)};\r\n        foreach(var asset in assets)\r\n        {\r\n            var path=Path.Combine(folder,asset.Path);Directory.CreateDirectory(Path.GetDirectoryName(path)!);\r\n            if(!File.Exists(path))await ModelDownload.Fetch(http,asset.Url,path,asset.Bytes,asset.Sha256,Console.WriteLine,token);\r\n            else await Verify(path,asset,token);\r\n            Console.WriteLine(\"Verified \"+Path.GetFileName(path));\r\n        }\r\n        File.WriteAllText(Path.Combine(folder,backend+\"-models.json\"),JsonSerializer.Serialize(new{backend,assets,verifiedUtc=DateTime.UtcNow},new JsonSerializerOptions{WriteIndented=true}));\r\n    }\r\n    static async Task Verify(string path,Asset asset,CancellationToken token)\r\n    {\r\n        if(new FileInfo(path).Length!=asset.Bytes)throw new InvalidDataException(\"Unexpected model size; original preserved: \"+path);\r\n        var hash=await Task.Run(()=>FileChecksum.Sha256(path),token);\r\n        if(!hash.Equals(asset.Sha256,StringComparison.OrdinalIgnoreCase))throw new InvalidDataException(\"Model checksum mismatch; original preserved: \"+path);\r\n    }\r\n}\r\n"
        },
        {
            "Ident": "notpointless.chomnr_humanoid_mocap",
            "Path": "Editor/HumanoidMocap/MappingEditor.cs",
            "FileName": "MappingEditor.cs",
            "PackageType": "library",
            "CodeKind": "Editor",
            "AssetVersionId": 395388,
            "IsPrivate": false,
            "Code": "#nullable enable annotations\r\n\r\nusing System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing Editor;\r\nusing HumanoidMocap.Mapping;\r\nusing SkeletonModel = HumanoidMocap.Skeleton.Skeleton;\r\n\r\nnamespace HumanoidMocap.Editor;\r\n\r\n/// <summary>\r\n/// Manual bone-mapping editor: one row per canonical <see cref=\"BoneRole\"/>, grouped\r\n/// anatomically (Body / Arms / Legs / Fingers L / Fingers R), each with a combo of the\r\n/// source skeleton's bone names (plus <c>&lt;none&gt;</c>), pre-filled from the entry's\r\n/// current mapping. Apply produces a <see cref=\"MappingSource.Manual\"/>\r\n/// <see cref=\"MappingResult\"/> that the window installs as the file's mapping override.\r\n/// </summary>\r\npublic sealed class MappingEditor : Dialog\r\n{\r\n\tstatic readonly (string Group, BoneRole[] Roles)[] Groups =\r\n\t{\r\n\t\t(\"Body\", new[]\r\n\t\t{\r\n\t\t\tBoneRole.Hips, BoneRole.Spine0, BoneRole.Spine1, BoneRole.Spine2,\r\n\t\t\tBoneRole.Spine3, BoneRole.Spine4, BoneRole.Neck, BoneRole.Head,\r\n\t\t}),\r\n\t\t(\"Arms\", new[]\r\n\t\t{\r\n\t\t\tBoneRole.ClavicleL, BoneRole.UpperArmL, BoneRole.LowerArmL, BoneRole.HandL,\r\n\t\t\tBoneRole.ClavicleR, BoneRole.UpperArmR, BoneRole.LowerArmR, BoneRole.HandR,\r\n\t\t}),\r\n\t\t(\"Legs\", new[]\r\n\t\t{\r\n\t\t\tBoneRole.UpperLegL, BoneRole.LowerLegL, BoneRole.FootL, BoneRole.ToeL,\r\n\t\t\tBoneRole.UpperLegR, BoneRole.LowerLegR, BoneRole.FootR, BoneRole.ToeR,\r\n\t\t}),\r\n\t\t(\"Fingers (left)\", FingerRoles( \"L\" )),\r\n\t\t(\"Fingers (right)\", FingerRoles( \"R\" )),\r\n\t};\r\n\r\n\tstatic BoneRole[] FingerRoles( string side )\r\n\t\t=> Enum.GetValues<BoneRole>()\r\n\t\t\t.Where( r => r.ToString().EndsWith( side, StringComparison.Ordinal )\r\n\t\t\t\t&& (r.ToString().StartsWith( \"Thumb\" ) || r.ToString().StartsWith( \"Index\" )\r\n\t\t\t\t\t|| r.ToString().StartsWith( \"Middle\" ) || r.ToString().StartsWith( \"Ring\" )\r\n\t\t\t\t\t|| r.ToString().StartsWith( \"Pinky\" )) )\r\n\t\t\t.ToArray();\r\n\r\n\treadonly SkeletonModel _skeleton;\r\n\treadonly Dictionary<BoneRole, int> _selection;\r\n\r\n\t/// <summary>Invoked with the manual mapping when the user applies.</summary>\r\n\tpublic Action<MappingResult> Applied { get; set; }\r\n\r\n\t/// <summary>Creates the editor pre-filled from <paramref name=\"current\"/>.</summary>\r\n\tpublic MappingEditor( Widget parent, string fileName, SkeletonModel skeleton, MappingResult current )\r\n\t\t: base( parent )\r\n\t{\r\n\t\t_skeleton = skeleton;\r\n\t\t_selection = new Dictionary<BoneRole, int>( current?.RoleToBone ?? new Dictionary<BoneRole, int>() );\r\n\r\n\t\tWindow.WindowTitle = $\"Bone Mapping - {fileName}\";\r\n\t\tWindow.SetWindowIcon( \"device_hub\" );\r\n\t\tWindow.SetModal( true, true );\r\n\t\tWindow.MinimumWidth = 460;\r\n\t\tWindow.MinimumHeight = 600;\r\n\r\n\t\tLayout = Layout.Column();\r\n\t\tLayout.Margin = 12;\r\n\t\tLayout.Spacing = 8;\r\n\r\n\t\tLayout.Add( new Label( this )\r\n\t\t{\r\n\t\t\tText = \"Assign bones to their roles; leave absent bones at <none>. For hand capture, \"\r\n\t\t\t\t+ \"map each wrist and its finger chains. Arms are optional; a torso and legs are not required.\",\r\n\t\t\tWordWrap = true,\r\n\t\t} );\r\n\r\n\t\tvar scroll = Layout.Add( new ScrollArea( this ), 1 );\r\n\t\tscroll.Canvas = new Widget( scroll );\r\n\t\tscroll.Canvas.Layout = Layout.Column();\r\n\t\tscroll.Canvas.Layout.Margin = new Sandbox.UI.Margin( 4, 4, 16, 4 );\r\n\t\tscroll.Canvas.Layout.Spacing = 4;\r\n\t\tvar canvas = scroll.Canvas.Layout;\r\n\r\n\t\tforeach ( var (group, roles) in Groups )\r\n\t\t{\r\n\t\t\tvar header = canvas.Add( new Label( this ) { Text = group } );\r\n\t\t\theader.SetStyles( $\"font-weight: 600; color: {Theme.Blue.Hex}; margin-top: 8px;\" );\r\n\r\n\t\t\tforeach ( var role in roles )\r\n\t\t\t\tcanvas.Add( BuildRoleRow( role ) );\r\n\t\t}\r\n\r\n\t\tcanvas.AddStretchCell();\r\n\r\n\t\tvar buttons = Layout.AddRow();\r\n\t\tbuttons.Spacing = 8;\r\n\t\tbuttons.AddStretchCell();\r\n\t\tbuttons.Add( new Button( \"Cancel\" ) { Clicked = Close } );\r\n\t\tvar apply = buttons.Add( new Button.Primary( \"Apply Mapping\" ) { Icon = \"check\" } );\r\n\t\tapply.Clicked = Apply;\r\n\r\n\t\tWindow.Size = new Vector2( 520, 720 );\r\n\t}\r\n\r\n\tWidget BuildRoleRow( BoneRole role )\r\n\t{\r\n\t\tvar row = new Widget( this );\r\n\t\trow.Layout = Layout.Row();\r\n\t\trow.Layout.Spacing = 8;\r\n\r\n\t\trow.Layout.Add( new Label( this ) { Text = role.ToString(), FixedWidth = 130 } );\r\n\r\n\t\tvar combo = row.Layout.Add( new ComboBox( this ), 1 );\r\n\t\tcombo.AddItem( \"<none>\", \"block\",\r\n\t\t\t() => _selection.Remove( role ),\r\n\t\t\tselected: !_selection.ContainsKey( role ) );\r\n\r\n\t\tfor ( var i = 0; i < _skeleton.Count; i++ )\r\n\t\t{\r\n\t\t\tvar boneIndex = i;\r\n\t\t\tcombo.AddItem( _skeleton[i].Name, null,\r\n\t\t\t\t() => _selection[role] = boneIndex,\r\n\t\t\t\tselected: _selection.TryGetValue( role, out var sel ) && sel == boneIndex );\r\n\t\t}\r\n\r\n\t\treturn row;\r\n\t}\r\n\r\n\tvoid Apply()\r\n\t{\r\n\t\t// Reject duplicate assignments up front (the target rig builder would throw later).\r\n\t\tvar duplicates = _selection.GroupBy( kv => kv.Value ).Where( g => g.Count() > 1 ).ToList();\r\n\t\tif ( duplicates.Count > 0 )\r\n\t\t{\r\n\t\t\tvar first = duplicates[0];\r\n\t\t\tvar roles = string.Join( \", \", first.Select( kv => kv.Key ) );\r\n\t\t\tnew PopupWindow( \"Duplicate assignment\",\r\n\t\t\t\t$\"Bone \\\"{_skeleton[first.Key].Name}\\\" is assigned to multiple roles: {roles}.\" )\r\n\t\t\t\t.Show();\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tvar result = new MappingResult( \"manual\", MappingSource.Manual ) { Confidence = 1f };\r\n\t\tforeach ( var kv in _selection )\r\n\t\t\tresult.RoleToBone[kv.Key] = kv.Value;\r\n\t\tresult.Notes.Add( \"Mapping assigned by hand in the mapping editor.\" );\r\n\r\n\t\tApplied?.Invoke( result );\r\n\t\tClose();\r\n\t}\r\n}\r\n"
        },
        {
            "Ident": "notpointless.chomnr_humanoid_mocap",
            "Path": "Editor/HumanoidMocap/MocapContactEditor.cs",
            "FileName": "MocapContactEditor.cs",
            "PackageType": "library",
            "CodeKind": "Editor",
            "AssetVersionId": 395388,
            "IsPrivate": false,
            "Code": "using System;\r\nusing System.Globalization;\r\nusing System.Linq;\r\nusing System.Threading.Tasks;\r\nusing Editor;\r\nusing Sandbox;\r\nusing HumanoidMocap.Mapping;\r\nusing HumanoidMocap.Motion;\r\n\r\nnamespace HumanoidMocap.Editor;\r\n\r\npublic sealed partial class RetargetWindow\r\n{\r\n    internal ContactEditorDialog OpenContactEditor(ContactInterval contact=null)\r\n    {\r\n        if(_editedMotion is null||_processing is not null)return null;\r\n        var dialog=new ContactEditorDialog(this,contact);dialog.Show();return dialog;\r\n    }\r\n\r\n    internal async Task SaveContactAsync(MotionDocument expected,int index,ContactInterval replacement)\r\n    {\r\n        if(expected!=_editedMotion||_processing is not null)throw new InvalidOperationException(\"The capture changed. Reopen contact editing.\");\r\n        var candidate=ContactAuthoring.Replace(expected,index,replacement);\r\n        if(new PropContactMotion(candidate).UnsupportedReason(replacement) is {} reason)throw new ArgumentException(reason);\r\n        var source=_rawMotion.Copy();source.Contacts=candidate.Contacts;\r\n        _editedMotion=_appliedCleanup is null?source:MotionCleanup.Apply(source,_appliedCleanup);\r\n        RefreshContacts();await RefreshMocapPreviewAsync();\r\n    }\r\n\r\n    internal sealed class ContactEditorDialog : Dialog\r\n    {\r\n        internal readonly LineEdit Start,End,Target;\r\n        internal readonly Label Status;\r\n        internal readonly Button Save;\r\n        internal readonly Button Place;\r\n        internal readonly Checkbox HoldOrientation;\r\n        internal readonly Checkbox Sliding;\r\n        internal readonly MocapContactKeys Keys;\r\n        internal FingerContactDialog FingerDialog;\r\n        internal readonly Button Fingers;\r\n        readonly MotionDocument _motion;\r\n        readonly ContactInterval _draft;\r\n\r\n        public ContactEditorDialog(RetargetWindow owner,ContactInterval contact):base(owner)\r\n        {\r\n            _motion=owner._editedMotion;\r\n            var index=contact is null?-1:_motion.Contacts.IndexOf(contact);\r\n            _draft=index<0?new(){Start=_motion.Frames[0].Time,End=_motion.Frames[^1].Time,Review=ContactReview.Suggested}:\r\n                _motion.Copy().Contacts[index];\r\n            Window.WindowTitle=index<0?\"Add wrist contact\":\"Edit wrist contact\";Window.SetWindowIcon(\"touch_app\");\r\n            Window.Size=new Vector2(560,430);Window.MinimumSize=new Vector2(520,420);\r\n            SetStyles($\"background-color: {Theme.WidgetBackground.Hex}; color: {Theme.Text.Hex};\");\r\n            Layout=Layout.Column();Layout.Margin=16;Layout.Spacing=10;\r\n            Layout.Add(new Label(\"Place an object-local wrist anchor and review it against the video. Saved edits return to Suggested; confirm them in Contact review.\",this){WordWrap=true});\r\n            var hands=Layout.Add(new ComboBox(this));\r\n            foreach(var bone in _motion.Bones.Where(b=>b.Role is BoneRole.HandL or BoneRole.HandR))\r\n            {var name=bone.Name;if(string.IsNullOrEmpty(_draft.Bone))_draft.Bone=name;hands.AddItem(name,onSelected:()=>{if(_draft.Bone!=name)_draft.LocalRotation=null;_draft.Bone=name;},selected:name==_draft.Bone);}\r\n            var props=Layout.Add(new ComboBox(this));\r\n            foreach(var prop in _motion.Objects)foreach(var bone in prop.Bones)\r\n            {\r\n                var id=prop.Id;var name=bone.Name;if(string.IsNullOrEmpty(_draft.Object)){_draft.Object=id;_draft.ObjectBone=name;}\r\n                props.AddItem(id+\" / \"+name,onSelected:()=>{if(_draft.Object!=id||_draft.ObjectBone!=name)_draft.LocalRotation=null;_draft.Object=id;_draft.ObjectBone=name;},\r\n                    selected:id==_draft.Object&&(name==_draft.ObjectBone||string.IsNullOrEmpty(_draft.ObjectBone)&&bone.Parent<0));\r\n            }\r\n            LineEdit Input(string title,string value){var row=Layout.AddRow();row.Spacing=8;row.Add(new Label(title,this){FixedWidth=160});return row.Add(new LineEdit(this){Text=value},1);}\r\n            Start=Input(\"Start at video (s)\",_draft.Start.ToString(\"R\",CultureInfo.InvariantCulture));\r\n            End=Input(\"End at video (s)\",_draft.End.ToString(\"R\",CultureInfo.InvariantCulture));\r\n            Target=Input(\"Local X, Y, Z (m)\",string.Join(\",\",_draft.LocalTarget.Select(v=>v.ToString(\"R\",CultureInfo.InvariantCulture))));\r\n            HoldOrientation=Layout.Add(new Checkbox(\"Hold wrist orientation relative to prop\"){Value=_draft.LocalRotation is not null});\r\n            HoldOrientation.ToolTip=\"Optional for a rigid grip. Place an anchor below, then review and confirm. Captured finger articulation remains unchanged.\";\r\n            var place=Place=Layout.Add(new Button(\"Use wrist at interval midpoint\",\"my_location\"));\r\n            place.ToolTip=\"Use the captured wrist at the nearest midpoint sample. This places a manual anchor; it does not detect a grip. Sliding position keys are preserved.\";\r\n            Status=new Label(\"\",this){WordWrap=true};\r\n            Status.SetStyles($\"color: {Theme.Yellow.Hex};\");\r\n            Sliding=Layout.Add(new Checkbox(\"Sliding contact \u00b7 animate the local wrist target\"){Value=_draft.Sliding});\r\n            Sliding.ToolTip=\"Uses at least two authored position keys. Turning this off keeps the keys for later and uses the fixed anchor above.\";\r\n            void RefreshMode()\r\n            {\r\n                _draft.Sliding=Sliding.Value;Keys.Visible=Sliding.Value;Target.ReadOnly=Sliding.Value;\r\n                props.Enabled=hands.Enabled=_draft.TargetKeys.Count==0&&_draft.FingerTargets.Count==0;\r\n                props.ToolTip=hands.ToolTip=!props.Enabled?\"Remove the draft's position keys and finger points before changing their hand or object coordinate frame.\":\"\";\r\n                Window.Size=new Vector2(560,Sliding.Value?700:500);\r\n            }\r\n            Keys=Layout.Add(new MocapContactKeys(this,_motion,_draft,Read,()=>owner.PlaybackTime,message=>Status.Text=message,RefreshMode));\r\n            Sliding.Clicked=RefreshMode;RefreshMode();\r\n            var fingerActions=Layout.AddRow();fingerActions.Spacing=8;\r\n            Fingers=fingerActions.Add(new Button(\"Finger contact points\u2026\",\"touch_app\"),1);\r\n            fingerActions.Add(new Button(\"Clear points\",\"clear\"){ToolTip=\"Remove all target-specific finger points from this draft. Cancel restores the saved contact.\",Clicked=()=>{\r\n                _draft.FingerTargets.Clear();RefreshMode();Status.Text=\"Finger points removed from this draft. Save to keep the change, or cancel to restore them.\";\r\n            }});\r\n            Fingers.Clicked=()=>{try{Read();FingerDialog=new(owner,this,_draft,()=>{RefreshMode();Status.Text=$\"{_draft.FingerTargets.Count} target-specific finger points. Save and review before confirming.\";});FingerDialog.Show();}catch(Exception error){Status.Text=error.Message;}};\r\n            Layout.Add(Status);\r\n            place.Clicked=()=>{try{Read();var time=ContactAuthoring.PlaceAtWrist(_motion,_draft,HoldOrientation.Value);Target.Text=string.Join(\",\",_draft.LocalTarget.Select(v=>v.ToString(\"R\",CultureInfo.InvariantCulture)));Status.Text=$\"Manual anchor placed from the wrist at {time:F3} s.\";}catch(Exception e){Status.Text=e.Message;}};\r\n            var buttons=Layout.AddRow();buttons.AddStretchCell();buttons.Add(new Button(\"Cancel\"){Clicked=Close});Save=buttons.Add(new Button.Primary(\"Save suggestion\"));\r\n            Save.Clicked=async ()=>{\r\n                try{Read();if(Sliding.Value&&Keys.HasUnappliedChanges)throw new ArgumentException(\"Add or update the edited sliding key before saving.\");\r\n                    if(HoldOrientation.Value&&_draft.LocalRotation is null)throw new ArgumentException(\"Use wrist at interval midpoint to place the orientation anchor.\");\r\n                    if(!HoldOrientation.Value)_draft.LocalRotation=null;\r\n                    _draft.Review=ContactReview.Suggested;_draft.Reason=\"Manually placed/edited wrist contact; requires review against the video.\";\r\n                    Save.Enabled=false;await owner.SaveContactAsync(_motion,index,_draft);await EditorPipeline.SwitchToMainThread();if(this.IsValid())Close();}\r\n                catch(Exception e){await EditorPipeline.SwitchToMainThread();if(this.IsValid()){Status.Text=e.Message;Save.Enabled=true;}}\r\n            };\r\n        }\r\n        void Read()\r\n        {\r\n            _draft.Start=double.Parse(Start.Text,CultureInfo.InvariantCulture);_draft.End=double.Parse(End.Text,CultureInfo.InvariantCulture);\r\n            _draft.LocalTarget=MotionDocument.A(Vector(Target));\r\n        }\r\n    }\r\n}\r\n"
        },
        {
            "Ident": "notpointless.chomnr_humanoid_mocap",
            "Path": "Editor/HumanoidMocap/MocapContactTimeline.cs",
            "FileName": "MocapContactTimeline.cs",
            "PackageType": "library",
            "CodeKind": "Editor",
            "AssetVersionId": 395388,
            "IsPrivate": false,
            "Code": "using System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing System.Threading.Tasks;\r\nusing Editor;\r\nusing Sandbox;\r\nusing HumanoidMocap.Mapping;\r\nusing HumanoidMocap.Motion;\r\n\r\nnamespace HumanoidMocap.Editor;\r\n\r\n// Uses the exposed Widget/Paint/Menu APIs and Theme colors used by sbox-public's\r\n// scrub bars. No MovieMaker session or engine-internal timeline dependency.\r\nsealed class MocapContactTimeline : Widget\r\n{\r\n    MotionDocument motion;\r\n    IReadOnlyList<WristPositionOffset> wristOffsets=Array.Empty<WristPositionOffset>();\r\n    double playhead;\r\n    public Action<float> Seek { get; set; }\r\n    public Action<MotionDocument,int> Edit { get; set; }\r\n    public Action<MotionDocument,int,ContactReview> Review { get; set; }\r\n    public Action<MotionDocument,int,double,double> ChangeRange { get; set; }\r\n    public Action<MotionDocument,WristPositionOffset> EditWrist { get; set; }\r\n\r\n    public MocapContactTimeline(Widget parent):base(parent)\r\n    {\r\n        FixedHeight=36;MouseTracking=true;Visible=false;\r\n        ToolTip=\"Contact intervals: yellow needs review, green confirmed, gray disabled. Click to seek; double-click to edit; right-click for review and timing.\";\r\n    }\r\n    public void SetMotion(MotionDocument value,IReadOnlyList<WristPositionOffset> edits=null)\r\n    {motion=value;wristOffsets=edits?.ToArray()??Array.Empty<WristPositionOffset>();Visible=value is not null&&(value.Contacts.Count>0||wristOffsets.Count>0);Update();}\r\n    public void SetPlayhead(double time)\r\n    {if(playhead==time)return;playhead=time;Update();}\r\n    float X(double time)\r\n    {\r\n        if(motion is null)return 6;\r\n        var start=motion.Frames[0].Time;var duration=motion.Frames[^1].Time-start;\r\n        return 6+(float)(duration>0?Math.Clamp((time-start)/duration,0,1):0)*Math.Max(1,Width-12);\r\n    }\r\n    float Fraction(float x)=>Math.Clamp((x-6)/Math.Max(1,Width-12),0,1);\r\n    int Lane(ContactInterval contact)=>motion.Bones.FirstOrDefault(b=>b.Name==contact.Bone)?.Role==BoneRole.HandR?1:0;\r\n    string Side(ContactInterval contact)=>motion.Bones.FirstOrDefault(b=>b.Name==contact.Bone)?.Role switch\r\n        {BoneRole.HandL=>\"L\",BoneRole.HandR=>\"R\",_=>contact.Bone};\r\n    internal Rect ContactRect(int index)\r\n    {\r\n        var c=motion.Contacts[index];var start=X(c.Start);var end=X(c.End);\r\n        return new(start,2+Lane(c)*18,Math.Max(2,end-start),14);\r\n    }\r\n    int[] Hits(Vector2 position)=>motion is null?Array.Empty<int>():Enumerable.Range(0,motion.Contacts.Count)\r\n        .Where(i=>ContactRect(i).Grow(2).IsInside(position)).ToArray();\r\n    internal Rect WristRect(int index)\r\n    {\r\n        var edit=wristOffsets[index];var start=X(edit.Start);var end=X(edit.End);\r\n        return new(start,15+(edit.Hand==BoneRole.HandR?18:0),Math.Max(2,end-start),3);\r\n    }\r\n    int[] WristHits(Vector2 position)=>Enumerable.Range(0,wristOffsets.Count).Where(i=>WristRect(i).Grow(2).IsInside(position)).ToArray();\r\n    protected override void OnPaint()\r\n    {\r\n        Paint.ClearPen();Paint.SetBrush(Theme.WindowBackground);Paint.DrawRect(LocalRect,3);\r\n        if(motion is null)return;\r\n        for(var i=0;i<motion.Contacts.Count;i++)\r\n        {\r\n            var c=motion.Contacts[i];var rect=ContactRect(i);\r\n            var color=c.Review==ContactReview.Suggested?Theme.Yellow:c.Review==ContactReview.Confirmed?Theme.Green:Theme.TextLight;\r\n            Paint.SetPen(color,1);Paint.SetBrush(color.WithAlpha(c.Review==ContactReview.Disabled?.1f:.25f));Paint.DrawRect(rect,2);\r\n            var state=c.Review==ContactReview.Suggested?\"?\":c.Review==ContactReview.Confirmed?\"\u2713\":\"\u00d7\";\r\n            var prefix=Side(c)+\" \"+state;\r\n            var text=prefix+\" \u00b7 \"+c.Object;\r\n            if(Paint.MeasureText(text).x>rect.Width-6)text=prefix;\r\n            if(Paint.MeasureText(text).x<=rect.Width-6)Paint.DrawText(rect.Shrink(3,0),text,TextFlag.LeftCenter);\r\n        }\r\n        for(var i=0;i<wristOffsets.Count;i++)\r\n        {\r\n            Paint.ClearPen();Paint.SetBrush(wristOffsets[i].Enabled?Theme.Blue:Theme.TextLight.WithAlpha(.35f));Paint.DrawRect(WristRect(i),1);\r\n        }\r\n        Paint.SetPen(Theme.Text.WithAlpha(.8f),1);var x=X(playhead);Paint.DrawLine(new Vector2(x,0),new Vector2(x,Height));\r\n    }\r\n    protected override void OnMouseMove(MouseEvent e)\r\n    {\r\n        var hits=Hits(e.LocalPosition);var wrists=WristHits(e.LocalPosition);Cursor=CursorShape.Finger;\r\n        ToolTip=hits.Length+wrists.Length==0?\"Click to seek. Yellow contacts need review; green are confirmed; blue marks manual wrist correction; gray is disabled.\":\r\n            string.Join(\"\\n\",hits.Select(i=>{var c=motion.Contacts[i];return $\"{c.Bone} \u2192 {c.Object} \u00b7 {c.Start:F3}\u2013{c.End:F3} s \u00b7 {c.Review}\";})\r\n                .Concat(wrists.Select(i=>{var c=wristOffsets[i];return $\"{(c.Hand==BoneRole.HandL?\"Left\":\"Right\")} wrist \u00b7 {c.Start:F3}\u2013{c.End:F3} s \u00b7 {(c.Enabled?\"Manual position correction\":\"Disabled correction\")}\";})))+\r\n            \"\\nClick to seek; double-click to edit; right-click to review or change timing.\";\r\n    }\r\n    protected override void OnMousePress(MouseEvent e)\r\n    {\r\n        var hits=Hits(e.LocalPosition);var wrists=WristHits(e.LocalPosition);var expected=motion;\r\n        if(expected is null)return;\r\n        if(e.LeftMouseButton)\r\n        {\r\n            Seek?.Invoke(Fraction(e.LocalPosition.x));\r\n            if(e.IsDoubleClick&&hits.Length+wrists.Length==1)\r\n            {if(wrists.Length==1)EditWrist?.Invoke(expected,wristOffsets[wrists[0]]);else Edit?.Invoke(expected,hits[0]);}\r\n            e.Accepted=true;\r\n        }\r\n        else if(e.RightMouseButton&&hits.Length+wrists.Length>0)\r\n        {\r\n            var menu=new Menu();var time=Math.Clamp(playhead,expected.Frames[0].Time,expected.Frames[^1].Time);\r\n            Seek?.Invoke(Fraction(X(time)));\r\n            foreach(var index in hits)\r\n            {\r\n                var c=expected.Contacts[index];var supported=new PropContactMotion(expected).UnsupportedReason(c) is null;\r\n                menu.AddHeading($\"{c.Bone} \u2192 {c.Object} \u00b7 {c.Review}\");\r\n                menu.AddOption(\"Edit contact\u2026\",\"edit\",()=>Edit?.Invoke(expected,index)).Enabled=supported;\r\n                menu.AddOption(\"Confirm\",\"check\",()=>Review?.Invoke(expected,index,ContactReview.Confirmed)).Enabled=supported;\r\n                menu.AddOption(\"Disable\",\"block\",()=>Review?.Invoke(expected,index,ContactReview.Disabled));\r\n                menu.AddOption($\"Start at playhead ({time:F3} s)\",\"first_page\",()=>ChangeRange?.Invoke(expected,index,time,c.End)).Enabled=supported&&time<c.End;\r\n                menu.AddOption($\"End at playhead ({time:F3} s)\",\"last_page\",()=>ChangeRange?.Invoke(expected,index,c.Start,time)).Enabled=supported&&time>c.Start;\r\n            }\r\n            foreach(var index in wrists)\r\n            {\r\n                var edit=wristOffsets[index];menu.AddHeading($\"{(edit.Hand==BoneRole.HandL?\"Left\":\"Right\")} wrist \u00b7 manual correction\");\r\n                menu.AddOption(\"Edit wrist correction\u2026\",\"edit_location\",()=>EditWrist?.Invoke(expected,edit));\r\n            }\r\n            menu.OpenAtCursor();e.Accepted=true;\r\n        }\r\n    }\r\n}\r\n\r\npublic sealed partial class RetargetWindow\r\n{\r\n    MocapContactTimeline _contactTimeline;\r\n    async Task ChangeContactRangeAsync(MotionDocument expected,int index,double start,double end)\r\n    {\r\n        try\r\n        {\r\n            if(expected!=_editedMotion||_processing is not null)return;\r\n            var contact=expected.Copy().Contacts[index];contact.Start=start;contact.End=end;\r\n            contact.Review=ContactReview.Suggested;contact.Reason=\"Interval edited on the timeline; review against the video.\";\r\n            await SaveContactAsync(expected,index,contact);\r\n        }\r\n        catch(Exception error){await EditorPipeline.SwitchToMainThread();if(this.IsValid())_captureStatus.Text=error.Message;}\r\n    }\r\n}\r\n"
        },
        {
            "Ident": "notpointless.chomnr_humanoid_mocap",
            "Path": "Code/HumanoidMocap/Inference/HandMotionBuilder.cs",
            "FileName": "HandMotionBuilder.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 395388,
            "IsPrivate": false,
            "Code": "using System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing System.Numerics;\r\nusing HumanoidMocap.Mapping;\r\nusing HumanoidMocap.Maths;\r\nusing HumanoidMocap.Motion;\r\nusing HumanoidMocap.Solve;\r\nusing HumanoidMocap.Target;\r\n\r\nnamespace HumanoidMocap.Inference;\r\nusing Vector3=System.Numerics.Vector3;\r\n\r\n/// <summary>Fits observed landmarks to a canonical hand skeleton. The source contains\r\n/// hands only; shoulders and arm IK belong to target retargeting.</summary>\r\npublic sealed class HandMotionBuilder\r\n{\r\n    readonly TargetRig rig;\r\n    readonly XForm[] rest;\r\n    readonly int[] sourceBones;\r\n    readonly Dictionary<int,int> documentIndices;\r\n    CameraObservation? previewCamera;\r\n    public MotionDocument Document { get; }\r\n    public bool SwapHands { get; set; }\r\n    public float WristPlaneWidth { get; set; }=.9f;\r\n    public float WristPlaneDepth { get; set; }=.4f;\r\n\r\n    public HandMotionBuilder(TargetRig template,string name,string video,string hash,double fps)\r\n    {\r\n        rig=template;rest=rig.Skeleton.RestWorld.Select(t=>new XForm(t.Pos/100,t.Rot)).ToArray();\r\n        sourceBones=rig.Skeleton.Bones.Where(b=>rig.RoleOf(b.Index) is BoneRole.HandL or BoneRole.HandR||\r\n            rig.RoleOf(b.Index) is { } role&&FingerSolver.IsFingerRole(role)).Select(b=>b.Index).ToArray();\r\n        documentIndices=sourceBones.Select((source,index)=>(source,index)).ToDictionary(x=>x.source,x=>x.index);\r\n        Document=new MotionDocument{Name=name,SourceVideo=video,SourceSha256=hash,SourceFps=fps,\r\n            Backend=\"MediaPipe hands / experimental managed C#\",ModelVersion=\"hand_landmarker/float16/1; hand-forest-v3-authored-metacarpals; camera-framing-v1\",\r\n            Space=MotionSpace.CameraRelative,MetricScaleCalibrated=false};\r\n        foreach(var source in sourceBones)\r\n        {\r\n            var role=rig.RoleOf(source);var hand=role is BoneRole.HandL or BoneRole.HandR;\r\n            var parent=hand?-1:rig.Skeleton[source].ParentIndex;\r\n            while(parent>=0&&!documentIndices.ContainsKey(parent))parent=rig.Skeleton[parent].ParentIndex;\r\n            if(!hand&&parent<0)throw new ArgumentException(\"Finger mapping must descend from a mapped hand.\");\r\n            var local=parent<0?new XForm(Vector3.Zero,rest[source].Rot):XForm.Compose(rest[parent].Inverse(),rest[source]);\r\n            Document.Bones.Add(new(){Name=rig.Skeleton[source].Name,Role=role,Parent=parent<0?-1:documentIndices[parent],Group=hand?\"arms\":\"fingers\",\r\n                RestPosition=MotionDocument.A(local.Pos),RestRotation=MotionDocument.A(local.Rot)});\r\n        }\r\n        Document.Diagnostics.AddRange(new[]{\"Experimental landmark reconstruction. Model-port parity has not been established.\",\r\n            \"Hand-relative 3D landmarks are reconstructed; rotations are fitted to a fixed canonical hand skeleton.\",\r\n            \"Metacarpal rest transforms are authored template anatomy, not observed motion. They are labeled Authored while their hand is observed.\",\r\n            \"Finger segment directions follow the landmarks. Axial twist is unmeasured and estimated by minimal swing relative to the parent segment; it is not captured finger torsion.\",\r\n            \"Camera-relative wrist translation uses an assumed image plane, not measured depth or camera motion.\",\r\n            \"Separated hand tracks can retain left/right identity through weak classifier disagreement. Saved handedness probabilities below 0.5 record that disagreement; no missing hand is generated.\",\r\n            \"The source contains no shoulders or elbows. Target arm IK is estimated after reconstruction.\",\r\n            \"Unobserved hands hold their last pose and remain labeled unobserved. No per-joint confidence is supplied.\"});\r\n    }\r\n    public void Add(double time,int width,int height,IReadOnlyList<HandObservation> observations)\r\n    {\r\n        if(width<=0||height<=0||!float.IsFinite(WristPlaneWidth)||!float.IsFinite(WristPlaneDepth)||WristPlaneWidth<=0||WristPlaneDepth<=0)\r\n            throw new ArgumentException(\"Invalid image dimensions or assumed wrist plane.\");\r\n        var focal=width*WristPlaneDepth/WristPlaneWidth;\r\n        if(previewCamera is null)\r\n        {\r\n            previewCamera=new(){Id=\"video\",Source=\"Authored preview camera matching the assumed wrist plane; not recovered video calibration\",\r\n                ImageWidth=width,ImageHeight=height,Calibrated=false,Synchronized=true,\r\n                Intrinsics=new[]{focal,0,width/2f,0,focal,height/2f,0,0,1}};\r\n            Document.Cameras.Add(previewCamera);\r\n        }\r\n        else if(previewCamera.Intrinsics is {} intrinsics&&(previewCamera.ImageWidth!=width||previewCamera.ImageHeight!=height||intrinsics[0]!=focal))\r\n        {\r\n            // One static camera cannot describe changing image/plane geometry.\r\n            previewCamera.ImageWidth=previewCamera.ImageHeight=null;previewCamera.Intrinsics=null;\r\n            previewCamera.Source=\"Assumed wrist-plane geometry changes within this clip; preview camera is unspecified\";\r\n        }\r\n        var previous=Document.Frames.LastOrDefault();\r\n        var frame=new MotionFrame{Time=time,\r\n            Positions=(previous?.Positions??Document.Bones.Select(b=>b.RestPosition).ToArray()).Select(p=>p.ToArray()).ToArray(),\r\n            Rotations=(previous?.Rotations??Document.Bones.Select(b=>b.RestRotation).ToArray()).Select(q=>q.ToArray()).ToArray(),\r\n            Evidence=Enumerable.Repeat(JointEvidence.Unobserved,sourceBones.Length).ToArray(),Confidence=null};\r\n        var desired=new Dictionary<int,Quaternion>();\r\n        foreach(var observed in observations.GroupBy(h=>h.Side).Select(g=>g.OrderByDescending(h=>h.Presence).First()))\r\n        {\r\n            if(observed.Side is not (\"L\" or \"R\")||observed.ImageLandmarks.Length!=21||observed.RelativeWorldLandmarks.Length!=21)continue;\r\n            var side=SwapHands?(observed.Side==\"L\"?\"R\":\"L\"):observed.Side;\r\n            BoneRole Role(string name)=>Enum.Parse<BoneRole>(name+side);\r\n            if(rig.BoneForRole(Role(\"Hand\")) is not int hand||rig.BoneForRole(Role(\"IndexProx\")) is not int index||\r\n                rig.BoneForRole(Role(\"PinkyProx\")) is not int pinky||rig.BoneForRole(Role(\"MiddleProx\")) is not int middle)continue;\r\n            // MediaPipe x-right/y-down/z-away -> document x-right/y-up/z-toward viewer.\r\n            var points=observed.RelativeWorldLandmarks.Select(v=>new Vector3(v.X,-v.Y,-v.Z)).ToArray();\r\n            var across=points[5]-points[17];var restAcross=rest[index].Pos-rest[pinky].Pos;\r\n            if(!TryBasis(rest[middle].Pos-rest[hand].Pos,restAcross,out var reference)||\r\n                !TryBasis(points[9]-points[0],across,out var orientation))continue;\r\n            var wrist=observed.ImageLandmarks[0];\r\n            if(!Finite(wrist))continue;\r\n            var handIndex=documentIndices[hand];\r\n            frame.Positions[handIndex]=MotionDocument.A(new Vector3((wrist.X/width-.5f)*WristPlaneWidth,\r\n                (.5f-wrist.Y/height)*WristPlaneWidth*height/width,-WristPlaneDepth));\r\n            desired[handIndex]=Quaternion.Normalize(orientation*Quaternion.Inverse(reference)*rest[hand].Rot);\r\n            frame.Evidence[handIndex]=JointEvidence.Reconstructed;\r\n            foreach(var finger in new[]{\"Thumb\",\"Index\",\"Middle\",\"Ring\",\"Pinky\"})\r\n                if(rig.BoneForRole(Role(finger+\"Meta\")) is int meta&&documentIndices.TryGetValue(meta,out var metaIndex))\r\n                {\r\n                    frame.Positions[metaIndex]=Document.Bones[metaIndex].RestPosition.ToArray();\r\n                    frame.Rotations[metaIndex]=Document.Bones[metaIndex].RestRotation.ToArray();\r\n                    frame.Evidence[metaIndex]=JointEvidence.Authored;\r\n                }\r\n            foreach(var (finger,start) in new[]{(\"Thumb\",1),(\"Index\",5),(\"Middle\",9),(\"Ring\",13),(\"Pinky\",17)})\r\n            {\r\n                var parentDelta=Quaternion.Normalize(desired[handIndex]*Quaternion.Inverse(rest[hand].Rot));\r\n                var segments=new[]{\"Prox\",\"Mid\",\"Dist\"};\r\n                for(var k=0;k<3;k++)\r\n                {\r\n                    if(rig.BoneForRole(Role(finger+segments[k])) is not int bone)continue;\r\n                    Vector3 direction;\r\n                    if(k<2&&rig.BoneForRole(Role(finger+segments[k+1])) is int next)direction=rest[next].Pos-rest[bone].Pos;\r\n                    else if(k>0&&rig.BoneForRole(Role(finger+segments[k-1])) is int parent)direction=rest[bone].Pos-rest[parent].Pos;\r\n                    else continue;\r\n                    var capturedDirection=points[start+k+1]-points[start+k];\r\n                    if(!Finite(direction)||!Finite(capturedDirection)||direction.LengthSquared()<1e-10f||capturedDirection.LengthSquared()<1e-10f)break;\r\n                    // A segment direction does not measure roll. Carry its parent's\r\n                    // frame and apply only the swing needed to match the observation.\r\n                    // Independent palm-axis bases become singular when a finger\r\n                    // points across the palm and can add a spurious 180-degree twist.\r\n                    var predictedDirection=Vector3.Transform(direction,parentDelta);\r\n                    var delta=Quaternion.Normalize(MathQ.FromTo(predictedDirection,capturedDirection)*parentDelta);\r\n                    var joint=documentIndices[bone];\r\n                    desired[joint]=Quaternion.Normalize(delta*rest[bone].Rot);\r\n                    frame.Evidence[joint]=JointEvidence.Reconstructed;\r\n                    parentDelta=delta;\r\n                }\r\n            }\r\n        }\r\n        var world=new Quaternion[sourceBones.Length];\r\n        for(var i=0;i<world.Length;i++)\r\n        {\r\n            var parent=Document.Bones[i].Parent;var parentRotation=parent<0?Quaternion.Identity:world[parent];\r\n            if(desired.TryGetValue(i,out var rotation))frame.Rotations[i]=MotionDocument.A(Quaternion.Normalize(Quaternion.Inverse(parentRotation)*rotation));\r\n            world[i]=Quaternion.Normalize(parentRotation*MotionDocument.Q(frame.Rotations[i]));\r\n        }\r\n        Document.Frames.Add(frame);\r\n    }\r\n    static bool Finite(Vector3 v)=>float.IsFinite(v.X)&&float.IsFinite(v.Y)&&float.IsFinite(v.Z);\r\n    static bool TryBasis(Vector3 direction,Vector3 across,out Quaternion rotation)\r\n    {\r\n        rotation=Quaternion.Identity;\r\n        if(!Finite(direction)||!Finite(across)||direction.LengthSquared()<1e-10f)return false;\r\n        var x=Vector3.Normalize(direction);var y=across-x*Vector3.Dot(across,x);\r\n        if(y.LengthSquared()<1e-10f)return false;\r\n        y=Vector3.Normalize(y);var z=Vector3.Normalize(Vector3.Cross(x,y));\r\n        rotation=Quaternion.Normalize(Quaternion.CreateFromRotationMatrix(new Matrix4x4(x.X,x.Y,x.Z,0,y.X,y.Y,y.Z,0,z.X,z.Y,z.Z,0,0,0,0,1)));\r\n        return true;\r\n    }\r\n}\r\n"
        },
        {
            "Ident": "notpointless.chomnr_humanoid_mocap",
            "Path": "Code/HumanoidMocap/Motion/CaptureGround.cs",
            "FileName": "CaptureGround.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 395388,
            "IsPrivate": false,
            "Code": "using System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing HumanoidMocap.Cleanup;\r\nusing HumanoidMocap.Mapping;\r\nusing HumanoidMocap.Maths;\r\nusing HumanoidMocap.Target;\r\n\r\nnamespace HumanoidMocap.Motion;\r\nusing Vector3 = System.Numerics.Vector3;\r\n\r\n/// <summary>Keeps a body capture on the floor all the way through. The foot lock corrects floor drift only\r\n/// from detected foot contacts, and only for world-relative captures; a camera-relative capture from a camera\r\n/// that moved was grounded once, and dance with few flat-footed moments had nothing to anchor on. On a\r\n/// step-dance clip the feet rose from the floor to 45 cm over 17 seconds.\r\n///\r\n/// Within any stretch of about a second and a half somebody standing, walking or dancing puts a foot down,\r\n/// so the lower envelope of the lowest foot's height is the floor: a rolling minimum followed by a rolling\r\n/// maximum over that window (a morphological opening), which follows a slow rise exactly and drops anything\r\n/// narrower than the window, such as jumps. Only the floor's change over the clip is removed, relative to its\r\n/// lowest level, never by more than would push a foot below the floor: a capture that sits at one height the\r\n/// whole time (a stage, a placed world capture) keeps it.</summary>\r\npublic static class CaptureGround\r\n{\r\n    /// <summary>Seconds within which a foot is expected to touch the floor.</summary>\r\n    public const double WindowSeconds = 1.5;\r\n    /// <summary>Corrections smaller than this, in centimetres, leave the clip untouched.</summary>\r\n    public const float MinimumCorrectionCm = 1;\r\n\r\n    /// <returns>The largest correction applied, in centimetres.</returns>\r\n    public static float Apply( List<XForm[]> frames, TargetRig target, TargetUpAxis axis, float fps )\r\n    {\r\n        if ( frames.Count < 3 || !(fps > 0) ) return 0;\r\n        var rig = target.Skeleton;\r\n        var up = axis == TargetUpAxis.YUpCm ? Vector3.UnitY : Vector3.UnitZ;\r\n        var toCm = axis == TargetUpAxis.ZUpEngine ? 2.54f : 1f;\r\n        var joints = new[] { BoneRole.FootL, BoneRole.FootR, BoneRole.ToeL, BoneRole.ToeR }\r\n            .Select( target.BoneForRole ).Where( b => b is not null ).Select( b => b.Value ).ToArray();\r\n        if ( joints.Length == 0 ) return 0;\r\n        // Height of the lowest foot joint above its own rest height, per frame.\r\n        var lowest = new float[frames.Count]; var world = new XForm[rig.Count];\r\n        for ( var f = 0; f < frames.Count; f++ )\r\n        {\r\n            FkUtil.ToWorld( frames[f], rig, world );\r\n            var h = float.PositiveInfinity;\r\n            foreach ( var j in joints ) h = Math.Min( h, Vector3.Dot( world[j].Pos, up ) - Vector3.Dot( rig.RestWorld[j].Pos, up ) );\r\n            lowest[f] = h;\r\n        }\r\n        var radius = Math.Max( 1, (int)Math.Round( WindowSeconds * fps / 2 ) );\r\n        var eroded = new float[frames.Count]; var floor = new double[frames.Count];\r\n        for ( var f = 0; f < frames.Count; f++ )\r\n        {\r\n            var m = float.PositiveInfinity;\r\n            for ( var k = Math.Max( 0, f - radius ); k <= Math.Min( frames.Count - 1, f + radius ); k++ ) m = Math.Min( m, lowest[k] );\r\n            eroded[f] = m;\r\n        }\r\n        for ( var f = 0; f < frames.Count; f++ )\r\n        {\r\n            var m = float.NegativeInfinity;\r\n            for ( var k = Math.Max( 0, f - radius ); k <= Math.Min( frames.Count - 1, f + radius ); k++ ) m = Math.Max( m, eroded[k] );\r\n            floor[f] = m;\r\n        }\r\n        // The rolling minimum steps as the window slides; a slow zero-phase filter leaves only the drift.\r\n        if ( frames.Count >= 8 && fps > 2 )\r\n        {\r\n            var (b, a) = MocapSmooth.ButterLowpass( 2, Math.Min( .5, fps * .2 ), fps );\r\n            floor = MocapSmooth.FiltFilt( b, a, floor );\r\n        }\r\n        var reference = floor.Min();\r\n        var largest = 0f;\r\n        var shifts = new float[frames.Count];\r\n        for ( var f = 0; f < frames.Count; f++ )\r\n        {\r\n            // Only the change in floor height is removed, and never below the floor.\r\n            shifts[f] = (float)Math.Min( floor[f] - reference, Math.Max( 0, lowest[f] ) );\r\n            largest = Math.Max( largest, Math.Abs( shifts[f] ) * toCm );\r\n        }\r\n        if ( largest < MinimumCorrectionCm ) return 0;\r\n        for ( var f = 0; f < frames.Count; f++ )\r\n            for ( var bone = 0; bone < rig.Count; bone++ )\r\n                if ( rig[bone].ParentIndex < 0 ) frames[f][bone].Pos -= up * shifts[f];\r\n        return largest;\r\n    }\r\n}\r\n"
        },
        {
            "Ident": "notpointless.chomnr_humanoid_mocap",
            "Path": "Code/HumanoidMocap/Motion/CapturePlacement.cs",
            "FileName": "CapturePlacement.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 395388,
            "IsPrivate": false,
            "Code": "using System;\r\nusing System.Numerics;\r\nusing HumanoidMocap.Maths;\r\nusing HumanoidMocap.Target;\r\n\r\nnamespace HumanoidMocap.Motion;\r\nusing Vector3 = System.Numerics.Vector3;\r\n\r\n/// <summary>The same explicit camera placement for captured hands and authoritative props.</summary>\r\npublic readonly record struct CapturePlacement(float Units,Quaternion Rotation,Vector3 Position)\r\n{\r\n    public static CapturePlacement ForTarget(TargetUpAxis axis,TargetCorrectionSettings settings)\r\n    {\r\n        var units=axis==TargetUpAxis.ZUpEngine?39.3700787f:100f;\r\n        var axisRotation=axis==TargetUpAxis.YUpCm?Quaternion.Identity:Quaternion.CreateFromAxisAngle(Vector3.UnitX,MathF.PI/2);\r\n        var camera=Quaternion.CreateFromYawPitchRoll(settings.CaptureCameraYawDegrees*MathF.PI/180,\r\n            settings.CaptureCameraPitchDegrees*MathF.PI/180,0);\r\n        return new(units,Quaternion.Normalize(axisRotation*camera),Vector3.Transform(settings.CaptureCameraPosition*units,axisRotation));\r\n    }\r\n    public XForm Transform(XForm capture)=>new(Vector3.Transform(capture.Pos*Units,Rotation)+Position,\r\n        Quaternion.Normalize(Rotation*capture.Rot));\r\n}\r\n"
        },
        {
            "Ident": "notpointless.chomnr_humanoid_mocap",
            "Path": "Code/HumanoidMocap/Target/SboxBoneClassifier.cs",
            "FileName": "SboxBoneClassifier.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 395388,
            "IsPrivate": false,
            "Code": "#nullable enable annotations\r\n\r\nusing System;\r\nusing System.Collections.Generic;\r\nusing System.Text.RegularExpressions;\r\nusing HumanoidMocap.Mapping;\r\n\r\nnamespace HumanoidMocap.Target;\r\n\r\n/// <summary>\r\n/// Name-based bone classification and role assignment rules for the s&amp;box humanoid rig\r\n/// (design doc \u00a73). Used by <see cref=\"TargetRigGenerator\"/> to produce the committed\r\n/// target-rig definition; consumers should read classes/roles from <see cref=\"TargetRig\"/>\r\n/// rather than re-deriving them.\r\n/// </summary>\r\npublic static partial class SboxBoneClassifier\r\n{\r\n    // Plain cached Regex instead of [GeneratedRegex]: the s&box in-engine compiler\r\n    // does not run the regex source generator, so partial GeneratedRegex methods\r\n    // fail to compile there (\"must have an implementation part\").\r\n    private static readonly Regex TwistSuffixRegex = new(@\"_twist\\d+$\");\r\n    private static Regex TwistSuffix() => TwistSuffixRegex;\r\n\r\n    // arm_elbow/leg_knee on the human rig; leg_glute on the legacy citizen rig.\r\n    private static readonly Regex ConstraintHelperRegex = new(@\"^(arm_elbow|leg_knee|leg_glute)_helper(_|$)\");\r\n    private static Regex ConstraintHelper() => ConstraintHelperRegex;\r\n\r\n    private static readonly Regex IkSuffixRegex = new(@\"(_IK_target|_IK_attach|_ikrule)$\");\r\n    private static Regex IkSuffix() => IkSuffixRegex;\r\n\r\n    private static readonly Regex AimMatrixPrefixRegex = new(@\"^aim_matrix_\");\r\n    private static Regex AimMatrixPrefix() => AimMatrixPrefixRegex;\r\n\r\n    // Face bones on the classic citizen rig (eye_L/R, ear_L/R, face_lid_*): no canonical\r\n    // role exists for them, the solver never retargets them, and the engine's procedural\r\n    // systems (eye look-at, blinking) pose them in game.\r\n    private static readonly Regex FacePrefixRegex = new(@\"^(eye|ear|face)_\");\r\n    private static Regex FacePrefix() => FacePrefixRegex;\r\n\r\n    // Case-insensitive twin of FacePrefixRegex for IsFaceBone: custom rigs classified by\r\n    // BoneClassRules match face names case-insensitively, and the channel decision must\r\n    // agree with that classification.\r\n    private static readonly Regex FacePrefixAnyCaseRegex = new(@\"^(eye|ear|face)_\", RegexOptions.IgnoreCase);\r\n\r\n    /// <summary>\r\n    /// True for face bones (<c>eye_*</c>, <c>ear_*</c>, <c>face_*</c>). Unlike the\r\n    /// twist/helper <see cref=\"BoneClass.ConstraintDriven\"/> bones \u2014 which the model's\r\n    /// AnimConstraintList re-drives on every evaluated frame \u2014 NOTHING drives face bones in\r\n    /// a compiled sequence: the constraint list never references them and the engine's eye\r\n    /// look-at / blinking only runs in game. A face joint left channel-less in the DMX is\r\n    /// baked statically by resourcecompiler, so the eyes detach from the moving head in\r\n    /// ModelDoc (\"eyes out of their sockets\"). Retargeted clips must therefore carry\r\n    /// rest-local channels for them \u2014 exactly what the shipped fbx2dmx clips do\r\n    /// (reference: <c>dev/m0/ref_idlepose.dmx</c> carries <c>eye_L_p/_o</c>,\r\n    /// <c>face_lid_*_p/_o</c> channels).\r\n    /// </summary>\r\n    public static bool IsFaceBone(string name)\r\n    {\r\n        ArgumentNullException.ThrowIfNull(name);\r\n        return FacePrefixAnyCaseRegex.IsMatch(name);\r\n    }\r\n\r\n    /// <summary>Classifies an s&amp;box rig bone by name.</summary>\r\n    public static BoneClass Classify(string name)\r\n    {\r\n        ArgumentNullException.ThrowIfNull(name);\r\n\r\n        if (TwistSuffix().IsMatch(name) || ConstraintHelper().IsMatch(name) || name == \"neck_clothing\"\r\n            || FacePrefix().IsMatch(name))\r\n            return BoneClass.ConstraintDriven;\r\n\r\n        if (name == \"root_IK\" || name == \"hold_L\" || name == \"hold_R\"\r\n            || IkSuffix().IsMatch(name) || AimMatrixPrefix().IsMatch(name))\r\n            return BoneClass.IkBaked;\r\n\r\n        return BoneClass.Animated;\r\n    }\r\n\r\n    /// <summary>\r\n    /// Returns the canonical role of an s&amp;box rig bone, or null when the bone carries no\r\n    /// role (every non-<see cref=\"BoneClass.Animated\"/> bone, by construction).\r\n    /// </summary>\r\n    public static BoneRole? RoleFor(string name)\r\n    {\r\n        ArgumentNullException.ThrowIfNull(name);\r\n\r\n        if (Classify(name) != BoneClass.Animated)\r\n            return null;\r\n\r\n        return RoleByName.TryGetValue(name, out var role) ? role : null;\r\n    }\r\n\r\n    /// <summary>Role table for the s&amp;box bone names (built once, ordinal-keyed).</summary>\r\n    private static readonly IReadOnlyDictionary<string, BoneRole> RoleByName = BuildRoleTable();\r\n\r\n    private static Dictionary<string, BoneRole> BuildRoleTable()\r\n    {\r\n        var table = new Dictionary<string, BoneRole>(StringComparer.Ordinal)\r\n        {\r\n            [\"pelvis\"] = BoneRole.Hips,\r\n            [\"spine_0\"] = BoneRole.Spine0,\r\n            [\"spine_1\"] = BoneRole.Spine1,\r\n            [\"spine_2\"] = BoneRole.Spine2,\r\n            [\"neck_0\"] = BoneRole.Neck,\r\n            [\"head\"] = BoneRole.Head,\r\n        };\r\n\r\n        foreach (var side in new[] { \"L\", \"R\" })\r\n        {\r\n            table[$\"clavicle_{side}\"] = ParseRole($\"Clavicle{side}\");\r\n            table[$\"arm_upper_{side}\"] = ParseRole($\"UpperArm{side}\");\r\n            table[$\"arm_lower_{side}\"] = ParseRole($\"LowerArm{side}\");\r\n            table[$\"hand_{side}\"] = ParseRole($\"Hand{side}\");\r\n            table[$\"leg_upper_{side}\"] = ParseRole($\"UpperLeg{side}\");\r\n            table[$\"leg_lower_{side}\"] = ParseRole($\"LowerLeg{side}\");\r\n            table[$\"ankle_{side}\"] = ParseRole($\"Foot{side}\");\r\n            table[$\"ball_{side}\"] = ParseRole($\"Toe{side}\");\r\n\r\n            foreach (var (finger, rolePrefix) in new[]\r\n            {\r\n                (\"thumb\", \"Thumb\"), (\"index\", \"Index\"), (\"middle\", \"Middle\"),\r\n                (\"ring\", \"Ring\"), (\"pinky\", \"Pinky\"),\r\n            })\r\n            {\r\n                // Segment naming on the rig: meta = metacarpal, 0/1/2 = proximal/middle/distal.\r\n                // The s&box thumb has no metacarpal bone, but the rule is kept uniform so a\r\n                // hypothetical finger_thumb_meta_* would still map (the enum defines ThumbMeta*).\r\n                table[$\"finger_{finger}_meta_{side}\"] = ParseRole($\"{rolePrefix}Meta{side}\");\r\n                table[$\"finger_{finger}_0_{side}\"] = ParseRole($\"{rolePrefix}Prox{side}\");\r\n                table[$\"finger_{finger}_1_{side}\"] = ParseRole($\"{rolePrefix}Mid{side}\");\r\n                table[$\"finger_{finger}_2_{side}\"] = ParseRole($\"{rolePrefix}Dist{side}\");\r\n            }\r\n        }\r\n\r\n        return table;\r\n    }\r\n\r\n    private static BoneRole ParseRole(string name) => Enum.Parse<BoneRole>(name);\r\n}\r\n"
        },
        {
            "Ident": "notpointless.chomnr_humanoid_mocap",
            "Path": "Editor/HumanoidMocap/Inference/GvhmrStatistics.cs",
            "FileName": "GvhmrStatistics.cs",
            "PackageType": "library",
            "CodeKind": "Editor",
            "AssetVersionId": 395388,
            "IsPrivate": false,
            "Code": "namespace HumanoidMocap.Inference;\r\n\r\n// GVHMR ee960bb6: MM_V1_AMASS_LOCAL_BEDLAM_CAM, stats_compose.py.\r\n// Values copied verbatim; terms in Gvhmr.LICENSE.\r\ninternal static class GvhmrStatistics\r\n{\r\n    internal static readonly float[] Mean={9.6969e-01f,-5.9719e-02f,-3.7700e-02f,5.8256e-02f,9.0800e-01f,1.0972e-01f,9.7636e-01f,4.3401e-02f,4.3110e-03f,-4.3032e-02f,9.0261e-01f,1.4478e-01f,9.9288e-01f,3.5673e-03f,1.6264e-02f,-2.2260e-03f,9.3470e-01f,-2.3495e-01f,9.7147e-01f,5.2553e-02f,-9.3666e-02f,-5.4550e-02f,8.3321e-01f,-2.4246e-01f,9.7971e-01f,-3.8429e-02f,5.3575e-03f,1.5537e-02f,8.1449e-01f,-3.0926e-01f,9.9532e-01f,-9.4398e-03f,-3.8328e-02f,8.5141e-03f,9.8880e-01f,1.9976e-04f,9.5602e-01f,-3.9528e-02f,2.0017e-01f,1.0363e-02f,9.5965e-01f,1.3770e-01f,9.6223e-01f,-4.6278e-02f,-1.5177e-01f,6.6705e-02f,9.5545e-01f,1.2519e-01f,9.9767e-01f,-1.2616e-02f,-2.5442e-04f,1.1661e-02f,9.9376e-01f,-3.6222e-02f,9.9511e-01f,-1.0583e-02f,1.2130e-02f,7.6461e-03f,9.9137e-01f,2.0029e-02f,9.9295e-01f,7.2917e-03f,4.9454e-03f,-8.0286e-03f,9.9137e-01f,2.3707e-03f,9.7698e-01f,1.9943e-02f,1.3808e-03f,-2.2006e-02f,9.7375e-01f,-6.7936e-02f,9.2804e-01f,2.5005e-01f,-5.7167e-02f,-2.4047e-01f,9.4246e-01f,2.5863e-02f,9.2957e-01f,-2.1329e-01f,1.1112e-01f,2.0741e-01f,9.4876e-01f,2.9901e-02f,9.7683e-01f,-4.1210e-02f,2.3248e-03f,4.0967e-02f,9.7365e-01f,5.7309e-03f,6.4513e-01f,6.1999e-01f,-2.5469e-01f,-6.2342e-01f,6.8177e-01f,3.5524e-02f,6.6192e-01f,-5.9341e-01f,2.7136e-01f,5.9269e-01f,6.8966e-01f,3.1309e-02f,6.8946e-01f,-1.1676e-01f,-4.9859e-01f,4.0969e-02f,9.3656e-01f,-1.4875e-01f,6.2787e-01f,1.3793e-01f,5.4289e-01f,-9.1946e-02f,9.2868e-01f,-1.1927e-01f,9.3012e-01f,-8.3810e-02f,-1.1951e-01f,9.7211e-02f,8.9118e-01f,5.9887e-02f,9.3033e-01f,7.1047e-02f,7.5264e-02f,-8.0679e-02f,8.8562e-01f,4.8960e-02f,0.2310f,0.1750f,0.2931f,-0.1859f,-1.1163f,-1.1028f,-0.2573f,0.3555f,0.3732f,0.2852f,-4.9862e-03f,-8.7136e-04f,-1.4187e-03f,1.4825e-02f,-9.4419e-01f,-5.1653e-02f,3.6018e-04f,-2.2327e-04f,2.2316e-03f,-4.4879e-02f,-9.7435e-01f,1.0021e-01f,-0.0002f,-0.0006f,0.0069f};\r\n    internal static readonly float[] StandardDeviation={0.0612f,0.1390f,0.1779f,0.1415f,0.1826f,0.3268f,0.0440f,0.1382f,0.1542f,0.1348f,0.1930f,0.3272f,0.0132f,0.0801f,0.0855f,0.0729f,0.1255f,0.2238f,0.0554f,0.1088f,0.1727f,0.0939f,0.3294f,0.3559f,0.0532f,0.1082f,0.1554f,0.0768f,0.3446f,0.3407f,0.0120f,0.0650f,0.0584f,0.0632f,0.0198f,0.1335f,0.0631f,0.1250f,0.1574f,0.1047f,0.0730f,0.2091f,0.0759f,0.1241f,0.1667f,0.1112f,0.0831f,0.2185f,0.0060f,0.0441f,0.0502f,0.0441f,0.0102f,0.0946f,0.0237f,0.0722f,0.0610f,0.0738f,0.0479f,0.0949f,0.0369f,0.0943f,0.0610f,0.0966f,0.0498f,0.0729f,0.0425f,0.1001f,0.1824f,0.0972f,0.0408f,0.1887f,0.0594f,0.1842f,0.1884f,0.2020f,0.0457f,0.1018f,0.0640f,0.1990f,0.1854f,0.2133f,0.0467f,0.0910f,0.0392f,0.1049f,0.1776f,0.1037f,0.0413f,0.1945f,0.1733f,0.2612f,0.1905f,0.2963f,0.1512f,0.1861f,0.1710f,0.2663f,0.1896f,0.3135f,0.1568f,0.2219f,0.3976f,0.1594f,0.2810f,0.1855f,0.0845f,0.2398f,0.4398f,0.1629f,0.2685f,0.1990f,0.0998f,0.2556f,0.1137f,0.2837f,0.1419f,0.2761f,0.1678f,0.2973f,0.1172f,0.3010f,0.1394f,0.2910f,0.1724f,0.3039f,0.8831f,0.7965f,1.0899f,1.1788f,1.2128f,1.1081f,0.9780f,1.1434f,0.8498f,1.1462f,0.7048f,0.1713f,0.6884f,0.1548f,0.1546f,0.2403f,0.6070f,0.5355f,0.5873f,0.6285f,0.2336f,0.7675f,0.0064f,0.0070f,0.0138f};\r\n}\r\n"
        },
        {
            "Ident": "notpointless.chomnr_humanoid_mocap",
            "Path": "Code/HumanoidMocap/Formats/Fbx/FbxBinaryWriter.cs",
            "FileName": "FbxBinaryWriter.cs",
            "PackageType": "library",
            "CodeKind": "Game",
            "AssetVersionId": 395388,
            "IsPrivate": false,
            "Code": "#nullable enable annotations\r\n\r\nusing System;\r\nusing System.Buffers.Binary;\r\nusing System.IO;\r\nusing System.Text;\r\n\r\nnamespace HumanoidMocap.Formats.Fbx;\r\n\r\n/// <summary>\r\n/// Serializes an <see cref=\"FbxNode\"/> tree back to binary FBX (version 7400 layout \u2014\r\n/// u32 header fields, universally readable). The inverse of\r\n/// <see cref=\"FbxTokenizer.Parse\"/>: a tree parsed from a 7.x binary file and written\r\n/// here re-parses to an identical tree (arrays are written uncompressed; zlib-encoded\r\n/// inputs therefore round-trip by VALUE, not byte-for-byte).\r\n/// </summary>\r\n/// <remarks>\r\n/// Used by <see cref=\"FbxBindPoseFixer\"/> to persist repaired node transforms. The footer\r\n/// is written the way Blender's exporter does: a fixed 16-byte watermark (importers treat\r\n/// it as opaque), zero padding to a 16-byte boundary, the version echo, 120 zero bytes and\r\n/// the closing magic. The FBX SDK computes a content hash here, but every consumer we\r\n/// target (s&amp;box, Blender, assimp) ignores it.\r\n/// </remarks>\r\npublic static class FbxBinaryWriter\r\n{\r\n    private const uint Version = 7400;\r\n\r\n    private static readonly byte[] HeaderMagic =\r\n        \"Kaydara FBX Binary  \\0\\x1a\\0\"u8.ToArray();\r\n\r\n    // Blender's fbx_binary.py FOOT_ID + closing magic bytes.\r\n    private static readonly byte[] FooterWatermark =\r\n    {\r\n        0xfa, 0xbc, 0xab, 0x09, 0xd0, 0xc8, 0xd4, 0x66, 0xb1, 0x76, 0xfb, 0x83, 0x1c, 0xf7, 0x26, 0x7e,\r\n    };\r\n\r\n    private static readonly byte[] FooterMagic =\r\n    {\r\n        0xf8, 0x5a, 0x8c, 0x6a, 0xde, 0xf5, 0xd9, 0x7e, 0xec, 0xe9, 0x0c, 0x6e, 0x0c, 0xc0, 0x00, 0x00,\r\n    };\r\n\r\n    /// <summary>\r\n    /// Serializes <paramref name=\"root\"/> (a virtual root whose children are the top-level\r\n    /// document nodes, as produced by <see cref=\"FbxTokenizer.Parse\"/>).\r\n    /// </summary>\r\n    public static byte[] Write(FbxNode root)\r\n    {\r\n        ArgumentNullException.ThrowIfNull(root);\r\n\r\n        using var ms = new MemoryStream();\r\n        ms.Write(HeaderMagic);\r\n        WriteU32(ms, Version);\r\n\r\n        foreach (var child in root.Children)\r\n            WriteNode(ms, child);\r\n        WriteNullRecord(ms);\r\n\r\n        WriteFooter(ms);\r\n        return ms.ToArray();\r\n    }\r\n\r\n    // ------------------------------------------------------------------ nodes\r\n\r\n    private static void WriteNode(MemoryStream ms, FbxNode node)\r\n    {\r\n        long headerAt = ms.Position;\r\n        // Placeholder header: endOffset, numProps, propListLen (patched after the body).\r\n        WriteU32(ms, 0);\r\n        WriteU32(ms, (uint)node.Properties.Count);\r\n        WriteU32(ms, 0);\r\n        var nameBytes = Encoding.ASCII.GetBytes(node.Name);\r\n        if (nameBytes.Length > byte.MaxValue)\r\n            throw new FormatException($\"FBX write: node name too long ({node.Name.Length} chars).\");\r\n        ms.WriteByte((byte)nameBytes.Length);\r\n        ms.Write(nameBytes);\r\n\r\n        long propsAt = ms.Position;\r\n        foreach (var p in node.Properties)\r\n            WriteProperty(ms, p, node.Name);\r\n        long propsLen = ms.Position - propsAt;\r\n\r\n        if (node.Children.Count > 0)\r\n        {\r\n            foreach (var child in node.Children)\r\n                WriteNode(ms, child);\r\n            WriteNullRecord(ms);\r\n        }\r\n\r\n        long endAt = ms.Position;\r\n        ms.Position = headerAt;\r\n        WriteU32(ms, checked((uint)endAt));\r\n        WriteU32(ms, (uint)node.Properties.Count);\r\n        WriteU32(ms, checked((uint)propsLen));\r\n        ms.Position = endAt;\r\n    }\r\n\r\n    private static void WriteNullRecord(MemoryStream ms)\r\n    {\r\n        Span<byte> zeros = stackalloc byte[13];\r\n        zeros.Clear();\r\n        ms.Write(zeros);\r\n    }\r\n\r\n    // ------------------------------------------------------------------ properties\r\n\r\n    private static void WriteProperty(MemoryStream ms, object value, string owner)\r\n    {\r\n        switch (value)\r\n        {\r\n            case short y:\r\n                ms.WriteByte((byte)'Y');\r\n                WriteI16(ms, y);\r\n                break;\r\n            case bool c:\r\n                ms.WriteByte((byte)'C');\r\n                ms.WriteByte(c ? (byte)1 : (byte)0);\r\n                break;\r\n            case int i:\r\n                ms.WriteByte((byte)'I');\r\n                WriteI32(ms, i);\r\n                break;\r\n            case float f:\r\n                ms.WriteByte((byte)'F');\r\n                WriteF32(ms, f);\r\n                break;\r\n            case double d:\r\n                ms.WriteByte((byte)'D');\r\n                WriteF64(ms, d);\r\n                break;\r\n            case long l:\r\n                ms.WriteByte((byte)'L');\r\n                WriteI64(ms, l);\r\n                break;\r\n\r\n            case float[] fa:\r\n                WriteArrayHeader(ms, 'f', fa.Length, 4);\r\n                foreach (var x in fa)\r\n                    WriteF32(ms, x);\r\n                break;\r\n            case double[] da:\r\n                WriteArrayHeader(ms, 'd', da.Length, 8);\r\n                foreach (var x in da)\r\n                    WriteF64(ms, x);\r\n                break;\r\n            case long[] la:\r\n                WriteArrayHeader(ms, 'l', la.Length, 8);\r\n                foreach (var x in la)\r\n                    WriteI64(ms, x);\r\n                break;\r\n            case int[] ia:\r\n                WriteArrayHeader(ms, 'i', ia.Length, 4);\r\n                foreach (var x in ia)\r\n                    WriteI32(ms, x);\r\n                break;\r\n            case bool[] ba:\r\n                WriteArrayHeader(ms, 'b', ba.Length, 1);\r\n                foreach (var x in ba)\r\n                    ms.WriteByte(x ? (byte)1 : (byte)0);\r\n                break;\r\n\r\n            case string s:\r\n            {\r\n                ms.WriteByte((byte)'S');\r\n                var bytes = Encoding.UTF8.GetBytes(s);\r\n                WriteU32(ms, (uint)bytes.Length);\r\n                ms.Write(bytes);\r\n                break;\r\n            }\r\n            case byte[] r:\r\n                ms.WriteByte((byte)'R');\r\n                WriteU32(ms, (uint)r.Length);\r\n                ms.Write(r);\r\n                break;\r\n\r\n            default:\r\n                throw new FormatException(\r\n                    $\"FBX write: node '{owner}': unsupported property CLR type {value?.GetType().Name ?? \"null\"}.\");\r\n        }\r\n    }\r\n\r\n    private static void WriteArrayHeader(MemoryStream ms, char code, int count, int elemSize)\r\n    {\r\n        ms.WriteByte((byte)code);\r\n        WriteU32(ms, (uint)count);\r\n        WriteU32(ms, 0); // encoding 0 = uncompressed\r\n        WriteU32(ms, checked((uint)(count * elemSize)));\r\n    }\r\n\r\n    // ------------------------------------------------------------------ footer\r\n\r\n    private static void WriteFooter(MemoryStream ms)\r\n    {\r\n        ms.Write(FooterWatermark);\r\n\r\n        // Zero-pad so the version echo starts 16-aligned (Blender pads at least 1 byte).\r\n        int pad = (int)(16 - ms.Position % 16);\r\n        for (int i = 0; i < pad; i++)\r\n            ms.WriteByte(0);\r\n\r\n        WriteU32(ms, Version);\r\n        Span<byte> zeros = stackalloc byte[120];\r\n        zeros.Clear();\r\n        ms.Write(zeros);\r\n        ms.Write(FooterMagic);\r\n    }\r\n\r\n    // ------------------------------------------------------------------ primitives\r\n\r\n    private static void WriteU32(MemoryStream ms, uint v)\r\n    {\r\n        Span<byte> b = stackalloc byte[4];\r\n        BinaryPrimitives.WriteUInt32LittleEndian(b, v);\r\n        ms.Write(b);\r\n    }\r\n\r\n    private static void WriteI16(MemoryStream ms, short v)\r\n    {\r\n        Span<byte> b = stackalloc byte[2];\r\n        BinaryPrimitives.WriteInt16LittleEndian(b, v);\r\n        ms.Write(b);\r\n    }\r\n\r\n    private static void WriteI32(MemoryStream ms, int v)\r\n    {\r\n        Span<byte> b = stackalloc byte[4];\r\n        BinaryPrimitives.WriteInt32LittleEndian(b, v);\r\n        ms.Write(b);\r\n    }\r\n\r\n    private static void WriteI64(MemoryStream ms, long v)\r\n    {\r\n        Span<byte> b = stackalloc byte[8];\r\n        BinaryPrimitives.WriteInt64LittleEndian(b, v);\r\n        ms.Write(b);\r\n    }\r\n\r\n    private static void WriteF32(MemoryStream ms, float v)\r\n    {\r\n        Span<byte> b = stackalloc byte[4];\r\n        BinaryPrimitives.WriteSingleLittleEndian(b, v);\r\n        ms.Write(b);\r\n    }\r\n\r\n    private static void WriteF64(MemoryStream ms, double v)\r\n    {\r\n        Span<byte> b = stackalloc byte[8];\r\n        BinaryPrimitives.WriteDoubleLittleEndian(b, v);\r\n        ms.Write(b);\r\n    }\r\n}\r\n"
        }
    ]
}