feat: optional mocap-clip layer with Mixamo retargeting + crossfade - #29
Merged
Conversation
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.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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>"→ optionalPosecodeIR.clip(parser.ts,clamp.ts,types.ts). Documented in theSPEC.mdgrammar and the languagevocab.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 longestAnimationClip.retargetMocapClip— bakes the clip onto the Mixamo-named character skeleton viaSkeletonUtils.retargetClip, with hardening: match bones by plain mixamo name (stripmixamorig\d*:prefix); snapshot + restore the target bone locals becauseretargetClipresets the skeleton to bind pose (which would destroycharacter.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 owntravel/turnroot choreography.createClipLayer— plays the baked clip through aTHREE.AnimationMixerand 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 viacreateViewer(canvas, { clips: { walk: url } }); newclipActivegetter; 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'sCh36_nonPBR.fbx(served via apublic/clips/walk.fbxsymlink; verified to resolve invite build) and addsclip "walk"to thewalk-cycleexample.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
playground/public/clips/rather than duplicating the binary.vite buildfollows it and copies the real file intodist/clips/walk.fbx(confirmed). Happy to swap to a real copy if preferred.walk-cycle→clipActive: 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
.posecodescript and registering it)Checklist
npm run typecheckpasses successfullynpm run buildcompiles without errorsnpm test) and all tests pass (205 passing, 14 new)npm run eval) and all checks passnode scripts/generate-content-pages.mjsto regenerate the static pages