Skip to content

Commit 0831245

Browse files
authored
feat: add a mjcf loader (#936)
* feat: add mjcf parser * chore: add AI disclaimer to the README * chore: CI fixes * feat(mjcf): cleanup usage of poses * chore: have CI check mjcf crates too
1 parent 6f48e09 commit 0831245

83 files changed

Lines changed: 11981 additions & 43 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/rapier-ci-build.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ jobs:
2323
steps:
2424
- uses: actions/checkout@v4
2525
- name: Cargo doc
26-
run: cargo doc --features parallel,simd-stable,serde-serialize,debug-render -p rapier3d -p rapier2d -p rapier3d-meshloader -p rapier3d-urdf
26+
run: cargo doc --features parallel,simd-stable,serde-serialize,debug-render -p rapier3d -p rapier2d -p rapier3d-meshloader -p rapier3d-urdf && cargo doc -p mjcf-rs --features msh && cargo doc -p rapier3d-mjcf --features stl,wavefront,msh
2727
build-native:
2828
runs-on: ubuntu-latest
2929
env:
@@ -37,6 +37,10 @@ jobs:
3737
run: cargo clippy -p rapier-examples-2d --features parallel,simd-stable
3838
- name: Clippy rapier3d
3939
run: cargo clippy -p rapier-examples-3d --features parallel,simd-stable
40+
- name: Clippy mjcf-rs
41+
run: cargo clippy -p mjcf-rs --all-targets --features msh
42+
- name: Clippy rapier3d-mjcf
43+
run: cargo clippy -p rapier3d-mjcf --all-targets --features stl,wavefront,msh
4044
- name: Build rapier2d
4145
run: cargo build --verbose -p rapier2d;
4246
- name: Build rapier3d
@@ -51,6 +55,10 @@ jobs:
5155
run: cd crates/rapier3d; cargo build --verbose --features simd-stable --features parallel;
5256
- name: Run tests
5357
run: cargo test
58+
- name: Test mjcf-rs
59+
run: cargo test -p mjcf-rs --features msh
60+
- name: Test rapier3d-mjcf
61+
run: cargo test -p rapier3d-mjcf --features stl,wavefront,msh
5462
- name: Check rapier_testbed2d
5563
run: cargo check --verbose -p rapier_testbed2d;
5664
- name: Check rapier_testbed3d

.typos.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ extend-ignore-re = [
1818
[default.extend-identifiers]
1919
anc_color = "anc_color"
2020
rady = "rady"
21+
iit_softfoot = "iit_softfoot"
22+
FoV = "FoV"
23+
# Shepperd's method: named after S. W. Shepperd (quaternion extraction).
24+
Shepperd = "Shepperd"
2125

2226
# Case insensitive, matches inside word.
2327
[default.extend-words]

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
- Update `glamx` to 0.3.
5757
- Updated `wide` version to `1` ([#902](https://github.com/dimforge/rapier/issues/902)).
5858

59-
## v0.31.0 (09 Jan. 2026)
59+
## v0.32.0 (09 Jan. 2026)
6060

6161
### Modified
6262

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ members = [
1313
"examples3d-f64",
1414
"crates/rapier3d-urdf",
1515
"crates/rapier3d-meshloader",
16+
"crates/mjcf-rs",
17+
"crates/rapier3d-mjcf",
1618
]
1719
resolver = "2"
1820

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,21 @@ The easiest way to get started with Rapier is to:
4949
Their source code are available on the `examples2d/` and `examples3d/` directory.
5050
3. Don't hesitate to ask for help on [Discord](https://discord.gg/vt9DJSW), or by opening an issue on GitHub.
5151

52+
## AI coding disclaimer and policy
53+
54+
AI coding is extensively used for the implementation and maintenance of the following crates: `mjcf-rs`, `rapier3d-mjcf`.
55+
56+
We actively use AI assistance (with human reviews) for the following tasks:
57+
- Documentation generation.
58+
- Changelogs generation.
59+
- Tests generation.
60+
- CI configuration and scripts.
61+
62+
We accept contributions involving AI coding as long as:
63+
- They are verified to work properly by a human.
64+
- The code quality is up to human-written code standards.
65+
- Include non-regression tests whenever applicable (which itself can be AI-generated).
66+
5267
## Resources and discussions
5368

5469
- [Dimforge](https://dimforge.com): See all the open-source projects we are working on! Follow our announcements

assets/3d/agility_cassie/cassie.xml

Lines changed: 282 additions & 0 deletions
Large diffs are not rendered by default.

assets/3d/agility_cassie/scene.xml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<mujoco model="cassie scene">
2+
<include file="cassie.xml"/>
3+
4+
<statistic center="0 0 0.55" extent="1.1"/>
5+
6+
<visual>
7+
<headlight diffuse="0.6 0.6 0.6" ambient="0.3 0.3 0.3" specular="0 0 0"/>
8+
<rgba haze="0.15 0.25 0.35 1"/>
9+
<global azimuth="150" elevation="-20"/>
10+
</visual>
11+
12+
<asset>
13+
<texture type="skybox" builtin="gradient" rgb1="0.3 0.5 0.7" rgb2="0 0 0" width="512" height="3072"/>
14+
<texture type="2d" name="groundplane" builtin="checker" mark="edge" rgb1="0.2 0.3 0.4" rgb2="0.1 0.2 0.3"
15+
markrgb="0.8 0.8 0.8" width="300" height="300"/>
16+
<material name="groundplane" texture="groundplane" texuniform="true" texrepeat="5 5" reflectance="0.2"/>
17+
</asset>
18+
19+
<worldbody>
20+
<light pos="0 0 3" dir="0 0 -1" directional="false"/>
21+
<geom name="floor" size="0 0 .125" type="plane" material="groundplane" conaffinity="15" condim="3"/>
22+
</worldbody>
23+
</mujoco>

crates/mjcf-rs/Cargo.toml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
[package]
2+
name = "mjcf-rs"
3+
version = "0.1.0"
4+
authors = ["Sébastien Crozet <sebcrozet@dimforge.com>"]
5+
description = "Pure-Rust parser for the MuJoCo MJCF XML format."
6+
documentation = "https://docs.rs/mjcf-rs"
7+
homepage = "https://rapier.rs"
8+
repository = "https://github.com/dimforge/rapier"
9+
readme = "README.md"
10+
categories = [
11+
"parser-implementations",
12+
"science",
13+
"simulation",
14+
]
15+
keywords = ["mjcf", "mujoco", "robotics", "parser", "xml"]
16+
license = "Apache-2.0"
17+
edition = "2024"
18+
19+
[features]
20+
default = []
21+
## Enable parsing MuJoCo's custom binary `.msh` mesh format.
22+
msh = ["dep:byteorder"]
23+
24+
[dependencies]
25+
roxmltree = "0.20"
26+
thiserror = "2"
27+
log = "0.4"
28+
byteorder = { version = "1", optional = true }
29+
glamx = { workspace = true, features = ["std", "f64"] }
30+
31+
[dev-dependencies]
32+
tempfile = "3"

crates/mjcf-rs/README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
## MJCF parser for Rust
2+
3+
> **Disclaimer.** Most of this crate — source, tests, and documentation — was
4+
> produced by an AI coding assistant working iteratively from MJCF reference
5+
> scenes (primarily the [MuJoCo Menagerie](https://github.com/google-deepmind/mujoco_menagerie)),
6+
> under human direction and review.
7+
8+
`mjcf-rs` is a pure-Rust parser for the [MuJoCo XML format (MJCF)](https://mujoco.readthedocs.io/en/stable/XMLreference.html).
9+
10+
It produces a typed AST that mirrors MJCF 1:1 in element names, with all
11+
preprocessing (`<include>` resolution, `<default>` class inheritance, angle
12+
unit normalization) already done — so consumers don't need to revisit the
13+
spec for any of those.
14+
15+
This crate has **no dependency** on `rapier3d` or any physics engine. Pair
16+
it with [`rapier3d-mjcf`](../rapier3d-mjcf) to actually simulate MJCF
17+
models.
18+
19+
### Status
20+
21+
See [`rapier3d-mjcf`'s feature matrix](../rapier3d-mjcf/README.md) for an
22+
up-to-date, phase-by-phase list of what's supported.

crates/mjcf-rs/src/assets.rs

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
//! `<asset>` library: meshes, hfields, textures, materials.
2+
3+
use crate::Pose;
4+
5+
/// Container for all assets defined in a model (and its includes).
6+
#[derive(Default, Clone, Debug)]
7+
pub struct Assets {
8+
/// `<mesh>` definitions, keyed by name.
9+
pub meshes: Vec<Mesh>,
10+
/// `<hfield>` definitions, keyed by name.
11+
pub hfields: Vec<Hfield>,
12+
/// `<texture>` definitions (recorded only — not loaded).
13+
pub textures: Vec<Texture>,
14+
/// `<material>` definitions (recorded only).
15+
pub materials: Vec<Material>,
16+
}
17+
18+
impl Assets {
19+
/// Looks up a `<mesh>` by name.
20+
pub fn mesh(&self, name: &str) -> Option<&Mesh> {
21+
self.meshes.iter().find(|m| m.name.as_deref() == Some(name))
22+
}
23+
24+
/// Looks up a `<hfield>` by name.
25+
pub fn hfield(&self, name: &str) -> Option<&Hfield> {
26+
self.hfields
27+
.iter()
28+
.find(|h| h.name.as_deref() == Some(name))
29+
}
30+
31+
/// Looks up a `<material>` by name.
32+
pub fn material(&self, name: &str) -> Option<&Material> {
33+
self.materials
34+
.iter()
35+
.find(|m| m.name.as_deref() == Some(name))
36+
}
37+
38+
/// Looks up a `<texture>` by name.
39+
pub fn texture(&self, name: &str) -> Option<&Texture> {
40+
self.textures
41+
.iter()
42+
.find(|t| t.name.as_deref() == Some(name))
43+
}
44+
}
45+
46+
/// `<mesh>` asset. Either references a file or carries inline vertex/face data.
47+
#[derive(Default, Clone, Debug)]
48+
pub struct Mesh {
49+
/// Asset name.
50+
pub name: Option<String>,
51+
/// Class for default lookup.
52+
pub class: Option<String>,
53+
/// Mesh file path (relative to `meshdir` / model dir).
54+
pub file: Option<String>,
55+
/// Per-axis scale (default `[1, 1, 1]`).
56+
pub scale: [f64; 3],
57+
/// Reference pose applied to the source mesh before use.
58+
pub refpose: Pose,
59+
/// Inline vertex array (alternative to `file`). Flat `[x0, y0, z0, x1, …]`.
60+
pub inline_vertices: Option<Vec<f64>>,
61+
/// Inline normals. Optional even when `inline_vertices` is set.
62+
pub inline_normals: Option<Vec<f64>>,
63+
/// Inline triangle indices.
64+
pub inline_faces: Option<Vec<u32>>,
65+
/// `inertia` attribute: `"shell"`, `"convex"` (default), or `"exact"`.
66+
pub inertia: MeshInertia,
67+
/// Maximum number of vertices when computing the convex hull.
68+
pub max_hull_vert: Option<u32>,
69+
/// `smoothnormal` (visualisation only — recorded).
70+
pub smoothnormal: f64,
71+
}
72+
73+
/// `<mesh inertia>` selector.
74+
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
75+
pub enum MeshInertia {
76+
/// Surface integral.
77+
Shell,
78+
/// Convex-hull interior (default).
79+
#[default]
80+
Convex,
81+
/// Tetrahedral integration over all triangles.
82+
Exact,
83+
/// Treat as a point mass at the geometric centre.
84+
Legacy,
85+
}
86+
87+
/// `<hfield>` heightfield asset.
88+
#[derive(Default, Clone, Debug)]
89+
pub struct Hfield {
90+
/// Asset name.
91+
pub name: Option<String>,
92+
/// Class.
93+
pub class: Option<String>,
94+
/// Number of grid rows.
95+
pub nrow: u32,
96+
/// Number of grid columns.
97+
pub ncol: u32,
98+
/// `(radius_x, radius_y, elevation_z, base_z)`.
99+
pub size: [f64; 4],
100+
/// File path (PNG / `.hfield`).
101+
pub file: Option<String>,
102+
/// Inline elevation data (`nrow * ncol` values, row-major).
103+
pub elevation: Option<Vec<f64>>,
104+
}
105+
106+
/// `<texture>` asset (recorded only).
107+
#[derive(Default, Clone, Debug)]
108+
pub struct Texture {
109+
/// Asset name.
110+
pub name: Option<String>,
111+
/// Asset class.
112+
pub class: Option<String>,
113+
/// Texture type (`2d`, `cube`, `skybox`).
114+
pub type_: Option<String>,
115+
/// File path.
116+
pub file: Option<String>,
117+
/// `builtin` (`"none"`, `"gradient"`, `"checker"`, `"flat"`).
118+
pub builtin: Option<String>,
119+
/// Primary RGB.
120+
pub rgb1: Option<[f64; 3]>,
121+
/// Secondary RGB.
122+
pub rgb2: Option<[f64; 3]>,
123+
}
124+
125+
/// `<material>` asset (recorded only).
126+
#[derive(Default, Clone, Debug)]
127+
pub struct Material {
128+
/// Asset name.
129+
pub name: Option<String>,
130+
/// Asset class.
131+
pub class: Option<String>,
132+
/// Texture reference.
133+
pub texture: Option<String>,
134+
/// `rgba`.
135+
pub rgba: Option<[f64; 4]>,
136+
/// `emission`.
137+
pub emission: f64,
138+
/// `specular`.
139+
pub specular: f64,
140+
/// `shininess`.
141+
pub shininess: f64,
142+
/// `roughness`.
143+
pub roughness: f64,
144+
/// `metallic`.
145+
pub metallic: f64,
146+
}

0 commit comments

Comments
 (0)