辅助对象_碰撞体

0 阅读3分钟

课程链接:www.bilibili.com/cheese/play…

代码链接:github.com/buglas/robo…

课程目标

  • 创建碰撞体对象
  • 控制碰撞体的可见性

1-碰撞体的概念

碰撞体就是用于做碰撞检测的物体。

在Web前端,一般只会将碰撞体可视化,不会做碰撞检测。

在URDF 中,碰撞体标签是,它存在于 中。

<link name="base">
  <inertial>
    <origin rpy="0.0 0.0 0.0" xyz="0.0 0.0 0.0"/>
    <mass value="0.01"/>
    <inertia ixx="0.0001" ixy="0.0" ixz="0.0" iyy="0.0001" iyz="0.0" izz="0.0001"/>
  </inertial>
  <visual>
    <origin rpy="0 0 0" xyz="0 0 0"/>
    <geometry>
      <box size="0.001 0.001 0.001"/>
    </geometry>
  </visual>
  <collision>
      <geometry>
          <box size="0.001 0.001 0.001"/>
      </geometry>
  </collision>
</link>

中的 定义了碰撞体的几何体。

中的几何体可以分成2大类:

  • 隐性几何体,如box、sphere、cylinder

      <geometry>
        <box size="0.001 0.001 0.001"/>
      </geometry>
      <geometry>
        <sphere radius="0.0065" />
      </geometry>
      <geometry>
        <cylinder length="0.13" radius="0.053"/>
      </geometry>
    
  • 显性几何体,即mesh,如:

    <geometry>
        <mesh filename="" scale="1e-3 1e-3 1e-3"/>
    </geometry>
    

2-geometry 几何体的解析

中的和 中的 解析是一样的,咱们再回顾一下相关代码。

  • src/robot/URDFLoader.ts
/* 解析geometry */
function processGeometry(
  geometryNode: Element,
  material: Material,
  parent: Group,
) {
  const geometryChildNode = geometryNode.children[0];
  const geoType = geometryNode.children[0].nodeName.toLowerCase();
  switch (geoType) {
    case "mesh":
      processMesh(geometryChildNode, material, parent);
      break;
    case "box":
      processBox(geometryChildNode, material, parent);
      break;
    case "sphere":
      processSphere(geometryChildNode, material, parent);
      break;
    case "cylinder":
      processCylinder(geometryChildNode, material, parent);
      break;
  }
}

// 解析 <mesh filename="" scale="1e-3 1e-3 1e-3"/>
function processMesh(
  meshNode: Element,
  material: Material,
  parent: Group
) {
  const { meshParsers } = _this;
  // 模型路径
  const filename = meshNode.getAttribute("filename");
  if(!filename){return}
  let filePath = _this.resolveSubPath(filename,urdfPath);
  if (!filePath) {
    return;
  }

  // 模型文件的格式
  const suffix = filePath.split(".").pop()?.toLowerCase();
  const meshParser=meshParsers[suffix||'']
  if (meshParser) {
    // 模型解析方法
    meshParser(filePath, material).then((obj) => {
      if (!obj) {
        return;
      }
      // 模型缩放
      const scaleAttr = meshNode.getAttribute("scale");
      if (scaleAttr) {
        const [x, y, z] = processTuple(scaleAttr);
        obj.scale.multiply(new Vector3(x, y, z));
      }
      // 将模型添加到visual图形
      parent.add(obj);
    });
  } else {
    console.warn(`无法解析以 ${suffix} 为后缀的模型.`);
  }
}

// 解析<box size="0.224 0.08 0.071"/>
function processBox(boxNode: Element, material: Material, parent: Group) {
  const [x, y, z] = processTuple(boxNode.getAttribute("size"));
  const boxMesh = new Mesh(new BoxGeometry(x, y, z), material);
  parent.add(boxMesh);
}

// 解析<sphere radius="0.0065" />
function processSphere(
  sphereNode: Element,
  material: Material,
  parent: Group
) {
  const radius = parseFloat(sphereNode.getAttribute("radius") || "0");
  const sphereMesh = new Mesh(new SphereGeometry(radius, 8, 6), material);
  parent.add(sphereMesh);
}

