Skip to content

t2m: foot-contact detection + IK pinning post-pass (#856)#887

Closed
fernandotonon wants to merge 2 commits into
feat/t2m-twist-857from
feat/foot-pin-856
Closed

t2m: foot-contact detection + IK pinning post-pass (#856)#887
fernandotonon wants to merge 2 commits into
feat/t2m-twist-857from
feat/foot-pin-856

Conversation

@fernandotonon

Copy link
Copy Markdown
Owner

Closes #856 (epic #837). Stacked on #886 (base = feat/t2m-twist-857) — retarget the base branch to master after #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

  • FootContact pure-data core (Ogre-free, unit-tested in FootContact_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 never apply()'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.
  • Surfaces (on-by-default in generation, per the issue): CLI --no-foot-pin opt-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.
  • Bone-matcher fix uncovered by this work: canonicalIndexForBone now maps upperleg to 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

  • Rumba walk: 2 contact spans detected and pinned (6 blended keyframes); renders upright, stride intact.
  • Quaternius Knight: pins after the alias fix; controlled render (pinned single-clip eval library) unchanged silhouette, no knee pops.
  • 8 pure-data core tests incl. skating rejection, unreachable-target clamp, straight-leg stability, air-time-robust ground level.

🤖 Generated with Claude Code

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>
@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 86566405-bef5-4efe-930f-a70237484bc1

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/foot-pin-856

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread src/AnimationMerger.cpp
Comment on lines +1588 to +1592
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

@fernandotonon

Copy link
Copy Markdown
Owner Author

Superseded by the combined PR #904 (all three stacked slices merged into one branch, feat/t2m-epic-837, incl. the twist-test fixes).

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