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 的关系:

刚体与碰撞体结构图 worldbody(世界根) body(刚体骨架) pos(位置) euler/quat(姿态) 子 body(嵌套) geom(碰撞体/视觉体) box sphere cylinder mesh 核心关系:body 定义运动学框架,geom 定义碰撞/视觉形状 一个 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),顺序别搞反。