辅助对象_碰撞体

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

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

课程目标

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

1-碰撞体的概念

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

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

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

xml 复制代码
<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

    xml 复制代码
      <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,如:

    xml 复制代码
    <geometry>
        <mesh filename="" scale="1e-3 1e-3 1e-3"/>
    </geometry>

2-geometry 几何体的解析

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

  • src/robot/URDFLoader.ts
ini 复制代码
/* 解析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);
}

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

  • src/robot/URDFLoader.ts
ini 复制代码
// 碰撞体材质
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模型查看碰撞体。

ini 复制代码
/* 机器人可视化 */
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
}

效果如下:

总结

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

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

相关推荐
yuanyxh9 小时前
Mac 软件推荐
前端·javascript·程序员
万少9 小时前
AtomCode开发微信小程序《谁去呀》 全流程
前端·javascript·后端
某人辛木9 小时前
Web自动化测试
前端·python·pycharm·pytest
云和数据.ChenGuang10 小时前
T5大模型
人工智能·机器人·pandas·数据预处理·数据训练
Kagol10 小时前
Superpowers GSD gstack AgentSkills深度测评
前端·人工智能
excel11 小时前
JavaScript 字符串与模板字面量:从表象到本质理解
前端
京东云开发者11 小时前
当AI成为导演-如何用AI创作动漫短剧
前端
李白的天不白12 小时前
使用 SmartAdmin 进行前后端开发
java·前端
乘风gg12 小时前
🤡PUA AI Coding 工具 的 10 条终极语录
前端·ai编程·claude
学Linux的语莫12 小时前
Vue 3 入门教程
前端·javascript·vue.js