use nalgebra::{Quaternion, Translation3, UnitQuaternion};
use std::f32::consts::PI;
use crate::math::so3;
use crate::misc::type_aliases::{Float, Iso3, Mat3, Mat4, Vec3, Vec6};
const EPSILON_TAYLOR_SERIES: Float = 1e-2;
const EPSILON_TAYLOR_SERIES_2: Float = EPSILON_TAYLOR_SERIES * EPSILON_TAYLOR_SERIES;
const _1_6: Float = 1.0 / 6.0;
const _1_8: Float = 0.125;
const _1_12: Float = 1.0 / 12.0;
const _1_15: Float = 1.0 / 15.0;
const _1_24: Float = 1.0 / 24.0;
const _1_48: Float = 1.0 / 48.0;
const _1_120: Float = 1.0 / 120.0;
pub type Twist = Vec6;
pub fn linear_velocity(xi: Twist) -> Vec3 {
Vec3::new(xi[0], xi[1], xi[2])
}
pub fn angular_velocity(xi: Twist) -> Vec3 {
Vec3::new(xi[3], xi[4], xi[5])
}
#[rustfmt::skip]
pub fn hat(xi: Twist) -> Mat4 {
let w1 = xi[3];
let w2 = xi[4];
let w3 = xi[5];
Mat4::new(
0.0, -w3, w2, xi[0],
w3, 0.0, -w1, xi[1],
-w2, w1, 0.0, xi[2],
0.0, 0.0, 0.0, 0.0,
)
}
pub fn vee(mat: Mat4) -> Twist {
Vec6::new(mat.m14, mat.m24, mat.m34, mat.m32, mat.m13, mat.m21)
}
pub fn exp(xi: Twist) -> Iso3 {
let xi_v = linear_velocity(xi);
let xi_w = angular_velocity(xi);
let theta_2 = xi_w.norm_squared();
let (omega, omega_2) = (so3::hat(xi_w), so3::hat_2(xi_w));
if theta_2 < EPSILON_TAYLOR_SERIES_2 {
let real_factor = 1.0 - _1_8 * theta_2; let imag_factor = 0.5 - _1_48 * theta_2; let coef_omega = 0.5 - _1_24 * theta_2; let coef_omega_2 = _1_6 - _1_120 * theta_2; let v = Mat3::identity() + coef_omega * omega + coef_omega_2 * omega_2;
let rotation = UnitQuaternion::from_quaternion(Quaternion::from_parts(
real_factor,
imag_factor * xi_w,
));
Iso3::from_parts(Translation3::from(v * xi_v), rotation)
} else {
let theta = theta_2.sqrt();
let half_theta = 0.5 * theta;
let real_factor = half_theta.cos();
let imag_factor = half_theta.sin() / theta;
let coef_omega = (1.0 - theta.cos()) / theta_2;
let coef_omega_2 = (theta - theta.sin()) / (theta * theta_2);
let v = Mat3::identity() + coef_omega * omega + coef_omega_2 * omega_2;
let rotation = UnitQuaternion::from_quaternion(Quaternion::from_parts(
real_factor,
imag_factor * xi_w,
));
Iso3::from_parts(Translation3::from(v * xi_v), rotation)
}
}
pub fn log(iso: Iso3) -> Twist {
let imag_vector = iso.rotation.vector();
let imag_norm_2 = imag_vector.norm_squared();
let real_factor = iso.rotation.scalar();
if imag_norm_2 < EPSILON_TAYLOR_SERIES_2 {
let theta_by_imag_norm = 2.0 / real_factor; let w = theta_by_imag_norm * imag_vector;
let (omega, omega_2) = (so3::hat(w), so3::hat_2(w));
let x_2 = imag_norm_2 / (real_factor * real_factor);
let coef_omega_2 = _1_12 * (1.0 + _1_15 * x_2); let v_inv = Mat3::identity() - 0.5 * omega + coef_omega_2 * omega_2;
let xi_v = v_inv * iso.translation.vector;
Vec6::new(xi_v[0], xi_v[1], xi_v[2], w[0], w[1], w[2])
} else {
let imag_norm = imag_norm_2.sqrt();
let theta = if real_factor.abs() < EPSILON_TAYLOR_SERIES {
let alpha = real_factor.abs() / imag_norm;
real_factor.signum() * (PI - 2.0 * alpha) } else {
2.0 * (imag_norm / real_factor).atan()
};
let theta_2 = theta * theta;
let w = (theta / imag_norm) * imag_vector;
let (omega, omega_2) = (so3::hat(w), so3::hat_2(w));
let coef_omega_2 = (1.0 - 0.5 * theta * real_factor / imag_norm) / theta_2;
let v_inv = Mat3::identity() - 0.5 * omega + coef_omega_2 * omega_2;
let xi_v = v_inv * iso.translation.vector;
Vec6::new(xi_v[0], xi_v[1], xi_v[2], w[0], w[1], w[2])
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx;
use quickcheck_macros;
const EPSILON_ROUNDTRIP_APPROX: Float = 1e-4;
#[test]
fn exp_log_round_trip() {
let xi = Vec6::zeros();
assert_eq!(xi, log(exp(xi)));
}
#[quickcheck_macros::quickcheck]
fn hat_vee_roundtrip(v1: Float, v2: Float, v3: Float, w1: Float, w2: Float, w3: Float) -> bool {
let xi = Vec6::new(v1, v2, v3, w1, w2, w3);
xi == vee(hat(xi))
}
#[quickcheck_macros::quickcheck]
fn log_exp_round_trip(
t1: Float,
t2: Float,
t3: Float,
a1: Float,
a2: Float,
a3: Float,
) -> bool {
let rigid_motion = gen_rigid_motion(t1, t2, t3, a1, a2, a3);
approx::relative_eq!(
rigid_motion,
exp(log(rigid_motion)),
epsilon = EPSILON_ROUNDTRIP_APPROX
)
}
fn gen_rigid_motion(t1: Float, t2: Float, t3: Float, a1: Float, a2: Float, a3: Float) -> Iso3 {
let translation = Translation3::from(Vec3::new(t1, t2, t3));
let rotation = UnitQuaternion::from_euler_angles(a1, a2, a3);
Iso3::from_parts(translation, rotation)
}
}