use anyhow::{Context, Result};
use glam::{Mat4, Quat, Vec3};
use std::path::Path;
use tracing::info;
#[derive(Debug, Clone)]
pub struct LinkVisual {
pub link_name: String,
pub geometry: GeometryType,
pub local_transform: Mat4,
pub color: [f32; 3],
}
#[derive(Debug, Clone)]
pub enum GeometryType {
Box { size: [f32; 3] },
Cylinder { radius: f32, height: f32 },
Sphere { radius: f32 },
Capsule { radius: f32, length: f32 },
}
#[derive(Debug, Clone)]
pub struct JointInfo {
pub name: String,
pub parent_link: String,
pub child_link: String,
pub origin: Mat4,
pub axis: Vec3,
}
pub struct UrdfModel {
pub link_visuals: Vec<LinkVisual>,
pub joints: Vec<JointInfo>,
}
pub struct UrdfLoader;
impl UrdfLoader {
pub fn load<P: AsRef<Path>>(path: P) -> Result<UrdfModel> {
let path = path.as_ref();
info!("Loading URDF from {:?}", path);
let robot = urdf_rs::read_file(path)
.with_context(|| format!("Failed to load URDF from {:?}", path))?;
let mut link_visuals = Vec::new();
let mut joints = Vec::new();
let left_color = [0.2, 0.6, 0.8]; let right_color = [0.8, 0.4, 0.2]; let base_color = [0.5, 0.5, 0.5];
for link in &robot.links {
for visual in &link.visual {
let geometry = match &visual.geometry {
urdf_rs::Geometry::Box { size } => GeometryType::Box {
size: [size[0] as f32, size[1] as f32, size[2] as f32],
},
urdf_rs::Geometry::Cylinder { radius, length } => GeometryType::Cylinder {
radius: *radius as f32,
height: *length as f32,
},
urdf_rs::Geometry::Sphere { radius } => GeometryType::Sphere {
radius: *radius as f32,
},
urdf_rs::Geometry::Capsule { radius, length } => GeometryType::Capsule {
radius: *radius as f32,
length: *length as f32,
},
urdf_rs::Geometry::Mesh { .. } => {
continue;
}
};
let color = Self::get_link_color(&link.name, left_color, right_color, base_color);
let local_transform = Self::pose_to_mat4(&visual.origin);
link_visuals.push(LinkVisual {
link_name: link.name.clone(),
geometry,
local_transform,
color,
});
}
}
for joint in &robot.joints {
let origin = Self::pose_to_mat4(&joint.origin);
let axis = Vec3::new(
joint.axis.xyz[0] as f32,
joint.axis.xyz[1] as f32,
joint.axis.xyz[2] as f32,
);
joints.push(JointInfo {
name: joint.name.clone(),
parent_link: joint.parent.link.clone(),
child_link: joint.child.link.clone(),
origin,
axis,
});
}
info!(
"Loaded {} link visuals, {} joints",
link_visuals.len(),
joints.len()
);
Ok(UrdfModel {
link_visuals,
joints,
})
}
fn get_link_color(name: &str, left: [f32; 3], right: [f32; 3], base: [f32; 3]) -> [f32; 3] {
if name.starts_with("left") {
left
} else if name.starts_with("right") {
right
} else {
base
}
}
fn pose_to_mat4(pose: &urdf_rs::Pose) -> Mat4 {
let translation = Vec3::new(pose.xyz[0] as f32, pose.xyz[1] as f32, pose.xyz[2] as f32);
let roll = pose.rpy[0] as f32;
let pitch = pose.rpy[1] as f32;
let yaw = pose.rpy[2] as f32;
let rotation = Quat::from_euler(glam::EulerRot::XYZ, roll, pitch, yaw);
Mat4::from_rotation_translation(rotation, translation)
}
}