辅助对象_碰撞体

课程链接: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
}

效果如下:

总结

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

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

相关推荐
heiyehk3 小时前
我用AI不断迭代到7W行代码的项目-像素画编辑器
前端·架构·设计
weedsfly4 小时前
前端懒加载全家桶:图片、路由、组件的按需加载
前端·javascript·面试
千帆_Evan4 小时前
🤖 AI 写代码总在重复造轮子?我们把 Utils 复用门禁做进了 Agent 运行时(已开源)
前端·agent
Kel4 小时前
多 Tab 聊天不翻车:纯前端实现跨标签页消息同步的三层协同架构
前端·javascript·人工智能
半个落月4 小时前
Promise 入门:理解 async/await 与 Promise.all
前端·javascript
廖菌4 小时前
DATASPACE3D 产品介绍:面向数字孪生与 Web3D 场景的一体化可视化搭建平台
前端·webgl
敖行客 Allthinker4 小时前
前端调试实战:DevTools调试工具 + 二分定位Bug法全总结
前端·typescript·chrome devtools
暗不需求4 小时前
手写深拷贝:从 JSON 到递归,彻底解决循环引用问题
前端·面试
灏仟亿前端技术团队4 小时前
DOMagic:把 AI 修改绑定到页面节点的前端开发态插件
前端
Lugas4 小时前
浏览器为什么要如此严格地限制跨域请求?
前端·黑客·浏览器