use super::{FloatingRobot, LinkId, LoadBuffer, Robot, RootMode, Workspace, generalized_count};
use crate::{
Error, Frame, JointType, Result,
model::{Joint, JointKinematics, Link, LinkDynamics, Tree, load_urdf},
};
use nalgebra::{Matrix3, Vector3};
use std::{
path::Path,
sync::{
Arc,
atomic::{AtomicU64, Ordering},
},
};
const UNOWNED_MODEL_ID: u64 = 0;
static NEXT_MODEL_ID: AtomicU64 = AtomicU64::new(1);
#[derive(Debug)]
pub(super) struct Model {
pub(super) model_id: u64,
pub(super) name: String,
pub(super) joints: Box<[Joint]>,
pub(super) links: Box<[Link]>,
pub(super) joint_kinematics: Box<[JointKinematics]>,
pub(super) link_dynamics: Box<[LinkDynamics]>,
pub(super) active_joint_indices: Box<[usize]>,
pub(super) joint_dof_indices: Box<[Option<usize>]>,
pub(super) parent_link_indices: Box<[usize]>,
}
impl Model {
fn from_tree(tree: Tree) -> Self {
let model_id = NEXT_MODEL_ID.fetch_add(1, Ordering::Relaxed);
assert_ne!(
model_id, UNOWNED_MODEL_ID,
"robot model identifier overflow"
);
let joint_kinematics: Box<[_]> = tree.joints.iter().map(Joint::kinematics).collect();
let link_dynamics: Box<[_]> = tree.links.iter().map(Link::dynamics).collect();
let active_joint_indices: Box<[_]> = tree
.joints
.iter()
.enumerate()
.filter_map(|(index, joint)| (joint.joint_type() != JointType::Fixed).then_some(index))
.collect();
let mut joint_dof_indices = vec![None; tree.joints.len()];
for (dof_index, &joint_index) in active_joint_indices.iter().enumerate() {
joint_dof_indices[joint_index] = Some(dof_index);
}
Self {
model_id,
name: tree.name,
joints: tree.joints.into_boxed_slice(),
links: tree.links.into_boxed_slice(),
joint_kinematics,
link_dynamics,
active_joint_indices,
joint_dof_indices: joint_dof_indices.into_boxed_slice(),
parent_link_indices: tree.parent_link_indices.into_boxed_slice(),
}
}
pub(super) fn link_count(&self) -> usize {
self.links.len()
}
pub(super) fn joint_count(&self) -> usize {
self.active_joint_indices.len()
}
pub(super) fn model_joint_count(&self) -> usize {
self.joints.len()
}
pub(super) fn active_joint(&self, dof_index: usize) -> Result<&Joint> {
let &joint_index = self
.active_joint_indices
.get(dof_index)
.ok_or(Error::InvalidJointIndex { index: dof_index })?;
Ok(&self.joints[joint_index])
}
pub(super) fn link_by_id(&self, link: LinkId) -> Result<&Link> {
let index = self.validate_link_id(link)?;
Ok(&self.links[index])
}
#[inline]
pub(super) fn joint_value(&self, values: &[f64], joint_index: usize) -> f64 {
self.joint_dof_indices[joint_index].map_or(0.0, |dof_index| values[dof_index])
}
pub(super) fn validate_slice(&self, name: &'static str, slice: &[f64]) -> Result<()> {
self.validate_slice_length(name, slice.len(), self.joint_count())?;
if slice.iter().all(|value| value.is_finite()) {
Ok(())
} else {
Err(Error::NonFiniteInput { input: name })
}
}
pub(super) fn validate_output(
&self,
base_mode: RootMode,
name: &'static str,
output: &[f64],
) -> Result<()> {
self.validate_slice_length(name, output.len(), generalized_count(self, base_mode))
}
pub(super) fn validate_joint_output(&self, name: &'static str, output: &[f64]) -> Result<()> {
self.validate_slice_length(name, output.len(), self.joint_count())
}
pub(super) fn validate_slice_length(
&self,
name: &'static str,
actual: usize,
expected: usize,
) -> Result<()> {
if actual == expected {
Ok(())
} else {
Err(Error::WrongSliceLength {
slice: name,
expected,
actual,
})
}
}
pub(super) fn validate_link_id(&self, link: LinkId) -> Result<usize> {
if link.model_id == self.model_id && link.index < self.links.len() {
Ok(link.index)
} else {
Err(Error::InvalidLinkId)
}
}
}
fn load_model(path: impl AsRef<Path>) -> Result<Arc<Model>> {
Ok(Arc::new(Model::from_tree(load_urdf(path)?)))
}
fn validate_floating_model(model: &Model) -> Result<()> {
let root = model
.links
.first()
.expect("validated robot tree has one root link");
if root.mass() <= 0.0 {
return Err(Error::InvalidModel(format!(
"floating-base root link {} must have positive mass",
root.name()
)));
}
Ok(())
}
#[derive(Clone, Debug)]
pub struct RobotModel {
pub(super) model: Arc<Model>,
}
impl RobotModel {
pub fn from_urdf(path: impl AsRef<Path>) -> Result<Self> {
Ok(Self {
model: load_model(path)?,
})
}
pub fn robot(&self) -> Robot {
Robot {
model: Arc::clone(&self.model),
workspace: Workspace::new(&self.model),
world_from_root: Frame::identity(),
}
}
pub fn validate_floating_base(&self) -> Result<()> {
validate_floating_model(&self.model)
}
pub fn floating_robot(&self) -> Result<FloatingRobot> {
self.validate_floating_base()?;
Ok(FloatingRobot {
model: Arc::clone(&self.model),
workspace: Workspace::new(&self.model),
})
}
pub fn load_buffer(&self) -> LoadBuffer {
LoadBuffer::new(&self.model)
}
}
macro_rules! model_queries {
($handle:ty) => {
impl $handle {
pub fn name(&self) -> &str {
&self.model.name
}
pub fn link_id(&self, name: &str) -> Result<LinkId> {
self.model
.links
.iter()
.position(|link| link.name() == name)
.map(|index| LinkId::new(self.model.model_id, index))
.ok_or_else(|| Error::UnknownLink {
name: name.to_owned(),
})
}
pub fn link_id_at(&self, index: usize) -> Result<LinkId> {
if index < self.model.link_count() {
Ok(LinkId::new(self.model.model_id, index))
} else {
Err(Error::InvalidLinkId)
}
}
pub fn link_count(&self) -> usize {
self.model.link_count()
}
pub fn joint_count(&self) -> usize {
self.model.joint_count()
}
pub fn joint_name(&self, dof_index: usize) -> Result<&str> {
Ok(self.model.active_joint(dof_index)?.name())
}
pub fn joint_type(&self, dof_index: usize) -> Result<JointType> {
Ok(self.model.active_joint(dof_index)?.joint_type())
}
pub fn joint_lower_limit(&self, dof_index: usize) -> Result<f64> {
Ok(self.model.active_joint(dof_index)?.lower_limit())
}
pub fn joint_upper_limit(&self, dof_index: usize) -> Result<f64> {
Ok(self.model.active_joint(dof_index)?.upper_limit())
}
pub fn joint_velocity_limit(&self, dof_index: usize) -> Result<f64> {
Ok(self.model.active_joint(dof_index)?.velocity_limit())
}
pub fn root_link_id(&self) -> LinkId {
LinkId::new(self.model.model_id, 0)
}
pub fn link_name(&self, link: LinkId) -> Result<&str> {
Ok(self.model.link_by_id(link)?.name())
}
pub fn link_mass(&self, link: LinkId) -> Result<f64> {
Ok(self.model.link_by_id(link)?.mass())
}
pub fn link_center_of_mass(&self, link: LinkId) -> Result<Vector3<f64>> {
Ok(*self.model.link_by_id(link)?.center_of_mass())
}
pub fn link_inertia(&self, link: LinkId) -> Result<Matrix3<f64>> {
Ok(*self.model.link_by_id(link)?.inertia())
}
}
};
}
model_queries!(RobotModel);
model_queries!(Robot);
model_queries!(FloatingRobot);