[Patch] Apply root motion once per anchor on modular assets - #1171
Open
miogds wants to merge 1 commit into
Open
Conversation
A modular asset (one skinned mesh per body part, all sharing a skeleton) resolves to several animation components; each extracted the same root deltas and applied them to the shared anchor entity, moving the character at N x clip speed — a 14-part character traveled at exactly 14 x. setRootMotionEnabled now designates the first resolved component as the anchor's transform driver; the others still extract and ground their poses so every part stays in sync. Flat assets are unchanged (their only component is the driver). Regression test drives a parent with two skinned children and asserts travel at clip speed, with both parts' poses grounded.
This was referenced Sep 5, 2026
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.
Summary
Follow-up fix to root motion (#1135), found by driving a real modular character: an asset that carries one skinned mesh per body part (a common UE export shape) resolves to several
AnimationComponents, all sampling the same clips against the same anchor.setRootMotionEnabledarmed root motion on every one of them, and each extracted the same horizontal/yaw delta and applied it to the shared anchor viatranslateBy/rotateTo— so the character moved at N× clip speed. A 14-part zombie traveled at exactly 14×; nothing looked wrong in the pose, only the transform raced.Fix
setRootMotionEnableddesignates the first resolved component as the anchor's transform driver (RootMotionState.drivesAnchor);applyRootMotiongates only the delta application on it. Every component still runs extraction and pose grounding (stripRootMotion), so all parts stay visually in sync. Flat assets are unchanged — their single component is the driver.Motion matching's anchor uses (
MotionMatchingState.anchorEntity) were audited too: they only read position/heading for the query, so no equivalent write amplification exists there.Testing
New regression test in
AnimationRootMotionTests: a parent root with two skinned children carrying animation components, root motion enabled on the parent — asserts the anchor travels at clip speed (not 2×) and that both parts' poses stay grounded. Verified the test fails at exactly 2× with the gate removed.All animation suites pass on this base (72 tests: root motion, inertialization, foot IK, motion matching, compiled sampler, policy). SwiftFormat lint clean.