Skip to content

fix(animation): Convert animation rest pose for VRM 0.0 retargeting#136

Merged
arkavo-com merged 1 commit intomainfrom
spike/vrma-vrm0-orientation
May 10, 2026
Merged

fix(animation): Convert animation rest pose for VRM 0.0 retargeting#136
arkavo-com merged 1 commit intomainfrom
spike/vrma-vrm0-orientation

Conversation

@arkavo-com
Copy link
Copy Markdown
Contributor

@arkavo-com arkavo-com commented May 3, 2026

Screenshots

Before / after on the same VRM 0.0 + VRMA case (AliciaSolid + Idle.vrma, frame 0):

Before — upside-down After — upright

Regression checks — VRM 1.0 unchanged, and the fix composes with the silhouette feature in #137:

VRM 1.0 + VRMA (no regression) VRM 0.0 + VRMA + --silhouette

Summary

Applies the existing convertForVRM0 coordinate transform to animationRestRotation and animationRestTranslation in VRMAnimationLoader.swift so the delta-retargeting computation delta = inverse(rest) * rotation no longer mixes coordinate frames.

The bug

For VRM 0.0 models with a VRMA animation, the avatar rendered upside-down once playback started — but rendered correctly when no animation played, and VRM 1.0 + VRMA was unaffected. Triangulating the symptoms pointed to the retargeting layer:

In makeRotationSampler, the per-frame rotation was being converted into the model's coordinate frame via convertRotationForVRM0, but the rest rotation it was being subtracted against (rotationRest) was not converted. The delta was therefore expressed in a hybrid frame. For pure Y-axis rotations the conversion is a no-op (Y stays positive), so the bug was invisible there; X- and Z-axis rotations (which is what an idle-hips track contains) accumulated a residual frame rotation that, combined with the renderer's vrmVersionRotation at draw time, manifested as a 180° X flip.

The fix

Symmetry: convert the rest rotation/translation with the same convertForVRM0 transform before computing deltas. Translation gets the same treatment (convertTranslationForVRM0 applied to animationRestTranslation).

19 lines added / 2 removed across one file. convertForVRM0=false (VRM 1.0) is bit-identical.

Verification

Case Before After
AliciaSolid (VRM 0.0) + Idle.vrma upside-down ✅ upright in idle pose
AliciaSolid (VRM 0.0) + Idle.vrma + --silhouette upside-down ✅ upright black silhouette
VRM1_Constraint_Twist_Sample (VRM 1.0) + Idle.vrma upright ✅ unchanged, upright
swift test --parallel n/a ✅ exit 0, 1188 tests

Background — and what NOT to pull forward

There is a stale local branch (now archive/animation-coordinate-fix-jan2026, ex feature/animation-coordinate-fix, Jan 19) that addressed a related symptom with a different mental model: it negated quaternion X+Y at the parser level on every glTF animation, framed as "RH→LH conversion." That premise doesn't match this codebase — the renderer doesn't flip handedness, and applying that conversion universally would break VRM 1.0 + VRMA renders. The salvageable part of that branch is the bone-name heuristic improvements (Mixamo, Blender, namespace prefixes), filed as #135.

Test plan

  • swift build clean
  • swift test --parallel --num-workers 14 -j 16 --disable-sandbox — 1188 tests, exit 0
  • Visually verified VRM 0.0 + VRMA renders upright (still + animated frames)
  • Visually verified VRM 1.0 + VRMA unchanged
  • If you have a regression model that exercised the original "lying down" symptom, please verify it stays fixed

Notes

Branch is named spike/ because it was developed as a focused experimental fix; it is now verified and ready to land. If you'd prefer a renamed branch for tidiness happy to push to a fix/ name and update this PR.

🤖 Generated with Claude Code

VRMA + VRM 0.0 rendered upside-down once animations played because the
delta-retargeting subtracted a converted per-frame rotation from an
unconverted rest rotation, mixing coordinate frames. Apply the same
convertForVRM0 transform to animationRestRotation/animationRestTranslation
before computing deltas so both sides live in the model frame.

Visually verified with VRMVideoRenderer + Idle.vrma:
- AliciaSolid (VRM 0.0): now right-side-up (was upside-down)
- VRM1_Constraint_Twist_Sample (VRM 1.0): unchanged
All 1188 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@arkavo-com arkavo-com merged commit 9f99efb into main May 10, 2026
2 checks passed
@arkavo-com arkavo-com deleted the spike/vrma-vrm0-orientation branch May 10, 2026 01:36
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