Skip to content

feat: optional mocap-clip layer with Mixamo retargeting + crossfade - #29

Merged
a-baran-orhan merged 1 commit into
mainfrom
claude/jovial-dhawan-e61501
Jul 10, 2026
Merged

feat: optional mocap-clip layer with Mixamo retargeting + crossfade#29
a-baran-orhan merged 1 commit into
mainfrom
claude/jovial-dhawan-e61501

Conversation

@a-baran-orhan

Copy link
Copy Markdown
Member

Description

Adds an optional mocap-clip layer to the viewer so standard movements can play a retargeted Mixamo animation clip, crossfaded over the procedural DSL keyframes — while the procedural path stays the guaranteed fallback. A missing clip, absent skinned character, or any retarget failure all leave the keyframes driving the figure, so a clip can never blank or break a movement.

What changed

Parser / DSL — new document-level directive clip "<name>" → optional PosecodeIR.clip (parser.ts, clamp.ts, types.ts). Documented in the SPEC.md grammar and the language vocab.ts (hover/completion). Renderers without a matching clip ignore it; phases always fully describe the movement.

Render (packages/posecode-render/src/clips.ts, new)

  • loadClipSource — loads an FBX or GLB and picks its longest AnimationClip.
  • retargetMocapClip — bakes the clip onto the Mixamo-named character skeleton via SkeletonUtils.retargetClip, with hardening: match bones by plain mixamo name (strip mixamorig\d*: prefix); snapshot + restore the target bone locals because retargetClip resets the skeleton to bind pose (which would destroy character.ts's anatomical rest calibration); drop tracks for unmapped target bones (they'd snap fingers/helpers into the T-pose); pin hip X/Z so the clip plays in place and composes with the DSL's own travel/turn root choreography.
  • createClipLayer — plays the baked clip through a THREE.AnimationMixer and blends it over the procedural pose written that frame; unsynced clip-only bones are restored to rest when weight hits zero.

Viewer (index.ts) — opt in via createViewer(canvas, { clips: { walk: url } }); new clipActive getter; a crossfade weight eases per frame so switching documents (or a clip arriving mid-play) fades rather than pops. Timeline time drives the mixer so pause/scrub/export stay deterministic.

Character (character.ts) — exposes the skinned retarget-target mesh and the driver-synced bone set the blend layer needs.

Playground — maps walk → the repo's Ch36_nonPBR.fbx (served via a public/clips/walk.fbx symlink; verified to resolve in vite build) and adds clip "walk" to the walk-cycle example.

Why

Mixamo clips are dense motion capture; posecode is procedural (one keyframe per phase + contact solving). This layer lets standard movements borrow mocap richness where a clip exists, without giving up the procedural engine that handles authored angles, ground contact, pins, and reach-IK.

Reviewer notes

  • The 35 MB FBX is served via a symlink into playground/public/clips/ rather than duplicating the binary. vite build follows it and copies the real file into dist/clips/walk.fbx (confirmed). Happy to swap to a real copy if preferred.
  • Verified end-to-end in the browser: walk-cycleclipActive: true, rig intact (no T-pose snap / exploded limbs), limbs animate; squat (no clip) → clipActive: false, procedural path renders cleanly. No console errors.

Fixes # (n/a)

Type of Change

  • New feature (non-breaking change which adds functionality)
  • Bug fix (non-breaking change which fixes an issue)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Movement/Preset addition (adding a new .posecode script and registering it)
  • Documentation update

Checklist

  • My code follows the code style guidelines of this project
  • I have performed a self-review of my own code
  • My changes generate no new TypeScript/compiler warnings or errors:
    • Running npm run typecheck passes successfully
    • Running npm run build compiles without errors
  • I have run the unit test suite (npm test) and all tests pass (205 passing, 14 new)
  • If applicable, I have run the fidelity evals (npm run eval) and all checks pass
  • If I added a new movement, I ran node scripts/generate-content-pages.mjs to regenerate the static pages

Add an optional layer so movements can play a retargeted Mixamo animation
clip, crossfaded over the procedural DSL keyframes. The procedural path
stays the fallback: a missing clip, absent character, or retarget failure
all leave the keyframes driving the figure.

- Parser: new document directive `clip "<name>"` → optional `PosecodeIR.clip`
  (parser, clamp, types), documented in SPEC.md grammar and language vocab.
- Render: `packages/posecode-render/src/clips.ts` loads an FBX/GLB, picks its
  longest AnimationClip, retargets it onto the Mixamo-named character skeleton
  via SkeletonUtils.retargetClip, and crossfades it with a THREE.AnimationMixer.
  Retarget hardening: match bones by plain mixamo name (strip mixamorig prefix);
  snapshot+restore target bone locals (retargetClip resets to bind pose, which
  would destroy character.ts's rest calibration); drop tracks for unmapped
  bones (they'd snap to T-pose); pin hip X/Z so the clip plays in place and
  composes with DSL travel/turn.
- Viewer: opt in via `createViewer(canvas, { clips: { walk: url } })`; new
  `clipActive` getter; weight eases per frame so switching docs fades not pops.
- Character: expose the skinned retarget target mesh + the driver-synced bone
  set for the blend layer.
- Playground: map `walk`→ the repo FBX (served via public/clips symlink) and
  add `clip "walk"` to the walk-cycle example.

Tests: 14 new (parser clip directive; clip retarget baking, bone filtering,
rest preservation, hip-pin, crossfade blend). Full suite 205 passing; all
packages + playground type-check clean; verified end-to-end in the browser.
@vercel

vercel Bot commented Jul 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
posecode Ready Ready Preview, Comment Jul 10, 2026 7:45pm

@a-baran-orhan
a-baran-orhan merged commit a33d62a into main Jul 10, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant