rapier3d-mjcf 0.34.0

MuJoCo MJCF loader for the 3D rapier physics engine.
Documentation

MJCF loader for the Rapier physics engine

Disclaimer. Most of this crate — source, tests, and documentation — was produced by an AI coding assistant working iteratively from MJCF reference scenes (primarily the MuJoCo Menagerie), under human direction and review.

Rapier is a set of 2D and 3D physics engines for games, animation, and robotics. The rapier3d-mjcf crate lets you convert a MuJoCo MJCF XML file into a set of rigid-bodies, colliders, and joints, for use with the rapier3d physics engine.

The parser (mjcf-rs) is a sibling crate. Both are pure Rust — no libmujoco / FFI bindings.

Quick start

use rapier3d::prelude::*;
use rapier3d_mjcf::{MjcfLoaderOptions, MjcfRobot};

let mut bodies = RigidBodySet::new();
let mut colliders = ColliderSet::new();
let mut impulse_joints = ImpulseJointSet::new();

let (robot, _model) =
    MjcfRobot::from_file("robot.xml", MjcfLoaderOptions::default()).unwrap();
robot.insert_using_impulse_joints(&mut bodies, &mut colliders, &mut impulse_joints);

Optional cargo features

Feature Effect
stl Load .stl meshes referenced by <mesh> assets.
wavefront Load .obj meshes referenced by <mesh> assets.
msh Parse MuJoCo's custom binary .msh mesh format (in mjcf-rs).

.dae (Collada) is intentionally unsupported because MJCF doesn't use it.

Feature matrix

This crate is rolled out in phases. The table tracks what each phase delivers and the current implementation status.

Legend: ✅ supported · ⚠️ partial / approximated · 📦 preserved as metadata · ❌ out of scope.

Phase 1 — Core kinematics ✅

  • <mujoco> root, <compiler> (angle, eulerseq, coordinate=local), <option> (timestep, gravity recorded)
  • <worldbody> and nested <body> trees
  • ✅ Body poses via pos, quat, axisangle, euler, xyaxes, zaxis
  • ✅ Angle-unit conversion for compiler/angle="degree" (default)
  • <joint> types: hinge, slide, ball, free
  • <freejoint>
  • <inertial> with mass + diaginertia or fullinertia
  • <geom> types: plane, sphere, capsule, cylinder, box, ellipsoid*
  • <geom fromto> form for capsule / cylinder / box
  • ✅ Multi-joint bodies: rapier intermediates synthesized so each rapier body has at most one parent joint, just like MJCF would expand.
  • ✅ Welded bodies (no joint) — fixed joint to parent, force-fixed when the parent is the world.
  • make_roots_fixed, enable_joint_collisions, shift, scale, collider_blueprint, rigid_body_blueprint, skip_plane_geoms loader options. (skip_plane_geoms drops <geom type="plane"> elements at load time so callers can supply their own ground.)

* ellipsoid is approximated as the convex hull of an icosphere scaled per-axis (rapier has no ellipsoid primitive).

