Skip to content

mujoco: support joint mimic constraints#990

Merged
azeey merged 18 commits into
mainfrom
azeey/mujoco_mimic_constraint
Jun 4, 2026
Merged

mujoco: support joint mimic constraints#990
azeey merged 18 commits into
mainfrom
azeey/mujoco_mimic_constraint

Conversation

@azeey

@azeey azeey commented May 27, 2026

Copy link
Copy Markdown
Contributor

🎉 New feature

Part of #299

Summary

Implement joint mimic features in the MuJoCo physics engine plugin, supporting coupling for standard 1-DOF and multi-DOF joints. Because MuJoCo solves joint equality constraints as soft force constraints, dynamic keyframe state-teleportation (e.g. SetJointPosition) without coordinate alignment generates severe physical spring-force solver spikes. We resolve this by performing automated C++ coordinate state propagation to pre-align downstream follower states in the active state arrays at the keyframing boundary.

Test it

ctest -R mimic

Screen capture

Screen.Recording.2026-05-27.at.16.54.47.mp4

A dynamic spring-suspended scissor lift. It showcases multi-stage joint mimic constraints using an open-tree topology, keeping the cargo platform perfectly flat and stable as a heavy rolling payload shifts the suspension's gravity center under real-time spring-damper physics!

File: https://gist.github.com/azeey/acf33ed620084a4e82e032edc0bd4bce#file-scissor_lift_demo-sdf

Screen.Recording.2026-05-27.at.16.36.10.mp4

A concentric, stacked planetary Orrery. Driven by a single master PID force controller, it showcases 9 high-precision mimic joint constraints—simulating accurate Keplerian orbital speed ratios and the perfect synchronous tidal locking of the Moon as they spin in harmony.

File: https://gist.github.com/azeey/acf33ed620084a4e82e032edc0bd4bce#file-orrery_demo-sdf

Checklist

  • Signed all commits for DCO
  • Added a screen capture or video to the PR description that demonstrates the feature
  • Added tests
  • Added example and/or tutorial
  • Updated documentation (as needed)
  • Updated migration guide (as needed)
  • Consider updating Python bindings (if the library has them)
  • codecheck passed (See contributing)
  • All tests passed (See test coverage)
  • Updated Bazel files (if adding new files). Created an issue otherwise.
  • While waiting for a review on your PR, please help review another open pull request to support the maintainers
  • Was GenAI used to generate this PR? If so, make sure to add "Generated-by" to your commits. (See this policy for more info.)

Generated-by: Gemini 3.0 Pro

Note to maintainers: Remember to use Squash-Merge and edit the commit message to match the pull request summary while retaining Signed-off-by and Generated-by messages.

Backports: If this is a backport, please use Rebase and Merge instead.

azeey added 15 commits May 13, 2026 11:13
- Support universal joints in the MuJoCo plugin by creating two hinge joints in series and two actuators.
- Add basic state integration tests and out-of-bounds DOF safety checks across MuJoCo, Dartsim, and Bullet-Featherstone wrappers.

Generated-by: Gemini 3.0 Pro
Signed-off-by: Addisu Z. Taddese <addisuzt@intrinsic.ai>
- Support screw joints in the MuJoCo physics engine plugin using coupled slide/hinge joints and equality constraints.
- Make the rotational hinge joint the primary axis in the MuJoCo plugin, fully aligning its coordinate units (radians) with DARTsim.
- Add a new anchored screw joint model with static damping in the test world.
- Add a unified screw joint integration test suite inside joint_features, validating kinematic coupling and damping decay.

Generated-by: Gemini 3.0 Pro
Signed-off-by: Addisu Z. Taddese <addisuzt@intrinsic.ai>
Generated-by: Gemini 3.0 Pro
Signed-off-by: Addisu Z. Taddese <addisuzt@intrinsic.ai>
…DF strings

- Revert test.world back to its pristine state to ensure all other engine tests (dartsim, tpe) expecting exactly 9 models pass cleanly.
- Migrate Universal joint checks in SDFFeatures_TEST to load a self-contained SDF string.
- Migrate Screw joint coupling and damping checks in joint_features to load a self-contained SDF string.

Generated-by: Gemini 3.0
Signed-off-by: Addisu Z. Taddese <addisuzt@intrinsic.ai>
- Verify screw joint position limits enforcement across DARTsim and MuJoCo plugins.
- Enforce position limits purely on the translational Slide joint to allow exact kinematic constraint coupling without solver damping.

Generated-by: Gemini 3.0
Signed-off-by: Addisu Z. Taddese <addisuzt@intrinsic.ai>
Signed-off-by: Addisu Z. Taddese <addisuzt@intrinsic.ai>
- Add an eqIndices vector to JointInfo to track equality constraint indices compiled in the mjModel.
- Resolve and store associated equality constraint indices during model compilation in resolveJointIndices.
- Dynamically resolve active degrees of freedom by subtracting eqIndices.size() from the physical joints compiled on the child body in GetJointDegreesOfFreedom.
- Refactor simultaneous position and velocity updates to utilize eqIndices and updateCoupledSecondaryJoint helper to avoid code duplication.
- Safely ensure Gazebo API queries are always backed by the actual compiled MuJoCo solver state, preventing out-of-bounds coordinate access.

