rapier3d/dynamics/joint/multibody_joint/
multibody_ik.rsuse crate::dynamics::{JointAxesMask, Multibody, MultibodyLink, RigidBodySet};
use crate::math::{Isometry, Jacobian, Real, ANG_DIM, DIM, SPATIAL_DIM};
use na::{self, DVector, SMatrix};
use parry::math::SpacialVector;
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct InverseKinematicsOption {
pub damping: Real,
pub max_iters: usize,
pub constrained_axes: JointAxesMask,
pub epsilon_linear: Real,
pub epsilon_angular: Real,
}
impl Default for InverseKinematicsOption {
fn default() -> Self {
Self {
damping: 1.0,
max_iters: 10,
constrained_axes: JointAxesMask::all(),
epsilon_linear: 1.0e-3,
epsilon_angular: 1.0e-3,
}
}
}
impl Multibody {
pub fn inverse_kinematics_delta(
&self,
link_id: usize,
desired_movement: &SpacialVector<Real>,
damping: Real,
displacements: &mut DVector<Real>,
) {
let body_jacobian = self.body_jacobian(link_id);
Self::inverse_kinematics_delta_with_jacobian(
body_jacobian,
desired_movement,
damping,
displacements,
);
}
#[profiling::function]
pub fn inverse_kinematics_delta_with_jacobian(
jacobian: &Jacobian<Real>,
desired_movement: &SpacialVector<Real>,
damping: Real,
displacements: &mut DVector<Real>,
) {
let identity = SMatrix::<Real, SPATIAL_DIM, SPATIAL_DIM>::identity();
let jj = jacobian * &jacobian.transpose() + identity * (damping * damping);
let inv_jj = jj.pseudo_inverse(1.0e-5).unwrap_or(identity);
displacements.gemv_tr(1.0, jacobian, &(inv_jj * desired_movement), 1.0);
}
#[profiling::function]
pub fn inverse_kinematics(
&self,
bodies: &RigidBodySet,
link_id: usize,
options: &InverseKinematicsOption,
target_pose: &Isometry<Real>,
joint_can_move: impl Fn(&MultibodyLink) -> bool,
displacements: &mut DVector<Real>,
) {
let mut jacobian = Jacobian::zeros(0);
let branch = self.kinematic_branch(link_id);
let can_move: Vec<_> = branch
.iter()
.map(|id| joint_can_move(&self.links[*id]))
.collect();
for _ in 0..options.max_iters {
let pose = self.forward_kinematics_single_branch(
bodies,
&branch,
Some(displacements.as_slice()),
Some(&mut jacobian),
);
for (id, can_move) in branch.iter().zip(can_move.iter()) {
if !*can_move {
let link = &self.links[*id];
jacobian
.columns_mut(link.assembly_id, link.joint.ndofs())
.fill(0.0);
}
}
let delta_lin = target_pose.translation.vector - pose.translation.vector;
let delta_ang = (target_pose.rotation * pose.rotation.inverse()).scaled_axis();
#[cfg(feature = "dim2")]
let mut delta = na::vector![delta_lin.x, delta_lin.y, delta_ang.x];
#[cfg(feature = "dim3")]
let mut delta = na::vector![
delta_lin.x,
delta_lin.y,
delta_lin.z,
delta_ang.x,
delta_ang.y,
delta_ang.z
];
if !options.constrained_axes.contains(JointAxesMask::LIN_X) {
delta[0] = 0.0;
}
if !options.constrained_axes.contains(JointAxesMask::LIN_Y) {
delta[1] = 0.0;
}
#[cfg(feature = "dim3")]
if !options.constrained_axes.contains(JointAxesMask::LIN_Z) {
delta[2] = 0.0;
}
if !options.constrained_axes.contains(JointAxesMask::ANG_X) {
delta[DIM] = 0.0;
}
#[cfg(feature = "dim3")]
if !options.constrained_axes.contains(JointAxesMask::ANG_Y) {
delta[DIM + 1] = 0.0;
}
#[cfg(feature = "dim3")]
if !options.constrained_axes.contains(JointAxesMask::ANG_Z) {
delta[DIM + 2] = 0.0;
}
if delta.rows(0, DIM).norm() <= options.epsilon_linear
&& delta.rows(DIM, ANG_DIM).norm() <= options.epsilon_angular
{
break;
}
Self::inverse_kinematics_delta_with_jacobian(
&jacobian,
&delta,
options.damping,
displacements,
);
}
}
}
#[cfg(test)]
mod test {
use crate::dynamics::{
MultibodyJointHandle, MultibodyJointSet, RevoluteJointBuilder, RigidBodyBuilder,
RigidBodySet,
};
use crate::math::{Jacobian, Real, Vector};
use approx::assert_relative_eq;
#[test]
fn one_link_fwd_kinematics() {
let mut bodies = RigidBodySet::new();
let mut multibodies = MultibodyJointSet::new();
let num_segments = 10;
let body = RigidBodyBuilder::fixed();
let mut last_body = bodies.insert(body);
let mut last_link = MultibodyJointHandle::invalid();
for _ in 0..num_segments {
let body = RigidBodyBuilder::dynamic().can_sleep(false);
let new_body = bodies.insert(body);
#[cfg(feature = "dim2")]
let builder = RevoluteJointBuilder::new();
#[cfg(feature = "dim3")]
let builder = RevoluteJointBuilder::new(Vector::z_axis());
let link_ab = builder
.local_anchor1((Vector::y() * (0.5 / num_segments as Real)).into())
.local_anchor2((Vector::y() * (-0.5 / num_segments as Real)).into());
last_link = multibodies
.insert(last_body, new_body, link_ab, true)
.unwrap();
last_body = new_body;
}
let (multibody, last_id) = multibodies.get_mut(last_link).unwrap();
multibody.forward_kinematics(&bodies, true); assert_eq!(multibody.ndofs(), num_segments);
let mut jacobian2 = Jacobian::zeros(0);
let link_pose1 = *multibody.link(last_id).unwrap().local_to_world();
let jacobian1 = multibody.body_jacobian(last_id);
let link_pose2 =
multibody.forward_kinematics_single_link(&bodies, last_id, None, Some(&mut jacobian2));
assert_eq!(link_pose1, link_pose2);
assert_eq!(jacobian1, &jacobian2);
let niter = 100;
let displacement_part: Vec<_> = (0..multibody.ndofs())
.map(|i| i as Real * -0.1 / niter as Real)
.collect();
let displacement_total: Vec<_> = displacement_part
.iter()
.map(|d| *d * niter as Real)
.collect();
let link_pose2 = multibody.forward_kinematics_single_link(
&bodies,
last_id,
Some(&displacement_total),
Some(&mut jacobian2),
);
for _ in 0..niter {
multibody.apply_displacements(&displacement_part);
multibody.forward_kinematics(&bodies, false);
}
let link_pose1 = *multibody.link(last_id).unwrap().local_to_world();
let jacobian1 = multibody.body_jacobian(last_id);
assert_relative_eq!(link_pose1, link_pose2, epsilon = 1.0e-5);
assert_relative_eq!(jacobian1, &jacobian2, epsilon = 1.0e-5);
}
}