#![warn(missing_docs)]
use na::RealField;
use rapier3d::{
dynamics::{
GenericJoint, GenericJointBuilder, ImpulseJointHandle, ImpulseJointSet, JointAxesMask,
JointAxis, MassProperties, MultibodyJointHandle, MultibodyJointSet, RigidBody,
RigidBodyBuilder, RigidBodyHandle, RigidBodySet, RigidBodyType,
},
geometry::{Collider, ColliderBuilder, ColliderHandle, ColliderSet, SharedShape, TriMeshFlags},
glamx::EulerRot,
math::{Pose, Real, Rotation, Vector},
na,
};
use std::collections::HashMap;
use std::path::Path;
use urdf_rs::{Geometry, Inertial, Joint, Pose as UrdfPose, Robot};
#[cfg(doc)]
use rapier3d::dynamics::Multibody;
bitflags::bitflags! {
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
pub struct UrdfMultibodyOptions: u8 {
const JOINTS_ARE_KINEMATIC = 0b0001;
const DISABLE_SELF_CONTACTS = 0b0010;
}
}
pub type LinkId = usize;
#[derive(Clone, Debug)]
pub struct UrdfLoaderOptions {
pub create_colliders_from_collision_shapes: bool,
pub create_colliders_from_visual_shapes: bool,
pub apply_imported_mass_props: bool,
pub enable_joint_collisions: bool,
pub make_roots_fixed: bool,
pub trimesh_flags: TriMeshFlags,
pub shift: Pose,
pub collider_blueprint: ColliderBuilder,
pub rigid_body_blueprint: RigidBodyBuilder,
}
impl Default for UrdfLoaderOptions {
fn default() -> Self {
Self {
create_colliders_from_collision_shapes: true,
create_colliders_from_visual_shapes: false,
apply_imported_mass_props: true,
enable_joint_collisions: false,
make_roots_fixed: false,
trimesh_flags: TriMeshFlags::all(),
shift: Pose::IDENTITY,
collider_blueprint: ColliderBuilder::default().density(0.0),
rigid_body_blueprint: RigidBodyBuilder::dynamic(),
}
}
}
#[derive(Clone, Debug)]
pub struct UrdfLink {
pub body: RigidBody,
pub colliders: Vec<Collider>,
}
#[derive(Clone, Debug)]
pub struct UrdfJoint {
pub joint: GenericJoint,
pub link1: LinkId,
pub link2: LinkId,
}
#[derive(Clone, Debug)]
pub struct UrdfRobot {
pub links: Vec<UrdfLink>,
pub joints: Vec<UrdfJoint>,
}
pub struct UrdfJointHandle<JointHandle> {
pub joint: JointHandle,
pub link1: RigidBodyHandle,
pub link2: RigidBodyHandle,
}
pub struct UrdfLinkHandle {
pub body: RigidBodyHandle,
pub colliders: Vec<ColliderHandle>,
}
pub struct UrdfRobotHandles<JointHandle> {
pub links: Vec<UrdfLinkHandle>,
pub joints: Vec<UrdfJointHandle<JointHandle>>,
}
impl UrdfRobot {
pub fn from_file(
path: impl AsRef<Path>,
options: UrdfLoaderOptions,
mesh_dir: Option<&Path>,
) -> anyhow::Result<(Self, Robot)> {
let path = path.as_ref().canonicalize()?;
let mesh_dir = mesh_dir
.or_else(|| path.parent())
.unwrap_or_else(|| Path::new("./"));
let robot = urdf_rs::read_file(&path)?;
Ok((Self::from_robot(&robot, options, mesh_dir), robot))
}
pub fn from_str(
str: &str,
options: UrdfLoaderOptions,
mesh_dir: &Path,
) -> anyhow::Result<(Self, Robot)> {
let robot = urdf_rs::read_from_string(str)?;
Ok((Self::from_robot(&robot, options, mesh_dir), robot))
}
pub fn from_robot(robot: &Robot, options: UrdfLoaderOptions, mesh_dir: &Path) -> Self {
let mut name_to_link_id = HashMap::new();
let mut link_is_root = vec![true; robot.links.len()];
let mut links: Vec<_> = robot
.links
.iter()
.enumerate()
.map(|(id, link)| {
name_to_link_id.insert(&link.name, id);
let mut colliders = vec![];
if options.create_colliders_from_collision_shapes {
colliders.extend(link.collision.iter().flat_map(|co| {
urdf_to_colliders(&options, mesh_dir, &co.geometry, &co.origin)
}))
}
if options.create_colliders_from_visual_shapes {
colliders.extend(link.visual.iter().flat_map(|vis| {
urdf_to_colliders(&options, mesh_dir, &vis.geometry, &vis.origin)
}))
}
let mut body = urdf_to_rigid_body(&options, &link.inertial);
let new_pos = options.shift * body.position();
body.set_position(new_pos, false);
UrdfLink { body, colliders }
})
.collect();
let joints: Vec<_> = robot
.joints
.iter()
.map(|joint| {
let link1 = name_to_link_id[&joint.parent.link];
let link2 = name_to_link_id[&joint.child.link];
let pose1 = *links[link1].body.position();
let rb2 = &mut links[link2].body;
let joint = urdf_to_joint(&options, joint, &pose1, rb2);
link_is_root[link2] = false;
UrdfJoint {
joint,
link1,
link2,
}
})
.collect();
if options.make_roots_fixed {
for (link, is_root) in links.iter_mut().zip(link_is_root.into_iter()) {
if is_root {
link.body.set_body_type(RigidBodyType::Fixed, false)
}
}
}
Self { links, joints }
}
pub fn insert_using_impulse_joints(
self,
rigid_body_set: &mut RigidBodySet,
collider_set: &mut ColliderSet,
joint_set: &mut ImpulseJointSet,
) -> UrdfRobotHandles<ImpulseJointHandle> {
let links: Vec<_> = self
.links
.into_iter()
.map(|link| {
let body = rigid_body_set.insert(link.body);
let colliders = link
.colliders
.into_iter()
.map(|co| collider_set.insert_with_parent(co, body, rigid_body_set))
.collect();
UrdfLinkHandle { body, colliders }
})
.collect();
let joints: Vec<_> = self
.joints
.into_iter()
.map(|joint| {
let link1 = links[joint.link1].body;
let link2 = links[joint.link2].body;
let joint = joint_set.insert(link1, link2, joint.joint, false);
UrdfJointHandle {
joint,
link1,
link2,
}
})
.collect();
UrdfRobotHandles { links, joints }
}
pub fn insert_using_multibody_joints(
self,
rigid_body_set: &mut RigidBodySet,
collider_set: &mut ColliderSet,
joint_set: &mut MultibodyJointSet,
multibody_options: UrdfMultibodyOptions,
) -> UrdfRobotHandles<Option<MultibodyJointHandle>> {
let links: Vec<_> = self
.links
.into_iter()
.map(|link| {
let body = rigid_body_set.insert(link.body);
let colliders = link
.colliders
.into_iter()
.map(|co| collider_set.insert_with_parent(co, body, rigid_body_set))
.collect();
UrdfLinkHandle { body, colliders }
})
.collect();
let joints: Vec<_> = self
.joints
.into_iter()
.map(|joint| {
let link1 = links[joint.link1].body;
let link2 = links[joint.link2].body;
let joint =
if multibody_options.contains(UrdfMultibodyOptions::JOINTS_ARE_KINEMATIC) {
joint_set.insert_kinematic(link1, link2, joint.joint, false)
} else {
joint_set.insert(link1, link2, joint.joint, false)
};
if let Some(joint) = joint {
let (multibody, _) = joint_set.get_mut(joint).unwrap_or_else(|| unreachable!());
multibody.set_self_contacts_enabled(
!multibody_options.contains(UrdfMultibodyOptions::DISABLE_SELF_CONTACTS),
);
}
UrdfJointHandle {
joint,
link1,
link2,
}
})
.collect();
UrdfRobotHandles { links, joints }
}
pub fn append_transform(&mut self, transform: &Pose) {
for link in &mut self.links {
let new_pos = transform * link.body.position();
link.body.set_position(new_pos, true);
}
}
}
#[rustfmt::skip]
fn urdf_to_rigid_body(options: &UrdfLoaderOptions, inertial: &Inertial) -> RigidBody {
let origin = urdf_to_pose(&inertial.origin);
let mut builder = options.rigid_body_blueprint.clone();
builder.body_type = RigidBodyType::Dynamic;
if options.apply_imported_mass_props {
builder = builder.additional_mass_properties(MassProperties::with_inertia_matrix(
origin.translation,
inertial.mass.value as Real,
na::Matrix3::new(
inertial.inertia.ixx as Real, inertial.inertia.ixy as Real, inertial.inertia.ixz as Real,
inertial.inertia.ixy as Real, inertial.inertia.iyy as Real, inertial.inertia.iyz as Real,
inertial.inertia.ixz as Real, inertial.inertia.iyz as Real,inertial.inertia.izz as Real,
).into(),
))
}
builder.build()
}
fn urdf_to_colliders(
options: &UrdfLoaderOptions,
_mesh_dir: &Path, geometry: &Geometry,
origin: &UrdfPose,
) -> Vec<Collider> {
let mut shape_transform = Pose::IDENTITY;
let mut colliders = Vec::new();
match &geometry {
Geometry::Box { size } => {
colliders.push(SharedShape::cuboid(
size[0] as Real / 2.0,
size[1] as Real / 2.0,
size[2] as Real / 2.0,
));
}
Geometry::Cylinder { radius, length } => {
shape_transform = Pose::rotation(Vector::X * Real::frac_pi_2());
colliders.push(SharedShape::cylinder(
*length as Real / 2.0,
*radius as Real,
));
}
Geometry::Capsule { radius, length } => {
colliders.push(SharedShape::capsule_z(
*length as Real / 2.0,
*radius as Real,
));
}
Geometry::Sphere { radius } => {
colliders.push(SharedShape::ball(*radius as Real));
}
#[cfg(not(feature = "__meshloader_is_enabled"))]
Geometry::Mesh { .. } => {
log::error!(
"Mesh loading is disabled by default. Enable one of the format features (`stl`, `collada`, `wavefront`) of `rapier3d-urdf` for mesh support."
);
}
#[cfg(feature = "__meshloader_is_enabled")]
Geometry::Mesh { filename, scale } => {
let full_path = _mesh_dir.join(filename);
let scale = scale
.map(|s| Vector::new(s[0] as Real, s[1] as Real, s[2] as Real))
.unwrap_or_else(|| Vector::splat(1.0));
let Ok(loaded_mesh) = rapier3d_meshloader::load_from_path(
full_path,
&rapier3d::prelude::MeshConverter::TriMeshWithFlags(options.trimesh_flags),
scale,
) else {
return Vec::new();
};
colliders.append(
&mut loaded_mesh
.into_iter()
.filter_map(|x| x.map(|s| s.shape).ok())
.collect(),
);
}
}
colliders
.drain(..)
.map(move |shape| {
let mut builder = options.collider_blueprint.clone();
builder.shape = shape;
builder
.position(urdf_to_pose(origin) * shape_transform)
.build()
})
.collect()
}
fn urdf_to_pose(pose: &UrdfPose) -> Pose {
Pose::from_parts(
Vector::new(
pose.xyz[0] as Real,
pose.xyz[1] as Real,
pose.xyz[2] as Real,
),
Rotation::from_euler(
EulerRot::XYZ,
pose.rpy[0] as Real,
pose.rpy[1] as Real,
pose.rpy[2] as Real,
),
)
}
fn urdf_to_joint(
options: &UrdfLoaderOptions,
joint: &Joint,
pose1: &Pose,
link2: &mut RigidBody,
) -> GenericJoint {
let locked_axes = match joint.joint_type {
urdf_rs::JointType::Fixed => JointAxesMask::LOCKED_FIXED_AXES,
urdf_rs::JointType::Continuous | urdf_rs::JointType::Revolute => {
JointAxesMask::LOCKED_REVOLUTE_AXES
}
urdf_rs::JointType::Floating => JointAxesMask::empty(),
urdf_rs::JointType::Planar => JointAxesMask::ANG_AXES | JointAxesMask::LIN_X,
urdf_rs::JointType::Prismatic => JointAxesMask::LOCKED_PRISMATIC_AXES,
urdf_rs::JointType::Spherical => JointAxesMask::LOCKED_SPHERICAL_AXES,
};
let joint_to_parent = urdf_to_pose(&joint.origin);
let joint_axis = Vector::new(
joint.axis.xyz[0] as Real,
joint.axis.xyz[1] as Real,
joint.axis.xyz[2] as Real,
)
.normalize_or_zero();
link2.set_position(pose1 * joint_to_parent, false);
let mut builder = GenericJointBuilder::new(locked_axes)
.local_anchor1(joint_to_parent.translation)
.contacts_enabled(options.enable_joint_collisions);
if joint_axis != Vector::ZERO {
builder = builder
.local_axis1(joint_to_parent.rotation * joint_axis)
.local_axis2(joint_axis);
}
match joint.joint_type {
urdf_rs::JointType::Prismatic => {
builder = builder.limits(
JointAxis::LinX,
[joint.limit.lower as Real, joint.limit.upper as Real],
)
}
urdf_rs::JointType::Revolute => {
builder = builder.limits(
JointAxis::AngX,
[joint.limit.lower as Real, joint.limit.upper as Real],
)
}
_ => {}
}
builder.build()
}