Skip to content

[Patch] Apply root motion once per anchor on modular assets - #1171

Open
miogds wants to merge 1 commit into
untoldengine:developfrom
miolabs:feature/root_motion_single_driver
Open

[Patch] Apply root motion once per anchor on modular assets#1171
miogds wants to merge 1 commit into
untoldengine:developfrom
miolabs:feature/root_motion_single_driver

Conversation

@miogds

@miogds miogds commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

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. setRootMotionEnabled armed root motion on every one of them, and each extracted the same horizontal/yaw delta and applied it to the shared anchor via translateBy/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

setRootMotionEnabled designates the first resolved component as the anchor's transform driver (RootMotionState.drivesAnchor); applyRootMotion gates 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.

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