辅助对象_碰撞体

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

效果如下:

总结

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

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

相关推荐
孔令飞41 分钟前
Go中指针引用Go中指针引用Go中指针引用Go中指针引用Go中指针引用Go中指针引用Go中指针引用Go中指针引用Go中指针引用Go中指针引用Go中指针引用Go中
前端
溪语流沙1 小时前
【Web全栈进阶】PostgreSQL上手:Docker跑库 + 把早报站从SQLite迁过去
前端·docker·postgresql
IT_陈寒1 小时前
SpringBoot自动配置的坑:你以为的捷径可能是弯路
前端·人工智能·后端
paopaokaka_luck1 小时前
高校社团管理(AI辅助任务分配、协同过滤算法推荐、ECharts数据可视化、活动参与闭环、校园交流与反馈、器材借还管理)
java·前端·javascript·spring boot·数据分析·echarts
XMAIPC_Robot1 小时前
RK3576/RK3588+FPGA+AI数据采集系统:实时性与边缘AI算力兼顾
人工智能·fpga开发·机器人·数据采集·rk3588+fpga·rk3576+fpga
Dovis(誓平步青云)1 小时前
导览音频切换太快,旧讲解不能覆盖新展品
android·前端·javascript·ecmascript·音视频·宠物
海盗12341 小时前
AI 新闻日报 2026-10-05:三周重构 30 万行 / macOS 给智能体加权限锁 / 具身订单结构被摆上台面
人工智能·macos·重构·机器人·人工智能aigc
沧沧凉凉11 小时前
一段需求、零素材,Claude Code 派了 33 个代理给我做了个 3D 动作游戏
游戏·ai编程·three.js
福兮说12 小时前
设计稿是 #4A7C6F,页面量出来是 #4B7C6F:HEX、HSL、透明度、canvas 来回转的七个坑
前端·javascript·css·canvas
郑州光合科技余经理13 小时前
海外版外卖加盟:总站与分站配送规则怎么分开管
java·开发语言·前端·后端·uni-app·php·ai编程