use core::ops::Mul;
use crate::linear_algebra::{Matrix, Vector};
use crate::scalar::Numeric;
use crate::spatial::lie::SO2;
use crate::spatial::small_angle_sq;
#[derive(Debug, Clone, Copy, PartialEq)]
#[allow(clippy::upper_case_acronyms)]
pub struct SE2<T: Numeric = f64> {
rotation: SO2<T>,
translation: Vector<2, T>,
}
impl<T: Numeric> SE2<T> {
#[inline]
pub fn identity() -> Self {
SE2 {
rotation: SO2::identity(),
translation: Vector::zeros(),
}
}
#[inline]
pub fn from_parts(rotation: SO2<T>, translation: Vector<2, T>) -> Self {
SE2 {
rotation,
translation,
}
}
#[inline]
pub fn rotation(self) -> SO2<T> {
self.rotation
}
#[inline]
pub fn translation(self) -> Vector<2, T> {
self.translation
}
#[inline]
pub fn compose(self, rhs: Self) -> Self {
SE2 {
rotation: self.rotation.compose(rhs.rotation),
translation: self.rotation.act(rhs.translation) + self.translation,
}
}
#[inline]
pub fn inverse(self) -> Self {
let r_inv = self.rotation.inverse();
SE2 {
rotation: r_inv,
translation: -r_inv.act(self.translation),
}
}
#[inline]
pub fn act(self, p: Vector<2, T>) -> Vector<2, T> {
self.rotation.act(p) + self.translation
}
#[inline]
pub fn exp(xi: Vector<3, T>) -> Self {
let [vx, vy, omega] = *xi.as_array();
let theta_sq = omega * omega;
let (a, b) = if theta_sq < small_angle_sq::<T>() {
(
T::ONE - theta_sq / T::from_f64(6.0),
omega * (T::HALF - theta_sq / T::from_f64(24.0)),
)
} else {
(omega.sin() / omega, (T::ONE - omega.cos()) / omega)
};
let translation = Vector::new([a * vx - b * vy, b * vx + a * vy]);
SE2 {
rotation: SO2::exp(omega),
translation,
}
}
#[inline]
pub fn log(self) -> Vector<3, T> {
let omega = self.rotation.log();
let theta_sq = omega * omega;
let (alpha, beta) = if theta_sq < small_angle_sq::<T>() {
(T::ONE - theta_sq / T::from_f64(12.0), omega * T::HALF)
} else {
let half = omega * T::HALF;
(half * (half.cos() / half.sin()), half)
};
let [tx, ty] = *self.translation.as_array();
Vector::new([alpha * tx + beta * ty, -beta * tx + alpha * ty, omega])
}
#[inline]
pub fn adjoint(self) -> Matrix<3, 3, T> {
let (c, s) = self.rotation.cos_sin();
let [tx, ty] = *self.translation.as_array();
Matrix::new([[c, -s, ty], [s, c, -tx], [T::ZERO, T::ZERO, T::ONE]])
}
#[inline]
pub fn hat(xi: Vector<3, T>) -> Matrix<3, 3, T> {
let [vx, vy, omega] = *xi.as_array();
Matrix::new([
[T::ZERO, -omega, vx],
[omega, T::ZERO, vy],
[T::ZERO, T::ZERO, T::ZERO],
])
}
#[inline]
pub fn vee(m: Matrix<3, 3, T>) -> Vector<3, T> {
let [[_, _, m02], [m10, _, m12], _] = m.into_array();
Vector::new([m02, m12, m10])
}
#[inline]
pub fn to_matrix(self) -> Matrix<3, 3, T> {
let (c, s) = self.rotation.cos_sin();
let [tx, ty] = *self.translation.as_array();
Matrix::new([[c, -s, tx], [s, c, ty], [T::ZERO, T::ZERO, T::ONE]])
}
#[inline]
pub fn try_from_matrix(m: Matrix<3, 3, T>) -> Option<Self> {
let [[m00, m01, m02], [m10, m11, m12], _] = m.into_array();
let r = SO2::try_from_matrix(Matrix::new([[m00, m01], [m10, m11]]))?;
Some(SE2 {
rotation: r,
translation: Vector::new([m02, m12]),
})
}
#[inline]
pub fn interpolate(self, other: Self, t: T) -> Self {
self.compose(Self::exp(self.inverse().compose(other).log() * t))
}
#[inline]
pub fn left_jacobian(xi: Vector<3, T>) -> Matrix<3, 3, T> {
let [rx, ry, omega] = *xi.as_array();
let theta_sq = omega * omega;
let (a, b, p, r) = if theta_sq < small_angle_sq::<T>() {
(
T::ONE - theta_sq / T::from_f64(6.0),
omega * (T::HALF - theta_sq / T::from_f64(24.0)),
T::HALF - theta_sq / T::from_f64(24.0),
omega * (T::ONE / T::from_f64(6.0) - theta_sq / T::from_f64(120.0)),
)
} else {
let (s, c) = (omega.sin(), omega.cos());
(
s / omega,
(T::ONE - c) / omega,
(T::ONE - c) / theta_sq,
(omega - s) / theta_sq,
)
};
let qx = p * ry + r * rx;
let qy = r * ry - p * rx;
Matrix::new([[a, -b, qx], [b, a, qy], [T::ZERO, T::ZERO, T::ONE]])
}
#[inline]
pub fn right_jacobian(xi: Vector<3, T>) -> Matrix<3, 3, T> {
Self::left_jacobian(-xi)
}
#[inline]
pub fn left_jacobian_inverse(xi: Vector<3, T>) -> Matrix<3, 3, T> {
let [rx, ry, omega] = *xi.as_array();
let theta_sq = omega * omega;
let (p, r) = if theta_sq < small_angle_sq::<T>() {
(
T::HALF - theta_sq / T::from_f64(24.0),
omega * (T::ONE / T::from_f64(6.0) - theta_sq / T::from_f64(120.0)),
)
} else {
let (s, c) = (omega.sin(), omega.cos());
((T::ONE - c) / theta_sq, (omega - s) / theta_sq)
};
let qx = p * ry + r * rx;
let qy = r * ry - p * rx;
let (alpha, beta) = if theta_sq < small_angle_sq::<T>() {
(T::ONE - theta_sq / T::from_f64(12.0), omega * T::HALF)
} else {
let half = omega * T::HALF;
(half * (half.cos() / half.sin()), half)
};
let cx = -(alpha * qx + beta * qy);
let cy = -(-beta * qx + alpha * qy);
Matrix::new([
[alpha, beta, cx],
[-beta, alpha, cy],
[T::ZERO, T::ZERO, T::ONE],
])
}
#[inline]
pub fn right_jacobian_inverse(xi: Vector<3, T>) -> Matrix<3, 3, T> {
Self::left_jacobian_inverse(-xi)
}
}
impl<T: Numeric> Mul for SE2<T> {
type Output = Self;
#[inline]
fn mul(self, rhs: Self) -> Self {
self.compose(rhs)
}
}