Skip to content

fix: keep a dynamic multibody root's rotation in 2D - #1028

Open
taslater wants to merge 1 commit into
dimforge:masterfrom
taslater:fix-2d-multibody-root-rotation
Open

taslater wants to merge 1 commit into
dimforge:masterfrom
taslater:fix-2d-multibody-root-rotation

Conversation

@taslater

@taslater taslater commented Sep 29, 2026 •

Copy link
Copy Markdown

Bug

In 2D, a multibody whose root is a dynamic rigid-body loses the root's rotation on the first step. Its position is kept, so the whole articulation ends up rotated about the root.

MultibodyJoint::set_free_pos (used when a dynamic root takes the body's pose) copies the translation into coords.x/coords.y and the rotation into joint_rot, but not into the angular coordinate coords.z. On the next integrate, a joint with one free angular DoF rebuilds joint_rot from that coordinate:

self.coords[DIM + dof_id] += vels[curr_free_dof] * dt;
self.joint_rot = rotation_from_angle(self.coords[DIM + dof_id]);

so the root's rotation becomes 0 + angvel * dt.

3D is unaffected: with three free angular DoFs, integrate composes onto joint_rot rather than rebuilding it from the coordinates.

Fix

One line in the dim2 branch of set_free_pos: self.coords.z = pos.rotation.angle();

Test

multibody_dynamic_root_keeps_its_initial_rotation (in physics_pipeline/test.rs, both dimensions): a dynamic root at 0.7 rad with one revolute multibody joint to a child, one step, and the root's rotation must be unchanged. Before the fix in 2D it changes by exactly 0.7 rad. In 3D it passes with or without the change, which confirms the 3D case.

cargo test -p rapier2d (217) and cargo test -p rapier3d (444) pass, and cargo fmt -- --check is clean. I did not run the full run-ci-checks.sh (parallel/SIMD feature builds, release-mode determinism tests, examples), and I did not run the testbed examples. Clippy on rapier2d/rapier3d reports two warnings, both in files this PR doesn't touch.

How I found it

I was evaluating multibody joints for a 2D articulated robot: a 5-capsule chain converted from impulse joints came out rotated at the first step by exactly its root's initial angle (0.245 rad with the root at one end of the chain, 1.57 rad with the root in the middle). I traced it to this. Until it's fixed, on released versions the workaround is multibody.link_mut(0).unwrap().joint.apply_displacement(&[0.0, 0.0, root_angle]) after inserting the joints.

AI disclosure

I used Claude Code (Anthropic) to help investigate this, and to write the fix and the test. I reviewed the diff, and ran the tests myself against a checkout of current master (846c463). I did not see an AI policy in CONTRIBUTING.md, so please tell me if you'd like this handled differently.

🤖 Generated with Claude Code

MultibodyJoint::set_free_pos copied the root's translation into
coords.x/y and its rotation into joint_rot, but not into the angular
coordinate coords.z. On the next integrate, a joint with one free
angular DoF rebuilds joint_rot from that coordinate, so a dynamic root
lost its initial rotation on the first step while keeping its position,
and the whole articulation came out rotated about the root.

3D is unaffected: with three free angular DoFs, integrate composes onto
joint_rot instead of rebuilding it from the coordinates.

Adds a regression test for both dimensions.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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