4. 刚体与碰撞体:定义 body、设置位置/姿态、使用 geom 创建形状
好,咱们进入 MuJoCo 模型里最核心的实体——刚体。说白了,你场景里所有会动、会撞的东西,都得靠 body 和 geom 搭出来。我刚开始学的时候,老把这两个概念搞混,后来踩了几个坑才明白:body 是骨架,geom 是皮肉。
4.1 body:刚体的骨架
每个 body 定义了一个刚体。它有自己的位置、姿态,还能挂子节点。你可以把它想象成一个空壳子,本身没有形状,但能承载 geom、joint、site 等元素。
关键点:一个 body 就是一个独立的运动学链节。它要么是固定的(没有 joint),要么通过 joint 连接到父 body。
4.1.1 body 的位置与姿态
位置用 pos 属性,姿态用 euler 或 quat 指定。我习惯用欧拉角,直观。但注意,MuJoCo 的欧拉角顺序是 XYZ(绕固定轴),不是某些引擎的 ZYX。
<body name="base" pos="0 0 0.5" euler="0 0 0">
<!-- 子元素 -->
</body>
这里 pos="0 0 0.5" 表示 body 原点在世界坐标系中抬高 0.5 米。euler="0 0 0" 就是没旋转。
我的习惯:尽量把 body 的原点设在它的质心或几何中心。这样后续加 joint、调惯性参数都方便。我在一个机械臂项目里吃过亏——原点偏了,算出来的关节力矩全是错的。
4.1.2 body 的层级结构
body 可以嵌套。子 body 的位置和姿态是相对于父 body 的。这就像你手臂的上臂、前臂、手掌——每个都是独立的 body,层层相连。
<body name="arm" pos="0 0 0">
<body name="forearm" pos="0.3 0 0">
<body name="hand" pos="0.25 0 0">
</body>
</body>
</body>
这样,你移动 arm,整个手臂都跟着动。但 hand 的位置只相对于 forearm。
4.2 geom:碰撞体的形状
geom 是几何体的缩写。它定义了刚体的碰撞形状和视觉外观。一个 body 可以挂多个 geom,但通常一个就够了——除非你需要组合复杂形状。
MuJoCo 内置了四种基本形状:box、sphere、cylinder、mesh。我一个个说。
4.2.1 box(长方体)
最常用的形状。用 size 指定半边长(注意是半边长!)。
<geom type="box" size="0.1 0.2 0.05" pos="0 0 0" />
这个 geom 在 X 方向半长 0.1 米,Y 方向 0.2 米,Z 方向 0.05 米。实际尺寸是 0.2×0.4×0.1 米。
注意:size 给的是半边长,不是全长。我见过有人把 box 的 size 写成 "0.5 0.5 0.5",以为是个 0.5 米的立方体,结果出来是 1 米的。嗯,这个坑我踩过。
4.2.2 sphere(球体)
简单,size 只用一个值——半径。
<geom type="sphere" size="0.1" pos="0 0 0" />
半径 0.1 米。球体在碰撞检测里计算最快,能用球体就别用复杂形状。
4.2.3 cylinder(圆柱体)
需要两个参数:半径和半高。
<geom type="cylinder" size="0.05 0.2" pos="0 0 0" />
这里半径 0.05 米,半高 0.2 米(总高 0.4 米)。圆柱的轴线默认沿 Z 轴。
小技巧:如果你想让圆柱横着放(轴线沿 X 轴),可以旋转 geom 或 body。我一般旋转 geom 本身:euler="0 1.57 0"(绕 Y 轴转 90 度)。
4.2.4 mesh(网格)
当基本形状不够用时,就用 mesh。你需要先加载一个 STL 或 OBJ 文件,然后在 geom 里引用。
<asset>
<mesh name="robot_arm" file="arm.stl" />
</asset>
<body>
<geom type="mesh" mesh="robot_arm" pos="0 0 0" />
</body>
mesh 的碰撞计算最慢,但精度最高。我一般只在视觉模型或复杂零件上用 mesh,简单碰撞体还是用基本形状。
4.3 geom 的公共属性
不管什么形状,都有几个重要属性要设。
| 属性 | 说明 | 常用值 |
|---|---|---|
type |
形状类型 | box, sphere, cylinder, mesh |
size |
尺寸参数(半边长/半径/半高) | 三个或一个浮点数 |
pos |
相对于 body 原点的位置 | "x y z" |
euler |
相对于 body 的姿态(欧拉角) | "x y z"(度) |
rgba |
颜色(渲染用) | "r g b a" |
friction |
摩擦系数(滑动、扭转、滚动) | "0.5 0.005 0.0001" |
solimp/solref |
接触刚度与阻尼参数 | 见第 6 章 |
摩擦系数有三个值:滑动摩擦、扭转摩擦、滚动摩擦。默认是 "1 0.005 0.0001"。我调机器人抓取时,经常把滑动摩擦改成 0.8~1.2,看实际效果。
4.4 实战:搭一个简单的刚体系统
咱们来写一个完整的例子:一个底座(box)上放一个球(sphere),旁边立一根柱子(cylinder)。
<mujoco model="simple_rigid_body">
<worldbody>
<body name="base" pos="0 0 0.1">
<geom type="box" size="0.3 0.3 0.1" rgba="0.8 0.2 0.2 1"/>
</body>
<body name="ball" pos="0 0 0.35">
<geom type="sphere" size="0.15" rgba="0.2 0.6 0.8 1"/>
</body>
<body name="pillar" pos="0.5 0 0.3">
<geom type="cylinder" size="0.05 0.3" rgba="0.3 0.8 0.3 1"/>
</body>
</worldbody>
</mujoco>
注意:ball 的 pos 是 "0 0 0.35",因为底座顶面在 Z=0.2 处,球半径 0.15,所以球心在 0.2+0.15=0.35。嗯,位置要算清楚,不然物体会悬空或穿模。
经验之谈:写模型时,先在脑子里过一遍坐标。我习惯画个草图,标出每个 body 的原点和 geom 的偏移。别偷懒,这一步能省你后面大量调试时间。
4.5 知识结构图
下面这张图帮你理清 body 和 geom 的关系:
4.6 避坑指南
- geom 穿透:两个 geom 初始位置重叠,仿真一开始就炸。检查 pos 和 size 是否算对。
- body 忘加 joint:没有 joint 的 body 是固定的。如果你想让物体动,记得加 joint。
- mesh 文件路径:MuJoCo 默认在模型文件所在目录找 mesh。路径不对就报错。我习惯把 mesh 放
assets/子目录,然后在 XML 里写相对路径。 - 摩擦系数太大:滑动摩擦超过 1.0 时,物体可能粘在地上推不动。先设 0.5 试试。
我曾经调一个四足机器人,后腿的 geom 位置写错了,导致机器人一直往一边歪。查了整整一天,最后发现是 pos 的 Y 坐标多了 0.01 米。嗯,小数点后第二位,也能让你崩溃。
好了,body 和 geom 的基本用法就这些。你动手写几个模型试试,很快就能上手。记住:先搭骨架(body),再贴皮肉(geom),顺序别搞反。