// 解析<cylinder length="0.13" radius="0.053"/>
function processCylinder(
  cylinderNode: Element,
  material: Material,
  parent: Group
) {
  const radius = parseFloat(cylinderNode.getAttribute("radius") || "0");
  const length = parseFloat(cylinderNode.getAttribute("length") || "0");
  const cylinderMesh = new Mesh(
    new CylinderGeometry(radius, radius, length, 6),
    material
  );
  cylinderMesh.rotation.set(Math.PI / 2, 0, 0);
  parent.add(cylinderMesh);
}

3-在Link 对象中创建碰撞体

碰撞体是添加到其所在父级link 对象中的。

  • src/robot/URDFLoader.ts
// 碰撞体材质
const collisionMaterial = new MeshStandardMaterial({
  color: 0xff0000,
  depthTest: false,
  depthWrite: false,
  fog: false,
  toneMapped: false,
  transparent: true,
  opacity: 0.7,
});

class URDFLoader {
    //...
    /* 解析<collision>
      <collision>
        <origin xyz="0.02 0 0" rpy="0 1.5707963267948966192313216916398 0"/>
        <geometry>
          <cylinder radius="0.01" length="0.02"/>
        </geometry>
      </collision>
    */
    function processCollision(
      collisionNode: Element,
      urdfLink: Group,
    ) {
      const linkName = urdfLink.name;
      const collisionHelper = new Group();
      collisionHelper.visible = false;
      processOriginAndGeometry(
        collisionNode,
        collisionMaterial,
        collisionHelper,
      );
      Object.assign(collisionHelper.userData, {
        isURDFHelper: true,
        helperType: "collisionHelper",
      });
      urdfLink.add(collisionHelper);
      collisionMap.set(linkName, collisionHelper);
    }

    /* 解析包含<origin>和<geometry>的元素,比如<visual>和<collision>*/
    function processOriginAndGeometry(
      node: Element,
      material: Material,
      parent: Group,
    ) {
      const visualChildren = Array.from(node.children);
      visualChildren.forEach((childNode) => {
        const type = childNode.nodeName.toLowerCase();
        if (type === "geometry") {
          processGeometry(childNode, material, parent);
        } else if (type === "origin") {
          const { xyz, rpy } = processOrigin(childNode);
          parent.position.set(xyz[0], xyz[1], xyz[2]);
          applyEulerZYX(parent, rpy);
        }
      });
    }
}

4-查看碰撞体

因为我们之前已经在URDFHelperControl 中架构好了辅助对象的显示逻辑,所以我们不需要再专门为其它辅助对象做什么。

由于PR2 模型是没有碰撞体的,所以我使用宇树H1模型查看碰撞体。

/* 机器人可视化 */
const hdrURL = "/texture/venice_sunset_1k.hdr";
const urdfURL = './models/h1_2_description/h1_2.urdf'
// const urdfURL = "./models/PR2/urdf/PR2.urdf";
let robotVisual = new RobotVisual(hdrURL);
const {tipStyle, tipMsg, urdfDragControls } = robotVisual;

// 辅助控制
const formControl = new URDFFormControl();
const { AllMaps, currentMapKey, currentMapEles } = formControl;

// 机器人
let robot: URDFRobot;
// 加载URDF模型
const urdfLoader= robotVisual.loadURDF(urdfURL,(model:URDFRobot)=>{
  robot = model;
  formControl.setRobot(model)
});

// 重写PR2 资源路径解析方法
/* urdfLoader.resolveSubPath=(filename: string)=>{
  return filename.replace(
    "package://urdf_tutorial", 
    './models/h1_2_description'
  );
} */
urdfLoader.resolveSubPath=(filename: string)=>{
  return './models/h1_2_description/'+filename
}

效果如下:

image-20260124180343499

总结

碰撞体依旧还是对 标签的可视化,所以没有太多新的知识,大家理解其概念就好。

下一章我们会说质心的可视化。