课程链接:www.bilibili.com/cheese/play…
课程目标
- 创建碰撞体对象
- 控制碰撞体的可见性
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
}
效果如下:
总结
碰撞体依旧还是对 标签的可视化,所以没有太多新的知识,大家理解其概念就好。
下一章我们会说质心的可视化。