The crates

avatar render --avatar model.fbx --pose Avatar.prefab -o posed.png # the rig layer, driven from a prefab's pose avatar render --avatar model.fbx --stretch 'Skirt_*:1.5' -o out.png
  • avatar-mesh — POD RawMesh / skin interchange, no glam.
  • avatar-fbx::meshes() + avatar-gltf — FBX / glTF → RawMesh + skin/bind.
  • avatar-posePosedSkeleton → world matrices, GPU bone-matrix palette, CPU skinning, pose::ik two-bone IK.
  • avatar-inputTrackerState / TrackerSource; MockSource + an osc-feature backend; OpenXR planned.

Bind & the rest-pose invariant

Bind comes from TransformLink / inverse-bind — never recomposed from Lcl + PreRotation. The load → pose → skin pipeline is validated by a renderer-free rest-pose reproduction invariant: posing the skeleton at its rest pose and skinning must reproduce the original mesh.

When the per-cluster bind Transforms can't be trusted (converted/MMD→FBX avatars), a pose can still be applied to the raw control points as a per-bone delta (G⁻¹ · world(pose) · world(rest)⁻¹ · G) that never involves the cluster binds — identity for every untouched bone. avatar render --pose <prefab> / --stretch are built on it (see render).

Using it from Rust

use std::path::Path;
let scene = avatar_fbx::FbxScene::load(Path::new("model.fbx"))?;
let meshes = avatar_fbx::FbxDocument::load(Path::new("model.fbx"))?.meshes()?;  // RawMesh + skin per mesh
let skeleton = avatar_armature::Skeleton::from_scene(&scene);
let skel = avatar_pose::PosedSkeleton::from_fbx(&skeleton, &scene, &meshes[0]);
let mut pose = skel.rest_pose();                     // identity palette at rest
pose.set_local(skel.index_of(bone_id).unwrap(), local_matrix);
let palette = skel.palette(&pose);                  // world ยท inverse_bind per bone
// GPU: upload `palette`; CPU: avatar_pose::cpu_skin(&mesh, &vertex_skin, &palette)

Signatures as of this docs pass; the crate READMEs are authoritative.

glam (f32) is confined to pose/input/gltf — it never reaches the lint/cli graph, which is verified by cargo tree.