- 物理引擎
- 游戏开发
- 机器人
【免费下载链接】rapier
2D and 3D physics engines focused on performance.
mjcf-rs是 Rapier 仓库中一个零物理引擎依赖的纯 Rust 解析器,用于读取 MuJoCo 的 MJCF XML 格式,并将其解析为与 MJCF 元素一一对应的类型化 AST。本文围绕该 crate 的定位、设计边界与完整实现展开,结合源码逐层讲解其<include>递归展开、<default>类继承、角度单位归一化等预处理能力,并给出与 rapier3d-mjcf 组合使用的实战路径,读者可据此将任何 MJCF 机器人模型直接接入 Rapier 物理引擎。
一、crate 定位:纯 Rust、类型化、零物理依赖
mjcf-rs的核心卖点在其 README 中表述得非常明确:它是一个pure-Rust parser for the MuJoCo XML format (MJCF),产物是一个与 MJCF 元素名称 1:1 对应的类型化 AST。所有需要“回头查规范”的预处理工作都已在解析阶段完成:
<include>递归展开(带环检测);<default>类继承解析(每个具体元素的最终属性被直接“烘焙”进 AST);- 角度单位归一化(MJCF 默认
<compiler angle="degree">,解析后统一为弧度); - 记录
<compiler eulerseq>,供后续转换侧按正确顺序应用欧拉三元组。
这些职责边界在 lib.rs 的文档注释中被明确划分为“做什么”与“不做什么”两部分。该 crate不依赖rapier3d或任何物理引擎——它只负责“读懂” MJCF 文件,真正的仿真由兄弟 crate rapier3d-mjcf 完成。
设计动机:解析与仿真的职责分离
从仓库结构(crates/mjcf-rs与crates/rapier3d-mjcf是两个独立 crate)可以看出,作者刻意将“格式理解”与“物理世界构建”解耦。这样带来的直接好处是:
mjcf-rs可以独立测试、独立发布、独立被其他项目复用(例如只做模型静态检查或转换工具);rapier3d-mjcf只需消费解析好的 AST,把精力集中在 MJCF 语义到 Rapier 原语(RigidBody、Collider、ImpulseJoint、Multibody)的映射上;- 两个 crate 都保持 pure Rust,不需要 libmujoco 或任何 FFI 绑定,在
no_std/WASM 等受限环境中也具备可移植潜力(从 Cargo.toml 的依赖清单roxmltree、thiserror、log、可选的byteorder、glamx可见,依赖面非常克制)。
二、快速上手:三行代码读入一个机器人模型
在 lib.rs 的 “Quick start” 中给出了最简用法:
use mjcf_rs::Model; let model = Model::from_file("robot.xml").unwrap(); println!("{} bodies", model.bodies_iter().count());解析入口有两个(见 loader/mod.rs):
Model::from_file(path):从文件路径读取 XML,<include>相对该文件所在目录解析,并把该目录作为资产(mesh/texture)解析的base_dir;Model::from_str(xml, base_dir):从字符串解析,<include>与资产路径都相对显式传入的base_dir。
两个入口都返回Result<Model, ParseError>,错误类型统一收敛在 error.rs 中(详见第七节)。
三、顶层 AST:扁平化的Model
mjcf-rs的 AST 不是 DOM 树嵌套结构,而是一个扁平化、完全解析后的视图。Model结构体(见 model.rs)包含:
| 字段 | 对应 MJCF 元素 | 说明 |
|---|---|---|
name | <mujoco model="…"> | 模型名 |
compiler | <compiler> | 已解析的编译期设置 |
option | <option> | timestep、gravity等仿真选项 |
assets | <asset> | mesh / hfield / texture / material 资产库 |
bodies | <worldbody>/<body> | 扁平Vec<BodyEntry>,索引 0 保留给隐式 world body |
contact | <contact> | pair 覆盖与 exclude 排除 |
equality | <equality> | connect / weld / joint / tendon 约束 |
actuators | <actuator> | 原样保留的驱动器元素 |
sensors | <sensor> | 传感器元素 |
keyframes | <keyframe><key> | 关键帧状态 |
tendons | <tendon><fixed> | 仅表示固定腱(线性关节坐标腱) |
扁平化的关键点在于BodyEntry { parent: Option<BodyId>, body: Body }:每个 body 都直接知道自己父体的BodyId,索引 0 的隐式 world body 的parent为None。这种设计让遍历、按名查找与后续转换都非常直接:
bodies_iter():迭代所有非 world body;body_id_by_name(name)、geom_by_name(name)、joint_by_name(name)、site_by_name(name):按名称查找,返回(BodyId, 索引)元组。
四、<compiler>与<option>:编译期设置的解析与默认值
MJCF 的行为高度依赖<compiler>块,mjcf-rs将其完整映射到 compiler.rs 的Compiler结构,解析逻辑在 loader/compiler_opts.rs。下表汇总了支持的属性、默认值与解析行为:
| 属性 | 默认值 | 解析行为 |
|---|---|---|
angle | "degree" | "degree"→ 角度属性乘以 π/180;"radian"→ 原样保留;解析后 AST 中所有角度均为弧度 |
eulerseq | "xyz" | 记录欧拉轴序字符串,供转换侧使用 |
coordinate | "local" | "global"是 MJCF 已废弃特性,直接返回Unsupported错误 |
autolimits | true(MJCF v3+) | 配合<joint limited="auto">使用 |
inertiadensity | 1000.0 | 推导惯量时使用的默认密度(kg/m³) |
inertiafromgeom | "auto" | true/false/auto三态 |
meshdir/texturedir/assetdir | 无 | 资产目录层级,assetdir作为回退 |
strippath | false | 从file=引用中剥离目录组件(兼容旧场景) |
discardvisual | false | 加载时丢弃仅视觉 geom(contype=conaffinity=0) |
convexhull | true | mesh geom 作为凸包而非三角网格 |
exactmeshinertia | false | 是否精确计算网格惯量 |
balanceinertia | false | 惯量对角化平衡 |
boundmass | 0.0 | 质量下限钳制 |
boundinertia | 0.0 | 惯量对角项下限钳制 |
settotalmass | -1.0 | 质量缩放目标(-1 表示禁用) |
<option>块目前只记录timestep(默认 0.002 秒)与gravity(默认[0, 0, -9.81]),保存在SimOption中(见 compiler.rs)。
值得注意的是,parse_compiler对未知的 compiler 属性采取“静默忽略”策略(源码注释明确写了 “Silently ignore other compiler options (not load-relevant)”),这对向前兼容很重要——未来 MJCF 规范新增的编译选项不会导致旧版解析器崩溃。
五、核心 AST:Body、Joint、Geom、Site、Inertial
5.1 Body(body.rs)
Body覆盖了<body>的完整属性面:name、pose(局部于父系)、mocap、gravcomp(重力补偿系数)、sleep、childclass(子元素默认类)、class、user透传数据,以及四类子元素容器:
inertial: Option<Inertial>;joints: Vec<Joint>(含<freejoint>);geoms: Vec<Geom>;sites: Vec<Site>。
5.2 Inertial 与惯量规格
Inertial记录质量mass与惯量。惯量有两种表示(InertiaSpec枚举):
Diagonal([f64; 3]):对应diaginertia="ixx iyy izz";Full([f64; 6]):对应fullinertia="ixx iyy izz ixy ixz iyz"。
5.3 Joint 与 JointType
Joint结构体完整覆盖 MJCF 关节属性,含type_、pos、axis、limited(三态)、range、stiffness、damping、springref、springdamper(时间常数/阻尼比二元组)、armature、frictionloss、ref_、margin等。JointType枚举四种自由度:
| 类型 | DoF | 说明 |
|---|---|---|
Hinge | 1 | 默认类型(#[default]) |
Slide | 1 | 棱柱副 |
Ball | 3 | 球副 |
Free | 6 | <freejoint>即编码为Joint { type_: Free, … } |
<joint limited="true|false|auto">的三态解析在 types.rs 的Tristate::resolve(autolimits, has_range)中完成:auto时若autolimits=true且声明了range才视为受限。
5.4 Geom 与 GeomType
Geom覆盖碰撞与视觉属性:type_、size、pose、fromto、friction(三元组,默认[1.0, 0.005, 0.0001])、mass/density覆盖、margin、contype/conaffinity(默认 1)、condim(默认 3)、group、priority、mesh/hfield/material引用、rgba。GeomType枚举包括:
Plane(半空间)、Hfield(高度场)、Sphere(MJCF 默认)、Capsule(沿 Z 轴)、Ellipsoid(由 loader 近似)、Cylinder(沿 Z 轴)、Box、Mesh(三角网格)、Sdf(超出范围,loader 会告警)。
5.5 Site
Site被解析并保留,但loader 不为 site 创建任何物理对象——site 本质上是命名的参考坐标系(body.rs注释原文:sites are pure named frames),通常用于传感器锚点、控制参考点。
六、<asset>资产库:Mesh、Hfield、Texture、Material
assets.rs 定义了Assets容器与四类资产结构:
Mesh:支持file引用或内联顶点/法线/面数据(vertex/normal/face属性),带scale、refpose、inertia策略与maxhullvert限制;Hfield:nrow × ncol网格、size=(radius_x, radius_y, elevation_z, base_z)、PNG 文件或内联elevation数据;Texture与Material:目前仅记录不加载(recorded only),服务于视觉信息透传。
MeshInertia枚举对应<mesh inertia>的四种策略:Shell(表面积分)、Convex(凸包内部,默认)、Exact(四面体积分)、Legacy(几何中心点质量)。
一个对真实场景兼容至关重要的细节(见 loader/mod.rs 的测试asset_name_defaults_to_file_stem):当<mesh>/<hfield>/<texture>没有name=但有file=时,资产以文件基名(去扩展名)注册,这样<geom mesh="hip">能解析<mesh file="hip.obj"/>。该行为是 muJoCo 官方场景(如 unitree_a1)的回归修复。
七、错误处理:面向使用者的ParseError
error.rs 使用thiserror定义了一个语义清晰的错误枚举,每个变体都携带足够定位信息:
| 变体 | 触发场景 |
|---|---|
Xml | XML 解析失败(包装roxmltree::Error) |
Io | 读取文件(或<include>目标)失败,带失败路径 |
BadAttribute | 属性无法解析(如非浮点数) |
BadAttributeValue | 属性值非法(如angle="grad") |
BadInclude | <include>文件缺失或形成包含环(报出当前导入栈) |
UnknownClass | 元素引用了不存在的<default class> |
Unsupported | 已解析但本版本 loader 不支持的特性(如coordinate="global"),带可选提示 |
InvalidModel | 模型级错误(如根元素不是<mujoco>) |
八、解析管线:多阶段处理与<include>展开
8.1 两阶段遍历设计
ParseState::process_root_children(见 loader/state.rs)揭示了 loader 的核心架构——按依赖关系分阶段处理顶层元素:
- 阶段 1:
<compiler>、<option>、<default>、<contact>、<equality>、<tendon>; - 阶段 1b:
<asset>延后到所有同文件<default>注册之后再解析——因为 MJCF 允许<asset>出现在<default>之前(如 robotiq_2f85 的 mesh 资产先于设置其scale的 defaults 声明),而 mesh 需要按其 default class 解析scale/inertia; - 阶段 2:
<worldbody>、<actuator>、<sensor>、<keyframe>——这些需要完整的<default>/<asset>表。
8.2<include>递归与环检测
parse_include维护一个include_stack,每次展开前用canonicalize()后的绝对路径做环检测,一旦发现重复导入立即返回BadInclude错误(消息中附带当前导入栈便于排查)。被包含文件同样必须具有<mujoco>根元素,其顶层子元素通过递归调用process_root_children与主文件内容无缝合并(defaults、assets 全部并入同一状态机),base_dir在递归期间切换到被包含文件所在目录、返回后恢复。
8.3<default>类继承
loader/defaults.rs 实现了完整的 MJCF 类继承语义:
- 无
class属性的<default>注册为"main",嵌套<default class="…">记录父类链(defaults_parent_of); class_chain从最具体的类一路回溯到"main";- 每个具体元素(joint/geom/site/material/mesh/pair/equality/actuator)在实例化时通过
merged_*_proto从最一般到最具体逐层合并原型,实例属性覆盖类默认属性。
源码中defaults.rs的parse_default对每个元素类型分别维护原型槽位,例如 actuator 按general/motor/position/velocity/intvelocity/damper分槽合并,保证<default><motor kp="…"/></default>只影响 motor 类 actuator。
测试parse_default_inheritance(见 loader/mod.rs)验证了:主类<joint damping="0.5" type="hinge"/>+ 子类<default class="leg"><joint damping="0.3" range="-30 30"/></default>合并后,使用class="leg"的关节得到damping=0.3、range=±30°(且已转弧度),而未用类的关节得到damping=0.5、无 range。测试default_geom_propagates_mesh_and_hfield则验证<default class="link_visual"><geom mesh="link_proto"/></default>能把 mesh 引用传播给裸<geom class="link_visual"/>实例(这是 wonik_allegro 手部场景的回归修复)。
8.4<attach>子模型拼接
<asset><model name="X" file="Y.xml"/>+<attach model="X" body="…" prefix="A_"/>是 MuJoCo 的组合建模机制(如 iit_softfoot 场景)。loader/attach.rs 实现完整拼接:
- 子模型先独立解析,其资产
file=被重写为绝对路径(避免并入父模型后因meshdir不同而失效); <attach>将子模型 body 子树深拷贝到当前父体下,所有名字(body/joint/geom/site)与外部引用(mesh、material、equality 的 body1/body2、contact pair、actuator 的 joint/tendon、sensor 的 objname/refname)统一加前缀,防止命名空间冲突;- 子模型的 equality、contact、actuator、sensor 一并迁移。
测试attach_splices_sub_model_with_prefix完整验证了该流程:rig下挂载A_attach_point、A_tip,关节A_hinge/A_slider,资产A_brick,equality weld 的body1也被改写成A_attach_point。
九、跨切面模块:contact、equality、tendon、extras
9.1 Contact(contact.rs)
<contact><pair>:按 geom 对覆盖condim、friction、margin、gap;<contact><exclude>:排除两个 body 之间的接触。
9.2 Equality(equality.rs)
四种约束变体:Connect(两点约束)、Weld(刚性固连,带可选relpose/anchor/torquescale)、Joint(两关节位置间四次多项式耦合:q2 − ref2 = p0 + p1·Δq + p2·Δq² + p3·Δq³ + p4·Δq⁴,省略joint2时退化为将joint1约束在常数p0)、Tendon(同理的腱长耦合)。每个变体共享EqualityCommon { name, class, active }元数据。
9.3 Tendon(tendon.rs)
仅表示固定腱:FixedTendon = Σ coefᵢ·qᵢ的关节坐标线性组合,用于驱动器传动与腱长耦合。site 路由的空间腱(spatial tendon)明确超出范围。
9.4 Actuator / Sensor / Keyframe(extras.rs)
Actuator保留驱动器的全部参数面(gear、ctrlrange、forcerange、gainprm/biasprm、gaintype/biastype、kp/kv等),ActuatorKind覆盖Motor/Position/Velocity/IntVelocity/Damper/General/Other;Sensor记录objtype/objname/reftype/refname/cutoff/noise;Keyframe记录qpos/qvel/act/ctrl/mpos/mquat完整状态数组。
十、可选特性:MuJoCo 二进制.msh网格解析
Cargo.toml 定义了唯一的可选 featuremsh(启用byteorder依赖),对应 msh.rs 中 MuJoCo 自定义二进制网格格式的解析器。文件布局为小端序:
i32 nvertex i32 nnormal i32 ntexcoord i32 nface f32[nvertex * 3] positions f32[nnormal * 3] normals f32[ntexcoord * 2] texcoords i32[nface * 3] triangle indicesMshMesh保留顶点、法线、纹理坐标与三角索引;注释明确指出碰撞只需 positions + faces,normals/texcoords 供需要者取用。parse(bytes)与parse_file(path)两个入口都返回std::io::Result<MshMesh>。
十一、与 rapier3d-mjcf 组合:从 AST 到可仿真的机器人
mjcf-rs本身不仿真(“What this crate doesnotdo: Simulate anything”,见 lib.rs)。完整工作流是与 rapier3d-mjcf 配合:
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);rapier3d-mjcf的 Cargo 特性与mjcf-rs的解析能力一一对应:stl(STL 网格)、wavefront(OBJ 网格)、msh(上述二进制网格,透传自mjcf-rs);.dae(Collada)被有意排除,因为 MJCF 不使用它。
仓库内置的示例 examples3d/mjcf3.rs 展示了完整用法:以assets/3d/agility_cassie/scene.xml为输入,设置make_roots_fixed: true与shift(Z-up 到 Y-up 的坐标系旋转),同一机器人分别用 impulse joints 与 multibody joints 两次插入物理世界。注意该示例直接使用了 MuJoCo Menagerie 场景资产(assets/3d/agility_cassie/目录下的cassie.xml与scene.xml)。
rapier3d-mjcfREADME 中维护着一份分阶段特性矩阵(当前仓库状态):
- Phase 1 核心运动学:
<mujoco>根、<compiler>(angle/eulerseq/coordinate=local)、body 位姿(pos/quat/axisangle/euler/xyaxes/zaxis)、四类关节、<inertial>、九类 geom 中的八类(ellipsoid 用 icosphere 凸包近似)、fromto形式、多关节 body 合成、make_roots_fixed/shift/scale/skip_plane_geoms等加载选项; - Phase 2 默认值/包含/mesh 资产:
<default>继承、<include>内联、<frame>位姿分组、mesh/hfield 碰撞体构建(背后依赖stl/wavefront/msh特性)、inertiafromgeom、autolimits、discardvisual、convexhull、strippath、meshdir/assetdir/texturedir; - Phase 3 接触过滤与等式约束:
contype/conaffinity到InteractionGroups的映射、<contact><exclude>、<contact><pair>、<equality><connect>、<equality><weld>(以 impulse joint 实现,即便模型其余部分用 multibody); - Phase 4 关节动力学:
damping/stiffness→ ForceBased JointMotor、springref、springdamper转换公式、armature转子惯量、frictionloss(速度上限电机近似)、gravcomp、boundmass/boundinertia/balanceinertia/settotalmass; - Phase 5 驱动器/传感器/关键帧:
apply_controls驱动 motor/position/velocity/damper、apply_keyframe应用完整状态、read_sensor读取状态可推导的传感器子集、<tendon><fixed>的关节坐标耦合(这正是 shadow hand 腱驱动手指联动的基础)。
rapier3d-mjcf亦明确列出超出范围项:<extension>插件、<flexcomp>/<skin>变形体、<composite>、<tendon><spatial>、cylinder/muscle/adhesion驱动器、需要接触面积分的传感器(touch/rangefinder/geomdist/camprojection)、<geom type="sdf">、coordinate="global"以及 MJCF 写回。
十二、版本与构建注意点
- crate 版本号跟随 workspace(
version.workspace = true),类别为parser-implementations/science/simulation,关键字mjcf/mujoco/robotics/parser/xml; - 依赖极简:
roxmltree(XML DOM)、thiserror(错误类型)、log(告警日志)、可选byteorder(仅msh特性)、glamx(f64精度的数学库,Pose/DQuat/DVec3均通过其再导出); - 位置/旋转全部使用f64 双精度(
glamx的DPose3),与机器人领域常见的毫米/弧度数值尺度匹配; Pose是glamx::DPose3的再导出,MJCF 的五种旋转规格(quat、axisangle、euler、xyaxes、zaxis)在 loader 中统一折叠为四元数;euler轴序解析(loader/parse_utils.rs 的quat_from_euler)支持 intrinsic(小写,默认)与 extrinsic(大写)两种语义。
十三、总结与适用边界
mjcf-rs以**“解析即预处理”**的设计哲学,把 MJCF 中最繁琐、最容易出错的语义(include 展开、default 继承、角度归一化、欧拉序)收敛在解析阶段,向消费方交付一个平坦、确定、可直接遍历的Model。它刻意保持纯解析定位、零物理引擎依赖,让单元测试(内嵌于 loader/mod.rs 的多组回归测试覆盖角度换算、类继承、资产默认命名、mesh/hfield 类传播、attach 拼接等关键路径)可以聚焦格式语义本身。
适用边界需要明确:它不仿真、不写回 MJCF(只读)、不覆盖coordinate="global"等废弃特性、空间腱与 SDF 等元素仅记录或告警。若目标是让 MJCF 模型跑起来,请以 rapier3d-mjcf 的特性矩阵为当前支持状态的权威参照。
- 物理引擎
- 游戏开发
- 机器人
【免费下载链接】rapier
2D and 3D physics engines focused on performance.
相关推荐
SO-ARM100 仿真模型解析:SO101 的 URDF 与 MuJoCo(MJCF)描述文件完全指南
SO ARM100 仿真模型解析:SO101 的 URDF 与 MuJoCo(MJCF)描述文件完全指南 本指南以 Simulation/SO101/READM
硬件开发机器人智能硬件具身智能人工智能Unitree H1 2 机器人描述(URDF & MJCF)解析:51 自由度关节树与 MuJoCo 可视化指南
Unitree H1 2 机器人描述(URDF & MJCF)解析:51 自由度关节树与 MuJoCo 可视化指南 本文以 unitree_rl_gym 仓库中
人工智能强化学习机器人具身智能MuJoCo中URDF模型导出为MJCF XML时的惯性参数问题解析
MuJoCo中URDF模型导出为MJCF XML时的惯性参数问题解析 引言 在使用MuJoCo物理引擎进行机器人仿真时,开发者经常需要将URDF格式的机器人模型
物理引擎机器人机器学习图形学
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考