use nabled_core::scalar::NabledReal;
use nabled_model::joint::{JointAxis, JointType};
use nabled_model::link::InertialSpec;
use ndarray::{Array1, Array2, ArrayView1};
pub fn from_inertial_spec<T: NabledReal>(spec: &InertialSpec<T>) -> SpatialInertia<T> {
SpatialInertia {
mass: spec.mass,
com: ndarray::arr1(&spec.com),
inertia: spec.inertia.clone(),
}
}
pub fn spatial_inertia_6x6<T: NabledReal>(spec: &InertialSpec<T>) -> Array2<T> {
let m = spec.mass;
let c = ndarray::arr1(&spec.com);
let i3 = &spec.inertia;
let cx = nabled_linalg::geometry::so3::hat(&c.view());
let mcx = cx.mapv(|v| v * m);
let top_left = i3 + &mcx.t().dot(&cx);
let top_right = mcx.t();
let bottom_left = mcx.clone();
let mut spatial = Array2::<T>::zeros((6, 6));
for i in 0..3 {
for j in 0..3 {
spatial[[i, j]] = top_left[[i, j]];
spatial[[i, j + 3]] = top_right[[i, j]];
spatial[[i + 3, j]] = bottom_left[[i, j]];
spatial[[i + 3, j + 3]] = if i == j { m } else { T::zero() };
}
}
spatial
}
pub fn spatial_inertia_apply<T: NabledReal>(
inertia: &Array2<T>,
v: &ArrayView1<'_, T>,
) -> Array1<T> {
inertia.dot(v)
}
pub fn spatial_inertia_transform<T: NabledReal>(
inertia: &Array2<T>,
adjoint: &Array2<T>,
) -> Array2<T> {
adjoint.t().dot(inertia).dot(adjoint)
}
pub fn joint_motion_subspace<T: NabledReal>(joint_type: JointType, axis: JointAxis) -> Array1<T> {
let unit = axis.unit_vector::<T>();
let mut s = Array1::<T>::zeros(6);
match joint_type {
JointType::Revolute => {
s[0] = unit[0];
s[1] = unit[1];
s[2] = unit[2];
}
JointType::Prismatic => {
s[3] = unit[0];
s[4] = unit[1];
s[5] = unit[2];
}
JointType::Fixed => {}
}
s
}
pub fn spatial_gravity<T: NabledReal>(gravity: &[T; 3]) -> Array1<T> {
let mut accel = Array1::<T>::zeros(6);
accel[3] = gravity[0];
accel[4] = gravity[1];
accel[5] = gravity[2];
accel
}
pub fn motion_cross_product<T: NabledReal>(
a: &ArrayView1<'_, T>,
b: &ArrayView1<'_, T>,
) -> Array1<T> {
nabled_linalg::geometry::twist::motion_cross(a, b)
}
pub fn force_cross_product<T: NabledReal>(
a: &ArrayView1<'_, T>,
b: &ArrayView1<'_, T>,
) -> Array1<T> {
nabled_linalg::geometry::twist::force_cross(a, b)
}
#[derive(Debug, Clone, PartialEq)]
pub struct SpatialInertia<T> {
pub mass: T,
pub com: Array1<T>,
pub inertia: Array2<T>,
}
#[cfg(test)]
mod tests {
use approx::assert_relative_eq;
use super::*;
#[test]
fn point_mass_inertia_diagonal() {
let spec = InertialSpec {
mass: 2.0_f64,
com: [0.5, 0.0, 0.0],
inertia: Array2::<f64>::zeros((3, 3)),
};
let i6 = spatial_inertia_6x6(&spec);
assert_relative_eq!(i6[[5, 5]], 2.0, epsilon = 1e-12);
assert_relative_eq!(i6[[4, 4]], 2.0, epsilon = 1e-12);
}
#[test]
fn spatial_inertia_apply_matches_hand_calc() {
let spec = InertialSpec {
mass: 1.0_f64,
com: [0.0, 0.0, 0.0],
inertia: Array2::<f64>::eye(3),
};
let i6 = spatial_inertia_6x6(&spec);
let v = ndarray::arr1(&[1.0, 0.0, 0.0, 0.0, 0.0, 0.0]);
let f = spatial_inertia_apply(&i6, &v.view());
assert_relative_eq!(f[0], 1.0, epsilon = 1e-12);
}
#[test]
fn revolute_z_subspace() {
let s = joint_motion_subspace::<f64>(JointType::Revolute, JointAxis::Z);
assert_relative_eq!(s[2], 1.0, epsilon = 1e-12);
}
}