Generated-by: Gemini 3.0 Pro
Signed-off-by: Addisu Z. Taddese <addisuzt@intrinsic.ai>
Signed-off-by: Addisu Z. Taddese <addisuzt@intrinsic.ai>
- Add convertScrewThreadPitch helper function to convert SDFormat screw thread pitch (m/rev) to MuJoCo coordinate ratio (m/rad).
- Document screw thread pitch conversion helper along with physical units.
- Deduplicate pitch conversion logic in Slide limits and equality constraint ratio calculations.

Generated-by: Gemini 3.0 Pro

Signed-off-by: Addisu Z. Taddese <addisuzt@intrinsic.ai>
Signed-off-by: Addisu Z. Taddese <addisuzt@intrinsic.ai>
- Replace manual index assignments with robust C++ std::fill zero-initialization.

Generated-by: Gemini 3.0 Pro

Signed-off-by: Addisu Z. Taddese <addisuzt@intrinsic.ai>
Replace implicit body ID comparisons and search loops with a
high-performance pointer-based constraint tracking scheme that stores
direct mjsEquality* pointers and caches the resolved screwEqIndex in
O(1) time.

Generated-by: Gemini 3.0 Pro
Signed-off-by: Addisu Z. Taddese <addisuzt@intrinsic.ai>
Signed-off-by: Addisu Z. Taddese <addisuzt@intrinsic.ai>
Implement joint mimic features in the MuJoCo physics engine plugin, supporting coupling for standard 1-DOF and multi-DOF joints. Because MuJoCo solves joint equality constraints as soft force constraints, dynamic keyframe state-teleportation (e.g. SetJointPosition) without coordinate alignment generates severe physical spring-force solver spikes. We resolve this by performing automated C++ coordinate state propagation to pre-align downstream follower states in the active state arrays at the keyframing boundary.

Generated-by: Gemini 3.0 Pro
Signed-off-by: Addisu Z. Taddese <addisuzt@intrinsic.ai>
@azeey
azeey requested a review from scpeters as a code owner May 27, 2026 04:57
@azeey
azeey requested a review from iche033 May 27, 2026 04:58
Sdf Joint degree-of-freedom queries are fired by Gazebo Sim's model parser before the active world spec has undergone its first dynamic MuJoCo compilation pass. Querying standard compiled tables during this phase results in false-positive safety violations and startup failures.

We resolve this by implementing a static spec DOM tree fallback path inside GetJointDegreesOfFreedom when query indices are uncompiled (out-of-bounds). We also add a virtual destructor to the Base class to make it polymorphic for safe casting, and add the GetJointDegreesOfFreedomPreCompiled unit test to assert this fallback path using a dummy model-swap trick.

Generated-by: Gemini 3.0 Pro
Signed-off-by: Addisu Z. Taddese <addisuzt@intrinsic.ai>
@iche033

iche033 commented May 29, 2026

Copy link
Copy Markdown
Contributor

cool demos! we need to add those to gz-sim example worlds once this gets in

Comment thread mujoco/src/JointFeatures.cc Outdated
if(this->PhysicsEngineName(name) != "bullet-featherstone" &&
this->PhysicsEngineName(name) != "mujoco")
{
GTEST_SKIP();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

based on #930 (comment), I think we need to be using continue instead of GTEST_SKIP?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think this is actually okay. The different tests are ran as different executables. The for loop is iterating through potential plugins found in each shared library. In theory, a shared library can have multiple plugins, but none of our physics engines do that.

for (const std::string &name : this->pluginNames)
{
CHECK_SUPPORTED_ENGINE(name, "mujoco")
CHECK_SUPPORTED_ENGINE(name, "bullet-featherstone")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Just realized these macros call GTEST_SKIP which may not be what we want?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Comment thread mujoco/src/JointFeatures.cc
Comment thread test/common_test/joint_mimic_features.cc Outdated
Comment thread mujoco/src/JointFeatures.cc Outdated
Comment on lines +204 to +205
if (c.followerJointInfo.lock() == _followerJoint &&
c.followerDof == _followerDof)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

minor optimization to short circuit the check if DOF does not match so avoid doing the lock()

Suggested change
if (c.followerJointInfo.lock() == _followerJoint &&
c.followerDof == _followerDof)
if (c.followerDof == _followerDoft &&
c.followerJointInfo.lock() == _followerJoin)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks 4b117e3

Signed-off-by: Addisu Z. Taddese <addisuzt@intrinsic.ai>
Base automatically changed from azeey/mujoco_more_joint_types to main June 3, 2026 19:45
Signed-off-by: Addisu Z. Taddese <addisuzt@intrinsic.ai>
@github-project-automation github-project-automation Bot moved this from Inbox to In review in Core development Jun 4, 2026
@azeey
azeey merged commit 8aac3a7 into main Jun 4, 2026
13 checks passed
@azeey
azeey deleted the azeey/mujoco_mimic_constraint branch June 4, 2026 01:27
@github-project-automation github-project-automation Bot moved this from In review to Done in Core development Jun 4, 2026
yaswanth1701 pushed a commit to yaswanth1701/gz-physics that referenced this pull request Jun 6, 2026
Implement joint mimic features in the MuJoCo physics engine plugin, supporting coupling for standard 1-DOF and multi-DOF joints. Because MuJoCo solves joint equality constraints as soft force constraints, dynamic keyframe state-teleportation (e.g. SetJointPosition) without coordinate alignment generates severe physical spring-force solver spikes. We resolve this by performing automated C++ coordinate state propagation to pre-align downstream follower states in the active state arrays at the keyframing boundary.


Generated-by: Gemini 3.0 Pro

---------

Signed-off-by: Addisu Z. Taddese <addisuzt@intrinsic.ai>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants