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 *;
use ;
let mut bodies = new;
let mut colliders = new;
let mut impulse_joints = new;
let =
from_file.unwrap;
robot.insert_using_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,gravityrecorded) - ✅
<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>withmass+diaginertiaorfullinertia - ✅
<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_geomsloader options. (skip_plane_geomsdrops<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 andchildclasspropagation. 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 thestl/wavefront/mshcargo 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/autolimits—rangeimplieslimited="true"when set. - ✅
compiler/discardvisual— drops visual-only geoms (those withcontype=conaffinity=0) at load time. - ✅
compiler/convexhull— whentrue,<geom type="mesh">becomes a convex hull instead of a triangle mesh. - ✅
compiler/strippath— strips directory components fromfile=references (legacy compatibility). - ✅
compiler/meshdir/assetdir/texturedirfor asset resolution.
Phase 3 — Contact filtering and equality constraints ✅
- ✅
contype/conaffinity→InteractionGroups(the loader optionContactFilterMode::{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-installableMjcfContactHooksthat 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 optionalrelpose.
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
dampingandstiffness(mapped to a rapierJointMotorusing theForceBasedmotor 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 addingarmature · 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 togravity_scale = 1 - gravcomp. Fractional values are honored to first order, and an opt-inMjcfRobotHandles::apply_gravity_compensationhelper 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-inMjcfRobotHandles::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*_scaledvariants (apply_controls_scaled/apply_controls_multibody_scaled) take again_scalethat 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_keyframeapplies a keyframe's full state:qpos/qveljoint coordinates (usingMjcfRobot::qpos_dofs, a precomputed map from MuJoCo's generalized-coordinate order onto the rapier joints/bodies), a floating base's world pose, andmpos/mquatmocap 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) andMjcfRobot::keyframe_by_nameremain available. (Per-jointqvelis applied on the multibody path; the impulse path applies only a floating base'sqvel.) - ✅
<sensor>reading viaMjcfRobot::read_sensorfor 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 withtendon="…"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.
frictionlossis approximated by a velocity-capped motor (Phase 4).solref/solimpsoftness 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.