t2m: foot-contact detection + IK pinning post-pass (#856)#887
t2m: foot-contact detection + IK pinning post-pass (#856)#887fernandotonon wants to merge 2 commits into
Conversation
Retargeted clips slide/float feet on rigs whose proportions differ from the source — the direction retarget transfers bone directions, not world foot positions. Core (FootContact.h/cpp, Ogre-free + unit-tested): contact detection (foot within a leg-length-scaled height band of the clip's ground level AND nearly stationary horizontally -> spans; ground = low percentile so jump clips don't skew it), analytic two-bone IK (solveKnee — keeps both segment lengths, preserves the pose's own bend plane, clamps unreachable targets), and the 0->1->0 edge blend weight. AnimationMerger::pinFeet: manual FK over the tracks (pure track math — the live skeleton is never apply()'d), detection in the canonical frame (Ct) so ground is horizontal regardless of rig axes, then per contact frame locks the foot to its span-start position blended over blendFrames: thigh re-aimed at the IK knee, shin at the pinned target, foot keyframe compensated to keep its ORIGINAL world orientation (no toe pop). Only thigh/shin/foot keyframes rewritten; _keyFrameDataChanged on each (the #854 cache gotcha). Effectively idempotent (re-detection re-pins to the same targets); 'qtme.footpin.<anim>' marker on bone[0], cleared when applyMotionClip regenerates the clip. Surfaces: ON by default in generation — CLI --no-foot-pin opts out, standalone 'qtmesh anim <f> --foot-pin --animation <name> -o out'; MCP foot_pin arg on generate_motion + standalone pin_feet tool; GUI 'Pin feet (contact cleanup)' checkbox in the Animations section. Also: canonicalIndexForBone learns 'upperleg' as a thigh alias — Quaternius-style rigs (UpperLeg.L/LowerLeg.L) previously mapped BOTH leg bones to the knee role, which broke pinning and double-aimed the knee in the retarget. Verified: Rumba walk 2 contact spans pinned; Knight (UpperLeg/LowerLeg naming) pins after the alias fix; renders upright, stride intact. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8cede98cab
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (!thighTrk || !shinTrk | ||
| || static_cast<int>(thighTrk->getNumKeyFrames()) != nk | ||
| || static_cast<int>(shinTrk->getNumKeyFrames()) != nk | ||
| || (footTrk && static_cast<int>(footTrk->getNumKeyFrames()) != nk)) | ||
| continue; // mixed keyframe grids — not a generated clip |
There was a problem hiding this comment.
Return an error when no legs are eligible for pinning
For standalone --foot-pin/MCP pin_feet on existing clips, a leg whose tracks do not match the generated dense keyframe grid is skipped here, but pinFeet() still falls through to res.ok = true and callers export/report success. In that scenario both legs can be skipped and the user gets an unchanged output file with a successful “0 spans” result instead of a clear unsupported/no-op response; track whether any complete leg was processed and fail or report a skip when none are eligible.
Useful? React with 👍 / 👎.
|
Superseded by the combined PR #904 (all three stacked slices merged into one branch, |
Closes #856 (epic #837). Stacked on #886 (base =
feat/t2m-twist-857) — retarget the base branch tomasterafter #886 merges.Retargeted clips slide/float feet on rigs whose proportions differ from the source: the direction retarget transfers bone directions, not world foot positions.
What's here
FootContactpure-data core (Ogre-free, unit-tested inFootContact_test.cpp— runs without GL):detectContacts— frames where a foot is within a leg-length-scaled height band of the clip's ground level AND nearly stationary horizontally → contact spans (min-length filtered; ground = low percentile so airborne clips don't skew it). A fast-translating on-ground foot (skating — the exact artifact) is correctly NOT a contact.solveKnee— analytic two-bone IK: keeps both segment lengths, preserves the pose's own bend plane (pole from the current knee), clamps unreachable targets, stable on straight legs.blendWeight— 0→1→0 edge ramps so knees don't pop at span borders.AnimationMerger::pinFeet— manual FK over the tracks (pure track math; the live skeleton is neverapply()'d — it may be a SkeletonInstance driving the viewport). Detection runs in the canonical frame (Ct) so "ground" is horizontal regardless of the rig's axes. Per contact frame, the foot is locked to its span-start position: thigh re-aimed at the IK knee, shin at the pinned target, and the foot keyframe is compensated to keep its original world orientation (no toe pop from parent corrections). Only thigh/shin/foot keyframes rewritten;_keyFrameDataChanged()per track (the Retarget post-processing controls: Mixamo-style arm-space slider #854 cache lesson). Effectively idempotent;qtme.footpin.<anim>marker cleared on clip regeneration.--no-foot-pinopt-out + standaloneqtmesh anim <f> --foot-pin --animation <name> -o out; MCPfoot_pinarg ongenerate_motion+ standalonepin_feettool; GUI "Pin feet (contact cleanup)" checkbox.canonicalIndexForBonenow mapsupperlegto the thigh role. Quaternius-style rigs (UpperLeg.L/LowerLeg.L) previously mapped BOTH leg bones onto the knee role — pinning couldn't find a thigh, and the retarget double-aimed the knee (the Knight resolves 14 roles now, up from 13).Verified
🤖 Generated with Claude Code