Phase 2 — Defaults, includes, mesh assets ✅

  • <default> class inheritance, with nested classes and childclass propagation. The parser bakes resolved attributes into every element so the loader never revisits the class hierarchy.
  • <include file="…"/> recursive inlining (with cycle detection).
  • <frame> pose grouping (poses are folded into children's local poses).
  • <asset><mesh> and <asset><hfield> (collider build behind the stl/wavefront/msh cargo features; inline-elevation hfields work without a feature).
  • compiler/inertiafromgeom (true/auto/false) — bodies without <inertial> derive their mass properties from the union of their geoms.
  • compiler/autolimitsrange implies limited="true" when set.
  • compiler/discardvisual — drops visual-only geoms (those with contype=conaffinity=0) at load time.
  • compiler/convexhull — when true, <geom type="mesh"> becomes a convex hull instead of a triangle mesh.
  • compiler/strippath — strips directory components from file= references (legacy compatibility).
  • compiler/meshdir / assetdir / texturedir for asset resolution.

Phase 3 — Contact filtering and equality constraints ✅

  • contype / conaffinityInteractionGroups (the loader option ContactFilterMode::{Symmetric,Asymmetric} chooses how MJCF's "OR" rule maps onto rapier's "AND" rule; symmetric is the default).
  • <contact><exclude> between bodies, via the user-installable MjcfContactHooks that the handles object exposes.
  • <contact><pair> per-pair friction / margin overrides through the same hook (uses rapier's contact-modification path).
  • <equality><connect> — point-to-point constraint as an impulse joint with all linear axes locked at the chosen anchor.
  • <equality><weld> — rigid attachment as a fixed impulse joint with optional relpose.

Equality joints are inserted as impulse joints even when the rest of the model uses multibody joints (rapier multibodies don't support loop-closure joints).

Phase 4 — Joint dynamics ✅

  • ✅ Joint damping and stiffness (mapped to a rapier JointMotor using the ForceBased motor model — the motor acts as a PD spring at the rest position).
  • springref — drives the spring's reference position (in radians for hinges).
  • springdamper="(timeconst, dampratio)" — converted to (stiffness, damping) via the standard MuJoCo formula.
  • armature — adds rotor inertia along the joint axis. For axes aligned with the body's principal axes the result is exact; for arbitrary axes the contribution is approximated by adding armature · outer(axis, axis) to the inertia tensor before re-diagonalizing.
  • frictionloss — approximated by a velocity-based motor with capped force (lossy; documented under "Limitations").
  • <body gravcomp>gravcomp ∈ {0, 1} maps directly to gravity_scale = 1 - gravcomp. Fractional values are honored to first order, and an opt-in MjcfRobotHandles::apply_gravity_compensation helper applies an exact per-step external force when configuration / mass changes matter.
  • compiler/boundmass, compiler/boundinertia, compiler/balanceinertia, compiler/settotalmass.

ref (joint reference) is recorded on the AST and used by the spring target, but not yet baked into the joint's local frames — the joint's reported angle is therefore offset by ref compared to MuJoCo's convention. (Tracked for a future polish pass.)

Phase 5 — Actuators, sensors, keyframes ✅

  • <actuator> pass-through. The handles object exposes per-actuator joint handles, and a built-in MjcfRobotHandles::apply_controls(impulse_joints, ctrl) drives the rapier joint motors for <motor>, <position>, <velocity>, and <damper> actuator types. <general> and <intvelocity> are recorded but left to the user to wire up. The *_scaled variants (apply_controls_scaled / apply_controls_multibody_scaled) take a gain_scale that uniformly softens (< 1) or stiffens (> 1) every actuator's gains and force limits — handy to ease a servo-driven move that would otherwise saturate and snap.
  • <keyframe> state — MjcfRobotHandles::apply_keyframe applies a keyframe's full state: qpos / qvel joint coordinates (using MjcfRobot::qpos_dofs, a precomputed map from MuJoCo's generalized-coordinate order onto the rapier joints/bodies), a floating base's world pose, and mpos / mquat mocap state. It works on both insertion paths — the multibody path writes the multibody's generalized coordinates (then runs forward-kinematics), the impulse-joint path runs forward-kinematics over the joint tree and writes each body's world pose. apply_mocap_keyframe (mocap only) and MjcfRobot::keyframe_by_name remain available. (Per-joint qvel is applied on the multibody path; the impulse path applies only a floating base's qvel.)
  • <sensor> reading via MjcfRobot::read_sensor for the state-derivable subset: framepos, framequat, framelinvel, frameangvel, velocimeter, gyro, subtreemass, subtreecom, clock. Sensors that need contact-area integration (touch, rangefinder, geomdist, camprojection) are out of scope.
  • <tendon><fixed> (linear joint-coordinate tendons). An actuator with tendon="…" is bound to the tendon's first joint, and the tendon's other joints are coupled to it (q_k = (coef_k/coef_0)·q_0, a multibody DoF coupling — exact for equal-inertia joints, which covers every menagerie fixed tendon) so they move as one. This is what makes e.g. the shadow hand's tendon-driven middle+distal finger segments curl together. Pairs already coupled by an explicit <equality><joint> (franka, robotiq) are left alone. <tendon><spatial> (site-routed cables with wrapping) and <equality><tendon> length constraints remain out of scope.

Out of scope ❌

  • <extension> / MuJoCo plugins.
  • <flexcomp> / <flex> / <skin> (deformables).
  • <composite> (procedural body clusters).
  • <tendon><spatial> and <equality> of tendons / flex (fixed-tendon joint coupling and actuation are supported — see above).
  • Fixed-tendon passive stiffness/damping/range/frictionloss.
  • <actuator> types: cylinder, muscle, adhesion.
  • <sensor> types that need contact-area integration: touch, rangefinder, geomdist, camprojection.
  • <geom type="sdf">.
  • <compiler coordinate="global"> (deprecated MJCF feature).
  • MJCF write-back (the parser is read-only).

Limitations

The mappings noted with ⚠️ above are deliberate trade-offs. In particular:

  • Ellipsoid shapes are approximated by an icosphere convex hull.
  • frictionloss is approximated by a velocity-capped motor (Phase 4).
  • solref / solimp softness parameters are not translated — rapier has its own joint-softness controls.

When inserting joints as multibody joints, the bodies are reset to their neutral configuration (all generalized coordinates = 0), matching the behavior of rapier3d-urdf.

Resources and discussions

  • Dimforge: See all the open-source projects we are working on! Follow our announcements on our blog.
  • User guide: Learn to use Rapier in your project by reading the official User Guides.
  • Discord: Come chat with us, get help, suggest features, on Discord!

Please make sure to familiarize yourself with our Code of Conduct and our Contribution Guidelines before contributing or participating in discussions with the community.