use crate::domains::matrix::Matrix;
use crate::prelude::*;
use crate::units::si::{Angle, Length};
pub fn dh_matrix(theta: &Ex, d: &Ex, a: &Ex, alpha: &Ex) -> Matrix {
let cos_theta = theta.cos();
let sin_theta = theta.sin();
let cos_alpha = alpha.cos();
let sin_alpha = alpha.sin();
let zero = theta.context().int(0);
let one = theta.context().int(1);
let r00 = cos_theta.clone();
let r01 = -(&sin_theta * &cos_alpha);
let r02 = &sin_theta * &sin_alpha;
let r03 = a * &cos_theta;
let r10 = sin_theta.clone();
let r11 = &cos_theta * &cos_alpha;
let r12 = -(&cos_theta * &sin_alpha);
let r13 = a * &sin_theta;
let r20 = zero.clone();
let r21 = sin_alpha;
let r22 = cos_alpha;
let r23 = d.clone();
let r30 = zero.clone();
let r31 = zero.clone();
let r32 = zero;
let r33 = one;
Matrix::from_rows_unchecked(vec![
vec![r00, r01, r02, r03],
vec![r10, r11, r12, r13],
vec![r20, r21, r22, r23],
vec![r30, r31, r32, r33],
])
}
fn mul_square(a: &Matrix, b: &Matrix) -> Matrix {
let n = a.nrows();
Matrix::from_fn_unchecked(n, n, |i, j| {
let mut acc = a.get(i, 0) * b.get(0, j);
for k in 1..n {
acc += a.get(i, k) * b.get(k, j);
}
acc
})
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct DhLink<'a> {
pub theta: &'a Ex,
pub d: &'a Ex,
pub a: &'a Ex,
pub alpha: &'a Ex,
}
pub fn fk_chain(links: &[DhLink<'_>]) -> Matrix {
let ctx = if let Some(first) = links.first() {
first.theta.context()
} else {
crate::api::context::Context::new()
};
let mut result = Matrix::identity_unchecked(&ctx, 4);
for link in links {
result = mul_square(&result, &dh_matrix(link.theta, link.d, link.a, link.alpha));
}
result
}
pub fn fk_position(links: &[DhLink<'_>]) -> (Ex, Ex, Ex) {
let t = fk_chain(links);
let px = t.get(0, 3).clone().eval();
let py = t.get(1, 3).clone().eval();
let pz = t.get(2, 3).clone().eval();
(px, py, pz)
}
pub fn fk_rotation(links: &[DhLink<'_>]) -> Matrix {
let t = fk_chain(links);
Matrix::from_fn_unchecked(3, 3, |i, j| t.get(i, j).clone())
}
pub fn rot_x(theta: &Ex) -> Matrix {
let zero = theta.context().int(0);
let one = theta.context().int(1);
let c = theta.cos();
let s = theta.sin();
Matrix::from_rows_unchecked(vec![
vec![one, zero.clone(), zero.clone()],
vec![zero.clone(), c.clone(), -&s],
vec![zero, s, c],
])
}
pub fn rot_y(theta: &Ex) -> Matrix {
let zero = theta.context().int(0);
let one = theta.context().int(1);
let c = theta.cos();
let s = theta.sin();
Matrix::from_rows_unchecked(vec![
vec![c.clone(), zero.clone(), s.clone()],
vec![zero.clone(), one, zero.clone()],
vec![-&s, zero, c],
])
}
pub fn rot_z(theta: &Ex) -> Matrix {
let zero = theta.context().int(0);
let one = theta.context().int(1);
let c = theta.cos();
let s = theta.sin();
Matrix::from_rows_unchecked(vec![
vec![c.clone(), -&s, zero.clone()],
vec![s, c, zero.clone()],
vec![zero.clone(), zero, one],
])
}
pub fn skew3(a: &Ex, b: &Ex, c: &Ex) -> Matrix {
let zero = a.context().int(0);
Matrix::from_rows_unchecked(vec![
vec![zero.clone(), -c, b.clone()],
vec![c.clone(), zero.clone(), -a],
vec![-b, a.clone(), zero],
])
}
pub fn homogeneous(rotation: &Matrix, position: &[Ex; 3]) -> Result<Matrix, SymplexError> {
if rotation.shape() != (3, 3) {
return Err(SymplexError::InvalidArgument {
operation: "robotics::homogeneous",
reason: format!(
"rotation must be 3×3, got {}×{}",
rotation.nrows(),
rotation.ncols()
),
});
}
let zero = position[0].context().int(0);
let one = position[0].context().int(1);
Ok(Matrix::from_rows_unchecked(vec![
vec![
rotation.get(0, 0).clone(),
rotation.get(0, 1).clone(),
rotation.get(0, 2).clone(),
position[0].clone(),
],
vec![
rotation.get(1, 0).clone(),
rotation.get(1, 1).clone(),
rotation.get(1, 2).clone(),
position[1].clone(),
],
vec![
rotation.get(2, 0).clone(),
rotation.get(2, 1).clone(),
rotation.get(2, 2).clone(),
position[2].clone(),
],
vec![zero.clone(), zero.clone(), zero, one],
]))
}
pub fn translation(x: &Ex, y: &Ex, z: &Ex) -> Matrix {
let zero = x.context().int(0);
let one = x.context().int(1);
Matrix::from_rows_unchecked(vec![
vec![one.clone(), zero.clone(), zero.clone(), x.clone()],
vec![zero.clone(), one.clone(), zero.clone(), y.clone()],
vec![zero.clone(), zero.clone(), one, z.clone()],
vec![zero.clone(), zero.clone(), zero, x.context().int(1)],
])
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EulerConvention {
ZYX,
ZXZ,
XYZ,
}
pub fn rot_euler(phi: &Ex, theta: &Ex, psi: &Ex, convention: EulerConvention) -> Matrix {
match convention {
EulerConvention::ZYX => mul_square(&mul_square(&rot_z(phi), &rot_y(theta)), &rot_x(psi)),
EulerConvention::ZXZ => mul_square(&mul_square(&rot_z(phi), &rot_x(theta)), &rot_z(psi)),
EulerConvention::XYZ => mul_square(&mul_square(&rot_x(phi), &rot_y(theta)), &rot_z(psi)),
}
}
const IK_TOLERANCE_ULPS: f64 = 64.0;
fn ik_equal(a: f64, b: f64, scale: f64) -> bool {
(a - b).abs() <= IK_TOLERANCE_ULPS * f64::EPSILON * scale
}
pub fn inverse_kinematics_2dof(l1: f64, l2: f64, target_x: f64, target_y: f64) -> Vec<(f64, f64)> {
if ![l1, l2, target_x, target_y].iter().all(|v| v.is_finite()) {
return vec![];
}
let scale = [l1, l2, target_x, target_y]
.iter()
.fold(0.0_f64, |m, v| m.max(v.abs()));
if scale == 0.0 {
return vec![(0.0, 0.0)];
}
let (l1, l2, x, y) = (l1 / scale, l2 / scale, target_x / scale, target_y / scale);
let r2 = x * x + y * y;
let mut angles: Vec<(f64, f64)> = Vec::new();
let direction = |l: f64| (l.signum() * y).atan2(l.signum() * x);
if l1 == 0.0 || l2 == 0.0 {
let l = if l1 == 0.0 { l2 } else { l1 };
if l == 0.0 {
return vec![];
}
if !ik_equal(r2, l * l, r2 + l * l) {
return vec![];
}
let phi = direction(l);
return vec![if l1 == 0.0 { (0.0, phi) } else { (phi, 0.0) }];
}
let num = r2 - l1 * l1 - l2 * l2;
let den = 2.0 * l1 * l2;
let mag = r2 + l1 * l1 + l2 * l2;
let cos_t2 = if ik_equal(num, den.abs(), mag) {
den.signum()
} else if ik_equal(num, -den.abs(), mag) {
-den.signum()
} else if num.abs() > den.abs() {
return vec![];
} else {
num / den
};
if cos_t2.abs() == 1.0 {
let t2 = if cos_t2 > 0.0 {
0.0
} else {
std::f64::consts::PI
};
let k1 = l1 + l2 * cos_t2;
if ik_equal(k1, 0.0, l1.abs() + l2.abs()) {
angles.push((0.0, t2));
} else {
angles.push((direction(k1), t2));
}
} else {
let sin_abs = (1.0 - cos_t2 * cos_t2).sqrt();
for sin_t2 in [sin_abs, -sin_abs] {
let (k1, k2) = (l1 + l2 * cos_t2, l2 * sin_t2);
let t1 = (k1 * y - k2 * x).atan2(k1 * x + k2 * y);
angles.push((t1, sin_t2.atan2(cos_t2)));
}
}
angles.sort_by(|a, b| a.0.total_cmp(&b.0).then(a.1.total_cmp(&b.1)));
angles
}
#[derive(Clone, Copy, Debug)]
pub struct DhParams<'a> {
pub theta: &'a Angle,
pub d: &'a Length,
pub a: &'a Length,
pub alpha: &'a Angle,
}
impl<'a> From<DhParams<'a>> for DhLink<'a> {
fn from(params: DhParams<'a>) -> Self {
DhLink {
theta: params.theta.inner(),
d: params.d.inner(),
a: params.a.inner(),
alpha: params.alpha.inner(),
}
}
}
fn untyped_links<'a>(dh_params: &[DhParams<'a>]) -> Vec<DhLink<'a>> {
dh_params.iter().map(|&p| DhLink::from(p)).collect()
}
pub fn fk_position_typed(dh_params: &[DhParams<'_>]) -> (Length, Length, Length) {
let (px, py, pz) = fk_position(&untyped_links(dh_params));
(
Length::from_ex(px),
Length::from_ex(py),
Length::from_ex(pz),
)
}
pub fn fk_chain_typed(dh_params: &[DhParams<'_>]) -> Matrix {
fk_chain(&untyped_links(dh_params))
}
pub fn fk_rotation_typed(dh_params: &[DhParams<'_>]) -> Matrix {
fk_rotation(&untyped_links(dh_params))
}